@h-rig/cli 0.0.6-alpha.9 → 0.0.6-alpha.90

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 (114) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/build-rig-binaries.d.ts +2 -0
  3. package/dist/bin/rig.d.ts +2 -0
  4. package/dist/bin/rig.js +11052 -6668
  5. package/dist/src/app/board.d.ts +23 -0
  6. package/dist/src/app/board.js +1786 -0
  7. package/dist/src/app/drone-ui.d.ts +37 -0
  8. package/dist/src/app/drone-ui.js +294 -0
  9. package/dist/src/app/theme.d.ts +47 -0
  10. package/dist/src/app/theme.js +150 -0
  11. package/dist/src/commands/_async-ui.d.ts +13 -0
  12. package/dist/src/commands/_async-ui.js +223 -0
  13. package/dist/src/commands/_authority-runs.d.ts +22 -0
  14. package/dist/src/commands/_authority-runs.js +2 -3
  15. package/dist/src/commands/_cli-format.d.ts +49 -0
  16. package/dist/src/commands/_cli-format.js +447 -0
  17. package/dist/src/commands/_connection-state.d.ts +44 -0
  18. package/dist/src/commands/_connection-state.js +30 -11
  19. package/dist/src/commands/_doctor-checks.d.ts +52 -0
  20. package/dist/src/commands/_doctor-checks.js +180 -44
  21. package/dist/src/commands/_help-catalog.d.ts +51 -0
  22. package/dist/src/commands/_help-catalog.js +442 -0
  23. package/dist/src/commands/_json-output.d.ts +11 -0
  24. package/dist/src/commands/_json-output.js +56 -0
  25. package/dist/src/commands/_operator-surface.d.ts +34 -0
  26. package/dist/src/commands/_operator-surface.js +220 -0
  27. package/dist/src/commands/_operator-view.d.ts +30 -0
  28. package/dist/src/commands/_operator-view.js +803 -73
  29. package/dist/src/commands/_parsers.d.ts +15 -0
  30. package/dist/src/commands/_parsers.js +18 -11
  31. package/dist/src/commands/_paths.d.ts +11 -0
  32. package/dist/src/commands/_pi-frontend.d.ts +27 -0
  33. package/dist/src/commands/_pi-frontend.js +742 -0
  34. package/dist/src/commands/_pi-install.d.ts +42 -0
  35. package/dist/src/commands/_pi-install.js +5 -4
  36. package/dist/src/commands/_policy.d.ts +8 -0
  37. package/dist/src/commands/_policy.js +12 -5
  38. package/dist/src/commands/_preflight.d.ts +22 -0
  39. package/dist/src/commands/_preflight.js +190 -128
  40. package/dist/src/commands/_probes.d.ts +1 -0
  41. package/dist/src/commands/_run-driver-helpers.d.ts +99 -0
  42. package/dist/src/commands/_run-driver-helpers.js +75 -22
  43. package/dist/src/commands/_run-replay.d.ts +24 -0
  44. package/dist/src/commands/_run-replay.js +142 -0
  45. package/dist/src/commands/_server-client.d.ts +186 -0
  46. package/dist/src/commands/_server-client.js +360 -61
  47. package/dist/src/commands/_snapshot-upload.d.ts +39 -0
  48. package/dist/src/commands/_snapshot-upload.js +163 -39
  49. package/dist/src/commands/_spinner.d.ts +25 -0
  50. package/dist/src/commands/_spinner.js +65 -0
  51. package/dist/src/commands/_task-picker.d.ts +9 -0
  52. package/dist/src/commands/_task-picker.js +172 -19
  53. package/dist/src/commands/agent.d.ts +3 -0
  54. package/dist/src/commands/agent.js +39 -20
  55. package/dist/src/commands/browser.d.ts +65 -0
  56. package/dist/src/commands/browser.js +334 -51
  57. package/dist/src/commands/connect.d.ts +7 -0
  58. package/dist/src/commands/connect.js +272 -33
  59. package/dist/src/commands/dist.d.ts +28 -0
  60. package/dist/src/commands/dist.js +19 -12
  61. package/dist/src/commands/doctor.d.ts +3 -0
  62. package/dist/src/commands/doctor.js +378 -45
  63. package/dist/src/commands/github.d.ts +3 -0
  64. package/dist/src/commands/github.js +375 -53
  65. package/dist/src/commands/inbox.d.ts +30 -0
  66. package/dist/src/commands/inbox.js +760 -70
  67. package/dist/src/commands/init.d.ts +74 -0
  68. package/dist/src/commands/init.js +963 -119
  69. package/dist/src/commands/inspect.d.ts +3 -0
  70. package/dist/src/commands/inspect.js +588 -32
  71. package/dist/src/commands/inspector.d.ts +3 -0
  72. package/dist/src/commands/inspector.js +20 -13
  73. package/dist/src/commands/pi.d.ts +3 -0
  74. package/dist/src/commands/pi.js +177 -0
  75. package/dist/src/commands/plugin.d.ts +16 -0
  76. package/dist/src/commands/plugin.js +95 -27
  77. package/dist/src/commands/profile-and-review.d.ts +4 -0
  78. package/dist/src/commands/profile-and-review.js +26 -19
  79. package/dist/src/commands/queue.d.ts +3 -0
  80. package/dist/src/commands/queue.js +32 -12
  81. package/dist/src/commands/remote.d.ts +3 -0
  82. package/dist/src/commands/remote.js +43 -36
  83. package/dist/src/commands/repo-git-harness.d.ts +5 -0
  84. package/dist/src/commands/repo-git-harness.js +22 -15
  85. package/dist/src/commands/run.d.ts +3 -0
  86. package/dist/src/commands/run.js +1379 -159
  87. package/dist/src/commands/server.d.ts +7 -0
  88. package/dist/src/commands/server.js +502 -57
  89. package/dist/src/commands/setup.d.ts +16 -0
  90. package/dist/src/commands/setup.js +390 -63
  91. package/dist/src/commands/stats.d.ts +12 -0
  92. package/dist/src/commands/stats.js +1051 -0
  93. package/dist/src/commands/task-report-bug.d.ts +19 -0
  94. package/dist/src/commands/task-report-bug.js +260 -62
  95. package/dist/src/commands/task-run-driver.d.ts +132 -0
  96. package/dist/src/commands/task-run-driver.js +886 -130
  97. package/dist/src/commands/task.d.ts +14 -0
  98. package/dist/src/commands/task.js +1644 -314
  99. package/dist/src/commands/test.d.ts +3 -0
  100. package/dist/src/commands/test.js +15 -8
  101. package/dist/src/commands/workspace.d.ts +3 -0
  102. package/dist/src/commands/workspace.js +18 -11
  103. package/dist/src/commands.d.ts +29 -0
  104. package/dist/src/commands.js +10967 -6637
  105. package/dist/src/index.d.ts +4 -0
  106. package/dist/src/index.js +10168 -5787
  107. package/dist/src/launcher.d.ts +61 -0
  108. package/dist/src/launcher.js +77 -13
  109. package/dist/src/report-bug.d.ts +44 -0
  110. package/dist/src/report-bug.js +3 -3
  111. package/dist/src/runner.d.ts +47 -0
  112. package/dist/src/runner.js +16 -22
  113. package/dist/src/withMutedConsole.d.ts +2 -0
  114. package/package.json +13 -6
@@ -0,0 +1,42 @@
1
+ export type PiInstallCheck = {
2
+ readonly ok: boolean;
3
+ readonly label: string;
4
+ readonly detail?: string;
5
+ readonly hint?: string;
6
+ };
7
+ export type CommandRunner = (command: string[], options?: {
8
+ cwd?: string;
9
+ }) => Promise<{
10
+ exitCode: number;
11
+ stdout: string;
12
+ stderr: string;
13
+ }>;
14
+ export type PiRigInstallStatus = {
15
+ readonly pi: PiInstallCheck;
16
+ readonly piRig: PiInstallCheck;
17
+ readonly extensionPath: string;
18
+ };
19
+ export declare function resolvePiRigExtensionPath(homeDir: string): string;
20
+ export declare function resolvePiRigPackageSource(projectRoot: string, exists?: (path: string) => boolean): string;
21
+ export declare function checkPiRigInstall(input?: {
22
+ readonly homeDir?: string;
23
+ readonly commandRunner?: CommandRunner;
24
+ readonly exists?: (path: string) => boolean;
25
+ }): Promise<PiRigInstallStatus>;
26
+ export declare function ensurePiRigInstalled(input: {
27
+ readonly projectRoot: string;
28
+ readonly homeDir?: string;
29
+ readonly commandRunner?: CommandRunner;
30
+ }): Promise<PiRigInstallStatus & {
31
+ installedPath: string;
32
+ }>;
33
+ export declare function ensureRemotePiRigInstalled(input: {
34
+ readonly requestJson: (pathname: string, init?: RequestInit) => Promise<unknown>;
35
+ }): Promise<PiRigInstallStatus & {
36
+ readonly remote: true;
37
+ }>;
38
+ export declare function buildPiSetupChecks(input?: {
39
+ readonly homeDir?: string;
40
+ readonly commandRunner?: CommandRunner;
41
+ readonly exists?: (path: string) => boolean;
42
+ }): Promise<PiInstallCheck[]>;
@@ -3,7 +3,8 @@
3
3
  import { existsSync, readFileSync, rmSync } from "fs";
4
4
  import { homedir } from "os";
5
5
  import { resolve } from "path";
6
- var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
6
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
7
+ var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
7
8
  var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
8
9
  export { default } from '@rig/pi-rig';
9
10
  `;
@@ -31,7 +32,7 @@ function resolvePiHomeDir(inputHomeDir) {
31
32
  function piListContainsPiRig(output) {
32
33
  return output.split(/\r?\n/).some((line) => {
33
34
  const normalized = line.trim();
34
- return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
35
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
35
36
  });
36
37
  }
37
38
  async function safeRun(runner, command, options) {
@@ -55,7 +56,7 @@ async function ensurePiBinaryAvailable(input) {
55
56
  }
56
57
  return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
57
58
  }
58
- const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi@latest install";
59
+ const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi-coding-agent@latest install";
59
60
  const parts = splitInstallCommand(installCommand);
60
61
  if (parts.length === 0) {
61
62
  return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
@@ -147,7 +148,7 @@ async function ensureRemotePiRigInstalled(input) {
147
148
  const payload = await input.requestJson("/api/pi-rig/install", {
148
149
  method: "POST",
149
150
  headers: { "content-type": "application/json" },
150
- body: JSON.stringify({ package: "@rig/pi-rig", scope: "global" })
151
+ body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
151
152
  });
152
153
  const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
153
154
  const piOk = record.piOk === true || record.ok === true;
@@ -0,0 +1,8 @@
1
+ import { type RunnerContext } from "../runner";
2
+ import type { PolicyMode } from "@rig/runtime/control-plane/runtime/guard-types";
3
+ export declare function resolveEffectivePolicyMode(context: RunnerContext): PolicyMode;
4
+ export declare function appendControlledBashAudit(context: RunnerContext, mode: PolicyMode, command: string, args: string[], matchedRuleIds: string[], action?: "warn" | "blocked"): void;
5
+ export declare function enforceNativeCommandPolicy(context: RunnerContext, args: string[], options: {
6
+ commandPrefix: string;
7
+ writeAuditLog: boolean;
8
+ }): Promise<void>;
@@ -5,12 +5,19 @@ import { resolve } from "path";
5
5
 
6
6
  // packages/cli/src/runner.ts
7
7
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
8
- import { CliError } from "@rig/runtime/control-plane/errors";
8
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
9
9
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
10
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
11
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
12
10
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
13
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
11
+
12
+ class CliError extends RuntimeCliError {
13
+ hint;
14
+ constructor(message, exitCode = 1, options = {}) {
15
+ super(message, exitCode);
16
+ if (options.hint?.trim()) {
17
+ this.hint = options.hint.trim();
18
+ }
19
+ }
20
+ }
14
21
  function formatCommand(parts) {
15
22
  return parts.map((part) => /[^a-zA-Z0-9_./:-]/.test(part) ? JSON.stringify(part) : part).join(" ");
16
23
  }
@@ -69,7 +76,7 @@ async function enforceNativeCommandPolicy(context, args, options) {
69
76
  appendControlledBashAudit(context, mode, command, args, matchedRuleIds, action === "block" ? "blocked" : action === "warn" ? "warn" : undefined);
70
77
  }
71
78
  if (action === "block") {
72
- throw new CliError2(`Policy blocked command: ${command}`, 126);
79
+ throw new CliError(`Policy blocked command: ${command}`, 126, { hint: "Review rig/policy/policy.json, or re-run with `--policy-mode observe` to log instead of block." });
73
80
  }
74
81
  }
75
82
  export {
@@ -0,0 +1,22 @@
1
+ import { type RunnerContext } from "../runner";
2
+ export type TaskRunPreflightStatus = "pass" | "warn" | "fail";
3
+ export type TaskRunPreflightCheck = {
4
+ readonly id: string;
5
+ readonly label: string;
6
+ readonly status: TaskRunPreflightStatus;
7
+ readonly detail?: string;
8
+ readonly remediation?: string;
9
+ };
10
+ export type TaskRunPreflightResult = {
11
+ readonly ok: boolean;
12
+ readonly checks: readonly TaskRunPreflightCheck[];
13
+ };
14
+ export type TaskRunPreflightOptions = {
15
+ readonly taskId?: string | null;
16
+ readonly runtimeAdapter?: "claude-code" | "codex" | "pi";
17
+ readonly requestJson?: (pathname: string, init?: RequestInit) => Promise<unknown>;
18
+ };
19
+ export declare function runFastTaskRunPreflight(context: RunnerContext, options?: TaskRunPreflightOptions): Promise<TaskRunPreflightResult>;
20
+ export declare function runProjectMainSyncPreflight(context: RunnerContext, options: {
21
+ disabled: boolean;
22
+ }): Promise<void>;
@@ -1,12 +1,19 @@
1
1
  // @bun
2
2
  // packages/cli/src/runner.ts
3
3
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
4
- import { CliError } from "@rig/runtime/control-plane/errors";
4
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
5
5
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
6
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
7
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
8
6
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
9
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
7
+
8
+ class CliError extends RuntimeCliError {
9
+ hint;
10
+ constructor(message, exitCode = 1, options = {}) {
11
+ super(message, exitCode);
12
+ if (options.hint?.trim()) {
13
+ this.hint = options.hint.trim();
14
+ }
15
+ }
16
+ }
10
17
 
11
18
  // packages/cli/src/commands/_preflight.ts
12
19
  import { ensureProjectMainFreshBeforeRun } from "@rig/runtime/control-plane/project-main-pre-run-sync";
@@ -33,9 +40,14 @@ function readJsonFile(path) {
33
40
  try {
34
41
  return JSON.parse(readFileSync(path, "utf8"));
35
42
  } catch (error) {
36
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
43
+ throw new CliError(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1, { hint: "Fix or delete that file, then re-select a server with `rig server use <alias|local>`." });
37
44
  }
38
45
  }
46
+ function writeJsonFile(path, value) {
47
+ mkdirSync(dirname(path), { recursive: true });
48
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
49
+ `, "utf8");
50
+ }
39
51
  function normalizeConnection(value) {
40
52
  if (!value || typeof value !== "object" || Array.isArray(value))
41
53
  return null;
@@ -76,29 +88,42 @@ function readRepoConnection(projectRoot) {
76
88
  return {
77
89
  selected,
78
90
  project: typeof record.project === "string" ? record.project : undefined,
79
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
91
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
92
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
80
93
  };
81
94
  }
95
+ function writeRepoConnection(projectRoot, state) {
96
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
97
+ }
82
98
  function resolveSelectedConnection(projectRoot, options = {}) {
83
99
  const repo = readRepoConnection(projectRoot);
84
100
  if (!repo)
85
101
  return null;
86
102
  if (repo.selected === "local")
87
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
103
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
88
104
  const global = readGlobalConnections(options);
89
105
  const connection = global.connections[repo.selected];
90
106
  if (!connection) {
91
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
107
+ throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
92
108
  }
93
- return { alias: repo.selected, connection };
109
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
110
+ }
111
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
112
+ const repo = readRepoConnection(projectRoot);
113
+ if (!repo)
114
+ return;
115
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
94
116
  }
95
117
 
96
118
  // packages/cli/src/commands/_server-client.ts
97
- import { spawnSync } from "child_process";
98
119
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
99
120
  import { resolve as resolve2 } from "path";
100
121
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
101
- var cachedGitHubBearerToken;
122
+ var scopedGitHubBearerTokens = new Map;
123
+ var serverPhaseListener = null;
124
+ function reportServerPhase(label) {
125
+ serverPhaseListener?.(label);
126
+ }
102
127
  function cleanToken(value) {
103
128
  const trimmed = value?.trim();
104
129
  return trimmed ? trimmed : null;
@@ -115,49 +140,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
115
140
  }
116
141
  }
117
142
  function readGitHubBearerTokenForRemote(projectRoot) {
118
- if (cachedGitHubBearerToken !== undefined)
119
- return cachedGitHubBearerToken;
143
+ const scopedKey = resolve2(projectRoot);
144
+ if (scopedGitHubBearerTokens.has(scopedKey))
145
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
120
146
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
121
- if (privateSession) {
122
- cachedGitHubBearerToken = privateSession;
123
- return cachedGitHubBearerToken;
124
- }
125
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
126
- if (envToken) {
127
- cachedGitHubBearerToken = envToken;
128
- return cachedGitHubBearerToken;
147
+ if (privateSession)
148
+ return privateSession;
149
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
150
+ }
151
+ function readStoredGitHubAuthToken(projectRoot) {
152
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
153
+ if (!existsSync2(path))
154
+ return null;
155
+ try {
156
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
157
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
158
+ } catch {
159
+ return null;
129
160
  }
130
- const result = spawnSync("gh", ["auth", "token"], {
131
- encoding: "utf8",
132
- timeout: 5000,
133
- stdio: ["ignore", "pipe", "ignore"]
134
- });
135
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
136
- return cachedGitHubBearerToken;
161
+ }
162
+ function readLocalConnectionFallbackToken(projectRoot) {
163
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
137
164
  }
138
165
  async function ensureServerForCli(projectRoot) {
139
166
  try {
140
167
  const selected = resolveSelectedConnection(projectRoot);
141
168
  if (selected?.connection.kind === "remote") {
169
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
170
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
171
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
142
172
  return {
143
173
  baseUrl: selected.connection.baseUrl,
144
- authToken: readGitHubBearerTokenForRemote(projectRoot),
145
- connectionKind: "remote"
174
+ authToken,
175
+ connectionKind: "remote",
176
+ serverProjectRoot
146
177
  };
147
178
  }
179
+ reportServerPhase("Starting local Rig server\u2026");
148
180
  const connection = await ensureLocalRigServerConnection(projectRoot);
149
181
  return {
150
182
  baseUrl: connection.baseUrl,
151
- authToken: connection.authToken,
152
- connectionKind: "local"
183
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
184
+ connectionKind: "local",
185
+ serverProjectRoot: resolve2(projectRoot)
153
186
  };
154
187
  } catch (error) {
155
188
  if (error instanceof Error) {
156
- throw new CliError2(error.message, 1);
189
+ throw new CliError(error.message, 1);
157
190
  }
158
191
  throw error;
159
192
  }
160
193
  }
194
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
195
+ const repo = readRepoConnection(projectRoot);
196
+ const slug = repo?.project?.trim();
197
+ if (!slug)
198
+ return null;
199
+ try {
200
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
201
+ headers: mergeHeaders(undefined, authToken)
202
+ });
203
+ if (!response.ok)
204
+ return null;
205
+ const payload = await response.json();
206
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
207
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
208
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
209
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
210
+ if (path)
211
+ writeRepoServerProjectRoot(projectRoot, path);
212
+ return path;
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
161
217
  function mergeHeaders(headers, authToken) {
162
218
  const merged = new Headers(headers);
163
219
  if (authToken) {
@@ -180,12 +236,65 @@ function diagnosticMessage(payload) {
180
236
  });
181
237
  return messages.length > 0 ? messages.join("; ") : null;
182
238
  }
239
+ var serverReachabilityCache = new Map;
240
+ async function probeServerReachability(baseUrl, authToken) {
241
+ try {
242
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
243
+ headers: mergeHeaders(undefined, authToken),
244
+ signal: AbortSignal.timeout(1500)
245
+ });
246
+ return response.ok;
247
+ } catch {
248
+ return false;
249
+ }
250
+ }
251
+ function cachedServerReachability(projectRoot, baseUrl, authToken) {
252
+ const key = resolve2(projectRoot);
253
+ const cached = serverReachabilityCache.get(key);
254
+ if (cached)
255
+ return cached;
256
+ const probe = probeServerReachability(baseUrl, authToken);
257
+ serverReachabilityCache.set(key, probe);
258
+ return probe;
259
+ }
260
+ function describeSelectedServer(projectRoot, server) {
261
+ try {
262
+ const selected = resolveSelectedConnection(projectRoot);
263
+ if (selected) {
264
+ return {
265
+ alias: selected.alias,
266
+ target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
267
+ };
268
+ }
269
+ } catch {}
270
+ return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
271
+ }
272
+ async function buildServerFailureContext(projectRoot, server) {
273
+ const { alias, target } = describeSelectedServer(projectRoot, server);
274
+ const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
275
+ const reachability = reachable ? "server is reachable" : "server is unreachable";
276
+ return {
277
+ contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
278
+ hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
279
+ };
280
+ }
183
281
  async function requestServerJson(context, pathname, init = {}) {
184
282
  const server = await ensureServerForCli(context.projectRoot);
185
- const response = await fetch(`${server.baseUrl}${pathname}`, {
186
- ...init,
187
- headers: mergeHeaders(init.headers, server.authToken)
188
- });
283
+ const headers = mergeHeaders(init.headers, server.authToken);
284
+ if (server.serverProjectRoot)
285
+ headers.set("x-rig-project-root", server.serverProjectRoot);
286
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
287
+ let response;
288
+ try {
289
+ response = await fetch(`${server.baseUrl}${pathname}`, {
290
+ ...init,
291
+ headers
292
+ });
293
+ } catch (error) {
294
+ const failure = await buildServerFailureContext(context.projectRoot, server);
295
+ throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
296
+ ${failure.contextLine}`, 1, { hint: failure.hint });
297
+ }
189
298
  const text = await response.text();
190
299
  const payload = text.trim().length > 0 ? (() => {
191
300
  try {
@@ -196,83 +305,26 @@ async function requestServerJson(context, pathname, init = {}) {
196
305
  })() : null;
197
306
  if (!response.ok) {
198
307
  const diagnostics = diagnosticMessage(payload);
199
- const detail = diagnostics ?? (text || response.statusText);
200
- throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
308
+ const rawDetail = diagnostics ?? (text || response.statusText);
309
+ const detail = diagnostics ? rawDetail : rawDetail.split(`
310
+ `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
311
+ const failure = await buildServerFailureContext(context.projectRoot, server);
312
+ throw new CliError(`Rig server request failed (${response.status}): ${detail}
313
+ ${failure.contextLine}`, 1, { hint: failure.hint });
201
314
  }
202
315
  return payload;
203
316
  }
204
-
205
- // packages/cli/src/commands/_pi-install.ts
206
- import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
207
- import { homedir as homedir2 } from "os";
208
- import { resolve as resolve3 } from "path";
209
- var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
210
- async function defaultCommandRunner(command, options = {}) {
211
- const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
212
- const [stdout, stderr, exitCode] = await Promise.all([
213
- new Response(proc.stdout).text(),
214
- new Response(proc.stderr).text(),
215
- proc.exited
216
- ]);
217
- return { exitCode, stdout, stderr };
218
- }
219
- function resolvePiRigExtensionPath(homeDir) {
220
- return resolve3(homeDir, ".pi", "agent", "extensions", "pi-rig");
221
- }
222
- function resolvePiHomeDir(inputHomeDir) {
223
- return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir2();
224
- }
225
- function piListContainsPiRig(output) {
226
- return output.split(/\r?\n/).some((line) => {
227
- const normalized = line.trim();
228
- return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
229
- });
230
- }
231
- async function safeRun(runner, command, options) {
232
- try {
233
- return await runner(command, options);
234
- } catch (error) {
235
- return { exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
236
- }
237
- }
238
- async function checkPiRigInstall(input = {}) {
239
- const home = resolvePiHomeDir(input.homeDir);
240
- const extensionPath = resolvePiRigExtensionPath(home);
241
- if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
242
- return {
243
- extensionPath,
244
- pi: { ok: true, label: "pi", detail: "fake-pi" },
245
- piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
246
- };
247
- }
248
- const exists = input.exists ?? existsSync3;
249
- const runner = input.commandRunner ?? defaultCommandRunner;
250
- const piResult = await safeRun(runner, ["pi", "--version"]);
251
- const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
252
- const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
253
- ${piListResult.stderr}`);
254
- const legacyBridge = exists(resolve3(extensionPath, "index.ts"));
255
- const hasPiRig = listedPiRig;
256
- return {
257
- extensionPath,
258
- pi: {
259
- ok: piResult.exitCode === 0,
260
- label: "pi",
261
- detail: (piResult.stdout || piResult.stderr).trim() || undefined,
262
- hint: piResult.exitCode === 0 ? undefined : "Install Pi or run `rig init --yes` to install/update the Pi runtime."
263
- },
264
- piRig: {
265
- ok: hasPiRig,
266
- label: "pi-rig global extension",
267
- detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : legacyBridge ? `${extensionPath} (legacy bridge; reinstall required)` : undefined,
268
- hint: hasPiRig ? undefined : "Run `rig init --yes` to install/enable the global pi-rig package with `pi install`."
269
- }
270
- };
271
- }
272
- async function buildPiSetupChecks(input = {}) {
273
- const status = await checkPiRigInstall(input);
274
- return [status.pi, status.piRig];
275
- }
317
+ var RESUMABLE_RUN_STATUSES = new Set([
318
+ "created",
319
+ "preparing",
320
+ "running",
321
+ "validating",
322
+ "reviewing",
323
+ "stopped",
324
+ "failed",
325
+ "needs-attention",
326
+ "needs_attention"
327
+ ]);
276
328
 
277
329
  // packages/cli/src/commands/_preflight.ts
278
330
  function preflightCheck(id, label, status, detail, remediation) {
@@ -329,6 +381,9 @@ function permissionAllowsPr(payload) {
329
381
  }
330
382
  return null;
331
383
  }
384
+ function isNotFoundError(error) {
385
+ return /\b(404|not found)\b/i.test(message(error));
386
+ }
332
387
  function projectCheckoutReady(payload) {
333
388
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
334
389
  return null;
@@ -361,19 +416,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
361
416
  const checks = [];
362
417
  const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
363
418
  const taskId = options.taskId?.trim() || null;
419
+ const requiresCurrentRunApi = Boolean(taskId);
420
+ const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
421
+ const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
422
+ let legacyServerCompatibility = false;
364
423
  try {
365
424
  await request("/api/server/status");
366
425
  checks.push(preflightCheck("server", "Rig server reachable", "pass"));
367
426
  } catch (error) {
368
- checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
427
+ if (isNotFoundError(error)) {
428
+ try {
429
+ await request("/health");
430
+ legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
431
+ checks.push(requiresCurrentRunApi && !allowLocalLegacyTaskRunCompatibility ? preflightCheck("server", "Rig server reachable", "fail", "legacy /health endpoint only; current task-run APIs are required", "Upgrade/select the Rig server before launching a task run.") : preflightCheck("server", "Rig server reachable", "pass", allowLocalLegacyTaskRunCompatibility ? "local legacy /health endpoint; submit endpoint will be authoritative" : "legacy /health endpoint"));
432
+ } catch (healthError) {
433
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
434
+ }
435
+ } else {
436
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
437
+ }
369
438
  }
370
439
  const repo = readRepoConnection(context.projectRoot);
371
- checks.push(repo ? preflightCheck("project-link", "project linked to Rig connection", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to Rig connection", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig connect use <alias|local>`."));
440
+ checks.push(repo ? preflightCheck("project-link", "project linked to Rig server", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to Rig server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
372
441
  try {
373
442
  const auth = await request("/api/github/auth/status");
374
- checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
443
+ checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
375
444
  } catch (error) {
376
- checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
445
+ checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
377
446
  }
378
447
  try {
379
448
  const projection = await request("/api/workspace/task-projection");
@@ -401,9 +470,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
401
470
  try {
402
471
  const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
403
472
  const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
404
- checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
473
+ 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."));
405
474
  } catch (error) {
406
- checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
475
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
407
476
  }
408
477
  try {
409
478
  const runs = await request("/api/runs?limit=200");
@@ -414,14 +483,7 @@ async function runFastTaskRunPreflight(context, options = {}) {
414
483
  }
415
484
  }
416
485
  if ((options.runtimeAdapter ?? "pi") === "pi") {
417
- const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
418
- ok: false,
419
- label: "pi/pi-rig checks",
420
- hint: message(error)
421
- }]);
422
- for (const pi of piChecks) {
423
- checks.push(preflightCheck(pi.label === "pi" ? "pi" : "pi-rig", pi.label, pi.ok ? "pass" : "fail", pi.detail, pi.hint ?? (pi.ok ? undefined : "Run `rig init --yes` to install/update Pi and enable pi-rig.")));
424
- }
486
+ checks.push(preflightCheck("runtime", "worker Pi SDK session daemon", "pass", selectedServer?.connectionKind === "remote" ? "remote worker-owned runtime" : "bundled server-owned runtime"));
425
487
  } else {
426
488
  checks.push(preflightCheck("runtime", "runtime adapter", "pass", options.runtimeAdapter));
427
489
  }
@@ -429,9 +491,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
429
491
  if (failures.length > 0) {
430
492
  const summary = failures.map((check) => `${check.label}${check.detail ? `: ${check.detail}` : ""}`).join("; ");
431
493
  if (failures.some((check) => check.id === "duplicate-active-run") && taskId) {
432
- throw new CliError2(`Task ${taskId} already has an active Rig run. ${summary}`, 1);
494
+ throw new CliError(`Task ${taskId} already has an active Rig run. ${summary}`, 1, { hint: `Attach to it with \`rig run attach <run-id> --follow\`, or stop it first with \`rig run stop <run-id>\`.` });
433
495
  }
434
- throw new CliError2(`Task run preflight failed: ${summary}`, 1);
496
+ throw new CliError(`Task run preflight failed: ${summary}`, 1, { hint: "Run `rig doctor` to diagnose, then retry `rig task run`." });
435
497
  }
436
498
  return { ok: true, checks };
437
499
  }
@@ -448,7 +510,7 @@ async function runProjectMainSyncPreflight(context, options) {
448
510
  runBootstrap: async () => {
449
511
  const bootstrap = await context.runCommand(["bun", "run", "bootstrap"]);
450
512
  if (bootstrap.exitCode !== 0) {
451
- throw new CliError2(bootstrap.stderr || bootstrap.stdout || "bun run bootstrap failed during project pre-run sync", bootstrap.exitCode || 1);
513
+ throw new CliError(bootstrap.stderr || bootstrap.stdout || "bun run bootstrap failed during project pre-run sync", bootstrap.exitCode || 1);
452
514
  }
453
515
  }
454
516
  });
@@ -0,0 +1 @@
1
+ export declare function runQuietBinaryProbe(binaryPath: string, args: string[], cwd: string): Promise<boolean>;