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
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Already installed? Run `codegraph upgrade` to update in place.

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 OpenClaw with Semantic Code Intelligence

**~16% cheaper · ~58% fewer tool calls · 100% local**

Expand All @@ -27,6 +27,7 @@ Follow [@getcodegraph](https://x.com/getcodegraph) on X for updates.
[![Codex](https://img.shields.io/badge/Codex-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)
[![OpenClaw](https://img.shields.io/badge/OpenClaw-supported-blueviolet.svg)](#supported-agents)
[![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)
Expand Down Expand Up @@ -76,7 +77,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. (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 OpenClaw — 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. (Shortcut: `npx @colbymchenry/codegraph` downloads and runs this in one go.)</sub>

### 3. Initialize each project

Expand Down Expand Up @@ -336,7 +337,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**, **OpenClaw**
- 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` / `codegraph node` commands, since the MCP server's own guidance only reaches the main agent. Removed cleanly by `codegraph uninstall`.
Expand All @@ -362,7 +363,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 / OpenClaw) for the MCP server to load.

### 3. Initialize Projects

Expand Down
137 changes: 137 additions & 0 deletions __tests__/installer-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ describe('Installer targets — contract', () => {
delete seed.mcpServers;
seed.mcp = { other: { type: 'local', command: ['x'], enabled: true } };
}
// openclaw uses `mcp.servers.<name>` (nested).
if (target.id === 'openclaw') {
delete seed.mcpServers;
seed.mcp = { servers: { other: { command: 'x' } } };
}
fs.writeFileSync(jsonPath, JSON.stringify(seed, null, 2) + '\n');

target.install(location, { autoAllow: true });
Expand All @@ -144,6 +149,9 @@ describe('Installer targets — contract', () => {
if (target.id === 'opencode') {
expect(after.mcp.other).toBeDefined();
expect(after.mcp.codegraph).toBeDefined();
} else if (target.id === 'openclaw') {
expect(after.mcp.servers.other).toBeDefined();
expect(after.mcp.servers.codegraph).toBeDefined();
} else {
expect(after.mcpServers.other).toBeDefined();
expect(after.mcpServers.codegraph).toBeDefined();
Expand Down Expand Up @@ -1547,4 +1555,133 @@ describe('Installer targets — opencode XDG config path (#535)', () => {
// But configuration state is read from the REAL path only.
expect(opencode.detect('global').alreadyConfigured).toBe(false);
});

// ── OpenClaw target tests ────────────────────────────────────────
// The OpenClaw target writes to $HOME/.openclaw/openclaw.json under
// mcp.servers.codegraph, and refuses local install (OpenClaw is a
// single gateway, no per-project config layer).

it('openclaw: install writes mcp.servers.codegraph to the resolved config file', () => {
const openclaw = getTarget('openclaw')!;
const result = openclaw.install('global', { autoAllow: true });
const cfg = openclaw.describePaths('global')[0];
expect(result.files.some((f) => f.path === cfg)).toBe(true);
expect(fs.existsSync(cfg)).toBe(true);

const parsed = JSON.parse(fs.readFileSync(cfg, 'utf-8'));
expect(parsed.mcp.servers.codegraph).toBeDefined();
expect(parsed.mcp.servers.codegraph.args).toContain('serve');
expect(parsed.mcp.servers.codegraph.args).toContain('--mcp');
expect(parsed.mcp.servers.codegraph.args).toContain('--path');
expect(parsed.mcp.servers.codegraph.env.CODEGRAPH_TELEMETRY).toBe('0');
});

it('openclaw: install preserves pre-existing sibling MCP servers (chrome-mcp, etc.)', () => {
const openclaw = getTarget('openclaw')!;
const cfg = openclaw.describePaths('global')[0];
fs.mkdirSync(path.dirname(cfg), { recursive: true });
fs.writeFileSync(
cfg,
JSON.stringify(
{
mcp: {
servers: {
'chrome-mcp': { type: 'streamableHttp', url: 'http://example/mcp' },
},
},
},
null,
2,
) + '\n',
);

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

const after = JSON.parse(fs.readFileSync(cfg, 'utf-8'));
expect(after.mcp.servers['chrome-mcp']).toBeDefined();
expect(after.mcp.servers.codegraph).toBeDefined();
});

it('openclaw: install preserves other top-level config keys (channels, agents, gateway)', () => {
const openclaw = getTarget('openclaw')!;
const cfg = openclaw.describePaths('global')[0];
fs.mkdirSync(path.dirname(cfg), { recursive: true });
fs.writeFileSync(
cfg,
JSON.stringify(
{
channels: { telegram: { botToken: 'redacted' } },
agents: { defaults: { model: 'deepseek-v4-pro' } },
gateway: { bind: 'loopback' },
},
null,
2,
) + '\n',
);

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

const after = JSON.parse(fs.readFileSync(cfg, 'utf-8'));
expect(after.channels.telegram.botToken).toBe('redacted');
expect(after.agents.defaults.model).toBe('deepseek-v4-pro');
expect(after.gateway.bind).toBe('loopback');
expect(after.mcp.servers.codegraph).toBeDefined();
});

it('openclaw: install is idempotent (re-run produces action=unchanged)', () => {
const openclaw = getTarget('openclaw')!;
openclaw.install('global', { autoAllow: true });
const second = openclaw.install('global', { autoAllow: true });
expect(second.files.some((f) => f.action === 'unchanged')).toBe(true);
expect(second.files.some((f) => f.action === 'updated')).toBe(false);
});

it('openclaw: uninstall strips mcp.servers.codegraph but leaves siblings intact', () => {
const openclaw = getTarget('openclaw')!;
const cfg = openclaw.describePaths('global')[0];
fs.mkdirSync(path.dirname(cfg), { recursive: true });
fs.writeFileSync(
cfg,
JSON.stringify(
{
mcp: {
servers: {
'chrome-mcp': { type: 'streamableHttp', url: 'http://example/mcp' },
},
},
},
null,
2,
) + '\n',
);

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

const after = JSON.parse(fs.readFileSync(cfg, 'utf-8'));
expect(after.mcp.servers['chrome-mcp']).toBeDefined();
expect(after.mcp.servers.codegraph).toBeUndefined();
});

it('openclaw: local install returns a note and writes nothing', () => {
const openclaw = getTarget('openclaw')!;
expect(openclaw.supportsLocation('local')).toBe(false);
const result = openclaw.install('local', { autoAllow: true });
expect(result.files).toEqual([]);
expect(result.notes && result.notes.length > 0).toBe(true);
});

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

it('openclaw: detect after install shows alreadyConfigured=true', () => {
const openclaw = getTarget('openclaw')!;
openclaw.install('global', { autoAllow: true });
const d = openclaw.detect('global');
expect(d.installed).toBe(true);
expect(d.alreadyConfigured).toBe(true);
});
});
Loading