[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-1061":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":14,"stars30d":17,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":18,"rankGlobal":10,"rankLanguage":10,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":10,"pushedAt":10,"updatedAt":28,"readmeContent":29,"aiSummary":30,"trendingCount":16,"starSnapshotCount":16,"syncStatus":14,"lastSyncTime":31,"discoverSource":32},1061,"cli-to-js","millionco\u002Fcli-to-js","millionco","Turn any CLI into a JavaScript API","",null,"TypeScript",397,10,2,1,0,17,45.82,"MIT License",false,"main",true,[24,25,26,27],"agent","api","cli","node","2026-06-12 04:00:07","# `cli-to-js`\n\n> **Warning:** This project is very experimental. APIs may change without notice.\n\nTurn any CLI into a JavaScript API, automatically.\n\nGive it a binary name, it reads `--help`, and hands you back a fully typed object where subcommands are methods, flags are options, and everything just works.\n\n```ts\nimport { convertCliToJs } from \"cli-to-js\";\n\nconst git = await convertCliToJs(\"git\");\nconst claude = await convertCliToJs(\"claude\");\n\n\u002F\u002F Get the files that changed in the last commit\nconst { stdout } = await git.diff({ nameOnly: true, _: [\"HEAD~1\"] });\nconst changedFiles = stdout.trim().split(\"\\n\");\n\n\u002F\u002F Ask Claude to review each changed file\nfor (const file of changedFiles) {\n  const review = await claude({\n    print: true,\n    model: \"sonnet\",\n    _: [`Review ${file} for bugs and suggest fixes`],\n  });\n\n  if (review.stdout.includes(\"no issues\")) continue;\n  console.log(`${file}:`, review.stdout);\n}\n```\n\n**Why this matters for agents.**\n\nAgents need to call CLI tools, but they work best with structured APIs, not raw shell strings.\n\n`cli-to-js` lets an agent introspect any binary on the system, get a typed interface, and call it safely. `$validate` catches hallucinated flag names before spawning a process and returns did-you-mean suggestions the agent can self-correct from in a single retry.\n\n`$spawn` returns a standard async iterator, so streaming and piping is just a `for await` loop.\n\n## Install\n\n```sh\nnpm install cli-to-js\n```\n\n## Quick start\n\n`convertCliToJs` runs `--help` on the binary, parses the output into a schema, and returns a Proxy-based API where every subcommand is a method and every flag is an option.\n\n```ts\nimport { convertCliToJs } from \"cli-to-js\";\n\nconst api = await convertCliToJs(\"my-tool\");\n\n\u002F\u002F Subcommand as a method\nconst result = await api.build({ output: \"dist\", minify: true });\n\u002F\u002F → my-tool build --output dist --minify\n\nconsole.log(result.stdout);\nconsole.log(result.exitCode);\n```\n\nHere's how JS option keys map to CLI flags:\n\n| JS option                 | CLI output                |\n| ------------------------- | ------------------------- |\n| `{ verbose: true }`       | `--verbose`               |\n| `{ verbose: false }`      | _(omitted)_               |\n| `{ output: \"file.txt\" }`  | `--output file.txt`       |\n| `{ dryRun: true }`        | `--dry-run`               |\n| `{ v: true }`             | `-v`                      |\n| `{ include: [\"a\", \"b\"] }` | `--include a --include b` |\n| `{ _: [\"file.txt\"] }`     | `file.txt`                |\n\n## TypeScript\n\nThe API is fully typed out of the box. Every subcommand returns `Promise\u003CCommandResult>`, and `$schema`, `$parse`, `$spawn` are all properly typed. No codegen needed.\n\nFor per-subcommand option types, pass a generic:\n\n```ts\nconst git = await convertCliToJs\u003C{\n  commit: { message?: string; all?: boolean; amend?: boolean };\n  push: { force?: boolean; setUpstream?: string };\n}>(\"git\");\n\ngit.commit({ message: \"hello\" }); \u002F\u002F message autocompletes as string\ngit.push({ foobar: true }); \u002F\u002F type error\n```\n\nOr generate a `.d.ts` from the parsed schema:\n\n```sh\nnpx cli-to-js git --dts --subcommands -o git.d.ts\n```\n\n## From a help text string\n\nIf you already have the help text, skip the binary lookup:\n\n```ts\nimport { fromHelpText } from \"cli-to-js\";\n\nconst api = fromHelpText(\"my-tool\", helpTextString);\nawait api.build({ watch: true });\n```\n\n## Subcommand parsing\n\nBy default, only the root `--help` is parsed. Enable `subcommands` to also parse every subcommand's help text and populate its flags in the schema:\n\n```ts\nconst git = await convertCliToJs(\"git\", { subcommands: true });\n\n\u002F\u002F Schema now includes each subcommand's flags\nconst commitFlags = git.$schema.command.subcommands.find((s) => s.name === \"commit\")?.flags;\n```\n\nOr parse on demand:\n\n```ts\nconst git = await convertCliToJs(\"git\");\n\n\u002F\u002F Parse one subcommand lazily\nconst commitSchema = await git.$parse(\"commit\");\nconsole.log(commitSchema.flags);\n\n\u002F\u002F Parse all discovered subcommands\nawait git.$parse();\n```\n\nHandles commander-style aliases (`init|setup`, `add|install`). The primary name is used.\n\n## Validation\n\nValidate options against the parsed schema before running a command. Returns an array of structured errors (empty means valid).\n\n```ts\nconst git = await convertCliToJs(\"git\", { subcommands: true });\n\nconst errors = git.$validate(\"commit\", { massage: \"fix typo\" });\n\u002F\u002F => [{ kind: \"unknown-flag\", name: \"massage\", suggestion: \"message\",\n\u002F\u002F       message: 'Unknown flag \"massage\". Did you mean \"message\"?' }]\n\nif (errors.length === 0) {\n  await git.commit({ message: \"fix typo\" });\n}\n```\n\nChecks for unknown flags (with Levenshtein-based suggestions), type mismatches (boolean vs value-taking), missing required positionals, and too many positionals.\n\nFor root command validation, pass options directly:\n\n```ts\nconst errors = git.$validate({ unknownFlag: true });\n```\n\nFor subcommand validation, the subcommand must be enriched first (via `subcommands: true` or `$parse(\"name\")`).\n\nOr use `validateOptions` directly with any `ParsedCommand`:\n\n```ts\nimport { validateOptions } from \"cli-to-js\";\n\nconst errors = validateOptions(schema.command, { verbose: \"wrong\" });\n```\n\n## Output parsing\n\nEvery command returns a `CommandPromise` with `.text()`, `.lines()`, and `.json()` for typed output:\n\n```ts\nconst branch = await git.branch({ showCurrent: true }).text();\n\u002F\u002F \"main\"\n\nconst files = await git.diff({ nameOnly: true, _: [\"HEAD~1\"] }).lines();\n\u002F\u002F [\"src\u002Findex.ts\", \"src\u002Futils.ts\"]\n\nconst packages = await npm.outdated({ json: true }).json\u003CRecord\u003Cstring, { current: string }>>();\n\u002F\u002F { \"lodash\": { current: \"4.17.20\" }, ... }\n```\n\nYou can also use the raw `CommandResult` directly:\n\n```ts\nconst result = await git.status();\nresult.stdout; \u002F\u002F raw string\nresult.exitCode; \u002F\u002F number\n```\n\n## Command strings\n\n`$command` returns the shell string instead of executing:\n\n```ts\ngit.$command.commit({ message: \"fix\", all: true });\n\u002F\u002F \"git commit --message fix --all\"\n\ngit.$command.push({ force: true });\n\u002F\u002F \"git push --force\"\n```\n\nCompose multiple commands into a runnable script with `script()`:\n\n```ts\nimport { script } from \"cli-to-js\";\n\nconst deploy = script(git.$command.commit({ message: \"deploy\", all: true }), git.$command.push());\n\ndeploy.run(); \u002F\u002F executes sequentially, stops on failure\nconsole.log(`${deploy}`); \u002F\u002F \"git commit --message deploy --all && git push\"\n```\n\n## Streaming\n\n### Callbacks\n\nGet real-time output while still receiving the buffered result:\n\n```ts\nconst result = await api.build(\n  { watch: true },\n  {\n    onStdout: (data) => process.stdout.write(data),\n    onStderr: (data) => process.stderr.write(data),\n  },\n);\n```\n\n### Async iterator\n\n`spawnCommand` and `$spawn` return a `CommandProcess` with raw streams and an async iterator that yields stdout lines:\n\n```ts\nconst proc = api.$spawn.test({ _: [\"--watch\"] });\n\nfor await (const line of proc) {\n  console.log(line);\n}\n\nconsole.log(\"exited with:\", await proc.exitCode);\n```\n\nOr use `spawnCommand` directly:\n\n```ts\nimport { spawnCommand } from \"cli-to-js\";\n\nconst proc = spawnCommand(\"npm\", [\"run\", \"dev\"]);\nfor await (const line of proc) {\n  if (line.includes(\"ready\")) console.log(\"Server is up\");\n}\n```\n\n### stdio inherit\n\nPass stdio through to the parent terminal for interactive CLIs:\n\n```ts\nawait api.login({}, { stdio: \"inherit\" });\n```\n\n## Per-call config\n\nEvery method accepts an optional second argument for execution config:\n\n```ts\nconst controller = new AbortController();\n\nawait api.build(\n  {},\n  {\n    cwd: \"\u002Fmy\u002Fproject\",\n    env: { NODE_ENV: \"production\" },\n    timeout: 60_000,\n    signal: controller.signal,\n  },\n);\n```\n\n## CLI\n\nGenerate a standalone JS\u002FTS wrapper for any CLI tool:\n\n```sh\nnpx cli-to-js git                          # TypeScript to stdout\nnpx cli-to-js git -o git.ts               # write to file\nnpx cli-to-js git --js -o git.js          # plain JavaScript\nnpx cli-to-js git --subcommands -o git.ts  # include per-subcommand flags\nnpx cli-to-js git --dts -o git.d.ts       # generate type declarations only\nnpx cli-to-js git --json                   # dump raw schema as JSON\n```\n\nThe generated code is **standalone**. It embeds a tiny runtime (spawn + options-to-args) and has zero dependencies on `cli-to-js`. Drop it into any project and it just works.\n\n## API\n\n### `convertCliToJs\u003CT>(binary, options?)`\n\nRuns `--help`, parses the output, returns the API proxy. Accepts an optional generic `T` for per-subcommand option types.\n\n| Option        | Type         | Default    | Description                                |\n| ------------- | ------------ | ---------- | ------------------------------------------ |\n| `helpFlag`    | `string`     | `\"--help\"` | Flag to get help text                      |\n| `timeout`     | `number`     | `10000`    | Timeout for help text fetch (ms)           |\n| `cwd`         | `string`     | -          | Default working directory for all commands |\n| `env`         | `ProcessEnv` | -          | Default environment for all commands       |\n| `subcommands` | `boolean`    | `false`    | Parse all subcommand help texts eagerly    |\n\n### `fromHelpText\u003CT>(binary, helpText, options?)`\n\nSame as `convertCliToJs` but from a static help text string. Accepts `cwd` and `env` options.\n\n### API proxy (`CliApi\u003CT>`)\n\nThe returned proxy is both callable and has subcommand methods:\n\n| Access                       | Description                              |\n| ---------------------------- | ---------------------------------------- |\n| `api.sub({ flag: val })`     | Run subcommand, returns `CommandPromise` |\n| `api.sub(opts).text()`       | Run and get trimmed stdout               |\n| `api.sub(opts).lines()`      | Run and get stdout as `string[]`         |\n| `api.sub(opts).json\u003CT>()`    | Run and parse stdout as JSON             |\n| `api(\"sub\", { flag: val })`  | Run subcommand by name                   |\n| `api({ flag: val })`         | Run root command                         |\n| `api.$schema`                | Parsed `CliSchema`                       |\n| `api.$validate(opts)`        | Validate options against root schema     |\n| `api.$validate(\"sub\", opts)` | Validate options against subcommand      |\n| `api.$command.sub(opts)`     | Get shell string without executing       |\n| `api.$spawn.sub(opts)`       | Spawn subcommand, get `CommandProcess`   |\n| `api.$parse(\"sub\")`          | Lazily parse a subcommand's help text    |\n| `api.$parse()`               | Parse all subcommand help texts          |\n\n### `RunConfig`\n\n| Option     | Type                     | Default    | Description               |\n| ---------- | ------------------------ | ---------- | ------------------------- | ---------- |\n| `timeout`  | `number`                 | `30000`    | Command timeout (ms)      |\n| `signal`   | `AbortSignal`            | -          | Abort signal              |\n| `cwd`      | `string`                 | -          | Working directory         |\n| `env`      | `ProcessEnv`             | -          | Environment variables     |\n| `stdio`    | `\"pipe\"                  | \"inherit\"` | `\"pipe\"`                  | stdio mode |\n| `onStdout` | `(data: string) => void` | -          | Real-time stdout callback |\n| `onStderr` | `(data: string) => void` | -          | Real-time stderr callback |\n\nColor output (`FORCE_COLOR`, `CLICOLOR_FORCE`) is auto-detected. It's enabled when streaming callbacks are provided and the parent process is connected to a TTY. To force it manually, pass `env: { ...process.env, FORCE_COLOR: \"1\" }`.\n\n### `CommandResult`\n\n```ts\ninterface CommandResult {\n  stdout: string;\n  stderr: string;\n  exitCode: number;\n}\n```\n\n### `CommandProcess`\n\nReturned by `spawnCommand` and `$spawn`:\n\n```ts\ninterface CommandProcess {\n  stdin: Writable | null;\n  stdout: Readable | null;\n  stderr: Readable | null;\n  pid: number | undefined;\n  kill: (signal?) => boolean;\n  exitCode: Promise\u003Cnumber>;\n  [Symbol.asyncIterator](): AsyncIterableIterator\u003Cstring>;\n}\n```\n\n## License\n\nMIT © Million Software, Inc.\n","`cli-to-js` 是一个将任何命令行工具（CLI）转换为 JavaScript API 的工具。其核心功能是通过读取 CLI 的 `--help` 信息自动生成一个完全类型化的 JavaScript 对象，其中子命令成为方法，标志成为选项。该项目使用 TypeScript 编写，提供完整的类型支持，无需额外的代码生成。它适用于需要调用 CLI 工具但更倾向于使用结构化 API 的场景，特别是对于自动化脚本和代理程序，能够提高开发效率和代码安全性。通过 `$validate` 和 `$spawn` 方法，可以确保参数正确性并处理流式输出。","2026-06-11 02:41:22","CREATED_QUERY"]