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.