1use hh_core::AgentKind;
11use std::path::Path;
12
13#[must_use]
26pub fn detect_agent(command: &[String]) -> AgentKind {
27 let Some(first) = command.first() else {
28 return AgentKind::Generic;
29 };
30 let basename = Path::new(first)
31 .file_name()
32 .map(std::ffi::OsStr::to_string_lossy)
33 .unwrap_or_default();
34 let stem = basename
35 .strip_suffix(".exe")
36 .unwrap_or(&basename)
37 .to_ascii_lowercase();
38 match stem.as_str() {
39 "claude" => AgentKind::ClaudeCode,
40 "claude-desktop" => AgentKind::ClaudeDesktop,
41 "codex" => AgentKind::CodexCli,
42 "gemini" => AgentKind::GeminiCli,
43 _ => AgentKind::Generic,
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 #[test]
52 fn empty_command_is_generic() {
53 assert_eq!(detect_agent(&[]), AgentKind::Generic);
54 }
55
56 #[test]
57 fn claude_basename_detects_claude_code() {
58 assert_eq!(detect_agent(&["claude".into()]), AgentKind::ClaudeCode);
59 assert_eq!(
60 detect_agent(&["/usr/local/bin/claude".into()]),
61 AgentKind::ClaudeCode
62 );
63 assert_eq!(detect_agent(&["claude.exe".into()]), AgentKind::ClaudeCode);
65 }
66
67 #[test]
68 fn codex_basename_detects_codex_cli() {
69 assert_eq!(detect_agent(&["codex".into()]), AgentKind::CodexCli);
70 assert_eq!(
71 detect_agent(&["/usr/local/bin/codex".into()]),
72 AgentKind::CodexCli
73 );
74 assert_eq!(detect_agent(&["codex.exe".into()]), AgentKind::CodexCli);
75 }
76
77 #[test]
78 fn gemini_basename_detects_gemini_cli() {
79 assert_eq!(detect_agent(&["gemini".into()]), AgentKind::GeminiCli);
80 assert_eq!(
81 detect_agent(&["/usr/local/bin/gemini".into()]),
82 AgentKind::GeminiCli
83 );
84 assert_eq!(detect_agent(&["gemini.exe".into()]), AgentKind::GeminiCli);
85 }
86
87 #[test]
88 fn other_commands_are_generic() {
89 assert_eq!(detect_agent(&["python3".into()]), AgentKind::Generic);
90 assert_eq!(
91 detect_agent(&["npx".into(), "tsx".into()]),
92 AgentKind::Generic
93 );
94 }
95}