cli-to-js-api-wrapper

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

cli-to-js: Turn Any CLI Into a JavaScript API

cli-to-js:将任意CLI转换为JavaScript API

Skill by ara.so — Daily 2026 Skills collection.
cli-to-js
reads a binary's
--help
output, parses it into a schema, and returns a fully typed Proxy-based API where subcommands are methods and flags are options. Designed for agent workflows where structured APIs are safer than raw shell strings.
ara.so提供的技能——2026每日技能合集。
cli-to-js
会读取二进制文件的
--help
输出,将其解析为架构,并返回一个基于Proxy的类型完备API,其中子命令对应方法,标志对应选项。专为Agent工作流设计,结构化API比原始Shell字符串更安全。

Install

安装

sh
npm install cli-to-js
sh
npm install cli-to-js

Core Concepts

核心概念

  • convertCliToJs(binary)
    — runs
    --help
    , parses output, returns typed API proxy
  • fromHelpText(binary, text)
    — same but from a static help string
  • Every subcommand becomes a method:
    api.subcommand({ flag: value })
  • Positional args use the
    _
    key:
    api.command({ _: ["file.txt"] })
  • camelCase keys auto-convert to kebab-case flags:
    { dryRun: true }
    --dry-run
  • convertCliToJs(binary)
    —— 执行
    --help
    ,解析输出,返回带类型的API代理
  • fromHelpText(binary, text)
    —— 功能同上,但基于静态帮助文本
  • 每个子命令都会成为一个方法:
    api.subcommand({ flag: value })
  • 位置参数使用
    _
    键:
    api.command({ _: ["file.txt"] })
  • 驼峰式键会自动转换为短横线式标志:
    { dryRun: true }
    --dry-run

Flag → CLI Mapping

标志 → CLI映射

JS optionCLI output
{ verbose: true }
--verbose
{ verbose: false }
(omitted)
{ output: "file.txt" }
--output file.txt
{ dryRun: true }
--dry-run
{ v: true }
-v
{ include: ["a","b"] }
--include a --include b
{ _: ["file.txt"] }
file.txt
JS选项CLI输出
{ verbose: true }
--verbose
{ verbose: false }
(省略)
{ output: "file.txt" }
--output file.txt
{ dryRun: true }
--dry-run
{ v: true }
-v
{ include: ["a","b"] }
--include a --include b
{ _: ["file.txt"] }
file.txt

Basic Usage

基础用法

ts
import { convertCliToJs } from "cli-to-js";

// Wrap any installed binary
const git = await convertCliToJs("git");
const npm = await convertCliToJs("npm");

// Call subcommands as methods
const result = await git.status();
console.log(result.stdout);
console.log(result.exitCode);

// Pass flags as options
await git.commit({ message: "fix: update logic", all: true });
// → git commit --message "fix: update logic" --all

// Positional arguments via _
const { stdout } = await git.diff({ nameOnly: true, _: ["HEAD~1"] });
const changedFiles = stdout.trim().split("\n");
ts
import { convertCliToJs } from "cli-to-js";

// 包装任意已安装的二进制文件
const git = await convertCliToJs("git");
const npm = await convertCliToJs("npm");

// 以方法形式调用子命令
const result = await git.status();
console.log(result.stdout);
console.log(result.exitCode);

// 将标志作为选项传入
await git.commit({ message: "fix: update logic", all: true });
// → git commit --message "fix: update logic" --all

// 通过_传递位置参数
const { stdout } = await git.diff({ nameOnly: true, _: ["HEAD~1"] });
const changedFiles = stdout.trim().split("\n");

TypeScript Generics for Full Typing

TypeScript泛型实现完整类型支持

ts
import { convertCliToJs } from "cli-to-js";

const git = await convertCliToJs<{
  commit: { message?: string; all?: boolean; amend?: boolean };
  push: { force?: boolean; setUpstream?: string };
  diff: { nameOnly?: boolean; stat?: boolean; _?: string[] };
}>("git");

// Fully autocompleted and type-checked
await git.commit({ message: "hello", all: true });
await git.push({ force: true });

// Type error — foobar doesn't exist
await git.push({ foobar: true }); // ❌ compile error
ts
import { convertCliToJs } from "cli-to-js";

const git = await convertCliToJs<{
  commit: { message?: string; all?: boolean; amend?: boolean };
  push: { force?: boolean; setUpstream?: string };
  diff: { nameOnly?: boolean; stat?: boolean; _?: string[] };
}>("git");

// 自动补全与类型检查
await git.commit({ message: "hello", all: true });
await git.push({ force: true });

// 类型错误 —— foobar不存在
await git.push({ foobar: true }); // ❌ 编译错误

Output Helpers

输出辅助方法

ts
const git = await convertCliToJs("git");

// .text() — trimmed stdout string
const branch = await git.branch({ showCurrent: true }).text();
// "main"

// .lines() — stdout split into array
const files = await git.diff({ nameOnly: true, _: ["HEAD~1"] }).lines();
// ["src/index.ts", "src/utils.ts"]

// .json<T>() — parse stdout as JSON
const packages = await npm.outdated({ json: true }).json<Record<string, { current: string }>>();
// { "lodash": { current: "4.17.20" }, ... }

// Raw result
const result = await git.log({ oneline: true, n: "5" });
result.stdout;   // string
result.stderr;   // string
result.exitCode; // number
ts
const git = await convertCliToJs("git");

// .text() —— 去除首尾空格的stdout字符串
const branch = await git.branch({ showCurrent: true }).text();
// "main"

// .lines() —— 将stdout拆分为数组
const files = await git.diff({ nameOnly: true, _: ["HEAD~1"] }).lines();
// ["src/index.ts", "src/utils.ts"]

// .json<T>() —— 将stdout解析为JSON
const packages = await npm.outdated({ json: true }).json<Record<string, { current: string }>>();
// { "lodash": { current: "4.17.20" }, ... }

// 原始结果
const result = await git.log({ oneline: true, n: "5" });
result.stdout;   // 字符串
result.stderr;   // 字符串
result.exitCode; // 数字

Validation (Critical for Agent Use)

验证(Agent场景至关重要)

Validate options before spawning — catches hallucinated flag names with did-you-mean suggestions:
ts
const git = await convertCliToJs("git", { subcommands: true });

const errors = git.$validate("commit", { massage: "fix typo" });
// [{ kind: "unknown-flag", name: "massage", suggestion: "message",
//    message: 'Unknown flag "massage". Did you mean "message"?' }]

// Always validate before running in agent workflows
if (errors.length === 0) {
  await git.commit({ message: "fix typo" });
} else {
  // Use errors[0].suggestion to self-correct
  console.log("Suggestion:", errors[0].suggestion);
}

// Validate root command options
const rootErrors = git.$validate({ unknownFlag: true });
在生成进程前验证选项——通过“你是不是想找”建议捕获错误的标志名称:
ts
const git = await convertCliToJs("git", { subcommands: true });

const errors = git.$validate("commit", { massage: "fix typo" });
// [{ kind: "unknown-flag", name: "massage", suggestion: "message",
//    message: 'Unknown flag "massage". Did you mean "message"?' }]

// 在Agent工作流中执行前务必验证
if (errors.length === 0) {
  await git.commit({ message: "fix typo" });
} else {
  // 使用errors[0].suggestion进行自我修正
  console.log("建议:", errors[0].suggestion);
}

// 验证根命令选项
const rootErrors = git.$validate({ unknownFlag: true });

Subcommand Parsing

子命令解析

ts
// Eager: parse all subcommands up front
const git = await convertCliToJs("git", { subcommands: true });
const commitFlags = git.$schema.command.subcommands
  .find((s) => s.name === "commit")?.flags;

// Lazy: parse one subcommand on demand
const git2 = await convertCliToJs("git");
const commitSchema = await git2.$parse("commit");
console.log(commitSchema.flags);

// Parse all subcommands lazily
await git2.$parse();
ts
// 预加载:提前解析所有子命令
const git = await convertCliToJs("git", { subcommands: true });
const commitFlags = git.$schema.command.subcommands
  .find((s) => s.name === "commit")?.flags;

// 懒加载:按需解析单个子命令
const git2 = await convertCliToJs("git");
const commitSchema = await git2.$parse("commit");
console.log(commitSchema.flags);

// 懒加载解析所有子命令
await git2.$parse();

Streaming Output

流式输出

ts
const api = await convertCliToJs("my-tool");

// Callbacks: real-time output + buffered result
const result = await api.build(
  { watch: false },
  {
    onStdout: (data) => process.stdout.write(data),
    onStderr: (data) => process.stderr.write(data),
  }
);

// Async iterator via $spawn
const proc = api.$spawn.test({ _: ["--watch"] });
for await (const line of proc) {
  console.log(line);
  if (line.includes("failed")) proc.kill();
}
console.log("Exit code:", await proc.exitCode);

// Direct spawnCommand
import { spawnCommand } from "cli-to-js";
const dev = spawnCommand("npm", ["run", "dev"]);
for await (const line of dev) {
  if (line.includes("ready")) {
    console.log("Server started");
    break;
  }
}
ts
const api = await convertCliToJs("my-tool");

// 回调:实时输出 + 缓冲结果
const result = await api.build(
  { watch: false },
  {
    onStdout: (data) => process.stdout.write(data),
    onStderr: (data) => process.stderr.write(data),
  }
);

// 通过$spawn实现异步迭代器
const proc = api.$spawn.test({ _: ["--watch"] });
for await (const line of proc) {
  console.log(line);
  if (line.includes("failed")) proc.kill();
}
console.log("退出码:", await proc.exitCode);

// 直接调用spawnCommand
import { spawnCommand } from "cli-to-js";
const dev = spawnCommand("npm", ["run", "dev"]);
for await (const line of dev) {
  if (line.includes("ready")) {
    console.log("服务器已启动");
    break;
  }
}

Per-Call Execution Config

单次调用执行配置

ts
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

await api.build(
  { minify: true },
  {
    cwd: "/my/project",
    env: { ...process.env, NODE_ENV: "production" },
    timeout: 60_000,
    signal: controller.signal,
    stdio: "inherit",  // pass through to terminal for interactive CLIs
  }
);
ts
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

await api.build(
  { minify: true },
  {
    cwd: "/my/project",
    env: { ...process.env, NODE_ENV: "production" },
    timeout: 60_000,
    signal: controller.signal,
    stdio: "inherit",  // 传递到终端以支持交互式CLI
  }
);

Command Strings (Without Executing)

命令字符串(不执行)

ts
const git = await convertCliToJs("git");

// Get the shell string instead of running it
git.$command.commit({ message: "deploy", all: true });
// "git commit --message deploy --all"

// Compose into a script
import { script } from "cli-to-js";

const deploy = script(
  git.$command.commit({ message: "deploy", all: true }),
  git.$command.push({ force: false })
);

console.log(`${deploy}`);
// "git commit --message deploy --all && git push"

deploy.run(); // executes sequentially, stops on failure
ts
const git = await convertCliToJs("git");

// 获取Shell字符串而非执行命令
git.$command.commit({ message: "deploy", all: true });
// "git commit --message deploy --all"

// 组合为脚本
import { script } from "cli-to-js";

const deploy = script(
  git.$command.commit({ message: "deploy", all: true }),
  git.$command.push({ force: false })
);

console.log(`${deploy}`);
// "git commit --message deploy --all && git push"

deploy.run(); // 按顺序执行,失败时停止

From Help Text String

基于帮助文本字符串创建API

ts
import { fromHelpText } from "cli-to-js";

const helpText = `
Usage: mytool [options]
  --output <dir>   Output directory
  --minify         Minify output
  --watch          Watch for changes
`;

const api = fromHelpText("mytool", helpText, { cwd: "/project" });
await api({ output: "dist", minify: true });
ts
import { fromHelpText } from "cli-to-js";

const helpText = `
Usage: mytool [options]
  --output <dir>   Output directory
  --minify         Minify output
  --watch          Watch for changes
`;

const api = fromHelpText("mytool", helpText, { cwd: "/project" });
await api({ output: "dist", minify: true });

CLI Code Generation

CLI代码生成

sh
undefined
sh
undefined

TypeScript wrapper to stdout

将TypeScript包装器输出到标准输出

npx cli-to-js git
npx cli-to-js git

Write to file

写入文件

npx cli-to-js git -o git.ts
npx cli-to-js git -o git.ts

Plain JavaScript

生成纯JavaScript代码

npx cli-to-js git --js -o git.js
npx cli-to-js git --js -o git.js

Include per-subcommand flags

包含子命令标志

npx cli-to-js git --subcommands -o git.ts
npx cli-to-js git --subcommands -o git.ts

Type declarations only

仅生成类型声明

npx cli-to-js git --dts -o git.d.ts
npx cli-to-js git --dts -o git.d.ts

Dump raw schema as JSON

以JSON格式导出原始架构

npx cli-to-js git --json

Generated files are **standalone** with zero runtime dependencies on `cli-to-js`.
npx cli-to-js git --json

生成的文件是**独立的**,完全不依赖`cli-to-js`运行时。

Agent Workflow Pattern

Agent工作流模式

ts
import { convertCliToJs } from "cli-to-js";

async function agentTask() {
  const git = await convertCliToJs("git", { subcommands: true });
  const claude = await convertCliToJs("claude");

  // Get changed files
  const files = await git.diff({ nameOnly: true, _: ["HEAD~1"] }).lines();

  for (const file of files) {
    // Validate before calling
    const errors = claude.$validate({ print: true, model: "sonnet" });
    if (errors.length > 0) {
      console.error("Invalid flags:", errors);
      continue;
    }

    const review = await claude({
      print: true,
      model: "sonnet",
      _: [`Review ${file} for bugs`],
    });

    if (!review.stdout.includes("no issues")) {
      console.log(`Issues in ${file}:`, review.stdout);
    }
  }
}
ts
import { convertCliToJs } from "cli-to-js";

async function agentTask() {
  const git = await convertCliToJs("git", { subcommands: true });
  const claude = await convertCliToJs("claude");

  // 获取变更文件
  const files = await git.diff({ nameOnly: true, _: ["HEAD~1"] }).lines();

  for (const file of files) {
    // 调用前验证
    const errors = claude.$validate({ print: true, model: "sonnet" });
    if (errors.length > 0) {
      console.error("无效标志:", errors);
      continue;
    }

    const review = await claude({
      print: true,
      model: "sonnet",
      _: [`Review ${file} for bugs`],
    });

    if (!review.stdout.includes("no issues")) {
      console.log(`${file}中存在问题:", review.stdout);
    }
  }
}

Schema Inspection

架构检查

ts
const git = await convertCliToJs("git", { subcommands: true });

// Full parsed schema
console.log(git.$schema);
// { binary: "git", command: { name: "git", flags: [...], subcommands: [...] } }

// List subcommands
git.$schema.command.subcommands.forEach((s) => {
  console.log(s.name, s.flags.map((f) => f.name));
});
ts
const git = await convertCliToJs("git", { subcommands: true });

// 完整解析后的架构
console.log(git.$schema);
// { binary: "git", command: { name: "git", flags: [...], subcommands: [...] } }

// 列出所有子命令
git.$schema.command.subcommands.forEach((s) => {
  console.log(s.name, s.flags.map((f) => f.name));
});

Common Patterns

常见模式

Wrap with default config:
ts
const docker = await convertCliToJs("docker", {
  cwd: process.env.PROJECT_DIR,
  env: { ...process.env, DOCKER_BUILDKIT: "1" },
  timeout: 120_000,
});
Root command call (no subcommand):
ts
const result = await api({ version: true });
// or
const result = await api("subcommand", { flag: true });
Interactive CLI passthrough:
ts
const gh = await convertCliToJs("gh");
await gh.auth({ login: true }, { stdio: "inherit" });
使用默认配置包装:
ts
const docker = await convertCliToJs("docker", {
  cwd: process.env.PROJECT_DIR,
  env: { ...process.env, DOCKER_BUILDKIT: "1" },
  timeout: 120_000,
});
调用根命令(无子命令):
ts
const result = await api({ version: true });
// 或者
const result = await api("subcommand", { flag: true });
交互式CLI透传:
ts
const gh = await convertCliToJs("gh");
await gh.auth({ login: true }, { stdio: "inherit" });

Troubleshooting

故障排查

Binary not found: Ensure the binary is in
PATH
. Test with
which <binary>
in terminal.
Help text not parsed correctly: Use
fromHelpText
with manually fetched help, or set
helpFlag
to the correct flag (
-h
,
help
, etc.):
ts
const api = await convertCliToJs("mytool", { helpFlag: "-h" });
Subcommand flags missing: Subcommand flags only populate when
subcommands: true
is set or
$parse("sub")
is called:
ts
await git.$parse("commit"); // now git.$validate("commit", opts) works
Type errors on dynamic subcommands: Pass a generic type to
convertCliToJs<T>
for per-subcommand option types.
Timeout on slow help output: Increase the help fetch timeout:
ts
const api = await convertCliToJs("slow-tool", { timeout: 30_000 });
未找到二进制文件: 确保二进制文件在
PATH
中。在终端中使用
which <binary>
测试。
帮助文本解析失败: 使用
fromHelpText
传入手动获取的帮助文本,或设置
helpFlag
为正确的标志(
-h
help
等):
ts
const api = await convertCliToJs("mytool", { helpFlag: "-h" });
子命令标志缺失: 只有设置
subcommands: true
或调用
$parse("sub")
后,子命令标志才会被填充:
ts
await git.$parse("commit"); // 此时git.$validate("commit", opts)可正常工作
动态子命令类型错误:
convertCliToJs<T>
传入泛型类型以支持子命令选项类型。
帮助输出超时: 增加帮助文本获取超时时间:
ts
const api = await convertCliToJs("slow-tool", { timeout: 30_000 });