@@ -105,6 +105,7 @@ impl std::error::Error for ClassifiedError {}
105105#[ derive( Clone , Debug , Eq , PartialEq ) ]
106106enum Command {
107107 Help ,
108+ HelpText { name : String , text : String } ,
108109 Auth ( services:: auth_command:: AuthRequest ) ,
109110 Completion ( services:: completion:: CompletionRequest ) ,
110111 Config ( services:: config:: ConfigSubcommand ) ,
@@ -117,9 +118,10 @@ enum Command {
117118}
118119
119120impl Command {
120- fn name ( & self ) -> & ' static str {
121+ fn name ( & self ) -> & str {
121122 match self {
122123 Self :: Help => "help" ,
124+ Self :: HelpText { name, .. } => name. as_str ( ) ,
123125 Self :: Auth ( _) => services:: auth_command:: NAME ,
124126 Self :: Completion ( _) => services:: completion:: NAME ,
125127 Self :: Config ( _) => services:: config:: NAME ,
@@ -294,11 +296,22 @@ where
294296 Err ( error) => {
295297 // Handle --help specially - user explicitly requested help
296298 if error. kind ( ) == clap:: error:: ErrorKind :: DisplayHelp {
299+ if let Some ( ( name, text) ) = render_subcommand_help_from_args ( & args_vec) {
300+ return Ok ( Command :: HelpText { name, text } ) ;
301+ }
302+
297303 // Return Help command for successful output
298304 return Ok ( Command :: Help ) ;
299305 }
300306 // Handle missing subcommand as validation error, not help display
301307 if error. kind ( ) == clap:: error:: ErrorKind :: DisplayHelpOnMissingArgumentOrSubcommand {
308+ if args_vec. get ( 1 ) . map ( String :: as_str) == Some ( services:: auth_command:: NAME ) {
309+ return Ok ( Command :: HelpText {
310+ name : services:: auth_command:: NAME . to_string ( ) ,
311+ text : cli_schema:: auth_help_text ( ) ,
312+ } ) ;
313+ }
314+
302315 // This means a required subcommand was not provided
303316 return Err ( ClassifiedError :: parse (
304317 "Missing required subcommand. Try: run 'sce --help' to see valid commands." ,
@@ -350,6 +363,25 @@ fn classify_clap_error(error: &clap::Error) -> ClassifiedError {
350363 }
351364}
352365
366+ fn render_subcommand_help_from_args ( args : & [ String ] ) -> Option < ( String , String ) > {
367+ let command_name = args. get ( 1 ) ?. clone ( ) ;
368+ let command_path = args[ 1 ..]
369+ . iter ( )
370+ . take_while ( |arg| !arg. starts_with ( '-' ) )
371+ . map ( String :: as_str)
372+ . collect :: < Vec < _ > > ( ) ;
373+
374+ if command_path. is_empty ( ) {
375+ return None ;
376+ }
377+
378+ if command_path. as_slice ( ) == [ services:: auth_command:: NAME ] {
379+ return Some ( ( command_name, cli_schema:: auth_help_text ( ) ) ) ;
380+ }
381+
382+ cli_schema:: render_help_for_path ( & command_path) . map ( |text| ( command_name, text) )
383+ }
384+
353385/// Clean up clap error messages to match our error message style.
354386fn clean_clap_error_message ( message : & str , kind : clap:: error:: ErrorKind ) -> String {
355387 use clap:: error:: ErrorKind ;
@@ -603,6 +635,7 @@ fn convert_hooks_subcommand(
603635fn dispatch ( command : & Command ) -> Result < String , ClassifiedError > {
604636 match command {
605637 Command :: Help => Ok ( command_surface:: help_text ( ) ) ,
638+ Command :: HelpText { text, .. } => Ok ( text. clone ( ) ) ,
606639 Command :: Auth ( request) => services:: auth_command:: run_auth_subcommand ( * request)
607640 . map_err ( |error| ClassifiedError :: runtime ( error. to_string ( ) ) ) ,
608641 Command :: Completion ( request) => Ok ( services:: completion:: render_completion ( * request) ) ,
@@ -700,6 +733,68 @@ mod tests {
700733 assert ! ( stdout. contains( "Usage:" ) ) ;
701734 }
702735
736+ #[ test]
737+ fn bare_auth_writes_auth_help_to_stdout ( ) {
738+ let mut stdout = Vec :: new ( ) ;
739+ let mut stderr = Vec :: new ( ) ;
740+ let code = run_with_dependency_check_and_streams (
741+ vec ! [ "sce" . to_string( ) , "auth" . to_string( ) ] ,
742+ || Ok ( ( ) ) ,
743+ & mut stdout,
744+ & mut stderr,
745+ ) ;
746+ assert_eq ! ( code, ExitCode :: SUCCESS ) ;
747+ assert ! ( stderr. is_empty( ) ) ;
748+
749+ let stdout = String :: from_utf8 ( stdout) . expect ( "stdout should be utf-8" ) ;
750+ assert ! ( stdout. contains( "Usage: auth <COMMAND>" ) ) ;
751+ assert ! ( stdout. contains( "login" ) ) ;
752+ assert ! ( stdout. contains( "status" ) ) ;
753+ assert ! ( stdout. contains( "sce auth status" ) ) ;
754+ }
755+
756+ #[ test]
757+ fn auth_help_writes_auth_specific_help_to_stdout ( ) {
758+ let mut stdout = Vec :: new ( ) ;
759+ let mut stderr = Vec :: new ( ) ;
760+ let code = run_with_dependency_check_and_streams (
761+ vec ! [ "sce" . to_string( ) , "auth" . to_string( ) , "--help" . to_string( ) ] ,
762+ || Ok ( ( ) ) ,
763+ & mut stdout,
764+ & mut stderr,
765+ ) ;
766+ assert_eq ! ( code, ExitCode :: SUCCESS ) ;
767+ assert ! ( stderr. is_empty( ) ) ;
768+
769+ let stdout = String :: from_utf8 ( stdout) . expect ( "stdout should be utf-8" ) ;
770+ assert ! ( stdout. contains( "Authenticate with `WorkOS` device authorization flow" ) ) ;
771+ assert ! ( stdout. contains( "Usage: auth <COMMAND>" ) ) ;
772+ assert ! ( stdout. contains( "sce auth login" ) ) ;
773+ }
774+
775+ #[ test]
776+ fn auth_login_help_writes_nested_auth_help_to_stdout ( ) {
777+ let mut stdout = Vec :: new ( ) ;
778+ let mut stderr = Vec :: new ( ) ;
779+ let code = run_with_dependency_check_and_streams (
780+ vec ! [
781+ "sce" . to_string( ) ,
782+ "auth" . to_string( ) ,
783+ "login" . to_string( ) ,
784+ "--help" . to_string( ) ,
785+ ] ,
786+ || Ok ( ( ) ) ,
787+ & mut stdout,
788+ & mut stderr,
789+ ) ;
790+ assert_eq ! ( code, ExitCode :: SUCCESS ) ;
791+ assert ! ( stderr. is_empty( ) ) ;
792+
793+ let stdout = String :: from_utf8 ( stdout) . expect ( "stdout should be utf-8" ) ;
794+ assert ! ( stdout. contains( "Start login flow and store credentials" ) ) ;
795+ assert ! ( stdout. contains( "--format <FORMAT>" ) ) ;
796+ }
797+
703798 #[ test]
704799 fn parse_failure_keeps_stdout_empty_and_reports_stderr ( ) {
705800 let mut stdout = Vec :: new ( ) ;
@@ -1121,6 +1216,20 @@ mod tests {
11211216 ) ;
11221217 }
11231218
1219+ #[ test]
1220+ fn parser_routes_bare_auth_to_auth_help_text ( ) {
1221+ let command = parse_command ( vec ! [ "sce" . to_string( ) , "auth" . to_string( ) ] )
1222+ . expect ( "bare auth should parse to help text" ) ;
1223+
1224+ match command {
1225+ Command :: HelpText { name, text } => {
1226+ assert_eq ! ( name, crate :: services:: auth_command:: NAME ) ;
1227+ assert ! ( text. contains( "Usage: auth <COMMAND>" ) ) ;
1228+ }
1229+ other => panic ! ( "expected auth help text, got {other:?}" ) ,
1230+ }
1231+ }
1232+
11241233 #[ test]
11251234 fn parser_routes_sync_json_format ( ) {
11261235 let command = parse_command ( vec ! [
0 commit comments