usm/cli-color-output [built]
Comprehensive CLI polish for the USM CLI — colorized status output, animated spinners with elapsed time for long operations, progress bars for batch file operations, tree-view rendering of file lists, an ASCII art USM banner on no-args/help, did-you-mean suggestions for typos, a non-blocking update-notifier hint, and --quiet/--verbose verbosity flags. Uses battle-tested libraries (picocolors, ora, cli-progress, treeify, update-notifier) pinned to CJS-compatible versions. All honor NO_COLOR and non-TTY (pipes/CI) by emitting plain text automatically.
Status: built
Why this exists
The CLI ships plain monochrome text with no progress feedback, flat file lists, no typo help, and no update awareness — it feels dated and is hard to scan during long operations. A comprehensive polish layer using battle-tested CLI libraries adds colorized status (green ✓, red ✗, yellow ⚠, cyan →, dim secondary), animated spinners with elapsed time for async work, progress bars for batch operations, tree-view file lists, an ASCII brand banner, did-you-mean suggestions, a non-blocking update notifier, and --quiet/--verbose flags. Every layer degrades gracefully to plain text in non-TTY/CI/NO_COLOR environments.
Design decisions
use-deps-not-inline [accepted]
Decision: Use battle-tested CLI libraries, not hand-rolled inline helpers
Rationale: Libraries like picocolors, ora, cli-progress, and update-notifier are tiny, battle-tested by millions of downloads, and handle edge cases (Windows terminals, truecolor detection, spinner cleanup) that inline code would miss. Less code to maintain, better UX, and they auto-install via npm with no user action needed.
Alternatives considered:
- Strictly zero new deps (all inline) — rejected: More code to maintain, misses edge cases, reinvents wheels
Consequences: Adds ~5 small runtime dependencies to package.json, all pinned to CJS-compatible versions (ora@5, cli-progress@3, update-notifier@5 — newer majors are ESM-only and incompatible with the CJS package).
respect-no-color [accepted]
Decision: Honor NO_COLOR env var and non-TTY stdout/stderr by emitting plain text
Rationale: Standard CLI convention (https://no-color.org) and CI hygiene. Piped/redirected output must stay plain so logs and grep work; colored escape codes in CI logs are noise. picocolors respects NO_COLOR natively; ora disables spinner in non-TTY.
Consequences: Color helpers short-circuit when process.stdout.isTTY is false or NO_COLOR is set. Spinner/progress bars suppressed in non-TTY.
palette [accepted]
Decision: green=✓, red=✗, yellow=⚠, cyan=→/info, dim=secondary values
Rationale: Conventional semantic mapping that reads correctly in both light and dark terminals. Dim for file paths and counts keeps the primary message in foreground.
Consequences: Single palette in src/cli/colors.ts applied consistently
cjs-pin-versions [accepted]
Decision: Pin dependency versions to CJS-compatible majors
Rationale: The package is CommonJS (no type field = module). Newer majors of ora (v6+) and update-notifier (v7+) are ESM-only and would break the CJS build. Pin to the last CJS-compatible major.
Consequences: package.json dependencies list exact major versions: picocolors (^1), ora (^5), cli-progress (^3), treeify (^0.1), update-notifier (^5).
How it works
Colored status output across all commands (color-output)
Every success/error/warning/skip/info line uses color helpers. Symbols are colored (green ✓, red ✗, yellow ⚠, dim ⊘, cyan →). File paths and counts dimmed.
- Import — color helpers from src/cli/colors.ts (picocolors wrapper)
- Replace — console.log with success symbol → success() helper
- Replace — console.error with fail symbol → error() helper
- Replace — console.warn with warn symbol → warning() helper
Spinner during long operations (spinner-flow)
scan, enrich, generate show a spinner with elapsed seconds while work is in progress. Spinner clears on completion and is replaced by the result line.
- Start — ora spinner before async work begins
- Tick — spinner updates with next frame automatically
- Stop — spinner.succeed() or spinner.fail() with colored result
- Fallback — non-TTY shows plain log line (ora auto-disables)
Progress bar for batch operations (progress-flow)
generate writing N files and scan detecting N services show an inline progress bar that updates without scrolling.
- Init — cli-progress single bar with total count
- Update — bar.increment() after each file written
- Complete — bar.stop(), newline, summary line
- Fallback — non-TTY logs count only (bar auto-disabled)
Tree view for file lists (tree-flow)
Files written/skipped lists render as a tree with connectors mirroring .usm/ structure via treeify.
- Collect — file paths from result
- Build — nested object from path segments
- Render — treeify.asTree() with dimmed paths via picocolors
- Fallback — non-TTY flat list
ASCII banner on no-args/help (banner-flow)
usm with no args shows an ASCII USM banner then the help text.
- Detect — no args or --help flag
- Render — ASCII banner constant from banner.ts
- Follow — with help text and command list
Did you mean suggestions for typos (did-you-mean-flow)
Unknown command triggers a did-you-mean suggestion. Commander has built-in suggestion support — just needs enabling.
- Detect — unknown command
- Configure — program.showSuggestionAfterError = true
- Render — Commander emits did-you-mean closest match
Update notifier hint (update-flow)
Non-blocking check of npm registry for newer @smithgray/usm. Cached 24h. One-line hint if newer exists.
- Init — update-notifier with 24h check interval
- Notify — updateNotifier.notify() renders one-line hint in TTY
- Fallback — suppressed in non-TTY automatically
Verbosity flags (verbosity-flow)
Global --quiet and --verbose flags control output level across all commands.
- Parse — --quiet / --verbose global options via Commander
- Route — --quiet routes to errors and final summary only
- Route — --verbose routes to timestamps, full paths, debug
- Default — current output level
Guarantees
symbols-colored
Status symbols are colored in TTY output
Acceptance criteria:
- [ ] ✓ green
- [ ] ✗ red
- [ ] ⚠ yellow
- [ ] ⊘ dim/yellow
- [ ] → cyan
no-color-respected
NO_COLOR env var disables all color
Acceptance criteria:
- [ ] NO_COLOR set → output is plain text
- [ ] NO_COLOR unset → colors active in TTY
non-tty-plain
Piped/redirected output is plain text
Acceptance criteria:
- [ ] stdout not a TTY → no ANSI codes
- [ ] stderr not a TTY → no ANSI codes
- [ ] spinner suppressed in non-TTY
- [ ] progress bar suppressed in non-TTY
- [ ] output remains grep-friendly
palette-consistent
Color semantics are consistent across all commands
Acceptance criteria:
- [ ] success states green across init/scan/validate/generate/enrich/etc
- [ ] error states red across all commands
- [ ] warning states yellow across all commands
- [ ] file paths and counts dim across all commands
deps-cjs-compatible
All new dependencies are CJS-compatible
Acceptance criteria:
- [ ] picocolors ^1 (CJS)
- [ ] ora ^5 (last CJS major; v6+ is ESM)
- [ ] cli-progress ^3 (CJS)
- [ ] treeify ^0.1 (CJS)
- [ ] update-notifier ^5 (last CJS major; v7+ is ESM)
- [ ] package.json remains CommonJS (no module type field)
spinner-long-ops
Animated spinner during async operations
Acceptance criteria:
- [ ] scan, enrich, generate show spinner with elapsed context
- [ ] spinner clears and is replaced by result line on completion
- [ ] spinner suppressed in non-TTY (plain log fallback)
progress-batch
Progress bar for batch file operations
Acceptance criteria:
- [ ] generate writing N files shows progress bar with current/total
- [ ] scan detecting services shows count progress
- [ ] bar hidden in non-TTY
tree-file-lists
File lists rendered as tree view
Acceptance criteria:
- [ ] Files written/skipped lists use tree connectors
- [ ] nested .usm/ structure shown hierarchically
- [ ] flat fallback in non-TTY
ascii-banner
ASCII art USM banner on no-args/help
Acceptance criteria:
- [ ] usm with no args shows ASCII USM banner then help
- [ ] banner suppressed in non-TTY
did-you-mean
Suggestions for unknown commands
Acceptance criteria:
- [ ] usm scaan suggests scan via did-you-mean
- [ ] suggestion shown via Commander built-in
update-notifier
One-line hint when a newer @smithgray/usm is published
Acceptance criteria:
- [ ] checks npm registry in background, non-blocking
- [ ] cached with 24h interval
- [ ] hint only shown if newer version exists and in TTY
verbosity-flags
--quiet and --verbose flags across commands
Acceptance criteria:
- [ ] --quiet shows only errors and final summary
- [ ] default shows current output level
- [ ] --verbose shows timestamps, full paths, debug info
Test specifications
tty-colored
Given:
- tty: true
- no_color_unset: true
Then:
- assertion: success lines contain green ANSI codes
- assertion: error lines contain red ANSI codes
no-color-plain
Given:
- env:
Then:
- assertion: no ANSI escape codes in output
- assertion: symbols still present as plain characters
piped-plain
Given:
- stdout_not_tty: true
Then:
- assertion: no ANSI escape codes in piped output
- assertion: no spinner animation in piped output
- assertion: no progress bar in piped output
spinner-during-async
Given:
- tty: true
- long_op: true
Then:
- assertion: spinner frames appear while work in progress
- assertion: spinner cleared on completion
progress-bar-batch
Given:
- tty: true
- batch_op: true
Then:
- assertion: bar appears with current/total
- assertion: bar at 100% on completion
tree-file-list
Given:
- tty: true
- generate_run: true
Then:
- assertion: file list uses tree connectors
- assertion: .usm structure shown hierarchically
ascii-banner
Given:
- no_args: true
Then:
- assertion: ASCII USM banner rendered
- assertion: help text follows banner
did-you-mean
Given:
- command: "scaan"
Then:
- assertion: did-you-mean scan suggestion shown
update-notifier
Given:
- newer_version_available: true
Then:
- assertion: one-line Update available hint in TTY
- assertion: hint suppressed in non-TTY
verbosity-quiet
Given:
- flag: "--quiet"
Then:
- assertion: only errors and final summary shown
- assertion: success lines suppressed
verbosity-verbose
Given:
- flag: "--verbose"
Then:
- assertion: timestamps present
- assertion: full paths shown
all-commands-covered
Given:
- review: true
Then:
- assertion: init, scan, validate, generate, enrich, scaffold, info, check, query, import, docs, mcp all use color/spinner/verbosity helpers
Implementation
- Primary: src/cli/index.ts
- Test code status: none
See Also
- usm/cli