@h-rig/cli 0.0.6-alpha.3 → 0.0.6-alpha.30

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 (47) hide show
  1. package/dist/bin/rig.js +3606 -1172
  2. package/dist/src/commands/_authority-runs.js +1 -0
  3. package/dist/src/commands/_cli-format.js +369 -0
  4. package/dist/src/commands/_connection-state.js +1 -3
  5. package/dist/src/commands/_doctor-checks.js +13 -27
  6. package/dist/src/commands/_help-catalog.js +388 -0
  7. package/dist/src/commands/_operator-surface.js +204 -0
  8. package/dist/src/commands/_operator-view.js +861 -56
  9. package/dist/src/commands/_parsers.js +0 -2
  10. package/dist/src/commands/_pi-frontend.js +841 -0
  11. package/dist/src/commands/_pi-install.js +4 -3
  12. package/dist/src/commands/_pi-worker-bridge-extension.js +759 -0
  13. package/dist/src/commands/_policy.js +0 -2
  14. package/dist/src/commands/_preflight.js +32 -109
  15. package/dist/src/commands/_run-driver-helpers.js +0 -2
  16. package/dist/src/commands/_server-client.js +161 -31
  17. package/dist/src/commands/_snapshot-upload.js +8 -23
  18. package/dist/src/commands/_task-picker.js +44 -16
  19. package/dist/src/commands/agent.js +9 -9
  20. package/dist/src/commands/browser.js +4 -6
  21. package/dist/src/commands/connect.js +132 -25
  22. package/dist/src/commands/dist.js +4 -6
  23. package/dist/src/commands/doctor.js +13 -27
  24. package/dist/src/commands/github.js +10 -25
  25. package/dist/src/commands/inbox.js +351 -31
  26. package/dist/src/commands/init.js +298 -71
  27. package/dist/src/commands/inspect.js +10 -12
  28. package/dist/src/commands/inspector.js +2 -4
  29. package/dist/src/commands/plugin.js +76 -22
  30. package/dist/src/commands/profile-and-review.js +8 -10
  31. package/dist/src/commands/queue.js +2 -3
  32. package/dist/src/commands/remote.js +18 -20
  33. package/dist/src/commands/repo-git-harness.js +6 -8
  34. package/dist/src/commands/run.js +1157 -122
  35. package/dist/src/commands/server.js +217 -33
  36. package/dist/src/commands/setup.js +17 -37
  37. package/dist/src/commands/task-report-bug.js +5 -7
  38. package/dist/src/commands/task-run-driver.js +660 -73
  39. package/dist/src/commands/task.js +1542 -252
  40. package/dist/src/commands/test.js +3 -5
  41. package/dist/src/commands/workspace.js +4 -6
  42. package/dist/src/commands.js +3599 -1159
  43. package/dist/src/index.js +3646 -1215
  44. package/dist/src/launcher.js +5 -3
  45. package/dist/src/report-bug.js +3 -3
  46. package/dist/src/runner.js +5 -19
  47. 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 as spawnSync2 } from "child_process";
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 connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
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 cachedGitHubBearerToken;
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
- cachedGitHubBearerToken = cleanToken(token ?? undefined);
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
- if (cachedGitHubBearerToken !== undefined)
181
- return cachedGitHubBearerToken;
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
- 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;
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: "@rig/pi-rig", scope: "global" })
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 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>`."));
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 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));
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 = spawnSync2("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
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 = spawnSync2("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
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 = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
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: "local", label: "Local server", hint: "run on this machine" },
1243
- { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" }
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: "https://rig.example.com" }) : undefined;
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: false
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 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;
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;