@h-rig/cli 0.0.6-alpha.2 → 0.0.6-alpha.21

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