帮公司将接口封装成 CLI + Skill 给 Agent 调用
最近帮公司将接口转成 CLI + Skill 的方式给 Agent 调用,过程遇到很多问题,最后干脆把这些经验沉淀成了一个SDK @renxqoo/agent-cli-sdk。只需要和 AI Agent 说"调哪个接口、字段怎么映射",它就能同时产出给 CLI 和给 Agent 用的 Skill 文件,认证、统一输出、类型化错误、渐进式披露全都内置。
我遇到了什么问题?
最初我们让 Agent 调用 curl 工具直接请求公司业务接口,或者写一份脚本直接调用接口,但很快发现走不通:
- 无法给每一个接口提供准确的参数校验,Agent 只能靠报错反复试;
- 没有统一的错误码和输出格式;
- 每次执行返回的格式可能都不一样,Agent 产生大量不确定性;
- 鉴权最麻烦:公司接口都要求登录态,凭证不能硬编码进脚本,也不能每次都让 Agent 向人要 token,token 过期后 Agent 更不知道该怎么办。
于是我写了 @renxqoo/agent-cli-sdk 来解决这些问题。其中鉴权的处理思路值得单独展开:
- 不让 Agent 碰凭证 :登录走 OAuth 2.1(默认 device 流程,终端里给个链接和 code 就能完成),凭证由 CLI 落盘到本地状态目录(如
~/.orders/credentials/orders.json),Agent 只管调命令,从头到尾接触不到 token; - token 过期自动刷新:CLI 检测到过期自动 refresh,Agent 无感知;
- 失败有确定信号 :刷新失败时统一抛
authentication类型错误、退出码 3,Agent 据此引导重新执行orders auth login,而不是盲目重试。
这套逻辑我封装成插件 defineAuth,后文"使用方式"一节有完整示例。
为什么是 CLI 而不是 MCP?
你可能注意到一个现象:Lark、MiniMax 等大厂在对外提供 AI 能力时,几乎都发布了官方 CLI 工具,把内部所有接口封装成命令供开发者和 Agent 使用。为什么不是直接用 MCP(Model Context Protocol)呢?
MCP 的局限性
MCP 确实在 Agent 工具生态中获得了不少关注,但它并不完美:
- 架构更重:MCP 需要常驻一个 Server 进程,增加部署和运维成本;
- 长驻上下文一:每次启动都会将MCP返回的Tools注入到上下文中,即便你没有使用它;
CLI 的天然优势
相比之下,CLI 是一个经过数十年验证的通用标准,它在 Agent 场景下有独特优势:
- Agent 天然适配 :CLI 工具通常有清晰的参数说明、帮助信息、统一的
--json输出、明确的退出码,这些都是 Agent 可靠调用的基础; - 管道与组合 :CLI 可以轻松通过 Unix 管道与其他命令组合(如
lark messages list | jq),而 MCP 无法直接参与这种生态; - 代码中直接调用 :任何语言都能通过
child_process或subprocess调用 CLI 命令,并在代码中处理返回的结构化数据,而 MCP 需要额外的客户端库和协议栈; - 标准化输出与错误处理:CLI 的 stdout/stderr 分离、退出码机制,天然适合自动化流程和错误分支处理。
核心思路:一份声明,三个同步产物
agent-cli-sdk 的核心思路是:用一次 defineCommand 声明,同时生成三个同步产物:
bash
defineCommand(name / description / zod / run)
│
├── CLI 人类和 Unix 管道可用(acme orders list | jq)
├── SKILL.md AI Agent 渐进式加载的技能描述(skills gen 自动生成)
└── agent dirs 同步到 ~/.claude、~/.codex 等目录(skills sync)
命令、文档、Agent 读到的东西永远是同一份。
此外,项目还内置了一个 skill 技能,教 Agent 如何使用 @renxqoo/agent-cli-sdk 根据 API 描述生成整个标准统一 CLI,不需要自己写代码。
核心亮点
- 🧩 Skill Factory:安装一个 skill,让 AI Agent 把任意公司 API 变成 CLI + Agent Skill
- 🔁 一次声明,多处同步:CLI 命令、SKILL.md、Agent 目录自动保持一致
- 🔐 OAuth 2.1 一行接入 :
defineAuth插件自动注入login / status / logout / register命令 - 📦 统一输出契约:成功和失败都有固定 JSON 结构,人和 Agent 都能可靠解析
- 🚦 9 类类型化错误 + 退出码:Agent 可以根据退出码自动分支处理
- 📚 渐进式披露:Agent 按需加载 Skill 详细内容,未使用的 API 更少的token消耗
- 🧱 插件系统:认证、日志、审计等都可以通过插件注入
- ⚡ TypeScript 优先,ESM-only,Node.js >= 20
使用方式
方式一:安装 Skill,让 Agent 自动生成 CLI
这是最快的路径,你甚至不用自己写代码。
1. 安装 Skill
直接将下面这段话发给你的 Agent:
text
请根据 https://skillhub.cn/install/skillhub.md,安装 @user_4998424d/agent-cli-builder。
2. 给 Agent 下任务
安装完成后,你可以直接给 Agent 下任务,例如:
text
将当前项目的所有接口都找出来,然后使用 agent-cli-builder skill 生成 CLI 应用。
Agent 会按照 SDK 契约自动生成 src/commands/*.ts 和 src/index.ts,包含 Zod 参数校验、统一错误处理等。
方式二:手动安装库,编写 CLI
如果你更喜欢自己掌控代码,可以手动安装 SDK:
bash
npm install @renxqoo/agent-cli-sdk
# 或
pnpm add @renxqoo/agent-cli-sdk
要求 Node.js >= 20,仅支持 ESM。
下面是一个完整的单命令 CLI 示例(30 行以内,无认证,公开数据):
ts
#!/usr/bin/env node
import { defineCli, defineCommand } from "@renxqoo/agent-cli-sdk";
import * as z from "zod";
import { realpathSync } from "node:fs";
import { fileURLToPath } from "node:url";
const app = defineCli({
name: "myapp",
description: "My data CLI",
baseUrl: "https://api.example.com",
commands: {
list: defineCommand({
name: "list",
description: "Query list",
args: {
schema: z.object({
limit: z.coerce.number().min(1).max(100).default(20),
}),
},
async run(ctx, args) {
const res = await ctx.get<{ items: Array<{ id: string; title: string }> }>("/items", {
limit: args.limit,
});
return { data: res.data.items, meta: { count: res.data.items.length } };
},
}),
},
});
function isMainEntry(): boolean {
try {
return realpathSync(process.argv[1] ?? "") === fileURLToPath(import.meta.url);
} catch {
return false;
}
}
if (isMainEntry()) app.run(process.argv.slice(2));
export default app;
运行时:
bash
myapp list --limit 5
如果需要 OAuth 认证,只需一行接入:
ts
import { defineCliApp, defineAuth } from "@renxqoo/agent-cli-sdk";
import { homedir } from "node:os";
import { join } from "node:path";
export default await defineCliApp({
name: "orders",
dir: join(homedir(), ".orders"), // 应用自己的状态目录
plugins: [
defineAuth({
credentialNamespace: "orders", // → config/orders.json + credentials/orders.json
baseUrl: "https://auth.example.com",
scope: "orders.read offline_access",
}),
],
commands: {},
});
// 自动注入:orders auth login / status / logout / register
支持 device(默认)、authorization_code + PKCE、client_credentials 三种 OAuth 2.1 流程。
核心 API 解析
defineCli(options) --- 组装 CLI
ts
defineCli({
name: 'orders', // 必填:命名空间
description: '...', // 必填
plugins: [authPlugin], // 可选:认证/日志/审计等插件
commands: { list, get }, // 必填:顶层命令 → orders list
namespaces: { orders: {...} }, // 可选:子命名空间 → orders orders list
baseUrl: 'https://api.x.com', // 可选:ctx.get/post/... 的后端地址
errorOnStatus: { 404: 'not_found', '5xx': 'server_error' }, // 可选
defaultFormat: 'auto', // 可选:'auto'(默认)| 'json' | 'human'
skillsDir: './skills', // 可选:启用内置 skills 命令
skillsTargets: [...], // 可选:同步目标(默认检测常见 Agent 目录)
})
defineCommand(spec) --- 声明命令
ts
import * as z from "zod";
defineCommand({
name: "get",
description: "Query a single order",
args: {
schema: z.object({
id: z.string().min(1).describe("Order ID"),
verbose: z.boolean().describe("Verbose output").default(false),
}),
pos: ["id"], // id 是位置参数,而不是同名 flag
},
humanFormat: (data) => `Order: ${data.id}`, // 可选:自定义人类可读输出
async run(ctx, args) {
const res = await ctx.get(`/orders/${args.id}`); // ctx.get/post/put/patch/delete
return { data: res.data };
},
});
Zod schema 是唯一的参数校验和类型来源。args.type 默认为 argv,也可以设为 json 来通过 --input / --input-file / stdin 接收完整结构化输入。
defineAuth(opts) --- OAuth 2.1 工厂
返回一个 Plugin,直接放入 defineCliApp({ plugins: [auth] }),认证相关命令自动挂载。
Plugin(钩子 + 提供者)
ts
const myPlugin = {
name: "audit",
enforce: "pre", // 'pre' | 'post'(默认 normal)
provides: {
commands: { telemetry: telemetryCmd }, // 贡献命令
namespaces: { admin: { users: userCmd } },
},
async beforeRequest(ctx, req) {
return { ...req, headers: { ...req.headers, "x-client": "my-cli" } };
},
async transformOutput(ctx, data) {
return data;
},
async handleUnauthorized(ctx, event) {
return { action: "decline" };
},
};
插件提供的命令会自动豁免该插件自身的 beforeCommand,但不会豁免其他插件。
统一输出契约
这是该 SDK 对 Agent 友好的关键设计之一:成功和失败都有固定 JSON 结构。
成功输出(stdout):
json
{"ok":true,"source":"orders","data":{"orders":[...]},"meta":{"count":2,"pagination":{"complete":true}}}
错误输出(stderr):
json
{
"ok": false,
"error": {
"type": "api",
"subtype": "not_found",
"message": "Order not found",
"hint": "Check the ID"
}
}
退出码表
| 退出码 | 分类 | 含义 |
|---|---|---|
| 0 | --- | 成功 |
| 1 | api | 服务端业务错误(404/500/429...) |
| 2 | validation | 参数校验失败 |
| 3 | authentication / authorization / config | 未登录 / 无权限 / 配置缺失 |
| 4 | network | DNS / 超时 / 连接拒绝 |
| 5 | internal | SDK 内部错误 |
| 6 | policy | 风控拦截 |
| 10 | confirmation | 高风险写操作需要 --yes |
配套 9 个类型化错误类:ValidationError / AuthenticationError / PermissionError / ConfigError / NetworkError / APIError(含 NotFoundError)/ PolicyError / InternalError / ConfirmationRequiredError。始终使用 errs.* 抛出,裸 Error 会被降级为 internal/unknown。
输出模式默认 auto:TTY 环境输出人类可读文本,管道/脚本环境自动切 JSON。Agent 和脚本应始终传 --json。
Skills 与渐进式披露
这是该项目区别于普通 CLI 框架的最大特色。
内置命令:
mycli skills gen mycli --init--- 生成带自动命令表的SKILL.md骨架mycli skills gen mycli--- 只刷新自动生成的区块,保留手写语义mycli skills sync--- 将技能复制到已安装的 Agent 目录(~/.agents始终同步;~/.claude/~/.codex/~/.cursor等存在时自动检测)mycli skills list/mycli skills read <name>--- 列出/读取内置技能
Agent 会懒加载 技能:先只读取 name + description,当任务匹配时才展开完整 SKILL.md,按需读取 references/。未用到的 API 不消耗 token。
适合谁用?
- 需要给公司内部 API 快速提供 CLI 的团队
- 正在构建 AI Agent 工具链,希望 CLI 和 Agent 能力保持同步的开发者
- 希望统一输出格式、错误处理、认证流程的 CLI 框架爱好者
- 想用"技能工厂"模式让 Agent 自动生成工具的探索者
结语与想法
@renxqoo/agent-cli-sdk 把公司数据接口快速构建成标准统一输出的 CLI + Skill 工具。它把"写命令""写文档""给 Agent 适配"三件事合并成一次声明,并且通过统一输出契约和类型化错误,让 Agent 能可靠地使用工具获取接口数据。
然而 CLI + Skill 有个前提:必须跑在能执行 shell 命令的环境里。本地使用的Claude code、Codex、workbuddy 都满足;云端 Agent 就算提供 sandbox,鉴权也会成为新的问题。
云端使用时,OAuth 登录需要人交互完成,而云端 Agent 只能通过对话框和你交互;Agent 平台普遍不提供凭据注入,你只能往环境变量里预置一个短期 token------过期就得重新登录,想存长期的刷新 token 又有泄露风险。
如果 Agent 是自己公司部署的,可以这么解决:在 CLI 与后端接口之间加一层网关做请求转发------CLI 发请求,网关在转发时自动替它戴上 token 再访问真实接口,凭证始终留在自己控制的内网里,Agent 在 sandbox 里全程接触不到。前提是网关自身必须做好访问控制------限定内网、绑定调用方身份、签发短期 token、留存审计日志,降低鉴权风险。
如果你也在寻求 CLI + Skill 的接入方案,不妨给它一个 Star ⭐ 试试看。