@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
@@ -215,7 +215,8 @@ __export(exports__connection_state, {
215
215
  resolveGlobalConnectionsPath: () => resolveGlobalConnectionsPath,
216
216
  readRepoConnection: () => readRepoConnection,
217
217
  readGlobalConnections: () => readGlobalConnections,
218
- isRemoteConnectionSelected: () => isRemoteConnectionSelected
218
+ isRemoteConnectionSelected: () => isRemoteConnectionSelected,
219
+ clearRepoServerProjectRoot: () => clearRepoServerProjectRoot
219
220
  });
220
221
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
221
222
  import { homedir } from "os";
@@ -299,12 +300,28 @@ function readRepoConnection(projectRoot) {
299
300
  selected,
300
301
  project: typeof record.project === "string" ? record.project : undefined,
301
302
  linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
302
- serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
303
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
304
+ serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
305
+ serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
303
306
  };
304
307
  }
305
308
  function writeRepoConnection(projectRoot, state) {
306
309
  writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
307
310
  }
311
+ function rootAllowedForSelection(repo, connection) {
312
+ const root = repo.serverProjectRoot?.trim();
313
+ if (!root)
314
+ return;
315
+ if (connection.kind === "remote") {
316
+ if (repo.serverProjectRootAlias !== repo.selected)
317
+ return;
318
+ if (repo.serverProjectRootBaseUrl !== connection.baseUrl)
319
+ return;
320
+ } else if (repo.serverProjectRootAlias && repo.serverProjectRootAlias !== repo.selected) {
321
+ return;
322
+ }
323
+ return root;
324
+ }
308
325
  function resolveSelectedConnection(projectRoot, options = {}) {
309
326
  const repo = readRepoConnection(projectRoot);
310
327
  if (!repo)
@@ -316,13 +333,41 @@ function resolveSelectedConnection(projectRoot, options = {}) {
316
333
  if (!connection) {
317
334
  throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
318
335
  }
319
- return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
336
+ return { alias: repo.selected, connection, serverProjectRoot: rootAllowedForSelection(repo, connection) };
320
337
  }
321
- function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
338
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot, metadata = {}) {
322
339
  const repo = readRepoConnection(projectRoot);
323
340
  if (!repo)
324
341
  return;
325
- writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
342
+ let inferred = metadata;
343
+ if (!inferred.alias || !inferred.baseUrl) {
344
+ try {
345
+ const selected = resolveSelectedConnection(projectRoot);
346
+ if (selected?.connection.kind === "remote") {
347
+ inferred = {
348
+ alias: inferred.alias ?? selected.alias,
349
+ baseUrl: inferred.baseUrl ?? selected.connection.baseUrl
350
+ };
351
+ }
352
+ } catch {}
353
+ }
354
+ writeRepoConnection(projectRoot, {
355
+ ...repo,
356
+ ...metadata.project ? { project: metadata.project } : {},
357
+ serverProjectRoot,
358
+ ...inferred.alias ? { serverProjectRootAlias: inferred.alias } : {},
359
+ ...inferred.baseUrl ? { serverProjectRootBaseUrl: inferred.baseUrl.replace(/\/+$/, "") } : {}
360
+ });
361
+ }
362
+ function clearRepoServerProjectRoot(projectRoot) {
363
+ const repo = readRepoConnection(projectRoot);
364
+ if (!repo)
365
+ return;
366
+ writeRepoConnection(projectRoot, {
367
+ selected: repo.selected,
368
+ ...repo.project ? { project: repo.project } : {},
369
+ ...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
370
+ });
326
371
  }
327
372
  function isRemoteConnectionSelected(projectRoot) {
328
373
  return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
@@ -349,12 +394,16 @@ __export(exports__server_client, {
349
394
  respondRunPiExtensionUiViaServer: () => respondRunPiExtensionUiViaServer,
350
395
  resolveServerConnectionLabel: () => resolveServerConnectionLabel,
351
396
  requestServerJson: () => requestServerJson,
397
+ repairRemoteProjectRootLink: () => repairRemoteProjectRootLink,
352
398
  registerProjectViaServer: () => registerProjectViaServer,
353
399
  prepareRemoteCheckoutViaServer: () => prepareRemoteCheckoutViaServer,
354
400
  postGitHubTokenViaServer: () => postGitHubTokenViaServer,
401
+ normalizeRepoSlug: () => normalizeRepoSlug,
355
402
  listWorkspaceTasksViaServer: () => listWorkspaceTasksViaServer,
356
403
  listRunsViaServer: () => listRunsViaServer,
357
404
  listGitHubProjectsViaServer: () => listGitHubProjectsViaServer,
405
+ isRemoteProjectRootLinkError: () => isRemoteProjectRootLinkError,
406
+ inspectRemoteProjectLink: () => inspectRemoteProjectLink,
358
407
  getWorkspaceTaskViaServer: () => getWorkspaceTaskViaServer,
359
408
  getRunTimelineViaServer: () => getRunTimelineViaServer,
360
409
  getRunPiStatusViaServer: () => getRunPiStatusViaServer,
@@ -366,13 +415,15 @@ __export(exports__server_client, {
366
415
  getRunDetailsViaServer: () => getRunDetailsViaServer,
367
416
  getGitHubProjectStatusFieldViaServer: () => getGitHubProjectStatusFieldViaServer,
368
417
  getGitHubAuthStatusViaServer: () => getGitHubAuthStatusViaServer,
418
+ formatRemoteProjectLinkHint: () => formatRemoteProjectLinkHint,
369
419
  ensureTaskLabelsViaServer: () => ensureTaskLabelsViaServer,
370
420
  ensureServerForCli: () => ensureServerForCli,
421
+ ensureRemoteProjectRootLink: () => ensureRemoteProjectRootLink,
371
422
  buildRunPiEventsWebSocketUrl: () => buildRunPiEventsWebSocketUrl,
372
423
  abortRunPiViaServer: () => abortRunPiViaServer
373
424
  });
374
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
375
- import { resolve as resolve2 } from "path";
425
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
426
+ import { dirname as dirname2, isAbsolute, resolve as resolve2 } from "path";
376
427
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
377
428
  function setServerPhaseListener(listener) {
378
429
  const previous = serverPhaseListener;
@@ -418,11 +469,10 @@ function readStoredGitHubAuthToken(projectRoot) {
418
469
  const parsed = readRemoteAuthState(projectRoot);
419
470
  return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
420
471
  }
421
- function inferRemoteServerProjectRootFromAuthState(projectRoot) {
422
- const repo = readRepoConnection(projectRoot);
423
- const slug = repo?.project?.trim();
424
- if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
472
+ function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
473
+ if (!/^[^/]+\/[^/]+$/.test(repoSlug))
425
474
  return null;
475
+ const repo = readRepoConnection(projectRoot);
426
476
  const auth = readRemoteAuthState(projectRoot);
427
477
  const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
428
478
  const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
@@ -431,25 +481,426 @@ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
431
481
  const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
432
482
  if (!checkoutBaseDir)
433
483
  return null;
434
- const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
435
- writeRepoServerProjectRoot(projectRoot, inferred);
436
- return inferred;
484
+ return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
437
485
  }
438
486
  function readLocalConnectionFallbackToken(projectRoot) {
439
487
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
440
488
  }
489
+ function normalizeRepoSlug(value) {
490
+ const slug = value?.trim();
491
+ return slug && /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
492
+ }
493
+ function readProjectLinkSlug(projectRoot) {
494
+ const path = resolve2(projectRoot, ".rig", "state", "project-link.json");
495
+ if (!existsSync2(path))
496
+ return null;
497
+ try {
498
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
499
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
500
+ return null;
501
+ return normalizeRepoSlug(typeof parsed.repoSlug === "string" ? String(parsed.repoSlug) : null);
502
+ } catch {
503
+ return null;
504
+ }
505
+ }
506
+ function writeProjectLinkConnection(projectRoot, repoSlug, alias) {
507
+ const path = resolve2(projectRoot, ".rig", "state", "project-link.json");
508
+ mkdirSync2(dirname2(path), { recursive: true });
509
+ writeFileSync2(path, `${JSON.stringify({ repoSlug, connection: alias, linkedAt: new Date().toISOString() }, null, 2)}
510
+ `, "utf8");
511
+ }
512
+ function remoteLinkRepairCommand(repoSlug) {
513
+ return repoSlug ? `rig server repair-link --repo ${repoSlug}` : "rig server repair-link --repo owner/repo";
514
+ }
515
+ function isRemoteProjectRootLinkError(error) {
516
+ const text = error instanceof Error ? error.message : String(error ?? "");
517
+ return /no server-host project root link|remote project(?:-root)? link|x-rig-project-root|serverProjectRoot/i.test(text);
518
+ }
519
+ function formatRemoteProjectLinkHint(resolution) {
520
+ const repair = remoteLinkRepairCommand(resolution.repoSlug);
521
+ if (resolution.status === "auth_required") {
522
+ 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}` : ""}\`.`;
523
+ }
524
+ if (resolution.status === "missing_project") {
525
+ return `Record the repo slug, then run \`${repair}\` (or \`rig init --repo owner/repo --server remote --github-auth device\`).`;
526
+ }
527
+ if (resolution.status === "not_remote")
528
+ return "Select a remote server with `rig server use <alias>` before repairing a remote project link.";
529
+ return `Run \`${repair}\` to backfill or prepare a server-host checkout for the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}.`;
530
+ }
531
+ function remoteProjectLinkFailure(input) {
532
+ const partial = {
533
+ status: input.status,
534
+ alias: input.alias,
535
+ baseUrl: input.baseUrl,
536
+ repoSlug: input.repoSlug
537
+ };
538
+ return {
539
+ ok: false,
540
+ ...input,
541
+ hint: formatRemoteProjectLinkHint(partial),
542
+ next: remoteLinkRepairCommand(input.repoSlug)
543
+ };
544
+ }
545
+ function remoteProjectLinkSuccess(input) {
546
+ return {
547
+ ok: true,
548
+ status: input.status,
549
+ alias: input.alias,
550
+ baseUrl: input.baseUrl,
551
+ repoSlug: input.repoSlug,
552
+ serverProjectRoot: input.serverProjectRoot,
553
+ source: input.source,
554
+ prepared: input.prepared ?? input.status === "prepared",
555
+ validated: input.validated ?? false,
556
+ message: input.message ?? `Remote project link ready for ${input.repoSlug}.`,
557
+ hint: "Remote-scoped task/run/status requests will send x-rig-project-root for this server-host checkout.",
558
+ next: "rig task list"
559
+ };
560
+ }
561
+ function localCheckoutPathCandidate(projectRoot, candidate) {
562
+ try {
563
+ const local = resolve2(projectRoot);
564
+ const resolved = resolve2(candidate);
565
+ return resolved === local || resolved.startsWith(`${local}/`);
566
+ } catch {
567
+ return false;
568
+ }
569
+ }
570
+ async function requestRemoteProjectLinkJson(baseUrl, pathname, authToken, init = {}) {
571
+ const requestUrl = new URL(`${baseUrl}${pathname}`);
572
+ if (authToken && queryAuthFallbackEnabled())
573
+ requestUrl.searchParams.set("rt", authToken);
574
+ const response = await fetch(requestUrl, {
575
+ ...init,
576
+ headers: mergeHeaders(init.headers, authToken)
577
+ });
578
+ const text = await response.text();
579
+ const payload = text.trim().length > 0 ? (() => {
580
+ try {
581
+ return JSON.parse(text);
582
+ } catch {
583
+ return null;
584
+ }
585
+ })() : null;
586
+ return { ok: response.ok, status: response.status, payload, text };
587
+ }
588
+ function payloadError(payload, fallback) {
589
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
590
+ const record = payload;
591
+ const value = record.error ?? record.message ?? record.reason;
592
+ if (typeof value === "string" && value.trim())
593
+ return value.trim();
594
+ }
595
+ return fallback;
596
+ }
597
+ function checkoutPathsFromProjectPayload(payload) {
598
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
599
+ return [];
600
+ const project = payload.project;
601
+ if (!project || typeof project !== "object" || Array.isArray(project))
602
+ return [];
603
+ const checkouts = project.checkouts;
604
+ if (!Array.isArray(checkouts))
605
+ return [];
606
+ return [...checkouts].reverse().flatMap((entry) => {
607
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
608
+ return [];
609
+ const path = entry.path;
610
+ return typeof path === "string" && path.trim() ? [path.trim()] : [];
611
+ });
612
+ }
613
+ function checkoutPathFromPreparePayload(payload) {
614
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
615
+ return null;
616
+ const checkout = payload.checkout;
617
+ if (!checkout || typeof checkout !== "object" || Array.isArray(checkout))
618
+ return null;
619
+ const path = checkout.path;
620
+ return typeof path === "string" && path.trim() ? path.trim() : null;
621
+ }
622
+ async function validateAndPersistRemoteRoot(input) {
623
+ const candidate = input.candidate.trim();
624
+ if (!candidate || !isAbsolute(candidate)) {
625
+ return remoteProjectLinkFailure({
626
+ status: "invalid_root",
627
+ alias: input.alias,
628
+ baseUrl: input.baseUrl,
629
+ repoSlug: input.repoSlug,
630
+ message: `Remote project root candidate is not an absolute server-host path: ${candidate || "(empty)"}.`
631
+ });
632
+ }
633
+ if (localCheckoutPathCandidate(input.projectRoot, candidate)) {
634
+ return remoteProjectLinkFailure({
635
+ status: "invalid_root",
636
+ alias: input.alias,
637
+ baseUrl: input.baseUrl,
638
+ repoSlug: input.repoSlug,
639
+ message: `Refusing to use local checkout path ${candidate} as a remote server project root.`
640
+ });
641
+ }
642
+ let response;
643
+ try {
644
+ response = await requestRemoteProjectLinkJson(input.baseUrl, "/api/server/project-root", input.authToken, {
645
+ method: "POST",
646
+ headers: { "content-type": "application/json" },
647
+ body: JSON.stringify({ projectRoot: candidate })
648
+ });
649
+ } catch (error) {
650
+ return remoteProjectLinkFailure({
651
+ status: "error",
652
+ alias: input.alias,
653
+ baseUrl: input.baseUrl,
654
+ repoSlug: input.repoSlug,
655
+ message: `Could not validate remote project root ${candidate}: ${error instanceof Error ? error.message : String(error)}`
656
+ });
657
+ }
658
+ if (response.status === 401 || response.status === 403) {
659
+ return remoteProjectLinkFailure({
660
+ status: "auth_required",
661
+ alias: input.alias,
662
+ baseUrl: input.baseUrl,
663
+ repoSlug: input.repoSlug,
664
+ statusCode: response.status,
665
+ message: `Remote server ${input.alias} requires authentication before validating project root ${candidate}.`
666
+ });
667
+ }
668
+ if (!response.ok) {
669
+ return remoteProjectLinkFailure({
670
+ status: "invalid_root",
671
+ alias: input.alias,
672
+ baseUrl: input.baseUrl,
673
+ repoSlug: input.repoSlug,
674
+ statusCode: response.status,
675
+ message: `Remote server rejected project root ${candidate} (${response.status}): ${payloadError(response.payload, response.text || "project-root validation failed")}`
676
+ });
677
+ }
678
+ const record = response.payload && typeof response.payload === "object" && !Array.isArray(response.payload) ? response.payload : {};
679
+ if (record.ok !== true) {
680
+ return remoteProjectLinkFailure({
681
+ status: "invalid_root",
682
+ alias: input.alias,
683
+ baseUrl: input.baseUrl,
684
+ repoSlug: input.repoSlug,
685
+ message: `Remote server did not accept project root ${candidate}: ${payloadError(response.payload, "project-root validation failed")}`
686
+ });
687
+ }
688
+ const accepted = typeof record.projectRoot === "string" && record.projectRoot.trim() ? record.projectRoot.trim() : typeof record.requestedProjectRoot === "string" && record.requestedProjectRoot.trim() ? record.requestedProjectRoot.trim() : candidate;
689
+ writeRepoServerProjectRoot(input.projectRoot, accepted, { alias: input.alias, baseUrl: input.baseUrl, project: input.repoSlug });
690
+ writeProjectLinkConnection(input.projectRoot, input.repoSlug, input.alias);
691
+ const status = input.status ?? (input.prepared ? "prepared" : "backfilled");
692
+ return remoteProjectLinkSuccess({
693
+ status,
694
+ alias: input.alias,
695
+ baseUrl: input.baseUrl,
696
+ repoSlug: input.repoSlug,
697
+ serverProjectRoot: accepted,
698
+ source: input.source,
699
+ prepared: input.prepared,
700
+ validated: true,
701
+ 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}.`
702
+ });
703
+ }
704
+ async function ensureRemoteProjectRootLink(projectRoot, options = {}) {
705
+ let selected;
706
+ try {
707
+ selected = resolveSelectedConnection(projectRoot);
708
+ } catch (error) {
709
+ return remoteProjectLinkFailure({ status: "error", message: error instanceof Error ? error.message : String(error) });
710
+ }
711
+ if (!selected || selected.connection.kind !== "remote") {
712
+ return remoteProjectLinkFailure({ status: "not_remote", message: "No remote Rig server is selected for this repo." });
713
+ }
714
+ const repo = readRepoConnection(projectRoot);
715
+ const explicitRepoRequested = options.repoSlug !== undefined;
716
+ const explicitRepoSlug = explicitRepoRequested ? normalizeRepoSlug(options.repoSlug) : null;
717
+ const repoProjectSlug = normalizeRepoSlug(repo?.project);
718
+ const repoSlug = explicitRepoSlug ?? repoProjectSlug ?? readProjectLinkSlug(projectRoot);
719
+ const alias = selected.alias;
720
+ const baseUrl = selected.connection.baseUrl;
721
+ const authToken = options.authToken === undefined ? readGitHubBearerTokenForRemote(projectRoot) : options.authToken;
722
+ const mode = options.mode ?? "backfill-only";
723
+ if (explicitRepoRequested && !explicitRepoSlug) {
724
+ return remoteProjectLinkFailure({
725
+ status: "missing_project",
726
+ alias,
727
+ baseUrl,
728
+ message: `Invalid --repo value ${JSON.stringify(options.repoSlug)}. Expected owner/repo.`
729
+ });
730
+ }
731
+ if (!repoSlug) {
732
+ return remoteProjectLinkFailure({
733
+ status: "missing_project",
734
+ alias,
735
+ baseUrl,
736
+ message: `Remote server ${alias} is selected, but this checkout has no GitHub repo slug recorded.`
737
+ });
738
+ }
739
+ const skippedCandidates = [];
740
+ const storedRoot = selected.serverProjectRoot?.trim() || null;
741
+ const storedRootMatchesResolvedRepo = repoProjectSlug === repoSlug;
742
+ if (storedRoot && storedRootMatchesResolvedRepo) {
743
+ if (localCheckoutPathCandidate(projectRoot, storedRoot)) {
744
+ clearRepoServerProjectRoot(projectRoot);
745
+ skippedCandidates.push(storedRoot);
746
+ } else {
747
+ const storedResult = await validateAndPersistRemoteRoot({
748
+ projectRoot,
749
+ alias,
750
+ baseUrl,
751
+ authToken,
752
+ repoSlug,
753
+ candidate: storedRoot,
754
+ source: "stored",
755
+ status: "ready"
756
+ });
757
+ if (storedResult.ok || storedResult.status === "auth_required" || storedResult.status === "error")
758
+ return storedResult;
759
+ clearRepoServerProjectRoot(projectRoot);
760
+ skippedCandidates.push(storedRoot);
761
+ }
762
+ } else if (storedRoot) {
763
+ skippedCandidates.push(storedRoot);
764
+ }
765
+ const authCandidate = inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug);
766
+ if (authCandidate) {
767
+ const authResult = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: authCandidate, source: "auth-state" });
768
+ if (authResult.ok || authResult.status === "auth_required")
769
+ return authResult;
770
+ }
771
+ let registryResponse;
772
+ try {
773
+ registryResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}`, authToken);
774
+ } catch (error) {
775
+ return remoteProjectLinkFailure({
776
+ status: "error",
777
+ alias,
778
+ baseUrl,
779
+ repoSlug,
780
+ message: `Could not read remote project registry for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
781
+ });
782
+ }
783
+ if (registryResponse.status === 401 || registryResponse.status === 403) {
784
+ return remoteProjectLinkFailure({
785
+ status: "auth_required",
786
+ alias,
787
+ baseUrl,
788
+ repoSlug,
789
+ statusCode: registryResponse.status,
790
+ message: `Remote server ${alias} requires authentication before reading the ${repoSlug} project registry.`
791
+ });
792
+ }
793
+ if (registryResponse.ok) {
794
+ const candidates = checkoutPathsFromProjectPayload(registryResponse.payload);
795
+ for (const candidate of candidates) {
796
+ const result = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate, source: "registry" });
797
+ if (result.ok || result.status === "auth_required")
798
+ return result;
799
+ skippedCandidates.push(candidate);
800
+ }
801
+ if (mode === "backfill-only") {
802
+ return remoteProjectLinkFailure({
803
+ status: candidates.length > 0 ? "invalid_root" : "no_server_checkout",
804
+ alias,
805
+ baseUrl,
806
+ repoSlug,
807
+ 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.`,
808
+ skippedCandidates
809
+ });
810
+ }
811
+ } else if (registryResponse.status === 404) {
812
+ if (mode === "backfill-only") {
813
+ return remoteProjectLinkFailure({
814
+ status: "project_not_registered",
815
+ alias,
816
+ baseUrl,
817
+ repoSlug,
818
+ statusCode: registryResponse.status,
819
+ message: `Remote server ${alias} has no registered project record for ${repoSlug}.`
820
+ });
821
+ }
822
+ } else {
823
+ return remoteProjectLinkFailure({
824
+ status: "error",
825
+ alias,
826
+ baseUrl,
827
+ repoSlug,
828
+ statusCode: registryResponse.status,
829
+ message: `Could not backfill ${repoSlug} from remote registry (${registryResponse.status}): ${payloadError(registryResponse.payload, registryResponse.text || "registry lookup failed")}`,
830
+ skippedCandidates
831
+ });
832
+ }
833
+ let prepareResponse;
834
+ try {
835
+ prepareResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}/prepare-checkout`, authToken, {
836
+ method: "POST",
837
+ headers: { "content-type": "application/json" },
838
+ body: JSON.stringify({ checkout: { kind: "managed-clone" }, repoUrl: `https://github.com/${repoSlug}.git` })
839
+ });
840
+ } catch (error) {
841
+ return remoteProjectLinkFailure({
842
+ status: "error",
843
+ alias,
844
+ baseUrl,
845
+ repoSlug,
846
+ message: `Could not prepare remote checkout for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
847
+ });
848
+ }
849
+ if (prepareResponse.status === 401 || prepareResponse.status === 403) {
850
+ return remoteProjectLinkFailure({
851
+ status: "auth_required",
852
+ alias,
853
+ baseUrl,
854
+ repoSlug,
855
+ statusCode: prepareResponse.status,
856
+ message: `Remote server ${alias} requires authentication before preparing ${repoSlug}.`
857
+ });
858
+ }
859
+ if (!prepareResponse.ok) {
860
+ return remoteProjectLinkFailure({
861
+ status: "error",
862
+ alias,
863
+ baseUrl,
864
+ repoSlug,
865
+ statusCode: prepareResponse.status,
866
+ message: `Remote checkout prepare failed for ${repoSlug} (${prepareResponse.status}): ${payloadError(prepareResponse.payload, prepareResponse.text || "prepare-checkout failed")}`,
867
+ skippedCandidates
868
+ });
869
+ }
870
+ const preparedPath = checkoutPathFromPreparePayload(prepareResponse.payload);
871
+ if (!preparedPath) {
872
+ return remoteProjectLinkFailure({
873
+ status: "invalid_root",
874
+ alias,
875
+ baseUrl,
876
+ repoSlug,
877
+ message: `Remote checkout prepare for ${repoSlug} did not return a checkout.path.`,
878
+ skippedCandidates
879
+ });
880
+ }
881
+ return validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: preparedPath, source: "prepare", prepared: true });
882
+ }
883
+ async function inspectRemoteProjectLink(projectRoot) {
884
+ return ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only" });
885
+ }
886
+ async function repairRemoteProjectRootLink(context, options = {}) {
887
+ const resolution = await ensureRemoteProjectRootLink(context.projectRoot, { mode: options.mode ?? "prepare-if-missing", repoSlug: options.repoSlug });
888
+ if (!resolution.ok)
889
+ throw new CliError(resolution.message, 1, { hint: resolution.hint });
890
+ return resolution;
891
+ }
441
892
  async function ensureServerForCli(projectRoot) {
442
893
  try {
443
894
  const selected = resolveSelectedConnection(projectRoot);
444
895
  if (selected?.connection.kind === "remote") {
445
896
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
446
897
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
447
- const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
898
+ const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
448
899
  return {
449
900
  baseUrl: selected.connection.baseUrl,
450
901
  authToken,
451
902
  connectionKind: "remote",
452
- serverProjectRoot
903
+ serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
453
904
  };
454
905
  }
455
906
  reportServerPhase("Starting local Rig server\u2026");
@@ -467,32 +918,6 @@ async function ensureServerForCli(projectRoot) {
467
918
  throw error;
468
919
  }
469
920
  }
470
- async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
471
- const repo = readRepoConnection(projectRoot);
472
- const slug = repo?.project?.trim();
473
- if (!slug)
474
- return null;
475
- try {
476
- const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
477
- if (authToken && queryAuthFallbackEnabled())
478
- url.searchParams.set("rt", authToken);
479
- const response = await fetch(url, {
480
- headers: mergeHeaders(undefined, authToken)
481
- });
482
- if (!response.ok)
483
- return null;
484
- const payload = await response.json();
485
- const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
486
- const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
487
- const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
488
- const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
489
- if (path)
490
- writeRepoServerProjectRoot(projectRoot, path);
491
- return path;
492
- } catch {
493
- return null;
494
- }
495
- }
496
921
  function appendTaskFilterParams(url, filters) {
497
922
  if (filters.assignee)
498
923
  url.searchParams.set("assignee", filters.assignee);
@@ -593,13 +1018,20 @@ function canUseRemoteWithoutProjectRoot(pathname) {
593
1018
  async function requestServerJson(context, pathname, init = {}) {
594
1019
  const server = await ensureServerForCli(context.projectRoot);
595
1020
  const requestUrl = new URL(`${server.baseUrl}${pathname}`);
596
- if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
597
- const repo = readRepoConnection(context.projectRoot);
598
- 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." });
1021
+ let scopedServerProjectRoot = server.serverProjectRoot;
1022
+ if (server.connectionKind === "remote" && !scopedServerProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
1023
+ const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: server.authToken });
1024
+ if (link.ok && link.serverProjectRoot) {
1025
+ scopedServerProjectRoot = link.serverProjectRoot;
1026
+ } else {
1027
+ const repo = readRepoConnection(context.projectRoot);
1028
+ const target = link.alias && link.baseUrl ? `${link.alias} (${link.baseUrl})` : server.baseUrl;
1029
+ 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 });
1030
+ }
599
1031
  }
600
1032
  const headers = mergeHeaders(init.headers, server.authToken);
601
- if (server.serverProjectRoot)
602
- headers.set("x-rig-project-root", server.serverProjectRoot);
1033
+ if (scopedServerProjectRoot)
1034
+ headers.set("x-rig-project-root", scopedServerProjectRoot);
603
1035
  if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
604
1036
  requestUrl.searchParams.set("rt", server.authToken);
605
1037
  }
@@ -1644,12 +2076,13 @@ var init__help_catalog = __esm(() => {
1644
2076
  {
1645
2077
  name: "server",
1646
2078
  summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
1647
- usage: ["rig server <status|list|add|use|start> [options]"],
2079
+ usage: ["rig server <status|list|add|use|repair-link|start> [options]"],
1648
2080
  commands: [
1649
- { command: "status", description: "Show the selected server for this repo.", primary: true },
2081
+ { command: "status", description: "Show the selected server and remote project-root link for this repo.", primary: true },
1650
2082
  { command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
1651
2083
  { command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
1652
2084
  { command: "use <alias>", description: "Select a saved remote server alias.", primary: true },
2085
+ { command: "repair-link [--prepare|--backfill-only] [--repo owner/repo]", description: "Backfill or prepare the selected remote's server-host checkout link.", primary: true },
1653
2086
  { command: "list", description: "List saved local/remote server aliases.", primary: true },
1654
2087
  { command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
1655
2088
  ],
@@ -1657,6 +2090,7 @@ var init__help_catalog = __esm(() => {
1657
2090
  "rig server status",
1658
2091
  "rig server add prod https://where.rig-does.work",
1659
2092
  "rig server use prod",
2093
+ "rig server repair-link --repo owner/repo",
1660
2094
  "rig server use local",
1661
2095
  "rig server start --port 3773"
1662
2096
  ],
@@ -2367,7 +2801,7 @@ function resolveControlPlaneDefinitionRoot(projectRoot) {
2367
2801
  var init__paths = () => {};
2368
2802
 
2369
2803
  // packages/cli/src/report-bug.ts
2370
- import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
2804
+ import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync3, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
2371
2805
  import { basename as basename2, extname, join, resolve as resolve7 } from "path";
2372
2806
  function slugifyBugTitle(value) {
2373
2807
  const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/g, "");
@@ -2406,8 +2840,8 @@ function createBugReportFiles(input) {
2406
2840
  }
2407
2841
  rmSync2(reportDir, { recursive: true, force: true });
2408
2842
  }
2409
- mkdirSync2(assetDir, { recursive: true });
2410
- mkdirSync2(screenshotDir, { recursive: true });
2843
+ mkdirSync3(assetDir, { recursive: true });
2844
+ mkdirSync3(screenshotDir, { recursive: true });
2411
2845
  const copiedScreenshots = copyEvidenceFiles(input.projectRoot, screenshotDir, input.screenshots ?? [], "screenshot");
2412
2846
  const copiedAssets = copyEvidenceFiles(input.projectRoot, assetDir, input.assets ?? [], "asset");
2413
2847
  const manifestAssets = [
@@ -2437,15 +2871,15 @@ function createBugReportFiles(input) {
2437
2871
  platform: process.platform,
2438
2872
  arch: process.arch
2439
2873
  };
2440
- writeFileSync2(join(assetDir, "README.md"), buildAssetReadme(input.title), "utf8");
2441
- writeFileSync2(join(screenshotDir, "README.md"), buildScreenshotReadme(input.title), "utf8");
2874
+ writeFileSync3(join(assetDir, "README.md"), buildAssetReadme(input.title), "utf8");
2875
+ writeFileSync3(join(screenshotDir, "README.md"), buildScreenshotReadme(input.title), "utf8");
2442
2876
  if (browserPath && browser) {
2443
- writeFileSync2(browserPath, `${JSON.stringify(browser, null, 2)}
2877
+ writeFileSync3(browserPath, `${JSON.stringify(browser, null, 2)}
2444
2878
  `, "utf8");
2445
2879
  }
2446
- writeFileSync2(manifestPath, `${JSON.stringify(manifest, null, 2)}
2880
+ writeFileSync3(manifestPath, `${JSON.stringify(manifest, null, 2)}
2447
2881
  `, "utf8");
2448
- writeFileSync2(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
2882
+ writeFileSync3(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
2449
2883
  return {
2450
2884
  slug,
2451
2885
  reportDir,
@@ -2622,7 +3056,7 @@ function formatAssetMarkdown(name, path) {
2622
3056
  var init_report_bug = () => {};
2623
3057
 
2624
3058
  // packages/cli/src/commands/task-report-bug.ts
2625
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
3059
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
2626
3060
  import { resolve as resolve8 } from "path";
2627
3061
  import pc2 from "picocolors";
2628
3062
  import {
@@ -2984,7 +3418,7 @@ function appendTaskConfigEntryPreservingText(taskConfigPath, issueId, entry) {
2984
3418
  `).map((line) => ` ${line}`).join(`
2985
3419
  `);
2986
3420
  if (!trimmed || trimmed === "{}") {
2987
- writeFileSync3(taskConfigPath, `{
3421
+ writeFileSync4(taskConfigPath, `{
2988
3422
  ${serializedEntry}
2989
3423
  }
2990
3424
  `, "utf8");
@@ -3000,7 +3434,7 @@ ${serializedEntry}
3000
3434
  throw new CliError(`Invalid trailing content in task config: ${taskConfigPath}`, 1, { hint: "Remove the trailing content so the file is a single JSON object, then retry." });
3001
3435
  }
3002
3436
  const comma = before.trim() === "{" ? "" : ",";
3003
- writeFileSync3(taskConfigPath, `${before}${comma}
3437
+ writeFileSync4(taskConfigPath, `${before}${comma}
3004
3438
  ${serializedEntry}
3005
3439
  }
3006
3440
  `, "utf8");
@@ -3385,7 +3819,7 @@ var init_task_report_bug = __esm(() => {
3385
3819
  });
3386
3820
 
3387
3821
  // packages/cli/src/commands/browser.ts
3388
- import { mkdirSync as mkdirSync3, rmSync as rmSync3 } from "fs";
3822
+ import { mkdirSync as mkdirSync4, rmSync as rmSync3 } from "fs";
3389
3823
  import { resolve as resolve9 } from "path";
3390
3824
  import { spawn } from "child_process";
3391
3825
  import { emitKeypressEvents } from "readline";
@@ -3963,7 +4397,7 @@ function readBrowserDemoCommandLine(command) {
3963
4397
  }
3964
4398
  async function launchBrowserDemo(runtime) {
3965
4399
  rmSync3(resolve9(runtime.browserDir, runtime.stateDir), { recursive: true, force: true });
3966
- mkdirSync3(resolve9(runtime.browserDir, runtime.stateDir), { recursive: true });
4400
+ mkdirSync4(resolve9(runtime.browserDir, runtime.stateDir), { recursive: true });
3967
4401
  const launcherPath = resolve9(runtime.browserDir, "scripts/electron-launcher.mjs");
3968
4402
  const launcher = await import(pathToFileURL(launcherPath).href);
3969
4403
  const electronPath = await launcher.resolveElectronPath();
@@ -4312,7 +4746,7 @@ var init_profile_and_review = __esm(() => {
4312
4746
  });
4313
4747
 
4314
4748
  // packages/cli/src/commands/_policy.ts
4315
- import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
4749
+ import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
4316
4750
  import { resolve as resolve10 } from "path";
4317
4751
  import { evaluate as evaluate2, loadPolicy as loadPolicy2, resolveAction as resolveAction2 } from "@rig/runtime/control-plane/runtime/guard";
4318
4752
  import { resolveHarnessPaths } from "@rig/runtime/control-plane/native/utils";
@@ -4329,7 +4763,7 @@ function appendControlledBashAudit(context, mode, command, args, matchedRuleIds,
4329
4763
  }
4330
4764
  const logsDir = resolveHarnessPaths(context.projectRoot).logsDir;
4331
4765
  const logFile = resolve10(logsDir, "controlled-bash.jsonl");
4332
- mkdirSync4(logsDir, { recursive: true });
4766
+ mkdirSync5(logsDir, { recursive: true });
4333
4767
  const payload = {
4334
4768
  timestamp: new Date().toISOString(),
4335
4769
  mode,
@@ -4623,9 +5057,9 @@ var exports_pi = {};
4623
5057
  __export(exports_pi, {
4624
5058
  executePi: () => executePi
4625
5059
  });
4626
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
5060
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4627
5061
  import { homedir as homedir4 } from "os";
4628
- import { dirname as dirname2, resolve as resolve12 } from "path";
5062
+ import { dirname as dirname3, resolve as resolve12 } from "path";
4629
5063
  function settingsPath(root) {
4630
5064
  return resolve12(root, ".pi", "settings.json");
4631
5065
  }
@@ -4650,8 +5084,8 @@ function packageKey(entry) {
4650
5084
  return JSON.stringify(entry);
4651
5085
  }
4652
5086
  function writeSettings(path, settings) {
4653
- mkdirSync5(dirname2(path), { recursive: true });
4654
- writeFileSync4(path, `${JSON.stringify(settings, null, 2)}
5087
+ mkdirSync6(dirname3(path), { recursive: true });
5088
+ writeFileSync5(path, `${JSON.stringify(settings, null, 2)}
4655
5089
  `, "utf-8");
4656
5090
  }
4657
5091
  async function searchNpmForPiExtensions(term) {
@@ -4834,6 +5268,14 @@ function permissionAllowsPr2(payload) {
4834
5268
  function isNotFoundError(error) {
4835
5269
  return /\b(404|not found)\b/i.test(message(error));
4836
5270
  }
5271
+ function isLoopbackBaseUrl(baseUrl) {
5272
+ try {
5273
+ const host = new URL(baseUrl).hostname.toLowerCase();
5274
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
5275
+ } catch {
5276
+ return false;
5277
+ }
5278
+ }
4837
5279
  function projectCheckoutReady(payload) {
4838
5280
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
4839
5281
  return null;
@@ -4888,25 +5330,37 @@ async function runFastTaskRunPreflight(context, options = {}) {
4888
5330
  }
4889
5331
  const repo = readRepoConnection(context.projectRoot);
4890
5332
  const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
4891
- 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>`."));
5333
+ let remoteRootBlocked = false;
5334
+ 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>`."));
5335
+ if (selectedServer?.connectionKind === "remote" && repo?.project && !selectedServer.serverProjectRoot) {
5336
+ const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: selectedServer.authToken });
5337
+ const loopbackDevRemote = !selectedServer.authToken && isLoopbackBaseUrl(selectedServer.baseUrl);
5338
+ 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));
5339
+ remoteRootBlocked = !link.ok && !loopbackDevRemote;
5340
+ }
4892
5341
  try {
4893
5342
  const auth = await request("/api/github/auth/status");
4894
5343
  checks.push(isAuthenticated2(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>`."));
4895
5344
  } catch (error) {
4896
5345
  checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
4897
5346
  }
4898
- try {
4899
- const projection = await request("/api/workspace/task-projection");
4900
- checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
4901
- } catch (error) {
4902
- checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
4903
- }
4904
- try {
4905
- const permissions = await request("/api/github/repo/permissions");
4906
- const allowed = permissionAllowsPr2(permissions);
4907
- 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."));
4908
- } catch (error) {
4909
- checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
5347
+ if (!remoteRootBlocked) {
5348
+ try {
5349
+ const projection = await request("/api/workspace/task-projection");
5350
+ checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
5351
+ } catch (error) {
5352
+ checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
5353
+ }
5354
+ try {
5355
+ const permissions = await request("/api/github/repo/permissions");
5356
+ const allowed = permissionAllowsPr2(permissions);
5357
+ 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."));
5358
+ } catch (error) {
5359
+ checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
5360
+ }
5361
+ } else {
5362
+ checks.push(preflightCheck("task-projection", "task projection ready", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
5363
+ 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."));
4910
5364
  }
4911
5365
  if (repo?.project) {
4912
5366
  try {
@@ -4918,19 +5372,24 @@ async function runFastTaskRunPreflight(context, options = {}) {
4918
5372
  }
4919
5373
  }
4920
5374
  if (taskId) {
4921
- try {
4922
- const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
4923
- const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
4924
- 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."));
4925
- } catch (error) {
4926
- checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
4927
- }
4928
- try {
4929
- const runs = await request("/api/runs?limit=200");
4930
- const duplicate = activeDuplicateRun(runs, taskId);
4931
- 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));
4932
- } catch (error) {
4933
- 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."));
5375
+ if (!remoteRootBlocked) {
5376
+ try {
5377
+ const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
5378
+ const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
5379
+ 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."));
5380
+ } catch (error) {
5381
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
5382
+ }
5383
+ try {
5384
+ const runs = await request("/api/runs?limit=200");
5385
+ const duplicate = activeDuplicateRun(runs, taskId);
5386
+ 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));
5387
+ } catch (error) {
5388
+ 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."));
5389
+ }
5390
+ } else {
5391
+ checks.push(preflightCheck("issue", "task/issue accessible", "fail", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
5392
+ 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."));
4934
5393
  }
4935
5394
  }
4936
5395
  if ((options.runtimeAdapter ?? "pi") === "pi") {
@@ -5411,7 +5870,7 @@ import {
5411
5870
  chmodSync,
5412
5871
  copyFileSync as copyFileSync2,
5413
5872
  existsSync as existsSync10,
5414
- mkdirSync as mkdirSync6,
5873
+ mkdirSync as mkdirSync7,
5415
5874
  readdirSync,
5416
5875
  readlinkSync,
5417
5876
  rmSync as rmSync4,
@@ -5516,7 +5975,7 @@ async function executeDist(context, args) {
5516
5975
  requireNoExtraArgs(pending, "rig dist install [--scope user|system] [--path <dir>]");
5517
5976
  const scope = parseInstallScope(scopeResult.value);
5518
5977
  const installDir = resolveInstallDir(scope, pathResult.value);
5519
- mkdirSync6(installDir, { recursive: true });
5978
+ mkdirSync7(installDir, { recursive: true });
5520
5979
  let source = await findLatestDistBinary(context.projectRoot);
5521
5980
  let buildDir = null;
5522
5981
  if (!source) {
@@ -5571,7 +6030,7 @@ async function executeDist(context, args) {
5571
6030
  const fp = await computeRuntimeImageFingerprint(context.projectRoot);
5572
6031
  const currentId = computeRuntimeImageId(fp);
5573
6032
  const imagesDir = resolve15(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
5574
- mkdirSync6(imagesDir, { recursive: true });
6033
+ mkdirSync7(imagesDir, { recursive: true });
5575
6034
  let pruned = 0;
5576
6035
  for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
5577
6036
  if (entry.isDirectory() && entry.name !== currentId) {
@@ -5583,9 +6042,9 @@ async function executeDist(context, args) {
5583
6042
  console.log(`Pruned ${pruned} stale image(s).`);
5584
6043
  }
5585
6044
  const imageDir = resolve15(imagesDir, currentId);
5586
- mkdirSync6(resolve15(imageDir, "bin/hooks"), { recursive: true });
5587
- mkdirSync6(resolve15(imageDir, "bin/plugins"), { recursive: true });
5588
- mkdirSync6(resolve15(imageDir, "bin/validators"), { recursive: true });
6045
+ mkdirSync7(resolve15(imageDir, "bin/hooks"), { recursive: true });
6046
+ mkdirSync7(resolve15(imageDir, "bin/plugins"), { recursive: true });
6047
+ mkdirSync7(resolve15(imageDir, "bin/validators"), { recursive: true });
5589
6048
  const hookNames = [
5590
6049
  "scope-guard",
5591
6050
  "import-guard",
@@ -5610,8 +6069,8 @@ async function executeDist(context, args) {
5610
6069
  const binPluginsDir = resolve15(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
5611
6070
  const validatorsRoot = resolve15(hostProjectRoot, "packages/runtime/src/control-plane/validators");
5612
6071
  const binValidatorsDir = resolve15(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
5613
- mkdirSync6(binPluginsDir, { recursive: true });
5614
- mkdirSync6(binValidatorsDir, { recursive: true });
6072
+ mkdirSync7(binPluginsDir, { recursive: true });
6073
+ mkdirSync7(binValidatorsDir, { recursive: true });
5615
6074
  if (existsSync10(pluginsDir)) {
5616
6075
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
5617
6076
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
@@ -6050,16 +6509,19 @@ function formatConnectionList(connections) {
6050
6509
  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(`
6051
6510
  `);
6052
6511
  }
6053
- function formatConnectionStatus(selected, connections) {
6512
+ function formatConnectionStatus(selected, connections, repo, remoteProjectLink) {
6054
6513
  const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
6055
6514
  const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
6515
+ 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";
6056
6516
  return [
6057
6517
  formatSection("Rig server", "selected for this repo"),
6058
6518
  `${themeFaint("\u2502")} ${themeDim("selected ")} ${pc4.bold(selected)}`,
6059
6519
  `${themeFaint("\u2502")} ${themeDim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
6060
6520
  `${themeFaint("\u2502")} ${themeDim("target ")} ${target ?? "not configured"}`,
6521
+ ...repo?.project ? [`${themeFaint("\u2502")} ${themeDim("project ")} ${repo.project}`] : [],
6522
+ ...connection?.kind === "remote" ? [`${themeFaint("\u2502")} ${themeDim("root ")} ${rootLabel}`] : [],
6061
6523
  "",
6062
- ...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
6524
+ ...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`"])
6063
6525
  ].join(`
6064
6526
  `);
6065
6527
  }
@@ -6350,7 +6812,7 @@ var init_inbox = __esm(() => {
6350
6812
 
6351
6813
  // packages/cli/src/commands/_snapshot-upload.ts
6352
6814
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
6353
- import { dirname as dirname3, resolve as resolve16, relative, sep } from "path";
6815
+ import { dirname as dirname4, resolve as resolve16, relative, sep } from "path";
6354
6816
  function toPosixPath(path) {
6355
6817
  return path.split(sep).join("/");
6356
6818
  }
@@ -6451,7 +6913,7 @@ __export(exports_init, {
6451
6913
  DEMO_TASKS_RELATIVE_DIR: () => DEMO_TASKS_RELATIVE_DIR,
6452
6914
  DEMO_TASKS: () => DEMO_TASKS
6453
6915
  });
6454
- import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
6916
+ import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
6455
6917
  import { spawnSync } from "child_process";
6456
6918
  import { basename as basename3, resolve as resolve17 } from "path";
6457
6919
  import { buildRigInitConfigSource } from "@rig/core";
@@ -6475,14 +6937,14 @@ function parseRepoSlug(value) {
6475
6937
  }
6476
6938
  function ensureRigPrivateDirs(projectRoot) {
6477
6939
  const rigDir = resolve17(projectRoot, ".rig");
6478
- mkdirSync7(resolve17(rigDir, "state"), { recursive: true });
6479
- mkdirSync7(resolve17(rigDir, "logs"), { recursive: true });
6480
- mkdirSync7(resolve17(rigDir, "runs"), { recursive: true });
6481
- mkdirSync7(resolve17(rigDir, "tmp"), { recursive: true });
6482
- mkdirSync7(resolve17(projectRoot, "artifacts"), { recursive: true });
6940
+ mkdirSync8(resolve17(rigDir, "state"), { recursive: true });
6941
+ mkdirSync8(resolve17(rigDir, "logs"), { recursive: true });
6942
+ mkdirSync8(resolve17(rigDir, "runs"), { recursive: true });
6943
+ mkdirSync8(resolve17(rigDir, "tmp"), { recursive: true });
6944
+ mkdirSync8(resolve17(projectRoot, "artifacts"), { recursive: true });
6483
6945
  const taskConfigPath = resolve17(rigDir, "task-config.json");
6484
6946
  if (!existsSync11(taskConfigPath))
6485
- writeFileSync5(taskConfigPath, `{}
6947
+ writeFileSync6(taskConfigPath, `{}
6486
6948
  `, "utf-8");
6487
6949
  }
6488
6950
  function ensureGitignoreEntries(projectRoot) {
@@ -6510,7 +6972,7 @@ function ensureRigConfigPackageDependencies(projectRoot) {
6510
6972
  ...existsSync11(path) ? existing : { name: "rig-project", private: true },
6511
6973
  devDependencies
6512
6974
  };
6513
- writeFileSync5(path, `${JSON.stringify(next, null, 2)}
6975
+ writeFileSync6(path, `${JSON.stringify(next, null, 2)}
6514
6976
  `, "utf8");
6515
6977
  }
6516
6978
  function applyGitHubProjectConfig(source, options) {
@@ -6536,9 +6998,9 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
6536
6998
  const selected = strategy ?? { kind: "managed-clone" };
6537
6999
  switch (selected.kind) {
6538
7000
  case "managed-clone":
6539
- return { kind: "managed-clone", path: projectRoot };
7001
+ return { kind: "managed-clone" };
6540
7002
  case "current-ref":
6541
- return { kind: "current-ref", path: projectRoot, ...selected.ref ? { ref: selected.ref } : {} };
7003
+ return { kind: "current-ref", ...selected.ref ? { ref: selected.ref } : {} };
6542
7004
  case "uploaded-snapshot":
6543
7005
  return { kind: "uploaded-snapshot", path: projectRoot, source: "local-working-tree" };
6544
7006
  case "existing-path":
@@ -6791,7 +7253,7 @@ function remoteGitHubAuthMetadata(payload) {
6791
7253
  };
6792
7254
  }
6793
7255
  function writeRemoteGitHubAuthState(projectRoot, input) {
6794
- writeFileSync5(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
7256
+ writeFileSync6(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
6795
7257
  authenticated: true,
6796
7258
  source: input.source,
6797
7259
  storedOnServer: true,
@@ -6839,7 +7301,7 @@ function runLocalFilesInit(context, options) {
6839
7301
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
6840
7302
  } else {
6841
7303
  const projectName = basename3(projectRoot) || "rig-project";
6842
- writeFileSync5(configTsPath, buildRigInitConfigSource({
7304
+ writeFileSync6(configTsPath, buildRigInitConfigSource({
6843
7305
  projectName,
6844
7306
  taskSource: { kind: "files", path: "tasks" },
6845
7307
  useStandardPlugin: true
@@ -6848,8 +7310,8 @@ function runLocalFilesInit(context, options) {
6848
7310
  ensureRigConfigPackageDependencies(projectRoot);
6849
7311
  const tasksDir = resolve17(projectRoot, "tasks");
6850
7312
  if (!existsSync11(tasksDir)) {
6851
- mkdirSync7(tasksDir, { recursive: true });
6852
- 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)}
7313
+ mkdirSync8(tasksDir, { recursive: true });
7314
+ 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)}
6853
7315
  `, "utf-8");
6854
7316
  }
6855
7317
  if (context.outputMode !== "json") {
@@ -6873,7 +7335,7 @@ function runDemoInit(context, options) {
6873
7335
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
6874
7336
  }
6875
7337
  } else {
6876
- writeFileSync5(configTsPath, buildRigInitConfigSource({
7338
+ writeFileSync6(configTsPath, buildRigInitConfigSource({
6877
7339
  projectName: basename3(projectRoot) || "rig-demo",
6878
7340
  taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
6879
7341
  useStandardPlugin: true
@@ -6882,14 +7344,14 @@ function runDemoInit(context, options) {
6882
7344
  }
6883
7345
  ensureRigConfigPackageDependencies(projectRoot);
6884
7346
  const demoTasksDir = resolve17(projectRoot, DEMO_TASKS_RELATIVE_DIR);
6885
- mkdirSync7(demoTasksDir, { recursive: true });
7347
+ mkdirSync8(demoTasksDir, { recursive: true });
6886
7348
  const taskIds = [];
6887
7349
  for (const task of DEMO_TASKS) {
6888
7350
  const id = String(task.id);
6889
7351
  taskIds.push(id);
6890
7352
  const taskPath = resolve17(demoTasksDir, `${id}.json`);
6891
7353
  if (!existsSync11(taskPath)) {
6892
- writeFileSync5(taskPath, `${JSON.stringify(task, null, 2)}
7354
+ writeFileSync6(taskPath, `${JSON.stringify(task, null, 2)}
6893
7355
  `, "utf-8");
6894
7356
  }
6895
7357
  }
@@ -6917,21 +7379,32 @@ function runDemoInit(context, options) {
6917
7379
  }
6918
7380
  async function runControlPlaneInit(context, options) {
6919
7381
  const projectRoot = context.projectRoot;
6920
- const detectedSlug = options.repoSlug ?? detectOriginRepoSlug(projectRoot);
7382
+ const existingRepoConnection = readRepoConnection(projectRoot);
7383
+ const selectedConnection = (() => {
7384
+ try {
7385
+ return resolveSelectedConnection(projectRoot);
7386
+ } catch {
7387
+ return null;
7388
+ }
7389
+ })();
7390
+ const selectedRemote = selectedConnection?.connection.kind === "remote" ? { alias: selectedConnection.alias, connection: selectedConnection.connection } : null;
7391
+ const detectedSlug = options.repoSlug ?? existingRepoConnection?.project ?? detectOriginRepoSlug(projectRoot);
7392
+ const serverKind = options.server ?? (selectedRemote ? "remote" : "local");
6921
7393
  if (!detectedSlug) {
6922
7394
  const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
6923
- if ((options.server ?? "local") === "local" && authMethod2 === "skip") {
7395
+ if (serverKind === "local" && authMethod2 === "skip") {
6924
7396
  return runLocalFilesInit(context, options);
6925
7397
  }
6926
7398
  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);
6927
7399
  }
6928
7400
  const repo = parseRepoSlug(detectedSlug);
6929
- const serverKind = options.server ?? "local";
6930
- const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : "remote");
7401
+ const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : selectedRemote?.alias ?? "remote");
7402
+ const selectedRemoteUrl = selectedRemote && (!options.connectionAlias || options.connectionAlias === selectedRemote.alias) ? selectedRemote.connection.baseUrl : undefined;
7403
+ const remoteUrl = serverKind === "remote" ? (options.remoteUrl ?? selectedRemoteUrl)?.replace(/\/+$/, "") : undefined;
6931
7404
  if (serverKind === "remote") {
6932
- if (!options.remoteUrl)
6933
- throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`." });
6934
- upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: options.remoteUrl });
7405
+ if (!remoteUrl)
7406
+ 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>`." });
7407
+ upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: remoteUrl });
6935
7408
  }
6936
7409
  writeRepoConnection(projectRoot, {
6937
7410
  selected: connectionAlias,
@@ -6954,11 +7427,11 @@ async function runControlPlaneInit(context, options) {
6954
7427
  taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
6955
7428
  useStandardPlugin: true
6956
7429
  }), options);
6957
- writeFileSync5(configTsPath, source, "utf-8");
7430
+ writeFileSync6(configTsPath, source, "utf-8");
6958
7431
  }
6959
7432
  ensureRigConfigPackageDependencies(projectRoot);
6960
7433
  }
6961
- writeFileSync5(resolve17(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
7434
+ writeFileSync6(resolve17(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
6962
7435
  `, "utf8");
6963
7436
  const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
6964
7437
  let uploadedSnapshot = null;
@@ -6973,7 +7446,7 @@ async function runControlPlaneInit(context, options) {
6973
7446
  let githubAuth = null;
6974
7447
  let deviceAuth = null;
6975
7448
  const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
6976
- const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${options.remoteUrl ?? "the remote Rig server"}.` : null;
7449
+ const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${remoteUrl ?? "the remote Rig server"}.` : null;
6977
7450
  if (remoteGhTokenWarning && !options.yes) {
6978
7451
  throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
6979
7452
  }
@@ -7353,8 +7826,8 @@ var init_init = __esm(() => {
7353
7826
 
7354
7827
  // packages/cli/src/commands/github.ts
7355
7828
  import { spawnSync as spawnSync2 } from "child_process";
7356
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync6 } from "fs";
7357
- import { dirname as dirname4, resolve as resolve18 } from "path";
7829
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
7830
+ import { dirname as dirname5, resolve as resolve18 } from "path";
7358
7831
  function printPayload(context, payload, fallback) {
7359
7832
  if (context.outputMode === "json")
7360
7833
  console.log(JSON.stringify(payload, null, 2));
@@ -7393,8 +7866,8 @@ function persistRemoteAuthSession(context, source, result, fallbackToken) {
7393
7866
  return;
7394
7867
  const repo = readRepoConnection(context.projectRoot);
7395
7868
  const path = resolve18(context.projectRoot, ".rig", "state", "github-auth.json");
7396
- mkdirSync8(dirname4(path), { recursive: true });
7397
- writeFileSync6(path, `${JSON.stringify({
7869
+ mkdirSync9(dirname5(path), { recursive: true });
7870
+ writeFileSync7(path, `${JSON.stringify({
7398
7871
  authenticated: true,
7399
7872
  source,
7400
7873
  storedOnServer: true,
@@ -8905,7 +9378,7 @@ __export(exports__pi_frontend, {
8905
9378
  buildOperatorPiEnv: () => buildOperatorPiEnv,
8906
9379
  attachRunBundledPiFrontend: () => attachRunBundledPiFrontend
8907
9380
  });
8908
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
9381
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
8909
9382
  import { homedir as homedir6, tmpdir } from "os";
8910
9383
  import { join as join3 } from "path";
8911
9384
  import { main as runPiMain } from "@earendil-works/pi-coding-agent";
@@ -8957,7 +9430,7 @@ function statusFromRunDetails(run) {
8957
9430
  async function prepareOperatorConsole(context, runId, tempSessionDir) {
8958
9431
  const server = await ensureServerForCli(context.projectRoot);
8959
9432
  const localCwd = join3(homedir6(), ".rig", "drones", runId.slice(0, 8));
8960
- mkdirSync9(localCwd, { recursive: true });
9433
+ mkdirSync10(localCwd, { recursive: true });
8961
9434
  trustDroneCwd(localCwd);
8962
9435
  installRigPiTheme();
8963
9436
  let sessionFileArg = [];
@@ -8979,7 +9452,7 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
8979
9452
  return line;
8980
9453
  }).join(`
8981
9454
  `);
8982
- writeFileSync7(localSessionPath, content);
9455
+ writeFileSync8(localSessionPath, content);
8983
9456
  sessionFileArg = ["--session", localSessionPath];
8984
9457
  }
8985
9458
  } catch {}
@@ -8995,12 +9468,12 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
8995
9468
  function trustDroneCwd(localCwd) {
8996
9469
  try {
8997
9470
  const agentDir = join3(homedir6(), ".pi", "agent");
8998
- mkdirSync9(agentDir, { recursive: true });
9471
+ mkdirSync10(agentDir, { recursive: true });
8999
9472
  const trustPath = join3(agentDir, "trust.json");
9000
9473
  const store = existsSync14(trustPath) ? JSON.parse(readFileSync10(trustPath, "utf8")) : {};
9001
9474
  if (store[localCwd] !== true) {
9002
9475
  store[localCwd] = true;
9003
- writeFileSync7(trustPath, `${JSON.stringify(store, null, "\t")}
9476
+ writeFileSync8(trustPath, `${JSON.stringify(store, null, "\t")}
9004
9477
  `);
9005
9478
  }
9006
9479
  } catch {}
@@ -9008,12 +9481,12 @@ function trustDroneCwd(localCwd) {
9008
9481
  function installRigPiTheme() {
9009
9482
  try {
9010
9483
  const themesDir = join3(homedir6(), ".pi", "agent", "themes");
9011
- mkdirSync9(themesDir, { recursive: true });
9484
+ mkdirSync10(themesDir, { recursive: true });
9012
9485
  const themePath = join3(themesDir, "rig.json");
9013
9486
  const next = `${JSON.stringify(RIG_PI_THEME, null, "\t")}
9014
9487
  `;
9015
9488
  if (!existsSync14(themePath) || readFileSync10(themePath, "utf8") !== next) {
9016
- writeFileSync7(themePath, next);
9489
+ writeFileSync8(themePath, next);
9017
9490
  }
9018
9491
  } catch {}
9019
9492
  }
@@ -9797,13 +10270,46 @@ function parseConnection(alias, value, options) {
9797
10270
  }
9798
10271
  return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
9799
10272
  }
9800
- function printJsonOrText(context, payload, text2) {
9801
- if (context.outputMode === "json") {
9802
- console.log(JSON.stringify(payload, null, 2));
9803
- } else {
10273
+ function printJsonOrText(context, _payload, text2) {
10274
+ if (context.outputMode !== "json") {
9804
10275
  console.log(text2);
9805
10276
  }
9806
10277
  }
10278
+ function formatRemoteProjectLinkText(result) {
10279
+ return formatSuccessCard(result.ok ? "Remote project link ready" : "Remote project link needs repair", [
10280
+ ["selected", result.alias ?? "remote"],
10281
+ ["target", result.baseUrl ?? "unknown"],
10282
+ ["repo", result.repoSlug ?? "owner/repo"],
10283
+ ["status", result.status],
10284
+ ...result.serverProjectRoot ? [["server root", result.serverProjectRoot]] : [],
10285
+ ["next", result.ok ? result.next ?? "rig task list" : result.hint]
10286
+ ]);
10287
+ }
10288
+ function parseRepairLinkArgs(rest, usage) {
10289
+ let repoSlug;
10290
+ let mode = "prepare-if-missing";
10291
+ const pending = [...rest];
10292
+ while (pending.length > 0) {
10293
+ const token = pending.shift();
10294
+ if (token === "--repo") {
10295
+ const value = pending.shift()?.trim();
10296
+ if (!value)
10297
+ throw new CliError(`Missing --repo value. Usage: ${usage}`, 1);
10298
+ const normalized = normalizeRepoSlug(value);
10299
+ if (!normalized)
10300
+ throw new CliError(`Invalid --repo value: ${value}`, 1, { hint: "Use the canonical owner/repo slug, e.g. `--repo humanity-org/humanwork`." });
10301
+ repoSlug = normalized;
10302
+ } else if (token === "--backfill-only") {
10303
+ mode = "backfill-only";
10304
+ } else if (token === "--prepare") {
10305
+ mode = "prepare-if-missing";
10306
+ } else {
10307
+ throw new CliError(`Unexpected argument: ${String(token)}
10308
+ Usage: ${usage}`, 1);
10309
+ }
10310
+ }
10311
+ return { repoSlug, mode };
10312
+ }
9807
10313
  async function promptForConnectionAlias(context) {
9808
10314
  const state = readGlobalConnections();
9809
10315
  const repo = readRepoConnection(context.projectRoot);
@@ -9857,37 +10363,67 @@ async function executeConnectionCommand(context, args, options) {
9857
10363
  }
9858
10364
  if (!alias)
9859
10365
  throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
10366
+ let selectedConnection = { kind: "local", mode: "auto" };
9860
10367
  if (alias !== "local") {
9861
10368
  const state = readGlobalConnections();
9862
- if (!state.connections[alias])
10369
+ const connection = state.connections[alias];
10370
+ if (!connection)
9863
10371
  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>`." });
10372
+ selectedConnection = connection;
9864
10373
  }
9865
10374
  const previousRepo = readRepoConnection(context.projectRoot);
10375
+ const preserveRemoteRoot = Boolean(previousRepo?.serverProjectRoot && previousRepo.selected === alias && selectedConnection.kind === "remote" && previousRepo.serverProjectRootAlias === alias && previousRepo.serverProjectRootBaseUrl === selectedConnection.baseUrl);
9866
10376
  const repoState = {
9867
10377
  selected: alias,
9868
10378
  ...previousRepo?.project ? { project: previousRepo.project } : {},
9869
10379
  linkedAt: new Date().toISOString(),
9870
- ...previousRepo?.serverProjectRoot && previousRepo.selected === alias ? { serverProjectRoot: previousRepo.serverProjectRoot } : {}
10380
+ ...preserveRemoteRoot ? {
10381
+ serverProjectRoot: previousRepo.serverProjectRoot,
10382
+ serverProjectRootAlias: previousRepo.serverProjectRootAlias,
10383
+ serverProjectRootBaseUrl: previousRepo.serverProjectRootBaseUrl
10384
+ } : {}
9871
10385
  };
9872
10386
  writeRepoConnection(context.projectRoot, repoState);
9873
- printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
10387
+ const remoteProjectLink = alias !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
10388
+ ok: false,
10389
+ status: "error",
10390
+ message: error instanceof Error ? error.message : String(error),
10391
+ hint: "Run `rig server repair-link` after authenticating the selected remote."
10392
+ })) : null;
10393
+ const latestRepo = readRepoConnection(context.projectRoot);
10394
+ const details = { selected: latestRepo?.selected ?? alias, repo: latestRepo, remoteProjectLink };
10395
+ printJsonOrText(context, details, formatSuccessCard("Rig server selected", [
9874
10396
  ["selected", alias],
9875
10397
  ["scope", "this repo"],
9876
- ["next", "rig task list"]
10398
+ ...remoteProjectLink ? [["remote link", remoteProjectLink.ok ? `ready (${remoteProjectLink.serverProjectRoot ?? "linked"})` : `${remoteProjectLink.status}: ${remoteProjectLink.hint}`]] : [],
10399
+ ["next", remoteProjectLink && !remoteProjectLink.ok ? "rig server repair-link" : "rig task list"]
9877
10400
  ]));
9878
- return { ok: true, group: options.group, command: "use", details: repoState };
10401
+ return { ok: true, group: options.group, command: "use", details };
10402
+ }
10403
+ case "repair-link": {
10404
+ const parsed = parseRepairLinkArgs(rest, `${usageName(options)} repair-link [--prepare|--backfill-only] [--repo owner/repo]`);
10405
+ const result = await repairRemoteProjectRootLink(context, parsed);
10406
+ printJsonOrText(context, result, formatRemoteProjectLinkText(result));
10407
+ return { ok: true, group: options.group, command: "repair-link", details: result };
9879
10408
  }
9880
10409
  case "status": {
9881
10410
  requireNoExtraArgs(rest, `${usageName(options)} status`);
9882
10411
  const repo = readRepoConnection(context.projectRoot);
9883
10412
  const global = readGlobalConnections();
9884
- const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
9885
- printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
10413
+ const remoteProjectLink = repo?.selected && repo.selected !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
10414
+ ok: false,
10415
+ status: "error",
10416
+ message: error instanceof Error ? error.message : String(error),
10417
+ hint: "Run `rig server repair-link` after authenticating the selected remote."
10418
+ })) : null;
10419
+ const latestRepo = readRepoConnection(context.projectRoot) ?? repo;
10420
+ const details = { selected: latestRepo?.selected ?? "local", repo: latestRepo, connections: global.connections, remoteProjectLink };
10421
+ printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections, latestRepo ?? null, remoteProjectLink));
9886
10422
  return { ok: true, group: options.group, command: "status", details };
9887
10423
  }
9888
10424
  default:
9889
10425
  throw new CliError(`Unknown ${options.group} command: ${String(command)}
9890
- Usage: ${usageName(options)} <list|add|use|status>`, 1);
10426
+ Usage: ${usageName(options)} <list|add|use|status|repair-link>`, 1);
9891
10427
  }
9892
10428
  }
9893
10429
  var init_connect = __esm(() => {
@@ -9895,13 +10431,14 @@ var init_connect = __esm(() => {
9895
10431
  init_runner();
9896
10432
  init__connection_state();
9897
10433
  init__cli_format();
10434
+ init__server_client();
9898
10435
  });
9899
10436
 
9900
10437
  // packages/cli/src/commands/server.ts
9901
10438
  import { resolveRigServerCommand } from "@rig/runtime/local-server";
9902
10439
  async function executeServer(context, args, options) {
9903
10440
  const [command = "status", ...rest] = args;
9904
- if (["status", "list", "add", "use"].includes(command)) {
10441
+ if (["status", "list", "add", "use", "repair-link"].includes(command)) {
9905
10442
  return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
9906
10443
  }
9907
10444
  switch (command) {
@@ -10688,7 +11225,7 @@ var init_task = __esm(() => {
10688
11225
  });
10689
11226
 
10690
11227
  // packages/cli/src/commands/task-run-driver.ts
10691
- import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
11228
+ import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync9 } from "fs";
10692
11229
  import { resolve as resolve23 } from "path";
10693
11230
  import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
10694
11231
  import { createInterface as createLineInterface } from "readline";
@@ -10791,7 +11328,7 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
10791
11328
  try {
10792
11329
  if (!statSync2(sourcePath).isFile())
10793
11330
  continue;
10794
- mkdirSync10(resolve23(targetPath, ".."), { recursive: true });
11331
+ mkdirSync11(resolve23(targetPath, ".."), { recursive: true });
10795
11332
  copyFileSync3(sourcePath, targetPath);
10796
11333
  copied += 1;
10797
11334
  } catch {}
@@ -11662,8 +12199,8 @@ async function executeRigOwnedTaskRun(context, input) {
11662
12199
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
11663
12200
  classifiedAt: new Date().toISOString()
11664
12201
  };
11665
- mkdirSync10(resolve23(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
11666
- writeFileSync8(resolve23(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
12202
+ mkdirSync11(resolve23(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
12203
+ writeFileSync9(resolve23(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
11667
12204
  `, "utf8");
11668
12205
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
11669
12206
  prompt = `${prompt}
@@ -11810,8 +12347,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
11810
12347
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
11811
12348
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
11812
12349
  if (readyFile) {
11813
- mkdirSync10(resolve23(readyFile, ".."), { recursive: true });
11814
- writeFileSync8(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
12350
+ mkdirSync11(resolve23(readyFile, ".."), { recursive: true });
12351
+ writeFileSync9(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
11815
12352
  `, "utf8");
11816
12353
  }
11817
12354
  appendRunLog(context.projectRoot, input.runId, {
@@ -12757,7 +13294,7 @@ var init_test = __esm(() => {
12757
13294
  });
12758
13295
 
12759
13296
  // packages/cli/src/commands/setup.ts
12760
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync3, writeFileSync as writeFileSync9 } from "fs";
13297
+ import { existsSync as existsSync16, mkdirSync as mkdirSync12, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
12761
13298
  import { resolve as resolve24 } from "path";
12762
13299
  import { createPluginHost } from "@rig/core";
12763
13300
  import {
@@ -12812,12 +13349,12 @@ function runSetupInit(projectRoot) {
12812
13349
  const stateDir = resolveControlPlaneHostStateDir(projectRoot);
12813
13350
  const logsDir = resolveControlPlaneHostLogsDir(projectRoot);
12814
13351
  const artifactsDir = resolveControlPlaneArtifactsDir(projectRoot);
12815
- mkdirSync11(stateDir, { recursive: true });
12816
- mkdirSync11(logsDir, { recursive: true });
12817
- mkdirSync11(artifactsDir, { recursive: true });
13352
+ mkdirSync12(stateDir, { recursive: true });
13353
+ mkdirSync12(logsDir, { recursive: true });
13354
+ mkdirSync12(artifactsDir, { recursive: true });
12818
13355
  const failuresPath = resolve24(stateDir, "failed_approaches.md");
12819
13356
  if (!existsSync16(failuresPath)) {
12820
- writeFileSync9(failuresPath, `# Failed Approaches
13357
+ writeFileSync10(failuresPath, `# Failed Approaches
12821
13358
 
12822
13359
  `, "utf-8");
12823
13360
  }
@@ -16007,6 +16544,7 @@ async function stopRunFromDetail(ctx, runId) {
16007
16544
  }
16008
16545
 
16009
16546
  // packages/cli/src/app-opentui/adapters/server.ts
16547
+ import { spawnSync as spawnSync5 } from "child_process";
16010
16548
  var SERVER_PROBE_TIMEOUT_MS = Number(process.env.RIG_SERVER_PROBE_TIMEOUT_MS) || 5000;
16011
16549
  function withTimeout(promise, ms, label) {
16012
16550
  return new Promise((resolve28, reject) => {
@@ -16044,11 +16582,128 @@ function startLocalRequested(ctx) {
16044
16582
  const action = intent.action;
16045
16583
  return action?.payload?.startLocal === true;
16046
16584
  }
16585
+ async function serverAliases(projectRoot) {
16586
+ const { readGlobalConnections: readGlobalConnections2, readRepoConnection: readRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16587
+ const selected = readRepoConnection2(projectRoot)?.selected ?? "local";
16588
+ const global = readGlobalConnections2();
16589
+ const aliases = [{ alias: "local", kind: "local", selected: selected === "local" }];
16590
+ for (const [alias, connection] of Object.entries(global.connections)) {
16591
+ aliases.push({
16592
+ alias,
16593
+ kind: connection.kind,
16594
+ ...connection.kind === "remote" ? { baseUrl: connection.baseUrl } : {},
16595
+ selected: alias === selected
16596
+ });
16597
+ }
16598
+ return aliases;
16599
+ }
16600
+ function firstString2(parts, offset = 0) {
16601
+ return parts.slice(offset).find((part) => part.trim() && !part.startsWith("-"))?.trim();
16602
+ }
16603
+ function normalizeUrl(value) {
16604
+ const trimmed = value?.trim().replace(/\/+$/, "");
16605
+ if (!trimmed)
16606
+ return;
16607
+ return /^https?:\/\//i.test(trimmed) ? trimmed : undefined;
16608
+ }
16609
+ async function useLocalServerFromApp(ctx) {
16610
+ const label = "Selecting local server";
16611
+ emitStarted(ctx, label);
16612
+ try {
16613
+ const projectRoot = projectRootOf(ctx);
16614
+ const { readRepoConnection: readRepoConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16615
+ const previous = readRepoConnection2(projectRoot);
16616
+ writeRepoConnection2(projectRoot, {
16617
+ selected: "local",
16618
+ ...previous?.project ? { project: previous.project } : {},
16619
+ linkedAt: new Date().toISOString()
16620
+ });
16621
+ emitCompleted(ctx, label, { selected: "local" });
16622
+ return refreshServerStatus(ctx);
16623
+ } catch (error) {
16624
+ emitFailed(ctx, label, error);
16625
+ throw error;
16626
+ }
16627
+ }
16628
+ async function useRemoteServerFromApp(ctx, alias) {
16629
+ const label = alias ? `Selecting remote ${alias}` : "Selecting remote server";
16630
+ emitStarted(ctx, label);
16631
+ try {
16632
+ const cleanAlias = alias?.trim();
16633
+ if (!cleanAlias)
16634
+ throw new Error("Enter a saved remote alias, or add one with: add remote \u2192 <alias> <https-url>.");
16635
+ const projectRoot = projectRootOf(ctx);
16636
+ const { readGlobalConnections: readGlobalConnections2, readRepoConnection: readRepoConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16637
+ const connection = readGlobalConnections2().connections[cleanAlias];
16638
+ if (!connection)
16639
+ throw new Error(`Remote alias ${cleanAlias} is not saved yet. Use add remote first.`);
16640
+ if (connection.kind !== "remote")
16641
+ throw new Error(`${cleanAlias} is not a remote server alias.`);
16642
+ const previous = readRepoConnection2(projectRoot);
16643
+ writeRepoConnection2(projectRoot, {
16644
+ selected: cleanAlias,
16645
+ ...previous?.project ? { project: previous.project } : {},
16646
+ linkedAt: new Date().toISOString()
16647
+ });
16648
+ emitCompleted(ctx, label, { selected: cleanAlias, baseUrl: connection.baseUrl });
16649
+ return refreshServerStatus(ctx);
16650
+ } catch (error) {
16651
+ emitFailed(ctx, label, error);
16652
+ throw error;
16653
+ }
16654
+ }
16655
+ async function addRemoteServerFromApp(ctx, args) {
16656
+ const label = "Adding remote server";
16657
+ emitStarted(ctx, label);
16658
+ try {
16659
+ const alias = firstString2(args, 2);
16660
+ const url = normalizeUrl(firstString2(args, alias ? args.indexOf(alias) + 1 : 3));
16661
+ if (!alias || !url)
16662
+ throw new Error("Add remote expects: <alias> <https-url>. Example: prod https://rig.example.com");
16663
+ const projectRoot = projectRootOf(ctx);
16664
+ const { readRepoConnection: readRepoConnection2, upsertGlobalConnection: upsertGlobalConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16665
+ upsertGlobalConnection2(alias, { kind: "remote", baseUrl: url });
16666
+ const previous = readRepoConnection2(projectRoot);
16667
+ writeRepoConnection2(projectRoot, {
16668
+ selected: alias,
16669
+ ...previous?.project ? { project: previous.project } : {},
16670
+ linkedAt: new Date().toISOString()
16671
+ });
16672
+ emitCompleted(ctx, label, { alias, baseUrl: url });
16673
+ return refreshServerStatus(ctx);
16674
+ } catch (error) {
16675
+ emitFailed(ctx, label, error);
16676
+ throw error;
16677
+ }
16678
+ }
16679
+ async function importGitHubAuthFromGhApp(ctx) {
16680
+ const label = "Importing GitHub auth";
16681
+ emitStarted(ctx, label);
16682
+ try {
16683
+ const projectRoot = projectRootOf(ctx);
16684
+ const token = spawnSync5("gh", ["auth", "token"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 1e4 }).stdout.trim();
16685
+ if (!token)
16686
+ throw new Error("Could not read GitHub token from gh. Sign in with gh auth login, then retry import auth.");
16687
+ const runtime = await runtimeOf(ctx);
16688
+ const { postGitHubTokenViaServer: postGitHubTokenViaServer2, setGitHubBearerTokenForCurrentProcess: setGitHubBearerTokenForCurrentProcess2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
16689
+ const { readRepoConnection: readRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16690
+ const selectedRepo = readRepoConnection2(projectRoot)?.project;
16691
+ const payload = await postGitHubTokenViaServer2(runtime, token, selectedRepo ? { selectedRepo } : {});
16692
+ const apiSessionToken = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.apiSessionToken : undefined;
16693
+ setGitHubBearerTokenForCurrentProcess2(typeof apiSessionToken === "string" ? apiSessionToken : token, projectRoot);
16694
+ emitCompleted(ctx, label, { selectedRepo: selectedRepo ?? null });
16695
+ return refreshServerStatus(ctx);
16696
+ } catch (error) {
16697
+ emitFailed(ctx, label, error);
16698
+ throw error;
16699
+ }
16700
+ }
16047
16701
  async function startLocalServerFromApp(ctx) {
16048
16702
  const label = "Starting local server";
16049
16703
  emitStarted(ctx, label);
16050
16704
  patchData(ctx, {
16051
16705
  lastIntent: undefined,
16706
+ remoteProjectLink: undefined,
16052
16707
  server: { label: "starting local server", reachable: false, error: "spawning local rig-server (detached)\u2026" }
16053
16708
  });
16054
16709
  try {
@@ -16073,32 +16728,61 @@ async function startLocalServerFromApp(ctx) {
16073
16728
  reachable: false,
16074
16729
  error: error instanceof Error ? error.message : String(error)
16075
16730
  };
16076
- patchData(ctx, { server: state, lastRefreshError: state.error });
16731
+ patchData(ctx, { server: state, remoteProjectLink: undefined, lastRefreshError: state.error });
16077
16732
  patchFooter(ctx, { server: "unavailable" });
16078
- emitCompleted(ctx, label, { server: state.label, reachable: false, error: state.error });
16733
+ emitCompleted(ctx, label, { serverLabel: state.label, reachable: false, error: state.error });
16079
16734
  return state;
16080
16735
  }
16081
16736
  }
16737
+ async function repairRemoteProjectRootLinkFromApp(ctx) {
16738
+ const label = "Repairing remote project link";
16739
+ emitStarted(ctx, label);
16740
+ try {
16741
+ const projectRoot = projectRootOf(ctx);
16742
+ const { ensureRemoteProjectRootLink: ensureRemoteProjectRootLink2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
16743
+ emitProgress(ctx, label, "backfilling or preparing server-host checkout");
16744
+ const result = await ensureRemoteProjectRootLink2(projectRoot, { mode: "prepare-if-missing" });
16745
+ patchData(ctx, { remoteProjectLink: result, server: { label: result.alias ?? "remote", baseUrl: result.baseUrl, kind: "remote", reachable: result.status !== "not_remote", remoteProjectLink: result } });
16746
+ patchFooter(ctx, { server: result.alias ?? "remote", message: `remote link ${result.status}` });
16747
+ if (!result.ok) {
16748
+ const error = new Error(result.message);
16749
+ error.hint = result.hint;
16750
+ patchData(ctx, { lastRefreshError: result.message });
16751
+ throw error;
16752
+ }
16753
+ emitCompleted(ctx, label, { repoSlug: result.repoSlug, serverProjectRoot: result.serverProjectRoot, status: result.status });
16754
+ return result;
16755
+ } catch (error) {
16756
+ const message2 = error instanceof Error ? error.message : String(error);
16757
+ const existing = ctx.getState().data?.remoteProjectLink;
16758
+ const remoteProjectLink = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : { ok: false, status: "error", message: message2 };
16759
+ patchData(ctx, { lastRefreshError: message2, remoteProjectLink });
16760
+ emitCompleted(ctx, label, { ok: false, error: message2, remoteProjectLink });
16761
+ throw error;
16762
+ }
16763
+ }
16082
16764
  async function refreshServerStatus(ctx) {
16083
16765
  if (startLocalRequested(ctx)) {
16084
16766
  return startLocalServerFromApp(ctx);
16085
16767
  }
16086
16768
  const label = "Checking server";
16087
16769
  emitStarted(ctx, label);
16088
- patchData(ctx, { server: { label: "checking selected server", reachable: false, error: "checking selected server\u2026" } });
16770
+ patchData(ctx, { server: { label: "checking selected server", reachable: false, error: "checking selected server\u2026" }, remoteProjectLink: undefined });
16089
16771
  const started = Date.now();
16090
16772
  try {
16091
- const { ensureServerForCli: ensureServerForCli2, requestServerJson: requestServerJson2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
16773
+ const { ensureRemoteProjectRootLink: ensureRemoteProjectRootLink2, ensureServerForCli: ensureServerForCli2, requestServerJson: requestServerJson2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
16092
16774
  const projectRoot = projectRootOf(ctx);
16775
+ const aliases = await serverAliases(projectRoot);
16093
16776
  emitProgress(ctx, label, "resolving selected server");
16094
16777
  const [connectionLabel, server] = await Promise.all([
16095
16778
  resolveServerConnectionLabel2(projectRoot),
16096
16779
  withTimeout(ensureServerForCli2(projectRoot), 5000, "server connection")
16097
16780
  ]);
16098
16781
  emitProgress(ctx, label, "requesting server status", { baseUrl: server.baseUrl, kind: server.connectionKind });
16099
- const [status, auth] = await Promise.all([
16782
+ const [status, auth, remoteProjectLink] = await Promise.all([
16100
16783
  withRetry("server status", () => withTimeout(requestServerJson2({ projectRoot }, "/api/server/status"), SERVER_PROBE_TIMEOUT_MS, "server status")).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) })),
16101
- withRetry("github auth status", () => withTimeout(requestServerJson2({ projectRoot }, "/api/github/auth/status"), SERVER_PROBE_TIMEOUT_MS, "github auth status")).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }))
16784
+ withRetry("github auth status", () => withTimeout(requestServerJson2({ projectRoot }, "/api/github/auth/status"), SERVER_PROBE_TIMEOUT_MS, "github auth status")).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) })),
16785
+ server.connectionKind === "remote" ? withTimeout(ensureRemoteProjectRootLink2(projectRoot, { mode: "backfill-only", authToken: server.authToken }), SERVER_PROBE_TIMEOUT_MS, "remote project link").catch((error) => ({ ok: false, status: "error", message: error instanceof Error ? error.message : String(error) })) : Promise.resolve(null)
16102
16786
  ]);
16103
16787
  const state = {
16104
16788
  label: connectionLabel,
@@ -16107,22 +16791,25 @@ async function refreshServerStatus(ctx) {
16107
16791
  reachable: true,
16108
16792
  latencyMs: Date.now() - started,
16109
16793
  status: status && typeof status === "object" && !Array.isArray(status) ? status : {},
16110
- auth: auth && typeof auth === "object" && !Array.isArray(auth) ? auth : {}
16794
+ auth: auth && typeof auth === "object" && !Array.isArray(auth) ? auth : {},
16795
+ aliases,
16796
+ ...remoteProjectLink && typeof remoteProjectLink === "object" && !Array.isArray(remoteProjectLink) ? { remoteProjectLink } : {}
16111
16797
  };
16112
- patchData(ctx, { server: state });
16798
+ patchData(ctx, { server: state, remoteProjectLink: state.remoteProjectLink });
16113
16799
  patchFooter(ctx, { server: connectionLabel, latency: `${state.latencyMs}ms` });
16114
- emitCompleted(ctx, label, { server: connectionLabel, latencyMs: state.latencyMs });
16800
+ emitCompleted(ctx, label, { serverLabel: connectionLabel, latencyMs: state.latencyMs });
16115
16801
  return state;
16116
16802
  } catch (error) {
16117
16803
  const state = {
16118
16804
  label: "server unavailable",
16119
16805
  reachable: false,
16120
16806
  latencyMs: Date.now() - started,
16807
+ aliases: await serverAliases(projectRootOf(ctx)).catch(() => []),
16121
16808
  error: error instanceof Error ? error.message : String(error)
16122
16809
  };
16123
- patchData(ctx, { server: state });
16810
+ patchData(ctx, { server: state, remoteProjectLink: undefined });
16124
16811
  patchFooter(ctx, { server: "unavailable" });
16125
- emitCompleted(ctx, label, { server: state.label, reachable: false, error: state.error });
16812
+ emitCompleted(ctx, label, { serverLabel: state.label, reachable: false, error: state.error });
16126
16813
  return state;
16127
16814
  }
16128
16815
  }
@@ -16992,7 +17679,7 @@ function withSelectable(line2, item) {
16992
17679
  var DOCTOR_ACTIONS = [
16993
17680
  { detail: "run diagnostics now", item: { id: "run", label: "run", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "run diagnostics" } },
16994
17681
  { detail: "open server controls", item: { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "open server controls" } },
16995
- { detail: "repair project/server/auth link", item: { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair project link" } }, message: "repair project link" } },
17682
+ { detail: "repair project/server/auth link", item: { id: "repair", label: "repair", intent: { scene: "init", argv: ["init", "--repair", "--yes"], action: { kind: "init-start", label: "Repair project link" } }, message: "repair project link natively" } },
16996
17683
  { detail: "open tasks", item: { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks" } }
16997
17684
  ];
16998
17685
  function checks(state) {
@@ -17065,6 +17752,48 @@ function renderDoctorScene(state, layout) {
17065
17752
  });
17066
17753
  }
17067
17754
 
17755
+ // packages/cli/src/app-opentui/remote-link.ts
17756
+ var REPAIRABLE_REMOTE_LINK_STATUSES = new Set([
17757
+ "auth_required",
17758
+ "project_not_registered",
17759
+ "no_server_checkout",
17760
+ "invalid_root",
17761
+ "needs_prepare",
17762
+ "error"
17763
+ ]);
17764
+ function remoteProjectLinkError(message2) {
17765
+ return /no server-host project root link|remote project(?:-root)? link|x-rig-project-root|serverProjectRoot/i.test(message2 ?? "");
17766
+ }
17767
+ function serverRecordForRemoteLink(state) {
17768
+ const server = state.data.server;
17769
+ return server && typeof server === "object" && !Array.isArray(server) ? server : null;
17770
+ }
17771
+ function remoteProjectLinkState(state) {
17772
+ const serverRecord = serverRecordForRemoteLink(state);
17773
+ if (!serverRecord || serverRecord.kind !== "remote")
17774
+ return null;
17775
+ const link = serverRecord.remoteProjectLink;
17776
+ if (link && typeof link === "object" && !Array.isArray(link))
17777
+ return link;
17778
+ const direct = state.data.remoteProjectLink;
17779
+ if (direct && typeof direct === "object" && !Array.isArray(direct))
17780
+ return direct;
17781
+ return null;
17782
+ }
17783
+ function shouldOfferRemoteLinkRepair(state, message2) {
17784
+ const link = remoteProjectLinkState(state);
17785
+ if (link) {
17786
+ if (link.ok === true)
17787
+ return false;
17788
+ const status = link.status ?? "";
17789
+ return REPAIRABLE_REMOTE_LINK_STATUSES.has(status);
17790
+ }
17791
+ const serverRecord = serverRecordForRemoteLink(state);
17792
+ if (serverRecord && serverRecord.kind !== "remote")
17793
+ return false;
17794
+ return remoteProjectLinkError(message2 ?? state.error?.message);
17795
+ }
17796
+
17068
17797
  // packages/cli/src/app-opentui/fleet-stats.ts
17069
17798
  var ACTIVE_STATUSES = new Set([
17070
17799
  "running",
@@ -18912,13 +19641,18 @@ function overviewBody(state, family, snap, layout) {
18912
19641
  function familyRecoveryRows(state, family, errorMessage2) {
18913
19642
  const selected = Math.max(0, state.selection.index);
18914
19643
  const intro = errorMessage2 ? line(`\u2717 couldn't load ${family || "this family"} \u2014 ${errorMessage2}`, { fg: RIG_UI.red, bold: true }) : line(`No data for ${family || "this family"} yet.`, { fg: RIG_UI.ink2 });
18915
- return [
19644
+ const repairRemote = shouldOfferRemoteLinkRepair(state, errorMessage2);
19645
+ const rows = [
18916
19646
  intro,
18917
- blank(),
18918
- selectableDeckRow({ label: "retry", detail: `reload ${family || "the family"}`, index: 0, active: selected === 0 }, { id: "family-retry", label: "retry", intent: { scene: "family", argv: [family], action: { kind: "refresh", label: `Reload ${family}`, payload: { family } } }, message: "reload this family" }),
18919
- selectableDeckRow({ label: "cockpit", detail: "back to the project cockpit", index: 1, active: selected === 1 }, { id: "family-home", label: "cockpit", intent: { scene: "main", argv: [], action: { kind: "refresh", label: "Home" } }, message: "back to the cockpit" }),
18920
- selectableDeckRow({ label: "doctor", detail: "diagnose project/setup", index: 2, active: selected === 2 }, { id: "family-doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose setup" })
19647
+ blank()
18921
19648
  ];
19649
+ let index = 0;
19650
+ if (repairRemote) {
19651
+ rows.push(selectableDeckRow({ label: "repair link", detail: "backfill/prepare selected remote checkout link", index, active: selected === index }, { id: "family-repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "family", family } } }, message: "repair remote project link" }));
19652
+ index += 1;
19653
+ }
19654
+ rows.push(selectableDeckRow({ label: "retry", detail: `reload ${family || "the family"}`, index, active: selected === index }, { id: "family-retry", label: "retry", intent: { scene: "family", argv: [family], action: { kind: "refresh", label: `Reload ${family}`, payload: { family } } }, message: "reload this family" }), selectableDeckRow({ label: "cockpit", detail: "back to the project cockpit", index: index + 1, active: selected === index + 1 }, { id: "family-home", label: "cockpit", intent: { scene: "main", argv: [], action: { kind: "refresh", label: "Home" } }, message: "back to the cockpit" }), selectableDeckRow({ label: "doctor", detail: "diagnose project/setup", index: index + 2, active: selected === index + 2 }, { id: "family-doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose setup" }));
19655
+ return rows;
18922
19656
  }
18923
19657
  function renderFamilyScene(state, layout) {
18924
19658
  const family = typeof state.data.familyName === "string" ? state.data.familyName : "";
@@ -18927,9 +19661,16 @@ function renderFamilyScene(state, layout) {
18927
19661
  const loading = state.status === "loading" && !snap;
18928
19662
  const errorMessage2 = state.error?.message ?? lastRefreshError(state) ?? undefined;
18929
19663
  const probeFailed = snap ? snap.state.ran === false && Boolean(state.error) : Boolean(state.error);
19664
+ const selected = Math.max(0, state.selection.index);
18930
19665
  const body = [
18931
19666
  ...loading ? loadingRows(family || "family", state.tick) : snap ? [
18932
- ...probeFailed && errorMessage2 ? [...errorBanner(errorMessage2), blank()] : [],
19667
+ ...probeFailed && errorMessage2 ? [
19668
+ ...errorBanner(errorMessage2),
19669
+ ...shouldOfferRemoteLinkRepair(state, errorMessage2) ? [
19670
+ selectableDeckRow({ label: "repair link", detail: "backfill/prepare selected remote checkout link", index: 0, active: selected === 0 }, { id: "family-repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "family", family } } }, message: "repair remote project link" })
19671
+ ] : [],
19672
+ blank()
19673
+ ] : [],
18933
19674
  ...current.subId ? formView(state, family, snap, current).lines : overviewBody(state, family, snap, layout)
18934
19675
  ] : familyRecoveryRows(state, family, errorMessage2)
18935
19676
  ];
@@ -19076,12 +19817,12 @@ function sortTasks2(tasks, spec) {
19076
19817
  }
19077
19818
 
19078
19819
  // packages/cli/src/app-opentui/scenes/fleet.ts
19079
- var FLEET_RECOVERY_ITEMS = [
19820
+ var BASE_FLEET_RECOVERY_ITEMS = [
19080
19821
  { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks and dispatch work" },
19081
- { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair project link" } }, message: "repair project/server/auth link" },
19082
19822
  { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
19083
19823
  { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose project" }
19084
19824
  ];
19825
+ var FLEET_REPAIR_LINK_ITEM = { id: "repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "fleet" } } }, message: "backfill or prepare remote project link" };
19085
19826
  var COL2 = {
19086
19827
  glyph: 1,
19087
19828
  runId: 8,
@@ -19246,33 +19987,43 @@ function runRow(width, run, active, optimistic) {
19246
19987
  const item = runSelectableItem(run);
19247
19988
  return item ? withSelectable(row, item) : row;
19248
19989
  }
19249
- var RECOVERY_DECKS = [
19990
+ var BASE_RECOVERY_DECKS = [
19250
19991
  { label: "tasks", detail: "open tasks and dispatch work" },
19251
- { label: "repair", detail: "repair project/server/auth link" },
19252
19992
  { label: "server", detail: "open server controls" },
19253
19993
  { label: "doctor", detail: "diagnose project" }
19254
19994
  ];
19255
- function recoveryRows(selected, reason) {
19995
+ function fleetRecoveryItems(state) {
19996
+ if (!shouldOfferRemoteLinkRepair(state))
19997
+ return BASE_FLEET_RECOVERY_ITEMS;
19998
+ return [BASE_FLEET_RECOVERY_ITEMS[0], FLEET_REPAIR_LINK_ITEM, ...BASE_FLEET_RECOVERY_ITEMS.slice(1)];
19999
+ }
20000
+ function fleetRecoveryDecks(state) {
20001
+ if (!shouldOfferRemoteLinkRepair(state))
20002
+ return BASE_RECOVERY_DECKS;
20003
+ return [BASE_RECOVERY_DECKS[0], { label: "repair link", detail: "backfill/prepare remote project-root link" }, ...BASE_RECOVERY_DECKS.slice(1)];
20004
+ }
20005
+ function recoveryRows(state, selected, reason) {
20006
+ const items = fleetRecoveryItems(state);
19256
20007
  return [
19257
20008
  ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
19258
20009
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
19259
- ...RECOVERY_DECKS.map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, FLEET_RECOVERY_ITEMS[index]))
20010
+ ...fleetRecoveryDecks(state).map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, items[index]))
19260
20011
  ];
19261
20012
  }
19262
- function emptyRows(query, total, selected) {
20013
+ function emptyRows(state, query, total, selected) {
19263
20014
  if (!query.trim() && total === 0)
19264
- return recoveryRows(selected, "No runs yet.");
20015
+ return recoveryRows(state, selected, "No runs yet.");
19265
20016
  if (query.trim() && total > 0) {
19266
20017
  return [
19267
- ...recoveryRows(selected, `No matching runs for ${JSON.stringify(query)} (${total} total).`)
20018
+ ...recoveryRows(state, selected, `No matching runs for ${JSON.stringify(query)} (${total} total).`)
19268
20019
  ];
19269
20020
  }
19270
- return recoveryRows(selected, "No runs are visible with this filter.");
20021
+ return recoveryRows(state, selected, "No runs are visible with this filter.");
19271
20022
  }
19272
20023
  function degradedRows(state, selected) {
19273
20024
  if (!state.error)
19274
20025
  return [];
19275
- return recoveryRows(selected, state.error.message);
20026
+ return recoveryRows(state, selected, state.error.message);
19276
20027
  }
19277
20028
  function renderFleetScene(state, layout) {
19278
20029
  const width = panelWidth4(layout);
@@ -19282,7 +20033,7 @@ function renderFleetScene(state, layout) {
19282
20033
  const runs = sortRuns(filterRunsForSearch(allRuns, query), spec);
19283
20034
  const selectedRun = typeof state.data.selectedRunId === "string" && runs.some((run) => run.runId === state.data.selectedRunId) ? state.data.selectedRunId : runs[0]?.runId;
19284
20035
  const selectedIndex = Math.max(0, runs.findIndex((run) => run.runId === selectedRun));
19285
- const recoveryIndex = Math.max(0, Math.min(3, state.selection.index));
20036
+ const recoveryIndex = Math.max(0, Math.min(fleetRecoveryItems(state).length - 1, state.selection.index));
19286
20037
  const optimistic = optimisticStatuses(state);
19287
20038
  const updatedAgo = updatedAgoLabel(state);
19288
20039
  const panelTop = 0;
@@ -19314,7 +20065,7 @@ function renderFleetScene(state, layout) {
19314
20065
  ...runs.map((run, index) => runRow(contentWidth, run, index === selectedIndex, optimistic.get(run.runId)))
19315
20066
  ] : state.error ? degradedRows(state, recoveryIndex) : loading ? loadingRows("runs", state.tick) : [
19316
20067
  ...query.trim() ? [line(searchSummary("runs", query, runs.length, allRuns.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
19317
- ...emptyRows(query, allRuns.length, recoveryIndex)
20068
+ ...emptyRows(state, query, allRuns.length, recoveryIndex)
19318
20069
  ]
19319
20070
  ];
19320
20071
  return makeSceneFrame({
@@ -19869,19 +20620,19 @@ function renderInboxScene(state, layout) {
19869
20620
  }
19870
20621
 
19871
20622
  // packages/cli/src/app-opentui/scenes/init.ts
19872
- var STEP_CONFIG_RUN = { id: "step-config", label: "run setup wizard", intent: { scene: "command", argv: ["init"], action: { kind: "command-run", label: "Run setup wizard" } }, message: "full interactive setup wizard" };
20623
+ var STEP_CONFIG_RUN = { id: "step-config", label: "run init", intent: { scene: "init", argv: ["init", "--yes"], action: { kind: "init-start", label: "Run init" } }, message: "initialize without embedded CLI prompts" };
19873
20624
  var STEP_SERVER = { id: "step-server", label: "select server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Choose server" } }, message: "select local or remote server" };
19874
- var STEP_AUTH = { id: "step-auth", label: "connect GitHub", intent: { scene: "command", argv: ["github", "auth", "import-gh"], action: { kind: "command-run", label: "Connect GitHub" } }, message: "import GitHub auth into the selected server" };
19875
- var STEP_TASKS = { id: "step-tasks", label: "pick a task source", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Configure task source" } }, message: "configure or repair the task source" };
20625
+ var STEP_AUTH = { id: "step-auth", label: "connect GitHub", intent: { scene: "server", argv: ["github", "auth", "status"], action: { kind: "refresh", label: "Open GitHub auth" } }, message: "open native server/auth controls" };
20626
+ var STEP_TASKS = { id: "step-tasks", label: "repair task source", intent: { scene: "init", argv: ["init", "--repair", "--yes"], action: { kind: "init-start", label: "Repair task source" } }, message: "repair generated task-source config without embedded CLI prompts" };
19876
20627
  var SETUP_STEPS = [
19877
- { id: "config", label: "Project config", detail: "rig.config + private state", done: (s) => s.configured, cta: "Run setup wizard", item: STEP_CONFIG_RUN },
20628
+ { id: "config", label: "Project config", detail: "rig.config + private state", done: (s) => s.configured, cta: "Run init", item: STEP_CONFIG_RUN },
19878
20629
  { id: "server", label: "Server target", detail: "local or remote, reachable", done: (s) => s.serverReachable, cta: "Choose a server", item: STEP_SERVER },
19879
20630
  { id: "auth", label: "GitHub auth", detail: "signed in on the selected server", done: (s) => s.authSignedIn, cta: "Connect GitHub", item: STEP_AUTH },
19880
20631
  { id: "tasks", label: "Task source", detail: "where work comes from", done: (s) => s.taskSourceReady, cta: "Pick a task source", item: STEP_TASKS }
19881
20632
  ];
19882
20633
  var SECONDARY_ACTIONS = [
19883
- { label: "repair", detail: "reconfigure generated config and private state", item: { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Reconfigure project" } }, message: "verify or rewrite generated config" } },
19884
- { label: "demo", detail: "seed an offline demo task source to explore", item: { id: "demo", label: "demo", intent: { scene: "command", argv: ["init", "--demo", "--repair"], action: { kind: "command-run", label: "Seed demo project" } }, message: "offline demo project" } },
20634
+ { label: "repair", detail: "reconfigure generated config and private state", item: { id: "repair", label: "repair", intent: { scene: "init", argv: ["init", "--repair", "--yes"], action: { kind: "init-start", label: "Reconfigure project" } }, message: "verify or rewrite generated config natively" } },
20635
+ { label: "demo", detail: "seed an offline demo task source to explore", item: { id: "demo", label: "demo", intent: { scene: "init", argv: ["init", "--demo", "--repair", "--yes"], action: { kind: "init-start", label: "Seed demo project" } }, message: "offline demo project without embedded CLI prompts" } },
19885
20636
  { label: "doctor", detail: "diagnose what is still missing", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify setup" } }
19886
20637
  ];
19887
20638
  function record2(value) {
@@ -19925,11 +20676,11 @@ function renderInitScene(state, layout) {
19925
20676
  const allDone = completed === SETUP_STEPS.length;
19926
20677
  const nextStep = SETUP_STEPS.find((step) => !step.done(signals));
19927
20678
  const selected = Math.max(0, state.selection.index);
19928
- const primary = nextStep ? selectableDeckRow({ label: "next", detail: `${nextStep.cta} \u2014 recommended`, active: selected === 0 }, { ...nextStep.item, id: "init-primary" }) : selectableDeckRow({ label: "verify", detail: "Setup complete \u2014 run doctor to confirm", active: selected === 0 }, { id: "init-primary", label: "verify", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify setup end-to-end" });
20679
+ const primary = nextStep ? selectableDeckRow({ label: "next", detail: `${nextStep.cta} \u2014 recommended`, active: selected === 0 }, { ...nextStep.item, id: "init-primary" }) : selectableDeckRow({ label: "verify", detail: "Init complete \u2014 run doctor to confirm", active: selected === 0 }, { id: "init-primary", label: "verify", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify setup end-to-end" });
19929
20680
  const secondaryRows = SECONDARY_ACTIONS.map(({ label, detail, item }, index) => selectableDeckRow({ label, detail, active: selected === index + 1 }, item));
19930
20681
  const progress = allDone ? "all set" : `${completed} of ${SETUP_STEPS.length} complete`;
19931
20682
  const panelLines = [
19932
- line(allDone ? "SETUP \xB7 ready to go" : "SETUP \xB7 let's get you running", { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink, bold: true }),
20683
+ line(allDone ? "INIT \xB7 ready to go" : "INIT \xB7 let's get you running", { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink, bold: true }),
19933
20684
  line(`progress ${progress}`, { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink2 }),
19934
20685
  blank(),
19935
20686
  line("CHECKLIST", { fg: RIG_UI.ink3, bold: true }),
@@ -19941,11 +20692,11 @@ function renderInitScene(state, layout) {
19941
20692
  line("OTHER PATHS", { fg: RIG_UI.ink3, bold: true }),
19942
20693
  ...secondaryRows,
19943
20694
  blank(),
19944
- line("enter/click runs the highlighted action \xB7 esc goes back \xB7 tab switches sections", { fg: RIG_UI.ink4 })
20695
+ line("enter/click runs the highlighted native action \xB7 esc goes back \xB7 tab switches sections", { fg: RIG_UI.ink4 })
19945
20696
  ];
19946
20697
  return makeSceneFrame({
19947
20698
  scene: "init",
19948
- title: "Setup",
20699
+ title: "Init",
19949
20700
  lines: [],
19950
20701
  panels: [{
19951
20702
  id: "init-setup",
@@ -19958,11 +20709,11 @@ function renderInitScene(state, layout) {
19958
20709
  opacity: 0.98,
19959
20710
  border: false,
19960
20711
  chrome: "ad-terminal",
19961
- headerText: "rig setup \xB7 guided onboarding",
20712
+ headerText: "rig init \xB7 guided onboarding",
19962
20713
  paddingX: 5,
19963
20714
  paddingY: 2
19964
20715
  }],
19965
- footer: { message: allDone ? "setup complete" : "setup in progress" },
20716
+ footer: { message: allDone ? "init complete" : "init in progress" },
19966
20717
  live: !allDone || state.status === "action"
19967
20718
  });
19968
20719
  }
@@ -21235,16 +21986,40 @@ function renderRunDetailScene(state, layout) {
21235
21986
  // packages/cli/src/app-opentui/scenes/server.ts
21236
21987
  var SERVER_ACTIONS = [
21237
21988
  { detail: "probe selected server, project root, and GitHub auth", item: { id: "refresh", label: "refresh", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Refresh server" } }, message: "refresh selected server" } },
21238
- { detail: "select local server for this repo", item: { id: "local", label: "use local", intent: { scene: "command", argv: ["server", "use", "local"], action: { kind: "command-run", label: "Use local server" } }, message: "switch this repo to local" } },
21239
- { detail: "choose/switch to a saved remote server alias", item: { id: "remote", label: "use remote", intent: { scene: "command", argv: ["server", "use"], action: { kind: "command-run", label: "Choose remote server" } }, message: "choose a saved remote alias" } },
21240
- { detail: "show saved local/remote aliases", item: { id: "list", label: "list servers", intent: { scene: "command", argv: ["server", "list"], action: { kind: "command-run", label: "List saved servers" } }, message: "list saved server aliases" } },
21241
- { detail: "open server add command workspace", item: { id: "add", label: "add remote", intent: { scene: "command", argv: ["server", "add"], action: { kind: "command-run", label: "Add remote server" } }, message: "open server add workspace" } },
21989
+ { detail: "select local server for this repo", item: { id: "local", label: "use local", intent: { scene: "server", argv: ["server", "use", "local"], action: { kind: "server-use-local", label: "Use local server" } }, message: "switch this repo to local" } },
21990
+ { detail: "type a saved remote alias to select", item: { id: "remote", label: "use remote", prompt: { label: "Use remote alias", scene: "server", argv: ["server", "use"], intentKind: "server-use-remote", payloadBase: {}, payloadKey: "alias" }, message: "choose a saved remote alias" } },
21991
+ { detail: "show saved local/remote aliases in the status panel", item: { id: "list", label: "list servers", intent: { scene: "server", argv: ["server", "list"], action: { kind: "refresh", label: "List servers" } }, message: "list saved server aliases" } },
21992
+ { detail: "add and select a remote: enter <alias> <https-url>", item: { id: "add", label: "add remote", prompt: { label: "Add remote", scene: "server", argv: ["server", "add"], intentKind: "server-add-remote", payloadBase: {}, payloadKey: "remote", appendValueToArgv: true }, message: "add a remote server alias" } },
21242
21993
  { detail: "spawn the local rig-server detached and return here", item: { id: "start", label: "start local", intent: { scene: "server", argv: ["server", "start"], action: { kind: "refresh", payload: { startLocal: true }, label: "Start local server" } }, message: "start local server detached" } },
21243
- { detail: "import gh token into the selected server namespace", item: { id: "auth", label: "import auth", intent: { scene: "command", argv: ["github", "auth", "import-gh"], action: { kind: "command-run", label: "Import GitHub auth" } }, message: "import gh auth to selected server" } },
21994
+ { detail: "import gh token into the selected server namespace", item: { id: "auth", label: "import auth", intent: { scene: "server", argv: ["github", "auth", "import-gh"], action: { kind: "github-auth-import", label: "Import GitHub auth" } }, message: "import gh auth to selected server" } },
21244
21995
  { detail: "diagnose server/auth/project linkage", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify server/auth" } },
21245
21996
  { detail: "verify selected server can list task source", item: { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["task", "list"], action: { kind: "refresh", label: "Open tasks" } }, message: "verify task source" } },
21246
21997
  { detail: "verify selected server can list recent runs", item: { id: "runs", label: "runs", intent: { scene: "fleet", argv: ["run", "status"], action: { kind: "refresh", label: "Open runs" } }, message: "open recent runs" } }
21247
21998
  ];
21999
+ function remoteAliasActions(server) {
22000
+ const aliases = (server?.aliases ?? []).filter((alias) => alias.kind === "remote");
22001
+ return aliases.map((alias) => ({
22002
+ detail: `${alias.selected ? "selected" : "select"} ${alias.baseUrl ?? "remote endpoint"}`,
22003
+ item: {
22004
+ id: `remote-alias:${alias.alias}`,
22005
+ label: alias.selected ? `\u2713 ${alias.alias}` : `use ${alias.alias}`,
22006
+ intent: { scene: "server", argv: ["server", "use", alias.alias], action: { kind: "server-use-remote", label: `Use ${alias.alias}`, payload: { alias: alias.alias } } },
22007
+ message: `select ${alias.alias}`
22008
+ }
22009
+ }));
22010
+ }
22011
+ function serverActions(state) {
22012
+ const server = serverState(state);
22013
+ const remoteRows = remoteAliasActions(server);
22014
+ let actions = remoteRows.length > 0 ? [...SERVER_ACTIONS.slice(0, 3), ...remoteRows, ...SERVER_ACTIONS.slice(3)] : [...SERVER_ACTIONS];
22015
+ if (!shouldOfferRemoteLinkRepair(state))
22016
+ return actions;
22017
+ const repair = { detail: "backfill or prepare the selected remote checkout/root link", item: { id: "repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link" } }, message: "prepare remote checkout link" } };
22018
+ const doctorIndex = actions.findIndex((entry) => entry.item.id === "doctor");
22019
+ if (doctorIndex < 0)
22020
+ return [...actions, repair];
22021
+ return [...actions.slice(0, doctorIndex), repair, ...actions.slice(doctorIndex)];
22022
+ }
21248
22023
  function cleanVisible(value, fallback = "unknown") {
21249
22024
  return value?.trim() || fallback;
21250
22025
  }
@@ -21269,31 +22044,37 @@ function selectedIndex(state, count) {
21269
22044
  function statusRows2(server) {
21270
22045
  const auth = server?.auth;
21271
22046
  const signedIn = auth && (auth.signedIn === true || auth.authenticated === true || auth.status === "authenticated") ? "authenticated" : "auth unknown";
22047
+ const link = server?.remoteProjectLink;
22048
+ const linkText = link ? link.ok ? `ready \xB7 ${link.serverProjectRoot ?? "linked"}` : `${link.status ?? "needs repair"} \xB7 ${link.hint ?? link.message ?? "run repair link"}` : server?.kind === "remote" ? "not checked" : "not required";
22049
+ const selectedLinkSummary = link?.ok ? " \xB7 root link ready" : server?.kind === "remote" && link ? ` \xB7 root link ${link.status ?? "needs repair"}` : "";
21272
22050
  return [
21273
- line(`selected ${cleanVisible(server?.label, "not checked")}`, { fg: RIG_UI.ink2 }),
22051
+ line(`selected ${cleanVisible(server?.label, "not checked")}${selectedLinkSummary}`, { fg: RIG_UI.ink2 }),
21274
22052
  line(`target ${targetLabel(server)}`, { fg: RIG_UI.ink3 }),
21275
22053
  line(`mode ${server?.kind ?? "local or remote"}`, { fg: RIG_UI.ink3 }),
21276
22054
  line(`health ${server ? server.reachable ? "reachable" : "unreachable" : "check pending"}`, { fg: server?.reachable ? RIG_UI.limeDim : server ? RIG_UI.red : RIG_UI.yellow }),
21277
22055
  line(`github ${signedIn}`, { fg: signedIn === "authenticated" ? RIG_UI.limeDim : RIG_UI.ink4 }),
22056
+ line(`root link ${linkText}`, { fg: link?.ok ? RIG_UI.limeDim : server?.kind === "remote" ? RIG_UI.yellow : RIG_UI.ink4 }),
22057
+ ...server?.aliases && server.aliases.length > 0 ? [line(`aliases ${server.aliases.map((alias) => `${alias.selected ? "*" : ""}${alias.alias}${alias.kind === "remote" && alias.baseUrl ? `=${alias.baseUrl}` : ""}`).join(", ")}`, { fg: RIG_UI.ink4 })] : [],
21278
22058
  ...server?.error ? [line(`attention ${server.error}`, { fg: RIG_UI.yellow })] : []
21279
22059
  ];
21280
22060
  }
21281
22061
  function renderServerScene(state, layout) {
21282
22062
  const server = serverState(state);
21283
- const selected = selectedIndex(state, 10);
22063
+ const actions = serverActions(state);
22064
+ const selected = selectedIndex(state, actions.length);
21284
22065
  const loading = state.status === "loading" && !server;
21285
22066
  const errorText = state.error?.message ?? lastRefreshError(state);
21286
22067
  const panelLines = [
21287
22068
  ...errorText ? errorBanner(errorText, "select refresh or doctor below to retry") : [],
21288
22069
  line("server controls", { fg: RIG_UI.ink3 }),
21289
22070
  blank(),
21290
- line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
21291
- ...SERVER_ACTIONS.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item)),
21292
- blank(),
21293
22071
  line("STATUS", { fg: RIG_UI.ink3, bold: true }),
21294
22072
  ...loading ? loadingRows("server status", state.tick) : statusRows2(server),
21295
22073
  blank(),
21296
- line("enter/click runs action \xB7 command rows stay inside OpenTUI PTY", { fg: RIG_UI.ink4 })
22074
+ line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
22075
+ ...actions.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item)),
22076
+ blank(),
22077
+ line("enter/click runs native actions \xB7 add/use remote prompts stay in OpenTUI", { fg: RIG_UI.ink4 })
21297
22078
  ];
21298
22079
  return makeSceneFrame({
21299
22080
  scene: "server",
@@ -21320,13 +22101,13 @@ function renderServerScene(state, layout) {
21320
22101
  }
21321
22102
 
21322
22103
  // packages/cli/src/app-opentui/scenes/tasks.ts
21323
- var TASK_RECOVERY_ITEMS = [
22104
+ var BASE_TASK_RECOVERY_ITEMS = [
21324
22105
  { id: "refresh", label: "refresh", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Refresh tasks" } }, message: "refresh task source" },
21325
- { id: "next", label: "next", intent: { scene: "command", argv: ["task", "run", "--next"], action: { kind: "command-run", label: "Run next task" } }, message: "dispatch next runnable task" },
21326
- { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair task source" } }, message: "repair project/task source" },
22106
+ { id: "next", label: "next", intent: { scene: "tasks", argv: ["task", "run", "--next"], action: { kind: "task-run-next", label: "Dispatch next task" } }, message: "dispatch next runnable task" },
21327
22107
  { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
21328
22108
  { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose task source" }
21329
22109
  ];
22110
+ var TASK_REPAIR_LINK_ITEM = { id: "repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "tasks" } } }, message: "backfill or prepare remote project link" };
21330
22111
  var COL3 = {
21331
22112
  glyph: 1,
21332
22113
  id: 11,
@@ -21486,34 +22267,44 @@ function taskRow(width, task, active, optimistic) {
21486
22267
  const item = taskSelectableItem(task);
21487
22268
  return item ? withSelectable(row, item) : row;
21488
22269
  }
21489
- var RECOVERY_DECKS2 = [
22270
+ var BASE_RECOVERY_DECKS2 = [
21490
22271
  { label: "refresh", detail: "reload task source" },
21491
22272
  { label: "next", detail: "dispatch next runnable task" },
21492
- { label: "repair", detail: "repair project/task source link" },
21493
22273
  { label: "server", detail: "open server controls" },
21494
22274
  { label: "doctor", detail: "diagnose task source" }
21495
22275
  ];
21496
- function recoveryRows2(selected, reason) {
22276
+ function taskRecoveryItems(state) {
22277
+ if (!shouldOfferRemoteLinkRepair(state))
22278
+ return BASE_TASK_RECOVERY_ITEMS;
22279
+ return [BASE_TASK_RECOVERY_ITEMS[0], BASE_TASK_RECOVERY_ITEMS[1], TASK_REPAIR_LINK_ITEM, ...BASE_TASK_RECOVERY_ITEMS.slice(2)];
22280
+ }
22281
+ function taskRecoveryDecks(state) {
22282
+ if (!shouldOfferRemoteLinkRepair(state))
22283
+ return BASE_RECOVERY_DECKS2;
22284
+ return [BASE_RECOVERY_DECKS2[0], BASE_RECOVERY_DECKS2[1], { label: "repair link", detail: "backfill/prepare remote project-root link" }, ...BASE_RECOVERY_DECKS2.slice(2)];
22285
+ }
22286
+ function recoveryRows2(state, selected, reason) {
22287
+ const items = taskRecoveryItems(state);
21497
22288
  return [
21498
22289
  ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
21499
22290
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
21500
- ...RECOVERY_DECKS2.map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, TASK_RECOVERY_ITEMS[index]))
22291
+ ...taskRecoveryDecks(state).map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, items[index]))
21501
22292
  ];
21502
22293
  }
21503
- function emptyRows2(query, total, selected) {
22294
+ function emptyRows2(state, query, total, selected) {
21504
22295
  if (!query.trim() && total === 0)
21505
- return recoveryRows2(selected, "No tasks loaded.");
22296
+ return recoveryRows2(state, selected, "No tasks loaded.");
21506
22297
  if (query.trim() && total > 0) {
21507
22298
  return [
21508
- ...recoveryRows2(selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
22299
+ ...recoveryRows2(state, selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
21509
22300
  ];
21510
22301
  }
21511
- return recoveryRows2(selected, "No tasks are visible with this filter.");
22302
+ return recoveryRows2(state, selected, "No tasks are visible with this filter.");
21512
22303
  }
21513
22304
  function degradedRows2(state, selected) {
21514
22305
  if (!state.error)
21515
22306
  return [];
21516
- return recoveryRows2(selected, state.error.message);
22307
+ return recoveryRows2(state, selected, state.error.message);
21517
22308
  }
21518
22309
  function rawString(raw, key) {
21519
22310
  const value = raw?.[key];
@@ -21537,7 +22328,10 @@ function renderTaskDetail(state, taskId3, layout) {
21537
22328
  const activeRunId = task?.activeRun?.runId?.trim();
21538
22329
  const width = panelWidth13(layout);
21539
22330
  const selected = Math.max(0, state.selection.index);
22331
+ const errorText = state.error?.message ?? lastRefreshError(state);
22332
+ const offerRepairLink = shouldOfferRemoteLinkRepair(state, errorText);
21540
22333
  const headerLines = [
22334
+ ...errorText ? errorBanner(errorText, offerRepairLink ? "select repair link below to prepare the remote checkout" : "retry dispatch after resolving the issue") : [],
21541
22335
  line(`${glyph2} ${title}`, { fg: color, bold: true }),
21542
22336
  line(`task ${taskId3} \xB7 ${status}`, { fg: RIG_UI.ink3 }),
21543
22337
  ...task && task.labels.length > 0 ? [line(`labels: ${task.labels.join(", ")}`, { fg: RIG_UI.ink4 })] : [],
@@ -21549,8 +22343,16 @@ function renderTaskDetail(state, taskId3, layout) {
21549
22343
  const actionRows2 = [];
21550
22344
  actionRows2.push(selectableDeckRow({ label: "back", detail: "return to the task list", index, active: selected === index }, { id: "task-detail-back", label: "back", data: { taskDetailId: undefined }, message: "back to task list" }));
21551
22345
  index += 1;
21552
- actionRows2.push(selectableDeckRow({ label: "dispatch", detail: "submit a Pi run for this task", index, active: selected === index }, { id: `task-detail-run:${taskId3}`, label: `dispatch ${taskId3}`, intent: { scene: "tasks", argv: ["run", taskId3], action: { kind: "task-run-id", payload: { task: taskId3 }, label: `Dispatching ${taskId3}` } }, message: `dispatch task ${taskId3}` }));
21553
- index += 1;
22346
+ if (offerRepairLink) {
22347
+ actionRows2.push(selectableDeckRow({ label: "repair link", detail: "prepare/backfill the selected remote project-root link", index, active: selected === index }, { id: `task-detail-repair-link:${taskId3}`, label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "tasks", taskDetailId: taskId3 } } }, message: "prepare remote checkout link" }));
22348
+ index += 1;
22349
+ }
22350
+ if (offerRepairLink) {
22351
+ actionRows2.push(line(" dispatch blocked until the remote project link is repaired", { fg: RIG_UI.ink4 }));
22352
+ } else {
22353
+ actionRows2.push(selectableDeckRow({ label: "dispatch", detail: "submit a Pi run for this task", index, active: selected === index }, { id: `task-detail-run:${taskId3}`, label: `dispatch ${taskId3}`, intent: { scene: "tasks", argv: ["run", taskId3], action: { kind: "task-run-id", payload: { task: taskId3 }, label: `Dispatching ${taskId3}` } }, message: `dispatch task ${taskId3}` }));
22354
+ index += 1;
22355
+ }
21554
22356
  if (activeRunId) {
21555
22357
  actionRows2.push(selectableDeckRow({ label: "attach", detail: `attach the Pi console to run ${activeRunId.slice(0, 8)}`, index, active: selected === index }, { id: `task-detail-attach:${taskId3}`, label: `attach ${activeRunId.slice(0, 8)}`, intent: { scene: "handoff", argv: ["attach", activeRunId], action: { kind: "run-attach", payload: { runId: activeRunId }, label: `Attach Pi ${activeRunId.slice(0, 8)}` } }, message: `attach Pi to ${activeRunId.slice(0, 8)}` }));
21556
22358
  index += 1;
@@ -21609,7 +22411,7 @@ function renderTasksScene(state, layout) {
21609
22411
  const tasks = sortTasks2(filterTasksForSearch(allTasks, query), spec);
21610
22412
  const selected = typeof state.data.selectedTaskId === "string" && tasks.some((task) => task.id === state.data.selectedTaskId) ? state.data.selectedTaskId : tasks[0]?.id;
21611
22413
  const selectedIndex2 = Math.max(0, tasks.findIndex((task) => task.id === selected));
21612
- const recoveryIndex = Math.max(0, Math.min(4, state.selection.index));
22414
+ const recoveryIndex = Math.max(0, Math.min(taskRecoveryItems(state).length - 1, state.selection.index));
21613
22415
  const dispatching = dispatchingFor(state);
21614
22416
  const dispatchLine = dispatching ? ` \xB7 ${state.data.dispatchingRun?.runId ?? "new"} ${dispatching.status}` : "";
21615
22417
  const updatedAgo = updatedAgoLabel2(state);
@@ -21629,7 +22431,7 @@ function renderTasksScene(state, layout) {
21629
22431
  ...tasks.map((task, index) => taskRow(contentWidth, task, index === selectedIndex2, dispatching && dispatching.taskId === task.id ? dispatching.status : undefined))
21630
22432
  ] : state.error ? degradedRows2(state, recoveryIndex) : loading ? loadingRows("tasks", state.tick) : [
21631
22433
  ...query.trim() ? [line(searchSummary("tasks", query, tasks.length, allTasks.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
21632
- ...emptyRows2(query, allTasks.length, recoveryIndex)
22434
+ ...emptyRows2(state, query, allTasks.length, recoveryIndex)
21633
22435
  ]
21634
22436
  ];
21635
22437
  return makeSceneFrame({
@@ -21986,6 +22788,29 @@ function createDomainAdapter() {
21986
22788
  else
21987
22789
  await loadInitFacts(ctx);
21988
22790
  return true;
22791
+ case "server-use-local":
22792
+ await useLocalServerFromApp(ctx);
22793
+ return true;
22794
+ case "server-use-remote":
22795
+ await useRemoteServerFromApp(ctx, payloadString8(intent, "alias") ?? intent.argv[2]);
22796
+ return true;
22797
+ case "server-add-remote":
22798
+ await addRemoteServerFromApp(ctx, intent.argv);
22799
+ return true;
22800
+ case "github-auth-import":
22801
+ await importGitHubAuthFromGhApp(ctx);
22802
+ return true;
22803
+ case "remote-link-repair": {
22804
+ await repairRemoteProjectRootLinkFromApp(ctx);
22805
+ const returnScene = payloadString8(intent, "returnScene");
22806
+ if (returnScene && returnScene !== intent.scene) {
22807
+ ctx.emit({ type: "scene.change", scene: returnScene, intent: { scene: returnScene, argv: intent.argv, action: { kind: "refresh", label: `Refresh ${returnScene}`, payload: intent.action.payload } } });
22808
+ await refreshScene(ctx, returnScene, { scene: returnScene, argv: intent.argv, action: { kind: "refresh", label: `Refresh ${returnScene}`, payload: intent.action.payload } });
22809
+ } else {
22810
+ await refreshServerStatus(ctx);
22811
+ }
22812
+ return true;
22813
+ }
21989
22814
  case "doctor-run":
21990
22815
  await runDoctorChecksForApp(ctx);
21991
22816
  return true;