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