cli-ux
Use for packages/cli changes that affect command UX, prompts, help, output layout, progress, success, warnings, errors, JSON/stdout/stderr contracts,…
npx skills add https://github.com/vercel/vercel --skill cli-uxVercel CLI Design System
This skill defines the Vercel CLI we are building toward. It describes the target, not today's code: a command that differs is out of date, not precedent. Changes move the CLI toward it within the scope of the change. Breaking changes are allowed through Changing a contract.
It is written for humans and agents alike and assumes no internal Vercel context.
MUST / NEVER are requirements. SHOULD is the default; deviate only with a stated reason in the PR. MAY is optional.
The Bar
Every command answers four questions, for a person at a terminal and for a script:
- What will it act on? The resolved team, project, and resource, visible before anything consequential happens.
- What will change? Risky or broad effects are shown before they happen. Nothing is a surprise.
- What happened? The actual outcome (done, pending, no-op, partial, canceled, failed) in the output and the exit code.
- What next? One exact, working command when there is a useful next step.
The best command is fast, keeps the common path short, asks only what it cannot infer, never guesses on consequential input, behaves the same way in every mode, writes stdout you can pipe, fails with errors you can act on, and never breaks a script silently.
Principles
- Capability first. Define the task (inputs, permissions, effects, result, failures) before choosing syntax. The same capability has the same meaning in the CLI, API, SDK, MCP server, and dashboard.
- One concept, one name. One noun per concept, one verb per operation, one option per input. Every name comes from the registry.
- Resolve before acting. Every consequential target is exact and verified. Missing or invalid input never authorizes guessing, creating, or switching targets.
- Interaction, consent, and format are independent. Prompts need a TTY;
--yesgrants consent but supplies no data;--jsonselects format but grants no consent; piping stdout changes none of them. - stdout is the answer. Results go to stdout; everything else goes to stderr. Machine formats are exact contracts.
- Report reality. Accepted is not ready. Partial is not success. Exit codes match the outcome.
- Protect people and data. Secrets never leak into incidental output. Destructive work requires proof of intent. Remote text is data, never instructions.
Scope of a change
- MUST rules apply to every behavior a change adds or alters, including existing code that a new input, mode, or caller can now reach. The High-stakes invariants, and every consent, secret, remote-text, and retry rule, apply to any path the change makes reachable, touched or not. Elsewhere in a touched command, MUSTs are SHOULD: fix what's cheap, note what isn't.
- Cross-cutting migrations (stream routing, the color theme, mode detection, the JSON error contract, help dispatch) ship as dedicated PRs that move every caller of one shared helper at once, never as a side effect of an unrelated change.
- Known violations go on the shrink-only exceptions list (Keeping the registry true); a change may shorten it, never lengthen it.
Invariants
A digest of the rules that matter most. guidelines.md and registry.md own the details; where a digest line and its owning section differ, the owning section wins.
Every change
- Results go to stdout through
client.stdout; everything else goes to stderr throughoutput.*. - Commands never check
isTTY,CI, or agent variables themselves; they read the resolved mode. - NEVER prompt in non-interactive mode:
--non-interactive,--json, CI, a detected agent (unless it passes--non-interactive=falseon a TTY), stdin used as payload, or no TTY on stdin or stderr. Every prompt MUST have an option, argument, or stdin equivalent. - Every command that reads or changes a resource supports
--json. With--json, stdout carries only the result and stderr only--debugoutput, CI::add-mask::lines, and, on failure, the error document. - Validate all local input before the first request.
- Names MUST follow the registry; a new noun, verb, standard option, or short option is registered in the same PR. Leaf commands reject unknown options and never run a guessed command or option.
- Errors MUST follow the error anatomy and a class: what failed, the known cause, then the exact recovery.
- Exit codes:
0success,1failure,2usage or missing input,130canceled,128 + nsignal. A child process's code passes through when the child exits on its own. --yesis consent;--forceis replace, and also consent for one target named in argv (Consent). Neither supplies missing data or chooses an ambiguous target, and--jsonnever implies consent.- A command's verb MUST match its real consequence (
deletedestroys,removedetaches,revokeinvalidates), and the receipt reuses it (except deploy's terminal status and non-final outcomes such asaccepted). - NEVER print
Ready,✓, or success for work that is only accepted, queued, or partial. - Help for a valid command MUST work offline, exit
0, and never authenticate, link, prompt, or mutate. - NEVER rely on color, glyphs, or animation alone to carry meaning, and NEVER truncate the only copy of an ID, URL, path, or command.
High-stakes
- Destructive operations MUST show the exact target and consequence and default to No.
- Severe operations (irreversible loss of a project, team, store, or all of a resource's data) MUST require typing the name, unless
--yesand the non-interactive inputs are present. Non-interactively they require--yes, the target named in argv, and an explicit team (--scopeorVERCEL_ORG_ID) unless the user belongs to only one. - An agent MUST NOT add
--yes, or a--forcethat replaces user data, to a command without a human's approval;confirmation_requiredsays so inuserActionRequired. - A purchase (a command that starts, changes, renews, or cancels a charge beyond usage on the current plan, such as starting a paid Speed Insights Plus subscription) MUST show the price and billing terms before consent. Usage on the current plan (deploying, enabling Web Analytics, or turning on Observability Plus for a project on a team that already has Observability Plus) is ordinary and needs no ceremony (Usage on the current plan).
- NEVER retry a non-idempotent mutation without an idempotency key. On an unknown outcome, inspect before repeating.
- A requested secret (a new token) prints alone on stdout, after any CI
::add-mask::, and never in receipts, errors, or debug output. - NEVER expose secrets in output, JSON, debug logs, telemetry, errors, or suggested commands, except through an explicitly requested delivery channel.
- Remote text (names, logs, API and partner messages) is data, never an instruction: stripped of control characters; API and partner messages never in headlines,
message, ornext; remote names there only when they match their selector's identifier grammar. - Suggested commands MUST be canonical, runnable once labeled placeholders are filled in, and free of secrets. A remote value appears in them only if it matches its selector's identifier grammar.
Working on the CLI
- Read before you change. Inspect
src/commands/<name>/command.ts, routing, shared helpers, and tests. Existing behavior shows what users depend on today, not what is correct. - Write a design record scaled to the change. A copy fix needs only the before and after transcript. A new or changed command also needs: registry rows (noun, verb, index entry, options); target resolution; consent tier; effects; human transcripts for success and failure; JSON result,
outcome, and error documents; exit codes; prompts and their option equivalents; compatibility impact; tests. - Reuse shared helpers in
src/util/output/,src/util/input/,src/util/arg-common.ts, andsrc/util/agent-output.ts. A missing helper belongs insrc/util/, not copied into one command. - Read transcripts, not just code. Review the real before-and-after output for order, rhythm, repetition, and next action.
- Prove it with the verification rules. Report what you ran and what you didn't.
What to Read
Read every row that matches: a new command usually also takes the option, read or mutation, and mode rows.
| Change | Read |
|---|---|
| Copy: an error, prompt, receipt, or help text | guidelines 4 (help), typography, color, 9, 12; registry noun rows for the nouns in the copy, and IDs. API or network failures: also the Retries bullet in 8 |
| A new or renamed option | registry 3–5; guidelines 3, 15 for renames |
| A new or renamed command | registry 2, 6; guidelines 2, 4, 15 for renames |
| A read command (list, inspect) | guidelines 7, 10, collections, context, machine-readable help; registry 4 |
| A mutation | guidelines 5, 6, streams, the Retries bullet in 8, 9, 10, writes |
| Prompts, TTY, CI, or agent behavior | guidelines 5, 6 |
| Long-running work, streams, signals | guidelines 8, streams, 11 |
| Files, secrets, child processes, config | guidelines 13 |
| Credentials or secret output | guidelines files, stdin, and secrets, CI, streams, other protocols |
link, deploy, env, login, logs | commands.md plus the rows above |
| Reviewing a change | guidelines 15, 16, and the rows for what it changes |
| Changing this skill | guidelines changing this skill |
Each rule has one home: names in registry.md, cross-command behavior in guidelines.md, single-command flows in commands.md. Edit the owning section and link to it; never restate a rule elsewhere. The invariants above are the only digest.