Pro Rust Cli Builder
Boost productivity using this plan, build, production, ready. Includes structured workflows, validation checks, and reusable patterns for development.
Pro Rust CLI Builder
A complete skill for building professional command-line tools in Rust using clap for argument parsing, with support for subcommands, configuration files, colored output, progress bars, and cross-platform distribution.
When to Use This Skill
Choose this skill when:
- Scaffolding a new Rust CLI tool from scratch with argument parsing
- Adding subcommands, flags, and positional arguments to existing CLIs
- Implementing configuration file loading (TOML, JSON, YAML)
- Adding colored terminal output, progress bars, and interactive prompts
- Packaging and distributing CLI tools via crates.io or GitHub releases
Consider alternatives when:
- Building a Rust web server → use an Actix/Axum skill
- Need a quick script without compilation → use a Python or Bash skill
- Building a GUI application → use a Rust GUI framework skill
- Working with Rust async networking → use a Tokio skill
Quick Start
# Create a new Rust CLI project cargo init my-cli cd my-cli cargo add clap --features derive cargo add anyhow colored serde toml cargo add indicatif # progress bars
// src/main.rs use clap::{Parser, Subcommand}; use anyhow::Result; #[derive(Parser)] #[command(name = "mytool", version, about = "A professional CLI tool")] struct Cli { /// Enable verbose output #[arg(short, long, global = true)] verbose: bool, /// Config file path #[arg(short, long, default_value = "~/.config/mytool/config.toml")] config: String, #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Initialize a new project Init { /// Project name name: String, /// Template to use #[arg(short, long, default_value = "default")] template: String, }, /// Run analysis on the project Analyze { /// Files to analyze #[arg(required = true)] files: Vec<String>, /// Output format #[arg(short, long, value_enum, default_value = "text")] format: OutputFormat, }, } #[derive(clap::ValueEnum, Clone)] enum OutputFormat { Text, Json, Table } fn main() -> Result<()> { let cli = Cli::parse(); match cli.command { Commands::Init { name, template } => init_project(&name, &template, cli.verbose), Commands::Analyze { files, format } => analyze_files(&files, format, cli.verbose), } }
Core Concepts
CLI Architecture Components
| Component | Crate | Purpose |
|---|---|---|
| Arg parsing | clap (derive) | Declarative argument definitions with types |
| Error handling | anyhow | Contextual error chains with ? operator |
| Colored output | colored | Terminal colors: "text".red().bold() |
| Progress bars | indicatif | Bars, spinners, multi-progress |
| Config files | serde + toml | Typed config deserialization |
| File dialogs | dialoguer | Interactive prompts, confirmations, selects |
| Logging | env_logger | RUST_LOG=debug environment-based logging |
Configuration File Loading
use serde::Deserialize; use std::path::PathBuf; #[derive(Deserialize, Default)] struct Config { #[serde(default)] output_dir: Option<PathBuf>, #[serde(default = "default_threads")] threads: usize, #[serde(default)] ignore_patterns: Vec<String>, } fn default_threads() -> usize { num_cpus::get() } fn load_config(path: &str) -> Result<Config> { let expanded = shellexpand::tilde(path); match std::fs::read_to_string(expanded.as_ref()) { Ok(content) => Ok(toml::from_str(&content)?), Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Config::default()), Err(e) => Err(e.into()), } }
Progress Bar for Long Operations
use indicatif::{ProgressBar, ProgressStyle, MultiProgress}; fn process_files(files: &[String]) -> Result<()> { let multi = MultiProgress::new(); let pb = multi.add(ProgressBar::new(files.len() as u64)); pb.set_style(ProgressStyle::default_bar() .template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")? .progress_chars("=>-")); for file in files { pb.set_message(format!("Processing {file}")); process_single_file(file)?; pb.inc(1); } pb.finish_with_message("Done!"); Ok(()) }
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
argParser | string | 'clap-derive' | Argument parsing style: derive macros vs builder API |
errorHandling | string | 'anyhow' | Error library: anyhow (apps) or thiserror (libs) |
configFormat | string | 'toml' | Config file format: TOML, JSON, or YAML |
colorSupport | string | 'auto' | Color output: auto-detect, always, or never |
completions | boolean | true | Generate shell completions (bash, zsh, fish) |
manPage | boolean | false | Generate man page from clap definitions |
Best Practices
-
Use clap's derive macros for type-safe argument parsing — The derive API provides compile-time validation, automatic help generation, and type conversion. Builder API is only needed for dynamic argument construction.
-
Layer configuration: CLI args → env vars → config file → defaults — Higher-priority sources override lower ones. Clap handles args,
std::envhandles env vars, serde handles config files. This follows the principle of least surprise. -
Use
anyhowfor applications,thiserrorfor libraries — Application CLIs benefit from anyhow's ergonomic?operator and context chains. Library crates should use thiserror for structured, matchable error types. -
Respect
NO_COLORand--no-colorconventions — Check theNO_COLORenvironment variable and provide a--no-colorflag. Automatically disable colors when stdout is not a TTY (piped to file or another program). -
Generate shell completions at build time — Use clap's
generatefeature to create completion scripts for bash, zsh, fish, and PowerShell. Include these in your distribution package for a polished user experience.
Common Issues
Arguments conflict or get parsed incorrectly — Clap processes arguments left-to-right. When a subcommand name conflicts with a flag value, use -- to separate flags from positional arguments. Set trailing_var_arg = true for commands that accept arbitrary trailing arguments.
Config file not found on first run — Don't error when the config file doesn't exist — return defaults instead. Create the config directory and a template file with mytool init or mytool config --init. Use directories crate for platform-appropriate config paths.
Binary size too large for distribution — Rust release binaries can be large due to debug symbols and static linking. Add [profile.release] optimizations: strip = true, lto = true, opt-level = "s". Use cargo-bloat to identify the largest contributors to binary size.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.