name: clack-cli-patterns description: Use when creating or modifying terminal CLI commands, prompts, or output formatting in OpenChamber. Enforces Clack UX standards with strict parity and safety across TTY/non-TTY, --quiet, and --json modes. license: MIT compatibility: opencode
Overview
OpenChamber terminal CLI uses @clack/prompts for interactive UX, but command policy and validation must be mode-agnostic.
Core principle: policy-first, UX-second. Clack is presentation, not enforcement.
Scope
Use this skill for terminal CLI work only (for example packages/web/bin/*).
Do not use this skill for web UI or VS Code webview styling work.
Mandatory Rules
Validation first
- Safety and correctness checks must run in all modes.
- Prompts may help collect input, but cannot be the only guard.
Mode parity is required
- Behavior must be equivalent in:
- Interactive TTY
- Non-interactive shells
--quiet--json- Fully pre-specified flags
- Invalid operations must fail deterministically with non-zero exit code.
- Behavior must be equivalent in:
Prompt guard contract
- Only prompt when all are true:
- stdout is TTY
- not
--quiet - not
--json - not automated/non-interactive context
- Only prompt when all are true:
Output contract
--json: machine-readable output only.--quiet: suppress non-essential output only.- Neither mode weakens policy enforcement.
Cancellation contract
- Handle prompt cancellation with
isCancel+cancel(...). - Handle SIGINT cleanly and use consistent exit semantics.
- Handle prompt cancellation with
Clack Primitive Standard
- Flow framing:
intro,outro,cancel - Status lines:
log.info,log.success,log.warn,log.error,log.step - Guidance blocks:
- default:
note - high-severity warnings only:
box
- default:
- Prompts:
select,confirm,text,password - Long-running feedback:
- unknown duration:
spinner - known duration:
progress - multi-stage:
tasks
- unknown duration:
Preferred Pattern
Centralize Clack imports and formatting helpers in one adapter module (for example cli-output.js) so command logic stays focused on behavior and policy.
Thin framework (recommended)
Use a small shared helper surface rather than command-specific formatting logic.
isJsonMode(options)isQuietMode(options)shouldRenderHumanOutput(options)canPrompt(options)createSpinner(options)createProgress(options, config)printJson(payload)
Keep this layer minimal. Do not hide core validation or command semantics inside output helpers.
Output Contracts by Mode
--quiet contract
--quiet should still return essential result data.
- Read/list commands: emit concise machine-friendly lines (not framed Clack blocks).
- Action commands: emit one minimal success line and concise errors.
- Do not suppress required outcomes entirely.
Quiet output should still be complete enough for scripts and quick human scanning.
- Status-like commands should list all active items, not only
running/ok. - Prefer compact stable key tokens in quiet lines (for example
port 3000 pass:yes).
--json contract (strict)
- Output must be JSON only (no extra text before/after payload).
- Warnings/info should be represented in JSON fields (for example
status,messages). - Preserve non-zero exit codes for failures.
Human UX Consistency
Framing completeness
- If human flow uses
intro, close withoutro(oroutro('')when you want structure without text). - Avoid orphan frame/spinner artifacts (prefer
spinner.clear()when a trailing spinner line is not wanted). - If a structured summary section immediately follows a spinner, prefer
spinner.clear()to avoid duplicate success lines.
Progress feedback for visible operations
- For operations users wait on (start/stop/restart/tunnel lifecycle), show in-progress spinner in interactive mode.
- Resolve each spinner explicitly to done/error so users can see completion state at the same visual location.
- Keep quiet/json modes non-animated.
Prompt flow design
- Ask required inputs in dependency order (for example hostname before token when token depends on chosen host/mode context).
- When offering save-vs-run flows, ask intent before collecting optional metadata (for example profile name only if user chooses save).
- Prefill editable values with
initialValue(not onlyplaceholder) so users can accept or edit quickly. - Reuse latest relevant values when safe (for example last managed-local config path, last managed-remote hostname).
Readability on narrow terminals
- Prefer short lines.
- Split long guidance into multiple detail lines.
- Use warning/info codes (
[CODE]) when the message has follow-up docs or repeat use.
Guidance tone
- Use
Optional Tipsfor non-required next actions. - Avoid wording that implies mandatory follow-up unless it is truly required.
Guidance rendering style (preferred)
- Prefer structured status lines for reusable hints:
logStatus('info', '[CODE]', '<actionable command or short guidance>')
- Use short, stable codes (for example
[START_PROFILE],[PORT_MISMATCH]) so users can quickly scan and recognize repeated guidance. - Prefer this style over boxed notes for routine follow-up actions.
- Reserve
note/boxed callouts for rare, high-context guidance where a long paragraph is truly necessary.
Parity Verification Matrix
For each command/subcommand, manually verify:
- default interactive TTY output
--quietoutput (minimal but informative)--jsonoutput (JSON-only)- non-TTY behavior (e.g. piped)
- error path in both human and json modes
Copy/Paste Snippets
Prompt Guard
if (canPrompt(options)) {
const value = await select({
message: 'Choose an option',
options: [{ value: 'a', label: 'Option A' }],
});
if (isCancel(value)) {
cancel('Operation cancelled.');
return;
}
}
Non-Interactive Fallback
if (!resolvedValue) {
if (canPrompt(options)) {
// prompt path
} else {
throw new Error('Missing required value. Provide --flag <value>.');
}
}
Spinner Guard
const spin = createSpinner(options);
spin?.start('Running operation...');
// ...work...
spin?.stop('Done');
JSON vs Human Output
if (options.json) {
printJson({ ok: true, data });
return;
}
intro('Operation');
log.success('Completed');
outro('done');
Implementation Checklist
- Add or update core validators first.
- Ensure validators execute in all modes.
- Add interactive Clack UX only as enhancement.
- Verify parity between interactive and non-interactive flows.
- Ensure script-safe deterministic failure behavior.
References
- Policy source:
AGENTS.md(CLI Parity and Safety Policy) - Terminal CLI precedent:
packages/web/bin/cli.js - Output adapter precedent:
packages/web/bin/cli-output.js