@@ -3,6 +3,7 @@ use anyhow::{Context, Result};
3
3
mod args;
4
4
use args:: { Args , CommandName , Subcommands , actions, claude, cursor} ;
5
5
use but_settings:: AppSettings ;
6
+ use colored:: Colorize ;
6
7
use metrics:: { Event , Metrics , Props , metrics_if_configured} ;
7
8
8
9
use but_claude:: hooks:: OutputAsJson ;
@@ -24,6 +25,14 @@ mod status;
24
25
25
26
#[ tokio:: main]
26
27
async fn main ( ) -> Result < ( ) > {
28
+ // Check if help is requested with no subcommand
29
+ if std:: env:: args ( ) . len ( ) == 1
30
+ || std:: env:: args ( ) . any ( |arg| arg == "--help" || arg == "-h" ) && std:: env:: args ( ) . len ( ) == 2
31
+ {
32
+ print_grouped_help ( ) ;
33
+ return Ok ( ( ) ) ;
34
+ }
35
+
27
36
let args: Args = clap:: Parser :: parse ( ) ;
28
37
let app_settings = AppSettings :: load_from_default_path_creating ( ) ?;
29
38
@@ -209,3 +218,68 @@ where
209
218
props. insert ( "error" , error) ;
210
219
props
211
220
}
221
+
222
+ fn print_grouped_help ( ) {
223
+ use clap:: CommandFactory ;
224
+ use std:: collections:: HashSet ;
225
+
226
+ let cmd = Args :: command ( ) ;
227
+ let subcommands: Vec < _ > = cmd. get_subcommands ( ) . collect ( ) ;
228
+
229
+ // Define command groupings and their order (excluding MISC)
230
+ let groups = [
231
+ ( "Inspection" . yellow ( ) , vec ! [ "log" , "status" ] ) ,
232
+ (
233
+ "Stack Operation" . yellow ( ) ,
234
+ vec ! [ "commit" , "rub" , "new" , "describe" , "branch" ] ,
235
+ ) ,
236
+ (
237
+ "Operation History" . yellow ( ) ,
238
+ vec ! [ "oplog" , "undo" , "restore" ] ,
239
+ ) ,
240
+ ] ;
241
+
242
+ println ! ( "{}" , "The GitButler CLI change control system" . red( ) ) ;
243
+ println ! ( ) ;
244
+ println ! ( "Usage: but [OPTIONS] <COMMAND>" ) ;
245
+ println ! ( ) ;
246
+
247
+ // Keep track of which commands we've already printed
248
+ let mut printed_commands = HashSet :: new ( ) ;
249
+
250
+ // Print grouped commands
251
+ for ( group_name, command_names) in & groups {
252
+ println ! ( "{group_name}:" ) ;
253
+ for cmd_name in command_names {
254
+ if let Some ( subcmd) = subcommands. iter ( ) . find ( |c| c. get_name ( ) == * cmd_name) {
255
+ let about = subcmd. get_about ( ) . unwrap_or_default ( ) ;
256
+ println ! ( " {:<10}{about}" , cmd_name. green( ) ) ;
257
+ printed_commands. insert ( cmd_name. to_string ( ) ) ;
258
+ }
259
+ }
260
+ println ! ( ) ;
261
+ }
262
+
263
+ // Collect any remaining commands not in the explicit groups
264
+ let misc_commands: Vec < _ > = subcommands
265
+ . iter ( )
266
+ . filter ( |subcmd| !printed_commands. contains ( subcmd. get_name ( ) ) && !subcmd. is_hide_set ( ) )
267
+ . collect ( ) ;
268
+
269
+ // Print MISC section if there are any ungrouped commands
270
+ if !misc_commands. is_empty ( ) {
271
+ println ! ( "{}:" , "Other Commands" . yellow( ) ) ;
272
+ for subcmd in misc_commands {
273
+ let about = subcmd. get_about ( ) . unwrap_or_default ( ) ;
274
+ println ! ( " {:<10}{}" , subcmd. get_name( ) . green( ) , about) ;
275
+ }
276
+ println ! ( ) ;
277
+ }
278
+
279
+ println ! ( "{}:" , "Options" . yellow( ) ) ;
280
+ println ! (
281
+ " -C, --current-dir <PATH> Run as if but was started in PATH instead of the current working directory [default: .]"
282
+ ) ;
283
+ println ! ( " -j, --json Whether to use JSON output format" ) ;
284
+ println ! ( " -h, --help Print help" ) ;
285
+ }
0 commit comments