Skip to content

src/cli/index.ts — top-level command dispatcher

Argv → subcommand dispatch; the cli module’s contract. Hand-rolled (no commander / yargs / cac) to keep the dependency tree slim and behaviour trivially predictable. Each subcommand handler lives in a sibling src/cli/<name>.ts.

export async function run(argv: readonly string[]): Promise<number>
// Re-exports for tests:
export { parseRunArgs, type RunArgs } from './run.js'
export { parsePruneArgs, parseDuration, parseSize } from './cache.js'
export { formatBytes } from './format.js'

run(argv) returns the exit code. bin.ts calls process.exit(await run(process.argv.slice(2))).

Argv first tokenHandler
runcli/run.ts:runCmd(rest)
watchcli/watch.ts:watchCmd(rest)
cachecli/cache.ts:cacheCmd(rest)
help / --help / -h / (empty)cli/help.ts:printHelp()
version / --versionprocess.stdout.write('vx <VERSION>\n')
anything elseprint unknown command, then help, exit 1

Per-subcommand parsers / handlers carry their own argv-walk loops. See:

  • No global flags (no --debug, no --quiet, no --color). Color is gated by env (NO_COLOR / FORCE_COLOR / TTY).
  • No tab completion.
  • No subcommand aliases beyond the help / version sugar.

tests/cli.test.ts covers the dispatcher table — help, version, unknown subcommand. Per-subcommand parser tests live in tests/cli.test.ts too (it’s one file).

To swap in a parser library, keep run(argv): Promise<number> and keep the per-subcommand re-exports stable (tests import them directly). Everything else can change.