Skip to content

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.

  1. Import — color helpers from src/cli/colors.ts (picocolors wrapper)
  2. Replace — console.log with success symbol → success() helper
  3. Replace — console.error with fail symbol → error() helper
  4. 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.

  1. Start — ora spinner before async work begins
  2. Tick — spinner updates with next frame automatically
  3. Stop — spinner.succeed() or spinner.fail() with colored result
  4. 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.

  1. Init — cli-progress single bar with total count
  2. Update — bar.increment() after each file written
  3. Complete — bar.stop(), newline, summary line
  4. 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.

  1. Collect — file paths from result
  2. Build — nested object from path segments
  3. Render — treeify.asTree() with dimmed paths via picocolors
  4. 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.

  1. Detect — no args or --help flag
  2. Render — ASCII banner constant from banner.ts
  3. 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.

  1. Detect — unknown command
  2. Configure — program.showSuggestionAfterError = true
  3. 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.

  1. Init — update-notifier with 24h check interval
  2. Notify — updateNotifier.notify() renders one-line hint in TTY
  3. Fallback — suppressed in non-TTY automatically

Verbosity flags (verbosity-flow)

Global --quiet and --verbose flags control output level across all commands.

  1. Parse — --quiet / --verbose global options via Commander
  2. Route — --quiet routes to errors and final summary only
  3. Route — --verbose routes to timestamps, full paths, debug
  4. 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