@h-rig/cli 0.0.6-alpha.88 → 0.0.6-alpha.89

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 (70) hide show
  1. package/dist/bin/rig.js +1159 -292
  2. package/dist/src/app/board.js +462 -48
  3. package/dist/src/app-opentui/adapters/doctor.js +458 -46
  4. package/dist/src/app-opentui/adapters/family.js +670 -147
  5. package/dist/src/app-opentui/adapters/fleet.js +477 -46
  6. package/dist/src/app-opentui/adapters/inbox.js +477 -46
  7. package/dist/src/app-opentui/adapters/init.js +497 -74
  8. package/dist/src/app-opentui/adapters/inspect.js +477 -46
  9. package/dist/src/app-opentui/adapters/pi-attach.js +484 -53
  10. package/dist/src/app-opentui/adapters/run-detail.js +477 -46
  11. package/dist/src/app-opentui/adapters/server.d.ts +26 -0
  12. package/dist/src/app-opentui/adapters/server.js +676 -59
  13. package/dist/src/app-opentui/adapters/tasks.js +539 -81
  14. package/dist/src/app-opentui/autocomplete.js +4 -2
  15. package/dist/src/app-opentui/bootstrap.js +1155 -288
  16. package/dist/src/app-opentui/command-palette.js +37 -10
  17. package/dist/src/app-opentui/index.js +566 -89
  18. package/dist/src/app-opentui/intent.js +33 -8
  19. package/dist/src/app-opentui/keymap.js +43 -29
  20. package/dist/src/app-opentui/pi-host-child.js +465 -53
  21. package/dist/src/app-opentui/react/App.js +104 -44
  22. package/dist/src/app-opentui/react/ChromeHost.js +16 -4
  23. package/dist/src/app-opentui/react/launch.js +555 -88
  24. package/dist/src/app-opentui/react/nav.js +1 -1
  25. package/dist/src/app-opentui/registry.js +1062 -237
  26. package/dist/src/app-opentui/remote-link.d.ts +10 -0
  27. package/dist/src/app-opentui/remote-link.js +47 -0
  28. package/dist/src/app-opentui/runtime.js +566 -89
  29. package/dist/src/app-opentui/scenes/doctor.js +1 -1
  30. package/dist/src/app-opentui/scenes/error.js +50 -4
  31. package/dist/src/app-opentui/scenes/family.js +60 -6
  32. package/dist/src/app-opentui/scenes/fleet.js +65 -13
  33. package/dist/src/app-opentui/scenes/help.js +4 -2
  34. package/dist/src/app-opentui/scenes/init.js +12 -12
  35. package/dist/src/app-opentui/scenes/main.js +7 -7
  36. package/dist/src/app-opentui/scenes/server.js +83 -11
  37. package/dist/src/app-opentui/scenes/tasks.js +79 -16
  38. package/dist/src/app-opentui/state.js +25 -5
  39. package/dist/src/app-opentui/surface-catalog.js +4 -2
  40. package/dist/src/app-opentui/types.d.ts +1 -1
  41. package/dist/src/commands/_cli-format.d.ts +10 -1
  42. package/dist/src/commands/_cli-format.js +5 -2
  43. package/dist/src/commands/_connection-state.d.ts +11 -1
  44. package/dist/src/commands/_connection-state.js +50 -5
  45. package/dist/src/commands/_doctor-checks.js +458 -46
  46. package/dist/src/commands/_help-catalog.js +4 -2
  47. package/dist/src/commands/_json-output.js +4 -0
  48. package/dist/src/commands/_operator-view.js +465 -53
  49. package/dist/src/commands/_pi-frontend.js +465 -53
  50. package/dist/src/commands/_preflight.js +509 -72
  51. package/dist/src/commands/_server-client.d.ts +33 -0
  52. package/dist/src/commands/_server-client.js +477 -46
  53. package/dist/src/commands/_server-events.js +446 -41
  54. package/dist/src/commands/_snapshot-upload.js +460 -48
  55. package/dist/src/commands/connect.js +620 -15
  56. package/dist/src/commands/doctor.js +458 -46
  57. package/dist/src/commands/github.js +462 -50
  58. package/dist/src/commands/inbox.js +458 -46
  59. package/dist/src/commands/init.js +497 -74
  60. package/dist/src/commands/inspect.js +458 -46
  61. package/dist/src/commands/run.js +465 -53
  62. package/dist/src/commands/server.js +647 -163
  63. package/dist/src/commands/setup.js +463 -51
  64. package/dist/src/commands/stats.js +462 -48
  65. package/dist/src/commands/task-run-driver.js +464 -52
  66. package/dist/src/commands/task.js +520 -81
  67. package/dist/src/commands.js +670 -147
  68. package/dist/src/index.js +674 -147
  69. package/dist/src/launcher.js +4 -0
  70. package/package.json +8 -8
@@ -183,12 +183,13 @@ var init__help_catalog = __esm(() => {
183
183
  {
184
184
  name: "server",
185
185
  summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
186
- usage: ["rig server <status|list|add|use|start> [options]"],
186
+ usage: ["rig server <status|list|add|use|repair-link|start> [options]"],
187
187
  commands: [
188
- { command: "status", description: "Show the selected server for this repo.", primary: true },
188
+ { command: "status", description: "Show the selected server and remote project-root link for this repo.", primary: true },
189
189
  { command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
190
190
  { command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
191
191
  { command: "use <alias>", description: "Select a saved remote server alias.", primary: true },
192
+ { command: "repair-link [--prepare|--backfill-only] [--repo owner/repo]", description: "Backfill or prepare the selected remote's server-host checkout link.", primary: true },
192
193
  { command: "list", description: "List saved local/remote server aliases.", primary: true },
193
194
  { command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
194
195
  ],
@@ -196,6 +197,7 @@ var init__help_catalog = __esm(() => {
196
197
  "rig server status",
197
198
  "rig server add prod https://where.rig-does.work",
198
199
  "rig server use prod",
200
+ "rig server repair-link --repo owner/repo",
199
201
  "rig server use local",
200
202
  "rig server start --port 3773"
201
203
  ],
@@ -3546,12 +3548,28 @@ function readRepoConnection(projectRoot) {
3546
3548
  selected,
3547
3549
  project: typeof record.project === "string" ? record.project : undefined,
3548
3550
  linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
3549
- serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
3551
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
3552
+ serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
3553
+ serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
3550
3554
  };
3551
3555
  }
3552
3556
  function writeRepoConnection(projectRoot, state) {
3553
3557
  writeJsonFile2(resolveRepoConnectionPath(projectRoot), state);
3554
3558
  }
3559
+ function rootAllowedForSelection(repo, connection) {
3560
+ const root = repo.serverProjectRoot?.trim();
3561
+ if (!root)
3562
+ return;
3563
+ if (connection.kind === "remote") {
3564
+ if (repo.serverProjectRootAlias !== repo.selected)
3565
+ return;
3566
+ if (repo.serverProjectRootBaseUrl !== connection.baseUrl)
3567
+ return;
3568
+ } else if (repo.serverProjectRootAlias && repo.serverProjectRootAlias !== repo.selected) {
3569
+ return;
3570
+ }
3571
+ return root;
3572
+ }
3555
3573
  function resolveSelectedConnection(projectRoot, options = {}) {
3556
3574
  const repo = readRepoConnection(projectRoot);
3557
3575
  if (!repo)
@@ -3563,13 +3581,41 @@ function resolveSelectedConnection(projectRoot, options = {}) {
3563
3581
  if (!connection) {
3564
3582
  throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
3565
3583
  }
3566
- return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
3584
+ return { alias: repo.selected, connection, serverProjectRoot: rootAllowedForSelection(repo, connection) };
3585
+ }
3586
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot, metadata = {}) {
3587
+ const repo = readRepoConnection(projectRoot);
3588
+ if (!repo)
3589
+ return;
3590
+ let inferred = metadata;
3591
+ if (!inferred.alias || !inferred.baseUrl) {
3592
+ try {
3593
+ const selected = resolveSelectedConnection(projectRoot);
3594
+ if (selected?.connection.kind === "remote") {
3595
+ inferred = {
3596
+ alias: inferred.alias ?? selected.alias,
3597
+ baseUrl: inferred.baseUrl ?? selected.connection.baseUrl
3598
+ };
3599
+ }
3600
+ } catch {}
3601
+ }
3602
+ writeRepoConnection(projectRoot, {
3603
+ ...repo,
3604
+ ...metadata.project ? { project: metadata.project } : {},
3605
+ serverProjectRoot,
3606
+ ...inferred.alias ? { serverProjectRootAlias: inferred.alias } : {},
3607
+ ...inferred.baseUrl ? { serverProjectRootBaseUrl: inferred.baseUrl.replace(/\/+$/, "") } : {}
3608
+ });
3567
3609
  }
3568
- function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
3610
+ function clearRepoServerProjectRoot(projectRoot) {
3569
3611
  const repo = readRepoConnection(projectRoot);
3570
3612
  if (!repo)
3571
3613
  return;
3572
- writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
3614
+ writeRepoConnection(projectRoot, {
3615
+ selected: repo.selected,
3616
+ ...repo.project ? { project: repo.project } : {},
3617
+ ...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
3618
+ });
3573
3619
  }
3574
3620
  function isRemoteConnectionSelected(projectRoot) {
3575
3621
  return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
@@ -3579,8 +3625,8 @@ var init__connection_state = __esm(() => {
3579
3625
  });
3580
3626
 
3581
3627
  // packages/cli/src/commands/_server-client.ts
3582
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
3583
- import { resolve as resolve10 } from "path";
3628
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
3629
+ import { dirname as dirname3, isAbsolute, resolve as resolve10 } from "path";
3584
3630
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
3585
3631
  function setServerPhaseListener(listener) {
3586
3632
  const previous = serverPhaseListener;
@@ -3626,11 +3672,10 @@ function readStoredGitHubAuthToken(projectRoot) {
3626
3672
  const parsed = readRemoteAuthState(projectRoot);
3627
3673
  return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
3628
3674
  }
3629
- function inferRemoteServerProjectRootFromAuthState(projectRoot) {
3630
- const repo = readRepoConnection(projectRoot);
3631
- const slug = repo?.project?.trim();
3632
- if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
3675
+ function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
3676
+ if (!/^[^/]+\/[^/]+$/.test(repoSlug))
3633
3677
  return null;
3678
+ const repo = readRepoConnection(projectRoot);
3634
3679
  const auth = readRemoteAuthState(projectRoot);
3635
3680
  const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
3636
3681
  const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
@@ -3639,25 +3684,419 @@ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
3639
3684
  const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
3640
3685
  if (!checkoutBaseDir)
3641
3686
  return null;
3642
- const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
3643
- writeRepoServerProjectRoot(projectRoot, inferred);
3644
- return inferred;
3687
+ return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
3645
3688
  }
3646
3689
  function readLocalConnectionFallbackToken(projectRoot) {
3647
3690
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
3648
3691
  }
3692
+ function normalizeRepoSlug(value) {
3693
+ const slug = value?.trim();
3694
+ return slug && /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
3695
+ }
3696
+ function readProjectLinkSlug(projectRoot) {
3697
+ const path = resolve10(projectRoot, ".rig", "state", "project-link.json");
3698
+ if (!existsSync6(path))
3699
+ return null;
3700
+ try {
3701
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
3702
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
3703
+ return null;
3704
+ return normalizeRepoSlug(typeof parsed.repoSlug === "string" ? String(parsed.repoSlug) : null);
3705
+ } catch {
3706
+ return null;
3707
+ }
3708
+ }
3709
+ function writeProjectLinkConnection(projectRoot, repoSlug, alias) {
3710
+ const path = resolve10(projectRoot, ".rig", "state", "project-link.json");
3711
+ mkdirSync6(dirname3(path), { recursive: true });
3712
+ writeFileSync5(path, `${JSON.stringify({ repoSlug, connection: alias, linkedAt: new Date().toISOString() }, null, 2)}
3713
+ `, "utf8");
3714
+ }
3715
+ function remoteLinkRepairCommand(repoSlug) {
3716
+ return repoSlug ? `rig server repair-link --repo ${repoSlug}` : "rig server repair-link --repo owner/repo";
3717
+ }
3718
+ function formatRemoteProjectLinkHint(resolution) {
3719
+ const repair = remoteLinkRepairCommand(resolution.repoSlug);
3720
+ if (resolution.status === "auth_required") {
3721
+ return `Authenticate the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}, then run \`${repair}\`. Use \`rig github auth import-gh\` or \`rig init --repair --server remote --github-auth device${resolution.repoSlug ? ` --repo ${resolution.repoSlug}` : ""}\`.`;
3722
+ }
3723
+ if (resolution.status === "missing_project") {
3724
+ return `Record the repo slug, then run \`${repair}\` (or \`rig init --repo owner/repo --server remote --github-auth device\`).`;
3725
+ }
3726
+ if (resolution.status === "not_remote")
3727
+ return "Select a remote server with `rig server use <alias>` before repairing a remote project link.";
3728
+ return `Run \`${repair}\` to backfill or prepare a server-host checkout for the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}.`;
3729
+ }
3730
+ function remoteProjectLinkFailure(input) {
3731
+ const partial = {
3732
+ status: input.status,
3733
+ alias: input.alias,
3734
+ baseUrl: input.baseUrl,
3735
+ repoSlug: input.repoSlug
3736
+ };
3737
+ return {
3738
+ ok: false,
3739
+ ...input,
3740
+ hint: formatRemoteProjectLinkHint(partial),
3741
+ next: remoteLinkRepairCommand(input.repoSlug)
3742
+ };
3743
+ }
3744
+ function remoteProjectLinkSuccess(input) {
3745
+ return {
3746
+ ok: true,
3747
+ status: input.status,
3748
+ alias: input.alias,
3749
+ baseUrl: input.baseUrl,
3750
+ repoSlug: input.repoSlug,
3751
+ serverProjectRoot: input.serverProjectRoot,
3752
+ source: input.source,
3753
+ prepared: input.prepared ?? input.status === "prepared",
3754
+ validated: input.validated ?? false,
3755
+ message: input.message ?? `Remote project link ready for ${input.repoSlug}.`,
3756
+ hint: "Remote-scoped task/run/status requests will send x-rig-project-root for this server-host checkout.",
3757
+ next: "rig task list"
3758
+ };
3759
+ }
3760
+ function localCheckoutPathCandidate(projectRoot, candidate) {
3761
+ try {
3762
+ const local = resolve10(projectRoot);
3763
+ const resolved = resolve10(candidate);
3764
+ return resolved === local || resolved.startsWith(`${local}/`);
3765
+ } catch {
3766
+ return false;
3767
+ }
3768
+ }
3769
+ async function requestRemoteProjectLinkJson(baseUrl, pathname, authToken, init = {}) {
3770
+ const requestUrl = new URL(`${baseUrl}${pathname}`);
3771
+ if (authToken && queryAuthFallbackEnabled())
3772
+ requestUrl.searchParams.set("rt", authToken);
3773
+ const response = await fetch(requestUrl, {
3774
+ ...init,
3775
+ headers: mergeHeaders(init.headers, authToken)
3776
+ });
3777
+ const text = await response.text();
3778
+ const payload = text.trim().length > 0 ? (() => {
3779
+ try {
3780
+ return JSON.parse(text);
3781
+ } catch {
3782
+ return null;
3783
+ }
3784
+ })() : null;
3785
+ return { ok: response.ok, status: response.status, payload, text };
3786
+ }
3787
+ function payloadError(payload, fallback) {
3788
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
3789
+ const record = payload;
3790
+ const value = record.error ?? record.message ?? record.reason;
3791
+ if (typeof value === "string" && value.trim())
3792
+ return value.trim();
3793
+ }
3794
+ return fallback;
3795
+ }
3796
+ function checkoutPathsFromProjectPayload(payload) {
3797
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
3798
+ return [];
3799
+ const project = payload.project;
3800
+ if (!project || typeof project !== "object" || Array.isArray(project))
3801
+ return [];
3802
+ const checkouts = project.checkouts;
3803
+ if (!Array.isArray(checkouts))
3804
+ return [];
3805
+ return [...checkouts].reverse().flatMap((entry) => {
3806
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
3807
+ return [];
3808
+ const path = entry.path;
3809
+ return typeof path === "string" && path.trim() ? [path.trim()] : [];
3810
+ });
3811
+ }
3812
+ function checkoutPathFromPreparePayload(payload) {
3813
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
3814
+ return null;
3815
+ const checkout = payload.checkout;
3816
+ if (!checkout || typeof checkout !== "object" || Array.isArray(checkout))
3817
+ return null;
3818
+ const path = checkout.path;
3819
+ return typeof path === "string" && path.trim() ? path.trim() : null;
3820
+ }
3821
+ async function validateAndPersistRemoteRoot(input) {
3822
+ const candidate = input.candidate.trim();
3823
+ if (!candidate || !isAbsolute(candidate)) {
3824
+ return remoteProjectLinkFailure({
3825
+ status: "invalid_root",
3826
+ alias: input.alias,
3827
+ baseUrl: input.baseUrl,
3828
+ repoSlug: input.repoSlug,
3829
+ message: `Remote project root candidate is not an absolute server-host path: ${candidate || "(empty)"}.`
3830
+ });
3831
+ }
3832
+ if (localCheckoutPathCandidate(input.projectRoot, candidate)) {
3833
+ return remoteProjectLinkFailure({
3834
+ status: "invalid_root",
3835
+ alias: input.alias,
3836
+ baseUrl: input.baseUrl,
3837
+ repoSlug: input.repoSlug,
3838
+ message: `Refusing to use local checkout path ${candidate} as a remote server project root.`
3839
+ });
3840
+ }
3841
+ let response;
3842
+ try {
3843
+ response = await requestRemoteProjectLinkJson(input.baseUrl, "/api/server/project-root", input.authToken, {
3844
+ method: "POST",
3845
+ headers: { "content-type": "application/json" },
3846
+ body: JSON.stringify({ projectRoot: candidate })
3847
+ });
3848
+ } catch (error) {
3849
+ return remoteProjectLinkFailure({
3850
+ status: "error",
3851
+ alias: input.alias,
3852
+ baseUrl: input.baseUrl,
3853
+ repoSlug: input.repoSlug,
3854
+ message: `Could not validate remote project root ${candidate}: ${error instanceof Error ? error.message : String(error)}`
3855
+ });
3856
+ }
3857
+ if (response.status === 401 || response.status === 403) {
3858
+ return remoteProjectLinkFailure({
3859
+ status: "auth_required",
3860
+ alias: input.alias,
3861
+ baseUrl: input.baseUrl,
3862
+ repoSlug: input.repoSlug,
3863
+ statusCode: response.status,
3864
+ message: `Remote server ${input.alias} requires authentication before validating project root ${candidate}.`
3865
+ });
3866
+ }
3867
+ if (!response.ok) {
3868
+ return remoteProjectLinkFailure({
3869
+ status: "invalid_root",
3870
+ alias: input.alias,
3871
+ baseUrl: input.baseUrl,
3872
+ repoSlug: input.repoSlug,
3873
+ statusCode: response.status,
3874
+ message: `Remote server rejected project root ${candidate} (${response.status}): ${payloadError(response.payload, response.text || "project-root validation failed")}`
3875
+ });
3876
+ }
3877
+ const record = response.payload && typeof response.payload === "object" && !Array.isArray(response.payload) ? response.payload : {};
3878
+ if (record.ok !== true) {
3879
+ return remoteProjectLinkFailure({
3880
+ status: "invalid_root",
3881
+ alias: input.alias,
3882
+ baseUrl: input.baseUrl,
3883
+ repoSlug: input.repoSlug,
3884
+ message: `Remote server did not accept project root ${candidate}: ${payloadError(response.payload, "project-root validation failed")}`
3885
+ });
3886
+ }
3887
+ const accepted = typeof record.projectRoot === "string" && record.projectRoot.trim() ? record.projectRoot.trim() : typeof record.requestedProjectRoot === "string" && record.requestedProjectRoot.trim() ? record.requestedProjectRoot.trim() : candidate;
3888
+ writeRepoServerProjectRoot(input.projectRoot, accepted, { alias: input.alias, baseUrl: input.baseUrl, project: input.repoSlug });
3889
+ writeProjectLinkConnection(input.projectRoot, input.repoSlug, input.alias);
3890
+ const status = input.status ?? (input.prepared ? "prepared" : "backfilled");
3891
+ return remoteProjectLinkSuccess({
3892
+ status,
3893
+ alias: input.alias,
3894
+ baseUrl: input.baseUrl,
3895
+ repoSlug: input.repoSlug,
3896
+ serverProjectRoot: accepted,
3897
+ source: input.source,
3898
+ prepared: input.prepared,
3899
+ validated: true,
3900
+ message: status === "ready" ? `Remote project link already points to ${accepted} and was validated on ${input.alias}.` : input.prepared ? `Prepared and linked remote checkout ${accepted} for ${input.repoSlug}.` : `Backfilled remote checkout ${accepted} for ${input.repoSlug}.`
3901
+ });
3902
+ }
3903
+ async function ensureRemoteProjectRootLink(projectRoot, options = {}) {
3904
+ let selected;
3905
+ try {
3906
+ selected = resolveSelectedConnection(projectRoot);
3907
+ } catch (error) {
3908
+ return remoteProjectLinkFailure({ status: "error", message: error instanceof Error ? error.message : String(error) });
3909
+ }
3910
+ if (!selected || selected.connection.kind !== "remote") {
3911
+ return remoteProjectLinkFailure({ status: "not_remote", message: "No remote Rig server is selected for this repo." });
3912
+ }
3913
+ const repo = readRepoConnection(projectRoot);
3914
+ const explicitRepoRequested = options.repoSlug !== undefined;
3915
+ const explicitRepoSlug = explicitRepoRequested ? normalizeRepoSlug(options.repoSlug) : null;
3916
+ const repoProjectSlug = normalizeRepoSlug(repo?.project);
3917
+ const repoSlug = explicitRepoSlug ?? repoProjectSlug ?? readProjectLinkSlug(projectRoot);
3918
+ const alias = selected.alias;
3919
+ const baseUrl = selected.connection.baseUrl;
3920
+ const authToken = options.authToken === undefined ? readGitHubBearerTokenForRemote(projectRoot) : options.authToken;
3921
+ const mode = options.mode ?? "backfill-only";
3922
+ if (explicitRepoRequested && !explicitRepoSlug) {
3923
+ return remoteProjectLinkFailure({
3924
+ status: "missing_project",
3925
+ alias,
3926
+ baseUrl,
3927
+ message: `Invalid --repo value ${JSON.stringify(options.repoSlug)}. Expected owner/repo.`
3928
+ });
3929
+ }
3930
+ if (!repoSlug) {
3931
+ return remoteProjectLinkFailure({
3932
+ status: "missing_project",
3933
+ alias,
3934
+ baseUrl,
3935
+ message: `Remote server ${alias} is selected, but this checkout has no GitHub repo slug recorded.`
3936
+ });
3937
+ }
3938
+ const skippedCandidates = [];
3939
+ const storedRoot = selected.serverProjectRoot?.trim() || null;
3940
+ const storedRootMatchesResolvedRepo = repoProjectSlug === repoSlug;
3941
+ if (storedRoot && storedRootMatchesResolvedRepo) {
3942
+ if (localCheckoutPathCandidate(projectRoot, storedRoot)) {
3943
+ clearRepoServerProjectRoot(projectRoot);
3944
+ skippedCandidates.push(storedRoot);
3945
+ } else {
3946
+ const storedResult = await validateAndPersistRemoteRoot({
3947
+ projectRoot,
3948
+ alias,
3949
+ baseUrl,
3950
+ authToken,
3951
+ repoSlug,
3952
+ candidate: storedRoot,
3953
+ source: "stored",
3954
+ status: "ready"
3955
+ });
3956
+ if (storedResult.ok || storedResult.status === "auth_required" || storedResult.status === "error")
3957
+ return storedResult;
3958
+ clearRepoServerProjectRoot(projectRoot);
3959
+ skippedCandidates.push(storedRoot);
3960
+ }
3961
+ } else if (storedRoot) {
3962
+ skippedCandidates.push(storedRoot);
3963
+ }
3964
+ const authCandidate = inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug);
3965
+ if (authCandidate) {
3966
+ const authResult = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: authCandidate, source: "auth-state" });
3967
+ if (authResult.ok || authResult.status === "auth_required")
3968
+ return authResult;
3969
+ }
3970
+ let registryResponse;
3971
+ try {
3972
+ registryResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}`, authToken);
3973
+ } catch (error) {
3974
+ return remoteProjectLinkFailure({
3975
+ status: "error",
3976
+ alias,
3977
+ baseUrl,
3978
+ repoSlug,
3979
+ message: `Could not read remote project registry for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
3980
+ });
3981
+ }
3982
+ if (registryResponse.status === 401 || registryResponse.status === 403) {
3983
+ return remoteProjectLinkFailure({
3984
+ status: "auth_required",
3985
+ alias,
3986
+ baseUrl,
3987
+ repoSlug,
3988
+ statusCode: registryResponse.status,
3989
+ message: `Remote server ${alias} requires authentication before reading the ${repoSlug} project registry.`
3990
+ });
3991
+ }
3992
+ if (registryResponse.ok) {
3993
+ const candidates = checkoutPathsFromProjectPayload(registryResponse.payload);
3994
+ for (const candidate of candidates) {
3995
+ const result = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate, source: "registry" });
3996
+ if (result.ok || result.status === "auth_required")
3997
+ return result;
3998
+ skippedCandidates.push(candidate);
3999
+ }
4000
+ if (mode === "backfill-only") {
4001
+ return remoteProjectLinkFailure({
4002
+ status: candidates.length > 0 ? "invalid_root" : "no_server_checkout",
4003
+ alias,
4004
+ baseUrl,
4005
+ repoSlug,
4006
+ message: candidates.length > 0 ? `Remote registry has checkout candidates for ${repoSlug}, but none validated for ${alias}.` : `Remote registry has ${repoSlug}, but no server checkout path is linked yet.`,
4007
+ skippedCandidates
4008
+ });
4009
+ }
4010
+ } else if (registryResponse.status === 404) {
4011
+ if (mode === "backfill-only") {
4012
+ return remoteProjectLinkFailure({
4013
+ status: "project_not_registered",
4014
+ alias,
4015
+ baseUrl,
4016
+ repoSlug,
4017
+ statusCode: registryResponse.status,
4018
+ message: `Remote server ${alias} has no registered project record for ${repoSlug}.`
4019
+ });
4020
+ }
4021
+ } else {
4022
+ return remoteProjectLinkFailure({
4023
+ status: "error",
4024
+ alias,
4025
+ baseUrl,
4026
+ repoSlug,
4027
+ statusCode: registryResponse.status,
4028
+ message: `Could not backfill ${repoSlug} from remote registry (${registryResponse.status}): ${payloadError(registryResponse.payload, registryResponse.text || "registry lookup failed")}`,
4029
+ skippedCandidates
4030
+ });
4031
+ }
4032
+ let prepareResponse;
4033
+ try {
4034
+ prepareResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}/prepare-checkout`, authToken, {
4035
+ method: "POST",
4036
+ headers: { "content-type": "application/json" },
4037
+ body: JSON.stringify({ checkout: { kind: "managed-clone" }, repoUrl: `https://github.com/${repoSlug}.git` })
4038
+ });
4039
+ } catch (error) {
4040
+ return remoteProjectLinkFailure({
4041
+ status: "error",
4042
+ alias,
4043
+ baseUrl,
4044
+ repoSlug,
4045
+ message: `Could not prepare remote checkout for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
4046
+ });
4047
+ }
4048
+ if (prepareResponse.status === 401 || prepareResponse.status === 403) {
4049
+ return remoteProjectLinkFailure({
4050
+ status: "auth_required",
4051
+ alias,
4052
+ baseUrl,
4053
+ repoSlug,
4054
+ statusCode: prepareResponse.status,
4055
+ message: `Remote server ${alias} requires authentication before preparing ${repoSlug}.`
4056
+ });
4057
+ }
4058
+ if (!prepareResponse.ok) {
4059
+ return remoteProjectLinkFailure({
4060
+ status: "error",
4061
+ alias,
4062
+ baseUrl,
4063
+ repoSlug,
4064
+ statusCode: prepareResponse.status,
4065
+ message: `Remote checkout prepare failed for ${repoSlug} (${prepareResponse.status}): ${payloadError(prepareResponse.payload, prepareResponse.text || "prepare-checkout failed")}`,
4066
+ skippedCandidates
4067
+ });
4068
+ }
4069
+ const preparedPath = checkoutPathFromPreparePayload(prepareResponse.payload);
4070
+ if (!preparedPath) {
4071
+ return remoteProjectLinkFailure({
4072
+ status: "invalid_root",
4073
+ alias,
4074
+ baseUrl,
4075
+ repoSlug,
4076
+ message: `Remote checkout prepare for ${repoSlug} did not return a checkout.path.`,
4077
+ skippedCandidates
4078
+ });
4079
+ }
4080
+ return validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: preparedPath, source: "prepare", prepared: true });
4081
+ }
4082
+ async function repairRemoteProjectRootLink(context, options = {}) {
4083
+ const resolution = await ensureRemoteProjectRootLink(context.projectRoot, { mode: options.mode ?? "prepare-if-missing", repoSlug: options.repoSlug });
4084
+ if (!resolution.ok)
4085
+ throw new CliError(resolution.message, 1, { hint: resolution.hint });
4086
+ return resolution;
4087
+ }
3649
4088
  async function ensureServerForCli(projectRoot) {
3650
4089
  try {
3651
4090
  const selected = resolveSelectedConnection(projectRoot);
3652
4091
  if (selected?.connection.kind === "remote") {
3653
4092
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
3654
4093
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
3655
- const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
4094
+ const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
3656
4095
  return {
3657
4096
  baseUrl: selected.connection.baseUrl,
3658
4097
  authToken,
3659
4098
  connectionKind: "remote",
3660
- serverProjectRoot
4099
+ serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
3661
4100
  };
3662
4101
  }
3663
4102
  reportServerPhase("Starting local Rig server\u2026");
@@ -3675,32 +4114,6 @@ async function ensureServerForCli(projectRoot) {
3675
4114
  throw error;
3676
4115
  }
3677
4116
  }
3678
- async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
3679
- const repo = readRepoConnection(projectRoot);
3680
- const slug = repo?.project?.trim();
3681
- if (!slug)
3682
- return null;
3683
- try {
3684
- const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
3685
- if (authToken && queryAuthFallbackEnabled())
3686
- url.searchParams.set("rt", authToken);
3687
- const response = await fetch(url, {
3688
- headers: mergeHeaders(undefined, authToken)
3689
- });
3690
- if (!response.ok)
3691
- return null;
3692
- const payload = await response.json();
3693
- const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
3694
- const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
3695
- const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
3696
- const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
3697
- if (path)
3698
- writeRepoServerProjectRoot(projectRoot, path);
3699
- return path;
3700
- } catch {
3701
- return null;
3702
- }
3703
- }
3704
4117
  function appendTaskFilterParams(url, filters) {
3705
4118
  if (filters.assignee)
3706
4119
  url.searchParams.set("assignee", filters.assignee);
@@ -3801,13 +4214,20 @@ function canUseRemoteWithoutProjectRoot(pathname) {
3801
4214
  async function requestServerJson(context, pathname, init = {}) {
3802
4215
  const server = await ensureServerForCli(context.projectRoot);
3803
4216
  const requestUrl = new URL(`${server.baseUrl}${pathname}`);
3804
- if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
3805
- const repo = readRepoConnection(context.projectRoot);
3806
- throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
4217
+ let scopedServerProjectRoot = server.serverProjectRoot;
4218
+ if (server.connectionKind === "remote" && !scopedServerProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
4219
+ const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: server.authToken });
4220
+ if (link.ok && link.serverProjectRoot) {
4221
+ scopedServerProjectRoot = link.serverProjectRoot;
4222
+ } else {
4223
+ const repo = readRepoConnection(context.projectRoot);
4224
+ const target = link.alias && link.baseUrl ? `${link.alias} (${link.baseUrl})` : server.baseUrl;
4225
+ throw new CliError(`Remote server ${target} is selected for ${link.repoSlug ?? repo?.project ?? "this repo"}, but this checkout has no server-host project root link. ${link.message}`, 1, { hint: link.hint });
4226
+ }
3807
4227
  }
3808
4228
  const headers = mergeHeaders(init.headers, server.authToken);
3809
- if (server.serverProjectRoot)
3810
- headers.set("x-rig-project-root", server.serverProjectRoot);
4229
+ if (scopedServerProjectRoot)
4230
+ headers.set("x-rig-project-root", scopedServerProjectRoot);
3811
4231
  if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
3812
4232
  requestUrl.searchParams.set("rt", server.authToken);
3813
4233
  }
@@ -4173,6 +4593,14 @@ function permissionAllowsPr(payload) {
4173
4593
  function isNotFoundError(error) {
4174
4594
  return /\b(404|not found)\b/i.test(message(error));
4175
4595
  }
4596
+ function isLoopbackBaseUrl(baseUrl) {
4597
+ try {
4598
+ const host = new URL(baseUrl).hostname.toLowerCase();
4599
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
4600
+ } catch {
4601
+ return false;
4602
+ }
4603
+ }
4176
4604
  function projectCheckoutReady(payload) {
4177
4605
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
4178
4606
  return null;
@@ -4227,25 +4655,37 @@ async function runFastTaskRunPreflight(context, options = {}) {
4227
4655
  }
4228
4656
  const repo = readRepoConnection(context.projectRoot);
4229
4657
  const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
4230
- checks.push(repo ? preflightCheck("project-link", "project linked to selected server", repo.project ? "pass" : remoteWithoutProjectLink ? "fail" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : " (missing project link)"}`, remoteWithoutProjectLink ? "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to restore the remote project link." : "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to selected server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
4658
+ let remoteRootBlocked = false;
4659
+ checks.push(repo ? preflightCheck("project-link", "project linked to selected server", repo.project ? "pass" : remoteWithoutProjectLink ? "fail" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : " (missing project link)"}`, remoteWithoutProjectLink ? "Run `rig server repair-link --repo owner/repo` or `rig init --repo owner/repo --server remote` to restore the remote project link." : "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to selected server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
4660
+ if (selectedServer?.connectionKind === "remote" && repo?.project && !selectedServer.serverProjectRoot) {
4661
+ const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: selectedServer.authToken });
4662
+ const loopbackDevRemote = !selectedServer.authToken && isLoopbackBaseUrl(selectedServer.baseUrl);
4663
+ checks.push(preflightCheck("remote-project-root-link", "remote server checkout/root linked", link.ok ? "pass" : loopbackDevRemote ? "warn" : "fail", link.ok ? `${link.repoSlug} -> ${link.serverProjectRoot}` : link.message, link.ok ? undefined : loopbackDevRemote ? "Loopback development remotes may continue without x-rig-project-root; production remotes must run `rig server repair-link`." : link.hint));
4664
+ remoteRootBlocked = !link.ok && !loopbackDevRemote;
4665
+ }
4231
4666
  try {
4232
4667
  const auth = await request("/api/github/auth/status");
4233
4668
  checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
4234
4669
  } catch (error) {
4235
4670
  checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
4236
4671
  }
4237
- try {
4238
- const projection = await request("/api/workspace/task-projection");
4239
- checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
4240
- } catch (error) {
4241
- checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
4242
- }
4243
- try {
4244
- const permissions = await request("/api/github/repo/permissions");
4245
- const allowed = permissionAllowsPr(permissions);
4246
- checks.push(allowed === false ? preflightCheck("github-repo-permissions", "GitHub PR permissions", "fail", JSON.stringify(permissions).slice(0, 120), "Grant the selected GitHub token permission to push branches and open PRs.") : preflightCheck("github-repo-permissions", "GitHub PR permissions", allowed === true ? "pass" : "warn", JSON.stringify(permissions).slice(0, 120), "Confirm the selected token can push branches and open PRs."));
4247
- } catch (error) {
4248
- checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
4672
+ if (!remoteRootBlocked) {
4673
+ try {
4674
+ const projection = await request("/api/workspace/task-projection");
4675
+ checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
4676
+ } catch (error) {
4677
+ checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
4678
+ }
4679
+ try {
4680
+ const permissions = await request("/api/github/repo/permissions");
4681
+ const allowed = permissionAllowsPr(permissions);
4682
+ checks.push(allowed === false ? preflightCheck("github-repo-permissions", "GitHub PR permissions", "fail", JSON.stringify(permissions).slice(0, 120), "Grant the selected GitHub token permission to push branches and open PRs.") : preflightCheck("github-repo-permissions", "GitHub PR permissions", allowed === true ? "pass" : "warn", JSON.stringify(permissions).slice(0, 120), "Confirm the selected token can push branches and open PRs."));
4683
+ } catch (error) {
4684
+ checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
4685
+ }
4686
+ } else {
4687
+ checks.push(preflightCheck("task-projection", "task projection ready", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
4688
+ checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
4249
4689
  }
4250
4690
  if (repo?.project) {
4251
4691
  try {
@@ -4257,19 +4697,24 @@ async function runFastTaskRunPreflight(context, options = {}) {
4257
4697
  }
4258
4698
  }
4259
4699
  if (taskId) {
4260
- try {
4261
- const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
4262
- const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
4263
- checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
4264
- } catch (error) {
4265
- checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
4266
- }
4267
- try {
4268
- const runs = await request("/api/runs?limit=200");
4269
- const duplicate = activeDuplicateRun(runs, taskId);
4270
- checks.push(duplicate ? preflightCheck("duplicate-active-run", "one active run per task", "fail", String(duplicate.runId ?? taskId), "Attach to or stop the existing run before starting another one for this task.") : preflightCheck("duplicate-active-run", "one active run per task", "pass", taskId));
4271
- } catch (error) {
4272
- checks.push(preflightCheck("duplicate-active-run", "one active run per task", "warn", message(error), "Could not list active runs; the server will still reject conflicting runs if configured."));
4700
+ if (!remoteRootBlocked) {
4701
+ try {
4702
+ const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
4703
+ const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
4704
+ checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
4705
+ } catch (error) {
4706
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
4707
+ }
4708
+ try {
4709
+ const runs = await request("/api/runs?limit=200");
4710
+ const duplicate = activeDuplicateRun(runs, taskId);
4711
+ checks.push(duplicate ? preflightCheck("duplicate-active-run", "one active run per task", "fail", String(duplicate.runId ?? taskId), "Attach to or stop the existing run before starting another one for this task.") : preflightCheck("duplicate-active-run", "one active run per task", "pass", taskId));
4712
+ } catch (error) {
4713
+ checks.push(preflightCheck("duplicate-active-run", "one active run per task", "warn", message(error), "Could not list active runs; the server will still reject conflicting runs if configured."));
4714
+ }
4715
+ } else {
4716
+ checks.push(preflightCheck("issue", "task/issue accessible", "fail", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
4717
+ checks.push(preflightCheck("duplicate-active-run", "one active run per task", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
4273
4718
  }
4274
4719
  }
4275
4720
  if ((options.runtimeAdapter ?? "pi") === "pi") {
@@ -4750,7 +5195,7 @@ import {
4750
5195
  chmodSync,
4751
5196
  copyFileSync as copyFileSync2,
4752
5197
  existsSync as existsSync8,
4753
- mkdirSync as mkdirSync6,
5198
+ mkdirSync as mkdirSync7,
4754
5199
  readdirSync,
4755
5200
  readlinkSync,
4756
5201
  rmSync as rmSync3,
@@ -4855,7 +5300,7 @@ async function executeDist(context, args) {
4855
5300
  requireNoExtraArgs(pending, "rig dist install [--scope user|system] [--path <dir>]");
4856
5301
  const scope = parseInstallScope(scopeResult.value);
4857
5302
  const installDir = resolveInstallDir(scope, pathResult.value);
4858
- mkdirSync6(installDir, { recursive: true });
5303
+ mkdirSync7(installDir, { recursive: true });
4859
5304
  let source = await findLatestDistBinary(context.projectRoot);
4860
5305
  let buildDir = null;
4861
5306
  if (!source) {
@@ -4910,7 +5355,7 @@ async function executeDist(context, args) {
4910
5355
  const fp = await computeRuntimeImageFingerprint(context.projectRoot);
4911
5356
  const currentId = computeRuntimeImageId(fp);
4912
5357
  const imagesDir = resolve13(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
4913
- mkdirSync6(imagesDir, { recursive: true });
5358
+ mkdirSync7(imagesDir, { recursive: true });
4914
5359
  let pruned = 0;
4915
5360
  for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
4916
5361
  if (entry.isDirectory() && entry.name !== currentId) {
@@ -4922,9 +5367,9 @@ async function executeDist(context, args) {
4922
5367
  console.log(`Pruned ${pruned} stale image(s).`);
4923
5368
  }
4924
5369
  const imageDir = resolve13(imagesDir, currentId);
4925
- mkdirSync6(resolve13(imageDir, "bin/hooks"), { recursive: true });
4926
- mkdirSync6(resolve13(imageDir, "bin/plugins"), { recursive: true });
4927
- mkdirSync6(resolve13(imageDir, "bin/validators"), { recursive: true });
5370
+ mkdirSync7(resolve13(imageDir, "bin/hooks"), { recursive: true });
5371
+ mkdirSync7(resolve13(imageDir, "bin/plugins"), { recursive: true });
5372
+ mkdirSync7(resolve13(imageDir, "bin/validators"), { recursive: true });
4928
5373
  const hookNames = [
4929
5374
  "scope-guard",
4930
5375
  "import-guard",
@@ -4949,8 +5394,8 @@ async function executeDist(context, args) {
4949
5394
  const binPluginsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
4950
5395
  const validatorsRoot = resolve13(hostProjectRoot, "packages/runtime/src/control-plane/validators");
4951
5396
  const binValidatorsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
4952
- mkdirSync6(binPluginsDir, { recursive: true });
4953
- mkdirSync6(binValidatorsDir, { recursive: true });
5397
+ mkdirSync7(binPluginsDir, { recursive: true });
5398
+ mkdirSync7(binValidatorsDir, { recursive: true });
4954
5399
  if (existsSync8(pluginsDir)) {
4955
5400
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
4956
5401
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
@@ -5389,16 +5834,19 @@ function formatConnectionList(connections) {
5389
5834
  return [formatSection("Rig servers", `${rows.length} available`), `${pc4.bold(pad("ALIAS", aliasWidth))} ${pc4.bold("KIND")} ${pc4.bold("TARGET")}`, ...lines, "", ...formatNextSteps(["Select one: `rig server use <alias|local>`"])].join(`
5390
5835
  `);
5391
5836
  }
5392
- function formatConnectionStatus(selected, connections) {
5837
+ function formatConnectionStatus(selected, connections, repo, remoteProjectLink) {
5393
5838
  const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
5394
5839
  const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
5840
+ const rootLabel = remoteProjectLink ? remoteProjectLink.ok ? `ready \xB7 ${remoteProjectLink.serverProjectRoot ?? repo?.serverProjectRoot ?? "linked"}` : `${remoteProjectLink.status ?? "needs repair"} \xB7 ${remoteProjectLink.hint ?? remoteProjectLink.message ?? "run rig server repair-link"}` : repo?.serverProjectRoot ? repo.serverProjectRoot : "not required";
5395
5841
  return [
5396
5842
  formatSection("Rig server", "selected for this repo"),
5397
5843
  `${themeFaint("\u2502")} ${themeDim("selected ")} ${pc4.bold(selected)}`,
5398
5844
  `${themeFaint("\u2502")} ${themeDim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
5399
5845
  `${themeFaint("\u2502")} ${themeDim("target ")} ${target ?? "not configured"}`,
5846
+ ...repo?.project ? [`${themeFaint("\u2502")} ${themeDim("project ")} ${repo.project}`] : [],
5847
+ ...connection?.kind === "remote" ? [`${themeFaint("\u2502")} ${themeDim("root ")} ${rootLabel}`] : [],
5400
5848
  "",
5401
- ...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
5849
+ ...formatNextSteps(remoteProjectLink && !remoteProjectLink.ok ? ["Repair remote link: `rig server repair-link`", "Authenticate first if needed: `rig github auth import-gh`"] : ["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
5402
5850
  ].join(`
5403
5851
  `);
5404
5852
  }
@@ -5858,7 +6306,7 @@ var init__pi_install = () => {};
5858
6306
 
5859
6307
  // packages/cli/src/commands/_snapshot-upload.ts
5860
6308
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
5861
- import { dirname as dirname3, resolve as resolve15, relative, sep } from "path";
6309
+ import { dirname as dirname4, resolve as resolve15, relative, sep } from "path";
5862
6310
  function toPosixPath(path) {
5863
6311
  return path.split(sep).join("/");
5864
6312
  }
@@ -6187,7 +6635,7 @@ var init__doctor_checks = __esm(() => {
6187
6635
  });
6188
6636
 
6189
6637
  // packages/cli/src/commands/init.ts
6190
- import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
6638
+ import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
6191
6639
  import { spawnSync } from "child_process";
6192
6640
  import { basename as basename2, resolve as resolve17 } from "path";
6193
6641
  import { buildRigInitConfigSource } from "@rig/core";
@@ -6211,14 +6659,14 @@ function parseRepoSlug(value) {
6211
6659
  }
6212
6660
  function ensureRigPrivateDirs(projectRoot) {
6213
6661
  const rigDir = resolve17(projectRoot, ".rig");
6214
- mkdirSync7(resolve17(rigDir, "state"), { recursive: true });
6215
- mkdirSync7(resolve17(rigDir, "logs"), { recursive: true });
6216
- mkdirSync7(resolve17(rigDir, "runs"), { recursive: true });
6217
- mkdirSync7(resolve17(rigDir, "tmp"), { recursive: true });
6218
- mkdirSync7(resolve17(projectRoot, "artifacts"), { recursive: true });
6662
+ mkdirSync8(resolve17(rigDir, "state"), { recursive: true });
6663
+ mkdirSync8(resolve17(rigDir, "logs"), { recursive: true });
6664
+ mkdirSync8(resolve17(rigDir, "runs"), { recursive: true });
6665
+ mkdirSync8(resolve17(rigDir, "tmp"), { recursive: true });
6666
+ mkdirSync8(resolve17(projectRoot, "artifacts"), { recursive: true });
6219
6667
  const taskConfigPath = resolve17(rigDir, "task-config.json");
6220
6668
  if (!existsSync11(taskConfigPath))
6221
- writeFileSync5(taskConfigPath, `{}
6669
+ writeFileSync6(taskConfigPath, `{}
6222
6670
  `, "utf-8");
6223
6671
  }
6224
6672
  function ensureGitignoreEntries(projectRoot) {
@@ -6246,7 +6694,7 @@ function ensureRigConfigPackageDependencies(projectRoot) {
6246
6694
  ...existsSync11(path) ? existing : { name: "rig-project", private: true },
6247
6695
  devDependencies
6248
6696
  };
6249
- writeFileSync5(path, `${JSON.stringify(next, null, 2)}
6697
+ writeFileSync6(path, `${JSON.stringify(next, null, 2)}
6250
6698
  `, "utf8");
6251
6699
  }
6252
6700
  function applyGitHubProjectConfig(source, options) {
@@ -6272,9 +6720,9 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
6272
6720
  const selected = strategy ?? { kind: "managed-clone" };
6273
6721
  switch (selected.kind) {
6274
6722
  case "managed-clone":
6275
- return { kind: "managed-clone", path: projectRoot };
6723
+ return { kind: "managed-clone" };
6276
6724
  case "current-ref":
6277
- return { kind: "current-ref", path: projectRoot, ...selected.ref ? { ref: selected.ref } : {} };
6725
+ return { kind: "current-ref", ...selected.ref ? { ref: selected.ref } : {} };
6278
6726
  case "uploaded-snapshot":
6279
6727
  return { kind: "uploaded-snapshot", path: projectRoot, source: "local-working-tree" };
6280
6728
  case "existing-path":
@@ -6527,7 +6975,7 @@ function remoteGitHubAuthMetadata(payload) {
6527
6975
  };
6528
6976
  }
6529
6977
  function writeRemoteGitHubAuthState(projectRoot, input) {
6530
- writeFileSync5(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
6978
+ writeFileSync6(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
6531
6979
  authenticated: true,
6532
6980
  source: input.source,
6533
6981
  storedOnServer: true,
@@ -6575,7 +7023,7 @@ function runLocalFilesInit(context, options) {
6575
7023
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
6576
7024
  } else {
6577
7025
  const projectName = basename2(projectRoot) || "rig-project";
6578
- writeFileSync5(configTsPath, buildRigInitConfigSource({
7026
+ writeFileSync6(configTsPath, buildRigInitConfigSource({
6579
7027
  projectName,
6580
7028
  taskSource: { kind: "files", path: "tasks" },
6581
7029
  useStandardPlugin: true
@@ -6584,8 +7032,8 @@ function runLocalFilesInit(context, options) {
6584
7032
  ensureRigConfigPackageDependencies(projectRoot);
6585
7033
  const tasksDir = resolve17(projectRoot, "tasks");
6586
7034
  if (!existsSync11(tasksDir)) {
6587
- mkdirSync7(tasksDir, { recursive: true });
6588
- writeFileSync5(resolve17(tasksDir, "T-1.json"), `${JSON.stringify({ id: "T-1", title: "My first Rig task", body: "Describe the change you want an agent to make." }, null, 2)}
7035
+ mkdirSync8(tasksDir, { recursive: true });
7036
+ writeFileSync6(resolve17(tasksDir, "T-1.json"), `${JSON.stringify({ id: "T-1", title: "My first Rig task", body: "Describe the change you want an agent to make." }, null, 2)}
6589
7037
  `, "utf-8");
6590
7038
  }
6591
7039
  if (context.outputMode !== "json") {
@@ -6609,7 +7057,7 @@ function runDemoInit(context, options) {
6609
7057
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
6610
7058
  }
6611
7059
  } else {
6612
- writeFileSync5(configTsPath, buildRigInitConfigSource({
7060
+ writeFileSync6(configTsPath, buildRigInitConfigSource({
6613
7061
  projectName: basename2(projectRoot) || "rig-demo",
6614
7062
  taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
6615
7063
  useStandardPlugin: true
@@ -6618,14 +7066,14 @@ function runDemoInit(context, options) {
6618
7066
  }
6619
7067
  ensureRigConfigPackageDependencies(projectRoot);
6620
7068
  const demoTasksDir = resolve17(projectRoot, DEMO_TASKS_RELATIVE_DIR);
6621
- mkdirSync7(demoTasksDir, { recursive: true });
7069
+ mkdirSync8(demoTasksDir, { recursive: true });
6622
7070
  const taskIds = [];
6623
7071
  for (const task of DEMO_TASKS) {
6624
7072
  const id = String(task.id);
6625
7073
  taskIds.push(id);
6626
7074
  const taskPath = resolve17(demoTasksDir, `${id}.json`);
6627
7075
  if (!existsSync11(taskPath)) {
6628
- writeFileSync5(taskPath, `${JSON.stringify(task, null, 2)}
7076
+ writeFileSync6(taskPath, `${JSON.stringify(task, null, 2)}
6629
7077
  `, "utf-8");
6630
7078
  }
6631
7079
  }
@@ -6653,21 +7101,32 @@ function runDemoInit(context, options) {
6653
7101
  }
6654
7102
  async function runControlPlaneInit(context, options) {
6655
7103
  const projectRoot = context.projectRoot;
6656
- const detectedSlug = options.repoSlug ?? detectOriginRepoSlug(projectRoot);
7104
+ const existingRepoConnection = readRepoConnection(projectRoot);
7105
+ const selectedConnection = (() => {
7106
+ try {
7107
+ return resolveSelectedConnection(projectRoot);
7108
+ } catch {
7109
+ return null;
7110
+ }
7111
+ })();
7112
+ const selectedRemote = selectedConnection?.connection.kind === "remote" ? { alias: selectedConnection.alias, connection: selectedConnection.connection } : null;
7113
+ const detectedSlug = options.repoSlug ?? existingRepoConnection?.project ?? detectOriginRepoSlug(projectRoot);
7114
+ const serverKind = options.server ?? (selectedRemote ? "remote" : "local");
6657
7115
  if (!detectedSlug) {
6658
7116
  const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
6659
- if ((options.server ?? "local") === "local" && authMethod2 === "skip") {
7117
+ if (serverKind === "local" && authMethod2 === "skip") {
6660
7118
  return runLocalFilesInit(context, options);
6661
7119
  }
6662
7120
  throw new CliError("Could not detect GitHub repo slug from origin. Pass --repo owner/repo \u2014 or run `rig init --yes --server local --github-auth skip` for a local files-source project without GitHub.", 1);
6663
7121
  }
6664
7122
  const repo = parseRepoSlug(detectedSlug);
6665
- const serverKind = options.server ?? "local";
6666
- const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : "remote");
7123
+ const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : selectedRemote?.alias ?? "remote");
7124
+ const selectedRemoteUrl = selectedRemote && (!options.connectionAlias || options.connectionAlias === selectedRemote.alias) ? selectedRemote.connection.baseUrl : undefined;
7125
+ const remoteUrl = serverKind === "remote" ? (options.remoteUrl ?? selectedRemoteUrl)?.replace(/\/+$/, "") : undefined;
6667
7126
  if (serverKind === "remote") {
6668
- if (!options.remoteUrl)
6669
- throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`." });
6670
- upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: options.remoteUrl });
7127
+ if (!remoteUrl)
7128
+ throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`, or first select a saved remote with `rig server use <alias>`." });
7129
+ upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: remoteUrl });
6671
7130
  }
6672
7131
  writeRepoConnection(projectRoot, {
6673
7132
  selected: connectionAlias,
@@ -6690,11 +7149,11 @@ async function runControlPlaneInit(context, options) {
6690
7149
  taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
6691
7150
  useStandardPlugin: true
6692
7151
  }), options);
6693
- writeFileSync5(configTsPath, source, "utf-8");
7152
+ writeFileSync6(configTsPath, source, "utf-8");
6694
7153
  }
6695
7154
  ensureRigConfigPackageDependencies(projectRoot);
6696
7155
  }
6697
- writeFileSync5(resolve17(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
7156
+ writeFileSync6(resolve17(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
6698
7157
  `, "utf8");
6699
7158
  const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
6700
7159
  let uploadedSnapshot = null;
@@ -6709,7 +7168,7 @@ async function runControlPlaneInit(context, options) {
6709
7168
  let githubAuth = null;
6710
7169
  let deviceAuth = null;
6711
7170
  const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
6712
- const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${options.remoteUrl ?? "the remote Rig server"}.` : null;
7171
+ const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${remoteUrl ?? "the remote Rig server"}.` : null;
6713
7172
  if (remoteGhTokenWarning && !options.yes) {
6714
7173
  throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
6715
7174
  }
@@ -7089,8 +7548,8 @@ var init_init = __esm(() => {
7089
7548
 
7090
7549
  // packages/cli/src/commands/github.ts
7091
7550
  import { spawnSync as spawnSync2 } from "child_process";
7092
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync6 } from "fs";
7093
- import { dirname as dirname4, resolve as resolve18 } from "path";
7551
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
7552
+ import { dirname as dirname5, resolve as resolve18 } from "path";
7094
7553
  function printPayload(context, payload, fallback) {
7095
7554
  if (context.outputMode === "json")
7096
7555
  console.log(JSON.stringify(payload, null, 2));
@@ -7129,8 +7588,8 @@ function persistRemoteAuthSession(context, source, result, fallbackToken) {
7129
7588
  return;
7130
7589
  const repo = readRepoConnection(context.projectRoot);
7131
7590
  const path = resolve18(context.projectRoot, ".rig", "state", "github-auth.json");
7132
- mkdirSync8(dirname4(path), { recursive: true });
7133
- writeFileSync6(path, `${JSON.stringify({
7591
+ mkdirSync9(dirname5(path), { recursive: true });
7592
+ writeFileSync7(path, `${JSON.stringify({
7134
7593
  authenticated: true,
7135
7594
  source,
7136
7595
  storedOnServer: true,
@@ -8633,7 +9092,7 @@ var init__operator_surface = __esm(() => {
8633
9092
  });
8634
9093
 
8635
9094
  // packages/cli/src/commands/_pi-frontend.ts
8636
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
9095
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
8637
9096
  import { homedir as homedir6, tmpdir } from "os";
8638
9097
  import { join as join3 } from "path";
8639
9098
  import { main as runPiMain } from "@earendil-works/pi-coding-agent";
@@ -8685,7 +9144,7 @@ function statusFromRunDetails(run) {
8685
9144
  async function prepareOperatorConsole(context, runId, tempSessionDir) {
8686
9145
  const server = await ensureServerForCli(context.projectRoot);
8687
9146
  const localCwd = join3(homedir6(), ".rig", "drones", runId.slice(0, 8));
8688
- mkdirSync9(localCwd, { recursive: true });
9147
+ mkdirSync10(localCwd, { recursive: true });
8689
9148
  trustDroneCwd(localCwd);
8690
9149
  installRigPiTheme();
8691
9150
  let sessionFileArg = [];
@@ -8707,7 +9166,7 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
8707
9166
  return line;
8708
9167
  }).join(`
8709
9168
  `);
8710
- writeFileSync7(localSessionPath, content);
9169
+ writeFileSync8(localSessionPath, content);
8711
9170
  sessionFileArg = ["--session", localSessionPath];
8712
9171
  }
8713
9172
  } catch {}
@@ -8723,12 +9182,12 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
8723
9182
  function trustDroneCwd(localCwd) {
8724
9183
  try {
8725
9184
  const agentDir = join3(homedir6(), ".pi", "agent");
8726
- mkdirSync9(agentDir, { recursive: true });
9185
+ mkdirSync10(agentDir, { recursive: true });
8727
9186
  const trustPath = join3(agentDir, "trust.json");
8728
9187
  const store = existsSync14(trustPath) ? JSON.parse(readFileSync10(trustPath, "utf8")) : {};
8729
9188
  if (store[localCwd] !== true) {
8730
9189
  store[localCwd] = true;
8731
- writeFileSync7(trustPath, `${JSON.stringify(store, null, "\t")}
9190
+ writeFileSync8(trustPath, `${JSON.stringify(store, null, "\t")}
8732
9191
  `);
8733
9192
  }
8734
9193
  } catch {}
@@ -8736,12 +9195,12 @@ function trustDroneCwd(localCwd) {
8736
9195
  function installRigPiTheme() {
8737
9196
  try {
8738
9197
  const themesDir = join3(homedir6(), ".pi", "agent", "themes");
8739
- mkdirSync9(themesDir, { recursive: true });
9198
+ mkdirSync10(themesDir, { recursive: true });
8740
9199
  const themePath = join3(themesDir, "rig.json");
8741
9200
  const next = `${JSON.stringify(RIG_PI_THEME, null, "\t")}
8742
9201
  `;
8743
9202
  if (!existsSync14(themePath) || readFileSync10(themePath, "utf8") !== next) {
8744
- writeFileSync7(themePath, next);
9203
+ writeFileSync8(themePath, next);
8745
9204
  }
8746
9205
  } catch {}
8747
9206
  }
@@ -9525,13 +9984,46 @@ function parseConnection(alias, value, options) {
9525
9984
  }
9526
9985
  return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
9527
9986
  }
9528
- function printJsonOrText(context, payload, text2) {
9529
- if (context.outputMode === "json") {
9530
- console.log(JSON.stringify(payload, null, 2));
9531
- } else {
9987
+ function printJsonOrText(context, _payload, text2) {
9988
+ if (context.outputMode !== "json") {
9532
9989
  console.log(text2);
9533
9990
  }
9534
9991
  }
9992
+ function formatRemoteProjectLinkText(result) {
9993
+ return formatSuccessCard(result.ok ? "Remote project link ready" : "Remote project link needs repair", [
9994
+ ["selected", result.alias ?? "remote"],
9995
+ ["target", result.baseUrl ?? "unknown"],
9996
+ ["repo", result.repoSlug ?? "owner/repo"],
9997
+ ["status", result.status],
9998
+ ...result.serverProjectRoot ? [["server root", result.serverProjectRoot]] : [],
9999
+ ["next", result.ok ? result.next ?? "rig task list" : result.hint]
10000
+ ]);
10001
+ }
10002
+ function parseRepairLinkArgs(rest, usage) {
10003
+ let repoSlug;
10004
+ let mode = "prepare-if-missing";
10005
+ const pending = [...rest];
10006
+ while (pending.length > 0) {
10007
+ const token = pending.shift();
10008
+ if (token === "--repo") {
10009
+ const value = pending.shift()?.trim();
10010
+ if (!value)
10011
+ throw new CliError(`Missing --repo value. Usage: ${usage}`, 1);
10012
+ const normalized = normalizeRepoSlug(value);
10013
+ if (!normalized)
10014
+ throw new CliError(`Invalid --repo value: ${value}`, 1, { hint: "Use the canonical owner/repo slug, e.g. `--repo humanity-org/humanwork`." });
10015
+ repoSlug = normalized;
10016
+ } else if (token === "--backfill-only") {
10017
+ mode = "backfill-only";
10018
+ } else if (token === "--prepare") {
10019
+ mode = "prepare-if-missing";
10020
+ } else {
10021
+ throw new CliError(`Unexpected argument: ${String(token)}
10022
+ Usage: ${usage}`, 1);
10023
+ }
10024
+ }
10025
+ return { repoSlug, mode };
10026
+ }
9535
10027
  async function promptForConnectionAlias(context) {
9536
10028
  const state = readGlobalConnections();
9537
10029
  const repo = readRepoConnection(context.projectRoot);
@@ -9585,37 +10077,67 @@ async function executeConnectionCommand(context, args, options) {
9585
10077
  }
9586
10078
  if (!alias)
9587
10079
  throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
10080
+ let selectedConnection = { kind: "local", mode: "auto" };
9588
10081
  if (alias !== "local") {
9589
10082
  const state = readGlobalConnections();
9590
- if (!state.connections[alias])
10083
+ const connection = state.connections[alias];
10084
+ if (!connection)
9591
10085
  throw new CliError(`Unknown Rig server: ${alias}`, 1, { hint: "Run `rig server list` to see saved aliases, or save one with `rig server add <alias> <url>`." });
10086
+ selectedConnection = connection;
9592
10087
  }
9593
10088
  const previousRepo = readRepoConnection(context.projectRoot);
10089
+ const preserveRemoteRoot = Boolean(previousRepo?.serverProjectRoot && previousRepo.selected === alias && selectedConnection.kind === "remote" && previousRepo.serverProjectRootAlias === alias && previousRepo.serverProjectRootBaseUrl === selectedConnection.baseUrl);
9594
10090
  const repoState = {
9595
10091
  selected: alias,
9596
10092
  ...previousRepo?.project ? { project: previousRepo.project } : {},
9597
10093
  linkedAt: new Date().toISOString(),
9598
- ...previousRepo?.serverProjectRoot && previousRepo.selected === alias ? { serverProjectRoot: previousRepo.serverProjectRoot } : {}
10094
+ ...preserveRemoteRoot ? {
10095
+ serverProjectRoot: previousRepo.serverProjectRoot,
10096
+ serverProjectRootAlias: previousRepo.serverProjectRootAlias,
10097
+ serverProjectRootBaseUrl: previousRepo.serverProjectRootBaseUrl
10098
+ } : {}
9599
10099
  };
9600
10100
  writeRepoConnection(context.projectRoot, repoState);
9601
- printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
10101
+ const remoteProjectLink = alias !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
10102
+ ok: false,
10103
+ status: "error",
10104
+ message: error instanceof Error ? error.message : String(error),
10105
+ hint: "Run `rig server repair-link` after authenticating the selected remote."
10106
+ })) : null;
10107
+ const latestRepo = readRepoConnection(context.projectRoot);
10108
+ const details = { selected: latestRepo?.selected ?? alias, repo: latestRepo, remoteProjectLink };
10109
+ printJsonOrText(context, details, formatSuccessCard("Rig server selected", [
9602
10110
  ["selected", alias],
9603
10111
  ["scope", "this repo"],
9604
- ["next", "rig task list"]
10112
+ ...remoteProjectLink ? [["remote link", remoteProjectLink.ok ? `ready (${remoteProjectLink.serverProjectRoot ?? "linked"})` : `${remoteProjectLink.status}: ${remoteProjectLink.hint}`]] : [],
10113
+ ["next", remoteProjectLink && !remoteProjectLink.ok ? "rig server repair-link" : "rig task list"]
9605
10114
  ]));
9606
- return { ok: true, group: options.group, command: "use", details: repoState };
10115
+ return { ok: true, group: options.group, command: "use", details };
10116
+ }
10117
+ case "repair-link": {
10118
+ const parsed = parseRepairLinkArgs(rest, `${usageName(options)} repair-link [--prepare|--backfill-only] [--repo owner/repo]`);
10119
+ const result = await repairRemoteProjectRootLink(context, parsed);
10120
+ printJsonOrText(context, result, formatRemoteProjectLinkText(result));
10121
+ return { ok: true, group: options.group, command: "repair-link", details: result };
9607
10122
  }
9608
10123
  case "status": {
9609
10124
  requireNoExtraArgs(rest, `${usageName(options)} status`);
9610
10125
  const repo = readRepoConnection(context.projectRoot);
9611
10126
  const global = readGlobalConnections();
9612
- const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
9613
- printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
10127
+ const remoteProjectLink = repo?.selected && repo.selected !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
10128
+ ok: false,
10129
+ status: "error",
10130
+ message: error instanceof Error ? error.message : String(error),
10131
+ hint: "Run `rig server repair-link` after authenticating the selected remote."
10132
+ })) : null;
10133
+ const latestRepo = readRepoConnection(context.projectRoot) ?? repo;
10134
+ const details = { selected: latestRepo?.selected ?? "local", repo: latestRepo, connections: global.connections, remoteProjectLink };
10135
+ printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections, latestRepo ?? null, remoteProjectLink));
9614
10136
  return { ok: true, group: options.group, command: "status", details };
9615
10137
  }
9616
10138
  default:
9617
10139
  throw new CliError(`Unknown ${options.group} command: ${String(command)}
9618
- Usage: ${usageName(options)} <list|add|use|status>`, 1);
10140
+ Usage: ${usageName(options)} <list|add|use|status|repair-link>`, 1);
9619
10141
  }
9620
10142
  }
9621
10143
  var init_connect = __esm(() => {
@@ -9623,13 +10145,14 @@ var init_connect = __esm(() => {
9623
10145
  init_runner();
9624
10146
  init__connection_state();
9625
10147
  init__cli_format();
10148
+ init__server_client();
9626
10149
  });
9627
10150
 
9628
10151
  // packages/cli/src/commands/server.ts
9629
10152
  import { resolveRigServerCommand } from "@rig/runtime/local-server";
9630
10153
  async function executeServer(context, args, options) {
9631
10154
  const [command = "status", ...rest] = args;
9632
- if (["status", "list", "add", "use"].includes(command)) {
10155
+ if (["status", "list", "add", "use", "repair-link"].includes(command)) {
9633
10156
  return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
9634
10157
  }
9635
10158
  switch (command) {
@@ -10411,7 +10934,7 @@ var init_task = __esm(() => {
10411
10934
  });
10412
10935
 
10413
10936
  // packages/cli/src/commands/task-run-driver.ts
10414
- import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
10937
+ import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync9 } from "fs";
10415
10938
  import { resolve as resolve23 } from "path";
10416
10939
  import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
10417
10940
  import { createInterface as createLineInterface } from "readline";
@@ -10514,7 +11037,7 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
10514
11037
  try {
10515
11038
  if (!statSync2(sourcePath).isFile())
10516
11039
  continue;
10517
- mkdirSync10(resolve23(targetPath, ".."), { recursive: true });
11040
+ mkdirSync11(resolve23(targetPath, ".."), { recursive: true });
10518
11041
  copyFileSync3(sourcePath, targetPath);
10519
11042
  copied += 1;
10520
11043
  } catch {}
@@ -11385,8 +11908,8 @@ async function executeRigOwnedTaskRun(context, input) {
11385
11908
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
11386
11909
  classifiedAt: new Date().toISOString()
11387
11910
  };
11388
- mkdirSync10(resolve23(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
11389
- writeFileSync8(resolve23(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
11911
+ mkdirSync11(resolve23(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
11912
+ writeFileSync9(resolve23(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
11390
11913
  `, "utf8");
11391
11914
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
11392
11915
  prompt = `${prompt}
@@ -11533,8 +12056,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
11533
12056
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
11534
12057
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
11535
12058
  if (readyFile) {
11536
- mkdirSync10(resolve23(readyFile, ".."), { recursive: true });
11537
- writeFileSync8(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
12059
+ mkdirSync11(resolve23(readyFile, ".."), { recursive: true });
12060
+ writeFileSync9(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
11538
12061
  `, "utf8");
11539
12062
  }
11540
12063
  appendRunLog(context.projectRoot, input.runId, {
@@ -12480,7 +13003,7 @@ var init_test = __esm(() => {
12480
13003
  });
12481
13004
 
12482
13005
  // packages/cli/src/commands/setup.ts
12483
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync3, writeFileSync as writeFileSync9 } from "fs";
13006
+ import { existsSync as existsSync16, mkdirSync as mkdirSync12, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
12484
13007
  import { resolve as resolve24 } from "path";
12485
13008
  import { createPluginHost } from "@rig/core";
12486
13009
  import {
@@ -12535,12 +13058,12 @@ function runSetupInit(projectRoot) {
12535
13058
  const stateDir = resolveControlPlaneHostStateDir(projectRoot);
12536
13059
  const logsDir = resolveControlPlaneHostLogsDir(projectRoot);
12537
13060
  const artifactsDir = resolveControlPlaneArtifactsDir(projectRoot);
12538
- mkdirSync11(stateDir, { recursive: true });
12539
- mkdirSync11(logsDir, { recursive: true });
12540
- mkdirSync11(artifactsDir, { recursive: true });
13061
+ mkdirSync12(stateDir, { recursive: true });
13062
+ mkdirSync12(logsDir, { recursive: true });
13063
+ mkdirSync12(artifactsDir, { recursive: true });
12541
13064
  const failuresPath = resolve24(stateDir, "failed_approaches.md");
12542
13065
  if (!existsSync16(failuresPath)) {
12543
- writeFileSync9(failuresPath, `# Failed Approaches
13066
+ writeFileSync10(failuresPath, `# Failed Approaches
12544
13067
 
12545
13068
  `, "utf-8");
12546
13069
  }