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.
Works with
---
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
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
setup-pre-commit
mattpocock/skills
Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.
agent-browser
vercel-labs/agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.

