CLI design specification

Text UI Design Language for Technical Command-Line Tools

A compact visual and behavioral system for spinners, progress bars, status lines, summaries, errors, and non-interactive logs. Designed for high-performance scientific and data-processing CLIs.

1. Design personality

The interface should feel precise, fast, scientific, and composed.

It should not feel chatty, gamified, noisy, or overly decorative.

An observatory control console, not a slot machine.
precise observable calm copyable TTY-aware log-friendly

2. Visual hierarchy

Primary task line

The main operation currently underway.

⠸ Building spatial index   12.4M / 97.2M rows   00:41 elapsed

Progress task line

Use only when the denominator is known.

▰▰▰▰▰▰▱▱▱▱  62%   Crossmatching partitions   18,432 / 29,716

Secondary detail line

Current partition, worker, cache status, or local detail.

  hpix=12481  rows=83,204  cache=warm  worker=07

Completed task line

Compact, aligned, and stable.

✓ Built spatial index        97.2M rows in 02:18
✓ Crossmatched catalogs      14.8M matches, 3.2GB written

3. Status symbols

Use a small, consistent symbol grammar. Do not assign a unique glyph to every concept.

State Unicode ASCII fallback Meaning
Running*Active indeterminate work
Progress complete segment#Completed fraction
Progress remaining segment-Remaining fraction
SuccessOKCompleted successfully
Warning!!Recoverable issue or degraded mode
ErrorXFailure
Transition->Next action, output path, or movement
Detail·-Sub-item or quiet detail

4. Color semantics

Use color semantically, not decoratively. The output must still be understandable in monochrome.

Role Use
Default textOrdinary information
Dim textSecondary details, paths, debug-adjacent context
GreenSuccess
Yellow / amberWarning, fallback, degraded mode
RedError
Blue / cyanActive operation, object names, paths

Good

✓ Wrote results       /data/run42/xmatch.parquet
! Missing metadata    assuming epoch=J2000
✗ Schema mismatch     column "ra" is float32, expected float64

Avoid

🌈✨ DONE!!! ✨🌈

5. Layout and spacing

Use alignment to make repeated output scannable.

✓ Read       objects.parquet        104.2M rows   8.4 GB
✓ Read       sources.parquet         97.8M rows   7.9 GB
✓ Wrote      matches.parquet         14.8M rows   1.2 GB

Recommended column order

[state] [verb] [object] [quantity] [rate/time/detail]

Example

✓ Indexed    gaia-dr3              1.82B rows    11m42s
✓ Cached     margin tiles          19.4M rows    4.1GB
✓ Wrote      xmatch.parquet        87.2M rows    2m08s

6. Spinners

A spinner means: work is happening, but the fraction complete is not known.

Recommended spinner

⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏

ASCII fallback

- \ | /

Good spinner use

⠦ Opening dataset metadata
⠏ Planning query
⠴ Waiting for workers

With details

⠸ Planning query      14 tables, 3 XMATCH joins
⠸ Building index      00:18 elapsed

Rule

Do not show a fake ETA for indeterminate work. If the denominator exists, use a progress bar.

7. Progress bars

Use progress bars only when the denominator is known.

Preferred compact style

▰▰▰▰▰▰▱▱▱▱  62%   Crossmatching partitions   18,432 / 29,716

ASCII fallback

[######----]  62%   Crossmatching partitions   18,432 / 29,716

Wide terminal

Crossmatching partitions  ▰▰▰▰▰▰▰▰▰▱▱▱▱▱▱  62%  18,432/29,716  41k rows/s

Narrow terminal

▰▰▰▰▰▰▱▱▱▱ 62%  Crossmatching

Progress fields

[label] [bar] [percent] [count] [rate] [eta]

8. Multi-level progress

For nested work, show one primary progress indicator and one changing detail line. Avoid one bar per worker.

Crossmatching  ▰▰▰▰▰▰▱▱▱▱  62%  18,432/29,716 partitions

  current: hpix=12481  rows L/R=83,204/71,992  cache=warm  worker=07

Pipeline form

✓ Read catalogs
✓ Build margin cache
⠸ Crossmatch partitions
  ▰▰▰▰▰▰▱▱▱▱  62%  18,432/29,716
· Write output
· Validate result

Completed form

✓ Read catalogs
✓ Build margin cache
✓ Crossmatch partitions        14.8M matches
✓ Write output                 /data/run42/xmatch.parquet
✓ Validate result              no duplicate left rows

9. Interactive UI versus logs

Interactive TTY mode

Use spinners, progress bars, line updates, color, and compact status.

⠸ Crossmatching partitions   18,432 / 29,716

Non-TTY / log mode

No animation. Emit durable, parseable events.

[2026-06-11 09:41:02] INFO  started crossmatching partitions total=29716
[2026-06-11 09:41:42] INFO  progress crossmatching done=18432 total=29716 rate=128/s
[2026-06-11 09:43:10] INFO  finished crossmatching matches=14802391 elapsed=128.4s
Pretty when interactive. Parseable when logged.

10. Verbosity levels

Quiet

✓ Wrote /data/run42/xmatch.parquet

Normal

✓ Loaded catalogs
Crossmatching  ▰▰▰▰▰▰▱▱▱▱  62%  18,432/29,716
✓ Wrote /data/run42/xmatch.parquet

Verbose

✓ Loaded left catalog        104.2M rows
✓ Loaded right catalog        97.8M rows
✓ Using HEALPix order         12
✓ Match radius                1.0 arcsec
✓ Margin cache                warm, 19.4M rows
Crossmatching  ▰▰▰▰▰▰▱▱▱▱  62%  18,432/29,716  128/s

11. Message style

Use short, direct technical phrases.

Prefer

Building spatial index
Reading Parquet metadata
Crossmatching partitions
Writing result catalog
Validating row counts

Avoid

Please wait while we build your spatial index...
Now doing a crossmatch, this could take a while...
Almost there...

12. Units and formatting

Use compact engineering-style formatting during progress, and exact counts in summaries when needed.

1,204 rows
83,204 rows
14.8M rows
1.82B rows
842 MB
7.3 GB
128/s
41k rows/s
02:18

Scientific parameters

radius=1.0 arcsec
area=9.6 deg²
order=12
hpix=1849231
epoch=J2000

13. Error design

Errors should explain the failed object, reason, and next action.

✗ Crossmatch failed

  reason: schema mismatch
  column: ra
  found:  float32
  expected: float64

  Fix the schema or pass --ra-column explicitly.

14. Recommended ACID-style run

▣ ▣ ▣   ACID
▣ ▢ ▣   Astronomical Catalog Inference Driver
▣ ▣ ▣

✓ Loaded registry             4 catalogs
✓ Planned query               3 joins, 1 XMATCH
✓ Opened left catalog          104.2M rows
✓ Opened right catalog          97.8M rows
✓ Prepared margin cache         19.4M rows

Crossmatching  ▰▰▰▰▰▰▱▱▱▱  62%  18,432/29,716 partitions  128/s  ETA 01:28

  hpix=12481  L=83,204  R=71,992  cache=warm  worker=07

Final output

✓ Crossmatched catalogs       14.8M matches
✓ Wrote result catalog         /data/run42/xmatch.parquet
✓ Validated output             no duplicate left rows

Summary
  elapsed:        02:18
  throughput:     706k rows/s
  output size:    1.2 GB

15. Style guide rules

1
Use spinners only for indeterminate work.
2
Use progress bars only when a denominator exists.
3
Show one primary progress indicator at a time.
4
Prefer stable, aligned summaries over scrolling logs.
5
Use color semantically and sparingly.
6
Every animated UI must degrade cleanly to logs.
7
Never fake progress or ETA.
8
Use exact numbers in summaries, compact numbers during progress.
9
Errors must explain the failed object, reason, and next action.
10
The UI should remain readable when copied into an issue, Slack, or a batch log.

16. Default implementation constants

Symbols:
  running     ⠋
  success     ✓
  warning     !
  error       ✗
  progress    ▰▱

Progress:
  ▰▰▰▰▰▰▱▱▱▱  62%  label  done/total  rate  ETA

Layout:
  [state] [verb/object aligned to ~28 chars] [main metric] [secondary metric]

Tone:
  concise technical verbs, no chatter

Modes:
  --quiet
  default interactive
  --verbose
  --debug
  --no-color
  --plain / auto for non-TTY