#[cfg(any(feature = "claude", feature = "copilot-cli"))]
use std::ffi::OsString;
#[cfg(any(feature = "claude", feature = "copilot-cli"))]
use std::path::PathBuf;
use crate::doctor::DoctorReport;
#[cfg(any(feature = "claude", feature = "copilot-cli"))]
use crate::error::Error;
use crate::error::Result;
use crate::host::{Capabilities, Desired, Outcome, Plugin, Scope, Source};
#[cfg(feature = "claude")]
pub(crate) mod claude;
#[cfg(feature = "codex")]
pub(crate) mod codex;
#[cfg(feature = "codex")]
pub(crate) mod mcptoml;
#[cfg(feature = "amp")]
pub(crate) mod amp;
#[cfg(feature = "antigravity")]
pub(crate) mod antigravity;
#[cfg(feature = "antigravity-cli")]
pub(crate) mod antigravity_cli;
#[cfg(feature = "augment")]
pub(crate) mod augment;
#[cfg(feature = "cline")]
pub(crate) mod cline;
#[cfg(feature = "copilot-cli")]
pub(crate) mod copilot_cli;
#[cfg(feature = "crush")]
pub(crate) mod crush;
#[cfg(feature = "cursor")]
pub(crate) mod cursor;
#[cfg(feature = "devin")]
pub(crate) mod devin;
#[cfg(feature = "droid")]
pub(crate) mod droid;
#[cfg(feature = "gemini")]
pub(crate) mod gemini;
#[cfg(feature = "goose")]
pub(crate) mod goose;
#[cfg(feature = "jetbrains-copilot")]
pub(crate) mod jetbrains_copilot;
#[cfg(feature = "kilo")]
pub(crate) mod kilo;
#[cfg(feature = "kimi")]
pub(crate) mod kimi;
#[cfg(feature = "kiro")]
pub(crate) mod kiro;
#[cfg(feature = "omp")]
pub(crate) mod omp;
#[cfg(feature = "openclaw")]
pub(crate) mod openclaw;
#[cfg(feature = "opencode")]
pub(crate) mod opencode;
#[cfg(feature = "pi")]
pub(crate) mod pi;
#[cfg(feature = "qwen-code")]
pub(crate) mod qwen_code;
#[cfg(feature = "vscode-copilot")]
pub(crate) mod vscode_copilot;
#[cfg(feature = "zed")]
pub(crate) mod zed;
macro_rules! cfg_config_backends {
($item:item) => {
#[cfg(any(
feature = "codex",
feature = "opencode",
feature = "gemini",
feature = "cursor",
feature = "cline",
feature = "devin",
feature = "qwen-code",
feature = "vscode-copilot",
feature = "jetbrains-copilot",
feature = "kimi",
feature = "kiro",
feature = "zed",
feature = "omp",
feature = "openclaw",
feature = "kilo",
feature = "antigravity",
feature = "antigravity-cli",
feature = "goose",
feature = "amp",
feature = "crush",
feature = "droid",
feature = "augment",
))]
$item
};
}
#[cfg(any(feature = "claude", feature = "copilot-cli"))]
pub(crate) fn non_empty_config_dir(var: &'static str, value: Option<OsString>) -> Result<Option<PathBuf>> {
match value {
None => Ok(None),
Some(v) if v.is_empty() => Err(Error::EmptyConfigDirOverride { var }),
Some(v) => Ok(Some(PathBuf::from(v))),
}
}
#[cfg(any(feature = "claude", feature = "copilot-cli"))]
pub(crate) fn config_dir_override(var: &'static str) -> Result<Option<PathBuf>> {
non_empty_config_dir(var, std::env::var_os(var))
}
#[cfg(any(feature = "claude", feature = "copilot-cli"))]
#[allow(dead_code)]
pub(crate) mod confedit;
cfg_config_backends! {
#[cfg(not(any(feature = "claude", feature = "copilot-cli")))]
#[allow(dead_code)]
pub(crate) mod confedit;
}
macro_rules! cfg_statusline_backends {
($item:item) => {
#[cfg(any(feature = "claude", feature = "qwen-code", feature = "antigravity-cli", feature = "droid", feature = "copilot-cli"))]
$item
};
}
pub(crate) use cfg_statusline_backends;
cfg_statusline_backends! {
#[allow(dead_code)]
pub(crate) mod statuslinejson;
}
cfg_config_backends! {
#[allow(dead_code)]
pub(crate) mod mcpjson;
}
cfg_config_backends! {
#[allow(dead_code)]
pub(crate) mod cchooks;
}
cfg_config_backends! {
#[allow(dead_code)]
pub(crate) mod report;
}
cfg_config_backends! {
#[allow(dead_code)]
pub(crate) mod skillsdir;
}
#[cfg(any(feature = "omp", feature = "cursor"))]
pub(crate) mod ccregistry;
pub enum BackendState {
Absent,
Healthy,
Disabled,
NeedsRepair,
}
pub trait AgentBackend {
fn id(&self) -> &'static str;
fn detect(&self) -> bool;
fn capabilities(&self) -> Capabilities;
fn probe(&self, plugin: &Plugin, scope: &Scope, source: &Source) -> Result<BackendState>;
fn reconcile(&self, plugin: &Plugin, desired: &Desired, scope: &Scope) -> Result<Outcome>;
fn remove(&self, plugin: &Plugin, scope: &Scope, source: &Source) -> Result<Outcome>;
fn forget(&self, _plugin: &Plugin, _scope: &Scope) -> Result<()> {
Ok(())
}
fn report(&self, plugin: &Plugin, source: &Source) -> DoctorReport;
}
pub const AGENT_IDS: &[&str] = &[
#[cfg(feature = "claude")]
"claude",
#[cfg(feature = "codex")]
"codex",
#[cfg(feature = "opencode")]
"opencode",
#[cfg(feature = "gemini")]
"gemini",
#[cfg(feature = "cursor")]
"cursor",
#[cfg(feature = "cline")]
"cline",
#[cfg(feature = "devin")]
"devin",
#[cfg(feature = "qwen-code")]
"qwen-code",
#[cfg(feature = "copilot-cli")]
"copilot-cli",
#[cfg(feature = "vscode-copilot")]
"vscode-copilot",
#[cfg(feature = "jetbrains-copilot")]
"jetbrains-copilot",
#[cfg(feature = "kimi")]
"kimi",
#[cfg(feature = "kiro")]
"kiro",
#[cfg(feature = "zed")]
"zed",
#[cfg(feature = "omp")]
"omp",
#[cfg(feature = "openclaw")]
"openclaw",
#[cfg(feature = "kilo")]
"kilo",
#[cfg(feature = "antigravity")]
"antigravity",
#[cfg(feature = "antigravity-cli")]
"antigravity-cli",
#[cfg(feature = "pi")]
"pi",
#[cfg(feature = "goose")]
"goose",
#[cfg(feature = "amp")]
"amp",
#[cfg(feature = "crush")]
"crush",
#[cfg(feature = "droid")]
"droid",
#[cfg(feature = "augment")]
"augment",
];
pub fn backend_for(id: &str) -> Option<Box<dyn AgentBackend>> {
match id {
#[cfg(feature = "claude")]
"claude" => Some(Box::new(claude::ClaudeBackend)),
#[cfg(feature = "codex")]
"codex" => Some(Box::new(codex::CodexBackend)),
#[cfg(feature = "opencode")]
"opencode" => Some(Box::new(opencode::OpencodeBackend)),
#[cfg(feature = "gemini")]
"gemini" => Some(Box::new(gemini::GeminiBackend)),
#[cfg(feature = "cursor")]
"cursor" => Some(Box::new(cursor::CursorBackend)),
#[cfg(feature = "cline")]
"cline" => Some(Box::new(cline::ClineBackend)),
#[cfg(feature = "devin")]
"devin" => Some(Box::new(devin::DevinBackend)),
#[cfg(feature = "qwen-code")]
"qwen-code" => Some(Box::new(qwen_code::QwenCodeBackend)),
#[cfg(feature = "copilot-cli")]
"copilot-cli" => Some(Box::new(copilot_cli::CopilotCliBackend)),
#[cfg(feature = "vscode-copilot")]
"vscode-copilot" => Some(Box::new(vscode_copilot::VscodeCopilotBackend)),
#[cfg(feature = "jetbrains-copilot")]
"jetbrains-copilot" => Some(Box::new(jetbrains_copilot::JetbrainsCopilotBackend)),
#[cfg(feature = "kimi")]
"kimi" => Some(Box::new(kimi::KimiBackend)),
#[cfg(feature = "kiro")]
"kiro" => Some(Box::new(kiro::KiroBackend)),
#[cfg(feature = "zed")]
"zed" => Some(Box::new(zed::ZedBackend)),
#[cfg(feature = "omp")]
"omp" => Some(Box::new(omp::OmpBackend)),
#[cfg(feature = "openclaw")]
"openclaw" => Some(Box::new(openclaw::OpenclawBackend)),
#[cfg(feature = "kilo")]
"kilo" => Some(Box::new(kilo::KiloBackend)),
#[cfg(feature = "antigravity")]
"antigravity" => Some(Box::new(antigravity::AntigravityBackend)),
#[cfg(feature = "antigravity-cli")]
"antigravity-cli" => Some(Box::new(antigravity_cli::AntigravityCliBackend)),
#[cfg(feature = "pi")]
"pi" => Some(Box::new(pi::PiBackend)),
#[cfg(feature = "goose")]
"goose" => Some(Box::new(goose::GooseBackend)),
#[cfg(feature = "amp")]
"amp" => Some(Box::new(amp::AmpBackend)),
#[cfg(feature = "crush")]
"crush" => Some(Box::new(crush::CrushBackend)),
#[cfg(feature = "droid")]
"droid" => Some(Box::new(droid::DroidBackend)),
#[cfg(feature = "augment")]
"augment" => Some(Box::new(augment::AugmentBackend)),
_ => None,
}
}
#[cfg(all(test, any(feature = "claude", feature = "copilot-cli")))]
mod config_dir_tests {
use super::non_empty_config_dir;
use crate::error::Error;
#[test]
fn unset_resolves_to_none() {
assert!(matches!(non_empty_config_dir("TEST_VAR", None), Ok(None)));
}
#[test]
fn non_empty_resolves_to_the_path() {
let resolved = non_empty_config_dir("TEST_VAR", Some("/some/dir".into())).unwrap();
assert_eq!(resolved, Some(std::path::PathBuf::from("/some/dir")));
}
#[test]
fn empty_is_rejected_naming_the_variable() {
let err = non_empty_config_dir("TEST_VAR", Some("".into())).unwrap_err();
assert!(matches!(err, Error::EmptyConfigDirOverride { var: "TEST_VAR" }), "wrong variant: {err:?}");
}
#[cfg(unix)]
#[test]
fn non_utf8_value_still_resolves() {
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
let raw = OsString::from_vec(vec![0x66, 0x6f, 0xff, 0x6f]); let resolved = non_empty_config_dir("TEST_VAR", Some(raw.clone())).unwrap();
assert_eq!(resolved, Some(std::path::PathBuf::from(raw)));
}
}