1use clap::{Parser, Subcommand};
2use std::path::PathBuf;
3
4#[derive(Parser)]
16#[command(name = "ssh-channels-hub")]
17#[command(version)]
18#[command(
19 about = "Manage SSH port-forwarding tunnels with auto-reconnect",
20 long_about = None,
21)]
22#[command(propagate_version = true)]
23#[command(arg_required_else_help = true)]
24pub struct Cli {
25 #[command(subcommand)]
26 pub command: Commands,
27
28 #[arg(short, long, global = true, value_name = "FILE")]
30 pub config: Option<PathBuf>,
31
32 #[arg(short, long, global = true)]
34 pub debug: bool,
35
36 #[arg(long, global = true)]
38 pub no_color: bool,
39}
40
41#[derive(Subcommand)]
42pub enum Commands {
43 #[command(long_about = "\
45Bring up every channel defined in config.toml.
46
47By default runs in the foreground until Ctrl+C; pass -D to detach.
48While running, `status` / `stop` / `restart` connect over a local
49IPC socket recorded next to config.toml.")]
50 Start {
51 #[arg(short = 'D', long)]
53 daemon: bool,
54 },
55
56 Stop,
58
59 Restart,
61
62 #[command(long_about = "\
64Connects to the running daemon over IPC to report live state.
65
66When no daemon is running, falls back to printing the channels
67declared in config.toml so you can still see the intended setup.")]
68 Status,
69
70 #[command(long_about = "\
72Parses config.toml and resolves every `[[channels]]` entry against
73~/.ssh/config. Catches missing host aliases, missing HostName/User,
74unparseable ports, and hosts that need a password but have no
75[auth.<alias>] block.")]
76 Validate {
77 config: Option<PathBuf>,
79 },
80
81 #[command(long_about = "\
83Reads ~/.ssh/config and writes a config.toml with one commented-out
84`[[channels]]` template per Host alias, plus stub [auth.<alias>]
85blocks for hosts that have no IdentityFile.")]
86 Generate {
87 #[arg(short, long, value_name = "FILE")]
89 ssh_config: Option<PathBuf>,
90
91 #[arg(short, long, value_name = "FILE")]
93 output: Option<PathBuf>,
94 },
95
96 #[command(long_about = "\
98For every `local->remote` channel, try a TCP connect to the local
99listen address and verify the tunnel is alive. `remote->local`
100channels are skipped — those can only be verified from the SSH
101server side.")]
102 Test {
103 #[arg(short, long, value_name = "FILE")]
105 config: Option<PathBuf>,
106 },
107}