@@ -1394,6 +1394,7 @@ def doctor_proxy(
13941394 )
13951395 for warning in warnings :
13961396 print (f"WARN: { warning } " , file = out )
1397+ _print_role_presets_discoverability_hint (out )
13971398 return 0
13981399 except ProxyCliError as exc :
13991400 if output_json :
@@ -2798,6 +2799,16 @@ def _with_path_args(parts: list[str]) -> str:
27982799 return doctor_cmd , client_cmd
27992800
28002801
2802+ _ROLE_PRESETS_DISCOVERABILITY_HINT = (
2803+ "Role presets: available. Inspect with "
2804+ "`agentveil-mcp-proxy role doctor --preset reviewer`."
2805+ )
2806+
2807+
2808+ def _print_role_presets_discoverability_hint (out : TextIO ) -> None :
2809+ print (_ROLE_PRESETS_DISCOVERABILITY_HINT , file = out )
2810+
2811+
28012812def _add_passphrase_args (parser : argparse .ArgumentParser ) -> None :
28022813 parser .add_argument ("--passphrase" , default = None , help = "MCP proxy identity passphrase" )
28032814 parser .add_argument ("--passphrase-file" , type = Path , default = None , help = "Read passphrase from file" )
@@ -3538,7 +3549,7 @@ def run_redirect_approval_demo_cli(
35383549 "\n "
35393550 "Advanced:\n "
35403551 " approval-center, client-config, client-doctor, client-run, configure-downstream,\n "
3541- " connect, control, disconnect, downstream, evidence-summary, explain, hook,\n "
3552+ " connect, control, disconnect, downstream, evidence-summary, explain, hook, role, \n "
35423553 " install-claude-hook, permission-doctor, reissue-grant, register, smoke,\n "
35433554 " status-claude-hook, templates, uninstall-claude-hook, wizard\n "
35443555 "\n "
@@ -4187,6 +4198,35 @@ def build_parser() -> argparse.ArgumentParser:
41874198 )
41884199 _add_json_arg (explain_role )
41894200
4201+ role = subparsers .add_parser (
4202+ "role" ,
4203+ help = "Inspect static role preset action-boundary behavior" ,
4204+ description = (
4205+ "Inspect what a role preset may read, block, or require approval for. "
4206+ "Roles map to action-boundary decisions; they are not inferred policy."
4207+ ),
4208+ formatter_class = argparse .RawDescriptionHelpFormatter ,
4209+ epilog = (
4210+ "Examples:\n "
4211+ " agentveil-mcp-proxy role doctor --preset reviewer\n "
4212+ " agentveil-mcp-proxy explain role --preset reviewer"
4213+ ),
4214+ )
4215+ role_subparsers = role .add_subparsers (dest = "role_action" , required = True )
4216+ role_doctor = role_subparsers .add_parser (
4217+ "doctor" ,
4218+ # claim-check: allow "blocked" is bounded role-doctor action-family status text.
4219+ help = "Show allowed, blocked, and approval-required action families by role preset" ,
4220+ )
4221+ _add_common_path_args (role_doctor )
4222+ role_doctor .add_argument (
4223+ "--preset" ,
4224+ choices = list (ROLE_PRESET_NAMES ),
4225+ default = None ,
4226+ help = "Inspect one preset; default reads role_preset from config or the preset set" ,
4227+ )
4228+ _add_json_arg (role_doctor )
4229+
41904230 templates = subparsers .add_parser (
41914231 "templates" ,
41924232 help = "Print copy-paste runnable starter commands for review/build/readonly agents" ,
@@ -6479,6 +6519,7 @@ def main(argv: list[str] | None = None) -> int:
64796519 )
64806520 print (f"Next: { doctor_cmd } " )
64816521 print (f" { client_cmd } " )
6522+ _print_role_presets_discoverability_hint (sys .stdout )
64826523 return 0
64836524 if args .command == "doctor" :
64846525 return doctor_proxy (
@@ -6784,6 +6825,15 @@ def main(argv: list[str] | None = None) -> int:
67846825 preset = args .preset ,
67856826 output_json = args .json_output ,
67866827 )
6828+ if args .command == "role" :
6829+ if args .role_action != "doctor" :
6830+ raise ProxyCliError ("role action must be doctor" )
6831+ return explain_role_proxy (
6832+ home = args .home ,
6833+ config_path = args .config ,
6834+ preset = args .preset ,
6835+ output_json = args .json_output ,
6836+ )
67876837 if args .command == "templates" :
67886838 if args .templates_action != "print" :
67896839 raise ProxyCliError ("templates action must be print" )
0 commit comments