use clap::{Args, Parser, Subcommand}; use clap_complete::Shell; /// Create and manage named SSH port forwards. #[derive(Parser)] #[command(name = "porthole", version, about)] pub struct Cli { #[command(subcommand)] pub command: Commands, } #[derive(Subcommand)] pub enum Commands { /// Save a new forward profile. #[command(visible_alias = "create", visible_alias = "new")] Add(AddArgs), /// Start a saved forward as a supervised background process. #[command(visible_alias = "start")] Open(OpenArgs), /// Stop a running forward. #[command(visible_alias = "stop")] Close(CloseArgs), /// Update a saved profile. Edit(EditArgs), /// Show detailed status for one forward. Status(StatusArgs), /// List all saved profiles with live status. #[command(visible_alias = "ls")] List(ListArgs), /// Delete a saved profile. #[command(visible_alias = "rm", visible_alias = "delete")] Remove(RemoveArgs), /// Close and delete every forward, tracked or not. #[command(visible_alias = "reset")] Wipe(WipeArgs), /// Generate a shell completion script. Completions { shell: Shell }, /// Internal: runs the supervisor loop for one profile. Not for direct /// use - `open` spawns this itself (spec ยง3). #[command(hide = true, name = "__supervise")] Supervise { name: String }, } /// Shared mapping/connection flags for `add` and `edit` - kept as one /// struct (`#[command(flatten)]`ed into both) so the two can never drift. #[derive(Args, Default)] pub struct MappingArgs { /// Local forward (current machine -> remote machine). #[arg(short, long, value_name = "[BIND:]PORT:HOST:PORT")] pub local: Option, /// Remote forward (remote machine -> current machine). #[arg(short, long, value_name = "[BIND:]PORT:HOST:PORT")] pub remote: Option, /// Dynamic forward (SOCKS proxy). #[arg(short, long, value_name = "[BIND:]PORT")] pub dynamic: Option, /// Jump-host chain, ending at the connection target. #[arg(long, value_name = "[USER@]HOST[:PORT]", value_delimiter = ',')] pub via: Vec, /// Default user for the target and any hop without one. #[arg(short, long, value_name = "USER")] pub user: Option, /// Identity file override. #[arg(short, long, value_name = "PATH")] pub identity: Option, /// SSH port of the final target. #[arg(short, long, value_name = "PORT")] pub port: Option, /// Auto-reconnect on connection drop. #[arg(long, num_args = 0..=1, default_missing_value = "true", value_name = "BOOL")] pub reconnect: Option, /// Base delay between reconnection attempts, in seconds. #[arg(long = "retry-interval", value_name = "SECONDS")] pub retry_interval: Option, /// Cap on the doubling reconnection delay, in seconds. #[arg(long = "backoff-max", value_name = "SECONDS")] pub backoff_max: Option, /// SSH ServerAliveInterval, in seconds. #[arg(long, value_name = "SECONDS")] pub keepalive: Option, } #[derive(Args)] pub struct AddArgs { /// Name of the new profile. pub name: String, #[command(flatten)] pub mapping: MappingArgs, } #[derive(Args)] pub struct EditArgs { /// Name of the profile to edit. pub name: String, #[command(flatten)] pub mapping: MappingArgs, } #[derive(Args)] pub struct OpenArgs { /// Name of the profile to open (ignored with --all). pub name: Option, /// Run attached in the current shell instead of detaching. #[arg(short, long)] pub foreground: bool, /// Open without auto-reconnect, regardless of the profile setting. #[arg(long)] pub once: bool, /// Open every profile with reconnect enabled that isn't already open. #[arg(long)] pub all: bool, } #[derive(Args)] pub struct CloseArgs { /// Name of the profile to close. pub name: String, /// Send SIGKILL immediately instead of SIGTERM with a graceful wait. #[arg(long)] pub force: bool, } #[derive(Args)] pub struct StatusArgs { /// Name of the profile to inspect. pub name: String, /// Machine-readable output. #[arg(long)] pub json: bool, } #[derive(Args)] pub struct ListArgs { /// Show only currently-open forwards. #[arg(long)] pub running: bool, /// Machine-readable output. #[arg(long)] pub json: bool, } #[derive(Args)] pub struct RemoveArgs { /// Name of the profile to delete. pub name: String, /// Delete the profile but leave an active instance running untracked. #[arg(long = "keep-running")] pub keep_running: bool, } #[derive(Args)] pub struct WipeArgs { /// Skip the confirmation prompt. #[arg(short, long)] pub yes: bool, }