Agent 如何快速调用公司接口?——CLI + Skill 实践与踩坑

36 阅读9分钟

帮公司将接口封装成 CLI + Skill 给 Agent 调用

最近帮公司将接口转成 CLI + Skill 的方式给 Agent 调用,过程遇到很多问题,最后干脆把这些经验沉淀成了一个SDK @renxqoo/agent-cli-sdk。只需要和 AI Agent 说“调哪个接口、字段怎么映射”,它就能同时产出给 CLI 和给 Agent 用的 Skill 文件,认证、统一输出、类型化错误、渐进式披露全都内置。

项目地址:github.com/renxqoo/age…


我遇到了什么问题?

最初我们让 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_processsubprocess 调用 CLI 命令,并在代码中处理返回的结构化数据,而 MCP 需要额外的客户端库和协议栈;
  • 标准化输出与错误处理:CLI 的 stdout/stderr 分离、退出码机制,天然适合自动化流程和错误分支处理。

核心思路:一份声明,三个同步产物

agent-cli-sdk 的核心思路是:用一次 defineCommand 声明,同时生成三个同步产物

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:

请根据 https://skillhub.cn/install/skillhub.md,安装 @user_4998424d/agent-cli-builder。

2. 给 Agent 下任务

安装完成后,你可以直接给 Agent 下任务,例如:

将当前项目的所有接口都找出来,然后使用 agent-cli-builder skill 生成 CLI 应用。

Agent 会按照 SDK 契约自动生成 src/commands/*.tssrc/index.ts,包含 Zod 参数校验、统一错误处理等。

方式二:手动安装库,编写 CLI

如果你更喜欢自己掌控代码,可以手动安装 SDK:

npm install @renxqoo/agent-cli-sdk
# 或
pnpm add @renxqoo/agent-cli-sdk

要求 Node.js >= 20,仅支持 ESM。

下面是一个完整的单命令 CLI 示例(30 行以内,无认证,公开数据):

#!/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;

运行时:

myapp list --limit 5

如果需要 OAuth 认证,只需一行接入:

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 + PKCEclient_credentials 三种 OAuth 2.1 流程。


核心 API 解析

defineCli(options) — 组装 CLI

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) — 声明命令

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(钩子 + 提供者)

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):

{"ok":true,"source":"orders","data":{"orders":[...]},"meta":{"count":2,"pagination":{"complete":true}}}

错误输出(stderr):

{
  "ok": false,
  "error": {
    "type": "api",
    "subtype": "not_found",
    "message": "Order not found",
    "hint": "Check the ID"
  }
}

退出码表

退出码分类含义
0成功
1api服务端业务错误(404/500/429…)
2validation参数校验失败
3authentication / authorization / config未登录 / 无权限 / 配置缺失
4networkDNS / 超时 / 连接拒绝
5internalSDK 内部错误
6policy风控拦截
10confirmation高风险写操作需要 --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 ⭐ 试试看。

项目地址:github.com/renxqoo/age…