@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.
Files changed (205) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +21 -22
  3. package/packages/ask-user/package.json +2 -2
  4. package/packages/ask-user/settings-tui.ts +18 -34
  5. package/packages/autocomplete/package.json +1 -1
  6. package/packages/autocomplete/src/constants.ts +2 -15
  7. package/packages/autocomplete/src/provider.ts +3 -17
  8. package/packages/btw/extensions/btw.ts +18 -45
  9. package/packages/btw/package.json +2 -2
  10. package/packages/compactor/README.md +2 -2
  11. package/packages/compactor/package.json +3 -3
  12. package/packages/compactor/skills/compactor/SKILL.md +1 -1
  13. package/packages/compactor/skills/compactor-detail/SKILL.md +3 -5
  14. package/packages/compactor/skills/compactor-doctor/SKILL.md +1 -1
  15. package/packages/compactor/skills/compactor-stats/SKILL.md +1 -1
  16. package/packages/compactor/src/commands/index.ts +47 -78
  17. package/packages/compactor/src/compaction/brief.ts +161 -90
  18. package/packages/compactor/src/compaction/build-sections.ts +3 -4
  19. package/packages/compactor/src/compaction/compact-args.ts +86 -0
  20. package/packages/compactor/src/compaction/cut.ts +270 -28
  21. package/packages/compactor/src/compaction/drill-down.ts +261 -0
  22. package/packages/compactor/src/compaction/format-recall.ts +96 -0
  23. package/packages/compactor/src/compaction/format.ts +8 -3
  24. package/packages/compactor/src/compaction/hooks.ts +248 -72
  25. package/packages/compactor/src/compaction/merge.ts +34 -4
  26. package/packages/compactor/src/compaction/rank.ts +270 -0
  27. package/packages/compactor/src/compaction/recall-scope.ts +28 -0
  28. package/packages/compactor/src/compaction/search-entries.ts +333 -96
  29. package/packages/compactor/src/compaction/skill-collapse.ts +35 -0
  30. package/packages/compactor/src/compaction/summarize.ts +37 -6
  31. package/packages/compactor/src/compaction/token-estimate.ts +104 -0
  32. package/packages/compactor/src/compaction/touched-files.ts +35 -0
  33. package/packages/compactor/src/config/manager.ts +2 -27
  34. package/packages/compactor/src/config/presets.ts +0 -2
  35. package/packages/compactor/src/config/schema.ts +2 -16
  36. package/packages/compactor/src/executor/executor.ts +6 -15
  37. package/packages/compactor/src/executor/runtime.ts +2 -12
  38. package/packages/compactor/src/index.ts +4 -121
  39. package/packages/compactor/src/info-screen.ts +3 -10
  40. package/packages/compactor/src/security/evaluator.ts +0 -53
  41. package/packages/compactor/src/security/policy.ts +7 -8
  42. package/packages/compactor/src/session/db.ts +0 -6
  43. package/packages/compactor/src/tools/ctx-execute-file.ts +0 -5
  44. package/packages/compactor/src/tools/register.ts +27 -50
  45. package/packages/compactor/src/tools/vcc-recall.ts +86 -48
  46. package/packages/compactor/src/tui/settings-overlay.ts +20 -40
  47. package/packages/compactor/src/types.ts +43 -100
  48. package/packages/core/constants.ts +1 -28
  49. package/packages/core/events.ts +1 -83
  50. package/packages/core/index.ts +1 -0
  51. package/packages/core/model-cache.ts +18 -12
  52. package/packages/core/package.json +3 -1
  53. package/packages/core/tui-overlay.ts +89 -0
  54. package/packages/core/tui-width.ts +18 -0
  55. package/packages/core/utils.ts +43 -0
  56. package/packages/footer/package.json +2 -2
  57. package/packages/footer/src/help.ts +2 -2
  58. package/packages/footer/src/index.ts +9 -10
  59. package/packages/footer/src/presets.ts +1 -27
  60. package/packages/footer/src/registry/index.ts +0 -23
  61. package/packages/footer/src/rendering/icons.ts +0 -13
  62. package/packages/footer/src/rendering/renderer.ts +5 -40
  63. package/packages/footer/src/rendering/separators.ts +0 -10
  64. package/packages/footer/src/rendering/theme.ts +1 -15
  65. package/packages/footer/src/segments/compactor.ts +7 -14
  66. package/packages/footer/src/segments/core.ts +15 -54
  67. package/packages/footer/src/segments/kanboard.ts +5 -5
  68. package/packages/footer/src/segments/mcp.ts +5 -5
  69. package/packages/footer/src/segments/memory.ts +4 -4
  70. package/packages/footer/src/segments/notify.ts +2 -2
  71. package/packages/footer/src/segments/ralph.ts +3 -3
  72. package/packages/footer/src/segments/status-ext.ts +1 -1
  73. package/packages/footer/src/segments/workflow.ts +3 -3
  74. package/packages/footer/src/tps-tracker.ts +0 -8
  75. package/packages/footer/src/tui/settings-tui.ts +14 -31
  76. package/packages/footer/src/types.ts +0 -15
  77. package/packages/image/package.json +2 -2
  78. package/packages/image/src/register-providers.ts +0 -11
  79. package/packages/image/src/tools.ts +0 -3
  80. package/packages/image/src/tui/model-selector.ts +36 -71
  81. package/packages/info-screen/config.ts +0 -16
  82. package/packages/info-screen/core-groups.ts +0 -10
  83. package/packages/info-screen/index.ts +22 -13
  84. package/packages/info-screen/package.json +2 -2
  85. package/packages/info-screen/registry.ts +1 -72
  86. package/packages/info-screen/tui/info-overlay.ts +85 -100
  87. package/packages/info-screen/usage-parser.ts +12 -47
  88. package/packages/input-shortcuts/package.json +2 -2
  89. package/packages/input-shortcuts/src/index.ts +3 -3
  90. package/packages/input-shortcuts/src/registers.ts +0 -8
  91. package/packages/input-shortcuts/src/settings-overlay.ts +4 -13
  92. package/packages/input-shortcuts/src/undo-redo.ts +0 -12
  93. package/packages/kanboard/package.json +2 -2
  94. package/packages/mcp/package.json +2 -2
  95. package/packages/mcp/src/bridge/client.ts +0 -15
  96. package/packages/mcp/src/config/manager.ts +0 -38
  97. package/packages/mcp/src/config/schema.ts +0 -27
  98. package/packages/mcp/src/config/sync.ts +0 -29
  99. package/packages/mcp/src/index.ts +1 -1
  100. package/packages/mcp/src/tui/add-overlay.ts +6 -6
  101. package/packages/mcp/src/types.ts +0 -6
  102. package/packages/memory/embedding.ts +0 -31
  103. package/packages/memory/mempalace.ts +0 -16
  104. package/packages/memory/package.json +4 -8
  105. package/packages/memory/storage.ts +56 -753
  106. package/packages/milestone/hooks.ts +1 -6
  107. package/packages/milestone/milestone.ts +2 -41
  108. package/packages/milestone/package.json +2 -2
  109. package/packages/notify/README.md +1 -0
  110. package/packages/notify/commands.ts +61 -3
  111. package/packages/notify/events.ts +0 -10
  112. package/packages/notify/package.json +2 -2
  113. package/packages/notify/settings.ts +0 -19
  114. package/packages/notify/tui/gotify-setup.ts +108 -143
  115. package/packages/notify/tui/ntfy-setup.ts +137 -172
  116. package/packages/notify/tui/recap-model-selector.ts +107 -101
  117. package/packages/notify/tui/settings-overlay.ts +94 -117
  118. package/packages/notify/tui/telegram-setup.ts +48 -79
  119. package/packages/notify/types.ts +0 -2
  120. package/packages/ralph/index.ts +11 -35
  121. package/packages/ralph/package.json +3 -3
  122. package/packages/ralph/ralph-loop.ts +3 -44
  123. package/packages/subagents/package.json +2 -1
  124. package/packages/subagents/src/agent-manager.ts +0 -51
  125. package/packages/subagents/src/agent-runner.ts +6 -80
  126. package/packages/subagents/src/conversation-viewer.ts +1 -19
  127. package/packages/subagents/src/core-compat.ts +8 -25
  128. package/packages/subagents/src/custom-agents.ts +0 -1
  129. package/packages/subagents/src/index.ts +3 -48
  130. package/packages/subagents/src/types.ts +0 -11
  131. package/packages/subagents/src/widget.ts +5 -5
  132. package/packages/unipi/bundled.js +4468 -7769
  133. package/packages/unipi/index.ts +6 -3
  134. package/packages/updater/package.json +2 -2
  135. package/packages/updater/src/changelog.ts +1 -16
  136. package/packages/updater/src/index.ts +0 -5
  137. package/packages/updater/src/installer.ts +4 -26
  138. package/packages/updater/src/markdown.ts +6 -152
  139. package/packages/updater/src/settings.ts +0 -15
  140. package/packages/updater/src/tui/changelog-overlay.ts +42 -234
  141. package/packages/updater/src/tui/list-detail-overlay.ts +260 -0
  142. package/packages/updater/src/tui/readme-overlay.ts +53 -210
  143. package/packages/updater/src/tui/settings-overlay.ts +13 -26
  144. package/packages/updater/src/version.ts +2 -31
  145. package/packages/utility/package.json +2 -4
  146. package/packages/utility/src/commands.ts +4 -5
  147. package/packages/utility/src/herdr-sync.ts +136 -0
  148. package/packages/utility/src/index.ts +11 -68
  149. package/packages/utility/src/lifecycle/cleanup.ts +2 -16
  150. package/packages/utility/src/{diff/settings.ts → settings.ts} +42 -80
  151. package/packages/utility/src/tools/env.ts +0 -0
  152. package/packages/utility/src/tui/name-badge-state.ts +18 -2
  153. package/packages/utility/src/tui/name-badge.ts +5 -4
  154. package/packages/utility/src/tui/util-settings-tui.ts +41 -224
  155. package/packages/web-api/package.json +2 -6
  156. package/packages/web-api/src/engine/dependencies.ts +13 -134
  157. package/packages/web-api/src/engine/extract.ts +9 -99
  158. package/packages/web-api/src/engine/format.ts +0 -75
  159. package/packages/web-api/src/engine/types.ts +0 -39
  160. package/packages/web-api/src/index.ts +1 -2
  161. package/packages/web-api/src/providers/base.ts +0 -10
  162. package/packages/web-api/src/providers/duckduckgo.ts +0 -1
  163. package/packages/web-api/src/providers/firecrawl.ts +0 -9
  164. package/packages/web-api/src/providers/jina-reader.ts +0 -9
  165. package/packages/web-api/src/providers/jina-search.ts +0 -9
  166. package/packages/web-api/src/providers/perplexity.ts +0 -9
  167. package/packages/web-api/src/providers/registry.ts +0 -80
  168. package/packages/web-api/src/providers/serpapi.ts +0 -9
  169. package/packages/web-api/src/providers/tavily.ts +0 -9
  170. package/packages/web-api/src/providers/wigolo-client.ts +0 -30
  171. package/packages/web-api/src/providers/wigolo.ts +0 -1
  172. package/packages/web-api/src/settings.ts +17 -77
  173. package/packages/web-api/src/tools.ts +21 -24
  174. package/packages/web-api/src/tui/settings-dialog.ts +0 -1
  175. package/packages/workflow/commands.ts +38 -122
  176. package/packages/workflow/index.ts +1 -5
  177. package/packages/workflow/package.json +2 -2
  178. package/packages/cocoindex/README.md +0 -94
  179. package/packages/cocoindex/bridge.ts +0 -842
  180. package/packages/cocoindex/commands.ts +0 -175
  181. package/packages/cocoindex/index.ts +0 -48
  182. package/packages/cocoindex/installer.ts +0 -397
  183. package/packages/cocoindex/package.json +0 -49
  184. package/packages/cocoindex/skills/cocoindex/SKILL.md +0 -88
  185. package/packages/cocoindex/tools.ts +0 -157
  186. package/packages/compactor/src/display/diff-renderer.ts +0 -281
  187. package/packages/compactor/src/display/line-width-safety.ts +0 -28
  188. package/packages/compactor/src/display/render-utils.ts +0 -52
  189. package/packages/compactor/src/display/thinking-label.ts +0 -18
  190. package/packages/compactor/src/display/tool-overrides.ts +0 -136
  191. package/packages/compactor/src/tools/compact.ts +0 -20
  192. package/packages/subagents/src/file-lock.ts +0 -102
  193. package/packages/utility/src/cache/ttl-cache.ts +0 -311
  194. package/packages/utility/src/diff/highlighter.ts +0 -352
  195. package/packages/utility/src/diff/parser.ts +0 -191
  196. package/packages/utility/src/diff/renderer.ts +0 -395
  197. package/packages/utility/src/diff/theme.ts +0 -316
  198. package/packages/utility/src/diff/wrapper.ts +0 -339
  199. package/packages/utility/src/display/capabilities.ts +0 -214
  200. package/packages/utility/src/display/width.ts +0 -226
  201. package/packages/utility/src/tools/batch.ts +0 -229
  202. package/packages/utility/src/tui/badge-settings-tui.ts +0 -388
  203. package/packages/utility/src/tui/badge-settings.ts +0 -67
  204. package/packages/utility/src/tui/settings-inspector.ts +0 -303
  205. package/packages/web-api/src/providers/llm-summarize.ts +0 -71
@@ -10,12 +10,12 @@
10
10
  */
11
11
 
12
12
  import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
13
- import { matchesKey, SettingsList, type SettingItem, type SettingsListTheme } from "@earendil-works/pi-tui";
13
+ import { Key, matchesKey, SettingsList, type SettingItem, type SettingsListTheme } from "@earendil-works/pi-tui";
14
14
  import { loadFooterSettings, saveFooterSettings } from "../config.js";
15
15
  import { PRESET_NAMES } from "../presets.js";
16
16
  import { setIconStyle } from "../rendering/icons.js";
17
17
  import type { FooterGroup, FooterSettings, SeparatorStyle, IconStyle } from "../types.js";
18
- import { boxInnerWidth } from "@pi-unipi/core";
18
+ import { boxInnerWidth, OverlayTheme } from "@pi-unipi/core";
19
19
 
20
20
  // ─── Section types ─────────────────────────────────────────────────────
21
21
 
@@ -43,27 +43,10 @@ const THEME: SettingsListTheme = {
43
43
  hint: (text) => `\x1b[2m${text}\x1b[0m`,
44
44
  };
45
45
 
46
- // ─── Helper: frame a line inside box drawing ───────────────────────────
46
+ // ─── Shared box-drawing helper ────────────────────────────────────────
47
47
 
48
- function frameLine(content: string, innerWidth: number): string {
49
- const visLen = visibleWidth(content);
50
- const pad = Math.max(0, innerWidth - visLen);
51
- return `\x1b[90m│\x1b[0m${content}${" ".repeat(pad)}\x1b[90m│\x1b[0m`;
52
- }
53
-
54
- function ruleLine(innerWidth: number): string {
55
- return `\x1b[90m├${"─".repeat(innerWidth)}┤\x1b[0m`;
56
- }
48
+ const overlay = new OverlayTheme();
57
49
 
58
- function borderLine(innerWidth: number, edge: "top" | "bottom"): string {
59
- const left = edge === "top" ? "┌" : "└";
60
- const right = edge === "top" ? "┐" : "┘";
61
- return `\x1b[90m${left}${"─".repeat(innerWidth)}${right}\x1b[0m`;
62
- }
63
-
64
- function visibleWidth(text: string): number {
65
- return text.replace(/\x1b\[[0-9;]*m/g, "").length;
66
- }
67
50
 
68
51
  // ─── Show the footer settings overlay ──────────────────────────────────
69
52
 
@@ -129,7 +112,7 @@ class FooterSettingsOverlay {
129
112
 
130
113
  handleInput(data: string): void {
131
114
  // Tab cycles sections
132
- if (data === "\t" || data === "\x1b[Z") {
115
+ if (data === "\t" || matchesKey(data, Key.shift("tab"))) {
133
116
  const idx = SECTIONS.indexOf(this.section);
134
117
  if (data === "\t") {
135
118
  this.section = SECTIONS[(idx + 1) % SECTIONS.length];
@@ -209,7 +192,7 @@ class FooterSettingsOverlay {
209
192
  }
210
193
 
211
194
  // Left arrow / backspace in segment drill-down — back to groups
212
- if (this.section === "segments" && this.selectedGroupId && (data === "\x1b[D" || data === "\x7f")) {
195
+ if (this.section === "segments" && this.selectedGroupId && (matchesKey(data, Key.left) || data === "\x7f")) {
213
196
  this.backToGroups();
214
197
  return;
215
198
  }
@@ -462,8 +445,8 @@ class FooterSettingsOverlay {
462
445
  const lines: string[] = [];
463
446
 
464
447
  // Header
465
- lines.push(borderLine(innerWidth, "top"));
466
- lines.push(frameLine(`\x1b[1m\x1b[36m⚙ Footer Settings\x1b[0m`, innerWidth));
448
+ lines.push(overlay.borderLine(innerWidth, "top"));
449
+ lines.push(overlay.frameLine(`\x1b[1m\x1b[36m⚙ Footer Settings\x1b[0m`, innerWidth));
467
450
 
468
451
  // Section tabs
469
452
  const tabParts = SECTIONS.map((s) => {
@@ -473,20 +456,20 @@ class FooterSettingsOverlay {
473
456
  }
474
457
  return `\x1b[2m${label}\x1b[0m`;
475
458
  });
476
- lines.push(frameLine(` ${tabParts.join(" ")}`, innerWidth));
477
- lines.push(ruleLine(innerWidth));
459
+ lines.push(overlay.frameLine(` ${tabParts.join(" ")}`, innerWidth));
460
+ lines.push(overlay.ruleLine(innerWidth));
478
461
 
479
462
  // Section content
480
463
  const activeList = this.currentList;
481
464
  if (activeList) {
482
465
  const contentLines = activeList.render(innerWidth - 2);
483
466
  for (const line of contentLines) {
484
- lines.push(frameLine(` ${line}`, innerWidth));
467
+ lines.push(overlay.frameLine(` ${line}`, innerWidth));
485
468
  }
486
469
  }
487
470
 
488
471
  // Footer hints
489
- lines.push(ruleLine(innerWidth));
472
+ lines.push(overlay.ruleLine(innerWidth));
490
473
 
491
474
  let hints: string;
492
475
  if (this.section === "segments" && this.selectedGroupId) {
@@ -496,8 +479,8 @@ class FooterSettingsOverlay {
496
479
  } else {
497
480
  hints = "j/k navigate · Space/Enter change · Tab section · q close";
498
481
  }
499
- lines.push(frameLine(`\x1b[2m${hints}\x1b[0m`, innerWidth));
500
- lines.push(borderLine(innerWidth, "bottom"));
482
+ lines.push(overlay.frameLine(`\x1b[2m${hints}\x1b[0m`, innerWidth));
483
+ lines.push(overlay.borderLine(innerWidth, "bottom"));
501
484
 
502
485
  return lines;
503
486
  }
@@ -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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/image",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "Image generation and image recognition tools for the Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "2.6.0"
37
+ "@pi-unipi/core": "2.6.1"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@earendil-works/pi-ai": "^0.80.0",
@@ -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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/info-screen",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "Dashboard and module registry for Unipi — configurable info overlay with tabbed groups",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@pi-unipi/core": "2.6.0"
36
+ "@pi-unipi/core": "2.6.1"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.80.0",