My agentic slop goes here. Not intended for anyone else!
at main 9.2 kB view raw
1(** Claude Code Hooks System 2 3 Hooks allow you to intercept and control events in Claude Code sessions, 4 such as tool usage, prompt submission, and session stops. 5 6 {1 Overview} 7 8 Hooks are organized by event type, with each event having: 9 - A typed input structure (accessible via submodules) 10 - A typed output structure for responses 11 - Helper functions for common responses 12 13 {1 Example Usage} 14 15 {[ 16 open Eio.Std 17 18 (* Block dangerous bash commands *) 19 let get_string json key = 20 match json with 21 | Jsont.Object (members, _) -> 22 List.find_map (fun ((name, _), value) -> 23 if name = key then 24 match value with 25 | Jsont.String (s, _) -> Some s 26 | _ -> None 27 else None 28 ) members 29 | _ -> None 30 in 31 let block_rm_rf ~input ~tool_use_id:_ ~context:_ = 32 let hook = Hooks.PreToolUse.of_json input in 33 if Hooks.PreToolUse.tool_name hook = "Bash" then 34 let tool_input = Hooks.PreToolUse.tool_input hook in 35 match get_string tool_input "command" with 36 | Some cmd when String.contains cmd "rm -rf" -> 37 let output = Hooks.PreToolUse.deny ~reason:"Dangerous command" () in 38 Hooks.continue 39 ~hook_specific_output:(Hooks.PreToolUse.output_to_json output) 40 () 41 | _ -> Hooks.continue () 42 else Hooks.continue () 43 44 let hooks = 45 Hooks.empty 46 |> Hooks.add Hooks.Pre_tool_use [ 47 Hooks.matcher ~pattern:"Bash" [block_rm_rf] 48 ] 49 50 let options = Claude.Options.create ~hooks:(Some hooks) () in 51 let client = Claude.Client.create ~options ~sw ~process_mgr () in 52 ]} 53*) 54 55(** The log source for hooks *) 56val src : Logs.Src.t 57 58(** {1 Hook Events} *) 59 60(** Hook event types *) 61type event = 62 | Pre_tool_use (** Fires before a tool is executed *) 63 | Post_tool_use (** Fires after a tool completes *) 64 | User_prompt_submit (** Fires when user submits a prompt *) 65 | Stop (** Fires when conversation stops *) 66 | Subagent_stop (** Fires when a subagent stops *) 67 | Pre_compact (** Fires before message compaction *) 68 69val event_to_string : event -> string 70val event_of_string : string -> event 71val event_jsont : event Jsont.t 72 73(** {1 Context} *) 74 75module Context : sig 76 type t = { 77 signal: unit option; 78 unknown : Unknown.t; 79 } 80 81 val create : ?signal:unit option -> ?unknown:Unknown.t -> unit -> t 82 val signal : t -> unit option 83 val unknown : t -> Unknown.t 84 val jsont : t Jsont.t 85end 86 87(** {1 Decisions} *) 88 89type decision = 90 | Continue (** Allow the action to proceed *) 91 | Block (** Block the action *) 92 93val decision_jsont : decision Jsont.t 94 95(** {1 Generic Hook Result} *) 96 97(** Generic result structure for hooks *) 98type result = { 99 decision: decision option; 100 system_message: string option; 101 hook_specific_output: Jsont.json option; 102 unknown: Unknown.t; 103} 104 105val result_jsont : result Jsont.t 106 107(** {1 Typed Hook Modules} *) 108 109(** PreToolUse hook - fires before tool execution *) 110module PreToolUse : sig 111 (** Typed input for PreToolUse hooks *) 112 type input = { 113 session_id: string; 114 transcript_path: string; 115 tool_name: string; 116 tool_input: Jsont.json; 117 unknown: Unknown.t; 118 } 119 120 type t = input 121 122 (** Parse hook input from JSON *) 123 val of_json : Jsont.json -> t 124 125 (** {2 Accessors} *) 126 val session_id : t -> string 127 val transcript_path : t -> string 128 val tool_name : t -> string 129 val tool_input : t -> Jsont.json 130 val unknown : t -> Unknown.t 131 132 val input_jsont : input Jsont.t 133 134 (** Permission decision for tool usage *) 135 type permission_decision = [ `Allow | `Deny | `Ask ] 136 137 val permission_decision_jsont : permission_decision Jsont.t 138 139 (** Typed output for PreToolUse hooks *) 140 type output = { 141 permission_decision: permission_decision option; 142 permission_decision_reason: string option; 143 updated_input: Jsont.json option; 144 unknown: Unknown.t; 145 } 146 147 val output_jsont : output Jsont.t 148 149 (** {2 Response Builders} *) 150 val allow : ?reason:string -> ?updated_input:Jsont.json -> ?unknown:Unknown.t -> unit -> output 151 val deny : ?reason:string -> ?unknown:Unknown.t -> unit -> output 152 val ask : ?reason:string -> ?unknown:Unknown.t -> unit -> output 153 val continue : ?unknown:Unknown.t -> unit -> output 154 155 (** Convert output to JSON for hook_specific_output *) 156 val output_to_json : output -> Jsont.json 157end 158 159(** PostToolUse hook - fires after tool execution *) 160module PostToolUse : sig 161 type input = { 162 session_id: string; 163 transcript_path: string; 164 tool_name: string; 165 tool_input: Jsont.json; 166 tool_response: Jsont.json; 167 unknown: Unknown.t; 168 } 169 170 type t = input 171 172 val of_json : Jsont.json -> t 173 174 val session_id : t -> string 175 val transcript_path : t -> string 176 val tool_name : t -> string 177 val tool_input : t -> Jsont.json 178 val tool_response : t -> Jsont.json 179 val unknown : t -> Unknown.t 180 181 val input_jsont : input Jsont.t 182 183 type output = { 184 decision: decision option; 185 reason: string option; 186 additional_context: string option; 187 unknown: Unknown.t; 188 } 189 190 val output_jsont : output Jsont.t 191 192 val continue : ?additional_context:string -> ?unknown:Unknown.t -> unit -> output 193 val block : ?reason:string -> ?additional_context:string -> ?unknown:Unknown.t -> unit -> output 194 val output_to_json : output -> Jsont.json 195end 196 197(** UserPromptSubmit hook - fires when user submits a prompt *) 198module UserPromptSubmit : sig 199 type input = { 200 session_id: string; 201 transcript_path: string; 202 prompt: string; 203 unknown: Unknown.t; 204 } 205 206 type t = input 207 208 val of_json : Jsont.json -> t 209 210 val session_id : t -> string 211 val transcript_path : t -> string 212 val prompt : t -> string 213 val unknown : t -> Unknown.t 214 215 val input_jsont : input Jsont.t 216 217 type output = { 218 decision: decision option; 219 reason: string option; 220 additional_context: string option; 221 unknown: Unknown.t; 222 } 223 224 val output_jsont : output Jsont.t 225 226 val continue : ?additional_context:string -> ?unknown:Unknown.t -> unit -> output 227 val block : ?reason:string -> ?unknown:Unknown.t -> unit -> output 228 val output_to_json : output -> Jsont.json 229end 230 231(** Stop hook - fires when conversation stops *) 232module Stop : sig 233 type input = { 234 session_id: string; 235 transcript_path: string; 236 stop_hook_active: bool; 237 unknown: Unknown.t; 238 } 239 240 type t = input 241 242 val of_json : Jsont.json -> t 243 244 val session_id : t -> string 245 val transcript_path : t -> string 246 val stop_hook_active : t -> bool 247 val unknown : t -> Unknown.t 248 249 val input_jsont : input Jsont.t 250 251 type output = { 252 decision: decision option; 253 reason: string option; 254 unknown: Unknown.t; 255 } 256 257 val output_jsont : output Jsont.t 258 259 val continue : ?unknown:Unknown.t -> unit -> output 260 val block : ?reason:string -> ?unknown:Unknown.t -> unit -> output 261 val output_to_json : output -> Jsont.json 262end 263 264(** SubagentStop hook - fires when a subagent stops *) 265module SubagentStop : sig 266 include module type of Stop 267 val of_json : Jsont.json -> t 268end 269 270(** PreCompact hook - fires before message compaction *) 271module PreCompact : sig 272 type input = { 273 session_id: string; 274 transcript_path: string; 275 unknown: Unknown.t; 276 } 277 278 type t = input 279 280 type output = unit 281 282 val of_json : Jsont.json -> t 283 284 val session_id : t -> string 285 val transcript_path : t -> string 286 val unknown : t -> Unknown.t 287 288 val input_jsont : input Jsont.t 289 290 val continue : unit -> output 291 val output_to_json : output -> Jsont.json 292end 293 294(** {1 Callbacks} *) 295 296(** Generic callback function type. 297 298 Callbacks receive: 299 - [input]: Raw JSON input (parse with [PreToolUse.of_json], etc.) 300 - [tool_use_id]: Optional tool use ID 301 - [context]: Hook context 302 303 And return a generic [result] with optional hook-specific output. 304*) 305type callback = 306 input:Jsont.json -> 307 tool_use_id:string option -> 308 context:Context.t -> 309 result 310 311(** {1 Matchers} *) 312 313(** A matcher configuration *) 314type matcher = { 315 matcher: string option; (** Pattern to match (e.g., "Bash" or "Write|Edit") *) 316 callbacks: callback list; (** Callbacks to invoke on match *) 317} 318 319(** Hook configuration: map from events to matchers *) 320type config = (event * matcher list) list 321 322(** {1 Generic Result Builders} *) 323 324(** [continue ?system_message ?hook_specific_output ?unknown ()] creates a continue result *) 325val continue : ?system_message:string -> ?hook_specific_output:Jsont.json -> ?unknown:Unknown.t -> unit -> result 326 327(** [block ?system_message ?hook_specific_output ?unknown ()] creates a block result *) 328val block : ?system_message:string -> ?hook_specific_output:Jsont.json -> ?unknown:Unknown.t -> unit -> result 329 330(** {1 Configuration Builders} *) 331 332(** [matcher ?pattern callbacks] creates a matcher *) 333val matcher : ?pattern:string -> callback list -> matcher 334 335(** Empty hooks configuration *) 336val empty : config 337 338(** [add event matchers config] adds matchers for an event *) 339val add : event -> matcher list -> config -> config 340 341(** {1 JSON Serialization} *) 342 343val result_to_json : result -> Jsont.json 344val config_to_protocol_format : config -> Jsont.json