context-mode 1.0.70 → 1.0.71

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.70"
9
+ "version": "1.0.71"
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.70",
16
+ "version": "1.0.71",
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.70",
3
+ "version": "1.0.71",
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.70",
6
+ "version": "1.0.71",
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.70",
3
+ "version": "1.0.71",
4
4
  "description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
5
5
  "author": {
6
6
  "name": "Mert Koseoğlu",
package/build/server.js CHANGED
@@ -531,6 +531,19 @@ server.registerTool("ctx_execute", {
531
531
  // The closure approach (function(__cm_req){ var require=...; })(require) correctly
532
532
  // shadows the CJS require for all code inside, including __cm_main().
533
533
  instrumentedCode = `
534
+ // FS read instrumentation — count bytes read via fs.readFileSync/readFile
535
+ let __cm_fs=0;
536
+ process.on('exit',()=>{if(__cm_fs>0)try{process.stderr.write('__CM_FS__:'+__cm_fs+'\\n')}catch{}});
537
+ (function(){
538
+ try{
539
+ var f=typeof require!=='undefined'?require('fs'):null;
540
+ if(!f)return;
541
+ var ors=f.readFileSync;
542
+ f.readFileSync=function(){var r=ors.apply(this,arguments);if(Buffer.isBuffer(r))__cm_fs+=r.length;else if(typeof r==='string')__cm_fs+=Buffer.byteLength(r);return r;};
543
+ var orf=f.readFile;
544
+ if(orf)f.readFile=function(){var a=Array.from(arguments),cb=a.pop();orf.apply(this,a.concat([function(e,d){if(!e&&d){if(Buffer.isBuffer(d))__cm_fs+=d.length;else if(typeof d==='string')__cm_fs+=Buffer.byteLength(d);}cb(e,d);}]));};
545
+ }catch{}
546
+ })();
534
547
  let __cm_net=0;
535
548
  // Report network bytes on process exit — works with both promise and callback patterns.
536
549
  // process.on('exit') fires after all I/O completes, unlike .finally() which fires
@@ -585,6 +598,12 @@ __cm_main().catch(e=>{console.error(e);process.exitCode=1});${background ? '\nse
585
598
  // Clean the metric line from stderr
586
599
  result.stderr = result.stderr.replace(/\n?__CM_NET__:\d+\n?/g, "");
587
600
  }
601
+ // Parse sandbox FS read metrics from stderr
602
+ const fsMatch = result.stderr?.match(/__CM_FS__:(\d+)/);
603
+ if (fsMatch) {
604
+ sessionStats.bytesSandboxed += parseInt(fsMatch[1]);
605
+ result.stderr = result.stderr.replace(/\n?__CM_FS__:\d+\n?/g, "");
606
+ }
588
607
  if (result.timedOut) {
589
608
  const partialOutput = result.stdout?.trim();
590
609
  if (result.backgrounded && partialOutput) {
@@ -373,9 +373,9 @@ export declare class AnalyticsEngine {
373
373
  queryAll(runtimeStats: RuntimeStats): FullReport;
374
374
  }
375
375
  /**
376
- * Render a FullReport as the same markdown output ctx_stats has always produced.
376
+ * Render a FullReport as a marketing-friendly, outcome-focused session story.
377
377
  *
378
- * Preserves the exact output format: Context Window Protection table,
379
- * TTL Cache section, Session Continuity table, and Analytics JSON block.
378
+ * Framework: Persona -> Metric -> Evidence -> Action -> ROI
379
+ * The output tells a narrative instead of dumping raw numbers.
380
380
  */
381
381
  export declare function formatReport(report: FullReport): string;
@@ -613,62 +613,95 @@ function kb(b) {
613
613
  return `${(b / 1024).toFixed(1)}KB`;
614
614
  }
615
615
  /**
616
- * Render a FullReport as the same markdown output ctx_stats has always produced.
616
+ * Render a FullReport as a marketing-friendly, outcome-focused session story.
617
617
  *
618
- * Preserves the exact output format: Context Window Protection table,
619
- * TTL Cache section, Session Continuity table, and Analytics JSON block.
618
+ * Framework: Persona -> Metric -> Evidence -> Action -> ROI
619
+ * The output tells a narrative instead of dumping raw numbers.
620
620
  */
621
621
  export function formatReport(report) {
622
- const lines = [
623
- `## context-mode \u2014 Session Report (${report.session.uptime_min} min)`,
624
- ];
625
- // ── Feature 1: Context Window Protection ──
626
- lines.push("", `### Context Window Protection`, "");
622
+ const lines = [];
623
+ // ── Hero: headline story ──
624
+ lines.push(`## Think in Code -- Session Report`);
627
625
  if (report.savings.total_calls === 0) {
628
- lines.push(`No context-mode tool calls yet. Use \`batch_execute\`, \`execute\`, or \`fetch_and_index\` to keep raw output out of your context window.`);
629
- }
630
- else {
631
- lines.push(`| Metric | Value |`, `|--------|------:|`, `| Total data processed | **${kb(report.savings.total_processed)}** |`, `| Kept in sandbox (never entered context) | **${kb(report.savings.kept_out)}** |`, `| Entered context | ${kb(report.savings.total_bytes_returned)} |`, `| Estimated tokens saved | ~${Math.round(report.savings.kept_out / 4).toLocaleString()} |`, `| **Context savings** | **${report.savings.savings_ratio.toFixed(1)}x (${report.savings.pct}% reduction)** |`);
632
- // Per-tool breakdown
633
- if (report.savings.by_tool.length > 0) {
634
- lines.push("", `| Tool | Calls | Context | Tokens |`, `|------|------:|--------:|-------:|`);
635
- for (const t of report.savings.by_tool) {
636
- lines.push(`| ${t.tool} | ${t.calls} | ${kb(t.calls > 0 ? (t.tokens * 4) : 0)} | ~${t.tokens.toLocaleString()} |`);
637
- }
638
- lines.push(`| **Total** | **${report.savings.total_calls}** | **${kb(report.savings.total_bytes_returned)}** | **~${Math.round(report.savings.total_bytes_returned / 4).toLocaleString()}** |`);
639
- }
640
- if (report.savings.kept_out > 0) {
641
- lines.push("", `Without context-mode, **${kb(report.savings.total_processed)}** of raw output would flood your context window. Instead, **${report.savings.pct}%** stayed in sandbox.`);
642
- }
643
- // Cache savings section
644
- if (report.cache) {
645
- lines.push("", `### TTL Cache`, "", `| Metric | Value |`, `|--------|------:|`, `| Cache hits | **${report.cache.hits}** |`, `| Data avoided by cache | **${kb(report.cache.bytes_saved)}** |`, `| Network requests saved | **${report.cache.hits}** |`, `| TTL remaining | **~${report.cache.ttl_hours_left}h** |`, "", `Content was already indexed in the knowledge base \u2014 ${report.cache.hits} fetch${report.cache.hits > 1 ? "es" : ""} skipped entirely. **${kb(report.cache.bytes_saved)}** of network I/O avoided. Search results served directly from local FTS5 index.`);
646
- if (report.cache.total_savings_ratio > report.savings.savings_ratio) {
647
- lines.push("", `**Total context savings (sandbox + cache): ${report.cache.total_savings_ratio.toFixed(1)}x** \u2014 ${kb(report.cache.total_with_cache)} processed, only ${kb(report.savings.total_bytes_returned)} entered context.`);
648
- }
626
+ lines.push("", "No sandbox activity yet. Use ctx_execute or ctx_batch_execute to start saving context.", "", "---", "_Display this entire report as-is in your response. Do NOT summarize, collapse, or paraphrase any section._");
627
+ return lines.join("\n");
628
+ }
629
+ const totalProcessed = report.savings.kept_out + (report.cache ? report.cache.bytes_saved : 0);
630
+ const totalReturned = report.savings.total_bytes_returned;
631
+ const grandTotal = totalProcessed + totalReturned;
632
+ const savingsPercent = grandTotal > 0
633
+ ? ((1 - totalReturned / grandTotal) * 100).toFixed(1)
634
+ : "0.0";
635
+ // Rough estimate: 4 bytes per token, ~2 min reading time per 1000 tokens
636
+ const tokensSaved = Math.round(totalProcessed / 4);
637
+ const extraMinutes = Math.round((tokensSaved / 1000) * 2);
638
+ lines.push("", `Your agent processed ${kb(totalProcessed)} of data.`, `Only ${kb(totalReturned)} entered your context window.`, "", `**Context saved: ${savingsPercent}% -- session extended by ~${extraMinutes} minutes**`);
639
+ // ── What happened ──
640
+ lines.push("", "### What happened", "");
641
+ // Count per-tool categories
642
+ const toolCallMap = new Map();
643
+ for (const t of report.savings.by_tool) {
644
+ toolCallMap.set(t.tool, t.calls);
645
+ }
646
+ const executeCount = (toolCallMap.get("ctx_execute") ?? 0) +
647
+ (toolCallMap.get("ctx_execute_file") ?? 0);
648
+ const batchCount = toolCallMap.get("ctx_batch_execute") ?? 0;
649
+ const searchCount = toolCallMap.get("ctx_search") ?? 0;
650
+ const fetchCount = toolCallMap.get("ctx_fetch_and_index") ?? 0;
651
+ const fileCount = executeCount + batchCount;
652
+ const networkCount = fetchCount;
653
+ const cacheCount = report.cache ? report.cache.hits : 0;
654
+ if (fileCount > 0) {
655
+ lines.push(`-> ${fileCount} file${fileCount !== 1 ? "s" : ""} analyzed in sandbox (never entered context)`);
656
+ }
657
+ if (networkCount > 0) {
658
+ lines.push(`-> ${networkCount} API call${networkCount !== 1 ? "s" : ""} sandboxed (responses indexed, not dumped)`);
659
+ }
660
+ if (searchCount > 0) {
661
+ lines.push(`-> ${searchCount} search quer${searchCount !== 1 ? "ies" : "y"} served from index`);
662
+ }
663
+ if (cacheCount > 0) {
664
+ lines.push(`-> ${cacheCount} repeat fetch${cacheCount !== 1 ? "es" : ""} avoided (TTL cache)`);
665
+ }
666
+ // ── Per-tool breakdown ──
667
+ const activatedTools = report.savings.by_tool.filter((t) => t.calls > 0);
668
+ if (activatedTools.length > 0) {
669
+ lines.push("", "### Per-tool breakdown", "", "| Tool | Calls | Data processed | Context used | Saved |", "|------|------:|---------------:|-------------:|------:|");
670
+ for (const t of activatedTools) {
671
+ const processed = t.tokens * 4; // bytes approximation from tokens
672
+ const contextUsed = t.context_kb * 1024;
673
+ const savedPct = processed > 0
674
+ ? (((processed - contextUsed) / processed) *
675
+ 100).toFixed(0)
676
+ : "--";
677
+ lines.push(`| ${t.tool} | ${t.calls} | ${kb(processed)} | ${kb(contextUsed)} | ${savedPct}% |`);
649
678
  }
650
679
  }
651
- // ── Session Continuity ──
680
+ // ── Session continuity ──
652
681
  if (report.continuity.total_events > 0) {
653
- lines.push("", "### Session Continuity", "", "| What's preserved | Count | I remember... | Why it matters |", "|------------------|------:|---------------|----------------|");
654
- for (const row of report.continuity.by_category) {
655
- lines.push(`| ${row.label} | ${row.count} | ${row.preview} | ${row.why} |`);
682
+ lines.push("", "### Session continuity", "");
683
+ const parts = [];
684
+ if (report.continuity.compact_count > 0) {
685
+ parts.push(`${report.continuity.compact_count} compaction${report.continuity.compact_count !== 1 ? "s" : ""}`);
686
+ }
687
+ parts.push(`${report.continuity.total_events} event${report.continuity.total_events !== 1 ? "s" : ""} preserved`);
688
+ // Count tasks from continuity categories
689
+ const taskRow = report.continuity.by_category.find((c) => c.category === "task" || c.category === "tasks");
690
+ if (taskRow && taskRow.count > 0) {
691
+ parts.push(`${taskRow.count} task${taskRow.count !== 1 ? "s" : ""} tracked`);
656
692
  }
657
- lines.push(`| **Total** | **${report.continuity.total_events}** | | **Zero knowledge lost on compact** |`);
658
- lines.push("");
693
+ lines.push(parts.join(" | "));
659
694
  if (report.continuity.compact_count > 0) {
660
- lines.push(`Context has been compacted **${report.continuity.compact_count} time(s)** \u2014 session knowledge was preserved each time.`);
695
+ lines.push("", `Session knowledge preserved across ${report.continuity.compact_count} compaction${report.continuity.compact_count !== 1 ? "s" : ""} -- zero context lost.`);
661
696
  }
662
697
  else {
663
- lines.push(`When your context compacts, all of this will restore Claude's awareness \u2014 no starting from scratch.`);
698
+ lines.push("", "When your context compacts, all of this will restore awareness -- no starting from scratch.");
664
699
  }
665
700
  if (report.continuity.resume_ready) {
666
- lines.push(`Resume snapshot ready for the next compaction.`);
701
+ lines.push("Resume snapshot ready for the next compaction.");
667
702
  }
668
- lines.push("");
669
- lines.push(`> **Note:** Previous session data is loaded when you start a new session. Without \`--continue\`, old session history is cleaned up to keep the database lean.`);
670
703
  }
671
- // ── Analytics JSON ──
704
+ // ── Analytics JSON (for power users) ──
672
705
  const analyticsJson = {
673
706
  session: {
674
707
  duration_min: report.session.duration_min,
@@ -702,7 +735,7 @@ export function formatReport(report) {
702
735
  continuity: { total_events: report.continuity.total_events },
703
736
  };
704
737
  lines.push("", "### Analytics (27 metrics)", "", "```json", JSON.stringify(analyticsJson, null, 2), "```");
705
- // Formatting directive
706
- lines.push("", "---", "_Display this entire report as-is in your response. Do NOT summarize, collapse, or paraphrase any section._");
738
+ // Footer
739
+ lines.push("", "---", "context-mode -- github.com/mksglu/context-mode", "", "_Display this entire report as-is in your response. Do NOT summarize, collapse, or paraphrase any section._");
707
740
  return lines.join("\n");
708
741
  }