# 从 github cli 学习如何设计 agent friendly cli
**作者**: yan5xu
**日期**: 2026-05-23T11:43:05.000Z
**来源**: [https://x.com/yan5xu/status/2058151727333880265](https://x.com/yan5xu/status/2058151727333880265)
---
GitHub 的 gh 应该是 vibe coding 里非常高频的命令了,而且它 CLI 本身也设计得很 agent-friendly,很适合拿来学习研究。
我觉得 gh 最值得学的,不是某几个具体 command,而是它处理了两个很关键的问题:
一个是 GitHub 能力面太大,CLI command 很容易爆炸。
另一个是 agent 的 context 很贵,命令输出不能把无关信息全塞进来。
先看 command 爆炸的问题。
GitHub 的能力面非常大。如果每个能力都做成一个 command,很快会变成:
```
gh issue list
gh issue create
gh issue comment delete
gh repo deploy-key add
gh project item archive
...
```
command 越来越多,整个 --help 也会变得难以维护。agent 也永远要猜:这个操作到底有没有对应的 command?
gh api 用很取巧的方式解决了这个问题(当然这也建立在 GitHub 本身非常完善的 RESTful API 基础上):
```
gh api repos/epiral/bb-viewer/issues/comments/4517246421 -X DELETE
```
它不是简单地"可以直接调 API",而是把 RESTful API 里的 resource model 自然迁移到了 CLI 里:路径定位资源,HTTP method 表达动作,认证和输出格式由 gh 统一处理。
所以文档里看到:
```
DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}
```
几乎不用翻译,就能写成:
```
gh api repos/epiral/bb-viewer/issues/comments/4517246421 -X DELETE
```
这对 agent 特别重要。API 文档本身就可以变成 CLI 使用说明。agent 不需要学一套和 API 文档完全不同的 DSL,也不需要等 CLI 作者给每个长尾能力都包一层 command。
比如用 REST 路径直接查一个 PR:
```
gh api repos/cli/cli/pulls/13492 --jq '{number: .number, title: .title, state: .state}'
```
输出:
```
{"number":13492,"state":"open","title":"Replace SITE_DEPLOY_PAT with gh-cli-site-deployer App"}
```
路径就是资源定位,--jq 做字段裁剪,整个过程不需要记任何专用 command。
这背后其实是一层 resource interface。resource 层解决的是覆盖面问题:能力很多,但语法可以统一。
但 resource 不是万能的。RESTful 一直以来的问题就是,有些用户意图很难自然 resource 化。
比如 login。它不是对某个资源做 CRUD。
比如 clone。它既涉及远程 repo,也涉及本地文件系统和 git 状态。
比如 checkout。它不是更新一个远程资源,而是在本地切换工作区状态。
比如 merge。它经常包含多个底层动作,但用户表达的不是"修改这个字段,再删除那个分支",而是"把这个改动合进去"。
所以 CLI 里还需要 command 层。command 不是"多步骤编排"的同义词。多步骤编排只是 command 的常见来源之一。command 的本质是承接那些无法自然 resource 化的用户意图。
如果从 gh 再抽象一步,可以把 resource 和 command 在语法上显式分开。
一个可能的方案是用 / 前缀表示 resource:
```
# command,没有 / 前缀,表达动作
cli login
cli clone epiral/bb-viewer
cli checkout 353
cli merge 353 --squash
cli status
# resource,用 / 前缀,表达对象路径
cli /issues list
cli /issues/42 get
cli /issues/42 update state=closed
cli /issues/42 delete
cli /issues/42/comments create body="LGTM"
cli /projects/4/items list
```
/ 的好处是它不占用任何单词。
如果用 api,会让人以为这是传统 API wrapper;如果用 resource,太啰嗦;如果直接写 issues list,又会有歧义:issues 到底是 command 还是 resource?
/issues 就很清楚:这是一个资源路径。
resource 层的动词可以收敛到一个很小的集合:
```
list
get
create
update
delete
```
这样 agent 学会一个资源,就基本学会了所有资源。
```
cli /issues list state=open author=me
cli /issues create title="Fix login" body="..."
cli /issues/42 update state=closed
cli /issues/42/comments create body="LGTM"
```
参数也可以分清楚:key=value 是资源参数,--flag 是 CLI 行为控制。
```
cli /issues list state=open --json --jq '.[].title'
```
resource 层负责覆盖长尾能力,command 层负责表达高层意图。两者不是替代关系,而是互补关系。
再看第二个问题:输出污染。
在 agent workflow 里,命令输出不是越多越好。无关字段进入 context,不仅浪费 token,还会污染语义空间,干扰后续推理。
gh 的 --json / --jq 很值得学。
比如不做裁剪,agent 拿到的可能是这种输出:
```
gh pr list -R cli/cli --limit 1 --json number,title,author,labels,state,reviewDecision,updatedAt
```
```
[
{
"author": {
"id": "MDQ6VXNlcjE2MTE1MTA=",
"is_bot": false,
"login": "williammartin",
"name": "William Martin"
},
"labels": [],
"number": 13492,
"reviewDecision": "REVIEW_REQUIRED",
"state": "OPEN",
"title": "Replace SITE_DEPLOY_PAT with gh-cli-site-deployer App",
"updatedAt": "2026-05-22T16:54:37Z"
}
]
```
但如果下一步只是要知道 PR 标题,真正需要进入 context 的只有一行:
```
gh pr list -R cli/cli --limit 1 --json title --jq '.[0].title'
```
```
Replace SITE_DEPLOY_PAT with gh-cli-site-deployer App
```
--jq 的价值不是"省一个管道",而是把信息裁剪发生在进入 LLM context 之前。先减少 token 浪费,再减少无关字段对后续推理的干扰。
还有一种情况:默认输出本身就是语义化的。
```
gh pr view 13492 -R cli/cli
```
```
title: Replace SITE_DEPLOY_PAT with gh-cli-site-deployer App
state: OPEN
author: williammartin
reviewers: copilot-pull-request-reviewer (Commented), BagToad (Requested)
number: 13492
url: https://github.com/cli/cli/pull/13492
additions: 26
deletions: 2
--
## Summary
Replaces the personally-held SITE_DEPLOY_PAT used by the release workflow...
```
这类输出比一整坨 JSON 更适合 LLM 直接理解。
所以 JSON 的定位应该是串联和精确抽取,不是默认认知界面。默认输出应该尽量语义化。这个点前一篇展开过,这里只 callback 一下:自然语言是模型更擅长的表征。
如果设计自己的 resource 风格 CLI,也可以沿用这个思路:
```
cli /issues/42 get
```
默认输出:
```
Issue #42: Fix login bug
State: open
Author: epiral
Labels: bug, auth
Updated: 2h ago
Login fails when session expires.
```
需要串联时:
```
cli /issues/42 get --json --jq '.title'
```
```
Fix login bug
```
默认语义化,需要时结构化。
最后还有一些执行层面的设计也值得学。
gh 的 flags 很一致:--repo、--assignee、--label、--json、--jq、--web 在不同 command 里复用。对人是降低学习成本,对 agent 是提高泛化能力。
--web 是一个自然的 fallback:
```
gh pr view --web
```
CLI 不需要假装覆盖所有交互。有些事情就是 Web 更合适。
还有非交互模式:
```
GH_PROMPT_DISABLED=1 gh pr create --title "fix bug" --body "..."
```
--yes 跳确认,--dry-run 做预览,token 走环境变量。这些都是 agent 能稳定使用 CLI 的基础设施。
所以 agent-friendly CLI 不只是 machine-readable CLI。
machine-readable 解决的是程序串联;agent-friendly 还要解决语义理解。
结构上要稳定可组合:resource path、统一动词、--json、--jq。
语义上要适合 LLM:默认自然语言输出、清晰错误信息、少 token 噪音。
从 gh 里可以学到的核心就是这个方向:用 resource 层避免 command 爆炸,用 command 层承接无法 resource 化的动作,再用输出裁剪避免污染 agent 的 context。
## 相关链接
- [yan5xu](https://x.com/yan5xu)
- [@yan5xu](https://x.com/yan5xu)
- [4.2K](https://x.com/yan5xu/status/2058151727333880265/analytics)
- [Upgrade to Premium](https://x.com/i/premium_sign_up)
- [7:43 PM · May 23, 2026](https://x.com/yan5xu/status/2058151727333880265)
- [4,255 Views](https://x.com/yan5xu/status/2058151727333880265/analytics)
---
*导出时间: 2026/5/24 09:01:12*