@h-rig/cli 0.0.6-alpha.88 → 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 (70) hide show
  1. package/dist/bin/rig.js +1159 -292
  2. package/dist/src/app/board.js +462 -48
  3. package/dist/src/app-opentui/adapters/doctor.js +458 -46
  4. package/dist/src/app-opentui/adapters/family.js +670 -147
  5. package/dist/src/app-opentui/adapters/fleet.js +477 -46
  6. package/dist/src/app-opentui/adapters/inbox.js +477 -46
  7. package/dist/src/app-opentui/adapters/init.js +497 -74
  8. package/dist/src/app-opentui/adapters/inspect.js +477 -46
  9. package/dist/src/app-opentui/adapters/pi-attach.js +484 -53
  10. package/dist/src/app-opentui/adapters/run-detail.js +477 -46
  11. package/dist/src/app-opentui/adapters/server.d.ts +26 -0
  12. package/dist/src/app-opentui/adapters/server.js +676 -59
  13. package/dist/src/app-opentui/adapters/tasks.js +539 -81
  14. package/dist/src/app-opentui/autocomplete.js +4 -2
  15. package/dist/src/app-opentui/bootstrap.js +1155 -288
  16. package/dist/src/app-opentui/command-palette.js +37 -10
  17. package/dist/src/app-opentui/index.js +566 -89
  18. package/dist/src/app-opentui/intent.js +33 -8
  19. package/dist/src/app-opentui/keymap.js +43 -29
  20. package/dist/src/app-opentui/pi-host-child.js +465 -53
  21. package/dist/src/app-opentui/react/App.js +104 -44
  22. package/dist/src/app-opentui/react/ChromeHost.js +16 -4
  23. package/dist/src/app-opentui/react/launch.js +555 -88
  24. package/dist/src/app-opentui/react/nav.js +1 -1
  25. package/dist/src/app-opentui/registry.js +1062 -237
  26. package/dist/src/app-opentui/remote-link.d.ts +10 -0
  27. package/dist/src/app-opentui/remote-link.js +47 -0
  28. package/dist/src/app-opentui/runtime.js +566 -89
  29. package/dist/src/app-opentui/scenes/doctor.js +1 -1
  30. package/dist/src/app-opentui/scenes/error.js +50 -4
  31. package/dist/src/app-opentui/scenes/family.js +60 -6
  32. package/dist/src/app-opentui/scenes/fleet.js +65 -13
  33. package/dist/src/app-opentui/scenes/help.js +4 -2
  34. package/dist/src/app-opentui/scenes/init.js +12 -12
  35. package/dist/src/app-opentui/scenes/main.js +7 -7
  36. package/dist/src/app-opentui/scenes/server.js +83 -11
  37. package/dist/src/app-opentui/scenes/tasks.js +79 -16
  38. package/dist/src/app-opentui/state.js +25 -5
  39. package/dist/src/app-opentui/surface-catalog.js +4 -2
  40. package/dist/src/app-opentui/types.d.ts +1 -1
  41. package/dist/src/commands/_cli-format.d.ts +10 -1
  42. package/dist/src/commands/_cli-format.js +5 -2
  43. package/dist/src/commands/_connection-state.d.ts +11 -1
  44. package/dist/src/commands/_connection-state.js +50 -5
  45. package/dist/src/commands/_doctor-checks.js +458 -46
  46. package/dist/src/commands/_help-catalog.js +4 -2
  47. package/dist/src/commands/_json-output.js +4 -0
  48. package/dist/src/commands/_operator-view.js +465 -53
  49. package/dist/src/commands/_pi-frontend.js +465 -53
  50. package/dist/src/commands/_preflight.js +509 -72
  51. package/dist/src/commands/_server-client.d.ts +33 -0
  52. package/dist/src/commands/_server-client.js +477 -46
  53. package/dist/src/commands/_server-events.js +446 -41
  54. package/dist/src/commands/_snapshot-upload.js +460 -48
  55. package/dist/src/commands/connect.js +620 -15
  56. package/dist/src/commands/doctor.js +458 -46
  57. package/dist/src/commands/github.js +462 -50
  58. package/dist/src/commands/inbox.js +458 -46
  59. package/dist/src/commands/init.js +497 -74
  60. package/dist/src/commands/inspect.js +458 -46
  61. package/dist/src/commands/run.js +465 -53
  62. package/dist/src/commands/server.js +647 -163
  63. package/dist/src/commands/setup.js +463 -51
  64. package/dist/src/commands/stats.js +462 -48
  65. package/dist/src/commands/task-run-driver.js +464 -52
  66. package/dist/src/commands/task.js +520 -81
  67. package/dist/src/commands.js +670 -147
  68. package/dist/src/index.js +674 -147
  69. package/dist/src/launcher.js +4 -0
  70. package/package.json +8 -8
@@ -643,7 +643,8 @@ __export(exports__connection_state, {
643
643
  resolveGlobalConnectionsPath: () => resolveGlobalConnectionsPath,
644
644
  readRepoConnection: () => readRepoConnection,
645
645
  readGlobalConnections: () => readGlobalConnections,
646
- isRemoteConnectionSelected: () => isRemoteConnectionSelected
646
+ isRemoteConnectionSelected: () => isRemoteConnectionSelected,
647
+ clearRepoServerProjectRoot: () => clearRepoServerProjectRoot
647
648
  });
648
649
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
649
650
  import { homedir } from "os";
@@ -727,12 +728,28 @@ function readRepoConnection(projectRoot) {
727
728
  selected,
728
729
  project: typeof record.project === "string" ? record.project : undefined,
729
730
  linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
730
- 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
731
734
  };
732
735
  }
733
736
  function writeRepoConnection(projectRoot, state) {
734
737
  writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
735
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
+ }
736
753
  function resolveSelectedConnection(projectRoot, options = {}) {
737
754
  const repo = readRepoConnection(projectRoot);
738
755
  if (!repo)
@@ -744,13 +761,41 @@ function resolveSelectedConnection(projectRoot, options = {}) {
744
761
  if (!connection) {
745
762
  throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
746
763
  }
747
- return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
764
+ return { alias: repo.selected, connection, serverProjectRoot: rootAllowedForSelection(repo, connection) };
748
765
  }
749
- function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
766
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot, metadata = {}) {
750
767
  const repo = readRepoConnection(projectRoot);
751
768
  if (!repo)
752
769
  return;
753
- writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
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
+ });
789
+ }
790
+ function clearRepoServerProjectRoot(projectRoot) {
791
+ const repo = readRepoConnection(projectRoot);
792
+ if (!repo)
793
+ return;
794
+ writeRepoConnection(projectRoot, {
795
+ selected: repo.selected,
796
+ ...repo.project ? { project: repo.project } : {},
797
+ ...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
798
+ });
754
799
  }
755
800
  function isRemoteConnectionSelected(projectRoot) {
756
801
  return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
@@ -777,12 +822,16 @@ __export(exports__server_client, {
777
822
  respondRunPiExtensionUiViaServer: () => respondRunPiExtensionUiViaServer,
778
823
  resolveServerConnectionLabel: () => resolveServerConnectionLabel,
779
824
  requestServerJson: () => requestServerJson,
825
+ repairRemoteProjectRootLink: () => repairRemoteProjectRootLink,
780
826
  registerProjectViaServer: () => registerProjectViaServer,
781
827
  prepareRemoteCheckoutViaServer: () => prepareRemoteCheckoutViaServer,
782
828
  postGitHubTokenViaServer: () => postGitHubTokenViaServer,
829
+ normalizeRepoSlug: () => normalizeRepoSlug,
783
830
  listWorkspaceTasksViaServer: () => listWorkspaceTasksViaServer,
784
831
  listRunsViaServer: () => listRunsViaServer,
785
832
  listGitHubProjectsViaServer: () => listGitHubProjectsViaServer,
833
+ isRemoteProjectRootLinkError: () => isRemoteProjectRootLinkError,
834
+ inspectRemoteProjectLink: () => inspectRemoteProjectLink,
786
835
  getWorkspaceTaskViaServer: () => getWorkspaceTaskViaServer,
787
836
  getRunTimelineViaServer: () => getRunTimelineViaServer,
788
837
  getRunPiStatusViaServer: () => getRunPiStatusViaServer,
@@ -794,13 +843,15 @@ __export(exports__server_client, {
794
843
  getRunDetailsViaServer: () => getRunDetailsViaServer,
795
844
  getGitHubProjectStatusFieldViaServer: () => getGitHubProjectStatusFieldViaServer,
796
845
  getGitHubAuthStatusViaServer: () => getGitHubAuthStatusViaServer,
846
+ formatRemoteProjectLinkHint: () => formatRemoteProjectLinkHint,
797
847
  ensureTaskLabelsViaServer: () => ensureTaskLabelsViaServer,
798
848
  ensureServerForCli: () => ensureServerForCli,
849
+ ensureRemoteProjectRootLink: () => ensureRemoteProjectRootLink,
799
850
  buildRunPiEventsWebSocketUrl: () => buildRunPiEventsWebSocketUrl,
800
851
  abortRunPiViaServer: () => abortRunPiViaServer
801
852
  });
802
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
803
- 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";
804
855
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
805
856
  function setServerPhaseListener(listener) {
806
857
  const previous = serverPhaseListener;
@@ -846,11 +897,10 @@ function readStoredGitHubAuthToken(projectRoot) {
846
897
  const parsed = readRemoteAuthState(projectRoot);
847
898
  return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
848
899
  }
849
- function inferRemoteServerProjectRootFromAuthState(projectRoot) {
850
- const repo = readRepoConnection(projectRoot);
851
- const slug = repo?.project?.trim();
852
- if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
900
+ function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
901
+ if (!/^[^/]+\/[^/]+$/.test(repoSlug))
853
902
  return null;
903
+ const repo = readRepoConnection(projectRoot);
854
904
  const auth = readRemoteAuthState(projectRoot);
855
905
  const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
856
906
  const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
@@ -859,25 +909,426 @@ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
859
909
  const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
860
910
  if (!checkoutBaseDir)
861
911
  return null;
862
- const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
863
- writeRepoServerProjectRoot(projectRoot, inferred);
864
- return inferred;
912
+ return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
865
913
  }
866
914
  function readLocalConnectionFallbackToken(projectRoot) {
867
915
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
868
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
+ }
869
1320
  async function ensureServerForCli(projectRoot) {
870
1321
  try {
871
1322
  const selected = resolveSelectedConnection(projectRoot);
872
1323
  if (selected?.connection.kind === "remote") {
873
1324
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
874
1325
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
875
- const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
1326
+ const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
876
1327
  return {
877
1328
  baseUrl: selected.connection.baseUrl,
878
1329
  authToken,
879
1330
  connectionKind: "remote",
880
- serverProjectRoot
1331
+ serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
881
1332
  };
882
1333
  }
883
1334
  reportServerPhase("Starting local Rig server\u2026");
@@ -895,32 +1346,6 @@ async function ensureServerForCli(projectRoot) {
895
1346
  throw error;
896
1347
  }
897
1348
  }
898
- async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
899
- const repo = readRepoConnection(projectRoot);
900
- const slug = repo?.project?.trim();
901
- if (!slug)
902
- return null;
903
- try {
904
- const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
905
- if (authToken && queryAuthFallbackEnabled())
906
- url.searchParams.set("rt", authToken);
907
- const response = await fetch(url, {
908
- headers: mergeHeaders(undefined, authToken)
909
- });
910
- if (!response.ok)
911
- return null;
912
- const payload = await response.json();
913
- const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
914
- const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
915
- const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
916
- const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
917
- if (path)
918
- writeRepoServerProjectRoot(projectRoot, path);
919
- return path;
920
- } catch {
921
- return null;
922
- }
923
- }
924
1349
  function appendTaskFilterParams(url, filters) {
925
1350
  if (filters.assignee)
926
1351
  url.searchParams.set("assignee", filters.assignee);
@@ -1021,13 +1446,20 @@ function canUseRemoteWithoutProjectRoot(pathname) {
1021
1446
  async function requestServerJson(context, pathname, init = {}) {
1022
1447
  const server = await ensureServerForCli(context.projectRoot);
1023
1448
  const requestUrl = new URL(`${server.baseUrl}${pathname}`);
1024
- if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
1025
- const repo = readRepoConnection(context.projectRoot);
1026
- 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
+ }
1027
1459
  }
1028
1460
  const headers = mergeHeaders(init.headers, server.authToken);
1029
- if (server.serverProjectRoot)
1030
- headers.set("x-rig-project-root", server.serverProjectRoot);
1461
+ if (scopedServerProjectRoot)
1462
+ headers.set("x-rig-project-root", scopedServerProjectRoot);
1031
1463
  if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
1032
1464
  requestUrl.searchParams.set("rt", server.authToken);
1033
1465
  }
@@ -2072,12 +2504,13 @@ var init__help_catalog = __esm(() => {
2072
2504
  {
2073
2505
  name: "server",
2074
2506
  summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
2075
- usage: ["rig server <status|list|add|use|start> [options]"],
2507
+ usage: ["rig server <status|list|add|use|repair-link|start> [options]"],
2076
2508
  commands: [
2077
- { 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 },
2078
2510
  { command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
2079
2511
  { command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
2080
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 },
2081
2514
  { command: "list", description: "List saved local/remote server aliases.", primary: true },
2082
2515
  { command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
2083
2516
  ],
@@ -2085,6 +2518,7 @@ var init__help_catalog = __esm(() => {
2085
2518
  "rig server status",
2086
2519
  "rig server add prod https://where.rig-does.work",
2087
2520
  "rig server use prod",
2521
+ "rig server repair-link --repo owner/repo",
2088
2522
  "rig server use local",
2089
2523
  "rig server start --port 3773"
2090
2524
  ],
@@ -2795,7 +3229,7 @@ function resolveControlPlaneDefinitionRoot(projectRoot) {
2795
3229
  var init__paths = () => {};
2796
3230
 
2797
3231
  // packages/cli/src/report-bug.ts
2798
- 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";
2799
3233
  import { basename as basename2, extname, join, resolve as resolve8 } from "path";
2800
3234
  function slugifyBugTitle(value) {
2801
3235
  const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/g, "");
@@ -2834,8 +3268,8 @@ function createBugReportFiles(input) {
2834
3268
  }
2835
3269
  rmSync2(reportDir, { recursive: true, force: true });
2836
3270
  }
2837
- mkdirSync3(assetDir, { recursive: true });
2838
- mkdirSync3(screenshotDir, { recursive: true });
3271
+ mkdirSync4(assetDir, { recursive: true });
3272
+ mkdirSync4(screenshotDir, { recursive: true });
2839
3273
  const copiedScreenshots = copyEvidenceFiles(input.projectRoot, screenshotDir, input.screenshots ?? [], "screenshot");
2840
3274
  const copiedAssets = copyEvidenceFiles(input.projectRoot, assetDir, input.assets ?? [], "asset");
2841
3275
  const manifestAssets = [
@@ -2865,15 +3299,15 @@ function createBugReportFiles(input) {
2865
3299
  platform: process.platform,
2866
3300
  arch: process.arch
2867
3301
  };
2868
- writeFileSync2(join(assetDir, "README.md"), buildAssetReadme(input.title), "utf8");
2869
- 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");
2870
3304
  if (browserPath && browser) {
2871
- writeFileSync2(browserPath, `${JSON.stringify(browser, null, 2)}
3305
+ writeFileSync3(browserPath, `${JSON.stringify(browser, null, 2)}
2872
3306
  `, "utf8");
2873
3307
  }
2874
- writeFileSync2(manifestPath, `${JSON.stringify(manifest, null, 2)}
3308
+ writeFileSync3(manifestPath, `${JSON.stringify(manifest, null, 2)}
2875
3309
  `, "utf8");
2876
- writeFileSync2(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
3310
+ writeFileSync3(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
2877
3311
  return {
2878
3312
  slug,
2879
3313
  reportDir,
@@ -3050,7 +3484,7 @@ function formatAssetMarkdown(name, path) {
3050
3484
  var init_report_bug = () => {};
3051
3485
 
3052
3486
  // packages/cli/src/commands/task-report-bug.ts
3053
- 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";
3054
3488
  import { resolve as resolve9 } from "path";
3055
3489
  import pc2 from "picocolors";
3056
3490
  import {
@@ -3412,7 +3846,7 @@ function appendTaskConfigEntryPreservingText(taskConfigPath, issueId, entry) {
3412
3846
  `).map((line) => ` ${line}`).join(`
3413
3847
  `);
3414
3848
  if (!trimmed || trimmed === "{}") {
3415
- writeFileSync3(taskConfigPath, `{
3849
+ writeFileSync4(taskConfigPath, `{
3416
3850
  ${serializedEntry}
3417
3851
  }
3418
3852
  `, "utf8");
@@ -3428,7 +3862,7 @@ ${serializedEntry}
3428
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." });
3429
3863
  }
3430
3864
  const comma = before.trim() === "{" ? "" : ",";
3431
- writeFileSync3(taskConfigPath, `${before}${comma}
3865
+ writeFileSync4(taskConfigPath, `${before}${comma}
3432
3866
  ${serializedEntry}
3433
3867
  }
3434
3868
  `, "utf8");
@@ -3813,7 +4247,7 @@ var init_task_report_bug = __esm(() => {
3813
4247
  });
3814
4248
 
3815
4249
  // packages/cli/src/commands/browser.ts
3816
- import { mkdirSync as mkdirSync4, rmSync as rmSync3 } from "fs";
4250
+ import { mkdirSync as mkdirSync5, rmSync as rmSync3 } from "fs";
3817
4251
  import { resolve as resolve10 } from "path";
3818
4252
  import { spawn } from "child_process";
3819
4253
  import { emitKeypressEvents } from "readline";
@@ -4391,7 +4825,7 @@ function readBrowserDemoCommandLine(command) {
4391
4825
  }
4392
4826
  async function launchBrowserDemo(runtime) {
4393
4827
  rmSync3(resolve10(runtime.browserDir, runtime.stateDir), { recursive: true, force: true });
4394
- mkdirSync4(resolve10(runtime.browserDir, runtime.stateDir), { recursive: true });
4828
+ mkdirSync5(resolve10(runtime.browserDir, runtime.stateDir), { recursive: true });
4395
4829
  const launcherPath = resolve10(runtime.browserDir, "scripts/electron-launcher.mjs");
4396
4830
  const launcher = await import(pathToFileURL(launcherPath).href);
4397
4831
  const electronPath = await launcher.resolveElectronPath();
@@ -4740,7 +5174,7 @@ var init_profile_and_review = __esm(() => {
4740
5174
  });
4741
5175
 
4742
5176
  // packages/cli/src/commands/_policy.ts
4743
- import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
5177
+ import { appendFileSync, mkdirSync as mkdirSync6 } from "fs";
4744
5178
  import { resolve as resolve11 } from "path";
4745
5179
  import { evaluate as evaluate2, loadPolicy as loadPolicy2, resolveAction as resolveAction2 } from "@rig/runtime/control-plane/runtime/guard";
4746
5180
  import { resolveHarnessPaths } from "@rig/runtime/control-plane/native/utils";
@@ -4757,7 +5191,7 @@ function appendControlledBashAudit(context, mode, command, args, matchedRuleIds,
4757
5191
  }
4758
5192
  const logsDir = resolveHarnessPaths(context.projectRoot).logsDir;
4759
5193
  const logFile = resolve11(logsDir, "controlled-bash.jsonl");
4760
- mkdirSync5(logsDir, { recursive: true });
5194
+ mkdirSync6(logsDir, { recursive: true });
4761
5195
  const payload = {
4762
5196
  timestamp: new Date().toISOString(),
4763
5197
  mode,
@@ -5051,9 +5485,9 @@ var exports_pi = {};
5051
5485
  __export(exports_pi, {
5052
5486
  executePi: () => executePi
5053
5487
  });
5054
- 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";
5055
5489
  import { homedir as homedir4 } from "os";
5056
- import { dirname as dirname2, resolve as resolve13 } from "path";
5490
+ import { dirname as dirname3, resolve as resolve13 } from "path";
5057
5491
  function settingsPath(root) {
5058
5492
  return resolve13(root, ".pi", "settings.json");
5059
5493
  }
@@ -5078,8 +5512,8 @@ function packageKey(entry) {
5078
5512
  return JSON.stringify(entry);
5079
5513
  }
5080
5514
  function writeSettings(path, settings) {
5081
- mkdirSync6(dirname2(path), { recursive: true });
5082
- writeFileSync4(path, `${JSON.stringify(settings, null, 2)}
5515
+ mkdirSync7(dirname3(path), { recursive: true });
5516
+ writeFileSync5(path, `${JSON.stringify(settings, null, 2)}
5083
5517
  `, "utf-8");
5084
5518
  }
5085
5519
  async function searchNpmForPiExtensions(term) {
@@ -5262,6 +5696,14 @@ function permissionAllowsPr2(payload) {
5262
5696
  function isNotFoundError(error) {
5263
5697
  return /\b(404|not found)\b/i.test(message(error));
5264
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
+ }
5265
5707
  function projectCheckoutReady(payload) {
5266
5708
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
5267
5709
  return null;
@@ -5316,25 +5758,37 @@ async function runFastTaskRunPreflight(context, options = {}) {
5316
5758
  }
5317
5759
  const repo = readRepoConnection(context.projectRoot);
5318
5760
  const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
5319
- 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
+ }
5320
5769
  try {
5321
5770
  const auth = await request("/api/github/auth/status");
5322
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>`."));
5323
5772
  } catch (error) {
5324
5773
  checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
5325
5774
  }
5326
- try {
5327
- const projection = await request("/api/workspace/task-projection");
5328
- checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
5329
- } catch (error) {
5330
- checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
5331
- }
5332
- try {
5333
- const permissions = await request("/api/github/repo/permissions");
5334
- const allowed = permissionAllowsPr2(permissions);
5335
- 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."));
5336
- } catch (error) {
5337
- 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."));
5338
5792
  }
5339
5793
  if (repo?.project) {
5340
5794
  try {
@@ -5346,19 +5800,24 @@ async function runFastTaskRunPreflight(context, options = {}) {
5346
5800
  }
5347
5801
  }
5348
5802
  if (taskId) {
5349
- try {
5350
- const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
5351
- const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
5352
- 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."));
5353
- } catch (error) {
5354
- checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
5355
- }
5356
- try {
5357
- const runs = await request("/api/runs?limit=200");
5358
- const duplicate = activeDuplicateRun(runs, taskId);
5359
- 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));
5360
- } catch (error) {
5361
- 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."));
5362
5821
  }
5363
5822
  }
5364
5823
  if ((options.runtimeAdapter ?? "pi") === "pi") {
@@ -5839,7 +6298,7 @@ import {
5839
6298
  chmodSync,
5840
6299
  copyFileSync as copyFileSync2,
5841
6300
  existsSync as existsSync11,
5842
- mkdirSync as mkdirSync7,
6301
+ mkdirSync as mkdirSync8,
5843
6302
  readdirSync,
5844
6303
  readlinkSync,
5845
6304
  rmSync as rmSync4,
@@ -5944,7 +6403,7 @@ async function executeDist(context, args) {
5944
6403
  requireNoExtraArgs(pending, "rig dist install [--scope user|system] [--path <dir>]");
5945
6404
  const scope = parseInstallScope(scopeResult.value);
5946
6405
  const installDir = resolveInstallDir(scope, pathResult.value);
5947
- mkdirSync7(installDir, { recursive: true });
6406
+ mkdirSync8(installDir, { recursive: true });
5948
6407
  let source = await findLatestDistBinary(context.projectRoot);
5949
6408
  let buildDir = null;
5950
6409
  if (!source) {
@@ -5999,7 +6458,7 @@ async function executeDist(context, args) {
5999
6458
  const fp = await computeRuntimeImageFingerprint(context.projectRoot);
6000
6459
  const currentId = computeRuntimeImageId(fp);
6001
6460
  const imagesDir = resolve16(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
6002
- mkdirSync7(imagesDir, { recursive: true });
6461
+ mkdirSync8(imagesDir, { recursive: true });
6003
6462
  let pruned = 0;
6004
6463
  for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
6005
6464
  if (entry.isDirectory() && entry.name !== currentId) {
@@ -6011,9 +6470,9 @@ async function executeDist(context, args) {
6011
6470
  console.log(`Pruned ${pruned} stale image(s).`);
6012
6471
  }
6013
6472
  const imageDir = resolve16(imagesDir, currentId);
6014
- mkdirSync7(resolve16(imageDir, "bin/hooks"), { recursive: true });
6015
- mkdirSync7(resolve16(imageDir, "bin/plugins"), { recursive: true });
6016
- 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 });
6017
6476
  const hookNames = [
6018
6477
  "scope-guard",
6019
6478
  "import-guard",
@@ -6038,8 +6497,8 @@ async function executeDist(context, args) {
6038
6497
  const binPluginsDir = resolve16(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
6039
6498
  const validatorsRoot = resolve16(hostProjectRoot, "packages/runtime/src/control-plane/validators");
6040
6499
  const binValidatorsDir = resolve16(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
6041
- mkdirSync7(binPluginsDir, { recursive: true });
6042
- mkdirSync7(binValidatorsDir, { recursive: true });
6500
+ mkdirSync8(binPluginsDir, { recursive: true });
6501
+ mkdirSync8(binValidatorsDir, { recursive: true });
6043
6502
  if (existsSync11(pluginsDir)) {
6044
6503
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
6045
6504
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
@@ -6478,16 +6937,19 @@ function formatConnectionList(connections) {
6478
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(`
6479
6938
  `);
6480
6939
  }
6481
- function formatConnectionStatus(selected, connections) {
6940
+ function formatConnectionStatus(selected, connections, repo, remoteProjectLink) {
6482
6941
  const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
6483
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";
6484
6944
  return [
6485
6945
  formatSection("Rig server", "selected for this repo"),
6486
6946
  `${themeFaint("\u2502")} ${themeDim("selected ")} ${pc4.bold(selected)}`,
6487
6947
  `${themeFaint("\u2502")} ${themeDim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
6488
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}`] : [],
6489
6951
  "",
6490
- ...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`"])
6491
6953
  ].join(`
6492
6954
  `);
6493
6955
  }
@@ -6778,7 +7240,7 @@ var init_inbox = __esm(() => {
6778
7240
 
6779
7241
  // packages/cli/src/commands/_snapshot-upload.ts
6780
7242
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
6781
- import { dirname as dirname3, resolve as resolve17, relative, sep } from "path";
7243
+ import { dirname as dirname4, resolve as resolve17, relative, sep } from "path";
6782
7244
  function toPosixPath(path) {
6783
7245
  return path.split(sep).join("/");
6784
7246
  }
@@ -6879,7 +7341,7 @@ __export(exports_init, {
6879
7341
  DEMO_TASKS_RELATIVE_DIR: () => DEMO_TASKS_RELATIVE_DIR,
6880
7342
  DEMO_TASKS: () => DEMO_TASKS
6881
7343
  });
6882
- 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";
6883
7345
  import { spawnSync } from "child_process";
6884
7346
  import { basename as basename3, resolve as resolve18 } from "path";
6885
7347
  import { buildRigInitConfigSource } from "@rig/core";
@@ -6903,14 +7365,14 @@ function parseRepoSlug(value) {
6903
7365
  }
6904
7366
  function ensureRigPrivateDirs(projectRoot) {
6905
7367
  const rigDir = resolve18(projectRoot, ".rig");
6906
- mkdirSync8(resolve18(rigDir, "state"), { recursive: true });
6907
- mkdirSync8(resolve18(rigDir, "logs"), { recursive: true });
6908
- mkdirSync8(resolve18(rigDir, "runs"), { recursive: true });
6909
- mkdirSync8(resolve18(rigDir, "tmp"), { recursive: true });
6910
- 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 });
6911
7373
  const taskConfigPath = resolve18(rigDir, "task-config.json");
6912
7374
  if (!existsSync12(taskConfigPath))
6913
- writeFileSync5(taskConfigPath, `{}
7375
+ writeFileSync6(taskConfigPath, `{}
6914
7376
  `, "utf-8");
6915
7377
  }
6916
7378
  function ensureGitignoreEntries(projectRoot) {
@@ -6938,7 +7400,7 @@ function ensureRigConfigPackageDependencies(projectRoot) {
6938
7400
  ...existsSync12(path) ? existing : { name: "rig-project", private: true },
6939
7401
  devDependencies
6940
7402
  };
6941
- writeFileSync5(path, `${JSON.stringify(next, null, 2)}
7403
+ writeFileSync6(path, `${JSON.stringify(next, null, 2)}
6942
7404
  `, "utf8");
6943
7405
  }
6944
7406
  function applyGitHubProjectConfig(source, options) {
@@ -6964,9 +7426,9 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
6964
7426
  const selected = strategy ?? { kind: "managed-clone" };
6965
7427
  switch (selected.kind) {
6966
7428
  case "managed-clone":
6967
- return { kind: "managed-clone", path: projectRoot };
7429
+ return { kind: "managed-clone" };
6968
7430
  case "current-ref":
6969
- return { kind: "current-ref", path: projectRoot, ...selected.ref ? { ref: selected.ref } : {} };
7431
+ return { kind: "current-ref", ...selected.ref ? { ref: selected.ref } : {} };
6970
7432
  case "uploaded-snapshot":
6971
7433
  return { kind: "uploaded-snapshot", path: projectRoot, source: "local-working-tree" };
6972
7434
  case "existing-path":
@@ -7219,7 +7681,7 @@ function remoteGitHubAuthMetadata(payload) {
7219
7681
  };
7220
7682
  }
7221
7683
  function writeRemoteGitHubAuthState(projectRoot, input) {
7222
- writeFileSync5(resolve18(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
7684
+ writeFileSync6(resolve18(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
7223
7685
  authenticated: true,
7224
7686
  source: input.source,
7225
7687
  storedOnServer: true,
@@ -7267,7 +7729,7 @@ function runLocalFilesInit(context, options) {
7267
7729
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
7268
7730
  } else {
7269
7731
  const projectName = basename3(projectRoot) || "rig-project";
7270
- writeFileSync5(configTsPath, buildRigInitConfigSource({
7732
+ writeFileSync6(configTsPath, buildRigInitConfigSource({
7271
7733
  projectName,
7272
7734
  taskSource: { kind: "files", path: "tasks" },
7273
7735
  useStandardPlugin: true
@@ -7276,8 +7738,8 @@ function runLocalFilesInit(context, options) {
7276
7738
  ensureRigConfigPackageDependencies(projectRoot);
7277
7739
  const tasksDir = resolve18(projectRoot, "tasks");
7278
7740
  if (!existsSync12(tasksDir)) {
7279
- mkdirSync8(tasksDir, { recursive: true });
7280
- 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)}
7281
7743
  `, "utf-8");
7282
7744
  }
7283
7745
  if (context.outputMode !== "json") {
@@ -7301,7 +7763,7 @@ function runDemoInit(context, options) {
7301
7763
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
7302
7764
  }
7303
7765
  } else {
7304
- writeFileSync5(configTsPath, buildRigInitConfigSource({
7766
+ writeFileSync6(configTsPath, buildRigInitConfigSource({
7305
7767
  projectName: basename3(projectRoot) || "rig-demo",
7306
7768
  taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
7307
7769
  useStandardPlugin: true
@@ -7310,14 +7772,14 @@ function runDemoInit(context, options) {
7310
7772
  }
7311
7773
  ensureRigConfigPackageDependencies(projectRoot);
7312
7774
  const demoTasksDir = resolve18(projectRoot, DEMO_TASKS_RELATIVE_DIR);
7313
- mkdirSync8(demoTasksDir, { recursive: true });
7775
+ mkdirSync9(demoTasksDir, { recursive: true });
7314
7776
  const taskIds = [];
7315
7777
  for (const task of DEMO_TASKS) {
7316
7778
  const id = String(task.id);
7317
7779
  taskIds.push(id);
7318
7780
  const taskPath = resolve18(demoTasksDir, `${id}.json`);
7319
7781
  if (!existsSync12(taskPath)) {
7320
- writeFileSync5(taskPath, `${JSON.stringify(task, null, 2)}
7782
+ writeFileSync6(taskPath, `${JSON.stringify(task, null, 2)}
7321
7783
  `, "utf-8");
7322
7784
  }
7323
7785
  }
@@ -7345,21 +7807,32 @@ function runDemoInit(context, options) {
7345
7807
  }
7346
7808
  async function runControlPlaneInit(context, options) {
7347
7809
  const projectRoot = context.projectRoot;
7348
- 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");
7349
7821
  if (!detectedSlug) {
7350
7822
  const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
7351
- if ((options.server ?? "local") === "local" && authMethod2 === "skip") {
7823
+ if (serverKind === "local" && authMethod2 === "skip") {
7352
7824
  return runLocalFilesInit(context, options);
7353
7825
  }
7354
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);
7355
7827
  }
7356
7828
  const repo = parseRepoSlug(detectedSlug);
7357
- const serverKind = options.server ?? "local";
7358
- 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;
7359
7832
  if (serverKind === "remote") {
7360
- if (!options.remoteUrl)
7361
- throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`." });
7362
- 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 });
7363
7836
  }
7364
7837
  writeRepoConnection(projectRoot, {
7365
7838
  selected: connectionAlias,
@@ -7382,11 +7855,11 @@ async function runControlPlaneInit(context, options) {
7382
7855
  taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
7383
7856
  useStandardPlugin: true
7384
7857
  }), options);
7385
- writeFileSync5(configTsPath, source, "utf-8");
7858
+ writeFileSync6(configTsPath, source, "utf-8");
7386
7859
  }
7387
7860
  ensureRigConfigPackageDependencies(projectRoot);
7388
7861
  }
7389
- 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)}
7390
7863
  `, "utf8");
7391
7864
  const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
7392
7865
  let uploadedSnapshot = null;
@@ -7401,7 +7874,7 @@ async function runControlPlaneInit(context, options) {
7401
7874
  let githubAuth = null;
7402
7875
  let deviceAuth = null;
7403
7876
  const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
7404
- 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;
7405
7878
  if (remoteGhTokenWarning && !options.yes) {
7406
7879
  throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
7407
7880
  }
@@ -7781,8 +8254,8 @@ var init_init = __esm(() => {
7781
8254
 
7782
8255
  // packages/cli/src/commands/github.ts
7783
8256
  import { spawnSync as spawnSync2 } from "child_process";
7784
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
7785
- 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";
7786
8259
  function printPayload(context, payload, fallback) {
7787
8260
  if (context.outputMode === "json")
7788
8261
  console.log(JSON.stringify(payload, null, 2));
@@ -7821,8 +8294,8 @@ function persistRemoteAuthSession(context, source, result, fallbackToken) {
7821
8294
  return;
7822
8295
  const repo = readRepoConnection(context.projectRoot);
7823
8296
  const path = resolve19(context.projectRoot, ".rig", "state", "github-auth.json");
7824
- mkdirSync9(dirname4(path), { recursive: true });
7825
- writeFileSync6(path, `${JSON.stringify({
8297
+ mkdirSync10(dirname5(path), { recursive: true });
8298
+ writeFileSync7(path, `${JSON.stringify({
7826
8299
  authenticated: true,
7827
8300
  source,
7828
8301
  storedOnServer: true,
@@ -9333,7 +9806,7 @@ __export(exports__pi_frontend, {
9333
9806
  buildOperatorPiEnv: () => buildOperatorPiEnv,
9334
9807
  attachRunBundledPiFrontend: () => attachRunBundledPiFrontend
9335
9808
  });
9336
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
9809
+ import { existsSync as existsSync15, mkdirSync as mkdirSync11, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
9337
9810
  import { homedir as homedir6, tmpdir } from "os";
9338
9811
  import { join as join3 } from "path";
9339
9812
  import { main as runPiMain } from "@earendil-works/pi-coding-agent";
@@ -9385,7 +9858,7 @@ function statusFromRunDetails(run) {
9385
9858
  async function prepareOperatorConsole(context, runId, tempSessionDir) {
9386
9859
  const server = await ensureServerForCli(context.projectRoot);
9387
9860
  const localCwd = join3(homedir6(), ".rig", "drones", runId.slice(0, 8));
9388
- mkdirSync10(localCwd, { recursive: true });
9861
+ mkdirSync11(localCwd, { recursive: true });
9389
9862
  trustDroneCwd(localCwd);
9390
9863
  installRigPiTheme();
9391
9864
  let sessionFileArg = [];
@@ -9407,7 +9880,7 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
9407
9880
  return line;
9408
9881
  }).join(`
9409
9882
  `);
9410
- writeFileSync7(localSessionPath, content);
9883
+ writeFileSync8(localSessionPath, content);
9411
9884
  sessionFileArg = ["--session", localSessionPath];
9412
9885
  }
9413
9886
  } catch {}
@@ -9423,12 +9896,12 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
9423
9896
  function trustDroneCwd(localCwd) {
9424
9897
  try {
9425
9898
  const agentDir = join3(homedir6(), ".pi", "agent");
9426
- mkdirSync10(agentDir, { recursive: true });
9899
+ mkdirSync11(agentDir, { recursive: true });
9427
9900
  const trustPath = join3(agentDir, "trust.json");
9428
9901
  const store = existsSync15(trustPath) ? JSON.parse(readFileSync10(trustPath, "utf8")) : {};
9429
9902
  if (store[localCwd] !== true) {
9430
9903
  store[localCwd] = true;
9431
- writeFileSync7(trustPath, `${JSON.stringify(store, null, "\t")}
9904
+ writeFileSync8(trustPath, `${JSON.stringify(store, null, "\t")}
9432
9905
  `);
9433
9906
  }
9434
9907
  } catch {}
@@ -9436,12 +9909,12 @@ function trustDroneCwd(localCwd) {
9436
9909
  function installRigPiTheme() {
9437
9910
  try {
9438
9911
  const themesDir = join3(homedir6(), ".pi", "agent", "themes");
9439
- mkdirSync10(themesDir, { recursive: true });
9912
+ mkdirSync11(themesDir, { recursive: true });
9440
9913
  const themePath = join3(themesDir, "rig.json");
9441
9914
  const next = `${JSON.stringify(RIG_PI_THEME, null, "\t")}
9442
9915
  `;
9443
9916
  if (!existsSync15(themePath) || readFileSync10(themePath, "utf8") !== next) {
9444
- writeFileSync7(themePath, next);
9917
+ writeFileSync8(themePath, next);
9445
9918
  }
9446
9919
  } catch {}
9447
9920
  }
@@ -10225,13 +10698,46 @@ function parseConnection(alias, value, options) {
10225
10698
  }
10226
10699
  return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
10227
10700
  }
10228
- function printJsonOrText(context, payload, text2) {
10229
- if (context.outputMode === "json") {
10230
- console.log(JSON.stringify(payload, null, 2));
10231
- } else {
10701
+ function printJsonOrText(context, _payload, text2) {
10702
+ if (context.outputMode !== "json") {
10232
10703
  console.log(text2);
10233
10704
  }
10234
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
+ }
10235
10741
  async function promptForConnectionAlias(context) {
10236
10742
  const state = readGlobalConnections();
10237
10743
  const repo = readRepoConnection(context.projectRoot);
@@ -10285,37 +10791,67 @@ async function executeConnectionCommand(context, args, options) {
10285
10791
  }
10286
10792
  if (!alias)
10287
10793
  throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
10794
+ let selectedConnection = { kind: "local", mode: "auto" };
10288
10795
  if (alias !== "local") {
10289
10796
  const state = readGlobalConnections();
10290
- if (!state.connections[alias])
10797
+ const connection = state.connections[alias];
10798
+ if (!connection)
10291
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;
10292
10801
  }
10293
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);
10294
10804
  const repoState = {
10295
10805
  selected: alias,
10296
10806
  ...previousRepo?.project ? { project: previousRepo.project } : {},
10297
10807
  linkedAt: new Date().toISOString(),
10298
- ...previousRepo?.serverProjectRoot && previousRepo.selected === alias ? { serverProjectRoot: previousRepo.serverProjectRoot } : {}
10808
+ ...preserveRemoteRoot ? {
10809
+ serverProjectRoot: previousRepo.serverProjectRoot,
10810
+ serverProjectRootAlias: previousRepo.serverProjectRootAlias,
10811
+ serverProjectRootBaseUrl: previousRepo.serverProjectRootBaseUrl
10812
+ } : {}
10299
10813
  };
10300
10814
  writeRepoConnection(context.projectRoot, repoState);
10301
- 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", [
10302
10824
  ["selected", alias],
10303
10825
  ["scope", "this repo"],
10304
- ["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"]
10305
10828
  ]));
10306
- 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 };
10307
10836
  }
10308
10837
  case "status": {
10309
10838
  requireNoExtraArgs(rest, `${usageName(options)} status`);
10310
10839
  const repo = readRepoConnection(context.projectRoot);
10311
10840
  const global = readGlobalConnections();
10312
- const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
10313
- 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));
10314
10850
  return { ok: true, group: options.group, command: "status", details };
10315
10851
  }
10316
10852
  default:
10317
10853
  throw new CliError(`Unknown ${options.group} command: ${String(command)}
10318
- Usage: ${usageName(options)} <list|add|use|status>`, 1);
10854
+ Usage: ${usageName(options)} <list|add|use|status|repair-link>`, 1);
10319
10855
  }
10320
10856
  }
10321
10857
  var init_connect = __esm(() => {
@@ -10323,13 +10859,14 @@ var init_connect = __esm(() => {
10323
10859
  init_runner();
10324
10860
  init__connection_state();
10325
10861
  init__cli_format();
10862
+ init__server_client();
10326
10863
  });
10327
10864
 
10328
10865
  // packages/cli/src/commands/server.ts
10329
10866
  import { resolveRigServerCommand } from "@rig/runtime/local-server";
10330
10867
  async function executeServer(context, args, options) {
10331
10868
  const [command = "status", ...rest] = args;
10332
- if (["status", "list", "add", "use"].includes(command)) {
10869
+ if (["status", "list", "add", "use", "repair-link"].includes(command)) {
10333
10870
  return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
10334
10871
  }
10335
10872
  switch (command) {
@@ -11116,7 +11653,7 @@ var init_task = __esm(() => {
11116
11653
  });
11117
11654
 
11118
11655
  // packages/cli/src/commands/task-run-driver.ts
11119
- 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";
11120
11657
  import { resolve as resolve24 } from "path";
11121
11658
  import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
11122
11659
  import { createInterface as createLineInterface } from "readline";
@@ -11219,7 +11756,7 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
11219
11756
  try {
11220
11757
  if (!statSync2(sourcePath).isFile())
11221
11758
  continue;
11222
- mkdirSync11(resolve24(targetPath, ".."), { recursive: true });
11759
+ mkdirSync12(resolve24(targetPath, ".."), { recursive: true });
11223
11760
  copyFileSync3(sourcePath, targetPath);
11224
11761
  copied += 1;
11225
11762
  } catch {}
@@ -12090,8 +12627,8 @@ async function executeRigOwnedTaskRun(context, input) {
12090
12627
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
12091
12628
  classifiedAt: new Date().toISOString()
12092
12629
  };
12093
- mkdirSync11(resolve24(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
12094
- 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)}
12095
12632
  `, "utf8");
12096
12633
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
12097
12634
  prompt = `${prompt}
@@ -12238,8 +12775,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
12238
12775
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
12239
12776
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
12240
12777
  if (readyFile) {
12241
- mkdirSync11(resolve24(readyFile, ".."), { recursive: true });
12242
- 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)}
12243
12780
  `, "utf8");
12244
12781
  }
12245
12782
  appendRunLog(context.projectRoot, input.runId, {
@@ -13185,7 +13722,7 @@ var init_test = __esm(() => {
13185
13722
  });
13186
13723
 
13187
13724
  // packages/cli/src/commands/setup.ts
13188
- 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";
13189
13726
  import { resolve as resolve25 } from "path";
13190
13727
  import { createPluginHost } from "@rig/core";
13191
13728
  import {
@@ -13240,12 +13777,12 @@ function runSetupInit(projectRoot) {
13240
13777
  const stateDir = resolveControlPlaneHostStateDir(projectRoot);
13241
13778
  const logsDir = resolveControlPlaneHostLogsDir(projectRoot);
13242
13779
  const artifactsDir = resolveControlPlaneArtifactsDir(projectRoot);
13243
- mkdirSync12(stateDir, { recursive: true });
13244
- mkdirSync12(logsDir, { recursive: true });
13245
- mkdirSync12(artifactsDir, { recursive: true });
13780
+ mkdirSync13(stateDir, { recursive: true });
13781
+ mkdirSync13(logsDir, { recursive: true });
13782
+ mkdirSync13(artifactsDir, { recursive: true });
13246
13783
  const failuresPath = resolve25(stateDir, "failed_approaches.md");
13247
13784
  if (!existsSync17(failuresPath)) {
13248
- writeFileSync9(failuresPath, `# Failed Approaches
13785
+ writeFileSync10(failuresPath, `# Failed Approaches
13249
13786
 
13250
13787
  `, "utf-8");
13251
13788
  }
@@ -15429,6 +15966,51 @@ var init_selectable = __esm(() => {
15429
15966
  init_scene();
15430
15967
  });
15431
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
+
15432
16014
  // packages/cli/src/app-opentui/fleet-stats.ts
15433
16015
  function normalizeStatus(status) {
15434
16016
  return status.replace(/_/g, "-").toLowerCase();
@@ -15619,7 +16201,9 @@ import {
15619
16201
  RigInboxInputsOutput,
15620
16202
  RigRunListOutput,
15621
16203
  RigRunShowOutput,
16204
+ RigServerRepairLinkOutput,
15622
16205
  RigServerStatusOutput,
16206
+ RigServerUseOutput,
15623
16207
  RigStatsOutput,
15624
16208
  RigTaskListOutput,
15625
16209
  RigTaskShowOutput
@@ -15655,7 +16239,9 @@ var init__json_output = __esm(() => {
15655
16239
  "task show": RigTaskShowOutput,
15656
16240
  "run list": RigRunListOutput,
15657
16241
  "run show": RigRunShowOutput,
16242
+ "server use": RigServerUseOutput,
15658
16243
  "server status": RigServerStatusOutput,
16244
+ "server repair-link": RigServerRepairLinkOutput,
15659
16245
  "inbox approvals": RigInboxApprovalsOutput,
15660
16246
  "inbox inputs": RigInboxInputsOutput,
15661
16247
  "doctor check": RigDoctorCheckOutput,
@@ -15986,16 +16572,31 @@ function taskViewIntent(argv, command = "", rest = [], raw) {
15986
16572
  const { payload, label } = taskViewPayload(command, rest);
15987
16573
  return intent("tasks", argv, "refresh", payload, label, raw);
15988
16574
  }
15989
- function routeServer(argv, command, raw, checkingLabel) {
15990
- if (!command || command === "status") {
16575
+ function routeServer(argv, command, rest, raw, checkingLabel) {
16576
+ if (!command || command === "status" || command === "list") {
15991
16577
  return intent("server", argv, "refresh", undefined, command ? checkingLabel : "Loading server", raw);
15992
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);
15993
16591
  return commandRunIntent(argv, raw);
15994
16592
  }
15995
16593
  function routeGithub(argv, command, rest, raw) {
15996
16594
  if (command === "auth" && (rest[0]?.toLowerCase() ?? "status") === "status" && rest.length <= 1) {
15997
16595
  return intent("server", argv, "refresh", undefined, "Checking GitHub auth", raw);
15998
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
+ }
15999
16600
  return commandRunIntent(argv, raw);
16000
16601
  }
16001
16602
  function routeDoctor(argv, command, raw) {
@@ -16044,11 +16645,16 @@ function intentFromArgv(argv) {
16044
16645
  return commandRunIntent(argv);
16045
16646
  }
16046
16647
  if (normalizedGroup === "server")
16047
- return routeServer(argv, normalizedCommand, undefined, "Checking server");
16648
+ return routeServer(argv, normalizedCommand, rest, undefined, "Checking server");
16048
16649
  if (normalizedGroup === "github")
16049
16650
  return routeGithub(argv, normalizedCommand, rest);
16050
- if (normalizedGroup === "init")
16051
- 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
+ }
16052
16658
  if (normalizedGroup === "doctor")
16053
16659
  return routeDoctor(argv, normalizedCommand);
16054
16660
  if (normalizedGroup === "inbox")
@@ -16111,12 +16717,17 @@ function intentFromTypeBar(value) {
16111
16717
  return commandRunIntent(parts, value);
16112
16718
  return intent("tasks", parts, "refresh", undefined, "Select a task, then press Enter; or use run next", value);
16113
16719
  }
16114
- if (first === "init")
16115
- 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
+ }
16116
16727
  if (first === "doctor")
16117
16728
  return routeDoctor(parts, second, value);
16118
16729
  if (first === "server")
16119
- return routeServer(parts, second, value, "Loading server");
16730
+ return routeServer(parts, second, rest, value, "Loading server");
16120
16731
  if (first === "github")
16121
16732
  return routeGithub(parts, second, rest, value);
16122
16733
  if (first === "inbox")
@@ -16559,12 +17170,6 @@ var init_autocomplete = __esm(() => {
16559
17170
  NAV_VERBS = ["runs", "tasks", "inbox", "server", "doctor", "help", "init", "main"];
16560
17171
  });
16561
17172
 
16562
- // packages/cli/src/app-opentui/pi-pty-host.ts
16563
- function getActivePiHost() {
16564
- return null;
16565
- }
16566
- function stopActivePiHost(_reason) {}
16567
-
16568
17173
  // packages/cli/src/app-opentui/keymap.ts
16569
17174
  function clearTypeBar(context, message2) {
16570
17175
  const typeBar = context.getTypeBar();
@@ -16573,22 +17178,15 @@ function clearTypeBar(context, message2) {
16573
17178
  context.emitTypeBarPatch({ value: "", mode: "nav", prompt: undefined, message: message2 });
16574
17179
  }
16575
17180
  function handleEmbeddedTerminalKey(context, key) {
16576
- const state = context.getState();
16577
- const piHost = getActivePiHost();
16578
- const commandHost = getActiveCommandHost();
16579
- const host = state.scene === "handoff" ? piHost : state.scene === "command" ? commandHost : null;
17181
+ if (context.getState().scene !== "command")
17182
+ return false;
17183
+ const host = getActiveCommandHost();
16580
17184
  if (!host)
16581
17185
  return false;
16582
17186
  if (key.ctrl && key.name === "]") {
16583
- if (state.scene === "handoff") {
16584
- stopActivePiHost("operator detach");
16585
- clearTypeBar(context, "detached from Pi");
16586
- context.runAppAction("Opening runs", () => context.runtime.runIntent({ scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } }));
16587
- } else {
16588
- stopActiveCommandHost("operator detach");
16589
- clearTypeBar(context, "closed command");
16590
- context.runAppAction("Project menu", () => context.runtime.runIntent({ scene: "main", argv: ["main"], action: { kind: "none", label: "Project menu" } }));
16591
- }
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" } }));
16592
17190
  return true;
16593
17191
  }
16594
17192
  const sequence = key.raw || key.sequence;
@@ -18097,10 +18695,14 @@ var init_preloader = __esm(() => {
18097
18695
  });
18098
18696
 
18099
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
+ }
18100
18701
  function renderErrorScene(state) {
18101
18702
  const message2 = state.error?.message ?? "Unknown app error";
18102
18703
  const hint = state.error?.hint ?? "Use the actions below to recover.";
18103
- 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));
18104
18706
  return makeSceneFrame({
18105
18707
  scene: "error",
18106
18708
  title: "Error",
@@ -18118,7 +18720,7 @@ function renderErrorScene(state) {
18118
18720
  ...hint ? [line(hint, { fg: RIG_UI.yellow })] : [],
18119
18721
  line("", { fg: RIG_UI.ink3 }),
18120
18722
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
18121
- ...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))
18122
18724
  ],
18123
18725
  backgroundColor: RIG_UI.panel,
18124
18726
  backgroundAlpha: 184,
@@ -18135,18 +18737,19 @@ function renderErrorScene(state) {
18135
18737
  live: true
18136
18738
  });
18137
18739
  }
18138
- var ERROR_ACTIONS;
18740
+ var ERROR_ACTIONS, ERROR_REPAIR_LINK_ACTION;
18139
18741
  var init_error = __esm(() => {
18140
18742
  init_drone();
18141
18743
  init_theme2();
18744
+ init_remote_link();
18142
18745
  init_scene();
18143
18746
  init_selectable();
18144
18747
  ERROR_ACTIONS = [
18145
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" } },
18146
18749
  { detail: "run diagnostics", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "run diagnostics" } },
18147
- { 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" } },
18148
- { 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" } }
18149
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" } };
18150
18753
  });
18151
18754
 
18152
18755
  // packages/cli/src/app-opentui/scenes/help.ts
@@ -18321,7 +18924,7 @@ function configuredMenuRows(selectedIndex2) {
18321
18924
  }));
18322
18925
  }
18323
18926
  function onboardingRows(selectedIndex2) {
18324
- 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);
18325
18928
  const secondary = ONBOARD_SECONDARY.map(([label, detail, item], index) => ({
18326
18929
  ...deckRow({ label, detail, index: index + 1, active: selectedIndex2 === index + 1, activateOnClick: true }),
18327
18930
  selectable: item
@@ -18381,7 +18984,7 @@ function renderMainScene(state, layout) {
18381
18984
  statusLine(state)
18382
18985
  ] : [
18383
18986
  line("WELCOME", { fg: RIG_UI.ink, bold: true }),
18384
- 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 }),
18385
18988
  blank(),
18386
18989
  line("GET STARTED", { fg: RIG_UI.ink, bold: true }),
18387
18990
  ...onboardingRows(actionSelected),
@@ -18422,7 +19025,7 @@ var init_main = __esm(() => {
18422
19025
  init_theme2();
18423
19026
  init_fleet_stats();
18424
19027
  CONFIGURED_MENU = [
18425
- ["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" }],
18426
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" }],
18427
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" }],
18428
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" }],
@@ -18441,10 +19044,10 @@ var init_main = __esm(() => {
18441
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" }]
18442
19045
  ];
18443
19046
  ONBOARD_PRIMARY = {
18444
- id: "onboard-setup",
18445
- label: "Start guided setup",
18446
- intent: { scene: "init", argv: ["init"], action: { kind: "refresh", label: "Setup" } },
18447
- 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"
18448
19051
  };
18449
19052
  ONBOARD_SECONDARY = [
18450
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" }],
@@ -18457,6 +19060,12 @@ var init_main = __esm(() => {
18457
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" };
18458
19061
  });
18459
19062
 
19063
+ // packages/cli/src/app-opentui/pi-pty-host.ts
19064
+ function getActivePiHost() {
19065
+ return null;
19066
+ }
19067
+ function stopActivePiHost(_reason) {}
19068
+
18460
19069
  // packages/cli/src/app-opentui/runtime.ts
18461
19070
  var exports_runtime = {};
18462
19071
  __export(exports_runtime, {
@@ -18523,7 +19132,7 @@ function entryIntentForScene(scene) {
18523
19132
  case "doctor":
18524
19133
  return { scene, argv: ["doctor"], action: { kind: "doctor-run", label: "Doctor" } };
18525
19134
  case "init":
18526
- return { scene, argv: ["init"], action: { kind: "refresh", label: "Setup" } };
19135
+ return { scene, argv: ["init"], action: { kind: "refresh", label: "Init" } };
18527
19136
  case "run-detail":
18528
19137
  return { scene, argv: ["run", "status"], action: { kind: "refresh", label: "Run" } };
18529
19138
  case "pi":
@@ -18612,7 +19221,7 @@ function sceneSectionLabel(scene) {
18612
19221
  handoff: "pi console",
18613
19222
  server: "server",
18614
19223
  doctor: "doctor",
18615
- init: "setup",
19224
+ init: "init",
18616
19225
  pi: "pi",
18617
19226
  plugin: "plugins",
18618
19227
  repo: "repo",
@@ -19102,7 +19711,7 @@ async function launchRigOpenTuiApp(options) {
19102
19711
  }
19103
19712
  for (const adapter of options.adapters ?? [])
19104
19713
  await adapter.start?.(runtime);
19105
- liveEvents = await connectRigServerEvents(options.projectRoot, {
19714
+ connectRigServerEvents(options.projectRoot, {
19106
19715
  onSnapshotInvalidated: () => refreshCurrentDataScene(),
19107
19716
  onRunLogAppended: () => {
19108
19717
  const scene = store.getState().scene;
@@ -19110,11 +19719,17 @@ async function launchRigOpenTuiApp(options) {
19110
19719
  refreshCurrentDataScene();
19111
19720
  },
19112
19721
  onStatus: (status) => store.patch({ footer: { live: liveLinkLabel(status) } })
19113
- }).catch(() => null);
19114
- if (!liveEvents)
19115
- store.patch({ footer: { live: liveLinkLabel("disconnected") } });
19116
- if (liveEvents)
19722
+ }).then((subscription) => {
19723
+ if (destroyed) {
19724
+ subscription.close();
19725
+ return;
19726
+ }
19727
+ liveEvents = subscription;
19117
19728
  resources.add(() => liveEvents?.close());
19729
+ }).catch(() => {
19730
+ if (!destroyed)
19731
+ store.patch({ footer: { live: liveLinkLabel("disconnected") } });
19732
+ });
19118
19733
  await runtime.runIntent(store.getState().intent);
19119
19734
  } catch (error) {
19120
19735
  const normalized = normalizeError(error);
@@ -19307,7 +19922,7 @@ var init_runtime = __esm(() => {
19307
19922
  { id: "tasks", label: "Tasks", scene: "tasks", argv: ["tasks"], enterLabel: "Tasks", member: ["tasks"] },
19308
19923
  { id: "inbox", label: "Inbox", scene: "inbox", argv: ["inbox"], enterLabel: "Inbox", member: ["inbox"], badge: inboxPendingCount },
19309
19924
  { id: "stats", label: "Stats", scene: "family", argv: ["stats"], enterLabel: "Stats", member: ["family"] },
19310
- { 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"] },
19311
19926
  { id: "help", label: "Help", scene: "help", argv: ["help"], enterLabel: "Help", member: ["help"] }
19312
19927
  ];
19313
19928
  SCENE_DATA_KEY = {
@@ -19671,7 +20286,7 @@ var init_nav = __esm(() => {
19671
20286
  { id: "tasks", label: "Tasks", scene: "tasks", argv: ["tasks"], enterLabel: "Tasks", member: ["tasks"] },
19672
20287
  { id: "inbox", label: "Inbox", scene: "inbox", argv: ["inbox"], enterLabel: "Inbox", member: ["inbox"], badge: inboxPendingCount2 },
19673
20288
  { id: "stats", label: "Stats", scene: "family", argv: ["stats"], enterLabel: "Stats", member: ["family"] },
19674
- { 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"] },
19675
20290
  { id: "help", label: "Help", scene: "help", argv: ["help"], enterLabel: "Help", member: ["help"] }
19676
20291
  ];
19677
20292
  });
@@ -19937,7 +20552,7 @@ async function launchRigReactApp(options) {
19937
20552
  }
19938
20553
  for (const adapter of options.adapters ?? [])
19939
20554
  await adapter.start?.(runtime);
19940
- liveEvents = await connectRigServerEvents(options.projectRoot, {
20555
+ connectRigServerEvents(options.projectRoot, {
19941
20556
  onSnapshotInvalidated: () => refreshCurrentDataScene(),
19942
20557
  onRunLogAppended: () => {
19943
20558
  const scene = store.getState().scene;
@@ -19945,9 +20560,11 @@ async function launchRigReactApp(options) {
19945
20560
  refreshCurrentDataScene();
19946
20561
  },
19947
20562
  onStatus: (status) => store.patch({ footer: { live: liveLinkLabel(status) } })
19948
- }).catch(() => null);
19949
- if (!liveEvents)
20563
+ }).then((subscription) => {
20564
+ liveEvents = subscription;
20565
+ }).catch(() => {
19950
20566
  store.patch({ footer: { live: liveLinkLabel("disconnected") } });
20567
+ });
19951
20568
  await runtime.runIntent(store.getState().intent);
19952
20569
  } catch (error) {
19953
20570
  const n = normalizeError(error);
@@ -21108,6 +21725,7 @@ async function stopRunFromDetail(ctx, runId) {
21108
21725
  }
21109
21726
 
21110
21727
  // packages/cli/src/app-opentui/adapters/server.ts
21728
+ import { spawnSync as spawnSync5 } from "child_process";
21111
21729
  var SERVER_PROBE_TIMEOUT_MS = Number(process.env.RIG_SERVER_PROBE_TIMEOUT_MS) || 5000;
21112
21730
  function withTimeout(promise, ms, label) {
21113
21731
  return new Promise((resolve29, reject) => {
@@ -21145,11 +21763,128 @@ function startLocalRequested(ctx) {
21145
21763
  const action = intent.action;
21146
21764
  return action?.payload?.startLocal === true;
21147
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
+ }
21148
21882
  async function startLocalServerFromApp(ctx) {
21149
21883
  const label = "Starting local server";
21150
21884
  emitStarted(ctx, label);
21151
21885
  patchData(ctx, {
21152
21886
  lastIntent: undefined,
21887
+ remoteProjectLink: undefined,
21153
21888
  server: { label: "starting local server", reachable: false, error: "spawning local rig-server (detached)\u2026" }
21154
21889
  });
21155
21890
  try {
@@ -21174,32 +21909,61 @@ async function startLocalServerFromApp(ctx) {
21174
21909
  reachable: false,
21175
21910
  error: error instanceof Error ? error.message : String(error)
21176
21911
  };
21177
- patchData(ctx, { server: state, lastRefreshError: state.error });
21912
+ patchData(ctx, { server: state, remoteProjectLink: undefined, lastRefreshError: state.error });
21178
21913
  patchFooter(ctx, { server: "unavailable" });
21179
- emitCompleted(ctx, label, { server: state.label, reachable: false, error: state.error });
21914
+ emitCompleted(ctx, label, { serverLabel: state.label, reachable: false, error: state.error });
21180
21915
  return state;
21181
21916
  }
21182
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
+ }
21183
21945
  async function refreshServerStatus(ctx) {
21184
21946
  if (startLocalRequested(ctx)) {
21185
21947
  return startLocalServerFromApp(ctx);
21186
21948
  }
21187
21949
  const label = "Checking server";
21188
21950
  emitStarted(ctx, label);
21189
- 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 });
21190
21952
  const started = Date.now();
21191
21953
  try {
21192
- 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));
21193
21955
  const projectRoot = projectRootOf(ctx);
21956
+ const aliases = await serverAliases(projectRoot);
21194
21957
  emitProgress(ctx, label, "resolving selected server");
21195
21958
  const [connectionLabel, server] = await Promise.all([
21196
21959
  resolveServerConnectionLabel2(projectRoot),
21197
21960
  withTimeout(ensureServerForCli2(projectRoot), 5000, "server connection")
21198
21961
  ]);
21199
21962
  emitProgress(ctx, label, "requesting server status", { baseUrl: server.baseUrl, kind: server.connectionKind });
21200
- const [status, auth] = await Promise.all([
21963
+ const [status, auth, remoteProjectLink] = await Promise.all([
21201
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) })),
21202
- 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)
21203
21967
  ]);
21204
21968
  const state = {
21205
21969
  label: connectionLabel,
@@ -21208,22 +21972,25 @@ async function refreshServerStatus(ctx) {
21208
21972
  reachable: true,
21209
21973
  latencyMs: Date.now() - started,
21210
21974
  status: status && typeof status === "object" && !Array.isArray(status) ? status : {},
21211
- 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 } : {}
21212
21978
  };
21213
- patchData(ctx, { server: state });
21979
+ patchData(ctx, { server: state, remoteProjectLink: state.remoteProjectLink });
21214
21980
  patchFooter(ctx, { server: connectionLabel, latency: `${state.latencyMs}ms` });
21215
- emitCompleted(ctx, label, { server: connectionLabel, latencyMs: state.latencyMs });
21981
+ emitCompleted(ctx, label, { serverLabel: connectionLabel, latencyMs: state.latencyMs });
21216
21982
  return state;
21217
21983
  } catch (error) {
21218
21984
  const state = {
21219
21985
  label: "server unavailable",
21220
21986
  reachable: false,
21221
21987
  latencyMs: Date.now() - started,
21988
+ aliases: await serverAliases(projectRootOf(ctx)).catch(() => []),
21222
21989
  error: error instanceof Error ? error.message : String(error)
21223
21990
  };
21224
- patchData(ctx, { server: state });
21991
+ patchData(ctx, { server: state, remoteProjectLink: undefined });
21225
21992
  patchFooter(ctx, { server: "unavailable" });
21226
- emitCompleted(ctx, label, { server: state.label, reachable: false, error: state.error });
21993
+ emitCompleted(ctx, label, { serverLabel: state.label, reachable: false, error: state.error });
21227
21994
  return state;
21228
21995
  }
21229
21996
  }
@@ -21810,7 +22577,7 @@ init_theme2();
21810
22577
  var DOCTOR_ACTIONS = [
21811
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" } },
21812
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" } },
21813
- { 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" } },
21814
22581
  { detail: "open tasks", item: { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks" } }
21815
22582
  ];
21816
22583
  function checks(state) {
@@ -21886,6 +22653,7 @@ function renderDoctorScene(state, layout) {
21886
22653
  // packages/cli/src/app-opentui/scenes/family.ts
21887
22654
  init_scene();
21888
22655
  init_selectable();
22656
+ init_remote_link();
21889
22657
  init_theme2();
21890
22658
 
21891
22659
  // packages/cli/src/app-opentui/scenes/family-domains/kit.ts
@@ -23648,13 +24416,18 @@ function overviewBody(state, family, snap, layout) {
23648
24416
  function familyRecoveryRows(state, family, errorMessage2) {
23649
24417
  const selected = Math.max(0, state.selection.index);
23650
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 });
23651
- return [
24419
+ const repairRemote = shouldOfferRemoteLinkRepair(state, errorMessage2);
24420
+ const rows = [
23652
24421
  intro,
23653
- blank(),
23654
- 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" }),
23655
- 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" }),
23656
- 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()
23657
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;
23658
24431
  }
23659
24432
  function renderFamilyScene(state, layout) {
23660
24433
  const family = typeof state.data.familyName === "string" ? state.data.familyName : "";
@@ -23663,9 +24436,16 @@ function renderFamilyScene(state, layout) {
23663
24436
  const loading = state.status === "loading" && !snap;
23664
24437
  const errorMessage2 = state.error?.message ?? lastRefreshError(state) ?? undefined;
23665
24438
  const probeFailed = snap ? snap.state.ran === false && Boolean(state.error) : Boolean(state.error);
24439
+ const selected = Math.max(0, state.selection.index);
23666
24440
  const body = [
23667
24441
  ...loading ? loadingRows(family || "family", state.tick) : snap ? [
23668
- ...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
+ ] : [],
23669
24449
  ...current.subId ? formView(state, family, snap, current).lines : overviewBody(state, family, snap, layout)
23670
24450
  ] : familyRecoveryRows(state, family, errorMessage2)
23671
24451
  ];
@@ -23817,13 +24597,14 @@ function sortTasks2(tasks, spec) {
23817
24597
 
23818
24598
  // packages/cli/src/app-opentui/scenes/fleet.ts
23819
24599
  init_fleet_stats();
24600
+ init_remote_link();
23820
24601
  init_theme2();
23821
- var FLEET_RECOVERY_ITEMS = [
24602
+ var BASE_FLEET_RECOVERY_ITEMS = [
23822
24603
  { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks and dispatch work" },
23823
- { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair project link" } }, message: "repair project/server/auth link" },
23824
24604
  { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
23825
24605
  { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose project" }
23826
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" };
23827
24608
  var COL2 = {
23828
24609
  glyph: 1,
23829
24610
  runId: 8,
@@ -23988,33 +24769,43 @@ function runRow(width, run, active, optimistic) {
23988
24769
  const item = runSelectableItem(run);
23989
24770
  return item ? withSelectable(row, item) : row;
23990
24771
  }
23991
- var RECOVERY_DECKS = [
24772
+ var BASE_RECOVERY_DECKS = [
23992
24773
  { label: "tasks", detail: "open tasks and dispatch work" },
23993
- { label: "repair", detail: "repair project/server/auth link" },
23994
24774
  { label: "server", detail: "open server controls" },
23995
24775
  { label: "doctor", detail: "diagnose project" }
23996
24776
  ];
23997
- 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);
23998
24789
  return [
23999
24790
  ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
24000
24791
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
24001
- ...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]))
24002
24793
  ];
24003
24794
  }
24004
- function emptyRows(query, total, selected) {
24795
+ function emptyRows(state, query, total, selected) {
24005
24796
  if (!query.trim() && total === 0)
24006
- return recoveryRows(selected, "No runs yet.");
24797
+ return recoveryRows(state, selected, "No runs yet.");
24007
24798
  if (query.trim() && total > 0) {
24008
24799
  return [
24009
- ...recoveryRows(selected, `No matching runs for ${JSON.stringify(query)} (${total} total).`)
24800
+ ...recoveryRows(state, selected, `No matching runs for ${JSON.stringify(query)} (${total} total).`)
24010
24801
  ];
24011
24802
  }
24012
- return recoveryRows(selected, "No runs are visible with this filter.");
24803
+ return recoveryRows(state, selected, "No runs are visible with this filter.");
24013
24804
  }
24014
24805
  function degradedRows(state, selected) {
24015
24806
  if (!state.error)
24016
24807
  return [];
24017
- return recoveryRows(selected, state.error.message);
24808
+ return recoveryRows(state, selected, state.error.message);
24018
24809
  }
24019
24810
  function renderFleetScene(state, layout) {
24020
24811
  const width = panelWidth4(layout);
@@ -24024,7 +24815,7 @@ function renderFleetScene(state, layout) {
24024
24815
  const runs = sortRuns(filterRunsForSearch(allRuns, query), spec);
24025
24816
  const selectedRun = typeof state.data.selectedRunId === "string" && runs.some((run) => run.runId === state.data.selectedRunId) ? state.data.selectedRunId : runs[0]?.runId;
24026
24817
  const selectedIndex = Math.max(0, runs.findIndex((run) => run.runId === selectedRun));
24027
- 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));
24028
24819
  const optimistic = optimisticStatuses(state);
24029
24820
  const updatedAgo = updatedAgoLabel(state);
24030
24821
  const panelTop = 0;
@@ -24056,7 +24847,7 @@ function renderFleetScene(state, layout) {
24056
24847
  ...runs.map((run, index) => runRow(contentWidth, run, index === selectedIndex, optimistic.get(run.runId)))
24057
24848
  ] : state.error ? degradedRows(state, recoveryIndex) : loading ? loadingRows("runs", state.tick) : [
24058
24849
  ...query.trim() ? [line(searchSummary("runs", query, runs.length, allRuns.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
24059
- ...emptyRows(query, allRuns.length, recoveryIndex)
24850
+ ...emptyRows(state, query, allRuns.length, recoveryIndex)
24060
24851
  ]
24061
24852
  ];
24062
24853
  return makeSceneFrame({
@@ -24544,19 +25335,19 @@ function renderInboxScene(state, layout) {
24544
25335
  init_scene();
24545
25336
  init_selectable();
24546
25337
  init_theme2();
24547
- 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" };
24548
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" };
24549
- 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" };
24550
- 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" };
24551
25342
  var SETUP_STEPS = [
24552
- { 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 },
24553
25344
  { id: "server", label: "Server target", detail: "local or remote, reachable", done: (s) => s.serverReachable, cta: "Choose a server", item: STEP_SERVER },
24554
25345
  { id: "auth", label: "GitHub auth", detail: "signed in on the selected server", done: (s) => s.authSignedIn, cta: "Connect GitHub", item: STEP_AUTH },
24555
25346
  { id: "tasks", label: "Task source", detail: "where work comes from", done: (s) => s.taskSourceReady, cta: "Pick a task source", item: STEP_TASKS }
24556
25347
  ];
24557
25348
  var SECONDARY_ACTIONS = [
24558
- { 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" } },
24559
- { 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" } },
24560
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" } }
24561
25352
  ];
24562
25353
  function record2(value) {
@@ -24600,11 +25391,11 @@ function renderInitScene(state, layout) {
24600
25391
  const allDone = completed === SETUP_STEPS.length;
24601
25392
  const nextStep = SETUP_STEPS.find((step) => !step.done(signals));
24602
25393
  const selected = Math.max(0, state.selection.index);
24603
- 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" });
24604
25395
  const secondaryRows = SECONDARY_ACTIONS.map(({ label, detail, item }, index) => selectableDeckRow({ label, detail, active: selected === index + 1 }, item));
24605
25396
  const progress = allDone ? "all set" : `${completed} of ${SETUP_STEPS.length} complete`;
24606
25397
  const panelLines = [
24607
- 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 }),
24608
25399
  line(`progress ${progress}`, { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink2 }),
24609
25400
  blank(),
24610
25401
  line("CHECKLIST", { fg: RIG_UI.ink3, bold: true }),
@@ -24616,11 +25407,11 @@ function renderInitScene(state, layout) {
24616
25407
  line("OTHER PATHS", { fg: RIG_UI.ink3, bold: true }),
24617
25408
  ...secondaryRows,
24618
25409
  blank(),
24619
- 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 })
24620
25411
  ];
24621
25412
  return makeSceneFrame({
24622
25413
  scene: "init",
24623
- title: "Setup",
25414
+ title: "Init",
24624
25415
  lines: [],
24625
25416
  panels: [{
24626
25417
  id: "init-setup",
@@ -24633,11 +25424,11 @@ function renderInitScene(state, layout) {
24633
25424
  opacity: 0.98,
24634
25425
  border: false,
24635
25426
  chrome: "ad-terminal",
24636
- headerText: "rig setup \xB7 guided onboarding",
25427
+ headerText: "rig init \xB7 guided onboarding",
24637
25428
  paddingX: 5,
24638
25429
  paddingY: 2
24639
25430
  }],
24640
- footer: { message: allDone ? "setup complete" : "setup in progress" },
25431
+ footer: { message: allDone ? "init complete" : "init in progress" },
24641
25432
  live: !allDone || state.status === "action"
24642
25433
  });
24643
25434
  }
@@ -25926,18 +26717,43 @@ function renderRunDetailScene(state, layout) {
25926
26717
  init_scene();
25927
26718
  init_selectable();
25928
26719
  init_theme2();
26720
+ init_remote_link();
25929
26721
  var SERVER_ACTIONS = [
25930
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" } },
25931
- { 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" } },
25932
- { 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" } },
25933
- { 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" } },
25934
- { 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" } },
25935
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" } },
25936
- { 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" } },
25937
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" } },
25938
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" } },
25939
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" } }
25940
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
+ }
25941
26757
  function cleanVisible(value, fallback = "unknown") {
25942
26758
  return value?.trim() || fallback;
25943
26759
  }
@@ -25962,31 +26778,37 @@ function selectedIndex(state, count) {
25962
26778
  function statusRows2(server) {
25963
26779
  const auth = server?.auth;
25964
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"}` : "";
25965
26784
  return [
25966
- line(`selected ${cleanVisible(server?.label, "not checked")}`, { fg: RIG_UI.ink2 }),
26785
+ line(`selected ${cleanVisible(server?.label, "not checked")}${selectedLinkSummary}`, { fg: RIG_UI.ink2 }),
25967
26786
  line(`target ${targetLabel(server)}`, { fg: RIG_UI.ink3 }),
25968
26787
  line(`mode ${server?.kind ?? "local or remote"}`, { fg: RIG_UI.ink3 }),
25969
26788
  line(`health ${server ? server.reachable ? "reachable" : "unreachable" : "check pending"}`, { fg: server?.reachable ? RIG_UI.limeDim : server ? RIG_UI.red : RIG_UI.yellow }),
25970
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 })] : [],
25971
26792
  ...server?.error ? [line(`attention ${server.error}`, { fg: RIG_UI.yellow })] : []
25972
26793
  ];
25973
26794
  }
25974
26795
  function renderServerScene(state, layout) {
25975
26796
  const server = serverState(state);
25976
- const selected = selectedIndex(state, 10);
26797
+ const actions = serverActions(state);
26798
+ const selected = selectedIndex(state, actions.length);
25977
26799
  const loading = state.status === "loading" && !server;
25978
26800
  const errorText = state.error?.message ?? lastRefreshError(state);
25979
26801
  const panelLines = [
25980
26802
  ...errorText ? errorBanner(errorText, "select refresh or doctor below to retry") : [],
25981
26803
  line("server controls", { fg: RIG_UI.ink3 }),
25982
26804
  blank(),
25983
- line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
25984
- ...SERVER_ACTIONS.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item)),
25985
- blank(),
25986
26805
  line("STATUS", { fg: RIG_UI.ink3, bold: true }),
25987
26806
  ...loading ? loadingRows("server status", state.tick) : statusRows2(server),
25988
26807
  blank(),
25989
- 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 })
25990
26812
  ];
25991
26813
  return makeSceneFrame({
25992
26814
  scene: "server",
@@ -26015,14 +26837,15 @@ function renderServerScene(state, layout) {
26015
26837
  // packages/cli/src/app-opentui/scenes/tasks.ts
26016
26838
  init_scene();
26017
26839
  init_selectable();
26840
+ init_remote_link();
26018
26841
  init_theme2();
26019
- var TASK_RECOVERY_ITEMS = [
26842
+ var BASE_TASK_RECOVERY_ITEMS = [
26020
26843
  { id: "refresh", label: "refresh", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Refresh tasks" } }, message: "refresh task source" },
26021
- { id: "next", label: "next", intent: { scene: "command", argv: ["task", "run", "--next"], action: { kind: "command-run", label: "Run next task" } }, message: "dispatch next runnable task" },
26022
- { 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" },
26023
26845
  { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
26024
26846
  { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose task source" }
26025
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" };
26026
26849
  var COL3 = {
26027
26850
  glyph: 1,
26028
26851
  id: 11,
@@ -26182,34 +27005,44 @@ function taskRow(width, task, active, optimistic) {
26182
27005
  const item = taskSelectableItem(task);
26183
27006
  return item ? withSelectable(row, item) : row;
26184
27007
  }
26185
- var RECOVERY_DECKS2 = [
27008
+ var BASE_RECOVERY_DECKS2 = [
26186
27009
  { label: "refresh", detail: "reload task source" },
26187
27010
  { label: "next", detail: "dispatch next runnable task" },
26188
- { label: "repair", detail: "repair project/task source link" },
26189
27011
  { label: "server", detail: "open server controls" },
26190
27012
  { label: "doctor", detail: "diagnose task source" }
26191
27013
  ];
26192
- 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);
26193
27026
  return [
26194
27027
  ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
26195
27028
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
26196
- ...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]))
26197
27030
  ];
26198
27031
  }
26199
- function emptyRows2(query, total, selected) {
27032
+ function emptyRows2(state, query, total, selected) {
26200
27033
  if (!query.trim() && total === 0)
26201
- return recoveryRows2(selected, "No tasks loaded.");
27034
+ return recoveryRows2(state, selected, "No tasks loaded.");
26202
27035
  if (query.trim() && total > 0) {
26203
27036
  return [
26204
- ...recoveryRows2(selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
27037
+ ...recoveryRows2(state, selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
26205
27038
  ];
26206
27039
  }
26207
- return recoveryRows2(selected, "No tasks are visible with this filter.");
27040
+ return recoveryRows2(state, selected, "No tasks are visible with this filter.");
26208
27041
  }
26209
27042
  function degradedRows2(state, selected) {
26210
27043
  if (!state.error)
26211
27044
  return [];
26212
- return recoveryRows2(selected, state.error.message);
27045
+ return recoveryRows2(state, selected, state.error.message);
26213
27046
  }
26214
27047
  function rawString(raw, key) {
26215
27048
  const value = raw?.[key];
@@ -26233,7 +27066,10 @@ function renderTaskDetail(state, taskId3, layout) {
26233
27066
  const activeRunId = task?.activeRun?.runId?.trim();
26234
27067
  const width = panelWidth13(layout);
26235
27068
  const selected = Math.max(0, state.selection.index);
27069
+ const errorText = state.error?.message ?? lastRefreshError(state);
27070
+ const offerRepairLink = shouldOfferRemoteLinkRepair(state, errorText);
26236
27071
  const headerLines = [
27072
+ ...errorText ? errorBanner(errorText, offerRepairLink ? "select repair link below to prepare the remote checkout" : "retry dispatch after resolving the issue") : [],
26237
27073
  line(`${glyph2} ${title}`, { fg: color, bold: true }),
26238
27074
  line(`task ${taskId3} \xB7 ${status}`, { fg: RIG_UI.ink3 }),
26239
27075
  ...task && task.labels.length > 0 ? [line(`labels: ${task.labels.join(", ")}`, { fg: RIG_UI.ink4 })] : [],
@@ -26245,8 +27081,16 @@ function renderTaskDetail(state, taskId3, layout) {
26245
27081
  const actionRows2 = [];
26246
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" }));
26247
27083
  index += 1;
26248
- 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}` }));
26249
- 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
+ }
26250
27094
  if (activeRunId) {
26251
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)}` }));
26252
27096
  index += 1;
@@ -26305,7 +27149,7 @@ function renderTasksScene(state, layout) {
26305
27149
  const tasks = sortTasks2(filterTasksForSearch(allTasks, query), spec);
26306
27150
  const selected = typeof state.data.selectedTaskId === "string" && tasks.some((task) => task.id === state.data.selectedTaskId) ? state.data.selectedTaskId : tasks[0]?.id;
26307
27151
  const selectedIndex2 = Math.max(0, tasks.findIndex((task) => task.id === selected));
26308
- 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));
26309
27153
  const dispatching = dispatchingFor(state);
26310
27154
  const dispatchLine = dispatching ? ` \xB7 ${state.data.dispatchingRun?.runId ?? "new"} ${dispatching.status}` : "";
26311
27155
  const updatedAgo = updatedAgoLabel2(state);
@@ -26325,7 +27169,7 @@ function renderTasksScene(state, layout) {
26325
27169
  ...tasks.map((task, index) => taskRow(contentWidth, task, index === selectedIndex2, dispatching && dispatching.taskId === task.id ? dispatching.status : undefined))
26326
27170
  ] : state.error ? degradedRows2(state, recoveryIndex) : loading ? loadingRows("tasks", state.tick) : [
26327
27171
  ...query.trim() ? [line(searchSummary("tasks", query, tasks.length, allTasks.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
26328
- ...emptyRows2(query, allTasks.length, recoveryIndex)
27172
+ ...emptyRows2(state, query, allTasks.length, recoveryIndex)
26329
27173
  ]
26330
27174
  ];
26331
27175
  return makeSceneFrame({
@@ -26686,6 +27530,29 @@ function createDomainAdapter() {
26686
27530
  else
26687
27531
  await loadInitFacts(ctx);
26688
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
+ }
26689
27556
  case "doctor-run":
26690
27557
  await runDoctorChecksForApp(ctx);
26691
27558
  return true;