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 _ => {}
22 }
23 }
24
25 if std::env::var("CLAUDE_CODE_VERSION").is_ok() {
27 return CallerKind::ClaudeCode;
28 }
29 if std::env::var("CURSOR_SESSION_ID").is_ok() {
30 return CallerKind::Cursor;
31 }
32 if std::env::var("WINDSURF_SESSION_ID").is_ok() {
33 return CallerKind::Windsurf;
34 }
35 if std::env::var("CLINE_SESSION_ID").is_ok() {
36 return CallerKind::Cline;
37 }
38 if std::env::var("AMP_SESSION_ID").is_ok() {
39 return CallerKind::Amp;
40 }
41 if std::env::var("GEMINI_PROJECT_DIR").is_ok() {
42 return CallerKind::GeminiCli;
43 }
44
45 if let Some(obj) = stdin.as_object() {
47 let has = |key: &str| obj.contains_key(key);
48 let str_val = |key: &str| obj.get(key).and_then(|v| v.as_str()).unwrap_or("");
49
50 match str_val("hook_event_name") {
54 "BeforeTool"
55 | "AfterTool"
56 | "BeforeAgent"
57 | "AfterAgent"
58 | "BeforeModel"
59 | "AfterModel"
60 | "BeforeToolSelection"
61 | "PreCompress"
62 | "SessionStart"
63 | "SessionEnd" => return CallerKind::GeminiCli,
64 "pre_tool_call"
65 | "post_tool_call"
66 | "pre_llm_call"
67 | "on_session_start"
68 | "on_session_end"
69 | "on_session_finalize"
70 | "subagent_stop" => {
71 return CallerKind::Hermes;
72 }
73 _ => {}
74 }
75
76 if has("tool_name") && has("tool_input") {
77 return CallerKind::ClaudeCode;
78 }
79 if has("type") && has("toolCall") {
80 return CallerKind::Cursor;
81 }
82 if has("event") && has("parameters") {
83 return CallerKind::Windsurf;
84 }
85 if has("type") && has("toolName") && !has("toolCall") {
87 return CallerKind::Cline;
88 }
89 if has("kind") {
90 return CallerKind::Amp;
91 }
92 }
93
94 CallerKind::Unknown
95}
96
97#[cfg(test)]
102#[path = "detect_tests.rs"]
103mod tests;