My agentic slop goes here. Not intended for anyone else!
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))