Skip to content

Oneshot Mode

Oneshot mode is for single-command CLIs that do not need subcommand dispatch. Instead of app!() which expects a command name as the first argument, app!(cmd) runs your function directly with all arguments.

When to use oneshot mode

Use oneshot mode when your tool does one thing:

  • A file processor that takes input and output paths
  • A server that starts on a port
  • A build tool that compiles the current directory
  • A greeting tool that takes a name

Use multi-command mode (app!()) when your tool does multiple distinct things:

  • A package manager with install, remove, search
  • A deployment tool with deploy, rollback, status
  • A project manager with create, test, publish, clean

Basic usage

use clish::prelude::*;

#[command]
/// Greet someone by name
fn greet(name: Pos<String>, excited: bool) {
    if excited {
        println!("Hello, {name}!!!");
    } else {
        println!("Hello, {name}.");
    }
}

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

Now the command runs directly:

myapp Alice              # Hello, Alice.
myapp Alice --excited    # Hello, Alice!!!
myapp --help             # Shows greet's help
myapp --version          # Shows app version

Notice there is no greet subcommand. The arguments go straight to the function.

Invocation behavior

In oneshot mode, the help and version flags work at the top level:

Invocation Action
myapp Runs the command with no arguments
myapp -h Prints short command help, exits 0
myapp --help Prints long command help (with details), exits 0
myapp --version Prints app name and version, exits 0
myapp --verbose --version Prints name, version, description, and target, exits 0
myapp arg1 arg2 Runs the command with arg1 arg2
myapp arg1 -h Runs the command with arg1 (help is handled after parsing)

This differs from multi-command mode where myapp with no args prints app-level help and exits with code 1.

Restrictions

Oneshot mode enforces these rules at startup. Violations produce a panic with a clear message:

Restriction Why Panic message
Exactly one #[command] in the binary Oneshot needs a single target command "oneshot mode requires exactly one command, found N commands"
No custom name attribute The command is the app, no subcommand name needed "oneshot command must not have a custom name"
No aliases Aliases are for subcommand lookup, irrelevant here "oneshot command must not have aliases"
Not hidden Hidden is for omitting from subcommand listings "oneshot command must not be hidden"
Not deprecated Deprecation warnings are for subcommands being phased out "oneshot command must not be deprecated"

If you hit any of these, switch to app!() for multi-command mode.

Styling in oneshot mode

Oneshot mode works with the full styling system:

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

#[command]
fn serve(port: Named<Option<u16>>, verbose: bool) {
    let p = port.unwrap_or(3000);
    if verbose {
        println!("Starting server on port {p}");
    }
}

fn main() {
    app!(serve)
        .styles(AppStyles {
            brand: AppStyle::Markup("[bold green]"),
            header: AppStyle::Markup("[bold yellow]"),
            ..Default::default()
        })
        .run();
}

The help output will show the app name and version with your custom brand style, and section headers will use your header style.

Oneshot vs multi-command comparison

Feature app!() app!(cmd)
Subcommand dispatch Yes No
Requires command name as first arg Yes No
Multiple commands allowed Yes No (exactly one)
myapp with no args Prints help, exits 1 Runs command with no args
Command name attribute Allowed Forbidden
Command aliases Allowed Forbidden
Command hidden Allowed Forbidden
Command deprecated Allowed Forbidden

Next step

Learn about Value Resolution to understand how parameters get their values from the command line, environment variables, and defaults.