@h-rig/cli 0.0.6-alpha.1 → 0.0.6-alpha.10

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.
@@ -2,9 +2,9 @@
2
2
  var __require = import.meta.require;
3
3
 
4
4
  // packages/cli/src/commands/init.ts
5
- import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
5
+ import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
6
6
  import { spawnSync as spawnSync2 } from "child_process";
7
- import { resolve as resolve5 } from "path";
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";
@@ -154,6 +154,8 @@ function resolveSelectedConnection(projectRoot, options = {}) {
154
154
 
155
155
  // packages/cli/src/commands/_server-client.ts
156
156
  import { spawnSync } from "child_process";
157
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
158
+ import { resolve as resolve2 } from "path";
157
159
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
158
160
  var cachedGitHubBearerToken;
159
161
  function cleanToken(value) {
@@ -163,9 +165,25 @@ function cleanToken(value) {
163
165
  function setGitHubBearerTokenForCurrentProcess(token) {
164
166
  cachedGitHubBearerToken = cleanToken(token ?? undefined);
165
167
  }
166
- function readGitHubBearerTokenForRemote() {
168
+ function readPrivateRemoteSessionToken(projectRoot) {
169
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
170
+ if (!existsSync2(path))
171
+ return null;
172
+ try {
173
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
174
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
179
+ function readGitHubBearerTokenForRemote(projectRoot) {
167
180
  if (cachedGitHubBearerToken !== undefined)
168
181
  return cachedGitHubBearerToken;
182
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
183
+ if (privateSession) {
184
+ cachedGitHubBearerToken = privateSession;
185
+ return cachedGitHubBearerToken;
186
+ }
169
187
  const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
170
188
  if (envToken) {
171
189
  cachedGitHubBearerToken = envToken;
@@ -185,7 +203,7 @@ async function ensureServerForCli(projectRoot) {
185
203
  if (selected?.connection.kind === "remote") {
186
204
  return {
187
205
  baseUrl: selected.connection.baseUrl,
188
- authToken: readGitHubBearerTokenForRemote(),
206
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
189
207
  connectionKind: "remote"
190
208
  };
191
209
  }
@@ -249,7 +267,7 @@ async function postGitHubTokenViaServer(context, token, options = {}) {
249
267
  const payload = await requestServerJson(context, "/api/github/auth/token", {
250
268
  method: "POST",
251
269
  headers: { "content-type": "application/json" },
252
- body: JSON.stringify({ token, selectedRepo: options.selectedRepo })
270
+ body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot })
253
271
  });
254
272
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
255
273
  }
@@ -270,7 +288,7 @@ async function registerProjectViaServer(context, input) {
270
288
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
271
289
  }
272
290
  function sleep(ms) {
273
- return new Promise((resolve2) => setTimeout(resolve2, ms));
291
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
274
292
  }
275
293
  async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
276
294
  const switched = await requestServerJson(context, "/api/server/project-root", {
@@ -301,9 +319,9 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
301
319
  }
302
320
 
303
321
  // packages/cli/src/commands/_pi-install.ts
304
- import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync } from "fs";
322
+ import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
305
323
  import { homedir as homedir2 } from "os";
306
- import { resolve as resolve2 } from "path";
324
+ import { resolve as resolve3 } from "path";
307
325
  var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
308
326
  var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
309
327
  export { default } from '@rig/pi-rig';
@@ -318,11 +336,11 @@ async function defaultCommandRunner(command, options = {}) {
318
336
  return { exitCode, stdout, stderr };
319
337
  }
320
338
  function resolvePiRigExtensionPath(homeDir) {
321
- return resolve2(homeDir, ".pi", "agent", "extensions", "pi-rig");
339
+ return resolve3(homeDir, ".pi", "agent", "extensions", "pi-rig");
322
340
  }
323
- function resolvePiRigPackageSource(projectRoot, exists = existsSync2) {
324
- const localPackage = resolve2(projectRoot, "packages", "pi-rig");
325
- if (exists(resolve2(localPackage, "package.json")))
341
+ function resolvePiRigPackageSource(projectRoot, exists = existsSync3) {
342
+ const localPackage = resolve3(projectRoot, "packages", "pi-rig");
343
+ if (exists(resolve3(localPackage, "package.json")))
326
344
  return localPackage;
327
345
  return `npm:${PI_RIG_PACKAGE_NAME}`;
328
346
  }
@@ -373,13 +391,13 @@ async function ensurePiBinaryAvailable(input) {
373
391
  ...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
374
392
  };
375
393
  }
376
- function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync2) {
394
+ function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync3) {
377
395
  const extensionPath = resolvePiRigExtensionPath(homeDir);
378
- const indexPath = resolve2(extensionPath, "index.ts");
396
+ const indexPath = resolve3(extensionPath, "index.ts");
379
397
  if (!exists(indexPath))
380
398
  return;
381
399
  try {
382
- const content = readFileSync2(indexPath, "utf8");
400
+ const content = readFileSync3(indexPath, "utf8");
383
401
  if (content === LEGACY_PI_RIG_MARKER || content.includes("Managed by Rig. Source package: @rig/pi-rig")) {
384
402
  rmSync(extensionPath, { recursive: true, force: true });
385
403
  }
@@ -395,13 +413,13 @@ async function checkPiRigInstall(input = {}) {
395
413
  piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
396
414
  };
397
415
  }
398
- const exists = input.exists ?? existsSync2;
416
+ const exists = input.exists ?? existsSync3;
399
417
  const runner = input.commandRunner ?? defaultCommandRunner;
400
418
  const piResult = await safeRun(runner, ["pi", "--version"]);
401
419
  const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
402
420
  const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
403
421
  ${piListResult.stderr}`);
404
- const legacyBridge = exists(resolve2(extensionPath, "index.ts"));
422
+ const legacyBridge = exists(resolve3(extensionPath, "index.ts"));
405
423
  const hasPiRig = listedPiRig;
406
424
  return {
407
425
  extensionPath,
@@ -478,7 +496,7 @@ async function buildPiSetupChecks(input = {}) {
478
496
 
479
497
  // packages/cli/src/commands/_snapshot-upload.ts
480
498
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
481
- import { dirname as dirname2, resolve as resolve3, relative, sep } from "path";
499
+ import { dirname as dirname2, resolve as resolve4, relative, sep } from "path";
482
500
  var SNAPSHOT_ARCHIVE_VERSION = 1;
483
501
  var SNAPSHOT_ARCHIVE_CONTENT_TYPE = "application/vnd.rig.snapshot+json";
484
502
  var DEFAULT_EXCLUDED_DIRECTORIES = new Set([
@@ -500,15 +518,15 @@ function assertManifestPath(root, relativePath) {
500
518
  if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\x00")) {
501
519
  throw new Error(`Invalid snapshot path: ${relativePath}`);
502
520
  }
503
- const resolved = resolve3(root, relativePath);
521
+ const resolved = resolve4(root, relativePath);
504
522
  const relativeToRoot = relative(root, resolved);
505
- if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || resolve3(relativeToRoot) === resolved) {
523
+ if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || resolve4(relativeToRoot) === resolved) {
506
524
  throw new Error(`Snapshot path escapes project root: ${relativePath}`);
507
525
  }
508
526
  return resolved;
509
527
  }
510
528
  async function buildSnapshotUploadManifest(projectRoot, options = {}) {
511
- const root = resolve3(projectRoot);
529
+ const root = resolve4(projectRoot);
512
530
  const excludedDirectories = [...new Set([
513
531
  ...DEFAULT_EXCLUDED_DIRECTORIES,
514
532
  ...options.excludedDirectories ?? []
@@ -520,7 +538,7 @@ async function buildSnapshotUploadManifest(projectRoot, options = {}) {
520
538
  for (const entry of entries) {
521
539
  if (entry.isDirectory() && excludedSet.has(entry.name))
522
540
  continue;
523
- const fullPath = resolve3(dir, entry.name);
541
+ const fullPath = resolve4(dir, entry.name);
524
542
  if (entry.isDirectory()) {
525
543
  await visit(fullPath);
526
544
  continue;
@@ -568,8 +586,8 @@ async function uploadSnapshotArchiveViaServer(context, input) {
568
586
  }
569
587
 
570
588
  // packages/cli/src/commands/_doctor-checks.ts
571
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
572
- import { resolve as resolve4 } from "path";
589
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
590
+ import { resolve as resolve5 } from "path";
573
591
  import { isSupportedBunVersion, MIN_SUPPORTED_BUN_VERSION } from "@rig/runtime/control-plane/setup-version";
574
592
 
575
593
  // packages/cli/src/commands/_parsers.ts
@@ -621,11 +639,11 @@ function repoSlugFromConfig(config) {
621
639
  function loadFallbackConfig(projectRoot) {
622
640
  const candidates = ["rig.config.ts", "rig.config.mts", "rig.config.json"];
623
641
  for (const name of candidates) {
624
- const path = resolve4(projectRoot, name);
625
- if (!existsSync3(path))
642
+ const path = resolve5(projectRoot, name);
643
+ if (!existsSync4(path))
626
644
  continue;
627
645
  try {
628
- const source = readFileSync3(path, "utf8");
646
+ const source = readFileSync4(path, "utf8");
629
647
  if (name.endsWith(".json"))
630
648
  return JSON.parse(source);
631
649
  const owner = source.match(/owner\s*:\s*["']([^"']+)["']/)?.[1];
@@ -704,7 +722,7 @@ async function runRigDoctorChecks(options) {
704
722
  checks.push(check("bun", `bun >= ${MIN_SUPPORTED_BUN_VERSION}`, isSupportedBunVersion(bunVersion) ? "pass" : "fail", `found ${bunVersion}`, `Install Bun ${MIN_SUPPORTED_BUN_VERSION} or newer.`), check("git", "git", which("git") ? "pass" : "fail", which("git") ?? undefined, "Install git and ensure it is on PATH."), check("jq", "jq", which("jq") ? "pass" : "warn", which("jq") ?? undefined, "Install jq (for example `brew install jq`)."));
705
723
  const loadedConfig = await loadConfig(projectRoot).catch(() => null);
706
724
  const config = loadedConfig ?? loadFallbackConfig(projectRoot);
707
- const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync3(resolve4(projectRoot, name)));
725
+ const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
708
726
  checks.push(config ? check("config", "rig.config loadable", "pass") : check("config", "rig.config loadable", hasConfigFile ? "fail" : "fail", hasConfigFile ? "config file exists but failed to load" : "missing rig.config.ts/json", "Run `rig init` or fix the config error."));
709
727
  const taskSourceKind = config?.taskSource?.kind;
710
728
  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."));
@@ -790,10 +808,10 @@ function countDoctorFailures(checks) {
790
808
  }
791
809
 
792
810
  // packages/cli/src/commands/init.ts
793
- var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.1";
811
+ var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
794
812
  var RIG_CONFIG_DEV_DEPENDENCIES = {
795
- "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_VERSION}`,
796
- "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_VERSION}`
813
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
814
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
797
815
  };
798
816
  function parseRepoSlugFromRemote(remoteUrl) {
799
817
  const trimmed = remoteUrl.trim();
@@ -813,20 +831,20 @@ function parseRepoSlug(value) {
813
831
  return { owner: match[1], repo: match[2], slug: `${match[1]}/${match[2]}` };
814
832
  }
815
833
  function ensureRigPrivateDirs(projectRoot) {
816
- const rigDir = resolve5(projectRoot, ".rig");
817
- mkdirSync2(resolve5(rigDir, "state"), { recursive: true });
818
- mkdirSync2(resolve5(rigDir, "logs"), { recursive: true });
819
- mkdirSync2(resolve5(rigDir, "runs"), { recursive: true });
820
- mkdirSync2(resolve5(rigDir, "tmp"), { recursive: true });
821
- mkdirSync2(resolve5(projectRoot, "artifacts"), { recursive: true });
822
- const taskConfigPath = resolve5(rigDir, "task-config.json");
823
- if (!existsSync4(taskConfigPath))
834
+ const rigDir = resolve6(projectRoot, ".rig");
835
+ mkdirSync2(resolve6(rigDir, "state"), { recursive: true });
836
+ mkdirSync2(resolve6(rigDir, "logs"), { recursive: true });
837
+ mkdirSync2(resolve6(rigDir, "runs"), { recursive: true });
838
+ mkdirSync2(resolve6(rigDir, "tmp"), { recursive: true });
839
+ mkdirSync2(resolve6(projectRoot, "artifacts"), { recursive: true });
840
+ const taskConfigPath = resolve6(rigDir, "task-config.json");
841
+ if (!existsSync5(taskConfigPath))
824
842
  writeFileSync2(taskConfigPath, `{}
825
843
  `, "utf-8");
826
844
  }
827
845
  function ensureGitignoreEntries(projectRoot) {
828
- const path = resolve5(projectRoot, ".gitignore");
829
- const existing = existsSync4(path) ? readFileSync4(path, "utf8") : "";
846
+ const path = resolve6(projectRoot, ".gitignore");
847
+ const existing = existsSync5(path) ? readFileSync5(path, "utf8") : "";
830
848
  const entries = [".rig/state/", ".rig/logs/", ".rig/runs/", ".rig/tmp/"];
831
849
  const missing = entries.filter((entry) => !existing.split(/\r?\n/).includes(entry));
832
850
  if (missing.length === 0)
@@ -839,14 +857,14 @@ function ensureGitignoreEntries(projectRoot) {
839
857
  `, "utf8");
840
858
  }
841
859
  function ensureRigConfigPackageDependencies(projectRoot) {
842
- const path = resolve5(projectRoot, "package.json");
843
- const existing = existsSync4(path) ? JSON.parse(readFileSync4(path, "utf8")) : {};
860
+ const path = resolve6(projectRoot, "package.json");
861
+ const existing = existsSync5(path) ? JSON.parse(readFileSync5(path, "utf8")) : {};
844
862
  const devDependencies = existing.devDependencies && typeof existing.devDependencies === "object" && !Array.isArray(existing.devDependencies) ? { ...existing.devDependencies } : {};
845
863
  for (const [name, spec] of Object.entries(RIG_CONFIG_DEV_DEPENDENCIES)) {
846
864
  devDependencies[name] = spec;
847
865
  }
848
866
  const next = {
849
- ...existsSync4(path) ? existing : { name: "rig-project", private: true },
867
+ ...existsSync5(path) ? existing : { name: "rig-project", private: true },
850
868
  devDependencies
851
869
  };
852
870
  writeFileSync2(path, `${JSON.stringify(next, null, 2)}
@@ -916,15 +934,54 @@ async function promptSelect(prompts, options) {
916
934
  throw new CliError2("Init cancelled.", 1);
917
935
  return String(value);
918
936
  }
919
- async function pollDeviceAuthOnce(context, pollId) {
937
+ function sleep2(ms) {
938
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
939
+ }
940
+ function positiveIntFromEnv(name, fallback) {
941
+ const value = Number.parseInt(process.env[name] ?? "", 10);
942
+ return Number.isFinite(value) && value >= 0 ? value : fallback;
943
+ }
944
+ function apiSessionTokenFrom(payload) {
945
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
946
+ return null;
947
+ const token = payload.apiSessionToken;
948
+ return typeof token === "string" && token.trim() ? token.trim() : null;
949
+ }
950
+ function writeRemoteGitHubAuthState(projectRoot, input) {
951
+ writeFileSync2(resolve6(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
952
+ authenticated: true,
953
+ source: input.source,
954
+ storedOnServer: true,
955
+ selectedRepo: input.selectedRepo,
956
+ ...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
957
+ updatedAt: new Date().toISOString()
958
+ }, null, 2)}
959
+ `, "utf8");
960
+ }
961
+ async function pollDeviceAuthUntilComplete(context, pollId, firstPayload) {
920
962
  if (typeof pollId !== "string" || !pollId.trim())
921
963
  return null;
922
- const payload = await requestServerJson(context, "/api/github/auth/device/poll", {
923
- method: "POST",
924
- headers: { "content-type": "application/json" },
925
- body: JSON.stringify({ pollId })
926
- }).catch(() => null);
927
- return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : null;
964
+ const intervalSeconds = typeof firstPayload.interval === "number" && Number.isFinite(firstPayload.interval) && firstPayload.interval > 0 ? firstPayload.interval : 5;
965
+ const timeoutMs = positiveIntFromEnv("RIG_DEVICE_AUTH_POLL_TIMEOUT_MS", 300000);
966
+ const intervalMs = positiveIntFromEnv("RIG_DEVICE_AUTH_POLL_INTERVAL_MS", Math.max(1000, intervalSeconds * 1000));
967
+ const deadline = Date.now() + timeoutMs;
968
+ let last = null;
969
+ do {
970
+ const payload = await requestServerJson(context, "/api/github/auth/device/poll", {
971
+ method: "POST",
972
+ headers: { "content-type": "application/json" },
973
+ body: JSON.stringify({ pollId })
974
+ }).catch(() => null);
975
+ last = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : null;
976
+ const status = typeof last?.status === "string" ? last.status : null;
977
+ if (status === "signed-in" || status === "expired" || status === "cancelled" || status === "failed") {
978
+ return last;
979
+ }
980
+ if (timeoutMs <= 0)
981
+ return last;
982
+ await sleep2(intervalMs);
983
+ } while (Date.now() < deadline);
984
+ return last;
928
985
  }
929
986
  async function runControlPlaneInit(context, options) {
930
987
  const projectRoot = context.projectRoot;
@@ -947,9 +1004,9 @@ async function runControlPlaneInit(context, options) {
947
1004
  });
948
1005
  ensureRigPrivateDirs(projectRoot);
949
1006
  ensureGitignoreEntries(projectRoot);
950
- const configTsPath = resolve5(projectRoot, "rig.config.ts");
951
- const configJsonPath = resolve5(projectRoot, "rig.config.json");
952
- const configExists = existsSync4(configTsPath) || existsSync4(configJsonPath);
1007
+ const configTsPath = resolve6(projectRoot, "rig.config.ts");
1008
+ const configJsonPath = resolve6(projectRoot, "rig.config.json");
1009
+ const configExists = existsSync5(configTsPath) || existsSync5(configJsonPath);
953
1010
  if (!options.privateStateOnly) {
954
1011
  if (configExists && !options.repair) {
955
1012
  if (context.outputMode !== "json")
@@ -965,7 +1022,7 @@ async function runControlPlaneInit(context, options) {
965
1022
  }
966
1023
  ensureRigConfigPackageDependencies(projectRoot);
967
1024
  }
968
- writeFileSync2(resolve5(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
1025
+ writeFileSync2(resolve6(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
969
1026
  `, "utf8");
970
1027
  const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
971
1028
  let uploadedSnapshot = null;
@@ -987,10 +1044,14 @@ async function runControlPlaneInit(context, options) {
987
1044
  const token = authMethod === "gh" && !options.githubToken ? readGhAuthToken() : options.githubToken?.trim();
988
1045
  if (token) {
989
1046
  githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
990
- setGitHubBearerTokenForCurrentProcess(token);
1047
+ const apiSessionToken = apiSessionTokenFrom(githubAuth);
1048
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
991
1049
  if (serverKind === "remote") {
992
- writeFileSync2(resolve5(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({ authenticated: true, source: authMethod === "gh" ? "gh" : "init-token", storedOnServer: true, selectedRepo: repo.slug, updatedAt: new Date().toISOString() }, null, 2)}
993
- `, "utf8");
1050
+ writeRemoteGitHubAuthState(projectRoot, {
1051
+ source: authMethod === "gh" ? "gh" : "init-token",
1052
+ selectedRepo: repo.slug,
1053
+ apiSessionToken
1054
+ });
994
1055
  }
995
1056
  } else if (authMethod === "device") {
996
1057
  const payload = await requestServerJson(context, "/api/github/auth/device/start", {
@@ -999,9 +1060,22 @@ async function runControlPlaneInit(context, options) {
999
1060
  body: JSON.stringify({ repoSlug: repo.slug })
1000
1061
  });
1001
1062
  deviceAuth = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
1002
- const completed = await pollDeviceAuthOnce(context, deviceAuth.pollId);
1003
- if (completed)
1063
+ if (context.outputMode !== "json") {
1064
+ const verificationUri = String(deviceAuth.verificationUri ?? deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by the server");
1065
+ const userCode = String(deviceAuth.userCode ?? deviceAuth.user_code ?? "the returned user code");
1066
+ console.log(`GitHub device flow: open ${verificationUri} and enter ${userCode}. Waiting for authorization...`);
1067
+ }
1068
+ const completed = await pollDeviceAuthUntilComplete(context, deviceAuth.pollId, deviceAuth);
1069
+ if (completed) {
1070
+ const apiSessionToken = apiSessionTokenFrom(completed);
1071
+ if (apiSessionToken) {
1072
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken);
1073
+ if (serverKind === "remote") {
1074
+ writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
1075
+ }
1076
+ }
1004
1077
  deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
1078
+ }
1005
1079
  }
1006
1080
  let remoteCheckoutPreparation = null;
1007
1081
  if (serverKind === "remote" && options.remoteCheckout?.kind !== "uploaded-snapshot") {
@@ -1021,6 +1095,12 @@ async function runControlPlaneInit(context, options) {
1021
1095
  });
1022
1096
  const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
1023
1097
  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
+ }
1024
1104
  const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
1025
1105
  const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
1026
1106
  remote: true,
@@ -1130,7 +1210,7 @@ function parseInitOptions(args) {
1130
1210
  async function runInteractiveControlPlaneInit(context, prompts) {
1131
1211
  prompts.intro?.("Initialize a Rig control-plane project");
1132
1212
  const projectRoot = context.projectRoot;
1133
- const existingConfig = existsSync4(resolve5(projectRoot, "rig.config.ts")) || existsSync4(resolve5(projectRoot, "rig.config.json"));
1213
+ const existingConfig = existsSync5(resolve6(projectRoot, "rig.config.ts")) || existsSync5(resolve6(projectRoot, "rig.config.json"));
1134
1214
  let repair = false;
1135
1215
  let privateStateOnly = false;
1136
1216
  if (existingConfig) {
@@ -1230,7 +1310,7 @@ async function runInteractiveControlPlaneInit(context, prompts) {
1230
1310
  });
1231
1311
  const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
1232
1312
  const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
1233
- const deviceMessage = deviceAuth ? ` GitHub device flow: open ${String(deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by the server")} and enter ${String(deviceAuth.user_code ?? "the returned user code")}.` : "";
1313
+ const deviceMessage = deviceAuth ? ` GitHub device flow: open ${String(deviceAuth.verificationUri ?? deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by the server")} and enter ${String(deviceAuth.userCode ?? deviceAuth.user_code ?? "the returned user code")}.` : "";
1234
1314
  prompts.outro?.(`Rig project initialized.${deviceMessage} Next: rig doctor && rig task list`);
1235
1315
  return result;
1236
1316
  }
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/run.ts
3
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
4
- import { resolve as resolve2 } from "path";
3
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
4
+ import { resolve as resolve3 } from "path";
5
5
  import { createInterface as createInterface2 } from "readline/promises";
6
6
 
7
7
  // packages/cli/src/runner.ts
@@ -64,6 +64,7 @@ import {
64
64
  listOpenEpics,
65
65
  resolveDefaultEpic,
66
66
  runResume,
67
+ runRestart,
67
68
  runStatus,
68
69
  runStop,
69
70
  startRun,
@@ -85,6 +86,8 @@ function parsePositiveInt(value, option, fallback) {
85
86
 
86
87
  // packages/cli/src/commands/_server-client.ts
87
88
  import { spawnSync } from "child_process";
89
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
90
+ import { resolve as resolve2 } from "path";
88
91
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
89
92
 
90
93
  // packages/cli/src/commands/_connection-state.ts
@@ -175,9 +178,25 @@ function cleanToken(value) {
175
178
  const trimmed = value?.trim();
176
179
  return trimmed ? trimmed : null;
177
180
  }
178
- function readGitHubBearerTokenForRemote() {
181
+ function readPrivateRemoteSessionToken(projectRoot) {
182
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
183
+ if (!existsSync2(path))
184
+ return null;
185
+ try {
186
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
187
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+ function readGitHubBearerTokenForRemote(projectRoot) {
179
193
  if (cachedGitHubBearerToken !== undefined)
180
194
  return cachedGitHubBearerToken;
195
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
196
+ if (privateSession) {
197
+ cachedGitHubBearerToken = privateSession;
198
+ return cachedGitHubBearerToken;
199
+ }
181
200
  const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
182
201
  if (envToken) {
183
202
  cachedGitHubBearerToken = envToken;
@@ -197,7 +216,7 @@ async function ensureServerForCli(projectRoot) {
197
216
  if (selected?.connection.kind === "remote") {
198
217
  return {
199
218
  baseUrl: selected.connection.baseUrl,
200
- authToken: readGitHubBearerTokenForRemote(),
219
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
201
220
  connectionKind: "remote"
202
221
  };
203
222
  }
@@ -388,6 +407,17 @@ async function attachRunOperatorView(context, input) {
388
407
  }
389
408
 
390
409
  // packages/cli/src/commands/run.ts
410
+ function normalizeRemoteRunDetails(payload) {
411
+ const run = payload.run;
412
+ if (!run || typeof run !== "object" || Array.isArray(run))
413
+ return null;
414
+ return {
415
+ ...run,
416
+ ...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
417
+ ...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
418
+ ...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
419
+ };
420
+ }
391
421
  function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
392
422
  if (noEpicPrompt) {
393
423
  return false;
@@ -526,7 +556,7 @@ async function executeRun(context, args) {
526
556
  if (!run.value) {
527
557
  throw new CliError2("run show requires --run <id>.");
528
558
  }
529
- const record = readAuthorityRun(context.projectRoot, run.value);
559
+ const record = readAuthorityRun(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
530
560
  if (!record) {
531
561
  throw new CliError2(`Run not found: ${run.value}`, 2);
532
562
  }
@@ -545,7 +575,7 @@ async function executeRun(context, args) {
545
575
  if (!run.value) {
546
576
  throw new CliError2("run timeline requires --run <id>.");
547
577
  }
548
- const timelinePath = resolve2(resolveAuthorityRunDir(context.projectRoot, run.value), "timeline.jsonl");
578
+ const timelinePath = resolve3(resolveAuthorityRunDir(context.projectRoot, run.value), "timeline.jsonl");
549
579
  const printEvents = () => {
550
580
  const events2 = readJsonlFile(timelinePath);
551
581
  if (context.outputMode === "text") {
@@ -557,12 +587,12 @@ async function executeRun(context, args) {
557
587
  };
558
588
  const events = printEvents();
559
589
  if (follow.value && context.outputMode === "text") {
560
- let lastLength = existsSync2(timelinePath) ? readFileSync2(timelinePath, "utf8").length : 0;
590
+ let lastLength = existsSync3(timelinePath) ? readFileSync3(timelinePath, "utf8").length : 0;
561
591
  while (true) {
562
592
  await Bun.sleep(1000);
563
- if (!existsSync2(timelinePath))
593
+ if (!existsSync3(timelinePath))
564
594
  continue;
565
- const next = readFileSync2(timelinePath, "utf8");
595
+ const next = readFileSync3(timelinePath, "utf8");
566
596
  if (next.length <= lastLength)
567
597
  continue;
568
598
  const delta = next.slice(lastLength);
@@ -707,6 +737,20 @@ async function executeRun(context, args) {
707
737
  }
708
738
  return { ok: true, group: "run", command, details: resumed };
709
739
  }
740
+ case "restart": {
741
+ requireNoExtraArgs(rest, "bun run rig run restart");
742
+ if (context.dryRun) {
743
+ if (context.outputMode === "text") {
744
+ console.log("[dry-run] rig run restart");
745
+ }
746
+ return { ok: true, group: "run", command };
747
+ }
748
+ const restarted = await runRestart(context.projectRoot, runtimeContext);
749
+ if (context.outputMode === "text") {
750
+ console.log(`Restarted run: ${restarted.runId}`);
751
+ }
752
+ return { ok: true, group: "run", command, details: restarted };
753
+ }
710
754
  case "stop": {
711
755
  const runOption = takeOption(rest, "--run");
712
756
  const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
@@ -62,6 +62,8 @@ function normalizeRuntimeAdapter(value) {
62
62
 
63
63
  // packages/cli/src/commands/_server-client.ts
64
64
  import { spawnSync } from "child_process";
65
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
66
+ import { resolve as resolve2 } from "path";
65
67
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
66
68
 
67
69
  // packages/cli/src/commands/_connection-state.ts
@@ -152,9 +154,25 @@ function cleanToken(value) {
152
154
  const trimmed = value?.trim();
153
155
  return trimmed ? trimmed : null;
154
156
  }
155
- function readGitHubBearerTokenForRemote() {
157
+ function readPrivateRemoteSessionToken(projectRoot) {
158
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
159
+ if (!existsSync2(path))
160
+ return null;
161
+ try {
162
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
163
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+ function readGitHubBearerTokenForRemote(projectRoot) {
156
169
  if (cachedGitHubBearerToken !== undefined)
157
170
  return cachedGitHubBearerToken;
171
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
172
+ if (privateSession) {
173
+ cachedGitHubBearerToken = privateSession;
174
+ return cachedGitHubBearerToken;
175
+ }
158
176
  const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
159
177
  if (envToken) {
160
178
  cachedGitHubBearerToken = envToken;
@@ -174,7 +192,7 @@ async function ensureServerForCli(projectRoot) {
174
192
  if (selected?.connection.kind === "remote") {
175
193
  return {
176
194
  baseUrl: selected.connection.baseUrl,
177
- authToken: readGitHubBearerTokenForRemote(),
195
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
178
196
  connectionKind: "remote"
179
197
  };
180
198
  }
@@ -281,7 +299,7 @@ async function executeServer(context, args, options) {
281
299
  const authTokenResult = takeOption(pending, "--auth-token");
282
300
  pending = authTokenResult.rest;
283
301
  requireNoExtraArgs(pending, "bun run rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
284
- const commandParts = ["bun", "run", "packages/server/src/server.ts", "start"];
302
+ const commandParts = ["rig-server", "start"];
285
303
  if (hostResult.value) {
286
304
  commandParts.push("--host", hostResult.value);
287
305
  }
@@ -304,7 +322,7 @@ async function executeServer(context, args, options) {
304
322
  const eventResult = takeOption(pending, "--event");
305
323
  pending = eventResult.rest;
306
324
  requireNoExtraArgs(pending, "bun run rig server notify-test [--event <type>]");
307
- const commandParts = ["bun", "run", "packages/server/src/server.ts", "notify-test"];
325
+ const commandParts = ["rig-server", "notify-test"];
308
326
  if (eventResult.value) {
309
327
  commandParts.push("--event", eventResult.value);
310
328
  }