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

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 (100) hide show
  1. package/dist/bin/build-rig-binaries.js +40 -8
  2. package/dist/bin/rig.js +2798 -1440
  3. package/dist/src/app/board.js +62 -13
  4. package/dist/src/app-opentui/adapters/command.d.ts +2 -0
  5. package/dist/src/app-opentui/adapters/command.js +329 -0
  6. package/dist/src/app-opentui/adapters/common.js +2 -2
  7. package/dist/src/app-opentui/adapters/doctor.js +63 -14
  8. package/dist/src/app-opentui/adapters/fleet.js +84 -20
  9. package/dist/src/app-opentui/adapters/inbox.js +83 -19
  10. package/dist/src/app-opentui/adapters/init.js +87 -23
  11. package/dist/src/app-opentui/adapters/pi-attach.js +96 -34
  12. package/dist/src/app-opentui/adapters/run-detail.js +83 -19
  13. package/dist/src/app-opentui/adapters/server.js +100 -23
  14. package/dist/src/app-opentui/adapters/tasks.d.ts +11 -0
  15. package/dist/src/app-opentui/adapters/tasks.js +742 -94
  16. package/dist/src/app-opentui/bootstrap.d.ts +1 -6
  17. package/dist/src/app-opentui/bootstrap.js +13769 -12368
  18. package/dist/src/app-opentui/command-pty-host.d.ts +62 -0
  19. package/dist/src/app-opentui/command-pty-host.js +248 -0
  20. package/dist/src/app-opentui/events.js +1 -1
  21. package/dist/src/app-opentui/focus-manager.d.ts +14 -0
  22. package/dist/src/app-opentui/focus-manager.js +24 -0
  23. package/dist/src/app-opentui/index.js +3802 -2882
  24. package/dist/src/app-opentui/intent.js +154 -48
  25. package/dist/src/app-opentui/keymap.d.ts +20 -0
  26. package/dist/src/app-opentui/keymap.js +707 -0
  27. package/dist/src/app-opentui/launch-routing.d.ts +16 -0
  28. package/dist/src/app-opentui/launch-routing.js +55 -0
  29. package/dist/src/app-opentui/layout.js +2 -2
  30. package/dist/src/app-opentui/pi-host-child.js +66 -16
  31. package/dist/src/app-opentui/pi-pty-host.d.ts +9 -0
  32. package/dist/src/app-opentui/pi-pty-host.js +9 -11
  33. package/dist/src/app-opentui/registry.js +3231 -2403
  34. package/dist/src/app-opentui/render/ascii-fleet.d.ts +15 -0
  35. package/dist/src/app-opentui/render/ascii-fleet.js +82 -0
  36. package/dist/src/app-opentui/render/graphics.d.ts +1 -1
  37. package/dist/src/app-opentui/render/graphics.js +131 -16
  38. package/dist/src/app-opentui/render/image-visual-layer-worker.js +408 -957
  39. package/dist/src/app-opentui/render/image-visual-layer.d.ts +18 -10
  40. package/dist/src/app-opentui/render/image-visual-layer.js +386 -356
  41. package/dist/src/app-opentui/render/panel-layout.d.ts +38 -0
  42. package/dist/src/app-opentui/render/panel-layout.js +48 -0
  43. package/dist/src/app-opentui/render/panels.d.ts +2 -0
  44. package/dist/src/app-opentui/render/panels.js +62 -29
  45. package/dist/src/app-opentui/render/preloader.d.ts +10 -0
  46. package/dist/src/app-opentui/render/preloader.js +163 -0
  47. package/dist/src/app-opentui/render/scene.d.ts +3 -0
  48. package/dist/src/app-opentui/render/scene.js +12 -0
  49. package/dist/src/app-opentui/render/text.js +5 -1
  50. package/dist/src/app-opentui/render/type-bar.d.ts +2 -1
  51. package/dist/src/app-opentui/render/type-bar.js +51 -16
  52. package/dist/src/app-opentui/runtime-resources.d.ts +16 -0
  53. package/dist/src/app-opentui/runtime-resources.js +62 -0
  54. package/dist/src/app-opentui/runtime.js +2322 -1513
  55. package/dist/src/app-opentui/scenes/command.d.ts +3 -0
  56. package/dist/src/app-opentui/scenes/command.js +103 -0
  57. package/dist/src/app-opentui/scenes/doctor.d.ts +2 -1
  58. package/dist/src/app-opentui/scenes/doctor.js +53 -14
  59. package/dist/src/app-opentui/scenes/error.js +44 -14
  60. package/dist/src/app-opentui/scenes/fleet.js +40 -21
  61. package/dist/src/app-opentui/scenes/handoff.js +142 -27
  62. package/dist/src/app-opentui/scenes/help.js +63 -48
  63. package/dist/src/app-opentui/scenes/inbox.d.ts +2 -1
  64. package/dist/src/app-opentui/scenes/inbox.js +64 -17
  65. package/dist/src/app-opentui/scenes/init.d.ts +2 -1
  66. package/dist/src/app-opentui/scenes/init.js +62 -21
  67. package/dist/src/app-opentui/scenes/main.d.ts +2 -1
  68. package/dist/src/app-opentui/scenes/main.js +80 -24
  69. package/dist/src/app-opentui/scenes/run-detail.d.ts +2 -1
  70. package/dist/src/app-opentui/scenes/run-detail.js +39 -25
  71. package/dist/src/app-opentui/scenes/server.d.ts +2 -1
  72. package/dist/src/app-opentui/scenes/server.js +71 -20
  73. package/dist/src/app-opentui/scenes/tasks.js +44 -22
  74. package/dist/src/app-opentui/state.js +70 -30
  75. package/dist/src/app-opentui/terminal-capabilities.d.ts +7 -0
  76. package/dist/src/app-opentui/terminal-capabilities.js +15 -0
  77. package/dist/src/app-opentui/types.d.ts +10 -2
  78. package/dist/src/commands/_doctor-checks.js +62 -13
  79. package/dist/src/commands/_operator-view.js +63 -13
  80. package/dist/src/commands/_pi-frontend.js +63 -13
  81. package/dist/src/commands/_preflight.js +64 -14
  82. package/dist/src/commands/_server-client.js +82 -18
  83. package/dist/src/commands/_snapshot-upload.js +62 -13
  84. package/dist/src/commands/connect.js +7 -1
  85. package/dist/src/commands/doctor.js +62 -13
  86. package/dist/src/commands/github.js +144 -23
  87. package/dist/src/commands/inbox.js +62 -13
  88. package/dist/src/commands/init.js +82 -18
  89. package/dist/src/commands/inspect.js +62 -13
  90. package/dist/src/commands/run.js +66 -13
  91. package/dist/src/commands/server.js +69 -14
  92. package/dist/src/commands/setup.js +62 -13
  93. package/dist/src/commands/stats.js +62 -13
  94. package/dist/src/commands/task-run-driver.js +62 -13
  95. package/dist/src/commands/task.js +65 -14
  96. package/dist/src/commands.js +227 -92
  97. package/dist/src/index.js +234 -99
  98. package/package.json +8 -9
  99. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.d.ts +0 -2
  100. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.js +0 -1484
@@ -0,0 +1,3 @@
1
+ import type { AppSceneFrame, AppState } from "../types";
2
+ import type { StageLayout } from "../layout";
3
+ export declare function renderCommandScene(state: AppState, layout?: StageLayout): AppSceneFrame;
@@ -0,0 +1,103 @@
1
+ // @bun
2
+ // packages/cli/src/app-opentui/theme.ts
3
+ import {
4
+ bold as otuiBold,
5
+ dim as otuiDim,
6
+ fg as otuiFg,
7
+ t,
8
+ TextAttributes
9
+ } from "@opentui/core";
10
+ var RIG_UI = {
11
+ bg: "#070809",
12
+ bg2: "#0b0c0e",
13
+ panel: "#101115",
14
+ panel2: "#101115",
15
+ glass: "#14161b",
16
+ ink: "#f2f3f6",
17
+ ink2: "#aeb0ba",
18
+ ink3: "#6c6e79",
19
+ ink4: "#44464f",
20
+ lime: "#ccff4d",
21
+ limeDim: "#a9d63f",
22
+ cyan: "#56d8ff",
23
+ red: "#ff5d5d",
24
+ yellow: "#ffd24d",
25
+ magenta: "#ff79b0"
26
+ };
27
+ var styles = {
28
+ ink: otuiFg(RIG_UI.ink),
29
+ ink2: otuiFg(RIG_UI.ink2),
30
+ ink3: otuiFg(RIG_UI.ink3),
31
+ ink4: otuiFg(RIG_UI.ink4),
32
+ lime: otuiFg(RIG_UI.lime),
33
+ limeDim: otuiFg(RIG_UI.limeDim),
34
+ cyan: otuiFg(RIG_UI.cyan),
35
+ red: otuiFg(RIG_UI.red),
36
+ yellow: otuiFg(RIG_UI.yellow),
37
+ magenta: otuiFg(RIG_UI.magenta)
38
+ };
39
+
40
+ // packages/cli/src/app-opentui/render/scene.ts
41
+ function line(text, options = {}) {
42
+ return { text, ...options };
43
+ }
44
+ function makeSceneFrame(input) {
45
+ return input;
46
+ }
47
+
48
+ // packages/cli/src/app-opentui/scenes/command.ts
49
+ function terminalState(state) {
50
+ const value = state.data.commandTerminal;
51
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
52
+ }
53
+ function panelWidth(layout) {
54
+ return layout?.centerWidth ?? 118;
55
+ }
56
+ function panelHeight(layout, top = 0) {
57
+ return Math.max(14, (layout?.centerHeight ?? 36) - top);
58
+ }
59
+ function header(snapshot, state) {
60
+ const label = snapshot?.label ?? (state.argv.length > 0 ? `rig ${state.argv.join(" ")}` : "rig");
61
+ const suffix = snapshot?.message ? ` \xB7 ${snapshot.message}` : "";
62
+ return `${label}${suffix}`;
63
+ }
64
+ function commandLines(snapshot) {
65
+ if (!snapshot) {
66
+ return [line("starting command\u2026", { fg: RIG_UI.ink3 })];
67
+ }
68
+ const rows = snapshot.lines.length > 0 ? snapshot.lines : ["starting command\u2026"];
69
+ return rows.map((text) => line(text, { fg: text.trim() ? RIG_UI.ink2 : RIG_UI.ink4 }));
70
+ }
71
+ function renderCommandScene(state, layout) {
72
+ const terminal = terminalState(state);
73
+ const width = panelWidth(layout);
74
+ const panelTop = 0;
75
+ return makeSceneFrame({
76
+ scene: "command",
77
+ title: "Command",
78
+ lines: [],
79
+ panels: [{
80
+ id: "command-terminal",
81
+ top: panelTop,
82
+ width,
83
+ height: panelHeight(layout, panelTop),
84
+ lines: commandLines(terminal),
85
+ backgroundColor: RIG_UI.panel,
86
+ backgroundAlpha: 184,
87
+ opacity: 0.98,
88
+ border: false,
89
+ chrome: "ad-terminal",
90
+ headerText: header(terminal, state),
91
+ stickyScroll: true,
92
+ stickyStart: "bottom",
93
+ paddingX: 5,
94
+ paddingY: 2
95
+ }],
96
+ footer: { message: terminal?.message ?? "command" },
97
+ live: terminal?.status === "starting" || terminal?.status === "running" || state.status === "action",
98
+ hideTypeBar: true
99
+ });
100
+ }
101
+ export {
102
+ renderCommandScene
103
+ };
@@ -1,2 +1,3 @@
1
+ import type { StageLayout } from "../layout";
1
2
  import type { AppSceneFrame, AppState } from "../types";
2
- export declare function renderDoctorScene(state: AppState): AppSceneFrame;
3
+ export declare function renderDoctorScene(state: AppState, layout?: StageLayout): AppSceneFrame;
@@ -41,11 +41,15 @@ var styles = {
41
41
  function line(text, options = {}) {
42
42
  return { text, ...options };
43
43
  }
44
- function blank() {
45
- return { text: "" };
46
- }
47
- function center(text, fg = RIG_UI.ink2, bold = false) {
48
- return { text, fg, bold, align: "center" };
44
+ function deckRow(input) {
45
+ const prefix = input.active ? "\u25B8" : " ";
46
+ const label = input.label.toUpperCase().padEnd(10);
47
+ return line(`${prefix} ${label} ${input.detail}`, {
48
+ fg: input.active ? RIG_UI.lime : RIG_UI.ink2,
49
+ bold: input.active,
50
+ selectableIndex: input.index,
51
+ activateOnClick: input.activateOnClick
52
+ });
49
53
  }
50
54
  function makeSceneFrame(input) {
51
55
  return input;
@@ -65,24 +69,59 @@ function glyph(status) {
65
69
  function doctorColor(status) {
66
70
  return status === "pass" ? RIG_UI.limeDim : status === "warn" ? RIG_UI.yellow : RIG_UI.red;
67
71
  }
68
- function renderDoctorScene(state) {
72
+ function panelWidth(layout) {
73
+ return layout?.centerWidth ?? 100;
74
+ }
75
+ function panelHeight(layout) {
76
+ return Math.max(10, (layout?.centerHeight ?? 24) - 1);
77
+ }
78
+ function actionRows(selected) {
79
+ return [
80
+ line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
81
+ deckRow({ label: "run", detail: "run diagnostics now", index: 0, active: selected === 0, activateOnClick: true }),
82
+ deckRow({ label: "server", detail: "open server controls", index: 1, active: selected === 1, activateOnClick: true }),
83
+ deckRow({ label: "repair", detail: "repair project/server/auth link", index: 2, active: selected === 2, activateOnClick: true }),
84
+ deckRow({ label: "tasks", detail: "open tasks", index: 3, active: selected === 3, activateOnClick: true })
85
+ ];
86
+ }
87
+ function renderDoctorScene(state, layout) {
69
88
  const records = checks(state);
89
+ const selected = Math.max(0, Math.min(3, state.selection.index));
70
90
  const failures = records.filter((check) => check.status === "fail").length;
71
91
  const warnings = records.filter((check) => check.status === "warn").length;
72
92
  const firstRemediation = records.find((check) => check.status !== "pass" && check.remediation)?.remediation;
93
+ const checkLines = records.length > 0 ? records.slice(0, 12).map((check) => line(`${glyph(check.status)} ${check.label}${check.detail ? ` \u2014 ${check.detail}` : ""}`, { fg: doctorColor(check.status) })) : [line("Diagnostics have not run in this session.", { fg: RIG_UI.ink3 })];
73
94
  return makeSceneFrame({
74
95
  scene: "doctor",
75
96
  title: "Doctor",
76
97
  lines: [
77
- center("doctor", RIG_UI.ink, true),
78
- center(records.length > 0 ? `${failures} fail \xB7 ${warnings} warn` : "diagnostics ready", RIG_UI.ink3),
79
- blank(),
80
- ...records.length > 0 ? records.slice(0, 14).map((check) => line(`${glyph(check.status)} ${check.label}${check.detail ? ` \u2014 ${check.detail}` : ""}`, { fg: doctorColor(check.status), align: "center" })) : [line("type doctor to run checks", { fg: RIG_UI.ink3, align: "center" })],
81
- blank(),
82
- line(firstRemediation ? `next: ${firstRemediation}` : records.length > 0 ? "ready" : "idle", { fg: firstRemediation ? RIG_UI.yellow : RIG_UI.ink4, align: "center" })
98
+ line("", { fg: RIG_UI.ink4 }),
99
+ line(` doctor \xB7 ${records.length > 0 ? `${failures} fail \xB7 ${warnings} warn` : "ready"}`, { fg: RIG_UI.ink3 })
83
100
  ],
84
- footer: { message: records.length > 0 ? `${records.length} checks` : "doctor idle" },
85
- typeBarPlaceholder: "doctor \xB7 server \xB7 tasks \xB7 init",
101
+ panels: [{
102
+ id: "doctor-actions",
103
+ top: 0,
104
+ width: panelWidth(layout),
105
+ height: panelHeight(layout),
106
+ lines: [
107
+ ...actionRows(selected),
108
+ line("", { fg: RIG_UI.ink3 }),
109
+ line("CHECKS", { fg: RIG_UI.ink3, bold: true }),
110
+ ...checkLines,
111
+ ...firstRemediation ? [line("", { fg: RIG_UI.ink3 }), line(`next: ${firstRemediation}`, { fg: RIG_UI.yellow })] : []
112
+ ],
113
+ backgroundColor: RIG_UI.panel,
114
+ backgroundAlpha: 184,
115
+ opacity: 1,
116
+ border: false,
117
+ chrome: "ad-terminal",
118
+ headerText: `doctor \xB7 ${records.length} checks`,
119
+ headerHeight: 3,
120
+ paddingX: layout?.compact ? 2 : 3,
121
+ paddingY: 1
122
+ }],
123
+ footer: { message: records.length > 0 ? `${records.length} checks` : "enter/click run to diagnose" },
124
+ hideTypeBar: true,
86
125
  live: state.status === "action" || records.length === 0
87
126
  });
88
127
  }
@@ -156,11 +156,18 @@ function errorDrone(tick) {
156
156
  }
157
157
 
158
158
  // packages/cli/src/app-opentui/render/scene.ts
159
- function blank() {
160
- return { text: "" };
159
+ function line(text, options = {}) {
160
+ return { text, ...options };
161
161
  }
162
- function center(text, fg = RIG_UI.ink2, bold = false) {
163
- return { text, fg, bold, align: "center" };
162
+ function deckRow(input) {
163
+ const prefix = input.active ? "\u25B8" : " ";
164
+ const label = input.label.toUpperCase().padEnd(10);
165
+ return line(`${prefix} ${label} ${input.detail}`, {
166
+ fg: input.active ? RIG_UI.lime : RIG_UI.ink2,
167
+ bold: input.active,
168
+ selectableIndex: input.index,
169
+ activateOnClick: input.activateOnClick
170
+ });
164
171
  }
165
172
  function makeSceneFrame(input) {
166
173
  return input;
@@ -168,20 +175,43 @@ function makeSceneFrame(input) {
168
175
 
169
176
  // packages/cli/src/app-opentui/scenes/error.ts
170
177
  function renderErrorScene(state) {
171
- const message = state.error?.message ?? "Unknown Rig app error";
172
- const hint = state.error?.hint ?? "type main, help, or retry after fixing the issue";
178
+ const message = state.error?.message ?? "Unknown app error";
179
+ const hint = state.error?.hint ?? "Use the actions below to recover.";
180
+ const selected = Math.max(0, Math.min(3, state.selection.index));
173
181
  return makeSceneFrame({
174
182
  scene: "error",
175
- title: "Rig error",
183
+ title: "Error",
176
184
  lines: [
177
- ...errorDrone(state.tick).slice(2, 16),
178
- blank(),
179
- center("DRONE SIGNAL DEGRADED", RIG_UI.red, true),
180
- center(message, RIG_UI.ink2),
181
- blank(),
182
- center(hint, RIG_UI.limeDim)
185
+ ...errorDrone(state.tick).slice(2, 10)
183
186
  ],
184
- typeBarPlaceholder: hint,
187
+ panels: [{
188
+ id: "error-recovery",
189
+ top: 2,
190
+ width: 96,
191
+ height: 18,
192
+ lines: [
193
+ line("DRONE SIGNAL DEGRADED", { fg: RIG_UI.red, bold: true }),
194
+ line(message, { fg: RIG_UI.ink2 }),
195
+ ...hint ? [line(hint, { fg: RIG_UI.yellow })] : [],
196
+ line("", { fg: RIG_UI.ink3 }),
197
+ line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
198
+ deckRow({ label: "main", detail: "return to dashboard", index: 0, active: selected === 0, activateOnClick: true }),
199
+ deckRow({ label: "doctor", detail: "run diagnostics", index: 1, active: selected === 1, activateOnClick: true }),
200
+ deckRow({ label: "server", detail: "open server controls", index: 2, active: selected === 2, activateOnClick: true }),
201
+ deckRow({ label: "repair", detail: "repair project/server/auth link", index: 3, active: selected === 3, activateOnClick: true })
202
+ ],
203
+ backgroundColor: RIG_UI.panel,
204
+ backgroundAlpha: 184,
205
+ opacity: 1,
206
+ border: false,
207
+ chrome: "ad-terminal",
208
+ headerText: "recovery",
209
+ headerHeight: 3,
210
+ paddingX: 3,
211
+ paddingY: 1
212
+ }],
213
+ footer: { message: "enter/click a recovery action" },
214
+ hideTypeBar: true,
185
215
  live: true
186
216
  });
187
217
  }
@@ -67,6 +67,16 @@ function statusColor(status) {
67
67
  function line(text, options = {}) {
68
68
  return { text, ...options };
69
69
  }
70
+ function deckRow(input) {
71
+ const prefix = input.active ? "\u25B8" : " ";
72
+ const label = input.label.toUpperCase().padEnd(10);
73
+ return line(`${prefix} ${label} ${input.detail}`, {
74
+ fg: input.active ? RIG_UI.lime : RIG_UI.ink2,
75
+ bold: input.active,
76
+ selectableIndex: input.index,
77
+ activateOnClick: input.activateOnClick
78
+ });
79
+ }
70
80
  function makeSceneFrame(input) {
71
81
  return input;
72
82
  }
@@ -132,31 +142,34 @@ function runRow(width, run, index, active) {
132
142
  return line(`${prefix}${clip(run.title, titleWidth)}`, {
133
143
  fg: active ? RIG_UI.ink : statusColor(run.status),
134
144
  bold: active,
135
- selectableIndex: index
145
+ selectableIndex: index,
146
+ activateOnClick: true
136
147
  });
137
148
  }
138
- function emptyRows(query, total) {
149
+ function recoveryRows(selected, reason) {
150
+ return [
151
+ ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
152
+ line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
153
+ deckRow({ label: "tasks", detail: "open tasks and dispatch work", index: 0, active: selected === 0, activateOnClick: true }),
154
+ deckRow({ label: "repair", detail: "repair project/server/auth link", index: 1, active: selected === 1, activateOnClick: true }),
155
+ deckRow({ label: "server", detail: "open server controls", index: 2, active: selected === 2, activateOnClick: true }),
156
+ deckRow({ label: "doctor", detail: "diagnose project", index: 3, active: selected === 3, activateOnClick: true })
157
+ ];
158
+ }
159
+ function emptyRows(query, total, selected) {
160
+ if (!query.trim() && total === 0)
161
+ return recoveryRows(selected, "No runs yet.");
139
162
  if (query.trim() && total > 0) {
140
163
  return [
141
- line("No matching runs.", { fg: RIG_UI.ink2, bold: true }),
142
- line(`search ${JSON.stringify(query)} returned 0/${total}`, { fg: RIG_UI.ink3 }),
143
- line("escape clears search", { fg: RIG_UI.ink4 })
164
+ ...recoveryRows(selected, `No matching runs for ${JSON.stringify(query)} (${total} total).`)
144
165
  ];
145
166
  }
146
- return [
147
- line("No runs yet.", { fg: RIG_UI.ink2, bold: true }),
148
- line("Open Tasks and dispatch work; live and completed runs appear here.", { fg: RIG_UI.ink3 }),
149
- line("tab switches screens", { fg: RIG_UI.ink4 })
150
- ];
167
+ return recoveryRows(selected, "No runs are visible with this filter.");
151
168
  }
152
- function degradedRows(state) {
169
+ function degradedRows(state, selected) {
153
170
  if (!state.error)
154
171
  return [];
155
- return [
156
- line(`status: ${state.error.message}`, { fg: RIG_UI.red, bold: true }),
157
- ...state.error.hint ? [line(state.error.hint, { fg: RIG_UI.yellow })] : [],
158
- line("", { fg: RIG_UI.ink3 })
159
- ];
172
+ return recoveryRows(selected, state.error.message);
160
173
  }
161
174
  function renderFleetScene(state, layout) {
162
175
  const width = panelWidth(layout);
@@ -165,21 +178,27 @@ function renderFleetScene(state, layout) {
165
178
  const runs = filterRunsForSearch(allRuns, query);
166
179
  const selectedRun = typeof state.data.selectedRunId === "string" && runs.some((run) => run.runId === state.data.selectedRunId) ? state.data.selectedRunId : runs[0]?.runId;
167
180
  const selectedIndex = Math.max(0, runs.findIndex((run) => run.runId === selectedRun));
181
+ const recoveryIndex = Math.max(0, Math.min(3, state.selection.index));
168
182
  const panelTop = 0;
169
183
  const contentWidth = Math.max(12, width - (layout?.compact ? 8 : 10));
170
184
  const panelLines = [
171
185
  line(`RUN STATUS TITLE`, { fg: RIG_UI.ink3, bold: true }),
172
186
  line("", { fg: RIG_UI.ink3 }),
173
- ...degradedRows(state),
174
- ...query.trim() ? [line(searchSummary("runs", query, runs.length, allRuns.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
175
- ...runs.length > 0 ? runs.map((run, index) => runRow(contentWidth, run, index, index === selectedIndex)) : emptyRows(query, allRuns.length)
187
+ ...runs.length > 0 ? [
188
+ ...degradedRows(state, recoveryIndex),
189
+ ...query.trim() ? [line(searchSummary("runs", query, runs.length, allRuns.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
190
+ ...runs.map((run, index) => runRow(contentWidth, run, index, index === selectedIndex))
191
+ ] : state.error ? degradedRows(state, recoveryIndex) : [
192
+ ...query.trim() ? [line(searchSummary("runs", query, runs.length, allRuns.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
193
+ ...emptyRows(query, allRuns.length, recoveryIndex)
194
+ ]
176
195
  ];
177
196
  return makeSceneFrame({
178
197
  scene: "fleet",
179
198
  title: "Runs",
180
199
  lines: [
181
200
  line("", { fg: RIG_UI.ink4 }),
182
- line(query.trim() ? ` rig runs --search ${JSON.stringify(query)}` : " rig runs", { fg: RIG_UI.ink3 })
201
+ line(query.trim() ? ` runs --search ${JSON.stringify(query)}` : " runs", { fg: RIG_UI.ink3 })
183
202
  ],
184
203
  panels: [{
185
204
  id: "runs-list",
@@ -192,7 +211,7 @@ function renderFleetScene(state, layout) {
192
211
  opacity: 1,
193
212
  border: false,
194
213
  chrome: "ad-terminal",
195
- headerText: query.trim() ? `rig runs --search ${JSON.stringify(query)}` : "rig runs",
214
+ headerText: query.trim() ? `runs --search ${JSON.stringify(query)}` : "runs",
196
215
  headerHeight: 3,
197
216
  paddingX: layout?.compact ? 2 : 3,
198
217
  paddingY: 1
@@ -41,12 +41,6 @@ var styles = {
41
41
  function line(text, options = {}) {
42
42
  return { text, ...options };
43
43
  }
44
- function blank() {
45
- return { text: "" };
46
- }
47
- function center(text, fg = RIG_UI.ink2, bold = false) {
48
- return { text, fg, bold, align: "center" };
49
- }
50
44
  function deckRow(input) {
51
45
  const prefix = input.active ? "\u25B8" : " ";
52
46
  const label = input.label.toUpperCase().padEnd(10);
@@ -61,6 +55,83 @@ function makeSceneFrame(input) {
61
55
  return input;
62
56
  }
63
57
 
58
+ // packages/cli/src/app-opentui/render/ascii-fleet.ts
59
+ var LEAD_DRONE = [
60
+ " .-=-. .-=-. ",
61
+ " ( !!! ) ( !!! ) ",
62
+ " '-=-'._ _.'-=-' ",
63
+ " '._ _.' ",
64
+ " '=$$$$$$$=.' ",
65
+ " =$$$$$$$$$$$= ",
66
+ " $$$@@@@@@@@@@$$$ ",
67
+ " $$$@@ @@$$$ ",
68
+ " $$@ ? @$$$ ",
69
+ " $$$@ '-' @$$$ ",
70
+ " $$$@@ @@$$$ ",
71
+ " $$$@@@@@@@@@@$$$ ",
72
+ " =$$$$$$$$$$$= ",
73
+ " '=$$$$$$$=.' ",
74
+ " _.' '._ ",
75
+ " .-=-.' '.-=-. ",
76
+ " ( !!! ) ( !!! ) ",
77
+ " '-=-' '-=-' "
78
+ ];
79
+ var MINI_DRONE = [
80
+ "(!!!) (!!!)",
81
+ " \\%==%/ ",
82
+ " %%?%% ",
83
+ " /%==%\\ ",
84
+ "(!!!) (!!!)"
85
+ ];
86
+ var ROTORS = ["---", "\\\\\\", "|||", "///"];
87
+ var FLEET_GRID_WIDTH = 52;
88
+ var FLEET_GRID_HEIGHT = 27;
89
+ var FLEET = [
90
+ { art: MINI_DRONE, x: 0, y: 2, amp: 1, speed: 0.05, phase: 0, dx: 1, driftSpeed: 0.02 },
91
+ { art: MINI_DRONE, x: 39, y: 2, amp: 1, speed: 0.044, phase: 2.1, dx: 1, driftSpeed: 0.017 },
92
+ { art: MINI_DRONE, x: 1, y: 20, amp: 1, speed: 0.048, phase: 4.2, dx: 1, driftSpeed: 0.023 },
93
+ { art: MINI_DRONE, x: 38, y: 20, amp: 1, speed: 0.041, phase: 1.3, dx: 1, driftSpeed: 0.015 },
94
+ { art: LEAD_DRONE, x: 10, y: 5, amp: 2, speed: 0.04, phase: 0, dx: 0, driftSpeed: 0.011 }
95
+ ];
96
+ function fleetRows(tick, options = {}) {
97
+ const animate = options.animate ?? true;
98
+ const t2 = animate ? tick : 0;
99
+ const blade = ROTORS[Math.floor(t2 / 4) % ROTORS.length];
100
+ const pulse = Math.sin(t2 * 0.07);
101
+ const eye = pulse > 0.45 ? "@" : pulse > -0.1 ? "o" : ".";
102
+ const grid = Array.from({ length: FLEET_GRID_HEIGHT }, () => Array.from({ length: FLEET_GRID_WIDTH }, () => " "));
103
+ for (const drone of FLEET) {
104
+ const yOffset = drone.y + Math.round(drone.amp * Math.sin(t2 * drone.speed + drone.phase));
105
+ const xOffset = drone.x + Math.round(drone.dx * Math.sin(t2 * drone.driftSpeed + drone.phase));
106
+ for (let row = 0;row < drone.art.length; row += 1) {
107
+ const source = drone.art[row];
108
+ const targetY = yOffset + row;
109
+ if (targetY < 0 || targetY >= FLEET_GRID_HEIGHT)
110
+ continue;
111
+ for (let col = 0;col < source.length; col += 1) {
112
+ let char = source[col];
113
+ if (char === " ")
114
+ continue;
115
+ const targetX = xOffset + col;
116
+ if (targetX < 0 || targetX >= FLEET_GRID_WIDTH)
117
+ continue;
118
+ if (source.slice(col, col + 3) === "!!!") {
119
+ for (let rotorIndex = 0;rotorIndex < 3; rotorIndex += 1) {
120
+ if (targetX + rotorIndex < FLEET_GRID_WIDTH)
121
+ grid[targetY][targetX + rotorIndex] = blade[rotorIndex];
122
+ }
123
+ col += 2;
124
+ continue;
125
+ }
126
+ if (char === "?")
127
+ char = eye;
128
+ grid[targetY][targetX] = char;
129
+ }
130
+ }
131
+ }
132
+ return grid.map((row) => row.join("").replace(/\s+$/, ""));
133
+ }
134
+
64
135
  // packages/cli/src/app-opentui/scenes/handoff.ts
65
136
  function handoffState(state) {
66
137
  const value = state.data.piAttach ?? state.data.handoff;
@@ -79,9 +150,32 @@ function terminalRail(snapshot) {
79
150
  const text = ` run ${snapshot.runId.slice(0, 8)} \xB7 bundled Pi \xB7 mouse wheel scroll \xB7 ctrl-] detach \xB7 ${snapshot.message ?? snapshot.status}`;
80
151
  return line(clip(text, snapshot.cols), { fg: RIG_UI.limeDim });
81
152
  }
82
- function terminalLines(snapshot) {
153
+ function shouldShowTerminalPreloader(snapshot) {
154
+ if (snapshot.status !== "starting" && snapshot.status !== "running")
155
+ return false;
156
+ const nonEmptyLines = snapshot.lines.filter((entry) => entry.trim().length > 0).length;
157
+ return nonEmptyLines <= 4;
158
+ }
159
+ function terminalPreloaderLines(snapshot, tick) {
160
+ const bodyRows = Math.max(1, snapshot.rows - 1);
161
+ const existing = snapshot.lines.slice(0, bodyRows).map((text, index) => line(text, { fg: RIG_UI.ink, styledText: snapshot.styledLines?.[index] }));
162
+ const sprite = fleetRows(tick);
163
+ const top = Math.max(0, Math.floor((bodyRows - sprite.length) / 2));
164
+ const left = Math.max(0, Math.floor((snapshot.cols - FLEET_GRID_WIDTH) / 2));
165
+ for (let index = 0;index < sprite.length && top + index < existing.length; index += 1) {
166
+ const targetIndex = top + index;
167
+ const current = snapshot.lines[targetIndex] ?? "";
168
+ if (current.trim())
169
+ continue;
170
+ existing[targetIndex] = line(`${" ".repeat(left)}${sprite[index] ?? ""}`, {
171
+ fg: index >= 7 && index <= 18 ? RIG_UI.lime : RIG_UI.limeDim
172
+ });
173
+ }
174
+ return existing;
175
+ }
176
+ function terminalLines(snapshot, tick) {
83
177
  const hasVisibleOutput = snapshot.lines.some((entry) => entry.trim().length > 0);
84
- const body = !hasVisibleOutput && snapshot.status === "running" ? [line(`starting bundled Pi for ${snapshot.runId.slice(0, 8)}\u2026`, { fg: RIG_UI.ink3 })] : snapshot.lines.map((text, index) => line(text, { fg: RIG_UI.ink, styledText: snapshot.styledLines?.[index] }));
178
+ const body = shouldShowTerminalPreloader(snapshot) ? terminalPreloaderLines(snapshot, tick) : !hasVisibleOutput && snapshot.status === "running" ? [line(`starting bundled Pi for ${snapshot.runId.slice(0, 8)}\u2026`, { fg: RIG_UI.ink3 })] : snapshot.lines.map((text, index) => line(text, { fg: RIG_UI.ink, styledText: snapshot.styledLines?.[index] }));
85
179
  return [...body, terminalRail(snapshot)];
86
180
  }
87
181
  function renderHandoffScene(state) {
@@ -97,7 +191,7 @@ function renderHandoffScene(state) {
97
191
  left: 0,
98
192
  width: terminal.cols,
99
193
  height: terminal.rows,
100
- lines: terminalLines(terminal),
194
+ lines: terminalLines(terminal, state.tick),
101
195
  backgroundColor: "#050607",
102
196
  backgroundAlpha: 128,
103
197
  opacity: 1,
@@ -115,30 +209,51 @@ function renderHandoffScene(state) {
115
209
  const handoff = handoffState(state);
116
210
  const runId = handoff?.runId ?? terminal?.runId ?? (typeof state.data.selectedRunId === "string" ? state.data.selectedRunId : undefined);
117
211
  const status = handoff?.status ?? (runId ? "preparing" : "select-run");
118
- const message = handoff?.message ?? (runId ? "ready to start bundled Pi inside Rig" : "choose a run before entering Pi");
212
+ const message = handoff?.message ?? (runId ? "ready to start bundled Pi inside this app" : "choose a run before entering Pi");
119
213
  const hasRun = Boolean(runId);
214
+ const selected = Math.max(0, Math.min(1, state.selection.index));
120
215
  return makeSceneFrame({
121
216
  scene: "handoff",
122
217
  title: "Pi handoff",
123
218
  lines: [
124
- center(hasRun ? "PI" : "ATTACH", RIG_UI.ink, true),
125
- center("Bundled Pi runs in this terminal. Rig only hosts the PTY.", RIG_UI.ink3),
126
- blank(),
127
- ...hasRun ? [
128
- line(` run ${runId}`, { fg: RIG_UI.limeDim }),
129
- line(` status ${status}`, { fg: status === "failed" ? RIG_UI.red : RIG_UI.cyan }),
130
- line(` message ${message}`, { fg: RIG_UI.ink2 }),
131
- blank(),
132
- deckRow({ label: "attach", detail: "open bundled Pi", index: 0, active: true })
133
- ] : [
134
- line(" run none selected", { fg: RIG_UI.yellow }),
135
- line(` message ${message}`, { fg: RIG_UI.ink2 }),
136
- blank(),
137
- deckRow({ label: "runs", detail: "select a run first", index: 0, active: true })
138
- ]
219
+ line("", { fg: RIG_UI.ink4 }),
220
+ line(` ${hasRun ? "pi" : "attach"} \xB7 ${status}`, { fg: RIG_UI.ink3 })
139
221
  ],
140
- footer: { run: runId ? `${runId.slice(0, 8)} handoff` : "select run", message: "real Pi only" },
141
- typeBarPlaceholder: runId ? `attach ${runId.slice(0, 8)} \xB7 runs` : "runs \xB7 attach <run>",
222
+ panels: [{
223
+ id: "pi-handoff",
224
+ top: 0,
225
+ width: 96,
226
+ height: 16,
227
+ lines: [
228
+ line(hasRun ? "Bundled Pi handoff" : "Select a run before entering Pi", { fg: RIG_UI.ink, bold: true }),
229
+ ...hasRun ? [
230
+ line(`run ${runId}`, { fg: RIG_UI.limeDim }),
231
+ line(`status ${status}`, { fg: status === "failed" ? RIG_UI.red : RIG_UI.cyan }),
232
+ line(`message ${message}`, { fg: RIG_UI.ink2 }),
233
+ line("", { fg: RIG_UI.ink3 }),
234
+ line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
235
+ deckRow({ label: "attach", detail: "open bundled Pi", index: 0, active: selected === 0, activateOnClick: true }),
236
+ deckRow({ label: "runs", detail: "return to fleet", index: 1, active: selected === 1, activateOnClick: true })
237
+ ] : [
238
+ line(`message ${message}`, { fg: RIG_UI.ink2 }),
239
+ line("", { fg: RIG_UI.ink3 }),
240
+ line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
241
+ deckRow({ label: "runs", detail: "select a run first", index: 0, active: selected === 0, activateOnClick: true }),
242
+ deckRow({ label: "tasks", detail: "open tasks to dispatch work", index: 1, active: selected === 1, activateOnClick: true })
243
+ ]
244
+ ],
245
+ backgroundColor: RIG_UI.panel,
246
+ backgroundAlpha: 184,
247
+ opacity: 1,
248
+ border: false,
249
+ chrome: "ad-terminal",
250
+ headerText: hasRun ? "pi handoff" : "attach",
251
+ headerHeight: 3,
252
+ paddingX: 3,
253
+ paddingY: 1
254
+ }],
255
+ footer: { run: runId ? `${runId.slice(0, 8)} handoff` : "select run", message: "enter/click action" },
256
+ hideTypeBar: true,
142
257
  live: true
143
258
  });
144
259
  }