@h-rig/cli 0.0.6-alpha.83 → 0.0.6-alpha.85

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 (163) hide show
  1. package/dist/bin/rig.js +22060 -14620
  2. package/dist/src/app/board.js +155 -28
  3. package/dist/src/app-opentui/adapters/doctor.d.ts +0 -2
  4. package/dist/src/app-opentui/adapters/doctor.js +1 -25
  5. package/dist/src/app-opentui/adapters/family.d.ts +62 -0
  6. package/dist/src/app-opentui/adapters/family.js +14305 -0
  7. package/dist/src/app-opentui/adapters/fleet.d.ts +0 -2
  8. package/dist/src/app-opentui/adapters/fleet.js +6 -40
  9. package/dist/src/app-opentui/adapters/inbox.d.ts +12 -2
  10. package/dist/src/app-opentui/adapters/inbox.js +54 -59
  11. package/dist/src/app-opentui/adapters/init.d.ts +0 -2
  12. package/dist/src/app-opentui/adapters/init.js +0 -26
  13. package/dist/src/app-opentui/adapters/inspect.d.ts +52 -0
  14. package/dist/src/app-opentui/adapters/inspect.js +1024 -0
  15. package/dist/src/app-opentui/adapters/pi-attach.d.ts +15 -6
  16. package/dist/src/app-opentui/adapters/pi-attach.js +348 -93
  17. package/dist/src/app-opentui/adapters/pi.d.ts +23 -0
  18. package/dist/src/app-opentui/adapters/pi.js +363 -0
  19. package/dist/src/app-opentui/adapters/plugin.d.ts +84 -0
  20. package/dist/src/app-opentui/adapters/plugin.js +544 -0
  21. package/dist/src/app-opentui/adapters/repo.d.ts +37 -0
  22. package/dist/src/app-opentui/adapters/repo.js +186 -0
  23. package/dist/src/app-opentui/adapters/run-detail.d.ts +9 -2
  24. package/dist/src/app-opentui/adapters/run-detail.js +97 -44
  25. package/dist/src/app-opentui/adapters/server.d.ts +10 -2
  26. package/dist/src/app-opentui/adapters/server.js +96 -27
  27. package/dist/src/app-opentui/adapters/tasks.d.ts +0 -2
  28. package/dist/src/app-opentui/adapters/tasks.js +437 -105
  29. package/dist/src/app-opentui/adapters/workspace.d.ts +49 -0
  30. package/dist/src/app-opentui/adapters/workspace.js +333 -0
  31. package/dist/src/app-opentui/autocomplete.d.ts +20 -0
  32. package/dist/src/app-opentui/autocomplete.js +576 -0
  33. package/dist/src/app-opentui/bootstrap.js +24313 -16963
  34. package/dist/src/app-opentui/command-palette.d.ts +3 -0
  35. package/dist/src/app-opentui/command-palette.js +1010 -0
  36. package/dist/src/app-opentui/drone.js +8 -6
  37. package/dist/src/app-opentui/fleet-stats.d.ts +32 -0
  38. package/dist/src/app-opentui/fleet-stats.js +114 -0
  39. package/dist/src/app-opentui/index.js +3438 -1724
  40. package/dist/src/app-opentui/intent.js +71 -48
  41. package/dist/src/app-opentui/keymap.d.ts +6 -5
  42. package/dist/src/app-opentui/keymap.js +1064 -23
  43. package/dist/src/app-opentui/layout.d.ts +7 -0
  44. package/dist/src/app-opentui/layout.js +13 -6
  45. package/dist/src/app-opentui/list-search.d.ts +24 -0
  46. package/dist/src/app-opentui/list-search.js +88 -1
  47. package/dist/src/app-opentui/pi-host-child.js +33 -1
  48. package/dist/src/app-opentui/pi-pty-host.d.ts +5 -0
  49. package/dist/src/app-opentui/pi-pty-host.js +21 -3
  50. package/dist/src/app-opentui/react/App.d.ts +9 -0
  51. package/dist/src/app-opentui/react/App.js +5099 -0
  52. package/dist/src/app-opentui/react/Backdrop.d.ts +5 -0
  53. package/dist/src/app-opentui/react/Backdrop.js +829 -0
  54. package/dist/src/app-opentui/react/ChromeHost.d.ts +5 -0
  55. package/dist/src/app-opentui/react/ChromeHost.js +2942 -0
  56. package/dist/src/app-opentui/react/SceneFrameView.d.ts +7 -0
  57. package/dist/src/app-opentui/react/SceneFrameView.js +529 -0
  58. package/dist/src/app-opentui/react/context.d.ts +17 -0
  59. package/dist/src/app-opentui/react/context.js +37 -0
  60. package/dist/src/app-opentui/react/launch.d.ts +2 -0
  61. package/dist/src/app-opentui/react/launch.js +5698 -0
  62. package/dist/src/app-opentui/react/nav.d.ts +18 -0
  63. package/dist/src/app-opentui/react/nav.js +54 -0
  64. package/dist/src/app-opentui/react/scroll.d.ts +12 -0
  65. package/dist/src/app-opentui/react/scroll.js +21 -0
  66. package/dist/src/app-opentui/react/syntax.d.ts +2 -0
  67. package/dist/src/app-opentui/react/syntax.js +64 -0
  68. package/dist/src/app-opentui/registry.js +19923 -5151
  69. package/dist/src/app-opentui/render/constants.d.ts +31 -0
  70. package/dist/src/app-opentui/render/constants.js +66 -0
  71. package/dist/src/app-opentui/render/graphics.d.ts +2 -1
  72. package/dist/src/app-opentui/render/graphics.js +106 -39
  73. package/dist/src/app-opentui/render/image-visual-layer-worker.js +108 -20
  74. package/dist/src/app-opentui/render/image-visual-layer.d.ts +7 -0
  75. package/dist/src/app-opentui/render/image-visual-layer.js +109 -20
  76. package/dist/src/app-opentui/render/native-host.d.ts +37 -0
  77. package/dist/src/app-opentui/render/native-host.js +179 -0
  78. package/dist/src/app-opentui/render/panels.js +18 -11
  79. package/dist/src/app-opentui/render/preloader.js +8 -6
  80. package/dist/src/app-opentui/render/scene.d.ts +50 -1
  81. package/dist/src/app-opentui/render/scene.js +183 -6
  82. package/dist/src/app-opentui/render/text.d.ts +7 -1
  83. package/dist/src/app-opentui/render/text.js +10 -7
  84. package/dist/src/app-opentui/render/type-bar.js +69 -30
  85. package/dist/src/app-opentui/runtime.d.ts +44 -1
  86. package/dist/src/app-opentui/runtime.js +3231 -1363
  87. package/dist/src/app-opentui/scenes/command.js +20 -6
  88. package/dist/src/app-opentui/scenes/doctor.js +176 -11
  89. package/dist/src/app-opentui/scenes/error.js +20 -10
  90. package/dist/src/app-opentui/scenes/family-domains/agent.d.ts +2 -0
  91. package/dist/src/app-opentui/scenes/family-domains/agent.js +348 -0
  92. package/dist/src/app-opentui/scenes/family-domains/browser.d.ts +2 -0
  93. package/dist/src/app-opentui/scenes/family-domains/browser.js +195 -0
  94. package/dist/src/app-opentui/scenes/family-domains/dist.d.ts +2 -0
  95. package/dist/src/app-opentui/scenes/family-domains/dist.js +243 -0
  96. package/dist/src/app-opentui/scenes/family-domains/git.d.ts +2 -0
  97. package/dist/src/app-opentui/scenes/family-domains/git.js +195 -0
  98. package/dist/src/app-opentui/scenes/family-domains/github.d.ts +2 -0
  99. package/dist/src/app-opentui/scenes/family-domains/github.js +274 -0
  100. package/dist/src/app-opentui/scenes/family-domains/harness.d.ts +2 -0
  101. package/dist/src/app-opentui/scenes/family-domains/harness.js +152 -0
  102. package/dist/src/app-opentui/scenes/family-domains/index.d.ts +4 -0
  103. package/dist/src/app-opentui/scenes/family-domains/index.js +1679 -0
  104. package/dist/src/app-opentui/scenes/family-domains/kit.d.ts +76 -0
  105. package/dist/src/app-opentui/scenes/family-domains/kit.js +305 -0
  106. package/dist/src/app-opentui/scenes/family-domains/profile.d.ts +2 -0
  107. package/dist/src/app-opentui/scenes/family-domains/profile.js +212 -0
  108. package/dist/src/app-opentui/scenes/family-domains/queue.d.ts +2 -0
  109. package/dist/src/app-opentui/scenes/family-domains/queue.js +146 -0
  110. package/dist/src/app-opentui/scenes/family-domains/remote.d.ts +2 -0
  111. package/dist/src/app-opentui/scenes/family-domains/remote.js +518 -0
  112. package/dist/src/app-opentui/scenes/family-domains/review.d.ts +2 -0
  113. package/dist/src/app-opentui/scenes/family-domains/review.js +280 -0
  114. package/dist/src/app-opentui/scenes/family-domains/setup.d.ts +2 -0
  115. package/dist/src/app-opentui/scenes/family-domains/setup.js +267 -0
  116. package/dist/src/app-opentui/scenes/family-domains/stats.d.ts +2 -0
  117. package/dist/src/app-opentui/scenes/family-domains/stats.js +370 -0
  118. package/dist/src/app-opentui/scenes/family.d.ts +3 -0
  119. package/dist/src/app-opentui/scenes/family.js +2144 -0
  120. package/dist/src/app-opentui/scenes/fleet.js +521 -31
  121. package/dist/src/app-opentui/scenes/handoff.js +204 -12
  122. package/dist/src/app-opentui/scenes/help.js +609 -40
  123. package/dist/src/app-opentui/scenes/inbox.js +278 -17
  124. package/dist/src/app-opentui/scenes/init.js +84 -39
  125. package/dist/src/app-opentui/scenes/inspect.d.ts +3 -0
  126. package/dist/src/app-opentui/scenes/inspect.js +793 -0
  127. package/dist/src/app-opentui/scenes/main.js +218 -39
  128. package/dist/src/app-opentui/scenes/pi.d.ts +3 -0
  129. package/dist/src/app-opentui/scenes/pi.js +508 -0
  130. package/dist/src/app-opentui/scenes/plugin.d.ts +3 -0
  131. package/dist/src/app-opentui/scenes/plugin.js +486 -0
  132. package/dist/src/app-opentui/scenes/repo.d.ts +3 -0
  133. package/dist/src/app-opentui/scenes/repo.js +424 -0
  134. package/dist/src/app-opentui/scenes/run-detail.js +333 -20
  135. package/dist/src/app-opentui/scenes/server.js +181 -15
  136. package/dist/src/app-opentui/scenes/tasks.js +486 -31
  137. package/dist/src/app-opentui/scenes/workspace.d.ts +3 -0
  138. package/dist/src/app-opentui/scenes/workspace.js +426 -0
  139. package/dist/src/app-opentui/selectable.d.ts +19 -0
  140. package/dist/src/app-opentui/selectable.js +79 -0
  141. package/dist/src/app-opentui/state.js +83 -30
  142. package/dist/src/app-opentui/surface-catalog.d.ts +20 -0
  143. package/dist/src/app-opentui/surface-catalog.js +540 -0
  144. package/dist/src/app-opentui/theme.d.ts +28 -6
  145. package/dist/src/app-opentui/theme.js +61 -8
  146. package/dist/src/app-opentui/types.d.ts +121 -4
  147. package/dist/src/commands/_authority-runs.d.ts +1 -1
  148. package/dist/src/commands/_authority-runs.js +2 -12
  149. package/dist/src/commands/_help-catalog.js +95 -15
  150. package/dist/src/commands/_operator-view.js +34 -2
  151. package/dist/src/commands/_pi-frontend.d.ts +2 -0
  152. package/dist/src/commands/_pi-frontend.js +34 -0
  153. package/dist/src/commands/_server-events.d.ts +26 -0
  154. package/dist/src/commands/_server-events.js +310 -0
  155. package/dist/src/commands/agent.js +2 -12
  156. package/dist/src/commands/run.js +34 -2
  157. package/dist/src/commands/server.js +2 -12
  158. package/dist/src/commands/stats.js +95 -15
  159. package/dist/src/commands/task-run-driver.js +2 -12
  160. package/dist/src/commands/task.js +131 -29
  161. package/dist/src/commands.js +192 -31
  162. package/dist/src/index.js +192 -31
  163. package/package.json +11 -9
@@ -11,15 +11,17 @@ var RIG_UI = {
11
11
  bg: "#070809",
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
- panel2: "#101115",
15
- glass: "#14161b",
16
- ink: "#f2f3f6",
17
- ink2: "#aeb0ba",
18
- ink3: "#6c6e79",
19
- ink4: "#44464f",
14
+ panel2: "#14161b",
15
+ glass: "#1e2230",
16
+ border: "#2a2e3a",
17
+ ink: "#f4f5f8",
18
+ ink2: "#c7c9d3",
19
+ ink3: "#9aa0ae",
20
+ ink4: "#787b86",
20
21
  lime: "#ccff4d",
21
22
  limeDim: "#a9d63f",
22
23
  cyan: "#56d8ff",
24
+ cyanDim: "#3f9fbd",
23
25
  red: "#ff5d5d",
24
26
  yellow: "#ffd24d",
25
27
  magenta: "#ff79b0"
@@ -38,11 +40,11 @@ var styles = {
38
40
  };
39
41
  function statusColor(status) {
40
42
  switch (status) {
41
- case "running":
42
43
  case "success":
43
44
  case "completed":
44
45
  case "merged":
45
46
  return RIG_UI.lime;
47
+ case "running":
46
48
  case "preparing":
47
49
  case "created":
48
50
  case "validating":
@@ -63,10 +65,141 @@ function statusColor(status) {
63
65
  }
64
66
  }
65
67
 
68
+ // packages/cli/src/app-opentui/drone.ts
69
+ import { RGBA, StyledText, TextAttributes as TextAttributes2 } from "@opentui/core";
70
+ var MINI_DRONE = [
71
+ "(!!!) (!!!)",
72
+ " \\%==%/ ",
73
+ " %%?%% ",
74
+ " /%==%\\ ",
75
+ "(!!!) (!!!)"
76
+ ];
77
+ var LEAD_MARK = [
78
+ " .-=-. ",
79
+ "=%%?%%=",
80
+ " '-=-' "
81
+ ];
82
+ var BLADE_FRAMES = ["---", "\\\\\\", "|||", "///"];
83
+ var EYE_FRAMES = ["o", "@", "\u2022", "."];
84
+ var COLOR = {
85
+ body: RGBA.fromHex(RIG_UI.lime),
86
+ mini: RGBA.fromHex(RIG_UI.limeDim),
87
+ rotor: RGBA.fromHex(RIG_UI.cyan),
88
+ path: RGBA.fromHex(RIG_UI.cyan),
89
+ eye: RGBA.fromHex(RIG_UI.ink),
90
+ dim: RGBA.fromHex(RIG_UI.ink4),
91
+ ink: RGBA.fromHex(RIG_UI.ink2)
92
+ };
93
+ function bladeForTick(tick, phase = 0) {
94
+ return BLADE_FRAMES[(Math.floor(tick / 3) + phase) % BLADE_FRAMES.length];
95
+ }
96
+ function eyeForTick(tick, phase = 0) {
97
+ const pulse = Math.sin(tick * 0.09 + phase);
98
+ return pulse > 0.55 ? EYE_FRAMES[1] : pulse > 0.1 ? EYE_FRAMES[0] : pulse > -0.45 ? EYE_FRAMES[2] : EYE_FRAMES[3];
99
+ }
100
+ function chunk(text, fg, bold = false, dim = false) {
101
+ let attributes = TextAttributes2.NONE;
102
+ if (bold)
103
+ attributes |= TextAttributes2.BOLD;
104
+ if (dim)
105
+ attributes |= TextAttributes2.DIM;
106
+ return { __isChunk: true, text, fg, ...attributes !== TextAttributes2.NONE ? { attributes } : {} };
107
+ }
108
+ function styledLine(text, colorFor) {
109
+ const chunks = [];
110
+ let run = "";
111
+ let runColor = null;
112
+ const flush = () => {
113
+ if (!run || !runColor)
114
+ return;
115
+ chunks.push(chunk(run, COLOR[runColor], runColor === "body" || runColor === "eye", runColor === "dim"));
116
+ run = "";
117
+ };
118
+ for (const char of text) {
119
+ const next = colorFor(char);
120
+ if (next !== runColor) {
121
+ flush();
122
+ runColor = next;
123
+ }
124
+ run += char;
125
+ }
126
+ flush();
127
+ return new StyledText(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
128
+ }
129
+ function droneColor(char) {
130
+ if (char === "?" || char === "o" || char === "@" || char === "\u2022")
131
+ return "eye";
132
+ if (char === "%" || char === "=")
133
+ return "mini";
134
+ if (char === "$")
135
+ return "body";
136
+ if (char === "(" || char === ")" || char === "/" || char === "\\" || char === "|" || char === "-")
137
+ return "rotor";
138
+ if (char === "\xB7" || char === "\u2022" || char === "'" || char === ".")
139
+ return "path";
140
+ if (char.trim() === "")
141
+ return "dim";
142
+ return "ink";
143
+ }
144
+ function motionColor(char) {
145
+ if (char === "\u25CF" || char === "\u25C6" || char === "\u25C9")
146
+ return "body";
147
+ if (char === "\u2022" || char === "\xB7" || char === "\u2500" || char === "\u2502" || char === "\u2571" || char === "\u2572" || char === "\u256D" || char === "\u256E" || char === "\u2570" || char === "\u256F")
148
+ return "path";
149
+ if (char === "\u258C" || char === "\u2590" || char === "\u2581" || char === "\u2594")
150
+ return "mini";
151
+ return droneColor(char);
152
+ }
153
+ function materializeDrone(lines, tick, phase = 0) {
154
+ const blade = bladeForTick(tick, phase);
155
+ const eye = eyeForTick(tick, phase);
156
+ return lines.map((line) => line.replaceAll("!!!", blade).replaceAll("?", eye));
157
+ }
158
+ function miniDroneLines(tick, align = "left", phase = 0) {
159
+ return materializeDrone(MINI_DRONE, tick, phase).map((text) => ({
160
+ text,
161
+ styledText: styledLine(text, droneColor),
162
+ fg: RIG_UI.limeDim,
163
+ align
164
+ }));
165
+ }
166
+ function brandFleetLines(tick, align = "center") {
167
+ const left = materializeDrone(MINI_DRONE, tick, 0);
168
+ const right = materializeDrone(MINI_DRONE, tick, 2);
169
+ const lead = materializeDrone(LEAD_MARK, tick, 1);
170
+ const packet = ["\xB7", "\u2022", "\u25CF", "\u2022"][Math.floor(tick / 2) % 4];
171
+ const bridge = [
172
+ ` ${left[0]} ${lead[0]} ${right[0]}`,
173
+ ` ${left[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${lead[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${right[1]}`,
174
+ ` ${left[2]} \xB7' '${lead[2]}' '\xB7 ${right[2]}`,
175
+ ` ${left[3]} \u2572 signal bus \u2571 ${right[3]}`,
176
+ ` ${left[4]} \xB7\u2500\u2500\u2500${packet}\u2500\u2500\u2500\u2500\u2500\u2500${packet}\u2500\u2500\u2500\xB7 ${right[4]}`
177
+ ];
178
+ return bridge.map((text) => ({
179
+ text,
180
+ styledText: styledLine(text, motionColor),
181
+ fg: RIG_UI.limeDim,
182
+ align
183
+ }));
184
+ }
185
+ function actionDrone(tick, phase = 0) {
186
+ const blade = bladeForTick(tick, phase);
187
+ const eye = eyeForTick(tick, phase);
188
+ return `(${blade})\u2572${eye}\u2571(${blade})`;
189
+ }
190
+ function actionDroneLine(tick, text, fg = RIG_UI.ink2) {
191
+ const prefix = actionDrone(tick);
192
+ const content = `${prefix} ${text}`;
193
+ return { text: content, styledText: styledLine(content, motionColor), fg, align: "center" };
194
+ }
195
+
66
196
  // packages/cli/src/app-opentui/render/scene.ts
67
197
  function line(text, options = {}) {
68
198
  return { text, ...options };
69
199
  }
200
+ function blank() {
201
+ return { text: "" };
202
+ }
70
203
  function deckRow(input) {
71
204
  const prefix = input.active ? "\u25B8" : " ";
72
205
  const label = input.label.toUpperCase().padEnd(10);
@@ -80,11 +213,81 @@ function deckRow(input) {
80
213
  function kv(label, value, fg = RIG_UI.ink2) {
81
214
  return line(` ${label.toUpperCase().padEnd(10)} ${value}`, { fg });
82
215
  }
216
+ function loadingRows(label, tick = 0, compact = false) {
217
+ const mascot = compact ? miniDroneLines(tick, "center") : brandFleetLines(tick, "center");
218
+ return [
219
+ blank(),
220
+ ...mascot,
221
+ blank(),
222
+ { ...actionDroneLine(tick, `loading ${label}\u2026`, RIG_UI.cyan), align: "center" }
223
+ ];
224
+ }
225
+ function errorBanner(message, hint) {
226
+ return [
227
+ line(`\u2717 failed to load \u2014 ${message}`, { fg: RIG_UI.red, bold: true }),
228
+ ...hint ? [line(` ${hint}`, { fg: RIG_UI.ink3 })] : [],
229
+ line(" select refresh/retry below", { fg: RIG_UI.ink4 }),
230
+ line("", { fg: RIG_UI.ink3 })
231
+ ];
232
+ }
233
+ function lastRefreshError(state) {
234
+ const value = state.data.lastRefreshError;
235
+ return typeof value === "string" && value.trim() ? value : undefined;
236
+ }
83
237
  function makeSceneFrame(input) {
84
238
  return input;
85
239
  }
86
240
 
241
+ // packages/cli/src/app-opentui/selectable.ts
242
+ function withSelectable(line2, item) {
243
+ return { ...line2, selectable: item, activateOnClick: true };
244
+ }
245
+
87
246
  // packages/cli/src/app-opentui/scenes/run-detail.ts
247
+ function actionRow(deck, item) {
248
+ const row = deckRow({ ...deck, activateOnClick: true });
249
+ return item ? withSelectable(row, item) : row;
250
+ }
251
+ function isTerminalRunStatus(status) {
252
+ return ["completed", "complete", "done", "failed", "error", "errored", "merged", "cancelled", "canceled", "stopped", "aborted"].includes(status.toLowerCase());
253
+ }
254
+ function runDetailActions(runId, status) {
255
+ const short = runId ? runId.slice(0, 8) : "";
256
+ const attach = {
257
+ label: "pi",
258
+ detail: "attach bundled Pi \u2014 live or finished transcript",
259
+ item: runId ? { id: "attach", label: "attach", intent: { scene: "handoff", argv: ["attach", runId], action: { kind: "run-attach", payload: { runId }, label: `Attach Pi ${short}` } }, message: "attach real bundled Pi" } : null
260
+ };
261
+ const inspect = {
262
+ label: "inspect",
263
+ detail: "logs/timeline/diff/failures",
264
+ item: runId ? { id: "inspect", label: "inspect", data: { selectedRunId: runId }, intent: { scene: "inspect", argv: ["inspect", runId], action: { kind: "refresh", payload: { runId }, label: `Inspect ${short}` } }, message: "logs/timeline/diff/failures" } : null
265
+ };
266
+ const fleet = {
267
+ label: "runs",
268
+ detail: "return to fleet",
269
+ item: { id: "fleet", label: "fleet", intent: { scene: "fleet", argv: ["fleet"], action: { kind: "refresh", label: "Opening fleet" } }, message: "back to fleet" }
270
+ };
271
+ if (runId && isTerminalRunStatus(status)) {
272
+ const rerun = {
273
+ label: "rerun",
274
+ detail: "re-dispatch this run from a clean runtime",
275
+ item: { id: "rerun", label: "rerun", intent: { scene: "command", argv: ["run", "restart", runId], action: { kind: "command-run", payload: { runId }, label: `Rerun ${short}` } }, message: "re-dispatch this run (rig run restart)" }
276
+ };
277
+ return [attach, rerun, inspect, fleet];
278
+ }
279
+ const steer = {
280
+ label: "steer",
281
+ detail: "send an inline steer message",
282
+ item: runId ? { id: "steer", label: "steer", prompt: { label: `steer ${short}`, scene: "run-detail", argv: ["steer", runId], intentKind: "run-steer", payloadBase: { runId }, payloadKey: "message" }, message: "send an inline steer message" } : null
283
+ };
284
+ const stop = {
285
+ label: "stop",
286
+ detail: "request stop",
287
+ item: runId ? { id: "stop", label: "stop", intent: { scene: "run-detail", argv: ["stop", runId], action: { kind: "run-stop", payload: { runId }, label: `Stop ${short}` } }, message: "request stop" } : null
288
+ };
289
+ return [attach, steer, stop, inspect, fleet];
290
+ }
88
291
  function snapshot(state) {
89
292
  const value = state.data.runDetail;
90
293
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -94,8 +297,103 @@ function selectedRunId(state, run) {
94
297
  return run.runId;
95
298
  return typeof state.data.selectedRunId === "string" ? state.data.selectedRunId : undefined;
96
299
  }
97
- function entryText(entry) {
98
- return String(entry.detail ?? entry.message ?? entry.text ?? entry.title ?? entry.type ?? "event").replace(/\s+/g, " ").slice(0, 96);
300
+ var ENTRY_ROW_LIMIT = 40;
301
+ function entryField(entry, keys) {
302
+ for (const key of keys) {
303
+ const value = entry[key];
304
+ if (typeof value === "string" && value.trim())
305
+ return value.trim();
306
+ if (typeof value === "number")
307
+ return String(value);
308
+ }
309
+ return "";
310
+ }
311
+ function entryMessage(entry) {
312
+ return String(entry.detail ?? entry.message ?? entry.text ?? entry.title ?? entry.type ?? "event").replace(/\s+/g, " ").slice(0, 160);
313
+ }
314
+ function entrySeverityColor(entry, fallback) {
315
+ const token = `${entryField(entry, ["type", "level", "status", "kind"])} ${entryMessage(entry)}`.toLowerCase();
316
+ if (/\b(fail|failed|failure|error|fatal|crash|abort)\b/.test(token))
317
+ return RIG_UI.red;
318
+ if (/\b(warn|warning|attention|degraded|retry)\b/.test(token))
319
+ return RIG_UI.yellow;
320
+ if (/\b(success|succeeded|completed|done|merged|passed)\b/.test(token))
321
+ return RIG_UI.limeDim;
322
+ return fallback;
323
+ }
324
+ function entryRow(entry, fallback) {
325
+ const color = entrySeverityColor(entry, fallback);
326
+ const time = entryField(entry, ["timestamp", "ts", "time", "at"]).replace("T", " ").slice(0, 19);
327
+ const type = entryField(entry, ["type", "level", "kind", "status"]).slice(0, 14);
328
+ const glyph = color === RIG_UI.red ? "\u2717" : color === RIG_UI.yellow ? "\u26A0" : "\xB7";
329
+ const segments = [time, type ? type.toUpperCase() : undefined, entryMessage(entry)].filter((part) => Boolean(part));
330
+ return line(`${glyph} ${segments.join(" \xB7 ")}`, { fg: color, bold: color === RIG_UI.red });
331
+ }
332
+ function entrySection(label, entries, fallback) {
333
+ if (entries.length === 0)
334
+ return [];
335
+ const shown = entries.slice(0, ENTRY_ROW_LIMIT);
336
+ const overflow = entries.length - shown.length;
337
+ return [
338
+ line("", { fg: RIG_UI.ink3 }),
339
+ line(`${label} (${entries.length})`, { fg: RIG_UI.ink3, bold: true }),
340
+ ...shown.map((entry) => entryRow(entry, fallback)),
341
+ ...overflow > 0 ? [line(` (${overflow} more)`, { fg: RIG_UI.ink4 })] : []
342
+ ];
343
+ }
344
+ function primaryError(state) {
345
+ return state.error?.message ?? lastRefreshError(state);
346
+ }
347
+ function optimisticStatus(state, runId) {
348
+ const value = state.data.optimisticRun;
349
+ if (!value || typeof value !== "object" || Array.isArray(value))
350
+ return;
351
+ const record = value;
352
+ if (runId && record.runId !== runId)
353
+ return;
354
+ return typeof record.status === "string" ? record.status : undefined;
355
+ }
356
+ function pendingSteer(state, runId) {
357
+ const value = state.data.pendingRunMessage;
358
+ if (!value || typeof value !== "object" || Array.isArray(value))
359
+ return null;
360
+ const record = value;
361
+ if (runId && record.runId !== runId)
362
+ return null;
363
+ if (typeof record.text !== "string")
364
+ return null;
365
+ return {
366
+ text: record.text,
367
+ status: typeof record.status === "string" ? record.status : "queued",
368
+ ...typeof record.result === "string" && record.result.trim() ? { result: record.result.trim() } : {}
369
+ };
370
+ }
371
+ function steerBlock(pending) {
372
+ const failed = pending.status === "failed";
373
+ const sent = pending.status === "sent" || pending.status === "delivered";
374
+ const glyph = failed ? "\u2717" : sent ? "\u2713" : "\u27F3";
375
+ const color = failed ? RIG_UI.red : sent ? RIG_UI.lime : RIG_UI.cyan;
376
+ const echoed = pending.text.length > 84 ? `${pending.text.slice(0, 83)}\u2026` : pending.text;
377
+ return [
378
+ line("", { fg: RIG_UI.ink3 }),
379
+ line(`${glyph} STEER \xB7 ${pending.status}`, { fg: color, bold: true }),
380
+ line(` "${echoed}"`, { fg: RIG_UI.ink2 }),
381
+ ...pending.result ? [line(` ${failed ? "error" : "result"}: ${pending.result.replace(/\s+/g, " ").slice(0, 120)}`, { fg: failed ? RIG_UI.red : RIG_UI.ink3 })] : []
382
+ ];
383
+ }
384
+ function timelineCountLabel(snapshot2) {
385
+ const shown = snapshot2.timeline.length;
386
+ const total = snapshot2.timelineTotal;
387
+ return total !== undefined && total > shown ? `showing ${shown} of ${total}` : `${shown} loaded`;
388
+ }
389
+ function loadMoreItem(runId, cursor) {
390
+ return {
391
+ id: "timeline-load-more",
392
+ label: "load more timeline",
393
+ data: { runDetailLoadMore: { runId, cursor } },
394
+ intent: { scene: "run-detail", argv: ["run", "status", runId], action: { kind: "refresh", payload: { runId }, label: "Load more timeline" } },
395
+ message: "fetch the next page of timeline events"
396
+ };
99
397
  }
100
398
  function panelWidth(layout) {
101
399
  return layout?.centerWidth ?? 100;
@@ -106,10 +404,24 @@ function panelHeight(layout) {
106
404
  function renderRunDetailScene(state, layout) {
107
405
  const run = snapshot(state);
108
406
  const runId = selectedRunId(state, run);
109
- const selected = Math.max(0, Math.min(2, state.selection.index));
110
- const timeline = run?.timeline.slice(-5).map((entry) => entryText(entry)) ?? [];
111
- const logs = run?.logs.slice(-3).map((entry) => entryText(entry)) ?? [];
112
- const status = run?.status ?? "loading";
407
+ const status = optimisticStatus(state, runId) ?? run?.status ?? "loading";
408
+ const actions = runDetailActions(runId, status);
409
+ const actionCount = actions.length;
410
+ const canLoadMore = Boolean(run?.timelineCursor && runId);
411
+ const loadMoreIndex = actionCount;
412
+ const maxIndex = canLoadMore ? loadMoreIndex : Math.max(0, actionCount - 1);
413
+ const selected = Math.max(0, Math.min(maxIndex, state.selection.index));
414
+ const timeline = run?.timeline ?? [];
415
+ const logs = run?.logs ?? [];
416
+ const pending = pendingSteer(state, runId);
417
+ const loading = state.status === "loading" && !run;
418
+ const errorText = primaryError(state);
419
+ const timelineSection = run ? [
420
+ line("", { fg: RIG_UI.ink3 }),
421
+ line(`TIMELINE \xB7 ${timelineCountLabel(run)}`, { fg: RIG_UI.ink3, bold: true }),
422
+ ...timeline.slice(0, ENTRY_ROW_LIMIT).map((entry) => entryRow(entry, RIG_UI.ink2)),
423
+ ...canLoadMore && runId ? [withSelectable(deckRow({ label: "load more", detail: `next page \xB7 ${timelineCountLabel(run)}`, index: loadMoreIndex, active: selected === loadMoreIndex, activateOnClick: true }), loadMoreItem(runId, run.timelineCursor))] : []
424
+ ] : [];
113
425
  return makeSceneFrame({
114
426
  scene: "run-detail",
115
427
  title: "Run",
@@ -123,20 +435,22 @@ function renderRunDetailScene(state, layout) {
123
435
  width: panelWidth(layout),
124
436
  height: panelHeight(layout),
125
437
  lines: [
438
+ ...errorText ? errorBanner(errorText, "select inspect/runs below, or retry the action") : [],
126
439
  line(run ? run.title : "Run telemetry", { fg: RIG_UI.ink, bold: true }),
127
- ...runId ? [kv("run", runId, RIG_UI.ink2), kv("status", status, statusColor(status))] : [line("loading run telemetry", { fg: RIG_UI.ink3 })],
440
+ ...loading ? loadingRows("run telemetry", state.tick) : runId ? [kv("run", runId, RIG_UI.ink2), kv("status", status, statusColor(status))] : [line("loading run telemetry", { fg: RIG_UI.ink3 })],
441
+ ...pending ? steerBlock(pending) : [],
128
442
  line("", { fg: RIG_UI.ink3 }),
129
443
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
130
- deckRow({ label: "pi", detail: "attach bundled Pi", index: 0, active: selected === 0, activateOnClick: true }),
131
- deckRow({ label: "stop", detail: "request stop", index: 1, active: selected === 1, activateOnClick: true }),
132
- deckRow({ label: "runs", detail: "return to fleet", index: 2, active: selected === 2, activateOnClick: true }),
133
- ...timeline.length > 0 ? [line("", { fg: RIG_UI.ink3 }), line("TIMELINE", { fg: RIG_UI.ink3, bold: true }), ...timeline.map((entry) => line(`\xB7 ${entry}`, { fg: RIG_UI.ink2 }))] : [],
134
- ...logs.length > 0 ? [line("", { fg: RIG_UI.ink3 }), line("LOGS", { fg: RIG_UI.ink3, bold: true }), ...logs.map((entry) => line(`\xB7 ${entry}`, { fg: RIG_UI.ink4 }))] : []
444
+ ...actions.map((action, index) => actionRow({ label: action.label, detail: action.detail, index, active: selected === index }, action.item)),
445
+ ...timelineSection,
446
+ ...entrySection("LOGS", logs, RIG_UI.ink3)
135
447
  ],
136
448
  backgroundColor: RIG_UI.panel,
137
449
  backgroundAlpha: 184,
138
450
  opacity: 1,
139
451
  border: false,
452
+ stickyScroll: true,
453
+ stickyStart: "bottom",
140
454
  chrome: "ad-terminal",
141
455
  headerText: runId ? `run ${runId.slice(0, 8)}` : "run",
142
456
  headerHeight: 3,
@@ -144,7 +458,6 @@ function renderRunDetailScene(state, layout) {
144
458
  paddingY: 1
145
459
  }],
146
460
  footer: { run: runId ? `${runId.slice(0, 8)} ${status}` : undefined, message: "enter/click action" },
147
- hideTypeBar: true,
148
461
  live: state.status === "action" || !run
149
462
  });
150
463
  }
@@ -11,15 +11,17 @@ var RIG_UI = {
11
11
  bg: "#070809",
12
12
  bg2: "#0b0c0e",
13
13
  panel: "#101115",
14
- panel2: "#101115",
15
- glass: "#14161b",
16
- ink: "#f2f3f6",
17
- ink2: "#aeb0ba",
18
- ink3: "#6c6e79",
19
- ink4: "#44464f",
14
+ panel2: "#14161b",
15
+ glass: "#1e2230",
16
+ border: "#2a2e3a",
17
+ ink: "#f4f5f8",
18
+ ink2: "#c7c9d3",
19
+ ink3: "#9aa0ae",
20
+ ink4: "#787b86",
20
21
  lime: "#ccff4d",
21
22
  limeDim: "#a9d63f",
22
23
  cyan: "#56d8ff",
24
+ cyanDim: "#3f9fbd",
23
25
  red: "#ff5d5d",
24
26
  yellow: "#ffd24d",
25
27
  magenta: "#ff79b0"
@@ -37,6 +39,134 @@ var styles = {
37
39
  magenta: otuiFg(RIG_UI.magenta)
38
40
  };
39
41
 
42
+ // packages/cli/src/app-opentui/drone.ts
43
+ import { RGBA, StyledText, TextAttributes as TextAttributes2 } from "@opentui/core";
44
+ var MINI_DRONE = [
45
+ "(!!!) (!!!)",
46
+ " \\%==%/ ",
47
+ " %%?%% ",
48
+ " /%==%\\ ",
49
+ "(!!!) (!!!)"
50
+ ];
51
+ var LEAD_MARK = [
52
+ " .-=-. ",
53
+ "=%%?%%=",
54
+ " '-=-' "
55
+ ];
56
+ var BLADE_FRAMES = ["---", "\\\\\\", "|||", "///"];
57
+ var EYE_FRAMES = ["o", "@", "\u2022", "."];
58
+ var COLOR = {
59
+ body: RGBA.fromHex(RIG_UI.lime),
60
+ mini: RGBA.fromHex(RIG_UI.limeDim),
61
+ rotor: RGBA.fromHex(RIG_UI.cyan),
62
+ path: RGBA.fromHex(RIG_UI.cyan),
63
+ eye: RGBA.fromHex(RIG_UI.ink),
64
+ dim: RGBA.fromHex(RIG_UI.ink4),
65
+ ink: RGBA.fromHex(RIG_UI.ink2)
66
+ };
67
+ function bladeForTick(tick, phase = 0) {
68
+ return BLADE_FRAMES[(Math.floor(tick / 3) + phase) % BLADE_FRAMES.length];
69
+ }
70
+ function eyeForTick(tick, phase = 0) {
71
+ const pulse = Math.sin(tick * 0.09 + phase);
72
+ return pulse > 0.55 ? EYE_FRAMES[1] : pulse > 0.1 ? EYE_FRAMES[0] : pulse > -0.45 ? EYE_FRAMES[2] : EYE_FRAMES[3];
73
+ }
74
+ function chunk(text, fg, bold = false, dim = false) {
75
+ let attributes = TextAttributes2.NONE;
76
+ if (bold)
77
+ attributes |= TextAttributes2.BOLD;
78
+ if (dim)
79
+ attributes |= TextAttributes2.DIM;
80
+ return { __isChunk: true, text, fg, ...attributes !== TextAttributes2.NONE ? { attributes } : {} };
81
+ }
82
+ function styledLine(text, colorFor) {
83
+ const chunks = [];
84
+ let run = "";
85
+ let runColor = null;
86
+ const flush = () => {
87
+ if (!run || !runColor)
88
+ return;
89
+ chunks.push(chunk(run, COLOR[runColor], runColor === "body" || runColor === "eye", runColor === "dim"));
90
+ run = "";
91
+ };
92
+ for (const char of text) {
93
+ const next = colorFor(char);
94
+ if (next !== runColor) {
95
+ flush();
96
+ runColor = next;
97
+ }
98
+ run += char;
99
+ }
100
+ flush();
101
+ return new StyledText(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
102
+ }
103
+ function droneColor(char) {
104
+ if (char === "?" || char === "o" || char === "@" || char === "\u2022")
105
+ return "eye";
106
+ if (char === "%" || char === "=")
107
+ return "mini";
108
+ if (char === "$")
109
+ return "body";
110
+ if (char === "(" || char === ")" || char === "/" || char === "\\" || char === "|" || char === "-")
111
+ return "rotor";
112
+ if (char === "\xB7" || char === "\u2022" || char === "'" || char === ".")
113
+ return "path";
114
+ if (char.trim() === "")
115
+ return "dim";
116
+ return "ink";
117
+ }
118
+ function motionColor(char) {
119
+ if (char === "\u25CF" || char === "\u25C6" || char === "\u25C9")
120
+ return "body";
121
+ if (char === "\u2022" || char === "\xB7" || char === "\u2500" || char === "\u2502" || char === "\u2571" || char === "\u2572" || char === "\u256D" || char === "\u256E" || char === "\u2570" || char === "\u256F")
122
+ return "path";
123
+ if (char === "\u258C" || char === "\u2590" || char === "\u2581" || char === "\u2594")
124
+ return "mini";
125
+ return droneColor(char);
126
+ }
127
+ function materializeDrone(lines, tick, phase = 0) {
128
+ const blade = bladeForTick(tick, phase);
129
+ const eye = eyeForTick(tick, phase);
130
+ return lines.map((line) => line.replaceAll("!!!", blade).replaceAll("?", eye));
131
+ }
132
+ function miniDroneLines(tick, align = "left", phase = 0) {
133
+ return materializeDrone(MINI_DRONE, tick, phase).map((text) => ({
134
+ text,
135
+ styledText: styledLine(text, droneColor),
136
+ fg: RIG_UI.limeDim,
137
+ align
138
+ }));
139
+ }
140
+ function brandFleetLines(tick, align = "center") {
141
+ const left = materializeDrone(MINI_DRONE, tick, 0);
142
+ const right = materializeDrone(MINI_DRONE, tick, 2);
143
+ const lead = materializeDrone(LEAD_MARK, tick, 1);
144
+ const packet = ["\xB7", "\u2022", "\u25CF", "\u2022"][Math.floor(tick / 2) % 4];
145
+ const bridge = [
146
+ ` ${left[0]} ${lead[0]} ${right[0]}`,
147
+ ` ${left[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${lead[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${right[1]}`,
148
+ ` ${left[2]} \xB7' '${lead[2]}' '\xB7 ${right[2]}`,
149
+ ` ${left[3]} \u2572 signal bus \u2571 ${right[3]}`,
150
+ ` ${left[4]} \xB7\u2500\u2500\u2500${packet}\u2500\u2500\u2500\u2500\u2500\u2500${packet}\u2500\u2500\u2500\xB7 ${right[4]}`
151
+ ];
152
+ return bridge.map((text) => ({
153
+ text,
154
+ styledText: styledLine(text, motionColor),
155
+ fg: RIG_UI.limeDim,
156
+ align
157
+ }));
158
+ }
159
+ function actionDrone(tick, phase = 0) {
160
+ const blade = bladeForTick(tick, phase);
161
+ const eye = eyeForTick(tick, phase);
162
+ return `(${blade})\u2572${eye}\u2571(${blade})`;
163
+ }
164
+ function actionDroneLine(tick, text, fg = RIG_UI.ink2) {
165
+ const prefix = actionDrone(tick);
166
+ const content = `${prefix} ${text}`;
167
+ return { text: content, styledText: styledLine(content, motionColor), fg, align: "center" };
168
+ }
169
+
40
170
  // packages/cli/src/app-opentui/render/scene.ts
41
171
  function line(text, options = {}) {
42
172
  return { text, ...options };
@@ -54,11 +184,49 @@ function deckRow(input) {
54
184
  activateOnClick: input.activateOnClick
55
185
  });
56
186
  }
187
+ function loadingRows(label, tick = 0, compact = false) {
188
+ const mascot = compact ? miniDroneLines(tick, "center") : brandFleetLines(tick, "center");
189
+ return [
190
+ blank(),
191
+ ...mascot,
192
+ blank(),
193
+ { ...actionDroneLine(tick, `loading ${label}\u2026`, RIG_UI.cyan), align: "center" }
194
+ ];
195
+ }
196
+ function errorBanner(message, hint) {
197
+ return [
198
+ line(`\u2717 failed to load \u2014 ${message}`, { fg: RIG_UI.red, bold: true }),
199
+ ...hint ? [line(` ${hint}`, { fg: RIG_UI.ink3 })] : [],
200
+ line(" select refresh/retry below", { fg: RIG_UI.ink4 }),
201
+ line("", { fg: RIG_UI.ink3 })
202
+ ];
203
+ }
204
+ function lastRefreshError(state) {
205
+ const value = state.data.lastRefreshError;
206
+ return typeof value === "string" && value.trim() ? value : undefined;
207
+ }
57
208
  function makeSceneFrame(input) {
58
209
  return input;
59
210
  }
60
211
 
212
+ // packages/cli/src/app-opentui/selectable.ts
213
+ function selectableDeckRow(deck, item) {
214
+ return { ...deckRow({ ...deck, activateOnClick: true }), selectable: item };
215
+ }
216
+
61
217
  // packages/cli/src/app-opentui/scenes/server.ts
218
+ var SERVER_ACTIONS = [
219
+ { detail: "probe selected server, project root, and GitHub auth", item: { id: "refresh", label: "refresh", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Refresh server" } }, message: "refresh selected server" } },
220
+ { detail: "select local server for this repo", item: { id: "local", label: "use local", intent: { scene: "command", argv: ["server", "use", "local"], action: { kind: "command-run", label: "Use local server" } }, message: "switch this repo to local" } },
221
+ { detail: "choose/switch to a saved remote server alias", item: { id: "remote", label: "use remote", intent: { scene: "command", argv: ["server", "use"], action: { kind: "command-run", label: "Choose remote server" } }, message: "choose a saved remote alias" } },
222
+ { detail: "show saved local/remote aliases", item: { id: "list", label: "list servers", intent: { scene: "command", argv: ["server", "list"], action: { kind: "command-run", label: "List saved servers" } }, message: "list saved server aliases" } },
223
+ { detail: "open server add command workspace", item: { id: "add", label: "add remote", intent: { scene: "command", argv: ["server", "add"], action: { kind: "command-run", label: "Add remote server" } }, message: "open server add workspace" } },
224
+ { detail: "spawn the local rig-server detached and return here", item: { id: "start", label: "start local", intent: { scene: "server", argv: ["server", "start"], action: { kind: "refresh", payload: { startLocal: true }, label: "Start local server" } }, message: "start local server detached" } },
225
+ { detail: "import gh token into the selected server namespace", item: { id: "auth", label: "import auth", intent: { scene: "command", argv: ["github", "auth", "import-gh"], action: { kind: "command-run", label: "Import GitHub auth" } }, message: "import gh auth to selected server" } },
226
+ { detail: "diagnose server/auth/project linkage", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify server/auth" } },
227
+ { detail: "verify selected server can list task source", item: { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["task", "list"], action: { kind: "refresh", label: "Open tasks" } }, message: "verify task source" } },
228
+ { detail: "verify selected server can list recent runs", item: { id: "runs", label: "runs", intent: { scene: "fleet", argv: ["run", "status"], action: { kind: "refresh", label: "Open runs" } }, message: "open recent runs" } }
229
+ ];
62
230
  function cleanVisible(value, fallback = "unknown") {
63
231
  return value?.trim() || fallback;
64
232
  }
@@ -94,22 +262,20 @@ function statusRows(server) {
94
262
  }
95
263
  function renderServerScene(state, layout) {
96
264
  const server = serverState(state);
97
- const selected = selectedIndex(state, 6);
265
+ const selected = selectedIndex(state, 10);
266
+ const loading = state.status === "loading" && !server;
267
+ const errorText = state.error?.message ?? lastRefreshError(state);
98
268
  const panelLines = [
269
+ ...errorText ? errorBanner(errorText, "select refresh or doctor below to retry") : [],
99
270
  line("server controls", { fg: RIG_UI.ink3 }),
100
271
  blank(),
101
272
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
102
- deckRow({ label: "refresh", detail: "probe selected server and GitHub auth", index: 0, active: selected === 0, activateOnClick: true }),
103
- deckRow({ label: "local", detail: "select local server for this repo", index: 1, active: selected === 1, activateOnClick: true }),
104
- deckRow({ label: "remote", detail: "choose from saved remote servers", index: 2, active: selected === 2, activateOnClick: true }),
105
- deckRow({ label: "list", detail: "show saved server aliases", index: 3, active: selected === 3, activateOnClick: true }),
106
- deckRow({ label: "auth", detail: "import GitHub auth to selected server", index: 4, active: selected === 4, activateOnClick: true }),
107
- deckRow({ label: "doctor", detail: "diagnose server/auth/project linkage", index: 5, active: selected === 5, activateOnClick: true }),
273
+ ...SERVER_ACTIONS.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item)),
108
274
  blank(),
109
275
  line("STATUS", { fg: RIG_UI.ink3, bold: true }),
110
- ...statusRows(server),
276
+ ...loading ? loadingRows("server status", state.tick) : statusRows(server),
111
277
  blank(),
112
- line("enter/click runs action \xB7 esc returns home", { fg: RIG_UI.ink4 })
278
+ line("enter/click runs action \xB7 command rows stay inside OpenTUI PTY", { fg: RIG_UI.ink4 })
113
279
  ];
114
280
  return makeSceneFrame({
115
281
  scene: "server",