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)]
216pub enum MigrateInstallOrigin {
217 MsiGlobal,
218 MsiCorporate,
219 ExeGlobal,
220 ExeCorporate,
221}
222
223#[derive(Args, Debug, Clone)]
225pub struct InstallTakeoverArgs {
226 #[arg(long, value_enum, hide = true)]
228 pub target: InstallTakeoverTarget,
229
230 #[arg(long, value_enum, default_value = "all", hide = true)]
232 pub scope: InstallTakeoverScope,
233}
234
235#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
237pub enum InstallTakeoverTarget {
238 MsiGlobal,
239 MsiCorporate,
240 ExeGlobal,
241 ExeCorporate,
242 Standalone,
243}
244
245#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
247pub enum InstallTakeoverScope {
248 All,
249 User,
250 Machine,
251}
252
253#[derive(Args, Debug, Clone)]
255pub struct InstallMaintenanceArgs {
256 #[arg(long, value_enum, hide = true)]
257 pub action: InstallMaintenanceAction,
258
259 #[arg(long, value_enum, hide = true)]
260 pub origin: MigrateInstallOrigin,
261
262 #[arg(long, value_name = "PATH", hide = true)]
263 pub current_path: Option<String>,
264
265 #[arg(long, value_name = "PATH", hide = true)]
266 pub legacy_path: Option<String>,
267}
268
269#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
270pub enum InstallMaintenanceAction {
271 SetMarker,
272 ClearMarker,
273 RepairGlobalPath,
274}
275
276#[derive(Parser)]
282#[command(
283 name = "speedqx",
284 author,
285 version,
286 disable_version_flag = true,
287 about = "SpeedQX Internet Speed Test - QubeTX Developer Tools",
288 long_about = "SpeedQX Internet Speed Test - QubeTX Developer Tools\n\n\
289 Eight-provider speed test (SpeedQX Methodology v4) using Cloudflare, M-Lab NDT7,\n\
290 M-Lab MSAK (multi-stream), LibreSpeed, fast.com (Netflix), CacheFly, Vultr, and\n\
291 Apple networkQuality. Providers run sequentially and are merged (capacity +\n\
292 consensus with confidence intervals) for maximum accuracy. --fast runs a quick\n\
293 three-provider subset with early stopping.",
294 after_long_help = "EXAMPLES:\n\
295 \x20 speedqx Run the full 8-provider test\n\
296 \x20 speedqx --fast Quick run (Cloudflare + NDT7 + MSAK, early-stop)\n\
297 \x20 speedqx --duration 60 60s per direction for the fixed-duration providers\n\
298 \x20 speedqx --fastcom-duration 30 Override fast.com to 30s/dir\n\
299 \x20 speedqx --skip-msak --skip-apple Drop MSAK + Apple from the full run\n\
300 \x20 speedqx update Check for updates and install\n\
301 \x20 speedqx --update Same as 'speedqx update' (legacy flag form)\n\
302 \x20 speedqx --json Output results as JSON\n\n\
303 Run 'speedqx --help' for full details, or 'speedqx -h' for a summary."
304)]
305pub struct SpeedQXCli {
306 #[arg(long, help_heading = "Output", global = true)]
308 pub json: bool,
309
310 #[arg(long, help_heading = "Output", global = true)]
312 pub ascii: bool,
313
314 #[arg(long, help_heading = "Output", global = true)]
316 pub no_color: bool,
317
318 #[arg(
320 long,
321 default_value = "30",
322 value_parser = parse_duration,
323 help_heading = "Speed Test"
324 )]
325 pub duration: TestDuration,
326
327 #[arg(
329 long,
330 default_value = "auto",
331 value_parser = parse_duration,
332 help_heading = "Speed Test"
333 )]
334 pub fastcom_duration: TestDuration,
335
336 #[arg(long, default_value = "20", help_heading = "Speed Test")]
338 pub latency_probes: u32,
339
340 #[arg(long = "fast", help_heading = "Speed Test")]
343 pub fast: bool,
344
345 #[arg(long = "skip-msak", help_heading = "Speed Test")]
347 pub skip_msak: bool,
348
349 #[arg(long = "skip-apple", help_heading = "Speed Test")]
351 pub skip_apple: bool,
352
353 #[arg(long = "update", help_heading = "Actions")]
355 pub update: bool,
356
357 #[arg(short = 'v', long = "version", action = clap::ArgAction::Version)]
359 pub version: (),
360
361 #[command(subcommand)]
363 pub command: Option<SpeedQXCommand>,
364}
365
366#[derive(Subcommand, Debug, Clone)]
369pub enum SpeedQXCommand {
370 Update,
372}
373
374fn parse_duration(s: &str) -> Result<TestDuration, String> {
375 if s.eq_ignore_ascii_case("auto") {
376 Ok(TestDuration::Auto)
377 } else {
378 s.parse::<u64>()
379 .map(TestDuration::Seconds)
380 .map_err(|_| format!("invalid duration '{}': expected a number or \"auto\"", s))
381 }
382}