@h-rig/cli 0.0.6-alpha.81 → 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 +2807 -1443
- 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 +102 -36
- 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 +13776 -12369
- 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 +3806 -2878
- 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 +15 -13
- package/dist/src/app-opentui/registry.js +3228 -2396
- 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 +413 -958
- package/dist/src/app-opentui/render/image-visual-layer.d.ts +19 -10
- package/dist/src/app-opentui/render/image-visual-layer.js +391 -357
- 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 +2329 -1512
- 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 -1480
|
@@ -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,52 +2490,43 @@ 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 = [];
|
|
2504
|
+
imagesVisible = false;
|
|
2505
|
+
flushPaused = false;
|
|
1614
2506
|
requestId = 0;
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
lastChromeKey = "";
|
|
2507
|
+
lastBackgroundKey = "";
|
|
2508
|
+
fatalWorkerError = null;
|
|
1618
2509
|
destroyed = false;
|
|
1619
|
-
constructor(stdout = process.stdout) {
|
|
2510
|
+
constructor(stdout = process.stdout, onFrameReady) {
|
|
1620
2511
|
this.stdout = stdout;
|
|
1621
|
-
this.
|
|
1622
|
-
|
|
1623
|
-
this.startWorker(this.motionSlot);
|
|
1624
|
-
this.startWorker(this.panelSlot);
|
|
1625
|
-
this.startWorker(this.chromeSlot);
|
|
1626
|
-
}
|
|
2512
|
+
this.onFrameReady = onFrameReady;
|
|
2513
|
+
this.protocol = getRigTerminalCapabilities().imageProtocol;
|
|
1627
2514
|
}
|
|
1628
2515
|
get enabled() {
|
|
1629
|
-
return
|
|
2516
|
+
return this.protocol === "kitty";
|
|
1630
2517
|
}
|
|
1631
2518
|
clear() {
|
|
1632
2519
|
if (this.enabled) {
|
|
1633
|
-
|
|
2520
|
+
if (this.imagesVisible)
|
|
2521
|
+
this.stdout.write([this.imageId, ...this.panelImageIds, ...this.panelChromeImageIds].map((id) => deleteKittyImage(id)).join("") + "\x1B[?25h");
|
|
2522
|
+
this.imagesVisible = false;
|
|
1634
2523
|
this.pendingFrames.clear();
|
|
1635
2524
|
this.recentWrittenKeys.length = 0;
|
|
1636
2525
|
this.resetSlot(this.motionSlot);
|
|
1637
|
-
this.
|
|
1638
|
-
this.resetSlot(this.chromeSlot);
|
|
1639
|
-
this.lastMotionFrameIndex = -1;
|
|
1640
|
-
this.lastPanelFrameIndex = -1;
|
|
1641
|
-
this.lastChromeKey = "";
|
|
2526
|
+
this.lastBackgroundKey = "";
|
|
1642
2527
|
this.outputBudgetBytes = this.outputBytesPerSecond;
|
|
1643
2528
|
this.lastBudgetAtMs = Date.now();
|
|
1644
2529
|
this.motionSlot.clearedThroughRequestId = this.requestId;
|
|
1645
|
-
this.panelSlot.clearedThroughRequestId = this.requestId;
|
|
1646
|
-
this.chromeSlot.clearedThroughRequestId = this.requestId;
|
|
1647
2530
|
}
|
|
1648
2531
|
}
|
|
1649
2532
|
destroy() {
|
|
@@ -1651,19 +2534,31 @@ class ImageVisualLayer {
|
|
|
1651
2534
|
return;
|
|
1652
2535
|
this.clear();
|
|
1653
2536
|
this.destroyed = true;
|
|
1654
|
-
const workers = [this.motionSlot.worker
|
|
2537
|
+
const workers = [this.motionSlot.worker];
|
|
1655
2538
|
this.motionSlot.worker = null;
|
|
1656
|
-
this.panelSlot.worker = null;
|
|
1657
|
-
this.chromeSlot.worker = null;
|
|
1658
2539
|
for (const worker of workers)
|
|
1659
2540
|
if (worker)
|
|
1660
2541
|
worker.terminate().catch(() => {
|
|
1661
2542
|
return;
|
|
1662
2543
|
});
|
|
1663
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
|
+
}
|
|
1664
2555
|
render(input) {
|
|
1665
2556
|
if (!this.enabled || this.destroyed)
|
|
1666
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();
|
|
1667
2562
|
const width = Math.max(1, Math.floor(input.pixelSize.width));
|
|
1668
2563
|
const height = Math.max(1, Math.floor(input.pixelSize.height));
|
|
1669
2564
|
if (width * height * 4 > MAX_IMAGE_BYTES)
|
|
@@ -1671,54 +2566,32 @@ class ImageVisualLayer {
|
|
|
1671
2566
|
if (this.isOutputBackedUp())
|
|
1672
2567
|
return;
|
|
1673
2568
|
this.refillOutputBudget();
|
|
1674
|
-
const
|
|
1675
|
-
const
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
if (!input.force && frameIndex === this.lastMotionFrameIndex)
|
|
1680
|
-
return;
|
|
1681
|
-
this.scheduleRender(this.motionSlot, { ...input, tick: visualTick, layer: "full" }, this.imageId, -10, input.force);
|
|
1682
|
-
this.lastMotionFrameIndex = frameIndex;
|
|
1683
|
-
return;
|
|
1684
|
-
}
|
|
1685
|
-
const motionFrameIndex = Math.floor(now / this.frameIntervalMs);
|
|
1686
|
-
if ((input.force || motionFrameIndex !== this.lastMotionFrameIndex) && this.outputBudgetBytes >= MIN_BACKGROUND_BUDGET_BYTES) {
|
|
1687
|
-
this.scheduleRender(this.motionSlot, { ...input, pixelSize: scaledPixelSize(input.pixelSize, droneLayerScale(), input.layout), panels: [], tick: visualTick, layer: "background" }, this.imageId, -30, input.force);
|
|
1688
|
-
this.lastMotionFrameIndex = motionFrameIndex;
|
|
1689
|
-
}
|
|
1690
|
-
const panelFrameIndex = Math.floor(now / this.panelIntervalMs);
|
|
1691
|
-
if ((input.force || panelFrameIndex !== this.lastPanelFrameIndex) && this.outputBudgetBytes >= MIN_PANEL_BUDGET_BYTES) {
|
|
1692
|
-
this.scheduleRender(this.panelSlot, { ...input, pixelSize: scaledPixelSize(input.pixelSize, panelLayerScale(), input.layout), tick: visualTick, layer: "panel-patches" }, this.imageId, -12, input.force, this.panelImageIds);
|
|
1693
|
-
this.lastPanelFrameIndex = panelFrameIndex;
|
|
1694
|
-
}
|
|
1695
|
-
const chromeInput = { ...input, tick: 0, layer: "panel-chrome" };
|
|
1696
|
-
const chromeKey = imageVisualFrameKey(chromeInput);
|
|
1697
|
-
if (input.force || chromeKey !== this.lastChromeKey) {
|
|
1698
|
-
this.scheduleRender(this.chromeSlot, chromeInput, this.imageId, -8, input.force, this.panelChromeImageIds);
|
|
1699
|
-
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;
|
|
1700
2574
|
}
|
|
1701
2575
|
}
|
|
1702
2576
|
flush() {
|
|
2577
|
+
this.throwIfFatal();
|
|
2578
|
+
if (this.flushPaused)
|
|
2579
|
+
return false;
|
|
1703
2580
|
if (this.pendingFrames.size === 0)
|
|
1704
2581
|
return false;
|
|
1705
2582
|
this.refillOutputBudget();
|
|
1706
2583
|
if (this.isOutputBackedUp()) {
|
|
1707
|
-
const
|
|
2584
|
+
const retainedSplit = this.pendingFrames.get("split");
|
|
1708
2585
|
this.pendingFrames.clear();
|
|
1709
|
-
if (
|
|
1710
|
-
this.pendingFrames.set("
|
|
2586
|
+
if (retainedSplit)
|
|
2587
|
+
this.pendingFrames.set("split", retainedSplit);
|
|
1711
2588
|
return false;
|
|
1712
2589
|
}
|
|
1713
2590
|
const framePriority = (frame) => {
|
|
1714
2591
|
const layer = pendingLayerKey(frame.key);
|
|
1715
|
-
if (layer === "
|
|
1716
|
-
return 0;
|
|
1717
|
-
if (layer === "background")
|
|
2592
|
+
if (layer === "split")
|
|
1718
2593
|
return 1;
|
|
1719
|
-
|
|
1720
|
-
return 2;
|
|
1721
|
-
return 2;
|
|
2594
|
+
return 4;
|
|
1722
2595
|
};
|
|
1723
2596
|
const frames = Array.from(this.pendingFrames.values()).sort((a, b) => framePriority(a) - framePriority(b));
|
|
1724
2597
|
const prefix = "\x1B[?25l\x1B7\x1B[s\x1B[H";
|
|
@@ -1730,12 +2603,14 @@ class ImageVisualLayer {
|
|
|
1730
2603
|
for (const frame of frames) {
|
|
1731
2604
|
const candidate = sequence + frame.sequence;
|
|
1732
2605
|
const candidateBytes = Buffer.byteLength(prefix + candidate + suffix);
|
|
1733
|
-
|
|
2606
|
+
const layer = pendingLayerKey(frame.key);
|
|
2607
|
+
const allowOneStaticFrame = selected.length === 0 && layer === "split";
|
|
2608
|
+
if (candidateBytes <= this.outputBudgetBytes || allowOneStaticFrame) {
|
|
1734
2609
|
selected.push(frame);
|
|
1735
2610
|
sequence = candidate;
|
|
1736
2611
|
byteLength = candidateBytes;
|
|
1737
|
-
} else if (
|
|
1738
|
-
retained.set(
|
|
2612
|
+
} else if (layer === "split") {
|
|
2613
|
+
retained.set(layer, frame);
|
|
1739
2614
|
}
|
|
1740
2615
|
}
|
|
1741
2616
|
this.pendingFrames.clear();
|
|
@@ -1750,10 +2625,11 @@ class ImageVisualLayer {
|
|
|
1750
2625
|
this.stdout.cork?.();
|
|
1751
2626
|
const accepted = this.stdout.write(payload);
|
|
1752
2627
|
this.stdout.uncork?.();
|
|
2628
|
+
this.imagesVisible = true;
|
|
1753
2629
|
return accepted;
|
|
1754
2630
|
}
|
|
1755
2631
|
createSlot() {
|
|
1756
|
-
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 };
|
|
1757
2633
|
}
|
|
1758
2634
|
resetSlot(slot) {
|
|
1759
2635
|
slot.busy = false;
|
|
@@ -1781,7 +2657,23 @@ class ImageVisualLayer {
|
|
|
1781
2657
|
isKnownKey(slot, key) {
|
|
1782
2658
|
return this.recentWrittenKeys.includes(key) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === key) || key === slot.inFlightKey || key === slot.queuedKey;
|
|
1783
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
|
+
}
|
|
1784
2675
|
scheduleRender(slot, input, imageId, zIndex, force, imageIds) {
|
|
2676
|
+
this.throwIfFatal();
|
|
1785
2677
|
const key = imageVisualFrameKey(input);
|
|
1786
2678
|
if (!force && this.isKnownKey(slot, key))
|
|
1787
2679
|
return;
|
|
@@ -1794,38 +2686,41 @@ class ImageVisualLayer {
|
|
|
1794
2686
|
requestId: ++this.requestId,
|
|
1795
2687
|
key
|
|
1796
2688
|
};
|
|
1797
|
-
|
|
1798
|
-
|
|
2689
|
+
slot.latestRequestId = request.requestId;
|
|
2690
|
+
if (!slot.worker) {
|
|
2691
|
+
this.renderInline(slot, request);
|
|
1799
2692
|
return;
|
|
1800
2693
|
}
|
|
1801
|
-
this.
|
|
2694
|
+
this.enqueueWorkerRender(slot, request);
|
|
1802
2695
|
}
|
|
1803
|
-
startWorker(slot) {
|
|
2696
|
+
startWorker(slot, name) {
|
|
1804
2697
|
const worker = createImageWorker();
|
|
1805
2698
|
if (!worker)
|
|
1806
2699
|
return;
|
|
1807
2700
|
slot.worker = worker;
|
|
1808
2701
|
worker.on("message", (message) => this.handleWorkerMessage(slot, message));
|
|
1809
|
-
worker.once("error", () =>
|
|
1810
|
-
|
|
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
|
+
});
|
|
1811
2710
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
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
|
+
}
|
|
1823
2721
|
}
|
|
1824
2722
|
enqueueWorkerRender(slot, request) {
|
|
1825
|
-
|
|
1826
|
-
this.renderSync(slot, request);
|
|
1827
|
-
return;
|
|
1828
|
-
}
|
|
2723
|
+
this.throwIfFatal();
|
|
1829
2724
|
if (slot.busy) {
|
|
1830
2725
|
slot.queuedRequest = request;
|
|
1831
2726
|
slot.queuedKey = request.key;
|
|
@@ -1834,13 +2729,16 @@ class ImageVisualLayer {
|
|
|
1834
2729
|
this.dispatchWorkerRender(slot, request);
|
|
1835
2730
|
}
|
|
1836
2731
|
dispatchWorkerRender(slot, request) {
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2732
|
+
this.throwIfFatal();
|
|
2733
|
+
if (!slot.worker)
|
|
2734
|
+
this.failWorker(String(request.layer ?? "visual"), new Error("worker is unavailable"));
|
|
2735
|
+
slot.busy = true;
|
|
2736
|
+
slot.inFlightKey = request.key;
|
|
2737
|
+
try {
|
|
2738
|
+
slot.worker.postMessage(request);
|
|
2739
|
+
} catch (error) {
|
|
2740
|
+
this.failWorker(String(request.layer ?? "visual"), error);
|
|
1840
2741
|
}
|
|
1841
|
-
slot.busy = true;
|
|
1842
|
-
slot.inFlightKey = request.key;
|
|
1843
|
-
slot.worker.postMessage(request);
|
|
1844
2742
|
}
|
|
1845
2743
|
handleWorkerMessage(slot, message) {
|
|
1846
2744
|
if (this.destroyed || !message || typeof message !== "object")
|
|
@@ -1848,14 +2746,14 @@ class ImageVisualLayer {
|
|
|
1848
2746
|
const response = message;
|
|
1849
2747
|
slot.busy = false;
|
|
1850
2748
|
slot.inFlightKey = "";
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
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
|
+
}
|
|
1859
2757
|
}
|
|
1860
2758
|
const next = slot.queuedRequest;
|
|
1861
2759
|
slot.queuedRequest = null;
|
|
@@ -1863,12 +2761,6 @@ class ImageVisualLayer {
|
|
|
1863
2761
|
if (next && !this.isKnownKey(slot, next.key))
|
|
1864
2762
|
this.dispatchWorkerRender(slot, next);
|
|
1865
2763
|
}
|
|
1866
|
-
renderSync(slot, request) {
|
|
1867
|
-
const result = renderImageVisualFrame(request);
|
|
1868
|
-
if (!result || this.isKnownKey(slot, result.key) || this.isOutputBackedUp())
|
|
1869
|
-
return;
|
|
1870
|
-
this.stageFrame(result);
|
|
1871
|
-
}
|
|
1872
2764
|
}
|
|
1873
2765
|
function pixelSizeForRenderer(renderer) {
|
|
1874
2766
|
const resolution = renderer.resolution;
|
|
@@ -1880,11 +2772,12 @@ function pixelSizeForRenderer(renderer) {
|
|
|
1880
2772
|
};
|
|
1881
2773
|
}
|
|
1882
2774
|
function forceModernImageVisuals() {
|
|
1883
|
-
return
|
|
2775
|
+
return getRigTerminalCapabilities().supportsKittyImages;
|
|
1884
2776
|
}
|
|
1885
2777
|
|
|
1886
2778
|
// packages/cli/src/app-opentui/render/text.ts
|
|
1887
|
-
import { TextAttributes as
|
|
2779
|
+
import { RGBA as RGBA3, TextAttributes as TextAttributes3, TextRenderable } from "@opentui/core";
|
|
2780
|
+
var TRANSPARENT2 = RGBA3.fromInts(0, 0, 0, 0);
|
|
1888
2781
|
function selectableMouseHandler(currentLineRef, onLineMouseDown) {
|
|
1889
2782
|
return (event) => {
|
|
1890
2783
|
const currentLine = currentLineRef();
|
|
@@ -1935,7 +2828,8 @@ function applyTextLine(renderable, line, top, left, width) {
|
|
|
1935
2828
|
renderable.width = width;
|
|
1936
2829
|
renderable.content = line?.styledText ?? line?.text ?? "";
|
|
1937
2830
|
renderable.fg = line?.fg ?? RIG_UI.ink2;
|
|
1938
|
-
renderable.
|
|
2831
|
+
renderable.bg = line?.bg ?? TRANSPARENT2;
|
|
2832
|
+
renderable.attributes = line?.bold ? TextAttributes3.BOLD : line?.dim ? TextAttributes3.DIM : 0;
|
|
1939
2833
|
}
|
|
1940
2834
|
function applyFlowTextLine(renderable, line, width) {
|
|
1941
2835
|
renderable.setRigSceneLine?.(line);
|
|
@@ -1944,7 +2838,8 @@ function applyFlowTextLine(renderable, line, width) {
|
|
|
1944
2838
|
renderable.width = Math.max(1, width);
|
|
1945
2839
|
renderable.content = line?.styledText ?? line?.text ?? "";
|
|
1946
2840
|
renderable.fg = line?.fg ?? RIG_UI.ink2;
|
|
1947
|
-
renderable.
|
|
2841
|
+
renderable.bg = line?.bg ?? TRANSPARENT2;
|
|
2842
|
+
renderable.attributes = line?.bold ? TextAttributes3.BOLD : line?.dim ? TextAttributes3.DIM : 0;
|
|
1948
2843
|
}
|
|
1949
2844
|
function clearTextLines(renderables, from = 0) {
|
|
1950
2845
|
for (let index = from;index < renderables.length; index += 1) {
|
|
@@ -1954,26 +2849,27 @@ function clearTextLines(renderables, from = 0) {
|
|
|
1954
2849
|
renderable.top = -1;
|
|
1955
2850
|
renderable.left = 0;
|
|
1956
2851
|
renderable.width = 1;
|
|
2852
|
+
renderable.bg = TRANSPARENT2;
|
|
1957
2853
|
}
|
|
1958
2854
|
}
|
|
1959
2855
|
|
|
1960
2856
|
// packages/cli/src/app-opentui/render/panels.ts
|
|
1961
|
-
import { RGBA as
|
|
1962
|
-
var
|
|
2857
|
+
import { RGBA as RGBA4, ScrollBoxRenderable } from "@opentui/core";
|
|
2858
|
+
var TRANSPARENT3 = RGBA4.fromInts(0, 0, 0, 0);
|
|
1963
2859
|
var MAX_PANEL_LINES = 420;
|
|
1964
2860
|
function hexToRgba(hex, alpha) {
|
|
1965
2861
|
const clean = hex.replace(/^#/, "");
|
|
1966
2862
|
const full = clean.length === 3 ? clean.split("").map((part) => `${part}${part}`).join("") : clean;
|
|
1967
2863
|
const value = Number.parseInt(full, 16);
|
|
1968
2864
|
if (!Number.isFinite(value))
|
|
1969
|
-
return
|
|
1970
|
-
return
|
|
2865
|
+
return RGBA4.fromInts(14, 15, 17, alpha);
|
|
2866
|
+
return RGBA4.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255, alpha);
|
|
1971
2867
|
}
|
|
1972
2868
|
function panelBackground(panel) {
|
|
1973
2869
|
return hexToRgba(panel.backgroundColor ?? RIG_UI.panel, panel.backgroundAlpha ?? 184);
|
|
1974
2870
|
}
|
|
1975
2871
|
function panelBorder(panel) {
|
|
1976
|
-
return panel.borderColor ? hexToRgba(panel.borderColor, panel.borderAlpha ?? 54) :
|
|
2872
|
+
return panel.borderColor ? hexToRgba(panel.borderColor, panel.borderAlpha ?? 54) : TRANSPARENT3;
|
|
1977
2873
|
}
|
|
1978
2874
|
function createScrollPanelLayer(renderer, id, onLineMouseDown) {
|
|
1979
2875
|
const panel = new ScrollBoxRenderable(renderer, {
|
|
@@ -1983,7 +2879,7 @@ function createScrollPanelLayer(renderer, id, onLineMouseDown) {
|
|
|
1983
2879
|
top: -2,
|
|
1984
2880
|
width: 1,
|
|
1985
2881
|
height: 1,
|
|
1986
|
-
backgroundColor:
|
|
2882
|
+
backgroundColor: TRANSPARENT3,
|
|
1987
2883
|
border: false,
|
|
1988
2884
|
shouldFill: true,
|
|
1989
2885
|
opacity: 1,
|
|
@@ -1999,7 +2895,7 @@ function createScrollPanelLayer(renderer, id, onLineMouseDown) {
|
|
|
1999
2895
|
visible: false,
|
|
2000
2896
|
width: 1,
|
|
2001
2897
|
trackOptions: {
|
|
2002
|
-
backgroundColor:
|
|
2898
|
+
backgroundColor: TRANSPARENT3,
|
|
2003
2899
|
foregroundColor: hexToRgba(RIG_UI.ink4, 84)
|
|
2004
2900
|
}
|
|
2005
2901
|
},
|
|
@@ -2007,18 +2903,24 @@ function createScrollPanelLayer(renderer, id, onLineMouseDown) {
|
|
|
2007
2903
|
height: 0,
|
|
2008
2904
|
visible: false,
|
|
2009
2905
|
trackOptions: {
|
|
2010
|
-
backgroundColor:
|
|
2011
|
-
foregroundColor:
|
|
2906
|
+
backgroundColor: TRANSPARENT3,
|
|
2907
|
+
foregroundColor: TRANSPARENT3
|
|
2012
2908
|
}
|
|
2013
2909
|
}
|
|
2014
2910
|
});
|
|
2015
2911
|
panel.__rigTextLines = [];
|
|
2016
|
-
|
|
2017
|
-
|
|
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);
|
|
2018
2921
|
panel.__rigTextLines.push(line);
|
|
2019
2922
|
panel.add(line);
|
|
2020
2923
|
}
|
|
2021
|
-
return panel;
|
|
2022
2924
|
}
|
|
2023
2925
|
function hidePanel(panel) {
|
|
2024
2926
|
panel.visible = false;
|
|
@@ -2026,7 +2928,7 @@ function hidePanel(panel) {
|
|
|
2026
2928
|
panel.left = 0;
|
|
2027
2929
|
panel.width = 1;
|
|
2028
2930
|
panel.height = 1;
|
|
2029
|
-
panel.backgroundColor =
|
|
2931
|
+
panel.backgroundColor = TRANSPARENT3;
|
|
2030
2932
|
panel.border = false;
|
|
2031
2933
|
panel.__rigPanelId = undefined;
|
|
2032
2934
|
panel.__rigTextLines.forEach((line) => applyFlowTextLine(line, undefined, 1));
|
|
@@ -2038,19 +2940,12 @@ function applyScrollPanels(panels, layout, scenePanels) {
|
|
|
2038
2940
|
hidePanel(renderable);
|
|
2039
2941
|
return;
|
|
2040
2942
|
}
|
|
2041
|
-
const
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
const
|
|
2047
|
-
const left = cardLeft + (panel.chrome === "ad-terminal" ? 1 : 0);
|
|
2048
|
-
const top = cardTop + headerHeight + (panel.chrome === "ad-terminal" ? 1 : 0);
|
|
2049
|
-
const width = Math.max(1, Math.min(layout.width - left, cardWidth - (panel.chrome === "ad-terminal" ? 2 : 0)));
|
|
2050
|
-
const height = Math.max(1, Math.min(layout.height - top, cardHeight - headerHeight - (panel.chrome === "ad-terminal" ? 2 : 0)));
|
|
2051
|
-
const paddingX = panel.paddingX ?? (panel.chrome === "ad-terminal" ? 5 : 2);
|
|
2052
|
-
const paddingY = panel.paddingY ?? (panel.chrome === "ad-terminal" ? 2 : 1);
|
|
2053
|
-
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;
|
|
2054
2949
|
renderable.visible = true;
|
|
2055
2950
|
renderable.left = left;
|
|
2056
2951
|
renderable.top = top;
|
|
@@ -2058,7 +2953,7 @@ function applyScrollPanels(panels, layout, scenePanels) {
|
|
|
2058
2953
|
renderable.height = height;
|
|
2059
2954
|
renderable.zIndex = panel.zIndex ?? 6;
|
|
2060
2955
|
renderable.opacity = panel.opacity ?? 1;
|
|
2061
|
-
renderable.backgroundColor = panel.chrome === "ad-terminal" ?
|
|
2956
|
+
renderable.backgroundColor = panel.chrome === "ad-terminal" ? TRANSPARENT3 : panelBackground(panel);
|
|
2062
2957
|
renderable.border = panel.chrome === "ad-terminal" ? false : panel.border ?? false;
|
|
2063
2958
|
renderable.borderColor = panelBorder(panel);
|
|
2064
2959
|
renderable.paddingX = paddingX;
|
|
@@ -2071,56 +2966,31 @@ function applyScrollPanels(panels, layout, scenePanels) {
|
|
|
2071
2966
|
renderable.__rigPanelId = panel.id;
|
|
2072
2967
|
renderable.scrollTo(0);
|
|
2073
2968
|
}
|
|
2969
|
+
const lineCount = Math.min(panel.lines.length, MAX_PANEL_LINES);
|
|
2970
|
+
ensurePanelLineCount(renderable, lineCount);
|
|
2074
2971
|
renderable.__rigTextLines.forEach((line, lineIndex) => {
|
|
2075
|
-
applyFlowTextLine(line, panel.lines[lineIndex], contentWidth);
|
|
2972
|
+
applyFlowTextLine(line, lineIndex < lineCount ? panel.lines[lineIndex] : undefined, contentWidth);
|
|
2076
2973
|
});
|
|
2077
2974
|
});
|
|
2078
2975
|
}
|
|
2079
2976
|
|
|
2080
2977
|
// packages/cli/src/app-opentui/render/type-bar.ts
|
|
2081
|
-
import { InputRenderable, InputRenderableEvents, TextRenderable as TextRenderable2 } from "@opentui/core";
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
id: "rig-typebar-prefix",
|
|
2085
|
-
content: " rig \u203A",
|
|
2086
|
-
position: "absolute",
|
|
2087
|
-
left: 0,
|
|
2088
|
-
top: 0,
|
|
2089
|
-
fg: RIG_UI.lime,
|
|
2090
|
-
selectable: true
|
|
2091
|
-
});
|
|
2092
|
-
const input = new InputRenderable(renderer, {
|
|
2093
|
-
id: "rig-typebar-input",
|
|
2094
|
-
position: "absolute",
|
|
2095
|
-
left: 7,
|
|
2096
|
-
top: 0,
|
|
2097
|
-
width: 40,
|
|
2098
|
-
placeholder: "fleet \xB7 tasks \xB7 run next \xB7 help",
|
|
2099
|
-
textColor: RIG_UI.ink,
|
|
2100
|
-
cursorColor: RIG_UI.lime,
|
|
2101
|
-
cursorStyle: { style: "block", blinking: false },
|
|
2102
|
-
showCursor: true,
|
|
2103
|
-
selectable: true
|
|
2104
|
-
});
|
|
2105
|
-
const footer = new TextRenderable2(renderer, {
|
|
2106
|
-
id: "rig-footer",
|
|
2107
|
-
content: "",
|
|
2108
|
-
position: "absolute",
|
|
2109
|
-
left: 0,
|
|
2110
|
-
top: 1,
|
|
2111
|
-
width: "100%",
|
|
2112
|
-
fg: RIG_UI.ink4,
|
|
2113
|
-
selectable: true
|
|
2114
|
-
});
|
|
2115
|
-
input.focus();
|
|
2116
|
-
return { input, prefix, footer };
|
|
2117
|
-
}
|
|
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);
|
|
2118
2981
|
function positionTypeBar(renderables, input, footer, width, typeBarTop, footerTop) {
|
|
2119
|
-
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;
|
|
2120
2990
|
renderables.prefix.top = typeBarTop;
|
|
2121
2991
|
renderables.input.top = typeBarTop;
|
|
2122
|
-
renderables.input.left =
|
|
2123
|
-
renderables.input.width = Math.max(10, width -
|
|
2992
|
+
renderables.input.left = 5;
|
|
2993
|
+
renderables.input.width = Math.max(10, width - 8);
|
|
2124
2994
|
renderables.input.placeholder = input.placeholder;
|
|
2125
2995
|
renderables.input.showCursor = true;
|
|
2126
2996
|
renderables.footer.top = footerTop;
|
|
@@ -2130,6 +3000,8 @@ function positionTypeBar(renderables, input, footer, width, typeBarTop, footerTo
|
|
|
2130
3000
|
renderables.input.focus();
|
|
2131
3001
|
}
|
|
2132
3002
|
function hideTypeBar(renderables, height) {
|
|
3003
|
+
renderables.background.visible = false;
|
|
3004
|
+
renderables.background.top = height + 1;
|
|
2133
3005
|
renderables.prefix.content = "";
|
|
2134
3006
|
renderables.prefix.top = height + 1;
|
|
2135
3007
|
renderables.input.value = "";
|
|
@@ -2141,17 +3013,22 @@ function hideTypeBar(renderables, height) {
|
|
|
2141
3013
|
renderables.footer.content = "";
|
|
2142
3014
|
renderables.footer.top = height + 1;
|
|
2143
3015
|
}
|
|
2144
|
-
function
|
|
2145
|
-
|
|
2146
|
-
|
|
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;
|
|
2147
3023
|
}
|
|
2148
3024
|
function formatFooter(footer, width) {
|
|
3025
|
+
const safeMessage = safeFooterMessage(footer.message);
|
|
2149
3026
|
const parts = [
|
|
2150
|
-
footer.project ?
|
|
2151
|
-
footer.server ?
|
|
2152
|
-
footer.auth ?
|
|
3027
|
+
footer.project ? "workspace" : null,
|
|
3028
|
+
footer.server ? "server selected" : null,
|
|
3029
|
+
footer.auth ? "auth ready" : null,
|
|
2153
3030
|
footer.run ? `run ${footer.run}` : null,
|
|
2154
|
-
|
|
3031
|
+
safeMessage
|
|
2155
3032
|
].filter((part) => Boolean(part));
|
|
2156
3033
|
return ` ${truncateText(parts.join(" \xB7 "), Math.max(8, width - 2))}`;
|
|
2157
3034
|
}
|
|
@@ -2163,9 +3040,6 @@ function line(text, options = {}) {
|
|
|
2163
3040
|
function blank() {
|
|
2164
3041
|
return { text: "" };
|
|
2165
3042
|
}
|
|
2166
|
-
function center(text, fg = RIG_UI.ink2, bold = false) {
|
|
2167
|
-
return { text, fg, bold, align: "center" };
|
|
2168
|
-
}
|
|
2169
3043
|
function deckRow(input) {
|
|
2170
3044
|
const prefix = input.active ? "\u25B8" : " ";
|
|
2171
3045
|
const label = input.label.toUpperCase().padEnd(10);
|
|
@@ -2176,6 +3050,9 @@ function deckRow(input) {
|
|
|
2176
3050
|
activateOnClick: input.activateOnClick
|
|
2177
3051
|
});
|
|
2178
3052
|
}
|
|
3053
|
+
function kv(label, value, fg = RIG_UI.ink2) {
|
|
3054
|
+
return line(` ${label.toUpperCase().padEnd(10)} ${value}`, { fg });
|
|
3055
|
+
}
|
|
2179
3056
|
function fitSceneLine(input, layout) {
|
|
2180
3057
|
const fitted = truncateText(input.text, layout.centerWidth);
|
|
2181
3058
|
return {
|
|
@@ -2190,9 +3067,52 @@ function makeSceneFrame(input) {
|
|
|
2190
3067
|
return input;
|
|
2191
3068
|
}
|
|
2192
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
|
+
|
|
2193
3113
|
// packages/cli/src/app-opentui/drone.ts
|
|
2194
|
-
import { RGBA as
|
|
2195
|
-
var
|
|
3114
|
+
import { RGBA as RGBA6, StyledText as StyledText2, TextAttributes as TextAttributes4 } from "@opentui/core";
|
|
3115
|
+
var MINI_DRONE2 = [
|
|
2196
3116
|
"(!!!) (!!!)",
|
|
2197
3117
|
" \\%==%/ ",
|
|
2198
3118
|
" %%?%% ",
|
|
@@ -2207,13 +3127,13 @@ var LEAD_MARK = [
|
|
|
2207
3127
|
var BLADE_FRAMES = ["---", "\\\\\\", "|||", "///"];
|
|
2208
3128
|
var EYE_FRAMES = ["o", "@", "\u2022", "."];
|
|
2209
3129
|
var COLOR = {
|
|
2210
|
-
body:
|
|
2211
|
-
mini:
|
|
2212
|
-
rotor:
|
|
2213
|
-
path:
|
|
2214
|
-
eye:
|
|
2215
|
-
dim:
|
|
2216
|
-
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)
|
|
2217
3137
|
};
|
|
2218
3138
|
function bladeForTick(tick, phase = 0) {
|
|
2219
3139
|
return BLADE_FRAMES[(Math.floor(tick / 3) + phase) % BLADE_FRAMES.length];
|
|
@@ -2223,12 +3143,12 @@ function eyeForTick(tick, phase = 0) {
|
|
|
2223
3143
|
return pulse > 0.55 ? EYE_FRAMES[1] : pulse > 0.1 ? EYE_FRAMES[0] : pulse > -0.45 ? EYE_FRAMES[2] : EYE_FRAMES[3];
|
|
2224
3144
|
}
|
|
2225
3145
|
function chunk2(text, fg, bold = false, dim = false) {
|
|
2226
|
-
let attributes =
|
|
3146
|
+
let attributes = TextAttributes4.NONE;
|
|
2227
3147
|
if (bold)
|
|
2228
|
-
attributes |=
|
|
3148
|
+
attributes |= TextAttributes4.BOLD;
|
|
2229
3149
|
if (dim)
|
|
2230
|
-
attributes |=
|
|
2231
|
-
return { __isChunk: true, text, fg, ...attributes !==
|
|
3150
|
+
attributes |= TextAttributes4.DIM;
|
|
3151
|
+
return { __isChunk: true, text, fg, ...attributes !== TextAttributes4.NONE ? { attributes } : {} };
|
|
2232
3152
|
}
|
|
2233
3153
|
function styledLine(text, colorFor) {
|
|
2234
3154
|
const chunks = [];
|
|
@@ -2237,599 +3157,315 @@ function styledLine(text, colorFor) {
|
|
|
2237
3157
|
const flush = () => {
|
|
2238
3158
|
if (!run || !runColor)
|
|
2239
3159
|
return;
|
|
2240
|
-
chunks.push(chunk2(run, COLOR[runColor], runColor === "body" || runColor === "eye", runColor === "dim"));
|
|
2241
|
-
run = "";
|
|
2242
|
-
};
|
|
2243
|
-
for (const char of text) {
|
|
2244
|
-
const next = colorFor(char);
|
|
2245
|
-
if (next !== runColor) {
|
|
2246
|
-
flush();
|
|
2247
|
-
runColor = next;
|
|
2248
|
-
}
|
|
2249
|
-
run += char;
|
|
2250
|
-
}
|
|
2251
|
-
flush();
|
|
2252
|
-
return new StyledText(chunks.length > 0 ? chunks : [chunk2("", COLOR.ink)]);
|
|
2253
|
-
}
|
|
2254
|
-
function droneColor(char) {
|
|
2255
|
-
if (char === "?" || char === "o" || char === "@" || char === "\u2022")
|
|
2256
|
-
return "eye";
|
|
2257
|
-
if (char === "%" || char === "=")
|
|
2258
|
-
return "mini";
|
|
2259
|
-
if (char === "$")
|
|
2260
|
-
return "body";
|
|
2261
|
-
if (char === "(" || char === ")" || char === "/" || char === "\\" || char === "|" || char === "-")
|
|
2262
|
-
return "rotor";
|
|
2263
|
-
if (char === "\xB7" || char === "\u2022" || char === "'" || char === ".")
|
|
2264
|
-
return "path";
|
|
2265
|
-
if (char.trim() === "")
|
|
2266
|
-
return "dim";
|
|
2267
|
-
return "ink";
|
|
2268
|
-
}
|
|
2269
|
-
function motionColor(char) {
|
|
2270
|
-
if (char === "\u25CF" || char === "\u25C6" || char === "\u25C9")
|
|
2271
|
-
return "body";
|
|
2272
|
-
if (char === "\u2022" || char === "\xB7" || char === "\u2500" || char === "\u2502" || char === "\u2571" || char === "\u2572" || char === "\u256D" || char === "\u256E" || char === "\u2570" || char === "\u256F")
|
|
2273
|
-
return "path";
|
|
2274
|
-
if (char === "\u258C" || char === "\u2590" || char === "\u2581" || char === "\u2594")
|
|
2275
|
-
return "mini";
|
|
2276
|
-
return droneColor(char);
|
|
2277
|
-
}
|
|
2278
|
-
function materializeDrone(lines, tick, phase = 0) {
|
|
2279
|
-
const blade = bladeForTick(tick, phase);
|
|
2280
|
-
const eye = eyeForTick(tick, phase);
|
|
2281
|
-
return lines.map((line2) => line2.replaceAll("!!!", blade).replaceAll("?", eye));
|
|
2282
|
-
}
|
|
2283
|
-
function brandFleetLines(tick, align = "center") {
|
|
2284
|
-
const left = materializeDrone(MINI_DRONE, tick, 0);
|
|
2285
|
-
const right = materializeDrone(MINI_DRONE, tick, 2);
|
|
2286
|
-
const lead = materializeDrone(LEAD_MARK, tick, 1);
|
|
2287
|
-
const packet = ["\xB7", "\u2022", "\u25CF", "\u2022"][Math.floor(tick / 2) % 4];
|
|
2288
|
-
const bridge = [
|
|
2289
|
-
` ${left[0]} ${lead[0]} ${right[0]}`,
|
|
2290
|
-
` ${left[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${lead[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${right[1]}`,
|
|
2291
|
-
` ${left[2]} \xB7' '${lead[2]}' '\xB7 ${right[2]}`,
|
|
2292
|
-
` ${left[3]} \u2572 signal bus \u2571 ${right[3]}`,
|
|
2293
|
-
` ${left[4]} \xB7\u2500\u2500\u2500${packet}\u2500\u2500\u2500\u2500\u2500\u2500${packet}\u2500\u2500\u2500\xB7 ${right[4]}`
|
|
2294
|
-
];
|
|
2295
|
-
return bridge.map((text) => ({
|
|
2296
|
-
text,
|
|
2297
|
-
styledText: styledLine(text, motionColor),
|
|
2298
|
-
fg: RIG_UI.limeDim,
|
|
2299
|
-
align
|
|
2300
|
-
}));
|
|
2301
|
-
}
|
|
2302
|
-
function errorDrone(tick) {
|
|
2303
|
-
return brandFleetLines(tick).map((line2, index) => ({
|
|
2304
|
-
...line2,
|
|
2305
|
-
fg: index % 2 === 0 ? RIG_UI.red : line2.fg
|
|
2306
|
-
}));
|
|
2307
|
-
}
|
|
2308
|
-
|
|
2309
|
-
// packages/cli/src/app-opentui/scenes/error.ts
|
|
2310
|
-
function renderErrorScene(state) {
|
|
2311
|
-
const message = state.error?.message ?? "Unknown Rig app error";
|
|
2312
|
-
const hint = state.error?.hint ?? "type main, help, or retry after fixing the issue";
|
|
2313
|
-
return makeSceneFrame({
|
|
2314
|
-
scene: "error",
|
|
2315
|
-
title: "Rig error",
|
|
2316
|
-
lines: [
|
|
2317
|
-
...errorDrone(state.tick).slice(2, 16),
|
|
2318
|
-
blank(),
|
|
2319
|
-
center("DRONE SIGNAL DEGRADED", RIG_UI.red, true),
|
|
2320
|
-
center(message, RIG_UI.ink2),
|
|
2321
|
-
blank(),
|
|
2322
|
-
center(hint, RIG_UI.limeDim)
|
|
2323
|
-
],
|
|
2324
|
-
typeBarPlaceholder: hint,
|
|
2325
|
-
live: true
|
|
2326
|
-
});
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
// packages/cli/src/app-opentui/scenes/help.ts
|
|
2330
|
-
var COMMANDS = [
|
|
2331
|
-
["runs", "show runs"],
|
|
2332
|
-
["tasks", "show task source"],
|
|
2333
|
-
["type in Runs", "search loaded runs"],
|
|
2334
|
-
["type in Tasks", "search loaded tasks"],
|
|
2335
|
-
["tasks open", "filter to open tasks"],
|
|
2336
|
-
["tasks all", "show every task returned by the source"],
|
|
2337
|
-
["tasks mine", "show tasks assigned to @me"],
|
|
2338
|
-
["tasks blocked", "show blocked work"],
|
|
2339
|
-
["tasks closed", "show closed work"],
|
|
2340
|
-
["tasks sort priority", "rank by priority"],
|
|
2341
|
-
["tasks sort recency", "rank by update time"],
|
|
2342
|
-
["tasks sort status", "group by status"],
|
|
2343
|
-
["run <task-id>", "dispatch selected task id"],
|
|
2344
|
-
["run next", "dispatch next runnable task"],
|
|
2345
|
-
["attach <run>", "open bundled Pi for a run"],
|
|
2346
|
-
["tab / \u2192", "next screen"],
|
|
2347
|
-
["\u2190", "previous screen"],
|
|
2348
|
-
["\u2191 \u2193", "move selection in Runs/Tasks"],
|
|
2349
|
-
["j k", "move selection outside search-first list screens"],
|
|
2350
|
-
["enter", "activate selected row"],
|
|
2351
|
-
["mouse wheel", "scroll panel under cursor"],
|
|
2352
|
-
["ctrl-]", "detach from embedded Pi"],
|
|
2353
|
-
["ctrl-c / q", "exit"]
|
|
2354
|
-
];
|
|
2355
|
-
function panelWidth(layout) {
|
|
2356
|
-
return layout?.centerWidth ?? 100;
|
|
2357
|
-
}
|
|
2358
|
-
function panelHeight(layout, top = 3) {
|
|
2359
|
-
return Math.max(4, (layout?.centerHeight ?? 24) - top);
|
|
2360
|
-
}
|
|
2361
|
-
function clip(value, width) {
|
|
2362
|
-
if (width <= 0)
|
|
2363
|
-
return "";
|
|
2364
|
-
return value.length <= width ? value : `${value.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
2365
|
-
}
|
|
2366
|
-
function commandRow(width, label, detail, index) {
|
|
2367
|
-
const keyWidth = Math.min(22, Math.max(12, Math.floor(width * 0.28)));
|
|
2368
|
-
const key = clip(label, keyWidth).padEnd(keyWidth);
|
|
2369
|
-
return line(`${key} ${clip(detail, Math.max(8, width - keyWidth - 4))}`, { fg: index < 3 ? RIG_UI.ink : RIG_UI.ink2 });
|
|
2370
|
-
}
|
|
2371
|
-
function unsupportedCommand(state) {
|
|
2372
|
-
const value = state.intent.action.payload?.unsupported ?? state.intent.action.payload?.unknown;
|
|
2373
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
2374
|
-
}
|
|
2375
|
-
function renderHelpScene(state, layout) {
|
|
2376
|
-
const width = panelWidth(layout);
|
|
2377
|
-
const unsupported = unsupportedCommand(state);
|
|
2378
|
-
const panelTop = 0;
|
|
2379
|
-
const panelLines = [
|
|
2380
|
-
...unsupported ? [
|
|
2381
|
-
line(`${unsupported} uses the plain CLI path.`, { fg: RIG_UI.yellow, bold: true }),
|
|
2382
|
-
line("Exit and rerun with RIG_PLAIN=1 if you want legacy text output.", { fg: RIG_UI.ink3 }),
|
|
2383
|
-
line("", { fg: RIG_UI.ink3 })
|
|
2384
|
-
] : [],
|
|
2385
|
-
line("COMMAND ACTION", { fg: RIG_UI.ink3, bold: true }),
|
|
2386
|
-
line("", { fg: RIG_UI.ink3 }),
|
|
2387
|
-
...COMMANDS.map(([label, detail], index) => commandRow(width - 6, label, detail, index))
|
|
2388
|
-
];
|
|
2389
|
-
return makeSceneFrame({
|
|
2390
|
-
scene: "help",
|
|
2391
|
-
title: "Rig help",
|
|
2392
|
-
lines: [
|
|
2393
|
-
line("", { fg: RIG_UI.ink4 }),
|
|
2394
|
-
line(" rig help", { fg: RIG_UI.ink3 })
|
|
2395
|
-
],
|
|
2396
|
-
panels: [{
|
|
2397
|
-
id: "help-reference",
|
|
2398
|
-
top: panelTop,
|
|
2399
|
-
width,
|
|
2400
|
-
height: panelHeight(layout, panelTop),
|
|
2401
|
-
lines: panelLines,
|
|
2402
|
-
backgroundColor: RIG_UI.panel,
|
|
2403
|
-
backgroundAlpha: 184,
|
|
2404
|
-
opacity: 1,
|
|
2405
|
-
border: false,
|
|
2406
|
-
chrome: "ad-terminal",
|
|
2407
|
-
headerText: "rig help",
|
|
2408
|
-
headerHeight: 3,
|
|
2409
|
-
paddingX: layout?.compact ? 2 : 3,
|
|
2410
|
-
paddingY: 1
|
|
2411
|
-
}],
|
|
2412
|
-
typeBarPlaceholder: "runs \xB7 tasks \xB7 help \xB7 attach <run>",
|
|
2413
|
-
footer: { message: "help" },
|
|
2414
|
-
live: true
|
|
2415
|
-
});
|
|
2416
|
-
}
|
|
2417
|
-
|
|
2418
|
-
// packages/cli/src/app-opentui/scenes/main.ts
|
|
2419
|
-
var CONFIGURED_OPTIONS = [
|
|
2420
|
-
["runs", "run list and attach"],
|
|
2421
|
-
["tasks", "task source and dispatch"],
|
|
2422
|
-
["help", "command reference"]
|
|
2423
|
-
];
|
|
2424
|
-
function renderMainScene(state) {
|
|
2425
|
-
const configured = state.data.projectConfigured === true;
|
|
2426
|
-
const selected = configured ? Math.max(0, Math.min(CONFIGURED_OPTIONS.length - 1, state.selection.index)) : Math.max(0, Math.min(1, state.selection.index));
|
|
2427
|
-
const lines = [
|
|
2428
|
-
center(configured ? "rig" : "rig setup", RIG_UI.ink, true),
|
|
2429
|
-
center(configured ? "runs \xB7 tasks \xB7 help" : "initialize this repo", RIG_UI.ink3),
|
|
2430
|
-
blank()
|
|
2431
|
-
];
|
|
2432
|
-
if (!configured) {
|
|
2433
|
-
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" }));
|
|
2434
|
-
} else {
|
|
2435
|
-
CONFIGURED_OPTIONS.forEach(([label, detail], index) => {
|
|
2436
|
-
lines.push(deckRow({ label, detail, index, active: index === selected }));
|
|
2437
|
-
});
|
|
2438
|
-
lines.push(blank(), line(` ${state.actionLabel ?? "ready"}`, { fg: state.status === "action" ? RIG_UI.cyan : RIG_UI.ink4 }));
|
|
2439
|
-
}
|
|
2440
|
-
return makeSceneFrame({
|
|
2441
|
-
scene: "main",
|
|
2442
|
-
title: "Rig",
|
|
2443
|
-
lines,
|
|
2444
|
-
typeBarPlaceholder: configured ? "runs \xB7 tasks \xB7 help" : "init \xB7 help",
|
|
2445
|
-
live: true
|
|
2446
|
-
});
|
|
2447
|
-
}
|
|
2448
|
-
|
|
2449
|
-
// packages/cli/src/app-opentui/pi-pty-host.ts
|
|
2450
|
-
import { fileURLToPath } from "url";
|
|
2451
|
-
import { basename } from "path";
|
|
2452
|
-
import { RGBA as RGBA4, StyledText as StyledText2, TextAttributes as TextAttributes4 } from "@opentui/core";
|
|
2453
|
-
import { Terminal as XtermTerminal } from "@xterm/headless";
|
|
2454
|
-
var MIN_COLS = 40;
|
|
2455
|
-
var MIN_ROWS = 12;
|
|
2456
|
-
var MAX_ROWS = 300;
|
|
2457
|
-
var MAX_SNAPSHOT_LINES = 1200;
|
|
2458
|
-
var fallbackChildScriptPath = fileURLToPath(new URL("./pi-host-child.ts", import.meta.url));
|
|
2459
|
-
var activeHost = null;
|
|
2460
|
-
function clampCols(cols) {
|
|
2461
|
-
return Math.max(MIN_COLS, Math.trunc(cols || 100));
|
|
2462
|
-
}
|
|
2463
|
-
function clampRows(rows) {
|
|
2464
|
-
return Math.max(MIN_ROWS, Math.min(MAX_ROWS, Math.trunc(rows || 35)));
|
|
2465
|
-
}
|
|
2466
|
-
var XTERM_COLOR_MODE_INDEXED_16 = 16777216;
|
|
2467
|
-
var XTERM_COLOR_MODE_INDEXED_256 = 33554432;
|
|
2468
|
-
var XTERM_COLOR_MODE_RGB = 50331648;
|
|
2469
|
-
function rgbaFromXtermColor(mode, value) {
|
|
2470
|
-
if (mode === 1 || mode === 2 || mode === XTERM_COLOR_MODE_INDEXED_16 || mode === XTERM_COLOR_MODE_INDEXED_256) {
|
|
2471
|
-
return RGBA4.fromIndex(value);
|
|
2472
|
-
}
|
|
2473
|
-
if (mode === 3 || mode === XTERM_COLOR_MODE_RGB) {
|
|
2474
|
-
return RGBA4.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255);
|
|
2475
|
-
}
|
|
2476
|
-
return;
|
|
2477
|
-
}
|
|
2478
|
-
function textAttributesFromCell(cell) {
|
|
2479
|
-
let attributes = TextAttributes4.NONE;
|
|
2480
|
-
if (cell.isBold())
|
|
2481
|
-
attributes |= TextAttributes4.BOLD;
|
|
2482
|
-
if (cell.isDim())
|
|
2483
|
-
attributes |= TextAttributes4.DIM;
|
|
2484
|
-
if (cell.isItalic())
|
|
2485
|
-
attributes |= TextAttributes4.ITALIC;
|
|
2486
|
-
if (cell.isUnderline())
|
|
2487
|
-
attributes |= TextAttributes4.UNDERLINE;
|
|
2488
|
-
if (cell.isBlink())
|
|
2489
|
-
attributes |= TextAttributes4.BLINK;
|
|
2490
|
-
if (cell.isInverse())
|
|
2491
|
-
attributes |= TextAttributes4.INVERSE;
|
|
2492
|
-
if (cell.isInvisible())
|
|
2493
|
-
attributes |= TextAttributes4.HIDDEN;
|
|
2494
|
-
if (cell.isStrikethrough())
|
|
2495
|
-
attributes |= TextAttributes4.STRIKETHROUGH;
|
|
2496
|
-
return attributes;
|
|
2497
|
-
}
|
|
2498
|
-
function sameRgba(a, b) {
|
|
2499
|
-
if (!a && !b)
|
|
2500
|
-
return true;
|
|
2501
|
-
return Boolean(a && b && a.equals(b));
|
|
2502
|
-
}
|
|
2503
|
-
function sameStyle(a, b) {
|
|
2504
|
-
return sameRgba(a.fg, b.fg) && sameRgba(a.bg, b.bg) && (a.attributes ?? 0) === (b.attributes ?? 0);
|
|
2505
|
-
}
|
|
2506
|
-
function styleFromCell(cell) {
|
|
2507
|
-
return {
|
|
2508
|
-
fg: rgbaFromXtermColor(cell.getFgColorMode(), cell.getFgColor()),
|
|
2509
|
-
bg: rgbaFromXtermColor(cell.getBgColorMode(), cell.getBgColor()),
|
|
2510
|
-
attributes: textAttributesFromCell(cell)
|
|
2511
|
-
};
|
|
2512
|
-
}
|
|
2513
|
-
function lineToStyledText(line2, cols) {
|
|
2514
|
-
if (!line2)
|
|
2515
|
-
return new StyledText2([{ __isChunk: true, text: "" }]);
|
|
2516
|
-
const chunks = [];
|
|
2517
|
-
let run = "";
|
|
2518
|
-
let runStyle = null;
|
|
2519
|
-
const flush = () => {
|
|
2520
|
-
if (!run)
|
|
2521
|
-
return;
|
|
2522
|
-
chunks.push({
|
|
2523
|
-
__isChunk: true,
|
|
2524
|
-
text: run,
|
|
2525
|
-
...runStyle?.fg ? { fg: runStyle.fg } : {},
|
|
2526
|
-
...runStyle?.bg ? { bg: runStyle.bg } : {},
|
|
2527
|
-
...(runStyle?.attributes ?? 0) !== 0 ? { attributes: runStyle.attributes } : {}
|
|
2528
|
-
});
|
|
2529
|
-
run = "";
|
|
2530
|
-
};
|
|
2531
|
-
for (let index = 0;index < cols; index += 1) {
|
|
2532
|
-
const cell = line2.getCell(index);
|
|
2533
|
-
if (!cell) {
|
|
2534
|
-
if (runStyle !== null)
|
|
2535
|
-
flush();
|
|
2536
|
-
runStyle = null;
|
|
2537
|
-
run += " ";
|
|
2538
|
-
continue;
|
|
2539
|
-
}
|
|
2540
|
-
if (cell.getWidth() === 0)
|
|
2541
|
-
continue;
|
|
2542
|
-
const style = styleFromCell(cell);
|
|
2543
|
-
if (!runStyle || !sameStyle(runStyle, style)) {
|
|
3160
|
+
chunks.push(chunk2(run, COLOR[runColor], runColor === "body" || runColor === "eye", runColor === "dim"));
|
|
3161
|
+
run = "";
|
|
3162
|
+
};
|
|
3163
|
+
for (const char of text) {
|
|
3164
|
+
const next = colorFor(char);
|
|
3165
|
+
if (next !== runColor) {
|
|
2544
3166
|
flush();
|
|
2545
|
-
|
|
3167
|
+
runColor = next;
|
|
2546
3168
|
}
|
|
2547
|
-
run +=
|
|
3169
|
+
run += char;
|
|
2548
3170
|
}
|
|
2549
3171
|
flush();
|
|
2550
|
-
return new StyledText2(chunks.length > 0 ? chunks : [
|
|
2551
|
-
}
|
|
2552
|
-
function childCommandPrefix() {
|
|
2553
|
-
const execName = basename(process.execPath).toLowerCase();
|
|
2554
|
-
const currentEntry = process.argv[1];
|
|
2555
|
-
if ((execName === "bun" || execName === "bun.exe") && currentEntry) {
|
|
2556
|
-
return [process.execPath, currentEntry, "__opentui-pi-host"];
|
|
2557
|
-
}
|
|
2558
|
-
if (currentEntry)
|
|
2559
|
-
return [process.execPath, "__opentui-pi-host"];
|
|
2560
|
-
return [process.execPath, fallbackChildScriptPath];
|
|
3172
|
+
return new StyledText2(chunks.length > 0 ? chunks : [chunk2("", COLOR.ink)]);
|
|
2561
3173
|
}
|
|
2562
|
-
function
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
};
|
|
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";
|
|
2577
3188
|
}
|
|
2578
|
-
function
|
|
2579
|
-
|
|
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);
|
|
2580
3197
|
}
|
|
2581
|
-
function
|
|
2582
|
-
|
|
2583
|
-
|
|
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));
|
|
2584
3202
|
}
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
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
|
-
|
|
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),
|
|
2780
3438
|
lines,
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
this.disposables.push(this.terminal.parser.registerCsiHandler({ prefix: ">", final: "c" }, (params) => {
|
|
2794
|
-
if (params.length === 0 || params[0] === 0)
|
|
2795
|
-
this.write("\x1B[>0;0;0c");
|
|
2796
|
-
return false;
|
|
2797
|
-
}));
|
|
2798
|
-
this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
|
|
2799
|
-
if (params[0] === 5) {
|
|
2800
|
-
this.write("\x1B[0n");
|
|
2801
|
-
} else if (params[0] === 6) {
|
|
2802
|
-
const row = this.terminal.buffer.active.cursorY + 1;
|
|
2803
|
-
const col = this.terminal.buffer.active.cursorX + 1;
|
|
2804
|
-
this.write(`\x1B[${row};${col}R`);
|
|
2805
|
-
}
|
|
2806
|
-
return false;
|
|
2807
|
-
}));
|
|
2808
|
-
}
|
|
2809
|
-
respondToRawTerminalQueries(text) {
|
|
2810
|
-
if (!text)
|
|
2811
|
-
return;
|
|
2812
|
-
const decrqm = /\x1b\[\?(\d+)\$p/g;
|
|
2813
|
-
let match;
|
|
2814
|
-
while ((match = decrqm.exec(text)) !== null) {
|
|
2815
|
-
this.write(`\x1B[?${match[1]};2$y`);
|
|
2816
|
-
}
|
|
2817
|
-
}
|
|
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
|
+
});
|
|
2818
3451
|
}
|
|
2819
3452
|
|
|
2820
3453
|
// packages/cli/src/app-opentui/runtime.ts
|
|
3454
|
+
init_pi_pty_host();
|
|
2821
3455
|
var MAX_SCENE_LINES = 200;
|
|
2822
3456
|
var MAX_SCENE_PANELS = 5;
|
|
2823
|
-
var
|
|
2824
|
-
var
|
|
3457
|
+
var ANIMATION_SLOWDOWN = 1;
|
|
3458
|
+
var PRELOADER_BACKDROP = RGBA7.fromHex(RIG_UI.bg);
|
|
2825
3459
|
var PRIMARY_NAV = [
|
|
3460
|
+
{ scene: "main", argv: ["main"], label: "Project menu" },
|
|
2826
3461
|
{ scene: "fleet", argv: ["runs"], label: "Opening runs" },
|
|
2827
3462
|
{ scene: "tasks", argv: ["tasks"], label: "Opening tasks" },
|
|
2828
3463
|
{ scene: "help", argv: ["help"], label: "Opening help" }
|
|
2829
3464
|
];
|
|
2830
3465
|
function primaryNavIntent(scene) {
|
|
2831
3466
|
const entry = PRIMARY_NAV.find((item) => item.scene === scene) ?? PRIMARY_NAV[0];
|
|
2832
|
-
|
|
3467
|
+
const kind = entry.scene === "help" || entry.scene === "main" ? "none" : "refresh";
|
|
3468
|
+
return { scene: entry.scene, argv: entry.argv, action: { kind, label: entry.label } };
|
|
2833
3469
|
}
|
|
2834
3470
|
function nextPrimaryScene(current, delta) {
|
|
2835
3471
|
const currentIndex = PRIMARY_NAV.findIndex((entry) => entry.scene === current);
|
|
@@ -2850,10 +3486,10 @@ function defaultSceneRenderer(state) {
|
|
|
2850
3486
|
scene: state.scene,
|
|
2851
3487
|
title,
|
|
2852
3488
|
lines: [
|
|
2853
|
-
{ text: title.toUpperCase()
|
|
2854
|
-
{ text: state.actionLabel ?? "adapter scene pending"
|
|
2855
|
-
{ text: "
|
|
2856
|
-
{ 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 }
|
|
2857
3493
|
],
|
|
2858
3494
|
typeBarPlaceholder: "runs \xB7 tasks \xB7 help",
|
|
2859
3495
|
live: false
|
|
@@ -2879,9 +3515,9 @@ function requestRender(renderer) {
|
|
|
2879
3515
|
function recordArray(value) {
|
|
2880
3516
|
return Array.isArray(value) ? value.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
2881
3517
|
}
|
|
2882
|
-
function
|
|
3518
|
+
function stringField2(record2, keys) {
|
|
2883
3519
|
for (const key of keys) {
|
|
2884
|
-
const value =
|
|
3520
|
+
const value = record2[key];
|
|
2885
3521
|
if (typeof value === "string" && value.trim())
|
|
2886
3522
|
return value.trim();
|
|
2887
3523
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -2889,33 +3525,84 @@ function stringField(record, keys) {
|
|
|
2889
3525
|
}
|
|
2890
3526
|
return;
|
|
2891
3527
|
}
|
|
2892
|
-
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
|
+
}
|
|
2893
3542
|
return [
|
|
2894
|
-
|
|
3543
|
+
...common,
|
|
2895
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" },
|
|
2896
3548
|
{ id: "help", label: "help", intent: { scene: "help", argv: ["help"], action: { kind: "none", label: "Opening help" } }, message: "selected help" }
|
|
2897
3549
|
];
|
|
2898
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
|
+
}
|
|
2899
3559
|
function fleetSelectableItems(state) {
|
|
2900
3560
|
const fleet = state.data.fleet;
|
|
2901
3561
|
const runs = fleet && typeof fleet === "object" && !Array.isArray(fleet) ? recordArray(fleet.runs) : [];
|
|
2902
|
-
|
|
2903
|
-
|
|
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"]);
|
|
2904
3567
|
if (!runId)
|
|
2905
3568
|
return [];
|
|
2906
|
-
const title =
|
|
3569
|
+
const title = stringField2(run, ["title", "taskTitle", "taskId"]) ?? "run";
|
|
2907
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)}` }];
|
|
2908
3571
|
});
|
|
2909
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
|
+
}
|
|
2910
3582
|
function taskSelectableItems(state) {
|
|
2911
3583
|
const tasks = state.data.tasks;
|
|
2912
3584
|
const records = tasks && typeof tasks === "object" && !Array.isArray(tasks) ? recordArray(tasks.records) : [];
|
|
2913
|
-
|
|
2914
|
-
|
|
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"]);
|
|
2915
3590
|
if (!taskId)
|
|
2916
3591
|
return [];
|
|
2917
|
-
const title =
|
|
2918
|
-
|
|
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];
|
|
2919
3606
|
});
|
|
2920
3607
|
}
|
|
2921
3608
|
function selectedRunId(state) {
|
|
@@ -2936,21 +3623,126 @@ function selectedRunId(state) {
|
|
|
2936
3623
|
}
|
|
2937
3624
|
return;
|
|
2938
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
|
+
}
|
|
2939
3634
|
function inboxSelectableItems(state) {
|
|
2940
3635
|
const inbox = state.data.inbox;
|
|
2941
3636
|
if (!inbox || typeof inbox !== "object" || Array.isArray(inbox))
|
|
2942
|
-
return
|
|
2943
|
-
const
|
|
2944
|
-
const approvals = recordArray(
|
|
2945
|
-
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();
|
|
2946
3643
|
return [...approvals, ...inputs].flatMap((item) => {
|
|
2947
|
-
const requestId =
|
|
2948
|
-
|
|
3644
|
+
const requestId = stringField2(item, ["requestId", "id"]);
|
|
3645
|
+
const runId = stringField2(item, ["runId"]);
|
|
3646
|
+
if (!requestId || !runId)
|
|
2949
3647
|
return [];
|
|
2950
|
-
|
|
2951
|
-
|
|
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
|
+
};
|
|
2952
3718
|
});
|
|
2953
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
|
+
}
|
|
2954
3746
|
function runDetailSelectableItems(state) {
|
|
2955
3747
|
const runId = selectedRunId(state);
|
|
2956
3748
|
if (!runId)
|
|
@@ -2965,12 +3757,24 @@ function selectableItems(state) {
|
|
|
2965
3757
|
switch (state.scene) {
|
|
2966
3758
|
case "main":
|
|
2967
3759
|
return mainSelectableItems(state);
|
|
3760
|
+
case "error":
|
|
3761
|
+
return errorSelectableItems();
|
|
3762
|
+
case "help":
|
|
3763
|
+
return helpSelectableItems();
|
|
2968
3764
|
case "fleet":
|
|
2969
3765
|
return fleetSelectableItems(state);
|
|
2970
3766
|
case "tasks":
|
|
2971
3767
|
return taskSelectableItems(state);
|
|
2972
3768
|
case "inbox":
|
|
2973
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();
|
|
2974
3778
|
case "run-detail":
|
|
2975
3779
|
return runDetailSelectableItems(state);
|
|
2976
3780
|
default:
|
|
@@ -3050,13 +3854,18 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3050
3854
|
let renderer = null;
|
|
3051
3855
|
let graphics = null;
|
|
3052
3856
|
let imageVisuals = null;
|
|
3857
|
+
let preloaderBackdrop = null;
|
|
3053
3858
|
const scrollPanels = [];
|
|
3054
3859
|
let typeBar = null;
|
|
3860
|
+
let brandText = null;
|
|
3055
3861
|
const textLines = [];
|
|
3056
|
-
let
|
|
3862
|
+
let renderTimer = null;
|
|
3057
3863
|
let destroyed = false;
|
|
3058
3864
|
let runnerPromise = null;
|
|
3059
3865
|
let renderedSelectableItems = [];
|
|
3866
|
+
const preloaderTransition = { scene: null, startedAtMs: 0, minUntilMs: 0 };
|
|
3867
|
+
const resources = new RuntimeResources;
|
|
3868
|
+
const focusManager = new AppFocusManager;
|
|
3060
3869
|
const emitAsyncError = (label, error) => {
|
|
3061
3870
|
const normalized = normalizeError(error);
|
|
3062
3871
|
events.emit({ type: "action.failed", label, message: normalized.message, hint: normalized.hint, cause: normalized.cause });
|
|
@@ -3064,6 +3873,23 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3064
3873
|
const runAppAction = (label, action) => {
|
|
3065
3874
|
action().catch((error) => emitAsyncError(label, error));
|
|
3066
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
|
+
};
|
|
3067
3893
|
const runtime = {
|
|
3068
3894
|
getState: () => store.getState(),
|
|
3069
3895
|
patchState: (patch) => store.patch(patch),
|
|
@@ -3090,29 +3916,35 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3090
3916
|
},
|
|
3091
3917
|
getRunnerContext: async () => {
|
|
3092
3918
|
if (!options.initializeRuntime)
|
|
3093
|
-
throw new Error("
|
|
3919
|
+
throw new Error("Runtime initializer was not provided to the OpenTUI app.");
|
|
3094
3920
|
runnerPromise ??= options.initializeRuntime();
|
|
3095
3921
|
return runnerPromise;
|
|
3096
3922
|
},
|
|
3097
3923
|
suspend: () => renderer?.suspend(),
|
|
3098
3924
|
resume: () => {
|
|
3099
3925
|
renderer?.resume();
|
|
3100
|
-
|
|
3101
|
-
renderedSelectableItems = renderNow(renderer, graphics, imageVisuals, scrollPanels, typeBar, textLines, store.getState(), options.sceneRenderers);
|
|
3926
|
+
renderApp();
|
|
3102
3927
|
},
|
|
3103
3928
|
destroy: () => renderer?.destroy()
|
|
3104
3929
|
};
|
|
3105
|
-
events.subscribe((event) => store.patch(reduceAppEvent(store.getState(), event)));
|
|
3106
|
-
store.subscribe(() => {
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
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);
|
|
3110
3942
|
try {
|
|
3111
3943
|
renderer = await createCliRenderer({
|
|
3112
3944
|
screenMode: "alternate-screen",
|
|
3113
3945
|
exitOnCtrlC: false,
|
|
3114
|
-
targetFps:
|
|
3115
|
-
maxFps:
|
|
3946
|
+
targetFps: 30,
|
|
3947
|
+
maxFps: 30,
|
|
3116
3948
|
useMouse: true,
|
|
3117
3949
|
autoFocus: false,
|
|
3118
3950
|
useKittyKeyboard: {
|
|
@@ -3124,24 +3956,41 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3124
3956
|
openConsoleOnError: false,
|
|
3125
3957
|
backgroundColor: "transparent"
|
|
3126
3958
|
});
|
|
3127
|
-
renderer.setTerminalTitle?.("
|
|
3959
|
+
renderer.setTerminalTitle?.("Operator cockpit");
|
|
3128
3960
|
const layout = computeStageLayout(renderer.width, renderer.height);
|
|
3129
|
-
imageVisuals = forceModernImageVisuals() ? new ImageVisualLayer(process.stdout) : null;
|
|
3961
|
+
imageVisuals = forceModernImageVisuals() ? new ImageVisualLayer(process.stdout, () => scheduleAppRender(0)) : null;
|
|
3130
3962
|
if (imageVisuals?.enabled) {
|
|
3131
|
-
|
|
3963
|
+
const flushImageVisuals = async () => {
|
|
3132
3964
|
imageVisuals?.flush();
|
|
3133
|
-
}
|
|
3965
|
+
};
|
|
3966
|
+
renderer.setFrameCallback(flushImageVisuals);
|
|
3967
|
+
resources.add(() => renderer?.removeFrameCallback(flushImageVisuals));
|
|
3134
3968
|
}
|
|
3135
3969
|
if (!imageVisuals?.enabled) {
|
|
3136
3970
|
graphics = createGraphicsLayer(renderer, layout);
|
|
3137
3971
|
renderer.root.add(graphics);
|
|
3138
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);
|
|
3139
3986
|
for (let index = 0;index < MAX_SCENE_PANELS; index += 1) {
|
|
3140
3987
|
const panel = createScrollPanelLayer(renderer, `rig-scroll-panel-${index}`, (line2) => {
|
|
3141
3988
|
const selectableIndex = line2.selectableIndex;
|
|
3142
3989
|
if (selectableIndex === undefined)
|
|
3143
3990
|
return;
|
|
3144
3991
|
selectIndex(store, selectableIndex, renderedSelectableItems);
|
|
3992
|
+
if (line2.activateOnClick)
|
|
3993
|
+
runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
|
|
3145
3994
|
});
|
|
3146
3995
|
scrollPanels.push(panel);
|
|
3147
3996
|
renderer.root.add(panel);
|
|
@@ -3152,146 +4001,84 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3152
4001
|
if (selectableIndex === undefined)
|
|
3153
4002
|
return;
|
|
3154
4003
|
selectIndex(store, selectableIndex, renderedSelectableItems);
|
|
4004
|
+
if (line2.activateOnClick)
|
|
4005
|
+
runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
|
|
3155
4006
|
});
|
|
3156
4007
|
textLines.push(textLine);
|
|
3157
4008
|
renderer.root.add(textLine);
|
|
3158
4009
|
}
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
typeBar: {
|
|
3169
|
-
value,
|
|
3170
|
-
...isListSearchScene(scene) ? { message: value.trim() ? `search ${JSON.stringify(value.trim())}` : undefined } : {}
|
|
3171
|
-
}
|
|
3172
|
-
});
|
|
3173
|
-
},
|
|
3174
|
-
onEnter(value) {
|
|
3175
|
-
const scene = store.getState().scene;
|
|
3176
|
-
if (isListSearchScene(scene) && value.trim()) {
|
|
3177
|
-
if (renderedSelectableItems.length === 0) {
|
|
3178
|
-
events.emit({ type: "typebar.patch", typeBar: { message: "no search match" } });
|
|
3179
|
-
return;
|
|
3180
|
-
}
|
|
3181
|
-
const snapshot = renderedSelectableItems;
|
|
3182
|
-
typeBar?.input && (typeBar.input.value = "");
|
|
3183
|
-
events.emit({ type: "typebar.patch", typeBar: { value: "" } });
|
|
3184
|
-
runAppAction("Activate search result", () => activateSelection(runtime, snapshot));
|
|
3185
|
-
return;
|
|
3186
|
-
}
|
|
3187
|
-
typeBar?.input && (typeBar.input.value = "");
|
|
3188
|
-
events.emit({ type: "typebar.patch", typeBar: { value: "" } });
|
|
3189
|
-
if (value.trim() === "") {
|
|
3190
|
-
runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
|
|
3191
|
-
return;
|
|
3192
|
-
}
|
|
3193
|
-
runAppAction("Run command", () => runtime.runIntent(intentFromTypeBar(value)));
|
|
3194
|
-
}
|
|
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
|
|
3195
4019
|
});
|
|
3196
|
-
renderer.
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
const
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
if (typeBarEmpty && (key.name === "right" || key.name === "tab")) {
|
|
3221
|
-
runAppAction("Navigate", () => runtime.runIntent(primaryNavIntent(nextPrimaryScene(store.getState().scene, 1))));
|
|
3222
|
-
return;
|
|
3223
|
-
}
|
|
3224
|
-
if (typeBarEmpty && key.name === "left") {
|
|
3225
|
-
runAppAction("Navigate", () => runtime.runIntent(primaryNavIntent(nextPrimaryScene(store.getState().scene, -1))));
|
|
3226
|
-
return;
|
|
3227
|
-
}
|
|
3228
|
-
if (!searchScene && typeBarEmpty && key.name === "r") {
|
|
3229
|
-
runAppAction("Opening runs", () => runtime.runIntent(primaryNavIntent("fleet")));
|
|
3230
|
-
return;
|
|
3231
|
-
}
|
|
3232
|
-
if (!searchScene && typeBarEmpty && key.name === "t") {
|
|
3233
|
-
runAppAction("Opening tasks", () => runtime.runIntent(primaryNavIntent("tasks")));
|
|
3234
|
-
return;
|
|
3235
|
-
}
|
|
3236
|
-
if (!searchScene && typeBarEmpty && (key.name === "h" || key.sequence === "?")) {
|
|
3237
|
-
runAppAction("Opening help", () => runtime.runIntent(primaryNavIntent("help")));
|
|
3238
|
-
return;
|
|
3239
|
-
}
|
|
3240
|
-
if (searchScene && key.name === "up" || !searchScene && typeBarEmpty && (key.name === "up" || key.name === "k")) {
|
|
3241
|
-
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)
|
|
3242
4044
|
return;
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
4045
|
+
host.write(event.bytes);
|
|
4046
|
+
} else if (state.scene === "command") {
|
|
4047
|
+
const host = getActiveCommandHost();
|
|
4048
|
+
if (!host)
|
|
3246
4049
|
return;
|
|
4050
|
+
host.write(event.bytes);
|
|
3247
4051
|
}
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
if (!searchScene && key.name === "q" && typeBarEmpty) {
|
|
3253
|
-
renderer?.destroy();
|
|
3254
|
-
return;
|
|
3255
|
-
}
|
|
3256
|
-
if (key.name === "escape") {
|
|
3257
|
-
if (inputValue) {
|
|
3258
|
-
if (typeBar)
|
|
3259
|
-
typeBar.input.value = "";
|
|
3260
|
-
events.emit({ type: "typebar.patch", typeBar: { value: "", message: undefined } });
|
|
3261
|
-
} else {
|
|
3262
|
-
runAppAction("Opening runs", () => runtime.runIntent({ scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } }));
|
|
3263
|
-
}
|
|
3264
|
-
}
|
|
3265
|
-
});
|
|
3266
|
-
renderer.keyInput.on("paste", (event) => {
|
|
3267
|
-
const host = getActivePiHost();
|
|
3268
|
-
if (!host || store.getState().scene !== "handoff")
|
|
3269
|
-
return;
|
|
3270
|
-
host.write(event.bytes);
|
|
3271
|
-
if (typeBar)
|
|
3272
|
-
typeBar.input.value = "";
|
|
3273
|
-
});
|
|
3274
|
-
renderer.on("selection", (selection) => {
|
|
4052
|
+
};
|
|
4053
|
+
renderer.keyInput.on("paste", onPaste);
|
|
4054
|
+
resources.add(() => renderer?.keyInput.off("paste", onPaste));
|
|
4055
|
+
const onSelection = (selection) => {
|
|
3275
4056
|
const selectedText = selection.getSelectedText();
|
|
3276
4057
|
if (!selectedText)
|
|
3277
4058
|
return;
|
|
3278
4059
|
renderer?.copyToClipboardOSC52?.(selectedText);
|
|
3279
4060
|
events.emit({ type: "typebar.patch", typeBar: { message: "copied selection" } });
|
|
3280
|
-
}
|
|
3281
|
-
renderer.on("
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
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 = () => {
|
|
3286
4070
|
destroyed = true;
|
|
3287
4071
|
stopActivePiHost("renderer destroyed");
|
|
4072
|
+
stopActiveCommandHost("renderer destroyed");
|
|
3288
4073
|
imageVisuals?.destroy();
|
|
3289
|
-
if (
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
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();
|
|
3295
4082
|
requestRender(renderer);
|
|
3296
4083
|
queueMicrotask(() => {
|
|
3297
4084
|
(async () => {
|
|
@@ -3309,46 +4096,77 @@ async function launchRigOpenTuiApp(options) {
|
|
|
3309
4096
|
}
|
|
3310
4097
|
})();
|
|
3311
4098
|
});
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
if (state.scene === "handoff" && getActivePiHost())
|
|
3317
|
-
return;
|
|
3318
|
-
store.patch({ tick: state.tick + 1 });
|
|
3319
|
-
}, FRAME_MS);
|
|
3320
|
-
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
|
+
});
|
|
3321
4103
|
} finally {
|
|
3322
4104
|
destroyed = true;
|
|
3323
|
-
if (
|
|
3324
|
-
|
|
3325
|
-
|
|
4105
|
+
if (renderTimer)
|
|
4106
|
+
clearTimeout(renderTimer);
|
|
4107
|
+
renderTimer = null;
|
|
4108
|
+
resources.disposeAll();
|
|
3326
4109
|
imageVisuals?.destroy();
|
|
3327
4110
|
stopActivePiHost("app shutdown");
|
|
3328
|
-
|
|
3329
|
-
renderer?.dropLive?.();
|
|
3330
|
-
} catch {}
|
|
4111
|
+
stopActiveCommandHost("app shutdown");
|
|
3331
4112
|
if (renderer && !renderer.isDestroyed)
|
|
3332
4113
|
renderer.destroy();
|
|
3333
4114
|
}
|
|
3334
4115
|
}
|
|
3335
|
-
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) {
|
|
3336
4117
|
const layout = computeStageLayout(renderer.width, renderer.height);
|
|
3337
4118
|
const animationTick = Math.floor(state.tick / ANIMATION_SLOWDOWN);
|
|
3338
4119
|
const renderState = animationTick === state.tick ? state : { ...state, tick: animationTick };
|
|
3339
|
-
const
|
|
3340
|
-
|
|
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) {
|
|
3341
4128
|
getActivePiHost()?.resize(layout.width, Math.max(1, layout.height));
|
|
3342
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;
|
|
3343
4149
|
if (graphics) {
|
|
3344
4150
|
resizeGraphicsLayer(graphics, layout);
|
|
3345
|
-
if (
|
|
4151
|
+
if (targetFrame.terminalActive || targetFrame.fullScreen || preloaderActive)
|
|
3346
4152
|
clearGraphicsLayer(graphics);
|
|
3347
4153
|
else
|
|
3348
4154
|
drawAmbientField(graphics, layout, animationTick, state.scene);
|
|
3349
4155
|
}
|
|
3350
|
-
if (
|
|
3351
|
-
|
|
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);
|
|
3352
4170
|
}
|
|
3353
4171
|
const contentLayout = frame.fullScreen ? { ...layout, centerWidth: layout.width, centerLeft: 0, centerTop: 0, centerHeight: Math.min(layout.height, MAX_SCENE_LINES) } : layout;
|
|
3354
4172
|
const sceneLines = fitSceneLines(frame.lines, contentLayout);
|
|
@@ -3374,18 +4192,13 @@ function renderNow(renderer, graphics, imageVisuals, scrollPanels, typeBar, text
|
|
|
3374
4192
|
...panel,
|
|
3375
4193
|
lines: panel.lines.map(remapSelectableLine)
|
|
3376
4194
|
}));
|
|
3377
|
-
if (imageVisuals && !frame.terminalActive && !frame.fullScreen) {
|
|
3378
|
-
imageVisuals.render({
|
|
3379
|
-
layout: contentLayout,
|
|
3380
|
-
pixelSize: pixelSizeForRenderer(renderer),
|
|
3381
|
-
panels: renderedPanels,
|
|
3382
|
-
scene: state.scene,
|
|
3383
|
-
tick: state.tick
|
|
3384
|
-
});
|
|
3385
|
-
}
|
|
3386
4195
|
if (graphics && !frame.terminalActive)
|
|
3387
4196
|
drawPanelChrome(graphics, contentLayout, renderedPanels);
|
|
3388
|
-
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
|
+
});
|
|
3389
4202
|
clearTextLines(textLines, renderedSceneLines.length);
|
|
3390
4203
|
applyScrollPanels(scrollPanels, contentLayout, renderedPanels);
|
|
3391
4204
|
if (typeBar) {
|
|
@@ -3399,6 +4212,10 @@ function renderNow(renderer, graphics, imageVisuals, scrollPanels, typeBar, text
|
|
|
3399
4212
|
}
|
|
3400
4213
|
}
|
|
3401
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
|
+
}
|
|
3402
4219
|
return renderedSelectableItems;
|
|
3403
4220
|
}
|
|
3404
4221
|
export {
|