2026 年,是造 CLI 的最好时机
本文转载自:AGI Hunt
作者:J0hn
原文发布日期:2026年4月3日
CLI 已经 50 岁了,但你现在造一个,可能比任何时候都更应该。
有几件事正在同时发生:Go 和 Rust 让分发变成了一行 curl,Agent 让 CLI 从开发者工具升级成了软件的通用入口,钉钉飞书等大厂也亲自下场做 CLI 了。
二十年空白
从 1995 年到 2015 年,将近二十年,几乎没有什么像样的新 CLI 工具被造出来。
ls、grep、awk、sed、curl,这些我们今天还在用的工具,基本都诞生于 1970 到 1995 年之间。那是 Unix 的黄金年代,每一个命令都算得上是一件精密的手工艺品。
然后呢?GUI 来了、Web 来了、iPhone 来了……
整个软件行业的注意力,全部涌向了图形界面和移动端。开 CLI?那是上个时代的事了吧。
Gabe Venberg 在一篇被 Hacker News 广泛讨论的文章里写过:
“在 1995 到 2015 的二十年里,CLI 工具的创新几乎完全停滞了。不是因为没有需求,是因为没有人觉得这件事值得做。”
直到 Go 和 Rust 出现,局面才被打破。
CLI 工具的三波浪潮
为什么是这两门语言呢?答案是:单二进制分发。
以前你用 Python 写个 CLI,用户得先装 Python,再 pip install,还得处理版本冲突、虚拟环境……光是安装流程就能劝退一半人了。
Go 和 Rust 编译出来的,就是一个独立的二进制文件。一行 curl,下载,给执行权限,完事。不需要运行时,不需要依赖管理,不需要 Docker。
kubectl、docker、gh、hugo、terraform、ripgrep、bat、fd,你现在天天在用的这些工具,全是 Go 或 Rust 写的。
终端文艺复兴
2015 年之后发生的事情,现在被社区称为「终端文艺复兴」(Terminal Renaissance)。
最有代表性的,就是 Rust 社区把经典 Unix 命令重写了一遍:
| 经典命令 | 现代替代 | 语言 | 改进了什么 |
|---|---|---|---|
| grep | ripgrep | Rust | 快 10 倍,默认递归,自动跳过 .gitignore |
| cat | bat | Rust | 语法高亮,行号,Git diff 标记 |
| ls | eza | Rust | 彩色输出,树形视图,Git 状态 |
| find | fd | Rust | 语法更直觉,默认忽略隐藏文件 |
| cd | zoxide | Rust | 记住你常去的目录,模糊匹配 |
| du | dust | Rust | 可视化磁盘占用,彩色层级 |
| sed | sd | Rust | 正则语法更直觉,不用转义 |
这些工具可不只是「更快」而已。它们在体验层做了一次升级:默认彩色输出、.gitignore 感知、更友好的错误提示、更符合直觉的参数设计。
KDAB 在一篇文章里推荐了一个挺巧妙的渐进式迁移方案:在 .zshrc 里用 alias 把旧命令指向新工具(比如 alias ls="eza"),日常使用时你感觉还是在用老命令,实际跑的已经是新引擎了。脚本里的老命令也不会受影响。
而终端本身,也在进化。Warp 把终端做成了 IDE 风格的交互体验,Ghostty 用 GPU 加速渲染,Kitty 支持图片内联显示。
CLI 不再是「难用但高效」的代名词了。它,变好用了。
而这还只是第一波浪潮,给人类用的。接下来的第二波则更为汹涌,因为它,是 Agent 带来的。
Agent 来了
2024 年底到 2025 年初,CLI 的角色发生了一次质变。
Courier 的一篇技术博客里说道:
“LLM 的训练数据里有几百万条 man page、Stack Overflow 回答和 shell 脚本。你的 CLI 不需要教它怎么用,给它看一下 –help 就够了。”
CLI 是 LLM 的母语。MCP 倒更像是后天学的外语。
这也不只是我一个人的感受。ScaleKit 做了一组严格的 benchmark,拿 GitHub 官方 MCP 服务器和 gh CLI 做对照,跑了 75 轮实验。结果是 CLI 在 token 消耗上便宜 10 到 32 倍,可靠性 100%(MCP 只有 72%)。
Composio 的联合创始人跑到旧金山街头做了个现场投票:CLI 还是 MCP?结果 17 比 3,CLI 压倒性胜出。
然后,钉钉和飞书在同一周官方发布了自家的 CLI 工具。两家都没选 MCP。
CLI 从「开发者的效率工具」,变成了「软件的通用入口」。
Smithery 在一篇《MCP vs CLI Is the Wrong Fight》中给了一个更细致的判断框架:CLI 赢在 LLM 已有训练先验的本地工具(git、docker、ffmpeg 这些),MCP 赢在零训练数据的远程内部 API。
CircleCI 的技术博客也给了类似的结论:内循环用 CLI(速度和 token 效率),外循环用 MCP(团队规模和结构化鉴权)。
但不管怎么分,有一件事正变得越来越明确:
如果你想让 Agent 能操控你的产品,造一个 CLI 应该是性价比最高的选择。
那问题来了……用什么框架造呢?
五大框架
每个语言生态都有自己的 CLI 框架王者。我把五个最主流的拉了出来:
Go:Cobra
43,500 颗星,173,000 个项目在用。kubectl、docker、gh、hugo、terraform,基本上 Go 生态里有子命令的 CLI 都是它。
Cobra 的核心卖点是子命令树。你定义好 Command、Flags、Args 三件套,框架帮你自动生成 --help、shell 补全、参数校验。父命令的 PersistentFlags 会自动继承给所有子命令,不用重复定义。
适合场景:中大型 CLI,10 个子命令以上的那种。飞书 CLI 就用的它。
Python:Typer
18,300 颗星。FastAPI 的作者 Sebastián Ramírez 造的,底层包了 Click。
Typer 的设计哲学跟 FastAPI 一脉相承:用 Python 的类型标注来定义参数,框架自动推断类型、生成帮助文本、做参数校验。写起来确实快,几行代码就能出一个功能完整的 CLI。
1 | import typer |
适合场景:快速原型、数据处理脚本、Python 团队的内部工具。
劣势:需要 Python 运行时,分发始终是个大问题。
Rust:Clap
10,000+ 颗星,240,000 个项目在用。ripgrep、bat、fd、dust,前面提到的那波「终端文艺复兴」明星工具,背后的引擎基本都是 Clap。
Clap 提供两种风格:derive macro(用 struct 定义参数,编译期检查)和 builder(运行时动态构建)。derive 风格写起来相当舒服:
1 |
|
适合场景:高性能工具、追求极致启动速度和安全性的项目。
Node.js:Commander.js
28,000 颗星,每周 5 亿次下载。零依赖,启动只要 18 毫秒。
Commander.js 的定位就是简单可靠。也没什么花哨的功能,但 API 设计干净,文档清晰,社区庞大。如果你的团队是 Node.js 栈,Commander.js 基本就是默认选择了。
oclif(Salesforce 出品,9,400 颗星)则更重一些,TypeScript 优先,自带插件系统和 CLI 脚手架生成器,启动时间 85 毫秒。适合需要插件架构的企业级工具。
Java:Picocli
5,200 颗星。Java 生态里造 CLI 的现代选择。
Picocli 最大的卖点是支持 GraalVM native image 编译,可以把 Java CLI 编译成原生二进制,启动速度从秒级降到毫秒级。还支持 ANSI 彩色输出和 shell 补全。
适合场景:已有 Java 生态的企业团队,不想换语言但想要现代 CLI 体验的。
该选哪个
框架介绍完了,实际选型时,该怎么做呢?
Sam Newby 写过一篇文章叫《Why Building CLIs in Go is Better Than Rust》,核心观点其实挺实在的:
Go + Cobra + GoReleaser 这套组合,在「快速造出来、跨平台分发」这件事上,目前应该是最省心的。
Railway 团队则提供了一个值得参考的案例。他们把自家 CLI 从 Go 重写成了 Rust,获得了更好的类型安全和 UX 特性(模糊选择、JSON 输出),但代价是显著的团队投入和更长的编译时间。
而我自己的选型逻辑,则大概是这样:
- 需要跨平台单二进制分发? Go 或 Rust。Go 编译快、上手简单;Rust 性能更好、类型更安全,但学习曲线陡。
- 团队是 Python 栈? Typer。写起来最快,分发可以用 PyInstaller 打包,但体验跟原生二进制比还是差点意思。
- 已有 Node.js 工具链? Commander.js。不要为了造 CLI 换语言,生态一致性比语言性能重要得多。
- 子命令超过 10 个? Cobra 或 Clap。它们的子命令树管理能力,是 Typer 和 Commander.js 比不了的。
- 追求极致性能和长期打磨? Rust + Clap。代价是前期投入大,但长期维护体验确实最好。
对大多数场景来说,Go + Cobra 依然是最稳的选择。
不是因为它最强,是因为它从「造出来」到「分发出去」这条路径最短。
Cobra 怎么用
既然推荐了 Cobra,那就展开讲讲它好在哪。
Cobra 的核心概念其实就三个:Command、Flags、Args。
Command
Command 是用户能调用的动作:
1 | cmd := &cobra.Command{ |
Flags
Flags 是 --key value 形式的参数:
1 | // 本命令独有 |
这里的 PersistentFlags 是 Cobra 一个挺巧妙的设计。定义在父命令上的 persistent flag 会自动传递给所有子孙命令。比如 --format 定义在根命令上,那不管是 my-cli search users --format table 还是 my-cli task get --format csv,都能直接用,不用每个命令重复定义一遍。
Args
Args 是不带 -- 的位置参数:
1 | cmd := &cobra.Command{ |
组装命令树
然后用 AddCommand 把命令树组装起来:
1 | rootCmd.AddCommand(newTaskCmd()) // my-cli task ... |
最终形成这样一棵树:
1 | my-cli |
这棵树对 Agent 来说是特别友好的。Agent 发现命令的过程,本质上就是一个树搜索。先跑 my-cli --help,看到有 task、search、config 三个名词。然后跑 my-cli search --help,看到 users、tags、web。逐层缩小范围,每一步都有确定性的输出。
而 --help 的内容呢,Cobra 帮你全自动生成了:
1 | $ my-cli search users --help |
你只需要写好 Use、Short、Example 和 flag 定义,Cobra 就把帮助文本、参数校验、shell 补全这些活儿全包了。
当然 Cobra 也有缺点。简单工具用它有点重(就一个 --input 参数的话真不需要它),容易导致全局变量(flag 绑定到包级变量),脚手架工具 cobra-cli 也基本处于停更状态了。
但对 10 个命令以上的中大型 CLI 来说,Cobra 目前还是 Go 生态里没有争议的首选。
| 方面 | 标准 flag 包 | Cobra |
|---|---|---|
| 子命令路由 | 手写 switch os.Args |
自动树遍历 |
| –help | 手写维护 | 自动生成 |
| 必填参数 | 手动 if 检查 | MarkFlagRequired() |
| 共享参数 | 每个函数传参 | PersistentFlags() |
| Shell 补全 | 不支持 | bash/zsh/fish/powershell |
上手造一个
说了这么多框架和原理,到底实际操作起来是什么样的呢?
我自己最近造了一个内部用的 CLI 工具,用的就是 Go + Cobra。但跟传统的开发流程不太一样:全程用 Claude Code 辅助完成的。
流程大概是这样。我先整理了三样东西作为 context:
- 设计规范。就是我之前写的那套 Agent CLI 设计原则的 checklist:noun-verb 结构、长参数优先、JSON 输出、–dry-run 支持等等。
- 框架知识。Cobra 的核心用法、命令树组装方式、flag 系统的最佳实践。
- 业务需求。这个 CLI 需要操作哪些资源、支持哪些操作、输入输出格式是什么。
当然,最重要的,是基于我现有的代码库。
然后把这三样东西一起丢给 Claude Code,让它生成命令树骨架。出来的第一版骨架基本就一步到位了。命令结构是 noun-verb 的,flag 都有长格式,--help 的 Example 部分也都写好了。我在这个基础上调了一些参数命名、补了几个边界情况的错误处理,大概两三轮迭代就定型了。
有点让我意外的是 --format 全局参数的实现。我只在设计规范里写了「支持 JSON 和 table 两种输出格式」,Claude Code 就自动用了 Cobra 的 PersistentPreRun 钩子在根命令上解析 --format,所有子命令执行时 format 变量已经可用了。这个实现方式,恰好就是 Cobra 的最佳实践。
还有 --dry-run 的处理。Claude Code 让每个写操作命令都检查 dry-run flag,如果开启就只输出「将要执行的操作」的 JSON 预览,不真正执行。结构化的 diff 输出,Agent 可以直接解析。
整个过程下来,最大的感受就是:
瓶颈不再是「怎么写代码」,而是「怎么把需求描述清楚」。
设计规范写得越清晰,Claude Code 生成的代码质量就越高。框架的最佳实践喂得越准确,出来的代码就越符合行业惯例。
换句话说,2026 年造 CLI 的门槛已经低到什么程度了呢?
你把设计原则想清楚,把需求描述写明白,AI 帮你把代码写了。
当然了,AI 生成的代码,我还是会自己审的。小到参数校验的边界情况、错误信息的措辞、退出码的语义分配,大到逻辑和流程、架构,这些细节 AI 能给你一个 80 分的起点,剩下 20 分还得靠人来打磨。
但从零到八十分这段路,以前可能要一两周,现在 10 分钟就跑完了。全程只需要 2 小时。
Agent 友好清单
CLI 造出来了,怎么确保它对 Agent 足够友好呢?
Lightning Labs 在一个 PR 里系统性地实现了 10 个 agent-CLI 设计轴,算是目前我见过的最完整的实战参考了。InfoQ 也发过一篇《Keep the Terminal Relevant: Patterns for AI Agent Driven CLIs》,从设计模式的角度给了不少好建议。
综合这些资料和我自己的实践,精简成六条最关键的:
输出必须结构化。
--json或--format json输出数据到 stdout,进度条和日志全走 stderr。Agent 解析 JSON 是零成本的,解析人类可读的 table 输出……那就全靠猜了。退出码要有语义。别只用 0 和 1。参数错误用 2(Agent 知道该修正参数重试),资源不存在用 3(Agent 知道该跳过或创建),权限不足用 4(Agent 知道该提示用户授权)。Lightning Labs 甚至给
--dry-run定义了专门的退出码 10。支持 –dry-run。每个有副作用的操作都应该能先预览。输出结构化的 JSON diff,告诉 Agent 什么会被创建、修改或删除。
支持 –no-interactive。Agent 回答不了
Are you sure? [y/N]这种问题。2019 年 AWS CLI v2 把默认 pager 改成了less,直接导致全球数千个 CI 任务挂起。这个教训到今天都还有人在踩。schema 自省。让 Agent 能查询 CLI 自身的能力。
my-cli schema --all输出完整命令树的 JSON,my-cli schema search users输出某个命令的参数定义。飞书 CLI 已经实现了这个功能,Agent 用起来确实省了不少事。错误信息要能指导修复。错误输出包含四个要素:错误类型(机器可读)、描述(发生了什么)、修复建议(怎么解决)、是否可重试。Agent 犯了错之后,错误信息是它唯一的修复依据。
这些原则的详细版本和完整的开发 checklist,我之前整理成了一个开源项目,可以直接丢给 AI 来参考。
就现在
回头看 CLI 这五十年,其实就是三波浪潮。
第一波,1970 到 1995,Unix 黄金期。 一群天才在贝尔实验室和伯克利造出了 ls、grep、awk、sed、pipe,奠定了命令行的基石。
第二波,2015 到 2024,终端文艺复兴。 Go 和 Rust 让分发变简单了,Rust 社区把经典工具重写了一遍,CLI 变好用了。
第三波,就是现在。 Agent 让 CLI 从开发者工具升级为软件入口,AI 让造 CLI 的门槛降到了「写清楚需求就行」。
你现在造一个 CLI,有成熟的框架可以选(Cobra、Clap、Typer),有 AI 帮你写代码(Claude Code、Codex),有现成的设计规范可以参考(clig.dev、12 Factor CLI、agent-cli-guide)。
框架在,工具在,规范在,速度在。当然,需求也要在。









