1use clap::{Args, Parser, Subcommand, ValueEnum};
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 #[command(name = "install-takeover", hide = true)]
147 InstallTakeover(InstallTakeoverArgs),
148
149 #[command(name = "install-maintenance", hide = true)]
151 InstallMaintenance(InstallMaintenanceArgs),
152}
153
154#[derive(Args, Debug, Clone, Default)]
160pub struct FixArgs {}
161
162#[derive(Args, Debug, Clone, Default)]
167pub struct MigrateArgs {
168 #[arg(long = "cargo-copy")]
170 pub cargo_copy: bool,
171
172 #[arg(long = "other-edition")]
174 pub other_edition: bool,
175
176 #[arg(long = "quiet")]
179 pub quiet: bool,
180
181 #[arg(long = "dry-run")]
183 pub dry_run: bool,
184
185 #[arg(long = "json")]
187 pub json: bool,
188
189 #[arg(long = "user-profile", value_name = "PATH")]
193 pub user_profile: Option<String>,
194
195 #[arg(long = "cargo-home", value_name = "PATH")]
198 pub cargo_home: Option<String>,
199
200 #[arg(long = "install-origin", value_enum, hide = true)]
203 pub install_origin: Option<MigrateInstallOrigin>,
204
205 #[arg(long = "retired-update", hide = true)]
208 pub retired_update: bool,
209}
210
211#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
217pub enum MigrateInstallOrigin {
218 MsiGlobal,
219 MsiCorporate,
220 ExeGlobal,
221 ExeCorporate,
222 MacosPkg,
223}
224
225#[derive(Args, Debug, Clone)]
227pub struct InstallTakeoverArgs {
228 #[arg(long, value_enum, hide = true)]
230 pub target: InstallTakeoverTarget,
231
232 #[arg(long, value_enum, default_value = "all", hide = true)]
234 pub scope: InstallTakeoverScope,
235}
236
237#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
239pub enum InstallTakeoverTarget {
240 MsiGlobal,
241 MsiCorporate,
242 ExeGlobal,
243 ExeCorporate,
244 Standalone,
245}
246
247#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
249pub enum InstallTakeoverScope {
250 All,
251 User,
252 Machine,
253}
254
255#[derive(Args, Debug, Clone)]
257pub struct InstallMaintenanceArgs {
258 #[arg(long, value_enum, hide = true)]
259 pub action: InstallMaintenanceAction,
260
261 #[arg(long, value_enum, hide = true)]
262 pub origin: MigrateInstallOrigin,
263
264 #[arg(long, value_name = "PATH", hide = true)]
265 pub current_path: Option<String>,
266
267 #[arg(long, value_name = "PATH", hide = true)]
268 pub legacy_path: Option<String>,
269}
270
271#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
272pub enum InstallMaintenanceAction {
273 SetMarker,
274 ClearMarker,
275 RepairGlobalPath,
276}
277
278#[derive(Parser)]
284#[command(
285 name = "speedqx",
286 author,
287 version,
288 disable_version_flag = true,
289 about = "SpeedQX Internet Speed Test - QubeTX Developer Tools",
290 long_about = "SpeedQX Internet Speed Test - QubeTX Developer Tools\n\n\
291 Eight-provider speed test (SpeedQX Methodology v4) using Cloudflare, M-Lab NDT7,\n\
292 M-Lab MSAK (multi-stream), LibreSpeed, fast.com (Netflix), CacheFly, Vultr, and\n\
293 Apple networkQuality. Providers run sequentially and are merged (capacity +\n\
294 consensus with confidence intervals) for maximum accuracy. --fast runs a quick\n\
295 three-provider subset with early stopping.",
296 after_long_help = "EXAMPLES:\n\
297 \x20 speedqx Run the full 8-provider test\n\
298 \x20 speedqx --fast Quick run (Cloudflare + NDT7 + MSAK, early-stop)\n\
299 \x20 speedqx --duration 60 60s per direction for the fixed-duration providers\n\
300 \x20 speedqx --fastcom-duration 30 Override fast.com to 30s/dir\n\
301 \x20 speedqx --skip-msak --skip-apple Drop MSAK + Apple from the full run\n\
302 \x20 speedqx update Check for updates and install\n\
303 \x20 speedqx --update Same as 'speedqx update' (legacy flag form)\n\
304 \x20 speedqx --json Output results as JSON\n\n\
305 Run 'speedqx --help' for full details, or 'speedqx -h' for a summary."
306)]
307pub struct SpeedQXCli {
308 #[arg(long, help_heading = "Output", global = true)]
310 pub json: bool,
311
312 #[arg(long, help_heading = "Output", global = true)]
314 pub ascii: bool,
315
316 #[arg(long, help_heading = "Output", global = true)]
318 pub no_color: bool,
319
320 #[arg(
322 long,
323 default_value = "30",
324 value_parser = parse_duration,
325 help_heading = "Speed Test"
326 )]
327 pub duration: TestDuration,
328
329 #[arg(
331 long,
332 default_value = "auto",
333 value_parser = parse_duration,
334 help_heading = "Speed Test"
335 )]
336 pub fastcom_duration: TestDuration,
337
338 #[arg(long, default_value = "20", help_heading = "Speed Test")]
340 pub latency_probes: u32,
341
342 #[arg(long = "fast", help_heading = "Speed Test")]
345 pub fast: bool,
346
347 #[arg(long = "skip-msak", help_heading = "Speed Test")]
349 pub skip_msak: bool,
350
351 #[arg(long = "skip-apple", help_heading = "Speed Test")]
353 pub skip_apple: bool,
354
355 #[arg(long = "update", help_heading = "Actions")]
357 pub update: bool,
358
359 #[arg(short = 'v', long = "version", action = clap::ArgAction::Version)]
361 pub version: (),
362
363 #[command(subcommand)]
365 pub command: Option<SpeedQXCommand>,
366}
367
368#[derive(Subcommand, Debug, Clone)]
371pub enum SpeedQXCommand {
372 Update,
374}
375
376fn parse_duration(s: &str) -> Result<TestDuration, String> {
377 if s.eq_ignore_ascii_case("auto") {
378 Ok(TestDuration::Auto)
379 } else {
380 s.parse::<u64>()
381 .map(TestDuration::Seconds)
382 .map_err(|_| format!("invalid duration '{}': expected a number or \"auto\"", s))
383 }
384}