面向 Agent 的 CLI 工具开发
定位
这个 skill 管跨语言的 CLI 契约设计与验收:命令怎么分、输出怎么给、退出码怎么排、非交互怎么处理、怎么避坑、怎么验收。语言层面的实现细节(如 Go 的 cobra/flag、Python 的 argparse)交给对应语言级 skill,本 skill 不重复。
核心目标一句话:让 CLI 对 Agent 低 token、低歧义、低风险,且可审计、可复现、可回滚;对人默认可读、可交互。这是同一个 CLI 的两个受众,不是两套工具。
什么时候读哪个 reference
- 设计命令面 / 输出契约 / 退出码,或评审一个 CLI 是否 agent-friendly → 读 references/design-principles.md(P0/P1/P2 分层 + 人机双受众规范)。评审时逐条核对,缺失项就是要报告的问题。
- 动手实现,想避开真实事故 → 读 references/pitfalls.md(dry-run 真只读、幂等信号、密钥安全、测试隔离等踩坑教训)。
- 写完要验收 → 读 references/verification.md(逐项粘证据的验收清单 + Agent 实测评测方法论)。
工作流
设计阶段
先过 P0 七条硬性要求——没有这些 Agent 根本用不了,细节见 design-principles.md:
| P0 | 一句话 |
|---|---|
| 非交互模式 | 检测到非 TTY 自动关交互(用 isatty(),stdin/stdout 分开测),不要求调用方主动传 flag |
| 结构化输出 | --json 统一 envelope {ok, data, error, meta};失败也走 stdout JSON + 非零退出码 |
| 退出码分层 | 0 成功 / 1 一般失败 / 2 用法错误 / 3 不存在 / 4 权限鉴权 / 5 冲突 / 6 超时 |
| dry-run | 有副作用的命令能预演,输出结构与真实执行一致,且真只读、不花钱 |
| 验证命令 | 提供 status/verify/doctor,让调用方在退出码之外再查一遍 |
| 输入校验 | 硬拦路径逃逸、命令注入 |
| 自助安装 | 配套 Skill 找不到 CLI 时提供可信下载地址并自动安装到用户目录,再校验版本与能力 |
过完 P0 再按需要加 P1(describe 自描述、结构化错误带 hint/next_commands、体积控制、写前日志、自动生成 SKILL.md、可组合性)。默认采用声明式命令(ensure/apply)而非命令式(create/delete),天然幂等安全。
人机双受众贯穿始终:默认输出人类可读(表格、颜色可用),--json 或非 TTY 时全部剥离只留机器契约;交互向导必须有非交互等价路径(--yes + 全量 flag);同一信息给双字段(程序用英文枚举 status,人看本地化 status_tag)。
CLI 可获得性与自助安装
配套 Skill 的第一步必须探测 CLI 的绝对路径和 version/capabilities。找不到可用二进制时,不要只回复「CLI 未安装」或把安装工作推回用户;用户请求使用该能力,即授权 Agent 完成无提权、仅用户目录的本地安装并继续原任务,除非用户明确要求不安装。
- 提供机器可执行来源:在 Skill、CLI 生成的 Skill 模板或
describe输出中写明可信的官方仓库地址或每个平台产物下载地址;不能只给产品主页、让 Agent 搜索下载链接,或依赖不稳定的包管理器名称。 - 提供确定性安装路径:交付非交互安装脚本或等价命令,按 OS/CPU 选择预编译产物,安装到用户可写目录,不使用
sudo,不修改系统目录或 shell 配置。产物已存在且不同版本时默认报冲突,只有显式--force/--replace才覆盖。 - 锁定并验证:安装后记录绝对二进制路径、下载来源和不可变版本/提交;立刻运行
version --json、capabilities --json或doctor --json验证,然后全程只使用该绝对路径。可提供校验和或签名时必须校验。 - 明确失败边界:下载源不可达、当前平台无产物、校验失败或缺少必要的
git/下载工具时,再报告具体阻塞项;不得用搜索结果中的随机 URL、底层 API 或临时curl旁路 CLI。没有可信下载源和可执行安装路径的 CLI,不算完整的 Agent 可用交付物。 - 后台自更新(与安装同源时):若 CLI 以「官方仓库预编译产物目录」为可信来源长期分发(例如 monorepo 将各平台二进制提交进
dist/),安装后还应具备静默后台自更新:对齐同一可信源、不污染--jsonstdout、可环境变量禁用、更新在独立进程完成以免短命令掐死。具体注入与节流约定以交付仓库的 AGENTS 共性规范为准(mc-cli 见根AGENTS.md「共性规范:后台自动自更新与 skill 同步」)。 - 配套 skill 反向同步(有配套 skill 时):CLI 安装链解决了「机器缺 CLI 时引导 Agent 安装」,但反向链路同样要管:skill 更新后已安装机器的 Agent 不会自知。约定:CLI 后台静默执行 skill 更新命令(如
npx skills update <名> -g -y),不自建各 agent 目录的同步逻辑;资格判据用 skill 管理工具自己的全局锁文件(能证明这台机器由它分发才参与);权威机的本地源在位时豁免,防止未推送的本地更新被仓库旧内容反向覆盖;多 CLI 并发更新同一份锁须互斥;独立节流状态与禁用开关。mc-cli 的完整落地见根AGENTS.md「skill 反向同步」。
登录凭证的「索取一次」约定
CLI 一旦把账号密码持久化到本地(0600)并支持会话过期自动重登,配套引导必须让 Agent 形成「向用户索取一次,之后永不再问」的行为;能力已具备但引导少一句「仅需一次」,每个新会话都会退化成反复向用户要密码(真实事故:yapi-cli 引导只写「密码用环境变量提供」,另一个 Agent 直接把设环境变量的事推给用户)。
- CLI 侧:登录命令支持环境变量传密码(非 TTY 不卡交互);凭证/Profile 同时保存账号密码;会话过期自动重登并刷新落盘。
- 报错侧:NEED_LOGIN 类错误的 hint 必须写明「向用户索取一次账号密码 → 环境变量运行 login → 保存后自动重登」,next_commands 给可照抄的带环境变量命令;只写「请重新登录」等于没写。
- Skill 侧:登录步骤写明「密码经环境变量提供、保存到本地后自动重登,索取一次即可;不要让用户自己设环境变量或反复索要」。
- 升级兼容:旧版落盘凭证不含密码时同样报 NEED_LOGIN,hint 里点明重新登录一次即可升级为新格式;凭证文件路径与字段名是对外契约,任何格式变更必须带旧格式的读写兼容测试(compat_test)。
实现阶段
对照 pitfalls.md 逐条避坑,重点:dry-run 分支拦住所有副作用;写命令做到第二遍收敛为 ok;密钥只从环境变量读、绝不进日志/输出/Git,且凭证绑定登录时的服务地址;上游业务成败判据与 HTTP 状态码解耦,写成功以回查终态为准;会话失效才重登一次、结果不确定的写请求不自动重放;测试全程重定向到临时目录、不碰真实用户资源。
CLI 配套 Skill 的分层与事务骨架
配套 Skill 不是 CLI 命令清单,而是把用户的自然语言目标编译为一套可审计、可授权、可恢复、可验证的事务流程。设计前先写清业务终态不变量,例如“发布目标全部成功且版本一致”或“平台状态与代码严格一致”;不能把“某条命令退出码为 0”直接定义为完成。
| 层 | 应负责 | 不应负责 |
|---|---|---|
| CLI | 鉴权、目标身份校验、版本解析、业务硬门禁、计划/差集、幂等、执行、状态查询、结构化错误 | 依赖 Agent 记住关键安全规则 |
| 辅助脚本 | 跨工具适配、旧 CLI 兼容、格式转换和临时确定性组合 | 长期承载分页、快照一致性、删除顺序等高风险领域规则 |
| Skill | 意图路由、上下文取值、流程编排、风险授权、异常分支和结果表达 | 自己重写 CLI 已能确定性完成的计算或调用底层 API 绕过 CLI |
| Agent | 处理真正的语义歧义和用户决定 | 解析不稳定文案、手算差集、猜测目标或默认值 |
默认采用以下事务骨架;按任务风险删减步骤,但不要颠倒安全顺序:
能力探测/登录检查 → 证据化识别目标 → 生成不可变计划
→ dry-run(离线只读)→ preflight(远程只读)→ 必要授权
→ apply → status/reconcile → verify 终态
- 最少提问:能从工作区、Git remote、现有配置、登录态或平台只读接口确定的信息直接取得;仅在目标确有歧义、缺少必要业务信息、将创建新资源或执行高风险写操作时提问。
- 按风险授权:只读探测和已明确授权的低风险动作可自动执行;生产、删除、覆盖、迁移等高风险动作只在计划和预检完成后确认一次。用户取消或改为自行处理时立即停止等待、查询、重试和再次提交。
- 以收敛定义完成:区分
planned、submitted、waiting_approval、running、succeeded、failed、unknown等状态;写入成功后必须再用status、verify或reconcile证明终态满足不变量。 - 安全重试:只重试明确失败且可重试的目标;成功、审批中、运行中和未知状态不得重复提交。批量操作要保持同一计划和幂等键,第二次执行应收敛为
unchanged或返回已有操作。 - 禁止旁路补洞:CLI 能力不足时优先补 CLI 或停止并报告,不能临时改用
curl、直接读凭据、调用底层 API 或手工复制业务规则绕过其鉴权、审计和安全门禁。 - 先复用再扩面:一次 Agent 失误不自动等于 CLI 缺命令。先判断能否用现有参数、结构化字段或
--out闭环;若问题只是 Skill 路由或结果判读,修 Skill。已有规则仍被忽略时,先把前置条件移到关键动作之前并删除后文重复,不要叠加同义提醒。只有稳定重复且现有契约无法确定性完成的流程才新增命令或参数,并确保减少的复杂度大于新增复杂度。 - 识别下沉信号:稳定、重复、机械、跨 Skill 复用或涉及高风险一致性的逻辑应成为 CLI 一等命令。若 Skill/脚本开始长期处理分页、差集、宽匹配、快照生命周期、删除顺序或复杂状态机,通常说明 CLI 缺少
plan/apply/verify、sync、batch等复合能力;脚本可作为过渡层,但不应成为第二套业务内核。 - 批量查询也要下沉:当 Agent 为同一目标集合重复执行相同发现、诊断或过滤命令时,CLI 应提供一次调用的
batch/--all-matches/聚合命令,内部做有界并发,并按目标返回结果、总体摘要和 partial/failures。不要把 N×M 次串行工具调用当作 Skill 编排能力。 - 不越权定义证据顺序:工具配套 Skill 只约束该工具的安全边界、调用方式和结果判读;除非用户或业务契约明确要求,不要替用户固定数据库、基础设施、APM、审计日志等跨系统数据源的查询先后。
- 控制用户输出:CLI 对 Agent 返回结构化细节;Skill 对用户只报告业务结论、目标/版本、真实状态和下一步,不倾倒命令、内部 ID、JSON 或无关中间过程。
CLI 配套 Skill 与高风险写操作
当 CLI 会修改配置、发布、迁移、删除或触发外部执行时,配套 Skill 只能负责路由和编排,不能代替 CLI 的安全边界。模型可能读到旧文档、调用旧二进制或跳过一条文字规则;错误操作必须由 CLI 本身拒绝。
- 先锁定二进制,再允许任何写入:Skill 的第一步验证当前实际入口及所需能力;能力不足时构建或安装确定版本,并把绝对二进制路径保存为本次会话唯一入口。后续命令不得混用裸命令名和不同副本。
- 已有映射默认不可覆盖:创建命令若命中同名但内容不同的配置,必须返回冲突;只有显式
--replace/--force才能覆盖。覆盖前由 CLI 校验目标资源的稳定身份(如仓库地址、资源 ID、版本或指纹),不能让 Agent 用默认值或占位值“先写再查”。 - 把目标身份写进不可变计划:高风险命令先生成计划,计划至少记录目标、环境、来源版本/提交、关键配置指纹和创建时刻。提交命令只接受该计划;发现目标或版本漂移时拒绝执行。不要让预检结果只存在于 Agent 的自然语言上下文。
- 阶段必须互斥且可判别:离线
--dry-run、远程只读--preflight、真实--apply/--yes分成独立路径和输出状态。dry-run 不得声称远程校验已完成;preflight 不得产生业务写入;提交前必须明确显示将使用的不可变计划。 - 版本是对象,不是文案:用户指定分支、标签、构建号或提交时,CLI 应只读解析为实际不可变版本,再让所有同批目标复用它。不得因为“当前分支碰巧指向同一提交”就静默改用另一种版本标识。
- 把安全前置条件做成机器可读能力探测:提供
version/capabilities/doctor --json,输出当前二进制版本、可用特性、配置状态和不会泄露密钥的诊断。Skill 依据这个输出选择路径,不靠解析 help 文案或猜测 PATH;找不到可用二进制时按本节的可信来源自动安装后再探测。 - Skill 的写入准则:只有在用户已授权相应高风险动作,且 CLI 已验证目标身份后,Skill 才能新增或替换本地映射。无法自动确认时保持配置原样,报告缺少的业务信息;不得通过匿名查询失败或默认配置推导出写入动作。
- 改类/删除写操作优先用「读后写」替代
--yes:对已有资源的修改和删除,比起「确认即放行」的--yes,更推荐强制先读后写——写命令要求带--version(一个读命令输出的资源指纹),CLI 写前重新读取目标、重算指纹、比对:缺失→拒绝并提示先读(confirmation_required/退出码 usage);不一致→拒绝并要求重读(conflict/退出码 conflict);一致才放行。价值有三:① 逼 Agent 写前先读,挡住上下文遗忘、鲁莽操作、操作错对象(app/id 指错,指纹自然对不上);② 旧值留在会话历史里可核对、可回滚;③ 附带乐观锁效果(读到写之间被改过即拦)。落地要点:指纹基准优选写操作实际提交的那份字段(如编辑页表单),做到「读的就是写基准」零漂移,排除防伪 token / 标识字段 / 易变运行态字段后取 sha256 前若干位;服务端无版本字段时纯客户端计算即可。新建(create)例外:无旧值可读,改走查重校验。语义定位:主叙述是「约束 Agent 自身」,乐观锁只是附带收益——错误文案要讲清「为什么不让你写」并在next_commands给出可照抄的读命令(带真实参数);写成功后在返回里回显最新 version 令牌与更新后对象,连续写直接复用上一步返回的令牌,不必中间再读。参考实现mbp-cli/tsp-cli的version.go+ 命令层 writeguard。指纹算法一旦发布即是对外契约:换算法会使历史 version 令牌全部失效(Agent 会话里的旧令牌全部被拒),只在与旧算法对照测试证明值不变时才可迁移;确实需要换时在错误 hint 里引导重新读一次而非硬拒。
验收阶段
按 verification.md 清单逐项粘实际命令和真实输出作证据,不接受「已确认」自陈。契约合规(清单)和 Agent 顺畅可用(真实 Agent 多步任务实测 + 看 transcript)都要做;实测必须用独立上下文的无头 Agent(用所用工具的无头/单次执行模式另起),不得由开发会话自己兼任——开发会话已知全部设计细节,测不出提示与错误是否自解释。新建或修改(含回归)CLI/配套 Skill 都要做这种独立上下文实测,不只是首次验收。高风险 CLI 还必须覆盖:旧二进制缺能力、同名映射覆盖、默认/占位映射、版本标识与提交不一致、阶段参数混用、计划漂移和凭据缺失等反向场景。
契约演进纪律
JSON envelope、退出码、已发布字段名是对外契约,发布后只加不改不删;破坏性变更升接口版本号并显著标注。