kjanat/dreamcli · Archived

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.

First seen Apr 8, 2026

Installation

$ npx skills add kjanat/dreamcli --skill cli-creation

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 1
License LICENSE
Default branch master
Open issues 8
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 11,007 B
  • docs SUMMARY.md 292 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 9 installs

SKILL.md

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(...)).

  1. 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.

  1. 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.

  1. 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:

deno doc jsr:@kjanat/dreamcli 2>/dev/null                    # root entry: builders, parsing, help, errors, every public type
deno doc jsr:@kjanat/dreamcli/completion 2>/dev/null         # one subpath at a time; also /config, /json-schema, /prompt, /testkit, /runtime, /version, /schema
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/[email protected] 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:

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 INVALIDBUILDERSTATE.

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. Sensitivity, not source, controls disclosure. Mark every credential-bearing input .sensitive(); that redacts the value in messages and omits details.value whether it came from argv, stdin, env, config, a prompt, or a default. A non-sensitive value may be shown from any of those sources, so flag.string().env('API_TOKEN') without .sensitive() prints the token in a validation failure. 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 the documented package entrypoints: @kjanat/dreamcli

and its /testkit, /runtime, /completion, /config, /json-schema, /prompt, /schema, and /version subpaths; 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.