1use clap::{Parser, Subcommand};
2
3#[derive(Subcommand, Debug, Clone, Copy, PartialEq, Eq)]
4pub enum Command {
5 Update,
7}
8
9#[derive(Parser, Debug)]
11#[command(name = "sd300")]
12#[command(
13 author,
14 version,
15 about = "SD-300 System Diagnostic — QubeTX Developer Tools"
16)]
17#[command(args_conflicts_with_subcommands = true)]
18#[command(
19 long_about = "SD-300 is a live, interactive terminal user interface for real-time \n\
20 system diagnostics and monitoring. Part of the QubeTX 300 Series \n\
21 alongside TR-300 (Machine Report) and ND-300 (Network Diagnostic).\n\n\
22 Run without flags to choose a diagnostic mode interactively, \n\
23 use --user / --tech to launch directly into a mode, or run \n\
24 sd300 update to check for and install the latest release."
25)]
26#[command(after_long_help = "\
27EXAMPLES:
28 sd300 Choose a diagnostic mode interactively
29 sd300 --user Launch directly into User Mode
30 sd300 --tech Launch directly into Technician Mode
31 sd300 update Check for updates and install
32 sd300 --update Same as 'sd300 update' (legacy flag form)
33
34KEYBINDINGS:
35 1-9 Switch to section
36 q / Esc Quit
37 Ctrl+C Quit to shell
38 m Return to mode selection
39 ? Help overlay
40 f Toggle temperature unit (C/F)
41 j / k Scroll (Processes, Connections, Drivers, Disk)
42 c / M / p / n Sort processes by CPU / Memory / PID / Name
43 r Refresh drivers (Drivers section)
44
45SECTIONS:
46 1 Overview System health dashboard / identity and gauges
47 2 CPU Load, sparkline, per-core utilization
48 3 Memory Usage, swap, top consumers
49 4 Disk Drive health, partitions, SMART data
50 5 GPU Utilization, VRAM, temperature
51 6 Network Connectivity, interfaces, active connections
52 7 Processes Running apps / sortable process table
53 8 Thermals Temperature, fans, battery, power
54 9 Drivers Device health, driver versions, services")]
55pub struct Cli {
56 #[arg(long, conflicts_with_all = ["tech", "update"])]
58 pub user: bool,
59
60 #[arg(long, conflicts_with_all = ["user", "update"])]
62 pub tech: bool,
63
64 #[arg(long, conflicts_with_all = ["user", "tech"])]
66 pub update: bool,
67
68 #[command(subcommand)]
70 pub command: Option<Command>,
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use clap::{CommandFactory, Parser};
77
78 #[test]
79 fn parses_positional_update_action() {
80 let cli = Cli::try_parse_from(["sd300", "update"]).expect("update action should parse");
81 assert_eq!(cli.command, Some(Command::Update));
82 assert!(!cli.update);
83 }
84
85 #[test]
86 fn parses_legacy_update_flag() {
87 let cli = Cli::try_parse_from(["sd300", "--update"]).expect("--update should parse");
88 assert!(cli.update);
89 assert_eq!(cli.command, None);
90 }
91
92 #[test]
93 fn rejects_update_with_mode_flags() {
94 let err = Cli::try_parse_from(["sd300", "update", "--tech"]).unwrap_err();
95 assert!(matches!(
96 err.kind(),
97 clap::error::ErrorKind::ArgumentConflict | clap::error::ErrorKind::UnknownArgument
98 ));
99 }
100
101 #[test]
102 fn help_includes_update_forms() {
103 let help = Cli::command().render_long_help().to_string();
104 assert!(help.contains("sd300 update"));
105 assert!(help.contains("--update"));
106 }
107}