@pellux/goodvibes-tui 1.1.0 → 1.9.0

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 (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -1,280 +0,0 @@
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
- buildKeyboardHints,
6
- buildPanelLine,
7
- buildPanelTitle,
8
- DEFAULT_PANEL_PALETTE,
9
- extendPalette,
10
- } from './polish.ts';
11
- import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
12
- import { truncateDisplay } from '../utils/terminal-width.ts';
13
- import { renderQrMatrix, generateQrMatrix } from '../renderer/qr-renderer.ts';
14
- import { encodeConnectionPayload } from '@pellux/goodvibes-sdk/platform/pairing';
15
- import type { UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
16
- import type { UiControlPlaneSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
17
-
18
- // Fixed-width placeholder — deliberately NOT derived from the real secret's
19
- // length, so masking doesn't leak how long the token/password is.
20
- const SECRET_MASK = '••••••••••••';
21
-
22
- // Domain accents only; base chrome (header/headerBg/label/info) comes from
23
- // DEFAULT_PANEL_PALETTE. qrFg/qrBg stay pure black/white — scanner contrast
24
- // requirements override theming.
25
- const C = extendPalette(DEFAULT_PANEL_PALETTE, {
26
- token: '#a78bfa',
27
- qrFg: '#000000',
28
- qrBg: '#ffffff',
29
- } as const);
30
-
31
- /**
32
- * Connection info passed to the QR panel.
33
- * Populated at construction; updated when the token is regenerated.
34
- */
35
- export interface QrPanelConnectionInfo {
36
- /** Full connection URL (e.g. http://192.168.1.x:3141) */
37
- readonly url: string;
38
- /** Auth token */
39
- readonly token: string;
40
- /** Username associated with the companion session */
41
- readonly username: string;
42
- /** Bootstrap password for companion authentication */
43
- readonly password?: string;
44
- /** SDK/surface version (defaults to '0.0.0' if omitted) */
45
- readonly version?: string;
46
- /** Surface identifier (defaults to 'tui' if omitted) */
47
- readonly surface?: string;
48
- }
49
-
50
- /**
51
- * Callback used by the panel to regenerate the companion token.
52
- * Returns updated connection info.
53
- */
54
- export type RegenerateTokenFn = () => QrPanelConnectionInfo;
55
-
56
- /**
57
- * Callback used by the panel to copy text to the clipboard.
58
- */
59
- export type CopyToClipboardFn = (text: string) => void;
60
-
61
- /**
62
- * QrPanel - displays a QR code for companion app pairing.
63
- *
64
- * Shows connection URL, truncated token, and username above the QR code.
65
- * Supports `r` to regenerate the token and `c` to copy the token.
66
- *
67
- * QR matrix generation uses the SDK's `generateQrMatrix` via `encodeConnectionPayload`.
68
- */
69
- export class QrPanel extends BasePanel {
70
- private connectionInfo: QrPanelConnectionInfo;
71
- private readonly regenerateToken: RegenerateTokenFn | undefined;
72
- private readonly copyToClipboard: CopyToClipboardFn | undefined;
73
- private readonly controlPlaneReadModel: UiReadModel<UiControlPlaneSnapshot> | undefined;
74
- private readonly localUserAuthManager: Pick<UserAuthManager, 'inspect'> | undefined;
75
- private readonly unsub: (() => void) | null;
76
- private lastStatus = '';
77
- /** v toggles whether the token/password render in the clear. */
78
- private revealed = false;
79
- /** Set when r is pressed while a companion session is live — confirmed with y/Enter, cancelled with n/Esc. */
80
- private confirmRegenerate: ConfirmState<null> | null = null;
81
-
82
- public constructor(
83
- connectionInfo: QrPanelConnectionInfo,
84
- regenerateToken?: RegenerateTokenFn,
85
- copyToClipboard?: CopyToClipboardFn,
86
- controlPlaneReadModel?: UiReadModel<UiControlPlaneSnapshot>,
87
- localUserAuthManager?: Pick<UserAuthManager, 'inspect'>,
88
- ) {
89
- super('qr-code', 'QR Code', 'Q', 'session');
90
- this.connectionInfo = connectionInfo;
91
- this.regenerateToken = regenerateToken;
92
- this.copyToClipboard = copyToClipboard;
93
- this.controlPlaneReadModel = controlPlaneReadModel;
94
- this.localUserAuthManager = localUserAuthManager;
95
- this.unsub = controlPlaneReadModel ? controlPlaneReadModel.subscribe(() => this.markDirty()) : null;
96
- }
97
-
98
- public override onDestroy(): void {
99
- this.unsub?.();
100
- }
101
-
102
- private hasLiveCompanionSession(): boolean {
103
- return (this.localUserAuthManager?.inspect().sessionCount ?? 0) > 0;
104
- }
105
-
106
- private doRegenerate(): void {
107
- if (!this.regenerateToken) return;
108
- this.connectionInfo = this.regenerateToken();
109
- this.lastStatus = 'Token regenerated.';
110
- this.markDirty();
111
- }
112
-
113
- public handleInput(key: string): boolean {
114
- if (this.confirmRegenerate) {
115
- const result = handleConfirmInput(this.confirmRegenerate, key);
116
- if (result === 'confirmed') {
117
- this.confirmRegenerate = null;
118
- this.doRegenerate();
119
- return true;
120
- }
121
- if (result === 'cancelled') {
122
- this.confirmRegenerate = null;
123
- this.lastStatus = 'Regeneration cancelled.';
124
- this.markDirty();
125
- return true;
126
- }
127
- return true; // absorbed
128
- }
129
- if (key === 'r') {
130
- if (!this.regenerateToken) {
131
- this.lastStatus = 'Regeneration not available.';
132
- this.markDirty();
133
- return true;
134
- }
135
- if (this.hasLiveCompanionSession()) {
136
- this.confirmRegenerate = {
137
- subject: null,
138
- label: 'pairing token — a companion session is live and will be disconnected',
139
- verb: 'Regenerate',
140
- };
141
- this.markDirty();
142
- return true;
143
- }
144
- this.doRegenerate();
145
- return true;
146
- }
147
- if (key === 'v') {
148
- this.revealed = !this.revealed;
149
- this.markDirty();
150
- return true;
151
- }
152
- if (key === 'c') {
153
- if (this.copyToClipboard) {
154
- this.copyToClipboard(this.connectionInfo.token);
155
- this.lastStatus = 'Token copied to clipboard.';
156
- } else {
157
- this.lastStatus = 'Clipboard not available.';
158
- }
159
- this.markDirty();
160
- return true;
161
- }
162
- return false;
163
- }
164
-
165
- public render(width: number, height: number): Line[] {
166
- this.needsRender = false;
167
- const lines: Line[] = [];
168
-
169
- const { url, token, username, password } = this.connectionInfo;
170
- const valueWidth = Math.max(0, width - 12);
171
- const displayToken = this.revealed ? token : SECRET_MASK;
172
- const displayPassword = password !== undefined ? (this.revealed ? password : SECRET_MASK) : undefined;
173
-
174
- // ── Title + purpose: tell the operator exactly what this code is for ────
175
- lines.push(buildPanelTitle(width, 'Companion Pairing', C));
176
- lines.push(
177
- buildPanelLine(width, [
178
- [' Scan with the GoodVibes companion app to pair this session.', C.label],
179
- ]),
180
- );
181
-
182
- // ── Connection info header ─────────────────────────────────────────────
183
- // Token and (if present) bootstrap password are masked by default — they
184
- // are credentials, not display text — and only render in the clear while
185
- // `revealed` is toggled on via v.
186
- lines.push(createEmptyLine(width));
187
- lines.push(
188
- buildPanelLine(width, [
189
- [' URL ', C.label],
190
- [truncateDisplay(url, valueWidth), C.info],
191
- ]),
192
- );
193
- lines.push(
194
- buildPanelLine(width, [
195
- [' Token ', C.label],
196
- [truncateDisplay(displayToken, valueWidth), C.token],
197
- ]),
198
- );
199
- lines.push(
200
- buildPanelLine(width, [
201
- [' Username ', C.label],
202
- [truncateDisplay(username, valueWidth), C.value],
203
- ]),
204
- );
205
- if (displayPassword !== undefined) {
206
- lines.push(
207
- buildPanelLine(width, [
208
- [' Password ', C.label],
209
- [truncateDisplay(displayPassword, valueWidth), C.value],
210
- ]),
211
- );
212
- }
213
-
214
- // ── Connected companions ──────────────────────────────────────────────
215
- if (this.controlPlaneReadModel) {
216
- const connected = this.controlPlaneReadModel.getSnapshot().activeClientIds.length;
217
- lines.push(
218
- buildPanelLine(width, [
219
- [' Companions ', C.label],
220
- [`connected: ${connected}`, connected > 0 ? C.good : C.dim],
221
- ]),
222
- );
223
- }
224
- lines.push(createEmptyLine(width));
225
-
226
- // ── QR code ────────────────────────────────────────────────────────────
227
- const payload = encodeConnectionPayload({
228
- url: this.connectionInfo.url,
229
- token: this.connectionInfo.token,
230
- username: this.connectionInfo.username,
231
- ...(this.connectionInfo.password !== undefined ? { password: this.connectionInfo.password } : {}),
232
- version: this.connectionInfo.version ?? '0.0.0',
233
- surface: this.connectionInfo.surface ?? 'tui',
234
- });
235
- const matrix = generateQrMatrix(payload);
236
- const qrLines = renderQrMatrix(matrix.modules, width, { fg: C.qrFg, bg: C.qrBg });
237
- for (const qrLine of qrLines) {
238
- lines.push(qrLine);
239
- }
240
-
241
- lines.push(createEmptyLine(width));
242
-
243
- // ── Footer: confirm overlay (regenerate while a companion is live) takes
244
- // priority over the ephemeral status message ──────────────────────────
245
- const footerLines: Line[] = [];
246
- if (this.confirmRegenerate) {
247
- footerLines.push(...renderConfirmLines(width, this.confirmRegenerate));
248
- } else if (this.lastStatus) {
249
- footerLines.push(
250
- buildPanelLine(width, [
251
- [` ${this.lastStatus} `, C.label],
252
- ]),
253
- );
254
- }
255
-
256
- // ── Hints ── only advertise actions that are actually wired ─────────────
257
- const hints: Array<{ keys: string; label: string }> = [];
258
- if (this.regenerateToken) hints.push({ keys: 'r', label: 'regenerate token' });
259
- if (this.copyToClipboard) hints.push({ keys: 'c', label: 'copy token' });
260
- hints.push({ keys: 'v', label: this.revealed ? 'hide token' : 'reveal token' });
261
- const hintsLine = hints.length > 0
262
- ? buildKeyboardHints(width, hints, C)
263
- : buildPanelLine(width, [[' Pairing is read-only in this surface.', C.dim]]);
264
- footerLines.push(hintsLine);
265
-
266
- // Push the footer at the bottom if we have room, otherwise append it
267
- // directly after the QR code.
268
- const remaining = height - lines.length - footerLines.length;
269
- if (remaining > 0) {
270
- for (let i = 0; i < remaining; i++) lines.push(createEmptyLine(width));
271
- }
272
- lines.push(...footerLines);
273
-
274
- // Clamp to the exact requested height — the QR matrix's row count is
275
- // driven by payload size, not by `height`, so it can legitimately
276
- // overflow a small pane; never hand back more (or fewer) lines than asked.
277
- while (lines.length < height) lines.push(createEmptyLine(width));
278
- return lines.slice(0, height);
279
- }
280
- }