cli-to-js
Use when wrapping CLI binaries in JavaScript, automating shell workflows in TypeScript, composing multiple CLIs into scripts, or building agent tool-use. Covers convertCliToJs, $command, $validate, $spawn, script(), .text()/.lines()/.json() output parsing.
install
source · Clone the upstream repo
git clone https://github.com/millionco/cli-to-js
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/millionco/cli-to-js "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.agents/skills/cli-to-js" ~/.claude/skills/millionco-cli-to-js-cli-to-js && rm -rf "$T"
manifest:
.agents/skills/cli-to-js/SKILL.mdsource content
cli-to-js
You are using cli-to-js to turn CLI binaries into callable JavaScript APIs.
REQUIRED for every cli-to-js usage:
- Call
once, reuse the returned APIconvertCliToJs("binary") - Use
,.text()
, or.lines()
for typed output — never manually split.json()result.stdout - Use
before executing when inputs come from untrusted sources$validate - Use
+$spawn
for streaming — never callbacks unless specifically askedfor await
Flag mapping
// JS option key → 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
API quick reference
import { convertCliToJs, script } from "cli-to-js"; const tool = await convertCliToJs("binary-name"); // Run + typed output await tool.subcommand({ flag: "val", _: ["pos"] }).text(); await tool.subcommand({ json: true }).json<MyType>(); await tool.subcommand().lines(); // Streaming for await (const line of tool.$spawn.subcommand({ watch: true })) { } // Validation (did-you-mean, choices, required flags, exclusive flags) const errors = tool.$validate({ misspeled: true }); // Shell string without executing tool.$command.subcommand({ flag: "val" }); // → "binary-name subcommand --flag val" // Compose into runnable script const deploy = script(git.$command.push(), docker.$command.build({ tag: "app", _: ["."] })); deploy.run(); // execute with && console.log(`${deploy}`); // get the string
When building tools
Bad:
const result = await api.status(); const lines = result.stdout.trim().split("\n");
Good:
const statusLines = await api.status().lines();
Bad:
const api1 = await convertCliToJs("git"); const api2 = await convertCliToJs("git"); // wasteful duplicate
Good:
const git = await convertCliToJs("git"); // reuse git everywhere
Bad:
await api.commit({ mesage: "fix" }); // typo silently becomes unknown flag
Good:
const errors = api.$validate({ mesage: "fix" }); // [{ kind: "unknown-flag", suggestion: "message" }] if (errors.length > 0) throw new Error(errors[0].message); await api.commit({ message: "fix" });
Multi-CLI workflow pattern
const git = await convertCliToJs("git"); const claude = await convertCliToJs("claude"); const files = await git.diff({ nameOnly: true, _: ["HEAD~1"] }).lines(); for (const file of files) { const review = await claude({ print: true, model: "sonnet", _: [`Review ${file} for bugs`], }).text(); if (review.includes("no issues")) continue; console.log(`${file}: ${review}`); }
Full API surface
| Method | Returns | Use for |
|---|---|---|
| | Run subcommand |
| | Trimmed stdout |
| | Split by newlines |
| | Parse JSON output |
| | streaming |
| | Shell string, no execution |
| | Pre-flight flag checking |
| | Subcommand flag checking |
| | Parsed schema from --help |
| | Lazily enrich subcommand |
| | Compose && chain |