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 +17 -0
- package/README.md +19 -14
- package/extensions/analyzer.ts +75 -20
- package/extensions/generator.ts +15 -10
- package/extensions/index.ts +93 -17
- package/package.json +1 -1
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
|
|
12
|
-
- **
|
|
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
|
|
15
|
-
- **
|
|
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
|
-
|
|
40
|
+
Files are categorized by their position in the conversation (more reliable than turn-based calculation):
|
|
37
41
|
|
|
38
|
-
| Status |
|
|
39
|
-
|
|
40
|
-
| **Active** |
|
|
41
|
-
| **Stale** |
|
|
42
|
-
| **Legacy** |
|
|
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
|
|
47
|
-
2. **Weighting**: It
|
|
48
|
-
3. **
|
|
49
|
-
4. **
|
|
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
|
|
package/extensions/analyzer.ts
CHANGED
|
@@ -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:
|
|
8
|
-
* AssistantMessage:
|
|
9
|
-
* ToolResultMessage:
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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(
|
|
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
|
-
//
|
|
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(
|
|
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(
|
|
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
|
-
//
|
|
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(
|
|
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
|
-
//
|
|
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
|
-
|
|
265
|
-
|
|
318
|
+
messageIndex: number,
|
|
319
|
+
totalMessages: number,
|
|
266
320
|
): FileContext["status"] {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (
|
|
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
|
|
package/extensions/generator.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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 {
|
package/extensions/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pi-context-map
|
|
3
3
|
* Professional Context Profiler for Pi.
|
|
4
|
-
* v0.
|
|
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,
|
|
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
|
|
51
|
-
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),
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 += ` |
|
|
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
|
-
|
|
187
|
-
?
|
|
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
|
|
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.
|
|
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",
|