pi-context-map 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0] - 2026-06-15
4
+ ### Features
5
+ - **Dark mode toggle**: Light/dark theme switcher in the report header. Preference saved to localStorage and restored on next load. Theme persists across SSE live updates.
6
+ - **Dynamic context window**: Replaced hardcoded 128k with actual context window size from Pi system via `ctx.getContextUsage()`. Now accurately reflects your model's real context limit (200k, 128k, etc.).
7
+
3
8
  ## [0.4.4] - 2026-06-15
4
9
  ### Bug Fixes
5
10
  - **Fixed SSE rendering**: Changed `document.replaceChild(document.documentElement)` to `document.body.innerHTML` replacement. CSS and JavaScript no longer render as visible text on the page.
@@ -14,9 +14,10 @@ export class ReportGenerator {
14
14
  public static generateHTML(
15
15
  composition: ContextComposition,
16
16
  insights: Insight[],
17
+ contextWindow: number = 128_000,
17
18
  ): string {
18
19
  const total = composition.total.tokens;
19
- const usagePercent = total > 0 ? Math.round((total / 128_000) * 100) : 0;
20
+ const usagePercent = total > 0 ? Math.round((total / contextWindow) * 100) : 0;
20
21
 
21
22
  const fileCards = composition.files_detail
22
23
  .map(
@@ -90,6 +91,26 @@ export class ReportGenerator {
90
91
  --seg-files: #007aff;
91
92
  --seg-summaries: #34c759;
92
93
  }
94
+ [data-theme="dark"] {
95
+ --canvas: #0a0a0b;
96
+ --canvas-alt: #141415;
97
+ --surface: #1a1a1c;
98
+ --hairline: #2c2c2e;
99
+ --hairline-soft: rgba(255,255,255,0.06);
100
+ --ink: #f5f5f7;
101
+ --ink-secondary: #a1a1a6;
102
+ --ink-tertiary: #6e6e73;
103
+ --ink-quaternary: #48484a;
104
+ --accent: #2997ff;
105
+ --accent-hover: #40a9ff;
106
+ --accent-soft: rgba(41,151,255,0.12);
107
+ --success: #30d158;
108
+ --success-soft: rgba(48,209,88,0.12);
109
+ --warning: #ff9f0a;
110
+ --warning-soft: rgba(255,159,10,0.12);
111
+ --danger: #ff453a;
112
+ --danger-soft: rgba(255,69,58,0.12);
113
+ }
93
114
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
94
115
  body {
95
116
  background: var(--canvas);
@@ -482,8 +503,15 @@ h2:first-of-type { margin-top: 48px; }
482
503
  <div class="container">
483
504
 
484
505
  <header>
485
- <div class="live-badge"><span class="dot"></span>Live</div>
486
- <h1>Context Profiler</h1>
506
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:20px;flex-wrap:wrap;">
507
+ <div class="live-badge"><span class="dot"></span>Live</div>
508
+ <button id="themeToggle" style="display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border:1px solid var(--hairline);border-radius:20px;background:var(--surface);color:var(--ink-secondary);font:inherit;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s;white-space:nowrap;" aria-label="Toggle theme">
509
+ <svg id="themeIconSun" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none;"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
510
+ <svg id="themeIconMoon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
511
+ <span id="themeLabel">Dark</span>
512
+ </button>
513
+ </div>
514
+ <h1>Context Profiler</h1>
487
515
  <p class="subtitle">Session context window breakdown with actionable recommendations</p>
488
516
 
489
517
  <div class="stats">
@@ -500,8 +528,8 @@ h2:first-of-type { margin-top: 48px; }
500
528
  <span class="stat-label">Alerts</span>
501
529
  </div>
502
530
  <div class="stat">
503
- <span class="stat-value">${usagePercent}<span style="font-size:14px;color:var(--ink-tertiary)">%</span></span>
504
- <span class="stat-label">of 128k Window</span>
531
+ <span class="stat-value">${(contextWindow / 1000).toFixed(0)}k</span>
532
+ <span class="stat-label">Context Window</span>
505
533
  </div>
506
534
  </div>
507
535
 
@@ -515,7 +543,7 @@ h2:first-of-type { margin-top: 48px; }
515
543
  </svg>
516
544
  </div>
517
545
  <div class="usage-label">
518
- ${usagePercent}% of 128k window
546
+ ${usagePercent}% of ${(contextWindow / 1000).toFixed(0)}k window
519
547
  <small>${usagePercent > 80 ? "Compaction recommended" : usagePercent > 60 ? "Monitor usage" : "Healthy"}</small>
520
548
  </div>
521
549
  </div>
@@ -594,6 +622,25 @@ h2:first-of-type { margin-top: 48px; }
594
622
  if (filter) filter.addEventListener('change', update);
595
623
  update();
596
624
 
625
+ // Theme toggle
626
+ var toggle = document.getElementById('themeToggle');
627
+ var label = document.getElementById('themeLabel');
628
+ var sunIcon = document.getElementById('themeIconSun');
629
+ var moonIcon = document.getElementById('themeIconMoon');
630
+ function applyTheme(t) {
631
+ document.documentElement.setAttribute('data-theme', t);
632
+ localStorage.setItem('context-map-theme', t);
633
+ if (label) label.textContent = t === 'dark' ? 'Light' : 'Dark';
634
+ if (sunIcon) sunIcon.style.display = t === 'dark' ? '' : 'none';
635
+ if (moonIcon) moonIcon.style.display = t === 'dark' ? 'none' : '';
636
+ }
637
+ var saved = localStorage.getItem('context-map-theme');
638
+ applyTheme(saved || 'light');
639
+ if (toggle) toggle.addEventListener('click', function() {
640
+ var cur = document.documentElement.getAttribute('data-theme');
641
+ applyTheme(cur === 'dark' ? 'light' : 'dark');
642
+ });
643
+
597
644
  // Insight toggles
598
645
  var btns = document.querySelectorAll('.insight-header');
599
646
  for (var j = 0; j < btns.length; j++) {
@@ -612,6 +659,8 @@ h2:first-of-type { margin-top: 48px; }
612
659
  try {
613
660
  var p = JSON.parse(e.data);
614
661
  if (p.html) {
662
+ // Preserve current theme before replacing body
663
+ var currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
615
664
  // Only replace the body content to preserve <style> and <script>
616
665
  var d = new DOMParser().parseFromString(p.html, 'text/html');
617
666
  var newBody = d.querySelector('body');
@@ -622,6 +671,25 @@ h2:first-of-type { margin-top: 48px; }
622
671
  if (newTitle) {
623
672
  document.title = newTitle.textContent || document.title;
624
673
  }
674
+ // Restore theme after body replacement
675
+ applyTheme(currentTheme);
676
+ // Re-bind new theme toggle button
677
+ var newToggle = document.getElementById('themeToggle');
678
+ if (newToggle) {
679
+ newToggle.addEventListener('click', function() {
680
+ var cur = document.documentElement.getAttribute('data-theme');
681
+ applyTheme(cur === 'dark' ? 'light' : 'dark');
682
+ });
683
+ }
684
+ // Re-bind new file search/filter
685
+ var ns = document.getElementById('fileSearch');
686
+ var nf = document.getElementById('fileFilter');
687
+ if (ns) ns.addEventListener('input', update);
688
+ if (nf) nf.addEventListener('change', update);
689
+ // Re-query cards after body replacement
690
+ cards = Array.from(document.getElementById('fileGrid').querySelectorAll('.file-card'));
691
+ total = cards.length;
692
+ update();
625
693
  }
626
694
  } catch(_) {}
627
695
  };
@@ -31,12 +31,22 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
31
31
  // session messages in Pi. (pi as any).session?.messages does NOT exist.
32
32
  let sessionMessages: AgentMessage[] = [];
33
33
  let currentTurn = 0;
34
+ let contextWindow = 128_000; // fallback, updated from Pi system
34
35
 
35
- // Capture messages before each LLM call
36
- pi.on("context", (event: any) => {
36
+ // Capture messages and context info before each LLM call
37
+ pi.on("context", (event: any, ctx: any) => {
37
38
  if (event?.messages && Array.isArray(event.messages)) {
38
39
  sessionMessages = event.messages;
39
40
  }
41
+ // Fetch actual context window from Pi system
42
+ try {
43
+ const usage = ctx?.getContextUsage?.();
44
+ if (usage?.contextWindow && usage.contextWindow > 0) {
45
+ contextWindow = usage.contextWindow;
46
+ }
47
+ } catch {
48
+ // Ignore — keep fallback
49
+ }
40
50
  });
41
51
 
42
52
  // Track turns
@@ -52,7 +62,11 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
52
62
  const messages = sessionMessages.length > 0 ? sessionMessages : [];
53
63
  const composition = analyzer.analyzeByType(messages, currentTurn);
54
64
  const insights = InsightEngine.generate(composition);
55
- const html = ReportGenerator.generateHTML(composition, insights);
65
+ const html = ReportGenerator.generateHTML(
66
+ composition,
67
+ insights,
68
+ contextWindow,
69
+ );
56
70
 
57
71
  // Write to disk
58
72
  try {
@@ -135,14 +149,14 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
135
149
  const { composition, insights } = await runAnalysis();
136
150
  const usagePercent =
137
151
  composition.total.tokens > 0
138
- ? Math.round((composition.total.tokens / 128_000) * 100)
152
+ ? Math.round((composition.total.tokens / contextWindow) * 100)
139
153
  : 0;
140
154
  const summary =
141
155
  `Context: ${composition.total.tokens.toLocaleString()} tokens total. ` +
142
156
  `System ${composition.system.percent}%, Tools ${composition.tools.percent}%, ` +
143
157
  `History ${composition.history.percent}%, Files ${composition.files.percent}%, ` +
144
158
  `Summaries ${composition.summaries.percent}%. ` +
145
- `Usage: ${usagePercent}% of typical 128k window. ` +
159
+ `Usage: ${usagePercent}% of ${(contextWindow / 1000).toFixed(0)}k window. ` +
146
160
  `${insights.length} insight(s) generated.`;
147
161
  return {
148
162
  type: "text" as const,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-context-map",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
4
4
  "description": "Professional context profiler for Pi that visualizes the session context window, token distribution, and integrates with Nexus packages for actionable insights.",
5
5
  "keywords": [
6
6
  "pi-package",