context-mode 1.0.41 → 1.0.42

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.
@@ -10,7 +10,11 @@
10
10
  * - null (passthrough)
11
11
  */
12
12
 
13
- import { ROUTING_BLOCK, READ_GUIDANCE, GREP_GUIDANCE, BASH_GUIDANCE } from "../routing-block.mjs";
13
+ import {
14
+ ROUTING_BLOCK, READ_GUIDANCE, GREP_GUIDANCE, BASH_GUIDANCE,
15
+ createRoutingBlock, createReadGuidance, createGrepGuidance, createBashGuidance,
16
+ } from "../routing-block.mjs";
17
+ import { createToolNamer } from "./tool-naming.mjs";
14
18
  import { existsSync, mkdirSync, rmSync, openSync, closeSync, constants as fsConstants } from "node:fs";
15
19
  import { tmpdir } from "node:os";
16
20
  import { resolve } from "node:path";
@@ -129,8 +133,22 @@ const TOOL_ALIASES = {
129
133
 
130
134
  /**
131
135
  * Route a PreToolUse event. Returns normalized decision object or null for passthrough.
136
+ *
137
+ * @param {string} toolName - The tool name as reported by the platform
138
+ * @param {object} toolInput - The tool input/parameters
139
+ * @param {string} [projectDir] - Project directory for security policy lookup
140
+ * @param {string} [platform="claude-code"] - Platform ID for tool name formatting
132
141
  */
133
- export function routePreToolUse(toolName, toolInput, projectDir) {
142
+ export function routePreToolUse(toolName, toolInput, projectDir, platform) {
143
+ // Build platform-specific tool namer (defaults to claude-code for backward compat)
144
+ const t = createToolNamer(platform || "claude-code");
145
+
146
+ // Build platform-specific guidance/routing content
147
+ const routingBlock = platform ? createRoutingBlock(t) : ROUTING_BLOCK;
148
+ const readGuidance = platform ? createReadGuidance(t) : READ_GUIDANCE;
149
+ const grepGuidance = platform ? createGrepGuidance(t) : GREP_GUIDANCE;
150
+ const bashGuidance = platform ? createBashGuidance(t) : BASH_GUIDANCE;
151
+
134
152
  // Normalize platform-specific tool name to canonical
135
153
  const canonical = TOOL_ALIASES[toolName] ?? toolName;
136
154
 
@@ -167,7 +185,7 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
167
185
  return {
168
186
  action: "modify",
169
187
  updatedInput: {
170
- command: 'echo "context-mode: curl/wget blocked. You MUST use mcp__plugin_context-mode_context-mode__ctx_fetch_and_index(url, source) to fetch URLs, or mcp__plugin_context-mode_context-mode__ctx_execute(language, code) to run HTTP calls in sandbox. Do NOT retry with curl/wget."',
188
+ command: `echo "context-mode: curl/wget blocked. You MUST use ${t("ctx_fetch_and_index")}(url, source) to fetch URLs, or ${t("ctx_execute")}(language, code) to run HTTP calls in sandbox. Do NOT retry with curl/wget."`,
171
189
  },
172
190
  };
173
191
  }
@@ -186,7 +204,7 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
186
204
  return {
187
205
  action: "modify",
188
206
  updatedInput: {
189
- command: 'echo "context-mode: Inline HTTP blocked. Use mcp__plugin_context-mode_context-mode__ctx_execute(language, code) to run HTTP calls in sandbox, or mcp__plugin_context-mode_context-mode__ctx_fetch_and_index(url, source) for web pages. Do NOT retry with Bash."',
207
+ command: `echo "context-mode: Inline HTTP blocked. Use ${t("ctx_execute")}(language, code) to run HTTP calls in sandbox, or ${t("ctx_fetch_and_index")}(url, source) for web pages. Do NOT retry with Bash."`,
190
208
  },
191
209
  };
192
210
  }
@@ -198,23 +216,23 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
198
216
  return {
199
217
  action: "modify",
200
218
  updatedInput: {
201
- command: `echo "context-mode: Build tool redirected to sandbox. Use mcp__plugin_context-mode_context-mode__ctx_execute(language: \\"shell\\", code: \\"${safeCmd}\\") to run this command. Do NOT retry with Bash."`,
219
+ command: `echo "context-mode: Build tool redirected to sandbox. Use ${t("ctx_execute")}(language: \\"shell\\", code: \\"${safeCmd}\\") to run this command. Do NOT retry with Bash."`,
202
220
  },
203
221
  };
204
222
  }
205
223
 
206
224
  // allow all other Bash commands, but inject routing nudge (once per session)
207
- return guidanceOnce("bash", BASH_GUIDANCE);
225
+ return guidanceOnce("bash", bashGuidance);
208
226
  }
209
227
 
210
228
  // ─── Read: nudge toward execute_file (once per session) ───
211
229
  if (canonical === "Read") {
212
- return guidanceOnce("read", READ_GUIDANCE);
230
+ return guidanceOnce("read", readGuidance);
213
231
  }
214
232
 
215
233
  // ─── Grep: nudge toward execute (once per session) ───
216
234
  if (canonical === "Grep") {
217
- return guidanceOnce("grep", GREP_GUIDANCE);
235
+ return guidanceOnce("grep", grepGuidance);
218
236
  }
219
237
 
220
238
  // ─── WebFetch: deny + redirect to sandbox ───
@@ -222,7 +240,7 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
222
240
  const url = toolInput.url ?? "";
223
241
  return {
224
242
  action: "deny",
225
- reason: `context-mode: WebFetch blocked. Use mcp__plugin_context-mode_context-mode__ctx_fetch_and_index(url: "${url}", source: "...") to fetch this URL in sandbox. Then use mcp__plugin_context-mode_context-mode__ctx_search(queries: [...]) to query results. Do NOT use curl, wget, mcp_web_fetch, or mcp_fetch_tool.`,
243
+ reason: `context-mode: WebFetch blocked. Use ${t("ctx_fetch_and_index")}(url: "${url}", source: "...") to fetch this URL in sandbox. Then use ${t("ctx_search")}(queries: [...]) to query results. Do NOT use curl, wget, mcp_web_fetch, or mcp_fetch_tool.`,
226
244
  };
227
245
  }
228
246
 
@@ -235,8 +253,8 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
235
253
 
236
254
  const updatedInput =
237
255
  subagentType === "Bash"
238
- ? { ...toolInput, [fieldName]: prompt + ROUTING_BLOCK, subagent_type: "general-purpose" }
239
- : { ...toolInput, [fieldName]: prompt + ROUTING_BLOCK };
256
+ ? { ...toolInput, [fieldName]: prompt + routingBlock, subagent_type: "general-purpose" }
257
+ : { ...toolInput, [fieldName]: prompt + routingBlock };
240
258
 
241
259
  return { action: "modify", updatedInput };
242
260
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Platform-aware MCP tool naming.
3
+ * Each platform has its own convention for how MCP tool names appear to the LLM.
4
+ *
5
+ * Evidence-based naming conventions (from official docs):
6
+ * | Platform | Pattern |
7
+ * |--------------------|------------------------------------------------------------|
8
+ * | Claude Code | mcp__plugin_context-mode_context-mode__<tool> |
9
+ * | Gemini CLI | mcp__context-mode__<tool> |
10
+ * | Antigravity | mcp__context-mode__<tool> |
11
+ * | OpenCode | context-mode_<tool> |
12
+ * | VS Code Copilot | context-mode_<tool> |
13
+ * | Kiro | @context-mode/<tool> |
14
+ * | Zed | mcp:context-mode:<tool> |
15
+ * | Cursor / Codex / OpenClaw / Pi | bare <tool> |
16
+ */
17
+
18
+ const TOOL_PREFIXES = {
19
+ "claude-code": (tool) => `mcp__plugin_context-mode_context-mode__${tool}`,
20
+ "gemini-cli": (tool) => `mcp__context-mode__${tool}`,
21
+ "antigravity": (tool) => `mcp__context-mode__${tool}`,
22
+ "opencode": (tool) => `context-mode_${tool}`,
23
+ "vscode-copilot": (tool) => `context-mode_${tool}`,
24
+ "kiro": (tool) => `@context-mode/${tool}`,
25
+ "zed": (tool) => `mcp:context-mode:${tool}`,
26
+ "cursor": (tool) => tool,
27
+ "codex": (tool) => tool,
28
+ "openclaw": (tool) => tool,
29
+ "pi": (tool) => tool,
30
+ };
31
+
32
+ /**
33
+ * Get the platform-specific MCP tool name for a bare tool name.
34
+ * Falls back to claude-code convention if platform is unknown.
35
+ */
36
+ export function getToolName(platform, bareTool) {
37
+ const fn = TOOL_PREFIXES[platform] || TOOL_PREFIXES["claude-code"];
38
+ return fn(bareTool);
39
+ }
40
+
41
+ /**
42
+ * Create a namer function bound to a specific platform.
43
+ * Returns (bareTool) => platformSpecificToolName.
44
+ */
45
+ export function createToolNamer(platform) {
46
+ return (bareTool) => getToolName(platform, bareTool);
47
+ }
48
+
49
+ /** List of all known platform IDs. */
50
+ export const KNOWN_PLATFORMS = Object.keys(TOOL_PREFIXES);
@@ -19,7 +19,7 @@ const tool = input.tool_name ?? "";
19
19
  const toolInput = input.tool_input ?? {};
20
20
  const projectDir = getInputProjectDir(input, CURSOR_OPTS);
21
21
 
22
- const decision = routePreToolUse(tool, toolInput, projectDir);
22
+ const decision = routePreToolUse(tool, toolInput, projectDir, "cursor");
23
23
  const response = formatDecision("cursor", decision);
24
24
  // Cursor treats empty stdout as an invalid hook response,
25
25
  // so even passthrough decisions must emit a syntactically valid no-op payload.
@@ -4,7 +4,10 @@ import "../suppress-stderr.mjs";
4
4
  * Cursor sessionStart hook for context-mode.
5
5
  */
6
6
 
7
- import { ROUTING_BLOCK } from "../routing-block.mjs";
7
+ import { createRoutingBlock } from "../routing-block.mjs";
8
+ import { createToolNamer } from "../core/tool-naming.mjs";
9
+
10
+ const ROUTING_BLOCK = createRoutingBlock(createToolNamer("cursor"));
8
11
  import {
9
12
  writeSessionEventsFile,
10
13
  buildSessionDirective,
@@ -19,7 +19,7 @@ const input = JSON.parse(raw);
19
19
  const tool = input.tool_name ?? "";
20
20
  const toolInput = input.tool_input ?? {};
21
21
 
22
- const decision = routePreToolUse(tool, toolInput, process.env.GEMINI_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR);
22
+ const decision = routePreToolUse(tool, toolInput, process.env.GEMINI_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR, "gemini-cli");
23
23
  const response = formatDecision("gemini-cli", decision);
24
24
  if (response !== null) {
25
25
  process.stdout.write(JSON.stringify(response) + "\n");
@@ -10,7 +10,10 @@ import "../suppress-stderr.mjs";
10
10
  * - "clear" → No action needed
11
11
  */
12
12
 
13
- import { ROUTING_BLOCK } from "../routing-block.mjs";
13
+ import { createRoutingBlock } from "../routing-block.mjs";
14
+ import { createToolNamer } from "../core/tool-naming.mjs";
15
+
16
+ const ROUTING_BLOCK = createRoutingBlock(createToolNamer("gemini-cli"));
14
17
  import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "../session-directive.mjs";
15
18
  import {
16
19
  readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
@@ -24,7 +24,7 @@ const tool = input.tool_name ?? "";
24
24
  const toolInput = input.tool_input ?? {};
25
25
  const projectDir = input.cwd ?? process.cwd();
26
26
 
27
- const decision = routePreToolUse(tool, toolInput, projectDir);
27
+ const decision = routePreToolUse(tool, toolInput, projectDir, "kiro");
28
28
 
29
29
  if (!decision) process.exit(0);
30
30
 
@@ -136,7 +136,7 @@ const tool = input.tool_name ?? "";
136
136
  const toolInput = input.tool_input ?? {};
137
137
 
138
138
  // ─── Route and format response ───
139
- const decision = routePreToolUse(tool, toolInput, process.env.CLAUDE_PROJECT_DIR);
139
+ const decision = routePreToolUse(tool, toolInput, process.env.CLAUDE_PROJECT_DIR, "claude-code");
140
140
  const response = formatDecision("claude-code", decision);
141
141
  if (response !== null) {
142
142
  process.stdout.write(JSON.stringify(response) + "\n");
@@ -1,28 +1,39 @@
1
1
  /**
2
2
  * Shared routing block for context-mode hooks.
3
3
  * Single source of truth — imported by pretooluse.mjs and sessionstart.mjs.
4
+ *
5
+ * Factory functions accept a tool namer `t(bareTool) => platformSpecificName`
6
+ * so each platform gets correct tool names in guidance messages.
7
+ *
8
+ * Backward compat: static exports (ROUTING_BLOCK, READ_GUIDANCE, etc.)
9
+ * default to claude-code naming convention.
4
10
  */
5
11
 
6
- export const ROUTING_BLOCK = `
12
+ import { createToolNamer } from "./core/tool-naming.mjs";
13
+
14
+ // ── Factory functions ─────────────────────────────────────
15
+
16
+ export function createRoutingBlock(t) {
17
+ return `
7
18
  <context_window_protection>
8
19
  <priority_instructions>
9
20
  Raw tool output floods your context window. You MUST use context-mode MCP tools to keep raw data in the sandbox.
10
21
  </priority_instructions>
11
22
 
12
23
  <tool_selection_hierarchy>
13
- 1. GATHER: mcp__plugin_context-mode_context-mode__ctx_batch_execute(commands, queries)
24
+ 1. GATHER: ${t("ctx_batch_execute")}(commands, queries)
14
25
  - Primary tool for research. Runs all commands, auto-indexes, and searches.
15
26
  - ONE call replaces many individual steps.
16
- 2. FOLLOW-UP: mcp__plugin_context-mode_context-mode__ctx_search(queries: ["q1", "q2", ...])
27
+ 2. FOLLOW-UP: ${t("ctx_search")}(queries: ["q1", "q2", ...])
17
28
  - Use for all follow-up questions. ONE call, many queries.
18
- 3. PROCESSING: mcp__plugin_context-mode_context-mode__ctx_execute(language, code) | mcp__plugin_context-mode_context-mode__ctx_execute_file(path, language, code)
29
+ 3. PROCESSING: ${t("ctx_execute")}(language, code) | ${t("ctx_execute_file")}(path, language, code)
19
30
  - Use for API calls, log analysis, and data processing.
20
31
  </tool_selection_hierarchy>
21
32
 
22
33
  <forbidden_actions>
23
34
  - DO NOT use Bash for commands producing >20 lines of output.
24
35
  - DO NOT use Read for analysis (use execute_file). Read IS correct for files you intend to Edit.
25
- - DO NOT use WebFetch (use mcp__plugin_context-mode_context-mode__ctx_fetch_and_index instead).
36
+ - DO NOT use WebFetch (use ${t("ctx_fetch_and_index")} instead).
26
37
  - Bash is ONLY for git/mkdir/rm/mv/navigation.
27
38
  </forbidden_actions>
28
39
 
@@ -52,9 +63,24 @@ export const ROUTING_BLOCK = `
52
63
  → Call the upgrade MCP tool, execute the returned shell command, display results as a checklist.
53
64
  </ctx_commands>
54
65
  </context_window_protection>`;
66
+ }
67
+
68
+ export function createReadGuidance(t) {
69
+ return '<context_guidance>\n <tip>\n If you are reading this file to Edit it, Read is the correct tool — Edit needs file content in context.\n If you are reading to analyze or explore, use ' + t("ctx_execute_file") + '(path, language, code) instead — only your printed summary will enter the context.\n </tip>\n</context_guidance>';
70
+ }
71
+
72
+ export function createGrepGuidance(t) {
73
+ return '<context_guidance>\n <tip>\n This operation may flood your context window. To stay efficient:\n - Use ' + t("ctx_execute") + '(language: "shell", code: "...") to run searches in the sandbox.\n - Only your final printed summary will enter the context.\n </tip>\n</context_guidance>';
74
+ }
55
75
 
56
- export const READ_GUIDANCE = '<context_guidance>\n <tip>\n If you are reading this file to Edit it, Read is the correct tool — Edit needs file content in context.\n If you are reading to analyze or explore, use mcp__plugin_context-mode_context-mode__ctx_execute_file(path, language, code) instead — only your printed summary will enter the context.\n </tip>\n</context_guidance>';
76
+ export function createBashGuidance(t) {
77
+ return '<context_guidance>\n <tip>\n This Bash command may produce large output. To stay efficient:\n - Use ' + t("ctx_batch_execute") + '(commands, queries) for multiple commands\n - Use ' + t("ctx_execute") + '(language: "shell", code: "...") to run in sandbox\n - Only your final printed summary will enter the context.\n - Bash is best for: git, mkdir, rm, mv, navigation, and short-output commands only.\n </tip>\n</context_guidance>';
78
+ }
57
79
 
58
- export const GREP_GUIDANCE = '<context_guidance>\n <tip>\n This operation may flood your context window. To stay efficient:\n - Use mcp__plugin_context-mode_context-mode__ctx_execute(language: "shell", code: "...") to run searches in the sandbox.\n - Only your final printed summary will enter the context.\n </tip>\n</context_guidance>';
80
+ // ── Backward compat: static exports defaulting to claude-code ──
59
81
 
60
- export const BASH_GUIDANCE = '<context_guidance>\n <tip>\n This Bash command may produce large output. To stay efficient:\n - Use mcp__plugin_context-mode_context-mode__ctx_batch_execute(commands, queries) for multiple commands\n - Use mcp__plugin_context-mode_context-mode__ctx_execute(language: "shell", code: "...") to run in sandbox\n - Only your final printed summary will enter the context.\n - Bash is best for: git, mkdir, rm, mv, navigation, and short-output commands only.\n </tip>\n</context_guidance>';
82
+ const _t = createToolNamer("claude-code");
83
+ export const ROUTING_BLOCK = createRoutingBlock(_t);
84
+ export const READ_GUIDANCE = createReadGuidance(_t);
85
+ export const GREP_GUIDANCE = createGrepGuidance(_t);
86
+ export const BASH_GUIDANCE = createBashGuidance(_t);
@@ -14,7 +14,10 @@ import "./suppress-stderr.mjs";
14
14
  * - "clear" → User cleared context. No resume.
15
15
  */
16
16
 
17
- import { ROUTING_BLOCK } from "./routing-block.mjs";
17
+ import { createRoutingBlock } from "./routing-block.mjs";
18
+ import { createToolNamer } from "./core/tool-naming.mjs";
19
+
20
+ const ROUTING_BLOCK = createRoutingBlock(createToolNamer("claude-code"));
18
21
  import { readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath } from "./session-helpers.mjs";
19
22
  import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "./session-directive.mjs";
20
23
  import { createSessionLoaders } from "./session-loaders.mjs";
@@ -19,7 +19,7 @@ const input = JSON.parse(raw);
19
19
  const tool = input.tool_name ?? "";
20
20
  const toolInput = input.tool_input ?? {};
21
21
 
22
- const decision = routePreToolUse(tool, toolInput, process.env.VSCODE_CWD || process.env.CLAUDE_PROJECT_DIR);
22
+ const decision = routePreToolUse(tool, toolInput, process.env.VSCODE_CWD || process.env.CLAUDE_PROJECT_DIR, "vscode-copilot");
23
23
  const response = formatDecision("vscode-copilot", decision);
24
24
  if (response !== null) {
25
25
  process.stdout.write(JSON.stringify(response) + "\n");
@@ -11,7 +11,10 @@ import "../suppress-stderr.mjs";
11
11
  */
12
12
 
13
13
  import { createSessionLoaders } from "../session-loaders.mjs";
14
- import { ROUTING_BLOCK } from "../routing-block.mjs";
14
+ import { createRoutingBlock } from "../routing-block.mjs";
15
+ import { createToolNamer } from "../core/tool-naming.mjs";
16
+
17
+ const ROUTING_BLOCK = createRoutingBlock(createToolNamer("vscode-copilot"));
15
18
  import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "../session-directive.mjs";
16
19
  import {
17
20
  readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
@@ -3,7 +3,7 @@
3
3
  "name": "Context Mode",
4
4
  "kind": "tool",
5
5
  "description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
6
- "version": "1.0.41",
6
+ "version": "1.0.42",
7
7
  "sandbox": {
8
8
  "mode": "permissive",
9
9
  "filesystem_access": "full",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.41",
3
+ "version": "1.0.42",
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",