From 986e9c6670d648d97c878b10242ffcee0ec8e11d Mon Sep 17 00:00:00 2001 From: miguelarcsplit Date: Sat, 23 May 2026 22:33:34 -0400 Subject: [PATCH] feat(installer): add Antigravity IDE support as a native plugin --- README.md | 12 +- package-lock.json | 5 +- src/installer/targets/antigravity.ts | 256 +++++++++++++++++++++++++++ src/installer/targets/registry.ts | 2 + src/installer/targets/types.ts | 2 +- 5 files changed, 268 insertions(+), 9 deletions(-) create mode 100644 src/installer/targets/antigravity.ts diff --git a/README.md b/README.md index faf357bc..5d9ae8d2 100644 --- a/README.md +++ b/README.md @@ -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** @@ -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) @@ -41,7 +42,7 @@ npx @colbymchenry/codegraph # zero-install, or: npm i -g @colbymchenry/codegraph ``` -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. +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. ### Initialize Projects @@ -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`) @@ -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 @@ -474,6 +475,7 @@ the MCP server and writing its instructions file: - **Codex CLI** - **opencode** - **Hermes Agent** +- **Antigravity IDE** ## Supported Languages @@ -534,7 +536,7 @@ MIT
-**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) diff --git a/package-lock.json b/package-lock.json index 36c592b1..d9b3b484 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@colbymchenry/codegraph", - "version": "0.9.3", + "version": "0.9.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@colbymchenry/codegraph", - "version": "0.9.3", + "version": "0.9.4", "license": "MIT", "dependencies": { "@clack/prompts": "^1.3.0", @@ -1431,7 +1431,6 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/src/installer/targets/antigravity.ts b/src/installer/targets/antigravity.ts new file mode 100644 index 00000000..2b2cb9be --- /dev/null +++ b/src/installer/targets/antigravity.ts @@ -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(); diff --git a/src/installer/targets/registry.ts b/src/installer/targets/registry.ts index 0091ab64..102ecd54 100644 --- a/src/installer/targets/registry.ts +++ b/src/installer/targets/registry.ts @@ -13,6 +13,7 @@ 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, @@ -20,6 +21,7 @@ export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([ codexTarget, opencodeTarget, hermesTarget, + antigravityTarget, ]); export function getTarget(id: string): AgentTarget | undefined { diff --git a/src/installer/targets/types.ts b/src/installer/targets/types.ts index 290f13ce..158a1e8d 100644 --- a/src/installer/targets/types.ts +++ b/src/installer/targets/types.ts @@ -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)`.