给 Agent 设计 CLI 的十个原则
Google 官方发了 7 条 CLI 设计原则,我读完发现我们都忽略了一种新用户
Agent 时代的 CLI 设计哲学探讨 
LLM 训练数据里有大量 shell 命令和 bash 脚本(GitHub 代码约占 5%、Stack Overflow 约占 2%),Agent 天生对命令行有相当的「直觉」。
但这个直觉有边界。Berkeley 的 BFCL V4 Leaderboard 显示,即使排名第一的模型,在复杂的多步工具调用场景下准确率也达不到 100%。SWE-bench 数据更直观:当前最强模型也只能解决约七成编码任务。
模型能力仍在飞速进步,但使用 CLI 的方式没变:跑 --help 、解析输出、拼命令、看退出码。模型越强,每一步判断越准 —— 但 CLI 本身设计不好,再强的模型也会掉进同样的坑。
Surge AI 有一个案例:让前沿模型处理只需改 2 行代码的问题,它走了 39 轮对话、改了 693 行代码,最终失败。原因是第一步就幻觉出了不存在的类名,然后在错误基础上越陷越深,连续 22 轮坚持「核心逻辑是对的」。而另一个模型遇到同样问题,发现文件被截断后主动要求重新读取,确认完整内容才动手,一次解决。
好的 CLI 设计,就是帮 Agent 做这个区分:减少它需要猜的东西,增加它能验证的东西。
grep -a / -A 、 ssh -v / -V 含义完全不同;Agent 基于概率选择,两个 token 概率接近时选错风险高Are you sure? [y/N] 对 Agent 是一堵墙(2019 年 AWS CLI v2 改默认 pager 为 less ,导致全球数千 CI 任务挂起)# 好:docker container ls / gh pr create / lark-cli calendar +agenda
# 不好:create-pr / delete-image / search-user
Agent 发现命令的过程是 树搜索 :mytool --help → 看到名词 → mytool user --help → 看到动词。每一步有 --help 可查,确定性缩小范围。verb-noun 结构把所有操作平铺一层,无层级引导。
Docker 是典范:container / image / volume 是名词, ls / rm / create 是动词,Agent 学会 docker container ls 就能推断 docker volume ls 。
所有参数都应有长格式( --verbose ),短格式( -v )作为可选的人类便利。
--dry-run 具备自描述能力; -n 在不同工具中可能是 --line-number 、 --numeric 、 --no-action--verbose 和 --version 不会混淆; -v / -V 一字之差意义迥异--output ,语义绑定强; -o 的语义绑定弱得多钉钉 CLI 的 --yes 参数描述是『跳过确认提示(AI Agent 模式)』 —— 参数名本身就是自描述的,Agent 一看就知道该在什么时候加它。
stdout 输出 JSON 数据, stderr 输出状态信息, 严格分流 。
结构化输出一旦发布即为 API。Kubernetes v1.14 弃用 --export ,v1.18 正式移除,导致数千 Helm chart 和 CI/CD 管道崩溃。 加新的可选字段安全,改变已有字段类型或名称就是破坏性变更。
GitHub CLI 在这方面做得趄庞:检测到输出被管道传输时,自动切换为 tab 分隔格式,去掉颜色转义符,文本不截断。飞书 CLI 支持 JSON、NDJSON、table、CSV、pretty 五种格式。
检测 TTY 还是 pipe,自动调整行为。非 TTY 下默认输出 JSON,不该要求 Agent 额外加 --json flag。钉钉 CLI 的 --yes 参数就专门为此设计:跳过所有确认提示,进入 AI Agent 模式。
gcloud 文档里有一条建议值得所有 CLI 开发者记住:「不要依赖 gcloud 的原始输出格式,永远使用 --format 标志。」因为原始输出格式可能随版本变化。
每个有副作用的命令支持 --dry-run ,给 Agent 探索-验证的反馈循环。好的输出是结构化 JSON diff,告诉 Agent 什么会被创建、修改或删除。
飞书的实现更细致:干跑时会输出完整的请求 URL、方法、参数,Agent 可以在执行前确认请求的正确性。Lightning Labs 的设计更进一步:--dry-run 使用专门的退出码(exit code 10),这样 Agent 可以通过退出码区分「干跑成功」和「真正执行成功」。
| 退出码 | 含义 | Agent 应对 |
|---|---|---|
| 0 | 成功 | 继续执行 |
| 1 | 一般错误 | 读 stderr 诊断 |
| 2 | 参数错误 | 修正参数重试 |
| 3 | 资源不存在 | 跳过或创建 |
| 4 | 权限不足 | 提示用户授权 |
| 5 | 冲突 / 已存在 | 跳过或更新 |
退出码一旦发布即为契约,不可随意修改。
javascript: 、 file: 协议)、域名、输出路径(拒绝写入 .ssh/ 、 .gnupg/ )--format json|table|csv 比 --format <string> 安全得多mytool schema --all 输出完整命令树 JSON;飞书 CLI 已实现 lark-cli schema calendar.events.list--help# 命令式:资源已存在会报错
mytool user create --name "john"
# 声明式:无论调用多少次,结果一致
mytool user ensure --name "john"
# 或
mytool user create --name "john" --if-not-exists
Agent 会重试(网络超时、结果不确定、任务中断恢复……)。 kubectl apply 是声明式设计的教科书。飞书 CLI 的 +messages-send 支持 --idempotency-key 参数:Agent 传入一个唯一标识符,即使命令被重复执行,服务端也只会处理一次。
{
"error": "permission_denied",
"message": "缺少 calendar:read 权限",
"suggestion": "运行 lark-cli auth login --domain calendar 授权",
"retryable": false
}
错误信息四要素: 错误类型 (机器可读)、 描述 (发生了什么)、 修复建议 (怎么解决)、 是否可重试 。
Anthropic 发现「描述是影响工具使用准确率的最关键因素」 —— 仅通过优化工具描述就在 SWE-bench 上大幅降低错误率。
好的 --help :以 2-3 个示例开头;明确标注必需 / 可选参数;参数描述含值域( --format json|table|csv );50 行以内。
[ ] noun-verb 层级命令结构,支持树搜索
[ ] 所有参数有长格式,短格式可选
[ ] stdout 输出 JSON,stderr 输出状态信息
[ ] 非 TTY 自动调整输出(默认 JSON)
[ ] 副作用操作支持 --dry-run,输出结构化 diff
[ ] 退出码有细粒度语义文档
[ ] 参数枚举约束,输入严格验证
[ ] 支持 schema 自省命令
[ ] 关键操作幂等或支持 --if-not-exists
[ ] 错误信息含类型、描述、修复建议、可重试标志
[ ] --help 示例开头,标注必需 / 可选,50 行内
[ ] --yes / --no-interactive 跳过所有交互提示
开源参考:agent-cli-guide (GUIDE.md + CHECKLIST.md,可直接丢给 Coding Agent 使用)
用这十条原则对照钉钉和飞书 CLI:
飞书做得好的: noun-verb 结构清晰,三层架构让 Agent 按需选择抽象层级,schema 命令提供完整 API 自省,五种输出格式,按域申请权限(最小权限原则),权限不足时自动提示修复方案。
飞书可以改进的: 非 TTY 下没有自动切 JSON,退出码没有文档化的细粒度语义, --idempotency-key 应该更广泛使用。
钉钉做得好的: --yes (AI Agent 模式)语义自描述, --mock 参数方便调试,帮助文本全中文(对中文 LLM 更友好),批量熔断防 Agent 失控。
钉钉可以改进的: 命令只有两层,缺少 shortcut 快捷层;没有 schema 自省命令;输出格式只有三种;帮助文本缺少使用示例。
Google Cloud 发了一篇技术文章:"10 分钟构建一个同时服务 AI Agent 和人类的 CLI"。它在讲一件比 "CLI 怎么写 "重要得多的事:
我们正在进入一个软件需要同时服务两种用户的时代 —— 人类和机器。
今年写的每一个 CLI,某个时刻都会被 Agent 调用。大多数 CLI 还没准备好。
交互式确认弹窗、彩色终端输出、进度条动画、" 你确定吗?(Y/n)" —— 对人类是友好体验,对 Agent 是灾难。
把数据和呈现分离(Decouple the data from the presentation)。
CLI 内部只做一件事 —— 生成数据。呈现层是可插拔的客户端。
# 人类得到交互式 TUI
a2acli watch --task abc123
# Agent 得到结构化 NDJSON
a2acli watch --task abc123 --no-tui
不需要维护两套代码库。状态由后端服务管理,CLI 只是轻量传输层。Agent 通过读取 skills/SKILL.md 学习如何使用这个 CLI。
人类会随机探索、看错误信息、Google、问同事。Agent 只能从你提供的信息里推断该怎么做。
示例比描述更重要。Agent 会解析示例来推断 flag 的用法。
建议在仓库根目录放 AGENTS.md ,显式定义 AI 开发者的交互规则、默认工作流和架构标准。
--json 或 --no-tui flagNO_COLOR 和 [APP]_NO_TUI 环境变量stdout 被 pipe 时自动跳过所有交互元素保护 Agent 的上下文窗口: 默认截断大文本块、脱敏敏感信息;需要完整数据时要求显式加 --full 或 --verbose 。
CLI 必须是无状态的: 用引用 ID(如 --task <ID> )来操作,让后端服务管理长时运行的上下文。Agent 调用 CLI 不是在开启一段对话,而是在执行一个操作。
CLI Flag > 环境变量 > 配置文件 > 默认值
Agent 不需要知道 URL 和 token,只需传 --env prod ,CLI 自己去找对应配置。
Error: database not initialized
Hint: run 'a2acli init' to create the local database
每条错误信息后加 Hint: ,Agent 可以解析来自我纠正。 尽早失败(Fail fast) :在执行重逻辑之前先验证配置和连接,不要让 Agent 等 30 秒之后才发现 token 不对。
同一个缩写在所有命令里必须代表同一个意思。不一致的 Flag 会破坏 Agent 的推理 —— 它在一个命令里学到的规律不能用在其他命令上。
默认行为永远应该是最安全、最常见的路径。 破坏性操作必须要求显式 Flag 才能触发。
用 "语义色彩 token" 而非直接用颜色名称:定义 Pass token 对应绿色(成功)、 Warn 对应黄色(警告 / 进行中)、 Error 对应红色(失败)、 Meta 对应灰色(元数据)。
不要给描述文字和帮助文本上色。 过度上色等于什么都没强调,用空白和对齐表达层级更有效。
--version 要能用,重要更新时主动提示git config user.name 或 $USER 获取身份并记录,在多 Agent 协作场景下审计追溯越来越重要在软件工程的历史上,我们设计过面向人类的界面(GUI、CLI、Web UI),也设计过面向机器的接口(API、消息队列、数据库连接)。但我们 从来没有认真设计过 "同时面向人类和机器 "的接口 。
这篇文章真正在做的事,是给这个新问题提供一套工程解法 —— 同一个入口,根据调用者的身份自动切换呈现方式,同时保证数据层的一致性。
2026 年最好的 CLI,不是终端界面最漂亮的那些,而是当人类输入命令和当 Agent 编程调用时,同样运行良好的那些。
这不只是 CLI 的问题 —— 随着 Agent 介入各种工具链,SDK、API、内部平台工具迟早都会被 Agent 调用。"对人类友好 "和 "对 Agent 可用 "将成为每个工具都必须同时满足的要求。
原文:Build a CLI for AI agents & humans in less than 10 mins(Google Cloud 官方)
CLI-Anything 最近很火 —— 它用 AI Agent 自动为 GIMP、Blender、Audacity 等 GUI 软件生成命令行工具,号称要构建 "Agent-Native Software Ecosystem"。
但我们花了一个晚上拆解它的代码后,得出了一个不太客气的结论: CLI-Anything 生成的 CLI 是给人用的,不是给 Agent 用的。
更重要的是,在拆解过程中,我们推导出了一套 Agent 时代的 CLI 设计哲学 —— 五条原则,每条都有代码级的实证支撑。
grep 只搜索, sort 只排序grep | sort | uniq ,简单工具组合出复杂能力精髓:一个好的 CLI 工具不是一个应用程序,而是一个 积木块 。
依然成立: Do one thing well、行为可预测(对 Agent 甚至比对人类更重要)、幂等性(Agent 会重试)。
根本变化: CLI 的消费者从人类变成了 LLM Agent。两者的差异不在于 "模型够不够聪明" —— 当前旗舰模型的 CLI 使用能力已和资深程序员相当。差异在于 Agent 消费 CLI 输出的架构方式 :
每轮 CLI 调用消耗的 token 要尽可能少,信息密度要尽可能高。
# 人类友好的输出(~45 tokens)
╭──────────────────────────────────╮
│ ✓ 3 layers in project │
│ 0: Background (solid) │
│ 1: Photo (image, 80%) │
│ 2: Title (text) │
╰──────────────────────────────────╯
# Agent 友好的输出(~25 tokens,信息量相同)
[{"i":0,"name":"Background","type":"solid"},
{"i":1,"name":"Photo","type":"image","opacity":0.8},
{"i":2,"name":"Title","type":"text"}]
token 消耗减少约 45%。单次差异不大,但复杂任务可能调用数十次 CLI,差异累积显著。
设计含义:
完成一个任务所需的 CLI 调用轮次要尽可能少。
# 需要两轮调用(多一轮查询确认)
$ cli-anything-gimp layer move 2 --to 0
{"status": "ok"}
$ cli-anything-gimp layer list # Agent 还得再调一次确认结果
# 只需一轮调用(操作 + 结果状态一起返回)
$ cli-anything-gimp layer move 2 --to 0
{"moved":2,"to":0,"current_order":["Title","Background","Photo"]}
设计含义:
CLI 输出在状态未变时,应保持字面完全一致,以利于 LLM 推理的 prompt cache 命中。
很多 Agent 框架在组装每轮上下文时会将工具状态重新注入到上下文中间。如果这部分输出带时间戳,每轮都不同,从该插入点开始后面全部 cache miss。
// 带时间戳 → 每轮都不同 → 从此点开始 cache miss
{"name":"my_project","layers":3,"last_modified":"2026-03-15T21:35:11Z"}
// 纯结构状态 → 项目没变就字面一致 → cache 命中
{"name":"my_project","layers":3}
设计含义:
CLI 的设计应尽量贴合训练数据中大量存在的 CLI 范式,降低模型幻觉概率。
# 非标准 → 模型会幻觉出 --json(训练数据中更常见)
$ mytool export --machine-readable
# 标准 → 贴合高频模式,模型不会猜错
$ mytool export --json
# 非标准子命令 → 模型可能猜测为 "mytool layer list"
$ mytool do --action=list --type=layer
# 标准子命令 → 和 git/docker/kubectl 一致,模型一次调对
$ mytool layer list
Convention over creativity — 在 Agent 时代,遵循约定比追求独特更重要。
设计含义:
--json 、 --output 、 --verbose 、 --quiet )tool <noun> <verb> 或 tool <verb> <noun> )--help 格式确定性的可重复模式封装到 CLI 中;创意性的、依赖上下文的编排交给 Skill 引导 Agent 完成。
Agent 时代的工具链是 Agent → Skill → CLI 三层结构:
# 适合封装到 CLI:确定性的批量操作
cli-anything-gimp batch convert --input "*.png" --format webp --quality 80
# 如果让 Agent 编排:每张图 3 轮调用 × 100 张 = 300 轮推理循环
# 适合 Skill + Agent 编排:创意性的、依赖上下文的任务
# Skill 指导 Agent:先建项目 → 根据图片内容选择构图 → 加文字 → 调色 → 导出
cli-anything-gimp project new --width 1080 --height 1080
cli-anything-gimp layer add-from-file photo.jpg
cli-anything-gimp filter add brightness -l 0 -p factor=1.3 # Agent 根据图片亮度决定参数
cli-anything-gimp draw text --text "Hello" --size 48 # Agent 根据内容决定文案
cli-anything-gimp export render post.png
经验法则:如果一个流程每次执行步骤都一样 → CLI;如果步骤取决于上下文 → Skill + Agent。
| 原则 | 优化目标 | 类别 | 根因 |
|---|---|---|---|
| 上下文经济性 | 每轮 token 量 | 效率 | 上下文窗口是有限共享资源 |
| 调用经济性 | 任务总轮次 | 效率 | 每轮推理循环有成本 |
| Cache 友好性 | 单 token 推理成本 | 效率 | Prompt cache 按前缀匹配 |
| 模型友好性 | 调用正确率 | 可靠性 | 模型基于训练数据做概率预测 |
| Skill-CLI 责任边界 | 鲁棒性与灵活性平衡 | 架构 | Agent 编排是概率性的,CLI 执行是确定性的 |
前三条优化 效率 ,第四条优化 可靠性 ,第五条优化 架构合理性 。
用五条原则对照 CLI-Anything 生成的 GIMP CLI:
repl_skin.py ,全是边框、emoji、彩色字符 —— Agent 不需要任何一行layer move 只返回 {"moved":2, "to":0} ,不返回新排列顺序;Agent 想确认结果还得再调一轮 layer listsession.py 里随意嵌入 datetime.now().isoformat() ,每次快照都不同--json flag;但这不是刻意为 Agent 设计的,只是 Python CLI 的通用最佳实践结论: CLI-Anything 是工程完成度不错的项目,但其设计哲学停留在 "给人用的 CLI 加个 --json "的阶段。它提出了正确的问题,但给出了一个没想透的答案。 最具说明性的信号是:无子命令时默认进入 REPL 模式 —— Unix 哲学就反对它,在 Agent 时代更不合适。
这套框架的适用范围不限于 CLI —— 任何 Agent 调用的工具接口,都面临相同的约束: