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 @@ -8,7 +8,7 @@ Already installed? Run `codegraph upgrade`

Follow [@getcodegraph](https://x.com/getcodegraph) on X for updates.

### Supercharge Claude Code, Cursor, Codex, OpenCode, Hermes Agent, Gemini, Antigravity, and Kiro with Semantic Code Intelligence
### Supercharge Claude Code, Cursor, Codex, OpenCode, Hermes Agent, Gemini, Antigravity, Kiro, and Grok Build with Semantic Code Intelligence

**Surgical context · fewer tool calls · faster answers · 100% local**

Expand All @@ -32,6 +32,7 @@ Follow [@getcodegraph](https://x.com/getcodegraph) on X for updates.
[![Gemini](https://img.shields.io/badge/Gemini-supported-blueviolet.svg)](#supported-agents)
[![Antigravity](https://img.shields.io/badge/Antigravity-supported-blueviolet.svg)](#supported-agents)
[![Kiro](https://img.shields.io/badge/Kiro-supported-blueviolet.svg)](#supported-agents)
[![Grok Build](https://img.shields.io/badge/Grok_Build-supported-blueviolet.svg)](#supported-agents)

<br>

Expand Down Expand Up @@ -102,7 +103,7 @@ In a **new terminal**, run the installer to connect CodeGraph to the agents you
codegraph install
```

<sub>Detects and auto-configures Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, and Kiro — wiring the CodeGraph MCP server into each. **This is the step that connects CodeGraph to your agent;** installing the CLI in step 1 does not do it on its own. It only wires up your agent — it does **not** index any code; building each project's graph is the separate `codegraph init` in step 3. (Shortcut: `npx @colbymchenry/codegraph` downloads and runs this in one go.)</sub>
<sub>Detects and auto-configures Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, and Grok Build — wiring the CodeGraph MCP server into each. **This is the step that connects CodeGraph to your agent;** installing the CLI in step 1 does not do it on its own. It only wires up your agent — it does **not** index any code; building each project's graph is the separate `codegraph init` in step 3. (Shortcut: `npx @colbymchenry/codegraph` downloads and runs this in one go.)</sub>

### 3. Initialize each project

Expand Down Expand Up @@ -415,7 +416,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**, **Gemini CLI**, **Antigravity IDE**, **Kiro**
- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, **Kiro**, **Grok Build**
- 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, plus a small marker-fenced CodeGraph section in the agent's instructions file (`CLAUDE.md` / `AGENTS.md` / `GEMINI.md`) — that's how subagents and non-MCP agents learn the `codegraph explore` command, since the MCP server's own guidance only reaches the main agent. Removed cleanly by `codegraph uninstall`.
Expand All @@ -442,7 +443,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 / Gemini CLI / Antigravity IDE / Kiro) for the MCP server to load.
Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro / Grok Build) for the MCP server to load.

### 3. Initialize Projects

Expand Down Expand Up @@ -800,6 +801,7 @@ is written):
- **Gemini CLI**
- **Antigravity IDE**
- **Kiro**
- **Grok Build**

## Supported Languages

Expand Down Expand Up @@ -908,7 +910,7 @@ MIT

<div align="center">

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

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

Expand Down
209 changes: 209 additions & 0 deletions __tests__/installer-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,7 @@ describe('Installer targets — registry', () => {
expect(getTarget('gemini')?.id).toBe('gemini');
expect(getTarget('antigravity')?.id).toBe('antigravity');
expect(getTarget('kiro')?.id).toBe('kiro');
expect(getTarget('grok')?.id).toBe('grok');
expect(getTarget('not-a-real-target')).toBeUndefined();
});

Expand Down Expand Up @@ -1571,6 +1572,214 @@ function listAllFiles(dir: string): string[] {
// SAME directory (which is exactly how this bug stayed invisible), so these
// tests deliberately split them.
// ---------------------------------------------------------------------------
describe('Installer targets — Grok Build', () => {
let tmpHome: string;
let tmpCwd: string;
let origCwd: string;
let homeRestore: { restore: () => void };

beforeEach(() => {
tmpHome = mkTmpDir('home');
tmpCwd = mkTmpDir('cwd');
origCwd = process.cwd();
process.chdir(tmpCwd);
homeRestore = setHome(tmpHome);
});

afterEach(() => {
homeRestore.restore();
process.chdir(origCwd);
fs.rmSync(tmpHome, { recursive: true, force: true });
fs.rmSync(tmpCwd, { recursive: true, force: true });
});

it('grok: install writes [mcp_servers.codegraph] to ~/.grok/config.toml AND the AGENTS.md codegraph block', () => {
const grok = getTarget('grok')!;
const first = grok.install('global', { autoAllow: false });
const tomlPath = path.join(tmpHome, '.grok', 'config.toml');
const agentsMd = path.join(tmpHome, '.grok', 'AGENTS.md');
expect(first.files.some((f) => f.path.endsWith('config.toml'))).toBe(true);
expect(fs.existsSync(tomlPath)).toBe(true);
const toml = fs.readFileSync(tomlPath, 'utf-8');
expect(toml).toContain('[mcp_servers.codegraph]');
expect(toml).toContain('command = "codegraph"');
expect(toml).toContain('args = ["serve", "--mcp"]');
// The short instructions block IS written (subagents / non-MCP
// harnesses read AGENTS.md but never the MCP initialize instructions).
expect(fs.existsSync(agentsMd)).toBe(true);
const body = fs.readFileSync(agentsMd, 'utf-8');
expect(body).toContain('## CodeGraph');
expect(body).toContain('codegraph explore');
// Re-install is fully unchanged (byte-equal block → idempotent).
const second = grok.install('global', { autoAllow: false });
for (const f of second.files) expect(f.action).toBe('unchanged');
});

it('grok: install preserves a pre-existing sibling MCP server in config.toml', () => {
const grok = getTarget('grok')!;
const dir = path.join(tmpHome, '.grok');
fs.mkdirSync(dir, { recursive: true });
const tomlPath = path.join(dir, 'config.toml');
fs.writeFileSync(tomlPath, [
'[mcp_servers.github]',
'command = "npx"',
'args = ["-y", "@modelcontextprotocol/server-github"]',
'',
].join('\n'));

grok.install('global', { autoAllow: false });

const after = fs.readFileSync(tomlPath, 'utf-8');
expect(after).toContain('[mcp_servers.github]');
expect(after).toContain('server-github');
expect(after).toContain('[mcp_servers.codegraph]');
});

it('grok: install preserves other top-level TOML sections (cli, models, features)', () => {
const grok = getTarget('grok')!;
const dir = path.join(tmpHome, '.grok');
fs.mkdirSync(dir, { recursive: true });
const tomlPath = path.join(dir, 'config.toml');
fs.writeFileSync(tomlPath, [
'[cli]',
'auto_update = true',
'',
'[models]',
'default = "grok-build"',
'',
].join('\n'));

grok.install('global', { autoAllow: false });

const after = fs.readFileSync(tomlPath, 'utf-8');
expect(after).toContain('auto_update = true');
expect(after).toContain('default = "grok-build"');
expect(after).toContain('[mcp_servers.codegraph]');
});

it('grok: install replaces a legacy AGENTS.md codegraph block with the current one, keeping user content', () => {
const grok = getTarget('grok')!;
const dir = path.join(tmpHome, '.grok');
fs.mkdirSync(dir, { recursive: true });
const agentsMd = path.join(dir, 'AGENTS.md');
fs.writeFileSync(agentsMd, `# My grok notes\n\nBe terse.\n\n${LEGACY_BLOCK}\n`);

const result = grok.install('global', { autoAllow: false });

const body = fs.readFileSync(agentsMd, 'utf-8');
expect(body).toContain('# My grok notes');
expect(body).toContain('Be terse.');
// Self-heal: the stale pre-#529 body is gone, the current block is in.
expect(body).not.toContain('Prefer `codegraph_search`');
expect(body).toContain('codegraph explore');
const mdEntry = result.files.find((f) => f.path.endsWith('AGENTS.md'));
expect(mdEntry?.action).toBe('updated');
});

it('grok: uninstall strips [mcp_servers.codegraph] but leaves siblings intact', () => {
const grok = getTarget('grok')!;
const dir = path.join(tmpHome, '.grok');
fs.mkdirSync(dir, { recursive: true });
const tomlPath = path.join(dir, 'config.toml');
// Pre-seed with a sibling MCP server.
fs.writeFileSync(tomlPath, [
'[mcp_servers.github]',
'command = "npx"',
'args = ["-y", "@modelcontextprotocol/server-github"]',
'',
'[mcp_servers.codegraph]',
'command = "codegraph"',
'args = ["serve", "--mcp"]',
'',
].join('\n'));

grok.uninstall('global');

const after = fs.readFileSync(tomlPath, 'utf-8');
expect(after).not.toContain('[mcp_servers.codegraph]');
expect(after).toContain('[mcp_servers.github]');
});

it('grok: local install writes ./.grok/config.toml (project scope)', () => {
const grok = getTarget('grok')!;
const result = grok.install('local', { autoAllow: false });
const tomlPath = path.join(tmpCwd, '.grok', 'config.toml');
// macOS symlinks /var → /private/var; fs.realpathSync normalizes both.
expect(result.files.some((f) => fs.realpathSync(f.path) === fs.realpathSync(tomlPath))).toBe(true);
expect(fs.existsSync(tomlPath)).toBe(true);
const toml = fs.readFileSync(tomlPath, 'utf-8');
expect(toml).toContain('[mcp_servers.codegraph]');
// Local install does NOT write AGENTS.md (that's global only).
expect(result.files.some((f) => f.path.endsWith('AGENTS.md'))).toBe(false);
});

it('grok: local uninstall removes config.toml when it becomes empty', () => {
const grok = getTarget('grok')!;
grok.install('local', { autoAllow: false });
const tomlPath = path.join(tmpCwd, '.grok', 'config.toml');
expect(fs.existsSync(tomlPath)).toBe(true);

grok.uninstall('local');

// The only entry was codegraph, so the file is removed entirely.
expect(fs.existsSync(tomlPath)).toBe(false);
// The .grok/ dir should also be cleaned up.
expect(fs.existsSync(path.join(tmpCwd, '.grok'))).toBe(false);
});

it('grok: local uninstall keeps config.toml when sibling servers exist', () => {
const grok = getTarget('grok')!;
grok.install('local', { autoAllow: false });
const tomlPath = path.join(tmpCwd, '.grok', 'config.toml');
// Add a sibling server.
const content = fs.readFileSync(tomlPath, 'utf-8');
const withSibling = content.replace(
/\n*$/,
'\n\n[mcp_servers.github]\ncommand = "npx"\nargs = ["-y", "@modelcontextprotocol/server-github"]\n',
);
fs.writeFileSync(tomlPath, withSibling);

grok.uninstall('local');

expect(fs.existsSync(tomlPath)).toBe(true);
const after = fs.readFileSync(tomlPath, 'utf-8');
expect(after).not.toContain('[mcp_servers.codegraph]');
expect(after).toContain('[mcp_servers.github]');
});

it('grok: detect before install shows alreadyConfigured=false', () => {
const grok = getTarget('grok')!;
expect(grok.detect('global').alreadyConfigured).toBe(false);
expect(grok.detect('local').alreadyConfigured).toBe(false);
});

it('grok: detect after install shows alreadyConfigured=true', () => {
const grok = getTarget('grok')!;
grok.install('global', { autoAllow: false });
expect(grok.detect('global').alreadyConfigured).toBe(true);
// Local was not installed, so it should still be false.
expect(grok.detect('local').alreadyConfigured).toBe(false);
});

it('grok: GROK_HOME env var overrides default config dir', () => {
const grok = getTarget('grok')!;
const customHome = mkTmpDir('grok-home');
process.env.GROK_HOME = customHome;
try {
grok.install('global', { autoAllow: false });
const tomlPath = path.join(customHome, 'config.toml');
expect(fs.existsSync(tomlPath)).toBe(true);
const toml = fs.readFileSync(tomlPath, 'utf-8');
expect(toml).toContain('[mcp_servers.codegraph]');
// Default location should NOT have been written.
expect(fs.existsSync(path.join(tmpHome, '.grok', 'config.toml'))).toBe(false);
} finally {
delete process.env.GROK_HOME;
fs.rmSync(customHome, { recursive: true, force: true });
}
});
});

describe('Installer targets — opencode XDG config path (#535)', () => {
let tmpHome: string;
let tmpCwd: string;
Expand Down
Loading