pi-context-map 0.6.2 → 0.7.1

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.1] - 2026-06-15
4
+ ### Bug Fixes
5
+ - **Fixed insights dropdown**: Changed from direct `addEventListener` to event delegation (`document.addEventListener('click', ...)` with `e.target.closest('.insight-header')`). The dropdown now works after SSE body replacement.
6
+ - **Fixed auto-open browser**: Added `cmd /c start` with `explorer` fallback for Windows. Browser now opens reliably on first `/context-map` invocation.
7
+ - **Removed unused variable**: Cleaned up `reportPath` destructuring warning in command handler.
8
+
9
+ ## [0.7.0] - 2026-06-15
10
+ ### Bug Fixes
11
+ - **Fixed token accuracy**: Now uses Pi's actual token count from `ctx.getContextUsage()` instead of heuristic estimation. The usage percentage now matches Pi's terminal display.
12
+ - **Fixed compaction summary detection**: Now detects `role: "compactionSummary"` (Pi's actual format) with the `summary` field. Summaries no longer show 0%.
13
+ - **Fixed file status calculation**: Changed from turn-based to position-based. Files in the last 30% of messages are "active", middle 40% are "stale", first 30% are "legacy".
14
+ - **Removed error spam**: Silent error handling instead of console.error for non-critical failures.
15
+
16
+ ### Features
17
+ - **Auto-open browser**: Report automatically opens in default browser on first `/context-map` invocation.
18
+ - **Pi actual tokens in HTML**: Generator accepts and displays Pi's real token count.
19
+
3
20
  ## [0.6.2] - 2026-06-15
4
21
  ### Bug Fixes
5
22
  - **Fixed Pi message format**: Now uses `type: "toolCall"` (not `"tool_use"`) and `toolCallId` (not `tool_call_id`) to match Pi's actual `@mariozechner/pi-ai` types.
package/README.md CHANGED
@@ -8,11 +8,15 @@ A visual context window mapping extension for [Pi](https://pi.dev/) that transfo
8
8
 
9
9
  ## Features
10
10
 
11
- - **Visual Context Budget**: Real-time breakdown of tokens used by System, History, Files, and Tool Results.
12
- - **Working Set Analysis**: Categorizes files as `Active`, `Stale`, or `Legacy` based on access recency.
11
+ - **Visual Context Budget**: Real-time breakdown of tokens used by System, Tools, History, Files, and Summaries.
12
+ - **Accurate Token Count**: Uses Pi's actual token count from `ctx.getContextUsage()`, not heuristic estimation matches the terminal display.
13
+ - **Working Set Analysis**: Categorizes files as `Active`, `Stale`, or `Legacy` based on position in the conversation.
13
14
  - **Token Weighting**: Identifies "token hogs" by calculating the approximate size of each file in the window.
14
- - **Operation Tracking**: Marks files with their last operation (Read 👁️, Write 📝, Edit ✍️).
15
- - **Temporal Mapping**: Visually maps when files entered the context to identify compaction candidates.
15
+ - **Operation Tracking**: Marks files with their last operation (Read, Write, Edit).
16
+ - **Compaction Detection**: Tracks compaction summaries and branch summaries as a separate slice.
17
+ - **Auto-Open Browser**: Report automatically opens in your default browser on first invocation.
18
+ - **Dark Mode**: Toggle between light and dark themes. Preference persists across sessions via localStorage.
19
+ - **Live Server**: SSE-powered localhost server with auto-refresh after each assistant message.
16
20
 
17
21
  ## Installation
18
22
 
@@ -33,20 +37,21 @@ The extension will analyze the session and create an interactive HTML report at:
33
37
 
34
38
  ## Context Statuses
35
39
 
36
- The extension categorizes files to help you manage context bloat:
40
+ Files are categorized by their position in the conversation (more reliable than turn-based calculation):
37
41
 
38
- | Status | Criteria | Action |
39
- |--------|----------|--------|
40
- | **Active** | Accessed in last 3 turns | Keep in context |
41
- | **Stale** | Accessed 4-10 turns ago | Monitor for removal |
42
- | **Legacy** | Accessed > 10 turns ago | Prime candidate for compaction |
42
+ | Status | Position in Messages | Action |
43
+ |--------|---------------------|--------|
44
+ | **Active** | Last 30% of messages | Keep in context |
45
+ | **Stale** | Middle 40% of messages | Monitor for removal |
46
+ | **Legacy** | First 30% of messages | Prime candidate for compaction |
43
47
 
44
48
  ## How It Works
45
49
 
46
- 1. **Scanning**: The analyzer iterates through the session history, identifying all `tool_use` calls involving file operations.
47
- 2. **Weighting**: It extracts the content length of tool results and applies a token heuristic (approx. 4 chars/token).
48
- 3. **Categorization**: It calculates the temporal distance between the current turn and the last file access.
49
- 4. **Visualization**: It generates a standalone HTML dashboard featuring a stacked composition bar, a file-weight grid with search/filter, and an interactive insights section.
50
+ 1. **Scanning**: The analyzer iterates through session messages, detecting `toolCall` blocks, `toolResult` messages, `compactionSummary` entries, and image attachments.
51
+ 2. **Weighting**: It calculates token counts for each message type using a code-aware heuristic (multipliers for code blocks, strings, etc.).
52
+ 3. **Accuracy**: When available, Pi's actual token count from `ctx.getContextUsage()` overrides the heuristic for the usage percentage.
53
+ 4. **Categorization**: Files are classified by their position in the message array (last 30% = active, middle 40% = stale, first 30% = legacy).
54
+ 5. **Visualization**: Generates a self-contained HTML dashboard with stacked composition bar, file cards with search/filter, dark mode toggle, and interactive insights.
50
55
 
51
56
  ## Live Localhost Server
52
57
 
@@ -3,13 +3,16 @@
3
3
  * Parses Pi session messages to identify the active working set of files,
4
4
  * their token weights, and their temporal status.
5
5
  *
6
- * Pi message format (from @mariozechner/pi-ai):
7
- * UserMessage: { role: "user", content: string | (TextContent | ImageContent)[] }
8
- * AssistantMessage: { role: "assistant", content: (TextContent | ThinkingContent | ToolCall)[] }
9
- * ToolResultMessage: { role: "toolResult", toolCallId, toolName, content: (TextContent | ImageContent)[] }
6
+ * Pi message format (from @mariozechner/pi-ai + pi-coding-agent):
7
+ * UserMessage: { role: "user", content: string | (TextContent | ImageContent)[] }
8
+ * AssistantMessage: { role: "assistant", content: (TextContent | ThinkingContent | ToolCall)[] }
9
+ * ToolResultMessage: { role: "toolResult", toolCallId, toolName, content }
10
+ * CompactionSummaryMessage:{ role: "compactionSummary", summary: string, tokensBefore: number }
11
+ * BranchSummaryMessage: { role: "branchSummary", summary: string }
12
+ * BashExecutionMessage: { role: "bashExecution", command, output }
13
+ * CustomMessage: { role: "custom", customType, content }
10
14
  *
11
15
  * ToolCall: { type: "toolCall", id, name, arguments }
12
- * ToolCall.id maps to ToolResultMessage.toolCallId
13
16
  */
14
17
  import { TokenCounter } from "./token-counter";
15
18
 
@@ -39,6 +42,9 @@ export interface ContextComposition {
39
42
  summaries: ContextSlice;
40
43
  total: ContextSlice;
41
44
  files_detail: FileContext[];
45
+ /** Pi's actual token count from ctx.getContextUsage() — may differ from heuristic total */
46
+ actualTokens?: number | null;
47
+ actualPercent?: number | null;
42
48
  }
43
49
 
44
50
  export class ContextAnalyzer {
@@ -60,23 +66,65 @@ export class ContextAnalyzer {
60
66
  systemTokens += TokenCounter.count(systemPrompt);
61
67
  }
62
68
 
69
+ // Track message indices for status calculation
70
+ const totalMessages = messages.length;
71
+
63
72
  for (let index = 0; index < messages.length; index++) {
64
73
  const msg = messages[index];
65
74
  const turn = index + 1;
75
+
76
+ // Normalize role — Pi may use different role strings
66
77
  const role = msg.role || "";
67
78
 
68
- // 1. Compaction summaries (Pi compaction entries)
79
+ // 1. Compaction summaries (Pi uses role="compactionSummary" with summary field)
69
80
  if (
81
+ role === "compactionSummary" ||
70
82
  role === "compaction" ||
71
83
  msg.type === "compaction" ||
72
84
  msg.customType === "compaction" ||
73
85
  msg.compactionEntry
74
86
  ) {
75
- summaryTokens += TokenCounter.countMessage(msg);
87
+ // Use the summary field if available, otherwise fall back to content
88
+ const summaryText =
89
+ typeof msg.summary === "string"
90
+ ? msg.summary
91
+ : typeof msg.content === "string"
92
+ ? msg.content
93
+ : JSON.stringify(msg.content || msg);
94
+ summaryTokens += TokenCounter.count(summaryText);
95
+ continue;
96
+ }
97
+
98
+ // 2. Branch summaries
99
+ if (role === "branchSummary") {
100
+ const summaryText =
101
+ typeof msg.summary === "string" ? msg.summary : JSON.stringify(msg);
102
+ summaryTokens += TokenCounter.count(summaryText);
103
+ continue;
104
+ }
105
+
106
+ // 3. Bash executions
107
+ if (role === "bashExecution") {
108
+ toolTokens += TokenCounter.countMessage(msg);
109
+ continue;
110
+ }
111
+
112
+ // 4. Custom messages (extensions)
113
+ if (role === "custom") {
114
+ // Categorize based on customType
115
+ const customType = msg.customType || "";
116
+ if (
117
+ customType.includes("compaction") ||
118
+ customType.includes("summary")
119
+ ) {
120
+ summaryTokens += TokenCounter.countMessage(msg);
121
+ } else {
122
+ historyTokens += TokenCounter.countMessage(msg);
123
+ }
76
124
  continue;
77
125
  }
78
126
 
79
- // 2. Tool results (Pi uses role="toolResult")
127
+ // 5. Tool results (Pi uses role="toolResult")
80
128
  if (role === "toolResult") {
81
129
  toolTokens += TokenCounter.countMessage(msg);
82
130
  // Track file content from tool results
@@ -100,7 +148,7 @@ export class ContextAnalyzer {
100
148
  turn,
101
149
  timestamp: msg.timestamp || Date.now(),
102
150
  },
103
- status: this.calculateStatus(turn, currentTurn),
151
+ status: this.calculateStatus(index, totalMessages),
104
152
  });
105
153
  }
106
154
  }
@@ -108,7 +156,7 @@ export class ContextAnalyzer {
108
156
  continue;
109
157
  }
110
158
 
111
- // 3. User messages
159
+ // 6. User messages
112
160
  if (role === "user") {
113
161
  historyTokens += TokenCounter.countMessage(msg);
114
162
  // Track file attachments (images, file paths in text)
@@ -127,7 +175,7 @@ export class ContextAnalyzer {
127
175
  turn,
128
176
  timestamp: msg.timestamp || Date.now(),
129
177
  },
130
- status: this.calculateStatus(turn, currentTurn),
178
+ status: this.calculateStatus(index, totalMessages),
131
179
  });
132
180
  }
133
181
  }
@@ -146,7 +194,7 @@ export class ContextAnalyzer {
146
194
  turn,
147
195
  timestamp: msg.timestamp || Date.now(),
148
196
  },
149
- status: this.calculateStatus(turn, currentTurn),
197
+ status: this.calculateStatus(index, totalMessages),
150
198
  });
151
199
  }
152
200
  }
@@ -157,7 +205,7 @@ export class ContextAnalyzer {
157
205
  continue;
158
206
  }
159
207
 
160
- // 4. Assistant messages — track toolCall blocks
208
+ // 7. Assistant messages — track toolCall blocks
161
209
  if (role === "assistant") {
162
210
  historyTokens += TokenCounter.countMessage(msg);
163
211
  if (Array.isArray(msg.content)) {
@@ -179,7 +227,7 @@ export class ContextAnalyzer {
179
227
  turn,
180
228
  timestamp: msg.timestamp || Date.now(),
181
229
  },
182
- status: this.calculateStatus(turn, currentTurn),
230
+ status: this.calculateStatus(index, totalMessages),
183
231
  });
184
232
  }
185
233
  }
@@ -188,7 +236,7 @@ export class ContextAnalyzer {
188
236
  continue;
189
237
  }
190
238
 
191
- // 5. Everything else
239
+ // 8. Everything else
192
240
  historyTokens += TokenCounter.countMessage(msg);
193
241
  }
194
242
 
@@ -260,13 +308,20 @@ export class ContextAnalyzer {
260
308
  }
261
309
  }
262
310
 
311
+ /**
312
+ * Calculate file status based on position in message array.
313
+ * Files near the end are "active", middle are "stale", beginning are "legacy".
314
+ * This is more reliable than turn-based calculation since the context event
315
+ * replaces all messages at once.
316
+ */
263
317
  private calculateStatus(
264
- turn: number,
265
- currentTurn: number,
318
+ messageIndex: number,
319
+ totalMessages: number,
266
320
  ): FileContext["status"] {
267
- const diff = currentTurn - turn;
268
- if (diff <= 3) return "active";
269
- if (diff <= 10) return "stale";
321
+ if (totalMessages === 0) return "legacy";
322
+ const ratio = messageIndex / totalMessages;
323
+ if (ratio >= 0.7) return "active";
324
+ if (ratio >= 0.3) return "stale";
270
325
  return "legacy";
271
326
  }
272
327
 
@@ -15,8 +15,13 @@ export class ReportGenerator {
15
15
  composition: ContextComposition,
16
16
  insights: Insight[],
17
17
  contextWindow: number = 128_000,
18
+ actualTokens?: number | null,
18
19
  ): string {
19
- const total = composition.total.tokens;
20
+ // Use Pi's actual token count when available
21
+ const total =
22
+ actualTokens != null && actualTokens > 0
23
+ ? actualTokens
24
+ : composition.total.tokens;
20
25
  const usagePercent =
21
26
  total > 0 ? Math.round((total / contextWindow) * 100) : 0;
22
27
 
@@ -643,15 +648,15 @@ h2:first-of-type { margin-top: 48px; }
643
648
  applyTheme(cur === 'dark' ? 'light' : 'dark');
644
649
  });
645
650
 
646
- // Insight toggles
647
- var btns = document.querySelectorAll('.insight-header');
648
- for (var j = 0; j < btns.length; j++) {
649
- btns[j].addEventListener('click', function() {
650
- var card = this.closest('.insight-card');
651
- var was = card.classList.toggle('collapsed');
652
- this.setAttribute('aria-expanded', was ? 'false' : 'true');
653
- });
654
- }
651
+ // Insight toggles (event delegation — survives SSE body replacement)
652
+ document.addEventListener('click', function(e) {
653
+ var btn = e.target.closest('.insight-header');
654
+ if (!btn) return;
655
+ var card = btn.closest('.insight-card');
656
+ if (!card) return;
657
+ var was = card.classList.toggle('collapsed');
658
+ btn.setAttribute('aria-expanded', was ? 'false' : 'true');
659
+ });
655
660
 
656
661
  // Live SSE
657
662
  try {
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * pi-context-map
3
3
  * Professional Context Profiler for Pi.
4
- * v0.6.2 — Fixed Pi message format (toolCall), system prompt detection, message persistence.
4
+ * v0.7.0 — Fixed token accuracy (uses Pi's actual count), compactionSummary detection,
5
+ * auto-open browser, position-based file status, error cleanup.
5
6
  */
6
7
 
7
8
  import type {
@@ -16,6 +17,7 @@ import { LiveReportServer } from "./live-server";
16
17
  import * as path from "node:path";
17
18
  import * as fs from "node:fs";
18
19
  import * as os from "node:os";
20
+ import { exec } from "node:child_process";
19
21
 
20
22
  function makeReportPath(sessionName?: string): string {
21
23
  const dir = path.join(os.homedir(), ".pi", "context-map");
@@ -30,6 +32,27 @@ function makeReportPath(sessionName?: string): string {
30
32
  return path.join(dir, filename);
31
33
  }
32
34
 
35
+ function openBrowser(url: string): void {
36
+ const platform = process.platform;
37
+ try {
38
+ if (platform === "win32") {
39
+ // Use cmd /c start with separate title arg to handle URLs with special chars
40
+ exec(`cmd /c start "" "${url}"`, (err) => {
41
+ if (err) {
42
+ // Fallback: try explorer directly
43
+ exec(`explorer "${url}"`);
44
+ }
45
+ });
46
+ } else if (platform === "darwin") {
47
+ exec(`open "${url}"`);
48
+ } else {
49
+ exec(`xdg-open "${url}"`);
50
+ }
51
+ } catch {
52
+ // Silent — browser open is best-effort
53
+ }
54
+ }
55
+
33
56
  export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
34
57
  const analyzer = new ContextAnalyzer();
35
58
  const liveServer = new LiveReportServer();
@@ -37,18 +60,30 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
37
60
  let sessionMessages: AgentMessage[] = [];
38
61
  let currentTurn = 0;
39
62
  let contextWindow = 128_000;
63
+ let actualTokens: number | null = null;
64
+ let actualPercent: number | null = null;
40
65
  let systemPrompt = "";
41
66
  let currentReportPath = makeReportPath();
67
+ let isFirstRun = true;
42
68
 
43
- // Capture messages, context window, and system prompt from Pi system
69
+ // Capture messages, context window, system prompt, and actual token count from Pi
44
70
  pi.on("context", (event: any, ctx: any) => {
45
71
  if (event?.messages && Array.isArray(event.messages)) {
46
72
  sessionMessages = event.messages;
47
73
  }
48
74
  try {
49
75
  const usage = ctx?.getContextUsage?.();
50
- if (usage?.contextWindow && usage.contextWindow > 0) {
51
- contextWindow = usage.contextWindow;
76
+ if (usage) {
77
+ if (usage.contextWindow && usage.contextWindow > 0) {
78
+ contextWindow = usage.contextWindow;
79
+ }
80
+ // Use Pi's actual token count — this is the real value
81
+ if (usage.tokens != null && usage.tokens > 0) {
82
+ actualTokens = usage.tokens;
83
+ }
84
+ if (usage.percent != null && usage.percent > 0) {
85
+ actualPercent = usage.percent;
86
+ }
52
87
  }
53
88
  } catch {
54
89
  // Keep fallback
@@ -71,6 +106,7 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
71
106
  // Update report path when session changes
72
107
  pi.on("session_start", () => {
73
108
  currentReportPath = makeReportPath();
109
+ isFirstRun = true;
74
110
  });
75
111
 
76
112
  // Persist messages on compaction so they survive reload
@@ -78,7 +114,7 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
78
114
  if (event?.compactionEntry) {
79
115
  try {
80
116
  pi.appendEntry("context-map-snapshot", {
81
- messages: sessionMessages.slice(-50), // Keep last 50 messages
117
+ messages: sessionMessages.slice(-50),
82
118
  turn: currentTurn,
83
119
  timestamp: Date.now(),
84
120
  });
@@ -99,11 +135,40 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
99
135
  currentTurn,
100
136
  systemPrompt,
101
137
  );
138
+
139
+ // Override with Pi's actual token count when available
140
+ if (actualTokens != null && actualTokens > 0) {
141
+ composition.actualTokens = actualTokens;
142
+ composition.actualPercent = actualPercent;
143
+ // Recalculate percentages relative to actual total
144
+ const total = actualTokens;
145
+ if (total > 0) {
146
+ composition.system.percent = Math.round(
147
+ (composition.system.tokens / total) * 100,
148
+ );
149
+ composition.tools.percent = Math.round(
150
+ (composition.tools.tokens / total) * 100,
151
+ );
152
+ composition.history.percent = Math.round(
153
+ (composition.history.tokens / total) * 100,
154
+ );
155
+ composition.files.percent = Math.round(
156
+ (composition.files.tokens / total) * 100,
157
+ );
158
+ composition.summaries.percent = Math.round(
159
+ (composition.summaries.tokens / total) * 100,
160
+ );
161
+ // Use Pi's actual total for the usage calculation
162
+ composition.total.tokens = total;
163
+ }
164
+ }
165
+
102
166
  const insights = InsightEngine.generate(composition);
103
167
  const html = ReportGenerator.generateHTML(
104
168
  composition,
105
169
  insights,
106
170
  contextWindow,
171
+ actualTokens,
107
172
  );
108
173
 
109
174
  try {
@@ -113,7 +178,7 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
113
178
  }
114
179
  fs.writeFileSync(currentReportPath, html, "utf8");
115
180
  } catch (err: any) {
116
- console.error(`[pi-context-map] Failed to write report: ${err.message}`);
181
+ // Silent don't spam console
117
182
  }
118
183
 
119
184
  if (liveServer.isRunning) {
@@ -137,7 +202,7 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
137
202
 
138
203
  ctx.ui.notify("Analyzing session context...", "info");
139
204
  try {
140
- const { composition, insights, reportPath } = await runAnalysis();
205
+ const { composition, insights } = await runAnalysis();
141
206
  const criticalCount = insights.filter(
142
207
  (i) => i.severity === "critical",
143
208
  ).length;
@@ -145,17 +210,27 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
145
210
  criticalCount > 0
146
211
  ? `Context map generated. ${criticalCount} critical insight(s) found.`
147
212
  : "Context map generated successfully.";
148
- let details = `File: ${reportPath}`;
213
+
214
+ // Use Pi's actual percentage when available
215
+ const usageDisplay =
216
+ actualPercent != null
217
+ ? `${actualPercent.toFixed(1)}%`
218
+ : `${composition.total.percent}%`;
219
+
220
+ let details = `Usage: ${usageDisplay} of ${(contextWindow / 1000).toFixed(0)}k`;
149
221
  if (serverUrl) {
150
- details += ` | Live: ${serverUrl}`;
222
+ details += ` | ${serverUrl}`;
151
223
  }
152
- details += ` | Messages: ${sessionMessages.length}`;
153
- details += ` | System: ${composition.system.tokens}t (${composition.system.percent}%)`;
154
- details += ` | Tools: ${composition.tools.tokens}t (${composition.tools.percent}%)`;
155
224
  ctx.ui.notify(
156
225
  `${summary} ${details}`,
157
226
  criticalCount > 0 ? "warning" : "success",
158
227
  );
228
+
229
+ // Auto-open browser on first run
230
+ if (isFirstRun && serverUrl) {
231
+ openBrowser(serverUrl);
232
+ isFirstRun = false;
233
+ }
159
234
  } catch (error: any) {
160
235
  ctx.ui.notify(
161
236
  `Failed to generate context map: ${error.message}`,
@@ -183,15 +258,16 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
183
258
  try {
184
259
  const { composition, insights, reportPath } = await runAnalysis();
185
260
  const usagePercent =
186
- composition.total.tokens > 0
187
- ? Math.round((composition.total.tokens / contextWindow) * 100)
188
- : 0;
261
+ actualPercent != null
262
+ ? actualPercent
263
+ : composition.total.tokens > 0
264
+ ? Math.round((composition.total.tokens / contextWindow) * 100)
265
+ : 0;
189
266
  const summary =
190
- `Context: ${composition.total.tokens.toLocaleString()} tokens total. ` +
267
+ `Context: ${composition.total.tokens.toLocaleString()} tokens (${usagePercent.toFixed(1)}% of ${(contextWindow / 1000).toFixed(0)}k). ` +
191
268
  `System ${composition.system.percent}%, Tools ${composition.tools.percent}%, ` +
192
269
  `History ${composition.history.percent}%, Files ${composition.files.percent}%, ` +
193
270
  `Summaries ${composition.summaries.percent}%. ` +
194
- `Usage: ${usagePercent}% of ${(contextWindow / 1000).toFixed(0)}k window. ` +
195
271
  `Messages: ${sessionMessages.length}. ` +
196
272
  `${insights.length} insight(s).`;
197
273
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-context-map",
3
- "version": "0.6.2",
3
+ "version": "0.7.1",
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",