Claude
Skills
Sign in
Back

cli-to-js-api-wrapper

Included with Lifetime
$97 forever

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

Backend & APIs

What this skill does


# 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

**

Related in Backend & APIs