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
11 changes: 6 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 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)](#)
[![opencode](https://img.shields.io/badge/opencode-supported-blueviolet.svg)](#)
[![Hermes Agent](https://img.shields.io/badge/Hermes_Agent-supported-blueviolet.svg)](#)
[![Antigravity](https://img.shields.io/badge/Antigravity-supported-blueviolet.svg)](#)

</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, and Antigravity.</sub>

### Initialize Projects

Expand Down Expand Up @@ -161,7 +162,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**
- 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 @@ -187,7 +188,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) for the MCP server to load.

### 3. Initialize Projects

Expand Down Expand Up @@ -495,7 +496,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**

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

Expand Down
42 changes: 41 additions & 1 deletion __tests__/installer-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,6 @@ describe('Installer targets — partial-state idempotency', () => {
expect(legacy.mcpServers.codegraph).toBeUndefined();
expect(legacy.mcpServers.other).toBeDefined();
});

// ---- Legacy auto-sync hook cleanup ----
// Pre-0.8 installs wrote `codegraph mark-dirty` / `sync-if-dirty`
// hooks to settings.json. Both subcommands were removed from the CLI,
Expand Down Expand Up @@ -607,6 +606,46 @@ describe('Installer targets — partial-state idempotency', () => {
// Both events emptied → the whole `hooks` object is removed.
expect(after.hooks).toBeUndefined();
});

it('antigravity: install writes mcp_config.json with correct format', () => {
const target = getTarget('antigravity')!;
target.install('global', { autoAllow: true });

const configPath = path.join(tmpHome, '.gemini', 'config', 'mcp_config.json');
expect(fs.existsSync(configPath)).toBe(true);

const content = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
expect(content.mcpServers).toBeDefined();
expect(content.mcpServers.codegraph).toBeDefined();
expect(content.mcpServers.codegraph.command).toBe('codegraph');
expect(content.mcpServers.codegraph.args).toEqual(['serve', '--mcp']);
expect(content.mcpServers.codegraph.env).toEqual({});
expect(content.mcpServers.codegraph.type).toBeUndefined();

const agentsMd = path.join(tmpHome, '.gemini', 'GEMINI.md');
expect(fs.existsSync(agentsMd)).toBe(true);
});

it('antigravity: uninstall removes only mcpServers.codegraph, preserving siblings', () => {
const target = getTarget('antigravity')!;
const configPath = path.join(tmpHome, '.gemini', 'config', 'mcp_config.json');
fs.mkdirSync(path.dirname(configPath), { recursive: true });

const initialConfig = {
mcpServers: {
other: { command: 'other-command' }
}
};
fs.writeFileSync(configPath, JSON.stringify(initialConfig, null, 2) + '\n');

target.install('global', { autoAllow: true });
target.uninstall('global');

const content = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
expect(content.mcpServers).toBeDefined();
expect(content.mcpServers.other).toEqual({ command: 'other-command' });
expect(content.mcpServers.codegraph).toBeUndefined();
});
});

describe('Installer targets — registry', () => {
Expand All @@ -616,6 +655,7 @@ describe('Installer targets — registry', () => {
expect(getTarget('codex')?.id).toBe('codex');
expect(getTarget('opencode')?.id).toBe('opencode');
expect(getTarget('hermes')?.id).toBe('hermes');
expect(getTarget('antigravity')?.id).toBe('antigravity');
expect(getTarget('not-a-real-target')).toBeUndefined();
});

Expand Down
1 change: 0 additions & 1 deletion package-lock.json

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

2 changes: 1 addition & 1 deletion src/bin/codegraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1341,7 +1341,7 @@ program
*/
program
.command('install')
.description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent)')
.description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, antigravity)')
.option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt')
.option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt')
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on')
Expand Down
210 changes: 210 additions & 0 deletions src/installer/targets/antigravity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import {
AgentTarget,
DetectionResult,
InstallOptions,
Location,
WriteResult,
} from './types';
import {
jsonDeepEqual,
readJsonFile,
removeMarkedSection,
replaceOrAppendMarkedSection,
writeJsonFile,
} from './shared';
import {
CODEGRAPH_SECTION_END,
CODEGRAPH_SECTION_START,
INSTRUCTIONS_TEMPLATE,
} from '../instructions-template';

/**
* Returns the path to the antigravity configuration directory.
* Under ~/.gemini/config
*/
function configDir(): string {
return path.join(os.homedir(), '.gemini', 'config');
}

/**
* Returns the path to the antigravity MCP configuration JSON file.
* Under ~/.gemini/config/mcp_config.json
*/
function mcpConfigPath(): string {
return path.join(configDir(), 'mcp_config.json');
}

/**
* Returns the path to the GEMINI.md instruction file for antigravity.
* Under ~/.gemini/GEMINI.md
*/
function instructionsPath(): string {
return path.join(os.homedir(), '.gemini', 'GEMINI.md');
}

/**
* Builds the canonical MCP configuration object for antigravity.
* Excludes the "type" field and includes "env: {}" as requested.
*/
function buildAntigravityMcpConfig(): { command: string; args: string[]; env: Record<string, any> } {
return {
command: 'codegraph',
args: ['serve', '--mcp'],
env: {},
};
}

/**
* Writes the antigravity MCP entry into ~/.gemini/config/mcp_config.json.
* Preserves sibling servers and creates directory/file if they do not exist.
*/
function writeMcpEntry(): WriteResult['files'][number] {
const file = mcpConfigPath();
const dir = path.dirname(file);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });

const existing = readJsonFile(file);
const before = existing.mcpServers?.codegraph;
const after = buildAntigravityMcpConfig();

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

const action: 'created' | 'updated' = before ? 'updated' : (fs.existsSync(file) ? 'updated' : 'created');
if (!existing.mcpServers) existing.mcpServers = {};
existing.mcpServers.codegraph = after;
writeJsonFile(file, existing);
return { path: file, action };
}

/**
* Writes the markdown agent instructions into ~/.gemini/config/AGENTS.md.
* Appends or replaces the section with the CodeGraph markers.
*/
function writeInstructionsEntry(): WriteResult['files'][number] {
const file = instructionsPath();
const dir = path.dirname(file);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });

const action = replaceOrAppendMarkedSection(
file,
INSTRUCTIONS_TEMPLATE,
CODEGRAPH_SECTION_START,
CODEGRAPH_SECTION_END,
);
const mapped: 'created' | 'updated' | 'unchanged' =
action === 'created' ? 'created'
: action === 'unchanged' ? 'unchanged'
: 'updated';
return { path: file, action: mapped };
}

class AntigravityTarget implements AgentTarget {
readonly id = 'antigravity' as const;
readonly displayName = 'antigravity';
readonly docsUrl = 'https://antigravity.google/download';

/**
* Returns true if the location is supported.
* antigravity target only supports global installation.
*/
supportsLocation(loc: Location): boolean {
return loc === 'global';
}

/**
* Detects whether antigravity target has been installed and configured.
*/
detect(loc: Location): DetectionResult {
if (loc !== 'global') {
return { installed: false, alreadyConfigured: false };
}
const mcpPath = mcpConfigPath();
const config = readJsonFile(mcpPath);
const alreadyConfigured = !!config.mcpServers?.codegraph;
const installed = fs.existsSync(path.join(os.homedir(), '.gemini')) || fs.existsSync(mcpPath);
return { installed, alreadyConfigured, configPath: mcpPath };
}

/**
* Installs codegraph MCP configurations and instructions for antigravity.
*/
install(loc: Location, _opts: InstallOptions): WriteResult {
if (loc !== 'global') {
return {
files: [],
notes: ['antigravity has no project-local config — re-run with --location=global to install.'],
};
}
const files: WriteResult['files'] = [];

files.push(writeMcpEntry());
files.push(writeInstructionsEntry());

return {
files,
notes: ['Restart antigravity for MCP changes to take effect.'],
};
}

/**
* Uninstalls codegraph configurations and instructions for antigravity.
*/
uninstall(loc: Location): WriteResult {
if (loc !== 'global') return { files: [] };
const files: WriteResult['files'] = [];

const mcpPath = mcpConfigPath();
const config = readJsonFile(mcpPath);
if (config.mcpServers?.codegraph) {
delete config.mcpServers.codegraph;
if (Object.keys(config.mcpServers).length === 0) {
delete config.mcpServers;
}
writeJsonFile(mcpPath, config);
files.push({ path: mcpPath, action: 'removed' });
} else {
files.push({ path: mcpPath, action: 'not-found' });
}

const instr = instructionsPath();
const action = removeMarkedSection(instr, CODEGRAPH_SECTION_START, CODEGRAPH_SECTION_END);
files.push({ path: instr, action });

return { files };
}

/**
* Prints the configuration snippet for manual pasting.
*/
printConfig(loc: Location): string {
if (loc !== 'global') {
return '# antigravity has no project-local config — use --location=global.\n';
}
const target = mcpConfigPath();
const snippet = JSON.stringify(
{
mcpServers: {
codegraph: buildAntigravityMcpConfig(),
},
},
null,
2
);
return `# Add to ${target}\n\n${snippet}\n`;
}

/**
* Returns list of paths created/modified by this target.
*/
describePaths(loc: Location): string[] {
if (loc !== 'global') return [];
return [mcpConfigPath(), instructionsPath()];
}
}

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