@@ -297,7 +297,14 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
297297 model_flag_raw,
298298 permission_mode,
299299 output_format,
300- } => print_status_snapshot ( & model, model_flag_raw. as_deref ( ) , permission_mode, output_format) ?,
300+ allowed_tools,
301+ } => print_status_snapshot (
302+ & model,
303+ model_flag_raw. as_deref ( ) ,
304+ permission_mode,
305+ output_format,
306+ allowed_tools. as_ref ( ) ,
307+ ) ?,
301308 CliAction :: Sandbox { output_format } => print_sandbox_status_snapshot ( output_format) ?,
302309 CliAction :: Prompt {
303310 prompt,
@@ -446,6 +453,7 @@ enum CliAction {
446453 model_flag_raw : Option < String > ,
447454 permission_mode : PermissionMode ,
448455 output_format : CliOutputFormat ,
456+ allowed_tools : Option < AllowedToolSet > ,
449457 } ,
450458 Sandbox {
451459 output_format : CliOutputFormat ,
@@ -792,9 +800,14 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
792800 if let Some ( action) = parse_local_help_action ( & rest) {
793801 return action;
794802 }
795- if let Some ( action) =
796- parse_single_word_command_alias ( & rest, & model, model_flag_raw. as_deref ( ) , permission_mode_override, output_format)
797- {
803+ if let Some ( action) = parse_single_word_command_alias (
804+ & rest,
805+ & model,
806+ model_flag_raw. as_deref ( ) ,
807+ permission_mode_override,
808+ output_format,
809+ allowed_tools. clone ( ) ,
810+ ) {
798811 return action;
799812 }
800813
@@ -999,6 +1012,7 @@ fn parse_single_word_command_alias(
9991012 model_flag_raw : Option < & str > ,
10001013 permission_mode_override : Option < PermissionMode > ,
10011014 output_format : CliOutputFormat ,
1015+ allowed_tools : Option < AllowedToolSet > ,
10021016) -> Option < Result < CliAction , String > > {
10031017 if rest. is_empty ( ) {
10041018 return None ;
@@ -1043,6 +1057,7 @@ fn parse_single_word_command_alias(
10431057 model_flag_raw : model_flag_raw. map ( str:: to_string) , // #148
10441058 permission_mode : permission_mode_override. unwrap_or_else ( default_permission_mode) ,
10451059 output_format,
1060+ allowed_tools,
10461061 } ) ) ,
10471062 "sandbox" => Some ( Ok ( CliAction :: Sandbox { output_format } ) ) ,
10481063 "doctor" => Some ( Ok ( CliAction :: Doctor { output_format } ) ) ,
@@ -3430,6 +3445,7 @@ fn run_resume_command(
34303445 default_permission_mode ( ) . as_str ( ) ,
34313446 & context,
34323447 None , // #148: resumed sessions don't have flag provenance
3448+ None ,
34333449 ) ) ,
34343450 } )
34353451 }
@@ -5737,6 +5753,7 @@ fn print_status_snapshot(
57375753 model_flag_raw : Option < & str > ,
57385754 permission_mode : PermissionMode ,
57395755 output_format : CliOutputFormat ,
5756+ allowed_tools : Option < & AllowedToolSet > ,
57405757) -> Result < ( ) , Box < dyn std:: error:: Error > > {
57415758 let usage = StatusUsage {
57425759 message_count : 0 ,
@@ -5770,6 +5787,7 @@ fn print_status_snapshot(
57705787 permission_mode. as_str( ) ,
57715788 & context,
57725789 Some ( & provenance) ,
5790+ allowed_tools,
57735791 ) ) ?
57745792 ) ,
57755793 }
@@ -5787,6 +5805,7 @@ fn status_json_value(
57875805 // that don't have provenance (legacy resume paths) pass None, in which
57885806 // case both new fields are omitted.
57895807 provenance : Option < & ModelProvenance > ,
5808+ allowed_tools : Option < & AllowedToolSet > ,
57905809) -> serde_json:: Value {
57915810 // #143: top-level `status` marker so claws can distinguish
57925811 // a clean run from a degraded run (config parse failed but other fields
@@ -5796,6 +5815,7 @@ fn status_json_value(
57965815 let degraded = context. config_load_error . is_some ( ) ;
57975816 let model_source = provenance. map ( |p| p. source . as_str ( ) ) ;
57985817 let model_raw = provenance. and_then ( |p| p. raw . clone ( ) ) ;
5818+ let allowed_tool_entries = allowed_tools. map ( |tools| tools. iter ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) ) ;
57995819 json ! ( {
58005820 "kind" : "status" ,
58015821 "status" : if degraded { "degraded" } else { "ok" } ,
@@ -5804,6 +5824,11 @@ fn status_json_value(
58045824 "model_source" : model_source,
58055825 "model_raw" : model_raw,
58065826 "permission_mode" : permission_mode,
5827+ "allowed_tools" : {
5828+ "source" : if allowed_tools. is_some( ) { "flag" } else { "default" } ,
5829+ "restricted" : allowed_tools. is_some( ) ,
5830+ "entries" : allowed_tool_entries,
5831+ } ,
58075832 "usage" : {
58085833 "messages" : usage. message_count,
58095834 "turns" : usage. turns,
@@ -10143,6 +10168,18 @@ mod tests {
1014310168 assert ! ( error. contains( "unsupported tool in --allowedTools: teleport" ) ) ;
1014410169 }
1014510170
10171+ #[ test]
10172+ fn rejects_empty_allowed_tools_flag ( ) {
10173+ for raw in [ "" , ",," ] {
10174+ let error = parse_args ( & [ "--allowedTools" . to_string ( ) , raw. to_string ( ) ] )
10175+ . expect_err ( "empty allowedTools should be rejected" ) ;
10176+ assert ! (
10177+ error. contains( "--allowedTools was provided with no usable tool names" ) ,
10178+ "unexpected error for {raw:?}: {error}"
10179+ ) ;
10180+ }
10181+ }
10182+
1014610183 #[ test]
1014710184 fn parses_system_prompt_options ( ) {
1014810185 let args = vec ! [
@@ -10597,7 +10634,14 @@ mod tests {
1059710634 cumulative : runtime:: TokenUsage :: default ( ) ,
1059810635 estimated_tokens : 0 ,
1059910636 } ;
10600- let json = super :: status_json_value ( Some ( "test-model" ) , usage, "workspace-write" , & context, None ) ;
10637+ let json = super :: status_json_value (
10638+ Some ( "test-model" ) ,
10639+ usage,
10640+ "workspace-write" ,
10641+ & context,
10642+ None ,
10643+ None ,
10644+ ) ;
1060110645 assert_eq ! (
1060210646 json. get( "status" ) . and_then( |v| v. as_str( ) ) ,
1060310647 Some ( "degraded" ) ,
@@ -10616,6 +10660,44 @@ mod tests {
1061610660 ) ;
1061710661 assert ! ( json. get( "workspace" ) . is_some( ) , "workspace field still reported" ) ;
1061810662 assert ! ( json. get( "sandbox" ) . is_some( ) , "sandbox field still reported" ) ;
10663+ assert_eq ! (
10664+ json. pointer( "/allowed_tools/source" ) . and_then( |v| v. as_str( ) ) ,
10665+ Some ( "default" ) ,
10666+ "default status should expose unrestricted tool source: {json}"
10667+ ) ;
10668+ assert_eq ! (
10669+ json. pointer( "/allowed_tools/restricted" ) . and_then( |v| v. as_bool( ) ) ,
10670+ Some ( false ) ,
10671+ "default status should expose unrestricted tool state: {json}"
10672+ ) ;
10673+
10674+ let allowed: super :: AllowedToolSet = [ "read_file" , "grep_search" ]
10675+ . into_iter ( )
10676+ . map ( str:: to_string)
10677+ . collect ( ) ;
10678+ let restricted_json = super :: status_json_value (
10679+ Some ( "test-model" ) ,
10680+ usage,
10681+ "workspace-write" ,
10682+ & context,
10683+ None ,
10684+ Some ( & allowed) ,
10685+ ) ;
10686+ assert_eq ! (
10687+ restricted_json
10688+ . pointer( "/allowed_tools/source" )
10689+ . and_then( |v| v. as_str( ) ) ,
10690+ Some ( "flag" ) ,
10691+ "flag status should expose allow-list source: {restricted_json}"
10692+ ) ;
10693+ assert_eq ! (
10694+ restricted_json
10695+ . pointer( "/allowed_tools/entries" )
10696+ . and_then( |v| v. as_array( ) )
10697+ . map( Vec :: len) ,
10698+ Some ( 2 ) ,
10699+ "flag status should expose allow-list entries: {restricted_json}"
10700+ ) ;
1061910701
1062010702 // Clean path: no config error → status: "ok", config_load_error: null.
1062110703 let clean_cwd = root. join ( "project-with-clean-config" ) ;
@@ -10624,8 +10706,14 @@ mod tests {
1062410706 super :: status_context ( None ) . expect ( "clean status_context should succeed" )
1062510707 } ) ;
1062610708 assert ! ( clean_context. config_load_error. is_none( ) ) ;
10627- let clean_json =
10628- super :: status_json_value ( Some ( "test-model" ) , usage, "workspace-write" , & clean_context, None ) ;
10709+ let clean_json = super :: status_json_value (
10710+ Some ( "test-model" ) ,
10711+ usage,
10712+ "workspace-write" ,
10713+ & clean_context,
10714+ None ,
10715+ None ,
10716+ ) ;
1062910717 assert_eq ! (
1063010718 clean_json. get( "status" ) . and_then( |v| v. as_str( ) ) ,
1063110719 Some ( "ok" ) ,
@@ -10702,6 +10790,7 @@ mod tests {
1070210790 model_flag_raw: None , // #148: no --model flag passed
1070310791 permission_mode: PermissionMode :: DangerFullAccess ,
1070410792 output_format: CliOutputFormat :: Text ,
10793+ allowed_tools: None ,
1070510794 }
1070610795 ) ;
1070710796 assert_eq ! (
0 commit comments