| name | cli-to-js-api-wrapper |
| description | Turn any CLI tool into a fully typed JavaScript/TypeScript API using cli-to-js |
| triggers | ["turn a CLI into a JavaScript API","wrap a command line tool in TypeScript","convert CLI to JS API","use cli-to-js to call shell commands","generate typed wrapper for CLI tool","call CLI tools from Node.js with types","spawn CLI commands with JavaScript API","introspect CLI help and create API"] |
cli-to-js: Turn Any CLI Into a 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.
Install
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
Flag → CLI Mapping
| JS option | CLI 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 |
Basic Usage
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 });
const { stdout } = await git.diff({ nameOnly: true, _: ["HEAD~1"] });
const changedFiles = stdout.trim().split("\n");
TypeScript Generics for Full Typing
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 });
await git.push({ foobar: true });
Output Helpers
const git = await convertCliToJs("git");
const branch = await git.branch({ showCurrent: true }).text();
const files = await git.diff({ nameOnly: true, _: ["HEAD~1"] }).lines();
const packages = await npm.outdated({ json: true }).json<Record<string, { current: string }>>();
const result = await git.log({ oneline: true, n: "5" });
result.stdout;
result.stderr;
result.exitCode;
Validation (Critical for Agent Use)
Validate options before spawning — catches hallucinated flag names with did-you-mean suggestions:
const git = await convertCliToJs("git", { subcommands: true });
const errors = git.$validate("commit", { massage: "fix typo" });
if (errors.length === 0) {
await git.commit({ message: "fix typo" });
} else {
console.log("Suggestion:", errors[0].suggestion);
}
const rootErrors = git.$validate({ unknownFlag: true });
Subcommand Parsing
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
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),
}
);
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);
import { spawnCommand } from "cli-to-js";
const dev = spawnCommand("npm", ["run", "dev"]);
for await ( line dev) {
(line.()) {
.();
;
}
}
Per-Call Execution Config
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",
}
);
Command Strings (Without Executing)
const git = await convertCliToJs("git");
git.$command.commit({ message: "deploy", all: true });
import { script } from "cli-to-js";
const deploy = script(
git.$command.commit({ message: "deploy", all: true }),
git.$command.push({ force: false })
);
console.log(`${deploy}`);
deploy.run();
From Help Text String
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
npx cli-to-js git
npx cli-to-js git -o git.ts
npx cli-to-js git --js -o git.js
npx cli-to-js git --subcommands -o git.ts
npx cli-to-js git --dts -o git.d.ts
npx cli-to-js git --json
Generated files are standalone with zero runtime dependencies on cli-to-js.
Agent Workflow Pattern
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("Invalid flags:", errors);
continue;
}
const review = await claude({
print: true,
model: "sonnet",
_: [`Review ${file} for bugs`],
});
(!review..()) {
.(, review.);
}
}
}
Schema Inspection
const git = await convertCliToJs("git", { subcommands: true });
console.log(git.$schema);
git.$schema.command.subcommands.forEach((s) => {
console.log(s.name, s.flags.map((f) => f.name));
});
Common Patterns
Wrap with default config:
const docker = await convertCliToJs("docker", {
cwd: process.env.PROJECT_DIR,
env: { ...process.env, DOCKER_BUILDKIT: "1" },
timeout: 120_000,
});
Root command call (no subcommand):
const result = await api({ version: true });
const result = await api("subcommand", { flag: true });
Interactive CLI passthrough:
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.):
const api = await convertCliToJs("mytool", { helpFlag: "-h" });
Subcommand flags missing: Subcommand flags only populate when subcommands: true is set or $parse("sub") is called:
await git.$parse("commit");
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:
const api = await convertCliToJs("slow-tool", { timeout: 30_000 });