context-mode 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
10
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.sh"
10
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
11
11
  }
12
12
  ]
13
13
  },
@@ -16,7 +16,7 @@
16
16
  "hooks": [
17
17
  {
18
18
  "type": "command",
19
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.sh"
19
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
20
20
  }
21
21
  ]
22
22
  },
@@ -25,7 +25,7 @@
25
25
  "hooks": [
26
26
  {
27
27
  "type": "command",
28
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.sh"
28
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
29
29
  }
30
30
  ]
31
31
  },
@@ -34,7 +34,7 @@
34
34
  "hooks": [
35
35
  {
36
36
  "type": "command",
37
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.sh"
37
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
38
38
  }
39
39
  ]
40
40
  },
@@ -43,7 +43,7 @@
43
43
  "hooks": [
44
44
  {
45
45
  "type": "command",
46
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.sh"
46
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
47
47
  }
48
48
  ]
49
49
  }
@@ -13,7 +13,7 @@
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": "0.8.0",
16
+ "version": "0.9.0",
17
17
  "author": {
18
18
  "name": "Mert Koseoğlu"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "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.",
5
5
  "author": {
6
6
  "name": "Mert Koseoğlu",
@@ -21,8 +21,8 @@
21
21
  ],
22
22
  "mcpServers": {
23
23
  "context-mode": {
24
- "command": "sh",
25
- "args": ["${CLAUDE_PLUGIN_ROOT}/start.sh"]
24
+ "command": "node",
25
+ "args": ["${CLAUDE_PLUGIN_ROOT}/start.mjs"]
26
26
  }
27
27
  },
28
28
  "skills": "./skills/"
package/.mcp.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "mcpServers": {
3
3
  "context-mode": {
4
- "command": "sh",
5
- "args": ["${CLAUDE_PLUGIN_ROOT}/start.sh"]
4
+ "command": "node",
5
+ "args": ["${CLAUDE_PLUGIN_ROOT}/start.mjs"]
6
6
  }
7
7
  }
8
8
  }
package/README.md CHANGED
@@ -202,9 +202,13 @@ npm run test:all # full suite
202
202
  ## Contributors
203
203
 
204
204
  <a href="https://github.com/mksglu/claude-context-mode/graphs/contributors">
205
- <img src="https://contrib.rocks/image?repo=mksglu/claude-context-mode" />
205
+ <img src="https://contrib.rocks/image?repo=mksglu/claude-context-mode&columns=8&anon=1" />
206
206
  </a>
207
207
 
208
+ ### Special Thanks
209
+
210
+ <a href="https://github.com/mksglu/claude-context-mode/issues/15"><img src="https://github.com/vaban-ru.png" width="32" /></a>
211
+
208
212
  ## License
209
213
 
210
214
  MIT
package/build/cli.js CHANGED
@@ -12,7 +12,7 @@
12
12
  import * as p from "@clack/prompts";
13
13
  import color from "picocolors";
14
14
  import { execSync } from "node:child_process";
15
- import { readFileSync, writeFileSync, copyFileSync, chmodSync, accessSync, readdirSync, constants } from "node:fs";
15
+ import { readFileSync, writeFileSync, copyFileSync, cpSync, chmodSync, accessSync, readdirSync, rmSync, constants } from "node:fs";
16
16
  import { resolve, dirname } from "node:path";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import { homedir } from "node:os";
@@ -22,7 +22,7 @@ if (args[0] === "setup") {
22
22
  setup();
23
23
  }
24
24
  else if (args[0] === "doctor") {
25
- doctor();
25
+ doctor().then((code) => process.exit(code));
26
26
  }
27
27
  else if (args[0] === "upgrade") {
28
28
  upgrade();
@@ -52,7 +52,7 @@ function readSettings() {
52
52
  }
53
53
  }
54
54
  function getHookScriptPath() {
55
- return resolve(getPluginRoot(), "hooks", "pretooluse.sh");
55
+ return resolve(getPluginRoot(), "hooks", "pretooluse.mjs");
56
56
  }
57
57
  function getLocalVersion() {
58
58
  try {
@@ -123,8 +123,10 @@ function semverGt(a, b) {
123
123
  * Doctor
124
124
  * ------------------------------------------------------- */
125
125
  async function doctor() {
126
- console.clear();
126
+ if (process.stdout.isTTY)
127
+ console.clear();
127
128
  p.intro(color.bgMagenta(color.white(" context-mode doctor ")));
129
+ let criticalFails = 0;
128
130
  const s = p.spinner();
129
131
  s.start("Running diagnostics");
130
132
  const runtimes = detectRuntimes();
@@ -142,10 +144,18 @@ async function doctor() {
142
144
  " — Using Node.js (install Bun for 3-5x speed boost)");
143
145
  }
144
146
  // Language coverage
145
- const total = 10;
147
+ const total = 11;
146
148
  const pct = ((available.length / total) * 100).toFixed(0);
147
- p.log.info(`Language coverage: ${available.length}/${total} (${pct}%)` +
148
- color.dim(` — ${available.join(", ")}`));
149
+ if (available.length < 2) {
150
+ criticalFails++;
151
+ p.log.error(color.red(`Language coverage: ${available.length}/${total} (${pct}%)`) +
152
+ " — too few runtimes detected" +
153
+ color.dim(` — ${available.join(", ") || "none"}`));
154
+ }
155
+ else {
156
+ p.log.info(`Language coverage: ${available.length}/${total} (${pct}%)` +
157
+ color.dim(` — ${available.join(", ")}`));
158
+ }
149
159
  // Server test
150
160
  p.log.step("Testing server initialization...");
151
161
  try {
@@ -160,10 +170,12 @@ async function doctor() {
160
170
  p.log.success(color.green("Server test: PASS"));
161
171
  }
162
172
  else {
173
+ criticalFails++;
163
174
  p.log.error(color.red("Server test: FAIL") + ` — exit ${result.exitCode}`);
164
175
  }
165
176
  }
166
177
  catch (err) {
178
+ criticalFails++;
167
179
  const message = err instanceof Error ? err.message : String(err);
168
180
  p.log.error(color.red("Server test: FAIL") + ` — ${message}`);
169
181
  }
@@ -175,13 +187,13 @@ async function doctor() {
175
187
  const hooks = settings.hooks;
176
188
  const preToolUse = hooks?.PreToolUse;
177
189
  if (preToolUse && preToolUse.length > 0) {
178
- const hasCorrectHook = preToolUse.some((entry) => entry.hooks?.some((h) => h.command?.includes("pretooluse.sh")));
190
+ const hasCorrectHook = preToolUse.some((entry) => entry.hooks?.some((h) => h.command?.includes("pretooluse.mjs")));
179
191
  if (hasCorrectHook) {
180
192
  p.log.success(color.green("Hooks installed: PASS") + " — PreToolUse hook configured");
181
193
  }
182
194
  else {
183
195
  p.log.error(color.red("Hooks installed: FAIL") +
184
- " — PreToolUse exists but does not point to pretooluse.sh" +
196
+ " — PreToolUse exists but does not point to pretooluse.mjs" +
185
197
  color.dim("\n Run: npx context-mode upgrade"));
186
198
  }
187
199
  }
@@ -244,10 +256,12 @@ async function doctor() {
244
256
  p.log.success(color.green("FTS5 / better-sqlite3: PASS") + " — native module works");
245
257
  }
246
258
  else {
259
+ criticalFails++;
247
260
  p.log.error(color.red("FTS5 / better-sqlite3: FAIL") + " — query returned unexpected result");
248
261
  }
249
262
  }
250
263
  catch (err) {
264
+ criticalFails++;
251
265
  const message = err instanceof Error ? err.message : String(err);
252
266
  p.log.error(color.red("FTS5 / better-sqlite3: FAIL") +
253
267
  ` — ${message}` +
@@ -291,15 +305,21 @@ async function doctor() {
291
305
  color.dim(" — could not verify against npm registry"));
292
306
  }
293
307
  // Summary
308
+ if (criticalFails > 0) {
309
+ p.outro(color.red(`Diagnostics failed — ${criticalFails} critical issue(s) found`));
310
+ return 1;
311
+ }
294
312
  p.outro(available.length >= 4
295
313
  ? color.green("Diagnostics complete!")
296
314
  : color.yellow("Some checks need attention — see above for details"));
315
+ return 0;
297
316
  }
298
317
  /* -------------------------------------------------------
299
318
  * Upgrade
300
319
  * ------------------------------------------------------- */
301
320
  async function upgrade() {
302
- console.clear();
321
+ if (process.stdout.isTTY)
322
+ console.clear();
303
323
  p.intro(color.bgCyan(color.black(" context-mode upgrade ")));
304
324
  let pluginRoot = getPluginRoot();
305
325
  const settingsPath = getSettingsPath();
@@ -325,12 +345,12 @@ async function upgrade() {
325
345
  }
326
346
  // Step 2: Install dependencies + build
327
347
  s.start("Installing dependencies & building");
328
- execSync("npm install --no-audit --no-fund 2>/dev/null", {
348
+ execSync("npm install --no-audit --no-fund", {
329
349
  cwd: srcDir,
330
350
  stdio: "pipe",
331
351
  timeout: 60000,
332
352
  });
333
- execSync("npm run build 2>/dev/null", {
353
+ execSync("npm run build", {
334
354
  cwd: srcDir,
335
355
  stdio: "pipe",
336
356
  timeout: 30000,
@@ -340,19 +360,19 @@ async function upgrade() {
340
360
  s.start("Installing files");
341
361
  const items = [
342
362
  "build", "hooks", "skills", ".claude-plugin",
343
- "start.sh", "server.bundle.mjs", "package.json", ".mcp.json",
363
+ "start.mjs", "server.bundle.mjs", "package.json", ".mcp.json",
344
364
  ];
345
365
  for (const item of items) {
346
366
  try {
347
- execSync(`rm -rf "${pluginRoot}/${item}"`, { stdio: "pipe" });
348
- execSync(`cp -r "${srcDir}/${item}" "${pluginRoot}/"`, { stdio: "pipe" });
367
+ rmSync(resolve(pluginRoot, item), { recursive: true, force: true });
368
+ cpSync(resolve(srcDir, item), resolve(pluginRoot, item), { recursive: true });
349
369
  }
350
370
  catch { /* some files may not exist */ }
351
371
  }
352
372
  s.stop(color.green("Files installed"));
353
373
  // Install production deps in plugin root
354
374
  s.start("Installing production dependencies");
355
- execSync("npm install --production --no-audit --no-fund 2>/dev/null", {
375
+ execSync("npm install --production --no-audit --no-fund", {
356
376
  cwd: pluginRoot,
357
377
  stdio: "pipe",
358
378
  timeout: 60000,
@@ -365,8 +385,9 @@ async function upgrade() {
365
385
  const newCacheDir = cacheMatch[1] + newVersion;
366
386
  s.start(`Migrating cache: ${oldDirVersion} → ${newVersion}`);
367
387
  try {
368
- execSync(`rm -rf "${newCacheDir}"`, { stdio: "pipe" });
369
- execSync(`mv "${pluginRoot}" "${newCacheDir}"`, { stdio: "pipe" });
388
+ rmSync(newCacheDir, { recursive: true, force: true });
389
+ cpSync(pluginRoot, newCacheDir, { recursive: true });
390
+ rmSync(pluginRoot, { recursive: true, force: true });
370
391
  pluginRoot = newCacheDir;
371
392
  s.stop(color.green(`Cache directory: ${newVersion}`));
372
393
  changes.push(`Migrated cache: ${oldDirVersion} → ${newVersion}`);
@@ -390,7 +411,7 @@ async function upgrade() {
390
411
  p.log.info(color.dim(" Could not update global npm — may need sudo or standalone install"));
391
412
  }
392
413
  // Cleanup
393
- execSync(`rm -rf "${tmpDir}"`, { stdio: "pipe" });
414
+ rmSync(tmpDir, { recursive: true, force: true });
394
415
  changes.push(newVersion !== localVersion
395
416
  ? `Updated v${localVersion} → v${newVersion}`
396
417
  : `Reinstalled v${localVersion} from GitHub`);
@@ -404,7 +425,7 @@ async function upgrade() {
404
425
  p.log.info(color.dim("Continuing with hooks/settings fix..."));
405
426
  // Cleanup on failure
406
427
  try {
407
- execSync(`rm -rf "${tmpDir}"`, { stdio: "pipe" });
428
+ rmSync(tmpDir, { recursive: true, force: true });
408
429
  }
409
430
  catch { /* ignore */ }
410
431
  }
@@ -423,14 +444,14 @@ async function upgrade() {
423
444
  }
424
445
  // Step 4: Fix hooks
425
446
  p.log.step("Configuring PreToolUse hooks...");
426
- const hookScriptPath = resolve(pluginRoot, "hooks", "pretooluse.sh");
447
+ const hookScriptPath = resolve(pluginRoot, "hooks", "pretooluse.mjs");
427
448
  const settings = readSettings() ?? {};
428
449
  const desiredHookEntry = {
429
450
  matcher: "Bash|Read|Grep|Glob|WebFetch|WebSearch|Task",
430
451
  hooks: [
431
452
  {
432
453
  type: "command",
433
- command: "bash " + hookScriptPath,
454
+ command: "node " + hookScriptPath,
434
455
  },
435
456
  ],
436
457
  };
@@ -439,7 +460,7 @@ async function upgrade() {
439
460
  if (existingPreToolUse && Array.isArray(existingPreToolUse)) {
440
461
  const existingIdx = existingPreToolUse.findIndex((entry) => {
441
462
  const entryHooks = entry.hooks;
442
- return entryHooks?.some((h) => h.command?.includes("pretooluse.sh"));
463
+ return entryHooks?.some((h) => h.command?.includes("pretooluse.mjs"));
443
464
  });
444
465
  if (existingIdx >= 0) {
445
466
  existingPreToolUse[existingIdx] = desiredHookEntry;
@@ -476,7 +497,7 @@ async function upgrade() {
476
497
  accessSync(hookScriptPath, constants.R_OK);
477
498
  chmodSync(hookScriptPath, 0o755);
478
499
  p.log.success(color.green("Permissions set") + color.dim(" — chmod +x " + hookScriptPath));
479
- changes.push("Set pretooluse.sh as executable");
500
+ changes.push("Set pretooluse.mjs as executable");
480
501
  }
481
502
  catch {
482
503
  p.log.error(color.red("Hook script not found") +
@@ -492,13 +513,17 @@ async function upgrade() {
492
513
  // Step 7: Run doctor
493
514
  p.log.step("Running doctor to verify...");
494
515
  console.log();
495
- await doctor();
516
+ const doctorCode = await doctor();
517
+ if (doctorCode !== 0) {
518
+ process.exit(doctorCode);
519
+ }
496
520
  }
497
521
  /* -------------------------------------------------------
498
522
  * Setup
499
523
  * ------------------------------------------------------- */
500
524
  async function setup() {
501
- console.clear();
525
+ if (process.stdout.isTTY)
526
+ console.clear();
502
527
  p.intro(color.bgCyan(color.black(" context-mode setup ")));
503
528
  const s = p.spinner();
504
529
  // Step 1: Detect runtimes
package/build/executor.js CHANGED
@@ -4,6 +4,19 @@ import { mkdtempSync, writeFileSync, rmSync, existsSync } from "node:fs";
4
4
  import { join, resolve } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { detectRuntimes, buildCommand, } from "./runtime.js";
7
+ const isWin = process.platform === "win32";
8
+ /** Kill process tree — on Windows, proc.kill() only kills the shell, not children. */
9
+ function killTree(proc) {
10
+ if (isWin && proc.pid) {
11
+ try {
12
+ execSync(`taskkill /F /T /PID ${proc.pid}`, { stdio: "pipe" });
13
+ }
14
+ catch { /* already dead */ }
15
+ }
16
+ else {
17
+ proc.kill("SIGKILL");
18
+ }
19
+ }
7
20
  export class PolyglotExecutor {
8
21
  #maxOutputBytes;
9
22
  #hardCapBytes;
@@ -77,13 +90,15 @@ export class PolyglotExecutor {
77
90
  return fp;
78
91
  }
79
92
  async #compileAndRun(srcPath, cwd, timeout) {
80
- const binPath = srcPath.replace(/\.rs$/, "");
93
+ const binSuffix = isWin ? ".exe" : "";
94
+ const binPath = srcPath.replace(/\.rs$/, "") + binSuffix;
81
95
  // Compile
82
96
  try {
83
- execSync(`rustc ${srcPath} -o ${binPath} 2>&1`, {
97
+ execSync(`rustc ${srcPath} -o ${binPath}`, {
84
98
  cwd,
85
99
  timeout: Math.min(timeout, 30_000),
86
100
  encoding: "utf-8",
101
+ stdio: ["pipe", "pipe", "pipe"],
87
102
  });
88
103
  }
89
104
  catch (err) {
@@ -137,15 +152,19 @@ export class PolyglotExecutor {
137
152
  }
138
153
  async #spawn(cmd, cwd, timeout) {
139
154
  return new Promise((res) => {
155
+ // Only .cmd/.bat shims need shell on Windows; real executables don't.
156
+ // Using shell: true globally causes process-tree kill issues with MSYS2/Git Bash.
157
+ const needsShell = isWin && ["tsx", "ts-node", "elixir"].includes(cmd[0]);
140
158
  const proc = spawn(cmd[0], cmd.slice(1), {
141
159
  cwd,
142
160
  stdio: ["ignore", "pipe", "pipe"],
143
161
  env: this.#buildSafeEnv(cwd),
162
+ shell: needsShell,
144
163
  });
145
164
  let timedOut = false;
146
165
  const timer = setTimeout(() => {
147
166
  timedOut = true;
148
- proc.kill("SIGKILL");
167
+ killTree(proc);
149
168
  }, timeout);
150
169
  // Stream-level byte cap: kill the process once combined stdout+stderr
151
170
  // exceeds hardCapBytes. Without this, a command like `yes` or
@@ -162,7 +181,7 @@ export class PolyglotExecutor {
162
181
  }
163
182
  else if (!capExceeded) {
164
183
  capExceeded = true;
165
- proc.kill("SIGKILL");
184
+ killTree(proc);
166
185
  }
167
186
  });
168
187
  proc.stderr.on("data", (chunk) => {
@@ -172,7 +191,7 @@ export class PolyglotExecutor {
172
191
  }
173
192
  else if (!capExceeded) {
174
193
  capExceeded = true;
175
- proc.kill("SIGKILL");
194
+ killTree(proc);
176
195
  }
177
196
  });
178
197
  proc.on("close", (exitCode) => {
@@ -239,7 +258,7 @@ export class PolyglotExecutor {
239
258
  "XDG_DATA_HOME",
240
259
  ];
241
260
  const env = {
242
- PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin",
261
+ PATH: process.env.PATH ?? (isWin ? "" : "/usr/local/bin:/usr/bin:/bin"),
243
262
  HOME: realHome,
244
263
  TMPDIR: tmpDir,
245
264
  LANG: "en_US.UTF-8",
@@ -247,6 +266,18 @@ export class PolyglotExecutor {
247
266
  PYTHONUNBUFFERED: "1",
248
267
  NO_COLOR: "1",
249
268
  };
269
+ // Windows-critical env vars
270
+ if (isWin) {
271
+ const winVars = [
272
+ "SYSTEMROOT", "SystemRoot", "COMSPEC", "PATHEXT",
273
+ "USERPROFILE", "APPDATA", "LOCALAPPDATA", "TEMP", "TMP",
274
+ "GOROOT", "GOPATH",
275
+ ];
276
+ for (const key of winVars) {
277
+ if (process.env[key])
278
+ env[key] = process.env[key];
279
+ }
280
+ }
250
281
  for (const key of passthrough) {
251
282
  if (process.env[key]) {
252
283
  env[key] = process.env[key];
package/build/runtime.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { execSync } from "node:child_process";
2
+ const isWindows = process.platform === "win32";
2
3
  function commandExists(cmd) {
3
4
  try {
4
- execSync(`command -v ${cmd} 2>/dev/null`, { stdio: "pipe" });
5
+ const check = isWindows ? `where ${cmd}` : `command -v ${cmd}`;
6
+ execSync(check, { stdio: "pipe" });
5
7
  return true;
6
8
  }
7
9
  catch {
@@ -10,8 +12,9 @@ function commandExists(cmd) {
10
12
  }
11
13
  function getVersion(cmd) {
12
14
  try {
13
- return execSync(`${cmd} --version 2>/dev/null`, {
15
+ return execSync(`${cmd} --version`, {
14
16
  encoding: "utf-8",
17
+ stdio: ["pipe", "pipe", "pipe"],
15
18
  timeout: 5000,
16
19
  })
17
20
  .trim()
package/build/server.js CHANGED
@@ -5,7 +5,7 @@ import { z } from "zod";
5
5
  import { PolyglotExecutor } from "./executor.js";
6
6
  import { ContentStore, cleanupStaleDBs } from "./store.js";
7
7
  import { detectRuntimes, getRuntimeSummary, getAvailableLanguages, hasBunRuntime, } from "./runtime.js";
8
- const VERSION = "0.8.0";
8
+ const VERSION = "0.8.1";
9
9
  const runtimes = detectRuntimes();
10
10
  const available = getAvailableLanguages(runtimes);
11
11
  const server = new McpServer({
package/hooks/hooks.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
10
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.sh"
10
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
11
11
  }
12
12
  ]
13
13
  },
@@ -16,7 +16,7 @@
16
16
  "hooks": [
17
17
  {
18
18
  "type": "command",
19
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.sh"
19
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
20
20
  }
21
21
  ]
22
22
  },
@@ -25,7 +25,7 @@
25
25
  "hooks": [
26
26
  {
27
27
  "type": "command",
28
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.sh"
28
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
29
29
  }
30
30
  ]
31
31
  },
@@ -34,7 +34,7 @@
34
34
  "hooks": [
35
35
  {
36
36
  "type": "command",
37
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.sh"
37
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
38
38
  }
39
39
  ]
40
40
  },
@@ -43,7 +43,7 @@
43
43
  "hooks": [
44
44
  {
45
45
  "type": "command",
46
- "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.sh"
46
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.mjs"
47
47
  }
48
48
  ]
49
49
  }