cli-creation

Build consumer-facing DreamCLI CLIs from scratch with Bun-first workflows and typed patterns. Use when asked to scaffold or implement a new @kjanat/dreamcli command-line app, add commands/flags/args/prompts/output/testing, or create starter files/tests for DreamCLI users.

kjanat/dreamcli9 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: cli-creation
description: Build consumer-facing DreamCLI CLIs from scratch with Bun-first workflows and typed patterns. Use when asked to scaffold or implement a new @kjanat/dreamcli command-line app, add commands/flags/args/prompts/output/testing, or create starter files/tests for DreamCLI users.
license: MIT
---

# CLI Creation

## Overview

Create runnable DreamCLI starter CLIs and extend them with typed command
patterns. This skill covers user-facing app code built **on** DreamCLI, not
DreamCLI framework internals.

Targets DreamCLI 4.0. Version 3 removed the DSL, made the default command the
root surface, and added a large typed-flag surface. Version 4 gave both
factories the same sources, so `.stdin()`, `.env()`, `.config()`, and
`.prompt()` are available on flags and positionals alike. Snippets below assume
both.

## Quick Start

1. Choose a starter mode:
   - `single`: one root command (`cli(name).default(command)`).
   - `multi`: grouped command surface (`group('...').command(...)`).
2. Generate starter files:
   - `python scripts/scaffold_cli.py --name mycli --mode single --out .`
   - Tests are generated by default; add `--no-test` only when explicitly requested.
   - Test template is auto-detected: Bun without Vitest uses `bun:test`; otherwise Vitest.
3. Run and validate generated files:
   - Use the printed path from the scaffolder output, for example `bun ./mycli.ts --help`.
   - Run the generated test unless `--no-test` was used.
4. Extend behavior with references:
   - `references/pattern-cookbook.md` — copy-ready, type-checked snippets.
   - `references/consumer-workflow.md` — request to validated CLI.
   - `references/runtime-notes.md` — Bun/Node/Deno execution.

## Looking Things Up

Prefer these over recalling API shapes from memory; they reflect the installed
or published version rather than training data.

**The API, offline-ish, no repo needed.** If `deno` is on the system this works
regardless of whether the project installed from npm or JSR:

```bash
deno doc jsr:@kjanat/dreamcli 2>/dev/null                    # full public API (~4k lines)
deno doc jsr:@kjanat/dreamcli/testkit 2>/dev/null            # subpath: testkit, runtime, schema, version
deno doc --json jsr:@kjanat/dreamcli 2>/dev/null             # machine-readable, for scripted lookups
deno doc --filter=CLIBuilder jsr:@kjanat/dreamcli 2>/dev/null # one symbol
```

`2>/dev/null` matters: deno writes download and type-check progress to stderr,
which otherwise swamps the documentation output.

Pin a version with `jsr:@kjanat/dreamcli@4.0.0-rc.2` when the project is not on
latest. `--filter` takes a declaration name; it prints nothing for a name that
does not exist, which is itself a useful signal.

**The docs site, as markdown.** Every page is authored markdown served under
`/raw/`, and any page URL returns markdown under content negotiation:

```bash
curl -s https://dreamcli.kjanat.dev/llms.txt        # index of every page, one line each
curl -s https://dreamcli.kjanat.dev/llms-full.txt   # every page concatenated (~250 kB)
curl -s https://dreamcli.kjanat.dev/raw/guide/flags # one page, authored markdown
curl -sH 'Accept: text/markdown' https://dreamcli.kjanat.dev/guide/flags
```

Start from `llms.txt` to find the right page, then fetch that page rather than
pulling `llms-full.txt` into context.

## Grounding Sources

Paths are relative to the dreamcli repository root.

- `examples/basic.ts` — single-command defaults.
- `examples/multi-command.ts` — grouped-command defaults.
- `examples/testing.ts` — `runCommand()` patterns.
- `examples/flag-types.ts` — the v3 typed-flag family.
- `examples/parser-control.ts` — negation, duplicates, spelling parity.
- `examples/output-extras.ts` — colors, hyperlinks, `setExitCode`.
- `examples/standard-schema.ts` — Standard Schema validation.
- `examples/help-config.ts` — help themes, flag order, routable default.
- `examples/gh/` — a full multi-command app used as a walkthrough.
- `docs/guide/getting-started.md` — baseline consumer narrative.
- `docs/guide/walkthrough.md` — end-to-end CLI composition.
- `docs/guide/upgrading-v3.md` — what changed from 2.x, for migrations.

## Workflow Decision Tree

- Simple one-command utility → `--mode single`.
- Nested command groups (git/gh style) → `--mode multi`.
- Tests wanted from the start → do nothing, they are scaffolded by default.
- Tests explicitly unwanted → add `--no-test`.
- npm/tsx or Deno instructions → keep generated code unchanged and give the
  runtime alternatives from `references/runtime-notes.md`.
- Migrating an existing 2.x CLI → read `docs/guide/upgrading-v3.md` first; the
  default-command and `finite` changes silently alter behavior.

## Extend the Starter

**Values.** Prefer a purpose-built kind over `flag.string()` / `arg.string()`
plus parsing. Both factories carry `string()`, `number()`, `boolean()`,
`enum(...)`, `custom(...)`, `keyValue()`, `url()`, `path()`, `date()`,
`duration()`, and `bytes()`. `flag` additionally carries `array()` and
`count()`; the arg form of `flag.array()` is `.variadic()`. Express validation
declaratively with constraints (`{ int, min, max }`, `{ nonEmpty, pattern }`,
chainable on both builders) or a Standard Schema passed to `.standard()` or to
`flag.custom()` / `arg.custom()`, not with hand-written checks in the action.

**Defaults.** A `.default()` value is validated where the chain declares it, so
a default that violates its own constraints, validator, or collection shape
throws `INVALID_DEFAULT` at build time. A collection default takes the shape the
input resolves to: an array for `flag.array()` and a variadic arg, a record for
`keyValue()`, a non-negative integer for `flag.count()`.

**Collections.** `flag.array()`, `flag.keyValue()`, `arg.keyValue()`, and
`.variadic()` aggregate from every source under one set of rules. Each source
decodes under its own policy, set by `.split({ cli, env, stdin })`: whole CLI
tokens by default, comma-delimited env values, line-delimited stdin, and native
arrays and objects from config. `.separator()` sets the CLI policy alone and is
no longer inherited by env or config. `.unique()` dedupes a list, and
`.duplicateKeys('last' | 'first' | 'error')` decides a repeated key on every
source, naming the source that carried it. A validator on the element builder
checks each element; one on the collection builder checks the finished value.
On the arg surface, `.separator()` and `.split()` require `.variadic()` or
`arg.keyValue()`, `.unique()` requires a variadic list, and `.duplicateKeys()`
requires `arg.keyValue()`. The compiler refuses every other shape and
`createArgSchema()` throws `INVALID_SCHEMA`.

**Argument order.** A variadic argument takes every remaining positional, so it
is the last one a command can declare. Anything registered behind it throws
`INVALID_BUILDER_STATE`.

**Sources.** Both factories declare the same sources. Chain `.stdin()`,
`.env()`, `.config()`, `.prompt()`, `.default()` on a flag or an argument and
let one resolution order (argv, stdin, env, config, prompt, default) do the
work. Count and key-value flags and key-value arguments are not promptable.
`.stdin()` takes `{ when, consume, trim }`; one command has one exclusive
stdin consumer unless every stdin input passes `{ consume: 'broadcast' }`. A `-`
occurrence on a collection splices the decoded buffer in at that position, so
`--tag before --tag - --tag after` over `a\nb\n` gives
`['before', 'a', 'b', 'after']`, and a variadic argument reads its tail the same
way. A `-` typed beside other occurrences with nothing piped fails with
`MISSING_STDIN`; a lone `-`, and a scalar `-`, fall through instead.
`{ trim: true }` drops one trailing line terminator from a single value, which
is what `arg.path({ mustExist: true }).stdin({ trim: true })` wants. Help names
each binding: `[stdin]`, `[stdin: '-']`, or `[stdin: when omitted]`. Stdin is
available to scalar, array, key-value, and variadic inputs; count flags cannot
read it, and key-value arguments cannot prompt.

**Provenance.** A handler receives `sources` beside `flags` and `args`, keyed
the same way, holding the stage that produced each value (`cli`, `stdin`, `env`
with its `envVar`, `config` with its `configPath`, `prompt`, `default`).
`wasExplicit(sources.flags.x)` is the predicate for "supplied rather than
defaulted"; never drop `.default()` to detect that, since it also drops
`defaultValue` from the exported schema.

**Cross-flag rules.** Put them in `.derive()`, which runs after resolution and
before the action, and return derived state to widen `ctx`.

**Diagnostics.** Values resolved through stdin, env, config, or a prompt are
redacted in validation messages and omit `details.value`; this includes stdin
selected by an explicit `-`. Only a literal CLI value is shown. The framework
cannot redact text your own code writes: a `flag.custom()` parse function's
thrown message and a Standard Schema issue message are shown verbatim, so write
them to describe the expectation rather than to interpolate the value.

**Output.** `out.log()` for results, `out.status()` for progress notes (stderr,
suppressed by `--quiet`), `out.table()` for lists, `out.json()` behind
`out.jsonMode`, `out.color`/`osc8()` for styling, `out.setExitCode()` when a
command must report normally but exit non-zero.

**Testing.** `runCommand()` from `@kjanat/dreamcli/testkit`, with `answers` for
prompts and `stat`/`mkdir` when `flag.path()` or `arg.path()` checks must run.
Assert output including trailing newlines.

## Resource Map

- `scripts/scaffold_cli.py` — generate Bun-first starter files and tests.
- `assets/templates/*.tpl` — source templates used by the scaffolder.
- `references/pattern-cookbook.md` — snippets by topic; all type-checked.
- `references/consumer-workflow.md` — end-to-end flow from request to validation.
- `references/runtime-notes.md` — runtime and package-manager execution guidance.

## Guardrails

- Do not modify DreamCLI core internals for consumer-app requests.
- Keep generated imports on `@kjanat/dreamcli` and `@kjanat/dreamcli/testkit`;
  never reach into `#internals/*` or `dist/`.
- Preserve the typed resolution flow: argv, stdin, env, config, prompt, default.
- Keep stdout machine-clean: progress and status go to stderr via `out.status()`,
  never interleaved with `out.json()`.
- `.default(cmd)` is the root surface and is not routable by name; add
  `{ route: true }` when a user expects `mycli <name>` to work too.
- Prefer Bun commands first; include npm/tsx and Deno alternatives when asked.

More Testing skills

← All Testing skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY