Skip to content
cccskills
FreePublished on GitHub

meta-planning-cli-planning

CLI specification frameworks — flag contracts, prompt flows, config precedence, exit codes, output per context (TTY, piped, JSON), error and help text, signals and cross-platform behaviour. Use when a spec adds or changes a command surface, an interactive flow, a config key, an exit path or what a command prints.

The English summary is being prepared. Showing the original description.

See how to install

Files included (3)

  • SKILL.md20.5 KB
  • examples/core.md22.7 KB
  • metadata.yaml524 B

SKILL.md (original)

Review the instructions the agent will receive before you install.

CLI Planning Patterns

Quick Guide: A command's flags, exit codes, output streams and config keys are contracts that humans, scripts and CI all branch on, so the spec states each one the feature touches — user-facing text verbatim — and leaves out the sections it does not touch. Four facts change the answer more often than taste does: a secret never takes a flag value; Ctrl+C at a key-reading prompt is a keypress, not a signal; exiting 130 is not the same as dying by SIGINT; and prompts render on stdout unless the spec moves them.

Detailed Resources:

  • examples/core.md — a fill-in template for each section below, and a worked specification for a deploy command that uses them

Which sections the spec needs

Each section governs one kind of contract. Include it only when the spec touches that contract — a section filled with placeholders reads as a decision nobody made.

  • Adds or changes a command, argument or flag — Command Surface, Exit-Code Contract and Output Contract (Patterns 1, 4 and 5).
  • Adds or changes a prompt or a wizard step — Interactive Flow (Pattern 2).
  • Adds or changes a config key — Configuration Precedence (Pattern 3), one table per key.
  • Changes only messages, help or diagnostics — Error Messages and Help Text (Pattern 6).
  • Runs longer than an instant, or writes anything — Signal Handling and Cancellation (Pattern 7), on top of whatever else applies.
  • Touches paths, files or terminal capabilities — Cross-Platform Concerns (Pattern 8).

When none of these holds, the feature has no CLI contract and the spec carries no CLI section.


<critical_requirements>

Before specifying a CLI change

Read the closest existing command first, and reuse its flag names, prompt helpers, config resolver, exit-code constants and output helpers. They are the vocabulary the spec has to speak; a spec that invents its own ships a CLI with two conventions, and leaves the developer to pick which one is wrong.

Give every prompted value a non-interactive twin, and state what happens with no TTY and no twin. A value only a prompt can supply makes the command unusable in CI, and nobody finds out until a pipeline hangs.

Give every terminating path an exit code, named by its constant, with cancellation kept apart from failure. Scripts branch on the code, so a path the spec leaves open exits with a number the developer chose and nobody documented.

Specify output for every context — TTY, piped, --json, quiet, verbose — and which stream carries the payload. stdout is what a pipe captures, so anything else written there corrupts the consumer's input.

State config precedence per key as a table, with the merge rule for arrays and objects. "Merged" without a rule gets implemented as replace in one place and concatenate in another.

</critical_requirements>


Auto-detection: CLI spec, command surface, subcommand design, flag contract, --json output, --dry-run, --yes, --no-input, non-interactive mode, prompt flow, wizard step, exit codes, exit status 130, SIGINT, SIGTERM, NO_COLOR, FORCE_COLOR, config precedence, help text, flag deprecation

Applies to:

  • Specifying a new command or subcommand — arguments, flags, aliases, help
  • Specifying an interactive flow — prompt order, keys, cancellation, the non-interactive path
  • Config keys and the order they resolve in
  • Exit codes, output per context, error text and signal behaviour
  • Changing an existing surface without breaking the scripts that call it

Handled elsewhere:

  • Implementing the command — parser declarations, prompt code and rendering belong to the implementation; this skill fixes the contract they implement.
  • The spec's general sections — goal, scope fence, file references and success criteria — belong to whoever writes the spec; this skill supplies the CLI sections that sit inside it.
  • Contracts for an HTTP API or a UI — status codes, request shapes and UI states are planned with frameworks of their own.

<philosophy>

Philosophy

A CLI's public API is bigger than its code. Flags, exit codes, output streams and config keys are what humans, scripts and CI branch on, and a contract the spec leaves implicit is one the developer invents and nobody documents.

So the spec answers three callers at once: a human at a TTY, a script piping the output, and CI with no TTY at all. User-facing text is written verbatim, because approximate wording produces a CLI that words one failure five ways. Cancellation and cleanup are stated as invariants a tester can assert, not as intentions.

The spec's size follows the surface it changes. A flag-only change needs no prompt-flow table, a new exit code is proposed only when no existing class fits, and a new subcommand only when a flag on an existing command cannot do the job. Handler bodies and parser wiring stay out — the spec fixes contracts, not code.

</philosophy>

<decision_framework>

Argument, flag, subcommand or prompt

Decided by the value's role, not by preference:

Is the value the thing the command acts on, and supplied in almost every call?
├─ YES → positional argument (app deploy <environment>)
└─ NO  → Does it change how the command behaves?
    ├─ YES → flag (--dry-run, --force, --concurrency=4)
    └─ NO  → Does it select a different operation altogether?
        ├─ YES → subcommand (app config get | app config set)
        └─ NO  → Can only the human at the terminal supply it?
            ├─ YES → prompt, with a twin that supplies the same value
            └─ NO  → derive it from config or the filesystem; do not ask

Prompt, derive or refuse

Can the value come from a flag, config or the filesystem?
├─ YES → derive it; do not prompt
└─ NO  → Is stdin a TTY, with prompting not switched off?
    ├─ NO  → usage error naming the twin that supplies the value
    └─ YES → Is the action destructive or expensive?
        ├─ YES → confirm, skippable with --yes
        └─ NO  → prompt, pre-filled with the best default

</decision_framework>


<patterns>

Core patterns

Pattern 1: Command Surface

Each flag is a row carrying what its parser declaration needs: long and short name, type and unit, default, env var, and conflicts — the combinations that are a usage error rather than a silent winner. Whether it is required, which flags it implies and whether help hides it go with it where they apply.

| Long        | Short | Type    | Default | Env Var       | Conflicts | Description                    |
| ----------- | ----- | ------- | ------- | ------------- | --------- | ------------------------------ |
| `--dry-run` | -n    | boolean | false   | —             | —         | Print the plan, change nothing |
| `--timeout` | —     | seconds | 300     | `APP_TIMEOUT` | —         | Per-phase timeout              |

The spec also states:

  • Short flags only for flags used constantly, checked against every sibling command — a one-letter collision is found by a user, not a reviewer.
  • Booleans default to false; --no-<flag> exists only where the default is true.
  • Repetition — whether --tag a --tag b accumulates or the last one wins.
  • Aliases, with the canonical name help shows.
  • Prefix matching — whether the parser accepts an unambiguous prefix of a long flag.
  • Names and depth — commands lowercase, in the codebase's existing verb or noun-verb form; long flags spelled out in kebab-case; subcommands at most two levels deep (noun, then verb) unless the existing commands already go deeper.
  • Compatibility, for every renamed or removed flag: what old invocations do now, the deprecation warning on stderr, the release that drops the alias, and whether the exit code changes.

Template: examples/core.md

Pattern 2: Interactive Flow

Prompt only for what cannot be derived, and give each step a table that ends in its non-interactive twin.

| Element      | Specification                                     |
| ------------ | ------------------------------------------------- |
| Reached when | No environment from the argument or config        |
| Prompt type  | select                                            |
| Message      | `Deploy to which environment?`                    |
| On cancel    | stderr `Cancelled.`, exit 130, nothing written    |
| Twin         | positional `environment`, or `defaultEnvironment` |

The spec also states:

  • A secret's twin — stdin or a file (--password-file), never a flag value. Whether an environment variable is accepted as well is a decision to write down: CI tooling expects one, and every child process can read it.
  • The switch that turns prompting off even at a TTY — --no-input, or the codebase's existing equivalent.
  • Accelerator keys — whether they are case sensitive, and whether they go inert while a text input or filter has focus; a live accelerator swallows the characters the field needed.
  • Keys — when a step adds any beyond the defaults, what arrows, Space, Enter, Tab, Escape, Ctrl+C and any single-letter accelerator do there; a key the spec leaves out does whatever the prompt code happens to do.
  • Terminal size — the minimum usable width, what degrades first — for a grid or table, the order its columns drop in — and whether the flow redraws on resize.
  • Back and forward — which steps are revisitable, and what survives the return.
  • Resumability — whether partial progress persists, where, and what a re-run does with it. "Nothing persisted" is a decision; leaving the line out is not.

Template: examples/core.md

Pattern 3: Configuration Precedence

Config is a table per key, because the resolution order is the contract and prose leaves it to be inferred.

| Precedence  | Source               | Identifier                              |
| ----------- | -------------------- | --------------------------------------- |
| 1 (highest) | Flag                 | `--region`                              |
| 2           | Environment variable | `APP_REGION`                            |
| 3           | Project config       | `region` in `./app.config.ts`           |
| 4           | User config          | `region` in `~/.config/app/config.json` |
| 5 (lowest)  | Built-in default     | `us-east-1`                             |

The spec also states, per key:

  • The layers it participates in — not every key exists at every layer.
  • Merge semantics — scalars take the highest layer that sets them; arrays and objects say replace, concatenate, or shallow or deep merge.
  • Absent versus empty — whether --tag "" or tags: [] overrides a lower layer or counts as unset.
  • Discovery — which filenames, searched from where, how far up, and the tie-break when two match.
  • Validation — on load or on use, and the exit code an invalid value produces.
  • Write-back — which file and layer a write lands in, whether comments and key order survive, and whether the write is atomic.

Template: examples/core.md

Pattern 4: Exit-Code Contract

Every terminating path gets a code and every code a documented meaning; scripts branch on them as they do on flags.

| Code     | Meaning                                                              |
| -------- | -------------------------------------------------------------------- |
| 0        | Success                                                              |
| 1        | Generic runtime failure                                              |
| 2        | Usage error — bad flag, missing argument                             |
| 3–125    | Domain classes the codebase defines                                  |
| 126, 127 | Set by the shell: found but not executable, not found                |
| 128+N    | Set by the shell: killed by signal N — 130 is SIGINT, 143 is SIGTERM |

Domain codes stay inside 3–125: a status is truncated to its low 8 bits, and the shell already gives 126 and above a meaning of its own.

The spec also states:

  • Existing constants reused — a new code only where no class fits, named by its constant rather than its number.
  • One code per failure class a caller acts on differently — two failures a script handles alike share a code.
  • Cancellation apart from failure — Ctrl+C and a declined confirmation exit with a code no failure uses.
  • Partial success — fail the run, or succeed with a code that says some items failed; either way, decided.
  • --dry-run exits 0 on a valid plan and with the real run's failure code otherwise, or CI cannot trust a passing dry run.

Template: examples/core.md

Pattern 5: Output Contract

stdout carries the data the caller asked for; stderr carries everything else — progress, warnings, diagnostics and prompts — so progress survives piping without corrupting the pipe.

| Context         | Progress            | Colour  | Payload                          |
| --------------- | ------------------- | ------- | -------------------------------- |
| TTY, default    | Spinner or progress | Yes     | Human-readable summary, stdout   |
| Piped (non-TTY) | Plain lines, stderr | No ANSI | Same payload, no decoration      |
| `--json`        | None                | None    | One JSON document, stdout        |
| `--quiet`       | None                | None    | Payload only; errors still shown |
| `--verbose`     | Per-step detail     | On TTY  | Payload plus diagnostics         |

The spec also states:

  • The --json shape, the error shape included — valid JSON on failure too, and nothing else on stdout.
  • Colour — off when the stream is not a TTY, TERM is dumb, --no-color is passed, or NO_COLOR is set to a non-empty value; a flag or user config overrides NO_COLOR. FORCE_COLOR is a convention rather than a standard — Node, for one, lets it override NO_COLOR — so the spec says which wins when both are set.
  • The quiet and verbose ladder — what each level suppresses or adds, whether each combines with --json, and which wins when they do not.
  • Progress — which operations show it and what each phase says: a spinner for waits of a few seconds, a count or percentage for longer ones.
  • No-op output — the exact message when a re-run has nothing to do, or silence written down as the decision. A short message such as Already up to date. is usually the better default, because silence leaves the user unsure whether anything ran.
  • A closed pipe — what the command does when the reader exits early (app list | head -1), and with which code.

Template: examples/core.md

Pattern 6: Error Messages and Help Text

Each error has four parts, written verbatim: what failed, named as the user named it; why, in the user's vocabulary; how to fix it, as a concrete command or edit; and where to look, when a file, config key or doc page is involved.

BAD:  Error: ENOENT
GOOD: Error: config file not found at ./app.config.ts.
      Run 'app init' to create one, or pass --config <path>.

The spec also states:

  • Errors on stderr, with no stack trace by default — and the flag or env var that reveals one.
  • Unknown commands and flags — whether a "did you mean" suggestion appears, and its matching rule. It suggests; it never runs the guess.
  • Validation errors naming the offending value and the accepted set.
  • Another tool's failure quoted as that tool reported it, not paraphrased.
  • Help per command — summary, usage line with argument arity, arguments, grouped flags, at least two real invocations and related commands, reachable as --help, -h and help <command>.

Template: examples/core.md

Pattern 7: Signal Handling and Cancellation

For a command that runs longer than an instant or writes anything, cleanup is an invariant a tester can assert:

After SIGINT during upload, no `.staging/` prefix remains under the target,
and the caller sees the process killed by SIGINT (status 130).

The spec settles each of:

  • SIGINT — the message, how fast it returns, and the exit status: a code, or the signal re-raised after cleanup.
  • SIGTERM — the same path as SIGINT, or a distinct one.
  • In-flight work — finished, abandoned or rolled back.
  • Partial writes — which files can be left half-written, and how a re-run recovers (atomic rename, lock file).
  • Terminal restoration — cursor, raw mode, alternate screen, colour reset.
  • Cleanup — temp files, lock files, child processes, open handles.
  • A second Ctrl+C — skips cleanup and exits now, or is ignored; the first one says which.
  • Prompt cancellation — what a cancelled prompt does: the same message and exit as SIGINT, or its own. A prompt that reads single keys has to be routed there explicitly, because raw mode turns Ctrl+C into a keypress the SIGINT handler never sees.

Template: examples/core.md

Pattern 8: Cross-Platform Concerns

Include the ones that apply, and name the ones deliberately out of scope.

  • Paths joined with the platform separator; user-supplied paths resolved against the cwd or against the config file, stated.
  • Home and config directories from the platform API, which reads USERPROFILE on Windows and HOME elsewhere; the config location per platform where they differ.
  • Case — whether two names differing only by case collide: distinct on Linux, one file by default on macOS and Windows.
  • Line endings written, and whether input endings survive.
  • Shell quoting in help examples, valid in every shell the project supports.
  • Executable bits on any file the command creates that has to run.
  • Long paths and reserved names for generated file names on Windows.
  • Unicode — the fallback glyphs where the terminal lacks it.

Template: examples/core.md

</patterns>

<red_flags>

Red flags

Breaks at runtime:

  • A secret accepted as a flag value — it lands in ps output and shell history. Read it from stdin or a file.
  • Prompts left on their default stream — prompt libraries and line-input builtins commonly render on stdout, so app init > plan.txt writes the questions into the file and leaves the user at a blank wait. Name stderr in the spec.
  • Prompt cancellation specified as "handled by the SIGINT handler" — a prompt that reads single keys puts the terminal in raw mode, where Ctrl+C arrives as a byte and no signal fires. Give the prompt's cancel its own path.
  • Exiting 130 after catching SIGINT, in a command that scripts call — a normal exit with 130 is not a death by SIGINT, so the calling shell script runs its next command. Specify re-raising the signal after cleanup.
  • A domain exit code above 125 — 126, 127 and 128+N already mean something to the shell, and the status is truncated to 8 bits, so 256 reads as success. Keep domain codes within 3–125.
  • A closed pipe left unspecified — the next write fails with EPIPE, a runtime that ignores SIGPIPE raises it as an error, and app list | head prints a stack trace. State the quiet exit.
  • A --json mode with no error shape — the consumer's parser fails on exactly the run it most needs to read.
  • A flag renamed or removed with no deprecation path — the scripts that used it break, and nobody reports it.
  • A writing command with no cancellation invariant — Ctrl+C leaves half-written files and a hidden cursor.
  • "Follows existing patterns" with no file reference — nothing verified the pattern exists, so the developer invents one and the CLI gains a second convention.

Surprising behaviour:

  • NO_COLOR set to an empty string leaves colour on; only a non-empty value disables it.
  • A parser that accepts unambiguous long-flag prefixes makes each new flag a potential break: --verb resolves to --verbose until --verbatim ships, and then it is an ambiguity error in every script that used it.

</red_flags>

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Hugging Face Inference SDK patterns for TypeScript/Node.js — InferenceClient setup, chat completion, text generation, streaming, embeddings, image generation, audio transcription, translation, summarization, and Inference Endpoints

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

LiteLLM proxy server setup, TypeScript client patterns via OpenAI SDK, model routing, fallbacks, load balancing, spend tracking, virtual keys, and production deployment

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

Serverless GPU compute platform for AI model deployment — web endpoints, GPU functions, model serving, and TypeScript client patterns

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

Local LLM inference with the Ollama JavaScript client -- chat, streaming, tool calling, vision, embeddings, structured output, model management, and OpenAI-compatible endpoint

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

Replicate SDK patterns for TypeScript/Node.js -- client setup, predictions, streaming, webhooks, file handling, model versioning, deployments, and training

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

Together AI SDK patterns for TypeScript — client setup, chat completions, streaming, structured output, function calling, embeddings, image generation, fine-tuning, and OpenAI-compatible endpoints

The English summary is being prepared. Showing the original description.

agents-inc/skills25Updated Oct 17, 2026

See all skills by agents-inc

Report a problem with this skill