Skip to content

Changelog

This page surfaces the repository changelog directly inside the docs site.

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

Fixed

  • Docs deploy — remove dead Workers runtime vars (BUN_VERSION, NODE_OPTIONS) that had no effect on the static assets Worker, and add html_handling: "drop-trailing-slash" so clean URLs resolve correctly instead of 404ing on trailing slashes.

2.0.1 - 2026-04-07

Fixed

  • Schema URL resolution$schema in emitted definition documents now points at dreamcli.schema.json instead of the /schema subpath export, which doesn't resolve on the jsdelivr CDN.
  • GitHub Pages deploy — use env import from node:process for env access and set VitePress base to /dreamcli/ so assets and links resolve correctly on GitHub Pages.
    This allows for the old github pages deploy to work as an alternative to the cf workers.
  • Publish pipeline — split build and publish into separate jobs, run build before pack with --ignore-scripts to prevent prepack output from breaking GITHUB_OUTPUT parsing, hardcode the npm CDN schema URL instead of the unreliable jsr.io esm.sh path, replace actions/setup-node with bun's native registry auth, and switch internal imports to #dreamcli/* subpath imports.
  • Package exports map — moved conditional exports from publishConfig into top-level exports so local resolution matches what consumers see after install.

2.0.0 - 2026-04-07

Added

  • String-literal schema DSL — added a single-source schema surface that parses definitions at compile time and runtime, then reuses the same model for JSON Schema generation.
  • Published definition schema export — added @kjanat/dreamcli/schema so tooling and docs consumers can import the generated definition schema locally instead of relying on the CDN URL.
  • Fish and PowerShell shell completions — expanded completion support beyond Bash and Zsh.
  • Source-backed docs surfaces — added generated API inventory pages, per-entrypoint symbol routes, source-backed example pages with related symbol links, and reference guides for planner, resolver, output, schema, support, migration, troubleshooting, and semantic deltas.
  • gh-project workflow helper — added a DreamCLI-powered project tool for syncing the re-foundation task board and PRD state.

Changed

  • Package identity and build pipeline — the package is now published as @kjanat/dreamcli, ships ESM-only, emits the definition schema during the tsdown prepare hook, tightens published exports for runtime-specific consumers, and hardens npm, JSR, and docs release checks.
  • Docs app architecture — VitePress now builds reference and example pages from data loaders instead of static generated files, adds runtime/twoslash settings UI, improves mobile twoslash UX, and copies root artifacts into deployed docs output.
  • Examples and walkthroughs — the gh example grew into a multi-file workspace canary, and the example set now doubles as richer docs source with broader JSDoc coverage and better walkthrough material.

Fixed

  • Completion and alias handling — hidden compatibility aliases now parse correctly, Bash and Zsh completion behavior is safer and more consistent, and shell completion edge cases were tightened.
  • Aggregate validation diagnostics — mixed flag and arg validation failures now surface clearer per-issue labels plus value-source labels such as env ... and stdin.
  • Schema and docs integration — schema URLs, generated meta descriptions, twoslash rendering, and source-backed docs pages now build reliably across local, CI, and Cloudflare deploys.
  • CLI and runtime edge cases — unresolved schema references now fail closed, runtime/support checks were hardened, and several dispatch/output/runtime regressions were corrected.

1.0.0 - 2026-04-02

Added

Release Automation

  • GitHub Actions npm publish workflow (.github/workflows/publish-npm.yml) — publishes the package to npm on GitHub release with provenance enabled, bringing npm release automation in line with the existing JSR publish flow.

Canonical Semantics Guide

  • docs/guide/semantics.md — centralized reference for parser and resolver behavior, including repeated flags, short-flag stacking, -- separator rules, --no-* alias behavior, value-source precedence, non-interactive prompt skipping, propagated-flag masking, and default-command root help/completion semantics.

Plugin Lifecycle Hooks

  • plugin(hooks, name?) and .plugin(definition) expose a typed extension surface around command execution.
  • Lifecycle phasesbeforeParse, afterResolve, beforeAction, and afterAction let plugins observe or instrument execution without reaching into CLI internals.

derive() Command Context

  • command(...).derive(handler) adds typed command-scoped pre-action context derived from fully resolved flags and args.
  • Derived context merges into ctx so commands can validate once and consume typed values in the action handler.

Schema Export and Validation

  • generateInputSchema() exports JSON Schema from CLI definitions for machine validation and tooling.
  • Schema export docs and reference coverage now document export formats, discriminator behavior, and default/hidden command handling.

Runtime Surface and Execution Options

  • Runtime support matrix and version guards added around adapter creation.
  • run() accepts jsonMode in options, letting callers force structured output without shell-level flags.
  • out.table() format and stream overrides expose finer control over tabular output.

Changed

  • Docs navigation and entrypoints — guide pages now link to the canonical semantics guide, the API reference landing page includes quick import guidance and key factories per subpath export, and the VitePress sidebar surfaces the semantics page under the Advanced guide section.
  • Public API surface tightened — runtime exports were pruned and guarded, explicit require conditions were added to package exports, and self-referencing package imports were hardened.
  • Docs and examples expanded — JSDoc/reference coverage grew across exported symbols, schema export/testing/runtime docs were added, and the gh walkthrough became a multi-file example package.
  • CI and packaging hardened — version-sync checks, supported Node pinning, preview publish verification, and Bun-pack package validation were added around the release surface.

Fixed

  • Default-command UX — single-command root help is merged correctly, root completions surface default-command flags, unknown root flags are rejected cleanly, and schema discriminator handling matches the actual default-command surface.
  • Stdin and runtime behavior — stdin reads defer until dispatch needs them, empty pipes are distinguished from no pipe, and test adapters now match real runtime behavior more closely.
  • Parser/help/completion/output edge cases — optional array flags resolve to [], variadic help formatting is corrected, bash/zsh completion edge cases are hardened, non-finite JSON values are rejected, and table options are preserved correctly.

0.9.2 - 2026-03-30

Added

Stdin-Backed Positional Arguments

  • ArgBuilder.stdin() lets positional args consume piped stdin when no CLI token is provided.
  • RuntimeAdapter.readStdin() adds full stdin reads to the runtime contract across Node, Bun, and Deno adapters.
  • RunOptions.stdinData and testkit plumbing let in-process tests feed stdin-backed commands without touching real process state.
  • Comprehensive stdin coverage added across schema, resolver, runtime, and testkit tests.

Changed

  • Positional-arg resolution for stdin-enabled args expanded from CLI → env → default to CLI → stdin → env → default.
  • Scripts now separate lint from format, so linting no longer doubles as a rewrite step.

0.9.1 - 2026-03-30

Added

Default Command Support

  • CLIBuilder.default(command) lets a CLI run a fallback command when no subcommand is specified.
  • Root args and flags flow through the default command while explicit subcommands still take precedence.

Package.json Auto-Discovery

  • CLIBuilder.packageJson(settings?) — opt-in builder method that enables automatic package.json discovery at .run() time. Walks up from cwd to find the nearest package.json and merges version and description into the CLI schema. Explicit .version() and .description() calls always take precedence over discovered values.
  • PackageJsonSettings.inferName — when true, infers the CLI binary name from the bin key (first key of the object) or the package name field (scope stripped). Defaults to false.
  • discoverPackageJson(adapter) — pure function that walks up from adapter.cwd to find and parse the nearest package.json. Returns PackageJsonData | null. All I/O flows through the adapter — fully testable with virtual filesystems.
  • inferCliName(pkg) — resolves CLI name from PackageJsonData with priority: bin key → scoped name (stripped) → undefined.
  • Silent error handling — malformed JSON, non-object roots, and missing package.json all return null (not errors). Deno permission denials degrade gracefully via the adapter's existing readFile contract.
  • Completions skip — package.json discovery is skipped for the completions subcommand, matching the existing config discovery skip pattern.
  • 43 new testspackage-json.test.ts (24 unit tests: walk-up resolution, field extraction, error resilience, Windows path termination) and cli-package-json.test.ts (19 integration tests: version/description fill, name inference, precedence, walk-up, completions skip, combined with config, error resilience).

help Virtual Subcommand

  • BINARY help <command> produces the same output as BINARY <command> --help. Rewrites argv via recursive execute() — no dispatch duplication.
  • Nested supporthelp db migrate works like db migrate --help.
  • Bare help shows root help.
  • Defers to real commands — if the user registers a command named help (or aliased as help), it takes priority over the virtual subcommand.
  • --json propagationhelp --json <command> correctly preserves json mode (help text routes to stderr, stdout reserved for data).
  • 10 new tests across cli.test.ts (7), cli-nesting.test.ts (2), cli-json.test.ts (1).

Arg Environment Variable Resolution

  • ArgBuilder.env(varName) binds a positional argument to an environment variable. When the CLI value is absent, the resolver reads the env var and coerces the string to the arg's declared kind (passthrough for strings, Number() with NaN guard for numbers, parseFn invocation for custom args). Resolution order: CLI → env → default.
  • ArgSchema.envVar field (string | undefined) stores the env var name on the runtime schema descriptor.
  • Env coercion for args via coerceArgEnvValue() in the resolver. Handles string (passthrough), number (parse + NaN guard), and custom (delegates to parseFn, wraps thrown errors).
  • [env: VAR] annotation in help output for args with env bindings, matching the existing flag annotation style.
  • Actionable required-arg error hintsbuildRequiredArgSuggest() generates suggestions including the env var when configured (e.g. "Provide a value for <target> or set DEPLOY_TARGET").
  • 16 new testsresolve-arg-env.test.ts (15 tests covering string/number/custom coercion, CLI > env > default precedence, deprecation warnings, error cases) and 1 help output test for the [env: VAR] annotation.

Command Metadata

  • CommandMeta added to action handlers and middleware, carrying the CLI name, invoked binary, version, and resolved leaf command name.

Documentation Site, README, and Examples

  • README added with project pitch, usage, install guidance, and comparison table.
  • Examples directory added with seven implementation examples.
  • VitePress documentation site added with concepts, guide, and reference sections.
  • GitHub Pages deploy workflow and sitemap added for hosted docs.
  • Walkthrough guide added for a GitHub CLI-style example application.

Changed

  • Completion generation was reorganized into shell-specific generators and the package/tooling surface was refreshed for the 0.9.1 milestone.
  • Comprehensive public-facing JSDoc examples were added to the builder APIs.

Fixed

  • Default commands no longer swallow nested unknown-command errors.

0.9.0 - 2026-02-11

Added

Deno Adapter

  • createDenoAdapter(ns?) — full RuntimeAdapter implementation for Deno. Reads argv from Deno.args (prepends synthetic ['deno', 'run'] for parity), env from Deno.env.toObject(), cwd from Deno.cwd(), stdout/stderr via TextEncoderDeno.stdout.write/Deno.stderr.write, stdin via Deno.stdin.readable stream with line-buffered reader, TTY detection via isTerminal(), readFile via Deno.readTextFile, and homedir/configDir from env vars.
  • Permission-safePermissionDenied errors gracefully degrade: env falls back to {}, cwd to /, readFile to null. Non-permission errors propagate.
  • deno-builtins.d.ts — ambient type declarations for TextEncoder, TextDecoder, and ReadableStream (needed because lib: ["ES2022"] excludes web platform APIs).
  • createAdapter() auto-detection now handles Deno runtime via globalThis.Deno feature probing.
  • Re-exported createDenoAdapter and DenoNamespace from @kjanat/dreamcli/runtime subpath.

Cross-Runtime CI

  • GitHub Actions CI workflow (.github/workflows/ci.yml) — lint+typecheck (Bun), test matrix (Node LTS + Bun stable), Deno smoke test, build with publint+attw.
  • Deno smoke test (scripts/deno-smoke-test.ts) — runs on real Deno runtime after build, exercising createDenoAdapter() against actual Deno APIs.

JSR Publishing

  • deno.json with JSR package config (@kjanat/dreamcli), three subpath exports, publish include/exclude rules.
  • GitHub Actions publish workflow (.github/workflows/publish-jsr.yml) — publishes to JSR on GitHub release with OIDC provenance.

Changed

  • .ts import extensions — all import specifiers switched from .js to .ts via allowImportingTsExtensions. tsconfig updated: noEmit: true + allowImportingTsExtensions: true replace declaration/declarationMap/sourceMap/outDir (all handled by tsdown). Removes the need for Deno's unstable: ["sloppy-imports"].
  • detectRuntime() updated with Deno detection via globalThis.Deno?.version?.deno.
  • createAdapter() switch now covers 'deno' case alongside 'node' and 'bun'.
  • Completion generator: typeof check on globalThis narrowed to avoid Deno type errors.
  • runtime/deno.ts expanded from empty stub (~5 lines) to full implementation (~318 lines).
  • Test count: 1695 tests across 47 test files (up from 1658 in v0.8.0).

0.8.0 - 2026-02-11

Breaking

  • Subpath exports — single "." entry split into ".", "./testkit", "./runtime". Test utilities (runCommand, createCaptureOutput, createTestPrompter, createTestAdapter, PROMPT_CANCEL) moved to @kjanat/dreamcli/testkit. Runtime adapters (createAdapter, createNodeAdapter, createBunAdapter, detectRuntime, ExitError, RUNTIMES, RuntimeAdapter) moved to @kjanat/dreamcli/runtime. createTestAdapter/TestAdapterOptions exported only from @kjanat/dreamcli/testkit.

Added

Spinner & Progress Bar

  • out.spinner(text, options?) creates a spinner handle for indeterminate progress feedback. Returns a SpinnerHandle with update(text), succeed(text?), fail(text?), stop(), and wrap(promise, options?) for auto-succeed/fail on promise settlement.
  • out.progress(options) creates a progress bar handle. Pass total for determinate mode (percentage bar); omit for indeterminate (pulsing animation). Returns a ProgressHandle with increment(n?), update(value), done(text?), and fail(text?).
  • Four rendering modes with automatic dispatch:
    • TTY — animated braille spinner (80ms frames) and bar rendering with ANSI cursor control. Hides cursor during animation, restores on terminal methods.
    • Static (fallback: 'static') — plain text at lifecycle boundaries (start, succeed, fail). No ANSI codes. For CI and piped output.
    • Noop (fallback: 'silent', default) — all methods are no-ops. Silent in non-TTY.
    • JSON mode — always noop (structured output only).
  • Active handle tracking — at most one spinner or progress may be active at a time. Creating a new one implicitly stops the previous to avoid garbled terminal output.
  • ActivityEvent discriminated union — 10-variant DU capturing spinner and progress lifecycle events (spinner:start, spinner:update, spinner:succeed, spinner:fail, spinner:stop, progress:start, progress:increment, progress:update, progress:done, progress:fail).
  • Testkit capture handlesCaptureOutputChannel subclass overrides spinner() and progress() to record ActivityEvent[] for assertion. CapturedOutput.activity array added.
  • New public types exported from barrel: ActivityEvent, Fallback, SpinnerHandle, SpinnerOptions, ProgressHandle, ProgressOptions.
  • out.stopActive() public method on Out to clean up active spinner/progress timers. Prevents process hangs when a handler throws before reaching a terminal method (stop, succeed, fail, done). runCommand() calls it automatically in a finally block; direct createOutput() users call it themselves.
  • progress:increment activity event — 10th ActivityEvent variant { type: 'progress:increment', delta }. increment() now emits this instead of reusing progress:update, making capture events unambiguous for testing.

Changed

  • Out interface extended with spinner() and progress() methods.
  • CapturedOutput extended with activity: ActivityEvent[] field.
  • createCaptureOutput now returns a CaptureOutputChannel that records activity events separately from stdout/stderr.
  • FlagParseFn<T> widened from (raw: string) => T to (raw: unknown) => T. Config files carry structured JSON data — parseFn now receives the raw value directly and is responsible for narrowing. CLI/env still pass strings; config passes the JSON value as-is.
  • Out interface extended with stopActive() method for explicit timer cleanup.
  • ActivityEvent union widened from 9 to 10 variants (added progress:increment).
  • OutputChannel refactored: activity handle implementations extracted to activity.ts (~581 lines), WriteFn type and writeLine helper extracted to writer.ts (~30 lines). index.ts reduced from 1156 to 589 lines. No public API changes.
  • All activity handle output (static and TTY) now routes to stderr. Previously static mode used a StaticWriters pair routing some output to stdout; the dual-writer abstraction is removed.
  • runCommand() calls out.stopActive() in a finally block, ensuring timer cleanup on handler exceptions.
  • Resolve coercion unified — three near-identical functions (coerceEnvValue ~105 lines, coerceConfigValue ~120 lines, coercePromptValue ~120 lines) replaced by single coerceValue() using CoerceSource discriminated union ('env' | 'config' | 'prompt'). Error messages parameterized via sourceLabel()/sourceDetails()/coercionError() helpers. resolve/index.ts reduced from ~1115 to ~940 lines.
  • Activity types extracted — 7 activity/output types (Fallback, SpinnerOptions, SpinnerHandle, ProgressOptions, ProgressHandle, ActivityEvent, TableColumn) moved from schema/command.ts to schema/activity.ts (~150 lines). command.ts reduced from 898 to 784 lines.
  • Root help extractedformatRootHelp() + padEnd() + wrapText() moved from cli/index.ts to cli/root-help.ts (~133 lines). Uses structural CLISchemaLike interface to avoid circular imports. cli/index.ts reduced from 901 to 793 lines.
  • infer/ stub deleted — removed empty src/core/infer/index.ts.
  • Source files in published package"src" added to files array in package.json.
  • Package manager migrated — pnpm → bun. packageManager set to bun@1.3.9.
  • Build configtsdown.config.ts changed to multi-entry build with minify: true.
  • Test count: 1658 tests across 46 test files (up from 1518 in v0.7.0).

Fixed

  • --config=<path> equals form now correctly parsed and stripped from argv before dispatch.
  • Zsh completion: multi-alias flags use all short aliases in the exclusion group, not just the first.
  • Bash completion: escapeForSingleQuote() sanitizes compgen -W words to prevent shell injection.
  • Win32 resolveConfigDir: strip trailing separator from homedir to avoid doubled backslashes on drive roots (e.g. C:\).
  • Win32 resolveConfigDir: treat empty APPDATA as unset, falling back to homedir-based path.
  • Win32 homedir: add HOMEDRIVE+HOMEPATH fallback; never use HOMEPATH alone.
  • Levenshtein distance: replace 2D array with Uint16Array rolling buffer, drop defensive undefined guards.
  • Completions command detection via schema lookup instead of raw argv[0] string match.
  • Child flag with propagate: false correctly masks ancestor's propagated flag of the same name.
  • Dispatch: exhaustiveness guard on subResult switch in nested command resolution.
  • Nested group help: binName built from full command path, not just root.
  • Config loader: lowercase extensions in buildExtensionList/buildLoaderMap for case-insensitive matching.
  • Empty-string env var fallbacks in runtime adapter treated as unset.
  • ProgressHandle.increment() was emitting progress:update events indistinguishable from update() calls. Now emits progress:increment with delta field.
  • Prompt number coercioncoercePromptValue was missing NaN guard for number flags; now handled by unified coerceValue().

0.7.0 - 2026-02-10

Added

Subcommand Nesting

  • CommandBuilder.command(sub) registers nested subcommands, building recursive command trees of unlimited depth. Parent commands store children as type-erased ErasedCommand entries — the parent doesn't need the child's generic types.
  • group(name) factory as a semantic alias for command(). Communicates intent: groups organise subcommands, leaf commands have actions. A group may also have its own .action() (e.g. git remote lists remotes, git remote add dispatches to a child).
  • flag.propagate() modifier marks a flag for automatic inheritance by all descendant commands. Propagated flags are collected from the ancestor chain at dispatch time. Child commands override a propagated flag by redeclaring the same name — child definition wins completely.
  • Recursive dispatch in CLIBuilder.execute(). Walks argv segments matching command names at each tree level. Handles hybrid commands (action + subcommands): subcommand match takes priority, else falls through to the parent handler. Groups without handlers show help.
  • Nested helpformatHelp() renders a "Commands:" section listing available subcommands for group commands. Usage line adapts to show <command> placeholder when subcommands exist.
  • Nested completions — bash and zsh generators traverse the full command tree depth. Propagated flags included at each nesting level. Bash uses path-keyed case statements; zsh generates per-group helper functions.
  • Scoped "did you mean?" — typo suggestions search within the current command scope, not the global command list. Help hint shows scoped path (e.g. Run 'myapp db --help').

Changed

  • CommandSchema extended with commands: readonly CommandSchema[] for nested subcommand schemas.
  • ErasedCommand extended with subcommands: ReadonlyMap<string, ErasedCommand> for dispatch.
  • ErasedCommand interface moved from cli/index.ts to schema/command.ts (shared location).
  • FlagSchema extended with propagate: boolean (default false).
  • RunOptions extended with mergedSchema internal field for propagated flag injection.
  • Dispatch logic extracted to cli/dispatch.ts (~285 lines) and flag propagation to cli/propagate.ts (~87 lines), reducing cli/index.ts by ~220 lines.
  • Test count: 1518 tests across 43 test files (up from 1300 in v0.6.0).

Fixed

  • Completion generator no longer recurses into hidden command subtrees.
  • Dispatch respects -- end-of-flags sentinel before command names.

0.6.0 - 2026-02-10

Added

Config File Discovery

  • CLIBuilder.config(appName) enables config file discovery with XDG-compliant search paths. Searches .{app}.json and {app}.config.json in cwd, then {configDir}/{app}/config.json. JSON loader built-in.
  • --config <path> global flag overrides config file discovery path. Extracted from argv before command dispatch.
  • CLIBuilder.configLoader(loader) plugin hook for registering custom config format loaders (YAML, TOML, etc.). configFormat(extensions, parseFn) convenience factory.

Schema Additions

  • flag.custom(parseFn) — new flag kind accepting an arbitrary parse function with full return-type inference from parseFn. Wired through parser coercion, all three resolve coercions (env, config, prompt), and help formatter.
  • .deprecated(message?) modifier on FlagBuilder and ArgBuilder. Emits structured DeprecationWarning during resolution when a deprecated flag/arg is explicitly provided (CLI, env, config, prompt — not for default fallthrough). Renders [deprecated] or [deprecated: <reason>] in help text.

RuntimeAdapter Extensions

  • readFile — async file read returning null for ENOENT, throws on other errors. Uses lazy import('node:fs/promises').
  • homedir — computed from env vars (HOME/USERPROFILE) + platform; avoids node:os.
  • configDirXDG_CONFIG_HOME on Unix, APPDATA on Windows; falls back to ~/.config or ~\AppData\Roaming.

Changed

  • RuntimeAdapter interface extended with readFile, homedir, configDir.
  • NodeProcess interface extended with platform field.
  • FlagSchema extended with parseFn: FlagParseFn<unknown> | undefined.
  • FlagSchema extended with deprecated: string | true | undefined.
  • ArgSchema extended with deprecated: string | true | undefined.
  • ResolveResult extended with warnings: readonly DeprecationWarning[].
  • CLISchema extended with configSettings for config file discovery.
  • FlagParseFn<T>, DeprecationWarning, ConfigResult, FormatLoader, configFormat exported from public API.
  • Test count: ~1300 tests (up from 1198 in v0.5.0).

0.5.0 - 2026-02-10

Added

Shell Completions

  • Bash completion generator — produces self-contained bash scripts with COMP_WORDS/COMP_CWORD scanning, per-command case branches with flag and enum value completions, and complete -F registration.
  • Zsh completion generator — produces #compdef scripts with _arguments flag specs, _describe subcommand lists, and enum value completions via ->state dispatch.
  • CLIBuilder.completions() adds a built-in completions --shell <bash|zsh> subcommand that outputs a ready-to-eval completion script. In --json mode, emits { script } JSON object. Fish/PowerShell accepted in the enum with descriptive "not yet supported" errors.

Runtime Portability

  • detectRuntime() via globalThis feature detection — identifies Node, Bun, Deno, or unknown. Exported Runtime type and RUNTIMES constant.
  • createAdapter() auto-detecting adapter factory — calls detectRuntime() and returns the appropriate RuntimeAdapter. CLIBuilder.run() uses it as default when no adapter is provided.
  • Bun adapter implementing RuntimeAdapter by delegating to the Node adapter (Bun's Node-compatible APIs).

Fixed

  • Completion: cross-command enum collision — collectEnumCases() scoped per-command to prevent enum values from one command leaking into another's completions.
  • Completion: shell injection safety — quoteShellArg() escapes schema.name and enum values in generated scripts.
  • Completion: conditional --version — bash generator omits --version from completions when schema.version is undefined, matching zsh behavior.
  • CLI: --json mode completions output { script } JSON instead of raw script text.
  • CLI: guard against double .completions() call.
  • Runtime: NodeProcess type exported from main barrel.
  • Runtime: @internal removed from GlobalForDetect (contradicted public export).
  • Runtime: createAdapter() switch uses default: never exhaustiveness guard.

Changed

  • Shell completion types, generator stubs, and barrel exports added to src/core/completion/.
  • Test count: 1198 tests across 35 test files (up from 1010 in v0.4.0).

0.4.0 - 2026-02-09

Added

Typed Middleware

  • middleware<Output>(handler) factory creating phantom-branded Middleware<Output> values. Handler receives { args, flags, ctx, out, next } — call next(additions) to continue the chain with typed context, omit next() to short-circuit (auth guards), or await next() for wrap-around patterns (timing, try/catch).
  • CommandBuilder.middleware(m) registers middleware in execution order. Each call widens the context type parameter C via WidenContext<C, Output> intersection — Record<string, never> (the default) is replaced entirely on the first call, preventing never collapse. Adding middleware drops the current handler (type signature changed).
  • Context type parameter C on CommandBuilder<F, A, C>, ActionParams<F, A, C>, and ActionHandler<F, A, C>. ctx in the action handler is Readonly<C> — property access is a type error until middleware extends it.
  • Middleware chain execution (executeWithMiddleware) in testkit. Builds a continuation chain from back to front; context accumulates via { ...ctx, ...additions } at each step. Replaces the former invokeHandler bridge.

Structured Output

  • out.json(value) emits JSON.stringify(value) to stdout. Always targets stdout regardless of JSON mode. Handlers should prefer this over out.log(JSON.stringify(...)).
  • out.table(rows, columns?) renders tabular data. In JSON mode: emits rows as JSON array. In text mode: pretty-prints aligned columns with headers (auto-inferred from first row when columns omitted). TableColumn<T> descriptor type with key and optional header.
  • out.jsonMode and out.isTTY readonly properties on Out interface. Handlers check these to skip decorative output (spinners, ANSI codes) when machine-readable output is expected or stdout is piped.
  • --json global flag detection in CLIBuilder.execute(). Strips --json from argv before command dispatch. CLI-level dispatch errors (unknown command, no action) rendered as JSON when active.
  • jsonMode and isTTY options on RunOptions, CLIRunOptions, and OutputOptions. CLIBuilder.run() auto-sources isTTY from adapter.isTTY.

Changed

  • ActionParams<F, A>ActionParams<F, A, C> with ctx: Readonly<C> (was Readonly<Record<string, unknown>>).
  • CommandBuilder carries third type parameter C (default Record<string, never>). All metadata/builder methods preserve C in return type.
  • CommandSchema.middleware added as readonly ErasedMiddlewareHandler[].
  • Out interface extended with json(), table(), jsonMode, and isTTY.
  • OutputChannel constructor accepts isTTY and jsonMode from resolved options. log/info redirect to stderr writer in JSON mode.
  • runCommand and CLIBuilder.execute error paths render JSON when jsonMode active.
  • createCaptureOutput accepts jsonMode and isTTY options.
  • Test count: 1010 tests across 31 test files (up from 797 in v0.3.0).

0.3.0 - 2026-02-09

Added

Interactive Prompting

  • Prompt type definitions (PromptConfig) as a discriminated union with four kinds: confirm, input, select, multiselect. Each kind has specialized fields — InputPromptConfig supports placeholder and validate, select/multiselect support SelectChoice arrays with optional labels and descriptions.
  • FlagBuilder.prompt(config) metadata modifier for declaring prompt configuration on flags, following the same immutable builder pattern as .env() and .config().
  • Prompt engine interface (PromptEngine) with promptOne(config) → Promise<PromptResult> as a pluggable renderer seam. ResolvedPromptConfig variant guarantees non-empty choices for select/multiselect after merging from FlagSchema.enumValues.
  • Built-in terminal prompter (createTerminalPrompter(read, write)) with line-based I/O for confirm (y/n), input (with validation and placeholder), select (numbered list), and multiselect (comma-separated numbers with min/max). All prompts have a MAX_RETRIES = 10 safety valve.
  • Test prompter (createTestPrompter(answers, options?)) with queue-based answers for deterministic testing. PROMPT_CANCEL symbol sentinel for simulating cancellation. onExhausted: 'throw' | 'cancel' controls behavior when answer queue is empty.
  • Prompt resolution in the resolver. Resolution chain expanded from CLI > env > config > default to CLI > env > config > prompt > default. Flags with prompt config and no value from prior sources trigger prompter.promptOne(). Cancelled prompts fall through to default/required. Non-interactive mode (no prompter) skips prompts entirely.
  • ReadFn (() => Promise<string | null>) as the minimal stdin abstraction. null signals EOF/cancel. RuntimeAdapter extended with stdin: ReadFn and stdinIsTTY: boolean.
  • Node adapter stdin wraps process.stdin via dynamic import('node:readline') with lazy per-call readline interfaces. Minimal node:readline type declarations in node-builtins.d.ts avoid @types/node dependency.
  • Automatic prompt gating in CLIBuilder.run(): when stdinIsTTY=true and no explicit prompter provided, auto-creates createTerminalPrompter(adapter.stdin, adapter.stderr). Prompt output routed to stderr to avoid interfering with piped stdout.
  • Command-level .interactive(resolver) API on CommandBuilder. Resolver receives partially resolved flags (after CLI/env/config), returns Record<string, PromptConfig | false | undefined> controlling which flags get prompted. Truthy PromptConfig overrides per-flag prompt; false explicitly suppresses; absent falls back to per-flag .prompt() config.
  • Testkit answers convenience on RunOptions. Accepts Record<string, TestAnswer> to auto-create a test prompter. prompter field also available for explicit engine injection. CLIRunOptions mirrors both fields.

Changed

  • resolve() is now async (Promise<ResolveResult>). All callers (runCommand, CLIBuilder.execute, CLIBuilder.run) updated to await.
  • Resolution chain expanded from CLI > env > config > default to CLI > env > config > prompt > default.
  • ResolveOptions extended with optional prompter: PromptEngine field.
  • RunOptions extended with prompter and answers fields.
  • CLIRunOptions extended with prompter and answers fields.
  • RuntimeAdapter extended with stdin: ReadFn and stdinIsTTY: boolean.
  • createTestAdapter defaults to EOF-returning stdin and stdinIsTTY: false.
  • Test count: 797 tests across 21 test files (up from 599 in v0.2.0).

0.2.0 - 2026-02-09

Added

Resolution Chain

  • Environment variable resolution in the resolver. Flags with .env('VAR') now resolve from the env record after CLI and before default. String, number, boolean (lenient: true/false/1/0/yes/no), enum, and array (comma-separated) coercion. Invalid env values produce ValidationError with TYPE_MISMATCH or INVALID_ENUM codes.
  • Config object resolution in the resolver. Flags with .config('dotted.path') resolve from a plain Record<string, unknown> after env and before default. resolveConfigPath() walks nested objects segment-by-segment. Config values may already be typed from JSON — coercion is lenient for matching types. Full chain: CLI > env > config > default.
  • Resolution source annotations in help text. Flags with env or config declarations now display [env: VAR] and [config: path] in formatHelp() output, ordered between description text and presence indicators.
  • Actionable required-flag error hints. When a required flag is missing after full resolution, ValidationError.suggest lists all configured sources (e.g. "Provide --region, set DEPLOY_REGION, or add deploy.region to config"). CI-friendly error messages with envVar/configPath in details.
  • Env/config wiring through testkit and CLI builder. RunOptions and CLIRunOptions accept env and config fields. runCommand() threads them into resolve(). CLIBuilder.run() auto-sources adapter.env when no explicit env option is provided.

Changed

  • Resolution chain expanded from CLI > default (v0.1) to CLI > env > config > default.
  • resolve() now accepts optional ResolveOptions parameter with env and config fields.
  • ResolveOptions exported from public API surface.

0.1.0 - 2026-02-09

Added

Core Framework

  • Structured errors (CLIError, ParseError, ValidationError) with stable error codes, toJSON() serialization, type guard functions (isCLIError, isParseError, isValidationError), and actionable suggest hints.
  • Flag builder (flag) with full type inference for boolean, string, number, enum, and array kinds. Supports .alias(), .default(), .required(), .describe(), .hidden(), .deprecated(), .env(), and .config() declarations.
  • Arg builder (arg) with type inference for string, number, custom parse functions, and variadic args. Supports .default(), .required(), .optional(), and .describe().
  • Command builder (command) with .flag(), .arg(), .description(), .example(), .hidden(), .alias(), and .action(). Accumulates phantom types so handler receives fully inferred flags and args.
  • Argv parser with tokenizer (tokenize) and schema-aware parser (parse). Handles long/short flags, = syntax, boolean negation (--no-*), flag stacking (-abc), -- separator, and type coercion against the schema.
  • Resolution chain (CLI parsed value → schema default). Validates all required flags/args, aggregates multiple errors into a single throw, and provides per-field suggestions.
  • Auto-generated help text (formatHelp) from command schema, including usage line, description, positional args, flags with types/defaults/aliases, examples section, and subcommand listing.
  • Output channel (createOutput) with log/info/warn/error methods, WriteFn abstraction, verbosity levels (normal/quiet), and TTY detection. Includes createCaptureOutput() test helper.
  • Test harness (runCommand) for running commands as pure functions with injected argv, env, and captured output. Returns RunResult with exitCode, stdout, stderr, and error.
  • CLI builder (cli) with .command() registration, .version(), subcommand dispatch, automatic --help/--version flag handling, and unknown-command error with suggestions.
  • RuntimeAdapter interface defining the platform abstraction boundary (argv, env, cwd, stdout/stderr, isTTY, exit). Includes createTestAdapter() for injectable test stubs and ExitError for testable process exits.
  • Node.js adapter (createNodeAdapter) wiring process.argv, process.env, process.cwd(), process.stdout/stderr, and TTY detection.
  • Stub files for Bun adapter, Deno adapter, runtime auto-detection, and shell completion generation.

Project Infrastructure

  • Project scaffold with src/ structure, TypeScript strict config, and ESM + CJS dual build via tsdown.
  • Vitest test framework with 464 passing tests across 12 test files.
  • Biome linter and dprint formatter configuration.
  • @vitest/coverage-v8 for test coverage reporting.
  • @arethetypeswrong/cli and publint for package quality checks.
  • CI script (pnpm run ci) running typecheck, lint, test, and build in sequence.
  • PRD.md with full product requirements document.
  • MIT License.
  • Markdownlint configuration.

Released under the MIT License.