-
-
Notifications
You must be signed in to change notification settings - Fork 0
CLI Subcommands
Most tools are TUI-only. The SSH User Manager (sshuser) is currently the
one exception, and it's the template to copy if a new tool should also be
scriptable/non-interactive (e.g. for cron jobs or CI).
clap's derive API, nested subcommands:
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>, // None => launch the TUI
}
#[derive(Subcommand)]
enum Commands {
SshUser { #[command(subcommand)] action: SshUserCmd },
// a new tool adds one more variant here
}SshUserCmd then nests further (User { action: UserAction },
Profiles { action: ProfilesAction }, Settings { action: SettingsAction })
— as deep as the tool's own command surface needs. run() matches on
cli.command: None calls tui::run(), Some(Commands::SshUser { action }) calls a run_sshuser(action) function that does the actual work.
run_sshuser in main.rs loads sshuser::config::load(), calls
sshuser::commands::add_user_commands/remove_user_commands to build the
command list, and executes it with ssh_exec::run_commands — the exact
same sshuser module functions tui/sshuser_screen.rs calls. Nothing
CLI-specific lives inside the sshuser module itself; main.rs is just
another caller, same as the screen is. This only works because of the rule
in Architecture that a <tool>/ module never depends on ratatui — if it
did, a CLI-only build path couldn't call into it without dragging in the
whole TUI stack.
- Define the
clapenum(s) for its command surface inmain.rs, nested under a newCommands::<Tool> { ... }variant — model it onSshUserCmd/UserAction/ProfilesAction/SettingsAction. - Write a
run_<tool>(action: ...) -> Result<(), Box<dyn Error>>function that loads the tool's config and calls straight into<tool>::functions — no new logic here, just argument plumbing andprintln!ing results. - Add the new arm to
run()'smatch cli.command. - Keep exit-code semantics consistent with
run_sshuser: on partial failure across multiple hosts, print a per-host result line either way, then returnErr(...)at the end so the process exits non-zero if anything failed (see thefailed > 0check pattern at the end ofUserAction::Add/Removeinmain.rs) — a script depending on this exit code shouldn't have to parse stdout to know if it worked.
Only add a CLI surface if there's an actual scripting use case — it's extra API surface to keep in sync with the TUI, not something every tool needs by default.