@pellux/goodvibes-tui 0.18.12 → 0.18.17

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 (196) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +3 -2
  5. package/src/config/index.ts +1 -138
  6. package/src/core/conversation-rendering.ts +3 -3
  7. package/src/core/conversation.ts +176 -423
  8. package/src/core/history.ts +45 -0
  9. package/src/core/orchestrator.ts +3 -735
  10. package/src/core/system-message-router.ts +19 -58
  11. package/src/daemon/cli.ts +82 -6
  12. package/src/input/command-registry.ts +2 -0
  13. package/src/input/commands/control-room-runtime.ts +1 -1
  14. package/src/input/commands/health-runtime.ts +1 -1
  15. package/src/input/commands/local-setup-review.ts +1 -1
  16. package/src/input/commands/platform-access-runtime.ts +1 -1
  17. package/src/input/commands/qrcode-runtime.ts +20 -0
  18. package/src/input/commands/subscription-runtime.ts +1 -1
  19. package/src/input/commands.ts +2 -0
  20. package/src/input/handler-content-actions.ts +2 -2
  21. package/src/input/handler-feed.ts +7 -1
  22. package/src/input/handler-modal-routes.ts +19 -2
  23. package/src/input/handler-modal-token-routes.ts +4 -1
  24. package/src/input/handler-picker-routes.ts +4 -2
  25. package/src/input/handler-ui-state.ts +1 -1
  26. package/src/input/handler.ts +1 -1
  27. package/src/input/model-picker.ts +11 -0
  28. package/src/input/search.ts +1 -1
  29. package/src/input/selection.ts +2 -2
  30. package/src/input/settings-modal.ts +31 -3
  31. package/src/main.ts +1 -1
  32. package/src/panels/agent-inspector-panel.ts +3 -3
  33. package/src/panels/agent-logs-panel.ts +26 -27
  34. package/src/panels/approval-panel.ts +2 -2
  35. package/src/panels/automation-control-panel.ts +3 -3
  36. package/src/panels/base-panel.ts +14 -14
  37. package/src/panels/builtin/operations.ts +1 -1
  38. package/src/panels/builtin/session.ts +67 -1
  39. package/src/panels/builtin/shared.ts +4 -4
  40. package/src/panels/cockpit-panel.ts +2 -2
  41. package/src/panels/communication-panel.ts +3 -3
  42. package/src/panels/context-visualizer-panel.ts +2 -2
  43. package/src/panels/control-plane-panel.ts +3 -3
  44. package/src/panels/cost-tracker-panel.ts +3 -3
  45. package/src/panels/debug-panel.ts +2 -2
  46. package/src/panels/diff-panel.ts +2 -2
  47. package/src/panels/docs-panel.ts +1 -1
  48. package/src/panels/eval-panel.ts +2 -2
  49. package/src/panels/file-explorer-panel.ts +3 -3
  50. package/src/panels/file-preview-panel.ts +3 -3
  51. package/src/panels/forensics-panel.ts +2 -2
  52. package/src/panels/git-panel.ts +1 -1
  53. package/src/panels/hooks-panel.ts +3 -3
  54. package/src/panels/incident-review-panel.ts +1 -1
  55. package/src/panels/intelligence-panel.ts +2 -2
  56. package/src/panels/knowledge-panel.ts +1 -1
  57. package/src/panels/local-auth-panel.ts +2 -2
  58. package/src/panels/marketplace-panel.ts +1 -1
  59. package/src/panels/mcp-panel.ts +3 -3
  60. package/src/panels/memory-panel.ts +1 -1
  61. package/src/panels/ops-control-panel.ts +3 -3
  62. package/src/panels/ops-strategy-panel.ts +2 -2
  63. package/src/panels/orchestration-panel.ts +2 -2
  64. package/src/panels/panel-list-panel.ts +6 -6
  65. package/src/panels/plan-dashboard-panel.ts +1 -1
  66. package/src/panels/plugins-panel.ts +2 -2
  67. package/src/panels/policy-panel.ts +2 -2
  68. package/src/panels/polish.ts +3 -3
  69. package/src/panels/provider-account-snapshot.ts +1 -1
  70. package/src/panels/provider-accounts-panel.ts +25 -29
  71. package/src/panels/provider-health-panel.ts +2 -2
  72. package/src/panels/provider-stats-panel.ts +3 -3
  73. package/src/panels/qr-panel.ts +182 -0
  74. package/src/panels/remote-panel.ts +3 -3
  75. package/src/panels/routes-panel.ts +3 -3
  76. package/src/panels/sandbox-panel.ts +2 -2
  77. package/src/panels/schedule-panel.ts +1 -1
  78. package/src/panels/scrollable-list-panel.ts +407 -0
  79. package/src/panels/security-panel.ts +2 -2
  80. package/src/panels/services-panel.ts +3 -3
  81. package/src/panels/session-browser-panel.ts +2 -2
  82. package/src/panels/settings-sync-panel.ts +2 -2
  83. package/src/panels/skills-panel.ts +6 -6
  84. package/src/panels/subscription-panel.ts +3 -3
  85. package/src/panels/symbol-outline-panel.ts +3 -3
  86. package/src/panels/system-messages-panel.ts +4 -4
  87. package/src/panels/tasks-panel.ts +2 -2
  88. package/src/panels/thinking-panel.ts +3 -3
  89. package/src/panels/token-budget-panel.ts +1 -1
  90. package/src/panels/tool-inspector-panel.ts +3 -3
  91. package/src/panels/types.ts +5 -5
  92. package/src/panels/watchers-panel.ts +3 -3
  93. package/src/panels/welcome-panel.ts +1 -1
  94. package/src/panels/worktree-panel.ts +22 -21
  95. package/src/panels/wrfc-panel.ts +3 -3
  96. package/src/permissions/prompt.ts +3 -22
  97. package/src/plugins/loader.ts +15 -304
  98. package/src/renderer/agent-detail-modal.ts +1 -1
  99. package/src/renderer/autocomplete-overlay.ts +2 -2
  100. package/src/renderer/bookmark-modal.ts +1 -1
  101. package/src/renderer/bottom-bar.ts +2 -2
  102. package/src/renderer/buffer.ts +1 -1
  103. package/src/renderer/code-block.ts +2 -2
  104. package/src/renderer/compositor.ts +2 -2
  105. package/src/renderer/context-inspector.ts +1 -1
  106. package/src/renderer/conversation-layout.ts +2 -2
  107. package/src/renderer/conversation-overlays.ts +1 -1
  108. package/src/renderer/conversation-surface.ts +2 -2
  109. package/src/renderer/diff-view.ts +2 -2
  110. package/src/renderer/diff.ts +1 -1
  111. package/src/renderer/file-picker-overlay.ts +2 -2
  112. package/src/renderer/file-tree.ts +2 -2
  113. package/src/renderer/help-overlay.ts +1 -1
  114. package/src/renderer/history-search-overlay.ts +2 -2
  115. package/src/renderer/live-tail-modal.ts +1 -1
  116. package/src/renderer/markdown.ts +2 -2
  117. package/src/renderer/modal-factory.ts +3 -3
  118. package/src/renderer/model-picker-overlay.ts +2 -2
  119. package/src/renderer/overlay-box.ts +2 -2
  120. package/src/renderer/panel-composite.ts +1 -1
  121. package/src/renderer/panel-picker-overlay.ts +2 -2
  122. package/src/renderer/panel-tab-bar.ts +1 -1
  123. package/src/renderer/panel-workspace-bar.ts +1 -1
  124. package/src/renderer/process-indicator.ts +2 -2
  125. package/src/renderer/process-modal.ts +1 -1
  126. package/src/renderer/profile-picker-modal.ts +2 -2
  127. package/src/renderer/progress.ts +2 -2
  128. package/src/renderer/qr-renderer.ts +117 -0
  129. package/src/renderer/search-overlay.ts +2 -2
  130. package/src/renderer/selection-modal-overlay.ts +2 -2
  131. package/src/renderer/session-picker-modal.ts +2 -2
  132. package/src/renderer/settings-modal-helpers.ts +122 -0
  133. package/src/renderer/settings-modal.ts +149 -113
  134. package/src/renderer/shell-surface.ts +1 -1
  135. package/src/renderer/system-message.ts +1 -1
  136. package/src/renderer/tab-strip.ts +2 -2
  137. package/src/renderer/text-layout.ts +1 -1
  138. package/src/renderer/thinking.ts +1 -1
  139. package/src/renderer/tool-call.ts +2 -2
  140. package/src/renderer/ui-factory.ts +2 -2
  141. package/src/runtime/bootstrap-command-context.ts +5 -6
  142. package/src/runtime/bootstrap-command-parts.ts +32 -18
  143. package/src/runtime/bootstrap-core.ts +3 -2
  144. package/src/runtime/bootstrap-hook-bridge.ts +15 -174
  145. package/src/runtime/bootstrap-shell.ts +4 -4
  146. package/src/runtime/bootstrap.ts +7 -2
  147. package/src/runtime/context.ts +4 -20
  148. package/src/runtime/diagnostics/panels/index.ts +6 -6
  149. package/src/runtime/diagnostics/panels/ops.ts +1 -1
  150. package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
  151. package/src/runtime/perf/panel-contracts.ts +32 -0
  152. package/src/runtime/perf/panel-health-monitor.ts +18 -0
  153. package/src/runtime/services.ts +5 -5
  154. package/src/runtime/store/domains/domain-read-matrix.ts +0 -2
  155. package/src/runtime/store/selectors/index.ts +11 -6
  156. package/src/runtime/store/state.ts +12 -4
  157. package/src/runtime/ui-events.ts +1 -0
  158. package/src/runtime/ui-read-model-helpers.ts +1 -32
  159. package/src/runtime/ui-read-models-observability-maintenance.ts +1 -81
  160. package/src/runtime/ui-read-models-observability-options.ts +1 -5
  161. package/src/runtime/ui-read-models-observability-remote.ts +1 -73
  162. package/src/runtime/ui-read-models-observability-security.ts +1 -172
  163. package/src/runtime/ui-read-models-observability-system.ts +1 -217
  164. package/src/runtime/ui-read-models-observability.ts +1 -59
  165. package/src/runtime/ui-service-queries.ts +1 -114
  166. package/src/runtime/ui-services.ts +1 -1
  167. package/src/shell/ui-openers.ts +1 -1
  168. package/src/tools/index.ts +1 -186
  169. package/src/types/grid.ts +48 -0
  170. package/src/utils/clipboard.ts +21 -0
  171. package/src/utils/splash-lines.ts +1 -1
  172. package/src/utils/terminal-width.ts +185 -0
  173. package/src/version.ts +1 -1
  174. package/src/config/service-registry.ts +0 -1
  175. package/src/config/subscription-providers.ts +0 -127
  176. package/src/daemon/facade-composition.ts +0 -398
  177. package/src/daemon/facade.ts +0 -638
  178. package/src/daemon/surface-policy.ts +0 -60
  179. package/src/daemon/types.ts +0 -191
  180. package/src/runtime/diagnostics/actions.ts +0 -776
  181. package/src/runtime/diagnostics/index.ts +0 -99
  182. package/src/runtime/diagnostics/panels/agents.ts +0 -252
  183. package/src/runtime/diagnostics/panels/events.ts +0 -188
  184. package/src/runtime/diagnostics/panels/health.ts +0 -242
  185. package/src/runtime/diagnostics/panels/tasks.ts +0 -251
  186. package/src/runtime/diagnostics/panels/tool-calls.ts +0 -267
  187. package/src/runtime/diagnostics/provider.ts +0 -262
  188. package/src/runtime/store/domains/conversation.ts +0 -181
  189. package/src/runtime/store/domains/permissions.ts +0 -143
  190. package/src/runtime/store/helpers/reducers/conversation.ts +0 -228
  191. package/src/runtime/store/helpers/reducers/lifecycle.ts +0 -440
  192. package/src/runtime/store/helpers/reducers/shared.ts +0 -60
  193. package/src/runtime/store/helpers/reducers/sync.ts +0 -555
  194. package/src/runtime/store/helpers/reducers.ts +0 -30
  195. package/src/runtime/ui-read-models-core.ts +0 -95
  196. package/src/runtime/ui-read-models-operations.ts +0 -203
@@ -2,7 +2,7 @@
2
2
  * MemoryPanel — project memory substrate TUI panel.
3
3
  */
4
4
 
5
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
5
+ import type { Line } from '../types/grid.ts';
6
6
  import type { MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state/memory-store';
7
7
  import type { MemoryRecord, MemoryClass } from '@pellux/goodvibes-sdk/platform/state/memory-store';
8
8
  import { BasePanel } from './base-panel.ts';
@@ -7,13 +7,13 @@
7
7
  * Requires the `operator-control-plane` feature flag to be enabled.
8
8
  * Open via Ctrl+O keybind or `/ops view` command.
9
9
  */
10
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
10
+ import type { Line } from '../types/grid.ts';
11
11
  import type { OpsEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
12
- import type { UiEventFeed } from '@pellux/goodvibes-sdk/platform/runtime/ui-events';
12
+ import type { UiEventFeed } from '../runtime/ui-events.ts';
13
13
  import type { OpsAuditEntry } from '../runtime/diagnostics/panels/ops.ts';
14
14
  import { OpsPanel } from '../runtime/diagnostics/panels/ops.ts';
15
15
  import { BasePanel } from './base-panel.ts';
16
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
16
+ import { createEmptyLine } from '../types/grid.ts';
17
17
  import {
18
18
  buildEmptyState,
19
19
  buildPanelLine,
@@ -8,10 +8,10 @@
8
8
  */
9
9
 
10
10
  import { BasePanel } from './base-panel.ts';
11
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
11
+ import type { Line } from '../types/grid.ts';
12
12
  import type { PlannerDecision, ExecutionStrategy } from '@pellux/goodvibes-sdk/platform/core/adaptive-planner';
13
13
  import type { PlannerEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
14
- import type { UiEventFeed } from '@pellux/goodvibes-sdk/platform/runtime/ui-events';
14
+ import type { UiEventFeed } from '../runtime/ui-events.ts';
15
15
  import type { OpsStrategyQuery } from '../runtime/ui-service-queries.ts';
16
16
  import {
17
17
  buildEmptyState,
@@ -1,5 +1,5 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { UiOrchestrationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
5
5
  import {
@@ -12,12 +12,12 @@
12
12
  *
13
13
  * Open via /panel list.
14
14
  */
15
- import type { Line, Cell } from '@pellux/goodvibes-sdk/platform/types/grid';
15
+ import type { Line, Cell } from '../types/grid.ts';
16
16
  import type { PanelCategory, PanelRegistration } from './types.ts';
17
17
  import { BasePanel } from './base-panel.ts';
18
18
  import type { PanelManager } from './panel-manager.ts';
19
- import type { PanelHealthMonitor } from '@pellux/goodvibes-sdk/platform/runtime/perf/panel-health-monitor';
20
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
19
+ import type { ComponentHealthMonitor } from '../runtime/perf/panel-health-monitor.ts';
20
+ import { createEmptyLine } from '../types/grid.ts';
21
21
  import {
22
22
  buildEmptyState,
23
23
  buildKeyValueLine,
@@ -30,7 +30,7 @@ import {
30
30
  resolvePrimaryScrollableSection,
31
31
  type PanelWorkspaceSection,
32
32
  } from './polish.ts';
33
- import { truncateDisplay } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
33
+ import { truncateDisplay } from '../utils/terminal-width.ts';
34
34
  import { wrapWithHangingIndent } from '../renderer/text-layout.ts';
35
35
  import {
36
36
  getPanelSearchFocusTransition,
@@ -112,9 +112,9 @@ export class PanelListPanel extends BasePanel {
112
112
 
113
113
  public constructor(
114
114
  private readonly panelManager: PanelManager,
115
- panelHealthMonitor?: PanelHealthMonitor,
115
+ componentHealthMonitor?: ComponentHealthMonitor,
116
116
  ) {
117
- super('panel-list', 'Panel List', 'L', 'session', panelHealthMonitor);
117
+ super('panel-list', 'Panel List', 'L', 'session', componentHealthMonitor);
118
118
  }
119
119
 
120
120
  public override onActivate(): void {
@@ -1,4 +1,4 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
2
  import type { ExecutionPlan, PlanItem, PlanItemStatus } from '@pellux/goodvibes-sdk/platform/core/execution-plan';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { PlanDashboardQuery } from '../runtime/ui-service-queries.ts';
@@ -1,5 +1,5 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { PluginManagerObserver, PluginStatus } from '@pellux/goodvibes-sdk/platform/plugins/manager';
5
5
  import {
@@ -1,5 +1,5 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { PolicyRuntimeState } from '@pellux/goodvibes-sdk/platform/runtime/permissions/policy-runtime';
5
5
  import type { PolicyPanelSnapshot } from '../runtime/diagnostics/panels/policy.ts';
@@ -1,6 +1,6 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine, createStyledCell } from '@pellux/goodvibes-sdk/platform/types/grid';
3
- import { getDisplayWidth, wrapText } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine, createStyledCell } from '../types/grid.ts';
3
+ import { getDisplayWidth, wrapText } from '../utils/terminal-width.ts';
4
4
  import { getSurfaceContentRows, getTrackedVisibleWindow, getVisibleWindow, type VisibleWindow } from '../renderer/surface-layout.ts';
5
5
  import { GLYPHS, UI_TONES } from '../renderer/ui-primitives.ts';
6
6
 
@@ -1,4 +1,4 @@
1
- import { listBuiltinSubscriptionProviders } from '../config/subscription-providers.ts';
1
+ import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config/subscription-providers';
2
2
  import type {
3
3
  ProviderAccountInspectionQuery,
4
4
  } from '../runtime/ui-service-queries.ts';
@@ -1,6 +1,6 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
3
- import { BasePanel } from './base-panel.ts';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
+ import { ScrollableListPanel } from './scrollable-list-panel.ts';
4
4
  import {
5
5
  buildDetailBlock,
6
6
  buildEmptyState,
@@ -29,11 +29,9 @@ const C = {
29
29
  selectBg: '#1e293b',
30
30
  } as const;
31
31
 
32
- export class ProviderAccountsPanel extends BasePanel {
32
+ export class ProviderAccountsPanel extends ScrollableListPanel<ProviderAccountRecord> {
33
33
  private records: ProviderAccountRecord[] = [];
34
34
  private loading = false;
35
- private selectedIndex = 0;
36
- private scrollOffset = 0;
37
35
  private readonly providerAccounts: ProviderAccountSnapshotQuery;
38
36
 
39
37
  public constructor(deps: ProviderAccountsPanelDeps) {
@@ -47,22 +45,26 @@ export class ProviderAccountsPanel extends BasePanel {
47
45
  if (!this.loading) void this.refresh();
48
46
  }
49
47
 
48
+ protected getItems(): readonly ProviderAccountRecord[] {
49
+ return this.records;
50
+ }
51
+
52
+ protected renderItem(item: ProviderAccountRecord, _index: number, selected: boolean, width: number): Line {
53
+ return buildPanelListRow(width, [
54
+ { text: item.providerId.padEnd(16), fg: item.active ? C.good : C.value },
55
+ { text: ` ${item.activeRoute.padEnd(14)}`, fg: item.activeRoute === 'subscription' ? C.info : item.activeRoute === 'api-key' ? C.warn : item.activeRoute === 'service-oauth' ? C.value : C.dim },
56
+ { text: ` models=${String(item.modelCount).padEnd(4)}`, fg: C.dim },
57
+ { text: ` ${item.authFreshness.padEnd(10)}`, fg: item.authFreshness === 'expired' ? C.bad : item.authFreshness === 'expiring' || item.authFreshness === 'pending' ? C.warn : C.dim },
58
+ { text: ` issues=${String(item.issues.length).padEnd(2)}`, fg: item.issues.length > 0 ? C.bad : C.good },
59
+ ], C, { selected });
60
+ }
61
+
50
62
  public handleInput(key: string): boolean {
51
63
  if (key === 'r') {
52
64
  void this.refresh();
53
65
  return true;
54
66
  }
55
- if (key === 'up' || key === 'k') {
56
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
57
- this.markDirty();
58
- return true;
59
- }
60
- if (key === 'down' || key === 'j') {
61
- this.selectedIndex = Math.min(Math.max(0, this.records.length - 1), this.selectedIndex + 1);
62
- this.markDirty();
63
- return true;
64
- }
65
- return false;
67
+ return super.handleInput(key);
66
68
  }
67
69
 
68
70
  private async refresh(): Promise<void> {
@@ -70,7 +72,7 @@ export class ProviderAccountsPanel extends BasePanel {
70
72
  this.markDirty();
71
73
  const snapshot = await this.buildSnapshot();
72
74
  this.records = [...snapshot.providers];
73
- this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.records.length - 1));
75
+ this.clampSelection();
74
76
  this.loading = false;
75
77
  this.markDirty();
76
78
  }
@@ -177,15 +179,9 @@ export class ProviderAccountsPanel extends BasePanel {
177
179
  }
178
180
  const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'Provider posture', postureLines, C) };
179
181
  const detailsSection: PanelWorkspaceSection = { lines: buildDetailBlock(width, 'Selected provider', detailRows, C) };
180
- const rawProviderLines: Line[] = this.records.map((record, absolute) => {
181
- return buildPanelListRow(width, [
182
- { text: record.providerId.padEnd(16), fg: record.active ? C.good : C.value },
183
- { text: ` ${record.activeRoute.padEnd(14)}`, fg: record.activeRoute === 'subscription' ? C.info : record.activeRoute === 'api-key' ? C.warn : record.activeRoute === 'service-oauth' ? C.value : C.dim },
184
- { text: ` models=${String(record.modelCount).padEnd(4)}`, fg: C.dim },
185
- { text: ` ${record.authFreshness.padEnd(10)}`, fg: record.authFreshness === 'expired' ? C.bad : record.authFreshness === 'expiring' || record.authFreshness === 'pending' ? C.warn : C.dim },
186
- { text: ` issues=${String(record.issues.length).padEnd(2)}`, fg: record.issues.length > 0 ? C.bad : C.good },
187
- ], C, { selected: absolute === this.selectedIndex });
188
- });
182
+ const rawProviderLines: Line[] = this.records.map((record, absolute) =>
183
+ this.renderItem(record, absolute, absolute === this.selectedIndex, width),
184
+ );
189
185
  const resolvedProvidersSection = resolvePrimaryScrollableSection(width, height, {
190
186
  intro,
191
187
  footerLines,
@@ -195,14 +191,14 @@ export class ProviderAccountsPanel extends BasePanel {
195
191
  title: 'Providers',
196
192
  scrollableLines: rawProviderLines,
197
193
  selectedIndex: this.selectedIndex,
198
- scrollOffset: this.scrollOffset,
194
+ scrollOffset: this.scrollStart,
199
195
  guardRows: 1,
200
196
  minRows: 4,
201
197
  appendWindowSummary: { dimColor: C.dim },
202
198
  },
203
199
  afterSections: [detailsSection],
204
200
  });
205
- this.scrollOffset = resolvedProvidersSection.scrollOffset;
201
+ this.scrollStart = resolvedProvidersSection.scrollOffset;
206
202
  const sections: PanelWorkspaceSection[] = [
207
203
  postureSection,
208
204
  resolvedProvidersSection.section,
@@ -1,9 +1,9 @@
1
1
  import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
2
2
  import { BasePanel } from './base-panel.ts';
3
- import { createEmptyLine, createStyledCell, type Line } from '@pellux/goodvibes-sdk/platform/types/grid';
3
+ import { createEmptyLine, createStyledCell, type Line } from '../types/grid.ts';
4
4
  import type { ProviderAuthRouteDescriptor } from '@pellux/goodvibes-sdk/platform/providers/interface';
5
5
  import type { ProviderEvent, TurnEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
6
- import type { UiEventFeed } from '@pellux/goodvibes-sdk/platform/runtime/ui-events';
6
+ import type { UiEventFeed } from '../runtime/ui-events.ts';
7
7
  import {
8
8
  type ProviderRuntimeInspectionQuery,
9
9
  } from '../runtime/ui-service-queries.ts';
@@ -1,7 +1,7 @@
1
1
  import { BasePanel } from './base-panel.ts';
2
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
+ import type { Line } from '../types/grid.ts';
3
3
  import type { ProviderEvent, TurnEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
4
- import type { UiEventFeed } from '@pellux/goodvibes-sdk/platform/runtime/ui-events';
4
+ import type { UiEventFeed } from '../runtime/ui-events.ts';
5
5
  import type { UiProvidersSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
6
6
  import {
7
7
  buildEmptyState,
@@ -11,7 +11,7 @@ import {
11
11
  DEFAULT_PANEL_PALETTE,
12
12
  type PanelWorkspaceSection,
13
13
  } from './polish.ts';
14
- import { truncateDisplay } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
14
+ import { truncateDisplay } from '../utils/terminal-width.ts';
15
15
 
16
16
  // ---------------------------------------------------------------------------
17
17
  // Constants
@@ -0,0 +1,182 @@
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
+ import { BasePanel } from './base-panel.ts';
4
+ import {
5
+ buildPanelLine,
6
+ DEFAULT_PANEL_PALETTE,
7
+ } from './polish.ts';
8
+ import { renderQrMatrix, generateQrMatrix } from '../renderer/qr-renderer.ts';
9
+ import { encodeConnectionPayload } from '@pellux/goodvibes-sdk/platform/pairing/index';
10
+
11
+ const C = {
12
+ ...DEFAULT_PANEL_PALETTE,
13
+ url: '#38bdf8',
14
+ token: '#a78bfa',
15
+ hint: '#64748b',
16
+ qrFg: '#000000',
17
+ qrBg: '#ffffff',
18
+ } as const;
19
+
20
+ /**
21
+ * Connection info passed to the QR panel.
22
+ * Populated at construction; updated when the token is regenerated.
23
+ */
24
+ export interface QrPanelConnectionInfo {
25
+ /** Full connection URL (e.g. http://192.168.1.x:3141) */
26
+ readonly url: string;
27
+ /** Auth token */
28
+ readonly token: string;
29
+ /** Username associated with the companion session */
30
+ readonly username: string;
31
+ /** Bootstrap password for companion authentication */
32
+ readonly password?: string;
33
+ /** SDK/surface version (defaults to '0.0.0' if omitted) */
34
+ readonly version?: string;
35
+ /** Surface identifier (defaults to 'tui' if omitted) */
36
+ readonly surface?: string;
37
+ }
38
+
39
+ /**
40
+ * Callback used by the panel to regenerate the companion token.
41
+ * Returns updated connection info.
42
+ */
43
+ export type RegenerateTokenFn = () => QrPanelConnectionInfo;
44
+
45
+ /**
46
+ * Callback used by the panel to copy text to the clipboard.
47
+ */
48
+ export type CopyToClipboardFn = (text: string) => void;
49
+
50
+ /**
51
+ * QrPanel - displays a QR code for companion app pairing.
52
+ *
53
+ * Shows connection URL, truncated token, and username above the QR code.
54
+ * Supports `r` to regenerate the token and `c` to copy the token.
55
+ *
56
+ * QR matrix generation uses the SDK's `generateQrMatrix` via `encodeConnectionPayload`.
57
+ */
58
+ export class QrPanel extends BasePanel {
59
+ private connectionInfo: QrPanelConnectionInfo;
60
+ private readonly regenerateToken: RegenerateTokenFn | undefined;
61
+ private readonly copyToClipboard: CopyToClipboardFn | undefined;
62
+ private lastStatus = '';
63
+
64
+ public constructor(
65
+ connectionInfo: QrPanelConnectionInfo,
66
+ regenerateToken?: RegenerateTokenFn,
67
+ copyToClipboard?: CopyToClipboardFn,
68
+ ) {
69
+ super('qr-code', 'QR Code', 'Q', 'session');
70
+ this.connectionInfo = connectionInfo;
71
+ this.regenerateToken = regenerateToken;
72
+ this.copyToClipboard = copyToClipboard;
73
+ }
74
+
75
+ public handleInput(key: string): boolean {
76
+ if (key === 'r') {
77
+ if (this.regenerateToken) {
78
+ this.connectionInfo = this.regenerateToken();
79
+ this.lastStatus = 'Token regenerated.';
80
+ } else {
81
+ this.lastStatus = 'Regeneration not available.';
82
+ }
83
+ this.markDirty();
84
+ return true;
85
+ }
86
+ if (key === 'c') {
87
+ if (this.copyToClipboard) {
88
+ this.copyToClipboard(this.connectionInfo.token);
89
+ this.lastStatus = 'Token copied to clipboard.';
90
+ } else {
91
+ this.lastStatus = 'Clipboard not available.';
92
+ }
93
+ this.markDirty();
94
+ return true;
95
+ }
96
+ return false;
97
+ }
98
+
99
+ public render(width: number, height: number): Line[] {
100
+ this.needsRender = false;
101
+ const lines: Line[] = [];
102
+
103
+ const { url, token, username, password } = this.connectionInfo;
104
+
105
+ // ── Connection info header ─────────────────────────────────────────────
106
+ lines.push(createEmptyLine(width));
107
+ lines.push(
108
+ buildPanelLine(width, [
109
+ [' URL ', C.label],
110
+ [url.slice(0, Math.max(0, width - 12)), C.url],
111
+ ]),
112
+ );
113
+ lines.push(
114
+ buildPanelLine(width, [
115
+ [' Token ', C.label],
116
+ [token, C.token],
117
+ ]),
118
+ );
119
+ lines.push(
120
+ buildPanelLine(width, [
121
+ [' Username ', C.label],
122
+ [username.slice(0, Math.max(0, width - 12)), C.value],
123
+ ]),
124
+ );
125
+ if (password !== undefined) {
126
+ lines.push(
127
+ buildPanelLine(width, [
128
+ [' Password ', C.label],
129
+ [password, C.value],
130
+ ]),
131
+ );
132
+ }
133
+ lines.push(createEmptyLine(width));
134
+
135
+ // ── QR code ────────────────────────────────────────────────────────────
136
+ const payload = encodeConnectionPayload({
137
+ url: this.connectionInfo.url,
138
+ token: this.connectionInfo.token,
139
+ username: this.connectionInfo.username,
140
+ ...(this.connectionInfo.password !== undefined ? { password: this.connectionInfo.password } : {}),
141
+ version: this.connectionInfo.version ?? '0.0.0',
142
+ surface: this.connectionInfo.surface ?? 'tui',
143
+ });
144
+ const matrix = generateQrMatrix(payload);
145
+ const qrLines = renderQrMatrix(matrix.modules, width, { fg: C.qrFg, bg: C.qrBg });
146
+ for (const qrLine of qrLines) {
147
+ lines.push(qrLine);
148
+ }
149
+
150
+ lines.push(createEmptyLine(width));
151
+
152
+ // ── Status message (ephemeral) ─────────────────────────────────────────
153
+ if (this.lastStatus) {
154
+ lines.push(
155
+ buildPanelLine(width, [
156
+ [` ${this.lastStatus} `, C.hint],
157
+ ]),
158
+ );
159
+ }
160
+
161
+ // ── Hints ──────────────────────────────────────────────────────────────
162
+ const hintsLine = buildPanelLine(width, [
163
+ [' r ', C.hint],
164
+ ['regenerate ', C.dim],
165
+ [' c ', C.hint],
166
+ ['copy token', C.dim],
167
+ ]);
168
+
169
+ // Push hints at the bottom if we have room, otherwise append after QR
170
+ const remaining = height - lines.length;
171
+ if (remaining > 2) {
172
+ // Fill with empty lines to push hints toward bottom
173
+ const fillCount = Math.max(0, remaining - 2);
174
+ for (let i = 0; i < fillCount; i++) {
175
+ lines.push(createEmptyLine(width));
176
+ }
177
+ }
178
+ lines.push(hintsLine);
179
+
180
+ return lines;
181
+ }
182
+ }
@@ -1,5 +1,5 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { UiReadModel, UiRemoteSnapshot } from '../runtime/ui-read-models.ts';
5
5
  import {
@@ -14,7 +14,7 @@ import {
14
14
  resolvePrimaryScrollableSection,
15
15
  type PanelWorkspaceSection,
16
16
  } from './polish.ts';
17
- import { truncateDisplay } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
17
+ import { truncateDisplay } from '../utils/terminal-width.ts';
18
18
  import { getTrackedVisibleWindow } from '../renderer/surface-layout.ts';
19
19
 
20
20
  const C = {
@@ -1,8 +1,8 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { UiReadModel, UiRoutesSnapshot } from '../runtime/ui-read-models.ts';
5
- import { truncateDisplay } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
5
+ import { truncateDisplay } from '../utils/terminal-width.ts';
6
6
  import {
7
7
  buildEmptyState,
8
8
  buildGuidanceLine,
@@ -1,5 +1,5 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
5
5
  import { buildSandboxReview, listSandboxPresets, listSandboxProfiles } from '@pellux/goodvibes-sdk/platform/runtime/sandbox/manager';
@@ -1,5 +1,5 @@
1
1
  import { BasePanel } from './base-panel.ts';
2
- import { type Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
+ import { type Line } from '../types/grid.ts';
3
3
  import type { AutomationManager } from '@pellux/goodvibes-sdk/platform/automation/index';
4
4
  import type { AutomationJob } from '@pellux/goodvibes-sdk/platform/automation/jobs';
5
5
  import type { AutomationRun } from '@pellux/goodvibes-sdk/platform/automation/runs';