Changelog
This page surfaces the repository changelog directly inside the docs site.
- Source of truth:
CHANGELOG.md - Related pages: API Reference, Semantic Delta Log, Examples
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 addhtml_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 —
$schemain emitted definition documents now points atdreamcli.schema.jsoninstead of the/schemasubpath export, which doesn't resolve on the jsdelivr CDN. - GitHub Pages deploy — use
envimport fromnode:processfor env access and set VitePressbaseto/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-scriptsto prevent prepack output from breakingGITHUB_OUTPUTparsing, hardcode the npm CDN schema URL instead of the unreliable jsr.io esm.sh path, replaceactions/setup-nodewith bun's native registry auth, and switch internal imports to#dreamcli/*subpath imports. - Package exports map — moved conditional exports from
publishConfiginto top-levelexportsso 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/schemaso 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-projectworkflow 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
ghexample 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 ...andstdin. - 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 phases —
beforeParse,afterResolve,beforeAction, andafterActionlet 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
ctxso 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()acceptsjsonModein 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
requireconditions 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
ghwalkthrough 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.stdinDataand 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
lintfromformat, 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 automaticpackage.jsondiscovery at.run()time. Walks up fromcwdto find the nearestpackage.jsonand mergesversionanddescriptioninto the CLI schema. Explicit.version()and.description()calls always take precedence over discovered values.PackageJsonSettings.inferName— whentrue, infers the CLI binary name from thebinkey (first key of the object) or the packagenamefield (scope stripped). Defaults tofalse.discoverPackageJson(adapter)— pure function that walks up fromadapter.cwdto find and parse the nearestpackage.json. ReturnsPackageJsonData | null. All I/O flows through the adapter — fully testable with virtual filesystems.inferCliName(pkg)— resolves CLI name fromPackageJsonDatawith priority: bin key → scoped name (stripped) →undefined.- Silent error handling — malformed JSON, non-object roots, and missing
package.jsonall returnnull(not errors). Deno permission denials degrade gracefully via the adapter's existingreadFilecontract. - Completions skip — package.json discovery is skipped for the
completionssubcommand, matching the existing config discovery skip pattern. - 43 new tests —
package-json.test.ts(24 unit tests: walk-up resolution, field extraction, error resilience, Windows path termination) andcli-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 asBINARY <command> --help. Rewrites argv via recursiveexecute()— no dispatch duplication.- Nested support —
help db migrateworks likedb migrate --help. - Bare
helpshows root help. - Defers to real commands — if the user registers a command named
help(or aliased ashelp), it takes priority over the virtual subcommand. --jsonpropagation —help --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,parseFninvocation for custom args). Resolution order: CLI → env → default.ArgSchema.envVarfield (string | undefined) stores the env var name on the runtime schema descriptor.- Env coercion for args via
coerceArgEnvValue()in the resolver. Handlesstring(passthrough),number(parse + NaN guard), andcustom(delegates toparseFn, wraps thrown errors). [env: VAR]annotation in help output for args with env bindings, matching the existing flag annotation style.- Actionable required-arg error hints —
buildRequiredArgSuggest()generates suggestions including the env var when configured (e.g. "Provide a value for <target> or set DEPLOY_TARGET"). - 16 new tests —
resolve-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
CommandMetaadded 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.1milestone. - 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?)— fullRuntimeAdapterimplementation for Deno. Reads argv fromDeno.args(prepends synthetic['deno', 'run']for parity), env fromDeno.env.toObject(), cwd fromDeno.cwd(), stdout/stderr viaTextEncoder→Deno.stdout.write/Deno.stderr.write, stdin viaDeno.stdin.readablestream with line-buffered reader, TTY detection viaisTerminal(),readFileviaDeno.readTextFile, and homedir/configDir from env vars.- Permission-safe —
PermissionDeniederrors gracefully degrade: env falls back to{}, cwd to/, readFile tonull. Non-permission errors propagate. deno-builtins.d.ts— ambient type declarations forTextEncoder,TextDecoder, andReadableStream(needed becauselib: ["ES2022"]excludes web platform APIs).createAdapter()auto-detection now handles Deno runtime viaglobalThis.Denofeature probing.- Re-exported
createDenoAdapterandDenoNamespacefrom@kjanat/dreamcli/runtimesubpath.
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, exercisingcreateDenoAdapter()against actual Deno APIs.
JSR Publishing
deno.jsonwith 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
.tsimport extensions — all import specifiers switched from.jsto.tsviaallowImportingTsExtensions. tsconfig updated:noEmit: true+allowImportingTsExtensions: truereplacedeclaration/declarationMap/sourceMap/outDir(all handled by tsdown). Removes the need for Deno'sunstable: ["sloppy-imports"].detectRuntime()updated with Deno detection viaglobalThis.Deno?.version?.deno.createAdapter()switch now covers'deno'case alongside'node'and'bun'.- Completion generator:
typeofcheck onglobalThisnarrowed to avoid Deno type errors. runtime/deno.tsexpanded 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/TestAdapterOptionsexported only from@kjanat/dreamcli/testkit.
Added
Spinner & Progress Bar
out.spinner(text, options?)creates a spinner handle for indeterminate progress feedback. Returns aSpinnerHandlewithupdate(text),succeed(text?),fail(text?),stop(), andwrap(promise, options?)for auto-succeed/fail on promise settlement.out.progress(options)creates a progress bar handle. Passtotalfor determinate mode (percentage bar); omit for indeterminate (pulsing animation). Returns aProgressHandlewithincrement(n?),update(value),done(text?), andfail(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.
ActivityEventdiscriminated 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 handles —
CaptureOutputChannelsubclass overridesspinner()andprogress()to recordActivityEvent[]for assertion.CapturedOutput.activityarray added. - New public types exported from barrel:
ActivityEvent,Fallback,SpinnerHandle,SpinnerOptions,ProgressHandle,ProgressOptions. out.stopActive()public method onOutto 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 afinallyblock; directcreateOutput()users call it themselves.progress:incrementactivity event — 10thActivityEventvariant{ type: 'progress:increment', delta }.increment()now emits this instead of reusingprogress:update, making capture events unambiguous for testing.
Changed
Outinterface extended withspinner()andprogress()methods.CapturedOutputextended withactivity: ActivityEvent[]field.createCaptureOutputnow returns aCaptureOutputChannelthat records activity events separately from stdout/stderr.FlagParseFn<T>widened from(raw: string) => Tto(raw: unknown) => T. Config files carry structured JSON data —parseFnnow receives the raw value directly and is responsible for narrowing. CLI/env still pass strings; config passes the JSON value as-is.Outinterface extended withstopActive()method for explicit timer cleanup.ActivityEventunion widened from 9 to 10 variants (addedprogress:increment).OutputChannelrefactored: activity handle implementations extracted toactivity.ts(~581 lines),WriteFntype andwriteLinehelper extracted towriter.ts(~30 lines).index.tsreduced 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
StaticWriterspair routing some output to stdout; the dual-writer abstraction is removed. runCommand()callsout.stopActive()in afinallyblock, ensuring timer cleanup on handler exceptions.- Resolve coercion unified — three near-identical functions (
coerceEnvValue~105 lines,coerceConfigValue~120 lines,coercePromptValue~120 lines) replaced by singlecoerceValue()usingCoerceSourcediscriminated union ('env' | 'config' | 'prompt'). Error messages parameterized viasourceLabel()/sourceDetails()/coercionError()helpers.resolve/index.tsreduced from ~1115 to ~940 lines. - Activity types extracted — 7 activity/output types (
Fallback,SpinnerOptions,SpinnerHandle,ProgressOptions,ProgressHandle,ActivityEvent,TableColumn) moved fromschema/command.tstoschema/activity.ts(~150 lines).command.tsreduced from 898 to 784 lines. - Root help extracted —
formatRootHelp()+padEnd()+wrapText()moved fromcli/index.tstocli/root-help.ts(~133 lines). Uses structuralCLISchemaLikeinterface to avoid circular imports.cli/index.tsreduced from 901 to 793 lines. infer/stub deleted — removed emptysrc/core/infer/index.ts.- Source files in published package —
"src"added tofilesarray in package.json. - Package manager migrated — pnpm → bun.
packageManagerset tobun@1.3.9. - Build config —
tsdown.config.tschanged to multi-entry build withminify: 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()sanitizescompgen -Wwords to prevent shell injection. - Win32
resolveConfigDir: strip trailing separator from homedir to avoid doubled backslashes on drive roots (e.g.C:\). - Win32
resolveConfigDir: treat emptyAPPDATAas unset, falling back to homedir-based path. - Win32
homedir: addHOMEDRIVE+HOMEPATHfallback; never useHOMEPATHalone. - Levenshtein distance: replace 2D array with
Uint16Arrayrolling buffer, drop defensiveundefinedguards. - Completions command detection via schema lookup instead of raw
argv[0]string match. - Child flag with
propagate: falsecorrectly masks ancestor's propagated flag of the same name. - Dispatch: exhaustiveness guard on
subResultswitch in nested command resolution. - Nested group help:
binNamebuilt from full command path, not just root. - Config loader: lowercase extensions in
buildExtensionList/buildLoaderMapfor case-insensitive matching. - Empty-string env var fallbacks in runtime adapter treated as unset.
ProgressHandle.increment()was emittingprogress:updateevents indistinguishable fromupdate()calls. Now emitsprogress:incrementwithdeltafield.- Prompt number coercion —
coercePromptValuewas missing NaN guard for number flags; now handled by unifiedcoerceValue().
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-erasedErasedCommandentries — the parent doesn't need the child's generic types.group(name)factory as a semantic alias forcommand(). Communicates intent: groups organise subcommands, leaf commands have actions. A group may also have its own.action()(e.g.git remotelists remotes,git remote adddispatches 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 help —
formatHelp()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
CommandSchemaextended withcommands: readonly CommandSchema[]for nested subcommand schemas.ErasedCommandextended withsubcommands: ReadonlyMap<string, ErasedCommand>for dispatch.ErasedCommandinterface moved fromcli/index.tstoschema/command.ts(shared location).FlagSchemaextended withpropagate: boolean(defaultfalse).RunOptionsextended withmergedSchemainternal field for propagated flag injection.- Dispatch logic extracted to
cli/dispatch.ts(~285 lines) and flag propagation tocli/propagate.ts(~87 lines), reducingcli/index.tsby ~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}.jsonand{app}.config.jsonin 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 fromparseFn. Wired through parser coercion, all three resolve coercions (env, config, prompt), and help formatter..deprecated(message?)modifier onFlagBuilderandArgBuilder. Emits structuredDeprecationWarningduring 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 returningnullfor ENOENT, throws on other errors. Uses lazyimport('node:fs/promises').homedir— computed from env vars (HOME/USERPROFILE) +platform; avoidsnode:os.configDir—XDG_CONFIG_HOMEon Unix,APPDATAon Windows; falls back to~/.configor~\AppData\Roaming.
Changed
RuntimeAdapterinterface extended withreadFile,homedir,configDir.NodeProcessinterface extended withplatformfield.FlagSchemaextended withparseFn: FlagParseFn<unknown> | undefined.FlagSchemaextended withdeprecated: string | true | undefined.ArgSchemaextended withdeprecated: string | true | undefined.ResolveResultextended withwarnings: readonly DeprecationWarning[].CLISchemaextended withconfigSettingsfor config file discovery.FlagParseFn<T>,DeprecationWarning,ConfigResult,FormatLoader,configFormatexported 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_CWORDscanning, per-command case branches with flag and enum value completions, andcomplete -Fregistration. - Zsh completion generator — produces
#compdefscripts with_argumentsflag specs,_describesubcommand lists, and enum value completions via->statedispatch. CLIBuilder.completions()adds a built-incompletions --shell <bash|zsh>subcommand that outputs a ready-to-eval completion script. In--jsonmode, emits{ script }JSON object. Fish/PowerShell accepted in the enum with descriptive "not yet supported" errors.
Runtime Portability
detectRuntime()viaglobalThisfeature detection — identifies Node, Bun, Deno, or unknown. ExportedRuntimetype andRUNTIMESconstant.createAdapter()auto-detecting adapter factory — callsdetectRuntime()and returns the appropriateRuntimeAdapter.CLIBuilder.run()uses it as default when no adapter is provided.- Bun adapter implementing
RuntimeAdapterby 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()escapesschema.nameand enum values in generated scripts. - Completion: conditional
--version— bash generator omits--versionfrom completions whenschema.versionis undefined, matching zsh behavior. - CLI:
--jsonmode completions output{ script }JSON instead of raw script text. - CLI: guard against double
.completions()call. - Runtime:
NodeProcesstype exported from main barrel. - Runtime:
@internalremoved fromGlobalForDetect(contradicted public export). - Runtime:
createAdapter()switch usesdefault: neverexhaustiveness 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-brandedMiddleware<Output>values. Handler receives{ args, flags, ctx, out, next }— callnext(additions)to continue the chain with typed context, omitnext()to short-circuit (auth guards), or awaitnext()for wrap-around patterns (timing, try/catch).CommandBuilder.middleware(m)registers middleware in execution order. Each call widens the context type parameterCviaWidenContext<C, Output>intersection —Record<string, never>(the default) is replaced entirely on the first call, preventingnevercollapse. Adding middleware drops the current handler (type signature changed).- Context type parameter
ConCommandBuilder<F, A, C>,ActionParams<F, A, C>, andActionHandler<F, A, C>.ctxin the action handler isReadonly<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 formerinvokeHandlerbridge.
Structured Output
out.json(value)emitsJSON.stringify(value)to stdout. Always targets stdout regardless of JSON mode. Handlers should prefer this overout.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 whencolumnsomitted).TableColumn<T>descriptor type withkeyand optionalheader.out.jsonModeandout.isTTYreadonly properties onOutinterface. Handlers check these to skip decorative output (spinners, ANSI codes) when machine-readable output is expected or stdout is piped.--jsonglobal flag detection inCLIBuilder.execute(). Strips--jsonfrom argv before command dispatch. CLI-level dispatch errors (unknown command, no action) rendered as JSON when active.jsonModeandisTTYoptions onRunOptions,CLIRunOptions, andOutputOptions.CLIBuilder.run()auto-sourcesisTTYfromadapter.isTTY.
Changed
ActionParams<F, A>→ActionParams<F, A, C>withctx: Readonly<C>(wasReadonly<Record<string, unknown>>).CommandBuildercarries third type parameterC(defaultRecord<string, never>). All metadata/builder methods preserveCin return type.CommandSchema.middlewareadded asreadonly ErasedMiddlewareHandler[].Outinterface extended withjson(),table(),jsonMode, andisTTY.OutputChannelconstructor acceptsisTTYandjsonModefrom resolved options.log/inforedirect to stderr writer in JSON mode.runCommandandCLIBuilder.executeerror paths render JSON whenjsonModeactive.createCaptureOutputacceptsjsonModeandisTTYoptions.- 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 —InputPromptConfigsupportsplaceholderandvalidate, select/multiselect supportSelectChoicearrays 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) withpromptOne(config) → Promise<PromptResult>as a pluggable renderer seam.ResolvedPromptConfigvariant guarantees non-empty choices for select/multiselect after merging fromFlagSchema.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 aMAX_RETRIES = 10safety valve. - Test prompter (
createTestPrompter(answers, options?)) with queue-based answers for deterministic testing.PROMPT_CANCELsymbol 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.nullsignals EOF/cancel.RuntimeAdapterextended withstdin: ReadFnandstdinIsTTY: boolean.- Node adapter stdin wraps
process.stdinvia dynamicimport('node:readline')with lazy per-call readline interfaces. Minimalnode:readlinetype declarations innode-builtins.d.tsavoid@types/nodedependency. - Automatic prompt gating in
CLIBuilder.run(): whenstdinIsTTY=trueand no explicit prompter provided, auto-createscreateTerminalPrompter(adapter.stdin, adapter.stderr). Prompt output routed to stderr to avoid interfering with piped stdout. - Command-level
.interactive(resolver)API onCommandBuilder. Resolver receives partially resolved flags (after CLI/env/config), returnsRecord<string, PromptConfig | false | undefined>controlling which flags get prompted. TruthyPromptConfigoverrides per-flag prompt;falseexplicitly suppresses; absent falls back to per-flag.prompt()config. - Testkit
answersconvenience onRunOptions. AcceptsRecord<string, TestAnswer>to auto-create a test prompter.prompterfield also available for explicit engine injection.CLIRunOptionsmirrors 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.
ResolveOptionsextended with optionalprompter: PromptEnginefield.RunOptionsextended withprompterandanswersfields.CLIRunOptionsextended withprompterandanswersfields.RuntimeAdapterextended withstdin: ReadFnandstdinIsTTY: boolean.createTestAdapterdefaults to EOF-returning stdin andstdinIsTTY: 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 theenvrecord after CLI and before default. String, number, boolean (lenient:true/false/1/0/yes/no), enum, and array (comma-separated) coercion. Invalid env values produceValidationErrorwithTYPE_MISMATCHorINVALID_ENUMcodes. - Config object resolution in the resolver. Flags with
.config('dotted.path')resolve from a plainRecord<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]informatHelp()output, ordered between description text and presence indicators. - Actionable required-flag error hints. When a required flag is missing after full resolution,
ValidationError.suggestlists all configured sources (e.g. "Provide --region, set DEPLOY_REGION, or add deploy.region to config"). CI-friendly error messages withenvVar/configPathin details. - Env/config wiring through testkit and CLI builder.
RunOptionsandCLIRunOptionsacceptenvandconfigfields.runCommand()threads them intoresolve().CLIBuilder.run()auto-sourcesadapter.envwhen no explicit env option is provided.
Changed
- Resolution chain expanded from CLI > default (v0.1) to CLI > env > config > default.
resolve()now accepts optionalResolveOptionsparameter withenvandconfigfields.ResolveOptionsexported 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 actionablesuggesthints. - 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 inferredflagsandargs. - 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) withlog/info/warn/errormethods,WriteFnabstraction, verbosity levels (normal/quiet), and TTY detection. IncludescreateCaptureOutput()test helper. - Test harness (
runCommand) for running commands as pure functions with injected argv, env, and captured output. ReturnsRunResultwithexitCode,stdout,stderr, anderror. - CLI builder (
cli) with.command()registration,.version(), subcommand dispatch, automatic--help/--versionflag 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 andExitErrorfor testable process exits. - Node.js adapter (
createNodeAdapter) wiringprocess.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-v8for test coverage reporting.@arethetypeswrong/cliandpublintfor 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.