context-mode 1.0.6 → 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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/build/adapters/claude-code/hooks.d.ts +3 -3
- package/build/adapters/claude-code/hooks.js +7 -3
- package/build/adapters/claude-code/index.js +1 -1
- package/build/adapters/detect.d.ts +6 -7
- package/build/adapters/detect.js +23 -24
- package/build/adapters/gemini-cli/hooks.d.ts +3 -3
- package/build/adapters/gemini-cli/hooks.js +7 -3
- package/build/adapters/gemini-cli/index.d.ts +2 -2
- package/build/adapters/gemini-cli/index.js +8 -8
- package/build/adapters/types.d.ts +1 -1
- package/build/adapters/vscode-copilot/hooks.d.ts +3 -3
- package/build/adapters/vscode-copilot/hooks.js +6 -3
- package/build/adapters/vscode-copilot/index.d.ts +2 -2
- package/build/adapters/vscode-copilot/index.js +8 -8
- package/build/cli.js +2 -2
- package/build/exit-classify.d.ts +19 -0
- package/build/exit-classify.js +12 -0
- package/build/server.js +29 -10
- package/hooks/gemini-cli/aftertool.mjs +3 -3
- package/hooks/gemini-cli/precompress.mjs +3 -3
- package/hooks/gemini-cli/sessionstart.mjs +4 -4
- package/hooks/posttooluse.mjs +3 -3
- package/hooks/precompact.mjs +3 -3
- package/hooks/sessionstart.mjs +4 -4
- package/hooks/userpromptsubmit.mjs +3 -3
- package/hooks/vscode-copilot/posttooluse.mjs +3 -3
- package/hooks/vscode-copilot/precompact.mjs +3 -3
- package/hooks/vscode-copilot/sessionstart.mjs +5 -4
- package/package.json +5 -4
- package/server.bundle.mjs +389 -0
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "1.0.
|
|
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.
|
|
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.
|
|
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
|
|
53
|
-
*
|
|
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
|
|
87
|
-
*
|
|
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
|
-
*
|
|
10
|
-
* - Claude Code:
|
|
11
|
-
* - Gemini CLI:
|
|
12
|
-
* - OpenCode:
|
|
13
|
-
* -
|
|
14
|
-
* - VS Code:
|
|
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
|
/**
|
package/build/adapters/detect.js
CHANGED
|
@@ -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
|
-
*
|
|
10
|
-
* - Claude Code:
|
|
11
|
-
* - Gemini CLI:
|
|
12
|
-
* - OpenCode:
|
|
13
|
-
* -
|
|
14
|
-
* - VS Code:
|
|
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.
|
|
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
|
|
35
|
+
reason: "GEMINI_PROJECT_DIR or GEMINI_CLI env var set",
|
|
37
36
|
};
|
|
38
37
|
}
|
|
39
|
-
if (process.env.
|
|
38
|
+
if (process.env.OPENCODE || process.env.OPENCODE_PID) {
|
|
40
39
|
return {
|
|
41
40
|
platform: "opencode",
|
|
42
41
|
confidence: "high",
|
|
43
|
-
reason: "
|
|
42
|
+
reason: "OPENCODE or OPENCODE_PID env var set",
|
|
44
43
|
};
|
|
45
44
|
}
|
|
46
|
-
if (process.env.
|
|
45
|
+
if (process.env.CODEX_CI || process.env.CODEX_THREAD_ID) {
|
|
47
46
|
return {
|
|
48
|
-
platform: "
|
|
47
|
+
platform: "codex",
|
|
49
48
|
confidence: "high",
|
|
50
|
-
reason: "
|
|
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, ".
|
|
75
|
+
if (existsSync(resolve(home, ".codex"))) {
|
|
84
76
|
return {
|
|
85
|
-
platform: "
|
|
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: "~/.
|
|
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
|
|
44
|
-
*
|
|
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
|
|
63
|
-
*
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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 =
|
|
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" | "
|
|
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
|
|
49
|
-
*
|
|
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
|
|
71
|
-
*
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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()) {
|
|
@@ -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 });
|