rust-cli
Skill1 starsChanged 6 months agoMITView on GitHub
Rust CLI development — command-line tools, TUI apps, shell integration. Use this skill when: - User builds a CLI tool or binary with Rust - User asks about clap, argh, or other argument parsing crates - User implements subcommands, flags, or shell completion - User asks about stdin/stdout/stderr handling or piping - User builds a TUI (Terminal UI) with ratatui or crossterm - User implements progress bars, spinners, or colored output - User works with config files (toml, yaml, json) in a CLI app - User asks about cross-platform terminal handling - User packages/distributes a CLI tool (cargo-dist, homebrew, etc.) Trigger for any Rust CLI or terminal application question.
- Installs packages
5 other projects use a version of this.
Adopted by
Most recently taken up by
- Galvarcus/claude-skillstheir own version, first seen Nov 8, 2026
- po4yka/rust-skillstheir own version, first seen Nov 2, 2026
- full-stack-skills/rust-skillstheir own version, first seen Oct 15, 2026
- Microbiosis/rust-claude-skillstheir own version, first seen May 28, 2026
- tumf/skillstheir own version, first seen Mar 24, 2026
Used with
Projects with it also use rust-macros.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
dawidpereiraSkillrust-macros
Rust macros — macro_rules!, proc macros, derive macros, attribute macros, fragment specifiers, repetition, hygiene, cargo expand, when to use macros vs generics. Use when writing or debugging declarative or procedural macros, choosing between macros and other abstractions, understanding expansion errors, or generating repetitive code.
Also in 3 projects that use this
Other versions5 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 5
- full-stack-skills/rust-skills★ 8same name
- po4yka/rust-skills★ 5same name
- tumf/skills★ 4same name
- Galvarcus/claude-skills★ 3same name
- Microbiosis/rust-claude-skills★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository24 other files it gives its agents.
- rust-api-designSkill
- rust-asyncSkill
- rust-axumSkill
- rust-cloud-nativeSkill
- rust-concurrencySkill
- rust-databaseSkill
- rust-dioxusSkill
- rust-ecosystemSkill
- rust-embeddedSkill
- rust-error-handlingSkill
- rust-fintechSkill
- rust-masterySkill
- rust-memory-optimizationSkill
- rust-mlSkill
- rust-networkingSkill
- rust-observabilitySkill
- rust-ownershipSkill
- rust-design-patternsSkill
- rust-performanceSkill
- rust-securitySkill
- rust-testingSkill
- rust-type-systemSkill
- rust-unsafeSkill
- rust-wasmSkill
The file
We split this skill into the rules it gives an agent
3 rules. 0 are common, 1 is distinctive, and 2 are very specific to this project.
0
Common
Most of the 4 edited versions we read keep these.
1
Distinctive
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 485
Keep parsing in cli.rs; keep business logic testable without clap.
- Very specific: line 488
Add integration tests with assert_cmd and predicates.
- Distinctive: line 490
Test install artifacts on at least one target per supported OS family.
“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Quick Navigation
- references/arg_parsing.md
- references/ux_feedback.md
Rust CLI Development
Build fast, reliable command-line tools in Rust. The ecosystem is excellent: clap for parsing, ratatui for TUI, tracing for observability.
Quick Setup
[dependencies]
clap = { version = "4", features = ["derive"] }
anyhow = "1"
serde = { version = "1", features = ["derive"] }
toml = "0.8"
# Output
colored = "2"
indicatif = "0.17" # Progress bars
# Optional TUI
ratatui = "0.29"
crossterm = "0.28"
[profile.release]
opt-level = "s" # Smaller binary
lto = true
strip = true # Strip debug symbolsArgument Parsing with Clap
use clap::{Parser, Subcommand, Args};
/// My awesome CLI tool
#[derive(Parser)]
#[command(name = "mytool")]
#[command(version, about, long_about = None)]
struct Cli {
/// Global verbosity flag
#[arg(short, long, global = true, action = clap::ArgAction::Count)]
verbose: u8,
/// Config file path
#[arg(short, long, default_value = "~/.config/mytool.toml")]
config: std::path::PathBuf,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Initialize a new project
Init(InitArgs),
/// Run the main operation
Run {
/// Target name
name: String,
/// Dry run — don't actually do anything
#[arg(long)]
dry_run: bool,
},
/// Show status
Status,
}
#[derive(Args)]
struct InitArgs {
/// Directory to initialize in
path: std::path::PathBuf,
/// Use a template
#[arg(short, long, value_enum, default_value = "default")]
template: Template,
}
#[derive(clap::ValueEnum, Clone)]
enum Template {
Default,
Minimal,
Full,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let log_level = match cli.verbose {
0 => tracing::Level::WARN,
1 => tracing::Level::INFO,
2 => tracing::Level::DEBUG,
_ => tracing::Level::TRACE,
};
tracing_subscriber::fmt().with_max_level(log_level).init();
match cli.command {
Commands::Init(args) => run_init(args),
Commands::Run { name, dry_run } => run_main(&name, dry_run),
Commands::Status => run_status(),
}
}Config Files
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Deserialize, Serialize)]
#[serde(default)]
struct Config {
pub api_url: String,
pub timeout_secs: u64,
pub output_format: OutputFormat,
}
impl Default for Config {
fn default() -> Self {
Self {
api_url: "https://api.example.com".into(),
timeout_secs: 30,
output_format: OutputFormat::Text,
}
}
}
fn load_config(path: &PathBuf) -> anyhow::Result<Config> {
if !path.exists() {
return Ok(Config::default());
}
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read config: {}", path.display()))?;
let config: Config = toml::from_str(&content)
.context("Failed to parse config file")?;
Ok(config)
}
fn save_config(config: &Config, path: &PathBuf) -> anyhow::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let content = toml::to_string_pretty(config)?;
std::fs::write(path, content)?;
Ok(())
}
// Platform-appropriate config directory
fn config_path() -> PathBuf {
dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("mytool")
.join("config.toml")
}Stdin / Stdout / Piping
use std::io::{self, BufRead, Write};
fn main() -> anyhow::Result<()> {
let stdin = io::stdin();
let stdout = io::stdout();
let mut out = io::BufWriter::new(stdout.lock()); // Buffered for performance
for line in stdin.lock().lines() {
let line = line?;
// Process and write
writeln!(out, "{}", process_line(&line))?;
}
Ok(())
}
// Detect if we're in a pipe (no TTY)
fn is_piped() -> bool {
!atty::is(atty::Stream::Stdout)
}
// Disable color when piped
fn should_colorize() -> bool {
atty::is(atty::Stream::Stdout) && std::env::var("NO_COLOR").is_err()
}Progress Bars & Spinners
use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
use std::time::Duration;
fn download_files(urls: &[String]) -> anyhow::Result<()> {
let mp = MultiProgress::new();
let overall = mp.add(ProgressBar::new(urls.len() as u64));
overall.set_style(
ProgressStyle::default_bar()
.template("{spinner} [{elapsed}] {bar:40.cyan/blue} {pos}/{len} {msg}")?
.progress_chars("█▉▊▋▌▍▎▏ "),
);
for url in urls {
let pb = mp.add(ProgressBar::new(0));
pb.set_style(
ProgressStyle::default_bar()
.template(" {msg} {bar:30} {bytes}/{total_bytes}")?
);
pb.set_message(url.clone());
// Simulate download
download_with_progress(url, &pb)?;
pb.finish_and_clear();
overall.inc(1);
}
overall.finish_with_message("Done!");
Ok(())
}
// Spinner for indeterminate tasks
fn run_with_spinner<F, T>(msg: &str, f: F) -> T
where F: FnOnce() -> T
{
let pb = ProgressBar::new_spinner();
pb.set_style(ProgressStyle::default_spinner()
.template("{spinner} {msg}")
.unwrap());
pb.set_message(msg.to_string());
pb.enable_steady_tick(Duration::from_millis(100));
let result = f();
pb.finish_and_clear();
result
}Colored Output
use colored::Colorize;
fn print_status(success: bool, message: &str) {
if success {
println!("{} {}", "✓".green().bold(), message);
} else {
eprintln!("{} {}", "✗".red().bold(), message);
}
}
fn print_table(headers: &[&str], rows: &[Vec<String>]) {
// header
let header: Vec<String> = headers.iter()
.map(|h| h.bold().underline().to_string())
.collect();
println!("{}", header.join(" "));
// rows
for row in rows {
println!("{}", row.join(" "));
}
}
// Respect NO_COLOR env var
fn colorize_if_supported(s: &str, color: fn(&str) -> colored::ColoredString) -> String {
if std::env::var("NO_COLOR").is_ok() || !atty::is(atty::Stream::Stdout) {
s.to_string()
} else {
color(s).to_string()
}
}Output Formats
Support both human-readable and machine-parseable output:
#[derive(clap::ValueEnum, Clone, serde::Deserialize)]
enum OutputFormat {
Text,
Json,
Table,
}
fn output_results(items: &[Item], format: &OutputFormat) -> anyhow::Result<()> {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(items)?);
}
OutputFormat::Table => {
print_table_view(items);
}
OutputFormat::Text => {
for item in items {
println!("{}: {}", item.name, item.value);
}
}
}
Ok(())
}Error Output
fn main() {
if let Err(err) = run() {
// Print error chain
eprintln!("{} {}", "error:".red().bold(), err);
for cause in err.chain().skip(1) {
eprintln!(" {} {}", "caused by:".dimmed(), cause);
}
std::process::exit(1);
}
}
// Or with better formatting:
fn main() {
if let Err(err) = run() {
eprintln!("{err:#}"); // anyhow's chain format
std::process::exit(1);
}
}Basic TUI with Ratatui
use crossterm::{
event::{self, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{prelude::*, widgets::*};
use std::io;
fn run_tui() -> io::Result<()> {
// Setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// App state
let mut counter = 0u32;
loop {
terminal.draw(|frame| {
let area = frame.area();
let block = Block::new()
.title("My TUI App")
.borders(Borders::ALL);
let paragraph = Paragraph::new(format!("Counter: {counter}"))
.block(block)
.alignment(Alignment::Center);
frame.render_widget(paragraph, area);
})?;
if event::poll(std::time::Duration::from_millis(50))? {
if let Event::Key(key) = event::read()? {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => break,
KeyCode::Up | KeyCode::Char('+') => counter += 1,
KeyCode::Down | KeyCode::Char('-') => counter = counter.saturating_sub(1),
_ => {}
}
}
}
}
// Cleanup
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
Ok(())
}Shell Completion
use clap::CommandFactory;
use clap_complete::{generate, Shell};
fn generate_completion(shell: Shell) {
let mut cmd = Cli::command();
let name = cmd.get_name().to_string();
generate(shell, &mut cmd, name, &mut std::io::stdout());
}
// Add to CLI:
#[derive(Subcommand)]
enum Commands {
// ... other commands
/// Generate shell completion
Completion {
#[arg(value_enum)]
shell: clap_complete::Shell,
},
}Project Layout
my-cli/
├── src/
│ ├── main.rs # Entry: Cli::parse() + dispatch
│ ├── cli.rs # Clap structs
│ ├── config.rs # Config loading/saving
│ ├── error.rs # AppError definition
│ └── commands/
│ ├── init.rs
│ ├── run.rs
│ └── status.rs
├── tests/
│ └── integration.rs
└── Cargo.tomlRelease Distribution
# Use cargo-dist for GitHub release automation
[workspace.metadata.dist]
cargo-dist-version = "0.22"
ci = "github"
installers = ["shell", "powershell", "homebrew"]
targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-pc-windows-msvc"]cargo install cargo-dist
cargo dist init
cargo dist buildAnti-Patterns
// Bad: prints errors and exits deep inside command logic.
fn run() {
eprintln!("failed");
std::process::exit(1);
}
// Good: return typed errors; main owns presentation and exit code.
fn run() -> Result<(), CliError> {
do_work()?;
Ok(())
}// Bad: progress bars in non-interactive logs.
ProgressBar::new(total);
// Good: gate TUI/progress behavior on terminal detection.
if std::io::IsTerminal::is_terminal(&std::io::stderr()) {
ProgressBar::new(total);
}Release Checklist
- Keep parsing in
cli.rs; keep business logic testable without clap. - Return errors from commands; map them to user-facing messages once.
- Respect
--quiet,--verbose, and non-interactive CI output. - Add integration tests with
assert_cmdandpredicates. - Generate shell completions and document config precedence.
- Test install artifacts on at least one target per supported OS family.
References
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

