OCaml Claude SDK using Eio and Jsont
at main 123 lines 4.3 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6open Eio.Std 7 8let src = Logs.Src.create "hooks_example" ~doc:"Hooks example" 9 10module Log = (val Logs.src_log src : Logs.LOG) 11 12(* Example 1: Block dangerous bash commands *) 13let block_dangerous_bash input = 14 if input.Claude.Hooks.PreToolUse.tool_name = "Bash" then 15 match 16 Claude.Tool_input.get_string input.Claude.Hooks.PreToolUse.tool_input 17 "command" 18 with 19 | Some command -> 20 if String.length command >= 6 && String.sub command 0 6 = "rm -rf" then begin 21 Log.app (fun m -> m "🚫 Blocked dangerous command: %s" command); 22 Claude.Hooks.PreToolUse.deny 23 ~reason:"Command contains dangerous 'rm -rf' pattern" () 24 end 25 else Claude.Hooks.PreToolUse.continue () 26 | _ -> Claude.Hooks.PreToolUse.continue () 27 else Claude.Hooks.PreToolUse.continue () 28 29(* Example 2: Log all tool usage *) 30let log_tool_usage input = 31 Log.app (fun m -> 32 m "📝 Tool %s called" input.Claude.Hooks.PreToolUse.tool_name); 33 Claude.Hooks.PreToolUse.continue () 34 35let run_example ~sw ~env = 36 Log.app (fun m -> m "🔧 Hooks System Example"); 37 Log.app (fun m -> m "====================\n"); 38 39 (* Configure hooks *) 40 let hooks = 41 Claude.Hooks.empty 42 |> Claude.Hooks.on_pre_tool_use log_tool_usage 43 |> Claude.Hooks.on_pre_tool_use ~pattern:"Bash" block_dangerous_bash 44 in 45 46 let options = 47 Claude.Options.default 48 |> Claude.Options.with_model (Claude.Model.of_string "sonnet") 49 |> Claude.Options.with_hooks hooks 50 in 51 52 let client = 53 Claude.Client.create ~options ~sw ~process_mgr:env#process_mgr 54 ~clock:env#clock () 55 in 56 57 (* Test 1: Safe command (should work) *) 58 Log.app (fun m -> m "Test 1: Safe bash command"); 59 Claude.Client.query client "Run the bash command: echo 'Hello from hooks!'"; 60 61 let messages = Claude.Client.receive_all client in 62 List.iter 63 (fun resp -> 64 match resp with 65 | Claude.Response.Text text -> 66 let content = Claude.Response.Text.content text in 67 if String.length content > 0 then 68 Log.app (fun m -> m "Claude: %s" content) 69 | Claude.Response.Complete _ -> Log.app (fun m -> m "✅ Test 1 complete\n") 70 | Claude.Response.Error err -> 71 Log.err (fun m -> m "❌ Error: %s" (Claude.Response.Error.message err)) 72 | _ -> ()) 73 messages; 74 75 (* Test 2: Dangerous command (should be blocked) *) 76 Log.app (fun m -> m "Test 2: Dangerous bash command (should be blocked)"); 77 Claude.Client.query client "Run the bash command: rm -rf /tmp/test"; 78 79 let messages = Claude.Client.receive_all client in 80 List.iter 81 (fun resp -> 82 match resp with 83 | Claude.Response.Text text -> 84 let content = Claude.Response.Text.content text in 85 if String.length content > 0 then 86 Log.app (fun m -> m "Claude: %s" content) 87 | Claude.Response.Complete _ -> Log.app (fun m -> m "✅ Test 2 complete") 88 | Claude.Response.Error err -> 89 Log.err (fun m -> m "❌ Error: %s" (Claude.Response.Error.message err)) 90 | _ -> ()) 91 messages; 92 93 Log.app (fun m -> m "\n===================="); 94 Log.app (fun m -> m "✨ Example complete!") 95 96let main ~env = Switch.run @@ fun sw -> run_example ~sw ~env 97 98(* Command-line interface *) 99open Cmdliner 100 101let main_term env = 102 let setup_log style_renderer level = 103 Fmt_tty.setup_std_outputs ?style_renderer (); 104 Logs.set_level level; 105 Logs.set_reporter (Logs_fmt.reporter ()); 106 if level = None then Logs.set_level (Some Logs.App); 107 match level with 108 | Some Logs.Info | Some Logs.Debug -> 109 Logs.Src.set_level Claude.Client.src (Some Logs.Info) 110 | _ -> () 111 in 112 let run style level = 113 setup_log style level; 114 main ~env 115 in 116 Term.(const run $ Fmt_cli.style_renderer () $ Logs_cli.level ()) 117 118let cmd env = 119 let doc = "Demonstrate Claude's hooks system" in 120 let info = Cmd.info "hooks_example" ~version:"1.0" ~doc in 121 Cmd.v info (main_term env) 122 123let () = Eio_main.run @@ fun env -> exit (Cmd.eval (cmd env))