mindgraph-mcp 0.14.7 → 0.14.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -7
- package/dist/cli.js +204 -90
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/mindgraph-mcp)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
|
|
6
|
-
Model Context Protocol server for [MindGraph](https://mindgraph.cloud) — plug your persistent, structured knowledge graph into Claude Desktop, Claude Code, and any MCP-compatible client.
|
|
6
|
+
Model Context Protocol server for [MindGraph](https://mindgraph.cloud) — plug your persistent, structured knowledge graph into Claude Desktop, Claude Code, Codex, and any MCP-compatible client.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
@@ -77,7 +77,10 @@ which state you're in).
|
|
|
77
77
|
|
|
78
78
|
The hooks open/rebind a Session at start and inject the current work brief,
|
|
79
79
|
stamp every MindGraph tool call with verified session/repo/commit provenance,
|
|
80
|
-
and prompt one reflection checkpoint before a substantial session ends.
|
|
80
|
+
and prompt one reflection checkpoint before a substantial session ends. A
|
|
81
|
+
session automatically re-claims only *this* agent's own prior work; other
|
|
82
|
+
tasks appear in the brief as context, and the agent claims one deliberately
|
|
83
|
+
when it starts that work. They
|
|
81
84
|
**fail open** — if MindGraph is unreachable, your coding session is never
|
|
82
85
|
blocked. `--scope project` (with `--project-dir`) installs into a single
|
|
83
86
|
repository's `.claude/settings.json` instead; `uninstall-hooks` reverses either
|
|
@@ -87,18 +90,36 @@ project settings.
|
|
|
87
90
|
|
|
88
91
|
### Coding agents (Codex)
|
|
89
92
|
|
|
90
|
-
|
|
93
|
+
Codex gets the same durable work substrate through its native command hooks.
|
|
94
|
+
Register the MCP server with a distinct agent identity, then install the hooks:
|
|
91
95
|
|
|
92
96
|
```bash
|
|
93
97
|
codex mcp add mindgraph --env MINDGRAPH_API_KEY=mg_your_key_here \
|
|
94
98
|
--env MINDGRAPH_PROFILE=coding --env MINDGRAPH_HARNESS=codex \
|
|
99
|
+
--env MINDGRAPH_AGENT_ID=codex:your-name \
|
|
95
100
|
-- npx -y mindgraph-mcp@latest
|
|
101
|
+
|
|
102
|
+
npx -y mindgraph-mcp install-hooks --harness codex --scope user \
|
|
103
|
+
--api-key mg_your_key_here --agent-id codex:your-name
|
|
96
104
|
```
|
|
97
105
|
|
|
98
106
|
(or the equivalent `[mcp_servers.mindgraph]` block in Codex's `config.toml`).
|
|
99
|
-
The
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
The installer copies the pinned self-contained runner to
|
|
108
|
+
`~/.mindgraph/bin/mindgraph-hook.cjs`, merge-safely upserts only entries marked
|
|
109
|
+
`--owner mindgraph`, and writes user hooks to `$CODEX_HOME/hooks.json`
|
|
110
|
+
(`~/.codex/hooks.json` by default). Use `--scope project --project-dir …` for
|
|
111
|
+
`<project>/.codex/hooks.json`. Re-run the install command to refresh owned
|
|
112
|
+
entries; foreign hooks are never changed. Codex requires newly installed or
|
|
113
|
+
changed command hooks to be reviewed with `/hooks` before they run.
|
|
114
|
+
|
|
115
|
+
At SessionStart the adapter opens/rebinds the graph Session and injects one
|
|
116
|
+
bounded work brief, re-claiming only this agent's own prior work.
|
|
117
|
+
PreToolUse replaces forged/absent invocation provenance
|
|
118
|
+
while preserving model-selected work targets, PostToolUse updates only the
|
|
119
|
+
disposable runtime ledger, Stop runs the once-per-session reflection
|
|
120
|
+
checkpoint, and SessionEnd performs best-effort cleanup. All paths fail open:
|
|
121
|
+
missing credentials or an unreachable MindGraph return Codex's no-op response
|
|
122
|
+
and never block the coding session.
|
|
102
123
|
|
|
103
124
|
## What You Get
|
|
104
125
|
|
|
@@ -146,7 +167,7 @@ mindgraph-mcp install Install into Claude Desktop config
|
|
|
146
167
|
mindgraph-mcp install-code Install into Claude Code (--hooks: also install session hooks)
|
|
147
168
|
mindgraph-mcp uninstall Remove from Claude Desktop config
|
|
148
169
|
mindgraph-mcp uninstall-code Remove from Claude Code
|
|
149
|
-
mindgraph-mcp install-hooks Install
|
|
170
|
+
mindgraph-mcp install-hooks Install coding hooks (--harness claude-code|codex)
|
|
150
171
|
mindgraph-mcp uninstall-hooks Remove MindGraph-owned hook entries
|
|
151
172
|
mindgraph-mcp status Show installation status
|
|
152
173
|
```
|
package/dist/cli.js
CHANGED
|
@@ -4917,7 +4917,7 @@ Capture knowledge when the user shares something worth remembering:
|
|
|
4917
4917
|
server = new import_server.Server(
|
|
4918
4918
|
{
|
|
4919
4919
|
name: "mindgraph",
|
|
4920
|
-
version: "0.14.
|
|
4920
|
+
version: "0.14.8"
|
|
4921
4921
|
},
|
|
4922
4922
|
{
|
|
4923
4923
|
capabilities: {
|
|
@@ -5182,7 +5182,7 @@ function saveHookEnv(env, dir) {
|
|
|
5182
5182
|
// src/cli-args.ts
|
|
5183
5183
|
function parseArgs(argv) {
|
|
5184
5184
|
const args = argv.slice(2);
|
|
5185
|
-
let
|
|
5185
|
+
let command2 = "serve";
|
|
5186
5186
|
let apiKey = process.env.MINDGRAPH_API_KEY;
|
|
5187
5187
|
let baseUrl = process.env.MINDGRAPH_BASE_URL;
|
|
5188
5188
|
let scope = "project";
|
|
@@ -5224,16 +5224,16 @@ function parseArgs(argv) {
|
|
|
5224
5224
|
break;
|
|
5225
5225
|
case "--help":
|
|
5226
5226
|
case "-h":
|
|
5227
|
-
|
|
5227
|
+
command2 = "help";
|
|
5228
5228
|
break;
|
|
5229
5229
|
default:
|
|
5230
5230
|
if (!arg.startsWith("-")) {
|
|
5231
|
-
|
|
5231
|
+
command2 = arg;
|
|
5232
5232
|
}
|
|
5233
5233
|
break;
|
|
5234
5234
|
}
|
|
5235
5235
|
}
|
|
5236
|
-
return { command, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId };
|
|
5236
|
+
return { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId };
|
|
5237
5237
|
}
|
|
5238
5238
|
function mcpAddFailureOutput(e) {
|
|
5239
5239
|
const err5 = e;
|
|
@@ -5247,16 +5247,17 @@ function classifyMcpAddFailure(e) {
|
|
|
5247
5247
|
return "other";
|
|
5248
5248
|
}
|
|
5249
5249
|
|
|
5250
|
-
// src/
|
|
5250
|
+
// src/hook-core.ts
|
|
5251
5251
|
var import_node_crypto2 = __toESM(require("crypto"));
|
|
5252
5252
|
var import_node_fs3 = __toESM(require("fs"));
|
|
5253
5253
|
var import_node_os3 = __toESM(require("os"));
|
|
5254
5254
|
var import_node_path3 = __toESM(require("path"));
|
|
5255
5255
|
var import_node_child_process2 = require("child_process");
|
|
5256
5256
|
init_codegraph();
|
|
5257
|
-
|
|
5257
|
+
var NOOP = { kind: "noop" };
|
|
5258
|
+
function defaultLedger(sessionId, options) {
|
|
5258
5259
|
return {
|
|
5259
|
-
harness:
|
|
5260
|
+
harness: options.harness,
|
|
5260
5261
|
harnessSessionId: sessionId,
|
|
5261
5262
|
materialToolCalls: 0,
|
|
5262
5263
|
mutatingOperations: 0,
|
|
@@ -5273,7 +5274,8 @@ function runtimeDir(options) {
|
|
|
5273
5274
|
return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path3.default.join(import_node_os3.default.homedir(), ".mindgraph", "runtime");
|
|
5274
5275
|
}
|
|
5275
5276
|
function ledgerPath(sessionId, options) {
|
|
5276
|
-
const
|
|
5277
|
+
const harness = "harness" in options ? options.harness : "claude-code";
|
|
5278
|
+
const key = import_node_crypto2.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
|
|
5277
5279
|
return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
|
|
5278
5280
|
}
|
|
5279
5281
|
function waitBriefly(ms) {
|
|
@@ -5302,10 +5304,10 @@ function withLedger(sessionId, options, update) {
|
|
|
5302
5304
|
}
|
|
5303
5305
|
}
|
|
5304
5306
|
if (lockFd === void 0) {
|
|
5305
|
-
return update(defaultLedger(sessionId));
|
|
5307
|
+
return update(defaultLedger(sessionId, options));
|
|
5306
5308
|
}
|
|
5307
5309
|
try {
|
|
5308
|
-
let ledger = defaultLedger(sessionId);
|
|
5310
|
+
let ledger = defaultLedger(sessionId, options);
|
|
5309
5311
|
try {
|
|
5310
5312
|
ledger = {
|
|
5311
5313
|
...ledger,
|
|
@@ -5356,8 +5358,9 @@ async function invocationContext(input, options) {
|
|
|
5356
5358
|
workspaceFile: options.workspaceFile
|
|
5357
5359
|
}).resolveRepository();
|
|
5358
5360
|
return {
|
|
5359
|
-
harness:
|
|
5361
|
+
harness: options.harness,
|
|
5360
5362
|
harnessSessionId: input.session_id,
|
|
5363
|
+
harnessTurnId: input.turn_id,
|
|
5361
5364
|
cwd: input.cwd,
|
|
5362
5365
|
repoId: repository.repoId,
|
|
5363
5366
|
branch: gitValue(input.cwd, ["branch", "--show-current"]),
|
|
@@ -5434,13 +5437,21 @@ async function repositoryScopeUids(input, client2, options, agentId) {
|
|
|
5434
5437
|
)
|
|
5435
5438
|
];
|
|
5436
5439
|
}
|
|
5440
|
+
function isOwnPriorWork(brief, agentId) {
|
|
5441
|
+
const reason = string(brief?.selection_reason);
|
|
5442
|
+
if (reason === "owned_live_lease" || reason === "same_agent_session_rebind") {
|
|
5443
|
+
return true;
|
|
5444
|
+
}
|
|
5445
|
+
return string(object(brief?.lease)?.lease_owner_agent_id) === agentId;
|
|
5446
|
+
}
|
|
5437
5447
|
async function sessionStart(input, client2, options) {
|
|
5438
|
-
const agentId = options.agentId || process.env.MINDGRAPH_AGENT_ID ||
|
|
5448
|
+
const agentId = options.agentId || process.env.MINDGRAPH_AGENT_ID || options.harness;
|
|
5449
|
+
const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
|
|
5439
5450
|
const opened = object(
|
|
5440
5451
|
await client2.session({
|
|
5441
5452
|
action: "open",
|
|
5442
|
-
label:
|
|
5443
|
-
harness:
|
|
5453
|
+
label: `${harnessLabel} ${input.session_id}`,
|
|
5454
|
+
harness: options.harness,
|
|
5444
5455
|
harness_session_id: input.session_id,
|
|
5445
5456
|
model: input.model,
|
|
5446
5457
|
agent_id: agentId
|
|
@@ -5458,7 +5469,7 @@ async function sessionStart(input, client2, options) {
|
|
|
5458
5469
|
);
|
|
5459
5470
|
const task = object(brief?.task);
|
|
5460
5471
|
const lease = object(brief?.lease);
|
|
5461
|
-
if (sessionUid && string(task?.uid) && number(task?.version)) {
|
|
5472
|
+
if (sessionUid && string(task?.uid) && number(task?.version) && isOwnPriorWork(brief, agentId)) {
|
|
5462
5473
|
try {
|
|
5463
5474
|
const claimed = object(
|
|
5464
5475
|
await client2.plan({
|
|
@@ -5483,7 +5494,11 @@ async function sessionStart(input, client2, options) {
|
|
|
5483
5494
|
} catch {
|
|
5484
5495
|
}
|
|
5485
5496
|
}
|
|
5486
|
-
|
|
5497
|
+
const briefText = boundedBrief(brief || { status: "no_eligible_work" });
|
|
5498
|
+
const briefHash = import_node_crypto2.default.createHash("sha256").update(briefText).digest("hex");
|
|
5499
|
+
const shouldInject = withLedger(input.session_id, options, (ledger) => {
|
|
5500
|
+
const changed = ledger.lastBriefHash !== briefHash;
|
|
5501
|
+
const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
|
|
5487
5502
|
ledger.sessionUid = sessionUid;
|
|
5488
5503
|
ledger.baselineWorktree = worktreeState(input.cwd);
|
|
5489
5504
|
const selectedTask = object(brief?.task);
|
|
@@ -5495,14 +5510,10 @@ async function sessionStart(input, client2, options) {
|
|
|
5495
5510
|
ledger.leaseEpoch = number(selectedLease?.lease_epoch);
|
|
5496
5511
|
ledger.leaseExpiresAt = number(selectedLease?.lease_expires_at);
|
|
5497
5512
|
ledger.executionUid = string(object(brief?.active_execution)?.uid) || string(latest?.uid);
|
|
5498
|
-
|
|
5513
|
+
ledger.lastBriefHash = briefHash;
|
|
5514
|
+
return changed || reinjectCompact;
|
|
5499
5515
|
});
|
|
5500
|
-
return {
|
|
5501
|
-
hookSpecificOutput: {
|
|
5502
|
-
hookEventName: "SessionStart",
|
|
5503
|
-
additionalContext: boundedBrief(brief || { status: "no_eligible_work" })
|
|
5504
|
-
}
|
|
5505
|
-
};
|
|
5516
|
+
return shouldInject ? { kind: "context", context: briefText } : NOOP;
|
|
5506
5517
|
}
|
|
5507
5518
|
async function preTool(input, options) {
|
|
5508
5519
|
const original = { ...input.tool_input || {} };
|
|
@@ -5527,12 +5538,7 @@ async function preTool(input, options) {
|
|
|
5527
5538
|
if (ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
|
|
5528
5539
|
updatedInput.expected_version = ledger.taskVersion;
|
|
5529
5540
|
}
|
|
5530
|
-
return {
|
|
5531
|
-
hookSpecificOutput: {
|
|
5532
|
-
hookEventName: "PreToolUse",
|
|
5533
|
-
updatedInput
|
|
5534
|
-
}
|
|
5535
|
-
};
|
|
5541
|
+
return { kind: "rewrite", updatedInput };
|
|
5536
5542
|
}
|
|
5537
5543
|
async function postTool(input, client2, options) {
|
|
5538
5544
|
const name = input.tool_name || "";
|
|
@@ -5543,7 +5549,7 @@ async function postTool(input, client2, options) {
|
|
|
5543
5549
|
let heartbeat;
|
|
5544
5550
|
withLedger(input.session_id, options, (ledger) => {
|
|
5545
5551
|
ledger.materialToolCalls += 1;
|
|
5546
|
-
if (["Edit", "Write", "NotebookEdit"].includes(name) || name === "Bash" && action !== "read") {
|
|
5552
|
+
if (["Edit", "Write", "NotebookEdit", "apply_patch"].includes(name) || name === "Bash" && action !== "read") {
|
|
5547
5553
|
ledger.mutatingOperations += 1;
|
|
5548
5554
|
}
|
|
5549
5555
|
if (name.includes("mindgraph_capture") && !payload?.error) {
|
|
@@ -5583,7 +5589,7 @@ async function postTool(input, client2, options) {
|
|
|
5583
5589
|
expected_version: heartbeat.taskVersion,
|
|
5584
5590
|
lease_epoch: heartbeat.leaseEpoch,
|
|
5585
5591
|
idempotency_key: `hook-heartbeat:${input.session_id}:${heartbeat.taskUid}:${Math.floor(nowSeconds / 60)}`,
|
|
5586
|
-
agent_id: options.agentId ||
|
|
5592
|
+
agent_id: options.agentId || options.harness
|
|
5587
5593
|
})
|
|
5588
5594
|
);
|
|
5589
5595
|
withLedger(input.session_id, options, (ledger) => {
|
|
@@ -5593,24 +5599,25 @@ async function postTool(input, client2, options) {
|
|
|
5593
5599
|
} catch {
|
|
5594
5600
|
}
|
|
5595
5601
|
}
|
|
5596
|
-
return
|
|
5602
|
+
return NOOP;
|
|
5597
5603
|
}
|
|
5598
5604
|
function stop(input, options) {
|
|
5599
|
-
if (input.stop_hook_active) return
|
|
5605
|
+
if (input.stop_hook_active) return NOOP;
|
|
5600
5606
|
return withLedger(input.session_id, options, (ledger) => {
|
|
5601
5607
|
const changed = ledger.mutatingOperations >= 2 || ledger.baselineWorktree !== "unknown" && worktreeState(input.cwd) !== ledger.baselineWorktree;
|
|
5602
5608
|
const substantial = Boolean(ledger.executionUid) || changed;
|
|
5603
5609
|
if (!substantial || ledger.iterationCheckpointed || ledger.memoryWritten || ledger.stopNudged) {
|
|
5604
|
-
return
|
|
5610
|
+
return NOOP;
|
|
5605
5611
|
}
|
|
5606
5612
|
ledger.stopNudged = true;
|
|
5607
5613
|
return {
|
|
5608
|
-
|
|
5614
|
+
kind: "block",
|
|
5609
5615
|
reason: "Substantial work is still uncheckpointed. Checkpoint the active iteration, capture one durable lesson/decision/risk, or explicitly state that nothing durable changed. If a local harness task should become authoritative outside this list, promote it once with a stable idempotency key; otherwise do not copy it."
|
|
5610
5616
|
};
|
|
5611
5617
|
});
|
|
5612
5618
|
}
|
|
5613
5619
|
async function sessionEnd(input, client2, options) {
|
|
5620
|
+
const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
|
|
5614
5621
|
const ledger = readLedger(input.session_id, options);
|
|
5615
5622
|
if (ledger.executionUid && ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0) {
|
|
5616
5623
|
try {
|
|
@@ -5622,9 +5629,9 @@ async function sessionEnd(input, client2, options) {
|
|
|
5622
5629
|
expected_version: ledger.taskVersion,
|
|
5623
5630
|
lease_epoch: ledger.leaseEpoch,
|
|
5624
5631
|
release_lease: true,
|
|
5625
|
-
summary:
|
|
5632
|
+
summary: `${harnessLabel} SessionEnd: ${input.reason || "interrupted"}`,
|
|
5626
5633
|
idempotency_key: `session-end:${input.session_id}:${ledger.executionUid}`,
|
|
5627
|
-
agent_id: options.agentId ||
|
|
5634
|
+
agent_id: options.agentId || options.harness
|
|
5628
5635
|
});
|
|
5629
5636
|
} catch {
|
|
5630
5637
|
}
|
|
@@ -5634,19 +5641,19 @@ async function sessionEnd(input, client2, options) {
|
|
|
5634
5641
|
await client2.session({
|
|
5635
5642
|
action: "close",
|
|
5636
5643
|
session_uid: ledger.sessionUid,
|
|
5637
|
-
agent_id: options.agentId ||
|
|
5644
|
+
agent_id: options.agentId || options.harness
|
|
5638
5645
|
});
|
|
5639
5646
|
} catch {
|
|
5640
5647
|
}
|
|
5641
5648
|
}
|
|
5642
|
-
return
|
|
5649
|
+
return NOOP;
|
|
5643
5650
|
}
|
|
5644
|
-
async function
|
|
5651
|
+
async function runHookCore(input, client2, options) {
|
|
5645
5652
|
switch (input.hook_event_name) {
|
|
5646
5653
|
case "SessionStart":
|
|
5647
5654
|
return sessionStart(input, client2, options);
|
|
5648
5655
|
case "PreToolUse":
|
|
5649
|
-
if (!input.tool_name?.startsWith("mcp__mindgraph__")) return
|
|
5656
|
+
if (!input.tool_name?.startsWith("mcp__mindgraph__")) return NOOP;
|
|
5650
5657
|
return preTool(input, options);
|
|
5651
5658
|
case "PostToolUse":
|
|
5652
5659
|
return postTool(input, client2, options);
|
|
@@ -5655,19 +5662,19 @@ async function runClaudeHook(input, client2, options = {}) {
|
|
|
5655
5662
|
ledger.nativeTasksCreated += 1;
|
|
5656
5663
|
return void 0;
|
|
5657
5664
|
});
|
|
5658
|
-
return
|
|
5665
|
+
return NOOP;
|
|
5659
5666
|
case "TaskCompleted":
|
|
5660
5667
|
withLedger(input.session_id, options, (ledger) => {
|
|
5661
5668
|
ledger.nativeTasksTerminal += 1;
|
|
5662
5669
|
return void 0;
|
|
5663
5670
|
});
|
|
5664
|
-
return
|
|
5671
|
+
return NOOP;
|
|
5665
5672
|
case "Stop":
|
|
5666
5673
|
return stop(input, options);
|
|
5667
5674
|
case "SessionEnd":
|
|
5668
5675
|
return sessionEnd(input, client2, options);
|
|
5669
5676
|
default:
|
|
5670
|
-
return
|
|
5677
|
+
return NOOP;
|
|
5671
5678
|
}
|
|
5672
5679
|
}
|
|
5673
5680
|
async function readHookInput(stream = process.stdin) {
|
|
@@ -5678,6 +5685,68 @@ async function readHookInput(stream = process.stdin) {
|
|
|
5678
5685
|
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
5679
5686
|
}
|
|
5680
5687
|
|
|
5688
|
+
// src/claude-hooks.ts
|
|
5689
|
+
async function runClaudeHook(input, client2, options = {}) {
|
|
5690
|
+
const output = await runHookCore(input, client2, {
|
|
5691
|
+
...options,
|
|
5692
|
+
harness: "claude-code",
|
|
5693
|
+
// D10: compaction is a new Claude context boundary, so the same brief must
|
|
5694
|
+
// be re-injected even when its content hash is unchanged.
|
|
5695
|
+
reinjectUnchangedCompact: true
|
|
5696
|
+
});
|
|
5697
|
+
switch (output.kind) {
|
|
5698
|
+
case "context":
|
|
5699
|
+
return {
|
|
5700
|
+
hookSpecificOutput: {
|
|
5701
|
+
hookEventName: "SessionStart",
|
|
5702
|
+
additionalContext: output.context
|
|
5703
|
+
}
|
|
5704
|
+
};
|
|
5705
|
+
case "rewrite":
|
|
5706
|
+
return {
|
|
5707
|
+
hookSpecificOutput: {
|
|
5708
|
+
hookEventName: "PreToolUse",
|
|
5709
|
+
updatedInput: output.updatedInput
|
|
5710
|
+
}
|
|
5711
|
+
};
|
|
5712
|
+
case "block":
|
|
5713
|
+
return { decision: "block", reason: output.reason };
|
|
5714
|
+
case "noop":
|
|
5715
|
+
return {};
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5718
|
+
|
|
5719
|
+
// src/codex-hooks.ts
|
|
5720
|
+
async function runCodexHook(input, client2, options = {}) {
|
|
5721
|
+
const output = await runHookCore(input, client2, {
|
|
5722
|
+
...options,
|
|
5723
|
+
harness: "codex",
|
|
5724
|
+
// B7's native contract suppresses repeated unchanged brief injections.
|
|
5725
|
+
reinjectUnchangedCompact: false
|
|
5726
|
+
});
|
|
5727
|
+
switch (output.kind) {
|
|
5728
|
+
case "context":
|
|
5729
|
+
return {
|
|
5730
|
+
hookSpecificOutput: {
|
|
5731
|
+
hookEventName: "SessionStart",
|
|
5732
|
+
additionalContext: output.context
|
|
5733
|
+
}
|
|
5734
|
+
};
|
|
5735
|
+
case "rewrite":
|
|
5736
|
+
return {
|
|
5737
|
+
hookSpecificOutput: {
|
|
5738
|
+
hookEventName: "PreToolUse",
|
|
5739
|
+
permissionDecision: "allow",
|
|
5740
|
+
updatedInput: output.updatedInput
|
|
5741
|
+
}
|
|
5742
|
+
};
|
|
5743
|
+
case "block":
|
|
5744
|
+
return { decision: "block", reason: output.reason };
|
|
5745
|
+
case "noop":
|
|
5746
|
+
return {};
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
|
|
5681
5750
|
// src/hook-installer.ts
|
|
5682
5751
|
var import_node_fs4 = __toESM(require("fs"));
|
|
5683
5752
|
var import_node_os4 = __toESM(require("os"));
|
|
@@ -5685,59 +5754,78 @@ var import_node_path4 = __toESM(require("path"));
|
|
|
5685
5754
|
var import_node_crypto3 = __toESM(require("crypto"));
|
|
5686
5755
|
var OWNER_MARKER = "--owner mindgraph";
|
|
5687
5756
|
var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
|
|
5688
|
-
|
|
5757
|
+
function command(harness) {
|
|
5758
|
+
return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
|
|
5759
|
+
}
|
|
5760
|
+
function windowsCommand(harness) {
|
|
5761
|
+
return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
|
|
5762
|
+
}
|
|
5689
5763
|
function installHookRunner(sourceFile, homeDir) {
|
|
5690
5764
|
const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
|
|
5691
5765
|
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
|
|
5692
5766
|
import_node_fs4.default.copyFileSync(sourceFile, target);
|
|
5693
5767
|
return target;
|
|
5694
5768
|
}
|
|
5695
|
-
function
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
} catch {
|
|
5769
|
+
function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path4.default.join(import_node_os4.default.homedir(), ".codex")) {
|
|
5770
|
+
if (harness === "codex") {
|
|
5771
|
+
return scope === "user" ? import_node_path4.default.join(codexHomeDir, "hooks.json") : import_node_path4.default.join(projectDir, ".codex", "hooks.json");
|
|
5699
5772
|
}
|
|
5700
|
-
}
|
|
5701
|
-
function settingsPath(scope, projectDir = process.cwd()) {
|
|
5702
5773
|
return scope === "user" ? import_node_path4.default.join(import_node_os4.default.homedir(), ".claude", "settings.json") : import_node_path4.default.join(projectDir, ".claude", "settings.json");
|
|
5703
5774
|
}
|
|
5704
5775
|
function readSettings(file) {
|
|
5705
5776
|
if (!import_node_fs4.default.existsSync(file)) return {};
|
|
5706
5777
|
return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
|
|
5707
5778
|
}
|
|
5708
|
-
function ownedHook(timeout) {
|
|
5779
|
+
function ownedHook(harness, timeout, additionalContextLimit) {
|
|
5709
5780
|
return {
|
|
5710
5781
|
type: "command",
|
|
5711
|
-
command:
|
|
5782
|
+
command: command(harness),
|
|
5783
|
+
...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
|
|
5712
5784
|
timeout,
|
|
5713
|
-
statusMessage: "Syncing MindGraph session context"
|
|
5785
|
+
statusMessage: "Syncing MindGraph session context",
|
|
5786
|
+
...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
|
|
5714
5787
|
};
|
|
5715
5788
|
}
|
|
5716
|
-
function desiredEntries() {
|
|
5717
|
-
|
|
5789
|
+
function desiredEntries(harness) {
|
|
5790
|
+
const common = {
|
|
5718
5791
|
SessionStart: [
|
|
5719
5792
|
{
|
|
5720
|
-
matcher: "startup|resume|clear|compact|fork",
|
|
5721
|
-
|
|
5793
|
+
matcher: harness === "claude-code" ? "startup|resume|clear|compact|fork" : "startup|resume|clear|compact",
|
|
5794
|
+
// B7 live acceptance caught cold SessionStart calls crossing the
|
|
5795
|
+
// inherited 20s edge in both harnesses: the hooks correctly failed
|
|
5796
|
+
// open, but no brief reached the model. The same installed runner
|
|
5797
|
+
// completed in 16.84s once warm. Keep a 30s cold-tenant margin.
|
|
5798
|
+
hooks: [
|
|
5799
|
+
ownedHook(
|
|
5800
|
+
harness,
|
|
5801
|
+
30,
|
|
5802
|
+
harness === "codex" ? 3e3 : void 0
|
|
5803
|
+
)
|
|
5804
|
+
]
|
|
5722
5805
|
}
|
|
5723
5806
|
],
|
|
5724
5807
|
PreToolUse: [
|
|
5725
5808
|
{
|
|
5726
5809
|
matcher: "mcp__mindgraph__.*",
|
|
5727
|
-
hooks: [ownedHook(3)]
|
|
5810
|
+
hooks: [ownedHook(harness, 3)]
|
|
5728
5811
|
}
|
|
5729
5812
|
],
|
|
5730
|
-
PostToolUse: [{ matcher: ".*", hooks: [ownedHook(5)] }],
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5813
|
+
PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 5)] }],
|
|
5814
|
+
Stop: [{ hooks: [ownedHook(harness, 5)] }],
|
|
5815
|
+
SessionEnd: [
|
|
5816
|
+
{ hooks: [ownedHook(harness, harness === "codex" ? 3 : 8)] }
|
|
5817
|
+
]
|
|
5735
5818
|
};
|
|
5819
|
+
return harness === "claude-code" ? {
|
|
5820
|
+
...common,
|
|
5821
|
+
TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
|
|
5822
|
+
TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
|
|
5823
|
+
} : common;
|
|
5736
5824
|
}
|
|
5737
5825
|
function hookCommand(value) {
|
|
5738
5826
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
5739
|
-
const
|
|
5740
|
-
return typeof
|
|
5827
|
+
const command2 = value.command;
|
|
5828
|
+
return typeof command2 === "string" ? command2 : void 0;
|
|
5741
5829
|
}
|
|
5742
5830
|
function entryHasOwnedHook(value) {
|
|
5743
5831
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
@@ -5753,13 +5841,13 @@ function writeSettings(file, settings) {
|
|
|
5753
5841
|
});
|
|
5754
5842
|
import_node_fs4.default.renameSync(temporary, file);
|
|
5755
5843
|
}
|
|
5756
|
-
function
|
|
5757
|
-
const file = settingsPath(scope, projectDir);
|
|
5844
|
+
function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
|
|
5845
|
+
const file = settingsPath(harness, scope, projectDir, codexHomeDir);
|
|
5758
5846
|
const settings = readSettings(file);
|
|
5759
5847
|
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
5760
5848
|
let added = 0;
|
|
5761
5849
|
let updated = 0;
|
|
5762
|
-
for (const [event, wanted] of Object.entries(desiredEntries())) {
|
|
5850
|
+
for (const [event, wanted] of Object.entries(desiredEntries(harness))) {
|
|
5763
5851
|
const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
5764
5852
|
const kept = current.filter((entry) => !entryHasOwnedHook(entry));
|
|
5765
5853
|
const hadOwned = kept.length !== current.length;
|
|
@@ -5774,8 +5862,8 @@ function installClaudeHooks(scope, projectDir = process.cwd()) {
|
|
|
5774
5862
|
writeSettings(file, settings);
|
|
5775
5863
|
return { path: file, added, updated };
|
|
5776
5864
|
}
|
|
5777
|
-
function
|
|
5778
|
-
const file = settingsPath(scope, projectDir);
|
|
5865
|
+
function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
|
|
5866
|
+
const file = settingsPath(harness, scope, projectDir, codexHomeDir);
|
|
5779
5867
|
if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
|
|
5780
5868
|
const settings = readSettings(file);
|
|
5781
5869
|
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
@@ -5810,6 +5898,18 @@ function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
|
|
|
5810
5898
|
writeSettings(file, settings);
|
|
5811
5899
|
return { path: file, removed };
|
|
5812
5900
|
}
|
|
5901
|
+
function installClaudeHooks(scope, projectDir = process.cwd()) {
|
|
5902
|
+
return installHooks("claude-code", scope, projectDir);
|
|
5903
|
+
}
|
|
5904
|
+
function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
|
|
5905
|
+
return uninstallHooks("claude-code", scope, projectDir);
|
|
5906
|
+
}
|
|
5907
|
+
function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
5908
|
+
return installHooks("codex", scope, projectDir, codexHomeDir);
|
|
5909
|
+
}
|
|
5910
|
+
function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
|
|
5911
|
+
return uninstallHooks("codex", scope, projectDir, codexHomeDir);
|
|
5912
|
+
}
|
|
5813
5913
|
|
|
5814
5914
|
// src/cli.ts
|
|
5815
5915
|
function getClaudeDesktopConfigPath() {
|
|
@@ -6019,8 +6119,8 @@ USAGE:
|
|
|
6019
6119
|
mindgraph-mcp install-code Install into Claude Code (--hooks: + session hooks)
|
|
6020
6120
|
mindgraph-mcp uninstall Remove from Claude Desktop config
|
|
6021
6121
|
mindgraph-mcp uninstall-code Remove from Claude Code
|
|
6022
|
-
mindgraph-mcp install-hooks --harness claude-code [--scope user|project]
|
|
6023
|
-
mindgraph-mcp uninstall-hooks --harness claude-code [--scope user|project]
|
|
6122
|
+
mindgraph-mcp install-hooks --harness claude-code|codex [--scope user|project]
|
|
6123
|
+
mindgraph-mcp uninstall-hooks --harness claude-code|codex [--scope user|project]
|
|
6024
6124
|
mindgraph-mcp status Show installation status
|
|
6025
6125
|
|
|
6026
6126
|
OPTIONS:
|
|
@@ -6028,7 +6128,7 @@ OPTIONS:
|
|
|
6028
6128
|
--base-url <url> Custom API base URL (default: https://api.mindgraph.cloud)
|
|
6029
6129
|
--scope <scope> Hook settings scope: project (default) or user
|
|
6030
6130
|
--project-dir <dir> Project root for project-scoped hooks
|
|
6031
|
-
--harness <name> Hook harness (
|
|
6131
|
+
--harness <name> Hook harness (claude-code or codex)
|
|
6032
6132
|
--help, -h Show this help message
|
|
6033
6133
|
|
|
6034
6134
|
EXAMPLES:
|
|
@@ -6166,8 +6266,8 @@ async function interactiveInit(baseUrl) {
|
|
|
6166
6266
|
console.log("\nDone! Your knowledge graph is now connected.");
|
|
6167
6267
|
}
|
|
6168
6268
|
async function main2() {
|
|
6169
|
-
const { command, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId } = parseArgs(process.argv);
|
|
6170
|
-
switch (
|
|
6269
|
+
const { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId } = parseArgs(process.argv);
|
|
6270
|
+
switch (command2) {
|
|
6171
6271
|
case "help":
|
|
6172
6272
|
printUsage();
|
|
6173
6273
|
break;
|
|
@@ -6200,14 +6300,15 @@ async function main2() {
|
|
|
6200
6300
|
uninstallClaudeCode();
|
|
6201
6301
|
break;
|
|
6202
6302
|
case "install-hooks": {
|
|
6203
|
-
|
|
6204
|
-
|
|
6303
|
+
const hookHarness = harness || "claude-code";
|
|
6304
|
+
if (hookHarness !== "claude-code" && hookHarness !== "codex") {
|
|
6305
|
+
throw new Error("--harness must be claude-code or codex");
|
|
6205
6306
|
}
|
|
6206
6307
|
const runner = installHookRunner(__filename);
|
|
6207
6308
|
console.log(`Installed pinned hook runner at ${runner}`);
|
|
6208
|
-
const result = installClaudeHooks(scope, projectDir);
|
|
6309
|
+
const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir);
|
|
6209
6310
|
console.log(
|
|
6210
|
-
`Installed ${result.added} and refreshed ${result.updated} MindGraph Claude Code hook entries in ${result.path}`
|
|
6311
|
+
`Installed ${result.added} and refreshed ${result.updated} MindGraph ${hookHarness === "codex" ? "Codex" : "Claude Code"} hook entries in ${result.path}`
|
|
6211
6312
|
);
|
|
6212
6313
|
printCodegraphStatus();
|
|
6213
6314
|
if (apiKey || baseUrl || agentId) {
|
|
@@ -6221,18 +6322,22 @@ async function main2() {
|
|
|
6221
6322
|
break;
|
|
6222
6323
|
}
|
|
6223
6324
|
case "uninstall-hooks": {
|
|
6224
|
-
|
|
6225
|
-
|
|
6325
|
+
const hookHarness = harness || "claude-code";
|
|
6326
|
+
if (hookHarness !== "claude-code" && hookHarness !== "codex") {
|
|
6327
|
+
throw new Error("--harness must be claude-code or codex");
|
|
6226
6328
|
}
|
|
6227
|
-
|
|
6228
|
-
const result = uninstallClaudeHooks(scope, projectDir);
|
|
6329
|
+
const result = hookHarness === "codex" ? uninstallCodexHooks(scope, projectDir) : uninstallClaudeHooks(scope, projectDir);
|
|
6229
6330
|
console.log(
|
|
6230
|
-
`Removed ${result.removed} MindGraph Claude Code hooks from ${result.path}`
|
|
6331
|
+
`Removed ${result.removed} MindGraph ${hookHarness === "codex" ? "Codex" : "Claude Code"} hooks from ${result.path}`
|
|
6231
6332
|
);
|
|
6232
6333
|
break;
|
|
6233
6334
|
}
|
|
6234
6335
|
case "hook": {
|
|
6235
6336
|
const stored = loadHookEnv();
|
|
6337
|
+
const hookHarness = harness || "claude-code";
|
|
6338
|
+
if (hookHarness !== "claude-code" && hookHarness !== "codex") {
|
|
6339
|
+
throw new Error("--harness must be claude-code or codex");
|
|
6340
|
+
}
|
|
6236
6341
|
const hookApiKey = apiKey ?? stored.apiKey;
|
|
6237
6342
|
const hookBaseUrl = baseUrl ?? stored.baseUrl;
|
|
6238
6343
|
if (!hookApiKey) {
|
|
@@ -6248,9 +6353,18 @@ async function main2() {
|
|
|
6248
6353
|
maxRetries: 0,
|
|
6249
6354
|
telemetrySurface: "mcp"
|
|
6250
6355
|
});
|
|
6251
|
-
const
|
|
6252
|
-
agentId: process.env.MINDGRAPH_AGENT_ID || stored.agentId ||
|
|
6253
|
-
}
|
|
6356
|
+
const hookOptions = {
|
|
6357
|
+
agentId: process.env.MINDGRAPH_AGENT_ID || stored.agentId || hookHarness
|
|
6358
|
+
};
|
|
6359
|
+
const output = hookHarness === "codex" ? await runCodexHook(
|
|
6360
|
+
input,
|
|
6361
|
+
client2,
|
|
6362
|
+
hookOptions
|
|
6363
|
+
) : await runClaudeHook(
|
|
6364
|
+
input,
|
|
6365
|
+
client2,
|
|
6366
|
+
hookOptions
|
|
6367
|
+
);
|
|
6254
6368
|
process.stdout.write(`${JSON.stringify(output)}
|
|
6255
6369
|
`);
|
|
6256
6370
|
} catch {
|
|
@@ -6266,7 +6380,7 @@ async function main2() {
|
|
|
6266
6380
|
break;
|
|
6267
6381
|
}
|
|
6268
6382
|
default:
|
|
6269
|
-
console.error(`Unknown command: ${
|
|
6383
|
+
console.error(`Unknown command: ${command2}`);
|
|
6270
6384
|
printUsage();
|
|
6271
6385
|
process.exit(1);
|
|
6272
6386
|
}
|
package/dist/index.js
CHANGED