@pi-unipi/info-screen 0.1.3 → 0.1.5
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/core-groups.ts +57 -18
- package/index.ts +3 -3
- package/package.json +1 -1
- package/settings/settings-tui.ts +47 -33
- package/tui/info-overlay.ts +21 -4
package/core-groups.ts
CHANGED
|
@@ -180,6 +180,7 @@ const loadTimes: Array<{ name: string; type: string; ms: number }> = [];
|
|
|
180
180
|
let totalLoadTimeMs = 0;
|
|
181
181
|
let loadTrackingStarted = false;
|
|
182
182
|
let loadTrackingStartMs = 0;
|
|
183
|
+
const moduleStartTimes = new Map<string, number>();
|
|
183
184
|
|
|
184
185
|
/** Start load time tracking */
|
|
185
186
|
export function startLoadTracking(): void {
|
|
@@ -189,10 +190,28 @@ export function startLoadTracking(): void {
|
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
192
|
|
|
193
|
+
/** Record when a module starts loading */
|
|
194
|
+
export function recordModuleStart(name: string): void {
|
|
195
|
+
moduleStartTimes.set(name, Date.now());
|
|
196
|
+
}
|
|
197
|
+
|
|
192
198
|
/** Record a load time */
|
|
193
|
-
export function recordLoadTime(name: string, type: string, ms
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
export function recordLoadTime(name: string, type: string, ms?: number): void {
|
|
200
|
+
// If no ms provided, calculate from start time
|
|
201
|
+
if (ms === undefined || ms === 0) {
|
|
202
|
+
const startTime = moduleStartTimes.get(name);
|
|
203
|
+
if (startTime) {
|
|
204
|
+
ms = Date.now() - startTime;
|
|
205
|
+
} else {
|
|
206
|
+
ms = 0;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Avoid duplicates
|
|
210
|
+
const existing = loadTimes.find(t => t.name === name && t.type === type);
|
|
211
|
+
if (!existing) {
|
|
212
|
+
loadTimes.push({ name, type, ms });
|
|
213
|
+
totalLoadTimeMs += ms;
|
|
214
|
+
}
|
|
196
215
|
}
|
|
197
216
|
|
|
198
217
|
/** Finish load tracking */
|
|
@@ -209,7 +228,7 @@ export function getLoadTimes(): Array<{ name: string; type: string; ms: number }
|
|
|
209
228
|
|
|
210
229
|
/** Get total load time */
|
|
211
230
|
export function getTotalLoadTime(): number {
|
|
212
|
-
return totalLoadTimeMs;
|
|
231
|
+
return totalLoadTimeMs > 0 ? totalLoadTimeMs : (loadTrackingStarted ? Date.now() - loadTrackingStartMs : 0);
|
|
213
232
|
}
|
|
214
233
|
|
|
215
234
|
/**
|
|
@@ -388,17 +407,13 @@ export function registerCoreGroups(): void {
|
|
|
388
407
|
const times = getLoadTimes();
|
|
389
408
|
const total = getTotalLoadTime();
|
|
390
409
|
|
|
391
|
-
//
|
|
392
|
-
const
|
|
410
|
+
// Filter out 0ms entries and sort by load time descending
|
|
411
|
+
const validTimes = times.filter(t => t.ms > 0);
|
|
412
|
+
const sorted = [...validTimes].sort((a, b) => b.ms - a.ms);
|
|
393
413
|
|
|
394
|
-
// Build list as comma-separated values
|
|
395
|
-
const listStr = sorted.length > 0
|
|
396
|
-
? sorted.map(t => `${t.name} (${t.ms}ms)`).join(", ")
|
|
397
|
-
: "none";
|
|
398
|
-
|
|
399
414
|
return {
|
|
400
415
|
total: { value: `${total}ms` },
|
|
401
|
-
count: { value: String(
|
|
416
|
+
count: { value: String(validTimes.length) },
|
|
402
417
|
list: {
|
|
403
418
|
value: sorted.length > 0 ? `${sorted[0].name} (${sorted[0].ms}ms)` : "none",
|
|
404
419
|
detail: sorted.length > 1 ? sorted.slice(1).map(t => `${t.name} (${t.ms}ms)`).join(", ") : undefined,
|
|
@@ -510,16 +525,28 @@ export function registerCoreGroups(): void {
|
|
|
510
525
|
return source === "sdk";
|
|
511
526
|
});
|
|
512
527
|
|
|
513
|
-
// Build tool list as comma-separated values
|
|
528
|
+
// Build tool list as comma-separated values with wrapping
|
|
514
529
|
const toolNames = tools.map((t) => `${t.name}`);
|
|
515
|
-
|
|
530
|
+
// Split into chunks of ~60 chars for wrapping
|
|
531
|
+
const chunks: string[] = [];
|
|
532
|
+
let current = "";
|
|
533
|
+
for (const name of toolNames) {
|
|
534
|
+
if (current && (current.length + name.length + 2) > 60) {
|
|
535
|
+
chunks.push(current);
|
|
536
|
+
current = name;
|
|
537
|
+
} else {
|
|
538
|
+
current = current ? `${current}, ${name}` : name;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (current) chunks.push(current);
|
|
516
542
|
|
|
517
543
|
return {
|
|
518
544
|
total: { value: String(tools.length) },
|
|
519
545
|
builtin: { value: String(builtin.length) },
|
|
520
546
|
registered: { value: String(extension.length + sdk.length) },
|
|
521
547
|
list: {
|
|
522
|
-
value:
|
|
548
|
+
value: chunks.length > 0 ? chunks[0] : "none",
|
|
549
|
+
detail: chunks.length > 1 ? chunks.slice(1).join("\n") : undefined,
|
|
523
550
|
},
|
|
524
551
|
};
|
|
525
552
|
},
|
|
@@ -585,16 +612,28 @@ export function registerCoreGroups(): void {
|
|
|
585
612
|
const global = skills.filter((s) => s.source === "global");
|
|
586
613
|
const project = skills.filter((s) => s.source === "project");
|
|
587
614
|
|
|
588
|
-
// Build skill list as comma-separated values
|
|
615
|
+
// Build skill list as comma-separated values with wrapping
|
|
589
616
|
const skillNames = skills.map((s) => `${s.name} (${s.source})`);
|
|
590
|
-
|
|
617
|
+
// Split into chunks of ~60 chars for wrapping
|
|
618
|
+
const chunks: string[] = [];
|
|
619
|
+
let current = "";
|
|
620
|
+
for (const name of skillNames) {
|
|
621
|
+
if (current && (current.length + name.length + 2) > 60) {
|
|
622
|
+
chunks.push(current);
|
|
623
|
+
current = name;
|
|
624
|
+
} else {
|
|
625
|
+
current = current ? `${current}, ${name}` : name;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (current) chunks.push(current);
|
|
591
629
|
|
|
592
630
|
return {
|
|
593
631
|
count: { value: String(skills.length) },
|
|
594
632
|
global: { value: String(global.length) },
|
|
595
633
|
project: { value: String(project.length) },
|
|
596
634
|
list: {
|
|
597
|
-
value:
|
|
635
|
+
value: chunks.length > 0 ? chunks[0] : "none",
|
|
636
|
+
detail: chunks.length > 1 ? chunks.slice(1).join("\n") : undefined,
|
|
598
637
|
},
|
|
599
638
|
};
|
|
600
639
|
},
|
package/index.ts
CHANGED
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
13
13
|
import { UNIPI_EVENTS, MODULES, UNIPI_PREFIX, emitEvent, getPackageVersion } from "@pi-unipi/core";
|
|
14
14
|
import { infoRegistry } from "./registry.js";
|
|
15
|
-
import { registerCoreGroups, trackModule, trackTool, setPiApi, registerSkillDir, startLoadTracking, recordLoadTime, finishLoadTracking } from "./core-groups.js";
|
|
15
|
+
import { registerCoreGroups, trackModule, trackTool, setPiApi, registerSkillDir, startLoadTracking, recordLoadTime, finishLoadTracking, recordModuleStart } from "./core-groups.js";
|
|
16
16
|
|
|
17
17
|
/** Re-export infoRegistry, registerSkillDir, and load tracking for external use */
|
|
18
|
-
export { infoRegistry, registerSkillDir, startLoadTracking, recordLoadTime, finishLoadTracking };
|
|
18
|
+
export { infoRegistry, registerSkillDir, startLoadTracking, recordLoadTime, finishLoadTracking, recordModuleStart };
|
|
19
19
|
import { getInfoSettings } from "./config.js";
|
|
20
20
|
import { InfoOverlay } from "./tui/info-overlay.js";
|
|
21
21
|
import { SettingsOverlay } from "./settings/settings-tui.js";
|
|
@@ -62,7 +62,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
62
62
|
if (event.name && event.name !== MODULES.INFO_SCREEN) {
|
|
63
63
|
// Track the module
|
|
64
64
|
trackModule(event.name, event.version || "unknown");
|
|
65
|
-
recordLoadTime(event.name, "module", event.loadTimeMs
|
|
65
|
+
recordLoadTime(event.name, "module", event.loadTimeMs);
|
|
66
66
|
|
|
67
67
|
// Track tools from this module
|
|
68
68
|
if (event.tools && Array.isArray(event.tools)) {
|
package/package.json
CHANGED
package/settings/settings-tui.ts
CHANGED
|
@@ -35,6 +35,7 @@ export class SettingsOverlay implements Component {
|
|
|
35
35
|
private settings: InfoScreenSettings;
|
|
36
36
|
private groups: Array<{ id: string; name: string; icon: string }>;
|
|
37
37
|
private selectedIndex = 0;
|
|
38
|
+
private savedGroupIndex = 0; // Saved position before entering stats mode
|
|
38
39
|
private mode: "groups" | "stats" = "groups";
|
|
39
40
|
private selectedGroupId: string | null = null;
|
|
40
41
|
/** Callback when overlay should close */
|
|
@@ -90,9 +91,9 @@ export class SettingsOverlay implements Component {
|
|
|
90
91
|
this.selectedIndex = (this.selectedIndex + 1) % this.groups.length;
|
|
91
92
|
break;
|
|
92
93
|
case " ": // Space - toggle visibility
|
|
93
|
-
case "\r": // Enter - toggle visibility
|
|
94
94
|
this.toggleGroupVisibility(this.groups[this.selectedIndex].id);
|
|
95
95
|
break;
|
|
96
|
+
case "\r": // Enter - enter stats mode
|
|
96
97
|
case "\x1b[C": // Right - enter stats mode
|
|
97
98
|
case "l":
|
|
98
99
|
this.enterStatsMode(this.groups[this.selectedIndex].id);
|
|
@@ -129,14 +130,12 @@ export class SettingsOverlay implements Component {
|
|
|
129
130
|
this.selectedIndex = (this.selectedIndex + 1) % group.config.stats.length;
|
|
130
131
|
break;
|
|
131
132
|
case " ": // Space - toggle stat
|
|
132
|
-
case "\r": // Enter - toggle stat
|
|
133
133
|
this.toggleStatVisibility(this.selectedGroupId, group.config.stats[this.selectedIndex].id);
|
|
134
134
|
break;
|
|
135
135
|
case "\x1b[D": // Left - back to groups
|
|
136
136
|
case "h":
|
|
137
|
-
|
|
138
|
-
this.
|
|
139
|
-
this.selectedIndex = this.groups.findIndex((g) => g.id === this.selectedGroupId) ?? 0;
|
|
137
|
+
case "\r": // Enter - also go back
|
|
138
|
+
this.backToGroups();
|
|
140
139
|
break;
|
|
141
140
|
case "q": // Quit from stats mode
|
|
142
141
|
case "\x1b":
|
|
@@ -145,6 +144,15 @@ export class SettingsOverlay implements Component {
|
|
|
145
144
|
}
|
|
146
145
|
}
|
|
147
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Return to groups mode, restoring cursor position.
|
|
149
|
+
*/
|
|
150
|
+
private backToGroups(): void {
|
|
151
|
+
this.mode = "groups";
|
|
152
|
+
this.selectedIndex = this.savedGroupIndex; // Restore saved position
|
|
153
|
+
this.selectedGroupId = null;
|
|
154
|
+
}
|
|
155
|
+
|
|
148
156
|
/**
|
|
149
157
|
* Toggle group visibility.
|
|
150
158
|
*/
|
|
@@ -176,6 +184,7 @@ export class SettingsOverlay implements Component {
|
|
|
176
184
|
* Enter stats editing mode for a group.
|
|
177
185
|
*/
|
|
178
186
|
private enterStatsMode(groupId: string): void {
|
|
187
|
+
this.savedGroupIndex = this.selectedIndex; // Save position for later
|
|
179
188
|
this.mode = "stats";
|
|
180
189
|
this.selectedGroupId = groupId;
|
|
181
190
|
this.selectedIndex = 0;
|
|
@@ -228,20 +237,28 @@ export class SettingsOverlay implements Component {
|
|
|
228
237
|
}
|
|
229
238
|
}
|
|
230
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Pad a line to fill a target visual width.
|
|
242
|
+
*/
|
|
243
|
+
private padToWidth(line: string, targetWidth: number): string {
|
|
244
|
+
const visLen = visibleWidth(line);
|
|
245
|
+
const pad = Math.max(0, targetWidth - visLen);
|
|
246
|
+
return line + " ".repeat(pad);
|
|
247
|
+
}
|
|
248
|
+
|
|
231
249
|
/**
|
|
232
250
|
* Render groups mode.
|
|
233
251
|
*/
|
|
234
252
|
private renderGroupsMode(width: number): string[] {
|
|
235
253
|
const lines: string[] = [];
|
|
254
|
+
const innerWidth = width - 2; // Subtract border chars
|
|
236
255
|
|
|
237
|
-
//
|
|
238
|
-
lines.push("");
|
|
239
|
-
lines.push(this.renderCentered(`${ansi.bold}⚙️ Info Screen Settings${ansi.reset}`, width));
|
|
240
|
-
lines.push("");
|
|
256
|
+
// Top border
|
|
257
|
+
lines.push(`${ansi.dim}╭${"─".repeat(innerWidth)}╮${ansi.reset}`);
|
|
241
258
|
|
|
242
|
-
//
|
|
243
|
-
lines.push(ansi.dim
|
|
244
|
-
lines.push("");
|
|
259
|
+
// Header
|
|
260
|
+
lines.push(`${ansi.dim}│${ansi.reset}${this.padToWidth(this.renderCentered(`${ansi.bold}⚙️ Info Screen Settings${ansi.reset}`, innerWidth), innerWidth)}${ansi.dim}│${ansi.reset}`);
|
|
261
|
+
lines.push(`${ansi.dim}├${"─".repeat(innerWidth)}┤${ansi.reset}`);
|
|
245
262
|
|
|
246
263
|
// Group list
|
|
247
264
|
for (let i = 0; i < this.groups.length; i++) {
|
|
@@ -259,18 +276,17 @@ export class SettingsOverlay implements Component {
|
|
|
259
276
|
line += ` ${ansi.dim}→ stats${ansi.reset}`;
|
|
260
277
|
}
|
|
261
278
|
|
|
262
|
-
if (visibleWidth(line) >
|
|
263
|
-
line = truncateToWidth(line,
|
|
279
|
+
if (visibleWidth(line) > innerWidth - 2) {
|
|
280
|
+
line = truncateToWidth(line, innerWidth - 2);
|
|
264
281
|
}
|
|
265
282
|
|
|
266
|
-
lines.push(line);
|
|
283
|
+
lines.push(`${ansi.dim}│${ansi.reset}${this.padToWidth(line, innerWidth)}${ansi.dim}│${ansi.reset}`);
|
|
267
284
|
}
|
|
268
285
|
|
|
269
286
|
// Footer
|
|
270
|
-
lines.push("");
|
|
271
|
-
lines.push(ansi.dim
|
|
272
|
-
lines.push(
|
|
273
|
-
lines.push("");
|
|
287
|
+
lines.push(`${ansi.dim}├${"─".repeat(innerWidth)}┤${ansi.reset}`);
|
|
288
|
+
lines.push(`${ansi.dim}│${ansi.reset}${this.padToWidth(this.renderCentered(`${ansi.dim}↑↓ select Space toggle Enter/→ stats J/K reorder q close${ansi.reset}`, innerWidth), innerWidth)}${ansi.dim}│${ansi.reset}`);
|
|
289
|
+
lines.push(`${ansi.dim}╰${"─".repeat(innerWidth)}╯${ansi.reset}`);
|
|
274
290
|
|
|
275
291
|
return lines;
|
|
276
292
|
}
|
|
@@ -288,15 +304,14 @@ export class SettingsOverlay implements Component {
|
|
|
288
304
|
}
|
|
289
305
|
|
|
290
306
|
const groupSettings = getGroupSettings(group.id);
|
|
307
|
+
const innerWidth = width - 2;
|
|
291
308
|
|
|
292
|
-
//
|
|
293
|
-
lines.push("");
|
|
294
|
-
lines.push(this.renderCentered(`${group.icon} ${group.name} Stats`, width));
|
|
295
|
-
lines.push("");
|
|
309
|
+
// Top border
|
|
310
|
+
lines.push(`${ansi.dim}╭${"─".repeat(innerWidth)}╮${ansi.reset}`);
|
|
296
311
|
|
|
297
|
-
//
|
|
298
|
-
lines.push(ansi.dim
|
|
299
|
-
lines.push("");
|
|
312
|
+
// Header
|
|
313
|
+
lines.push(`${ansi.dim}│${ansi.reset}${this.padToWidth(this.renderCentered(`${group.icon} ${group.name} Stats`, innerWidth), innerWidth)}${ansi.dim}│${ansi.reset}`);
|
|
314
|
+
lines.push(`${ansi.dim}├${"─".repeat(innerWidth)}┤${ansi.reset}`);
|
|
300
315
|
|
|
301
316
|
// Stats list
|
|
302
317
|
for (let i = 0; i < group.config.stats.length; i++) {
|
|
@@ -309,18 +324,17 @@ export class SettingsOverlay implements Component {
|
|
|
309
324
|
|
|
310
325
|
let line = ` ${indicator} ${toggle} ${stat.label}`;
|
|
311
326
|
|
|
312
|
-
if (visibleWidth(line) >
|
|
313
|
-
line = truncateToWidth(line,
|
|
327
|
+
if (visibleWidth(line) > innerWidth - 2) {
|
|
328
|
+
line = truncateToWidth(line, innerWidth - 2);
|
|
314
329
|
}
|
|
315
330
|
|
|
316
|
-
lines.push(line);
|
|
331
|
+
lines.push(`${ansi.dim}│${ansi.reset}${this.padToWidth(line, innerWidth)}${ansi.dim}│${ansi.reset}`);
|
|
317
332
|
}
|
|
318
333
|
|
|
319
334
|
// Footer
|
|
320
|
-
lines.push("");
|
|
321
|
-
lines.push(ansi.dim
|
|
322
|
-
lines.push(
|
|
323
|
-
lines.push("");
|
|
335
|
+
lines.push(`${ansi.dim}├${"─".repeat(innerWidth)}┤${ansi.reset}`);
|
|
336
|
+
lines.push(`${ansi.dim}│${ansi.reset}${this.padToWidth(this.renderCentered(`${ansi.dim}↑↓ select Space toggle ←/Enter back q close${ansi.reset}`, innerWidth), innerWidth)}${ansi.dim}│${ansi.reset}`);
|
|
337
|
+
lines.push(`${ansi.dim}╰${"─".repeat(innerWidth)}╯${ansi.reset}`);
|
|
324
338
|
|
|
325
339
|
return lines;
|
|
326
340
|
}
|
package/tui/info-overlay.ts
CHANGED
|
@@ -153,12 +153,12 @@ export class InfoOverlay implements Component {
|
|
|
153
153
|
return this.renderError(width);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
// Check for new groups
|
|
156
|
+
// Check for new groups and re-fetch data
|
|
157
157
|
const allGroups = infoRegistry.getAllGroups();
|
|
158
158
|
const groupIds = allGroups.map(g => g.id).join(",");
|
|
159
159
|
const currentIds = this.groups.map(g => g.id).join(",");
|
|
160
160
|
|
|
161
|
-
if (groupIds !== currentIds) {
|
|
161
|
+
if (groupIds !== currentIds || this.groups.length !== allGroups.length) {
|
|
162
162
|
this.groups = allGroups;
|
|
163
163
|
// Apply saved order
|
|
164
164
|
const settings = getInfoSettings();
|
|
@@ -170,10 +170,11 @@ export class InfoOverlay implements Component {
|
|
|
170
170
|
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
|
-
// Load data for any new groups (non-blocking)
|
|
174
|
-
this.loadDataForNewGroups(allGroups);
|
|
175
173
|
}
|
|
176
174
|
|
|
175
|
+
// Always re-fetch data for all groups to catch late updates
|
|
176
|
+
this.refreshAllData();
|
|
177
|
+
|
|
177
178
|
if (this.groups.length === 0) {
|
|
178
179
|
return this.renderEmpty(width);
|
|
179
180
|
}
|
|
@@ -197,6 +198,22 @@ export class InfoOverlay implements Component {
|
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Refresh data for all groups (non-blocking).
|
|
203
|
+
*/
|
|
204
|
+
private refreshAllData(): void {
|
|
205
|
+
for (const group of this.groups) {
|
|
206
|
+
// Invalidate cache to get fresh data
|
|
207
|
+
infoRegistry.invalidateCache(group.id);
|
|
208
|
+
// Fetch fresh data (non-blocking)
|
|
209
|
+
infoRegistry.getGroupData(group.id).then(data => {
|
|
210
|
+
this.groupData.set(group.id, data);
|
|
211
|
+
}).catch(() => {
|
|
212
|
+
// Ignore errors
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
200
217
|
/**
|
|
201
218
|
* Render loading state.
|
|
202
219
|
*/
|