@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
@@ -91,6 +91,21 @@ async function captureConsole(fn) {
91
91
  console.error = original.error;
92
92
  }
93
93
  }
94
+ async function releaseRendererForExternalTui(ctx) {
95
+ const renderer = ctx.renderer;
96
+ if (!renderer)
97
+ return;
98
+ if (renderer.suspend) {
99
+ await renderer.suspend();
100
+ return;
101
+ }
102
+ if (renderer.destroy) {
103
+ await renderer.destroy();
104
+ }
105
+ }
106
+ async function resumeRendererAfterExternalTui(ctx) {
107
+ await ctx.renderer?.resume?.();
108
+ }
94
109
  function arrayFromPayload(value) {
95
110
  if (Array.isArray(value)) {
96
111
  return value.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
@@ -628,7 +643,8 @@ __export(exports__connection_state, {
628
643
  resolveGlobalConnectionsPath: () => resolveGlobalConnectionsPath,
629
644
  readRepoConnection: () => readRepoConnection,
630
645
  readGlobalConnections: () => readGlobalConnections,
631
- isRemoteConnectionSelected: () => isRemoteConnectionSelected
646
+ isRemoteConnectionSelected: () => isRemoteConnectionSelected,
647
+ clearRepoServerProjectRoot: () => clearRepoServerProjectRoot
632
648
  });
633
649
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
634
650
  import { homedir } from "os";
@@ -712,12 +728,28 @@ function readRepoConnection(projectRoot) {
712
728
  selected,
713
729
  project: typeof record.project === "string" ? record.project : undefined,
714
730
  linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
715
- serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
731
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
732
+ serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
733
+ serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
716
734
  };
717
735
  }
718
736
  function writeRepoConnection(projectRoot, state) {
719
737
  writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
720
738
  }
739
+ function rootAllowedForSelection(repo, connection) {
740
+ const root = repo.serverProjectRoot?.trim();
741
+ if (!root)
742
+ return;
743
+ if (connection.kind === "remote") {
744
+ if (repo.serverProjectRootAlias !== repo.selected)
745
+ return;
746
+ if (repo.serverProjectRootBaseUrl !== connection.baseUrl)
747
+ return;
748
+ } else if (repo.serverProjectRootAlias && repo.serverProjectRootAlias !== repo.selected) {
749
+ return;
750
+ }
751
+ return root;
752
+ }
721
753
  function resolveSelectedConnection(projectRoot, options = {}) {
722
754
  const repo = readRepoConnection(projectRoot);
723
755
  if (!repo)
@@ -729,13 +761,41 @@ function resolveSelectedConnection(projectRoot, options = {}) {
729
761
  if (!connection) {
730
762
  throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
731
763
  }
732
- return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
764
+ return { alias: repo.selected, connection, serverProjectRoot: rootAllowedForSelection(repo, connection) };
765
+ }
766
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot, metadata = {}) {
767
+ const repo = readRepoConnection(projectRoot);
768
+ if (!repo)
769
+ return;
770
+ let inferred = metadata;
771
+ if (!inferred.alias || !inferred.baseUrl) {
772
+ try {
773
+ const selected = resolveSelectedConnection(projectRoot);
774
+ if (selected?.connection.kind === "remote") {
775
+ inferred = {
776
+ alias: inferred.alias ?? selected.alias,
777
+ baseUrl: inferred.baseUrl ?? selected.connection.baseUrl
778
+ };
779
+ }
780
+ } catch {}
781
+ }
782
+ writeRepoConnection(projectRoot, {
783
+ ...repo,
784
+ ...metadata.project ? { project: metadata.project } : {},
785
+ serverProjectRoot,
786
+ ...inferred.alias ? { serverProjectRootAlias: inferred.alias } : {},
787
+ ...inferred.baseUrl ? { serverProjectRootBaseUrl: inferred.baseUrl.replace(/\/+$/, "") } : {}
788
+ });
733
789
  }
734
- function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
790
+ function clearRepoServerProjectRoot(projectRoot) {
735
791
  const repo = readRepoConnection(projectRoot);
736
792
  if (!repo)
737
793
  return;
738
- writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
794
+ writeRepoConnection(projectRoot, {
795
+ selected: repo.selected,
796
+ ...repo.project ? { project: repo.project } : {},
797
+ ...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
798
+ });
739
799
  }
740
800
  function isRemoteConnectionSelected(projectRoot) {
741
801
  return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
@@ -762,12 +822,16 @@ __export(exports__server_client, {
762
822
  respondRunPiExtensionUiViaServer: () => respondRunPiExtensionUiViaServer,
763
823
  resolveServerConnectionLabel: () => resolveServerConnectionLabel,
764
824
  requestServerJson: () => requestServerJson,
825
+ repairRemoteProjectRootLink: () => repairRemoteProjectRootLink,
765
826
  registerProjectViaServer: () => registerProjectViaServer,
766
827
  prepareRemoteCheckoutViaServer: () => prepareRemoteCheckoutViaServer,
767
828
  postGitHubTokenViaServer: () => postGitHubTokenViaServer,
829
+ normalizeRepoSlug: () => normalizeRepoSlug,
768
830
  listWorkspaceTasksViaServer: () => listWorkspaceTasksViaServer,
769
831
  listRunsViaServer: () => listRunsViaServer,
770
832
  listGitHubProjectsViaServer: () => listGitHubProjectsViaServer,
833
+ isRemoteProjectRootLinkError: () => isRemoteProjectRootLinkError,
834
+ inspectRemoteProjectLink: () => inspectRemoteProjectLink,
771
835
  getWorkspaceTaskViaServer: () => getWorkspaceTaskViaServer,
772
836
  getRunTimelineViaServer: () => getRunTimelineViaServer,
773
837
  getRunPiStatusViaServer: () => getRunPiStatusViaServer,
@@ -779,13 +843,15 @@ __export(exports__server_client, {
779
843
  getRunDetailsViaServer: () => getRunDetailsViaServer,
780
844
  getGitHubProjectStatusFieldViaServer: () => getGitHubProjectStatusFieldViaServer,
781
845
  getGitHubAuthStatusViaServer: () => getGitHubAuthStatusViaServer,
846
+ formatRemoteProjectLinkHint: () => formatRemoteProjectLinkHint,
782
847
  ensureTaskLabelsViaServer: () => ensureTaskLabelsViaServer,
783
848
  ensureServerForCli: () => ensureServerForCli,
849
+ ensureRemoteProjectRootLink: () => ensureRemoteProjectRootLink,
784
850
  buildRunPiEventsWebSocketUrl: () => buildRunPiEventsWebSocketUrl,
785
851
  abortRunPiViaServer: () => abortRunPiViaServer
786
852
  });
787
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
788
- import { resolve as resolve3 } from "path";
853
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
854
+ import { dirname as dirname2, isAbsolute, resolve as resolve3 } from "path";
789
855
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
790
856
  function setServerPhaseListener(listener) {
791
857
  const previous = serverPhaseListener;
@@ -831,11 +897,10 @@ function readStoredGitHubAuthToken(projectRoot) {
831
897
  const parsed = readRemoteAuthState(projectRoot);
832
898
  return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
833
899
  }
834
- function inferRemoteServerProjectRootFromAuthState(projectRoot) {
835
- const repo = readRepoConnection(projectRoot);
836
- const slug = repo?.project?.trim();
837
- if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
900
+ function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
901
+ if (!/^[^/]+\/[^/]+$/.test(repoSlug))
838
902
  return null;
903
+ const repo = readRepoConnection(projectRoot);
839
904
  const auth = readRemoteAuthState(projectRoot);
840
905
  const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
841
906
  const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
@@ -844,25 +909,426 @@ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
844
909
  const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
845
910
  if (!checkoutBaseDir)
846
911
  return null;
847
- const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
848
- writeRepoServerProjectRoot(projectRoot, inferred);
849
- return inferred;
912
+ return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
850
913
  }
851
914
  function readLocalConnectionFallbackToken(projectRoot) {
852
915
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
853
916
  }
917
+ function normalizeRepoSlug(value) {
918
+ const slug = value?.trim();
919
+ return slug && /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
920
+ }
921
+ function readProjectLinkSlug(projectRoot) {
922
+ const path = resolve3(projectRoot, ".rig", "state", "project-link.json");
923
+ if (!existsSync3(path))
924
+ return null;
925
+ try {
926
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
927
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
928
+ return null;
929
+ return normalizeRepoSlug(typeof parsed.repoSlug === "string" ? String(parsed.repoSlug) : null);
930
+ } catch {
931
+ return null;
932
+ }
933
+ }
934
+ function writeProjectLinkConnection(projectRoot, repoSlug, alias) {
935
+ const path = resolve3(projectRoot, ".rig", "state", "project-link.json");
936
+ mkdirSync3(dirname2(path), { recursive: true });
937
+ writeFileSync2(path, `${JSON.stringify({ repoSlug, connection: alias, linkedAt: new Date().toISOString() }, null, 2)}
938
+ `, "utf8");
939
+ }
940
+ function remoteLinkRepairCommand(repoSlug) {
941
+ return repoSlug ? `rig server repair-link --repo ${repoSlug}` : "rig server repair-link --repo owner/repo";
942
+ }
943
+ function isRemoteProjectRootLinkError(error) {
944
+ const text = error instanceof Error ? error.message : String(error ?? "");
945
+ return /no server-host project root link|remote project(?:-root)? link|x-rig-project-root|serverProjectRoot/i.test(text);
946
+ }
947
+ function formatRemoteProjectLinkHint(resolution) {
948
+ const repair = remoteLinkRepairCommand(resolution.repoSlug);
949
+ if (resolution.status === "auth_required") {
950
+ 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}` : ""}\`.`;
951
+ }
952
+ if (resolution.status === "missing_project") {
953
+ return `Record the repo slug, then run \`${repair}\` (or \`rig init --repo owner/repo --server remote --github-auth device\`).`;
954
+ }
955
+ if (resolution.status === "not_remote")
956
+ return "Select a remote server with `rig server use <alias>` before repairing a remote project link.";
957
+ return `Run \`${repair}\` to backfill or prepare a server-host checkout for the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}.`;
958
+ }
959
+ function remoteProjectLinkFailure(input) {
960
+ const partial = {
961
+ status: input.status,
962
+ alias: input.alias,
963
+ baseUrl: input.baseUrl,
964
+ repoSlug: input.repoSlug
965
+ };
966
+ return {
967
+ ok: false,
968
+ ...input,
969
+ hint: formatRemoteProjectLinkHint(partial),
970
+ next: remoteLinkRepairCommand(input.repoSlug)
971
+ };
972
+ }
973
+ function remoteProjectLinkSuccess(input) {
974
+ return {
975
+ ok: true,
976
+ status: input.status,
977
+ alias: input.alias,
978
+ baseUrl: input.baseUrl,
979
+ repoSlug: input.repoSlug,
980
+ serverProjectRoot: input.serverProjectRoot,
981
+ source: input.source,
982
+ prepared: input.prepared ?? input.status === "prepared",
983
+ validated: input.validated ?? false,
984
+ message: input.message ?? `Remote project link ready for ${input.repoSlug}.`,
985
+ hint: "Remote-scoped task/run/status requests will send x-rig-project-root for this server-host checkout.",
986
+ next: "rig task list"
987
+ };
988
+ }
989
+ function localCheckoutPathCandidate(projectRoot, candidate) {
990
+ try {
991
+ const local = resolve3(projectRoot);
992
+ const resolved = resolve3(candidate);
993
+ return resolved === local || resolved.startsWith(`${local}/`);
994
+ } catch {
995
+ return false;
996
+ }
997
+ }
998
+ async function requestRemoteProjectLinkJson(baseUrl, pathname, authToken, init = {}) {
999
+ const requestUrl = new URL(`${baseUrl}${pathname}`);
1000
+ if (authToken && queryAuthFallbackEnabled())
1001
+ requestUrl.searchParams.set("rt", authToken);
1002
+ const response = await fetch(requestUrl, {
1003
+ ...init,
1004
+ headers: mergeHeaders(init.headers, authToken)
1005
+ });
1006
+ const text = await response.text();
1007
+ const payload = text.trim().length > 0 ? (() => {
1008
+ try {
1009
+ return JSON.parse(text);
1010
+ } catch {
1011
+ return null;
1012
+ }
1013
+ })() : null;
1014
+ return { ok: response.ok, status: response.status, payload, text };
1015
+ }
1016
+ function payloadError(payload, fallback) {
1017
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
1018
+ const record = payload;
1019
+ const value = record.error ?? record.message ?? record.reason;
1020
+ if (typeof value === "string" && value.trim())
1021
+ return value.trim();
1022
+ }
1023
+ return fallback;
1024
+ }
1025
+ function checkoutPathsFromProjectPayload(payload) {
1026
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
1027
+ return [];
1028
+ const project = payload.project;
1029
+ if (!project || typeof project !== "object" || Array.isArray(project))
1030
+ return [];
1031
+ const checkouts = project.checkouts;
1032
+ if (!Array.isArray(checkouts))
1033
+ return [];
1034
+ return [...checkouts].reverse().flatMap((entry) => {
1035
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
1036
+ return [];
1037
+ const path = entry.path;
1038
+ return typeof path === "string" && path.trim() ? [path.trim()] : [];
1039
+ });
1040
+ }
1041
+ function checkoutPathFromPreparePayload(payload) {
1042
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
1043
+ return null;
1044
+ const checkout = payload.checkout;
1045
+ if (!checkout || typeof checkout !== "object" || Array.isArray(checkout))
1046
+ return null;
1047
+ const path = checkout.path;
1048
+ return typeof path === "string" && path.trim() ? path.trim() : null;
1049
+ }
1050
+ async function validateAndPersistRemoteRoot(input) {
1051
+ const candidate = input.candidate.trim();
1052
+ if (!candidate || !isAbsolute(candidate)) {
1053
+ return remoteProjectLinkFailure({
1054
+ status: "invalid_root",
1055
+ alias: input.alias,
1056
+ baseUrl: input.baseUrl,
1057
+ repoSlug: input.repoSlug,
1058
+ message: `Remote project root candidate is not an absolute server-host path: ${candidate || "(empty)"}.`
1059
+ });
1060
+ }
1061
+ if (localCheckoutPathCandidate(input.projectRoot, candidate)) {
1062
+ return remoteProjectLinkFailure({
1063
+ status: "invalid_root",
1064
+ alias: input.alias,
1065
+ baseUrl: input.baseUrl,
1066
+ repoSlug: input.repoSlug,
1067
+ message: `Refusing to use local checkout path ${candidate} as a remote server project root.`
1068
+ });
1069
+ }
1070
+ let response;
1071
+ try {
1072
+ response = await requestRemoteProjectLinkJson(input.baseUrl, "/api/server/project-root", input.authToken, {
1073
+ method: "POST",
1074
+ headers: { "content-type": "application/json" },
1075
+ body: JSON.stringify({ projectRoot: candidate })
1076
+ });
1077
+ } catch (error) {
1078
+ return remoteProjectLinkFailure({
1079
+ status: "error",
1080
+ alias: input.alias,
1081
+ baseUrl: input.baseUrl,
1082
+ repoSlug: input.repoSlug,
1083
+ message: `Could not validate remote project root ${candidate}: ${error instanceof Error ? error.message : String(error)}`
1084
+ });
1085
+ }
1086
+ if (response.status === 401 || response.status === 403) {
1087
+ return remoteProjectLinkFailure({
1088
+ status: "auth_required",
1089
+ alias: input.alias,
1090
+ baseUrl: input.baseUrl,
1091
+ repoSlug: input.repoSlug,
1092
+ statusCode: response.status,
1093
+ message: `Remote server ${input.alias} requires authentication before validating project root ${candidate}.`
1094
+ });
1095
+ }
1096
+ if (!response.ok) {
1097
+ return remoteProjectLinkFailure({
1098
+ status: "invalid_root",
1099
+ alias: input.alias,
1100
+ baseUrl: input.baseUrl,
1101
+ repoSlug: input.repoSlug,
1102
+ statusCode: response.status,
1103
+ message: `Remote server rejected project root ${candidate} (${response.status}): ${payloadError(response.payload, response.text || "project-root validation failed")}`
1104
+ });
1105
+ }
1106
+ const record = response.payload && typeof response.payload === "object" && !Array.isArray(response.payload) ? response.payload : {};
1107
+ if (record.ok !== true) {
1108
+ return remoteProjectLinkFailure({
1109
+ status: "invalid_root",
1110
+ alias: input.alias,
1111
+ baseUrl: input.baseUrl,
1112
+ repoSlug: input.repoSlug,
1113
+ message: `Remote server did not accept project root ${candidate}: ${payloadError(response.payload, "project-root validation failed")}`
1114
+ });
1115
+ }
1116
+ const accepted = typeof record.projectRoot === "string" && record.projectRoot.trim() ? record.projectRoot.trim() : typeof record.requestedProjectRoot === "string" && record.requestedProjectRoot.trim() ? record.requestedProjectRoot.trim() : candidate;
1117
+ writeRepoServerProjectRoot(input.projectRoot, accepted, { alias: input.alias, baseUrl: input.baseUrl, project: input.repoSlug });
1118
+ writeProjectLinkConnection(input.projectRoot, input.repoSlug, input.alias);
1119
+ const status = input.status ?? (input.prepared ? "prepared" : "backfilled");
1120
+ return remoteProjectLinkSuccess({
1121
+ status,
1122
+ alias: input.alias,
1123
+ baseUrl: input.baseUrl,
1124
+ repoSlug: input.repoSlug,
1125
+ serverProjectRoot: accepted,
1126
+ source: input.source,
1127
+ prepared: input.prepared,
1128
+ validated: true,
1129
+ 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}.`
1130
+ });
1131
+ }
1132
+ async function ensureRemoteProjectRootLink(projectRoot, options = {}) {
1133
+ let selected;
1134
+ try {
1135
+ selected = resolveSelectedConnection(projectRoot);
1136
+ } catch (error) {
1137
+ return remoteProjectLinkFailure({ status: "error", message: error instanceof Error ? error.message : String(error) });
1138
+ }
1139
+ if (!selected || selected.connection.kind !== "remote") {
1140
+ return remoteProjectLinkFailure({ status: "not_remote", message: "No remote Rig server is selected for this repo." });
1141
+ }
1142
+ const repo = readRepoConnection(projectRoot);
1143
+ const explicitRepoRequested = options.repoSlug !== undefined;
1144
+ const explicitRepoSlug = explicitRepoRequested ? normalizeRepoSlug(options.repoSlug) : null;
1145
+ const repoProjectSlug = normalizeRepoSlug(repo?.project);
1146
+ const repoSlug = explicitRepoSlug ?? repoProjectSlug ?? readProjectLinkSlug(projectRoot);
1147
+ const alias = selected.alias;
1148
+ const baseUrl = selected.connection.baseUrl;
1149
+ const authToken = options.authToken === undefined ? readGitHubBearerTokenForRemote(projectRoot) : options.authToken;
1150
+ const mode = options.mode ?? "backfill-only";
1151
+ if (explicitRepoRequested && !explicitRepoSlug) {
1152
+ return remoteProjectLinkFailure({
1153
+ status: "missing_project",
1154
+ alias,
1155
+ baseUrl,
1156
+ message: `Invalid --repo value ${JSON.stringify(options.repoSlug)}. Expected owner/repo.`
1157
+ });
1158
+ }
1159
+ if (!repoSlug) {
1160
+ return remoteProjectLinkFailure({
1161
+ status: "missing_project",
1162
+ alias,
1163
+ baseUrl,
1164
+ message: `Remote server ${alias} is selected, but this checkout has no GitHub repo slug recorded.`
1165
+ });
1166
+ }
1167
+ const skippedCandidates = [];
1168
+ const storedRoot = selected.serverProjectRoot?.trim() || null;
1169
+ const storedRootMatchesResolvedRepo = repoProjectSlug === repoSlug;
1170
+ if (storedRoot && storedRootMatchesResolvedRepo) {
1171
+ if (localCheckoutPathCandidate(projectRoot, storedRoot)) {
1172
+ clearRepoServerProjectRoot(projectRoot);
1173
+ skippedCandidates.push(storedRoot);
1174
+ } else {
1175
+ const storedResult = await validateAndPersistRemoteRoot({
1176
+ projectRoot,
1177
+ alias,
1178
+ baseUrl,
1179
+ authToken,
1180
+ repoSlug,
1181
+ candidate: storedRoot,
1182
+ source: "stored",
1183
+ status: "ready"
1184
+ });
1185
+ if (storedResult.ok || storedResult.status === "auth_required" || storedResult.status === "error")
1186
+ return storedResult;
1187
+ clearRepoServerProjectRoot(projectRoot);
1188
+ skippedCandidates.push(storedRoot);
1189
+ }
1190
+ } else if (storedRoot) {
1191
+ skippedCandidates.push(storedRoot);
1192
+ }
1193
+ const authCandidate = inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug);
1194
+ if (authCandidate) {
1195
+ const authResult = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: authCandidate, source: "auth-state" });
1196
+ if (authResult.ok || authResult.status === "auth_required")
1197
+ return authResult;
1198
+ }
1199
+ let registryResponse;
1200
+ try {
1201
+ registryResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}`, authToken);
1202
+ } catch (error) {
1203
+ return remoteProjectLinkFailure({
1204
+ status: "error",
1205
+ alias,
1206
+ baseUrl,
1207
+ repoSlug,
1208
+ message: `Could not read remote project registry for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
1209
+ });
1210
+ }
1211
+ if (registryResponse.status === 401 || registryResponse.status === 403) {
1212
+ return remoteProjectLinkFailure({
1213
+ status: "auth_required",
1214
+ alias,
1215
+ baseUrl,
1216
+ repoSlug,
1217
+ statusCode: registryResponse.status,
1218
+ message: `Remote server ${alias} requires authentication before reading the ${repoSlug} project registry.`
1219
+ });
1220
+ }
1221
+ if (registryResponse.ok) {
1222
+ const candidates = checkoutPathsFromProjectPayload(registryResponse.payload);
1223
+ for (const candidate of candidates) {
1224
+ const result = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate, source: "registry" });
1225
+ if (result.ok || result.status === "auth_required")
1226
+ return result;
1227
+ skippedCandidates.push(candidate);
1228
+ }
1229
+ if (mode === "backfill-only") {
1230
+ return remoteProjectLinkFailure({
1231
+ status: candidates.length > 0 ? "invalid_root" : "no_server_checkout",
1232
+ alias,
1233
+ baseUrl,
1234
+ repoSlug,
1235
+ 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.`,
1236
+ skippedCandidates
1237
+ });
1238
+ }
1239
+ } else if (registryResponse.status === 404) {
1240
+ if (mode === "backfill-only") {
1241
+ return remoteProjectLinkFailure({
1242
+ status: "project_not_registered",
1243
+ alias,
1244
+ baseUrl,
1245
+ repoSlug,
1246
+ statusCode: registryResponse.status,
1247
+ message: `Remote server ${alias} has no registered project record for ${repoSlug}.`
1248
+ });
1249
+ }
1250
+ } else {
1251
+ return remoteProjectLinkFailure({
1252
+ status: "error",
1253
+ alias,
1254
+ baseUrl,
1255
+ repoSlug,
1256
+ statusCode: registryResponse.status,
1257
+ message: `Could not backfill ${repoSlug} from remote registry (${registryResponse.status}): ${payloadError(registryResponse.payload, registryResponse.text || "registry lookup failed")}`,
1258
+ skippedCandidates
1259
+ });
1260
+ }
1261
+ let prepareResponse;
1262
+ try {
1263
+ prepareResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}/prepare-checkout`, authToken, {
1264
+ method: "POST",
1265
+ headers: { "content-type": "application/json" },
1266
+ body: JSON.stringify({ checkout: { kind: "managed-clone" }, repoUrl: `https://github.com/${repoSlug}.git` })
1267
+ });
1268
+ } catch (error) {
1269
+ return remoteProjectLinkFailure({
1270
+ status: "error",
1271
+ alias,
1272
+ baseUrl,
1273
+ repoSlug,
1274
+ message: `Could not prepare remote checkout for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
1275
+ });
1276
+ }
1277
+ if (prepareResponse.status === 401 || prepareResponse.status === 403) {
1278
+ return remoteProjectLinkFailure({
1279
+ status: "auth_required",
1280
+ alias,
1281
+ baseUrl,
1282
+ repoSlug,
1283
+ statusCode: prepareResponse.status,
1284
+ message: `Remote server ${alias} requires authentication before preparing ${repoSlug}.`
1285
+ });
1286
+ }
1287
+ if (!prepareResponse.ok) {
1288
+ return remoteProjectLinkFailure({
1289
+ status: "error",
1290
+ alias,
1291
+ baseUrl,
1292
+ repoSlug,
1293
+ statusCode: prepareResponse.status,
1294
+ message: `Remote checkout prepare failed for ${repoSlug} (${prepareResponse.status}): ${payloadError(prepareResponse.payload, prepareResponse.text || "prepare-checkout failed")}`,
1295
+ skippedCandidates
1296
+ });
1297
+ }
1298
+ const preparedPath = checkoutPathFromPreparePayload(prepareResponse.payload);
1299
+ if (!preparedPath) {
1300
+ return remoteProjectLinkFailure({
1301
+ status: "invalid_root",
1302
+ alias,
1303
+ baseUrl,
1304
+ repoSlug,
1305
+ message: `Remote checkout prepare for ${repoSlug} did not return a checkout.path.`,
1306
+ skippedCandidates
1307
+ });
1308
+ }
1309
+ return validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: preparedPath, source: "prepare", prepared: true });
1310
+ }
1311
+ async function inspectRemoteProjectLink(projectRoot) {
1312
+ return ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only" });
1313
+ }
1314
+ async function repairRemoteProjectRootLink(context, options = {}) {
1315
+ const resolution = await ensureRemoteProjectRootLink(context.projectRoot, { mode: options.mode ?? "prepare-if-missing", repoSlug: options.repoSlug });
1316
+ if (!resolution.ok)
1317
+ throw new CliError(resolution.message, 1, { hint: resolution.hint });
1318
+ return resolution;
1319
+ }
854
1320
  async function ensureServerForCli(projectRoot) {
855
1321
  try {
856
1322
  const selected = resolveSelectedConnection(projectRoot);
857
1323
  if (selected?.connection.kind === "remote") {
858
1324
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
859
1325
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
860
- const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
1326
+ const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
861
1327
  return {
862
1328
  baseUrl: selected.connection.baseUrl,
863
1329
  authToken,
864
1330
  connectionKind: "remote",
865
- serverProjectRoot
1331
+ serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
866
1332
  };
867
1333
  }
868
1334
  reportServerPhase("Starting local Rig server\u2026");
@@ -880,32 +1346,6 @@ async function ensureServerForCli(projectRoot) {
880
1346
  throw error;
881
1347
  }
882
1348
  }
883
- async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
884
- const repo = readRepoConnection(projectRoot);
885
- const slug = repo?.project?.trim();
886
- if (!slug)
887
- return null;
888
- try {
889
- const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
890
- if (authToken && queryAuthFallbackEnabled())
891
- url.searchParams.set("rt", authToken);
892
- const response = await fetch(url, {
893
- headers: mergeHeaders(undefined, authToken)
894
- });
895
- if (!response.ok)
896
- return null;
897
- const payload = await response.json();
898
- const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
899
- const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
900
- const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
901
- const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
902
- if (path)
903
- writeRepoServerProjectRoot(projectRoot, path);
904
- return path;
905
- } catch {
906
- return null;
907
- }
908
- }
909
1349
  function appendTaskFilterParams(url, filters) {
910
1350
  if (filters.assignee)
911
1351
  url.searchParams.set("assignee", filters.assignee);
@@ -1006,13 +1446,20 @@ function canUseRemoteWithoutProjectRoot(pathname) {
1006
1446
  async function requestServerJson(context, pathname, init = {}) {
1007
1447
  const server = await ensureServerForCli(context.projectRoot);
1008
1448
  const requestUrl = new URL(`${server.baseUrl}${pathname}`);
1009
- if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
1010
- const repo = readRepoConnection(context.projectRoot);
1011
- 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." });
1449
+ let scopedServerProjectRoot = server.serverProjectRoot;
1450
+ if (server.connectionKind === "remote" && !scopedServerProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
1451
+ const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: server.authToken });
1452
+ if (link.ok && link.serverProjectRoot) {
1453
+ scopedServerProjectRoot = link.serverProjectRoot;
1454
+ } else {
1455
+ const repo = readRepoConnection(context.projectRoot);
1456
+ const target = link.alias && link.baseUrl ? `${link.alias} (${link.baseUrl})` : server.baseUrl;
1457
+ 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 });
1458
+ }
1012
1459
  }
1013
1460
  const headers = mergeHeaders(init.headers, server.authToken);
1014
- if (server.serverProjectRoot)
1015
- headers.set("x-rig-project-root", server.serverProjectRoot);
1461
+ if (scopedServerProjectRoot)
1462
+ headers.set("x-rig-project-root", scopedServerProjectRoot);
1016
1463
  if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
1017
1464
  requestUrl.searchParams.set("rt", server.authToken);
1018
1465
  }
@@ -2057,12 +2504,13 @@ var init__help_catalog = __esm(() => {
2057
2504
  {
2058
2505
  name: "server",
2059
2506
  summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
2060
- usage: ["rig server <status|list|add|use|start> [options]"],
2507
+ usage: ["rig server <status|list|add|use|repair-link|start> [options]"],
2061
2508
  commands: [
2062
- { command: "status", description: "Show the selected server for this repo.", primary: true },
2509
+ { command: "status", description: "Show the selected server and remote project-root link for this repo.", primary: true },
2063
2510
  { command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
2064
2511
  { command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
2065
2512
  { command: "use <alias>", description: "Select a saved remote server alias.", primary: true },
2513
+ { command: "repair-link [--prepare|--backfill-only] [--repo owner/repo]", description: "Backfill or prepare the selected remote's server-host checkout link.", primary: true },
2066
2514
  { command: "list", description: "List saved local/remote server aliases.", primary: true },
2067
2515
  { command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
2068
2516
  ],
@@ -2070,6 +2518,7 @@ var init__help_catalog = __esm(() => {
2070
2518
  "rig server status",
2071
2519
  "rig server add prod https://where.rig-does.work",
2072
2520
  "rig server use prod",
2521
+ "rig server repair-link --repo owner/repo",
2073
2522
  "rig server use local",
2074
2523
  "rig server start --port 3773"
2075
2524
  ],
@@ -2780,7 +3229,7 @@ function resolveControlPlaneDefinitionRoot(projectRoot) {
2780
3229
  var init__paths = () => {};
2781
3230
 
2782
3231
  // packages/cli/src/report-bug.ts
2783
- import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
3232
+ import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
2784
3233
  import { basename as basename2, extname, join, resolve as resolve8 } from "path";
2785
3234
  function slugifyBugTitle(value) {
2786
3235
  const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/g, "");
@@ -2819,8 +3268,8 @@ function createBugReportFiles(input) {
2819
3268
  }
2820
3269
  rmSync2(reportDir, { recursive: true, force: true });
2821
3270
  }
2822
- mkdirSync3(assetDir, { recursive: true });
2823
- mkdirSync3(screenshotDir, { recursive: true });
3271
+ mkdirSync4(assetDir, { recursive: true });
3272
+ mkdirSync4(screenshotDir, { recursive: true });
2824
3273
  const copiedScreenshots = copyEvidenceFiles(input.projectRoot, screenshotDir, input.screenshots ?? [], "screenshot");
2825
3274
  const copiedAssets = copyEvidenceFiles(input.projectRoot, assetDir, input.assets ?? [], "asset");
2826
3275
  const manifestAssets = [
@@ -2850,15 +3299,15 @@ function createBugReportFiles(input) {
2850
3299
  platform: process.platform,
2851
3300
  arch: process.arch
2852
3301
  };
2853
- writeFileSync2(join(assetDir, "README.md"), buildAssetReadme(input.title), "utf8");
2854
- writeFileSync2(join(screenshotDir, "README.md"), buildScreenshotReadme(input.title), "utf8");
3302
+ writeFileSync3(join(assetDir, "README.md"), buildAssetReadme(input.title), "utf8");
3303
+ writeFileSync3(join(screenshotDir, "README.md"), buildScreenshotReadme(input.title), "utf8");
2855
3304
  if (browserPath && browser) {
2856
- writeFileSync2(browserPath, `${JSON.stringify(browser, null, 2)}
3305
+ writeFileSync3(browserPath, `${JSON.stringify(browser, null, 2)}
2857
3306
  `, "utf8");
2858
3307
  }
2859
- writeFileSync2(manifestPath, `${JSON.stringify(manifest, null, 2)}
3308
+ writeFileSync3(manifestPath, `${JSON.stringify(manifest, null, 2)}
2860
3309
  `, "utf8");
2861
- writeFileSync2(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
3310
+ writeFileSync3(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
2862
3311
  return {
2863
3312
  slug,
2864
3313
  reportDir,
@@ -3035,7 +3484,7 @@ function formatAssetMarkdown(name, path) {
3035
3484
  var init_report_bug = () => {};
3036
3485
 
3037
3486
  // packages/cli/src/commands/task-report-bug.ts
3038
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
3487
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
3039
3488
  import { resolve as resolve9 } from "path";
3040
3489
  import pc2 from "picocolors";
3041
3490
  import {
@@ -3397,7 +3846,7 @@ function appendTaskConfigEntryPreservingText(taskConfigPath, issueId, entry) {
3397
3846
  `).map((line) => ` ${line}`).join(`
3398
3847
  `);
3399
3848
  if (!trimmed || trimmed === "{}") {
3400
- writeFileSync3(taskConfigPath, `{
3849
+ writeFileSync4(taskConfigPath, `{
3401
3850
  ${serializedEntry}
3402
3851
  }
3403
3852
  `, "utf8");
@@ -3413,7 +3862,7 @@ ${serializedEntry}
3413
3862
  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." });
3414
3863
  }
3415
3864
  const comma = before.trim() === "{" ? "" : ",";
3416
- writeFileSync3(taskConfigPath, `${before}${comma}
3865
+ writeFileSync4(taskConfigPath, `${before}${comma}
3417
3866
  ${serializedEntry}
3418
3867
  }
3419
3868
  `, "utf8");
@@ -3798,7 +4247,7 @@ var init_task_report_bug = __esm(() => {
3798
4247
  });
3799
4248
 
3800
4249
  // packages/cli/src/commands/browser.ts
3801
- import { mkdirSync as mkdirSync4, rmSync as rmSync3 } from "fs";
4250
+ import { mkdirSync as mkdirSync5, rmSync as rmSync3 } from "fs";
3802
4251
  import { resolve as resolve10 } from "path";
3803
4252
  import { spawn } from "child_process";
3804
4253
  import { emitKeypressEvents } from "readline";
@@ -4376,7 +4825,7 @@ function readBrowserDemoCommandLine(command) {
4376
4825
  }
4377
4826
  async function launchBrowserDemo(runtime) {
4378
4827
  rmSync3(resolve10(runtime.browserDir, runtime.stateDir), { recursive: true, force: true });
4379
- mkdirSync4(resolve10(runtime.browserDir, runtime.stateDir), { recursive: true });
4828
+ mkdirSync5(resolve10(runtime.browserDir, runtime.stateDir), { recursive: true });
4380
4829
  const launcherPath = resolve10(runtime.browserDir, "scripts/electron-launcher.mjs");
4381
4830
  const launcher = await import(pathToFileURL(launcherPath).href);
4382
4831
  const electronPath = await launcher.resolveElectronPath();
@@ -4725,7 +5174,7 @@ var init_profile_and_review = __esm(() => {
4725
5174
  });
4726
5175
 
4727
5176
  // packages/cli/src/commands/_policy.ts
4728
- import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
5177
+ import { appendFileSync, mkdirSync as mkdirSync6 } from "fs";
4729
5178
  import { resolve as resolve11 } from "path";
4730
5179
  import { evaluate as evaluate2, loadPolicy as loadPolicy2, resolveAction as resolveAction2 } from "@rig/runtime/control-plane/runtime/guard";
4731
5180
  import { resolveHarnessPaths } from "@rig/runtime/control-plane/native/utils";
@@ -4742,7 +5191,7 @@ function appendControlledBashAudit(context, mode, command, args, matchedRuleIds,
4742
5191
  }
4743
5192
  const logsDir = resolveHarnessPaths(context.projectRoot).logsDir;
4744
5193
  const logFile = resolve11(logsDir, "controlled-bash.jsonl");
4745
- mkdirSync5(logsDir, { recursive: true });
5194
+ mkdirSync6(logsDir, { recursive: true });
4746
5195
  const payload = {
4747
5196
  timestamp: new Date().toISOString(),
4748
5197
  mode,
@@ -5036,9 +5485,9 @@ var exports_pi = {};
5036
5485
  __export(exports_pi, {
5037
5486
  executePi: () => executePi
5038
5487
  });
5039
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
5488
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
5040
5489
  import { homedir as homedir4 } from "os";
5041
- import { dirname as dirname2, resolve as resolve13 } from "path";
5490
+ import { dirname as dirname3, resolve as resolve13 } from "path";
5042
5491
  function settingsPath(root) {
5043
5492
  return resolve13(root, ".pi", "settings.json");
5044
5493
  }
@@ -5063,8 +5512,8 @@ function packageKey(entry) {
5063
5512
  return JSON.stringify(entry);
5064
5513
  }
5065
5514
  function writeSettings(path, settings) {
5066
- mkdirSync6(dirname2(path), { recursive: true });
5067
- writeFileSync4(path, `${JSON.stringify(settings, null, 2)}
5515
+ mkdirSync7(dirname3(path), { recursive: true });
5516
+ writeFileSync5(path, `${JSON.stringify(settings, null, 2)}
5068
5517
  `, "utf-8");
5069
5518
  }
5070
5519
  async function searchNpmForPiExtensions(term) {
@@ -5247,6 +5696,14 @@ function permissionAllowsPr2(payload) {
5247
5696
  function isNotFoundError(error) {
5248
5697
  return /\b(404|not found)\b/i.test(message(error));
5249
5698
  }
5699
+ function isLoopbackBaseUrl(baseUrl) {
5700
+ try {
5701
+ const host = new URL(baseUrl).hostname.toLowerCase();
5702
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
5703
+ } catch {
5704
+ return false;
5705
+ }
5706
+ }
5250
5707
  function projectCheckoutReady(payload) {
5251
5708
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
5252
5709
  return null;
@@ -5301,25 +5758,37 @@ async function runFastTaskRunPreflight(context, options = {}) {
5301
5758
  }
5302
5759
  const repo = readRepoConnection(context.projectRoot);
5303
5760
  const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
5304
- 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>`."));
5761
+ let remoteRootBlocked = false;
5762
+ 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>`."));
5763
+ if (selectedServer?.connectionKind === "remote" && repo?.project && !selectedServer.serverProjectRoot) {
5764
+ const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: selectedServer.authToken });
5765
+ const loopbackDevRemote = !selectedServer.authToken && isLoopbackBaseUrl(selectedServer.baseUrl);
5766
+ 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));
5767
+ remoteRootBlocked = !link.ok && !loopbackDevRemote;
5768
+ }
5305
5769
  try {
5306
5770
  const auth = await request("/api/github/auth/status");
5307
5771
  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>`."));
5308
5772
  } catch (error) {
5309
5773
  checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
5310
5774
  }
5311
- try {
5312
- const projection = await request("/api/workspace/task-projection");
5313
- checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
5314
- } catch (error) {
5315
- checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
5316
- }
5317
- try {
5318
- const permissions = await request("/api/github/repo/permissions");
5319
- const allowed = permissionAllowsPr2(permissions);
5320
- 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."));
5321
- } catch (error) {
5322
- checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
5775
+ if (!remoteRootBlocked) {
5776
+ try {
5777
+ const projection = await request("/api/workspace/task-projection");
5778
+ checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
5779
+ } catch (error) {
5780
+ checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
5781
+ }
5782
+ try {
5783
+ const permissions = await request("/api/github/repo/permissions");
5784
+ const allowed = permissionAllowsPr2(permissions);
5785
+ 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."));
5786
+ } catch (error) {
5787
+ checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
5788
+ }
5789
+ } else {
5790
+ checks.push(preflightCheck("task-projection", "task projection ready", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
5791
+ 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."));
5323
5792
  }
5324
5793
  if (repo?.project) {
5325
5794
  try {
@@ -5331,19 +5800,24 @@ async function runFastTaskRunPreflight(context, options = {}) {
5331
5800
  }
5332
5801
  }
5333
5802
  if (taskId) {
5334
- try {
5335
- const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
5336
- const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
5337
- 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."));
5338
- } catch (error) {
5339
- checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
5340
- }
5341
- try {
5342
- const runs = await request("/api/runs?limit=200");
5343
- const duplicate = activeDuplicateRun(runs, taskId);
5344
- 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));
5345
- } catch (error) {
5346
- 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."));
5803
+ if (!remoteRootBlocked) {
5804
+ try {
5805
+ const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
5806
+ const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
5807
+ 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."));
5808
+ } catch (error) {
5809
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
5810
+ }
5811
+ try {
5812
+ const runs = await request("/api/runs?limit=200");
5813
+ const duplicate = activeDuplicateRun(runs, taskId);
5814
+ 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));
5815
+ } catch (error) {
5816
+ 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."));
5817
+ }
5818
+ } else {
5819
+ checks.push(preflightCheck("issue", "task/issue accessible", "fail", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
5820
+ 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."));
5347
5821
  }
5348
5822
  }
5349
5823
  if ((options.runtimeAdapter ?? "pi") === "pi") {
@@ -5824,7 +6298,7 @@ import {
5824
6298
  chmodSync,
5825
6299
  copyFileSync as copyFileSync2,
5826
6300
  existsSync as existsSync11,
5827
- mkdirSync as mkdirSync7,
6301
+ mkdirSync as mkdirSync8,
5828
6302
  readdirSync,
5829
6303
  readlinkSync,
5830
6304
  rmSync as rmSync4,
@@ -5929,7 +6403,7 @@ async function executeDist(context, args) {
5929
6403
  requireNoExtraArgs(pending, "rig dist install [--scope user|system] [--path <dir>]");
5930
6404
  const scope = parseInstallScope(scopeResult.value);
5931
6405
  const installDir = resolveInstallDir(scope, pathResult.value);
5932
- mkdirSync7(installDir, { recursive: true });
6406
+ mkdirSync8(installDir, { recursive: true });
5933
6407
  let source = await findLatestDistBinary(context.projectRoot);
5934
6408
  let buildDir = null;
5935
6409
  if (!source) {
@@ -5984,7 +6458,7 @@ async function executeDist(context, args) {
5984
6458
  const fp = await computeRuntimeImageFingerprint(context.projectRoot);
5985
6459
  const currentId = computeRuntimeImageId(fp);
5986
6460
  const imagesDir = resolve16(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
5987
- mkdirSync7(imagesDir, { recursive: true });
6461
+ mkdirSync8(imagesDir, { recursive: true });
5988
6462
  let pruned = 0;
5989
6463
  for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
5990
6464
  if (entry.isDirectory() && entry.name !== currentId) {
@@ -5996,9 +6470,9 @@ async function executeDist(context, args) {
5996
6470
  console.log(`Pruned ${pruned} stale image(s).`);
5997
6471
  }
5998
6472
  const imageDir = resolve16(imagesDir, currentId);
5999
- mkdirSync7(resolve16(imageDir, "bin/hooks"), { recursive: true });
6000
- mkdirSync7(resolve16(imageDir, "bin/plugins"), { recursive: true });
6001
- mkdirSync7(resolve16(imageDir, "bin/validators"), { recursive: true });
6473
+ mkdirSync8(resolve16(imageDir, "bin/hooks"), { recursive: true });
6474
+ mkdirSync8(resolve16(imageDir, "bin/plugins"), { recursive: true });
6475
+ mkdirSync8(resolve16(imageDir, "bin/validators"), { recursive: true });
6002
6476
  const hookNames = [
6003
6477
  "scope-guard",
6004
6478
  "import-guard",
@@ -6023,8 +6497,8 @@ async function executeDist(context, args) {
6023
6497
  const binPluginsDir = resolve16(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
6024
6498
  const validatorsRoot = resolve16(hostProjectRoot, "packages/runtime/src/control-plane/validators");
6025
6499
  const binValidatorsDir = resolve16(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
6026
- mkdirSync7(binPluginsDir, { recursive: true });
6027
- mkdirSync7(binValidatorsDir, { recursive: true });
6500
+ mkdirSync8(binPluginsDir, { recursive: true });
6501
+ mkdirSync8(binValidatorsDir, { recursive: true });
6028
6502
  if (existsSync11(pluginsDir)) {
6029
6503
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
6030
6504
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
@@ -6463,16 +6937,19 @@ function formatConnectionList(connections) {
6463
6937
  return [formatSection("Rig servers", `${rows.length} available`), `${pc4.bold(pad("ALIAS", aliasWidth))} ${pc4.bold("KIND")} ${pc4.bold("TARGET")}`, ...lines, "", ...formatNextSteps(["Select one: `rig server use <alias|local>`"])].join(`
6464
6938
  `);
6465
6939
  }
6466
- function formatConnectionStatus(selected, connections) {
6940
+ function formatConnectionStatus(selected, connections, repo, remoteProjectLink) {
6467
6941
  const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
6468
6942
  const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
6943
+ 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";
6469
6944
  return [
6470
6945
  formatSection("Rig server", "selected for this repo"),
6471
6946
  `${themeFaint("\u2502")} ${themeDim("selected ")} ${pc4.bold(selected)}`,
6472
6947
  `${themeFaint("\u2502")} ${themeDim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
6473
6948
  `${themeFaint("\u2502")} ${themeDim("target ")} ${target ?? "not configured"}`,
6949
+ ...repo?.project ? [`${themeFaint("\u2502")} ${themeDim("project ")} ${repo.project}`] : [],
6950
+ ...connection?.kind === "remote" ? [`${themeFaint("\u2502")} ${themeDim("root ")} ${rootLabel}`] : [],
6474
6951
  "",
6475
- ...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
6952
+ ...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`"])
6476
6953
  ].join(`
6477
6954
  `);
6478
6955
  }
@@ -6763,7 +7240,7 @@ var init_inbox = __esm(() => {
6763
7240
 
6764
7241
  // packages/cli/src/commands/_snapshot-upload.ts
6765
7242
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
6766
- import { dirname as dirname3, resolve as resolve17, relative, sep } from "path";
7243
+ import { dirname as dirname4, resolve as resolve17, relative, sep } from "path";
6767
7244
  function toPosixPath(path) {
6768
7245
  return path.split(sep).join("/");
6769
7246
  }
@@ -6864,7 +7341,7 @@ __export(exports_init, {
6864
7341
  DEMO_TASKS_RELATIVE_DIR: () => DEMO_TASKS_RELATIVE_DIR,
6865
7342
  DEMO_TASKS: () => DEMO_TASKS
6866
7343
  });
6867
- import { appendFileSync as appendFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
7344
+ import { appendFileSync as appendFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
6868
7345
  import { spawnSync } from "child_process";
6869
7346
  import { basename as basename3, resolve as resolve18 } from "path";
6870
7347
  import { buildRigInitConfigSource } from "@rig/core";
@@ -6888,14 +7365,14 @@ function parseRepoSlug(value) {
6888
7365
  }
6889
7366
  function ensureRigPrivateDirs(projectRoot) {
6890
7367
  const rigDir = resolve18(projectRoot, ".rig");
6891
- mkdirSync8(resolve18(rigDir, "state"), { recursive: true });
6892
- mkdirSync8(resolve18(rigDir, "logs"), { recursive: true });
6893
- mkdirSync8(resolve18(rigDir, "runs"), { recursive: true });
6894
- mkdirSync8(resolve18(rigDir, "tmp"), { recursive: true });
6895
- mkdirSync8(resolve18(projectRoot, "artifacts"), { recursive: true });
7368
+ mkdirSync9(resolve18(rigDir, "state"), { recursive: true });
7369
+ mkdirSync9(resolve18(rigDir, "logs"), { recursive: true });
7370
+ mkdirSync9(resolve18(rigDir, "runs"), { recursive: true });
7371
+ mkdirSync9(resolve18(rigDir, "tmp"), { recursive: true });
7372
+ mkdirSync9(resolve18(projectRoot, "artifacts"), { recursive: true });
6896
7373
  const taskConfigPath = resolve18(rigDir, "task-config.json");
6897
7374
  if (!existsSync12(taskConfigPath))
6898
- writeFileSync5(taskConfigPath, `{}
7375
+ writeFileSync6(taskConfigPath, `{}
6899
7376
  `, "utf-8");
6900
7377
  }
6901
7378
  function ensureGitignoreEntries(projectRoot) {
@@ -6923,7 +7400,7 @@ function ensureRigConfigPackageDependencies(projectRoot) {
6923
7400
  ...existsSync12(path) ? existing : { name: "rig-project", private: true },
6924
7401
  devDependencies
6925
7402
  };
6926
- writeFileSync5(path, `${JSON.stringify(next, null, 2)}
7403
+ writeFileSync6(path, `${JSON.stringify(next, null, 2)}
6927
7404
  `, "utf8");
6928
7405
  }
6929
7406
  function applyGitHubProjectConfig(source, options) {
@@ -6949,9 +7426,9 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
6949
7426
  const selected = strategy ?? { kind: "managed-clone" };
6950
7427
  switch (selected.kind) {
6951
7428
  case "managed-clone":
6952
- return { kind: "managed-clone", path: projectRoot };
7429
+ return { kind: "managed-clone" };
6953
7430
  case "current-ref":
6954
- return { kind: "current-ref", path: projectRoot, ...selected.ref ? { ref: selected.ref } : {} };
7431
+ return { kind: "current-ref", ...selected.ref ? { ref: selected.ref } : {} };
6955
7432
  case "uploaded-snapshot":
6956
7433
  return { kind: "uploaded-snapshot", path: projectRoot, source: "local-working-tree" };
6957
7434
  case "existing-path":
@@ -7204,7 +7681,7 @@ function remoteGitHubAuthMetadata(payload) {
7204
7681
  };
7205
7682
  }
7206
7683
  function writeRemoteGitHubAuthState(projectRoot, input) {
7207
- writeFileSync5(resolve18(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
7684
+ writeFileSync6(resolve18(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
7208
7685
  authenticated: true,
7209
7686
  source: input.source,
7210
7687
  storedOnServer: true,
@@ -7252,7 +7729,7 @@ function runLocalFilesInit(context, options) {
7252
7729
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
7253
7730
  } else {
7254
7731
  const projectName = basename3(projectRoot) || "rig-project";
7255
- writeFileSync5(configTsPath, buildRigInitConfigSource({
7732
+ writeFileSync6(configTsPath, buildRigInitConfigSource({
7256
7733
  projectName,
7257
7734
  taskSource: { kind: "files", path: "tasks" },
7258
7735
  useStandardPlugin: true
@@ -7261,8 +7738,8 @@ function runLocalFilesInit(context, options) {
7261
7738
  ensureRigConfigPackageDependencies(projectRoot);
7262
7739
  const tasksDir = resolve18(projectRoot, "tasks");
7263
7740
  if (!existsSync12(tasksDir)) {
7264
- mkdirSync8(tasksDir, { recursive: true });
7265
- writeFileSync5(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)}
7741
+ mkdirSync9(tasksDir, { recursive: true });
7742
+ 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)}
7266
7743
  `, "utf-8");
7267
7744
  }
7268
7745
  if (context.outputMode !== "json") {
@@ -7286,7 +7763,7 @@ function runDemoInit(context, options) {
7286
7763
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
7287
7764
  }
7288
7765
  } else {
7289
- writeFileSync5(configTsPath, buildRigInitConfigSource({
7766
+ writeFileSync6(configTsPath, buildRigInitConfigSource({
7290
7767
  projectName: basename3(projectRoot) || "rig-demo",
7291
7768
  taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
7292
7769
  useStandardPlugin: true
@@ -7295,14 +7772,14 @@ function runDemoInit(context, options) {
7295
7772
  }
7296
7773
  ensureRigConfigPackageDependencies(projectRoot);
7297
7774
  const demoTasksDir = resolve18(projectRoot, DEMO_TASKS_RELATIVE_DIR);
7298
- mkdirSync8(demoTasksDir, { recursive: true });
7775
+ mkdirSync9(demoTasksDir, { recursive: true });
7299
7776
  const taskIds = [];
7300
7777
  for (const task of DEMO_TASKS) {
7301
7778
  const id = String(task.id);
7302
7779
  taskIds.push(id);
7303
7780
  const taskPath = resolve18(demoTasksDir, `${id}.json`);
7304
7781
  if (!existsSync12(taskPath)) {
7305
- writeFileSync5(taskPath, `${JSON.stringify(task, null, 2)}
7782
+ writeFileSync6(taskPath, `${JSON.stringify(task, null, 2)}
7306
7783
  `, "utf-8");
7307
7784
  }
7308
7785
  }
@@ -7330,21 +7807,32 @@ function runDemoInit(context, options) {
7330
7807
  }
7331
7808
  async function runControlPlaneInit(context, options) {
7332
7809
  const projectRoot = context.projectRoot;
7333
- const detectedSlug = options.repoSlug ?? detectOriginRepoSlug(projectRoot);
7810
+ const existingRepoConnection = readRepoConnection(projectRoot);
7811
+ const selectedConnection = (() => {
7812
+ try {
7813
+ return resolveSelectedConnection(projectRoot);
7814
+ } catch {
7815
+ return null;
7816
+ }
7817
+ })();
7818
+ const selectedRemote = selectedConnection?.connection.kind === "remote" ? { alias: selectedConnection.alias, connection: selectedConnection.connection } : null;
7819
+ const detectedSlug = options.repoSlug ?? existingRepoConnection?.project ?? detectOriginRepoSlug(projectRoot);
7820
+ const serverKind = options.server ?? (selectedRemote ? "remote" : "local");
7334
7821
  if (!detectedSlug) {
7335
7822
  const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
7336
- if ((options.server ?? "local") === "local" && authMethod2 === "skip") {
7823
+ if (serverKind === "local" && authMethod2 === "skip") {
7337
7824
  return runLocalFilesInit(context, options);
7338
7825
  }
7339
7826
  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);
7340
7827
  }
7341
7828
  const repo = parseRepoSlug(detectedSlug);
7342
- const serverKind = options.server ?? "local";
7343
- const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : "remote");
7829
+ const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : selectedRemote?.alias ?? "remote");
7830
+ const selectedRemoteUrl = selectedRemote && (!options.connectionAlias || options.connectionAlias === selectedRemote.alias) ? selectedRemote.connection.baseUrl : undefined;
7831
+ const remoteUrl = serverKind === "remote" ? (options.remoteUrl ?? selectedRemoteUrl)?.replace(/\/+$/, "") : undefined;
7344
7832
  if (serverKind === "remote") {
7345
- if (!options.remoteUrl)
7346
- throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`." });
7347
- upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: options.remoteUrl });
7833
+ if (!remoteUrl)
7834
+ 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>`." });
7835
+ upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: remoteUrl });
7348
7836
  }
7349
7837
  writeRepoConnection(projectRoot, {
7350
7838
  selected: connectionAlias,
@@ -7367,11 +7855,11 @@ async function runControlPlaneInit(context, options) {
7367
7855
  taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
7368
7856
  useStandardPlugin: true
7369
7857
  }), options);
7370
- writeFileSync5(configTsPath, source, "utf-8");
7858
+ writeFileSync6(configTsPath, source, "utf-8");
7371
7859
  }
7372
7860
  ensureRigConfigPackageDependencies(projectRoot);
7373
7861
  }
7374
- writeFileSync5(resolve18(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
7862
+ writeFileSync6(resolve18(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
7375
7863
  `, "utf8");
7376
7864
  const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
7377
7865
  let uploadedSnapshot = null;
@@ -7386,7 +7874,7 @@ async function runControlPlaneInit(context, options) {
7386
7874
  let githubAuth = null;
7387
7875
  let deviceAuth = null;
7388
7876
  const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
7389
- const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${options.remoteUrl ?? "the remote Rig server"}.` : null;
7877
+ const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${remoteUrl ?? "the remote Rig server"}.` : null;
7390
7878
  if (remoteGhTokenWarning && !options.yes) {
7391
7879
  throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
7392
7880
  }
@@ -7766,8 +8254,8 @@ var init_init = __esm(() => {
7766
8254
 
7767
8255
  // packages/cli/src/commands/github.ts
7768
8256
  import { spawnSync as spawnSync2 } from "child_process";
7769
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
7770
- import { dirname as dirname4, resolve as resolve19 } from "path";
8257
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
8258
+ import { dirname as dirname5, resolve as resolve19 } from "path";
7771
8259
  function printPayload(context, payload, fallback) {
7772
8260
  if (context.outputMode === "json")
7773
8261
  console.log(JSON.stringify(payload, null, 2));
@@ -7806,8 +8294,8 @@ function persistRemoteAuthSession(context, source, result, fallbackToken) {
7806
8294
  return;
7807
8295
  const repo = readRepoConnection(context.projectRoot);
7808
8296
  const path = resolve19(context.projectRoot, ".rig", "state", "github-auth.json");
7809
- mkdirSync9(dirname4(path), { recursive: true });
7810
- writeFileSync6(path, `${JSON.stringify({
8297
+ mkdirSync10(dirname5(path), { recursive: true });
8298
+ writeFileSync7(path, `${JSON.stringify({
7811
8299
  authenticated: true,
7812
8300
  source,
7813
8301
  storedOnServer: true,
@@ -9310,7 +9798,15 @@ var init__operator_surface = __esm(() => {
9310
9798
  });
9311
9799
 
9312
9800
  // packages/cli/src/commands/_pi-frontend.ts
9313
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
9801
+ var exports__pi_frontend = {};
9802
+ __export(exports__pi_frontend, {
9803
+ shouldRequireOperatorTranscript: () => shouldRequireOperatorTranscript,
9804
+ runWithProcessExitGuard: () => runWithProcessExitGuard,
9805
+ missingOperatorTranscriptMessage: () => missingOperatorTranscriptMessage,
9806
+ buildOperatorPiEnv: () => buildOperatorPiEnv,
9807
+ attachRunBundledPiFrontend: () => attachRunBundledPiFrontend
9808
+ });
9809
+ import { existsSync as existsSync15, mkdirSync as mkdirSync11, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
9314
9810
  import { homedir as homedir6, tmpdir } from "os";
9315
9811
  import { join as join3 } from "path";
9316
9812
  import { main as runPiMain } from "@earendil-works/pi-coding-agent";
@@ -9362,7 +9858,7 @@ function statusFromRunDetails(run) {
9362
9858
  async function prepareOperatorConsole(context, runId, tempSessionDir) {
9363
9859
  const server = await ensureServerForCli(context.projectRoot);
9364
9860
  const localCwd = join3(homedir6(), ".rig", "drones", runId.slice(0, 8));
9365
- mkdirSync10(localCwd, { recursive: true });
9861
+ mkdirSync11(localCwd, { recursive: true });
9366
9862
  trustDroneCwd(localCwd);
9367
9863
  installRigPiTheme();
9368
9864
  let sessionFileArg = [];
@@ -9384,7 +9880,7 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
9384
9880
  return line;
9385
9881
  }).join(`
9386
9882
  `);
9387
- writeFileSync7(localSessionPath, content);
9883
+ writeFileSync8(localSessionPath, content);
9388
9884
  sessionFileArg = ["--session", localSessionPath];
9389
9885
  }
9390
9886
  } catch {}
@@ -9400,12 +9896,12 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
9400
9896
  function trustDroneCwd(localCwd) {
9401
9897
  try {
9402
9898
  const agentDir = join3(homedir6(), ".pi", "agent");
9403
- mkdirSync10(agentDir, { recursive: true });
9899
+ mkdirSync11(agentDir, { recursive: true });
9404
9900
  const trustPath = join3(agentDir, "trust.json");
9405
9901
  const store = existsSync15(trustPath) ? JSON.parse(readFileSync10(trustPath, "utf8")) : {};
9406
9902
  if (store[localCwd] !== true) {
9407
9903
  store[localCwd] = true;
9408
- writeFileSync7(trustPath, `${JSON.stringify(store, null, "\t")}
9904
+ writeFileSync8(trustPath, `${JSON.stringify(store, null, "\t")}
9409
9905
  `);
9410
9906
  }
9411
9907
  } catch {}
@@ -9413,15 +9909,38 @@ function trustDroneCwd(localCwd) {
9413
9909
  function installRigPiTheme() {
9414
9910
  try {
9415
9911
  const themesDir = join3(homedir6(), ".pi", "agent", "themes");
9416
- mkdirSync10(themesDir, { recursive: true });
9912
+ mkdirSync11(themesDir, { recursive: true });
9417
9913
  const themePath = join3(themesDir, "rig.json");
9418
9914
  const next = `${JSON.stringify(RIG_PI_THEME, null, "\t")}
9419
9915
  `;
9420
9916
  if (!existsSync15(themePath) || readFileSync10(themePath, "utf8") !== next) {
9421
- writeFileSync7(themePath, next);
9917
+ writeFileSync8(themePath, next);
9422
9918
  }
9423
9919
  } catch {}
9424
9920
  }
9921
+ async function runWithProcessExitGuard(body) {
9922
+ const realExit = process.exit;
9923
+ let exitCode = 0;
9924
+ let signalQuit = () => {};
9925
+ const quit = new Promise((resolve23) => {
9926
+ signalQuit = resolve23;
9927
+ });
9928
+ const guardedExit = (code) => {
9929
+ exitCode = typeof code === "number" ? code : 0;
9930
+ signalQuit();
9931
+ return;
9932
+ };
9933
+ process.exit = guardedExit;
9934
+ try {
9935
+ await Promise.race([Promise.resolve().then(body), quit]);
9936
+ } finally {
9937
+ process.exit = realExit;
9938
+ }
9939
+ return exitCode;
9940
+ }
9941
+ function runPiMainReturningOnQuit(args, options) {
9942
+ return runWithProcessExitGuard(() => runPiMain(args, options));
9943
+ }
9425
9944
  async function attachRunBundledPiFrontend(context, input) {
9426
9945
  const tempSessionDir = mkdtempSync(join3(tmpdir(), "rig-pi-frontend-sessions-"));
9427
9946
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -9437,16 +9956,20 @@ async function attachRunBundledPiFrontend(context, input) {
9437
9956
  };
9438
9957
  let detached = false;
9439
9958
  try {
9440
- await runPiMain([
9959
+ const piArgs = [
9441
9960
  "--offline",
9442
9961
  "--no-extensions",
9443
9962
  "--no-skills",
9444
9963
  "--no-prompt-templates",
9445
9964
  "--no-context-files",
9446
9965
  ...sessionFileArg
9447
- ], {
9448
- extensionFactories: [piRigExtensionFactory]
9449
- });
9966
+ ];
9967
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
9968
+ if (input.returnOnQuit) {
9969
+ await runPiMainReturningOnQuit(piArgs, piOptions);
9970
+ } else {
9971
+ await runPiMain(piArgs, piOptions);
9972
+ }
9450
9973
  detached = true;
9451
9974
  } finally {
9452
9975
  restoreEnv();
@@ -10175,13 +10698,46 @@ function parseConnection(alias, value, options) {
10175
10698
  }
10176
10699
  return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
10177
10700
  }
10178
- function printJsonOrText(context, payload, text2) {
10179
- if (context.outputMode === "json") {
10180
- console.log(JSON.stringify(payload, null, 2));
10181
- } else {
10701
+ function printJsonOrText(context, _payload, text2) {
10702
+ if (context.outputMode !== "json") {
10182
10703
  console.log(text2);
10183
10704
  }
10184
10705
  }
10706
+ function formatRemoteProjectLinkText(result) {
10707
+ return formatSuccessCard(result.ok ? "Remote project link ready" : "Remote project link needs repair", [
10708
+ ["selected", result.alias ?? "remote"],
10709
+ ["target", result.baseUrl ?? "unknown"],
10710
+ ["repo", result.repoSlug ?? "owner/repo"],
10711
+ ["status", result.status],
10712
+ ...result.serverProjectRoot ? [["server root", result.serverProjectRoot]] : [],
10713
+ ["next", result.ok ? result.next ?? "rig task list" : result.hint]
10714
+ ]);
10715
+ }
10716
+ function parseRepairLinkArgs(rest, usage) {
10717
+ let repoSlug;
10718
+ let mode = "prepare-if-missing";
10719
+ const pending = [...rest];
10720
+ while (pending.length > 0) {
10721
+ const token = pending.shift();
10722
+ if (token === "--repo") {
10723
+ const value = pending.shift()?.trim();
10724
+ if (!value)
10725
+ throw new CliError(`Missing --repo value. Usage: ${usage}`, 1);
10726
+ const normalized = normalizeRepoSlug(value);
10727
+ if (!normalized)
10728
+ throw new CliError(`Invalid --repo value: ${value}`, 1, { hint: "Use the canonical owner/repo slug, e.g. `--repo humanity-org/humanwork`." });
10729
+ repoSlug = normalized;
10730
+ } else if (token === "--backfill-only") {
10731
+ mode = "backfill-only";
10732
+ } else if (token === "--prepare") {
10733
+ mode = "prepare-if-missing";
10734
+ } else {
10735
+ throw new CliError(`Unexpected argument: ${String(token)}
10736
+ Usage: ${usage}`, 1);
10737
+ }
10738
+ }
10739
+ return { repoSlug, mode };
10740
+ }
10185
10741
  async function promptForConnectionAlias(context) {
10186
10742
  const state = readGlobalConnections();
10187
10743
  const repo = readRepoConnection(context.projectRoot);
@@ -10235,37 +10791,67 @@ async function executeConnectionCommand(context, args, options) {
10235
10791
  }
10236
10792
  if (!alias)
10237
10793
  throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
10794
+ let selectedConnection = { kind: "local", mode: "auto" };
10238
10795
  if (alias !== "local") {
10239
10796
  const state = readGlobalConnections();
10240
- if (!state.connections[alias])
10797
+ const connection = state.connections[alias];
10798
+ if (!connection)
10241
10799
  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>`." });
10800
+ selectedConnection = connection;
10242
10801
  }
10243
10802
  const previousRepo = readRepoConnection(context.projectRoot);
10803
+ const preserveRemoteRoot = Boolean(previousRepo?.serverProjectRoot && previousRepo.selected === alias && selectedConnection.kind === "remote" && previousRepo.serverProjectRootAlias === alias && previousRepo.serverProjectRootBaseUrl === selectedConnection.baseUrl);
10244
10804
  const repoState = {
10245
10805
  selected: alias,
10246
10806
  ...previousRepo?.project ? { project: previousRepo.project } : {},
10247
10807
  linkedAt: new Date().toISOString(),
10248
- ...previousRepo?.serverProjectRoot && previousRepo.selected === alias ? { serverProjectRoot: previousRepo.serverProjectRoot } : {}
10808
+ ...preserveRemoteRoot ? {
10809
+ serverProjectRoot: previousRepo.serverProjectRoot,
10810
+ serverProjectRootAlias: previousRepo.serverProjectRootAlias,
10811
+ serverProjectRootBaseUrl: previousRepo.serverProjectRootBaseUrl
10812
+ } : {}
10249
10813
  };
10250
10814
  writeRepoConnection(context.projectRoot, repoState);
10251
- printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
10815
+ const remoteProjectLink = alias !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
10816
+ ok: false,
10817
+ status: "error",
10818
+ message: error instanceof Error ? error.message : String(error),
10819
+ hint: "Run `rig server repair-link` after authenticating the selected remote."
10820
+ })) : null;
10821
+ const latestRepo = readRepoConnection(context.projectRoot);
10822
+ const details = { selected: latestRepo?.selected ?? alias, repo: latestRepo, remoteProjectLink };
10823
+ printJsonOrText(context, details, formatSuccessCard("Rig server selected", [
10252
10824
  ["selected", alias],
10253
10825
  ["scope", "this repo"],
10254
- ["next", "rig task list"]
10826
+ ...remoteProjectLink ? [["remote link", remoteProjectLink.ok ? `ready (${remoteProjectLink.serverProjectRoot ?? "linked"})` : `${remoteProjectLink.status}: ${remoteProjectLink.hint}`]] : [],
10827
+ ["next", remoteProjectLink && !remoteProjectLink.ok ? "rig server repair-link" : "rig task list"]
10255
10828
  ]));
10256
- return { ok: true, group: options.group, command: "use", details: repoState };
10829
+ return { ok: true, group: options.group, command: "use", details };
10830
+ }
10831
+ case "repair-link": {
10832
+ const parsed = parseRepairLinkArgs(rest, `${usageName(options)} repair-link [--prepare|--backfill-only] [--repo owner/repo]`);
10833
+ const result = await repairRemoteProjectRootLink(context, parsed);
10834
+ printJsonOrText(context, result, formatRemoteProjectLinkText(result));
10835
+ return { ok: true, group: options.group, command: "repair-link", details: result };
10257
10836
  }
10258
10837
  case "status": {
10259
10838
  requireNoExtraArgs(rest, `${usageName(options)} status`);
10260
10839
  const repo = readRepoConnection(context.projectRoot);
10261
10840
  const global = readGlobalConnections();
10262
- const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
10263
- printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
10841
+ const remoteProjectLink = repo?.selected && repo.selected !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
10842
+ ok: false,
10843
+ status: "error",
10844
+ message: error instanceof Error ? error.message : String(error),
10845
+ hint: "Run `rig server repair-link` after authenticating the selected remote."
10846
+ })) : null;
10847
+ const latestRepo = readRepoConnection(context.projectRoot) ?? repo;
10848
+ const details = { selected: latestRepo?.selected ?? "local", repo: latestRepo, connections: global.connections, remoteProjectLink };
10849
+ printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections, latestRepo ?? null, remoteProjectLink));
10264
10850
  return { ok: true, group: options.group, command: "status", details };
10265
10851
  }
10266
10852
  default:
10267
10853
  throw new CliError(`Unknown ${options.group} command: ${String(command)}
10268
- Usage: ${usageName(options)} <list|add|use|status>`, 1);
10854
+ Usage: ${usageName(options)} <list|add|use|status|repair-link>`, 1);
10269
10855
  }
10270
10856
  }
10271
10857
  var init_connect = __esm(() => {
@@ -10273,13 +10859,14 @@ var init_connect = __esm(() => {
10273
10859
  init_runner();
10274
10860
  init__connection_state();
10275
10861
  init__cli_format();
10862
+ init__server_client();
10276
10863
  });
10277
10864
 
10278
10865
  // packages/cli/src/commands/server.ts
10279
10866
  import { resolveRigServerCommand } from "@rig/runtime/local-server";
10280
10867
  async function executeServer(context, args, options) {
10281
10868
  const [command = "status", ...rest] = args;
10282
- if (["status", "list", "add", "use"].includes(command)) {
10869
+ if (["status", "list", "add", "use", "repair-link"].includes(command)) {
10283
10870
  return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
10284
10871
  }
10285
10872
  switch (command) {
@@ -11066,7 +11653,7 @@ var init_task = __esm(() => {
11066
11653
  });
11067
11654
 
11068
11655
  // packages/cli/src/commands/task-run-driver.ts
11069
- import { copyFileSync as copyFileSync3, existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
11656
+ import { copyFileSync as copyFileSync3, existsSync as existsSync16, mkdirSync as mkdirSync12, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync9 } from "fs";
11070
11657
  import { resolve as resolve24 } from "path";
11071
11658
  import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
11072
11659
  import { createInterface as createLineInterface } from "readline";
@@ -11169,7 +11756,7 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
11169
11756
  try {
11170
11757
  if (!statSync2(sourcePath).isFile())
11171
11758
  continue;
11172
- mkdirSync11(resolve24(targetPath, ".."), { recursive: true });
11759
+ mkdirSync12(resolve24(targetPath, ".."), { recursive: true });
11173
11760
  copyFileSync3(sourcePath, targetPath);
11174
11761
  copied += 1;
11175
11762
  } catch {}
@@ -12040,8 +12627,8 @@ async function executeRigOwnedTaskRun(context, input) {
12040
12627
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
12041
12628
  classifiedAt: new Date().toISOString()
12042
12629
  };
12043
- mkdirSync11(resolve24(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
12044
- writeFileSync8(resolve24(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
12630
+ mkdirSync12(resolve24(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
12631
+ writeFileSync9(resolve24(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
12045
12632
  `, "utf8");
12046
12633
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
12047
12634
  prompt = `${prompt}
@@ -12188,8 +12775,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
12188
12775
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
12189
12776
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
12190
12777
  if (readyFile) {
12191
- mkdirSync11(resolve24(readyFile, ".."), { recursive: true });
12192
- writeFileSync8(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
12778
+ mkdirSync12(resolve24(readyFile, ".."), { recursive: true });
12779
+ writeFileSync9(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
12193
12780
  `, "utf8");
12194
12781
  }
12195
12782
  appendRunLog(context.projectRoot, input.runId, {
@@ -13135,7 +13722,7 @@ var init_test = __esm(() => {
13135
13722
  });
13136
13723
 
13137
13724
  // packages/cli/src/commands/setup.ts
13138
- import { existsSync as existsSync17, mkdirSync as mkdirSync12, readdirSync as readdirSync3, writeFileSync as writeFileSync9 } from "fs";
13725
+ import { existsSync as existsSync17, mkdirSync as mkdirSync13, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
13139
13726
  import { resolve as resolve25 } from "path";
13140
13727
  import { createPluginHost } from "@rig/core";
13141
13728
  import {
@@ -13190,12 +13777,12 @@ function runSetupInit(projectRoot) {
13190
13777
  const stateDir = resolveControlPlaneHostStateDir(projectRoot);
13191
13778
  const logsDir = resolveControlPlaneHostLogsDir(projectRoot);
13192
13779
  const artifactsDir = resolveControlPlaneArtifactsDir(projectRoot);
13193
- mkdirSync12(stateDir, { recursive: true });
13194
- mkdirSync12(logsDir, { recursive: true });
13195
- mkdirSync12(artifactsDir, { recursive: true });
13780
+ mkdirSync13(stateDir, { recursive: true });
13781
+ mkdirSync13(logsDir, { recursive: true });
13782
+ mkdirSync13(artifactsDir, { recursive: true });
13196
13783
  const failuresPath = resolve25(stateDir, "failed_approaches.md");
13197
13784
  if (!existsSync17(failuresPath)) {
13198
- writeFileSync9(failuresPath, `# Failed Approaches
13785
+ writeFileSync10(failuresPath, `# Failed Approaches
13199
13786
 
13200
13787
  `, "utf-8");
13201
13788
  }
@@ -14712,414 +15299,23 @@ async function stopFleetRun(ctx, runId) {
14712
15299
  }
14713
15300
  var init_fleet = () => {};
14714
15301
 
14715
- // packages/cli/src/app-opentui/pi-pty-host.ts
14716
- import { fileURLToPath as fileURLToPath2 } from "url";
14717
- import { basename as basename4 } from "path";
14718
- import { RGBA, StyledText, TextAttributes } from "@opentui/core";
14719
- import { Terminal as XtermTerminal2 } from "@xterm/headless";
14720
- function clampCols2(cols) {
14721
- return Math.max(MIN_COLS2, Math.trunc(cols || 100));
14722
- }
14723
- function clampRows2(rows) {
14724
- return Math.max(MIN_ROWS2, Math.min(MAX_ROWS2, Math.trunc(rows || 35)));
14725
- }
14726
- function rgbaFromXtermColor(mode, value) {
14727
- if (mode === 1 || mode === 2 || mode === XTERM_COLOR_MODE_INDEXED_16 || mode === XTERM_COLOR_MODE_INDEXED_256) {
14728
- return RGBA.fromIndex(value);
14729
- }
14730
- if (mode === 3 || mode === XTERM_COLOR_MODE_RGB) {
14731
- return RGBA.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255);
14732
- }
14733
- return;
14734
- }
14735
- function textAttributesFromCell(cell) {
14736
- let attributes = TextAttributes.NONE;
14737
- if (cell.isBold())
14738
- attributes |= TextAttributes.BOLD;
14739
- if (cell.isDim())
14740
- attributes |= TextAttributes.DIM;
14741
- if (cell.isItalic())
14742
- attributes |= TextAttributes.ITALIC;
14743
- if (cell.isUnderline())
14744
- attributes |= TextAttributes.UNDERLINE;
14745
- if (cell.isBlink())
14746
- attributes |= TextAttributes.BLINK;
14747
- if (cell.isInverse())
14748
- attributes |= TextAttributes.INVERSE;
14749
- if (cell.isInvisible())
14750
- attributes |= TextAttributes.HIDDEN;
14751
- if (cell.isStrikethrough())
14752
- attributes |= TextAttributes.STRIKETHROUGH;
14753
- return attributes;
14754
- }
14755
- function sameRgba(a, b) {
14756
- if (!a && !b)
14757
- return true;
14758
- return Boolean(a && b && a.equals(b));
14759
- }
14760
- function sameStyle(a, b) {
14761
- return sameRgba(a.fg, b.fg) && sameRgba(a.bg, b.bg) && (a.attributes ?? 0) === (b.attributes ?? 0);
14762
- }
14763
- function styleFromCell(cell) {
14764
- return {
14765
- fg: rgbaFromXtermColor(cell.getFgColorMode(), cell.getFgColor()),
14766
- bg: rgbaFromXtermColor(cell.getBgColorMode(), cell.getBgColor()),
14767
- attributes: textAttributesFromCell(cell)
14768
- };
14769
- }
14770
- function lineToStyledText(line, cols) {
14771
- if (!line)
14772
- return new StyledText([{ __isChunk: true, text: "" }]);
14773
- const chunks = [];
14774
- let run = "";
14775
- let runStyle = null;
14776
- const flush = () => {
14777
- if (!run)
14778
- return;
14779
- chunks.push({
14780
- __isChunk: true,
14781
- text: run,
14782
- ...runStyle?.fg ? { fg: runStyle.fg } : {},
14783
- ...runStyle?.bg ? { bg: runStyle.bg } : {},
14784
- ...(runStyle?.attributes ?? 0) !== 0 ? { attributes: runStyle.attributes } : {}
14785
- });
14786
- run = "";
14787
- };
14788
- for (let index = 0;index < cols; index += 1) {
14789
- const cell = line.getCell(index);
14790
- if (!cell) {
14791
- if (runStyle !== null)
14792
- flush();
14793
- runStyle = null;
14794
- run += " ";
14795
- continue;
14796
- }
14797
- if (cell.getWidth() === 0)
14798
- continue;
14799
- const style = styleFromCell(cell);
14800
- if (!runStyle || !sameStyle(runStyle, style)) {
14801
- flush();
14802
- runStyle = style;
14803
- }
14804
- run += cell.getChars() || " ";
14805
- }
14806
- flush();
14807
- return new StyledText(chunks.length > 0 ? chunks : [{ __isChunk: true, text: "" }]);
14808
- }
14809
- function childCommandPrefix2() {
14810
- const execName = basename4(process.execPath).toLowerCase();
14811
- const currentEntry = process.argv[1];
14812
- if (execName === "bun" || execName === "bun.exe") {
14813
- return currentEntry ? [process.execPath, currentEntry, "__opentui-pi-host"] : [process.execPath, fallbackChildScriptPath];
14814
- }
14815
- return [process.execPath, "__opentui-pi-host"];
14816
- }
14817
- function withEnv2(base) {
14818
- const env = {};
14819
- for (const [key, value] of Object.entries(base ?? process.env)) {
14820
- if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP" || key === "NO_COLOR")
14821
- continue;
14822
- if (typeof value === "string")
14823
- env[key] = value;
14824
- }
14825
- return {
14826
- ...env,
14827
- TERM: "xterm-256color",
14828
- COLORTERM: "truecolor",
14829
- FORCE_COLOR: env.FORCE_COLOR ?? "1",
14830
- RIG_OPENTUI_PI_HOST: "1"
14831
- };
14832
- }
14833
- function getActivePiHost() {
14834
- return activeHost2 && !activeHost2.disposed ? activeHost2 : null;
14835
- }
14836
- async function startPiPtyHost(options) {
14837
- activeHost2?.dispose("replace");
14838
- const host = new PiPtyHost(options);
14839
- activeHost2 = host;
14840
- await host.start();
14841
- return host;
14842
- }
14843
- function stopActivePiHost(reason = "detach") {
14844
- activeHost2?.dispose(reason);
14845
- activeHost2 = null;
14846
- }
14847
-
14848
- class PiPtyHost {
14849
- runId;
14850
- projectRoot;
14851
- onSnapshot;
14852
- onExit;
14853
- onError;
14854
- terminal;
14855
- disposables = [];
14856
- decoder = new TextDecoder("utf-8");
14857
- proc = null;
14858
- pty = null;
14859
- status = "starting";
14860
- cols;
14861
- rows;
14862
- message = "starting bundled Pi";
14863
- lastResizeError = null;
14864
- exitCode;
14865
- signal;
14866
- notifyTimer = null;
14867
- lastStreamKey = "";
14868
- _disposed = false;
14869
- constructor(options) {
14870
- this.runId = options.runId;
14871
- this.projectRoot = options.projectRoot;
14872
- this.cols = clampCols2(options.cols);
14873
- this.rows = clampRows2(options.rows);
14874
- this.onSnapshot = options.onSnapshot;
14875
- this.onExit = options.onExit;
14876
- this.onError = options.onError;
14877
- this.terminal = new XtermTerminal2({
14878
- allowProposedApi: true,
14879
- cols: this.cols,
14880
- rows: this.rows,
14881
- scrollback: 1000
14882
- });
14883
- this.registerTerminalResponders();
14884
- this.disposables.push(this.terminal.onWriteParsed(() => {
14885
- if (this._disposed)
14886
- return;
14887
- const snapshot = this.createSnapshot();
14888
- const key = snapshot.lines.join(`
14889
- `);
14890
- if (key === this.lastStreamKey)
14891
- return;
14892
- this.lastStreamKey = key;
14893
- this.onSnapshot?.(snapshot);
14894
- }));
14895
- }
14896
- get disposed() {
14897
- return this._disposed;
14898
- }
14899
- get snapshot() {
14900
- return this.createSnapshot();
14901
- }
14902
- async start() {
14903
- if (this._disposed)
14904
- throw new Error("Pi PTY host is disposed.");
14905
- if (typeof Bun.Terminal !== "function") {
14906
- throw new Error("Bun native PTY is unavailable in this runtime. Pi host requires Bun.Terminal.");
14907
- }
14908
- const spawnOptions = {
14909
- cwd: this.projectRoot,
14910
- env: withEnv2(process.env),
14911
- terminal: {
14912
- cols: this.cols,
14913
- rows: this.rows,
14914
- name: "xterm-256color",
14915
- data: (_terminal, data) => this.handlePtyData(data)
14916
- }
14917
- };
14918
- const proc = Bun.spawn([
14919
- ...childCommandPrefix2(),
14920
- "--run-id",
14921
- this.runId,
14922
- "--project-root",
14923
- this.projectRoot
14924
- ], spawnOptions);
14925
- if (!proc.terminal)
14926
- throw new Error("Bun did not attach a terminal to the bundled Pi child process.");
14927
- this.proc = proc;
14928
- this.pty = proc.terminal;
14929
- this.status = "running";
14930
- this.message = "bundled Pi running inside this app";
14931
- this.emitSnapshotSoon(0);
14932
- proc.exited.then((exitCode) => {
14933
- if (this._disposed)
14934
- return;
14935
- this.status = exitCode === 0 ? "exited" : "failed";
14936
- this.exitCode = exitCode;
14937
- this.signal = null;
14938
- this.message = exitCode === 0 ? "bundled Pi exited" : `bundled Pi exited with code ${exitCode}`;
14939
- const snapshot = this.createSnapshot();
14940
- this.onSnapshot?.(snapshot);
14941
- this.onExit?.(snapshot);
14942
- if (activeHost2 === this)
14943
- activeHost2 = null;
14944
- this.dispose("exit", { kill: false, notify: false });
14945
- }).catch((error) => {
14946
- if (this._disposed)
14947
- return;
14948
- this.status = "failed";
14949
- this.message = error instanceof Error ? error.message : String(error);
14950
- const snapshot = this.createSnapshot();
14951
- this.onSnapshot?.(snapshot);
14952
- this.onError?.(error, snapshot);
14953
- if (activeHost2 === this)
14954
- activeHost2 = null;
14955
- this.dispose("error", { kill: false, notify: false });
14956
- });
14957
- }
14958
- write(data) {
14959
- if (this._disposed || !this.pty)
14960
- return;
14961
- try {
14962
- this.pty.write(data);
14963
- } catch (error) {
14964
- this.status = "failed";
14965
- this.message = error instanceof Error ? error.message : String(error);
14966
- const snapshot = this.createSnapshot();
14967
- this.onSnapshot?.(snapshot);
14968
- this.onError?.(error, snapshot);
14969
- }
14970
- }
14971
- resize(cols, rows) {
14972
- const nextCols = clampCols2(cols);
14973
- const nextRows = clampRows2(rows);
14974
- if (nextCols === this.cols && nextRows === this.rows)
14975
- return;
14976
- this.cols = nextCols;
14977
- this.rows = nextRows;
14978
- this.terminal.resize(nextCols, nextRows);
14979
- try {
14980
- this.pty?.resize(nextCols, nextRows);
14981
- this.lastResizeError = null;
14982
- } catch (error) {
14983
- this.lastResizeError = error instanceof Error ? error.message : String(error);
14984
- }
14985
- this.emitSnapshotSoon(0);
14986
- }
14987
- detach() {
14988
- this.dispose("detach");
14989
- return this.createSnapshot("detached from bundled Pi");
14990
- }
14991
- dispose(reason = "dispose", options = {}) {
14992
- if (this._disposed)
14993
- return;
14994
- this._disposed = true;
14995
- if (this.notifyTimer)
14996
- clearTimeout(this.notifyTimer);
14997
- this.notifyTimer = null;
14998
- for (const disposable of this.disposables.splice(0)) {
14999
- try {
15000
- disposable.dispose();
15001
- } catch {}
15002
- }
15003
- if (options.kill !== false) {
15004
- try {
15005
- this.proc?.kill("SIGTERM");
15006
- } catch {}
15007
- }
15008
- try {
15009
- this.pty?.close();
15010
- } catch {}
15011
- try {
15012
- this.terminal.dispose();
15013
- } catch {}
15014
- if (activeHost2 === this)
15015
- activeHost2 = null;
15016
- if (options.notify) {
15017
- this.message = reason;
15018
- this.onSnapshot?.(this.createSnapshot(reason));
15019
- }
15020
- }
15021
- handlePtyData(data) {
15022
- if (this._disposed)
15023
- return;
15024
- const text2 = this.decoder.decode(data, { stream: true });
15025
- this.respondToRawTerminalQueries(text2);
15026
- this.terminal.write(data);
15027
- }
15028
- emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS2) {
15029
- if (this._disposed || this.notifyTimer)
15030
- return;
15031
- this.notifyTimer = setTimeout(() => {
15032
- this.notifyTimer = null;
15033
- if (this._disposed)
15034
- return;
15035
- this.onSnapshot?.(this.createSnapshot());
15036
- }, delayMs);
15037
- }
15038
- createSnapshot(message2 = this.message) {
15039
- const buffer = this.terminal.buffer.active;
15040
- const end = buffer.length;
15041
- const start = Math.max(0, end - MAX_SNAPSHOT_LINES2);
15042
- const styledStart = Math.max(start, end - this.rows - STYLED_SNAPSHOT_MARGIN);
15043
- const lines = [];
15044
- const styledLines = [];
15045
- for (let row = start;row < end; row += 1) {
15046
- const line = buffer.getLine(row);
15047
- lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
15048
- if (row >= styledStart)
15049
- styledLines[lines.length - 1] = lineToStyledText(line, this.cols);
15050
- }
15051
- while (lines.length < this.rows) {
15052
- lines.push("");
15053
- styledLines[lines.length - 1] = new StyledText([{ __isChunk: true, text: "" }]);
15054
- }
15055
- const resolvedMessage = this.lastResizeError ? `resize degraded: ${this.lastResizeError}` : message2;
15056
- return {
15057
- runId: this.runId,
15058
- status: this.status,
15059
- cols: this.cols,
15060
- rows: this.rows,
15061
- lines,
15062
- styledLines,
15063
- message: resolvedMessage,
15064
- ...this.lastResizeError ? { resizeError: this.lastResizeError } : {},
15065
- ...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
15066
- ...this.signal !== undefined ? { signal: this.signal } : {}
15067
- };
15068
- }
15069
- registerTerminalResponders() {
15070
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "c" }, (params) => {
15071
- if (params.length === 0 || params[0] === 0)
15072
- this.write("\x1B[?62;22c");
15073
- return false;
15074
- }));
15075
- this.disposables.push(this.terminal.parser.registerCsiHandler({ prefix: ">", final: "c" }, (params) => {
15076
- if (params.length === 0 || params[0] === 0)
15077
- this.write("\x1B[>0;0;0c");
15078
- return false;
15079
- }));
15080
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
15081
- if (params[0] === 5) {
15082
- this.write("\x1B[0n");
15083
- } else if (params[0] === 6) {
15084
- const row = this.terminal.buffer.active.cursorY + 1;
15085
- const col = this.terminal.buffer.active.cursorX + 1;
15086
- this.write(`\x1B[${row};${col}R`);
15087
- }
15088
- return false;
15089
- }));
15090
- }
15091
- respondToRawTerminalQueries(text2) {
15092
- if (!text2)
15093
- return;
15094
- const decrqm = /\x1b\[\?(\d+)\$p/g;
15095
- let match;
15096
- while ((match = decrqm.exec(text2)) !== null) {
15097
- this.write(`\x1B[?${match[1]};2$y`);
15098
- }
15099
- }
15100
- }
15101
- 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;
15102
- var init_pi_pty_host = __esm(() => {
15103
- fallbackChildScriptPath = fileURLToPath2(new URL("./pi-host-child.ts", import.meta.url));
15104
- });
15105
-
15106
- // packages/cli/src/app-opentui/adapters/inspect.ts
15107
- var exports_inspect = {};
15108
- __export(exports_inspect, {
15109
- loadInspectRuns: () => loadInspectRuns,
15110
- loadInspect: () => loadInspect,
15111
- INSPECT_VIEWS: () => INSPECT_VIEWS
15112
- });
15113
- import { execFile } from "child_process";
15114
- import { existsSync as existsSync21, readFileSync as readFileSync14, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
15115
- import { resolve as resolve28 } from "path";
15116
- import { promisify } from "util";
15117
- import { resolveTaskArtifactDirs as resolveTaskArtifactDirs3 } from "@rig/runtime/control-plane/authority-files";
15118
- import { readTaskArtifactPreview as readTaskArtifactPreview2 } from "@rig/runtime/control-plane/native/workspace-ops";
15119
- import { resolveHarnessPaths as resolveHarnessPaths3, resolveMonorepoRoot as resolveMonorepoRoot3, runCapture as runCapture4 } from "@rig/runtime/control-plane/native/utils";
15120
- function isFailureEntry(entry) {
15121
- const status = `${entry.status ?? entry.level ?? entry.type ?? ""}`.toLowerCase();
15122
- return status.includes("fail") || status.includes("error");
15302
+ // packages/cli/src/app-opentui/adapters/inspect.ts
15303
+ var exports_inspect = {};
15304
+ __export(exports_inspect, {
15305
+ loadInspectRuns: () => loadInspectRuns,
15306
+ loadInspect: () => loadInspect,
15307
+ INSPECT_VIEWS: () => INSPECT_VIEWS
15308
+ });
15309
+ import { execFile } from "child_process";
15310
+ import { existsSync as existsSync21, readFileSync as readFileSync14, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
15311
+ import { resolve as resolve28 } from "path";
15312
+ import { promisify } from "util";
15313
+ import { resolveTaskArtifactDirs as resolveTaskArtifactDirs3 } from "@rig/runtime/control-plane/authority-files";
15314
+ import { readTaskArtifactPreview as readTaskArtifactPreview2 } from "@rig/runtime/control-plane/native/workspace-ops";
15315
+ import { resolveHarnessPaths as resolveHarnessPaths3, resolveMonorepoRoot as resolveMonorepoRoot3, runCapture as runCapture4 } from "@rig/runtime/control-plane/native/utils";
15316
+ function isFailureEntry(entry) {
15317
+ const status = `${entry.status ?? entry.level ?? entry.type ?? ""}`.toLowerCase();
15318
+ return status.includes("fail") || status.includes("error");
15123
15319
  }
15124
15320
  async function gitDiff(projectRoot) {
15125
15321
  try {
@@ -15325,14 +15521,6 @@ function recordStep(ctx, runId, label, emitLabel) {
15325
15521
  });
15326
15522
  emitProgress(ctx, emitLabel, label);
15327
15523
  }
15328
- function settleSteps(ctx, runId) {
15329
- const now = Date.now();
15330
- const previous = currentAttachState(ctx, runId);
15331
- if (!previous?.steps)
15332
- return;
15333
- const steps = previous.steps.map((step) => step.status === "running" ? { ...step, status: "done", endedAtMs: step.endedAtMs ?? now } : step);
15334
- patchData(ctx, { piAttach: { ...previous, steps } });
15335
- }
15336
15524
  async function preparePiAttachHandoff(ctx, runId) {
15337
15525
  const cleanRunId = runId.trim();
15338
15526
  const label = `Preparing Pi ${cleanRunId.slice(0, 8)}`;
@@ -15380,85 +15568,41 @@ async function attachRunWithBundledPi(ctx, runId) {
15380
15568
  throw error;
15381
15569
  }
15382
15570
  emitStarted(ctx, label, { piAttach: { runId: cleanRunId, status: "entering-pi" } });
15383
- patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "starting bundled Pi", steps: [] } });
15571
+ patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "handing the terminal to worker Pi\u2026" } });
15572
+ const projectRoot = projectRootOf(ctx);
15573
+ const outputMode = ctx.rig?.outputMode ?? "text";
15574
+ await releaseRendererForExternalTui(ctx);
15575
+ let outcome = null;
15576
+ let attachError = null;
15384
15577
  try {
15385
- recordStep(ctx, cleanRunId, "connecting to server and fetching run transcript", label);
15386
- await preparePiAttachHandoff(ctx, cleanRunId).catch((error) => {
15387
- patchData(ctx, { lastRefreshError: normalizeAppError(error).message });
15388
- return null;
15389
- });
15390
- recordStep(ctx, cleanRunId, "spawning Bun PTY host for bundled Pi", label);
15391
- const projectRoot = projectRootOf(ctx);
15392
- const cols = typeof process.stdout.columns === "number" ? process.stdout.columns : 100;
15393
- const rows = Math.max(1, (typeof process.stdout.rows === "number" ? process.stdout.rows : 35) - 1);
15394
- const patchTerminal = (snapshot) => {
15395
- const previousSteps = currentAttachState(ctx, cleanRunId)?.steps;
15396
- const failed = snapshot.status === "failed";
15397
- const reason = failed ? snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}` : undefined;
15398
- patchData(ctx, {
15399
- piTerminal: snapshot,
15400
- piAttach: {
15401
- runId: cleanRunId,
15402
- status: failed ? "failed" : snapshot.status === "exited" ? "returned" : "entering-pi",
15403
- message: snapshot.message ?? "bundled Pi running",
15404
- result: { runId: cleanRunId, status: snapshot.status, exitCode: snapshot.exitCode },
15405
- ...previousSteps ? { steps: previousSteps } : {},
15406
- ...reason ? { failureReason: reason } : {}
15407
- }
15408
- });
15409
- };
15410
- recordStep(ctx, cleanRunId, "starting bundled Pi", label);
15411
- await startPiPtyHost({
15412
- runId: cleanRunId,
15413
- projectRoot,
15414
- cols,
15415
- rows,
15416
- onSnapshot: (snapshot) => {
15417
- if (snapshot.status === "running" && snapshot.lines.some((entry) => entry.trim().length > 0)) {
15418
- settleSteps(ctx, cleanRunId);
15419
- }
15420
- patchTerminal(snapshot);
15421
- },
15422
- onExit: (snapshot) => {
15423
- patchTerminal(snapshot);
15424
- if (snapshot.status === "failed") {
15425
- Promise.resolve().then(() => (init_inspect2(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
15426
- emitFailed(ctx, label, new Error(snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}`), { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
15427
- return;
15428
- }
15429
- emitCompleted(ctx, label, { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
15430
- ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
15431
- refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
15432
- },
15433
- onError: (error, snapshot) => {
15434
- patchTerminal(snapshot);
15435
- emitFailed(ctx, label, error, { runId: cleanRunId });
15436
- }
15437
- });
15438
- const record = {
15439
- runId: cleanRunId,
15440
- status: "running",
15441
- rendered: "bundled Pi embedded via Bun.Terminal + @xterm/headless"
15442
- };
15443
- emitProgress(ctx, label, "bundled Pi is running", { piAttach: { runId: cleanRunId, status: "entering-pi", message: "bundled Pi running", result: record } });
15444
- return record;
15578
+ const { attachRunBundledPiFrontend: attachRunBundledPiFrontend2 } = await Promise.resolve().then(() => (init__pi_frontend(), exports__pi_frontend));
15579
+ outcome = await attachRunBundledPiFrontend2({ projectRoot, outputMode }, { runId: cleanRunId, returnOnQuit: true });
15445
15580
  } catch (error) {
15446
- const reason = error instanceof Error ? error.message : String(error);
15581
+ attachError = error;
15582
+ } finally {
15583
+ await resumeRendererAfterExternalTui(ctx);
15584
+ }
15585
+ if (attachError) {
15586
+ const reason = normalizeAppError(attachError).message;
15447
15587
  patchData(ctx, {
15448
- piAttach: {
15449
- runId: cleanRunId,
15450
- status: "failed",
15451
- message: reason,
15452
- failureReason: reason,
15453
- ...currentAttachState(ctx, cleanRunId)?.steps ? { steps: currentAttachState(ctx, cleanRunId).steps } : {}
15454
- }
15588
+ piTerminal: undefined,
15589
+ piAttach: { runId: cleanRunId, status: "failed", message: reason, failureReason: reason }
15455
15590
  });
15456
- emitFailed(ctx, label, error, { runId: cleanRunId });
15457
- throw error;
15591
+ Promise.resolve().then(() => (init_inspect2(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
15592
+ emitFailed(ctx, label, attachError, { runId: cleanRunId });
15593
+ throw attachError;
15458
15594
  }
15595
+ const detached = outcome?.detached === true;
15596
+ patchData(ctx, {
15597
+ piTerminal: undefined,
15598
+ piAttach: { runId: cleanRunId, status: "returned", message: detached ? "detached from worker Pi" : "returned from worker Pi" }
15599
+ });
15600
+ emitCompleted(ctx, label, { runId: cleanRunId, result: { runId: cleanRunId, detached } });
15601
+ ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
15602
+ refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
15603
+ return { runId: cleanRunId, status: "returned", detached };
15459
15604
  }
15460
15605
  var init_pi_attach = __esm(() => {
15461
- init_pi_pty_host();
15462
15606
  init_fleet();
15463
15607
  });
15464
15608
 
@@ -15510,7 +15654,7 @@ import {
15510
15654
  dim as otuiDim,
15511
15655
  fg as otuiFg,
15512
15656
  t,
15513
- TextAttributes as TextAttributes2
15657
+ TextAttributes
15514
15658
  } from "@opentui/core";
15515
15659
  function statusColor3(status) {
15516
15660
  switch (status) {
@@ -15615,7 +15759,7 @@ var init_theme2 = __esm(() => {
15615
15759
  });
15616
15760
 
15617
15761
  // packages/cli/src/app-opentui/drone.ts
15618
- import { RGBA as RGBA2, StyledText as StyledText2, TextAttributes as TextAttributes3 } from "@opentui/core";
15762
+ import { RGBA, StyledText, TextAttributes as TextAttributes2 } from "@opentui/core";
15619
15763
  function bladeForTick(tick, phase = 0) {
15620
15764
  return BLADE_FRAMES2[(Math.floor(tick / 3) + phase) % BLADE_FRAMES2.length];
15621
15765
  }
@@ -15624,12 +15768,12 @@ function eyeForTick(tick, phase = 0) {
15624
15768
  return pulse > 0.55 ? EYE_FRAMES2[1] : pulse > 0.1 ? EYE_FRAMES2[0] : pulse > -0.45 ? EYE_FRAMES2[2] : EYE_FRAMES2[3];
15625
15769
  }
15626
15770
  function chunk(text2, fg2, bold2 = false, dim = false) {
15627
- let attributes = TextAttributes3.NONE;
15771
+ let attributes = TextAttributes2.NONE;
15628
15772
  if (bold2)
15629
- attributes |= TextAttributes3.BOLD;
15773
+ attributes |= TextAttributes2.BOLD;
15630
15774
  if (dim)
15631
- attributes |= TextAttributes3.DIM;
15632
- return { __isChunk: true, text: text2, fg: fg2, ...attributes !== TextAttributes3.NONE ? { attributes } : {} };
15775
+ attributes |= TextAttributes2.DIM;
15776
+ return { __isChunk: true, text: text2, fg: fg2, ...attributes !== TextAttributes2.NONE ? { attributes } : {} };
15633
15777
  }
15634
15778
  function styledLine(text2, colorFor) {
15635
15779
  const chunks = [];
@@ -15650,7 +15794,7 @@ function styledLine(text2, colorFor) {
15650
15794
  run += char;
15651
15795
  }
15652
15796
  flush();
15653
- return new StyledText2(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
15797
+ return new StyledText(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
15654
15798
  }
15655
15799
  function droneColor(char) {
15656
15800
  if (char === "?" || char === "o" || char === "@" || char === "\u2022")
@@ -15742,13 +15886,13 @@ var init_drone = __esm(() => {
15742
15886
  BLADE_FRAMES2 = ["---", "\\\\\\", "|||", "///"];
15743
15887
  EYE_FRAMES2 = ["o", "@", "\u2022", "."];
15744
15888
  COLOR = {
15745
- body: RGBA2.fromHex(RIG_UI.lime),
15746
- mini: RGBA2.fromHex(RIG_UI.limeDim),
15747
- rotor: RGBA2.fromHex(RIG_UI.cyan),
15748
- path: RGBA2.fromHex(RIG_UI.cyan),
15749
- eye: RGBA2.fromHex(RIG_UI.ink),
15750
- dim: RGBA2.fromHex(RIG_UI.ink4),
15751
- ink: RGBA2.fromHex(RIG_UI.ink2)
15889
+ body: RGBA.fromHex(RIG_UI.lime),
15890
+ mini: RGBA.fromHex(RIG_UI.limeDim),
15891
+ rotor: RGBA.fromHex(RIG_UI.cyan),
15892
+ path: RGBA.fromHex(RIG_UI.cyan),
15893
+ eye: RGBA.fromHex(RIG_UI.ink),
15894
+ dim: RGBA.fromHex(RIG_UI.ink4),
15895
+ ink: RGBA.fromHex(RIG_UI.ink2)
15752
15896
  };
15753
15897
  });
15754
15898
 
@@ -15822,6 +15966,51 @@ var init_selectable = __esm(() => {
15822
15966
  init_scene();
15823
15967
  });
15824
15968
 
15969
+ // packages/cli/src/app-opentui/remote-link.ts
15970
+ function remoteProjectLinkError(message2) {
15971
+ return /no server-host project root link|remote project(?:-root)? link|x-rig-project-root|serverProjectRoot/i.test(message2 ?? "");
15972
+ }
15973
+ function serverRecordForRemoteLink(state) {
15974
+ const server = state.data.server;
15975
+ return server && typeof server === "object" && !Array.isArray(server) ? server : null;
15976
+ }
15977
+ function remoteProjectLinkState(state) {
15978
+ const serverRecord = serverRecordForRemoteLink(state);
15979
+ if (!serverRecord || serverRecord.kind !== "remote")
15980
+ return null;
15981
+ const link = serverRecord.remoteProjectLink;
15982
+ if (link && typeof link === "object" && !Array.isArray(link))
15983
+ return link;
15984
+ const direct = state.data.remoteProjectLink;
15985
+ if (direct && typeof direct === "object" && !Array.isArray(direct))
15986
+ return direct;
15987
+ return null;
15988
+ }
15989
+ function shouldOfferRemoteLinkRepair(state, message2) {
15990
+ const link = remoteProjectLinkState(state);
15991
+ if (link) {
15992
+ if (link.ok === true)
15993
+ return false;
15994
+ const status = link.status ?? "";
15995
+ return REPAIRABLE_REMOTE_LINK_STATUSES.has(status);
15996
+ }
15997
+ const serverRecord = serverRecordForRemoteLink(state);
15998
+ if (serverRecord && serverRecord.kind !== "remote")
15999
+ return false;
16000
+ return remoteProjectLinkError(message2 ?? state.error?.message);
16001
+ }
16002
+ var REPAIRABLE_REMOTE_LINK_STATUSES;
16003
+ var init_remote_link = __esm(() => {
16004
+ REPAIRABLE_REMOTE_LINK_STATUSES = new Set([
16005
+ "auth_required",
16006
+ "project_not_registered",
16007
+ "no_server_checkout",
16008
+ "invalid_root",
16009
+ "needs_prepare",
16010
+ "error"
16011
+ ]);
16012
+ });
16013
+
15825
16014
  // packages/cli/src/app-opentui/fleet-stats.ts
15826
16015
  function normalizeStatus(status) {
15827
16016
  return status.replace(/_/g, "-").toLowerCase();
@@ -16012,7 +16201,9 @@ import {
16012
16201
  RigInboxInputsOutput,
16013
16202
  RigRunListOutput,
16014
16203
  RigRunShowOutput,
16204
+ RigServerRepairLinkOutput,
16015
16205
  RigServerStatusOutput,
16206
+ RigServerUseOutput,
16016
16207
  RigStatsOutput,
16017
16208
  RigTaskListOutput,
16018
16209
  RigTaskShowOutput
@@ -16048,7 +16239,9 @@ var init__json_output = __esm(() => {
16048
16239
  "task show": RigTaskShowOutput,
16049
16240
  "run list": RigRunListOutput,
16050
16241
  "run show": RigRunShowOutput,
16242
+ "server use": RigServerUseOutput,
16051
16243
  "server status": RigServerStatusOutput,
16244
+ "server repair-link": RigServerRepairLinkOutput,
16052
16245
  "inbox approvals": RigInboxApprovalsOutput,
16053
16246
  "inbox inputs": RigInboxInputsOutput,
16054
16247
  "doctor check": RigDoctorCheckOutput,
@@ -16058,7 +16251,7 @@ var init__json_output = __esm(() => {
16058
16251
 
16059
16252
  // packages/cli/src/launcher.ts
16060
16253
  import { existsSync as existsSync22 } from "fs";
16061
- import { basename as basename5, resolve as resolve29 } from "path";
16254
+ import { basename as basename4, resolve as resolve29 } from "path";
16062
16255
  import { loadDotEnvSecrets } from "@rig/runtime/baked-secrets";
16063
16256
  import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME, resolveNearestRigProjectRoot } from "@rig/runtime/layout";
16064
16257
  function parsePolicyMode(value) {
@@ -16083,7 +16276,7 @@ function resolveProjectRoot({
16083
16276
  return resolve29(cwd, envProjectRoot);
16084
16277
  }
16085
16278
  const fallbackImportDir = importDir ?? cwd;
16086
- const execName = basename5(execPath).toLowerCase();
16279
+ const execName = basename4(execPath).toLowerCase();
16087
16280
  const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve29(execPath, "..", "..")] : [];
16088
16281
  const candidates = [cwd, ...execCandidates, resolve29(fallbackImportDir, "..")];
16089
16282
  for (const candidate of candidates) {
@@ -16379,16 +16572,31 @@ function taskViewIntent(argv, command = "", rest = [], raw) {
16379
16572
  const { payload, label } = taskViewPayload(command, rest);
16380
16573
  return intent("tasks", argv, "refresh", payload, label, raw);
16381
16574
  }
16382
- function routeServer(argv, command, raw, checkingLabel) {
16383
- if (!command || command === "status") {
16575
+ function routeServer(argv, command, rest, raw, checkingLabel) {
16576
+ if (!command || command === "status" || command === "list") {
16384
16577
  return intent("server", argv, "refresh", undefined, command ? checkingLabel : "Loading server", raw);
16385
16578
  }
16579
+ if (command === "use") {
16580
+ const alias = firstNonOption(rest);
16581
+ if (alias === "local")
16582
+ return intent("server", argv, "server-use-local", undefined, "Use local server", raw);
16583
+ if (alias)
16584
+ return intent("server", argv, "server-use-remote", { alias }, `Use ${alias}`, raw);
16585
+ return intent("server", argv, "refresh", undefined, "Select a remote alias in Server", raw);
16586
+ }
16587
+ if (command === "add")
16588
+ return intent("server", argv, "server-add-remote", { argv: [...argv] }, "Add remote server", raw);
16589
+ if (command === "repair-link")
16590
+ return intent("server", argv, "remote-link-repair", undefined, "Repair remote link", raw);
16386
16591
  return commandRunIntent(argv, raw);
16387
16592
  }
16388
16593
  function routeGithub(argv, command, rest, raw) {
16389
16594
  if (command === "auth" && (rest[0]?.toLowerCase() ?? "status") === "status" && rest.length <= 1) {
16390
16595
  return intent("server", argv, "refresh", undefined, "Checking GitHub auth", raw);
16391
16596
  }
16597
+ if (command === "auth" && rest[0]?.toLowerCase() === "import-gh" && rest.length <= 1) {
16598
+ return intent("server", argv, "github-auth-import", undefined, "Import GitHub auth", raw);
16599
+ }
16392
16600
  return commandRunIntent(argv, raw);
16393
16601
  }
16394
16602
  function routeDoctor(argv, command, raw) {
@@ -16437,11 +16645,16 @@ function intentFromArgv(argv) {
16437
16645
  return commandRunIntent(argv);
16438
16646
  }
16439
16647
  if (normalizedGroup === "server")
16440
- return routeServer(argv, normalizedCommand, undefined, "Checking server");
16648
+ return routeServer(argv, normalizedCommand, rest, undefined, "Checking server");
16441
16649
  if (normalizedGroup === "github")
16442
16650
  return routeGithub(argv, normalizedCommand, rest);
16443
- if (normalizedGroup === "init")
16444
- return commandRunIntent(argv);
16651
+ if (normalizedGroup === "init") {
16652
+ if (!normalizedCommand)
16653
+ return intent("init", argv, "refresh", undefined, "Open init");
16654
+ if (rest.includes("--yes") || normalizedCommand === "--yes")
16655
+ return intent("init", argv, "init-start", undefined, "Run init");
16656
+ return intent("init", argv, "refresh", undefined, "Open init");
16657
+ }
16445
16658
  if (normalizedGroup === "doctor")
16446
16659
  return routeDoctor(argv, normalizedCommand);
16447
16660
  if (normalizedGroup === "inbox")
@@ -16504,12 +16717,17 @@ function intentFromTypeBar(value) {
16504
16717
  return commandRunIntent(parts, value);
16505
16718
  return intent("tasks", parts, "refresh", undefined, "Select a task, then press Enter; or use run next", value);
16506
16719
  }
16507
- if (first === "init")
16508
- return commandRunIntent(parts, value);
16720
+ if (first === "init") {
16721
+ if (!second)
16722
+ return intent("init", parts, "refresh", undefined, "Open init", value);
16723
+ if (parts.includes("--yes"))
16724
+ return intent("init", parts, "init-start", undefined, "Run init", value);
16725
+ return intent("init", parts, "refresh", undefined, "Open init", value);
16726
+ }
16509
16727
  if (first === "doctor")
16510
16728
  return routeDoctor(parts, second, value);
16511
16729
  if (first === "server")
16512
- return routeServer(parts, second, value, "Loading server");
16730
+ return routeServer(parts, second, rest, value, "Loading server");
16513
16731
  if (first === "github")
16514
16732
  return routeGithub(parts, second, rest, value);
16515
16733
  if (first === "inbox")
@@ -16960,22 +17178,15 @@ function clearTypeBar(context, message2) {
16960
17178
  context.emitTypeBarPatch({ value: "", mode: "nav", prompt: undefined, message: message2 });
16961
17179
  }
16962
17180
  function handleEmbeddedTerminalKey(context, key) {
16963
- const state = context.getState();
16964
- const piHost = getActivePiHost();
16965
- const commandHost = getActiveCommandHost();
16966
- const host = state.scene === "handoff" ? piHost : state.scene === "command" ? commandHost : null;
17181
+ if (context.getState().scene !== "command")
17182
+ return false;
17183
+ const host = getActiveCommandHost();
16967
17184
  if (!host)
16968
17185
  return false;
16969
17186
  if (key.ctrl && key.name === "]") {
16970
- if (state.scene === "handoff") {
16971
- stopActivePiHost("operator detach");
16972
- clearTypeBar(context, "detached from Pi");
16973
- context.runAppAction("Opening runs", () => context.runtime.runIntent({ scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } }));
16974
- } else {
16975
- stopActiveCommandHost("operator detach");
16976
- clearTypeBar(context, "closed command");
16977
- context.runAppAction("Project menu", () => context.runtime.runIntent({ scene: "main", argv: ["main"], action: { kind: "none", label: "Project menu" } }));
16978
- }
17187
+ stopActiveCommandHost("operator detach");
17188
+ clearTypeBar(context, "closed command");
17189
+ context.runAppAction("Project menu", () => context.runtime.runIntent({ scene: "main", argv: ["main"], action: { kind: "none", label: "Project menu" } }));
16979
17190
  return true;
16980
17191
  }
16981
17192
  const sequence = key.raw || key.sequence;
@@ -17184,7 +17395,6 @@ var init_keymap = __esm(() => {
17184
17395
  init_autocomplete();
17185
17396
  init_command_pty_host();
17186
17397
  init_intent();
17187
- init_pi_pty_host();
17188
17398
  });
17189
17399
 
17190
17400
  // packages/cli/src/app-opentui/runtime-resources.ts
@@ -17341,7 +17551,7 @@ var init_constants = __esm(() => {
17341
17551
  });
17342
17552
 
17343
17553
  // packages/cli/src/app-opentui/render/graphics.ts
17344
- import { FrameBufferRenderable, RGBA as RGBA3 } from "@opentui/core";
17554
+ import { FrameBufferRenderable, RGBA as RGBA2 } from "@opentui/core";
17345
17555
  function sceneKind(scene) {
17346
17556
  if (scene === "tasks")
17347
17557
  return "loop";
@@ -17436,7 +17646,7 @@ function paletteColor(ac, c2, mg, ink5) {
17436
17646
  const b = (AC_RGB[2] * ac + C2_RGB[2] * c2 + MG_RGB[2] * mg + INK_RGB[2] * ink5) / total;
17437
17647
  const intensity = Math.min(1, total / 4);
17438
17648
  const lift = 0.34 + intensity * 0.66;
17439
- return RGBA3.fromInts(Math.round(r * lift), Math.round(g * lift), Math.round(b * lift), 255);
17649
+ return RGBA2.fromInts(Math.round(r * lift), Math.round(g * lift), Math.round(b * lift), 255);
17440
17650
  }
17441
17651
  function clearCanvas(canvas) {
17442
17652
  canvas.ac.fill(0);
@@ -17791,8 +18001,8 @@ function staticFleetTick(scene) {
17791
18001
  }
17792
18002
  function withAlpha(hex, alpha) {
17793
18003
  const a = Math.max(0, Math.min(255, Math.round(alpha * 255)));
17794
- const [r, g, b] = RGBA3.fromHex(hex).toInts();
17795
- return RGBA3.fromInts(r, g, b, a);
18004
+ const [r, g, b] = RGBA2.fromHex(hex).toInts();
18005
+ return RGBA2.fromInts(r, g, b, a);
17796
18006
  }
17797
18007
  function asciiFleetColor(char, row, alpha) {
17798
18008
  if (char === "@" || char === "$" || char === "o")
@@ -17800,7 +18010,7 @@ function asciiFleetColor(char, row, alpha) {
17800
18010
  if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
17801
18011
  return withAlpha(RIG_UI.cyan, alpha);
17802
18012
  if (char === "." || char === "'" || char === "_" || row < 3 || row > FLEET_GRID_HEIGHT - 4) {
17803
- return RGBA3.fromInts(108, 110, 121, Math.min(170, Math.max(0, Math.min(255, Math.round(alpha * 255)))));
18013
+ return RGBA2.fromInts(108, 110, 121, Math.min(170, Math.max(0, Math.min(255, Math.round(alpha * 255)))));
17804
18014
  }
17805
18015
  return withAlpha(RIG_UI.limeDim, alpha);
17806
18016
  }
@@ -17854,13 +18064,13 @@ var init_graphics = __esm(() => {
17854
18064
  init_theme2();
17855
18065
  init_ascii_fleet();
17856
18066
  init_constants();
17857
- TRANSPARENT = RGBA3.fromValues(0, 0, 0, 0);
17858
- BACKDROP = RGBA3.fromHex(RIG_UI.bg);
17859
- PANEL_BG = RGBA3.fromInts(16, 17, 21, 184);
17860
- PANEL_HEADER_BG = RGBA3.fromInts(16, 17, 21, 188);
17861
- PANEL_LINE = RGBA3.fromInts(255, 255, 255, 20);
17862
- PANEL_LINE_DIM = RGBA3.fromInts(255, 255, 255, 8);
17863
- PANEL_TEXT_DIM = RGBA3.fromInts(108, 110, 121, 255);
18067
+ TRANSPARENT = RGBA2.fromValues(0, 0, 0, 0);
18068
+ BACKDROP = RGBA2.fromHex(RIG_UI.bg);
18069
+ PANEL_BG = RGBA2.fromInts(16, 17, 21, 184);
18070
+ PANEL_HEADER_BG = RGBA2.fromInts(16, 17, 21, 188);
18071
+ PANEL_LINE = RGBA2.fromInts(255, 255, 255, 20);
18072
+ PANEL_LINE_DIM = RGBA2.fromInts(255, 255, 255, 8);
18073
+ PANEL_TEXT_DIM = RGBA2.fromInts(108, 110, 121, 255);
17864
18074
  AC_RGB = [204, 255, 77];
17865
18075
  C2_RGB = [86, 216, 255];
17866
18076
  MG_RGB = [255, 121, 176];
@@ -17903,7 +18113,7 @@ var init_hover = __esm(() => {
17903
18113
  });
17904
18114
 
17905
18115
  // packages/cli/src/app-opentui/render/text.ts
17906
- import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
18116
+ import { RGBA as RGBA3, TextAttributes as TextAttributes3, TextRenderable } from "@opentui/core";
17907
18117
  function lineIsClickable(line2) {
17908
18118
  return line2?.selectable !== undefined || line2?.selectableIndex !== undefined;
17909
18119
  }
@@ -17912,7 +18122,7 @@ function lineSelectableId(line2) {
17912
18122
  }
17913
18123
  function lineBackground(line2) {
17914
18124
  if (line2?.bg)
17915
- return RGBA4.fromHex(line2.bg);
18125
+ return RGBA3.fromHex(line2.bg);
17916
18126
  const id = lineSelectableId(line2);
17917
18127
  if (id !== undefined && isHovered(id))
17918
18128
  return HOVER_BG;
@@ -17981,7 +18191,7 @@ function applyTextLine(renderable, line2, top, left, width) {
17981
18191
  renderable.content = line2?.styledText ?? line2?.text ?? "";
17982
18192
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
17983
18193
  renderable.bg = lineBackground(line2);
17984
- renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
18194
+ renderable.attributes = line2?.bold ? TextAttributes3.BOLD : line2?.dim ? TextAttributes3.DIM : 0;
17985
18195
  }
17986
18196
  function applyFlowTextLine(renderable, line2, width) {
17987
18197
  renderable.setRigSceneLine?.(line2);
@@ -17991,7 +18201,7 @@ function applyFlowTextLine(renderable, line2, width) {
17991
18201
  renderable.content = line2?.styledText ?? line2?.text ?? "";
17992
18202
  renderable.fg = line2?.fg ?? RIG_UI.ink2;
17993
18203
  renderable.bg = lineBackground(line2);
17994
- renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
18204
+ renderable.attributes = line2?.bold ? TextAttributes3.BOLD : line2?.dim ? TextAttributes3.DIM : 0;
17995
18205
  }
17996
18206
  function clearTextLines(renderables, from = 0) {
17997
18207
  for (let index = from;index < renderables.length; index += 1) {
@@ -18008,19 +18218,30 @@ var TRANSPARENT2, HOVER_BG;
18008
18218
  var init_text = __esm(() => {
18009
18219
  init_theme2();
18010
18220
  init_hover();
18011
- TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
18012
- HOVER_BG = RGBA4.fromHex(RIG_UI.hover);
18221
+ TRANSPARENT2 = RGBA3.fromInts(0, 0, 0, 0);
18222
+ HOVER_BG = RGBA3.fromHex(RIG_UI.hover);
18013
18223
  });
18014
18224
 
18225
+ // packages/cli/src/app-opentui/render/terminal-handoff.ts
18226
+ function resumeRendererClean(renderer) {
18227
+ try {
18228
+ renderer.pendingSuspendedTerminalSetup = true;
18229
+ } catch {}
18230
+ renderer.resume();
18231
+ try {
18232
+ renderer.requestRender?.();
18233
+ } catch {}
18234
+ }
18235
+
18015
18236
  // packages/cli/src/app-opentui/render/panels.ts
18016
- import { RGBA as RGBA5, ScrollBoxRenderable } from "@opentui/core";
18237
+ import { RGBA as RGBA4, ScrollBoxRenderable } from "@opentui/core";
18017
18238
  function hexToRgba(hex, alpha) {
18018
18239
  const clean = hex.replace(/^#/, "");
18019
18240
  const full = clean.length === 3 ? clean.split("").map((part) => `${part}${part}`).join("") : clean;
18020
18241
  const value = Number.parseInt(full, 16);
18021
18242
  if (!Number.isFinite(value))
18022
- return RGBA5.fromInts(14, 15, 17, alpha);
18023
- return RGBA5.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255, alpha);
18243
+ return RGBA4.fromInts(14, 15, 17, alpha);
18244
+ return RGBA4.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255, alpha);
18024
18245
  }
18025
18246
  function panelBackground(panel) {
18026
18247
  return hexToRgba(panel.backgroundColor ?? RIG_UI.panel, PANEL_OPAQUE_ALPHA);
@@ -18137,12 +18358,12 @@ var TRANSPARENT3, MAX_PANEL_LINES = 420, PANEL_OPAQUE_ALPHA = 255, PANEL_BORDER;
18137
18358
  var init_panels = __esm(() => {
18138
18359
  init_theme2();
18139
18360
  init_text();
18140
- TRANSPARENT3 = RGBA5.fromInts(0, 0, 0, 0);
18361
+ TRANSPARENT3 = RGBA4.fromInts(0, 0, 0, 0);
18141
18362
  PANEL_BORDER = hexToRgba(RIG_UI.border, 255);
18142
18363
  });
18143
18364
 
18144
18365
  // packages/cli/src/app-opentui/render/type-bar.ts
18145
- import { BoxRenderable, InputRenderable, InputRenderableEvents, RGBA as RGBA6, StyledText as StyledText3, TextRenderable as TextRenderable2 } from "@opentui/core";
18366
+ import { BoxRenderable, InputRenderable, InputRenderableEvents, RGBA as RGBA5, StyledText as StyledText2, TextRenderable as TextRenderable2 } from "@opentui/core";
18146
18367
  function createTypeBar(renderer) {
18147
18368
  const background = new BoxRenderable(renderer, {
18148
18369
  id: "typebar-card",
@@ -18289,13 +18510,13 @@ function formatFooter(footer, width) {
18289
18510
  chunks.push(cell4.style(text2));
18290
18511
  used += visibleWidth(text2);
18291
18512
  });
18292
- return new StyledText3(chunks);
18513
+ return new StyledText2(chunks);
18293
18514
  }
18294
18515
  var TYPEBAR_BG, TYPEBAR_BORDER, FOOTER_SEPARATOR = " \xB7 ";
18295
18516
  var init_type_bar = __esm(() => {
18296
18517
  init_theme2();
18297
- TYPEBAR_BG = RGBA6.fromInts(20, 25, 14, 224);
18298
- TYPEBAR_BORDER = RGBA6.fromInts(204, 255, 77, 92);
18518
+ TYPEBAR_BG = RGBA5.fromInts(20, 25, 14, 224);
18519
+ TYPEBAR_BORDER = RGBA5.fromInts(204, 255, 77, 92);
18299
18520
  });
18300
18521
 
18301
18522
  // packages/cli/src/app-opentui/render/native-host.ts
@@ -18303,7 +18524,7 @@ import {
18303
18524
  BoxRenderable as BoxRenderable2,
18304
18525
  CodeRenderable,
18305
18526
  DiffRenderable,
18306
- RGBA as RGBA7,
18527
+ RGBA as RGBA6,
18307
18528
  ScrollBoxRenderable as ScrollBoxRenderable2,
18308
18529
  SyntaxStyle,
18309
18530
  TextRenderable as TextRenderable3,
@@ -18313,19 +18534,19 @@ function rigSyntaxStyle() {
18313
18534
  if (syntaxStyle)
18314
18535
  return syntaxStyle;
18315
18536
  syntaxStyle = SyntaxStyle.fromStyles({
18316
- keyword: { fg: RGBA7.fromHex(RIG_UI.magenta), bold: true },
18317
- string: { fg: RGBA7.fromHex(RIG_UI.lime) },
18318
- number: { fg: RGBA7.fromHex(RIG_UI.cyan) },
18319
- comment: { fg: RGBA7.fromHex(RIG_UI.ink4), italic: true },
18320
- function: { fg: RGBA7.fromHex(RIG_UI.cyan) },
18321
- type: { fg: RGBA7.fromHex(RIG_UI.limeDim) },
18322
- constant: { fg: RGBA7.fromHex(RIG_UI.yellow) },
18323
- default: { fg: RGBA7.fromHex(RIG_UI.ink2) }
18537
+ keyword: { fg: RGBA6.fromHex(RIG_UI.magenta), bold: true },
18538
+ string: { fg: RGBA6.fromHex(RIG_UI.lime) },
18539
+ number: { fg: RGBA6.fromHex(RIG_UI.cyan) },
18540
+ comment: { fg: RGBA6.fromHex(RIG_UI.ink4), italic: true },
18541
+ function: { fg: RGBA6.fromHex(RIG_UI.cyan) },
18542
+ type: { fg: RGBA6.fromHex(RIG_UI.limeDim) },
18543
+ constant: { fg: RGBA6.fromHex(RIG_UI.yellow) },
18544
+ default: { fg: RGBA6.fromHex(RIG_UI.ink2) }
18324
18545
  });
18325
18546
  return syntaxStyle;
18326
18547
  }
18327
18548
  function tableContent(rows) {
18328
- return rows.map((row, rowIndex) => row.map((cell4) => [{ __isChunk: true, text: cell4, fg: RGBA7.fromHex(rowIndex === 0 ? RIG_UI.ink3 : RIG_UI.ink2) }]));
18549
+ return rows.map((row, rowIndex) => row.map((cell4) => [{ __isChunk: true, text: cell4, fg: RGBA6.fromHex(rowIndex === 0 ? RIG_UI.ink3 : RIG_UI.ink2) }]));
18329
18550
  }
18330
18551
  function createNativeHost(renderer) {
18331
18552
  try {
@@ -18474,10 +18695,14 @@ var init_preloader = __esm(() => {
18474
18695
  });
18475
18696
 
18476
18697
  // packages/cli/src/app-opentui/scenes/error.ts
18698
+ function errorActions(state) {
18699
+ return shouldOfferRemoteLinkRepair(state, state.error?.message) ? [...ERROR_ACTIONS, ERROR_REPAIR_LINK_ACTION] : ERROR_ACTIONS;
18700
+ }
18477
18701
  function renderErrorScene(state) {
18478
18702
  const message2 = state.error?.message ?? "Unknown app error";
18479
18703
  const hint = state.error?.hint ?? "Use the actions below to recover.";
18480
- const selected = Math.max(0, Math.min(3, state.selection.index));
18704
+ const actions = errorActions(state);
18705
+ const selected = Math.max(0, Math.min(actions.length - 1, state.selection.index));
18481
18706
  return makeSceneFrame({
18482
18707
  scene: "error",
18483
18708
  title: "Error",
@@ -18495,7 +18720,7 @@ function renderErrorScene(state) {
18495
18720
  ...hint ? [line(hint, { fg: RIG_UI.yellow })] : [],
18496
18721
  line("", { fg: RIG_UI.ink3 }),
18497
18722
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
18498
- ...ERROR_ACTIONS.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item))
18723
+ ...actions.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item))
18499
18724
  ],
18500
18725
  backgroundColor: RIG_UI.panel,
18501
18726
  backgroundAlpha: 184,
@@ -18512,18 +18737,19 @@ function renderErrorScene(state) {
18512
18737
  live: true
18513
18738
  });
18514
18739
  }
18515
- var ERROR_ACTIONS;
18740
+ var ERROR_ACTIONS, ERROR_REPAIR_LINK_ACTION;
18516
18741
  var init_error = __esm(() => {
18517
18742
  init_drone();
18518
18743
  init_theme2();
18744
+ init_remote_link();
18519
18745
  init_scene();
18520
18746
  init_selectable();
18521
18747
  ERROR_ACTIONS = [
18522
18748
  { detail: "return to dashboard", item: { id: "main", label: "main", intent: { scene: "main", argv: [], action: { kind: "refresh", label: "Back to dashboard" } }, message: "back to dashboard" } },
18523
18749
  { detail: "run diagnostics", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "run diagnostics" } },
18524
- { 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" } },
18525
- { 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" } }
18750
+ { 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" } }
18526
18751
  ];
18752
+ 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" } };
18527
18753
  });
18528
18754
 
18529
18755
  // packages/cli/src/app-opentui/scenes/help.ts
@@ -18698,7 +18924,7 @@ function configuredMenuRows(selectedIndex2) {
18698
18924
  }));
18699
18925
  }
18700
18926
  function onboardingRows(selectedIndex2) {
18701
- const primary = selectableDeckRow({ label: "setup", detail: "Start guided setup \u2014 recommended", active: selectedIndex2 === 0 }, ONBOARD_PRIMARY);
18927
+ const primary = selectableDeckRow({ label: "init", detail: "Start guided init \u2014 recommended", active: selectedIndex2 === 0 }, ONBOARD_PRIMARY);
18702
18928
  const secondary = ONBOARD_SECONDARY.map(([label, detail, item], index) => ({
18703
18929
  ...deckRow({ label, detail, index: index + 1, active: selectedIndex2 === index + 1, activateOnClick: true }),
18704
18930
  selectable: item
@@ -18758,7 +18984,7 @@ function renderMainScene(state, layout) {
18758
18984
  statusLine(state)
18759
18985
  ] : [
18760
18986
  line("WELCOME", { fg: RIG_UI.ink, bold: true }),
18761
- line("This project isn't set up yet. One guided flow gets you running.", { fg: RIG_UI.ink2 }),
18987
+ line("This project isn't initialized yet. One native init flow gets you running.", { fg: RIG_UI.ink2 }),
18762
18988
  blank(),
18763
18989
  line("GET STARTED", { fg: RIG_UI.ink, bold: true }),
18764
18990
  ...onboardingRows(actionSelected),
@@ -18799,7 +19025,7 @@ var init_main = __esm(() => {
18799
19025
  init_theme2();
18800
19026
  init_fleet_stats();
18801
19027
  CONFIGURED_MENU = [
18802
- ["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" }],
19028
+ ["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" }],
18803
19029
  ["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" }],
18804
19030
  ["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" }],
18805
19031
  ["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" }],
@@ -18818,10 +19044,10 @@ var init_main = __esm(() => {
18818
19044
  ["help", "all actions, shortcuts, mouse controls", { id: "help", label: "help", intent: { scene: "help", argv: ["help"], action: { kind: "none", label: "Opening help" } }, message: "selected help" }]
18819
19045
  ];
18820
19046
  ONBOARD_PRIMARY = {
18821
- id: "onboard-setup",
18822
- label: "Start guided setup",
18823
- intent: { scene: "init", argv: ["init"], action: { kind: "refresh", label: "Setup" } },
18824
- message: "open the guided setup checklist"
19047
+ id: "onboard-init",
19048
+ label: "Start guided init",
19049
+ intent: { scene: "init", argv: ["init"], action: { kind: "refresh", label: "Init" } },
19050
+ message: "open the native init checklist"
18825
19051
  };
18826
19052
  ONBOARD_SECONDARY = [
18827
19053
  ["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" }],
@@ -18834,6 +19060,12 @@ var init_main = __esm(() => {
18834
19060
  TILE_TASKS = { id: "tile-tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Opening tasks" } }, message: "ready tasks \u2014 dispatch work" };
18835
19061
  });
18836
19062
 
19063
+ // packages/cli/src/app-opentui/pi-pty-host.ts
19064
+ function getActivePiHost() {
19065
+ return null;
19066
+ }
19067
+ function stopActivePiHost(_reason) {}
19068
+
18837
19069
  // packages/cli/src/app-opentui/runtime.ts
18838
19070
  var exports_runtime = {};
18839
19071
  __export(exports_runtime, {
@@ -18853,7 +19085,7 @@ __export(exports_runtime, {
18853
19085
  });
18854
19086
  import { existsSync as existsSync23 } from "fs";
18855
19087
  import { resolve as resolve30 } from "path";
18856
- import { BoxRenderable as BoxRenderable3, RGBA as RGBA8, StyledText as StyledText4, TextRenderable as TextRenderable4, createCliRenderer } from "@opentui/core";
19088
+ import { BoxRenderable as BoxRenderable3, RGBA as RGBA7, StyledText as StyledText3, TextRenderable as TextRenderable4, createCliRenderer } from "@opentui/core";
18857
19089
  function inboxPendingCount(state) {
18858
19090
  const inbox = state.data.inbox;
18859
19091
  if (!inbox || typeof inbox !== "object" || Array.isArray(inbox))
@@ -18900,7 +19132,7 @@ function entryIntentForScene(scene) {
18900
19132
  case "doctor":
18901
19133
  return { scene, argv: ["doctor"], action: { kind: "doctor-run", label: "Doctor" } };
18902
19134
  case "init":
18903
- return { scene, argv: ["init"], action: { kind: "refresh", label: "Setup" } };
19135
+ return { scene, argv: ["init"], action: { kind: "refresh", label: "Init" } };
18904
19136
  case "run-detail":
18905
19137
  return { scene, argv: ["run", "status"], action: { kind: "refresh", label: "Run" } };
18906
19138
  case "pi":
@@ -18989,7 +19221,7 @@ function sceneSectionLabel(scene) {
18989
19221
  handoff: "pi console",
18990
19222
  server: "server",
18991
19223
  doctor: "doctor",
18992
- init: "setup",
19224
+ init: "init",
18993
19225
  pi: "pi",
18994
19226
  plugin: "plugins",
18995
19227
  repo: "repo",
@@ -19022,7 +19254,7 @@ function buildNavStrip(state) {
19022
19254
  chunks.push(otuiBold(styles.yellow(String(badgeCount))));
19023
19255
  }
19024
19256
  });
19025
- return new StyledText4(chunks);
19257
+ return new StyledText3(chunks);
19026
19258
  }
19027
19259
  function requestRender(renderer) {
19028
19260
  renderer.requestRender?.();
@@ -19250,7 +19482,8 @@ async function launchRigOpenTuiApp(options) {
19250
19482
  },
19251
19483
  suspend: () => renderer?.suspend(),
19252
19484
  resume: () => {
19253
- renderer?.resume();
19485
+ if (renderer)
19486
+ resumeRendererClean(renderer);
19254
19487
  renderApp();
19255
19488
  },
19256
19489
  destroy: () => renderer?.destroy()
@@ -19478,7 +19711,7 @@ async function launchRigOpenTuiApp(options) {
19478
19711
  }
19479
19712
  for (const adapter of options.adapters ?? [])
19480
19713
  await adapter.start?.(runtime);
19481
- liveEvents = await connectRigServerEvents(options.projectRoot, {
19714
+ connectRigServerEvents(options.projectRoot, {
19482
19715
  onSnapshotInvalidated: () => refreshCurrentDataScene(),
19483
19716
  onRunLogAppended: () => {
19484
19717
  const scene = store.getState().scene;
@@ -19486,11 +19719,17 @@ async function launchRigOpenTuiApp(options) {
19486
19719
  refreshCurrentDataScene();
19487
19720
  },
19488
19721
  onStatus: (status) => store.patch({ footer: { live: liveLinkLabel(status) } })
19489
- }).catch(() => null);
19490
- if (!liveEvents)
19491
- store.patch({ footer: { live: liveLinkLabel("disconnected") } });
19492
- if (liveEvents)
19722
+ }).then((subscription) => {
19723
+ if (destroyed) {
19724
+ subscription.close();
19725
+ return;
19726
+ }
19727
+ liveEvents = subscription;
19493
19728
  resources.add(() => liveEvents?.close());
19729
+ }).catch(() => {
19730
+ if (!destroyed)
19731
+ store.patch({ footer: { live: liveLinkLabel("disconnected") } });
19732
+ });
19494
19733
  await runtime.runIntent(store.getState().intent);
19495
19734
  } catch (error) {
19496
19735
  const normalized = normalizeError(error);
@@ -19676,15 +19915,14 @@ var init_runtime = __esm(() => {
19676
19915
  init_help();
19677
19916
  init_main();
19678
19917
  init_selectable();
19679
- init_pi_pty_host();
19680
- PRELOADER_BACKDROP = RGBA8.fromHex(RIG_UI.bg);
19918
+ PRELOADER_BACKDROP = RGBA7.fromHex(RIG_UI.bg);
19681
19919
  PRIMARY_NAV = [
19682
19920
  { id: "cockpit", label: "Cockpit", scene: "main", argv: ["main"], enterLabel: "Cockpit", member: ["main"] },
19683
19921
  { id: "runs", label: "Runs", scene: "fleet", argv: ["runs"], enterLabel: "Runs", member: ["fleet", "run-detail", "inspect", "handoff"] },
19684
19922
  { id: "tasks", label: "Tasks", scene: "tasks", argv: ["tasks"], enterLabel: "Tasks", member: ["tasks"] },
19685
19923
  { id: "inbox", label: "Inbox", scene: "inbox", argv: ["inbox"], enterLabel: "Inbox", member: ["inbox"], badge: inboxPendingCount },
19686
19924
  { id: "stats", label: "Stats", scene: "family", argv: ["stats"], enterLabel: "Stats", member: ["family"] },
19687
- { id: "setup", label: "Setup", scene: "init", argv: ["init"], enterLabel: "Setup", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
19925
+ { id: "init", label: "Init", scene: "init", argv: ["init"], enterLabel: "Init", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
19688
19926
  { id: "help", label: "Help", scene: "help", argv: ["help"], enterLabel: "Help", member: ["help"] }
19689
19927
  ];
19690
19928
  SCENE_DATA_KEY = {
@@ -19794,10 +20032,10 @@ var init_scroll = __esm(() => {
19794
20032
  // packages/cli/src/app-opentui/react/SceneFrameView.tsx
19795
20033
  import { useEffect as useEffect2, useRef as useRef2 } from "react";
19796
20034
  import { useRenderer as useRenderer2 } from "@opentui/react";
19797
- import { TextAttributes as TextAttributes5 } from "@opentui/core";
20035
+ import { TextAttributes as TextAttributes4 } from "@opentui/core";
19798
20036
  import { jsxDEV, Fragment } from "@opentui/react/jsx-dev-runtime";
19799
20037
  function lineAttributes(line2) {
19800
- return line2?.bold ? TextAttributes5.BOLD : line2?.dim ? TextAttributes5.DIM : 0;
20038
+ return line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
19801
20039
  }
19802
20040
  function lineContent(line2) {
19803
20041
  return line2.styledText ?? line2.text ?? "";
@@ -20048,7 +20286,7 @@ var init_nav = __esm(() => {
20048
20286
  { id: "tasks", label: "Tasks", scene: "tasks", argv: ["tasks"], enterLabel: "Tasks", member: ["tasks"] },
20049
20287
  { id: "inbox", label: "Inbox", scene: "inbox", argv: ["inbox"], enterLabel: "Inbox", member: ["inbox"], badge: inboxPendingCount2 },
20050
20288
  { id: "stats", label: "Stats", scene: "family", argv: ["stats"], enterLabel: "Stats", member: ["family"] },
20051
- { id: "setup", label: "Setup", scene: "init", argv: ["init"], enterLabel: "Setup", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
20289
+ { id: "init", label: "Init", scene: "init", argv: ["init"], enterLabel: "Init", member: ["init", "doctor", "server", "pi", "plugin", "repo", "workspace"] },
20052
20290
  { id: "help", label: "Help", scene: "help", argv: ["help"], enterLabel: "Help", member: ["help"] }
20053
20291
  ];
20054
20292
  });
@@ -20180,8 +20418,48 @@ async function launchRigReactApp(options) {
20180
20418
  const store = createAppStore(initialState);
20181
20419
  const events = createAppEventBus();
20182
20420
  let renderer = null;
20421
+ let root = null;
20183
20422
  let runnerPromise = null;
20184
20423
  let suppressHistoryPush = false;
20424
+ let resolveExit = () => {};
20425
+ const appExit = new Promise((resolve31) => {
20426
+ resolveExit = resolve31;
20427
+ });
20428
+ let relaunching = false;
20429
+ const mountRenderer = async () => {
20430
+ renderer = await createCliRenderer2({
20431
+ screenMode: "alternate-screen",
20432
+ exitOnCtrlC: false,
20433
+ targetFps: 30,
20434
+ maxFps: 30,
20435
+ useMouse: true,
20436
+ autoFocus: false,
20437
+ useKittyKeyboard: { disambiguate: true }
20438
+ });
20439
+ renderer.on("destroy", () => {
20440
+ if (!relaunching)
20441
+ resolveExit();
20442
+ });
20443
+ root = createRoot(renderer);
20444
+ root.render(/* @__PURE__ */ jsxDEV4(RigAppProvider, {
20445
+ value: { store, runtime, runAppAction },
20446
+ children: /* @__PURE__ */ jsxDEV4(App, {
20447
+ sceneRenderers: options.sceneRenderers
20448
+ }, undefined, false, undefined, this)
20449
+ }, undefined, false, undefined, this));
20450
+ relaunching = false;
20451
+ };
20452
+ const teardownRenderer = () => {
20453
+ relaunching = true;
20454
+ try {
20455
+ root?.unmount();
20456
+ } catch {}
20457
+ root = null;
20458
+ try {
20459
+ renderer?.destroy();
20460
+ } catch {}
20461
+ renderer = null;
20462
+ };
20185
20463
  const runAppAction = (label, action) => {
20186
20464
  action().catch((error) => {
20187
20465
  const n = normalizeError(error);
@@ -20223,8 +20501,10 @@ async function launchRigReactApp(options) {
20223
20501
  runnerPromise ??= options.initializeRuntime();
20224
20502
  return runnerPromise;
20225
20503
  },
20226
- suspend: () => renderer?.suspend(),
20227
- resume: () => renderer?.resume(),
20504
+ suspend: () => teardownRenderer(),
20505
+ resume: async () => {
20506
+ await mountRenderer();
20507
+ },
20228
20508
  destroy: () => renderer?.destroy()
20229
20509
  };
20230
20510
  events.subscribe((event) => store.patch(reduceAppEvent(store.getState(), event)));
@@ -20232,15 +20512,7 @@ async function launchRigReactApp(options) {
20232
20512
  if (typeof tick.unref === "function") {
20233
20513
  tick.unref();
20234
20514
  }
20235
- renderer = await createCliRenderer2({
20236
- screenMode: "alternate-screen",
20237
- exitOnCtrlC: false,
20238
- targetFps: 30,
20239
- maxFps: 30,
20240
- useMouse: true,
20241
- autoFocus: false,
20242
- useKittyKeyboard: { disambiguate: true }
20243
- });
20515
+ await mountRenderer();
20244
20516
  let liveEvents = null;
20245
20517
  const canAutoRefresh = (state) => {
20246
20518
  if (state.status === "action" || state.status === "loading")
@@ -20271,12 +20543,6 @@ async function launchRigReactApp(options) {
20271
20543
  }, FALLBACK_REFRESH_MS);
20272
20544
  if (typeof poll.unref === "function")
20273
20545
  poll.unref();
20274
- createRoot(renderer).render(/* @__PURE__ */ jsxDEV4(RigAppProvider, {
20275
- value: { store, runtime, runAppAction },
20276
- children: /* @__PURE__ */ jsxDEV4(App, {
20277
- sceneRenderers: options.sceneRenderers
20278
- }, undefined, false, undefined, this)
20279
- }, undefined, false, undefined, this));
20280
20546
  queueMicrotask(() => {
20281
20547
  (async () => {
20282
20548
  try {
@@ -20286,7 +20552,7 @@ async function launchRigReactApp(options) {
20286
20552
  }
20287
20553
  for (const adapter of options.adapters ?? [])
20288
20554
  await adapter.start?.(runtime);
20289
- liveEvents = await connectRigServerEvents(options.projectRoot, {
20555
+ connectRigServerEvents(options.projectRoot, {
20290
20556
  onSnapshotInvalidated: () => refreshCurrentDataScene(),
20291
20557
  onRunLogAppended: () => {
20292
20558
  const scene = store.getState().scene;
@@ -20294,9 +20560,11 @@ async function launchRigReactApp(options) {
20294
20560
  refreshCurrentDataScene();
20295
20561
  },
20296
20562
  onStatus: (status) => store.patch({ footer: { live: liveLinkLabel(status) } })
20297
- }).catch(() => null);
20298
- if (!liveEvents)
20563
+ }).then((subscription) => {
20564
+ liveEvents = subscription;
20565
+ }).catch(() => {
20299
20566
  store.patch({ footer: { live: liveLinkLabel("disconnected") } });
20567
+ });
20300
20568
  await runtime.runIntent(store.getState().intent);
20301
20569
  } catch (error) {
20302
20570
  const n = normalizeError(error);
@@ -20304,14 +20572,11 @@ async function launchRigReactApp(options) {
20304
20572
  }
20305
20573
  })();
20306
20574
  });
20307
- await new Promise((resolve31) => {
20308
- renderer?.on("destroy", () => {
20309
- clearInterval(tick);
20310
- clearInterval(poll);
20311
- liveEvents?.close();
20312
- resolve31();
20313
- });
20314
- });
20575
+ await appExit;
20576
+ clearInterval(tick);
20577
+ clearInterval(poll);
20578
+ liveEvents?.close();
20579
+ teardownRenderer();
20315
20580
  }
20316
20581
  var POLLABLE_SCENES;
20317
20582
  var init_launch = __esm(() => {
@@ -20325,7 +20590,7 @@ var init_launch = __esm(() => {
20325
20590
 
20326
20591
  // packages/cli/src/app-opentui/bootstrap.ts
20327
20592
  import { existsSync as existsSync24, readFileSync as readFileSync15 } from "fs";
20328
- import { basename as basename6, resolve as resolve31 } from "path";
20593
+ import { basename as basename5, resolve as resolve31 } from "path";
20329
20594
 
20330
20595
  // packages/cli/src/app-opentui/adapters/command.ts
20331
20596
  init_command_pty_host();
@@ -21460,6 +21725,7 @@ async function stopRunFromDetail(ctx, runId) {
21460
21725
  }
21461
21726
 
21462
21727
  // packages/cli/src/app-opentui/adapters/server.ts
21728
+ import { spawnSync as spawnSync5 } from "child_process";
21463
21729
  var SERVER_PROBE_TIMEOUT_MS = Number(process.env.RIG_SERVER_PROBE_TIMEOUT_MS) || 5000;
21464
21730
  function withTimeout(promise, ms, label) {
21465
21731
  return new Promise((resolve29, reject) => {
@@ -21497,11 +21763,128 @@ function startLocalRequested(ctx) {
21497
21763
  const action = intent.action;
21498
21764
  return action?.payload?.startLocal === true;
21499
21765
  }
21766
+ async function serverAliases(projectRoot) {
21767
+ const { readGlobalConnections: readGlobalConnections2, readRepoConnection: readRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
21768
+ const selected = readRepoConnection2(projectRoot)?.selected ?? "local";
21769
+ const global = readGlobalConnections2();
21770
+ const aliases = [{ alias: "local", kind: "local", selected: selected === "local" }];
21771
+ for (const [alias, connection] of Object.entries(global.connections)) {
21772
+ aliases.push({
21773
+ alias,
21774
+ kind: connection.kind,
21775
+ ...connection.kind === "remote" ? { baseUrl: connection.baseUrl } : {},
21776
+ selected: alias === selected
21777
+ });
21778
+ }
21779
+ return aliases;
21780
+ }
21781
+ function firstString2(parts, offset = 0) {
21782
+ return parts.slice(offset).find((part) => part.trim() && !part.startsWith("-"))?.trim();
21783
+ }
21784
+ function normalizeUrl(value) {
21785
+ const trimmed = value?.trim().replace(/\/+$/, "");
21786
+ if (!trimmed)
21787
+ return;
21788
+ return /^https?:\/\//i.test(trimmed) ? trimmed : undefined;
21789
+ }
21790
+ async function useLocalServerFromApp(ctx) {
21791
+ const label = "Selecting local server";
21792
+ emitStarted(ctx, label);
21793
+ try {
21794
+ const projectRoot = projectRootOf(ctx);
21795
+ const { readRepoConnection: readRepoConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
21796
+ const previous = readRepoConnection2(projectRoot);
21797
+ writeRepoConnection2(projectRoot, {
21798
+ selected: "local",
21799
+ ...previous?.project ? { project: previous.project } : {},
21800
+ linkedAt: new Date().toISOString()
21801
+ });
21802
+ emitCompleted(ctx, label, { selected: "local" });
21803
+ return refreshServerStatus(ctx);
21804
+ } catch (error) {
21805
+ emitFailed(ctx, label, error);
21806
+ throw error;
21807
+ }
21808
+ }
21809
+ async function useRemoteServerFromApp(ctx, alias) {
21810
+ const label = alias ? `Selecting remote ${alias}` : "Selecting remote server";
21811
+ emitStarted(ctx, label);
21812
+ try {
21813
+ const cleanAlias = alias?.trim();
21814
+ if (!cleanAlias)
21815
+ throw new Error("Enter a saved remote alias, or add one with: add remote \u2192 <alias> <https-url>.");
21816
+ const projectRoot = projectRootOf(ctx);
21817
+ const { readGlobalConnections: readGlobalConnections2, readRepoConnection: readRepoConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
21818
+ const connection = readGlobalConnections2().connections[cleanAlias];
21819
+ if (!connection)
21820
+ throw new Error(`Remote alias ${cleanAlias} is not saved yet. Use add remote first.`);
21821
+ if (connection.kind !== "remote")
21822
+ throw new Error(`${cleanAlias} is not a remote server alias.`);
21823
+ const previous = readRepoConnection2(projectRoot);
21824
+ writeRepoConnection2(projectRoot, {
21825
+ selected: cleanAlias,
21826
+ ...previous?.project ? { project: previous.project } : {},
21827
+ linkedAt: new Date().toISOString()
21828
+ });
21829
+ emitCompleted(ctx, label, { selected: cleanAlias, baseUrl: connection.baseUrl });
21830
+ return refreshServerStatus(ctx);
21831
+ } catch (error) {
21832
+ emitFailed(ctx, label, error);
21833
+ throw error;
21834
+ }
21835
+ }
21836
+ async function addRemoteServerFromApp(ctx, args) {
21837
+ const label = "Adding remote server";
21838
+ emitStarted(ctx, label);
21839
+ try {
21840
+ const alias = firstString2(args, 2);
21841
+ const url = normalizeUrl(firstString2(args, alias ? args.indexOf(alias) + 1 : 3));
21842
+ if (!alias || !url)
21843
+ throw new Error("Add remote expects: <alias> <https-url>. Example: prod https://rig.example.com");
21844
+ const projectRoot = projectRootOf(ctx);
21845
+ const { readRepoConnection: readRepoConnection2, upsertGlobalConnection: upsertGlobalConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
21846
+ upsertGlobalConnection2(alias, { kind: "remote", baseUrl: url });
21847
+ const previous = readRepoConnection2(projectRoot);
21848
+ writeRepoConnection2(projectRoot, {
21849
+ selected: alias,
21850
+ ...previous?.project ? { project: previous.project } : {},
21851
+ linkedAt: new Date().toISOString()
21852
+ });
21853
+ emitCompleted(ctx, label, { alias, baseUrl: url });
21854
+ return refreshServerStatus(ctx);
21855
+ } catch (error) {
21856
+ emitFailed(ctx, label, error);
21857
+ throw error;
21858
+ }
21859
+ }
21860
+ async function importGitHubAuthFromGhApp(ctx) {
21861
+ const label = "Importing GitHub auth";
21862
+ emitStarted(ctx, label);
21863
+ try {
21864
+ const projectRoot = projectRootOf(ctx);
21865
+ const token = spawnSync5("gh", ["auth", "token"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 1e4 }).stdout.trim();
21866
+ if (!token)
21867
+ throw new Error("Could not read GitHub token from gh. Sign in with gh auth login, then retry import auth.");
21868
+ const runtime = await runtimeOf(ctx);
21869
+ const { postGitHubTokenViaServer: postGitHubTokenViaServer2, setGitHubBearerTokenForCurrentProcess: setGitHubBearerTokenForCurrentProcess2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
21870
+ const { readRepoConnection: readRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
21871
+ const selectedRepo = readRepoConnection2(projectRoot)?.project;
21872
+ const payload = await postGitHubTokenViaServer2(runtime, token, selectedRepo ? { selectedRepo } : {});
21873
+ const apiSessionToken = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.apiSessionToken : undefined;
21874
+ setGitHubBearerTokenForCurrentProcess2(typeof apiSessionToken === "string" ? apiSessionToken : token, projectRoot);
21875
+ emitCompleted(ctx, label, { selectedRepo: selectedRepo ?? null });
21876
+ return refreshServerStatus(ctx);
21877
+ } catch (error) {
21878
+ emitFailed(ctx, label, error);
21879
+ throw error;
21880
+ }
21881
+ }
21500
21882
  async function startLocalServerFromApp(ctx) {
21501
21883
  const label = "Starting local server";
21502
21884
  emitStarted(ctx, label);
21503
21885
  patchData(ctx, {
21504
21886
  lastIntent: undefined,
21887
+ remoteProjectLink: undefined,
21505
21888
  server: { label: "starting local server", reachable: false, error: "spawning local rig-server (detached)\u2026" }
21506
21889
  });
21507
21890
  try {
@@ -21526,32 +21909,61 @@ async function startLocalServerFromApp(ctx) {
21526
21909
  reachable: false,
21527
21910
  error: error instanceof Error ? error.message : String(error)
21528
21911
  };
21529
- patchData(ctx, { server: state, lastRefreshError: state.error });
21912
+ patchData(ctx, { server: state, remoteProjectLink: undefined, lastRefreshError: state.error });
21530
21913
  patchFooter(ctx, { server: "unavailable" });
21531
- emitCompleted(ctx, label, { server: state.label, reachable: false, error: state.error });
21914
+ emitCompleted(ctx, label, { serverLabel: state.label, reachable: false, error: state.error });
21532
21915
  return state;
21533
21916
  }
21534
21917
  }
21918
+ async function repairRemoteProjectRootLinkFromApp(ctx) {
21919
+ const label = "Repairing remote project link";
21920
+ emitStarted(ctx, label);
21921
+ try {
21922
+ const projectRoot = projectRootOf(ctx);
21923
+ const { ensureRemoteProjectRootLink: ensureRemoteProjectRootLink2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
21924
+ emitProgress(ctx, label, "backfilling or preparing server-host checkout");
21925
+ const result = await ensureRemoteProjectRootLink2(projectRoot, { mode: "prepare-if-missing" });
21926
+ patchData(ctx, { remoteProjectLink: result, server: { label: result.alias ?? "remote", baseUrl: result.baseUrl, kind: "remote", reachable: result.status !== "not_remote", remoteProjectLink: result } });
21927
+ patchFooter(ctx, { server: result.alias ?? "remote", message: `remote link ${result.status}` });
21928
+ if (!result.ok) {
21929
+ const error = new Error(result.message);
21930
+ error.hint = result.hint;
21931
+ patchData(ctx, { lastRefreshError: result.message });
21932
+ throw error;
21933
+ }
21934
+ emitCompleted(ctx, label, { repoSlug: result.repoSlug, serverProjectRoot: result.serverProjectRoot, status: result.status });
21935
+ return result;
21936
+ } catch (error) {
21937
+ const message2 = error instanceof Error ? error.message : String(error);
21938
+ const existing = ctx.getState().data?.remoteProjectLink;
21939
+ const remoteProjectLink = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : { ok: false, status: "error", message: message2 };
21940
+ patchData(ctx, { lastRefreshError: message2, remoteProjectLink });
21941
+ emitCompleted(ctx, label, { ok: false, error: message2, remoteProjectLink });
21942
+ throw error;
21943
+ }
21944
+ }
21535
21945
  async function refreshServerStatus(ctx) {
21536
21946
  if (startLocalRequested(ctx)) {
21537
21947
  return startLocalServerFromApp(ctx);
21538
21948
  }
21539
21949
  const label = "Checking server";
21540
21950
  emitStarted(ctx, label);
21541
- patchData(ctx, { server: { label: "checking selected server", reachable: false, error: "checking selected server\u2026" } });
21951
+ patchData(ctx, { server: { label: "checking selected server", reachable: false, error: "checking selected server\u2026" }, remoteProjectLink: undefined });
21542
21952
  const started = Date.now();
21543
21953
  try {
21544
- const { ensureServerForCli: ensureServerForCli2, requestServerJson: requestServerJson2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
21954
+ const { ensureRemoteProjectRootLink: ensureRemoteProjectRootLink2, ensureServerForCli: ensureServerForCli2, requestServerJson: requestServerJson2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
21545
21955
  const projectRoot = projectRootOf(ctx);
21956
+ const aliases = await serverAliases(projectRoot);
21546
21957
  emitProgress(ctx, label, "resolving selected server");
21547
21958
  const [connectionLabel, server] = await Promise.all([
21548
21959
  resolveServerConnectionLabel2(projectRoot),
21549
21960
  withTimeout(ensureServerForCli2(projectRoot), 5000, "server connection")
21550
21961
  ]);
21551
21962
  emitProgress(ctx, label, "requesting server status", { baseUrl: server.baseUrl, kind: server.connectionKind });
21552
- const [status, auth] = await Promise.all([
21963
+ const [status, auth, remoteProjectLink] = await Promise.all([
21553
21964
  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) })),
21554
- 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) }))
21965
+ 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) })),
21966
+ 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)
21555
21967
  ]);
21556
21968
  const state = {
21557
21969
  label: connectionLabel,
@@ -21560,22 +21972,25 @@ async function refreshServerStatus(ctx) {
21560
21972
  reachable: true,
21561
21973
  latencyMs: Date.now() - started,
21562
21974
  status: status && typeof status === "object" && !Array.isArray(status) ? status : {},
21563
- auth: auth && typeof auth === "object" && !Array.isArray(auth) ? auth : {}
21975
+ auth: auth && typeof auth === "object" && !Array.isArray(auth) ? auth : {},
21976
+ aliases,
21977
+ ...remoteProjectLink && typeof remoteProjectLink === "object" && !Array.isArray(remoteProjectLink) ? { remoteProjectLink } : {}
21564
21978
  };
21565
- patchData(ctx, { server: state });
21979
+ patchData(ctx, { server: state, remoteProjectLink: state.remoteProjectLink });
21566
21980
  patchFooter(ctx, { server: connectionLabel, latency: `${state.latencyMs}ms` });
21567
- emitCompleted(ctx, label, { server: connectionLabel, latencyMs: state.latencyMs });
21981
+ emitCompleted(ctx, label, { serverLabel: connectionLabel, latencyMs: state.latencyMs });
21568
21982
  return state;
21569
21983
  } catch (error) {
21570
21984
  const state = {
21571
21985
  label: "server unavailable",
21572
21986
  reachable: false,
21573
21987
  latencyMs: Date.now() - started,
21988
+ aliases: await serverAliases(projectRootOf(ctx)).catch(() => []),
21574
21989
  error: error instanceof Error ? error.message : String(error)
21575
21990
  };
21576
- patchData(ctx, { server: state });
21991
+ patchData(ctx, { server: state, remoteProjectLink: undefined });
21577
21992
  patchFooter(ctx, { server: "unavailable" });
21578
- emitCompleted(ctx, label, { server: state.label, reachable: false, error: state.error });
21993
+ emitCompleted(ctx, label, { serverLabel: state.label, reachable: false, error: state.error });
21579
21994
  return state;
21580
21995
  }
21581
21996
  }
@@ -22162,7 +22577,7 @@ init_theme2();
22162
22577
  var DOCTOR_ACTIONS = [
22163
22578
  { detail: "run diagnostics now", item: { id: "run", label: "run", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "run diagnostics" } },
22164
22579
  { 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" } },
22165
- { 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" } },
22580
+ { 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" } },
22166
22581
  { detail: "open tasks", item: { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks" } }
22167
22582
  ];
22168
22583
  function checks(state) {
@@ -22238,6 +22653,7 @@ function renderDoctorScene(state, layout) {
22238
22653
  // packages/cli/src/app-opentui/scenes/family.ts
22239
22654
  init_scene();
22240
22655
  init_selectable();
22656
+ init_remote_link();
22241
22657
  init_theme2();
22242
22658
 
22243
22659
  // packages/cli/src/app-opentui/scenes/family-domains/kit.ts
@@ -24000,13 +24416,18 @@ function overviewBody(state, family, snap, layout) {
24000
24416
  function familyRecoveryRows(state, family, errorMessage2) {
24001
24417
  const selected = Math.max(0, state.selection.index);
24002
24418
  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 });
24003
- return [
24419
+ const repairRemote = shouldOfferRemoteLinkRepair(state, errorMessage2);
24420
+ const rows = [
24004
24421
  intro,
24005
- blank(),
24006
- 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" }),
24007
- 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" }),
24008
- 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" })
24422
+ blank()
24009
24423
  ];
24424
+ let index = 0;
24425
+ if (repairRemote) {
24426
+ 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" }));
24427
+ index += 1;
24428
+ }
24429
+ 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" }));
24430
+ return rows;
24010
24431
  }
24011
24432
  function renderFamilyScene(state, layout) {
24012
24433
  const family = typeof state.data.familyName === "string" ? state.data.familyName : "";
@@ -24015,9 +24436,16 @@ function renderFamilyScene(state, layout) {
24015
24436
  const loading = state.status === "loading" && !snap;
24016
24437
  const errorMessage2 = state.error?.message ?? lastRefreshError(state) ?? undefined;
24017
24438
  const probeFailed = snap ? snap.state.ran === false && Boolean(state.error) : Boolean(state.error);
24439
+ const selected = Math.max(0, state.selection.index);
24018
24440
  const body = [
24019
24441
  ...loading ? loadingRows(family || "family", state.tick) : snap ? [
24020
- ...probeFailed && errorMessage2 ? [...errorBanner(errorMessage2), blank()] : [],
24442
+ ...probeFailed && errorMessage2 ? [
24443
+ ...errorBanner(errorMessage2),
24444
+ ...shouldOfferRemoteLinkRepair(state, errorMessage2) ? [
24445
+ 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" })
24446
+ ] : [],
24447
+ blank()
24448
+ ] : [],
24021
24449
  ...current.subId ? formView(state, family, snap, current).lines : overviewBody(state, family, snap, layout)
24022
24450
  ] : familyRecoveryRows(state, family, errorMessage2)
24023
24451
  ];
@@ -24169,13 +24597,14 @@ function sortTasks2(tasks, spec) {
24169
24597
 
24170
24598
  // packages/cli/src/app-opentui/scenes/fleet.ts
24171
24599
  init_fleet_stats();
24600
+ init_remote_link();
24172
24601
  init_theme2();
24173
- var FLEET_RECOVERY_ITEMS = [
24602
+ var BASE_FLEET_RECOVERY_ITEMS = [
24174
24603
  { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks and dispatch work" },
24175
- { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair project link" } }, message: "repair project/server/auth link" },
24176
24604
  { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
24177
24605
  { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose project" }
24178
24606
  ];
24607
+ var 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" };
24179
24608
  var COL2 = {
24180
24609
  glyph: 1,
24181
24610
  runId: 8,
@@ -24340,33 +24769,43 @@ function runRow(width, run, active, optimistic) {
24340
24769
  const item = runSelectableItem(run);
24341
24770
  return item ? withSelectable(row, item) : row;
24342
24771
  }
24343
- var RECOVERY_DECKS = [
24772
+ var BASE_RECOVERY_DECKS = [
24344
24773
  { label: "tasks", detail: "open tasks and dispatch work" },
24345
- { label: "repair", detail: "repair project/server/auth link" },
24346
24774
  { label: "server", detail: "open server controls" },
24347
24775
  { label: "doctor", detail: "diagnose project" }
24348
24776
  ];
24349
- function recoveryRows(selected, reason) {
24777
+ function fleetRecoveryItems(state) {
24778
+ if (!shouldOfferRemoteLinkRepair(state))
24779
+ return BASE_FLEET_RECOVERY_ITEMS;
24780
+ return [BASE_FLEET_RECOVERY_ITEMS[0], FLEET_REPAIR_LINK_ITEM, ...BASE_FLEET_RECOVERY_ITEMS.slice(1)];
24781
+ }
24782
+ function fleetRecoveryDecks(state) {
24783
+ if (!shouldOfferRemoteLinkRepair(state))
24784
+ return BASE_RECOVERY_DECKS;
24785
+ return [BASE_RECOVERY_DECKS[0], { label: "repair link", detail: "backfill/prepare remote project-root link" }, ...BASE_RECOVERY_DECKS.slice(1)];
24786
+ }
24787
+ function recoveryRows(state, selected, reason) {
24788
+ const items = fleetRecoveryItems(state);
24350
24789
  return [
24351
24790
  ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
24352
24791
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
24353
- ...RECOVERY_DECKS.map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, FLEET_RECOVERY_ITEMS[index]))
24792
+ ...fleetRecoveryDecks(state).map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, items[index]))
24354
24793
  ];
24355
24794
  }
24356
- function emptyRows(query, total, selected) {
24795
+ function emptyRows(state, query, total, selected) {
24357
24796
  if (!query.trim() && total === 0)
24358
- return recoveryRows(selected, "No runs yet.");
24797
+ return recoveryRows(state, selected, "No runs yet.");
24359
24798
  if (query.trim() && total > 0) {
24360
24799
  return [
24361
- ...recoveryRows(selected, `No matching runs for ${JSON.stringify(query)} (${total} total).`)
24800
+ ...recoveryRows(state, selected, `No matching runs for ${JSON.stringify(query)} (${total} total).`)
24362
24801
  ];
24363
24802
  }
24364
- return recoveryRows(selected, "No runs are visible with this filter.");
24803
+ return recoveryRows(state, selected, "No runs are visible with this filter.");
24365
24804
  }
24366
24805
  function degradedRows(state, selected) {
24367
24806
  if (!state.error)
24368
24807
  return [];
24369
- return recoveryRows(selected, state.error.message);
24808
+ return recoveryRows(state, selected, state.error.message);
24370
24809
  }
24371
24810
  function renderFleetScene(state, layout) {
24372
24811
  const width = panelWidth4(layout);
@@ -24376,7 +24815,7 @@ function renderFleetScene(state, layout) {
24376
24815
  const runs = sortRuns(filterRunsForSearch(allRuns, query), spec);
24377
24816
  const selectedRun = typeof state.data.selectedRunId === "string" && runs.some((run) => run.runId === state.data.selectedRunId) ? state.data.selectedRunId : runs[0]?.runId;
24378
24817
  const selectedIndex = Math.max(0, runs.findIndex((run) => run.runId === selectedRun));
24379
- const recoveryIndex = Math.max(0, Math.min(3, state.selection.index));
24818
+ const recoveryIndex = Math.max(0, Math.min(fleetRecoveryItems(state).length - 1, state.selection.index));
24380
24819
  const optimistic = optimisticStatuses(state);
24381
24820
  const updatedAgo = updatedAgoLabel(state);
24382
24821
  const panelTop = 0;
@@ -24408,7 +24847,7 @@ function renderFleetScene(state, layout) {
24408
24847
  ...runs.map((run, index) => runRow(contentWidth, run, index === selectedIndex, optimistic.get(run.runId)))
24409
24848
  ] : state.error ? degradedRows(state, recoveryIndex) : loading ? loadingRows("runs", state.tick) : [
24410
24849
  ...query.trim() ? [line(searchSummary("runs", query, runs.length, allRuns.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
24411
- ...emptyRows(query, allRuns.length, recoveryIndex)
24850
+ ...emptyRows(state, query, allRuns.length, recoveryIndex)
24412
24851
  ]
24413
24852
  ];
24414
24853
  return makeSceneFrame({
@@ -24896,19 +25335,19 @@ function renderInboxScene(state, layout) {
24896
25335
  init_scene();
24897
25336
  init_selectable();
24898
25337
  init_theme2();
24899
- var 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" };
25338
+ var 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" };
24900
25339
  var 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" };
24901
- var 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" };
24902
- var 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" };
25340
+ var 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" };
25341
+ var 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" };
24903
25342
  var SETUP_STEPS = [
24904
- { id: "config", label: "Project config", detail: "rig.config + private state", done: (s) => s.configured, cta: "Run setup wizard", item: STEP_CONFIG_RUN },
25343
+ { id: "config", label: "Project config", detail: "rig.config + private state", done: (s) => s.configured, cta: "Run init", item: STEP_CONFIG_RUN },
24905
25344
  { id: "server", label: "Server target", detail: "local or remote, reachable", done: (s) => s.serverReachable, cta: "Choose a server", item: STEP_SERVER },
24906
25345
  { id: "auth", label: "GitHub auth", detail: "signed in on the selected server", done: (s) => s.authSignedIn, cta: "Connect GitHub", item: STEP_AUTH },
24907
25346
  { id: "tasks", label: "Task source", detail: "where work comes from", done: (s) => s.taskSourceReady, cta: "Pick a task source", item: STEP_TASKS }
24908
25347
  ];
24909
25348
  var SECONDARY_ACTIONS = [
24910
- { 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" } },
24911
- { 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" } },
25349
+ { 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" } },
25350
+ { 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" } },
24912
25351
  { 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" } }
24913
25352
  ];
24914
25353
  function record2(value) {
@@ -24952,11 +25391,11 @@ function renderInitScene(state, layout) {
24952
25391
  const allDone = completed === SETUP_STEPS.length;
24953
25392
  const nextStep = SETUP_STEPS.find((step) => !step.done(signals));
24954
25393
  const selected = Math.max(0, state.selection.index);
24955
- 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" });
25394
+ 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" });
24956
25395
  const secondaryRows = SECONDARY_ACTIONS.map(({ label, detail, item }, index) => selectableDeckRow({ label, detail, active: selected === index + 1 }, item));
24957
25396
  const progress = allDone ? "all set" : `${completed} of ${SETUP_STEPS.length} complete`;
24958
25397
  const panelLines = [
24959
- line(allDone ? "SETUP \xB7 ready to go" : "SETUP \xB7 let's get you running", { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink, bold: true }),
25398
+ line(allDone ? "INIT \xB7 ready to go" : "INIT \xB7 let's get you running", { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink, bold: true }),
24960
25399
  line(`progress ${progress}`, { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink2 }),
24961
25400
  blank(),
24962
25401
  line("CHECKLIST", { fg: RIG_UI.ink3, bold: true }),
@@ -24968,11 +25407,11 @@ function renderInitScene(state, layout) {
24968
25407
  line("OTHER PATHS", { fg: RIG_UI.ink3, bold: true }),
24969
25408
  ...secondaryRows,
24970
25409
  blank(),
24971
- line("enter/click runs the highlighted action \xB7 esc goes back \xB7 tab switches sections", { fg: RIG_UI.ink4 })
25410
+ line("enter/click runs the highlighted native action \xB7 esc goes back \xB7 tab switches sections", { fg: RIG_UI.ink4 })
24972
25411
  ];
24973
25412
  return makeSceneFrame({
24974
25413
  scene: "init",
24975
- title: "Setup",
25414
+ title: "Init",
24976
25415
  lines: [],
24977
25416
  panels: [{
24978
25417
  id: "init-setup",
@@ -24985,11 +25424,11 @@ function renderInitScene(state, layout) {
24985
25424
  opacity: 0.98,
24986
25425
  border: false,
24987
25426
  chrome: "ad-terminal",
24988
- headerText: "rig setup \xB7 guided onboarding",
25427
+ headerText: "rig init \xB7 guided onboarding",
24989
25428
  paddingX: 5,
24990
25429
  paddingY: 2
24991
25430
  }],
24992
- footer: { message: allDone ? "setup complete" : "setup in progress" },
25431
+ footer: { message: allDone ? "init complete" : "init in progress" },
24993
25432
  live: !allDone || state.status === "action"
24994
25433
  });
24995
25434
  }
@@ -26278,18 +26717,43 @@ function renderRunDetailScene(state, layout) {
26278
26717
  init_scene();
26279
26718
  init_selectable();
26280
26719
  init_theme2();
26720
+ init_remote_link();
26281
26721
  var SERVER_ACTIONS = [
26282
26722
  { 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" } },
26283
- { 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" } },
26284
- { 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" } },
26285
- { 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" } },
26286
- { 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" } },
26723
+ { 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" } },
26724
+ { 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" } },
26725
+ { 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" } },
26726
+ { 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" } },
26287
26727
  { 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" } },
26288
- { 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" } },
26728
+ { 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" } },
26289
26729
  { 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" } },
26290
26730
  { 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" } },
26291
26731
  { 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" } }
26292
26732
  ];
26733
+ function remoteAliasActions(server) {
26734
+ const aliases = (server?.aliases ?? []).filter((alias) => alias.kind === "remote");
26735
+ return aliases.map((alias) => ({
26736
+ detail: `${alias.selected ? "selected" : "select"} ${alias.baseUrl ?? "remote endpoint"}`,
26737
+ item: {
26738
+ id: `remote-alias:${alias.alias}`,
26739
+ label: alias.selected ? `\u2713 ${alias.alias}` : `use ${alias.alias}`,
26740
+ intent: { scene: "server", argv: ["server", "use", alias.alias], action: { kind: "server-use-remote", label: `Use ${alias.alias}`, payload: { alias: alias.alias } } },
26741
+ message: `select ${alias.alias}`
26742
+ }
26743
+ }));
26744
+ }
26745
+ function serverActions(state) {
26746
+ const server = serverState(state);
26747
+ const remoteRows = remoteAliasActions(server);
26748
+ let actions = remoteRows.length > 0 ? [...SERVER_ACTIONS.slice(0, 3), ...remoteRows, ...SERVER_ACTIONS.slice(3)] : [...SERVER_ACTIONS];
26749
+ if (!shouldOfferRemoteLinkRepair(state))
26750
+ return actions;
26751
+ 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" } };
26752
+ const doctorIndex = actions.findIndex((entry) => entry.item.id === "doctor");
26753
+ if (doctorIndex < 0)
26754
+ return [...actions, repair];
26755
+ return [...actions.slice(0, doctorIndex), repair, ...actions.slice(doctorIndex)];
26756
+ }
26293
26757
  function cleanVisible(value, fallback = "unknown") {
26294
26758
  return value?.trim() || fallback;
26295
26759
  }
@@ -26314,31 +26778,37 @@ function selectedIndex(state, count) {
26314
26778
  function statusRows2(server) {
26315
26779
  const auth = server?.auth;
26316
26780
  const signedIn = auth && (auth.signedIn === true || auth.authenticated === true || auth.status === "authenticated") ? "authenticated" : "auth unknown";
26781
+ const link = server?.remoteProjectLink;
26782
+ 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";
26783
+ const selectedLinkSummary = link?.ok ? " \xB7 root link ready" : server?.kind === "remote" && link ? ` \xB7 root link ${link.status ?? "needs repair"}` : "";
26317
26784
  return [
26318
- line(`selected ${cleanVisible(server?.label, "not checked")}`, { fg: RIG_UI.ink2 }),
26785
+ line(`selected ${cleanVisible(server?.label, "not checked")}${selectedLinkSummary}`, { fg: RIG_UI.ink2 }),
26319
26786
  line(`target ${targetLabel(server)}`, { fg: RIG_UI.ink3 }),
26320
26787
  line(`mode ${server?.kind ?? "local or remote"}`, { fg: RIG_UI.ink3 }),
26321
26788
  line(`health ${server ? server.reachable ? "reachable" : "unreachable" : "check pending"}`, { fg: server?.reachable ? RIG_UI.limeDim : server ? RIG_UI.red : RIG_UI.yellow }),
26322
26789
  line(`github ${signedIn}`, { fg: signedIn === "authenticated" ? RIG_UI.limeDim : RIG_UI.ink4 }),
26790
+ line(`root link ${linkText}`, { fg: link?.ok ? RIG_UI.limeDim : server?.kind === "remote" ? RIG_UI.yellow : RIG_UI.ink4 }),
26791
+ ...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 })] : [],
26323
26792
  ...server?.error ? [line(`attention ${server.error}`, { fg: RIG_UI.yellow })] : []
26324
26793
  ];
26325
26794
  }
26326
26795
  function renderServerScene(state, layout) {
26327
26796
  const server = serverState(state);
26328
- const selected = selectedIndex(state, 10);
26797
+ const actions = serverActions(state);
26798
+ const selected = selectedIndex(state, actions.length);
26329
26799
  const loading = state.status === "loading" && !server;
26330
26800
  const errorText = state.error?.message ?? lastRefreshError(state);
26331
26801
  const panelLines = [
26332
26802
  ...errorText ? errorBanner(errorText, "select refresh or doctor below to retry") : [],
26333
26803
  line("server controls", { fg: RIG_UI.ink3 }),
26334
26804
  blank(),
26335
- line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
26336
- ...SERVER_ACTIONS.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item)),
26337
- blank(),
26338
26805
  line("STATUS", { fg: RIG_UI.ink3, bold: true }),
26339
26806
  ...loading ? loadingRows("server status", state.tick) : statusRows2(server),
26340
26807
  blank(),
26341
- line("enter/click runs action \xB7 command rows stay inside OpenTUI PTY", { fg: RIG_UI.ink4 })
26808
+ line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
26809
+ ...actions.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item)),
26810
+ blank(),
26811
+ line("enter/click runs native actions \xB7 add/use remote prompts stay in OpenTUI", { fg: RIG_UI.ink4 })
26342
26812
  ];
26343
26813
  return makeSceneFrame({
26344
26814
  scene: "server",
@@ -26367,14 +26837,15 @@ function renderServerScene(state, layout) {
26367
26837
  // packages/cli/src/app-opentui/scenes/tasks.ts
26368
26838
  init_scene();
26369
26839
  init_selectable();
26840
+ init_remote_link();
26370
26841
  init_theme2();
26371
- var TASK_RECOVERY_ITEMS = [
26842
+ var BASE_TASK_RECOVERY_ITEMS = [
26372
26843
  { id: "refresh", label: "refresh", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Refresh tasks" } }, message: "refresh task source" },
26373
- { id: "next", label: "next", intent: { scene: "command", argv: ["task", "run", "--next"], action: { kind: "command-run", label: "Run next task" } }, message: "dispatch next runnable task" },
26374
- { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair task source" } }, message: "repair project/task source" },
26844
+ { 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" },
26375
26845
  { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
26376
26846
  { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose task source" }
26377
26847
  ];
26848
+ var 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" };
26378
26849
  var COL3 = {
26379
26850
  glyph: 1,
26380
26851
  id: 11,
@@ -26534,34 +27005,44 @@ function taskRow(width, task, active, optimistic) {
26534
27005
  const item = taskSelectableItem(task);
26535
27006
  return item ? withSelectable(row, item) : row;
26536
27007
  }
26537
- var RECOVERY_DECKS2 = [
27008
+ var BASE_RECOVERY_DECKS2 = [
26538
27009
  { label: "refresh", detail: "reload task source" },
26539
27010
  { label: "next", detail: "dispatch next runnable task" },
26540
- { label: "repair", detail: "repair project/task source link" },
26541
27011
  { label: "server", detail: "open server controls" },
26542
27012
  { label: "doctor", detail: "diagnose task source" }
26543
27013
  ];
26544
- function recoveryRows2(selected, reason) {
27014
+ function taskRecoveryItems(state) {
27015
+ if (!shouldOfferRemoteLinkRepair(state))
27016
+ return BASE_TASK_RECOVERY_ITEMS;
27017
+ return [BASE_TASK_RECOVERY_ITEMS[0], BASE_TASK_RECOVERY_ITEMS[1], TASK_REPAIR_LINK_ITEM, ...BASE_TASK_RECOVERY_ITEMS.slice(2)];
27018
+ }
27019
+ function taskRecoveryDecks(state) {
27020
+ if (!shouldOfferRemoteLinkRepair(state))
27021
+ return BASE_RECOVERY_DECKS2;
27022
+ return [BASE_RECOVERY_DECKS2[0], BASE_RECOVERY_DECKS2[1], { label: "repair link", detail: "backfill/prepare remote project-root link" }, ...BASE_RECOVERY_DECKS2.slice(2)];
27023
+ }
27024
+ function recoveryRows2(state, selected, reason) {
27025
+ const items = taskRecoveryItems(state);
26545
27026
  return [
26546
27027
  ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
26547
27028
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
26548
- ...RECOVERY_DECKS2.map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, TASK_RECOVERY_ITEMS[index]))
27029
+ ...taskRecoveryDecks(state).map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, items[index]))
26549
27030
  ];
26550
27031
  }
26551
- function emptyRows2(query, total, selected) {
27032
+ function emptyRows2(state, query, total, selected) {
26552
27033
  if (!query.trim() && total === 0)
26553
- return recoveryRows2(selected, "No tasks loaded.");
27034
+ return recoveryRows2(state, selected, "No tasks loaded.");
26554
27035
  if (query.trim() && total > 0) {
26555
27036
  return [
26556
- ...recoveryRows2(selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
27037
+ ...recoveryRows2(state, selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
26557
27038
  ];
26558
27039
  }
26559
- return recoveryRows2(selected, "No tasks are visible with this filter.");
27040
+ return recoveryRows2(state, selected, "No tasks are visible with this filter.");
26560
27041
  }
26561
27042
  function degradedRows2(state, selected) {
26562
27043
  if (!state.error)
26563
27044
  return [];
26564
- return recoveryRows2(selected, state.error.message);
27045
+ return recoveryRows2(state, selected, state.error.message);
26565
27046
  }
26566
27047
  function rawString(raw, key) {
26567
27048
  const value = raw?.[key];
@@ -26585,7 +27066,10 @@ function renderTaskDetail(state, taskId3, layout) {
26585
27066
  const activeRunId = task?.activeRun?.runId?.trim();
26586
27067
  const width = panelWidth13(layout);
26587
27068
  const selected = Math.max(0, state.selection.index);
27069
+ const errorText = state.error?.message ?? lastRefreshError(state);
27070
+ const offerRepairLink = shouldOfferRemoteLinkRepair(state, errorText);
26588
27071
  const headerLines = [
27072
+ ...errorText ? errorBanner(errorText, offerRepairLink ? "select repair link below to prepare the remote checkout" : "retry dispatch after resolving the issue") : [],
26589
27073
  line(`${glyph2} ${title}`, { fg: color, bold: true }),
26590
27074
  line(`task ${taskId3} \xB7 ${status}`, { fg: RIG_UI.ink3 }),
26591
27075
  ...task && task.labels.length > 0 ? [line(`labels: ${task.labels.join(", ")}`, { fg: RIG_UI.ink4 })] : [],
@@ -26597,8 +27081,16 @@ function renderTaskDetail(state, taskId3, layout) {
26597
27081
  const actionRows2 = [];
26598
27082
  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" }));
26599
27083
  index += 1;
26600
- 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}` }));
26601
- index += 1;
27084
+ if (offerRepairLink) {
27085
+ 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" }));
27086
+ index += 1;
27087
+ }
27088
+ if (offerRepairLink) {
27089
+ actionRows2.push(line(" dispatch blocked until the remote project link is repaired", { fg: RIG_UI.ink4 }));
27090
+ } else {
27091
+ 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}` }));
27092
+ index += 1;
27093
+ }
26602
27094
  if (activeRunId) {
26603
27095
  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)}` }));
26604
27096
  index += 1;
@@ -26657,7 +27149,7 @@ function renderTasksScene(state, layout) {
26657
27149
  const tasks = sortTasks2(filterTasksForSearch(allTasks, query), spec);
26658
27150
  const selected = typeof state.data.selectedTaskId === "string" && tasks.some((task) => task.id === state.data.selectedTaskId) ? state.data.selectedTaskId : tasks[0]?.id;
26659
27151
  const selectedIndex2 = Math.max(0, tasks.findIndex((task) => task.id === selected));
26660
- const recoveryIndex = Math.max(0, Math.min(4, state.selection.index));
27152
+ const recoveryIndex = Math.max(0, Math.min(taskRecoveryItems(state).length - 1, state.selection.index));
26661
27153
  const dispatching = dispatchingFor(state);
26662
27154
  const dispatchLine = dispatching ? ` \xB7 ${state.data.dispatchingRun?.runId ?? "new"} ${dispatching.status}` : "";
26663
27155
  const updatedAgo = updatedAgoLabel2(state);
@@ -26677,7 +27169,7 @@ function renderTasksScene(state, layout) {
26677
27169
  ...tasks.map((task, index) => taskRow(contentWidth, task, index === selectedIndex2, dispatching && dispatching.taskId === task.id ? dispatching.status : undefined))
26678
27170
  ] : state.error ? degradedRows2(state, recoveryIndex) : loading ? loadingRows("tasks", state.tick) : [
26679
27171
  ...query.trim() ? [line(searchSummary("tasks", query, tasks.length, allTasks.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
26680
- ...emptyRows2(query, allTasks.length, recoveryIndex)
27172
+ ...emptyRows2(state, query, allTasks.length, recoveryIndex)
26681
27173
  ]
26682
27174
  ];
26683
27175
  return makeSceneFrame({
@@ -27038,6 +27530,29 @@ function createDomainAdapter() {
27038
27530
  else
27039
27531
  await loadInitFacts(ctx);
27040
27532
  return true;
27533
+ case "server-use-local":
27534
+ await useLocalServerFromApp(ctx);
27535
+ return true;
27536
+ case "server-use-remote":
27537
+ await useRemoteServerFromApp(ctx, payloadString8(intent, "alias") ?? intent.argv[2]);
27538
+ return true;
27539
+ case "server-add-remote":
27540
+ await addRemoteServerFromApp(ctx, intent.argv);
27541
+ return true;
27542
+ case "github-auth-import":
27543
+ await importGitHubAuthFromGhApp(ctx);
27544
+ return true;
27545
+ case "remote-link-repair": {
27546
+ await repairRemoteProjectRootLinkFromApp(ctx);
27547
+ const returnScene = payloadString8(intent, "returnScene");
27548
+ if (returnScene && returnScene !== intent.scene) {
27549
+ ctx.emit({ type: "scene.change", scene: returnScene, intent: { scene: returnScene, argv: intent.argv, action: { kind: "refresh", label: `Refresh ${returnScene}`, payload: intent.action.payload } } });
27550
+ await refreshScene(ctx, returnScene, { scene: returnScene, argv: intent.argv, action: { kind: "refresh", label: `Refresh ${returnScene}`, payload: intent.action.payload } });
27551
+ } else {
27552
+ await refreshServerStatus(ctx);
27553
+ }
27554
+ return true;
27555
+ }
27041
27556
  case "doctor-run":
27042
27557
  await runDoctorChecksForApp(ctx);
27043
27558
  return true;
@@ -27256,7 +27771,7 @@ function parsePolicyMode2(value) {
27256
27771
  function resolveProjectRoot2(input) {
27257
27772
  if (input.envProjectRoot)
27258
27773
  return resolve31(input.cwd, input.envProjectRoot);
27259
- const execName = basename6(input.execPath).toLowerCase();
27774
+ const execName = basename5(input.execPath).toLowerCase();
27260
27775
  const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve31(input.execPath, "..", "..")] : [];
27261
27776
  const candidates = [input.cwd, ...execCandidates, resolve31(input.importDir, "..")];
27262
27777
  for (const candidate of candidates) {