1use crate::types::CallerKind;
2
3pub fn detect_caller(stdin: &serde_json::Value) -> CallerKind {
11 if let Ok(val) = std::env::var("POLYHOOK_CALLER") {
13 match val.to_lowercase().as_str() {
14 "claude-code" | "claudecode" => return CallerKind::ClaudeCode,
15 "cursor" => return CallerKind::Cursor,
16 "windsurf" => return CallerKind::Windsurf,
17 "cline" => return CallerKind::Cline,
18 "amp" => return CallerKind::Amp,
19 "gemini-cli" | "geminicli" => return CallerKind::GeminiCli,
20 "hermes" | "hermes-agent" | "hermesagent" => return CallerKind::Hermes,
21 "pi" => return CallerKind::Pi,
22 "codex" => return CallerKind::Codex,
23 _ => {}
24 }
25 }
26
27 if std::env::var("CLAUDE_CODE_VERSION").is_ok() {
29 return CallerKind::ClaudeCode;
30 }
31 if std::env::var("CURSOR_SESSION_ID").is_ok() {
32 return CallerKind::Cursor;
33 }
34 if std::env::var("WINDSURF_SESSION_ID").is_ok() {
35 return CallerKind::Windsurf;
36 }
37 if std::env::var("CLINE_SESSION_ID").is_ok() {
38 return CallerKind::Cline;
39 }
40 if std::env::var("AMP_SESSION_ID").is_ok() {
41 return CallerKind::Amp;
42 }
43 if std::env::var("GEMINI_PROJECT_DIR").is_ok() {
44 return CallerKind::GeminiCli;
45 }
46
47 if let Some(obj) = stdin.as_object() {
49 let has = |key: &str| obj.contains_key(key);
50 let str_val = |key: &str| obj.get(key).and_then(|v| v.as_str()).unwrap_or("");
51
52 match str_val("hook_event_name") {
56 "BeforeTool"
57 | "AfterTool"
58 | "BeforeAgent"
59 | "AfterAgent"
60 | "BeforeModel"
61 | "AfterModel"
62 | "BeforeToolSelection"
63 | "PreCompress"
64 | "SessionStart"
65 | "SessionEnd" => return CallerKind::GeminiCli,
66 "pre_tool_call"
67 | "post_tool_call"
68 | "pre_llm_call"
69 | "on_session_start"
70 | "on_session_end"
71 | "on_session_finalize"
72 | "subagent_stop" => {
73 return CallerKind::Hermes;
74 }
75 _ => {}
76 }
77
78 if has("hookName") && has("clineVersion") {
81 return CallerKind::Cline;
82 }
83
84 if has("hook_event_name") && has("turn_id") {
87 return CallerKind::Codex;
88 }
89
90 if str_val("hook_event_name") == "UserPromptSubmit" {
92 return CallerKind::ClaudeCode;
93 }
94
95 if has("tool_name") && has("tool_input") {
96 return CallerKind::ClaudeCode;
97 }
98 if has("type") && has("toolCall") {
99 return CallerKind::Cursor;
100 }
101 if has("event") && has("parameters") {
102 return CallerKind::Windsurf;
103 }
104 if has("type") && has("toolName") && !has("toolCall") {
106 return CallerKind::Cline;
107 }
108 if has("kind") {
109 return CallerKind::Amp;
110 }
111 }
112
113 CallerKind::Unknown
114}
115
116#[cfg(test)]
121#[path = "detect_tests.rs"]
122mod tests;