@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.
- package/dist/bin/rig.js +1159 -292
- package/dist/src/app/board.js +462 -48
- package/dist/src/app-opentui/adapters/doctor.js +458 -46
- package/dist/src/app-opentui/adapters/family.js +670 -147
- package/dist/src/app-opentui/adapters/fleet.js +477 -46
- package/dist/src/app-opentui/adapters/inbox.js +477 -46
- package/dist/src/app-opentui/adapters/init.js +497 -74
- package/dist/src/app-opentui/adapters/inspect.js +477 -46
- package/dist/src/app-opentui/adapters/pi-attach.js +484 -53
- package/dist/src/app-opentui/adapters/run-detail.js +477 -46
- package/dist/src/app-opentui/adapters/server.d.ts +26 -0
- package/dist/src/app-opentui/adapters/server.js +676 -59
- package/dist/src/app-opentui/adapters/tasks.js +539 -81
- package/dist/src/app-opentui/autocomplete.js +4 -2
- package/dist/src/app-opentui/bootstrap.js +1155 -288
- package/dist/src/app-opentui/command-palette.js +37 -10
- package/dist/src/app-opentui/index.js +566 -89
- package/dist/src/app-opentui/intent.js +33 -8
- package/dist/src/app-opentui/keymap.js +43 -29
- package/dist/src/app-opentui/pi-host-child.js +465 -53
- package/dist/src/app-opentui/react/App.js +104 -44
- package/dist/src/app-opentui/react/ChromeHost.js +16 -4
- package/dist/src/app-opentui/react/launch.js +555 -88
- package/dist/src/app-opentui/react/nav.js +1 -1
- package/dist/src/app-opentui/registry.js +1062 -237
- package/dist/src/app-opentui/remote-link.d.ts +10 -0
- package/dist/src/app-opentui/remote-link.js +47 -0
- package/dist/src/app-opentui/runtime.js +566 -89
- package/dist/src/app-opentui/scenes/doctor.js +1 -1
- package/dist/src/app-opentui/scenes/error.js +50 -4
- package/dist/src/app-opentui/scenes/family.js +60 -6
- package/dist/src/app-opentui/scenes/fleet.js +65 -13
- package/dist/src/app-opentui/scenes/help.js +4 -2
- package/dist/src/app-opentui/scenes/init.js +12 -12
- package/dist/src/app-opentui/scenes/main.js +7 -7
- package/dist/src/app-opentui/scenes/server.js +83 -11
- package/dist/src/app-opentui/scenes/tasks.js +79 -16
- package/dist/src/app-opentui/state.js +25 -5
- package/dist/src/app-opentui/surface-catalog.js +4 -2
- package/dist/src/app-opentui/types.d.ts +1 -1
- package/dist/src/commands/_cli-format.d.ts +10 -1
- package/dist/src/commands/_cli-format.js +5 -2
- package/dist/src/commands/_connection-state.d.ts +11 -1
- package/dist/src/commands/_connection-state.js +50 -5
- package/dist/src/commands/_doctor-checks.js +458 -46
- package/dist/src/commands/_help-catalog.js +4 -2
- package/dist/src/commands/_json-output.js +4 -0
- package/dist/src/commands/_operator-view.js +465 -53
- package/dist/src/commands/_pi-frontend.js +465 -53
- package/dist/src/commands/_preflight.js +509 -72
- package/dist/src/commands/_server-client.d.ts +33 -0
- package/dist/src/commands/_server-client.js +477 -46
- package/dist/src/commands/_server-events.js +446 -41
- package/dist/src/commands/_snapshot-upload.js +460 -48
- package/dist/src/commands/connect.js +620 -15
- package/dist/src/commands/doctor.js +458 -46
- package/dist/src/commands/github.js +462 -50
- package/dist/src/commands/inbox.js +458 -46
- package/dist/src/commands/init.js +497 -74
- package/dist/src/commands/inspect.js +458 -46
- package/dist/src/commands/run.js +465 -53
- package/dist/src/commands/server.js +647 -163
- package/dist/src/commands/setup.js +463 -51
- package/dist/src/commands/stats.js +462 -48
- package/dist/src/commands/task-run-driver.js +464 -52
- package/dist/src/commands/task.js +520 -81
- package/dist/src/commands.js +670 -147
- package/dist/src/index.js +674 -147
- package/dist/src/launcher.js +4 -0
- package/package.json +8 -8
package/dist/src/commands.js
CHANGED
|
@@ -864,12 +864,28 @@ function readRepoConnection(projectRoot) {
|
|
|
864
864
|
selected,
|
|
865
865
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
866
866
|
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
867
|
-
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
867
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
|
|
868
|
+
serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
|
|
869
|
+
serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
|
|
868
870
|
};
|
|
869
871
|
}
|
|
870
872
|
function writeRepoConnection(projectRoot, state) {
|
|
871
873
|
writeJsonFile2(resolveRepoConnectionPath(projectRoot), state);
|
|
872
874
|
}
|
|
875
|
+
function rootAllowedForSelection(repo, connection) {
|
|
876
|
+
const root = repo.serverProjectRoot?.trim();
|
|
877
|
+
if (!root)
|
|
878
|
+
return;
|
|
879
|
+
if (connection.kind === "remote") {
|
|
880
|
+
if (repo.serverProjectRootAlias !== repo.selected)
|
|
881
|
+
return;
|
|
882
|
+
if (repo.serverProjectRootBaseUrl !== connection.baseUrl)
|
|
883
|
+
return;
|
|
884
|
+
} else if (repo.serverProjectRootAlias && repo.serverProjectRootAlias !== repo.selected) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
return root;
|
|
888
|
+
}
|
|
873
889
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
874
890
|
const repo = readRepoConnection(projectRoot);
|
|
875
891
|
if (!repo)
|
|
@@ -881,13 +897,41 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
881
897
|
if (!connection) {
|
|
882
898
|
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
883
899
|
}
|
|
884
|
-
return { alias: repo.selected, connection, serverProjectRoot: repo
|
|
900
|
+
return { alias: repo.selected, connection, serverProjectRoot: rootAllowedForSelection(repo, connection) };
|
|
901
|
+
}
|
|
902
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot, metadata = {}) {
|
|
903
|
+
const repo = readRepoConnection(projectRoot);
|
|
904
|
+
if (!repo)
|
|
905
|
+
return;
|
|
906
|
+
let inferred = metadata;
|
|
907
|
+
if (!inferred.alias || !inferred.baseUrl) {
|
|
908
|
+
try {
|
|
909
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
910
|
+
if (selected?.connection.kind === "remote") {
|
|
911
|
+
inferred = {
|
|
912
|
+
alias: inferred.alias ?? selected.alias,
|
|
913
|
+
baseUrl: inferred.baseUrl ?? selected.connection.baseUrl
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
} catch {}
|
|
917
|
+
}
|
|
918
|
+
writeRepoConnection(projectRoot, {
|
|
919
|
+
...repo,
|
|
920
|
+
...metadata.project ? { project: metadata.project } : {},
|
|
921
|
+
serverProjectRoot,
|
|
922
|
+
...inferred.alias ? { serverProjectRootAlias: inferred.alias } : {},
|
|
923
|
+
...inferred.baseUrl ? { serverProjectRootBaseUrl: inferred.baseUrl.replace(/\/+$/, "") } : {}
|
|
924
|
+
});
|
|
885
925
|
}
|
|
886
|
-
function
|
|
926
|
+
function clearRepoServerProjectRoot(projectRoot) {
|
|
887
927
|
const repo = readRepoConnection(projectRoot);
|
|
888
928
|
if (!repo)
|
|
889
929
|
return;
|
|
890
|
-
writeRepoConnection(projectRoot, {
|
|
930
|
+
writeRepoConnection(projectRoot, {
|
|
931
|
+
selected: repo.selected,
|
|
932
|
+
...repo.project ? { project: repo.project } : {},
|
|
933
|
+
...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
|
|
934
|
+
});
|
|
891
935
|
}
|
|
892
936
|
function isRemoteConnectionSelected(projectRoot) {
|
|
893
937
|
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
@@ -897,8 +941,8 @@ var init__connection_state = __esm(() => {
|
|
|
897
941
|
});
|
|
898
942
|
|
|
899
943
|
// packages/cli/src/commands/_server-client.ts
|
|
900
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
901
|
-
import { resolve as resolve10 } from "path";
|
|
944
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
945
|
+
import { dirname as dirname3, isAbsolute, resolve as resolve10 } from "path";
|
|
902
946
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
903
947
|
function setServerPhaseListener(listener) {
|
|
904
948
|
const previous = serverPhaseListener;
|
|
@@ -944,11 +988,10 @@ function readStoredGitHubAuthToken(projectRoot) {
|
|
|
944
988
|
const parsed = readRemoteAuthState(projectRoot);
|
|
945
989
|
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
946
990
|
}
|
|
947
|
-
function
|
|
948
|
-
|
|
949
|
-
const slug = repo?.project?.trim();
|
|
950
|
-
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
991
|
+
function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
|
|
992
|
+
if (!/^[^/]+\/[^/]+$/.test(repoSlug))
|
|
951
993
|
return null;
|
|
994
|
+
const repo = readRepoConnection(projectRoot);
|
|
952
995
|
const auth = readRemoteAuthState(projectRoot);
|
|
953
996
|
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
954
997
|
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
@@ -957,25 +1000,419 @@ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
|
957
1000
|
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
958
1001
|
if (!checkoutBaseDir)
|
|
959
1002
|
return null;
|
|
960
|
-
|
|
961
|
-
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
962
|
-
return inferred;
|
|
1003
|
+
return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
|
|
963
1004
|
}
|
|
964
1005
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
965
1006
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
966
1007
|
}
|
|
1008
|
+
function normalizeRepoSlug(value) {
|
|
1009
|
+
const slug = value?.trim();
|
|
1010
|
+
return slug && /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
|
|
1011
|
+
}
|
|
1012
|
+
function readProjectLinkSlug(projectRoot) {
|
|
1013
|
+
const path = resolve10(projectRoot, ".rig", "state", "project-link.json");
|
|
1014
|
+
if (!existsSync6(path))
|
|
1015
|
+
return null;
|
|
1016
|
+
try {
|
|
1017
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
1018
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
1019
|
+
return null;
|
|
1020
|
+
return normalizeRepoSlug(typeof parsed.repoSlug === "string" ? String(parsed.repoSlug) : null);
|
|
1021
|
+
} catch {
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
function writeProjectLinkConnection(projectRoot, repoSlug, alias) {
|
|
1026
|
+
const path = resolve10(projectRoot, ".rig", "state", "project-link.json");
|
|
1027
|
+
mkdirSync6(dirname3(path), { recursive: true });
|
|
1028
|
+
writeFileSync5(path, `${JSON.stringify({ repoSlug, connection: alias, linkedAt: new Date().toISOString() }, null, 2)}
|
|
1029
|
+
`, "utf8");
|
|
1030
|
+
}
|
|
1031
|
+
function remoteLinkRepairCommand(repoSlug) {
|
|
1032
|
+
return repoSlug ? `rig server repair-link --repo ${repoSlug}` : "rig server repair-link --repo owner/repo";
|
|
1033
|
+
}
|
|
1034
|
+
function formatRemoteProjectLinkHint(resolution) {
|
|
1035
|
+
const repair = remoteLinkRepairCommand(resolution.repoSlug);
|
|
1036
|
+
if (resolution.status === "auth_required") {
|
|
1037
|
+
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}` : ""}\`.`;
|
|
1038
|
+
}
|
|
1039
|
+
if (resolution.status === "missing_project") {
|
|
1040
|
+
return `Record the repo slug, then run \`${repair}\` (or \`rig init --repo owner/repo --server remote --github-auth device\`).`;
|
|
1041
|
+
}
|
|
1042
|
+
if (resolution.status === "not_remote")
|
|
1043
|
+
return "Select a remote server with `rig server use <alias>` before repairing a remote project link.";
|
|
1044
|
+
return `Run \`${repair}\` to backfill or prepare a server-host checkout for the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}.`;
|
|
1045
|
+
}
|
|
1046
|
+
function remoteProjectLinkFailure(input) {
|
|
1047
|
+
const partial = {
|
|
1048
|
+
status: input.status,
|
|
1049
|
+
alias: input.alias,
|
|
1050
|
+
baseUrl: input.baseUrl,
|
|
1051
|
+
repoSlug: input.repoSlug
|
|
1052
|
+
};
|
|
1053
|
+
return {
|
|
1054
|
+
ok: false,
|
|
1055
|
+
...input,
|
|
1056
|
+
hint: formatRemoteProjectLinkHint(partial),
|
|
1057
|
+
next: remoteLinkRepairCommand(input.repoSlug)
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
function remoteProjectLinkSuccess(input) {
|
|
1061
|
+
return {
|
|
1062
|
+
ok: true,
|
|
1063
|
+
status: input.status,
|
|
1064
|
+
alias: input.alias,
|
|
1065
|
+
baseUrl: input.baseUrl,
|
|
1066
|
+
repoSlug: input.repoSlug,
|
|
1067
|
+
serverProjectRoot: input.serverProjectRoot,
|
|
1068
|
+
source: input.source,
|
|
1069
|
+
prepared: input.prepared ?? input.status === "prepared",
|
|
1070
|
+
validated: input.validated ?? false,
|
|
1071
|
+
message: input.message ?? `Remote project link ready for ${input.repoSlug}.`,
|
|
1072
|
+
hint: "Remote-scoped task/run/status requests will send x-rig-project-root for this server-host checkout.",
|
|
1073
|
+
next: "rig task list"
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function localCheckoutPathCandidate(projectRoot, candidate) {
|
|
1077
|
+
try {
|
|
1078
|
+
const local = resolve10(projectRoot);
|
|
1079
|
+
const resolved = resolve10(candidate);
|
|
1080
|
+
return resolved === local || resolved.startsWith(`${local}/`);
|
|
1081
|
+
} catch {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
async function requestRemoteProjectLinkJson(baseUrl, pathname, authToken, init = {}) {
|
|
1086
|
+
const requestUrl = new URL(`${baseUrl}${pathname}`);
|
|
1087
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
1088
|
+
requestUrl.searchParams.set("rt", authToken);
|
|
1089
|
+
const response = await fetch(requestUrl, {
|
|
1090
|
+
...init,
|
|
1091
|
+
headers: mergeHeaders(init.headers, authToken)
|
|
1092
|
+
});
|
|
1093
|
+
const text = await response.text();
|
|
1094
|
+
const payload = text.trim().length > 0 ? (() => {
|
|
1095
|
+
try {
|
|
1096
|
+
return JSON.parse(text);
|
|
1097
|
+
} catch {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
})() : null;
|
|
1101
|
+
return { ok: response.ok, status: response.status, payload, text };
|
|
1102
|
+
}
|
|
1103
|
+
function payloadError(payload, fallback) {
|
|
1104
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
1105
|
+
const record = payload;
|
|
1106
|
+
const value = record.error ?? record.message ?? record.reason;
|
|
1107
|
+
if (typeof value === "string" && value.trim())
|
|
1108
|
+
return value.trim();
|
|
1109
|
+
}
|
|
1110
|
+
return fallback;
|
|
1111
|
+
}
|
|
1112
|
+
function checkoutPathsFromProjectPayload(payload) {
|
|
1113
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1114
|
+
return [];
|
|
1115
|
+
const project = payload.project;
|
|
1116
|
+
if (!project || typeof project !== "object" || Array.isArray(project))
|
|
1117
|
+
return [];
|
|
1118
|
+
const checkouts = project.checkouts;
|
|
1119
|
+
if (!Array.isArray(checkouts))
|
|
1120
|
+
return [];
|
|
1121
|
+
return [...checkouts].reverse().flatMap((entry) => {
|
|
1122
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
1123
|
+
return [];
|
|
1124
|
+
const path = entry.path;
|
|
1125
|
+
return typeof path === "string" && path.trim() ? [path.trim()] : [];
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
function checkoutPathFromPreparePayload(payload) {
|
|
1129
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1130
|
+
return null;
|
|
1131
|
+
const checkout = payload.checkout;
|
|
1132
|
+
if (!checkout || typeof checkout !== "object" || Array.isArray(checkout))
|
|
1133
|
+
return null;
|
|
1134
|
+
const path = checkout.path;
|
|
1135
|
+
return typeof path === "string" && path.trim() ? path.trim() : null;
|
|
1136
|
+
}
|
|
1137
|
+
async function validateAndPersistRemoteRoot(input) {
|
|
1138
|
+
const candidate = input.candidate.trim();
|
|
1139
|
+
if (!candidate || !isAbsolute(candidate)) {
|
|
1140
|
+
return remoteProjectLinkFailure({
|
|
1141
|
+
status: "invalid_root",
|
|
1142
|
+
alias: input.alias,
|
|
1143
|
+
baseUrl: input.baseUrl,
|
|
1144
|
+
repoSlug: input.repoSlug,
|
|
1145
|
+
message: `Remote project root candidate is not an absolute server-host path: ${candidate || "(empty)"}.`
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
if (localCheckoutPathCandidate(input.projectRoot, candidate)) {
|
|
1149
|
+
return remoteProjectLinkFailure({
|
|
1150
|
+
status: "invalid_root",
|
|
1151
|
+
alias: input.alias,
|
|
1152
|
+
baseUrl: input.baseUrl,
|
|
1153
|
+
repoSlug: input.repoSlug,
|
|
1154
|
+
message: `Refusing to use local checkout path ${candidate} as a remote server project root.`
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
let response;
|
|
1158
|
+
try {
|
|
1159
|
+
response = await requestRemoteProjectLinkJson(input.baseUrl, "/api/server/project-root", input.authToken, {
|
|
1160
|
+
method: "POST",
|
|
1161
|
+
headers: { "content-type": "application/json" },
|
|
1162
|
+
body: JSON.stringify({ projectRoot: candidate })
|
|
1163
|
+
});
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
return remoteProjectLinkFailure({
|
|
1166
|
+
status: "error",
|
|
1167
|
+
alias: input.alias,
|
|
1168
|
+
baseUrl: input.baseUrl,
|
|
1169
|
+
repoSlug: input.repoSlug,
|
|
1170
|
+
message: `Could not validate remote project root ${candidate}: ${error instanceof Error ? error.message : String(error)}`
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
if (response.status === 401 || response.status === 403) {
|
|
1174
|
+
return remoteProjectLinkFailure({
|
|
1175
|
+
status: "auth_required",
|
|
1176
|
+
alias: input.alias,
|
|
1177
|
+
baseUrl: input.baseUrl,
|
|
1178
|
+
repoSlug: input.repoSlug,
|
|
1179
|
+
statusCode: response.status,
|
|
1180
|
+
message: `Remote server ${input.alias} requires authentication before validating project root ${candidate}.`
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
if (!response.ok) {
|
|
1184
|
+
return remoteProjectLinkFailure({
|
|
1185
|
+
status: "invalid_root",
|
|
1186
|
+
alias: input.alias,
|
|
1187
|
+
baseUrl: input.baseUrl,
|
|
1188
|
+
repoSlug: input.repoSlug,
|
|
1189
|
+
statusCode: response.status,
|
|
1190
|
+
message: `Remote server rejected project root ${candidate} (${response.status}): ${payloadError(response.payload, response.text || "project-root validation failed")}`
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
const record = response.payload && typeof response.payload === "object" && !Array.isArray(response.payload) ? response.payload : {};
|
|
1194
|
+
if (record.ok !== true) {
|
|
1195
|
+
return remoteProjectLinkFailure({
|
|
1196
|
+
status: "invalid_root",
|
|
1197
|
+
alias: input.alias,
|
|
1198
|
+
baseUrl: input.baseUrl,
|
|
1199
|
+
repoSlug: input.repoSlug,
|
|
1200
|
+
message: `Remote server did not accept project root ${candidate}: ${payloadError(response.payload, "project-root validation failed")}`
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
const accepted = typeof record.projectRoot === "string" && record.projectRoot.trim() ? record.projectRoot.trim() : typeof record.requestedProjectRoot === "string" && record.requestedProjectRoot.trim() ? record.requestedProjectRoot.trim() : candidate;
|
|
1204
|
+
writeRepoServerProjectRoot(input.projectRoot, accepted, { alias: input.alias, baseUrl: input.baseUrl, project: input.repoSlug });
|
|
1205
|
+
writeProjectLinkConnection(input.projectRoot, input.repoSlug, input.alias);
|
|
1206
|
+
const status = input.status ?? (input.prepared ? "prepared" : "backfilled");
|
|
1207
|
+
return remoteProjectLinkSuccess({
|
|
1208
|
+
status,
|
|
1209
|
+
alias: input.alias,
|
|
1210
|
+
baseUrl: input.baseUrl,
|
|
1211
|
+
repoSlug: input.repoSlug,
|
|
1212
|
+
serverProjectRoot: accepted,
|
|
1213
|
+
source: input.source,
|
|
1214
|
+
prepared: input.prepared,
|
|
1215
|
+
validated: true,
|
|
1216
|
+
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}.`
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
async function ensureRemoteProjectRootLink(projectRoot, options = {}) {
|
|
1220
|
+
let selected;
|
|
1221
|
+
try {
|
|
1222
|
+
selected = resolveSelectedConnection(projectRoot);
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
return remoteProjectLinkFailure({ status: "error", message: error instanceof Error ? error.message : String(error) });
|
|
1225
|
+
}
|
|
1226
|
+
if (!selected || selected.connection.kind !== "remote") {
|
|
1227
|
+
return remoteProjectLinkFailure({ status: "not_remote", message: "No remote Rig server is selected for this repo." });
|
|
1228
|
+
}
|
|
1229
|
+
const repo = readRepoConnection(projectRoot);
|
|
1230
|
+
const explicitRepoRequested = options.repoSlug !== undefined;
|
|
1231
|
+
const explicitRepoSlug = explicitRepoRequested ? normalizeRepoSlug(options.repoSlug) : null;
|
|
1232
|
+
const repoProjectSlug = normalizeRepoSlug(repo?.project);
|
|
1233
|
+
const repoSlug = explicitRepoSlug ?? repoProjectSlug ?? readProjectLinkSlug(projectRoot);
|
|
1234
|
+
const alias = selected.alias;
|
|
1235
|
+
const baseUrl = selected.connection.baseUrl;
|
|
1236
|
+
const authToken = options.authToken === undefined ? readGitHubBearerTokenForRemote(projectRoot) : options.authToken;
|
|
1237
|
+
const mode = options.mode ?? "backfill-only";
|
|
1238
|
+
if (explicitRepoRequested && !explicitRepoSlug) {
|
|
1239
|
+
return remoteProjectLinkFailure({
|
|
1240
|
+
status: "missing_project",
|
|
1241
|
+
alias,
|
|
1242
|
+
baseUrl,
|
|
1243
|
+
message: `Invalid --repo value ${JSON.stringify(options.repoSlug)}. Expected owner/repo.`
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
if (!repoSlug) {
|
|
1247
|
+
return remoteProjectLinkFailure({
|
|
1248
|
+
status: "missing_project",
|
|
1249
|
+
alias,
|
|
1250
|
+
baseUrl,
|
|
1251
|
+
message: `Remote server ${alias} is selected, but this checkout has no GitHub repo slug recorded.`
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
const skippedCandidates = [];
|
|
1255
|
+
const storedRoot = selected.serverProjectRoot?.trim() || null;
|
|
1256
|
+
const storedRootMatchesResolvedRepo = repoProjectSlug === repoSlug;
|
|
1257
|
+
if (storedRoot && storedRootMatchesResolvedRepo) {
|
|
1258
|
+
if (localCheckoutPathCandidate(projectRoot, storedRoot)) {
|
|
1259
|
+
clearRepoServerProjectRoot(projectRoot);
|
|
1260
|
+
skippedCandidates.push(storedRoot);
|
|
1261
|
+
} else {
|
|
1262
|
+
const storedResult = await validateAndPersistRemoteRoot({
|
|
1263
|
+
projectRoot,
|
|
1264
|
+
alias,
|
|
1265
|
+
baseUrl,
|
|
1266
|
+
authToken,
|
|
1267
|
+
repoSlug,
|
|
1268
|
+
candidate: storedRoot,
|
|
1269
|
+
source: "stored",
|
|
1270
|
+
status: "ready"
|
|
1271
|
+
});
|
|
1272
|
+
if (storedResult.ok || storedResult.status === "auth_required" || storedResult.status === "error")
|
|
1273
|
+
return storedResult;
|
|
1274
|
+
clearRepoServerProjectRoot(projectRoot);
|
|
1275
|
+
skippedCandidates.push(storedRoot);
|
|
1276
|
+
}
|
|
1277
|
+
} else if (storedRoot) {
|
|
1278
|
+
skippedCandidates.push(storedRoot);
|
|
1279
|
+
}
|
|
1280
|
+
const authCandidate = inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug);
|
|
1281
|
+
if (authCandidate) {
|
|
1282
|
+
const authResult = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: authCandidate, source: "auth-state" });
|
|
1283
|
+
if (authResult.ok || authResult.status === "auth_required")
|
|
1284
|
+
return authResult;
|
|
1285
|
+
}
|
|
1286
|
+
let registryResponse;
|
|
1287
|
+
try {
|
|
1288
|
+
registryResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}`, authToken);
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
return remoteProjectLinkFailure({
|
|
1291
|
+
status: "error",
|
|
1292
|
+
alias,
|
|
1293
|
+
baseUrl,
|
|
1294
|
+
repoSlug,
|
|
1295
|
+
message: `Could not read remote project registry for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
if (registryResponse.status === 401 || registryResponse.status === 403) {
|
|
1299
|
+
return remoteProjectLinkFailure({
|
|
1300
|
+
status: "auth_required",
|
|
1301
|
+
alias,
|
|
1302
|
+
baseUrl,
|
|
1303
|
+
repoSlug,
|
|
1304
|
+
statusCode: registryResponse.status,
|
|
1305
|
+
message: `Remote server ${alias} requires authentication before reading the ${repoSlug} project registry.`
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
if (registryResponse.ok) {
|
|
1309
|
+
const candidates = checkoutPathsFromProjectPayload(registryResponse.payload);
|
|
1310
|
+
for (const candidate of candidates) {
|
|
1311
|
+
const result = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate, source: "registry" });
|
|
1312
|
+
if (result.ok || result.status === "auth_required")
|
|
1313
|
+
return result;
|
|
1314
|
+
skippedCandidates.push(candidate);
|
|
1315
|
+
}
|
|
1316
|
+
if (mode === "backfill-only") {
|
|
1317
|
+
return remoteProjectLinkFailure({
|
|
1318
|
+
status: candidates.length > 0 ? "invalid_root" : "no_server_checkout",
|
|
1319
|
+
alias,
|
|
1320
|
+
baseUrl,
|
|
1321
|
+
repoSlug,
|
|
1322
|
+
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.`,
|
|
1323
|
+
skippedCandidates
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
} else if (registryResponse.status === 404) {
|
|
1327
|
+
if (mode === "backfill-only") {
|
|
1328
|
+
return remoteProjectLinkFailure({
|
|
1329
|
+
status: "project_not_registered",
|
|
1330
|
+
alias,
|
|
1331
|
+
baseUrl,
|
|
1332
|
+
repoSlug,
|
|
1333
|
+
statusCode: registryResponse.status,
|
|
1334
|
+
message: `Remote server ${alias} has no registered project record for ${repoSlug}.`
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
} else {
|
|
1338
|
+
return remoteProjectLinkFailure({
|
|
1339
|
+
status: "error",
|
|
1340
|
+
alias,
|
|
1341
|
+
baseUrl,
|
|
1342
|
+
repoSlug,
|
|
1343
|
+
statusCode: registryResponse.status,
|
|
1344
|
+
message: `Could not backfill ${repoSlug} from remote registry (${registryResponse.status}): ${payloadError(registryResponse.payload, registryResponse.text || "registry lookup failed")}`,
|
|
1345
|
+
skippedCandidates
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
let prepareResponse;
|
|
1349
|
+
try {
|
|
1350
|
+
prepareResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}/prepare-checkout`, authToken, {
|
|
1351
|
+
method: "POST",
|
|
1352
|
+
headers: { "content-type": "application/json" },
|
|
1353
|
+
body: JSON.stringify({ checkout: { kind: "managed-clone" }, repoUrl: `https://github.com/${repoSlug}.git` })
|
|
1354
|
+
});
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
return remoteProjectLinkFailure({
|
|
1357
|
+
status: "error",
|
|
1358
|
+
alias,
|
|
1359
|
+
baseUrl,
|
|
1360
|
+
repoSlug,
|
|
1361
|
+
message: `Could not prepare remote checkout for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
if (prepareResponse.status === 401 || prepareResponse.status === 403) {
|
|
1365
|
+
return remoteProjectLinkFailure({
|
|
1366
|
+
status: "auth_required",
|
|
1367
|
+
alias,
|
|
1368
|
+
baseUrl,
|
|
1369
|
+
repoSlug,
|
|
1370
|
+
statusCode: prepareResponse.status,
|
|
1371
|
+
message: `Remote server ${alias} requires authentication before preparing ${repoSlug}.`
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
if (!prepareResponse.ok) {
|
|
1375
|
+
return remoteProjectLinkFailure({
|
|
1376
|
+
status: "error",
|
|
1377
|
+
alias,
|
|
1378
|
+
baseUrl,
|
|
1379
|
+
repoSlug,
|
|
1380
|
+
statusCode: prepareResponse.status,
|
|
1381
|
+
message: `Remote checkout prepare failed for ${repoSlug} (${prepareResponse.status}): ${payloadError(prepareResponse.payload, prepareResponse.text || "prepare-checkout failed")}`,
|
|
1382
|
+
skippedCandidates
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
const preparedPath = checkoutPathFromPreparePayload(prepareResponse.payload);
|
|
1386
|
+
if (!preparedPath) {
|
|
1387
|
+
return remoteProjectLinkFailure({
|
|
1388
|
+
status: "invalid_root",
|
|
1389
|
+
alias,
|
|
1390
|
+
baseUrl,
|
|
1391
|
+
repoSlug,
|
|
1392
|
+
message: `Remote checkout prepare for ${repoSlug} did not return a checkout.path.`,
|
|
1393
|
+
skippedCandidates
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
return validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: preparedPath, source: "prepare", prepared: true });
|
|
1397
|
+
}
|
|
1398
|
+
async function repairRemoteProjectRootLink(context, options = {}) {
|
|
1399
|
+
const resolution = await ensureRemoteProjectRootLink(context.projectRoot, { mode: options.mode ?? "prepare-if-missing", repoSlug: options.repoSlug });
|
|
1400
|
+
if (!resolution.ok)
|
|
1401
|
+
throw new CliError(resolution.message, 1, { hint: resolution.hint });
|
|
1402
|
+
return resolution;
|
|
1403
|
+
}
|
|
967
1404
|
async function ensureServerForCli(projectRoot) {
|
|
968
1405
|
try {
|
|
969
1406
|
const selected = resolveSelectedConnection(projectRoot);
|
|
970
1407
|
if (selected?.connection.kind === "remote") {
|
|
971
1408
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
972
1409
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
973
|
-
const
|
|
1410
|
+
const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
|
|
974
1411
|
return {
|
|
975
1412
|
baseUrl: selected.connection.baseUrl,
|
|
976
1413
|
authToken,
|
|
977
1414
|
connectionKind: "remote",
|
|
978
|
-
serverProjectRoot
|
|
1415
|
+
serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
|
|
979
1416
|
};
|
|
980
1417
|
}
|
|
981
1418
|
reportServerPhase("Starting local Rig server\u2026");
|
|
@@ -993,32 +1430,6 @@ async function ensureServerForCli(projectRoot) {
|
|
|
993
1430
|
throw error;
|
|
994
1431
|
}
|
|
995
1432
|
}
|
|
996
|
-
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
997
|
-
const repo = readRepoConnection(projectRoot);
|
|
998
|
-
const slug = repo?.project?.trim();
|
|
999
|
-
if (!slug)
|
|
1000
|
-
return null;
|
|
1001
|
-
try {
|
|
1002
|
-
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
1003
|
-
if (authToken && queryAuthFallbackEnabled())
|
|
1004
|
-
url.searchParams.set("rt", authToken);
|
|
1005
|
-
const response = await fetch(url, {
|
|
1006
|
-
headers: mergeHeaders(undefined, authToken)
|
|
1007
|
-
});
|
|
1008
|
-
if (!response.ok)
|
|
1009
|
-
return null;
|
|
1010
|
-
const payload = await response.json();
|
|
1011
|
-
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
1012
|
-
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
1013
|
-
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
1014
|
-
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
1015
|
-
if (path)
|
|
1016
|
-
writeRepoServerProjectRoot(projectRoot, path);
|
|
1017
|
-
return path;
|
|
1018
|
-
} catch {
|
|
1019
|
-
return null;
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
1433
|
function appendTaskFilterParams(url, filters) {
|
|
1023
1434
|
if (filters.assignee)
|
|
1024
1435
|
url.searchParams.set("assignee", filters.assignee);
|
|
@@ -1119,13 +1530,20 @@ function canUseRemoteWithoutProjectRoot(pathname) {
|
|
|
1119
1530
|
async function requestServerJson(context, pathname, init = {}) {
|
|
1120
1531
|
const server = await ensureServerForCli(context.projectRoot);
|
|
1121
1532
|
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1533
|
+
let scopedServerProjectRoot = server.serverProjectRoot;
|
|
1534
|
+
if (server.connectionKind === "remote" && !scopedServerProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
1535
|
+
const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: server.authToken });
|
|
1536
|
+
if (link.ok && link.serverProjectRoot) {
|
|
1537
|
+
scopedServerProjectRoot = link.serverProjectRoot;
|
|
1538
|
+
} else {
|
|
1539
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
1540
|
+
const target = link.alias && link.baseUrl ? `${link.alias} (${link.baseUrl})` : server.baseUrl;
|
|
1541
|
+
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 });
|
|
1542
|
+
}
|
|
1125
1543
|
}
|
|
1126
1544
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
1127
|
-
if (
|
|
1128
|
-
headers.set("x-rig-project-root",
|
|
1545
|
+
if (scopedServerProjectRoot)
|
|
1546
|
+
headers.set("x-rig-project-root", scopedServerProjectRoot);
|
|
1129
1547
|
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
1130
1548
|
requestUrl.searchParams.set("rt", server.authToken);
|
|
1131
1549
|
}
|
|
@@ -1491,6 +1909,14 @@ function permissionAllowsPr(payload) {
|
|
|
1491
1909
|
function isNotFoundError(error) {
|
|
1492
1910
|
return /\b(404|not found)\b/i.test(message(error));
|
|
1493
1911
|
}
|
|
1912
|
+
function isLoopbackBaseUrl(baseUrl) {
|
|
1913
|
+
try {
|
|
1914
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
1915
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
1916
|
+
} catch {
|
|
1917
|
+
return false;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1494
1920
|
function projectCheckoutReady(payload) {
|
|
1495
1921
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1496
1922
|
return null;
|
|
@@ -1545,25 +1971,37 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
1545
1971
|
}
|
|
1546
1972
|
const repo = readRepoConnection(context.projectRoot);
|
|
1547
1973
|
const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
|
|
1548
|
-
|
|
1974
|
+
let remoteRootBlocked = false;
|
|
1975
|
+
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>`."));
|
|
1976
|
+
if (selectedServer?.connectionKind === "remote" && repo?.project && !selectedServer.serverProjectRoot) {
|
|
1977
|
+
const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: selectedServer.authToken });
|
|
1978
|
+
const loopbackDevRemote = !selectedServer.authToken && isLoopbackBaseUrl(selectedServer.baseUrl);
|
|
1979
|
+
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));
|
|
1980
|
+
remoteRootBlocked = !link.ok && !loopbackDevRemote;
|
|
1981
|
+
}
|
|
1549
1982
|
try {
|
|
1550
1983
|
const auth = await request("/api/github/auth/status");
|
|
1551
1984
|
checks.push(isAuthenticated(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>`."));
|
|
1552
1985
|
} catch (error) {
|
|
1553
1986
|
checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
1554
1987
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1988
|
+
if (!remoteRootBlocked) {
|
|
1989
|
+
try {
|
|
1990
|
+
const projection = await request("/api/workspace/task-projection");
|
|
1991
|
+
checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
|
|
1994
|
+
}
|
|
1995
|
+
try {
|
|
1996
|
+
const permissions = await request("/api/github/repo/permissions");
|
|
1997
|
+
const allowed = permissionAllowsPr(permissions);
|
|
1998
|
+
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."));
|
|
1999
|
+
} catch (error) {
|
|
2000
|
+
checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
|
|
2001
|
+
}
|
|
2002
|
+
} else {
|
|
2003
|
+
checks.push(preflightCheck("task-projection", "task projection ready", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
|
|
2004
|
+
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."));
|
|
1567
2005
|
}
|
|
1568
2006
|
if (repo?.project) {
|
|
1569
2007
|
try {
|
|
@@ -1575,19 +2013,24 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
1575
2013
|
}
|
|
1576
2014
|
}
|
|
1577
2015
|
if (taskId) {
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
2016
|
+
if (!remoteRootBlocked) {
|
|
2017
|
+
try {
|
|
2018
|
+
const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
|
|
2019
|
+
const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
|
|
2020
|
+
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."));
|
|
2021
|
+
} catch (error) {
|
|
2022
|
+
checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
|
|
2023
|
+
}
|
|
2024
|
+
try {
|
|
2025
|
+
const runs = await request("/api/runs?limit=200");
|
|
2026
|
+
const duplicate = activeDuplicateRun(runs, taskId);
|
|
2027
|
+
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));
|
|
2028
|
+
} catch (error) {
|
|
2029
|
+
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."));
|
|
2030
|
+
}
|
|
2031
|
+
} else {
|
|
2032
|
+
checks.push(preflightCheck("issue", "task/issue accessible", "fail", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
|
|
2033
|
+
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."));
|
|
1591
2034
|
}
|
|
1592
2035
|
}
|
|
1593
2036
|
if ((options.runtimeAdapter ?? "pi") === "pi") {
|
|
@@ -2071,16 +2514,19 @@ function formatConnectionList(connections) {
|
|
|
2071
2514
|
return [formatSection("Rig servers", `${rows.length} available`), `${pc3.bold(pad("ALIAS", aliasWidth))} ${pc3.bold("KIND")} ${pc3.bold("TARGET")}`, ...lines, "", ...formatNextSteps(["Select one: `rig server use <alias|local>`"])].join(`
|
|
2072
2515
|
`);
|
|
2073
2516
|
}
|
|
2074
|
-
function formatConnectionStatus(selected, connections) {
|
|
2517
|
+
function formatConnectionStatus(selected, connections, repo, remoteProjectLink) {
|
|
2075
2518
|
const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
|
|
2076
2519
|
const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
|
|
2520
|
+
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";
|
|
2077
2521
|
return [
|
|
2078
2522
|
formatSection("Rig server", "selected for this repo"),
|
|
2079
2523
|
`${themeFaint("\u2502")} ${themeDim("selected ")} ${pc3.bold(selected)}`,
|
|
2080
2524
|
`${themeFaint("\u2502")} ${themeDim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
|
|
2081
2525
|
`${themeFaint("\u2502")} ${themeDim("target ")} ${target ?? "not configured"}`,
|
|
2526
|
+
...repo?.project ? [`${themeFaint("\u2502")} ${themeDim("project ")} ${repo.project}`] : [],
|
|
2527
|
+
...connection?.kind === "remote" ? [`${themeFaint("\u2502")} ${themeDim("root ")} ${rootLabel}`] : [],
|
|
2082
2528
|
"",
|
|
2083
|
-
...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
|
|
2529
|
+
...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`"])
|
|
2084
2530
|
].join(`
|
|
2085
2531
|
`);
|
|
2086
2532
|
}
|
|
@@ -2579,7 +3025,7 @@ var init__operator_surface = __esm(() => {
|
|
|
2579
3025
|
});
|
|
2580
3026
|
|
|
2581
3027
|
// packages/cli/src/commands/_pi-frontend.ts
|
|
2582
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
3028
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
2583
3029
|
import { homedir as homedir6, tmpdir } from "os";
|
|
2584
3030
|
import { join as join3 } from "path";
|
|
2585
3031
|
import { main as runPiMain } from "@earendil-works/pi-coding-agent";
|
|
@@ -2631,7 +3077,7 @@ function statusFromRunDetails(run) {
|
|
|
2631
3077
|
async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
2632
3078
|
const server = await ensureServerForCli(context.projectRoot);
|
|
2633
3079
|
const localCwd = join3(homedir6(), ".rig", "drones", runId.slice(0, 8));
|
|
2634
|
-
|
|
3080
|
+
mkdirSync10(localCwd, { recursive: true });
|
|
2635
3081
|
trustDroneCwd(localCwd);
|
|
2636
3082
|
installRigPiTheme();
|
|
2637
3083
|
let sessionFileArg = [];
|
|
@@ -2653,7 +3099,7 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
|
2653
3099
|
return line;
|
|
2654
3100
|
}).join(`
|
|
2655
3101
|
`);
|
|
2656
|
-
|
|
3102
|
+
writeFileSync8(localSessionPath, content);
|
|
2657
3103
|
sessionFileArg = ["--session", localSessionPath];
|
|
2658
3104
|
}
|
|
2659
3105
|
} catch {}
|
|
@@ -2669,12 +3115,12 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
|
2669
3115
|
function trustDroneCwd(localCwd) {
|
|
2670
3116
|
try {
|
|
2671
3117
|
const agentDir = join3(homedir6(), ".pi", "agent");
|
|
2672
|
-
|
|
3118
|
+
mkdirSync10(agentDir, { recursive: true });
|
|
2673
3119
|
const trustPath = join3(agentDir, "trust.json");
|
|
2674
3120
|
const store = existsSync14(trustPath) ? JSON.parse(readFileSync10(trustPath, "utf8")) : {};
|
|
2675
3121
|
if (store[localCwd] !== true) {
|
|
2676
3122
|
store[localCwd] = true;
|
|
2677
|
-
|
|
3123
|
+
writeFileSync8(trustPath, `${JSON.stringify(store, null, "\t")}
|
|
2678
3124
|
`);
|
|
2679
3125
|
}
|
|
2680
3126
|
} catch {}
|
|
@@ -2682,12 +3128,12 @@ function trustDroneCwd(localCwd) {
|
|
|
2682
3128
|
function installRigPiTheme() {
|
|
2683
3129
|
try {
|
|
2684
3130
|
const themesDir = join3(homedir6(), ".pi", "agent", "themes");
|
|
2685
|
-
|
|
3131
|
+
mkdirSync10(themesDir, { recursive: true });
|
|
2686
3132
|
const themePath = join3(themesDir, "rig.json");
|
|
2687
3133
|
const next = `${JSON.stringify(RIG_PI_THEME, null, "\t")}
|
|
2688
3134
|
`;
|
|
2689
3135
|
if (!existsSync14(themePath) || readFileSync10(themePath, "utf8") !== next) {
|
|
2690
|
-
|
|
3136
|
+
writeFileSync8(themePath, next);
|
|
2691
3137
|
}
|
|
2692
3138
|
} catch {}
|
|
2693
3139
|
}
|
|
@@ -3115,12 +3561,13 @@ var init__help_catalog = __esm(() => {
|
|
|
3115
3561
|
{
|
|
3116
3562
|
name: "server",
|
|
3117
3563
|
summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
|
|
3118
|
-
usage: ["rig server <status|list|add|use|start> [options]"],
|
|
3564
|
+
usage: ["rig server <status|list|add|use|repair-link|start> [options]"],
|
|
3119
3565
|
commands: [
|
|
3120
|
-
{ command: "status", description: "Show the selected server for this repo.", primary: true },
|
|
3566
|
+
{ command: "status", description: "Show the selected server and remote project-root link for this repo.", primary: true },
|
|
3121
3567
|
{ command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
|
|
3122
3568
|
{ command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
|
|
3123
3569
|
{ command: "use <alias>", description: "Select a saved remote server alias.", primary: true },
|
|
3570
|
+
{ command: "repair-link [--prepare|--backfill-only] [--repo owner/repo]", description: "Backfill or prepare the selected remote's server-host checkout link.", primary: true },
|
|
3124
3571
|
{ command: "list", description: "List saved local/remote server aliases.", primary: true },
|
|
3125
3572
|
{ command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
|
|
3126
3573
|
],
|
|
@@ -3128,6 +3575,7 @@ var init__help_catalog = __esm(() => {
|
|
|
3128
3575
|
"rig server status",
|
|
3129
3576
|
"rig server add prod https://where.rig-does.work",
|
|
3130
3577
|
"rig server use prod",
|
|
3578
|
+
"rig server repair-link --repo owner/repo",
|
|
3131
3579
|
"rig server use local",
|
|
3132
3580
|
"rig server start --port 3773"
|
|
3133
3581
|
],
|
|
@@ -7456,7 +7904,7 @@ import {
|
|
|
7456
7904
|
chmodSync,
|
|
7457
7905
|
copyFileSync as copyFileSync2,
|
|
7458
7906
|
existsSync as existsSync8,
|
|
7459
|
-
mkdirSync as
|
|
7907
|
+
mkdirSync as mkdirSync7,
|
|
7460
7908
|
readdirSync,
|
|
7461
7909
|
readlinkSync,
|
|
7462
7910
|
rmSync as rmSync3,
|
|
@@ -7573,7 +8021,7 @@ async function executeDist(context, args) {
|
|
|
7573
8021
|
requireNoExtraArgs(pending, "rig dist install [--scope user|system] [--path <dir>]");
|
|
7574
8022
|
const scope = parseInstallScope(scopeResult.value);
|
|
7575
8023
|
const installDir = resolveInstallDir(scope, pathResult.value);
|
|
7576
|
-
|
|
8024
|
+
mkdirSync7(installDir, { recursive: true });
|
|
7577
8025
|
let source = await findLatestDistBinary(context.projectRoot);
|
|
7578
8026
|
let buildDir = null;
|
|
7579
8027
|
if (!source) {
|
|
@@ -7628,7 +8076,7 @@ async function executeDist(context, args) {
|
|
|
7628
8076
|
const fp = await computeRuntimeImageFingerprint(context.projectRoot);
|
|
7629
8077
|
const currentId = computeRuntimeImageId(fp);
|
|
7630
8078
|
const imagesDir = resolve13(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
|
|
7631
|
-
|
|
8079
|
+
mkdirSync7(imagesDir, { recursive: true });
|
|
7632
8080
|
let pruned = 0;
|
|
7633
8081
|
for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
|
|
7634
8082
|
if (entry.isDirectory() && entry.name !== currentId) {
|
|
@@ -7640,9 +8088,9 @@ async function executeDist(context, args) {
|
|
|
7640
8088
|
console.log(`Pruned ${pruned} stale image(s).`);
|
|
7641
8089
|
}
|
|
7642
8090
|
const imageDir = resolve13(imagesDir, currentId);
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
8091
|
+
mkdirSync7(resolve13(imageDir, "bin/hooks"), { recursive: true });
|
|
8092
|
+
mkdirSync7(resolve13(imageDir, "bin/plugins"), { recursive: true });
|
|
8093
|
+
mkdirSync7(resolve13(imageDir, "bin/validators"), { recursive: true });
|
|
7646
8094
|
const hookNames = [
|
|
7647
8095
|
"scope-guard",
|
|
7648
8096
|
"import-guard",
|
|
@@ -7667,8 +8115,8 @@ async function executeDist(context, args) {
|
|
|
7667
8115
|
const binPluginsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
|
|
7668
8116
|
const validatorsRoot = resolve13(hostProjectRoot, "packages/runtime/src/control-plane/validators");
|
|
7669
8117
|
const binValidatorsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
|
|
7670
|
-
|
|
7671
|
-
|
|
8118
|
+
mkdirSync7(binPluginsDir, { recursive: true });
|
|
8119
|
+
mkdirSync7(binValidatorsDir, { recursive: true });
|
|
7672
8120
|
if (existsSync8(pluginsDir)) {
|
|
7673
8121
|
for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
|
|
7674
8122
|
const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
|
|
@@ -7775,7 +8223,7 @@ init_inbox();
|
|
|
7775
8223
|
init_runner();
|
|
7776
8224
|
init__connection_state();
|
|
7777
8225
|
init__server_client();
|
|
7778
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as
|
|
8226
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
7779
8227
|
import { spawnSync } from "child_process";
|
|
7780
8228
|
import { basename as basename2, resolve as resolve17 } from "path";
|
|
7781
8229
|
import { buildRigInitConfigSource } from "@rig/core";
|
|
@@ -7961,7 +8409,7 @@ async function buildPiSetupChecks(input = {}) {
|
|
|
7961
8409
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
7962
8410
|
init__server_client();
|
|
7963
8411
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
7964
|
-
import { dirname as
|
|
8412
|
+
import { dirname as dirname4, resolve as resolve15, relative, sep } from "path";
|
|
7965
8413
|
var SNAPSHOT_ARCHIVE_VERSION = 1;
|
|
7966
8414
|
var SNAPSHOT_ARCHIVE_CONTENT_TYPE = "application/vnd.rig.snapshot+json";
|
|
7967
8415
|
var DEFAULT_EXCLUDED_DIRECTORIES = new Set([
|
|
@@ -8310,14 +8758,14 @@ function parseRepoSlug(value) {
|
|
|
8310
8758
|
}
|
|
8311
8759
|
function ensureRigPrivateDirs(projectRoot) {
|
|
8312
8760
|
const rigDir = resolve17(projectRoot, ".rig");
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8761
|
+
mkdirSync8(resolve17(rigDir, "state"), { recursive: true });
|
|
8762
|
+
mkdirSync8(resolve17(rigDir, "logs"), { recursive: true });
|
|
8763
|
+
mkdirSync8(resolve17(rigDir, "runs"), { recursive: true });
|
|
8764
|
+
mkdirSync8(resolve17(rigDir, "tmp"), { recursive: true });
|
|
8765
|
+
mkdirSync8(resolve17(projectRoot, "artifacts"), { recursive: true });
|
|
8318
8766
|
const taskConfigPath = resolve17(rigDir, "task-config.json");
|
|
8319
8767
|
if (!existsSync11(taskConfigPath))
|
|
8320
|
-
|
|
8768
|
+
writeFileSync6(taskConfigPath, `{}
|
|
8321
8769
|
`, "utf-8");
|
|
8322
8770
|
}
|
|
8323
8771
|
function ensureGitignoreEntries(projectRoot) {
|
|
@@ -8345,7 +8793,7 @@ function ensureRigConfigPackageDependencies(projectRoot) {
|
|
|
8345
8793
|
...existsSync11(path) ? existing : { name: "rig-project", private: true },
|
|
8346
8794
|
devDependencies
|
|
8347
8795
|
};
|
|
8348
|
-
|
|
8796
|
+
writeFileSync6(path, `${JSON.stringify(next, null, 2)}
|
|
8349
8797
|
`, "utf8");
|
|
8350
8798
|
}
|
|
8351
8799
|
function applyGitHubProjectConfig(source, options) {
|
|
@@ -8371,9 +8819,9 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
|
8371
8819
|
const selected = strategy ?? { kind: "managed-clone" };
|
|
8372
8820
|
switch (selected.kind) {
|
|
8373
8821
|
case "managed-clone":
|
|
8374
|
-
return { kind: "managed-clone"
|
|
8822
|
+
return { kind: "managed-clone" };
|
|
8375
8823
|
case "current-ref":
|
|
8376
|
-
return { kind: "current-ref",
|
|
8824
|
+
return { kind: "current-ref", ...selected.ref ? { ref: selected.ref } : {} };
|
|
8377
8825
|
case "uploaded-snapshot":
|
|
8378
8826
|
return { kind: "uploaded-snapshot", path: projectRoot, source: "local-working-tree" };
|
|
8379
8827
|
case "existing-path":
|
|
@@ -8643,7 +9091,7 @@ function remoteGitHubAuthMetadata(payload) {
|
|
|
8643
9091
|
};
|
|
8644
9092
|
}
|
|
8645
9093
|
function writeRemoteGitHubAuthState(projectRoot, input) {
|
|
8646
|
-
|
|
9094
|
+
writeFileSync6(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
|
|
8647
9095
|
authenticated: true,
|
|
8648
9096
|
source: input.source,
|
|
8649
9097
|
storedOnServer: true,
|
|
@@ -8691,7 +9139,7 @@ function runLocalFilesInit(context, options) {
|
|
|
8691
9139
|
console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
|
|
8692
9140
|
} else {
|
|
8693
9141
|
const projectName = basename2(projectRoot) || "rig-project";
|
|
8694
|
-
|
|
9142
|
+
writeFileSync6(configTsPath, buildRigInitConfigSource({
|
|
8695
9143
|
projectName,
|
|
8696
9144
|
taskSource: { kind: "files", path: "tasks" },
|
|
8697
9145
|
useStandardPlugin: true
|
|
@@ -8700,8 +9148,8 @@ function runLocalFilesInit(context, options) {
|
|
|
8700
9148
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
8701
9149
|
const tasksDir = resolve17(projectRoot, "tasks");
|
|
8702
9150
|
if (!existsSync11(tasksDir)) {
|
|
8703
|
-
|
|
8704
|
-
|
|
9151
|
+
mkdirSync8(tasksDir, { recursive: true });
|
|
9152
|
+
writeFileSync6(resolve17(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)}
|
|
8705
9153
|
`, "utf-8");
|
|
8706
9154
|
}
|
|
8707
9155
|
if (context.outputMode !== "json") {
|
|
@@ -8764,7 +9212,7 @@ function runDemoInit(context, options) {
|
|
|
8764
9212
|
console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
|
|
8765
9213
|
}
|
|
8766
9214
|
} else {
|
|
8767
|
-
|
|
9215
|
+
writeFileSync6(configTsPath, buildRigInitConfigSource({
|
|
8768
9216
|
projectName: basename2(projectRoot) || "rig-demo",
|
|
8769
9217
|
taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
|
|
8770
9218
|
useStandardPlugin: true
|
|
@@ -8773,14 +9221,14 @@ function runDemoInit(context, options) {
|
|
|
8773
9221
|
}
|
|
8774
9222
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
8775
9223
|
const demoTasksDir = resolve17(projectRoot, DEMO_TASKS_RELATIVE_DIR);
|
|
8776
|
-
|
|
9224
|
+
mkdirSync8(demoTasksDir, { recursive: true });
|
|
8777
9225
|
const taskIds = [];
|
|
8778
9226
|
for (const task of DEMO_TASKS) {
|
|
8779
9227
|
const id = String(task.id);
|
|
8780
9228
|
taskIds.push(id);
|
|
8781
9229
|
const taskPath = resolve17(demoTasksDir, `${id}.json`);
|
|
8782
9230
|
if (!existsSync11(taskPath)) {
|
|
8783
|
-
|
|
9231
|
+
writeFileSync6(taskPath, `${JSON.stringify(task, null, 2)}
|
|
8784
9232
|
`, "utf-8");
|
|
8785
9233
|
}
|
|
8786
9234
|
}
|
|
@@ -8808,21 +9256,32 @@ function runDemoInit(context, options) {
|
|
|
8808
9256
|
}
|
|
8809
9257
|
async function runControlPlaneInit(context, options) {
|
|
8810
9258
|
const projectRoot = context.projectRoot;
|
|
8811
|
-
const
|
|
9259
|
+
const existingRepoConnection = readRepoConnection(projectRoot);
|
|
9260
|
+
const selectedConnection = (() => {
|
|
9261
|
+
try {
|
|
9262
|
+
return resolveSelectedConnection(projectRoot);
|
|
9263
|
+
} catch {
|
|
9264
|
+
return null;
|
|
9265
|
+
}
|
|
9266
|
+
})();
|
|
9267
|
+
const selectedRemote = selectedConnection?.connection.kind === "remote" ? { alias: selectedConnection.alias, connection: selectedConnection.connection } : null;
|
|
9268
|
+
const detectedSlug = options.repoSlug ?? existingRepoConnection?.project ?? detectOriginRepoSlug(projectRoot);
|
|
9269
|
+
const serverKind = options.server ?? (selectedRemote ? "remote" : "local");
|
|
8812
9270
|
if (!detectedSlug) {
|
|
8813
9271
|
const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
|
|
8814
|
-
if (
|
|
9272
|
+
if (serverKind === "local" && authMethod2 === "skip") {
|
|
8815
9273
|
return runLocalFilesInit(context, options);
|
|
8816
9274
|
}
|
|
8817
9275
|
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);
|
|
8818
9276
|
}
|
|
8819
9277
|
const repo = parseRepoSlug(detectedSlug);
|
|
8820
|
-
const
|
|
8821
|
-
const
|
|
9278
|
+
const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : selectedRemote?.alias ?? "remote");
|
|
9279
|
+
const selectedRemoteUrl = selectedRemote && (!options.connectionAlias || options.connectionAlias === selectedRemote.alias) ? selectedRemote.connection.baseUrl : undefined;
|
|
9280
|
+
const remoteUrl = serverKind === "remote" ? (options.remoteUrl ?? selectedRemoteUrl)?.replace(/\/+$/, "") : undefined;
|
|
8822
9281
|
if (serverKind === "remote") {
|
|
8823
|
-
if (!
|
|
8824
|
-
throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server
|
|
8825
|
-
upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl:
|
|
9282
|
+
if (!remoteUrl)
|
|
9283
|
+
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>`." });
|
|
9284
|
+
upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: remoteUrl });
|
|
8826
9285
|
}
|
|
8827
9286
|
writeRepoConnection(projectRoot, {
|
|
8828
9287
|
selected: connectionAlias,
|
|
@@ -8845,11 +9304,11 @@ async function runControlPlaneInit(context, options) {
|
|
|
8845
9304
|
taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
|
|
8846
9305
|
useStandardPlugin: true
|
|
8847
9306
|
}), options);
|
|
8848
|
-
|
|
9307
|
+
writeFileSync6(configTsPath, source, "utf-8");
|
|
8849
9308
|
}
|
|
8850
9309
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
8851
9310
|
}
|
|
8852
|
-
|
|
9311
|
+
writeFileSync6(resolve17(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
|
|
8853
9312
|
`, "utf8");
|
|
8854
9313
|
const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
|
|
8855
9314
|
let uploadedSnapshot = null;
|
|
@@ -8864,7 +9323,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
8864
9323
|
let githubAuth = null;
|
|
8865
9324
|
let deviceAuth = null;
|
|
8866
9325
|
const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
|
|
8867
|
-
const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${
|
|
9326
|
+
const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${remoteUrl ?? "the remote Rig server"}.` : null;
|
|
8868
9327
|
if (remoteGhTokenWarning && !options.yes) {
|
|
8869
9328
|
throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
|
|
8870
9329
|
}
|
|
@@ -9180,8 +9639,8 @@ init__server_client();
|
|
|
9180
9639
|
init__connection_state();
|
|
9181
9640
|
init__async_ui();
|
|
9182
9641
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
9183
|
-
import { mkdirSync as
|
|
9184
|
-
import { dirname as
|
|
9642
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
9643
|
+
import { dirname as dirname5, resolve as resolve18 } from "path";
|
|
9185
9644
|
function printPayload(context, payload, fallback) {
|
|
9186
9645
|
if (context.outputMode === "json")
|
|
9187
9646
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -9220,8 +9679,8 @@ function persistRemoteAuthSession(context, source, result, fallbackToken) {
|
|
|
9220
9679
|
return;
|
|
9221
9680
|
const repo = readRepoConnection(context.projectRoot);
|
|
9222
9681
|
const path = resolve18(context.projectRoot, ".rig", "state", "github-auth.json");
|
|
9223
|
-
|
|
9224
|
-
|
|
9682
|
+
mkdirSync9(dirname5(path), { recursive: true });
|
|
9683
|
+
writeFileSync7(path, `${JSON.stringify({
|
|
9225
9684
|
authenticated: true,
|
|
9226
9685
|
source,
|
|
9227
9686
|
storedOnServer: true,
|
|
@@ -11001,6 +11460,7 @@ init_drone_ui();
|
|
|
11001
11460
|
init_runner();
|
|
11002
11461
|
init__connection_state();
|
|
11003
11462
|
init__cli_format();
|
|
11463
|
+
init__server_client();
|
|
11004
11464
|
function usageName(options) {
|
|
11005
11465
|
return `rig ${options.group}`;
|
|
11006
11466
|
}
|
|
@@ -11020,13 +11480,46 @@ function parseConnection(alias, value, options) {
|
|
|
11020
11480
|
}
|
|
11021
11481
|
return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
|
|
11022
11482
|
}
|
|
11023
|
-
function printJsonOrText(context,
|
|
11024
|
-
if (context.outputMode
|
|
11025
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
11026
|
-
} else {
|
|
11483
|
+
function printJsonOrText(context, _payload, text2) {
|
|
11484
|
+
if (context.outputMode !== "json") {
|
|
11027
11485
|
console.log(text2);
|
|
11028
11486
|
}
|
|
11029
11487
|
}
|
|
11488
|
+
function formatRemoteProjectLinkText(result) {
|
|
11489
|
+
return formatSuccessCard(result.ok ? "Remote project link ready" : "Remote project link needs repair", [
|
|
11490
|
+
["selected", result.alias ?? "remote"],
|
|
11491
|
+
["target", result.baseUrl ?? "unknown"],
|
|
11492
|
+
["repo", result.repoSlug ?? "owner/repo"],
|
|
11493
|
+
["status", result.status],
|
|
11494
|
+
...result.serverProjectRoot ? [["server root", result.serverProjectRoot]] : [],
|
|
11495
|
+
["next", result.ok ? result.next ?? "rig task list" : result.hint]
|
|
11496
|
+
]);
|
|
11497
|
+
}
|
|
11498
|
+
function parseRepairLinkArgs(rest, usage) {
|
|
11499
|
+
let repoSlug;
|
|
11500
|
+
let mode = "prepare-if-missing";
|
|
11501
|
+
const pending = [...rest];
|
|
11502
|
+
while (pending.length > 0) {
|
|
11503
|
+
const token = pending.shift();
|
|
11504
|
+
if (token === "--repo") {
|
|
11505
|
+
const value = pending.shift()?.trim();
|
|
11506
|
+
if (!value)
|
|
11507
|
+
throw new CliError(`Missing --repo value. Usage: ${usage}`, 1);
|
|
11508
|
+
const normalized = normalizeRepoSlug(value);
|
|
11509
|
+
if (!normalized)
|
|
11510
|
+
throw new CliError(`Invalid --repo value: ${value}`, 1, { hint: "Use the canonical owner/repo slug, e.g. `--repo humanity-org/humanwork`." });
|
|
11511
|
+
repoSlug = normalized;
|
|
11512
|
+
} else if (token === "--backfill-only") {
|
|
11513
|
+
mode = "backfill-only";
|
|
11514
|
+
} else if (token === "--prepare") {
|
|
11515
|
+
mode = "prepare-if-missing";
|
|
11516
|
+
} else {
|
|
11517
|
+
throw new CliError(`Unexpected argument: ${String(token)}
|
|
11518
|
+
Usage: ${usage}`, 1);
|
|
11519
|
+
}
|
|
11520
|
+
}
|
|
11521
|
+
return { repoSlug, mode };
|
|
11522
|
+
}
|
|
11030
11523
|
async function promptForConnectionAlias(context) {
|
|
11031
11524
|
const state = readGlobalConnections();
|
|
11032
11525
|
const repo = readRepoConnection(context.projectRoot);
|
|
@@ -11080,37 +11573,67 @@ async function executeConnectionCommand(context, args, options) {
|
|
|
11080
11573
|
}
|
|
11081
11574
|
if (!alias)
|
|
11082
11575
|
throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
|
|
11576
|
+
let selectedConnection = { kind: "local", mode: "auto" };
|
|
11083
11577
|
if (alias !== "local") {
|
|
11084
11578
|
const state = readGlobalConnections();
|
|
11085
|
-
|
|
11579
|
+
const connection = state.connections[alias];
|
|
11580
|
+
if (!connection)
|
|
11086
11581
|
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>`." });
|
|
11582
|
+
selectedConnection = connection;
|
|
11087
11583
|
}
|
|
11088
11584
|
const previousRepo = readRepoConnection(context.projectRoot);
|
|
11585
|
+
const preserveRemoteRoot = Boolean(previousRepo?.serverProjectRoot && previousRepo.selected === alias && selectedConnection.kind === "remote" && previousRepo.serverProjectRootAlias === alias && previousRepo.serverProjectRootBaseUrl === selectedConnection.baseUrl);
|
|
11089
11586
|
const repoState = {
|
|
11090
11587
|
selected: alias,
|
|
11091
11588
|
...previousRepo?.project ? { project: previousRepo.project } : {},
|
|
11092
11589
|
linkedAt: new Date().toISOString(),
|
|
11093
|
-
...
|
|
11590
|
+
...preserveRemoteRoot ? {
|
|
11591
|
+
serverProjectRoot: previousRepo.serverProjectRoot,
|
|
11592
|
+
serverProjectRootAlias: previousRepo.serverProjectRootAlias,
|
|
11593
|
+
serverProjectRootBaseUrl: previousRepo.serverProjectRootBaseUrl
|
|
11594
|
+
} : {}
|
|
11094
11595
|
};
|
|
11095
11596
|
writeRepoConnection(context.projectRoot, repoState);
|
|
11096
|
-
|
|
11597
|
+
const remoteProjectLink = alias !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
|
|
11598
|
+
ok: false,
|
|
11599
|
+
status: "error",
|
|
11600
|
+
message: error instanceof Error ? error.message : String(error),
|
|
11601
|
+
hint: "Run `rig server repair-link` after authenticating the selected remote."
|
|
11602
|
+
})) : null;
|
|
11603
|
+
const latestRepo = readRepoConnection(context.projectRoot);
|
|
11604
|
+
const details = { selected: latestRepo?.selected ?? alias, repo: latestRepo, remoteProjectLink };
|
|
11605
|
+
printJsonOrText(context, details, formatSuccessCard("Rig server selected", [
|
|
11097
11606
|
["selected", alias],
|
|
11098
11607
|
["scope", "this repo"],
|
|
11099
|
-
["
|
|
11608
|
+
...remoteProjectLink ? [["remote link", remoteProjectLink.ok ? `ready (${remoteProjectLink.serverProjectRoot ?? "linked"})` : `${remoteProjectLink.status}: ${remoteProjectLink.hint}`]] : [],
|
|
11609
|
+
["next", remoteProjectLink && !remoteProjectLink.ok ? "rig server repair-link" : "rig task list"]
|
|
11100
11610
|
]));
|
|
11101
|
-
return { ok: true, group: options.group, command: "use", details
|
|
11611
|
+
return { ok: true, group: options.group, command: "use", details };
|
|
11612
|
+
}
|
|
11613
|
+
case "repair-link": {
|
|
11614
|
+
const parsed = parseRepairLinkArgs(rest, `${usageName(options)} repair-link [--prepare|--backfill-only] [--repo owner/repo]`);
|
|
11615
|
+
const result = await repairRemoteProjectRootLink(context, parsed);
|
|
11616
|
+
printJsonOrText(context, result, formatRemoteProjectLinkText(result));
|
|
11617
|
+
return { ok: true, group: options.group, command: "repair-link", details: result };
|
|
11102
11618
|
}
|
|
11103
11619
|
case "status": {
|
|
11104
11620
|
requireNoExtraArgs(rest, `${usageName(options)} status`);
|
|
11105
11621
|
const repo = readRepoConnection(context.projectRoot);
|
|
11106
11622
|
const global = readGlobalConnections();
|
|
11107
|
-
const
|
|
11108
|
-
|
|
11623
|
+
const remoteProjectLink = repo?.selected && repo.selected !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
|
|
11624
|
+
ok: false,
|
|
11625
|
+
status: "error",
|
|
11626
|
+
message: error instanceof Error ? error.message : String(error),
|
|
11627
|
+
hint: "Run `rig server repair-link` after authenticating the selected remote."
|
|
11628
|
+
})) : null;
|
|
11629
|
+
const latestRepo = readRepoConnection(context.projectRoot) ?? repo;
|
|
11630
|
+
const details = { selected: latestRepo?.selected ?? "local", repo: latestRepo, connections: global.connections, remoteProjectLink };
|
|
11631
|
+
printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections, latestRepo ?? null, remoteProjectLink));
|
|
11109
11632
|
return { ok: true, group: options.group, command: "status", details };
|
|
11110
11633
|
}
|
|
11111
11634
|
default:
|
|
11112
11635
|
throw new CliError(`Unknown ${options.group} command: ${String(command)}
|
|
11113
|
-
Usage: ${usageName(options)} <list|add|use|status>`, 1);
|
|
11636
|
+
Usage: ${usageName(options)} <list|add|use|status|repair-link>`, 1);
|
|
11114
11637
|
}
|
|
11115
11638
|
}
|
|
11116
11639
|
|
|
@@ -11118,7 +11641,7 @@ Usage: ${usageName(options)} <list|add|use|status>`, 1);
|
|
|
11118
11641
|
init__server_client();
|
|
11119
11642
|
async function executeServer(context, args, options) {
|
|
11120
11643
|
const [command = "status", ...rest] = args;
|
|
11121
|
-
if (["status", "list", "add", "use"].includes(command)) {
|
|
11644
|
+
if (["status", "list", "add", "use", "repair-link"].includes(command)) {
|
|
11122
11645
|
return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
|
|
11123
11646
|
}
|
|
11124
11647
|
switch (command) {
|
|
@@ -11326,7 +11849,7 @@ init_task();
|
|
|
11326
11849
|
|
|
11327
11850
|
// packages/cli/src/commands/task-run-driver.ts
|
|
11328
11851
|
init_runner();
|
|
11329
|
-
import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as
|
|
11852
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync9 } from "fs";
|
|
11330
11853
|
import { resolve as resolve23 } from "path";
|
|
11331
11854
|
import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
|
|
11332
11855
|
import { createInterface as createLineInterface } from "readline";
|
|
@@ -11431,7 +11954,7 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
|
|
|
11431
11954
|
try {
|
|
11432
11955
|
if (!statSync2(sourcePath).isFile())
|
|
11433
11956
|
continue;
|
|
11434
|
-
|
|
11957
|
+
mkdirSync11(resolve23(targetPath, ".."), { recursive: true });
|
|
11435
11958
|
copyFileSync3(sourcePath, targetPath);
|
|
11436
11959
|
copied += 1;
|
|
11437
11960
|
} catch {}
|
|
@@ -12323,8 +12846,8 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
12323
12846
|
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
12324
12847
|
classifiedAt: new Date().toISOString()
|
|
12325
12848
|
};
|
|
12326
|
-
|
|
12327
|
-
|
|
12849
|
+
mkdirSync11(resolve23(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
12850
|
+
writeFileSync9(resolve23(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
12328
12851
|
`, "utf8");
|
|
12329
12852
|
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
12330
12853
|
prompt = `${prompt}
|
|
@@ -12471,8 +12994,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
12471
12994
|
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
12472
12995
|
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
12473
12996
|
if (readyFile) {
|
|
12474
|
-
|
|
12475
|
-
|
|
12997
|
+
mkdirSync11(resolve23(readyFile, ".."), { recursive: true });
|
|
12998
|
+
writeFileSync9(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
12476
12999
|
`, "utf8");
|
|
12477
13000
|
}
|
|
12478
13001
|
appendRunLog(context.projectRoot, input.runId, {
|
|
@@ -13392,7 +13915,7 @@ async function executeTest(context, args) {
|
|
|
13392
13915
|
init_runner();
|
|
13393
13916
|
init__parsers();
|
|
13394
13917
|
init__paths();
|
|
13395
|
-
import { existsSync as existsSync16, mkdirSync as
|
|
13918
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync12, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
13396
13919
|
import { resolve as resolve24 } from "path";
|
|
13397
13920
|
import { createPluginHost } from "@rig/core";
|
|
13398
13921
|
import {
|
|
@@ -13448,12 +13971,12 @@ function runSetupInit(projectRoot) {
|
|
|
13448
13971
|
const stateDir = resolveControlPlaneHostStateDir(projectRoot);
|
|
13449
13972
|
const logsDir = resolveControlPlaneHostLogsDir(projectRoot);
|
|
13450
13973
|
const artifactsDir = resolveControlPlaneArtifactsDir(projectRoot);
|
|
13451
|
-
|
|
13452
|
-
|
|
13453
|
-
|
|
13974
|
+
mkdirSync12(stateDir, { recursive: true });
|
|
13975
|
+
mkdirSync12(logsDir, { recursive: true });
|
|
13976
|
+
mkdirSync12(artifactsDir, { recursive: true });
|
|
13454
13977
|
const failuresPath = resolve24(stateDir, "failed_approaches.md");
|
|
13455
13978
|
if (!existsSync16(failuresPath)) {
|
|
13456
|
-
|
|
13979
|
+
writeFileSync10(failuresPath, `# Failed Approaches
|
|
13457
13980
|
|
|
13458
13981
|
`, "utf-8");
|
|
13459
13982
|
}
|