@h-rig/cli 0.0.6-alpha.81 → 0.0.6-alpha.83

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 (100) hide show
  1. package/dist/bin/build-rig-binaries.js +40 -8
  2. package/dist/bin/rig.js +2807 -1443
  3. package/dist/src/app/board.js +62 -13
  4. package/dist/src/app-opentui/adapters/command.d.ts +2 -0
  5. package/dist/src/app-opentui/adapters/command.js +329 -0
  6. package/dist/src/app-opentui/adapters/common.js +2 -2
  7. package/dist/src/app-opentui/adapters/doctor.js +63 -14
  8. package/dist/src/app-opentui/adapters/fleet.js +84 -20
  9. package/dist/src/app-opentui/adapters/inbox.js +83 -19
  10. package/dist/src/app-opentui/adapters/init.js +87 -23
  11. package/dist/src/app-opentui/adapters/pi-attach.js +102 -36
  12. package/dist/src/app-opentui/adapters/run-detail.js +83 -19
  13. package/dist/src/app-opentui/adapters/server.js +100 -23
  14. package/dist/src/app-opentui/adapters/tasks.d.ts +11 -0
  15. package/dist/src/app-opentui/adapters/tasks.js +742 -94
  16. package/dist/src/app-opentui/bootstrap.d.ts +1 -6
  17. package/dist/src/app-opentui/bootstrap.js +13776 -12369
  18. package/dist/src/app-opentui/command-pty-host.d.ts +62 -0
  19. package/dist/src/app-opentui/command-pty-host.js +248 -0
  20. package/dist/src/app-opentui/events.js +1 -1
  21. package/dist/src/app-opentui/focus-manager.d.ts +14 -0
  22. package/dist/src/app-opentui/focus-manager.js +24 -0
  23. package/dist/src/app-opentui/index.js +3806 -2878
  24. package/dist/src/app-opentui/intent.js +154 -48
  25. package/dist/src/app-opentui/keymap.d.ts +20 -0
  26. package/dist/src/app-opentui/keymap.js +707 -0
  27. package/dist/src/app-opentui/launch-routing.d.ts +16 -0
  28. package/dist/src/app-opentui/launch-routing.js +55 -0
  29. package/dist/src/app-opentui/layout.js +2 -2
  30. package/dist/src/app-opentui/pi-host-child.js +66 -16
  31. package/dist/src/app-opentui/pi-pty-host.d.ts +9 -0
  32. package/dist/src/app-opentui/pi-pty-host.js +15 -13
  33. package/dist/src/app-opentui/registry.js +3228 -2396
  34. package/dist/src/app-opentui/render/ascii-fleet.d.ts +15 -0
  35. package/dist/src/app-opentui/render/ascii-fleet.js +82 -0
  36. package/dist/src/app-opentui/render/graphics.d.ts +1 -1
  37. package/dist/src/app-opentui/render/graphics.js +131 -16
  38. package/dist/src/app-opentui/render/image-visual-layer-worker.js +413 -958
  39. package/dist/src/app-opentui/render/image-visual-layer.d.ts +19 -10
  40. package/dist/src/app-opentui/render/image-visual-layer.js +391 -357
  41. package/dist/src/app-opentui/render/panel-layout.d.ts +38 -0
  42. package/dist/src/app-opentui/render/panel-layout.js +48 -0
  43. package/dist/src/app-opentui/render/panels.d.ts +2 -0
  44. package/dist/src/app-opentui/render/panels.js +62 -29
  45. package/dist/src/app-opentui/render/preloader.d.ts +10 -0
  46. package/dist/src/app-opentui/render/preloader.js +163 -0
  47. package/dist/src/app-opentui/render/scene.d.ts +3 -0
  48. package/dist/src/app-opentui/render/scene.js +12 -0
  49. package/dist/src/app-opentui/render/text.js +5 -1
  50. package/dist/src/app-opentui/render/type-bar.d.ts +2 -1
  51. package/dist/src/app-opentui/render/type-bar.js +51 -16
  52. package/dist/src/app-opentui/runtime-resources.d.ts +16 -0
  53. package/dist/src/app-opentui/runtime-resources.js +62 -0
  54. package/dist/src/app-opentui/runtime.js +2329 -1512
  55. package/dist/src/app-opentui/scenes/command.d.ts +3 -0
  56. package/dist/src/app-opentui/scenes/command.js +103 -0
  57. package/dist/src/app-opentui/scenes/doctor.d.ts +2 -1
  58. package/dist/src/app-opentui/scenes/doctor.js +53 -14
  59. package/dist/src/app-opentui/scenes/error.js +44 -14
  60. package/dist/src/app-opentui/scenes/fleet.js +40 -21
  61. package/dist/src/app-opentui/scenes/handoff.js +142 -27
  62. package/dist/src/app-opentui/scenes/help.js +63 -48
  63. package/dist/src/app-opentui/scenes/inbox.d.ts +2 -1
  64. package/dist/src/app-opentui/scenes/inbox.js +64 -17
  65. package/dist/src/app-opentui/scenes/init.d.ts +2 -1
  66. package/dist/src/app-opentui/scenes/init.js +62 -21
  67. package/dist/src/app-opentui/scenes/main.d.ts +2 -1
  68. package/dist/src/app-opentui/scenes/main.js +80 -24
  69. package/dist/src/app-opentui/scenes/run-detail.d.ts +2 -1
  70. package/dist/src/app-opentui/scenes/run-detail.js +39 -25
  71. package/dist/src/app-opentui/scenes/server.d.ts +2 -1
  72. package/dist/src/app-opentui/scenes/server.js +71 -20
  73. package/dist/src/app-opentui/scenes/tasks.js +44 -22
  74. package/dist/src/app-opentui/state.js +70 -30
  75. package/dist/src/app-opentui/terminal-capabilities.d.ts +7 -0
  76. package/dist/src/app-opentui/terminal-capabilities.js +15 -0
  77. package/dist/src/app-opentui/types.d.ts +10 -2
  78. package/dist/src/commands/_doctor-checks.js +62 -13
  79. package/dist/src/commands/_operator-view.js +63 -13
  80. package/dist/src/commands/_pi-frontend.js +63 -13
  81. package/dist/src/commands/_preflight.js +64 -14
  82. package/dist/src/commands/_server-client.js +82 -18
  83. package/dist/src/commands/_snapshot-upload.js +62 -13
  84. package/dist/src/commands/connect.js +7 -1
  85. package/dist/src/commands/doctor.js +62 -13
  86. package/dist/src/commands/github.js +144 -23
  87. package/dist/src/commands/inbox.js +62 -13
  88. package/dist/src/commands/init.js +82 -18
  89. package/dist/src/commands/inspect.js +62 -13
  90. package/dist/src/commands/run.js +66 -13
  91. package/dist/src/commands/server.js +69 -14
  92. package/dist/src/commands/setup.js +62 -13
  93. package/dist/src/commands/stats.js +62 -13
  94. package/dist/src/commands/task-run-driver.js +62 -13
  95. package/dist/src/commands/task.js +65 -14
  96. package/dist/src/commands.js +227 -92
  97. package/dist/src/index.js +234 -99
  98. package/package.json +8 -9
  99. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.d.ts +0 -2
  100. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.js +0 -1480
@@ -564,17 +564,21 @@ function cleanToken(value) {
564
564
  const trimmed = value?.trim();
565
565
  return trimmed ? trimmed : null;
566
566
  }
567
- function readPrivateRemoteSessionToken(projectRoot) {
567
+ function readRemoteAuthState(projectRoot) {
568
568
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
569
569
  if (!existsSync2(path))
570
570
  return null;
571
571
  try {
572
572
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
573
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
573
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
574
574
  } catch {
575
575
  return null;
576
576
  }
577
577
  }
578
+ function readPrivateRemoteSessionToken(projectRoot) {
579
+ const parsed = readRemoteAuthState(projectRoot);
580
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
581
+ }
578
582
  function readGitHubBearerTokenForRemote(projectRoot) {
579
583
  const scopedKey = resolve2(projectRoot);
580
584
  if (scopedGitHubBearerTokens.has(scopedKey))
@@ -585,15 +589,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
585
589
  return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
586
590
  }
587
591
  function readStoredGitHubAuthToken(projectRoot) {
588
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
589
- if (!existsSync2(path))
592
+ const parsed = readRemoteAuthState(projectRoot);
593
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
594
+ }
595
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
596
+ const repo = readRepoConnection(projectRoot);
597
+ const slug = repo?.project?.trim();
598
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
590
599
  return null;
591
- try {
592
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
593
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
594
- } catch {
600
+ const auth = readRemoteAuthState(projectRoot);
601
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
602
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
603
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
595
604
  return null;
596
- }
605
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
606
+ if (!checkoutBaseDir)
607
+ return null;
608
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
609
+ writeRepoServerProjectRoot(projectRoot, inferred);
610
+ return inferred;
597
611
  }
598
612
  function readLocalConnectionFallbackToken(projectRoot) {
599
613
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
@@ -604,7 +618,7 @@ async function ensureServerForCli(projectRoot) {
604
618
  if (selected?.connection.kind === "remote") {
605
619
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
606
620
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
607
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
621
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
608
622
  return {
609
623
  baseUrl: selected.connection.baseUrl,
610
624
  authToken,
@@ -633,7 +647,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
633
647
  if (!slug)
634
648
  return null;
635
649
  try {
636
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
650
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
651
+ if (authToken && queryAuthFallbackEnabled())
652
+ url.searchParams.set("rt", authToken);
653
+ const response = await fetch(url, {
637
654
  headers: mergeHeaders(undefined, authToken)
638
655
  });
639
656
  if (!response.ok)
@@ -660,10 +677,23 @@ function appendTaskFilterParams(url, filters) {
660
677
  if (filters.limit !== undefined)
661
678
  url.searchParams.set("limit", String(filters.limit));
662
679
  }
680
+ function mergeCookie(existing, name, value) {
681
+ const encoded = `${name}=${encodeURIComponent(value)}`;
682
+ if (!existing?.trim())
683
+ return encoded;
684
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
685
+ return [...parts, encoded].join("; ");
686
+ }
687
+ function queryAuthFallbackEnabled(env = process.env) {
688
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
689
+ }
663
690
  function mergeHeaders(headers, authToken) {
664
691
  const merged = new Headers(headers);
665
692
  if (authToken) {
666
- merged.set("authorization", `Bearer ${authToken}`);
693
+ const bearer = `Bearer ${authToken}`;
694
+ merged.set("authorization", bearer);
695
+ merged.set("x-auth", bearer);
696
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
667
697
  }
668
698
  return merged;
669
699
  }
@@ -724,15 +754,34 @@ async function buildServerFailureContext(projectRoot, server) {
724
754
  hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
725
755
  };
726
756
  }
757
+ function isLoopbackRemoteBaseUrl(baseUrl) {
758
+ try {
759
+ const host = new URL(baseUrl).hostname.toLowerCase();
760
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
761
+ } catch {
762
+ return false;
763
+ }
764
+ }
765
+ function canUseRemoteWithoutProjectRoot(pathname) {
766
+ return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
767
+ }
727
768
  async function requestServerJson(context, pathname, init = {}) {
728
769
  const server = await ensureServerForCli(context.projectRoot);
770
+ const requestUrl = new URL(`${server.baseUrl}${pathname}`);
771
+ if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
772
+ const repo = readRepoConnection(context.projectRoot);
773
+ 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." });
774
+ }
729
775
  const headers = mergeHeaders(init.headers, server.authToken);
730
776
  if (server.serverProjectRoot)
731
777
  headers.set("x-rig-project-root", server.serverProjectRoot);
778
+ if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
779
+ requestUrl.searchParams.set("rt", server.authToken);
780
+ }
732
781
  reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
733
782
  let response;
734
783
  try {
735
- response = await fetch(`${server.baseUrl}${pathname}`, {
784
+ response = await fetch(requestUrl, {
736
785
  ...init,
737
786
  headers
738
787
  });
@@ -0,0 +1,2 @@
1
+ import type { AppAdapterContext } from "./common";
2
+ export declare function runCommandInAppPty(ctx: AppAdapterContext, argv: readonly string[]): Promise<void>;
@@ -0,0 +1,329 @@
1
+ // @bun
2
+ // packages/cli/src/app-opentui/adapters/common.ts
3
+ function projectRootOf(ctx) {
4
+ const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
5
+ if (typeof root === "string" && root.trim())
6
+ return root;
7
+ throw new Error("App adapter requires a projectRoot.");
8
+ }
9
+ function normalizeAppError(error) {
10
+ const message = error instanceof Error ? error.message : String(error);
11
+ const rawHint = error instanceof Error ? error.hint : undefined;
12
+ return {
13
+ message,
14
+ ...typeof rawHint === "string" && rawHint.trim() ? { hint: rawHint.trim() } : {}
15
+ };
16
+ }
17
+ function emitStarted(ctx, label, optimistic) {
18
+ ctx.emit({ type: "action.started", label, ...optimistic ? { optimistic } : {} });
19
+ }
20
+ function emitCompleted(ctx, label, data) {
21
+ ctx.emit({ type: "action.completed", label, ...data ? { data } : {} });
22
+ }
23
+ function emitFailed(ctx, label, error, data) {
24
+ if (data)
25
+ patchData(ctx, data);
26
+ const normalized = normalizeAppError(error);
27
+ ctx.emit({
28
+ type: "action.failed",
29
+ label,
30
+ message: normalized.message,
31
+ ...normalized.hint ? { hint: normalized.hint } : {},
32
+ cause: error
33
+ });
34
+ }
35
+ function patchData(ctx, data) {
36
+ ctx.emit({ type: "data.patch", data });
37
+ }
38
+ function patchFooter(ctx, footer) {
39
+ ctx.emit({ type: "footer.patch", footer });
40
+ }
41
+
42
+ // packages/cli/src/app-opentui/command-pty-host.ts
43
+ import { basename } from "path";
44
+ import { fileURLToPath } from "url";
45
+ import { Terminal as XtermTerminal } from "@xterm/headless";
46
+ var MIN_COLS = 40;
47
+ var MIN_ROWS = 10;
48
+ var MAX_ROWS = 300;
49
+ var MAX_SNAPSHOT_LINES = 420;
50
+ var SNAPSHOT_DELAY_MS = 80;
51
+ var fallbackCliScriptPath = fileURLToPath(new URL("../../bin/rig.ts", import.meta.url));
52
+ var activeHost = null;
53
+ function clampCols(cols) {
54
+ return Math.max(MIN_COLS, Math.trunc(cols || 120));
55
+ }
56
+ function clampRows(rows) {
57
+ return Math.max(MIN_ROWS, Math.min(MAX_ROWS, Math.trunc(rows || 36)));
58
+ }
59
+ function childCommandPrefix() {
60
+ const execName = basename(process.execPath).toLowerCase();
61
+ const currentEntry = process.argv[1];
62
+ if (execName === "bun" || execName === "bun.exe") {
63
+ return currentEntry ? [process.execPath, currentEntry] : [process.execPath, fallbackCliScriptPath];
64
+ }
65
+ return [process.execPath];
66
+ }
67
+ function withEnv(base) {
68
+ const env = {};
69
+ for (const [key, value] of Object.entries(base ?? process.env)) {
70
+ if (key === "NO_COLOR")
71
+ continue;
72
+ if (typeof value === "string")
73
+ env[key] = value;
74
+ }
75
+ return {
76
+ ...env,
77
+ TERM: "xterm-256color",
78
+ COLORTERM: "truecolor",
79
+ FORCE_COLOR: env.FORCE_COLOR ?? "1",
80
+ RIG_PLAIN: "1",
81
+ RIG_OPENTUI_COMMAND_HOST: "1",
82
+ RIG_CLI_PLAIN_HELP: env.RIG_CLI_PLAIN_HELP ?? "1"
83
+ };
84
+ }
85
+ function commandLabel(argv, fallback) {
86
+ const command = argv.join(" ").trim();
87
+ return fallback?.trim() || (command ? `rig ${command}` : "rig");
88
+ }
89
+ async function startCommandPtyHost(options) {
90
+ activeHost?.dispose("replace");
91
+ const host = new CommandPtyHost(options);
92
+ activeHost = host;
93
+ await host.start();
94
+ return host;
95
+ }
96
+
97
+ class CommandPtyHost {
98
+ argv;
99
+ projectRoot;
100
+ label;
101
+ env;
102
+ onSnapshot;
103
+ onExit;
104
+ onError;
105
+ terminal;
106
+ decoder = new TextDecoder("utf-8");
107
+ proc = null;
108
+ pty = null;
109
+ status = "starting";
110
+ cols;
111
+ rows;
112
+ message = "starting command";
113
+ exitCode;
114
+ signal;
115
+ notifyTimer = null;
116
+ _disposed = false;
117
+ constructor(options) {
118
+ this.argv = [...options.argv];
119
+ this.projectRoot = options.projectRoot;
120
+ this.label = commandLabel(options.argv, options.label);
121
+ this.env = options.env;
122
+ this.cols = clampCols(options.cols);
123
+ this.rows = clampRows(options.rows);
124
+ this.onSnapshot = options.onSnapshot;
125
+ this.onExit = options.onExit;
126
+ this.onError = options.onError;
127
+ this.terminal = new XtermTerminal({ allowProposedApi: true, cols: this.cols, rows: this.rows, scrollback: 2000 });
128
+ }
129
+ get disposed() {
130
+ return this._disposed;
131
+ }
132
+ get snapshot() {
133
+ return this.createSnapshot();
134
+ }
135
+ async start() {
136
+ if (this._disposed)
137
+ throw new Error("Command PTY host is disposed.");
138
+ if (typeof Bun.Terminal !== "function") {
139
+ throw new Error("Bun native PTY is unavailable in this runtime. Command host requires Bun.Terminal.");
140
+ }
141
+ const spawnOptions = {
142
+ cwd: this.projectRoot,
143
+ env: withEnv(this.env),
144
+ terminal: {
145
+ cols: this.cols,
146
+ rows: this.rows,
147
+ name: "xterm-256color",
148
+ data: (_terminal, data) => this.handlePtyData(data)
149
+ }
150
+ };
151
+ const proc = Bun.spawn([...childCommandPrefix(), ...this.argv], spawnOptions);
152
+ if (!proc.terminal)
153
+ throw new Error("Bun did not attach a terminal to the command child process.");
154
+ this.proc = proc;
155
+ this.pty = proc.terminal;
156
+ this.status = "running";
157
+ this.message = "running";
158
+ this.emitSnapshotSoon(0);
159
+ proc.exited.then((exitCode) => {
160
+ if (this._disposed)
161
+ return;
162
+ this.status = exitCode === 0 ? "exited" : "failed";
163
+ this.exitCode = exitCode;
164
+ this.signal = null;
165
+ this.message = exitCode === 0 ? "completed" : `exited with code ${exitCode}`;
166
+ const snapshot = this.createSnapshot();
167
+ this.onSnapshot?.(snapshot);
168
+ this.onExit?.(snapshot);
169
+ if (activeHost === this)
170
+ activeHost = null;
171
+ this.dispose("exit", { kill: false, notify: false });
172
+ }).catch((error) => {
173
+ if (this._disposed)
174
+ return;
175
+ this.status = "failed";
176
+ this.message = error instanceof Error ? error.message : String(error);
177
+ const snapshot = this.createSnapshot();
178
+ this.onSnapshot?.(snapshot);
179
+ this.onError?.(error, snapshot);
180
+ if (activeHost === this)
181
+ activeHost = null;
182
+ this.dispose("error", { kill: false, notify: false });
183
+ });
184
+ }
185
+ write(data) {
186
+ if (this._disposed || !this.pty)
187
+ return;
188
+ try {
189
+ this.pty.write(data);
190
+ } catch (error) {
191
+ this.status = "failed";
192
+ this.message = error instanceof Error ? error.message : String(error);
193
+ const snapshot = this.createSnapshot();
194
+ this.onSnapshot?.(snapshot);
195
+ this.onError?.(error, snapshot);
196
+ }
197
+ }
198
+ resize(cols, rows) {
199
+ const nextCols = clampCols(cols);
200
+ const nextRows = clampRows(rows);
201
+ if (nextCols === this.cols && nextRows === this.rows)
202
+ return;
203
+ this.cols = nextCols;
204
+ this.rows = nextRows;
205
+ this.terminal.resize(nextCols, nextRows);
206
+ try {
207
+ this.pty?.resize(nextCols, nextRows);
208
+ } catch {}
209
+ this.emitSnapshotSoon(0);
210
+ }
211
+ dispose(reason = "dispose", options = {}) {
212
+ if (this._disposed)
213
+ return;
214
+ this._disposed = true;
215
+ if (this.notifyTimer)
216
+ clearTimeout(this.notifyTimer);
217
+ this.notifyTimer = null;
218
+ if (options.kill !== false) {
219
+ try {
220
+ this.proc?.kill("SIGTERM");
221
+ } catch {}
222
+ }
223
+ try {
224
+ this.pty?.close();
225
+ } catch {}
226
+ try {
227
+ this.terminal.dispose();
228
+ } catch {}
229
+ if (activeHost === this)
230
+ activeHost = null;
231
+ if (options.notify) {
232
+ this.message = reason;
233
+ this.onSnapshot?.(this.createSnapshot(reason));
234
+ }
235
+ }
236
+ handlePtyData(data) {
237
+ if (this._disposed)
238
+ return;
239
+ this.decoder.decode(data, { stream: true });
240
+ this.terminal.write(data, () => this.emitSnapshotSoon());
241
+ }
242
+ emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS) {
243
+ if (this._disposed || this.notifyTimer)
244
+ return;
245
+ this.notifyTimer = setTimeout(() => {
246
+ this.notifyTimer = null;
247
+ if (this._disposed)
248
+ return;
249
+ this.onSnapshot?.(this.createSnapshot());
250
+ }, delayMs);
251
+ }
252
+ createSnapshot(message = this.message) {
253
+ const buffer = this.terminal.buffer.active;
254
+ const end = buffer.length;
255
+ const start = Math.max(0, end - MAX_SNAPSHOT_LINES);
256
+ const lines = [];
257
+ for (let row = start;row < end; row += 1) {
258
+ const line = buffer.getLine(row);
259
+ lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
260
+ }
261
+ while (lines.length < this.rows)
262
+ lines.push("");
263
+ return {
264
+ command: this.argv,
265
+ label: this.label,
266
+ status: this.status,
267
+ cols: this.cols,
268
+ rows: this.rows,
269
+ lines,
270
+ message,
271
+ ...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
272
+ ...this.signal !== undefined ? { signal: this.signal } : {}
273
+ };
274
+ }
275
+ }
276
+
277
+ // packages/cli/src/app-opentui/adapters/command.ts
278
+ function commandLabel2(argv) {
279
+ const command = argv.join(" ").trim();
280
+ return command ? `rig ${command}` : "rig";
281
+ }
282
+ async function runCommandInAppPty(ctx, argv) {
283
+ const label = commandLabel2(argv);
284
+ emitStarted(ctx, label, { command: { label, argv, status: "starting" } });
285
+ patchData(ctx, {
286
+ command: { label, argv, status: "starting" },
287
+ commandTerminal: {
288
+ command: argv,
289
+ label,
290
+ status: "starting",
291
+ cols: 120,
292
+ rows: 32,
293
+ lines: [],
294
+ message: "starting"
295
+ }
296
+ });
297
+ patchFooter(ctx, { message: label });
298
+ try {
299
+ const host = await startCommandPtyHost({
300
+ argv,
301
+ projectRoot: projectRootOf(ctx),
302
+ cols: 120,
303
+ rows: 32,
304
+ label,
305
+ onSnapshot(snapshot) {
306
+ patchData(ctx, { commandTerminal: snapshot, command: { label, argv, status: snapshot.status, message: snapshot.message } });
307
+ patchFooter(ctx, { message: `${label} \xB7 ${snapshot.message ?? snapshot.status}` });
308
+ },
309
+ onExit(snapshot) {
310
+ patchData(ctx, { commandTerminal: snapshot, command: { label, argv, status: snapshot.status, message: snapshot.message } });
311
+ if (snapshot.status === "exited")
312
+ emitCompleted(ctx, label, { exitCode: snapshot.exitCode ?? 0 });
313
+ else
314
+ emitFailed(ctx, label, new Error(snapshot.message ?? "Command failed"), { commandTerminal: snapshot });
315
+ },
316
+ onError(error, snapshot) {
317
+ patchData(ctx, { commandTerminal: snapshot, command: { label, argv, status: "failed", message: snapshot.message } });
318
+ emitFailed(ctx, label, error, { commandTerminal: snapshot });
319
+ }
320
+ });
321
+ patchData(ctx, { commandTerminal: host.snapshot });
322
+ } catch (error) {
323
+ emitFailed(ctx, label, error);
324
+ throw error;
325
+ }
326
+ }
327
+ export {
328
+ runCommandInAppPty
329
+ };
@@ -13,7 +13,7 @@ function projectRootOf(ctx) {
13
13
  const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
14
14
  if (typeof root === "string" && root.trim())
15
15
  return root;
16
- throw new Error("Rig app adapter requires a projectRoot.");
16
+ throw new Error("App adapter requires a projectRoot.");
17
17
  }
18
18
  async function runtimeOf(ctx) {
19
19
  if (ctx.rig && "runId" in ctx.rig && ctx.ensureRuntime === undefined) {
@@ -21,7 +21,7 @@ async function runtimeOf(ctx) {
21
21
  }
22
22
  if (ctx.ensureRuntime)
23
23
  return ctx.ensureRuntime();
24
- throw new Error("Rig app adapter requires ensureRuntime() before this action can run.");
24
+ throw new Error("App adapter requires ensureRuntime() before this action can run.");
25
25
  }
26
26
  function normalizeAppError(error) {
27
27
  const message = error instanceof Error ? error.message : String(error);
@@ -145,17 +145,21 @@ function cleanToken(value) {
145
145
  const trimmed = value?.trim();
146
146
  return trimmed ? trimmed : null;
147
147
  }
148
- function readPrivateRemoteSessionToken(projectRoot) {
148
+ function readRemoteAuthState(projectRoot) {
149
149
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
150
150
  if (!existsSync2(path))
151
151
  return null;
152
152
  try {
153
153
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
154
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
154
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
155
155
  } catch {
156
156
  return null;
157
157
  }
158
158
  }
159
+ function readPrivateRemoteSessionToken(projectRoot) {
160
+ const parsed = readRemoteAuthState(projectRoot);
161
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
162
+ }
159
163
  function readGitHubBearerTokenForRemote(projectRoot) {
160
164
  const scopedKey = resolve2(projectRoot);
161
165
  if (scopedGitHubBearerTokens.has(scopedKey))
@@ -166,15 +170,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
166
170
  return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
167
171
  }
168
172
  function readStoredGitHubAuthToken(projectRoot) {
169
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
170
- if (!existsSync2(path))
173
+ const parsed = readRemoteAuthState(projectRoot);
174
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
175
+ }
176
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
177
+ const repo = readRepoConnection(projectRoot);
178
+ const slug = repo?.project?.trim();
179
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
171
180
  return null;
172
- try {
173
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
174
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
175
- } catch {
181
+ const auth = readRemoteAuthState(projectRoot);
182
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
183
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
184
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
176
185
  return null;
177
- }
186
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
187
+ if (!checkoutBaseDir)
188
+ return null;
189
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
190
+ writeRepoServerProjectRoot(projectRoot, inferred);
191
+ return inferred;
178
192
  }
179
193
  function readLocalConnectionFallbackToken(projectRoot) {
180
194
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
@@ -185,7 +199,7 @@ async function ensureServerForCli(projectRoot) {
185
199
  if (selected?.connection.kind === "remote") {
186
200
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
187
201
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
188
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
202
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
189
203
  return {
190
204
  baseUrl: selected.connection.baseUrl,
191
205
  authToken,
@@ -214,7 +228,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
214
228
  if (!slug)
215
229
  return null;
216
230
  try {
217
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
231
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
232
+ if (authToken && queryAuthFallbackEnabled())
233
+ url.searchParams.set("rt", authToken);
234
+ const response = await fetch(url, {
218
235
  headers: mergeHeaders(undefined, authToken)
219
236
  });
220
237
  if (!response.ok)
@@ -231,10 +248,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
231
248
  return null;
232
249
  }
233
250
  }
251
+ function mergeCookie(existing, name, value) {
252
+ const encoded = `${name}=${encodeURIComponent(value)}`;
253
+ if (!existing?.trim())
254
+ return encoded;
255
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
256
+ return [...parts, encoded].join("; ");
257
+ }
258
+ function queryAuthFallbackEnabled(env = process.env) {
259
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
260
+ }
234
261
  function mergeHeaders(headers, authToken) {
235
262
  const merged = new Headers(headers);
236
263
  if (authToken) {
237
- merged.set("authorization", `Bearer ${authToken}`);
264
+ const bearer = `Bearer ${authToken}`;
265
+ merged.set("authorization", bearer);
266
+ merged.set("x-auth", bearer);
267
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
238
268
  }
239
269
  return merged;
240
270
  }
@@ -294,15 +324,34 @@ async function buildServerFailureContext(projectRoot, server) {
294
324
  hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
295
325
  };
296
326
  }
327
+ function isLoopbackRemoteBaseUrl(baseUrl) {
328
+ try {
329
+ const host = new URL(baseUrl).hostname.toLowerCase();
330
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
331
+ } catch {
332
+ return false;
333
+ }
334
+ }
335
+ function canUseRemoteWithoutProjectRoot(pathname) {
336
+ return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
337
+ }
297
338
  async function requestServerJson(context, pathname, init = {}) {
298
339
  const server = await ensureServerForCli(context.projectRoot);
340
+ const requestUrl = new URL(`${server.baseUrl}${pathname}`);
341
+ if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
342
+ const repo = readRepoConnection(context.projectRoot);
343
+ 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." });
344
+ }
299
345
  const headers = mergeHeaders(init.headers, server.authToken);
300
346
  if (server.serverProjectRoot)
301
347
  headers.set("x-rig-project-root", server.serverProjectRoot);
348
+ if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
349
+ requestUrl.searchParams.set("rt", server.authToken);
350
+ }
302
351
  reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
303
352
  let response;
304
353
  try {
305
- response = await fetch(`${server.baseUrl}${pathname}`, {
354
+ response = await fetch(requestUrl, {
306
355
  ...init,
307
356
  headers
308
357
  });
@@ -699,7 +748,7 @@ function projectRootOf(ctx) {
699
748
  const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
700
749
  if (typeof root === "string" && root.trim())
701
750
  return root;
702
- throw new Error("Rig app adapter requires a projectRoot.");
751
+ throw new Error("App adapter requires a projectRoot.");
703
752
  }
704
753
  function normalizeAppError(error) {
705
754
  const message = error instanceof Error ? error.message : String(error);