My agentic slop goes here. Not intended for anyone else!
at main 4.8 kB view raw
1open Eio.Std 2 3let src = Logs.Src.create "hooks_example" ~doc:"Hooks example" 4module Log = (val Logs.src_log src : Logs.LOG) 5 6(* Example 1: Block dangerous bash commands *) 7let block_dangerous_bash ~input ~tool_use_id:_ ~context:_ = 8 let hook = Claude.Hooks.PreToolUse.of_json input in 9 let tool_name = Claude.Hooks.PreToolUse.tool_name hook in 10 11 if tool_name = "Bash" then 12 let tool_input = Claude.Hooks.PreToolUse.tool_input hook in 13 match Test_json_utils.get_string tool_input "command" with 14 | Some command -> 15 if String.length command >= 6 && String.sub command 0 6 = "rm -rf" then begin 16 Log.app (fun m -> m "🚫 Blocked dangerous command: %s" command); 17 let output = Claude.Hooks.PreToolUse.deny 18 ~reason:"Command contains dangerous 'rm -rf' pattern" () in 19 Claude.Hooks.continue 20 ~system_message:"Blocked dangerous rm -rf command" 21 ~hook_specific_output:(Claude.Hooks.PreToolUse.output_to_json output) 22 () 23 end else 24 Claude.Hooks.continue () 25 | _ -> 26 Claude.Hooks.continue () 27 else 28 Claude.Hooks.continue () 29 30(* Example 2: Log all tool usage *) 31let log_tool_usage ~input ~tool_use_id ~context:_ = 32 let hook = Claude.Hooks.PreToolUse.of_json input in 33 let tool_name = Claude.Hooks.PreToolUse.tool_name hook in 34 let tool_use_id_str = Option.value tool_use_id ~default:"<none>" in 35 Log.app (fun m -> m "📝 Tool %s called (ID: %s)" tool_name tool_use_id_str); 36 Claude.Hooks.continue () 37 38let run_example ~sw ~env = 39 Log.app (fun m -> m "🔧 Hooks System Example"); 40 Log.app (fun m -> m "====================\n"); 41 42 (* Configure hooks *) 43 let hooks = 44 Claude.Hooks.empty 45 |> Claude.Hooks.add Claude.Hooks.Pre_tool_use [ 46 (* Log all tool usage *) 47 Claude.Hooks.matcher [log_tool_usage]; 48 (* Block dangerous bash commands *) 49 Claude.Hooks.matcher ~pattern:"Bash" [block_dangerous_bash]; 50 ] 51 in 52 53 let options = Claude.Options.create 54 ~model:(Claude.Model.of_string "sonnet") 55 ~hooks 56 () in 57 58 let client = Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr () in 59 60 (* Test 1: Safe command (should work) *) 61 Log.app (fun m -> m "Test 1: Safe bash command"); 62 Claude.Client.query client "Run the bash command: echo 'Hello from hooks!'"; 63 64 let messages = Claude.Client.receive_all client in 65 List.iter (fun msg -> 66 match msg with 67 | Claude.Message.Assistant msg -> 68 List.iter (function 69 | Claude.Content_block.Text t -> 70 let text = Claude.Content_block.Text.text t in 71 if String.length text > 0 then 72 Log.app (fun m -> m "Claude: %s" text) 73 | _ -> () 74 ) (Claude.Message.Assistant.content msg) 75 | Claude.Message.Result msg -> 76 if Claude.Message.Result.is_error msg then 77 Log.err (fun m -> m "❌ Error!") 78 else 79 Log.app (fun m -> m "✅ Test 1 complete\n") 80 | _ -> () 81 ) messages; 82 83 (* Test 2: Dangerous command (should be blocked) *) 84 Log.app (fun m -> m "Test 2: Dangerous bash command (should be blocked)"); 85 Claude.Client.query client "Run the bash command: rm -rf /tmp/test"; 86 87 let messages = Claude.Client.receive_all client in 88 List.iter (fun msg -> 89 match msg with 90 | Claude.Message.Assistant msg -> 91 List.iter (function 92 | Claude.Content_block.Text t -> 93 let text = Claude.Content_block.Text.text t in 94 if String.length text > 0 then 95 Log.app (fun m -> m "Claude: %s" text) 96 | _ -> () 97 ) (Claude.Message.Assistant.content msg) 98 | Claude.Message.Result msg -> 99 if Claude.Message.Result.is_error msg then 100 Log.err (fun m -> m "❌ Error!") 101 else 102 Log.app (fun m -> m "✅ Test 2 complete") 103 | _ -> () 104 ) messages; 105 106 Log.app (fun m -> m "\n===================="); 107 Log.app (fun m -> m "✨ Example complete!") 108 109let main ~env = 110 Switch.run @@ fun sw -> 111 run_example ~sw ~env 112 113(* Command-line interface *) 114open Cmdliner 115 116let main_term env = 117 let setup_log style_renderer level = 118 Fmt_tty.setup_std_outputs ?style_renderer (); 119 Logs.set_level level; 120 Logs.set_reporter (Logs_fmt.reporter ()); 121 if level = None then Logs.set_level (Some Logs.App); 122 match level with 123 | Some Logs.Info | Some Logs.Debug -> 124 Logs.Src.set_level Claude.Client.src (Some Logs.Info) 125 | _ -> () 126 in 127 let run style level = 128 setup_log style level; 129 main ~env 130 in 131 Term.(const run $ Fmt_cli.style_renderer () $ Logs_cli.level ()) 132 133let cmd env = 134 let doc = "Demonstrate Claude's hooks system" in 135 let info = Cmd.info "hooks_example" ~version:"1.0" ~doc in 136 Cmd.v info (main_term env) 137 138let () = 139 Eio_main.run @@ fun env -> 140 exit (Cmd.eval (cmd env))