context-mode 1.0.92 → 1.0.94

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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Claude Code plugins by Mert Koseoğlu",
9
- "version": "1.0.92"
9
+ "version": "1.0.94"
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.92",
16
+ "version": "1.0.94",
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.92",
3
+ "version": "1.0.94",
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",
@@ -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.92",
6
+ "version": "1.0.94",
7
7
  "sandbox": {
8
8
  "mode": "permissive",
9
9
  "filesystem_access": "full",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.92",
3
+ "version": "1.0.94",
4
4
  "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.",
5
5
  "author": {
6
6
  "name": "Mert Koseoğlu",
package/build/cli.d.ts CHANGED
@@ -13,3 +13,5 @@
13
13
  */
14
14
  /** Normalize Windows backslash paths to forward slashes for Bash (MSYS2) compatibility. */
15
15
  export declare function toUnixPath(p: string): string;
16
+ export declare function npmExecFile(args: string[], opts?: Record<string, unknown>): void;
17
+ export declare function npmExec(command: string, opts?: Record<string, unknown>): void;
package/build/cli.js CHANGED
@@ -120,6 +120,26 @@ else {
120
120
  export function toUnixPath(p) {
121
121
  return p.replace(/\\/g, "/");
122
122
  }
123
+ /**
124
+ * Windows-safe npm execution. On Windows:
125
+ * - "npm" → "npm.cmd" (Node won't resolve via PATHEXT in execFile)
126
+ * - shell: true required (Node v20+ CVE-2024-27980 mitigation)
127
+ * See: https://github.com/mksglu/context-mode/issues/344
128
+ */
129
+ const isWin = process.platform === "win32";
130
+ export function npmExecFile(args, opts = {}) {
131
+ execFileSync(isWin ? "npm.cmd" : "npm", args, {
132
+ ...opts,
133
+ ...(isWin ? { shell: true } : {}),
134
+ });
135
+ }
136
+ export function npmExec(command, opts = {}) {
137
+ const { execSync: es } = require("node:child_process");
138
+ es(isWin ? command.replace(/^npm /, "npm.cmd ") : command, {
139
+ ...opts,
140
+ ...(isWin ? { shell: true } : {}),
141
+ });
142
+ }
123
143
  function defaultPluginRoot() {
124
144
  const __filename = fileURLToPath(import.meta.url);
125
145
  const __dirname = dirname(__filename);
@@ -400,7 +420,7 @@ async function insight(port) {
400
420
  if (!existsSync(join(cacheDir, "node_modules"))) {
401
421
  console.log("Installing dependencies (first run)...");
402
422
  try {
403
- execSync("npm install --production=false", { cwd: cacheDir, stdio: "inherit", timeout: 300000 });
423
+ npmExec("npm install --production=false", { cwd: cacheDir, stdio: "inherit", timeout: 300000 });
404
424
  }
405
425
  catch {
406
426
  // Clean up partial install so next run retries fresh
@@ -512,12 +532,12 @@ async function upgrade() {
512
532
  }
513
533
  // Step 2: Install dependencies + build
514
534
  s.start("Installing dependencies & building");
515
- execFileSync("npm", ["install", "--no-audit", "--no-fund"], {
535
+ npmExecFile(["install", "--no-audit", "--no-fund"], {
516
536
  cwd: srcDir,
517
537
  stdio: "pipe",
518
538
  timeout: 120000,
519
539
  });
520
- execFileSync("npm", ["run", "build"], {
540
+ npmExecFile(["run", "build"], {
521
541
  cwd: srcDir,
522
542
  stdio: "pipe",
523
543
  timeout: 60000,
@@ -558,7 +578,7 @@ async function upgrade() {
558
578
  p.log.info(color.dim(" Registry synced to " + pluginRoot));
559
579
  // Install production deps
560
580
  s.start("Installing production dependencies");
561
- execFileSync("npm", ["install", "--production", "--no-audit", "--no-fund"], {
581
+ npmExecFile(["install", "--production", "--no-audit", "--no-fund"], {
562
582
  cwd: pluginRoot,
563
583
  stdio: "pipe",
564
584
  timeout: 60000,
@@ -568,7 +588,7 @@ async function upgrade() {
568
588
  // Rebuild native addons for current Node.js ABI (fixes #131)
569
589
  s.start("Rebuilding native addons");
570
590
  try {
571
- execFileSync("npm", ["rebuild", "better-sqlite3"], {
591
+ npmExecFile(["rebuild", "better-sqlite3"], {
572
592
  cwd: pluginRoot,
573
593
  stdio: "pipe",
574
594
  timeout: 60000,
@@ -587,7 +607,7 @@ async function upgrade() {
587
607
  // Update global npm
588
608
  s.start("Updating npm global package");
589
609
  try {
590
- execFileSync("npm", ["install", "-g", pluginRoot, "--no-audit", "--no-fund"], {
610
+ npmExecFile(["install", "-g", pluginRoot, "--no-audit", "--no-fund"], {
591
611
  stdio: "pipe",
592
612
  timeout: 30000,
593
613
  });
package/build/server.js CHANGED
@@ -1700,6 +1700,25 @@ server.registerTool("ctx_upgrade", {
1700
1700
  const pluginRoot = existsSync(resolve(__pkg_dir, "package.json")) ? __pkg_dir : dirname(__pkg_dir);
1701
1701
  const bundlePath = resolve(pluginRoot, "cli.bundle.mjs");
1702
1702
  const fallbackPath = resolve(pluginRoot, "build", "cli.js");
1703
+ // Clean up insight-cache on upgrade so next ctx_insight does fresh build
1704
+ try {
1705
+ const sessDir = getSessionDir();
1706
+ const insightCacheDir = join(dirname(sessDir), "insight-cache");
1707
+ if (existsSync(insightCacheDir)) {
1708
+ // Kill any running insight server first
1709
+ try {
1710
+ if (process.platform === "win32") {
1711
+ execSync('for /f "tokens=5" %a in (\'netstat -ano ^| findstr :4747\') do taskkill /F /PID %a', { stdio: "pipe" });
1712
+ }
1713
+ else {
1714
+ execSync("lsof -ti:4747 | xargs kill 2>/dev/null", { stdio: "pipe" });
1715
+ }
1716
+ }
1717
+ catch { /* no process to kill */ }
1718
+ rmSync(insightCacheDir, { recursive: true, force: true });
1719
+ }
1720
+ }
1721
+ catch { /* best effort — don't block upgrade */ }
1703
1722
  let cmd;
1704
1723
  if (existsSync(bundlePath)) {
1705
1724
  cmd = `node "${bundlePath}" upgrade`;
@@ -1726,13 +1745,13 @@ server.registerTool("ctx_upgrade", {
1726
1745
  `console.log("- [x] Starting inline upgrade (no CLI found)");`,
1727
1746
  `execFileSync("git",["clone","--depth","1","${repoUrl}",T],{stdio:"inherit"});`,
1728
1747
  `console.log("- [x] Cloned latest source");`,
1729
- `execFileSync("npm",["install"],{cwd:T,stdio:"inherit"});`,
1730
- `execFileSync("npm",["run","build"],{cwd:T,stdio:"inherit"});`,
1748
+ `execFileSync(process.platform==="win32"?"npm.cmd":"npm",["install"],{cwd:T,stdio:"inherit",shell:process.platform==="win32"});`,
1749
+ `execFileSync(process.platform==="win32"?"npm.cmd":"npm",["run","build"],{cwd:T,stdio:"inherit",shell:process.platform==="win32"});`,
1731
1750
  `console.log("- [x] Built from source");`,
1732
1751
  ...copyDirs.map((d) => `if(existsSync(join(T,${JSON.stringify(d)})))cpSync(join(T,${JSON.stringify(d)}),join(P,${JSON.stringify(d)}),{recursive:true,force:true});`),
1733
1752
  ...copyFiles.map((f) => `if(existsSync(join(T,${JSON.stringify(f)})))cpSync(join(T,${JSON.stringify(f)}),join(P,${JSON.stringify(f)}),{force:true});`),
1734
1753
  `console.log("- [x] Copied build artifacts");`,
1735
- `execFileSync("npm",["install","--production"],{cwd:P,stdio:"inherit"});`,
1754
+ `execFileSync(process.platform==="win32"?"npm.cmd":"npm",["install","--production"],{cwd:P,stdio:"inherit",shell:process.platform==="win32"});`,
1736
1755
  `console.log("- [x] Installed production dependencies");`,
1737
1756
  `console.log("## context-mode upgrade complete");`,
1738
1757
  `}catch(e){`,
@@ -1904,6 +1923,7 @@ server.registerTool("ctx_insight", {
1904
1923
  }
1905
1924
  try {
1906
1925
  const steps = [];
1926
+ let sourceUpdated = false;
1907
1927
  // Ensure cache dir
1908
1928
  mkdirSync(cacheDir, { recursive: true });
1909
1929
  // Copy source files if needed (check by comparing server.mjs mtime)
@@ -1914,13 +1934,14 @@ server.registerTool("ctx_insight", {
1914
1934
  steps.push("Copying source files...");
1915
1935
  cpSync(insightSource, cacheDir, { recursive: true, force: true });
1916
1936
  steps.push("Source files copied.");
1937
+ sourceUpdated = true;
1917
1938
  }
1918
- // Install deps if needed
1939
+ // Install deps if needed (also reinstall when source updated and package.json may have changed)
1919
1940
  const hasNodeModules = existsSync(join(cacheDir, "node_modules"));
1920
- if (!hasNodeModules) {
1941
+ if (!hasNodeModules || sourceUpdated) {
1921
1942
  steps.push("Installing dependencies (first run, ~30s)...");
1922
1943
  try {
1923
- execSync("npm install --production=false", {
1944
+ execSync(process.platform === "win32" ? "npm.cmd install --production=false" : "npm install --production=false", {
1924
1945
  cwd: cacheDir,
1925
1946
  stdio: "pipe",
1926
1947
  timeout: 300000,
@@ -1949,7 +1970,8 @@ server.registerTool("ctx_insight", {
1949
1970
  timeout: 60000,
1950
1971
  });
1951
1972
  steps.push("Build complete.");
1952
- // Pre-check: is port already in use? (prevents orphan zombie processes)
1973
+ // Pre-check: is port already in use?
1974
+ let portOccupied = false;
1953
1975
  try {
1954
1976
  const { request } = await import("node:http");
1955
1977
  await new Promise((resolve, reject) => {
@@ -1961,9 +1983,29 @@ server.registerTool("ctx_insight", {
1961
1983
  req.on("timeout", () => { req.destroy(); reject(); });
1962
1984
  req.end();
1963
1985
  });
1964
- // If we get here, port is already responding
1986
+ portOccupied = true;
1987
+ }
1988
+ catch {
1989
+ // Port is free, proceed with spawn
1990
+ }
1991
+ if (portOccupied && sourceUpdated) {
1992
+ // Source was updated but stale server is running on port — kill it so fresh code runs
1993
+ steps.push("Killing stale dashboard server (source updated)...");
1994
+ try {
1995
+ if (process.platform === "win32") {
1996
+ execSync(`for /f "tokens=5" %a in ('netstat -ano ^| findstr :${port}') do taskkill /F /PID %a`, { stdio: "pipe" });
1997
+ }
1998
+ else {
1999
+ execSync(`lsof -ti:${port} | xargs kill 2>/dev/null`, { stdio: "pipe" });
2000
+ }
2001
+ await new Promise(r => setTimeout(r, 500)); // Wait for port to free
2002
+ }
2003
+ catch { /* no process to kill — proceed anyway */ }
2004
+ steps.push("Stale server killed.");
2005
+ }
2006
+ else if (portOccupied) {
2007
+ // Source unchanged, server is running fine — just open browser
1965
2008
  steps.push("Dashboard already running.");
1966
- // Open browser anyway
1967
2009
  const url = `http://localhost:${port}`;
1968
2010
  const platform = process.platform;
1969
2011
  try {
@@ -1979,9 +2021,6 @@ server.registerTool("ctx_insight", {
1979
2021
  content: [{ type: "text", text: `Dashboard already running at http://localhost:${port}` }],
1980
2022
  });
1981
2023
  }
1982
- catch {
1983
- // Port is free, proceed with spawn
1984
- }
1985
2024
  // Kill any previous insight child this MCP spawned (e.g. re-invocation).
1986
2025
  if (_insightChild && _insightChild.pid && !_insightChild.killed) {
1987
2026
  try {