Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

CodeGraph parses your codebase with tree-sitter, stores every symbol, edge,
and file in a local SQLite database (FTS5), and exposes the graph to
AI agents — Claude Code, Cursor, Codex CLI, opencode, Hermes — over the
AI agents — Claude Code, Cursor, Codex CLI, opencode, Hermes, Antigravity CLI — over the
Model Context Protocol (MCP).

Agents that consult the graph instead of grepping the filesystem make
Expand All @@ -23,7 +23,7 @@ Agents that consult the graph instead of grepping the filesystem make
- **Local.** Index lives in `.codegraph/db.sqlite` next to your code. Nothing
leaves the machine.
- **Multi-agent.** A single `codegraph install` configures Claude Code, Cursor,
Codex, opencode and Hermes in one go.
Codex, opencode, Hermes and Antigravity CLI in one go.
- **Live.** Built-in file watcher keeps the index in sync while the MCP server
serves your agent.

Expand Down Expand Up @@ -136,7 +136,7 @@ crates/
codegraph-graph/ callers / callees / impact radius (BFS)
codegraph-context/ markdown + JSON context formatters
codegraph-mcp/ hand-rolled JSON-RPC 2.0 server over stdio
codegraph-installer/ Claude / Cursor / Codex / opencode / Hermes targets
codegraph-installer/ Claude / Cursor / Codex / opencode / Hermes / Antigravity targets
codegraph/ CLI binary (clap) + file watcher (notify)
```

Expand Down
1 change: 1 addition & 0 deletions crates/codegraph-installer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub trait AgentTarget: Send + Sync {

pub fn registry() -> Vec<Arc<dyn AgentTarget>> {
vec![
Arc::new(targets::antigravity::AntigravityTarget),
Arc::new(targets::claude::ClaudeTarget),
Arc::new(targets::cursor::CursorTarget),
Arc::new(targets::codex::CodexTarget),
Expand Down
193 changes: 193 additions & 0 deletions crates/codegraph-installer/src/targets/antigravity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
use crate::{
targets::jsonutil, AgentTarget, DetectStatus, InstallOpts, InstallReport, INSTRUCTIONS_MD,
};
use anyhow::Result;
use camino::Utf8PathBuf;
use serde_json::{json, Value};

pub struct AntigravityTarget;

impl AntigravityTarget {
fn config_paths(&self, _opts: &InstallOpts) -> Vec<Utf8PathBuf> {
let mut paths = Vec::new();
if let Some(home) = dirs::home_dir() {
if let Ok(p) = Utf8PathBuf::from_path_buf(home.join(".gemini").join("antigravity-cli").join("mcp_config.json")) {
paths.push(p);
}
if let Ok(p) = Utf8PathBuf::from_path_buf(home.join(".gemini").join("config").join("mcp_config.json")) {
paths.push(p);
}
Comment on lines +14 to +19
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicated

}
if let Some(config) = dirs::config_dir() {
if let Ok(p) = Utf8PathBuf::from_path_buf(config.join("gemini").join("antigravity-cli").join("mcp_config.json")) {
paths.push(p);
}
if let Ok(p) = Utf8PathBuf::from_path_buf(config.join("gemini").join("config").join("mcp_config.json")) {
paths.push(p);
}
}
paths
}

fn instructions_paths(&self, _opts: &InstallOpts) -> Vec<Utf8PathBuf> {
let mut paths = Vec::new();
if let Some(home) = dirs::home_dir() {
if let Ok(p) = Utf8PathBuf::from_path_buf(home.join(".gemini").join("antigravity-cli").join("ANTIGRAVITY.md")) {
paths.push(p);
}
if let Ok(p) = Utf8PathBuf::from_path_buf(home.join(".gemini").join("config").join("ANTIGRAVITY.md")) {
paths.push(p);
}
}
if let Some(config) = dirs::config_dir() {
if let Ok(p) = Utf8PathBuf::from_path_buf(config.join("gemini").join("antigravity-cli").join("ANTIGRAVITY.md")) {
paths.push(p);
}
if let Ok(p) = Utf8PathBuf::from_path_buf(config.join("gemini").join("config").join("ANTIGRAVITY.md")) {
paths.push(p);
}
}
paths
}
}

impl AgentTarget for AntigravityTarget {
fn id(&self) -> &'static str {
"antigravity"
}
fn label(&self) -> &'static str {
"Antigravity CLI"
}

fn detect(&self, opts: &InstallOpts) -> DetectStatus {
let paths = self.config_paths(opts);
if paths.is_empty() {
return DetectStatus::NotFound;
}

let mut all_not_found = true;
let mut any_configured = false;

for p in &paths {
if !p.exists() {
if let Some(parent) = p.parent() {
if parent.exists() {
all_not_found = false;
}
}
} else {
all_not_found = false;
if let Ok(v) = jsonutil::read_or_default(p) {
if v.pointer("/mcpServers/codegraph").is_some() {
any_configured = true;
}
}
}
}

if any_configured {
DetectStatus::AlreadyConfigured
} else if all_not_found {
DetectStatus::NotFound
} else {
DetectStatus::Found
}
}

fn install(&self, opts: &InstallOpts) -> Result<InstallReport> {
let paths = self.config_paths(opts);
if paths.is_empty() {
return Err(anyhow::anyhow!("no settings paths"));
}

let mut written = Vec::new();
let mcp_entry = json!({
"command": opts.binary_path.as_str(),
"args": serve_args(opts),
});

for settings in &paths {
if let Some(parent) = settings.parent() {
if !parent.exists() {
std::fs::create_dir_all(parent.as_std_path())?;
}
}

let mut v = jsonutil::read_or_default(settings)?;
let mut changed = false;
{
if !v.is_object() {
v = Value::Object(Default::default());
}
let obj = v.as_object_mut().unwrap();
let servers = obj
.entry("mcpServers")
.or_insert_with(|| Value::Object(Default::default()));
let servers = servers
.as_object_mut()
.ok_or_else(|| anyhow::anyhow!("mcpServers not an object"))?;
if servers.get("codegraph") != Some(&mcp_entry) {
servers.insert("codegraph".into(), mcp_entry.clone());
changed = true;
}
}

if changed {
jsonutil::write_pretty(settings, &v)?;
written.push(settings.clone());
}
}

for md in self.instructions_paths(opts) {
let want = INSTRUCTIONS_MD;
let existing = std::fs::read_to_string(md.as_std_path()).ok();
if existing.as_deref() != Some(want) {
if let Some(parent) = md.parent() {
std::fs::create_dir_all(parent.as_std_path())?;
}
std::fs::write(md.as_std_path(), want)?;
written.push(md);
}
}

if written.is_empty() {
Ok(InstallReport::Unchanged)
} else {
Ok(InstallReport::Installed(written))
}
}

fn uninstall(&self, opts: &InstallOpts) -> Result<InstallReport> {
let mut removed = Vec::new();
for settings in self.config_paths(opts) {
if settings.exists() {
let mut v = jsonutil::read_or_default(&settings)?;
let mut changed = false;
if let Some(servers) = v.pointer_mut("/mcpServers").and_then(|s| s.as_object_mut())
{
if servers.remove("codegraph").is_some() {
changed = true;
}
}
if changed {
jsonutil::write_pretty(&settings, &v)?;
removed.push(settings);
}
}
}
if removed.is_empty() {
Ok(InstallReport::Unchanged)
} else {
Ok(InstallReport::Updated(removed))
}
}
}

fn serve_args(opts: &InstallOpts) -> Value {
let mut args = vec![Value::String("serve".into()), Value::String("--mcp".into())];
if let Some(root) = &opts.project_root {
args.push(Value::String("--path".into()));
args.push(Value::String(root.to_string()));
}
Value::Array(args)
}
1 change: 1 addition & 0 deletions crates/codegraph-installer/src/targets/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod antigravity;
pub mod claude;
pub mod codex;
pub mod cursor;
Expand Down