@h-rig/cli 0.0.6-alpha.5 → 0.0.6-alpha.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/rig.js +4129 -1207
  3. package/dist/src/commands/_cli-format.js +369 -0
  4. package/dist/src/commands/_connection-state.js +12 -6
  5. package/dist/src/commands/_doctor-checks.js +79 -34
  6. package/dist/src/commands/_help-catalog.js +445 -0
  7. package/dist/src/commands/_operator-surface.js +220 -0
  8. package/dist/src/commands/_operator-view.js +1124 -64
  9. package/dist/src/commands/_parsers.js +0 -2
  10. package/dist/src/commands/_pi-frontend.js +1080 -0
  11. package/dist/src/commands/_pi-install.js +4 -3
  12. package/dist/src/commands/_pi-remote-session.js +771 -0
  13. package/dist/src/commands/_pi-worker-bridge-extension.js +834 -0
  14. package/dist/src/commands/_policy.js +0 -2
  15. package/dist/src/commands/_preflight.js +98 -116
  16. package/dist/src/commands/_run-driver-helpers.js +2 -2
  17. package/dist/src/commands/_server-client.js +225 -48
  18. package/dist/src/commands/_snapshot-upload.js +74 -30
  19. package/dist/src/commands/_spinner.js +63 -0
  20. package/dist/src/commands/_task-picker.js +44 -16
  21. package/dist/src/commands/agent.js +8 -9
  22. package/dist/src/commands/browser.js +4 -6
  23. package/dist/src/commands/connect.js +134 -26
  24. package/dist/src/commands/dist.js +4 -6
  25. package/dist/src/commands/doctor.js +79 -34
  26. package/dist/src/commands/github.js +76 -32
  27. package/dist/src/commands/inbox.js +410 -31
  28. package/dist/src/commands/init.js +398 -90
  29. package/dist/src/commands/inspect.js +296 -23
  30. package/dist/src/commands/inspector.js +2 -4
  31. package/dist/src/commands/pi.js +168 -0
  32. package/dist/src/commands/plugin.js +81 -22
  33. package/dist/src/commands/profile-and-review.js +8 -10
  34. package/dist/src/commands/queue.js +2 -3
  35. package/dist/src/commands/remote.js +18 -20
  36. package/dist/src/commands/repo-git-harness.js +6 -8
  37. package/dist/src/commands/run.js +1422 -131
  38. package/dist/src/commands/server.js +280 -40
  39. package/dist/src/commands/setup.js +84 -45
  40. package/dist/src/commands/task-report-bug.js +5 -7
  41. package/dist/src/commands/task-run-driver.js +676 -70
  42. package/dist/src/commands/task.js +1861 -260
  43. package/dist/src/commands/test.js +3 -5
  44. package/dist/src/commands/workspace.js +4 -6
  45. package/dist/src/commands.js +4111 -1183
  46. package/dist/src/index.js +4122 -1203
  47. package/dist/src/launcher.js +5 -3
  48. package/dist/src/report-bug.js +3 -3
  49. package/dist/src/runner.js +5 -19
  50. 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 as spawnSync2 } from "child_process";
7
- import { resolve as resolve6 } from "path";
6
+ import { spawnSync } from "child_process";
7
+ import { basename, 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 connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
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 cachedGitHubBearerToken;
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
- cachedGitHubBearerToken = cleanToken(token ?? undefined);
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,47 @@ function readPrivateRemoteSessionToken(projectRoot) {
177
183
  }
178
184
  }
179
185
  function readGitHubBearerTokenForRemote(projectRoot) {
180
- if (cachedGitHubBearerToken !== undefined)
181
- return cachedGitHubBearerToken;
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
- cachedGitHubBearerToken = privateSession;
185
- return cachedGitHubBearerToken;
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;
190
+ if (privateSession)
191
+ return privateSession;
192
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
193
+ }
194
+ function readStoredGitHubAuthToken(projectRoot) {
195
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
196
+ if (!existsSync2(path))
197
+ return null;
198
+ try {
199
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
200
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
201
+ } catch {
202
+ return null;
191
203
  }
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;
204
+ }
205
+ function readLocalConnectionFallbackToken(projectRoot) {
206
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
199
207
  }
200
208
  async function ensureServerForCli(projectRoot) {
201
209
  try {
202
210
  const selected = resolveSelectedConnection(projectRoot);
203
211
  if (selected?.connection.kind === "remote") {
212
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
213
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
204
214
  return {
205
215
  baseUrl: selected.connection.baseUrl,
206
- authToken: readGitHubBearerTokenForRemote(projectRoot),
207
- connectionKind: "remote"
216
+ authToken,
217
+ connectionKind: "remote",
218
+ serverProjectRoot
208
219
  };
209
220
  }
210
221
  const connection = await ensureLocalRigServerConnection(projectRoot);
211
222
  return {
212
223
  baseUrl: connection.baseUrl,
213
- authToken: connection.authToken,
214
- connectionKind: "local"
224
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
225
+ connectionKind: "local",
226
+ serverProjectRoot: resolve2(projectRoot)
215
227
  };
216
228
  } catch (error) {
217
229
  if (error instanceof Error) {
@@ -220,6 +232,29 @@ async function ensureServerForCli(projectRoot) {
220
232
  throw error;
221
233
  }
222
234
  }
235
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
236
+ const repo = readRepoConnection(projectRoot);
237
+ const slug = repo?.project?.trim();
238
+ if (!slug)
239
+ return null;
240
+ try {
241
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
242
+ headers: mergeHeaders(undefined, authToken)
243
+ });
244
+ if (!response.ok)
245
+ return null;
246
+ const payload = await response.json();
247
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
248
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
249
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
250
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
251
+ if (path)
252
+ writeRepoServerProjectRoot(projectRoot, path);
253
+ return path;
254
+ } catch {
255
+ return null;
256
+ }
257
+ }
223
258
  function mergeHeaders(headers, authToken) {
224
259
  const merged = new Headers(headers);
225
260
  if (authToken) {
@@ -244,9 +279,12 @@ function diagnosticMessage(payload) {
244
279
  }
245
280
  async function requestServerJson(context, pathname, init = {}) {
246
281
  const server = await ensureServerForCli(context.projectRoot);
282
+ const headers = mergeHeaders(init.headers, server.authToken);
283
+ if (server.serverProjectRoot)
284
+ headers.set("x-rig-project-root", server.serverProjectRoot);
247
285
  const response = await fetch(`${server.baseUrl}${pathname}`, {
248
286
  ...init,
249
- headers: mergeHeaders(init.headers, server.authToken)
287
+ headers
250
288
  });
251
289
  const text = await response.text();
252
290
  const payload = text.trim().length > 0 ? (() => {
@@ -290,39 +328,64 @@ async function registerProjectViaServer(context, input) {
290
328
  function sleep(ms) {
291
329
  return new Promise((resolve3) => setTimeout(resolve3, ms));
292
330
  }
331
+ function isRetryableProjectRootSwitchError(error) {
332
+ if (!(error instanceof Error))
333
+ return false;
334
+ const message = error.message.toLowerCase();
335
+ 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");
336
+ }
293
337
  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
338
  const timeoutMs = options.timeoutMs ?? 30000;
300
339
  const pollMs = options.pollMs ?? 1000;
301
340
  const deadline = Date.now() + timeoutMs;
302
341
  let lastError;
342
+ let switched = null;
303
343
  while (Date.now() < deadline) {
304
344
  try {
305
- const status = await requestServerJson(context, "/api/server/status");
306
- if (status && typeof status === "object" && !Array.isArray(status)) {
307
- const record = status;
308
- if (record.projectRoot === projectRoot) {
309
- return { ok: true, switched, status: record };
310
- }
311
- lastError = `server projectRoot=${String(record.projectRoot ?? "unknown")}`;
312
- }
345
+ switched = await requestServerJson(context, "/api/server/project-root", {
346
+ method: "POST",
347
+ headers: { "content-type": "application/json" },
348
+ body: JSON.stringify({ projectRoot })
349
+ });
350
+ break;
313
351
  } catch (error) {
314
352
  lastError = error;
353
+ if (!isRetryableProjectRootSwitchError(error))
354
+ throw error;
355
+ await sleep(pollMs);
315
356
  }
316
- await sleep(pollMs);
317
357
  }
318
- throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
358
+ if (!switched) {
359
+ 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);
360
+ }
361
+ const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
362
+ if (record.ok === true) {
363
+ writeRepoServerProjectRoot(context.projectRoot, projectRoot);
364
+ return { ok: true, switched: record };
365
+ }
366
+ throw new CliError2(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
367
+ }
368
+ async function ensureTaskLabelsViaServer(context) {
369
+ const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
370
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
371
+ }
372
+ async function listGitHubProjectsViaServer(context, owner) {
373
+ const url = new URL("http://rig.local/api/github/projects");
374
+ url.searchParams.set("owner", owner);
375
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
376
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
377
+ }
378
+ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
379
+ const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
380
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
319
381
  }
320
382
 
321
383
  // packages/cli/src/commands/_pi-install.ts
322
384
  import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
323
385
  import { homedir as homedir2 } from "os";
324
386
  import { resolve as resolve3 } from "path";
325
- var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
387
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
388
+ var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
326
389
  var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
327
390
  export { default } from '@rig/pi-rig';
328
391
  `;
@@ -350,7 +413,7 @@ function resolvePiHomeDir(inputHomeDir) {
350
413
  function piListContainsPiRig(output) {
351
414
  return output.split(/\r?\n/).some((line) => {
352
415
  const normalized = line.trim();
353
- return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
416
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
354
417
  });
355
418
  }
356
419
  async function safeRun(runner, command, options) {
@@ -466,7 +529,7 @@ async function ensureRemotePiRigInstalled(input) {
466
529
  const payload = await input.requestJson("/api/pi-rig/install", {
467
530
  method: "POST",
468
531
  headers: { "content-type": "application/json" },
469
- body: JSON.stringify({ package: "@rig/pi-rig", scope: "global" })
532
+ body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
470
533
  });
471
534
  const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
472
535
  const piOk = record.piOk === true || record.ok === true;
@@ -727,7 +790,7 @@ async function runRigDoctorChecks(options) {
727
790
  const taskSourceKind = config?.taskSource?.kind;
728
791
  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
792
  const repo = readRepoConnection(projectRoot);
730
- checks.push(repo ? check("project-link", "repo selected Rig connection", 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 connection", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig connect use <alias|local>`."));
793
+ 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
794
  const selected = (() => {
732
795
  try {
733
796
  return resolveSelectedConnection(projectRoot);
@@ -735,7 +798,7 @@ async function runRigDoctorChecks(options) {
735
798
  return null;
736
799
  }
737
800
  })();
738
- checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server connection", repo ? "fail" : "warn", repo ? "selected alias is missing" : "will auto-start local server", repo ? "Run `rig connect list` and `rig connect use <alias|local>`." : undefined));
801
+ 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
802
  let server = null;
740
803
  try {
741
804
  server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
@@ -809,6 +872,7 @@ function countDoctorFailures(checks) {
809
872
 
810
873
  // packages/cli/src/commands/init.ts
811
874
  var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
875
+ var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
812
876
  var RIG_CONFIG_DEV_DEPENDENCIES = {
813
877
  "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
814
878
  "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
@@ -819,7 +883,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
819
883
  return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
820
884
  }
821
885
  function detectOriginRepoSlug(projectRoot) {
822
- const result = spawnSync2("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
886
+ const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
823
887
  if (result.status !== 0)
824
888
  return null;
825
889
  return parseRepoSlugFromRemote(result.stdout.trim());
@@ -875,11 +939,14 @@ function applyGitHubProjectConfig(source, options) {
875
939
  return source;
876
940
  const projectId = JSON.stringify(options.githubProject);
877
941
  const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
942
+ const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
943
+ statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
944
+ `)},` : "";
878
945
  return source.replace(` projects: { enabled: false },`, [
879
946
  ` projects: {`,
880
947
  ` enabled: true,`,
881
948
  ` projectId: ${projectId},`,
882
- ` statusFieldId: ${statusFieldId},`,
949
+ ` statusFieldId: ${statusFieldId},${statuses}`,
883
950
  ` },`
884
951
  ].join(`
885
952
  `));
@@ -900,21 +967,41 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
900
967
  }
901
968
  }
902
969
  function detectGhLogin() {
903
- const result = spawnSync2("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
970
+ const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
904
971
  return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
905
972
  }
906
973
  function readGhAuthToken() {
907
- const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
974
+ const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
908
975
  if (result.status !== 0 || !result.stdout.trim()) {
909
976
  throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
910
977
  }
911
978
  return result.stdout.trim();
912
979
  }
980
+ function refreshGhProjectScopesAndReadToken() {
981
+ const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
982
+ encoding: "utf8",
983
+ stdio: ["inherit", "pipe", "pipe"]
984
+ });
985
+ if (result.status !== 0)
986
+ return null;
987
+ try {
988
+ return readGhAuthToken();
989
+ } catch {
990
+ return null;
991
+ }
992
+ }
913
993
  async function loadClackPrompts() {
914
994
  return await import("@clack/prompts");
915
995
  }
996
+ function clackTextOptions(options) {
997
+ return {
998
+ message: options.message,
999
+ ...options.placeholder ? { placeholder: options.placeholder } : {},
1000
+ ...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
1001
+ };
1002
+ }
916
1003
  async function promptRequiredText(prompts, options) {
917
- const value = await prompts.text(options);
1004
+ const value = await prompts.text(clackTextOptions(options));
918
1005
  if (prompts.isCancel(value))
919
1006
  throw new CliError2("Init cancelled.", 1);
920
1007
  const text = String(value ?? "").trim();
@@ -923,7 +1010,7 @@ async function promptRequiredText(prompts, options) {
923
1010
  return text;
924
1011
  }
925
1012
  async function promptOptionalText(prompts, options) {
926
- const value = await prompts.text(options);
1013
+ const value = await prompts.text(clackTextOptions(options));
927
1014
  if (prompts.isCancel(value))
928
1015
  throw new CliError2("Init cancelled.", 1);
929
1016
  return String(value ?? "").trim();
@@ -934,6 +1021,164 @@ async function promptSelect(prompts, options) {
934
1021
  throw new CliError2("Init cancelled.", 1);
935
1022
  return String(value);
936
1023
  }
1024
+ function repoOwnerFromSlug(repoSlug) {
1025
+ return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
1026
+ }
1027
+ function recordArray(value, key) {
1028
+ if (!value || typeof value !== "object" || Array.isArray(value))
1029
+ return [];
1030
+ const raw = value[key];
1031
+ return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
1032
+ }
1033
+ async function listGitHubProjectsForInit(context, owner, token) {
1034
+ if (token?.trim()) {
1035
+ try {
1036
+ return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
1037
+ } catch (directError) {
1038
+ const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
1039
+ if (recordArray(serverPayload, "projects").length > 0)
1040
+ return serverPayload;
1041
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
1042
+ }
1043
+ }
1044
+ return listGitHubProjectsViaServer(context, owner);
1045
+ }
1046
+ async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
1047
+ if (token?.trim()) {
1048
+ try {
1049
+ return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
1050
+ } catch (directError) {
1051
+ const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
1052
+ if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
1053
+ return serverPayload;
1054
+ }
1055
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
1056
+ }
1057
+ }
1058
+ return getGitHubProjectStatusFieldViaServer(context, projectId);
1059
+ }
1060
+ var PROJECT_STATUS_PROMPTS = {
1061
+ running: "Running/In progress",
1062
+ prOpen: "PR open/review",
1063
+ ciFixing: "CI/review fixing",
1064
+ merging: "Merging",
1065
+ done: "Done",
1066
+ needsAttention: "Needs attention"
1067
+ };
1068
+ var DEFAULT_PROJECT_STATUS_OPTIONS = {
1069
+ running: "In Progress",
1070
+ prOpen: "In Review",
1071
+ ciFixing: "In Review",
1072
+ merging: "Merging",
1073
+ done: "Done",
1074
+ needsAttention: "Needs Attention"
1075
+ };
1076
+ async function promptManualProjectStatusMapping(prompts) {
1077
+ const statuses = {};
1078
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
1079
+ const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
1080
+ const value = await promptOptionalText(prompts, {
1081
+ message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
1082
+ placeholder: defaultLabel
1083
+ });
1084
+ statuses[key] = value || defaultLabel;
1085
+ }
1086
+ return statuses;
1087
+ }
1088
+ function projectScopeError(value) {
1089
+ const text = typeof value === "string" ? value : JSON.stringify(value ?? "");
1090
+ return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text);
1091
+ }
1092
+ function optionName(option) {
1093
+ return String(option.name ?? option.label ?? option.id ?? "").trim();
1094
+ }
1095
+ function autoProjectStatusValue(options, key, label) {
1096
+ const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
1097
+ const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
1098
+ if (!match)
1099
+ return null;
1100
+ return String(match.id ?? match.name);
1101
+ }
1102
+ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
1103
+ const projectChoice = await promptSelect(prompts, {
1104
+ message: "GitHub Projects status sync",
1105
+ initialValue: "select",
1106
+ options: [
1107
+ { value: "select", label: "Select accessible ProjectV2" },
1108
+ { value: "off", label: "Off" },
1109
+ { value: "manual", label: "Enter ProjectV2 ids manually" }
1110
+ ]
1111
+ });
1112
+ if (projectChoice === "off")
1113
+ return { githubProject: "off" };
1114
+ if (projectChoice === "manual") {
1115
+ return {
1116
+ githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
1117
+ githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
1118
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
1119
+ };
1120
+ }
1121
+ const owner = repoOwnerFromSlug(repoSlug);
1122
+ if (!owner)
1123
+ throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
1124
+ let activeToken = githubToken?.trim() || null;
1125
+ let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
1126
+ let projects = recordArray(projectsPayload, "projects");
1127
+ if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
1128
+ prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
1129
+ const refreshedToken = refreshProjectToken();
1130
+ if (refreshedToken) {
1131
+ activeToken = refreshedToken;
1132
+ projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
1133
+ projects = recordArray(projectsPayload, "projects");
1134
+ }
1135
+ }
1136
+ if (projects.length === 0) {
1137
+ const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
1138
+ prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
1139
+ return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
1140
+ }
1141
+ const selectedProjectId = await promptSelect(prompts, {
1142
+ message: "GitHub ProjectV2 project",
1143
+ options: [
1144
+ ...projects.map((project) => ({
1145
+ value: String(project.id),
1146
+ label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
1147
+ hint: typeof project.url === "string" ? project.url : undefined
1148
+ })),
1149
+ { value: "manual", label: "Enter ProjectV2 id manually" }
1150
+ ]
1151
+ });
1152
+ const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
1153
+ const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
1154
+ const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
1155
+ const rawField = fieldPayloadRecord.field;
1156
+ const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
1157
+ const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
1158
+ const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
1159
+ if (options.length === 0) {
1160
+ return {
1161
+ githubProject: projectId,
1162
+ githubProjectStatusField: fieldId,
1163
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
1164
+ ...activeToken ? { githubToken: activeToken } : {}
1165
+ };
1166
+ }
1167
+ const statuses = {};
1168
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
1169
+ const auto = autoProjectStatusValue(options, key, label);
1170
+ statuses[key] = auto ?? await promptSelect(prompts, {
1171
+ message: `Project status option for ${label}`,
1172
+ options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
1173
+ });
1174
+ }
1175
+ return {
1176
+ githubProject: projectId,
1177
+ githubProjectStatusField: fieldId,
1178
+ githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
1179
+ ...activeToken ? { githubToken: activeToken } : {}
1180
+ };
1181
+ }
937
1182
  function sleep2(ms) {
938
1183
  return new Promise((resolve7) => setTimeout(resolve7, ms));
939
1184
  }
@@ -947,12 +1192,29 @@ function apiSessionTokenFrom(payload) {
947
1192
  const token = payload.apiSessionToken;
948
1193
  return typeof token === "string" && token.trim() ? token.trim() : null;
949
1194
  }
1195
+ function cleanPayloadString(value) {
1196
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1197
+ }
1198
+ function remoteGitHubAuthMetadata(payload) {
1199
+ if (!payload)
1200
+ return {};
1201
+ const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
1202
+ return {
1203
+ ...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
1204
+ ...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
1205
+ ...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
1206
+ ...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
1207
+ ...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
1208
+ ...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
1209
+ };
1210
+ }
950
1211
  function writeRemoteGitHubAuthState(projectRoot, input) {
951
1212
  writeFileSync2(resolve6(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
952
1213
  authenticated: true,
953
1214
  source: input.source,
954
1215
  storedOnServer: true,
955
1216
  selectedRepo: input.selectedRepo,
1217
+ ...remoteGitHubAuthMetadata(input.authPayload ?? null),
956
1218
  ...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
957
1219
  updatedAt: new Date().toISOString()
958
1220
  }, null, 2)}
@@ -983,11 +1245,48 @@ async function pollDeviceAuthUntilComplete(context, pollId, firstPayload) {
983
1245
  } while (Date.now() < deadline);
984
1246
  return last;
985
1247
  }
1248
+ function runLocalFilesInit(context, options) {
1249
+ const projectRoot = context.projectRoot;
1250
+ ensureRigPrivateDirs(projectRoot);
1251
+ ensureGitignoreEntries(projectRoot);
1252
+ writeRepoConnection(projectRoot, { selected: "local", linkedAt: new Date().toISOString() });
1253
+ const configTsPath = resolve6(projectRoot, "rig.config.ts");
1254
+ const configExists = existsSync5(configTsPath) || existsSync5(resolve6(projectRoot, "rig.config.json"));
1255
+ if (configExists && !options.repair) {
1256
+ if (context.outputMode !== "json")
1257
+ console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
1258
+ } else {
1259
+ const projectName = basename(projectRoot) || "rig-project";
1260
+ writeFileSync2(configTsPath, buildRigInitConfigSource({
1261
+ projectName,
1262
+ taskSource: { kind: "files", path: "tasks" },
1263
+ useStandardPlugin: true
1264
+ }), "utf-8");
1265
+ }
1266
+ ensureRigConfigPackageDependencies(projectRoot);
1267
+ const tasksDir = resolve6(projectRoot, "tasks");
1268
+ if (!existsSync5(tasksDir)) {
1269
+ mkdirSync2(tasksDir, { recursive: true });
1270
+ writeFileSync2(resolve6(tasksDir, "T-1.json"), `${JSON.stringify({ id: "T-1", title: "My first Rig task", body: "Describe the change you want an agent to make." }, null, 2)}
1271
+ `, "utf-8");
1272
+ }
1273
+ if (context.outputMode !== "json") {
1274
+ console.log("Initialized a local files-source Rig project (no GitHub).");
1275
+ console.log(" tasks live in tasks/*.json \xB7 server: local");
1276
+ console.log("Next: `rig task list`, then `rig task run T-1`.");
1277
+ console.log("To wire GitHub later: `rig init --repair --repo owner/repo --github-auth gh`.");
1278
+ }
1279
+ return { ok: true, group: "init", command: "init", details: { mode: "local-files", projectRoot, taskSourcePath: "tasks" } };
1280
+ }
986
1281
  async function runControlPlaneInit(context, options) {
987
1282
  const projectRoot = context.projectRoot;
988
1283
  const detectedSlug = options.repoSlug ?? detectOriginRepoSlug(projectRoot);
989
1284
  if (!detectedSlug) {
990
- throw new CliError2("Could not detect GitHub repo slug from origin. Pass --repo owner/repo.", 1);
1285
+ const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
1286
+ if ((options.server ?? "local") === "local" && authMethod2 === "skip") {
1287
+ return runLocalFilesInit(context, options);
1288
+ }
1289
+ throw new CliError2("Could not detect GitHub repo slug from origin. Pass --repo owner/repo \u2014 or run `rig init --yes --server local --github-auth skip` for a local files-source project without GitHub.", 1);
991
1290
  }
992
1291
  const repo = parseRepoSlug(detectedSlug);
993
1292
  const serverKind = options.server ?? "local";
@@ -1045,12 +1344,13 @@ async function runControlPlaneInit(context, options) {
1045
1344
  if (token) {
1046
1345
  githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
1047
1346
  const apiSessionToken = apiSessionTokenFrom(githubAuth);
1048
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
1347
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
1049
1348
  if (serverKind === "remote") {
1050
1349
  writeRemoteGitHubAuthState(projectRoot, {
1051
1350
  source: authMethod === "gh" ? "gh" : "init-token",
1052
1351
  selectedRepo: repo.slug,
1053
- apiSessionToken
1352
+ apiSessionToken,
1353
+ authPayload: githubAuth
1054
1354
  });
1055
1355
  }
1056
1356
  } else if (authMethod === "device") {
@@ -1069,9 +1369,9 @@ async function runControlPlaneInit(context, options) {
1069
1369
  if (completed) {
1070
1370
  const apiSessionToken = apiSessionTokenFrom(completed);
1071
1371
  if (apiSessionToken) {
1072
- setGitHubBearerTokenForCurrentProcess(apiSessionToken);
1372
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
1073
1373
  if (serverKind === "remote") {
1074
- writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
1374
+ writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
1075
1375
  }
1076
1376
  }
1077
1377
  deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
@@ -1089,19 +1389,25 @@ async function runControlPlaneInit(context, options) {
1089
1389
  Object.assign(checkout, preparedCheckout);
1090
1390
  }
1091
1391
  }
1392
+ const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
1393
+ if (serverKind === "remote" && checkoutPath && token) {
1394
+ githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
1395
+ const apiSessionToken = apiSessionTokenFrom(githubAuth);
1396
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
1397
+ writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
1398
+ }
1092
1399
  const registered = await registerProjectViaServer(context, {
1093
1400
  repoSlug: repo.slug,
1094
1401
  checkout
1095
1402
  });
1096
- const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
1097
1403
  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
1404
  const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
1405
+ const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
1406
+ ok: false,
1407
+ ready: false,
1408
+ labelsReady: false,
1409
+ error: error instanceof Error ? error.message : String(error)
1410
+ }));
1105
1411
  const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
1106
1412
  remote: true,
1107
1413
  pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
@@ -1132,6 +1438,7 @@ async function runControlPlaneInit(context, options) {
1132
1438
  githubAuth,
1133
1439
  deviceAuth,
1134
1440
  githubAuthWarning: remoteGhTokenWarning,
1441
+ labelSetup,
1135
1442
  pi,
1136
1443
  doctor
1137
1444
  };
@@ -1238,12 +1545,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
1238
1545
  });
1239
1546
  const serverChoice = await promptSelect(prompts, {
1240
1547
  message: "Rig server",
1548
+ initialValue: "remote",
1241
1549
  options: [
1242
- { value: "local", label: "Local server", hint: "run on this machine" },
1243
- { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" }
1550
+ { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
1551
+ { value: "local", label: "Local server", hint: "run on this machine" }
1244
1552
  ]
1245
1553
  });
1246
- const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: "https://rig.example.com" }) : undefined;
1554
+ const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
1247
1555
  let remoteCheckout;
1248
1556
  if (serverChoice === "remote") {
1249
1557
  const checkout = await promptSelect(prompts, {
@@ -1275,38 +1583,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
1275
1583
  { value: "skip", label: "Skip for now" }
1276
1584
  ]
1277
1585
  });
1586
+ let remoteGhTokenConfirmed = false;
1278
1587
  if (serverChoice === "remote" && authMethod === "gh") {
1279
1588
  if (!prompts.confirm)
1280
1589
  throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
1281
1590
  const confirmed = await prompts.confirm({
1282
1591
  message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
1283
- initialValue: false
1592
+ initialValue: true
1284
1593
  });
1285
1594
  if (prompts.isCancel(confirmed) || confirmed !== true) {
1286
1595
  throw new CliError2("Remote gh-token import cancelled.", 1);
1287
1596
  }
1597
+ remoteGhTokenConfirmed = true;
1288
1598
  }
1289
- const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
1290
- const projectChoice = await promptSelect(prompts, {
1291
- message: "GitHub Projects status sync",
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;
1599
+ const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
1600
+ const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
1601
+ const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
1299
1602
  const result = await runControlPlaneInit(context, {
1300
1603
  server: serverChoice,
1301
1604
  remoteUrl,
1302
1605
  repoSlug,
1303
- githubToken,
1606
+ githubToken: effectiveGithubToken,
1304
1607
  githubAuthMethod: authMethod,
1305
- githubProject,
1306
- githubProjectStatusField,
1608
+ githubProject: projectConfig.githubProject,
1609
+ githubProjectStatusField: projectConfig.githubProjectStatusField,
1610
+ githubProjectStatuses: projectConfig.githubProjectStatuses,
1307
1611
  remoteCheckout,
1308
1612
  repair,
1309
- privateStateOnly
1613
+ privateStateOnly,
1614
+ yes: remoteGhTokenConfirmed || undefined
1310
1615
  });
1311
1616
  const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
1312
1617
  const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
@@ -1325,6 +1630,9 @@ Usage: rig init [--server local|remote] [--remote-url <url>] [--repo owner/repo]
1325
1630
  if (parsed.rest.length > 0)
1326
1631
  throw new CliError2(`Unexpected arguments: ${parsed.rest.join(" ")}
1327
1632
  Usage: rig init`, 1);
1633
+ if (!process.stdin.isTTY) {
1634
+ throw new CliError2("rig init is interactive and needs a terminal. For scripts, pass flags: rig init --yes --server local --github-auth skip [--repo owner/repo].", 1);
1635
+ }
1328
1636
  return runInteractiveControlPlaneInit(context, await loadClackPrompts());
1329
1637
  }
1330
1638
  export {