11// CLI is the presentation boundary — printing and exit are expected here.
22#![ allow( clippy:: print_stdout, clippy:: print_stderr, clippy:: exit) ]
33use anyhow:: Result ;
4- use clap:: Parser ;
4+ use clap:: { CommandFactory , Parser } ;
55
66use auths_cli:: cli:: { AuthsCli , RootCommand } ;
77use 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