Agent 正在变成 CLI 的一类正式用户。它不像人那样盯着屏幕敲命令,而是「读输出、做判断、发下一条」,中间没有人参与。这意味着过去那些为人类手速和耐心做的设计假设,很多都要重新算一遍。
Agent 拿外部能力通常走三条路:直接调 API、接 MCP、在终端跑命令。三者的底层都是对远端接口的封装,但 CLI 有两个绕不开的优势。第一,通用 Agent 基本都自带 Bash 工具,Codex、Claude Code、CowAgent、OpenClaw 都是如此,服务方不需要额外维护一个 MCP server。第二,鉴权、拼参数、翻页、错误处理这些脏活都被封进命令里,Agent 不用自己拼 HTTP 请求,也不必把整份接口文档塞进上下文,读一份精简的命令说明就能干活,省 token,也更少出错。
用户形态一变,设计约束就跟着变。本文以近期实践的一个开源 CLI 为例,把语言、授权、命令、Skill、分发、安全这六块的关键取舍拆开讲。
语言选型:先看分发,再看性能
给 Agent 用的 CLI,语言选择的硬指标其实只有三条。
运行环境不可预测。它可能跑在本地 Mac、Docker 容器、远程 Linux,你没法假设对方装了正确版本的 Node 或 Python。最稳的形态是下载一个二进制直接跑。
要能交叉编译。macOS / Linux / Windows 乘 amd64 / arm64,六个组合都得覆盖,一套代码编出来。
启动要快。Agent 会高频反复调用同一条命令,解释器冷启动那几百毫秒会被放大。
几个候选对照着看:Node / TypeScript 生态好、写得快,但依赖目标机有 Node 运行时,打成单文件产物体积也大。Python 同样卡在运行时和版本上,分发是老大难。Rust 单二进制、性能好,各项都满足,代价是开发效率和编译速度偏低。

Go 是折中得比较好的那个:单静态二进制,设 CGO_ENABLED=0 之后零依赖,交叉编译支持成熟,启动在毫秒级,写起来也不慢。最终选了 Golang,CLI 框架用 Cobra 处理命令解析和路由。产物既然是纯二进制,分发就能用一套 GoReleaser 配置同时产出 npm 包、Homebrew Cask 和 GitHub Release。
语言这事没有标准答案,看团队技术栈。但如果首要用户是 Agent,就必须优先选能编成零依赖单二进制的语言。分发顺不顺,直接决定 Agent 能不能自己把它装上。
授权:别让 API Key 一把钥匙开所有门
最省事的授权是让用户填 API Key。坑也很明显:长期有效、全量权限、明文落盘,泄露一次就是大事故,而且没法按操作细分权限。一个会被 Agent 自动调用、还要管各种资源的 CLI,用这种方式风险太高。
实践里走的是 OAuth 2.0 Device Authorization Grant,也就是设备码流程。用户在浏览器登录并按 scope 授权,CLI 拿到的是短期有效、可刷新、可精细授权、服务端随时能吊销的 access token。
gh、gcloud 这类传统 CLI 还有另一种 OAuth 实现:本地起一个临时 HTTP server 接浏览器回调。这套对 Agent 很不友好——Agent 可能跑在没有浏览器、没有图形界面、端口不对外开放的服务器上。设备码流程把授权动作放到任意一台有浏览器的机器上完成,CLI 侧只负责发起和轮询。
把登录拆成两阶段
人类登录时,CLI 可以自动弹浏览器然后一直轮询等结果。但 Agent 是「执行命令、拿结果、决定下一步」的循环。如果同一次工具调用里既吐登录链接又阻塞轮询,链接就传不回给用户,整个会话卡死。
所以 Agent 的登录被拆成异步两阶段。第一阶段立刻返回,返回的 JSON 里除了验证 URL 和设备码,还带一个给 Agent 看的 next_action,明确告诉它下一步该跑什么:
第二阶段开始轮询,每次最多等若干秒就返回,超时没完成就让 Agent 通过下一次工具调用再轮询。

–wait 这个参数用来区分两类用户:不传,默认阻塞到设备码过期,走人类的交互式登录;传 –wait,切到 Agent 的有界轮询路径。
授权页要说清三件事
授权登录页是用户直接做决策的地方,必须让人一眼看懂:是哪个账号、哪台设备、哪个 CLI 在请求授权;这次申请了哪些权限,按模块逐条列出 scope,让用户清楚到底给了 CLI 什么能力;授权成功后提示可以回终端,因为那边还在轮询,用户得知道流程走完了。
命令与参数:同一句话,两种输出
同一条命令,开发者想看清晰的表格和流式回复,Agent 要的是一次性返回、可直接解析的结构化数据。这两套诉求要在一份命令设计里同时满足。
–json 是全局参数,让命令输出结构化 JSON。给 Agent 的 Skill 里只要约定一句「总是加 –json」,它就能拿到稳定可解析的结果。
流式与非流式这块有个容易忽略的点。和大模型交互的命令,人类习惯 SSE 的打字机效果,但 Agent 通过 Bash 调用时输出会被管道重定向,流式会把内容切成碎片,解析起来很痛苦,它要的是一次性完整回复。这里没把 –stream / –no-stream 做成必选项,而是按运行环境自动选默认值:终端下默认流式;管道或重定向(Agent 的典型场景)下默认非流式;–json 时强制非流式。显式传参可以覆盖。
写操作要能试运行。破坏性、变更类命令支持 –dry-run,不实际发请求,只打印将要发送的 HTTP 请求。Agent 能在真正执行前预检参数,也多了一道安全校验。
输出分流和退出码这两件事,直接决定 Agent 能不能稳定判断结果。结果走 stdout,过程信息走 stderr。等待进度、更新通知、交互确认的问句这些,如果混进 stdout,Agent 解析 JSON 就会崩。分流之后它只管 stdout。
退出码也要区分,让 Agent 在失败时知道该重试还是该停。实现里设计了这么几个:0 成功、1 一般错误、2 参数错误、3 认证/权限问题、4 网络异常。权限不足(exit 3)时,错误信息里直接给出命令提示,比如 linkai auth login –scope “…”,Agent 看到提示会重新拉起授权,而不是反复重试同一条命令。
Skill:给 Agent 的一份扁平说明书
命令封好了,接下来是怎么让 Agent 知道该怎么调。靠反复 –help 试参数,试错成本太高。配套的 Skill 就是写给 Agent 看的 CLI 说明书。
结构尽量扁平:一个主 SKILL.md 做入口,各模块的命令说明放进 references/ 目录。

skills/linkai-cli/
├── SKILL.md # 主入口:全局说明 + 各模块简介 + 决策流程
└── references/ # 按模块拆分的细节:auth / install / admin ...
装进 Agent 后只占一个目录。原则是先让 Agent 读主 Skill 文件,需要模块细节再往 references 里钻。有些 CLI 把每个子模块都装成一个独立 Skill,这其实不好:Agent 失去了对整个 CLI 的全局视角,大量子 Skill 还会吃掉上下文。
Skill 通过 go:embed 打包进二进制,跟 CLI 版本严格锁定,另外实现了 skill install 命令,一键装技能。还有个细节:主 SKILL.md 里内置了一段安装说明,Agent 如果先拿到 Skill(比如从 Skill Hub 直接下载),也能顺着引导把 CLI 二进制装上。
分发与更新:让 Agent 一句话装好
分发要处理两样东西:CLI 二进制和 Agent Skill。理想状态是给 Agent 一句话,它自己就装完了。
渠道尽量铺开,不同平台和系统的开发者、Agent 都得能找到合适的下载方式。npm 适合有 Node 环境的机器,它屏蔽了操作系统和架构差异,自动挑对应的二进制并加进环境变量。一键安装脚本更适合无依赖环境,除了下 CLI,还会把 Skill 分发到常用 Agent 的技能目录里,Codex、Claude Code、Cursor、Openclaw、CowAgent 都覆盖。Homebrew、go install、GitHub Release 下载也一并支持。
要让 Agent 一句话装好,一份清晰的 install.md 很关键。把从零安装的完整步骤写成指南,用户把一句话发给 Agent 就能完成初始化和 Skill 安装。

版本更新是分发的延续,做了两层。被动通知:命令启动时后台拉最新版本号并缓存,命令结束时在 stderr 输出一行提示,引导更新。主动更新:update 命令自动检测初始安装方式,调对应的包管理器升级,同时同步 Skill。
安全:授权只是第一道门
登录授权解决的是「谁在用」,Agent 场景下还有几道必须补的防线。
权限最小化。scope 用 资源:动作 格式,比如 app:read、db:write。默认只给只读和内容生成类权限,变更、删除这类高危操作必须显式设置。这样即便 Agent 误操作,也越不过 CLI 的权限边界。
Token 存储。macOS 下优先把凭证放进系统钥匙串,其他平台落到文件并收紧权限。用服务端可撤销的 opaque token,不用自解析 JWT,登出时同步吊销服务端 token。
设备绑定。每个请求带设备 ID,服务端把 token 和设备绑在一起,降低泄露后的盗用风险。
危险字符过滤。Agent 接收的内容可能来自不可信来源,网页、外部输入都算,里面可能藏着不可见攻击字符:Bidi 覆盖、零宽字符、ANSI 转义。这些能改变命令语义或制造终端欺骗。CLI 在输入侧直接拒掉这类字符,输出侧同步剥离。

六条可以复用的判断
CLI 的设计思路和 GUI 产品差别很大;同样是 CLI,面向 Agent 和面向开发者又是两回事。把这套实践收一收,大致是六条:
语言选能编成单二进制的静态语言。
登录用设备码流程,权限确认放浏览器,给 Agent 提供分阶段登录命令。
命令与参数上,给人清晰的展示,给 Agent 结构化数据,同时容忍多余参数、支持操作预演、出错时给出下一步引导。
Skill 作为 Agent 的说明书,单目录、扁平结构,降低理解和上下文成本。
分发铺多渠道,配一份能直接发给 Agent 的安装手册。
安全除授权外,还要做权限最小化、凭证保护、设备绑定、风险内容过滤。
这套思路不局限于 CLI。结构化输出、容错设计、配套 Skill、顺畅分发、最小权限,放到 API、SDK 以及任何同时服务人和 Agent 的产品上,同样成立。
文中相关开源 CLI 项目的仓库 ID 为 4788。