context-mode 0.9.21 → 1.0.0

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.
Files changed (102) hide show
  1. package/.claude-plugin/hooks/hooks.json +46 -4
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/.claude-plugin/plugin.json +4 -4
  4. package/README.md +377 -191
  5. package/build/adapters/claude-code/config.d.ts +8 -0
  6. package/build/adapters/claude-code/config.js +8 -0
  7. package/build/adapters/claude-code/hooks.d.ts +53 -0
  8. package/build/adapters/claude-code/hooks.js +88 -0
  9. package/build/adapters/claude-code/index.d.ts +50 -0
  10. package/build/adapters/claude-code/index.js +523 -0
  11. package/build/adapters/codex/config.d.ts +8 -0
  12. package/build/adapters/codex/config.js +8 -0
  13. package/build/adapters/codex/hooks.d.ts +21 -0
  14. package/build/adapters/codex/hooks.js +27 -0
  15. package/build/adapters/codex/index.d.ts +44 -0
  16. package/build/adapters/codex/index.js +223 -0
  17. package/build/adapters/detect.d.ts +26 -0
  18. package/build/adapters/detect.js +131 -0
  19. package/build/adapters/gemini-cli/config.d.ts +8 -0
  20. package/build/adapters/gemini-cli/config.js +8 -0
  21. package/build/adapters/gemini-cli/hooks.d.ts +44 -0
  22. package/build/adapters/gemini-cli/hooks.js +64 -0
  23. package/build/adapters/gemini-cli/index.d.ts +57 -0
  24. package/build/adapters/gemini-cli/index.js +468 -0
  25. package/build/adapters/opencode/config.d.ts +8 -0
  26. package/build/adapters/opencode/config.js +8 -0
  27. package/build/adapters/opencode/hooks.d.ts +38 -0
  28. package/build/adapters/opencode/hooks.js +50 -0
  29. package/build/adapters/opencode/index.d.ts +52 -0
  30. package/build/adapters/opencode/index.js +386 -0
  31. package/build/adapters/types.d.ts +218 -0
  32. package/build/adapters/types.js +13 -0
  33. package/build/adapters/vscode-copilot/config.d.ts +8 -0
  34. package/build/adapters/vscode-copilot/config.js +8 -0
  35. package/build/adapters/vscode-copilot/hooks.d.ts +49 -0
  36. package/build/adapters/vscode-copilot/hooks.js +76 -0
  37. package/build/adapters/vscode-copilot/index.d.ts +58 -0
  38. package/build/adapters/vscode-copilot/index.js +512 -0
  39. package/build/cli.d.ts +9 -6
  40. package/build/cli.js +133 -423
  41. package/build/db-base.d.ts +84 -0
  42. package/build/db-base.js +128 -0
  43. package/build/executor.d.ts +6 -7
  44. package/build/executor.js +111 -51
  45. package/build/opencode-plugin.d.ts +37 -0
  46. package/build/opencode-plugin.js +118 -0
  47. package/build/runtime.js +1 -1
  48. package/build/server.js +436 -117
  49. package/build/session/db.d.ts +110 -0
  50. package/build/session/db.js +285 -0
  51. package/build/session/extract.d.ts +51 -0
  52. package/build/session/extract.js +407 -0
  53. package/build/session/snapshot.d.ts +70 -0
  54. package/build/session/snapshot.js +309 -0
  55. package/build/store.d.ts +4 -22
  56. package/build/store.js +67 -55
  57. package/build/truncate.d.ts +59 -0
  58. package/build/truncate.js +157 -0
  59. package/build/types.d.ts +101 -0
  60. package/build/types.js +20 -0
  61. package/configs/claude-code/CLAUDE.md +62 -0
  62. package/configs/codex/AGENTS.md +58 -0
  63. package/configs/codex/config.toml +5 -0
  64. package/configs/gemini-cli/GEMINI.md +58 -0
  65. package/configs/gemini-cli/mcp.json +7 -0
  66. package/configs/gemini-cli/settings.json +49 -0
  67. package/configs/opencode/AGENTS.md +58 -0
  68. package/configs/opencode/opencode.json +10 -0
  69. package/configs/vscode-copilot/copilot-instructions.md +58 -0
  70. package/configs/vscode-copilot/hooks.json +16 -0
  71. package/configs/vscode-copilot/mcp.json +8 -0
  72. package/hooks/core/formatters.mjs +86 -0
  73. package/hooks/core/routing.mjs +262 -0
  74. package/hooks/core/stdin.mjs +19 -0
  75. package/hooks/formatters/claude-code.mjs +57 -0
  76. package/hooks/formatters/gemini-cli.mjs +55 -0
  77. package/hooks/formatters/vscode-copilot.mjs +55 -0
  78. package/hooks/gemini-cli/aftertool.mjs +58 -0
  79. package/hooks/gemini-cli/beforetool.mjs +25 -0
  80. package/hooks/gemini-cli/precompress.mjs +51 -0
  81. package/hooks/gemini-cli/sessionstart.mjs +117 -0
  82. package/hooks/hooks.json +46 -4
  83. package/hooks/posttooluse.mjs +53 -0
  84. package/hooks/precompact.mjs +55 -0
  85. package/hooks/pretooluse.mjs +23 -266
  86. package/hooks/routing-block.mjs +19 -6
  87. package/hooks/session-directive.mjs +353 -0
  88. package/hooks/session-helpers.mjs +112 -0
  89. package/hooks/sessionstart.mjs +123 -16
  90. package/hooks/userpromptsubmit.mjs +58 -0
  91. package/hooks/vscode-copilot/posttooluse.mjs +58 -0
  92. package/hooks/vscode-copilot/precompact.mjs +51 -0
  93. package/hooks/vscode-copilot/pretooluse.mjs +25 -0
  94. package/hooks/vscode-copilot/sessionstart.mjs +115 -0
  95. package/package.json +20 -17
  96. package/skills/context-mode/SKILL.md +49 -49
  97. package/skills/{doctor → ctx-doctor}/SKILL.md +3 -3
  98. package/skills/{stats → ctx-stats}/SKILL.md +3 -3
  99. package/skills/{upgrade → ctx-upgrade}/SKILL.md +3 -3
  100. package/start.mjs +47 -0
  101. package/hooks/pretooluse.sh +0 -147
  102. package/server.bundle.mjs +0 -341
@@ -0,0 +1,523 @@
1
+ /**
2
+ * adapters/claude-code — Claude Code platform adapter.
3
+ *
4
+ * Implements HookAdapter for Claude Code's JSON stdin/stdout hook paradigm.
5
+ *
6
+ * Claude Code hook specifics:
7
+ * - I/O: JSON on stdin, JSON on stdout
8
+ * - Arg modification: `updatedInput` field in response
9
+ * - Blocking: `permissionDecision: "deny"` in response
10
+ * - PostToolUse output: `updatedMCPToolOutput` field
11
+ * - PreCompact: stdout on exit 0
12
+ * - Session ID: transcript_path UUID > session_id > CLAUDE_SESSION_ID > ppid
13
+ * - Config: ~/.claude/settings.json
14
+ * - Session dir: ~/.claude/context-mode/sessions/
15
+ */
16
+ import { createHash } from "node:crypto";
17
+ import { readFileSync, writeFileSync, mkdirSync, copyFileSync, accessSync, readdirSync, chmodSync, constants, } from "node:fs";
18
+ import { resolve, join } from "node:path";
19
+ import { homedir } from "node:os";
20
+ import { HOOK_TYPES, HOOK_SCRIPTS, PRE_TOOL_USE_MATCHER_PATTERN, isContextModeHook, buildHookCommand, } from "./hooks.js";
21
+ // ─────────────────────────────────────────────────────────
22
+ // Adapter implementation
23
+ // ─────────────────────────────────────────────────────────
24
+ export class ClaudeCodeAdapter {
25
+ name = "Claude Code";
26
+ paradigm = "json-stdio";
27
+ capabilities = {
28
+ preToolUse: true,
29
+ postToolUse: true,
30
+ preCompact: true,
31
+ sessionStart: true,
32
+ canModifyArgs: true,
33
+ canModifyOutput: true,
34
+ canInjectSessionContext: true,
35
+ };
36
+ // ── Input parsing ──────────────────────────────────────
37
+ parsePreToolUseInput(raw) {
38
+ const input = raw;
39
+ return {
40
+ toolName: input.tool_name ?? "",
41
+ toolInput: input.tool_input ?? {},
42
+ sessionId: this.extractSessionId(input),
43
+ projectDir: process.env.CLAUDE_PROJECT_DIR,
44
+ raw,
45
+ };
46
+ }
47
+ parsePostToolUseInput(raw) {
48
+ const input = raw;
49
+ return {
50
+ toolName: input.tool_name ?? "",
51
+ toolInput: input.tool_input ?? {},
52
+ toolOutput: input.tool_output,
53
+ isError: input.is_error,
54
+ sessionId: this.extractSessionId(input),
55
+ projectDir: process.env.CLAUDE_PROJECT_DIR,
56
+ raw,
57
+ };
58
+ }
59
+ parsePreCompactInput(raw) {
60
+ const input = raw;
61
+ return {
62
+ sessionId: this.extractSessionId(input),
63
+ projectDir: process.env.CLAUDE_PROJECT_DIR,
64
+ raw,
65
+ };
66
+ }
67
+ parseSessionStartInput(raw) {
68
+ const input = raw;
69
+ const rawSource = input.source ?? "startup";
70
+ let source;
71
+ switch (rawSource) {
72
+ case "compact":
73
+ source = "compact";
74
+ break;
75
+ case "resume":
76
+ source = "resume";
77
+ break;
78
+ case "clear":
79
+ source = "clear";
80
+ break;
81
+ default:
82
+ source = "startup";
83
+ }
84
+ return {
85
+ sessionId: this.extractSessionId(input),
86
+ source,
87
+ projectDir: process.env.CLAUDE_PROJECT_DIR,
88
+ raw,
89
+ };
90
+ }
91
+ // ── Response formatting ────────────────────────────────
92
+ formatPreToolUseResponse(response) {
93
+ if (response.decision === "deny") {
94
+ return {
95
+ permissionDecision: "deny",
96
+ reason: response.reason ?? "Blocked by context-mode hook",
97
+ };
98
+ }
99
+ if (response.decision === "modify" && response.updatedInput) {
100
+ return { updatedInput: response.updatedInput };
101
+ }
102
+ if (response.decision === "context" && response.additionalContext) {
103
+ // Claude Code: inject additionalContext into model context
104
+ return { additionalContext: response.additionalContext };
105
+ }
106
+ if (response.decision === "ask") {
107
+ // Claude Code: native "ask" — prompt user for permission
108
+ return { permissionDecision: "ask" };
109
+ }
110
+ // "allow" — return null/undefined for passthrough
111
+ return undefined;
112
+ }
113
+ formatPostToolUseResponse(response) {
114
+ const result = {};
115
+ if (response.additionalContext) {
116
+ result.additionalContext = response.additionalContext;
117
+ }
118
+ if (response.updatedOutput) {
119
+ result.updatedMCPToolOutput = response.updatedOutput;
120
+ }
121
+ return Object.keys(result).length > 0 ? result : undefined;
122
+ }
123
+ formatPreCompactResponse(response) {
124
+ // Claude Code: stdout content on exit 0 is injected as context
125
+ return response.context ?? "";
126
+ }
127
+ formatSessionStartResponse(response) {
128
+ // Claude Code: stdout content is injected as additional context
129
+ return response.context ?? "";
130
+ }
131
+ // ── Configuration ──────────────────────────────────────
132
+ getSettingsPath() {
133
+ return resolve(homedir(), ".claude", "settings.json");
134
+ }
135
+ getSessionDir() {
136
+ const dir = join(homedir(), ".claude", "context-mode", "sessions");
137
+ mkdirSync(dir, { recursive: true });
138
+ return dir;
139
+ }
140
+ getSessionDBPath(projectDir) {
141
+ const hash = createHash("sha256")
142
+ .update(projectDir)
143
+ .digest("hex")
144
+ .slice(0, 16);
145
+ return join(this.getSessionDir(), `${hash}.db`);
146
+ }
147
+ getSessionEventsPath(projectDir) {
148
+ const hash = createHash("sha256")
149
+ .update(projectDir)
150
+ .digest("hex")
151
+ .slice(0, 16);
152
+ return join(this.getSessionDir(), `${hash}-events.md`);
153
+ }
154
+ generateHookConfig(pluginRoot) {
155
+ const preToolUseCommand = `node ${pluginRoot}/hooks/pretooluse.mjs`;
156
+ const preToolUseMatchers = [
157
+ "Bash",
158
+ "WebFetch",
159
+ "Read",
160
+ "Grep",
161
+ "Task",
162
+ "mcp__plugin_context-mode_context-mode__ctx_execute",
163
+ "mcp__plugin_context-mode_context-mode__ctx_execute_file",
164
+ "mcp__plugin_context-mode_context-mode__ctx_batch_execute",
165
+ ];
166
+ return {
167
+ PreToolUse: preToolUseMatchers.map((matcher) => ({
168
+ matcher,
169
+ hooks: [{ type: "command", command: preToolUseCommand }],
170
+ })),
171
+ PostToolUse: [
172
+ {
173
+ matcher: "",
174
+ hooks: [
175
+ {
176
+ type: "command",
177
+ command: `node ${pluginRoot}/hooks/posttooluse.mjs`,
178
+ },
179
+ ],
180
+ },
181
+ ],
182
+ PreCompact: [
183
+ {
184
+ matcher: "",
185
+ hooks: [
186
+ {
187
+ type: "command",
188
+ command: `node ${pluginRoot}/hooks/precompact.mjs`,
189
+ },
190
+ ],
191
+ },
192
+ ],
193
+ UserPromptSubmit: [
194
+ {
195
+ matcher: "",
196
+ hooks: [
197
+ {
198
+ type: "command",
199
+ command: `node ${pluginRoot}/hooks/userpromptsubmit.mjs`,
200
+ },
201
+ ],
202
+ },
203
+ ],
204
+ SessionStart: [
205
+ {
206
+ matcher: "",
207
+ hooks: [
208
+ {
209
+ type: "command",
210
+ command: `node ${pluginRoot}/hooks/sessionstart.mjs`,
211
+ },
212
+ ],
213
+ },
214
+ ],
215
+ };
216
+ }
217
+ readSettings() {
218
+ try {
219
+ const raw = readFileSync(this.getSettingsPath(), "utf-8");
220
+ return JSON.parse(raw);
221
+ }
222
+ catch {
223
+ return null;
224
+ }
225
+ }
226
+ writeSettings(settings) {
227
+ writeFileSync(this.getSettingsPath(), JSON.stringify(settings, null, 2) + "\n", "utf-8");
228
+ }
229
+ // ── Diagnostics (doctor) ─────────────────────────────────
230
+ validateHooks(pluginRoot) {
231
+ const results = [];
232
+ const settings = this.readSettings();
233
+ if (!settings) {
234
+ results.push({
235
+ check: "PreToolUse hook",
236
+ status: "fail",
237
+ message: "Could not read ~/.claude/settings.json",
238
+ fix: "context-mode upgrade",
239
+ });
240
+ return results;
241
+ }
242
+ const hooks = settings.hooks;
243
+ // Check PreToolUse
244
+ const preToolUse = hooks?.PreToolUse;
245
+ if (preToolUse && preToolUse.length > 0) {
246
+ const hasHook = preToolUse.some((entry) => isContextModeHook(entry, HOOK_TYPES.PRE_TOOL_USE));
247
+ results.push({
248
+ check: "PreToolUse hook",
249
+ status: hasHook ? "pass" : "fail",
250
+ message: hasHook
251
+ ? "PreToolUse hook configured"
252
+ : "PreToolUse exists but does not point to pretooluse.mjs",
253
+ fix: hasHook ? undefined : "context-mode upgrade",
254
+ });
255
+ }
256
+ else {
257
+ results.push({
258
+ check: "PreToolUse hook",
259
+ status: "fail",
260
+ message: "No PreToolUse hooks found",
261
+ fix: "context-mode upgrade",
262
+ });
263
+ }
264
+ // Check SessionStart
265
+ const sessionStart = hooks?.SessionStart;
266
+ if (sessionStart && sessionStart.length > 0) {
267
+ const hasHook = sessionStart.some((entry) => isContextModeHook(entry, HOOK_TYPES.SESSION_START));
268
+ results.push({
269
+ check: "SessionStart hook",
270
+ status: hasHook ? "pass" : "fail",
271
+ message: hasHook
272
+ ? "SessionStart hook configured"
273
+ : "SessionStart exists but does not point to sessionstart.mjs",
274
+ fix: hasHook ? undefined : "context-mode upgrade",
275
+ });
276
+ }
277
+ else {
278
+ results.push({
279
+ check: "SessionStart hook",
280
+ status: "fail",
281
+ message: "No SessionStart hooks found",
282
+ fix: "context-mode upgrade",
283
+ });
284
+ }
285
+ return results;
286
+ }
287
+ checkPluginRegistration() {
288
+ const settings = this.readSettings();
289
+ if (!settings) {
290
+ return {
291
+ check: "Plugin registration",
292
+ status: "warn",
293
+ message: "Could not read settings.json",
294
+ };
295
+ }
296
+ const enabledPlugins = settings.enabledPlugins;
297
+ if (!enabledPlugins) {
298
+ return {
299
+ check: "Plugin registration",
300
+ status: "warn",
301
+ message: "No enabledPlugins section found (might be using standalone MCP mode)",
302
+ };
303
+ }
304
+ const pluginKey = Object.keys(enabledPlugins).find((k) => k.startsWith("context-mode"));
305
+ if (pluginKey && enabledPlugins[pluginKey]) {
306
+ return {
307
+ check: "Plugin registration",
308
+ status: "pass",
309
+ message: `Plugin enabled: ${pluginKey}`,
310
+ };
311
+ }
312
+ return {
313
+ check: "Plugin registration",
314
+ status: "warn",
315
+ message: "context-mode not in enabledPlugins (might be using standalone MCP mode)",
316
+ };
317
+ }
318
+ getInstalledVersion() {
319
+ // Primary: read from installed_plugins.json
320
+ try {
321
+ const ipPath = resolve(homedir(), ".claude", "plugins", "installed_plugins.json");
322
+ const ipRaw = JSON.parse(readFileSync(ipPath, "utf-8"));
323
+ const plugins = ipRaw.plugins ?? {};
324
+ for (const [key, entries] of Object.entries(plugins)) {
325
+ if (!key.toLowerCase().includes("context-mode"))
326
+ continue;
327
+ const arr = entries;
328
+ if (arr.length > 0 && typeof arr[0].version === "string") {
329
+ return arr[0].version;
330
+ }
331
+ }
332
+ }
333
+ catch {
334
+ /* fallback below */
335
+ }
336
+ // Fallback: scan common plugin cache locations
337
+ const bases = [
338
+ resolve(homedir(), ".claude"),
339
+ resolve(homedir(), ".config", "claude"),
340
+ ];
341
+ for (const base of bases) {
342
+ const cacheDir = resolve(base, "plugins", "cache", "context-mode", "context-mode");
343
+ try {
344
+ const entries = readdirSync(cacheDir);
345
+ const versions = entries
346
+ .filter((e) => /^\d+\.\d+\.\d+/.test(e))
347
+ .sort((a, b) => {
348
+ const pa = a.split(".").map(Number);
349
+ const pb = b.split(".").map(Number);
350
+ for (let i = 0; i < 3; i++) {
351
+ if ((pa[i] ?? 0) !== (pb[i] ?? 0))
352
+ return (pa[i] ?? 0) - (pb[i] ?? 0);
353
+ }
354
+ return 0;
355
+ });
356
+ if (versions.length > 0)
357
+ return versions[versions.length - 1];
358
+ }
359
+ catch {
360
+ /* continue */
361
+ }
362
+ }
363
+ return "not installed";
364
+ }
365
+ // ── Upgrade ────────────────────────────────────────────
366
+ configureAllHooks(pluginRoot) {
367
+ const settings = this.readSettings() ?? {};
368
+ const hooks = (settings.hooks ?? {});
369
+ const changes = [];
370
+ const hookTypes = [
371
+ HOOK_TYPES.PRE_TOOL_USE,
372
+ HOOK_TYPES.SESSION_START,
373
+ ];
374
+ for (const hookType of hookTypes) {
375
+ const command = buildHookCommand(hookType);
376
+ if (hookType === HOOK_TYPES.PRE_TOOL_USE) {
377
+ const entry = {
378
+ matcher: PRE_TOOL_USE_MATCHER_PATTERN,
379
+ hooks: [{ type: "command", command }],
380
+ };
381
+ const existing = hooks.PreToolUse;
382
+ if (existing && Array.isArray(existing)) {
383
+ const idx = existing.findIndex((e) => isContextModeHook(e, hookType));
384
+ if (idx >= 0) {
385
+ existing[idx] = entry;
386
+ changes.push(`Updated existing ${hookType} hook entry`);
387
+ }
388
+ else {
389
+ existing.push(entry);
390
+ changes.push(`Added ${hookType} hook entry`);
391
+ }
392
+ hooks.PreToolUse = existing;
393
+ }
394
+ else {
395
+ hooks.PreToolUse = [entry];
396
+ changes.push(`Created ${hookType} hooks section`);
397
+ }
398
+ }
399
+ else {
400
+ const entry = {
401
+ matcher: "",
402
+ hooks: [{ type: "command", command }],
403
+ };
404
+ const existing = hooks[hookType];
405
+ if (existing && Array.isArray(existing)) {
406
+ const idx = existing.findIndex((e) => isContextModeHook(e, hookType));
407
+ if (idx >= 0) {
408
+ existing[idx] = entry;
409
+ changes.push(`Updated existing ${hookType} hook entry`);
410
+ }
411
+ else {
412
+ existing.push(entry);
413
+ changes.push(`Added ${hookType} hook entry`);
414
+ }
415
+ hooks[hookType] = existing;
416
+ }
417
+ else {
418
+ hooks[hookType] = [entry];
419
+ changes.push(`Created ${hookType} hooks section`);
420
+ }
421
+ }
422
+ }
423
+ settings.hooks = hooks;
424
+ this.writeSettings(settings);
425
+ return changes;
426
+ }
427
+ backupSettings() {
428
+ const settingsPath = this.getSettingsPath();
429
+ try {
430
+ accessSync(settingsPath, constants.R_OK);
431
+ const backupPath = settingsPath + ".bak";
432
+ copyFileSync(settingsPath, backupPath);
433
+ return backupPath;
434
+ }
435
+ catch {
436
+ return null;
437
+ }
438
+ }
439
+ setHookPermissions(pluginRoot) {
440
+ const set = [];
441
+ for (const [, scriptName] of Object.entries(HOOK_SCRIPTS)) {
442
+ const scriptPath = resolve(pluginRoot, "hooks", scriptName);
443
+ try {
444
+ accessSync(scriptPath, constants.R_OK);
445
+ chmodSync(scriptPath, 0o755);
446
+ set.push(scriptPath);
447
+ }
448
+ catch {
449
+ /* skip missing scripts */
450
+ }
451
+ }
452
+ return set;
453
+ }
454
+ updatePluginRegistry(pluginRoot, version) {
455
+ try {
456
+ const ipPath = resolve(homedir(), ".claude", "plugins", "installed_plugins.json");
457
+ const ipRaw = JSON.parse(readFileSync(ipPath, "utf-8"));
458
+ for (const [key, entries] of Object.entries(ipRaw.plugins || {})) {
459
+ if (!key.toLowerCase().includes("context-mode"))
460
+ continue;
461
+ for (const entry of entries) {
462
+ entry.installPath = pluginRoot;
463
+ entry.version = version;
464
+ entry.lastUpdated = new Date().toISOString();
465
+ }
466
+ }
467
+ writeFileSync(ipPath, JSON.stringify(ipRaw, null, 2) + "\n", "utf-8");
468
+ }
469
+ catch {
470
+ /* best effort */
471
+ }
472
+ }
473
+ // ── Routing Instructions (soft enforcement) ────────────
474
+ getRoutingInstructionsConfig() {
475
+ return {
476
+ fileName: "CLAUDE.md",
477
+ globalPath: resolve(homedir(), ".claude", "CLAUDE.md"),
478
+ projectRelativePath: "CLAUDE.md",
479
+ };
480
+ }
481
+ writeRoutingInstructions(projectDir, pluginRoot) {
482
+ const config = this.getRoutingInstructionsConfig();
483
+ const targetPath = resolve(projectDir, config.projectRelativePath);
484
+ const sourcePath = resolve(pluginRoot, "configs", "claude-code", config.fileName);
485
+ try {
486
+ const content = readFileSync(sourcePath, "utf-8");
487
+ // Check if file exists and already has context-mode instructions
488
+ try {
489
+ const existing = readFileSync(targetPath, "utf-8");
490
+ if (existing.includes("context-mode"))
491
+ return null;
492
+ // Append to existing file
493
+ writeFileSync(targetPath, existing.trimEnd() + "\n\n" + content, "utf-8");
494
+ return targetPath;
495
+ }
496
+ catch {
497
+ // File doesn't exist — create it
498
+ writeFileSync(targetPath, content, "utf-8");
499
+ return targetPath;
500
+ }
501
+ }
502
+ catch {
503
+ return null;
504
+ }
505
+ }
506
+ // ── Internal helpers ───────────────────────────────────
507
+ /**
508
+ * Extract session ID from Claude Code hook input.
509
+ * Priority: transcript_path UUID > session_id field > CLAUDE_SESSION_ID env > ppid fallback.
510
+ */
511
+ extractSessionId(input) {
512
+ if (input.transcript_path) {
513
+ const match = input.transcript_path.match(/([a-f0-9-]{36})\.jsonl$/);
514
+ if (match)
515
+ return match[1];
516
+ }
517
+ if (input.session_id)
518
+ return input.session_id;
519
+ if (process.env.CLAUDE_SESSION_ID)
520
+ return process.env.CLAUDE_SESSION_ID;
521
+ return `pid-${process.ppid}`;
522
+ }
523
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * adapters/codex/config — Thin re-exports from CodexAdapter.
3
+ *
4
+ * This module exists for backward compatibility. All logic lives in the
5
+ * adapter class (index.ts). New code should use getAdapter() from detect.ts.
6
+ */
7
+ export { CodexAdapter } from "./index.js";
8
+ export { HOOK_TYPES, ROUTING_INSTRUCTIONS_PATH } from "./hooks.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * adapters/codex/config — Thin re-exports from CodexAdapter.
3
+ *
4
+ * This module exists for backward compatibility. All logic lives in the
5
+ * adapter class (index.ts). New code should use getAdapter() from detect.ts.
6
+ */
7
+ export { CodexAdapter } from "./index.js";
8
+ export { HOOK_TYPES, ROUTING_INSTRUCTIONS_PATH } from "./hooks.js";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * adapters/codex/hooks — Codex CLI hook definitions (stub).
3
+ *
4
+ * Codex CLI does NOT support hooks (PRs #2904, #9796 were closed without merge).
5
+ * Only MCP integration is available. This module exports empty/stub constants
6
+ * for interface consistency with other adapters.
7
+ *
8
+ * Config: ~/.codex/config.toml (TOML format, not JSON)
9
+ * MCP: full support via [mcp_servers] in config.toml
10
+ */
11
+ /**
12
+ * Codex CLI hook types — empty object.
13
+ * Codex CLI has no hook support; only MCP integration is available.
14
+ */
15
+ export declare const HOOK_TYPES: {};
16
+ /**
17
+ * Path to the routing instructions file appended to the system prompt
18
+ * when Codex CLI initializes the MCP server. This is the only integration
19
+ * point since hooks are not supported.
20
+ */
21
+ export declare const ROUTING_INSTRUCTIONS_PATH = "configs/codex/AGENTS.md";
@@ -0,0 +1,27 @@
1
+ /**
2
+ * adapters/codex/hooks — Codex CLI hook definitions (stub).
3
+ *
4
+ * Codex CLI does NOT support hooks (PRs #2904, #9796 were closed without merge).
5
+ * Only MCP integration is available. This module exports empty/stub constants
6
+ * for interface consistency with other adapters.
7
+ *
8
+ * Config: ~/.codex/config.toml (TOML format, not JSON)
9
+ * MCP: full support via [mcp_servers] in config.toml
10
+ */
11
+ // ─────────────────────────────────────────────────────────
12
+ // Hook type constants (empty — no hook support)
13
+ // ─────────────────────────────────────────────────────────
14
+ /**
15
+ * Codex CLI hook types — empty object.
16
+ * Codex CLI has no hook support; only MCP integration is available.
17
+ */
18
+ export const HOOK_TYPES = {};
19
+ // ─────────────────────────────────────────────────────────
20
+ // Routing instructions
21
+ // ─────────────────────────────────────────────────────────
22
+ /**
23
+ * Path to the routing instructions file appended to the system prompt
24
+ * when Codex CLI initializes the MCP server. This is the only integration
25
+ * point since hooks are not supported.
26
+ */
27
+ export const ROUTING_INSTRUCTIONS_PATH = "configs/codex/AGENTS.md";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * adapters/codex — Codex CLI platform adapter.
3
+ *
4
+ * Implements HookAdapter for Codex CLI's MCP-only paradigm.
5
+ *
6
+ * Codex CLI hook specifics:
7
+ * - NO hook support (PRs #2904, #9796 were closed without merge)
8
+ * - Only "hook": notify config for agent-turn-complete (very limited)
9
+ * - Config: ~/.codex/config.toml (TOML format, not JSON)
10
+ * - MCP: full support via [mcp_servers] in config.toml
11
+ * - All capabilities are false — MCP is the only integration path
12
+ * - Session dir: ~/.codex/context-mode/sessions/
13
+ */
14
+ import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, PreToolUseEvent, PostToolUseEvent, PreCompactEvent, SessionStartEvent, PreToolUseResponse, PostToolUseResponse, PreCompactResponse, SessionStartResponse, HookRegistration, RoutingInstructionsConfig } from "../types.js";
15
+ export declare class CodexAdapter implements HookAdapter {
16
+ readonly name = "Codex CLI";
17
+ readonly paradigm: HookParadigm;
18
+ readonly capabilities: PlatformCapabilities;
19
+ parsePreToolUseInput(_raw: unknown): PreToolUseEvent;
20
+ parsePostToolUseInput(_raw: unknown): PostToolUseEvent;
21
+ parsePreCompactInput(_raw: unknown): PreCompactEvent;
22
+ parseSessionStartInput(_raw: unknown): SessionStartEvent;
23
+ formatPreToolUseResponse(_response: PreToolUseResponse): unknown;
24
+ formatPostToolUseResponse(_response: PostToolUseResponse): unknown;
25
+ formatPreCompactResponse(_response: PreCompactResponse): unknown;
26
+ formatSessionStartResponse(_response: SessionStartResponse): unknown;
27
+ getSettingsPath(): string;
28
+ getSessionDir(): string;
29
+ getSessionDBPath(projectDir: string): string;
30
+ getSessionEventsPath(projectDir: string): string;
31
+ generateHookConfig(_pluginRoot: string): HookRegistration;
32
+ readSettings(): Record<string, unknown> | null;
33
+ writeSettings(_settings: Record<string, unknown>): void;
34
+ validateHooks(_pluginRoot: string): DiagnosticResult[];
35
+ checkPluginRegistration(): DiagnosticResult;
36
+ getInstalledVersion(): string;
37
+ configureAllHooks(_pluginRoot: string): string[];
38
+ backupSettings(): string | null;
39
+ setHookPermissions(_pluginRoot: string): string[];
40
+ updatePluginRegistry(_pluginRoot: string, _version: string): void;
41
+ getRoutingInstructionsConfig(): RoutingInstructionsConfig;
42
+ writeRoutingInstructions(projectDir: string, pluginRoot: string): string | null;
43
+ getRoutingInstructions(): string;
44
+ }