@h-rig/cli 0.0.6-alpha.2 → 0.0.6-alpha.21
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 +1288 -315
- package/dist/src/commands/_authority-runs.js +1 -0
- package/dist/src/commands/_cli-format.js +106 -0
- package/dist/src/commands/_doctor-checks.js +10 -22
- package/dist/src/commands/_operator-surface.js +204 -0
- package/dist/src/commands/_operator-view.js +207 -51
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_pi-session.js +253 -0
- package/dist/src/commands/_preflight.js +33 -28
- package/dist/src/commands/_server-client.js +80 -27
- package/dist/src/commands/_snapshot-upload.js +7 -20
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +2 -0
- package/dist/src/commands/doctor.js +10 -22
- package/dist/src/commands/github.js +9 -22
- package/dist/src/commands/init.js +295 -66
- package/dist/src/commands/queue.js +1 -0
- package/dist/src/commands/run.js +456 -95
- package/dist/src/commands/server.js +9 -22
- package/dist/src/commands/setup.js +10 -22
- package/dist/src/commands/task-run-driver.js +539 -64
- package/dist/src/commands/task.js +502 -130
- package/dist/src/commands.js +1276 -306
- package/dist/src/index.js +1288 -315
- package/dist/src/launcher.js +5 -3
- package/dist/src/runner.js +3 -2
- package/package.json +5 -4
|
@@ -3,7 +3,7 @@ var __require = import.meta.require;
|
|
|
3
3
|
|
|
4
4
|
// packages/cli/src/commands/init.ts
|
|
5
5
|
import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
6
|
-
import { spawnSync
|
|
6
|
+
import { spawnSync } from "child_process";
|
|
7
7
|
import { resolve as resolve6 } from "path";
|
|
8
8
|
|
|
9
9
|
// packages/cli/src/runner.ts
|
|
@@ -49,6 +49,7 @@ function takeOption(args, option) {
|
|
|
49
49
|
|
|
50
50
|
// packages/cli/src/commands/init.ts
|
|
51
51
|
import { buildRigInitConfigSource } from "@rig/core";
|
|
52
|
+
import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
|
|
52
53
|
|
|
53
54
|
// packages/cli/src/commands/_connection-state.ts
|
|
54
55
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -153,17 +154,17 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
// packages/cli/src/commands/_server-client.ts
|
|
156
|
-
import { spawnSync } from "child_process";
|
|
157
157
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
158
158
|
import { resolve as resolve2 } from "path";
|
|
159
159
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
160
|
-
var
|
|
160
|
+
var scopedGitHubBearerTokens = new Map;
|
|
161
161
|
function cleanToken(value) {
|
|
162
162
|
const trimmed = value?.trim();
|
|
163
163
|
return trimmed ? trimmed : null;
|
|
164
164
|
}
|
|
165
|
-
function setGitHubBearerTokenForCurrentProcess(token) {
|
|
166
|
-
|
|
165
|
+
function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
166
|
+
const scopedKey = resolve2(projectRoot ?? process.cwd());
|
|
167
|
+
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
167
168
|
}
|
|
168
169
|
function readPrivateRemoteSessionToken(projectRoot) {
|
|
169
170
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
@@ -177,25 +178,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
const scopedKey = resolve2(projectRoot);
|
|
182
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
183
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
182
184
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
183
|
-
if (privateSession)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
188
|
-
if (envToken) {
|
|
189
|
-
cachedGitHubBearerToken = envToken;
|
|
190
|
-
return cachedGitHubBearerToken;
|
|
191
|
-
}
|
|
192
|
-
const result = spawnSync("gh", ["auth", "token"], {
|
|
193
|
-
encoding: "utf8",
|
|
194
|
-
timeout: 5000,
|
|
195
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
196
|
-
});
|
|
197
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
198
|
-
return cachedGitHubBearerToken;
|
|
185
|
+
if (privateSession)
|
|
186
|
+
return privateSession;
|
|
187
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
199
188
|
}
|
|
200
189
|
async function ensureServerForCli(projectRoot) {
|
|
201
190
|
try {
|
|
@@ -290,16 +279,36 @@ async function registerProjectViaServer(context, input) {
|
|
|
290
279
|
function sleep(ms) {
|
|
291
280
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
292
281
|
}
|
|
282
|
+
function isRetryableProjectRootSwitchError(error) {
|
|
283
|
+
if (!(error instanceof Error))
|
|
284
|
+
return false;
|
|
285
|
+
const message = error.message.toLowerCase();
|
|
286
|
+
return message.includes("rig server request failed (401): auth-required") || message.includes("rig server request failed (401): github-token-required") || message.includes("rig server request failed (502)") || message.includes("rig server request failed (503)") || message.includes("bad gateway") || message.includes("fetch failed") || message.includes("econnrefused") || message.includes("connection refused");
|
|
287
|
+
}
|
|
293
288
|
async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
|
|
294
|
-
const switched = await requestServerJson(context, "/api/server/project-root", {
|
|
295
|
-
method: "POST",
|
|
296
|
-
headers: { "content-type": "application/json" },
|
|
297
|
-
body: JSON.stringify({ projectRoot })
|
|
298
|
-
});
|
|
299
289
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
300
290
|
const pollMs = options.pollMs ?? 1000;
|
|
301
291
|
const deadline = Date.now() + timeoutMs;
|
|
302
292
|
let lastError;
|
|
293
|
+
let switched = null;
|
|
294
|
+
while (Date.now() < deadline) {
|
|
295
|
+
try {
|
|
296
|
+
switched = await requestServerJson(context, "/api/server/project-root", {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: { "content-type": "application/json" },
|
|
299
|
+
body: JSON.stringify({ projectRoot })
|
|
300
|
+
});
|
|
301
|
+
break;
|
|
302
|
+
} catch (error) {
|
|
303
|
+
lastError = error;
|
|
304
|
+
if (!isRetryableProjectRootSwitchError(error))
|
|
305
|
+
throw error;
|
|
306
|
+
await sleep(pollMs);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (!switched) {
|
|
310
|
+
throw new CliError2(`Rig server did not accept project-root switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no response")}).`, 1);
|
|
311
|
+
}
|
|
303
312
|
while (Date.now() < deadline) {
|
|
304
313
|
try {
|
|
305
314
|
const status = await requestServerJson(context, "/api/server/status");
|
|
@@ -317,12 +326,27 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
|
|
|
317
326
|
}
|
|
318
327
|
throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
|
|
319
328
|
}
|
|
329
|
+
async function ensureTaskLabelsViaServer(context) {
|
|
330
|
+
const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
|
|
331
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
332
|
+
}
|
|
333
|
+
async function listGitHubProjectsViaServer(context, owner) {
|
|
334
|
+
const url = new URL("http://rig.local/api/github/projects");
|
|
335
|
+
url.searchParams.set("owner", owner);
|
|
336
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
337
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
|
|
338
|
+
}
|
|
339
|
+
async function getGitHubProjectStatusFieldViaServer(context, projectId) {
|
|
340
|
+
const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
|
|
341
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
342
|
+
}
|
|
320
343
|
|
|
321
344
|
// packages/cli/src/commands/_pi-install.ts
|
|
322
345
|
import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
|
|
323
346
|
import { homedir as homedir2 } from "os";
|
|
324
347
|
import { resolve as resolve3 } from "path";
|
|
325
|
-
var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
348
|
+
var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
|
|
349
|
+
var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
326
350
|
var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
|
|
327
351
|
export { default } from '@rig/pi-rig';
|
|
328
352
|
`;
|
|
@@ -350,7 +374,7 @@ function resolvePiHomeDir(inputHomeDir) {
|
|
|
350
374
|
function piListContainsPiRig(output) {
|
|
351
375
|
return output.split(/\r?\n/).some((line) => {
|
|
352
376
|
const normalized = line.trim();
|
|
353
|
-
return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
377
|
+
return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
354
378
|
});
|
|
355
379
|
}
|
|
356
380
|
async function safeRun(runner, command, options) {
|
|
@@ -466,7 +490,7 @@ async function ensureRemotePiRigInstalled(input) {
|
|
|
466
490
|
const payload = await input.requestJson("/api/pi-rig/install", {
|
|
467
491
|
method: "POST",
|
|
468
492
|
headers: { "content-type": "application/json" },
|
|
469
|
-
body: JSON.stringify({ package:
|
|
493
|
+
body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
|
|
470
494
|
});
|
|
471
495
|
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
472
496
|
const piOk = record.piOk === true || record.ok === true;
|
|
@@ -809,6 +833,7 @@ function countDoctorFailures(checks) {
|
|
|
809
833
|
|
|
810
834
|
// packages/cli/src/commands/init.ts
|
|
811
835
|
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
836
|
+
var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
|
|
812
837
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
813
838
|
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
814
839
|
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
@@ -819,7 +844,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
|
|
|
819
844
|
return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
|
|
820
845
|
}
|
|
821
846
|
function detectOriginRepoSlug(projectRoot) {
|
|
822
|
-
const result =
|
|
847
|
+
const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
|
|
823
848
|
if (result.status !== 0)
|
|
824
849
|
return null;
|
|
825
850
|
return parseRepoSlugFromRemote(result.stdout.trim());
|
|
@@ -875,11 +900,14 @@ function applyGitHubProjectConfig(source, options) {
|
|
|
875
900
|
return source;
|
|
876
901
|
const projectId = JSON.stringify(options.githubProject);
|
|
877
902
|
const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
|
|
903
|
+
const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
|
|
904
|
+
statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
|
|
905
|
+
`)},` : "";
|
|
878
906
|
return source.replace(` projects: { enabled: false },`, [
|
|
879
907
|
` projects: {`,
|
|
880
908
|
` enabled: true,`,
|
|
881
909
|
` projectId: ${projectId},`,
|
|
882
|
-
` statusFieldId: ${statusFieldId}
|
|
910
|
+
` statusFieldId: ${statusFieldId},${statuses}`,
|
|
883
911
|
` },`
|
|
884
912
|
].join(`
|
|
885
913
|
`));
|
|
@@ -900,21 +928,41 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
|
900
928
|
}
|
|
901
929
|
}
|
|
902
930
|
function detectGhLogin() {
|
|
903
|
-
const result =
|
|
931
|
+
const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
904
932
|
return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
|
|
905
933
|
}
|
|
906
934
|
function readGhAuthToken() {
|
|
907
|
-
const result =
|
|
935
|
+
const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
|
|
908
936
|
if (result.status !== 0 || !result.stdout.trim()) {
|
|
909
937
|
throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
|
|
910
938
|
}
|
|
911
939
|
return result.stdout.trim();
|
|
912
940
|
}
|
|
941
|
+
function refreshGhProjectScopesAndReadToken() {
|
|
942
|
+
const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
|
|
943
|
+
encoding: "utf8",
|
|
944
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
945
|
+
});
|
|
946
|
+
if (result.status !== 0)
|
|
947
|
+
return null;
|
|
948
|
+
try {
|
|
949
|
+
return readGhAuthToken();
|
|
950
|
+
} catch {
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
913
954
|
async function loadClackPrompts() {
|
|
914
955
|
return await import("@clack/prompts");
|
|
915
956
|
}
|
|
957
|
+
function clackTextOptions(options) {
|
|
958
|
+
return {
|
|
959
|
+
message: options.message,
|
|
960
|
+
...options.placeholder ? { placeholder: options.placeholder } : {},
|
|
961
|
+
...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
|
|
962
|
+
};
|
|
963
|
+
}
|
|
916
964
|
async function promptRequiredText(prompts, options) {
|
|
917
|
-
const value = await prompts.text(options);
|
|
965
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
918
966
|
if (prompts.isCancel(value))
|
|
919
967
|
throw new CliError2("Init cancelled.", 1);
|
|
920
968
|
const text = String(value ?? "").trim();
|
|
@@ -923,7 +971,7 @@ async function promptRequiredText(prompts, options) {
|
|
|
923
971
|
return text;
|
|
924
972
|
}
|
|
925
973
|
async function promptOptionalText(prompts, options) {
|
|
926
|
-
const value = await prompts.text(options);
|
|
974
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
927
975
|
if (prompts.isCancel(value))
|
|
928
976
|
throw new CliError2("Init cancelled.", 1);
|
|
929
977
|
return String(value ?? "").trim();
|
|
@@ -934,6 +982,164 @@ async function promptSelect(prompts, options) {
|
|
|
934
982
|
throw new CliError2("Init cancelled.", 1);
|
|
935
983
|
return String(value);
|
|
936
984
|
}
|
|
985
|
+
function repoOwnerFromSlug(repoSlug) {
|
|
986
|
+
return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
|
|
987
|
+
}
|
|
988
|
+
function recordArray(value, key) {
|
|
989
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
990
|
+
return [];
|
|
991
|
+
const raw = value[key];
|
|
992
|
+
return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
993
|
+
}
|
|
994
|
+
async function listGitHubProjectsForInit(context, owner, token) {
|
|
995
|
+
if (token?.trim()) {
|
|
996
|
+
try {
|
|
997
|
+
return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
|
|
998
|
+
} catch (directError) {
|
|
999
|
+
const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
|
|
1000
|
+
if (recordArray(serverPayload, "projects").length > 0)
|
|
1001
|
+
return serverPayload;
|
|
1002
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return listGitHubProjectsViaServer(context, owner);
|
|
1006
|
+
}
|
|
1007
|
+
async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
|
|
1008
|
+
if (token?.trim()) {
|
|
1009
|
+
try {
|
|
1010
|
+
return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
|
|
1011
|
+
} catch (directError) {
|
|
1012
|
+
const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
|
|
1013
|
+
if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
|
|
1014
|
+
return serverPayload;
|
|
1015
|
+
}
|
|
1016
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return getGitHubProjectStatusFieldViaServer(context, projectId);
|
|
1020
|
+
}
|
|
1021
|
+
var PROJECT_STATUS_PROMPTS = {
|
|
1022
|
+
running: "Running/In progress",
|
|
1023
|
+
prOpen: "PR open/review",
|
|
1024
|
+
ciFixing: "CI/review fixing",
|
|
1025
|
+
merging: "Merging",
|
|
1026
|
+
done: "Done",
|
|
1027
|
+
needsAttention: "Needs attention"
|
|
1028
|
+
};
|
|
1029
|
+
var DEFAULT_PROJECT_STATUS_OPTIONS = {
|
|
1030
|
+
running: "In Progress",
|
|
1031
|
+
prOpen: "In Review",
|
|
1032
|
+
ciFixing: "In Review",
|
|
1033
|
+
merging: "Merging",
|
|
1034
|
+
done: "Done",
|
|
1035
|
+
needsAttention: "Needs Attention"
|
|
1036
|
+
};
|
|
1037
|
+
async function promptManualProjectStatusMapping(prompts) {
|
|
1038
|
+
const statuses = {};
|
|
1039
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
1040
|
+
const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
|
|
1041
|
+
const value = await promptOptionalText(prompts, {
|
|
1042
|
+
message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
|
|
1043
|
+
placeholder: defaultLabel
|
|
1044
|
+
});
|
|
1045
|
+
statuses[key] = value || defaultLabel;
|
|
1046
|
+
}
|
|
1047
|
+
return statuses;
|
|
1048
|
+
}
|
|
1049
|
+
function projectScopeError(value) {
|
|
1050
|
+
const text = typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
1051
|
+
return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text);
|
|
1052
|
+
}
|
|
1053
|
+
function optionName(option) {
|
|
1054
|
+
return String(option.name ?? option.label ?? option.id ?? "").trim();
|
|
1055
|
+
}
|
|
1056
|
+
function autoProjectStatusValue(options, key, label) {
|
|
1057
|
+
const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
|
|
1058
|
+
const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
|
|
1059
|
+
if (!match)
|
|
1060
|
+
return null;
|
|
1061
|
+
return String(match.id ?? match.name);
|
|
1062
|
+
}
|
|
1063
|
+
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
|
|
1064
|
+
const projectChoice = await promptSelect(prompts, {
|
|
1065
|
+
message: "GitHub Projects status sync",
|
|
1066
|
+
initialValue: "select",
|
|
1067
|
+
options: [
|
|
1068
|
+
{ value: "select", label: "Select accessible ProjectV2" },
|
|
1069
|
+
{ value: "off", label: "Off" },
|
|
1070
|
+
{ value: "manual", label: "Enter ProjectV2 ids manually" }
|
|
1071
|
+
]
|
|
1072
|
+
});
|
|
1073
|
+
if (projectChoice === "off")
|
|
1074
|
+
return { githubProject: "off" };
|
|
1075
|
+
if (projectChoice === "manual") {
|
|
1076
|
+
return {
|
|
1077
|
+
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
1078
|
+
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
1079
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
const owner = repoOwnerFromSlug(repoSlug);
|
|
1083
|
+
if (!owner)
|
|
1084
|
+
throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
|
|
1085
|
+
let activeToken = githubToken?.trim() || null;
|
|
1086
|
+
let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
1087
|
+
let projects = recordArray(projectsPayload, "projects");
|
|
1088
|
+
if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
|
|
1089
|
+
prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
|
|
1090
|
+
const refreshedToken = refreshProjectToken();
|
|
1091
|
+
if (refreshedToken) {
|
|
1092
|
+
activeToken = refreshedToken;
|
|
1093
|
+
projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
1094
|
+
projects = recordArray(projectsPayload, "projects");
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
if (projects.length === 0) {
|
|
1098
|
+
const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
|
|
1099
|
+
prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
|
|
1100
|
+
return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
|
|
1101
|
+
}
|
|
1102
|
+
const selectedProjectId = await promptSelect(prompts, {
|
|
1103
|
+
message: "GitHub ProjectV2 project",
|
|
1104
|
+
options: [
|
|
1105
|
+
...projects.map((project) => ({
|
|
1106
|
+
value: String(project.id),
|
|
1107
|
+
label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
|
|
1108
|
+
hint: typeof project.url === "string" ? project.url : undefined
|
|
1109
|
+
})),
|
|
1110
|
+
{ value: "manual", label: "Enter ProjectV2 id manually" }
|
|
1111
|
+
]
|
|
1112
|
+
});
|
|
1113
|
+
const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
|
|
1114
|
+
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
1115
|
+
const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
|
|
1116
|
+
const rawField = fieldPayloadRecord.field;
|
|
1117
|
+
const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
|
|
1118
|
+
const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
|
|
1119
|
+
const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
1120
|
+
if (options.length === 0) {
|
|
1121
|
+
return {
|
|
1122
|
+
githubProject: projectId,
|
|
1123
|
+
githubProjectStatusField: fieldId,
|
|
1124
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
|
|
1125
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
const statuses = {};
|
|
1129
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
1130
|
+
const auto = autoProjectStatusValue(options, key, label);
|
|
1131
|
+
statuses[key] = auto ?? await promptSelect(prompts, {
|
|
1132
|
+
message: `Project status option for ${label}`,
|
|
1133
|
+
options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
return {
|
|
1137
|
+
githubProject: projectId,
|
|
1138
|
+
githubProjectStatusField: fieldId,
|
|
1139
|
+
githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
|
|
1140
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
937
1143
|
function sleep2(ms) {
|
|
938
1144
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
939
1145
|
}
|
|
@@ -947,12 +1153,29 @@ function apiSessionTokenFrom(payload) {
|
|
|
947
1153
|
const token = payload.apiSessionToken;
|
|
948
1154
|
return typeof token === "string" && token.trim() ? token.trim() : null;
|
|
949
1155
|
}
|
|
1156
|
+
function cleanPayloadString(value) {
|
|
1157
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1158
|
+
}
|
|
1159
|
+
function remoteGitHubAuthMetadata(payload) {
|
|
1160
|
+
if (!payload)
|
|
1161
|
+
return {};
|
|
1162
|
+
const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
|
|
1163
|
+
return {
|
|
1164
|
+
...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
|
|
1165
|
+
...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
|
|
1166
|
+
...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
|
|
1167
|
+
...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
|
|
1168
|
+
...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
|
|
1169
|
+
...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
950
1172
|
function writeRemoteGitHubAuthState(projectRoot, input) {
|
|
951
1173
|
writeFileSync2(resolve6(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
|
|
952
1174
|
authenticated: true,
|
|
953
1175
|
source: input.source,
|
|
954
1176
|
storedOnServer: true,
|
|
955
1177
|
selectedRepo: input.selectedRepo,
|
|
1178
|
+
...remoteGitHubAuthMetadata(input.authPayload ?? null),
|
|
956
1179
|
...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
|
|
957
1180
|
updatedAt: new Date().toISOString()
|
|
958
1181
|
}, null, 2)}
|
|
@@ -1045,12 +1268,13 @@ async function runControlPlaneInit(context, options) {
|
|
|
1045
1268
|
if (token) {
|
|
1046
1269
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
|
|
1047
1270
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
1048
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
1271
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
1049
1272
|
if (serverKind === "remote") {
|
|
1050
1273
|
writeRemoteGitHubAuthState(projectRoot, {
|
|
1051
1274
|
source: authMethod === "gh" ? "gh" : "init-token",
|
|
1052
1275
|
selectedRepo: repo.slug,
|
|
1053
|
-
apiSessionToken
|
|
1276
|
+
apiSessionToken,
|
|
1277
|
+
authPayload: githubAuth
|
|
1054
1278
|
});
|
|
1055
1279
|
}
|
|
1056
1280
|
} else if (authMethod === "device") {
|
|
@@ -1069,9 +1293,9 @@ async function runControlPlaneInit(context, options) {
|
|
|
1069
1293
|
if (completed) {
|
|
1070
1294
|
const apiSessionToken = apiSessionTokenFrom(completed);
|
|
1071
1295
|
if (apiSessionToken) {
|
|
1072
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken);
|
|
1296
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
|
|
1073
1297
|
if (serverKind === "remote") {
|
|
1074
|
-
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
|
|
1298
|
+
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
|
|
1075
1299
|
}
|
|
1076
1300
|
}
|
|
1077
1301
|
deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
|
|
@@ -1089,19 +1313,25 @@ async function runControlPlaneInit(context, options) {
|
|
|
1089
1313
|
Object.assign(checkout, preparedCheckout);
|
|
1090
1314
|
}
|
|
1091
1315
|
}
|
|
1316
|
+
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
1317
|
+
if (serverKind === "remote" && checkoutPath && token) {
|
|
1318
|
+
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
|
|
1319
|
+
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
1320
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
1321
|
+
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
|
|
1322
|
+
}
|
|
1092
1323
|
const registered = await registerProjectViaServer(context, {
|
|
1093
1324
|
repoSlug: repo.slug,
|
|
1094
1325
|
checkout
|
|
1095
1326
|
});
|
|
1096
|
-
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
1097
1327
|
const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
|
|
1098
|
-
if (serverRootSwitch && token) {
|
|
1099
|
-
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath ?? undefined });
|
|
1100
|
-
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
1101
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
1102
|
-
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken });
|
|
1103
|
-
}
|
|
1104
1328
|
const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
|
|
1329
|
+
const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
|
|
1330
|
+
ok: false,
|
|
1331
|
+
ready: false,
|
|
1332
|
+
labelsReady: false,
|
|
1333
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1334
|
+
}));
|
|
1105
1335
|
const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
|
|
1106
1336
|
remote: true,
|
|
1107
1337
|
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
@@ -1132,6 +1362,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
1132
1362
|
githubAuth,
|
|
1133
1363
|
deviceAuth,
|
|
1134
1364
|
githubAuthWarning: remoteGhTokenWarning,
|
|
1365
|
+
labelSetup,
|
|
1135
1366
|
pi,
|
|
1136
1367
|
doctor
|
|
1137
1368
|
};
|
|
@@ -1238,12 +1469,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
1238
1469
|
});
|
|
1239
1470
|
const serverChoice = await promptSelect(prompts, {
|
|
1240
1471
|
message: "Rig server",
|
|
1472
|
+
initialValue: "remote",
|
|
1241
1473
|
options: [
|
|
1242
|
-
{ value: "
|
|
1243
|
-
{ value: "
|
|
1474
|
+
{ value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
|
|
1475
|
+
{ value: "local", label: "Local server", hint: "run on this machine" }
|
|
1244
1476
|
]
|
|
1245
1477
|
});
|
|
1246
|
-
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder:
|
|
1478
|
+
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
|
|
1247
1479
|
let remoteCheckout;
|
|
1248
1480
|
if (serverChoice === "remote") {
|
|
1249
1481
|
const checkout = await promptSelect(prompts, {
|
|
@@ -1275,38 +1507,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
1275
1507
|
{ value: "skip", label: "Skip for now" }
|
|
1276
1508
|
]
|
|
1277
1509
|
});
|
|
1510
|
+
let remoteGhTokenConfirmed = false;
|
|
1278
1511
|
if (serverChoice === "remote" && authMethod === "gh") {
|
|
1279
1512
|
if (!prompts.confirm)
|
|
1280
1513
|
throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
|
|
1281
1514
|
const confirmed = await prompts.confirm({
|
|
1282
1515
|
message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
|
|
1283
|
-
initialValue:
|
|
1516
|
+
initialValue: true
|
|
1284
1517
|
});
|
|
1285
1518
|
if (prompts.isCancel(confirmed) || confirmed !== true) {
|
|
1286
1519
|
throw new CliError2("Remote gh-token import cancelled.", 1);
|
|
1287
1520
|
}
|
|
1521
|
+
remoteGhTokenConfirmed = true;
|
|
1288
1522
|
}
|
|
1289
|
-
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
options: [
|
|
1293
|
-
{ value: "off", label: "Off" },
|
|
1294
|
-
{ value: "configure", label: "Configure ProjectV2 status field" }
|
|
1295
|
-
]
|
|
1296
|
-
});
|
|
1297
|
-
const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
|
|
1298
|
-
const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
|
|
1523
|
+
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
|
|
1524
|
+
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
|
|
1525
|
+
const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
|
|
1299
1526
|
const result = await runControlPlaneInit(context, {
|
|
1300
1527
|
server: serverChoice,
|
|
1301
1528
|
remoteUrl,
|
|
1302
1529
|
repoSlug,
|
|
1303
|
-
githubToken,
|
|
1530
|
+
githubToken: effectiveGithubToken,
|
|
1304
1531
|
githubAuthMethod: authMethod,
|
|
1305
|
-
githubProject,
|
|
1306
|
-
githubProjectStatusField,
|
|
1532
|
+
githubProject: projectConfig.githubProject,
|
|
1533
|
+
githubProjectStatusField: projectConfig.githubProjectStatusField,
|
|
1534
|
+
githubProjectStatuses: projectConfig.githubProjectStatuses,
|
|
1307
1535
|
remoteCheckout,
|
|
1308
1536
|
repair,
|
|
1309
|
-
privateStateOnly
|
|
1537
|
+
privateStateOnly,
|
|
1538
|
+
yes: remoteGhTokenConfirmed || undefined
|
|
1310
1539
|
});
|
|
1311
1540
|
const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
|
|
1312
1541
|
const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
|
|
@@ -90,6 +90,7 @@ import { ensureProjectMainFreshBeforeRun } from "@rig/runtime/control-plane/proj
|
|
|
90
90
|
|
|
91
91
|
// packages/cli/src/commands/_server-client.ts
|
|
92
92
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
93
|
+
var scopedGitHubBearerTokens = new Map;
|
|
93
94
|
|
|
94
95
|
// packages/cli/src/commands/_preflight.ts
|
|
95
96
|
async function runProjectMainSyncPreflight(context, options) {
|