context-mode 1.0.5 → 1.0.7

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 (35) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/build/adapters/claude-code/hooks.d.ts +3 -3
  4. package/build/adapters/claude-code/hooks.js +7 -3
  5. package/build/adapters/claude-code/index.js +1 -1
  6. package/build/adapters/detect.d.ts +6 -7
  7. package/build/adapters/detect.js +23 -24
  8. package/build/adapters/gemini-cli/hooks.d.ts +3 -3
  9. package/build/adapters/gemini-cli/hooks.js +7 -3
  10. package/build/adapters/gemini-cli/index.d.ts +2 -2
  11. package/build/adapters/gemini-cli/index.js +8 -8
  12. package/build/adapters/types.d.ts +1 -1
  13. package/build/adapters/vscode-copilot/hooks.d.ts +3 -3
  14. package/build/adapters/vscode-copilot/hooks.js +6 -3
  15. package/build/adapters/vscode-copilot/index.d.ts +2 -2
  16. package/build/adapters/vscode-copilot/index.js +8 -8
  17. package/build/cli.js +2 -2
  18. package/build/exit-classify.d.ts +19 -0
  19. package/build/exit-classify.js +12 -0
  20. package/build/server.js +29 -10
  21. package/build/session/extract.js +11 -5
  22. package/build/session/snapshot.d.ts +5 -0
  23. package/build/session/snapshot.js +28 -2
  24. package/hooks/gemini-cli/aftertool.mjs +3 -3
  25. package/hooks/gemini-cli/precompress.mjs +3 -3
  26. package/hooks/gemini-cli/sessionstart.mjs +4 -4
  27. package/hooks/posttooluse.mjs +3 -3
  28. package/hooks/precompact.mjs +3 -3
  29. package/hooks/sessionstart.mjs +4 -4
  30. package/hooks/userpromptsubmit.mjs +3 -3
  31. package/hooks/vscode-copilot/posttooluse.mjs +3 -3
  32. package/hooks/vscode-copilot/precompact.mjs +3 -3
  33. package/hooks/vscode-copilot/sessionstart.mjs +5 -4
  34. package/package.json +5 -4
  35. package/server.bundle.mjs +1 -1
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Claude Code plugins by Mert Koseoğlu",
9
- "version": "1.0.5"
9
+ "version": "1.0.7"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "context-mode",
14
14
  "source": "./",
15
15
  "description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
16
- "version": "1.0.5",
16
+ "version": "1.0.7",
17
17
  "author": {
18
18
  "name": "Mert Koseoğlu"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "MCP server that saves 98% of your context window with session continuity. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and automatic state restore across compactions.",
5
5
  "author": {
6
6
  "name": "Mert Koseoğlu",
@@ -49,7 +49,7 @@ export declare function isContextModeHook(entry: {
49
49
  }, hookType: HookType): boolean;
50
50
  /**
51
51
  * Build the hook command string for a given hook type.
52
- * Uses the CLI dispatcher: `context-mode hook claude-code <event>`
53
- * Requires global install: `npm install -g context-mode`
52
+ * Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
53
+ * Falls back to CLI dispatcher if pluginRoot is not provided.
54
54
  */
55
- export declare function buildHookCommand(hookType: HookType): string;
55
+ export declare function buildHookCommand(hookType: HookType, pluginRoot?: string): string;
@@ -83,9 +83,13 @@ export function isContextModeHook(entry, hookType) {
83
83
  }
84
84
  /**
85
85
  * Build the hook command string for a given hook type.
86
- * Uses the CLI dispatcher: `context-mode hook claude-code <event>`
87
- * Requires global install: `npm install -g context-mode`
86
+ * Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
87
+ * Falls back to CLI dispatcher if pluginRoot is not provided.
88
88
  */
89
- export function buildHookCommand(hookType) {
89
+ export function buildHookCommand(hookType, pluginRoot) {
90
+ if (pluginRoot) {
91
+ const scriptName = HOOK_SCRIPTS[hookType];
92
+ return `node "${pluginRoot}/hooks/${scriptName}"`;
93
+ }
90
94
  return `context-mode hook claude-code ${hookType.toLowerCase()}`;
91
95
  }
@@ -372,7 +372,7 @@ export class ClaudeCodeAdapter {
372
372
  HOOK_TYPES.SESSION_START,
373
373
  ];
374
374
  for (const hookType of hookTypes) {
375
- const command = buildHookCommand(hookType);
375
+ const command = buildHookCommand(hookType, pluginRoot);
376
376
  if (hookType === HOOK_TYPES.PRE_TOOL_USE) {
377
377
  const entry = {
378
378
  matcher: PRE_TOOL_USE_MATCHER_PATTERN,
@@ -6,13 +6,12 @@
6
6
  * 2. Config directory existence (medium confidence)
7
7
  * 3. Fallback to Claude Code (low confidence — most common)
8
8
  *
9
- * Each platform sets identifiable env vars or creates config dirs:
10
- * - Claude Code: CLAUDE_PROJECT_DIR, ~/.claude/
11
- * - Gemini CLI: GEMINI_PROJECT_DIR, ~/.gemini/
12
- * - OpenCode: OPENCODE_PROJECT_DIR, .opencode/
13
- * - Copilot CLI: GITHUB_COPILOT_*, ~/.config/github-copilot/
14
- * - VS Code: VSCODE_*, ~/.vscode/
15
- * - Cursor: CURSOR_*, ~/.cursor/
9
+ * Verified env vars per platform (from source code audit):
10
+ * - Claude Code: CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
11
+ * - Gemini CLI: GEMINI_PROJECT_DIR (hooks), GEMINI_CLI (MCP) | ~/.gemini/
12
+ * - OpenCode: OPENCODE, OPENCODE_PID | ~/.config/opencode/
13
+ * - Codex CLI: CODEX_CI, CODEX_THREAD_ID | ~/.codex/
14
+ * - VS Code Copilot: VSCODE_PID, VSCODE_CWD | ~/.vscode/
16
15
  */
17
16
  import type { PlatformId, DetectionSignal, HookAdapter } from "./types.js";
18
17
  /**
@@ -6,13 +6,12 @@
6
6
  * 2. Config directory existence (medium confidence)
7
7
  * 3. Fallback to Claude Code (low confidence — most common)
8
8
  *
9
- * Each platform sets identifiable env vars or creates config dirs:
10
- * - Claude Code: CLAUDE_PROJECT_DIR, ~/.claude/
11
- * - Gemini CLI: GEMINI_PROJECT_DIR, ~/.gemini/
12
- * - OpenCode: OPENCODE_PROJECT_DIR, .opencode/
13
- * - Copilot CLI: GITHUB_COPILOT_*, ~/.config/github-copilot/
14
- * - VS Code: VSCODE_*, ~/.vscode/
15
- * - Cursor: CURSOR_*, ~/.cursor/
9
+ * Verified env vars per platform (from source code audit):
10
+ * - Claude Code: CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
11
+ * - Gemini CLI: GEMINI_PROJECT_DIR (hooks), GEMINI_CLI (MCP) | ~/.gemini/
12
+ * - OpenCode: OPENCODE, OPENCODE_PID | ~/.config/opencode/
13
+ * - Codex CLI: CODEX_CI, CODEX_THREAD_ID | ~/.codex/
14
+ * - VS Code Copilot: VSCODE_PID, VSCODE_CWD | ~/.vscode/
16
15
  */
17
16
  import { existsSync } from "node:fs";
18
17
  import { resolve } from "node:path";
@@ -29,25 +28,25 @@ export function detectPlatform() {
29
28
  reason: "CLAUDE_PROJECT_DIR or CLAUDE_SESSION_ID env var set",
30
29
  };
31
30
  }
32
- if (process.env.GEMINI_PROJECT_DIR || process.env.GEMINI_SESSION_ID) {
31
+ if (process.env.GEMINI_PROJECT_DIR || process.env.GEMINI_CLI) {
33
32
  return {
34
33
  platform: "gemini-cli",
35
34
  confidence: "high",
36
- reason: "GEMINI_PROJECT_DIR or GEMINI_SESSION_ID env var set",
35
+ reason: "GEMINI_PROJECT_DIR or GEMINI_CLI env var set",
37
36
  };
38
37
  }
39
- if (process.env.OPENCODE_PROJECT_DIR || process.env.OPENCODE_SESSION_ID) {
38
+ if (process.env.OPENCODE || process.env.OPENCODE_PID) {
40
39
  return {
41
40
  platform: "opencode",
42
41
  confidence: "high",
43
- reason: "OPENCODE_PROJECT_DIR or OPENCODE_SESSION_ID env var set",
42
+ reason: "OPENCODE or OPENCODE_PID env var set",
44
43
  };
45
44
  }
46
- if (process.env.GITHUB_COPILOT_AGENT || process.env.COPILOT_SESSION_ID) {
45
+ if (process.env.CODEX_CI || process.env.CODEX_THREAD_ID) {
47
46
  return {
48
- platform: "copilot-cli",
47
+ platform: "codex",
49
48
  confidence: "high",
50
- reason: "GITHUB_COPILOT_AGENT or COPILOT_SESSION_ID env var set",
49
+ reason: "CODEX_CI or CODEX_THREAD_ID env var set",
51
50
  };
52
51
  }
53
52
  if (process.env.VSCODE_PID || process.env.VSCODE_CWD) {
@@ -57,13 +56,6 @@ export function detectPlatform() {
57
56
  reason: "VSCODE_PID or VSCODE_CWD env var set",
58
57
  };
59
58
  }
60
- if (process.env.CURSOR_SESSION_ID || process.env.CURSOR_TRACE_ID) {
61
- return {
62
- platform: "cursor",
63
- confidence: "high",
64
- reason: "CURSOR_SESSION_ID or CURSOR_TRACE_ID env var set",
65
- };
66
- }
67
59
  // ── Medium confidence: config directory existence ──────
68
60
  const home = homedir();
69
61
  if (existsSync(resolve(home, ".claude"))) {
@@ -80,11 +72,18 @@ export function detectPlatform() {
80
72
  reason: "~/.gemini/ directory exists",
81
73
  };
82
74
  }
83
- if (existsSync(resolve(home, ".cursor"))) {
75
+ if (existsSync(resolve(home, ".codex"))) {
84
76
  return {
85
- platform: "cursor",
77
+ platform: "codex",
78
+ confidence: "medium",
79
+ reason: "~/.codex/ directory exists",
80
+ };
81
+ }
82
+ if (existsSync(resolve(home, ".config", "opencode"))) {
83
+ return {
84
+ platform: "opencode",
86
85
  confidence: "medium",
87
- reason: "~/.cursor/ directory exists",
86
+ reason: "~/.config/opencode/ directory exists",
88
87
  };
89
88
  }
90
89
  // ── Low confidence: fallback ───────────────────────────
@@ -40,7 +40,7 @@ export declare function isContextModeHook(entry: {
40
40
  }, hookType: HookType): boolean;
41
41
  /**
42
42
  * Build the hook command string for a given hook type.
43
- * Uses the CLI dispatcher: `context-mode hook gemini-cli <event>`
44
- * Requires global install: `npm install -g context-mode`
43
+ * Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
44
+ * Falls back to CLI dispatcher if pluginRoot is not provided.
45
45
  */
46
- export declare function buildHookCommand(hookType: HookType): string;
46
+ export declare function buildHookCommand(hookType: HookType, pluginRoot?: string): string;
@@ -59,9 +59,13 @@ export function isContextModeHook(entry, hookType) {
59
59
  }
60
60
  /**
61
61
  * Build the hook command string for a given hook type.
62
- * Uses the CLI dispatcher: `context-mode hook gemini-cli <event>`
63
- * Requires global install: `npm install -g context-mode`
62
+ * Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
63
+ * Falls back to CLI dispatcher if pluginRoot is not provided.
64
64
  */
65
- export function buildHookCommand(hookType) {
65
+ export function buildHookCommand(hookType, pluginRoot) {
66
+ const scriptName = HOOK_SCRIPTS[hookType];
67
+ if (pluginRoot && scriptName) {
68
+ return `node "${pluginRoot}/hooks/${scriptName}"`;
69
+ }
66
70
  return `context-mode hook gemini-cli ${hookType.toLowerCase()}`;
67
71
  }
@@ -35,13 +35,13 @@ export declare class GeminiCLIAdapter implements HookAdapter {
35
35
  getSessionDir(): string;
36
36
  getSessionDBPath(projectDir: string): string;
37
37
  getSessionEventsPath(projectDir: string): string;
38
- generateHookConfig(_pluginRoot: string): HookRegistration;
38
+ generateHookConfig(pluginRoot: string): HookRegistration;
39
39
  readSettings(): Record<string, unknown> | null;
40
40
  writeSettings(settings: Record<string, unknown>): void;
41
41
  validateHooks(pluginRoot: string): DiagnosticResult[];
42
42
  checkPluginRegistration(): DiagnosticResult;
43
43
  getInstalledVersion(): string;
44
- configureAllHooks(_pluginRoot: string): string[];
44
+ configureAllHooks(pluginRoot: string): string[];
45
45
  backupSettings(): string | null;
46
46
  setHookPermissions(pluginRoot: string): string[];
47
47
  updatePluginRegistry(pluginRoot: string, version: string): void;
@@ -25,7 +25,7 @@ import { homedir } from "node:os";
25
25
  // ─────────────────────────────────────────────────────────
26
26
  // Hook constants (re-exported from hooks.ts)
27
27
  // ─────────────────────────────────────────────────────────
28
- import { HOOK_TYPES as GEMINI_HOOK_NAMES, HOOK_SCRIPTS as GEMINI_HOOK_SCRIPTS, } from "./hooks.js";
28
+ import { HOOK_TYPES as GEMINI_HOOK_NAMES, HOOK_SCRIPTS as GEMINI_HOOK_SCRIPTS, buildHookCommand as buildGeminiHookCommand, } from "./hooks.js";
29
29
  // ─────────────────────────────────────────────────────────
30
30
  // Adapter implementation
31
31
  // ─────────────────────────────────────────────────────────
@@ -176,7 +176,7 @@ export class GeminiCLIAdapter {
176
176
  .slice(0, 16);
177
177
  return join(this.getSessionDir(), `${hash}-events.md`);
178
178
  }
179
- generateHookConfig(_pluginRoot) {
179
+ generateHookConfig(pluginRoot) {
180
180
  return {
181
181
  [GEMINI_HOOK_NAMES.BEFORE_TOOL]: [
182
182
  {
@@ -184,7 +184,7 @@ export class GeminiCLIAdapter {
184
184
  hooks: [
185
185
  {
186
186
  type: "command",
187
- command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.BEFORE_TOOL.toLowerCase()}`,
187
+ command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.BEFORE_TOOL, pluginRoot),
188
188
  },
189
189
  ],
190
190
  },
@@ -195,7 +195,7 @@ export class GeminiCLIAdapter {
195
195
  hooks: [
196
196
  {
197
197
  type: "command",
198
- command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.AFTER_TOOL.toLowerCase()}`,
198
+ command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.AFTER_TOOL, pluginRoot),
199
199
  },
200
200
  ],
201
201
  },
@@ -206,7 +206,7 @@ export class GeminiCLIAdapter {
206
206
  hooks: [
207
207
  {
208
208
  type: "command",
209
- command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.PRE_COMPRESS.toLowerCase()}`,
209
+ command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.PRE_COMPRESS, pluginRoot),
210
210
  },
211
211
  ],
212
212
  },
@@ -217,7 +217,7 @@ export class GeminiCLIAdapter {
217
217
  hooks: [
218
218
  {
219
219
  type: "command",
220
- command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.SESSION_START.toLowerCase()}`,
220
+ command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.SESSION_START, pluginRoot),
221
221
  },
222
222
  ],
223
223
  },
@@ -339,7 +339,7 @@ export class GeminiCLIAdapter {
339
339
  return "not installed";
340
340
  }
341
341
  // ── Upgrade ────────────────────────────────────────────
342
- configureAllHooks(_pluginRoot) {
342
+ configureAllHooks(pluginRoot) {
343
343
  const settings = this.readSettings() ?? {};
344
344
  const hooks = (settings.hooks ?? {});
345
345
  const changes = [];
@@ -348,7 +348,7 @@ export class GeminiCLIAdapter {
348
348
  { name: GEMINI_HOOK_NAMES.SESSION_START },
349
349
  ];
350
350
  for (const config of hookConfigs) {
351
- const command = `context-mode hook gemini-cli ${config.name.toLowerCase()}`;
351
+ const command = buildGeminiHookCommand(config.name, pluginRoot);
352
352
  const entry = {
353
353
  matcher: "",
354
354
  hooks: [{ type: "command", command }],
@@ -206,7 +206,7 @@ export interface DiagnosticResult {
206
206
  fix?: string;
207
207
  }
208
208
  /** Supported platform identifiers. */
209
- export type PlatformId = "claude-code" | "gemini-cli" | "opencode" | "codex" | "copilot-cli" | "vscode-copilot" | "cursor" | "unknown";
209
+ export type PlatformId = "claude-code" | "gemini-cli" | "opencode" | "codex" | "vscode-copilot" | "unknown";
210
210
  /** Detection signal used to identify which platform is running. */
211
211
  export interface DetectionSignal {
212
212
  /** Platform identifier. */
@@ -45,7 +45,7 @@ export declare function isContextModeHook(entry: {
45
45
  }, hookType: HookType): boolean;
46
46
  /**
47
47
  * Build the hook command string for a given hook type.
48
- * Uses the CLI dispatcher: `context-mode hook vscode-copilot <event>`
49
- * Requires global install: `npm install -g context-mode`
48
+ * Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
49
+ * Falls back to CLI dispatcher if pluginRoot is not provided.
50
50
  */
51
- export declare function buildHookCommand(hookType: HookType): string;
51
+ export declare function buildHookCommand(hookType: HookType, pluginRoot?: string): string;
@@ -67,13 +67,16 @@ export function isContextModeHook(entry, hookType) {
67
67
  }
68
68
  /**
69
69
  * Build the hook command string for a given hook type.
70
- * Uses the CLI dispatcher: `context-mode hook vscode-copilot <event>`
71
- * Requires global install: `npm install -g context-mode`
70
+ * Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
71
+ * Falls back to CLI dispatcher if pluginRoot is not provided.
72
72
  */
73
- export function buildHookCommand(hookType) {
73
+ export function buildHookCommand(hookType, pluginRoot) {
74
74
  const scriptName = HOOK_SCRIPTS[hookType];
75
75
  if (!scriptName) {
76
76
  throw new Error(`No script defined for hook type: ${hookType}`);
77
77
  }
78
+ if (pluginRoot) {
79
+ return `node "${pluginRoot}/hooks/${scriptName}"`;
80
+ }
78
81
  return `context-mode hook vscode-copilot ${hookType.toLowerCase()}`;
79
82
  }
@@ -38,13 +38,13 @@ export declare class VSCodeCopilotAdapter implements HookAdapter {
38
38
  getSessionDir(): string;
39
39
  getSessionDBPath(projectDir: string): string;
40
40
  getSessionEventsPath(projectDir: string): string;
41
- generateHookConfig(_pluginRoot: string): HookRegistration;
41
+ generateHookConfig(pluginRoot: string): HookRegistration;
42
42
  readSettings(): Record<string, unknown> | null;
43
43
  writeSettings(settings: Record<string, unknown>): void;
44
44
  validateHooks(pluginRoot: string): DiagnosticResult[];
45
45
  checkPluginRegistration(): DiagnosticResult;
46
46
  getInstalledVersion(): string;
47
- configureAllHooks(_pluginRoot: string): string[];
47
+ configureAllHooks(pluginRoot: string): string[];
48
48
  backupSettings(): string | null;
49
49
  setHookPermissions(pluginRoot: string): string[];
50
50
  updatePluginRegistry(_pluginRoot: string, _version: string): void;
@@ -28,7 +28,7 @@ import { homedir } from "node:os";
28
28
  // ─────────────────────────────────────────────────────────
29
29
  // Hook constants (re-exported from hooks.ts)
30
30
  // ─────────────────────────────────────────────────────────
31
- import { HOOK_TYPES as VSCODE_HOOK_NAMES, HOOK_SCRIPTS as VSCODE_HOOK_SCRIPTS, } from "./hooks.js";
31
+ import { HOOK_TYPES as VSCODE_HOOK_NAMES, HOOK_SCRIPTS as VSCODE_HOOK_SCRIPTS, buildHookCommand as buildVSCodeHookCommand, } from "./hooks.js";
32
32
  // ─────────────────────────────────────────────────────────
33
33
  // Adapter implementation
34
34
  // ─────────────────────────────────────────────────────────
@@ -193,7 +193,7 @@ export class VSCodeCopilotAdapter {
193
193
  .slice(0, 16);
194
194
  return join(this.getSessionDir(), `${hash}-events.md`);
195
195
  }
196
- generateHookConfig(_pluginRoot) {
196
+ generateHookConfig(pluginRoot) {
197
197
  return {
198
198
  [VSCODE_HOOK_NAMES.PRE_TOOL_USE]: [
199
199
  {
@@ -201,7 +201,7 @@ export class VSCodeCopilotAdapter {
201
201
  hooks: [
202
202
  {
203
203
  type: "command",
204
- command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.PRE_TOOL_USE.toLowerCase()}`,
204
+ command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.PRE_TOOL_USE, pluginRoot),
205
205
  },
206
206
  ],
207
207
  },
@@ -212,7 +212,7 @@ export class VSCodeCopilotAdapter {
212
212
  hooks: [
213
213
  {
214
214
  type: "command",
215
- command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.POST_TOOL_USE.toLowerCase()}`,
215
+ command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.POST_TOOL_USE, pluginRoot),
216
216
  },
217
217
  ],
218
218
  },
@@ -223,7 +223,7 @@ export class VSCodeCopilotAdapter {
223
223
  hooks: [
224
224
  {
225
225
  type: "command",
226
- command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.PRE_COMPACT.toLowerCase()}`,
226
+ command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.PRE_COMPACT, pluginRoot),
227
227
  },
228
228
  ],
229
229
  },
@@ -234,7 +234,7 @@ export class VSCodeCopilotAdapter {
234
234
  hooks: [
235
235
  {
236
236
  type: "command",
237
- command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.SESSION_START.toLowerCase()}`,
237
+ command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.SESSION_START, pluginRoot),
238
238
  },
239
239
  ],
240
240
  },
@@ -398,7 +398,7 @@ export class VSCodeCopilotAdapter {
398
398
  return "not installed";
399
399
  }
400
400
  // ── Upgrade ────────────────────────────────────────────
401
- configureAllHooks(_pluginRoot) {
401
+ configureAllHooks(pluginRoot) {
402
402
  const changes = [];
403
403
  const hookConfig = { hooks: {} };
404
404
  const hooks = hookConfig.hooks;
@@ -418,7 +418,7 @@ export class VSCodeCopilotAdapter {
418
418
  hooks: [
419
419
  {
420
420
  type: "command",
421
- command: `context-mode hook vscode-copilot ${hookType.toLowerCase()}`,
421
+ command: buildVSCodeHookCommand(hookType, pluginRoot),
422
422
  },
423
423
  ],
424
424
  },
package/build/cli.js CHANGED
@@ -17,7 +17,7 @@ import { execSync } from "node:child_process";
17
17
  import { readFileSync, cpSync, accessSync, readdirSync, rmSync, closeSync, openSync, constants } from "node:fs";
18
18
  import { resolve, dirname, join } from "node:path";
19
19
  import { tmpdir, devNull } from "node:os";
20
- import { fileURLToPath } from "node:url";
20
+ import { fileURLToPath, pathToFileURL } from "node:url";
21
21
  import { detectRuntimes, getRuntimeSummary, hasBunRuntime, getAvailableLanguages, } from "./runtime.js";
22
22
  // ── Adapter imports ──────────────────────────────────────
23
23
  import { detectPlatform, getAdapter } from "./adapters/detect.js";
@@ -62,7 +62,7 @@ async function hookDispatch(platform, event) {
62
62
  process.exit(1);
63
63
  }
64
64
  const pluginRoot = getPluginRoot();
65
- await import(join(pluginRoot, scriptPath));
65
+ await import(pathToFileURL(join(pluginRoot, scriptPath)).href);
66
66
  }
67
67
  /* -------------------------------------------------------
68
68
  * Entry point
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Classify non-zero exit codes for ctx_execute / ctx_execute_file.
3
+ *
4
+ * Shell commands like `grep` exit 1 for "no matches" — not a real error.
5
+ * We treat exit code 1 as a soft failure when:
6
+ * - language is "shell"
7
+ * - exit code is exactly 1
8
+ * - stdout has non-whitespace content
9
+ */
10
+ export interface ExitClassification {
11
+ isError: boolean;
12
+ output: string;
13
+ }
14
+ export declare function classifyNonZeroExit(params: {
15
+ language: string;
16
+ exitCode: number;
17
+ stdout: string;
18
+ stderr: string;
19
+ }): ExitClassification;
@@ -0,0 +1,12 @@
1
+ export function classifyNonZeroExit(params) {
2
+ const { language, exitCode, stdout, stderr } = params;
3
+ const isSoftFail = language === "shell" &&
4
+ exitCode === 1 &&
5
+ stdout.trim().length > 0;
6
+ return {
7
+ isError: !isSoftFail,
8
+ output: isSoftFail
9
+ ? stdout
10
+ : `Exit code: ${exitCode}\n\nstdout:\n${stdout}\n\nstderr:\n${stderr}`,
11
+ };
12
+ }
package/build/server.js CHANGED
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { createRequire } from "node:module";
5
5
  import { createHash } from "node:crypto";
6
6
  import { existsSync, unlinkSync, readdirSync, readFileSync, rmSync } from "node:fs";
7
- import { join, dirname } from "node:path";
7
+ import { join, dirname, resolve } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import { homedir, tmpdir } from "node:os";
10
10
  import { z } from "zod";
@@ -12,7 +12,8 @@ import { PolyglotExecutor } from "./executor.js";
12
12
  import { ContentStore, cleanupStaleDBs } from "./store.js";
13
13
  import { readBashPolicies, evaluateCommandDenyOnly, extractShellCommands, readToolDenyPatterns, evaluateFilePath, } from "./security.js";
14
14
  import { detectRuntimes, getRuntimeSummary, getAvailableLanguages, hasBunRuntime, } from "./runtime.js";
15
- const VERSION = "1.0.5";
15
+ import { classifyNonZeroExit } from "./exit-classify.js";
16
+ const VERSION = "1.0.7";
16
17
  // Prevent silent server death from unhandled async errors
17
18
  process.on("unhandledRejection", (err) => {
18
19
  process.stderr.write(`[context-mode] unhandledRejection: ${err}\n`);
@@ -420,21 +421,23 @@ __cm_main().catch(e=>{console.error(e);process.exitCode=1});${background ? '\nse
420
421
  });
421
422
  }
422
423
  if (result.exitCode !== 0) {
423
- const output = `Exit code: ${result.exitCode}\n\nstdout:\n${result.stdout}\n\nstderr:\n${result.stderr}`;
424
+ const { isError, output } = classifyNonZeroExit({
425
+ language, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr,
426
+ });
424
427
  if (intent && intent.trim().length > 0 && Buffer.byteLength(output) > INTENT_SEARCH_THRESHOLD) {
425
428
  trackIndexed(Buffer.byteLength(output));
426
429
  return trackResponse("ctx_execute", {
427
430
  content: [
428
- { type: "text", text: intentSearch(output, intent, `execute:${language}:error`) },
431
+ { type: "text", text: intentSearch(output, intent, isError ? `execute:${language}:error` : `execute:${language}`) },
429
432
  ],
430
- isError: true,
433
+ isError,
431
434
  });
432
435
  }
433
436
  return trackResponse("ctx_execute", {
434
437
  content: [
435
438
  { type: "text", text: output },
436
439
  ],
437
- isError: true,
440
+ isError,
438
441
  });
439
442
  }
440
443
  const stdout = result.stdout || "(no output)";
@@ -598,21 +601,23 @@ server.registerTool("ctx_execute_file", {
598
601
  });
599
602
  }
600
603
  if (result.exitCode !== 0) {
601
- const output = `Error processing ${path} (exit ${result.exitCode}):\n${result.stderr || result.stdout}`;
604
+ const { isError, output } = classifyNonZeroExit({
605
+ language, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr,
606
+ });
602
607
  if (intent && intent.trim().length > 0 && Buffer.byteLength(output) > INTENT_SEARCH_THRESHOLD) {
603
608
  trackIndexed(Buffer.byteLength(output));
604
609
  return trackResponse("ctx_execute_file", {
605
610
  content: [
606
- { type: "text", text: intentSearch(output, intent, `file:${path}:error`) },
611
+ { type: "text", text: intentSearch(output, intent, isError ? `file:${path}:error` : `file:${path}`) },
607
612
  ],
608
- isError: true,
613
+ isError,
609
614
  });
610
615
  }
611
616
  return trackResponse("ctx_execute_file", {
612
617
  content: [
613
618
  { type: "text", text: output },
614
619
  ],
615
- isError: true,
620
+ isError,
616
621
  });
617
622
  }
618
623
  const stdout = result.stdout || "(no output)";
@@ -1455,6 +1460,20 @@ async function main() {
1455
1460
  process.on("SIGTERM", () => { shutdown(); process.exit(0); });
1456
1461
  const transport = new StdioServerTransport();
1457
1462
  await server.connect(transport);
1463
+ // Write routing instructions for hookless platforms (e.g. Codex CLI)
1464
+ try {
1465
+ const { detectPlatform, getAdapter } = await import("./adapters/detect.js");
1466
+ const signal = detectPlatform();
1467
+ const adapter = await getAdapter(signal.platform);
1468
+ if (!adapter.capabilities.sessionStart) {
1469
+ const pluginRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
1470
+ const projectDir = process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_HOME ?? process.cwd();
1471
+ const written = adapter.writeRoutingInstructions(projectDir, pluginRoot);
1472
+ if (written)
1473
+ console.error(`Wrote routing instructions: ${written}`);
1474
+ }
1475
+ }
1476
+ catch { /* best effort — don't block server startup */ }
1458
1477
  console.error(`Context Mode MCP server v${VERSION} running on stdio`);
1459
1478
  console.error(`Detected runtimes:\n${getRuntimeSummary(runtimes)}`);
1460
1479
  if (!hasBunRuntime()) {
@@ -244,17 +244,23 @@ function extractSkill(input) {
244
244
  }
245
245
  /**
246
246
  * Category 9: subagent
247
- * Agent tool calls (subagent dispatches).
247
+ * Agent tool calls tracks both launch and completion.
248
+ * When tool_response is present, the agent has completed and the result
249
+ * is captured at higher priority (P2) so it survives budget trimming.
248
250
  */
249
251
  function extractSubagent(input) {
250
252
  if (input.tool_name !== "Agent")
251
253
  return [];
252
- const prompt = String(input.tool_input["prompt"] ?? input.tool_input["description"] ?? "");
254
+ const prompt = truncate(String(input.tool_input["prompt"] ?? input.tool_input["description"] ?? ""), 200);
255
+ const response = input.tool_response ? truncate(String(input.tool_response), 300) : "";
256
+ const isCompleted = response.length > 0;
253
257
  return [{
254
- type: "subagent",
258
+ type: isCompleted ? "subagent_completed" : "subagent_launched",
255
259
  category: "subagent",
256
- data: truncate(prompt, 300),
257
- priority: 3,
260
+ data: isCompleted
261
+ ? truncate(`[completed] ${prompt} → ${response}`, 300)
262
+ : truncate(`[launched] ${prompt}`, 300),
263
+ priority: isCompleted ? 2 : 3,
258
264
  }];
259
265
  }
260
266
  /**
@@ -57,6 +57,11 @@ export declare function renderErrors(errorEvents: StoredEvent[]): string;
57
57
  * Render <intent> from the most recent intent event.
58
58
  */
59
59
  export declare function renderIntent(intentEvent: StoredEvent): string;
60
+ /**
61
+ * Render <subagents> from subagent events.
62
+ * Shows agent dispatch status (launched/completed) and result summaries.
63
+ */
64
+ export declare function renderSubagents(subagentEvents: StoredEvent[]): string;
60
65
  /**
61
66
  * Render <mcp_tools> from MCP tool call events.
62
67
  * Deduplicates by tool name, shows usage count.
@@ -190,6 +190,23 @@ export function renderErrors(errorEvents) {
190
190
  export function renderIntent(intentEvent) {
191
191
  return ` <intent mode="${escapeXML(intentEvent.data)}">${escapeXML(truncateString(intentEvent.data, 100))}</intent>`;
192
192
  }
193
+ /**
194
+ * Render <subagents> from subagent events.
195
+ * Shows agent dispatch status (launched/completed) and result summaries.
196
+ */
197
+ export function renderSubagents(subagentEvents) {
198
+ if (subagentEvents.length === 0)
199
+ return "";
200
+ const lines = [" <subagents>"];
201
+ for (const ev of subagentEvents) {
202
+ const status = ev.type === "subagent_completed" ? "completed"
203
+ : ev.type === "subagent_launched" ? "launched"
204
+ : "unknown";
205
+ lines.push(` <agent status="${status}">${escapeXML(truncateString(ev.data, 200))}</agent>`);
206
+ }
207
+ lines.push(" </subagents>");
208
+ return lines.join("\n");
209
+ }
193
210
  /**
194
211
  * Render <mcp_tools> from MCP tool call events.
195
212
  * Deduplicates by tool name, shows usage count.
@@ -297,7 +314,7 @@ export function buildResumeSnapshot(events, opts) {
297
314
  const rules = renderRules(ruleEvents);
298
315
  if (rules)
299
316
  p1Sections.push(rules);
300
- // P2 sections (35% budget): decisions, environment, errors_resolved
317
+ // P2 sections (35% budget): decisions, environment, errors_resolved, completed subagents
301
318
  const p2Sections = [];
302
319
  const decisions = renderDecisions(decisionEvents);
303
320
  if (decisions)
@@ -310,7 +327,12 @@ export function buildResumeSnapshot(events, opts) {
310
327
  const errors = renderErrors(errorEvents);
311
328
  if (errors)
312
329
  p2Sections.push(errors);
313
- // P3-P4 sections (15% budget): intent, mcp_tools
330
+ // Completed subagents are P2 — their results must survive budget trimming
331
+ const completedSubagents = subagentEvents.filter(e => e.type === "subagent_completed");
332
+ const subagentsP2 = renderSubagents(completedSubagents);
333
+ if (subagentsP2)
334
+ p2Sections.push(subagentsP2);
335
+ // P3-P4 sections (15% budget): intent, mcp_tools, launched subagents
314
336
  const p3Sections = [];
315
337
  if (intentEvents.length > 0) {
316
338
  const lastIntent = intentEvents[intentEvents.length - 1];
@@ -319,6 +341,10 @@ export function buildResumeSnapshot(events, opts) {
319
341
  const mcpTools = renderMcpTools(mcpEvents);
320
342
  if (mcpTools)
321
343
  p3Sections.push(mcpTools);
344
+ const launchedSubagents = subagentEvents.filter(e => e.type === "subagent_launched");
345
+ const subagentsP3 = renderSubagents(launchedSubagents);
346
+ if (subagentsP3)
347
+ p3Sections.push(subagentsP3);
322
348
  // ── Assemble with budget trimming ──
323
349
  const header = `<session_resume compact_count="${compactCount}" events_captured="${events.length}" generated_at="${now}">`;
324
350
  const footer = `</session_resume>`;
@@ -13,7 +13,7 @@ import { readStdin, getSessionId, getSessionDBPath, getProjectDir, GEMINI_OPTS }
13
13
  import { appendFileSync } from "node:fs";
14
14
  import { join, dirname } from "node:path";
15
15
  import { homedir } from "node:os";
16
- import { fileURLToPath } from "node:url";
16
+ import { fileURLToPath, pathToFileURL } from "node:url";
17
17
 
18
18
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
19
19
  const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
@@ -26,8 +26,8 @@ try {
26
26
 
27
27
  appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] CALL: ${input.tool_name}\n`);
28
28
 
29
- const { extractEvents } = await import(join(PKG_SESSION, "extract.js"));
30
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
29
+ const { extractEvents } = await import(pathToFileURL(join(PKG_SESSION, "extract.js")).href);
30
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
31
31
 
32
32
  const dbPath = getSessionDBPath(OPTS);
33
33
  const db = new SessionDB({ dbPath });
@@ -12,7 +12,7 @@ import { readStdin, getSessionId, getSessionDBPath, GEMINI_OPTS } from "../sessi
12
12
  import { appendFileSync } from "node:fs";
13
13
  import { join, dirname } from "node:path";
14
14
  import { homedir } from "node:os";
15
- import { fileURLToPath } from "node:url";
15
+ import { fileURLToPath, pathToFileURL } from "node:url";
16
16
 
17
17
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
18
18
  const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
@@ -23,8 +23,8 @@ try {
23
23
  const raw = await readStdin();
24
24
  const input = JSON.parse(raw);
25
25
 
26
- const { buildResumeSnapshot } = await import(join(PKG_SESSION, "snapshot.js"));
27
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
26
+ const { buildResumeSnapshot } = await import(pathToFileURL(join(PKG_SESSION, "snapshot.js")).href);
27
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
28
28
 
29
29
  const dbPath = getSessionDBPath(OPTS);
30
30
  const db = new SessionDB({ dbPath });
@@ -19,7 +19,7 @@ import {
19
19
  import { join, dirname } from "node:path";
20
20
  import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
21
21
  import { homedir } from "node:os";
22
- import { fileURLToPath } from "node:url";
22
+ import { fileURLToPath, pathToFileURL } from "node:url";
23
23
 
24
24
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
25
25
  const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
@@ -33,7 +33,7 @@ try {
33
33
  const source = input.source ?? "startup";
34
34
 
35
35
  if (source === "compact") {
36
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
36
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
37
37
  const dbPath = getSessionDBPath(OPTS);
38
38
  const db = new SessionDB({ dbPath });
39
39
  const sessionId = getSessionId(input, OPTS);
@@ -53,7 +53,7 @@ try {
53
53
  } else if (source === "resume") {
54
54
  try { unlinkSync(getCleanupFlagPath(OPTS)); } catch { /* no flag */ }
55
55
 
56
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
56
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
57
57
  const dbPath = getSessionDBPath(OPTS);
58
58
  const db = new SessionDB({ dbPath });
59
59
 
@@ -65,7 +65,7 @@ try {
65
65
 
66
66
  db.close();
67
67
  } else if (source === "startup") {
68
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
68
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
69
69
  const dbPath = getSessionDBPath(OPTS);
70
70
  const db = new SessionDB({ dbPath });
71
71
  try { unlinkSync(getSessionEventsPath(OPTS)); } catch { /* no stale file */ }
@@ -11,7 +11,7 @@ import "./suppress-stderr.mjs";
11
11
 
12
12
  import { readStdin, getSessionId, getSessionDBPath } from "./session-helpers.mjs";
13
13
  import { join, dirname } from "node:path";
14
- import { fileURLToPath } from "node:url";
14
+ import { fileURLToPath, pathToFileURL } from "node:url";
15
15
 
16
16
  // Resolve absolute path for imports — relative dynamic imports can fail
17
17
  // when Claude Code invokes hooks from a different working directory.
@@ -22,8 +22,8 @@ try {
22
22
  const raw = await readStdin();
23
23
  const input = JSON.parse(raw);
24
24
 
25
- const { extractEvents } = await import(join(PKG_SESSION, "extract.js"));
26
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
25
+ const { extractEvents } = await import(pathToFileURL(join(PKG_SESSION, "extract.js")).href);
26
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
27
27
 
28
28
  const dbPath = getSessionDBPath();
29
29
  const db = new SessionDB({ dbPath });
@@ -12,7 +12,7 @@ import { readStdin, getSessionId, getSessionDBPath } from "./session-helpers.mjs
12
12
  import { appendFileSync } from "node:fs";
13
13
  import { join, dirname } from "node:path";
14
14
  import { homedir } from "node:os";
15
- import { fileURLToPath } from "node:url";
15
+ import { fileURLToPath, pathToFileURL } from "node:url";
16
16
 
17
17
  // Resolve absolute path for imports
18
18
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
@@ -23,8 +23,8 @@ try {
23
23
  const raw = await readStdin();
24
24
  const input = JSON.parse(raw);
25
25
 
26
- const { buildResumeSnapshot } = await import(join(PKG_SESSION, "snapshot.js"));
27
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
26
+ const { buildResumeSnapshot } = await import(pathToFileURL(join(PKG_SESSION, "snapshot.js")).href);
27
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
28
28
 
29
29
  const dbPath = getSessionDBPath();
30
30
  const db = new SessionDB({ dbPath });
@@ -18,7 +18,7 @@ import { ROUTING_BLOCK } from "./routing-block.mjs";
18
18
  import { readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath } from "./session-helpers.mjs";
19
19
  import { writeSessionEventsFile, buildSessionDirective, getAllProjectEvents } from "./session-directive.mjs";
20
20
  import { join, dirname } from "node:path";
21
- import { fileURLToPath } from "node:url";
21
+ import { fileURLToPath, pathToFileURL } from "node:url";
22
22
  import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
23
23
  import { homedir } from "node:os";
24
24
 
@@ -35,7 +35,7 @@ try {
35
35
 
36
36
  if (source === "compact") {
37
37
  // Session was compacted — write events to file for auto-indexing, inject directive only
38
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
38
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
39
39
  const dbPath = getSessionDBPath();
40
40
  const db = new SessionDB({ dbPath });
41
41
  const sessionId = getSessionId(input);
@@ -56,7 +56,7 @@ try {
56
56
  // User used --continue — clear cleanup flag so startup doesn't wipe data
57
57
  try { unlinkSync(getCleanupFlagPath()); } catch { /* no flag */ }
58
58
 
59
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
59
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
60
60
  const dbPath = getSessionDBPath();
61
61
  const db = new SessionDB({ dbPath });
62
62
 
@@ -69,7 +69,7 @@ try {
69
69
  db.close();
70
70
  } else if (source === "startup") {
71
71
  // Fresh session (no --continue) — clean slate, capture CLAUDE.md rules.
72
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
72
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
73
73
  const dbPath = getSessionDBPath();
74
74
  const db = new SessionDB({ dbPath });
75
75
  try { unlinkSync(getSessionEventsPath()); } catch { /* no stale file */ }
@@ -11,7 +11,7 @@ import "./suppress-stderr.mjs";
11
11
 
12
12
  import { readStdin, getSessionId, getSessionDBPath } from "./session-helpers.mjs";
13
13
  import { join, dirname } from "node:path";
14
- import { fileURLToPath } from "node:url";
14
+ import { fileURLToPath, pathToFileURL } from "node:url";
15
15
 
16
16
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
17
17
  const PKG_SESSION = join(HOOK_DIR, "..", "build", "session");
@@ -30,8 +30,8 @@ try {
30
30
  || trimmed.startsWith("<tool-result>");
31
31
 
32
32
  if (trimmed.length > 0 && !isSystemMessage) {
33
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
34
- const { extractUserEvents } = await import(join(PKG_SESSION, "extract.js"));
33
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
34
+ const { extractUserEvents } = await import(pathToFileURL(join(PKG_SESSION, "extract.js")).href);
35
35
  const dbPath = getSessionDBPath();
36
36
  const db = new SessionDB({ dbPath });
37
37
  const sessionId = getSessionId(input);
@@ -12,8 +12,8 @@ import "../suppress-stderr.mjs";
12
12
  import { readStdin, getSessionId, getSessionDBPath, getProjectDir, VSCODE_OPTS } from "../session-helpers.mjs";
13
13
  import { appendFileSync } from "node:fs";
14
14
  import { join, dirname } from "node:path";
15
+ import { fileURLToPath, pathToFileURL } from "node:url";
15
16
  import { homedir } from "node:os";
16
- import { fileURLToPath } from "node:url";
17
17
 
18
18
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
19
19
  const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
@@ -26,8 +26,8 @@ try {
26
26
 
27
27
  appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] CALL: ${input.tool_name}\n`);
28
28
 
29
- const { extractEvents } = await import(join(PKG_SESSION, "extract.js"));
30
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
29
+ const { extractEvents } = await import(pathToFileURL(join(PKG_SESSION, "extract.js")).href);
30
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
31
31
 
32
32
  const dbPath = getSessionDBPath(OPTS);
33
33
  const db = new SessionDB({ dbPath });
@@ -11,8 +11,8 @@ import "../suppress-stderr.mjs";
11
11
  import { readStdin, getSessionId, getSessionDBPath, VSCODE_OPTS } from "../session-helpers.mjs";
12
12
  import { appendFileSync } from "node:fs";
13
13
  import { join, dirname } from "node:path";
14
+ import { fileURLToPath, pathToFileURL } from "node:url";
14
15
  import { homedir } from "node:os";
15
- import { fileURLToPath } from "node:url";
16
16
 
17
17
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
18
18
  const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
@@ -23,8 +23,8 @@ try {
23
23
  const raw = await readStdin();
24
24
  const input = JSON.parse(raw);
25
25
 
26
- const { buildResumeSnapshot } = await import(join(PKG_SESSION, "snapshot.js"));
27
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
26
+ const { buildResumeSnapshot } = await import(pathToFileURL(join(PKG_SESSION, "snapshot.js")).href);
27
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
28
28
 
29
29
  const dbPath = getSessionDBPath(OPTS);
30
30
  const db = new SessionDB({ dbPath });
@@ -18,9 +18,10 @@ import {
18
18
  } from "../session-helpers.mjs";
19
19
  import { join } from "node:path";
20
20
  import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
21
+ import { fileURLToPath, pathToFileURL } from "node:url";
21
22
  import { homedir } from "node:os";
22
23
 
23
- const HOOK_DIR = new URL(".", import.meta.url).pathname;
24
+ const HOOK_DIR = fileURLToPath(new URL(".", import.meta.url));
24
25
  const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
25
26
  const OPTS = VSCODE_OPTS;
26
27
 
@@ -32,7 +33,7 @@ try {
32
33
  const source = input.source ?? "startup";
33
34
 
34
35
  if (source === "compact") {
35
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
36
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
36
37
  const dbPath = getSessionDBPath(OPTS);
37
38
  const db = new SessionDB({ dbPath });
38
39
  const sessionId = getSessionId(input, OPTS);
@@ -52,7 +53,7 @@ try {
52
53
  } else if (source === "resume") {
53
54
  try { unlinkSync(getCleanupFlagPath(OPTS)); } catch { /* no flag */ }
54
55
 
55
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
56
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
56
57
  const dbPath = getSessionDBPath(OPTS);
57
58
  const db = new SessionDB({ dbPath });
58
59
 
@@ -64,7 +65,7 @@ try {
64
65
 
65
66
  db.close();
66
67
  } else if (source === "startup") {
67
- const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
68
+ const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
68
69
  const dbPath = getSessionDBPath(OPTS);
69
70
  const db = new SessionDB({ dbPath });
70
71
  try { unlinkSync(getSessionEventsPath(OPTS)); } catch { /* no stale file */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "type": "module",
5
5
  "description": "MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
6
6
  "author": "Mert Koseoğlu",
@@ -26,10 +26,11 @@
26
26
  },
27
27
  "homepage": "https://github.com/mksglu/context-mode#readme",
28
28
  "bugs": "https://github.com/mksglu/context-mode/issues",
29
- "main": "./build/cli.js",
29
+ "main": "./build/opencode-plugin.js",
30
30
  "exports": {
31
- ".": "./build/cli.js",
32
- "./plugin": "./build/opencode-plugin.js"
31
+ ".": "./build/opencode-plugin.js",
32
+ "./plugin": "./build/opencode-plugin.js",
33
+ "./cli": "./build/cli.js"
33
34
  },
34
35
  "bin": {
35
36
  "context-mode": "./build/cli.js"
package/server.bundle.mjs CHANGED
@@ -202,7 +202,7 @@ ${n}`}}};import{createRequire as YE}from"node:module";var dd=null;function $y(){
202
202
  `);Buffer.byteLength(x)>r&&p.length>1&&(p.pop(),g(),p=[_])}g(),i=[]},u=0;for(;u<o.length;){let l=o[u];if(/^[-_*]{3,}\s*$/.test(l)){c(),u++;continue}let d=l.match(/^(#{1,4})\s+(.+)$/);if(d){c();let m=d[1].length,p=d[2].trim();for(;s.length>0&&s[s.length-1].level>=m;)s.pop();s.push({level:m,text:p}),a=p,i.push(l),u++;continue}let f=l.match(/^(`{3,})(.*)?$/);if(f){let m=f[1],p=[l];for(u++;u<o.length;){if(p.push(o[u]),o[u].startsWith(m)&&o[u].trim()===m){u++;break}u++}i.push(...p);continue}i.push(l),u++}return c(),n}#T(e,r){let n=e.split(/\n\s*\n/);if(n.length>=3&&n.length<=200&&n.every(c=>Buffer.byteLength(c)<5e3))return n.map((c,u)=>{let l=c.trim();return{title:l.split(`
203
203
  `)[0].slice(0,80)||`Section ${u+1}`,content:l}}).filter(c=>c.content.length>0);let o=e.split(`
204
204
  `);if(o.length<=r)return[{title:"Output",content:e}];let s=[],a=Math.max(r-2,1);for(let c=0;c<o.length;c+=a){let u=o.slice(c,c+r);if(u.length===0)break;let l=c+1,d=Math.min(c+u.length,o.length),f=u[0]?.trim().slice(0,80);s.push({title:f||`Lines ${l}-${d}`,content:u.join(`
205
- `)})}return s}#b(e,r,n,o){let s=r.length>0?r.join(" > "):"(root)",i=JSON.stringify(e,null,2);if(Buffer.byteLength(i)<=o&&!(typeof e=="object"&&e!==null&&!Array.isArray(e)&&Object.values(e).some(c=>typeof c=="object"&&c!==null))){n.push({title:s,content:i,hasCode:!0});return}if(typeof e=="object"&&e!==null&&!Array.isArray(e)){let a=Object.entries(e);if(a.length>0){for(let[c,u]of a)this.#b(u,[...r,c],n,o);return}n.push({title:s,content:i,hasCode:!0});return}if(Array.isArray(e)){this.#P(e,r,n,o);return}n.push({title:s,content:i,hasCode:!1})}#E(e){if(e.length===0)return null;let r=e[0];if(typeof r!="object"||r===null||Array.isArray(r))return null;let n=["id","name","title","path","slug","key","label"],o=r;for(let s of n)if(s in o&&(typeof o[s]=="string"||typeof o[s]=="number"))return s;return null}#z(e,r,n,o,s){let i=e?`${e} > `:"";if(!s)return r===n?`${i}[${r}]`:`${i}[${r}-${n}]`;let a=c=>String(c[s]);return o.length===1?`${i}${a(o[0])}`:o.length<=3?i+o.map(a).join(", "):`${i}${a(o[0])}\u2026${a(o[o.length-1])}`}#P(e,r,n,o){let s=r.length>0?r.join(" > "):"(root)",i=this.#E(e),a=[],c=0,u=l=>{if(a.length===0)return;let d=this.#z(s,c,l,a,i);n.push({title:d,content:JSON.stringify(a,null,2),hasCode:!0})};for(let l=0;l<e.length;l++){a.push(e[l]);let d=JSON.stringify(a,null,2);Buffer.byteLength(d)>o&&a.length>1&&(a.pop(),u(l-1),a=[e[l]],c=l)}u(c+a.length-1)}#R(e,r){return e.length===0?r||"Untitled":e.map(n=>n.text).join(" > ")}};import{readFileSync as Iy}from"node:fs";import{resolve as bn}from"node:path";import{homedir as Oy}from"node:os";function Ny(t){let e=t.match(/^Bash\((.+)\)$/);return e?e[1]:null}function oz(t){let e=t.match(/^(\w+)\((.+)\)$/);return e?{tool:e[1],glob:e[2]}:null}function sz(t){return t.replace(/[.*+?^${}()|[\]\\\/\-]/g,"\\$&")}function Ry(t){return t.replace(/[.+?^${}()|[\]\\\/\-]/g,"\\$&").replace(/\*/g,".*")}function iz(t,e=!1){let r,n=t.indexOf(":");if(n!==-1){let o=t.slice(0,n),s=t.slice(n+1),i=sz(o),a=Ry(s);r=`^${i}(\\s${a})?$`}else r=`^${Ry(t)}$`;return new RegExp(r,e?"i":"")}function az(t,e=!1){let r="",n=0;for(;n<t.length;)t[n]==="*"&&t[n+1]==="*"?n+2<t.length&&t[n+2]==="/"?(r+="(.*/)?",n+=3):(r+=".*",n+=2):t[n]==="*"?(r+="[^/]*",n++):t[n]==="?"?(r+="[^/]",n++):(r+=t[n].replace(/[.+^${}()|[\]\\\/\-]/g,"\\$&"),n++);return new RegExp(`^${r}$`,e?"i":"")}function cz(t,e,r=!1){for(let n of e){let o=Ny(n);if(o&&iz(o,r).test(t))return n}return null}function uz(t){let e=[],r="",n=!1,o=!1,s=!1;for(let i=0;i<t.length;i++){let a=t[i],c=i>0?t[i-1]:"";a==="'"&&!o&&!s&&c!=="\\"?(n=!n,r+=a):a==='"'&&!n&&!s&&c!=="\\"?(o=!o,r+=a):a==="`"&&!n&&!o&&c!=="\\"?(s=!s,r+=a):!n&&!o&&!s?a===";"?(e.push(r.trim()),r=""):a==="|"&&t[i+1]==="|"||a==="&"&&t[i+1]==="&"?(e.push(r.trim()),r="",i++):a==="|"?(e.push(r.trim()),r=""):r+=a:r+=a}return r.trim()&&e.push(r.trim()),e.filter(i=>i.length>0)}function pd(t){let e;try{e=Iy(t,"utf-8")}catch{return null}let r;try{r=JSON.parse(e)}catch{return null}let n=r?.permissions;if(!n||typeof n!="object")return null;let o=s=>Array.isArray(s)?s.filter(i=>typeof i=="string"&&Ny(i)!==null):[];return{allow:o(n.allow),deny:o(n.deny),ask:o(n.ask)}}function fd(t,e){let r=[];if(t){let s=bn(t,".claude","settings.local.json"),i=pd(s);i&&r.push(i);let a=bn(t,".claude","settings.json"),c=pd(a);c&&r.push(c)}let n=e??bn(Oy(),".claude","settings.json"),o=pd(n);return o&&r.push(o),r}function Cy(t,e,r){let n=[],o=a=>{let c;try{c=Iy(a,"utf-8")}catch{return null}let u;try{u=JSON.parse(c)}catch{return null}let l=u?.permissions?.deny;if(!Array.isArray(l))return[];let d=[];for(let f of l){if(typeof f!="string")continue;let m=oz(f);m&&m.tool===t&&d.push(m.glob)}return d};if(e){let a=o(bn(e,".claude","settings.local.json"));a!==null&&n.push(a);let c=o(bn(e,".claude","settings.json"));c!==null&&n.push(c)}let s=r??bn(Oy(),".claude","settings.json"),i=o(s);return i!==null&&n.push(i),n}function md(t,e,r=process.platform==="win32"){let n=uz(t);for(let o of n)for(let s of e){let i=cz(o,s.deny,r);if(i)return{decision:"deny",matchedPattern:i}}return{decision:"allow"}}function Ay(t,e,r=process.platform==="win32"){let n=t.replace(/\\/g,"/");for(let o of e)for(let s of o)if(az(s,r).test(n))return{denied:!0,matchedPattern:s};return{denied:!1}}var lz={python:[/os\.system\(\s*(['"])(.*?)\1\s*\)/g,/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*(['"])(.*?)\1/g],javascript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],typescript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],ruby:[/system\(\s*(['"])(.*?)\1/g,/`(.*?)`/g],go:[/exec\.Command\(\s*(['"`])(.*?)\1/g],php:[/shell_exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])system\(\s*(['"`])(.*?)\1/g,/passthru\(\s*(['"`])(.*?)\1/g,/proc_open\(\s*(['"`])(.*?)\1/g],rust:[/Command::new\(\s*(['"`])(.*?)\1/g]};function dz(t){let e=[],r=/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*\[([^\]]+)\]/g,n;for(;(n=r.exec(t))!==null;){let s=[...n[1].matchAll(/(['"])(.*?)\1/g)].map(i=>i[2]);s.length>0&&e.push(s.join(" "))}return e}function jy(t,e){let r=lz[e];if(!r&&e!=="python")return[];let n=[];if(r)for(let o of r){o.lastIndex=0;let s;for(;(s=o.exec(t))!==null;){let i=s[s.length-1];i&&n.push(i)}}return e==="python"&&n.push(...dz(t)),n}var Fy="1.0.5";process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
205
+ `)})}return s}#b(e,r,n,o){let s=r.length>0?r.join(" > "):"(root)",i=JSON.stringify(e,null,2);if(Buffer.byteLength(i)<=o&&!(typeof e=="object"&&e!==null&&!Array.isArray(e)&&Object.values(e).some(c=>typeof c=="object"&&c!==null))){n.push({title:s,content:i,hasCode:!0});return}if(typeof e=="object"&&e!==null&&!Array.isArray(e)){let a=Object.entries(e);if(a.length>0){for(let[c,u]of a)this.#b(u,[...r,c],n,o);return}n.push({title:s,content:i,hasCode:!0});return}if(Array.isArray(e)){this.#P(e,r,n,o);return}n.push({title:s,content:i,hasCode:!1})}#E(e){if(e.length===0)return null;let r=e[0];if(typeof r!="object"||r===null||Array.isArray(r))return null;let n=["id","name","title","path","slug","key","label"],o=r;for(let s of n)if(s in o&&(typeof o[s]=="string"||typeof o[s]=="number"))return s;return null}#z(e,r,n,o,s){let i=e?`${e} > `:"";if(!s)return r===n?`${i}[${r}]`:`${i}[${r}-${n}]`;let a=c=>String(c[s]);return o.length===1?`${i}${a(o[0])}`:o.length<=3?i+o.map(a).join(", "):`${i}${a(o[0])}\u2026${a(o[o.length-1])}`}#P(e,r,n,o){let s=r.length>0?r.join(" > "):"(root)",i=this.#E(e),a=[],c=0,u=l=>{if(a.length===0)return;let d=this.#z(s,c,l,a,i);n.push({title:d,content:JSON.stringify(a,null,2),hasCode:!0})};for(let l=0;l<e.length;l++){a.push(e[l]);let d=JSON.stringify(a,null,2);Buffer.byteLength(d)>o&&a.length>1&&(a.pop(),u(l-1),a=[e[l]],c=l)}u(c+a.length-1)}#R(e,r){return e.length===0?r||"Untitled":e.map(n=>n.text).join(" > ")}};import{readFileSync as Iy}from"node:fs";import{resolve as bn}from"node:path";import{homedir as Oy}from"node:os";function Ny(t){let e=t.match(/^Bash\((.+)\)$/);return e?e[1]:null}function oz(t){let e=t.match(/^(\w+)\((.+)\)$/);return e?{tool:e[1],glob:e[2]}:null}function sz(t){return t.replace(/[.*+?^${}()|[\]\\\/\-]/g,"\\$&")}function Ry(t){return t.replace(/[.+?^${}()|[\]\\\/\-]/g,"\\$&").replace(/\*/g,".*")}function iz(t,e=!1){let r,n=t.indexOf(":");if(n!==-1){let o=t.slice(0,n),s=t.slice(n+1),i=sz(o),a=Ry(s);r=`^${i}(\\s${a})?$`}else r=`^${Ry(t)}$`;return new RegExp(r,e?"i":"")}function az(t,e=!1){let r="",n=0;for(;n<t.length;)t[n]==="*"&&t[n+1]==="*"?n+2<t.length&&t[n+2]==="/"?(r+="(.*/)?",n+=3):(r+=".*",n+=2):t[n]==="*"?(r+="[^/]*",n++):t[n]==="?"?(r+="[^/]",n++):(r+=t[n].replace(/[.+^${}()|[\]\\\/\-]/g,"\\$&"),n++);return new RegExp(`^${r}$`,e?"i":"")}function cz(t,e,r=!1){for(let n of e){let o=Ny(n);if(o&&iz(o,r).test(t))return n}return null}function uz(t){let e=[],r="",n=!1,o=!1,s=!1;for(let i=0;i<t.length;i++){let a=t[i],c=i>0?t[i-1]:"";a==="'"&&!o&&!s&&c!=="\\"?(n=!n,r+=a):a==='"'&&!n&&!s&&c!=="\\"?(o=!o,r+=a):a==="`"&&!n&&!o&&c!=="\\"?(s=!s,r+=a):!n&&!o&&!s?a===";"?(e.push(r.trim()),r=""):a==="|"&&t[i+1]==="|"||a==="&"&&t[i+1]==="&"?(e.push(r.trim()),r="",i++):a==="|"?(e.push(r.trim()),r=""):r+=a:r+=a}return r.trim()&&e.push(r.trim()),e.filter(i=>i.length>0)}function pd(t){let e;try{e=Iy(t,"utf-8")}catch{return null}let r;try{r=JSON.parse(e)}catch{return null}let n=r?.permissions;if(!n||typeof n!="object")return null;let o=s=>Array.isArray(s)?s.filter(i=>typeof i=="string"&&Ny(i)!==null):[];return{allow:o(n.allow),deny:o(n.deny),ask:o(n.ask)}}function fd(t,e){let r=[];if(t){let s=bn(t,".claude","settings.local.json"),i=pd(s);i&&r.push(i);let a=bn(t,".claude","settings.json"),c=pd(a);c&&r.push(c)}let n=e??bn(Oy(),".claude","settings.json"),o=pd(n);return o&&r.push(o),r}function Cy(t,e,r){let n=[],o=a=>{let c;try{c=Iy(a,"utf-8")}catch{return null}let u;try{u=JSON.parse(c)}catch{return null}let l=u?.permissions?.deny;if(!Array.isArray(l))return[];let d=[];for(let f of l){if(typeof f!="string")continue;let m=oz(f);m&&m.tool===t&&d.push(m.glob)}return d};if(e){let a=o(bn(e,".claude","settings.local.json"));a!==null&&n.push(a);let c=o(bn(e,".claude","settings.json"));c!==null&&n.push(c)}let s=r??bn(Oy(),".claude","settings.json"),i=o(s);return i!==null&&n.push(i),n}function md(t,e,r=process.platform==="win32"){let n=uz(t);for(let o of n)for(let s of e){let i=cz(o,s.deny,r);if(i)return{decision:"deny",matchedPattern:i}}return{decision:"allow"}}function Ay(t,e,r=process.platform==="win32"){let n=t.replace(/\\/g,"/");for(let o of e)for(let s of o)if(az(s,r).test(n))return{denied:!0,matchedPattern:s};return{denied:!1}}var lz={python:[/os\.system\(\s*(['"])(.*?)\1\s*\)/g,/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*(['"])(.*?)\1/g],javascript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],typescript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],ruby:[/system\(\s*(['"])(.*?)\1/g,/`(.*?)`/g],go:[/exec\.Command\(\s*(['"`])(.*?)\1/g],php:[/shell_exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])system\(\s*(['"`])(.*?)\1/g,/passthru\(\s*(['"`])(.*?)\1/g,/proc_open\(\s*(['"`])(.*?)\1/g],rust:[/Command::new\(\s*(['"`])(.*?)\1/g]};function dz(t){let e=[],r=/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*\[([^\]]+)\]/g,n;for(;(n=r.exec(t))!==null;){let s=[...n[1].matchAll(/(['"])(.*?)\1/g)].map(i=>i[2]);s.length>0&&e.push(s.join(" "))}return e}function jy(t,e){let r=lz[e];if(!r&&e!=="python")return[];let n=[];if(r)for(let o of r){o.lastIndex=0;let s;for(;(s=o.exec(t))!==null;){let i=s[s.length-1];i&&n.push(i)}}return e==="python"&&n.push(...dz(t)),n}var Fy="1.0.6";process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
206
206
  `)});process.on("uncaughtException",t=>{process.stderr.write(`[context-mode] uncaughtException: ${t?.message??t}
207
207
  `)});var vd=xi(),_z=gy(vd),Et=new yi({name:"context-mode",version:Fy}),zo=new $i({runtimes:vd,projectRoot:process.env.CLAUDE_PROJECT_DIR}),$n=null;function vz(t){try{let e=ki(qy(),".claude","context-mode","sessions");if(!Dy(e))return;let r=mz(e).filter(n=>n.endsWith("-events.md"));for(let n of r){let o=ki(e,n);try{t.index({path:o,source:"session-events"}),fz(o)}catch{}}}catch{}}function Po(){return $n||($n=new wi),vz($n),$n}var De={calls:{},bytesReturned:{},bytesIndexed:0,bytesSandboxed:0,sessionStart:Date.now()};function W(t,e){let r=e.content.reduce((n,o)=>n+Buffer.byteLength(o.text),0);return De.calls[t]=(De.calls[t]||0)+1,De.bytesReturned[t]=(De.bytesReturned[t]||0)+r,e}function ir(t){De.bytesIndexed+=t}function xd(t,e){try{let r=fd(process.env.CLAUDE_PROJECT_DIR),n=md(t,r);if(n.decision==="deny")return W(e,{content:[{type:"text",text:`Command blocked by security policy: matches deny pattern ${n.matchedPattern}`}],isError:!0})}catch{}return null}function Uy(t,e,r){try{let n=jy(t,e);if(n.length===0)return null;let o=fd(process.env.CLAUDE_PROJECT_DIR);for(let s of n){let i=md(s,o);if(i.decision==="deny")return W(r,{content:[{type:"text",text:`Command blocked by security policy: embedded shell command "${s}" matches deny pattern ${i.matchedPattern}`}],isError:!0})}}catch{}return null}function xz(t,e){try{let r=Cy("Read",process.env.CLAUDE_PROJECT_DIR),n=Ay(t,r);if(n.denied)return W(e,{content:[{type:"text",text:`File access blocked by security policy: path matches Read deny pattern ${n.matchedPattern}`}],isError:!0})}catch{}return null}var bz=_z.join(", "),$z=ud()?" (Bun detected \u2014 JS/TS runs 3-5x faster)":"",wz="",kz="";function Sz(t){let e=[],r=0,n=0;for(;n<t.length;)if(t[n]===wz){for(e.push(r),n++;n<t.length&&t[n]!==kz;)r++,n++;n<t.length&&n++}else r++,n++;return e}function Vy(t,e,r=1500,n){if(t.length<=r)return t;let o=[];if(n)for(let u of Sz(n))o.push(u);if(o.length===0){let u=e.toLowerCase().split(/\s+/).filter(d=>d.length>2),l=t.toLowerCase();for(let d of u){let f=l.indexOf(d);for(;f!==-1;)o.push(f),f=l.indexOf(d,f+1)}}if(o.length===0)return t.slice(0,r)+`
208
208
  \u2026`;o.sort((u,l)=>u-l);let s=300,i=[];for(let u of o){let l=Math.max(0,u-s),d=Math.min(t.length,u+s);i.length>0&&l<=i[i.length-1][1]?i[i.length-1][1]=d:i.push([l,d])}let a=[],c=0;for(let[u,l]of i){if(c>=r)break;let d=t.slice(u,Math.min(l,u+(r-c)));a.push((u>0?"\u2026":"")+d+(l<t.length?"\u2026":"")),c+=d.length}return a.join(`