# CLI — `cli.ts` _(no submodules)_ ## Purpose Owns the five command surfaces — `createCommand`, `devCommand`, `buildCommand`, `packageCommand`, `signCommand` — plus the `runCli(argv, deps?)` dispatcher that the published binary delegates to. Every command is a pure function: it validates its option struct against the matching Zod schema, runs the orchestrated work, and returns a typed report. `runCli` never calls `process.exit`; the only exit-call lives in `src/bin/suaveplan-electrobun.ts`, so the entire CLI is unit-testable without a child process. `createCommand` is the scaffolder entry: it loads the layered config, applies CLI overrides, optionally runs the prompt flow, then hands off to `generate`. The other four commands are runner-only — they probe the upstream `electrobun` binary, raise `ELECTROBUN.BINARY_UNAVAILABLE` when missing, spawn the binary with the canonical argv, and return a `CommandReport` carrying exit code, stdout, stderr, and duration. ## Features - Five command functions, each with Zod-validated options and structured reports. - Pure `runCli(argv)` dispatcher with `--help` / `-h` short-circuit and closed-set flag parsing. - Long-flag `--key=value` and `--key value` forms supported uniformly. - Unknown flags / subcommands raise `ELECTROBUN.UNKNOWN_FLAG`. - Missing required values raise `ELECTROBUN.INVALID_OPTIONS`. - `timeRunner` wrapper increments invocation + duration histograms per call and increments failure counters on `ElectrobunError` paths. ## Usage: Basic ```ts import { runCli } from "@suaveplan/electrobun/cli"; const { code } = await runCli(process.argv.slice(2)); process.exit(code); ``` ## Usage: Advanced ```ts import { buildCommand } from "@suaveplan/electrobun/cli"; import type { CommandRunner } from "@suaveplan/electrobun/types"; const customRunner: CommandRunner = async (input) => { // ship the spawn to a remote CI worker return await postToWorker(input); }; const report = await buildCommand( { cwd: "/proj", target: "linux", update: true }, { runner: customRunner } ); ``` ## API Reference - `createCommand(opts: CreateCommandOptions, deps: CommandDeps & { prompter: Prompter }): Promise`. - `devCommand(opts: DevCommandOptions, deps?: { runner? }): Promise`. - `buildCommand(opts: BuildCommandOptions, deps?: { runner? }): Promise`. - `packageCommand(opts: PackageCommandOptions, deps?: { runner? }): Promise`. - `signCommand(opts: SignCommandOptions, deps?: { runner? }): Promise`. - `runCli(argv: readonly string[], deps?: RunCliOptions): Promise<{ code: number }>`. - `HELP_TEXT: string` — the full help banner. ## Implementation Notes The arg parser uses a closed `ALL_KNOWN_FLAGS` set. New flags must be added to the set before the parser will accept them; an unknown flag fails the run with `ELECTROBUN.UNKNOWN_FLAG`, which keeps subtle typos from being silently swallowed. The `--help` and `-h` short forms are intercepted before subcommand dispatch so they work uniformly across every subcommand. `runCli` catches every `ElectrobunError` and folds it into `code: 1` with a one-line stderr message. Any other thrown value re-propagates, on the principle that the runtime should not eat unexpected exceptions — if something other than a typed error escapes the call, the bin wrapper surfaces it with `code: 2`. The `timeRunner` helper increments `electrobun_runner_invocations_total` on entry, records the duration histogram on every exit (success or failure), and records `electrobun_runner_failures_total` when the thrown `ElectrobunError` carries a numeric `details.exitCode`. The failure counter labels the exit code with `-1` when no numeric exit-code is available — for instance when the probe stage raises `BINARY_UNAVAILABLE` — so dashboards can still attribute the failure to the right command without inventing fake exit codes.