use std::sync::OnceLock;
use super::types::{AgentFingerprint, DetectionResult};
use super::vendors;
const VENDOR_SLICES: &[&[AgentFingerprint]] = &[
vendors::anthropic::FINGERPRINTS, vendors::openai::FINGERPRINTS, vendors::google::FINGERPRINTS,
vendors::cursor::FINGERPRINTS,
vendors::microsoft::FINGERPRINTS,
vendors::codeium::FINGERPRINTS,
vendors::others::FINGERPRINTS, ];
fn agent_db() -> &'static [AgentFingerprint] {
static DB: OnceLock<Vec<AgentFingerprint>> = OnceLock::new();
DB.get_or_init(|| {
VENDOR_SLICES.iter().flat_map(|s| s.iter().cloned()).collect()
})
}
pub fn get_agent_db() -> &'static [AgentFingerprint] {
agent_db()
}
pub fn matches_patterns(cmd: &str, patterns: &[&str]) -> bool {
patterns.iter().all(|p| p.split('|').any(|sub| cmd.contains(sub)))
}
pub fn is_agentwatch_self_command(cmd: &str) -> bool {
let Some(first) = cmd.split_whitespace().next() else {
return false;
};
let first = first.to_ascii_lowercase();
first == "agentwatch"
|| first == "agentwatch.exe"
|| first.contains("/agentwatch.app/")
|| first.ends_with("/agentwatch.app")
|| first.ends_with("/target/debug/agentwatch")
|| first.ends_with("/target/release/agentwatch")
|| first.ends_with("\\target\\debug\\agentwatch.exe")
|| first.ends_with("\\target\\release\\agentwatch.exe")
}
pub fn classify_process(cmd: &str) -> Option<DetectionResult> {
if is_agentwatch_self_command(cmd) {
return None;
}
agent_db().iter().find(|fp| {
matches_patterns(cmd, fp.requires)
&& !fp.excludes.iter().any(|e| cmd.contains(e))
}).map(|fp| {
let reasons: Vec<String> = fp.requires.iter()
.filter_map(|p| {
p.split('|')
.find(|sub| cmd.contains(sub))
.map(|s| format!("matched '{}'", s))
})
.collect();
DetectionResult {
agent_id: fp.id.to_string(),
name: fp.name.to_string(),
icon: fp.icon.to_string(),
vendor: fp.vendor.to_string(),
color: fp.color.to_string(),
role: fp.role,
host_app: fp.host_app.map(|s| s.to_string()),
confidence: fp.confidence,
match_reason: reasons,
adopt_children: fp.adopt_children,
}
})
}