|
| 1 | +--- |
| 2 | +name: cli-to-js |
| 3 | +description: 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. |
| 4 | +--- |
| 5 | + |
| 6 | +# cli-to-js |
| 7 | + |
| 8 | +You are using cli-to-js to turn CLI binaries into callable JavaScript APIs. |
| 9 | + |
| 10 | +## REQUIRED for every cli-to-js usage: |
| 11 | + |
| 12 | +1. Call `convertCliToJs("binary")` once, reuse the returned API |
| 13 | +2. Use `.text()`, `.lines()`, or `.json()` for typed output — never manually split `result.stdout` |
| 14 | +3. Use `$validate` before executing when inputs come from untrusted sources |
| 15 | +4. Use `$spawn` + `for await` for streaming — never callbacks unless specifically asked |
| 16 | + |
| 17 | +## Flag mapping |
| 18 | + |
| 19 | +```ts |
| 20 | +// JS option key → CLI output |
| 21 | +// { verbose: true } → --verbose |
| 22 | +// { verbose: false } → (omitted) |
| 23 | +// { output: "file.txt" } → --output file.txt |
| 24 | +// { dryRun: true } → --dry-run |
| 25 | +// { v: true } → -v |
| 26 | +// { include: ["a", "b"] } → --include a --include b |
| 27 | +// { _: ["file.txt"] } → file.txt |
| 28 | +``` |
| 29 | + |
| 30 | +## API quick reference |
| 31 | + |
| 32 | +```ts |
| 33 | +import { convertCliToJs, script } from "cli-to-js"; |
| 34 | +const tool = await convertCliToJs("binary-name"); |
| 35 | + |
| 36 | +// Run + typed output |
| 37 | +await tool.subcommand({ flag: "val", _: ["pos"] }).text(); |
| 38 | +await tool.subcommand({ json: true }).json<MyType>(); |
| 39 | +await tool.subcommand().lines(); |
| 40 | + |
| 41 | +// Streaming |
| 42 | +for await (const line of tool.$spawn.subcommand({ watch: true })) {} |
| 43 | + |
| 44 | +// Validation (did-you-mean, choices, required flags, exclusive flags) |
| 45 | +const errors = tool.$validate({ misspeled: true }); |
| 46 | + |
| 47 | +// Shell string without executing |
| 48 | +tool.$command.subcommand({ flag: "val" }); |
| 49 | +// → "binary-name subcommand --flag val" |
| 50 | + |
| 51 | +// Compose into runnable script |
| 52 | +const deploy = script(git.$command.push(), docker.$command.build({ tag: "app", _: ["."] })); |
| 53 | +deploy.run(); // execute with && |
| 54 | +console.log(`${deploy}`); // get the string |
| 55 | +``` |
| 56 | + |
| 57 | +## When building tools |
| 58 | + |
| 59 | +**Bad:** |
| 60 | +```ts |
| 61 | +const result = await api.status(); |
| 62 | +const lines = result.stdout.trim().split("\n"); |
| 63 | +``` |
| 64 | + |
| 65 | +**Good:** |
| 66 | +```ts |
| 67 | +const statusLines = await api.status().lines(); |
| 68 | +``` |
| 69 | + |
| 70 | +**Bad:** |
| 71 | +```ts |
| 72 | +const api1 = await convertCliToJs("git"); |
| 73 | +const api2 = await convertCliToJs("git"); // wasteful duplicate |
| 74 | +``` |
| 75 | + |
| 76 | +**Good:** |
| 77 | +```ts |
| 78 | +const git = await convertCliToJs("git"); |
| 79 | +// reuse git everywhere |
| 80 | +``` |
| 81 | + |
| 82 | +**Bad:** |
| 83 | +```ts |
| 84 | +await api.commit({ mesage: "fix" }); // typo silently becomes unknown flag |
| 85 | +``` |
| 86 | + |
| 87 | +**Good:** |
| 88 | +```ts |
| 89 | +const errors = api.$validate({ mesage: "fix" }); |
| 90 | +// [{ kind: "unknown-flag", suggestion: "message" }] |
| 91 | +if (errors.length > 0) throw new Error(errors[0].message); |
| 92 | +await api.commit({ message: "fix" }); |
| 93 | +``` |
| 94 | + |
| 95 | +## Multi-CLI workflow pattern |
| 96 | + |
| 97 | +```ts |
| 98 | +const git = await convertCliToJs("git"); |
| 99 | +const claude = await convertCliToJs("claude"); |
| 100 | + |
| 101 | +const files = await git.diff({ nameOnly: true, _: ["HEAD~1"] }).lines(); |
| 102 | + |
| 103 | +for (const file of files) { |
| 104 | + const review = await claude({ |
| 105 | + print: true, |
| 106 | + model: "sonnet", |
| 107 | + _: [`Review ${file} for bugs`], |
| 108 | + }).text(); |
| 109 | + |
| 110 | + if (review.includes("no issues")) continue; |
| 111 | + console.log(`${file}: ${review}`); |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +## Full API surface |
| 116 | + |
| 117 | +| Method | Returns | Use for | |
| 118 | +|------------------------------|--------------------|--------------------------------| |
| 119 | +| `api.sub(opts)` | `CommandPromise` | Run subcommand | |
| 120 | +| `.text()` | `Promise<string>` | Trimmed stdout | |
| 121 | +| `.lines()` | `Promise<string[]>`| Split by newlines | |
| 122 | +| `.json<T>()` | `Promise<T>` | Parse JSON output | |
| 123 | +| `api.$spawn.sub(opts)` | `CommandProcess` | `for await` streaming | |
| 124 | +| `api.$command.sub(opts)` | `string` | Shell string, no execution | |
| 125 | +| `api.$validate(opts)` | `ValidationError[]`| Pre-flight flag checking | |
| 126 | +| `api.$validate("sub", opts)` | `ValidationError[]`| Subcommand flag checking | |
| 127 | +| `api.$schema` | `CliSchema` | Parsed schema from --help | |
| 128 | +| `api.$parse("sub")` | `ParsedCommand` | Lazily enrich subcommand | |
| 129 | +| `script(...cmds)` | `{ run, toString }`| Compose && chain | |
0 commit comments