cli-ux

CLI user experience patterns: error messages that guide (not just report), help text design, autocomplete setup, config file hierarchy (~/.config/<tool>), environment variable conventions, --json/--quiet/--verbose flags, and progress indication. The difference between a tool people tolerate and one they recommend.

8 stars

Best use case

cli-ux is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

CLI user experience patterns: error messages that guide (not just report), help text design, autocomplete setup, config file hierarchy (~/.config/<tool>), environment variable conventions, --json/--quiet/--verbose flags, and progress indication. The difference between a tool people tolerate and one they recommend.

Teams using cli-ux should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/cli-ux/SKILL.md --create-dirs "https://raw.githubusercontent.com/marvinrichter/clarc/main/skills/cli-ux/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/cli-ux/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How cli-ux Compares

Feature / Agentcli-uxStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

CLI user experience patterns: error messages that guide (not just report), help text design, autocomplete setup, config file hierarchy (~/.config/<tool>), environment variable conventions, --json/--quiet/--verbose flags, and progress indication. The difference between a tool people tolerate and one they recommend.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# CLI UX Skill

## When to Activate

- Writing error messages for a CLI tool
- Designing `--help` output or man pages
- Adding config file support or env-var overrides
- Implementing autocomplete for shell (bash/zsh/fish)
- Adding spinners or progress bars
- Reviewing the UX of an existing CLI tool
- Designing the initial UX for a new CLI tool where flag naming, config hierarchy, and output format decisions have long-term consequences
- Auditing a CLI that developers find hard to use or frequently get wrong — focusing on error message quality and help text clarity
- Adding `--json` and `--quiet` output modes so a CLI can be scripted and piped without breaking human-readable output

---

## Error Message Design

Good error messages tell users what went wrong **and what to do next**.

### Format

```
error: <what went wrong>
hint:  <what the user should do>

See: https://docs.example.com/errors/<code>
```

### Examples

```
# Bad — reports the internal exception
Error: ENOENT: no such file or directory, open '/etc/config.json'

# Good — explains context and gives next step
error: config file not found at /etc/config.json
hint:  run `tool init` to create a default config, or pass --config <path>
```

```
# Bad — cryptic status code
Error: 401

# Good — actionable
error: authentication failed (401)
hint:  your API token may be expired. Run `tool auth login` to re-authenticate.
```

### Error message checklist

- Start with `error:` (lowercase) for the problem statement
- Add `hint:` on the next line with the recommended action
- Include relevant values (the path that was missing, the token that failed)
- Link to docs for complex errors
- Exit with code 1 for runtime errors, 2 for usage errors
- Never print a raw stack trace to users — log it to a debug file instead

---

## Help Text Design

Every command's `--help` must follow this structure:

```
Usage: tool <command> [options]

  One-sentence description of what this command does.

Options:
  --output, -o <file>   Output file path  [default: stdout]
  --format <fmt>        Output format: json|table|csv  [default: table]
  --verbose, -v         Enable verbose logging
  --quiet, -q           Suppress all output except errors
  --json                Output as machine-readable JSON
  --help, -h            Show this help message

Examples:
  tool export --output report.json --format json
  tool export --quiet | jq '.users[]'
```

### Rules

- **Usage line first** — always show the command signature
- **Description in one sentence** — users scan, they don't read
- **Default values** — always show `[default: x]` for options with defaults
- **Examples at the end** — concrete, copy-pasteable examples are the most-read section
- **No jargon** — use plain language; avoid internal code names
- **Consistent option ordering** — most-used options first

---

## Config File Hierarchy

Apply settings in this priority order (highest first):

```
1. CLI flags              --output ./report.json
2. Environment variables  MY_TOOL_OUTPUT=./report.json
3. Project config         ./.mytool.json  or  ./.mytool/config.json
4. User config            ~/.config/mytool/config.json  ($XDG_CONFIG_HOME/mytool/)
5. System config          /etc/mytool/config.json
6. Built-in defaults
```

### Implementation (Node.js)

```typescript
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import os from 'os';

interface Config {
  output: string;
  format: 'json' | 'table' | 'csv';
  token?: string;
}

const DEFAULT_CONFIG: Config = { output: 'stdout', format: 'table' };

function loadConfig(cliFlags: Partial<Config>): Config {
  const userConfigPath = join(
    process.env.XDG_CONFIG_HOME ?? join(os.homedir(), '.config'),
    'mytool',
    'config.json',
  );

  const projectConfigPath = join(process.cwd(), '.mytool.json');

  const userConfig = existsSync(userConfigPath)
    ? JSON.parse(readFileSync(userConfigPath, 'utf-8'))
    : {};

  const projectConfig = existsSync(projectConfigPath)
    ? JSON.parse(readFileSync(projectConfigPath, 'utf-8'))
    : {};

  const envConfig = {
    ...(process.env.MY_TOOL_OUTPUT && { output: process.env.MY_TOOL_OUTPUT }),
    ...(process.env.MY_TOOL_FORMAT && { format: process.env.MY_TOOL_FORMAT }),
    ...(process.env.MY_TOOL_TOKEN && { token: process.env.MY_TOOL_TOKEN }),
  };

  // Merge in priority order: defaults < user < project < env < CLI flags
  return { ...DEFAULT_CONFIG, ...userConfig, ...projectConfig, ...envConfig, ...cliFlags };
}
```

---

## Environment Variable Conventions

| Naming rule | Example |
|---|---|
| All uppercase | `MY_TOOL_TOKEN` |
| Prefix with tool name | `MY_TOOL_` (not `TOKEN`) |
| Underscore-separated | `MY_TOOL_OUTPUT_FORMAT` |
| No abbreviations | `MY_TOOL_TIMEOUT` (not `MY_TOOL_TO`) |

### Standard env vars to support

```
MY_TOOL_TOKEN       # Auth token (avoids --token flag in shell history)
MY_TOOL_CONFIG      # Override config file path
MY_TOOL_LOG_LEVEL   # debug|info|warn|error
MY_TOOL_NO_COLOR    # Disable colored output (honour this if set to any value)
NO_COLOR            # Standard cross-tool convention (https://no-color.org)
```

### Document env vars in `--help`

```
Environment Variables:
  MY_TOOL_TOKEN    API token (alternative to --token)
  MY_TOOL_CONFIG   Config file path (alternative to --config)
  NO_COLOR         Disable colored output
```

---

## Autocomplete

Shell autocomplete dramatically improves discoverability.

### Node.js — yargs built-in

```typescript
// yargs generates completions automatically
yargs(hideBin(process.argv))
  .completion('completion', 'Generate shell completion script')
  .argv;

// Install: tool completion >> ~/.zshrc
```

### Python — click built-in

```bash
# click generates completions for bash/zsh/fish
_MY_TOOL_COMPLETE=bash_source my-tool >> ~/.bashrc
_MY_TOOL_COMPLETE=zsh_source my-tool >> ~/.zshrc
_MY_TOOL_COMPLETE=fish_source my-tool > ~/.config/fish/completions/my-tool.fish
```

### Go — cobra built-in

```go
// cobra has a built-in completion command
rootCmd.AddCommand(completionCmd)

var completionCmd = &cobra.Command{
  Use:   "completion [bash|zsh|fish|powershell]",
  Short: "Generate shell completion script",
  RunE: func(cmd *cobra.Command, args []string) error {
    switch args[0] {
    case "bash":   return rootCmd.GenBashCompletion(os.Stdout)
    case "zsh":    return rootCmd.GenZshCompletion(os.Stdout)
    case "fish":   return rootCmd.GenFishCompletion(os.Stdout, true)
    default:       return fmt.Errorf("unsupported shell: %s", args[0])
    }
  },
}
```

### Rust — clap built-in

```rust
use clap_complete::{generate, Shell};

// Add completion subcommand
fn generate_completion(shell: Shell) {
    let mut cmd = Cli::command();
    generate(shell, &mut cmd, "my-tool", &mut io::stdout());
}
```

### Document installation in README

```markdown
## Shell Completion

```bash
# bash
tool completion bash >> ~/.bashrc

# zsh
tool completion zsh >> ~/.zshrc

# fish
tool completion fish > ~/.config/fish/completions/tool.fish
```
```

---

## Progress Indication

### Spinner — unknown duration

```typescript
// Node.js — ora
import ora from 'ora';

const spinner = ora('Fetching data…').start();
try {
  const data = await fetchData();
  spinner.succeed('Data fetched');
} catch (error) {
  spinner.fail(`Failed: ${error.message}`);
  process.exit(1);
}
```

```python
# Python — rich
from rich.console import Console

console = Console()
with console.status("Fetching data..."):
    data = fetch_data()
console.print("[green]Done![/green]")
```

### Progress bar — known steps

```typescript
// Node.js — cli-progress
import { SingleBar, Presets } from 'cli-progress';

const bar = new SingleBar({}, Presets.shades_classic);
bar.start(totalFiles, 0);

for (const file of files) {
  await processFile(file);
  bar.increment();
}
bar.stop();
```

### `--no-progress` flag

Always provide a way to disable progress output (important for CI and piping):

```typescript
if (!argv.noProgress && process.stdout.isTTY) {
  const spinner = ora('Processing…').start();
  // ...
}
```

---

## Summary: The 10 UX Rules

1. **Error messages answer "now what?"** — always add a `hint:` line
2. **`--help` examples are mandatory** — copy-pasteable, at the end
3. **Config hierarchy is always the same** — CLI flag > env var > project > user > default
4. **Env vars are prefixed and UPPERCASED** — `MY_TOOL_TOKEN`, not `TOKEN`
5. **Autocomplete ships in v1** — not a nice-to-have
6. **Spinner for unknown wait, bar for known progress**
7. **`--no-progress` for CI** — spinners in CI logs are noise
8. **`NO_COLOR` is honoured** — use the standard, do not invent your own
9. **Errors to stderr, data to stdout** — always, no exceptions
10. **TTY check before interactive prompts** — never block a pipe waiting for input

---

## Checklist

- [ ] Error messages have `error:` + `hint:` lines
- [ ] No raw stack traces in user-facing output
- [ ] `--help` has Usage, Description, Options with defaults, and Examples sections
- [ ] Config hierarchy implemented: CLI > env > project > user > defaults
- [ ] Env vars prefixed with tool name and documented in `--help`
- [ ] `NO_COLOR` respected
- [ ] Autocomplete command added (`completion bash|zsh|fish`)
- [ ] Spinner used for unknown-duration operations
- [ ] Progress bar used for known-step operations
- [ ] `--no-progress` flag available for CI environments
- [ ] TTY check before interactive prompts

Related Skills

zero-trust-patterns

8
from marvinrichter/clarc

Zero-Trust security patterns — mTLS between microservices (Istio/SPIFFE), SPIRE workload identity, OPA/Envoy authorization, NetworkPolicy default-deny-all, short-lived credentials, service mesh security, and Kubernetes RBAC hardening.

wireframing

8
from marvinrichter/clarc

Wireframing and prototyping workflow: fidelity levels (lo-fi sketch → mid-fi wireframe → hi-fi prototype), tool selection (Figma, Excalidraw, Balsamiq), user flow diagrams, wireframe annotation standards, information architecture (IA) mapping, and the handoff from wireframe to visual design. For developers who need to communicate UI structure before writing code.

webrtc-patterns

8
from marvinrichter/clarc

WebRTC patterns — peer connection setup, ICE/STUN/TURN configuration, signaling server design, SFU vs mesh topology, screen sharing, media track management, and reconnect/ICE restart handling.

webhook-patterns

8
from marvinrichter/clarc

Webhook patterns for receiving, verifying (HMAC), and idempotently processing third-party events. Covers Stripe, GitHub, and generic webhook patterns, delivery guarantees, retry handling, and testing.

web-performance

8
from marvinrichter/clarc

Web performance optimization: Core Web Vitals (LCP, CLS, INP), Lighthouse CI with budget configuration, bundle analysis (webpack-bundle-analyzer, vite-bundle-visualizer), hydration performance, network waterfall reading, image optimization (WebP/AVIF, srcset), and font performance.

wasm-performance

8
from marvinrichter/clarc

WebAssembly performance: wasm-opt binary optimization, size reduction (panic=abort, LTO, strip), profiling WASM in Chrome DevTools, memory management (linear memory, avoiding GC pressure), SIMD, and multi-threading with SharedArrayBuffer.

wasm-patterns

8
from marvinrichter/clarc

WebAssembly patterns: wasm-pack, wasm-bindgen (JS↔Wasm interop), WASI, Component Model, wasm-opt, Rust-to-WASM compilation, JS integration (web workers, streaming instantiation), and production deployment (CDN, Content-Type headers).

visual-testing

8
from marvinrichter/clarc

Visual Regression Testing: tool comparison (Chromatic/Percy/Playwright screenshots/BackstopJS), pixel-diff vs AI-based comparison, baseline management, flakiness strategies (masks, tolerances, waitForLoadState), CI integration with GitHub Actions, and Storybook integration.

visual-identity

8
from marvinrichter/clarc

Brand identity development: color palette construction (primary/secondary/semantic/neutral), logo concept brief writing, typeface pairings, brand voice definition, mood board direction, and Brand Guidelines document structure. Use when establishing or evolving a visual brand — not for implementing existing tokens.

ux-micro-patterns

8
from marvinrichter/clarc

UX micro-patterns for every product state: Empty States, Loading States (skeleton screens, spinners, optimistic UI), Error States, Success States, Confirmation Dialogs, Onboarding Flows, and Progressive Disclosure. These patterns apply to every feature — done wrong, they're the biggest source of user confusion.

typography-design

8
from marvinrichter/clarc

Typography as a creative discipline: typeface selection criteria, type pairing (serif + sans, display + body), modular scale systems, line-height and tracking ratios, hierarchy construction, and web/mobile rendering considerations. The decisions behind design tokens, not the tokens themselves.

typescript-testing

8
from marvinrichter/clarc

TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.