@pi-unipi/unipi 2.6.1 → 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.
Files changed (186) hide show
  1. package/package.json +1 -2
  2. package/packages/ask-user/settings-tui.ts +18 -34
  3. package/packages/autocomplete/src/constants.ts +0 -15
  4. package/packages/autocomplete/src/provider.ts +3 -17
  5. package/packages/btw/extensions/btw.ts +18 -45
  6. package/packages/compactor/README.md +2 -2
  7. package/packages/compactor/skills/compactor/SKILL.md +1 -1
  8. package/packages/compactor/skills/compactor-detail/SKILL.md +3 -5
  9. package/packages/compactor/skills/compactor-doctor/SKILL.md +1 -1
  10. package/packages/compactor/skills/compactor-stats/SKILL.md +1 -1
  11. package/packages/compactor/src/commands/index.ts +47 -78
  12. package/packages/compactor/src/compaction/brief.ts +161 -90
  13. package/packages/compactor/src/compaction/build-sections.ts +3 -4
  14. package/packages/compactor/src/compaction/compact-args.ts +86 -0
  15. package/packages/compactor/src/compaction/cut.ts +270 -28
  16. package/packages/compactor/src/compaction/drill-down.ts +261 -0
  17. package/packages/compactor/src/compaction/format-recall.ts +96 -0
  18. package/packages/compactor/src/compaction/format.ts +8 -3
  19. package/packages/compactor/src/compaction/hooks.ts +248 -72
  20. package/packages/compactor/src/compaction/merge.ts +34 -4
  21. package/packages/compactor/src/compaction/rank.ts +270 -0
  22. package/packages/compactor/src/compaction/recall-scope.ts +28 -0
  23. package/packages/compactor/src/compaction/search-entries.ts +333 -96
  24. package/packages/compactor/src/compaction/skill-collapse.ts +35 -0
  25. package/packages/compactor/src/compaction/summarize.ts +37 -6
  26. package/packages/compactor/src/compaction/token-estimate.ts +104 -0
  27. package/packages/compactor/src/compaction/touched-files.ts +35 -0
  28. package/packages/compactor/src/config/manager.ts +2 -27
  29. package/packages/compactor/src/config/presets.ts +0 -2
  30. package/packages/compactor/src/config/schema.ts +2 -16
  31. package/packages/compactor/src/executor/executor.ts +6 -15
  32. package/packages/compactor/src/executor/runtime.ts +2 -12
  33. package/packages/compactor/src/index.ts +4 -121
  34. package/packages/compactor/src/info-screen.ts +3 -10
  35. package/packages/compactor/src/security/evaluator.ts +0 -53
  36. package/packages/compactor/src/security/policy.ts +7 -8
  37. package/packages/compactor/src/session/db.ts +0 -6
  38. package/packages/compactor/src/tools/ctx-execute-file.ts +0 -5
  39. package/packages/compactor/src/tools/register.ts +27 -50
  40. package/packages/compactor/src/tools/vcc-recall.ts +86 -48
  41. package/packages/compactor/src/tui/settings-overlay.ts +20 -40
  42. package/packages/compactor/src/types.ts +43 -100
  43. package/packages/core/constants.ts +0 -28
  44. package/packages/core/events.ts +1 -83
  45. package/packages/core/index.ts +1 -0
  46. package/packages/core/package.json +2 -0
  47. package/packages/core/tui-overlay.ts +89 -0
  48. package/packages/core/tui-width.ts +18 -0
  49. package/packages/core/utils.ts +43 -0
  50. package/packages/footer/src/help.ts +2 -2
  51. package/packages/footer/src/index.ts +9 -10
  52. package/packages/footer/src/presets.ts +1 -27
  53. package/packages/footer/src/registry/index.ts +0 -23
  54. package/packages/footer/src/rendering/icons.ts +0 -13
  55. package/packages/footer/src/rendering/renderer.ts +5 -40
  56. package/packages/footer/src/rendering/separators.ts +0 -10
  57. package/packages/footer/src/rendering/theme.ts +1 -15
  58. package/packages/footer/src/segments/compactor.ts +7 -14
  59. package/packages/footer/src/segments/core.ts +15 -54
  60. package/packages/footer/src/segments/kanboard.ts +5 -5
  61. package/packages/footer/src/segments/mcp.ts +5 -5
  62. package/packages/footer/src/segments/memory.ts +4 -4
  63. package/packages/footer/src/segments/notify.ts +2 -2
  64. package/packages/footer/src/segments/ralph.ts +3 -3
  65. package/packages/footer/src/segments/status-ext.ts +1 -1
  66. package/packages/footer/src/segments/workflow.ts +3 -3
  67. package/packages/footer/src/tps-tracker.ts +0 -8
  68. package/packages/footer/src/tui/settings-tui.ts +14 -31
  69. package/packages/footer/src/types.ts +0 -15
  70. package/packages/image/src/register-providers.ts +0 -11
  71. package/packages/image/src/tools.ts +0 -3
  72. package/packages/image/src/tui/model-selector.ts +36 -71
  73. package/packages/info-screen/config.ts +0 -16
  74. package/packages/info-screen/core-groups.ts +0 -10
  75. package/packages/info-screen/index.ts +22 -13
  76. package/packages/info-screen/registry.ts +1 -72
  77. package/packages/info-screen/tui/info-overlay.ts +85 -100
  78. package/packages/info-screen/usage-parser.ts +12 -47
  79. package/packages/input-shortcuts/src/index.ts +3 -3
  80. package/packages/input-shortcuts/src/registers.ts +0 -8
  81. package/packages/input-shortcuts/src/settings-overlay.ts +4 -13
  82. package/packages/input-shortcuts/src/undo-redo.ts +0 -12
  83. package/packages/mcp/src/bridge/client.ts +0 -15
  84. package/packages/mcp/src/config/manager.ts +0 -38
  85. package/packages/mcp/src/config/schema.ts +0 -27
  86. package/packages/mcp/src/config/sync.ts +0 -29
  87. package/packages/mcp/src/index.ts +1 -1
  88. package/packages/mcp/src/tui/add-overlay.ts +6 -6
  89. package/packages/mcp/src/types.ts +0 -6
  90. package/packages/memory/embedding.ts +0 -31
  91. package/packages/memory/mempalace.ts +0 -16
  92. package/packages/memory/package.json +2 -6
  93. package/packages/memory/storage.ts +56 -753
  94. package/packages/milestone/hooks.ts +1 -6
  95. package/packages/milestone/milestone.ts +2 -41
  96. package/packages/notify/events.ts +0 -10
  97. package/packages/notify/settings.ts +0 -19
  98. package/packages/notify/tui/gotify-setup.ts +108 -143
  99. package/packages/notify/tui/ntfy-setup.ts +137 -172
  100. package/packages/notify/tui/recap-model-selector.ts +36 -71
  101. package/packages/notify/tui/settings-overlay.ts +50 -86
  102. package/packages/notify/tui/telegram-setup.ts +48 -79
  103. package/packages/notify/types.ts +0 -2
  104. package/packages/ralph/index.ts +11 -35
  105. package/packages/ralph/ralph-loop.ts +3 -44
  106. package/packages/subagents/package.json +1 -0
  107. package/packages/subagents/src/agent-manager.ts +0 -51
  108. package/packages/subagents/src/agent-runner.ts +6 -80
  109. package/packages/subagents/src/conversation-viewer.ts +1 -19
  110. package/packages/subagents/src/core-compat.ts +8 -25
  111. package/packages/subagents/src/custom-agents.ts +0 -1
  112. package/packages/subagents/src/index.ts +3 -48
  113. package/packages/subagents/src/types.ts +0 -11
  114. package/packages/subagents/src/widget.ts +5 -5
  115. package/packages/unipi/bundled.js +4330 -7707
  116. package/packages/unipi/index.ts +6 -3
  117. package/packages/updater/src/changelog.ts +1 -16
  118. package/packages/updater/src/index.ts +0 -5
  119. package/packages/updater/src/installer.ts +4 -26
  120. package/packages/updater/src/markdown.ts +6 -152
  121. package/packages/updater/src/settings.ts +0 -15
  122. package/packages/updater/src/tui/changelog-overlay.ts +42 -234
  123. package/packages/updater/src/tui/list-detail-overlay.ts +260 -0
  124. package/packages/updater/src/tui/readme-overlay.ts +53 -210
  125. package/packages/updater/src/tui/settings-overlay.ts +13 -26
  126. package/packages/updater/src/version.ts +2 -31
  127. package/packages/utility/package.json +1 -3
  128. package/packages/utility/src/commands.ts +4 -5
  129. package/packages/utility/src/herdr-sync.ts +136 -0
  130. package/packages/utility/src/index.ts +11 -68
  131. package/packages/utility/src/lifecycle/cleanup.ts +2 -16
  132. package/packages/utility/src/{diff/settings.ts → settings.ts} +42 -80
  133. package/packages/utility/src/tools/env.ts +0 -0
  134. package/packages/utility/src/tui/name-badge-state.ts +18 -2
  135. package/packages/utility/src/tui/name-badge.ts +5 -4
  136. package/packages/utility/src/tui/util-settings-tui.ts +41 -224
  137. package/packages/web-api/package.json +0 -4
  138. package/packages/web-api/src/engine/dependencies.ts +13 -134
  139. package/packages/web-api/src/engine/extract.ts +9 -99
  140. package/packages/web-api/src/engine/format.ts +0 -75
  141. package/packages/web-api/src/engine/types.ts +0 -39
  142. package/packages/web-api/src/index.ts +1 -2
  143. package/packages/web-api/src/providers/base.ts +0 -10
  144. package/packages/web-api/src/providers/duckduckgo.ts +0 -1
  145. package/packages/web-api/src/providers/firecrawl.ts +0 -9
  146. package/packages/web-api/src/providers/jina-reader.ts +0 -9
  147. package/packages/web-api/src/providers/jina-search.ts +0 -9
  148. package/packages/web-api/src/providers/perplexity.ts +0 -9
  149. package/packages/web-api/src/providers/registry.ts +0 -80
  150. package/packages/web-api/src/providers/serpapi.ts +0 -9
  151. package/packages/web-api/src/providers/tavily.ts +0 -9
  152. package/packages/web-api/src/providers/wigolo-client.ts +0 -30
  153. package/packages/web-api/src/providers/wigolo.ts +0 -1
  154. package/packages/web-api/src/settings.ts +17 -77
  155. package/packages/web-api/src/tools.ts +21 -24
  156. package/packages/web-api/src/tui/settings-dialog.ts +0 -1
  157. package/packages/workflow/commands.ts +38 -122
  158. package/packages/workflow/index.ts +1 -5
  159. package/packages/cocoindex/README.md +0 -94
  160. package/packages/cocoindex/bridge.ts +0 -842
  161. package/packages/cocoindex/commands.ts +0 -175
  162. package/packages/cocoindex/index.ts +0 -48
  163. package/packages/cocoindex/installer.ts +0 -397
  164. package/packages/cocoindex/package.json +0 -49
  165. package/packages/cocoindex/skills/cocoindex/SKILL.md +0 -88
  166. package/packages/cocoindex/tools.ts +0 -157
  167. package/packages/compactor/src/display/diff-renderer.ts +0 -281
  168. package/packages/compactor/src/display/line-width-safety.ts +0 -28
  169. package/packages/compactor/src/display/render-utils.ts +0 -52
  170. package/packages/compactor/src/display/thinking-label.ts +0 -18
  171. package/packages/compactor/src/display/tool-overrides.ts +0 -136
  172. package/packages/compactor/src/tools/compact.ts +0 -20
  173. package/packages/subagents/src/file-lock.ts +0 -102
  174. package/packages/utility/src/cache/ttl-cache.ts +0 -311
  175. package/packages/utility/src/diff/highlighter.ts +0 -352
  176. package/packages/utility/src/diff/parser.ts +0 -191
  177. package/packages/utility/src/diff/renderer.ts +0 -395
  178. package/packages/utility/src/diff/theme.ts +0 -316
  179. package/packages/utility/src/diff/wrapper.ts +0 -339
  180. package/packages/utility/src/display/capabilities.ts +0 -214
  181. package/packages/utility/src/display/width.ts +0 -226
  182. package/packages/utility/src/tools/batch.ts +0 -229
  183. package/packages/utility/src/tui/badge-settings-tui.ts +0 -388
  184. package/packages/utility/src/tui/badge-settings.ts +0 -67
  185. package/packages/utility/src/tui/settings-inspector.ts +0 -303
  186. package/packages/web-api/src/providers/llm-summarize.ts +0 -71
@@ -145,8 +145,6 @@ export interface FooterSegment {
145
145
  description: string;
146
146
  /** Layout zone assignment */
147
147
  zone: SegmentZone;
148
- /** Icon glyph (Nerd Font or ASCII) */
149
- icon: string;
150
148
  /** Render function */
151
149
  render: SegmentRenderFn;
152
150
  /** Whether this segment is shown by default */
@@ -161,8 +159,6 @@ export interface FooterGroup {
161
159
  id: string;
162
160
  /** Display name */
163
161
  name: string;
164
- /** Group icon */
165
- icon: string;
166
162
  /** Segments within this group */
167
163
  segments: FooterSegment[];
168
164
  /** Whether this group is shown by default */
@@ -199,13 +195,6 @@ export interface FooterSettings {
199
195
  groups: Record<string, FooterGroupSettings>;
200
196
  }
201
197
 
202
- /** Full footer config (settings + runtime state) */
203
- export interface FooterConfig {
204
- settings: FooterSettings;
205
- /** Runtime: whether footer is currently active */
206
- active: boolean;
207
- }
208
-
209
198
  // ─── Presets ────────────────────────────────────────────────────────────────
210
199
 
211
200
  /** Preset definition */
@@ -218,10 +207,6 @@ export interface PresetDef {
218
207
  secondarySegments: string[];
219
208
  /** Color scheme for this preset */
220
209
  colors?: ColorScheme;
221
- /** Per-segment options */
222
- segmentOptions?: Record<string, Record<string, unknown>>;
223
- /** Zone order (default: left, center, right) */
224
- zoneOrder?: ("left" | "center" | "right")[];
225
210
  /** Zone separator string (between zones) */
226
211
  zoneSeparator?: string;
227
212
  }
@@ -207,14 +207,3 @@ async function resolveProviderKey(
207
207
  const envName = `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
208
208
  return process.env[envName] || undefined;
209
209
  }
210
-
211
- /** Provider ids pi-ai can currently generate with, after registration. */
212
- export function registeredProviderIds(images: {
213
- getProviders?: () => ReadonlyArray<{ id: string }>;
214
- }): string[] {
215
- try {
216
- return (images.getProviders?.() ?? []).map((p) => p.id);
217
- } catch {
218
- return [];
219
- }
220
- }
@@ -19,7 +19,6 @@ import {
19
19
  listAllImageGenModels,
20
20
  resolveImageGenModel,
21
21
  resolveVisionModel,
22
- splitModelRef,
23
22
  type ChatModelRegistry,
24
23
  type VisionModel,
25
24
  } from "./models.js";
@@ -325,5 +324,3 @@ function currentSessionModel(ctx: ExtensionContext): string {
325
324
  }
326
325
  return "";
327
326
  }
328
-
329
- export { splitModelRef };
@@ -6,9 +6,9 @@
6
6
  */
7
7
 
8
8
  import type { Component } from "@earendil-works/pi-tui";
9
- import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
9
+ import { matchesKey } from "@earendil-works/pi-tui";
10
10
  import type { Theme } from "@earendil-works/pi-coding-agent";
11
- import { boxInnerWidth, safeRepeat } from "@pi-unipi/core";
11
+ import { boxInnerWidth, OverlayTheme } from "@pi-unipi/core";
12
12
 
13
13
  export interface SelectableModel {
14
14
  provider: string;
@@ -29,7 +29,7 @@ export class ImageModelSelectorOverlay implements Component {
29
29
  private filterMode = false;
30
30
  private saved = false;
31
31
  private error: string | null = null;
32
- private theme: Theme | null = null;
32
+ private overlay = new OverlayTheme();
33
33
  /** Free-text entry, for models the catalog does not know about. */
34
34
  private customMode = false;
35
35
  private custom = "";
@@ -55,7 +55,7 @@ export class ImageModelSelectorOverlay implements Component {
55
55
  }
56
56
 
57
57
  setTheme(theme: Theme): void {
58
- this.theme = theme;
58
+ this.overlay.setTheme(theme);
59
59
  }
60
60
 
61
61
  invalidate(): void {}
@@ -229,41 +229,6 @@ export class ImageModelSelectorOverlay implements Component {
229
229
  this.onClose?.();
230
230
  }
231
231
 
232
- // ─── Theme helpers ───────────────────────────────────────────────────
233
-
234
- private fg(color: string, text: string): string {
235
- if (this.theme) return this.theme.fg(color as never, text);
236
- const codes: Record<string, string> = {
237
- accent: "\x1b[36m",
238
- success: "\x1b[32m",
239
- warning: "\x1b[33m",
240
- error: "\x1b[31m",
241
- dim: "\x1b[2m",
242
- borderMuted: "\x1b[90m",
243
- };
244
- return `${codes[color] ?? ""}${text}\x1b[0m`;
245
- }
246
-
247
- private bold(text: string): string {
248
- return this.theme ? this.theme.bold(text) : `\x1b[1m${text}\x1b[0m`;
249
- }
250
-
251
- private frameLine(content: string, innerWidth: number): string {
252
- const truncated = truncateToWidth(content, innerWidth, "");
253
- const padding = safeRepeat(" ", innerWidth - visibleWidth(truncated));
254
- return `${this.fg("borderMuted", "│")}${truncated}${padding}${this.fg("borderMuted", "│")}`;
255
- }
256
-
257
- private ruleLine(innerWidth: number): string {
258
- return this.fg("borderMuted", `├${safeRepeat("─", innerWidth)}┤`);
259
- }
260
-
261
- private borderLine(innerWidth: number, edge: "top" | "bottom"): string {
262
- const left = edge === "top" ? "┌" : "└";
263
- const right = edge === "top" ? "┐" : "┘";
264
- return this.fg("borderMuted", `${left}${safeRepeat("─", innerWidth)}${right}`);
265
- }
266
-
267
232
  render(width: number): string[] {
268
233
  const innerWidth = boxInnerWidth(width);
269
234
  const lines: string[] = [];
@@ -277,48 +242,48 @@ export class ImageModelSelectorOverlay implements Component {
277
242
  ? "Model used by image_generate"
278
243
  : "Vision model used by image_recognize";
279
244
 
280
- lines.push(this.borderLine(innerWidth, "top"));
281
- lines.push(this.frameLine(this.fg("accent", this.bold(title)), innerWidth));
282
- lines.push(this.frameLine(this.fg("dim", subtitle), innerWidth));
283
- lines.push(this.ruleLine(innerWidth));
245
+ lines.push(this.overlay.borderLine(innerWidth, "top"));
246
+ lines.push(this.overlay.frameLine(this.overlay.fg("accent", this.overlay.bold(title)), innerWidth));
247
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", subtitle), innerWidth));
248
+ lines.push(this.overlay.ruleLine(innerWidth));
284
249
 
285
250
  // Filter bar
286
251
  if (this.customMode) {
287
252
  lines.push(
288
- this.frameLine(
289
- ` ${this.fg("accent", "Model:")} ${this.custom}${this.fg("accent", "█")}`,
253
+ this.overlay.frameLine(
254
+ ` ${this.overlay.fg("accent", "Model:")} ${this.custom}${this.overlay.fg("accent", "█")}`,
290
255
  innerWidth,
291
256
  ),
292
257
  );
293
258
  lines.push(
294
- this.frameLine(
295
- ` ${this.fg("dim", "e.g. omniroute/fal/fal-ai/flux-2-pro")}`,
259
+ this.overlay.frameLine(
260
+ ` ${this.overlay.fg("dim", "e.g. omniroute/fal/fal-ai/flux-2-pro")}`,
296
261
  innerWidth,
297
262
  ),
298
263
  );
299
264
  } else if (this.filterMode) {
300
265
  lines.push(
301
- this.frameLine(
302
- ` ${this.fg("accent", "Filter:")} ${this.filter}${this.fg("accent", "█")}`,
266
+ this.overlay.frameLine(
267
+ ` ${this.overlay.fg("accent", "Filter:")} ${this.filter}${this.overlay.fg("accent", "█")}`,
303
268
  innerWidth,
304
269
  ),
305
270
  );
306
271
  } else if (this.filter) {
307
272
  lines.push(
308
- this.frameLine(
309
- ` ${this.fg("dim", "Filter:")} ${this.filter} ${this.fg("dim", "(press / to edit)")}`,
273
+ this.overlay.frameLine(
274
+ ` ${this.overlay.fg("dim", "Filter:")} ${this.filter} ${this.overlay.fg("dim", "(press / to edit)")}`,
310
275
  innerWidth,
311
276
  ),
312
277
  );
313
278
  } else {
314
279
  lines.push(
315
- this.frameLine(
316
- ` ${this.fg("dim", `${this.models.length} models · / filter · c custom`)}`,
280
+ this.overlay.frameLine(
281
+ ` ${this.overlay.fg("dim", `${this.models.length} models · / filter · c custom`)}`,
317
282
  innerWidth,
318
283
  ),
319
284
  );
320
285
  }
321
- lines.push(this.ruleLine(innerWidth));
286
+ lines.push(this.overlay.ruleLine(innerWidth));
322
287
 
323
288
  // Model list
324
289
  const terminalRows = process.stdout.rows ?? 30;
@@ -335,47 +300,47 @@ export class ImageModelSelectorOverlay implements Component {
335
300
  ? "No image models available (needs OpenRouter)"
336
301
  : "No vision-capable models configured"
337
302
  : "No models match the filter";
338
- lines.push(this.frameLine(` ${this.fg("dim", empty)}`, innerWidth));
303
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("dim", empty)}`, innerWidth));
339
304
  } else {
340
305
  for (let i = start; i < end; i++) {
341
306
  const model = this.filtered[i];
342
307
  const isSelected = i === this.selectedIndex;
343
- const marker = isSelected ? this.fg("accent", "▸") : " ";
308
+ const marker = isSelected ? this.overlay.fg("accent", "▸") : " ";
344
309
  const label = model.name || model.id;
345
- const providerTag = this.fg("dim", `[${model.provider}]`);
310
+ const providerTag = this.overlay.fg("dim", `[${model.provider}]`);
346
311
  const base = isSelected
347
- ? `${providerTag} ${this.bold(label)}`
348
- : `${providerTag} ${this.fg("dim", label)}`;
312
+ ? `${providerTag} ${this.overlay.bold(label)}`
313
+ : `${providerTag} ${this.overlay.fg("dim", label)}`;
349
314
  const display = model.unavailable
350
- ? `${base} ${this.fg("warning", `(${model.unavailable})`)}`
315
+ ? `${base} ${this.overlay.fg("warning", `(${model.unavailable})`)}`
351
316
  : base;
352
- lines.push(this.frameLine(` ${marker} ${display}`, innerWidth));
317
+ lines.push(this.overlay.frameLine(` ${marker} ${display}`, innerWidth));
353
318
  }
354
319
  }
355
320
 
356
321
  if (!this.customMode && this.filtered.length > maxVisible) {
357
322
  const pct = Math.round(((this.selectedIndex + 1) / this.filtered.length) * 100);
358
323
  lines.push(
359
- this.frameLine(
360
- this.fg("dim", ` ${pct}% (${this.selectedIndex + 1}/${this.filtered.length})`),
324
+ this.overlay.frameLine(
325
+ this.overlay.fg("dim", ` ${pct}% (${this.selectedIndex + 1}/${this.filtered.length})`),
361
326
  innerWidth,
362
327
  ),
363
328
  );
364
329
  }
365
330
 
366
331
  if (this.error) {
367
- lines.push(this.ruleLine(innerWidth));
368
- lines.push(this.frameLine(` ${this.fg("error", `⚠ ${this.error}`)}`, innerWidth));
332
+ lines.push(this.overlay.ruleLine(innerWidth));
333
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("error", `⚠ ${this.error}`)}`, innerWidth));
369
334
  }
370
335
  if (this.saved) {
371
- lines.push(this.ruleLine(innerWidth));
372
- lines.push(this.frameLine(` ${this.fg("success", "✓ Model saved")}`, innerWidth));
336
+ lines.push(this.overlay.ruleLine(innerWidth));
337
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("success", "✓ Model saved")}`, innerWidth));
373
338
  }
374
339
 
375
- lines.push(this.ruleLine(innerWidth));
340
+ lines.push(this.overlay.ruleLine(innerWidth));
376
341
  lines.push(
377
- this.frameLine(
378
- this.fg(
342
+ this.overlay.frameLine(
343
+ this.overlay.fg(
379
344
  "dim",
380
345
  this.customMode
381
346
  ? "Enter save · Esc back to list"
@@ -384,7 +349,7 @@ export class ImageModelSelectorOverlay implements Component {
384
349
  innerWidth,
385
350
  ),
386
351
  );
387
- lines.push(this.borderLine(innerWidth, "bottom"));
352
+ lines.push(this.overlay.borderLine(innerWidth, "bottom"));
388
353
 
389
354
  return lines;
390
355
  }
@@ -164,15 +164,6 @@ export function setGroupSettings(groupId: string, groupSettings: GroupSettings):
164
164
  saveInfoSettings(settings);
165
165
  }
166
166
 
167
- /**
168
- * Check if a group is enabled.
169
- */
170
- export function isGroupEnabled(groupId: string): boolean {
171
- const settings = getInfoSettings();
172
- if (!(groupId in settings.groups)) return true; // Default to enabled
173
- return settings.groups[groupId].show;
174
- }
175
-
176
167
  /**
177
168
  * Check if a stat within a group is enabled.
178
169
  */
@@ -182,10 +173,3 @@ export function isStatEnabled(groupId: string, statId: string): boolean {
182
173
  if (!(statId in groupSettings.stats)) return true;
183
174
  return groupSettings.stats[statId];
184
175
  }
185
-
186
- /**
187
- * Clear cached settings (for testing or reload).
188
- */
189
- export function clearSettingsCache(): void {
190
- cachedSettings = null;
191
- }
@@ -141,11 +141,6 @@ export function startLoadTracking(): void {
141
141
  }
142
142
  }
143
143
 
144
- /** Record when a module starts loading */
145
- export function recordModuleStart(name: string): void {
146
- moduleStartTimes.set(name, Date.now());
147
- }
148
-
149
144
  /** Record a load time */
150
145
  export function recordLoadTime(name: string, type: string, ms?: number): void {
151
146
  // If no ms provided, calculate from start time
@@ -172,11 +167,6 @@ export function finishLoadTracking(): void {
172
167
  }
173
168
  }
174
169
 
175
- /** Get load times */
176
- export function getLoadTimes(): Array<{ name: string; type: string; ms: number }> {
177
- return [...loadTimes];
178
- }
179
-
180
170
  /** Get total load time */
181
171
  export function getTotalLoadTime(): number {
182
172
  return totalLoadTimeMs > 0 ? totalLoadTimeMs : (loadTrackingStarted ? Date.now() - loadTrackingStartMs : 0);
@@ -12,12 +12,12 @@
12
12
  import { dirname } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
14
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
15
- import { UNIPI_EVENTS, MODULES, UNIPI_PREFIX, emitEvent, getPackageVersion, type UnipiModuleEvent, type UnipiInfoGroupEvent } from "@pi-unipi/core";
15
+ import { UNIPI_EVENTS, MODULES, UNIPI_PREFIX, emitEvent, getPackageVersion, type UnipiModuleEvent } from "@pi-unipi/core";
16
16
  import { infoRegistry } from "./registry.js";
17
- import { registerCoreGroups, trackModule, trackTool, setPiApi, registerSkillDir, startLoadTracking, recordLoadTime, finishLoadTracking, recordModuleStart } from "./core-groups.js";
17
+ import { registerCoreGroups, trackModule, trackTool, setPiApi, registerSkillDir, startLoadTracking, recordLoadTime, finishLoadTracking } from "./core-groups.js";
18
18
 
19
19
  /** Re-export for external use */
20
- export { infoRegistry, registerSkillDir, startLoadTracking, recordLoadTime, finishLoadTracking, recordModuleStart };
20
+ export { infoRegistry, registerSkillDir, startLoadTracking, recordLoadTime, finishLoadTracking };
21
21
  import { getInfoSettings } from "./config.js";
22
22
  import { InfoOverlay } from "./tui/info-overlay.js";
23
23
  import { SettingsOverlay } from "./settings/settings-tui.js";
@@ -95,10 +95,6 @@ export default function (pi: ExtensionAPI) {
95
95
  }
96
96
  });
97
97
 
98
- pi.events.on(UNIPI_EVENTS.INFO_GROUP_REGISTERED, (_data) => {
99
- // Group already registered via globalThis in registerGroup()
100
- });
101
-
102
98
  // Track built-in tools
103
99
  const trackedBuiltinTools = new Set<string>();
104
100
  pi.on("tool_call", async (event, _ctx) => {
@@ -116,9 +112,10 @@ export default function (pi: ExtensionAPI) {
116
112
  * Background: each group fetches independently, overlay re-renders reactively.
117
113
  */
118
114
  function showOverlay(ctx: ExtensionContext, autoCloseMs?: number): void {
115
+ let overlay: InfoOverlay;
119
116
  ctx.ui.custom<void>(
120
117
  (tui, theme, _keybindings, done) => {
121
- const overlay = new InfoOverlay();
118
+ overlay = new InfoOverlay();
122
119
  overlay.setTheme(theme);
123
120
  overlayVisible = true;
124
121
  overlay.onClose = () => {
@@ -127,11 +124,7 @@ export default function (pi: ExtensionAPI) {
127
124
  done();
128
125
  };
129
126
  overlay.requestRender = () => tui.requestRender();
130
- // Boot dashboard dismisses itself; any keypress cancels the timer.
131
- if (autoCloseMs && autoCloseMs > 0) {
132
- overlay.startBootTimer(autoCloseMs);
133
- }
134
- return {
127
+ const component = {
135
128
  render: (w: number) => overlay.render(w),
136
129
  invalidate: () => overlay.invalidate(),
137
130
  handleInput: (data: string) => {
@@ -139,6 +132,11 @@ export default function (pi: ExtensionAPI) {
139
132
  tui.requestRender();
140
133
  },
141
134
  };
135
+ // Boot dashboard dismisses itself; any keypress cancels the timer.
136
+ if (autoCloseMs && autoCloseMs > 0) {
137
+ overlay.startBootTimer(autoCloseMs);
138
+ }
139
+ return component;
142
140
  },
143
141
  {
144
142
  overlay: true,
@@ -148,6 +146,17 @@ export default function (pi: ExtensionAPI) {
148
146
  anchor: "center" as const,
149
147
  margin: 2,
150
148
  },
149
+ // `done()` (the extension UI's close callback) pops the *topmost* overlay
150
+ // in the TUI stack, not this one specifically. When another overlay (e.g.
151
+ // the updater's "Update Available" prompt) is stacked on top, the boot
152
+ // auto-close timer must not fire `done()` — that would pop the covering
153
+ // overlay and strand this dashboard with a spent one-shot close the user
154
+ // can no longer dismiss. `isTopmostOverlay` lets the boot timer defer
155
+ // until we are the focused (topmost) entry; the user can still press
156
+ // q/Esc to close once the covering overlay is gone.
157
+ onHandle: (handle) => {
158
+ overlay.isTopmostOverlay = () => handle.isFocused();
159
+ },
151
160
  }
152
161
  );
153
162
  }
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { InfoGroup, GroupData } from "./types.js";
9
- import { getInfoSettings, isStatEnabled } from "./config.js";
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
- };