面向 Agent 的 CLI 设计实践
随着 Agent 技术走向成熟,越来越多的产品需要同时面对人类和 Agent 两类用户,而 CLI(命令行工具)正是更容易被 Agent 调用的形态。未来可能所有产品都需要实现自己的 CLI,封装自己的核心能力供 Agent 使用。本文以近期实践的开源 CLI 为例,从编程语言、登录授权、命令设计、Skill、分发和安全六个方面,探讨如何设计一个对 Agent 友好的 CLI 工具。
为什么是 CLI
Agent 访问外部能力主要有三种方式:调用 API、通过 MCP、以及在终端执行命令。三者其实底层都是对远程接口的封装,但 CLI 对 Agent 有两个独特优势:一是几乎所有通用 Agent 都自带 Bash 工具 (例如 Codex、Claude Code、CowAgent、OpenClaw 等),无需服务提供方搭建 MCP server;二是它把鉴权、参数组装、分页、错误处理等封装进命令,Agent 不用自己构造 HTTP 请求,也不用把整份接口文档塞进上下文,只读一份精简的命令说明就能按需决策,省 token 也更准确。
但随之而来的是使用者的形态也发生了改变,过去 CLI 的主要用户是坐在终端前的开发者,现在多了一个 "读取输出 - 做决策 - 执行下一条命令" 的 Agent。两类用户的诉求有诸多差异,如何同时满足人类和 Agent 的使用体验,就成为了设计上的一个重要课题。下文以一个平台产品的 CLI 设计实践为例,对过程中的关键设计决策进行介绍,整体思路如下图:

一、编程语言
CLI 在开发语言选择上有几个硬指标:
- 单文件且无运行时依赖:Agent 的运行环境不可预测,可能是本地计算机、Docker 容器、远程 Linux 等,无法假设它装了正确的 Node 或 Python 版本,最好是下载一个二进制就能跑
- 可交叉编译:需要覆盖 macOS / Linux / Windows × amd64 / arm64,支持一套代码跨平台编译
- 启动快:Agent 会高频反复调用命令,静态语言是首选
几个候选对照:
- Node / TypeScript:生态好、写得快,但依赖目标机器有 Node 运行时,打包成单文件产物大
- Python:同样受运行时和版本问题限制,分发是大问题
- Rust:单二进制、性能好,各项都满足,但是开发效率和编译速度偏低
- Go:单静态二进制,设置
CGO_ENABLED=0后零依赖,交叉编译支持好,启动毫秒级,开发效率不错
最终选择 Golang 作为开发语言,并使用 Cobra 作为 CLI 框架,处理命令解析和路由。由于产物是纯二进制,分发时可以用一套 GoReleaser 配置同时产出 npm 包、Homebrew Cask 和 GitHub Release。语言选择本身没有标准答案,取决于团队技术栈,但如果首要用户是 Agent,则必须优先考虑能编译成零依赖单二进制的语言,分发的便捷程度,直接决定 Agent 能不能自己把它快速装上。
二、登录与授权
最简单的 CLI 授权方式是让用户直接填写 API Key,但问题也很明显:API Key 通常长期有效、全量权限、明文存储,一旦泄露风险很大,也无法按操作细分权限。对于一个会被 Agent 自动调用、管理各类资源的 CLI 来说,这种 "一把钥匙开所有门" 的方式风险太高。
所以实践中采用了 OAuth 2.0 Device Authorization Grant(设备码流程) 的方案,用户在浏览器登录并按 scope 授权,CLI 拿到的是短期有效、可刷新、可精细授权、服务端可随时吊销的 access token。整个流程分三步:

另外,很多传统 CLI (例如 gh、gcloud ) 会有另一种 OAuth 的实现,通过本地起一个临时 HTTP server 接收浏览器回调的方式。但它对 Agent 其实并不友好,因为 Agent 可能运行在没有浏览器和图形界面、端口不开放的服务器环境。而设备码流程支持授权过程在任意一台机器的浏览器上完成,CLI 侧只做发起和轮询。在整个授权过程有两个关键阶段:
1. 分阶段轮询
人类用户手动登录时,CLI 可以自动打开浏览器并一直轮询等待。但 Agent 是「执行命令 → 拿结果 → 决定下一步」的循环,如果在同一次工具调用里既要输出登录链接又要轮询,链接就没法通过模型返回给用户,从而阻塞整个会话。
所以在实现中将 Agent 的登录拆成了异步的两阶段。第一阶段立即返回,返回的 JSON 除了验证 URL 和设备码,还带一个给 Agent 看的 next_action,明确告诉它下一步该跑什么命令:
linkai auth login --no-wait --json第二阶段开始轮询,每次最多等待若干秒后返回,超时未完成时 Agent 可以通过下一次工具调用再次轮询:
linkai auth login --device-code <code> --wait 60 --json这里使用 --wait 参数区分了两类用户:不传(默认阻塞到设备码过期)走人类的交互式登录;传 --wait 则切到 Agent 的有界轮询路径。
2. 授权页面
授权登录页是用户直接交互和做决策界面,需要把三件事呈现清楚:
- 是哪个 账号/设备/CLI 在请求授权
- 这次申请了哪些权限,逐条列出不同模块的 scope,让用户清楚这次授权到底给了 CLI 什么能力
- 授权成功后提示可以回到终端,因为终端还在轮询,用户需要知道流程已走完

三、命令与参数
这一节介绍命令参数设计的细节,对于同一条命令,开发者希望看到清晰的表格和流式回复,而 Agent 则更需要一次性返回可解析的结构化数据。

1. JSON 格式输出
--json 是一个全局参数,让命令可以输出结构化的 JSON 格式数据,这样只需在给 Agent 的 Skill 中约定 "总是加 --json 参数",就会让 Agent 能拿到稳定可解析的结构化数据。
2. 流式与非流式
与大模型交互的这类命令,人类习惯流式(SSE)的打字机效果,但 Agent 通过 Bash 调用时输出会被管道重定向,流式会把内容切成碎片,不利于解析,它更需要的是一次性的完整回复。
这里没有把流式参数 (--stream/--no-stream) 作为必选项,而是根据运行环境自动选择默认值:在终端下默认流式,管道或重定向(Agent 的典型场景)下默认非流式,同时 --json 时强制非流式,显式传参可以覆盖。
3. 写操作试运行
对于破坏性/变更类的命令支持 --dry-run,可以不实际发起请求,只打印将要发送的 HTTP 请求。这样 Agent 可在真正执行前预检查参数是否正确,也增加了一道安全校验。
4. 输出分流与退出码
结果走标准输出(stdout),过程信息走标准错误(stderr)。像等待进度、更新通知、交互确认的问句这些过程信息,如果输出到 stdout,会影响 Agent 对 JSON 结构的解析,分流之后 Agent 只需处理标准输出即可。
同时对退出码也做了区分,让 Agent 能在失败时判断应该重试还是停止,在实现中设计了这样几个退出码:0 成功、1 一般错误、2 参数错误、3 认证/权限问题、4 网络异常。其中权限不足(exit 3)时,会在错误信息中给出命令提示(如 linkai auth login --scope "..."),Agent 根据提示会重新拉起授权,而非反复重试。
四、Skill 设计
CLI 通过命令实现了一系列接口的封装,下一步需要解决的是如何让 Agent 了解如何正确调用这些命令。如果只靠反复 --help 来查看参数显然试错成本太高,所以需要一个 Skill 来配套使用,这是一份写给 Agent 看的 CLI 说明书。
Skill 的结构尽可能做得扁平,一个主 SKILL.md 作为入口,各个模块的命令说明则放到 references/ 目录中。
skills/linkai-cli/
├── SKILL.md # 主入口:全局说明 + 各模块简介 + 决策流程
└── references/ # 按模块拆分的细节:auth / install / admin ...这样的结构安装到 Agent 中只会有一个目录,原则是优先让 Agent 看主 Skill 文件,需要了解模块细节的再深入 references 中查看。有些 CLI 会把每个子模块都安装为一个 Skill,这样其实并不友好,一是 Agent 没有对整个 CLI 的全局视角,二是大量的子 Skill 会增加 Agent 的上下文消耗。
在构建中,Skill 会通过 go:embed 打包进二进制,与 CLI 的版本严格锁定,同时实现了 skill install 命令,可以一键安装技能。
另外还有一个细节优化,在主 SKILL.md 里内置了一段安装说明,当 Agent 先拿到 Skill 时 (例如从 Skill Hub 中直接下载),也能根据引导顺利安装 CLI 二进制。
五、分发与更新
CLI 的分发设计对易用性的提升很关键,主要分发两部分内容:CLI 二进制和 Agent Skill。理想状态是给 Agent 一句话就能自己装好。在实现上建议尽量支持多种渠道,要让不同平台和系统下的开发者及 Agent 都能够找到合适的渠道顺利下载,例如:
| 方式 | 命令 |
|---|---|
| npm | npm i -g linkai-cli |
| 安装脚本 | curl -fsSL .../install.sh |
| Homebrew | brew install .../linkai |
| Go | go install .../linkai-cli@latest |
| GitHub Release | 下载二进制 |
对于有 Node 环境的机器,通过 npm 安装是最便捷的,因为可以屏蔽不同操作系统和平台架构的差异,自动选择安装合适的 CLI 二进制并加入环境变量。而一键安装脚本则是更好适配无依赖的环境,除了下载 CLI,还会把 Skill 一键分发到各常用 Agent 的技能目录中 (Codex、Claude Code、Cursor、Openclaw、CowAgent 等)。
为了进一步让 Agent 能一句话装好,一份清晰的安装说明 (install.md) 也至关重要,将从零安装的完整步骤写成指南,用户只要把一句话发给 Agent 就能一键完成 CLI 和 Skill 的初始化,例如:
阅读 https://cdn.link-ai.tech/cli/install.md 并按其中步骤安装 CLI 与 Skill,然后开始使用。

版本更新是分发的延续,做了两层实现:
- 被动通知:命令启动时后台拉取最新版本号并缓存,命令结束时在 stderr 输出一行提示,引导完成更新
- 主动更新:
update命令自动检测初始安装方式,调用对应包管理器升级,并自动同步 Skill
六、安全性
登录授权只是第一道防护,在 Agent 使用场景还有几个关键点:
- 权限最小化: 权限 scope 用
资源:动作格式(例如app:read、db:write),默认只授予只读和内容生成类权限,对于变更、删除等高危操作须显式设置,这样即使 Agent 误操作也无法越过 CLI 的权限管控 - Token 存储: 在 macOS 下优先将凭证存入系统钥匙串,其他平台存储至文件(权限
0600),使用服务端可撤销的 opaque token(而非自解析 JWT),在登出时同步吊销服务端 token - 设备绑定: 每个请求携带设备 ID,服务端将 token 与设备绑定,降低 Token 泄露后的盗用风险
- 危险字符过滤: Agent 接收的内容可能来自不可信来源(网页、外部输入等),存在不可见攻击字符的风险,例如:Bidi 覆盖、零宽字符、ANSI 转义等,可能改变命令语义或制造终端欺骗,因此在 CLI 输入侧就拒绝这类危险字符,并在输出侧同步剥离。
小结
CLI 的设计思路和 GUI 产品大相径庭,而同样是 CLI,面向 Agent 和面向开发者也有不少差异。回顾全文,可以归纳为几条思路:
- 语言选择:优先选择能够编译成单二进制的静态语言
- 登录授权:采用设备码流程,权限确认在浏览器完成,并面向 Agent 提供分阶段登录
- 命令与参数:为开发者提供清晰展示,为 Agent 提供结构化数据,同时容忍多余参数、支持操作预演、错误时给出下一步引导
- Skill 配套:作为给 Agent 的说明书,采用单目录、扁平结构,降低理解和上下文成本
- 分发安装:支持多渠道分发,并配一份能发给 Agent 的安装手册
- 安全防护:除登录授权外,还要支持权限最小化、凭证保护、设备绑定、风险内容过滤等
这些思路其实不限于 CLI。结构化输出、容错设计、配套的 Skill、顺畅的分发、最小权限,同样适用于 API、SDK 以及其他面向 "人与 Agent 双用户" 的产品。
本文相关的开源 CLI 项目:github.com/MinimalFuture/linkai-cli
