@h-rig/cli 0.0.6-alpha.87 → 0.0.6-alpha.89
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/rig.js +1380 -865
- package/dist/src/app/board.js +462 -48
- package/dist/src/app-opentui/adapters/common.d.ts +3 -0
- package/dist/src/app-opentui/adapters/common.js +4 -0
- package/dist/src/app-opentui/adapters/doctor.js +458 -46
- package/dist/src/app-opentui/adapters/family.js +701 -151
- package/dist/src/app-opentui/adapters/fleet.js +477 -46
- package/dist/src/app-opentui/adapters/inbox.js +477 -46
- package/dist/src/app-opentui/adapters/init.js +497 -74
- package/dist/src/app-opentui/adapters/inspect.js +477 -46
- package/dist/src/app-opentui/adapters/pi-attach.d.ts +7 -0
- package/dist/src/app-opentui/adapters/pi-attach.js +1004 -519
- package/dist/src/app-opentui/adapters/run-detail.js +477 -46
- package/dist/src/app-opentui/adapters/server.d.ts +26 -0
- package/dist/src/app-opentui/adapters/server.js +676 -59
- package/dist/src/app-opentui/adapters/tasks.js +621 -549
- package/dist/src/app-opentui/autocomplete.js +4 -2
- package/dist/src/app-opentui/bootstrap.js +1376 -861
- package/dist/src/app-opentui/command-palette.js +37 -10
- package/dist/src/app-opentui/index.js +632 -528
- package/dist/src/app-opentui/intent.js +33 -8
- package/dist/src/app-opentui/keymap.js +43 -414
- package/dist/src/app-opentui/pi-host-child.js +496 -57
- package/dist/src/app-opentui/pi-pty-host.d.ts +14 -64
- package/dist/src/app-opentui/pi-pty-host.js +3 -397
- package/dist/src/app-opentui/react/App.js +144 -469
- package/dist/src/app-opentui/react/ChromeHost.js +44 -415
- package/dist/src/app-opentui/react/launch.js +659 -552
- package/dist/src/app-opentui/react/nav.js +1 -1
- package/dist/src/app-opentui/registry.js +1181 -742
- package/dist/src/app-opentui/remote-link.d.ts +10 -0
- package/dist/src/app-opentui/remote-link.js +47 -0
- package/dist/src/app-opentui/render/terminal-handoff.d.ts +16 -0
- package/dist/src/app-opentui/render/terminal-handoff.js +14 -0
- package/dist/src/app-opentui/runtime.js +632 -528
- package/dist/src/app-opentui/scenes/doctor.js +1 -1
- package/dist/src/app-opentui/scenes/error.js +50 -4
- package/dist/src/app-opentui/scenes/family.js +60 -6
- package/dist/src/app-opentui/scenes/fleet.js +65 -13
- package/dist/src/app-opentui/scenes/help.js +4 -2
- package/dist/src/app-opentui/scenes/init.js +12 -12
- package/dist/src/app-opentui/scenes/main.js +7 -7
- package/dist/src/app-opentui/scenes/server.js +83 -11
- package/dist/src/app-opentui/scenes/tasks.js +79 -16
- package/dist/src/app-opentui/state.js +25 -5
- package/dist/src/app-opentui/surface-catalog.js +4 -2
- package/dist/src/app-opentui/types.d.ts +1 -1
- package/dist/src/commands/_cli-format.d.ts +10 -1
- package/dist/src/commands/_cli-format.js +5 -2
- package/dist/src/commands/_connection-state.d.ts +11 -1
- package/dist/src/commands/_connection-state.js +50 -5
- package/dist/src/commands/_doctor-checks.js +458 -46
- package/dist/src/commands/_help-catalog.js +4 -2
- package/dist/src/commands/_json-output.js +4 -0
- package/dist/src/commands/_operator-view.js +496 -57
- package/dist/src/commands/_pi-frontend.d.ts +25 -0
- package/dist/src/commands/_pi-frontend.js +497 -57
- package/dist/src/commands/_preflight.js +509 -72
- package/dist/src/commands/_server-client.d.ts +33 -0
- package/dist/src/commands/_server-client.js +477 -46
- package/dist/src/commands/_server-events.js +446 -41
- package/dist/src/commands/_snapshot-upload.js +460 -48
- package/dist/src/commands/connect.js +620 -15
- package/dist/src/commands/doctor.js +458 -46
- package/dist/src/commands/github.js +462 -50
- package/dist/src/commands/inbox.js +458 -46
- package/dist/src/commands/init.js +497 -74
- package/dist/src/commands/inspect.js +458 -46
- package/dist/src/commands/run.js +496 -57
- package/dist/src/commands/server.js +647 -163
- package/dist/src/commands/setup.js +463 -51
- package/dist/src/commands/stats.js +462 -48
- package/dist/src/commands/task-run-driver.js +464 -52
- package/dist/src/commands/task.js +551 -85
- package/dist/src/commands.js +701 -151
- package/dist/src/index.js +705 -151
- package/dist/src/launcher.js +4 -0
- package/package.json +8 -8
|
@@ -215,16 +215,31 @@ function taskViewIntent(argv, command = "", rest = [], raw) {
|
|
|
215
215
|
const { payload, label } = taskViewPayload(command, rest);
|
|
216
216
|
return intent("tasks", argv, "refresh", payload, label, raw);
|
|
217
217
|
}
|
|
218
|
-
function routeServer(argv, command, raw, checkingLabel) {
|
|
219
|
-
if (!command || command === "status") {
|
|
218
|
+
function routeServer(argv, command, rest, raw, checkingLabel) {
|
|
219
|
+
if (!command || command === "status" || command === "list") {
|
|
220
220
|
return intent("server", argv, "refresh", undefined, command ? checkingLabel : "Loading server", raw);
|
|
221
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);
|
|
222
234
|
return commandRunIntent(argv, raw);
|
|
223
235
|
}
|
|
224
236
|
function routeGithub(argv, command, rest, raw) {
|
|
225
237
|
if (command === "auth" && (rest[0]?.toLowerCase() ?? "status") === "status" && rest.length <= 1) {
|
|
226
238
|
return intent("server", argv, "refresh", undefined, "Checking GitHub auth", raw);
|
|
227
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
|
+
}
|
|
228
243
|
return commandRunIntent(argv, raw);
|
|
229
244
|
}
|
|
230
245
|
function routeDoctor(argv, command, raw) {
|
|
@@ -273,11 +288,16 @@ function intentFromArgv(argv) {
|
|
|
273
288
|
return commandRunIntent(argv);
|
|
274
289
|
}
|
|
275
290
|
if (normalizedGroup === "server")
|
|
276
|
-
return routeServer(argv, normalizedCommand, undefined, "Checking server");
|
|
291
|
+
return routeServer(argv, normalizedCommand, rest, undefined, "Checking server");
|
|
277
292
|
if (normalizedGroup === "github")
|
|
278
293
|
return routeGithub(argv, normalizedCommand, rest);
|
|
279
|
-
if (normalizedGroup === "init")
|
|
280
|
-
|
|
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
|
+
}
|
|
281
301
|
if (normalizedGroup === "doctor")
|
|
282
302
|
return routeDoctor(argv, normalizedCommand);
|
|
283
303
|
if (normalizedGroup === "inbox")
|
|
@@ -340,12 +360,17 @@ function intentFromTypeBar(value) {
|
|
|
340
360
|
return commandRunIntent(parts, value);
|
|
341
361
|
return intent("tasks", parts, "refresh", undefined, "Select a task, then press Enter; or use run next", value);
|
|
342
362
|
}
|
|
343
|
-
if (first === "init")
|
|
344
|
-
|
|
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
|
+
}
|
|
345
370
|
if (first === "doctor")
|
|
346
371
|
return routeDoctor(parts, second, value);
|
|
347
372
|
if (first === "server")
|
|
348
|
-
return routeServer(parts, second, value, "Loading server");
|
|
373
|
+
return routeServer(parts, second, rest, value, "Loading server");
|
|
349
374
|
if (first === "github")
|
|
350
375
|
return routeGithub(parts, second, rest, value);
|
|
351
376
|
if (first === "inbox")
|
|
@@ -502,7 +527,7 @@ function reduceAppEvent(state, event) {
|
|
|
502
527
|
}
|
|
503
528
|
|
|
504
529
|
// packages/cli/src/app-opentui/runtime.ts
|
|
505
|
-
import { BoxRenderable as BoxRenderable3, RGBA as
|
|
530
|
+
import { BoxRenderable as BoxRenderable3, RGBA as RGBA7, StyledText as StyledText3, TextRenderable as TextRenderable4, createCliRenderer } from "@opentui/core";
|
|
506
531
|
|
|
507
532
|
// packages/cli/src/app-opentui/command-pty-host.ts
|
|
508
533
|
import { basename } from "path";
|
|
@@ -806,12 +831,13 @@ var PRIMARY_GROUPS = [
|
|
|
806
831
|
{
|
|
807
832
|
name: "server",
|
|
808
833
|
summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
|
|
809
|
-
usage: ["rig server <status|list|add|use|start> [options]"],
|
|
834
|
+
usage: ["rig server <status|list|add|use|repair-link|start> [options]"],
|
|
810
835
|
commands: [
|
|
811
|
-
{ command: "status", description: "Show the selected server for this repo.", primary: true },
|
|
836
|
+
{ command: "status", description: "Show the selected server and remote project-root link for this repo.", primary: true },
|
|
812
837
|
{ command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
|
|
813
838
|
{ command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
|
|
814
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 },
|
|
815
841
|
{ command: "list", description: "List saved local/remote server aliases.", primary: true },
|
|
816
842
|
{ command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
|
|
817
843
|
],
|
|
@@ -819,6 +845,7 @@ var PRIMARY_GROUPS = [
|
|
|
819
845
|
"rig server status",
|
|
820
846
|
"rig server add prod https://where.rig-does.work",
|
|
821
847
|
"rig server use prod",
|
|
848
|
+
"rig server repair-link --repo owner/repo",
|
|
822
849
|
"rig server use local",
|
|
823
850
|
"rig server start --port 3773"
|
|
824
851
|
],
|
|
@@ -1308,397 +1335,6 @@ function cycleCompletion(value, state, candidates = commandCandidates()) {
|
|
|
1308
1335
|
return { value: matches[nextIndex], state: { base, index: nextIndex } };
|
|
1309
1336
|
}
|
|
1310
1337
|
|
|
1311
|
-
// packages/cli/src/app-opentui/pi-pty-host.ts
|
|
1312
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1313
|
-
import { basename as basename2 } from "path";
|
|
1314
|
-
import { RGBA, StyledText, TextAttributes } from "@opentui/core";
|
|
1315
|
-
import { Terminal as XtermTerminal2 } from "@xterm/headless";
|
|
1316
|
-
var MIN_COLS2 = 40;
|
|
1317
|
-
var MIN_ROWS2 = 12;
|
|
1318
|
-
var MAX_ROWS2 = 300;
|
|
1319
|
-
var MAX_SNAPSHOT_LINES2 = 360;
|
|
1320
|
-
var STYLED_SNAPSHOT_MARGIN = 6;
|
|
1321
|
-
var SNAPSHOT_DELAY_MS2 = 120;
|
|
1322
|
-
var fallbackChildScriptPath = fileURLToPath2(new URL("./pi-host-child.ts", import.meta.url));
|
|
1323
|
-
var activeHost2 = null;
|
|
1324
|
-
function clampCols2(cols) {
|
|
1325
|
-
return Math.max(MIN_COLS2, Math.trunc(cols || 100));
|
|
1326
|
-
}
|
|
1327
|
-
function clampRows2(rows) {
|
|
1328
|
-
return Math.max(MIN_ROWS2, Math.min(MAX_ROWS2, Math.trunc(rows || 35)));
|
|
1329
|
-
}
|
|
1330
|
-
var XTERM_COLOR_MODE_INDEXED_16 = 16777216;
|
|
1331
|
-
var XTERM_COLOR_MODE_INDEXED_256 = 33554432;
|
|
1332
|
-
var XTERM_COLOR_MODE_RGB = 50331648;
|
|
1333
|
-
function rgbaFromXtermColor(mode, value) {
|
|
1334
|
-
if (mode === 1 || mode === 2 || mode === XTERM_COLOR_MODE_INDEXED_16 || mode === XTERM_COLOR_MODE_INDEXED_256) {
|
|
1335
|
-
return RGBA.fromIndex(value);
|
|
1336
|
-
}
|
|
1337
|
-
if (mode === 3 || mode === XTERM_COLOR_MODE_RGB) {
|
|
1338
|
-
return RGBA.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255);
|
|
1339
|
-
}
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1342
|
-
function textAttributesFromCell(cell) {
|
|
1343
|
-
let attributes = TextAttributes.NONE;
|
|
1344
|
-
if (cell.isBold())
|
|
1345
|
-
attributes |= TextAttributes.BOLD;
|
|
1346
|
-
if (cell.isDim())
|
|
1347
|
-
attributes |= TextAttributes.DIM;
|
|
1348
|
-
if (cell.isItalic())
|
|
1349
|
-
attributes |= TextAttributes.ITALIC;
|
|
1350
|
-
if (cell.isUnderline())
|
|
1351
|
-
attributes |= TextAttributes.UNDERLINE;
|
|
1352
|
-
if (cell.isBlink())
|
|
1353
|
-
attributes |= TextAttributes.BLINK;
|
|
1354
|
-
if (cell.isInverse())
|
|
1355
|
-
attributes |= TextAttributes.INVERSE;
|
|
1356
|
-
if (cell.isInvisible())
|
|
1357
|
-
attributes |= TextAttributes.HIDDEN;
|
|
1358
|
-
if (cell.isStrikethrough())
|
|
1359
|
-
attributes |= TextAttributes.STRIKETHROUGH;
|
|
1360
|
-
return attributes;
|
|
1361
|
-
}
|
|
1362
|
-
function sameRgba(a, b) {
|
|
1363
|
-
if (!a && !b)
|
|
1364
|
-
return true;
|
|
1365
|
-
return Boolean(a && b && a.equals(b));
|
|
1366
|
-
}
|
|
1367
|
-
function sameStyle(a, b) {
|
|
1368
|
-
return sameRgba(a.fg, b.fg) && sameRgba(a.bg, b.bg) && (a.attributes ?? 0) === (b.attributes ?? 0);
|
|
1369
|
-
}
|
|
1370
|
-
function styleFromCell(cell) {
|
|
1371
|
-
return {
|
|
1372
|
-
fg: rgbaFromXtermColor(cell.getFgColorMode(), cell.getFgColor()),
|
|
1373
|
-
bg: rgbaFromXtermColor(cell.getBgColorMode(), cell.getBgColor()),
|
|
1374
|
-
attributes: textAttributesFromCell(cell)
|
|
1375
|
-
};
|
|
1376
|
-
}
|
|
1377
|
-
function lineToStyledText(line, cols) {
|
|
1378
|
-
if (!line)
|
|
1379
|
-
return new StyledText([{ __isChunk: true, text: "" }]);
|
|
1380
|
-
const chunks = [];
|
|
1381
|
-
let run = "";
|
|
1382
|
-
let runStyle = null;
|
|
1383
|
-
const flush = () => {
|
|
1384
|
-
if (!run)
|
|
1385
|
-
return;
|
|
1386
|
-
chunks.push({
|
|
1387
|
-
__isChunk: true,
|
|
1388
|
-
text: run,
|
|
1389
|
-
...runStyle?.fg ? { fg: runStyle.fg } : {},
|
|
1390
|
-
...runStyle?.bg ? { bg: runStyle.bg } : {},
|
|
1391
|
-
...(runStyle?.attributes ?? 0) !== 0 ? { attributes: runStyle.attributes } : {}
|
|
1392
|
-
});
|
|
1393
|
-
run = "";
|
|
1394
|
-
};
|
|
1395
|
-
for (let index = 0;index < cols; index += 1) {
|
|
1396
|
-
const cell = line.getCell(index);
|
|
1397
|
-
if (!cell) {
|
|
1398
|
-
if (runStyle !== null)
|
|
1399
|
-
flush();
|
|
1400
|
-
runStyle = null;
|
|
1401
|
-
run += " ";
|
|
1402
|
-
continue;
|
|
1403
|
-
}
|
|
1404
|
-
if (cell.getWidth() === 0)
|
|
1405
|
-
continue;
|
|
1406
|
-
const style = styleFromCell(cell);
|
|
1407
|
-
if (!runStyle || !sameStyle(runStyle, style)) {
|
|
1408
|
-
flush();
|
|
1409
|
-
runStyle = style;
|
|
1410
|
-
}
|
|
1411
|
-
run += cell.getChars() || " ";
|
|
1412
|
-
}
|
|
1413
|
-
flush();
|
|
1414
|
-
return new StyledText(chunks.length > 0 ? chunks : [{ __isChunk: true, text: "" }]);
|
|
1415
|
-
}
|
|
1416
|
-
function childCommandPrefix2() {
|
|
1417
|
-
const execName = basename2(process.execPath).toLowerCase();
|
|
1418
|
-
const currentEntry = process.argv[1];
|
|
1419
|
-
if (execName === "bun" || execName === "bun.exe") {
|
|
1420
|
-
return currentEntry ? [process.execPath, currentEntry, "__opentui-pi-host"] : [process.execPath, fallbackChildScriptPath];
|
|
1421
|
-
}
|
|
1422
|
-
return [process.execPath, "__opentui-pi-host"];
|
|
1423
|
-
}
|
|
1424
|
-
function withEnv2(base) {
|
|
1425
|
-
const env = {};
|
|
1426
|
-
for (const [key, value] of Object.entries(base ?? process.env)) {
|
|
1427
|
-
if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP" || key === "NO_COLOR")
|
|
1428
|
-
continue;
|
|
1429
|
-
if (typeof value === "string")
|
|
1430
|
-
env[key] = value;
|
|
1431
|
-
}
|
|
1432
|
-
return {
|
|
1433
|
-
...env,
|
|
1434
|
-
TERM: "xterm-256color",
|
|
1435
|
-
COLORTERM: "truecolor",
|
|
1436
|
-
FORCE_COLOR: env.FORCE_COLOR ?? "1",
|
|
1437
|
-
RIG_OPENTUI_PI_HOST: "1"
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
function getActivePiHost() {
|
|
1441
|
-
return activeHost2 && !activeHost2.disposed ? activeHost2 : null;
|
|
1442
|
-
}
|
|
1443
|
-
function stopActivePiHost(reason = "detach") {
|
|
1444
|
-
activeHost2?.dispose(reason);
|
|
1445
|
-
activeHost2 = null;
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
class PiPtyHost {
|
|
1449
|
-
runId;
|
|
1450
|
-
projectRoot;
|
|
1451
|
-
onSnapshot;
|
|
1452
|
-
onExit;
|
|
1453
|
-
onError;
|
|
1454
|
-
terminal;
|
|
1455
|
-
disposables = [];
|
|
1456
|
-
decoder = new TextDecoder("utf-8");
|
|
1457
|
-
proc = null;
|
|
1458
|
-
pty = null;
|
|
1459
|
-
status = "starting";
|
|
1460
|
-
cols;
|
|
1461
|
-
rows;
|
|
1462
|
-
message = "starting bundled Pi";
|
|
1463
|
-
lastResizeError = null;
|
|
1464
|
-
exitCode;
|
|
1465
|
-
signal;
|
|
1466
|
-
notifyTimer = null;
|
|
1467
|
-
lastStreamKey = "";
|
|
1468
|
-
_disposed = false;
|
|
1469
|
-
constructor(options) {
|
|
1470
|
-
this.runId = options.runId;
|
|
1471
|
-
this.projectRoot = options.projectRoot;
|
|
1472
|
-
this.cols = clampCols2(options.cols);
|
|
1473
|
-
this.rows = clampRows2(options.rows);
|
|
1474
|
-
this.onSnapshot = options.onSnapshot;
|
|
1475
|
-
this.onExit = options.onExit;
|
|
1476
|
-
this.onError = options.onError;
|
|
1477
|
-
this.terminal = new XtermTerminal2({
|
|
1478
|
-
allowProposedApi: true,
|
|
1479
|
-
cols: this.cols,
|
|
1480
|
-
rows: this.rows,
|
|
1481
|
-
scrollback: 1000
|
|
1482
|
-
});
|
|
1483
|
-
this.registerTerminalResponders();
|
|
1484
|
-
this.disposables.push(this.terminal.onWriteParsed(() => {
|
|
1485
|
-
if (this._disposed)
|
|
1486
|
-
return;
|
|
1487
|
-
const snapshot = this.createSnapshot();
|
|
1488
|
-
const key = snapshot.lines.join(`
|
|
1489
|
-
`);
|
|
1490
|
-
if (key === this.lastStreamKey)
|
|
1491
|
-
return;
|
|
1492
|
-
this.lastStreamKey = key;
|
|
1493
|
-
this.onSnapshot?.(snapshot);
|
|
1494
|
-
}));
|
|
1495
|
-
}
|
|
1496
|
-
get disposed() {
|
|
1497
|
-
return this._disposed;
|
|
1498
|
-
}
|
|
1499
|
-
get snapshot() {
|
|
1500
|
-
return this.createSnapshot();
|
|
1501
|
-
}
|
|
1502
|
-
async start() {
|
|
1503
|
-
if (this._disposed)
|
|
1504
|
-
throw new Error("Pi PTY host is disposed.");
|
|
1505
|
-
if (typeof Bun.Terminal !== "function") {
|
|
1506
|
-
throw new Error("Bun native PTY is unavailable in this runtime. Pi host requires Bun.Terminal.");
|
|
1507
|
-
}
|
|
1508
|
-
const spawnOptions = {
|
|
1509
|
-
cwd: this.projectRoot,
|
|
1510
|
-
env: withEnv2(process.env),
|
|
1511
|
-
terminal: {
|
|
1512
|
-
cols: this.cols,
|
|
1513
|
-
rows: this.rows,
|
|
1514
|
-
name: "xterm-256color",
|
|
1515
|
-
data: (_terminal, data) => this.handlePtyData(data)
|
|
1516
|
-
}
|
|
1517
|
-
};
|
|
1518
|
-
const proc = Bun.spawn([
|
|
1519
|
-
...childCommandPrefix2(),
|
|
1520
|
-
"--run-id",
|
|
1521
|
-
this.runId,
|
|
1522
|
-
"--project-root",
|
|
1523
|
-
this.projectRoot
|
|
1524
|
-
], spawnOptions);
|
|
1525
|
-
if (!proc.terminal)
|
|
1526
|
-
throw new Error("Bun did not attach a terminal to the bundled Pi child process.");
|
|
1527
|
-
this.proc = proc;
|
|
1528
|
-
this.pty = proc.terminal;
|
|
1529
|
-
this.status = "running";
|
|
1530
|
-
this.message = "bundled Pi running inside this app";
|
|
1531
|
-
this.emitSnapshotSoon(0);
|
|
1532
|
-
proc.exited.then((exitCode) => {
|
|
1533
|
-
if (this._disposed)
|
|
1534
|
-
return;
|
|
1535
|
-
this.status = exitCode === 0 ? "exited" : "failed";
|
|
1536
|
-
this.exitCode = exitCode;
|
|
1537
|
-
this.signal = null;
|
|
1538
|
-
this.message = exitCode === 0 ? "bundled Pi exited" : `bundled Pi exited with code ${exitCode}`;
|
|
1539
|
-
const snapshot = this.createSnapshot();
|
|
1540
|
-
this.onSnapshot?.(snapshot);
|
|
1541
|
-
this.onExit?.(snapshot);
|
|
1542
|
-
if (activeHost2 === this)
|
|
1543
|
-
activeHost2 = null;
|
|
1544
|
-
this.dispose("exit", { kill: false, notify: false });
|
|
1545
|
-
}).catch((error) => {
|
|
1546
|
-
if (this._disposed)
|
|
1547
|
-
return;
|
|
1548
|
-
this.status = "failed";
|
|
1549
|
-
this.message = error instanceof Error ? error.message : String(error);
|
|
1550
|
-
const snapshot = this.createSnapshot();
|
|
1551
|
-
this.onSnapshot?.(snapshot);
|
|
1552
|
-
this.onError?.(error, snapshot);
|
|
1553
|
-
if (activeHost2 === this)
|
|
1554
|
-
activeHost2 = null;
|
|
1555
|
-
this.dispose("error", { kill: false, notify: false });
|
|
1556
|
-
});
|
|
1557
|
-
}
|
|
1558
|
-
write(data) {
|
|
1559
|
-
if (this._disposed || !this.pty)
|
|
1560
|
-
return;
|
|
1561
|
-
try {
|
|
1562
|
-
this.pty.write(data);
|
|
1563
|
-
} catch (error) {
|
|
1564
|
-
this.status = "failed";
|
|
1565
|
-
this.message = error instanceof Error ? error.message : String(error);
|
|
1566
|
-
const snapshot = this.createSnapshot();
|
|
1567
|
-
this.onSnapshot?.(snapshot);
|
|
1568
|
-
this.onError?.(error, snapshot);
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
resize(cols, rows) {
|
|
1572
|
-
const nextCols = clampCols2(cols);
|
|
1573
|
-
const nextRows = clampRows2(rows);
|
|
1574
|
-
if (nextCols === this.cols && nextRows === this.rows)
|
|
1575
|
-
return;
|
|
1576
|
-
this.cols = nextCols;
|
|
1577
|
-
this.rows = nextRows;
|
|
1578
|
-
this.terminal.resize(nextCols, nextRows);
|
|
1579
|
-
try {
|
|
1580
|
-
this.pty?.resize(nextCols, nextRows);
|
|
1581
|
-
this.lastResizeError = null;
|
|
1582
|
-
} catch (error) {
|
|
1583
|
-
this.lastResizeError = error instanceof Error ? error.message : String(error);
|
|
1584
|
-
}
|
|
1585
|
-
this.emitSnapshotSoon(0);
|
|
1586
|
-
}
|
|
1587
|
-
detach() {
|
|
1588
|
-
this.dispose("detach");
|
|
1589
|
-
return this.createSnapshot("detached from bundled Pi");
|
|
1590
|
-
}
|
|
1591
|
-
dispose(reason = "dispose", options = {}) {
|
|
1592
|
-
if (this._disposed)
|
|
1593
|
-
return;
|
|
1594
|
-
this._disposed = true;
|
|
1595
|
-
if (this.notifyTimer)
|
|
1596
|
-
clearTimeout(this.notifyTimer);
|
|
1597
|
-
this.notifyTimer = null;
|
|
1598
|
-
for (const disposable of this.disposables.splice(0)) {
|
|
1599
|
-
try {
|
|
1600
|
-
disposable.dispose();
|
|
1601
|
-
} catch {}
|
|
1602
|
-
}
|
|
1603
|
-
if (options.kill !== false) {
|
|
1604
|
-
try {
|
|
1605
|
-
this.proc?.kill("SIGTERM");
|
|
1606
|
-
} catch {}
|
|
1607
|
-
}
|
|
1608
|
-
try {
|
|
1609
|
-
this.pty?.close();
|
|
1610
|
-
} catch {}
|
|
1611
|
-
try {
|
|
1612
|
-
this.terminal.dispose();
|
|
1613
|
-
} catch {}
|
|
1614
|
-
if (activeHost2 === this)
|
|
1615
|
-
activeHost2 = null;
|
|
1616
|
-
if (options.notify) {
|
|
1617
|
-
this.message = reason;
|
|
1618
|
-
this.onSnapshot?.(this.createSnapshot(reason));
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
handlePtyData(data) {
|
|
1622
|
-
if (this._disposed)
|
|
1623
|
-
return;
|
|
1624
|
-
const text = this.decoder.decode(data, { stream: true });
|
|
1625
|
-
this.respondToRawTerminalQueries(text);
|
|
1626
|
-
this.terminal.write(data);
|
|
1627
|
-
}
|
|
1628
|
-
emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS2) {
|
|
1629
|
-
if (this._disposed || this.notifyTimer)
|
|
1630
|
-
return;
|
|
1631
|
-
this.notifyTimer = setTimeout(() => {
|
|
1632
|
-
this.notifyTimer = null;
|
|
1633
|
-
if (this._disposed)
|
|
1634
|
-
return;
|
|
1635
|
-
this.onSnapshot?.(this.createSnapshot());
|
|
1636
|
-
}, delayMs);
|
|
1637
|
-
}
|
|
1638
|
-
createSnapshot(message = this.message) {
|
|
1639
|
-
const buffer = this.terminal.buffer.active;
|
|
1640
|
-
const end = buffer.length;
|
|
1641
|
-
const start = Math.max(0, end - MAX_SNAPSHOT_LINES2);
|
|
1642
|
-
const styledStart = Math.max(start, end - this.rows - STYLED_SNAPSHOT_MARGIN);
|
|
1643
|
-
const lines = [];
|
|
1644
|
-
const styledLines = [];
|
|
1645
|
-
for (let row = start;row < end; row += 1) {
|
|
1646
|
-
const line = buffer.getLine(row);
|
|
1647
|
-
lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
|
|
1648
|
-
if (row >= styledStart)
|
|
1649
|
-
styledLines[lines.length - 1] = lineToStyledText(line, this.cols);
|
|
1650
|
-
}
|
|
1651
|
-
while (lines.length < this.rows) {
|
|
1652
|
-
lines.push("");
|
|
1653
|
-
styledLines[lines.length - 1] = new StyledText([{ __isChunk: true, text: "" }]);
|
|
1654
|
-
}
|
|
1655
|
-
const resolvedMessage = this.lastResizeError ? `resize degraded: ${this.lastResizeError}` : message;
|
|
1656
|
-
return {
|
|
1657
|
-
runId: this.runId,
|
|
1658
|
-
status: this.status,
|
|
1659
|
-
cols: this.cols,
|
|
1660
|
-
rows: this.rows,
|
|
1661
|
-
lines,
|
|
1662
|
-
styledLines,
|
|
1663
|
-
message: resolvedMessage,
|
|
1664
|
-
...this.lastResizeError ? { resizeError: this.lastResizeError } : {},
|
|
1665
|
-
...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
|
|
1666
|
-
...this.signal !== undefined ? { signal: this.signal } : {}
|
|
1667
|
-
};
|
|
1668
|
-
}
|
|
1669
|
-
registerTerminalResponders() {
|
|
1670
|
-
this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "c" }, (params) => {
|
|
1671
|
-
if (params.length === 0 || params[0] === 0)
|
|
1672
|
-
this.write("\x1B[?62;22c");
|
|
1673
|
-
return false;
|
|
1674
|
-
}));
|
|
1675
|
-
this.disposables.push(this.terminal.parser.registerCsiHandler({ prefix: ">", final: "c" }, (params) => {
|
|
1676
|
-
if (params.length === 0 || params[0] === 0)
|
|
1677
|
-
this.write("\x1B[>0;0;0c");
|
|
1678
|
-
return false;
|
|
1679
|
-
}));
|
|
1680
|
-
this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
|
|
1681
|
-
if (params[0] === 5) {
|
|
1682
|
-
this.write("\x1B[0n");
|
|
1683
|
-
} else if (params[0] === 6) {
|
|
1684
|
-
const row = this.terminal.buffer.active.cursorY + 1;
|
|
1685
|
-
const col = this.terminal.buffer.active.cursorX + 1;
|
|
1686
|
-
this.write(`\x1B[${row};${col}R`);
|
|
1687
|
-
}
|
|
1688
|
-
return false;
|
|
1689
|
-
}));
|
|
1690
|
-
}
|
|
1691
|
-
respondToRawTerminalQueries(text) {
|
|
1692
|
-
if (!text)
|
|
1693
|
-
return;
|
|
1694
|
-
const decrqm = /\x1b\[\?(\d+)\$p/g;
|
|
1695
|
-
let match;
|
|
1696
|
-
while ((match = decrqm.exec(text)) !== null) {
|
|
1697
|
-
this.write(`\x1B[?${match[1]};2$y`);
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
1338
|
// packages/cli/src/app-opentui/keymap.ts
|
|
1703
1339
|
var autocompleteState;
|
|
1704
1340
|
function clearTypeBar(context, message) {
|
|
@@ -1708,22 +1344,15 @@ function clearTypeBar(context, message) {
|
|
|
1708
1344
|
context.emitTypeBarPatch({ value: "", mode: "nav", prompt: undefined, message });
|
|
1709
1345
|
}
|
|
1710
1346
|
function handleEmbeddedTerminalKey(context, key) {
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
const
|
|
1714
|
-
const host = state.scene === "handoff" ? piHost : state.scene === "command" ? commandHost : null;
|
|
1347
|
+
if (context.getState().scene !== "command")
|
|
1348
|
+
return false;
|
|
1349
|
+
const host = getActiveCommandHost();
|
|
1715
1350
|
if (!host)
|
|
1716
1351
|
return false;
|
|
1717
1352
|
if (key.ctrl && key.name === "]") {
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
context.runAppAction("Opening runs", () => context.runtime.runIntent({ scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } }));
|
|
1722
|
-
} else {
|
|
1723
|
-
stopActiveCommandHost("operator detach");
|
|
1724
|
-
clearTypeBar(context, "closed command");
|
|
1725
|
-
context.runAppAction("Project menu", () => context.runtime.runIntent({ scene: "main", argv: ["main"], action: { kind: "none", label: "Project menu" } }));
|
|
1726
|
-
}
|
|
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" } }));
|
|
1727
1356
|
return true;
|
|
1728
1357
|
}
|
|
1729
1358
|
const sequence = key.raw || key.sequence;
|
|
@@ -1976,8 +1605,8 @@ import { WsTransport } from "@rig/client";
|
|
|
1976
1605
|
import { RIG_WS_CHANNELS } from "@rig/contracts";
|
|
1977
1606
|
|
|
1978
1607
|
// packages/cli/src/commands/_server-client.ts
|
|
1979
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
1980
|
-
import { resolve as resolve2 } from "path";
|
|
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";
|
|
1981
1610
|
|
|
1982
1611
|
// packages/cli/src/runner.ts
|
|
1983
1612
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
@@ -2069,12 +1698,28 @@ function readRepoConnection(projectRoot) {
|
|
|
2069
1698
|
selected,
|
|
2070
1699
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
2071
1700
|
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
2072
|
-
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : 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
|
|
2073
1704
|
};
|
|
2074
1705
|
}
|
|
2075
1706
|
function writeRepoConnection(projectRoot, state) {
|
|
2076
1707
|
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
2077
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
|
+
}
|
|
2078
1723
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
2079
1724
|
const repo = readRepoConnection(projectRoot);
|
|
2080
1725
|
if (!repo)
|
|
@@ -2086,13 +1731,41 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
2086
1731
|
if (!connection) {
|
|
2087
1732
|
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
2088
1733
|
}
|
|
2089
|
-
return { alias: repo.selected, connection, serverProjectRoot: repo
|
|
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
|
+
});
|
|
2090
1759
|
}
|
|
2091
|
-
function
|
|
1760
|
+
function clearRepoServerProjectRoot(projectRoot) {
|
|
2092
1761
|
const repo = readRepoConnection(projectRoot);
|
|
2093
1762
|
if (!repo)
|
|
2094
1763
|
return;
|
|
2095
|
-
writeRepoConnection(projectRoot, {
|
|
1764
|
+
writeRepoConnection(projectRoot, {
|
|
1765
|
+
selected: repo.selected,
|
|
1766
|
+
...repo.project ? { project: repo.project } : {},
|
|
1767
|
+
...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
|
|
1768
|
+
});
|
|
2096
1769
|
}
|
|
2097
1770
|
|
|
2098
1771
|
// packages/cli/src/commands/_server-client.ts
|
|
@@ -2133,11 +1806,10 @@ function readStoredGitHubAuthToken(projectRoot) {
|
|
|
2133
1806
|
const parsed = readRemoteAuthState(projectRoot);
|
|
2134
1807
|
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
2135
1808
|
}
|
|
2136
|
-
function
|
|
2137
|
-
|
|
2138
|
-
const slug = repo?.project?.trim();
|
|
2139
|
-
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
1809
|
+
function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
|
|
1810
|
+
if (!/^[^/]+\/[^/]+$/.test(repoSlug))
|
|
2140
1811
|
return null;
|
|
1812
|
+
const repo = readRepoConnection(projectRoot);
|
|
2141
1813
|
const auth = readRemoteAuthState(projectRoot);
|
|
2142
1814
|
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
2143
1815
|
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
@@ -2146,25 +1818,413 @@ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
|
2146
1818
|
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
2147
1819
|
if (!checkoutBaseDir)
|
|
2148
1820
|
return null;
|
|
2149
|
-
|
|
2150
|
-
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
2151
|
-
return inferred;
|
|
1821
|
+
return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
|
|
2152
1822
|
}
|
|
2153
1823
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
2154
1824
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
2155
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
|
+
}
|
|
2156
2216
|
async function ensureServerForCli(projectRoot) {
|
|
2157
2217
|
try {
|
|
2158
2218
|
const selected = resolveSelectedConnection(projectRoot);
|
|
2159
2219
|
if (selected?.connection.kind === "remote") {
|
|
2160
2220
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
2161
2221
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
2162
|
-
const
|
|
2222
|
+
const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
|
|
2163
2223
|
return {
|
|
2164
2224
|
baseUrl: selected.connection.baseUrl,
|
|
2165
2225
|
authToken,
|
|
2166
2226
|
connectionKind: "remote",
|
|
2167
|
-
serverProjectRoot
|
|
2227
|
+
serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
|
|
2168
2228
|
};
|
|
2169
2229
|
}
|
|
2170
2230
|
reportServerPhase("Starting local Rig server\u2026");
|
|
@@ -2182,32 +2242,6 @@ async function ensureServerForCli(projectRoot) {
|
|
|
2182
2242
|
throw error;
|
|
2183
2243
|
}
|
|
2184
2244
|
}
|
|
2185
|
-
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
2186
|
-
const repo = readRepoConnection(projectRoot);
|
|
2187
|
-
const slug = repo?.project?.trim();
|
|
2188
|
-
if (!slug)
|
|
2189
|
-
return null;
|
|
2190
|
-
try {
|
|
2191
|
-
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
2192
|
-
if (authToken && queryAuthFallbackEnabled())
|
|
2193
|
-
url.searchParams.set("rt", authToken);
|
|
2194
|
-
const response = await fetch(url, {
|
|
2195
|
-
headers: mergeHeaders(undefined, authToken)
|
|
2196
|
-
});
|
|
2197
|
-
if (!response.ok)
|
|
2198
|
-
return null;
|
|
2199
|
-
const payload = await response.json();
|
|
2200
|
-
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
2201
|
-
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
2202
|
-
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
2203
|
-
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
2204
|
-
if (path)
|
|
2205
|
-
writeRepoServerProjectRoot(projectRoot, path);
|
|
2206
|
-
return path;
|
|
2207
|
-
} catch {
|
|
2208
|
-
return null;
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
2245
|
function mergeCookie(existing, name, value) {
|
|
2212
2246
|
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
2213
2247
|
if (!existing?.trim())
|
|
@@ -2283,7 +2317,7 @@ import {
|
|
|
2283
2317
|
dim as otuiDim,
|
|
2284
2318
|
fg as otuiFg,
|
|
2285
2319
|
t,
|
|
2286
|
-
TextAttributes
|
|
2320
|
+
TextAttributes
|
|
2287
2321
|
} from "@opentui/core";
|
|
2288
2322
|
var RIG_UI = {
|
|
2289
2323
|
bg: "#070809",
|
|
@@ -2319,7 +2353,7 @@ var styles = {
|
|
|
2319
2353
|
};
|
|
2320
2354
|
|
|
2321
2355
|
// packages/cli/src/app-opentui/drone.ts
|
|
2322
|
-
import { RGBA
|
|
2356
|
+
import { RGBA, StyledText, TextAttributes as TextAttributes2 } from "@opentui/core";
|
|
2323
2357
|
var MINI_DRONE = [
|
|
2324
2358
|
"(!!!) (!!!)",
|
|
2325
2359
|
" \\%==%/ ",
|
|
@@ -2335,13 +2369,13 @@ var LEAD_MARK = [
|
|
|
2335
2369
|
var BLADE_FRAMES = ["---", "\\\\\\", "|||", "///"];
|
|
2336
2370
|
var EYE_FRAMES = ["o", "@", "\u2022", "."];
|
|
2337
2371
|
var COLOR = {
|
|
2338
|
-
body:
|
|
2339
|
-
mini:
|
|
2340
|
-
rotor:
|
|
2341
|
-
path:
|
|
2342
|
-
eye:
|
|
2343
|
-
dim:
|
|
2344
|
-
ink:
|
|
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)
|
|
2345
2379
|
};
|
|
2346
2380
|
function bladeForTick(tick, phase = 0) {
|
|
2347
2381
|
return BLADE_FRAMES[(Math.floor(tick / 3) + phase) % BLADE_FRAMES.length];
|
|
@@ -2351,12 +2385,12 @@ function eyeForTick(tick, phase = 0) {
|
|
|
2351
2385
|
return pulse > 0.55 ? EYE_FRAMES[1] : pulse > 0.1 ? EYE_FRAMES[0] : pulse > -0.45 ? EYE_FRAMES[2] : EYE_FRAMES[3];
|
|
2352
2386
|
}
|
|
2353
2387
|
function chunk(text, fg, bold = false, dim = false) {
|
|
2354
|
-
let attributes =
|
|
2388
|
+
let attributes = TextAttributes2.NONE;
|
|
2355
2389
|
if (bold)
|
|
2356
|
-
attributes |=
|
|
2390
|
+
attributes |= TextAttributes2.BOLD;
|
|
2357
2391
|
if (dim)
|
|
2358
|
-
attributes |=
|
|
2359
|
-
return { __isChunk: true, text, fg, ...attributes !==
|
|
2392
|
+
attributes |= TextAttributes2.DIM;
|
|
2393
|
+
return { __isChunk: true, text, fg, ...attributes !== TextAttributes2.NONE ? { attributes } : {} };
|
|
2360
2394
|
}
|
|
2361
2395
|
function styledLine(text, colorFor) {
|
|
2362
2396
|
const chunks = [];
|
|
@@ -2377,7 +2411,7 @@ function styledLine(text, colorFor) {
|
|
|
2377
2411
|
run += char;
|
|
2378
2412
|
}
|
|
2379
2413
|
flush();
|
|
2380
|
-
return new
|
|
2414
|
+
return new StyledText(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
|
|
2381
2415
|
}
|
|
2382
2416
|
function droneColor(char) {
|
|
2383
2417
|
if (char === "?" || char === "o" || char === "@" || char === "\u2022")
|
|
@@ -2469,7 +2503,7 @@ function makeSceneFrame(input) {
|
|
|
2469
2503
|
}
|
|
2470
2504
|
|
|
2471
2505
|
// packages/cli/src/app-opentui/render/graphics.ts
|
|
2472
|
-
import { FrameBufferRenderable, RGBA as
|
|
2506
|
+
import { FrameBufferRenderable, RGBA as RGBA2 } from "@opentui/core";
|
|
2473
2507
|
|
|
2474
2508
|
// packages/cli/src/app-opentui/render/ascii-fleet.ts
|
|
2475
2509
|
var LEAD_DRONE = [
|
|
@@ -2613,13 +2647,13 @@ var GLOW_FALLOFF_EXP_ASCII = 2.2;
|
|
|
2613
2647
|
// packages/cli/src/app-opentui/render/graphics.ts
|
|
2614
2648
|
var BRAILLE_SAMPLES_X = 2;
|
|
2615
2649
|
var BRAILLE_SAMPLES_Y = 4;
|
|
2616
|
-
var TRANSPARENT =
|
|
2617
|
-
var BACKDROP =
|
|
2618
|
-
var PANEL_BG =
|
|
2619
|
-
var PANEL_HEADER_BG =
|
|
2620
|
-
var PANEL_LINE =
|
|
2621
|
-
var PANEL_LINE_DIM =
|
|
2622
|
-
var PANEL_TEXT_DIM =
|
|
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);
|
|
2623
2657
|
var AC_RGB = [204, 255, 77];
|
|
2624
2658
|
var C2_RGB = [86, 216, 255];
|
|
2625
2659
|
var MG_RGB = [255, 121, 176];
|
|
@@ -2724,7 +2758,7 @@ function paletteColor(ac, c2, mg, ink) {
|
|
|
2724
2758
|
const b = (AC_RGB[2] * ac + C2_RGB[2] * c2 + MG_RGB[2] * mg + INK_RGB[2] * ink) / total;
|
|
2725
2759
|
const intensity = Math.min(1, total / 4);
|
|
2726
2760
|
const lift = 0.34 + intensity * 0.66;
|
|
2727
|
-
return
|
|
2761
|
+
return RGBA2.fromInts(Math.round(r * lift), Math.round(g * lift), Math.round(b * lift), 255);
|
|
2728
2762
|
}
|
|
2729
2763
|
function clearCanvas(canvas) {
|
|
2730
2764
|
canvas.ac.fill(0);
|
|
@@ -3079,8 +3113,8 @@ function staticFleetTick(scene) {
|
|
|
3079
3113
|
}
|
|
3080
3114
|
function withAlpha(hex, alpha) {
|
|
3081
3115
|
const a = Math.max(0, Math.min(255, Math.round(alpha * 255)));
|
|
3082
|
-
const [r, g, b] =
|
|
3083
|
-
return
|
|
3116
|
+
const [r, g, b] = RGBA2.fromHex(hex).toInts();
|
|
3117
|
+
return RGBA2.fromInts(r, g, b, a);
|
|
3084
3118
|
}
|
|
3085
3119
|
function asciiFleetColor(char, row, alpha) {
|
|
3086
3120
|
if (char === "@" || char === "$" || char === "o")
|
|
@@ -3088,7 +3122,7 @@ function asciiFleetColor(char, row, alpha) {
|
|
|
3088
3122
|
if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
|
|
3089
3123
|
return withAlpha(RIG_UI.cyan, alpha);
|
|
3090
3124
|
if (char === "." || char === "'" || char === "_" || row < 3 || row > FLEET_GRID_HEIGHT - 4) {
|
|
3091
|
-
return
|
|
3125
|
+
return RGBA2.fromInts(108, 110, 121, Math.min(170, Math.max(0, Math.min(255, Math.round(alpha * 255)))));
|
|
3092
3126
|
}
|
|
3093
3127
|
return withAlpha(RIG_UI.limeDim, alpha);
|
|
3094
3128
|
}
|
|
@@ -3201,7 +3235,7 @@ function sparklineString(values, width) {
|
|
|
3201
3235
|
}
|
|
3202
3236
|
|
|
3203
3237
|
// packages/cli/src/app-opentui/render/text.ts
|
|
3204
|
-
import { RGBA as
|
|
3238
|
+
import { RGBA as RGBA3, TextAttributes as TextAttributes3, TextRenderable } from "@opentui/core";
|
|
3205
3239
|
|
|
3206
3240
|
// packages/cli/src/app-opentui/render/hover.ts
|
|
3207
3241
|
var hoveredId;
|
|
@@ -3231,8 +3265,8 @@ function subscribeHover(listener) {
|
|
|
3231
3265
|
}
|
|
3232
3266
|
|
|
3233
3267
|
// packages/cli/src/app-opentui/render/text.ts
|
|
3234
|
-
var TRANSPARENT2 =
|
|
3235
|
-
var HOVER_BG =
|
|
3268
|
+
var TRANSPARENT2 = RGBA3.fromInts(0, 0, 0, 0);
|
|
3269
|
+
var HOVER_BG = RGBA3.fromHex(RIG_UI.hover);
|
|
3236
3270
|
function lineIsClickable(line2) {
|
|
3237
3271
|
return line2?.selectable !== undefined || line2?.selectableIndex !== undefined;
|
|
3238
3272
|
}
|
|
@@ -3241,7 +3275,7 @@ function lineSelectableId(line2) {
|
|
|
3241
3275
|
}
|
|
3242
3276
|
function lineBackground(line2) {
|
|
3243
3277
|
if (line2?.bg)
|
|
3244
|
-
return
|
|
3278
|
+
return RGBA3.fromHex(line2.bg);
|
|
3245
3279
|
const id = lineSelectableId(line2);
|
|
3246
3280
|
if (id !== undefined && isHovered(id))
|
|
3247
3281
|
return HOVER_BG;
|
|
@@ -3291,12 +3325,12 @@ function applyFlowTextLine(renderable, line2, width) {
|
|
|
3291
3325
|
renderable.content = line2?.styledText ?? line2?.text ?? "";
|
|
3292
3326
|
renderable.fg = line2?.fg ?? RIG_UI.ink2;
|
|
3293
3327
|
renderable.bg = lineBackground(line2);
|
|
3294
|
-
renderable.attributes = line2?.bold ?
|
|
3328
|
+
renderable.attributes = line2?.bold ? TextAttributes3.BOLD : line2?.dim ? TextAttributes3.DIM : 0;
|
|
3295
3329
|
}
|
|
3296
3330
|
|
|
3297
3331
|
// packages/cli/src/app-opentui/render/panels.ts
|
|
3298
|
-
import { RGBA as
|
|
3299
|
-
var TRANSPARENT3 =
|
|
3332
|
+
import { RGBA as RGBA4, ScrollBoxRenderable } from "@opentui/core";
|
|
3333
|
+
var TRANSPARENT3 = RGBA4.fromInts(0, 0, 0, 0);
|
|
3300
3334
|
var MAX_PANEL_LINES = 420;
|
|
3301
3335
|
var PANEL_OPAQUE_ALPHA = 255;
|
|
3302
3336
|
var PANEL_BORDER = hexToRgba(RIG_UI.border, 255);
|
|
@@ -3305,8 +3339,8 @@ function hexToRgba(hex, alpha) {
|
|
|
3305
3339
|
const full = clean.length === 3 ? clean.split("").map((part) => `${part}${part}`).join("") : clean;
|
|
3306
3340
|
const value = Number.parseInt(full, 16);
|
|
3307
3341
|
if (!Number.isFinite(value))
|
|
3308
|
-
return
|
|
3309
|
-
return
|
|
3342
|
+
return RGBA4.fromInts(14, 15, 17, alpha);
|
|
3343
|
+
return RGBA4.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255, alpha);
|
|
3310
3344
|
}
|
|
3311
3345
|
function panelBackground(panel) {
|
|
3312
3346
|
return hexToRgba(panel.backgroundColor ?? RIG_UI.panel, PANEL_OPAQUE_ALPHA);
|
|
@@ -3421,9 +3455,9 @@ function applyScrollPanels(panels, layout, scenePanels) {
|
|
|
3421
3455
|
}
|
|
3422
3456
|
|
|
3423
3457
|
// packages/cli/src/app-opentui/render/type-bar.ts
|
|
3424
|
-
import { BoxRenderable, InputRenderable, InputRenderableEvents, RGBA as
|
|
3425
|
-
var TYPEBAR_BG =
|
|
3426
|
-
var TYPEBAR_BORDER =
|
|
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);
|
|
3427
3461
|
function createTypeBar(renderer) {
|
|
3428
3462
|
const background = new BoxRenderable(renderer, {
|
|
3429
3463
|
id: "typebar-card",
|
|
@@ -3571,7 +3605,7 @@ function formatFooter(footer, width) {
|
|
|
3571
3605
|
chunks.push(cell.style(text));
|
|
3572
3606
|
used += visibleWidth(text);
|
|
3573
3607
|
});
|
|
3574
|
-
return new
|
|
3608
|
+
return new StyledText2(chunks);
|
|
3575
3609
|
}
|
|
3576
3610
|
|
|
3577
3611
|
// packages/cli/src/app-opentui/render/native-host.ts
|
|
@@ -3579,7 +3613,7 @@ import {
|
|
|
3579
3613
|
BoxRenderable as BoxRenderable2,
|
|
3580
3614
|
CodeRenderable,
|
|
3581
3615
|
DiffRenderable,
|
|
3582
|
-
RGBA as
|
|
3616
|
+
RGBA as RGBA6,
|
|
3583
3617
|
ScrollBoxRenderable as ScrollBoxRenderable2,
|
|
3584
3618
|
SyntaxStyle,
|
|
3585
3619
|
TextRenderable as TextRenderable3,
|
|
@@ -3590,19 +3624,19 @@ function rigSyntaxStyle() {
|
|
|
3590
3624
|
if (syntaxStyle)
|
|
3591
3625
|
return syntaxStyle;
|
|
3592
3626
|
syntaxStyle = SyntaxStyle.fromStyles({
|
|
3593
|
-
keyword: { fg:
|
|
3594
|
-
string: { fg:
|
|
3595
|
-
number: { fg:
|
|
3596
|
-
comment: { fg:
|
|
3597
|
-
function: { fg:
|
|
3598
|
-
type: { fg:
|
|
3599
|
-
constant: { fg:
|
|
3600
|
-
default: { fg:
|
|
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) }
|
|
3601
3635
|
});
|
|
3602
3636
|
return syntaxStyle;
|
|
3603
3637
|
}
|
|
3604
3638
|
function tableContent(rows) {
|
|
3605
|
-
return rows.map((row, rowIndex) => row.map((cell) => [{ __isChunk: true, text: cell, fg:
|
|
3639
|
+
return rows.map((row, rowIndex) => row.map((cell) => [{ __isChunk: true, text: cell, fg: RGBA6.fromHex(rowIndex === 0 ? RIG_UI.ink3 : RIG_UI.ink2) }]));
|
|
3606
3640
|
}
|
|
3607
3641
|
function createNativeHost(renderer) {
|
|
3608
3642
|
try {
|
|
@@ -3700,6 +3734,48 @@ function destroyNativeHost(host) {
|
|
|
3700
3734
|
} catch {}
|
|
3701
3735
|
}
|
|
3702
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
|
+
|
|
3703
3779
|
// packages/cli/src/app-opentui/selectable.ts
|
|
3704
3780
|
function selectableDeckRow(deck, item) {
|
|
3705
3781
|
return { ...deckRow({ ...deck, activateOnClick: true }), selectable: item };
|
|
@@ -3709,13 +3785,17 @@ function selectableDeckRow(deck, item) {
|
|
|
3709
3785
|
var ERROR_ACTIONS = [
|
|
3710
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" } },
|
|
3711
3787
|
{ detail: "run diagnostics", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "run diagnostics" } },
|
|
3712
|
-
{ 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" } }
|
|
3713
|
-
{ detail: "repair project/server/auth link", item: { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair project link" } }, message: "repair project link" } }
|
|
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" } }
|
|
3714
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
|
+
}
|
|
3715
3794
|
function renderErrorScene(state) {
|
|
3716
3795
|
const message = state.error?.message ?? "Unknown app error";
|
|
3717
3796
|
const hint = state.error?.hint ?? "Use the actions below to recover.";
|
|
3718
|
-
const
|
|
3797
|
+
const actions = errorActions(state);
|
|
3798
|
+
const selected = Math.max(0, Math.min(actions.length - 1, state.selection.index));
|
|
3719
3799
|
return makeSceneFrame({
|
|
3720
3800
|
scene: "error",
|
|
3721
3801
|
title: "Error",
|
|
@@ -3733,7 +3813,7 @@ function renderErrorScene(state) {
|
|
|
3733
3813
|
...hint ? [line(hint, { fg: RIG_UI.yellow })] : [],
|
|
3734
3814
|
line("", { fg: RIG_UI.ink3 }),
|
|
3735
3815
|
line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
|
|
3736
|
-
...
|
|
3816
|
+
...actions.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item))
|
|
3737
3817
|
],
|
|
3738
3818
|
backgroundColor: RIG_UI.panel,
|
|
3739
3819
|
backgroundAlpha: 184,
|
|
@@ -3861,7 +3941,7 @@ function renderHelpScene(state, layout) {
|
|
|
3861
3941
|
|
|
3862
3942
|
// packages/cli/src/app-opentui/scenes/main.ts
|
|
3863
3943
|
var CONFIGURED_MENU = [
|
|
3864
|
-
["init", "setup, reconfigure, repair project state", { id: "init", label: "init", intent: { scene: "
|
|
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" }],
|
|
3865
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" }],
|
|
3866
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" }],
|
|
3867
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" }],
|
|
@@ -3880,10 +3960,10 @@ var CONFIGURED_MENU = [
|
|
|
3880
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" }]
|
|
3881
3961
|
];
|
|
3882
3962
|
var ONBOARD_PRIMARY = {
|
|
3883
|
-
id: "onboard-
|
|
3884
|
-
label: "Start guided
|
|
3885
|
-
intent: { scene: "init", argv: ["init"], action: { kind: "refresh", label: "
|
|
3886
|
-
message: "open the
|
|
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"
|
|
3887
3967
|
};
|
|
3888
3968
|
var ONBOARD_SECONDARY = [
|
|
3889
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" }],
|
|
@@ -3946,7 +4026,7 @@ function configuredMenuRows(selectedIndex) {
|
|
|
3946
4026
|
}));
|
|
3947
4027
|
}
|
|
3948
4028
|
function onboardingRows(selectedIndex) {
|
|
3949
|
-
const primary = selectableDeckRow({ label: "
|
|
4029
|
+
const primary = selectableDeckRow({ label: "init", detail: "Start guided init \u2014 recommended", active: selectedIndex === 0 }, ONBOARD_PRIMARY);
|
|
3950
4030
|
const secondary = ONBOARD_SECONDARY.map(([label, detail, item], index) => ({
|
|
3951
4031
|
...deckRow({ label, detail, index: index + 1, active: selectedIndex === index + 1, activateOnClick: true }),
|
|
3952
4032
|
selectable: item
|
|
@@ -4010,7 +4090,7 @@ function renderMainScene(state, layout) {
|
|
|
4010
4090
|
statusLine(state)
|
|
4011
4091
|
] : [
|
|
4012
4092
|
line("WELCOME", { fg: RIG_UI.ink, bold: true }),
|
|
4013
|
-
line("This project isn't
|
|
4093
|
+
line("This project isn't initialized yet. One native init flow gets you running.", { fg: RIG_UI.ink2 }),
|
|
4014
4094
|
blank(),
|
|
4015
4095
|
line("GET STARTED", { fg: RIG_UI.ink, bold: true }),
|
|
4016
4096
|
...onboardingRows(actionSelected),
|
|
@@ -4046,7 +4126,7 @@ function renderMainScene(state, layout) {
|
|
|
4046
4126
|
}
|
|
4047
4127
|
|
|
4048
4128
|
// packages/cli/src/app-opentui/runtime.ts
|
|
4049
|
-
var PRELOADER_BACKDROP =
|
|
4129
|
+
var PRELOADER_BACKDROP = RGBA7.fromHex(RIG_UI.bg);
|
|
4050
4130
|
function inboxPendingCount(state) {
|
|
4051
4131
|
const inbox = state.data.inbox;
|
|
4052
4132
|
if (!inbox || typeof inbox !== "object" || Array.isArray(inbox))
|
|
@@ -4062,7 +4142,7 @@ var PRIMARY_NAV = [
|
|
|
4062
4142
|
{ id: "tasks", label: "Tasks", scene: "tasks", argv: ["tasks"], enterLabel: "Tasks", member: ["tasks"] },
|
|
4063
4143
|
{ id: "inbox", label: "Inbox", scene: "inbox", argv: ["inbox"], enterLabel: "Inbox", member: ["inbox"], badge: inboxPendingCount },
|
|
4064
4144
|
{ id: "stats", label: "Stats", scene: "family", argv: ["stats"], enterLabel: "Stats", member: ["family"] },
|
|
4065
|
-
{ id: "
|
|
4145
|
+
{ id: "init", label: "Init", scene: "init", argv: ["init"], enterLabel: "Init", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
|
|
4066
4146
|
{ id: "help", label: "Help", scene: "help", argv: ["help"], enterLabel: "Help", member: ["help"] }
|
|
4067
4147
|
];
|
|
4068
4148
|
function activeNavSection(scene) {
|
|
@@ -4083,7 +4163,7 @@ function entryIntentForScene(scene) {
|
|
|
4083
4163
|
case "doctor":
|
|
4084
4164
|
return { scene, argv: ["doctor"], action: { kind: "doctor-run", label: "Doctor" } };
|
|
4085
4165
|
case "init":
|
|
4086
|
-
return { scene, argv: ["init"], action: { kind: "refresh", label: "
|
|
4166
|
+
return { scene, argv: ["init"], action: { kind: "refresh", label: "Init" } };
|
|
4087
4167
|
case "run-detail":
|
|
4088
4168
|
return { scene, argv: ["run", "status"], action: { kind: "refresh", label: "Run" } };
|
|
4089
4169
|
case "pi":
|
|
@@ -4117,7 +4197,7 @@ function sceneSectionLabel(scene) {
|
|
|
4117
4197
|
handoff: "pi console",
|
|
4118
4198
|
server: "server",
|
|
4119
4199
|
doctor: "doctor",
|
|
4120
|
-
init: "
|
|
4200
|
+
init: "init",
|
|
4121
4201
|
pi: "pi",
|
|
4122
4202
|
plugin: "plugins",
|
|
4123
4203
|
repo: "repo",
|
|
@@ -4150,7 +4230,7 @@ function buildNavStrip(state) {
|
|
|
4150
4230
|
chunks.push(otuiBold(styles.yellow(String(badgeCount))));
|
|
4151
4231
|
}
|
|
4152
4232
|
});
|
|
4153
|
-
return new
|
|
4233
|
+
return new StyledText3(chunks);
|
|
4154
4234
|
}
|
|
4155
4235
|
function renderedItemsOrStateItems(_state, renderedItems) {
|
|
4156
4236
|
return renderedItems ? [...renderedItems] : [];
|
|
@@ -4290,7 +4370,7 @@ function Backdrop(props) {
|
|
|
4290
4370
|
// packages/cli/src/app-opentui/react/SceneFrameView.tsx
|
|
4291
4371
|
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
4292
4372
|
import { useRenderer as useRenderer2 } from "@opentui/react";
|
|
4293
|
-
import { TextAttributes as
|
|
4373
|
+
import { TextAttributes as TextAttributes4 } from "@opentui/core";
|
|
4294
4374
|
|
|
4295
4375
|
// packages/cli/src/app-opentui/react/scroll.ts
|
|
4296
4376
|
var registries = new Set;
|
|
@@ -4312,7 +4392,7 @@ function scrollActiveBody(delta) {
|
|
|
4312
4392
|
// packages/cli/src/app-opentui/react/SceneFrameView.tsx
|
|
4313
4393
|
import { jsxDEV, Fragment } from "@opentui/react/jsx-dev-runtime";
|
|
4314
4394
|
function lineAttributes(line2) {
|
|
4315
|
-
return line2?.bold ?
|
|
4395
|
+
return line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
|
|
4316
4396
|
}
|
|
4317
4397
|
function lineContent(line2) {
|
|
4318
4398
|
return line2.styledText ?? line2.text ?? "";
|
|
@@ -4522,7 +4602,7 @@ var PRIMARY_NAV2 = [
|
|
|
4522
4602
|
{ id: "tasks", label: "Tasks", scene: "tasks", argv: ["tasks"], enterLabel: "Tasks", member: ["tasks"] },
|
|
4523
4603
|
{ id: "inbox", label: "Inbox", scene: "inbox", argv: ["inbox"], enterLabel: "Inbox", member: ["inbox"], badge: inboxPendingCount2 },
|
|
4524
4604
|
{ id: "stats", label: "Stats", scene: "family", argv: ["stats"], enterLabel: "Stats", member: ["family"] },
|
|
4525
|
-
{ id: "
|
|
4605
|
+
{ id: "init", label: "Init", scene: "init", argv: ["init"], enterLabel: "Init", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
|
|
4526
4606
|
{ id: "help", label: "Help", scene: "help", argv: ["help"], enterLabel: "Help", member: ["help"] }
|
|
4527
4607
|
];
|
|
4528
4608
|
function navSectionIntent(section) {
|
|
@@ -4652,8 +4732,48 @@ async function launchRigReactApp(options) {
|
|
|
4652
4732
|
const store = createAppStore(initialState);
|
|
4653
4733
|
const events = createAppEventBus();
|
|
4654
4734
|
let renderer = null;
|
|
4735
|
+
let root = null;
|
|
4655
4736
|
let runnerPromise = null;
|
|
4656
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
|
+
};
|
|
4657
4777
|
const runAppAction = (label, action) => {
|
|
4658
4778
|
action().catch((error) => {
|
|
4659
4779
|
const n = normalizeError(error);
|
|
@@ -4695,8 +4815,10 @@ async function launchRigReactApp(options) {
|
|
|
4695
4815
|
runnerPromise ??= options.initializeRuntime();
|
|
4696
4816
|
return runnerPromise;
|
|
4697
4817
|
},
|
|
4698
|
-
suspend: () =>
|
|
4699
|
-
resume: () =>
|
|
4818
|
+
suspend: () => teardownRenderer(),
|
|
4819
|
+
resume: async () => {
|
|
4820
|
+
await mountRenderer();
|
|
4821
|
+
},
|
|
4700
4822
|
destroy: () => renderer?.destroy()
|
|
4701
4823
|
};
|
|
4702
4824
|
events.subscribe((event) => store.patch(reduceAppEvent(store.getState(), event)));
|
|
@@ -4704,15 +4826,7 @@ async function launchRigReactApp(options) {
|
|
|
4704
4826
|
if (typeof tick.unref === "function") {
|
|
4705
4827
|
tick.unref();
|
|
4706
4828
|
}
|
|
4707
|
-
|
|
4708
|
-
screenMode: "alternate-screen",
|
|
4709
|
-
exitOnCtrlC: false,
|
|
4710
|
-
targetFps: 30,
|
|
4711
|
-
maxFps: 30,
|
|
4712
|
-
useMouse: true,
|
|
4713
|
-
autoFocus: false,
|
|
4714
|
-
useKittyKeyboard: { disambiguate: true }
|
|
4715
|
-
});
|
|
4829
|
+
await mountRenderer();
|
|
4716
4830
|
let liveEvents = null;
|
|
4717
4831
|
const canAutoRefresh = (state) => {
|
|
4718
4832
|
if (state.status === "action" || state.status === "loading")
|
|
@@ -4743,12 +4857,6 @@ async function launchRigReactApp(options) {
|
|
|
4743
4857
|
}, FALLBACK_REFRESH_MS);
|
|
4744
4858
|
if (typeof poll.unref === "function")
|
|
4745
4859
|
poll.unref();
|
|
4746
|
-
createRoot(renderer).render(/* @__PURE__ */ jsxDEV4(RigAppProvider, {
|
|
4747
|
-
value: { store, runtime, runAppAction },
|
|
4748
|
-
children: /* @__PURE__ */ jsxDEV4(App, {
|
|
4749
|
-
sceneRenderers: options.sceneRenderers
|
|
4750
|
-
}, undefined, false, undefined, this)
|
|
4751
|
-
}, undefined, false, undefined, this));
|
|
4752
4860
|
queueMicrotask(() => {
|
|
4753
4861
|
(async () => {
|
|
4754
4862
|
try {
|
|
@@ -4758,7 +4866,7 @@ async function launchRigReactApp(options) {
|
|
|
4758
4866
|
}
|
|
4759
4867
|
for (const adapter of options.adapters ?? [])
|
|
4760
4868
|
await adapter.start?.(runtime);
|
|
4761
|
-
|
|
4869
|
+
connectRigServerEvents(options.projectRoot, {
|
|
4762
4870
|
onSnapshotInvalidated: () => refreshCurrentDataScene(),
|
|
4763
4871
|
onRunLogAppended: () => {
|
|
4764
4872
|
const scene = store.getState().scene;
|
|
@@ -4766,9 +4874,11 @@ async function launchRigReactApp(options) {
|
|
|
4766
4874
|
refreshCurrentDataScene();
|
|
4767
4875
|
},
|
|
4768
4876
|
onStatus: (status) => store.patch({ footer: { live: liveLinkLabel(status) } })
|
|
4769
|
-
}).
|
|
4770
|
-
|
|
4877
|
+
}).then((subscription) => {
|
|
4878
|
+
liveEvents = subscription;
|
|
4879
|
+
}).catch(() => {
|
|
4771
4880
|
store.patch({ footer: { live: liveLinkLabel("disconnected") } });
|
|
4881
|
+
});
|
|
4772
4882
|
await runtime.runIntent(store.getState().intent);
|
|
4773
4883
|
} catch (error) {
|
|
4774
4884
|
const n = normalizeError(error);
|
|
@@ -4776,14 +4886,11 @@ async function launchRigReactApp(options) {
|
|
|
4776
4886
|
}
|
|
4777
4887
|
})();
|
|
4778
4888
|
});
|
|
4779
|
-
await
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
resolve3();
|
|
4785
|
-
});
|
|
4786
|
-
});
|
|
4889
|
+
await appExit;
|
|
4890
|
+
clearInterval(tick);
|
|
4891
|
+
clearInterval(poll);
|
|
4892
|
+
liveEvents?.close();
|
|
4893
|
+
teardownRenderer();
|
|
4787
4894
|
}
|
|
4788
4895
|
export {
|
|
4789
4896
|
launchRigReactApp
|