Skip to content

Commit 95dace9

Browse files
authored
feat(installer): add Hermes Agent target (colbymchenry#274)
Adds Hermes Agent (Nous Research) as a CodeGraph installer target. Writes mcp_servers.codegraph and ensures platform_toolsets.cli includes mcp-codegraph in $HERMES_HOME/config.yaml, with full installer contract-test coverage.
1 parent cda42c8 commit 95dace9

7 files changed

Lines changed: 374 additions & 11 deletions

File tree

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# CodeGraph
44

5-
### Supercharge Claude Code, Cursor, Codex, and OpenCode with Semantic Code Intelligence
5+
### Supercharge Claude Code, Cursor, Codex, OpenCode, and Hermes Agent with Semantic Code Intelligence
66

77
**~35% cheaper · ~70% fewer tool calls · 100% local**
88

@@ -18,6 +18,7 @@
1818
[![Cursor](https://img.shields.io/badge/Cursor-supported-blueviolet.svg)](#)
1919
[![Codex CLI](https://img.shields.io/badge/Codex_CLI-supported-blueviolet.svg)](#)
2020
[![opencode](https://img.shields.io/badge/opencode-supported-blueviolet.svg)](#)
21+
[![Hermes Agent](https://img.shields.io/badge/Hermes_Agent-supported-blueviolet.svg)](#)
2122

2223
</div>
2324

@@ -40,7 +41,7 @@ npx @colbymchenry/codegraph # zero-install, or:
4041
npm i -g @colbymchenry/codegraph
4142
```
4243

43-
<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.</sub>
44+
<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>
4445

4546
### Initialize Projects
4647

@@ -159,7 +160,7 @@ npx @colbymchenry/codegraph
159160
```
160161

161162
The installer will:
162-
- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**
163+
- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**
163164
- Prompt to install `codegraph` on your PATH (so agents can launch the MCP server)
164165
- Ask whether configs apply to all your projects or just this one
165166
- Write each chosen agent's MCP server config + an instructions file (e.g. `CLAUDE.md`, `.cursor/rules/codegraph.mdc`, `~/.codex/AGENTS.md`)
@@ -185,7 +186,7 @@ codegraph install --print-config codex # print snippet, no file wr
185186

186187
### 2. Restart Your Agent
187188

188-
Restart your agent (Claude Code / Cursor / Codex CLI / opencode) for the MCP server to load.
189+
Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent) for the MCP server to load.
189190

190191
### 3. Initialize Projects
191192

@@ -498,7 +499,7 @@ MIT
498499

499500
<div align="center">
500501

501-
**Made for AI coding agents — Claude Code, Cursor, Codex CLI, and opencode**
502+
**Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, and Hermes Agent**
502503

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

__tests__/installer-targets.test.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,25 @@ function mkTmpDir(label: string): string {
3131
// `os.homedir()` reads first. Same trick the rest of the suite uses
3232
// when it needs a mock home.
3333
function setHome(dir: string): { restore: () => void } {
34-
const prev = { HOME: process.env.HOME, USERPROFILE: process.env.USERPROFILE };
34+
const prev = {
35+
HOME: process.env.HOME,
36+
USERPROFILE: process.env.USERPROFILE,
37+
APPDATA: process.env.APPDATA,
38+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
39+
HERMES_HOME: process.env.HERMES_HOME,
40+
};
3541
process.env.HOME = dir;
3642
process.env.USERPROFILE = dir;
43+
process.env.APPDATA = path.join(dir, '.config');
44+
process.env.XDG_CONFIG_HOME = path.join(dir, '.config');
45+
delete process.env.HERMES_HOME;
3746
return {
3847
restore() {
3948
if (prev.HOME === undefined) delete process.env.HOME; else process.env.HOME = prev.HOME;
4049
if (prev.USERPROFILE === undefined) delete process.env.USERPROFILE; else process.env.USERPROFILE = prev.USERPROFILE;
50+
if (prev.APPDATA === undefined) delete process.env.APPDATA; else process.env.APPDATA = prev.APPDATA;
51+
if (prev.XDG_CONFIG_HOME === undefined) delete process.env.XDG_CONFIG_HOME; else process.env.XDG_CONFIG_HOME = prev.XDG_CONFIG_HOME;
52+
if (prev.HERMES_HOME === undefined) delete process.env.HERMES_HOME; else process.env.HERMES_HOME = prev.HERMES_HOME;
4153
},
4254
};
4355
}
@@ -298,12 +310,59 @@ describe('Installer targets — partial-state idempotency', () => {
298310
it('opencode: local install writes ./opencode.jsonc and ./AGENTS.md in cwd', () => {
299311
const opencode = getTarget('opencode')!;
300312
const result = opencode.install('local', { autoAllow: true });
301-
const paths = result.files.map((f) => f.path);
313+
const paths = result.files.map((f) => f.path.replace(/\\/g, '/'));
302314
// macOS realpath shenanigans (/var vs /private/var) — suffix match.
303315
expect(paths.some((p) => p.endsWith('/opencode.jsonc'))).toBe(true);
304316
expect(paths.some((p) => p.endsWith('/AGENTS.md'))).toBe(true);
305317
});
306318

319+
it('hermes: install adds codegraph MCP server and cli toolset, preserving existing yaml', () => {
320+
const hermes = getTarget('hermes')!;
321+
const config = path.join(tmpHome, '.hermes', 'config.yaml');
322+
fs.mkdirSync(path.dirname(config), { recursive: true });
323+
fs.writeFileSync(config, [
324+
'model:',
325+
' default: qwen-3.7',
326+
'mcp_servers:',
327+
' other:',
328+
' command: other',
329+
'platform_toolsets:',
330+
' cli:',
331+
' - hermes-cli',
332+
' discord:',
333+
' - hermes-discord',
334+
'',
335+
].join('\n'));
336+
337+
const result = hermes.install('global', { autoAllow: true });
338+
expect(result.files[0].action).toBe('updated');
339+
const body = fs.readFileSync(config, 'utf-8');
340+
expect(body).toContain('model:\n default: qwen-3.7');
341+
expect(body).toContain('mcp_servers:\n other:\n command: other');
342+
expect(body).toContain(' codegraph:\n command: codegraph');
343+
expect(body).toContain(' - hermes-cli');
344+
expect(body).toContain(' - mcp-codegraph');
345+
expect(body).toContain(' discord:\n - hermes-discord');
346+
347+
const second = hermes.install('global', { autoAllow: true });
348+
expect(second.files[0].action).toBe('unchanged');
349+
});
350+
351+
it('hermes: uninstall removes only codegraph MCP server and toolset entry', () => {
352+
const hermes = getTarget('hermes')!;
353+
const config = path.join(tmpHome, '.hermes', 'config.yaml');
354+
fs.mkdirSync(path.dirname(config), { recursive: true });
355+
356+
hermes.install('global', { autoAllow: true });
357+
fs.appendFileSync(config, 'custom:\n keep: true\n');
358+
359+
hermes.uninstall('global');
360+
const body = fs.readFileSync(config, 'utf-8');
361+
expect(body).not.toContain('codegraph:');
362+
expect(body).not.toContain('mcp-codegraph');
363+
expect(body).toContain('custom:\n keep: true');
364+
});
365+
307366
it('opencode: uninstall removes only mcp.codegraph, preserves comments and siblings', () => {
308367
const opencode = getTarget('opencode')!;
309368
const dir = path.join(tmpHome, '.config', 'opencode');
@@ -358,7 +417,7 @@ describe('Installer targets — partial-state idempotency', () => {
358417
const claude = getTarget('claude')!;
359418
const result = claude.install('local', { autoAllow: false });
360419
// The MCP entry lands in ./.mcp.json — the file Claude Code reads.
361-
expect(result.files.some((f) => f.path.endsWith('/.mcp.json'))).toBe(true);
420+
expect(result.files.some((f) => f.path.replace(/\\/g, '/').endsWith('/.mcp.json'))).toBe(true);
362421
expect(fs.existsSync(path.join(tmpCwd, '.mcp.json'))).toBe(true);
363422
expect(fs.existsSync(path.join(tmpCwd, '.claude.json'))).toBe(false);
364423
const cfg = JSON.parse(fs.readFileSync(path.join(tmpCwd, '.mcp.json'), 'utf-8'));
@@ -556,6 +615,7 @@ describe('Installer targets — registry', () => {
556615
expect(getTarget('cursor')?.id).toBe('cursor');
557616
expect(getTarget('codex')?.id).toBe('codex');
558617
expect(getTarget('opencode')?.id).toBe('opencode');
618+
expect(getTarget('hermes')?.id).toBe('hermes');
559619
expect(getTarget('not-a-real-target')).toBeUndefined();
560620
});
561621

src/bin/codegraph.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1341,7 +1341,7 @@ program
13411341
*/
13421342
program
13431343
.command('install')
1344-
.description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode)')
1344+
.description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent)')
13451345
.option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt')
13461346
.option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt')
13471347
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on')

src/installer/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
* CodeGraph Interactive Installer
33
*
44
* Multi-target: writes MCP server config + instructions for the
5-
* agents the user picks (Claude Code, Cursor, Codex CLI, opencode).
5+
* agents the user picks (Claude Code, Cursor, Codex CLI, opencode,
6+
* Hermes Agent).
67
* Defaults to the Claude-only behavior for backwards compatibility
78
* when no targets are explicitly chosen and nothing else is detected.
89
*

0 commit comments

Comments
 (0)