@h-rig/cli 0.0.6-alpha.82 → 0.0.6-alpha.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/build-rig-binaries.js +40 -8
- package/dist/bin/rig.js +2798 -1440
- package/dist/src/app/board.js +62 -13
- package/dist/src/app-opentui/adapters/command.d.ts +2 -0
- package/dist/src/app-opentui/adapters/command.js +329 -0
- package/dist/src/app-opentui/adapters/common.js +2 -2
- package/dist/src/app-opentui/adapters/doctor.js +63 -14
- package/dist/src/app-opentui/adapters/fleet.js +84 -20
- package/dist/src/app-opentui/adapters/inbox.js +83 -19
- package/dist/src/app-opentui/adapters/init.js +87 -23
- package/dist/src/app-opentui/adapters/pi-attach.js +96 -34
- package/dist/src/app-opentui/adapters/run-detail.js +83 -19
- package/dist/src/app-opentui/adapters/server.js +100 -23
- package/dist/src/app-opentui/adapters/tasks.d.ts +11 -0
- package/dist/src/app-opentui/adapters/tasks.js +742 -94
- package/dist/src/app-opentui/bootstrap.d.ts +1 -6
- package/dist/src/app-opentui/bootstrap.js +13769 -12368
- package/dist/src/app-opentui/command-pty-host.d.ts +62 -0
- package/dist/src/app-opentui/command-pty-host.js +248 -0
- package/dist/src/app-opentui/events.js +1 -1
- package/dist/src/app-opentui/focus-manager.d.ts +14 -0
- package/dist/src/app-opentui/focus-manager.js +24 -0
- package/dist/src/app-opentui/index.js +3802 -2882
- package/dist/src/app-opentui/intent.js +154 -48
- package/dist/src/app-opentui/keymap.d.ts +20 -0
- package/dist/src/app-opentui/keymap.js +707 -0
- package/dist/src/app-opentui/launch-routing.d.ts +16 -0
- package/dist/src/app-opentui/launch-routing.js +55 -0
- package/dist/src/app-opentui/layout.js +2 -2
- package/dist/src/app-opentui/pi-host-child.js +66 -16
- package/dist/src/app-opentui/pi-pty-host.d.ts +9 -0
- package/dist/src/app-opentui/pi-pty-host.js +9 -11
- package/dist/src/app-opentui/registry.js +3231 -2403
- package/dist/src/app-opentui/render/ascii-fleet.d.ts +15 -0
- package/dist/src/app-opentui/render/ascii-fleet.js +82 -0
- package/dist/src/app-opentui/render/graphics.d.ts +1 -1
- package/dist/src/app-opentui/render/graphics.js +131 -16
- package/dist/src/app-opentui/render/image-visual-layer-worker.js +408 -957
- package/dist/src/app-opentui/render/image-visual-layer.d.ts +18 -10
- package/dist/src/app-opentui/render/image-visual-layer.js +386 -356
- package/dist/src/app-opentui/render/panel-layout.d.ts +38 -0
- package/dist/src/app-opentui/render/panel-layout.js +48 -0
- package/dist/src/app-opentui/render/panels.d.ts +2 -0
- package/dist/src/app-opentui/render/panels.js +62 -29
- package/dist/src/app-opentui/render/preloader.d.ts +10 -0
- package/dist/src/app-opentui/render/preloader.js +163 -0
- package/dist/src/app-opentui/render/scene.d.ts +3 -0
- package/dist/src/app-opentui/render/scene.js +12 -0
- package/dist/src/app-opentui/render/text.js +5 -1
- package/dist/src/app-opentui/render/type-bar.d.ts +2 -1
- package/dist/src/app-opentui/render/type-bar.js +51 -16
- package/dist/src/app-opentui/runtime-resources.d.ts +16 -0
- package/dist/src/app-opentui/runtime-resources.js +62 -0
- package/dist/src/app-opentui/runtime.js +2322 -1513
- package/dist/src/app-opentui/scenes/command.d.ts +3 -0
- package/dist/src/app-opentui/scenes/command.js +103 -0
- package/dist/src/app-opentui/scenes/doctor.d.ts +2 -1
- package/dist/src/app-opentui/scenes/doctor.js +53 -14
- package/dist/src/app-opentui/scenes/error.js +44 -14
- package/dist/src/app-opentui/scenes/fleet.js +40 -21
- package/dist/src/app-opentui/scenes/handoff.js +142 -27
- package/dist/src/app-opentui/scenes/help.js +63 -48
- package/dist/src/app-opentui/scenes/inbox.d.ts +2 -1
- package/dist/src/app-opentui/scenes/inbox.js +64 -17
- package/dist/src/app-opentui/scenes/init.d.ts +2 -1
- package/dist/src/app-opentui/scenes/init.js +62 -21
- package/dist/src/app-opentui/scenes/main.d.ts +2 -1
- package/dist/src/app-opentui/scenes/main.js +80 -24
- package/dist/src/app-opentui/scenes/run-detail.d.ts +2 -1
- package/dist/src/app-opentui/scenes/run-detail.js +39 -25
- package/dist/src/app-opentui/scenes/server.d.ts +2 -1
- package/dist/src/app-opentui/scenes/server.js +71 -20
- package/dist/src/app-opentui/scenes/tasks.js +44 -22
- package/dist/src/app-opentui/state.js +70 -30
- package/dist/src/app-opentui/terminal-capabilities.d.ts +7 -0
- package/dist/src/app-opentui/terminal-capabilities.js +15 -0
- package/dist/src/app-opentui/types.d.ts +10 -2
- package/dist/src/commands/_doctor-checks.js +62 -13
- package/dist/src/commands/_operator-view.js +63 -13
- package/dist/src/commands/_pi-frontend.js +63 -13
- package/dist/src/commands/_preflight.js +64 -14
- package/dist/src/commands/_server-client.js +82 -18
- package/dist/src/commands/_snapshot-upload.js +62 -13
- package/dist/src/commands/connect.js +7 -1
- package/dist/src/commands/doctor.js +62 -13
- package/dist/src/commands/github.js +144 -23
- package/dist/src/commands/inbox.js +62 -13
- package/dist/src/commands/init.js +82 -18
- package/dist/src/commands/inspect.js +62 -13
- package/dist/src/commands/run.js +66 -13
- package/dist/src/commands/server.js +69 -14
- package/dist/src/commands/setup.js +62 -13
- package/dist/src/commands/stats.js +62 -13
- package/dist/src/commands/task-run-driver.js +62 -13
- package/dist/src/commands/task.js +65 -14
- package/dist/src/commands.js +227 -92
- package/dist/src/index.js +234 -99
- package/package.json +8 -9
- package/dist/src/app-opentui/render/image-visual-layer-native-canvas.d.ts +0 -2
- package/dist/src/app-opentui/render/image-visual-layer-native-canvas.js +0 -1484
|
@@ -44,42 +44,93 @@ function line(text, options = {}) {
|
|
|
44
44
|
function blank() {
|
|
45
45
|
return { text: "" };
|
|
46
46
|
}
|
|
47
|
-
function
|
|
48
|
-
|
|
47
|
+
function deckRow(input) {
|
|
48
|
+
const prefix = input.active ? "\u25B8" : " ";
|
|
49
|
+
const label = input.label.toUpperCase().padEnd(10);
|
|
50
|
+
return line(`${prefix} ${label} ${input.detail}`, {
|
|
51
|
+
fg: input.active ? RIG_UI.lime : RIG_UI.ink2,
|
|
52
|
+
bold: input.active,
|
|
53
|
+
selectableIndex: input.index,
|
|
54
|
+
activateOnClick: input.activateOnClick
|
|
55
|
+
});
|
|
49
56
|
}
|
|
50
57
|
function makeSceneFrame(input) {
|
|
51
58
|
return input;
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
// packages/cli/src/app-opentui/scenes/server.ts
|
|
62
|
+
function cleanVisible(value, fallback = "unknown") {
|
|
63
|
+
return value?.trim() || fallback;
|
|
64
|
+
}
|
|
65
|
+
function targetLabel(server) {
|
|
66
|
+
if (!server)
|
|
67
|
+
return "not checked";
|
|
68
|
+
return server.kind === "remote" ? "remote endpoint" : server.kind === "local" ? "local endpoint" : cleanVisible(server.baseUrl);
|
|
69
|
+
}
|
|
55
70
|
function serverState(state) {
|
|
56
71
|
const server = state.data.server;
|
|
57
72
|
return server && typeof server === "object" && !Array.isArray(server) ? server : null;
|
|
58
73
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
74
|
+
function panelWidth(layout) {
|
|
75
|
+
return layout?.centerWidth ?? 118;
|
|
76
|
+
}
|
|
77
|
+
function panelHeight(layout) {
|
|
78
|
+
return Math.max(18, layout?.centerHeight ?? 34);
|
|
79
|
+
}
|
|
80
|
+
function selectedIndex(state, count) {
|
|
81
|
+
return Math.max(0, Math.min(count - 1, state.selection.index));
|
|
82
|
+
}
|
|
83
|
+
function statusRows(server) {
|
|
61
84
|
const auth = server?.auth;
|
|
62
85
|
const signedIn = auth && (auth.signedIn === true || auth.authenticated === true || auth.status === "authenticated") ? "authenticated" : "auth unknown";
|
|
86
|
+
return [
|
|
87
|
+
line(`selected ${cleanVisible(server?.label, "not checked")}`, { fg: RIG_UI.ink2 }),
|
|
88
|
+
line(`target ${targetLabel(server)}`, { fg: RIG_UI.ink3 }),
|
|
89
|
+
line(`mode ${server?.kind ?? "local or remote"}`, { fg: RIG_UI.ink3 }),
|
|
90
|
+
line(`health ${server ? server.reachable ? "reachable" : "unreachable" : "check pending"}`, { fg: server?.reachable ? RIG_UI.limeDim : server ? RIG_UI.red : RIG_UI.yellow }),
|
|
91
|
+
line(`github ${signedIn}`, { fg: signedIn === "authenticated" ? RIG_UI.limeDim : RIG_UI.ink4 }),
|
|
92
|
+
...server?.error ? [line(`attention ${server.error}`, { fg: RIG_UI.yellow })] : []
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
function renderServerScene(state, layout) {
|
|
96
|
+
const server = serverState(state);
|
|
97
|
+
const selected = selectedIndex(state, 6);
|
|
98
|
+
const panelLines = [
|
|
99
|
+
line("server controls", { fg: RIG_UI.ink3 }),
|
|
100
|
+
blank(),
|
|
101
|
+
line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
|
|
102
|
+
deckRow({ label: "refresh", detail: "probe selected server and GitHub auth", index: 0, active: selected === 0, activateOnClick: true }),
|
|
103
|
+
deckRow({ label: "local", detail: "select local server for this repo", index: 1, active: selected === 1, activateOnClick: true }),
|
|
104
|
+
deckRow({ label: "remote", detail: "choose from saved remote servers", index: 2, active: selected === 2, activateOnClick: true }),
|
|
105
|
+
deckRow({ label: "list", detail: "show saved server aliases", index: 3, active: selected === 3, activateOnClick: true }),
|
|
106
|
+
deckRow({ label: "auth", detail: "import GitHub auth to selected server", index: 4, active: selected === 4, activateOnClick: true }),
|
|
107
|
+
deckRow({ label: "doctor", detail: "diagnose server/auth/project linkage", index: 5, active: selected === 5, activateOnClick: true }),
|
|
108
|
+
blank(),
|
|
109
|
+
line("STATUS", { fg: RIG_UI.ink3, bold: true }),
|
|
110
|
+
...statusRows(server),
|
|
111
|
+
blank(),
|
|
112
|
+
line("enter/click runs action \xB7 esc returns home", { fg: RIG_UI.ink4 })
|
|
113
|
+
];
|
|
63
114
|
return makeSceneFrame({
|
|
64
115
|
scene: "server",
|
|
65
116
|
title: "Server",
|
|
66
|
-
lines: [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
117
|
+
lines: [],
|
|
118
|
+
panels: [{
|
|
119
|
+
id: "server-actions",
|
|
120
|
+
top: 0,
|
|
121
|
+
width: panelWidth(layout),
|
|
122
|
+
height: panelHeight(layout),
|
|
123
|
+
lines: panelLines,
|
|
124
|
+
backgroundColor: RIG_UI.panel,
|
|
125
|
+
backgroundAlpha: 184,
|
|
126
|
+
opacity: 0.98,
|
|
127
|
+
border: false,
|
|
128
|
+
chrome: "ad-terminal",
|
|
129
|
+
headerText: "rig server \xB7 select / repair / auth",
|
|
130
|
+
paddingX: 5,
|
|
131
|
+
paddingY: 2
|
|
132
|
+
}],
|
|
81
133
|
footer: { server: server?.label, message: server?.latencyMs !== undefined ? `${server.latencyMs}ms` : undefined },
|
|
82
|
-
typeBarPlaceholder: "server \xB7 doctor \xB7 tasks \xB7 runs",
|
|
83
134
|
live: state.status === "action" || !server
|
|
84
135
|
});
|
|
85
136
|
}
|
|
@@ -67,6 +67,16 @@ function statusColor(status) {
|
|
|
67
67
|
function line(text, options = {}) {
|
|
68
68
|
return { text, ...options };
|
|
69
69
|
}
|
|
70
|
+
function deckRow(input) {
|
|
71
|
+
const prefix = input.active ? "\u25B8" : " ";
|
|
72
|
+
const label = input.label.toUpperCase().padEnd(10);
|
|
73
|
+
return line(`${prefix} ${label} ${input.detail}`, {
|
|
74
|
+
fg: input.active ? RIG_UI.lime : RIG_UI.ink2,
|
|
75
|
+
bold: input.active,
|
|
76
|
+
selectableIndex: input.index,
|
|
77
|
+
activateOnClick: input.activateOnClick
|
|
78
|
+
});
|
|
79
|
+
}
|
|
70
80
|
function makeSceneFrame(input) {
|
|
71
81
|
return input;
|
|
72
82
|
}
|
|
@@ -148,35 +158,41 @@ function taskRow(width, task, index, active) {
|
|
|
148
158
|
const priority = task.priority === undefined ? "prio --" : `prio ${String(task.priority).slice(0, 3).padStart(2)}`;
|
|
149
159
|
const marker = active ? "\u258C" : " ";
|
|
150
160
|
const prefix = `${marker} ${id} ${status} ${priority.padEnd(8)} `;
|
|
161
|
+
const activeRun = task.activeRun;
|
|
162
|
+
const activeSuffix = activeRun ? ` \xB7 attach ${activeRun.runId.slice(0, 8)}${activeRun.status ? ` ${activeRun.status}` : ""}` : "";
|
|
151
163
|
const titleWidth = Math.max(12, width - prefix.length - 2);
|
|
152
|
-
return line(`${prefix}${clip(task.title
|
|
164
|
+
return line(`${prefix}${clip(`${task.title}${activeSuffix}`, titleWidth)}`, {
|
|
153
165
|
fg: active ? RIG_UI.ink : statusColor(task.status),
|
|
154
166
|
bold: active,
|
|
155
|
-
selectableIndex: index
|
|
167
|
+
selectableIndex: index,
|
|
168
|
+
activateOnClick: true
|
|
156
169
|
});
|
|
157
170
|
}
|
|
158
|
-
function
|
|
171
|
+
function recoveryRows(selected, reason) {
|
|
172
|
+
return [
|
|
173
|
+
...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
|
|
174
|
+
line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
|
|
175
|
+
deckRow({ label: "refresh", detail: "reload task source", index: 0, active: selected === 0, activateOnClick: true }),
|
|
176
|
+
deckRow({ label: "next", detail: "dispatch next runnable task", index: 1, active: selected === 1, activateOnClick: true }),
|
|
177
|
+
deckRow({ label: "repair", detail: "repair project/task source link", index: 2, active: selected === 2, activateOnClick: true }),
|
|
178
|
+
deckRow({ label: "server", detail: "open server controls", index: 3, active: selected === 3, activateOnClick: true }),
|
|
179
|
+
deckRow({ label: "doctor", detail: "diagnose task source", index: 4, active: selected === 4, activateOnClick: true })
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
function emptyRows(query, total, selected) {
|
|
183
|
+
if (!query.trim() && total === 0)
|
|
184
|
+
return recoveryRows(selected, "No tasks loaded.");
|
|
159
185
|
if (query.trim() && total > 0) {
|
|
160
186
|
return [
|
|
161
|
-
|
|
162
|
-
line(`search ${JSON.stringify(query)} returned 0/${total}`, { fg: RIG_UI.ink3 }),
|
|
163
|
-
line("escape clears search", { fg: RIG_UI.ink4 })
|
|
187
|
+
...recoveryRows(selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
|
|
164
188
|
];
|
|
165
189
|
}
|
|
166
|
-
return
|
|
167
|
-
line("No tasks loaded.", { fg: RIG_UI.ink2, bold: true }),
|
|
168
|
-
line("Check the task source or switch filters from Help.", { fg: RIG_UI.ink3 }),
|
|
169
|
-
line("tab switches screens", { fg: RIG_UI.ink4 })
|
|
170
|
-
];
|
|
190
|
+
return recoveryRows(selected, "No tasks are visible with this filter.");
|
|
171
191
|
}
|
|
172
|
-
function degradedRows(state) {
|
|
192
|
+
function degradedRows(state, selected) {
|
|
173
193
|
if (!state.error)
|
|
174
194
|
return [];
|
|
175
|
-
return
|
|
176
|
-
line(`status: ${state.error.message}`, { fg: RIG_UI.red, bold: true }),
|
|
177
|
-
...state.error.hint ? [line(state.error.hint, { fg: RIG_UI.yellow })] : [],
|
|
178
|
-
line("", { fg: RIG_UI.ink3 })
|
|
179
|
-
];
|
|
195
|
+
return recoveryRows(selected, state.error.message);
|
|
180
196
|
}
|
|
181
197
|
function renderTasksScene(state, layout) {
|
|
182
198
|
const width = panelWidth(layout);
|
|
@@ -186,6 +202,7 @@ function renderTasksScene(state, layout) {
|
|
|
186
202
|
const tasks = filterTasksForSearch(allTasks, query);
|
|
187
203
|
const selected = typeof state.data.selectedTaskId === "string" && tasks.some((task) => task.id === state.data.selectedTaskId) ? state.data.selectedTaskId : tasks[0]?.id;
|
|
188
204
|
const selectedIndex = Math.max(0, tasks.findIndex((task) => task.id === selected));
|
|
205
|
+
const recoveryIndex = Math.max(0, Math.min(4, state.selection.index));
|
|
189
206
|
const dispatch = state.data.dispatchingRun;
|
|
190
207
|
const dispatchLine = dispatch && typeof dispatch === "object" && !Array.isArray(dispatch) ? ` \xB7 ${dispatch.runId ?? "new"} ${dispatch.status ?? "dispatching"}` : "";
|
|
191
208
|
const panelTop = 0;
|
|
@@ -193,16 +210,21 @@ function renderTasksScene(state, layout) {
|
|
|
193
210
|
const panelLines = [
|
|
194
211
|
line("TASK STATUS PRIORITY TITLE", { fg: RIG_UI.ink3, bold: true }),
|
|
195
212
|
line("", { fg: RIG_UI.ink3 }),
|
|
196
|
-
...
|
|
197
|
-
|
|
198
|
-
|
|
213
|
+
...tasks.length > 0 ? [
|
|
214
|
+
...degradedRows(state, recoveryIndex),
|
|
215
|
+
...query.trim() ? [line(searchSummary("tasks", query, tasks.length, allTasks.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
|
|
216
|
+
...tasks.map((task, index) => taskRow(contentWidth, task, index, index === selectedIndex))
|
|
217
|
+
] : state.error ? degradedRows(state, recoveryIndex) : [
|
|
218
|
+
...query.trim() ? [line(searchSummary("tasks", query, tasks.length, allTasks.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
|
|
219
|
+
...emptyRows(query, allTasks.length, recoveryIndex)
|
|
220
|
+
]
|
|
199
221
|
];
|
|
200
222
|
return makeSceneFrame({
|
|
201
223
|
scene: "tasks",
|
|
202
224
|
title: "Tasks",
|
|
203
225
|
lines: [
|
|
204
226
|
line("", { fg: RIG_UI.ink4 }),
|
|
205
|
-
line(query.trim() ? `
|
|
227
|
+
line(query.trim() ? ` tasks --search ${JSON.stringify(query)}` : ` tasks \xB7 ${taskViewLabel(view)}${dispatchLine}`, { fg: RIG_UI.ink3 })
|
|
206
228
|
],
|
|
207
229
|
panels: [{
|
|
208
230
|
id: "tasks-list",
|
|
@@ -215,7 +237,7 @@ function renderTasksScene(state, layout) {
|
|
|
215
237
|
opacity: 1,
|
|
216
238
|
border: false,
|
|
217
239
|
chrome: "ad-terminal",
|
|
218
|
-
headerText: query.trim() ? `
|
|
240
|
+
headerText: query.trim() ? `tasks --search ${JSON.stringify(query)}` : `tasks \xB7 ${taskViewLabel(view)}${dispatchLine}`,
|
|
219
241
|
headerHeight: 3,
|
|
220
242
|
paddingX: layout?.compact ? 2 : 3,
|
|
221
243
|
paddingY: 1
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/app-opentui/intent.ts
|
|
3
3
|
var NAV_SCENES = new Map([
|
|
4
|
-
["", "
|
|
5
|
-
["main", "
|
|
6
|
-
["home", "
|
|
4
|
+
["", "main"],
|
|
5
|
+
["main", "main"],
|
|
6
|
+
["home", "main"],
|
|
7
7
|
["fleet", "fleet"],
|
|
8
8
|
["runs", "fleet"],
|
|
9
9
|
["run", "fleet"],
|
|
10
10
|
["tasks", "tasks"],
|
|
11
11
|
["task", "tasks"],
|
|
12
|
+
["inbox", "inbox"],
|
|
13
|
+
["server", "server"],
|
|
14
|
+
["init", "init"],
|
|
15
|
+
["doctor", "doctor"],
|
|
12
16
|
["help", "help"]
|
|
13
17
|
]);
|
|
14
18
|
var TASK_VIEW_COMMANDS = new Set([
|
|
@@ -30,8 +34,11 @@ var TASK_VIEW_COMMANDS = new Set([
|
|
|
30
34
|
"search",
|
|
31
35
|
"q"
|
|
32
36
|
]);
|
|
37
|
+
function isHelpCommand(command) {
|
|
38
|
+
return command === "help" || command === "--help" || command === "-h";
|
|
39
|
+
}
|
|
33
40
|
function isTaskViewCommand(command) {
|
|
34
|
-
return !command || TASK_VIEW_COMMANDS.has(command) || command.startsWith("-");
|
|
41
|
+
return !command || TASK_VIEW_COMMANDS.has(command) || command.startsWith("-") && !isHelpCommand(command);
|
|
35
42
|
}
|
|
36
43
|
function intent(scene, argv, kind, payload, label, raw) {
|
|
37
44
|
return {
|
|
@@ -45,9 +52,12 @@ function intent(scene, argv, kind, payload, label, raw) {
|
|
|
45
52
|
}
|
|
46
53
|
};
|
|
47
54
|
}
|
|
48
|
-
function
|
|
49
|
-
const command = argv.join(" ").trim()
|
|
50
|
-
return intent("
|
|
55
|
+
function commandRunIntent(argv, raw, reason) {
|
|
56
|
+
const command = argv.join(" ").trim();
|
|
57
|
+
return intent("command", argv, "command-run", { argv: [...argv], ...reason ? { reason } : {} }, command ? `Running rig ${command}` : "Running rig", raw);
|
|
58
|
+
}
|
|
59
|
+
function unsupportedPlainCliIntent(argv, raw, reason) {
|
|
60
|
+
return commandRunIntent(argv, raw, reason);
|
|
51
61
|
}
|
|
52
62
|
function firstValueAfter(args, option) {
|
|
53
63
|
const index = args.indexOf(option);
|
|
@@ -56,11 +66,19 @@ function firstValueAfter(args, option) {
|
|
|
56
66
|
function firstNonOption(args) {
|
|
57
67
|
return args.find((part) => part.trim() && !part.startsWith("-"));
|
|
58
68
|
}
|
|
59
|
-
function
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
62
|
-
return
|
|
63
|
-
|
|
69
|
+
function inboxActionIntent(argv, command, rest, raw) {
|
|
70
|
+
const requestId = firstNonOption(rest);
|
|
71
|
+
if (command === "approve" || command === "approvals") {
|
|
72
|
+
return requestId ? intent("inbox", argv, "inbox-approve", { requestId }, `Approving ${requestId}`, raw) : intent("inbox", argv, "refresh", undefined, "Loading approvals", raw);
|
|
73
|
+
}
|
|
74
|
+
if (command === "reject") {
|
|
75
|
+
return requestId ? intent("inbox", argv, "inbox-reject", { requestId }, `Rejecting ${requestId}`, raw) : intent("inbox", argv, "refresh", undefined, "Loading approvals", raw);
|
|
76
|
+
}
|
|
77
|
+
if (command === "answer" || command === "inputs" || command === "respond") {
|
|
78
|
+
const answer = rest.slice(requestId ? 1 : 0).join(" ").trim();
|
|
79
|
+
return requestId && answer ? intent("inbox", argv, "inbox-answer", { requestId, answer }, `Answering ${requestId}`, raw) : intent("inbox", argv, "refresh", undefined, "Loading inbox", raw);
|
|
80
|
+
}
|
|
81
|
+
return intent("inbox", argv, "refresh", undefined, "Loading inbox", raw);
|
|
64
82
|
}
|
|
65
83
|
function taskViewPayload(command, rest) {
|
|
66
84
|
const normalized = command.toLowerCase();
|
|
@@ -119,22 +137,15 @@ function intentFromArgv(argv) {
|
|
|
119
137
|
const normalizedGroup = group.toLowerCase();
|
|
120
138
|
const normalizedCommand = command.toLowerCase();
|
|
121
139
|
if (!normalizedGroup)
|
|
122
|
-
return intent("
|
|
140
|
+
return intent("main", argv, "none", undefined, "Project menu");
|
|
123
141
|
if (normalizedGroup === "--help" || normalizedGroup === "-h" || normalizedGroup === "help") {
|
|
124
142
|
return argv.length === 1 ? intent("help", argv, "none", undefined, "Help") : unsupportedPlainCliIntent(argv);
|
|
125
143
|
}
|
|
126
144
|
if (normalizedGroup === "task") {
|
|
127
|
-
if (normalizedCommand === "run")
|
|
128
|
-
|
|
129
|
-
return intent("tasks", argv, "task-run-next", undefined, "Dispatching next task");
|
|
130
|
-
}
|
|
131
|
-
const task = explicitTaskIdArg(rest);
|
|
132
|
-
if (task)
|
|
133
|
-
return intent("tasks", argv, "task-run-id", { task }, `Dispatching ${task}`);
|
|
134
|
-
return intent("tasks", argv, "refresh", undefined, "Select a task, then press Enter; or use run next");
|
|
135
|
-
}
|
|
145
|
+
if (normalizedCommand === "run" || normalizedCommand === "ready" || normalizedCommand === "status")
|
|
146
|
+
return commandRunIntent(argv);
|
|
136
147
|
if (!isTaskViewCommand(normalizedCommand))
|
|
137
|
-
return
|
|
148
|
+
return commandRunIntent(argv);
|
|
138
149
|
return taskViewIntent(argv, normalizedCommand, rest);
|
|
139
150
|
}
|
|
140
151
|
if (normalizedGroup === "tasks") {
|
|
@@ -145,20 +156,49 @@ function intentFromArgv(argv) {
|
|
|
145
156
|
if (normalizedGroup === "run") {
|
|
146
157
|
if (normalizedCommand === "attach") {
|
|
147
158
|
const runId = firstNonOption(rest);
|
|
148
|
-
|
|
159
|
+
const nativeOnly = Boolean(runId) && rest.filter((part) => part.trim()).length === 1;
|
|
160
|
+
return nativeOnly ? intent("handoff", argv, "run-attach", { runId }, `Attaching ${runId}`) : commandRunIntent(argv);
|
|
149
161
|
}
|
|
150
|
-
if (!normalizedCommand)
|
|
162
|
+
if (!normalizedCommand || normalizedCommand === "list" || normalizedCommand === "status")
|
|
151
163
|
return intent("fleet", argv, "refresh", undefined, "Loading fleet");
|
|
152
|
-
|
|
164
|
+
if (normalizedCommand === "stop") {
|
|
165
|
+
const runId = firstNonOption(rest);
|
|
166
|
+
return runId && rest.filter((part) => part.trim()).length === 1 ? intent("fleet", argv, "run-stop", { runId }, "Stopping run") : commandRunIntent(argv);
|
|
167
|
+
}
|
|
168
|
+
return commandRunIntent(argv);
|
|
169
|
+
}
|
|
170
|
+
if (normalizedGroup === "server") {
|
|
171
|
+
if (!normalizedCommand || normalizedCommand === "status")
|
|
172
|
+
return intent("server", argv, "refresh", undefined, normalizedCommand ? "Checking server" : "Loading server");
|
|
173
|
+
return unsupportedPlainCliIntent(argv, undefined, `server ${normalizedCommand} is not executed in the shell; exit and run it in plain mode.`);
|
|
174
|
+
}
|
|
175
|
+
if (normalizedGroup === "github") {
|
|
176
|
+
if (normalizedCommand === "auth" && (rest[0]?.toLowerCase() ?? "status") === "status" && rest.length <= 1) {
|
|
177
|
+
return intent("server", argv, "refresh", undefined, "Checking GitHub auth");
|
|
178
|
+
}
|
|
179
|
+
return unsupportedPlainCliIntent(argv, undefined, "github auth mutations are not executed in the shell; exit and run them in plain mode.");
|
|
180
|
+
}
|
|
181
|
+
if (normalizedGroup === "init")
|
|
182
|
+
return commandRunIntent(argv);
|
|
183
|
+
if (normalizedGroup === "doctor") {
|
|
184
|
+
return normalizedCommand ? commandRunIntent(argv) : intent("doctor", argv, "doctor-run", undefined, "Running doctor");
|
|
185
|
+
}
|
|
186
|
+
if (normalizedGroup === "inbox") {
|
|
187
|
+
if (normalizedCommand === "watch")
|
|
188
|
+
return commandRunIntent(argv);
|
|
189
|
+
if ((normalizedCommand === "approvals" || normalizedCommand === "inputs") && rest.length > 0)
|
|
190
|
+
return commandRunIntent(argv);
|
|
191
|
+
return normalizedCommand && normalizedCommand !== "approvals" && normalizedCommand !== "inputs" ? commandRunIntent(argv) : inboxActionIntent(argv, normalizedCommand, rest);
|
|
153
192
|
}
|
|
154
193
|
if (normalizedGroup === "attach") {
|
|
155
|
-
const
|
|
156
|
-
|
|
194
|
+
const attachArgs = [command, ...rest].filter((part) => part.trim());
|
|
195
|
+
const runId = firstNonOption(attachArgs);
|
|
196
|
+
return runId && attachArgs.length === 1 ? intent("handoff", argv, "run-attach", { runId }, `Attaching ${runId}`) : commandRunIntent(argv);
|
|
157
197
|
}
|
|
158
198
|
const nav = NAV_SCENES.get(normalizedGroup);
|
|
159
|
-
if (nav)
|
|
199
|
+
if (nav && argv.length === 1)
|
|
160
200
|
return intent(nav, argv, "navigate", undefined, `Opening ${nav}`);
|
|
161
|
-
return
|
|
201
|
+
return commandRunIntent(argv);
|
|
162
202
|
}
|
|
163
203
|
|
|
164
204
|
// packages/cli/src/app-opentui/state.ts
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ImageProtocol } from "@earendil-works/pi-tui";
|
|
2
|
+
export type RigTerminalCapabilities = {
|
|
3
|
+
readonly imageProtocol: ImageProtocol;
|
|
4
|
+
readonly supportsKittyImages: boolean;
|
|
5
|
+
readonly visualMode: "kitty-images" | "framebuffer-fallback";
|
|
6
|
+
};
|
|
7
|
+
export declare function getRigTerminalCapabilities(): RigTerminalCapabilities;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/app-opentui/terminal-capabilities.ts
|
|
3
|
+
import { getCapabilities } from "@earendil-works/pi-tui";
|
|
4
|
+
function getRigTerminalCapabilities() {
|
|
5
|
+
const imageProtocol = getCapabilities().images;
|
|
6
|
+
const supportsKittyImages = imageProtocol === "kitty";
|
|
7
|
+
return {
|
|
8
|
+
imageProtocol,
|
|
9
|
+
supportsKittyImages,
|
|
10
|
+
visualMode: supportsKittyImages ? "kitty-images" : "framebuffer-fallback"
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
getRigTerminalCapabilities
|
|
15
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { StyledText } from "@opentui/core";
|
|
2
2
|
import type { RunnerContext } from "../runner";
|
|
3
3
|
import type { StageLayout } from "./layout";
|
|
4
|
-
export type AppSceneId = "main" | "fleet" | "tasks" | "inbox" | "server" | "init" | "doctor" | "run-detail" | "handoff" | "help" | "error";
|
|
5
|
-
export type AppActionKind = "none" | "refresh" | "navigate" | "task-run-next" | "task-run-id" | "run-stop" | "run-attach" | "run-steer" | "inbox-approve" | "inbox-reject" | "inbox-answer" | "init-start" | "doctor-run";
|
|
4
|
+
export type AppSceneId = "main" | "fleet" | "tasks" | "inbox" | "server" | "init" | "doctor" | "command" | "run-detail" | "handoff" | "help" | "error";
|
|
5
|
+
export type AppActionKind = "none" | "refresh" | "navigate" | "task-run-next" | "task-run-id" | "run-stop" | "run-attach" | "run-steer" | "inbox-approve" | "inbox-reject" | "inbox-answer" | "init-start" | "command-run" | "doctor-run";
|
|
6
6
|
export type AppIntent = {
|
|
7
7
|
readonly scene: AppSceneId;
|
|
8
8
|
readonly argv: readonly string[];
|
|
@@ -36,6 +36,13 @@ export type AppSelectionState = {
|
|
|
36
36
|
readonly index: number;
|
|
37
37
|
readonly count: number;
|
|
38
38
|
};
|
|
39
|
+
export type AppSelectableItem = {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly label: string;
|
|
42
|
+
readonly data?: Record<string, unknown>;
|
|
43
|
+
readonly intent?: AppIntent;
|
|
44
|
+
readonly message?: string;
|
|
45
|
+
};
|
|
39
46
|
export type AppState = {
|
|
40
47
|
readonly projectRoot: string;
|
|
41
48
|
readonly scene: AppSceneId;
|
|
@@ -112,6 +119,7 @@ export type AppSceneLine = {
|
|
|
112
119
|
readonly text: string;
|
|
113
120
|
readonly styledText?: StyledText;
|
|
114
121
|
readonly fg?: string;
|
|
122
|
+
readonly bg?: string;
|
|
115
123
|
readonly bold?: boolean;
|
|
116
124
|
readonly dim?: boolean;
|
|
117
125
|
readonly align?: "left" | "center" | "right";
|
|
@@ -134,17 +134,21 @@ function cleanToken(value) {
|
|
|
134
134
|
const trimmed = value?.trim();
|
|
135
135
|
return trimmed ? trimmed : null;
|
|
136
136
|
}
|
|
137
|
-
function
|
|
137
|
+
function readRemoteAuthState(projectRoot) {
|
|
138
138
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
139
139
|
if (!existsSync2(path))
|
|
140
140
|
return null;
|
|
141
141
|
try {
|
|
142
142
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
143
|
-
return
|
|
143
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
144
144
|
} catch {
|
|
145
145
|
return null;
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
149
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
150
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
151
|
+
}
|
|
148
152
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
149
153
|
const scopedKey = resolve2(projectRoot);
|
|
150
154
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -155,15 +159,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
155
159
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
156
160
|
}
|
|
157
161
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
158
|
-
const
|
|
159
|
-
|
|
162
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
163
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
164
|
+
}
|
|
165
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
166
|
+
const repo = readRepoConnection(projectRoot);
|
|
167
|
+
const slug = repo?.project?.trim();
|
|
168
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
160
169
|
return null;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
171
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
172
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
173
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
165
174
|
return null;
|
|
166
|
-
|
|
175
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
176
|
+
if (!checkoutBaseDir)
|
|
177
|
+
return null;
|
|
178
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
179
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
180
|
+
return inferred;
|
|
167
181
|
}
|
|
168
182
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
169
183
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -174,7 +188,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
174
188
|
if (selected?.connection.kind === "remote") {
|
|
175
189
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
176
190
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
177
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
191
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
178
192
|
return {
|
|
179
193
|
baseUrl: selected.connection.baseUrl,
|
|
180
194
|
authToken,
|
|
@@ -203,7 +217,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
203
217
|
if (!slug)
|
|
204
218
|
return null;
|
|
205
219
|
try {
|
|
206
|
-
const
|
|
220
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
221
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
222
|
+
url.searchParams.set("rt", authToken);
|
|
223
|
+
const response = await fetch(url, {
|
|
207
224
|
headers: mergeHeaders(undefined, authToken)
|
|
208
225
|
});
|
|
209
226
|
if (!response.ok)
|
|
@@ -220,10 +237,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
220
237
|
return null;
|
|
221
238
|
}
|
|
222
239
|
}
|
|
240
|
+
function mergeCookie(existing, name, value) {
|
|
241
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
242
|
+
if (!existing?.trim())
|
|
243
|
+
return encoded;
|
|
244
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
245
|
+
return [...parts, encoded].join("; ");
|
|
246
|
+
}
|
|
247
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
248
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
249
|
+
}
|
|
223
250
|
function mergeHeaders(headers, authToken) {
|
|
224
251
|
const merged = new Headers(headers);
|
|
225
252
|
if (authToken) {
|
|
226
|
-
|
|
253
|
+
const bearer = `Bearer ${authToken}`;
|
|
254
|
+
merged.set("authorization", bearer);
|
|
255
|
+
merged.set("x-auth", bearer);
|
|
256
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
227
257
|
}
|
|
228
258
|
return merged;
|
|
229
259
|
}
|
|
@@ -284,15 +314,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
284
314
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
285
315
|
};
|
|
286
316
|
}
|
|
317
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
318
|
+
try {
|
|
319
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
320
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
321
|
+
} catch {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
326
|
+
return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
|
|
327
|
+
}
|
|
287
328
|
async function requestServerJson(context, pathname, init = {}) {
|
|
288
329
|
const server = await ensureServerForCli(context.projectRoot);
|
|
330
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
331
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
332
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
333
|
+
throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
|
|
334
|
+
}
|
|
289
335
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
290
336
|
if (server.serverProjectRoot)
|
|
291
337
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
338
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
339
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
340
|
+
}
|
|
292
341
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
293
342
|
let response;
|
|
294
343
|
try {
|
|
295
|
-
response = await fetch(
|
|
344
|
+
response = await fetch(requestUrl, {
|
|
296
345
|
...init,
|
|
297
346
|
headers
|
|
298
347
|
});
|