跪拜 Guibai
← Back to the summary

One Declaration Generates a CLI, Agent Skills, and Docs from Company APIs

Helping Companies Wrap Interfaces into CLI + Skills for Agent Invocation

Recently, I helped a company convert interfaces into CLI + Skill format for Agent invocation. The process encountered many problems, and in the end, I crystallized these experiences into an SDK: @renxqoo/agent-cli-sdk. You just need to tell the AI Agent "which interface to call and how to map the fields," and it can simultaneously produce Skill files for both CLI and Agent use. Authentication, unified output, typed errors, and progressive disclosure are all built-in.

Project address: https://github.com/renxqoo/agent-cli-sdk


What Problems Did I Encounter?

Initially, we had the Agent use the curl tool to directly request the company's business interfaces, or write a script to call the interfaces directly, but we quickly found this unworkable:

So I wrote @renxqoo/agent-cli-sdk to solve these problems. The authentication handling approach is worth expanding on separately:

I encapsulated this logic into the defineAuth plugin; the "Usage" section below has a complete example.


Why CLI Instead of MCP?

You might notice a phenomenon: Major companies like Lark and MiniMax, when providing external AI capabilities, almost all release official CLI tools, wrapping all internal interfaces into commands for developers and Agents to use. Why not use MCP (Model Context Protocol) directly?

Limitations of MCP

MCP has indeed gained considerable attention in the Agent tool ecosystem, but it's not perfect:

Natural Advantages of CLI

In contrast, CLI is a universal standard validated over decades, with unique advantages in Agent scenarios:


Core Idea: One Declaration, Three Synchronized Artifacts

The core idea of agent-cli-sdk is: Use a single defineCommand declaration to simultaneously generate three synchronized artifacts:

defineCommand(name / description / zod / run)
        │
        ├── CLI          Usable by humans and Unix pipes (acme orders list | jq)
        ├── SKILL.md     AI Agent progressively loaded skill description (auto-generated by skills gen)
        └── agent dirs   Synced to ~/.claude, ~/.codex, etc. directories (skills sync)

The command, documentation, and what the Agent reads are always the same thing.

Additionally, the project includes a built-in skill that teaches the Agent how to use @renxqoo/agent-cli-sdk to generate an entire standardized CLI based on an API description, without needing to write code yourself.


Core Highlights


Usage

Method 1: Install the Skill, Let the Agent Auto-Generate the CLI

This is the fastest path; you don't even need to write code yourself.

1. Install the Skill

Send the following directly to your Agent:

Please install @user_4998424d/agent-cli-builder according to https://skillhub.cn/install/skillhub.md.

2. Assign Tasks to the Agent

After installation, you can directly assign tasks to the Agent, for example:

Find all interfaces in the current project, then use the agent-cli-builder skill to generate a CLI application.

The Agent will automatically generate src/commands/*.ts and src/index.ts according to the SDK contract, including Zod parameter validation, unified error handling, etc.

Method 2: Manually Install the Library and Write the CLI

If you prefer to control the code yourself, you can manually install the SDK:

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

Requires Node.js >= 20, ESM only.

Below is a complete single-command CLI example (under 30 lines, no auth, public data):

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

Runtime:

myapp list --limit 5

If OAuth authentication is needed, just one line integrates it:

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"), // App's own state directory
  plugins: [
    defineAuth({
      credentialNamespace: "orders", // → config/orders.json + credentials/orders.json
      baseUrl: "https://auth.example.com",
      scope: "orders.read offline_access",
    }),
  ],
  commands: {},
});
// Auto-injects: orders auth login / status / logout / register

Supports three OAuth 2.1 flows: device (default), authorization_code + PKCE, and client_credentials.


Core API Analysis

defineCli(options) — Assemble CLI

defineCli({
  name: 'orders',                  // Required: namespace
  description: '...',              // Required
  plugins: [authPlugin],           // Optional: auth/logging/audit plugins
  commands: { list, get },         // Required: top-level commands → orders list
  namespaces: { orders: {...} },   // Optional: sub-namespaces → orders orders list
  baseUrl: 'https://api.x.com',    // Optional: backend address for ctx.get/post/...
  errorOnStatus: { 404: 'not_found', '5xx': 'server_error' },  // Optional
  defaultFormat: 'auto',           // Optional: 'auto' (default) | 'json' | 'human'
  skillsDir: './skills',           // Optional: enable built-in skills commands
  skillsTargets: [...],            // Optional: sync targets (default detects common Agent directories)
})

defineCommand(spec) — Declare Command

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 is a positional argument, not a flag of the same name
  },
  humanFormat: (data) => `Order: ${data.id}`, // Optional: custom human-readable output
  async run(ctx, args) {
    const res = await ctx.get(`/orders/${args.id}`); // ctx.get/post/put/patch/delete
    return { data: res.data };
  },
});

The Zod schema is the sole source of parameter validation and types. args.type defaults to argv, but can also be set to json to receive complete structured input via --input / --input-file / stdin.

defineAuth(opts) — OAuth 2.1 Factory

Returns a Plugin, directly placed into defineCliApp({ plugins: [auth] }), and authentication-related commands are automatically mounted.

Plugin (Hooks + Providers)

const myPlugin = {
  name: "audit",
  enforce: "pre", // 'pre' | 'post' (default normal)
  provides: {
    commands: { telemetry: telemetryCmd }, // Contribute commands
    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" };
  },
};

Commands provided by a plugin are automatically exempt from that plugin's own beforeCommand, but not from other plugins.


Unified Output Contract

This is one of the key designs making the SDK Agent-friendly: Both success and failure have fixed JSON structures.

Success output (stdout):

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

Error output (stderr):

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

Exit Code Table

Exit Code Category Meaning
0 Success
1 api Server-side business error (404/500/429…)
2 validation Parameter validation failure
3 authentication / authorization / config Not logged in / No permission / Missing config
4 network DNS / Timeout / Connection refused
5 internal SDK internal error
6 policy Risk control interception
10 confirmation High-risk write operation requires --yes

Comes with 9 typed error classes: ValidationError / AuthenticationError / PermissionError / ConfigError / NetworkError / APIError (including NotFoundError) / PolicyError / InternalError / ConfirmationRequiredError. Always throw using errs.*; bare Error will be downgraded to internal/unknown.

The output mode defaults to auto: TTY environments output human-readable text, pipe/script environments automatically switch to JSON. Agents and scripts should always pass --json.


Skills and Progressive Disclosure

This is the project's biggest differentiator from ordinary CLI frameworks.

Built-in commands:

The Agent will lazy-load skills: first only read name + description, and only expand the full SKILL.md when a task matches, reading references/ on demand. Unused APIs consume no tokens.


Who Is It For?


Conclusion and Thoughts

@renxqoo/agent-cli-sdk quickly builds company data interfaces into standardized, uniformly outputting CLI + Skill tools. It merges the three tasks of "writing commands," "writing docs," and "adapting for Agents" into a single declaration, and through a unified output contract and typed errors, allows Agents to reliably use tools to fetch interface data.

However, CLI + Skill has a prerequisite: it must run in an environment capable of executing shell commands. Locally used Claude Code, Codex, and Workbuddy all satisfy this; even if a cloud Agent provides a sandbox, authentication becomes a new problem.

When used in the cloud, OAuth login requires human interaction, but a cloud Agent can only interact with you through a dialog box; Agent platforms generally don't provide credential injection, so you can only pre-set a short-term token in environment variables—once it expires, you must log in again, and storing a long-term refresh token carries leakage risks.

If the Agent is deployed by your own company, you can solve this by adding a gateway layer between the CLI and the backend interface for request forwarding—the CLI sends the request, and the gateway automatically attaches the token when forwarding to access the real interface. Credentials always stay within your controlled internal network, and the Agent in the sandbox never touches them. The prerequisite is that the gateway itself must implement proper access control—restrict to the internal network, bind the caller's identity, issue short-term tokens, and retain audit logs to reduce authentication risks.

If you are also seeking a CLI + Skill integration solution, why not give it a Star ⭐ and try it out.

Project address: https://github.com/renxqoo/agent-cli-sdk