@h-rig/cli 0.0.6-alpha.5 → 0.0.6-alpha.50
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 +4072 -1204
- package/dist/src/commands/_cli-format.js +369 -0
- package/dist/src/commands/_connection-state.js +12 -6
- package/dist/src/commands/_doctor-checks.js +65 -34
- package/dist/src/commands/_help-catalog.js +445 -0
- package/dist/src/commands/_operator-surface.js +220 -0
- package/dist/src/commands/_operator-view.js +1109 -63
- package/dist/src/commands/_parsers.js +0 -2
- package/dist/src/commands/_pi-frontend.js +1066 -0
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_pi-remote-session.js +757 -0
- package/dist/src/commands/_pi-worker-bridge-extension.js +820 -0
- package/dist/src/commands/_policy.js +0 -2
- package/dist/src/commands/_preflight.js +84 -116
- package/dist/src/commands/_run-driver-helpers.js +2 -2
- package/dist/src/commands/_server-client.js +211 -48
- package/dist/src/commands/_snapshot-upload.js +60 -30
- package/dist/src/commands/_spinner.js +63 -0
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +8 -9
- package/dist/src/commands/browser.js +4 -6
- package/dist/src/commands/connect.js +134 -26
- package/dist/src/commands/dist.js +4 -6
- package/dist/src/commands/doctor.js +65 -34
- package/dist/src/commands/github.js +62 -32
- package/dist/src/commands/inbox.js +396 -31
- package/dist/src/commands/init.js +342 -88
- package/dist/src/commands/inspect.js +282 -23
- package/dist/src/commands/inspector.js +2 -4
- package/dist/src/commands/pi.js +168 -0
- 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 +1407 -130
- package/dist/src/commands/server.js +266 -40
- package/dist/src/commands/setup.js +69 -44
- package/dist/src/commands/task-report-bug.js +5 -7
- package/dist/src/commands/task-run-driver.js +662 -70
- package/dist/src/commands/task.js +1846 -259
- package/dist/src/commands/test.js +3 -5
- package/dist/src/commands/workspace.js +4 -6
- package/dist/src/commands.js +4054 -1180
- package/dist/src/index.js +4065 -1200
- 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 +7 -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";
|
|
@@ -132,7 +131,8 @@ function readRepoConnection(projectRoot) {
|
|
|
132
131
|
return {
|
|
133
132
|
selected,
|
|
134
133
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
135
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
134
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
135
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
function writeRepoConnection(projectRoot, state) {
|
|
@@ -143,27 +143,33 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
143
143
|
if (!repo)
|
|
144
144
|
return null;
|
|
145
145
|
if (repo.selected === "local")
|
|
146
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" } };
|
|
146
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
147
147
|
const global = readGlobalConnections(options);
|
|
148
148
|
const connection = global.connections[repo.selected];
|
|
149
149
|
if (!connection) {
|
|
150
|
-
throw new CliError2(`Selected Rig
|
|
150
|
+
throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
151
151
|
}
|
|
152
|
-
return { alias: repo.selected, connection };
|
|
152
|
+
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
153
|
+
}
|
|
154
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
155
|
+
const repo = readRepoConnection(projectRoot);
|
|
156
|
+
if (!repo)
|
|
157
|
+
return;
|
|
158
|
+
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
// packages/cli/src/commands/_server-client.ts
|
|
156
|
-
import { spawnSync } from "child_process";
|
|
157
162
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
158
163
|
import { resolve as resolve2 } from "path";
|
|
159
164
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
160
|
-
var
|
|
165
|
+
var scopedGitHubBearerTokens = new Map;
|
|
161
166
|
function cleanToken(value) {
|
|
162
167
|
const trimmed = value?.trim();
|
|
163
168
|
return trimmed ? trimmed : null;
|
|
164
169
|
}
|
|
165
|
-
function setGitHubBearerTokenForCurrentProcess(token) {
|
|
166
|
-
|
|
170
|
+
function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
171
|
+
const scopedKey = resolve2(projectRoot ?? process.cwd());
|
|
172
|
+
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
167
173
|
}
|
|
168
174
|
function readPrivateRemoteSessionToken(projectRoot) {
|
|
169
175
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
@@ -177,41 +183,33 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
177
183
|
}
|
|
178
184
|
}
|
|
179
185
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
180
|
-
|
|
181
|
-
|
|
186
|
+
const scopedKey = resolve2(projectRoot);
|
|
187
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
188
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
182
189
|
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;
|
|
190
|
+
if (privateSession)
|
|
191
|
+
return privateSession;
|
|
192
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
199
193
|
}
|
|
200
194
|
async function ensureServerForCli(projectRoot) {
|
|
201
195
|
try {
|
|
202
196
|
const selected = resolveSelectedConnection(projectRoot);
|
|
203
197
|
if (selected?.connection.kind === "remote") {
|
|
198
|
+
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
199
|
+
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
204
200
|
return {
|
|
205
201
|
baseUrl: selected.connection.baseUrl,
|
|
206
|
-
authToken
|
|
207
|
-
connectionKind: "remote"
|
|
202
|
+
authToken,
|
|
203
|
+
connectionKind: "remote",
|
|
204
|
+
serverProjectRoot
|
|
208
205
|
};
|
|
209
206
|
}
|
|
210
207
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
211
208
|
return {
|
|
212
209
|
baseUrl: connection.baseUrl,
|
|
213
210
|
authToken: connection.authToken,
|
|
214
|
-
connectionKind: "local"
|
|
211
|
+
connectionKind: "local",
|
|
212
|
+
serverProjectRoot: resolve2(projectRoot)
|
|
215
213
|
};
|
|
216
214
|
} catch (error) {
|
|
217
215
|
if (error instanceof Error) {
|
|
@@ -220,6 +218,29 @@ async function ensureServerForCli(projectRoot) {
|
|
|
220
218
|
throw error;
|
|
221
219
|
}
|
|
222
220
|
}
|
|
221
|
+
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
222
|
+
const repo = readRepoConnection(projectRoot);
|
|
223
|
+
const slug = repo?.project?.trim();
|
|
224
|
+
if (!slug)
|
|
225
|
+
return null;
|
|
226
|
+
try {
|
|
227
|
+
const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
|
|
228
|
+
headers: mergeHeaders(undefined, authToken)
|
|
229
|
+
});
|
|
230
|
+
if (!response.ok)
|
|
231
|
+
return null;
|
|
232
|
+
const payload = await response.json();
|
|
233
|
+
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
234
|
+
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
235
|
+
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
236
|
+
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
237
|
+
if (path)
|
|
238
|
+
writeRepoServerProjectRoot(projectRoot, path);
|
|
239
|
+
return path;
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
223
244
|
function mergeHeaders(headers, authToken) {
|
|
224
245
|
const merged = new Headers(headers);
|
|
225
246
|
if (authToken) {
|
|
@@ -244,9 +265,12 @@ function diagnosticMessage(payload) {
|
|
|
244
265
|
}
|
|
245
266
|
async function requestServerJson(context, pathname, init = {}) {
|
|
246
267
|
const server = await ensureServerForCli(context.projectRoot);
|
|
268
|
+
const headers = mergeHeaders(init.headers, server.authToken);
|
|
269
|
+
if (server.serverProjectRoot)
|
|
270
|
+
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
247
271
|
const response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
248
272
|
...init,
|
|
249
|
-
headers
|
|
273
|
+
headers
|
|
250
274
|
});
|
|
251
275
|
const text = await response.text();
|
|
252
276
|
const payload = text.trim().length > 0 ? (() => {
|
|
@@ -290,39 +314,64 @@ async function registerProjectViaServer(context, input) {
|
|
|
290
314
|
function sleep(ms) {
|
|
291
315
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
292
316
|
}
|
|
317
|
+
function isRetryableProjectRootSwitchError(error) {
|
|
318
|
+
if (!(error instanceof Error))
|
|
319
|
+
return false;
|
|
320
|
+
const message = error.message.toLowerCase();
|
|
321
|
+
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");
|
|
322
|
+
}
|
|
293
323
|
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
324
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
300
325
|
const pollMs = options.pollMs ?? 1000;
|
|
301
326
|
const deadline = Date.now() + timeoutMs;
|
|
302
327
|
let lastError;
|
|
328
|
+
let switched = null;
|
|
303
329
|
while (Date.now() < deadline) {
|
|
304
330
|
try {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
lastError = `server projectRoot=${String(record.projectRoot ?? "unknown")}`;
|
|
312
|
-
}
|
|
331
|
+
switched = await requestServerJson(context, "/api/server/project-root", {
|
|
332
|
+
method: "POST",
|
|
333
|
+
headers: { "content-type": "application/json" },
|
|
334
|
+
body: JSON.stringify({ projectRoot })
|
|
335
|
+
});
|
|
336
|
+
break;
|
|
313
337
|
} catch (error) {
|
|
314
338
|
lastError = error;
|
|
339
|
+
if (!isRetryableProjectRootSwitchError(error))
|
|
340
|
+
throw error;
|
|
341
|
+
await sleep(pollMs);
|
|
315
342
|
}
|
|
316
|
-
await sleep(pollMs);
|
|
317
343
|
}
|
|
318
|
-
|
|
344
|
+
if (!switched) {
|
|
345
|
+
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);
|
|
346
|
+
}
|
|
347
|
+
const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
|
|
348
|
+
if (record.ok === true) {
|
|
349
|
+
writeRepoServerProjectRoot(context.projectRoot, projectRoot);
|
|
350
|
+
return { ok: true, switched: record };
|
|
351
|
+
}
|
|
352
|
+
throw new CliError2(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
|
|
353
|
+
}
|
|
354
|
+
async function ensureTaskLabelsViaServer(context) {
|
|
355
|
+
const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
|
|
356
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
357
|
+
}
|
|
358
|
+
async function listGitHubProjectsViaServer(context, owner) {
|
|
359
|
+
const url = new URL("http://rig.local/api/github/projects");
|
|
360
|
+
url.searchParams.set("owner", owner);
|
|
361
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
362
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
|
|
363
|
+
}
|
|
364
|
+
async function getGitHubProjectStatusFieldViaServer(context, projectId) {
|
|
365
|
+
const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
|
|
366
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
319
367
|
}
|
|
320
368
|
|
|
321
369
|
// packages/cli/src/commands/_pi-install.ts
|
|
322
370
|
import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
|
|
323
371
|
import { homedir as homedir2 } from "os";
|
|
324
372
|
import { resolve as resolve3 } from "path";
|
|
325
|
-
var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
373
|
+
var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
|
|
374
|
+
var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
326
375
|
var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
|
|
327
376
|
export { default } from '@rig/pi-rig';
|
|
328
377
|
`;
|
|
@@ -350,7 +399,7 @@ function resolvePiHomeDir(inputHomeDir) {
|
|
|
350
399
|
function piListContainsPiRig(output) {
|
|
351
400
|
return output.split(/\r?\n/).some((line) => {
|
|
352
401
|
const normalized = line.trim();
|
|
353
|
-
return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
402
|
+
return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
354
403
|
});
|
|
355
404
|
}
|
|
356
405
|
async function safeRun(runner, command, options) {
|
|
@@ -466,7 +515,7 @@ async function ensureRemotePiRigInstalled(input) {
|
|
|
466
515
|
const payload = await input.requestJson("/api/pi-rig/install", {
|
|
467
516
|
method: "POST",
|
|
468
517
|
headers: { "content-type": "application/json" },
|
|
469
|
-
body: JSON.stringify({ package:
|
|
518
|
+
body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
|
|
470
519
|
});
|
|
471
520
|
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
472
521
|
const piOk = record.piOk === true || record.ok === true;
|
|
@@ -727,7 +776,7 @@ async function runRigDoctorChecks(options) {
|
|
|
727
776
|
const taskSourceKind = config?.taskSource?.kind;
|
|
728
777
|
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
778
|
const repo = readRepoConnection(projectRoot);
|
|
730
|
-
checks.push(repo ? check("project-link", "repo selected Rig
|
|
779
|
+
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
780
|
const selected = (() => {
|
|
732
781
|
try {
|
|
733
782
|
return resolveSelectedConnection(projectRoot);
|
|
@@ -735,7 +784,7 @@ async function runRigDoctorChecks(options) {
|
|
|
735
784
|
return null;
|
|
736
785
|
}
|
|
737
786
|
})();
|
|
738
|
-
checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server
|
|
787
|
+
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
788
|
let server = null;
|
|
740
789
|
try {
|
|
741
790
|
server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
|
|
@@ -809,6 +858,7 @@ function countDoctorFailures(checks) {
|
|
|
809
858
|
|
|
810
859
|
// packages/cli/src/commands/init.ts
|
|
811
860
|
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
861
|
+
var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
|
|
812
862
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
813
863
|
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
814
864
|
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
@@ -819,7 +869,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
|
|
|
819
869
|
return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
|
|
820
870
|
}
|
|
821
871
|
function detectOriginRepoSlug(projectRoot) {
|
|
822
|
-
const result =
|
|
872
|
+
const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
|
|
823
873
|
if (result.status !== 0)
|
|
824
874
|
return null;
|
|
825
875
|
return parseRepoSlugFromRemote(result.stdout.trim());
|
|
@@ -875,11 +925,14 @@ function applyGitHubProjectConfig(source, options) {
|
|
|
875
925
|
return source;
|
|
876
926
|
const projectId = JSON.stringify(options.githubProject);
|
|
877
927
|
const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
|
|
928
|
+
const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
|
|
929
|
+
statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
|
|
930
|
+
`)},` : "";
|
|
878
931
|
return source.replace(` projects: { enabled: false },`, [
|
|
879
932
|
` projects: {`,
|
|
880
933
|
` enabled: true,`,
|
|
881
934
|
` projectId: ${projectId},`,
|
|
882
|
-
` statusFieldId: ${statusFieldId}
|
|
935
|
+
` statusFieldId: ${statusFieldId},${statuses}`,
|
|
883
936
|
` },`
|
|
884
937
|
].join(`
|
|
885
938
|
`));
|
|
@@ -900,21 +953,41 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
|
900
953
|
}
|
|
901
954
|
}
|
|
902
955
|
function detectGhLogin() {
|
|
903
|
-
const result =
|
|
956
|
+
const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
904
957
|
return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
|
|
905
958
|
}
|
|
906
959
|
function readGhAuthToken() {
|
|
907
|
-
const result =
|
|
960
|
+
const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
|
|
908
961
|
if (result.status !== 0 || !result.stdout.trim()) {
|
|
909
962
|
throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
|
|
910
963
|
}
|
|
911
964
|
return result.stdout.trim();
|
|
912
965
|
}
|
|
966
|
+
function refreshGhProjectScopesAndReadToken() {
|
|
967
|
+
const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
|
|
968
|
+
encoding: "utf8",
|
|
969
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
970
|
+
});
|
|
971
|
+
if (result.status !== 0)
|
|
972
|
+
return null;
|
|
973
|
+
try {
|
|
974
|
+
return readGhAuthToken();
|
|
975
|
+
} catch {
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
913
979
|
async function loadClackPrompts() {
|
|
914
980
|
return await import("@clack/prompts");
|
|
915
981
|
}
|
|
982
|
+
function clackTextOptions(options) {
|
|
983
|
+
return {
|
|
984
|
+
message: options.message,
|
|
985
|
+
...options.placeholder ? { placeholder: options.placeholder } : {},
|
|
986
|
+
...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
|
|
987
|
+
};
|
|
988
|
+
}
|
|
916
989
|
async function promptRequiredText(prompts, options) {
|
|
917
|
-
const value = await prompts.text(options);
|
|
990
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
918
991
|
if (prompts.isCancel(value))
|
|
919
992
|
throw new CliError2("Init cancelled.", 1);
|
|
920
993
|
const text = String(value ?? "").trim();
|
|
@@ -923,7 +996,7 @@ async function promptRequiredText(prompts, options) {
|
|
|
923
996
|
return text;
|
|
924
997
|
}
|
|
925
998
|
async function promptOptionalText(prompts, options) {
|
|
926
|
-
const value = await prompts.text(options);
|
|
999
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
927
1000
|
if (prompts.isCancel(value))
|
|
928
1001
|
throw new CliError2("Init cancelled.", 1);
|
|
929
1002
|
return String(value ?? "").trim();
|
|
@@ -934,6 +1007,164 @@ async function promptSelect(prompts, options) {
|
|
|
934
1007
|
throw new CliError2("Init cancelled.", 1);
|
|
935
1008
|
return String(value);
|
|
936
1009
|
}
|
|
1010
|
+
function repoOwnerFromSlug(repoSlug) {
|
|
1011
|
+
return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
|
|
1012
|
+
}
|
|
1013
|
+
function recordArray(value, key) {
|
|
1014
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1015
|
+
return [];
|
|
1016
|
+
const raw = value[key];
|
|
1017
|
+
return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
1018
|
+
}
|
|
1019
|
+
async function listGitHubProjectsForInit(context, owner, token) {
|
|
1020
|
+
if (token?.trim()) {
|
|
1021
|
+
try {
|
|
1022
|
+
return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
|
|
1023
|
+
} catch (directError) {
|
|
1024
|
+
const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
|
|
1025
|
+
if (recordArray(serverPayload, "projects").length > 0)
|
|
1026
|
+
return serverPayload;
|
|
1027
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return listGitHubProjectsViaServer(context, owner);
|
|
1031
|
+
}
|
|
1032
|
+
async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
|
|
1033
|
+
if (token?.trim()) {
|
|
1034
|
+
try {
|
|
1035
|
+
return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
|
|
1036
|
+
} catch (directError) {
|
|
1037
|
+
const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
|
|
1038
|
+
if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
|
|
1039
|
+
return serverPayload;
|
|
1040
|
+
}
|
|
1041
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return getGitHubProjectStatusFieldViaServer(context, projectId);
|
|
1045
|
+
}
|
|
1046
|
+
var PROJECT_STATUS_PROMPTS = {
|
|
1047
|
+
running: "Running/In progress",
|
|
1048
|
+
prOpen: "PR open/review",
|
|
1049
|
+
ciFixing: "CI/review fixing",
|
|
1050
|
+
merging: "Merging",
|
|
1051
|
+
done: "Done",
|
|
1052
|
+
needsAttention: "Needs attention"
|
|
1053
|
+
};
|
|
1054
|
+
var DEFAULT_PROJECT_STATUS_OPTIONS = {
|
|
1055
|
+
running: "In Progress",
|
|
1056
|
+
prOpen: "In Review",
|
|
1057
|
+
ciFixing: "In Review",
|
|
1058
|
+
merging: "Merging",
|
|
1059
|
+
done: "Done",
|
|
1060
|
+
needsAttention: "Needs Attention"
|
|
1061
|
+
};
|
|
1062
|
+
async function promptManualProjectStatusMapping(prompts) {
|
|
1063
|
+
const statuses = {};
|
|
1064
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
1065
|
+
const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
|
|
1066
|
+
const value = await promptOptionalText(prompts, {
|
|
1067
|
+
message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
|
|
1068
|
+
placeholder: defaultLabel
|
|
1069
|
+
});
|
|
1070
|
+
statuses[key] = value || defaultLabel;
|
|
1071
|
+
}
|
|
1072
|
+
return statuses;
|
|
1073
|
+
}
|
|
1074
|
+
function projectScopeError(value) {
|
|
1075
|
+
const text = typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
1076
|
+
return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text);
|
|
1077
|
+
}
|
|
1078
|
+
function optionName(option) {
|
|
1079
|
+
return String(option.name ?? option.label ?? option.id ?? "").trim();
|
|
1080
|
+
}
|
|
1081
|
+
function autoProjectStatusValue(options, key, label) {
|
|
1082
|
+
const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
|
|
1083
|
+
const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
|
|
1084
|
+
if (!match)
|
|
1085
|
+
return null;
|
|
1086
|
+
return String(match.id ?? match.name);
|
|
1087
|
+
}
|
|
1088
|
+
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
|
|
1089
|
+
const projectChoice = await promptSelect(prompts, {
|
|
1090
|
+
message: "GitHub Projects status sync",
|
|
1091
|
+
initialValue: "select",
|
|
1092
|
+
options: [
|
|
1093
|
+
{ value: "select", label: "Select accessible ProjectV2" },
|
|
1094
|
+
{ value: "off", label: "Off" },
|
|
1095
|
+
{ value: "manual", label: "Enter ProjectV2 ids manually" }
|
|
1096
|
+
]
|
|
1097
|
+
});
|
|
1098
|
+
if (projectChoice === "off")
|
|
1099
|
+
return { githubProject: "off" };
|
|
1100
|
+
if (projectChoice === "manual") {
|
|
1101
|
+
return {
|
|
1102
|
+
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
1103
|
+
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
1104
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
const owner = repoOwnerFromSlug(repoSlug);
|
|
1108
|
+
if (!owner)
|
|
1109
|
+
throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
|
|
1110
|
+
let activeToken = githubToken?.trim() || null;
|
|
1111
|
+
let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
1112
|
+
let projects = recordArray(projectsPayload, "projects");
|
|
1113
|
+
if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
|
|
1114
|
+
prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
|
|
1115
|
+
const refreshedToken = refreshProjectToken();
|
|
1116
|
+
if (refreshedToken) {
|
|
1117
|
+
activeToken = refreshedToken;
|
|
1118
|
+
projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
1119
|
+
projects = recordArray(projectsPayload, "projects");
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
if (projects.length === 0) {
|
|
1123
|
+
const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
|
|
1124
|
+
prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
|
|
1125
|
+
return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
|
|
1126
|
+
}
|
|
1127
|
+
const selectedProjectId = await promptSelect(prompts, {
|
|
1128
|
+
message: "GitHub ProjectV2 project",
|
|
1129
|
+
options: [
|
|
1130
|
+
...projects.map((project) => ({
|
|
1131
|
+
value: String(project.id),
|
|
1132
|
+
label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
|
|
1133
|
+
hint: typeof project.url === "string" ? project.url : undefined
|
|
1134
|
+
})),
|
|
1135
|
+
{ value: "manual", label: "Enter ProjectV2 id manually" }
|
|
1136
|
+
]
|
|
1137
|
+
});
|
|
1138
|
+
const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
|
|
1139
|
+
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
1140
|
+
const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
|
|
1141
|
+
const rawField = fieldPayloadRecord.field;
|
|
1142
|
+
const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
|
|
1143
|
+
const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
|
|
1144
|
+
const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
1145
|
+
if (options.length === 0) {
|
|
1146
|
+
return {
|
|
1147
|
+
githubProject: projectId,
|
|
1148
|
+
githubProjectStatusField: fieldId,
|
|
1149
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
|
|
1150
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
const statuses = {};
|
|
1154
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
1155
|
+
const auto = autoProjectStatusValue(options, key, label);
|
|
1156
|
+
statuses[key] = auto ?? await promptSelect(prompts, {
|
|
1157
|
+
message: `Project status option for ${label}`,
|
|
1158
|
+
options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
githubProject: projectId,
|
|
1163
|
+
githubProjectStatusField: fieldId,
|
|
1164
|
+
githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
|
|
1165
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
937
1168
|
function sleep2(ms) {
|
|
938
1169
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
939
1170
|
}
|
|
@@ -947,12 +1178,29 @@ function apiSessionTokenFrom(payload) {
|
|
|
947
1178
|
const token = payload.apiSessionToken;
|
|
948
1179
|
return typeof token === "string" && token.trim() ? token.trim() : null;
|
|
949
1180
|
}
|
|
1181
|
+
function cleanPayloadString(value) {
|
|
1182
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1183
|
+
}
|
|
1184
|
+
function remoteGitHubAuthMetadata(payload) {
|
|
1185
|
+
if (!payload)
|
|
1186
|
+
return {};
|
|
1187
|
+
const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
|
|
1188
|
+
return {
|
|
1189
|
+
...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
|
|
1190
|
+
...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
|
|
1191
|
+
...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
|
|
1192
|
+
...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
|
|
1193
|
+
...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
|
|
1194
|
+
...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
950
1197
|
function writeRemoteGitHubAuthState(projectRoot, input) {
|
|
951
1198
|
writeFileSync2(resolve6(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
|
|
952
1199
|
authenticated: true,
|
|
953
1200
|
source: input.source,
|
|
954
1201
|
storedOnServer: true,
|
|
955
1202
|
selectedRepo: input.selectedRepo,
|
|
1203
|
+
...remoteGitHubAuthMetadata(input.authPayload ?? null),
|
|
956
1204
|
...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
|
|
957
1205
|
updatedAt: new Date().toISOString()
|
|
958
1206
|
}, null, 2)}
|
|
@@ -1045,12 +1293,13 @@ async function runControlPlaneInit(context, options) {
|
|
|
1045
1293
|
if (token) {
|
|
1046
1294
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
|
|
1047
1295
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
1048
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
1296
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
1049
1297
|
if (serverKind === "remote") {
|
|
1050
1298
|
writeRemoteGitHubAuthState(projectRoot, {
|
|
1051
1299
|
source: authMethod === "gh" ? "gh" : "init-token",
|
|
1052
1300
|
selectedRepo: repo.slug,
|
|
1053
|
-
apiSessionToken
|
|
1301
|
+
apiSessionToken,
|
|
1302
|
+
authPayload: githubAuth
|
|
1054
1303
|
});
|
|
1055
1304
|
}
|
|
1056
1305
|
} else if (authMethod === "device") {
|
|
@@ -1069,9 +1318,9 @@ async function runControlPlaneInit(context, options) {
|
|
|
1069
1318
|
if (completed) {
|
|
1070
1319
|
const apiSessionToken = apiSessionTokenFrom(completed);
|
|
1071
1320
|
if (apiSessionToken) {
|
|
1072
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken);
|
|
1321
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
|
|
1073
1322
|
if (serverKind === "remote") {
|
|
1074
|
-
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
|
|
1323
|
+
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
|
|
1075
1324
|
}
|
|
1076
1325
|
}
|
|
1077
1326
|
deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
|
|
@@ -1089,19 +1338,25 @@ async function runControlPlaneInit(context, options) {
|
|
|
1089
1338
|
Object.assign(checkout, preparedCheckout);
|
|
1090
1339
|
}
|
|
1091
1340
|
}
|
|
1341
|
+
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
1342
|
+
if (serverKind === "remote" && checkoutPath && token) {
|
|
1343
|
+
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
|
|
1344
|
+
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
1345
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
1346
|
+
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
|
|
1347
|
+
}
|
|
1092
1348
|
const registered = await registerProjectViaServer(context, {
|
|
1093
1349
|
repoSlug: repo.slug,
|
|
1094
1350
|
checkout
|
|
1095
1351
|
});
|
|
1096
|
-
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
1097
1352
|
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
1353
|
const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
|
|
1354
|
+
const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
|
|
1355
|
+
ok: false,
|
|
1356
|
+
ready: false,
|
|
1357
|
+
labelsReady: false,
|
|
1358
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1359
|
+
}));
|
|
1105
1360
|
const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
|
|
1106
1361
|
remote: true,
|
|
1107
1362
|
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
@@ -1132,6 +1387,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
1132
1387
|
githubAuth,
|
|
1133
1388
|
deviceAuth,
|
|
1134
1389
|
githubAuthWarning: remoteGhTokenWarning,
|
|
1390
|
+
labelSetup,
|
|
1135
1391
|
pi,
|
|
1136
1392
|
doctor
|
|
1137
1393
|
};
|
|
@@ -1238,12 +1494,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
1238
1494
|
});
|
|
1239
1495
|
const serverChoice = await promptSelect(prompts, {
|
|
1240
1496
|
message: "Rig server",
|
|
1497
|
+
initialValue: "remote",
|
|
1241
1498
|
options: [
|
|
1242
|
-
{ value: "
|
|
1243
|
-
{ value: "
|
|
1499
|
+
{ value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
|
|
1500
|
+
{ value: "local", label: "Local server", hint: "run on this machine" }
|
|
1244
1501
|
]
|
|
1245
1502
|
});
|
|
1246
|
-
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder:
|
|
1503
|
+
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
|
|
1247
1504
|
let remoteCheckout;
|
|
1248
1505
|
if (serverChoice === "remote") {
|
|
1249
1506
|
const checkout = await promptSelect(prompts, {
|
|
@@ -1275,38 +1532,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
1275
1532
|
{ value: "skip", label: "Skip for now" }
|
|
1276
1533
|
]
|
|
1277
1534
|
});
|
|
1535
|
+
let remoteGhTokenConfirmed = false;
|
|
1278
1536
|
if (serverChoice === "remote" && authMethod === "gh") {
|
|
1279
1537
|
if (!prompts.confirm)
|
|
1280
1538
|
throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
|
|
1281
1539
|
const confirmed = await prompts.confirm({
|
|
1282
1540
|
message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
|
|
1283
|
-
initialValue:
|
|
1541
|
+
initialValue: true
|
|
1284
1542
|
});
|
|
1285
1543
|
if (prompts.isCancel(confirmed) || confirmed !== true) {
|
|
1286
1544
|
throw new CliError2("Remote gh-token import cancelled.", 1);
|
|
1287
1545
|
}
|
|
1546
|
+
remoteGhTokenConfirmed = true;
|
|
1288
1547
|
}
|
|
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;
|
|
1548
|
+
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
|
|
1549
|
+
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
|
|
1550
|
+
const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
|
|
1299
1551
|
const result = await runControlPlaneInit(context, {
|
|
1300
1552
|
server: serverChoice,
|
|
1301
1553
|
remoteUrl,
|
|
1302
1554
|
repoSlug,
|
|
1303
|
-
githubToken,
|
|
1555
|
+
githubToken: effectiveGithubToken,
|
|
1304
1556
|
githubAuthMethod: authMethod,
|
|
1305
|
-
githubProject,
|
|
1306
|
-
githubProjectStatusField,
|
|
1557
|
+
githubProject: projectConfig.githubProject,
|
|
1558
|
+
githubProjectStatusField: projectConfig.githubProjectStatusField,
|
|
1559
|
+
githubProjectStatuses: projectConfig.githubProjectStatuses,
|
|
1307
1560
|
remoteCheckout,
|
|
1308
1561
|
repair,
|
|
1309
|
-
privateStateOnly
|
|
1562
|
+
privateStateOnly,
|
|
1563
|
+
yes: remoteGhTokenConfirmed || undefined
|
|
1310
1564
|
});
|
|
1311
1565
|
const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
|
|
1312
1566
|
const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
|