Skip to main content

cli-to-js-api-wrapper

Turn any CLI tool into a fully typed JavaScript/TypeScript API using cli-to-js

소스 정보

저장소
reason-machines/trending-skills
최근 소스 활동
2026년 4월 14일 01:40
감지된 SKILL.md 언어
영어
스타
83
포크
15

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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](https://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 ```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` ## 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 ```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"); ``` ## TypeScript Generics for Full Typing ```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 ``` ## 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 ``` ## Validation (Critical for Agent Use) 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 }); ``` ## 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(); ``` ## 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; } } ``` ## 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 } ); ``` ## 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 ``` ## From Help Text String ```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 ```sh # TypeScript wrapper to stdout npx cli-to-js git # Write to file npx cli-to-js git -o git.ts # Plain JavaScript npx cli-to-js git --js -o git.js # Include per-subcommand flags npx cli-to-js git --subcommands -o git.ts # Type declarations only npx cli-to-js git --dts -o git.d.ts # Dump raw schema as JSON npx cli-to-js git --json ``` Generated files are **standalone** with zero runtime dependencies on `cli-to-js`. ## Agent Workflow Pattern ```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); } } } ``` ## 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)); }); ``` ## 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" }); ``` ## 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 }); ```
GitHub에서 보기