use std::path::PathBuf;
use super::mcpjson::{self, RemoteShape, ServerShape};
use super::report;
use super::{AgentBackend, BackendState};
use crate::doctor::{CheckStatus, DoctorCheck, DoctorReport};
use crate::error::{Error, Result};
use crate::host::{Capabilities, Desired, Outcome, Plugin, Scope, Source};
pub(crate) struct AntigravityBackend;
const SHAPE: ServerShape = ServerShape::plain().with_remote(RemoteShape::ServerUrlSseOnly);
impl AgentBackend for AntigravityBackend {
fn id(&self) -> &'static str {
"antigravity"
}
fn detect(&self) -> bool {
ide_marker().is_some_and(|p| p.is_dir())
}
fn capabilities(&self) -> Capabilities {
Capabilities {
plugins: false,
mcp: true,
hooks: false,
commands: false,
agents: false,
skills: false,
instructions: false,
statusline: false,
scopes: &["user"],
}
}
fn probe(&self, plugin: &Plugin, scope: &Scope, source: &Source) -> Result<BackendState> {
let comp = plugin.components(source)?.with_client(self.id());
let mcp = mcp_config(scope)?;
mcpjson::probe(&mcp, &["mcpServers"], &comp.mcp_servers, SHAPE)
}
fn reconcile(&self, plugin: &Plugin, desired: &Desired, scope: &Scope) -> Result<Outcome> {
let comp = plugin.components(&desired.source)?.with_client(self.id());
let mcp = mcp_config(scope)?;
mcpjson::reconcile(&mcp, &["mcpServers"], &comp.mcp_servers, SHAPE)
}
fn remove(&self, plugin: &Plugin, scope: &Scope, source: &Source) -> Result<Outcome> {
let comp = plugin.components(source)?.with_client(self.id());
let mcp = mcp_config(scope)?;
mcpjson::remove(&mcp, &["mcpServers"], &comp.mcp_servers, SHAPE)
}
fn report(&self, plugin: &Plugin, source: &Source) -> DoctorReport {
DoctorReport::from_checks(report_checks(self, plugin, source))
}
}
fn gemini_config_dir() -> Result<PathBuf> {
dirs::home_dir()
.map(|h| h.join(".gemini").join("config"))
.ok_or_else(|| Error::Tree("no home directory (HOME unset); cannot locate ~/.gemini/config".into()))
}
fn mcp_config(scope: &Scope) -> Result<PathBuf> {
match scope {
Scope::User => Ok(gemini_config_dir()?.join("mcp_config.json")),
Scope::Project { .. } => {
Err(Error::Tree("antigravity backend is user-scope only (project-level `.agents/` IDE parity is unconfirmed)".into()))
}
}
}
fn ide_marker() -> Option<PathBuf> {
dirs::home_dir().map(|h| h.join(".gemini").join("antigravity-ide"))
}
fn report_checks(backend: &AntigravityBackend, plugin: &Plugin, source: &Source) -> Vec<DoctorCheck> {
let mut checks = Vec::new();
checks.push(if backend.detect() {
DoctorCheck { name: "antigravity detected", status: CheckStatus::Ok("~/.gemini/antigravity-ide present".into()) }
} else {
DoctorCheck {
name: "antigravity detected",
status: CheckStatus::Fail {
problem: "Antigravity desktop IDE not detected".into(),
fix: "install it from https://antigravity.google/download".into(),
},
}
});
let dir = match gemini_config_dir() {
Ok(dir) => dir,
Err(e) => {
checks.push(DoctorCheck { name: "mcp_config.json", status: CheckStatus::Warn(e.to_string()) });
return checks;
}
};
let mcp = dir.join("mcp_config.json");
let root = report::read_json_config(&mut checks, "mcp_config.json", &mcp);
let Some(comp) = report::components(&mut checks, plugin, source).map(|c| c.with_client(backend.id())) else {
return checks;
};
checks.push(report::check_mcp_registered(
&comp.mcp_servers,
root.as_ref(),
&["mcpServers"],
"not in mcp_config.json",
"run the host's `setup`",
));
checks.push(report::check_mcp_command(&comp.mcp_servers));
checks
}
#[cfg(test)]
#[path = "../../tests/unit/antigravity.rs"]
mod antigravity_tests;