en un clic
en un clic
| name | create-cli-command |
| description | 将前端 API 能力封装成 agentbay-cli 命令的标准化流程 |
本 skill 必须与 feature-development-workflow 配套使用,两者分工如下:
| Skill | 负责 | 触发时机 |
|---|---|---|
| feature-development-workflow | 分支管理(从 aliyun/master 拉 feat 分支)、双远程推送(origin → aliyun)、PR 流程、变更档案 | 开发前、提交后 |
| create-cli-command(本文件) | SDK 模型 → Client 接口 → Cobra 命令 → mock 同步 → 单测 → 对客文档 | 开发中 |
执行铁律:
feature-development-workflow 的 Phase 0(变更档案初始化)和分支创建,确认当前在 feat-<name> 分支且基于 aliyun/master,否则不得进入本 skill 的 Phase 1。feature-development-workflow 的 Phase 4-6 完成 commit、双远程 push(origin 先、aliyun 后)、PR、trace.md 更新。若用户未提前走
feature-development-workflow,本 skill 执行第一步必须主动提醒并引导用户先建档、拉分支。
将 agent-bay 前端控制台中的 API 能力封装成 agentbay-cli 命令行工具,包括:
当用户提出以下需求时触发:
查找前端 API 实现
agent-bay/src/http/api/ 目录下搜索接口确认 API 信息
xiaoying(CLI 使用,不是前端的 xiaoying-double-centre)2025-05-01与用户确认
--name 而非位置参数)internal/client/
├── {action}_request_model.go # 请求模型
└── {action}_response_model.go # 响应模型
要求:
Validate() 方法GetXxx() 辅助方法在 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 等数字字段)。< 开头走 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"),
}
在 internal/agentbay/client.go 中:
Client interface 中添加方法定义clientWrapper 中实现方法⚠️ 重要:更新接口后必须同步更新所有 Mock 类!
# 1. 查找所有实现该接口的 mock 类
grep -r "type mock.*Client struct" cmd/ test/
# 2. 为每个 mock 类添加新方法
# 示例:在 cmd/image_status_helper_test.go 和 cmd/image_list_helper_test.go 中添加
// 为每个 mock 类添加(返回 "not implemented")
func (m *mockClient) NewMethod(ctx context.Context, request *client.NewRequest) (*client.NewResponse, error) {
return nil, fmt.Errorf("not implemented")
}
检查清单:
go test ./cmd/... 确保编译通过在 cmd/ 目录下创建命令文件:
--name, --api-key-id)MarkFlagRequired()规则: 每个 CLI 命令调用的每一个对外接口请求,无论成功还是失败、无论是否带 -v / --verbose,都必须在终端打印对应的 RequestId,便于客户在出现问题时直接复制日志给运维定位。
❌ 禁止做法(之前的旧规范):
// ❌ 不要再用 verbose 守卫保护 RequestId 打印
verbose, _ := cmd.Flags().GetBool("verbose")
if verbose && resp.Body.RequestId != nil && *resp.Body.RequestId != "" {
printRequestIDIfVerbose(cmd, *resp.Body.RequestId)
}
✅ 正确做法:
// 1. 成功路径:直接打印(不依赖 verbose)
if resp != nil && resp.Body != nil {
if reqId := resp.Body.GetRequestId(); reqId != nil && *reqId != "" {
fmt.Printf("[INFO] {Action} Request ID: %s\n", *reqId)
}
}
// 2. 错误路径:err 中携带的 RequestId 也必须打印
resp, err := apiClient.{Action}(ctx, req)
if err != nil {
// 不再使用 printRequestIDFromErrIfVerbose(带 verbose 守卫的版本)
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):
// init() 中注册 flag
apikeyDeleteCmd.Flags().BoolP("yes", "y", false, "Skip all confirmation prompts (for non-interactive use)")
// RunE 中使用
autoYes, _ := cmd.Flags().GetBool("yes")
// 每个确认点调用 ConfirmPrompt,autoYes 透传
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,无任何输出--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 <数值>
在 main.go 的 init() 中注册命令:
rootCmd.AddCommand(cmd.XxxCmd)
在 test/unit/cmd/ 目录创建测试文件:
测试覆盖要求:
测试命名规范:
Test<命令组>Cmd // 测试命令组
Test<子命令>Cmd // 测试子命令
编译测试
go build -o agentbay .
帮助信息测试
./agentbay <command> --help
./agentbay <command> <subcommand> --help
参数校验测试
运行新命令的单元测试
go test -v ./test/unit/cmd/ -run TestXxx -count=1
🔁 全量回归测试(强制)
新增 / 修改 CLI 命令后,必须运行全量测试,确保没有任何已有命令的单测因接口变更、mock 缺失或公共代码改动而被破坏:
# 全量测试(包括 cmd / internal / test/unit/...)
go test ./... -count=1
# 或更严格:跑 race 检测
go test ./... -count=1 -race
通过标准:
go build -o agentbay . 无错误且产出的二进制保留在项目根目录供用户直接使用go test ./... -count=1 全部 PASS重要:每次新增或修改命令后,必须执行
go build -o agentbay .重新构建二进制到项目根目录。不要仅用go build ./...(只验证编译不输出文件),否则用户运行./agentbay时仍是旧版本。
⚠️ 隔离原则:新增命令不得修改其它命令的公共行为。如果必须改公共代码(如 internal/agentbay/client.go、config、auth),必须在 PR/变更档案里明确列出影响范围,并跑完所有相关命令的回归用例。
本阶段委托 update-cli-command-docs skill 执行,不在本 skill 内展开。
加载并执行 .qoder/skills/update-cli-command-docs/SKILL.md,该 skill 将完成:
docs/en/<group>.md 和 docs/zh/<group>.mdREADME.md 和 README.zh-CN.md Command Overview 表格CHANGELOG.md(git-cliff 生成 + 中文翻译)⚠️ 不得在本 Phase 中内联执行文档操作,必须遵循
update-cli-command-docs的 Phase 0-3 完整流程。
对客文档(cli-analysis/ 目录、钉钉文档)不在此 skill 范围内,需手动同步。
⚠️ 重要: 必须询问用户是否提交,不要自动提交!
提交前展示:
git status
git diff --stat
询问用户:"需要我帮你提交代码吗?"
用户确认后,使用规范的 commit message:
git add -A
git commit -m "feat: add <功能描述> CLI command
- 具体改动点 1
- 具体改动点 2
- 具体改动点 3"
✅ 必须包含:
✅ 代码质量:
✅ docs/ 命令文档(必须):
docs/en/<command-group>.md 已更新(语法、参数、示例、输出)docs/zh/<command-group>.md 已更新(与英文版结构一致)README.md Command Overview 表格已更新README.zh-CN.md Command Overview 表格已更新✅ 对客文档(cli-analysis/):
✅ 提交规范:
xiaoying,不是前端的 xiaoying-double-centre"HttpStatusCode":"200"),parser 必须用 dual_format_responses.go 中的 int32FromFlexibleJSON 容错,绝不直接 json.Unmarshal 打到 *int32。详见 references/api-format.md 响应解析容错模板。--name),不使用位置参数agentbay.Client 接口添加新方法后,必须立即更新所有 mock 类!
grep -r "type mock.*Client struct" cmd/ test/fmt.Errorf("not implemented"))mockGetMcpImageInfoClient, mockImageListClient*mockClient does not implement agentbay.Client (missing method Xxx)