Skip to content

Why dreamcli

Most TypeScript CLI frameworks treat the type system like decoration. You define flags in one place, then use parsed values somewhere else as a loosely typed blob. Env vars, config files, and interactive prompts live in separate universes. Testing means hacking process.argv.

dreamcli collapses all of that into a single typed schema.

Comparison

CapabilityCommander / YargsCitty / CACCleyedreamcli
Type inference from definitionManual interfacesBasicGoodFull — flags, args, context
Resolution chain (CLI → env → config → prompt → default)DIY glueCLI onlyCLI onlyBuilt-in, per-flag
Interactive prompts from schemaSeparate libraryNoNoIntegrated
Middleware with typed contextNoNoNoYes — accumulates via intersection
In-process test harnessShell outShell outShell outrunCommand() + capture
Shell completions from schemaPluginNoNoBuilt-in (bash/zsh)
Structured output (--json, tables, spinners)DIYNoNoBuilt-in
Config file discoveryDIYNoNoBuilt-in (XDG paths, JSON)

The Core Insight

The closest analog is what tRPC did to API routes — the individual pieces existed. The insight was wiring them so types flow end-to-end.

One flag declaration:

ts
flag
	.enum(['us', 'eu', 'ap'])
	.alias('r')
	.env('DEPLOY_REGION')
	.config('deploy.region')
	.prompt({ kind: 'select', message: 'Which region?' })
	.default('us');

This single chain configures:

  • Parsing — accepts --region us or -r eu
  • Type inferenceflags.region is "us" | "eu" | "ap" in the handler
  • Resolution — CLI → env var → config file → interactive prompt → default
  • Help text — flag appears with description, alias, default, and choices
  • Shell completions — tab-complete suggests us, eu, ap
  • Testing — override via env, config, or answers in test harness

Design Principles

  1. Schema is the law. Everything derives from it: parsing, types, resolution, help, completions.
  2. Progressive disclosure. A 5-line script stays 5 lines. Complexity scales by composition.
  3. Deterministic behavior. Resolution rules are explicit and consistent.
  4. Portable by default. Core avoids runtime-specific APIs; adapters handle the edges.
  5. Ergonomic, not clever. Predictable DX over type-theory stunts.

Released under the MIT License.