| name | create-cli-command |
| description | 将前端 API 能力封装成 agentbay-cli 命令的标准化流程 |
Create CLI Command
🔗 前置约束(必读)
本 skill 必须与 feature-development-workflow 配套使用,两者分工如下:
| Skill | 负责 | 触发时机 |
|---|
| feature-development-workflow | 分支管理(从 aliyun/master 拉 feat 分支)、双远程推送(origin → aliyun)、PR 流程、变更档案 | 开发前、提交后 |
| create-cli-command(本文件) | SDK 模型 → Client 接口 → Cobra 命令 → mock 同步 → 单测 → 对客文档 readiness | 开发中 |
执行铁律:
- 开发前:必须先执行
feature-development-workflow 的 Phase 0(变更档案初始化)和分支创建,确认当前在 feat-<name> 分支且基于 aliyun/master,否则不得进入本 skill 的 Phase 1。⚠️ 切分支前必须先询问用户:用户可能已在当前分支开发,不要自动切换 feat 分支。
- 开发中:按本 skill 的 Phase 1-5 实现代码和测试。
- 提交/推送:切回
feature-development-workflow 的 Phase 4-6 完成 commit、双远程 push(origin 先、aliyun 后)、PR、trace.md 更新。
- 禁止跳过:不得在未拉 feat 分支时直接在 master 上开发,不得单远程推送,不得跳过变更档案。
若用户未提前走 feature-development-workflow,本 skill 执行第一步必须主动提醒并引导用户先建档、拉分支。
📋 职责
将 agent-bay 前端控制台中的 API 能力封装成 agentbay-cli 命令行工具,包括:
- 创建 CLI 命令(使用 Cobra 框架)
- 实现 SDK 客户端方法
- 编写单元测试
- 生成对客文档
🎯 触发场景
当用户提出以下需求时触发:
- "帮我把 XX 接口封装成 CLI"
- "新增 XX 功能的命令行工具"
- "把前端的 XX 能力做成 CLI 命令"
- "新增 agentbay xx 命令"
🚀 执行步骤
Phase 1: 需求分析
-
查找前端 API 实现
- 在
agent-bay/src/http/api/ 目录下搜索接口
- 确认接口的 Action、Version、Product ID
- 分析请求参数和响应结构
-
确认 API 信息
- Product ID:
xiaoying(CLI 使用,不是前端的 xiaoying-double-centre)
- API Version:
2025-05-01
- Endpoint: 根据环境自动选择
-
与用户确认
- 命令名称和层级结构
- 参数设计(使用命名参数
--name 而非位置参数)
- 是否需要子命令
Phase 2: 代码实现
2.1 创建 SDK 层模型
internal/client/
├── {action}_request_model.go # 请求模型
└── {action}_response_model.go # 响应模型
要求:
- 请求模型必须有
Validate() 方法
- 响应模型提供
GetXxx() 辅助方法
- 注意后端实际返回的字段类型(可能是字符串而非对象)
2.2 添加 SDK 客户端方法
在 internal/client/client.go 中添加:
{Action}WithOptions() - 完整调用方法
{Action}() - 简化调用方法
{Action}WithContext() - 支持 context 的方法
parse{Action}Response() - 响应解析函数
⚠️ parser 必须放入 internal/client/dual_format_responses.go(而非 client.go),且必须遵守下述容错规范:
- 所有
*int32 / *int64 字段用 json.RawMessage + int32FromFlexibleJSON 解析,兼容数字与字符串两种序列化形式(服务端常会以字符串返回 HttpStatusCode 等数字字段)。
- body 以
< 开头走 XML 分支、否则走 JSON 分支,两条路径都要调用 applyMapHeadersAndStatus 归一 headers / statusCode。
- 解析失败统一用
&ErrWithRequestID{Err: ..., RequestID: extractRequestIDFromResponse(res)} 包装。
- 必须在
internal/client/ 下配套一个 xxx_parse_test.go,至少覆盖「JSON 数字字段为字符串 / 数字 / XML」三种场景。
反面案例:BatchCreateHideResourceGroupsWithMaxSession 早期直接用 json.Unmarshal 打到 *int32,遇到 "HttpStatusCode":"200" 直接报 cannot unmarshal string into Go struct field ... of type int32。详见 references/api-format.md 与规则 development.md 响应解析必须使用容错模板。
API 配置模板:
params := &openapiutil.Params{
Action: dara.String("ActionName"),
Version: dara.String("2025-05-01"),
Protocol: dara.String("HTTPS"),
Pathname: dara.String("/"),
Method: dara.String("POST"),
AuthType: dara.String("AK"),
Style: dara.String("RPC"),
ReqBodyType: dara.String("formData"),
BodyType: dara.String("string"),
}
2.3 添加客户端接口
在 internal/agentbay/client.go 中:
- 在
Client interface 中添加方法定义
- 在
clientWrapper 中实现方法
⚠️ 重要:更新接口后必须同步更新所有 Mock 类!
grep -r "type mock.*Client struct" cmd/ test/
func (m *mockClient) NewMethod(ctx context.Context, request *client.NewRequest) (*client.NewResponse, error) {
return nil, fmt.Errorf("not implemented")
}
检查清单:
2.4 创建 CLI 命令
在 cmd/ 目录下创建命令文件:
- 使用命名参数(
--name, --api-key-id)
- 标记必填参数:
MarkFlagRequired()
- 提供清晰的帮助信息和示例
- 实现错误处理和友好提示
- 每个对外 API 请求必须默认打印 RequestId(详见下方铁律)
🔑 RequestId 打印铁律(强制)
规则: 每个 CLI 命令调用的每一个对外接口请求,无论成功还是失败、无论是否带 -v / --verbose,都必须在终端打印对应的 RequestId,便于客户在出现问题时直接复制日志给运维定位。
❌ 禁止做法(之前的旧规范):
verbose, _ := cmd.Flags().GetBool("verbose")
if verbose && resp.Body.RequestId != nil && *resp.Body.RequestId != "" {
printRequestIDIfVerbose(cmd, *resp.Body.RequestId)
}
✅ 正确做法:
if resp != nil && resp.Body != nil {
if reqId := resp.Body.GetRequestId(); reqId != nil && *reqId != "" {
fmt.Printf("[INFO] {Action} Request ID: %s\n", *reqId)
}
}
resp, err := apiClient.{Action}(ctx, req)
if err != nil {
if reqId := extractRequestIDFromErr(err); reqId != "" {
fmt.Printf("[INFO] {Action} Request ID: %s\n", reqId)
}
return fmt.Errorf("[ERROR] Failed to {action}: %w", err)
}
多接口命令: 命令体内若调用多个接口(如先 GetMcpImageInfo 再 BatchCreateXxx),每一个接口的 RequestId 都要分别打印,并在前缀里标注接口名以便区分:
[INFO] GetMcpImageInfo Request ID: 1A2B3C4D-...
[INFO] BatchCreateHideResourceGroupsWithMaxSession Request ID: 5E6F7G8H-...
参考实现: cmd/image_set_max_session.go
verbose / -v 的真正用途: 仅控制额外的调试信息(请求体、响应体 JSON、堆栈等),不再控制 RequestId 是否打印。
🛡️ 破坏性操作的二次确认设计(强制)
规则:命令涉及不可逆操作(删除、永久停用等)时,必须同时实现二次确认提示和 --yes / -y 跳过参数。
哪些情况触发:
| 操作类型 | 示例 | 是否需要 |
|---|
| 永久删除资源 | apikey delete, image delete | ✅ 必须 |
| 多步骤前置依赖(如先禁用才能删除) | 每步都提示 | ✅ 每步 |
| 可逆状态变更 | enable, disable | ❌ 不需要 |
| 查询/只读操作 | list, status | ❌ 不需要 |
标准实现(复用 cmd/confirm.go 中已有的 ConfirmPrompt):
apikeyDeleteCmd.Flags().BoolP("yes", "y", false, "Skip all confirmation prompts (for non-interactive use)")
autoYes, _ := cmd.Flags().GetBool("yes")
confirmed, err := ConfirmPrompt("Are you sure you want to delete? [y/N]: ", autoYes)
if err != nil {
return fmt.Errorf("[ERROR] %w", err)
}
if !confirmed {
fmt.Printf("[INFO] Operation cancelled.\n")
return nil
}
ConfirmPrompt 三种行为:
--yes 传入 → 直接 true,无任何输出
- 交互式 TTY → 打印提示,读取输入(仅 y/Y/yes/YES 通过)
- 非 TTY 且无
--yes → 返回错误,提示用户加 --yes
多步骤命令:每步单独调用 ConfirmPrompt(prompt, autoYes),一个 --yes 跳过全部步骤。
参考实现:
cmd/apikey_delete.go —— 多步骤(禁用确认 + 删除确认)
cmd/image.go runImageDelete —— 单步骤确认
命令层级:
agentbay
└── apikey # 命令组
├── create --name <名称> # 子命令
└── concurrency # 子命令组
└── set --api-key-id <ID> --concurrency <数值>
2.5 注册命令
在 main.go 的 init() 中注册命令:
rootCmd.AddCommand(cmd.XxxCmd)
Phase 3: 单元测试
在 test/unit/cmd/ 目录创建测试文件:
测试覆盖要求:
- 命令元数据测试(Use, Short, Long, GroupID)
- 子命令结构测试
- 必填参数验证测试
- 参数默认值测试
测试命名规范:
Test<命令组>Cmd
Test<子命令>Cmd
Phase 4: 测试验证
-
编译测试
go build -o agentbay .
-
帮助信息测试
./agentbay <command> --help
./agentbay <command> <subcommand> --help
-
参数校验测试
-
运行新命令的单元测试
go test -v ./test/unit/cmd/ -run TestXxx -count=1
-
🔁 全量回归测试(强制)
新增 / 修改 CLI 命令后,必须运行全量测试,确保没有任何已有命令的单测因接口变更、mock 缺失或公共代码改动而被破坏:
go test ./... -count=1
go test ./... -count=1 -race
通过标准:
重要:每次新增或修改命令后,必须执行 go build -o agentbay . 重新构建二进制到项目根目录。不要仅用 go build ./...(只验证编译不输出文件),否则用户运行 ./agentbay 时仍是旧版本。
⚠️ 隔离原则:新增命令不得修改其它命令的公共行为。如果必须改公共代码(如 internal/agentbay/client.go、config、auth),必须在 PR/变更档案里明确列出影响范围,并跑完所有相关命令的回归用例。
Phase 5: 文档生成与同步
本阶段委托 update-cli-command-docs skill 执行,不在本 skill 内展开。
加载并执行 .qoder/skills/update-cli-command-docs/SKILL.md,该 skill 将完成:
- 更新
docs/en/<group>.md 和 docs/zh/<group>.md
- 更新
README.md 和 README.zh-CN.md Command Overview 表格
- 完成 LLM-facing docs readiness:如修改
README.md 或 docs/en/**,执行 bash scripts/build-llms-full.sh 并同步 llms-full.txt;文档结构变化时检查 llms.txt
- 更新
CHANGELOG.md readiness(校验 commit/PR title 可被 release-prep 采集;不在日常命令开发中全量生成 CHANGELOG)
⚠️ 不得在本 Phase 中内联执行文档操作,必须遵循 update-cli-command-docs 的 Phase 0-3 完整流程。
对客文档(cli-analysis/ 目录、钉钉文档)不在此 skill 范围内,需手动同步。
Phase 6: 代码提交(需用户确认)
⚠️ 重要: 必须询问用户是否提交,不要自动提交!
提交前展示:
git status
git diff --stat
询问用户:"需要我帮你提交代码吗?"
用户确认后,使用规范的 commit message:
git add -A
git commit -m "feat: add <功能描述> CLI command
- 具体改动点 1
- 具体改动点 2
- 具体改动点 3"
📤 输出标准
代码输出
✅ 必须包含:
✅ 代码质量:
文档输出
✅ docs/ 命令文档(必须):
✅ 对客文档(cli-analysis/):
Git 提交
✅ 提交与 CHANGELOG readiness:
📚 参考资料
🛠️ 工具脚本
📝 模板文件
⚠️ 注意事项
- Product ID: CLI 使用
xiaoying,不是前端的 xiaoying-double-centre
- 响应格式: 后端返回的字段类型可能与预期不同,需要实际测试确认。数字字段可能被返回为字符串(如
"HttpStatusCode":"200"),parser 必须用 dual_format_responses.go 中的 int32FromFlexibleJSON 容错,绝不直接 json.Unmarshal 打到 *int32。详见 references/api-format.md 响应解析容错模板。
- 参数设计: 始终使用命名参数(
--name),不使用位置参数
- 命令层级: 相关功能组织为子命令,不要创建顶级命令
- 测试覆盖: 必须有单元测试,且所有测试通过
- 文档面向客户: 对客文档不包含代码实现细节
- llms 文档同步: 新增 / 修改 CLI 命令导致
README.md 或 docs/en/** 变化时,必须通过 update-cli-command-docs 执行 bash scripts/build-llms-full.sh 并同步 llms-full.txt;文档结构变化时检查 llms.txt
- 不要自动提交: 必须用户明确要求才执行 git commit
- ⚠️ 接口变更必须同步 Mock: 给
agentbay.Client 接口添加新方法后,必须立即更新所有 mock 类!
- 查找所有 mock 类:
grep -r "type mock.*Client struct" cmd/ test/
- 为每个 mock 类添加新方法(返回
fmt.Errorf("not implemented"))
- 常见 mock 类:
mockGetMcpImageInfoClient, mockImageListClient
- 否则 CI 会报错:
*mockClient does not implement agentbay.Client (missing method Xxx)