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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/bin/statusline.mjs +43 -36
- package/build/server.js +6 -1
- package/build/session/analytics.d.ts +21 -0
- package/build/session/analytics.js +1 -1
- package/cli.bundle.mjs +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +1 -1
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.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.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
package/bin/statusline.mjs
CHANGED
|
@@ -212,7 +212,7 @@ async function main() {
|
|
|
212
212
|
const {
|
|
213
213
|
getRealBytesStats,
|
|
214
214
|
getMultiAdapterLifetimeStats,
|
|
215
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
//
|
|
261
|
-
|
|
264
|
+
// This-chat bytes — real 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
|
-
//
|
|
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
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
298
|
-
if (
|
|
299
|
-
const blocks = [
|
|
300
|
-
|
|
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(`${
|
|
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:
|
|
319
|
+
// ACTIVE: this-chat · lifetime · [N tools] · % efficient
|
|
313
320
|
const valueBlocks = [
|
|
314
|
-
`${bold(
|
|
321
|
+
`${bold(kb(sessionBytes))} ${dim("this chat")}`,
|
|
315
322
|
];
|
|
316
|
-
if (
|
|
317
|
-
valueBlocks.push(`${bold(
|
|
323
|
+
if (lifetimeBytes > 0) {
|
|
324
|
+
valueBlocks.push(`${bold(kb(lifetimeBytes))} ${dim("lifetime")}`);
|
|
318
325
|
}
|
|
319
326
|
if (showMultiAdapter) {
|
|
320
|
-
valueBlocks.push(`${
|
|
327
|
+
valueBlocks.push(`${dim(`across ${realAdapters.length} tools`)}`);
|
|
321
328
|
}
|
|
322
329
|
if (pct > 0) {
|
|
323
|
-
valueBlocks.push(`${bold(`${pct}%`)} ${dim("
|
|
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
|
-
|
|
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.
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|