const KNOWN_AGENTS: &[(&str, &str)] = &[
("claude", "claude.ai"),
("codex", "openai"),
("gemini", "google"),
];
pub fn agent_provider_for_command(command: &str) -> Option<&'static str> {
let lower = command.to_lowercase();
for &(agent, provider) in KNOWN_AGENTS {
for (i, _) in lower.match_indices(agent) {
let before_ok = i == 0
|| lower.as_bytes().get(i - 1).is_some_and(|&b| {
b == b' ' || b == b'/' || b == b'\t' || b == b'\n' || b == b';' || b == b'&'
});
let after = i + agent.len();
let after_ok = after >= lower.len()
|| lower.as_bytes().get(after).is_some_and(|&b| {
b == b' ' || b == b'\t' || b == b'\n' || b == b';' || b == b'&'
});
if before_ok && after_ok {
return Some(provider);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detects_claude_command() {
assert_eq!(
agent_provider_for_command("claude -p 'review code'"),
Some("claude.ai")
);
}
#[test]
fn test_detects_claude_with_path() {
assert_eq!(
agent_provider_for_command("/usr/local/bin/claude --help"),
Some("claude.ai")
);
}
#[test]
fn test_detects_codex_command() {
assert_eq!(
agent_provider_for_command("codex run tests"),
Some("openai")
);
}
#[test]
fn test_detects_gemini_command() {
assert_eq!(agent_provider_for_command("gemini chat"), Some("google"));
}
#[test]
fn test_no_agent_in_command() {
assert_eq!(agent_provider_for_command("cargo test --workspace"), None);
}
#[test]
fn test_no_partial_match() {
assert_eq!(agent_provider_for_command("claudette run"), None);
}
#[test]
fn test_agent_after_semicolon() {
assert_eq!(
agent_provider_for_command("cd /repo; claude -p 'fix'"),
Some("claude.ai")
);
}
#[test]
fn test_agent_after_ampersand() {
assert_eq!(
agent_provider_for_command("export FOO=1 && codex run"),
Some("openai")
);
}
#[test]
fn test_agent_at_end_of_command() {
assert_eq!(agent_provider_for_command("exec claude"), Some("claude.ai"));
}
#[test]
fn test_empty_command() {
assert_eq!(agent_provider_for_command(""), None);
}
#[test]
fn test_no_match_for_substring_codex() {
assert_eq!(agent_provider_for_command("mycodex run"), None);
}
#[test]
fn test_agent_with_pipe() {
assert_eq!(
agent_provider_for_command("echo foo | claude -p 'fix'"),
Some("claude.ai")
);
}
#[test]
fn test_agent_case_insensitive() {
assert_eq!(
agent_provider_for_command("Claude -p 'test'"),
Some("claude.ai")
);
assert_eq!(agent_provider_for_command("CODEX run"), Some("openai"));
assert_eq!(agent_provider_for_command("GEMINI chat"), Some("google"));
}
#[test]
fn test_agent_after_tab() {
assert_eq!(
agent_provider_for_command("cd /repo\tclaude -p 'fix'"),
Some("claude.ai")
);
}
#[test]
fn test_agent_after_newline() {
assert_eq!(
agent_provider_for_command("export FOO=1\nclaude -p 'fix'"),
Some("claude.ai")
);
}
#[test]
fn test_agent_gemini_at_end() {
assert_eq!(agent_provider_for_command("exec gemini"), Some("google"));
}
#[test]
fn test_no_match_agent_in_middle_of_word() {
assert_eq!(agent_provider_for_command("geminist run"), None);
}
}