How auto-syncing works — and why you don't need to run codegraph sync manually
-When your agent (Claude Code, Cursor, Codex, opencode) launches `codegraph serve --mcp`, three layers keep the index in step with your code — and make sure the agent never gets a silent wrong answer in the brief window between an edit and the next sync:
+When your agent (Claude Code, Cursor, Codex, opencode, Grok) launches `codegraph serve --mcp`, three layers keep the index in step with your code — and make sure the agent never gets a silent wrong answer in the brief window between an edit and the next sync:
1. **File watcher with debounced auto-sync.** A native FSEvents / inotify / ReadDirectoryChangesW watcher captures every source-file create / modify / delete and triggers a re-index after a debounce window (default `2000ms`, tunable via `CODEGRAPH_WATCH_DEBOUNCE_MS`, clamped to `[100ms, 60s]`). Bursts of edits collapse into a single sync.
@@ -376,10 +377,10 @@ 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**, **GitHub Copilot** (VS Code, Copilot CLI, JetBrains IDEs)
+- 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**, **GitHub Copilot** (VS Code, Copilot CLI, JetBrains IDEs)
- 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`.
+- 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` / Grok `rules/codegraph.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`.
- Set up auto-allow permissions when Claude Code is one of the targets
The installer **wires up your agents only — it does not index your code.** After it finishes, build each project's graph yourself with `codegraph init` (step 3). One global `codegraph install` covers every project; you run `codegraph init` once per project.
@@ -390,9 +391,11 @@ The installer **wires up your agents only — it does not index your code.** Aft
codegraph install --yes # auto-detect agents, install global
codegraph install --yes --init # same, then build the current project's index (one-shot bootstrap)
codegraph install --target=cursor,claude --yes # explicit target list
+codegraph install --target=grok --yes # Grok only
codegraph install --target=auto --location=local # detected agents, project-local
codegraph install --target=copilot-vscode,copilot-cli,copilot-jetbrains --yes # GitHub Copilot everywhere
codegraph install --print-config codex # print snippet, no file writes
+codegraph install --print-config grok # same, for Grok
codegraph install --print-config copilot-vscode # same, for Copilot in VS Code
```
@@ -407,7 +410,7 @@ codegraph install --print-config copilot-vscode # same, for Copilot in VS C
### 2. Restart Your Agent
-Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro / VS Code, the Copilot CLI, or your JetBrains IDE for GitHub Copilot) for the MCP server to load.
+Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro / Grok / VS Code, the Copilot CLI, or your JetBrains IDE for GitHub Copilot) for the MCP server to load.
### 3. Initialize Projects
@@ -785,6 +788,7 @@ is written):
- **Gemini CLI**
- **Antigravity IDE**
- **Kiro**
+- **Grok**
- **GitHub Copilot** — Copilot Chat in VS Code (`copilot-vscode`), the Copilot CLI (`copilot-cli`), and the Copilot plugin in JetBrains IDEs (`copilot-jetbrains`)
## Supported Languages
@@ -886,7 +890,7 @@ MIT
-**Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, and GitHub Copilot**
+**Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, Grok, and GitHub Copilot**
[Report Bug](https://github.com/colbymchenry/codegraph/issues) · [Request Feature](https://github.com/colbymchenry/codegraph/issues)
diff --git a/__tests__/installer-targets.test.ts b/__tests__/installer-targets.test.ts
index 4ec3e5903..9e04569f4 100644
--- a/__tests__/installer-targets.test.ts
+++ b/__tests__/installer-targets.test.ts
@@ -39,6 +39,7 @@ function setHome(dir: string): { restore: () => void } {
APPDATA: process.env.APPDATA,
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
HERMES_HOME: process.env.HERMES_HOME,
+ GROK_HOME: process.env.GROK_HOME,
COPILOT_HOME: process.env.COPILOT_HOME,
};
process.env.HOME = dir;
@@ -46,6 +47,7 @@ function setHome(dir: string): { restore: () => void } {
process.env.APPDATA = path.join(dir, '.config');
process.env.XDG_CONFIG_HOME = path.join(dir, '.config');
delete process.env.HERMES_HOME;
+ delete process.env.GROK_HOME;
delete process.env.COPILOT_HOME;
return {
restore() {
@@ -54,6 +56,7 @@ function setHome(dir: string): { restore: () => void } {
if (prev.APPDATA === undefined) delete process.env.APPDATA; else process.env.APPDATA = prev.APPDATA;
if (prev.XDG_CONFIG_HOME === undefined) delete process.env.XDG_CONFIG_HOME; else process.env.XDG_CONFIG_HOME = prev.XDG_CONFIG_HOME;
if (prev.HERMES_HOME === undefined) delete process.env.HERMES_HOME; else process.env.HERMES_HOME = prev.HERMES_HOME;
+ if (prev.GROK_HOME === undefined) delete process.env.GROK_HOME; else process.env.GROK_HOME = prev.GROK_HOME;
if (prev.COPILOT_HOME === undefined) delete process.env.COPILOT_HOME; else process.env.COPILOT_HOME = prev.COPILOT_HOME;
},
};
@@ -967,6 +970,125 @@ describe('Installer targets — partial-state idempotency', () => {
expect(fs.readFileSync(tomlPath, 'utf-8')).toBe(historyTables);
});
+ it('grok: install writes config.toml AND the rules/codegraph.md block (#704)', () => {
+ const grok = getTarget('grok')!;
+ const first = grok.install('global', { autoAllow: false });
+ const rulesMd = path.join(tmpHome, '.grok', 'rules', 'codegraph.md');
+ expect(first.files.some((f) => f.path.endsWith('config.toml'))).toBe(true);
+ expect(fs.existsSync(rulesMd)).toBe(true);
+ const body = fs.readFileSync(rulesMd, 'utf-8');
+ expect(body).toContain('## CodeGraph');
+ expect(body).toContain('codegraph explore');
+ const toml = fs.readFileSync(path.join(tmpHome, '.grok', 'config.toml'), 'utf-8');
+ expect(toml).toContain('[mcp_servers.codegraph]');
+ expect(toml).toContain('command = "codegraph"');
+ expect(toml).toContain('args = ["serve", "--mcp"]');
+ expect(first.notes?.join(' ')).toMatch(/\/mcps/);
+ const second = grok.install('global', { autoAllow: false });
+ for (const f of second.files) expect(f.action).toBe('unchanged');
+ });
+
+ it('grok: install replaces a legacy rules/codegraph.md block with the current one, keeping user content', () => {
+ const grok = getTarget('grok')!;
+ const dir = path.join(tmpHome, '.grok', 'rules');
+ fs.mkdirSync(dir, { recursive: true });
+ const rulesMd = path.join(dir, 'codegraph.md');
+ fs.writeFileSync(rulesMd, `# My grok notes\n\nBe terse.\n\n${LEGACY_BLOCK}\n`);
+
+ const result = grok.install('global', { autoAllow: false });
+
+ const body = fs.readFileSync(rulesMd, 'utf-8');
+ expect(body).toContain('# My grok notes');
+ expect(body).toContain('Be terse.');
+ expect(body).not.toContain('Prefer `codegraph_search`');
+ expect(body).toContain('codegraph explore');
+ const mdEntry = result.files.find((f) => f.path.endsWith('codegraph.md'));
+ expect(mdEntry?.action).toBe('updated');
+ });
+
+ it('grok: local install writes ./.grok/config.toml and ./.grok/rules/codegraph.md', () => {
+ const grok = getTarget('grok')!;
+ const result = grok.install('local', { autoAllow: false });
+ const paths = result.files.map((f) => f.path.replace(/\\/g, '/'));
+ expect(paths.some((p) => p.endsWith('/.grok/config.toml'))).toBe(true);
+ expect(paths.some((p) => p.endsWith('/.grok/rules/codegraph.md'))).toBe(true);
+
+ const toml = fs.readFileSync(path.join(process.cwd(), '.grok', 'config.toml'), 'utf-8');
+ expect(toml).toContain('[mcp_servers.codegraph]');
+ expect(fs.readFileSync(path.join(process.cwd(), '.grok', 'rules', 'codegraph.md'), 'utf-8'))
+ .toContain('codegraph explore');
+
+ expect(result.notes?.join(' ')).toMatch(/trusted/);
+
+ expect(fs.existsSync(path.join(tmpHome, '.grok', 'config.toml'))).toBe(false);
+ });
+
+ it('grok: local uninstall reverses the local install and leaves the global entry alone', () => {
+ const grok = getTarget('grok')!;
+ grok.install('global', { autoAllow: false });
+ grok.install('local', { autoAllow: false });
+ expect(grok.detect('local').alreadyConfigured).toBe(true);
+
+ grok.uninstall('local');
+
+ expect(grok.detect('local').alreadyConfigured).toBe(false);
+ expect(grok.detect('global').alreadyConfigured).toBe(true);
+ expect(fs.readFileSync(path.join(tmpHome, '.grok', 'config.toml'), 'utf-8'))
+ .toContain('[mcp_servers.codegraph]');
+ });
+
+ it('grok: install preserves a sibling [mcp_servers.other] table', () => {
+ const grok = getTarget('grok')!;
+ const tomlPath = path.join(tmpHome, '.grok', 'config.toml');
+ fs.mkdirSync(path.dirname(tomlPath), { recursive: true });
+ fs.writeFileSync(tomlPath, [
+ '[models]',
+ 'default = "grok-4"',
+ '',
+ '[mcp_servers.other]',
+ 'command = "other"',
+ 'args = ["serve"]',
+ '',
+ ].join('\n'));
+
+ grok.install('global', { autoAllow: false });
+ const afterInstall = fs.readFileSync(tomlPath, 'utf-8');
+ expect(afterInstall).toContain('[models]');
+ expect(afterInstall).toContain('default = "grok-4"');
+ expect(afterInstall).toContain('[mcp_servers.other]');
+ expect(afterInstall).toContain('command = "other"');
+ expect(afterInstall).toContain('[mcp_servers.codegraph]');
+
+ grok.uninstall('global');
+ const afterUninstall = fs.readFileSync(tomlPath, 'utf-8');
+ expect(afterUninstall).toContain('[models]');
+ expect(afterUninstall).toContain('[mcp_servers.other]');
+ expect(afterUninstall).not.toContain('[mcp_servers.codegraph]');
+ });
+
+ it('grok: GROK_HOME redirects the global config dir', () => {
+ const custom = path.join(tmpHome, 'custom-grok');
+ process.env.GROK_HOME = custom;
+ const grok = getTarget('grok')!;
+ grok.install('global', { autoAllow: false });
+ expect(fs.existsSync(path.join(custom, 'config.toml'))).toBe(true);
+ expect(fs.existsSync(path.join(tmpHome, '.grok', 'config.toml'))).toBe(false);
+ expect(fs.readFileSync(path.join(custom, 'config.toml'), 'utf-8'))
+ .toContain('[mcp_servers.codegraph]');
+ expect(fs.existsSync(path.join(custom, 'rules', 'codegraph.md'))).toBe(true);
+ });
+
+ it('grok: printConfig names config.toml and does not write', () => {
+ const grok = getTarget('grok')!;
+ const before = listAllFiles(tmpHome).concat(listAllFiles(tmpCwd));
+ const out = grok.printConfig('global');
+ expect(out).toContain('[mcp_servers.codegraph]');
+ expect(out).toContain('command = "codegraph"');
+ expect(out).toMatch(/config\.toml/);
+ const after = listAllFiles(tmpHome).concat(listAllFiles(tmpCwd));
+ expect(after.sort()).toEqual(before.sort());
+ });
+
it('claude: local install writes ./.mcp.json (project scope), not ./.claude.json', () => {
const claude = getTarget('claude')!;
const result = claude.install('local', { autoAllow: false });
@@ -1317,6 +1439,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('copilot-vscode')?.id).toBe('copilot-vscode');
expect(getTarget('copilot-cli')?.id).toBe('copilot-cli');
expect(getTarget('copilot-jetbrains')?.id).toBe('copilot-jetbrains');
@@ -1330,6 +1453,11 @@ describe('Installer targets — registry', () => {
expect(csv.map((t) => t.id)).toEqual(['claude', 'cursor']);
});
+ it("resolveTargetFlag('all') includes grok", () => {
+ const ids = resolveTargetFlag('all', 'global').map((t) => t.id);
+ expect(ids).toContain('grok');
+ });
+
it("resolveTargetFlag('all') includes every Copilot target", () => {
const ids = resolveTargetFlag('all', 'global').map((t) => t.id);
expect(ids).toContain('copilot-vscode');
diff --git a/site/src/content/docs/getting-started/installation.md b/site/src/content/docs/getting-started/installation.md
index 4f9b90986..ea5ed5f3b 100644
--- a/site/src/content/docs/getting-started/installation.md
+++ b/site/src/content/docs/getting-started/installation.md
@@ -11,10 +11,10 @@ npx @colbymchenry/codegraph
The installer will:
-- Ask which agent(s) to configure — auto-detecting installed ones from **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, and **Kiro**.
+- Ask which agent(s) to configure — auto-detecting installed ones from **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, **Kiro**, and **Grok**.
- 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`). Cursor and Kiro get the MCP config only. Removed cleanly by `codegraph uninstall`.
+- 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` / Grok `rules/codegraph.md`). Cursor and Kiro get the MCP config only. Removed cleanly by `codegraph uninstall`.
- Set up auto-allow permissions when Claude Code is one of the targets.
The installer **wires up your agents only — it does not index your code.** After it finishes, build each project's graph yourself with `codegraph init` (step 3 below).
@@ -38,7 +38,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) for the MCP server to load.
## 3. Initialize projects
diff --git a/site/src/content/docs/getting-started/introduction.md b/site/src/content/docs/getting-started/introduction.md
index 44d6546c1..29e894a01 100644
--- a/site/src/content/docs/getting-started/introduction.md
+++ b/site/src/content/docs/getting-started/introduction.md
@@ -5,7 +5,7 @@ description: What CodeGraph is, and why it makes AI coding agents faster and mor
CodeGraph is a **local-first code-intelligence tool**. It parses your codebase with [tree-sitter](https://tree-sitter.github.io/), stores every symbol, edge, and file in a local SQLite database, and exposes the result as a queryable **knowledge graph** — over the [Model Context Protocol (MCP)](/codegraph/reference/mcp-server/), a CLI, and a TypeScript library.
-It exists to make AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, and Kiro — **answer structural questions without scanning files**. Instead of fanning out across `grep`, `glob`, and `Read` to reconstruct how code fits together, an agent queries a pre-built index and gets the answer in a handful of calls.
+It exists to make AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, and Grok — **answer structural questions without scanning files**. Instead of fanning out across `grep`, `glob`, and `Read` to reconstruct how code fits together, an agent queries a pre-built index and gets the answer in a handful of calls.
## Why it matters
diff --git a/site/src/content/docs/getting-started/quickstart.md b/site/src/content/docs/getting-started/quickstart.md
index e1ea543f0..57c386128 100644
--- a/site/src/content/docs/getting-started/quickstart.md
+++ b/site/src/content/docs/getting-started/quickstart.md
@@ -25,7 +25,7 @@ Already have Node? `npm i -g @colbymchenry/codegraph` works on any version. Code
codegraph install
```
-Auto-detects and configures Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, and Kiro — wiring the CodeGraph MCP server into each. This step connects your agents only; it does **not** index any code. (Shortcut: `npx @colbymchenry/codegraph` downloads and runs the installer in one go.)
+Auto-detects and configures Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, and Grok — wiring the CodeGraph MCP server into each. This step connects your agents only; it does **not** index any code. (Shortcut: `npx @colbymchenry/codegraph` downloads and runs the installer in one go.)
## 3. Initialize each project
diff --git a/site/src/content/docs/guides/indexing.md b/site/src/content/docs/guides/indexing.md
index 1518c3d0a..7d904d94f 100644
--- a/site/src/content/docs/guides/indexing.md
+++ b/site/src/content/docs/guides/indexing.md
@@ -24,7 +24,7 @@ codegraph sync # incremental — only changed files
## Stay fresh automatically
-**You don't need to run `codegraph sync` by hand during an agent session.** When your agent (Claude Code, Cursor, Codex, opencode, Hermes, Gemini, Antigravity, Kiro) launches `codegraph serve --mcp`, three layers cooperate to keep the index in step with your code — and to never give the agent a quiet wrong answer in the small window between an edit and the next sync.
+**You don't need to run `codegraph sync` by hand during an agent session.** When your agent (Claude Code, Cursor, Codex, opencode, Hermes, Gemini, Antigravity, Kiro, Grok) launches `codegraph serve --mcp`, three layers cooperate to keep the index in step with your code — and to never give the agent a quiet wrong answer in the small window between an edit and the next sync.
### 1. File watcher with debounced auto-sync (always on)
diff --git a/site/src/content/docs/reference/integrations.md b/site/src/content/docs/reference/integrations.md
index 1b4b87c89..989b84ed2 100644
--- a/site/src/content/docs/reference/integrations.md
+++ b/site/src/content/docs/reference/integrations.md
@@ -3,7 +3,7 @@ title: Integrations
description: Supported agents, and manual MCP setup.
---
-The interactive installer auto-detects and configures each supported agent — wiring the CodeGraph MCP server into each. For the agents that use an instructions file, it also writes a short marker-fenced CodeGraph section (`CLAUDE.md`, `AGENTS.md`, or `GEMINI.md`) so subagents and non-MCP harnesses learn the `codegraph explore` command; `codegraph uninstall` removes it.
+The interactive installer auto-detects and configures each supported agent — wiring the CodeGraph MCP server into each. For the agents that use an instructions file, it also writes a short marker-fenced CodeGraph section (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, or Grok's `rules/codegraph.md`) so subagents and non-MCP harnesses learn the `codegraph explore` command; `codegraph uninstall` removes it.
## Supported agents
@@ -15,6 +15,7 @@ The interactive installer auto-detects and configures each supported agent — w
- **Gemini CLI**
- **Antigravity IDE**
- **Kiro**
+- **Grok**
Run `npx @colbymchenry/codegraph` and pick your agent(s); see [Installation](/codegraph/getting-started/installation/) for the non-interactive flags.
@@ -57,3 +58,15 @@ One wildcard auto-approves every CodeGraph tool. The server lists a single tool
:::tip
Cursor launches MCP subprocesses with the wrong working directory. The installer handles this for you by injecting a `--path` argument; if you wire Cursor up by hand, pass the project path explicitly.
:::
+
+### Grok
+
+Add to `~/.grok/config.toml` (or `.grok/config.toml` in a project):
+
+```toml
+[mcp_servers.codegraph]
+command = "codegraph"
+args = ["serve", "--mcp"]
+```
+
+Start a new Grok session, or press `r` in `/mcps`, so Grok picks up the server.
diff --git a/src/bin/codegraph.ts b/src/bin/codegraph.ts
index 19038df1b..34038420a 100644
--- a/src/bin/codegraph.ts
+++ b/src/bin/codegraph.ts
@@ -1855,7 +1855,7 @@ program
// ever fires for a person who typed it.
if (process.stdin.isTTY && !process.env.CODEGRAPH_DAEMON_INTERNAL) {
console.error(chalk.bold('\nCodeGraph MCP server\n'));
- console.error("This is the MCP server your AI agent (Claude Code, Cursor, Codex, opencode, …)");
+ console.error("This is the MCP server your AI agent (Claude Code, Cursor, Codex, opencode, Grok, …)");
console.error("starts automatically — you don't run it yourself.");
console.error(`\nIt's already wired up by ${chalk.cyan('codegraph install')}. To check on things:`);
console.error(` ${chalk.cyan('codegraph status')} ${chalk.dim('— is this project indexed and healthy?')}`);
@@ -2340,7 +2340,7 @@ program
*/
program
.command('install')
- .description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, GitHub Copilot)')
+ .description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, Grok, GitHub Copilot)')
.option('-t, --target ', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt')
.option('-l, --location ', 'Install location: "global" or "local". Default: prompt')
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on')
@@ -2454,7 +2454,7 @@ program
*/
program
.command('uninstall')
- .description('Remove codegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, GitHub Copilot)')
+ .description('Remove codegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, Grok, GitHub Copilot)')
.option('-t, --target ', 'Target agent(s): comma-separated ids, or "all". Default: all')
.option('-l, --location ', 'Uninstall location: "global" or "local". Default: prompt')
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=all')
diff --git a/src/installer/index.ts b/src/installer/index.ts
index 199a6de75..21492ca31 100644
--- a/src/installer/index.ts
+++ b/src/installer/index.ts
@@ -3,7 +3,7 @@
*
* Multi-target: writes MCP server config + instructions for the
* agents the user picks (Claude Code, Cursor, Codex CLI, opencode,
- * Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, and GitHub
+ * Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, Grok, and GitHub
* Copilot in VS Code / the Copilot CLI / JetBrains IDEs).
* Defaults to the Claude-only behavior for backwards compatibility
* when no targets are explicitly chosen and nothing else is detected.
@@ -469,8 +469,8 @@ export async function runUninstaller(opts: RunUninstallerOptions): Promise
const sel = await clack.select({
message: 'Remove CodeGraph from all your projects, or just this one?',
options: [
- { value: 'global' as const, label: 'All projects (global)', hint: '~/.claude, ~/.cursor, ~/.codex, ~/.config/opencode, ~/.hermes, ~/.gemini, ~/.kiro, ~/.copilot, ~/.config/github-copilot' },
- { value: 'local' as const, label: 'Just this project (local)', hint: './.claude, ./.cursor, ./.vscode, ./opencode.jsonc, ./.gemini, ./.kiro' },
+ { value: 'global' as const, label: 'All projects (global)', hint: '~/.claude, ~/.cursor, ~/.codex, ~/.config/opencode, ~/.hermes, ~/.gemini, ~/.kiro, ~/.grok, ~/.copilot, ~/.config/github-copilot' },
+ { value: 'local' as const, label: 'Just this project (local)', hint: './.claude, ./.cursor, ./.vscode, ./opencode.jsonc, ./.gemini, ./.kiro, ./.grok' },
],
initialValue: 'global' as const,
});
diff --git a/src/installer/targets/grok.ts b/src/installer/targets/grok.ts
new file mode 100644
index 000000000..f68ef62e6
--- /dev/null
+++ b/src/installer/targets/grok.ts
@@ -0,0 +1,196 @@
+/**
+ * Grok (Grok Build TUI / `grok` CLI) target.
+ *
+ * - MCP server entry to `config.toml` as the dotted-key table
+ * `[mcp_servers.codegraph]`. Same TOML serializer as Codex
+ * (`./toml.ts`).
+ * - Instructions to `$GROK_HOME/rules/codegraph.md` (global) or
+ * `/.grok/rules/codegraph.md` (local). Grok always scans
+ * `rules/*.md` at those locations; it does not load a home-level
+ * `AGENTS.md` the way Codex does.
+ *
+ * Both locations are supported:
+ * - global: `$GROK_HOME/config.toml` (default `~/.grok/config.toml`)
+ * - local: `/.grok/config.toml`
+ *
+ * Project-scoped files contribute `[mcp_servers]` (and `[permission]`,
+ * `[plugins]`). Repo-local MCP servers are gated on folder trust — the
+ * same store as project hooks (`~/.grok/trusted_folders.toml`) — so a
+ * local install is surfaced with a trust note rather than silent
+ * success.
+ *
+ * Honors `$GROK_HOME` (default `~/.grok`).
+ *
+ * No installer-written permissions: Grok's `[permission]` table is
+ * user-owned (sibling allow/deny rules), and MCP tools can be
+ * always-allowed from the first prompt. `autoAllow` is ignored.
+ */
+
+import * as fs from 'fs';
+import * as path from 'path';
+import * as os from 'os';
+import {
+ AgentTarget,
+ DetectionResult,
+ InstallOptions,
+ Location,
+ WriteResult,
+} from './types';
+import {
+ atomicWriteFileSync,
+ getMcpServerConfig,
+ removeMarkedSection,
+ upsertInstructionsEntry,
+} from './shared';
+import {
+ CODEGRAPH_SECTION_END,
+ CODEGRAPH_SECTION_START,
+} from '../instructions-template';
+import { buildTomlTable, removeTomlTable, upsertTomlTable } from './toml';
+
+const TOML_HEADER = 'mcp_servers.codegraph';
+
+function grokHome(): string {
+ return process.env.GROK_HOME
+ ? path.resolve(process.env.GROK_HOME)
+ : path.join(os.homedir(), '.grok');
+}
+
+function configDir(loc: Location): string {
+ return loc === 'global' ? grokHome() : path.join(process.cwd(), '.grok');
+}
+
+function tomlConfigPath(loc: Location): string {
+ return path.join(configDir(loc), 'config.toml');
+}
+
+function instructionsPath(loc: Location): string {
+ return path.join(configDir(loc), 'rules', 'codegraph.md');
+}
+
+/**
+ * Repo-local MCP is skipped until the folder is trusted (same gate as
+ * project hooks). Say so rather than reporting silent success.
+ */
+function trustNote(): string {
+ return `Grok applies ${tomlConfigPath('local')} only in a trusted project — untrusted folders skip repo-local MCP servers. Trust this project in Grok to activate it.`;
+}
+
+class GrokTarget implements AgentTarget {
+ readonly id = 'grok' as const;
+ readonly displayName = 'Grok';
+ readonly docsUrl = 'https://docs.x.ai/build/features/mcp-servers';
+
+ supportsLocation(_loc: Location): boolean {
+ return true;
+ }
+
+ detect(loc: Location): DetectionResult {
+ const tomlPath = tomlConfigPath(loc);
+ let alreadyConfigured = false;
+ if (fs.existsSync(tomlPath)) {
+ try {
+ const content = fs.readFileSync(tomlPath, 'utf-8');
+ alreadyConfigured = content.includes(`[${TOML_HEADER}]`);
+ } catch { /* ignore */ }
+ }
+ // Global: ~/.grok (or $GROK_HOME) existing means Grok has run here.
+ // Local: the project only counts as "Grok-enabled" once it actually
+ // has a .grok/ dir or config file of its own.
+ const installed = fs.existsSync(configDir(loc)) || fs.existsSync(tomlPath);
+ return { installed, alreadyConfigured, configPath: tomlPath };
+ }
+
+ install(loc: Location, _opts: InstallOptions): WriteResult {
+ const files: WriteResult['files'] = [];
+
+ files.push(writeMcpEntry(loc));
+
+ // rules/codegraph.md gets the short marker-fenced CodeGraph block
+ // (#704): subagents and non-MCP harnesses read project rules but
+ // never the MCP initialize instructions. Upsert self-heals a
+ // stale pre-#529 block.
+ files.push(upsertInstructionsEntry(instructionsPath(loc)));
+
+ const notes = [
+ 'Start a new Grok session (or press r in /mcps) for MCP changes to take effect.',
+ ];
+ if (loc === 'local') notes.push(trustNote());
+ return { files, notes };
+ }
+
+ uninstall(loc: Location): WriteResult {
+ const files: WriteResult['files'] = [];
+
+ const tomlPath = tomlConfigPath(loc);
+ if (fs.existsSync(tomlPath)) {
+ const content = fs.readFileSync(tomlPath, 'utf-8');
+ const { content: nextContent, action } = removeTomlTable(content, TOML_HEADER);
+ if (action === 'removed') {
+ if (nextContent.trim() === '') {
+ try { fs.unlinkSync(tomlPath); } catch { /* ignore */ }
+ } else {
+ atomicWriteFileSync(tomlPath, nextContent.trimEnd() + '\n');
+ }
+ files.push({ path: tomlPath, action: 'removed' });
+ } else {
+ files.push({ path: tomlPath, action: 'not-found' });
+ }
+ } else {
+ files.push({ path: tomlPath, action: 'not-found' });
+ }
+
+ files.push(removeInstructionsEntry(loc));
+
+ return { files };
+ }
+
+ printConfig(loc: Location): string {
+ const block = buildCodegraphBlock();
+ return `# Add to ${tomlConfigPath(loc)}\n\n${block}\n`;
+ }
+
+ describePaths(loc: Location): string[] {
+ return [tomlConfigPath(loc), instructionsPath(loc)];
+ }
+}
+
+function buildCodegraphBlock(): string {
+ const mcp = getMcpServerConfig();
+ return buildTomlTable(TOML_HEADER, {
+ command: mcp.command,
+ args: mcp.args,
+ });
+}
+
+function writeMcpEntry(loc: Location): WriteResult['files'][number] {
+ const file = tomlConfigPath(loc);
+ const dir = path.dirname(file);
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
+
+ const block = buildCodegraphBlock();
+ // Single read — `existing === ''` derives both "is the file empty
+ // or absent" and "what was its content," avoiding a TOCTOU window
+ // between two `fs.existsSync` calls.
+ const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : '';
+ const created = existing.length === 0;
+ const { content: nextContent, action } = upsertTomlTable(existing, TOML_HEADER, block);
+
+ if (action === 'unchanged') {
+ return { path: file, action: 'unchanged' };
+ }
+ atomicWriteFileSync(file, nextContent);
+ return { path: file, action: created ? 'created' : 'updated' };
+}
+
+/**
+ * Strip the marker-delimited CodeGraph block from this location's
+ * rules/codegraph.md if a prior install wrote one.
+ */
+function removeInstructionsEntry(loc: Location): WriteResult['files'][number] {
+ const file = instructionsPath(loc);
+ const action = removeMarkedSection(file, CODEGRAPH_SECTION_START, CODEGRAPH_SECTION_END);
+ return { path: file, action };
+}
+
+export const grokTarget: AgentTarget = new GrokTarget();
diff --git a/src/installer/targets/registry.ts b/src/installer/targets/registry.ts
index 3798b39ad..dc1604601 100644
--- a/src/installer/targets/registry.ts
+++ b/src/installer/targets/registry.ts
@@ -16,6 +16,7 @@ import { hermesTarget } from './hermes';
import { geminiTarget } from './gemini';
import { antigravityTarget } from './antigravity';
import { kiroTarget } from './kiro';
+import { grokTarget } from './grok';
import { copilotVscodeTarget } from './copilot-vscode';
import { copilotCliTarget } from './copilot-cli';
import { copilotJetbrainsTarget } from './copilot-jetbrains';
@@ -29,6 +30,7 @@ export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([
geminiTarget,
antigravityTarget,
kiroTarget,
+ grokTarget,
copilotVscodeTarget,
copilotCliTarget,
copilotJetbrainsTarget,
diff --git a/src/installer/targets/toml.ts b/src/installer/targets/toml.ts
index 1dc086bf3..9fd77b87c 100644
--- a/src/installer/targets/toml.ts
+++ b/src/installer/targets/toml.ts
@@ -1,7 +1,7 @@
/**
* Tiny TOML helpers — just enough to inject / replace / remove a
* single dotted-key table block (`[mcp_servers.codegraph]`) inside an
- * existing `~/.codex/config.toml`. We deliberately do NOT try to be a
+ * existing Codex or Grok `config.toml`. We deliberately do NOT try to be a
* general TOML parser/serializer; that would mean pulling in a
* dependency (~50KB) for ~6 lines of output.
*
diff --git a/src/installer/targets/types.ts b/src/installer/targets/types.ts
index d93680573..e5b6e9b47 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' | 'gemini' | 'antigravity' | 'kiro' | 'copilot-vscode' | 'copilot-cli' | 'copilot-jetbrains';
+export type TargetId = 'claude' | 'cursor' | 'codex' | 'opencode' | 'hermes' | 'gemini' | 'antigravity' | 'kiro' | 'grok' | 'copilot-vscode' | 'copilot-cli' | 'copilot-jetbrains';
/**
* Result of `target.detect(location)`.