ADR 0009: Argument Parser — Commander

Field Value
Status Accepted
Date 2026-05-12
Deciders Project maintainers
Supersedes
Superseded by
Related ADRs 0001 (Runtime + language), 0010 (Prompts), 0011 (Schemas)

Summary

Use Commander as the argument-parsing library for @aidokit/cli. It is the smallest mature option that comfortably handles aidokit's command surface (≤ 15 commands at v1.0), has first-class TypeScript types, and adds negligible install weight. Two alternatives were considered and rejected: oclif (overbuilt for our surface) and a custom parser (re-implements help / validation / parsing for no gain).

Maintainer confirmed Commander on 2026-05-12.


Context

@aidokit/cli exposes 13 commands by v1.0 (per .docs/docs/specs/cli-reference.md §7), with subcommands on mcp, skills, new, search, list, and add/remove adapter. Flags include both global (--yes, --verbose, --json, --no-color, --cwd) and per-command flags. The parser needs to:

The CLI is not a plugin host: there is no need for dynamic command discovery, marketplace-installable plugins, or per-command CWD shifting. Adapters and stack packs are imported statically (ADR-0005, ADR-0006); they do not register their own commands.

The choice is constrained by two earlier ADRs:


Decision

1. Use Commander v12+

import { Command } from 'commander';

const program = new Command()
  .name('aidokit')
  .description('Bootstrap an AI-assisted development workflow')
  .version(VERSION);

program
  .command('init')
  .description('Bootstrap a project (interactive)')
  .option('--adapter <name>', 'Adapter to install')
  .option('--conformance <level>', 'Conformance level')
  .option('--stack <ids>', 'Comma-separated stack-pack ids')
  .option('-y, --yes', 'Skip confirmation prompts')
  .option('--dry-run', 'Print plan without writing')
  .action(initCommand);

await program.parseAsync(process.argv);

2. Each command lives in its own module

packages/cli/src/commands/<command>.ts exports a handler function. The top-level cli.ts wires commands into the Commander tree. This keeps command logic isolated and testable independent of the parser.

3. Help and version are Commander's

We do not write custom help formatters. Commander's default output is acceptable for v0.1. Customisation (colour, sections) can be added in v0.5 if user feedback warrants it.

4. Exit code mapping is the CLI shell's job, not Commander's

Command handlers return Promise<void> and throw AidoError. A top-level catch in cli.ts maps AidoError.code to exit codes per .docs/ARCHITECTURE.md §16.2. Commander's own parse-error exit (code 1) is acceptable for malformed invocations (exit code 2 in our taxonomy is reserved for "bad invocation"; we wire this through Commander's .exitOverride()).

5. Pinned to a tested minor range

@aidokit/cli pins Commander to ^12.0.0 (or whichever current major is in use at v0.1 release). Major-version upgrades require a Changeset entry and a test pass.


Consequences

Positive

Negative

Neutral


Alternatives considered

Alternative 1: oclif

A framework for building CLIs (Salesforce's CLI, Heroku CLI). File-per-command convention, automatic doc generation, built-in plugin system.

Pros

Cons

Reason rejected: oclif is the right tool for Salesforce's CLI; it is wrong-sized for ours. We can revisit if the command surface grows past ~30 commands or we adopt a runtime plugin model.

Alternative 2: Custom parser

A purpose-built parser inside @aidokit/cli with no external dependency.

Pros

Cons

Reason rejected: the operational cost vastly exceeds Commander's install weight. Custom parsers are tempting and almost always wrong for CLI tools at this scale.

Alternative 3: yargs

Long-standing alternative to Commander; richer middleware story.

Pros

Cons

Reason rejected: yargs is a viable choice; we prefer Commander on size and TS ergonomics. If the maintainer prefers yargs, this ADR can be revised before Accepted.

Alternative 4: Citty

Modern, minimal, used by Nuxt. ESM-first.

Pros

Cons

Reason rejected: size advantage is marginal; maturity advantage of Commander outweighs it.

Alternative 5: Cmd-ts

Type-first arg parser; strong inference of handler types from definitions.

Pros

Cons

Reason rejected: strong inference is appealing but not worth the maturity gap.


Implementation notes

If accepted:

// packages/cli/src/commands/init.ts
import type { ProjectContext } from '@aidokit/core';

export interface InitOptions {
  adapter?: string;
  conformance?: 'minimum' | 'standard' | 'strict';
  stack?: string;
  yes?: boolean;
  dryRun?: boolean;
}

export async function initCommand(options: InitOptions): Promise<void> {
  // ...
}

If a different parser is accepted, the same per-command-handler pattern applies; only the wiring at the top changes.


Validation

This decision is validated when:


References

Internal

External