@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.
Files changed (78) hide show
  1. package/dist/bin/rig.js +1380 -865
  2. package/dist/src/app/board.js +462 -48
  3. package/dist/src/app-opentui/adapters/common.d.ts +3 -0
  4. package/dist/src/app-opentui/adapters/common.js +4 -0
  5. package/dist/src/app-opentui/adapters/doctor.js +458 -46
  6. package/dist/src/app-opentui/adapters/family.js +701 -151
  7. package/dist/src/app-opentui/adapters/fleet.js +477 -46
  8. package/dist/src/app-opentui/adapters/inbox.js +477 -46
  9. package/dist/src/app-opentui/adapters/init.js +497 -74
  10. package/dist/src/app-opentui/adapters/inspect.js +477 -46
  11. package/dist/src/app-opentui/adapters/pi-attach.d.ts +7 -0
  12. package/dist/src/app-opentui/adapters/pi-attach.js +1004 -519
  13. package/dist/src/app-opentui/adapters/run-detail.js +477 -46
  14. package/dist/src/app-opentui/adapters/server.d.ts +26 -0
  15. package/dist/src/app-opentui/adapters/server.js +676 -59
  16. package/dist/src/app-opentui/adapters/tasks.js +621 -549
  17. package/dist/src/app-opentui/autocomplete.js +4 -2
  18. package/dist/src/app-opentui/bootstrap.js +1376 -861
  19. package/dist/src/app-opentui/command-palette.js +37 -10
  20. package/dist/src/app-opentui/index.js +632 -528
  21. package/dist/src/app-opentui/intent.js +33 -8
  22. package/dist/src/app-opentui/keymap.js +43 -414
  23. package/dist/src/app-opentui/pi-host-child.js +496 -57
  24. package/dist/src/app-opentui/pi-pty-host.d.ts +14 -64
  25. package/dist/src/app-opentui/pi-pty-host.js +3 -397
  26. package/dist/src/app-opentui/react/App.js +144 -469
  27. package/dist/src/app-opentui/react/ChromeHost.js +44 -415
  28. package/dist/src/app-opentui/react/launch.js +659 -552
  29. package/dist/src/app-opentui/react/nav.js +1 -1
  30. package/dist/src/app-opentui/registry.js +1181 -742
  31. package/dist/src/app-opentui/remote-link.d.ts +10 -0
  32. package/dist/src/app-opentui/remote-link.js +47 -0
  33. package/dist/src/app-opentui/render/terminal-handoff.d.ts +16 -0
  34. package/dist/src/app-opentui/render/terminal-handoff.js +14 -0
  35. package/dist/src/app-opentui/runtime.js +632 -528
  36. package/dist/src/app-opentui/scenes/doctor.js +1 -1
  37. package/dist/src/app-opentui/scenes/error.js +50 -4
  38. package/dist/src/app-opentui/scenes/family.js +60 -6
  39. package/dist/src/app-opentui/scenes/fleet.js +65 -13
  40. package/dist/src/app-opentui/scenes/help.js +4 -2
  41. package/dist/src/app-opentui/scenes/init.js +12 -12
  42. package/dist/src/app-opentui/scenes/main.js +7 -7
  43. package/dist/src/app-opentui/scenes/server.js +83 -11
  44. package/dist/src/app-opentui/scenes/tasks.js +79 -16
  45. package/dist/src/app-opentui/state.js +25 -5
  46. package/dist/src/app-opentui/surface-catalog.js +4 -2
  47. package/dist/src/app-opentui/types.d.ts +1 -1
  48. package/dist/src/commands/_cli-format.d.ts +10 -1
  49. package/dist/src/commands/_cli-format.js +5 -2
  50. package/dist/src/commands/_connection-state.d.ts +11 -1
  51. package/dist/src/commands/_connection-state.js +50 -5
  52. package/dist/src/commands/_doctor-checks.js +458 -46
  53. package/dist/src/commands/_help-catalog.js +4 -2
  54. package/dist/src/commands/_json-output.js +4 -0
  55. package/dist/src/commands/_operator-view.js +496 -57
  56. package/dist/src/commands/_pi-frontend.d.ts +25 -0
  57. package/dist/src/commands/_pi-frontend.js +497 -57
  58. package/dist/src/commands/_preflight.js +509 -72
  59. package/dist/src/commands/_server-client.d.ts +33 -0
  60. package/dist/src/commands/_server-client.js +477 -46
  61. package/dist/src/commands/_server-events.js +446 -41
  62. package/dist/src/commands/_snapshot-upload.js +460 -48
  63. package/dist/src/commands/connect.js +620 -15
  64. package/dist/src/commands/doctor.js +458 -46
  65. package/dist/src/commands/github.js +462 -50
  66. package/dist/src/commands/inbox.js +458 -46
  67. package/dist/src/commands/init.js +497 -74
  68. package/dist/src/commands/inspect.js +458 -46
  69. package/dist/src/commands/run.js +496 -57
  70. package/dist/src/commands/server.js +647 -163
  71. package/dist/src/commands/setup.js +463 -51
  72. package/dist/src/commands/stats.js +462 -48
  73. package/dist/src/commands/task-run-driver.js +464 -52
  74. package/dist/src/commands/task.js +551 -85
  75. package/dist/src/commands.js +701 -151
  76. package/dist/src/index.js +705 -151
  77. package/dist/src/launcher.js +4 -0
  78. 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
- return commandRunIntent(argv);
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
- return commandRunIntent(parts, value);
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 RGBA8, StyledText as StyledText4, TextRenderable as TextRenderable4, createCliRenderer } from "@opentui/core";
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
- const state = context.getState();
1712
- const piHost = getActivePiHost();
1713
- const commandHost = getActiveCommandHost();
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
- if (state.scene === "handoff") {
1719
- stopActivePiHost("operator detach");
1720
- clearTypeBar(context, "detached from Pi");
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.serverProjectRoot };
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 writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
1760
+ function clearRepoServerProjectRoot(projectRoot) {
2092
1761
  const repo = readRepoConnection(projectRoot);
2093
1762
  if (!repo)
2094
1763
  return;
2095
- writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
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 inferRemoteServerProjectRootFromAuthState(projectRoot) {
2137
- const repo = readRepoConnection(projectRoot);
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
- const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
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 serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
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 as TextAttributes2
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 as RGBA2, StyledText as StyledText2, TextAttributes as TextAttributes3 } from "@opentui/core";
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: RGBA2.fromHex(RIG_UI.lime),
2339
- mini: RGBA2.fromHex(RIG_UI.limeDim),
2340
- rotor: RGBA2.fromHex(RIG_UI.cyan),
2341
- path: RGBA2.fromHex(RIG_UI.cyan),
2342
- eye: RGBA2.fromHex(RIG_UI.ink),
2343
- dim: RGBA2.fromHex(RIG_UI.ink4),
2344
- ink: RGBA2.fromHex(RIG_UI.ink2)
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 = TextAttributes3.NONE;
2388
+ let attributes = TextAttributes2.NONE;
2355
2389
  if (bold)
2356
- attributes |= TextAttributes3.BOLD;
2390
+ attributes |= TextAttributes2.BOLD;
2357
2391
  if (dim)
2358
- attributes |= TextAttributes3.DIM;
2359
- return { __isChunk: true, text, fg, ...attributes !== TextAttributes3.NONE ? { 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 StyledText2(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
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 RGBA3 } from "@opentui/core";
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 = RGBA3.fromValues(0, 0, 0, 0);
2617
- var BACKDROP = RGBA3.fromHex(RIG_UI.bg);
2618
- var PANEL_BG = RGBA3.fromInts(16, 17, 21, 184);
2619
- var PANEL_HEADER_BG = RGBA3.fromInts(16, 17, 21, 188);
2620
- var PANEL_LINE = RGBA3.fromInts(255, 255, 255, 20);
2621
- var PANEL_LINE_DIM = RGBA3.fromInts(255, 255, 255, 8);
2622
- var PANEL_TEXT_DIM = RGBA3.fromInts(108, 110, 121, 255);
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 RGBA3.fromInts(Math.round(r * lift), Math.round(g * lift), Math.round(b * lift), 255);
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] = RGBA3.fromHex(hex).toInts();
3083
- return RGBA3.fromInts(r, g, b, a);
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 RGBA3.fromInts(108, 110, 121, Math.min(170, Math.max(0, Math.min(255, Math.round(alpha * 255)))));
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 RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
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 = RGBA4.fromInts(0, 0, 0, 0);
3235
- var HOVER_BG = RGBA4.fromHex(RIG_UI.hover);
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 RGBA4.fromHex(line2.bg);
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 ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
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 RGBA5, ScrollBoxRenderable } from "@opentui/core";
3299
- var TRANSPARENT3 = RGBA5.fromInts(0, 0, 0, 0);
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 RGBA5.fromInts(14, 15, 17, alpha);
3309
- return RGBA5.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255, alpha);
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 RGBA6, StyledText as StyledText3, TextRenderable as TextRenderable2 } from "@opentui/core";
3425
- var TYPEBAR_BG = RGBA6.fromInts(20, 25, 14, 224);
3426
- var TYPEBAR_BORDER = RGBA6.fromInts(204, 255, 77, 92);
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 StyledText3(chunks);
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 RGBA7,
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: RGBA7.fromHex(RIG_UI.magenta), bold: true },
3594
- string: { fg: RGBA7.fromHex(RIG_UI.lime) },
3595
- number: { fg: RGBA7.fromHex(RIG_UI.cyan) },
3596
- comment: { fg: RGBA7.fromHex(RIG_UI.ink4), italic: true },
3597
- function: { fg: RGBA7.fromHex(RIG_UI.cyan) },
3598
- type: { fg: RGBA7.fromHex(RIG_UI.limeDim) },
3599
- constant: { fg: RGBA7.fromHex(RIG_UI.yellow) },
3600
- default: { fg: RGBA7.fromHex(RIG_UI.ink2) }
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: RGBA7.fromHex(rowIndex === 0 ? RIG_UI.ink3 : RIG_UI.ink2) }]));
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 selected = Math.max(0, Math.min(3, state.selection.index));
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
- ...ERROR_ACTIONS.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item))
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: "command", argv: ["init"], action: { kind: "command-run", label: "Run init" } }, message: "run setup/reconfigure flow" }],
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-setup",
3884
- label: "Start guided setup",
3885
- intent: { scene: "init", argv: ["init"], action: { kind: "refresh", label: "Setup" } },
3886
- message: "open the guided setup checklist"
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: "setup", detail: "Start guided setup \u2014 recommended", active: selectedIndex === 0 }, ONBOARD_PRIMARY);
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 set up yet. One guided flow gets you running.", { fg: RIG_UI.ink2 }),
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 = RGBA8.fromHex(RIG_UI.bg);
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: "setup", label: "Setup", scene: "init", argv: ["init"], enterLabel: "Setup", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
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: "Setup" } };
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: "setup",
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 StyledText4(chunks);
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 TextAttributes5 } from "@opentui/core";
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 ? TextAttributes5.BOLD : line2?.dim ? TextAttributes5.DIM : 0;
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: "setup", label: "Setup", scene: "init", argv: ["init"], enterLabel: "Setup", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
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: () => renderer?.suspend(),
4699
- resume: () => renderer?.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
- renderer = await createCliRenderer2({
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
- liveEvents = await connectRigServerEvents(options.projectRoot, {
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
- }).catch(() => null);
4770
- if (!liveEvents)
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 new Promise((resolve3) => {
4780
- renderer?.on("destroy", () => {
4781
- clearInterval(tick);
4782
- clearInterval(poll);
4783
- liveEvents?.close();
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