Writing CLI Commands
Use one consistent output structure—not one presentation for every kind of data.
Workflow
- Read the command, its renderer, and its exact-output tests. Start with
internal/cli/context.go for shared output and the nearest command-specific
renderer for assessments or metrics. Identify the command's human output,
JSON shape, stdout/stderr behavior, and exit behavior before changing them.
Done when the affected contracts and applicable states are identified.
- Draft invocation-and-output examples using the rules below. Cover success,
no-op, dry run, blocked/error, empty, and multiple-item states where supported;
do not add features merely to fill this list. Done when each applicable state
has truthful wording and only justified guidance.
- Implement with the existing rendering patterns. Keep presentation in
internal/cli, not API models or client methods; do not introduce a rendering
framework for a new command. Done when the examples are implemented without
unrelated behavior or JSON changes.
- Add or update exact-output tests for the affected states. Check JSON separately,
stdout/stderr separation, exit behavior, and that dry runs make no mutation
requests. Run
go test ./internal/cli and broader tests if shared behavior
changed. Inspect captured human output for alignment and readability; do not
mutate real resources to obtain examples. Done when checks pass and affected
README examples match the output. If verification cannot finish, report what
remains unverified rather than treating the check as complete.
Output grammar
- Start human-readable command results with an uppercase command heading, such
as
QUEUE SET-PERCENT, followed by resource identity on labeled lines, such as
Queue: and Destination:. This is a result format, not a requirement to wrap
parser errors, help, version output, or progress messages in report sections.
- Use this order: command heading → resource identity → outcome or assessment →
relevant context → actionable next steps. Omit inapplicable sections.
- Use uppercase section headings. Use
RESULT for completed mutations or explicit
no-ops; use STATUS, READINESS, or a descriptive data heading for reads and
assessments. Do not imply that a read performed a change.
- Separate sections with a blank line and put a blank line after each section
heading. Use sentence case and terminal punctuation for prose; keep labels and
table cells concise. End output with a newline.
- Report a single outcome in one plain sentence. Say what changed, including
before/after values when useful. Say “remains” or “already” for a no-op rather
than claiming a change.
- Use borderless, aligned tables for collections and comparisons. Prefer the
existing
text/tabwriter pattern. No boxed ASCII tables, side-by-side panels,
decorative banners, or duplicate prose restating every table row.
- Explain empty results in a sentence rather than printing nothing or an empty
table. Omit empty optional sections.
Dry runs and truthful assessments
- Append exactly
(DRY RUN) to the command heading. Use PROPOSED CHANGE
instead of RESULT, with “would…” language, never completed-action wording.
- A blocked dry run retains its dry-run heading and explains the blockers; do
not show a proposed change that cannot proceed.
- Do not offer follow-up steps that assume the dry run actually made a change.
- Distinguish observed facts from conclusions. “No known blockers” is not proof
of readiness. Do not turn missing data into zero, stale data into live data,
or a latest sample into a historical maximum. Label observation windows and
independent refresh times where they affect interpretation.
Next steps
- Include
NEXT STEPS only when the current state supports a useful, safe action.
Never add an empty section, generic encouragement, or a speculative remedy.
- Always number the steps, including a single step:
1.. Put each command on its
own line, indented three spaces beneath its step, with a blank line before it.
- Use known resource identifiers in suggested commands. Use explicit placeholders
such as
<percentage> only for values the operator must choose; never invent a
safe percentage or silently choose a consequential action.
- State prerequisites before consequential actions: “If no known blockers remain…”
rather than unconditionally telling an operator to move a pipeline.
- Do not attach per-resource workflow guidance to an unkeyed list command.
Automation and diagnostics
- Keep
--json machine-readable and preserve its existing contract, including
API-shaped responses where applicable. Never mix human headings, guidance, or
progress into JSON stdout or add presentation-only fields to its payload.
- Write command results to stdout; diagnostics and progress to stderr. Successful
commands should not emit unsolicited blank lines or chatter on stderr.
- Preserve meaningful non-zero exits for errors and blocked assessments. A nicely
rendered report does not turn failure into success. Errors should identify the
failed operation and actionable cause without exposing credentials.
Examples
Successful mutation with one useful next step:
QUEUE SET-PERCENT
Queue: default
Destination: production
RESULT
Routing changed from 10% to 25%.
NEXT STEPS
1. Review destination activity:
cluster-migrator queue metrics default
Dry run without post-mutation guidance:
QUEUE SET-PERCENT (DRY RUN)
Queue: default
Destination: production
PROPOSED CHANGE
Routing would change from 10% to 25%.
These excerpts illustrate the grammar, not exhaustive command-specific guidance.
Use the command's current contracts and tests to choose the actual next steps.
1---2name: writing-cli-commands3description: Guides CLI command design for cluster-migrator. Use when adding commands or changing CLI output and its tests.4---56# Writing CLI Commands78Use one consistent output structure—not one presentation for every kind of data.910## Workflow11121. Read the command, its renderer, and its exact-output tests. Start with13 `internal/cli/context.go` for shared output and the nearest command-specific14 renderer for assessments or metrics. Identify the command's human output,15 JSON shape, stdout/stderr behavior, and exit behavior before changing them.16 Done when the affected contracts and applicable states are identified.172. Draft invocation-and-output examples using the rules below. Cover success,18 no-op, dry run, blocked/error, empty, and multiple-item states where supported;19 do not add features merely to fill this list. Done when each applicable state20 has truthful wording and only justified guidance.213. Implement with the existing rendering patterns. Keep presentation in22 `internal/cli`, not API models or client methods; do not introduce a rendering23 framework for a new command. Done when the examples are implemented without24 unrelated behavior or JSON changes.254. Add or update exact-output tests for the affected states. Check JSON separately,26 stdout/stderr separation, exit behavior, and that dry runs make no mutation27 requests. Run `go test ./internal/cli` and broader tests if shared behavior28 changed. Inspect captured human output for alignment and readability; do not29 mutate real resources to obtain examples. Done when checks pass and affected30 README examples match the output. If verification cannot finish, report what31 remains unverified rather than treating the check as complete.3233## Output grammar3435- Start human-readable command results with an uppercase command heading, such36 as `QUEUE SET-PERCENT`, followed by resource identity on labeled lines, such as37 `Queue:` and `Destination:`. This is a result format, not a requirement to wrap38 parser errors, help, version output, or progress messages in report sections.39- Use this order: command heading → resource identity → outcome or assessment →40 relevant context → actionable next steps. Omit inapplicable sections.41- Use uppercase section headings. Use `RESULT` for completed mutations or explicit42 no-ops; use `STATUS`, `READINESS`, or a descriptive data heading for reads and43 assessments. Do not imply that a read performed a change.44- Separate sections with a blank line and put a blank line after each section45 heading. Use sentence case and terminal punctuation for prose; keep labels and46 table cells concise. End output with a newline.47- Report a single outcome in one plain sentence. Say what changed, including48 before/after values when useful. Say “remains” or “already” for a no-op rather49 than claiming a change.50- Use borderless, aligned tables for collections and comparisons. Prefer the51 existing `text/tabwriter` pattern. No boxed ASCII tables, side-by-side panels,52 decorative banners, or duplicate prose restating every table row.53- Explain empty results in a sentence rather than printing nothing or an empty54 table. Omit empty optional sections.5556## Dry runs and truthful assessments5758- Append exactly ` (DRY RUN)` to the command heading. Use `PROPOSED CHANGE`59 instead of `RESULT`, with “would…” language, never completed-action wording.60- A blocked dry run retains its dry-run heading and explains the blockers; do61 not show a proposed change that cannot proceed.62- Do not offer follow-up steps that assume the dry run actually made a change.63- Distinguish observed facts from conclusions. “No known blockers” is not proof64 of readiness. Do not turn missing data into zero, stale data into live data,65 or a latest sample into a historical maximum. Label observation windows and66 independent refresh times where they affect interpretation.6768## Next steps6970- Include `NEXT STEPS` only when the current state supports a useful, safe action.71 Never add an empty section, generic encouragement, or a speculative remedy.72- Always number the steps, including a single step: `1.`. Put each command on its73 own line, indented three spaces beneath its step, with a blank line before it.74- Use known resource identifiers in suggested commands. Use explicit placeholders75 such as `<percentage>` only for values the operator must choose; never invent a76 safe percentage or silently choose a consequential action.77- State prerequisites before consequential actions: “If no known blockers remain…”78 rather than unconditionally telling an operator to move a pipeline.79- Do not attach per-resource workflow guidance to an unkeyed list command.8081## Automation and diagnostics8283- Keep `--json` machine-readable and preserve its existing contract, including84 API-shaped responses where applicable. Never mix human headings, guidance, or85 progress into JSON stdout or add presentation-only fields to its payload.86- Write command results to stdout; diagnostics and progress to stderr. Successful87 commands should not emit unsolicited blank lines or chatter on stderr.88- Preserve meaningful non-zero exits for errors and blocked assessments. A nicely89 rendered report does not turn failure into success. Errors should identify the90 failed operation and actionable cause without exposing credentials.9192## Examples9394Successful mutation with one useful next step:9596```text97QUEUE SET-PERCENT98Queue: default99Destination: production100101RESULT102103Routing changed from 10% to 25%.104105NEXT STEPS1061071. Review destination activity:108109 cluster-migrator queue metrics default110```111112Dry run without post-mutation guidance:113114```text115QUEUE SET-PERCENT (DRY RUN)116Queue: default117Destination: production118119PROPOSED CHANGE120121Routing would change from 10% to 25%.122```123124These excerpts illustrate the grammar, not exhaustive command-specific guidance.125Use the command's current contracts and tests to choose the actual next steps.