1use clap::{Args, Parser, Subcommand};
2
3use crate::speedtest::TestDuration;
4
5#[derive(Parser)]
7#[command(
8 name = "nd300",
9 author,
10 version,
11 disable_version_flag = true,
12 about = "ND-300 Network Diagnostic - QubeTX Developer Tools",
13 long_about = "ND-300 Network Diagnostic - QubeTX Developer Tools\n\n\
14 Cross-platform network diagnostics with 25+ concurrent checks,\n\
15 multi-stage network repair, and DNS configuration management.",
16 after_long_help = "EXAMPLES:\n\
17 \x20 nd300 Run standard diagnostics\n\
18 \x20 nd300 -t Technician mode (deep diagnostics)\n\
19 \x20 nd300 dns Change DNS servers and verify connectivity\n\
20 \x20 nd300 -d Same as 'nd300 dns' (legacy flag form)\n\
21 \x20 nd300 fix Diagnostic-driven triage and recovery loop\n\
22 \x20 nd300 -f Same as 'nd300 fix' (legacy flag form)\n\
23 \x20 nd300 update Check for updates and install\n\
24 \x20 nd300 clear-dns Reset DNS cache\n\
25 \x20 nd300 uninstall Remove nd300 from this system\n\
26 \x20 nd300 --fast Skip speed test for faster execution\n\
27 \x20 nd300 --json Output results as JSON\n\n\
28 Run 'nd300 --help' for full details, or 'nd300 -h' for a summary."
29)]
30pub struct Nd300Cli {
31 #[arg(
33 short = 't',
34 long = "tech",
35 alias = "technician",
36 help_heading = "Modes",
37 global = true
38 )]
39 pub tech: bool,
40
41 #[arg(short = 'T', long, help_heading = "Modes", global = true)]
43 pub title: Option<String>,
44
45 #[arg(long, help_heading = "Output", global = true)]
47 pub json: bool,
48
49 #[arg(long, help_heading = "Output", global = true)]
51 pub ascii: bool,
52
53 #[arg(long, help_heading = "Output", global = true)]
55 pub no_color: bool,
56
57 #[arg(long, help_heading = "Output", global = true)]
59 pub verbose: bool,
60
61 #[arg(long, help_heading = "Speed Test", global = true)]
63 pub fast: bool,
64
65 #[arg(long, default_value = "15", help_heading = "Speed Test", global = true)]
67 pub speed_duration: u64,
68
69 #[arg(short = 'd', long = "dns", help_heading = "Actions")]
71 pub dns: bool,
72
73 #[arg(short = 'f', long = "fix", help_heading = "Actions")]
75 pub fix: bool,
76
77 #[arg(short = 'c', long = "clear-dns", help_heading = "Actions")]
79 pub clear_dns: bool,
80
81 #[arg(long = "uninstall", help_heading = "Actions")]
83 pub uninstall: bool,
84
85 #[arg(long = "update", help_heading = "Actions")]
87 pub update: bool,
88
89 #[arg(
92 short = 'y',
93 long = "yes",
94 alias = "non-interactive",
95 help_heading = "Actions",
96 global = true
97 )]
98 pub yes: bool,
99
100 #[arg(short = 'v', long = "version", action = clap::ArgAction::Version)]
102 pub version: (),
103
104 #[command(subcommand)]
106 pub command: Option<Nd300Command>,
107}
108
109#[derive(Subcommand, Debug, Clone)]
114pub enum Nd300Command {
115 Fix(FixArgs),
120
121 Dns,
124
125 Update,
127
128 #[command(name = "clear-dns")]
130 ClearDns,
131
132 Uninstall,
134
135 #[command(name = "migrate-cleanup", hide = true)]
142 MigrateCleanup(MigrateArgs),
143}
144
145#[derive(Args, Debug, Clone, Default)]
151pub struct FixArgs {}
152
153#[derive(Args, Debug, Clone, Default)]
158pub struct MigrateArgs {
159 #[arg(long = "cargo-copy")]
161 pub cargo_copy: bool,
162
163 #[arg(long = "other-edition")]
165 pub other_edition: bool,
166
167 #[arg(long = "quiet")]
170 pub quiet: bool,
171
172 #[arg(long = "dry-run")]
174 pub dry_run: bool,
175
176 #[arg(long = "json")]
178 pub json: bool,
179
180 #[arg(long = "user-profile", value_name = "PATH")]
184 pub user_profile: Option<String>,
185
186 #[arg(long = "cargo-home", value_name = "PATH")]
189 pub cargo_home: Option<String>,
190}
191
192#[derive(Parser)]
198#[command(
199 name = "speedqx",
200 author,
201 version,
202 disable_version_flag = true,
203 about = "SpeedQX Internet Speed Test - QubeTX Developer Tools",
204 long_about = "SpeedQX Internet Speed Test - QubeTX Developer Tools\n\n\
205 Eight-provider speed test (SpeedQX Methodology v4) using Cloudflare, M-Lab NDT7,\n\
206 M-Lab MSAK (multi-stream), LibreSpeed, fast.com (Netflix), CacheFly, Vultr, and\n\
207 Apple networkQuality. Providers run sequentially and are merged (capacity +\n\
208 consensus with confidence intervals) for maximum accuracy. --fast runs a quick\n\
209 three-provider subset with early stopping.",
210 after_long_help = "EXAMPLES:\n\
211 \x20 speedqx Run the full 8-provider test\n\
212 \x20 speedqx --fast Quick run (Cloudflare + NDT7 + MSAK, early-stop)\n\
213 \x20 speedqx --duration 60 60s per direction for the fixed-duration providers\n\
214 \x20 speedqx --fastcom-duration 30 Override fast.com to 30s/dir\n\
215 \x20 speedqx --skip-msak --skip-apple Drop MSAK + Apple from the full run\n\
216 \x20 speedqx update Check for updates and install\n\
217 \x20 speedqx --update Same as 'speedqx update' (legacy flag form)\n\
218 \x20 speedqx --json Output results as JSON\n\n\
219 Run 'speedqx --help' for full details, or 'speedqx -h' for a summary."
220)]
221pub struct SpeedQXCli {
222 #[arg(long, help_heading = "Output", global = true)]
224 pub json: bool,
225
226 #[arg(long, help_heading = "Output", global = true)]
228 pub ascii: bool,
229
230 #[arg(long, help_heading = "Output", global = true)]
232 pub no_color: bool,
233
234 #[arg(
236 long,
237 default_value = "30",
238 value_parser = parse_duration,
239 help_heading = "Speed Test"
240 )]
241 pub duration: TestDuration,
242
243 #[arg(
245 long,
246 default_value = "auto",
247 value_parser = parse_duration,
248 help_heading = "Speed Test"
249 )]
250 pub fastcom_duration: TestDuration,
251
252 #[arg(long, default_value = "20", help_heading = "Speed Test")]
254 pub latency_probes: u32,
255
256 #[arg(long = "fast", help_heading = "Speed Test")]
259 pub fast: bool,
260
261 #[arg(long = "skip-msak", help_heading = "Speed Test")]
263 pub skip_msak: bool,
264
265 #[arg(long = "skip-apple", help_heading = "Speed Test")]
267 pub skip_apple: bool,
268
269 #[arg(long = "update", help_heading = "Actions")]
271 pub update: bool,
272
273 #[arg(short = 'v', long = "version", action = clap::ArgAction::Version)]
275 pub version: (),
276
277 #[command(subcommand)]
279 pub command: Option<SpeedQXCommand>,
280}
281
282#[derive(Subcommand, Debug, Clone)]
285pub enum SpeedQXCommand {
286 Update,
288}
289
290fn parse_duration(s: &str) -> Result<TestDuration, String> {
291 if s.eq_ignore_ascii_case("auto") {
292 Ok(TestDuration::Auto)
293 } else {
294 s.parse::<u64>()
295 .map(TestDuration::Seconds)
296 .map_err(|_| format!("invalid duration '{}': expected a number or \"auto\"", s))
297 }
298}