@@ -372,7 +372,14 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
372372 model_flag_raw,
373373 permission_mode,
374374 output_format,
375- } => print_status_snapshot ( & model, model_flag_raw. as_deref ( ) , permission_mode, output_format) ?,
375+ allowed_tools,
376+ } => print_status_snapshot (
377+ & model,
378+ model_flag_raw. as_deref ( ) ,
379+ permission_mode,
380+ output_format,
381+ allowed_tools. as_ref ( ) ,
382+ ) ?,
376383 CliAction :: Sandbox { output_format } => print_sandbox_status_snapshot ( output_format) ?,
377384 CliAction :: Prompt {
378385 prompt,
@@ -510,6 +517,7 @@ enum CliAction {
510517 model_flag_raw : Option < String > ,
511518 permission_mode : PermissionMode ,
512519 output_format : CliOutputFormat ,
520+ allowed_tools : Option < AllowedToolSet > ,
513521 } ,
514522 Sandbox {
515523 output_format : CliOutputFormat ,
@@ -844,9 +852,14 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
844852 if let Some ( action) = parse_local_help_action ( & rest) {
845853 return action;
846854 }
847- if let Some ( action) =
848- parse_single_word_command_alias ( & rest, & model, model_flag_raw. as_deref ( ) , permission_mode_override, output_format)
849- {
855+ if let Some ( action) = parse_single_word_command_alias (
856+ & rest,
857+ & model,
858+ model_flag_raw. as_deref ( ) ,
859+ permission_mode_override,
860+ output_format,
861+ allowed_tools. clone ( ) ,
862+ ) {
850863 return action;
851864 }
852865
@@ -1051,6 +1064,7 @@ fn parse_single_word_command_alias(
10511064 model_flag_raw : Option < & str > ,
10521065 permission_mode_override : Option < PermissionMode > ,
10531066 output_format : CliOutputFormat ,
1067+ allowed_tools : Option < AllowedToolSet > ,
10541068) -> Option < Result < CliAction , String > > {
10551069 if rest. is_empty ( ) {
10561070 return None ;
@@ -1095,6 +1109,7 @@ fn parse_single_word_command_alias(
10951109 model_flag_raw : model_flag_raw. map ( str:: to_string) , // #148
10961110 permission_mode : permission_mode_override. unwrap_or_else ( default_permission_mode) ,
10971111 output_format,
1112+ allowed_tools,
10981113 } ) ) ,
10991114 "sandbox" => Some ( Ok ( CliAction :: Sandbox { output_format } ) ) ,
11001115 "doctor" => Some ( Ok ( CliAction :: Doctor { output_format } ) ) ,
@@ -3226,6 +3241,7 @@ fn run_resume_command(
32263241 default_permission_mode ( ) . as_str ( ) ,
32273242 & context,
32283243 None , // #148: resumed sessions don't have flag provenance
3244+ None ,
32293245 ) ) ,
32303246 } )
32313247 }
@@ -5417,6 +5433,7 @@ fn print_status_snapshot(
54175433 model_flag_raw : Option < & str > ,
54185434 permission_mode : PermissionMode ,
54195435 output_format : CliOutputFormat ,
5436+ allowed_tools : Option < & AllowedToolSet > ,
54205437) -> Result < ( ) , Box < dyn std:: error:: Error > > {
54215438 let usage = StatusUsage {
54225439 message_count : 0 ,
@@ -5450,6 +5467,7 @@ fn print_status_snapshot(
54505467 permission_mode. as_str( ) ,
54515468 & context,
54525469 Some ( & provenance) ,
5470+ allowed_tools,
54535471 ) ) ?
54545472 ) ,
54555473 }
@@ -5467,6 +5485,7 @@ fn status_json_value(
54675485 // that don't have provenance (legacy resume paths) pass None, in which
54685486 // case both new fields are omitted.
54695487 provenance : Option < & ModelProvenance > ,
5488+ allowed_tools : Option < & AllowedToolSet > ,
54705489) -> serde_json:: Value {
54715490 // #143: top-level `status` marker so claws can distinguish
54725491 // a clean run from a degraded run (config parse failed but other fields
@@ -5476,6 +5495,7 @@ fn status_json_value(
54765495 let degraded = context. config_load_error . is_some ( ) ;
54775496 let model_source = provenance. map ( |p| p. source . as_str ( ) ) ;
54785497 let model_raw = provenance. and_then ( |p| p. raw . clone ( ) ) ;
5498+ let allowed_tool_entries = allowed_tools. map ( |tools| tools. iter ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) ) ;
54795499 json ! ( {
54805500 "kind" : "status" ,
54815501 "status" : if degraded { "degraded" } else { "ok" } ,
@@ -5484,6 +5504,11 @@ fn status_json_value(
54845504 "model_source" : model_source,
54855505 "model_raw" : model_raw,
54865506 "permission_mode" : permission_mode,
5507+ "allowed_tools" : {
5508+ "source" : if allowed_tools. is_some( ) { "flag" } else { "default" } ,
5509+ "restricted" : allowed_tools. is_some( ) ,
5510+ "entries" : allowed_tool_entries,
5511+ } ,
54875512 "usage" : {
54885513 "messages" : usage. message_count,
54895514 "turns" : usage. turns,
@@ -9807,6 +9832,18 @@ mod tests {
98079832 assert ! ( error. contains( "unsupported tool in --allowedTools: teleport" ) ) ;
98089833 }
98099834
9835+ #[ test]
9836+ fn rejects_empty_allowed_tools_flag ( ) {
9837+ for raw in [ "" , ",," ] {
9838+ let error = parse_args ( & [ "--allowedTools" . to_string ( ) , raw. to_string ( ) ] )
9839+ . expect_err ( "empty allowedTools should be rejected" ) ;
9840+ assert ! (
9841+ error. contains( "--allowedTools was provided with no usable tool names" ) ,
9842+ "unexpected error for {raw:?}: {error}"
9843+ ) ;
9844+ }
9845+ }
9846+
98109847 #[ test]
98119848 fn parses_system_prompt_options ( ) {
98129849 let args = vec ! [
@@ -10261,7 +10298,14 @@ mod tests {
1026110298 cumulative : runtime:: TokenUsage :: default ( ) ,
1026210299 estimated_tokens : 0 ,
1026310300 } ;
10264- let json = super :: status_json_value ( Some ( "test-model" ) , usage, "workspace-write" , & context, None ) ;
10301+ let json = super :: status_json_value (
10302+ Some ( "test-model" ) ,
10303+ usage,
10304+ "workspace-write" ,
10305+ & context,
10306+ None ,
10307+ None ,
10308+ ) ;
1026510309 assert_eq ! (
1026610310 json. get( "status" ) . and_then( |v| v. as_str( ) ) ,
1026710311 Some ( "degraded" ) ,
@@ -10280,6 +10324,44 @@ mod tests {
1028010324 ) ;
1028110325 assert ! ( json. get( "workspace" ) . is_some( ) , "workspace field still reported" ) ;
1028210326 assert ! ( json. get( "sandbox" ) . is_some( ) , "sandbox field still reported" ) ;
10327+ assert_eq ! (
10328+ json. pointer( "/allowed_tools/source" ) . and_then( |v| v. as_str( ) ) ,
10329+ Some ( "default" ) ,
10330+ "default status should expose unrestricted tool source: {json}"
10331+ ) ;
10332+ assert_eq ! (
10333+ json. pointer( "/allowed_tools/restricted" ) . and_then( |v| v. as_bool( ) ) ,
10334+ Some ( false ) ,
10335+ "default status should expose unrestricted tool state: {json}"
10336+ ) ;
10337+
10338+ let allowed: super :: AllowedToolSet = [ "read_file" , "grep_search" ]
10339+ . into_iter ( )
10340+ . map ( str:: to_string)
10341+ . collect ( ) ;
10342+ let restricted_json = super :: status_json_value (
10343+ Some ( "test-model" ) ,
10344+ usage,
10345+ "workspace-write" ,
10346+ & context,
10347+ None ,
10348+ Some ( & allowed) ,
10349+ ) ;
10350+ assert_eq ! (
10351+ restricted_json
10352+ . pointer( "/allowed_tools/source" )
10353+ . and_then( |v| v. as_str( ) ) ,
10354+ Some ( "flag" ) ,
10355+ "flag status should expose allow-list source: {restricted_json}"
10356+ ) ;
10357+ assert_eq ! (
10358+ restricted_json
10359+ . pointer( "/allowed_tools/entries" )
10360+ . and_then( |v| v. as_array( ) )
10361+ . map( Vec :: len) ,
10362+ Some ( 2 ) ,
10363+ "flag status should expose allow-list entries: {restricted_json}"
10364+ ) ;
1028310365
1028410366 // Clean path: no config error → status: "ok", config_load_error: null.
1028510367 let clean_cwd = root. join ( "project-with-clean-config" ) ;
@@ -10288,8 +10370,14 @@ mod tests {
1028810370 super :: status_context ( None ) . expect ( "clean status_context should succeed" )
1028910371 } ) ;
1029010372 assert ! ( clean_context. config_load_error. is_none( ) ) ;
10291- let clean_json =
10292- super :: status_json_value ( Some ( "test-model" ) , usage, "workspace-write" , & clean_context, None ) ;
10373+ let clean_json = super :: status_json_value (
10374+ Some ( "test-model" ) ,
10375+ usage,
10376+ "workspace-write" ,
10377+ & clean_context,
10378+ None ,
10379+ None ,
10380+ ) ;
1029310381 assert_eq ! (
1029410382 clean_json. get( "status" ) . and_then( |v| v. as_str( ) ) ,
1029510383 Some ( "ok" ) ,
@@ -10366,6 +10454,7 @@ mod tests {
1036610454 model_flag_raw: None , // #148: no --model flag passed
1036710455 permission_mode: PermissionMode :: DangerFullAccess ,
1036810456 output_format: CliOutputFormat :: Text ,
10457+ allowed_tools: None ,
1036910458 }
1037010459 ) ;
1037110460 assert_eq ! (
0 commit comments