@pi-unipi/unipi 2.6.0 → 2.6.2
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 +19 -0
- package/package.json +21 -22
- package/packages/ask-user/package.json +2 -2
- package/packages/ask-user/settings-tui.ts +18 -34
- package/packages/autocomplete/package.json +1 -1
- package/packages/autocomplete/src/constants.ts +2 -15
- package/packages/autocomplete/src/provider.ts +3 -17
- package/packages/btw/extensions/btw.ts +18 -45
- package/packages/btw/package.json +2 -2
- package/packages/compactor/README.md +2 -2
- package/packages/compactor/package.json +3 -3
- package/packages/compactor/skills/compactor/SKILL.md +1 -1
- package/packages/compactor/skills/compactor-detail/SKILL.md +3 -5
- package/packages/compactor/skills/compactor-doctor/SKILL.md +1 -1
- package/packages/compactor/skills/compactor-stats/SKILL.md +1 -1
- package/packages/compactor/src/commands/index.ts +47 -78
- package/packages/compactor/src/compaction/brief.ts +161 -90
- package/packages/compactor/src/compaction/build-sections.ts +3 -4
- package/packages/compactor/src/compaction/compact-args.ts +86 -0
- package/packages/compactor/src/compaction/cut.ts +270 -28
- package/packages/compactor/src/compaction/drill-down.ts +261 -0
- package/packages/compactor/src/compaction/format-recall.ts +96 -0
- package/packages/compactor/src/compaction/format.ts +8 -3
- package/packages/compactor/src/compaction/hooks.ts +248 -72
- package/packages/compactor/src/compaction/merge.ts +34 -4
- package/packages/compactor/src/compaction/rank.ts +270 -0
- package/packages/compactor/src/compaction/recall-scope.ts +28 -0
- package/packages/compactor/src/compaction/search-entries.ts +333 -96
- package/packages/compactor/src/compaction/skill-collapse.ts +35 -0
- package/packages/compactor/src/compaction/summarize.ts +37 -6
- package/packages/compactor/src/compaction/token-estimate.ts +104 -0
- package/packages/compactor/src/compaction/touched-files.ts +35 -0
- package/packages/compactor/src/config/manager.ts +2 -27
- package/packages/compactor/src/config/presets.ts +0 -2
- package/packages/compactor/src/config/schema.ts +2 -16
- package/packages/compactor/src/executor/executor.ts +6 -15
- package/packages/compactor/src/executor/runtime.ts +2 -12
- package/packages/compactor/src/index.ts +4 -121
- package/packages/compactor/src/info-screen.ts +3 -10
- package/packages/compactor/src/security/evaluator.ts +0 -53
- package/packages/compactor/src/security/policy.ts +7 -8
- package/packages/compactor/src/session/db.ts +0 -6
- package/packages/compactor/src/tools/ctx-execute-file.ts +0 -5
- package/packages/compactor/src/tools/register.ts +27 -50
- package/packages/compactor/src/tools/vcc-recall.ts +86 -48
- package/packages/compactor/src/tui/settings-overlay.ts +20 -40
- package/packages/compactor/src/types.ts +43 -100
- package/packages/core/constants.ts +1 -28
- package/packages/core/events.ts +1 -83
- package/packages/core/index.ts +1 -0
- package/packages/core/model-cache.ts +18 -12
- package/packages/core/package.json +3 -1
- package/packages/core/tui-overlay.ts +89 -0
- package/packages/core/tui-width.ts +18 -0
- package/packages/core/utils.ts +43 -0
- package/packages/footer/package.json +2 -2
- package/packages/footer/src/help.ts +2 -2
- package/packages/footer/src/index.ts +9 -10
- package/packages/footer/src/presets.ts +1 -27
- package/packages/footer/src/registry/index.ts +0 -23
- package/packages/footer/src/rendering/icons.ts +0 -13
- package/packages/footer/src/rendering/renderer.ts +5 -40
- package/packages/footer/src/rendering/separators.ts +0 -10
- package/packages/footer/src/rendering/theme.ts +1 -15
- package/packages/footer/src/segments/compactor.ts +7 -14
- package/packages/footer/src/segments/core.ts +15 -54
- package/packages/footer/src/segments/kanboard.ts +5 -5
- package/packages/footer/src/segments/mcp.ts +5 -5
- package/packages/footer/src/segments/memory.ts +4 -4
- package/packages/footer/src/segments/notify.ts +2 -2
- package/packages/footer/src/segments/ralph.ts +3 -3
- package/packages/footer/src/segments/status-ext.ts +1 -1
- package/packages/footer/src/segments/workflow.ts +3 -3
- package/packages/footer/src/tps-tracker.ts +0 -8
- package/packages/footer/src/tui/settings-tui.ts +14 -31
- package/packages/footer/src/types.ts +0 -15
- package/packages/image/package.json +2 -2
- package/packages/image/src/register-providers.ts +0 -11
- package/packages/image/src/tools.ts +0 -3
- package/packages/image/src/tui/model-selector.ts +36 -71
- package/packages/info-screen/config.ts +0 -16
- package/packages/info-screen/core-groups.ts +0 -10
- package/packages/info-screen/index.ts +22 -13
- package/packages/info-screen/package.json +2 -2
- package/packages/info-screen/registry.ts +1 -72
- package/packages/info-screen/tui/info-overlay.ts +85 -100
- package/packages/info-screen/usage-parser.ts +12 -47
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/input-shortcuts/src/index.ts +3 -3
- package/packages/input-shortcuts/src/registers.ts +0 -8
- package/packages/input-shortcuts/src/settings-overlay.ts +4 -13
- package/packages/input-shortcuts/src/undo-redo.ts +0 -12
- package/packages/kanboard/package.json +2 -2
- package/packages/mcp/package.json +2 -2
- package/packages/mcp/src/bridge/client.ts +0 -15
- package/packages/mcp/src/config/manager.ts +0 -38
- package/packages/mcp/src/config/schema.ts +0 -27
- package/packages/mcp/src/config/sync.ts +0 -29
- package/packages/mcp/src/index.ts +1 -1
- package/packages/mcp/src/tui/add-overlay.ts +6 -6
- package/packages/mcp/src/types.ts +0 -6
- package/packages/memory/embedding.ts +0 -31
- package/packages/memory/mempalace.ts +0 -16
- package/packages/memory/package.json +4 -8
- package/packages/memory/storage.ts +56 -753
- package/packages/milestone/hooks.ts +1 -6
- package/packages/milestone/milestone.ts +2 -41
- package/packages/milestone/package.json +2 -2
- package/packages/notify/README.md +1 -0
- package/packages/notify/commands.ts +61 -3
- package/packages/notify/events.ts +0 -10
- package/packages/notify/package.json +2 -2
- package/packages/notify/settings.ts +0 -19
- package/packages/notify/tui/gotify-setup.ts +108 -143
- package/packages/notify/tui/ntfy-setup.ts +137 -172
- package/packages/notify/tui/recap-model-selector.ts +107 -101
- package/packages/notify/tui/settings-overlay.ts +94 -117
- package/packages/notify/tui/telegram-setup.ts +48 -79
- package/packages/notify/types.ts +0 -2
- package/packages/ralph/index.ts +11 -35
- package/packages/ralph/package.json +3 -3
- package/packages/ralph/ralph-loop.ts +3 -44
- package/packages/subagents/package.json +2 -1
- package/packages/subagents/src/agent-manager.ts +0 -51
- package/packages/subagents/src/agent-runner.ts +6 -80
- package/packages/subagents/src/conversation-viewer.ts +1 -19
- package/packages/subagents/src/core-compat.ts +8 -25
- package/packages/subagents/src/custom-agents.ts +0 -1
- package/packages/subagents/src/index.ts +3 -48
- package/packages/subagents/src/types.ts +0 -11
- package/packages/subagents/src/widget.ts +5 -5
- package/packages/unipi/bundled.js +4468 -7769
- package/packages/unipi/index.ts +6 -3
- package/packages/updater/package.json +2 -2
- package/packages/updater/src/changelog.ts +1 -16
- package/packages/updater/src/index.ts +0 -5
- package/packages/updater/src/installer.ts +4 -26
- package/packages/updater/src/markdown.ts +6 -152
- package/packages/updater/src/settings.ts +0 -15
- package/packages/updater/src/tui/changelog-overlay.ts +42 -234
- package/packages/updater/src/tui/list-detail-overlay.ts +260 -0
- package/packages/updater/src/tui/readme-overlay.ts +53 -210
- package/packages/updater/src/tui/settings-overlay.ts +13 -26
- package/packages/updater/src/version.ts +2 -31
- package/packages/utility/package.json +2 -4
- package/packages/utility/src/commands.ts +4 -5
- package/packages/utility/src/herdr-sync.ts +136 -0
- package/packages/utility/src/index.ts +11 -68
- package/packages/utility/src/lifecycle/cleanup.ts +2 -16
- package/packages/utility/src/{diff/settings.ts → settings.ts} +42 -80
- package/packages/utility/src/tools/env.ts +0 -0
- package/packages/utility/src/tui/name-badge-state.ts +18 -2
- package/packages/utility/src/tui/name-badge.ts +5 -4
- package/packages/utility/src/tui/util-settings-tui.ts +41 -224
- package/packages/web-api/package.json +2 -6
- package/packages/web-api/src/engine/dependencies.ts +13 -134
- package/packages/web-api/src/engine/extract.ts +9 -99
- package/packages/web-api/src/engine/format.ts +0 -75
- package/packages/web-api/src/engine/types.ts +0 -39
- package/packages/web-api/src/index.ts +1 -2
- package/packages/web-api/src/providers/base.ts +0 -10
- package/packages/web-api/src/providers/duckduckgo.ts +0 -1
- package/packages/web-api/src/providers/firecrawl.ts +0 -9
- package/packages/web-api/src/providers/jina-reader.ts +0 -9
- package/packages/web-api/src/providers/jina-search.ts +0 -9
- package/packages/web-api/src/providers/perplexity.ts +0 -9
- package/packages/web-api/src/providers/registry.ts +0 -80
- package/packages/web-api/src/providers/serpapi.ts +0 -9
- package/packages/web-api/src/providers/tavily.ts +0 -9
- package/packages/web-api/src/providers/wigolo-client.ts +0 -30
- package/packages/web-api/src/providers/wigolo.ts +0 -1
- package/packages/web-api/src/settings.ts +17 -77
- package/packages/web-api/src/tools.ts +21 -24
- package/packages/web-api/src/tui/settings-dialog.ts +0 -1
- package/packages/workflow/commands.ts +38 -122
- package/packages/workflow/index.ts +1 -5
- package/packages/workflow/package.json +2 -2
- package/packages/cocoindex/README.md +0 -94
- package/packages/cocoindex/bridge.ts +0 -842
- package/packages/cocoindex/commands.ts +0 -175
- package/packages/cocoindex/index.ts +0 -48
- package/packages/cocoindex/installer.ts +0 -397
- package/packages/cocoindex/package.json +0 -49
- package/packages/cocoindex/skills/cocoindex/SKILL.md +0 -88
- package/packages/cocoindex/tools.ts +0 -157
- package/packages/compactor/src/display/diff-renderer.ts +0 -281
- package/packages/compactor/src/display/line-width-safety.ts +0 -28
- package/packages/compactor/src/display/render-utils.ts +0 -52
- package/packages/compactor/src/display/thinking-label.ts +0 -18
- package/packages/compactor/src/display/tool-overrides.ts +0 -136
- package/packages/compactor/src/tools/compact.ts +0 -20
- package/packages/subagents/src/file-lock.ts +0 -102
- package/packages/utility/src/cache/ttl-cache.ts +0 -311
- package/packages/utility/src/diff/highlighter.ts +0 -352
- package/packages/utility/src/diff/parser.ts +0 -191
- package/packages/utility/src/diff/renderer.ts +0 -395
- package/packages/utility/src/diff/theme.ts +0 -316
- package/packages/utility/src/diff/wrapper.ts +0 -339
- package/packages/utility/src/display/capabilities.ts +0 -214
- package/packages/utility/src/display/width.ts +0 -226
- package/packages/utility/src/tools/batch.ts +0 -229
- package/packages/utility/src/tui/badge-settings-tui.ts +0 -388
- package/packages/utility/src/tui/badge-settings.ts +0 -67
- package/packages/utility/src/tui/settings-inspector.ts +0 -303
- package/packages/web-api/src/providers/llm-summarize.ts +0 -71
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { InfoGroup, GroupData } from "./types.js";
|
|
9
|
-
import {
|
|
9
|
+
import { isStatEnabled } from "./config.js";
|
|
10
10
|
|
|
11
11
|
/** Callback for reactive updates */
|
|
12
12
|
type GroupUpdateCallback = (groupId: string, data: GroupData) => void;
|
|
@@ -24,9 +24,6 @@ class InfoRegistry {
|
|
|
24
24
|
/** Cache TTL in ms */
|
|
25
25
|
private cacheTtlMs = 5000;
|
|
26
26
|
|
|
27
|
-
/** Subscribers per group */
|
|
28
|
-
private subscribers = new Map<string, Set<GroupUpdateCallback>>();
|
|
29
|
-
|
|
30
27
|
/** Global subscribers (any group update) */
|
|
31
28
|
private globalSubscribers = new Set<GroupUpdateCallback>();
|
|
32
29
|
|
|
@@ -43,33 +40,6 @@ class InfoRegistry {
|
|
|
43
40
|
this.notifyGroupRegistered(group.id);
|
|
44
41
|
}
|
|
45
42
|
|
|
46
|
-
/**
|
|
47
|
-
* Unregister an info group.
|
|
48
|
-
*/
|
|
49
|
-
unregisterGroup(groupId: string): void {
|
|
50
|
-
this.groups.delete(groupId);
|
|
51
|
-
this.dataCache.delete(groupId);
|
|
52
|
-
this.lastUpdated.delete(groupId);
|
|
53
|
-
this.subscribers.delete(groupId);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get all registered groups, sorted by priority.
|
|
58
|
-
*/
|
|
59
|
-
getGroups(): InfoGroup[] {
|
|
60
|
-
const settings = getInfoSettings();
|
|
61
|
-
const allGroups = Array.from(this.groups.values());
|
|
62
|
-
|
|
63
|
-
return allGroups
|
|
64
|
-
.filter((group) => {
|
|
65
|
-
const groupSettings = settings.groups[group.id];
|
|
66
|
-
if (groupSettings && !groupSettings.show) return false;
|
|
67
|
-
if (!groupSettings && !group.config.showByDefault) return false;
|
|
68
|
-
return true;
|
|
69
|
-
})
|
|
70
|
-
.sort((a, b) => a.priority - b.priority);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
43
|
/**
|
|
74
44
|
* Get all registered groups (including hidden ones).
|
|
75
45
|
*/
|
|
@@ -100,13 +70,6 @@ class InfoRegistry {
|
|
|
100
70
|
return this.lastUpdated.get(groupId) ?? 0;
|
|
101
71
|
}
|
|
102
72
|
|
|
103
|
-
/**
|
|
104
|
-
* Synchronous: check if a group is currently fetching.
|
|
105
|
-
*/
|
|
106
|
-
isFetching(groupId: string): boolean {
|
|
107
|
-
return this.inflight.has(groupId);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
73
|
/**
|
|
111
74
|
* Get data for a group, using cache if fresh.
|
|
112
75
|
* Returns immediately from cache if fresh, otherwise fetches in background
|
|
@@ -176,21 +139,6 @@ class InfoRegistry {
|
|
|
176
139
|
}
|
|
177
140
|
}
|
|
178
141
|
|
|
179
|
-
/**
|
|
180
|
-
* Subscribe to updates for a specific group.
|
|
181
|
-
* Returns unsubscribe function.
|
|
182
|
-
*/
|
|
183
|
-
subscribe(groupId: string, callback: GroupUpdateCallback): () => void {
|
|
184
|
-
if (!this.subscribers.has(groupId)) {
|
|
185
|
-
this.subscribers.set(groupId, new Set());
|
|
186
|
-
}
|
|
187
|
-
this.subscribers.get(groupId)!.add(callback);
|
|
188
|
-
|
|
189
|
-
return () => {
|
|
190
|
-
this.subscribers.get(groupId)?.delete(callback);
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
142
|
/**
|
|
195
143
|
* Subscribe to all group updates.
|
|
196
144
|
* Returns unsubscribe function.
|
|
@@ -203,14 +151,6 @@ class InfoRegistry {
|
|
|
203
151
|
}
|
|
204
152
|
|
|
205
153
|
private notifySubscribers(groupId: string, data: GroupData): void {
|
|
206
|
-
// Per-group subscribers
|
|
207
|
-
const groupSubs = this.subscribers.get(groupId);
|
|
208
|
-
if (groupSubs) {
|
|
209
|
-
for (const cb of groupSubs) {
|
|
210
|
-
try { cb(groupId, data); } catch { /* ignore */ }
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
154
|
// Global subscribers
|
|
215
155
|
for (const cb of this.globalSubscribers) {
|
|
216
156
|
try { cb(groupId, data); } catch { /* ignore */ }
|
|
@@ -239,14 +179,6 @@ class InfoRegistry {
|
|
|
239
179
|
this.lastUpdated.delete(groupId);
|
|
240
180
|
}
|
|
241
181
|
|
|
242
|
-
/**
|
|
243
|
-
* Invalidate all caches.
|
|
244
|
-
*/
|
|
245
|
-
invalidateAllCaches(): void {
|
|
246
|
-
this.dataCache.clear();
|
|
247
|
-
this.lastUpdated.clear();
|
|
248
|
-
}
|
|
249
|
-
|
|
250
182
|
/**
|
|
251
183
|
* Notify that a new group was registered.
|
|
252
184
|
* Subscribers can use this to sync group lists.
|
|
@@ -266,6 +198,3 @@ export const infoRegistry = new InfoRegistry();
|
|
|
266
198
|
if (!globalThis.__unipi_info_registry) {
|
|
267
199
|
globalThis.__unipi_info_registry = infoRegistry;
|
|
268
200
|
}
|
|
269
|
-
export const getGlobalRegistry = (): InfoRegistry => {
|
|
270
|
-
return (globalThis.__unipi_info_registry as InfoRegistry | undefined) ?? infoRegistry;
|
|
271
|
-
};
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Component } from "@earendil-works/pi-tui";
|
|
11
|
-
import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
11
|
+
import { Key, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
12
12
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { infoRegistry } from "../registry.js";
|
|
14
14
|
import { getInfoSettings } from "../config.js";
|
|
15
15
|
import type { InfoGroup, GroupData } from "../types.js";
|
|
16
|
-
import { boxInnerWidth } from "@pi-unipi/core";
|
|
16
|
+
import { boxInnerWidth, OverlayTheme } from "@pi-unipi/core";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* How long to wait before warming the non-visible tabs.
|
|
@@ -63,11 +63,17 @@ export class InfoOverlay implements Component {
|
|
|
63
63
|
|
|
64
64
|
onClose?: () => void;
|
|
65
65
|
requestRender?: () => void;
|
|
66
|
+
/**
|
|
67
|
+
* Whether this overlay is the focused (topmost) entry in the TUI overlay
|
|
68
|
+
* stack. The boot auto-close timer only fires while this is true — see
|
|
69
|
+
* `startBootTimer` for why.
|
|
70
|
+
*/
|
|
71
|
+
isTopmostOverlay?: () => boolean;
|
|
66
72
|
|
|
67
|
-
private
|
|
73
|
+
private overlay = new OverlayTheme();
|
|
68
74
|
|
|
69
75
|
setTheme(theme: Theme): void {
|
|
70
|
-
this.theme
|
|
76
|
+
this.overlay.setTheme(theme);
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
constructor() {
|
|
@@ -230,17 +236,36 @@ export class InfoOverlay implements Component {
|
|
|
230
236
|
*
|
|
231
237
|
* Used when the overlay is shown on boot: the dashboard is informational, so
|
|
232
238
|
* it should get out of the way on its own rather than requiring a keypress.
|
|
239
|
+
*
|
|
240
|
+
* The close callback (`onClose` → `done`) pops the *topmost* overlay in the
|
|
241
|
+
* TUI stack, not this one specifically. When another overlay (e.g. the
|
|
242
|
+
* updater's "Update Available" prompt) is stacked on top, firing `done()`
|
|
243
|
+
* here would remove the covering overlay and strand this dashboard with a
|
|
244
|
+
* spent one-shot close the user can no longer trigger — leaving the starting
|
|
245
|
+
* screen stuck. So we defer the auto-close until we are actually the
|
|
246
|
+
* focused/topmost overlay; the user can still press q/Esc to dismiss it once
|
|
247
|
+
* the covering overlay is gone.
|
|
233
248
|
*/
|
|
234
249
|
startBootTimer(ms: number): void {
|
|
235
250
|
this.cancelBootTimer();
|
|
236
251
|
if (!Number.isFinite(ms) || ms <= 0) return;
|
|
237
|
-
|
|
238
|
-
this.bootTimer =
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
252
|
+
const arm = (): void => {
|
|
253
|
+
this.bootTimer = setTimeout(() => {
|
|
254
|
+
this.bootTimer = null;
|
|
255
|
+
if (this._destroyed) return;
|
|
256
|
+
if (this.isTopmostOverlay && !this.isTopmostOverlay()) {
|
|
257
|
+
// Something is stacked on top of us — closing now would pop it
|
|
258
|
+
// instead of this dashboard. Retry shortly; once the covering
|
|
259
|
+
// overlay closes we'll be topmost and can auto-close safely.
|
|
260
|
+
arm();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
this.destroy();
|
|
264
|
+
this.onClose?.();
|
|
265
|
+
}, ms);
|
|
266
|
+
this.bootTimer.unref?.();
|
|
267
|
+
};
|
|
268
|
+
arm();
|
|
244
269
|
}
|
|
245
270
|
|
|
246
271
|
invalidate(): void {
|
|
@@ -251,17 +276,17 @@ export class InfoOverlay implements Component {
|
|
|
251
276
|
// Any keypress means the user is driving; stop the boot auto-close.
|
|
252
277
|
this.cancelBootTimer();
|
|
253
278
|
|
|
254
|
-
if (data
|
|
279
|
+
if (matchesKey(data, Key.right) || data === "l") {
|
|
255
280
|
this.activeTabIndex = (this.activeTabIndex + 1) % this.groups.length;
|
|
256
281
|
this.scrollOffset = 0;
|
|
257
282
|
this.fetchActiveGroup();
|
|
258
|
-
} else if (data
|
|
283
|
+
} else if (matchesKey(data, Key.left) || data === "h") {
|
|
259
284
|
this.activeTabIndex = (this.activeTabIndex - 1 + this.groups.length) % this.groups.length;
|
|
260
285
|
this.scrollOffset = 0;
|
|
261
286
|
this.fetchActiveGroup();
|
|
262
|
-
} else if (data
|
|
287
|
+
} else if (matchesKey(data, Key.down) || data === "j") {
|
|
263
288
|
this.scrollOffset++;
|
|
264
|
-
} else if (data
|
|
289
|
+
} else if (matchesKey(data, Key.up) || data === "k") {
|
|
265
290
|
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
266
291
|
} else if (data === "g") {
|
|
267
292
|
this.scrollOffset = 0;
|
|
@@ -273,7 +298,7 @@ export class InfoOverlay implements Component {
|
|
|
273
298
|
} else if (data === "R") {
|
|
274
299
|
// Refresh all
|
|
275
300
|
this.refreshAll();
|
|
276
|
-
} else if (data === "q" || matchesKey(data,
|
|
301
|
+
} else if (data === "q" || matchesKey(data, Key.escape)) {
|
|
277
302
|
this.destroy();
|
|
278
303
|
this.onClose?.();
|
|
279
304
|
}
|
|
@@ -309,60 +334,20 @@ export class InfoOverlay implements Component {
|
|
|
309
334
|
return this.renderDashboard(width);
|
|
310
335
|
}
|
|
311
336
|
|
|
312
|
-
// ─── Theme helpers ───────────────────────────────────────────────────
|
|
313
|
-
|
|
314
|
-
private fg(color: string, text: string): string {
|
|
315
|
-
if (this.theme) return this.theme.fg(color as any, text);
|
|
316
|
-
const c: Record<string, string> = {
|
|
317
|
-
accent: "\x1b[36m", success: "\x1b[32m", warning: "\x1b[33m",
|
|
318
|
-
error: "\x1b[31m", dim: "\x1b[2m", borderMuted: "\x1b[90m",
|
|
319
|
-
};
|
|
320
|
-
return `${c[color] ?? ""}${text}\x1b[0m`;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
private bold(text: string): string {
|
|
324
|
-
return this.theme ? this.theme.bold(text) : `\x1b[1m${text}\x1b[0m`;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
private bg(color: string, text: string): string {
|
|
328
|
-
return this.theme ? this.theme.bg(color as any, text) : text;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
private frameLine(content: string, innerWidth: number): string {
|
|
332
|
-
const truncated = truncateToWidth(content, innerWidth, "");
|
|
333
|
-
const padding = Math.max(0, innerWidth - visibleWidth(truncated));
|
|
334
|
-
return `${this.fg("borderMuted", "│")}${truncated}${" ".repeat(padding)}${this.fg("borderMuted", "│")}`;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
private ruleLine(innerWidth: number): string {
|
|
338
|
-
return this.fg("borderMuted", `├${"─".repeat(innerWidth)}┤`);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
private borderLine(innerWidth: number, edge: "top" | "bottom"): string {
|
|
342
|
-
const left = edge === "top" ? "┌" : "└";
|
|
343
|
-
const right = edge === "top" ? "┐" : "┘";
|
|
344
|
-
return this.fg("borderMuted", `${left}${"─".repeat(innerWidth)}${right}`);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
private getDialogHeight(): number {
|
|
348
|
-
const terminalRows = process.stdout.rows ?? 30;
|
|
349
|
-
return Math.max(18, Math.min(32, Math.floor(terminalRows * 0.78)));
|
|
350
|
-
}
|
|
351
|
-
|
|
352
337
|
// ─── State views ─────────────────────────────────────────────────────
|
|
353
338
|
|
|
354
339
|
private renderEmpty(width: number): string[] {
|
|
355
340
|
const innerWidth = boxInnerWidth(width);
|
|
356
341
|
const lines: string[] = [];
|
|
357
|
-
lines.push(this.borderLine(innerWidth, "top"));
|
|
358
|
-
lines.push(this.frameLine(this.fg("accent", this.bold("📊 UniPi Info Screen")), innerWidth));
|
|
359
|
-
lines.push(this.ruleLine(innerWidth));
|
|
360
|
-
lines.push(this.frameLine(this.fg("dim", "No groups registered."), innerWidth));
|
|
361
|
-
lines.push(this.frameLine(this.fg("dim", "Modules will register groups on startup."), innerWidth));
|
|
362
|
-
for (let i = 0; i < 4; i++) lines.push(this.frameLine("", innerWidth));
|
|
363
|
-
lines.push(this.ruleLine(innerWidth));
|
|
364
|
-
lines.push(this.frameLine(this.fg("dim", "q/Esc close · r refresh"), innerWidth));
|
|
365
|
-
lines.push(this.borderLine(innerWidth, "bottom"));
|
|
342
|
+
lines.push(this.overlay.borderLine(innerWidth, "top"));
|
|
343
|
+
lines.push(this.overlay.frameLine(this.overlay.fg("accent", this.overlay.bold("📊 UniPi Info Screen")), innerWidth));
|
|
344
|
+
lines.push(this.overlay.ruleLine(innerWidth));
|
|
345
|
+
lines.push(this.overlay.frameLine(this.overlay.fg("dim", "No groups registered."), innerWidth));
|
|
346
|
+
lines.push(this.overlay.frameLine(this.overlay.fg("dim", "Modules will register groups on startup."), innerWidth));
|
|
347
|
+
for (let i = 0; i < 4; i++) lines.push(this.overlay.frameLine("", innerWidth));
|
|
348
|
+
lines.push(this.overlay.ruleLine(innerWidth));
|
|
349
|
+
lines.push(this.overlay.frameLine(this.overlay.fg("dim", "q/Esc close · r refresh"), innerWidth));
|
|
350
|
+
lines.push(this.overlay.borderLine(innerWidth, "bottom"));
|
|
366
351
|
return lines;
|
|
367
352
|
}
|
|
368
353
|
|
|
@@ -377,19 +362,19 @@ export class InfoOverlay implements Component {
|
|
|
377
362
|
const CONTENT_HEIGHT = 12;
|
|
378
363
|
const lines: string[] = [];
|
|
379
364
|
|
|
380
|
-
lines.push(this.borderLine(innerWidth, "top"));
|
|
365
|
+
lines.push(this.overlay.borderLine(innerWidth, "top"));
|
|
381
366
|
|
|
382
367
|
// Header: group name + loading indicator
|
|
383
368
|
const loadingDot = isLoading
|
|
384
|
-
? ` ${this.fg("warning", "●")}`
|
|
385
|
-
: ` ${this.fg("success", "●")}`;
|
|
386
|
-
const headerText = this.fg("accent", this.bold(` ${group.icon} ${group.name} `)) + loadingDot;
|
|
387
|
-
lines.push(this.frameLine(headerText, innerWidth));
|
|
388
|
-
lines.push(this.ruleLine(innerWidth));
|
|
369
|
+
? ` ${this.overlay.fg("warning", "●")}`
|
|
370
|
+
: ` ${this.overlay.fg("success", "●")}`;
|
|
371
|
+
const headerText = this.overlay.fg("accent", this.overlay.bold(` ${group.icon} ${group.name} `)) + loadingDot;
|
|
372
|
+
lines.push(this.overlay.frameLine(headerText, innerWidth));
|
|
373
|
+
lines.push(this.overlay.ruleLine(innerWidth));
|
|
389
374
|
|
|
390
375
|
// Tab bar
|
|
391
|
-
lines.push(this.frameLine(this.renderTabBar(innerWidth), innerWidth));
|
|
392
|
-
lines.push(this.ruleLine(innerWidth));
|
|
376
|
+
lines.push(this.overlay.frameLine(this.renderTabBar(innerWidth), innerWidth));
|
|
377
|
+
lines.push(this.overlay.ruleLine(innerWidth));
|
|
393
378
|
|
|
394
379
|
// Content with scrolling
|
|
395
380
|
const contentLines = this.renderGroupContent(innerWidth, group, data);
|
|
@@ -399,13 +384,13 @@ export class InfoOverlay implements Component {
|
|
|
399
384
|
|
|
400
385
|
const visible = wrapped.slice(this.scrollOffset, this.scrollOffset + CONTENT_HEIGHT);
|
|
401
386
|
for (let i = 0; i < CONTENT_HEIGHT; i++) {
|
|
402
|
-
lines.push(this.frameLine(visible[i] ?? "", innerWidth));
|
|
387
|
+
lines.push(this.overlay.frameLine(visible[i] ?? "", innerWidth));
|
|
403
388
|
}
|
|
404
389
|
|
|
405
390
|
// Footer
|
|
406
|
-
lines.push(this.ruleLine(innerWidth));
|
|
407
|
-
lines.push(this.frameLine(this.renderFooter(innerWidth, wrapped.length, CONTENT_HEIGHT), innerWidth));
|
|
408
|
-
lines.push(this.borderLine(innerWidth, "bottom"));
|
|
391
|
+
lines.push(this.overlay.ruleLine(innerWidth));
|
|
392
|
+
lines.push(this.overlay.frameLine(this.renderFooter(innerWidth, wrapped.length, CONTENT_HEIGHT), innerWidth));
|
|
393
|
+
lines.push(this.overlay.borderLine(innerWidth, "bottom"));
|
|
409
394
|
|
|
410
395
|
return lines;
|
|
411
396
|
}
|
|
@@ -414,7 +399,7 @@ export class InfoOverlay implements Component {
|
|
|
414
399
|
if (this.groups.length === 0) return "";
|
|
415
400
|
|
|
416
401
|
const tabWidths = this.groups.map(g => visibleWidth(` ${g.icon} ${g.name} `));
|
|
417
|
-
const sepW = visibleWidth(this.fg("borderMuted", "│"));
|
|
402
|
+
const sepW = visibleWidth(this.overlay.fg("borderMuted", "│"));
|
|
418
403
|
const indicatorSpace = 3;
|
|
419
404
|
let maxTabs = 0;
|
|
420
405
|
let totalW = 0;
|
|
@@ -443,18 +428,18 @@ export class InfoOverlay implements Component {
|
|
|
443
428
|
const color = TAB_FG[i % TAB_FG.length]!;
|
|
444
429
|
// Per-tab loading indicator
|
|
445
430
|
const isLoading = this.groupLoading.get(g.id) ?? false;
|
|
446
|
-
const dot = isLoading ? this.fg("warning", "●") : "";
|
|
431
|
+
const dot = isLoading ? this.overlay.fg("warning", "●") : "";
|
|
447
432
|
|
|
448
433
|
if (isActive) {
|
|
449
|
-
tabs.push(this.fg(color, this.bold(` ${g.icon} ${g.name} ${dot}`)));
|
|
434
|
+
tabs.push(this.overlay.fg(color, this.overlay.bold(` ${g.icon} ${g.name} ${dot}`)));
|
|
450
435
|
} else {
|
|
451
|
-
tabs.push(this.fg("dim", ` ${g.icon} ${g.name} ${dot}`));
|
|
436
|
+
tabs.push(this.overlay.fg("dim", ` ${g.icon} ${g.name} ${dot}`));
|
|
452
437
|
}
|
|
453
438
|
}
|
|
454
439
|
|
|
455
|
-
const tabStr = tabs.join(this.fg("borderMuted", "│"));
|
|
456
|
-
if (this.tabScrollOffset > 0) return `${this.fg("dim", "◀")} ${tabStr}`;
|
|
457
|
-
if (this.tabScrollOffset + maxTabs < this.groups.length) return `${tabStr} ${this.fg("dim", "▶")}`;
|
|
440
|
+
const tabStr = tabs.join(this.overlay.fg("borderMuted", "│"));
|
|
441
|
+
if (this.tabScrollOffset > 0) return `${this.overlay.fg("dim", "◀")} ${tabStr}`;
|
|
442
|
+
if (this.tabScrollOffset + maxTabs < this.groups.length) return `${tabStr} ${this.overlay.fg("dim", "▶")}`;
|
|
458
443
|
return tabStr;
|
|
459
444
|
}
|
|
460
445
|
|
|
@@ -465,15 +450,15 @@ export class InfoOverlay implements Component {
|
|
|
465
450
|
const isActive = i === this.activeTabIndex;
|
|
466
451
|
const color = TAB_FG[i % TAB_FG.length]!;
|
|
467
452
|
const isLoading = this.groupLoading.get(g.id) ?? false;
|
|
468
|
-
const dot = isLoading ? this.fg("warning", "●") : "";
|
|
453
|
+
const dot = isLoading ? this.overlay.fg("warning", "●") : "";
|
|
469
454
|
|
|
470
455
|
if (isActive) {
|
|
471
|
-
tabs.push(this.fg(color, this.bold(` ${g.icon} ${g.name} ${dot}`)));
|
|
456
|
+
tabs.push(this.overlay.fg(color, this.overlay.bold(` ${g.icon} ${g.name} ${dot}`)));
|
|
472
457
|
} else {
|
|
473
|
-
tabs.push(this.fg("dim", ` ${g.icon} ${g.name} ${dot}`));
|
|
458
|
+
tabs.push(this.overlay.fg("dim", ` ${g.icon} ${g.name} ${dot}`));
|
|
474
459
|
}
|
|
475
460
|
}
|
|
476
|
-
return tabs.join(this.fg("borderMuted", "│"));
|
|
461
|
+
return tabs.join(this.overlay.fg("borderMuted", "│"));
|
|
477
462
|
}
|
|
478
463
|
|
|
479
464
|
private renderGroupContent(width: number, group: InfoGroup, data: GroupData): string[] {
|
|
@@ -482,14 +467,14 @@ export class InfoOverlay implements Component {
|
|
|
482
467
|
const visibleStats = infoRegistry.getVisibleStats(group.id);
|
|
483
468
|
|
|
484
469
|
if (visibleStats.length === 0) {
|
|
485
|
-
lines.push(` ${this.fg("dim", "No stats configured for this group.")}`);
|
|
470
|
+
lines.push(` ${this.overlay.fg("dim", "No stats configured for this group.")}`);
|
|
486
471
|
return lines;
|
|
487
472
|
}
|
|
488
473
|
|
|
489
474
|
// If no data yet and loading, show placeholder per stat
|
|
490
475
|
if (Object.keys(data).length === 0 && isLoading) {
|
|
491
476
|
for (const stat of visibleStats) {
|
|
492
|
-
lines.push(` ${this.fg("dim", `${stat.label}:`)} ${this.fg("warning", "···")}`);
|
|
477
|
+
lines.push(` ${this.overlay.fg("dim", `${stat.label}:`)} ${this.overlay.fg("warning", "···")}`);
|
|
493
478
|
}
|
|
494
479
|
return lines;
|
|
495
480
|
}
|
|
@@ -502,12 +487,12 @@ export class InfoOverlay implements Component {
|
|
|
502
487
|
const detail = statData?.detail;
|
|
503
488
|
|
|
504
489
|
const label = `${stat.label}:`.padEnd(maxLabelLen + 1);
|
|
505
|
-
let line = ` ${this.fg("dim", label)} ${this.bold(value)}`;
|
|
490
|
+
let line = ` ${this.overlay.fg("dim", label)} ${this.overlay.bold(value)}`;
|
|
506
491
|
|
|
507
492
|
if (detail) {
|
|
508
493
|
const detailLines = detail.split("\n");
|
|
509
494
|
if (detailLines.length === 1) {
|
|
510
|
-
line += ` ${this.fg("dim", `(${detail})`)}`;
|
|
495
|
+
line += ` ${this.overlay.fg("dim", `(${detail})`)}`;
|
|
511
496
|
} else {
|
|
512
497
|
lines.push(line);
|
|
513
498
|
for (const dLine of detailLines) {
|
|
@@ -536,7 +521,7 @@ export class InfoOverlay implements Component {
|
|
|
536
521
|
const hasScroll = totalLines > visibleHeight;
|
|
537
522
|
let scrollStr = "";
|
|
538
523
|
if (hasScroll) {
|
|
539
|
-
scrollStr = this.fg("dim", `${this.scrollOffset + 1}-${Math.min(this.scrollOffset + visibleHeight, totalLines)}/${totalLines}`);
|
|
524
|
+
scrollStr = this.overlay.fg("dim", `${this.scrollOffset + 1}-${Math.min(this.scrollOffset + visibleHeight, totalLines)}/${totalLines}`);
|
|
540
525
|
}
|
|
541
526
|
|
|
542
527
|
// Last updated for active group
|
|
@@ -545,17 +530,17 @@ export class InfoOverlay implements Component {
|
|
|
545
530
|
const age = lastUp > 0 ? humanizeAge(Date.now() - lastUp) : "loading…";
|
|
546
531
|
|
|
547
532
|
const hints = [
|
|
548
|
-
`${this.fg("accent", "←/→")} tabs`,
|
|
549
|
-
`${this.fg("success", "↑/↓")} scroll`,
|
|
550
|
-
`${this.fg("warning", "r")} refresh`,
|
|
551
|
-
`${this.fg("error", "q/Esc")} close`,
|
|
533
|
+
`${this.overlay.fg("accent", "←/→")} tabs`,
|
|
534
|
+
`${this.overlay.fg("success", "↑/↓")} scroll`,
|
|
535
|
+
`${this.overlay.fg("warning", "r")} refresh`,
|
|
536
|
+
`${this.overlay.fg("error", "q/Esc")} close`,
|
|
552
537
|
];
|
|
553
538
|
|
|
554
|
-
const hintStr = hints.join(` ${this.fg("borderMuted", "•")} `);
|
|
539
|
+
const hintStr = hints.join(` ${this.overlay.fg("borderMuted", "•")} `);
|
|
555
540
|
|
|
556
541
|
// Build right side: age + hints
|
|
557
|
-
const ageStr = this.fg("dim", `⏱ ${age}`);
|
|
558
|
-
const rightStr = `${ageStr} ${this.fg("borderMuted", "│")} ${hintStr}`;
|
|
542
|
+
const ageStr = this.overlay.fg("dim", `⏱ ${age}`);
|
|
543
|
+
const rightStr = `${ageStr} ${this.overlay.fg("borderMuted", "│")} ${hintStr}`;
|
|
559
544
|
|
|
560
545
|
const scrollW = visibleWidth(scrollStr);
|
|
561
546
|
const rightW = visibleWidth(rightStr);
|
|
@@ -25,13 +25,10 @@ export interface UsageStats {
|
|
|
25
25
|
today: number;
|
|
26
26
|
week: number;
|
|
27
27
|
month: number;
|
|
28
|
-
allTime: number;
|
|
29
28
|
};
|
|
30
29
|
/** Total cost by period (USD) */
|
|
31
30
|
cost: {
|
|
32
31
|
today: number;
|
|
33
|
-
week: number;
|
|
34
|
-
month: number;
|
|
35
32
|
allTime: number;
|
|
36
33
|
};
|
|
37
34
|
/** Token counts by model (all time) */
|
|
@@ -44,8 +41,6 @@ export interface UsageStats {
|
|
|
44
41
|
byModelMonth: Record<string, { tokens: number; cost: number; sessions: number }>;
|
|
45
42
|
/** Total sessions */
|
|
46
43
|
sessionCount: number;
|
|
47
|
-
/** Total messages */
|
|
48
|
-
messageCount: number;
|
|
49
44
|
}
|
|
50
45
|
|
|
51
46
|
/** Time period boundaries */
|
|
@@ -275,16 +270,8 @@ function collectSessionFiles(dir: string, files: string[]): void {
|
|
|
275
270
|
}
|
|
276
271
|
|
|
277
272
|
/**
|
|
278
|
-
* Parse all session files and aggregate usage stats
|
|
279
|
-
*
|
|
280
|
-
*/
|
|
281
|
-
export function parseUsageStats(): UsageStats {
|
|
282
|
-
const { stats } = collectStats(null);
|
|
283
|
-
return stats;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Async variant that yields to the event loop while parsing.
|
|
273
|
+
* Parse all session files and aggregate usage stats, yielding to the event
|
|
274
|
+
* loop while parsing.
|
|
288
275
|
*
|
|
289
276
|
* A cold parse is several seconds of pure CPU. Running it synchronously starves
|
|
290
277
|
* the event loop, so keystrokes queue up and the UI cannot repaint. Deferring
|
|
@@ -292,41 +279,34 @@ export function parseUsageStats(): UsageStats {
|
|
|
292
279
|
* `yieldEvery` files, control returns to the loop.
|
|
293
280
|
*/
|
|
294
281
|
export async function parseUsageStatsAsync(): Promise<UsageStats> {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
};
|
|
298
|
-
const { stats, pending } = collectStats(yielder);
|
|
299
|
-
if (pending) await pending;
|
|
282
|
+
const { stats, pending } = collectStats();
|
|
283
|
+
await pending;
|
|
300
284
|
return stats;
|
|
301
285
|
}
|
|
302
286
|
|
|
303
287
|
/** Empty stats accumulator. */
|
|
304
288
|
function emptyStats(): UsageStats {
|
|
305
289
|
return {
|
|
306
|
-
tokens: { today: 0, week: 0, month: 0
|
|
307
|
-
cost: { today: 0,
|
|
290
|
+
tokens: { today: 0, week: 0, month: 0 },
|
|
291
|
+
cost: { today: 0, allTime: 0 },
|
|
308
292
|
byModel: {},
|
|
309
293
|
byModelToday: {},
|
|
310
294
|
byModelWeek: {},
|
|
311
295
|
byModelMonth: {},
|
|
312
296
|
sessionCount: 0,
|
|
313
|
-
messageCount: 0,
|
|
314
297
|
};
|
|
315
298
|
}
|
|
316
299
|
|
|
317
300
|
/**
|
|
318
|
-
*
|
|
301
|
+
* Scan all session files and aggregate usage stats.
|
|
319
302
|
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
* as `pending` progresses, and the caller must await it.
|
|
303
|
+
* The returned `stats` object is filled in as `pending` progresses; the caller
|
|
304
|
+
* must await it.
|
|
323
305
|
*/
|
|
324
|
-
function collectStats(
|
|
325
|
-
yielder: (() => Promise<void>) | null,
|
|
326
|
-
): { stats: UsageStats; pending: Promise<void> | null } {
|
|
306
|
+
function collectStats(): { stats: UsageStats; pending: Promise<void> } {
|
|
327
307
|
const stats = emptyStats();
|
|
328
308
|
const sessionsDir = getSessionsDir();
|
|
329
|
-
if (!existsSync(sessionsDir)) return { stats, pending:
|
|
309
|
+
if (!existsSync(sessionsDir)) return { stats, pending: Promise.resolve() };
|
|
330
310
|
|
|
331
311
|
const sessionFiles: string[] = [];
|
|
332
312
|
collectSessionFiles(sessionsDir, sessionFiles);
|
|
@@ -405,7 +385,6 @@ function collectStats(
|
|
|
405
385
|
counted++;
|
|
406
386
|
const model = models[modelIdx] ?? "unknown";
|
|
407
387
|
|
|
408
|
-
stats.tokens.allTime += countedTokens;
|
|
409
388
|
stats.cost.allTime += cost;
|
|
410
389
|
bump(stats.byModel, model, countedTokens, cost);
|
|
411
390
|
|
|
@@ -416,19 +395,16 @@ function collectStats(
|
|
|
416
395
|
}
|
|
417
396
|
if (timestamp >= weekStart) {
|
|
418
397
|
stats.tokens.week += countedTokens;
|
|
419
|
-
stats.cost.week += cost;
|
|
420
398
|
bump(stats.byModelWeek, model, countedTokens, cost);
|
|
421
399
|
}
|
|
422
400
|
if (timestamp >= monthStart) {
|
|
423
401
|
stats.tokens.month += countedTokens;
|
|
424
|
-
stats.cost.month += cost;
|
|
425
402
|
bump(stats.byModelMonth, model, countedTokens, cost);
|
|
426
403
|
}
|
|
427
404
|
}
|
|
428
405
|
|
|
429
406
|
if (counted > 0) {
|
|
430
407
|
stats.sessionCount++;
|
|
431
|
-
stats.messageCount += counted;
|
|
432
408
|
}
|
|
433
409
|
};
|
|
434
410
|
|
|
@@ -440,17 +416,6 @@ function collectStats(
|
|
|
440
416
|
|
|
441
417
|
const YIELD_EVERY = 25;
|
|
442
418
|
|
|
443
|
-
if (!yielder) {
|
|
444
|
-
for (const item of work) {
|
|
445
|
-
const records = item.cached
|
|
446
|
-
? item.cached.records
|
|
447
|
-
: (nextFiles[item.path].records = extractRecords(item.path, modelIndex, models));
|
|
448
|
-
aggregate(records);
|
|
449
|
-
}
|
|
450
|
-
finish();
|
|
451
|
-
return { stats, pending: null };
|
|
452
|
-
}
|
|
453
|
-
|
|
454
419
|
const pending = (async () => {
|
|
455
420
|
let sinceYield = 0;
|
|
456
421
|
for (const item of work) {
|
|
@@ -467,7 +432,7 @@ function collectStats(
|
|
|
467
432
|
aggregate(records);
|
|
468
433
|
if (sinceYield >= YIELD_EVERY) {
|
|
469
434
|
sinceYield = 0;
|
|
470
|
-
await
|
|
435
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
471
436
|
}
|
|
472
437
|
}
|
|
473
438
|
finish();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/input-shortcuts",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "Keyboard shortcuts for stash/restore, undo/redo, clipboard, and thinking toggle — chord-based overlay system",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "2.6.
|
|
36
|
+
"@pi-unipi/core": "2.6.1"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import { Key } from "@earendil-works/pi-tui";
|
|
15
|
+
import { Key, matchesKey } from "@earendil-works/pi-tui";
|
|
16
16
|
import { MODULES, emitEvent, UNIPI_EVENTS, INPUT_SHORTCUTS_COMMANDS } from "@pi-unipi/core";
|
|
17
17
|
import { RegisterStore } from "./registers.ts";
|
|
18
18
|
import { UndoRedoBuffer } from "./undo-redo.ts";
|
|
@@ -20,6 +20,7 @@ import { ChordOverlay, type ChordCallbacks } from "./chord-overlay.ts";
|
|
|
20
20
|
import { SettingsOverlay } from "./settings-overlay.ts";
|
|
21
21
|
import { loadConfig } from "./settings-overlay.ts";
|
|
22
22
|
import { copyToClipboard } from "./clipboard.ts";
|
|
23
|
+
import { THINKING_CYCLE } from "./types.ts";
|
|
23
24
|
|
|
24
25
|
// ─── Status feedback ────────────────────────────────────────────────────────
|
|
25
26
|
|
|
@@ -111,7 +112,7 @@ export default function inputShortcutsExtension(pi: ExtensionAPI): void {
|
|
|
111
112
|
if (!ui || suppressInputListener) return;
|
|
112
113
|
|
|
113
114
|
// Only snapshot for edit keys (printable, backspace, delete, enter)
|
|
114
|
-
const isEditKey = data.length === 1 || data === "\x7f" || data
|
|
115
|
+
const isEditKey = data.length === 1 || data === "\x7f" || matchesKey(data, Key.delete) || data === "\r" || data === "\n";
|
|
115
116
|
if (!isEditKey) return;
|
|
116
117
|
|
|
117
118
|
// Capture text BEFORE the keypress is processed by the editor
|
|
@@ -254,7 +255,6 @@ export default function inputShortcutsExtension(pi: ExtensionAPI): void {
|
|
|
254
255
|
|
|
255
256
|
function doToggleThinking(): void {
|
|
256
257
|
const current = pi.getThinkingLevel();
|
|
257
|
-
const THINKING_CYCLE = ["off", "low", "medium", "high", "xhigh"] as const;
|
|
258
258
|
const idx = THINKING_CYCLE.indexOf(current as any);
|
|
259
259
|
const nextIdx = idx >= 0 ? (idx + 1) % THINKING_CYCLE.length : 0;
|
|
260
260
|
const next = THINKING_CYCLE[nextIdx];
|
|
@@ -44,14 +44,6 @@ export class RegisterStore {
|
|
|
44
44
|
return this.data!.registers[index] ?? "";
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/** Set a numbered register (0-9) and persist. */
|
|
48
|
-
setRegister(index: number, text: string): void {
|
|
49
|
-
if (index < 0 || index > 9) return;
|
|
50
|
-
this.ensureLoaded();
|
|
51
|
-
this.data!.registers[index] = text;
|
|
52
|
-
this.save();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
47
|
/** Lazy load from disk on first access. */
|
|
56
48
|
private ensureLoaded(): void {
|
|
57
49
|
if (this.loaded) return;
|