ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

为什么你的 CLI 需要一份规范?深入理解 Usage——CLI 界的 OpenAPI 完全解读

为什么你的 CLI 需要一份规范?深入理解 Usage——CLI 界的 OpenAPI 完全解读

【免费下载链接】usage

A specification for CLIs

项目地址:https://gitcode.com/gh_mirrors/usage/usage
点击查看免费下载

写 CLI 的人大多有过这种烦恼:帮助信息写在代码里,补全脚本另写一份,文档手工维护,三者经常对不上。Usage 就是为此而生的CLI 规范(CLI specification):你用一份 KDL 文件描述命令、标志(flags)、参数和帮助文本,然后由它统一生成 shell 补全、--help输出、Markdown 参考文档、man page,甚至 TypeScript / Python 类型化 SDK。一句话概括:Define your CLI once. Generate the tools around it.

一、问题:CLI 的"接口契约"散落各处

回想一下 HTTP 服务的世界:OpenAPI 让接口文档、客户端、Mock 都来自同一份定义。而 CLI 呢?

  • 帮助文本 → 散落在代码字符串里
  • 补全脚本 → 手写,或者用框架自动生成但无法自定义
  • 文档站 → 复制粘贴,版本漂移
  • 调用方的代码 → 没有类型信息,全靠猜

结果是同一份接口信息被维护了 N 份,而且没有任何工具能检查"这次改动是不是 breaking change"。

Usage 的思路和 OpenAPI 完全一致:先把 CLI 的接口写成一份规范,其余产物全部从规范派生。🎯

二、什么是 Usage?三个身份,一份规范

Usage 同时是三样东西(详见 README.md):

身份说明
📄一份可移植的 KDL 规范*.usage.kdl文件,描述命令树、标志、参数、配置、补全
🛠️一条命令行工具usage,负责 lint、生成补全/文档/manpage/SDK、检查接口 diff
⚙️一个 Rust 框架usage-rs,从 struct 派生类型化解析器,并导出可移植规范

规范语言选的是KDL——"读起来像配置文件,嵌套起来像 XML"。语法非常直觉,看这个最小示例(取自 docs/spec/index.md):

bin "mycli" about "Process project files" flag "-f --force" help="Overwrite existing output" flag "-v --version" help="Print the CLI version" arg "<dir>" help="The directory to use" // 尖括号 = 必填 arg "[file]" help="The file to read" // 方括号 = 可选

<file>表示必填位置参数,[file]表示可选,key=value属性补充元数据——十行就能把一个小工具的接口讲清楚。

三、核心能力:一份规范能生成什么?

1. 解析、帮助与 shell 补全

规范描述的就是接口语义:哪个标志全局(global=#true)、哪个取值有枚举(choices)、子命令如何嵌套。lib/src/complete/ 下实现了 bash、zsh、fish、PowerShell、Nu 五种 shell 的补全逻辑,全部从同一份规范派生,天然保持一致。

2. 文档与 man page 一键生成

usage generate markdown --file mycli.usage.kdl --out-file reference.md usage generate manpage --file mycli.usage.kdl --out-file mycli.1

参考实现见 cli/src/cli/generate/markdown.rs 与 cli/src/cli/generate/manpage.rs。接口变了?重新生成即可,文档漂移问题从根上消失。

3. 类型化 SDK:给"调用你 CLI 的代码"用

如果别的产品要通过子进程调用你的 CLI,Usage 可以生成TypeScript / Python SDK(见 lib/src/sdk/),调用方拿到的是类型化客户端而不是拼字符串——这正是 OpenAPI 生态里"代码生成客户端"在 CLI 世界的对应物。

4. 配置解析与优先级规则

规范可以声明 flag 由配置文件、环境变量或默认值回退支撑,优先级恒定为CLI flag > 环境变量 > 配置文件 > 默认值,实现位于 config/src/resolve.rs。相关规则详见 docs/spec/resolution.md。

5. 命令"效果"声明:为 AI Agent 时代做准备

比较有前瞻性的是effect字段,声明一条命令会对外部世界做什么:

effect含义
read只读检查,幂等
write创建/修改状态
destructive可能删除或不可逆覆盖,值得二次确认

AI 编码助手拿到这份规范后,可以直接获得一份"只读命令白名单",而不用每次都问用户。相关测试见 conformance/tests/effect.rs。

6. 接口 diff:CLI 版的 breaking change 检查

usage diff可以比较两版规范,在发布前发现移除标志、改名参数这类破坏性变更(实现见 cli/src/cli/diff.rs)。这对维护多版本 CLI 的团队非常实用。

四、四条接入路径:总有一条适合你

docs/index.md 把用户分成了四类,每类都有明确入口:

  1. 新建 Rust CLI:struct +#[usage(...)]派生,代码即规范(docs/rust/quickstart.md)
  2. 已有 CLI:从 clap、Cobra、kong、urfave 等框架导出规范,应用代码一行不改(docs/spec/integrations.md,Cobra 转换实现见 integrations/cobra/convert.go)
  3. Shell 脚本:在注释里声明参数,Usage 帮你解析并注入环境变量(docs/cli/scripts.md)
  4. 纯规范工作流:手写 KDL,用 CLI 生成一切产物(docs/guide/getting-started.md)

项目还内置了外部工具实测:benches/external/ 目录里有fd、starship、tokei的真实规范与"损失清单"(losses),诚实展示哪些接口特性规范能覆盖、哪些还差一点——这种透明度很少见。

五、快速上手:5 分钟体验完整闭环

# 安装(三选一) brew install usage # 或 mise use -g usage cargo install usage-cli --locked # 假设已写好 mycli.usage.kdl usage lint mycli.usage.kdl # 校验规范 usage generate completion zsh mycli --file mycli.usage.kdl --install # 装补全 usage generate markdown --file mycli.usage.kdl --out-file reference.md

规范语法与完整示例在 docs/spec/index.md,各节点(cmd、flag、arg、config、complete……)的字段级参考在 docs/spec/reference/。想看真实世界的规范长什么样,仓库自带了不少:examples/kitchen-sink.usage.kdl、examples/with-examples.usage.kdl,以及从 mise 等复杂工具导出的 benches/mise.usage.kdl。

六、为什么值得认真对待?

  • ✅单一事实源:帮助、补全、文档、SDK 永远同步
  • ✅生态互通:KDL 规范与语言/框架无关,Rust、Go(预览版,见 go/argv/)、脚本都能消费它
  • ✅可验证:lint抓错误,diff抓破坏性变更
  • ✅面向 AI 协作:effect声明让 Agent 安全地自动执行命令
  • ✅测试严谨:conformance/tests/ 下有上百个一致性测试,corpus/ 提供结构化用例集

如果你的 CLI 正在被用户、脚本、Agent 从多个入口调用,一份规范不再是"锦上添花",而是接口工程的基本盘——就像每个严肃的 API 都该有一份 OpenAPI 一样。

下一步建议:从你最头疼的那个 CLI 开始,试着手写一份 20 行的usage.kdl,运行一次usage lint和usage generate markdown,你就会明白这份规范的价值。🚀

【免费下载链接】usage

A specification for CLIs

项目地址:https://gitcode.com/gh_mirrors/usage/usage
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进