1use serde::{Deserialize, Serialize};
2use thiserror::Error;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct HookEvent {
6 pub session_id: String,
7 pub transcript_path: String,
8 pub cwd: String,
9 #[serde(default)]
10 pub permission_mode: Option<String>,
11 pub hook_event_name: String,
12 #[serde(default)]
13 pub tool_name: Option<String>,
14 #[serde(default)]
15 pub tool_input: Option<serde_json::Value>,
16 #[serde(default)]
17 pub tool_response: Option<serde_json::Value>,
18 #[serde(default)]
19 pub tool_use_id: Option<String>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct HookResponse {
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub r#continue: Option<bool>,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub suppress_output: Option<bool>,
28}
29
30impl HookResponse {
31 pub fn allow() -> Self {
32 Self {
33 r#continue: None,
34 suppress_output: Some(true),
35 }
36 }
37}
38
39#[derive(Debug, Error)]
40pub enum HookError {
41 #[error("Failed to parse hook event: {0}")]
42 ParseError(#[from] serde_json::Error),
43 #[error("IO error: {0}")]
44 IoError(#[from] std::io::Error),
45}
46
47pub fn parse_hook_event(json: &str) -> Result<HookEvent, HookError> {
48 Ok(serde_json::from_str(json)?)
49}
50
51impl HookEvent {
52 pub fn file_path(&self) -> Option<String> {
54 self.tool_input
55 .as_ref()?
56 .get("file_path")?
57 .as_str()
58 .map(String::from)
59 }
60
61 pub fn is_file_modification(&self) -> bool {
62 matches!(self.tool_name.as_deref(), Some("Write") | Some("Edit"))
63 }
64}