Skip to main content

zenith_cli/commands/plugin/
detect.rs

1//! Detect which agents are present on the machine, and whether the Zenith skill
2//! is already installed for them.
3
4use std::path::{Path, PathBuf};
5
6use super::agent::{ALL_AGENTS, Agent, Scope};
7use super::paths::{home_dir, skill_target};
8
9/// True when the skill is already present for `(agent, scope)`.
10pub fn is_installed(agent: Agent, scope: Scope, project_root: &Path) -> bool {
11    skill_target(agent, scope, project_root)
12        .map(|t| t.root().exists())
13        .unwrap_or(false)
14}
15
16/// The directory whose existence signals that `agent` is in use, for a scope.
17/// Detection markers are intentionally the agent's *config* dir, which may
18/// differ from where the skill is written.
19pub fn marker_dir(agent: Agent, scope: Scope, project_root: &Path) -> Option<PathBuf> {
20    let (proj, user): (&str, fn(&Path) -> PathBuf) = match agent {
21        Agent::ClaudeCode => (".claude", |h| h.join(".claude")),
22        Agent::Codex => (".codex", |h| h.join(".codex")),
23        Agent::OpenCode => (".opencode", |h| h.join(".config").join("opencode")),
24        Agent::Cursor => (".cursor", |h| h.join(".cursor")),
25        Agent::Windsurf => (".windsurf", |h| h.join(".windsurf")),
26        Agent::Aider => (".aider", |h| h.join(".aider")),
27        Agent::Zed => (".zed", |h| h.join(".zed")),
28        Agent::Gemini => (".gemini", |h| h.join(".gemini")),
29        Agent::Copilot => (".copilot", |h| h.join(".copilot")),
30        Agent::Continue => (".continue", |h| h.join(".continue")),
31        Agent::Kiro => (".kiro", |h| h.join(".kiro")),
32        Agent::Antigravity => (".antigravity", |h| h.join(".antigravity")),
33    };
34    match scope {
35        Scope::Project => Some(project_root.join(proj)),
36        Scope::User => home_dir().map(|h| user(&h)),
37    }
38}
39
40/// True when `agent` appears to be in use in either the project or user scope.
41pub fn is_present(agent: Agent, project_root: &Path) -> bool {
42    [Scope::Project, Scope::User].into_iter().any(|scope| {
43        marker_dir(agent, scope, project_root)
44            .map(|d| d.exists())
45            .unwrap_or(false)
46    })
47}
48
49/// All agents detected as present, in stable order.
50pub fn detect_present(project_root: &Path) -> Vec<Agent> {
51    ALL_AGENTS
52        .iter()
53        .copied()
54        .filter(|a| is_present(*a, project_root))
55        .collect()
56}