@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.
- package/dist/bin/build-rig-binaries.js +40 -8
- package/dist/bin/rig.js +2798 -1440
- package/dist/src/app/board.js +62 -13
- package/dist/src/app-opentui/adapters/command.d.ts +2 -0
- package/dist/src/app-opentui/adapters/command.js +329 -0
- package/dist/src/app-opentui/adapters/common.js +2 -2
- package/dist/src/app-opentui/adapters/doctor.js +63 -14
- package/dist/src/app-opentui/adapters/fleet.js +84 -20
- package/dist/src/app-opentui/adapters/inbox.js +83 -19
- package/dist/src/app-opentui/adapters/init.js +87 -23
- package/dist/src/app-opentui/adapters/pi-attach.js +96 -34
- package/dist/src/app-opentui/adapters/run-detail.js +83 -19
- package/dist/src/app-opentui/adapters/server.js +100 -23
- package/dist/src/app-opentui/adapters/tasks.d.ts +11 -0
- package/dist/src/app-opentui/adapters/tasks.js +742 -94
- package/dist/src/app-opentui/bootstrap.d.ts +1 -6
- package/dist/src/app-opentui/bootstrap.js +13769 -12368
- package/dist/src/app-opentui/command-pty-host.d.ts +62 -0
- package/dist/src/app-opentui/command-pty-host.js +248 -0
- package/dist/src/app-opentui/events.js +1 -1
- package/dist/src/app-opentui/focus-manager.d.ts +14 -0
- package/dist/src/app-opentui/focus-manager.js +24 -0
- package/dist/src/app-opentui/index.js +3802 -2882
- package/dist/src/app-opentui/intent.js +154 -48
- package/dist/src/app-opentui/keymap.d.ts +20 -0
- package/dist/src/app-opentui/keymap.js +707 -0
- package/dist/src/app-opentui/launch-routing.d.ts +16 -0
- package/dist/src/app-opentui/launch-routing.js +55 -0
- package/dist/src/app-opentui/layout.js +2 -2
- package/dist/src/app-opentui/pi-host-child.js +66 -16
- package/dist/src/app-opentui/pi-pty-host.d.ts +9 -0
- package/dist/src/app-opentui/pi-pty-host.js +9 -11
- package/dist/src/app-opentui/registry.js +3231 -2403
- package/dist/src/app-opentui/render/ascii-fleet.d.ts +15 -0
- package/dist/src/app-opentui/render/ascii-fleet.js +82 -0
- package/dist/src/app-opentui/render/graphics.d.ts +1 -1
- package/dist/src/app-opentui/render/graphics.js +131 -16
- package/dist/src/app-opentui/render/image-visual-layer-worker.js +408 -957
- package/dist/src/app-opentui/render/image-visual-layer.d.ts +18 -10
- package/dist/src/app-opentui/render/image-visual-layer.js +386 -356
- package/dist/src/app-opentui/render/panel-layout.d.ts +38 -0
- package/dist/src/app-opentui/render/panel-layout.js +48 -0
- package/dist/src/app-opentui/render/panels.d.ts +2 -0
- package/dist/src/app-opentui/render/panels.js +62 -29
- package/dist/src/app-opentui/render/preloader.d.ts +10 -0
- package/dist/src/app-opentui/render/preloader.js +163 -0
- package/dist/src/app-opentui/render/scene.d.ts +3 -0
- package/dist/src/app-opentui/render/scene.js +12 -0
- package/dist/src/app-opentui/render/text.js +5 -1
- package/dist/src/app-opentui/render/type-bar.d.ts +2 -1
- package/dist/src/app-opentui/render/type-bar.js +51 -16
- package/dist/src/app-opentui/runtime-resources.d.ts +16 -0
- package/dist/src/app-opentui/runtime-resources.js +62 -0
- package/dist/src/app-opentui/runtime.js +2322 -1513
- package/dist/src/app-opentui/scenes/command.d.ts +3 -0
- package/dist/src/app-opentui/scenes/command.js +103 -0
- package/dist/src/app-opentui/scenes/doctor.d.ts +2 -1
- package/dist/src/app-opentui/scenes/doctor.js +53 -14
- package/dist/src/app-opentui/scenes/error.js +44 -14
- package/dist/src/app-opentui/scenes/fleet.js +40 -21
- package/dist/src/app-opentui/scenes/handoff.js +142 -27
- package/dist/src/app-opentui/scenes/help.js +63 -48
- package/dist/src/app-opentui/scenes/inbox.d.ts +2 -1
- package/dist/src/app-opentui/scenes/inbox.js +64 -17
- package/dist/src/app-opentui/scenes/init.d.ts +2 -1
- package/dist/src/app-opentui/scenes/init.js +62 -21
- package/dist/src/app-opentui/scenes/main.d.ts +2 -1
- package/dist/src/app-opentui/scenes/main.js +80 -24
- package/dist/src/app-opentui/scenes/run-detail.d.ts +2 -1
- package/dist/src/app-opentui/scenes/run-detail.js +39 -25
- package/dist/src/app-opentui/scenes/server.d.ts +2 -1
- package/dist/src/app-opentui/scenes/server.js +71 -20
- package/dist/src/app-opentui/scenes/tasks.js +44 -22
- package/dist/src/app-opentui/state.js +70 -30
- package/dist/src/app-opentui/terminal-capabilities.d.ts +7 -0
- package/dist/src/app-opentui/terminal-capabilities.js +15 -0
- package/dist/src/app-opentui/types.d.ts +10 -2
- package/dist/src/commands/_doctor-checks.js +62 -13
- package/dist/src/commands/_operator-view.js +63 -13
- package/dist/src/commands/_pi-frontend.js +63 -13
- package/dist/src/commands/_preflight.js +64 -14
- package/dist/src/commands/_server-client.js +82 -18
- package/dist/src/commands/_snapshot-upload.js +62 -13
- package/dist/src/commands/connect.js +7 -1
- package/dist/src/commands/doctor.js +62 -13
- package/dist/src/commands/github.js +144 -23
- package/dist/src/commands/inbox.js +62 -13
- package/dist/src/commands/init.js +82 -18
- package/dist/src/commands/inspect.js +62 -13
- package/dist/src/commands/run.js +66 -13
- package/dist/src/commands/server.js +69 -14
- package/dist/src/commands/setup.js +62 -13
- package/dist/src/commands/stats.js +62 -13
- package/dist/src/commands/task-run-driver.js +62 -13
- package/dist/src/commands/task.js +65 -14
- package/dist/src/commands.js +227 -92
- package/dist/src/index.js +234 -99
- package/package.json +8 -9
- package/dist/src/app-opentui/render/image-visual-layer-native-canvas.d.ts +0 -2
- package/dist/src/app-opentui/render/image-visual-layer-native-canvas.js +0 -1484
|
@@ -1,279 +1,656 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
import { existsSync } from "fs";
|
|
4
|
-
import { resolve } from "path";
|
|
5
|
-
import { createCliRenderer } from "@opentui/core";
|
|
6
|
-
|
|
7
|
-
// packages/cli/src/app-opentui/events.ts
|
|
8
|
-
function createAppEventBus() {
|
|
9
|
-
const listeners = new Set;
|
|
10
|
-
return {
|
|
11
|
-
emit(event) {
|
|
12
|
-
for (const listener of [...listeners])
|
|
13
|
-
listener(event);
|
|
14
|
-
},
|
|
15
|
-
subscribe(listener) {
|
|
16
|
-
listeners.add(listener);
|
|
17
|
-
return () => listeners.delete(listener);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
function normalizeError(error) {
|
|
22
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
23
|
-
const rawHint = error instanceof Error ? error.hint : undefined;
|
|
24
|
-
return {
|
|
25
|
-
message: message || "Unknown Rig app error",
|
|
26
|
-
...typeof rawHint === "string" && rawHint.trim() ? { hint: rawHint.trim() } : {},
|
|
27
|
-
cause: error
|
|
28
|
-
};
|
|
29
|
-
}
|
|
2
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
30
3
|
|
|
31
|
-
// packages/cli/src/app-opentui/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
["run", "fleet"],
|
|
39
|
-
["tasks", "tasks"],
|
|
40
|
-
["task", "tasks"],
|
|
41
|
-
["help", "help"]
|
|
42
|
-
]);
|
|
43
|
-
var TASK_VIEW_COMMANDS = new Set([
|
|
44
|
-
"",
|
|
45
|
-
"all",
|
|
46
|
-
"open",
|
|
47
|
-
"ready",
|
|
48
|
-
"blocked",
|
|
49
|
-
"closed",
|
|
50
|
-
"done",
|
|
51
|
-
"mine",
|
|
52
|
-
"me",
|
|
53
|
-
"sort",
|
|
54
|
-
"status",
|
|
55
|
-
"state",
|
|
56
|
-
"assignee",
|
|
57
|
-
"assigned",
|
|
58
|
-
"label",
|
|
59
|
-
"search",
|
|
60
|
-
"q"
|
|
61
|
-
]);
|
|
62
|
-
function isTaskViewCommand(command) {
|
|
63
|
-
return !command || TASK_VIEW_COMMANDS.has(command) || command.startsWith("-");
|
|
4
|
+
// packages/cli/src/app-opentui/pi-pty-host.ts
|
|
5
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6
|
+
import { basename as basename2 } from "path";
|
|
7
|
+
import { RGBA, StyledText, TextAttributes } from "@opentui/core";
|
|
8
|
+
import { Terminal as XtermTerminal2 } from "@xterm/headless";
|
|
9
|
+
function clampCols2(cols) {
|
|
10
|
+
return Math.max(MIN_COLS2, Math.trunc(cols || 100));
|
|
64
11
|
}
|
|
65
|
-
function
|
|
66
|
-
return
|
|
67
|
-
scene,
|
|
68
|
-
argv,
|
|
69
|
-
...raw ? { raw } : {},
|
|
70
|
-
action: {
|
|
71
|
-
kind,
|
|
72
|
-
...label ? { label } : {},
|
|
73
|
-
...payload ? { payload } : {}
|
|
74
|
-
}
|
|
75
|
-
};
|
|
12
|
+
function clampRows2(rows) {
|
|
13
|
+
return Math.max(MIN_ROWS2, Math.min(MAX_ROWS2, Math.trunc(rows || 35)));
|
|
76
14
|
}
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
15
|
+
function rgbaFromXtermColor(mode, value) {
|
|
16
|
+
if (mode === 1 || mode === 2 || mode === XTERM_COLOR_MODE_INDEXED_16 || mode === XTERM_COLOR_MODE_INDEXED_256) {
|
|
17
|
+
return RGBA.fromIndex(value);
|
|
18
|
+
}
|
|
19
|
+
if (mode === 3 || mode === XTERM_COLOR_MODE_RGB) {
|
|
20
|
+
return RGBA.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255);
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
80
23
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
24
|
+
function textAttributesFromCell(cell) {
|
|
25
|
+
let attributes = TextAttributes.NONE;
|
|
26
|
+
if (cell.isBold())
|
|
27
|
+
attributes |= TextAttributes.BOLD;
|
|
28
|
+
if (cell.isDim())
|
|
29
|
+
attributes |= TextAttributes.DIM;
|
|
30
|
+
if (cell.isItalic())
|
|
31
|
+
attributes |= TextAttributes.ITALIC;
|
|
32
|
+
if (cell.isUnderline())
|
|
33
|
+
attributes |= TextAttributes.UNDERLINE;
|
|
34
|
+
if (cell.isBlink())
|
|
35
|
+
attributes |= TextAttributes.BLINK;
|
|
36
|
+
if (cell.isInverse())
|
|
37
|
+
attributes |= TextAttributes.INVERSE;
|
|
38
|
+
if (cell.isInvisible())
|
|
39
|
+
attributes |= TextAttributes.HIDDEN;
|
|
40
|
+
if (cell.isStrikethrough())
|
|
41
|
+
attributes |= TextAttributes.STRIKETHROUGH;
|
|
42
|
+
return attributes;
|
|
84
43
|
}
|
|
85
|
-
function
|
|
86
|
-
|
|
44
|
+
function sameRgba(a, b) {
|
|
45
|
+
if (!a && !b)
|
|
46
|
+
return true;
|
|
47
|
+
return Boolean(a && b && a.equals(b));
|
|
87
48
|
}
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
if (taskValue && !taskValue.startsWith("-"))
|
|
91
|
-
return taskValue;
|
|
92
|
-
return args.find((part) => part.trim() && part !== "next" && part !== "--next" && part !== "--task" && !part.startsWith("-"));
|
|
49
|
+
function sameStyle(a, b) {
|
|
50
|
+
return sameRgba(a.fg, b.fg) && sameRgba(a.bg, b.bg) && (a.attributes ?? 0) === (b.attributes ?? 0);
|
|
93
51
|
}
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const status = firstValueAfter(args, "--status");
|
|
100
|
-
const assignee = firstValueAfter(args, "--assignee") ?? firstValueAfter(args, "--assigned");
|
|
101
|
-
const label = firstValueAfter(args, "--label");
|
|
102
|
-
const search = firstValueAfter(args, "--search") ?? firstValueAfter(args, "-q");
|
|
103
|
-
const optionPayload = {
|
|
104
|
-
...sort ? { taskSort: sort } : {},
|
|
105
|
-
...state ? { taskState: state, taskFilterPreset: "custom" } : {},
|
|
106
|
-
...status ? { taskStatus: status, taskFilterPreset: "custom" } : {},
|
|
107
|
-
...assignee ? { taskAssignee: assignee, taskFilterPreset: "custom" } : {},
|
|
108
|
-
...label ? { taskLabel: label, taskFilterPreset: "custom" } : {},
|
|
109
|
-
...search ? { taskSearch: search, taskFilterPreset: "custom" } : {}
|
|
52
|
+
function styleFromCell(cell) {
|
|
53
|
+
return {
|
|
54
|
+
fg: rgbaFromXtermColor(cell.getFgColorMode(), cell.getFgColor()),
|
|
55
|
+
bg: rgbaFromXtermColor(cell.getBgColorMode(), cell.getBgColor()),
|
|
56
|
+
attributes: textAttributesFromCell(cell)
|
|
110
57
|
};
|
|
111
|
-
if (normalized === "sort") {
|
|
112
|
-
const value = firstNonOption(rest) ?? sort ?? "priority";
|
|
113
|
-
return { payload: { ...optionPayload, taskSort: value }, label: `Sorting tasks by ${value}` };
|
|
114
|
-
}
|
|
115
|
-
if (normalized === "status") {
|
|
116
|
-
const value = firstNonOption(rest) ?? status ?? "open";
|
|
117
|
-
return { payload: { ...optionPayload, taskStatus: value, taskFilterPreset: "custom" }, label: `Filtering tasks by status ${value}` };
|
|
118
|
-
}
|
|
119
|
-
if (normalized === "state") {
|
|
120
|
-
const value = firstNonOption(rest) ?? state ?? "open";
|
|
121
|
-
return { payload: { ...optionPayload, taskState: value, taskFilterPreset: "custom" }, label: `Filtering tasks by state ${value}` };
|
|
122
|
-
}
|
|
123
|
-
if (normalized === "assignee" || normalized === "assigned" || normalized === "mine" || normalized === "me") {
|
|
124
|
-
const value = normalized === "mine" || normalized === "me" ? "me" : firstNonOption(rest) ?? assignee ?? "me";
|
|
125
|
-
return { payload: { ...optionPayload, taskAssignee: value, taskState: "open", taskFilterPreset: value === "me" ? "mine" : "custom" }, label: value === "me" ? "Filtering my tasks" : `Filtering tasks for ${value}` };
|
|
126
|
-
}
|
|
127
|
-
if (normalized === "label") {
|
|
128
|
-
const value = firstNonOption(rest) ?? label;
|
|
129
|
-
return { payload: { ...optionPayload, ...value ? { taskLabel: value } : {}, taskFilterPreset: "custom" }, label: value ? `Filtering tasks labeled ${value}` : "Filtering tasks by label" };
|
|
130
|
-
}
|
|
131
|
-
if (normalized === "search" || normalized === "q") {
|
|
132
|
-
const value = rest.join(" ").trim() || search;
|
|
133
|
-
return { payload: { ...optionPayload, ...value ? { taskSearch: value } : {}, taskFilterPreset: "custom" }, label: value ? `Searching tasks for ${value}` : "Searching tasks" };
|
|
134
|
-
}
|
|
135
|
-
if (["all", "open", "ready", "blocked", "closed", "done"].includes(normalized)) {
|
|
136
|
-
return { payload: { ...optionPayload, taskFilterPreset: normalized === "done" ? "closed" : normalized }, label: `Filtering tasks: ${normalized}` };
|
|
137
|
-
}
|
|
138
|
-
if (Object.keys(optionPayload).length > 0)
|
|
139
|
-
return { payload: optionPayload, label: "Filtering tasks" };
|
|
140
|
-
return { label: "Loading tasks" };
|
|
141
|
-
}
|
|
142
|
-
function taskViewIntent(argv, command = "", rest = [], raw) {
|
|
143
|
-
const { payload, label } = taskViewPayload(command, rest);
|
|
144
|
-
return intent("tasks", argv, "refresh", payload, label, raw);
|
|
145
58
|
}
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
59
|
+
function lineToStyledText(line, cols) {
|
|
60
|
+
if (!line)
|
|
61
|
+
return new StyledText([{ __isChunk: true, text: "" }]);
|
|
62
|
+
const chunks = [];
|
|
63
|
+
let run = "";
|
|
64
|
+
let runStyle = null;
|
|
65
|
+
const flush = () => {
|
|
66
|
+
if (!run)
|
|
67
|
+
return;
|
|
68
|
+
chunks.push({
|
|
69
|
+
__isChunk: true,
|
|
70
|
+
text: run,
|
|
71
|
+
...runStyle?.fg ? { fg: runStyle.fg } : {},
|
|
72
|
+
...runStyle?.bg ? { bg: runStyle.bg } : {},
|
|
73
|
+
...(runStyle?.attributes ?? 0) !== 0 ? { attributes: runStyle.attributes } : {}
|
|
74
|
+
});
|
|
75
|
+
run = "";
|
|
76
|
+
};
|
|
77
|
+
for (let index = 0;index < cols; index += 1) {
|
|
78
|
+
const cell = line.getCell(index);
|
|
79
|
+
if (!cell) {
|
|
80
|
+
if (runStyle !== null)
|
|
81
|
+
flush();
|
|
82
|
+
runStyle = null;
|
|
83
|
+
run += " ";
|
|
84
|
+
continue;
|
|
164
85
|
}
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return unsupportedPlainCliIntent(argv);
|
|
172
|
-
return taskViewIntent(argv, normalizedCommand, rest);
|
|
173
|
-
}
|
|
174
|
-
if (normalizedGroup === "run") {
|
|
175
|
-
if (normalizedCommand === "attach") {
|
|
176
|
-
const runId = firstNonOption(rest);
|
|
177
|
-
return intent("handoff", argv, "run-attach", { runId }, runId ? `Attaching ${runId}` : "Attaching run");
|
|
86
|
+
if (cell.getWidth() === 0)
|
|
87
|
+
continue;
|
|
88
|
+
const style = styleFromCell(cell);
|
|
89
|
+
if (!runStyle || !sameStyle(runStyle, style)) {
|
|
90
|
+
flush();
|
|
91
|
+
runStyle = style;
|
|
178
92
|
}
|
|
179
|
-
|
|
180
|
-
return intent("fleet", argv, "refresh", undefined, "Loading fleet");
|
|
181
|
-
return unsupportedPlainCliIntent(argv);
|
|
182
|
-
}
|
|
183
|
-
if (normalizedGroup === "attach") {
|
|
184
|
-
const runId = firstNonOption([command, ...rest]);
|
|
185
|
-
return intent("handoff", argv, "run-attach", { runId }, runId ? `Attaching ${runId}` : "Attaching run");
|
|
93
|
+
run += cell.getChars() || " ";
|
|
186
94
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return intent(nav, argv, "navigate", undefined, `Opening ${nav}`);
|
|
190
|
-
return unsupportedPlainCliIntent(argv);
|
|
191
|
-
}
|
|
192
|
-
function intentFromTypeBar(value) {
|
|
193
|
-
const raw = value.trim();
|
|
194
|
-
if (!raw)
|
|
195
|
-
return intent("fleet", [], "refresh", undefined, "Opening runs", value);
|
|
196
|
-
const parts = raw.split(/\s+/).filter(Boolean);
|
|
197
|
-
const [first = "", second = "", ...rest] = parts;
|
|
198
|
-
const firstScene = NAV_SCENES.get(first.toLowerCase());
|
|
199
|
-
if (first === "tasks" || first === "task") {
|
|
200
|
-
if (second === "run") {
|
|
201
|
-
const task = explicitTaskIdArg(rest);
|
|
202
|
-
if (rest.includes("--next") || task === "next")
|
|
203
|
-
return intent("tasks", parts, "task-run-next", undefined, "Dispatching next task", value);
|
|
204
|
-
if (task)
|
|
205
|
-
return intent("tasks", parts, "task-run-id", { task }, `Dispatching ${task}`, value);
|
|
206
|
-
return intent("tasks", parts, "refresh", undefined, "Select a task, then press Enter; or use run next", value);
|
|
207
|
-
}
|
|
208
|
-
if (!isTaskViewCommand(second.toLowerCase()))
|
|
209
|
-
return unsupportedPlainCliIntent(parts, value);
|
|
210
|
-
return taskViewIntent(parts, second, rest, value);
|
|
211
|
-
}
|
|
212
|
-
if (first === "run") {
|
|
213
|
-
if (second === "next" || second === "--next")
|
|
214
|
-
return intent("tasks", parts, "task-run-next", undefined, "Dispatching next task", value);
|
|
215
|
-
if (second === "--task") {
|
|
216
|
-
const task = explicitTaskIdArg([second, ...rest]);
|
|
217
|
-
if (task)
|
|
218
|
-
return intent("tasks", parts, "task-run-id", { task }, `Dispatching ${task}`, value);
|
|
219
|
-
return intent("tasks", parts, "refresh", undefined, "Select a task, then press Enter; or use run next", value);
|
|
220
|
-
}
|
|
221
|
-
if (second && !second.startsWith("-"))
|
|
222
|
-
return intent("tasks", parts, "task-run-id", { task: second }, `Dispatching ${second}`, value);
|
|
223
|
-
return intent("tasks", parts, "refresh", undefined, "Select a task, then press Enter; or use run next", value);
|
|
224
|
-
}
|
|
225
|
-
if (firstScene && parts.length === 1) {
|
|
226
|
-
return intent(firstScene, parts, "navigate", undefined, `Opening ${firstScene}`, value);
|
|
227
|
-
}
|
|
228
|
-
if (first === "stop")
|
|
229
|
-
return intent("fleet", parts, "run-stop", { runId: second || undefined }, second ? `Stopping ${second}` : "Stopping run", value);
|
|
230
|
-
if (first === "attach") {
|
|
231
|
-
if (!second)
|
|
232
|
-
return intent("handoff", parts, "navigate", undefined, "Select a run to attach", value);
|
|
233
|
-
return intent("handoff", parts, "run-attach", { runId: second }, `Attaching ${second}`, value);
|
|
234
|
-
}
|
|
235
|
-
return intentFromArgv(parts);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// packages/cli/src/app-opentui/layout.ts
|
|
239
|
-
function computeStageLayout(width, height) {
|
|
240
|
-
const safeWidth = Math.max(40, Math.trunc(width || 80));
|
|
241
|
-
const safeHeight = Math.max(18, Math.trunc(height || 24));
|
|
242
|
-
const compact = safeWidth < 92 || safeHeight < 30;
|
|
243
|
-
const horizontalGutter = compact ? 1 : 2;
|
|
244
|
-
const centerWidth = Math.max(36, safeWidth - horizontalGutter * 2);
|
|
245
|
-
const centerLeft = horizontalGutter;
|
|
246
|
-
const typeBarTop = Math.max(0, safeHeight - 3);
|
|
247
|
-
const footerTop = Math.max(0, safeHeight - 1);
|
|
248
|
-
const centerTop = compact ? 1 : Math.max(1, Math.floor(safeHeight * 0.055));
|
|
249
|
-
const centerHeight = Math.max(8, typeBarTop - centerTop - 1);
|
|
250
|
-
return { width: safeWidth, height: safeHeight, centerWidth, centerLeft, centerTop, centerHeight, typeBarTop, footerTop, compact };
|
|
95
|
+
flush();
|
|
96
|
+
return new StyledText(chunks.length > 0 ? chunks : [{ __isChunk: true, text: "" }]);
|
|
251
97
|
}
|
|
252
|
-
function
|
|
253
|
-
|
|
98
|
+
function childCommandPrefix2() {
|
|
99
|
+
const execName = basename2(process.execPath).toLowerCase();
|
|
100
|
+
const currentEntry = process.argv[1];
|
|
101
|
+
if (execName === "bun" || execName === "bun.exe") {
|
|
102
|
+
return currentEntry ? [process.execPath, currentEntry, "__opentui-pi-host"] : [process.execPath, fallbackChildScriptPath];
|
|
103
|
+
}
|
|
104
|
+
return [process.execPath, "__opentui-pi-host"];
|
|
254
105
|
}
|
|
255
|
-
function
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (align === "center") {
|
|
263
|
-
const left = Math.floor(pad / 2);
|
|
264
|
-
return `${" ".repeat(left)}${text}${" ".repeat(pad - left)}`;
|
|
106
|
+
function withEnv2(base) {
|
|
107
|
+
const env = {};
|
|
108
|
+
for (const [key, value] of Object.entries(base ?? process.env)) {
|
|
109
|
+
if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP" || key === "NO_COLOR")
|
|
110
|
+
continue;
|
|
111
|
+
if (typeof value === "string")
|
|
112
|
+
env[key] = value;
|
|
265
113
|
}
|
|
266
|
-
return
|
|
114
|
+
return {
|
|
115
|
+
...env,
|
|
116
|
+
TERM: "xterm-256color",
|
|
117
|
+
COLORTERM: "truecolor",
|
|
118
|
+
FORCE_COLOR: env.FORCE_COLOR ?? "1",
|
|
119
|
+
RIG_OPENTUI_PI_HOST: "1"
|
|
120
|
+
};
|
|
267
121
|
}
|
|
268
|
-
function
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
122
|
+
function getActivePiHost() {
|
|
123
|
+
return activeHost2 && !activeHost2.disposed ? activeHost2 : null;
|
|
124
|
+
}
|
|
125
|
+
function stopActivePiHost(reason = "detach") {
|
|
126
|
+
activeHost2?.dispose(reason);
|
|
127
|
+
activeHost2 = null;
|
|
274
128
|
}
|
|
275
129
|
|
|
276
|
-
|
|
130
|
+
class PiPtyHost {
|
|
131
|
+
runId;
|
|
132
|
+
projectRoot;
|
|
133
|
+
onSnapshot;
|
|
134
|
+
onExit;
|
|
135
|
+
onError;
|
|
136
|
+
terminal;
|
|
137
|
+
disposables = [];
|
|
138
|
+
decoder = new TextDecoder("utf-8");
|
|
139
|
+
proc = null;
|
|
140
|
+
pty = null;
|
|
141
|
+
status = "starting";
|
|
142
|
+
cols;
|
|
143
|
+
rows;
|
|
144
|
+
message = "starting bundled Pi";
|
|
145
|
+
exitCode;
|
|
146
|
+
signal;
|
|
147
|
+
notifyTimer = null;
|
|
148
|
+
_disposed = false;
|
|
149
|
+
constructor(options) {
|
|
150
|
+
this.runId = options.runId;
|
|
151
|
+
this.projectRoot = options.projectRoot;
|
|
152
|
+
this.cols = clampCols2(options.cols);
|
|
153
|
+
this.rows = clampRows2(options.rows);
|
|
154
|
+
this.onSnapshot = options.onSnapshot;
|
|
155
|
+
this.onExit = options.onExit;
|
|
156
|
+
this.onError = options.onError;
|
|
157
|
+
this.terminal = new XtermTerminal2({
|
|
158
|
+
allowProposedApi: true,
|
|
159
|
+
cols: this.cols,
|
|
160
|
+
rows: this.rows,
|
|
161
|
+
scrollback: 1000
|
|
162
|
+
});
|
|
163
|
+
this.registerTerminalResponders();
|
|
164
|
+
}
|
|
165
|
+
get disposed() {
|
|
166
|
+
return this._disposed;
|
|
167
|
+
}
|
|
168
|
+
get snapshot() {
|
|
169
|
+
return this.createSnapshot();
|
|
170
|
+
}
|
|
171
|
+
async start() {
|
|
172
|
+
if (this._disposed)
|
|
173
|
+
throw new Error("Pi PTY host is disposed.");
|
|
174
|
+
if (typeof Bun.Terminal !== "function") {
|
|
175
|
+
throw new Error("Bun native PTY is unavailable in this runtime. Pi host requires Bun.Terminal.");
|
|
176
|
+
}
|
|
177
|
+
const spawnOptions = {
|
|
178
|
+
cwd: this.projectRoot,
|
|
179
|
+
env: withEnv2(process.env),
|
|
180
|
+
terminal: {
|
|
181
|
+
cols: this.cols,
|
|
182
|
+
rows: this.rows,
|
|
183
|
+
name: "xterm-256color",
|
|
184
|
+
data: (_terminal, data) => this.handlePtyData(data)
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const proc = Bun.spawn([
|
|
188
|
+
...childCommandPrefix2(),
|
|
189
|
+
"--run-id",
|
|
190
|
+
this.runId,
|
|
191
|
+
"--project-root",
|
|
192
|
+
this.projectRoot
|
|
193
|
+
], spawnOptions);
|
|
194
|
+
if (!proc.terminal)
|
|
195
|
+
throw new Error("Bun did not attach a terminal to the bundled Pi child process.");
|
|
196
|
+
this.proc = proc;
|
|
197
|
+
this.pty = proc.terminal;
|
|
198
|
+
this.status = "running";
|
|
199
|
+
this.message = "bundled Pi running inside this app";
|
|
200
|
+
this.emitSnapshotSoon(0);
|
|
201
|
+
proc.exited.then((exitCode) => {
|
|
202
|
+
if (this._disposed)
|
|
203
|
+
return;
|
|
204
|
+
this.status = exitCode === 0 ? "exited" : "failed";
|
|
205
|
+
this.exitCode = exitCode;
|
|
206
|
+
this.signal = null;
|
|
207
|
+
this.message = exitCode === 0 ? "bundled Pi exited" : `bundled Pi exited with code ${exitCode}`;
|
|
208
|
+
const snapshot = this.createSnapshot();
|
|
209
|
+
this.onSnapshot?.(snapshot);
|
|
210
|
+
this.onExit?.(snapshot);
|
|
211
|
+
if (activeHost2 === this)
|
|
212
|
+
activeHost2 = null;
|
|
213
|
+
this.dispose("exit", { kill: false, notify: false });
|
|
214
|
+
}).catch((error) => {
|
|
215
|
+
if (this._disposed)
|
|
216
|
+
return;
|
|
217
|
+
this.status = "failed";
|
|
218
|
+
this.message = error instanceof Error ? error.message : String(error);
|
|
219
|
+
const snapshot = this.createSnapshot();
|
|
220
|
+
this.onSnapshot?.(snapshot);
|
|
221
|
+
this.onError?.(error, snapshot);
|
|
222
|
+
if (activeHost2 === this)
|
|
223
|
+
activeHost2 = null;
|
|
224
|
+
this.dispose("error", { kill: false, notify: false });
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
write(data) {
|
|
228
|
+
if (this._disposed || !this.pty)
|
|
229
|
+
return;
|
|
230
|
+
try {
|
|
231
|
+
this.pty.write(data);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
this.status = "failed";
|
|
234
|
+
this.message = error instanceof Error ? error.message : String(error);
|
|
235
|
+
const snapshot = this.createSnapshot();
|
|
236
|
+
this.onSnapshot?.(snapshot);
|
|
237
|
+
this.onError?.(error, snapshot);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
resize(cols, rows) {
|
|
241
|
+
const nextCols = clampCols2(cols);
|
|
242
|
+
const nextRows = clampRows2(rows);
|
|
243
|
+
if (nextCols === this.cols && nextRows === this.rows)
|
|
244
|
+
return;
|
|
245
|
+
this.cols = nextCols;
|
|
246
|
+
this.rows = nextRows;
|
|
247
|
+
this.terminal.resize(nextCols, nextRows);
|
|
248
|
+
try {
|
|
249
|
+
this.pty?.resize(nextCols, nextRows);
|
|
250
|
+
} catch {}
|
|
251
|
+
this.emitSnapshotSoon(0);
|
|
252
|
+
}
|
|
253
|
+
detach() {
|
|
254
|
+
this.dispose("detach");
|
|
255
|
+
return this.createSnapshot("detached from bundled Pi");
|
|
256
|
+
}
|
|
257
|
+
dispose(reason = "dispose", options = {}) {
|
|
258
|
+
if (this._disposed)
|
|
259
|
+
return;
|
|
260
|
+
this._disposed = true;
|
|
261
|
+
if (this.notifyTimer)
|
|
262
|
+
clearTimeout(this.notifyTimer);
|
|
263
|
+
this.notifyTimer = null;
|
|
264
|
+
for (const disposable of this.disposables.splice(0)) {
|
|
265
|
+
try {
|
|
266
|
+
disposable.dispose();
|
|
267
|
+
} catch {}
|
|
268
|
+
}
|
|
269
|
+
if (options.kill !== false) {
|
|
270
|
+
try {
|
|
271
|
+
this.proc?.kill("SIGTERM");
|
|
272
|
+
} catch {}
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
this.pty?.close();
|
|
276
|
+
} catch {}
|
|
277
|
+
try {
|
|
278
|
+
this.terminal.dispose();
|
|
279
|
+
} catch {}
|
|
280
|
+
if (activeHost2 === this)
|
|
281
|
+
activeHost2 = null;
|
|
282
|
+
if (options.notify) {
|
|
283
|
+
this.message = reason;
|
|
284
|
+
this.onSnapshot?.(this.createSnapshot(reason));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
handlePtyData(data) {
|
|
288
|
+
if (this._disposed)
|
|
289
|
+
return;
|
|
290
|
+
const text = this.decoder.decode(data, { stream: true });
|
|
291
|
+
this.respondToRawTerminalQueries(text);
|
|
292
|
+
this.terminal.write(data, () => this.emitSnapshotSoon());
|
|
293
|
+
}
|
|
294
|
+
emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS2) {
|
|
295
|
+
if (this._disposed || this.notifyTimer)
|
|
296
|
+
return;
|
|
297
|
+
this.notifyTimer = setTimeout(() => {
|
|
298
|
+
this.notifyTimer = null;
|
|
299
|
+
if (this._disposed)
|
|
300
|
+
return;
|
|
301
|
+
this.onSnapshot?.(this.createSnapshot());
|
|
302
|
+
}, delayMs);
|
|
303
|
+
}
|
|
304
|
+
createSnapshot(message = this.message) {
|
|
305
|
+
const buffer = this.terminal.buffer.active;
|
|
306
|
+
const end = buffer.length;
|
|
307
|
+
const start = Math.max(0, end - MAX_SNAPSHOT_LINES2);
|
|
308
|
+
const styledStart = Math.max(start, end - this.rows - STYLED_SNAPSHOT_MARGIN);
|
|
309
|
+
const lines = [];
|
|
310
|
+
const styledLines = [];
|
|
311
|
+
for (let row = start;row < end; row += 1) {
|
|
312
|
+
const line = buffer.getLine(row);
|
|
313
|
+
lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
|
|
314
|
+
if (row >= styledStart)
|
|
315
|
+
styledLines[lines.length - 1] = lineToStyledText(line, this.cols);
|
|
316
|
+
}
|
|
317
|
+
while (lines.length < this.rows) {
|
|
318
|
+
lines.push("");
|
|
319
|
+
styledLines[lines.length - 1] = new StyledText([{ __isChunk: true, text: "" }]);
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
runId: this.runId,
|
|
323
|
+
status: this.status,
|
|
324
|
+
cols: this.cols,
|
|
325
|
+
rows: this.rows,
|
|
326
|
+
lines,
|
|
327
|
+
styledLines,
|
|
328
|
+
message,
|
|
329
|
+
...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
|
|
330
|
+
...this.signal !== undefined ? { signal: this.signal } : {}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
registerTerminalResponders() {
|
|
334
|
+
this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "c" }, (params) => {
|
|
335
|
+
if (params.length === 0 || params[0] === 0)
|
|
336
|
+
this.write("\x1B[?62;22c");
|
|
337
|
+
return false;
|
|
338
|
+
}));
|
|
339
|
+
this.disposables.push(this.terminal.parser.registerCsiHandler({ prefix: ">", final: "c" }, (params) => {
|
|
340
|
+
if (params.length === 0 || params[0] === 0)
|
|
341
|
+
this.write("\x1B[>0;0;0c");
|
|
342
|
+
return false;
|
|
343
|
+
}));
|
|
344
|
+
this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
|
|
345
|
+
if (params[0] === 5) {
|
|
346
|
+
this.write("\x1B[0n");
|
|
347
|
+
} else if (params[0] === 6) {
|
|
348
|
+
const row = this.terminal.buffer.active.cursorY + 1;
|
|
349
|
+
const col = this.terminal.buffer.active.cursorX + 1;
|
|
350
|
+
this.write(`\x1B[${row};${col}R`);
|
|
351
|
+
}
|
|
352
|
+
return false;
|
|
353
|
+
}));
|
|
354
|
+
}
|
|
355
|
+
respondToRawTerminalQueries(text) {
|
|
356
|
+
if (!text)
|
|
357
|
+
return;
|
|
358
|
+
const decrqm = /\x1b\[\?(\d+)\$p/g;
|
|
359
|
+
let match;
|
|
360
|
+
while ((match = decrqm.exec(text)) !== null) {
|
|
361
|
+
this.write(`\x1B[?${match[1]};2$y`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
var MIN_COLS2 = 40, MIN_ROWS2 = 12, MAX_ROWS2 = 300, MAX_SNAPSHOT_LINES2 = 360, STYLED_SNAPSHOT_MARGIN = 6, SNAPSHOT_DELAY_MS2 = 120, fallbackChildScriptPath, activeHost2 = null, XTERM_COLOR_MODE_INDEXED_16 = 16777216, XTERM_COLOR_MODE_INDEXED_256 = 33554432, XTERM_COLOR_MODE_RGB = 50331648;
|
|
366
|
+
var init_pi_pty_host = __esm(() => {
|
|
367
|
+
fallbackChildScriptPath = fileURLToPath2(new URL("./pi-host-child.ts", import.meta.url));
|
|
368
|
+
});
|
|
369
|
+
// packages/cli/src/app-opentui/runtime.ts
|
|
370
|
+
import { existsSync } from "fs";
|
|
371
|
+
import { resolve } from "path";
|
|
372
|
+
import { BoxRenderable as BoxRenderable2, RGBA as RGBA7, TextRenderable as TextRenderable3, createCliRenderer } from "@opentui/core";
|
|
373
|
+
|
|
374
|
+
// packages/cli/src/app-opentui/events.ts
|
|
375
|
+
function createAppEventBus() {
|
|
376
|
+
const listeners = new Set;
|
|
377
|
+
return {
|
|
378
|
+
emit(event) {
|
|
379
|
+
for (const listener of [...listeners])
|
|
380
|
+
listener(event);
|
|
381
|
+
},
|
|
382
|
+
subscribe(listener) {
|
|
383
|
+
listeners.add(listener);
|
|
384
|
+
return () => listeners.delete(listener);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function normalizeError(error) {
|
|
389
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
390
|
+
const rawHint = error instanceof Error ? error.hint : undefined;
|
|
391
|
+
return {
|
|
392
|
+
message: message || "Unknown app error",
|
|
393
|
+
...typeof rawHint === "string" && rawHint.trim() ? { hint: rawHint.trim() } : {},
|
|
394
|
+
cause: error
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// packages/cli/src/app-opentui/focus-manager.ts
|
|
399
|
+
class AppFocusManager {
|
|
400
|
+
modalStack = [];
|
|
401
|
+
pushModal(owner) {
|
|
402
|
+
this.modalStack = [...this.modalStack.filter((entry) => entry !== owner), owner];
|
|
403
|
+
}
|
|
404
|
+
popModal(owner) {
|
|
405
|
+
this.modalStack = this.modalStack.filter((entry) => entry !== owner);
|
|
406
|
+
}
|
|
407
|
+
get modal() {
|
|
408
|
+
const owner = this.modalStack[this.modalStack.length - 1];
|
|
409
|
+
return owner ? { active: true, owner } : { active: false };
|
|
410
|
+
}
|
|
411
|
+
restoreTypeBarFocus(typeBar, options = {}) {
|
|
412
|
+
if (options.hidden || this.modal.active || !typeBar)
|
|
413
|
+
return;
|
|
414
|
+
if (!typeBar.input.focused)
|
|
415
|
+
typeBar.input.focus();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// packages/cli/src/app-opentui/command-pty-host.ts
|
|
420
|
+
import { basename } from "path";
|
|
421
|
+
import { fileURLToPath } from "url";
|
|
422
|
+
import { Terminal as XtermTerminal } from "@xterm/headless";
|
|
423
|
+
var MIN_COLS = 40;
|
|
424
|
+
var MIN_ROWS = 10;
|
|
425
|
+
var MAX_ROWS = 300;
|
|
426
|
+
var MAX_SNAPSHOT_LINES = 420;
|
|
427
|
+
var SNAPSHOT_DELAY_MS = 80;
|
|
428
|
+
var fallbackCliScriptPath = fileURLToPath(new URL("../../bin/rig.ts", import.meta.url));
|
|
429
|
+
var activeHost = null;
|
|
430
|
+
function clampCols(cols) {
|
|
431
|
+
return Math.max(MIN_COLS, Math.trunc(cols || 120));
|
|
432
|
+
}
|
|
433
|
+
function clampRows(rows) {
|
|
434
|
+
return Math.max(MIN_ROWS, Math.min(MAX_ROWS, Math.trunc(rows || 36)));
|
|
435
|
+
}
|
|
436
|
+
function childCommandPrefix() {
|
|
437
|
+
const execName = basename(process.execPath).toLowerCase();
|
|
438
|
+
const currentEntry = process.argv[1];
|
|
439
|
+
if (execName === "bun" || execName === "bun.exe") {
|
|
440
|
+
return currentEntry ? [process.execPath, currentEntry] : [process.execPath, fallbackCliScriptPath];
|
|
441
|
+
}
|
|
442
|
+
return [process.execPath];
|
|
443
|
+
}
|
|
444
|
+
function withEnv(base) {
|
|
445
|
+
const env = {};
|
|
446
|
+
for (const [key, value] of Object.entries(base ?? process.env)) {
|
|
447
|
+
if (key === "NO_COLOR")
|
|
448
|
+
continue;
|
|
449
|
+
if (typeof value === "string")
|
|
450
|
+
env[key] = value;
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
...env,
|
|
454
|
+
TERM: "xterm-256color",
|
|
455
|
+
COLORTERM: "truecolor",
|
|
456
|
+
FORCE_COLOR: env.FORCE_COLOR ?? "1",
|
|
457
|
+
RIG_PLAIN: "1",
|
|
458
|
+
RIG_OPENTUI_COMMAND_HOST: "1",
|
|
459
|
+
RIG_CLI_PLAIN_HELP: env.RIG_CLI_PLAIN_HELP ?? "1"
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function commandLabel(argv, fallback) {
|
|
463
|
+
const command = argv.join(" ").trim();
|
|
464
|
+
return fallback?.trim() || (command ? `rig ${command}` : "rig");
|
|
465
|
+
}
|
|
466
|
+
function getActiveCommandHost() {
|
|
467
|
+
return activeHost && !activeHost.disposed ? activeHost : null;
|
|
468
|
+
}
|
|
469
|
+
function stopActiveCommandHost(reason = "detach") {
|
|
470
|
+
activeHost?.dispose(reason);
|
|
471
|
+
activeHost = null;
|
|
472
|
+
}
|
|
473
|
+
class CommandPtyHost {
|
|
474
|
+
argv;
|
|
475
|
+
projectRoot;
|
|
476
|
+
label;
|
|
477
|
+
env;
|
|
478
|
+
onSnapshot;
|
|
479
|
+
onExit;
|
|
480
|
+
onError;
|
|
481
|
+
terminal;
|
|
482
|
+
decoder = new TextDecoder("utf-8");
|
|
483
|
+
proc = null;
|
|
484
|
+
pty = null;
|
|
485
|
+
status = "starting";
|
|
486
|
+
cols;
|
|
487
|
+
rows;
|
|
488
|
+
message = "starting command";
|
|
489
|
+
exitCode;
|
|
490
|
+
signal;
|
|
491
|
+
notifyTimer = null;
|
|
492
|
+
_disposed = false;
|
|
493
|
+
constructor(options) {
|
|
494
|
+
this.argv = [...options.argv];
|
|
495
|
+
this.projectRoot = options.projectRoot;
|
|
496
|
+
this.label = commandLabel(options.argv, options.label);
|
|
497
|
+
this.env = options.env;
|
|
498
|
+
this.cols = clampCols(options.cols);
|
|
499
|
+
this.rows = clampRows(options.rows);
|
|
500
|
+
this.onSnapshot = options.onSnapshot;
|
|
501
|
+
this.onExit = options.onExit;
|
|
502
|
+
this.onError = options.onError;
|
|
503
|
+
this.terminal = new XtermTerminal({ allowProposedApi: true, cols: this.cols, rows: this.rows, scrollback: 2000 });
|
|
504
|
+
}
|
|
505
|
+
get disposed() {
|
|
506
|
+
return this._disposed;
|
|
507
|
+
}
|
|
508
|
+
get snapshot() {
|
|
509
|
+
return this.createSnapshot();
|
|
510
|
+
}
|
|
511
|
+
async start() {
|
|
512
|
+
if (this._disposed)
|
|
513
|
+
throw new Error("Command PTY host is disposed.");
|
|
514
|
+
if (typeof Bun.Terminal !== "function") {
|
|
515
|
+
throw new Error("Bun native PTY is unavailable in this runtime. Command host requires Bun.Terminal.");
|
|
516
|
+
}
|
|
517
|
+
const spawnOptions = {
|
|
518
|
+
cwd: this.projectRoot,
|
|
519
|
+
env: withEnv(this.env),
|
|
520
|
+
terminal: {
|
|
521
|
+
cols: this.cols,
|
|
522
|
+
rows: this.rows,
|
|
523
|
+
name: "xterm-256color",
|
|
524
|
+
data: (_terminal, data) => this.handlePtyData(data)
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
const proc = Bun.spawn([...childCommandPrefix(), ...this.argv], spawnOptions);
|
|
528
|
+
if (!proc.terminal)
|
|
529
|
+
throw new Error("Bun did not attach a terminal to the command child process.");
|
|
530
|
+
this.proc = proc;
|
|
531
|
+
this.pty = proc.terminal;
|
|
532
|
+
this.status = "running";
|
|
533
|
+
this.message = "running";
|
|
534
|
+
this.emitSnapshotSoon(0);
|
|
535
|
+
proc.exited.then((exitCode) => {
|
|
536
|
+
if (this._disposed)
|
|
537
|
+
return;
|
|
538
|
+
this.status = exitCode === 0 ? "exited" : "failed";
|
|
539
|
+
this.exitCode = exitCode;
|
|
540
|
+
this.signal = null;
|
|
541
|
+
this.message = exitCode === 0 ? "completed" : `exited with code ${exitCode}`;
|
|
542
|
+
const snapshot = this.createSnapshot();
|
|
543
|
+
this.onSnapshot?.(snapshot);
|
|
544
|
+
this.onExit?.(snapshot);
|
|
545
|
+
if (activeHost === this)
|
|
546
|
+
activeHost = null;
|
|
547
|
+
this.dispose("exit", { kill: false, notify: false });
|
|
548
|
+
}).catch((error) => {
|
|
549
|
+
if (this._disposed)
|
|
550
|
+
return;
|
|
551
|
+
this.status = "failed";
|
|
552
|
+
this.message = error instanceof Error ? error.message : String(error);
|
|
553
|
+
const snapshot = this.createSnapshot();
|
|
554
|
+
this.onSnapshot?.(snapshot);
|
|
555
|
+
this.onError?.(error, snapshot);
|
|
556
|
+
if (activeHost === this)
|
|
557
|
+
activeHost = null;
|
|
558
|
+
this.dispose("error", { kill: false, notify: false });
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
write(data) {
|
|
562
|
+
if (this._disposed || !this.pty)
|
|
563
|
+
return;
|
|
564
|
+
try {
|
|
565
|
+
this.pty.write(data);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
this.status = "failed";
|
|
568
|
+
this.message = error instanceof Error ? error.message : String(error);
|
|
569
|
+
const snapshot = this.createSnapshot();
|
|
570
|
+
this.onSnapshot?.(snapshot);
|
|
571
|
+
this.onError?.(error, snapshot);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
resize(cols, rows) {
|
|
575
|
+
const nextCols = clampCols(cols);
|
|
576
|
+
const nextRows = clampRows(rows);
|
|
577
|
+
if (nextCols === this.cols && nextRows === this.rows)
|
|
578
|
+
return;
|
|
579
|
+
this.cols = nextCols;
|
|
580
|
+
this.rows = nextRows;
|
|
581
|
+
this.terminal.resize(nextCols, nextRows);
|
|
582
|
+
try {
|
|
583
|
+
this.pty?.resize(nextCols, nextRows);
|
|
584
|
+
} catch {}
|
|
585
|
+
this.emitSnapshotSoon(0);
|
|
586
|
+
}
|
|
587
|
+
dispose(reason = "dispose", options = {}) {
|
|
588
|
+
if (this._disposed)
|
|
589
|
+
return;
|
|
590
|
+
this._disposed = true;
|
|
591
|
+
if (this.notifyTimer)
|
|
592
|
+
clearTimeout(this.notifyTimer);
|
|
593
|
+
this.notifyTimer = null;
|
|
594
|
+
if (options.kill !== false) {
|
|
595
|
+
try {
|
|
596
|
+
this.proc?.kill("SIGTERM");
|
|
597
|
+
} catch {}
|
|
598
|
+
}
|
|
599
|
+
try {
|
|
600
|
+
this.pty?.close();
|
|
601
|
+
} catch {}
|
|
602
|
+
try {
|
|
603
|
+
this.terminal.dispose();
|
|
604
|
+
} catch {}
|
|
605
|
+
if (activeHost === this)
|
|
606
|
+
activeHost = null;
|
|
607
|
+
if (options.notify) {
|
|
608
|
+
this.message = reason;
|
|
609
|
+
this.onSnapshot?.(this.createSnapshot(reason));
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
handlePtyData(data) {
|
|
613
|
+
if (this._disposed)
|
|
614
|
+
return;
|
|
615
|
+
this.decoder.decode(data, { stream: true });
|
|
616
|
+
this.terminal.write(data, () => this.emitSnapshotSoon());
|
|
617
|
+
}
|
|
618
|
+
emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS) {
|
|
619
|
+
if (this._disposed || this.notifyTimer)
|
|
620
|
+
return;
|
|
621
|
+
this.notifyTimer = setTimeout(() => {
|
|
622
|
+
this.notifyTimer = null;
|
|
623
|
+
if (this._disposed)
|
|
624
|
+
return;
|
|
625
|
+
this.onSnapshot?.(this.createSnapshot());
|
|
626
|
+
}, delayMs);
|
|
627
|
+
}
|
|
628
|
+
createSnapshot(message = this.message) {
|
|
629
|
+
const buffer = this.terminal.buffer.active;
|
|
630
|
+
const end = buffer.length;
|
|
631
|
+
const start = Math.max(0, end - MAX_SNAPSHOT_LINES);
|
|
632
|
+
const lines = [];
|
|
633
|
+
for (let row = start;row < end; row += 1) {
|
|
634
|
+
const line = buffer.getLine(row);
|
|
635
|
+
lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
|
|
636
|
+
}
|
|
637
|
+
while (lines.length < this.rows)
|
|
638
|
+
lines.push("");
|
|
639
|
+
return {
|
|
640
|
+
command: this.argv,
|
|
641
|
+
label: this.label,
|
|
642
|
+
status: this.status,
|
|
643
|
+
cols: this.cols,
|
|
644
|
+
rows: this.rows,
|
|
645
|
+
lines,
|
|
646
|
+
message,
|
|
647
|
+
...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
|
|
648
|
+
...this.signal !== undefined ? { signal: this.signal } : {}
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// packages/cli/src/app-opentui/list-search.ts
|
|
277
654
|
function isListSearchScene(scene) {
|
|
278
655
|
return scene === "fleet" || scene === "tasks";
|
|
279
656
|
}
|
|
@@ -291,19 +668,411 @@ function valueText(value) {
|
|
|
291
668
|
const record = value;
|
|
292
669
|
return ["id", "runId", "taskId", "number", "title", "name", "summary", "status", "state", "label", "labels", "assignee", "assignees", "url"].map((key) => valueText(record[key])).join(" ");
|
|
293
670
|
}
|
|
294
|
-
return "";
|
|
295
|
-
}
|
|
296
|
-
function includesQuery(haystack, query) {
|
|
297
|
-
const needle = query.trim().toLowerCase();
|
|
298
|
-
if (!needle)
|
|
299
|
-
return true;
|
|
300
|
-
return haystack.map(valueText).join(" ").toLowerCase().includes(needle);
|
|
671
|
+
return "";
|
|
672
|
+
}
|
|
673
|
+
function includesQuery(haystack, query) {
|
|
674
|
+
const needle = query.trim().toLowerCase();
|
|
675
|
+
if (!needle)
|
|
676
|
+
return true;
|
|
677
|
+
return haystack.map(valueText).join(" ").toLowerCase().includes(needle);
|
|
678
|
+
}
|
|
679
|
+
function filterRunsForSearch(runs, query) {
|
|
680
|
+
return query.trim() ? runs.filter((run) => includesQuery([run.runId, run.status, run.title, run.taskId, run.raw], query)) : [...runs];
|
|
681
|
+
}
|
|
682
|
+
function filterTasksForSearch(tasks, query) {
|
|
683
|
+
return query.trim() ? tasks.filter((task) => includesQuery([task.id, task.status, task.title, task.priority, task.labels, task.assignees, task.raw], query)) : [...tasks];
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// packages/cli/src/app-opentui/keymap.ts
|
|
687
|
+
init_pi_pty_host();
|
|
688
|
+
function clearTypeBar(context, message) {
|
|
689
|
+
const typeBar = context.getTypeBar();
|
|
690
|
+
if (typeBar)
|
|
691
|
+
typeBar.input.value = "";
|
|
692
|
+
context.emitTypeBarPatch({ value: "", message });
|
|
693
|
+
}
|
|
694
|
+
function handleEmbeddedTerminalKey(context, key) {
|
|
695
|
+
const state = context.getState();
|
|
696
|
+
const piHost = getActivePiHost();
|
|
697
|
+
const commandHost = getActiveCommandHost();
|
|
698
|
+
const host = state.scene === "handoff" ? piHost : state.scene === "command" ? commandHost : null;
|
|
699
|
+
if (!host)
|
|
700
|
+
return false;
|
|
701
|
+
if (key.ctrl && key.name === "]") {
|
|
702
|
+
if (state.scene === "handoff") {
|
|
703
|
+
stopActivePiHost("operator detach");
|
|
704
|
+
clearTypeBar(context, "detached from Pi");
|
|
705
|
+
context.runAppAction("Opening runs", () => context.runtime.runIntent({ scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } }));
|
|
706
|
+
} else {
|
|
707
|
+
stopActiveCommandHost("operator detach");
|
|
708
|
+
clearTypeBar(context, "closed command");
|
|
709
|
+
context.runAppAction("Project menu", () => context.runtime.runIntent({ scene: "main", argv: ["main"], action: { kind: "none", label: "Project menu" } }));
|
|
710
|
+
}
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
const sequence = key.raw || key.sequence;
|
|
714
|
+
if (sequence)
|
|
715
|
+
host.write(sequence);
|
|
716
|
+
const typeBar = context.getTypeBar();
|
|
717
|
+
if (typeBar)
|
|
718
|
+
typeBar.input.value = "";
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
function handleAppKeyPress(context, key) {
|
|
722
|
+
if (handleEmbeddedTerminalKey(context, key))
|
|
723
|
+
return;
|
|
724
|
+
const renderer = context.renderer();
|
|
725
|
+
if (key.ctrl && key.name === "c") {
|
|
726
|
+
renderer?.destroy();
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const state = context.getState();
|
|
730
|
+
const currentScene = state.scene;
|
|
731
|
+
const searchScene = isListSearchScene(currentScene);
|
|
732
|
+
const typeBar = context.getTypeBar();
|
|
733
|
+
const inputValue = typeBar?.input.value ?? "";
|
|
734
|
+
const typeBarEmpty = inputValue.trim() === "";
|
|
735
|
+
if (typeBarEmpty && (key.name === "right" || key.name === "tab")) {
|
|
736
|
+
context.runAppAction("Navigate", () => context.runtime.runIntent(context.primaryNavIntent(context.nextPrimaryScene(context.getState().scene, 1))));
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (typeBarEmpty && key.name === "left") {
|
|
740
|
+
context.runAppAction("Navigate", () => context.runtime.runIntent(context.primaryNavIntent(context.nextPrimaryScene(context.getState().scene, -1))));
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (!searchScene && typeBarEmpty && key.name === "r") {
|
|
744
|
+
context.runAppAction("Opening runs", () => context.runtime.runIntent(context.primaryNavIntent("fleet")));
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (!searchScene && typeBarEmpty && key.name === "t") {
|
|
748
|
+
context.runAppAction("Opening tasks", () => context.runtime.runIntent(context.primaryNavIntent("tasks")));
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (!searchScene && typeBarEmpty && (key.name === "h" || key.sequence === "?")) {
|
|
752
|
+
context.runAppAction("Opening help", () => context.runtime.runIntent(context.primaryNavIntent("help")));
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (searchScene && key.name === "up" || !searchScene && typeBarEmpty && (key.name === "up" || key.name === "k")) {
|
|
756
|
+
if (context.moveSelection(-1, context.getRenderedSelectableItems()))
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (searchScene && key.name === "down" || !searchScene && typeBarEmpty && (key.name === "down" || key.name === "j")) {
|
|
760
|
+
if (context.moveSelection(1, context.getRenderedSelectableItems()))
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
if (typeBarEmpty && (key.name === "return" || key.name === "enter" || key.name === "space" || key.sequence === " ")) {
|
|
764
|
+
context.runAppAction("Activate selection", () => context.activateSelection(context.getRenderedSelectableItems()));
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (!searchScene && key.name === "q" && typeBarEmpty) {
|
|
768
|
+
renderer?.destroy();
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (key.name === "escape") {
|
|
772
|
+
if (inputValue)
|
|
773
|
+
clearTypeBar(context);
|
|
774
|
+
else
|
|
775
|
+
context.runAppAction("Project menu", () => context.runtime.runIntent({ scene: "main", argv: ["main"], action: { kind: "none", label: "Project menu" } }));
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// packages/cli/src/app-opentui/layout.ts
|
|
780
|
+
function computeStageLayout(width, height) {
|
|
781
|
+
const safeWidth = Math.max(40, Math.trunc(width || 80));
|
|
782
|
+
const safeHeight = Math.max(18, Math.trunc(height || 24));
|
|
783
|
+
const compact = safeWidth < 92 || safeHeight < 30;
|
|
784
|
+
const horizontalGutter = compact ? 1 : 2;
|
|
785
|
+
const centerWidth = Math.max(36, safeWidth - horizontalGutter * 2);
|
|
786
|
+
const centerLeft = horizontalGutter;
|
|
787
|
+
const footerTop = Math.max(0, safeHeight - 1);
|
|
788
|
+
const typeBarTop = footerTop;
|
|
789
|
+
const centerTop = compact ? 1 : Math.max(1, Math.floor(safeHeight * 0.055));
|
|
790
|
+
const centerHeight = Math.max(8, footerTop - centerTop);
|
|
791
|
+
return { width: safeWidth, height: safeHeight, centerWidth, centerLeft, centerTop, centerHeight, typeBarTop, footerTop, compact };
|
|
792
|
+
}
|
|
793
|
+
function visibleWidth(text) {
|
|
794
|
+
return [...text].length;
|
|
795
|
+
}
|
|
796
|
+
function alignText(text, width, align = "left") {
|
|
797
|
+
const length = visibleWidth(text);
|
|
798
|
+
if (length >= width)
|
|
799
|
+
return text;
|
|
800
|
+
const pad = width - length;
|
|
801
|
+
if (align === "right")
|
|
802
|
+
return `${" ".repeat(pad)}${text}`;
|
|
803
|
+
if (align === "center") {
|
|
804
|
+
const left = Math.floor(pad / 2);
|
|
805
|
+
return `${" ".repeat(left)}${text}${" ".repeat(pad - left)}`;
|
|
806
|
+
}
|
|
807
|
+
return text;
|
|
808
|
+
}
|
|
809
|
+
function truncateText(text, width) {
|
|
810
|
+
if (visibleWidth(text) <= width)
|
|
811
|
+
return text;
|
|
812
|
+
if (width <= 1)
|
|
813
|
+
return "\u2026";
|
|
814
|
+
return `${[...text].slice(0, Math.max(0, width - 1)).join("")}\u2026`;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// packages/cli/src/app-opentui/runtime-resources.ts
|
|
818
|
+
class RuntimeResources {
|
|
819
|
+
disposers = [];
|
|
820
|
+
disposed = false;
|
|
821
|
+
add(disposer) {
|
|
822
|
+
if (this.disposed) {
|
|
823
|
+
disposer();
|
|
824
|
+
return () => {
|
|
825
|
+
return;
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
this.disposers.push(disposer);
|
|
829
|
+
return disposer;
|
|
830
|
+
}
|
|
831
|
+
setTimeout(callback, delayMs) {
|
|
832
|
+
const timer = setTimeout(() => {
|
|
833
|
+
this.remove(dispose);
|
|
834
|
+
callback();
|
|
835
|
+
}, delayMs);
|
|
836
|
+
const dispose = () => clearTimeout(timer);
|
|
837
|
+
this.add(dispose);
|
|
838
|
+
return timer;
|
|
839
|
+
}
|
|
840
|
+
setInterval(callback, delayMs) {
|
|
841
|
+
const timer = setInterval(callback, delayMs);
|
|
842
|
+
this.add(() => clearInterval(timer));
|
|
843
|
+
return timer;
|
|
844
|
+
}
|
|
845
|
+
clearTimeout(timer) {
|
|
846
|
+
if (timer)
|
|
847
|
+
clearTimeout(timer);
|
|
848
|
+
}
|
|
849
|
+
on(emitter, event, listener) {
|
|
850
|
+
emitter.on(event, listener);
|
|
851
|
+
this.add(() => {
|
|
852
|
+
if (typeof emitter.off === "function")
|
|
853
|
+
emitter.off(event, listener);
|
|
854
|
+
else
|
|
855
|
+
emitter.removeListener?.(event, listener);
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
disposeAll() {
|
|
859
|
+
if (this.disposed)
|
|
860
|
+
return;
|
|
861
|
+
this.disposed = true;
|
|
862
|
+
for (let index = this.disposers.length - 1;index >= 0; index -= 1) {
|
|
863
|
+
try {
|
|
864
|
+
this.disposers[index]?.();
|
|
865
|
+
} catch {}
|
|
866
|
+
}
|
|
867
|
+
this.disposers.length = 0;
|
|
868
|
+
}
|
|
869
|
+
remove(disposer) {
|
|
870
|
+
const index = this.disposers.indexOf(disposer);
|
|
871
|
+
if (index >= 0)
|
|
872
|
+
this.disposers.splice(index, 1);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// packages/cli/src/app-opentui/intent.ts
|
|
877
|
+
var NAV_SCENES = new Map([
|
|
878
|
+
["", "main"],
|
|
879
|
+
["main", "main"],
|
|
880
|
+
["home", "main"],
|
|
881
|
+
["fleet", "fleet"],
|
|
882
|
+
["runs", "fleet"],
|
|
883
|
+
["run", "fleet"],
|
|
884
|
+
["tasks", "tasks"],
|
|
885
|
+
["task", "tasks"],
|
|
886
|
+
["inbox", "inbox"],
|
|
887
|
+
["server", "server"],
|
|
888
|
+
["init", "init"],
|
|
889
|
+
["doctor", "doctor"],
|
|
890
|
+
["help", "help"]
|
|
891
|
+
]);
|
|
892
|
+
var TASK_VIEW_COMMANDS = new Set([
|
|
893
|
+
"",
|
|
894
|
+
"all",
|
|
895
|
+
"open",
|
|
896
|
+
"ready",
|
|
897
|
+
"blocked",
|
|
898
|
+
"closed",
|
|
899
|
+
"done",
|
|
900
|
+
"mine",
|
|
901
|
+
"me",
|
|
902
|
+
"sort",
|
|
903
|
+
"status",
|
|
904
|
+
"state",
|
|
905
|
+
"assignee",
|
|
906
|
+
"assigned",
|
|
907
|
+
"label",
|
|
908
|
+
"search",
|
|
909
|
+
"q"
|
|
910
|
+
]);
|
|
911
|
+
function isHelpCommand(command) {
|
|
912
|
+
return command === "help" || command === "--help" || command === "-h";
|
|
913
|
+
}
|
|
914
|
+
function isTaskViewCommand(command) {
|
|
915
|
+
return !command || TASK_VIEW_COMMANDS.has(command) || command.startsWith("-") && !isHelpCommand(command);
|
|
916
|
+
}
|
|
917
|
+
function intent(scene, argv, kind, payload, label, raw) {
|
|
918
|
+
return {
|
|
919
|
+
scene,
|
|
920
|
+
argv,
|
|
921
|
+
...raw ? { raw } : {},
|
|
922
|
+
action: {
|
|
923
|
+
kind,
|
|
924
|
+
...label ? { label } : {},
|
|
925
|
+
...payload ? { payload } : {}
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function commandRunIntent(argv, raw, reason) {
|
|
930
|
+
const command = argv.join(" ").trim();
|
|
931
|
+
return intent("command", argv, "command-run", { argv: [...argv], ...reason ? { reason } : {} }, command ? `Running rig ${command}` : "Running rig", raw);
|
|
932
|
+
}
|
|
933
|
+
function unsupportedPlainCliIntent(argv, raw, reason) {
|
|
934
|
+
return commandRunIntent(argv, raw, reason);
|
|
935
|
+
}
|
|
936
|
+
function firstValueAfter(args, option) {
|
|
937
|
+
const index = args.indexOf(option);
|
|
938
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
939
|
+
}
|
|
940
|
+
function firstNonOption(args) {
|
|
941
|
+
return args.find((part) => part.trim() && !part.startsWith("-"));
|
|
942
|
+
}
|
|
943
|
+
function inboxActionIntent(argv, command, rest, raw) {
|
|
944
|
+
const requestId = firstNonOption(rest);
|
|
945
|
+
if (command === "approve" || command === "approvals") {
|
|
946
|
+
return requestId ? intent("inbox", argv, "inbox-approve", { requestId }, `Approving ${requestId}`, raw) : intent("inbox", argv, "refresh", undefined, "Loading approvals", raw);
|
|
947
|
+
}
|
|
948
|
+
if (command === "reject") {
|
|
949
|
+
return requestId ? intent("inbox", argv, "inbox-reject", { requestId }, `Rejecting ${requestId}`, raw) : intent("inbox", argv, "refresh", undefined, "Loading approvals", raw);
|
|
950
|
+
}
|
|
951
|
+
if (command === "answer" || command === "inputs" || command === "respond") {
|
|
952
|
+
const answer = rest.slice(requestId ? 1 : 0).join(" ").trim();
|
|
953
|
+
return requestId && answer ? intent("inbox", argv, "inbox-answer", { requestId, answer }, `Answering ${requestId}`, raw) : intent("inbox", argv, "refresh", undefined, "Loading inbox", raw);
|
|
954
|
+
}
|
|
955
|
+
return intent("inbox", argv, "refresh", undefined, "Loading inbox", raw);
|
|
956
|
+
}
|
|
957
|
+
function taskViewPayload(command, rest) {
|
|
958
|
+
const normalized = command.toLowerCase();
|
|
959
|
+
const args = command ? [command, ...rest] : rest;
|
|
960
|
+
const sort = firstValueAfter(args, "--sort") ?? firstValueAfter(args, "-s");
|
|
961
|
+
const state = firstValueAfter(args, "--state");
|
|
962
|
+
const status = firstValueAfter(args, "--status");
|
|
963
|
+
const assignee = firstValueAfter(args, "--assignee") ?? firstValueAfter(args, "--assigned");
|
|
964
|
+
const label = firstValueAfter(args, "--label");
|
|
965
|
+
const search = firstValueAfter(args, "--search") ?? firstValueAfter(args, "-q");
|
|
966
|
+
const optionPayload = {
|
|
967
|
+
...sort ? { taskSort: sort } : {},
|
|
968
|
+
...state ? { taskState: state, taskFilterPreset: "custom" } : {},
|
|
969
|
+
...status ? { taskStatus: status, taskFilterPreset: "custom" } : {},
|
|
970
|
+
...assignee ? { taskAssignee: assignee, taskFilterPreset: "custom" } : {},
|
|
971
|
+
...label ? { taskLabel: label, taskFilterPreset: "custom" } : {},
|
|
972
|
+
...search ? { taskSearch: search, taskFilterPreset: "custom" } : {}
|
|
973
|
+
};
|
|
974
|
+
if (normalized === "sort") {
|
|
975
|
+
const value = firstNonOption(rest) ?? sort ?? "priority";
|
|
976
|
+
return { payload: { ...optionPayload, taskSort: value }, label: `Sorting tasks by ${value}` };
|
|
977
|
+
}
|
|
978
|
+
if (normalized === "status") {
|
|
979
|
+
const value = firstNonOption(rest) ?? status ?? "open";
|
|
980
|
+
return { payload: { ...optionPayload, taskStatus: value, taskFilterPreset: "custom" }, label: `Filtering tasks by status ${value}` };
|
|
981
|
+
}
|
|
982
|
+
if (normalized === "state") {
|
|
983
|
+
const value = firstNonOption(rest) ?? state ?? "open";
|
|
984
|
+
return { payload: { ...optionPayload, taskState: value, taskFilterPreset: "custom" }, label: `Filtering tasks by state ${value}` };
|
|
985
|
+
}
|
|
986
|
+
if (normalized === "assignee" || normalized === "assigned" || normalized === "mine" || normalized === "me") {
|
|
987
|
+
const value = normalized === "mine" || normalized === "me" ? "me" : firstNonOption(rest) ?? assignee ?? "me";
|
|
988
|
+
return { payload: { ...optionPayload, taskAssignee: value, taskState: "open", taskFilterPreset: value === "me" ? "mine" : "custom" }, label: value === "me" ? "Filtering my tasks" : `Filtering tasks for ${value}` };
|
|
989
|
+
}
|
|
990
|
+
if (normalized === "label") {
|
|
991
|
+
const value = firstNonOption(rest) ?? label;
|
|
992
|
+
return { payload: { ...optionPayload, ...value ? { taskLabel: value } : {}, taskFilterPreset: "custom" }, label: value ? `Filtering tasks labeled ${value}` : "Filtering tasks by label" };
|
|
993
|
+
}
|
|
994
|
+
if (normalized === "search" || normalized === "q") {
|
|
995
|
+
const value = rest.join(" ").trim() || search;
|
|
996
|
+
return { payload: { ...optionPayload, ...value ? { taskSearch: value } : {}, taskFilterPreset: "custom" }, label: value ? `Searching tasks for ${value}` : "Searching tasks" };
|
|
997
|
+
}
|
|
998
|
+
if (["all", "open", "ready", "blocked", "closed", "done"].includes(normalized)) {
|
|
999
|
+
return { payload: { ...optionPayload, taskFilterPreset: normalized === "done" ? "closed" : normalized }, label: `Filtering tasks: ${normalized}` };
|
|
1000
|
+
}
|
|
1001
|
+
if (Object.keys(optionPayload).length > 0)
|
|
1002
|
+
return { payload: optionPayload, label: "Filtering tasks" };
|
|
1003
|
+
return { label: "Loading tasks" };
|
|
301
1004
|
}
|
|
302
|
-
function
|
|
303
|
-
|
|
1005
|
+
function taskViewIntent(argv, command = "", rest = [], raw) {
|
|
1006
|
+
const { payload, label } = taskViewPayload(command, rest);
|
|
1007
|
+
return intent("tasks", argv, "refresh", payload, label, raw);
|
|
304
1008
|
}
|
|
305
|
-
function
|
|
306
|
-
|
|
1009
|
+
function intentFromArgv(argv) {
|
|
1010
|
+
const [group = "", command = "", ...rest] = argv;
|
|
1011
|
+
const normalizedGroup = group.toLowerCase();
|
|
1012
|
+
const normalizedCommand = command.toLowerCase();
|
|
1013
|
+
if (!normalizedGroup)
|
|
1014
|
+
return intent("main", argv, "none", undefined, "Project menu");
|
|
1015
|
+
if (normalizedGroup === "--help" || normalizedGroup === "-h" || normalizedGroup === "help") {
|
|
1016
|
+
return argv.length === 1 ? intent("help", argv, "none", undefined, "Help") : unsupportedPlainCliIntent(argv);
|
|
1017
|
+
}
|
|
1018
|
+
if (normalizedGroup === "task") {
|
|
1019
|
+
if (normalizedCommand === "run" || normalizedCommand === "ready" || normalizedCommand === "status")
|
|
1020
|
+
return commandRunIntent(argv);
|
|
1021
|
+
if (!isTaskViewCommand(normalizedCommand))
|
|
1022
|
+
return commandRunIntent(argv);
|
|
1023
|
+
return taskViewIntent(argv, normalizedCommand, rest);
|
|
1024
|
+
}
|
|
1025
|
+
if (normalizedGroup === "tasks") {
|
|
1026
|
+
if (!isTaskViewCommand(normalizedCommand))
|
|
1027
|
+
return unsupportedPlainCliIntent(argv);
|
|
1028
|
+
return taskViewIntent(argv, normalizedCommand, rest);
|
|
1029
|
+
}
|
|
1030
|
+
if (normalizedGroup === "run") {
|
|
1031
|
+
if (normalizedCommand === "attach") {
|
|
1032
|
+
const runId = firstNonOption(rest);
|
|
1033
|
+
const nativeOnly = Boolean(runId) && rest.filter((part) => part.trim()).length === 1;
|
|
1034
|
+
return nativeOnly ? intent("handoff", argv, "run-attach", { runId }, `Attaching ${runId}`) : commandRunIntent(argv);
|
|
1035
|
+
}
|
|
1036
|
+
if (!normalizedCommand || normalizedCommand === "list" || normalizedCommand === "status")
|
|
1037
|
+
return intent("fleet", argv, "refresh", undefined, "Loading fleet");
|
|
1038
|
+
if (normalizedCommand === "stop") {
|
|
1039
|
+
const runId = firstNonOption(rest);
|
|
1040
|
+
return runId && rest.filter((part) => part.trim()).length === 1 ? intent("fleet", argv, "run-stop", { runId }, "Stopping run") : commandRunIntent(argv);
|
|
1041
|
+
}
|
|
1042
|
+
return commandRunIntent(argv);
|
|
1043
|
+
}
|
|
1044
|
+
if (normalizedGroup === "server") {
|
|
1045
|
+
if (!normalizedCommand || normalizedCommand === "status")
|
|
1046
|
+
return intent("server", argv, "refresh", undefined, normalizedCommand ? "Checking server" : "Loading server");
|
|
1047
|
+
return unsupportedPlainCliIntent(argv, undefined, `server ${normalizedCommand} is not executed in the shell; exit and run it in plain mode.`);
|
|
1048
|
+
}
|
|
1049
|
+
if (normalizedGroup === "github") {
|
|
1050
|
+
if (normalizedCommand === "auth" && (rest[0]?.toLowerCase() ?? "status") === "status" && rest.length <= 1) {
|
|
1051
|
+
return intent("server", argv, "refresh", undefined, "Checking GitHub auth");
|
|
1052
|
+
}
|
|
1053
|
+
return unsupportedPlainCliIntent(argv, undefined, "github auth mutations are not executed in the shell; exit and run them in plain mode.");
|
|
1054
|
+
}
|
|
1055
|
+
if (normalizedGroup === "init")
|
|
1056
|
+
return commandRunIntent(argv);
|
|
1057
|
+
if (normalizedGroup === "doctor") {
|
|
1058
|
+
return normalizedCommand ? commandRunIntent(argv) : intent("doctor", argv, "doctor-run", undefined, "Running doctor");
|
|
1059
|
+
}
|
|
1060
|
+
if (normalizedGroup === "inbox") {
|
|
1061
|
+
if (normalizedCommand === "watch")
|
|
1062
|
+
return commandRunIntent(argv);
|
|
1063
|
+
if ((normalizedCommand === "approvals" || normalizedCommand === "inputs") && rest.length > 0)
|
|
1064
|
+
return commandRunIntent(argv);
|
|
1065
|
+
return normalizedCommand && normalizedCommand !== "approvals" && normalizedCommand !== "inputs" ? commandRunIntent(argv) : inboxActionIntent(argv, normalizedCommand, rest);
|
|
1066
|
+
}
|
|
1067
|
+
if (normalizedGroup === "attach") {
|
|
1068
|
+
const attachArgs = [command, ...rest].filter((part) => part.trim());
|
|
1069
|
+
const runId = firstNonOption(attachArgs);
|
|
1070
|
+
return runId && attachArgs.length === 1 ? intent("handoff", argv, "run-attach", { runId }, `Attaching ${runId}`) : commandRunIntent(argv);
|
|
1071
|
+
}
|
|
1072
|
+
const nav = NAV_SCENES.get(normalizedGroup);
|
|
1073
|
+
if (nav && argv.length === 1)
|
|
1074
|
+
return intent(nav, argv, "navigate", undefined, `Opening ${nav}`);
|
|
1075
|
+
return commandRunIntent(argv);
|
|
307
1076
|
}
|
|
308
1077
|
|
|
309
1078
|
// packages/cli/src/app-opentui/state.ts
|
|
@@ -431,7 +1200,7 @@ import {
|
|
|
431
1200
|
dim as otuiDim,
|
|
432
1201
|
fg as otuiFg,
|
|
433
1202
|
t,
|
|
434
|
-
TextAttributes
|
|
1203
|
+
TextAttributes as TextAttributes2
|
|
435
1204
|
} from "@opentui/core";
|
|
436
1205
|
var RIG_UI = {
|
|
437
1206
|
bg: "#070809",
|
|
@@ -462,16 +1231,175 @@ var styles = {
|
|
|
462
1231
|
yellow: otuiFg(RIG_UI.yellow),
|
|
463
1232
|
magenta: otuiFg(RIG_UI.magenta)
|
|
464
1233
|
};
|
|
1234
|
+
// packages/cli/src/app-opentui/adapters/tasks.ts
|
|
1235
|
+
function rawRecord(record, key) {
|
|
1236
|
+
const direct = record[key];
|
|
1237
|
+
if (direct !== undefined && direct !== null)
|
|
1238
|
+
return direct;
|
|
1239
|
+
const raw = record.raw;
|
|
1240
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw[key] : undefined;
|
|
1241
|
+
}
|
|
1242
|
+
function optionalString(record, keys) {
|
|
1243
|
+
for (const key of keys) {
|
|
1244
|
+
const value = rawRecord(record, key);
|
|
1245
|
+
if (typeof value === "string" && value.trim())
|
|
1246
|
+
return value.trim();
|
|
1247
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
1248
|
+
return String(value);
|
|
1249
|
+
}
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
function activeRunForRecord(record) {
|
|
1253
|
+
const value = rawRecord(record, "activeRun");
|
|
1254
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1255
|
+
return;
|
|
1256
|
+
const activeRun = value;
|
|
1257
|
+
const runId = optionalString(activeRun, ["runId", "id"]);
|
|
1258
|
+
if (!runId)
|
|
1259
|
+
return;
|
|
1260
|
+
const status = optionalString(activeRun, ["status", "state"]);
|
|
1261
|
+
const stage = optionalString(activeRun, ["stage", "phase"]);
|
|
1262
|
+
return {
|
|
1263
|
+
runId,
|
|
1264
|
+
...status ? { status } : {},
|
|
1265
|
+
...stage ? { stage } : {}
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
function activeRunIdFromTaskLike(task) {
|
|
1269
|
+
return activeRunForRecord(task)?.runId;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// packages/cli/src/app-opentui/render/graphics.ts
|
|
1273
|
+
import { FrameBufferRenderable, RGBA as RGBA2 } from "@opentui/core";
|
|
1274
|
+
|
|
1275
|
+
// packages/cli/src/app-opentui/render/ascii-fleet.ts
|
|
1276
|
+
var LEAD_DRONE = [
|
|
1277
|
+
" .-=-. .-=-. ",
|
|
1278
|
+
" ( !!! ) ( !!! ) ",
|
|
1279
|
+
" '-=-'._ _.'-=-' ",
|
|
1280
|
+
" '._ _.' ",
|
|
1281
|
+
" '=$$$$$$$=.' ",
|
|
1282
|
+
" =$$$$$$$$$$$= ",
|
|
1283
|
+
" $$$@@@@@@@@@@$$$ ",
|
|
1284
|
+
" $$$@@ @@$$$ ",
|
|
1285
|
+
" $$@ ? @$$$ ",
|
|
1286
|
+
" $$$@ '-' @$$$ ",
|
|
1287
|
+
" $$$@@ @@$$$ ",
|
|
1288
|
+
" $$$@@@@@@@@@@$$$ ",
|
|
1289
|
+
" =$$$$$$$$$$$= ",
|
|
1290
|
+
" '=$$$$$$$=.' ",
|
|
1291
|
+
" _.' '._ ",
|
|
1292
|
+
" .-=-.' '.-=-. ",
|
|
1293
|
+
" ( !!! ) ( !!! ) ",
|
|
1294
|
+
" '-=-' '-=-' "
|
|
1295
|
+
];
|
|
1296
|
+
var MINI_DRONE = [
|
|
1297
|
+
"(!!!) (!!!)",
|
|
1298
|
+
" \\%==%/ ",
|
|
1299
|
+
" %%?%% ",
|
|
1300
|
+
" /%==%\\ ",
|
|
1301
|
+
"(!!!) (!!!)"
|
|
1302
|
+
];
|
|
1303
|
+
var ROTORS = ["---", "\\\\\\", "|||", "///"];
|
|
1304
|
+
var FLEET_GRID_WIDTH = 52;
|
|
1305
|
+
var FLEET_GRID_HEIGHT = 27;
|
|
1306
|
+
var FLEET = [
|
|
1307
|
+
{ art: MINI_DRONE, x: 0, y: 2, amp: 1, speed: 0.05, phase: 0, dx: 1, driftSpeed: 0.02 },
|
|
1308
|
+
{ art: MINI_DRONE, x: 39, y: 2, amp: 1, speed: 0.044, phase: 2.1, dx: 1, driftSpeed: 0.017 },
|
|
1309
|
+
{ art: MINI_DRONE, x: 1, y: 20, amp: 1, speed: 0.048, phase: 4.2, dx: 1, driftSpeed: 0.023 },
|
|
1310
|
+
{ art: MINI_DRONE, x: 38, y: 20, amp: 1, speed: 0.041, phase: 1.3, dx: 1, driftSpeed: 0.015 },
|
|
1311
|
+
{ art: LEAD_DRONE, x: 10, y: 5, amp: 2, speed: 0.04, phase: 0, dx: 0, driftSpeed: 0.011 }
|
|
1312
|
+
];
|
|
1313
|
+
function fleetRows(tick, options = {}) {
|
|
1314
|
+
const animate = options.animate ?? true;
|
|
1315
|
+
const t2 = animate ? tick : 0;
|
|
1316
|
+
const blade = ROTORS[Math.floor(t2 / 4) % ROTORS.length];
|
|
1317
|
+
const pulse = Math.sin(t2 * 0.07);
|
|
1318
|
+
const eye = pulse > 0.45 ? "@" : pulse > -0.1 ? "o" : ".";
|
|
1319
|
+
const grid = Array.from({ length: FLEET_GRID_HEIGHT }, () => Array.from({ length: FLEET_GRID_WIDTH }, () => " "));
|
|
1320
|
+
for (const drone of FLEET) {
|
|
1321
|
+
const yOffset = drone.y + Math.round(drone.amp * Math.sin(t2 * drone.speed + drone.phase));
|
|
1322
|
+
const xOffset = drone.x + Math.round(drone.dx * Math.sin(t2 * drone.driftSpeed + drone.phase));
|
|
1323
|
+
for (let row = 0;row < drone.art.length; row += 1) {
|
|
1324
|
+
const source = drone.art[row];
|
|
1325
|
+
const targetY = yOffset + row;
|
|
1326
|
+
if (targetY < 0 || targetY >= FLEET_GRID_HEIGHT)
|
|
1327
|
+
continue;
|
|
1328
|
+
for (let col = 0;col < source.length; col += 1) {
|
|
1329
|
+
let char = source[col];
|
|
1330
|
+
if (char === " ")
|
|
1331
|
+
continue;
|
|
1332
|
+
const targetX = xOffset + col;
|
|
1333
|
+
if (targetX < 0 || targetX >= FLEET_GRID_WIDTH)
|
|
1334
|
+
continue;
|
|
1335
|
+
if (source.slice(col, col + 3) === "!!!") {
|
|
1336
|
+
for (let rotorIndex = 0;rotorIndex < 3; rotorIndex += 1) {
|
|
1337
|
+
if (targetX + rotorIndex < FLEET_GRID_WIDTH)
|
|
1338
|
+
grid[targetY][targetX + rotorIndex] = blade[rotorIndex];
|
|
1339
|
+
}
|
|
1340
|
+
col += 2;
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
if (char === "?")
|
|
1344
|
+
char = eye;
|
|
1345
|
+
grid[targetY][targetX] = char;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
return grid.map((row) => row.join("").replace(/\s+$/, ""));
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// packages/cli/src/app-opentui/render/panel-layout.ts
|
|
1353
|
+
function panelCellRect(layout, panel) {
|
|
1354
|
+
const left = Math.max(0, Math.min(layout.width - 1, layout.centerLeft + (panel.left ?? 0)));
|
|
1355
|
+
const desiredWidth = panel.width ?? layout.centerWidth;
|
|
1356
|
+
const width = Math.max(1, Math.min(layout.width - left, desiredWidth));
|
|
1357
|
+
const top = Math.max(0, Math.min(layout.height - 1, layout.centerTop + panel.top));
|
|
1358
|
+
const height = Math.max(1, Math.min(layout.height - top, panel.height));
|
|
1359
|
+
if (width <= 3 || height <= 3)
|
|
1360
|
+
return null;
|
|
1361
|
+
const right = left + width - 1;
|
|
1362
|
+
const bottom = top + height - 1;
|
|
1363
|
+
const headerHeight = panel.chrome === "ad-terminal" ? Math.max(3, Math.min(height - 2, panel.headerHeight ?? 3)) : 0;
|
|
1364
|
+
const dividerY = headerHeight ? Math.min(bottom - 1, top + headerHeight) : top;
|
|
1365
|
+
return { left, top, width, height, right, bottom, headerHeight, dividerY };
|
|
1366
|
+
}
|
|
1367
|
+
function panelContentRect(layout, panel) {
|
|
1368
|
+
const rect = panelCellRect(layout, panel);
|
|
1369
|
+
if (!rect)
|
|
1370
|
+
return null;
|
|
1371
|
+
const chromeInset = panel.chrome === "ad-terminal" ? 1 : 0;
|
|
1372
|
+
const left = rect.left + chromeInset;
|
|
1373
|
+
const top = rect.top + rect.headerHeight + chromeInset;
|
|
1374
|
+
const width = Math.max(1, Math.min(layout.width - left, rect.width - chromeInset * 2));
|
|
1375
|
+
const height = Math.max(1, Math.min(layout.height - top, rect.height - rect.headerHeight - chromeInset * 2));
|
|
1376
|
+
const paddingX = panel.paddingX ?? (panel.chrome === "ad-terminal" ? 5 : 2);
|
|
1377
|
+
const paddingY = panel.paddingY ?? (panel.chrome === "ad-terminal" ? 2 : 1);
|
|
1378
|
+
const contentWidth = Math.max(1, width - paddingX * 2 - 1);
|
|
1379
|
+
return { left, top, width, height, paddingX, paddingY, contentWidth };
|
|
1380
|
+
}
|
|
1381
|
+
function panelPixelPlacement(layout, panel, size, minPixels = 8) {
|
|
1382
|
+
const cellW = size.width / layout.width;
|
|
1383
|
+
const cellH = size.height / layout.height;
|
|
1384
|
+
const leftCells = layout.centerLeft + (panel.left ?? 0);
|
|
1385
|
+
const topCells = layout.centerTop + panel.top;
|
|
1386
|
+
const widthCells = panel.width ?? layout.centerWidth;
|
|
1387
|
+
const heightCells = panel.height;
|
|
1388
|
+
const left = Math.round(leftCells * cellW);
|
|
1389
|
+
const top = Math.round(topCells * cellH);
|
|
1390
|
+
const width = Math.round(widthCells * cellW);
|
|
1391
|
+
const height = Math.round(heightCells * cellH);
|
|
1392
|
+
return width > minPixels && height > minPixels ? { left, top, width, height, leftCells, topCells, widthCells, heightCells } : null;
|
|
1393
|
+
}
|
|
465
1394
|
|
|
466
1395
|
// packages/cli/src/app-opentui/render/graphics.ts
|
|
467
|
-
|
|
468
|
-
var
|
|
469
|
-
var
|
|
470
|
-
var
|
|
471
|
-
var
|
|
472
|
-
var
|
|
473
|
-
var
|
|
474
|
-
var PANEL_TEXT_DIM = RGBA.fromInts(108, 110, 121, 255);
|
|
1396
|
+
var TRANSPARENT = RGBA2.fromValues(0, 0, 0, 0);
|
|
1397
|
+
var BACKDROP = RGBA2.fromHex(RIG_UI.bg);
|
|
1398
|
+
var PANEL_BG = RGBA2.fromInts(16, 17, 21, 184);
|
|
1399
|
+
var PANEL_HEADER_BG = RGBA2.fromInts(16, 17, 21, 188);
|
|
1400
|
+
var PANEL_LINE = RGBA2.fromInts(255, 255, 255, 20);
|
|
1401
|
+
var PANEL_LINE_DIM = RGBA2.fromInts(255, 255, 255, 8);
|
|
1402
|
+
var PANEL_TEXT_DIM = RGBA2.fromInts(108, 110, 121, 255);
|
|
475
1403
|
var AC_RGB = [204, 255, 77];
|
|
476
1404
|
var C2_RGB = [86, 216, 255];
|
|
477
1405
|
var MG_RGB = [255, 121, 176];
|
|
@@ -576,7 +1504,7 @@ function paletteColor(ac, c2, mg, ink) {
|
|
|
576
1504
|
const b = (AC_RGB[2] * ac + C2_RGB[2] * c2 + MG_RGB[2] * mg + INK_RGB[2] * ink) / total;
|
|
577
1505
|
const intensity = Math.min(1, total / 4);
|
|
578
1506
|
const lift = 0.34 + intensity * 0.66;
|
|
579
|
-
return
|
|
1507
|
+
return RGBA2.fromInts(Math.round(r * lift), Math.round(g * lift), Math.round(b * lift), 255);
|
|
580
1508
|
}
|
|
581
1509
|
function clearCanvas(canvas) {
|
|
582
1510
|
canvas.ac.fill(0);
|
|
@@ -885,14 +1813,6 @@ function resizeGraphicsLayer(layer, layout) {
|
|
|
885
1813
|
function clearGraphicsLayer(layer) {
|
|
886
1814
|
layer.frameBuffer.clear(BACKDROP);
|
|
887
1815
|
}
|
|
888
|
-
function panelRect(layout, panel) {
|
|
889
|
-
const left = Math.max(0, Math.min(layout.width - 1, layout.centerLeft + (panel.left ?? 0)));
|
|
890
|
-
const desiredWidth = panel.width ?? layout.centerWidth;
|
|
891
|
-
const width = Math.max(1, Math.min(layout.width - left, desiredWidth));
|
|
892
|
-
const top = Math.max(0, Math.min(layout.height - 1, layout.centerTop + panel.top));
|
|
893
|
-
const height = Math.max(1, Math.min(layout.height - top, panel.height));
|
|
894
|
-
return width > 3 && height > 3 ? { left, top, width, height } : null;
|
|
895
|
-
}
|
|
896
1816
|
function writeCell(layer, x, y, char, fg, bg) {
|
|
897
1817
|
layer.frameBuffer.setCellWithAlphaBlending(x, y, char, fg, bg);
|
|
898
1818
|
}
|
|
@@ -915,14 +1835,10 @@ function drawRoundedFill(layer, left, top, right, bottom, dividerY) {
|
|
|
915
1835
|
drawCornerCut(layer, right, bottom, "\u2598", PANEL_BG);
|
|
916
1836
|
}
|
|
917
1837
|
function drawAdTerminalPanel(layer, layout, panel) {
|
|
918
|
-
const rect =
|
|
1838
|
+
const rect = panelCellRect(layout, panel);
|
|
919
1839
|
if (!rect)
|
|
920
1840
|
return;
|
|
921
|
-
const { left, top, width,
|
|
922
|
-
const right = left + width - 1;
|
|
923
|
-
const bottom = top + height - 1;
|
|
924
|
-
const headerHeight = Math.max(3, Math.min(height - 2, panel.headerHeight ?? 3));
|
|
925
|
-
const dividerY = Math.min(bottom - 1, top + headerHeight);
|
|
1841
|
+
const { left, top, width, right, bottom, headerHeight, dividerY } = rect;
|
|
926
1842
|
drawRoundedFill(layer, left, top, right, bottom, dividerY);
|
|
927
1843
|
if (dividerY > top && dividerY < bottom) {
|
|
928
1844
|
for (let x = left + 2;x < right - 1; x += 1) {
|
|
@@ -941,14 +1857,62 @@ function drawPanelChrome(layer, layout, panels) {
|
|
|
941
1857
|
drawAdTerminalPanel(layer, layout, panel);
|
|
942
1858
|
}
|
|
943
1859
|
}
|
|
944
|
-
function
|
|
945
|
-
|
|
1860
|
+
function staticFleetTick(scene) {
|
|
1861
|
+
let hash = 0;
|
|
1862
|
+
for (let index = 0;index < scene.length; index += 1)
|
|
1863
|
+
hash = hash * 33 + scene.charCodeAt(index) >>> 0;
|
|
1864
|
+
return 11 + hash % 97;
|
|
1865
|
+
}
|
|
1866
|
+
function asciiFleetColor(char, row) {
|
|
1867
|
+
if (char === "@" || char === "$" || char === "o")
|
|
1868
|
+
return RGBA2.fromHex(RIG_UI.lime);
|
|
1869
|
+
if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
|
|
1870
|
+
return RGBA2.fromHex(RIG_UI.cyan);
|
|
1871
|
+
if (char === "." || char === "'" || char === "_" || row < 3 || row > FLEET_GRID_HEIGHT - 4)
|
|
1872
|
+
return RGBA2.fromInts(108, 110, 121, 170);
|
|
1873
|
+
return RGBA2.fromHex(RIG_UI.limeDim);
|
|
1874
|
+
}
|
|
1875
|
+
function drawAmbientField(layer, layout, _tick, scene = "fleet") {
|
|
1876
|
+
const fb = layer.frameBuffer;
|
|
1877
|
+
fb.clear(BACKDROP);
|
|
1878
|
+
const rows = fleetRows(staticFleetTick(scene), { animate: true });
|
|
1879
|
+
const left = Math.floor((layout.width - FLEET_GRID_WIDTH) / 2);
|
|
1880
|
+
const top = Math.floor((layout.height - FLEET_GRID_HEIGHT) / 2);
|
|
1881
|
+
for (let row = 0;row < rows.length; row += 1) {
|
|
1882
|
+
const y = top + row;
|
|
1883
|
+
if (y < 0 || y >= layout.height)
|
|
1884
|
+
continue;
|
|
1885
|
+
const line = rows[row];
|
|
1886
|
+
for (let col = 0;col < line.length; col += 1) {
|
|
1887
|
+
const x = left + col;
|
|
1888
|
+
if (x < 0 || x >= layout.width)
|
|
1889
|
+
continue;
|
|
1890
|
+
const char = line[col];
|
|
1891
|
+
if (char === " ")
|
|
1892
|
+
continue;
|
|
1893
|
+
fb.setCellWithAlphaBlending(x, y, char, asciiFleetColor(char, row), TRANSPARENT);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
946
1896
|
}
|
|
947
1897
|
|
|
948
1898
|
// packages/cli/src/app-opentui/render/image-visual-layer.ts
|
|
949
1899
|
import { Worker } from "worker_threads";
|
|
950
1900
|
import { deflateSync } from "zlib";
|
|
951
|
-
import { allocateImageId, deleteKittyImage
|
|
1901
|
+
import { allocateImageId, deleteKittyImage } from "@earendil-works/pi-tui";
|
|
1902
|
+
|
|
1903
|
+
// packages/cli/src/app-opentui/terminal-capabilities.ts
|
|
1904
|
+
import { getCapabilities } from "@earendil-works/pi-tui";
|
|
1905
|
+
function getRigTerminalCapabilities() {
|
|
1906
|
+
const imageProtocol = getCapabilities().images;
|
|
1907
|
+
const supportsKittyImages = imageProtocol === "kitty";
|
|
1908
|
+
return {
|
|
1909
|
+
imageProtocol,
|
|
1910
|
+
supportsKittyImages,
|
|
1911
|
+
visualMode: supportsKittyImages ? "kitty-images" : "framebuffer-fallback"
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// packages/cli/src/app-opentui/render/image-visual-layer.ts
|
|
952
1916
|
var BG = [7, 8, 9, 255];
|
|
953
1917
|
var PANEL = [16, 17, 21, 184];
|
|
954
1918
|
var PANEL_HEADER = [16, 17, 21, 190];
|
|
@@ -962,12 +1926,10 @@ var PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
|
962
1926
|
var CRC_TABLE = makeCrcTable();
|
|
963
1927
|
var MAX_IMAGE_BYTES = 48 * 1024 * 1024;
|
|
964
1928
|
var KITTY_CHUNK_SIZE = 4096;
|
|
965
|
-
var PANEL_BLUR_SCALE =
|
|
1929
|
+
var PANEL_BLUR_SCALE = 1;
|
|
966
1930
|
var PANEL_MASK_CACHE_LIMIT = 16;
|
|
967
1931
|
var ZLIB_LEVEL = 1;
|
|
968
1932
|
var DEFAULT_IMAGE_RESOLUTION_SCALE = 1;
|
|
969
|
-
var DEFAULT_DRONE_LAYER_SCALE = 0.18;
|
|
970
|
-
var DEFAULT_IMAGE_FPS = 24;
|
|
971
1933
|
var panelMaskCache = new Map;
|
|
972
1934
|
var BG_WORD = rgbaWord(BG);
|
|
973
1935
|
function imageTransport() {
|
|
@@ -982,24 +1944,6 @@ function imageResolutionScale() {
|
|
|
982
1944
|
return Math.max(0.25, Math.min(1, raw));
|
|
983
1945
|
return DEFAULT_IMAGE_RESOLUTION_SCALE;
|
|
984
1946
|
}
|
|
985
|
-
function droneLayerScale() {
|
|
986
|
-
const raw = Number.parseFloat(process.env.RIG_OPENTUI_DRONE_SCALE ?? "");
|
|
987
|
-
if (Number.isFinite(raw) && raw > 0)
|
|
988
|
-
return Math.max(0.15, Math.min(1, raw));
|
|
989
|
-
return DEFAULT_DRONE_LAYER_SCALE;
|
|
990
|
-
}
|
|
991
|
-
function scaledPixelSize(size, scale, layout) {
|
|
992
|
-
return {
|
|
993
|
-
width: Math.max(layout.width, Math.floor(size.width * scale)),
|
|
994
|
-
height: Math.max(layout.height, Math.floor(size.height * scale))
|
|
995
|
-
};
|
|
996
|
-
}
|
|
997
|
-
function imageFps() {
|
|
998
|
-
const raw = Number.parseFloat(process.env.RIG_OPENTUI_IMAGE_FPS ?? "");
|
|
999
|
-
if (Number.isFinite(raw) && raw > 0)
|
|
1000
|
-
return Math.max(10, Math.min(60, raw));
|
|
1001
|
-
return DEFAULT_IMAGE_FPS;
|
|
1002
|
-
}
|
|
1003
1947
|
function rgbaWord(color) {
|
|
1004
1948
|
return (color[3] << 24 | color[2] << 16 | color[1] << 8 | color[0]) >>> 0;
|
|
1005
1949
|
}
|
|
@@ -1015,6 +1959,109 @@ function fillRgba(data, color, word = rgbaWord(color)) {
|
|
|
1015
1959
|
data[i + 3] = color[3];
|
|
1016
1960
|
}
|
|
1017
1961
|
}
|
|
1962
|
+
var ASCII_GLYPHS = {
|
|
1963
|
+
".": ["00000", "00000", "00000", "00000", "00000", "00100", "00100"],
|
|
1964
|
+
"'": ["00100", "00100", "00000", "00000", "00000", "00000", "00000"],
|
|
1965
|
+
"-": ["00000", "00000", "00000", "11111", "00000", "00000", "00000"],
|
|
1966
|
+
_: ["00000", "00000", "00000", "00000", "00000", "00000", "11111"],
|
|
1967
|
+
"=": ["00000", "11111", "00000", "00000", "11111", "00000", "00000"],
|
|
1968
|
+
"|": ["00100", "00100", "00100", "00100", "00100", "00100", "00100"],
|
|
1969
|
+
"/": ["00001", "00010", "00010", "00100", "01000", "01000", "10000"],
|
|
1970
|
+
"\\": ["10000", "01000", "01000", "00100", "00010", "00010", "00001"],
|
|
1971
|
+
"(": ["00010", "00100", "01000", "01000", "01000", "00100", "00010"],
|
|
1972
|
+
")": ["01000", "00100", "00010", "00010", "00010", "00100", "01000"],
|
|
1973
|
+
"!": ["00100", "00100", "00100", "00100", "00100", "00000", "00100"],
|
|
1974
|
+
"%": ["11001", "11010", "00100", "01000", "10110", "00110", "00000"],
|
|
1975
|
+
$: ["00100", "11110", "10100", "11110", "00101", "11110", "00100"],
|
|
1976
|
+
"@": ["01110", "10001", "10111", "10101", "10111", "10000", "01111"],
|
|
1977
|
+
o: ["00000", "01110", "10001", "10001", "10001", "01110", "00000"],
|
|
1978
|
+
"*": ["00100", "10101", "01110", "11111", "01110", "10101", "00100"]
|
|
1979
|
+
};
|
|
1980
|
+
function sceneStaticTick(scene) {
|
|
1981
|
+
let hash = 0;
|
|
1982
|
+
for (let index = 0;index < scene.length; index += 1)
|
|
1983
|
+
hash = hash * 33 + scene.charCodeAt(index) >>> 0;
|
|
1984
|
+
return 11 + hash % 97;
|
|
1985
|
+
}
|
|
1986
|
+
function charColor(char, row, totalRows) {
|
|
1987
|
+
if (char === "@" || char === "$" || char === "o")
|
|
1988
|
+
return LIME;
|
|
1989
|
+
if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
|
|
1990
|
+
return CYAN;
|
|
1991
|
+
if (char === "." || char === "'" || char === "_" || row < 3 || row > totalRows - 4)
|
|
1992
|
+
return INK_DIM;
|
|
1993
|
+
if (char === "-" || char === "=" || char === "(" || char === ")")
|
|
1994
|
+
return LIME_DIM;
|
|
1995
|
+
return MAGENTA;
|
|
1996
|
+
}
|
|
1997
|
+
function fillRectAlpha(data, width, height, left, top, rectWidth, rectHeight, color, alpha) {
|
|
1998
|
+
const x0 = Math.max(0, Math.floor(left));
|
|
1999
|
+
const y0 = Math.max(0, Math.floor(top));
|
|
2000
|
+
const x1 = Math.min(width, Math.ceil(left + rectWidth));
|
|
2001
|
+
const y1 = Math.min(height, Math.ceil(top + rectHeight));
|
|
2002
|
+
for (let y = y0;y < y1; y += 1) {
|
|
2003
|
+
for (let x = x0;x < x1; x += 1)
|
|
2004
|
+
blendOver(data, (y * width + x) * 4, color, alpha);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
function drawAsciiGlyph(data, width, height, x, y, cellWidth, cellHeight, char, color, alpha) {
|
|
2008
|
+
const pattern = ASCII_GLYPHS[char] ?? ASCII_GLYPHS["*"];
|
|
2009
|
+
const glyphWidth = Math.max(1, cellWidth * 0.56);
|
|
2010
|
+
const glyphHeight = Math.max(1, cellHeight * 0.7);
|
|
2011
|
+
const originX = x + (cellWidth - glyphWidth) * 0.5;
|
|
2012
|
+
const originY = y + (cellHeight - glyphHeight) * 0.5;
|
|
2013
|
+
const pixelWidth = Math.max(1, glyphWidth / pattern[0].length);
|
|
2014
|
+
const pixelHeight = Math.max(1, glyphHeight / pattern.length);
|
|
2015
|
+
for (let row = 0;row < pattern.length; row += 1) {
|
|
2016
|
+
const bits = pattern[row];
|
|
2017
|
+
for (let col = 0;col < bits.length; col += 1) {
|
|
2018
|
+
if (bits[col] !== "1")
|
|
2019
|
+
continue;
|
|
2020
|
+
fillRectAlpha(data, width, height, originX + col * pixelWidth, originY + row * pixelHeight, pixelWidth * 0.72, pixelHeight * 0.72, color, alpha);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
function drawStaticAsciiFleetBackground(data, width, height, scene, layout) {
|
|
2025
|
+
const rows = fleetRows(sceneStaticTick(scene), { animate: true });
|
|
2026
|
+
const cellWidth = width / Math.max(1, layout.width);
|
|
2027
|
+
const cellHeight = height / Math.max(1, layout.height);
|
|
2028
|
+
const leftCells = Math.floor((layout.width - FLEET_GRID_WIDTH) / 2);
|
|
2029
|
+
const topCells = Math.floor((layout.height - FLEET_GRID_HEIGHT) / 2);
|
|
2030
|
+
const fleetLeft = leftCells * cellWidth;
|
|
2031
|
+
const fleetTop = topCells * cellHeight;
|
|
2032
|
+
drawGlow2(data, width, height, width * 0.5, height * 0.5, Math.min(width, height) * 0.4, LIME, 0.03);
|
|
2033
|
+
drawGlow2(data, width, height, width * 0.18, height * 0.22, Math.min(width, height) * 0.28, CYAN, 0.022);
|
|
2034
|
+
drawGlow2(data, width, height, width * 0.82, height * 0.78, Math.min(width, height) * 0.22, MAGENTA, 0.014);
|
|
2035
|
+
for (let row = 0;row < rows.length; row += 1) {
|
|
2036
|
+
const line = rows[row];
|
|
2037
|
+
for (let col = 0;col < line.length; col += 1) {
|
|
2038
|
+
const char = line[col];
|
|
2039
|
+
if (char === " ")
|
|
2040
|
+
continue;
|
|
2041
|
+
const x = fleetLeft + col * cellWidth;
|
|
2042
|
+
const y = fleetTop + row * cellHeight;
|
|
2043
|
+
const color = charColor(char, row, rows.length);
|
|
2044
|
+
drawAsciiGlyph(data, width, height, x + cellWidth * 0.07, y + cellHeight * 0.05, cellWidth, cellHeight, char, color, 0.52);
|
|
2045
|
+
if (char === "@" || char === "$" || char === "%") {
|
|
2046
|
+
drawGlow2(data, width, height, x + cellWidth * 0.5, y + cellHeight * 0.5, Math.max(cellWidth, cellHeight) * 0.95, color, 0.02);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
function applyStaticAtmosphere(data, width, height) {
|
|
2052
|
+
const cx = width * 0.5;
|
|
2053
|
+
const cy = height * 0.42;
|
|
2054
|
+
const radius = Math.max(width, height) * 0.78;
|
|
2055
|
+
for (let y = 0;y < height; y += 1) {
|
|
2056
|
+
for (let x = 0;x < width; x += 1) {
|
|
2057
|
+
const distance = Math.hypot(x - cx, y - cy) / radius;
|
|
2058
|
+
const alpha = Math.max(0.1, Math.min(0.82, 0.12 + Math.pow(distance, 1.65) * 0.62));
|
|
2059
|
+
blendOver(data, (y * width + x) * 4, BG, alpha);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
drawGlow2(data, width, height, width * 0.82, height * 0.04, Math.min(width, height) * 0.2, LIME, 0.03);
|
|
2063
|
+
drawGlow2(data, width, height, width * 0.12, height * 0.18, Math.min(width, height) * 0.16, CYAN, 0.022);
|
|
2064
|
+
}
|
|
1018
2065
|
function makeCrcTable() {
|
|
1019
2066
|
const table = new Uint32Array(256);
|
|
1020
2067
|
for (let n = 0;n < 256; n += 1) {
|
|
@@ -1085,9 +2132,6 @@ function kittyImageSequence(base64, options) {
|
|
|
1085
2132
|
}
|
|
1086
2133
|
return chunks.join("");
|
|
1087
2134
|
}
|
|
1088
|
-
function isModernImageTerminal(protocol) {
|
|
1089
|
-
return protocol === "kitty";
|
|
1090
|
-
}
|
|
1091
2135
|
function clampByte(value) {
|
|
1092
2136
|
return Math.max(0, Math.min(255, Math.round(value)));
|
|
1093
2137
|
}
|
|
@@ -1099,23 +2143,6 @@ function blendOver(data, index, color, alphaScale = 1) {
|
|
|
1099
2143
|
data[index + 2] = clampByte(color[2] * alpha + data[index + 2] * inv);
|
|
1100
2144
|
data[index + 3] = 255;
|
|
1101
2145
|
}
|
|
1102
|
-
function drawDisc(data, width, height, cx, cy, radius, color, alpha = 1) {
|
|
1103
|
-
const minX = Math.max(0, Math.floor(cx - radius));
|
|
1104
|
-
const maxX = Math.min(width - 1, Math.ceil(cx + radius));
|
|
1105
|
-
const minY = Math.max(0, Math.floor(cy - radius));
|
|
1106
|
-
const maxY = Math.min(height - 1, Math.ceil(cy + radius));
|
|
1107
|
-
for (let y = minY;y <= maxY; y += 1) {
|
|
1108
|
-
for (let x = minX;x <= maxX; x += 1) {
|
|
1109
|
-
const dx = x + 0.5 - cx;
|
|
1110
|
-
const dy = y + 0.5 - cy;
|
|
1111
|
-
const d = Math.sqrt(dx * dx + dy * dy);
|
|
1112
|
-
if (d > radius)
|
|
1113
|
-
continue;
|
|
1114
|
-
const edge = Math.max(0, Math.min(1, radius - d));
|
|
1115
|
-
blendOver(data, (y * width + x) * 4, color, alpha * Math.min(1, 0.35 + edge));
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
2146
|
function drawGlow2(data, width, height, cx, cy, radius, color, alpha = 0.18) {
|
|
1120
2147
|
const minX = Math.max(0, Math.floor(cx - radius));
|
|
1121
2148
|
const maxX = Math.min(width - 1, Math.ceil(cx + radius));
|
|
@@ -1133,30 +2160,6 @@ function drawGlow2(data, width, height, cx, cy, radius, color, alpha = 0.18) {
|
|
|
1133
2160
|
}
|
|
1134
2161
|
}
|
|
1135
2162
|
}
|
|
1136
|
-
function drawLine2(data, width, height, x0, y0, x1, y1, color, alpha = 0.5, thickness = 1.4) {
|
|
1137
|
-
const dx = x1 - x0;
|
|
1138
|
-
const dy = y1 - y0;
|
|
1139
|
-
const steps = Math.max(1, Math.ceil(Math.hypot(dx, dy) * 1.2));
|
|
1140
|
-
for (let i = 0;i <= steps; i += 1) {
|
|
1141
|
-
const t2 = i / steps;
|
|
1142
|
-
drawDisc(data, width, height, x0 + dx * t2, y0 + dy * t2, thickness, color, alpha);
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
function drawDrone(data, width, height, x, y, scale, tick, color) {
|
|
1146
|
-
const wobble = Math.sin(tick * 0.055) * scale * 0.35;
|
|
1147
|
-
const cy = y + wobble;
|
|
1148
|
-
drawGlow2(data, width, height, x, cy, scale * 7, color, 0.18);
|
|
1149
|
-
drawDisc(data, width, height, x, cy, scale * 1.45, color, 0.92);
|
|
1150
|
-
drawDisc(data, width, height, x, cy, scale * 0.55, [235, 255, 176, 255], 0.85);
|
|
1151
|
-
const arm = scale * 4.1;
|
|
1152
|
-
const rotor = scale * 1.6;
|
|
1153
|
-
const arms = [[-arm, -arm * 0.72], [arm, -arm * 0.72], [-arm, arm * 0.72], [arm, arm * 0.72]];
|
|
1154
|
-
for (const [dx, dy] of arms) {
|
|
1155
|
-
drawLine2(data, width, height, x, cy, x + dx, cy + dy, color, 0.42, Math.max(1, scale * 0.22));
|
|
1156
|
-
drawGlow2(data, width, height, x + dx, cy + dy, rotor * 2.6, color, 0.1);
|
|
1157
|
-
drawDisc(data, width, height, x + dx, cy + dy, rotor, color, 0.34 + 0.12 * Math.sin(tick * 0.15));
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
2163
|
function roundRectAlpha(px, py, x, y, w, h, r) {
|
|
1161
2164
|
const rx = Math.max(x + r, Math.min(px, x + w - r));
|
|
1162
2165
|
const ry = Math.max(y + r, Math.min(py, y + h - r));
|
|
@@ -1165,18 +2168,8 @@ function roundRectAlpha(px, py, x, y, w, h, r) {
|
|
|
1165
2168
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1166
2169
|
return Math.max(0, Math.min(1, r + 0.75 - dist));
|
|
1167
2170
|
}
|
|
1168
|
-
function
|
|
1169
|
-
|
|
1170
|
-
const cellH = size.height / layout.height;
|
|
1171
|
-
const leftCells = layout.centerLeft + (panel.left ?? 0);
|
|
1172
|
-
const topCells = layout.centerTop + panel.top;
|
|
1173
|
-
const widthCells = panel.width ?? layout.centerWidth;
|
|
1174
|
-
const heightCells = panel.height;
|
|
1175
|
-
const left = Math.round(leftCells * cellW);
|
|
1176
|
-
const top = Math.round(topCells * cellH);
|
|
1177
|
-
const width = Math.round(widthCells * cellW);
|
|
1178
|
-
const height = Math.round(heightCells * cellH);
|
|
1179
|
-
return width > 8 && height > 8 ? { left, top, width, height } : null;
|
|
2171
|
+
function panelRect(layout, panel, size) {
|
|
2172
|
+
return panelPixelPlacement(layout, panel, size);
|
|
1180
2173
|
}
|
|
1181
2174
|
function downsampleRegion(data, canvasW, canvasH, rect, scale) {
|
|
1182
2175
|
const w = Math.max(1, Math.ceil(rect.width / scale));
|
|
@@ -1325,7 +2318,8 @@ function getPanelMask(width, height, rect, headerPx, blurScale) {
|
|
|
1325
2318
|
function compositePanel(data, width, height, rect, headerPx) {
|
|
1326
2319
|
const blurScale = PANEL_BLUR_SCALE;
|
|
1327
2320
|
const sampled = downsampleRegion(data, width, height, rect, blurScale);
|
|
1328
|
-
const
|
|
2321
|
+
const blurRadius = Math.max(8, Math.min(28, Math.round(Math.min(rect.width, rect.height) * 0.018)));
|
|
2322
|
+
const blurred = blurRgb(sampled.pixels, sampled.width, sampled.height, blurRadius);
|
|
1329
2323
|
const mask = getPanelMask(width, height, rect, headerPx, blurScale);
|
|
1330
2324
|
for (let i = 0;i < mask.indices.length; i += 1) {
|
|
1331
2325
|
const idx = mask.indices[i];
|
|
@@ -1343,161 +2337,10 @@ function compositePanel(data, width, height, rect, headerPx) {
|
|
|
1343
2337
|
blendOver(data, mask.dividerIndices[i], PANEL_LINE2, 0.28);
|
|
1344
2338
|
}
|
|
1345
2339
|
}
|
|
1346
|
-
function
|
|
1347
|
-
if (scene === "tasks")
|
|
1348
|
-
return "loop";
|
|
1349
|
-
if (scene === "help")
|
|
1350
|
-
return "carrier";
|
|
1351
|
-
return "dispatch";
|
|
1352
|
-
}
|
|
1353
|
-
function seededUnit(seed) {
|
|
1354
|
-
let value = seed >>> 0;
|
|
1355
|
-
value = value * 1664525 + 1013904223 >>> 0;
|
|
1356
|
-
return value / 4294967295;
|
|
1357
|
-
}
|
|
1358
|
-
function sceneCore(width, height, kind) {
|
|
1359
|
-
if (kind === "dispatch")
|
|
1360
|
-
return { x: width * 0.3, y: height * 0.45 };
|
|
1361
|
-
if (kind === "carrier")
|
|
1362
|
-
return { x: width * 0.5, y: height * 0.54 };
|
|
1363
|
-
return { x: width * 0.09, y: height * 0.56 };
|
|
1364
|
-
}
|
|
1365
|
-
function dispatchLane(width, index, count) {
|
|
1366
|
-
const a = seededUnit(28672 + index * 101);
|
|
1367
|
-
const b = seededUnit(36864 + index * 131);
|
|
1368
|
-
const c = seededUnit(45056 + index * 151);
|
|
1369
|
-
const ang = (-0.9 + 1.8 * (index / Math.max(1, count - 1))) * (Math.PI * 0.5);
|
|
1370
|
-
return { ang, len: width * (0.32 + a * 0.42), curve: (b - 0.5) * 0.7, phase: c * Math.PI * 2 };
|
|
1371
|
-
}
|
|
1372
|
-
function lanePoint(width, height, lane, p, kind = "dispatch") {
|
|
1373
|
-
const core = sceneCore(width, height, kind);
|
|
1374
|
-
const dist = lane.len * p;
|
|
1375
|
-
const bend = Math.sin(p * Math.PI) * lane.curve * Math.min(width, height) * 0.035;
|
|
1376
|
-
const ux = Math.cos(lane.ang);
|
|
1377
|
-
const uy = Math.sin(lane.ang);
|
|
1378
|
-
return { x: core.x + ux * dist + -uy * bend, y: core.y + uy * dist + ux * bend };
|
|
1379
|
-
}
|
|
1380
|
-
function drawMarketingRails(data, width, height, kind, tick) {
|
|
1381
|
-
const ys = kind === "loop" ? [0.18, 0.5, 0.82] : kind === "carrier" ? [0.24, 0.54, 0.82] : [0.18, 0.58, 0.82];
|
|
1382
|
-
ys.forEach((ratio, index) => {
|
|
1383
|
-
const y = height * ratio + Math.sin(tick * 0.018 + index) * Math.max(2, height * 0.0025);
|
|
1384
|
-
const color = index === 2 ? CYAN : index === 1 ? LIME : LIME_DIM;
|
|
1385
|
-
drawLine2(data, width, height, width * 0.06, y, width * 0.94, y + Math.sin(index) * height * 0.002, color, index === 1 ? 0.11 : 0.07, Math.max(0.8, height * 0.0012));
|
|
1386
|
-
for (let p = (tick * 0.004 + index * 0.21) % 1;p < 1; p += 0.18 + index * 0.02) {
|
|
1387
|
-
const x = width * (0.06 + 0.88 * p);
|
|
1388
|
-
drawDisc(data, width, height, x, y, Math.max(1.2, height * (0.0017 + index * 0.0004)), color, 0.24);
|
|
1389
|
-
drawGlow2(data, width, height, x, y, Math.max(5, height * 0.006), color, 0.035);
|
|
1390
|
-
}
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
function drawDispatchScene(data, width, height, tick) {
|
|
1394
|
-
const core = sceneCore(width, height, "dispatch");
|
|
1395
|
-
const min = Math.min(width, height);
|
|
1396
|
-
drawGlow2(data, width, height, core.x, core.y, min * 0.085, LIME, 0.12);
|
|
1397
|
-
drawDisc(data, width, height, core.x, core.y, min * 0.0065, LIME, 0.85);
|
|
1398
|
-
const laneCount = width < 1000 ? 7 : 12;
|
|
1399
|
-
const lanes = Array.from({ length: laneCount }, (_, index) => dispatchLane(width, index, laneCount));
|
|
1400
|
-
lanes.forEach((lane) => {
|
|
1401
|
-
let prev = lanePoint(width, height, lane, 0);
|
|
1402
|
-
for (let step = 1;step <= 32; step += 1) {
|
|
1403
|
-
const next = lanePoint(width, height, lane, step / 32);
|
|
1404
|
-
drawLine2(data, width, height, prev.x, prev.y, next.x, next.y, lane.phase > Math.PI ? CYAN : LIME_DIM, 0.045, Math.max(0.7, min * 0.0009));
|
|
1405
|
-
prev = next;
|
|
1406
|
-
}
|
|
1407
|
-
});
|
|
1408
|
-
const packets = width < 1000 ? 70 : 150;
|
|
1409
|
-
for (let i = 0;i < packets; i += 1) {
|
|
1410
|
-
const lane = lanes[i % lanes.length];
|
|
1411
|
-
const speed = 0.003 + seededUnit(12288 + i * 17) * 0.009;
|
|
1412
|
-
const p = (seededUnit(16384 + i * 19) + tick * speed) % 1;
|
|
1413
|
-
const pt = lanePoint(width, height, lane, p);
|
|
1414
|
-
const wobble = (seededUnit(20480 + i * 23) - 0.5) * min * 0.012;
|
|
1415
|
-
const fade = p < 0.1 ? p / 0.1 : p > 0.88 ? (1 - p) / 0.12 : 1;
|
|
1416
|
-
const color = seededUnit(24576 + i * 29) < 0.22 ? CYAN : LIME_DIM;
|
|
1417
|
-
const y = pt.y + wobble * Math.sin(p * Math.PI * 2);
|
|
1418
|
-
drawLine2(data, width, height, pt.x - min * 0.012, y - min * 0.002, pt.x, y, color, 0.08 * fade, Math.max(0.7, min * 0.0008));
|
|
1419
|
-
drawDisc(data, width, height, pt.x, y, Math.max(1.3, min * 0.0022), color, 0.5 * fade);
|
|
1420
|
-
drawGlow2(data, width, height, pt.x, y, Math.max(4, min * 0.008), color, 0.045 * fade);
|
|
1421
|
-
}
|
|
1422
|
-
for (let i = 0;i < (width < 1000 ? 4 : 8); i += 1) {
|
|
1423
|
-
const lane = lanes[i % lanes.length];
|
|
1424
|
-
const p = (tick * 0.002 + i * 0.13) % 1;
|
|
1425
|
-
const pt = lanePoint(width, height, lane, p);
|
|
1426
|
-
drawDrone(data, width, height, pt.x, pt.y + Math.sin(tick * 0.04 + i) * min * 0.004, min * (0.006 + i % 2 * 0.0015), tick + i * 11, i % 3 === 0 ? CYAN : LIME_DIM);
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
function drawLoopScene(data, width, height, tick) {
|
|
1430
|
-
const min = Math.min(width, height);
|
|
1431
|
-
const y = height * 0.56;
|
|
1432
|
-
const x0 = width * 0.09;
|
|
1433
|
-
const x1 = width * 0.91;
|
|
1434
|
-
drawLine2(data, width, height, x0, y, x1, y, LIME_DIM, 0.12, Math.max(0.8, min * 0.001));
|
|
1435
|
-
const gates = width < 1000 ? 4 : 5;
|
|
1436
|
-
const active = Math.floor(tick / 16) % gates;
|
|
1437
|
-
for (let i = 0;i < gates; i += 1) {
|
|
1438
|
-
const x = x0 + (x1 - x0) * (i / Math.max(1, gates - 1));
|
|
1439
|
-
const color = i === active ? LIME : i <= active ? LIME_DIM : INK_DIM;
|
|
1440
|
-
drawLine2(data, width, height, x, y - min * 0.05, x, y + min * 0.05, color, i === active ? 0.25 : 0.08, Math.max(0.8, min * 0.001));
|
|
1441
|
-
drawDisc(data, width, height, x, y, i === active ? min * 0.006 : min * 0.0035, color, i === active ? 0.72 : 0.25);
|
|
1442
|
-
drawGlow2(data, width, height, x, y, i === active ? min * 0.018 : min * 0.01, color, i === active ? 0.1 : 0.035);
|
|
1443
|
-
}
|
|
1444
|
-
const packets = width < 1000 ? 36 : 84;
|
|
1445
|
-
for (let i = 0;i < packets; i += 1) {
|
|
1446
|
-
const p = (seededUnit(33024 + i * 13) + tick * (0.0022 + seededUnit(33280 + i * 17) * 0.004)) % 1;
|
|
1447
|
-
const x = x0 + (x1 - x0) * p;
|
|
1448
|
-
const segment = Math.min(gates - 1, Math.floor(p * gates));
|
|
1449
|
-
const color = segment < active ? LIME_DIM : segment === active ? CYAN : INK_DIM;
|
|
1450
|
-
const py = y + (seededUnit(33536 + i * 19) - 0.5) * min * 0.018 * Math.sin(p * Math.PI * 2);
|
|
1451
|
-
drawLine2(data, width, height, x - min * 0.014, py, x, py, color, 0.1, Math.max(0.7, min * 0.0008));
|
|
1452
|
-
drawDisc(data, width, height, x, py, Math.max(1.2, min * 0.002), color, color === INK_DIM ? 0.22 : 0.58);
|
|
1453
|
-
}
|
|
1454
|
-
drawDrone(data, width, height, x0 + (x1 - x0) * (tick * 0.003 % 1), y - min * 0.04, min * 0.0065, tick, LIME_DIM);
|
|
1455
|
-
drawDrone(data, width, height, x0 + (x1 - x0) * ((tick * 0.002 + 0.5) % 1), y + min * 0.036, min * 0.0055, tick + 40, MAGENTA);
|
|
1456
|
-
}
|
|
1457
|
-
function drawCarrierScene(data, width, height, tick) {
|
|
1458
|
-
const min = Math.min(width, height);
|
|
1459
|
-
const core = sceneCore(width, height, "carrier");
|
|
1460
|
-
drawGlow2(data, width, height, core.x, core.y, min * 0.075, LIME, 0.12);
|
|
1461
|
-
drawDisc(data, width, height, core.x, core.y, min * 0.0065, LIME, 0.92);
|
|
1462
|
-
const nodes = width < 1000 ? 5 : 7;
|
|
1463
|
-
const rx = width * 0.36;
|
|
1464
|
-
const ry = height * 0.22;
|
|
1465
|
-
for (let i = 0;i < nodes; i += 1) {
|
|
1466
|
-
const a = i / nodes * Math.PI * 2 - Math.PI / 2 + tick * 0.006;
|
|
1467
|
-
const x = core.x + Math.cos(a) * rx;
|
|
1468
|
-
const y = core.y + Math.sin(a) * ry;
|
|
1469
|
-
const color = i % 2 ? CYAN : LIME_DIM;
|
|
1470
|
-
drawLine2(data, width, height, core.x, core.y, x, y, color, 0.055, Math.max(0.7, min * 0.0008));
|
|
1471
|
-
drawGlow2(data, width, height, x, y, min * 0.018, color, 0.055);
|
|
1472
|
-
drawDisc(data, width, height, x, y, min * 0.0048, color, 0.62);
|
|
1473
|
-
drawDrone(data, width, height, x, y - min * 0.018, min * 0.005, tick + i * 9, color);
|
|
1474
|
-
}
|
|
1475
|
-
const packets = width < 1000 ? 30 : 72;
|
|
1476
|
-
for (let i = 0;i < packets; i += 1) {
|
|
1477
|
-
const lane = i % nodes;
|
|
1478
|
-
const a = lane / nodes * Math.PI * 2 - Math.PI / 2 + tick * 0.006;
|
|
1479
|
-
const nx = core.x + Math.cos(a) * rx;
|
|
1480
|
-
const ny = core.y + Math.sin(a) * ry;
|
|
1481
|
-
const p = (seededUnit(37120 + i * 37) + tick * (0.003 + seededUnit(37376 + i * 43) * 0.009)) % 1;
|
|
1482
|
-
const x = core.x + (nx - core.x) * p;
|
|
1483
|
-
const y = core.y + (ny - core.y) * p;
|
|
1484
|
-
const color = seededUnit(37632 + i * 47) < 0.35 ? CYAN : LIME_DIM;
|
|
1485
|
-
drawDisc(data, width, height, x, y, Math.max(1.2, min * 0.002), color, 0.48);
|
|
1486
|
-
drawGlow2(data, width, height, x, y, min * 0.008, color, 0.035);
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
function drawBackground(data, width, height, tick, scene) {
|
|
2340
|
+
function drawBackground(data, width, height, _tick, scene, layout) {
|
|
1490
2341
|
fillRgba(data, BG, BG_WORD);
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
drawGlow2(data, width, height, width * 0.12, height * 0.18, Math.min(width, height) * 0.17, CYAN, 0.025);
|
|
1494
|
-
drawMarketingRails(data, width, height, kind, tick);
|
|
1495
|
-
if (kind === "loop")
|
|
1496
|
-
drawLoopScene(data, width, height, tick);
|
|
1497
|
-
else if (kind === "carrier")
|
|
1498
|
-
drawCarrierScene(data, width, height, tick);
|
|
1499
|
-
else
|
|
1500
|
-
drawDispatchScene(data, width, height, tick);
|
|
2342
|
+
drawStaticAsciiFleetBackground(data, width, height, scene, layout);
|
|
2343
|
+
applyStaticAtmosphere(data, width, height);
|
|
1501
2344
|
}
|
|
1502
2345
|
function imageVisualFrameKey(input) {
|
|
1503
2346
|
const width = Math.max(1, Math.floor(input.pixelSize.width));
|
|
@@ -1522,18 +2365,80 @@ function encodeKittyFrame(width, height, data, input) {
|
|
|
1522
2365
|
compressed: true
|
|
1523
2366
|
});
|
|
1524
2367
|
}
|
|
2368
|
+
function cropRgba(data, sourceWidth, rect) {
|
|
2369
|
+
const out = new Uint8ClampedArray(rect.width * rect.height * 4);
|
|
2370
|
+
for (let y = 0;y < rect.height; y += 1) {
|
|
2371
|
+
const sourceStart = ((rect.top + y) * sourceWidth + rect.left) * 4;
|
|
2372
|
+
out.set(data.subarray(sourceStart, sourceStart + rect.width * 4), y * rect.width * 4);
|
|
2373
|
+
}
|
|
2374
|
+
return out;
|
|
2375
|
+
}
|
|
2376
|
+
function encodePanelPatch(data, sourceWidth, placement, imageId, zIndex) {
|
|
2377
|
+
const patch = cropRgba(data, sourceWidth, placement);
|
|
2378
|
+
const png = encodePng(placement.width, placement.height, patch);
|
|
2379
|
+
const sequence = kittyImageSequence(png.toString("base64"), {
|
|
2380
|
+
columns: Math.max(1, Math.round(placement.widthCells)),
|
|
2381
|
+
rows: Math.max(1, Math.round(placement.heightCells)),
|
|
2382
|
+
imageId,
|
|
2383
|
+
zIndex,
|
|
2384
|
+
format: 100
|
|
2385
|
+
});
|
|
2386
|
+
return `\x1B[${Math.max(1, Math.round(placement.topCells) + 1)};${Math.max(1, Math.round(placement.leftCells) + 1)}H${sequence}`;
|
|
2387
|
+
}
|
|
1525
2388
|
function renderImageVisualFrame(input) {
|
|
1526
2389
|
const width = Math.max(1, Math.floor(input.pixelSize.width));
|
|
1527
2390
|
const height = Math.max(1, Math.floor(input.pixelSize.height));
|
|
1528
2391
|
const byteCount = width * height * 4;
|
|
1529
2392
|
if (byteCount > MAX_IMAGE_BYTES)
|
|
1530
2393
|
return null;
|
|
2394
|
+
const layer = input.layer ?? "full";
|
|
1531
2395
|
const data = new Uint8ClampedArray(byteCount);
|
|
1532
|
-
drawBackground(data, width, height, input.
|
|
2396
|
+
drawBackground(data, width, height, 0, input.scene, input.layout);
|
|
2397
|
+
if (layer === "split") {
|
|
2398
|
+
const sequences = [encodeKittyFrame(width, height, data, input)];
|
|
2399
|
+
let panelIndex = 0;
|
|
2400
|
+
for (const panel of input.panels) {
|
|
2401
|
+
if (panel.chrome !== "ad-terminal")
|
|
2402
|
+
continue;
|
|
2403
|
+
const placement = panelPixelPlacement(input.layout, panel, { width, height });
|
|
2404
|
+
if (!placement)
|
|
2405
|
+
continue;
|
|
2406
|
+
const cellH = height / input.layout.height;
|
|
2407
|
+
const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
|
|
2408
|
+
compositePanel(data, width, height, placement, headerPx);
|
|
2409
|
+
const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
|
|
2410
|
+
sequences.push(encodePanelPatch(data, width, placement, imageId, -12));
|
|
2411
|
+
panelIndex += 1;
|
|
2412
|
+
}
|
|
2413
|
+
return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
|
|
2414
|
+
}
|
|
2415
|
+
if (layer === "panel-chrome")
|
|
2416
|
+
return { key: imageVisualFrameKey(input), sequence: "" };
|
|
2417
|
+
if (layer === "background") {
|
|
2418
|
+
return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
|
|
2419
|
+
}
|
|
2420
|
+
if (layer === "panel-patches") {
|
|
2421
|
+
const sequences = [];
|
|
2422
|
+
let panelIndex = 0;
|
|
2423
|
+
for (const panel of input.panels) {
|
|
2424
|
+
if (panel.chrome !== "ad-terminal")
|
|
2425
|
+
continue;
|
|
2426
|
+
const placement = panelPixelPlacement(input.layout, panel, { width, height });
|
|
2427
|
+
if (!placement)
|
|
2428
|
+
continue;
|
|
2429
|
+
const cellH = height / input.layout.height;
|
|
2430
|
+
const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
|
|
2431
|
+
compositePanel(data, width, height, placement, headerPx);
|
|
2432
|
+
const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
|
|
2433
|
+
sequences.push(encodePanelPatch(data, width, placement, imageId, input.zIndex));
|
|
2434
|
+
panelIndex += 1;
|
|
2435
|
+
}
|
|
2436
|
+
return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
|
|
2437
|
+
}
|
|
1533
2438
|
for (const panel of input.panels) {
|
|
1534
2439
|
if (panel.chrome !== "ad-terminal")
|
|
1535
2440
|
continue;
|
|
1536
|
-
const rect =
|
|
2441
|
+
const rect = panelRect(input.layout, panel, { width, height });
|
|
1537
2442
|
if (!rect)
|
|
1538
2443
|
continue;
|
|
1539
2444
|
const cellH = height / input.layout.height;
|
|
@@ -1542,8 +2447,11 @@ function renderImageVisualFrame(input) {
|
|
|
1542
2447
|
}
|
|
1543
2448
|
return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
|
|
1544
2449
|
}
|
|
2450
|
+
function shouldRenderImagesInline() {
|
|
2451
|
+
return process.env.RIG_OPENTUI_IMAGE_WORKER === "0" || import.meta.url.includes("$bunfs");
|
|
2452
|
+
}
|
|
1545
2453
|
function createImageWorker() {
|
|
1546
|
-
if (
|
|
2454
|
+
if (shouldRenderImagesInline())
|
|
1547
2455
|
return null;
|
|
1548
2456
|
const isTypeScriptRuntime = import.meta.url.endsWith(".ts");
|
|
1549
2457
|
const workerUrl = new URL(isTypeScriptRuntime ? "./image-visual-layer-worker.ts" : "./image-visual-layer-worker.js", import.meta.url);
|
|
@@ -1559,24 +2467,8 @@ function createImageWorker() {
|
|
|
1559
2467
|
}
|
|
1560
2468
|
}
|
|
1561
2469
|
var RECENT_WRITTEN_LIMIT = 24;
|
|
1562
|
-
var DEFAULT_PANEL_FPS = 24;
|
|
1563
|
-
var DEFAULT_PANEL_LAYER_SCALE = 0.22;
|
|
1564
2470
|
var DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES = 256 * 1024;
|
|
1565
2471
|
var DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND = 850 * 1024;
|
|
1566
|
-
var MIN_BACKGROUND_BUDGET_BYTES = 24 * 1024;
|
|
1567
|
-
var MIN_PANEL_BUDGET_BYTES = 56 * 1024;
|
|
1568
|
-
function panelFps() {
|
|
1569
|
-
const raw = Number.parseFloat(process.env.RIG_OPENTUI_PANEL_FPS ?? "");
|
|
1570
|
-
if (Number.isFinite(raw) && raw > 0)
|
|
1571
|
-
return Math.max(4, Math.min(60, raw));
|
|
1572
|
-
return DEFAULT_PANEL_FPS;
|
|
1573
|
-
}
|
|
1574
|
-
function panelLayerScale() {
|
|
1575
|
-
const raw = Number.parseFloat(process.env.RIG_OPENTUI_PANEL_SCALE ?? "");
|
|
1576
|
-
if (Number.isFinite(raw) && raw > 0)
|
|
1577
|
-
return Math.max(0.18, Math.min(1, raw));
|
|
1578
|
-
return DEFAULT_PANEL_LAYER_SCALE;
|
|
1579
|
-
}
|
|
1580
2472
|
function imageOutputBacklogBytes() {
|
|
1581
2473
|
const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BACKLOG_BYTES ?? "", 10);
|
|
1582
2474
|
if (Number.isFinite(raw) && raw > 0)
|
|
@@ -1598,36 +2490,30 @@ class ImageVisualLayer {
|
|
|
1598
2490
|
panelImageIds = Array.from({ length: 8 }, () => allocateImageId());
|
|
1599
2491
|
panelChromeImageIds = Array.from({ length: 8 }, () => allocateImageId());
|
|
1600
2492
|
stdout;
|
|
2493
|
+
onFrameReady;
|
|
1601
2494
|
protocol;
|
|
1602
2495
|
transport = imageTransport();
|
|
1603
|
-
frameIntervalMs = 1000 / imageFps();
|
|
1604
|
-
panelIntervalMs = 1000 / panelFps();
|
|
1605
2496
|
maxOutputBacklogBytes = imageOutputBacklogBytes();
|
|
1606
2497
|
outputBytesPerSecond = imageOutputBytesPerSecond();
|
|
1607
2498
|
outputBudgetBytes = imageOutputBytesPerSecond();
|
|
1608
2499
|
lastBudgetAtMs = Date.now();
|
|
1609
2500
|
motionSlot = this.createSlot();
|
|
1610
|
-
|
|
1611
|
-
chromeSlot = this.createSlot();
|
|
2501
|
+
workersStarted = false;
|
|
1612
2502
|
pendingFrames = new Map;
|
|
1613
2503
|
recentWrittenKeys = [];
|
|
1614
2504
|
imagesVisible = false;
|
|
2505
|
+
flushPaused = false;
|
|
1615
2506
|
requestId = 0;
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
lastChromeKey = "";
|
|
2507
|
+
lastBackgroundKey = "";
|
|
2508
|
+
fatalWorkerError = null;
|
|
1619
2509
|
destroyed = false;
|
|
1620
|
-
constructor(stdout = process.stdout) {
|
|
2510
|
+
constructor(stdout = process.stdout, onFrameReady) {
|
|
1621
2511
|
this.stdout = stdout;
|
|
1622
|
-
this.
|
|
1623
|
-
|
|
1624
|
-
this.startWorker(this.motionSlot);
|
|
1625
|
-
this.startWorker(this.panelSlot);
|
|
1626
|
-
this.startWorker(this.chromeSlot);
|
|
1627
|
-
}
|
|
2512
|
+
this.onFrameReady = onFrameReady;
|
|
2513
|
+
this.protocol = getRigTerminalCapabilities().imageProtocol;
|
|
1628
2514
|
}
|
|
1629
2515
|
get enabled() {
|
|
1630
|
-
return
|
|
2516
|
+
return this.protocol === "kitty";
|
|
1631
2517
|
}
|
|
1632
2518
|
clear() {
|
|
1633
2519
|
if (this.enabled) {
|
|
@@ -1637,16 +2523,10 @@ class ImageVisualLayer {
|
|
|
1637
2523
|
this.pendingFrames.clear();
|
|
1638
2524
|
this.recentWrittenKeys.length = 0;
|
|
1639
2525
|
this.resetSlot(this.motionSlot);
|
|
1640
|
-
this.
|
|
1641
|
-
this.resetSlot(this.chromeSlot);
|
|
1642
|
-
this.lastMotionFrameIndex = -1;
|
|
1643
|
-
this.lastPanelFrameIndex = -1;
|
|
1644
|
-
this.lastChromeKey = "";
|
|
2526
|
+
this.lastBackgroundKey = "";
|
|
1645
2527
|
this.outputBudgetBytes = this.outputBytesPerSecond;
|
|
1646
2528
|
this.lastBudgetAtMs = Date.now();
|
|
1647
2529
|
this.motionSlot.clearedThroughRequestId = this.requestId;
|
|
1648
|
-
this.panelSlot.clearedThroughRequestId = this.requestId;
|
|
1649
|
-
this.chromeSlot.clearedThroughRequestId = this.requestId;
|
|
1650
2530
|
}
|
|
1651
2531
|
}
|
|
1652
2532
|
destroy() {
|
|
@@ -1654,19 +2534,31 @@ class ImageVisualLayer {
|
|
|
1654
2534
|
return;
|
|
1655
2535
|
this.clear();
|
|
1656
2536
|
this.destroyed = true;
|
|
1657
|
-
const workers = [this.motionSlot.worker
|
|
2537
|
+
const workers = [this.motionSlot.worker];
|
|
1658
2538
|
this.motionSlot.worker = null;
|
|
1659
|
-
this.panelSlot.worker = null;
|
|
1660
|
-
this.chromeSlot.worker = null;
|
|
1661
2539
|
for (const worker of workers)
|
|
1662
2540
|
if (worker)
|
|
1663
2541
|
worker.terminate().catch(() => {
|
|
1664
2542
|
return;
|
|
1665
2543
|
});
|
|
1666
2544
|
}
|
|
2545
|
+
setFlushPaused(paused) {
|
|
2546
|
+
this.flushPaused = paused;
|
|
2547
|
+
}
|
|
2548
|
+
isReady(input) {
|
|
2549
|
+
if (!this.enabled || this.destroyed)
|
|
2550
|
+
return true;
|
|
2551
|
+
this.throwIfFatal();
|
|
2552
|
+
const splitKey = imageVisualFrameKey({ ...input, tick: 0, layer: "split" });
|
|
2553
|
+
return this.recentWrittenKeys.includes(splitKey) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === splitKey);
|
|
2554
|
+
}
|
|
1667
2555
|
render(input) {
|
|
1668
2556
|
if (!this.enabled || this.destroyed)
|
|
1669
2557
|
return;
|
|
2558
|
+
this.throwIfFatal();
|
|
2559
|
+
if (this.transport !== "png")
|
|
2560
|
+
this.failWorker("renderer", new Error("OpenTUI image visuals require PNG transport for static-background rendering."));
|
|
2561
|
+
this.ensureWorkersStarted();
|
|
1670
2562
|
const width = Math.max(1, Math.floor(input.pixelSize.width));
|
|
1671
2563
|
const height = Math.max(1, Math.floor(input.pixelSize.height));
|
|
1672
2564
|
if (width * height * 4 > MAX_IMAGE_BYTES)
|
|
@@ -1674,54 +2566,32 @@ class ImageVisualLayer {
|
|
|
1674
2566
|
if (this.isOutputBackedUp())
|
|
1675
2567
|
return;
|
|
1676
2568
|
this.refillOutputBudget();
|
|
1677
|
-
const
|
|
1678
|
-
const
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
if (!input.force && frameIndex === this.lastMotionFrameIndex)
|
|
1683
|
-
return;
|
|
1684
|
-
this.scheduleRender(this.motionSlot, { ...input, tick: visualTick, layer: "full" }, this.imageId, -10, input.force);
|
|
1685
|
-
this.lastMotionFrameIndex = frameIndex;
|
|
1686
|
-
return;
|
|
1687
|
-
}
|
|
1688
|
-
const motionFrameIndex = Math.floor(now / this.frameIntervalMs);
|
|
1689
|
-
if ((input.force || motionFrameIndex !== this.lastMotionFrameIndex) && this.outputBudgetBytes >= MIN_BACKGROUND_BUDGET_BYTES) {
|
|
1690
|
-
this.scheduleRender(this.motionSlot, { ...input, pixelSize: scaledPixelSize(input.pixelSize, droneLayerScale(), input.layout), panels: [], tick: visualTick, layer: "background" }, this.imageId, -30, input.force);
|
|
1691
|
-
this.lastMotionFrameIndex = motionFrameIndex;
|
|
1692
|
-
}
|
|
1693
|
-
const panelFrameIndex = Math.floor(now / this.panelIntervalMs);
|
|
1694
|
-
if ((input.force || panelFrameIndex !== this.lastPanelFrameIndex) && this.outputBudgetBytes >= MIN_PANEL_BUDGET_BYTES) {
|
|
1695
|
-
this.scheduleRender(this.panelSlot, { ...input, pixelSize: scaledPixelSize(input.pixelSize, panelLayerScale(), input.layout), tick: visualTick, layer: "panel-patches" }, this.imageId, -12, input.force, this.panelImageIds);
|
|
1696
|
-
this.lastPanelFrameIndex = panelFrameIndex;
|
|
1697
|
-
}
|
|
1698
|
-
const chromeInput = { ...input, tick: 0, layer: "panel-chrome" };
|
|
1699
|
-
const chromeKey = imageVisualFrameKey(chromeInput);
|
|
1700
|
-
if (input.force || chromeKey !== this.lastChromeKey) {
|
|
1701
|
-
this.scheduleRender(this.chromeSlot, chromeInput, this.imageId, -8, input.force, this.panelChromeImageIds);
|
|
1702
|
-
this.lastChromeKey = chromeKey;
|
|
2569
|
+
const splitInput = { ...input, tick: 0, layer: "split" };
|
|
2570
|
+
const splitKey = imageVisualFrameKey(splitInput);
|
|
2571
|
+
if (input.force || splitKey !== this.lastBackgroundKey) {
|
|
2572
|
+
this.scheduleRender(this.motionSlot, splitInput, this.imageId, -30, input.force, this.panelImageIds);
|
|
2573
|
+
this.lastBackgroundKey = splitKey;
|
|
1703
2574
|
}
|
|
1704
2575
|
}
|
|
1705
2576
|
flush() {
|
|
2577
|
+
this.throwIfFatal();
|
|
2578
|
+
if (this.flushPaused)
|
|
2579
|
+
return false;
|
|
1706
2580
|
if (this.pendingFrames.size === 0)
|
|
1707
2581
|
return false;
|
|
1708
2582
|
this.refillOutputBudget();
|
|
1709
2583
|
if (this.isOutputBackedUp()) {
|
|
1710
|
-
const
|
|
2584
|
+
const retainedSplit = this.pendingFrames.get("split");
|
|
1711
2585
|
this.pendingFrames.clear();
|
|
1712
|
-
if (
|
|
1713
|
-
this.pendingFrames.set("
|
|
2586
|
+
if (retainedSplit)
|
|
2587
|
+
this.pendingFrames.set("split", retainedSplit);
|
|
1714
2588
|
return false;
|
|
1715
2589
|
}
|
|
1716
2590
|
const framePriority = (frame) => {
|
|
1717
2591
|
const layer = pendingLayerKey(frame.key);
|
|
1718
|
-
if (layer === "
|
|
1719
|
-
return 0;
|
|
1720
|
-
if (layer === "background")
|
|
2592
|
+
if (layer === "split")
|
|
1721
2593
|
return 1;
|
|
1722
|
-
|
|
1723
|
-
return 2;
|
|
1724
|
-
return 2;
|
|
2594
|
+
return 4;
|
|
1725
2595
|
};
|
|
1726
2596
|
const frames = Array.from(this.pendingFrames.values()).sort((a, b) => framePriority(a) - framePriority(b));
|
|
1727
2597
|
const prefix = "\x1B[?25l\x1B7\x1B[s\x1B[H";
|
|
@@ -1733,12 +2603,14 @@ class ImageVisualLayer {
|
|
|
1733
2603
|
for (const frame of frames) {
|
|
1734
2604
|
const candidate = sequence + frame.sequence;
|
|
1735
2605
|
const candidateBytes = Buffer.byteLength(prefix + candidate + suffix);
|
|
1736
|
-
|
|
2606
|
+
const layer = pendingLayerKey(frame.key);
|
|
2607
|
+
const allowOneStaticFrame = selected.length === 0 && layer === "split";
|
|
2608
|
+
if (candidateBytes <= this.outputBudgetBytes || allowOneStaticFrame) {
|
|
1737
2609
|
selected.push(frame);
|
|
1738
2610
|
sequence = candidate;
|
|
1739
2611
|
byteLength = candidateBytes;
|
|
1740
|
-
} else if (
|
|
1741
|
-
retained.set(
|
|
2612
|
+
} else if (layer === "split") {
|
|
2613
|
+
retained.set(layer, frame);
|
|
1742
2614
|
}
|
|
1743
2615
|
}
|
|
1744
2616
|
this.pendingFrames.clear();
|
|
@@ -1757,7 +2629,7 @@ class ImageVisualLayer {
|
|
|
1757
2629
|
return accepted;
|
|
1758
2630
|
}
|
|
1759
2631
|
createSlot() {
|
|
1760
|
-
return { worker: null, busy: false, queuedRequest: null, inFlightKey: "", queuedKey: "", clearedThroughRequestId: 0 };
|
|
2632
|
+
return { worker: null, busy: false, queuedRequest: null, inFlightKey: "", queuedKey: "", clearedThroughRequestId: 0, latestRequestId: 0 };
|
|
1761
2633
|
}
|
|
1762
2634
|
resetSlot(slot) {
|
|
1763
2635
|
slot.busy = false;
|
|
@@ -1785,7 +2657,23 @@ class ImageVisualLayer {
|
|
|
1785
2657
|
isKnownKey(slot, key) {
|
|
1786
2658
|
return this.recentWrittenKeys.includes(key) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === key) || key === slot.inFlightKey || key === slot.queuedKey;
|
|
1787
2659
|
}
|
|
2660
|
+
ensureWorkersStarted() {
|
|
2661
|
+
if (this.workersStarted)
|
|
2662
|
+
return;
|
|
2663
|
+
this.workersStarted = true;
|
|
2664
|
+
this.startWorker(this.motionSlot, "static-visuals");
|
|
2665
|
+
}
|
|
2666
|
+
failWorker(name, error) {
|
|
2667
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2668
|
+
this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${detail}`);
|
|
2669
|
+
throw this.fatalWorkerError;
|
|
2670
|
+
}
|
|
2671
|
+
throwIfFatal() {
|
|
2672
|
+
if (this.fatalWorkerError)
|
|
2673
|
+
throw this.fatalWorkerError;
|
|
2674
|
+
}
|
|
1788
2675
|
scheduleRender(slot, input, imageId, zIndex, force, imageIds) {
|
|
2676
|
+
this.throwIfFatal();
|
|
1789
2677
|
const key = imageVisualFrameKey(input);
|
|
1790
2678
|
if (!force && this.isKnownKey(slot, key))
|
|
1791
2679
|
return;
|
|
@@ -1798,53 +2686,59 @@ class ImageVisualLayer {
|
|
|
1798
2686
|
requestId: ++this.requestId,
|
|
1799
2687
|
key
|
|
1800
2688
|
};
|
|
1801
|
-
|
|
1802
|
-
|
|
2689
|
+
slot.latestRequestId = request.requestId;
|
|
2690
|
+
if (!slot.worker) {
|
|
2691
|
+
this.renderInline(slot, request);
|
|
1803
2692
|
return;
|
|
1804
2693
|
}
|
|
1805
|
-
this.
|
|
2694
|
+
this.enqueueWorkerRender(slot, request);
|
|
1806
2695
|
}
|
|
1807
|
-
startWorker(slot) {
|
|
2696
|
+
startWorker(slot, name) {
|
|
1808
2697
|
const worker = createImageWorker();
|
|
1809
2698
|
if (!worker)
|
|
1810
2699
|
return;
|
|
1811
2700
|
slot.worker = worker;
|
|
1812
2701
|
worker.on("message", (message) => this.handleWorkerMessage(slot, message));
|
|
1813
|
-
worker.once("error", () =>
|
|
1814
|
-
|
|
2702
|
+
worker.once("error", (error) => {
|
|
2703
|
+
if (!this.destroyed)
|
|
2704
|
+
this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2705
|
+
});
|
|
2706
|
+
worker.once("exit", (code) => {
|
|
2707
|
+
if (!this.destroyed)
|
|
2708
|
+
this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} exited unexpectedly with code ${code}.`);
|
|
2709
|
+
});
|
|
1815
2710
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
this.renderSync(slot, queued);
|
|
2711
|
+
renderInline(slot, request) {
|
|
2712
|
+
try {
|
|
2713
|
+
const result = renderImageVisualFrame(request);
|
|
2714
|
+
if (result?.sequence && !this.isKnownKey(slot, result.key) && (!this.isOutputBackedUp() || pendingLayerKey(result.key) === "split")) {
|
|
2715
|
+
this.stageFrame({ key: result.key, sequence: result.sequence });
|
|
2716
|
+
this.onFrameReady?.();
|
|
2717
|
+
}
|
|
2718
|
+
} catch (error) {
|
|
2719
|
+
this.failWorker(String(request.layer ?? "visual"), error);
|
|
2720
|
+
}
|
|
1827
2721
|
}
|
|
1828
2722
|
enqueueWorkerRender(slot, request) {
|
|
1829
|
-
|
|
1830
|
-
this.renderSync(slot, request);
|
|
1831
|
-
return;
|
|
1832
|
-
}
|
|
2723
|
+
this.throwIfFatal();
|
|
1833
2724
|
if (slot.busy) {
|
|
1834
2725
|
slot.queuedRequest = request;
|
|
1835
2726
|
slot.queuedKey = request.key;
|
|
1836
2727
|
return;
|
|
1837
2728
|
}
|
|
1838
2729
|
this.dispatchWorkerRender(slot, request);
|
|
1839
|
-
}
|
|
1840
|
-
dispatchWorkerRender(slot, request) {
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
}
|
|
2730
|
+
}
|
|
2731
|
+
dispatchWorkerRender(slot, request) {
|
|
2732
|
+
this.throwIfFatal();
|
|
2733
|
+
if (!slot.worker)
|
|
2734
|
+
this.failWorker(String(request.layer ?? "visual"), new Error("worker is unavailable"));
|
|
1845
2735
|
slot.busy = true;
|
|
1846
2736
|
slot.inFlightKey = request.key;
|
|
1847
|
-
|
|
2737
|
+
try {
|
|
2738
|
+
slot.worker.postMessage(request);
|
|
2739
|
+
} catch (error) {
|
|
2740
|
+
this.failWorker(String(request.layer ?? "visual"), error);
|
|
2741
|
+
}
|
|
1848
2742
|
}
|
|
1849
2743
|
handleWorkerMessage(slot, message) {
|
|
1850
2744
|
if (this.destroyed || !message || typeof message !== "object")
|
|
@@ -1852,14 +2746,14 @@ class ImageVisualLayer {
|
|
|
1852
2746
|
const response = message;
|
|
1853
2747
|
slot.busy = false;
|
|
1854
2748
|
slot.inFlightKey = "";
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
2749
|
+
const isStale = response.requestId <= slot.clearedThroughRequestId || response.requestId < slot.latestRequestId;
|
|
2750
|
+
if (!isStale) {
|
|
2751
|
+
if (response.error)
|
|
2752
|
+
this.failWorker(pendingLayerKey(response.key), new Error(response.error));
|
|
2753
|
+
if (response.sequence && !this.isKnownKey(slot, response.key) && (!this.isOutputBackedUp() || pendingLayerKey(response.key) === "split")) {
|
|
2754
|
+
this.stageFrame({ key: response.key, sequence: response.sequence });
|
|
2755
|
+
this.onFrameReady?.();
|
|
2756
|
+
}
|
|
1863
2757
|
}
|
|
1864
2758
|
const next = slot.queuedRequest;
|
|
1865
2759
|
slot.queuedRequest = null;
|
|
@@ -1867,12 +2761,6 @@ class ImageVisualLayer {
|
|
|
1867
2761
|
if (next && !this.isKnownKey(slot, next.key))
|
|
1868
2762
|
this.dispatchWorkerRender(slot, next);
|
|
1869
2763
|
}
|
|
1870
|
-
renderSync(slot, request) {
|
|
1871
|
-
const result = renderImageVisualFrame(request);
|
|
1872
|
-
if (!result || this.isKnownKey(slot, result.key) || this.isOutputBackedUp())
|
|
1873
|
-
return;
|
|
1874
|
-
this.stageFrame(result);
|
|
1875
|
-
}
|
|
1876
2764
|
}
|
|
1877
2765
|
function pixelSizeForRenderer(renderer) {
|
|
1878
2766
|
const resolution = renderer.resolution;
|
|
@@ -1884,11 +2772,12 @@ function pixelSizeForRenderer(renderer) {
|
|
|
1884
2772
|
};
|
|
1885
2773
|
}
|
|
1886
2774
|
function forceModernImageVisuals() {
|
|
1887
|
-
return
|
|
2775
|
+
return getRigTerminalCapabilities().supportsKittyImages;
|
|
1888
2776
|
}
|
|
1889
2777
|
|
|
1890
2778
|
// packages/cli/src/app-opentui/render/text.ts
|
|
1891
|
-
import { TextAttributes as
|
|
2779
|
+
import { RGBA as RGBA3, TextAttributes as TextAttributes3, TextRenderable } from "@opentui/core";
|
|
2780
|
+
var TRANSPARENT2 = RGBA3.fromInts(0, 0, 0, 0);
|
|
1892
2781
|
function selectableMouseHandler(currentLineRef, onLineMouseDown) {
|
|
1893
2782
|
return (event) => {
|
|
1894
2783
|
const currentLine = currentLineRef();
|
|
@@ -1939,7 +2828,8 @@ function applyTextLine(renderable, line, top, left, width) {
|
|
|
1939
2828
|
renderable.width = width;
|
|
1940
2829
|
renderable.content = line?.styledText ?? line?.text ?? "";
|
|
1941
2830
|
renderable.fg = line?.fg ?? RIG_UI.ink2;
|
|
1942
|
-
renderable.
|
|
2831
|
+
renderable.bg = line?.bg ?? TRANSPARENT2;
|
|
2832
|
+
renderable.attributes = line?.bold ? TextAttributes3.BOLD : line?.dim ? TextAttributes3.DIM : 0;
|
|
1943
2833
|
}
|
|
1944
2834
|
function applyFlowTextLine(renderable, line, width) {
|
|
1945
2835
|
renderable.setRigSceneLine?.(line);
|
|
@@ -1948,7 +2838,8 @@ function applyFlowTextLine(renderable, line, width) {
|
|
|
1948
2838
|
renderable.width = Math.max(1, width);
|
|
1949
2839
|
renderable.content = line?.styledText ?? line?.text ?? "";
|
|
1950
2840
|
renderable.fg = line?.fg ?? RIG_UI.ink2;
|
|
1951
|
-
renderable.
|
|
2841
|
+
renderable.bg = line?.bg ?? TRANSPARENT2;
|
|
2842
|
+
renderable.attributes = line?.bold ? TextAttributes3.BOLD : line?.dim ? TextAttributes3.DIM : 0;
|
|
1952
2843
|
}
|
|
1953
2844
|
function clearTextLines(renderables, from = 0) {
|
|
1954
2845
|
for (let index = from;index < renderables.length; index += 1) {
|
|
@@ -1958,26 +2849,27 @@ function clearTextLines(renderables, from = 0) {
|
|
|
1958
2849
|
renderable.top = -1;
|
|
1959
2850
|
renderable.left = 0;
|
|
1960
2851
|
renderable.width = 1;
|
|
2852
|
+
renderable.bg = TRANSPARENT2;
|
|
1961
2853
|
}
|
|
1962
2854
|
}
|
|
1963
2855
|
|
|
1964
2856
|
// packages/cli/src/app-opentui/render/panels.ts
|
|
1965
|
-
import { RGBA as
|
|
1966
|
-
var
|
|
2857
|
+
import { RGBA as RGBA4, ScrollBoxRenderable } from "@opentui/core";
|
|
2858
|
+
var TRANSPARENT3 = RGBA4.fromInts(0, 0, 0, 0);
|
|
1967
2859
|
var MAX_PANEL_LINES = 420;
|
|
1968
2860
|
function hexToRgba(hex, alpha) {
|
|
1969
2861
|
const clean = hex.replace(/^#/, "");
|
|
1970
2862
|
const full = clean.length === 3 ? clean.split("").map((part) => `${part}${part}`).join("") : clean;
|
|
1971
2863
|
const value = Number.parseInt(full, 16);
|
|
1972
2864
|
if (!Number.isFinite(value))
|
|
1973
|
-
return
|
|
1974
|
-
return
|
|
2865
|
+
return RGBA4.fromInts(14, 15, 17, alpha);
|
|
2866
|
+
return RGBA4.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255, alpha);
|
|
1975
2867
|
}
|
|
1976
2868
|
function panelBackground(panel) {
|
|
1977
2869
|
return hexToRgba(panel.backgroundColor ?? RIG_UI.panel, panel.backgroundAlpha ?? 184);
|
|
1978
2870
|
}
|
|
1979
2871
|
function panelBorder(panel) {
|
|
1980
|
-
return panel.borderColor ? hexToRgba(panel.borderColor, panel.borderAlpha ?? 54) :
|
|
2872
|
+
return panel.borderColor ? hexToRgba(panel.borderColor, panel.borderAlpha ?? 54) : TRANSPARENT3;
|
|
1981
2873
|
}
|
|
1982
2874
|
function createScrollPanelLayer(renderer, id, onLineMouseDown) {
|
|
1983
2875
|
const panel = new ScrollBoxRenderable(renderer, {
|
|
@@ -1987,7 +2879,7 @@ function createScrollPanelLayer(renderer, id, onLineMouseDown) {
|
|
|
1987
2879
|
top: -2,
|
|
1988
2880
|
width: 1,
|
|
1989
2881
|
height: 1,
|
|
1990
|
-
backgroundColor:
|
|
2882
|
+
backgroundColor: TRANSPARENT3,
|
|
1991
2883
|
border: false,
|
|
1992
2884
|
shouldFill: true,
|
|
1993
2885
|
opacity: 1,
|
|
@@ -2003,7 +2895,7 @@ function createScrollPanelLayer(renderer, id, onLineMouseDown) {
|
|
|
2003
2895
|
visible: false,
|
|
2004
2896
|
width: 1,
|
|
2005
2897
|
trackOptions: {
|
|
2006
|
-
backgroundColor:
|
|
2898
|
+
backgroundColor: TRANSPARENT3,
|
|
2007
2899
|
foregroundColor: hexToRgba(RIG_UI.ink4, 84)
|
|
2008
2900
|
}
|
|
2009
2901
|
},
|
|
@@ -2011,18 +2903,24 @@ function createScrollPanelLayer(renderer, id, onLineMouseDown) {
|
|
|
2011
2903
|
height: 0,
|
|
2012
2904
|
visible: false,
|
|
2013
2905
|
trackOptions: {
|
|
2014
|
-
backgroundColor:
|
|
2015
|
-
foregroundColor:
|
|
2906
|
+
backgroundColor: TRANSPARENT3,
|
|
2907
|
+
foregroundColor: TRANSPARENT3
|
|
2016
2908
|
}
|
|
2017
2909
|
}
|
|
2018
2910
|
});
|
|
2019
2911
|
panel.__rigTextLines = [];
|
|
2020
|
-
|
|
2021
|
-
|
|
2912
|
+
panel.__rigRenderer = renderer;
|
|
2913
|
+
panel.__rigOnLineMouseDown = onLineMouseDown;
|
|
2914
|
+
return panel;
|
|
2915
|
+
}
|
|
2916
|
+
function ensurePanelLineCount(panel, desiredCount) {
|
|
2917
|
+
const target = Math.min(MAX_PANEL_LINES, Math.max(0, desiredCount));
|
|
2918
|
+
while (panel.__rigTextLines.length < target) {
|
|
2919
|
+
const index = panel.__rigTextLines.length;
|
|
2920
|
+
const line = createFlowTextLine(panel.__rigRenderer, `${panel.id}-line-${index}`, panel.__rigOnLineMouseDown);
|
|
2022
2921
|
panel.__rigTextLines.push(line);
|
|
2023
2922
|
panel.add(line);
|
|
2024
2923
|
}
|
|
2025
|
-
return panel;
|
|
2026
2924
|
}
|
|
2027
2925
|
function hidePanel(panel) {
|
|
2028
2926
|
panel.visible = false;
|
|
@@ -2030,7 +2928,7 @@ function hidePanel(panel) {
|
|
|
2030
2928
|
panel.left = 0;
|
|
2031
2929
|
panel.width = 1;
|
|
2032
2930
|
panel.height = 1;
|
|
2033
|
-
panel.backgroundColor =
|
|
2931
|
+
panel.backgroundColor = TRANSPARENT3;
|
|
2034
2932
|
panel.border = false;
|
|
2035
2933
|
panel.__rigPanelId = undefined;
|
|
2036
2934
|
panel.__rigTextLines.forEach((line) => applyFlowTextLine(line, undefined, 1));
|
|
@@ -2042,19 +2940,12 @@ function applyScrollPanels(panels, layout, scenePanels) {
|
|
|
2042
2940
|
hidePanel(renderable);
|
|
2043
2941
|
return;
|
|
2044
2942
|
}
|
|
2045
|
-
const
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
const
|
|
2051
|
-
const left = cardLeft + (panel.chrome === "ad-terminal" ? 1 : 0);
|
|
2052
|
-
const top = cardTop + headerHeight + (panel.chrome === "ad-terminal" ? 1 : 0);
|
|
2053
|
-
const width = Math.max(1, Math.min(layout.width - left, cardWidth - (panel.chrome === "ad-terminal" ? 2 : 0)));
|
|
2054
|
-
const height = Math.max(1, Math.min(layout.height - top, cardHeight - headerHeight - (panel.chrome === "ad-terminal" ? 2 : 0)));
|
|
2055
|
-
const paddingX = panel.paddingX ?? (panel.chrome === "ad-terminal" ? 5 : 2);
|
|
2056
|
-
const paddingY = panel.paddingY ?? (panel.chrome === "ad-terminal" ? 2 : 1);
|
|
2057
|
-
const contentWidth = Math.max(1, width - paddingX * 2 - 1);
|
|
2943
|
+
const contentRect = panelContentRect(layout, panel);
|
|
2944
|
+
if (!contentRect) {
|
|
2945
|
+
hidePanel(renderable);
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
const { left, top, width, height, paddingX, paddingY, contentWidth } = contentRect;
|
|
2058
2949
|
renderable.visible = true;
|
|
2059
2950
|
renderable.left = left;
|
|
2060
2951
|
renderable.top = top;
|
|
@@ -2062,7 +2953,7 @@ function applyScrollPanels(panels, layout, scenePanels) {
|
|
|
2062
2953
|
renderable.height = height;
|
|
2063
2954
|
renderable.zIndex = panel.zIndex ?? 6;
|
|
2064
2955
|
renderable.opacity = panel.opacity ?? 1;
|
|
2065
|
-
renderable.backgroundColor = panel.chrome === "ad-terminal" ?
|
|
2956
|
+
renderable.backgroundColor = panel.chrome === "ad-terminal" ? TRANSPARENT3 : panelBackground(panel);
|
|
2066
2957
|
renderable.border = panel.chrome === "ad-terminal" ? false : panel.border ?? false;
|
|
2067
2958
|
renderable.borderColor = panelBorder(panel);
|
|
2068
2959
|
renderable.paddingX = paddingX;
|
|
@@ -2075,56 +2966,31 @@ function applyScrollPanels(panels, layout, scenePanels) {
|
|
|
2075
2966
|
renderable.__rigPanelId = panel.id;
|
|
2076
2967
|
renderable.scrollTo(0);
|
|
2077
2968
|
}
|
|
2969
|
+
const lineCount = Math.min(panel.lines.length, MAX_PANEL_LINES);
|
|
2970
|
+
ensurePanelLineCount(renderable, lineCount);
|
|
2078
2971
|
renderable.__rigTextLines.forEach((line, lineIndex) => {
|
|
2079
|
-
applyFlowTextLine(line, panel.lines[lineIndex], contentWidth);
|
|
2972
|
+
applyFlowTextLine(line, lineIndex < lineCount ? panel.lines[lineIndex] : undefined, contentWidth);
|
|
2080
2973
|
});
|
|
2081
2974
|
});
|
|
2082
2975
|
}
|
|
2083
2976
|
|
|
2084
2977
|
// packages/cli/src/app-opentui/render/type-bar.ts
|
|
2085
|
-
import { InputRenderable, InputRenderableEvents, TextRenderable as TextRenderable2 } from "@opentui/core";
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
id: "rig-typebar-prefix",
|
|
2089
|
-
content: " rig \u203A",
|
|
2090
|
-
position: "absolute",
|
|
2091
|
-
left: 0,
|
|
2092
|
-
top: 0,
|
|
2093
|
-
fg: RIG_UI.lime,
|
|
2094
|
-
selectable: true
|
|
2095
|
-
});
|
|
2096
|
-
const input = new InputRenderable(renderer, {
|
|
2097
|
-
id: "rig-typebar-input",
|
|
2098
|
-
position: "absolute",
|
|
2099
|
-
left: 7,
|
|
2100
|
-
top: 0,
|
|
2101
|
-
width: 40,
|
|
2102
|
-
placeholder: "fleet \xB7 tasks \xB7 run next \xB7 help",
|
|
2103
|
-
textColor: RIG_UI.ink,
|
|
2104
|
-
cursorColor: RIG_UI.lime,
|
|
2105
|
-
cursorStyle: { style: "block", blinking: false },
|
|
2106
|
-
showCursor: true,
|
|
2107
|
-
selectable: true
|
|
2108
|
-
});
|
|
2109
|
-
const footer = new TextRenderable2(renderer, {
|
|
2110
|
-
id: "rig-footer",
|
|
2111
|
-
content: "",
|
|
2112
|
-
position: "absolute",
|
|
2113
|
-
left: 0,
|
|
2114
|
-
top: 1,
|
|
2115
|
-
width: "100%",
|
|
2116
|
-
fg: RIG_UI.ink4,
|
|
2117
|
-
selectable: true
|
|
2118
|
-
});
|
|
2119
|
-
input.focus();
|
|
2120
|
-
return { input, prefix, footer };
|
|
2121
|
-
}
|
|
2978
|
+
import { BoxRenderable, InputRenderable, InputRenderableEvents, RGBA as RGBA5, TextRenderable as TextRenderable2 } from "@opentui/core";
|
|
2979
|
+
var TYPEBAR_BG = RGBA5.fromInts(20, 25, 14, 224);
|
|
2980
|
+
var TYPEBAR_BORDER = RGBA5.fromInts(204, 255, 77, 92);
|
|
2122
2981
|
function positionTypeBar(renderables, input, footer, width, typeBarTop, footerTop) {
|
|
2123
|
-
renderables.
|
|
2982
|
+
renderables.background.visible = true;
|
|
2983
|
+
const cardTop = Math.max(0, typeBarTop - 1);
|
|
2984
|
+
renderables.background.left = 0;
|
|
2985
|
+
renderables.background.top = cardTop;
|
|
2986
|
+
renderables.background.width = width;
|
|
2987
|
+
renderables.background.height = Math.max(3, footerTop - cardTop + 1);
|
|
2988
|
+
renderables.prefix.content = " \u203A";
|
|
2989
|
+
renderables.prefix.left = 2;
|
|
2124
2990
|
renderables.prefix.top = typeBarTop;
|
|
2125
2991
|
renderables.input.top = typeBarTop;
|
|
2126
|
-
renderables.input.left =
|
|
2127
|
-
renderables.input.width = Math.max(10, width -
|
|
2992
|
+
renderables.input.left = 5;
|
|
2993
|
+
renderables.input.width = Math.max(10, width - 8);
|
|
2128
2994
|
renderables.input.placeholder = input.placeholder;
|
|
2129
2995
|
renderables.input.showCursor = true;
|
|
2130
2996
|
renderables.footer.top = footerTop;
|
|
@@ -2134,6 +3000,8 @@ function positionTypeBar(renderables, input, footer, width, typeBarTop, footerTo
|
|
|
2134
3000
|
renderables.input.focus();
|
|
2135
3001
|
}
|
|
2136
3002
|
function hideTypeBar(renderables, height) {
|
|
3003
|
+
renderables.background.visible = false;
|
|
3004
|
+
renderables.background.top = height + 1;
|
|
2137
3005
|
renderables.prefix.content = "";
|
|
2138
3006
|
renderables.prefix.top = height + 1;
|
|
2139
3007
|
renderables.input.value = "";
|
|
@@ -2145,17 +3013,22 @@ function hideTypeBar(renderables, height) {
|
|
|
2145
3013
|
renderables.footer.content = "";
|
|
2146
3014
|
renderables.footer.top = height + 1;
|
|
2147
3015
|
}
|
|
2148
|
-
function
|
|
2149
|
-
|
|
2150
|
-
|
|
3016
|
+
function safeFooterMessage(message) {
|
|
3017
|
+
const trimmed = message?.trim();
|
|
3018
|
+
if (!trimmed)
|
|
3019
|
+
return null;
|
|
3020
|
+
if (/https?:|\b[a-z0-9-]+\.[a-z]{2,}\b|\brig\b/i.test(trimmed))
|
|
3021
|
+
return null;
|
|
3022
|
+
return trimmed;
|
|
2151
3023
|
}
|
|
2152
3024
|
function formatFooter(footer, width) {
|
|
3025
|
+
const safeMessage = safeFooterMessage(footer.message);
|
|
2153
3026
|
const parts = [
|
|
2154
|
-
footer.project ?
|
|
2155
|
-
footer.server ?
|
|
2156
|
-
footer.auth ?
|
|
3027
|
+
footer.project ? "workspace" : null,
|
|
3028
|
+
footer.server ? "server selected" : null,
|
|
3029
|
+
footer.auth ? "auth ready" : null,
|
|
2157
3030
|
footer.run ? `run ${footer.run}` : null,
|
|
2158
|
-
|
|
3031
|
+
safeMessage
|
|
2159
3032
|
].filter((part) => Boolean(part));
|
|
2160
3033
|
return ` ${truncateText(parts.join(" \xB7 "), Math.max(8, width - 2))}`;
|
|
2161
3034
|
}
|
|
@@ -2167,9 +3040,6 @@ function line(text, options = {}) {
|
|
|
2167
3040
|
function blank() {
|
|
2168
3041
|
return { text: "" };
|
|
2169
3042
|
}
|
|
2170
|
-
function center(text, fg = RIG_UI.ink2, bold = false) {
|
|
2171
|
-
return { text, fg, bold, align: "center" };
|
|
2172
|
-
}
|
|
2173
3043
|
function deckRow(input) {
|
|
2174
3044
|
const prefix = input.active ? "\u25B8" : " ";
|
|
2175
3045
|
const label = input.label.toUpperCase().padEnd(10);
|
|
@@ -2180,6 +3050,9 @@ function deckRow(input) {
|
|
|
2180
3050
|
activateOnClick: input.activateOnClick
|
|
2181
3051
|
});
|
|
2182
3052
|
}
|
|
3053
|
+
function kv(label, value, fg = RIG_UI.ink2) {
|
|
3054
|
+
return line(` ${label.toUpperCase().padEnd(10)} ${value}`, { fg });
|
|
3055
|
+
}
|
|
2183
3056
|
function fitSceneLine(input, layout) {
|
|
2184
3057
|
const fitted = truncateText(input.text, layout.centerWidth);
|
|
2185
3058
|
return {
|
|
@@ -2194,9 +3067,52 @@ function makeSceneFrame(input) {
|
|
|
2194
3067
|
return input;
|
|
2195
3068
|
}
|
|
2196
3069
|
|
|
3070
|
+
// packages/cli/src/app-opentui/render/preloader.ts
|
|
3071
|
+
var PRELOADER_MIN_DURATION_MS = 520;
|
|
3072
|
+
var PRELOADER_MAX_DURATION_MS = 5000;
|
|
3073
|
+
function maybeStartPreloaderTransition(transition, scene, now = Date.now()) {
|
|
3074
|
+
if (transition.scene === scene)
|
|
3075
|
+
return;
|
|
3076
|
+
transition.scene = scene;
|
|
3077
|
+
transition.startedAtMs = now;
|
|
3078
|
+
transition.minUntilMs = now + PRELOADER_MIN_DURATION_MS;
|
|
3079
|
+
}
|
|
3080
|
+
function isPreloaderActive(transition, targetReady, now = Date.now()) {
|
|
3081
|
+
if (now < transition.minUntilMs)
|
|
3082
|
+
return true;
|
|
3083
|
+
if (!targetReady && now - transition.startedAtMs < PRELOADER_MAX_DURATION_MS)
|
|
3084
|
+
return true;
|
|
3085
|
+
return false;
|
|
3086
|
+
}
|
|
3087
|
+
function preloaderLine(line2, options = {}) {
|
|
3088
|
+
return { text: line2, bg: RIG_UI.bg, ...options };
|
|
3089
|
+
}
|
|
3090
|
+
function blockLine(text, leftPad, fg) {
|
|
3091
|
+
return preloaderLine(`${" ".repeat(Math.max(0, leftPad))}${text.padEnd(FLEET_GRID_WIDTH)}`, { fg });
|
|
3092
|
+
}
|
|
3093
|
+
function renderAsciiPreloaderFrame(scene, layout, tick) {
|
|
3094
|
+
const sprite = fleetRows(tick);
|
|
3095
|
+
const leftPad = Math.max(0, Math.floor((layout.width - FLEET_GRID_WIDTH) / 2));
|
|
3096
|
+
const blank2 = () => preloaderLine("");
|
|
3097
|
+
const body = [
|
|
3098
|
+
...sprite.map((text, index) => blockLine(text, leftPad, index >= 7 && index <= 18 ? RIG_UI.lime : RIG_UI.limeDim))
|
|
3099
|
+
];
|
|
3100
|
+
const topPadding = Math.max(0, Math.floor((layout.height - body.length) / 2));
|
|
3101
|
+
const bottomPadding = Math.max(0, layout.height - body.length - topPadding);
|
|
3102
|
+
return {
|
|
3103
|
+
scene,
|
|
3104
|
+
title: "Preloader",
|
|
3105
|
+
lines: [...Array.from({ length: topPadding }, blank2), ...body, ...Array.from({ length: bottomPadding }, blank2)],
|
|
3106
|
+
live: true,
|
|
3107
|
+
fullScreen: true,
|
|
3108
|
+
hideTypeBar: true,
|
|
3109
|
+
footer: { message: "" }
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
|
|
2197
3113
|
// packages/cli/src/app-opentui/drone.ts
|
|
2198
|
-
import { RGBA as
|
|
2199
|
-
var
|
|
3114
|
+
import { RGBA as RGBA6, StyledText as StyledText2, TextAttributes as TextAttributes4 } from "@opentui/core";
|
|
3115
|
+
var MINI_DRONE2 = [
|
|
2200
3116
|
"(!!!) (!!!)",
|
|
2201
3117
|
" \\%==%/ ",
|
|
2202
3118
|
" %%?%% ",
|
|
@@ -2211,13 +3127,13 @@ var LEAD_MARK = [
|
|
|
2211
3127
|
var BLADE_FRAMES = ["---", "\\\\\\", "|||", "///"];
|
|
2212
3128
|
var EYE_FRAMES = ["o", "@", "\u2022", "."];
|
|
2213
3129
|
var COLOR = {
|
|
2214
|
-
body:
|
|
2215
|
-
mini:
|
|
2216
|
-
rotor:
|
|
2217
|
-
path:
|
|
2218
|
-
eye:
|
|
2219
|
-
dim:
|
|
2220
|
-
ink:
|
|
3130
|
+
body: RGBA6.fromHex(RIG_UI.lime),
|
|
3131
|
+
mini: RGBA6.fromHex(RIG_UI.limeDim),
|
|
3132
|
+
rotor: RGBA6.fromHex(RIG_UI.cyan),
|
|
3133
|
+
path: RGBA6.fromHex(RIG_UI.cyan),
|
|
3134
|
+
eye: RGBA6.fromHex(RIG_UI.ink),
|
|
3135
|
+
dim: RGBA6.fromHex(RIG_UI.ink4),
|
|
3136
|
+
ink: RGBA6.fromHex(RIG_UI.ink2)
|
|
2221
3137
|
};
|
|
2222
3138
|
function bladeForTick(tick, phase = 0) {
|
|
2223
3139
|
return BLADE_FRAMES[(Math.floor(tick / 3) + phase) % BLADE_FRAMES.length];
|
|
@@ -2227,12 +3143,12 @@ function eyeForTick(tick, phase = 0) {
|
|
|
2227
3143
|
return pulse > 0.55 ? EYE_FRAMES[1] : pulse > 0.1 ? EYE_FRAMES[0] : pulse > -0.45 ? EYE_FRAMES[2] : EYE_FRAMES[3];
|
|
2228
3144
|
}
|
|
2229
3145
|
function chunk2(text, fg, bold = false, dim = false) {
|
|
2230
|
-
let attributes =
|
|
3146
|
+
let attributes = TextAttributes4.NONE;
|
|
2231
3147
|
if (bold)
|
|
2232
|
-
attributes |=
|
|
3148
|
+
attributes |= TextAttributes4.BOLD;
|
|
2233
3149
|
if (dim)
|
|
2234
|
-
attributes |=
|
|
2235
|
-
return { __isChunk: true, text, fg, ...attributes !==
|
|
3150
|
+
attributes |= TextAttributes4.DIM;
|
|
3151
|
+
return { __isChunk: true, text, fg, ...attributes !== TextAttributes4.NONE ? { attributes } : {} };
|
|
2236
3152
|
}
|
|
2237
3153
|
function styledLine(text, colorFor) {
|
|
2238
3154
|
const chunks = [];
|
|
@@ -2244,600 +3160,312 @@ function styledLine(text, colorFor) {
|
|
|
2244
3160
|
chunks.push(chunk2(run, COLOR[runColor], runColor === "body" || runColor === "eye", runColor === "dim"));
|
|
2245
3161
|
run = "";
|
|
2246
3162
|
};
|
|
2247
|
-
for (const char of text) {
|
|
2248
|
-
const next = colorFor(char);
|
|
2249
|
-
if (next !== runColor) {
|
|
2250
|
-
flush();
|
|
2251
|
-
runColor = next;
|
|
2252
|
-
}
|
|
2253
|
-
run += char;
|
|
2254
|
-
}
|
|
2255
|
-
flush();
|
|
2256
|
-
return new StyledText(chunks.length > 0 ? chunks : [chunk2("", COLOR.ink)]);
|
|
2257
|
-
}
|
|
2258
|
-
function droneColor(char) {
|
|
2259
|
-
if (char === "?" || char === "o" || char === "@" || char === "\u2022")
|
|
2260
|
-
return "eye";
|
|
2261
|
-
if (char === "%" || char === "=")
|
|
2262
|
-
return "mini";
|
|
2263
|
-
if (char === "$")
|
|
2264
|
-
return "body";
|
|
2265
|
-
if (char === "(" || char === ")" || char === "/" || char === "\\" || char === "|" || char === "-")
|
|
2266
|
-
return "rotor";
|
|
2267
|
-
if (char === "\xB7" || char === "\u2022" || char === "'" || char === ".")
|
|
2268
|
-
return "path";
|
|
2269
|
-
if (char.trim() === "")
|
|
2270
|
-
return "dim";
|
|
2271
|
-
return "ink";
|
|
2272
|
-
}
|
|
2273
|
-
function motionColor(char) {
|
|
2274
|
-
if (char === "\u25CF" || char === "\u25C6" || char === "\u25C9")
|
|
2275
|
-
return "body";
|
|
2276
|
-
if (char === "\u2022" || char === "\xB7" || char === "\u2500" || char === "\u2502" || char === "\u2571" || char === "\u2572" || char === "\u256D" || char === "\u256E" || char === "\u2570" || char === "\u256F")
|
|
2277
|
-
return "path";
|
|
2278
|
-
if (char === "\u258C" || char === "\u2590" || char === "\u2581" || char === "\u2594")
|
|
2279
|
-
return "mini";
|
|
2280
|
-
return droneColor(char);
|
|
2281
|
-
}
|
|
2282
|
-
function materializeDrone(lines, tick, phase = 0) {
|
|
2283
|
-
const blade = bladeForTick(tick, phase);
|
|
2284
|
-
const eye = eyeForTick(tick, phase);
|
|
2285
|
-
return lines.map((line2) => line2.replaceAll("!!!", blade).replaceAll("?", eye));
|
|
2286
|
-
}
|
|
2287
|
-
function brandFleetLines(tick, align = "center") {
|
|
2288
|
-
const left = materializeDrone(MINI_DRONE, tick, 0);
|
|
2289
|
-
const right = materializeDrone(MINI_DRONE, tick, 2);
|
|
2290
|
-
const lead = materializeDrone(LEAD_MARK, tick, 1);
|
|
2291
|
-
const packet = ["\xB7", "\u2022", "\u25CF", "\u2022"][Math.floor(tick / 2) % 4];
|
|
2292
|
-
const bridge = [
|
|
2293
|
-
` ${left[0]} ${lead[0]} ${right[0]}`,
|
|
2294
|
-
` ${left[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${lead[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${right[1]}`,
|
|
2295
|
-
` ${left[2]} \xB7' '${lead[2]}' '\xB7 ${right[2]}`,
|
|
2296
|
-
` ${left[3]} \u2572 signal bus \u2571 ${right[3]}`,
|
|
2297
|
-
` ${left[4]} \xB7\u2500\u2500\u2500${packet}\u2500\u2500\u2500\u2500\u2500\u2500${packet}\u2500\u2500\u2500\xB7 ${right[4]}`
|
|
2298
|
-
];
|
|
2299
|
-
return bridge.map((text) => ({
|
|
2300
|
-
text,
|
|
2301
|
-
styledText: styledLine(text, motionColor),
|
|
2302
|
-
fg: RIG_UI.limeDim,
|
|
2303
|
-
align
|
|
2304
|
-
}));
|
|
2305
|
-
}
|
|
2306
|
-
function errorDrone(tick) {
|
|
2307
|
-
return brandFleetLines(tick).map((line2, index) => ({
|
|
2308
|
-
...line2,
|
|
2309
|
-
fg: index % 2 === 0 ? RIG_UI.red : line2.fg
|
|
2310
|
-
}));
|
|
2311
|
-
}
|
|
2312
|
-
|
|
2313
|
-
// packages/cli/src/app-opentui/scenes/error.ts
|
|
2314
|
-
function renderErrorScene(state) {
|
|
2315
|
-
const message = state.error?.message ?? "Unknown Rig app error";
|
|
2316
|
-
const hint = state.error?.hint ?? "type main, help, or retry after fixing the issue";
|
|
2317
|
-
return makeSceneFrame({
|
|
2318
|
-
scene: "error",
|
|
2319
|
-
title: "Rig error",
|
|
2320
|
-
lines: [
|
|
2321
|
-
...errorDrone(state.tick).slice(2, 16),
|
|
2322
|
-
blank(),
|
|
2323
|
-
center("DRONE SIGNAL DEGRADED", RIG_UI.red, true),
|
|
2324
|
-
center(message, RIG_UI.ink2),
|
|
2325
|
-
blank(),
|
|
2326
|
-
center(hint, RIG_UI.limeDim)
|
|
2327
|
-
],
|
|
2328
|
-
typeBarPlaceholder: hint,
|
|
2329
|
-
live: true
|
|
2330
|
-
});
|
|
2331
|
-
}
|
|
2332
|
-
|
|
2333
|
-
// packages/cli/src/app-opentui/scenes/help.ts
|
|
2334
|
-
var COMMANDS = [
|
|
2335
|
-
["runs", "show runs"],
|
|
2336
|
-
["tasks", "show task source"],
|
|
2337
|
-
["type in Runs", "search loaded runs"],
|
|
2338
|
-
["type in Tasks", "search loaded tasks"],
|
|
2339
|
-
["tasks open", "filter to open tasks"],
|
|
2340
|
-
["tasks all", "show every task returned by the source"],
|
|
2341
|
-
["tasks mine", "show tasks assigned to @me"],
|
|
2342
|
-
["tasks blocked", "show blocked work"],
|
|
2343
|
-
["tasks closed", "show closed work"],
|
|
2344
|
-
["tasks sort priority", "rank by priority"],
|
|
2345
|
-
["tasks sort recency", "rank by update time"],
|
|
2346
|
-
["tasks sort status", "group by status"],
|
|
2347
|
-
["run <task-id>", "dispatch selected task id"],
|
|
2348
|
-
["run next", "dispatch next runnable task"],
|
|
2349
|
-
["attach <run>", "open bundled Pi for a run"],
|
|
2350
|
-
["tab / \u2192", "next screen"],
|
|
2351
|
-
["\u2190", "previous screen"],
|
|
2352
|
-
["\u2191 \u2193", "move selection in Runs/Tasks"],
|
|
2353
|
-
["j k", "move selection outside search-first list screens"],
|
|
2354
|
-
["enter", "activate selected row"],
|
|
2355
|
-
["mouse wheel", "scroll panel under cursor"],
|
|
2356
|
-
["ctrl-]", "detach from embedded Pi"],
|
|
2357
|
-
["ctrl-c / q", "exit"]
|
|
2358
|
-
];
|
|
2359
|
-
function panelWidth(layout) {
|
|
2360
|
-
return layout?.centerWidth ?? 100;
|
|
2361
|
-
}
|
|
2362
|
-
function panelHeight(layout, top = 3) {
|
|
2363
|
-
return Math.max(4, (layout?.centerHeight ?? 24) - top);
|
|
2364
|
-
}
|
|
2365
|
-
function clip(value, width) {
|
|
2366
|
-
if (width <= 0)
|
|
2367
|
-
return "";
|
|
2368
|
-
return value.length <= width ? value : `${value.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
2369
|
-
}
|
|
2370
|
-
function commandRow(width, label, detail, index) {
|
|
2371
|
-
const keyWidth = Math.min(22, Math.max(12, Math.floor(width * 0.28)));
|
|
2372
|
-
const key = clip(label, keyWidth).padEnd(keyWidth);
|
|
2373
|
-
return line(`${key} ${clip(detail, Math.max(8, width - keyWidth - 4))}`, { fg: index < 3 ? RIG_UI.ink : RIG_UI.ink2 });
|
|
2374
|
-
}
|
|
2375
|
-
function unsupportedCommand(state) {
|
|
2376
|
-
const value = state.intent.action.payload?.unsupported ?? state.intent.action.payload?.unknown;
|
|
2377
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
2378
|
-
}
|
|
2379
|
-
function renderHelpScene(state, layout) {
|
|
2380
|
-
const width = panelWidth(layout);
|
|
2381
|
-
const unsupported = unsupportedCommand(state);
|
|
2382
|
-
const panelTop = 0;
|
|
2383
|
-
const panelLines = [
|
|
2384
|
-
...unsupported ? [
|
|
2385
|
-
line(`${unsupported} uses the plain CLI path.`, { fg: RIG_UI.yellow, bold: true }),
|
|
2386
|
-
line("Exit and rerun with RIG_PLAIN=1 if you want legacy text output.", { fg: RIG_UI.ink3 }),
|
|
2387
|
-
line("", { fg: RIG_UI.ink3 })
|
|
2388
|
-
] : [],
|
|
2389
|
-
line("COMMAND ACTION", { fg: RIG_UI.ink3, bold: true }),
|
|
2390
|
-
line("", { fg: RIG_UI.ink3 }),
|
|
2391
|
-
...COMMANDS.map(([label, detail], index) => commandRow(width - 6, label, detail, index))
|
|
2392
|
-
];
|
|
2393
|
-
return makeSceneFrame({
|
|
2394
|
-
scene: "help",
|
|
2395
|
-
title: "Rig help",
|
|
2396
|
-
lines: [
|
|
2397
|
-
line("", { fg: RIG_UI.ink4 }),
|
|
2398
|
-
line(" rig help", { fg: RIG_UI.ink3 })
|
|
2399
|
-
],
|
|
2400
|
-
panels: [{
|
|
2401
|
-
id: "help-reference",
|
|
2402
|
-
top: panelTop,
|
|
2403
|
-
width,
|
|
2404
|
-
height: panelHeight(layout, panelTop),
|
|
2405
|
-
lines: panelLines,
|
|
2406
|
-
backgroundColor: RIG_UI.panel,
|
|
2407
|
-
backgroundAlpha: 184,
|
|
2408
|
-
opacity: 1,
|
|
2409
|
-
border: false,
|
|
2410
|
-
chrome: "ad-terminal",
|
|
2411
|
-
headerText: "rig help",
|
|
2412
|
-
headerHeight: 3,
|
|
2413
|
-
paddingX: layout?.compact ? 2 : 3,
|
|
2414
|
-
paddingY: 1
|
|
2415
|
-
}],
|
|
2416
|
-
typeBarPlaceholder: "runs \xB7 tasks \xB7 help \xB7 attach <run>",
|
|
2417
|
-
footer: { message: "help" },
|
|
2418
|
-
live: true
|
|
2419
|
-
});
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
|
-
// packages/cli/src/app-opentui/scenes/main.ts
|
|
2423
|
-
var CONFIGURED_OPTIONS = [
|
|
2424
|
-
["runs", "run list and attach"],
|
|
2425
|
-
["tasks", "task source and dispatch"],
|
|
2426
|
-
["help", "command reference"]
|
|
2427
|
-
];
|
|
2428
|
-
function renderMainScene(state) {
|
|
2429
|
-
const configured = state.data.projectConfigured === true;
|
|
2430
|
-
const selected = configured ? Math.max(0, Math.min(CONFIGURED_OPTIONS.length - 1, state.selection.index)) : Math.max(0, Math.min(1, state.selection.index));
|
|
2431
|
-
const lines = [
|
|
2432
|
-
center(configured ? "rig" : "rig setup", RIG_UI.ink, true),
|
|
2433
|
-
center(configured ? "runs \xB7 tasks \xB7 help" : "initialize this repo", RIG_UI.ink3),
|
|
2434
|
-
blank()
|
|
2435
|
-
];
|
|
2436
|
-
if (!configured) {
|
|
2437
|
-
lines.push(deckRow({ label: "init", detail: "create config and server link", index: 0, active: selected === 0 }), deckRow({ label: "help", detail: "show command reference", index: 1, active: selected === 1 }), blank(), line("enter opens selection", { fg: RIG_UI.ink4, align: "center" }));
|
|
2438
|
-
} else {
|
|
2439
|
-
CONFIGURED_OPTIONS.forEach(([label, detail], index) => {
|
|
2440
|
-
lines.push(deckRow({ label, detail, index, active: index === selected }));
|
|
2441
|
-
});
|
|
2442
|
-
lines.push(blank(), line(` ${state.actionLabel ?? "ready"}`, { fg: state.status === "action" ? RIG_UI.cyan : RIG_UI.ink4 }));
|
|
2443
|
-
}
|
|
2444
|
-
return makeSceneFrame({
|
|
2445
|
-
scene: "main",
|
|
2446
|
-
title: "Rig",
|
|
2447
|
-
lines,
|
|
2448
|
-
typeBarPlaceholder: configured ? "runs \xB7 tasks \xB7 help" : "init \xB7 help",
|
|
2449
|
-
live: true
|
|
2450
|
-
});
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
|
-
// packages/cli/src/app-opentui/pi-pty-host.ts
|
|
2454
|
-
import { fileURLToPath } from "url";
|
|
2455
|
-
import { basename } from "path";
|
|
2456
|
-
import { RGBA as RGBA4, StyledText as StyledText2, TextAttributes as TextAttributes4 } from "@opentui/core";
|
|
2457
|
-
import { Terminal as XtermTerminal } from "@xterm/headless";
|
|
2458
|
-
var MIN_COLS = 40;
|
|
2459
|
-
var MIN_ROWS = 12;
|
|
2460
|
-
var MAX_ROWS = 300;
|
|
2461
|
-
var MAX_SNAPSHOT_LINES = 360;
|
|
2462
|
-
var STYLED_SNAPSHOT_MARGIN = 24;
|
|
2463
|
-
var SNAPSHOT_DELAY_MS = 33;
|
|
2464
|
-
var fallbackChildScriptPath = fileURLToPath(new URL("./pi-host-child.ts", import.meta.url));
|
|
2465
|
-
var activeHost = null;
|
|
2466
|
-
function clampCols(cols) {
|
|
2467
|
-
return Math.max(MIN_COLS, Math.trunc(cols || 100));
|
|
2468
|
-
}
|
|
2469
|
-
function clampRows(rows) {
|
|
2470
|
-
return Math.max(MIN_ROWS, Math.min(MAX_ROWS, Math.trunc(rows || 35)));
|
|
2471
|
-
}
|
|
2472
|
-
var XTERM_COLOR_MODE_INDEXED_16 = 16777216;
|
|
2473
|
-
var XTERM_COLOR_MODE_INDEXED_256 = 33554432;
|
|
2474
|
-
var XTERM_COLOR_MODE_RGB = 50331648;
|
|
2475
|
-
function rgbaFromXtermColor(mode, value) {
|
|
2476
|
-
if (mode === 1 || mode === 2 || mode === XTERM_COLOR_MODE_INDEXED_16 || mode === XTERM_COLOR_MODE_INDEXED_256) {
|
|
2477
|
-
return RGBA4.fromIndex(value);
|
|
2478
|
-
}
|
|
2479
|
-
if (mode === 3 || mode === XTERM_COLOR_MODE_RGB) {
|
|
2480
|
-
return RGBA4.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255);
|
|
2481
|
-
}
|
|
2482
|
-
return;
|
|
2483
|
-
}
|
|
2484
|
-
function textAttributesFromCell(cell) {
|
|
2485
|
-
let attributes = TextAttributes4.NONE;
|
|
2486
|
-
if (cell.isBold())
|
|
2487
|
-
attributes |= TextAttributes4.BOLD;
|
|
2488
|
-
if (cell.isDim())
|
|
2489
|
-
attributes |= TextAttributes4.DIM;
|
|
2490
|
-
if (cell.isItalic())
|
|
2491
|
-
attributes |= TextAttributes4.ITALIC;
|
|
2492
|
-
if (cell.isUnderline())
|
|
2493
|
-
attributes |= TextAttributes4.UNDERLINE;
|
|
2494
|
-
if (cell.isBlink())
|
|
2495
|
-
attributes |= TextAttributes4.BLINK;
|
|
2496
|
-
if (cell.isInverse())
|
|
2497
|
-
attributes |= TextAttributes4.INVERSE;
|
|
2498
|
-
if (cell.isInvisible())
|
|
2499
|
-
attributes |= TextAttributes4.HIDDEN;
|
|
2500
|
-
if (cell.isStrikethrough())
|
|
2501
|
-
attributes |= TextAttributes4.STRIKETHROUGH;
|
|
2502
|
-
return attributes;
|
|
2503
|
-
}
|
|
2504
|
-
function sameRgba(a, b) {
|
|
2505
|
-
if (!a && !b)
|
|
2506
|
-
return true;
|
|
2507
|
-
return Boolean(a && b && a.equals(b));
|
|
2508
|
-
}
|
|
2509
|
-
function sameStyle(a, b) {
|
|
2510
|
-
return sameRgba(a.fg, b.fg) && sameRgba(a.bg, b.bg) && (a.attributes ?? 0) === (b.attributes ?? 0);
|
|
2511
|
-
}
|
|
2512
|
-
function styleFromCell(cell) {
|
|
2513
|
-
return {
|
|
2514
|
-
fg: rgbaFromXtermColor(cell.getFgColorMode(), cell.getFgColor()),
|
|
2515
|
-
bg: rgbaFromXtermColor(cell.getBgColorMode(), cell.getBgColor()),
|
|
2516
|
-
attributes: textAttributesFromCell(cell)
|
|
2517
|
-
};
|
|
2518
|
-
}
|
|
2519
|
-
function lineToStyledText(line2, cols) {
|
|
2520
|
-
if (!line2)
|
|
2521
|
-
return new StyledText2([{ __isChunk: true, text: "" }]);
|
|
2522
|
-
const chunks = [];
|
|
2523
|
-
let run = "";
|
|
2524
|
-
let runStyle = null;
|
|
2525
|
-
const flush = () => {
|
|
2526
|
-
if (!run)
|
|
2527
|
-
return;
|
|
2528
|
-
chunks.push({
|
|
2529
|
-
__isChunk: true,
|
|
2530
|
-
text: run,
|
|
2531
|
-
...runStyle?.fg ? { fg: runStyle.fg } : {},
|
|
2532
|
-
...runStyle?.bg ? { bg: runStyle.bg } : {},
|
|
2533
|
-
...(runStyle?.attributes ?? 0) !== 0 ? { attributes: runStyle.attributes } : {}
|
|
2534
|
-
});
|
|
2535
|
-
run = "";
|
|
2536
|
-
};
|
|
2537
|
-
for (let index = 0;index < cols; index += 1) {
|
|
2538
|
-
const cell = line2.getCell(index);
|
|
2539
|
-
if (!cell) {
|
|
2540
|
-
if (runStyle !== null)
|
|
2541
|
-
flush();
|
|
2542
|
-
runStyle = null;
|
|
2543
|
-
run += " ";
|
|
2544
|
-
continue;
|
|
2545
|
-
}
|
|
2546
|
-
if (cell.getWidth() === 0)
|
|
2547
|
-
continue;
|
|
2548
|
-
const style = styleFromCell(cell);
|
|
2549
|
-
if (!runStyle || !sameStyle(runStyle, style)) {
|
|
3163
|
+
for (const char of text) {
|
|
3164
|
+
const next = colorFor(char);
|
|
3165
|
+
if (next !== runColor) {
|
|
2550
3166
|
flush();
|
|
2551
|
-
|
|
3167
|
+
runColor = next;
|
|
2552
3168
|
}
|
|
2553
|
-
run +=
|
|
3169
|
+
run += char;
|
|
2554
3170
|
}
|
|
2555
3171
|
flush();
|
|
2556
|
-
return new StyledText2(chunks.length > 0 ? chunks : [
|
|
2557
|
-
}
|
|
2558
|
-
function childCommandPrefix() {
|
|
2559
|
-
const execName = basename(process.execPath).toLowerCase();
|
|
2560
|
-
const currentEntry = process.argv[1];
|
|
2561
|
-
if ((execName === "bun" || execName === "bun.exe") && currentEntry) {
|
|
2562
|
-
return [process.execPath, currentEntry, "__opentui-pi-host"];
|
|
2563
|
-
}
|
|
2564
|
-
if (currentEntry)
|
|
2565
|
-
return [process.execPath, "__opentui-pi-host"];
|
|
2566
|
-
return [process.execPath, fallbackChildScriptPath];
|
|
3172
|
+
return new StyledText2(chunks.length > 0 ? chunks : [chunk2("", COLOR.ink)]);
|
|
2567
3173
|
}
|
|
2568
|
-
function
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
};
|
|
3174
|
+
function droneColor(char) {
|
|
3175
|
+
if (char === "?" || char === "o" || char === "@" || char === "\u2022")
|
|
3176
|
+
return "eye";
|
|
3177
|
+
if (char === "%" || char === "=")
|
|
3178
|
+
return "mini";
|
|
3179
|
+
if (char === "$")
|
|
3180
|
+
return "body";
|
|
3181
|
+
if (char === "(" || char === ")" || char === "/" || char === "\\" || char === "|" || char === "-")
|
|
3182
|
+
return "rotor";
|
|
3183
|
+
if (char === "\xB7" || char === "\u2022" || char === "'" || char === ".")
|
|
3184
|
+
return "path";
|
|
3185
|
+
if (char.trim() === "")
|
|
3186
|
+
return "dim";
|
|
3187
|
+
return "ink";
|
|
2583
3188
|
}
|
|
2584
|
-
function
|
|
2585
|
-
|
|
3189
|
+
function motionColor(char) {
|
|
3190
|
+
if (char === "\u25CF" || char === "\u25C6" || char === "\u25C9")
|
|
3191
|
+
return "body";
|
|
3192
|
+
if (char === "\u2022" || char === "\xB7" || char === "\u2500" || char === "\u2502" || char === "\u2571" || char === "\u2572" || char === "\u256D" || char === "\u256E" || char === "\u2570" || char === "\u256F")
|
|
3193
|
+
return "path";
|
|
3194
|
+
if (char === "\u258C" || char === "\u2590" || char === "\u2581" || char === "\u2594")
|
|
3195
|
+
return "mini";
|
|
3196
|
+
return droneColor(char);
|
|
2586
3197
|
}
|
|
2587
|
-
function
|
|
2588
|
-
|
|
2589
|
-
|
|
3198
|
+
function materializeDrone(lines, tick, phase = 0) {
|
|
3199
|
+
const blade = bladeForTick(tick, phase);
|
|
3200
|
+
const eye = eyeForTick(tick, phase);
|
|
3201
|
+
return lines.map((line2) => line2.replaceAll("!!!", blade).replaceAll("?", eye));
|
|
2590
3202
|
}
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
}
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
3203
|
+
function brandFleetLines(tick, align = "center") {
|
|
3204
|
+
const left = materializeDrone(MINI_DRONE2, tick, 0);
|
|
3205
|
+
const right = materializeDrone(MINI_DRONE2, tick, 2);
|
|
3206
|
+
const lead = materializeDrone(LEAD_MARK, tick, 1);
|
|
3207
|
+
const packet = ["\xB7", "\u2022", "\u25CF", "\u2022"][Math.floor(tick / 2) % 4];
|
|
3208
|
+
const bridge = [
|
|
3209
|
+
` ${left[0]} ${lead[0]} ${right[0]}`,
|
|
3210
|
+
` ${left[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${lead[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${right[1]}`,
|
|
3211
|
+
` ${left[2]} \xB7' '${lead[2]}' '\xB7 ${right[2]}`,
|
|
3212
|
+
` ${left[3]} \u2572 signal bus \u2571 ${right[3]}`,
|
|
3213
|
+
` ${left[4]} \xB7\u2500\u2500\u2500${packet}\u2500\u2500\u2500\u2500\u2500\u2500${packet}\u2500\u2500\u2500\xB7 ${right[4]}`
|
|
3214
|
+
];
|
|
3215
|
+
return bridge.map((text) => ({
|
|
3216
|
+
text,
|
|
3217
|
+
styledText: styledLine(text, motionColor),
|
|
3218
|
+
fg: RIG_UI.limeDim,
|
|
3219
|
+
align
|
|
3220
|
+
}));
|
|
3221
|
+
}
|
|
3222
|
+
function errorDrone(tick) {
|
|
3223
|
+
return brandFleetLines(tick).map((line2, index) => ({
|
|
3224
|
+
...line2,
|
|
3225
|
+
fg: index % 2 === 0 ? RIG_UI.red : line2.fg
|
|
3226
|
+
}));
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
// packages/cli/src/app-opentui/scenes/error.ts
|
|
3230
|
+
function renderErrorScene(state) {
|
|
3231
|
+
const message = state.error?.message ?? "Unknown app error";
|
|
3232
|
+
const hint = state.error?.hint ?? "Use the actions below to recover.";
|
|
3233
|
+
const selected = Math.max(0, Math.min(3, state.selection.index));
|
|
3234
|
+
return makeSceneFrame({
|
|
3235
|
+
scene: "error",
|
|
3236
|
+
title: "Error",
|
|
3237
|
+
lines: [
|
|
3238
|
+
...errorDrone(state.tick).slice(2, 10)
|
|
3239
|
+
],
|
|
3240
|
+
panels: [{
|
|
3241
|
+
id: "error-recovery",
|
|
3242
|
+
top: 2,
|
|
3243
|
+
width: 96,
|
|
3244
|
+
height: 18,
|
|
3245
|
+
lines: [
|
|
3246
|
+
line("DRONE SIGNAL DEGRADED", { fg: RIG_UI.red, bold: true }),
|
|
3247
|
+
line(message, { fg: RIG_UI.ink2 }),
|
|
3248
|
+
...hint ? [line(hint, { fg: RIG_UI.yellow })] : [],
|
|
3249
|
+
line("", { fg: RIG_UI.ink3 }),
|
|
3250
|
+
line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
|
|
3251
|
+
deckRow({ label: "main", detail: "return to dashboard", index: 0, active: selected === 0, activateOnClick: true }),
|
|
3252
|
+
deckRow({ label: "doctor", detail: "run diagnostics", index: 1, active: selected === 1, activateOnClick: true }),
|
|
3253
|
+
deckRow({ label: "server", detail: "open server controls", index: 2, active: selected === 2, activateOnClick: true }),
|
|
3254
|
+
deckRow({ label: "repair", detail: "repair project/server/auth link", index: 3, active: selected === 3, activateOnClick: true })
|
|
3255
|
+
],
|
|
3256
|
+
backgroundColor: RIG_UI.panel,
|
|
3257
|
+
backgroundAlpha: 184,
|
|
3258
|
+
opacity: 1,
|
|
3259
|
+
border: false,
|
|
3260
|
+
chrome: "ad-terminal",
|
|
3261
|
+
headerText: "recovery",
|
|
3262
|
+
headerHeight: 3,
|
|
3263
|
+
paddingX: 3,
|
|
3264
|
+
paddingY: 1
|
|
3265
|
+
}],
|
|
3266
|
+
footer: { message: "enter/click a recovery action" },
|
|
3267
|
+
hideTypeBar: true,
|
|
3268
|
+
live: true
|
|
3269
|
+
});
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
// packages/cli/src/app-opentui/scenes/help.ts
|
|
3273
|
+
var ACTIONS = [
|
|
3274
|
+
["runs", "show live/completed runs"],
|
|
3275
|
+
["tasks", "browse task source and dispatch work"],
|
|
3276
|
+
["run next", "dispatch next runnable task"],
|
|
3277
|
+
["server", "local/remote server controls"],
|
|
3278
|
+
["init", "setup/reconfigure/repair wizard"],
|
|
3279
|
+
["doctor", "diagnose project/server/auth"],
|
|
3280
|
+
["github", "GitHub auth/repo actions"],
|
|
3281
|
+
["plugin", "plugin and source actions"],
|
|
3282
|
+
["setup", "bootstrap/check/preflight"],
|
|
3283
|
+
["inspect", "inspect project/server state"],
|
|
3284
|
+
["stats", "run/task metrics"],
|
|
3285
|
+
["repo", "repo utilities"],
|
|
3286
|
+
["profile", "profile/config actions"],
|
|
3287
|
+
["review", "review automation"],
|
|
3288
|
+
["browser", "browser tooling/wizards"],
|
|
3289
|
+
["pi", "Pi install/attach/status"],
|
|
3290
|
+
["queue", "queue operations"],
|
|
3291
|
+
["agent", "agent/runtime operations"],
|
|
3292
|
+
["inspector", "inspector tools"],
|
|
3293
|
+
["dist", "distribution helpers"],
|
|
3294
|
+
["workspace", "workspace/project utilities"],
|
|
3295
|
+
["remote", "remote endpoint utilities"],
|
|
3296
|
+
["git", "git helper actions"],
|
|
3297
|
+
["harness", "harness operations"],
|
|
3298
|
+
["test", "test helpers"]
|
|
3299
|
+
];
|
|
3300
|
+
var SHORTCUTS = [
|
|
3301
|
+
["tab / \u2192", "next workspace"],
|
|
3302
|
+
["\u2190 / esc", "previous/back or clear search"],
|
|
3303
|
+
["\u2191 \u2193 / j k", "move selection"],
|
|
3304
|
+
["enter / space / click", "activate selected action"],
|
|
3305
|
+
["mouse wheel", "scroll panel under cursor"],
|
|
3306
|
+
["ctrl-]", "detach from embedded Pi/command terminal"],
|
|
3307
|
+
["ctrl-c / q", "exit"]
|
|
3308
|
+
];
|
|
3309
|
+
function panelWidth(layout) {
|
|
3310
|
+
return layout?.centerWidth ?? 100;
|
|
3311
|
+
}
|
|
3312
|
+
function panelHeight(layout, top = 1) {
|
|
3313
|
+
return Math.max(8, (layout?.centerHeight ?? 24) - top);
|
|
3314
|
+
}
|
|
3315
|
+
function unsupportedCommand(state) {
|
|
3316
|
+
const value = state.intent.action.payload?.unsupported ?? state.intent.action.payload?.unknown;
|
|
3317
|
+
if (typeof value !== "string" || !value.trim())
|
|
3318
|
+
return null;
|
|
3319
|
+
const reason = state.intent.action.payload?.reason;
|
|
3320
|
+
return { command: value.trim(), ...typeof reason === "string" && reason.trim() ? { reason: reason.trim() } : {} };
|
|
3321
|
+
}
|
|
3322
|
+
function renderHelpScene(state, layout) {
|
|
3323
|
+
const unsupported = unsupportedCommand(state);
|
|
3324
|
+
const selected = Math.max(0, Math.min(ACTIONS.length - 1, state.selection.index));
|
|
3325
|
+
const panelLines = [
|
|
3326
|
+
...unsupported ? [
|
|
3327
|
+
line(`${unsupported.command} needs the action catalog.`, { fg: RIG_UI.yellow, bold: true }),
|
|
3328
|
+
line(unsupported.reason ?? "Pick an action below; explicit plain mode remains available with RIG_PLAIN=1.", { fg: RIG_UI.ink3 }),
|
|
3329
|
+
line("", { fg: RIG_UI.ink3 })
|
|
3330
|
+
] : [],
|
|
3331
|
+
line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
|
|
3332
|
+
...ACTIONS.map(([label, detail], index) => deckRow({ label, detail, index, active: selected === index, activateOnClick: true })),
|
|
3333
|
+
line("", { fg: RIG_UI.ink3 }),
|
|
3334
|
+
line("KEYS / MOUSE", { fg: RIG_UI.ink3, bold: true }),
|
|
3335
|
+
...SHORTCUTS.map(([label, detail]) => line(`${label.padEnd(22)} ${detail}`, { fg: RIG_UI.ink2 }))
|
|
3336
|
+
];
|
|
3337
|
+
return makeSceneFrame({
|
|
3338
|
+
scene: "help",
|
|
3339
|
+
title: "Command catalog",
|
|
3340
|
+
lines: [
|
|
3341
|
+
line("", { fg: RIG_UI.ink4 }),
|
|
3342
|
+
line(" command catalog \xB7 TUI-first Rig", { fg: RIG_UI.ink3, bold: true })
|
|
3343
|
+
],
|
|
3344
|
+
panels: [{
|
|
3345
|
+
id: "help-reference",
|
|
3346
|
+
top: 0,
|
|
3347
|
+
width: panelWidth(layout),
|
|
3348
|
+
height: panelHeight(layout),
|
|
3349
|
+
lines: panelLines,
|
|
3350
|
+
backgroundColor: RIG_UI.panel,
|
|
3351
|
+
backgroundAlpha: 184,
|
|
3352
|
+
opacity: 1,
|
|
3353
|
+
border: false,
|
|
3354
|
+
chrome: "ad-terminal",
|
|
3355
|
+
headerText: "actions",
|
|
3356
|
+
headerHeight: 3,
|
|
3357
|
+
paddingX: layout?.compact ? 2 : 3,
|
|
3358
|
+
paddingY: 1
|
|
3359
|
+
}],
|
|
3360
|
+
hideTypeBar: true,
|
|
3361
|
+
footer: { message: "enter/click opens a workspace" },
|
|
3362
|
+
live: true
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
// packages/cli/src/app-opentui/scenes/main.ts
|
|
3367
|
+
var CONFIGURED_MENU = [
|
|
3368
|
+
["init", "setup, reconfigure, repair project state"],
|
|
3369
|
+
["server", "local/remote target, auth, linkage"],
|
|
3370
|
+
["github", "auth, selected repo, stored token"],
|
|
3371
|
+
["tasks", "browse, filter, dispatch, attach active work"],
|
|
3372
|
+
["runs", "attach, stop, resume, inspect live sessions"],
|
|
3373
|
+
["inbox", "approve, reject, answer, attach"],
|
|
3374
|
+
["doctor", "diagnose and repair broken surfaces"],
|
|
3375
|
+
["help", "all actions, shortcuts, mouse controls"]
|
|
3376
|
+
];
|
|
3377
|
+
var UNCONFIGURED_MENU = [
|
|
3378
|
+
["init", "full setup wizard and reconfigure flow"],
|
|
3379
|
+
["server", "select local/remote target"],
|
|
3380
|
+
["github", "connect or import authentication"],
|
|
3381
|
+
["doctor", "find missing setup pieces"],
|
|
3382
|
+
["help", "all actions and controls"]
|
|
3383
|
+
];
|
|
3384
|
+
function record(value) {
|
|
3385
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
3386
|
+
}
|
|
3387
|
+
function serverSummary(state) {
|
|
3388
|
+
const server = record(state.data.server);
|
|
3389
|
+
const auth = record(server?.auth);
|
|
3390
|
+
const signedIn = auth?.signedIn === true || auth?.authenticated === true || auth?.status === "authenticated";
|
|
3391
|
+
const login = typeof auth?.login === "string" && auth.login.trim() ? auth.login.trim() : undefined;
|
|
3392
|
+
return {
|
|
3393
|
+
label: typeof server?.label === "string" && server.label.trim() ? server.label.trim() : state.footer.server ?? "not checked yet",
|
|
3394
|
+
target: typeof server?.kind === "string" ? `${server.kind} endpoint` : "open Server to probe",
|
|
3395
|
+
health: server ? server.reachable === false ? "unreachable" : "reachable" : "pending",
|
|
3396
|
+
auth: server ? signedIn ? `signed in${login ? ` as ${login}` : ""}` : "not signed in / unknown" : "pending"
|
|
3397
|
+
};
|
|
3398
|
+
}
|
|
3399
|
+
function panelWidth2(layout) {
|
|
3400
|
+
return layout?.centerWidth ?? 118;
|
|
3401
|
+
}
|
|
3402
|
+
function panelHeight2(layout) {
|
|
3403
|
+
return Math.max(22, layout?.centerHeight ?? 36);
|
|
3404
|
+
}
|
|
3405
|
+
function actionRows(state, configured) {
|
|
3406
|
+
const options = configured ? CONFIGURED_MENU : UNCONFIGURED_MENU;
|
|
3407
|
+
const selected = Math.max(0, Math.min(options.length - 1, state.selection.index));
|
|
3408
|
+
return options.map(([label, detail], index) => deckRow({ label, detail, index, active: index === selected, activateOnClick: true }));
|
|
3409
|
+
}
|
|
3410
|
+
function renderMainScene(state, layout) {
|
|
3411
|
+
const configured = state.data.projectConfigured === true;
|
|
3412
|
+
const server = serverSummary(state);
|
|
3413
|
+
const lines = [
|
|
3414
|
+
line("PROJECT COCKPIT", { fg: RIG_UI.ink3, bold: true }),
|
|
3415
|
+
line("click a row or use arrows + enter \xB7 esc returns here \xB7 ctrl-c exits", { fg: RIG_UI.ink4 }),
|
|
3416
|
+
blank(),
|
|
3417
|
+
line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
|
|
3418
|
+
...actionRows(state, configured),
|
|
3419
|
+
blank(),
|
|
3420
|
+
line("CONTEXT", { fg: RIG_UI.ink3, bold: true }),
|
|
3421
|
+
kv("workspace", "current repository", RIG_UI.ink2),
|
|
3422
|
+
kv("config", configured ? "configured" : "needs setup", configured ? RIG_UI.limeDim : RIG_UI.yellow),
|
|
3423
|
+
kv("server", server.label, server.health === "reachable" ? RIG_UI.limeDim : RIG_UI.yellow),
|
|
3424
|
+
kv("target", server.target, RIG_UI.ink3),
|
|
3425
|
+
kv("github", server.auth, server.auth.startsWith("signed in") ? RIG_UI.limeDim : RIG_UI.yellow),
|
|
3426
|
+
blank(),
|
|
3427
|
+
line(state.actionLabel ?? "ready", { fg: state.status === "action" ? RIG_UI.cyan : RIG_UI.ink4 })
|
|
3428
|
+
];
|
|
3429
|
+
return makeSceneFrame({
|
|
3430
|
+
scene: "main",
|
|
3431
|
+
title: "Project cockpit",
|
|
3432
|
+
lines: [],
|
|
3433
|
+
panels: [{
|
|
3434
|
+
id: "project-cockpit",
|
|
3435
|
+
top: 0,
|
|
3436
|
+
width: panelWidth2(layout),
|
|
3437
|
+
height: panelHeight2(layout),
|
|
2788
3438
|
lines,
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
this.disposables.push(this.terminal.parser.registerCsiHandler({ prefix: ">", final: "c" }, (params) => {
|
|
2802
|
-
if (params.length === 0 || params[0] === 0)
|
|
2803
|
-
this.write("\x1B[>0;0;0c");
|
|
2804
|
-
return false;
|
|
2805
|
-
}));
|
|
2806
|
-
this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
|
|
2807
|
-
if (params[0] === 5) {
|
|
2808
|
-
this.write("\x1B[0n");
|
|
2809
|
-
} else if (params[0] === 6) {
|
|
2810
|
-
const row = this.terminal.buffer.active.cursorY + 1;
|
|
2811
|
-
const col = this.terminal.buffer.active.cursorX + 1;
|
|
2812
|
-
this.write(`\x1B[${row};${col}R`);
|
|
2813
|
-
}
|
|
2814
|
-
return false;
|
|
2815
|
-
}));
|
|
2816
|
-
}
|
|
2817
|
-
respondToRawTerminalQueries(text) {
|
|
2818
|
-
if (!text)
|
|
2819
|
-
return;
|
|
2820
|
-
const decrqm = /\x1b\[\?(\d+)\$p/g;
|
|
2821
|
-
let match;
|
|
2822
|
-
while ((match = decrqm.exec(text)) !== null) {
|
|
2823
|
-
this.write(`\x1B[?${match[1]};2$y`);
|
|
2824
|
-
}
|
|
2825
|
-
}
|
|
3439
|
+
backgroundColor: RIG_UI.panel,
|
|
3440
|
+
backgroundAlpha: 184,
|
|
3441
|
+
opacity: 0.98,
|
|
3442
|
+
border: false,
|
|
3443
|
+
chrome: "ad-terminal",
|
|
3444
|
+
headerText: "project cockpit",
|
|
3445
|
+
paddingX: layout?.compact ? 3 : 5,
|
|
3446
|
+
paddingY: 2
|
|
3447
|
+
}],
|
|
3448
|
+
live: true,
|
|
3449
|
+
hideTypeBar: true
|
|
3450
|
+
});
|
|
2826
3451
|
}
|
|
2827
3452
|
|
|
2828
3453
|
// packages/cli/src/app-opentui/runtime.ts
|
|
3454
|
+
init_pi_pty_host();
|
|
2829
3455
|
var MAX_SCENE_LINES = 200;
|
|
2830
3456
|
var MAX_SCENE_PANELS = 5;
|
|
2831
|
-
var
|
|
2832
|
-
var
|
|
3457
|
+
var ANIMATION_SLOWDOWN = 1;
|
|
3458
|
+
var PRELOADER_BACKDROP = RGBA7.fromHex(RIG_UI.bg);
|
|
2833
3459
|
var PRIMARY_NAV = [
|
|
3460
|
+
{ scene: "main", argv: ["main"], label: "Project menu" },
|
|
2834
3461
|
{ scene: "fleet", argv: ["runs"], label: "Opening runs" },
|
|
2835
3462
|
{ scene: "tasks", argv: ["tasks"], label: "Opening tasks" },
|
|
2836
3463
|
{ scene: "help", argv: ["help"], label: "Opening help" }
|
|
2837
3464
|
];
|
|
2838
3465
|
function primaryNavIntent(scene) {
|
|
2839
3466
|
const entry = PRIMARY_NAV.find((item) => item.scene === scene) ?? PRIMARY_NAV[0];
|
|
2840
|
-
|
|
3467
|
+
const kind = entry.scene === "help" || entry.scene === "main" ? "none" : "refresh";
|
|
3468
|
+
return { scene: entry.scene, argv: entry.argv, action: { kind, label: entry.label } };
|
|
2841
3469
|
}
|
|
2842
3470
|
function nextPrimaryScene(current, delta) {
|
|
2843
3471
|
const currentIndex = PRIMARY_NAV.findIndex((entry) => entry.scene === current);
|
|
@@ -2858,10 +3486,10 @@ function defaultSceneRenderer(state) {
|
|
|
2858
3486
|
scene: state.scene,
|
|
2859
3487
|
title,
|
|
2860
3488
|
lines: [
|
|
2861
|
-
{ text: title.toUpperCase()
|
|
2862
|
-
{ text: state.actionLabel ?? "adapter scene pending"
|
|
2863
|
-
{ text: "
|
|
2864
|
-
{ text: "
|
|
3489
|
+
{ text: ` ${title.toUpperCase()}`, fg: RIG_UI.ink, bold: true },
|
|
3490
|
+
{ text: ` ${state.actionLabel ?? "adapter scene pending"}`, fg: RIG_UI.limeDim },
|
|
3491
|
+
{ text: " choose an action from the available rows", fg: RIG_UI.ink3 },
|
|
3492
|
+
{ text: " esc returns home \xB7 arrows select \xB7 enter activates", fg: RIG_UI.ink4 }
|
|
2865
3493
|
],
|
|
2866
3494
|
typeBarPlaceholder: "runs \xB7 tasks \xB7 help",
|
|
2867
3495
|
live: false
|
|
@@ -2887,9 +3515,9 @@ function requestRender(renderer) {
|
|
|
2887
3515
|
function recordArray(value) {
|
|
2888
3516
|
return Array.isArray(value) ? value.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
2889
3517
|
}
|
|
2890
|
-
function
|
|
3518
|
+
function stringField2(record2, keys) {
|
|
2891
3519
|
for (const key of keys) {
|
|
2892
|
-
const value =
|
|
3520
|
+
const value = record2[key];
|
|
2893
3521
|
if (typeof value === "string" && value.trim())
|
|
2894
3522
|
return value.trim();
|
|
2895
3523
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -2897,33 +3525,84 @@ function stringField(record, keys) {
|
|
|
2897
3525
|
}
|
|
2898
3526
|
return;
|
|
2899
3527
|
}
|
|
2900
|
-
function mainSelectableItems(
|
|
3528
|
+
function mainSelectableItems(state) {
|
|
3529
|
+
const configured = state.data.projectConfigured === true;
|
|
3530
|
+
const common = [
|
|
3531
|
+
{ id: "init", label: "init", intent: { scene: "command", argv: ["init"], action: { kind: "command-run", label: "Run init" } }, message: "run setup/reconfigure flow" },
|
|
3532
|
+
{ id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Checking server" } }, message: "check selected server" },
|
|
3533
|
+
{ id: "github", label: "github", intent: { scene: "server", argv: ["github", "auth", "status"], action: { kind: "refresh", label: "Checking GitHub auth" } }, message: "check GitHub auth" }
|
|
3534
|
+
];
|
|
3535
|
+
if (!configured) {
|
|
3536
|
+
return [
|
|
3537
|
+
...common,
|
|
3538
|
+
{ id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Running doctor" } }, message: "diagnose missing setup" },
|
|
3539
|
+
{ id: "help", label: "help", intent: { scene: "help", argv: ["help"], action: { kind: "none", label: "Opening help" } }, message: "selected help" }
|
|
3540
|
+
];
|
|
3541
|
+
}
|
|
2901
3542
|
return [
|
|
2902
|
-
|
|
3543
|
+
...common,
|
|
2903
3544
|
{ id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Opening tasks" } }, message: "selected tasks" },
|
|
3545
|
+
{ id: "runs", label: "runs", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } }, message: "selected runs" },
|
|
3546
|
+
{ id: "inbox", label: "inbox", intent: { scene: "inbox", argv: ["inbox"], action: { kind: "refresh", label: "Loading inbox" } }, message: "selected inbox" },
|
|
3547
|
+
{ id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Running doctor" } }, message: "diagnose project" },
|
|
2904
3548
|
{ id: "help", label: "help", intent: { scene: "help", argv: ["help"], action: { kind: "none", label: "Opening help" } }, message: "selected help" }
|
|
2905
3549
|
];
|
|
2906
3550
|
}
|
|
3551
|
+
function fleetRecoveryItems() {
|
|
3552
|
+
return [
|
|
3553
|
+
{ id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks and dispatch work" },
|
|
3554
|
+
{ id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair project link" } }, message: "repair project/server/auth link" },
|
|
3555
|
+
{ id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
|
|
3556
|
+
{ id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose project" }
|
|
3557
|
+
];
|
|
3558
|
+
}
|
|
2907
3559
|
function fleetSelectableItems(state) {
|
|
2908
3560
|
const fleet = state.data.fleet;
|
|
2909
3561
|
const runs = fleet && typeof fleet === "object" && !Array.isArray(fleet) ? recordArray(fleet.runs) : [];
|
|
2910
|
-
|
|
2911
|
-
|
|
3562
|
+
const visibleRuns = filterRunsForSearch(runs, listSearchQuery(state));
|
|
3563
|
+
if (visibleRuns.length === 0)
|
|
3564
|
+
return fleetRecoveryItems();
|
|
3565
|
+
return visibleRuns.flatMap((run) => {
|
|
3566
|
+
const runId = stringField2(run, ["runId", "id"]);
|
|
2912
3567
|
if (!runId)
|
|
2913
3568
|
return [];
|
|
2914
|
-
const title =
|
|
3569
|
+
const title = stringField2(run, ["title", "taskTitle", "taskId"]) ?? "run";
|
|
2915
3570
|
return [{ id: runId, label: title, data: { selectedRunId: runId }, intent: { scene: "handoff", argv: ["attach", runId], action: { kind: "run-attach", payload: { runId }, label: `Attach Pi ${runId.slice(0, 8)}` } }, message: `attach Pi to ${runId.slice(0, 8)}` }];
|
|
2916
3571
|
});
|
|
2917
3572
|
}
|
|
3573
|
+
function taskRecoveryItems() {
|
|
3574
|
+
return [
|
|
3575
|
+
{ id: "refresh", label: "refresh", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Refresh tasks" } }, message: "refresh task source" },
|
|
3576
|
+
{ id: "next", label: "next", intent: { scene: "command", argv: ["task", "run", "--next"], action: { kind: "command-run", label: "Run next task" } }, message: "dispatch next runnable task" },
|
|
3577
|
+
{ id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair task source" } }, message: "repair project/task source" },
|
|
3578
|
+
{ id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
|
|
3579
|
+
{ id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose task source" }
|
|
3580
|
+
];
|
|
3581
|
+
}
|
|
2918
3582
|
function taskSelectableItems(state) {
|
|
2919
3583
|
const tasks = state.data.tasks;
|
|
2920
3584
|
const records = tasks && typeof tasks === "object" && !Array.isArray(tasks) ? recordArray(tasks.records) : [];
|
|
2921
|
-
|
|
2922
|
-
|
|
3585
|
+
const visibleTasks = filterTasksForSearch(records, listSearchQuery(state));
|
|
3586
|
+
if (visibleTasks.length === 0)
|
|
3587
|
+
return taskRecoveryItems();
|
|
3588
|
+
return visibleTasks.flatMap((task) => {
|
|
3589
|
+
const taskId = stringField2(task, ["id", "taskId", "number"]);
|
|
2923
3590
|
if (!taskId)
|
|
2924
3591
|
return [];
|
|
2925
|
-
const title =
|
|
2926
|
-
|
|
3592
|
+
const title = stringField2(task, ["title", "name", "summary"]) ?? "task";
|
|
3593
|
+
const activeRunId = activeRunIdFromTaskLike(task);
|
|
3594
|
+
if (activeRunId) {
|
|
3595
|
+
const item2 = {
|
|
3596
|
+
id: taskId,
|
|
3597
|
+
label: title,
|
|
3598
|
+
data: { selectedTaskId: taskId, selectedRunId: activeRunId },
|
|
3599
|
+
intent: { scene: "handoff", argv: ["attach", activeRunId], action: { kind: "run-attach", payload: { runId: activeRunId }, label: `Attach Pi ${activeRunId.slice(0, 8)}` } },
|
|
3600
|
+
message: `attach Pi to ${activeRunId.slice(0, 8)}`
|
|
3601
|
+
};
|
|
3602
|
+
return [item2];
|
|
3603
|
+
}
|
|
3604
|
+
const item = { id: taskId, label: title, data: { selectedTaskId: taskId }, intent: { scene: "tasks", argv: ["run", taskId], action: { kind: "task-run-id", payload: { task: taskId }, label: `Dispatching ${taskId}` } }, message: `dispatch task ${taskId}` };
|
|
3605
|
+
return [item];
|
|
2927
3606
|
});
|
|
2928
3607
|
}
|
|
2929
3608
|
function selectedRunId(state) {
|
|
@@ -2944,21 +3623,126 @@ function selectedRunId(state) {
|
|
|
2944
3623
|
}
|
|
2945
3624
|
return;
|
|
2946
3625
|
}
|
|
3626
|
+
function inboxRecoveryItems() {
|
|
3627
|
+
return [
|
|
3628
|
+
{ id: "refresh", label: "refresh", intent: { scene: "inbox", argv: ["inbox"], action: { kind: "refresh", label: "Refresh inbox" } }, message: "refresh inbox" },
|
|
3629
|
+
{ id: "runs", label: "runs", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Open runs" } }, message: "open runs" },
|
|
3630
|
+
{ id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks" },
|
|
3631
|
+
{ id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose inbox" }
|
|
3632
|
+
];
|
|
3633
|
+
}
|
|
2947
3634
|
function inboxSelectableItems(state) {
|
|
2948
3635
|
const inbox = state.data.inbox;
|
|
2949
3636
|
if (!inbox || typeof inbox !== "object" || Array.isArray(inbox))
|
|
2950
|
-
return
|
|
2951
|
-
const
|
|
2952
|
-
const approvals = recordArray(
|
|
2953
|
-
const inputs = recordArray(
|
|
3637
|
+
return inboxRecoveryItems();
|
|
3638
|
+
const record2 = inbox;
|
|
3639
|
+
const approvals = recordArray(record2.approvals).map((entry) => ({ ...entry, kind: "approvals" }));
|
|
3640
|
+
const inputs = recordArray(record2.inputs).map((entry) => ({ ...entry, kind: "inputs" }));
|
|
3641
|
+
if (approvals.length === 0 && inputs.length === 0)
|
|
3642
|
+
return inboxRecoveryItems();
|
|
2954
3643
|
return [...approvals, ...inputs].flatMap((item) => {
|
|
2955
|
-
const requestId =
|
|
2956
|
-
|
|
3644
|
+
const requestId = stringField2(item, ["requestId", "id"]);
|
|
3645
|
+
const runId = stringField2(item, ["runId"]);
|
|
3646
|
+
if (!requestId || !runId)
|
|
2957
3647
|
return [];
|
|
2958
|
-
|
|
2959
|
-
|
|
3648
|
+
if (item.kind === "approvals") {
|
|
3649
|
+
return [
|
|
3650
|
+
{ id: `${requestId}:approve`, label: "approve", data: { selectedInboxRequestId: requestId }, intent: { scene: "inbox", argv: ["inbox", "approve", requestId], action: { kind: "inbox-approve", payload: { requestId }, label: `Approve ${requestId}` } }, message: `approve ${requestId}` },
|
|
3651
|
+
{ id: `${requestId}:reject`, label: "reject", data: { selectedInboxRequestId: requestId }, intent: { scene: "inbox", argv: ["inbox", "reject", requestId], action: { kind: "inbox-reject", payload: { requestId }, label: `Reject ${requestId}` } }, message: `reject ${requestId}` },
|
|
3652
|
+
{ id: `${requestId}:attach`, label: "attach", data: { selectedInboxRequestId: requestId, selectedRunId: runId }, intent: { scene: "handoff", argv: ["attach", runId], action: { kind: "run-attach", payload: { runId }, label: `Attach Pi ${runId.slice(0, 8)}` } }, message: `attach run ${runId.slice(0, 8)}` }
|
|
3653
|
+
];
|
|
3654
|
+
}
|
|
3655
|
+
return [
|
|
3656
|
+
{ id: `${requestId}:answer`, label: "answer", data: { selectedInboxRequestId: requestId, selectedRunId: runId }, intent: { scene: "command", argv: ["inbox", "respond", "--run", runId, "--request", requestId], action: { kind: "command-run", label: `Answer ${requestId}` } }, message: `answer ${requestId}` },
|
|
3657
|
+
{ id: `${requestId}:attach`, label: "attach", data: { selectedInboxRequestId: requestId, selectedRunId: runId }, intent: { scene: "handoff", argv: ["attach", runId], action: { kind: "run-attach", payload: { runId }, label: `Attach Pi ${runId.slice(0, 8)}` } }, message: `attach run ${runId.slice(0, 8)}` }
|
|
3658
|
+
];
|
|
3659
|
+
});
|
|
3660
|
+
}
|
|
3661
|
+
function serverSelectableItems(_state) {
|
|
3662
|
+
return [
|
|
3663
|
+
{ id: "refresh", label: "refresh", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Refresh server" } }, message: "refresh selected server" },
|
|
3664
|
+
{ id: "local", label: "local", intent: { scene: "command", argv: ["server", "use", "local"], action: { kind: "command-run", label: "Use local server" } }, message: "switch this repo to local" },
|
|
3665
|
+
{ id: "remote", label: "remote", intent: { scene: "command", argv: ["server", "use"], action: { kind: "command-run", label: "Choose remote server" } }, message: "choose a saved remote alias" },
|
|
3666
|
+
{ id: "list", label: "list", intent: { scene: "command", argv: ["server", "list"], action: { kind: "command-run", label: "List saved servers" } }, message: "list saved server aliases" },
|
|
3667
|
+
{ id: "auth", label: "auth", intent: { scene: "command", argv: ["github", "auth", "import-gh"], action: { kind: "command-run", label: "Import GitHub auth" } }, message: "import gh auth" },
|
|
3668
|
+
{ id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify server/auth" }
|
|
3669
|
+
];
|
|
3670
|
+
}
|
|
3671
|
+
function initSelectableItems(_state) {
|
|
3672
|
+
return [
|
|
3673
|
+
{ id: "wizard", label: "wizard", intent: { scene: "command", argv: ["init"], action: { kind: "command-run", label: "Run init wizard" } }, message: "full init wizard" },
|
|
3674
|
+
{ id: "reconfigure", label: "reconfigure", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Reconfigure project" } }, message: "verify or rewrite generated config" },
|
|
3675
|
+
{ id: "demo", label: "demo", intent: { scene: "command", argv: ["init", "--demo", "--repair"], action: { kind: "command-run", label: "Seed demo project" } }, message: "offline demo project" },
|
|
3676
|
+
{ id: "remote", label: "remote", intent: { scene: "command", argv: ["init"], action: { kind: "command-run", label: "Run init remote wizard" } }, message: "choose remote server in init wizard" },
|
|
3677
|
+
{ id: "auth", label: "auth", intent: { scene: "command", argv: ["github", "auth", "import-gh"], action: { kind: "command-run", label: "Import GitHub auth" } }, message: "import gh auth" },
|
|
3678
|
+
{ id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify setup" }
|
|
3679
|
+
];
|
|
3680
|
+
}
|
|
3681
|
+
var CLI_CATALOG_ACTIONS = [
|
|
3682
|
+
{ id: "runs", label: "runs", argv: ["runs"], scene: "fleet", kind: "refresh", detail: "show live/completed runs" },
|
|
3683
|
+
{ id: "tasks", label: "tasks", argv: ["tasks"], scene: "tasks", kind: "refresh", detail: "browse task source" },
|
|
3684
|
+
{ id: "run-next", label: "run next", argv: ["task", "run", "--next"], detail: "dispatch next runnable task" },
|
|
3685
|
+
{ id: "server", label: "server", argv: ["server", "status"], scene: "server", kind: "refresh", detail: "local/remote controls" },
|
|
3686
|
+
{ id: "init", label: "init", argv: ["init"], detail: "setup/reconfigure/repair wizard" },
|
|
3687
|
+
{ id: "doctor", label: "doctor", argv: ["doctor"], scene: "doctor", kind: "doctor-run", detail: "diagnose project/server/auth" },
|
|
3688
|
+
{ id: "github", label: "github", argv: ["github"], detail: "GitHub auth/repo actions" },
|
|
3689
|
+
{ id: "plugin", label: "plugin", argv: ["plugin"], detail: "plugin/source actions" },
|
|
3690
|
+
{ id: "setup", label: "setup", argv: ["setup"], detail: "bootstrap/check/preflight" },
|
|
3691
|
+
{ id: "inspect", label: "inspect", argv: ["inspect"], detail: "inspect project/server state" },
|
|
3692
|
+
{ id: "stats", label: "stats", argv: ["stats"], detail: "run/task metrics" },
|
|
3693
|
+
{ id: "repo", label: "repo", argv: ["repo"], detail: "repo utilities" },
|
|
3694
|
+
{ id: "profile", label: "profile", argv: ["profile"], detail: "profile/config actions" },
|
|
3695
|
+
{ id: "review", label: "review", argv: ["review"], detail: "review automation" },
|
|
3696
|
+
{ id: "browser", label: "browser", argv: ["browser"], detail: "browser tooling/wizards" },
|
|
3697
|
+
{ id: "pi", label: "pi", argv: ["pi"], detail: "Pi install/attach/status" },
|
|
3698
|
+
{ id: "queue", label: "queue", argv: ["queue"], detail: "queue operations" },
|
|
3699
|
+
{ id: "agent", label: "agent", argv: ["agent"], detail: "agent/runtime operations" },
|
|
3700
|
+
{ id: "inspector", label: "inspector", argv: ["inspector"], detail: "inspector tools" },
|
|
3701
|
+
{ id: "dist", label: "dist", argv: ["dist"], detail: "distribution helpers" },
|
|
3702
|
+
{ id: "workspace", label: "workspace", argv: ["workspace"], detail: "workspace/project utilities" },
|
|
3703
|
+
{ id: "remote", label: "remote", argv: ["remote"], detail: "remote endpoint utilities" },
|
|
3704
|
+
{ id: "git", label: "git", argv: ["git"], detail: "git helper actions" },
|
|
3705
|
+
{ id: "harness", label: "harness", argv: ["harness"], detail: "harness operations" },
|
|
3706
|
+
{ id: "test", label: "test", argv: ["test"], detail: "test helpers" }
|
|
3707
|
+
];
|
|
3708
|
+
function helpSelectableItems() {
|
|
3709
|
+
return CLI_CATALOG_ACTIONS.map((entry) => {
|
|
3710
|
+
const scene = entry.scene ?? "command";
|
|
3711
|
+
const kind = entry.kind ?? "command-run";
|
|
3712
|
+
return {
|
|
3713
|
+
id: entry.id,
|
|
3714
|
+
label: entry.label,
|
|
3715
|
+
intent: { scene, argv: entry.argv, action: { kind, label: `Open ${entry.label}` } },
|
|
3716
|
+
message: entry.detail
|
|
3717
|
+
};
|
|
2960
3718
|
});
|
|
2961
3719
|
}
|
|
3720
|
+
function handoffSelectableItems(state) {
|
|
3721
|
+
const runId = typeof state.data.selectedRunId === "string" ? state.data.selectedRunId : undefined;
|
|
3722
|
+
return runId ? [
|
|
3723
|
+
{ id: "attach", label: "attach", intent: { scene: "handoff", argv: ["attach", runId], action: { kind: "run-attach", payload: { runId }, label: `Attach Pi ${runId.slice(0, 8)}` } }, message: "open bundled Pi" },
|
|
3724
|
+
{ id: "runs", label: "runs", intent: { scene: "fleet", argv: ["fleet"], action: { kind: "refresh", label: "Back to fleet" } }, message: "back to fleet" }
|
|
3725
|
+
] : [
|
|
3726
|
+
{ id: "runs", label: "runs", intent: { scene: "fleet", argv: ["fleet"], action: { kind: "refresh", label: "Open runs" } }, message: "select a run first" },
|
|
3727
|
+
{ id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks" }
|
|
3728
|
+
];
|
|
3729
|
+
}
|
|
3730
|
+
function errorSelectableItems() {
|
|
3731
|
+
return [
|
|
3732
|
+
{ id: "main", label: "main", intent: { scene: "main", argv: [], action: { kind: "refresh", label: "Back to dashboard" } }, message: "back to dashboard" },
|
|
3733
|
+
{ id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "run diagnostics" },
|
|
3734
|
+
{ id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "open server controls" },
|
|
3735
|
+
{ id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair project link" } }, message: "repair project link" }
|
|
3736
|
+
];
|
|
3737
|
+
}
|
|
3738
|
+
function doctorSelectableItems() {
|
|
3739
|
+
return [
|
|
3740
|
+
{ id: "run", label: "run", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "run diagnostics" },
|
|
3741
|
+
{ id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "open server controls" },
|
|
3742
|
+
{ id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair project link" } }, message: "repair project link" },
|
|
3743
|
+
{ id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks" }
|
|
3744
|
+
];
|
|
3745
|
+
}
|
|
2962
3746
|
function runDetailSelectableItems(state) {
|
|
2963
3747
|
const runId = selectedRunId(state);
|
|
2964
3748
|
if (!runId)
|
|
@@ -2973,12 +3757,24 @@ function selectableItems(state) {
|
|
|
2973
3757
|
switch (state.scene) {
|
|
2974
3758
|
case "main":
|
|
2975
3759
|
return mainSelectableItems(state);
|
|
3760
|
+
case "error":
|
|
3761
|
+
return errorSelectableItems();
|
|
3762
|
+
case "help":
|
|
3763
|
+
return helpSelectableItems();
|
|
2976
3764
|
case "fleet":
|
|
2977
3765
|
return fleetSelectableItems(state);
|
|
2978
3766
|
case "tasks":
|
|
2979
3767
|
return taskSelectableItems(state);
|
|
2980
3768
|
case "inbox":
|
|
2981
3769
|
return inboxSelectableItems(state);
|
|
3770
|
+
case "handoff":
|
|
3771
|
+
return handoffSelectableItems(state);
|
|
3772
|
+
case "server":
|
|
3773
|
+
return serverSelectableItems(state);
|
|
3774
|
+
case "init":
|
|
3775
|
+
return initSelectableItems(state);
|
|
3776
|
+
case "doctor":
|
|
3777
|
+
return doctorSelectableItems();
|
|
2982
3778
|
case "run-detail":
|
|
2983
3779
|
return runDetailSelectableItems(state);
|
|
2984
3780
|
default:
|
|
@@ -3058,13 +3854,18 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3058
3854
|
let renderer = null;
|
|
3059
3855
|
let graphics = null;
|
|
3060
3856
|
let imageVisuals = null;
|
|
3857
|
+
let preloaderBackdrop = null;
|
|
3061
3858
|
const scrollPanels = [];
|
|
3062
3859
|
let typeBar = null;
|
|
3860
|
+
let brandText = null;
|
|
3063
3861
|
const textLines = [];
|
|
3064
|
-
let
|
|
3862
|
+
let renderTimer = null;
|
|
3065
3863
|
let destroyed = false;
|
|
3066
3864
|
let runnerPromise = null;
|
|
3067
3865
|
let renderedSelectableItems = [];
|
|
3866
|
+
const preloaderTransition = { scene: null, startedAtMs: 0, minUntilMs: 0 };
|
|
3867
|
+
const resources = new RuntimeResources;
|
|
3868
|
+
const focusManager = new AppFocusManager;
|
|
3068
3869
|
const emitAsyncError = (label, error) => {
|
|
3069
3870
|
const normalized = normalizeError(error);
|
|
3070
3871
|
events.emit({ type: "action.failed", label, message: normalized.message, hint: normalized.hint, cause: normalized.cause });
|
|
@@ -3072,6 +3873,23 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3072
3873
|
const runAppAction = (label, action) => {
|
|
3073
3874
|
action().catch((error) => emitAsyncError(label, error));
|
|
3074
3875
|
};
|
|
3876
|
+
const renderApp = () => {
|
|
3877
|
+
if (!renderer || destroyed)
|
|
3878
|
+
return;
|
|
3879
|
+
const currentState = store.getState();
|
|
3880
|
+
renderedSelectableItems = renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPanels, typeBar, brandText, textLines, currentState, options.sceneRenderers, preloaderTransition, scheduleAppRender);
|
|
3881
|
+
focusManager.restoreTypeBarFocus(typeBar, { hidden: currentState.scene === "handoff" });
|
|
3882
|
+
};
|
|
3883
|
+
const scheduleAppRender = (delayMs = 0) => {
|
|
3884
|
+
if (destroyed)
|
|
3885
|
+
return;
|
|
3886
|
+
if (renderTimer)
|
|
3887
|
+
return;
|
|
3888
|
+
renderTimer = resources.setTimeout(() => {
|
|
3889
|
+
renderTimer = null;
|
|
3890
|
+
renderApp();
|
|
3891
|
+
}, Math.max(0, delayMs));
|
|
3892
|
+
};
|
|
3075
3893
|
const runtime = {
|
|
3076
3894
|
getState: () => store.getState(),
|
|
3077
3895
|
patchState: (patch) => store.patch(patch),
|
|
@@ -3098,29 +3916,35 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3098
3916
|
},
|
|
3099
3917
|
getRunnerContext: async () => {
|
|
3100
3918
|
if (!options.initializeRuntime)
|
|
3101
|
-
throw new Error("
|
|
3919
|
+
throw new Error("Runtime initializer was not provided to the OpenTUI app.");
|
|
3102
3920
|
runnerPromise ??= options.initializeRuntime();
|
|
3103
3921
|
return runnerPromise;
|
|
3104
3922
|
},
|
|
3105
3923
|
suspend: () => renderer?.suspend(),
|
|
3106
3924
|
resume: () => {
|
|
3107
3925
|
renderer?.resume();
|
|
3108
|
-
|
|
3109
|
-
renderedSelectableItems = renderNow(renderer, graphics, imageVisuals, scrollPanels, typeBar, textLines, store.getState(), options.sceneRenderers);
|
|
3926
|
+
renderApp();
|
|
3110
3927
|
},
|
|
3111
3928
|
destroy: () => renderer?.destroy()
|
|
3112
3929
|
};
|
|
3113
|
-
events.subscribe((event) => store.patch(reduceAppEvent(store.getState(), event)));
|
|
3114
|
-
store.subscribe(() => {
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3930
|
+
resources.add(events.subscribe((event) => store.patch(reduceAppEvent(store.getState(), event))));
|
|
3931
|
+
resources.add(store.subscribe(() => {
|
|
3932
|
+
scheduleAppRender(0);
|
|
3933
|
+
}));
|
|
3934
|
+
resources.setInterval(() => {
|
|
3935
|
+
const state = store.getState();
|
|
3936
|
+
if (state.scene === "handoff" && getActivePiHost())
|
|
3937
|
+
return;
|
|
3938
|
+
if (state.scene === "command" && getActiveCommandHost())
|
|
3939
|
+
return;
|
|
3940
|
+
store.patch({ tick: state.tick + 1 });
|
|
3941
|
+
}, 96);
|
|
3118
3942
|
try {
|
|
3119
3943
|
renderer = await createCliRenderer({
|
|
3120
3944
|
screenMode: "alternate-screen",
|
|
3121
3945
|
exitOnCtrlC: false,
|
|
3122
|
-
targetFps:
|
|
3123
|
-
maxFps:
|
|
3946
|
+
targetFps: 30,
|
|
3947
|
+
maxFps: 30,
|
|
3124
3948
|
useMouse: true,
|
|
3125
3949
|
autoFocus: false,
|
|
3126
3950
|
useKittyKeyboard: {
|
|
@@ -3132,24 +3956,41 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3132
3956
|
openConsoleOnError: false,
|
|
3133
3957
|
backgroundColor: "transparent"
|
|
3134
3958
|
});
|
|
3135
|
-
renderer.setTerminalTitle?.("
|
|
3959
|
+
renderer.setTerminalTitle?.("Operator cockpit");
|
|
3136
3960
|
const layout = computeStageLayout(renderer.width, renderer.height);
|
|
3137
|
-
imageVisuals = forceModernImageVisuals() ? new ImageVisualLayer(process.stdout) : null;
|
|
3961
|
+
imageVisuals = forceModernImageVisuals() ? new ImageVisualLayer(process.stdout, () => scheduleAppRender(0)) : null;
|
|
3138
3962
|
if (imageVisuals?.enabled) {
|
|
3139
|
-
|
|
3963
|
+
const flushImageVisuals = async () => {
|
|
3140
3964
|
imageVisuals?.flush();
|
|
3141
|
-
}
|
|
3965
|
+
};
|
|
3966
|
+
renderer.setFrameCallback(flushImageVisuals);
|
|
3967
|
+
resources.add(() => renderer?.removeFrameCallback(flushImageVisuals));
|
|
3142
3968
|
}
|
|
3143
3969
|
if (!imageVisuals?.enabled) {
|
|
3144
3970
|
graphics = createGraphicsLayer(renderer, layout);
|
|
3145
3971
|
renderer.root.add(graphics);
|
|
3146
3972
|
}
|
|
3973
|
+
preloaderBackdrop = new BoxRenderable2(renderer, {
|
|
3974
|
+
id: "rig-preloader-backdrop",
|
|
3975
|
+
position: "absolute",
|
|
3976
|
+
left: 0,
|
|
3977
|
+
top: 0,
|
|
3978
|
+
width: layout.width,
|
|
3979
|
+
height: layout.height,
|
|
3980
|
+
backgroundColor: PRELOADER_BACKDROP,
|
|
3981
|
+
border: false,
|
|
3982
|
+
zIndex: 100,
|
|
3983
|
+
visible: false
|
|
3984
|
+
});
|
|
3985
|
+
renderer.root.add(preloaderBackdrop);
|
|
3147
3986
|
for (let index = 0;index < MAX_SCENE_PANELS; index += 1) {
|
|
3148
3987
|
const panel = createScrollPanelLayer(renderer, `rig-scroll-panel-${index}`, (line2) => {
|
|
3149
3988
|
const selectableIndex = line2.selectableIndex;
|
|
3150
3989
|
if (selectableIndex === undefined)
|
|
3151
3990
|
return;
|
|
3152
3991
|
selectIndex(store, selectableIndex, renderedSelectableItems);
|
|
3992
|
+
if (line2.activateOnClick)
|
|
3993
|
+
runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
|
|
3153
3994
|
});
|
|
3154
3995
|
scrollPanels.push(panel);
|
|
3155
3996
|
renderer.root.add(panel);
|
|
@@ -3160,146 +4001,84 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3160
4001
|
if (selectableIndex === undefined)
|
|
3161
4002
|
return;
|
|
3162
4003
|
selectIndex(store, selectableIndex, renderedSelectableItems);
|
|
4004
|
+
if (line2.activateOnClick)
|
|
4005
|
+
runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
|
|
3163
4006
|
});
|
|
3164
4007
|
textLines.push(textLine);
|
|
3165
4008
|
renderer.root.add(textLine);
|
|
3166
4009
|
}
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
typeBar: {
|
|
3177
|
-
value,
|
|
3178
|
-
...isListSearchScene(scene) ? { message: value.trim() ? `search ${JSON.stringify(value.trim())}` : undefined } : {}
|
|
3179
|
-
}
|
|
3180
|
-
});
|
|
3181
|
-
},
|
|
3182
|
-
onEnter(value) {
|
|
3183
|
-
const scene = store.getState().scene;
|
|
3184
|
-
if (isListSearchScene(scene) && value.trim()) {
|
|
3185
|
-
if (renderedSelectableItems.length === 0) {
|
|
3186
|
-
events.emit({ type: "typebar.patch", typeBar: { message: "no search match" } });
|
|
3187
|
-
return;
|
|
3188
|
-
}
|
|
3189
|
-
const snapshot = renderedSelectableItems;
|
|
3190
|
-
typeBar?.input && (typeBar.input.value = "");
|
|
3191
|
-
events.emit({ type: "typebar.patch", typeBar: { value: "" } });
|
|
3192
|
-
runAppAction("Activate search result", () => activateSelection(runtime, snapshot));
|
|
3193
|
-
return;
|
|
3194
|
-
}
|
|
3195
|
-
typeBar?.input && (typeBar.input.value = "");
|
|
3196
|
-
events.emit({ type: "typebar.patch", typeBar: { value: "" } });
|
|
3197
|
-
if (value.trim() === "") {
|
|
3198
|
-
runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
|
|
3199
|
-
return;
|
|
3200
|
-
}
|
|
3201
|
-
runAppAction("Run command", () => runtime.runIntent(intentFromTypeBar(value)));
|
|
3202
|
-
}
|
|
4010
|
+
brandText = new TextRenderable3(renderer, {
|
|
4011
|
+
id: "brand-mark",
|
|
4012
|
+
content: "RIG",
|
|
4013
|
+
position: "absolute",
|
|
4014
|
+
left: layout.centerLeft,
|
|
4015
|
+
top: Math.max(0, layout.centerTop - 1),
|
|
4016
|
+
fg: RIG_UI.lime,
|
|
4017
|
+
zIndex: 8,
|
|
4018
|
+
selectable: false
|
|
3203
4019
|
});
|
|
3204
|
-
renderer.
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
}
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
const
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
if (typeBarEmpty && (key.name === "right" || key.name === "tab")) {
|
|
3229
|
-
runAppAction("Navigate", () => runtime.runIntent(primaryNavIntent(nextPrimaryScene(store.getState().scene, 1))));
|
|
3230
|
-
return;
|
|
3231
|
-
}
|
|
3232
|
-
if (typeBarEmpty && key.name === "left") {
|
|
3233
|
-
runAppAction("Navigate", () => runtime.runIntent(primaryNavIntent(nextPrimaryScene(store.getState().scene, -1))));
|
|
3234
|
-
return;
|
|
3235
|
-
}
|
|
3236
|
-
if (!searchScene && typeBarEmpty && key.name === "r") {
|
|
3237
|
-
runAppAction("Opening runs", () => runtime.runIntent(primaryNavIntent("fleet")));
|
|
3238
|
-
return;
|
|
3239
|
-
}
|
|
3240
|
-
if (!searchScene && typeBarEmpty && key.name === "t") {
|
|
3241
|
-
runAppAction("Opening tasks", () => runtime.runIntent(primaryNavIntent("tasks")));
|
|
3242
|
-
return;
|
|
3243
|
-
}
|
|
3244
|
-
if (!searchScene && typeBarEmpty && (key.name === "h" || key.sequence === "?")) {
|
|
3245
|
-
runAppAction("Opening help", () => runtime.runIntent(primaryNavIntent("help")));
|
|
3246
|
-
return;
|
|
3247
|
-
}
|
|
3248
|
-
if (searchScene && key.name === "up" || !searchScene && typeBarEmpty && (key.name === "up" || key.name === "k")) {
|
|
3249
|
-
if (moveSelection(store, -1, renderedSelectableItems))
|
|
4020
|
+
renderer.root.add(brandText);
|
|
4021
|
+
typeBar = null;
|
|
4022
|
+
const onKeyPress = (key) => {
|
|
4023
|
+
handleAppKeyPress({
|
|
4024
|
+
renderer: () => renderer,
|
|
4025
|
+
runtime,
|
|
4026
|
+
getState: () => store.getState(),
|
|
4027
|
+
getTypeBar: () => typeBar,
|
|
4028
|
+
getRenderedSelectableItems: () => renderedSelectableItems,
|
|
4029
|
+
emitTypeBarPatch: (typeBarPatch) => events.emit({ type: "typebar.patch", typeBar: typeBarPatch }),
|
|
4030
|
+
runAppAction,
|
|
4031
|
+
primaryNavIntent,
|
|
4032
|
+
nextPrimaryScene,
|
|
4033
|
+
moveSelection: (delta, items) => moveSelection(store, delta, items),
|
|
4034
|
+
activateSelection: (items) => activateSelection(runtime, items)
|
|
4035
|
+
}, key);
|
|
4036
|
+
};
|
|
4037
|
+
renderer.keyInput.on("keypress", onKeyPress);
|
|
4038
|
+
resources.add(() => renderer?.keyInput.off("keypress", onKeyPress));
|
|
4039
|
+
const onPaste = (event) => {
|
|
4040
|
+
const state = store.getState();
|
|
4041
|
+
if (state.scene === "handoff") {
|
|
4042
|
+
const host = getActivePiHost();
|
|
4043
|
+
if (!host)
|
|
3250
4044
|
return;
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
4045
|
+
host.write(event.bytes);
|
|
4046
|
+
} else if (state.scene === "command") {
|
|
4047
|
+
const host = getActiveCommandHost();
|
|
4048
|
+
if (!host)
|
|
3254
4049
|
return;
|
|
4050
|
+
host.write(event.bytes);
|
|
3255
4051
|
}
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
if (!searchScene && key.name === "q" && typeBarEmpty) {
|
|
3261
|
-
renderer?.destroy();
|
|
3262
|
-
return;
|
|
3263
|
-
}
|
|
3264
|
-
if (key.name === "escape") {
|
|
3265
|
-
if (inputValue) {
|
|
3266
|
-
if (typeBar)
|
|
3267
|
-
typeBar.input.value = "";
|
|
3268
|
-
events.emit({ type: "typebar.patch", typeBar: { value: "", message: undefined } });
|
|
3269
|
-
} else {
|
|
3270
|
-
runAppAction("Opening runs", () => runtime.runIntent({ scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } }));
|
|
3271
|
-
}
|
|
3272
|
-
}
|
|
3273
|
-
});
|
|
3274
|
-
renderer.keyInput.on("paste", (event) => {
|
|
3275
|
-
const host = getActivePiHost();
|
|
3276
|
-
if (!host || store.getState().scene !== "handoff")
|
|
3277
|
-
return;
|
|
3278
|
-
host.write(event.bytes);
|
|
3279
|
-
if (typeBar)
|
|
3280
|
-
typeBar.input.value = "";
|
|
3281
|
-
});
|
|
3282
|
-
renderer.on("selection", (selection) => {
|
|
4052
|
+
};
|
|
4053
|
+
renderer.keyInput.on("paste", onPaste);
|
|
4054
|
+
resources.add(() => renderer?.keyInput.off("paste", onPaste));
|
|
4055
|
+
const onSelection = (selection) => {
|
|
3283
4056
|
const selectedText = selection.getSelectedText();
|
|
3284
4057
|
if (!selectedText)
|
|
3285
4058
|
return;
|
|
3286
4059
|
renderer?.copyToClipboardOSC52?.(selectedText);
|
|
3287
4060
|
events.emit({ type: "typebar.patch", typeBar: { message: "copied selection" } });
|
|
3288
|
-
}
|
|
3289
|
-
renderer.on("
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
4061
|
+
};
|
|
4062
|
+
renderer.on("selection", onSelection);
|
|
4063
|
+
resources.add(() => renderer?.off("selection", onSelection));
|
|
4064
|
+
const onResize = () => {
|
|
4065
|
+
renderApp();
|
|
4066
|
+
};
|
|
4067
|
+
renderer.on("resize", onResize);
|
|
4068
|
+
resources.add(() => renderer?.off("resize", onResize));
|
|
4069
|
+
const onDestroy = () => {
|
|
3294
4070
|
destroyed = true;
|
|
3295
4071
|
stopActivePiHost("renderer destroyed");
|
|
4072
|
+
stopActiveCommandHost("renderer destroyed");
|
|
3296
4073
|
imageVisuals?.destroy();
|
|
3297
|
-
if (
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
4074
|
+
if (renderTimer)
|
|
4075
|
+
clearTimeout(renderTimer);
|
|
4076
|
+
renderTimer = null;
|
|
4077
|
+
resources.disposeAll();
|
|
4078
|
+
};
|
|
4079
|
+
renderer.on("destroy", onDestroy);
|
|
4080
|
+
resources.add(() => renderer?.off("destroy", onDestroy));
|
|
4081
|
+
renderApp();
|
|
3303
4082
|
requestRender(renderer);
|
|
3304
4083
|
queueMicrotask(() => {
|
|
3305
4084
|
(async () => {
|
|
@@ -3317,46 +4096,77 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3317
4096
|
}
|
|
3318
4097
|
})();
|
|
3319
4098
|
});
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
if (state.scene === "handoff" && getActivePiHost())
|
|
3325
|
-
return;
|
|
3326
|
-
store.patch({ tick: state.tick + 1 });
|
|
3327
|
-
}, FRAME_MS);
|
|
3328
|
-
await new Promise((resolveDestroy) => renderer?.on("destroy", resolveDestroy));
|
|
4099
|
+
await new Promise((resolveDestroy) => {
|
|
4100
|
+
renderer?.on("destroy", resolveDestroy);
|
|
4101
|
+
resources.add(() => renderer?.off("destroy", resolveDestroy));
|
|
4102
|
+
});
|
|
3329
4103
|
} finally {
|
|
3330
4104
|
destroyed = true;
|
|
3331
|
-
if (
|
|
3332
|
-
|
|
3333
|
-
|
|
4105
|
+
if (renderTimer)
|
|
4106
|
+
clearTimeout(renderTimer);
|
|
4107
|
+
renderTimer = null;
|
|
4108
|
+
resources.disposeAll();
|
|
3334
4109
|
imageVisuals?.destroy();
|
|
3335
4110
|
stopActivePiHost("app shutdown");
|
|
3336
|
-
|
|
3337
|
-
renderer?.dropLive?.();
|
|
3338
|
-
} catch {}
|
|
4111
|
+
stopActiveCommandHost("app shutdown");
|
|
3339
4112
|
if (renderer && !renderer.isDestroyed)
|
|
3340
4113
|
renderer.destroy();
|
|
3341
4114
|
}
|
|
3342
4115
|
}
|
|
3343
|
-
function renderNow(renderer, graphics, imageVisuals, scrollPanels, typeBar, textLines, state, renderers) {
|
|
4116
|
+
function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPanels, typeBar, brandText, textLines, state, renderers, preloaderTransition, scheduleRenderAfter) {
|
|
3344
4117
|
const layout = computeStageLayout(renderer.width, renderer.height);
|
|
3345
4118
|
const animationTick = Math.floor(state.tick / ANIMATION_SLOWDOWN);
|
|
3346
4119
|
const renderState = animationTick === state.tick ? state : { ...state, tick: animationTick };
|
|
3347
|
-
const
|
|
3348
|
-
|
|
4120
|
+
const now = Date.now();
|
|
4121
|
+
const sceneJustChanged = preloaderTransition.scene !== renderState.scene;
|
|
4122
|
+
maybeStartPreloaderTransition(preloaderTransition, renderState.scene, now);
|
|
4123
|
+
const targetFrame = rendererForScene(renderState.scene, renderers)(renderState, layout);
|
|
4124
|
+
const targetContentLayout = targetFrame.fullScreen ? { ...layout, centerWidth: layout.width, centerLeft: 0, centerTop: 0, centerHeight: Math.min(layout.height, MAX_SCENE_LINES) } : layout;
|
|
4125
|
+
const targetPanelsForImages = (targetFrame.panels ?? []).map((panel) => ({ ...panel }));
|
|
4126
|
+
let targetVisualReady = true;
|
|
4127
|
+
if (targetFrame.terminalActive) {
|
|
3349
4128
|
getActivePiHost()?.resize(layout.width, Math.max(1, layout.height));
|
|
3350
4129
|
}
|
|
4130
|
+
if (renderState.scene === "command") {
|
|
4131
|
+
getActiveCommandHost()?.resize(Math.max(40, targetContentLayout.centerWidth - 12), Math.max(10, targetContentLayout.centerHeight - 6));
|
|
4132
|
+
}
|
|
4133
|
+
if (imageVisuals && targetFrame.terminalActive) {
|
|
4134
|
+
imageVisuals.clear();
|
|
4135
|
+
} else if (imageVisuals && !targetFrame.fullScreen && !sceneJustChanged) {
|
|
4136
|
+
const visualInput = {
|
|
4137
|
+
layout: targetContentLayout,
|
|
4138
|
+
pixelSize: pixelSizeForRenderer(renderer),
|
|
4139
|
+
panels: targetPanelsForImages,
|
|
4140
|
+
scene: state.scene,
|
|
4141
|
+
tick: state.tick
|
|
4142
|
+
};
|
|
4143
|
+
imageVisuals.render(visualInput);
|
|
4144
|
+
targetVisualReady = imageVisuals.isReady(visualInput);
|
|
4145
|
+
}
|
|
4146
|
+
const preloaderActive = sceneJustChanged || isPreloaderActive(preloaderTransition, targetVisualReady, now);
|
|
4147
|
+
imageVisuals?.setFlushPaused(preloaderActive);
|
|
4148
|
+
const frame = preloaderActive ? renderAsciiPreloaderFrame(renderState.scene, layout, state.tick) : targetFrame;
|
|
3351
4149
|
if (graphics) {
|
|
3352
4150
|
resizeGraphicsLayer(graphics, layout);
|
|
3353
|
-
if (
|
|
4151
|
+
if (targetFrame.terminalActive || targetFrame.fullScreen || preloaderActive)
|
|
3354
4152
|
clearGraphicsLayer(graphics);
|
|
3355
4153
|
else
|
|
3356
4154
|
drawAmbientField(graphics, layout, animationTick, state.scene);
|
|
3357
4155
|
}
|
|
3358
|
-
if (
|
|
3359
|
-
|
|
4156
|
+
if (preloaderBackdrop) {
|
|
4157
|
+
preloaderBackdrop.visible = preloaderActive;
|
|
4158
|
+
preloaderBackdrop.left = 0;
|
|
4159
|
+
preloaderBackdrop.top = 0;
|
|
4160
|
+
preloaderBackdrop.width = layout.width;
|
|
4161
|
+
preloaderBackdrop.height = layout.height;
|
|
4162
|
+
preloaderBackdrop.zIndex = 100;
|
|
4163
|
+
preloaderBackdrop.backgroundColor = PRELOADER_BACKDROP;
|
|
4164
|
+
}
|
|
4165
|
+
if (brandText) {
|
|
4166
|
+
brandText.visible = !targetFrame.terminalActive && !preloaderActive;
|
|
4167
|
+
brandText.content = "RIG";
|
|
4168
|
+
brandText.left = layout.centerLeft;
|
|
4169
|
+
brandText.top = Math.max(0, layout.centerTop - 1);
|
|
3360
4170
|
}
|
|
3361
4171
|
const contentLayout = frame.fullScreen ? { ...layout, centerWidth: layout.width, centerLeft: 0, centerTop: 0, centerHeight: Math.min(layout.height, MAX_SCENE_LINES) } : layout;
|
|
3362
4172
|
const sceneLines = fitSceneLines(frame.lines, contentLayout);
|
|
@@ -3382,18 +4192,13 @@ function renderNow(renderer, graphics, imageVisuals, scrollPanels, typeBar, text
|
|
|
3382
4192
|
...panel,
|
|
3383
4193
|
lines: panel.lines.map(remapSelectableLine)
|
|
3384
4194
|
}));
|
|
3385
|
-
if (imageVisuals && !frame.terminalActive && !frame.fullScreen) {
|
|
3386
|
-
imageVisuals.render({
|
|
3387
|
-
layout: contentLayout,
|
|
3388
|
-
pixelSize: pixelSizeForRenderer(renderer),
|
|
3389
|
-
panels: renderedPanels,
|
|
3390
|
-
scene: state.scene,
|
|
3391
|
-
tick: state.tick
|
|
3392
|
-
});
|
|
3393
|
-
}
|
|
3394
4195
|
if (graphics && !frame.terminalActive)
|
|
3395
4196
|
drawPanelChrome(graphics, contentLayout, renderedPanels);
|
|
3396
|
-
renderedSceneLines.forEach((entry, index) =>
|
|
4197
|
+
renderedSceneLines.forEach((entry, index) => {
|
|
4198
|
+
const textLine = textLines[index];
|
|
4199
|
+
textLine.zIndex = preloaderActive ? 102 : 5;
|
|
4200
|
+
applyTextLine(textLine, entry, contentLayout.centerTop + index, contentLayout.centerLeft, contentLayout.centerWidth);
|
|
4201
|
+
});
|
|
3397
4202
|
clearTextLines(textLines, renderedSceneLines.length);
|
|
3398
4203
|
applyScrollPanels(scrollPanels, contentLayout, renderedPanels);
|
|
3399
4204
|
if (typeBar) {
|
|
@@ -3407,6 +4212,10 @@ function renderNow(renderer, graphics, imageVisuals, scrollPanels, typeBar, text
|
|
|
3407
4212
|
}
|
|
3408
4213
|
}
|
|
3409
4214
|
requestRender(renderer);
|
|
4215
|
+
if (preloaderActive) {
|
|
4216
|
+
const minDelay = Math.max(0, preloaderTransition.minUntilMs - Date.now());
|
|
4217
|
+
scheduleRenderAfter?.(sceneJustChanged ? 0 : Math.max(32, Math.min(160, minDelay || 96)));
|
|
4218
|
+
}
|
|
3410
4219
|
return renderedSelectableItems;
|
|
3411
4220
|
}
|
|
3412
4221
|
export {
|