第10章 把Agent装进CLI作者光谷老亢| 源码地址https://github.com/kang-airtc/cli-mini-book走完前九章读者手里已经有一份能用 npm 与 curl 两种渠道分发、布局规范、Agent 友好的命令行工具。但严格来说目前这份 CLI 还只是一个壳子它有完整的命令树、漂亮的输出、稳定的退出码唯独没有 Agent 的能力。这一章把第 1 章提出的问题接续完整现在 CLI 已经准备好了下一步如何把真正的 Agent 能力装进去。10.1 三条升级路径10.1.1 升级路径概览如图 10-1 所示从普通 CLI 升级为 Agent 工具有三条互补路径。第一条是给 CLI 加上调用大模型的能力自己成为一个微型 Agent。第二条是让 CLI 被 Claude Code、OpenCode 等成熟 Agent 当作外部工具调用。第三条是双形态设计CLI 既给人用、也给 Agent 调度两条接口共用同一份业务逻辑。三条路径并不互斥可以同时铺设。10.1.2 三条路径的适用场景读者具体选哪条路径取决于场景需求。三种路径的适用条件如表 10-1 所示。表 10-1 三条升级路径的适用场景与实现成本路径适用场景实现成本内嵌大模型CLI 本身是产品入口用户期望直接对话中等调 SDK、管 token、做流式被Agent调用CLI 已具备具体业务能力数据库查询、API 操作低稳定 JSON 输出即可双形态CLI 既要给人手动使用也要被工作流编排中高维护两套调用契约如表 10-1 所示被 Agent 调用是成本最低的路径因为前面五章已经把这条契约的关键基础稳定退出码、JSON 输出模式、stderr 隔离都做好了。下文按从易到难展开三条路径。10.2 路径一CLI被Agent当工具调用10.2.1 工具描述清单Claude Code、OpenCode 等支持外部工具的 Agent都通过某种形式的工具描述清单了解可用工具。以 Claude Code 为例可以在项目根目录的 .claude/settings.json 里声明允许调用的命令。{permissions:{allow:[Bash(my-agent-py status --output json),Bash(my-agent-py models --output json)]}}把 my-agent-py 的命令列入 allow 清单后Claude Code 在执行 Bash 工具调用时不会因这些命令触发权限确认。这是把 CLI 接入 Agent 的最简方式不用写适配代码、不用注册 MCP只需要保证命令的输出可解析。10.2.2 stdin/stdout/exitcode的契约Agent 与 CLI 的协作完全建立在三条契约上标准输入提供输入数据、标准输出返回结构化结果、退出码表示成败。下面这段示例展示了一种典型的 Agent 调用模式。# Agent 用 echo 注入参数读取 stdout 解析result$(echo{query:latest release}|my-agent-py search--stdin--outputjson)# 通过退出码判断成败if[$?-ne0];thenecho命令失败2exit1fi# 用 jq 解析结构化结果echo$result|jq.items[0].title任何 Agent 都能通过类似流程调用 my-agent-py。读者要确保三件事JSON 输出永远写到 stdout 一次性完整输出、错误一律走 stderr、退出码语义按第 5 章约定。这些前五章都已做完路径一几乎是零额外工作。注意与 Agent 协作的 CLI 切忌在 stdout 里穿插进度条、彩色标题、emoji 标记。这些内容对人类有帮助对 JSON 解析器是噪声。最稳妥的做法是检测--output json标志后把所有“装饰类”输出全部禁用只保留纯净数据流。10.3 路径二让CLI自己具备Agent能力10.3.1 在子命令里调用大模型让 CLI 自己成为一个微型 Agent本质上是把 OpenAI、Anthropic 或本地推理服务的 SDK 嵌入到某个子命令里。下面这段 Python 示例展示了一个最小的 chat 子命令。importosimportclickfromanthropicimportAnthropiccli.command()click.argument(prompt)click.option(--model,defaultclaude-opus-4-7)defchat(prompt,model):与大模型对话clientAnthropic(api_keyos.environ[ANTHROPIC_API_KEY])respclient.messages.create(modelmodel,max_tokens1024,messages[{role:user,content:prompt}],)click.echo(resp.content[0].text)这段代码把 my-agent-py 升级为可以直接调用模型的工具。读者敲my-agent-py chat 重构这段代码CLI 会把提示词发送给 Anthropic把回复打印到 stdout。10.3.2 流式输出的处理实际产品里模型响应往往是流式streaming的需要逐 token 打印以减少用户等待感。下面是流式版本的核心改动。withclient.messages.stream(modelmodel,max_tokens1024,messages[{role:user,content:prompt}],)asstream:fortextinstream.text_stream:click.echo(text,nlFalse)click.echo()# 结束加一个换行nlFalse让 click.echo 不自动添加换行保证流式输出在终端里像打字机一样连续。对于--output json模式则应当切回非流式调用等待完整响应后一次性输出 JSON避免半 JSON 半文本的混合状态。10.3.3 API密钥的管理把模型 SDK 嵌入 CLI 后最棘手的问题不是技术而是凭证管理。读者应当为 CLI 设计一个稳健的密钥来源链环境变量优先、配置文件次之、命令行参数兜底但仅用于一次性调用。defresolve_api_key():# 1. 环境变量ifkey:os.environ.get(ANTHROPIC_API_KEY):returnkey# 2. 配置文件cfgPath.home()/.my-agent/config.jsonifcfg.exists():returnjson.loads(cfg.read_text()).get(anthropic_api_key)# 3. 让用户配置raiseclick.UsageError(未找到 API 密钥。请设置 ANTHROPIC_API_KEY 环境变量或运行 my-agent-py config set anthropic_api_key value)绝对禁止把 API 密钥写在代码字面量里、commit 到 Git 仓库、或者在 --help 输出中显示。配置文件应当设置权限 0600避免同机器上其他用户读到。注意当 CLI 把模型调用能力开放给读者后需要把“模型调用”与“工具执行”两类操作的输出明确分开。一种做法是模型回复走 stdout模型调用的元数据token 用量、延迟、模型名走 stderr 的 debug 流。这样上游解析器读 stdout 就只看到内容调试与计量靠 stderr。10.4 路径三双形态共生10.4.1 双形态的核心业务逻辑剥离双形态指 CLI 同时承担“给人手动用”与“给 Agent 自动调度”两种角色。要让两种调用都顺畅关键在于把业务逻辑从输出渲染中剥离出来。下面以 my-agent-py 的一个 search 子命令为例。# 业务逻辑纯函数不关心输出defdo_search(query:str)-dict:itemsbackend.query(query)return{items:items,count:len(items),query:query}# 渲染层给人看 vs 给程序看cli.command()click.argument(query)click.option(--output,typeclick.Choice([text,json]),defaulttext)defsearch(query,output):datado_search(query)ifoutputjson:click.echo(json.dumps(data))else:console.print(f找到{data[count]}条结果)foritemindata[items]:console.print(f -{item[title]})do_search 是纯函数可以被单元测试覆盖、可以被另一个 Python 工具直接 import 调用、可以被这份 CLI 通过两种渲染方式包装。这种分层是 CLI 演化为复杂产品时必不可少的设计抽象。10.4.2 给Agent的额外约定双形态 CLI 还需要为 Agent 调用方提供一些额外约定便于自动化调度。常见做法包括以下几条在 JSON 输出里加入 schema 版本号、用 dry-run 模式让 Agent 预演命令副作用、对长任务输出进度可解析的 JSON 行NDJSON。这些约定在 CLI 设计初期就要规划避免后期重构。10.5 全书速查与延伸方向10.5.1 核心命令速查学完本书后读者已经掌握了一组贯穿“从空目录到全球分发”的命令。速查清单如表 10-2 所示。表 10-2 Agent CLI建设全流程核心命令速查阶段Node.jsPython初始化npm init -ypython -m venv venv安装框架npm install commanderpip install click rich本地链接npm install -g .pip install -e .编译/构建npm run buildpython -m build --wheel发布npm publishgh release create vX.Y.Z dist/*.whl一行安装npm install -gcurl -fsSL | bash卸载npm uninstall -gpip uninstall如表 10-2 所示两种语言的工具链可以一一对应。读者掌握其中一条路径后迁移到另一条路径只需要替换具体命令名思路完全一致。10.5.2 延伸方向走完这本书之后可以沿三条方向继续探索。第一条是 MCPModel Context Protocol模型上下文协议把 CLI 的能力暴露为 MCP 服务器让 Claude Code、OpenCode 通过标准协议而非命令行调用。第二条是 Skill 化把 CLI 的能力包装为 Agent Skill 文档提升被自动发现与组合的可能。第三条是私有 Registry在企业内部搭一份 npm registry 或 PyPI 镜像把 Agent CLI 的分发限定在组织边界内。笔者建议读者把第一条 MCP 路径作为最优先的延伸目标。当下 MCP 已经成为多家头部 Agent 厂商的事实标准把自己写的 CLI 同时支持“命令行调用 MCP 服务”两种形态能极大扩展工具的使用边界。注意从命令行 CLI 升级为 MCP 服务器最大的设计变化是从“一次调用一次进程”变为“长连接持续会话”。读者要重新考虑状态保持、并发请求、流式响应等问题。这些主题超出了本书范围建议参考 modelcontextprotocol.io 官方文档。10.6 结语整本书从一个反常识的问题出发在 Web 与 IDE 都已成熟的今天为什么 Agent 产品集体回到命令行。九章的实战回答了这个问题因为 CLI 的“双向接口”属性让一份实现同时服务于人类与机器因为它的接入成本被压到了不能更低因为它与 40 年来的 Unix 生态天然兼容。读者现在不仅掌握了把一份 Agent 能力打包成 CLI、发布到全球、被一行命令装上的完整链条也理解了这条链条背后每一步的设计取舍。接下来要把它真正用起来。读者可以从手头的一个真实需求入手——也许是把团队的某个内部 API 包装成 CLI、也许是把一段反复使用的提示词流程封装成命令——按照本书的路径一步一步把它变成一个被同事和 Agent 同时使用的小工具。这是把这本书的内容内化为手艺的最直接方式。
第10章 把Agent装进CLI
第10章 把Agent装进CLI作者光谷老亢| 源码地址https://github.com/kang-airtc/cli-mini-book走完前九章读者手里已经有一份能用 npm 与 curl 两种渠道分发、布局规范、Agent 友好的命令行工具。但严格来说目前这份 CLI 还只是一个壳子它有完整的命令树、漂亮的输出、稳定的退出码唯独没有 Agent 的能力。这一章把第 1 章提出的问题接续完整现在 CLI 已经准备好了下一步如何把真正的 Agent 能力装进去。10.1 三条升级路径10.1.1 升级路径概览如图 10-1 所示从普通 CLI 升级为 Agent 工具有三条互补路径。第一条是给 CLI 加上调用大模型的能力自己成为一个微型 Agent。第二条是让 CLI 被 Claude Code、OpenCode 等成熟 Agent 当作外部工具调用。第三条是双形态设计CLI 既给人用、也给 Agent 调度两条接口共用同一份业务逻辑。三条路径并不互斥可以同时铺设。10.1.2 三条路径的适用场景读者具体选哪条路径取决于场景需求。三种路径的适用条件如表 10-1 所示。表 10-1 三条升级路径的适用场景与实现成本路径适用场景实现成本内嵌大模型CLI 本身是产品入口用户期望直接对话中等调 SDK、管 token、做流式被Agent调用CLI 已具备具体业务能力数据库查询、API 操作低稳定 JSON 输出即可双形态CLI 既要给人手动使用也要被工作流编排中高维护两套调用契约如表 10-1 所示被 Agent 调用是成本最低的路径因为前面五章已经把这条契约的关键基础稳定退出码、JSON 输出模式、stderr 隔离都做好了。下文按从易到难展开三条路径。10.2 路径一CLI被Agent当工具调用10.2.1 工具描述清单Claude Code、OpenCode 等支持外部工具的 Agent都通过某种形式的工具描述清单了解可用工具。以 Claude Code 为例可以在项目根目录的 .claude/settings.json 里声明允许调用的命令。{permissions:{allow:[Bash(my-agent-py status --output json),Bash(my-agent-py models --output json)]}}把 my-agent-py 的命令列入 allow 清单后Claude Code 在执行 Bash 工具调用时不会因这些命令触发权限确认。这是把 CLI 接入 Agent 的最简方式不用写适配代码、不用注册 MCP只需要保证命令的输出可解析。10.2.2 stdin/stdout/exitcode的契约Agent 与 CLI 的协作完全建立在三条契约上标准输入提供输入数据、标准输出返回结构化结果、退出码表示成败。下面这段示例展示了一种典型的 Agent 调用模式。# Agent 用 echo 注入参数读取 stdout 解析result$(echo{query:latest release}|my-agent-py search--stdin--outputjson)# 通过退出码判断成败if[$?-ne0];thenecho命令失败2exit1fi# 用 jq 解析结构化结果echo$result|jq.items[0].title任何 Agent 都能通过类似流程调用 my-agent-py。读者要确保三件事JSON 输出永远写到 stdout 一次性完整输出、错误一律走 stderr、退出码语义按第 5 章约定。这些前五章都已做完路径一几乎是零额外工作。注意与 Agent 协作的 CLI 切忌在 stdout 里穿插进度条、彩色标题、emoji 标记。这些内容对人类有帮助对 JSON 解析器是噪声。最稳妥的做法是检测--output json标志后把所有“装饰类”输出全部禁用只保留纯净数据流。10.3 路径二让CLI自己具备Agent能力10.3.1 在子命令里调用大模型让 CLI 自己成为一个微型 Agent本质上是把 OpenAI、Anthropic 或本地推理服务的 SDK 嵌入到某个子命令里。下面这段 Python 示例展示了一个最小的 chat 子命令。importosimportclickfromanthropicimportAnthropiccli.command()click.argument(prompt)click.option(--model,defaultclaude-opus-4-7)defchat(prompt,model):与大模型对话clientAnthropic(api_keyos.environ[ANTHROPIC_API_KEY])respclient.messages.create(modelmodel,max_tokens1024,messages[{role:user,content:prompt}],)click.echo(resp.content[0].text)这段代码把 my-agent-py 升级为可以直接调用模型的工具。读者敲my-agent-py chat 重构这段代码CLI 会把提示词发送给 Anthropic把回复打印到 stdout。10.3.2 流式输出的处理实际产品里模型响应往往是流式streaming的需要逐 token 打印以减少用户等待感。下面是流式版本的核心改动。withclient.messages.stream(modelmodel,max_tokens1024,messages[{role:user,content:prompt}],)asstream:fortextinstream.text_stream:click.echo(text,nlFalse)click.echo()# 结束加一个换行nlFalse让 click.echo 不自动添加换行保证流式输出在终端里像打字机一样连续。对于--output json模式则应当切回非流式调用等待完整响应后一次性输出 JSON避免半 JSON 半文本的混合状态。10.3.3 API密钥的管理把模型 SDK 嵌入 CLI 后最棘手的问题不是技术而是凭证管理。读者应当为 CLI 设计一个稳健的密钥来源链环境变量优先、配置文件次之、命令行参数兜底但仅用于一次性调用。defresolve_api_key():# 1. 环境变量ifkey:os.environ.get(ANTHROPIC_API_KEY):returnkey# 2. 配置文件cfgPath.home()/.my-agent/config.jsonifcfg.exists():returnjson.loads(cfg.read_text()).get(anthropic_api_key)# 3. 让用户配置raiseclick.UsageError(未找到 API 密钥。请设置 ANTHROPIC_API_KEY 环境变量或运行 my-agent-py config set anthropic_api_key value)绝对禁止把 API 密钥写在代码字面量里、commit 到 Git 仓库、或者在 --help 输出中显示。配置文件应当设置权限 0600避免同机器上其他用户读到。注意当 CLI 把模型调用能力开放给读者后需要把“模型调用”与“工具执行”两类操作的输出明确分开。一种做法是模型回复走 stdout模型调用的元数据token 用量、延迟、模型名走 stderr 的 debug 流。这样上游解析器读 stdout 就只看到内容调试与计量靠 stderr。10.4 路径三双形态共生10.4.1 双形态的核心业务逻辑剥离双形态指 CLI 同时承担“给人手动用”与“给 Agent 自动调度”两种角色。要让两种调用都顺畅关键在于把业务逻辑从输出渲染中剥离出来。下面以 my-agent-py 的一个 search 子命令为例。# 业务逻辑纯函数不关心输出defdo_search(query:str)-dict:itemsbackend.query(query)return{items:items,count:len(items),query:query}# 渲染层给人看 vs 给程序看cli.command()click.argument(query)click.option(--output,typeclick.Choice([text,json]),defaulttext)defsearch(query,output):datado_search(query)ifoutputjson:click.echo(json.dumps(data))else:console.print(f找到{data[count]}条结果)foritemindata[items]:console.print(f -{item[title]})do_search 是纯函数可以被单元测试覆盖、可以被另一个 Python 工具直接 import 调用、可以被这份 CLI 通过两种渲染方式包装。这种分层是 CLI 演化为复杂产品时必不可少的设计抽象。10.4.2 给Agent的额外约定双形态 CLI 还需要为 Agent 调用方提供一些额外约定便于自动化调度。常见做法包括以下几条在 JSON 输出里加入 schema 版本号、用 dry-run 模式让 Agent 预演命令副作用、对长任务输出进度可解析的 JSON 行NDJSON。这些约定在 CLI 设计初期就要规划避免后期重构。10.5 全书速查与延伸方向10.5.1 核心命令速查学完本书后读者已经掌握了一组贯穿“从空目录到全球分发”的命令。速查清单如表 10-2 所示。表 10-2 Agent CLI建设全流程核心命令速查阶段Node.jsPython初始化npm init -ypython -m venv venv安装框架npm install commanderpip install click rich本地链接npm install -g .pip install -e .编译/构建npm run buildpython -m build --wheel发布npm publishgh release create vX.Y.Z dist/*.whl一行安装npm install -gcurl -fsSL | bash卸载npm uninstall -gpip uninstall如表 10-2 所示两种语言的工具链可以一一对应。读者掌握其中一条路径后迁移到另一条路径只需要替换具体命令名思路完全一致。10.5.2 延伸方向走完这本书之后可以沿三条方向继续探索。第一条是 MCPModel Context Protocol模型上下文协议把 CLI 的能力暴露为 MCP 服务器让 Claude Code、OpenCode 通过标准协议而非命令行调用。第二条是 Skill 化把 CLI 的能力包装为 Agent Skill 文档提升被自动发现与组合的可能。第三条是私有 Registry在企业内部搭一份 npm registry 或 PyPI 镜像把 Agent CLI 的分发限定在组织边界内。笔者建议读者把第一条 MCP 路径作为最优先的延伸目标。当下 MCP 已经成为多家头部 Agent 厂商的事实标准把自己写的 CLI 同时支持“命令行调用 MCP 服务”两种形态能极大扩展工具的使用边界。注意从命令行 CLI 升级为 MCP 服务器最大的设计变化是从“一次调用一次进程”变为“长连接持续会话”。读者要重新考虑状态保持、并发请求、流式响应等问题。这些主题超出了本书范围建议参考 modelcontextprotocol.io 官方文档。10.6 结语整本书从一个反常识的问题出发在 Web 与 IDE 都已成熟的今天为什么 Agent 产品集体回到命令行。九章的实战回答了这个问题因为 CLI 的“双向接口”属性让一份实现同时服务于人类与机器因为它的接入成本被压到了不能更低因为它与 40 年来的 Unix 生态天然兼容。读者现在不仅掌握了把一份 Agent 能力打包成 CLI、发布到全球、被一行命令装上的完整链条也理解了这条链条背后每一步的设计取舍。接下来要把它真正用起来。读者可以从手头的一个真实需求入手——也许是把团队的某个内部 API 包装成 CLI、也许是把一段反复使用的提示词流程封装成命令——按照本书的路径一步一步把它变成一个被同事和 Agent 同时使用的小工具。这是把这本书的内容内化为手艺的最直接方式。