@h-rig/cli 0.0.6-alpha.89 → 0.0.6-alpha.90
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 +8 -40
- package/dist/bin/rig.js +12519 -26675
- package/dist/src/app/board.js +74 -693
- package/dist/src/app/drone-ui.js +1 -1
- package/dist/src/app/theme.js +0 -1
- package/dist/src/commands/_async-ui.js +1 -1
- package/dist/src/commands/_authority-runs.d.ts +1 -1
- package/dist/src/commands/_authority-runs.js +12 -2
- package/dist/src/commands/_cli-format.d.ts +1 -10
- package/dist/src/commands/_cli-format.js +2 -5
- package/dist/src/commands/_connection-state.d.ts +1 -11
- package/dist/src/commands/_connection-state.js +5 -50
- package/dist/src/commands/_doctor-checks.js +40 -501
- package/dist/src/commands/_help-catalog.js +17 -99
- package/dist/src/commands/_json-output.js +0 -4
- package/dist/src/commands/_operator-view.js +54 -575
- package/dist/src/commands/_pi-frontend.d.ts +0 -27
- package/dist/src/commands/_pi-frontend.js +52 -576
- package/dist/src/commands/_preflight.js +66 -553
- package/dist/src/commands/_server-client.d.ts +0 -33
- package/dist/src/commands/_server-client.js +46 -541
- package/dist/src/commands/_snapshot-upload.js +42 -503
- package/dist/src/commands/_spinner.js +1 -1
- package/dist/src/commands/agent.js +12 -2
- package/dist/src/commands/browser.js +1 -1
- package/dist/src/commands/connect.js +15 -626
- package/dist/src/commands/doctor.js +41 -502
- package/dist/src/commands/github.js +51 -584
- package/dist/src/commands/inbox.js +41 -502
- package/dist/src/commands/init.js +74 -561
- package/dist/src/commands/inspect.js +41 -502
- package/dist/src/commands/run.js +54 -578
- package/dist/src/commands/server.js +163 -692
- package/dist/src/commands/setup.js +46 -507
- package/dist/src/commands/stats.js +58 -601
- package/dist/src/commands/task-run-driver.js +58 -509
- package/dist/src/commands/task.js +110 -729
- package/dist/src/commands.js +237 -1112
- package/dist/src/index.js +244 -1123
- package/dist/src/launcher.js +0 -4
- package/package.json +9 -13
- package/dist/src/app-opentui/adapters/command.d.ts +0 -2
- package/dist/src/app-opentui/adapters/command.js +0 -329
- package/dist/src/app-opentui/adapters/common.d.ts +0 -56
- package/dist/src/app-opentui/adapters/common.js +0 -153
- package/dist/src/app-opentui/adapters/doctor.d.ts +0 -9
- package/dist/src/app-opentui/adapters/doctor.js +0 -1217
- package/dist/src/app-opentui/adapters/family.d.ts +0 -62
- package/dist/src/app-opentui/adapters/family.js +0 -14855
- package/dist/src/app-opentui/adapters/fleet.d.ts +0 -14
- package/dist/src/app-opentui/adapters/fleet.js +0 -1335
- package/dist/src/app-opentui/adapters/inbox.d.ts +0 -43
- package/dist/src/app-opentui/adapters/inbox.js +0 -1944
- package/dist/src/app-opentui/adapters/init.d.ts +0 -11
- package/dist/src/app-opentui/adapters/init.js +0 -2818
- package/dist/src/app-opentui/adapters/inspect.d.ts +0 -52
- package/dist/src/app-opentui/adapters/inspect.js +0 -1455
- package/dist/src/app-opentui/adapters/pi-attach.d.ts +0 -32
- package/dist/src/app-opentui/adapters/pi-attach.js +0 -2101
- package/dist/src/app-opentui/adapters/pi.d.ts +0 -23
- package/dist/src/app-opentui/adapters/pi.js +0 -363
- package/dist/src/app-opentui/adapters/plugin.d.ts +0 -84
- package/dist/src/app-opentui/adapters/plugin.js +0 -544
- package/dist/src/app-opentui/adapters/repo.d.ts +0 -37
- package/dist/src/app-opentui/adapters/repo.js +0 -186
- package/dist/src/app-opentui/adapters/run-detail.d.ts +0 -27
- package/dist/src/app-opentui/adapters/run-detail.js +0 -1441
- package/dist/src/app-opentui/adapters/server.d.ts +0 -48
- package/dist/src/app-opentui/adapters/server.js +0 -1561
- package/dist/src/app-opentui/adapters/tasks.d.ts +0 -64
- package/dist/src/app-opentui/adapters/tasks.js +0 -4524
- package/dist/src/app-opentui/adapters/workspace.d.ts +0 -49
- package/dist/src/app-opentui/adapters/workspace.js +0 -333
- package/dist/src/app-opentui/autocomplete.d.ts +0 -20
- package/dist/src/app-opentui/autocomplete.js +0 -578
- package/dist/src/app-opentui/bootstrap.d.ts +0 -11
- package/dist/src/app-opentui/bootstrap.js +0 -27833
- package/dist/src/app-opentui/command-palette.d.ts +0 -3
- package/dist/src/app-opentui/command-palette.js +0 -1038
- package/dist/src/app-opentui/command-pty-host.d.ts +0 -62
- package/dist/src/app-opentui/command-pty-host.js +0 -248
- package/dist/src/app-opentui/drone.d.ts +0 -12
- package/dist/src/app-opentui/drone.js +0 -230
- package/dist/src/app-opentui/events.d.ts +0 -7
- package/dist/src/app-opentui/events.js +0 -28
- package/dist/src/app-opentui/fleet-stats.d.ts +0 -32
- package/dist/src/app-opentui/fleet-stats.js +0 -114
- package/dist/src/app-opentui/focus-manager.d.ts +0 -14
- package/dist/src/app-opentui/focus-manager.js +0 -24
- package/dist/src/app-opentui/index.d.ts +0 -8
- package/dist/src/app-opentui/index.js +0 -5260
- package/dist/src/app-opentui/intent.d.ts +0 -3
- package/dist/src/app-opentui/intent.js +0 -365
- package/dist/src/app-opentui/keymap.d.ts +0 -21
- package/dist/src/app-opentui/keymap.js +0 -1377
- package/dist/src/app-opentui/launch-routing.d.ts +0 -16
- package/dist/src/app-opentui/launch-routing.js +0 -55
- package/dist/src/app-opentui/layout.d.ts +0 -22
- package/dist/src/app-opentui/layout.js +0 -51
- package/dist/src/app-opentui/list-search.d.ts +0 -36
- package/dist/src/app-opentui/list-search.js +0 -130
- package/dist/src/app-opentui/pi-host-child.d.ts +0 -2
- package/dist/src/app-opentui/pi-host-child.js +0 -1299
- package/dist/src/app-opentui/pi-pty-host.d.ts +0 -28
- package/dist/src/app-opentui/pi-pty-host.js +0 -10
- package/dist/src/app-opentui/react/App.d.ts +0 -9
- package/dist/src/app-opentui/react/App.js +0 -3866
- package/dist/src/app-opentui/react/Backdrop.d.ts +0 -5
- package/dist/src/app-opentui/react/Backdrop.js +0 -823
- package/dist/src/app-opentui/react/ChromeHost.d.ts +0 -5
- package/dist/src/app-opentui/react/ChromeHost.js +0 -1539
- package/dist/src/app-opentui/react/SceneFrameView.d.ts +0 -7
- package/dist/src/app-opentui/react/SceneFrameView.js +0 -579
- package/dist/src/app-opentui/react/context.d.ts +0 -17
- package/dist/src/app-opentui/react/context.js +0 -37
- package/dist/src/app-opentui/react/launch.d.ts +0 -2
- package/dist/src/app-opentui/react/launch.js +0 -4897
- package/dist/src/app-opentui/react/nav.d.ts +0 -18
- package/dist/src/app-opentui/react/nav.js +0 -54
- package/dist/src/app-opentui/react/scroll.d.ts +0 -12
- package/dist/src/app-opentui/react/scroll.js +0 -21
- package/dist/src/app-opentui/react/syntax.d.ts +0 -2
- package/dist/src/app-opentui/react/syntax.js +0 -65
- package/dist/src/app-opentui/registry.d.ts +0 -4
- package/dist/src/app-opentui/registry.js +0 -22876
- package/dist/src/app-opentui/remote-link.d.ts +0 -10
- package/dist/src/app-opentui/remote-link.js +0 -47
- package/dist/src/app-opentui/render/ascii-fleet.d.ts +0 -15
- package/dist/src/app-opentui/render/ascii-fleet.js +0 -82
- package/dist/src/app-opentui/render/constants.d.ts +0 -30
- package/dist/src/app-opentui/render/constants.js +0 -64
- package/dist/src/app-opentui/render/graphics.d.ts +0 -40
- package/dist/src/app-opentui/render/graphics.js +0 -713
- package/dist/src/app-opentui/render/hover.d.ts +0 -11
- package/dist/src/app-opentui/render/hover.js +0 -34
- package/dist/src/app-opentui/render/native-host.d.ts +0 -37
- package/dist/src/app-opentui/render/native-host.js +0 -180
- package/dist/src/app-opentui/render/panel-layout.d.ts +0 -38
- package/dist/src/app-opentui/render/panel-layout.js +0 -48
- package/dist/src/app-opentui/render/panels.d.ts +0 -12
- package/dist/src/app-opentui/render/panels.js +0 -288
- package/dist/src/app-opentui/render/preloader.d.ts +0 -10
- package/dist/src/app-opentui/render/preloader.js +0 -166
- package/dist/src/app-opentui/render/scene.d.ts +0 -68
- package/dist/src/app-opentui/render/scene.js +0 -300
- package/dist/src/app-opentui/render/terminal-handoff.d.ts +0 -16
- package/dist/src/app-opentui/render/terminal-handoff.js +0 -14
- package/dist/src/app-opentui/render/text.d.ts +0 -29
- package/dist/src/app-opentui/render/text.js +0 -177
- package/dist/src/app-opentui/render/type-bar.d.ts +0 -15
- package/dist/src/app-opentui/render/type-bar.js +0 -212
- package/dist/src/app-opentui/runtime-resources.d.ts +0 -16
- package/dist/src/app-opentui/runtime-resources.js +0 -62
- package/dist/src/app-opentui/runtime.d.ts +0 -45
- package/dist/src/app-opentui/runtime.js +0 -5232
- package/dist/src/app-opentui/scenes/command.d.ts +0 -3
- package/dist/src/app-opentui/scenes/command.js +0 -118
- package/dist/src/app-opentui/scenes/doctor.d.ts +0 -3
- package/dist/src/app-opentui/scenes/doctor.js +0 -296
- package/dist/src/app-opentui/scenes/error.d.ts +0 -2
- package/dist/src/app-opentui/scenes/error.js +0 -277
- package/dist/src/app-opentui/scenes/family-domains/agent.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/agent.js +0 -349
- package/dist/src/app-opentui/scenes/family-domains/browser.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/browser.js +0 -196
- package/dist/src/app-opentui/scenes/family-domains/dist.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/dist.js +0 -244
- package/dist/src/app-opentui/scenes/family-domains/git.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/git.js +0 -196
- package/dist/src/app-opentui/scenes/family-domains/github.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/github.js +0 -275
- package/dist/src/app-opentui/scenes/family-domains/harness.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/harness.js +0 -153
- package/dist/src/app-opentui/scenes/family-domains/index.d.ts +0 -4
- package/dist/src/app-opentui/scenes/family-domains/index.js +0 -1680
- package/dist/src/app-opentui/scenes/family-domains/kit.d.ts +0 -76
- package/dist/src/app-opentui/scenes/family-domains/kit.js +0 -306
- package/dist/src/app-opentui/scenes/family-domains/profile.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/profile.js +0 -213
- package/dist/src/app-opentui/scenes/family-domains/queue.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/queue.js +0 -147
- package/dist/src/app-opentui/scenes/family-domains/remote.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/remote.js +0 -519
- package/dist/src/app-opentui/scenes/family-domains/review.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/review.js +0 -281
- package/dist/src/app-opentui/scenes/family-domains/setup.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/setup.js +0 -268
- package/dist/src/app-opentui/scenes/family-domains/stats.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/stats.js +0 -371
- package/dist/src/app-opentui/scenes/family.d.ts +0 -3
- package/dist/src/app-opentui/scenes/family.js +0 -2199
- package/dist/src/app-opentui/scenes/fleet.d.ts +0 -3
- package/dist/src/app-opentui/scenes/fleet.js +0 -766
- package/dist/src/app-opentui/scenes/handoff.d.ts +0 -2
- package/dist/src/app-opentui/scenes/handoff.js +0 -455
- package/dist/src/app-opentui/scenes/help.d.ts +0 -3
- package/dist/src/app-opentui/scenes/help.js +0 -725
- package/dist/src/app-opentui/scenes/inbox.d.ts +0 -3
- package/dist/src/app-opentui/scenes/inbox.js +0 -427
- package/dist/src/app-opentui/scenes/init.d.ts +0 -3
- package/dist/src/app-opentui/scenes/init.js +0 -181
- package/dist/src/app-opentui/scenes/inspect.d.ts +0 -3
- package/dist/src/app-opentui/scenes/inspect.js +0 -794
- package/dist/src/app-opentui/scenes/main.d.ts +0 -3
- package/dist/src/app-opentui/scenes/main.js +0 -332
- package/dist/src/app-opentui/scenes/pi.d.ts +0 -3
- package/dist/src/app-opentui/scenes/pi.js +0 -509
- package/dist/src/app-opentui/scenes/plugin.d.ts +0 -3
- package/dist/src/app-opentui/scenes/plugin.js +0 -487
- package/dist/src/app-opentui/scenes/repo.d.ts +0 -3
- package/dist/src/app-opentui/scenes/repo.js +0 -425
- package/dist/src/app-opentui/scenes/run-detail.d.ts +0 -3
- package/dist/src/app-opentui/scenes/run-detail.js +0 -467
- package/dist/src/app-opentui/scenes/server.d.ts +0 -3
- package/dist/src/app-opentui/scenes/server.js +0 -378
- package/dist/src/app-opentui/scenes/tasks.d.ts +0 -3
- package/dist/src/app-opentui/scenes/tasks.js +0 -768
- package/dist/src/app-opentui/scenes/workspace.d.ts +0 -3
- package/dist/src/app-opentui/scenes/workspace.js +0 -427
- package/dist/src/app-opentui/selectable.d.ts +0 -19
- package/dist/src/app-opentui/selectable.js +0 -80
- package/dist/src/app-opentui/state.d.ts +0 -4
- package/dist/src/app-opentui/state.js +0 -399
- package/dist/src/app-opentui/surface-catalog.d.ts +0 -20
- package/dist/src/app-opentui/surface-catalog.js +0 -542
- package/dist/src/app-opentui/theme.d.ts +0 -59
- package/dist/src/app-opentui/theme.js +0 -142
- package/dist/src/app-opentui/types.d.ts +0 -313
- package/dist/src/app-opentui/types.js +0 -1
- package/dist/src/commands/_server-events.d.ts +0 -26
- package/dist/src/commands/_server-events.js +0 -715
|
@@ -1,4897 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/cli/src/app-opentui/react/launch.tsx
|
|
3
|
-
import { createCliRenderer as createCliRenderer2 } from "@opentui/core";
|
|
4
|
-
import { createRoot } from "@opentui/react";
|
|
5
|
-
|
|
6
|
-
// packages/cli/src/app-opentui/events.ts
|
|
7
|
-
function createAppEventBus() {
|
|
8
|
-
const listeners = new Set;
|
|
9
|
-
return {
|
|
10
|
-
emit(event) {
|
|
11
|
-
for (const listener of [...listeners])
|
|
12
|
-
listener(event);
|
|
13
|
-
},
|
|
14
|
-
subscribe(listener) {
|
|
15
|
-
listeners.add(listener);
|
|
16
|
-
return () => listeners.delete(listener);
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
function normalizeError(error) {
|
|
21
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
22
|
-
const rawHint = error instanceof Error ? error.hint : undefined;
|
|
23
|
-
return {
|
|
24
|
-
message: message || "Unknown app error",
|
|
25
|
-
...typeof rawHint === "string" && rawHint.trim() ? { hint: rawHint.trim() } : {},
|
|
26
|
-
cause: error
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// packages/cli/src/app-opentui/intent.ts
|
|
31
|
-
var NAV_SCENES = new Map([
|
|
32
|
-
["", "main"],
|
|
33
|
-
["main", "main"],
|
|
34
|
-
["home", "main"],
|
|
35
|
-
["fleet", "fleet"],
|
|
36
|
-
["runs", "fleet"],
|
|
37
|
-
["run", "fleet"],
|
|
38
|
-
["tasks", "tasks"],
|
|
39
|
-
["task", "tasks"],
|
|
40
|
-
["inbox", "inbox"],
|
|
41
|
-
["server", "server"],
|
|
42
|
-
["init", "init"],
|
|
43
|
-
["doctor", "doctor"],
|
|
44
|
-
["pi", "pi"],
|
|
45
|
-
["plugin", "plugin"],
|
|
46
|
-
["repo", "repo"],
|
|
47
|
-
["workspace", "workspace"],
|
|
48
|
-
["inspect", "inspect"],
|
|
49
|
-
["help", "help"]
|
|
50
|
-
]);
|
|
51
|
-
var TASK_VIEW_COMMANDS = new Set([
|
|
52
|
-
"",
|
|
53
|
-
"all",
|
|
54
|
-
"open",
|
|
55
|
-
"ready",
|
|
56
|
-
"blocked",
|
|
57
|
-
"closed",
|
|
58
|
-
"done",
|
|
59
|
-
"mine",
|
|
60
|
-
"me",
|
|
61
|
-
"sort",
|
|
62
|
-
"status",
|
|
63
|
-
"state",
|
|
64
|
-
"assignee",
|
|
65
|
-
"assigned",
|
|
66
|
-
"label",
|
|
67
|
-
"search",
|
|
68
|
-
"q"
|
|
69
|
-
]);
|
|
70
|
-
var HOLLOW_FAMILIES = new Set([
|
|
71
|
-
"browser",
|
|
72
|
-
"stats",
|
|
73
|
-
"dist",
|
|
74
|
-
"agent",
|
|
75
|
-
"queue",
|
|
76
|
-
"remote",
|
|
77
|
-
"harness",
|
|
78
|
-
"git",
|
|
79
|
-
"setup",
|
|
80
|
-
"profile",
|
|
81
|
-
"review",
|
|
82
|
-
"inspector",
|
|
83
|
-
"test"
|
|
84
|
-
]);
|
|
85
|
-
function familyIntent(family, argv, raw) {
|
|
86
|
-
return intent("family", argv, "refresh", { family }, `Opening ${family}`, raw);
|
|
87
|
-
}
|
|
88
|
-
function isHelpCommand(command) {
|
|
89
|
-
return command === "help" || command === "--help" || command === "-h";
|
|
90
|
-
}
|
|
91
|
-
function isTaskViewCommand(command) {
|
|
92
|
-
return !command || TASK_VIEW_COMMANDS.has(command) || command.startsWith("-") && !isHelpCommand(command);
|
|
93
|
-
}
|
|
94
|
-
function intent(scene, argv, kind, payload, label, raw) {
|
|
95
|
-
return {
|
|
96
|
-
scene,
|
|
97
|
-
argv,
|
|
98
|
-
...raw ? { raw } : {},
|
|
99
|
-
action: {
|
|
100
|
-
kind,
|
|
101
|
-
...label ? { label } : {},
|
|
102
|
-
...payload ? { payload } : {}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function commandRunIntent(argv, raw, reason) {
|
|
107
|
-
const command = argv.join(" ").trim();
|
|
108
|
-
return intent("command", argv, "command-run", { argv: [...argv], ...reason ? { reason } : {} }, command ? `Running rig ${command}` : "Running rig", raw);
|
|
109
|
-
}
|
|
110
|
-
function firstValueAfter(args, option) {
|
|
111
|
-
const index = args.indexOf(option);
|
|
112
|
-
return index >= 0 ? args[index + 1] : undefined;
|
|
113
|
-
}
|
|
114
|
-
function firstNonOption(args) {
|
|
115
|
-
return args.find((part) => part.trim() && !part.startsWith("-"));
|
|
116
|
-
}
|
|
117
|
-
function inboxActionIntent(argv, command, rest, raw) {
|
|
118
|
-
const requestId = firstNonOption(rest);
|
|
119
|
-
if (command === "approve" || command === "approvals") {
|
|
120
|
-
return requestId ? intent("inbox", argv, "inbox-approve", { requestId }, `Approving ${requestId}`, raw) : intent("inbox", argv, "refresh", undefined, "Loading approvals", raw);
|
|
121
|
-
}
|
|
122
|
-
if (command === "reject") {
|
|
123
|
-
return requestId ? intent("inbox", argv, "inbox-reject", { requestId }, `Rejecting ${requestId}`, raw) : intent("inbox", argv, "refresh", undefined, "Loading approvals", raw);
|
|
124
|
-
}
|
|
125
|
-
if (command === "answer" || command === "inputs" || command === "respond") {
|
|
126
|
-
const answer = rest.slice(requestId ? 1 : 0).join(" ").trim();
|
|
127
|
-
return requestId && answer ? intent("inbox", argv, "inbox-answer", { requestId, answer }, `Answering ${requestId}`, raw) : intent("inbox", argv, "refresh", undefined, "Loading inbox", raw);
|
|
128
|
-
}
|
|
129
|
-
return intent("inbox", argv, "refresh", undefined, "Loading inbox", raw);
|
|
130
|
-
}
|
|
131
|
-
function parseSimpleTaskRunArgs(args) {
|
|
132
|
-
let next = false;
|
|
133
|
-
let task;
|
|
134
|
-
let detach = false;
|
|
135
|
-
for (let index = 0;index < args.length; index += 1) {
|
|
136
|
-
const arg = args[index];
|
|
137
|
-
if (arg === "next" || arg === "--next") {
|
|
138
|
-
next = true;
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (arg === "--detach") {
|
|
142
|
-
detach = true;
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
if (arg === "--task") {
|
|
146
|
-
const value = args[index + 1];
|
|
147
|
-
if (!value || value.startsWith("-"))
|
|
148
|
-
return { ok: false, reason: "task run --task needs an id" };
|
|
149
|
-
if (task)
|
|
150
|
-
return { ok: false, reason: "task run received more than one task id" };
|
|
151
|
-
task = value;
|
|
152
|
-
index += 1;
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (arg.startsWith("-")) {
|
|
156
|
-
return { ok: false, reason: `task run option ${arg} uses the command workspace` };
|
|
157
|
-
}
|
|
158
|
-
if (task)
|
|
159
|
-
return { ok: false, reason: "task run received more than one task id" };
|
|
160
|
-
task = arg;
|
|
161
|
-
}
|
|
162
|
-
if (next && task)
|
|
163
|
-
return { ok: false, reason: "task run cannot combine next with a task id" };
|
|
164
|
-
return { ok: true, next, ...task ? { task } : {}, detach };
|
|
165
|
-
}
|
|
166
|
-
function taskViewPayload(command, rest) {
|
|
167
|
-
const normalized = command.toLowerCase();
|
|
168
|
-
const args = command ? [command, ...rest] : rest;
|
|
169
|
-
const sort = firstValueAfter(args, "--sort") ?? firstValueAfter(args, "-s");
|
|
170
|
-
const state = firstValueAfter(args, "--state");
|
|
171
|
-
const status = firstValueAfter(args, "--status");
|
|
172
|
-
const assignee = firstValueAfter(args, "--assignee") ?? firstValueAfter(args, "--assigned");
|
|
173
|
-
const label = firstValueAfter(args, "--label");
|
|
174
|
-
const search = firstValueAfter(args, "--search") ?? firstValueAfter(args, "-q");
|
|
175
|
-
const optionPayload = {
|
|
176
|
-
...sort ? { taskSort: sort } : {},
|
|
177
|
-
...state ? { taskState: state, taskFilterPreset: "custom" } : {},
|
|
178
|
-
...status ? { taskStatus: status, taskFilterPreset: "custom" } : {},
|
|
179
|
-
...assignee ? { taskAssignee: assignee, taskFilterPreset: "custom" } : {},
|
|
180
|
-
...label ? { taskLabel: label, taskFilterPreset: "custom" } : {},
|
|
181
|
-
...search ? { taskSearch: search, taskFilterPreset: "custom" } : {}
|
|
182
|
-
};
|
|
183
|
-
if (normalized === "sort") {
|
|
184
|
-
const value = firstNonOption(rest) ?? sort ?? "priority";
|
|
185
|
-
return { payload: { ...optionPayload, taskSort: value }, label: `Sorting tasks by ${value}` };
|
|
186
|
-
}
|
|
187
|
-
if (normalized === "status") {
|
|
188
|
-
const value = firstNonOption(rest) ?? status ?? "open";
|
|
189
|
-
return { payload: { ...optionPayload, taskStatus: value, taskFilterPreset: "custom" }, label: `Filtering tasks by status ${value}` };
|
|
190
|
-
}
|
|
191
|
-
if (normalized === "state") {
|
|
192
|
-
const value = firstNonOption(rest) ?? state ?? "open";
|
|
193
|
-
return { payload: { ...optionPayload, taskState: value, taskFilterPreset: "custom" }, label: `Filtering tasks by state ${value}` };
|
|
194
|
-
}
|
|
195
|
-
if (normalized === "assignee" || normalized === "assigned" || normalized === "mine" || normalized === "me") {
|
|
196
|
-
const value = normalized === "mine" || normalized === "me" ? "me" : firstNonOption(rest) ?? assignee ?? "me";
|
|
197
|
-
return { payload: { ...optionPayload, taskAssignee: value, taskState: "open", taskFilterPreset: value === "me" ? "mine" : "custom" }, label: value === "me" ? "Filtering my tasks" : `Filtering tasks for ${value}` };
|
|
198
|
-
}
|
|
199
|
-
if (normalized === "label") {
|
|
200
|
-
const value = firstNonOption(rest) ?? label;
|
|
201
|
-
return { payload: { ...optionPayload, ...value ? { taskLabel: value } : {}, taskFilterPreset: "custom" }, label: value ? `Filtering tasks labeled ${value}` : "Filtering tasks by label" };
|
|
202
|
-
}
|
|
203
|
-
if (normalized === "search" || normalized === "q") {
|
|
204
|
-
const value = rest.join(" ").trim() || search;
|
|
205
|
-
return { payload: { ...optionPayload, ...value ? { taskSearch: value } : {}, taskFilterPreset: "custom" }, label: value ? `Searching tasks for ${value}` : "Searching tasks" };
|
|
206
|
-
}
|
|
207
|
-
if (["all", "open", "ready", "blocked", "closed", "done"].includes(normalized)) {
|
|
208
|
-
return { payload: { ...optionPayload, taskFilterPreset: normalized === "done" ? "closed" : normalized }, label: `Filtering tasks: ${normalized}` };
|
|
209
|
-
}
|
|
210
|
-
if (Object.keys(optionPayload).length > 0)
|
|
211
|
-
return { payload: optionPayload, label: "Filtering tasks" };
|
|
212
|
-
return { label: "Loading tasks" };
|
|
213
|
-
}
|
|
214
|
-
function taskViewIntent(argv, command = "", rest = [], raw) {
|
|
215
|
-
const { payload, label } = taskViewPayload(command, rest);
|
|
216
|
-
return intent("tasks", argv, "refresh", payload, label, raw);
|
|
217
|
-
}
|
|
218
|
-
function routeServer(argv, command, rest, raw, checkingLabel) {
|
|
219
|
-
if (!command || command === "status" || command === "list") {
|
|
220
|
-
return intent("server", argv, "refresh", undefined, command ? checkingLabel : "Loading server", raw);
|
|
221
|
-
}
|
|
222
|
-
if (command === "use") {
|
|
223
|
-
const alias = firstNonOption(rest);
|
|
224
|
-
if (alias === "local")
|
|
225
|
-
return intent("server", argv, "server-use-local", undefined, "Use local server", raw);
|
|
226
|
-
if (alias)
|
|
227
|
-
return intent("server", argv, "server-use-remote", { alias }, `Use ${alias}`, raw);
|
|
228
|
-
return intent("server", argv, "refresh", undefined, "Select a remote alias in Server", raw);
|
|
229
|
-
}
|
|
230
|
-
if (command === "add")
|
|
231
|
-
return intent("server", argv, "server-add-remote", { argv: [...argv] }, "Add remote server", raw);
|
|
232
|
-
if (command === "repair-link")
|
|
233
|
-
return intent("server", argv, "remote-link-repair", undefined, "Repair remote link", raw);
|
|
234
|
-
return commandRunIntent(argv, raw);
|
|
235
|
-
}
|
|
236
|
-
function routeGithub(argv, command, rest, raw) {
|
|
237
|
-
if (command === "auth" && (rest[0]?.toLowerCase() ?? "status") === "status" && rest.length <= 1) {
|
|
238
|
-
return intent("server", argv, "refresh", undefined, "Checking GitHub auth", raw);
|
|
239
|
-
}
|
|
240
|
-
if (command === "auth" && rest[0]?.toLowerCase() === "import-gh" && rest.length <= 1) {
|
|
241
|
-
return intent("server", argv, "github-auth-import", undefined, "Import GitHub auth", raw);
|
|
242
|
-
}
|
|
243
|
-
return commandRunIntent(argv, raw);
|
|
244
|
-
}
|
|
245
|
-
function routeDoctor(argv, command, raw) {
|
|
246
|
-
return command ? commandRunIntent(argv, raw) : intent("doctor", argv, "doctor-run", undefined, "Running doctor", raw);
|
|
247
|
-
}
|
|
248
|
-
function routeInbox(argv, command, rest, raw) {
|
|
249
|
-
if (command === "watch")
|
|
250
|
-
return commandRunIntent(argv, raw);
|
|
251
|
-
if ((command === "approvals" || command === "inputs") && rest.length > 0)
|
|
252
|
-
return commandRunIntent(argv, raw);
|
|
253
|
-
return command && command !== "approvals" && command !== "inputs" ? commandRunIntent(argv, raw) : inboxActionIntent(argv, command, rest, raw);
|
|
254
|
-
}
|
|
255
|
-
function intentFromArgv(argv) {
|
|
256
|
-
const [group = "", command = "", ...rest] = argv;
|
|
257
|
-
const normalizedGroup = group.toLowerCase();
|
|
258
|
-
const normalizedCommand = command.toLowerCase();
|
|
259
|
-
if (!normalizedGroup)
|
|
260
|
-
return intent("main", argv, "none", undefined, "Project menu");
|
|
261
|
-
if (normalizedGroup === "--help" || normalizedGroup === "-h" || normalizedGroup === "help") {
|
|
262
|
-
return argv.length === 1 ? intent("help", argv, "none", undefined, "Help") : commandRunIntent(argv);
|
|
263
|
-
}
|
|
264
|
-
if (normalizedGroup === "task") {
|
|
265
|
-
if (normalizedCommand === "run" || normalizedCommand === "ready" || normalizedCommand === "status")
|
|
266
|
-
return commandRunIntent(argv);
|
|
267
|
-
if (!isTaskViewCommand(normalizedCommand))
|
|
268
|
-
return commandRunIntent(argv);
|
|
269
|
-
return taskViewIntent(argv, normalizedCommand, rest);
|
|
270
|
-
}
|
|
271
|
-
if (normalizedGroup === "tasks") {
|
|
272
|
-
if (!isTaskViewCommand(normalizedCommand))
|
|
273
|
-
return commandRunIntent(argv);
|
|
274
|
-
return taskViewIntent(argv, normalizedCommand, rest);
|
|
275
|
-
}
|
|
276
|
-
if (normalizedGroup === "run") {
|
|
277
|
-
if (normalizedCommand === "attach") {
|
|
278
|
-
const runId = firstNonOption(rest);
|
|
279
|
-
const nativeOnly = Boolean(runId) && rest.filter((part) => part.trim()).length === 1;
|
|
280
|
-
return nativeOnly ? intent("handoff", argv, "run-attach", { runId }, `Attaching ${runId}`) : commandRunIntent(argv);
|
|
281
|
-
}
|
|
282
|
-
if (!normalizedCommand || normalizedCommand === "list" || normalizedCommand === "status")
|
|
283
|
-
return intent("fleet", argv, "refresh", undefined, "Loading fleet");
|
|
284
|
-
if (normalizedCommand === "stop") {
|
|
285
|
-
const runId = firstNonOption(rest);
|
|
286
|
-
return runId && rest.filter((part) => part.trim()).length === 1 ? intent("fleet", argv, "run-stop", { runId }, "Stopping run") : commandRunIntent(argv);
|
|
287
|
-
}
|
|
288
|
-
return commandRunIntent(argv);
|
|
289
|
-
}
|
|
290
|
-
if (normalizedGroup === "server")
|
|
291
|
-
return routeServer(argv, normalizedCommand, rest, undefined, "Checking server");
|
|
292
|
-
if (normalizedGroup === "github")
|
|
293
|
-
return routeGithub(argv, normalizedCommand, rest);
|
|
294
|
-
if (normalizedGroup === "init") {
|
|
295
|
-
if (!normalizedCommand)
|
|
296
|
-
return intent("init", argv, "refresh", undefined, "Open init");
|
|
297
|
-
if (rest.includes("--yes") || normalizedCommand === "--yes")
|
|
298
|
-
return intent("init", argv, "init-start", undefined, "Run init");
|
|
299
|
-
return intent("init", argv, "refresh", undefined, "Open init");
|
|
300
|
-
}
|
|
301
|
-
if (normalizedGroup === "doctor")
|
|
302
|
-
return routeDoctor(argv, normalizedCommand);
|
|
303
|
-
if (normalizedGroup === "inbox")
|
|
304
|
-
return routeInbox(argv, normalizedCommand, rest);
|
|
305
|
-
if (normalizedGroup === "attach") {
|
|
306
|
-
const attachArgs = [command, ...rest].filter((part) => part.trim());
|
|
307
|
-
const runId = firstNonOption(attachArgs);
|
|
308
|
-
return runId && attachArgs.length === 1 ? intent("handoff", argv, "run-attach", { runId }, `Attaching ${runId}`) : commandRunIntent(argv);
|
|
309
|
-
}
|
|
310
|
-
if (HOLLOW_FAMILIES.has(normalizedGroup)) {
|
|
311
|
-
if (!normalizedCommand || isHelpCommand(normalizedCommand))
|
|
312
|
-
return familyIntent(normalizedGroup, argv);
|
|
313
|
-
return commandRunIntent(argv);
|
|
314
|
-
}
|
|
315
|
-
const nav = NAV_SCENES.get(normalizedGroup);
|
|
316
|
-
if (nav && argv.length === 1)
|
|
317
|
-
return intent(nav, argv, "navigate", undefined, `Opening ${nav}`);
|
|
318
|
-
return commandRunIntent(argv);
|
|
319
|
-
}
|
|
320
|
-
function intentFromTypeBar(value) {
|
|
321
|
-
const raw = value.trim();
|
|
322
|
-
if (!raw)
|
|
323
|
-
return intent("main", [], "none", undefined, "Project menu", value);
|
|
324
|
-
const parts = raw.split(/\s+/).filter(Boolean);
|
|
325
|
-
const [first = "", second = "", ...rest] = parts;
|
|
326
|
-
const firstScene = NAV_SCENES.get(first.toLowerCase());
|
|
327
|
-
if (first === "tasks" || first === "task") {
|
|
328
|
-
if (second === "run" || first === "task" && (second === "ready" || second === "status"))
|
|
329
|
-
return commandRunIntent(parts, value);
|
|
330
|
-
if (!isTaskViewCommand(second.toLowerCase()))
|
|
331
|
-
return commandRunIntent(parts, value);
|
|
332
|
-
return taskViewIntent(parts, second, rest, value);
|
|
333
|
-
}
|
|
334
|
-
if (first === "run") {
|
|
335
|
-
if (second === "attach") {
|
|
336
|
-
const runId = firstNonOption(rest);
|
|
337
|
-
return runId && rest.filter((part) => part.trim()).length === 1 ? intent("handoff", parts, "run-attach", { runId }, "Attaching run", value) : commandRunIntent(parts, value);
|
|
338
|
-
}
|
|
339
|
-
if (second === "stop") {
|
|
340
|
-
const runId = firstNonOption(rest);
|
|
341
|
-
return runId && rest.filter((part) => part.trim()).length === 1 ? intent("fleet", parts, "run-stop", { runId }, "Stopping run", value) : commandRunIntent(parts, value);
|
|
342
|
-
}
|
|
343
|
-
if (second === "next" || second === "--next" || second === "--task") {
|
|
344
|
-
const parsed = parseSimpleTaskRunArgs([second, ...rest]);
|
|
345
|
-
if (!parsed.ok)
|
|
346
|
-
return commandRunIntent(parts, value, parsed.reason);
|
|
347
|
-
const payload = parsed.detach ? { detach: true } : undefined;
|
|
348
|
-
if (parsed.next)
|
|
349
|
-
return intent("tasks", parts, "task-run-next", payload, parsed.detach ? "Dispatching next task detached" : "Dispatching next task", value);
|
|
350
|
-
if (parsed.task)
|
|
351
|
-
return intent("tasks", parts, "task-run-id", { ...payload ?? {}, task: parsed.task }, parsed.detach ? `Dispatching ${parsed.task} detached` : `Dispatching ${parsed.task}`, value);
|
|
352
|
-
return intent("tasks", parts, "refresh", undefined, "Select a task, then press Enter; or use run next", value);
|
|
353
|
-
}
|
|
354
|
-
if (second && !second.startsWith("-")) {
|
|
355
|
-
if (rest.length > 0)
|
|
356
|
-
return commandRunIntent(parts, value);
|
|
357
|
-
return intent("tasks", parts, "task-run-id", { task: second }, `Dispatching ${second}`, value);
|
|
358
|
-
}
|
|
359
|
-
if (second?.startsWith("-"))
|
|
360
|
-
return commandRunIntent(parts, value);
|
|
361
|
-
return intent("tasks", parts, "refresh", undefined, "Select a task, then press Enter; or use run next", value);
|
|
362
|
-
}
|
|
363
|
-
if (first === "init") {
|
|
364
|
-
if (!second)
|
|
365
|
-
return intent("init", parts, "refresh", undefined, "Open init", value);
|
|
366
|
-
if (parts.includes("--yes"))
|
|
367
|
-
return intent("init", parts, "init-start", undefined, "Run init", value);
|
|
368
|
-
return intent("init", parts, "refresh", undefined, "Open init", value);
|
|
369
|
-
}
|
|
370
|
-
if (first === "doctor")
|
|
371
|
-
return routeDoctor(parts, second, value);
|
|
372
|
-
if (first === "server")
|
|
373
|
-
return routeServer(parts, second, rest, value, "Loading server");
|
|
374
|
-
if (first === "github")
|
|
375
|
-
return routeGithub(parts, second, rest, value);
|
|
376
|
-
if (first === "inbox")
|
|
377
|
-
return routeInbox(parts, second, rest, value);
|
|
378
|
-
if (firstScene && parts.length === 1) {
|
|
379
|
-
return intent(firstScene, parts, "navigate", undefined, `Opening ${firstScene}`, value);
|
|
380
|
-
}
|
|
381
|
-
if (first === "stop")
|
|
382
|
-
return intent("fleet", parts, "run-stop", { runId: second || undefined }, second ? `Stopping ${second}` : "Stopping run", value);
|
|
383
|
-
if (first === "attach") {
|
|
384
|
-
if (!second)
|
|
385
|
-
return intent("handoff", parts, "navigate", undefined, "Select a run to attach", value);
|
|
386
|
-
return parts.length === 2 ? intent("handoff", parts, "run-attach", { runId: second }, `Attaching ${second}`, value) : commandRunIntent(parts, value);
|
|
387
|
-
}
|
|
388
|
-
return intentFromArgv(parts);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// packages/cli/src/app-opentui/state.ts
|
|
392
|
-
function mergePatch(state, patch) {
|
|
393
|
-
return {
|
|
394
|
-
...state,
|
|
395
|
-
...patch,
|
|
396
|
-
typeBar: patch.typeBar ? { ...state.typeBar, ...patch.typeBar } : state.typeBar,
|
|
397
|
-
footer: patch.footer ? { ...state.footer, ...patch.footer } : state.footer,
|
|
398
|
-
selection: patch.selection ? { ...state.selection, ...patch.selection } : state.selection,
|
|
399
|
-
data: patch.data ? { ...state.data, ...patch.data } : state.data
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
function createInitialAppState(options) {
|
|
403
|
-
const initialIntent = intentFromArgv(options.argv);
|
|
404
|
-
const projectName = options.projectRoot.split(/[\\/]/).filter(Boolean).at(-1) ?? options.projectRoot;
|
|
405
|
-
return {
|
|
406
|
-
projectRoot: options.projectRoot,
|
|
407
|
-
scene: initialIntent.scene,
|
|
408
|
-
status: initialIntent.action.kind === "none" || initialIntent.action.kind === "navigate" ? "idle" : "loading",
|
|
409
|
-
tick: 0,
|
|
410
|
-
argv: options.argv,
|
|
411
|
-
intent: initialIntent,
|
|
412
|
-
typeBar: {
|
|
413
|
-
value: "",
|
|
414
|
-
placeholder: "/ search \xB7 : command \xB7 tab next \xB7 ? help",
|
|
415
|
-
mode: "nav"
|
|
416
|
-
},
|
|
417
|
-
history: [],
|
|
418
|
-
footer: {
|
|
419
|
-
project: projectName,
|
|
420
|
-
...options.initialFooter ?? {}
|
|
421
|
-
},
|
|
422
|
-
selection: { index: 0, count: 0 },
|
|
423
|
-
data: {
|
|
424
|
-
projectConfigured: options.projectConfigured ?? false
|
|
425
|
-
},
|
|
426
|
-
runtimeReady: false,
|
|
427
|
-
actionLabel: initialIntent.action.label
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
function createAppStore(initialState) {
|
|
431
|
-
let state = initialState;
|
|
432
|
-
const listeners = new Set;
|
|
433
|
-
const notify = () => {
|
|
434
|
-
for (const listener of [...listeners])
|
|
435
|
-
listener(state);
|
|
436
|
-
};
|
|
437
|
-
return {
|
|
438
|
-
getState() {
|
|
439
|
-
return state;
|
|
440
|
-
},
|
|
441
|
-
setState(next) {
|
|
442
|
-
state = next;
|
|
443
|
-
notify();
|
|
444
|
-
},
|
|
445
|
-
patch(patch) {
|
|
446
|
-
state = mergePatch(state, patch);
|
|
447
|
-
notify();
|
|
448
|
-
},
|
|
449
|
-
subscribe(listener) {
|
|
450
|
-
listeners.add(listener);
|
|
451
|
-
return () => listeners.delete(listener);
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
function reduceAppEvent(state, event) {
|
|
456
|
-
switch (event.type) {
|
|
457
|
-
case "scene.change":
|
|
458
|
-
return {
|
|
459
|
-
scene: event.scene,
|
|
460
|
-
...event.intent ? { intent: event.intent, actionLabel: event.intent.action.label } : {},
|
|
461
|
-
status: "idle",
|
|
462
|
-
error: undefined,
|
|
463
|
-
confirm: undefined,
|
|
464
|
-
typeBar: { message: undefined, value: "", mode: "nav", prompt: undefined }
|
|
465
|
-
};
|
|
466
|
-
case "action.started":
|
|
467
|
-
return {
|
|
468
|
-
status: "action",
|
|
469
|
-
actionLabel: event.label,
|
|
470
|
-
error: undefined,
|
|
471
|
-
confirm: undefined,
|
|
472
|
-
data: event.optimistic ?? {},
|
|
473
|
-
typeBar: { message: event.label }
|
|
474
|
-
};
|
|
475
|
-
case "action.progress":
|
|
476
|
-
return {
|
|
477
|
-
status: "action",
|
|
478
|
-
actionLabel: event.label,
|
|
479
|
-
data: event.data ?? {},
|
|
480
|
-
typeBar: { message: event.detail ?? event.label }
|
|
481
|
-
};
|
|
482
|
-
case "action.completed":
|
|
483
|
-
return {
|
|
484
|
-
status: "success",
|
|
485
|
-
actionLabel: event.label,
|
|
486
|
-
data: event.data ?? {},
|
|
487
|
-
dataUpdatedAtMs: Date.now(),
|
|
488
|
-
typeBar: { message: `${event.label} complete` }
|
|
489
|
-
};
|
|
490
|
-
case "action.failed":
|
|
491
|
-
return {
|
|
492
|
-
status: "error",
|
|
493
|
-
actionLabel: event.label,
|
|
494
|
-
error: { message: event.message, hint: event.hint, cause: event.cause },
|
|
495
|
-
typeBar: { message: event.message, placeholder: event.hint ?? "runs \xB7 tasks \xB7 help" },
|
|
496
|
-
data: {
|
|
497
|
-
optimisticRun: undefined,
|
|
498
|
-
pendingRunMessage: undefined,
|
|
499
|
-
inboxOptimistic: undefined,
|
|
500
|
-
dispatchingRun: undefined,
|
|
501
|
-
lastRefreshError: event.message
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
case "data.patch":
|
|
505
|
-
return { data: event.data, dataUpdatedAtMs: Date.now() };
|
|
506
|
-
case "confirm.request":
|
|
507
|
-
return {
|
|
508
|
-
confirm: event.confirm,
|
|
509
|
-
typeBar: { message: `${event.confirm.label} \u2014 enter to confirm \xB7 esc to cancel` }
|
|
510
|
-
};
|
|
511
|
-
case "confirm.cancel":
|
|
512
|
-
return { confirm: undefined, typeBar: { message: "cancelled" } };
|
|
513
|
-
case "footer.patch":
|
|
514
|
-
return { footer: event.footer };
|
|
515
|
-
case "typebar.patch":
|
|
516
|
-
return { typeBar: event.typeBar };
|
|
517
|
-
case "runtime.ready":
|
|
518
|
-
return { runtimeReady: true, footer: { message: "runtime ready" } };
|
|
519
|
-
case "runtime.failed":
|
|
520
|
-
return {
|
|
521
|
-
scene: "error",
|
|
522
|
-
status: "error",
|
|
523
|
-
runtimeReady: false,
|
|
524
|
-
error: { message: event.message, hint: event.hint, cause: event.cause }
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// packages/cli/src/app-opentui/runtime.ts
|
|
530
|
-
import { BoxRenderable as BoxRenderable3, RGBA as RGBA7, StyledText as StyledText3, TextRenderable as TextRenderable4, createCliRenderer } from "@opentui/core";
|
|
531
|
-
|
|
532
|
-
// packages/cli/src/app-opentui/command-pty-host.ts
|
|
533
|
-
import { basename } from "path";
|
|
534
|
-
import { fileURLToPath } from "url";
|
|
535
|
-
import { Terminal as XtermTerminal } from "@xterm/headless";
|
|
536
|
-
var MIN_COLS = 40;
|
|
537
|
-
var MIN_ROWS = 10;
|
|
538
|
-
var MAX_ROWS = 300;
|
|
539
|
-
var MAX_SNAPSHOT_LINES = 420;
|
|
540
|
-
var SNAPSHOT_DELAY_MS = 80;
|
|
541
|
-
var fallbackCliScriptPath = fileURLToPath(new URL("../../bin/rig.ts", import.meta.url));
|
|
542
|
-
var activeHost = null;
|
|
543
|
-
function clampCols(cols) {
|
|
544
|
-
return Math.max(MIN_COLS, Math.trunc(cols || 120));
|
|
545
|
-
}
|
|
546
|
-
function clampRows(rows) {
|
|
547
|
-
return Math.max(MIN_ROWS, Math.min(MAX_ROWS, Math.trunc(rows || 36)));
|
|
548
|
-
}
|
|
549
|
-
function childCommandPrefix() {
|
|
550
|
-
const execName = basename(process.execPath).toLowerCase();
|
|
551
|
-
const currentEntry = process.argv[1];
|
|
552
|
-
if (execName === "bun" || execName === "bun.exe") {
|
|
553
|
-
return currentEntry ? [process.execPath, currentEntry] : [process.execPath, fallbackCliScriptPath];
|
|
554
|
-
}
|
|
555
|
-
return [process.execPath];
|
|
556
|
-
}
|
|
557
|
-
function withEnv(base) {
|
|
558
|
-
const env = {};
|
|
559
|
-
for (const [key, value] of Object.entries(base ?? process.env)) {
|
|
560
|
-
if (key === "NO_COLOR")
|
|
561
|
-
continue;
|
|
562
|
-
if (typeof value === "string")
|
|
563
|
-
env[key] = value;
|
|
564
|
-
}
|
|
565
|
-
return {
|
|
566
|
-
...env,
|
|
567
|
-
TERM: "xterm-256color",
|
|
568
|
-
COLORTERM: "truecolor",
|
|
569
|
-
FORCE_COLOR: env.FORCE_COLOR ?? "1",
|
|
570
|
-
RIG_PLAIN: "1",
|
|
571
|
-
RIG_OPENTUI_COMMAND_HOST: "1",
|
|
572
|
-
RIG_CLI_PLAIN_HELP: env.RIG_CLI_PLAIN_HELP ?? "1"
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
function commandLabel(argv, fallback) {
|
|
576
|
-
const command = argv.join(" ").trim();
|
|
577
|
-
return fallback?.trim() || (command ? `rig ${command}` : "rig");
|
|
578
|
-
}
|
|
579
|
-
function getActiveCommandHost() {
|
|
580
|
-
return activeHost && !activeHost.disposed ? activeHost : null;
|
|
581
|
-
}
|
|
582
|
-
function stopActiveCommandHost(reason = "detach") {
|
|
583
|
-
activeHost?.dispose(reason);
|
|
584
|
-
activeHost = null;
|
|
585
|
-
}
|
|
586
|
-
class CommandPtyHost {
|
|
587
|
-
argv;
|
|
588
|
-
projectRoot;
|
|
589
|
-
label;
|
|
590
|
-
env;
|
|
591
|
-
onSnapshot;
|
|
592
|
-
onExit;
|
|
593
|
-
onError;
|
|
594
|
-
terminal;
|
|
595
|
-
decoder = new TextDecoder("utf-8");
|
|
596
|
-
proc = null;
|
|
597
|
-
pty = null;
|
|
598
|
-
status = "starting";
|
|
599
|
-
cols;
|
|
600
|
-
rows;
|
|
601
|
-
message = "starting command";
|
|
602
|
-
exitCode;
|
|
603
|
-
signal;
|
|
604
|
-
notifyTimer = null;
|
|
605
|
-
_disposed = false;
|
|
606
|
-
constructor(options) {
|
|
607
|
-
this.argv = [...options.argv];
|
|
608
|
-
this.projectRoot = options.projectRoot;
|
|
609
|
-
this.label = commandLabel(options.argv, options.label);
|
|
610
|
-
this.env = options.env;
|
|
611
|
-
this.cols = clampCols(options.cols);
|
|
612
|
-
this.rows = clampRows(options.rows);
|
|
613
|
-
this.onSnapshot = options.onSnapshot;
|
|
614
|
-
this.onExit = options.onExit;
|
|
615
|
-
this.onError = options.onError;
|
|
616
|
-
this.terminal = new XtermTerminal({ allowProposedApi: true, cols: this.cols, rows: this.rows, scrollback: 2000 });
|
|
617
|
-
}
|
|
618
|
-
get disposed() {
|
|
619
|
-
return this._disposed;
|
|
620
|
-
}
|
|
621
|
-
get snapshot() {
|
|
622
|
-
return this.createSnapshot();
|
|
623
|
-
}
|
|
624
|
-
async start() {
|
|
625
|
-
if (this._disposed)
|
|
626
|
-
throw new Error("Command PTY host is disposed.");
|
|
627
|
-
if (typeof Bun.Terminal !== "function") {
|
|
628
|
-
throw new Error("Bun native PTY is unavailable in this runtime. Command host requires Bun.Terminal.");
|
|
629
|
-
}
|
|
630
|
-
const spawnOptions = {
|
|
631
|
-
cwd: this.projectRoot,
|
|
632
|
-
env: withEnv(this.env),
|
|
633
|
-
terminal: {
|
|
634
|
-
cols: this.cols,
|
|
635
|
-
rows: this.rows,
|
|
636
|
-
name: "xterm-256color",
|
|
637
|
-
data: (_terminal, data) => this.handlePtyData(data)
|
|
638
|
-
}
|
|
639
|
-
};
|
|
640
|
-
const proc = Bun.spawn([...childCommandPrefix(), ...this.argv], spawnOptions);
|
|
641
|
-
if (!proc.terminal)
|
|
642
|
-
throw new Error("Bun did not attach a terminal to the command child process.");
|
|
643
|
-
this.proc = proc;
|
|
644
|
-
this.pty = proc.terminal;
|
|
645
|
-
this.status = "running";
|
|
646
|
-
this.message = "running";
|
|
647
|
-
this.emitSnapshotSoon(0);
|
|
648
|
-
proc.exited.then((exitCode) => {
|
|
649
|
-
if (this._disposed)
|
|
650
|
-
return;
|
|
651
|
-
this.status = exitCode === 0 ? "exited" : "failed";
|
|
652
|
-
this.exitCode = exitCode;
|
|
653
|
-
this.signal = null;
|
|
654
|
-
this.message = exitCode === 0 ? "completed" : `exited with code ${exitCode}`;
|
|
655
|
-
const snapshot = this.createSnapshot();
|
|
656
|
-
this.onSnapshot?.(snapshot);
|
|
657
|
-
this.onExit?.(snapshot);
|
|
658
|
-
if (activeHost === this)
|
|
659
|
-
activeHost = null;
|
|
660
|
-
this.dispose("exit", { kill: false, notify: false });
|
|
661
|
-
}).catch((error) => {
|
|
662
|
-
if (this._disposed)
|
|
663
|
-
return;
|
|
664
|
-
this.status = "failed";
|
|
665
|
-
this.message = error instanceof Error ? error.message : String(error);
|
|
666
|
-
const snapshot = this.createSnapshot();
|
|
667
|
-
this.onSnapshot?.(snapshot);
|
|
668
|
-
this.onError?.(error, snapshot);
|
|
669
|
-
if (activeHost === this)
|
|
670
|
-
activeHost = null;
|
|
671
|
-
this.dispose("error", { kill: false, notify: false });
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
write(data) {
|
|
675
|
-
if (this._disposed || !this.pty)
|
|
676
|
-
return;
|
|
677
|
-
try {
|
|
678
|
-
this.pty.write(data);
|
|
679
|
-
} catch (error) {
|
|
680
|
-
this.status = "failed";
|
|
681
|
-
this.message = error instanceof Error ? error.message : String(error);
|
|
682
|
-
const snapshot = this.createSnapshot();
|
|
683
|
-
this.onSnapshot?.(snapshot);
|
|
684
|
-
this.onError?.(error, snapshot);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
resize(cols, rows) {
|
|
688
|
-
const nextCols = clampCols(cols);
|
|
689
|
-
const nextRows = clampRows(rows);
|
|
690
|
-
if (nextCols === this.cols && nextRows === this.rows)
|
|
691
|
-
return;
|
|
692
|
-
this.cols = nextCols;
|
|
693
|
-
this.rows = nextRows;
|
|
694
|
-
this.terminal.resize(nextCols, nextRows);
|
|
695
|
-
try {
|
|
696
|
-
this.pty?.resize(nextCols, nextRows);
|
|
697
|
-
} catch {}
|
|
698
|
-
this.emitSnapshotSoon(0);
|
|
699
|
-
}
|
|
700
|
-
dispose(reason = "dispose", options = {}) {
|
|
701
|
-
if (this._disposed)
|
|
702
|
-
return;
|
|
703
|
-
this._disposed = true;
|
|
704
|
-
if (this.notifyTimer)
|
|
705
|
-
clearTimeout(this.notifyTimer);
|
|
706
|
-
this.notifyTimer = null;
|
|
707
|
-
if (options.kill !== false) {
|
|
708
|
-
try {
|
|
709
|
-
this.proc?.kill("SIGTERM");
|
|
710
|
-
} catch {}
|
|
711
|
-
}
|
|
712
|
-
try {
|
|
713
|
-
this.pty?.close();
|
|
714
|
-
} catch {}
|
|
715
|
-
try {
|
|
716
|
-
this.terminal.dispose();
|
|
717
|
-
} catch {}
|
|
718
|
-
if (activeHost === this)
|
|
719
|
-
activeHost = null;
|
|
720
|
-
if (options.notify) {
|
|
721
|
-
this.message = reason;
|
|
722
|
-
this.onSnapshot?.(this.createSnapshot(reason));
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
handlePtyData(data) {
|
|
726
|
-
if (this._disposed)
|
|
727
|
-
return;
|
|
728
|
-
this.decoder.decode(data, { stream: true });
|
|
729
|
-
this.terminal.write(data, () => this.emitSnapshotSoon());
|
|
730
|
-
}
|
|
731
|
-
emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS) {
|
|
732
|
-
if (this._disposed || this.notifyTimer)
|
|
733
|
-
return;
|
|
734
|
-
this.notifyTimer = setTimeout(() => {
|
|
735
|
-
this.notifyTimer = null;
|
|
736
|
-
if (this._disposed)
|
|
737
|
-
return;
|
|
738
|
-
this.onSnapshot?.(this.createSnapshot());
|
|
739
|
-
}, delayMs);
|
|
740
|
-
}
|
|
741
|
-
createSnapshot(message = this.message) {
|
|
742
|
-
const buffer = this.terminal.buffer.active;
|
|
743
|
-
const end = buffer.length;
|
|
744
|
-
const start = Math.max(0, end - MAX_SNAPSHOT_LINES);
|
|
745
|
-
const lines = [];
|
|
746
|
-
for (let row = start;row < end; row += 1) {
|
|
747
|
-
const line = buffer.getLine(row);
|
|
748
|
-
lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
|
|
749
|
-
}
|
|
750
|
-
while (lines.length < this.rows)
|
|
751
|
-
lines.push("");
|
|
752
|
-
return {
|
|
753
|
-
command: this.argv,
|
|
754
|
-
label: this.label,
|
|
755
|
-
status: this.status,
|
|
756
|
-
cols: this.cols,
|
|
757
|
-
rows: this.rows,
|
|
758
|
-
lines,
|
|
759
|
-
message,
|
|
760
|
-
...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
|
|
761
|
-
...this.signal !== undefined ? { signal: this.signal } : {}
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// packages/cli/src/commands/_help-catalog.ts
|
|
767
|
-
import pc from "picocolors";
|
|
768
|
-
function helpCatalog() {
|
|
769
|
-
return { sections: TOP_LEVEL_SECTIONS, groups: ALL_GROUPS };
|
|
770
|
-
}
|
|
771
|
-
var TOP_LEVEL_SECTIONS = [
|
|
772
|
-
{
|
|
773
|
-
title: "Pi console",
|
|
774
|
-
subtitle: "the operator surface \u2014 every attach opens the run's FULL Pi session",
|
|
775
|
-
commands: [
|
|
776
|
-
{ command: "rig run attach <run-id>", description: "Open the run's Pi console: complete transcript (live AND finished runs), worker turns streaming in live." },
|
|
777
|
-
{ command: "type a message", description: "Inside the console, plain text steers the worker mid-turn \u2014 it lands in the agent's context, not a local model." },
|
|
778
|
-
{ command: "!<command>", description: "Inside the console, runs shell in the WORKER's workspace; output streams back into the transcript." },
|
|
779
|
-
{ command: "/<command>", description: "The palette includes the WORKER session's slash commands ([worker]-tagged) next to /rig." },
|
|
780
|
-
{ command: "/rig abort | /rig stop", description: "Abort the worker's current turn, or stop the whole run, from inside the console." },
|
|
781
|
-
{ command: "rig run steer <id> --message <text>", description: "Steer without attaching \u2014 queue a message; the worker picks it up within seconds." }
|
|
782
|
-
]
|
|
783
|
-
},
|
|
784
|
-
{
|
|
785
|
-
title: "Start here",
|
|
786
|
-
subtitle: "one-time setup, pick a server",
|
|
787
|
-
commands: [
|
|
788
|
-
{ command: "rig init", description: "Wizard: config, GitHub auth, task source, server, Pi wiring." },
|
|
789
|
-
{ command: "rig server use <alias|local>", description: "Pick which Rig server owns this repo (`rig server list` to see them)." },
|
|
790
|
-
{ command: "rig server status", description: "Show the selected server for this repo." }
|
|
791
|
-
]
|
|
792
|
-
},
|
|
793
|
-
{
|
|
794
|
-
title: "Work",
|
|
795
|
-
subtitle: "find a task, put an agent on it, answer what it asks",
|
|
796
|
-
commands: [
|
|
797
|
-
{ command: "rig task list", description: "What's on the board (from the selected source/server)." },
|
|
798
|
-
{ command: "rig task run --next", description: "Dispatch an agent on the next ready task; interactive mode drops you into its Pi console." },
|
|
799
|
-
{ command: "rig run status", description: "Active and recent runs at a glance." },
|
|
800
|
-
{ command: "rig run attach <id> --follow", description: "Same as plain attach \u2014 the Pi console is always the interactive surface." },
|
|
801
|
-
{ command: "rig inbox approvals", description: "Approvals workers are waiting on (then `rig inbox approve \u2026`)." }
|
|
802
|
-
]
|
|
803
|
-
},
|
|
804
|
-
{
|
|
805
|
-
title: "Watch",
|
|
806
|
-
subtitle: "fleet metrics and per-task forensics",
|
|
807
|
-
commands: [
|
|
808
|
-
{ command: "rig stats [--since 7d]", description: "Fleet metrics: completion/failure rates, median run time, steering, stalls." },
|
|
809
|
-
{ command: "rig inspect logs --task <id>", description: "Latest run log for a task." },
|
|
810
|
-
{ command: "rig inspect diff --task <id>", description: "Changed files for a task." }
|
|
811
|
-
]
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
title: "Unblock",
|
|
815
|
-
subtitle: "diagnose wiring, fix auth",
|
|
816
|
-
commands: [
|
|
817
|
-
{ command: "rig doctor", description: "Check every part of the wiring \u2014 run this when anything feels off." },
|
|
818
|
-
{ command: "rig github auth status", description: "GitHub auth state on the selected server." }
|
|
819
|
-
]
|
|
820
|
-
},
|
|
821
|
-
{
|
|
822
|
-
title: "Extend",
|
|
823
|
-
subtitle: "plugins contribute validators, hooks, task sources, commands",
|
|
824
|
-
commands: [
|
|
825
|
-
{ command: "rig plugin list", description: "What the rig.config.ts plugins contribute." },
|
|
826
|
-
{ command: "rig plugin run <command-id>", description: "Execute a plugin-contributed CLI command." }
|
|
827
|
-
]
|
|
828
|
-
}
|
|
829
|
-
];
|
|
830
|
-
var PRIMARY_GROUPS = [
|
|
831
|
-
{
|
|
832
|
-
name: "server",
|
|
833
|
-
summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
|
|
834
|
-
usage: ["rig server <status|list|add|use|repair-link|start> [options]"],
|
|
835
|
-
commands: [
|
|
836
|
-
{ command: "status", description: "Show the selected server and remote project-root link for this repo.", primary: true },
|
|
837
|
-
{ command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
|
|
838
|
-
{ command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
|
|
839
|
-
{ command: "use <alias>", description: "Select a saved remote server alias.", primary: true },
|
|
840
|
-
{ command: "repair-link [--prepare|--backfill-only] [--repo owner/repo]", description: "Backfill or prepare the selected remote's server-host checkout link.", primary: true },
|
|
841
|
-
{ command: "list", description: "List saved local/remote server aliases.", primary: true },
|
|
842
|
-
{ command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
|
|
843
|
-
],
|
|
844
|
-
examples: [
|
|
845
|
-
"rig server status",
|
|
846
|
-
"rig server add prod https://where.rig-does.work",
|
|
847
|
-
"rig server use prod",
|
|
848
|
-
"rig server repair-link --repo owner/repo",
|
|
849
|
-
"rig server use local",
|
|
850
|
-
"rig server start --port 3773"
|
|
851
|
-
],
|
|
852
|
-
next: ["Use `rig task list` to see server-owned work.", "Use `rig run list` or `rig run attach <id> --follow` to monitor runs."]
|
|
853
|
-
},
|
|
854
|
-
{
|
|
855
|
-
name: "task",
|
|
856
|
-
summary: "Find work, start Pi-backed runs, and validate task results.",
|
|
857
|
-
usage: ["rig task <list|next|show|run> [options]"],
|
|
858
|
-
commands: [
|
|
859
|
-
{ command: "list [--assignee <login|me|@me>] [--state open|closed]", description: "List tasks from the selected server/source.", primary: true },
|
|
860
|
-
{ command: "next [filters]", description: "Render the next matching task as a selected-task card.", primary: true },
|
|
861
|
-
{ command: "show <id>|--task <id> [--raw]", description: "Show a human task summary; --raw prints the full payload.", primary: true },
|
|
862
|
-
{ command: "run [#<issue>|<task-id>|--next|--task <id>]", description: "Submit a task run; interactive follows with bundled Pi.", primary: true },
|
|
863
|
-
{ command: "ready", description: "List task IDs that are runnable now." },
|
|
864
|
-
{ command: "validate|verify [--task <id>]", description: "Run configured task checks/review gates." },
|
|
865
|
-
{ command: "details --task <id>", description: "Show full task info from the configured source." },
|
|
866
|
-
{ command: "reopen [--task <id> | --all] [--reason <text>]", description: "Reopen closed task(s) in the configured source." },
|
|
867
|
-
{ command: "artifacts|artifact-dir|artifact-write", description: "Inspect or write task artifacts." },
|
|
868
|
-
{ command: "report-bug", description: "Create a structured bug report/task." }
|
|
869
|
-
],
|
|
870
|
-
examples: [
|
|
871
|
-
"rig task list --assignee @me --limit 20",
|
|
872
|
-
"rig task next",
|
|
873
|
-
"rig task show 123 --raw",
|
|
874
|
-
"rig task run --next",
|
|
875
|
-
"rig task run #123 --runtime-adapter pi",
|
|
876
|
-
"rig task run --title 'Investigate deploy drift' --initial-prompt 'Check server health'"
|
|
877
|
-
],
|
|
878
|
-
next: ["Use `--detach` to submit without attaching.", "Use `rig run attach <run-id> --follow` to rejoin a live run."]
|
|
879
|
-
},
|
|
880
|
-
{
|
|
881
|
-
name: "run",
|
|
882
|
-
summary: "Observe, attach to, and control Rig runs. `attach` opens the Pi console \u2014 the full worker session, live.",
|
|
883
|
-
usage: ["rig run <list|status|show|attach|steer|stop|resume|restart> [options]"],
|
|
884
|
-
commands: [
|
|
885
|
-
{ command: "list", description: "List recent runs from the selected server or local state.", primary: true },
|
|
886
|
-
{ command: "status", description: "Render active and recent run groups.", primary: true },
|
|
887
|
-
{ command: "show <id>|--run <id> [--raw]", description: "Show a human run summary; --raw prints the full payload.", primary: true },
|
|
888
|
-
{ command: "attach <run-id>|--run <id>", description: "Open the run's Pi console \u2014 see 'Inside the console' below. Works on live AND finished runs.", primary: true },
|
|
889
|
-
{ command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching \u2014 delivered into the agent's context within seconds.", primary: true },
|
|
890
|
-
{ command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
|
|
891
|
-
{ command: "resume [<run-id>]", description: "Resume an interrupted run on the selected server (defaults to the most recent resumable)." },
|
|
892
|
-
{ command: "restart [<run-id>]", description: "Re-dispatch a run from a clean runtime, reopening its Pi session where possible." },
|
|
893
|
-
{ command: "timeline --run <id> [--follow]", description: "Stream raw run timeline events (scripts; humans should attach)." },
|
|
894
|
-
{ command: "replay <run-id>|--run <id> [--with-session]", description: "Print the run's consolidated run.jsonl as a merged timeline; --with-session interleaves the Pi session log." },
|
|
895
|
-
{ command: "delete|cleanup", description: "Remove completed run records/artifacts." }
|
|
896
|
-
],
|
|
897
|
-
examples: [
|
|
898
|
-
"rig run attach <run-id> # full session console \u2014 live mirror, steering, worker shell",
|
|
899
|
-
"rig run steer <run-id> --message 'focus on the failing test first'",
|
|
900
|
-
"rig run list",
|
|
901
|
-
"rig run show <run-id>",
|
|
902
|
-
"rig run stop <run-id>"
|
|
903
|
-
],
|
|
904
|
-
next: [
|
|
905
|
-
"Inside the console: the run's COMPLETE transcript loads on open (finished runs too), and new worker turns stream in live.",
|
|
906
|
-
"Inside the console: plain text = steering into the worker's context \xB7 !<cmd> = shell in the WORKER workspace \xB7 /<cmd> includes the worker session's commands \xB7 /rig abort stops the current turn.",
|
|
907
|
-
"The console is read-write but remote-only: nothing runs on your machine; the worker keeps running when you exit.",
|
|
908
|
-
"Use `rig task run --next` to create a new run; interactive mode drops you straight into its console.",
|
|
909
|
-
"Use `--json` when scripts need the full structured record."
|
|
910
|
-
]
|
|
911
|
-
},
|
|
912
|
-
{
|
|
913
|
-
name: "inbox",
|
|
914
|
-
summary: "Review approval and user-input requests that block worker runs.",
|
|
915
|
-
usage: ["rig inbox <approvals|approve|inputs|respond> [options]"],
|
|
916
|
-
commands: [
|
|
917
|
-
{ command: "approvals [--run <id>] [--task <id>]", description: "List pending approvals.", primary: true },
|
|
918
|
-
{ command: "inputs [--run <id>] [--task <id>]", description: "List pending user-input requests.", primary: true },
|
|
919
|
-
{ command: "approve --run <id> --request <id> --decision approve|reject", description: "Resolve an approval request." },
|
|
920
|
-
{ command: "respond --run <id> --request <id> --answer key=value", description: "Answer a user-input request." }
|
|
921
|
-
],
|
|
922
|
-
examples: [
|
|
923
|
-
"rig inbox approvals",
|
|
924
|
-
"rig inbox inputs --run <run-id>",
|
|
925
|
-
"rig inbox approve --run <run-id> --request <request-id> --decision approve"
|
|
926
|
-
],
|
|
927
|
-
next: ["Rejoin the run after resolving a block: `rig run attach <run-id> --follow`."]
|
|
928
|
-
},
|
|
929
|
-
{
|
|
930
|
-
name: "stats",
|
|
931
|
-
summary: "Fleet metrics computed from on-disk run journals (no server required).",
|
|
932
|
-
usage: ["rig stats [show] [--since <7d|30d|ISO date>]"],
|
|
933
|
-
commands: [
|
|
934
|
-
{ command: "show [--since <window>]", description: "Total runs, completion/failure/needs-attention rates, median run time, steering, stalls, approvals.", primary: true }
|
|
935
|
-
],
|
|
936
|
-
examples: [
|
|
937
|
-
"rig stats",
|
|
938
|
-
"rig stats --since 7d",
|
|
939
|
-
"rig stats --since 2026-06-01 --json"
|
|
940
|
-
],
|
|
941
|
-
next: ["Inspect outliers with `rig run list` and `rig run show <run-id>`.", "Use `--json` for the schema'd envelope (see docs/cli-json.md)."]
|
|
942
|
-
},
|
|
943
|
-
{
|
|
944
|
-
name: "inspect",
|
|
945
|
-
summary: "Inspect logs, artifacts, graphs, failures for a task.",
|
|
946
|
-
usage: ["rig inspect <logs|artifacts|failures|graph|audit|diff> --task <id>"],
|
|
947
|
-
commands: [
|
|
948
|
-
{ command: "logs --task <id>", description: "Latest run log for a task (local or selected server).", primary: true },
|
|
949
|
-
{ command: "artifacts --task <id>", description: "List the task's completion artifacts.", primary: true },
|
|
950
|
-
{ command: "failures --task <id>", description: "Recorded failures for a task.", primary: true },
|
|
951
|
-
{ command: "diff --task <id>", description: "Changed files for a task.", primary: true },
|
|
952
|
-
{ command: "graph", description: "Task dependency graph." },
|
|
953
|
-
{ command: "audit", description: "Controlled-command audit trail." }
|
|
954
|
-
],
|
|
955
|
-
examples: ["rig inspect logs --task <id>", "rig inspect diff --task <id>"],
|
|
956
|
-
next: ["Use `rig stats` for fleet-level metrics across runs."]
|
|
957
|
-
},
|
|
958
|
-
{
|
|
959
|
-
name: "repo",
|
|
960
|
-
summary: "Repository sync/baseline helpers for the Rig-managed checkout.",
|
|
961
|
-
usage: ["rig repo <sync|reset-baseline>"],
|
|
962
|
-
commands: [
|
|
963
|
-
{ command: "sync", description: "Sync project repository state.", primary: true },
|
|
964
|
-
{ command: "reset-baseline", description: "Reset the managed baseline for the repo." }
|
|
965
|
-
],
|
|
966
|
-
examples: ["rig repo sync"]
|
|
967
|
-
},
|
|
968
|
-
{
|
|
969
|
-
name: "plugin",
|
|
970
|
-
summary: "Plugin listing, validation, and plugin-contributed commands.",
|
|
971
|
-
usage: ["rig plugin <list|validate|run> [options]"],
|
|
972
|
-
commands: [
|
|
973
|
-
{ command: "list", description: "List plugins declared in rig.config.ts and their contributions.", primary: true },
|
|
974
|
-
{ command: "validate --task <id>", description: "Run plugin-contributed validators for a task.", primary: true },
|
|
975
|
-
{ command: "run <command-id> [args...]", description: "Execute a plugin-contributed CLI command (also callable as `rig <command-id>`)." }
|
|
976
|
-
],
|
|
977
|
-
examples: ["rig plugin list", "rig plugin run <command-id>"]
|
|
978
|
-
},
|
|
979
|
-
{
|
|
980
|
-
name: "init",
|
|
981
|
-
summary: "Set up Rig for this repo: server, GitHub auth, checkout strategy, task source, and Pi wiring.",
|
|
982
|
-
usage: ["rig init [--yes] [--server local|remote] [--repo owner/repo] [--remote-url <url>]"],
|
|
983
|
-
commands: [
|
|
984
|
-
{ command: "init", description: "Interactive setup wizard for a new or existing Rig repo.", primary: true },
|
|
985
|
-
{ command: "init --demo", description: "Offline demo project: files task source + 3 sample tasks, zero GitHub.", primary: true },
|
|
986
|
-
{ command: "init --yes", description: "Non-interactive setup using detected/default settings.", primary: true },
|
|
987
|
-
{ command: "init --server remote --remote-url <url>", description: "Link this repo to a remote Rig server.", primary: true },
|
|
988
|
-
{ command: "init --repair", description: "Repair missing private state without replacing project config." }
|
|
989
|
-
],
|
|
990
|
-
examples: [
|
|
991
|
-
"rig init",
|
|
992
|
-
"rig init --demo",
|
|
993
|
-
"rig init --yes --repo humanity-org/humanwork",
|
|
994
|
-
"rig init --server remote --remote-url https://where.rig-does.work --repo owner/repo"
|
|
995
|
-
],
|
|
996
|
-
next: ["After init, run `rig server status`.", "Then use `rig task list` and `rig task run --next` for day-to-day work."]
|
|
997
|
-
},
|
|
998
|
-
{
|
|
999
|
-
name: "doctor",
|
|
1000
|
-
summary: "Diagnostics for project/server/GitHub/Pi state.",
|
|
1001
|
-
usage: ["rig doctor"],
|
|
1002
|
-
commands: [
|
|
1003
|
-
{ command: "doctor", description: "Run setup and runtime diagnostics.", primary: true },
|
|
1004
|
-
{ command: "check", description: "Compatibility spelling for diagnostics." }
|
|
1005
|
-
],
|
|
1006
|
-
examples: ["rig doctor", "rig doctor --json"],
|
|
1007
|
-
next: ["Use `rig server status` and `rig github auth status` to inspect common failure points."]
|
|
1008
|
-
},
|
|
1009
|
-
{
|
|
1010
|
-
name: "github",
|
|
1011
|
-
summary: "GitHub auth helpers for the selected Rig server.",
|
|
1012
|
-
usage: ["rig github auth <status|import-gh|token>"],
|
|
1013
|
-
commands: [
|
|
1014
|
-
{ command: "auth status", description: "Show GitHub auth state.", primary: true },
|
|
1015
|
-
{ command: "auth import-gh", description: "Import the current `gh` token into the selected server." },
|
|
1016
|
-
{ command: "auth token --token <token>", description: "Store a token on the selected server." }
|
|
1017
|
-
],
|
|
1018
|
-
examples: ["rig github auth status", "rig github auth import-gh"],
|
|
1019
|
-
next: ["After auth is valid, use `rig task run --next`."]
|
|
1020
|
-
}
|
|
1021
|
-
];
|
|
1022
|
-
var ADVANCED_GROUPS = [
|
|
1023
|
-
{
|
|
1024
|
-
name: "setup",
|
|
1025
|
-
summary: "Bootstrap/check local setup.",
|
|
1026
|
-
usage: ["rig setup <bootstrap|check|preflight>"],
|
|
1027
|
-
commands: [
|
|
1028
|
-
{ command: "bootstrap", description: "Bootstrap local setup dependencies.", primary: true },
|
|
1029
|
-
{ command: "check", description: "Check local setup state (toolchain, deps, config).", primary: true },
|
|
1030
|
-
{ command: "preflight", description: "Run preflight checks before a run.", primary: true }
|
|
1031
|
-
]
|
|
1032
|
-
},
|
|
1033
|
-
{
|
|
1034
|
-
name: "profile",
|
|
1035
|
-
summary: "Runtime profile/model defaults.",
|
|
1036
|
-
usage: ["rig profile <show|set>"],
|
|
1037
|
-
commands: [
|
|
1038
|
-
{ command: "show", description: "Show the active execution profile.", primary: true },
|
|
1039
|
-
{ command: "set [--model <model>] [--runtime <runtime>] [--plugin <plugin>]", description: "Set model/runtime/plugin profile defaults.", primary: true }
|
|
1040
|
-
]
|
|
1041
|
-
},
|
|
1042
|
-
{
|
|
1043
|
-
name: "review",
|
|
1044
|
-
summary: "Inspect or change completion review gate policy.",
|
|
1045
|
-
usage: ["rig review <show|set>"],
|
|
1046
|
-
commands: [
|
|
1047
|
-
{ command: "show", description: "Show current review gate settings." },
|
|
1048
|
-
{ command: "set <off|advisory|required> [--provider <provider>]", description: "Change review strictness/provider (e.g. --provider greptile)." }
|
|
1049
|
-
],
|
|
1050
|
-
examples: ["rig review show", "rig review set required --provider greptile"],
|
|
1051
|
-
next: ["Use `rig inbox approvals` for blocked run handoffs."]
|
|
1052
|
-
},
|
|
1053
|
-
{
|
|
1054
|
-
name: "browser",
|
|
1055
|
-
summary: "Browser workstation actions for browser-required tasks: launch, check, CDP probe, profile reset, and app-specific smokes.",
|
|
1056
|
-
usage: ["rig browser <help|explain|demo|hp-next|cdp-probe|profile-persistence|profile-lock-check|smoke-test> [options]"],
|
|
1057
|
-
commands: [
|
|
1058
|
-
{ command: "help", description: "Rich browser command help (canonical: `rig browser help`).", primary: true },
|
|
1059
|
-
{ command: "explain", description: "Explain the browser-required task contract and runtime helper commands.", primary: true },
|
|
1060
|
-
{ command: "demo [--port <n>] [--profile <name>] [--state-dir <path>] [--target-url <url>] [--keep-open] [--no-build]", description: "Run a guided real browser/agent demo flow.", primary: true },
|
|
1061
|
-
{ command: "hp-next dev", description: "Start hp-next Rig Browser in dev/watch mode.", primary: true },
|
|
1062
|
-
{ command: "hp-next start", description: "Build/start the hp-next browser workstation without watch mode.", primary: true },
|
|
1063
|
-
{ command: "hp-next check", description: "Validate the hp-next CDP endpoint on the configured port.", primary: true },
|
|
1064
|
-
{ command: "hp-next e2e", description: "Run hp-next browser e2e smoke checks through Rig Browser.", primary: true },
|
|
1065
|
-
{ command: "hp-next reset", description: "Reset the hp-next browser profile when stale browser state is suspected.", primary: true },
|
|
1066
|
-
{ command: "cdp-probe", description: "Run an isolated CDP attach/evaluate/layout probe." },
|
|
1067
|
-
{ command: "profile-persistence", description: "Verify a named browser profile persists browser state." },
|
|
1068
|
-
{ command: "profile-lock-check", description: "Verify concurrent launches reject the same profile." },
|
|
1069
|
-
{ command: "smoke-test", description: "Run the browser package smoke test." }
|
|
1070
|
-
],
|
|
1071
|
-
examples: [
|
|
1072
|
-
"rig browser help",
|
|
1073
|
-
"rig browser hp-next check",
|
|
1074
|
-
"rig browser hp-next e2e",
|
|
1075
|
-
"rig browser cdp-probe",
|
|
1076
|
-
"rig browser profile-lock-check"
|
|
1077
|
-
],
|
|
1078
|
-
next: ["Inside a task run, use rig-browser-launch, rig-browser-check, and rig-browser-attach-info from the worker PATH."]
|
|
1079
|
-
},
|
|
1080
|
-
{
|
|
1081
|
-
name: "pi",
|
|
1082
|
-
summary: "Manage Pi extension packages for this project (community extensions from npm/git).",
|
|
1083
|
-
usage: ["rig pi <list|add|remove|search> [args]"],
|
|
1084
|
-
commands: [
|
|
1085
|
-
{ command: "list", description: "Show project and user Pi extension packages." },
|
|
1086
|
-
{ command: "add <source>", description: "Add an npm/git Pi extension to .pi/settings.json (auto-installs at next session)." },
|
|
1087
|
-
{ command: "remove <source>", description: "Remove an operator-added Pi extension." },
|
|
1088
|
-
{ command: "search [term]", description: "Discover Pi extension packages on the npm registry." }
|
|
1089
|
-
],
|
|
1090
|
-
examples: ["rig pi search subagents", "rig pi add pi-subagents", "rig pi list"],
|
|
1091
|
-
next: ["Config-managed extensions: declare `runtime: { pi: { packages: [...] } }` in rig.config.ts \u2014 workers pick them up automatically."]
|
|
1092
|
-
},
|
|
1093
|
-
{
|
|
1094
|
-
name: "queue",
|
|
1095
|
-
summary: "Run task queues locally.",
|
|
1096
|
-
usage: ["rig queue run [--workers <n>] [--max-tasks <n>] [--action validate|verify|pipeline] [--isolation off|worktree] [--no-runtime-reuse] [--fail-fast] [--skip-project-sync]"],
|
|
1097
|
-
commands: [
|
|
1098
|
-
{ command: "run [--workers <n>] [--max-tasks <n>] [--action validate|verify|pipeline] [--isolation off|worktree] [--no-runtime-reuse] [--fail-fast] [--skip-project-sync]", description: "Process queue work: drain matching tasks with N workers, optional per-task action and isolation.", primary: true }
|
|
1099
|
-
]
|
|
1100
|
-
},
|
|
1101
|
-
{
|
|
1102
|
-
name: "agent",
|
|
1103
|
-
summary: "Runtime agent workspace helpers.",
|
|
1104
|
-
usage: ["rig agent <list|prepare|run|cleanup>"],
|
|
1105
|
-
commands: [
|
|
1106
|
-
{ command: "list", description: "List prepared agent runtimes.", primary: true },
|
|
1107
|
-
{ command: "prepare [--id <id>] [--mode <mode>] [--task <task>]", description: "Prepare an isolated agent runtime workspace.", primary: true },
|
|
1108
|
-
{ command: "run [--id <id>] [--mode <mode>] [--task <task>] [--skip-project-sync]", description: "Prepare (if needed) and run an agent in its workspace.", primary: true },
|
|
1109
|
-
{ command: "cleanup [--all] [--id <id>]", description: "Remove prepared agent workspaces.", primary: true }
|
|
1110
|
-
]
|
|
1111
|
-
},
|
|
1112
|
-
{ name: "inspector", summary: "Event stream and drift scanners.", usage: ["rig inspector <stream|scan-upstream-drift>"], commands: [{ command: "stream", description: "Stream events." }] },
|
|
1113
|
-
{
|
|
1114
|
-
name: "dist",
|
|
1115
|
-
summary: "Build/install packaged Rig CLI.",
|
|
1116
|
-
usage: ["rig dist <build|install|doctor|rebuild-agent>"],
|
|
1117
|
-
commands: [
|
|
1118
|
-
{ command: "build [--output-dir <dir>]", description: "Build the distributable Rig CLI.", primary: true },
|
|
1119
|
-
{ command: "install [--scope <scope>] [--path <path>]", description: "Install the built CLI to a scope/path.", primary: true },
|
|
1120
|
-
{ command: "doctor", description: "Diagnose the dist toolchain and install state.", primary: true },
|
|
1121
|
-
{ command: "rebuild-agent", description: "Rebuild the agent runtime image, pruning stale images.", primary: true }
|
|
1122
|
-
]
|
|
1123
|
-
},
|
|
1124
|
-
{ name: "workspace", summary: "Workspace topology/service helpers.", usage: ["rig workspace <summary|topology|remote-hosts>"], commands: [{ command: "summary", description: "Show workspace summary." }] },
|
|
1125
|
-
{
|
|
1126
|
-
name: "remote",
|
|
1127
|
-
summary: "Compatibility remote orchestration controls.",
|
|
1128
|
-
usage: ["rig remote <status|tasks|watch|pause|resume|continue|stop|refresh|add-iterations|remove-iterations|endpoint ...>"],
|
|
1129
|
-
commands: [
|
|
1130
|
-
{ command: "status [--remote <alias>]", description: "Show the remote orchestration state.", primary: true },
|
|
1131
|
-
{ command: "tasks [--remote <alias>]", description: "List the remote's tracked tasks.", primary: true },
|
|
1132
|
-
{ command: "watch [--remote <alias>] [--seconds <n>] [--event <type>]", description: "Stream remote orchestration events.", primary: true },
|
|
1133
|
-
{ command: "pause [--remote <alias>]", description: "Pause the running remote orchestration.", primary: true },
|
|
1134
|
-
{ command: "resume [--remote <alias>] [--max-workers <n>] [--max-iterations <n>] [--direct-merge]", description: "Resume the remote with optional tuning.", primary: true },
|
|
1135
|
-
{ command: "continue [--remote <alias>]", description: "Continue a paused remote orchestration.", primary: true },
|
|
1136
|
-
{ command: "stop [--remote <alias>]", description: "Stop the remote orchestration.", primary: true },
|
|
1137
|
-
{ command: "refresh [--remote <alias>]", description: "Refresh remote state.", primary: true },
|
|
1138
|
-
{ command: "add-iterations [--count <n>] [--remote <alias>]", description: "Grant the remote more iterations.", primary: true },
|
|
1139
|
-
{ command: "remove-iterations [--count <n>] [--remote <alias>]", description: "Reduce the remote's iteration budget.", primary: true },
|
|
1140
|
-
{ command: "endpoint list", description: "List configured remote endpoints.", primary: true },
|
|
1141
|
-
{ command: "endpoint add --alias <alias> --host <host> --port <n> [--token <token>]", description: "Register a remote endpoint.", primary: true },
|
|
1142
|
-
{ command: "endpoint remove --alias <alias>", description: "Remove a remote endpoint.", primary: true },
|
|
1143
|
-
{ command: "endpoint test [--alias <alias>]", description: "Test connectivity to a remote endpoint.", primary: true },
|
|
1144
|
-
{ command: "endpoint doctor", description: "Diagnose remote endpoint configuration.", primary: true }
|
|
1145
|
-
]
|
|
1146
|
-
},
|
|
1147
|
-
{ name: "git", summary: "Pass through to Rig git-flow helper.", usage: ["rig git <args...>"], commands: [{ command: "<args...>", description: "Advanced git flow operations." }] },
|
|
1148
|
-
{ name: "harness", summary: "Pass through to runtime harness CLI.", usage: ["rig harness <args...>"], commands: [{ command: "<args...>", description: "Advanced harness operations." }] },
|
|
1149
|
-
{ name: "test", summary: "Project test wrappers.", usage: ["rig test <unit|e2e|all>"], commands: [{ command: "all", description: "Run configured project tests." }] }
|
|
1150
|
-
];
|
|
1151
|
-
var ALL_GROUPS = [...PRIMARY_GROUPS, ...ADVANCED_GROUPS];
|
|
1152
|
-
|
|
1153
|
-
// packages/cli/src/app-opentui/surface-catalog.ts
|
|
1154
|
-
var NATIVE_FAMILY_SCENES = {
|
|
1155
|
-
run: { scene: "fleet", kind: "refresh", argv: ["run", "status"], label: "Open runs" },
|
|
1156
|
-
task: { scene: "tasks", kind: "refresh", argv: ["task", "list"], label: "Open tasks" },
|
|
1157
|
-
inbox: { scene: "inbox", kind: "refresh", argv: ["inbox", "approvals"], label: "Open inbox" },
|
|
1158
|
-
server: { scene: "server", kind: "refresh", argv: ["server", "status"], label: "Open server" },
|
|
1159
|
-
init: { scene: "init", kind: "refresh", argv: ["init"], label: "Open init" },
|
|
1160
|
-
doctor: { scene: "doctor", kind: "doctor-run", argv: ["doctor"], label: "Run doctor" }
|
|
1161
|
-
};
|
|
1162
|
-
var BESPOKE_FAMILIES = new Set([
|
|
1163
|
-
"run",
|
|
1164
|
-
"task",
|
|
1165
|
-
"inbox",
|
|
1166
|
-
"server",
|
|
1167
|
-
"init",
|
|
1168
|
-
"doctor",
|
|
1169
|
-
"pi",
|
|
1170
|
-
"plugin",
|
|
1171
|
-
"repo",
|
|
1172
|
-
"workspace",
|
|
1173
|
-
"inspect"
|
|
1174
|
-
]);
|
|
1175
|
-
var SAFE_DIRECT_COMMANDS = new Set([
|
|
1176
|
-
"server status",
|
|
1177
|
-
"server list",
|
|
1178
|
-
"server use local",
|
|
1179
|
-
"task list",
|
|
1180
|
-
"task next",
|
|
1181
|
-
"task ready",
|
|
1182
|
-
"run list",
|
|
1183
|
-
"run status",
|
|
1184
|
-
"inbox approvals",
|
|
1185
|
-
"inbox inputs",
|
|
1186
|
-
"stats show",
|
|
1187
|
-
"plugin list",
|
|
1188
|
-
"doctor doctor",
|
|
1189
|
-
"doctor check",
|
|
1190
|
-
"github auth status",
|
|
1191
|
-
"browser help",
|
|
1192
|
-
"browser explain",
|
|
1193
|
-
"pi list",
|
|
1194
|
-
"pi search",
|
|
1195
|
-
"setup check",
|
|
1196
|
-
"profile show",
|
|
1197
|
-
"review show",
|
|
1198
|
-
"repo sync",
|
|
1199
|
-
"agent list",
|
|
1200
|
-
"dist doctor",
|
|
1201
|
-
"workspace summary",
|
|
1202
|
-
"remote status"
|
|
1203
|
-
]);
|
|
1204
|
-
function normalizeCommandTemplate(command) {
|
|
1205
|
-
return command.replace(/\s+/g, " ").trim();
|
|
1206
|
-
}
|
|
1207
|
-
function firstAlternative(value) {
|
|
1208
|
-
return value.split("|")[0]?.trim() ?? value.trim();
|
|
1209
|
-
}
|
|
1210
|
-
function stripOptionalSegments(value) {
|
|
1211
|
-
return value.replace(/\[[^\]]*\]/g, "").replace(/\s+/g, " ").trim();
|
|
1212
|
-
}
|
|
1213
|
-
function commandNeedsArguments(template) {
|
|
1214
|
-
return /<[^>]+>|\.\.\.|\|/.test(template);
|
|
1215
|
-
}
|
|
1216
|
-
function commandKey(family, command) {
|
|
1217
|
-
return `${family} ${normalizeCommandTemplate(command)}`.replace(/\s+/g, " ").trim();
|
|
1218
|
-
}
|
|
1219
|
-
function commandArgvForTemplate(group, command) {
|
|
1220
|
-
const family = group.name;
|
|
1221
|
-
const template = normalizeCommandTemplate(command.command);
|
|
1222
|
-
if (!template || template === family)
|
|
1223
|
-
return [family, "--help"];
|
|
1224
|
-
const key = commandKey(family, template);
|
|
1225
|
-
if (SAFE_DIRECT_COMMANDS.has(key))
|
|
1226
|
-
return [family, ...stripOptionalSegments(template).split(/\s+/).filter(Boolean)];
|
|
1227
|
-
const base = firstAlternative(stripOptionalSegments(template));
|
|
1228
|
-
const parts = base.split(/\s+/).filter(Boolean);
|
|
1229
|
-
if (!commandNeedsArguments(template) && parts.length > 0)
|
|
1230
|
-
return [family, ...parts];
|
|
1231
|
-
return [family, "--help"];
|
|
1232
|
-
}
|
|
1233
|
-
function promptArgvForTemplate(group, command) {
|
|
1234
|
-
const base = stripOptionalSegments(firstAlternative(normalizeCommandTemplate(command.command)));
|
|
1235
|
-
const words = [];
|
|
1236
|
-
for (const part of base.split(/\s+/).filter(Boolean)) {
|
|
1237
|
-
if (/[<>]|\.\.\./.test(part))
|
|
1238
|
-
break;
|
|
1239
|
-
words.push(part);
|
|
1240
|
-
}
|
|
1241
|
-
return [group.name, ...words];
|
|
1242
|
-
}
|
|
1243
|
-
function sceneForGroup(group) {
|
|
1244
|
-
const native = NATIVE_FAMILY_SCENES[group.name];
|
|
1245
|
-
if (native)
|
|
1246
|
-
return native;
|
|
1247
|
-
if (!BESPOKE_FAMILIES.has(group.name)) {
|
|
1248
|
-
return { scene: "family", kind: "refresh", argv: [group.name], label: `Open ${group.name}` };
|
|
1249
|
-
}
|
|
1250
|
-
return { scene: "command", kind: "command-run", argv: [group.name, "--help"], label: `Open ${group.name}` };
|
|
1251
|
-
}
|
|
1252
|
-
function tuiSurfaceActions() {
|
|
1253
|
-
const { groups } = helpCatalog();
|
|
1254
|
-
return groups.flatMap((group) => {
|
|
1255
|
-
const family = sceneForGroup(group);
|
|
1256
|
-
const groupAction = {
|
|
1257
|
-
id: `family:${group.name}`,
|
|
1258
|
-
family: group.name,
|
|
1259
|
-
label: group.name,
|
|
1260
|
-
detail: group.summary,
|
|
1261
|
-
argv: family.argv,
|
|
1262
|
-
scene: family.scene,
|
|
1263
|
-
kind: family.kind,
|
|
1264
|
-
primary: true
|
|
1265
|
-
};
|
|
1266
|
-
const commandActions = group.commands.map((command, index) => {
|
|
1267
|
-
const argv = commandArgvForTemplate(group, command);
|
|
1268
|
-
const direct = argv[0] === group.name && argv.slice(1).join(" ") === stripOptionalSegments(firstAlternative(command.command));
|
|
1269
|
-
const template = normalizeCommandTemplate(command.command);
|
|
1270
|
-
const needsArgs = commandNeedsArguments(template) && argv.length === 2 && argv[1] === "--help";
|
|
1271
|
-
const promptArgv = needsArgs ? promptArgvForTemplate(group, command) : undefined;
|
|
1272
|
-
return {
|
|
1273
|
-
id: `command:${group.name}:${index}:${command.command}`,
|
|
1274
|
-
family: group.name,
|
|
1275
|
-
label: `${group.name} ${command.command}`,
|
|
1276
|
-
detail: direct ? command.description : needsArgs ? `${command.description} \xB7 enter args to run` : `${command.description} \xB7 opens ${group.name} command workspace/help`,
|
|
1277
|
-
argv,
|
|
1278
|
-
scene: "command",
|
|
1279
|
-
kind: "command-run",
|
|
1280
|
-
primary: command.primary,
|
|
1281
|
-
template: `rig ${group.name} ${command.command}`.replace(/\s+/g, " ").trim(),
|
|
1282
|
-
...needsArgs ? { needsArgs: true, promptArgv } : {}
|
|
1283
|
-
};
|
|
1284
|
-
});
|
|
1285
|
-
return [groupAction, ...commandActions];
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
function intentForSurfaceAction(action) {
|
|
1289
|
-
return {
|
|
1290
|
-
scene: action.scene,
|
|
1291
|
-
argv: action.argv,
|
|
1292
|
-
action: {
|
|
1293
|
-
kind: action.kind,
|
|
1294
|
-
label: action.kind === "command-run" ? `Open ${action.label}` : action.label,
|
|
1295
|
-
payload: {
|
|
1296
|
-
argv: [...action.argv],
|
|
1297
|
-
family: action.family,
|
|
1298
|
-
...action.template ? { template: action.template } : {}
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
};
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
// packages/cli/src/app-opentui/autocomplete.ts
|
|
1305
|
-
var NAV_VERBS = ["runs", "tasks", "inbox", "server", "doctor", "help", "init", "main"];
|
|
1306
|
-
var cachedCandidates = null;
|
|
1307
|
-
function commandCandidates() {
|
|
1308
|
-
if (cachedCandidates)
|
|
1309
|
-
return cachedCandidates;
|
|
1310
|
-
const set = new Set(NAV_VERBS);
|
|
1311
|
-
for (const action of tuiSurfaceActions()) {
|
|
1312
|
-
const argv = action.promptArgv ?? action.argv;
|
|
1313
|
-
const command = argv.join(" ").trim();
|
|
1314
|
-
if (command && !command.endsWith("--help"))
|
|
1315
|
-
set.add(command);
|
|
1316
|
-
if (action.family)
|
|
1317
|
-
set.add(action.family);
|
|
1318
|
-
}
|
|
1319
|
-
cachedCandidates = [...set].sort((a, b) => a.localeCompare(b));
|
|
1320
|
-
return cachedCandidates;
|
|
1321
|
-
}
|
|
1322
|
-
function matchesFor(base, candidates = commandCandidates()) {
|
|
1323
|
-
const needle = base.trim().toLowerCase();
|
|
1324
|
-
if (!needle)
|
|
1325
|
-
return [];
|
|
1326
|
-
return candidates.filter((candidate) => candidate.toLowerCase().startsWith(needle) && candidate.toLowerCase() !== needle);
|
|
1327
|
-
}
|
|
1328
|
-
function cycleCompletion(value, state, candidates = commandCandidates()) {
|
|
1329
|
-
const base = state && matchesFor(state.base, candidates).includes(value) ? state.base : value;
|
|
1330
|
-
const matches = matchesFor(base, candidates);
|
|
1331
|
-
if (matches.length === 0)
|
|
1332
|
-
return { value, state: undefined };
|
|
1333
|
-
const startIndex = state && base === state.base ? state.index : -1;
|
|
1334
|
-
const nextIndex = (startIndex + 1) % matches.length;
|
|
1335
|
-
return { value: matches[nextIndex], state: { base, index: nextIndex } };
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
// packages/cli/src/app-opentui/keymap.ts
|
|
1339
|
-
var autocompleteState;
|
|
1340
|
-
function clearTypeBar(context, message) {
|
|
1341
|
-
const typeBar = context.getTypeBar();
|
|
1342
|
-
if (typeBar)
|
|
1343
|
-
typeBar.input.value = "";
|
|
1344
|
-
context.emitTypeBarPatch({ value: "", mode: "nav", prompt: undefined, message });
|
|
1345
|
-
}
|
|
1346
|
-
function handleEmbeddedTerminalKey(context, key) {
|
|
1347
|
-
if (context.getState().scene !== "command")
|
|
1348
|
-
return false;
|
|
1349
|
-
const host = getActiveCommandHost();
|
|
1350
|
-
if (!host)
|
|
1351
|
-
return false;
|
|
1352
|
-
if (key.ctrl && key.name === "]") {
|
|
1353
|
-
stopActiveCommandHost("operator detach");
|
|
1354
|
-
clearTypeBar(context, "closed command");
|
|
1355
|
-
context.runAppAction("Project menu", () => context.runtime.runIntent({ scene: "main", argv: ["main"], action: { kind: "none", label: "Project menu" } }));
|
|
1356
|
-
return true;
|
|
1357
|
-
}
|
|
1358
|
-
const sequence = key.raw || key.sequence;
|
|
1359
|
-
if (sequence)
|
|
1360
|
-
host.write(sequence);
|
|
1361
|
-
const typeBar = context.getTypeBar();
|
|
1362
|
-
if (typeBar)
|
|
1363
|
-
typeBar.input.value = "";
|
|
1364
|
-
return true;
|
|
1365
|
-
}
|
|
1366
|
-
function printableChar(key) {
|
|
1367
|
-
if (key.ctrl || key.meta || key.super || key.hyper)
|
|
1368
|
-
return null;
|
|
1369
|
-
if (key.name === "space")
|
|
1370
|
-
return " ";
|
|
1371
|
-
const seq = key.sequence;
|
|
1372
|
-
if (seq && seq.length === 1 && seq >= " " && seq !== "\x7F")
|
|
1373
|
-
return seq;
|
|
1374
|
-
return null;
|
|
1375
|
-
}
|
|
1376
|
-
function enterInputMode(context, mode) {
|
|
1377
|
-
context.emitTypeBarPatch({ mode, value: "", prompt: undefined, message: undefined });
|
|
1378
|
-
}
|
|
1379
|
-
function submitInput(context, state) {
|
|
1380
|
-
const mode = state.typeBar.mode ?? "nav";
|
|
1381
|
-
const value = state.typeBar.value.trim();
|
|
1382
|
-
if (mode === "search") {
|
|
1383
|
-
context.emitTypeBarPatch({ mode: "nav" });
|
|
1384
|
-
return;
|
|
1385
|
-
}
|
|
1386
|
-
if (mode === "command") {
|
|
1387
|
-
if (!value) {
|
|
1388
|
-
clearTypeBar(context);
|
|
1389
|
-
return;
|
|
1390
|
-
}
|
|
1391
|
-
const intent2 = intentFromTypeBar(value);
|
|
1392
|
-
context.emitTypeBarPatch({ value: "", mode: "nav", prompt: undefined });
|
|
1393
|
-
context.runAppAction(intent2.action.label ?? "Run command", () => context.runtime.runIntent(intent2));
|
|
1394
|
-
return;
|
|
1395
|
-
}
|
|
1396
|
-
if (mode === "prompt") {
|
|
1397
|
-
const prompt = state.typeBar.prompt;
|
|
1398
|
-
if (!prompt || !value) {
|
|
1399
|
-
clearTypeBar(context, prompt ? "enter a value" : undefined);
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
const intent2 = prompt.appendValueToArgv ? {
|
|
1403
|
-
scene: prompt.scene,
|
|
1404
|
-
argv: [...prompt.argv, ...value.split(/\s+/).filter(Boolean)],
|
|
1405
|
-
action: { kind: prompt.intentKind, label: `${prompt.label} ${value}`.trim(), payload: { ...prompt.payloadBase, argv: [...prompt.argv, ...value.split(/\s+/).filter(Boolean)] } }
|
|
1406
|
-
} : {
|
|
1407
|
-
scene: prompt.scene,
|
|
1408
|
-
argv: prompt.argv,
|
|
1409
|
-
action: { kind: prompt.intentKind, label: prompt.label, payload: { ...prompt.payloadBase, [prompt.payloadKey]: value } }
|
|
1410
|
-
};
|
|
1411
|
-
context.emitTypeBarPatch({ value: "", mode: "nav", prompt: undefined });
|
|
1412
|
-
context.runAppAction(prompt.label, () => context.runtime.runIntent(intent2));
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
|
-
clearTypeBar(context);
|
|
1416
|
-
}
|
|
1417
|
-
function handleAppKeyPress(context, key) {
|
|
1418
|
-
if (handleEmbeddedTerminalKey(context, key))
|
|
1419
|
-
return;
|
|
1420
|
-
const renderer = context.renderer();
|
|
1421
|
-
if (key.ctrl && key.name === "c") {
|
|
1422
|
-
renderer?.destroy();
|
|
1423
|
-
return;
|
|
1424
|
-
}
|
|
1425
|
-
const state = context.getState();
|
|
1426
|
-
const currentScene = state.scene;
|
|
1427
|
-
const mode = state.typeBar.mode ?? "nav";
|
|
1428
|
-
if (state.confirm) {
|
|
1429
|
-
if (key.name === "return" || key.name === "enter" || key.sequence === "y") {
|
|
1430
|
-
const intent2 = state.confirm.intent;
|
|
1431
|
-
const label = state.confirm.label;
|
|
1432
|
-
context.runAppAction(label, () => context.runtime.runIntent(intent2));
|
|
1433
|
-
return;
|
|
1434
|
-
}
|
|
1435
|
-
if (key.name === "escape" || key.sequence === "n") {
|
|
1436
|
-
context.runtime.emit({ type: "confirm.cancel" });
|
|
1437
|
-
return;
|
|
1438
|
-
}
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1441
|
-
if (mode !== "nav") {
|
|
1442
|
-
if (key.name === "escape") {
|
|
1443
|
-
autocompleteState = undefined;
|
|
1444
|
-
clearTypeBar(context);
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1447
|
-
if (mode === "command" && key.name === "tab") {
|
|
1448
|
-
const result = cycleCompletion(state.typeBar.value, autocompleteState, commandCandidates());
|
|
1449
|
-
autocompleteState = result.state;
|
|
1450
|
-
if (result.value !== state.typeBar.value)
|
|
1451
|
-
context.emitTypeBarPatch({ value: result.value });
|
|
1452
|
-
return;
|
|
1453
|
-
}
|
|
1454
|
-
if (key.name === "return" || key.name === "enter") {
|
|
1455
|
-
autocompleteState = undefined;
|
|
1456
|
-
if (mode === "command") {
|
|
1457
|
-
const items = context.getRenderedSelectableItems();
|
|
1458
|
-
if (items.length > 0) {
|
|
1459
|
-
context.runAppAction("Run command", () => context.activateSelection(items));
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
submitInput(context, state);
|
|
1464
|
-
return;
|
|
1465
|
-
}
|
|
1466
|
-
if (key.name === "backspace" || key.sequence === "\x7F") {
|
|
1467
|
-
context.emitTypeBarPatch({ value: state.typeBar.value.slice(0, -1) });
|
|
1468
|
-
return;
|
|
1469
|
-
}
|
|
1470
|
-
if (key.name === "up" || key.name === "down") {
|
|
1471
|
-
context.moveSelection(key.name === "up" ? -1 : 1, context.getRenderedSelectableItems());
|
|
1472
|
-
return;
|
|
1473
|
-
}
|
|
1474
|
-
const char = printableChar(key);
|
|
1475
|
-
if (char) {
|
|
1476
|
-
context.emitTypeBarPatch({ value: state.typeBar.value + char });
|
|
1477
|
-
return;
|
|
1478
|
-
}
|
|
1479
|
-
return;
|
|
1480
|
-
}
|
|
1481
|
-
const hasFilter = state.typeBar.value.trim() !== "";
|
|
1482
|
-
if (key.sequence === "/" || key.sequence === ":") {
|
|
1483
|
-
autocompleteState = undefined;
|
|
1484
|
-
context.runtime.patchState({ selection: { index: 0, count: 0 } });
|
|
1485
|
-
enterInputMode(context, "command");
|
|
1486
|
-
return;
|
|
1487
|
-
}
|
|
1488
|
-
if (key.name === "right" || key.name === "tab") {
|
|
1489
|
-
context.runAppAction("Navigate", () => context.runtime.runIntent(context.primaryNavIntent(context.nextPrimaryScene(context.getState().scene, 1))));
|
|
1490
|
-
return;
|
|
1491
|
-
}
|
|
1492
|
-
if (key.name === "left") {
|
|
1493
|
-
context.runAppAction("Navigate", () => context.runtime.runIntent(context.primaryNavIntent(context.nextPrimaryScene(context.getState().scene, -1))));
|
|
1494
|
-
return;
|
|
1495
|
-
}
|
|
1496
|
-
if (key.name === "r") {
|
|
1497
|
-
context.runAppAction("Opening runs", () => context.runtime.runIntent(context.primaryNavIntent("fleet")));
|
|
1498
|
-
return;
|
|
1499
|
-
}
|
|
1500
|
-
if (key.name === "t") {
|
|
1501
|
-
context.runAppAction("Opening tasks", () => context.runtime.runIntent(context.primaryNavIntent("tasks")));
|
|
1502
|
-
return;
|
|
1503
|
-
}
|
|
1504
|
-
if (key.name === "h" || key.sequence === "?") {
|
|
1505
|
-
context.runAppAction("Opening help", () => context.runtime.runIntent(context.primaryNavIntent("help")));
|
|
1506
|
-
return;
|
|
1507
|
-
}
|
|
1508
|
-
const PAGE_ROWS = 16;
|
|
1509
|
-
if (key.name === "pageup") {
|
|
1510
|
-
context.scrollActiveBody(-PAGE_ROWS);
|
|
1511
|
-
return;
|
|
1512
|
-
}
|
|
1513
|
-
if (key.name === "pagedown") {
|
|
1514
|
-
context.scrollActiveBody(PAGE_ROWS);
|
|
1515
|
-
return;
|
|
1516
|
-
}
|
|
1517
|
-
if (key.ctrl && key.name === "u") {
|
|
1518
|
-
context.scrollActiveBody(-Math.round(PAGE_ROWS / 2));
|
|
1519
|
-
return;
|
|
1520
|
-
}
|
|
1521
|
-
if (key.ctrl && key.name === "d") {
|
|
1522
|
-
context.scrollActiveBody(Math.round(PAGE_ROWS / 2));
|
|
1523
|
-
return;
|
|
1524
|
-
}
|
|
1525
|
-
if (key.name === "home" && !hasFilter) {
|
|
1526
|
-
context.scrollActiveBody(-1e6);
|
|
1527
|
-
return;
|
|
1528
|
-
}
|
|
1529
|
-
if (key.name === "end" && !hasFilter) {
|
|
1530
|
-
context.scrollActiveBody(1e6);
|
|
1531
|
-
return;
|
|
1532
|
-
}
|
|
1533
|
-
if (key.name === "up" || key.name === "k") {
|
|
1534
|
-
if (context.moveSelection(-1, context.getRenderedSelectableItems()))
|
|
1535
|
-
return;
|
|
1536
|
-
}
|
|
1537
|
-
if (key.name === "down" || key.name === "j") {
|
|
1538
|
-
if (context.moveSelection(1, context.getRenderedSelectableItems()))
|
|
1539
|
-
return;
|
|
1540
|
-
}
|
|
1541
|
-
if (key.name === "return" || key.name === "enter" || key.name === "space" || key.sequence === " ") {
|
|
1542
|
-
context.runAppAction("Activate selection", () => context.activateSelection(context.getRenderedSelectableItems()));
|
|
1543
|
-
return;
|
|
1544
|
-
}
|
|
1545
|
-
if (key.name === "q") {
|
|
1546
|
-
if (currentScene === "main")
|
|
1547
|
-
renderer?.destroy();
|
|
1548
|
-
else
|
|
1549
|
-
context.runAppAction("Back", () => context.runtime.goBack());
|
|
1550
|
-
return;
|
|
1551
|
-
}
|
|
1552
|
-
if (key.name === "escape") {
|
|
1553
|
-
if (hasFilter)
|
|
1554
|
-
clearTypeBar(context);
|
|
1555
|
-
else
|
|
1556
|
-
context.runAppAction("Back", () => context.runtime.goBack());
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
// packages/cli/src/app-opentui/layout.ts
|
|
1561
|
-
var MIN_TERMINAL_WIDTH = 48;
|
|
1562
|
-
var MIN_TERMINAL_HEIGHT = 16;
|
|
1563
|
-
function computeStageLayout(width, height) {
|
|
1564
|
-
const rawWidth = Math.trunc(width || 80);
|
|
1565
|
-
const rawHeight = Math.trunc(height || 24);
|
|
1566
|
-
const tooSmall = rawWidth < MIN_TERMINAL_WIDTH || rawHeight < MIN_TERMINAL_HEIGHT;
|
|
1567
|
-
const safeWidth = Math.max(40, rawWidth);
|
|
1568
|
-
const safeHeight = Math.max(18, rawHeight);
|
|
1569
|
-
const compact = safeWidth < 92 || safeHeight < 30;
|
|
1570
|
-
const horizontalGutter = compact ? 1 : 2;
|
|
1571
|
-
const centerWidth = Math.max(36, safeWidth - horizontalGutter * 2);
|
|
1572
|
-
const centerLeft = horizontalGutter;
|
|
1573
|
-
const footerTop = Math.max(0, safeHeight - 1);
|
|
1574
|
-
const typeBarTop = Math.max(0, footerTop - 1);
|
|
1575
|
-
const centerTop = compact ? 1 : Math.max(1, Math.floor(safeHeight * 0.055));
|
|
1576
|
-
const centerHeight = Math.max(6, typeBarTop - centerTop);
|
|
1577
|
-
return { width: safeWidth, height: safeHeight, centerWidth, centerLeft, centerTop, centerHeight, typeBarTop, footerTop, compact, tooSmall };
|
|
1578
|
-
}
|
|
1579
|
-
function visibleWidth(text) {
|
|
1580
|
-
return [...text].length;
|
|
1581
|
-
}
|
|
1582
|
-
function alignText(text, width, align = "left") {
|
|
1583
|
-
const length = visibleWidth(text);
|
|
1584
|
-
if (length >= width)
|
|
1585
|
-
return text;
|
|
1586
|
-
const pad = width - length;
|
|
1587
|
-
if (align === "right")
|
|
1588
|
-
return `${" ".repeat(pad)}${text}`;
|
|
1589
|
-
if (align === "center") {
|
|
1590
|
-
const left = Math.floor(pad / 2);
|
|
1591
|
-
return `${" ".repeat(left)}${text}${" ".repeat(pad - left)}`;
|
|
1592
|
-
}
|
|
1593
|
-
return text;
|
|
1594
|
-
}
|
|
1595
|
-
function truncateText(text, width) {
|
|
1596
|
-
if (visibleWidth(text) <= width)
|
|
1597
|
-
return text;
|
|
1598
|
-
if (width <= 1)
|
|
1599
|
-
return "\u2026";
|
|
1600
|
-
return `${[...text].slice(0, Math.max(0, width - 1)).join("")}\u2026`;
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
// packages/cli/src/commands/_server-events.ts
|
|
1604
|
-
import { WsTransport } from "@rig/client";
|
|
1605
|
-
import { RIG_WS_CHANNELS } from "@rig/contracts";
|
|
1606
|
-
|
|
1607
|
-
// packages/cli/src/commands/_server-client.ts
|
|
1608
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1609
|
-
import { dirname as dirname2, isAbsolute, resolve as resolve2 } from "path";
|
|
1610
|
-
|
|
1611
|
-
// packages/cli/src/runner.ts
|
|
1612
|
-
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
1613
|
-
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
1614
|
-
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
1615
|
-
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
1616
|
-
|
|
1617
|
-
class CliError extends RuntimeCliError {
|
|
1618
|
-
hint;
|
|
1619
|
-
constructor(message, exitCode = 1, options = {}) {
|
|
1620
|
-
super(message, exitCode);
|
|
1621
|
-
if (options.hint?.trim()) {
|
|
1622
|
-
this.hint = options.hint.trim();
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
// packages/cli/src/commands/_server-client.ts
|
|
1628
|
-
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
1629
|
-
|
|
1630
|
-
// packages/cli/src/commands/_connection-state.ts
|
|
1631
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
1632
|
-
import { homedir } from "os";
|
|
1633
|
-
import { dirname, resolve } from "path";
|
|
1634
|
-
function resolveGlobalConnectionsPath(env = process.env) {
|
|
1635
|
-
const explicit = env.RIG_CONNECTIONS_FILE?.trim();
|
|
1636
|
-
if (explicit)
|
|
1637
|
-
return resolve(explicit);
|
|
1638
|
-
const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
|
|
1639
|
-
if (stateDir)
|
|
1640
|
-
return resolve(stateDir, "connections.json");
|
|
1641
|
-
return resolve(homedir(), ".rig", "connections.json");
|
|
1642
|
-
}
|
|
1643
|
-
function resolveRepoConnectionPath(projectRoot) {
|
|
1644
|
-
return resolve(projectRoot, ".rig", "state", "connection.json");
|
|
1645
|
-
}
|
|
1646
|
-
function readJsonFile(path) {
|
|
1647
|
-
if (!existsSync(path))
|
|
1648
|
-
return null;
|
|
1649
|
-
try {
|
|
1650
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
1651
|
-
} catch (error) {
|
|
1652
|
-
throw new CliError(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1, { hint: "Fix or delete that file, then re-select a server with `rig server use <alias|local>`." });
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
function writeJsonFile(path, value) {
|
|
1656
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
1657
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
1658
|
-
`, "utf8");
|
|
1659
|
-
}
|
|
1660
|
-
function normalizeConnection(value) {
|
|
1661
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1662
|
-
return null;
|
|
1663
|
-
const record = value;
|
|
1664
|
-
if (record.kind === "local")
|
|
1665
|
-
return { kind: "local", mode: "auto" };
|
|
1666
|
-
if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
|
|
1667
|
-
const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
|
|
1668
|
-
return { kind: "remote", baseUrl };
|
|
1669
|
-
}
|
|
1670
|
-
return null;
|
|
1671
|
-
}
|
|
1672
|
-
function readGlobalConnections(options = {}) {
|
|
1673
|
-
const path = resolveGlobalConnectionsPath(options.env ?? process.env);
|
|
1674
|
-
const payload = readJsonFile(path);
|
|
1675
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
1676
|
-
return { connections: {} };
|
|
1677
|
-
}
|
|
1678
|
-
const rawConnections = payload.connections;
|
|
1679
|
-
const connections = {};
|
|
1680
|
-
if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
|
|
1681
|
-
for (const [alias, raw] of Object.entries(rawConnections)) {
|
|
1682
|
-
const connection = normalizeConnection(raw);
|
|
1683
|
-
if (connection)
|
|
1684
|
-
connections[alias] = connection;
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
return { connections };
|
|
1688
|
-
}
|
|
1689
|
-
function readRepoConnection(projectRoot) {
|
|
1690
|
-
const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
|
|
1691
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1692
|
-
return null;
|
|
1693
|
-
const record = payload;
|
|
1694
|
-
const selected = typeof record.selected === "string" ? record.selected.trim() : "";
|
|
1695
|
-
if (!selected)
|
|
1696
|
-
return null;
|
|
1697
|
-
return {
|
|
1698
|
-
selected,
|
|
1699
|
-
project: typeof record.project === "string" ? record.project : undefined,
|
|
1700
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
1701
|
-
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
|
|
1702
|
-
serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
|
|
1703
|
-
serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
|
|
1704
|
-
};
|
|
1705
|
-
}
|
|
1706
|
-
function writeRepoConnection(projectRoot, state) {
|
|
1707
|
-
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
1708
|
-
}
|
|
1709
|
-
function rootAllowedForSelection(repo, connection) {
|
|
1710
|
-
const root = repo.serverProjectRoot?.trim();
|
|
1711
|
-
if (!root)
|
|
1712
|
-
return;
|
|
1713
|
-
if (connection.kind === "remote") {
|
|
1714
|
-
if (repo.serverProjectRootAlias !== repo.selected)
|
|
1715
|
-
return;
|
|
1716
|
-
if (repo.serverProjectRootBaseUrl !== connection.baseUrl)
|
|
1717
|
-
return;
|
|
1718
|
-
} else if (repo.serverProjectRootAlias && repo.serverProjectRootAlias !== repo.selected) {
|
|
1719
|
-
return;
|
|
1720
|
-
}
|
|
1721
|
-
return root;
|
|
1722
|
-
}
|
|
1723
|
-
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
1724
|
-
const repo = readRepoConnection(projectRoot);
|
|
1725
|
-
if (!repo)
|
|
1726
|
-
return null;
|
|
1727
|
-
if (repo.selected === "local")
|
|
1728
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
1729
|
-
const global = readGlobalConnections(options);
|
|
1730
|
-
const connection = global.connections[repo.selected];
|
|
1731
|
-
if (!connection) {
|
|
1732
|
-
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
1733
|
-
}
|
|
1734
|
-
return { alias: repo.selected, connection, serverProjectRoot: rootAllowedForSelection(repo, connection) };
|
|
1735
|
-
}
|
|
1736
|
-
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot, metadata = {}) {
|
|
1737
|
-
const repo = readRepoConnection(projectRoot);
|
|
1738
|
-
if (!repo)
|
|
1739
|
-
return;
|
|
1740
|
-
let inferred = metadata;
|
|
1741
|
-
if (!inferred.alias || !inferred.baseUrl) {
|
|
1742
|
-
try {
|
|
1743
|
-
const selected = resolveSelectedConnection(projectRoot);
|
|
1744
|
-
if (selected?.connection.kind === "remote") {
|
|
1745
|
-
inferred = {
|
|
1746
|
-
alias: inferred.alias ?? selected.alias,
|
|
1747
|
-
baseUrl: inferred.baseUrl ?? selected.connection.baseUrl
|
|
1748
|
-
};
|
|
1749
|
-
}
|
|
1750
|
-
} catch {}
|
|
1751
|
-
}
|
|
1752
|
-
writeRepoConnection(projectRoot, {
|
|
1753
|
-
...repo,
|
|
1754
|
-
...metadata.project ? { project: metadata.project } : {},
|
|
1755
|
-
serverProjectRoot,
|
|
1756
|
-
...inferred.alias ? { serverProjectRootAlias: inferred.alias } : {},
|
|
1757
|
-
...inferred.baseUrl ? { serverProjectRootBaseUrl: inferred.baseUrl.replace(/\/+$/, "") } : {}
|
|
1758
|
-
});
|
|
1759
|
-
}
|
|
1760
|
-
function clearRepoServerProjectRoot(projectRoot) {
|
|
1761
|
-
const repo = readRepoConnection(projectRoot);
|
|
1762
|
-
if (!repo)
|
|
1763
|
-
return;
|
|
1764
|
-
writeRepoConnection(projectRoot, {
|
|
1765
|
-
selected: repo.selected,
|
|
1766
|
-
...repo.project ? { project: repo.project } : {},
|
|
1767
|
-
...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
|
|
1768
|
-
});
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
// packages/cli/src/commands/_server-client.ts
|
|
1772
|
-
var scopedGitHubBearerTokens = new Map;
|
|
1773
|
-
var serverPhaseListener = null;
|
|
1774
|
-
function reportServerPhase(label) {
|
|
1775
|
-
serverPhaseListener?.(label);
|
|
1776
|
-
}
|
|
1777
|
-
function cleanToken(value) {
|
|
1778
|
-
const trimmed = value?.trim();
|
|
1779
|
-
return trimmed ? trimmed : null;
|
|
1780
|
-
}
|
|
1781
|
-
function readRemoteAuthState(projectRoot) {
|
|
1782
|
-
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
1783
|
-
if (!existsSync2(path))
|
|
1784
|
-
return null;
|
|
1785
|
-
try {
|
|
1786
|
-
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
1787
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
1788
|
-
} catch {
|
|
1789
|
-
return null;
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
function readPrivateRemoteSessionToken(projectRoot) {
|
|
1793
|
-
const parsed = readRemoteAuthState(projectRoot);
|
|
1794
|
-
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
1795
|
-
}
|
|
1796
|
-
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
1797
|
-
const scopedKey = resolve2(projectRoot);
|
|
1798
|
-
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
1799
|
-
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
1800
|
-
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
1801
|
-
if (privateSession)
|
|
1802
|
-
return privateSession;
|
|
1803
|
-
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
1804
|
-
}
|
|
1805
|
-
function readStoredGitHubAuthToken(projectRoot) {
|
|
1806
|
-
const parsed = readRemoteAuthState(projectRoot);
|
|
1807
|
-
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
1808
|
-
}
|
|
1809
|
-
function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
|
|
1810
|
-
if (!/^[^/]+\/[^/]+$/.test(repoSlug))
|
|
1811
|
-
return null;
|
|
1812
|
-
const repo = readRepoConnection(projectRoot);
|
|
1813
|
-
const auth = readRemoteAuthState(projectRoot);
|
|
1814
|
-
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
1815
|
-
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
1816
|
-
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
1817
|
-
return null;
|
|
1818
|
-
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
1819
|
-
if (!checkoutBaseDir)
|
|
1820
|
-
return null;
|
|
1821
|
-
return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
|
|
1822
|
-
}
|
|
1823
|
-
function readLocalConnectionFallbackToken(projectRoot) {
|
|
1824
|
-
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
1825
|
-
}
|
|
1826
|
-
function normalizeRepoSlug(value) {
|
|
1827
|
-
const slug = value?.trim();
|
|
1828
|
-
return slug && /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
|
|
1829
|
-
}
|
|
1830
|
-
function readProjectLinkSlug(projectRoot) {
|
|
1831
|
-
const path = resolve2(projectRoot, ".rig", "state", "project-link.json");
|
|
1832
|
-
if (!existsSync2(path))
|
|
1833
|
-
return null;
|
|
1834
|
-
try {
|
|
1835
|
-
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
1836
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
1837
|
-
return null;
|
|
1838
|
-
return normalizeRepoSlug(typeof parsed.repoSlug === "string" ? String(parsed.repoSlug) : null);
|
|
1839
|
-
} catch {
|
|
1840
|
-
return null;
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
function writeProjectLinkConnection(projectRoot, repoSlug, alias) {
|
|
1844
|
-
const path = resolve2(projectRoot, ".rig", "state", "project-link.json");
|
|
1845
|
-
mkdirSync2(dirname2(path), { recursive: true });
|
|
1846
|
-
writeFileSync2(path, `${JSON.stringify({ repoSlug, connection: alias, linkedAt: new Date().toISOString() }, null, 2)}
|
|
1847
|
-
`, "utf8");
|
|
1848
|
-
}
|
|
1849
|
-
function remoteLinkRepairCommand(repoSlug) {
|
|
1850
|
-
return repoSlug ? `rig server repair-link --repo ${repoSlug}` : "rig server repair-link --repo owner/repo";
|
|
1851
|
-
}
|
|
1852
|
-
function formatRemoteProjectLinkHint(resolution) {
|
|
1853
|
-
const repair = remoteLinkRepairCommand(resolution.repoSlug);
|
|
1854
|
-
if (resolution.status === "auth_required") {
|
|
1855
|
-
return `Authenticate the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}, then run \`${repair}\`. Use \`rig github auth import-gh\` or \`rig init --repair --server remote --github-auth device${resolution.repoSlug ? ` --repo ${resolution.repoSlug}` : ""}\`.`;
|
|
1856
|
-
}
|
|
1857
|
-
if (resolution.status === "missing_project") {
|
|
1858
|
-
return `Record the repo slug, then run \`${repair}\` (or \`rig init --repo owner/repo --server remote --github-auth device\`).`;
|
|
1859
|
-
}
|
|
1860
|
-
if (resolution.status === "not_remote")
|
|
1861
|
-
return "Select a remote server with `rig server use <alias>` before repairing a remote project link.";
|
|
1862
|
-
return `Run \`${repair}\` to backfill or prepare a server-host checkout for the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}.`;
|
|
1863
|
-
}
|
|
1864
|
-
function remoteProjectLinkFailure(input) {
|
|
1865
|
-
const partial = {
|
|
1866
|
-
status: input.status,
|
|
1867
|
-
alias: input.alias,
|
|
1868
|
-
baseUrl: input.baseUrl,
|
|
1869
|
-
repoSlug: input.repoSlug
|
|
1870
|
-
};
|
|
1871
|
-
return {
|
|
1872
|
-
ok: false,
|
|
1873
|
-
...input,
|
|
1874
|
-
hint: formatRemoteProjectLinkHint(partial),
|
|
1875
|
-
next: remoteLinkRepairCommand(input.repoSlug)
|
|
1876
|
-
};
|
|
1877
|
-
}
|
|
1878
|
-
function remoteProjectLinkSuccess(input) {
|
|
1879
|
-
return {
|
|
1880
|
-
ok: true,
|
|
1881
|
-
status: input.status,
|
|
1882
|
-
alias: input.alias,
|
|
1883
|
-
baseUrl: input.baseUrl,
|
|
1884
|
-
repoSlug: input.repoSlug,
|
|
1885
|
-
serverProjectRoot: input.serverProjectRoot,
|
|
1886
|
-
source: input.source,
|
|
1887
|
-
prepared: input.prepared ?? input.status === "prepared",
|
|
1888
|
-
validated: input.validated ?? false,
|
|
1889
|
-
message: input.message ?? `Remote project link ready for ${input.repoSlug}.`,
|
|
1890
|
-
hint: "Remote-scoped task/run/status requests will send x-rig-project-root for this server-host checkout.",
|
|
1891
|
-
next: "rig task list"
|
|
1892
|
-
};
|
|
1893
|
-
}
|
|
1894
|
-
function localCheckoutPathCandidate(projectRoot, candidate) {
|
|
1895
|
-
try {
|
|
1896
|
-
const local = resolve2(projectRoot);
|
|
1897
|
-
const resolved = resolve2(candidate);
|
|
1898
|
-
return resolved === local || resolved.startsWith(`${local}/`);
|
|
1899
|
-
} catch {
|
|
1900
|
-
return false;
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
async function requestRemoteProjectLinkJson(baseUrl, pathname, authToken, init = {}) {
|
|
1904
|
-
const requestUrl = new URL(`${baseUrl}${pathname}`);
|
|
1905
|
-
if (authToken && queryAuthFallbackEnabled())
|
|
1906
|
-
requestUrl.searchParams.set("rt", authToken);
|
|
1907
|
-
const response = await fetch(requestUrl, {
|
|
1908
|
-
...init,
|
|
1909
|
-
headers: mergeHeaders(init.headers, authToken)
|
|
1910
|
-
});
|
|
1911
|
-
const text = await response.text();
|
|
1912
|
-
const payload = text.trim().length > 0 ? (() => {
|
|
1913
|
-
try {
|
|
1914
|
-
return JSON.parse(text);
|
|
1915
|
-
} catch {
|
|
1916
|
-
return null;
|
|
1917
|
-
}
|
|
1918
|
-
})() : null;
|
|
1919
|
-
return { ok: response.ok, status: response.status, payload, text };
|
|
1920
|
-
}
|
|
1921
|
-
function payloadError(payload, fallback) {
|
|
1922
|
-
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
1923
|
-
const record = payload;
|
|
1924
|
-
const value = record.error ?? record.message ?? record.reason;
|
|
1925
|
-
if (typeof value === "string" && value.trim())
|
|
1926
|
-
return value.trim();
|
|
1927
|
-
}
|
|
1928
|
-
return fallback;
|
|
1929
|
-
}
|
|
1930
|
-
function checkoutPathsFromProjectPayload(payload) {
|
|
1931
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1932
|
-
return [];
|
|
1933
|
-
const project = payload.project;
|
|
1934
|
-
if (!project || typeof project !== "object" || Array.isArray(project))
|
|
1935
|
-
return [];
|
|
1936
|
-
const checkouts = project.checkouts;
|
|
1937
|
-
if (!Array.isArray(checkouts))
|
|
1938
|
-
return [];
|
|
1939
|
-
return [...checkouts].reverse().flatMap((entry) => {
|
|
1940
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
1941
|
-
return [];
|
|
1942
|
-
const path = entry.path;
|
|
1943
|
-
return typeof path === "string" && path.trim() ? [path.trim()] : [];
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
function checkoutPathFromPreparePayload(payload) {
|
|
1947
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1948
|
-
return null;
|
|
1949
|
-
const checkout = payload.checkout;
|
|
1950
|
-
if (!checkout || typeof checkout !== "object" || Array.isArray(checkout))
|
|
1951
|
-
return null;
|
|
1952
|
-
const path = checkout.path;
|
|
1953
|
-
return typeof path === "string" && path.trim() ? path.trim() : null;
|
|
1954
|
-
}
|
|
1955
|
-
async function validateAndPersistRemoteRoot(input) {
|
|
1956
|
-
const candidate = input.candidate.trim();
|
|
1957
|
-
if (!candidate || !isAbsolute(candidate)) {
|
|
1958
|
-
return remoteProjectLinkFailure({
|
|
1959
|
-
status: "invalid_root",
|
|
1960
|
-
alias: input.alias,
|
|
1961
|
-
baseUrl: input.baseUrl,
|
|
1962
|
-
repoSlug: input.repoSlug,
|
|
1963
|
-
message: `Remote project root candidate is not an absolute server-host path: ${candidate || "(empty)"}.`
|
|
1964
|
-
});
|
|
1965
|
-
}
|
|
1966
|
-
if (localCheckoutPathCandidate(input.projectRoot, candidate)) {
|
|
1967
|
-
return remoteProjectLinkFailure({
|
|
1968
|
-
status: "invalid_root",
|
|
1969
|
-
alias: input.alias,
|
|
1970
|
-
baseUrl: input.baseUrl,
|
|
1971
|
-
repoSlug: input.repoSlug,
|
|
1972
|
-
message: `Refusing to use local checkout path ${candidate} as a remote server project root.`
|
|
1973
|
-
});
|
|
1974
|
-
}
|
|
1975
|
-
let response;
|
|
1976
|
-
try {
|
|
1977
|
-
response = await requestRemoteProjectLinkJson(input.baseUrl, "/api/server/project-root", input.authToken, {
|
|
1978
|
-
method: "POST",
|
|
1979
|
-
headers: { "content-type": "application/json" },
|
|
1980
|
-
body: JSON.stringify({ projectRoot: candidate })
|
|
1981
|
-
});
|
|
1982
|
-
} catch (error) {
|
|
1983
|
-
return remoteProjectLinkFailure({
|
|
1984
|
-
status: "error",
|
|
1985
|
-
alias: input.alias,
|
|
1986
|
-
baseUrl: input.baseUrl,
|
|
1987
|
-
repoSlug: input.repoSlug,
|
|
1988
|
-
message: `Could not validate remote project root ${candidate}: ${error instanceof Error ? error.message : String(error)}`
|
|
1989
|
-
});
|
|
1990
|
-
}
|
|
1991
|
-
if (response.status === 401 || response.status === 403) {
|
|
1992
|
-
return remoteProjectLinkFailure({
|
|
1993
|
-
status: "auth_required",
|
|
1994
|
-
alias: input.alias,
|
|
1995
|
-
baseUrl: input.baseUrl,
|
|
1996
|
-
repoSlug: input.repoSlug,
|
|
1997
|
-
statusCode: response.status,
|
|
1998
|
-
message: `Remote server ${input.alias} requires authentication before validating project root ${candidate}.`
|
|
1999
|
-
});
|
|
2000
|
-
}
|
|
2001
|
-
if (!response.ok) {
|
|
2002
|
-
return remoteProjectLinkFailure({
|
|
2003
|
-
status: "invalid_root",
|
|
2004
|
-
alias: input.alias,
|
|
2005
|
-
baseUrl: input.baseUrl,
|
|
2006
|
-
repoSlug: input.repoSlug,
|
|
2007
|
-
statusCode: response.status,
|
|
2008
|
-
message: `Remote server rejected project root ${candidate} (${response.status}): ${payloadError(response.payload, response.text || "project-root validation failed")}`
|
|
2009
|
-
});
|
|
2010
|
-
}
|
|
2011
|
-
const record = response.payload && typeof response.payload === "object" && !Array.isArray(response.payload) ? response.payload : {};
|
|
2012
|
-
if (record.ok !== true) {
|
|
2013
|
-
return remoteProjectLinkFailure({
|
|
2014
|
-
status: "invalid_root",
|
|
2015
|
-
alias: input.alias,
|
|
2016
|
-
baseUrl: input.baseUrl,
|
|
2017
|
-
repoSlug: input.repoSlug,
|
|
2018
|
-
message: `Remote server did not accept project root ${candidate}: ${payloadError(response.payload, "project-root validation failed")}`
|
|
2019
|
-
});
|
|
2020
|
-
}
|
|
2021
|
-
const accepted = typeof record.projectRoot === "string" && record.projectRoot.trim() ? record.projectRoot.trim() : typeof record.requestedProjectRoot === "string" && record.requestedProjectRoot.trim() ? record.requestedProjectRoot.trim() : candidate;
|
|
2022
|
-
writeRepoServerProjectRoot(input.projectRoot, accepted, { alias: input.alias, baseUrl: input.baseUrl, project: input.repoSlug });
|
|
2023
|
-
writeProjectLinkConnection(input.projectRoot, input.repoSlug, input.alias);
|
|
2024
|
-
const status = input.status ?? (input.prepared ? "prepared" : "backfilled");
|
|
2025
|
-
return remoteProjectLinkSuccess({
|
|
2026
|
-
status,
|
|
2027
|
-
alias: input.alias,
|
|
2028
|
-
baseUrl: input.baseUrl,
|
|
2029
|
-
repoSlug: input.repoSlug,
|
|
2030
|
-
serverProjectRoot: accepted,
|
|
2031
|
-
source: input.source,
|
|
2032
|
-
prepared: input.prepared,
|
|
2033
|
-
validated: true,
|
|
2034
|
-
message: status === "ready" ? `Remote project link already points to ${accepted} and was validated on ${input.alias}.` : input.prepared ? `Prepared and linked remote checkout ${accepted} for ${input.repoSlug}.` : `Backfilled remote checkout ${accepted} for ${input.repoSlug}.`
|
|
2035
|
-
});
|
|
2036
|
-
}
|
|
2037
|
-
async function ensureRemoteProjectRootLink(projectRoot, options = {}) {
|
|
2038
|
-
let selected;
|
|
2039
|
-
try {
|
|
2040
|
-
selected = resolveSelectedConnection(projectRoot);
|
|
2041
|
-
} catch (error) {
|
|
2042
|
-
return remoteProjectLinkFailure({ status: "error", message: error instanceof Error ? error.message : String(error) });
|
|
2043
|
-
}
|
|
2044
|
-
if (!selected || selected.connection.kind !== "remote") {
|
|
2045
|
-
return remoteProjectLinkFailure({ status: "not_remote", message: "No remote Rig server is selected for this repo." });
|
|
2046
|
-
}
|
|
2047
|
-
const repo = readRepoConnection(projectRoot);
|
|
2048
|
-
const explicitRepoRequested = options.repoSlug !== undefined;
|
|
2049
|
-
const explicitRepoSlug = explicitRepoRequested ? normalizeRepoSlug(options.repoSlug) : null;
|
|
2050
|
-
const repoProjectSlug = normalizeRepoSlug(repo?.project);
|
|
2051
|
-
const repoSlug = explicitRepoSlug ?? repoProjectSlug ?? readProjectLinkSlug(projectRoot);
|
|
2052
|
-
const alias = selected.alias;
|
|
2053
|
-
const baseUrl = selected.connection.baseUrl;
|
|
2054
|
-
const authToken = options.authToken === undefined ? readGitHubBearerTokenForRemote(projectRoot) : options.authToken;
|
|
2055
|
-
const mode = options.mode ?? "backfill-only";
|
|
2056
|
-
if (explicitRepoRequested && !explicitRepoSlug) {
|
|
2057
|
-
return remoteProjectLinkFailure({
|
|
2058
|
-
status: "missing_project",
|
|
2059
|
-
alias,
|
|
2060
|
-
baseUrl,
|
|
2061
|
-
message: `Invalid --repo value ${JSON.stringify(options.repoSlug)}. Expected owner/repo.`
|
|
2062
|
-
});
|
|
2063
|
-
}
|
|
2064
|
-
if (!repoSlug) {
|
|
2065
|
-
return remoteProjectLinkFailure({
|
|
2066
|
-
status: "missing_project",
|
|
2067
|
-
alias,
|
|
2068
|
-
baseUrl,
|
|
2069
|
-
message: `Remote server ${alias} is selected, but this checkout has no GitHub repo slug recorded.`
|
|
2070
|
-
});
|
|
2071
|
-
}
|
|
2072
|
-
const skippedCandidates = [];
|
|
2073
|
-
const storedRoot = selected.serverProjectRoot?.trim() || null;
|
|
2074
|
-
const storedRootMatchesResolvedRepo = repoProjectSlug === repoSlug;
|
|
2075
|
-
if (storedRoot && storedRootMatchesResolvedRepo) {
|
|
2076
|
-
if (localCheckoutPathCandidate(projectRoot, storedRoot)) {
|
|
2077
|
-
clearRepoServerProjectRoot(projectRoot);
|
|
2078
|
-
skippedCandidates.push(storedRoot);
|
|
2079
|
-
} else {
|
|
2080
|
-
const storedResult = await validateAndPersistRemoteRoot({
|
|
2081
|
-
projectRoot,
|
|
2082
|
-
alias,
|
|
2083
|
-
baseUrl,
|
|
2084
|
-
authToken,
|
|
2085
|
-
repoSlug,
|
|
2086
|
-
candidate: storedRoot,
|
|
2087
|
-
source: "stored",
|
|
2088
|
-
status: "ready"
|
|
2089
|
-
});
|
|
2090
|
-
if (storedResult.ok || storedResult.status === "auth_required" || storedResult.status === "error")
|
|
2091
|
-
return storedResult;
|
|
2092
|
-
clearRepoServerProjectRoot(projectRoot);
|
|
2093
|
-
skippedCandidates.push(storedRoot);
|
|
2094
|
-
}
|
|
2095
|
-
} else if (storedRoot) {
|
|
2096
|
-
skippedCandidates.push(storedRoot);
|
|
2097
|
-
}
|
|
2098
|
-
const authCandidate = inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug);
|
|
2099
|
-
if (authCandidate) {
|
|
2100
|
-
const authResult = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: authCandidate, source: "auth-state" });
|
|
2101
|
-
if (authResult.ok || authResult.status === "auth_required")
|
|
2102
|
-
return authResult;
|
|
2103
|
-
}
|
|
2104
|
-
let registryResponse;
|
|
2105
|
-
try {
|
|
2106
|
-
registryResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}`, authToken);
|
|
2107
|
-
} catch (error) {
|
|
2108
|
-
return remoteProjectLinkFailure({
|
|
2109
|
-
status: "error",
|
|
2110
|
-
alias,
|
|
2111
|
-
baseUrl,
|
|
2112
|
-
repoSlug,
|
|
2113
|
-
message: `Could not read remote project registry for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
|
|
2114
|
-
});
|
|
2115
|
-
}
|
|
2116
|
-
if (registryResponse.status === 401 || registryResponse.status === 403) {
|
|
2117
|
-
return remoteProjectLinkFailure({
|
|
2118
|
-
status: "auth_required",
|
|
2119
|
-
alias,
|
|
2120
|
-
baseUrl,
|
|
2121
|
-
repoSlug,
|
|
2122
|
-
statusCode: registryResponse.status,
|
|
2123
|
-
message: `Remote server ${alias} requires authentication before reading the ${repoSlug} project registry.`
|
|
2124
|
-
});
|
|
2125
|
-
}
|
|
2126
|
-
if (registryResponse.ok) {
|
|
2127
|
-
const candidates = checkoutPathsFromProjectPayload(registryResponse.payload);
|
|
2128
|
-
for (const candidate of candidates) {
|
|
2129
|
-
const result = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate, source: "registry" });
|
|
2130
|
-
if (result.ok || result.status === "auth_required")
|
|
2131
|
-
return result;
|
|
2132
|
-
skippedCandidates.push(candidate);
|
|
2133
|
-
}
|
|
2134
|
-
if (mode === "backfill-only") {
|
|
2135
|
-
return remoteProjectLinkFailure({
|
|
2136
|
-
status: candidates.length > 0 ? "invalid_root" : "no_server_checkout",
|
|
2137
|
-
alias,
|
|
2138
|
-
baseUrl,
|
|
2139
|
-
repoSlug,
|
|
2140
|
-
message: candidates.length > 0 ? `Remote registry has checkout candidates for ${repoSlug}, but none validated for ${alias}.` : `Remote registry has ${repoSlug}, but no server checkout path is linked yet.`,
|
|
2141
|
-
skippedCandidates
|
|
2142
|
-
});
|
|
2143
|
-
}
|
|
2144
|
-
} else if (registryResponse.status === 404) {
|
|
2145
|
-
if (mode === "backfill-only") {
|
|
2146
|
-
return remoteProjectLinkFailure({
|
|
2147
|
-
status: "project_not_registered",
|
|
2148
|
-
alias,
|
|
2149
|
-
baseUrl,
|
|
2150
|
-
repoSlug,
|
|
2151
|
-
statusCode: registryResponse.status,
|
|
2152
|
-
message: `Remote server ${alias} has no registered project record for ${repoSlug}.`
|
|
2153
|
-
});
|
|
2154
|
-
}
|
|
2155
|
-
} else {
|
|
2156
|
-
return remoteProjectLinkFailure({
|
|
2157
|
-
status: "error",
|
|
2158
|
-
alias,
|
|
2159
|
-
baseUrl,
|
|
2160
|
-
repoSlug,
|
|
2161
|
-
statusCode: registryResponse.status,
|
|
2162
|
-
message: `Could not backfill ${repoSlug} from remote registry (${registryResponse.status}): ${payloadError(registryResponse.payload, registryResponse.text || "registry lookup failed")}`,
|
|
2163
|
-
skippedCandidates
|
|
2164
|
-
});
|
|
2165
|
-
}
|
|
2166
|
-
let prepareResponse;
|
|
2167
|
-
try {
|
|
2168
|
-
prepareResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}/prepare-checkout`, authToken, {
|
|
2169
|
-
method: "POST",
|
|
2170
|
-
headers: { "content-type": "application/json" },
|
|
2171
|
-
body: JSON.stringify({ checkout: { kind: "managed-clone" }, repoUrl: `https://github.com/${repoSlug}.git` })
|
|
2172
|
-
});
|
|
2173
|
-
} catch (error) {
|
|
2174
|
-
return remoteProjectLinkFailure({
|
|
2175
|
-
status: "error",
|
|
2176
|
-
alias,
|
|
2177
|
-
baseUrl,
|
|
2178
|
-
repoSlug,
|
|
2179
|
-
message: `Could not prepare remote checkout for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
|
|
2180
|
-
});
|
|
2181
|
-
}
|
|
2182
|
-
if (prepareResponse.status === 401 || prepareResponse.status === 403) {
|
|
2183
|
-
return remoteProjectLinkFailure({
|
|
2184
|
-
status: "auth_required",
|
|
2185
|
-
alias,
|
|
2186
|
-
baseUrl,
|
|
2187
|
-
repoSlug,
|
|
2188
|
-
statusCode: prepareResponse.status,
|
|
2189
|
-
message: `Remote server ${alias} requires authentication before preparing ${repoSlug}.`
|
|
2190
|
-
});
|
|
2191
|
-
}
|
|
2192
|
-
if (!prepareResponse.ok) {
|
|
2193
|
-
return remoteProjectLinkFailure({
|
|
2194
|
-
status: "error",
|
|
2195
|
-
alias,
|
|
2196
|
-
baseUrl,
|
|
2197
|
-
repoSlug,
|
|
2198
|
-
statusCode: prepareResponse.status,
|
|
2199
|
-
message: `Remote checkout prepare failed for ${repoSlug} (${prepareResponse.status}): ${payloadError(prepareResponse.payload, prepareResponse.text || "prepare-checkout failed")}`,
|
|
2200
|
-
skippedCandidates
|
|
2201
|
-
});
|
|
2202
|
-
}
|
|
2203
|
-
const preparedPath = checkoutPathFromPreparePayload(prepareResponse.payload);
|
|
2204
|
-
if (!preparedPath) {
|
|
2205
|
-
return remoteProjectLinkFailure({
|
|
2206
|
-
status: "invalid_root",
|
|
2207
|
-
alias,
|
|
2208
|
-
baseUrl,
|
|
2209
|
-
repoSlug,
|
|
2210
|
-
message: `Remote checkout prepare for ${repoSlug} did not return a checkout.path.`,
|
|
2211
|
-
skippedCandidates
|
|
2212
|
-
});
|
|
2213
|
-
}
|
|
2214
|
-
return validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: preparedPath, source: "prepare", prepared: true });
|
|
2215
|
-
}
|
|
2216
|
-
async function ensureServerForCli(projectRoot) {
|
|
2217
|
-
try {
|
|
2218
|
-
const selected = resolveSelectedConnection(projectRoot);
|
|
2219
|
-
if (selected?.connection.kind === "remote") {
|
|
2220
|
-
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
2221
|
-
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
2222
|
-
const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
|
|
2223
|
-
return {
|
|
2224
|
-
baseUrl: selected.connection.baseUrl,
|
|
2225
|
-
authToken,
|
|
2226
|
-
connectionKind: "remote",
|
|
2227
|
-
serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
|
|
2228
|
-
};
|
|
2229
|
-
}
|
|
2230
|
-
reportServerPhase("Starting local Rig server\u2026");
|
|
2231
|
-
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
2232
|
-
return {
|
|
2233
|
-
baseUrl: connection.baseUrl,
|
|
2234
|
-
authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
|
|
2235
|
-
connectionKind: "local",
|
|
2236
|
-
serverProjectRoot: resolve2(projectRoot)
|
|
2237
|
-
};
|
|
2238
|
-
} catch (error) {
|
|
2239
|
-
if (error instanceof Error) {
|
|
2240
|
-
throw new CliError(error.message, 1);
|
|
2241
|
-
}
|
|
2242
|
-
throw error;
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
function mergeCookie(existing, name, value) {
|
|
2246
|
-
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
2247
|
-
if (!existing?.trim())
|
|
2248
|
-
return encoded;
|
|
2249
|
-
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
2250
|
-
return [...parts, encoded].join("; ");
|
|
2251
|
-
}
|
|
2252
|
-
function queryAuthFallbackEnabled(env = process.env) {
|
|
2253
|
-
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
2254
|
-
}
|
|
2255
|
-
function mergeHeaders(headers, authToken) {
|
|
2256
|
-
const merged = new Headers(headers);
|
|
2257
|
-
if (authToken) {
|
|
2258
|
-
const bearer = `Bearer ${authToken}`;
|
|
2259
|
-
merged.set("authorization", bearer);
|
|
2260
|
-
merged.set("x-auth", bearer);
|
|
2261
|
-
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
2262
|
-
}
|
|
2263
|
-
return merged;
|
|
2264
|
-
}
|
|
2265
|
-
var serverReachabilityCache = new Map;
|
|
2266
|
-
var RESUMABLE_RUN_STATUSES = new Set([
|
|
2267
|
-
"created",
|
|
2268
|
-
"preparing",
|
|
2269
|
-
"running",
|
|
2270
|
-
"validating",
|
|
2271
|
-
"reviewing",
|
|
2272
|
-
"stopped",
|
|
2273
|
-
"failed",
|
|
2274
|
-
"needs-attention",
|
|
2275
|
-
"needs_attention"
|
|
2276
|
-
]);
|
|
2277
|
-
|
|
2278
|
-
// packages/cli/src/commands/_server-events.ts
|
|
2279
|
-
function buildRigWebSocketUrl(baseUrl, authToken) {
|
|
2280
|
-
const url = new URL(baseUrl);
|
|
2281
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
2282
|
-
if (authToken)
|
|
2283
|
-
url.searchParams.set("token", authToken);
|
|
2284
|
-
return url.toString();
|
|
2285
|
-
}
|
|
2286
|
-
async function connectRigServerEvents(projectRoot, handlers) {
|
|
2287
|
-
const { baseUrl, authToken } = await ensureServerForCli(projectRoot);
|
|
2288
|
-
const transport = new WsTransport(buildRigWebSocketUrl(baseUrl, authToken));
|
|
2289
|
-
const unsubscribers = [];
|
|
2290
|
-
if (handlers.onSnapshotInvalidated)
|
|
2291
|
-
unsubscribers.push(transport.subscribe(RIG_WS_CHANNELS.snapshotInvalidated, handlers.onSnapshotInvalidated));
|
|
2292
|
-
if (handlers.onRunLogAppended)
|
|
2293
|
-
unsubscribers.push(transport.subscribe(RIG_WS_CHANNELS.runLogAppended, handlers.onRunLogAppended));
|
|
2294
|
-
if (handlers.onEngineEvent)
|
|
2295
|
-
unsubscribers.push(transport.subscribe(RIG_WS_CHANNELS.event, handlers.onEngineEvent));
|
|
2296
|
-
let status = "connecting";
|
|
2297
|
-
unsubscribers.push(transport.subscribeState((state) => {
|
|
2298
|
-
status = state.status;
|
|
2299
|
-
handlers.onStatus?.(state.status);
|
|
2300
|
-
}));
|
|
2301
|
-
return {
|
|
2302
|
-
connected: () => status === "connected",
|
|
2303
|
-
close: () => {
|
|
2304
|
-
for (const unsubscribe of unsubscribers) {
|
|
2305
|
-
try {
|
|
2306
|
-
unsubscribe();
|
|
2307
|
-
} catch {}
|
|
2308
|
-
}
|
|
2309
|
-
transport.dispose();
|
|
2310
|
-
}
|
|
2311
|
-
};
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
// packages/cli/src/app-opentui/theme.ts
|
|
2315
|
-
import {
|
|
2316
|
-
bold as otuiBold,
|
|
2317
|
-
dim as otuiDim,
|
|
2318
|
-
fg as otuiFg,
|
|
2319
|
-
t,
|
|
2320
|
-
TextAttributes
|
|
2321
|
-
} from "@opentui/core";
|
|
2322
|
-
var RIG_UI = {
|
|
2323
|
-
bg: "#070809",
|
|
2324
|
-
bg2: "#0b0c0e",
|
|
2325
|
-
panel: "#101115",
|
|
2326
|
-
panel2: "#14161b",
|
|
2327
|
-
hover: "#181b24",
|
|
2328
|
-
glass: "#1e2230",
|
|
2329
|
-
border: "#2a2e3a",
|
|
2330
|
-
ink: "#f4f5f8",
|
|
2331
|
-
ink2: "#c7c9d3",
|
|
2332
|
-
ink3: "#9aa0ae",
|
|
2333
|
-
ink4: "#787b86",
|
|
2334
|
-
lime: "#ccff4d",
|
|
2335
|
-
limeDim: "#a9d63f",
|
|
2336
|
-
cyan: "#56d8ff",
|
|
2337
|
-
cyanDim: "#3f9fbd",
|
|
2338
|
-
red: "#ff5d5d",
|
|
2339
|
-
yellow: "#ffd24d",
|
|
2340
|
-
magenta: "#ff79b0"
|
|
2341
|
-
};
|
|
2342
|
-
var styles = {
|
|
2343
|
-
ink: otuiFg(RIG_UI.ink),
|
|
2344
|
-
ink2: otuiFg(RIG_UI.ink2),
|
|
2345
|
-
ink3: otuiFg(RIG_UI.ink3),
|
|
2346
|
-
ink4: otuiFg(RIG_UI.ink4),
|
|
2347
|
-
lime: otuiFg(RIG_UI.lime),
|
|
2348
|
-
limeDim: otuiFg(RIG_UI.limeDim),
|
|
2349
|
-
cyan: otuiFg(RIG_UI.cyan),
|
|
2350
|
-
red: otuiFg(RIG_UI.red),
|
|
2351
|
-
yellow: otuiFg(RIG_UI.yellow),
|
|
2352
|
-
magenta: otuiFg(RIG_UI.magenta)
|
|
2353
|
-
};
|
|
2354
|
-
|
|
2355
|
-
// packages/cli/src/app-opentui/drone.ts
|
|
2356
|
-
import { RGBA, StyledText, TextAttributes as TextAttributes2 } from "@opentui/core";
|
|
2357
|
-
var MINI_DRONE = [
|
|
2358
|
-
"(!!!) (!!!)",
|
|
2359
|
-
" \\%==%/ ",
|
|
2360
|
-
" %%?%% ",
|
|
2361
|
-
" /%==%\\ ",
|
|
2362
|
-
"(!!!) (!!!)"
|
|
2363
|
-
];
|
|
2364
|
-
var LEAD_MARK = [
|
|
2365
|
-
" .-=-. ",
|
|
2366
|
-
"=%%?%%=",
|
|
2367
|
-
" '-=-' "
|
|
2368
|
-
];
|
|
2369
|
-
var BLADE_FRAMES = ["---", "\\\\\\", "|||", "///"];
|
|
2370
|
-
var EYE_FRAMES = ["o", "@", "\u2022", "."];
|
|
2371
|
-
var COLOR = {
|
|
2372
|
-
body: RGBA.fromHex(RIG_UI.lime),
|
|
2373
|
-
mini: RGBA.fromHex(RIG_UI.limeDim),
|
|
2374
|
-
rotor: RGBA.fromHex(RIG_UI.cyan),
|
|
2375
|
-
path: RGBA.fromHex(RIG_UI.cyan),
|
|
2376
|
-
eye: RGBA.fromHex(RIG_UI.ink),
|
|
2377
|
-
dim: RGBA.fromHex(RIG_UI.ink4),
|
|
2378
|
-
ink: RGBA.fromHex(RIG_UI.ink2)
|
|
2379
|
-
};
|
|
2380
|
-
function bladeForTick(tick, phase = 0) {
|
|
2381
|
-
return BLADE_FRAMES[(Math.floor(tick / 3) + phase) % BLADE_FRAMES.length];
|
|
2382
|
-
}
|
|
2383
|
-
function eyeForTick(tick, phase = 0) {
|
|
2384
|
-
const pulse = Math.sin(tick * 0.09 + phase);
|
|
2385
|
-
return pulse > 0.55 ? EYE_FRAMES[1] : pulse > 0.1 ? EYE_FRAMES[0] : pulse > -0.45 ? EYE_FRAMES[2] : EYE_FRAMES[3];
|
|
2386
|
-
}
|
|
2387
|
-
function chunk(text, fg, bold = false, dim = false) {
|
|
2388
|
-
let attributes = TextAttributes2.NONE;
|
|
2389
|
-
if (bold)
|
|
2390
|
-
attributes |= TextAttributes2.BOLD;
|
|
2391
|
-
if (dim)
|
|
2392
|
-
attributes |= TextAttributes2.DIM;
|
|
2393
|
-
return { __isChunk: true, text, fg, ...attributes !== TextAttributes2.NONE ? { attributes } : {} };
|
|
2394
|
-
}
|
|
2395
|
-
function styledLine(text, colorFor) {
|
|
2396
|
-
const chunks = [];
|
|
2397
|
-
let run = "";
|
|
2398
|
-
let runColor = null;
|
|
2399
|
-
const flush = () => {
|
|
2400
|
-
if (!run || !runColor)
|
|
2401
|
-
return;
|
|
2402
|
-
chunks.push(chunk(run, COLOR[runColor], runColor === "body" || runColor === "eye", runColor === "dim"));
|
|
2403
|
-
run = "";
|
|
2404
|
-
};
|
|
2405
|
-
for (const char of text) {
|
|
2406
|
-
const next = colorFor(char);
|
|
2407
|
-
if (next !== runColor) {
|
|
2408
|
-
flush();
|
|
2409
|
-
runColor = next;
|
|
2410
|
-
}
|
|
2411
|
-
run += char;
|
|
2412
|
-
}
|
|
2413
|
-
flush();
|
|
2414
|
-
return new StyledText(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
|
|
2415
|
-
}
|
|
2416
|
-
function droneColor(char) {
|
|
2417
|
-
if (char === "?" || char === "o" || char === "@" || char === "\u2022")
|
|
2418
|
-
return "eye";
|
|
2419
|
-
if (char === "%" || char === "=")
|
|
2420
|
-
return "mini";
|
|
2421
|
-
if (char === "$")
|
|
2422
|
-
return "body";
|
|
2423
|
-
if (char === "(" || char === ")" || char === "/" || char === "\\" || char === "|" || char === "-")
|
|
2424
|
-
return "rotor";
|
|
2425
|
-
if (char === "\xB7" || char === "\u2022" || char === "'" || char === ".")
|
|
2426
|
-
return "path";
|
|
2427
|
-
if (char.trim() === "")
|
|
2428
|
-
return "dim";
|
|
2429
|
-
return "ink";
|
|
2430
|
-
}
|
|
2431
|
-
function motionColor(char) {
|
|
2432
|
-
if (char === "\u25CF" || char === "\u25C6" || char === "\u25C9")
|
|
2433
|
-
return "body";
|
|
2434
|
-
if (char === "\u2022" || char === "\xB7" || char === "\u2500" || char === "\u2502" || char === "\u2571" || char === "\u2572" || char === "\u256D" || char === "\u256E" || char === "\u2570" || char === "\u256F")
|
|
2435
|
-
return "path";
|
|
2436
|
-
if (char === "\u258C" || char === "\u2590" || char === "\u2581" || char === "\u2594")
|
|
2437
|
-
return "mini";
|
|
2438
|
-
return droneColor(char);
|
|
2439
|
-
}
|
|
2440
|
-
function materializeDrone(lines, tick, phase = 0) {
|
|
2441
|
-
const blade = bladeForTick(tick, phase);
|
|
2442
|
-
const eye = eyeForTick(tick, phase);
|
|
2443
|
-
return lines.map((line) => line.replaceAll("!!!", blade).replaceAll("?", eye));
|
|
2444
|
-
}
|
|
2445
|
-
function brandFleetLines(tick, align = "center") {
|
|
2446
|
-
const left = materializeDrone(MINI_DRONE, tick, 0);
|
|
2447
|
-
const right = materializeDrone(MINI_DRONE, tick, 2);
|
|
2448
|
-
const lead = materializeDrone(LEAD_MARK, tick, 1);
|
|
2449
|
-
const packet = ["\xB7", "\u2022", "\u25CF", "\u2022"][Math.floor(tick / 2) % 4];
|
|
2450
|
-
const bridge = [
|
|
2451
|
-
` ${left[0]} ${lead[0]} ${right[0]}`,
|
|
2452
|
-
` ${left[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${lead[1]} \xB7\u2500\u2500${packet}\u2500\u2500\xB7 ${right[1]}`,
|
|
2453
|
-
` ${left[2]} \xB7' '${lead[2]}' '\xB7 ${right[2]}`,
|
|
2454
|
-
` ${left[3]} \u2572 signal bus \u2571 ${right[3]}`,
|
|
2455
|
-
` ${left[4]} \xB7\u2500\u2500\u2500${packet}\u2500\u2500\u2500\u2500\u2500\u2500${packet}\u2500\u2500\u2500\xB7 ${right[4]}`
|
|
2456
|
-
];
|
|
2457
|
-
return bridge.map((text) => ({
|
|
2458
|
-
text,
|
|
2459
|
-
styledText: styledLine(text, motionColor),
|
|
2460
|
-
fg: RIG_UI.limeDim,
|
|
2461
|
-
align
|
|
2462
|
-
}));
|
|
2463
|
-
}
|
|
2464
|
-
function errorDrone(tick) {
|
|
2465
|
-
return brandFleetLines(tick).map((line, index) => ({
|
|
2466
|
-
...line,
|
|
2467
|
-
fg: index % 2 === 0 ? RIG_UI.red : line.fg
|
|
2468
|
-
}));
|
|
2469
|
-
}
|
|
2470
|
-
|
|
2471
|
-
// packages/cli/src/app-opentui/render/scene.ts
|
|
2472
|
-
function line(text, options = {}) {
|
|
2473
|
-
return { text, ...options };
|
|
2474
|
-
}
|
|
2475
|
-
function blank() {
|
|
2476
|
-
return { text: "" };
|
|
2477
|
-
}
|
|
2478
|
-
function deckRow(input) {
|
|
2479
|
-
const prefix = input.active ? "\u25B8" : " ";
|
|
2480
|
-
const label = input.label.toUpperCase().padEnd(10);
|
|
2481
|
-
return line(`${prefix} ${label} ${input.detail}`, {
|
|
2482
|
-
fg: input.active ? RIG_UI.lime : RIG_UI.ink2,
|
|
2483
|
-
bold: input.active,
|
|
2484
|
-
selectableIndex: input.index,
|
|
2485
|
-
activateOnClick: input.activateOnClick
|
|
2486
|
-
});
|
|
2487
|
-
}
|
|
2488
|
-
function kv(label, value, fg = RIG_UI.ink2) {
|
|
2489
|
-
return line(` ${label.toUpperCase().padEnd(10)} ${value}`, { fg });
|
|
2490
|
-
}
|
|
2491
|
-
function fitSceneLine(input, layout) {
|
|
2492
|
-
const fitted = truncateText(input.text, layout.centerWidth);
|
|
2493
|
-
return {
|
|
2494
|
-
...input,
|
|
2495
|
-
text: alignText(fitted, layout.centerWidth, input.align ?? "left")
|
|
2496
|
-
};
|
|
2497
|
-
}
|
|
2498
|
-
function fitSceneLines(lines, layout) {
|
|
2499
|
-
return lines.slice(0, layout.centerHeight).map((entry) => fitSceneLine(entry, layout));
|
|
2500
|
-
}
|
|
2501
|
-
function makeSceneFrame(input) {
|
|
2502
|
-
return input;
|
|
2503
|
-
}
|
|
2504
|
-
|
|
2505
|
-
// packages/cli/src/app-opentui/render/graphics.ts
|
|
2506
|
-
import { FrameBufferRenderable, RGBA as RGBA2 } from "@opentui/core";
|
|
2507
|
-
|
|
2508
|
-
// packages/cli/src/app-opentui/render/ascii-fleet.ts
|
|
2509
|
-
var LEAD_DRONE = [
|
|
2510
|
-
" .-=-. .-=-. ",
|
|
2511
|
-
" ( !!! ) ( !!! ) ",
|
|
2512
|
-
" '-=-'._ _.'-=-' ",
|
|
2513
|
-
" '._ _.' ",
|
|
2514
|
-
" '=$$$$$$$=.' ",
|
|
2515
|
-
" =$$$$$$$$$$$= ",
|
|
2516
|
-
" $$$@@@@@@@@@@$$$ ",
|
|
2517
|
-
" $$$@@ @@$$$ ",
|
|
2518
|
-
" $$@ ? @$$$ ",
|
|
2519
|
-
" $$$@ '-' @$$$ ",
|
|
2520
|
-
" $$$@@ @@$$$ ",
|
|
2521
|
-
" $$$@@@@@@@@@@$$$ ",
|
|
2522
|
-
" =$$$$$$$$$$$= ",
|
|
2523
|
-
" '=$$$$$$$=.' ",
|
|
2524
|
-
" _.' '._ ",
|
|
2525
|
-
" .-=-.' '.-=-. ",
|
|
2526
|
-
" ( !!! ) ( !!! ) ",
|
|
2527
|
-
" '-=-' '-=-' "
|
|
2528
|
-
];
|
|
2529
|
-
var MINI_DRONE2 = [
|
|
2530
|
-
"(!!!) (!!!)",
|
|
2531
|
-
" \\%==%/ ",
|
|
2532
|
-
" %%?%% ",
|
|
2533
|
-
" /%==%\\ ",
|
|
2534
|
-
"(!!!) (!!!)"
|
|
2535
|
-
];
|
|
2536
|
-
var ROTORS = ["---", "\\\\\\", "|||", "///"];
|
|
2537
|
-
var FLEET_GRID_WIDTH = 52;
|
|
2538
|
-
var FLEET_GRID_HEIGHT = 27;
|
|
2539
|
-
var FLEET = [
|
|
2540
|
-
{ art: MINI_DRONE2, x: 0, y: 2, amp: 1, speed: 0.05, phase: 0, dx: 1, driftSpeed: 0.02 },
|
|
2541
|
-
{ art: MINI_DRONE2, x: 39, y: 2, amp: 1, speed: 0.044, phase: 2.1, dx: 1, driftSpeed: 0.017 },
|
|
2542
|
-
{ art: MINI_DRONE2, x: 1, y: 20, amp: 1, speed: 0.048, phase: 4.2, dx: 1, driftSpeed: 0.023 },
|
|
2543
|
-
{ art: MINI_DRONE2, x: 38, y: 20, amp: 1, speed: 0.041, phase: 1.3, dx: 1, driftSpeed: 0.015 },
|
|
2544
|
-
{ art: LEAD_DRONE, x: 10, y: 5, amp: 2, speed: 0.04, phase: 0, dx: 0, driftSpeed: 0.011 }
|
|
2545
|
-
];
|
|
2546
|
-
function fleetRows(tick, options = {}) {
|
|
2547
|
-
const animate = options.animate ?? true;
|
|
2548
|
-
const t2 = animate ? tick : 0;
|
|
2549
|
-
const blade = ROTORS[Math.floor(t2 / 4) % ROTORS.length];
|
|
2550
|
-
const pulse = Math.sin(t2 * 0.07);
|
|
2551
|
-
const eye = pulse > 0.45 ? "@" : pulse > -0.1 ? "o" : ".";
|
|
2552
|
-
const grid = Array.from({ length: FLEET_GRID_HEIGHT }, () => Array.from({ length: FLEET_GRID_WIDTH }, () => " "));
|
|
2553
|
-
for (const drone of FLEET) {
|
|
2554
|
-
const yOffset = drone.y + Math.round(drone.amp * Math.sin(t2 * drone.speed + drone.phase));
|
|
2555
|
-
const xOffset = drone.x + Math.round(drone.dx * Math.sin(t2 * drone.driftSpeed + drone.phase));
|
|
2556
|
-
for (let row = 0;row < drone.art.length; row += 1) {
|
|
2557
|
-
const source = drone.art[row];
|
|
2558
|
-
const targetY = yOffset + row;
|
|
2559
|
-
if (targetY < 0 || targetY >= FLEET_GRID_HEIGHT)
|
|
2560
|
-
continue;
|
|
2561
|
-
for (let col = 0;col < source.length; col += 1) {
|
|
2562
|
-
let char = source[col];
|
|
2563
|
-
if (char === " ")
|
|
2564
|
-
continue;
|
|
2565
|
-
const targetX = xOffset + col;
|
|
2566
|
-
if (targetX < 0 || targetX >= FLEET_GRID_WIDTH)
|
|
2567
|
-
continue;
|
|
2568
|
-
if (source.slice(col, col + 3) === "!!!") {
|
|
2569
|
-
for (let rotorIndex = 0;rotorIndex < 3; rotorIndex += 1) {
|
|
2570
|
-
if (targetX + rotorIndex < FLEET_GRID_WIDTH)
|
|
2571
|
-
grid[targetY][targetX + rotorIndex] = blade[rotorIndex];
|
|
2572
|
-
}
|
|
2573
|
-
col += 2;
|
|
2574
|
-
continue;
|
|
2575
|
-
}
|
|
2576
|
-
if (char === "?")
|
|
2577
|
-
char = eye;
|
|
2578
|
-
grid[targetY][targetX] = char;
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
return grid.map((row) => row.join("").replace(/\s+$/, ""));
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
// packages/cli/src/app-opentui/render/panel-layout.ts
|
|
2586
|
-
function panelCellRect(layout, panel) {
|
|
2587
|
-
const left = Math.max(0, Math.min(layout.width - 1, layout.centerLeft + (panel.left ?? 0)));
|
|
2588
|
-
const desiredWidth = panel.width ?? layout.centerWidth;
|
|
2589
|
-
const width = Math.max(1, Math.min(layout.width - left, desiredWidth));
|
|
2590
|
-
const top = Math.max(0, Math.min(layout.height - 1, layout.centerTop + panel.top));
|
|
2591
|
-
const height = Math.max(1, Math.min(layout.height - top, panel.height));
|
|
2592
|
-
if (width <= 3 || height <= 3)
|
|
2593
|
-
return null;
|
|
2594
|
-
const right = left + width - 1;
|
|
2595
|
-
const bottom = top + height - 1;
|
|
2596
|
-
const headerHeight = panel.chrome === "ad-terminal" ? Math.max(3, Math.min(height - 2, panel.headerHeight ?? 3)) : 0;
|
|
2597
|
-
const dividerY = headerHeight ? Math.min(bottom - 1, top + headerHeight) : top;
|
|
2598
|
-
return { left, top, width, height, right, bottom, headerHeight, dividerY };
|
|
2599
|
-
}
|
|
2600
|
-
function panelContentRect(layout, panel) {
|
|
2601
|
-
const rect = panelCellRect(layout, panel);
|
|
2602
|
-
if (!rect)
|
|
2603
|
-
return null;
|
|
2604
|
-
const chromeInset = panel.chrome === "ad-terminal" ? 1 : 0;
|
|
2605
|
-
const left = rect.left + chromeInset;
|
|
2606
|
-
const top = rect.top + rect.headerHeight + chromeInset;
|
|
2607
|
-
const width = Math.max(1, Math.min(layout.width - left, rect.width - chromeInset * 2));
|
|
2608
|
-
const height = Math.max(1, Math.min(layout.height - top, rect.height - rect.headerHeight - chromeInset * 2));
|
|
2609
|
-
const paddingX = panel.paddingX ?? (panel.chrome === "ad-terminal" ? 5 : 2);
|
|
2610
|
-
const paddingY = panel.paddingY ?? (panel.chrome === "ad-terminal" ? 2 : 1);
|
|
2611
|
-
const contentWidth = Math.max(1, width - paddingX * 2 - 1);
|
|
2612
|
-
return { left, top, width, height, paddingX, paddingY, contentWidth };
|
|
2613
|
-
}
|
|
2614
|
-
|
|
2615
|
-
// packages/cli/src/app-opentui/render/constants.ts
|
|
2616
|
-
var LCG_MULTIPLIER = 1664525;
|
|
2617
|
-
var LCG_INCREMENT = 1013904223;
|
|
2618
|
-
var LCG_DIVISOR = 4294967295;
|
|
2619
|
-
var PACKET_CYAN_PROBABILITY = 0.22;
|
|
2620
|
-
var LANE_INTERP_STEPS = 32;
|
|
2621
|
-
var LANE_LEN_BASE = 0.32;
|
|
2622
|
-
var LANE_LEN_SPREAD = 0.42;
|
|
2623
|
-
var LANE_CURVE_RANGE = 0.7;
|
|
2624
|
-
var PACKET_SPEED_BASE = 0.003;
|
|
2625
|
-
var PACKET_SPEED_SPREAD = 0.009;
|
|
2626
|
-
var DISPATCH_LANES_COMPACT = 7;
|
|
2627
|
-
var DISPATCH_LANES_WIDE = 12;
|
|
2628
|
-
var DISPATCH_PACKETS_COMPACT = 70;
|
|
2629
|
-
var DISPATCH_PACKETS_WIDE = 150;
|
|
2630
|
-
var CARRIER_NODES_COMPACT = 5;
|
|
2631
|
-
var CARRIER_NODES_WIDE = 7;
|
|
2632
|
-
var LOOP_GATES_COMPACT = 4;
|
|
2633
|
-
var LOOP_GATES_WIDE = 5;
|
|
2634
|
-
var DISPATCH_DRONES_COMPACT = 4;
|
|
2635
|
-
var DISPATCH_DRONES_WIDE = 8;
|
|
2636
|
-
var CORE_DISPATCH_X = 0.3;
|
|
2637
|
-
var CORE_DISPATCH_Y = 0.45;
|
|
2638
|
-
var CORE_CARRIER_X = 0.5;
|
|
2639
|
-
var CORE_CARRIER_Y = 0.54;
|
|
2640
|
-
var CORE_LOOP_X = 0.09;
|
|
2641
|
-
var CORE_LOOP_Y = 0.56;
|
|
2642
|
-
var RAIL_YS_LOOP = [0.18, 0.5, 0.82];
|
|
2643
|
-
var RAIL_YS_CARRIER = [0.24, 0.54, 0.82];
|
|
2644
|
-
var RAIL_YS_DISPATCH = [0.18, 0.58, 0.82];
|
|
2645
|
-
var GLOW_FALLOFF_EXP_ASCII = 2.2;
|
|
2646
|
-
|
|
2647
|
-
// packages/cli/src/app-opentui/render/graphics.ts
|
|
2648
|
-
var BRAILLE_SAMPLES_X = 2;
|
|
2649
|
-
var BRAILLE_SAMPLES_Y = 4;
|
|
2650
|
-
var TRANSPARENT = RGBA2.fromValues(0, 0, 0, 0);
|
|
2651
|
-
var BACKDROP = RGBA2.fromHex(RIG_UI.bg);
|
|
2652
|
-
var PANEL_BG = RGBA2.fromInts(16, 17, 21, 184);
|
|
2653
|
-
var PANEL_HEADER_BG = RGBA2.fromInts(16, 17, 21, 188);
|
|
2654
|
-
var PANEL_LINE = RGBA2.fromInts(255, 255, 255, 20);
|
|
2655
|
-
var PANEL_LINE_DIM = RGBA2.fromInts(255, 255, 255, 8);
|
|
2656
|
-
var PANEL_TEXT_DIM = RGBA2.fromInts(108, 110, 121, 255);
|
|
2657
|
-
var AC_RGB = [204, 255, 77];
|
|
2658
|
-
var C2_RGB = [86, 216, 255];
|
|
2659
|
-
var MG_RGB = [255, 121, 176];
|
|
2660
|
-
var INK_RGB = [108, 110, 121];
|
|
2661
|
-
var BRAILLE_BITS = [
|
|
2662
|
-
[1, 8],
|
|
2663
|
-
[2, 16],
|
|
2664
|
-
[4, 32],
|
|
2665
|
-
[64, 128]
|
|
2666
|
-
];
|
|
2667
|
-
function sceneKind(scene) {
|
|
2668
|
-
if (scene === "tasks")
|
|
2669
|
-
return "loop";
|
|
2670
|
-
if (scene === "help")
|
|
2671
|
-
return "carrier";
|
|
2672
|
-
return "dispatch";
|
|
2673
|
-
}
|
|
2674
|
-
function seeded(seed) {
|
|
2675
|
-
let value = seed >>> 0;
|
|
2676
|
-
return () => {
|
|
2677
|
-
value = value * LCG_MULTIPLIER + LCG_INCREMENT >>> 0;
|
|
2678
|
-
return value / LCG_DIVISOR;
|
|
2679
|
-
};
|
|
2680
|
-
}
|
|
2681
|
-
function channelBuffer(canvas, channel) {
|
|
2682
|
-
switch (channel) {
|
|
2683
|
-
case 0:
|
|
2684
|
-
return canvas.ac;
|
|
2685
|
-
case 1:
|
|
2686
|
-
return canvas.c2;
|
|
2687
|
-
case 2:
|
|
2688
|
-
return canvas.mg;
|
|
2689
|
-
case 3:
|
|
2690
|
-
return canvas.ink;
|
|
2691
|
-
}
|
|
2692
|
-
}
|
|
2693
|
-
function addSample(canvas, channel, x, y, value) {
|
|
2694
|
-
const ix = Math.trunc(x);
|
|
2695
|
-
const iy = Math.trunc(y);
|
|
2696
|
-
if (ix < 0 || iy < 0 || ix >= canvas.width || iy >= canvas.height)
|
|
2697
|
-
return;
|
|
2698
|
-
const buffer = channelBuffer(canvas, channel);
|
|
2699
|
-
const index = iy * canvas.width + ix;
|
|
2700
|
-
buffer[index] = Math.min(1, buffer[index] + value);
|
|
2701
|
-
}
|
|
2702
|
-
function drawDot(canvas, channel, cx, cy, radius, value) {
|
|
2703
|
-
const minX = Math.floor(cx - radius);
|
|
2704
|
-
const maxX = Math.ceil(cx + radius);
|
|
2705
|
-
const minY = Math.floor(cy - radius);
|
|
2706
|
-
const maxY = Math.ceil(cy + radius);
|
|
2707
|
-
for (let y = minY;y <= maxY; y += 1) {
|
|
2708
|
-
for (let x = minX;x <= maxX; x += 1) {
|
|
2709
|
-
const dx = x - cx;
|
|
2710
|
-
const dy = y - cy;
|
|
2711
|
-
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
2712
|
-
if (distance > radius)
|
|
2713
|
-
continue;
|
|
2714
|
-
const falloff = 1 - distance / Math.max(0.001, radius);
|
|
2715
|
-
addSample(canvas, channel, x, y, value * (0.28 + falloff * 0.72));
|
|
2716
|
-
}
|
|
2717
|
-
}
|
|
2718
|
-
}
|
|
2719
|
-
function drawGlow(canvas, channel, cx, cy, radius, value) {
|
|
2720
|
-
const minX = Math.floor(cx - radius);
|
|
2721
|
-
const maxX = Math.ceil(cx + radius);
|
|
2722
|
-
const minY = Math.floor(cy - radius);
|
|
2723
|
-
const maxY = Math.ceil(cy + radius);
|
|
2724
|
-
for (let y = minY;y <= maxY; y += 1) {
|
|
2725
|
-
for (let x = minX;x <= maxX; x += 1) {
|
|
2726
|
-
const dx = x - cx;
|
|
2727
|
-
const dy = y - cy;
|
|
2728
|
-
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
2729
|
-
if (distance > radius)
|
|
2730
|
-
continue;
|
|
2731
|
-
const falloff = Math.pow(1 - distance / Math.max(0.001, radius), GLOW_FALLOFF_EXP_ASCII);
|
|
2732
|
-
addSample(canvas, channel, x, y, value * falloff);
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
function drawLine(canvas, channel, x0, y0, x1, y1, value, width = 0.8) {
|
|
2737
|
-
const dx = x1 - x0;
|
|
2738
|
-
const dy = y1 - y0;
|
|
2739
|
-
const steps = Math.max(1, Math.ceil(Math.hypot(dx, dy) * 1.7));
|
|
2740
|
-
for (let step = 0;step <= steps; step += 1) {
|
|
2741
|
-
const t2 = step / steps;
|
|
2742
|
-
const x = x0 + dx * t2;
|
|
2743
|
-
const y = y0 + dy * t2;
|
|
2744
|
-
drawDot(canvas, channel, x, y, width, value);
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
function drawCircleStroke(canvas, channel, cx, cy, radius, value) {
|
|
2748
|
-
const steps = Math.max(18, Math.ceil(radius * 8));
|
|
2749
|
-
for (let step = 0;step < steps; step += 1) {
|
|
2750
|
-
const a = step / steps * Math.PI * 2;
|
|
2751
|
-
drawDot(canvas, channel, cx + Math.cos(a) * radius, cy + Math.sin(a) * radius, 0.72, value);
|
|
2752
|
-
}
|
|
2753
|
-
}
|
|
2754
|
-
function paletteColor(ac, c2, mg, ink) {
|
|
2755
|
-
const total = Math.max(0.001, ac + c2 + mg + ink);
|
|
2756
|
-
const r = (AC_RGB[0] * ac + C2_RGB[0] * c2 + MG_RGB[0] * mg + INK_RGB[0] * ink) / total;
|
|
2757
|
-
const g = (AC_RGB[1] * ac + C2_RGB[1] * c2 + MG_RGB[1] * mg + INK_RGB[1] * ink) / total;
|
|
2758
|
-
const b = (AC_RGB[2] * ac + C2_RGB[2] * c2 + MG_RGB[2] * mg + INK_RGB[2] * ink) / total;
|
|
2759
|
-
const intensity = Math.min(1, total / 4);
|
|
2760
|
-
const lift = 0.34 + intensity * 0.66;
|
|
2761
|
-
return RGBA2.fromInts(Math.round(r * lift), Math.round(g * lift), Math.round(b * lift), 255);
|
|
2762
|
-
}
|
|
2763
|
-
function clearCanvas(canvas) {
|
|
2764
|
-
canvas.ac.fill(0);
|
|
2765
|
-
canvas.c2.fill(0);
|
|
2766
|
-
canvas.mg.fill(0);
|
|
2767
|
-
canvas.ink.fill(0);
|
|
2768
|
-
}
|
|
2769
|
-
function makeCanvas(layout) {
|
|
2770
|
-
const width = Math.max(1, layout.width * BRAILLE_SAMPLES_X);
|
|
2771
|
-
const height = Math.max(1, layout.height * BRAILLE_SAMPLES_Y);
|
|
2772
|
-
const size = width * height;
|
|
2773
|
-
return {
|
|
2774
|
-
width,
|
|
2775
|
-
height,
|
|
2776
|
-
ac: new Float32Array(size),
|
|
2777
|
-
c2: new Float32Array(size),
|
|
2778
|
-
mg: new Float32Array(size),
|
|
2779
|
-
ink: new Float32Array(size)
|
|
2780
|
-
};
|
|
2781
|
-
}
|
|
2782
|
-
|
|
2783
|
-
class MarketingGlyphCanvas {
|
|
2784
|
-
rand = seeded(6221137);
|
|
2785
|
-
canvas;
|
|
2786
|
-
layout;
|
|
2787
|
-
activeKind = null;
|
|
2788
|
-
lanes = [];
|
|
2789
|
-
motes = [];
|
|
2790
|
-
packets = [];
|
|
2791
|
-
constructor(layout) {
|
|
2792
|
-
this.layout = layout;
|
|
2793
|
-
this.canvas = makeCanvas(layout);
|
|
2794
|
-
this.reset("dispatch");
|
|
2795
|
-
}
|
|
2796
|
-
resize(layout) {
|
|
2797
|
-
if (layout.width === this.layout.width && layout.height === this.layout.height)
|
|
2798
|
-
return;
|
|
2799
|
-
this.layout = layout;
|
|
2800
|
-
this.canvas = makeCanvas(layout);
|
|
2801
|
-
this.reset(this.activeKind ?? "dispatch");
|
|
2802
|
-
}
|
|
2803
|
-
render(layer, scene, tick) {
|
|
2804
|
-
const kind = sceneKind(scene);
|
|
2805
|
-
if (kind !== this.activeKind)
|
|
2806
|
-
this.reset(kind);
|
|
2807
|
-
clearCanvas(this.canvas);
|
|
2808
|
-
this.drawMarketingRails(kind, tick);
|
|
2809
|
-
if (kind === "loop")
|
|
2810
|
-
this.drawLoop(tick);
|
|
2811
|
-
else if (kind === "carrier")
|
|
2812
|
-
this.drawCarrier(tick);
|
|
2813
|
-
else
|
|
2814
|
-
this.drawDispatch(tick);
|
|
2815
|
-
this.blit(layer);
|
|
2816
|
-
}
|
|
2817
|
-
reset(kind) {
|
|
2818
|
-
this.activeKind = kind;
|
|
2819
|
-
this.lanes = [];
|
|
2820
|
-
this.motes = [];
|
|
2821
|
-
this.packets = [];
|
|
2822
|
-
if (kind === "dispatch")
|
|
2823
|
-
this.setupDispatch();
|
|
2824
|
-
else if (kind === "loop")
|
|
2825
|
-
this.setupLoop();
|
|
2826
|
-
else
|
|
2827
|
-
this.setupCarrier();
|
|
2828
|
-
}
|
|
2829
|
-
setupDispatch() {
|
|
2830
|
-
const count = this.layout.compact ? DISPATCH_LANES_COMPACT : DISPATCH_LANES_WIDE;
|
|
2831
|
-
for (let i = 0;i < count; i += 1) {
|
|
2832
|
-
const ang = (-0.9 + 1.8 * (i / Math.max(1, count - 1))) * (Math.PI * 0.5);
|
|
2833
|
-
this.lanes.push({ ang, len: this.canvas.width * (LANE_LEN_BASE + this.rand() * LANE_LEN_SPREAD), curve: (this.rand() - 0.5) * LANE_CURVE_RANGE, phase: this.rand() * Math.PI * 2 });
|
|
2834
|
-
}
|
|
2835
|
-
const packets = this.layout.compact ? DISPATCH_PACKETS_COMPACT : DISPATCH_PACKETS_WIDE;
|
|
2836
|
-
for (let i = 0;i < packets; i += 1)
|
|
2837
|
-
this.packets.push(this.packet(true));
|
|
2838
|
-
}
|
|
2839
|
-
setupLoop() {
|
|
2840
|
-
const lanes = this.layout.compact ? 4 : 7;
|
|
2841
|
-
for (let i = 0;i < lanes; i += 1)
|
|
2842
|
-
this.packets.push(this.packet(true));
|
|
2843
|
-
}
|
|
2844
|
-
setupCarrier() {
|
|
2845
|
-
const nodes = this.layout.compact ? CARRIER_NODES_COMPACT : CARRIER_NODES_WIDE;
|
|
2846
|
-
for (let i = 0;i < nodes * 6; i += 1)
|
|
2847
|
-
this.packets.push(this.packet(true));
|
|
2848
|
-
}
|
|
2849
|
-
packet(random = false) {
|
|
2850
|
-
return {
|
|
2851
|
-
lane: Math.floor(this.rand() * Math.max(1, this.lanes.length || 7)),
|
|
2852
|
-
p: random ? this.rand() : 0,
|
|
2853
|
-
spd: PACKET_SPEED_BASE + this.rand() * PACKET_SPEED_SPREAD,
|
|
2854
|
-
channel: this.rand() < PACKET_CYAN_PROBABILITY ? 1 : 0,
|
|
2855
|
-
wobble: (this.rand() - 0.5) * 7,
|
|
2856
|
-
size: 0.8 + this.rand() * 1.3
|
|
2857
|
-
};
|
|
2858
|
-
}
|
|
2859
|
-
core(kind) {
|
|
2860
|
-
if (kind === "dispatch")
|
|
2861
|
-
return { x: this.canvas.width * CORE_DISPATCH_X, y: this.canvas.height * CORE_DISPATCH_Y };
|
|
2862
|
-
if (kind === "carrier")
|
|
2863
|
-
return { x: this.canvas.width * CORE_CARRIER_X, y: this.canvas.height * CORE_CARRIER_Y };
|
|
2864
|
-
return { x: this.canvas.width * CORE_LOOP_X, y: this.canvas.height * CORE_LOOP_Y };
|
|
2865
|
-
}
|
|
2866
|
-
lanePoint(lane, p, kind = "dispatch") {
|
|
2867
|
-
const core = this.core(kind);
|
|
2868
|
-
const dist = lane.len * p;
|
|
2869
|
-
const bend = Math.sin(p * Math.PI) * lane.curve * 28;
|
|
2870
|
-
const ux = Math.cos(lane.ang);
|
|
2871
|
-
const uy = Math.sin(lane.ang);
|
|
2872
|
-
return { x: core.x + ux * dist + -uy * bend, y: core.y + uy * dist + ux * bend };
|
|
2873
|
-
}
|
|
2874
|
-
drawMotes(tick) {
|
|
2875
|
-
for (const mote of this.motes) {
|
|
2876
|
-
mote.x += 0.1 * mote.z;
|
|
2877
|
-
if (mote.x > this.canvas.width + 2)
|
|
2878
|
-
mote.x = -2;
|
|
2879
|
-
const alpha = (0.035 + 0.08 * mote.z) * (0.55 + 0.45 * Math.sin(tick * 0.03 + mote.tw));
|
|
2880
|
-
drawDot(this.canvas, mote.channel, mote.x, mote.y, 0.45 * mote.z, alpha);
|
|
2881
|
-
}
|
|
2882
|
-
}
|
|
2883
|
-
drawMarketingRails(kind, tick) {
|
|
2884
|
-
const ys = kind === "loop" ? RAIL_YS_LOOP : kind === "carrier" ? RAIL_YS_CARRIER : RAIL_YS_DISPATCH;
|
|
2885
|
-
ys.forEach((ratio, index) => {
|
|
2886
|
-
const y = this.canvas.height * ratio + Math.sin(tick * 0.018 + index) * 1.8;
|
|
2887
|
-
drawLine(this.canvas, index === 1 ? 0 : 1, this.canvas.width * 0.06, y, this.canvas.width * 0.94, y + Math.sin(index) * 2, index === 1 ? 0.11 : 0.07, 0.55);
|
|
2888
|
-
for (let p = (tick * 0.004 + index * 0.21) % 1;p < 1; p += 0.18 + index * 0.02) {
|
|
2889
|
-
const x = this.canvas.width * (0.06 + 0.88 * p);
|
|
2890
|
-
drawDot(this.canvas, index === 2 ? 1 : 0, x, y, 1 + index * 0.25, 0.24);
|
|
2891
|
-
drawGlow(this.canvas, index === 2 ? 1 : 0, x, y, 3.8, 0.035);
|
|
2892
|
-
}
|
|
2893
|
-
});
|
|
2894
|
-
}
|
|
2895
|
-
drawDispatch(tick) {
|
|
2896
|
-
const core = this.core("dispatch");
|
|
2897
|
-
drawGlow(this.canvas, 0, core.x, core.y, 30, 0.12);
|
|
2898
|
-
drawDot(this.canvas, 0, core.x, core.y, 2.2, 0.85);
|
|
2899
|
-
for (const lane of this.lanes) {
|
|
2900
|
-
let prev = this.lanePoint(lane, 0);
|
|
2901
|
-
for (let i = 1;i <= LANE_INTERP_STEPS; i += 1) {
|
|
2902
|
-
const next = this.lanePoint(lane, i / LANE_INTERP_STEPS);
|
|
2903
|
-
drawLine(this.canvas, lane.phase > Math.PI ? 1 : 0, prev.x, prev.y, next.x, next.y, 0.045, 0.38);
|
|
2904
|
-
prev = next;
|
|
2905
|
-
}
|
|
2906
|
-
}
|
|
2907
|
-
for (const packet of this.packets) {
|
|
2908
|
-
packet.p += packet.spd;
|
|
2909
|
-
if (packet.p > 1)
|
|
2910
|
-
Object.assign(packet, this.packet(false));
|
|
2911
|
-
const lane = this.lanes[packet.lane % Math.max(1, this.lanes.length)];
|
|
2912
|
-
if (!lane)
|
|
2913
|
-
continue;
|
|
2914
|
-
const pt = this.lanePoint(lane, packet.p);
|
|
2915
|
-
const fade = packet.p < 0.1 ? packet.p / 0.1 : packet.p > 0.88 ? (1 - packet.p) / 0.12 : 1;
|
|
2916
|
-
const y = pt.y + packet.wobble * Math.sin(packet.p * Math.PI * 2);
|
|
2917
|
-
drawLine(this.canvas, packet.channel, pt.x - 4, y - 1, pt.x, y, 0.08 * fade, 0.38);
|
|
2918
|
-
drawDot(this.canvas, packet.channel, pt.x, y, packet.size, 0.5 * fade);
|
|
2919
|
-
drawGlow(this.canvas, packet.channel, pt.x, y, packet.size * 4, 0.045 * fade);
|
|
2920
|
-
}
|
|
2921
|
-
for (let i = 0;i < (this.layout.compact ? DISPATCH_DRONES_COMPACT : DISPATCH_DRONES_WIDE); i += 1) {
|
|
2922
|
-
const lane = this.lanes[i % Math.max(1, this.lanes.length)];
|
|
2923
|
-
if (!lane)
|
|
2924
|
-
continue;
|
|
2925
|
-
const p = (tick * 0.002 + i * 0.13) % 1;
|
|
2926
|
-
const pt = this.lanePoint(lane, p);
|
|
2927
|
-
this.drawDrone(pt.x, pt.y + Math.sin(tick * 0.04 + i) * 2, 2.6 + i % 2 * 0.6, tick + i * 11, i % 3 === 0 ? 1 : 0);
|
|
2928
|
-
}
|
|
2929
|
-
}
|
|
2930
|
-
drawLoop(tick) {
|
|
2931
|
-
const y = this.canvas.height * 0.56;
|
|
2932
|
-
const x0 = this.canvas.width * 0.09;
|
|
2933
|
-
const x1 = this.canvas.width * 0.91;
|
|
2934
|
-
drawLine(this.canvas, 0, x0, y, x1, y, 0.12, 0.55);
|
|
2935
|
-
const gates = this.layout.compact ? LOOP_GATES_COMPACT : LOOP_GATES_WIDE;
|
|
2936
|
-
const active = Math.floor(tick / 16) % gates;
|
|
2937
|
-
for (let i = 0;i < gates; i += 1) {
|
|
2938
|
-
const x = x0 + (x1 - x0) * (i / Math.max(1, gates - 1));
|
|
2939
|
-
drawLine(this.canvas, i === active ? 0 : 3, x, y - 22, x, y + 22, i === active ? 0.25 : 0.08, i === active ? 0.7 : 0.42);
|
|
2940
|
-
drawDot(this.canvas, i <= active ? 0 : 3, x, y, i === active ? 2.2 : 1.2, i === active ? 0.72 : 0.25);
|
|
2941
|
-
drawCircleStroke(this.canvas, i <= active ? 0 : 3, x, y, i === active ? 4.2 : 2.4, i === active ? 0.18 : 0.08);
|
|
2942
|
-
}
|
|
2943
|
-
for (const packet of this.packets) {
|
|
2944
|
-
packet.p += packet.spd * 0.7;
|
|
2945
|
-
if (packet.p > 1)
|
|
2946
|
-
packet.p = 0;
|
|
2947
|
-
const x = x0 + (x1 - x0) * packet.p;
|
|
2948
|
-
const segment = Math.min(gates - 1, Math.floor(packet.p * gates));
|
|
2949
|
-
const channel = segment < active ? 0 : segment === active ? 1 : 3;
|
|
2950
|
-
const py = y + packet.wobble * Math.sin(packet.p * Math.PI * 2);
|
|
2951
|
-
drawLine(this.canvas, channel, x - 6, py, x, py, 0.1, 0.42);
|
|
2952
|
-
drawDot(this.canvas, channel, x, py, packet.size, channel === 3 ? 0.22 : 0.58);
|
|
2953
|
-
drawGlow(this.canvas, channel, x, py, 3.8, channel === 3 ? 0.02 : 0.04);
|
|
2954
|
-
}
|
|
2955
|
-
this.drawDrone(x0 + (x1 - x0) * (tick * 0.003 % 1), y - 18, 2.7, tick, 0);
|
|
2956
|
-
this.drawDrone(x0 + (x1 - x0) * ((tick * 0.002 + 0.5) % 1), y + 16, 2.2, tick + 40, 2);
|
|
2957
|
-
}
|
|
2958
|
-
drawCarrier(tick) {
|
|
2959
|
-
const core = this.core("carrier");
|
|
2960
|
-
drawGlow(this.canvas, 0, core.x, core.y, 26, 0.12);
|
|
2961
|
-
drawDot(this.canvas, 0, core.x, core.y, 2.4, 0.92);
|
|
2962
|
-
drawCircleStroke(this.canvas, 0, core.x, core.y, 8 + Math.sin(tick * 0.06) * 2, 0.14);
|
|
2963
|
-
const nodes = this.layout.compact ? CARRIER_NODES_COMPACT : CARRIER_NODES_WIDE;
|
|
2964
|
-
const rx = this.canvas.width * 0.36;
|
|
2965
|
-
const ry = this.canvas.height * 0.22;
|
|
2966
|
-
for (let i = 0;i < nodes; i += 1) {
|
|
2967
|
-
const a = i / nodes * Math.PI * 2 - Math.PI / 2 + tick * 0.006;
|
|
2968
|
-
const x = core.x + Math.cos(a) * rx;
|
|
2969
|
-
const y = core.y + Math.sin(a) * ry;
|
|
2970
|
-
drawLine(this.canvas, i % 2 ? 1 : 0, core.x, core.y, x, y, 0.055, 0.36);
|
|
2971
|
-
drawCircleStroke(this.canvas, i % 2 ? 1 : 0, x, y, 4.2, 0.15);
|
|
2972
|
-
drawDot(this.canvas, i % 2 ? 1 : 0, x, y, 1.7, 0.62);
|
|
2973
|
-
this.drawDrone(x, y - 8, 2.1, tick + i * 9, i % 2 ? 1 : 0);
|
|
2974
|
-
}
|
|
2975
|
-
for (const packet of this.packets) {
|
|
2976
|
-
packet.p += packet.spd;
|
|
2977
|
-
if (packet.p > 1)
|
|
2978
|
-
packet.p = 0;
|
|
2979
|
-
const lane = packet.lane % nodes;
|
|
2980
|
-
const a = lane / nodes * Math.PI * 2 - Math.PI / 2 + tick * 0.006;
|
|
2981
|
-
const nx = core.x + Math.cos(a) * rx;
|
|
2982
|
-
const ny = core.y + Math.sin(a) * ry;
|
|
2983
|
-
const x = core.x + (nx - core.x) * packet.p;
|
|
2984
|
-
const y = core.y + (ny - core.y) * packet.p;
|
|
2985
|
-
drawDot(this.canvas, packet.channel, x, y, packet.size, 0.48);
|
|
2986
|
-
drawGlow(this.canvas, packet.channel, x, y, 3.2, 0.035);
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
drawDrone(x, y, scale, tick, channel) {
|
|
2990
|
-
const wobble = Math.sin(tick * 0.08) * 0.7;
|
|
2991
|
-
drawDot(this.canvas, channel, x, y + wobble, scale, 0.58);
|
|
2992
|
-
drawDot(this.canvas, 3, x, y + wobble, Math.max(0.6, scale * 0.28), 0.42);
|
|
2993
|
-
const rotor = scale * 1.75;
|
|
2994
|
-
const arms = [
|
|
2995
|
-
[-rotor, -rotor * 0.75],
|
|
2996
|
-
[rotor, -rotor * 0.75],
|
|
2997
|
-
[-rotor, rotor * 0.75],
|
|
2998
|
-
[rotor, rotor * 0.75]
|
|
2999
|
-
];
|
|
3000
|
-
for (const [dx, dy] of arms) {
|
|
3001
|
-
drawLine(this.canvas, channel, x, y + wobble, x + dx, y + dy + wobble, 0.11, 0.38);
|
|
3002
|
-
drawCircleStroke(this.canvas, channel, x + dx, y + dy + wobble, scale * (0.65 + Math.abs(Math.sin(tick * 0.18)) * 0.25), 0.16);
|
|
3003
|
-
drawDot(this.canvas, channel, x + dx, y + dy + wobble, 0.7, 0.25);
|
|
3004
|
-
}
|
|
3005
|
-
drawLine(this.canvas, channel === 1 ? 0 : 1, x - rotor * 2.4, y + wobble + 1.2, x - rotor * 1.2, y + wobble, 0.05, 0.35);
|
|
3006
|
-
}
|
|
3007
|
-
blit(layer) {
|
|
3008
|
-
const fb = layer.frameBuffer;
|
|
3009
|
-
fb.clear(BACKDROP);
|
|
3010
|
-
const { width, height, ac, c2, mg, ink } = this.canvas;
|
|
3011
|
-
for (let cy = 0;cy < this.layout.height; cy += 1) {
|
|
3012
|
-
for (let cx = 0;cx < this.layout.width; cx += 1) {
|
|
3013
|
-
let bits = 0;
|
|
3014
|
-
let acSum = 0;
|
|
3015
|
-
let c2Sum = 0;
|
|
3016
|
-
let mgSum = 0;
|
|
3017
|
-
let inkSum = 0;
|
|
3018
|
-
for (let sy = 0;sy < BRAILLE_SAMPLES_Y; sy += 1) {
|
|
3019
|
-
for (let sx = 0;sx < BRAILLE_SAMPLES_X; sx += 1) {
|
|
3020
|
-
const px = cx * BRAILLE_SAMPLES_X + sx;
|
|
3021
|
-
const py = cy * BRAILLE_SAMPLES_Y + sy;
|
|
3022
|
-
if (px >= width || py >= height)
|
|
3023
|
-
continue;
|
|
3024
|
-
const index = py * width + px;
|
|
3025
|
-
const a = ac[index];
|
|
3026
|
-
const c = c2[index];
|
|
3027
|
-
const m = mg[index];
|
|
3028
|
-
const k = ink[index];
|
|
3029
|
-
const total2 = a + c + m + k;
|
|
3030
|
-
acSum += a;
|
|
3031
|
-
c2Sum += c;
|
|
3032
|
-
mgSum += m;
|
|
3033
|
-
inkSum += k;
|
|
3034
|
-
if (total2 > 0.11)
|
|
3035
|
-
bits |= BRAILLE_BITS[sy][sx];
|
|
3036
|
-
}
|
|
3037
|
-
}
|
|
3038
|
-
const total = acSum + c2Sum + mgSum + inkSum;
|
|
3039
|
-
if (bits === 0 || total <= 0.02)
|
|
3040
|
-
continue;
|
|
3041
|
-
const char = String.fromCharCode(10240 + bits);
|
|
3042
|
-
fb.setCellWithAlphaBlending(cx, cy, char, paletteColor(acSum, c2Sum, mgSum, inkSum), TRANSPARENT);
|
|
3043
|
-
}
|
|
3044
|
-
}
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
function createGraphicsLayer(renderer, layout) {
|
|
3048
|
-
const layer = new FrameBufferRenderable(renderer, {
|
|
3049
|
-
id: "rig-marketing-glyph-canvas",
|
|
3050
|
-
width: layout.width,
|
|
3051
|
-
height: layout.height,
|
|
3052
|
-
position: "absolute",
|
|
3053
|
-
left: 0,
|
|
3054
|
-
top: 0,
|
|
3055
|
-
respectAlpha: true
|
|
3056
|
-
});
|
|
3057
|
-
layer.__rigMarketingCanvas = new MarketingGlyphCanvas(layout);
|
|
3058
|
-
return layer;
|
|
3059
|
-
}
|
|
3060
|
-
function resizeGraphicsLayer(layer, layout) {
|
|
3061
|
-
if (layer.width !== layout.width)
|
|
3062
|
-
layer.width = layout.width;
|
|
3063
|
-
if (layer.height !== layout.height)
|
|
3064
|
-
layer.height = layout.height;
|
|
3065
|
-
layer.__rigMarketingCanvas?.resize(layout);
|
|
3066
|
-
}
|
|
3067
|
-
function clearGraphicsLayer(layer) {
|
|
3068
|
-
layer.frameBuffer.clear(BACKDROP);
|
|
3069
|
-
layer.__rigAmbientKey = undefined;
|
|
3070
|
-
}
|
|
3071
|
-
function writeCell(layer, x, y, char, fg, bg) {
|
|
3072
|
-
layer.frameBuffer.setCellWithAlphaBlending(x, y, char, fg, bg);
|
|
3073
|
-
}
|
|
3074
|
-
function drawFullCell(layer, x, y, bg) {
|
|
3075
|
-
writeCell(layer, x, y, " ", TRANSPARENT, bg);
|
|
3076
|
-
}
|
|
3077
|
-
function drawPanelFill(layer, left, top, right, bottom, dividerY) {
|
|
3078
|
-
const bgForY = (y) => y < dividerY ? PANEL_HEADER_BG : PANEL_BG;
|
|
3079
|
-
for (let y = top;y <= bottom; y += 1) {
|
|
3080
|
-
const bg = bgForY(y);
|
|
3081
|
-
for (let x = left;x <= right; x += 1)
|
|
3082
|
-
drawFullCell(layer, x, y, bg);
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3085
|
-
function drawAdTerminalPanel(layer, layout, panel) {
|
|
3086
|
-
const rect = panelCellRect(layout, panel);
|
|
3087
|
-
if (!rect)
|
|
3088
|
-
return;
|
|
3089
|
-
const { left, top, width, right, bottom, headerHeight, dividerY } = rect;
|
|
3090
|
-
drawPanelFill(layer, left, top, right, bottom, dividerY);
|
|
3091
|
-
if (dividerY > top && dividerY < bottom) {
|
|
3092
|
-
for (let x = left + 2;x < right - 1; x += 1) {
|
|
3093
|
-
writeCell(layer, x, dividerY, "\u2500", PANEL_LINE_DIM, PANEL_BG);
|
|
3094
|
-
}
|
|
3095
|
-
}
|
|
3096
|
-
if (panel.headerText) {
|
|
3097
|
-
const x = left + Math.max(2, Math.min(5, panel.paddingX ?? 4));
|
|
3098
|
-
const y = top + Math.max(1, Math.floor(headerHeight / 2));
|
|
3099
|
-
layer.frameBuffer.drawText(panel.headerText.slice(0, Math.max(0, width - 8)), x, y, PANEL_TEXT_DIM, TRANSPARENT);
|
|
3100
|
-
}
|
|
3101
|
-
}
|
|
3102
|
-
function drawPanelChrome(layer, layout, panels) {
|
|
3103
|
-
for (const panel of panels) {
|
|
3104
|
-
if (panel.chrome === "ad-terminal")
|
|
3105
|
-
drawAdTerminalPanel(layer, layout, panel);
|
|
3106
|
-
}
|
|
3107
|
-
}
|
|
3108
|
-
function staticFleetTick(scene) {
|
|
3109
|
-
let hash = 0;
|
|
3110
|
-
for (let index = 0;index < scene.length; index += 1)
|
|
3111
|
-
hash = hash * 33 + scene.charCodeAt(index) >>> 0;
|
|
3112
|
-
return 11 + hash % 97;
|
|
3113
|
-
}
|
|
3114
|
-
function withAlpha(hex, alpha) {
|
|
3115
|
-
const a = Math.max(0, Math.min(255, Math.round(alpha * 255)));
|
|
3116
|
-
const [r, g, b] = RGBA2.fromHex(hex).toInts();
|
|
3117
|
-
return RGBA2.fromInts(r, g, b, a);
|
|
3118
|
-
}
|
|
3119
|
-
function asciiFleetColor(char, row, alpha) {
|
|
3120
|
-
if (char === "@" || char === "$" || char === "o")
|
|
3121
|
-
return withAlpha(RIG_UI.lime, alpha);
|
|
3122
|
-
if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
|
|
3123
|
-
return withAlpha(RIG_UI.cyan, alpha);
|
|
3124
|
-
if (char === "." || char === "'" || char === "_" || row < 3 || row > FLEET_GRID_HEIGHT - 4) {
|
|
3125
|
-
return RGBA2.fromInts(108, 110, 121, Math.min(170, Math.max(0, Math.min(255, Math.round(alpha * 255)))));
|
|
3126
|
-
}
|
|
3127
|
-
return withAlpha(RIG_UI.limeDim, alpha);
|
|
3128
|
-
}
|
|
3129
|
-
var AMBIENT_LOAD_BUCKETS = 5;
|
|
3130
|
-
function ambientLoadBucket(load) {
|
|
3131
|
-
if (!Number.isFinite(load))
|
|
3132
|
-
return 0;
|
|
3133
|
-
return Math.max(0, Math.min(AMBIENT_LOAD_BUCKETS, Math.round(load * AMBIENT_LOAD_BUCKETS)));
|
|
3134
|
-
}
|
|
3135
|
-
function drawAmbientField(layer, layout, _tick, scene = "fleet", load = 0, activeRuns = 0) {
|
|
3136
|
-
const bucket = ambientLoadBucket(load);
|
|
3137
|
-
const rows = fleetRows(staticFleetTick(scene), { animate: true });
|
|
3138
|
-
let droneTotal = 0;
|
|
3139
|
-
for (const line2 of rows)
|
|
3140
|
-
for (const char of line2)
|
|
3141
|
-
if (char === "@" || char === "$" || char === "%")
|
|
3142
|
-
droneTotal += 1;
|
|
3143
|
-
const litTarget = Math.max(0, Math.min(droneTotal, Math.floor(activeRuns)));
|
|
3144
|
-
const key = `${layout.width}x${layout.height}|${scene}|l${bucket}|d${litTarget}`;
|
|
3145
|
-
if (layer.__rigAmbientKey === key)
|
|
3146
|
-
return;
|
|
3147
|
-
const fb = layer.frameBuffer;
|
|
3148
|
-
fb.clear(BACKDROP);
|
|
3149
|
-
const left = Math.floor((layout.width - FLEET_GRID_WIDTH) / 2);
|
|
3150
|
-
const top = Math.floor((layout.height - FLEET_GRID_HEIGHT) / 2);
|
|
3151
|
-
const baseAlpha = 0.62 + bucket / AMBIENT_LOAD_BUCKETS * 0.18;
|
|
3152
|
-
let droneIndex = 0;
|
|
3153
|
-
for (let row = 0;row < rows.length; row += 1) {
|
|
3154
|
-
const y = top + row;
|
|
3155
|
-
if (y < 0 || y >= layout.height)
|
|
3156
|
-
continue;
|
|
3157
|
-
const line2 = rows[row];
|
|
3158
|
-
for (let col = 0;col < line2.length; col += 1) {
|
|
3159
|
-
const x = left + col;
|
|
3160
|
-
if (x < 0 || x >= layout.width)
|
|
3161
|
-
continue;
|
|
3162
|
-
const char = line2[col];
|
|
3163
|
-
if (char === " ")
|
|
3164
|
-
continue;
|
|
3165
|
-
const isDrone = char === "@" || char === "$" || char === "%";
|
|
3166
|
-
const lit = isDrone && droneIndex < litTarget;
|
|
3167
|
-
if (isDrone)
|
|
3168
|
-
droneIndex += 1;
|
|
3169
|
-
const alpha = lit ? 1 : baseAlpha;
|
|
3170
|
-
fb.setCellWithAlphaBlending(x, y, char, asciiFleetColor(char, row, alpha), TRANSPARENT);
|
|
3171
|
-
}
|
|
3172
|
-
}
|
|
3173
|
-
layer.__rigAmbientKey = key;
|
|
3174
|
-
}
|
|
3175
|
-
|
|
3176
|
-
// packages/cli/src/app-opentui/fleet-stats.ts
|
|
3177
|
-
var ACTIVE_STATUSES = new Set([
|
|
3178
|
-
"running",
|
|
3179
|
-
"preparing",
|
|
3180
|
-
"created",
|
|
3181
|
-
"validating",
|
|
3182
|
-
"reviewing",
|
|
3183
|
-
"closing-out",
|
|
3184
|
-
"stopping",
|
|
3185
|
-
"loading"
|
|
3186
|
-
]);
|
|
3187
|
-
var SPARK_CHARS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
3188
|
-
function normalizeStatus(status) {
|
|
3189
|
-
return status.replace(/_/g, "-").toLowerCase();
|
|
3190
|
-
}
|
|
3191
|
-
function runsFromState(state) {
|
|
3192
|
-
const fleet = state.data.fleet;
|
|
3193
|
-
if (!fleet || typeof fleet !== "object" || Array.isArray(fleet))
|
|
3194
|
-
return [];
|
|
3195
|
-
const runs = fleet.runs;
|
|
3196
|
-
return Array.isArray(runs) ? runs : [];
|
|
3197
|
-
}
|
|
3198
|
-
function computeFleetStats(runs) {
|
|
3199
|
-
const byStatus = new Map;
|
|
3200
|
-
let active = 0;
|
|
3201
|
-
let succeeded = 0;
|
|
3202
|
-
let failed = 0;
|
|
3203
|
-
for (const run of runs) {
|
|
3204
|
-
const status = normalizeStatus(run.status ?? "");
|
|
3205
|
-
byStatus.set(status, (byStatus.get(status) ?? 0) + 1);
|
|
3206
|
-
if (ACTIVE_STATUSES.has(status))
|
|
3207
|
-
active += 1;
|
|
3208
|
-
else if (status === "success" || status === "completed" || status === "merged")
|
|
3209
|
-
succeeded += 1;
|
|
3210
|
-
else if (status === "failed" || status === "error")
|
|
3211
|
-
failed += 1;
|
|
3212
|
-
}
|
|
3213
|
-
const total = runs.length;
|
|
3214
|
-
const load = total === 0 ? 0 : active / total;
|
|
3215
|
-
const sparkline = buildSparklineSeries(active, succeeded, failed);
|
|
3216
|
-
return { total, active, succeeded, failed, byStatus, load, sparkline };
|
|
3217
|
-
}
|
|
3218
|
-
function buildSparklineSeries(active, succeeded, failed) {
|
|
3219
|
-
const peak = Math.max(1, active, succeeded, failed);
|
|
3220
|
-
const base = [succeeded, Math.round(succeeded * 0.6 + failed * 0.4), failed, Math.round((active + failed) / 2), active];
|
|
3221
|
-
return base.map((value) => Math.max(0, Math.min(peak, value)) / peak);
|
|
3222
|
-
}
|
|
3223
|
-
function fleetStatsFromState(state) {
|
|
3224
|
-
return computeFleetStats(runsFromState(state));
|
|
3225
|
-
}
|
|
3226
|
-
function sparklineString(values, width) {
|
|
3227
|
-
if (values.length === 0)
|
|
3228
|
-
return "";
|
|
3229
|
-
const series = width && width > 0 && values.length > width ? values.slice(values.length - width) : values;
|
|
3230
|
-
return series.map((value) => {
|
|
3231
|
-
const clamped = Math.max(0, Math.min(1, value));
|
|
3232
|
-
const index = Math.min(SPARK_CHARS.length - 1, Math.round(clamped * (SPARK_CHARS.length - 1)));
|
|
3233
|
-
return SPARK_CHARS[index];
|
|
3234
|
-
}).join("");
|
|
3235
|
-
}
|
|
3236
|
-
|
|
3237
|
-
// packages/cli/src/app-opentui/render/text.ts
|
|
3238
|
-
import { RGBA as RGBA3, TextAttributes as TextAttributes3, TextRenderable } from "@opentui/core";
|
|
3239
|
-
|
|
3240
|
-
// packages/cli/src/app-opentui/render/hover.ts
|
|
3241
|
-
var hoveredId;
|
|
3242
|
-
var listeners = new Set;
|
|
3243
|
-
function getHoveredSelectableId() {
|
|
3244
|
-
return hoveredId;
|
|
3245
|
-
}
|
|
3246
|
-
function isHovered(id) {
|
|
3247
|
-
return id !== undefined && id === hoveredId;
|
|
3248
|
-
}
|
|
3249
|
-
function setHoveredSelectableId(id) {
|
|
3250
|
-
if (hoveredId === id)
|
|
3251
|
-
return;
|
|
3252
|
-
hoveredId = id;
|
|
3253
|
-
for (const listener of listeners)
|
|
3254
|
-
listener();
|
|
3255
|
-
}
|
|
3256
|
-
function clearHoveredSelectableId(id) {
|
|
3257
|
-
if (id !== undefined && hoveredId === id)
|
|
3258
|
-
setHoveredSelectableId(undefined);
|
|
3259
|
-
}
|
|
3260
|
-
function subscribeHover(listener) {
|
|
3261
|
-
listeners.add(listener);
|
|
3262
|
-
return () => {
|
|
3263
|
-
listeners.delete(listener);
|
|
3264
|
-
};
|
|
3265
|
-
}
|
|
3266
|
-
|
|
3267
|
-
// packages/cli/src/app-opentui/render/text.ts
|
|
3268
|
-
var TRANSPARENT2 = RGBA3.fromInts(0, 0, 0, 0);
|
|
3269
|
-
var HOVER_BG = RGBA3.fromHex(RIG_UI.hover);
|
|
3270
|
-
function lineIsClickable(line2) {
|
|
3271
|
-
return line2?.selectable !== undefined || line2?.selectableIndex !== undefined;
|
|
3272
|
-
}
|
|
3273
|
-
function lineSelectableId(line2) {
|
|
3274
|
-
return line2?.selectable?.id ?? line2?.selectableId;
|
|
3275
|
-
}
|
|
3276
|
-
function lineBackground(line2) {
|
|
3277
|
-
if (line2?.bg)
|
|
3278
|
-
return RGBA3.fromHex(line2.bg);
|
|
3279
|
-
const id = lineSelectableId(line2);
|
|
3280
|
-
if (id !== undefined && isHovered(id))
|
|
3281
|
-
return HOVER_BG;
|
|
3282
|
-
return TRANSPARENT2;
|
|
3283
|
-
}
|
|
3284
|
-
function selectableMouseHandler(currentLineRef, onLineMouseDown) {
|
|
3285
|
-
return (event) => {
|
|
3286
|
-
const currentLine = currentLineRef();
|
|
3287
|
-
if (!lineIsClickable(currentLine))
|
|
3288
|
-
return;
|
|
3289
|
-
onLineMouseDown?.(currentLine, event);
|
|
3290
|
-
};
|
|
3291
|
-
}
|
|
3292
|
-
function hoverHandlers(currentLineRef) {
|
|
3293
|
-
return {
|
|
3294
|
-
onMouseOver: () => {
|
|
3295
|
-
const id = lineSelectableId(currentLineRef());
|
|
3296
|
-
if (id !== undefined)
|
|
3297
|
-
setHoveredSelectableId(id);
|
|
3298
|
-
},
|
|
3299
|
-
onMouseOut: () => clearHoveredSelectableId(lineSelectableId(currentLineRef()))
|
|
3300
|
-
};
|
|
3301
|
-
}
|
|
3302
|
-
function createFlowTextLine(renderer, id, onLineMouseDown) {
|
|
3303
|
-
let currentLine;
|
|
3304
|
-
const renderable = new TextRenderable(renderer, {
|
|
3305
|
-
id,
|
|
3306
|
-
content: "",
|
|
3307
|
-
width: "100%",
|
|
3308
|
-
height: 1,
|
|
3309
|
-
flexShrink: 0,
|
|
3310
|
-
fg: RIG_UI.ink2,
|
|
3311
|
-
selectable: true,
|
|
3312
|
-
onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
|
|
3313
|
-
...hoverHandlers(() => currentLine)
|
|
3314
|
-
});
|
|
3315
|
-
renderable.setRigSceneLine = (line2) => {
|
|
3316
|
-
currentLine = line2;
|
|
3317
|
-
};
|
|
3318
|
-
return renderable;
|
|
3319
|
-
}
|
|
3320
|
-
function applyFlowTextLine(renderable, line2, width) {
|
|
3321
|
-
renderable.setRigSceneLine?.(line2);
|
|
3322
|
-
renderable.visible = Boolean(line2);
|
|
3323
|
-
renderable.height = line2 ? 1 : 0;
|
|
3324
|
-
renderable.width = Math.max(1, width);
|
|
3325
|
-
renderable.content = line2?.styledText ?? line2?.text ?? "";
|
|
3326
|
-
renderable.fg = line2?.fg ?? RIG_UI.ink2;
|
|
3327
|
-
renderable.bg = lineBackground(line2);
|
|
3328
|
-
renderable.attributes = line2?.bold ? TextAttributes3.BOLD : line2?.dim ? TextAttributes3.DIM : 0;
|
|
3329
|
-
}
|
|
3330
|
-
|
|
3331
|
-
// packages/cli/src/app-opentui/render/panels.ts
|
|
3332
|
-
import { RGBA as RGBA4, ScrollBoxRenderable } from "@opentui/core";
|
|
3333
|
-
var TRANSPARENT3 = RGBA4.fromInts(0, 0, 0, 0);
|
|
3334
|
-
var MAX_PANEL_LINES = 420;
|
|
3335
|
-
var PANEL_OPAQUE_ALPHA = 255;
|
|
3336
|
-
var PANEL_BORDER = hexToRgba(RIG_UI.border, 255);
|
|
3337
|
-
function hexToRgba(hex, alpha) {
|
|
3338
|
-
const clean = hex.replace(/^#/, "");
|
|
3339
|
-
const full = clean.length === 3 ? clean.split("").map((part) => `${part}${part}`).join("") : clean;
|
|
3340
|
-
const value = Number.parseInt(full, 16);
|
|
3341
|
-
if (!Number.isFinite(value))
|
|
3342
|
-
return RGBA4.fromInts(14, 15, 17, alpha);
|
|
3343
|
-
return RGBA4.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255, alpha);
|
|
3344
|
-
}
|
|
3345
|
-
function panelBackground(panel) {
|
|
3346
|
-
return hexToRgba(panel.backgroundColor ?? RIG_UI.panel, PANEL_OPAQUE_ALPHA);
|
|
3347
|
-
}
|
|
3348
|
-
function panelBorder(panel) {
|
|
3349
|
-
if (panel.borderColor)
|
|
3350
|
-
return hexToRgba(panel.borderColor, panel.borderAlpha ?? 255);
|
|
3351
|
-
return PANEL_BORDER;
|
|
3352
|
-
}
|
|
3353
|
-
function createScrollPanelLayer(renderer, id, onLineMouseDown) {
|
|
3354
|
-
const panel = new ScrollBoxRenderable(renderer, {
|
|
3355
|
-
id,
|
|
3356
|
-
position: "absolute",
|
|
3357
|
-
left: 0,
|
|
3358
|
-
top: -2,
|
|
3359
|
-
width: 1,
|
|
3360
|
-
height: 1,
|
|
3361
|
-
backgroundColor: TRANSPARENT3,
|
|
3362
|
-
border: false,
|
|
3363
|
-
shouldFill: true,
|
|
3364
|
-
opacity: 1,
|
|
3365
|
-
zIndex: 6,
|
|
3366
|
-
scrollY: true,
|
|
3367
|
-
scrollX: false,
|
|
3368
|
-
stickyScroll: false,
|
|
3369
|
-
viewportCulling: true,
|
|
3370
|
-
paddingX: 2,
|
|
3371
|
-
paddingY: 1,
|
|
3372
|
-
verticalScrollbarOptions: {
|
|
3373
|
-
showArrows: false,
|
|
3374
|
-
visible: false,
|
|
3375
|
-
width: 1,
|
|
3376
|
-
trackOptions: {
|
|
3377
|
-
backgroundColor: TRANSPARENT3,
|
|
3378
|
-
foregroundColor: hexToRgba(RIG_UI.ink4, 84)
|
|
3379
|
-
}
|
|
3380
|
-
},
|
|
3381
|
-
horizontalScrollbarOptions: {
|
|
3382
|
-
height: 0,
|
|
3383
|
-
visible: false,
|
|
3384
|
-
trackOptions: {
|
|
3385
|
-
backgroundColor: TRANSPARENT3,
|
|
3386
|
-
foregroundColor: TRANSPARENT3
|
|
3387
|
-
}
|
|
3388
|
-
}
|
|
3389
|
-
});
|
|
3390
|
-
panel.__rigTextLines = [];
|
|
3391
|
-
panel.__rigRenderer = renderer;
|
|
3392
|
-
panel.__rigOnLineMouseDown = onLineMouseDown;
|
|
3393
|
-
return panel;
|
|
3394
|
-
}
|
|
3395
|
-
function ensurePanelLineCount(panel, desiredCount) {
|
|
3396
|
-
const target = Math.min(MAX_PANEL_LINES, Math.max(0, desiredCount));
|
|
3397
|
-
while (panel.__rigTextLines.length < target) {
|
|
3398
|
-
const index = panel.__rigTextLines.length;
|
|
3399
|
-
const line2 = createFlowTextLine(panel.__rigRenderer, `${panel.id}-line-${index}`, panel.__rigOnLineMouseDown);
|
|
3400
|
-
panel.__rigTextLines.push(line2);
|
|
3401
|
-
panel.add(line2);
|
|
3402
|
-
}
|
|
3403
|
-
}
|
|
3404
|
-
function hidePanel(panel) {
|
|
3405
|
-
panel.visible = false;
|
|
3406
|
-
panel.top = -2;
|
|
3407
|
-
panel.left = 0;
|
|
3408
|
-
panel.width = 1;
|
|
3409
|
-
panel.height = 1;
|
|
3410
|
-
panel.backgroundColor = TRANSPARENT3;
|
|
3411
|
-
panel.border = false;
|
|
3412
|
-
panel.__rigPanelId = undefined;
|
|
3413
|
-
panel.__rigTextLines.forEach((line2) => applyFlowTextLine(line2, undefined, 1));
|
|
3414
|
-
}
|
|
3415
|
-
function applyScrollPanels(panels, layout, scenePanels) {
|
|
3416
|
-
panels.forEach((renderable, index) => {
|
|
3417
|
-
const panel = scenePanels[index];
|
|
3418
|
-
if (!panel || panel.height <= 0) {
|
|
3419
|
-
hidePanel(renderable);
|
|
3420
|
-
return;
|
|
3421
|
-
}
|
|
3422
|
-
const contentRect = panelContentRect(layout, panel);
|
|
3423
|
-
if (!contentRect) {
|
|
3424
|
-
hidePanel(renderable);
|
|
3425
|
-
return;
|
|
3426
|
-
}
|
|
3427
|
-
const { left, top, width, height, paddingX, paddingY, contentWidth } = contentRect;
|
|
3428
|
-
renderable.visible = true;
|
|
3429
|
-
renderable.left = left;
|
|
3430
|
-
renderable.top = top;
|
|
3431
|
-
renderable.width = width;
|
|
3432
|
-
renderable.height = height;
|
|
3433
|
-
renderable.zIndex = panel.zIndex ?? 6;
|
|
3434
|
-
renderable.opacity = panel.opacity ?? 1;
|
|
3435
|
-
const adTerminal = panel.chrome === "ad-terminal";
|
|
3436
|
-
renderable.backgroundColor = adTerminal ? TRANSPARENT3 : panelBackground(panel);
|
|
3437
|
-
renderable.border = adTerminal ? false : true;
|
|
3438
|
-
renderable.borderColor = adTerminal ? TRANSPARENT3 : panelBorder(panel);
|
|
3439
|
-
renderable.paddingX = paddingX;
|
|
3440
|
-
renderable.paddingY = paddingY;
|
|
3441
|
-
renderable.stickyScroll = panel.stickyScroll ?? false;
|
|
3442
|
-
renderable.stickyStart = panel.stickyStart;
|
|
3443
|
-
renderable.verticalScrollBar.visible = false;
|
|
3444
|
-
renderable.horizontalScrollBar.visible = false;
|
|
3445
|
-
if (renderable.__rigPanelId !== panel.id) {
|
|
3446
|
-
renderable.__rigPanelId = panel.id;
|
|
3447
|
-
renderable.scrollTo(0);
|
|
3448
|
-
}
|
|
3449
|
-
const lineCount = Math.min(panel.lines.length, MAX_PANEL_LINES);
|
|
3450
|
-
ensurePanelLineCount(renderable, lineCount);
|
|
3451
|
-
renderable.__rigTextLines.forEach((line2, lineIndex) => {
|
|
3452
|
-
applyFlowTextLine(line2, lineIndex < lineCount ? panel.lines[lineIndex] : undefined, contentWidth);
|
|
3453
|
-
});
|
|
3454
|
-
});
|
|
3455
|
-
}
|
|
3456
|
-
|
|
3457
|
-
// packages/cli/src/app-opentui/render/type-bar.ts
|
|
3458
|
-
import { BoxRenderable, InputRenderable, InputRenderableEvents, RGBA as RGBA5, StyledText as StyledText2, TextRenderable as TextRenderable2 } from "@opentui/core";
|
|
3459
|
-
var TYPEBAR_BG = RGBA5.fromInts(20, 25, 14, 224);
|
|
3460
|
-
var TYPEBAR_BORDER = RGBA5.fromInts(204, 255, 77, 92);
|
|
3461
|
-
function createTypeBar(renderer) {
|
|
3462
|
-
const background = new BoxRenderable(renderer, {
|
|
3463
|
-
id: "typebar-card",
|
|
3464
|
-
position: "absolute",
|
|
3465
|
-
left: 0,
|
|
3466
|
-
top: 0,
|
|
3467
|
-
width: 1,
|
|
3468
|
-
height: 1,
|
|
3469
|
-
backgroundColor: TYPEBAR_BG,
|
|
3470
|
-
border: true,
|
|
3471
|
-
borderColor: TYPEBAR_BORDER,
|
|
3472
|
-
zIndex: 4
|
|
3473
|
-
});
|
|
3474
|
-
const prefix = new TextRenderable2(renderer, {
|
|
3475
|
-
id: "typebar-prefix",
|
|
3476
|
-
content: " \u203A",
|
|
3477
|
-
position: "absolute",
|
|
3478
|
-
left: 1,
|
|
3479
|
-
top: 0,
|
|
3480
|
-
fg: RIG_UI.lime,
|
|
3481
|
-
zIndex: 7,
|
|
3482
|
-
selectable: true
|
|
3483
|
-
});
|
|
3484
|
-
const input = new InputRenderable(renderer, {
|
|
3485
|
-
id: "typebar-input",
|
|
3486
|
-
position: "absolute",
|
|
3487
|
-
left: 4,
|
|
3488
|
-
top: 0,
|
|
3489
|
-
width: 40,
|
|
3490
|
-
placeholder: "runs \xB7 tasks \xB7 run next \xB7 help",
|
|
3491
|
-
textColor: RIG_UI.ink,
|
|
3492
|
-
cursorColor: RIG_UI.lime,
|
|
3493
|
-
cursorStyle: { style: "block", blinking: false },
|
|
3494
|
-
showCursor: true,
|
|
3495
|
-
zIndex: 7,
|
|
3496
|
-
selectable: true
|
|
3497
|
-
});
|
|
3498
|
-
const footer = new TextRenderable2(renderer, {
|
|
3499
|
-
id: "footer",
|
|
3500
|
-
content: "",
|
|
3501
|
-
position: "absolute",
|
|
3502
|
-
left: 0,
|
|
3503
|
-
top: 1,
|
|
3504
|
-
width: "100%",
|
|
3505
|
-
fg: RIG_UI.ink3,
|
|
3506
|
-
zIndex: 7,
|
|
3507
|
-
selectable: true
|
|
3508
|
-
});
|
|
3509
|
-
return { background, input, prefix, footer };
|
|
3510
|
-
}
|
|
3511
|
-
function modePrefix(mode) {
|
|
3512
|
-
switch (mode) {
|
|
3513
|
-
case "search":
|
|
3514
|
-
return { glyph: " /", color: RIG_UI.cyan };
|
|
3515
|
-
case "command":
|
|
3516
|
-
return { glyph: " /", color: RIG_UI.lime };
|
|
3517
|
-
case "prompt":
|
|
3518
|
-
return { glyph: " ?", color: RIG_UI.yellow };
|
|
3519
|
-
default:
|
|
3520
|
-
return { glyph: " \u203A", color: RIG_UI.ink3 };
|
|
3521
|
-
}
|
|
3522
|
-
}
|
|
3523
|
-
function positionTypeBar(renderables, input, footer, width, typeBarTop, footerTop) {
|
|
3524
|
-
const mode = input.mode ?? "nav";
|
|
3525
|
-
const editing = mode !== "nav";
|
|
3526
|
-
const { glyph, color } = modePrefix(mode);
|
|
3527
|
-
renderables.background.visible = true;
|
|
3528
|
-
const cardTop = Math.max(0, typeBarTop - 1);
|
|
3529
|
-
renderables.background.left = 0;
|
|
3530
|
-
renderables.background.top = cardTop;
|
|
3531
|
-
renderables.background.width = width;
|
|
3532
|
-
renderables.background.height = Math.max(3, footerTop - cardTop + 1);
|
|
3533
|
-
renderables.prefix.content = glyph;
|
|
3534
|
-
renderables.prefix.fg = color;
|
|
3535
|
-
renderables.prefix.left = 2;
|
|
3536
|
-
renderables.prefix.top = typeBarTop;
|
|
3537
|
-
renderables.input.top = typeBarTop;
|
|
3538
|
-
renderables.input.left = 5;
|
|
3539
|
-
renderables.input.width = Math.max(10, width - 8);
|
|
3540
|
-
renderables.input.value = editing ? input.value : "";
|
|
3541
|
-
renderables.input.placeholder = editing ? mode === "prompt" ? input.prompt?.label ?? "type a value" : mode === "command" ? "rig \u2026" : "filter\u2026" : input.placeholder;
|
|
3542
|
-
renderables.input.showCursor = editing;
|
|
3543
|
-
if (renderables.input.focused)
|
|
3544
|
-
renderables.input.blur();
|
|
3545
|
-
renderables.footer.top = footerTop;
|
|
3546
|
-
renderables.footer.width = "100%";
|
|
3547
|
-
renderables.footer.content = formatFooter(footer, width);
|
|
3548
|
-
}
|
|
3549
|
-
function hideTypeBar(renderables, height) {
|
|
3550
|
-
renderables.background.visible = false;
|
|
3551
|
-
renderables.background.top = height + 1;
|
|
3552
|
-
renderables.prefix.content = "";
|
|
3553
|
-
renderables.prefix.top = height + 1;
|
|
3554
|
-
renderables.input.value = "";
|
|
3555
|
-
renderables.input.placeholder = "";
|
|
3556
|
-
renderables.input.top = height + 1;
|
|
3557
|
-
renderables.input.left = 0;
|
|
3558
|
-
renderables.input.width = 1;
|
|
3559
|
-
renderables.input.blur();
|
|
3560
|
-
renderables.footer.content = "";
|
|
3561
|
-
renderables.footer.top = height + 1;
|
|
3562
|
-
}
|
|
3563
|
-
function safeFooterMessage(message) {
|
|
3564
|
-
const trimmed = message?.trim();
|
|
3565
|
-
if (!trimmed)
|
|
3566
|
-
return null;
|
|
3567
|
-
if (/https?:|\b[a-z0-9-]+\.[a-z]{2,}\b|\brig\b/i.test(trimmed))
|
|
3568
|
-
return null;
|
|
3569
|
-
return trimmed;
|
|
3570
|
-
}
|
|
3571
|
-
function liveCell(live) {
|
|
3572
|
-
const connected = live.trimStart().startsWith("\u25CF");
|
|
3573
|
-
return { text: live, style: connected ? styles.cyan : styles.ink3 };
|
|
3574
|
-
}
|
|
3575
|
-
function footerCells(footer) {
|
|
3576
|
-
const safeMessage = safeFooterMessage(footer.message);
|
|
3577
|
-
const cells = [
|
|
3578
|
-
footer.section ? { text: `\u25B8 ${footer.section}`, style: (s) => otuiBold(styles.ink2(s)) } : null,
|
|
3579
|
-
footer.live ? liveCell(footer.live) : null,
|
|
3580
|
-
footer.project ? { text: "workspace", style: styles.ink3 } : null,
|
|
3581
|
-
footer.server ? { text: "server selected", style: styles.ink3 } : null,
|
|
3582
|
-
footer.auth ? { text: "auth ready", style: styles.ink3 } : null,
|
|
3583
|
-
footer.run ? { text: `run ${footer.run}`, style: styles.ink3 } : null,
|
|
3584
|
-
safeMessage ? { text: safeMessage, style: styles.ink2 } : null,
|
|
3585
|
-
footer.updated ? { text: footer.updated, style: styles.ink3 } : null
|
|
3586
|
-
];
|
|
3587
|
-
return cells.filter((cell) => cell !== null);
|
|
3588
|
-
}
|
|
3589
|
-
var FOOTER_SEPARATOR = " \xB7 ";
|
|
3590
|
-
function formatFooter(footer, width) {
|
|
3591
|
-
const budget = Math.max(8, width - 2);
|
|
3592
|
-
const chunks = [styles.ink3(" ")];
|
|
3593
|
-
let used = 1;
|
|
3594
|
-
footerCells(footer).forEach((cell, index) => {
|
|
3595
|
-
const sep = index === 0 ? "" : FOOTER_SEPARATOR;
|
|
3596
|
-
const remaining = budget - used - visibleWidth(sep);
|
|
3597
|
-
if (remaining <= 0)
|
|
3598
|
-
return;
|
|
3599
|
-
if (sep) {
|
|
3600
|
-
chunks.push(styles.ink4(sep));
|
|
3601
|
-
used += visibleWidth(sep);
|
|
3602
|
-
}
|
|
3603
|
-
const fits = visibleWidth(cell.text) <= remaining;
|
|
3604
|
-
const text = fits ? cell.text : remaining <= 1 ? "\u2026" : `${[...cell.text].slice(0, Math.max(0, remaining - 1)).join("")}\u2026`;
|
|
3605
|
-
chunks.push(cell.style(text));
|
|
3606
|
-
used += visibleWidth(text);
|
|
3607
|
-
});
|
|
3608
|
-
return new StyledText2(chunks);
|
|
3609
|
-
}
|
|
3610
|
-
|
|
3611
|
-
// packages/cli/src/app-opentui/render/native-host.ts
|
|
3612
|
-
import {
|
|
3613
|
-
BoxRenderable as BoxRenderable2,
|
|
3614
|
-
CodeRenderable,
|
|
3615
|
-
DiffRenderable,
|
|
3616
|
-
RGBA as RGBA6,
|
|
3617
|
-
ScrollBoxRenderable as ScrollBoxRenderable2,
|
|
3618
|
-
SyntaxStyle,
|
|
3619
|
-
TextRenderable as TextRenderable3,
|
|
3620
|
-
TextTableRenderable
|
|
3621
|
-
} from "@opentui/core";
|
|
3622
|
-
var syntaxStyle = null;
|
|
3623
|
-
function rigSyntaxStyle() {
|
|
3624
|
-
if (syntaxStyle)
|
|
3625
|
-
return syntaxStyle;
|
|
3626
|
-
syntaxStyle = SyntaxStyle.fromStyles({
|
|
3627
|
-
keyword: { fg: RGBA6.fromHex(RIG_UI.magenta), bold: true },
|
|
3628
|
-
string: { fg: RGBA6.fromHex(RIG_UI.lime) },
|
|
3629
|
-
number: { fg: RGBA6.fromHex(RIG_UI.cyan) },
|
|
3630
|
-
comment: { fg: RGBA6.fromHex(RIG_UI.ink4), italic: true },
|
|
3631
|
-
function: { fg: RGBA6.fromHex(RIG_UI.cyan) },
|
|
3632
|
-
type: { fg: RGBA6.fromHex(RIG_UI.limeDim) },
|
|
3633
|
-
constant: { fg: RGBA6.fromHex(RIG_UI.yellow) },
|
|
3634
|
-
default: { fg: RGBA6.fromHex(RIG_UI.ink2) }
|
|
3635
|
-
});
|
|
3636
|
-
return syntaxStyle;
|
|
3637
|
-
}
|
|
3638
|
-
function tableContent(rows) {
|
|
3639
|
-
return rows.map((row, rowIndex) => row.map((cell) => [{ __isChunk: true, text: cell, fg: RGBA6.fromHex(rowIndex === 0 ? RIG_UI.ink3 : RIG_UI.ink2) }]));
|
|
3640
|
-
}
|
|
3641
|
-
function createNativeHost(renderer) {
|
|
3642
|
-
try {
|
|
3643
|
-
const scrollBox = new ScrollBoxRenderable2(renderer, {
|
|
3644
|
-
id: "rig-native-scrollbox",
|
|
3645
|
-
position: "absolute",
|
|
3646
|
-
left: 0,
|
|
3647
|
-
top: 0,
|
|
3648
|
-
width: 1,
|
|
3649
|
-
height: 1,
|
|
3650
|
-
scrollY: true,
|
|
3651
|
-
zIndex: 9,
|
|
3652
|
-
visible: false
|
|
3653
|
-
});
|
|
3654
|
-
renderer.root.add(scrollBox);
|
|
3655
|
-
return { scrollBox, code: null, diff: null, table: null, active: null, lastKey: "" };
|
|
3656
|
-
} catch {
|
|
3657
|
-
return null;
|
|
3658
|
-
}
|
|
3659
|
-
}
|
|
3660
|
-
function hideAllChildren(host) {
|
|
3661
|
-
if (host.code)
|
|
3662
|
-
host.code.visible = false;
|
|
3663
|
-
if (host.diff)
|
|
3664
|
-
host.diff.visible = false;
|
|
3665
|
-
if (host.table)
|
|
3666
|
-
host.table.visible = false;
|
|
3667
|
-
}
|
|
3668
|
-
function hideNativeHost(host) {
|
|
3669
|
-
if (!host)
|
|
3670
|
-
return;
|
|
3671
|
-
host.scrollBox.visible = false;
|
|
3672
|
-
hideAllChildren(host);
|
|
3673
|
-
host.active = null;
|
|
3674
|
-
}
|
|
3675
|
-
function applyNativeHost(host, mount, layout) {
|
|
3676
|
-
if (!host)
|
|
3677
|
-
return false;
|
|
3678
|
-
try {
|
|
3679
|
-
const left = mount.left ?? layout.centerLeft;
|
|
3680
|
-
const top = mount.top ?? layout.centerTop;
|
|
3681
|
-
const width = Math.max(8, mount.width ?? layout.centerWidth);
|
|
3682
|
-
const height = Math.max(3, mount.height ?? layout.centerHeight);
|
|
3683
|
-
host.scrollBox.left = left;
|
|
3684
|
-
host.scrollBox.top = top;
|
|
3685
|
-
host.scrollBox.width = width;
|
|
3686
|
-
host.scrollBox.height = height;
|
|
3687
|
-
host.scrollBox.visible = true;
|
|
3688
|
-
hideAllChildren(host);
|
|
3689
|
-
const rowCount = mount.rows?.length ?? 0;
|
|
3690
|
-
const key = `${mount.kind}:${width}x${height}:${mount.content.length}:${mount.filetype ?? ""}:${rowCount}`;
|
|
3691
|
-
if (mount.kind === "table") {
|
|
3692
|
-
if (!host.table) {
|
|
3693
|
-
host.table = new TextTableRenderable(host.scrollBox.ctx, { id: "rig-native-table", content: [], width: "100%", showBorders: true, columnWidthMode: "full", borderColor: RIG_UI.ink4 });
|
|
3694
|
-
host.scrollBox.add(host.table);
|
|
3695
|
-
}
|
|
3696
|
-
if (host.lastKey !== key)
|
|
3697
|
-
host.table.content = tableContent(mount.rows ?? []);
|
|
3698
|
-
host.table.visible = true;
|
|
3699
|
-
} else if (mount.kind === "diff") {
|
|
3700
|
-
if (!host.diff) {
|
|
3701
|
-
host.diff = new DiffRenderable(host.scrollBox.ctx, { id: "rig-native-diff", diff: "", width: "100%" });
|
|
3702
|
-
host.scrollBox.add(host.diff);
|
|
3703
|
-
}
|
|
3704
|
-
if (host.lastKey !== key)
|
|
3705
|
-
host.diff.diff = mount.content;
|
|
3706
|
-
host.diff.visible = true;
|
|
3707
|
-
} else {
|
|
3708
|
-
const filetype = mount.kind === "markdown" ? "markdown" : mount.filetype ?? "text";
|
|
3709
|
-
if (!host.code) {
|
|
3710
|
-
host.code = new CodeRenderable(host.scrollBox.ctx, { id: "rig-native-code", content: "", filetype, syntaxStyle: rigSyntaxStyle(), width: "100%" });
|
|
3711
|
-
host.scrollBox.add(host.code);
|
|
3712
|
-
}
|
|
3713
|
-
if (host.lastKey !== key) {
|
|
3714
|
-
host.code.filetype = filetype;
|
|
3715
|
-
host.code.content = mount.content;
|
|
3716
|
-
}
|
|
3717
|
-
host.code.visible = true;
|
|
3718
|
-
}
|
|
3719
|
-
host.active = mount.kind;
|
|
3720
|
-
host.lastKey = key;
|
|
3721
|
-
if (mount.stickyBottom)
|
|
3722
|
-
host.scrollBox.scrollTo?.({ x: 0, y: Number.MAX_SAFE_INTEGER });
|
|
3723
|
-
return true;
|
|
3724
|
-
} catch {
|
|
3725
|
-
hideNativeHost(host);
|
|
3726
|
-
return false;
|
|
3727
|
-
}
|
|
3728
|
-
}
|
|
3729
|
-
function destroyNativeHost(host) {
|
|
3730
|
-
if (!host)
|
|
3731
|
-
return;
|
|
3732
|
-
try {
|
|
3733
|
-
host.scrollBox.destroy?.();
|
|
3734
|
-
} catch {}
|
|
3735
|
-
}
|
|
3736
|
-
|
|
3737
|
-
// packages/cli/src/app-opentui/remote-link.ts
|
|
3738
|
-
var REPAIRABLE_REMOTE_LINK_STATUSES = new Set([
|
|
3739
|
-
"auth_required",
|
|
3740
|
-
"project_not_registered",
|
|
3741
|
-
"no_server_checkout",
|
|
3742
|
-
"invalid_root",
|
|
3743
|
-
"needs_prepare",
|
|
3744
|
-
"error"
|
|
3745
|
-
]);
|
|
3746
|
-
function remoteProjectLinkError(message) {
|
|
3747
|
-
return /no server-host project root link|remote project(?:-root)? link|x-rig-project-root|serverProjectRoot/i.test(message ?? "");
|
|
3748
|
-
}
|
|
3749
|
-
function serverRecordForRemoteLink(state) {
|
|
3750
|
-
const server = state.data.server;
|
|
3751
|
-
return server && typeof server === "object" && !Array.isArray(server) ? server : null;
|
|
3752
|
-
}
|
|
3753
|
-
function remoteProjectLinkState(state) {
|
|
3754
|
-
const serverRecord = serverRecordForRemoteLink(state);
|
|
3755
|
-
if (!serverRecord || serverRecord.kind !== "remote")
|
|
3756
|
-
return null;
|
|
3757
|
-
const link = serverRecord.remoteProjectLink;
|
|
3758
|
-
if (link && typeof link === "object" && !Array.isArray(link))
|
|
3759
|
-
return link;
|
|
3760
|
-
const direct = state.data.remoteProjectLink;
|
|
3761
|
-
if (direct && typeof direct === "object" && !Array.isArray(direct))
|
|
3762
|
-
return direct;
|
|
3763
|
-
return null;
|
|
3764
|
-
}
|
|
3765
|
-
function shouldOfferRemoteLinkRepair(state, message) {
|
|
3766
|
-
const link = remoteProjectLinkState(state);
|
|
3767
|
-
if (link) {
|
|
3768
|
-
if (link.ok === true)
|
|
3769
|
-
return false;
|
|
3770
|
-
const status = link.status ?? "";
|
|
3771
|
-
return REPAIRABLE_REMOTE_LINK_STATUSES.has(status);
|
|
3772
|
-
}
|
|
3773
|
-
const serverRecord = serverRecordForRemoteLink(state);
|
|
3774
|
-
if (serverRecord && serverRecord.kind !== "remote")
|
|
3775
|
-
return false;
|
|
3776
|
-
return remoteProjectLinkError(message ?? state.error?.message);
|
|
3777
|
-
}
|
|
3778
|
-
|
|
3779
|
-
// packages/cli/src/app-opentui/selectable.ts
|
|
3780
|
-
function selectableDeckRow(deck, item) {
|
|
3781
|
-
return { ...deckRow({ ...deck, activateOnClick: true }), selectable: item };
|
|
3782
|
-
}
|
|
3783
|
-
|
|
3784
|
-
// packages/cli/src/app-opentui/scenes/error.ts
|
|
3785
|
-
var ERROR_ACTIONS = [
|
|
3786
|
-
{ detail: "return to dashboard", item: { id: "main", label: "main", intent: { scene: "main", argv: [], action: { kind: "refresh", label: "Back to dashboard" } }, message: "back to dashboard" } },
|
|
3787
|
-
{ detail: "run diagnostics", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "run diagnostics" } },
|
|
3788
|
-
{ detail: "open server controls", item: { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "open server controls" } }
|
|
3789
|
-
];
|
|
3790
|
-
var ERROR_REPAIR_LINK_ACTION = { detail: "backfill or prepare selected remote project-root link", item: { id: "repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "server" } } }, message: "repair remote project link" } };
|
|
3791
|
-
function errorActions(state) {
|
|
3792
|
-
return shouldOfferRemoteLinkRepair(state, state.error?.message) ? [...ERROR_ACTIONS, ERROR_REPAIR_LINK_ACTION] : ERROR_ACTIONS;
|
|
3793
|
-
}
|
|
3794
|
-
function renderErrorScene(state) {
|
|
3795
|
-
const message = state.error?.message ?? "Unknown app error";
|
|
3796
|
-
const hint = state.error?.hint ?? "Use the actions below to recover.";
|
|
3797
|
-
const actions = errorActions(state);
|
|
3798
|
-
const selected = Math.max(0, Math.min(actions.length - 1, state.selection.index));
|
|
3799
|
-
return makeSceneFrame({
|
|
3800
|
-
scene: "error",
|
|
3801
|
-
title: "Error",
|
|
3802
|
-
lines: [
|
|
3803
|
-
...errorDrone(state.tick).slice(2, 10)
|
|
3804
|
-
],
|
|
3805
|
-
panels: [{
|
|
3806
|
-
id: "error-recovery",
|
|
3807
|
-
top: 2,
|
|
3808
|
-
width: 96,
|
|
3809
|
-
height: 18,
|
|
3810
|
-
lines: [
|
|
3811
|
-
line("DRONE SIGNAL DEGRADED", { fg: RIG_UI.red, bold: true }),
|
|
3812
|
-
line(message, { fg: RIG_UI.ink2 }),
|
|
3813
|
-
...hint ? [line(hint, { fg: RIG_UI.yellow })] : [],
|
|
3814
|
-
line("", { fg: RIG_UI.ink3 }),
|
|
3815
|
-
line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
|
|
3816
|
-
...actions.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item))
|
|
3817
|
-
],
|
|
3818
|
-
backgroundColor: RIG_UI.panel,
|
|
3819
|
-
backgroundAlpha: 184,
|
|
3820
|
-
opacity: 1,
|
|
3821
|
-
border: false,
|
|
3822
|
-
chrome: "ad-terminal",
|
|
3823
|
-
headerText: "recovery",
|
|
3824
|
-
headerHeight: 3,
|
|
3825
|
-
paddingX: 3,
|
|
3826
|
-
paddingY: 1
|
|
3827
|
-
}],
|
|
3828
|
-
footer: { message: "enter/click a recovery action" },
|
|
3829
|
-
hideTypeBar: true,
|
|
3830
|
-
live: true
|
|
3831
|
-
});
|
|
3832
|
-
}
|
|
3833
|
-
|
|
3834
|
-
// packages/cli/src/app-opentui/scenes/help.ts
|
|
3835
|
-
function surfaceActionItem(action) {
|
|
3836
|
-
if (action.needsArgs && action.promptArgv) {
|
|
3837
|
-
return {
|
|
3838
|
-
id: action.id,
|
|
3839
|
-
label: action.label,
|
|
3840
|
-
prompt: {
|
|
3841
|
-
label: action.template ?? action.label,
|
|
3842
|
-
scene: "command",
|
|
3843
|
-
argv: [...action.promptArgv],
|
|
3844
|
-
intentKind: "command-run",
|
|
3845
|
-
payloadBase: {},
|
|
3846
|
-
payloadKey: "args",
|
|
3847
|
-
appendValueToArgv: true
|
|
3848
|
-
},
|
|
3849
|
-
message: `${action.template ?? action.label} \xB7 enter args to run`
|
|
3850
|
-
};
|
|
3851
|
-
}
|
|
3852
|
-
return {
|
|
3853
|
-
id: action.id,
|
|
3854
|
-
label: action.label,
|
|
3855
|
-
intent: intentForSurfaceAction(action),
|
|
3856
|
-
message: action.template ? `${action.template} \xB7 ${action.detail}` : action.detail
|
|
3857
|
-
};
|
|
3858
|
-
}
|
|
3859
|
-
var SHORTCUTS = [
|
|
3860
|
-
["tab / \u2192", "next workspace"],
|
|
3861
|
-
["\u2190", "previous workspace"],
|
|
3862
|
-
["esc", "back one screen \xB7 clears search filter first"],
|
|
3863
|
-
["\u2191 \u2193 / j k", "move selection"],
|
|
3864
|
-
["enter / space / click", "activate selected action"],
|
|
3865
|
-
["/", "search the current list (runs \xB7 tasks)"],
|
|
3866
|
-
[":", "command palette \u2014 type any rig command"],
|
|
3867
|
-
["mouse wheel", "scroll panel under cursor"],
|
|
3868
|
-
["ctrl-]", "detach from embedded Pi/command terminal"],
|
|
3869
|
-
["q", "back \xB7 exits from the home cockpit"],
|
|
3870
|
-
["ctrl-c", "exit"]
|
|
3871
|
-
];
|
|
3872
|
-
function panelWidth(layout) {
|
|
3873
|
-
return layout?.centerWidth ?? 100;
|
|
3874
|
-
}
|
|
3875
|
-
function panelHeight(layout, top = 1) {
|
|
3876
|
-
return Math.max(8, (layout?.centerHeight ?? 24) - top);
|
|
3877
|
-
}
|
|
3878
|
-
function unsupportedCommand(state) {
|
|
3879
|
-
const value = state.intent.action.payload?.unsupported ?? state.intent.action.payload?.unknown;
|
|
3880
|
-
if (typeof value !== "string" || !value.trim())
|
|
3881
|
-
return null;
|
|
3882
|
-
const reason = state.intent.action.payload?.reason;
|
|
3883
|
-
return { command: value.trim(), ...typeof reason === "string" && reason.trim() ? { reason: reason.trim() } : {} };
|
|
3884
|
-
}
|
|
3885
|
-
function renderHelpScene(state, layout) {
|
|
3886
|
-
const unsupported = unsupportedCommand(state);
|
|
3887
|
-
const actions = tuiSurfaceActions();
|
|
3888
|
-
const selected = Math.max(0, Math.min(actions.length - 1, state.selection.index));
|
|
3889
|
-
const actionLines = [];
|
|
3890
|
-
let previousFamily = "";
|
|
3891
|
-
actions.forEach((action, index) => {
|
|
3892
|
-
if (action.family !== previousFamily) {
|
|
3893
|
-
if (actionLines.length > 0)
|
|
3894
|
-
actionLines.push(line("", { fg: RIG_UI.ink4 }));
|
|
3895
|
-
actionLines.push(line(action.family.toUpperCase(), { fg: RIG_UI.ink3, bold: true }));
|
|
3896
|
-
previousFamily = action.family;
|
|
3897
|
-
}
|
|
3898
|
-
const label = action.id.startsWith("family:") ? action.label : ` ${action.label}`;
|
|
3899
|
-
const detail = action.template ? `${action.detail} \xB7 ${action.template}` : action.detail;
|
|
3900
|
-
actionLines.push(selectableDeckRow({ label, detail, index, active: selected === index }, surfaceActionItem(action)));
|
|
3901
|
-
});
|
|
3902
|
-
const panelLines = [
|
|
3903
|
-
...unsupported ? [
|
|
3904
|
-
line(`${unsupported.command} needs the action catalog.`, { fg: RIG_UI.yellow, bold: true }),
|
|
3905
|
-
line(unsupported.reason ?? "Pick an action below; command workspaces run inside this TUI unless RIG_PLAIN=1/--json/CI is explicit.", { fg: RIG_UI.ink3 }),
|
|
3906
|
-
line("", { fg: RIG_UI.ink3 })
|
|
3907
|
-
] : [],
|
|
3908
|
-
line("SURFACES / ACTIONS", { fg: RIG_UI.ink3, bold: true }),
|
|
3909
|
-
...actionLines,
|
|
3910
|
-
line("", { fg: RIG_UI.ink3 }),
|
|
3911
|
-
line("KEYS / MOUSE", { fg: RIG_UI.ink3, bold: true }),
|
|
3912
|
-
...SHORTCUTS.map(([label, detail]) => line(`${label.padEnd(22)} ${detail}`, { fg: RIG_UI.ink2 }))
|
|
3913
|
-
];
|
|
3914
|
-
return makeSceneFrame({
|
|
3915
|
-
scene: "help",
|
|
3916
|
-
title: "Command catalog",
|
|
3917
|
-
lines: [
|
|
3918
|
-
line("", { fg: RIG_UI.ink4 }),
|
|
3919
|
-
line(" command catalog \xB7 TUI-first Rig", { fg: RIG_UI.ink3, bold: true })
|
|
3920
|
-
],
|
|
3921
|
-
panels: [{
|
|
3922
|
-
id: "help-reference",
|
|
3923
|
-
top: 0,
|
|
3924
|
-
width: panelWidth(layout),
|
|
3925
|
-
height: panelHeight(layout),
|
|
3926
|
-
lines: panelLines,
|
|
3927
|
-
backgroundColor: RIG_UI.panel,
|
|
3928
|
-
backgroundAlpha: 184,
|
|
3929
|
-
opacity: 1,
|
|
3930
|
-
border: false,
|
|
3931
|
-
chrome: "ad-terminal",
|
|
3932
|
-
headerText: "actions",
|
|
3933
|
-
headerHeight: 3,
|
|
3934
|
-
paddingX: layout?.compact ? 2 : 3,
|
|
3935
|
-
paddingY: 1
|
|
3936
|
-
}],
|
|
3937
|
-
footer: { message: "enter/click opens a workspace" },
|
|
3938
|
-
live: true
|
|
3939
|
-
});
|
|
3940
|
-
}
|
|
3941
|
-
|
|
3942
|
-
// packages/cli/src/app-opentui/scenes/main.ts
|
|
3943
|
-
var CONFIGURED_MENU = [
|
|
3944
|
-
["init", "guided setup, reconfigure, repair project state", { id: "init", label: "init", intent: { scene: "init", argv: ["init"], action: { kind: "refresh", label: "Open init" } }, message: "open native init flow" }],
|
|
3945
|
-
["server", "local/remote target, auth, linkage", { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Checking server" } }, message: "check selected server" }],
|
|
3946
|
-
["github", "auth, selected repo, stored token", { id: "github", label: "github", intent: { scene: "server", argv: ["github", "auth", "status"], action: { kind: "refresh", label: "Checking GitHub auth" } }, message: "check GitHub auth" }],
|
|
3947
|
-
["tasks", "browse, filter, dispatch, attach active work", { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Opening tasks" } }, message: "selected tasks" }],
|
|
3948
|
-
["runs", "attach, stop, resume, inspect live sessions", { id: "runs", label: "runs", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } }, message: "selected runs" }],
|
|
3949
|
-
["inbox", "approve, reject, answer, attach", { id: "inbox", label: "inbox", intent: { scene: "inbox", argv: ["inbox"], action: { kind: "refresh", label: "Loading inbox" } }, message: "selected inbox" }],
|
|
3950
|
-
["doctor", "diagnose and repair broken surfaces", { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Running doctor" } }, message: "diagnose project" }],
|
|
3951
|
-
["browser", "browser workstation, hp-next, CDP, profile smokes", { id: "browser", label: "browser", intent: { scene: "family", argv: ["browser"], action: { kind: "refresh", label: "Open browser workstation", payload: { family: "browser" } } }, message: "browser workstation control panel" }],
|
|
3952
|
-
["stats", "fleet metrics: completion, failures, run time", { id: "stats", label: "stats", intent: { scene: "family", argv: ["stats"], action: { kind: "refresh", label: "Open stats", payload: { family: "stats" } } }, message: "fleet metrics dashboard" }],
|
|
3953
|
-
["agent", "runtime agent workspace roster and controls", { id: "agent", label: "agent", intent: { scene: "family", argv: ["agent"], action: { kind: "refresh", label: "Open agents", payload: { family: "agent" } } }, message: "agent workspace controls" }],
|
|
3954
|
-
["remote", "remote orchestration status and controls", { id: "remote", label: "remote", intent: { scene: "family", argv: ["remote"], action: { kind: "refresh", label: "Open remote", payload: { family: "remote" } } }, message: "remote orchestration controls" }],
|
|
3955
|
-
["pi", "Pi extension packages, search/add/remove/list", { id: "pi", label: "pi", intent: { scene: "pi", argv: ["pi", "list"], action: { kind: "refresh", label: "Open Pi extensions" } }, message: "Pi extension package actions" }],
|
|
3956
|
-
["inspect", "logs, artifacts, failures, graph, audit, diff", { id: "inspect", label: "inspect", intent: { scene: "inspect", argv: ["inspect"], action: { kind: "refresh", label: "Open inspect actions" } }, message: "logs/artifacts/failures/diff" }],
|
|
3957
|
-
["plugin", "plugin list, validation, contributed commands", { id: "plugin", label: "plugin", intent: { scene: "plugin", argv: ["plugin", "list"], action: { kind: "refresh", label: "Open plugin actions" } }, message: "plugin contributions" }],
|
|
3958
|
-
["repo", "repo sync, git helper, harness helper", { id: "repo", label: "repo", intent: { scene: "repo", argv: ["repo", "status"], action: { kind: "refresh", label: "Open repo/git/harness actions" } }, message: "repo/git/harness helpers" }],
|
|
3959
|
-
["workspace", "workspace topology, remotes, service helpers", { id: "workspace", label: "workspace", intent: { scene: "workspace", argv: ["workspace", "summary"], action: { kind: "refresh", label: "Open workspace summary" } }, message: "workspace topology" }],
|
|
3960
|
-
["help", "all actions, shortcuts, mouse controls", { id: "help", label: "help", intent: { scene: "help", argv: ["help"], action: { kind: "none", label: "Opening help" } }, message: "selected help" }]
|
|
3961
|
-
];
|
|
3962
|
-
var ONBOARD_PRIMARY = {
|
|
3963
|
-
id: "onboard-init",
|
|
3964
|
-
label: "Start guided init",
|
|
3965
|
-
intent: { scene: "init", argv: ["init"], action: { kind: "refresh", label: "Init" } },
|
|
3966
|
-
message: "open the native init checklist"
|
|
3967
|
-
};
|
|
3968
|
-
var ONBOARD_SECONDARY = [
|
|
3969
|
-
["server", "select a local or remote target", { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Checking server" } }, message: "check selected server" }],
|
|
3970
|
-
["doctor", "diagnose what is missing", { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Running doctor" } }, message: "diagnose missing setup" }],
|
|
3971
|
-
["help", "shortcuts and controls", { id: "help", label: "help", intent: { scene: "help", argv: ["help"], action: { kind: "none", label: "Opening help" } }, message: "selected help" }]
|
|
3972
|
-
];
|
|
3973
|
-
function record(value) {
|
|
3974
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
3975
|
-
}
|
|
3976
|
-
function serverSummary(state) {
|
|
3977
|
-
const server = record(state.data.server);
|
|
3978
|
-
const auth = record(server?.auth);
|
|
3979
|
-
const signedIn = auth?.signedIn === true || auth?.authenticated === true || auth?.status === "authenticated";
|
|
3980
|
-
const login = typeof auth?.login === "string" && auth.login.trim() ? auth.login.trim() : undefined;
|
|
3981
|
-
return {
|
|
3982
|
-
label: typeof server?.label === "string" && server.label.trim() ? server.label.trim() : state.footer.server ?? "not checked yet",
|
|
3983
|
-
target: typeof server?.kind === "string" ? `${server.kind} endpoint` : "open Server to probe",
|
|
3984
|
-
health: server ? server.reachable === false ? "unreachable" : "reachable" : "pending",
|
|
3985
|
-
auth: server ? signedIn ? `signed in${login ? ` as ${login}` : ""}` : "not signed in / unknown" : "pending"
|
|
3986
|
-
};
|
|
3987
|
-
}
|
|
3988
|
-
function inboxPending(state) {
|
|
3989
|
-
const inbox = record(state.data.inbox);
|
|
3990
|
-
const approvals = Array.isArray(inbox?.approvals) ? inbox.approvals.length : 0;
|
|
3991
|
-
const inputs = Array.isArray(inbox?.inputs) ? inbox.inputs.length : 0;
|
|
3992
|
-
return approvals + inputs;
|
|
3993
|
-
}
|
|
3994
|
-
function readyTaskCount(state) {
|
|
3995
|
-
const tasks = record(state.data.tasks);
|
|
3996
|
-
const records = Array.isArray(tasks?.records) ? tasks.records : [];
|
|
3997
|
-
return records.filter((entry) => {
|
|
3998
|
-
const status = record(entry)?.status;
|
|
3999
|
-
return typeof status === "string" && /(ready|open|todo|pending)/i.test(status);
|
|
4000
|
-
}).length;
|
|
4001
|
-
}
|
|
4002
|
-
function needsAttention(state) {
|
|
4003
|
-
const stats = fleetStatsFromState(state);
|
|
4004
|
-
let attention = stats.failed;
|
|
4005
|
-
for (const [status, count] of stats.byStatus) {
|
|
4006
|
-
if (/(needs-attention|stalled|blocked|error)/i.test(status))
|
|
4007
|
-
attention += count;
|
|
4008
|
-
}
|
|
4009
|
-
return attention;
|
|
4010
|
-
}
|
|
4011
|
-
function tile(count, label, hint, color, item, active) {
|
|
4012
|
-
const lit = count > 0;
|
|
4013
|
-
const row = selectableDeckRow({ label: `${String(count).padStart(2)} ${label}`, detail: hint, active }, item);
|
|
4014
|
-
return { ...row, fg: lit ? color : RIG_UI.ink3, bold: lit || active };
|
|
4015
|
-
}
|
|
4016
|
-
function panelWidth2(layout) {
|
|
4017
|
-
return layout?.centerWidth ?? 118;
|
|
4018
|
-
}
|
|
4019
|
-
function panelHeight2(layout) {
|
|
4020
|
-
return Math.max(22, layout?.centerHeight ?? 36);
|
|
4021
|
-
}
|
|
4022
|
-
function configuredMenuRows(selectedIndex) {
|
|
4023
|
-
return CONFIGURED_MENU.map(([label, detail, item], index) => ({
|
|
4024
|
-
...deckRow({ label, detail, index, active: index === selectedIndex, activateOnClick: true }),
|
|
4025
|
-
selectable: item
|
|
4026
|
-
}));
|
|
4027
|
-
}
|
|
4028
|
-
function onboardingRows(selectedIndex) {
|
|
4029
|
-
const primary = selectableDeckRow({ label: "init", detail: "Start guided init \u2014 recommended", active: selectedIndex === 0 }, ONBOARD_PRIMARY);
|
|
4030
|
-
const secondary = ONBOARD_SECONDARY.map(([label, detail, item], index) => ({
|
|
4031
|
-
...deckRow({ label, detail, index: index + 1, active: selectedIndex === index + 1, activateOnClick: true }),
|
|
4032
|
-
selectable: item
|
|
4033
|
-
}));
|
|
4034
|
-
return [primary, ...secondary];
|
|
4035
|
-
}
|
|
4036
|
-
var TILE_RUNS = { id: "tile-runs", label: "runs", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } }, message: "active runs \u2014 attach/stop/inspect" };
|
|
4037
|
-
var TILE_ATTENTION = { id: "tile-attention", label: "attention", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } }, message: "runs that need attention" };
|
|
4038
|
-
var TILE_INBOX = { id: "tile-inbox", label: "inbox", intent: { scene: "inbox", argv: ["inbox"], action: { kind: "refresh", label: "Loading inbox" } }, message: "pending approvals + input requests" };
|
|
4039
|
-
var TILE_TASKS = { id: "tile-tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Opening tasks" } }, message: "ready tasks \u2014 dispatch work" };
|
|
4040
|
-
function dashboardLines(state, selected) {
|
|
4041
|
-
const stats = fleetStatsFromState(state);
|
|
4042
|
-
const attention = needsAttention(state);
|
|
4043
|
-
const pending = inboxPending(state);
|
|
4044
|
-
const ready = readyTaskCount(state);
|
|
4045
|
-
const spark = sparklineString(stats.sparkline, 24);
|
|
4046
|
-
const lines = [
|
|
4047
|
-
line("FLEET", { fg: RIG_UI.ink, bold: true }),
|
|
4048
|
-
tile(stats.active, "ACTIVE RUNS", "attach \xB7 steer \xB7 stop \xB7 inspect", RIG_UI.cyan, TILE_RUNS, selected === 0),
|
|
4049
|
-
tile(attention, "NEEDS ATTENTION", "failed / stalled runs to triage", RIG_UI.red, TILE_ATTENTION, selected === 1),
|
|
4050
|
-
tile(pending, "PENDING APPROVALS", "blockers waiting on you", RIG_UI.yellow, TILE_INBOX, selected === 2),
|
|
4051
|
-
tile(ready, "READY TASKS", "dispatch a new run", RIG_UI.lime, TILE_TASKS, selected === 3)
|
|
4052
|
-
];
|
|
4053
|
-
if (spark.trim())
|
|
4054
|
-
lines.push(line(` throughput ${spark} ${stats.succeeded} done \xB7 ${stats.failed} failed \xB7 ${stats.total} total`, { fg: RIG_UI.ink3 }));
|
|
4055
|
-
return { lines, tileCount: 4 };
|
|
4056
|
-
}
|
|
4057
|
-
function statusLine(state) {
|
|
4058
|
-
const label = state.actionLabel ?? (state.runtimeReady ? "ready" : "starting up");
|
|
4059
|
-
if (state.status === "error") {
|
|
4060
|
-
return line(`\u2717 ${state.error?.message ?? label}`, { fg: RIG_UI.red, bold: true });
|
|
4061
|
-
}
|
|
4062
|
-
if (state.status === "action") {
|
|
4063
|
-
return line(`\u25D0 ${label}`, { fg: RIG_UI.cyan, bold: true });
|
|
4064
|
-
}
|
|
4065
|
-
if (state.status === "success") {
|
|
4066
|
-
return line(`\u2713 ${label}`, { fg: RIG_UI.limeDim, bold: true });
|
|
4067
|
-
}
|
|
4068
|
-
return line(`\u25CF ${label}`, { fg: RIG_UI.ink2, bold: true });
|
|
4069
|
-
}
|
|
4070
|
-
function renderMainScene(state, layout) {
|
|
4071
|
-
const configured = state.data.projectConfigured === true;
|
|
4072
|
-
const server = serverSummary(state);
|
|
4073
|
-
const selected = Math.max(0, state.selection.index);
|
|
4074
|
-
const dash = configured ? dashboardLines(state, selected) : { lines: [], tileCount: 0 };
|
|
4075
|
-
const actionSelected = configured ? selected - dash.tileCount : selected;
|
|
4076
|
-
const lines = configured ? [
|
|
4077
|
-
line("COCKPIT", { fg: RIG_UI.ink, bold: true }),
|
|
4078
|
-
line("arrows + enter or click a tile/row \xB7 esc goes back \xB7 ctrl-c exits", { fg: RIG_UI.ink3 }),
|
|
4079
|
-
blank(),
|
|
4080
|
-
...dash.lines,
|
|
4081
|
-
blank(),
|
|
4082
|
-
line("MANAGE", { fg: RIG_UI.ink, bold: true }),
|
|
4083
|
-
...configuredMenuRows(actionSelected),
|
|
4084
|
-
blank(),
|
|
4085
|
-
line("CONTEXT", { fg: RIG_UI.ink3, bold: true }),
|
|
4086
|
-
kv("config", "configured", RIG_UI.limeDim),
|
|
4087
|
-
kv("server", `${server.label} \xB7 ${server.health}`, server.health === "reachable" ? RIG_UI.limeDim : RIG_UI.yellow),
|
|
4088
|
-
kv("github", server.auth, server.auth.startsWith("signed in") ? RIG_UI.limeDim : RIG_UI.yellow),
|
|
4089
|
-
blank(),
|
|
4090
|
-
statusLine(state)
|
|
4091
|
-
] : [
|
|
4092
|
-
line("WELCOME", { fg: RIG_UI.ink, bold: true }),
|
|
4093
|
-
line("This project isn't initialized yet. One native init flow gets you running.", { fg: RIG_UI.ink2 }),
|
|
4094
|
-
blank(),
|
|
4095
|
-
line("GET STARTED", { fg: RIG_UI.ink, bold: true }),
|
|
4096
|
-
...onboardingRows(actionSelected),
|
|
4097
|
-
blank(),
|
|
4098
|
-
line("CONTEXT", { fg: RIG_UI.ink3, bold: true }),
|
|
4099
|
-
kv("config", "needs setup", RIG_UI.yellow),
|
|
4100
|
-
kv("server", `${server.label} \xB7 ${server.health}`, server.health === "reachable" ? RIG_UI.limeDim : RIG_UI.yellow),
|
|
4101
|
-
kv("github", server.auth, server.auth.startsWith("signed in") ? RIG_UI.limeDim : RIG_UI.yellow),
|
|
4102
|
-
blank(),
|
|
4103
|
-
statusLine(state)
|
|
4104
|
-
];
|
|
4105
|
-
return makeSceneFrame({
|
|
4106
|
-
scene: "main",
|
|
4107
|
-
title: "Cockpit",
|
|
4108
|
-
lines: [],
|
|
4109
|
-
panels: [{
|
|
4110
|
-
id: "cockpit",
|
|
4111
|
-
top: 0,
|
|
4112
|
-
width: panelWidth2(layout),
|
|
4113
|
-
height: panelHeight2(layout),
|
|
4114
|
-
lines,
|
|
4115
|
-
backgroundColor: RIG_UI.panel,
|
|
4116
|
-
backgroundAlpha: 184,
|
|
4117
|
-
opacity: 0.98,
|
|
4118
|
-
border: false,
|
|
4119
|
-
chrome: "ad-terminal",
|
|
4120
|
-
headerText: "cockpit",
|
|
4121
|
-
paddingX: layout?.compact ? 3 : 5,
|
|
4122
|
-
paddingY: 2
|
|
4123
|
-
}],
|
|
4124
|
-
live: true
|
|
4125
|
-
});
|
|
4126
|
-
}
|
|
4127
|
-
|
|
4128
|
-
// packages/cli/src/app-opentui/runtime.ts
|
|
4129
|
-
var PRELOADER_BACKDROP = RGBA7.fromHex(RIG_UI.bg);
|
|
4130
|
-
function inboxPendingCount(state) {
|
|
4131
|
-
const inbox = state.data.inbox;
|
|
4132
|
-
if (!inbox || typeof inbox !== "object" || Array.isArray(inbox))
|
|
4133
|
-
return 0;
|
|
4134
|
-
const record2 = inbox;
|
|
4135
|
-
const approvals = Array.isArray(record2.approvals) ? record2.approvals.length : 0;
|
|
4136
|
-
const inputs = Array.isArray(record2.inputs) ? record2.inputs.length : 0;
|
|
4137
|
-
return approvals + inputs;
|
|
4138
|
-
}
|
|
4139
|
-
var PRIMARY_NAV = [
|
|
4140
|
-
{ id: "cockpit", label: "Cockpit", scene: "main", argv: ["main"], enterLabel: "Cockpit", member: ["main"] },
|
|
4141
|
-
{ id: "runs", label: "Runs", scene: "fleet", argv: ["runs"], enterLabel: "Runs", member: ["fleet", "run-detail", "inspect", "handoff"] },
|
|
4142
|
-
{ id: "tasks", label: "Tasks", scene: "tasks", argv: ["tasks"], enterLabel: "Tasks", member: ["tasks"] },
|
|
4143
|
-
{ id: "inbox", label: "Inbox", scene: "inbox", argv: ["inbox"], enterLabel: "Inbox", member: ["inbox"], badge: inboxPendingCount },
|
|
4144
|
-
{ id: "stats", label: "Stats", scene: "family", argv: ["stats"], enterLabel: "Stats", member: ["family"] },
|
|
4145
|
-
{ id: "init", label: "Init", scene: "init", argv: ["init"], enterLabel: "Init", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
|
|
4146
|
-
{ id: "help", label: "Help", scene: "help", argv: ["help"], enterLabel: "Help", member: ["help"] }
|
|
4147
|
-
];
|
|
4148
|
-
function activeNavSection(scene) {
|
|
4149
|
-
return PRIMARY_NAV.find((section) => section.member.includes(scene)) ?? PRIMARY_NAV[0];
|
|
4150
|
-
}
|
|
4151
|
-
function entryIntentForScene(scene) {
|
|
4152
|
-
switch (scene) {
|
|
4153
|
-
case "main":
|
|
4154
|
-
return { scene, argv: ["main"], action: { kind: "refresh", label: "Cockpit" } };
|
|
4155
|
-
case "fleet":
|
|
4156
|
-
return { scene, argv: ["runs"], action: { kind: "refresh", label: "Runs" } };
|
|
4157
|
-
case "tasks":
|
|
4158
|
-
return { scene, argv: ["tasks"], action: { kind: "refresh", label: "Tasks" } };
|
|
4159
|
-
case "inbox":
|
|
4160
|
-
return { scene, argv: ["inbox"], action: { kind: "refresh", label: "Inbox" } };
|
|
4161
|
-
case "server":
|
|
4162
|
-
return { scene, argv: ["server", "status"], action: { kind: "refresh", label: "Server" } };
|
|
4163
|
-
case "doctor":
|
|
4164
|
-
return { scene, argv: ["doctor"], action: { kind: "doctor-run", label: "Doctor" } };
|
|
4165
|
-
case "init":
|
|
4166
|
-
return { scene, argv: ["init"], action: { kind: "refresh", label: "Init" } };
|
|
4167
|
-
case "run-detail":
|
|
4168
|
-
return { scene, argv: ["run", "status"], action: { kind: "refresh", label: "Run" } };
|
|
4169
|
-
case "pi":
|
|
4170
|
-
return { scene, argv: ["pi", "list"], action: { kind: "refresh", label: "Pi" } };
|
|
4171
|
-
case "plugin":
|
|
4172
|
-
return { scene, argv: ["plugin", "list"], action: { kind: "refresh", label: "Plugins" } };
|
|
4173
|
-
case "repo":
|
|
4174
|
-
return { scene, argv: ["repo", "status"], action: { kind: "refresh", label: "Repo" } };
|
|
4175
|
-
case "workspace":
|
|
4176
|
-
return { scene, argv: ["workspace", "summary"], action: { kind: "refresh", label: "Workspace" } };
|
|
4177
|
-
case "inspect":
|
|
4178
|
-
return { scene, argv: ["inspect"], action: { kind: "refresh", label: "Inspect" } };
|
|
4179
|
-
case "family":
|
|
4180
|
-
return { scene, argv: ["stats"], action: { kind: "refresh", label: "Stats", payload: { family: "stats" } } };
|
|
4181
|
-
case "help":
|
|
4182
|
-
return { scene, argv: ["help"], action: { kind: "none", label: "Help" } };
|
|
4183
|
-
case "command":
|
|
4184
|
-
case "handoff":
|
|
4185
|
-
case "error":
|
|
4186
|
-
return { scene: "main", argv: ["main"], action: { kind: "refresh", label: "Cockpit" } };
|
|
4187
|
-
}
|
|
4188
|
-
}
|
|
4189
|
-
function sceneSectionLabel(scene) {
|
|
4190
|
-
const labels = {
|
|
4191
|
-
main: "cockpit",
|
|
4192
|
-
fleet: "runs",
|
|
4193
|
-
tasks: "tasks",
|
|
4194
|
-
inbox: "inbox",
|
|
4195
|
-
inspect: "inspect",
|
|
4196
|
-
"run-detail": "run",
|
|
4197
|
-
handoff: "pi console",
|
|
4198
|
-
server: "server",
|
|
4199
|
-
doctor: "doctor",
|
|
4200
|
-
init: "init",
|
|
4201
|
-
pi: "pi",
|
|
4202
|
-
plugin: "plugins",
|
|
4203
|
-
repo: "repo",
|
|
4204
|
-
workspace: "workspace",
|
|
4205
|
-
family: "family",
|
|
4206
|
-
command: "command",
|
|
4207
|
-
help: "help",
|
|
4208
|
-
error: "error"
|
|
4209
|
-
};
|
|
4210
|
-
return labels[scene] ?? scene;
|
|
4211
|
-
}
|
|
4212
|
-
function liveLinkLabel(status) {
|
|
4213
|
-
if (status === "connected")
|
|
4214
|
-
return "\u25CF live";
|
|
4215
|
-
if (status === "connecting")
|
|
4216
|
-
return "\u25CB connecting";
|
|
4217
|
-
return "\u25CB polling";
|
|
4218
|
-
}
|
|
4219
|
-
function buildNavStrip(state) {
|
|
4220
|
-
const active = activeNavSection(state.scene);
|
|
4221
|
-
const chunks = [];
|
|
4222
|
-
PRIMARY_NAV.forEach((section, index) => {
|
|
4223
|
-
if (index > 0)
|
|
4224
|
-
chunks.push(styles.ink4(" "));
|
|
4225
|
-
const isActive = section.id === active.id;
|
|
4226
|
-
chunks.push(isActive ? otuiBold(styles.lime(section.label)) : styles.ink3(section.label));
|
|
4227
|
-
const badgeCount = section.badge ? section.badge(state) : 0;
|
|
4228
|
-
if (badgeCount > 0) {
|
|
4229
|
-
chunks.push(styles.ink4(" "));
|
|
4230
|
-
chunks.push(otuiBold(styles.yellow(String(badgeCount))));
|
|
4231
|
-
}
|
|
4232
|
-
});
|
|
4233
|
-
return new StyledText3(chunks);
|
|
4234
|
-
}
|
|
4235
|
-
function renderedItemsOrStateItems(_state, renderedItems) {
|
|
4236
|
-
return renderedItems ? [...renderedItems] : [];
|
|
4237
|
-
}
|
|
4238
|
-
function selectedRenderedItemId(state) {
|
|
4239
|
-
if (state.scene === "fleet") {
|
|
4240
|
-
const selected = state.data.selectedRunId;
|
|
4241
|
-
return typeof selected === "string" && selected.trim() ? selected.trim() : undefined;
|
|
4242
|
-
}
|
|
4243
|
-
if (state.scene === "tasks") {
|
|
4244
|
-
const selected = state.data.selectedTaskId;
|
|
4245
|
-
return typeof selected === "string" && selected.trim() ? selected.trim() : undefined;
|
|
4246
|
-
}
|
|
4247
|
-
return;
|
|
4248
|
-
}
|
|
4249
|
-
function effectiveRenderedIndex(state, items) {
|
|
4250
|
-
if (items.length === 0)
|
|
4251
|
-
return -1;
|
|
4252
|
-
const selectedId = selectedRenderedItemId(state);
|
|
4253
|
-
if (selectedId) {
|
|
4254
|
-
const renderedIndex = items.findIndex((item) => item.id === selectedId);
|
|
4255
|
-
if (renderedIndex >= 0)
|
|
4256
|
-
return renderedIndex;
|
|
4257
|
-
if (state.scene === "fleet" || state.scene === "tasks")
|
|
4258
|
-
return 0;
|
|
4259
|
-
}
|
|
4260
|
-
return Math.max(0, Math.min(items.length - 1, state.selection.index));
|
|
4261
|
-
}
|
|
4262
|
-
function selectIndex(store, index, renderedItems) {
|
|
4263
|
-
const state = store.getState();
|
|
4264
|
-
const items = renderedItemsOrStateItems(state, renderedItems);
|
|
4265
|
-
if (items.length === 0)
|
|
4266
|
-
return false;
|
|
4267
|
-
const next = Math.max(0, Math.min(items.length - 1, index));
|
|
4268
|
-
const item = items[next];
|
|
4269
|
-
store.patch({
|
|
4270
|
-
selection: { index: next, count: items.length },
|
|
4271
|
-
...item.data ? { data: item.data } : {},
|
|
4272
|
-
typeBar: { message: item.message ?? item.label }
|
|
4273
|
-
});
|
|
4274
|
-
return true;
|
|
4275
|
-
}
|
|
4276
|
-
function moveSelection(store, delta, renderedItems) {
|
|
4277
|
-
const state = store.getState();
|
|
4278
|
-
const items = renderedItemsOrStateItems(state, renderedItems);
|
|
4279
|
-
const current = effectiveRenderedIndex(state, items);
|
|
4280
|
-
return current >= 0 && selectIndex(store, current + delta, items);
|
|
4281
|
-
}
|
|
4282
|
-
function selectById(store, id, renderedItems) {
|
|
4283
|
-
const items = renderedItemsOrStateItems(store.getState(), renderedItems);
|
|
4284
|
-
const index = items.findIndex((item) => item.id === id);
|
|
4285
|
-
return index >= 0 && selectIndex(store, index, items);
|
|
4286
|
-
}
|
|
4287
|
-
var DESTRUCTIVE_ACTION_KINDS = new Set(["run-stop", "inbox-reject"]);
|
|
4288
|
-
function intentNeedsConfirm(intent2) {
|
|
4289
|
-
return DESTRUCTIVE_ACTION_KINDS.has(intent2.action.kind) || intent2.action.payload?.confirm === true;
|
|
4290
|
-
}
|
|
4291
|
-
async function activateSelection(runtime, renderedItems) {
|
|
4292
|
-
const state = runtime.getState();
|
|
4293
|
-
const items = renderedItemsOrStateItems(state, renderedItems);
|
|
4294
|
-
const index = effectiveRenderedIndex(state, items);
|
|
4295
|
-
if (index < 0)
|
|
4296
|
-
return false;
|
|
4297
|
-
const item = items[index];
|
|
4298
|
-
if (item.data)
|
|
4299
|
-
runtime.patchState({ data: item.data });
|
|
4300
|
-
if (item.prompt) {
|
|
4301
|
-
runtime.emit({ type: "typebar.patch", typeBar: { mode: "prompt", value: "", prompt: item.prompt } });
|
|
4302
|
-
return true;
|
|
4303
|
-
}
|
|
4304
|
-
if (item.intent) {
|
|
4305
|
-
if (intentNeedsConfirm(item.intent)) {
|
|
4306
|
-
runtime.emit({ type: "confirm.request", confirm: { label: item.intent.action.label ?? "Confirm action", intent: item.intent } });
|
|
4307
|
-
return true;
|
|
4308
|
-
}
|
|
4309
|
-
await runtime.runIntent(item.intent);
|
|
4310
|
-
return true;
|
|
4311
|
-
}
|
|
4312
|
-
runtime.patchState({ typeBar: { message: item.message ?? item.label } });
|
|
4313
|
-
return true;
|
|
4314
|
-
}
|
|
4315
|
-
|
|
4316
|
-
// packages/cli/src/app-opentui/react/App.tsx
|
|
4317
|
-
import { useMemo, useRef as useRef4, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
4318
|
-
import { useKeyboard, useRenderer as useRenderer4, useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
|
|
4319
|
-
|
|
4320
|
-
// packages/cli/src/app-opentui/react/Backdrop.tsx
|
|
4321
|
-
import { useEffect, useRef } from "react";
|
|
4322
|
-
import { useRenderer } from "@opentui/react";
|
|
4323
|
-
var ANIMATION_SLOWDOWN = 1;
|
|
4324
|
-
function isTerminalScene(scene) {
|
|
4325
|
-
return scene === "handoff" || scene === "command";
|
|
4326
|
-
}
|
|
4327
|
-
function Backdrop(props) {
|
|
4328
|
-
const renderer = useRenderer();
|
|
4329
|
-
const graphicsRef = useRef(null);
|
|
4330
|
-
useEffect(() => {
|
|
4331
|
-
const layout = computeStageLayout(renderer.width, renderer.height);
|
|
4332
|
-
const graphics = createGraphicsLayer(renderer, layout);
|
|
4333
|
-
renderer.root.add(graphics);
|
|
4334
|
-
graphicsRef.current = graphics;
|
|
4335
|
-
return () => {
|
|
4336
|
-
const g = graphicsRef.current;
|
|
4337
|
-
if (g) {
|
|
4338
|
-
try {
|
|
4339
|
-
renderer.root.remove(g.id);
|
|
4340
|
-
} catch {}
|
|
4341
|
-
}
|
|
4342
|
-
graphicsRef.current = null;
|
|
4343
|
-
};
|
|
4344
|
-
}, [renderer]);
|
|
4345
|
-
useEffect(() => {
|
|
4346
|
-
const graphics = graphicsRef.current;
|
|
4347
|
-
if (!graphics)
|
|
4348
|
-
return;
|
|
4349
|
-
const layout = computeStageLayout(renderer.width, renderer.height);
|
|
4350
|
-
if (layout.tooSmall) {
|
|
4351
|
-
clearGraphicsLayer(graphics);
|
|
4352
|
-
return;
|
|
4353
|
-
}
|
|
4354
|
-
const stats = fleetStatsFromState(props.state);
|
|
4355
|
-
const animationTick = Math.floor(props.state.tick / ANIMATION_SLOWDOWN);
|
|
4356
|
-
const panels = (props.frame.panels ?? []).map((panel) => ({ ...panel }));
|
|
4357
|
-
const terminalActive = props.frame.terminalActive ?? isTerminalScene(props.state.scene);
|
|
4358
|
-
const fullScreen = Boolean(props.frame.fullScreen);
|
|
4359
|
-
resizeGraphicsLayer(graphics, layout);
|
|
4360
|
-
if (terminalActive || fullScreen) {
|
|
4361
|
-
clearGraphicsLayer(graphics);
|
|
4362
|
-
} else {
|
|
4363
|
-
drawAmbientField(graphics, layout, animationTick, props.state.scene, stats.load, stats.active);
|
|
4364
|
-
drawPanelChrome(graphics, layout, panels);
|
|
4365
|
-
}
|
|
4366
|
-
});
|
|
4367
|
-
return null;
|
|
4368
|
-
}
|
|
4369
|
-
|
|
4370
|
-
// packages/cli/src/app-opentui/react/SceneFrameView.tsx
|
|
4371
|
-
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
4372
|
-
import { useRenderer as useRenderer2 } from "@opentui/react";
|
|
4373
|
-
import { TextAttributes as TextAttributes4 } from "@opentui/core";
|
|
4374
|
-
|
|
4375
|
-
// packages/cli/src/app-opentui/react/scroll.ts
|
|
4376
|
-
var registries = new Set;
|
|
4377
|
-
function registerScrollables(provider) {
|
|
4378
|
-
registries.add(provider);
|
|
4379
|
-
return () => {
|
|
4380
|
-
registries.delete(provider);
|
|
4381
|
-
};
|
|
4382
|
-
}
|
|
4383
|
-
function scrollActiveBody(delta) {
|
|
4384
|
-
for (const provider of registries) {
|
|
4385
|
-
for (const body of provider()) {
|
|
4386
|
-
if (body.visible)
|
|
4387
|
-
body.scrollBy(delta);
|
|
4388
|
-
}
|
|
4389
|
-
}
|
|
4390
|
-
}
|
|
4391
|
-
|
|
4392
|
-
// packages/cli/src/app-opentui/react/SceneFrameView.tsx
|
|
4393
|
-
import { jsxDEV, Fragment } from "@opentui/react/jsx-dev-runtime";
|
|
4394
|
-
function lineAttributes(line2) {
|
|
4395
|
-
return line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
|
|
4396
|
-
}
|
|
4397
|
-
function lineContent(line2) {
|
|
4398
|
-
return line2.styledText ?? line2.text ?? "";
|
|
4399
|
-
}
|
|
4400
|
-
function LineView(props) {
|
|
4401
|
-
const { line: line2, onLineClick } = props;
|
|
4402
|
-
const id = lineSelectableId(line2);
|
|
4403
|
-
return /* @__PURE__ */ jsxDEV("text", {
|
|
4404
|
-
style: { position: "absolute", left: props.left, top: props.top, width: props.width, zIndex: 5 },
|
|
4405
|
-
fg: line2.fg ?? RIG_UI.ink2,
|
|
4406
|
-
bg: lineBackground(line2),
|
|
4407
|
-
attributes: lineAttributes(line2),
|
|
4408
|
-
content: lineContent(line2),
|
|
4409
|
-
onMouseDown: onLineClick ? () => onLineClick(line2) : undefined,
|
|
4410
|
-
onMouseOver: id !== undefined ? () => setHoveredSelectableId(id) : undefined,
|
|
4411
|
-
onMouseOut: id !== undefined ? () => clearHoveredSelectableId(id) : undefined
|
|
4412
|
-
}, undefined, false, undefined, this);
|
|
4413
|
-
}
|
|
4414
|
-
function PanelHost(props) {
|
|
4415
|
-
const renderer = useRenderer2();
|
|
4416
|
-
const poolRef = useRef2([]);
|
|
4417
|
-
const clickRef = useRef2(props.onLineClick);
|
|
4418
|
-
clickRef.current = props.onLineClick;
|
|
4419
|
-
useEffect2(() => {
|
|
4420
|
-
const unregister = registerScrollables(() => poolRef.current);
|
|
4421
|
-
return () => {
|
|
4422
|
-
unregister();
|
|
4423
|
-
for (const panel of poolRef.current) {
|
|
4424
|
-
try {
|
|
4425
|
-
renderer.root.remove(panel.id);
|
|
4426
|
-
} catch {}
|
|
4427
|
-
}
|
|
4428
|
-
poolRef.current = [];
|
|
4429
|
-
};
|
|
4430
|
-
}, [renderer]);
|
|
4431
|
-
useEffect2(() => {
|
|
4432
|
-
const pool = poolRef.current;
|
|
4433
|
-
while (pool.length < props.panels.length) {
|
|
4434
|
-
const panel = createScrollPanelLayer(renderer, `react-scroll-panel-${pool.length}`, (line2) => clickRef.current?.(line2));
|
|
4435
|
-
renderer.root.add(panel);
|
|
4436
|
-
pool.push(panel);
|
|
4437
|
-
}
|
|
4438
|
-
applyScrollPanels(pool, props.layout, props.panels);
|
|
4439
|
-
});
|
|
4440
|
-
return null;
|
|
4441
|
-
}
|
|
4442
|
-
function NativeHostView(props) {
|
|
4443
|
-
const renderer = useRenderer2();
|
|
4444
|
-
const hostRef = useRef2(null);
|
|
4445
|
-
useEffect2(() => {
|
|
4446
|
-
const host = createNativeHost(renderer);
|
|
4447
|
-
hostRef.current = host;
|
|
4448
|
-
const unregister = registerScrollables(() => host ? [host.scrollBox] : []);
|
|
4449
|
-
return () => {
|
|
4450
|
-
unregister();
|
|
4451
|
-
destroyNativeHost(hostRef.current);
|
|
4452
|
-
hostRef.current = null;
|
|
4453
|
-
};
|
|
4454
|
-
}, [renderer]);
|
|
4455
|
-
useEffect2(() => {
|
|
4456
|
-
const host = hostRef.current;
|
|
4457
|
-
if (!host)
|
|
4458
|
-
return;
|
|
4459
|
-
if (props.native) {
|
|
4460
|
-
if (!applyNativeHost(host, props.native, props.layout))
|
|
4461
|
-
hideNativeHost(host);
|
|
4462
|
-
} else {
|
|
4463
|
-
hideNativeHost(host);
|
|
4464
|
-
}
|
|
4465
|
-
});
|
|
4466
|
-
return null;
|
|
4467
|
-
}
|
|
4468
|
-
function SceneFrameView(props) {
|
|
4469
|
-
const { frame, layout } = props;
|
|
4470
|
-
const contentLayout = frame.fullScreen ? { ...layout, centerWidth: layout.width, centerLeft: 0, centerTop: 0 } : layout;
|
|
4471
|
-
const lines = fitSceneLines(frame.lines ?? [], contentLayout);
|
|
4472
|
-
const nativeActive = frame.native && !frame.terminalActive;
|
|
4473
|
-
return /* @__PURE__ */ jsxDEV(Fragment, {
|
|
4474
|
-
children: [
|
|
4475
|
-
lines.map((line2, index) => /* @__PURE__ */ jsxDEV(LineView, {
|
|
4476
|
-
line: line2,
|
|
4477
|
-
top: contentLayout.centerTop + index,
|
|
4478
|
-
left: contentLayout.centerLeft,
|
|
4479
|
-
width: contentLayout.centerWidth,
|
|
4480
|
-
onLineClick: props.onLineClick
|
|
4481
|
-
}, `line-${index}`, false, undefined, this)),
|
|
4482
|
-
/* @__PURE__ */ jsxDEV(PanelHost, {
|
|
4483
|
-
panels: frame.panels ?? [],
|
|
4484
|
-
layout: contentLayout,
|
|
4485
|
-
onLineClick: props.onLineClick
|
|
4486
|
-
}, undefined, false, undefined, this),
|
|
4487
|
-
/* @__PURE__ */ jsxDEV(NativeHostView, {
|
|
4488
|
-
native: nativeActive ? frame.native : undefined,
|
|
4489
|
-
layout: contentLayout
|
|
4490
|
-
}, undefined, false, undefined, this)
|
|
4491
|
-
]
|
|
4492
|
-
}, undefined, true, undefined, this);
|
|
4493
|
-
}
|
|
4494
|
-
|
|
4495
|
-
// packages/cli/src/app-opentui/react/ChromeHost.tsx
|
|
4496
|
-
import { useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
4497
|
-
import { useRenderer as useRenderer3, useTerminalDimensions } from "@opentui/react";
|
|
4498
|
-
import { TextRenderable as TextRenderable5 } from "@opentui/core";
|
|
4499
|
-
function isTerminalScene2(scene) {
|
|
4500
|
-
return scene === "handoff" || scene === "command";
|
|
4501
|
-
}
|
|
4502
|
-
function ChromeHost(props) {
|
|
4503
|
-
const renderer = useRenderer3();
|
|
4504
|
-
const dims = useTerminalDimensions();
|
|
4505
|
-
const navRef = useRef3(null);
|
|
4506
|
-
const barRef = useRef3(null);
|
|
4507
|
-
useEffect3(() => {
|
|
4508
|
-
const nav = new TextRenderable5(renderer, { id: "react-primary-nav", content: "", position: "absolute", left: 0, top: -2, width: 1, zIndex: 9, selectable: false });
|
|
4509
|
-
renderer.root.add(nav);
|
|
4510
|
-
navRef.current = nav;
|
|
4511
|
-
const bar = createTypeBar(renderer);
|
|
4512
|
-
renderer.root.add(bar.background);
|
|
4513
|
-
renderer.root.add(bar.prefix);
|
|
4514
|
-
renderer.root.add(bar.input);
|
|
4515
|
-
renderer.root.add(bar.footer);
|
|
4516
|
-
barRef.current = bar;
|
|
4517
|
-
return () => {
|
|
4518
|
-
try {
|
|
4519
|
-
renderer.root.remove(nav.id);
|
|
4520
|
-
} catch {}
|
|
4521
|
-
for (const r of [bar.background, bar.prefix, bar.input, bar.footer]) {
|
|
4522
|
-
try {
|
|
4523
|
-
renderer.root.remove(r.id);
|
|
4524
|
-
} catch {}
|
|
4525
|
-
}
|
|
4526
|
-
navRef.current = null;
|
|
4527
|
-
barRef.current = null;
|
|
4528
|
-
};
|
|
4529
|
-
}, [renderer]);
|
|
4530
|
-
useEffect3(() => {
|
|
4531
|
-
const { state, frame } = props;
|
|
4532
|
-
const layout = computeStageLayout(dims.width, dims.height);
|
|
4533
|
-
const nav = navRef.current;
|
|
4534
|
-
const bar = barRef.current;
|
|
4535
|
-
const terminalActive = isTerminalScene2(state.scene);
|
|
4536
|
-
const showNav = !frame.fullScreen && !terminalActive && state.typeBar.mode !== "command";
|
|
4537
|
-
if (nav) {
|
|
4538
|
-
nav.visible = showNav;
|
|
4539
|
-
if (showNav) {
|
|
4540
|
-
nav.content = buildNavStrip(state);
|
|
4541
|
-
nav.left = layout.centerLeft;
|
|
4542
|
-
nav.top = Math.max(0, layout.centerTop - 2);
|
|
4543
|
-
nav.width = layout.centerWidth;
|
|
4544
|
-
nav.fg = RIG_UI.ink3;
|
|
4545
|
-
nav.zIndex = 9;
|
|
4546
|
-
}
|
|
4547
|
-
}
|
|
4548
|
-
if (bar) {
|
|
4549
|
-
if (frame.hideTypeBar) {
|
|
4550
|
-
hideTypeBar(bar, layout.height);
|
|
4551
|
-
} else {
|
|
4552
|
-
positionTypeBar(bar, { ...state.typeBar, placeholder: frame.typeBarPlaceholder ?? state.typeBar.placeholder }, { section: sceneSectionLabel(state.scene), ...state.footer, ...frame.footer ?? {} }, layout.width, layout.typeBarTop, layout.footerTop);
|
|
4553
|
-
}
|
|
4554
|
-
}
|
|
4555
|
-
});
|
|
4556
|
-
return null;
|
|
4557
|
-
}
|
|
4558
|
-
|
|
4559
|
-
// packages/cli/src/app-opentui/react/context.tsx
|
|
4560
|
-
import { createContext, useContext, useSyncExternalStore } from "react";
|
|
4561
|
-
import { jsxDEV as jsxDEV2 } from "@opentui/react/jsx-dev-runtime";
|
|
4562
|
-
var RigAppContext = createContext(null);
|
|
4563
|
-
function RigAppProvider(props) {
|
|
4564
|
-
return /* @__PURE__ */ jsxDEV2(RigAppContext.Provider, {
|
|
4565
|
-
value: props.value,
|
|
4566
|
-
children: props.children
|
|
4567
|
-
}, undefined, false, undefined, this);
|
|
4568
|
-
}
|
|
4569
|
-
function useCtx() {
|
|
4570
|
-
const value = useContext(RigAppContext);
|
|
4571
|
-
if (!value)
|
|
4572
|
-
throw new Error("Rig react components must render inside <RigAppProvider>.");
|
|
4573
|
-
return value;
|
|
4574
|
-
}
|
|
4575
|
-
function useAppState() {
|
|
4576
|
-
const { store } = useCtx();
|
|
4577
|
-
return useSyncExternalStore(store.subscribe, store.getState, store.getState);
|
|
4578
|
-
}
|
|
4579
|
-
function useRuntime() {
|
|
4580
|
-
return useCtx().runtime;
|
|
4581
|
-
}
|
|
4582
|
-
function useStore() {
|
|
4583
|
-
return useCtx().store;
|
|
4584
|
-
}
|
|
4585
|
-
function useRunAppAction() {
|
|
4586
|
-
return useCtx().runAppAction;
|
|
4587
|
-
}
|
|
4588
|
-
|
|
4589
|
-
// packages/cli/src/app-opentui/react/nav.ts
|
|
4590
|
-
function inboxPendingCount2(state) {
|
|
4591
|
-
const inbox = state.data.inbox;
|
|
4592
|
-
if (!inbox || typeof inbox !== "object" || Array.isArray(inbox))
|
|
4593
|
-
return 0;
|
|
4594
|
-
const record2 = inbox;
|
|
4595
|
-
const approvals = Array.isArray(record2.approvals) ? record2.approvals.length : 0;
|
|
4596
|
-
const inputs = Array.isArray(record2.inputs) ? record2.inputs.length : 0;
|
|
4597
|
-
return approvals + inputs;
|
|
4598
|
-
}
|
|
4599
|
-
var PRIMARY_NAV2 = [
|
|
4600
|
-
{ id: "cockpit", label: "Cockpit", scene: "main", argv: ["main"], enterLabel: "Cockpit", member: ["main"] },
|
|
4601
|
-
{ id: "runs", label: "Runs", scene: "fleet", argv: ["runs"], enterLabel: "Runs", member: ["fleet", "run-detail", "inspect", "handoff"] },
|
|
4602
|
-
{ id: "tasks", label: "Tasks", scene: "tasks", argv: ["tasks"], enterLabel: "Tasks", member: ["tasks"] },
|
|
4603
|
-
{ id: "inbox", label: "Inbox", scene: "inbox", argv: ["inbox"], enterLabel: "Inbox", member: ["inbox"], badge: inboxPendingCount2 },
|
|
4604
|
-
{ id: "stats", label: "Stats", scene: "family", argv: ["stats"], enterLabel: "Stats", member: ["family"] },
|
|
4605
|
-
{ id: "init", label: "Init", scene: "init", argv: ["init"], enterLabel: "Init", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
|
|
4606
|
-
{ id: "help", label: "Help", scene: "help", argv: ["help"], enterLabel: "Help", member: ["help"] }
|
|
4607
|
-
];
|
|
4608
|
-
function navSectionIntent(section) {
|
|
4609
|
-
if (section.scene === "help") {
|
|
4610
|
-
return { scene: "help", argv: ["help"], action: { kind: "none", label: "Help" } };
|
|
4611
|
-
}
|
|
4612
|
-
if (section.scene === "family") {
|
|
4613
|
-
return { scene: "family", argv: section.argv, action: { kind: "refresh", label: section.enterLabel, payload: { family: section.argv[0] } } };
|
|
4614
|
-
}
|
|
4615
|
-
return { scene: section.scene, argv: section.argv, action: { kind: "refresh", label: section.enterLabel } };
|
|
4616
|
-
}
|
|
4617
|
-
function activeNavSection2(scene) {
|
|
4618
|
-
return PRIMARY_NAV2.find((section) => section.member.includes(scene)) ?? PRIMARY_NAV2[0];
|
|
4619
|
-
}
|
|
4620
|
-
function primaryNavIntent(scene) {
|
|
4621
|
-
const section = PRIMARY_NAV2.find((entry) => entry.scene === scene || entry.member.includes(scene)) ?? PRIMARY_NAV2[0];
|
|
4622
|
-
return navSectionIntent(section);
|
|
4623
|
-
}
|
|
4624
|
-
function nextPrimaryScene(current, delta) {
|
|
4625
|
-
const active = activeNavSection2(current);
|
|
4626
|
-
const index = PRIMARY_NAV2.findIndex((entry) => entry.id === active.id);
|
|
4627
|
-
const base = index < 0 ? 0 : index;
|
|
4628
|
-
return PRIMARY_NAV2[(base + delta + PRIMARY_NAV2.length) % PRIMARY_NAV2.length].scene;
|
|
4629
|
-
}
|
|
4630
|
-
|
|
4631
|
-
// packages/cli/src/app-opentui/react/App.tsx
|
|
4632
|
-
import { jsxDEV as jsxDEV3 } from "@opentui/react/jsx-dev-runtime";
|
|
4633
|
-
var BUILTIN = {
|
|
4634
|
-
main: renderMainScene,
|
|
4635
|
-
help: renderHelpScene,
|
|
4636
|
-
error: renderErrorScene
|
|
4637
|
-
};
|
|
4638
|
-
function defaultFrame(state) {
|
|
4639
|
-
return {
|
|
4640
|
-
scene: state.scene,
|
|
4641
|
-
lines: [
|
|
4642
|
-
{ text: ` ${state.scene}`, fg: "#f4f5f8", bold: true },
|
|
4643
|
-
{ text: " Return to cockpit", fg: "#9aa0ae", selectable: { id: "home", label: "cockpit", intent: primaryNavIntent("main") } }
|
|
4644
|
-
]
|
|
4645
|
-
};
|
|
4646
|
-
}
|
|
4647
|
-
function App(props) {
|
|
4648
|
-
const state = useAppState();
|
|
4649
|
-
const runtime = useRuntime();
|
|
4650
|
-
const runAppAction = useRunAppAction();
|
|
4651
|
-
const store = useStore();
|
|
4652
|
-
const renderer = useRenderer4();
|
|
4653
|
-
useSyncExternalStore2(subscribeHover, getHoveredSelectableId, getHoveredSelectableId);
|
|
4654
|
-
const dims = useTerminalDimensions2();
|
|
4655
|
-
const layout = computeStageLayout(dims.width, dims.height);
|
|
4656
|
-
const registry = useMemo(() => ({ ...BUILTIN, ...props.sceneRenderers ?? {} }), [props.sceneRenderers]);
|
|
4657
|
-
const render = registry[state.scene];
|
|
4658
|
-
const frame = render ? render(state, layout) : defaultFrame(state);
|
|
4659
|
-
const contentLayout = frame.fullScreen ? { ...layout, centerWidth: layout.width, centerLeft: 0, centerTop: 0 } : layout;
|
|
4660
|
-
const selectables = deriveSelectables(fitSceneLines(frame.lines ?? [], contentLayout), frame.panels);
|
|
4661
|
-
const itemsRef = useRef4(selectables);
|
|
4662
|
-
itemsRef.current = selectables;
|
|
4663
|
-
useKeyboard((key) => handleAppKeyPress({
|
|
4664
|
-
renderer: () => renderer,
|
|
4665
|
-
runtime,
|
|
4666
|
-
getState: () => store.getState(),
|
|
4667
|
-
getTypeBar: () => null,
|
|
4668
|
-
getRenderedSelectableItems: () => itemsRef.current,
|
|
4669
|
-
emitTypeBarPatch: (patch) => runtime.emit({ type: "typebar.patch", typeBar: patch }),
|
|
4670
|
-
runAppAction,
|
|
4671
|
-
primaryNavIntent,
|
|
4672
|
-
nextPrimaryScene,
|
|
4673
|
-
moveSelection: (delta, items) => moveSelection(store, delta, items),
|
|
4674
|
-
activateSelection: (items) => activateSelection(runtime, items),
|
|
4675
|
-
scrollActiveBody: (delta) => scrollActiveBody(delta)
|
|
4676
|
-
}, key));
|
|
4677
|
-
const onLineClick = (line2) => {
|
|
4678
|
-
const items = itemsRef.current;
|
|
4679
|
-
const id = line2.selectable?.id ?? line2.selectableId;
|
|
4680
|
-
if (id !== undefined) {
|
|
4681
|
-
if (!selectById(store, id, items))
|
|
4682
|
-
return;
|
|
4683
|
-
} else if (line2.selectableIndex !== undefined) {
|
|
4684
|
-
selectIndex(store, line2.selectableIndex, items);
|
|
4685
|
-
} else {
|
|
4686
|
-
return;
|
|
4687
|
-
}
|
|
4688
|
-
runAppAction("Activate selection", () => activateSelection(runtime, items));
|
|
4689
|
-
};
|
|
4690
|
-
return /* @__PURE__ */ jsxDEV3("box", {
|
|
4691
|
-
style: { position: "absolute", left: 0, top: 0, width: "100%", height: "100%" },
|
|
4692
|
-
children: [
|
|
4693
|
-
/* @__PURE__ */ jsxDEV3(Backdrop, {
|
|
4694
|
-
state,
|
|
4695
|
-
frame
|
|
4696
|
-
}, undefined, false, undefined, this),
|
|
4697
|
-
/* @__PURE__ */ jsxDEV3(SceneFrameView, {
|
|
4698
|
-
frame,
|
|
4699
|
-
layout,
|
|
4700
|
-
onLineClick
|
|
4701
|
-
}, undefined, false, undefined, this),
|
|
4702
|
-
/* @__PURE__ */ jsxDEV3(ChromeHost, {
|
|
4703
|
-
state,
|
|
4704
|
-
frame
|
|
4705
|
-
}, undefined, false, undefined, this)
|
|
4706
|
-
]
|
|
4707
|
-
}, undefined, true, undefined, this);
|
|
4708
|
-
}
|
|
4709
|
-
function deriveSelectables(fittedLines, panels) {
|
|
4710
|
-
const seen = new Set;
|
|
4711
|
-
const out = [];
|
|
4712
|
-
const collect = (lines) => {
|
|
4713
|
-
for (const line2 of lines ?? []) {
|
|
4714
|
-
const item = line2.selectable;
|
|
4715
|
-
if (item && !seen.has(item.id)) {
|
|
4716
|
-
seen.add(item.id);
|
|
4717
|
-
out.push(item);
|
|
4718
|
-
}
|
|
4719
|
-
}
|
|
4720
|
-
};
|
|
4721
|
-
collect(fittedLines);
|
|
4722
|
-
for (const panel of panels ?? [])
|
|
4723
|
-
collect(panel.lines);
|
|
4724
|
-
return out;
|
|
4725
|
-
}
|
|
4726
|
-
|
|
4727
|
-
// packages/cli/src/app-opentui/react/launch.tsx
|
|
4728
|
-
import { jsxDEV as jsxDEV4 } from "@opentui/react/jsx-dev-runtime";
|
|
4729
|
-
var POLLABLE_SCENES = new Set(["main", "fleet", "tasks", "inbox", "run-detail", "inspect", "server", "doctor", "workspace"]);
|
|
4730
|
-
async function launchRigReactApp(options) {
|
|
4731
|
-
const initialState = createInitialAppState(options);
|
|
4732
|
-
const store = createAppStore(initialState);
|
|
4733
|
-
const events = createAppEventBus();
|
|
4734
|
-
let renderer = null;
|
|
4735
|
-
let root = null;
|
|
4736
|
-
let runnerPromise = null;
|
|
4737
|
-
let suppressHistoryPush = false;
|
|
4738
|
-
let resolveExit = () => {};
|
|
4739
|
-
const appExit = new Promise((resolve3) => {
|
|
4740
|
-
resolveExit = resolve3;
|
|
4741
|
-
});
|
|
4742
|
-
let relaunching = false;
|
|
4743
|
-
const mountRenderer = async () => {
|
|
4744
|
-
renderer = await createCliRenderer2({
|
|
4745
|
-
screenMode: "alternate-screen",
|
|
4746
|
-
exitOnCtrlC: false,
|
|
4747
|
-
targetFps: 30,
|
|
4748
|
-
maxFps: 30,
|
|
4749
|
-
useMouse: true,
|
|
4750
|
-
autoFocus: false,
|
|
4751
|
-
useKittyKeyboard: { disambiguate: true }
|
|
4752
|
-
});
|
|
4753
|
-
renderer.on("destroy", () => {
|
|
4754
|
-
if (!relaunching)
|
|
4755
|
-
resolveExit();
|
|
4756
|
-
});
|
|
4757
|
-
root = createRoot(renderer);
|
|
4758
|
-
root.render(/* @__PURE__ */ jsxDEV4(RigAppProvider, {
|
|
4759
|
-
value: { store, runtime, runAppAction },
|
|
4760
|
-
children: /* @__PURE__ */ jsxDEV4(App, {
|
|
4761
|
-
sceneRenderers: options.sceneRenderers
|
|
4762
|
-
}, undefined, false, undefined, this)
|
|
4763
|
-
}, undefined, false, undefined, this));
|
|
4764
|
-
relaunching = false;
|
|
4765
|
-
};
|
|
4766
|
-
const teardownRenderer = () => {
|
|
4767
|
-
relaunching = true;
|
|
4768
|
-
try {
|
|
4769
|
-
root?.unmount();
|
|
4770
|
-
} catch {}
|
|
4771
|
-
root = null;
|
|
4772
|
-
try {
|
|
4773
|
-
renderer?.destroy();
|
|
4774
|
-
} catch {}
|
|
4775
|
-
renderer = null;
|
|
4776
|
-
};
|
|
4777
|
-
const runAppAction = (label, action) => {
|
|
4778
|
-
action().catch((error) => {
|
|
4779
|
-
const n = normalizeError(error);
|
|
4780
|
-
events.emit({ type: "action.failed", label, message: n.message, hint: n.hint, cause: n.cause });
|
|
4781
|
-
});
|
|
4782
|
-
};
|
|
4783
|
-
const runtime = {
|
|
4784
|
-
getState: () => store.getState(),
|
|
4785
|
-
patchState: (patch) => store.patch(patch),
|
|
4786
|
-
emit: (event) => events.emit(event),
|
|
4787
|
-
setScene: (scene, intent2) => events.emit({ type: "scene.change", scene, ...intent2 ? { intent: intent2 } : {} }),
|
|
4788
|
-
runIntent: async (nextIntent) => {
|
|
4789
|
-
const previousScene = store.getState().scene;
|
|
4790
|
-
if (!suppressHistoryPush && nextIntent.scene !== previousScene) {
|
|
4791
|
-
store.patch({ history: [...store.getState().history, previousScene].slice(-25) });
|
|
4792
|
-
}
|
|
4793
|
-
events.emit({ type: "scene.change", scene: nextIntent.scene, intent: nextIntent });
|
|
4794
|
-
if (nextIntent.action.kind !== "none" && nextIntent.action.kind !== "navigate") {
|
|
4795
|
-
events.emit({ type: "action.started", label: nextIntent.action.label ?? nextIntent.action.kind, optimistic: { lastIntent: nextIntent } });
|
|
4796
|
-
}
|
|
4797
|
-
for (const adapter of options.adapters ?? []) {
|
|
4798
|
-
await adapter.handleIntent?.(runtime, nextIntent);
|
|
4799
|
-
}
|
|
4800
|
-
},
|
|
4801
|
-
goBack: async () => {
|
|
4802
|
-
const current = store.getState();
|
|
4803
|
-
const previous = current.history[current.history.length - 1];
|
|
4804
|
-
store.patch({ history: current.history.slice(0, -1) });
|
|
4805
|
-
suppressHistoryPush = true;
|
|
4806
|
-
try {
|
|
4807
|
-
await runtime.runIntent(entryIntentForScene(previous ?? "main"));
|
|
4808
|
-
} finally {
|
|
4809
|
-
suppressHistoryPush = false;
|
|
4810
|
-
}
|
|
4811
|
-
},
|
|
4812
|
-
getRunnerContext: async () => {
|
|
4813
|
-
if (!options.initializeRuntime)
|
|
4814
|
-
throw new Error("Runtime initializer was not provided to the OpenTUI app.");
|
|
4815
|
-
runnerPromise ??= options.initializeRuntime();
|
|
4816
|
-
return runnerPromise;
|
|
4817
|
-
},
|
|
4818
|
-
suspend: () => teardownRenderer(),
|
|
4819
|
-
resume: async () => {
|
|
4820
|
-
await mountRenderer();
|
|
4821
|
-
},
|
|
4822
|
-
destroy: () => renderer?.destroy()
|
|
4823
|
-
};
|
|
4824
|
-
events.subscribe((event) => store.patch(reduceAppEvent(store.getState(), event)));
|
|
4825
|
-
const tick = setInterval(() => store.patch({ tick: store.getState().tick + 1 }), 96);
|
|
4826
|
-
if (typeof tick.unref === "function") {
|
|
4827
|
-
tick.unref();
|
|
4828
|
-
}
|
|
4829
|
-
await mountRenderer();
|
|
4830
|
-
let liveEvents = null;
|
|
4831
|
-
const canAutoRefresh = (state) => {
|
|
4832
|
-
if (state.status === "action" || state.status === "loading")
|
|
4833
|
-
return false;
|
|
4834
|
-
if (state.confirm)
|
|
4835
|
-
return false;
|
|
4836
|
-
const mode = state.typeBar.mode ?? "nav";
|
|
4837
|
-
if (mode === "command" || mode === "prompt")
|
|
4838
|
-
return false;
|
|
4839
|
-
return POLLABLE_SCENES.has(state.scene);
|
|
4840
|
-
};
|
|
4841
|
-
const refreshCurrentDataScene = () => {
|
|
4842
|
-
const state = store.getState();
|
|
4843
|
-
if (!canAutoRefresh(state))
|
|
4844
|
-
return;
|
|
4845
|
-
const refreshIntent = entryIntentForScene(state.scene);
|
|
4846
|
-
runAppAction("live-refresh", async () => {
|
|
4847
|
-
for (const adapter of options.adapters ?? [])
|
|
4848
|
-
await adapter.handleIntent?.(runtime, refreshIntent);
|
|
4849
|
-
});
|
|
4850
|
-
};
|
|
4851
|
-
const FALLBACK_REFRESH_MS = Number(process.env.RIG_OPENTUI_FALLBACK_REFRESH_MS) || 15000;
|
|
4852
|
-
const poll = setInterval(() => {
|
|
4853
|
-
const erroredDataScene = store.getState().status === "error";
|
|
4854
|
-
if (liveEvents?.connected() && !erroredDataScene)
|
|
4855
|
-
return;
|
|
4856
|
-
refreshCurrentDataScene();
|
|
4857
|
-
}, FALLBACK_REFRESH_MS);
|
|
4858
|
-
if (typeof poll.unref === "function")
|
|
4859
|
-
poll.unref();
|
|
4860
|
-
queueMicrotask(() => {
|
|
4861
|
-
(async () => {
|
|
4862
|
-
try {
|
|
4863
|
-
if (options.initializeRuntime) {
|
|
4864
|
-
const runner = await runtime.getRunnerContext();
|
|
4865
|
-
events.emit({ type: "runtime.ready", runner });
|
|
4866
|
-
}
|
|
4867
|
-
for (const adapter of options.adapters ?? [])
|
|
4868
|
-
await adapter.start?.(runtime);
|
|
4869
|
-
connectRigServerEvents(options.projectRoot, {
|
|
4870
|
-
onSnapshotInvalidated: () => refreshCurrentDataScene(),
|
|
4871
|
-
onRunLogAppended: () => {
|
|
4872
|
-
const scene = store.getState().scene;
|
|
4873
|
-
if (scene === "run-detail" || scene === "fleet")
|
|
4874
|
-
refreshCurrentDataScene();
|
|
4875
|
-
},
|
|
4876
|
-
onStatus: (status) => store.patch({ footer: { live: liveLinkLabel(status) } })
|
|
4877
|
-
}).then((subscription) => {
|
|
4878
|
-
liveEvents = subscription;
|
|
4879
|
-
}).catch(() => {
|
|
4880
|
-
store.patch({ footer: { live: liveLinkLabel("disconnected") } });
|
|
4881
|
-
});
|
|
4882
|
-
await runtime.runIntent(store.getState().intent);
|
|
4883
|
-
} catch (error) {
|
|
4884
|
-
const n = normalizeError(error);
|
|
4885
|
-
events.emit({ type: "runtime.failed", message: n.message, hint: n.hint, cause: n.cause });
|
|
4886
|
-
}
|
|
4887
|
-
})();
|
|
4888
|
-
});
|
|
4889
|
-
await appExit;
|
|
4890
|
-
clearInterval(tick);
|
|
4891
|
-
clearInterval(poll);
|
|
4892
|
-
liveEvents?.close();
|
|
4893
|
-
teardownRenderer();
|
|
4894
|
-
}
|
|
4895
|
-
export {
|
|
4896
|
-
launchRigReactApp
|
|
4897
|
-
};
|