use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "ssh-channels-hub")]
#[command(version)]
#[command(
about = "Manage SSH port-forwarding tunnels with auto-reconnect",
long_about = None,
)]
#[command(propagate_version = true)]
#[command(arg_required_else_help = true)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
#[arg(short, long, global = true, value_name = "FILE")]
pub config: Option<PathBuf>,
#[arg(short, long, global = true)]
pub debug: bool,
#[arg(long, global = true)]
pub no_color: bool,
}
#[derive(Subcommand)]
pub enum Commands {
#[command(long_about = "\
Bring up every channel defined in config.toml.
By default runs in the foreground until Ctrl+C; pass -D to detach.
While running, `status` / `stop` / `restart` connect over a local
IPC socket recorded next to config.toml.")]
Start {
#[arg(short = 'D', long)]
daemon: bool,
},
Stop,
Restart,
#[command(long_about = "\
Connects to the running daemon over IPC to report live state, with
per-channel health (Connected / Reconnecting / Failed / Stopped).
When no daemon is running, falls back to printing the channels
declared in config.toml so you can still see the intended setup.
With --watch, re-renders periodically until interrupted (Ctrl+C);
useful for monitoring a fragile link.")]
Status {
#[arg(short = 'w', long)]
watch: bool,
#[arg(short = 'n', long, default_value_t = 2, value_name = "SECONDS")]
interval: u64,
},
#[command(long_about = "\
Parses config.toml and resolves every `[[channels]]` entry against
~/.ssh/config. Catches missing host aliases, missing HostName/User,
unparseable ports, and hosts that need a password but have no
[auth.<alias>] block.")]
Validate {
config: Option<PathBuf>,
},
#[command(long_about = "\
Reads ~/.ssh/config and writes a config.toml with one commented-out
`[[channels]]` template per Host alias, plus stub [auth.<alias>]
blocks for hosts that have no IdentityFile.")]
Generate {
#[arg(short, long, value_name = "FILE")]
ssh_config: Option<PathBuf>,
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
},
#[command(long_about = "\
Scans SSH config and reports each `Host <alias>` block as supported or
unsupported for ssh-channels-hub. Use this before writing channels to find
missing HostName/User fields, unsupported ProxyJump forms, and hosts that
will need a password in config.toml.")]
Hosts {
#[arg(short, long, value_name = "FILE")]
ssh_config: Option<PathBuf>,
#[arg(long, value_enum, default_value_t = HostOutputFormat::Table)]
format: HostOutputFormat,
},
#[command(long_about = "\
For every `local->remote` channel, try a TCP connect to the local
listen address and verify the tunnel is alive. `remote->local`
channels are skipped — those can only be verified from the SSH
server side.")]
Test {
#[arg(short, long, value_name = "FILE")]
config: Option<PathBuf>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum HostOutputFormat {
Table,
Json,
}