Skip to content

Commit 0d176b4

Browse files
authored
Merge pull request #139 from auths-dev/dev-simplifyHelpOutput
refactor: simplify cli help
2 parents e455b8f + 453d592 commit 0d176b4

2 files changed

Lines changed: 213 additions & 28 deletions

File tree

crates/auths-cli/src/cli.rs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ fn cli_styles() -> Styles {
5656
about = "\x1b[1;32mauths \u{2014} cryptographic identity for developers and agents\x1b[0m",
5757
version,
5858
styles = cli_styles(),
59-
after_help = "Run 'auths <command> --help' for details on any command.\nRun 'auths --help-all' for advanced commands (id, device, key, policy, ...)."
59+
after_help = "Run 'auths <command> --help' for details on any command.\nRun 'auths --help-all' for all commands including advanced ones."
6060
)]
6161
pub struct AuthsCli {
6262
#[command(subcommand)]
@@ -75,7 +75,7 @@ pub struct AuthsCli {
7575
)]
7676
pub format: OutputFormat,
7777

78-
#[clap(long, global = true, help = "Emit machine-readable JSON")]
78+
#[clap(short = 'j', long, global = true, help = "Emit machine-readable JSON")]
7979
pub json: bool,
8080

8181
#[clap(short, long, global = true, help = "Suppress non-essential output")]
@@ -93,35 +93,59 @@ pub struct AuthsCli {
9393
#[derive(Subcommand, Debug)]
9494
#[command(rename_all = "lowercase")]
9595
pub enum RootCommand {
96+
// ── Primary ──
9697
Init(InitCommand),
97-
Reset(ResetCommand),
9898
Sign(SignCommand),
99-
SignCommit(SignCommitCommand),
10099
Verify(UnifiedVerifyCommand),
101100
Status(StatusCommand),
102101
Whoami(WhoamiCommand),
103-
Tutorial(LearnCommand),
102+
103+
// ── Setup & Troubleshooting ──
104+
Pair(PairCommand),
104105
Doctor(DoctorCommand),
106+
Tutorial(LearnCommand),
107+
108+
// ── Utilities ──
109+
Config(ConfigCommand),
110+
Completions(CompletionsCommand),
111+
112+
// ── Advanced (visible via --help-all) ──
113+
#[command(hide = true)]
114+
Reset(ResetCommand),
115+
#[command(hide = true)]
116+
SignCommit(SignCommitCommand),
117+
#[command(hide = true)]
105118
Signers(SignersCommand),
106-
Pair(PairCommand),
119+
#[command(hide = true)]
107120
Error(ErrorLookupCommand),
108-
Completions(CompletionsCommand),
109121
#[command(hide = true)]
110-
Emergency(EmergencyCommand),
111-
112122
Id(IdCommand),
123+
#[command(hide = true)]
113124
Device(DeviceCommand),
125+
#[command(hide = true)]
114126
Key(KeyCommand),
127+
#[command(hide = true)]
115128
Approval(ApprovalCommand),
129+
#[command(hide = true)]
116130
Artifact(ArtifactCommand),
131+
#[command(hide = true)]
117132
Policy(PolicyCommand),
133+
#[command(hide = true)]
118134
Git(GitCommand),
135+
#[command(hide = true)]
119136
Trust(TrustCommand),
137+
#[command(hide = true)]
120138
Namespace(NamespaceCommand),
139+
#[command(hide = true)]
121140
Org(OrgCommand),
141+
#[command(hide = true)]
122142
Audit(AuditCommand),
123-
Config(ConfigCommand),
143+
#[command(hide = true)]
144+
Auth(AuthCommand),
124145

146+
// ── Internal (visible via --help-all) ──
147+
#[command(hide = true)]
148+
Emergency(EmergencyCommand),
125149
#[command(hide = true)]
126150
Agent(AgentCommand),
127151
#[command(hide = true)]
@@ -136,5 +160,4 @@ pub enum RootCommand {
136160
Log(LogCommand),
137161
#[command(hide = true)]
138162
Account(AccountCommand),
139-
Auth(AuthCommand),
140163
}

crates/auths-cli/src/main.rs

Lines changed: 179 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// CLI is the presentation boundary — printing and exit are expected here.
22
#![allow(clippy::print_stdout, clippy::print_stderr, clippy::exit)]
33
use anyhow::Result;
4-
use clap::Parser;
4+
use clap::{CommandFactory, Parser};
55

66
use auths_cli::cli::{AuthsCli, RootCommand};
77
use auths_cli::commands::executable::ExecutableCommand;
@@ -36,24 +36,25 @@ fn run() -> Result<()> {
3636

3737
let _telemetry = init_audit_sinks();
3838

39-
let cli = AuthsCli::parse();
39+
// Intercept top-level help/--help-all BEFORE clap parsing so we can
40+
// print grouped output while letting clap handle subcommand help normally
41+
// (e.g. `auths init --help` still works via clap).
42+
let raw_args: Vec<String> = std::env::args().skip(1).collect();
43+
let has_help = raw_args.iter().any(|a| a == "--help" || a == "-h");
44+
let has_help_all = raw_args.iter().any(|a| a == "--help-all");
45+
let first_non_flag = raw_args.iter().find(|a| !a.starts_with('-'));
4046

41-
if cli.help_all {
42-
use clap::CommandFactory;
43-
let mut cmd = AuthsCli::command();
44-
let sub_names: Vec<String> = cmd
45-
.get_subcommands()
46-
.map(|s| s.get_name().to_string())
47-
.collect();
48-
for name in &sub_names {
49-
if let Some(sub) = cmd.find_subcommand_mut(name) {
50-
*sub = sub.clone().hide(false);
51-
}
52-
}
53-
cmd.print_help()?;
47+
if has_help_all {
48+
print_grouped_help(true)?;
49+
return Ok(());
50+
}
51+
if has_help && first_non_flag.is_none() {
52+
print_grouped_help(false)?;
5453
return Ok(());
5554
}
5655

56+
let cli = AuthsCli::parse();
57+
5758
let is_json = cli.json || matches!(cli.format, OutputFormat::Json);
5859
if is_json {
5960
set_json_mode(true);
@@ -64,8 +65,7 @@ fn run() -> Result<()> {
6465
let command = match cli.command {
6566
Some(cmd) => cmd,
6667
None => {
67-
use clap::CommandFactory;
68-
AuthsCli::command().print_help()?;
68+
print_grouped_help(false)?;
6969
return Ok(());
7070
}
7171
};
@@ -119,3 +119,165 @@ fn run() -> Result<()> {
119119

120120
result
121121
}
122+
123+
/// Command group definition for grouped help output.
124+
struct CommandGroup {
125+
heading: &'static str,
126+
commands: &'static [&'static str],
127+
}
128+
129+
/// The primary commands shown in default help.
130+
const HELP_GROUPS: &[CommandGroup] = &[
131+
CommandGroup {
132+
heading: "Primary",
133+
commands: &["init", "sign", "verify", "status", "whoami"],
134+
},
135+
CommandGroup {
136+
heading: "Setup & Troubleshooting",
137+
commands: &["pair", "doctor", "tutorial"],
138+
},
139+
CommandGroup {
140+
heading: "Utilities",
141+
commands: &["config", "completions"],
142+
},
143+
];
144+
145+
fn print_grouped_help(show_all: bool) -> Result<()> {
146+
let cmd = AuthsCli::command();
147+
148+
// ANSI codes matching cli_styles()
149+
const BLUE_BOLD: &str = "\x1b[1;34m";
150+
const CYAN_BOLD: &str = "\x1b[1;36m";
151+
const GREEN_BOLD: &str = "\x1b[1;32m";
152+
const RESET: &str = "\x1b[0m";
153+
154+
// Header
155+
println!("{GREEN_BOLD}auths \u{2014} cryptographic identity for developers and agents{RESET}");
156+
println!();
157+
println!("{BLUE_BOLD}Usage:{RESET} auths [OPTIONS] [COMMAND]");
158+
159+
// Collect subcommand metadata
160+
let subcommands: Vec<(&str, String, bool)> = cmd
161+
.get_subcommands()
162+
.map(|s| {
163+
let name = s.get_name();
164+
let about = s.get_about().map(|a| a.to_string()).unwrap_or_default();
165+
let hidden = s.is_hide_set();
166+
(name, about, hidden)
167+
})
168+
.collect();
169+
170+
// Print primary groups
171+
for group in HELP_GROUPS {
172+
println!();
173+
println!("{BLUE_BOLD}{}:{RESET}", group.heading);
174+
for &cmd_name in group.commands {
175+
if let Some((_, about, _)) = subcommands.iter().find(|(n, _, _)| *n == cmd_name) {
176+
println!(" {CYAN_BOLD}{:<13}{RESET}{}", cmd_name, about);
177+
}
178+
}
179+
}
180+
181+
// If --help-all, show advanced and internal groups
182+
if show_all {
183+
// Collect all names in primary groups
184+
let primary_names: Vec<&str> = HELP_GROUPS
185+
.iter()
186+
.flat_map(|g| g.commands.iter().copied())
187+
.collect();
188+
189+
// Internal commands (always hidden, even in --help-all context)
190+
let internal = [
191+
"emergency",
192+
"agent",
193+
"witness",
194+
"scim",
195+
"commit",
196+
"debug",
197+
"log",
198+
"account",
199+
];
200+
201+
// Advanced = hidden but not internal
202+
let advanced: Vec<&(&str, String, bool)> = subcommands
203+
.iter()
204+
.filter(|(name, _, _)| !primary_names.contains(name) && !internal.contains(name))
205+
.collect();
206+
207+
if !advanced.is_empty() {
208+
println!();
209+
println!("{BLUE_BOLD}Advanced:{RESET}");
210+
for (name, about, _) in &advanced {
211+
println!(" {CYAN_BOLD}{:<13}{RESET}{}", name, about);
212+
}
213+
}
214+
215+
// Internal
216+
let internal_cmds: Vec<&(&str, String, bool)> = subcommands
217+
.iter()
218+
.filter(|(name, _, _)| internal.contains(name))
219+
.collect();
220+
221+
if !internal_cmds.is_empty() {
222+
println!();
223+
println!("{BLUE_BOLD}Internal:{RESET}");
224+
for (name, about, _) in &internal_cmds {
225+
println!(" {CYAN_BOLD}{:<13}{RESET}{}", name, about);
226+
}
227+
}
228+
}
229+
230+
// Options
231+
println!();
232+
println!("{BLUE_BOLD}Options:{RESET}");
233+
for arg in cmd.get_arguments() {
234+
if arg.is_hide_set() {
235+
continue;
236+
}
237+
let long = arg.get_long().map(|l| format!("--{l}"));
238+
let short = arg.get_short().map(|s| format!("-{s}"));
239+
let about = arg.get_help().map(|h| h.to_string()).unwrap_or_default();
240+
241+
// Only show value placeholder for args that take a value (not bool flags)
242+
let takes_value = !matches!(
243+
arg.get_action(),
244+
clap::ArgAction::SetTrue
245+
| clap::ArgAction::SetFalse
246+
| clap::ArgAction::Count
247+
| clap::ArgAction::Version
248+
| clap::ArgAction::Help
249+
| clap::ArgAction::HelpShort
250+
| clap::ArgAction::HelpLong
251+
);
252+
let value_hint = if takes_value {
253+
arg.get_value_names()
254+
.unwrap_or_default()
255+
.iter()
256+
.map(|v| format!(" <{v}>"))
257+
.collect::<String>()
258+
} else {
259+
String::new()
260+
};
261+
262+
// Build the flag string and pad to consistent width
263+
let flag_str = match (short, long) {
264+
(Some(s), Some(l)) => format!(" {s}, {l}{value_hint}"),
265+
(None, Some(l)) => format!(" {l}{value_hint}"),
266+
(Some(s), None) => format!(" {s}{value_hint}"),
267+
(None, None) => continue,
268+
};
269+
println!("{CYAN_BOLD}{:<23}{RESET} {about}", flag_str);
270+
}
271+
// Help and version flags (clap adds these separately, not in get_arguments)
272+
println!("{CYAN_BOLD}{:<23}{RESET} Print help", " -h, --help");
273+
println!("{CYAN_BOLD}{:<23}{RESET} Print version", " -V, --version");
274+
275+
// Footer
276+
println!();
277+
println!("Run 'auths <command> --help' for details on any command.");
278+
if !show_all {
279+
println!("Run 'auths --help-all' for all commands including advanced ones.");
280+
}
281+
282+
Ok(())
283+
}

0 commit comments

Comments
 (0)