1use clap::CommandFactory;
2use std::io::{IsTerminal, Write};
3
4fn ansi_codes() -> (&'static str, &'static str, &'static str, &'static str) {
6 if !std::io::stdout().is_terminal() || std::env::var_os("NO_COLOR").is_some() {
8 return ("", "", "", "");
9 }
10 ("\x1b[0m", "\x1b[1m", "\x1b[32m", "\x1b[37m")
11}
12
13fn capitalize(s: &str) -> String {
15 let mut chars = s.chars();
16 match chars.next() {
17 None => String::new(),
18 Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
19 }
20}
21
22pub fn print_command_help(path: &[&str]) -> bool {
24 let app = crate::Cli::command();
25
26 let mut current_cmd = &app;
28 for name in path {
29 let Some(cmd) = current_cmd
30 .get_subcommands()
31 .find(|c| c.get_name() == *name)
32 else {
33 return false;
34 };
35 current_cmd = cmd;
36 }
37
38 let mut stdout = std::io::stdout();
39 let (reset, bold, green, white) = ansi_codes();
40
41 let full_cmd = format!("mi6 {}", path.join(" "));
43
44 let _ = writeln!(
46 stdout,
47 "{bold}{green}usage:{reset} {bold}{white}{full_cmd} [options]{reset}"
48 );
49 let _ = writeln!(stdout);
50
51 if let Some(about) = current_cmd.get_about() {
53 let _ = writeln!(stdout, "{}", about);
54 let _ = writeln!(stdout);
55 }
56
57 let subcommands: Vec<_> = current_cmd
59 .get_subcommands()
60 .filter(|c| !c.is_hide_set())
61 .collect();
62 let has_subcommands = !subcommands.is_empty();
63
64 if has_subcommands {
65 let cmd_name = capitalize(path.last().unwrap_or(&""));
67 let _ = writeln!(stdout, "{bold}{green}{cmd_name} Commands{reset}");
68 for sub in &subcommands {
69 let name = sub.get_name();
70 let desc = sub.get_about().map(|s| s.to_string()).unwrap_or_default();
71 let _ = writeln!(stdout, " {bold}{white}{name:<20}{reset} {desc}");
72 }
73 let _ = writeln!(stdout);
74 }
75
76 let positionals: Vec<_> = current_cmd.get_positionals().collect();
78 let has_positionals = !positionals.is_empty();
79 if has_positionals {
80 let _ = writeln!(stdout, "{bold}{green}Arguments{reset}");
81 for arg in positionals {
82 let name = arg.get_id().as_str();
83 let help = arg.get_help().map(|s| s.to_string()).unwrap_or_default();
84 let _ = writeln!(stdout, " {bold}{white}<{name}>{reset} {help}");
85 }
86 }
87
88 let options: Vec<_> = current_cmd
90 .get_opts()
91 .filter(|a| !a.is_hide_set() && !a.is_positional())
92 .collect();
93 let has_options = !options.is_empty();
94
95 if has_positionals && (has_options || has_subcommands) {
97 let _ = writeln!(stdout);
98 }
99
100 if has_options {
101 let _ = writeln!(stdout, "{bold}{green}Options{reset}");
102 for opt in options {
103 let short = opt
104 .get_short()
105 .map(|c| format!("-{c}, "))
106 .unwrap_or_default();
107 let long = opt
108 .get_long()
109 .map_or_else(|| opt.get_id().to_string(), |l| format!("--{l}"));
110 let help = opt.get_help().map(|s| s.to_string()).unwrap_or_default();
111 let _ = writeln!(stdout, " {bold}{white}{short}{long}{reset} {help}");
112 }
113 if has_subcommands {
115 let _ = writeln!(stdout);
116 }
117 }
118
119 if has_subcommands {
121 let _ = writeln!(
122 stdout,
123 "See arguments for each command with {bold}{white}{full_cmd} <command> -h{reset}"
124 );
125 }
126
127 true
128}
129
130pub fn print_subcommand_help(subcommand: &str) -> bool {
132 print_command_help(&[subcommand])
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
136pub enum CommandCategory {
137 Inputs,
138 Outputs,
139 Meta,
140}
141
142impl CommandCategory {
143 pub fn name(&self) -> &str {
144 match self {
145 CommandCategory::Inputs => "Input Commands",
146 CommandCategory::Outputs => "Output Commands",
147 CommandCategory::Meta => "Meta Commands",
148 }
149 }
150}
151
152fn get_command_category(name: &str) -> CommandCategory {
157 match name {
158 "ingest" => CommandCategory::Inputs,
160
161 "session" | "tui" | "watch" => CommandCategory::Outputs,
163
164 "enable" | "disable" | "status" => CommandCategory::Meta,
166
167 _ => CommandCategory::Meta,
169 }
170}
171
172pub fn print_help() {
174 let mut stdout = std::io::stdout();
175 let (reset, bold, green, white) = ansi_codes();
176
177 let _ = writeln!(
178 stdout,
179 "{bold}{green}usage:{reset} {bold}{white}mi6 <command> [<args>]{reset}"
180 );
181 let _ = writeln!(stdout);
182 let _ = writeln!(
183 stdout,
184 "Tool for monitoring and managing agentic coding sessions"
185 );
186 let _ = writeln!(stdout);
187
188 let app = crate::Cli::command();
190 let mut commands_by_category: std::collections::HashMap<
191 CommandCategory,
192 Vec<(String, String)>,
193 > = std::collections::HashMap::new();
194
195 for subcommand in app.get_subcommands() {
196 if subcommand.is_hide_set() {
198 continue;
199 }
200
201 let name = subcommand.get_name();
203 if name == "help" {
204 continue;
205 }
206
207 let name = name.to_string();
208 let description = subcommand
209 .get_about()
210 .map(|s| s.to_string())
211 .unwrap_or_default();
212 let category = get_command_category(&name);
213
214 commands_by_category
215 .entry(category)
216 .or_default()
217 .push((name, description));
218 }
219
220 let categories = [
222 CommandCategory::Inputs,
223 CommandCategory::Outputs,
224 CommandCategory::Meta,
225 ];
226
227 let mut first = true;
228 for category in &categories {
229 if let Some(mut commands) = commands_by_category.remove(category) {
230 commands.sort_by(|a, b| a.0.cmp(&b.0));
232
233 if !first {
235 let _ = writeln!(stdout);
236 }
237 first = false;
238
239 let _ = writeln!(stdout, "{bold}{green}{}{reset}", category.name());
240
241 for (name, description) in commands {
242 let _ = writeln!(stdout, " {bold}{white}{name:<20}{reset} {description}");
243 }
244 }
245 }
246
247 let _ = writeln!(stdout);
248 let _ = writeln!(
249 stdout,
250 "See arguments for each command with {bold}{white}mi6 <command> -h{reset}"
251 );
252}