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 arg_entries: Vec<(String, String)> = positionals
82 .iter()
83 .map(|arg| {
84 let name = arg.get_id().as_str();
85 let arg_str = format!("<{name}>");
86 let help = arg.get_help().map(|s| s.to_string()).unwrap_or_default();
87 (arg_str, help)
88 })
89 .collect();
90
91 let max_width = arg_entries.iter().map(|(s, _)| s.len()).max().unwrap_or(0);
92
93 let _ = writeln!(stdout, "{bold}{green}Arguments{reset}");
94 for (arg_str, help) in arg_entries {
95 let _ = writeln!(
96 stdout,
97 " {bold}{white}{arg_str:<max_width$}{reset} {help}"
98 );
99 }
100 }
101
102 let options: Vec<_> = current_cmd
104 .get_opts()
105 .filter(|a| !a.is_hide_set() && !a.is_positional())
106 .collect();
107 let has_options = !options.is_empty();
108
109 if has_positionals && (has_options || has_subcommands) {
111 let _ = writeln!(stdout);
112 }
113
114 if has_options {
115 let option_entries: Vec<(String, String)> = options
117 .iter()
118 .map(|opt| {
119 let short = opt
120 .get_short()
121 .map(|c| format!("-{c}, "))
122 .unwrap_or_default();
123 let long = opt
124 .get_long()
125 .map_or_else(|| opt.get_id().to_string(), |l| l.to_string());
126 let aliases: Vec<&str> = opt.get_visible_aliases().unwrap_or_default();
127 let long_with_aliases = if aliases.is_empty() {
128 format!("--{long}")
129 } else {
130 let alias_str = aliases
131 .iter()
132 .map(|a| format!("--{a}"))
133 .collect::<Vec<_>>()
134 .join(", ");
135 format!("--{long}, {alias_str}")
136 };
137 let opt_str = format!("{short}{long_with_aliases}");
138 let help = opt.get_help().map(|s| s.to_string()).unwrap_or_default();
139 (opt_str, help)
140 })
141 .collect();
142
143 let max_width = option_entries
144 .iter()
145 .map(|(s, _)| s.len())
146 .max()
147 .unwrap_or(0);
148
149 let _ = writeln!(stdout, "{bold}{green}Options{reset}");
150 for (opt_str, help) in option_entries {
151 let _ = writeln!(
152 stdout,
153 " {bold}{white}{opt_str:<max_width$}{reset} {help}"
154 );
155 }
156 if has_subcommands {
158 let _ = writeln!(stdout);
159 }
160 }
161
162 if has_subcommands {
164 let _ = writeln!(
165 stdout,
166 "See arguments for each command with {bold}{white}{full_cmd} <command> -h{reset}"
167 );
168 }
169
170 true
171}
172
173pub fn print_subcommand_help(subcommand: &str) -> bool {
175 print_command_help(&[subcommand])
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
179pub enum CommandCategory {
180 Inputs,
181 Outputs,
182 Meta,
183}
184
185impl CommandCategory {
186 pub fn name(&self) -> &str {
187 match self {
188 CommandCategory::Inputs => "Input Commands",
189 CommandCategory::Outputs => "Output Commands",
190 CommandCategory::Meta => "Meta Commands",
191 }
192 }
193}
194
195fn get_command_category(name: &str) -> CommandCategory {
200 match name {
201 "ingest" => CommandCategory::Inputs,
203
204 "session" | "tui" | "watch" => CommandCategory::Outputs,
206
207 "enable" | "disable" | "status" => CommandCategory::Meta,
209
210 _ => CommandCategory::Meta,
212 }
213}
214
215pub fn print_help() {
217 let mut stdout = std::io::stdout();
218 let (reset, bold, green, white) = ansi_codes();
219
220 let _ = writeln!(
221 stdout,
222 "{bold}{green}usage:{reset} {bold}{white}mi6 <command> [<args>]{reset}"
223 );
224 let _ = writeln!(stdout);
225 let _ = writeln!(
226 stdout,
227 "Tool for monitoring and managing agentic coding sessions"
228 );
229 let _ = writeln!(stdout);
230
231 let app = crate::Cli::command();
233 let mut commands_by_category: std::collections::HashMap<
234 CommandCategory,
235 Vec<(String, String)>,
236 > = std::collections::HashMap::new();
237
238 for subcommand in app.get_subcommands() {
239 if subcommand.is_hide_set() {
241 continue;
242 }
243
244 let name = subcommand.get_name();
246 if name == "help" {
247 continue;
248 }
249
250 let name = name.to_string();
251 let description = subcommand
252 .get_about()
253 .map(|s| s.to_string())
254 .unwrap_or_default();
255 let category = get_command_category(&name);
256
257 commands_by_category
258 .entry(category)
259 .or_default()
260 .push((name, description));
261 }
262
263 let categories = [
265 CommandCategory::Inputs,
266 CommandCategory::Outputs,
267 CommandCategory::Meta,
268 ];
269
270 let mut first = true;
271 for category in &categories {
272 if let Some(mut commands) = commands_by_category.remove(category) {
273 commands.sort_by(|a, b| a.0.cmp(&b.0));
275
276 if !first {
278 let _ = writeln!(stdout);
279 }
280 first = false;
281
282 let _ = writeln!(stdout, "{bold}{green}{}{reset}", category.name());
283
284 for (name, description) in commands {
285 let _ = writeln!(stdout, " {bold}{white}{name:<20}{reset} {description}");
286 }
287 }
288 }
289
290 let _ = writeln!(stdout);
291 let _ = writeln!(
292 stdout,
293 "See arguments for each command with {bold}{white}mi6 <command> -h{reset}"
294 );
295}