Skip to content
Open
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
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# CodeGraph

### Supercharge Claude Code, Cursor, Codex, OpenCode, and Hermes Agent with Semantic Code Intelligence
### Supercharge Claude Code, Cursor, Codex, OpenCode, Hermes Agent, and Antigravity IDE with Semantic Code Intelligence

**~35% cheaper · ~70% fewer tool calls · 100% local**

Expand All @@ -19,6 +19,7 @@
[![Codex CLI](https://img.shields.io/badge/Codex_CLI-supported-blueviolet.svg)](#supported-agents)
[![opencode](https://img.shields.io/badge/opencode-supported-blueviolet.svg)](#supported-agents)
[![Hermes Agent](https://img.shields.io/badge/Hermes_Agent-supported-blueviolet.svg)](#supported-agents)
[![Antigravity IDE](https://img.shields.io/badge/Antigravity_IDE-supported-blueviolet.svg)](#supported-agents)

</div>

Expand All @@ -41,7 +42,7 @@ npx @colbymchenry/codegraph # zero-install, or:
npm i -g @colbymchenry/codegraph
```

<sub>CodeGraph bundles its own runtime — nothing to compile, no native build, works the same everywhere. The interactive installer auto-configures your agent(s) — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent.</sub>
<sub>CodeGraph bundles its own runtime — nothing to compile, no native build, works the same everywhere. The interactive installer auto-configures your agent(s) — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Antigravity IDE.</sub>

### Initialize Projects

Expand Down Expand Up @@ -171,7 +172,7 @@ npx @colbymchenry/codegraph
```

The installer will:
- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**
- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Antigravity IDE**
- Prompt to install `codegraph` on your PATH (so agents can launch the MCP server)
- Ask whether configs apply to all your projects or just this one
- Write each chosen agent's MCP server config + an instructions file (e.g. `CLAUDE.md`, `.cursor/rules/codegraph.mdc`, `~/.codex/AGENTS.md`)
Expand All @@ -197,7 +198,7 @@ codegraph install --print-config codex # print snippet, no file wr

### 2. Restart Your Agent

Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent) for the MCP server to load.
Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Antigravity IDE) for the MCP server to load.

### 3. Initialize Projects

Expand Down Expand Up @@ -474,6 +475,7 @@ the MCP server and writing its instructions file:
- **Codex CLI**
- **opencode**
- **Hermes Agent**
- **Antigravity IDE**

## Supported Languages

Expand Down Expand Up @@ -534,7 +536,7 @@ MIT

<div align="center">

**Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, and Hermes Agent**
**Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, and Antigravity IDE**

[Report Bug](https://github.com/colbymchenry/codegraph/issues) · [Request Feature](https://github.com/colbymchenry/codegraph/issues)

Expand Down
5 changes: 2 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

256 changes: 256 additions & 0 deletions src/installer/targets/antigravity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
/**
* Antigravity IDE target.
*
* Installs CodeGraph as an Antigravity Plugin, enabling support for
* both global (`~/.gemini/config/plugins/codegraph/`) and local
* (`./.agents/plugins/codegraph/`) deployments.
*
* The plugin bundles:
* - `plugin.json`
* - `mcp_config.json`
* - `skills/codegraph-instructions/SKILL.md` (Agent instructions)
*/

import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import {
AgentTarget,
DetectionResult,
InstallOptions,
Location,
WriteResult,
} from './types';
import {
getMcpServerConfig,
jsonDeepEqual,
readJsonFile,
writeJsonFile,
} from './shared';
import {
INSTRUCTIONS_TEMPLATE,
} from '../instructions-template';

function configDir(loc: Location): string {
return loc === 'global'
? path.join(os.homedir(), '.gemini', 'config', 'plugins', 'codegraph')
: path.join(process.cwd(), '.agents', 'plugins', 'codegraph');
}

function pluginJsonPath(loc: Location): string {
return path.join(configDir(loc), 'plugin.json');
}

function mcpJsonPath(loc: Location): string {
return path.join(configDir(loc), 'mcp_config.json');
}

function instructionsPath(loc: Location): string {
return path.join(configDir(loc), 'skills', 'codegraph-instructions', 'SKILL.md');
}

function rulesPath(loc: Location): string {
return path.join(configDir(loc), 'rules', 'codegraph-rules.md');
}

class AntigravityTarget implements AgentTarget {
readonly id = 'antigravity' as const;
readonly displayName = 'Antigravity IDE';
readonly docsUrl = '';

supportsLocation(_loc: Location): boolean {
return true;
}

detect(loc: Location): DetectionResult {
const dir = configDir(loc);
const installed = fs.existsSync(dir);
return { installed, alreadyConfigured: installed, configPath: dir };
}

install(loc: Location, _opts: InstallOptions): WriteResult {
const files: WriteResult['files'] = [];

// Cleanup legacy files from earlier flawed global install
if (loc === 'global') {
const legacyCleanup = cleanupLegacyFiles();
files.push(...legacyCleanup);
}

files.push(writePluginJson(loc));
files.push(writeMcpEntry(loc));
files.push(writeInstructionsEntry(loc));
files.push(writeRulesEntry(loc));

return { files };
}

uninstall(loc: Location): WriteResult {
const files: WriteResult['files'] = [];

const dir = configDir(loc);
if (fs.existsSync(dir)) {
try {
fs.rmSync(dir, { recursive: true, force: true });
files.push({ path: dir, action: 'removed' });
} catch {
// Ignore removal errors
}
} else {
files.push({ path: dir, action: 'not-found' });
}

if (loc === 'global') {
const legacyCleanup = cleanupLegacyFiles();
files.push(...legacyCleanup);
}

return { files };
}

printConfig(loc: Location): string {
const target = mcpJsonPath(loc);
const snippet = JSON.stringify({ mcpServers: { codegraph: getMcpServerConfig() } }, null, 2);
return `# Add to ${target}\n\n${snippet}\n`;
}

describePaths(loc: Location): string[] {
return [configDir(loc)];
}
}

function writePluginJson(loc: Location): WriteResult['files'][number] {
const file = pluginJsonPath(loc);
const dir = path.dirname(file);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });

const existing = fs.existsSync(file) ? readJsonFile(file) : null;
const after = { name: 'codegraph', description: 'CodeGraph Semantic Code Intelligence' };

if (existing && jsonDeepEqual(existing, after)) {
return { path: file, action: 'unchanged' };
}

writeJsonFile(file, after);
return { path: file, action: existing ? 'updated' : 'created' };
}

function writeMcpEntry(loc: Location): WriteResult['files'][number] {
const file = mcpJsonPath(loc);
const dir = path.dirname(file);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });

const existing = fs.existsSync(file) ? readJsonFile(file) : null;
const after = { mcpServers: { codegraph: getMcpServerConfig() } };

if (existing && jsonDeepEqual(existing, after)) {
return { path: file, action: 'unchanged' };
}

writeJsonFile(file, after);
return { path: file, action: existing ? 'updated' : 'created' };
}

function writeInstructionsEntry(loc: Location): WriteResult['files'][number] {
const file = instructionsPath(loc);
const dir = path.dirname(file);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });

const SKILL_CONTENT = `---
name: codegraph-instructions
description: Explains how to use the CodeGraph MCP server for semantic code intelligence, codebase graph queries, finding references, and understanding architecture. Use this whenever the codegraph_* tools are available or the user asks questions about code structure.
---

${INSTRUCTIONS_TEMPLATE}
`;

let action: 'created' | 'updated' | 'unchanged' = 'created';
if (fs.existsSync(file)) {
const existing = fs.readFileSync(file, 'utf-8');
if (existing === SKILL_CONTENT) {
action = 'unchanged';
} else {
action = 'updated';
}
}

if (action !== 'unchanged') {
fs.writeFileSync(file, SKILL_CONTENT, 'utf-8');
}

return { path: file, action };
}

function writeRulesEntry(loc: Location): WriteResult['files'][number] {
const file = rulesPath(loc);
const dir = path.dirname(file);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });

const RULE_CONTENT = `---
name: codegraph-rules
description: Defines when to use CodeGraph over native grep search.
trigger: always_on
---

${INSTRUCTIONS_TEMPLATE}
`;

let action: 'created' | 'updated' | 'unchanged' = 'created';
if (fs.existsSync(file)) {
const existing = fs.readFileSync(file, 'utf-8');
if (existing === RULE_CONTENT) {
action = 'unchanged';
} else {
action = 'updated';
}
}

if (action !== 'unchanged') {
fs.writeFileSync(file, RULE_CONTENT, 'utf-8');
}

return { path: file, action };
}

function cleanupLegacyFiles(): WriteResult['files'] {
const files: WriteResult['files'] = [];

// Cleanup wrong mcp config file
const oldMcpPath = path.join(os.homedir(), '.gemini', 'config', 'mcp_config.json');
if (fs.existsSync(oldMcpPath)) {
try {
const config = readJsonFile(oldMcpPath);
if (config?.mcpServers?.codegraph) {
delete config.mcpServers.codegraph;
if (Object.keys(config.mcpServers).length === 0) {
delete config.mcpServers;
}
writeJsonFile(oldMcpPath, config);
files.push({ path: oldMcpPath, action: 'updated' });
}
} catch {
// ignore
}
}

// Cleanup wrong instructions file
const oldInstrDir = path.join(os.homedir(), '.gemini', 'antigravity-ide', 'mcp', 'codegraph');
const oldInstr = path.join(oldInstrDir, 'instructions.md');
if (fs.existsSync(oldInstr)) {
try {
fs.unlinkSync(oldInstr);
files.push({ path: oldInstr, action: 'removed' });

// Cleanup empty dirs up to .gemini/antigravity-ide
if (fs.readdirSync(oldInstrDir).length === 0) fs.rmdirSync(oldInstrDir);
const parentDir = path.dirname(oldInstrDir);
if (fs.readdirSync(parentDir).length === 0) fs.rmdirSync(parentDir);
} catch {
// ignore
}
}

return files;
}

export const antigravityTarget: AgentTarget = new AntigravityTarget();
2 changes: 2 additions & 0 deletions src/installer/targets/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import { cursorTarget } from './cursor';
import { codexTarget } from './codex';
import { opencodeTarget } from './opencode';
import { hermesTarget } from './hermes';
import { antigravityTarget } from './antigravity';

export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([
claudeTarget,
cursorTarget,
codexTarget,
opencodeTarget,
hermesTarget,
antigravityTarget,
]);

export function getTarget(id: string): AgentTarget | undefined {
Expand Down
2 changes: 1 addition & 1 deletion src/installer/targets/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type Location = 'global' | 'local';
* lookup. New targets add a value here when they're added to the
* registry. Keep these short and lowercase.
*/
export type TargetId = 'claude' | 'cursor' | 'codex' | 'opencode' | 'hermes';
export type TargetId = 'claude' | 'cursor' | 'codex' | 'opencode' | 'hermes' | 'antigravity';

/**
* Result of `target.detect(location)`.
Expand Down