# Rust CLI

> When to activate: Rust CLI tools, clap, command-line arguments, subcommands, config files, environment variables, terminal output

- Skill: `mattakushi432/rust-cli` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/rust-cli`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/rust-cli/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/rust-cli

---


# Rust CLI Patterns

## clap with Derive API

```toml
[dependencies]
clap = { version = "4", features = ["derive", "env"] }
```

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

#[derive(Parser, Debug)]
#[command(name = "mytool", version, about = "A powerful CLI tool")]
struct Cli {
    #[command(subcommand)]
    command: Commands,

    #[arg(short, long, action = clap::ArgAction::Count)]
    verbose: u8,

    #[arg(short, long, env = "MYTOOL_CONFIG", default_value = "~/.config/mytool.toml")]
    config: std::path::PathBuf,
}

#[derive(Subcommand, Debug)]
enum Commands {
    /// Process files
    Process(ProcessArgs),
    /// Show status
    Status {
        #[arg(short, long)]
        detailed: bool,
    },
}

#[derive(Args, Debug)]
struct ProcessArgs {
    #[arg(required = true)]
    files: Vec<std::path::PathBuf>,

    #[arg(short, long, default_value = ".")]
    output: std::path::PathBuf,

    #[arg(short = 'j', long, default_value_t = 4)]
    jobs: usize,
}

fn main() {
    let cli = Cli::parse();
    match cli.command {
        Commands::Process(args) => process(args),
        Commands::Status { detailed } => status(detailed),
    }
}
```

## Progress Output with indicatif

```toml
[dependencies]
indicatif = "0.17"
```

```rust
use indicatif::{ProgressBar, ProgressStyle};

fn process_with_progress(items: Vec<String>) {
    let pb = ProgressBar::new(items.len() as u64);
    pb.set_style(
        ProgressStyle::with_template(
            "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} {msg}"
        )
        .unwrap()
        .progress_chars("=>-"),
    );

    for item in &items {
        pb.set_message(format!("processing {item}"));
        do_work(item);
        pb.inc(1);
    }

    pb.finish_with_message("done");
}
```

## Colored Terminal Output

```toml
[dependencies]
colored = "2"
```

```rust
use colored::Colorize;

fn print_status(ok: bool, message: &str) {
    if ok {
        println!("{} {}", "✓".green().bold(), message);
    } else {
        eprintln!("{} {}", "✗".red().bold(), message);
    }
}
```

## Config File Handling

```toml
[dependencies]
config = "0.14"
serde = { version = "1", features = ["derive"] }
```

```rust
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Settings {
    database_url: String,
    port: u16,
    workers: usize,
}

fn load_settings() -> anyhow::Result<Settings> {
    let settings = config::Config::builder()
        .set_default("port", 8080)?
        .set_default("workers", 4)?
        .add_source(config::File::with_name("config").required(false))
        .add_source(config::Environment::with_prefix("APP").separator("__"))
        .build()?;

    Ok(settings.try_deserialize::<Settings>()?)
}
```

## Interactive Prompts

```toml
[dependencies]
dialoguer = "0.11"
```

```rust
use dialoguer::{Confirm, Input, Select};

fn interactive_setup() -> anyhow::Result<()> {
    let name: String = Input::new()
        .with_prompt("Project name")
        .default("my-project".into())
        .interact_text()?;

    let framework = Select::new()
        .with_prompt("Select framework")
        .items(&["axum", "actix-web", "warp"])
        .default(0)
        .interact()?;

    let confirmed = Confirm::new()
        .with_prompt(format!("Create project '{name}'?"))
        .default(true)
        .interact()?;

    if confirmed { create_project(name, framework)?; }
    Ok(())
}
```

## Exit Codes and Error Reporting

```rust
fn main() {
    if let Err(e) = run() {
        eprintln!("error: {e:#}");
        std::process::exit(1);
    }
}
```

## Common Anti-Patterns

- **Parsing args manually** — use `clap`; it handles edge cases and generates help
- **Writing errors to stdout** — errors go to `stderr`; use `eprintln!` or a logger
- **Hard-coding config values** — support env vars and config files from the start
- **No progress feedback for long ops** — users need to know the tool is working
- **Panicking on invalid input** — validate early with `clap` validators or early returns

