context-mode 1.0.116 → 1.0.118

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.116"
9
+ "version": "1.0.118"
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.116",
16
+ "version": "1.0.118",
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.116",
3
+ "version": "1.0.118",
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.116",
6
+ "version": "1.0.118",
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.116",
3
+ "version": "1.0.118",
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",
@@ -212,7 +212,7 @@ async function main() {
212
212
  const {
213
213
  getRealBytesStats,
214
214
  getMultiAdapterLifetimeStats,
215
- OPUS_INPUT_PRICE_PER_TOKEN,
215
+ kb,
216
216
  } = analytics;
217
217
 
218
218
  // Sessions dir doesn't exist yet — first ever launch
@@ -251,14 +251,31 @@ async function main() {
251
251
  multi = null;
252
252
  }
253
253
 
254
- const PRICE = OPUS_INPUT_PRICE_PER_TOKEN ?? (15 / 1_000_000);
255
- const lifetimeTokens = lifetime?.totalSavedTokens ?? 0;
256
- const sessionTokens = conversation?.totalSavedTokens ?? 0;
257
- const lifetimeUsd = lifetimeTokens * PRICE;
258
- const sessionUsd = sessionTokens * PRICE;
254
+ // v1.0.118: drop the $ math ctx_stats's narrative renderer is the source
255
+ // of truth and uses byte-based metrics. Statusline mirrors the same
256
+ // formulas so the two displays never diverge again.
257
+ //
258
+ // Lifetime bytes multi-adapter aggregate when present, else local-DB
259
+ // real bytes. Mirrors src/session/analytics.ts:1684 narrative renderer.
260
+ const lifetimeBytes = (multi?.totalBytes && multi.totalBytes > 0)
261
+ ? multi.totalBytes
262
+ : (lifetime?.totalSavedTokens ?? 0) * 4;
259
263
 
260
- // Reduction % — bytes avoided + snapshot bytes vs returned bytes.
261
- // Mirrors persistStats() math in src/server.ts:565-568.
264
+ // This-chat bytesreal bytes accounting (data + bytes-avoided + snapshot).
265
+ const sessionBytes = conversation
266
+ ? ((conversation.eventDataBytes ?? 0)
267
+ + (conversation.bytesAvoided ?? 0)
268
+ + (conversation.snapshotBytes ?? 0))
269
+ : 0;
270
+
271
+ // Per-day average — same lifetime-day computation ctx_stats opener uses.
272
+ const sinceMs = lifetime?.firstEventMs ?? multi?.perAdapter?.[0]?.firstMs ?? 0;
273
+ const lifetimeDays = sinceMs > 0
274
+ ? Math.max(1, Math.round((Date.now() - sinceMs) / 86_400_000))
275
+ : 0;
276
+ const perDayBytes = lifetimeDays > 0 ? lifetimeBytes / lifetimeDays : 0;
277
+
278
+ // Reduction % — same as before (bytes-avoided + snapshot vs returned).
262
279
  const totalReturned = lifetime?.bytesReturned ?? 0;
263
280
  const totalKept =
264
281
  (lifetime?.bytesAvoided ?? 0)
@@ -271,36 +288,26 @@ async function main() {
271
288
 
272
289
  const dot = statusDot(pct);
273
290
 
274
- // Multi-adapter aggregation. Real adapters = those passing the isReal
275
- // filter (>=100 events, >=5 distinct projects, recent activity, avg
276
- // bytes >= 50). When 2+ real adapters exist, surface a cross-tool $.
277
- // multi.totalBytes is dataBytes + rescueBytes, NOT bytes-avoided — so
278
- // it's a different (and typically smaller) lens than getRealBytesStats.
279
- // Render the multi $ alongside lifetime $ rather than instead of it.
291
+ // Cross-tool count used in the headline when 2+ real adapters detected.
280
292
  const realAdapters = (multi?.perAdapter ?? []).filter((a) => a?.isReal);
281
- const multiTotalTokens = (multi?.totalBytes ?? 0) / 4;
282
- const multiUsd = multiTotalTokens * PRICE;
283
- const showMultiAdapter = realAdapters.length >= 2 && multiUsd > 0;
284
-
285
- // BRAND-NEW: no local SessionDB data at all → headline.
286
- // Multi-adapter alone (without local data) means another tool has
287
- // history but THIS Claude session is fresh — still show headline,
288
- // not someone else's lifetime $, to avoid surprising users with a
289
- // number they can't trace to their current adapter.
290
- if (lifetimeTokens === 0 && sessionTokens === 0) {
293
+ const showMultiAdapter = realAdapters.length >= 2;
294
+
295
+ // BRAND-NEW: no data at all marketing headline.
296
+ if (lifetimeBytes === 0 && sessionBytes === 0) {
291
297
  process.stdout.write(
292
298
  `${brand("context-mode")} ${green("●")} ${dim("saves ~98% of context window")}`,
293
299
  );
294
300
  return;
295
301
  }
296
302
 
297
- // FRESH session, no session $ yet — lead with persistence value.
298
- if (sessionUsd === 0 && lifetimeUsd > 0) {
299
- const blocks = [
300
- `${bold(fmtUsd(lifetimeUsd))} ${dim("saved across sessions")}`,
301
- ];
303
+ // FRESH session, no this-chat data yet — lead with lifetime number.
304
+ if (sessionBytes === 0 && lifetimeBytes > 0) {
305
+ const blocks = [`${bold(kb(lifetimeBytes))} ${dim("kept out")}`];
306
+ if (perDayBytes > 0) {
307
+ blocks.push(`${bold(kb(perDayBytes) + "/day")}`);
308
+ }
302
309
  if (showMultiAdapter) {
303
- blocks.push(`${bold(fmtUsd(multiUsd))} ${dim(`across ${realAdapters.length} tools`)}`);
310
+ blocks.push(`${dim(`across ${realAdapters.length} tools`)}`);
304
311
  }
305
312
  blocks.push(dim("preserved across compact, restart & upgrade"));
306
313
  process.stdout.write(
@@ -309,18 +316,18 @@ async function main() {
309
316
  return;
310
317
  }
311
318
 
312
- // ACTIVE: session $ · lifetime $ · [multi $] · % efficient
319
+ // ACTIVE: this-chat · lifetime · [N tools] · % efficient
313
320
  const valueBlocks = [
314
- `${bold(fmtUsd(sessionUsd))} ${dim("saved this session")}`,
321
+ `${bold(kb(sessionBytes))} ${dim("this chat")}`,
315
322
  ];
316
- if (lifetimeUsd > 0) {
317
- valueBlocks.push(`${bold(fmtUsd(lifetimeUsd))} ${dim("saved across sessions")}`);
323
+ if (lifetimeBytes > 0) {
324
+ valueBlocks.push(`${bold(kb(lifetimeBytes))} ${dim("lifetime")}`);
318
325
  }
319
326
  if (showMultiAdapter) {
320
- valueBlocks.push(`${bold(fmtUsd(multiUsd))} ${dim(`across ${realAdapters.length} tools`)}`);
327
+ valueBlocks.push(`${dim(`across ${realAdapters.length} tools`)}`);
321
328
  }
322
329
  if (pct > 0) {
323
- valueBlocks.push(`${bold(`${pct}%`)} ${dim("efficient")}`);
330
+ valueBlocks.push(`${bold(`${pct}%`)} ${dim("kept out")}`);
324
331
  }
325
332
 
326
333
  const head = `${brand("context-mode")} ${dot} `;
package/build/server.js CHANGED
@@ -2552,7 +2552,12 @@ server.registerTool("ctx_stats", {
2552
2552
  }
2553
2553
  }
2554
2554
  catch { /* never block ctx_stats */ }
2555
- text = formatReport(report, VERSION, _latestVersion, { lifetime, mcpUsage, multiAdapter, conversation, realBytes });
2555
+ // v1.0.117: pass projectDir as cwd so the narrative renderer's
2556
+ // "started in <path>" line matches the user's actual project, not
2557
+ // the MCP server's chdir'd plugin install dir. getProjectDir()
2558
+ // includes v1.0.115's transcript heuristic which reads the literal
2559
+ // cwd from Claude Code's session jsonl.
2560
+ text = formatReport(report, VERSION, _latestVersion, { lifetime, mcpUsage, multiAdapter, conversation, realBytes, cwd: projectDir });
2556
2561
  }
2557
2562
  finally {
2558
2563
  sdb.close();
@@ -480,6 +480,27 @@ export declare const autoMemoryLabels: Record<string, string>;
480
480
  * each tool's own surface area.
481
481
  */
482
482
  export declare const adapterLabels: Record<string, string>;
483
+ /**
484
+ * Format a byte count for the narrative dashboard.
485
+ *
486
+ * Single-unit auto-scale (Grafana / CloudWatch / Datadog convention).
487
+ * Decimals shrink as the integer part grows so the number stays readable
488
+ * at every magnitude. Max output width is 8 characters which fits the
489
+ * existing `padStart(8)` callsites in Sections 1, 3, 4.
490
+ *
491
+ * < 1 KB → "X B" e.g. "100 B"
492
+ * 1 KB – < 100 KB → "X.Y KB" e.g. "4.7 KB", "92.8 KB"
493
+ * 100 KB – < 1 MB → "X KB" e.g. "227 KB", "976 KB"
494
+ * 1 MB – < 100 MB → "X.Y MB" e.g. "4.5 MB", "11.6 MB"
495
+ * 100 MB – < 1 GB → "X MB" e.g. "178 MB", "906 MB"
496
+ * 1 GB – < 100 GB → "X.YY GB" e.g. "1.00 GB", "11.36 GB"
497
+ * ≥ 100 GB → "X.Y GB" e.g. "216.6 GB"
498
+ *
499
+ * Replaced the dual-unit "X KB (0.YY MB)" form because the parenthetical
500
+ * rounded to 0.00 / 0.01 in the common range and added noise without
501
+ * information. Scale awareness comes from the unit jump between rows.
502
+ */
503
+ export declare function kb(b: number): string;
483
504
  /**
484
505
  * Locale + IANA-timezone detection for the narrative renderer.
485
506
  *
@@ -1059,7 +1059,7 @@ function adapterLabel(name) {
1059
1059
  * rounded to 0.00 / 0.01 in the common range and added noise without
1060
1060
  * information. Scale awareness comes from the unit jump between rows.
1061
1061
  */
1062
- function kb(b) {
1062
+ export function kb(b) {
1063
1063
  if (!Number.isFinite(b) || b <= 0)
1064
1064
  return "0 B";
1065
1065
  if (b < 1024)
package/cli.bundle.mjs CHANGED
@@ -945,7 +945,7 @@ THINK IN CODE \u2014 NON-NEGOTIABLE: When commands produce data you need to anal
945
945
  `),c=Buffer.byteLength(a),u=a.split(`
946
946
  `).length;if(i&&o.length===0)return K("ctx_batch_execute",{content:[{type:"text",text:`Batch timed out after ${r}ms. No output captured.`}],isError:!0});mr(c);let d=ms(),l=`batch:${t.map(_=>_.label).join(",").slice(0,80)}`,m=d.index({content:a,source:l}),f=d.getChunksBySource(m.sourceId),p=["## Indexed Sections",""],h=[];for(let _ of f){let b=Buffer.byteLength(_.content);p.push(`- ${_.title} (${(b/1024).toFixed(1)}KB)`),h.push(_.title)}let g=g$(d,e,l),y=d.getDistinctiveTerms?d.getDistinctiveTerms(m.sourceId):[],x=[`Executed ${t.length} commands (${u} lines, ${(c/1024).toFixed(1)}KB). Indexed ${m.totalChunks} sections. Searched ${e.length} queries.`,"",...p,"",...g,y.length>0?`
947
947
  Searchable terms for follow-up: ${y.join(", ")}`:""].join(`
948
- `);return K("ctx_batch_execute",{content:[{type:"text",text:x}]})}catch(s){let o=s instanceof Error?s.message:String(s);return K("ctx_batch_execute",{content:[{type:"text",text:`Batch execution error: ${o}`}],isError:!0})}});rt.registerTool("ctx_stats",{title:"Session Statistics",description:"Returns context consumption statistics for the current session. Shows total bytes returned to context, breakdown by tool, call counts, estimated token usage, and context savings ratio.",inputSchema:U.object({})},async()=>{let t;try{let e=fr(),r=ls(e),n=ju({projectDir:e,sessionsDir:ct()});if(ze(n)){let s=Xt(),o=new s(n,{readonly:!0});try{let i=new vo(o),a=i.queryAll(se),c=i.getMcpToolUsage(),u=Ki({sessionsDir:ct()}),d;try{d=Zu()}catch{}let l,m;try{let f=process.env.CLAUDE_SESSION_ID;if(f||(f=o.prepare("SELECT session_id FROM session_events WHERE session_id LIKE '________-____-____-____-____________' ORDER BY created_at DESC LIMIT 1").get()?.session_id),f){l=YE({sessionId:f,sessionsDir:ct(),worktreeHash:r});let p=uy({sessionId:f,sessionsDir:ct(),worktreeHash:r}),h=uy({sessionsDir:ct()});m={conversation:p,lifetime:h}}}catch{}t=qu(a,Ur,Hr,{lifetime:u,mcpUsage:c,multiAdapter:d,conversation:l,realBytes:m})}finally{o.close()}}else{let o=new vo(u$()).queryAll(se),i=Ki({sessionsDir:ct()}),a;try{a=Zu()}catch{}t=qu(o,Ur,Hr,{lifetime:i,multiAdapter:a})}}catch{let r=new vo(u$()).queryAll(se),n;try{n=Ki({sessionsDir:ct()})}catch{}let s;try{s=Zu()}catch{}t=qu(r,Ur,Hr,n||s?{lifetime:n,multiAdapter:s}:void 0)}return K("ctx_stats",{content:[{type:"text",text:t}]})});rt.registerTool("ctx_doctor",{title:"Run Diagnostics",description:"Diagnose context-mode installation. Runs all checks server-side and returns a plain-text status report with [OK]/[FAIL]/[WARN] prefixes (renderer-safe across MCP clients). No CLI execution needed.",inputSchema:U.object({})},async()=>{let t=["context-mode doctor",""],e=ze(ut(Nt,"package.json"))?Nt:Qt(Nt),r=11,n=(Gu.length/r*100).toFixed(0);t.push(`[OK] Runtimes: ${Gu.length}/${r} (${n}%) \u2014 ${Gu.join(", ")}`),_s()?t.push("[OK] Performance: FAST (Bun)"):t.push("[WARN] Performance: NORMAL \u2014 install Bun for 3-5x speed boost");{let o=new po({runtimes:ea});try{let i=await o.execute({language:"javascript",code:'console.log("ok");',timeout:5e3});if(i.exitCode===0&&i.stdout.trim()==="ok")t.push("[OK] Server test: PASS");else{let a=i.stderr?.trim()?` (${i.stderr.trim().slice(0,200)})`:"";t.push(`[FAIL] Server test: FAIL \u2014 exit ${i.exitCode}${a}`)}}catch(i){t.push(`[FAIL] Server test: FAIL \u2014 ${i instanceof Error?i.message:i}`)}finally{o.cleanupBackgrounded()}}{let o;try{let i=Xt();o=new i(":memory:"),o.exec("CREATE VIRTUAL TABLE fts_test USING fts5(content)"),o.exec("INSERT INTO fts_test(content) VALUES ('hello world')");let a=o.prepare("SELECT * FROM fts_test WHERE fts_test MATCH 'hello'").get();a&&a.content==="hello world"?t.push("[OK] FTS5 / SQLite: PASS \u2014 native module works"):t.push("[FAIL] FTS5 / SQLite: FAIL \u2014 unexpected result")}catch(i){t.push(`[FAIL] FTS5 / SQLite: FAIL \u2014 ${i instanceof Error?i.message:i}`)}finally{try{o?.close()}catch{}}}let s=await mF();if(s){for(let i of s.validateHooks(e)){let a=i.status==="pass"?"[OK]":i.status==="warn"?"[WARN]":"[FAIL]",c=i.fix?` \u2014 fix: ${i.fix}`:"";t.push(`${a} ${i.check}: ${i.message}${c}`)}let o=ga(s,e);o.length===0&&t.push("[OK] Hook scripts: no direct .mjs script paths to verify");for(let i of o){let a=ut(e,i);ze(a)?t.push(`[OK] Hook script: PASS \u2014 ${a}`):t.push(`[FAIL] Hook script: FAIL \u2014 not found at ${a}`)}}else t.push("[WARN] Hooks: adapter detection unavailable");return t.push(`[OK] Version: v${Ur}`),K("ctx_doctor",{content:[{type:"text",text:t.join(`
948
+ `);return K("ctx_batch_execute",{content:[{type:"text",text:x}]})}catch(s){let o=s instanceof Error?s.message:String(s);return K("ctx_batch_execute",{content:[{type:"text",text:`Batch execution error: ${o}`}],isError:!0})}});rt.registerTool("ctx_stats",{title:"Session Statistics",description:"Returns context consumption statistics for the current session. Shows total bytes returned to context, breakdown by tool, call counts, estimated token usage, and context savings ratio.",inputSchema:U.object({})},async()=>{let t;try{let e=fr(),r=ls(e),n=ju({projectDir:e,sessionsDir:ct()});if(ze(n)){let s=Xt(),o=new s(n,{readonly:!0});try{let i=new vo(o),a=i.queryAll(se),c=i.getMcpToolUsage(),u=Ki({sessionsDir:ct()}),d;try{d=Zu()}catch{}let l,m;try{let f=process.env.CLAUDE_SESSION_ID;if(f||(f=o.prepare("SELECT session_id FROM session_events WHERE session_id LIKE '________-____-____-____-____________' ORDER BY created_at DESC LIMIT 1").get()?.session_id),f){l=YE({sessionId:f,sessionsDir:ct(),worktreeHash:r});let p=uy({sessionId:f,sessionsDir:ct(),worktreeHash:r}),h=uy({sessionsDir:ct()});m={conversation:p,lifetime:h}}}catch{}t=qu(a,Ur,Hr,{lifetime:u,mcpUsage:c,multiAdapter:d,conversation:l,realBytes:m,cwd:e})}finally{o.close()}}else{let o=new vo(u$()).queryAll(se),i=Ki({sessionsDir:ct()}),a;try{a=Zu()}catch{}t=qu(o,Ur,Hr,{lifetime:i,multiAdapter:a})}}catch{let r=new vo(u$()).queryAll(se),n;try{n=Ki({sessionsDir:ct()})}catch{}let s;try{s=Zu()}catch{}t=qu(r,Ur,Hr,n||s?{lifetime:n,multiAdapter:s}:void 0)}return K("ctx_stats",{content:[{type:"text",text:t}]})});rt.registerTool("ctx_doctor",{title:"Run Diagnostics",description:"Diagnose context-mode installation. Runs all checks server-side and returns a plain-text status report with [OK]/[FAIL]/[WARN] prefixes (renderer-safe across MCP clients). No CLI execution needed.",inputSchema:U.object({})},async()=>{let t=["context-mode doctor",""],e=ze(ut(Nt,"package.json"))?Nt:Qt(Nt),r=11,n=(Gu.length/r*100).toFixed(0);t.push(`[OK] Runtimes: ${Gu.length}/${r} (${n}%) \u2014 ${Gu.join(", ")}`),_s()?t.push("[OK] Performance: FAST (Bun)"):t.push("[WARN] Performance: NORMAL \u2014 install Bun for 3-5x speed boost");{let o=new po({runtimes:ea});try{let i=await o.execute({language:"javascript",code:'console.log("ok");',timeout:5e3});if(i.exitCode===0&&i.stdout.trim()==="ok")t.push("[OK] Server test: PASS");else{let a=i.stderr?.trim()?` (${i.stderr.trim().slice(0,200)})`:"";t.push(`[FAIL] Server test: FAIL \u2014 exit ${i.exitCode}${a}`)}}catch(i){t.push(`[FAIL] Server test: FAIL \u2014 ${i instanceof Error?i.message:i}`)}finally{o.cleanupBackgrounded()}}{let o;try{let i=Xt();o=new i(":memory:"),o.exec("CREATE VIRTUAL TABLE fts_test USING fts5(content)"),o.exec("INSERT INTO fts_test(content) VALUES ('hello world')");let a=o.prepare("SELECT * FROM fts_test WHERE fts_test MATCH 'hello'").get();a&&a.content==="hello world"?t.push("[OK] FTS5 / SQLite: PASS \u2014 native module works"):t.push("[FAIL] FTS5 / SQLite: FAIL \u2014 unexpected result")}catch(i){t.push(`[FAIL] FTS5 / SQLite: FAIL \u2014 ${i instanceof Error?i.message:i}`)}finally{try{o?.close()}catch{}}}let s=await mF();if(s){for(let i of s.validateHooks(e)){let a=i.status==="pass"?"[OK]":i.status==="warn"?"[WARN]":"[FAIL]",c=i.fix?` \u2014 fix: ${i.fix}`:"";t.push(`${a} ${i.check}: ${i.message}${c}`)}let o=ga(s,e);o.length===0&&t.push("[OK] Hook scripts: no direct .mjs script paths to verify");for(let i of o){let a=ut(e,i);ze(a)?t.push(`[OK] Hook script: PASS \u2014 ${a}`):t.push(`[FAIL] Hook script: FAIL \u2014 not found at ${a}`)}}else t.push("[WARN] Hooks: adapter detection unavailable");return t.push(`[OK] Version: v${Ur}`),K("ctx_doctor",{content:[{type:"text",text:t.join(`
949
949
  `)}]})});rt.registerTool("ctx_upgrade",{title:"Upgrade Plugin",description:"Upgrade context-mode to the latest version. Returns a shell command to execute. You MUST run the returned command using your shell tool (Bash, shell_execute, run_in_terminal, etc.) and display the output as a checklist. Tell the user to restart their session after upgrade.",inputSchema:U.object({})},async()=>{let t=ze(ut(Nt,"package.json"))?Nt:Qt(Nt),e=ut(t,"cli.bundle.mjs"),r=ut(t,"build","cli.js");try{let o=ct(),i=Ce(Qt(o),"insight-cache");ze(i)&&(wy(4747),Ku(i,{recursive:!0,force:!0}))}catch{}let n;if(ze(e))n=`${Fe(e)} upgrade`;else if(ze(r))n=`${Fe(r)} upgrade`;else{let i=['import{execFileSync}from"node:child_process";','import{cpSync,rmSync,existsSync,mkdtempSync,readFileSync,writeFileSync}from"node:fs";','import{join}from"node:path";','import{tmpdir}from"node:os";',`const P=${JSON.stringify(t)};`,'const T=mkdtempSync(join(tmpdir(),"ctx-upgrade-"));',"try{",'console.log("- [x] Starting inline upgrade (no CLI found)");','execFileSync("git",["clone","--depth","1","https://github.com/mksglu/context-mode.git",T],{stdio:"inherit"});','console.log("- [x] Cloned latest source");','execFileSync(process.platform==="win32"?"npm.cmd":"npm",["install"],{cwd:T,stdio:"inherit",shell:process.platform==="win32"});','execFileSync(process.platform==="win32"?"npm.cmd":"npm",["run","build"],{cwd:T,stdio:"inherit",shell:process.platform==="win32"});','console.log("- [x] Built from source");','const pkg=JSON.parse(readFileSync(join(T,"package.json"),"utf8"));','const items=[...(Array.isArray(pkg.files)?pkg.files:[]),"src","package.json"];',"for(const item of items){const from=join(T,item);const to=join(P,item);if(existsSync(from)){rmSync(to,{recursive:true,force:true});cpSync(from,to,{recursive:true,force:true});}}",'writeFileSync(join(P,".mcp.json"),JSON.stringify({mcpServers:{"context-mode":{command:"node",args:["${CLAUDE_PLUGIN_ROOT}/start.mjs"]}}},null,2)+"\\n");','console.log("- [x] Copied package files");','execFileSync(process.platform==="win32"?"npm.cmd":"npm",["install","--production"],{cwd:P,stdio:"inherit",shell:process.platform==="win32"});','console.log("- [x] Installed production dependencies");','console.log("## context-mode upgrade complete");',"}catch(e){",'console.error("- [ ] Upgrade failed:",e.message);',"process.exit(1);","}finally{","try{rmSync(T,{recursive:true,force:true})}catch{}","}"].join(`
950
950
  `),a=ut(t,".ctx-upgrade-inline.mjs"),{writeFileSync:c}=await import("node:fs");c(a,i),n=Fe(a)}let s=["## ctx-upgrade","","Run this command using your shell execution tool:","","```",n,"```","","After the command completes, display results as a markdown checklist:","- `[x]` for success, `[ ]` for failure","- Example format:"," ```"," ## context-mode upgrade"," - [x] Pulled latest from GitHub"," - [x] Built and installed v0.9.24"," - [x] npm global updated"," - [x] Hooks configured"," - [x] Doctor: all checks PASS"," ```","- Tell the user to restart their session to pick up the new version."].join(`
951
951
  `);return K("ctx_upgrade",{content:[{type:"text",text:s}]})});rt.registerTool("ctx_purge",{title:"Purge Knowledge Base",description:"Permanently deletes ALL session data for this project: FTS5 knowledge base (indexed content), session events DB (analytics, metadata, resume snapshots), and session events markdown. Resets in-memory stats. This is irreversible.",inputSchema:U.object({confirm:U.boolean().describe("Must be true to confirm the destructive operation.")})},async({confirm:t})=>{if(!t)return K("ctx_purge",{content:[{type:"text",text:"Purge cancelled. Pass confirm: true to proceed."}]});let e;try{e=fy()}catch{}if(pr){try{pr.cleanup()}catch{}pr=null}let r=e?Qt(e):void 0,{deleted:n}=EE({projectDir:fr(),sessionsDir:ct(),storePath:e,contentDir:r,legacyContentDir:Ce(Qu(),".context-mode","content"),contentHash:us(fr())});se.calls={},se.bytesReturned={},se.bytesIndexed=0,se.bytesSandboxed=0,se.cacheHits=0,se.cacheBytesSaved=0,se.sessionStart=Date.now(),n.push("session stats");try{let s=p$();ze(s)&&Yi(s)}catch{}return K("ctx_purge",{content:[{type:"text",text:`Purged: ${n.join(", ")}. All session data for this project has been permanently deleted.`}]})});Ji=5e3;rt.registerTool("ctx_insight",{title:"Open Insight Dashboard",description:"Opens the context-mode Insight dashboard in the browser. Shows personal analytics: session activity, tool usage, error rate, parallel work patterns, project focus, and actionable insights. First run installs dependencies (~30s). Subsequent runs open instantly.",inputSchema:U.object({port:U.coerce.number().int().min(1).max(65535).optional().describe("Port to serve on (default: 4747)"),sessionDir:U.string().optional().describe("Override INSIGHT_SESSION_DIR: directory containing context-mode session .db files"),contentDir:U.string().optional().describe("Override INSIGHT_CONTENT_DIR: directory containing context-mode content/index .db files"),insightSessionDir:U.string().optional().describe("Alias for sessionDir / INSIGHT_SESSION_DIR"),insightContentDir:U.string().optional().describe("Alias for contentDir / INSIGHT_CONTENT_DIR")})},async({port:t,sessionDir:e,contentDir:r,insightSessionDir:n,insightContentDir:s})=>{let o=t||4747,i=e||n,a=r||s,c=ze(ut(Nt,"package.json"))?Nt:Qt(Nt),u=ut(c,"insight"),d=i?ut(i):ct(),l=a?ut(a):Ce(Qt(d),"content"),m=Ce(Qt(d),"insight-cache");if(!ze(Ce(u,"server.mjs")))return K("ctx_insight",{content:[{type:"text",text:"Error: Insight source not found in plugin. Try upgrading context-mode."}]});try{let f=[],p=!1;Xi(m,{recursive:!0});let h=e$(Ce(u,"server.mjs")).mtimeMs,g=ze(Ce(m,"server.mjs"))?e$(Ce(m,"server.mjs")).mtimeMs:0;if(h>g&&(f.push("Copying source files..."),sF(u,m,{recursive:!0,force:!0}),f.push("Source files copied."),p=!0),!ze(Ce(m,"node_modules"))||p){f.push("Installing dependencies (first run, ~30s)...");try{t$(process.platform==="win32"?"npm.cmd install --production=false":"npm install --production=false",{cwd:m,stdio:"pipe",timeout:3e5})}catch{try{Ku(Ce(m,"node_modules"),{recursive:!0,force:!0})}catch{}throw new Error("npm install failed \u2014 please retry")}if(!ze(Ce(m,"node_modules","vite"))||!ze(Ce(m,"node_modules","better-sqlite3")))throw Ku(Ce(m,"node_modules"),{recursive:!0,force:!0}),new Error("npm install incomplete \u2014 please retry");f.push("Dependencies installed.")}f.push("Building dashboard..."),t$("npx vite build",{cwd:m,stdio:"pipe",timeout:6e4}),f.push("Build complete.");let x=!1;try{let{request:T}=await import("node:http");await new Promise((z,P)=>{let O=T(`http://127.0.0.1:${o}/api/overview`,{timeout:2e3},V=>{V.resume(),z()});O.on("error",()=>P()),O.on("timeout",()=>{O.destroy(),P()}),O.end()}),x=!0}catch{}if(x&&p){f.push("Killing stale dashboard server (source updated)...");let T=wy(o);if(T.attemptedPids.length>0&&T.killedPids.length===0)return K("ctx_insight",{content:[{type:"text",text:`Could not free port ${o} (kill failed for ${T.attemptedPids.join(", ")}: ${T.errors.join("; ")}). Try ctx_insight({ port: ${o+1} }) or stop the process manually.`}]});if(T.errors.length>0&&T.attemptedPids.length===0)return K("ctx_insight",{content:[{type:"text",text:`Cannot reclaim port ${o}: ${T.errors.join("; ")}. Stop the process manually or pick another port.`}]});await new Promise(z=>setTimeout(z,500)),f.push(`Stale server killed (${T.killedPids.length} pid${T.killedPids.length===1?"":"s"}).`)}else if(x){f.push("Dashboard already running.");let T=`http://localhost:${o}`,z=gy(T),P=z.ok?"":` (auto-open failed: ${z.reason}; navigate manually)`;return K("ctx_insight",{content:[{type:"text",text:`Dashboard already running at ${T}${P}`}]})}if(Fr&&Fr.pid&&!Fr.killed)try{Fr.kill("SIGTERM")}catch{}let{spawn:_}=await import("node:child_process"),b=_("node",[Ce(m,"server.mjs")],{cwd:m,env:{...process.env,PORT:String(o),INSIGHT_SESSION_DIR:d,INSIGHT_CONTENT_DIR:l,INSIGHT_PARENT_PID:String(process.pid)},detached:!0,stdio:"ignore"});b.on("error",()=>{}),b.unref(),Fr=b,await new Promise(T=>setTimeout(T,1500));try{let{request:T}=await import("node:http");await new Promise((z,P)=>{let O=T(`http://127.0.0.1:${o}/api/overview`,{timeout:3e3},V=>{z(),V.resume()});O.on("error",P),O.on("timeout",()=>{O.destroy(),P(new Error("timeout"))}),O.end()})}catch{return K("ctx_insight",{content:[{type:"text",text:`Port ${o} appears to be in use. Either a previous dashboard is still running, or another service is using this port.
@@ -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.116",
6
+ "version": "1.0.118",
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.116",
3
+ "version": "1.0.118",
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",
package/server.bundle.mjs CHANGED
@@ -943,7 +943,7 @@ THINK IN CODE \u2014 NON-NEGOTIABLE: When commands produce data you need to anal
943
943
  `),c=Buffer.byteLength(a),u=a.split(`
944
944
  `).length;if(i&&s.length===0)return B("ctx_batch_execute",{content:[{type:"text",text:`Batch timed out after ${r}ms. No output captured.`}],isError:!0});Yt(c);let l=Fn(),d=`batch:${t.map(x=>x.label).join(",").slice(0,80)}`,f=l.index({content:a,source:d}),m=l.getChunksBySource(f.sourceId),p=["## Indexed Sections",""],h=[];for(let x of m){let b=Buffer.byteLength(x.content);p.push(`- ${x.title} (${(b/1024).toFixed(1)}KB)`),h.push(x.title)}let g=sj(l,e,d),y=l.getDistinctiveTerms?l.getDistinctiveTerms(f.sourceId):[],_=[`Executed ${t.length} commands (${u} lines, ${(c/1024).toFixed(1)}KB). Indexed ${f.totalChunks} sections. Searched ${e.length} queries.`,"",...p,"",...g,y.length>0?`
945
945
  Searchable terms for follow-up: ${y.join(", ")}`:""].join(`
946
- `);return B("ctx_batch_execute",{content:[{type:"text",text:_}]})}catch(o){let s=o instanceof Error?o.message:String(o);return B("ctx_batch_execute",{content:[{type:"text",text:`Batch execution error: ${s}`}],isError:!0})}});function Ek(){return{prepare:()=>({run:()=>{},get:(...t)=>({cnt:0,compact_count:0,minutes:null,rate:0,avg:0,outcome:"exploratory"}),all:()=>[]})}}We.registerTool("ctx_stats",{title:"Session Statistics",description:"Returns context consumption statistics for the current session. Shows total bytes returned to context, breakdown by tool, call counts, estimated token usage, and context savings ratio.",inputSchema:D.object({})},async()=>{let t;try{let e=Xt(),r=An(e),n=pc({projectDir:e,sessionsDir:Qe()});if(Ie(n)){let o=Wt(),s=new o(n,{readonly:!0});try{let i=new Uo(s),a=i.queryAll(te),c=i.getMcpToolUsage(),u=ai({sessionsDir:Qe()}),l;try{l=Ic()}catch{}let d,f;try{let m=process.env.CLAUDE_SESSION_ID;if(m||(m=s.prepare("SELECT session_id FROM session_events WHERE session_id LIKE '________-____-____-____-____________' ORDER BY created_at DESC LIMIT 1").get()?.session_id),m){d=mk({sessionId:m,sessionsDir:Qe(),worktreeHash:r});let p=qm({sessionId:m,sessionsDir:Qe(),worktreeHash:r}),h=qm({sessionsDir:Qe()});f={conversation:p,lifetime:h}}}catch{}t=Nc(a,kr,wr,{lifetime:u,mcpUsage:c,multiAdapter:l,conversation:d,realBytes:f})}finally{s.close()}}else{let s=new Uo(Ek()).queryAll(te),i=ai({sessionsDir:Qe()}),a;try{a=Ic()}catch{}t=Nc(s,kr,wr,{lifetime:i,multiAdapter:a})}}catch{let r=new Uo(Ek()).queryAll(te),n;try{n=ai({sessionsDir:Qe()})}catch{}let o;try{o=Ic()}catch{}t=Nc(r,kr,wr,n||o?{lifetime:n,multiAdapter:o}:void 0)}return B("ctx_stats",{content:[{type:"text",text:t}]})});We.registerTool("ctx_doctor",{title:"Run Diagnostics",description:"Diagnose context-mode installation. Runs all checks server-side and returns a plain-text status report with [OK]/[FAIL]/[WARN] prefixes (renderer-safe across MCP clients). No CLI execution needed.",inputSchema:D.object({})},async()=>{let t=["context-mode doctor",""],e=Ie(et(St,"package.json"))?St:Dt(St),r=11,n=(jc.length/r*100).toFixed(0);t.push(`[OK] Runtimes: ${jc.length}/${r} (${n}%) \u2014 ${jc.join(", ")}`),oc()?t.push("[OK] Performance: FAST (Bun)"):t.push("[WARN] Performance: NORMAL \u2014 install Bun for 3-5x speed boost");{let s=new Vs({runtimes:pi});try{let i=await s.execute({language:"javascript",code:'console.log("ok");',timeout:5e3});if(i.exitCode===0&&i.stdout.trim()==="ok")t.push("[OK] Server test: PASS");else{let a=i.stderr?.trim()?` (${i.stderr.trim().slice(0,200)})`:"";t.push(`[FAIL] Server test: FAIL \u2014 exit ${i.exitCode}${a}`)}}catch(i){t.push(`[FAIL] Server test: FAIL \u2014 ${i instanceof Error?i.message:i}`)}finally{s.cleanupBackgrounded()}}{let s;try{let i=Wt();s=new i(":memory:"),s.exec("CREATE VIRTUAL TABLE fts_test USING fts5(content)"),s.exec("INSERT INTO fts_test(content) VALUES ('hello world')");let a=s.prepare("SELECT * FROM fts_test WHERE fts_test MATCH 'hello'").get();a&&a.content==="hello world"?t.push("[OK] FTS5 / SQLite: PASS \u2014 native module works"):t.push("[FAIL] FTS5 / SQLite: FAIL \u2014 unexpected result")}catch(i){t.push(`[FAIL] FTS5 / SQLite: FAIL \u2014 ${i instanceof Error?i.message:i}`)}finally{try{s?.close()}catch{}}}let o=await HD();if(o){for(let i of o.validateHooks(e)){let a=i.status==="pass"?"[OK]":i.status==="warn"?"[WARN]":"[FAIL]",c=i.fix?` \u2014 fix: ${i.fix}`:"";t.push(`${a} ${i.check}: ${i.message}${c}`)}let s=ok(o,e);s.length===0&&t.push("[OK] Hook scripts: no direct .mjs script paths to verify");for(let i of s){let a=et(e,i);Ie(a)?t.push(`[OK] Hook script: PASS \u2014 ${a}`):t.push(`[FAIL] Hook script: FAIL \u2014 not found at ${a}`)}}else t.push("[WARN] Hooks: adapter detection unavailable");return t.push(`[OK] Version: v${kr}`),B("ctx_doctor",{content:[{type:"text",text:t.join(`
946
+ `);return B("ctx_batch_execute",{content:[{type:"text",text:_}]})}catch(o){let s=o instanceof Error?o.message:String(o);return B("ctx_batch_execute",{content:[{type:"text",text:`Batch execution error: ${s}`}],isError:!0})}});function Ek(){return{prepare:()=>({run:()=>{},get:(...t)=>({cnt:0,compact_count:0,minutes:null,rate:0,avg:0,outcome:"exploratory"}),all:()=>[]})}}We.registerTool("ctx_stats",{title:"Session Statistics",description:"Returns context consumption statistics for the current session. Shows total bytes returned to context, breakdown by tool, call counts, estimated token usage, and context savings ratio.",inputSchema:D.object({})},async()=>{let t;try{let e=Xt(),r=An(e),n=pc({projectDir:e,sessionsDir:Qe()});if(Ie(n)){let o=Wt(),s=new o(n,{readonly:!0});try{let i=new Uo(s),a=i.queryAll(te),c=i.getMcpToolUsage(),u=ai({sessionsDir:Qe()}),l;try{l=Ic()}catch{}let d,f;try{let m=process.env.CLAUDE_SESSION_ID;if(m||(m=s.prepare("SELECT session_id FROM session_events WHERE session_id LIKE '________-____-____-____-____________' ORDER BY created_at DESC LIMIT 1").get()?.session_id),m){d=mk({sessionId:m,sessionsDir:Qe(),worktreeHash:r});let p=qm({sessionId:m,sessionsDir:Qe(),worktreeHash:r}),h=qm({sessionsDir:Qe()});f={conversation:p,lifetime:h}}}catch{}t=Nc(a,kr,wr,{lifetime:u,mcpUsage:c,multiAdapter:l,conversation:d,realBytes:f,cwd:e})}finally{s.close()}}else{let s=new Uo(Ek()).queryAll(te),i=ai({sessionsDir:Qe()}),a;try{a=Ic()}catch{}t=Nc(s,kr,wr,{lifetime:i,multiAdapter:a})}}catch{let r=new Uo(Ek()).queryAll(te),n;try{n=ai({sessionsDir:Qe()})}catch{}let o;try{o=Ic()}catch{}t=Nc(r,kr,wr,n||o?{lifetime:n,multiAdapter:o}:void 0)}return B("ctx_stats",{content:[{type:"text",text:t}]})});We.registerTool("ctx_doctor",{title:"Run Diagnostics",description:"Diagnose context-mode installation. Runs all checks server-side and returns a plain-text status report with [OK]/[FAIL]/[WARN] prefixes (renderer-safe across MCP clients). No CLI execution needed.",inputSchema:D.object({})},async()=>{let t=["context-mode doctor",""],e=Ie(et(St,"package.json"))?St:Dt(St),r=11,n=(jc.length/r*100).toFixed(0);t.push(`[OK] Runtimes: ${jc.length}/${r} (${n}%) \u2014 ${jc.join(", ")}`),oc()?t.push("[OK] Performance: FAST (Bun)"):t.push("[WARN] Performance: NORMAL \u2014 install Bun for 3-5x speed boost");{let s=new Vs({runtimes:pi});try{let i=await s.execute({language:"javascript",code:'console.log("ok");',timeout:5e3});if(i.exitCode===0&&i.stdout.trim()==="ok")t.push("[OK] Server test: PASS");else{let a=i.stderr?.trim()?` (${i.stderr.trim().slice(0,200)})`:"";t.push(`[FAIL] Server test: FAIL \u2014 exit ${i.exitCode}${a}`)}}catch(i){t.push(`[FAIL] Server test: FAIL \u2014 ${i instanceof Error?i.message:i}`)}finally{s.cleanupBackgrounded()}}{let s;try{let i=Wt();s=new i(":memory:"),s.exec("CREATE VIRTUAL TABLE fts_test USING fts5(content)"),s.exec("INSERT INTO fts_test(content) VALUES ('hello world')");let a=s.prepare("SELECT * FROM fts_test WHERE fts_test MATCH 'hello'").get();a&&a.content==="hello world"?t.push("[OK] FTS5 / SQLite: PASS \u2014 native module works"):t.push("[FAIL] FTS5 / SQLite: FAIL \u2014 unexpected result")}catch(i){t.push(`[FAIL] FTS5 / SQLite: FAIL \u2014 ${i instanceof Error?i.message:i}`)}finally{try{s?.close()}catch{}}}let o=await HD();if(o){for(let i of o.validateHooks(e)){let a=i.status==="pass"?"[OK]":i.status==="warn"?"[WARN]":"[FAIL]",c=i.fix?` \u2014 fix: ${i.fix}`:"";t.push(`${a} ${i.check}: ${i.message}${c}`)}let s=ok(o,e);s.length===0&&t.push("[OK] Hook scripts: no direct .mjs script paths to verify");for(let i of s){let a=et(e,i);Ie(a)?t.push(`[OK] Hook script: PASS \u2014 ${a}`):t.push(`[FAIL] Hook script: FAIL \u2014 not found at ${a}`)}}else t.push("[WARN] Hooks: adapter detection unavailable");return t.push(`[OK] Version: v${kr}`),B("ctx_doctor",{content:[{type:"text",text:t.join(`
947
947
  `)}]})});We.registerTool("ctx_upgrade",{title:"Upgrade Plugin",description:"Upgrade context-mode to the latest version. Returns a shell command to execute. You MUST run the returned command using your shell tool (Bash, shell_execute, run_in_terminal, etc.) and display the output as a checklist. Tell the user to restart their session after upgrade.",inputSchema:D.object({})},async()=>{let t=Ie(et(St,"package.json"))?St:Dt(St),e=et(t,"cli.bundle.mjs"),r=et(t,"build","cli.js");try{let s=Qe(),i=Ee(Dt(s),"insight-cache");Ie(i)&&(Nk(4747),Mc(i,{recursive:!0,force:!0}))}catch{}let n;if(Ie(e))n=`${Ne(e)} upgrade`;else if(Ie(r))n=`${Ne(r)} upgrade`;else{let i=['import{execFileSync}from"node:child_process";','import{cpSync,rmSync,existsSync,mkdtempSync,readFileSync,writeFileSync}from"node:fs";','import{join}from"node:path";','import{tmpdir}from"node:os";',`const P=${JSON.stringify(t)};`,'const T=mkdtempSync(join(tmpdir(),"ctx-upgrade-"));',"try{",'console.log("- [x] Starting inline upgrade (no CLI found)");','execFileSync("git",["clone","--depth","1","https://github.com/mksglu/context-mode.git",T],{stdio:"inherit"});','console.log("- [x] Cloned latest source");','execFileSync(process.platform==="win32"?"npm.cmd":"npm",["install"],{cwd:T,stdio:"inherit",shell:process.platform==="win32"});','execFileSync(process.platform==="win32"?"npm.cmd":"npm",["run","build"],{cwd:T,stdio:"inherit",shell:process.platform==="win32"});','console.log("- [x] Built from source");','const pkg=JSON.parse(readFileSync(join(T,"package.json"),"utf8"));','const items=[...(Array.isArray(pkg.files)?pkg.files:[]),"src","package.json"];',"for(const item of items){const from=join(T,item);const to=join(P,item);if(existsSync(from)){rmSync(to,{recursive:true,force:true});cpSync(from,to,{recursive:true,force:true});}}",'writeFileSync(join(P,".mcp.json"),JSON.stringify({mcpServers:{"context-mode":{command:"node",args:["${CLAUDE_PLUGIN_ROOT}/start.mjs"]}}},null,2)+"\\n");','console.log("- [x] Copied package files");','execFileSync(process.platform==="win32"?"npm.cmd":"npm",["install","--production"],{cwd:P,stdio:"inherit",shell:process.platform==="win32"});','console.log("- [x] Installed production dependencies");','console.log("## context-mode upgrade complete");',"}catch(e){",'console.error("- [ ] Upgrade failed:",e.message);',"process.exit(1);","}finally{","try{rmSync(T,{recursive:true,force:true})}catch{}","}"].join(`
948
948
  `),a=et(t,".ctx-upgrade-inline.mjs"),{writeFileSync:c}=await import("node:fs");c(a,i),n=Ne(a)}let o=["## ctx-upgrade","","Run this command using your shell execution tool:","","```",n,"```","","After the command completes, display results as a markdown checklist:","- `[x]` for success, `[ ]` for failure","- Example format:"," ```"," ## context-mode upgrade"," - [x] Pulled latest from GitHub"," - [x] Built and installed v0.9.24"," - [x] npm global updated"," - [x] Hooks configured"," - [x] Doctor: all checks PASS"," ```","- Tell the user to restart their session to pick up the new version."].join(`
949
949
  `);return B("ctx_upgrade",{content:[{type:"text",text:o}]})});We.registerTool("ctx_purge",{title:"Purge Knowledge Base",description:"Permanently deletes ALL session data for this project: FTS5 knowledge base (indexed content), session events DB (analytics, metadata, resume snapshots), and session events markdown. Resets in-memory stats. This is irreversible.",inputSchema:D.object({confirm:D.boolean().describe("Must be true to confirm the destructive operation.")})},async({confirm:t})=>{if(!t)return B("ctx_purge",{content:[{type:"text",text:"Purge cancelled. Pass confirm: true to proceed."}]});let e;try{e=Gm()}catch{}if(Jt){try{Jt.cleanup()}catch{}Jt=null}let r=e?Dt(e):void 0,{deleted:n}=NS({projectDir:Xt(),sessionsDir:Qe(),storePath:e,contentDir:r,legacyContentDir:Ee(Zc(),".context-mode","content"),contentHash:In(Xt())});te.calls={},te.bytesReturned={},te.bytesIndexed=0,te.bytesSandboxed=0,te.cacheHits=0,te.cacheBytesSaved=0,te.sessionStart=Date.now(),n.push("session stats");try{let o=$k();Ie(o)&&ui(o)}catch{}return B("ctx_purge",{content:[{type:"text",text:`Purged: ${n.join(", ")}. All session data for this project has been permanently deleted.`}]})});var ci=5e3;function xj(t,e){return e==="darwin"?[{cmd:"open",args:[t]}]:e==="win32"?[{cmd:"cmd",args:["/c","start","",t]}]:[{cmd:"xdg-open",args:[t]},{cmd:"sensible-browser",args:[t]}]}function Tk(t,e=process.platform,r=Rk){let n=xj(t,e),o=[];for(let{cmd:s,args:i}of n)try{let a=r(s,i,{stdio:"ignore",timeout:ci});if(!a.error&&a.status===0)return{ok:!0,method:s};let c=a.error?.message??`status=${a.status===null?"signaled":a.status}`;o.push(`${s}: ${c}`)}catch(a){o.push(`${s}: ${a instanceof Error?a.message:String(a)}`)}return{ok:!1,method:"none",reason:o.join("; ")}}function Nk(t,e=process.platform,r=Rk){let n={killedPids:[],attemptedPids:[],errors:[]};if(!Number.isInteger(t)||t<1||t>65535)return n.errors.push(`invalid port: ${t}`),n;try{if(e==="win32"){let o=r("netstat",["-ano"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:ci});if(o.error)return n.errors.push(`netstat: ${o.error.message}`),n;if(o.status!==0||typeof o.stdout!="string")return n;let s=`:${t}`,i=new Set;for(let a of o.stdout.split(/\r?\n/)){let c=a.trim();if(!c)continue;let u=c.split(/\s+/);if(u.length<5)continue;let l=u[0],d=u[1],f=u[2],m=u[u.length-1];l==="TCP"&&d.endsWith(s)&&(f!=="0.0.0.0:0"&&f!=="[::]:0"||/^\d+$/.test(m)&&i.add(m))}for(let a of i){n.attemptedPids.push(a);try{let c=r("taskkill",["/F","/PID",a],{stdio:"ignore",timeout:ci});c.error||c.status!==0?n.errors.push(`taskkill ${a}: ${c.error?.message??`status=${c.status}`}`):n.killedPids.push(a)}catch(c){n.errors.push(`taskkill ${a}: ${c instanceof Error?c.message:String(c)}`)}}}else{let o=r("lsof",["-ti",`:${t}`],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:ci});if(o.error)return n.errors.push(`lsof: ${o.error.message}`),n;if(o.status!==0||typeof o.stdout!="string")return n;let s=o.stdout.split(/\r?\n/).filter(i=>/^\d+$/.test(i));for(let i of s){n.attemptedPids.push(i);try{let a=r("kill",[i],{stdio:"ignore",timeout:ci});a.error||a.status!==0?n.errors.push(`kill ${i}: ${a.error?.message??`status=${a.status}`}`):n.killedPids.push(i)}catch(a){n.errors.push(`kill ${i}: ${a instanceof Error?a.message:String(a)}`)}}}}catch(o){n.errors.push(o instanceof Error?o.message:String(o))}return n}We.registerTool("ctx_insight",{title:"Open Insight Dashboard",description:"Opens the context-mode Insight dashboard in the browser. Shows personal analytics: session activity, tool usage, error rate, parallel work patterns, project focus, and actionable insights. First run installs dependencies (~30s). Subsequent runs open instantly.",inputSchema:D.object({port:D.coerce.number().int().min(1).max(65535).optional().describe("Port to serve on (default: 4747)"),sessionDir:D.string().optional().describe("Override INSIGHT_SESSION_DIR: directory containing context-mode session .db files"),contentDir:D.string().optional().describe("Override INSIGHT_CONTENT_DIR: directory containing context-mode content/index .db files"),insightSessionDir:D.string().optional().describe("Alias for sessionDir / INSIGHT_SESSION_DIR"),insightContentDir:D.string().optional().describe("Alias for contentDir / INSIGHT_CONTENT_DIR")})},async({port:t,sessionDir:e,contentDir:r,insightSessionDir:n,insightContentDir:o})=>{let s=t||4747,i=e||n,a=r||o,c=Ie(et(St,"package.json"))?St:Dt(St),u=et(c,"insight"),l=i?et(i):Qe(),d=a?et(a):Ee(Dt(l),"content"),f=Ee(Dt(l),"insight-cache");if(!Ie(Ee(u,"server.mjs")))return B("ctx_insight",{content:[{type:"text",text:"Error: Insight source not found in plugin. Try upgrading context-mode."}]});try{let m=[],p=!1;li(f,{recursive:!0});let h=gk(Ee(u,"server.mjs")).mtimeMs,g=Ie(Ee(f,"server.mjs"))?gk(Ee(f,"server.mjs")).mtimeMs:0;if(h>g&&(m.push("Copying source files..."),ID(u,f,{recursive:!0,force:!0}),m.push("Source files copied."),p=!0),!Ie(Ee(f,"node_modules"))||p){m.push("Installing dependencies (first run, ~30s)...");try{yk(process.platform==="win32"?"npm.cmd install --production=false":"npm install --production=false",{cwd:f,stdio:"pipe",timeout:3e5})}catch{try{Mc(Ee(f,"node_modules"),{recursive:!0,force:!0})}catch{}throw new Error("npm install failed \u2014 please retry")}if(!Ie(Ee(f,"node_modules","vite"))||!Ie(Ee(f,"node_modules","better-sqlite3")))throw Mc(Ee(f,"node_modules"),{recursive:!0,force:!0}),new Error("npm install incomplete \u2014 please retry");m.push("Dependencies installed.")}m.push("Building dashboard..."),yk("npx vite build",{cwd:f,stdio:"pipe",timeout:6e4}),m.push("Build complete.");let _=!1;try{let{request:I}=await import("node:http");await new Promise((Z,T)=>{let P=I(`http://127.0.0.1:${s}/api/overview`,{timeout:2e3},V=>{V.resume(),Z()});P.on("error",()=>T()),P.on("timeout",()=>{P.destroy(),T()}),P.end()}),_=!0}catch{}if(_&&p){m.push("Killing stale dashboard server (source updated)...");let I=Nk(s);if(I.attemptedPids.length>0&&I.killedPids.length===0)return B("ctx_insight",{content:[{type:"text",text:`Could not free port ${s} (kill failed for ${I.attemptedPids.join(", ")}: ${I.errors.join("; ")}). Try ctx_insight({ port: ${s+1} }) or stop the process manually.`}]});if(I.errors.length>0&&I.attemptedPids.length===0)return B("ctx_insight",{content:[{type:"text",text:`Cannot reclaim port ${s}: ${I.errors.join("; ")}. Stop the process manually or pick another port.`}]});await new Promise(Z=>setTimeout(Z,500)),m.push(`Stale server killed (${I.killedPids.length} pid${I.killedPids.length===1?"":"s"}).`)}else if(_){m.push("Dashboard already running.");let I=`http://localhost:${s}`,Z=Tk(I),T=Z.ok?"":` (auto-open failed: ${Z.reason}; navigate manually)`;return B("ctx_insight",{content:[{type:"text",text:`Dashboard already running at ${I}${T}`}]})}if(br&&br.pid&&!br.killed)try{br.kill("SIGTERM")}catch{}let{spawn:x}=await import("node:child_process"),b=x("node",[Ee(f,"server.mjs")],{cwd:f,env:{...process.env,PORT:String(s),INSIGHT_SESSION_DIR:l,INSIGHT_CONTENT_DIR:d,INSIGHT_PARENT_PID:String(process.pid)},detached:!0,stdio:"ignore"});b.on("error",()=>{}),b.unref(),br=b,await new Promise(I=>setTimeout(I,1500));try{let{request:I}=await import("node:http");await new Promise((Z,T)=>{let P=I(`http://127.0.0.1:${s}/api/overview`,{timeout:3e3},V=>{Z(),V.resume()});P.on("error",T),P.on("timeout",()=>{P.destroy(),T(new Error("timeout"))}),P.end()})}catch{return B("ctx_insight",{content:[{type:"text",text:`Port ${s} appears to be in use. Either a previous dashboard is still running, or another service is using this port.