@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
package/dist/bin/rig.js CHANGED
@@ -338,7 +338,8 @@ __export(exports__connection_state, {
338
338
  resolveGlobalConnectionsPath: () => resolveGlobalConnectionsPath,
339
339
  readRepoConnection: () => readRepoConnection,
340
340
  readGlobalConnections: () => readGlobalConnections,
341
- isRemoteConnectionSelected: () => isRemoteConnectionSelected
341
+ isRemoteConnectionSelected: () => isRemoteConnectionSelected,
342
+ clearRepoServerProjectRoot: () => clearRepoServerProjectRoot
342
343
  });
343
344
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
344
345
  import { homedir } from "os";
@@ -422,12 +423,28 @@ function readRepoConnection(projectRoot) {
422
423
  selected,
423
424
  project: typeof record.project === "string" ? record.project : undefined,
424
425
  linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
425
- serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
426
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
427
+ serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
428
+ serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
426
429
  };
427
430
  }
428
431
  function writeRepoConnection(projectRoot, state) {
429
432
  writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
430
433
  }
434
+ function rootAllowedForSelection(repo, connection) {
435
+ const root = repo.serverProjectRoot?.trim();
436
+ if (!root)
437
+ return;
438
+ if (connection.kind === "remote") {
439
+ if (repo.serverProjectRootAlias !== repo.selected)
440
+ return;
441
+ if (repo.serverProjectRootBaseUrl !== connection.baseUrl)
442
+ return;
443
+ } else if (repo.serverProjectRootAlias && repo.serverProjectRootAlias !== repo.selected) {
444
+ return;
445
+ }
446
+ return root;
447
+ }
431
448
  function resolveSelectedConnection(projectRoot, options = {}) {
432
449
  const repo = readRepoConnection(projectRoot);
433
450
  if (!repo)
@@ -439,13 +456,41 @@ function resolveSelectedConnection(projectRoot, options = {}) {
439
456
  if (!connection) {
440
457
  throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
441
458
  }
442
- return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
459
+ return { alias: repo.selected, connection, serverProjectRoot: rootAllowedForSelection(repo, connection) };
460
+ }
461
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot, metadata = {}) {
462
+ const repo = readRepoConnection(projectRoot);
463
+ if (!repo)
464
+ return;
465
+ let inferred = metadata;
466
+ if (!inferred.alias || !inferred.baseUrl) {
467
+ try {
468
+ const selected = resolveSelectedConnection(projectRoot);
469
+ if (selected?.connection.kind === "remote") {
470
+ inferred = {
471
+ alias: inferred.alias ?? selected.alias,
472
+ baseUrl: inferred.baseUrl ?? selected.connection.baseUrl
473
+ };
474
+ }
475
+ } catch {}
476
+ }
477
+ writeRepoConnection(projectRoot, {
478
+ ...repo,
479
+ ...metadata.project ? { project: metadata.project } : {},
480
+ serverProjectRoot,
481
+ ...inferred.alias ? { serverProjectRootAlias: inferred.alias } : {},
482
+ ...inferred.baseUrl ? { serverProjectRootBaseUrl: inferred.baseUrl.replace(/\/+$/, "") } : {}
483
+ });
443
484
  }
444
- function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
485
+ function clearRepoServerProjectRoot(projectRoot) {
445
486
  const repo = readRepoConnection(projectRoot);
446
487
  if (!repo)
447
488
  return;
448
- writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
489
+ writeRepoConnection(projectRoot, {
490
+ selected: repo.selected,
491
+ ...repo.project ? { project: repo.project } : {},
492
+ ...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
493
+ });
449
494
  }
450
495
  function isRemoteConnectionSelected(projectRoot) {
451
496
  return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
@@ -472,12 +517,16 @@ __export(exports__server_client, {
472
517
  respondRunPiExtensionUiViaServer: () => respondRunPiExtensionUiViaServer,
473
518
  resolveServerConnectionLabel: () => resolveServerConnectionLabel,
474
519
  requestServerJson: () => requestServerJson,
520
+ repairRemoteProjectRootLink: () => repairRemoteProjectRootLink,
475
521
  registerProjectViaServer: () => registerProjectViaServer,
476
522
  prepareRemoteCheckoutViaServer: () => prepareRemoteCheckoutViaServer,
477
523
  postGitHubTokenViaServer: () => postGitHubTokenViaServer,
524
+ normalizeRepoSlug: () => normalizeRepoSlug,
478
525
  listWorkspaceTasksViaServer: () => listWorkspaceTasksViaServer,
479
526
  listRunsViaServer: () => listRunsViaServer,
480
527
  listGitHubProjectsViaServer: () => listGitHubProjectsViaServer,
528
+ isRemoteProjectRootLinkError: () => isRemoteProjectRootLinkError,
529
+ inspectRemoteProjectLink: () => inspectRemoteProjectLink,
481
530
  getWorkspaceTaskViaServer: () => getWorkspaceTaskViaServer,
482
531
  getRunTimelineViaServer: () => getRunTimelineViaServer,
483
532
  getRunPiStatusViaServer: () => getRunPiStatusViaServer,
@@ -489,13 +538,15 @@ __export(exports__server_client, {
489
538
  getRunDetailsViaServer: () => getRunDetailsViaServer,
490
539
  getGitHubProjectStatusFieldViaServer: () => getGitHubProjectStatusFieldViaServer,
491
540
  getGitHubAuthStatusViaServer: () => getGitHubAuthStatusViaServer,
541
+ formatRemoteProjectLinkHint: () => formatRemoteProjectLinkHint,
492
542
  ensureTaskLabelsViaServer: () => ensureTaskLabelsViaServer,
493
543
  ensureServerForCli: () => ensureServerForCli,
544
+ ensureRemoteProjectRootLink: () => ensureRemoteProjectRootLink,
494
545
  buildRunPiEventsWebSocketUrl: () => buildRunPiEventsWebSocketUrl,
495
546
  abortRunPiViaServer: () => abortRunPiViaServer
496
547
  });
497
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
498
- import { resolve as resolve3 } from "path";
548
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
549
+ import { dirname as dirname2, isAbsolute, resolve as resolve3 } from "path";
499
550
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
500
551
  function setServerPhaseListener(listener) {
501
552
  const previous = serverPhaseListener;
@@ -541,11 +592,10 @@ function readStoredGitHubAuthToken(projectRoot) {
541
592
  const parsed = readRemoteAuthState(projectRoot);
542
593
  return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
543
594
  }
544
- function inferRemoteServerProjectRootFromAuthState(projectRoot) {
545
- const repo = readRepoConnection(projectRoot);
546
- const slug = repo?.project?.trim();
547
- if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
595
+ function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
596
+ if (!/^[^/]+\/[^/]+$/.test(repoSlug))
548
597
  return null;
598
+ const repo = readRepoConnection(projectRoot);
549
599
  const auth = readRemoteAuthState(projectRoot);
550
600
  const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
551
601
  const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
@@ -554,25 +604,426 @@ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
554
604
  const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
555
605
  if (!checkoutBaseDir)
556
606
  return null;
557
- const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
558
- writeRepoServerProjectRoot(projectRoot, inferred);
559
- return inferred;
607
+ return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
560
608
  }
561
609
  function readLocalConnectionFallbackToken(projectRoot) {
562
610
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
563
611
  }
612
+ function normalizeRepoSlug(value) {
613
+ const slug = value?.trim();
614
+ return slug && /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
615
+ }
616
+ function readProjectLinkSlug(projectRoot) {
617
+ const path = resolve3(projectRoot, ".rig", "state", "project-link.json");
618
+ if (!existsSync3(path))
619
+ return null;
620
+ try {
621
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
622
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
623
+ return null;
624
+ return normalizeRepoSlug(typeof parsed.repoSlug === "string" ? String(parsed.repoSlug) : null);
625
+ } catch {
626
+ return null;
627
+ }
628
+ }
629
+ function writeProjectLinkConnection(projectRoot, repoSlug, alias) {
630
+ const path = resolve3(projectRoot, ".rig", "state", "project-link.json");
631
+ mkdirSync3(dirname2(path), { recursive: true });
632
+ writeFileSync2(path, `${JSON.stringify({ repoSlug, connection: alias, linkedAt: new Date().toISOString() }, null, 2)}
633
+ `, "utf8");
634
+ }
635
+ function remoteLinkRepairCommand(repoSlug) {
636
+ return repoSlug ? `rig server repair-link --repo ${repoSlug}` : "rig server repair-link --repo owner/repo";
637
+ }
638
+ function isRemoteProjectRootLinkError(error) {
639
+ const text = error instanceof Error ? error.message : String(error ?? "");
640
+ return /no server-host project root link|remote project(?:-root)? link|x-rig-project-root|serverProjectRoot/i.test(text);
641
+ }
642
+ function formatRemoteProjectLinkHint(resolution) {
643
+ const repair = remoteLinkRepairCommand(resolution.repoSlug);
644
+ if (resolution.status === "auth_required") {
645
+ 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}` : ""}\`.`;
646
+ }
647
+ if (resolution.status === "missing_project") {
648
+ return `Record the repo slug, then run \`${repair}\` (or \`rig init --repo owner/repo --server remote --github-auth device\`).`;
649
+ }
650
+ if (resolution.status === "not_remote")
651
+ return "Select a remote server with `rig server use <alias>` before repairing a remote project link.";
652
+ return `Run \`${repair}\` to backfill or prepare a server-host checkout for the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}.`;
653
+ }
654
+ function remoteProjectLinkFailure(input) {
655
+ const partial = {
656
+ status: input.status,
657
+ alias: input.alias,
658
+ baseUrl: input.baseUrl,
659
+ repoSlug: input.repoSlug
660
+ };
661
+ return {
662
+ ok: false,
663
+ ...input,
664
+ hint: formatRemoteProjectLinkHint(partial),
665
+ next: remoteLinkRepairCommand(input.repoSlug)
666
+ };
667
+ }
668
+ function remoteProjectLinkSuccess(input) {
669
+ return {
670
+ ok: true,
671
+ status: input.status,
672
+ alias: input.alias,
673
+ baseUrl: input.baseUrl,
674
+ repoSlug: input.repoSlug,
675
+ serverProjectRoot: input.serverProjectRoot,
676
+ source: input.source,
677
+ prepared: input.prepared ?? input.status === "prepared",
678
+ validated: input.validated ?? false,
679
+ message: input.message ?? `Remote project link ready for ${input.repoSlug}.`,
680
+ hint: "Remote-scoped task/run/status requests will send x-rig-project-root for this server-host checkout.",
681
+ next: "rig task list"
682
+ };
683
+ }
684
+ function localCheckoutPathCandidate(projectRoot, candidate) {
685
+ try {
686
+ const local = resolve3(projectRoot);
687
+ const resolved = resolve3(candidate);
688
+ return resolved === local || resolved.startsWith(`${local}/`);
689
+ } catch {
690
+ return false;
691
+ }
692
+ }
693
+ async function requestRemoteProjectLinkJson(baseUrl, pathname, authToken, init = {}) {
694
+ const requestUrl = new URL(`${baseUrl}${pathname}`);
695
+ if (authToken && queryAuthFallbackEnabled())
696
+ requestUrl.searchParams.set("rt", authToken);
697
+ const response = await fetch(requestUrl, {
698
+ ...init,
699
+ headers: mergeHeaders(init.headers, authToken)
700
+ });
701
+ const text = await response.text();
702
+ const payload = text.trim().length > 0 ? (() => {
703
+ try {
704
+ return JSON.parse(text);
705
+ } catch {
706
+ return null;
707
+ }
708
+ })() : null;
709
+ return { ok: response.ok, status: response.status, payload, text };
710
+ }
711
+ function payloadError(payload, fallback) {
712
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
713
+ const record = payload;
714
+ const value = record.error ?? record.message ?? record.reason;
715
+ if (typeof value === "string" && value.trim())
716
+ return value.trim();
717
+ }
718
+ return fallback;
719
+ }
720
+ function checkoutPathsFromProjectPayload(payload) {
721
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
722
+ return [];
723
+ const project = payload.project;
724
+ if (!project || typeof project !== "object" || Array.isArray(project))
725
+ return [];
726
+ const checkouts = project.checkouts;
727
+ if (!Array.isArray(checkouts))
728
+ return [];
729
+ return [...checkouts].reverse().flatMap((entry) => {
730
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
731
+ return [];
732
+ const path = entry.path;
733
+ return typeof path === "string" && path.trim() ? [path.trim()] : [];
734
+ });
735
+ }
736
+ function checkoutPathFromPreparePayload(payload) {
737
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
738
+ return null;
739
+ const checkout = payload.checkout;
740
+ if (!checkout || typeof checkout !== "object" || Array.isArray(checkout))
741
+ return null;
742
+ const path = checkout.path;
743
+ return typeof path === "string" && path.trim() ? path.trim() : null;
744
+ }
745
+ async function validateAndPersistRemoteRoot(input) {
746
+ const candidate = input.candidate.trim();
747
+ if (!candidate || !isAbsolute(candidate)) {
748
+ return remoteProjectLinkFailure({
749
+ status: "invalid_root",
750
+ alias: input.alias,
751
+ baseUrl: input.baseUrl,
752
+ repoSlug: input.repoSlug,
753
+ message: `Remote project root candidate is not an absolute server-host path: ${candidate || "(empty)"}.`
754
+ });
755
+ }
756
+ if (localCheckoutPathCandidate(input.projectRoot, candidate)) {
757
+ return remoteProjectLinkFailure({
758
+ status: "invalid_root",
759
+ alias: input.alias,
760
+ baseUrl: input.baseUrl,
761
+ repoSlug: input.repoSlug,
762
+ message: `Refusing to use local checkout path ${candidate} as a remote server project root.`
763
+ });
764
+ }
765
+ let response;
766
+ try {
767
+ response = await requestRemoteProjectLinkJson(input.baseUrl, "/api/server/project-root", input.authToken, {
768
+ method: "POST",
769
+ headers: { "content-type": "application/json" },
770
+ body: JSON.stringify({ projectRoot: candidate })
771
+ });
772
+ } catch (error) {
773
+ return remoteProjectLinkFailure({
774
+ status: "error",
775
+ alias: input.alias,
776
+ baseUrl: input.baseUrl,
777
+ repoSlug: input.repoSlug,
778
+ message: `Could not validate remote project root ${candidate}: ${error instanceof Error ? error.message : String(error)}`
779
+ });
780
+ }
781
+ if (response.status === 401 || response.status === 403) {
782
+ return remoteProjectLinkFailure({
783
+ status: "auth_required",
784
+ alias: input.alias,
785
+ baseUrl: input.baseUrl,
786
+ repoSlug: input.repoSlug,
787
+ statusCode: response.status,
788
+ message: `Remote server ${input.alias} requires authentication before validating project root ${candidate}.`
789
+ });
790
+ }
791
+ if (!response.ok) {
792
+ return remoteProjectLinkFailure({
793
+ status: "invalid_root",
794
+ alias: input.alias,
795
+ baseUrl: input.baseUrl,
796
+ repoSlug: input.repoSlug,
797
+ statusCode: response.status,
798
+ message: `Remote server rejected project root ${candidate} (${response.status}): ${payloadError(response.payload, response.text || "project-root validation failed")}`
799
+ });
800
+ }
801
+ const record = response.payload && typeof response.payload === "object" && !Array.isArray(response.payload) ? response.payload : {};
802
+ if (record.ok !== true) {
803
+ return remoteProjectLinkFailure({
804
+ status: "invalid_root",
805
+ alias: input.alias,
806
+ baseUrl: input.baseUrl,
807
+ repoSlug: input.repoSlug,
808
+ message: `Remote server did not accept project root ${candidate}: ${payloadError(response.payload, "project-root validation failed")}`
809
+ });
810
+ }
811
+ const accepted = typeof record.projectRoot === "string" && record.projectRoot.trim() ? record.projectRoot.trim() : typeof record.requestedProjectRoot === "string" && record.requestedProjectRoot.trim() ? record.requestedProjectRoot.trim() : candidate;
812
+ writeRepoServerProjectRoot(input.projectRoot, accepted, { alias: input.alias, baseUrl: input.baseUrl, project: input.repoSlug });
813
+ writeProjectLinkConnection(input.projectRoot, input.repoSlug, input.alias);
814
+ const status = input.status ?? (input.prepared ? "prepared" : "backfilled");
815
+ return remoteProjectLinkSuccess({
816
+ status,
817
+ alias: input.alias,
818
+ baseUrl: input.baseUrl,
819
+ repoSlug: input.repoSlug,
820
+ serverProjectRoot: accepted,
821
+ source: input.source,
822
+ prepared: input.prepared,
823
+ validated: true,
824
+ 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}.`
825
+ });
826
+ }
827
+ async function ensureRemoteProjectRootLink(projectRoot, options = {}) {
828
+ let selected;
829
+ try {
830
+ selected = resolveSelectedConnection(projectRoot);
831
+ } catch (error) {
832
+ return remoteProjectLinkFailure({ status: "error", message: error instanceof Error ? error.message : String(error) });
833
+ }
834
+ if (!selected || selected.connection.kind !== "remote") {
835
+ return remoteProjectLinkFailure({ status: "not_remote", message: "No remote Rig server is selected for this repo." });
836
+ }
837
+ const repo = readRepoConnection(projectRoot);
838
+ const explicitRepoRequested = options.repoSlug !== undefined;
839
+ const explicitRepoSlug = explicitRepoRequested ? normalizeRepoSlug(options.repoSlug) : null;
840
+ const repoProjectSlug = normalizeRepoSlug(repo?.project);
841
+ const repoSlug = explicitRepoSlug ?? repoProjectSlug ?? readProjectLinkSlug(projectRoot);
842
+ const alias = selected.alias;
843
+ const baseUrl = selected.connection.baseUrl;
844
+ const authToken = options.authToken === undefined ? readGitHubBearerTokenForRemote(projectRoot) : options.authToken;
845
+ const mode = options.mode ?? "backfill-only";
846
+ if (explicitRepoRequested && !explicitRepoSlug) {
847
+ return remoteProjectLinkFailure({
848
+ status: "missing_project",
849
+ alias,
850
+ baseUrl,
851
+ message: `Invalid --repo value ${JSON.stringify(options.repoSlug)}. Expected owner/repo.`
852
+ });
853
+ }
854
+ if (!repoSlug) {
855
+ return remoteProjectLinkFailure({
856
+ status: "missing_project",
857
+ alias,
858
+ baseUrl,
859
+ message: `Remote server ${alias} is selected, but this checkout has no GitHub repo slug recorded.`
860
+ });
861
+ }
862
+ const skippedCandidates = [];
863
+ const storedRoot = selected.serverProjectRoot?.trim() || null;
864
+ const storedRootMatchesResolvedRepo = repoProjectSlug === repoSlug;
865
+ if (storedRoot && storedRootMatchesResolvedRepo) {
866
+ if (localCheckoutPathCandidate(projectRoot, storedRoot)) {
867
+ clearRepoServerProjectRoot(projectRoot);
868
+ skippedCandidates.push(storedRoot);
869
+ } else {
870
+ const storedResult = await validateAndPersistRemoteRoot({
871
+ projectRoot,
872
+ alias,
873
+ baseUrl,
874
+ authToken,
875
+ repoSlug,
876
+ candidate: storedRoot,
877
+ source: "stored",
878
+ status: "ready"
879
+ });
880
+ if (storedResult.ok || storedResult.status === "auth_required" || storedResult.status === "error")
881
+ return storedResult;
882
+ clearRepoServerProjectRoot(projectRoot);
883
+ skippedCandidates.push(storedRoot);
884
+ }
885
+ } else if (storedRoot) {
886
+ skippedCandidates.push(storedRoot);
887
+ }
888
+ const authCandidate = inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug);
889
+ if (authCandidate) {
890
+ const authResult = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: authCandidate, source: "auth-state" });
891
+ if (authResult.ok || authResult.status === "auth_required")
892
+ return authResult;
893
+ }
894
+ let registryResponse;
895
+ try {
896
+ registryResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}`, authToken);
897
+ } catch (error) {
898
+ return remoteProjectLinkFailure({
899
+ status: "error",
900
+ alias,
901
+ baseUrl,
902
+ repoSlug,
903
+ message: `Could not read remote project registry for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
904
+ });
905
+ }
906
+ if (registryResponse.status === 401 || registryResponse.status === 403) {
907
+ return remoteProjectLinkFailure({
908
+ status: "auth_required",
909
+ alias,
910
+ baseUrl,
911
+ repoSlug,
912
+ statusCode: registryResponse.status,
913
+ message: `Remote server ${alias} requires authentication before reading the ${repoSlug} project registry.`
914
+ });
915
+ }
916
+ if (registryResponse.ok) {
917
+ const candidates = checkoutPathsFromProjectPayload(registryResponse.payload);
918
+ for (const candidate of candidates) {
919
+ const result = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate, source: "registry" });
920
+ if (result.ok || result.status === "auth_required")
921
+ return result;
922
+ skippedCandidates.push(candidate);
923
+ }
924
+ if (mode === "backfill-only") {
925
+ return remoteProjectLinkFailure({
926
+ status: candidates.length > 0 ? "invalid_root" : "no_server_checkout",
927
+ alias,
928
+ baseUrl,
929
+ repoSlug,
930
+ 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.`,
931
+ skippedCandidates
932
+ });
933
+ }
934
+ } else if (registryResponse.status === 404) {
935
+ if (mode === "backfill-only") {
936
+ return remoteProjectLinkFailure({
937
+ status: "project_not_registered",
938
+ alias,
939
+ baseUrl,
940
+ repoSlug,
941
+ statusCode: registryResponse.status,
942
+ message: `Remote server ${alias} has no registered project record for ${repoSlug}.`
943
+ });
944
+ }
945
+ } else {
946
+ return remoteProjectLinkFailure({
947
+ status: "error",
948
+ alias,
949
+ baseUrl,
950
+ repoSlug,
951
+ statusCode: registryResponse.status,
952
+ message: `Could not backfill ${repoSlug} from remote registry (${registryResponse.status}): ${payloadError(registryResponse.payload, registryResponse.text || "registry lookup failed")}`,
953
+ skippedCandidates
954
+ });
955
+ }
956
+ let prepareResponse;
957
+ try {
958
+ prepareResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}/prepare-checkout`, authToken, {
959
+ method: "POST",
960
+ headers: { "content-type": "application/json" },
961
+ body: JSON.stringify({ checkout: { kind: "managed-clone" }, repoUrl: `https://github.com/${repoSlug}.git` })
962
+ });
963
+ } catch (error) {
964
+ return remoteProjectLinkFailure({
965
+ status: "error",
966
+ alias,
967
+ baseUrl,
968
+ repoSlug,
969
+ message: `Could not prepare remote checkout for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
970
+ });
971
+ }
972
+ if (prepareResponse.status === 401 || prepareResponse.status === 403) {
973
+ return remoteProjectLinkFailure({
974
+ status: "auth_required",
975
+ alias,
976
+ baseUrl,
977
+ repoSlug,
978
+ statusCode: prepareResponse.status,
979
+ message: `Remote server ${alias} requires authentication before preparing ${repoSlug}.`
980
+ });
981
+ }
982
+ if (!prepareResponse.ok) {
983
+ return remoteProjectLinkFailure({
984
+ status: "error",
985
+ alias,
986
+ baseUrl,
987
+ repoSlug,
988
+ statusCode: prepareResponse.status,
989
+ message: `Remote checkout prepare failed for ${repoSlug} (${prepareResponse.status}): ${payloadError(prepareResponse.payload, prepareResponse.text || "prepare-checkout failed")}`,
990
+ skippedCandidates
991
+ });
992
+ }
993
+ const preparedPath = checkoutPathFromPreparePayload(prepareResponse.payload);
994
+ if (!preparedPath) {
995
+ return remoteProjectLinkFailure({
996
+ status: "invalid_root",
997
+ alias,
998
+ baseUrl,
999
+ repoSlug,
1000
+ message: `Remote checkout prepare for ${repoSlug} did not return a checkout.path.`,
1001
+ skippedCandidates
1002
+ });
1003
+ }
1004
+ return validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: preparedPath, source: "prepare", prepared: true });
1005
+ }
1006
+ async function inspectRemoteProjectLink(projectRoot) {
1007
+ return ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only" });
1008
+ }
1009
+ async function repairRemoteProjectRootLink(context, options = {}) {
1010
+ const resolution = await ensureRemoteProjectRootLink(context.projectRoot, { mode: options.mode ?? "prepare-if-missing", repoSlug: options.repoSlug });
1011
+ if (!resolution.ok)
1012
+ throw new CliError(resolution.message, 1, { hint: resolution.hint });
1013
+ return resolution;
1014
+ }
564
1015
  async function ensureServerForCli(projectRoot) {
565
1016
  try {
566
1017
  const selected = resolveSelectedConnection(projectRoot);
567
1018
  if (selected?.connection.kind === "remote") {
568
1019
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
569
1020
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
570
- const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
1021
+ const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
571
1022
  return {
572
1023
  baseUrl: selected.connection.baseUrl,
573
1024
  authToken,
574
1025
  connectionKind: "remote",
575
- serverProjectRoot
1026
+ serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
576
1027
  };
577
1028
  }
578
1029
  reportServerPhase("Starting local Rig server\u2026");
@@ -590,32 +1041,6 @@ async function ensureServerForCli(projectRoot) {
590
1041
  throw error;
591
1042
  }
592
1043
  }
593
- async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
594
- const repo = readRepoConnection(projectRoot);
595
- const slug = repo?.project?.trim();
596
- if (!slug)
597
- return null;
598
- try {
599
- const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
600
- if (authToken && queryAuthFallbackEnabled())
601
- url.searchParams.set("rt", authToken);
602
- const response = await fetch(url, {
603
- headers: mergeHeaders(undefined, authToken)
604
- });
605
- if (!response.ok)
606
- return null;
607
- const payload = await response.json();
608
- const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
609
- const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
610
- const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
611
- const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
612
- if (path)
613
- writeRepoServerProjectRoot(projectRoot, path);
614
- return path;
615
- } catch {
616
- return null;
617
- }
618
- }
619
1044
  function appendTaskFilterParams(url, filters) {
620
1045
  if (filters.assignee)
621
1046
  url.searchParams.set("assignee", filters.assignee);
@@ -716,13 +1141,20 @@ function canUseRemoteWithoutProjectRoot(pathname) {
716
1141
  async function requestServerJson(context, pathname, init = {}) {
717
1142
  const server = await ensureServerForCli(context.projectRoot);
718
1143
  const requestUrl = new URL(`${server.baseUrl}${pathname}`);
719
- if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
720
- const repo = readRepoConnection(context.projectRoot);
721
- throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
1144
+ let scopedServerProjectRoot = server.serverProjectRoot;
1145
+ if (server.connectionKind === "remote" && !scopedServerProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
1146
+ const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: server.authToken });
1147
+ if (link.ok && link.serverProjectRoot) {
1148
+ scopedServerProjectRoot = link.serverProjectRoot;
1149
+ } else {
1150
+ const repo = readRepoConnection(context.projectRoot);
1151
+ const target = link.alias && link.baseUrl ? `${link.alias} (${link.baseUrl})` : server.baseUrl;
1152
+ throw new CliError(`Remote server ${target} is selected for ${link.repoSlug ?? repo?.project ?? "this repo"}, but this checkout has no server-host project root link. ${link.message}`, 1, { hint: link.hint });
1153
+ }
722
1154
  }
723
1155
  const headers = mergeHeaders(init.headers, server.authToken);
724
- if (server.serverProjectRoot)
725
- headers.set("x-rig-project-root", server.serverProjectRoot);
1156
+ if (scopedServerProjectRoot)
1157
+ headers.set("x-rig-project-root", scopedServerProjectRoot);
726
1158
  if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
727
1159
  requestUrl.searchParams.set("rt", server.authToken);
728
1160
  }
@@ -1346,7 +1778,15 @@ var init__async_ui = __esm(() => {
1346
1778
  });
1347
1779
 
1348
1780
  // packages/cli/src/commands/_pi-frontend.ts
1349
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
1781
+ var exports__pi_frontend = {};
1782
+ __export(exports__pi_frontend, {
1783
+ shouldRequireOperatorTranscript: () => shouldRequireOperatorTranscript,
1784
+ runWithProcessExitGuard: () => runWithProcessExitGuard,
1785
+ missingOperatorTranscriptMessage: () => missingOperatorTranscriptMessage,
1786
+ buildOperatorPiEnv: () => buildOperatorPiEnv,
1787
+ attachRunBundledPiFrontend: () => attachRunBundledPiFrontend
1788
+ });
1789
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
1350
1790
  import { homedir as homedir2, tmpdir } from "os";
1351
1791
  import { join } from "path";
1352
1792
  import { main as runPiMain } from "@earendil-works/pi-coding-agent";
@@ -1398,7 +1838,7 @@ function statusFromRunDetails(run) {
1398
1838
  async function prepareOperatorConsole(context, runId, tempSessionDir) {
1399
1839
  const server = await ensureServerForCli(context.projectRoot);
1400
1840
  const localCwd = join(homedir2(), ".rig", "drones", runId.slice(0, 8));
1401
- mkdirSync3(localCwd, { recursive: true });
1841
+ mkdirSync4(localCwd, { recursive: true });
1402
1842
  trustDroneCwd(localCwd);
1403
1843
  installRigPiTheme();
1404
1844
  let sessionFileArg = [];
@@ -1420,7 +1860,7 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
1420
1860
  return line;
1421
1861
  }).join(`
1422
1862
  `);
1423
- writeFileSync2(localSessionPath, content);
1863
+ writeFileSync3(localSessionPath, content);
1424
1864
  sessionFileArg = ["--session", localSessionPath];
1425
1865
  }
1426
1866
  } catch {}
@@ -1436,12 +1876,12 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
1436
1876
  function trustDroneCwd(localCwd) {
1437
1877
  try {
1438
1878
  const agentDir = join(homedir2(), ".pi", "agent");
1439
- mkdirSync3(agentDir, { recursive: true });
1879
+ mkdirSync4(agentDir, { recursive: true });
1440
1880
  const trustPath = join(agentDir, "trust.json");
1441
1881
  const store = existsSync4(trustPath) ? JSON.parse(readFileSync3(trustPath, "utf8")) : {};
1442
1882
  if (store[localCwd] !== true) {
1443
1883
  store[localCwd] = true;
1444
- writeFileSync2(trustPath, `${JSON.stringify(store, null, "\t")}
1884
+ writeFileSync3(trustPath, `${JSON.stringify(store, null, "\t")}
1445
1885
  `);
1446
1886
  }
1447
1887
  } catch {}
@@ -1449,15 +1889,38 @@ function trustDroneCwd(localCwd) {
1449
1889
  function installRigPiTheme() {
1450
1890
  try {
1451
1891
  const themesDir = join(homedir2(), ".pi", "agent", "themes");
1452
- mkdirSync3(themesDir, { recursive: true });
1892
+ mkdirSync4(themesDir, { recursive: true });
1453
1893
  const themePath = join(themesDir, "rig.json");
1454
1894
  const next = `${JSON.stringify(RIG_PI_THEME, null, "\t")}
1455
1895
  `;
1456
1896
  if (!existsSync4(themePath) || readFileSync3(themePath, "utf8") !== next) {
1457
- writeFileSync2(themePath, next);
1897
+ writeFileSync3(themePath, next);
1458
1898
  }
1459
1899
  } catch {}
1460
1900
  }
1901
+ async function runWithProcessExitGuard(body) {
1902
+ const realExit = process.exit;
1903
+ let exitCode = 0;
1904
+ let signalQuit = () => {};
1905
+ const quit = new Promise((resolve4) => {
1906
+ signalQuit = resolve4;
1907
+ });
1908
+ const guardedExit = (code) => {
1909
+ exitCode = typeof code === "number" ? code : 0;
1910
+ signalQuit();
1911
+ return;
1912
+ };
1913
+ process.exit = guardedExit;
1914
+ try {
1915
+ await Promise.race([Promise.resolve().then(body), quit]);
1916
+ } finally {
1917
+ process.exit = realExit;
1918
+ }
1919
+ return exitCode;
1920
+ }
1921
+ function runPiMainReturningOnQuit(args, options) {
1922
+ return runWithProcessExitGuard(() => runPiMain(args, options));
1923
+ }
1461
1924
  async function attachRunBundledPiFrontend(context, input) {
1462
1925
  const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
1463
1926
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -1473,16 +1936,20 @@ async function attachRunBundledPiFrontend(context, input) {
1473
1936
  };
1474
1937
  let detached = false;
1475
1938
  try {
1476
- await runPiMain([
1939
+ const piArgs = [
1477
1940
  "--offline",
1478
1941
  "--no-extensions",
1479
1942
  "--no-skills",
1480
1943
  "--no-prompt-templates",
1481
1944
  "--no-context-files",
1482
1945
  ...sessionFileArg
1483
- ], {
1484
- extensionFactories: [piRigExtensionFactory]
1485
- });
1946
+ ];
1947
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
1948
+ if (input.returnOnQuit) {
1949
+ await runPiMainReturningOnQuit(piArgs, piOptions);
1950
+ } else {
1951
+ await runPiMain(piArgs, piOptions);
1952
+ }
1486
1953
  detached = true;
1487
1954
  } finally {
1488
1955
  restoreEnv();
@@ -1695,6 +2162,21 @@ async function captureConsole(fn) {
1695
2162
  console.error = original.error;
1696
2163
  }
1697
2164
  }
2165
+ async function releaseRendererForExternalTui(ctx) {
2166
+ const renderer = ctx.renderer;
2167
+ if (!renderer)
2168
+ return;
2169
+ if (renderer.suspend) {
2170
+ await renderer.suspend();
2171
+ return;
2172
+ }
2173
+ if (renderer.destroy) {
2174
+ await renderer.destroy();
2175
+ }
2176
+ }
2177
+ async function resumeRendererAfterExternalTui(ctx) {
2178
+ await ctx.renderer?.resume?.();
2179
+ }
1698
2180
  function arrayFromPayload(value) {
1699
2181
  if (Array.isArray(value)) {
1700
2182
  return value.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
@@ -2727,12 +3209,13 @@ var init__help_catalog = __esm(() => {
2727
3209
  {
2728
3210
  name: "server",
2729
3211
  summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
2730
- usage: ["rig server <status|list|add|use|start> [options]"],
3212
+ usage: ["rig server <status|list|add|use|repair-link|start> [options]"],
2731
3213
  commands: [
2732
- { command: "status", description: "Show the selected server for this repo.", primary: true },
3214
+ { command: "status", description: "Show the selected server and remote project-root link for this repo.", primary: true },
2733
3215
  { command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
2734
3216
  { command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
2735
3217
  { command: "use <alias>", description: "Select a saved remote server alias.", primary: true },
3218
+ { command: "repair-link [--prepare|--backfill-only] [--repo owner/repo]", description: "Backfill or prepare the selected remote's server-host checkout link.", primary: true },
2736
3219
  { command: "list", description: "List saved local/remote server aliases.", primary: true },
2737
3220
  { command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
2738
3221
  ],
@@ -2740,6 +3223,7 @@ var init__help_catalog = __esm(() => {
2740
3223
  "rig server status",
2741
3224
  "rig server add prod https://where.rig-does.work",
2742
3225
  "rig server use prod",
3226
+ "rig server repair-link --repo owner/repo",
2743
3227
  "rig server use local",
2744
3228
  "rig server start --port 3773"
2745
3229
  ],
@@ -3254,7 +3738,7 @@ function resolveControlPlaneDefinitionRoot(projectRoot) {
3254
3738
  var init__paths = () => {};
3255
3739
 
3256
3740
  // packages/cli/src/report-bug.ts
3257
- import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
3741
+ import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync5, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
3258
3742
  import { basename as basename2, extname, join as join2, resolve as resolve8 } from "path";
3259
3743
  function slugifyBugTitle(value) {
3260
3744
  const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/g, "");
@@ -3293,8 +3777,8 @@ function createBugReportFiles(input) {
3293
3777
  }
3294
3778
  rmSync3(reportDir, { recursive: true, force: true });
3295
3779
  }
3296
- mkdirSync4(assetDir, { recursive: true });
3297
- mkdirSync4(screenshotDir, { recursive: true });
3780
+ mkdirSync5(assetDir, { recursive: true });
3781
+ mkdirSync5(screenshotDir, { recursive: true });
3298
3782
  const copiedScreenshots = copyEvidenceFiles(input.projectRoot, screenshotDir, input.screenshots ?? [], "screenshot");
3299
3783
  const copiedAssets = copyEvidenceFiles(input.projectRoot, assetDir, input.assets ?? [], "asset");
3300
3784
  const manifestAssets = [
@@ -3324,15 +3808,15 @@ function createBugReportFiles(input) {
3324
3808
  platform: process.platform,
3325
3809
  arch: process.arch
3326
3810
  };
3327
- writeFileSync3(join2(assetDir, "README.md"), buildAssetReadme(input.title), "utf8");
3328
- writeFileSync3(join2(screenshotDir, "README.md"), buildScreenshotReadme(input.title), "utf8");
3811
+ writeFileSync4(join2(assetDir, "README.md"), buildAssetReadme(input.title), "utf8");
3812
+ writeFileSync4(join2(screenshotDir, "README.md"), buildScreenshotReadme(input.title), "utf8");
3329
3813
  if (browserPath && browser) {
3330
- writeFileSync3(browserPath, `${JSON.stringify(browser, null, 2)}
3814
+ writeFileSync4(browserPath, `${JSON.stringify(browser, null, 2)}
3331
3815
  `, "utf8");
3332
3816
  }
3333
- writeFileSync3(manifestPath, `${JSON.stringify(manifest, null, 2)}
3817
+ writeFileSync4(manifestPath, `${JSON.stringify(manifest, null, 2)}
3334
3818
  `, "utf8");
3335
- writeFileSync3(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
3819
+ writeFileSync4(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
3336
3820
  return {
3337
3821
  slug,
3338
3822
  reportDir,
@@ -3509,7 +3993,7 @@ function formatAssetMarkdown(name, path) {
3509
3993
  var init_report_bug = () => {};
3510
3994
 
3511
3995
  // packages/cli/src/commands/task-report-bug.ts
3512
- import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3996
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3513
3997
  import { resolve as resolve9 } from "path";
3514
3998
  import pc3 from "picocolors";
3515
3999
  import {
@@ -3871,7 +4355,7 @@ function appendTaskConfigEntryPreservingText(taskConfigPath, issueId, entry) {
3871
4355
  `).map((line) => ` ${line}`).join(`
3872
4356
  `);
3873
4357
  if (!trimmed || trimmed === "{}") {
3874
- writeFileSync4(taskConfigPath, `{
4358
+ writeFileSync5(taskConfigPath, `{
3875
4359
  ${serializedEntry}
3876
4360
  }
3877
4361
  `, "utf8");
@@ -3887,7 +4371,7 @@ ${serializedEntry}
3887
4371
  throw new CliError(`Invalid trailing content in task config: ${taskConfigPath}`, 1, { hint: "Remove the trailing content so the file is a single JSON object, then retry." });
3888
4372
  }
3889
4373
  const comma = before.trim() === "{" ? "" : ",";
3890
- writeFileSync4(taskConfigPath, `${before}${comma}
4374
+ writeFileSync5(taskConfigPath, `${before}${comma}
3891
4375
  ${serializedEntry}
3892
4376
  }
3893
4377
  `, "utf8");
@@ -4272,7 +4756,7 @@ var init_task_report_bug = __esm(() => {
4272
4756
  });
4273
4757
 
4274
4758
  // packages/cli/src/commands/browser.ts
4275
- import { mkdirSync as mkdirSync5, rmSync as rmSync4 } from "fs";
4759
+ import { mkdirSync as mkdirSync6, rmSync as rmSync4 } from "fs";
4276
4760
  import { resolve as resolve10 } from "path";
4277
4761
  import { spawn } from "child_process";
4278
4762
  import { emitKeypressEvents } from "readline";
@@ -4850,7 +5334,7 @@ function readBrowserDemoCommandLine(command) {
4850
5334
  }
4851
5335
  async function launchBrowserDemo(runtime) {
4852
5336
  rmSync4(resolve10(runtime.browserDir, runtime.stateDir), { recursive: true, force: true });
4853
- mkdirSync5(resolve10(runtime.browserDir, runtime.stateDir), { recursive: true });
5337
+ mkdirSync6(resolve10(runtime.browserDir, runtime.stateDir), { recursive: true });
4854
5338
  const launcherPath = resolve10(runtime.browserDir, "scripts/electron-launcher.mjs");
4855
5339
  const launcher = await import(pathToFileURL(launcherPath).href);
4856
5340
  const electronPath = await launcher.resolveElectronPath();
@@ -5199,7 +5683,7 @@ var init_profile_and_review = __esm(() => {
5199
5683
  });
5200
5684
 
5201
5685
  // packages/cli/src/commands/_policy.ts
5202
- import { appendFileSync, mkdirSync as mkdirSync6 } from "fs";
5686
+ import { appendFileSync, mkdirSync as mkdirSync7 } from "fs";
5203
5687
  import { resolve as resolve11 } from "path";
5204
5688
  import { evaluate as evaluate2, loadPolicy as loadPolicy2, resolveAction as resolveAction2 } from "@rig/runtime/control-plane/runtime/guard";
5205
5689
  import { resolveHarnessPaths } from "@rig/runtime/control-plane/native/utils";
@@ -5216,7 +5700,7 @@ function appendControlledBashAudit(context, mode, command, args, matchedRuleIds,
5216
5700
  }
5217
5701
  const logsDir = resolveHarnessPaths(context.projectRoot).logsDir;
5218
5702
  const logFile = resolve11(logsDir, "controlled-bash.jsonl");
5219
- mkdirSync6(logsDir, { recursive: true });
5703
+ mkdirSync7(logsDir, { recursive: true });
5220
5704
  const payload = {
5221
5705
  timestamp: new Date().toISOString(),
5222
5706
  mode,
@@ -5510,9 +5994,9 @@ var exports_pi = {};
5510
5994
  __export(exports_pi, {
5511
5995
  executePi: () => executePi
5512
5996
  });
5513
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
5997
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
5514
5998
  import { homedir as homedir5 } from "os";
5515
- import { dirname as dirname2, resolve as resolve13 } from "path";
5999
+ import { dirname as dirname3, resolve as resolve13 } from "path";
5516
6000
  function settingsPath(root) {
5517
6001
  return resolve13(root, ".pi", "settings.json");
5518
6002
  }
@@ -5537,8 +6021,8 @@ function packageKey(entry) {
5537
6021
  return JSON.stringify(entry);
5538
6022
  }
5539
6023
  function writeSettings(path, settings) {
5540
- mkdirSync7(dirname2(path), { recursive: true });
5541
- writeFileSync5(path, `${JSON.stringify(settings, null, 2)}
6024
+ mkdirSync8(dirname3(path), { recursive: true });
6025
+ writeFileSync6(path, `${JSON.stringify(settings, null, 2)}
5542
6026
  `, "utf-8");
5543
6027
  }
5544
6028
  async function searchNpmForPiExtensions(term) {
@@ -5721,6 +6205,14 @@ function permissionAllowsPr2(payload) {
5721
6205
  function isNotFoundError(error) {
5722
6206
  return /\b(404|not found)\b/i.test(message(error));
5723
6207
  }
6208
+ function isLoopbackBaseUrl(baseUrl) {
6209
+ try {
6210
+ const host = new URL(baseUrl).hostname.toLowerCase();
6211
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
6212
+ } catch {
6213
+ return false;
6214
+ }
6215
+ }
5724
6216
  function projectCheckoutReady(payload) {
5725
6217
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
5726
6218
  return null;
@@ -5775,25 +6267,37 @@ async function runFastTaskRunPreflight(context, options = {}) {
5775
6267
  }
5776
6268
  const repo = readRepoConnection(context.projectRoot);
5777
6269
  const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
5778
- checks.push(repo ? preflightCheck("project-link", "project linked to selected server", repo.project ? "pass" : remoteWithoutProjectLink ? "fail" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : " (missing project link)"}`, remoteWithoutProjectLink ? "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to restore the remote project link." : "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to selected server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
6270
+ let remoteRootBlocked = false;
6271
+ checks.push(repo ? preflightCheck("project-link", "project linked to selected server", repo.project ? "pass" : remoteWithoutProjectLink ? "fail" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : " (missing project link)"}`, remoteWithoutProjectLink ? "Run `rig server repair-link --repo owner/repo` or `rig init --repo owner/repo --server remote` to restore the remote project link." : "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to selected server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
6272
+ if (selectedServer?.connectionKind === "remote" && repo?.project && !selectedServer.serverProjectRoot) {
6273
+ const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: selectedServer.authToken });
6274
+ const loopbackDevRemote = !selectedServer.authToken && isLoopbackBaseUrl(selectedServer.baseUrl);
6275
+ checks.push(preflightCheck("remote-project-root-link", "remote server checkout/root linked", link.ok ? "pass" : loopbackDevRemote ? "warn" : "fail", link.ok ? `${link.repoSlug} -> ${link.serverProjectRoot}` : link.message, link.ok ? undefined : loopbackDevRemote ? "Loopback development remotes may continue without x-rig-project-root; production remotes must run `rig server repair-link`." : link.hint));
6276
+ remoteRootBlocked = !link.ok && !loopbackDevRemote;
6277
+ }
5779
6278
  try {
5780
6279
  const auth = await request("/api/github/auth/status");
5781
6280
  checks.push(isAuthenticated2(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
5782
6281
  } catch (error) {
5783
6282
  checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
5784
6283
  }
5785
- try {
5786
- const projection = await request("/api/workspace/task-projection");
5787
- checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
5788
- } catch (error) {
5789
- checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
5790
- }
5791
- try {
5792
- const permissions = await request("/api/github/repo/permissions");
5793
- const allowed = permissionAllowsPr2(permissions);
5794
- checks.push(allowed === false ? preflightCheck("github-repo-permissions", "GitHub PR permissions", "fail", JSON.stringify(permissions).slice(0, 120), "Grant the selected GitHub token permission to push branches and open PRs.") : preflightCheck("github-repo-permissions", "GitHub PR permissions", allowed === true ? "pass" : "warn", JSON.stringify(permissions).slice(0, 120), "Confirm the selected token can push branches and open PRs."));
5795
- } catch (error) {
5796
- checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
6284
+ if (!remoteRootBlocked) {
6285
+ try {
6286
+ const projection = await request("/api/workspace/task-projection");
6287
+ checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
6288
+ } catch (error) {
6289
+ checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
6290
+ }
6291
+ try {
6292
+ const permissions = await request("/api/github/repo/permissions");
6293
+ const allowed = permissionAllowsPr2(permissions);
6294
+ checks.push(allowed === false ? preflightCheck("github-repo-permissions", "GitHub PR permissions", "fail", JSON.stringify(permissions).slice(0, 120), "Grant the selected GitHub token permission to push branches and open PRs.") : preflightCheck("github-repo-permissions", "GitHub PR permissions", allowed === true ? "pass" : "warn", JSON.stringify(permissions).slice(0, 120), "Confirm the selected token can push branches and open PRs."));
6295
+ } catch (error) {
6296
+ checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
6297
+ }
6298
+ } else {
6299
+ checks.push(preflightCheck("task-projection", "task projection ready", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
6300
+ checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
5797
6301
  }
5798
6302
  if (repo?.project) {
5799
6303
  try {
@@ -5805,19 +6309,24 @@ async function runFastTaskRunPreflight(context, options = {}) {
5805
6309
  }
5806
6310
  }
5807
6311
  if (taskId) {
5808
- try {
5809
- const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
5810
- const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
5811
- checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
5812
- } catch (error) {
5813
- checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
5814
- }
5815
- try {
5816
- const runs = await request("/api/runs?limit=200");
5817
- const duplicate = activeDuplicateRun(runs, taskId);
5818
- checks.push(duplicate ? preflightCheck("duplicate-active-run", "one active run per task", "fail", String(duplicate.runId ?? taskId), "Attach to or stop the existing run before starting another one for this task.") : preflightCheck("duplicate-active-run", "one active run per task", "pass", taskId));
5819
- } catch (error) {
5820
- checks.push(preflightCheck("duplicate-active-run", "one active run per task", "warn", message(error), "Could not list active runs; the server will still reject conflicting runs if configured."));
6312
+ if (!remoteRootBlocked) {
6313
+ try {
6314
+ const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
6315
+ const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
6316
+ checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
6317
+ } catch (error) {
6318
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
6319
+ }
6320
+ try {
6321
+ const runs = await request("/api/runs?limit=200");
6322
+ const duplicate = activeDuplicateRun(runs, taskId);
6323
+ checks.push(duplicate ? preflightCheck("duplicate-active-run", "one active run per task", "fail", String(duplicate.runId ?? taskId), "Attach to or stop the existing run before starting another one for this task.") : preflightCheck("duplicate-active-run", "one active run per task", "pass", taskId));
6324
+ } catch (error) {
6325
+ checks.push(preflightCheck("duplicate-active-run", "one active run per task", "warn", message(error), "Could not list active runs; the server will still reject conflicting runs if configured."));
6326
+ }
6327
+ } else {
6328
+ checks.push(preflightCheck("issue", "task/issue accessible", "fail", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
6329
+ checks.push(preflightCheck("duplicate-active-run", "one active run per task", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
5821
6330
  }
5822
6331
  }
5823
6332
  if ((options.runtimeAdapter ?? "pi") === "pi") {
@@ -6298,7 +6807,7 @@ import {
6298
6807
  chmodSync,
6299
6808
  copyFileSync as copyFileSync2,
6300
6809
  existsSync as existsSync12,
6301
- mkdirSync as mkdirSync8,
6810
+ mkdirSync as mkdirSync9,
6302
6811
  readdirSync,
6303
6812
  readlinkSync,
6304
6813
  rmSync as rmSync5,
@@ -6403,7 +6912,7 @@ async function executeDist(context, args) {
6403
6912
  requireNoExtraArgs(pending, "rig dist install [--scope user|system] [--path <dir>]");
6404
6913
  const scope = parseInstallScope(scopeResult.value);
6405
6914
  const installDir = resolveInstallDir(scope, pathResult.value);
6406
- mkdirSync8(installDir, { recursive: true });
6915
+ mkdirSync9(installDir, { recursive: true });
6407
6916
  let source = await findLatestDistBinary(context.projectRoot);
6408
6917
  let buildDir = null;
6409
6918
  if (!source) {
@@ -6458,7 +6967,7 @@ async function executeDist(context, args) {
6458
6967
  const fp = await computeRuntimeImageFingerprint(context.projectRoot);
6459
6968
  const currentId = computeRuntimeImageId(fp);
6460
6969
  const imagesDir = resolve16(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
6461
- mkdirSync8(imagesDir, { recursive: true });
6970
+ mkdirSync9(imagesDir, { recursive: true });
6462
6971
  let pruned = 0;
6463
6972
  for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
6464
6973
  if (entry.isDirectory() && entry.name !== currentId) {
@@ -6470,9 +6979,9 @@ async function executeDist(context, args) {
6470
6979
  console.log(`Pruned ${pruned} stale image(s).`);
6471
6980
  }
6472
6981
  const imageDir = resolve16(imagesDir, currentId);
6473
- mkdirSync8(resolve16(imageDir, "bin/hooks"), { recursive: true });
6474
- mkdirSync8(resolve16(imageDir, "bin/plugins"), { recursive: true });
6475
- mkdirSync8(resolve16(imageDir, "bin/validators"), { recursive: true });
6982
+ mkdirSync9(resolve16(imageDir, "bin/hooks"), { recursive: true });
6983
+ mkdirSync9(resolve16(imageDir, "bin/plugins"), { recursive: true });
6984
+ mkdirSync9(resolve16(imageDir, "bin/validators"), { recursive: true });
6476
6985
  const hookNames = [
6477
6986
  "scope-guard",
6478
6987
  "import-guard",
@@ -6497,8 +7006,8 @@ async function executeDist(context, args) {
6497
7006
  const binPluginsDir = resolve16(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
6498
7007
  const validatorsRoot = resolve16(hostProjectRoot, "packages/runtime/src/control-plane/validators");
6499
7008
  const binValidatorsDir = resolve16(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
6500
- mkdirSync8(binPluginsDir, { recursive: true });
6501
- mkdirSync8(binValidatorsDir, { recursive: true });
7009
+ mkdirSync9(binPluginsDir, { recursive: true });
7010
+ mkdirSync9(binValidatorsDir, { recursive: true });
6502
7011
  if (existsSync12(pluginsDir)) {
6503
7012
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
6504
7013
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
@@ -6937,16 +7446,19 @@ function formatConnectionList(connections) {
6937
7446
  return [formatSection("Rig servers", `${rows.length} available`), `${pc5.bold(pad("ALIAS", aliasWidth))} ${pc5.bold("KIND")} ${pc5.bold("TARGET")}`, ...lines, "", ...formatNextSteps(["Select one: `rig server use <alias|local>`"])].join(`
6938
7447
  `);
6939
7448
  }
6940
- function formatConnectionStatus(selected, connections) {
7449
+ function formatConnectionStatus(selected, connections, repo, remoteProjectLink) {
6941
7450
  const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
6942
7451
  const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
7452
+ const rootLabel = remoteProjectLink ? remoteProjectLink.ok ? `ready \xB7 ${remoteProjectLink.serverProjectRoot ?? repo?.serverProjectRoot ?? "linked"}` : `${remoteProjectLink.status ?? "needs repair"} \xB7 ${remoteProjectLink.hint ?? remoteProjectLink.message ?? "run rig server repair-link"}` : repo?.serverProjectRoot ? repo.serverProjectRoot : "not required";
6943
7453
  return [
6944
7454
  formatSection("Rig server", "selected for this repo"),
6945
7455
  `${themeFaint("\u2502")} ${themeDim("selected ")} ${pc5.bold(selected)}`,
6946
7456
  `${themeFaint("\u2502")} ${themeDim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
6947
7457
  `${themeFaint("\u2502")} ${themeDim("target ")} ${target ?? "not configured"}`,
7458
+ ...repo?.project ? [`${themeFaint("\u2502")} ${themeDim("project ")} ${repo.project}`] : [],
7459
+ ...connection?.kind === "remote" ? [`${themeFaint("\u2502")} ${themeDim("root ")} ${rootLabel}`] : [],
6948
7460
  "",
6949
- ...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
7461
+ ...formatNextSteps(remoteProjectLink && !remoteProjectLink.ok ? ["Repair remote link: `rig server repair-link`", "Authenticate first if needed: `rig github auth import-gh`"] : ["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
6950
7462
  ].join(`
6951
7463
  `);
6952
7464
  }
@@ -7175,7 +7687,7 @@ var init_inbox = __esm(() => {
7175
7687
 
7176
7688
  // packages/cli/src/commands/_snapshot-upload.ts
7177
7689
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
7178
- import { dirname as dirname3, resolve as resolve17, relative, sep } from "path";
7690
+ import { dirname as dirname4, resolve as resolve17, relative, sep } from "path";
7179
7691
  function toPosixPath(path) {
7180
7692
  return path.split(sep).join("/");
7181
7693
  }
@@ -7276,7 +7788,7 @@ __export(exports_init, {
7276
7788
  DEMO_TASKS_RELATIVE_DIR: () => DEMO_TASKS_RELATIVE_DIR,
7277
7789
  DEMO_TASKS: () => DEMO_TASKS
7278
7790
  });
7279
- import { appendFileSync as appendFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
7791
+ import { appendFileSync as appendFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
7280
7792
  import { spawnSync } from "child_process";
7281
7793
  import { basename as basename3, resolve as resolve18 } from "path";
7282
7794
  import { buildRigInitConfigSource } from "@rig/core";
@@ -7300,14 +7812,14 @@ function parseRepoSlug(value) {
7300
7812
  }
7301
7813
  function ensureRigPrivateDirs(projectRoot) {
7302
7814
  const rigDir = resolve18(projectRoot, ".rig");
7303
- mkdirSync9(resolve18(rigDir, "state"), { recursive: true });
7304
- mkdirSync9(resolve18(rigDir, "logs"), { recursive: true });
7305
- mkdirSync9(resolve18(rigDir, "runs"), { recursive: true });
7306
- mkdirSync9(resolve18(rigDir, "tmp"), { recursive: true });
7307
- mkdirSync9(resolve18(projectRoot, "artifacts"), { recursive: true });
7815
+ mkdirSync10(resolve18(rigDir, "state"), { recursive: true });
7816
+ mkdirSync10(resolve18(rigDir, "logs"), { recursive: true });
7817
+ mkdirSync10(resolve18(rigDir, "runs"), { recursive: true });
7818
+ mkdirSync10(resolve18(rigDir, "tmp"), { recursive: true });
7819
+ mkdirSync10(resolve18(projectRoot, "artifacts"), { recursive: true });
7308
7820
  const taskConfigPath = resolve18(rigDir, "task-config.json");
7309
7821
  if (!existsSync13(taskConfigPath))
7310
- writeFileSync6(taskConfigPath, `{}
7822
+ writeFileSync7(taskConfigPath, `{}
7311
7823
  `, "utf-8");
7312
7824
  }
7313
7825
  function ensureGitignoreEntries(projectRoot) {
@@ -7335,7 +7847,7 @@ function ensureRigConfigPackageDependencies(projectRoot) {
7335
7847
  ...existsSync13(path) ? existing : { name: "rig-project", private: true },
7336
7848
  devDependencies
7337
7849
  };
7338
- writeFileSync6(path, `${JSON.stringify(next, null, 2)}
7850
+ writeFileSync7(path, `${JSON.stringify(next, null, 2)}
7339
7851
  `, "utf8");
7340
7852
  }
7341
7853
  function applyGitHubProjectConfig(source, options) {
@@ -7361,9 +7873,9 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
7361
7873
  const selected = strategy ?? { kind: "managed-clone" };
7362
7874
  switch (selected.kind) {
7363
7875
  case "managed-clone":
7364
- return { kind: "managed-clone", path: projectRoot };
7876
+ return { kind: "managed-clone" };
7365
7877
  case "current-ref":
7366
- return { kind: "current-ref", path: projectRoot, ...selected.ref ? { ref: selected.ref } : {} };
7878
+ return { kind: "current-ref", ...selected.ref ? { ref: selected.ref } : {} };
7367
7879
  case "uploaded-snapshot":
7368
7880
  return { kind: "uploaded-snapshot", path: projectRoot, source: "local-working-tree" };
7369
7881
  case "existing-path":
@@ -7616,7 +8128,7 @@ function remoteGitHubAuthMetadata(payload) {
7616
8128
  };
7617
8129
  }
7618
8130
  function writeRemoteGitHubAuthState(projectRoot, input) {
7619
- writeFileSync6(resolve18(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
8131
+ writeFileSync7(resolve18(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
7620
8132
  authenticated: true,
7621
8133
  source: input.source,
7622
8134
  storedOnServer: true,
@@ -7664,7 +8176,7 @@ function runLocalFilesInit(context, options) {
7664
8176
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
7665
8177
  } else {
7666
8178
  const projectName = basename3(projectRoot) || "rig-project";
7667
- writeFileSync6(configTsPath, buildRigInitConfigSource({
8179
+ writeFileSync7(configTsPath, buildRigInitConfigSource({
7668
8180
  projectName,
7669
8181
  taskSource: { kind: "files", path: "tasks" },
7670
8182
  useStandardPlugin: true
@@ -7673,8 +8185,8 @@ function runLocalFilesInit(context, options) {
7673
8185
  ensureRigConfigPackageDependencies(projectRoot);
7674
8186
  const tasksDir = resolve18(projectRoot, "tasks");
7675
8187
  if (!existsSync13(tasksDir)) {
7676
- mkdirSync9(tasksDir, { recursive: true });
7677
- writeFileSync6(resolve18(tasksDir, "T-1.json"), `${JSON.stringify({ id: "T-1", title: "My first Rig task", body: "Describe the change you want an agent to make." }, null, 2)}
8188
+ mkdirSync10(tasksDir, { recursive: true });
8189
+ writeFileSync7(resolve18(tasksDir, "T-1.json"), `${JSON.stringify({ id: "T-1", title: "My first Rig task", body: "Describe the change you want an agent to make." }, null, 2)}
7678
8190
  `, "utf-8");
7679
8191
  }
7680
8192
  if (context.outputMode !== "json") {
@@ -7698,7 +8210,7 @@ function runDemoInit(context, options) {
7698
8210
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
7699
8211
  }
7700
8212
  } else {
7701
- writeFileSync6(configTsPath, buildRigInitConfigSource({
8213
+ writeFileSync7(configTsPath, buildRigInitConfigSource({
7702
8214
  projectName: basename3(projectRoot) || "rig-demo",
7703
8215
  taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
7704
8216
  useStandardPlugin: true
@@ -7707,14 +8219,14 @@ function runDemoInit(context, options) {
7707
8219
  }
7708
8220
  ensureRigConfigPackageDependencies(projectRoot);
7709
8221
  const demoTasksDir = resolve18(projectRoot, DEMO_TASKS_RELATIVE_DIR);
7710
- mkdirSync9(demoTasksDir, { recursive: true });
8222
+ mkdirSync10(demoTasksDir, { recursive: true });
7711
8223
  const taskIds = [];
7712
8224
  for (const task of DEMO_TASKS) {
7713
8225
  const id = String(task.id);
7714
8226
  taskIds.push(id);
7715
8227
  const taskPath = resolve18(demoTasksDir, `${id}.json`);
7716
8228
  if (!existsSync13(taskPath)) {
7717
- writeFileSync6(taskPath, `${JSON.stringify(task, null, 2)}
8229
+ writeFileSync7(taskPath, `${JSON.stringify(task, null, 2)}
7718
8230
  `, "utf-8");
7719
8231
  }
7720
8232
  }
@@ -7742,21 +8254,32 @@ function runDemoInit(context, options) {
7742
8254
  }
7743
8255
  async function runControlPlaneInit(context, options) {
7744
8256
  const projectRoot = context.projectRoot;
7745
- const detectedSlug = options.repoSlug ?? detectOriginRepoSlug(projectRoot);
8257
+ const existingRepoConnection = readRepoConnection(projectRoot);
8258
+ const selectedConnection = (() => {
8259
+ try {
8260
+ return resolveSelectedConnection(projectRoot);
8261
+ } catch {
8262
+ return null;
8263
+ }
8264
+ })();
8265
+ const selectedRemote = selectedConnection?.connection.kind === "remote" ? { alias: selectedConnection.alias, connection: selectedConnection.connection } : null;
8266
+ const detectedSlug = options.repoSlug ?? existingRepoConnection?.project ?? detectOriginRepoSlug(projectRoot);
8267
+ const serverKind = options.server ?? (selectedRemote ? "remote" : "local");
7746
8268
  if (!detectedSlug) {
7747
8269
  const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
7748
- if ((options.server ?? "local") === "local" && authMethod2 === "skip") {
8270
+ if (serverKind === "local" && authMethod2 === "skip") {
7749
8271
  return runLocalFilesInit(context, options);
7750
8272
  }
7751
8273
  throw new CliError("Could not detect GitHub repo slug from origin. Pass --repo owner/repo \u2014 or run `rig init --yes --server local --github-auth skip` for a local files-source project without GitHub.", 1);
7752
8274
  }
7753
8275
  const repo = parseRepoSlug(detectedSlug);
7754
- const serverKind = options.server ?? "local";
7755
- const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : "remote");
8276
+ const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : selectedRemote?.alias ?? "remote");
8277
+ const selectedRemoteUrl = selectedRemote && (!options.connectionAlias || options.connectionAlias === selectedRemote.alias) ? selectedRemote.connection.baseUrl : undefined;
8278
+ const remoteUrl = serverKind === "remote" ? (options.remoteUrl ?? selectedRemoteUrl)?.replace(/\/+$/, "") : undefined;
7756
8279
  if (serverKind === "remote") {
7757
- if (!options.remoteUrl)
7758
- throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`." });
7759
- upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: options.remoteUrl });
8280
+ if (!remoteUrl)
8281
+ throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`, or first select a saved remote with `rig server use <alias>`." });
8282
+ upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: remoteUrl });
7760
8283
  }
7761
8284
  writeRepoConnection(projectRoot, {
7762
8285
  selected: connectionAlias,
@@ -7779,11 +8302,11 @@ async function runControlPlaneInit(context, options) {
7779
8302
  taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
7780
8303
  useStandardPlugin: true
7781
8304
  }), options);
7782
- writeFileSync6(configTsPath, source, "utf-8");
8305
+ writeFileSync7(configTsPath, source, "utf-8");
7783
8306
  }
7784
8307
  ensureRigConfigPackageDependencies(projectRoot);
7785
8308
  }
7786
- writeFileSync6(resolve18(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
8309
+ writeFileSync7(resolve18(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
7787
8310
  `, "utf8");
7788
8311
  const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
7789
8312
  let uploadedSnapshot = null;
@@ -7798,7 +8321,7 @@ async function runControlPlaneInit(context, options) {
7798
8321
  let githubAuth = null;
7799
8322
  let deviceAuth = null;
7800
8323
  const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
7801
- const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${options.remoteUrl ?? "the remote Rig server"}.` : null;
8324
+ const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${remoteUrl ?? "the remote Rig server"}.` : null;
7802
8325
  if (remoteGhTokenWarning && !options.yes) {
7803
8326
  throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
7804
8327
  }
@@ -8178,8 +8701,8 @@ var init_init = __esm(() => {
8178
8701
 
8179
8702
  // packages/cli/src/commands/github.ts
8180
8703
  import { spawnSync as spawnSync2 } from "child_process";
8181
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
8182
- import { dirname as dirname4, resolve as resolve19 } from "path";
8704
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync8 } from "fs";
8705
+ import { dirname as dirname5, resolve as resolve19 } from "path";
8183
8706
  function printPayload(context, payload, fallback) {
8184
8707
  if (context.outputMode === "json")
8185
8708
  console.log(JSON.stringify(payload, null, 2));
@@ -8218,8 +8741,8 @@ function persistRemoteAuthSession(context, source, result, fallbackToken) {
8218
8741
  return;
8219
8742
  const repo = readRepoConnection(context.projectRoot);
8220
8743
  const path = resolve19(context.projectRoot, ".rig", "state", "github-auth.json");
8221
- mkdirSync10(dirname4(path), { recursive: true });
8222
- writeFileSync7(path, `${JSON.stringify({
8744
+ mkdirSync11(dirname5(path), { recursive: true });
8745
+ writeFileSync8(path, `${JSON.stringify({
8223
8746
  authenticated: true,
8224
8747
  source,
8225
8748
  storedOnServer: true,
@@ -10343,13 +10866,46 @@ function parseConnection(alias, value, options) {
10343
10866
  }
10344
10867
  return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
10345
10868
  }
10346
- function printJsonOrText(context, payload, text2) {
10347
- if (context.outputMode === "json") {
10348
- console.log(JSON.stringify(payload, null, 2));
10349
- } else {
10869
+ function printJsonOrText(context, _payload, text2) {
10870
+ if (context.outputMode !== "json") {
10350
10871
  console.log(text2);
10351
10872
  }
10352
10873
  }
10874
+ function formatRemoteProjectLinkText(result) {
10875
+ return formatSuccessCard(result.ok ? "Remote project link ready" : "Remote project link needs repair", [
10876
+ ["selected", result.alias ?? "remote"],
10877
+ ["target", result.baseUrl ?? "unknown"],
10878
+ ["repo", result.repoSlug ?? "owner/repo"],
10879
+ ["status", result.status],
10880
+ ...result.serverProjectRoot ? [["server root", result.serverProjectRoot]] : [],
10881
+ ["next", result.ok ? result.next ?? "rig task list" : result.hint]
10882
+ ]);
10883
+ }
10884
+ function parseRepairLinkArgs(rest, usage) {
10885
+ let repoSlug;
10886
+ let mode = "prepare-if-missing";
10887
+ const pending = [...rest];
10888
+ while (pending.length > 0) {
10889
+ const token = pending.shift();
10890
+ if (token === "--repo") {
10891
+ const value = pending.shift()?.trim();
10892
+ if (!value)
10893
+ throw new CliError(`Missing --repo value. Usage: ${usage}`, 1);
10894
+ const normalized = normalizeRepoSlug(value);
10895
+ if (!normalized)
10896
+ throw new CliError(`Invalid --repo value: ${value}`, 1, { hint: "Use the canonical owner/repo slug, e.g. `--repo humanity-org/humanwork`." });
10897
+ repoSlug = normalized;
10898
+ } else if (token === "--backfill-only") {
10899
+ mode = "backfill-only";
10900
+ } else if (token === "--prepare") {
10901
+ mode = "prepare-if-missing";
10902
+ } else {
10903
+ throw new CliError(`Unexpected argument: ${String(token)}
10904
+ Usage: ${usage}`, 1);
10905
+ }
10906
+ }
10907
+ return { repoSlug, mode };
10908
+ }
10353
10909
  async function promptForConnectionAlias(context) {
10354
10910
  const state = readGlobalConnections();
10355
10911
  const repo = readRepoConnection(context.projectRoot);
@@ -10403,37 +10959,67 @@ async function executeConnectionCommand(context, args, options) {
10403
10959
  }
10404
10960
  if (!alias)
10405
10961
  throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
10962
+ let selectedConnection = { kind: "local", mode: "auto" };
10406
10963
  if (alias !== "local") {
10407
10964
  const state = readGlobalConnections();
10408
- if (!state.connections[alias])
10965
+ const connection = state.connections[alias];
10966
+ if (!connection)
10409
10967
  throw new CliError(`Unknown Rig server: ${alias}`, 1, { hint: "Run `rig server list` to see saved aliases, or save one with `rig server add <alias> <url>`." });
10968
+ selectedConnection = connection;
10410
10969
  }
10411
10970
  const previousRepo = readRepoConnection(context.projectRoot);
10971
+ const preserveRemoteRoot = Boolean(previousRepo?.serverProjectRoot && previousRepo.selected === alias && selectedConnection.kind === "remote" && previousRepo.serverProjectRootAlias === alias && previousRepo.serverProjectRootBaseUrl === selectedConnection.baseUrl);
10412
10972
  const repoState = {
10413
10973
  selected: alias,
10414
10974
  ...previousRepo?.project ? { project: previousRepo.project } : {},
10415
10975
  linkedAt: new Date().toISOString(),
10416
- ...previousRepo?.serverProjectRoot && previousRepo.selected === alias ? { serverProjectRoot: previousRepo.serverProjectRoot } : {}
10976
+ ...preserveRemoteRoot ? {
10977
+ serverProjectRoot: previousRepo.serverProjectRoot,
10978
+ serverProjectRootAlias: previousRepo.serverProjectRootAlias,
10979
+ serverProjectRootBaseUrl: previousRepo.serverProjectRootBaseUrl
10980
+ } : {}
10417
10981
  };
10418
10982
  writeRepoConnection(context.projectRoot, repoState);
10419
- printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
10983
+ const remoteProjectLink = alias !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
10984
+ ok: false,
10985
+ status: "error",
10986
+ message: error instanceof Error ? error.message : String(error),
10987
+ hint: "Run `rig server repair-link` after authenticating the selected remote."
10988
+ })) : null;
10989
+ const latestRepo = readRepoConnection(context.projectRoot);
10990
+ const details = { selected: latestRepo?.selected ?? alias, repo: latestRepo, remoteProjectLink };
10991
+ printJsonOrText(context, details, formatSuccessCard("Rig server selected", [
10420
10992
  ["selected", alias],
10421
10993
  ["scope", "this repo"],
10422
- ["next", "rig task list"]
10994
+ ...remoteProjectLink ? [["remote link", remoteProjectLink.ok ? `ready (${remoteProjectLink.serverProjectRoot ?? "linked"})` : `${remoteProjectLink.status}: ${remoteProjectLink.hint}`]] : [],
10995
+ ["next", remoteProjectLink && !remoteProjectLink.ok ? "rig server repair-link" : "rig task list"]
10423
10996
  ]));
10424
- return { ok: true, group: options.group, command: "use", details: repoState };
10997
+ return { ok: true, group: options.group, command: "use", details };
10998
+ }
10999
+ case "repair-link": {
11000
+ const parsed = parseRepairLinkArgs(rest, `${usageName(options)} repair-link [--prepare|--backfill-only] [--repo owner/repo]`);
11001
+ const result = await repairRemoteProjectRootLink(context, parsed);
11002
+ printJsonOrText(context, result, formatRemoteProjectLinkText(result));
11003
+ return { ok: true, group: options.group, command: "repair-link", details: result };
10425
11004
  }
10426
11005
  case "status": {
10427
11006
  requireNoExtraArgs(rest, `${usageName(options)} status`);
10428
11007
  const repo = readRepoConnection(context.projectRoot);
10429
11008
  const global = readGlobalConnections();
10430
- const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
10431
- printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
11009
+ const remoteProjectLink = repo?.selected && repo.selected !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
11010
+ ok: false,
11011
+ status: "error",
11012
+ message: error instanceof Error ? error.message : String(error),
11013
+ hint: "Run `rig server repair-link` after authenticating the selected remote."
11014
+ })) : null;
11015
+ const latestRepo = readRepoConnection(context.projectRoot) ?? repo;
11016
+ const details = { selected: latestRepo?.selected ?? "local", repo: latestRepo, connections: global.connections, remoteProjectLink };
11017
+ printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections, latestRepo ?? null, remoteProjectLink));
10432
11018
  return { ok: true, group: options.group, command: "status", details };
10433
11019
  }
10434
11020
  default:
10435
11021
  throw new CliError(`Unknown ${options.group} command: ${String(command)}
10436
- Usage: ${usageName(options)} <list|add|use|status>`, 1);
11022
+ Usage: ${usageName(options)} <list|add|use|status|repair-link>`, 1);
10437
11023
  }
10438
11024
  }
10439
11025
  var init_connect = __esm(() => {
@@ -10441,13 +11027,14 @@ var init_connect = __esm(() => {
10441
11027
  init_runner();
10442
11028
  init__connection_state();
10443
11029
  init__cli_format();
11030
+ init__server_client();
10444
11031
  });
10445
11032
 
10446
11033
  // packages/cli/src/commands/server.ts
10447
11034
  import { resolveRigServerCommand } from "@rig/runtime/local-server";
10448
11035
  async function executeServer(context, args, options) {
10449
11036
  const [command = "status", ...rest] = args;
10450
- if (["status", "list", "add", "use"].includes(command)) {
11037
+ if (["status", "list", "add", "use", "repair-link"].includes(command)) {
10451
11038
  return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
10452
11039
  }
10453
11040
  switch (command) {
@@ -11234,7 +11821,7 @@ var init_task = __esm(() => {
11234
11821
  });
11235
11822
 
11236
11823
  // packages/cli/src/commands/task-run-driver.ts
11237
- import { copyFileSync as copyFileSync3, existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
11824
+ import { copyFileSync as copyFileSync3, existsSync as existsSync16, mkdirSync as mkdirSync12, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync9 } from "fs";
11238
11825
  import { resolve as resolve24 } from "path";
11239
11826
  import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
11240
11827
  import { createInterface as createLineInterface } from "readline";
@@ -11337,7 +11924,7 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
11337
11924
  try {
11338
11925
  if (!statSync2(sourcePath).isFile())
11339
11926
  continue;
11340
- mkdirSync11(resolve24(targetPath, ".."), { recursive: true });
11927
+ mkdirSync12(resolve24(targetPath, ".."), { recursive: true });
11341
11928
  copyFileSync3(sourcePath, targetPath);
11342
11929
  copied += 1;
11343
11930
  } catch {}
@@ -12208,8 +12795,8 @@ async function executeRigOwnedTaskRun(context, input) {
12208
12795
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
12209
12796
  classifiedAt: new Date().toISOString()
12210
12797
  };
12211
- mkdirSync11(resolve24(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
12212
- writeFileSync8(resolve24(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
12798
+ mkdirSync12(resolve24(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
12799
+ writeFileSync9(resolve24(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
12213
12800
  `, "utf8");
12214
12801
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
12215
12802
  prompt = `${prompt}
@@ -12356,8 +12943,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
12356
12943
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
12357
12944
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
12358
12945
  if (readyFile) {
12359
- mkdirSync11(resolve24(readyFile, ".."), { recursive: true });
12360
- writeFileSync8(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
12946
+ mkdirSync12(resolve24(readyFile, ".."), { recursive: true });
12947
+ writeFileSync9(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
12361
12948
  `, "utf8");
12362
12949
  }
12363
12950
  appendRunLog(context.projectRoot, input.runId, {
@@ -13303,7 +13890,7 @@ var init_test = __esm(() => {
13303
13890
  });
13304
13891
 
13305
13892
  // packages/cli/src/commands/setup.ts
13306
- import { existsSync as existsSync17, mkdirSync as mkdirSync12, readdirSync as readdirSync3, writeFileSync as writeFileSync9 } from "fs";
13893
+ import { existsSync as existsSync17, mkdirSync as mkdirSync13, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
13307
13894
  import { resolve as resolve25 } from "path";
13308
13895
  import { createPluginHost } from "@rig/core";
13309
13896
  import {
@@ -13358,12 +13945,12 @@ function runSetupInit(projectRoot) {
13358
13945
  const stateDir = resolveControlPlaneHostStateDir(projectRoot);
13359
13946
  const logsDir = resolveControlPlaneHostLogsDir(projectRoot);
13360
13947
  const artifactsDir = resolveControlPlaneArtifactsDir(projectRoot);
13361
- mkdirSync12(stateDir, { recursive: true });
13362
- mkdirSync12(logsDir, { recursive: true });
13363
- mkdirSync12(artifactsDir, { recursive: true });
13948
+ mkdirSync13(stateDir, { recursive: true });
13949
+ mkdirSync13(logsDir, { recursive: true });
13950
+ mkdirSync13(artifactsDir, { recursive: true });
13364
13951
  const failuresPath = resolve25(stateDir, "failed_approaches.md");
13365
13952
  if (!existsSync17(failuresPath)) {
13366
- writeFileSync9(failuresPath, `# Failed Approaches
13953
+ writeFileSync10(failuresPath, `# Failed Approaches
13367
13954
 
13368
13955
  `, "utf-8");
13369
13956
  }
@@ -15341,414 +15928,23 @@ async function runInitYes(ctx, args = ["--yes"]) {
15341
15928
  }
15342
15929
  var init_init2 = () => {};
15343
15930
 
15344
- // packages/cli/src/app-opentui/pi-pty-host.ts
15345
- import { fileURLToPath as fileURLToPath2 } from "url";
15346
- import { basename as basename4 } from "path";
15347
- import { RGBA, StyledText, TextAttributes } from "@opentui/core";
15348
- import { Terminal as XtermTerminal2 } from "@xterm/headless";
15349
- function clampCols2(cols) {
15350
- return Math.max(MIN_COLS2, Math.trunc(cols || 100));
15351
- }
15352
- function clampRows2(rows) {
15353
- return Math.max(MIN_ROWS2, Math.min(MAX_ROWS2, Math.trunc(rows || 35)));
15354
- }
15355
- function rgbaFromXtermColor(mode, value) {
15356
- if (mode === 1 || mode === 2 || mode === XTERM_COLOR_MODE_INDEXED_16 || mode === XTERM_COLOR_MODE_INDEXED_256) {
15357
- return RGBA.fromIndex(value);
15358
- }
15359
- if (mode === 3 || mode === XTERM_COLOR_MODE_RGB) {
15360
- return RGBA.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255);
15361
- }
15362
- return;
15363
- }
15364
- function textAttributesFromCell(cell) {
15365
- let attributes = TextAttributes.NONE;
15366
- if (cell.isBold())
15367
- attributes |= TextAttributes.BOLD;
15368
- if (cell.isDim())
15369
- attributes |= TextAttributes.DIM;
15370
- if (cell.isItalic())
15371
- attributes |= TextAttributes.ITALIC;
15372
- if (cell.isUnderline())
15373
- attributes |= TextAttributes.UNDERLINE;
15374
- if (cell.isBlink())
15375
- attributes |= TextAttributes.BLINK;
15376
- if (cell.isInverse())
15377
- attributes |= TextAttributes.INVERSE;
15378
- if (cell.isInvisible())
15379
- attributes |= TextAttributes.HIDDEN;
15380
- if (cell.isStrikethrough())
15381
- attributes |= TextAttributes.STRIKETHROUGH;
15382
- return attributes;
15383
- }
15384
- function sameRgba(a, b) {
15385
- if (!a && !b)
15386
- return true;
15387
- return Boolean(a && b && a.equals(b));
15388
- }
15389
- function sameStyle(a, b) {
15390
- return sameRgba(a.fg, b.fg) && sameRgba(a.bg, b.bg) && (a.attributes ?? 0) === (b.attributes ?? 0);
15391
- }
15392
- function styleFromCell(cell) {
15393
- return {
15394
- fg: rgbaFromXtermColor(cell.getFgColorMode(), cell.getFgColor()),
15395
- bg: rgbaFromXtermColor(cell.getBgColorMode(), cell.getBgColor()),
15396
- attributes: textAttributesFromCell(cell)
15397
- };
15398
- }
15399
- function lineToStyledText(line, cols) {
15400
- if (!line)
15401
- return new StyledText([{ __isChunk: true, text: "" }]);
15402
- const chunks = [];
15403
- let run = "";
15404
- let runStyle = null;
15405
- const flush = () => {
15406
- if (!run)
15407
- return;
15408
- chunks.push({
15409
- __isChunk: true,
15410
- text: run,
15411
- ...runStyle?.fg ? { fg: runStyle.fg } : {},
15412
- ...runStyle?.bg ? { bg: runStyle.bg } : {},
15413
- ...(runStyle?.attributes ?? 0) !== 0 ? { attributes: runStyle.attributes } : {}
15414
- });
15415
- run = "";
15416
- };
15417
- for (let index = 0;index < cols; index += 1) {
15418
- const cell = line.getCell(index);
15419
- if (!cell) {
15420
- if (runStyle !== null)
15421
- flush();
15422
- runStyle = null;
15423
- run += " ";
15424
- continue;
15425
- }
15426
- if (cell.getWidth() === 0)
15427
- continue;
15428
- const style = styleFromCell(cell);
15429
- if (!runStyle || !sameStyle(runStyle, style)) {
15430
- flush();
15431
- runStyle = style;
15432
- }
15433
- run += cell.getChars() || " ";
15434
- }
15435
- flush();
15436
- return new StyledText(chunks.length > 0 ? chunks : [{ __isChunk: true, text: "" }]);
15437
- }
15438
- function childCommandPrefix2() {
15439
- const execName = basename4(process.execPath).toLowerCase();
15440
- const currentEntry = process.argv[1];
15441
- if (execName === "bun" || execName === "bun.exe") {
15442
- return currentEntry ? [process.execPath, currentEntry, "__opentui-pi-host"] : [process.execPath, fallbackChildScriptPath];
15443
- }
15444
- return [process.execPath, "__opentui-pi-host"];
15445
- }
15446
- function withEnv2(base) {
15447
- const env = {};
15448
- for (const [key, value] of Object.entries(base ?? process.env)) {
15449
- if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP" || key === "NO_COLOR")
15450
- continue;
15451
- if (typeof value === "string")
15452
- env[key] = value;
15453
- }
15454
- return {
15455
- ...env,
15456
- TERM: "xterm-256color",
15457
- COLORTERM: "truecolor",
15458
- FORCE_COLOR: env.FORCE_COLOR ?? "1",
15459
- RIG_OPENTUI_PI_HOST: "1"
15460
- };
15461
- }
15462
- function getActivePiHost() {
15463
- return activeHost2 && !activeHost2.disposed ? activeHost2 : null;
15464
- }
15465
- async function startPiPtyHost(options) {
15466
- activeHost2?.dispose("replace");
15467
- const host = new PiPtyHost(options);
15468
- activeHost2 = host;
15469
- await host.start();
15470
- return host;
15471
- }
15472
- function stopActivePiHost(reason = "detach") {
15473
- activeHost2?.dispose(reason);
15474
- activeHost2 = null;
15475
- }
15476
-
15477
- class PiPtyHost {
15478
- runId;
15479
- projectRoot;
15480
- onSnapshot;
15481
- onExit;
15482
- onError;
15483
- terminal;
15484
- disposables = [];
15485
- decoder = new TextDecoder("utf-8");
15486
- proc = null;
15487
- pty = null;
15488
- status = "starting";
15489
- cols;
15490
- rows;
15491
- message = "starting bundled Pi";
15492
- lastResizeError = null;
15493
- exitCode;
15494
- signal;
15495
- notifyTimer = null;
15496
- lastStreamKey = "";
15497
- _disposed = false;
15498
- constructor(options) {
15499
- this.runId = options.runId;
15500
- this.projectRoot = options.projectRoot;
15501
- this.cols = clampCols2(options.cols);
15502
- this.rows = clampRows2(options.rows);
15503
- this.onSnapshot = options.onSnapshot;
15504
- this.onExit = options.onExit;
15505
- this.onError = options.onError;
15506
- this.terminal = new XtermTerminal2({
15507
- allowProposedApi: true,
15508
- cols: this.cols,
15509
- rows: this.rows,
15510
- scrollback: 1000
15511
- });
15512
- this.registerTerminalResponders();
15513
- this.disposables.push(this.terminal.onWriteParsed(() => {
15514
- if (this._disposed)
15515
- return;
15516
- const snapshot = this.createSnapshot();
15517
- const key = snapshot.lines.join(`
15518
- `);
15519
- if (key === this.lastStreamKey)
15520
- return;
15521
- this.lastStreamKey = key;
15522
- this.onSnapshot?.(snapshot);
15523
- }));
15524
- }
15525
- get disposed() {
15526
- return this._disposed;
15527
- }
15528
- get snapshot() {
15529
- return this.createSnapshot();
15530
- }
15531
- async start() {
15532
- if (this._disposed)
15533
- throw new Error("Pi PTY host is disposed.");
15534
- if (typeof Bun.Terminal !== "function") {
15535
- throw new Error("Bun native PTY is unavailable in this runtime. Pi host requires Bun.Terminal.");
15536
- }
15537
- const spawnOptions = {
15538
- cwd: this.projectRoot,
15539
- env: withEnv2(process.env),
15540
- terminal: {
15541
- cols: this.cols,
15542
- rows: this.rows,
15543
- name: "xterm-256color",
15544
- data: (_terminal, data) => this.handlePtyData(data)
15545
- }
15546
- };
15547
- const proc = Bun.spawn([
15548
- ...childCommandPrefix2(),
15549
- "--run-id",
15550
- this.runId,
15551
- "--project-root",
15552
- this.projectRoot
15553
- ], spawnOptions);
15554
- if (!proc.terminal)
15555
- throw new Error("Bun did not attach a terminal to the bundled Pi child process.");
15556
- this.proc = proc;
15557
- this.pty = proc.terminal;
15558
- this.status = "running";
15559
- this.message = "bundled Pi running inside this app";
15560
- this.emitSnapshotSoon(0);
15561
- proc.exited.then((exitCode) => {
15562
- if (this._disposed)
15563
- return;
15564
- this.status = exitCode === 0 ? "exited" : "failed";
15565
- this.exitCode = exitCode;
15566
- this.signal = null;
15567
- this.message = exitCode === 0 ? "bundled Pi exited" : `bundled Pi exited with code ${exitCode}`;
15568
- const snapshot = this.createSnapshot();
15569
- this.onSnapshot?.(snapshot);
15570
- this.onExit?.(snapshot);
15571
- if (activeHost2 === this)
15572
- activeHost2 = null;
15573
- this.dispose("exit", { kill: false, notify: false });
15574
- }).catch((error) => {
15575
- if (this._disposed)
15576
- return;
15577
- this.status = "failed";
15578
- this.message = error instanceof Error ? error.message : String(error);
15579
- const snapshot = this.createSnapshot();
15580
- this.onSnapshot?.(snapshot);
15581
- this.onError?.(error, snapshot);
15582
- if (activeHost2 === this)
15583
- activeHost2 = null;
15584
- this.dispose("error", { kill: false, notify: false });
15585
- });
15586
- }
15587
- write(data) {
15588
- if (this._disposed || !this.pty)
15589
- return;
15590
- try {
15591
- this.pty.write(data);
15592
- } catch (error) {
15593
- this.status = "failed";
15594
- this.message = error instanceof Error ? error.message : String(error);
15595
- const snapshot = this.createSnapshot();
15596
- this.onSnapshot?.(snapshot);
15597
- this.onError?.(error, snapshot);
15598
- }
15599
- }
15600
- resize(cols, rows) {
15601
- const nextCols = clampCols2(cols);
15602
- const nextRows = clampRows2(rows);
15603
- if (nextCols === this.cols && nextRows === this.rows)
15604
- return;
15605
- this.cols = nextCols;
15606
- this.rows = nextRows;
15607
- this.terminal.resize(nextCols, nextRows);
15608
- try {
15609
- this.pty?.resize(nextCols, nextRows);
15610
- this.lastResizeError = null;
15611
- } catch (error) {
15612
- this.lastResizeError = error instanceof Error ? error.message : String(error);
15613
- }
15614
- this.emitSnapshotSoon(0);
15615
- }
15616
- detach() {
15617
- this.dispose("detach");
15618
- return this.createSnapshot("detached from bundled Pi");
15619
- }
15620
- dispose(reason = "dispose", options = {}) {
15621
- if (this._disposed)
15622
- return;
15623
- this._disposed = true;
15624
- if (this.notifyTimer)
15625
- clearTimeout(this.notifyTimer);
15626
- this.notifyTimer = null;
15627
- for (const disposable of this.disposables.splice(0)) {
15628
- try {
15629
- disposable.dispose();
15630
- } catch {}
15631
- }
15632
- if (options.kill !== false) {
15633
- try {
15634
- this.proc?.kill("SIGTERM");
15635
- } catch {}
15636
- }
15637
- try {
15638
- this.pty?.close();
15639
- } catch {}
15640
- try {
15641
- this.terminal.dispose();
15642
- } catch {}
15643
- if (activeHost2 === this)
15644
- activeHost2 = null;
15645
- if (options.notify) {
15646
- this.message = reason;
15647
- this.onSnapshot?.(this.createSnapshot(reason));
15648
- }
15649
- }
15650
- handlePtyData(data) {
15651
- if (this._disposed)
15652
- return;
15653
- const text2 = this.decoder.decode(data, { stream: true });
15654
- this.respondToRawTerminalQueries(text2);
15655
- this.terminal.write(data);
15656
- }
15657
- emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS2) {
15658
- if (this._disposed || this.notifyTimer)
15659
- return;
15660
- this.notifyTimer = setTimeout(() => {
15661
- this.notifyTimer = null;
15662
- if (this._disposed)
15663
- return;
15664
- this.onSnapshot?.(this.createSnapshot());
15665
- }, delayMs);
15666
- }
15667
- createSnapshot(message2 = this.message) {
15668
- const buffer = this.terminal.buffer.active;
15669
- const end = buffer.length;
15670
- const start = Math.max(0, end - MAX_SNAPSHOT_LINES2);
15671
- const styledStart = Math.max(start, end - this.rows - STYLED_SNAPSHOT_MARGIN);
15672
- const lines = [];
15673
- const styledLines = [];
15674
- for (let row = start;row < end; row += 1) {
15675
- const line = buffer.getLine(row);
15676
- lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
15677
- if (row >= styledStart)
15678
- styledLines[lines.length - 1] = lineToStyledText(line, this.cols);
15679
- }
15680
- while (lines.length < this.rows) {
15681
- lines.push("");
15682
- styledLines[lines.length - 1] = new StyledText([{ __isChunk: true, text: "" }]);
15683
- }
15684
- const resolvedMessage = this.lastResizeError ? `resize degraded: ${this.lastResizeError}` : message2;
15685
- return {
15686
- runId: this.runId,
15687
- status: this.status,
15688
- cols: this.cols,
15689
- rows: this.rows,
15690
- lines,
15691
- styledLines,
15692
- message: resolvedMessage,
15693
- ...this.lastResizeError ? { resizeError: this.lastResizeError } : {},
15694
- ...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
15695
- ...this.signal !== undefined ? { signal: this.signal } : {}
15696
- };
15697
- }
15698
- registerTerminalResponders() {
15699
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "c" }, (params) => {
15700
- if (params.length === 0 || params[0] === 0)
15701
- this.write("\x1B[?62;22c");
15702
- return false;
15703
- }));
15704
- this.disposables.push(this.terminal.parser.registerCsiHandler({ prefix: ">", final: "c" }, (params) => {
15705
- if (params.length === 0 || params[0] === 0)
15706
- this.write("\x1B[>0;0;0c");
15707
- return false;
15708
- }));
15709
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
15710
- if (params[0] === 5) {
15711
- this.write("\x1B[0n");
15712
- } else if (params[0] === 6) {
15713
- const row = this.terminal.buffer.active.cursorY + 1;
15714
- const col = this.terminal.buffer.active.cursorX + 1;
15715
- this.write(`\x1B[${row};${col}R`);
15716
- }
15717
- return false;
15718
- }));
15719
- }
15720
- respondToRawTerminalQueries(text2) {
15721
- if (!text2)
15722
- return;
15723
- const decrqm = /\x1b\[\?(\d+)\$p/g;
15724
- let match;
15725
- while ((match = decrqm.exec(text2)) !== null) {
15726
- this.write(`\x1B[?${match[1]};2$y`);
15727
- }
15728
- }
15729
- }
15730
- var MIN_COLS2 = 40, MIN_ROWS2 = 12, MAX_ROWS2 = 300, MAX_SNAPSHOT_LINES2 = 360, STYLED_SNAPSHOT_MARGIN = 6, SNAPSHOT_DELAY_MS2 = 120, fallbackChildScriptPath, activeHost2 = null, XTERM_COLOR_MODE_INDEXED_16 = 16777216, XTERM_COLOR_MODE_INDEXED_256 = 33554432, XTERM_COLOR_MODE_RGB = 50331648;
15731
- var init_pi_pty_host = __esm(() => {
15732
- fallbackChildScriptPath = fileURLToPath2(new URL("./pi-host-child.ts", import.meta.url));
15733
- });
15734
-
15735
- // packages/cli/src/app-opentui/adapters/inspect.ts
15736
- var exports_inspect = {};
15737
- __export(exports_inspect, {
15738
- loadInspectRuns: () => loadInspectRuns,
15739
- loadInspect: () => loadInspect,
15740
- INSPECT_VIEWS: () => INSPECT_VIEWS
15741
- });
15742
- import { execFile } from "child_process";
15743
- import { existsSync as existsSync21, readFileSync as readFileSync14, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
15744
- import { resolve as resolve28 } from "path";
15745
- import { promisify } from "util";
15746
- import { resolveTaskArtifactDirs as resolveTaskArtifactDirs3 } from "@rig/runtime/control-plane/authority-files";
15747
- import { readTaskArtifactPreview as readTaskArtifactPreview2 } from "@rig/runtime/control-plane/native/workspace-ops";
15748
- import { resolveHarnessPaths as resolveHarnessPaths3, resolveMonorepoRoot as resolveMonorepoRoot3, runCapture as runCapture4 } from "@rig/runtime/control-plane/native/utils";
15749
- function isFailureEntry(entry) {
15750
- const status = `${entry.status ?? entry.level ?? entry.type ?? ""}`.toLowerCase();
15751
- return status.includes("fail") || status.includes("error");
15931
+ // packages/cli/src/app-opentui/adapters/inspect.ts
15932
+ var exports_inspect = {};
15933
+ __export(exports_inspect, {
15934
+ loadInspectRuns: () => loadInspectRuns,
15935
+ loadInspect: () => loadInspect,
15936
+ INSPECT_VIEWS: () => INSPECT_VIEWS
15937
+ });
15938
+ import { execFile } from "child_process";
15939
+ import { existsSync as existsSync21, readFileSync as readFileSync14, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
15940
+ import { resolve as resolve28 } from "path";
15941
+ import { promisify } from "util";
15942
+ import { resolveTaskArtifactDirs as resolveTaskArtifactDirs3 } from "@rig/runtime/control-plane/authority-files";
15943
+ import { readTaskArtifactPreview as readTaskArtifactPreview2 } from "@rig/runtime/control-plane/native/workspace-ops";
15944
+ import { resolveHarnessPaths as resolveHarnessPaths3, resolveMonorepoRoot as resolveMonorepoRoot3, runCapture as runCapture4 } from "@rig/runtime/control-plane/native/utils";
15945
+ function isFailureEntry(entry) {
15946
+ const status = `${entry.status ?? entry.level ?? entry.type ?? ""}`.toLowerCase();
15947
+ return status.includes("fail") || status.includes("error");
15752
15948
  }
15753
15949
  async function gitDiff(projectRoot) {
15754
15950
  try {
@@ -15954,14 +16150,6 @@ function recordStep(ctx, runId, label, emitLabel) {
15954
16150
  });
15955
16151
  emitProgress(ctx, emitLabel, label);
15956
16152
  }
15957
- function settleSteps(ctx, runId) {
15958
- const now = Date.now();
15959
- const previous = currentAttachState(ctx, runId);
15960
- if (!previous?.steps)
15961
- return;
15962
- const steps = previous.steps.map((step) => step.status === "running" ? { ...step, status: "done", endedAtMs: step.endedAtMs ?? now } : step);
15963
- patchData(ctx, { piAttach: { ...previous, steps } });
15964
- }
15965
16153
  async function preparePiAttachHandoff(ctx, runId) {
15966
16154
  const cleanRunId = runId.trim();
15967
16155
  const label = `Preparing Pi ${cleanRunId.slice(0, 8)}`;
@@ -16009,85 +16197,41 @@ async function attachRunWithBundledPi(ctx, runId) {
16009
16197
  throw error;
16010
16198
  }
16011
16199
  emitStarted(ctx, label, { piAttach: { runId: cleanRunId, status: "entering-pi" } });
16012
- patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "starting bundled Pi", steps: [] } });
16200
+ patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "handing the terminal to worker Pi\u2026" } });
16201
+ const projectRoot = projectRootOf(ctx);
16202
+ const outputMode = ctx.rig?.outputMode ?? "text";
16203
+ await releaseRendererForExternalTui(ctx);
16204
+ let outcome = null;
16205
+ let attachError = null;
16013
16206
  try {
16014
- recordStep(ctx, cleanRunId, "connecting to server and fetching run transcript", label);
16015
- await preparePiAttachHandoff(ctx, cleanRunId).catch((error) => {
16016
- patchData(ctx, { lastRefreshError: normalizeAppError(error).message });
16017
- return null;
16018
- });
16019
- recordStep(ctx, cleanRunId, "spawning Bun PTY host for bundled Pi", label);
16020
- const projectRoot = projectRootOf(ctx);
16021
- const cols = typeof process.stdout.columns === "number" ? process.stdout.columns : 100;
16022
- const rows = Math.max(1, (typeof process.stdout.rows === "number" ? process.stdout.rows : 35) - 1);
16023
- const patchTerminal = (snapshot) => {
16024
- const previousSteps = currentAttachState(ctx, cleanRunId)?.steps;
16025
- const failed = snapshot.status === "failed";
16026
- const reason = failed ? snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}` : undefined;
16027
- patchData(ctx, {
16028
- piTerminal: snapshot,
16029
- piAttach: {
16030
- runId: cleanRunId,
16031
- status: failed ? "failed" : snapshot.status === "exited" ? "returned" : "entering-pi",
16032
- message: snapshot.message ?? "bundled Pi running",
16033
- result: { runId: cleanRunId, status: snapshot.status, exitCode: snapshot.exitCode },
16034
- ...previousSteps ? { steps: previousSteps } : {},
16035
- ...reason ? { failureReason: reason } : {}
16036
- }
16037
- });
16038
- };
16039
- recordStep(ctx, cleanRunId, "starting bundled Pi", label);
16040
- await startPiPtyHost({
16041
- runId: cleanRunId,
16042
- projectRoot,
16043
- cols,
16044
- rows,
16045
- onSnapshot: (snapshot) => {
16046
- if (snapshot.status === "running" && snapshot.lines.some((entry) => entry.trim().length > 0)) {
16047
- settleSteps(ctx, cleanRunId);
16048
- }
16049
- patchTerminal(snapshot);
16050
- },
16051
- onExit: (snapshot) => {
16052
- patchTerminal(snapshot);
16053
- if (snapshot.status === "failed") {
16054
- Promise.resolve().then(() => (init_inspect2(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
16055
- emitFailed(ctx, label, new Error(snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}`), { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
16056
- return;
16057
- }
16058
- emitCompleted(ctx, label, { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
16059
- ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
16060
- refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
16061
- },
16062
- onError: (error, snapshot) => {
16063
- patchTerminal(snapshot);
16064
- emitFailed(ctx, label, error, { runId: cleanRunId });
16065
- }
16066
- });
16067
- const record = {
16068
- runId: cleanRunId,
16069
- status: "running",
16070
- rendered: "bundled Pi embedded via Bun.Terminal + @xterm/headless"
16071
- };
16072
- emitProgress(ctx, label, "bundled Pi is running", { piAttach: { runId: cleanRunId, status: "entering-pi", message: "bundled Pi running", result: record } });
16073
- return record;
16207
+ const { attachRunBundledPiFrontend: attachRunBundledPiFrontend2 } = await Promise.resolve().then(() => (init__pi_frontend(), exports__pi_frontend));
16208
+ outcome = await attachRunBundledPiFrontend2({ projectRoot, outputMode }, { runId: cleanRunId, returnOnQuit: true });
16074
16209
  } catch (error) {
16075
- const reason = error instanceof Error ? error.message : String(error);
16210
+ attachError = error;
16211
+ } finally {
16212
+ await resumeRendererAfterExternalTui(ctx);
16213
+ }
16214
+ if (attachError) {
16215
+ const reason = normalizeAppError(attachError).message;
16076
16216
  patchData(ctx, {
16077
- piAttach: {
16078
- runId: cleanRunId,
16079
- status: "failed",
16080
- message: reason,
16081
- failureReason: reason,
16082
- ...currentAttachState(ctx, cleanRunId)?.steps ? { steps: currentAttachState(ctx, cleanRunId).steps } : {}
16083
- }
16217
+ piTerminal: undefined,
16218
+ piAttach: { runId: cleanRunId, status: "failed", message: reason, failureReason: reason }
16084
16219
  });
16085
- emitFailed(ctx, label, error, { runId: cleanRunId });
16086
- throw error;
16220
+ Promise.resolve().then(() => (init_inspect2(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
16221
+ emitFailed(ctx, label, attachError, { runId: cleanRunId });
16222
+ throw attachError;
16087
16223
  }
16224
+ const detached = outcome?.detached === true;
16225
+ patchData(ctx, {
16226
+ piTerminal: undefined,
16227
+ piAttach: { runId: cleanRunId, status: "returned", message: detached ? "detached from worker Pi" : "returned from worker Pi" }
16228
+ });
16229
+ emitCompleted(ctx, label, { runId: cleanRunId, result: { runId: cleanRunId, detached } });
16230
+ ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
16231
+ refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
16232
+ return { runId: cleanRunId, status: "returned", detached };
16088
16233
  }
16089
16234
  var init_pi_attach = __esm(() => {
16090
- init_pi_pty_host();
16091
16235
  init_fleet();
16092
16236
  });
16093
16237
 
@@ -16684,6 +16828,7 @@ var LOG_PAGE = 100, TIMELINE_PAGE = 200;
16684
16828
  var init_run_detail = () => {};
16685
16829
 
16686
16830
  // packages/cli/src/app-opentui/adapters/server.ts
16831
+ import { spawnSync as spawnSync5 } from "child_process";
16687
16832
  function withTimeout(promise, ms, label) {
16688
16833
  return new Promise((resolve29, reject) => {
16689
16834
  const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
@@ -16720,11 +16865,128 @@ function startLocalRequested(ctx) {
16720
16865
  const action = intent.action;
16721
16866
  return action?.payload?.startLocal === true;
16722
16867
  }
16868
+ async function serverAliases(projectRoot) {
16869
+ const { readGlobalConnections: readGlobalConnections2, readRepoConnection: readRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16870
+ const selected = readRepoConnection2(projectRoot)?.selected ?? "local";
16871
+ const global = readGlobalConnections2();
16872
+ const aliases = [{ alias: "local", kind: "local", selected: selected === "local" }];
16873
+ for (const [alias, connection] of Object.entries(global.connections)) {
16874
+ aliases.push({
16875
+ alias,
16876
+ kind: connection.kind,
16877
+ ...connection.kind === "remote" ? { baseUrl: connection.baseUrl } : {},
16878
+ selected: alias === selected
16879
+ });
16880
+ }
16881
+ return aliases;
16882
+ }
16883
+ function firstString2(parts, offset = 0) {
16884
+ return parts.slice(offset).find((part) => part.trim() && !part.startsWith("-"))?.trim();
16885
+ }
16886
+ function normalizeUrl(value) {
16887
+ const trimmed = value?.trim().replace(/\/+$/, "");
16888
+ if (!trimmed)
16889
+ return;
16890
+ return /^https?:\/\//i.test(trimmed) ? trimmed : undefined;
16891
+ }
16892
+ async function useLocalServerFromApp(ctx) {
16893
+ const label = "Selecting local server";
16894
+ emitStarted(ctx, label);
16895
+ try {
16896
+ const projectRoot = projectRootOf(ctx);
16897
+ const { readRepoConnection: readRepoConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16898
+ const previous = readRepoConnection2(projectRoot);
16899
+ writeRepoConnection2(projectRoot, {
16900
+ selected: "local",
16901
+ ...previous?.project ? { project: previous.project } : {},
16902
+ linkedAt: new Date().toISOString()
16903
+ });
16904
+ emitCompleted(ctx, label, { selected: "local" });
16905
+ return refreshServerStatus(ctx);
16906
+ } catch (error) {
16907
+ emitFailed(ctx, label, error);
16908
+ throw error;
16909
+ }
16910
+ }
16911
+ async function useRemoteServerFromApp(ctx, alias) {
16912
+ const label = alias ? `Selecting remote ${alias}` : "Selecting remote server";
16913
+ emitStarted(ctx, label);
16914
+ try {
16915
+ const cleanAlias = alias?.trim();
16916
+ if (!cleanAlias)
16917
+ throw new Error("Enter a saved remote alias, or add one with: add remote \u2192 <alias> <https-url>.");
16918
+ const projectRoot = projectRootOf(ctx);
16919
+ const { readGlobalConnections: readGlobalConnections2, readRepoConnection: readRepoConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16920
+ const connection = readGlobalConnections2().connections[cleanAlias];
16921
+ if (!connection)
16922
+ throw new Error(`Remote alias ${cleanAlias} is not saved yet. Use add remote first.`);
16923
+ if (connection.kind !== "remote")
16924
+ throw new Error(`${cleanAlias} is not a remote server alias.`);
16925
+ const previous = readRepoConnection2(projectRoot);
16926
+ writeRepoConnection2(projectRoot, {
16927
+ selected: cleanAlias,
16928
+ ...previous?.project ? { project: previous.project } : {},
16929
+ linkedAt: new Date().toISOString()
16930
+ });
16931
+ emitCompleted(ctx, label, { selected: cleanAlias, baseUrl: connection.baseUrl });
16932
+ return refreshServerStatus(ctx);
16933
+ } catch (error) {
16934
+ emitFailed(ctx, label, error);
16935
+ throw error;
16936
+ }
16937
+ }
16938
+ async function addRemoteServerFromApp(ctx, args) {
16939
+ const label = "Adding remote server";
16940
+ emitStarted(ctx, label);
16941
+ try {
16942
+ const alias = firstString2(args, 2);
16943
+ const url = normalizeUrl(firstString2(args, alias ? args.indexOf(alias) + 1 : 3));
16944
+ if (!alias || !url)
16945
+ throw new Error("Add remote expects: <alias> <https-url>. Example: prod https://rig.example.com");
16946
+ const projectRoot = projectRootOf(ctx);
16947
+ const { readRepoConnection: readRepoConnection2, upsertGlobalConnection: upsertGlobalConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16948
+ upsertGlobalConnection2(alias, { kind: "remote", baseUrl: url });
16949
+ const previous = readRepoConnection2(projectRoot);
16950
+ writeRepoConnection2(projectRoot, {
16951
+ selected: alias,
16952
+ ...previous?.project ? { project: previous.project } : {},
16953
+ linkedAt: new Date().toISOString()
16954
+ });
16955
+ emitCompleted(ctx, label, { alias, baseUrl: url });
16956
+ return refreshServerStatus(ctx);
16957
+ } catch (error) {
16958
+ emitFailed(ctx, label, error);
16959
+ throw error;
16960
+ }
16961
+ }
16962
+ async function importGitHubAuthFromGhApp(ctx) {
16963
+ const label = "Importing GitHub auth";
16964
+ emitStarted(ctx, label);
16965
+ try {
16966
+ const projectRoot = projectRootOf(ctx);
16967
+ const token = spawnSync5("gh", ["auth", "token"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 1e4 }).stdout.trim();
16968
+ if (!token)
16969
+ throw new Error("Could not read GitHub token from gh. Sign in with gh auth login, then retry import auth.");
16970
+ const runtime = await runtimeOf(ctx);
16971
+ const { postGitHubTokenViaServer: postGitHubTokenViaServer2, setGitHubBearerTokenForCurrentProcess: setGitHubBearerTokenForCurrentProcess2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
16972
+ const { readRepoConnection: readRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16973
+ const selectedRepo = readRepoConnection2(projectRoot)?.project;
16974
+ const payload = await postGitHubTokenViaServer2(runtime, token, selectedRepo ? { selectedRepo } : {});
16975
+ const apiSessionToken = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.apiSessionToken : undefined;
16976
+ setGitHubBearerTokenForCurrentProcess2(typeof apiSessionToken === "string" ? apiSessionToken : token, projectRoot);
16977
+ emitCompleted(ctx, label, { selectedRepo: selectedRepo ?? null });
16978
+ return refreshServerStatus(ctx);
16979
+ } catch (error) {
16980
+ emitFailed(ctx, label, error);
16981
+ throw error;
16982
+ }
16983
+ }
16723
16984
  async function startLocalServerFromApp(ctx) {
16724
16985
  const label = "Starting local server";
16725
16986
  emitStarted(ctx, label);
16726
16987
  patchData(ctx, {
16727
16988
  lastIntent: undefined,
16989
+ remoteProjectLink: undefined,
16728
16990
  server: { label: "starting local server", reachable: false, error: "spawning local rig-server (detached)\u2026" }
16729
16991
  });
16730
16992
  try {
@@ -16749,32 +17011,61 @@ async function startLocalServerFromApp(ctx) {
16749
17011
  reachable: false,
16750
17012
  error: error instanceof Error ? error.message : String(error)
16751
17013
  };
16752
- patchData(ctx, { server: state, lastRefreshError: state.error });
17014
+ patchData(ctx, { server: state, remoteProjectLink: undefined, lastRefreshError: state.error });
16753
17015
  patchFooter(ctx, { server: "unavailable" });
16754
- emitCompleted(ctx, label, { server: state.label, reachable: false, error: state.error });
17016
+ emitCompleted(ctx, label, { serverLabel: state.label, reachable: false, error: state.error });
16755
17017
  return state;
16756
17018
  }
16757
17019
  }
17020
+ async function repairRemoteProjectRootLinkFromApp(ctx) {
17021
+ const label = "Repairing remote project link";
17022
+ emitStarted(ctx, label);
17023
+ try {
17024
+ const projectRoot = projectRootOf(ctx);
17025
+ const { ensureRemoteProjectRootLink: ensureRemoteProjectRootLink2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
17026
+ emitProgress(ctx, label, "backfilling or preparing server-host checkout");
17027
+ const result = await ensureRemoteProjectRootLink2(projectRoot, { mode: "prepare-if-missing" });
17028
+ patchData(ctx, { remoteProjectLink: result, server: { label: result.alias ?? "remote", baseUrl: result.baseUrl, kind: "remote", reachable: result.status !== "not_remote", remoteProjectLink: result } });
17029
+ patchFooter(ctx, { server: result.alias ?? "remote", message: `remote link ${result.status}` });
17030
+ if (!result.ok) {
17031
+ const error = new Error(result.message);
17032
+ error.hint = result.hint;
17033
+ patchData(ctx, { lastRefreshError: result.message });
17034
+ throw error;
17035
+ }
17036
+ emitCompleted(ctx, label, { repoSlug: result.repoSlug, serverProjectRoot: result.serverProjectRoot, status: result.status });
17037
+ return result;
17038
+ } catch (error) {
17039
+ const message2 = error instanceof Error ? error.message : String(error);
17040
+ const existing = ctx.getState().data?.remoteProjectLink;
17041
+ const remoteProjectLink = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : { ok: false, status: "error", message: message2 };
17042
+ patchData(ctx, { lastRefreshError: message2, remoteProjectLink });
17043
+ emitCompleted(ctx, label, { ok: false, error: message2, remoteProjectLink });
17044
+ throw error;
17045
+ }
17046
+ }
16758
17047
  async function refreshServerStatus(ctx) {
16759
17048
  if (startLocalRequested(ctx)) {
16760
17049
  return startLocalServerFromApp(ctx);
16761
17050
  }
16762
17051
  const label = "Checking server";
16763
17052
  emitStarted(ctx, label);
16764
- patchData(ctx, { server: { label: "checking selected server", reachable: false, error: "checking selected server\u2026" } });
17053
+ patchData(ctx, { server: { label: "checking selected server", reachable: false, error: "checking selected server\u2026" }, remoteProjectLink: undefined });
16765
17054
  const started = Date.now();
16766
17055
  try {
16767
- const { ensureServerForCli: ensureServerForCli2, requestServerJson: requestServerJson2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
17056
+ const { ensureRemoteProjectRootLink: ensureRemoteProjectRootLink2, ensureServerForCli: ensureServerForCli2, requestServerJson: requestServerJson2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
16768
17057
  const projectRoot = projectRootOf(ctx);
17058
+ const aliases = await serverAliases(projectRoot);
16769
17059
  emitProgress(ctx, label, "resolving selected server");
16770
17060
  const [connectionLabel, server] = await Promise.all([
16771
17061
  resolveServerConnectionLabel2(projectRoot),
16772
17062
  withTimeout(ensureServerForCli2(projectRoot), 5000, "server connection")
16773
17063
  ]);
16774
17064
  emitProgress(ctx, label, "requesting server status", { baseUrl: server.baseUrl, kind: server.connectionKind });
16775
- const [status, auth] = await Promise.all([
17065
+ const [status, auth, remoteProjectLink] = await Promise.all([
16776
17066
  withRetry("server status", () => withTimeout(requestServerJson2({ projectRoot }, "/api/server/status"), SERVER_PROBE_TIMEOUT_MS, "server status")).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) })),
16777
- withRetry("github auth status", () => withTimeout(requestServerJson2({ projectRoot }, "/api/github/auth/status"), SERVER_PROBE_TIMEOUT_MS, "github auth status")).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }))
17067
+ withRetry("github auth status", () => withTimeout(requestServerJson2({ projectRoot }, "/api/github/auth/status"), SERVER_PROBE_TIMEOUT_MS, "github auth status")).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) })),
17068
+ server.connectionKind === "remote" ? withTimeout(ensureRemoteProjectRootLink2(projectRoot, { mode: "backfill-only", authToken: server.authToken }), SERVER_PROBE_TIMEOUT_MS, "remote project link").catch((error) => ({ ok: false, status: "error", message: error instanceof Error ? error.message : String(error) })) : Promise.resolve(null)
16778
17069
  ]);
16779
17070
  const state = {
16780
17071
  label: connectionLabel,
@@ -16783,22 +17074,25 @@ async function refreshServerStatus(ctx) {
16783
17074
  reachable: true,
16784
17075
  latencyMs: Date.now() - started,
16785
17076
  status: status && typeof status === "object" && !Array.isArray(status) ? status : {},
16786
- auth: auth && typeof auth === "object" && !Array.isArray(auth) ? auth : {}
17077
+ auth: auth && typeof auth === "object" && !Array.isArray(auth) ? auth : {},
17078
+ aliases,
17079
+ ...remoteProjectLink && typeof remoteProjectLink === "object" && !Array.isArray(remoteProjectLink) ? { remoteProjectLink } : {}
16787
17080
  };
16788
- patchData(ctx, { server: state });
17081
+ patchData(ctx, { server: state, remoteProjectLink: state.remoteProjectLink });
16789
17082
  patchFooter(ctx, { server: connectionLabel, latency: `${state.latencyMs}ms` });
16790
- emitCompleted(ctx, label, { server: connectionLabel, latencyMs: state.latencyMs });
17083
+ emitCompleted(ctx, label, { serverLabel: connectionLabel, latencyMs: state.latencyMs });
16791
17084
  return state;
16792
17085
  } catch (error) {
16793
17086
  const state = {
16794
17087
  label: "server unavailable",
16795
17088
  reachable: false,
16796
17089
  latencyMs: Date.now() - started,
17090
+ aliases: await serverAliases(projectRootOf(ctx)).catch(() => []),
16797
17091
  error: error instanceof Error ? error.message : String(error)
16798
17092
  };
16799
- patchData(ctx, { server: state });
17093
+ patchData(ctx, { server: state, remoteProjectLink: undefined });
16800
17094
  patchFooter(ctx, { server: "unavailable" });
16801
- emitCompleted(ctx, label, { server: state.label, reachable: false, error: state.error });
17095
+ emitCompleted(ctx, label, { serverLabel: state.label, reachable: false, error: state.error });
16802
17096
  return state;
16803
17097
  }
16804
17098
  }
@@ -17378,7 +17672,7 @@ import {
17378
17672
  dim as otuiDim,
17379
17673
  fg as otuiFg,
17380
17674
  t,
17381
- TextAttributes as TextAttributes2
17675
+ TextAttributes
17382
17676
  } from "@opentui/core";
17383
17677
  function statusColor3(status) {
17384
17678
  switch (status) {
@@ -17483,7 +17777,7 @@ var init_theme2 = __esm(() => {
17483
17777
  });
17484
17778
 
17485
17779
  // packages/cli/src/app-opentui/drone.ts
17486
- import { RGBA as RGBA2, StyledText as StyledText2, TextAttributes as TextAttributes3 } from "@opentui/core";
17780
+ import { RGBA, StyledText, TextAttributes as TextAttributes2 } from "@opentui/core";
17487
17781
  function bladeForTick(tick, phase = 0) {
17488
17782
  return BLADE_FRAMES2[(Math.floor(tick / 3) + phase) % BLADE_FRAMES2.length];
17489
17783
  }
@@ -17492,12 +17786,12 @@ function eyeForTick(tick, phase = 0) {
17492
17786
  return pulse > 0.55 ? EYE_FRAMES2[1] : pulse > 0.1 ? EYE_FRAMES2[0] : pulse > -0.45 ? EYE_FRAMES2[2] : EYE_FRAMES2[3];
17493
17787
  }
17494
17788
  function chunk(text2, fg2, bold2 = false, dim = false) {
17495
- let attributes = TextAttributes3.NONE;
17789
+ let attributes = TextAttributes2.NONE;
17496
17790
  if (bold2)
17497
- attributes |= TextAttributes3.BOLD;
17791
+ attributes |= TextAttributes2.BOLD;
17498
17792
  if (dim)
17499
- attributes |= TextAttributes3.DIM;
17500
- return { __isChunk: true, text: text2, fg: fg2, ...attributes !== TextAttributes3.NONE ? { attributes } : {} };
17793
+ attributes |= TextAttributes2.DIM;
17794
+ return { __isChunk: true, text: text2, fg: fg2, ...attributes !== TextAttributes2.NONE ? { attributes } : {} };
17501
17795
  }
17502
17796
  function styledLine(text2, colorFor) {
17503
17797
  const chunks = [];
@@ -17518,7 +17812,7 @@ function styledLine(text2, colorFor) {
17518
17812
  run += char;
17519
17813
  }
17520
17814
  flush();
17521
- return new StyledText2(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
17815
+ return new StyledText(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
17522
17816
  }
17523
17817
  function droneColor(char) {
17524
17818
  if (char === "?" || char === "o" || char === "@" || char === "\u2022")
@@ -17610,13 +17904,13 @@ var init_drone = __esm(() => {
17610
17904
  BLADE_FRAMES2 = ["---", "\\\\\\", "|||", "///"];
17611
17905
  EYE_FRAMES2 = ["o", "@", "\u2022", "."];
17612
17906
  COLOR = {
17613
- body: RGBA2.fromHex(RIG_UI.lime),
17614
- mini: RGBA2.fromHex(RIG_UI.limeDim),
17615
- rotor: RGBA2.fromHex(RIG_UI.cyan),
17616
- path: RGBA2.fromHex(RIG_UI.cyan),
17617
- eye: RGBA2.fromHex(RIG_UI.ink),
17618
- dim: RGBA2.fromHex(RIG_UI.ink4),
17619
- ink: RGBA2.fromHex(RIG_UI.ink2)
17907
+ body: RGBA.fromHex(RIG_UI.lime),
17908
+ mini: RGBA.fromHex(RIG_UI.limeDim),
17909
+ rotor: RGBA.fromHex(RIG_UI.cyan),
17910
+ path: RGBA.fromHex(RIG_UI.cyan),
17911
+ eye: RGBA.fromHex(RIG_UI.ink),
17912
+ dim: RGBA.fromHex(RIG_UI.ink4),
17913
+ ink: RGBA.fromHex(RIG_UI.ink2)
17620
17914
  };
17621
17915
  });
17622
17916
 
@@ -17826,11 +18120,56 @@ var init_doctor3 = __esm(() => {
17826
18120
  DOCTOR_ACTIONS = [
17827
18121
  { detail: "run diagnostics now", item: { id: "run", label: "run", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "run diagnostics" } },
17828
18122
  { 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" } },
17829
- { 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" } },
18123
+ { detail: "repair project/server/auth link", item: { id: "repair", label: "repair", intent: { scene: "init", argv: ["init", "--repair", "--yes"], action: { kind: "init-start", label: "Repair project link" } }, message: "repair project link natively" } },
17830
18124
  { detail: "open tasks", item: { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks" } }
17831
18125
  ];
17832
18126
  });
17833
18127
 
18128
+ // packages/cli/src/app-opentui/remote-link.ts
18129
+ function remoteProjectLinkError(message2) {
18130
+ return /no server-host project root link|remote project(?:-root)? link|x-rig-project-root|serverProjectRoot/i.test(message2 ?? "");
18131
+ }
18132
+ function serverRecordForRemoteLink(state) {
18133
+ const server = state.data.server;
18134
+ return server && typeof server === "object" && !Array.isArray(server) ? server : null;
18135
+ }
18136
+ function remoteProjectLinkState(state) {
18137
+ const serverRecord = serverRecordForRemoteLink(state);
18138
+ if (!serverRecord || serverRecord.kind !== "remote")
18139
+ return null;
18140
+ const link = serverRecord.remoteProjectLink;
18141
+ if (link && typeof link === "object" && !Array.isArray(link))
18142
+ return link;
18143
+ const direct = state.data.remoteProjectLink;
18144
+ if (direct && typeof direct === "object" && !Array.isArray(direct))
18145
+ return direct;
18146
+ return null;
18147
+ }
18148
+ function shouldOfferRemoteLinkRepair(state, message2) {
18149
+ const link = remoteProjectLinkState(state);
18150
+ if (link) {
18151
+ if (link.ok === true)
18152
+ return false;
18153
+ const status = link.status ?? "";
18154
+ return REPAIRABLE_REMOTE_LINK_STATUSES.has(status);
18155
+ }
18156
+ const serverRecord = serverRecordForRemoteLink(state);
18157
+ if (serverRecord && serverRecord.kind !== "remote")
18158
+ return false;
18159
+ return remoteProjectLinkError(message2 ?? state.error?.message);
18160
+ }
18161
+ var REPAIRABLE_REMOTE_LINK_STATUSES;
18162
+ var init_remote_link = __esm(() => {
18163
+ REPAIRABLE_REMOTE_LINK_STATUSES = new Set([
18164
+ "auth_required",
18165
+ "project_not_registered",
18166
+ "no_server_checkout",
18167
+ "invalid_root",
18168
+ "needs_prepare",
18169
+ "error"
18170
+ ]);
18171
+ });
18172
+
17834
18173
  // packages/cli/src/app-opentui/fleet-stats.ts
17835
18174
  function normalizeStatus(status) {
17836
18175
  return status.replace(/_/g, "-").toLowerCase();
@@ -19746,13 +20085,18 @@ function overviewBody(state, family, snap, layout) {
19746
20085
  function familyRecoveryRows(state, family, errorMessage2) {
19747
20086
  const selected = Math.max(0, state.selection.index);
19748
20087
  const intro = errorMessage2 ? line(`\u2717 couldn't load ${family || "this family"} \u2014 ${errorMessage2}`, { fg: RIG_UI.red, bold: true }) : line(`No data for ${family || "this family"} yet.`, { fg: RIG_UI.ink2 });
19749
- return [
20088
+ const repairRemote = shouldOfferRemoteLinkRepair(state, errorMessage2);
20089
+ const rows = [
19750
20090
  intro,
19751
- blank(),
19752
- selectableDeckRow({ label: "retry", detail: `reload ${family || "the family"}`, index: 0, active: selected === 0 }, { id: "family-retry", label: "retry", intent: { scene: "family", argv: [family], action: { kind: "refresh", label: `Reload ${family}`, payload: { family } } }, message: "reload this family" }),
19753
- selectableDeckRow({ label: "cockpit", detail: "back to the project cockpit", index: 1, active: selected === 1 }, { id: "family-home", label: "cockpit", intent: { scene: "main", argv: [], action: { kind: "refresh", label: "Home" } }, message: "back to the cockpit" }),
19754
- selectableDeckRow({ label: "doctor", detail: "diagnose project/setup", index: 2, active: selected === 2 }, { id: "family-doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose setup" })
20091
+ blank()
19755
20092
  ];
20093
+ let index = 0;
20094
+ if (repairRemote) {
20095
+ rows.push(selectableDeckRow({ label: "repair link", detail: "backfill/prepare selected remote checkout link", index, active: selected === index }, { id: "family-repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "family", family } } }, message: "repair remote project link" }));
20096
+ index += 1;
20097
+ }
20098
+ rows.push(selectableDeckRow({ label: "retry", detail: `reload ${family || "the family"}`, index, active: selected === index }, { id: "family-retry", label: "retry", intent: { scene: "family", argv: [family], action: { kind: "refresh", label: `Reload ${family}`, payload: { family } } }, message: "reload this family" }), selectableDeckRow({ label: "cockpit", detail: "back to the project cockpit", index: index + 1, active: selected === index + 1 }, { id: "family-home", label: "cockpit", intent: { scene: "main", argv: [], action: { kind: "refresh", label: "Home" } }, message: "back to the cockpit" }), selectableDeckRow({ label: "doctor", detail: "diagnose project/setup", index: index + 2, active: selected === index + 2 }, { id: "family-doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose setup" }));
20099
+ return rows;
19756
20100
  }
19757
20101
  function renderFamilyScene(state, layout) {
19758
20102
  const family = typeof state.data.familyName === "string" ? state.data.familyName : "";
@@ -19761,9 +20105,16 @@ function renderFamilyScene(state, layout) {
19761
20105
  const loading = state.status === "loading" && !snap;
19762
20106
  const errorMessage2 = state.error?.message ?? lastRefreshError(state) ?? undefined;
19763
20107
  const probeFailed = snap ? snap.state.ran === false && Boolean(state.error) : Boolean(state.error);
20108
+ const selected = Math.max(0, state.selection.index);
19764
20109
  const body = [
19765
20110
  ...loading ? loadingRows(family || "family", state.tick) : snap ? [
19766
- ...probeFailed && errorMessage2 ? [...errorBanner(errorMessage2), blank()] : [],
20111
+ ...probeFailed && errorMessage2 ? [
20112
+ ...errorBanner(errorMessage2),
20113
+ ...shouldOfferRemoteLinkRepair(state, errorMessage2) ? [
20114
+ selectableDeckRow({ label: "repair link", detail: "backfill/prepare selected remote checkout link", index: 0, active: selected === 0 }, { id: "family-repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "family", family } } }, message: "repair remote project link" })
20115
+ ] : [],
20116
+ blank()
20117
+ ] : [],
19767
20118
  ...current.subId ? formView(state, family, snap, current).lines : overviewBody(state, family, snap, layout)
19768
20119
  ] : familyRecoveryRows(state, family, errorMessage2)
19769
20120
  ];
@@ -19797,6 +20148,7 @@ function renderFamilyScene(state, layout) {
19797
20148
  var init_family2 = __esm(() => {
19798
20149
  init_scene();
19799
20150
  init_selectable();
20151
+ init_remote_link();
19800
20152
  init_theme2();
19801
20153
  init_family_domains();
19802
20154
  init_kit();
@@ -20067,27 +20419,38 @@ function runRow(width, run, active, optimistic) {
20067
20419
  const item = runSelectableItem(run);
20068
20420
  return item ? withSelectable(row, item) : row;
20069
20421
  }
20070
- function recoveryRows(selected, reason) {
20422
+ function fleetRecoveryItems(state) {
20423
+ if (!shouldOfferRemoteLinkRepair(state))
20424
+ return BASE_FLEET_RECOVERY_ITEMS;
20425
+ return [BASE_FLEET_RECOVERY_ITEMS[0], FLEET_REPAIR_LINK_ITEM, ...BASE_FLEET_RECOVERY_ITEMS.slice(1)];
20426
+ }
20427
+ function fleetRecoveryDecks(state) {
20428
+ if (!shouldOfferRemoteLinkRepair(state))
20429
+ return BASE_RECOVERY_DECKS;
20430
+ return [BASE_RECOVERY_DECKS[0], { label: "repair link", detail: "backfill/prepare remote project-root link" }, ...BASE_RECOVERY_DECKS.slice(1)];
20431
+ }
20432
+ function recoveryRows(state, selected, reason) {
20433
+ const items = fleetRecoveryItems(state);
20071
20434
  return [
20072
20435
  ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
20073
20436
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
20074
- ...RECOVERY_DECKS.map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, FLEET_RECOVERY_ITEMS[index]))
20437
+ ...fleetRecoveryDecks(state).map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, items[index]))
20075
20438
  ];
20076
20439
  }
20077
- function emptyRows(query, total, selected) {
20440
+ function emptyRows(state, query, total, selected) {
20078
20441
  if (!query.trim() && total === 0)
20079
- return recoveryRows(selected, "No runs yet.");
20442
+ return recoveryRows(state, selected, "No runs yet.");
20080
20443
  if (query.trim() && total > 0) {
20081
20444
  return [
20082
- ...recoveryRows(selected, `No matching runs for ${JSON.stringify(query)} (${total} total).`)
20445
+ ...recoveryRows(state, selected, `No matching runs for ${JSON.stringify(query)} (${total} total).`)
20083
20446
  ];
20084
20447
  }
20085
- return recoveryRows(selected, "No runs are visible with this filter.");
20448
+ return recoveryRows(state, selected, "No runs are visible with this filter.");
20086
20449
  }
20087
20450
  function degradedRows(state, selected) {
20088
20451
  if (!state.error)
20089
20452
  return [];
20090
- return recoveryRows(selected, state.error.message);
20453
+ return recoveryRows(state, selected, state.error.message);
20091
20454
  }
20092
20455
  function renderFleetScene(state, layout) {
20093
20456
  const width = panelWidth4(layout);
@@ -20097,7 +20460,7 @@ function renderFleetScene(state, layout) {
20097
20460
  const runs = sortRuns(filterRunsForSearch(allRuns, query), spec);
20098
20461
  const selectedRun = typeof state.data.selectedRunId === "string" && runs.some((run) => run.runId === state.data.selectedRunId) ? state.data.selectedRunId : runs[0]?.runId;
20099
20462
  const selectedIndex = Math.max(0, runs.findIndex((run) => run.runId === selectedRun));
20100
- const recoveryIndex = Math.max(0, Math.min(3, state.selection.index));
20463
+ const recoveryIndex = Math.max(0, Math.min(fleetRecoveryItems(state).length - 1, state.selection.index));
20101
20464
  const optimistic = optimisticStatuses(state);
20102
20465
  const updatedAgo = updatedAgoLabel(state);
20103
20466
  const panelTop = 0;
@@ -20129,7 +20492,7 @@ function renderFleetScene(state, layout) {
20129
20492
  ...runs.map((run, index) => runRow(contentWidth, run, index === selectedIndex, optimistic.get(run.runId)))
20130
20493
  ] : state.error ? degradedRows(state, recoveryIndex) : loading ? loadingRows("runs", state.tick) : [
20131
20494
  ...query.trim() ? [line(searchSummary("runs", query, runs.length, allRuns.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
20132
- ...emptyRows(query, allRuns.length, recoveryIndex)
20495
+ ...emptyRows(state, query, allRuns.length, recoveryIndex)
20133
20496
  ]
20134
20497
  ];
20135
20498
  return makeSceneFrame({
@@ -20164,18 +20527,19 @@ function renderFleetScene(state, layout) {
20164
20527
  live: true
20165
20528
  });
20166
20529
  }
20167
- var FLEET_RECOVERY_ITEMS, COL2, GUTTER3 = " ", RUN_COLUMNS, RECOVERY_DECKS;
20530
+ var BASE_FLEET_RECOVERY_ITEMS, FLEET_REPAIR_LINK_ITEM, COL2, GUTTER3 = " ", RUN_COLUMNS, BASE_RECOVERY_DECKS;
20168
20531
  var init_fleet2 = __esm(() => {
20169
20532
  init_scene();
20170
20533
  init_selectable();
20171
20534
  init_fleet_stats();
20535
+ init_remote_link();
20172
20536
  init_theme2();
20173
- FLEET_RECOVERY_ITEMS = [
20537
+ BASE_FLEET_RECOVERY_ITEMS = [
20174
20538
  { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks and dispatch work" },
20175
- { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair project link" } }, message: "repair project/server/auth link" },
20176
20539
  { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
20177
20540
  { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose project" }
20178
20541
  ];
20542
+ FLEET_REPAIR_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: "fleet" } } }, message: "backfill or prepare remote project link" };
20179
20543
  COL2 = {
20180
20544
  glyph: 1,
20181
20545
  runId: 8,
@@ -20189,9 +20553,8 @@ var init_fleet2 = __esm(() => {
20189
20553
  { key: "title", label: "task/title", hint: "by task title" },
20190
20554
  { key: "age", label: "age", hint: "by recency" }
20191
20555
  ];
20192
- RECOVERY_DECKS = [
20556
+ BASE_RECOVERY_DECKS = [
20193
20557
  { label: "tasks", detail: "open tasks and dispatch work" },
20194
- { label: "repair", detail: "repair project/server/auth link" },
20195
20558
  { label: "server", detail: "open server controls" },
20196
20559
  { label: "doctor", detail: "diagnose project" }
20197
20560
  ];
@@ -20771,11 +21134,11 @@ function renderInitScene(state, layout) {
20771
21134
  const allDone = completed === SETUP_STEPS.length;
20772
21135
  const nextStep = SETUP_STEPS.find((step) => !step.done(signals));
20773
21136
  const selected = Math.max(0, state.selection.index);
20774
- const primary = nextStep ? selectableDeckRow({ label: "next", detail: `${nextStep.cta} \u2014 recommended`, active: selected === 0 }, { ...nextStep.item, id: "init-primary" }) : selectableDeckRow({ label: "verify", detail: "Setup complete \u2014 run doctor to confirm", active: selected === 0 }, { id: "init-primary", label: "verify", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify setup end-to-end" });
21137
+ const primary = nextStep ? selectableDeckRow({ label: "next", detail: `${nextStep.cta} \u2014 recommended`, active: selected === 0 }, { ...nextStep.item, id: "init-primary" }) : selectableDeckRow({ label: "verify", detail: "Init complete \u2014 run doctor to confirm", active: selected === 0 }, { id: "init-primary", label: "verify", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify setup end-to-end" });
20775
21138
  const secondaryRows = SECONDARY_ACTIONS.map(({ label, detail, item }, index) => selectableDeckRow({ label, detail, active: selected === index + 1 }, item));
20776
21139
  const progress = allDone ? "all set" : `${completed} of ${SETUP_STEPS.length} complete`;
20777
21140
  const panelLines = [
20778
- line(allDone ? "SETUP \xB7 ready to go" : "SETUP \xB7 let's get you running", { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink, bold: true }),
21141
+ line(allDone ? "INIT \xB7 ready to go" : "INIT \xB7 let's get you running", { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink, bold: true }),
20779
21142
  line(`progress ${progress}`, { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink2 }),
20780
21143
  blank(),
20781
21144
  line("CHECKLIST", { fg: RIG_UI.ink3, bold: true }),
@@ -20787,11 +21150,11 @@ function renderInitScene(state, layout) {
20787
21150
  line("OTHER PATHS", { fg: RIG_UI.ink3, bold: true }),
20788
21151
  ...secondaryRows,
20789
21152
  blank(),
20790
- line("enter/click runs the highlighted action \xB7 esc goes back \xB7 tab switches sections", { fg: RIG_UI.ink4 })
21153
+ line("enter/click runs the highlighted native action \xB7 esc goes back \xB7 tab switches sections", { fg: RIG_UI.ink4 })
20791
21154
  ];
20792
21155
  return makeSceneFrame({
20793
21156
  scene: "init",
20794
- title: "Setup",
21157
+ title: "Init",
20795
21158
  lines: [],
20796
21159
  panels: [{
20797
21160
  id: "init-setup",
@@ -20804,11 +21167,11 @@ function renderInitScene(state, layout) {
20804
21167
  opacity: 0.98,
20805
21168
  border: false,
20806
21169
  chrome: "ad-terminal",
20807
- headerText: "rig setup \xB7 guided onboarding",
21170
+ headerText: "rig init \xB7 guided onboarding",
20808
21171
  paddingX: 5,
20809
21172
  paddingY: 2
20810
21173
  }],
20811
- footer: { message: allDone ? "setup complete" : "setup in progress" },
21174
+ footer: { message: allDone ? "init complete" : "init in progress" },
20812
21175
  live: !allDone || state.status === "action"
20813
21176
  });
20814
21177
  }
@@ -20817,19 +21180,19 @@ var init_init3 = __esm(() => {
20817
21180
  init_scene();
20818
21181
  init_selectable();
20819
21182
  init_theme2();
20820
- STEP_CONFIG_RUN = { id: "step-config", label: "run setup wizard", intent: { scene: "command", argv: ["init"], action: { kind: "command-run", label: "Run setup wizard" } }, message: "full interactive setup wizard" };
21183
+ STEP_CONFIG_RUN = { id: "step-config", label: "run init", intent: { scene: "init", argv: ["init", "--yes"], action: { kind: "init-start", label: "Run init" } }, message: "initialize without embedded CLI prompts" };
20821
21184
  STEP_SERVER = { id: "step-server", label: "select server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Choose server" } }, message: "select local or remote server" };
20822
- STEP_AUTH = { id: "step-auth", label: "connect GitHub", intent: { scene: "command", argv: ["github", "auth", "import-gh"], action: { kind: "command-run", label: "Connect GitHub" } }, message: "import GitHub auth into the selected server" };
20823
- STEP_TASKS = { id: "step-tasks", label: "pick a task source", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Configure task source" } }, message: "configure or repair the task source" };
21185
+ STEP_AUTH = { id: "step-auth", label: "connect GitHub", intent: { scene: "server", argv: ["github", "auth", "status"], action: { kind: "refresh", label: "Open GitHub auth" } }, message: "open native server/auth controls" };
21186
+ STEP_TASKS = { id: "step-tasks", label: "repair task source", intent: { scene: "init", argv: ["init", "--repair", "--yes"], action: { kind: "init-start", label: "Repair task source" } }, message: "repair generated task-source config without embedded CLI prompts" };
20824
21187
  SETUP_STEPS = [
20825
- { id: "config", label: "Project config", detail: "rig.config + private state", done: (s) => s.configured, cta: "Run setup wizard", item: STEP_CONFIG_RUN },
21188
+ { id: "config", label: "Project config", detail: "rig.config + private state", done: (s) => s.configured, cta: "Run init", item: STEP_CONFIG_RUN },
20826
21189
  { id: "server", label: "Server target", detail: "local or remote, reachable", done: (s) => s.serverReachable, cta: "Choose a server", item: STEP_SERVER },
20827
21190
  { id: "auth", label: "GitHub auth", detail: "signed in on the selected server", done: (s) => s.authSignedIn, cta: "Connect GitHub", item: STEP_AUTH },
20828
21191
  { id: "tasks", label: "Task source", detail: "where work comes from", done: (s) => s.taskSourceReady, cta: "Pick a task source", item: STEP_TASKS }
20829
21192
  ];
20830
21193
  SECONDARY_ACTIONS = [
20831
- { label: "repair", detail: "reconfigure generated config and private state", item: { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Reconfigure project" } }, message: "verify or rewrite generated config" } },
20832
- { label: "demo", detail: "seed an offline demo task source to explore", item: { id: "demo", label: "demo", intent: { scene: "command", argv: ["init", "--demo", "--repair"], action: { kind: "command-run", label: "Seed demo project" } }, message: "offline demo project" } },
21194
+ { label: "repair", detail: "reconfigure generated config and private state", item: { id: "repair", label: "repair", intent: { scene: "init", argv: ["init", "--repair", "--yes"], action: { kind: "init-start", label: "Reconfigure project" } }, message: "verify or rewrite generated config natively" } },
21195
+ { label: "demo", detail: "seed an offline demo task source to explore", item: { id: "demo", label: "demo", intent: { scene: "init", argv: ["init", "--demo", "--repair", "--yes"], action: { kind: "init-start", label: "Seed demo project" } }, message: "offline demo project without embedded CLI prompts" } },
20833
21196
  { label: "doctor", detail: "diagnose what is still missing", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify setup" } }
20834
21197
  ];
20835
21198
  });
@@ -22128,6 +22491,30 @@ var init_run_detail2 = __esm(() => {
22128
22491
  });
22129
22492
 
22130
22493
  // packages/cli/src/app-opentui/scenes/server.ts
22494
+ function remoteAliasActions(server) {
22495
+ const aliases = (server?.aliases ?? []).filter((alias) => alias.kind === "remote");
22496
+ return aliases.map((alias) => ({
22497
+ detail: `${alias.selected ? "selected" : "select"} ${alias.baseUrl ?? "remote endpoint"}`,
22498
+ item: {
22499
+ id: `remote-alias:${alias.alias}`,
22500
+ label: alias.selected ? `\u2713 ${alias.alias}` : `use ${alias.alias}`,
22501
+ intent: { scene: "server", argv: ["server", "use", alias.alias], action: { kind: "server-use-remote", label: `Use ${alias.alias}`, payload: { alias: alias.alias } } },
22502
+ message: `select ${alias.alias}`
22503
+ }
22504
+ }));
22505
+ }
22506
+ function serverActions(state) {
22507
+ const server = serverState(state);
22508
+ const remoteRows = remoteAliasActions(server);
22509
+ let actions = remoteRows.length > 0 ? [...SERVER_ACTIONS.slice(0, 3), ...remoteRows, ...SERVER_ACTIONS.slice(3)] : [...SERVER_ACTIONS];
22510
+ if (!shouldOfferRemoteLinkRepair(state))
22511
+ return actions;
22512
+ const repair = { detail: "backfill or prepare the selected remote checkout/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" } }, message: "prepare remote checkout link" } };
22513
+ const doctorIndex = actions.findIndex((entry) => entry.item.id === "doctor");
22514
+ if (doctorIndex < 0)
22515
+ return [...actions, repair];
22516
+ return [...actions.slice(0, doctorIndex), repair, ...actions.slice(doctorIndex)];
22517
+ }
22131
22518
  function cleanVisible(value, fallback = "unknown") {
22132
22519
  return value?.trim() || fallback;
22133
22520
  }
@@ -22152,31 +22539,37 @@ function selectedIndex(state, count) {
22152
22539
  function statusRows2(server) {
22153
22540
  const auth = server?.auth;
22154
22541
  const signedIn = auth && (auth.signedIn === true || auth.authenticated === true || auth.status === "authenticated") ? "authenticated" : "auth unknown";
22542
+ const link = server?.remoteProjectLink;
22543
+ const linkText = link ? link.ok ? `ready \xB7 ${link.serverProjectRoot ?? "linked"}` : `${link.status ?? "needs repair"} \xB7 ${link.hint ?? link.message ?? "run repair link"}` : server?.kind === "remote" ? "not checked" : "not required";
22544
+ const selectedLinkSummary = link?.ok ? " \xB7 root link ready" : server?.kind === "remote" && link ? ` \xB7 root link ${link.status ?? "needs repair"}` : "";
22155
22545
  return [
22156
- line(`selected ${cleanVisible(server?.label, "not checked")}`, { fg: RIG_UI.ink2 }),
22546
+ line(`selected ${cleanVisible(server?.label, "not checked")}${selectedLinkSummary}`, { fg: RIG_UI.ink2 }),
22157
22547
  line(`target ${targetLabel(server)}`, { fg: RIG_UI.ink3 }),
22158
22548
  line(`mode ${server?.kind ?? "local or remote"}`, { fg: RIG_UI.ink3 }),
22159
22549
  line(`health ${server ? server.reachable ? "reachable" : "unreachable" : "check pending"}`, { fg: server?.reachable ? RIG_UI.limeDim : server ? RIG_UI.red : RIG_UI.yellow }),
22160
22550
  line(`github ${signedIn}`, { fg: signedIn === "authenticated" ? RIG_UI.limeDim : RIG_UI.ink4 }),
22551
+ line(`root link ${linkText}`, { fg: link?.ok ? RIG_UI.limeDim : server?.kind === "remote" ? RIG_UI.yellow : RIG_UI.ink4 }),
22552
+ ...server?.aliases && server.aliases.length > 0 ? [line(`aliases ${server.aliases.map((alias) => `${alias.selected ? "*" : ""}${alias.alias}${alias.kind === "remote" && alias.baseUrl ? `=${alias.baseUrl}` : ""}`).join(", ")}`, { fg: RIG_UI.ink4 })] : [],
22161
22553
  ...server?.error ? [line(`attention ${server.error}`, { fg: RIG_UI.yellow })] : []
22162
22554
  ];
22163
22555
  }
22164
22556
  function renderServerScene(state, layout) {
22165
22557
  const server = serverState(state);
22166
- const selected = selectedIndex(state, 10);
22558
+ const actions = serverActions(state);
22559
+ const selected = selectedIndex(state, actions.length);
22167
22560
  const loading = state.status === "loading" && !server;
22168
22561
  const errorText = state.error?.message ?? lastRefreshError(state);
22169
22562
  const panelLines = [
22170
22563
  ...errorText ? errorBanner(errorText, "select refresh or doctor below to retry") : [],
22171
22564
  line("server controls", { fg: RIG_UI.ink3 }),
22172
22565
  blank(),
22173
- line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
22174
- ...SERVER_ACTIONS.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item)),
22175
- blank(),
22176
22566
  line("STATUS", { fg: RIG_UI.ink3, bold: true }),
22177
22567
  ...loading ? loadingRows("server status", state.tick) : statusRows2(server),
22178
22568
  blank(),
22179
- line("enter/click runs action \xB7 command rows stay inside OpenTUI PTY", { fg: RIG_UI.ink4 })
22569
+ line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
22570
+ ...actions.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item)),
22571
+ blank(),
22572
+ line("enter/click runs native actions \xB7 add/use remote prompts stay in OpenTUI", { fg: RIG_UI.ink4 })
22180
22573
  ];
22181
22574
  return makeSceneFrame({
22182
22575
  scene: "server",
@@ -22206,14 +22599,15 @@ var init_server3 = __esm(() => {
22206
22599
  init_scene();
22207
22600
  init_selectable();
22208
22601
  init_theme2();
22602
+ init_remote_link();
22209
22603
  SERVER_ACTIONS = [
22210
22604
  { detail: "probe selected server, project root, and GitHub auth", item: { id: "refresh", label: "refresh", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Refresh server" } }, message: "refresh selected server" } },
22211
- { detail: "select local server for this repo", item: { id: "local", label: "use local", intent: { scene: "command", argv: ["server", "use", "local"], action: { kind: "command-run", label: "Use local server" } }, message: "switch this repo to local" } },
22212
- { detail: "choose/switch to a saved remote server alias", item: { id: "remote", label: "use remote", intent: { scene: "command", argv: ["server", "use"], action: { kind: "command-run", label: "Choose remote server" } }, message: "choose a saved remote alias" } },
22213
- { detail: "show saved local/remote aliases", item: { id: "list", label: "list servers", intent: { scene: "command", argv: ["server", "list"], action: { kind: "command-run", label: "List saved servers" } }, message: "list saved server aliases" } },
22214
- { detail: "open server add command workspace", item: { id: "add", label: "add remote", intent: { scene: "command", argv: ["server", "add"], action: { kind: "command-run", label: "Add remote server" } }, message: "open server add workspace" } },
22605
+ { detail: "select local server for this repo", item: { id: "local", label: "use local", intent: { scene: "server", argv: ["server", "use", "local"], action: { kind: "server-use-local", label: "Use local server" } }, message: "switch this repo to local" } },
22606
+ { detail: "type a saved remote alias to select", item: { id: "remote", label: "use remote", prompt: { label: "Use remote alias", scene: "server", argv: ["server", "use"], intentKind: "server-use-remote", payloadBase: {}, payloadKey: "alias" }, message: "choose a saved remote alias" } },
22607
+ { detail: "show saved local/remote aliases in the status panel", item: { id: "list", label: "list servers", intent: { scene: "server", argv: ["server", "list"], action: { kind: "refresh", label: "List servers" } }, message: "list saved server aliases" } },
22608
+ { detail: "add and select a remote: enter <alias> <https-url>", item: { id: "add", label: "add remote", prompt: { label: "Add remote", scene: "server", argv: ["server", "add"], intentKind: "server-add-remote", payloadBase: {}, payloadKey: "remote", appendValueToArgv: true }, message: "add a remote server alias" } },
22215
22609
  { detail: "spawn the local rig-server detached and return here", item: { id: "start", label: "start local", intent: { scene: "server", argv: ["server", "start"], action: { kind: "refresh", payload: { startLocal: true }, label: "Start local server" } }, message: "start local server detached" } },
22216
- { detail: "import gh token into the selected server namespace", item: { id: "auth", label: "import auth", intent: { scene: "command", argv: ["github", "auth", "import-gh"], action: { kind: "command-run", label: "Import GitHub auth" } }, message: "import gh auth to selected server" } },
22610
+ { detail: "import gh token into the selected server namespace", item: { id: "auth", label: "import auth", intent: { scene: "server", argv: ["github", "auth", "import-gh"], action: { kind: "github-auth-import", label: "Import GitHub auth" } }, message: "import gh auth to selected server" } },
22217
22611
  { detail: "diagnose server/auth/project linkage", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify server/auth" } },
22218
22612
  { detail: "verify selected server can list task source", item: { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["task", "list"], action: { kind: "refresh", label: "Open tasks" } }, message: "verify task source" } },
22219
22613
  { detail: "verify selected server can list recent runs", item: { id: "runs", label: "runs", intent: { scene: "fleet", argv: ["run", "status"], action: { kind: "refresh", label: "Open runs" } }, message: "open recent runs" } }
@@ -22365,27 +22759,38 @@ function taskRow(width, task, active, optimistic) {
22365
22759
  const item = taskSelectableItem(task);
22366
22760
  return item ? withSelectable(row, item) : row;
22367
22761
  }
22368
- function recoveryRows2(selected, reason) {
22762
+ function taskRecoveryItems(state) {
22763
+ if (!shouldOfferRemoteLinkRepair(state))
22764
+ return BASE_TASK_RECOVERY_ITEMS;
22765
+ return [BASE_TASK_RECOVERY_ITEMS[0], BASE_TASK_RECOVERY_ITEMS[1], TASK_REPAIR_LINK_ITEM, ...BASE_TASK_RECOVERY_ITEMS.slice(2)];
22766
+ }
22767
+ function taskRecoveryDecks(state) {
22768
+ if (!shouldOfferRemoteLinkRepair(state))
22769
+ return BASE_RECOVERY_DECKS2;
22770
+ return [BASE_RECOVERY_DECKS2[0], BASE_RECOVERY_DECKS2[1], { label: "repair link", detail: "backfill/prepare remote project-root link" }, ...BASE_RECOVERY_DECKS2.slice(2)];
22771
+ }
22772
+ function recoveryRows2(state, selected, reason) {
22773
+ const items = taskRecoveryItems(state);
22369
22774
  return [
22370
22775
  ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
22371
22776
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
22372
- ...RECOVERY_DECKS2.map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, TASK_RECOVERY_ITEMS[index]))
22777
+ ...taskRecoveryDecks(state).map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, items[index]))
22373
22778
  ];
22374
22779
  }
22375
- function emptyRows2(query, total, selected) {
22780
+ function emptyRows2(state, query, total, selected) {
22376
22781
  if (!query.trim() && total === 0)
22377
- return recoveryRows2(selected, "No tasks loaded.");
22782
+ return recoveryRows2(state, selected, "No tasks loaded.");
22378
22783
  if (query.trim() && total > 0) {
22379
22784
  return [
22380
- ...recoveryRows2(selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
22785
+ ...recoveryRows2(state, selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
22381
22786
  ];
22382
22787
  }
22383
- return recoveryRows2(selected, "No tasks are visible with this filter.");
22788
+ return recoveryRows2(state, selected, "No tasks are visible with this filter.");
22384
22789
  }
22385
22790
  function degradedRows2(state, selected) {
22386
22791
  if (!state.error)
22387
22792
  return [];
22388
- return recoveryRows2(selected, state.error.message);
22793
+ return recoveryRows2(state, selected, state.error.message);
22389
22794
  }
22390
22795
  function rawString(raw, key) {
22391
22796
  const value = raw?.[key];
@@ -22409,7 +22814,10 @@ function renderTaskDetail(state, taskId3, layout) {
22409
22814
  const activeRunId = task?.activeRun?.runId?.trim();
22410
22815
  const width = panelWidth13(layout);
22411
22816
  const selected = Math.max(0, state.selection.index);
22817
+ const errorText = state.error?.message ?? lastRefreshError(state);
22818
+ const offerRepairLink = shouldOfferRemoteLinkRepair(state, errorText);
22412
22819
  const headerLines = [
22820
+ ...errorText ? errorBanner(errorText, offerRepairLink ? "select repair link below to prepare the remote checkout" : "retry dispatch after resolving the issue") : [],
22413
22821
  line(`${glyph2} ${title}`, { fg: color, bold: true }),
22414
22822
  line(`task ${taskId3} \xB7 ${status}`, { fg: RIG_UI.ink3 }),
22415
22823
  ...task && task.labels.length > 0 ? [line(`labels: ${task.labels.join(", ")}`, { fg: RIG_UI.ink4 })] : [],
@@ -22421,8 +22829,16 @@ function renderTaskDetail(state, taskId3, layout) {
22421
22829
  const actionRows2 = [];
22422
22830
  actionRows2.push(selectableDeckRow({ label: "back", detail: "return to the task list", index, active: selected === index }, { id: "task-detail-back", label: "back", data: { taskDetailId: undefined }, message: "back to task list" }));
22423
22831
  index += 1;
22424
- actionRows2.push(selectableDeckRow({ label: "dispatch", detail: "submit a Pi run for this task", index, active: selected === index }, { id: `task-detail-run:${taskId3}`, label: `dispatch ${taskId3}`, intent: { scene: "tasks", argv: ["run", taskId3], action: { kind: "task-run-id", payload: { task: taskId3 }, label: `Dispatching ${taskId3}` } }, message: `dispatch task ${taskId3}` }));
22425
- index += 1;
22832
+ if (offerRepairLink) {
22833
+ actionRows2.push(selectableDeckRow({ label: "repair link", detail: "prepare/backfill the selected remote project-root link", index, active: selected === index }, { id: `task-detail-repair-link:${taskId3}`, label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "tasks", taskDetailId: taskId3 } } }, message: "prepare remote checkout link" }));
22834
+ index += 1;
22835
+ }
22836
+ if (offerRepairLink) {
22837
+ actionRows2.push(line(" dispatch blocked until the remote project link is repaired", { fg: RIG_UI.ink4 }));
22838
+ } else {
22839
+ actionRows2.push(selectableDeckRow({ label: "dispatch", detail: "submit a Pi run for this task", index, active: selected === index }, { id: `task-detail-run:${taskId3}`, label: `dispatch ${taskId3}`, intent: { scene: "tasks", argv: ["run", taskId3], action: { kind: "task-run-id", payload: { task: taskId3 }, label: `Dispatching ${taskId3}` } }, message: `dispatch task ${taskId3}` }));
22840
+ index += 1;
22841
+ }
22426
22842
  if (activeRunId) {
22427
22843
  actionRows2.push(selectableDeckRow({ label: "attach", detail: `attach the Pi console to run ${activeRunId.slice(0, 8)}`, index, active: selected === index }, { id: `task-detail-attach:${taskId3}`, label: `attach ${activeRunId.slice(0, 8)}`, intent: { scene: "handoff", argv: ["attach", activeRunId], action: { kind: "run-attach", payload: { runId: activeRunId }, label: `Attach Pi ${activeRunId.slice(0, 8)}` } }, message: `attach Pi to ${activeRunId.slice(0, 8)}` }));
22428
22844
  index += 1;
@@ -22481,7 +22897,7 @@ function renderTasksScene(state, layout) {
22481
22897
  const tasks = sortTasks2(filterTasksForSearch(allTasks, query), spec);
22482
22898
  const selected = typeof state.data.selectedTaskId === "string" && tasks.some((task) => task.id === state.data.selectedTaskId) ? state.data.selectedTaskId : tasks[0]?.id;
22483
22899
  const selectedIndex2 = Math.max(0, tasks.findIndex((task) => task.id === selected));
22484
- const recoveryIndex = Math.max(0, Math.min(4, state.selection.index));
22900
+ const recoveryIndex = Math.max(0, Math.min(taskRecoveryItems(state).length - 1, state.selection.index));
22485
22901
  const dispatching = dispatchingFor(state);
22486
22902
  const dispatchLine = dispatching ? ` \xB7 ${state.data.dispatchingRun?.runId ?? "new"} ${dispatching.status}` : "";
22487
22903
  const updatedAgo = updatedAgoLabel2(state);
@@ -22501,7 +22917,7 @@ function renderTasksScene(state, layout) {
22501
22917
  ...tasks.map((task, index) => taskRow(contentWidth, task, index === selectedIndex2, dispatching && dispatching.taskId === task.id ? dispatching.status : undefined))
22502
22918
  ] : state.error ? degradedRows2(state, recoveryIndex) : loading ? loadingRows("tasks", state.tick) : [
22503
22919
  ...query.trim() ? [line(searchSummary("tasks", query, tasks.length, allTasks.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
22504
- ...emptyRows2(query, allTasks.length, recoveryIndex)
22920
+ ...emptyRows2(state, query, allTasks.length, recoveryIndex)
22505
22921
  ]
22506
22922
  ];
22507
22923
  return makeSceneFrame({
@@ -22535,18 +22951,19 @@ function renderTasksScene(state, layout) {
22535
22951
  live: true
22536
22952
  });
22537
22953
  }
22538
- var TASK_RECOVERY_ITEMS, COL3, GUTTER4 = " ", TASK_COLUMNS, RECOVERY_DECKS2;
22954
+ var BASE_TASK_RECOVERY_ITEMS, TASK_REPAIR_LINK_ITEM, COL3, GUTTER4 = " ", TASK_COLUMNS, BASE_RECOVERY_DECKS2;
22539
22955
  var init_tasks2 = __esm(() => {
22540
22956
  init_scene();
22541
22957
  init_selectable();
22958
+ init_remote_link();
22542
22959
  init_theme2();
22543
- TASK_RECOVERY_ITEMS = [
22960
+ BASE_TASK_RECOVERY_ITEMS = [
22544
22961
  { id: "refresh", label: "refresh", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Refresh tasks" } }, message: "refresh task source" },
22545
- { id: "next", label: "next", intent: { scene: "command", argv: ["task", "run", "--next"], action: { kind: "command-run", label: "Run next task" } }, message: "dispatch next runnable task" },
22546
- { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair task source" } }, message: "repair project/task source" },
22962
+ { id: "next", label: "next", intent: { scene: "tasks", argv: ["task", "run", "--next"], action: { kind: "task-run-next", label: "Dispatch next task" } }, message: "dispatch next runnable task" },
22547
22963
  { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
22548
22964
  { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose task source" }
22549
22965
  ];
22966
+ TASK_REPAIR_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: "tasks" } } }, message: "backfill or prepare remote project link" };
22550
22967
  COL3 = {
22551
22968
  glyph: 1,
22552
22969
  id: 11,
@@ -22561,10 +22978,9 @@ var init_tasks2 = __esm(() => {
22561
22978
  { key: "assignee", label: "assignee", hint: "by assignee" },
22562
22979
  { key: "title", label: "title", hint: "by title" }
22563
22980
  ];
22564
- RECOVERY_DECKS2 = [
22981
+ BASE_RECOVERY_DECKS2 = [
22565
22982
  { label: "refresh", detail: "reload task source" },
22566
22983
  { label: "next", detail: "dispatch next runnable task" },
22567
- { label: "repair", detail: "repair project/task source link" },
22568
22984
  { label: "server", detail: "open server controls" },
22569
22985
  { label: "doctor", detail: "diagnose task source" }
22570
22986
  ];
@@ -22898,6 +23314,29 @@ function createDomainAdapter() {
22898
23314
  else
22899
23315
  await loadInitFacts(ctx);
22900
23316
  return true;
23317
+ case "server-use-local":
23318
+ await useLocalServerFromApp(ctx);
23319
+ return true;
23320
+ case "server-use-remote":
23321
+ await useRemoteServerFromApp(ctx, payloadString8(intent, "alias") ?? intent.argv[2]);
23322
+ return true;
23323
+ case "server-add-remote":
23324
+ await addRemoteServerFromApp(ctx, intent.argv);
23325
+ return true;
23326
+ case "github-auth-import":
23327
+ await importGitHubAuthFromGhApp(ctx);
23328
+ return true;
23329
+ case "remote-link-repair": {
23330
+ await repairRemoteProjectRootLinkFromApp(ctx);
23331
+ const returnScene = payloadString8(intent, "returnScene");
23332
+ if (returnScene && returnScene !== intent.scene) {
23333
+ ctx.emit({ type: "scene.change", scene: returnScene, intent: { scene: returnScene, argv: intent.argv, action: { kind: "refresh", label: `Refresh ${returnScene}`, payload: intent.action.payload } } });
23334
+ await refreshScene(ctx, returnScene, { scene: returnScene, argv: intent.argv, action: { kind: "refresh", label: `Refresh ${returnScene}`, payload: intent.action.payload } });
23335
+ } else {
23336
+ await refreshServerStatus(ctx);
23337
+ }
23338
+ return true;
23339
+ }
22901
23340
  case "doctor-run":
22902
23341
  await runDoctorChecksForApp(ctx);
22903
23342
  return true;
@@ -23000,7 +23439,9 @@ import {
23000
23439
  RigInboxInputsOutput,
23001
23440
  RigRunListOutput,
23002
23441
  RigRunShowOutput,
23442
+ RigServerRepairLinkOutput,
23003
23443
  RigServerStatusOutput,
23444
+ RigServerUseOutput,
23004
23445
  RigStatsOutput,
23005
23446
  RigTaskListOutput,
23006
23447
  RigTaskShowOutput
@@ -23036,7 +23477,9 @@ var init__json_output = __esm(() => {
23036
23477
  "task show": RigTaskShowOutput,
23037
23478
  "run list": RigRunListOutput,
23038
23479
  "run show": RigRunShowOutput,
23480
+ "server use": RigServerUseOutput,
23039
23481
  "server status": RigServerStatusOutput,
23482
+ "server repair-link": RigServerRepairLinkOutput,
23040
23483
  "inbox approvals": RigInboxApprovalsOutput,
23041
23484
  "inbox inputs": RigInboxInputsOutput,
23042
23485
  "doctor check": RigDoctorCheckOutput,
@@ -23046,7 +23489,7 @@ var init__json_output = __esm(() => {
23046
23489
 
23047
23490
  // packages/cli/src/launcher.ts
23048
23491
  import { existsSync as existsSync22 } from "fs";
23049
- import { basename as basename5, resolve as resolve29 } from "path";
23492
+ import { basename as basename4, resolve as resolve29 } from "path";
23050
23493
  import { loadDotEnvSecrets } from "@rig/runtime/baked-secrets";
23051
23494
  import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME, resolveNearestRigProjectRoot } from "@rig/runtime/layout";
23052
23495
  function parsePolicyMode(value) {
@@ -23071,7 +23514,7 @@ function resolveProjectRoot({
23071
23514
  return resolve29(cwd, envProjectRoot);
23072
23515
  }
23073
23516
  const fallbackImportDir = importDir ?? cwd;
23074
- const execName = basename5(execPath).toLowerCase();
23517
+ const execName = basename4(execPath).toLowerCase();
23075
23518
  const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve29(execPath, "..", "..")] : [];
23076
23519
  const candidates = [cwd, ...execCandidates, resolve29(fallbackImportDir, "..")];
23077
23520
  for (const candidate of candidates) {
@@ -23367,16 +23810,31 @@ function taskViewIntent(argv, command = "", rest = [], raw) {
23367
23810
  const { payload, label } = taskViewPayload(command, rest);
23368
23811
  return intent("tasks", argv, "refresh", payload, label, raw);
23369
23812
  }
23370
- function routeServer(argv, command, raw, checkingLabel) {
23371
- if (!command || command === "status") {
23813
+ function routeServer(argv, command, rest, raw, checkingLabel) {
23814
+ if (!command || command === "status" || command === "list") {
23372
23815
  return intent("server", argv, "refresh", undefined, command ? checkingLabel : "Loading server", raw);
23373
23816
  }
23817
+ if (command === "use") {
23818
+ const alias = firstNonOption(rest);
23819
+ if (alias === "local")
23820
+ return intent("server", argv, "server-use-local", undefined, "Use local server", raw);
23821
+ if (alias)
23822
+ return intent("server", argv, "server-use-remote", { alias }, `Use ${alias}`, raw);
23823
+ return intent("server", argv, "refresh", undefined, "Select a remote alias in Server", raw);
23824
+ }
23825
+ if (command === "add")
23826
+ return intent("server", argv, "server-add-remote", { argv: [...argv] }, "Add remote server", raw);
23827
+ if (command === "repair-link")
23828
+ return intent("server", argv, "remote-link-repair", undefined, "Repair remote link", raw);
23374
23829
  return commandRunIntent(argv, raw);
23375
23830
  }
23376
23831
  function routeGithub(argv, command, rest, raw) {
23377
23832
  if (command === "auth" && (rest[0]?.toLowerCase() ?? "status") === "status" && rest.length <= 1) {
23378
23833
  return intent("server", argv, "refresh", undefined, "Checking GitHub auth", raw);
23379
23834
  }
23835
+ if (command === "auth" && rest[0]?.toLowerCase() === "import-gh" && rest.length <= 1) {
23836
+ return intent("server", argv, "github-auth-import", undefined, "Import GitHub auth", raw);
23837
+ }
23380
23838
  return commandRunIntent(argv, raw);
23381
23839
  }
23382
23840
  function routeDoctor(argv, command, raw) {
@@ -23425,11 +23883,16 @@ function intentFromArgv(argv) {
23425
23883
  return commandRunIntent(argv);
23426
23884
  }
23427
23885
  if (normalizedGroup === "server")
23428
- return routeServer(argv, normalizedCommand, undefined, "Checking server");
23886
+ return routeServer(argv, normalizedCommand, rest, undefined, "Checking server");
23429
23887
  if (normalizedGroup === "github")
23430
23888
  return routeGithub(argv, normalizedCommand, rest);
23431
- if (normalizedGroup === "init")
23432
- return commandRunIntent(argv);
23889
+ if (normalizedGroup === "init") {
23890
+ if (!normalizedCommand)
23891
+ return intent("init", argv, "refresh", undefined, "Open init");
23892
+ if (rest.includes("--yes") || normalizedCommand === "--yes")
23893
+ return intent("init", argv, "init-start", undefined, "Run init");
23894
+ return intent("init", argv, "refresh", undefined, "Open init");
23895
+ }
23433
23896
  if (normalizedGroup === "doctor")
23434
23897
  return routeDoctor(argv, normalizedCommand);
23435
23898
  if (normalizedGroup === "inbox")
@@ -23492,12 +23955,17 @@ function intentFromTypeBar(value) {
23492
23955
  return commandRunIntent(parts, value);
23493
23956
  return intent("tasks", parts, "refresh", undefined, "Select a task, then press Enter; or use run next", value);
23494
23957
  }
23495
- if (first === "init")
23496
- return commandRunIntent(parts, value);
23958
+ if (first === "init") {
23959
+ if (!second)
23960
+ return intent("init", parts, "refresh", undefined, "Open init", value);
23961
+ if (parts.includes("--yes"))
23962
+ return intent("init", parts, "init-start", undefined, "Run init", value);
23963
+ return intent("init", parts, "refresh", undefined, "Open init", value);
23964
+ }
23497
23965
  if (first === "doctor")
23498
23966
  return routeDoctor(parts, second, value);
23499
23967
  if (first === "server")
23500
- return routeServer(parts, second, value, "Loading server");
23968
+ return routeServer(parts, second, rest, value, "Loading server");
23501
23969
  if (first === "github")
23502
23970
  return routeGithub(parts, second, rest, value);
23503
23971
  if (first === "inbox")
@@ -23948,22 +24416,15 @@ function clearTypeBar(context, message2) {
23948
24416
  context.emitTypeBarPatch({ value: "", mode: "nav", prompt: undefined, message: message2 });
23949
24417
  }
23950
24418
  function handleEmbeddedTerminalKey(context, key) {
23951
- const state = context.getState();
23952
- const piHost = getActivePiHost();
23953
- const commandHost = getActiveCommandHost();
23954
- const host = state.scene === "handoff" ? piHost : state.scene === "command" ? commandHost : null;
24419
+ if (context.getState().scene !== "command")
24420
+ return false;
24421
+ const host = getActiveCommandHost();
23955
24422
  if (!host)
23956
24423
  return false;
23957
24424
  if (key.ctrl && key.name === "]") {
23958
- if (state.scene === "handoff") {
23959
- stopActivePiHost("operator detach");
23960
- clearTypeBar(context, "detached from Pi");
23961
- context.runAppAction("Opening runs", () => context.runtime.runIntent({ scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } }));
23962
- } else {
23963
- stopActiveCommandHost("operator detach");
23964
- clearTypeBar(context, "closed command");
23965
- context.runAppAction("Project menu", () => context.runtime.runIntent({ scene: "main", argv: ["main"], action: { kind: "none", label: "Project menu" } }));
23966
- }
24425
+ stopActiveCommandHost("operator detach");
24426
+ clearTypeBar(context, "closed command");
24427
+ context.runAppAction("Project menu", () => context.runtime.runIntent({ scene: "main", argv: ["main"], action: { kind: "none", label: "Project menu" } }));
23967
24428
  return true;
23968
24429
  }
23969
24430
  const sequence = key.raw || key.sequence;
@@ -24172,7 +24633,6 @@ var init_keymap = __esm(() => {
24172
24633
  init_autocomplete();
24173
24634
  init_command_pty_host();
24174
24635
  init_intent();
24175
- init_pi_pty_host();
24176
24636
  });
24177
24637
 
24178
24638
  // packages/cli/src/app-opentui/runtime-resources.ts
@@ -24329,7 +24789,7 @@ var init_constants = __esm(() => {
24329
24789
  });
24330
24790
 
24331
24791
  // packages/cli/src/app-opentui/render/graphics.ts
24332
- import { FrameBufferRenderable, RGBA as RGBA3 } from "@opentui/core";
24792
+ import { FrameBufferRenderable, RGBA as RGBA2 } from "@opentui/core";
24333
24793
  function sceneKind(scene) {
24334
24794
  if (scene === "tasks")
24335
24795
  return "loop";
@@ -24424,7 +24884,7 @@ function paletteColor(ac, c2, mg, ink5) {
24424
24884
  const b = (AC_RGB[2] * ac + C2_RGB[2] * c2 + MG_RGB[2] * mg + INK_RGB[2] * ink5) / total;
24425
24885
  const intensity = Math.min(1, total / 4);
24426
24886
  const lift = 0.34 + intensity * 0.66;
24427
- return RGBA3.fromInts(Math.round(r * lift), Math.round(g * lift), Math.round(b * lift), 255);
24887
+ return RGBA2.fromInts(Math.round(r * lift), Math.round(g * lift), Math.round(b * lift), 255);
24428
24888
  }
24429
24889
  function clearCanvas(canvas) {
24430
24890
  canvas.ac.fill(0);
@@ -24779,8 +25239,8 @@ function staticFleetTick(scene) {
24779
25239
  }
24780
25240
  function withAlpha(hex, alpha) {
24781
25241
  const a = Math.max(0, Math.min(255, Math.round(alpha * 255)));
24782
- const [r, g, b] = RGBA3.fromHex(hex).toInts();
24783
- return RGBA3.fromInts(r, g, b, a);
25242
+ const [r, g, b] = RGBA2.fromHex(hex).toInts();
25243
+ return RGBA2.fromInts(r, g, b, a);
24784
25244
  }
24785
25245
  function asciiFleetColor(char, row, alpha) {
24786
25246
  if (char === "@" || char === "$" || char === "o")
@@ -24788,7 +25248,7 @@ function asciiFleetColor(char, row, alpha) {
24788
25248
  if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
24789
25249
  return withAlpha(RIG_UI.cyan, alpha);
24790
25250
  if (char === "." || char === "'" || char === "_" || row < 3 || row > FLEET_GRID_HEIGHT - 4) {
24791
- return RGBA3.fromInts(108, 110, 121, Math.min(170, Math.max(0, Math.min(255, Math.round(alpha * 255)))));
25251
+ return RGBA2.fromInts(108, 110, 121, Math.min(170, Math.max(0, Math.min(255, Math.round(alpha * 255)))));
24792
25252
  }
24793
25253
  return withAlpha(RIG_UI.limeDim, alpha);
24794
25254
  }
@@ -24842,13 +25302,13 @@ var init_graphics = __esm(() => {
24842
25302
  init_theme2();
24843
25303
  init_ascii_fleet();
24844
25304
  init_constants();
24845
- TRANSPARENT = RGBA3.fromValues(0, 0, 0, 0);
24846
- BACKDROP = RGBA3.fromHex(RIG_UI.bg);
24847
- PANEL_BG = RGBA3.fromInts(16, 17, 21, 184);
24848
- PANEL_HEADER_BG = RGBA3.fromInts(16, 17, 21, 188);
24849
- PANEL_LINE = RGBA3.fromInts(255, 255, 255, 20);
24850
- PANEL_LINE_DIM = RGBA3.fromInts(255, 255, 255, 8);
24851
- PANEL_TEXT_DIM = RGBA3.fromInts(108, 110, 121, 255);
25305
+ TRANSPARENT = RGBA2.fromValues(0, 0, 0, 0);
25306
+ BACKDROP = RGBA2.fromHex(RIG_UI.bg);
25307
+ PANEL_BG = RGBA2.fromInts(16, 17, 21, 184);
25308
+ PANEL_HEADER_BG = RGBA2.fromInts(16, 17, 21, 188);
25309
+ PANEL_LINE = RGBA2.fromInts(255, 255, 255, 20);
25310
+ PANEL_LINE_DIM = RGBA2.fromInts(255, 255, 255, 8);
25311
+ PANEL_TEXT_DIM = RGBA2.fromInts(108, 110, 121, 255);
24852
25312
  AC_RGB = [204, 255, 77];
24853
25313
  C2_RGB = [86, 216, 255];
24854
25314
  MG_RGB = [255, 121, 176];
@@ -24891,7 +25351,7 @@ var init_hover = __esm(() => {
24891
25351
  });
24892
25352
 
24893
25353
  // packages/cli/src/app-opentui/render/text.ts
24894
- import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
25354
+ import { RGBA as RGBA3, TextAttributes as TextAttributes3, TextRenderable } from "@opentui/core";
24895
25355
  function lineIsClickable(line2) {
24896
25356
  return line2?.selectable !== undefined || line2?.selectableIndex !== undefined;
24897
25357
  }
@@ -24900,7 +25360,7 @@ function lineSelectableId(line2) {
24900
25360
  }
24901
25361
  function lineBackground(line2) {
24902
25362
  if (line2?.bg)
24903
- return RGBA4.fromHex(line2.bg);
25363
+ return RGBA3.fromHex(line2.bg);
24904
25364
  const id = lineSelectableId(line2);
24905
25365
  if (id !== undefined && isHovered(id))
24906
25366
  return HOVER_BG;
@@ -24969,7 +25429,7 @@ function applyTextLine(renderable, line2, top, left, width) {
24969
25429
  renderable.content = line2?.styledText ?? line2?.text ?? "";
24970
25430
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
24971
25431
  renderable.bg = lineBackground(line2);
24972
- renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
25432
+ renderable.attributes = line2?.bold ? TextAttributes3.BOLD : line2?.dim ? TextAttributes3.DIM : 0;
24973
25433
  }
24974
25434
  function applyFlowTextLine(renderable, line2, width) {
24975
25435
  renderable.setRigSceneLine?.(line2);
@@ -24979,7 +25439,7 @@ function applyFlowTextLine(renderable, line2, width) {
24979
25439
  renderable.content = line2?.styledText ?? line2?.text ?? "";
24980
25440
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
24981
25441
  renderable.bg = lineBackground(line2);
24982
- renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
25442
+ renderable.attributes = line2?.bold ? TextAttributes3.BOLD : line2?.dim ? TextAttributes3.DIM : 0;
24983
25443
  }
24984
25444
  function clearTextLines(renderables, from = 0) {
24985
25445
  for (let index = from;index < renderables.length; index += 1) {
@@ -24996,19 +25456,30 @@ var TRANSPARENT2, HOVER_BG;
24996
25456
  var init_text = __esm(() => {
24997
25457
  init_theme2();
24998
25458
  init_hover();
24999
- TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
25000
- HOVER_BG = RGBA4.fromHex(RIG_UI.hover);
25459
+ TRANSPARENT2 = RGBA3.fromInts(0, 0, 0, 0);
25460
+ HOVER_BG = RGBA3.fromHex(RIG_UI.hover);
25001
25461
  });
25002
25462
 
25463
+ // packages/cli/src/app-opentui/render/terminal-handoff.ts
25464
+ function resumeRendererClean(renderer) {
25465
+ try {
25466
+ renderer.pendingSuspendedTerminalSetup = true;
25467
+ } catch {}
25468
+ renderer.resume();
25469
+ try {
25470
+ renderer.requestRender?.();
25471
+ } catch {}
25472
+ }
25473
+
25003
25474
  // packages/cli/src/app-opentui/render/panels.ts
25004
- import { RGBA as RGBA5, ScrollBoxRenderable } from "@opentui/core";
25475
+ import { RGBA as RGBA4, ScrollBoxRenderable } from "@opentui/core";
25005
25476
  function hexToRgba(hex, alpha) {
25006
25477
  const clean = hex.replace(/^#/, "");
25007
25478
  const full = clean.length === 3 ? clean.split("").map((part) => `${part}${part}`).join("") : clean;
25008
25479
  const value = Number.parseInt(full, 16);
25009
25480
  if (!Number.isFinite(value))
25010
- return RGBA5.fromInts(14, 15, 17, alpha);
25011
- return RGBA5.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255, alpha);
25481
+ return RGBA4.fromInts(14, 15, 17, alpha);
25482
+ return RGBA4.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255, alpha);
25012
25483
  }
25013
25484
  function panelBackground(panel) {
25014
25485
  return hexToRgba(panel.backgroundColor ?? RIG_UI.panel, PANEL_OPAQUE_ALPHA);
@@ -25125,12 +25596,12 @@ var TRANSPARENT3, MAX_PANEL_LINES = 420, PANEL_OPAQUE_ALPHA = 255, PANEL_BORDER;
25125
25596
  var init_panels = __esm(() => {
25126
25597
  init_theme2();
25127
25598
  init_text();
25128
- TRANSPARENT3 = RGBA5.fromInts(0, 0, 0, 0);
25599
+ TRANSPARENT3 = RGBA4.fromInts(0, 0, 0, 0);
25129
25600
  PANEL_BORDER = hexToRgba(RIG_UI.border, 255);
25130
25601
  });
25131
25602
 
25132
25603
  // packages/cli/src/app-opentui/render/type-bar.ts
25133
- import { BoxRenderable, InputRenderable, InputRenderableEvents, RGBA as RGBA6, StyledText as StyledText3, TextRenderable as TextRenderable2 } from "@opentui/core";
25604
+ import { BoxRenderable, InputRenderable, InputRenderableEvents, RGBA as RGBA5, StyledText as StyledText2, TextRenderable as TextRenderable2 } from "@opentui/core";
25134
25605
  function createTypeBar(renderer) {
25135
25606
  const background = new BoxRenderable(renderer, {
25136
25607
  id: "typebar-card",
@@ -25277,13 +25748,13 @@ function formatFooter(footer, width) {
25277
25748
  chunks.push(cell4.style(text2));
25278
25749
  used += visibleWidth(text2);
25279
25750
  });
25280
- return new StyledText3(chunks);
25751
+ return new StyledText2(chunks);
25281
25752
  }
25282
25753
  var TYPEBAR_BG, TYPEBAR_BORDER, FOOTER_SEPARATOR = " \xB7 ";
25283
25754
  var init_type_bar = __esm(() => {
25284
25755
  init_theme2();
25285
- TYPEBAR_BG = RGBA6.fromInts(20, 25, 14, 224);
25286
- TYPEBAR_BORDER = RGBA6.fromInts(204, 255, 77, 92);
25756
+ TYPEBAR_BG = RGBA5.fromInts(20, 25, 14, 224);
25757
+ TYPEBAR_BORDER = RGBA5.fromInts(204, 255, 77, 92);
25287
25758
  });
25288
25759
 
25289
25760
  // packages/cli/src/app-opentui/render/native-host.ts
@@ -25291,7 +25762,7 @@ import {
25291
25762
  BoxRenderable as BoxRenderable2,
25292
25763
  CodeRenderable,
25293
25764
  DiffRenderable,
25294
- RGBA as RGBA7,
25765
+ RGBA as RGBA6,
25295
25766
  ScrollBoxRenderable as ScrollBoxRenderable2,
25296
25767
  SyntaxStyle,
25297
25768
  TextRenderable as TextRenderable3,
@@ -25301,19 +25772,19 @@ function rigSyntaxStyle() {
25301
25772
  if (syntaxStyle)
25302
25773
  return syntaxStyle;
25303
25774
  syntaxStyle = SyntaxStyle.fromStyles({
25304
- keyword: { fg: RGBA7.fromHex(RIG_UI.magenta), bold: true },
25305
- string: { fg: RGBA7.fromHex(RIG_UI.lime) },
25306
- number: { fg: RGBA7.fromHex(RIG_UI.cyan) },
25307
- comment: { fg: RGBA7.fromHex(RIG_UI.ink4), italic: true },
25308
- function: { fg: RGBA7.fromHex(RIG_UI.cyan) },
25309
- type: { fg: RGBA7.fromHex(RIG_UI.limeDim) },
25310
- constant: { fg: RGBA7.fromHex(RIG_UI.yellow) },
25311
- default: { fg: RGBA7.fromHex(RIG_UI.ink2) }
25775
+ keyword: { fg: RGBA6.fromHex(RIG_UI.magenta), bold: true },
25776
+ string: { fg: RGBA6.fromHex(RIG_UI.lime) },
25777
+ number: { fg: RGBA6.fromHex(RIG_UI.cyan) },
25778
+ comment: { fg: RGBA6.fromHex(RIG_UI.ink4), italic: true },
25779
+ function: { fg: RGBA6.fromHex(RIG_UI.cyan) },
25780
+ type: { fg: RGBA6.fromHex(RIG_UI.limeDim) },
25781
+ constant: { fg: RGBA6.fromHex(RIG_UI.yellow) },
25782
+ default: { fg: RGBA6.fromHex(RIG_UI.ink2) }
25312
25783
  });
25313
25784
  return syntaxStyle;
25314
25785
  }
25315
25786
  function tableContent(rows) {
25316
- return rows.map((row, rowIndex) => row.map((cell4) => [{ __isChunk: true, text: cell4, fg: RGBA7.fromHex(rowIndex === 0 ? RIG_UI.ink3 : RIG_UI.ink2) }]));
25787
+ return rows.map((row, rowIndex) => row.map((cell4) => [{ __isChunk: true, text: cell4, fg: RGBA6.fromHex(rowIndex === 0 ? RIG_UI.ink3 : RIG_UI.ink2) }]));
25317
25788
  }
25318
25789
  function createNativeHost(renderer) {
25319
25790
  try {
@@ -25462,10 +25933,14 @@ var init_preloader = __esm(() => {
25462
25933
  });
25463
25934
 
25464
25935
  // packages/cli/src/app-opentui/scenes/error.ts
25936
+ function errorActions(state) {
25937
+ return shouldOfferRemoteLinkRepair(state, state.error?.message) ? [...ERROR_ACTIONS, ERROR_REPAIR_LINK_ACTION] : ERROR_ACTIONS;
25938
+ }
25465
25939
  function renderErrorScene(state) {
25466
25940
  const message2 = state.error?.message ?? "Unknown app error";
25467
25941
  const hint = state.error?.hint ?? "Use the actions below to recover.";
25468
- const selected = Math.max(0, Math.min(3, state.selection.index));
25942
+ const actions = errorActions(state);
25943
+ const selected = Math.max(0, Math.min(actions.length - 1, state.selection.index));
25469
25944
  return makeSceneFrame({
25470
25945
  scene: "error",
25471
25946
  title: "Error",
@@ -25483,7 +25958,7 @@ function renderErrorScene(state) {
25483
25958
  ...hint ? [line(hint, { fg: RIG_UI.yellow })] : [],
25484
25959
  line("", { fg: RIG_UI.ink3 }),
25485
25960
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
25486
- ...ERROR_ACTIONS.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item))
25961
+ ...actions.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item))
25487
25962
  ],
25488
25963
  backgroundColor: RIG_UI.panel,
25489
25964
  backgroundAlpha: 184,
@@ -25500,18 +25975,19 @@ function renderErrorScene(state) {
25500
25975
  live: true
25501
25976
  });
25502
25977
  }
25503
- var ERROR_ACTIONS;
25978
+ var ERROR_ACTIONS, ERROR_REPAIR_LINK_ACTION;
25504
25979
  var init_error = __esm(() => {
25505
25980
  init_drone();
25506
25981
  init_theme2();
25982
+ init_remote_link();
25507
25983
  init_scene();
25508
25984
  init_selectable();
25509
25985
  ERROR_ACTIONS = [
25510
25986
  { detail: "return to dashboard", item: { id: "main", label: "main", intent: { scene: "main", argv: [], action: { kind: "refresh", label: "Back to dashboard" } }, message: "back to dashboard" } },
25511
25987
  { detail: "run diagnostics", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "run diagnostics" } },
25512
- { 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" } },
25513
- { 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" } }
25988
+ { 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" } }
25514
25989
  ];
25990
+ 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" } };
25515
25991
  });
25516
25992
 
25517
25993
  // packages/cli/src/app-opentui/scenes/help.ts
@@ -25686,7 +26162,7 @@ function configuredMenuRows(selectedIndex2) {
25686
26162
  }));
25687
26163
  }
25688
26164
  function onboardingRows(selectedIndex2) {
25689
- const primary = selectableDeckRow({ label: "setup", detail: "Start guided setup \u2014 recommended", active: selectedIndex2 === 0 }, ONBOARD_PRIMARY);
26165
+ const primary = selectableDeckRow({ label: "init", detail: "Start guided init \u2014 recommended", active: selectedIndex2 === 0 }, ONBOARD_PRIMARY);
25690
26166
  const secondary = ONBOARD_SECONDARY.map(([label, detail, item], index) => ({
25691
26167
  ...deckRow({ label, detail, index: index + 1, active: selectedIndex2 === index + 1, activateOnClick: true }),
25692
26168
  selectable: item
@@ -25746,7 +26222,7 @@ function renderMainScene(state, layout) {
25746
26222
  statusLine(state)
25747
26223
  ] : [
25748
26224
  line("WELCOME", { fg: RIG_UI.ink, bold: true }),
25749
- line("This project isn't set up yet. One guided flow gets you running.", { fg: RIG_UI.ink2 }),
26225
+ line("This project isn't initialized yet. One native init flow gets you running.", { fg: RIG_UI.ink2 }),
25750
26226
  blank(),
25751
26227
  line("GET STARTED", { fg: RIG_UI.ink, bold: true }),
25752
26228
  ...onboardingRows(actionSelected),
@@ -25787,7 +26263,7 @@ var init_main = __esm(() => {
25787
26263
  init_theme2();
25788
26264
  init_fleet_stats();
25789
26265
  CONFIGURED_MENU = [
25790
- ["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" }],
26266
+ ["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" }],
25791
26267
  ["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" }],
25792
26268
  ["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" }],
25793
26269
  ["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" }],
@@ -25806,10 +26282,10 @@ var init_main = __esm(() => {
25806
26282
  ["help", "all actions, shortcuts, mouse controls", { id: "help", label: "help", intent: { scene: "help", argv: ["help"], action: { kind: "none", label: "Opening help" } }, message: "selected help" }]
25807
26283
  ];
25808
26284
  ONBOARD_PRIMARY = {
25809
- id: "onboard-setup",
25810
- label: "Start guided setup",
25811
- intent: { scene: "init", argv: ["init"], action: { kind: "refresh", label: "Setup" } },
25812
- message: "open the guided setup checklist"
26285
+ id: "onboard-init",
26286
+ label: "Start guided init",
26287
+ intent: { scene: "init", argv: ["init"], action: { kind: "refresh", label: "Init" } },
26288
+ message: "open the native init checklist"
25813
26289
  };
25814
26290
  ONBOARD_SECONDARY = [
25815
26291
  ["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" }],
@@ -25822,6 +26298,12 @@ var init_main = __esm(() => {
25822
26298
  TILE_TASKS = { id: "tile-tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Opening tasks" } }, message: "ready tasks \u2014 dispatch work" };
25823
26299
  });
25824
26300
 
26301
+ // packages/cli/src/app-opentui/pi-pty-host.ts
26302
+ function getActivePiHost() {
26303
+ return null;
26304
+ }
26305
+ function stopActivePiHost(_reason) {}
26306
+
25825
26307
  // packages/cli/src/app-opentui/runtime.ts
25826
26308
  var exports_runtime = {};
25827
26309
  __export(exports_runtime, {
@@ -25841,7 +26323,7 @@ __export(exports_runtime, {
25841
26323
  });
25842
26324
  import { existsSync as existsSync23 } from "fs";
25843
26325
  import { resolve as resolve30 } from "path";
25844
- import { BoxRenderable as BoxRenderable3, RGBA as RGBA8, StyledText as StyledText4, TextRenderable as TextRenderable4, createCliRenderer } from "@opentui/core";
26326
+ import { BoxRenderable as BoxRenderable3, RGBA as RGBA7, StyledText as StyledText3, TextRenderable as TextRenderable4, createCliRenderer } from "@opentui/core";
25845
26327
  function inboxPendingCount(state) {
25846
26328
  const inbox = state.data.inbox;
25847
26329
  if (!inbox || typeof inbox !== "object" || Array.isArray(inbox))
@@ -25888,7 +26370,7 @@ function entryIntentForScene(scene) {
25888
26370
  case "doctor":
25889
26371
  return { scene, argv: ["doctor"], action: { kind: "doctor-run", label: "Doctor" } };
25890
26372
  case "init":
25891
- return { scene, argv: ["init"], action: { kind: "refresh", label: "Setup" } };
26373
+ return { scene, argv: ["init"], action: { kind: "refresh", label: "Init" } };
25892
26374
  case "run-detail":
25893
26375
  return { scene, argv: ["run", "status"], action: { kind: "refresh", label: "Run" } };
25894
26376
  case "pi":
@@ -25977,7 +26459,7 @@ function sceneSectionLabel(scene) {
25977
26459
  handoff: "pi console",
25978
26460
  server: "server",
25979
26461
  doctor: "doctor",
25980
- init: "setup",
26462
+ init: "init",
25981
26463
  pi: "pi",
25982
26464
  plugin: "plugins",
25983
26465
  repo: "repo",
@@ -26010,7 +26492,7 @@ function buildNavStrip(state) {
26010
26492
  chunks.push(otuiBold(styles.yellow(String(badgeCount))));
26011
26493
  }
26012
26494
  });
26013
- return new StyledText4(chunks);
26495
+ return new StyledText3(chunks);
26014
26496
  }
26015
26497
  function requestRender(renderer) {
26016
26498
  renderer.requestRender?.();
@@ -26238,7 +26720,8 @@ async function launchRigOpenTuiApp(options) {
26238
26720
  },
26239
26721
  suspend: () => renderer?.suspend(),
26240
26722
  resume: () => {
26241
- renderer?.resume();
26723
+ if (renderer)
26724
+ resumeRendererClean(renderer);
26242
26725
  renderApp();
26243
26726
  },
26244
26727
  destroy: () => renderer?.destroy()
@@ -26466,7 +26949,7 @@ async function launchRigOpenTuiApp(options) {
26466
26949
  }
26467
26950
  for (const adapter of options.adapters ?? [])
26468
26951
  await adapter.start?.(runtime);
26469
- liveEvents = await connectRigServerEvents(options.projectRoot, {
26952
+ connectRigServerEvents(options.projectRoot, {
26470
26953
  onSnapshotInvalidated: () => refreshCurrentDataScene(),
26471
26954
  onRunLogAppended: () => {
26472
26955
  const scene = store.getState().scene;
@@ -26474,11 +26957,17 @@ async function launchRigOpenTuiApp(options) {
26474
26957
  refreshCurrentDataScene();
26475
26958
  },
26476
26959
  onStatus: (status) => store.patch({ footer: { live: liveLinkLabel(status) } })
26477
- }).catch(() => null);
26478
- if (!liveEvents)
26479
- store.patch({ footer: { live: liveLinkLabel("disconnected") } });
26480
- if (liveEvents)
26960
+ }).then((subscription) => {
26961
+ if (destroyed) {
26962
+ subscription.close();
26963
+ return;
26964
+ }
26965
+ liveEvents = subscription;
26481
26966
  resources.add(() => liveEvents?.close());
26967
+ }).catch(() => {
26968
+ if (!destroyed)
26969
+ store.patch({ footer: { live: liveLinkLabel("disconnected") } });
26970
+ });
26482
26971
  await runtime.runIntent(store.getState().intent);
26483
26972
  } catch (error) {
26484
26973
  const normalized = normalizeError(error);
@@ -26664,15 +27153,14 @@ var init_runtime = __esm(() => {
26664
27153
  init_help();
26665
27154
  init_main();
26666
27155
  init_selectable();
26667
- init_pi_pty_host();
26668
- PRELOADER_BACKDROP = RGBA8.fromHex(RIG_UI.bg);
27156
+ PRELOADER_BACKDROP = RGBA7.fromHex(RIG_UI.bg);
26669
27157
  PRIMARY_NAV = [
26670
27158
  { id: "cockpit", label: "Cockpit", scene: "main", argv: ["main"], enterLabel: "Cockpit", member: ["main"] },
26671
27159
  { id: "runs", label: "Runs", scene: "fleet", argv: ["runs"], enterLabel: "Runs", member: ["fleet", "run-detail", "inspect", "handoff"] },
26672
27160
  { id: "tasks", label: "Tasks", scene: "tasks", argv: ["tasks"], enterLabel: "Tasks", member: ["tasks"] },
26673
27161
  { id: "inbox", label: "Inbox", scene: "inbox", argv: ["inbox"], enterLabel: "Inbox", member: ["inbox"], badge: inboxPendingCount },
26674
27162
  { id: "stats", label: "Stats", scene: "family", argv: ["stats"], enterLabel: "Stats", member: ["family"] },
26675
- { id: "setup", label: "Setup", scene: "init", argv: ["init"], enterLabel: "Setup", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
27163
+ { id: "init", label: "Init", scene: "init", argv: ["init"], enterLabel: "Init", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
26676
27164
  { id: "help", label: "Help", scene: "help", argv: ["help"], enterLabel: "Help", member: ["help"] }
26677
27165
  ];
26678
27166
  SCENE_DATA_KEY = {
@@ -26782,10 +27270,10 @@ var init_scroll = __esm(() => {
26782
27270
  // packages/cli/src/app-opentui/react/SceneFrameView.tsx
26783
27271
  import { useEffect as useEffect2, useRef as useRef2 } from "react";
26784
27272
  import { useRenderer as useRenderer2 } from "@opentui/react";
26785
- import { TextAttributes as TextAttributes5 } from "@opentui/core";
27273
+ import { TextAttributes as TextAttributes4 } from "@opentui/core";
26786
27274
  import { jsxDEV, Fragment } from "@opentui/react/jsx-dev-runtime";
26787
27275
  function lineAttributes(line2) {
26788
- return line2?.bold ? TextAttributes5.BOLD : line2?.dim ? TextAttributes5.DIM : 0;
27276
+ return line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
26789
27277
  }
26790
27278
  function lineContent(line2) {
26791
27279
  return line2.styledText ?? line2.text ?? "";
@@ -27036,7 +27524,7 @@ var init_nav = __esm(() => {
27036
27524
  { id: "tasks", label: "Tasks", scene: "tasks", argv: ["tasks"], enterLabel: "Tasks", member: ["tasks"] },
27037
27525
  { id: "inbox", label: "Inbox", scene: "inbox", argv: ["inbox"], enterLabel: "Inbox", member: ["inbox"], badge: inboxPendingCount2 },
27038
27526
  { id: "stats", label: "Stats", scene: "family", argv: ["stats"], enterLabel: "Stats", member: ["family"] },
27039
- { id: "setup", label: "Setup", scene: "init", argv: ["init"], enterLabel: "Setup", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
27527
+ { id: "init", label: "Init", scene: "init", argv: ["init"], enterLabel: "Init", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
27040
27528
  { id: "help", label: "Help", scene: "help", argv: ["help"], enterLabel: "Help", member: ["help"] }
27041
27529
  ];
27042
27530
  });
@@ -27168,8 +27656,48 @@ async function launchRigReactApp(options) {
27168
27656
  const store = createAppStore(initialState);
27169
27657
  const events = createAppEventBus();
27170
27658
  let renderer = null;
27659
+ let root = null;
27171
27660
  let runnerPromise = null;
27172
27661
  let suppressHistoryPush = false;
27662
+ let resolveExit = () => {};
27663
+ const appExit = new Promise((resolve31) => {
27664
+ resolveExit = resolve31;
27665
+ });
27666
+ let relaunching = false;
27667
+ const mountRenderer = async () => {
27668
+ renderer = await createCliRenderer2({
27669
+ screenMode: "alternate-screen",
27670
+ exitOnCtrlC: false,
27671
+ targetFps: 30,
27672
+ maxFps: 30,
27673
+ useMouse: true,
27674
+ autoFocus: false,
27675
+ useKittyKeyboard: { disambiguate: true }
27676
+ });
27677
+ renderer.on("destroy", () => {
27678
+ if (!relaunching)
27679
+ resolveExit();
27680
+ });
27681
+ root = createRoot(renderer);
27682
+ root.render(/* @__PURE__ */ jsxDEV4(RigAppProvider, {
27683
+ value: { store, runtime, runAppAction },
27684
+ children: /* @__PURE__ */ jsxDEV4(App, {
27685
+ sceneRenderers: options.sceneRenderers
27686
+ }, undefined, false, undefined, this)
27687
+ }, undefined, false, undefined, this));
27688
+ relaunching = false;
27689
+ };
27690
+ const teardownRenderer = () => {
27691
+ relaunching = true;
27692
+ try {
27693
+ root?.unmount();
27694
+ } catch {}
27695
+ root = null;
27696
+ try {
27697
+ renderer?.destroy();
27698
+ } catch {}
27699
+ renderer = null;
27700
+ };
27173
27701
  const runAppAction = (label, action) => {
27174
27702
  action().catch((error) => {
27175
27703
  const n = normalizeError(error);
@@ -27211,8 +27739,10 @@ async function launchRigReactApp(options) {
27211
27739
  runnerPromise ??= options.initializeRuntime();
27212
27740
  return runnerPromise;
27213
27741
  },
27214
- suspend: () => renderer?.suspend(),
27215
- resume: () => renderer?.resume(),
27742
+ suspend: () => teardownRenderer(),
27743
+ resume: async () => {
27744
+ await mountRenderer();
27745
+ },
27216
27746
  destroy: () => renderer?.destroy()
27217
27747
  };
27218
27748
  events.subscribe((event) => store.patch(reduceAppEvent(store.getState(), event)));
@@ -27220,15 +27750,7 @@ async function launchRigReactApp(options) {
27220
27750
  if (typeof tick.unref === "function") {
27221
27751
  tick.unref();
27222
27752
  }
27223
- renderer = await createCliRenderer2({
27224
- screenMode: "alternate-screen",
27225
- exitOnCtrlC: false,
27226
- targetFps: 30,
27227
- maxFps: 30,
27228
- useMouse: true,
27229
- autoFocus: false,
27230
- useKittyKeyboard: { disambiguate: true }
27231
- });
27753
+ await mountRenderer();
27232
27754
  let liveEvents = null;
27233
27755
  const canAutoRefresh = (state) => {
27234
27756
  if (state.status === "action" || state.status === "loading")
@@ -27259,12 +27781,6 @@ async function launchRigReactApp(options) {
27259
27781
  }, FALLBACK_REFRESH_MS);
27260
27782
  if (typeof poll.unref === "function")
27261
27783
  poll.unref();
27262
- createRoot(renderer).render(/* @__PURE__ */ jsxDEV4(RigAppProvider, {
27263
- value: { store, runtime, runAppAction },
27264
- children: /* @__PURE__ */ jsxDEV4(App, {
27265
- sceneRenderers: options.sceneRenderers
27266
- }, undefined, false, undefined, this)
27267
- }, undefined, false, undefined, this));
27268
27784
  queueMicrotask(() => {
27269
27785
  (async () => {
27270
27786
  try {
@@ -27274,7 +27790,7 @@ async function launchRigReactApp(options) {
27274
27790
  }
27275
27791
  for (const adapter of options.adapters ?? [])
27276
27792
  await adapter.start?.(runtime);
27277
- liveEvents = await connectRigServerEvents(options.projectRoot, {
27793
+ connectRigServerEvents(options.projectRoot, {
27278
27794
  onSnapshotInvalidated: () => refreshCurrentDataScene(),
27279
27795
  onRunLogAppended: () => {
27280
27796
  const scene = store.getState().scene;
@@ -27282,9 +27798,11 @@ async function launchRigReactApp(options) {
27282
27798
  refreshCurrentDataScene();
27283
27799
  },
27284
27800
  onStatus: (status) => store.patch({ footer: { live: liveLinkLabel(status) } })
27285
- }).catch(() => null);
27286
- if (!liveEvents)
27801
+ }).then((subscription) => {
27802
+ liveEvents = subscription;
27803
+ }).catch(() => {
27287
27804
  store.patch({ footer: { live: liveLinkLabel("disconnected") } });
27805
+ });
27288
27806
  await runtime.runIntent(store.getState().intent);
27289
27807
  } catch (error) {
27290
27808
  const n = normalizeError(error);
@@ -27292,14 +27810,11 @@ async function launchRigReactApp(options) {
27292
27810
  }
27293
27811
  })();
27294
27812
  });
27295
- await new Promise((resolve31) => {
27296
- renderer?.on("destroy", () => {
27297
- clearInterval(tick);
27298
- clearInterval(poll);
27299
- liveEvents?.close();
27300
- resolve31();
27301
- });
27302
- });
27813
+ await appExit;
27814
+ clearInterval(tick);
27815
+ clearInterval(poll);
27816
+ liveEvents?.close();
27817
+ teardownRenderer();
27303
27818
  }
27304
27819
  var POLLABLE_SCENES;
27305
27820
  var init_launch = __esm(() => {
@@ -27319,7 +27834,7 @@ __export(exports_bootstrap, {
27319
27834
  buildAppLaunchOptions: () => buildAppLaunchOptions
27320
27835
  });
27321
27836
  import { existsSync as existsSync24, readFileSync as readFileSync15 } from "fs";
27322
- import { basename as basename6, resolve as resolve31 } from "path";
27837
+ import { basename as basename5, resolve as resolve31 } from "path";
27323
27838
  async function launchRigOpenTuiBootstrap(options) {
27324
27839
  const launchOptions = buildAppLaunchOptions(options);
27325
27840
  await launchOpenTuiApp(launchOptions);
@@ -27425,7 +27940,7 @@ function parsePolicyMode2(value) {
27425
27940
  function resolveProjectRoot2(input) {
27426
27941
  if (input.envProjectRoot)
27427
27942
  return resolve31(input.cwd, input.envProjectRoot);
27428
- const execName = basename6(input.execPath).toLowerCase();
27943
+ const execName = basename5(input.execPath).toLowerCase();
27429
27944
  const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve31(input.execPath, "..", "..")] : [];
27430
27945
  const candidates = [input.cwd, ...execCandidates, resolve31(input.importDir, "..")];
27431
27946
  for (const candidate of candidates) {
@@ -27488,7 +28003,7 @@ var init_bootstrap = __esm(() => {
27488
28003
  // packages/cli/bin/rig.ts
27489
28004
  init_launch_routing();
27490
28005
  import { existsSync as existsSync25, readFileSync as readFileSync16 } from "fs";
27491
- import { dirname as dirname5, resolve as resolve32 } from "path";
28006
+ import { dirname as dirname6, resolve as resolve32 } from "path";
27492
28007
  function packageVersionFrom(startDir) {
27493
28008
  let current = resolve32(startDir);
27494
28009
  while (true) {
@@ -27500,7 +28015,7 @@ function packageVersionFrom(startDir) {
27500
28015
  return pkg.version;
27501
28016
  } catch {}
27502
28017
  }
27503
- const parent = dirname5(current);
28018
+ const parent = dirname6(current);
27504
28019
  if (parent === current)
27505
28020
  return null;
27506
28021
  current = parent;