Skip to content

为 AI Agent 重新设计命令行 #91

Description

@yinguangyao

2026 年,你的 CLI 最频繁的用户,可能已经不是人类了。


在 AI 时代,如果你的产品不能被 AI 自动发现、自动配置、自动调用,那它就会被直接跳过,这也会成为一个产品的生死线。

  1. CLI 怎么又火起来了?

前阵子 OpenClaw 三个月冲到 GitHub 325k stars,超过 React 登顶第一。

它之所以火,很大程度上是因为特别擅长调用 CLI——遇到没有内置的能力,Agent 会自己在终端里找工具、拼命令、跑通整个流程。

创始人 Peter Steinberger 发了一条语音让它推荐餐厅,它没有内置语音识别,但自己找到 Whisper API、用 curl 调了一下、把语音转成文字再查餐厅,9 秒搞定,全程没人写脚本。

越来越多的团队开始重新重视 CLI,甚至专门为 AI Agent 设计新的命令行工具。未来一定是 less GUI for machines, better GUI for humans。

1.1 LLM 很懂 CLI

GPT、Claude、Gemini 的训练数据中就包含了海量的 shell 命令、man page、Stack Overflow 上的命令行用法。LLM 见过 git commit -m "fix bug" 的次数,比见过任何 MCP 工具定义都多。

这意味着 Agent 调用 CLI 时几乎不需要额外的上下文注入——比如模型已经"知道" gh issue create 大概要什么参数、aws s3 ls 会返回什么。

而 MCP 工具的 Schema 必须在每次对话开始时注入到上下文中,50 个工具的 Schema 可能就消耗上万 token。

1.2 Token 效率更高

Scalekit 的 benchmark 测试给出了数据:

差异主要在于:MCP 往往要求事先把工具 Schema 放进上下文;CLI 只在执行后产生输出,未调用时几乎没有额外 token。

1.3 安装成本低

gh、kubectl、aws、terraform 已经装在数百万台开发机上。Agent 直接 shell_exec("gh issue list --json title,state") 就行了,甚至不需要启动 MCP Server、然后等待连接、处理超时。

1.4 Unix 哲学天然适合 Agent

管道组合、stdout/stderr、用退出码表示结果——这些 Unix 里常见的用法,在 AI Agent 时代依然完美适用:

# Agent 可以组合多个 CLI 完成复杂任务
gh pr list --json number,title --jq '.[].number' | \
  xargs -I {} gh pr view {} --json reviews --jq '.reviews[].state' | \
  sort | uniq -c

但是这里有个关键前提——CLI 必须是 AI 友好的。很多老 CLI 按人手交互设计,Agent 用起来会踩坑。


  1. 从现有工具学习

这里找了一些开源的 CLI 工具来学习一下设计的思路,涉及的工具包括:GitHub CLI (gh)、twitter-cli、AWS CLI、joelclaw、kubectl、terraform、speakeasy 等。

2.1 人类友好 vs AI 友好

面向人类优化的 CLI,常见问题是:

  1. 使用一堆无意义字符组成的 loading 动画;

  2. 缺少结构化数据,输出一堆五颜六色的文字与表格;

  3. 需要人来进行交互确认,在无键盘环境下阻塞 AI;

# 人类友好
$ docker ps
CONTAINER ID   IMAGE     STATUS          PORTS
a1b2c3d4e5f6   nginx     Up 2 hours      0.0.0.0:80->80/tcp

# AI 友好
$ docker ps --format json
{"ID":"a1b2c3d4e5f6","Image":"nginx","Status":"Up 2 hours","Ports":"0.0.0.0:80->80/tcp"}

还有 Spinner 会向 stdout 持续输出 loading 动画;Continue? [Y/n] 在自动化调用时没有 stdin,进程会一直等待。

twitter-cli 的 TTY 检测:当 stdout 是 TTY(人类在终端前)时显示富文本表格,不是 TTY(Agent 通过 subprocess 调用)时自动输出结构化 YAML。Agent 不需要"知道"要加什么标志,调用方式完全一样,输出格式自动适配:

AWS CLI 的环境变量方案:适合需要长期运行的 Agent 环境,一次配好所有"关闭人类特性"的选项:

export AWS_PAGER=""              # 关掉分页器
export AWS_CLI_AUTO_PROMPT=off   # 关掉交互式补全
export AWS_DEFAULT_OUTPUT=json   # 默认 JSON
export NO_COLOR=1                # 关掉颜色

两种方式各有适用场景:TTY 检测是零配置的,适合 Agent 临时调用任意 CLI;环境变量是显式的,适合 Agent 运行环境的初始化脚本。

2.2 减少上下文

在活跃仓库里用 gh issue list 获取未关闭的 Issue:数量一多,上下文很快被占满;而 Agent 可能只需要编号、标题等少量信息。
gh 的字段指定:gh 的 --json 不只是"输出 JSON",它支持指定要哪些字段。配合 --jq,还能做条件过滤:

# 没有字段投影:返回完整 Issue 对象,几十个字段
gh issue view 123

# 有字段投影:只返回 3 个字段
gh issue view 123 --json title,body,state

# 加 --jq 过滤:只要 bug 标签的 Issue 编号
gh issue list --json number,labels --jq '[.[] | select(.labels[].name=="bug")].number'

AWS CLI 的 JMESPath 查询:--query 参数支持过滤、排序、切片、统计、嵌套提取,几乎等于一个轻量级查询语言。对比效果:

# 不用 --query:describe-instances 返回 3000+ 行
aws ec2 describe-instances --output json | wc -l
3247

# 用 --query + --filters:同样的 API 调用,返回 2 行
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query 'Reservations[].Instances[].{ID:InstanceId,Type:InstanceType}'
[{"ID": "i-a071c394", "Type": "t3.medium"}, {"ID": "i-4b41a37c", "Type": "m5.large"}]

Token 消耗从数千降到几十,差了两个数量级。AWS CLI 还支持 --filters(服务端过滤)+ --query(客户端过滤)一起使用——先让 API 只返回匹配的数据,再从结果中只取需要的字段。

twitter-cli 的分层截断:提供了四层递进控制,Agent 按需逐步放开:

twitter feed                    # 默认:截断长推文,返回默认条数
twitter feed --max 5            # 再限制:只返回 5 条
twitter feed --max 5 -c         # 再压缩:紧凑模式,省略非关键字段
twitter feed -o tweets.json     # 兜底:输出到文件而非 stdout,完全不占上下文
twitter feed --full-text        # 反方向:需要完整正文时才展开

2.3 渐进式披露

有些 CLI 把所有命令的完整文档塞进一个巨大的 --help 输出。Agent 只想知道"怎么创建一个 EC2 实例",却被迫读完 200 个服务的全部文档。

这会造成 Claude Code 团队所说的"上下文腐烂"——大量无关信息争夺模型注意力,干扰核心任务。

AWS CLI 的三层 Help 是分层探索的典范。Agent 可以从最粗的粒度开始,按需深入:

$ aws help                        # "有哪些服务?" → 列出 200+ 服务名
$ aws ec2 help                    # "ec2 能做什么?" → 列出 describe-instances 等操作
$ aws ec2 describe-instances help # "怎么用?" → 参数、示例、输出格式

第三层的 Help 有标准六段结构——Name、Description、Synopsis、Options、Examples、Output。

Agent 可以精确定位到 Examples 段看用法示例,或者看 Output 段了解返回数据结构。三条命令完成从"不知道有这个服务"到"能正确构造调用"的全过程。

2.4 提供等待命令

许多云平台 API 异步返回,命令结束不代表资源就绪。若没有封装,Agent 只能自己写轮询:

# 没有 wait 时,Agent 必须自己轮询(浪费多次调用 + sleep)
aws cloudformation create-change-set --stack-name my-stack --change-set-name cs1
while true; do
  status=$(aws cloudformation describe-change-set ... --query 'Status' --output text)
  [ "$status" = "CREATE_COMPLETE" ] && break
  sleep 10
done

每次轮询都消耗一次 CLI 调用的 token 和延迟。如果 Agent 判断错了终止条件,可能无限循环。

AWS CLI 的 wait 子命令把轮询封装成一条阻塞命令:

aws cloudformation create-change-set --stack-name my-stack --change-set-name cs1
aws cloudformation wait change-set-create-complete --stack-name my-stack --change-set-name cs1
# 一条命令,阻塞直到完成或超时

Agent 不需要写循环、判断状态、处理超时——wait 内部已经处理好了。

terraform 用更彻底的方式解决:apply 本身就是同步阻塞的,跑完所有资源变更才返回。Agent 不需要知道背后是异步的。

2.5 语义化的命令

问题:Agent 想取消一个点赞,会猜什么命令?如果你的 CLI 用 tw ul 123,LLM 多半猜不到 ul 是 unlike 的缩写。如果你用 twitter unlike 1234567890,LLM 的训练数据中见过大量 "unlike" 这个词,几乎不会猜错。

twitter-cli 全部使用完整英文单词:bookmarks、followers、following、unlike、unretweet、unbookmark。命令名就是自然语言动词/名词,不存在缩写歧义。

✅ twitter bookmarks --max 20     # LLM 训练数据中见过 "bookmarks"
✅ twitter unlike 1234567890      # LLM 训练数据中见过 "unlike"
❌ tw bm -n 20                    # "bm" 可能是 bookmark 也可能是 benchmark
❌ tw ul 123                      # "ul" 可能是 unlike 也可能是 upload 也可能是 unlink

AWS CLI 的 describe-instances、create-stack、delete-bucket 也是好例子——动词-名词模式,语义直白,没有缩写。

2.6 提示下一步

Agent 执行了一个部署命令,拿到了 deployment_id: deploy-456。然后呢?

它需要自己去猜:是不是有个 status 子命令?参数名是 --id 还是 --deployment-id 还是位置参数?能不能看日志?日志命令叫什么?Agent 每猜错一次,就浪费一次调用和一段上下文。
joelclaw 的 HATEOAS 模式从根本上解决了这个问题——每个响应自带"下一步菜单" :

{
  "ok": true,
  "result": {"deployment_id": "deploy-456", "status": "running"},
  "next_actions": [
    {
      "command": "myapp deploy status <id>",
      "description": "查看部署状态",
      "params": {"id": {"value": "deploy-456"}}
    },
    {
      "command": "myapp logs <id> [--lines <n>]",
      "description": "查看日志",
      "params": {"id": {"value": "deploy-456"}, "n": {"default": 50}}
    }
  ]
}

Agent 不需要猜——params.id.value 直接给出了 deploy-456 这个值,知道下一步要执行 status 命令,不会幻觉出一个不存在的选项。

失败时同理:只有 message 时,模型可能泛泛地「再部署一次」或猜错子命令。若带上 error.code、fix 文字说明,并在 next_actions 里给出和该错误绑定的具体命令(先看哪类日志、用哪个超时参数重试),排障路径就具体多了:

{
  "ok": false,
  "error": {"code": "HEALTH_CHECK_TIMEOUT", "message": "Health check failed after 30s"},
  "fix": "Check application startup logs or increase timeout",
  "next_actions": [
    {"command": "myapp logs deploy-456 --type health-check", "description": "查看健康检查日志"},
    {"command": "myapp deploy create --env staging --health-timeout 60", "description": "增加超时重试"}
  ]
}

目前只有 joelclaw 这样的新项目实现了完整的 HATEOAS。gh、aws、kubectl 都没有。这是现有 CLI 和"理想 AI 友好 CLI"之间最大的差距之一。

2.7 如何让 Agent 发现

问题:Agent 不知道你的 CLI 存在。它知道 gh、aws、kubectl 是因为 LLM 训练数据里见过。但如果你写了一个新的 CLI,Agent 怎么知道它能做什么?

SKILL.md:twitter-cli 和 speakeasy 提供了遵循 Vercel Skills 规范的 SKILL.md。安装后,Agent 启动时自动加载这份"操作手册":

npx skills add jackwener/twitter-cli    # Agent 自动获得 twitter-cli 的用法
npx skills add speakeasy-api/skills     # Agent 自动获得 speakeasy 的用法

speakeasy 还更进一步——内置了 agent 子命令,Agent 可以像浏览目录一样逐层深入文档:

speakeasy agent                         # 顶层能力概览
speakeasy agent workflows               # 工作流列表
speakeasy agent workflows sdk-generation # 具体某个工作流的详细指引

AGENTS.md:对于项目专属的 CLI,在项目根目录放一个 AGENTS.md(或 CLAUDE.md),Agent 每次启动时自动加载。Vercel 的评测数据很有说服力:8KB 压缩文档嵌入 AGENTS.md,任务通过率 100%,而 Skills 机制最高只有 79%。原因很简单——被动注入的上下文始终在 Agent 视野内,按需加载的 Skill 可能在该用的时候没被触发。

2.8 Dry run(演练模式)

Dry run = 只演练、不改现场:校验参数和权限,告诉你「如果真执行会动什么」,但不创建、不删除、不扣费。

对人:等价于「先看计划再点确认」。对 Agent:删除、发布、改配额这类操作若只能真跑,模型一旦理解错或参数错,代价很大;有 dry run 就能先拿一份「将要发生的事」,再决定要不要执行。

几个常见例子(名字不统一,但意思一样):

工具 典型用法
kubectl apply ... --dry-run=server:连上集群校验,但不真正写入
Terraform terraform plan:打印将要增删改的资源,再 apply
AWS CLI 部分命令有 --dry-run,或单独的 validate-*;新 CLI 最好统一成 --dry-run

和「真执行」输出要同一种形状:真跑时 stdout 若是 JSON,预览里也应该是 JSON,只是多几个字段(例如 dry_run: true、变更列表)。

这样人和 Agent 都用同一套解析方式;若预览只有一行「OK」、没有结构化明细,Agent 没法向用户说清楚会动哪些资源,也没法在代码里判断「计划里有没有 delete」。

kubectl apply -f app.yaml --dry-run=server -o json   # 预览 + 结构化输出
terraform plan -out=tfplan && terraform apply tfplan # 先看 plan,再执行
  1. 从零设计一个 CLI

3.1 命令结构

采用 <tool> <resource> <action> 三段式。Agent 可以从资源名推断操作,与 REST API 天然对应:

myapp server list
myapp server create --name web-1 --type web
myapp server delete web-1 --force
myapp deploy create --env staging
myapp deploy status deploy-456
myapp deploy rollback deploy-456

命名用完整英文单词(bookmarks 不是 bm),用 describe/create/delete 不是 desc/mk/rm。

3.2 输出设计

# stdout 只放结构化结果,stderr 放日志
$ myapp deploy --debug 2>deploy.log

# 统一响应信封
{"ok": true, "schemaVersion": 1, "result": {...}, "next_actions": [...]}
{"ok": false, "schemaVersion": 1, "error": {"code": "...", "message": "..."}, "fix": "...", "next_actions": [...]}

注意:

  • ok 字段区分成功/失败,结构永远统一

  • schemaVersion 管理格式兼容性

  • 错误也是结构化的,带 code 和可选的 fix

  • next_actions 告诉 Agent 下一步(见 2.7)

3.3 退出码

不要只用 0 和 1。分类的退出码让 Agent 自动选择恢复策略:

退出码 含义 Agent 策略
0 成功 继续
2 参数错误 修正命令
3 认证/权限错误 重新认证
4 建议重试 sleep + retry

3.4 CLI-Anything

前面说的都是「怎么设计一个好 CLI」,但现实是大量桌面软件根本没有 CLI——GIMP、Blender、LibreOffice、Audacity 这些,只有 GUI。

Agent 想操作它们,要么靠截屏点击(Computer Use,慢且贵),要么等开发者手写 MCP Server(每个软件都得单独适配)。

港大 HKUDS 实验室开源的 CLI-Anything 换了个思路:直接从软件源码自动生成一套结构化 CLI,不用改源码、不用写 Server,跑完就能用。


生成出来的 CLI 默认就带本文提到的那些特性:AI 看到可读文本,加 --json 得到结构化输出,还有状态持久化和撤销/重做。

这个项目的意义在于:AI 友好的 CLI 不一定要人手写。如果生成工具足够成熟,「给任何软件加一层 CLI」可以变成一件自动化的事。

3.5 Dry run 设计要点

新 CLI 建议一开始就支持 dry run,可以按下面几条做:

  • 入口:全局 --dry-run,或单独的 plan 子命令(和 terraform plan 一样好记)。

  • 同一套校验:预览和真跑走同一套参数解析与权限检查;不要写两套逻辑,否则会出现「演练过了、真跑才报错」。

  • 同一套输出:预览仍是 3.2 里的 JSON 信封,加上 dry_run: true 和变更列表(如 planned_operations);有需要时加 warnings。

  • 退出码:预览里若发现错误(参数错、没权限等),和真失败一样用非零;只有预览且一切合法时用 0,表示「去掉 --dry-run 就可以当真执行」。

  • 写进 AGENTS.md:破坏性操作默认先 dry run,写一句话即可,Agent 启动就能读到。

示例(预览成功时的响应):

{
  "ok": true,
  "schemaVersion": 1,
  "dry_run": true,
  "result": {
    "summary": "Would create 1 server, update 2 security rules",
    "planned_operations": [
      {"op": "create", "resource": "server/web-2", "details": {"type": "web", "region": "ap-east-1"}},
      {"op": "update", "resource": "rule/allow-ssh", "details": {"cidr": "203.0.113.0/24"}}
    ]
  },
  "next_actions": [
    {"command": "myapp server create --name web-2 --type web", "description": "确认后执行真实创建(去掉 --dry-run)"}
  ]
}

结语

CLI 的故事写了 50 年。从 Ken Thompson 在 Unix 上写出第一个 shell,到 2026 年 AI Agent 在终端里自主操作——CLI 的本质从未改变:它是计算机世界的通用语言。

只不过现在,会说这门语言的不再只是人类。


参考资料

  1. Agentic CLI Design: 7 Principles — tumf

  2. CLI Design for AI Agents — JoelClaw

  3. 7 Principles for AI Agent Tool Design — Alex Chen

  4. Making your CLI agent-friendly — Speakeasy

  5. MCP vs CLI: Benchmarking Cost & Reliability — Scalekit

  6. AGENTS.md outperforms skills in our agent evals — Vercel

  7. jackwener/twitter-cli — jackwener

  8. AWS CLI User Guide — AWS

  9. Writing CLI Tools That AI Agents Actually Want to Use — uenyioha


Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions