clish 0.1.0-beta.5

Elegant CLI framework for Rust.
Documentation

clish

The most elegant CLI framework for Rust.

clish (/klɪʃ/ "KEL-ish")

Crates.io Docs.rs License Docs Rust edition Repository

use clish::prelude::*;

#[command]
fn deploy(target: Pos<String>, env: Named<String>, force: bool) {
    println!("Deploying {target} to {env} (force={force})");
}

fn main() {
    app!().run();
}

Inspired by Typer

Quick Start

[dependencies]
clish = "0.1.0-beta.5"
use clish::prelude::*;

#[command]
fn hello(name: Named<Option<String>>) {
    println!("Hello, {}!", name.unwrap_or("world"));
}

fn main() {
    app!().run();
}
cargo run -- hello --name Alice
# Hello, Alice!

Usage

app -h                      # Short help
app --help                  # Long help (includes details)
app --version               # Show version
app --verbose --version     # Version + platform info
app <cmd> --help            # Per-command help
app <cmd> --help            # Also works: app --help <cmd>
app <cmd> -- arg1 --arg2    # -- separator: everything after is positional

Argument Types

Type CLI Form
Pos<T> <arg> required positional
Pos<Option<T>> [arg] optional positional
Pos<Vec<T>> <arg>... variadic (zero or more)
Named<T> --name <val> required option
Named<Option<T>> [--name <val>] optional option
Named<Vec<T>> --name <val> --name <val> repeatable
bool --flag presence flag

Named options support --name=value, --name value, and -n value forms. Flags support bundling: -abc is equivalent to -a -b -c.

Command Options

#[command(
    help = "Deploy the app",
    details = "Long description shown on --help.",
    aliases = ["ship", "push"],
    hidden = false,
    deprecated = true,
    deprecation_note = "use 'ship' instead",
    param(host, short = 'h', help = "Target host", env = "HOST"),
    param(port, short = 'p', default = "8080"),
    param(level, choices = ["debug", "info"]),
    param(verbose, conflicts_with = ["quiet"]),
    param(output, requires = ["format"]),
)]
fn deploy(host: Pos<String>, port: Named<u16>, level: Named<String>, verbose: bool, quiet: bool, output: Named<Option<String>>, format: Named<Option<String>>) { ... }

Parameter Options

Key Type Description
help string Short description
details string Long description (shown only in --help)
name string Override the CLI flag name
short char Single-character alias (-d)
placeholder string Custom help token
hide bool Omit from help listings
default string Default value
env string Environment variable fallback
choices array Allowed values
conflicts_with array Mutual exclusion
requires array Prerequisites
value_hint string Shell completion hint (reserved)

Resolution order: CLI argument > $ENV_VAR > default > error.

Oneshot Mode

Pass a command function to app!() for single-command CLIs without subcommand dispatch:

use clish::prelude::*;

#[command]
fn greet(name: Pos<String>) {
    println!("Hello, {name}!");
}

fn main() {
    app!(greet).run();
}

Oneshot mode enforces that the command has no custom name, aliases, hidden, or deprecated attributes, and that no other commands are registered.

Compact Usage

Commands with many options can clutter the usage line. By default, clish collapses the usage to [OPTIONS] when a command has more than 5 named options and flags:

# Default: 6+ options -> compact
Usage: myapp deploy <HOST> [OPTIONS]

# 5 or fewer -> full listing
Usage: myapp deploy <HOST> [-p <PORT>] [-l <LEVEL>] [-v]

Configure the threshold or disable it entirely:

app!()
    .compact_usage_limit(Some(10))   // collapse at 10+ options
    .compact_usage(true)             // always collapse (shorthand for Some(0))
    .compact_usage(false)            // never collapse (shorthand for None)
    .run();

The full Arguments and Options sections are always shown regardless.

Styling

use clish::prelude::*;
use clish::help::{AppStyles, AppStyle};

app!()
    .styles(AppStyles {
        header: AppStyle::Markup("[bold cyan underline]"),
        command: AppStyle::Markup("[bold cyan]"),
        ..Default::default()
    })
    .run();

Or use anstyle::Style values directly:

use anstyle::{Style, AnsiColor};

app!()
    .styles(AppStyles {
        header: AppStyle::Anstyle(Style::new().fg_color(Some(AnsiColor::Cyan.into()))),
        ..Default::default()
    })
    .run();

Error Handling

Errors are printed to stderr with structured formatting:

error: unknown command 'deplyo'
  |
1 | myapp deplyo
  |        ^^^^^^
  |
  = hint: run 'myapp --help' for available commands

Command functions can return Result<(), String> for custom error handling.

Documentation

Full docs: https://razkar.codeberg.page/clish API documentation: https://docs.rs/clish

License

MIT or Apache-2.0

Cheers, RazkarStudio.

Copyright (c) 2026 RazkarStudio. All rights reserved.