home / skills / zhanghandong / rust-skills / domain-cli

domain-cli skill

/skills/domain-cli

This skill helps you design robust CLI tools with layered config, strong error handling, and user-friendly prompts.

npx playbooks add skill zhanghandong/rust-skills --skill domain-cli

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.9 KB
---
name: domain-cli
description: "Use when building CLI tools. Keywords: CLI, command line, terminal, clap, structopt, argument parsing, subcommand, interactive, TUI, ratatui, crossterm, indicatif, progress bar, colored output, shell completion, config file, environment variable, 命令行, 终端应用, 参数解析"
globs: ["**/Cargo.toml"]
user-invocable: false
---

# CLI Domain

> **Layer 3: Domain Constraints**

## Domain Constraints → Design Implications

| Domain Rule | Design Constraint | Rust Implication |
|-------------|-------------------|------------------|
| User ergonomics | Clear help, errors | clap derive macros |
| Config precedence | CLI > env > file | Layered config loading |
| Exit codes | Non-zero on error | Proper Result handling |
| Stdout/stderr | Data vs errors | eprintln! for errors |
| Interruptible | Handle Ctrl+C | Signal handling |

---

## Critical Constraints

### User Communication

```
RULE: Errors to stderr, data to stdout
WHY: Pipeable output, scriptability
RUST: eprintln! for errors, println! for data
```

### Configuration Priority

```
RULE: CLI args > env vars > config file > defaults
WHY: User expectation, override capability
RUST: Layered config with clap + figment/config
```

### Exit Codes

```
RULE: Return non-zero on any error
WHY: Script integration, automation
RUST: main() -> Result<(), Error> or explicit exit()
```

---

## Trace Down ↓

From constraints to design (Layer 2):

```
"Need argument parsing"
    ↓ m05-type-driven: Derive structs for args
    ↓ clap: #[derive(Parser)]

"Need config layering"
    ↓ m09-domain: Config as domain object
    ↓ figment/config: Layer sources

"Need progress display"
    ↓ m12-lifecycle: Progress bar as RAII
    ↓ indicatif: ProgressBar
```

---

## Key Crates

| Purpose | Crate |
|---------|-------|
| Argument parsing | clap |
| Interactive prompts | dialoguer |
| Progress bars | indicatif |
| Colored output | colored |
| Terminal UI | ratatui |
| Terminal control | crossterm |
| Console utilities | console |

## Design Patterns

| Pattern | Purpose | Implementation |
|---------|---------|----------------|
| Args struct | Type-safe args | `#[derive(Parser)]` |
| Subcommands | Command hierarchy | `#[derive(Subcommand)]` |
| Config layers | Override precedence | CLI > env > file |
| Progress | User feedback | `ProgressBar::new(len)` |

## Code Pattern: CLI Structure

```rust
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "myapp", about = "My CLI tool")]
struct Cli {
    /// Enable verbose output
    #[arg(short, long)]
    verbose: bool,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Initialize a new project
    Init { name: String },
    /// Run the application
    Run {
        #[arg(short, long)]
        port: Option<u16>,
    },
}

fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();
    match cli.command {
        Commands::Init { name } => init_project(&name)?,
        Commands::Run { port } => run_server(port.unwrap_or(8080))?,
    }
    Ok(())
}
```

---

## Common Mistakes

| Mistake | Domain Violation | Fix |
|---------|-----------------|-----|
| Errors to stdout | Breaks piping | eprintln! |
| No help text | Poor UX | #[arg(help = "...")] |
| Panic on error | Bad exit code | Result + proper handling |
| No progress for long ops | User uncertainty | indicatif |

---

## Trace to Layer 1

| Constraint | Layer 2 Pattern | Layer 1 Implementation |
|------------|-----------------|------------------------|
| Type-safe args | Derive macros | clap Parser |
| Error handling | Result propagation | anyhow + exit codes |
| User feedback | Progress RAII | indicatif ProgressBar |
| Config precedence | Builder pattern | Layered sources |

---

## Related Skills

| When | See |
|------|-----|
| Error handling | m06-error-handling |
| Type-driven args | m05-type-driven |
| Progress lifecycle | m12-lifecycle |
| Async CLI | m07-concurrency |

Overview

This skill provides practical guidelines and patterns for building robust Rust CLI tools. It captures domain constraints (user communication, config precedence, exit codes, interrupt handling) and maps them to concrete crates and code patterns for argument parsing, config layering, progress display, and terminal UI. Use it to design predictable, scriptable, and user-friendly command-line applications.

How this skill works

The skill inspects common CLI domain rules and translates them into design constraints and Rust implementations. It recommends crates (clap, figment/config, indicatif, dialoguer, ratatui, crossterm) and patterns (derive-based args structs, subcommands, layered config, RAII progress bars). It also enumerates common mistakes and the fixes to enforce correct stdout/stderr usage, exit codes, and config precedence.

When to use it

  • Building a new Rust command-line tool or utility
  • Designing argument parsing and subcommands with type safety
  • Implementing layered configuration (CLI > env > file > defaults)
  • Adding progress indicators, prompts, or TUI components
  • Ensuring correct exit codes and scriptable output

Best practices

  • Send data to stdout and errors to stderr (println! vs eprintln!)
  • Enforce config precedence: CLI args > environment vars > config file > defaults
  • Use #[derive(Parser)] and #[derive(Subcommand)] from clap for type-driven args
  • Return Result from main or call std::process::exit with non-zero codes on error
  • Use RAII-style progress bars (indicatif) for long operations and signal handling for interrupts

Example use cases

  • A scaffolding tool with Init and Run subcommands parsed via clap derives
  • A background worker that reads config from file, environment, and CLI with clear precedence
  • An installer that shows progress bars and interactive prompts (indicatif + dialoguer)
  • A TUI-based admin tool using ratatui + crossterm with proper terminal cleanup
  • A CLI intended for automation where stdout is machine-readable and stderr contains human messages

FAQ

How should I structure argument parsing for multiple commands?

Define a top-level args struct with #[derive(Parser)] and an enum with #[derive(Subcommand)] for subcommands; match the enum to dispatch handlers.

What is the recommended config precedence?

Apply sources in order: CLI args override environment variables, which override config file values, which override built-in defaults.