@h-rig/cli 0.0.6-alpha.7 → 0.0.6-alpha.70

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 (53) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/rig.js +4507 -1506
  3. package/dist/src/commands/_async-ui.js +152 -0
  4. package/dist/src/commands/_authority-runs.js +2 -3
  5. package/dist/src/commands/_cli-format.js +369 -0
  6. package/dist/src/commands/_connection-state.js +30 -11
  7. package/dist/src/commands/_doctor-checks.js +177 -43
  8. package/dist/src/commands/_help-catalog.js +485 -0
  9. package/dist/src/commands/_json-output.js +56 -0
  10. package/dist/src/commands/_operator-surface.js +220 -0
  11. package/dist/src/commands/_operator-view.js +595 -72
  12. package/dist/src/commands/_parsers.js +18 -11
  13. package/dist/src/commands/_pi-frontend.js +411 -0
  14. package/dist/src/commands/_pi-install.js +4 -3
  15. package/dist/src/commands/_policy.js +12 -5
  16. package/dist/src/commands/_preflight.js +187 -127
  17. package/dist/src/commands/_run-driver-helpers.js +75 -22
  18. package/dist/src/commands/_run-replay.js +142 -0
  19. package/dist/src/commands/_server-client.js +343 -60
  20. package/dist/src/commands/_snapshot-upload.js +160 -38
  21. package/dist/src/commands/_spinner.js +65 -0
  22. package/dist/src/commands/_task-picker.js +44 -16
  23. package/dist/src/commands/agent.js +39 -20
  24. package/dist/src/commands/browser.js +28 -21
  25. package/dist/src/commands/connect.js +146 -33
  26. package/dist/src/commands/dist.js +19 -12
  27. package/dist/src/commands/doctor.js +304 -44
  28. package/dist/src/commands/github.js +301 -52
  29. package/dist/src/commands/inbox.js +679 -72
  30. package/dist/src/commands/init.js +622 -118
  31. package/dist/src/commands/inspect.js +515 -32
  32. package/dist/src/commands/inspector.js +20 -13
  33. package/dist/src/commands/pi.js +177 -0
  34. package/dist/src/commands/plugin.js +95 -27
  35. package/dist/src/commands/profile-and-review.js +26 -19
  36. package/dist/src/commands/queue.js +32 -12
  37. package/dist/src/commands/remote.js +43 -36
  38. package/dist/src/commands/repo-git-harness.js +22 -15
  39. package/dist/src/commands/run.js +1162 -158
  40. package/dist/src/commands/server.js +373 -56
  41. package/dist/src/commands/setup.js +316 -62
  42. package/dist/src/commands/stats.js +1030 -0
  43. package/dist/src/commands/task-report-bug.js +29 -22
  44. package/dist/src/commands/task-run-driver.js +862 -129
  45. package/dist/src/commands/task.js +1423 -311
  46. package/dist/src/commands/test.js +15 -8
  47. package/dist/src/commands/workspace.js +18 -11
  48. package/dist/src/commands.js +4446 -1499
  49. package/dist/src/index.js +4502 -1504
  50. package/dist/src/launcher.js +77 -13
  51. package/dist/src/report-bug.js +3 -3
  52. package/dist/src/runner.js +16 -22
  53. package/package.json +10 -5
@@ -3,17 +3,24 @@ var __require = import.meta.require;
3
3
 
4
4
  // packages/cli/src/commands/init.ts
5
5
  import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
6
- import { spawnSync as spawnSync2 } from "child_process";
7
- import { resolve as resolve6 } from "path";
6
+ import { spawnSync } from "child_process";
7
+ import { basename, resolve as resolve6 } from "path";
8
8
 
9
9
  // packages/cli/src/runner.ts
10
10
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
11
- import { CliError } from "@rig/runtime/control-plane/errors";
11
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
12
12
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
13
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
14
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
15
13
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
16
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
14
+
15
+ class CliError extends RuntimeCliError {
16
+ hint;
17
+ constructor(message, exitCode = 1, options = {}) {
18
+ super(message, exitCode);
19
+ if (options.hint?.trim()) {
20
+ this.hint = options.hint.trim();
21
+ }
22
+ }
23
+ }
17
24
  function takeFlag(args, flag) {
18
25
  const rest = [];
19
26
  let value = false;
@@ -34,7 +41,7 @@ function takeOption(args, option) {
34
41
  if (current === option) {
35
42
  const next = args[index + 1];
36
43
  if (!next || next.startsWith("-")) {
37
- throw new CliError(`Missing value for ${option}`);
44
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
38
45
  }
39
46
  value = next;
40
47
  index += 1;
@@ -49,6 +56,7 @@ function takeOption(args, option) {
49
56
 
50
57
  // packages/cli/src/commands/init.ts
51
58
  import { buildRigInitConfigSource } from "@rig/core";
59
+ import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
52
60
 
53
61
  // packages/cli/src/commands/_connection-state.ts
54
62
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -72,7 +80,7 @@ function readJsonFile(path) {
72
80
  try {
73
81
  return JSON.parse(readFileSync(path, "utf8"));
74
82
  } catch (error) {
75
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
83
+ 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>`." });
76
84
  }
77
85
  }
78
86
  function writeJsonFile(path, value) {
@@ -115,7 +123,7 @@ function writeGlobalConnections(state, options = {}) {
115
123
  function upsertGlobalConnection(alias, connection, options = {}) {
116
124
  const cleanAlias = alias.trim();
117
125
  if (!cleanAlias)
118
- throw new CliError2("Connection alias is required.", 1);
126
+ throw new CliError("Connection alias is required.", 1, { hint: "Save a server with `rig server add <alias> <url>`." });
119
127
  const state = readGlobalConnections(options);
120
128
  state.connections[cleanAlias] = connection;
121
129
  writeGlobalConnections(state, options);
@@ -132,7 +140,8 @@ function readRepoConnection(projectRoot) {
132
140
  return {
133
141
  selected,
134
142
  project: typeof record.project === "string" ? record.project : undefined,
135
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
143
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
144
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
136
145
  };
137
146
  }
138
147
  function writeRepoConnection(projectRoot, state) {
@@ -143,27 +152,37 @@ function resolveSelectedConnection(projectRoot, options = {}) {
143
152
  if (!repo)
144
153
  return null;
145
154
  if (repo.selected === "local")
146
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
155
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
147
156
  const global = readGlobalConnections(options);
148
157
  const connection = global.connections[repo.selected];
149
158
  if (!connection) {
150
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
159
+ throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
151
160
  }
152
- return { alias: repo.selected, connection };
161
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
162
+ }
163
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
164
+ const repo = readRepoConnection(projectRoot);
165
+ if (!repo)
166
+ return;
167
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
153
168
  }
154
169
 
155
170
  // packages/cli/src/commands/_server-client.ts
156
- import { spawnSync } from "child_process";
157
171
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
158
172
  import { resolve as resolve2 } from "path";
159
173
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
160
- var cachedGitHubBearerToken;
174
+ var scopedGitHubBearerTokens = new Map;
175
+ var serverPhaseListener = null;
176
+ function reportServerPhase(label) {
177
+ serverPhaseListener?.(label);
178
+ }
161
179
  function cleanToken(value) {
162
180
  const trimmed = value?.trim();
163
181
  return trimmed ? trimmed : null;
164
182
  }
165
- function setGitHubBearerTokenForCurrentProcess(token) {
166
- cachedGitHubBearerToken = cleanToken(token ?? undefined);
183
+ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
184
+ const scopedKey = resolve2(projectRoot ?? process.cwd());
185
+ scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
167
186
  }
168
187
  function readPrivateRemoteSessionToken(projectRoot) {
169
188
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
@@ -177,49 +196,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
177
196
  }
178
197
  }
179
198
  function readGitHubBearerTokenForRemote(projectRoot) {
180
- if (cachedGitHubBearerToken !== undefined)
181
- return cachedGitHubBearerToken;
199
+ const scopedKey = resolve2(projectRoot);
200
+ if (scopedGitHubBearerTokens.has(scopedKey))
201
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
182
202
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
183
- if (privateSession) {
184
- cachedGitHubBearerToken = privateSession;
185
- return cachedGitHubBearerToken;
186
- }
187
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
188
- if (envToken) {
189
- cachedGitHubBearerToken = envToken;
190
- return cachedGitHubBearerToken;
203
+ if (privateSession)
204
+ return privateSession;
205
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
206
+ }
207
+ function readStoredGitHubAuthToken(projectRoot) {
208
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
209
+ if (!existsSync2(path))
210
+ return null;
211
+ try {
212
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
213
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
214
+ } catch {
215
+ return null;
191
216
  }
192
- const result = spawnSync("gh", ["auth", "token"], {
193
- encoding: "utf8",
194
- timeout: 5000,
195
- stdio: ["ignore", "pipe", "ignore"]
196
- });
197
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
198
- return cachedGitHubBearerToken;
217
+ }
218
+ function readLocalConnectionFallbackToken(projectRoot) {
219
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
199
220
  }
200
221
  async function ensureServerForCli(projectRoot) {
201
222
  try {
202
223
  const selected = resolveSelectedConnection(projectRoot);
203
224
  if (selected?.connection.kind === "remote") {
225
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
226
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
227
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
204
228
  return {
205
229
  baseUrl: selected.connection.baseUrl,
206
- authToken: readGitHubBearerTokenForRemote(projectRoot),
207
- connectionKind: "remote"
230
+ authToken,
231
+ connectionKind: "remote",
232
+ serverProjectRoot
208
233
  };
209
234
  }
235
+ reportServerPhase("Starting local Rig server\u2026");
210
236
  const connection = await ensureLocalRigServerConnection(projectRoot);
211
237
  return {
212
238
  baseUrl: connection.baseUrl,
213
- authToken: connection.authToken,
214
- connectionKind: "local"
239
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
240
+ connectionKind: "local",
241
+ serverProjectRoot: resolve2(projectRoot)
215
242
  };
216
243
  } catch (error) {
217
244
  if (error instanceof Error) {
218
- throw new CliError2(error.message, 1);
245
+ throw new CliError(error.message, 1);
219
246
  }
220
247
  throw error;
221
248
  }
222
249
  }
250
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
251
+ const repo = readRepoConnection(projectRoot);
252
+ const slug = repo?.project?.trim();
253
+ if (!slug)
254
+ return null;
255
+ try {
256
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
257
+ headers: mergeHeaders(undefined, authToken)
258
+ });
259
+ if (!response.ok)
260
+ return null;
261
+ const payload = await response.json();
262
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
263
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
264
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
265
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
266
+ if (path)
267
+ writeRepoServerProjectRoot(projectRoot, path);
268
+ return path;
269
+ } catch {
270
+ return null;
271
+ }
272
+ }
223
273
  function mergeHeaders(headers, authToken) {
224
274
  const merged = new Headers(headers);
225
275
  if (authToken) {
@@ -242,12 +292,65 @@ function diagnosticMessage(payload) {
242
292
  });
243
293
  return messages.length > 0 ? messages.join("; ") : null;
244
294
  }
295
+ var serverReachabilityCache = new Map;
296
+ async function probeServerReachability(baseUrl, authToken) {
297
+ try {
298
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
299
+ headers: mergeHeaders(undefined, authToken),
300
+ signal: AbortSignal.timeout(1500)
301
+ });
302
+ return response.ok;
303
+ } catch {
304
+ return false;
305
+ }
306
+ }
307
+ function cachedServerReachability(projectRoot, baseUrl, authToken) {
308
+ const key = resolve2(projectRoot);
309
+ const cached = serverReachabilityCache.get(key);
310
+ if (cached)
311
+ return cached;
312
+ const probe = probeServerReachability(baseUrl, authToken);
313
+ serverReachabilityCache.set(key, probe);
314
+ return probe;
315
+ }
316
+ function describeSelectedServer(projectRoot, server) {
317
+ try {
318
+ const selected = resolveSelectedConnection(projectRoot);
319
+ if (selected) {
320
+ return {
321
+ alias: selected.alias,
322
+ target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
323
+ };
324
+ }
325
+ } catch {}
326
+ return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
327
+ }
328
+ async function buildServerFailureContext(projectRoot, server) {
329
+ const { alias, target } = describeSelectedServer(projectRoot, server);
330
+ const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
331
+ const reachability = reachable ? "server is reachable" : "server is unreachable";
332
+ return {
333
+ contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
334
+ hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
335
+ };
336
+ }
245
337
  async function requestServerJson(context, pathname, init = {}) {
246
338
  const server = await ensureServerForCli(context.projectRoot);
247
- const response = await fetch(`${server.baseUrl}${pathname}`, {
248
- ...init,
249
- headers: mergeHeaders(init.headers, server.authToken)
250
- });
339
+ const headers = mergeHeaders(init.headers, server.authToken);
340
+ if (server.serverProjectRoot)
341
+ headers.set("x-rig-project-root", server.serverProjectRoot);
342
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
343
+ let response;
344
+ try {
345
+ response = await fetch(`${server.baseUrl}${pathname}`, {
346
+ ...init,
347
+ headers
348
+ });
349
+ } catch (error) {
350
+ const failure = await buildServerFailureContext(context.projectRoot, server);
351
+ throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
352
+ ${failure.contextLine}`, 1, { hint: failure.hint });
353
+ }
251
354
  const text = await response.text();
252
355
  const payload = text.trim().length > 0 ? (() => {
253
356
  try {
@@ -259,7 +362,9 @@ async function requestServerJson(context, pathname, init = {}) {
259
362
  if (!response.ok) {
260
363
  const diagnostics = diagnosticMessage(payload);
261
364
  const detail = diagnostics ?? (text || response.statusText);
262
- throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
365
+ const failure = await buildServerFailureContext(context.projectRoot, server);
366
+ throw new CliError(`Rig server request failed (${response.status}): ${detail}
367
+ ${failure.contextLine}`, 1, { hint: failure.hint });
263
368
  }
264
369
  return payload;
265
370
  }
@@ -290,39 +395,75 @@ async function registerProjectViaServer(context, input) {
290
395
  function sleep(ms) {
291
396
  return new Promise((resolve3) => setTimeout(resolve3, ms));
292
397
  }
398
+ function isRetryableProjectRootSwitchError(error) {
399
+ if (!(error instanceof Error))
400
+ return false;
401
+ const message = error.message.toLowerCase();
402
+ return message.includes("rig server request failed (401): auth-required") || message.includes("rig server request failed (401): github-token-required") || message.includes("rig server request failed (502)") || message.includes("rig server request failed (503)") || message.includes("bad gateway") || message.includes("fetch failed") || message.includes("econnrefused") || message.includes("connection refused");
403
+ }
293
404
  async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
294
- const switched = await requestServerJson(context, "/api/server/project-root", {
295
- method: "POST",
296
- headers: { "content-type": "application/json" },
297
- body: JSON.stringify({ projectRoot })
298
- });
299
405
  const timeoutMs = options.timeoutMs ?? 30000;
300
406
  const pollMs = options.pollMs ?? 1000;
301
407
  const deadline = Date.now() + timeoutMs;
302
408
  let lastError;
409
+ let switched = null;
303
410
  while (Date.now() < deadline) {
304
411
  try {
305
- const status = await requestServerJson(context, "/api/server/status");
306
- if (status && typeof status === "object" && !Array.isArray(status)) {
307
- const record = status;
308
- if (record.projectRoot === projectRoot) {
309
- return { ok: true, switched, status: record };
310
- }
311
- lastError = `server projectRoot=${String(record.projectRoot ?? "unknown")}`;
312
- }
412
+ switched = await requestServerJson(context, "/api/server/project-root", {
413
+ method: "POST",
414
+ headers: { "content-type": "application/json" },
415
+ body: JSON.stringify({ projectRoot })
416
+ });
417
+ break;
313
418
  } catch (error) {
314
419
  lastError = error;
420
+ if (!isRetryableProjectRootSwitchError(error))
421
+ throw error;
422
+ await sleep(pollMs);
315
423
  }
316
- await sleep(pollMs);
317
424
  }
318
- throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
425
+ if (!switched) {
426
+ throw new CliError(`Rig server did not accept project-root switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no response")}).`, 1);
427
+ }
428
+ const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
429
+ if (record.ok === true) {
430
+ writeRepoServerProjectRoot(context.projectRoot, projectRoot);
431
+ return { ok: true, switched: record };
432
+ }
433
+ throw new CliError(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
319
434
  }
435
+ async function ensureTaskLabelsViaServer(context) {
436
+ const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
437
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
438
+ }
439
+ async function listGitHubProjectsViaServer(context, owner) {
440
+ const url = new URL("http://rig.local/api/github/projects");
441
+ url.searchParams.set("owner", owner);
442
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
443
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
444
+ }
445
+ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
446
+ const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
447
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
448
+ }
449
+ var RESUMABLE_RUN_STATUSES = new Set([
450
+ "created",
451
+ "preparing",
452
+ "running",
453
+ "validating",
454
+ "reviewing",
455
+ "stopped",
456
+ "failed",
457
+ "needs-attention",
458
+ "needs_attention"
459
+ ]);
320
460
 
321
461
  // packages/cli/src/commands/_pi-install.ts
322
462
  import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
323
463
  import { homedir as homedir2 } from "os";
324
464
  import { resolve as resolve3 } from "path";
325
- var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
465
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
466
+ var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
326
467
  var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
327
468
  export { default } from '@rig/pi-rig';
328
469
  `;
@@ -350,7 +491,7 @@ function resolvePiHomeDir(inputHomeDir) {
350
491
  function piListContainsPiRig(output) {
351
492
  return output.split(/\r?\n/).some((line) => {
352
493
  const normalized = line.trim();
353
- return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
494
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
354
495
  });
355
496
  }
356
497
  async function safeRun(runner, command, options) {
@@ -466,7 +607,7 @@ async function ensureRemotePiRigInstalled(input) {
466
607
  const payload = await input.requestJson("/api/pi-rig/install", {
467
608
  method: "POST",
468
609
  headers: { "content-type": "application/json" },
469
- body: JSON.stringify({ package: "@rig/pi-rig", scope: "global" })
610
+ body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
470
611
  });
471
612
  const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
472
613
  const piOk = record.piOk === true || record.ok === true;
@@ -719,7 +860,10 @@ async function runRigDoctorChecks(options) {
719
860
  const bunVersion = options.bunVersion ?? Bun.version;
720
861
  const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
721
862
  const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
863
+ const progress = options.onProgress ?? (() => {});
864
+ progress("Checking local toolchain\u2026");
722
865
  checks.push(check("bun", `bun >= ${MIN_SUPPORTED_BUN_VERSION}`, isSupportedBunVersion(bunVersion) ? "pass" : "fail", `found ${bunVersion}`, `Install Bun ${MIN_SUPPORTED_BUN_VERSION} or newer.`), check("git", "git", which("git") ? "pass" : "fail", which("git") ?? undefined, "Install git and ensure it is on PATH."), check("jq", "jq", which("jq") ? "pass" : "warn", which("jq") ?? undefined, "Install jq (for example `brew install jq`)."));
866
+ progress("Loading rig.config\u2026");
723
867
  const loadedConfig = await loadConfig(projectRoot).catch(() => null);
724
868
  const config = loadedConfig ?? loadFallbackConfig(projectRoot);
725
869
  const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
@@ -727,7 +871,7 @@ async function runRigDoctorChecks(options) {
727
871
  const taskSourceKind = config?.taskSource?.kind;
728
872
  checks.push(taskSourceKind ? check("task-source", "task source configured", "pass", taskSourceKind) : check("task-source", "task source configured", "fail", "missing taskSource", "Configure taskSource in rig.config.ts."));
729
873
  const repo = readRepoConnection(projectRoot);
730
- checks.push(repo ? check("project-link", "repo selected Rig connection", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to link this checkout to a GitHub repo slug.") : check("project-link", "repo selected Rig connection", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig connect use <alias|local>`."));
874
+ checks.push(repo ? check("project-link", "repo selected Rig server", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to link this checkout to a GitHub repo slug.") : check("project-link", "repo selected Rig server", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
731
875
  const selected = (() => {
732
876
  try {
733
877
  return resolveSelectedConnection(projectRoot);
@@ -735,9 +879,10 @@ async function runRigDoctorChecks(options) {
735
879
  return null;
736
880
  }
737
881
  })();
738
- checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server connection", repo ? "fail" : "warn", repo ? "selected alias is missing" : "will auto-start local server", repo ? "Run `rig connect list` and `rig connect use <alias|local>`." : undefined));
882
+ checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server", repo ? "fail" : "warn", repo ? "selected alias is missing" : "will auto-start local server", repo ? "Run `rig server list` and `rig server use <alias|local>`." : undefined));
739
883
  let server = null;
740
884
  try {
885
+ progress("Connecting to the selected Rig server\u2026");
741
886
  server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
742
887
  checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
743
888
  } catch (error) {
@@ -745,18 +890,21 @@ async function runRigDoctorChecks(options) {
745
890
  }
746
891
  if (server || options.requestJson) {
747
892
  try {
893
+ progress("Checking server status\u2026");
748
894
  const status = await request("/api/server/status");
749
895
  checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
750
896
  } catch (error) {
751
897
  checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
752
898
  }
753
899
  try {
900
+ progress("Checking GitHub auth\u2026");
754
901
  const auth = await request("/api/github/auth/status");
755
902
  checks.push(isAuthenticated(auth) ? check("github-auth", "GitHub auth", "pass") : check("github-auth", "GitHub auth", "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
756
903
  } catch (error) {
757
904
  checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
758
905
  }
759
906
  try {
907
+ progress("Checking GitHub repo permissions\u2026");
760
908
  const permissions = await request("/api/github/repo/permissions");
761
909
  const allowed = permissionAllowsPr(permissions);
762
910
  checks.push(allowed === true ? check("github-repo-permissions", "GitHub repo PR permissions", "pass", JSON.stringify(permissions).slice(0, 180)) : allowed === false ? check("github-repo-permissions", "GitHub repo PR permissions", "fail", JSON.stringify(permissions).slice(0, 180), "Grant the selected GitHub token permission to push branches, open PRs, and merge according to repo rules.") : check("github-repo-permissions", "GitHub repo PR permissions", "warn", JSON.stringify(permissions).slice(0, 180), "Confirm the selected token can push branches and open PRs."));
@@ -764,6 +912,7 @@ async function runRigDoctorChecks(options) {
764
912
  checks.push(check("github-repo-permissions", "GitHub repo PR permissions", "warn", errorMessage(error), "Ensure the server exposes repo permission checks and the token can open PRs."));
765
913
  }
766
914
  try {
915
+ progress("Checking GitHub issue labels\u2026");
767
916
  const labels = await request("/api/workspace/task-labels");
768
917
  const ready = labelsReady(labels);
769
918
  checks.push(ready === false ? check("task-labels", "GitHub issue labels", "fail", JSON.stringify(labels).slice(0, 180), "Let Rig create required labels or create the configured lifecycle labels manually.") : check("task-labels", "GitHub issue labels", ready === true ? "pass" : "warn", JSON.stringify(labels).slice(0, 180), "Confirm required Rig lifecycle labels exist."));
@@ -771,6 +920,7 @@ async function runRigDoctorChecks(options) {
771
920
  checks.push(check("task-labels", "GitHub issue labels", "warn", errorMessage(error), "Run `rig init`/`rig doctor` after label setup is wired on the server."));
772
921
  }
773
922
  try {
923
+ progress("Checking task projection\u2026");
774
924
  const projection = await request("/api/workspace/task-projection");
775
925
  checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
776
926
  } catch (error) {
@@ -779,6 +929,7 @@ async function runRigDoctorChecks(options) {
779
929
  const slug = projectStatusSlug(projectRoot, config);
780
930
  if (slug) {
781
931
  try {
932
+ progress("Checking server project checkout\u2026");
782
933
  const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
783
934
  checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
784
935
  } catch (error) {
@@ -793,6 +944,7 @@ async function runRigDoctorChecks(options) {
793
944
  }
794
945
  checks.push(githubProjectsCheck(config));
795
946
  checks.push(prMergeCheck(config));
947
+ progress("Checking Pi installation\u2026");
796
948
  const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
797
949
  ok: false,
798
950
  label: "pi/pi-rig checks",
@@ -809,6 +961,7 @@ function countDoctorFailures(checks) {
809
961
 
810
962
  // packages/cli/src/commands/init.ts
811
963
  var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
964
+ var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
812
965
  var RIG_CONFIG_DEV_DEPENDENCIES = {
813
966
  "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
814
967
  "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
@@ -819,7 +972,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
819
972
  return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
820
973
  }
821
974
  function detectOriginRepoSlug(projectRoot) {
822
- const result = spawnSync2("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
975
+ const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
823
976
  if (result.status !== 0)
824
977
  return null;
825
978
  return parseRepoSlugFromRemote(result.stdout.trim());
@@ -827,7 +980,7 @@ function detectOriginRepoSlug(projectRoot) {
827
980
  function parseRepoSlug(value) {
828
981
  const match = value.trim().match(/^([^/\s]+)\/([^/\s]+)$/);
829
982
  if (!match)
830
- throw new CliError2(`Invalid GitHub repo slug: ${value}. Expected owner/repo.`, 1);
983
+ throw new CliError(`Invalid GitHub repo slug: ${value}. Expected owner/repo.`, 1, { hint: "Pass the slug as owner/repo, e.g. `rig init --repo acme/widgets`." });
831
984
  return { owner: match[1], repo: match[2], slug: `${match[1]}/${match[2]}` };
832
985
  }
833
986
  function ensureRigPrivateDirs(projectRoot) {
@@ -875,11 +1028,14 @@ function applyGitHubProjectConfig(source, options) {
875
1028
  return source;
876
1029
  const projectId = JSON.stringify(options.githubProject);
877
1030
  const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
1031
+ const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
1032
+ statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
1033
+ `)},` : "";
878
1034
  return source.replace(` projects: { enabled: false },`, [
879
1035
  ` projects: {`,
880
1036
  ` enabled: true,`,
881
1037
  ` projectId: ${projectId},`,
882
- ` statusFieldId: ${statusFieldId},`,
1038
+ ` statusFieldId: ${statusFieldId},${statuses}`,
883
1039
  ` },`
884
1040
  ].join(`
885
1041
  `));
@@ -900,40 +1056,218 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
900
1056
  }
901
1057
  }
902
1058
  function detectGhLogin() {
903
- const result = spawnSync2("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
1059
+ const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
904
1060
  return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
905
1061
  }
906
1062
  function readGhAuthToken() {
907
- const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
1063
+ const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
908
1064
  if (result.status !== 0 || !result.stdout.trim()) {
909
- throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
1065
+ throw new CliError(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1, { hint: "Sign in with `gh auth login`, or pass a token directly: `rig init --github-auth token --github-token <token>`." });
910
1066
  }
911
1067
  return result.stdout.trim();
912
1068
  }
1069
+ function refreshGhProjectScopesAndReadToken() {
1070
+ const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
1071
+ encoding: "utf8",
1072
+ stdio: ["inherit", "pipe", "pipe"]
1073
+ });
1074
+ if (result.status !== 0)
1075
+ return null;
1076
+ try {
1077
+ return readGhAuthToken();
1078
+ } catch {
1079
+ return null;
1080
+ }
1081
+ }
913
1082
  async function loadClackPrompts() {
914
1083
  return await import("@clack/prompts");
915
1084
  }
1085
+ function clackTextOptions(options) {
1086
+ return {
1087
+ message: options.message,
1088
+ ...options.placeholder ? { placeholder: options.placeholder } : {},
1089
+ ...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
1090
+ };
1091
+ }
916
1092
  async function promptRequiredText(prompts, options) {
917
- const value = await prompts.text(options);
1093
+ const value = await prompts.text(clackTextOptions(options));
918
1094
  if (prompts.isCancel(value))
919
- throw new CliError2("Init cancelled.", 1);
1095
+ throw new CliError("Init cancelled.", 1);
920
1096
  const text = String(value ?? "").trim();
921
1097
  if (!text)
922
- throw new CliError2(`${options.message} is required.`, 1);
1098
+ throw new CliError(`${options.message} is required.`, 1);
923
1099
  return text;
924
1100
  }
925
1101
  async function promptOptionalText(prompts, options) {
926
- const value = await prompts.text(options);
1102
+ const value = await prompts.text(clackTextOptions(options));
927
1103
  if (prompts.isCancel(value))
928
- throw new CliError2("Init cancelled.", 1);
1104
+ throw new CliError("Init cancelled.", 1);
929
1105
  return String(value ?? "").trim();
930
1106
  }
931
1107
  async function promptSelect(prompts, options) {
932
1108
  const value = await prompts.select(options);
933
1109
  if (prompts.isCancel(value))
934
- throw new CliError2("Init cancelled.", 1);
1110
+ throw new CliError("Init cancelled.", 1);
935
1111
  return String(value);
936
1112
  }
1113
+ function repoOwnerFromSlug(repoSlug) {
1114
+ return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
1115
+ }
1116
+ function recordArray(value, key) {
1117
+ if (!value || typeof value !== "object" || Array.isArray(value))
1118
+ return [];
1119
+ const raw = value[key];
1120
+ return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
1121
+ }
1122
+ async function listGitHubProjectsForInit(context, owner, token) {
1123
+ if (token?.trim()) {
1124
+ try {
1125
+ return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
1126
+ } catch (directError) {
1127
+ const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
1128
+ if (recordArray(serverPayload, "projects").length > 0)
1129
+ return serverPayload;
1130
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
1131
+ }
1132
+ }
1133
+ return listGitHubProjectsViaServer(context, owner);
1134
+ }
1135
+ async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
1136
+ if (token?.trim()) {
1137
+ try {
1138
+ return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
1139
+ } catch (directError) {
1140
+ const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
1141
+ if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
1142
+ return serverPayload;
1143
+ }
1144
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
1145
+ }
1146
+ }
1147
+ return getGitHubProjectStatusFieldViaServer(context, projectId);
1148
+ }
1149
+ var PROJECT_STATUS_PROMPTS = {
1150
+ running: "Running/In progress",
1151
+ prOpen: "PR open/review",
1152
+ ciFixing: "CI/review fixing",
1153
+ merging: "Merging",
1154
+ done: "Done",
1155
+ needsAttention: "Needs attention"
1156
+ };
1157
+ var DEFAULT_PROJECT_STATUS_OPTIONS = {
1158
+ running: "In Progress",
1159
+ prOpen: "In Review",
1160
+ ciFixing: "In Review",
1161
+ merging: "Merging",
1162
+ done: "Done",
1163
+ needsAttention: "Needs Attention"
1164
+ };
1165
+ async function promptManualProjectStatusMapping(prompts) {
1166
+ const statuses = {};
1167
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
1168
+ const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
1169
+ const value = await promptOptionalText(prompts, {
1170
+ message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
1171
+ placeholder: defaultLabel
1172
+ });
1173
+ statuses[key] = value || defaultLabel;
1174
+ }
1175
+ return statuses;
1176
+ }
1177
+ function projectScopeError(value) {
1178
+ const text = typeof value === "string" ? value : JSON.stringify(value ?? "");
1179
+ return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text);
1180
+ }
1181
+ function optionName(option) {
1182
+ return String(option.name ?? option.label ?? option.id ?? "").trim();
1183
+ }
1184
+ function autoProjectStatusValue(options, key, label) {
1185
+ const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
1186
+ const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
1187
+ if (!match)
1188
+ return null;
1189
+ return String(match.id ?? match.name);
1190
+ }
1191
+ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
1192
+ const projectChoice = await promptSelect(prompts, {
1193
+ message: "GitHub Projects status sync",
1194
+ initialValue: "select",
1195
+ options: [
1196
+ { value: "select", label: "Select accessible ProjectV2" },
1197
+ { value: "off", label: "Off" },
1198
+ { value: "manual", label: "Enter ProjectV2 ids manually" }
1199
+ ]
1200
+ });
1201
+ if (projectChoice === "off")
1202
+ return { githubProject: "off" };
1203
+ if (projectChoice === "manual") {
1204
+ return {
1205
+ githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
1206
+ githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
1207
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
1208
+ };
1209
+ }
1210
+ const owner = repoOwnerFromSlug(repoSlug);
1211
+ if (!owner)
1212
+ throw new CliError(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1, { hint: "Pass the slug as owner/repo, e.g. `rig init --repo acme/widgets`." });
1213
+ let activeToken = githubToken?.trim() || null;
1214
+ let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
1215
+ let projects = recordArray(projectsPayload, "projects");
1216
+ if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
1217
+ prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
1218
+ const refreshedToken = refreshProjectToken();
1219
+ if (refreshedToken) {
1220
+ activeToken = refreshedToken;
1221
+ projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
1222
+ projects = recordArray(projectsPayload, "projects");
1223
+ }
1224
+ }
1225
+ if (projects.length === 0) {
1226
+ const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
1227
+ prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
1228
+ return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
1229
+ }
1230
+ const selectedProjectId = await promptSelect(prompts, {
1231
+ message: "GitHub ProjectV2 project",
1232
+ options: [
1233
+ ...projects.map((project) => ({
1234
+ value: String(project.id),
1235
+ label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
1236
+ hint: typeof project.url === "string" ? project.url : undefined
1237
+ })),
1238
+ { value: "manual", label: "Enter ProjectV2 id manually" }
1239
+ ]
1240
+ });
1241
+ const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
1242
+ const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
1243
+ const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
1244
+ const rawField = fieldPayloadRecord.field;
1245
+ const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
1246
+ const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
1247
+ const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
1248
+ if (options.length === 0) {
1249
+ return {
1250
+ githubProject: projectId,
1251
+ githubProjectStatusField: fieldId,
1252
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
1253
+ ...activeToken ? { githubToken: activeToken } : {}
1254
+ };
1255
+ }
1256
+ const statuses = {};
1257
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
1258
+ const auto = autoProjectStatusValue(options, key, label);
1259
+ statuses[key] = auto ?? await promptSelect(prompts, {
1260
+ message: `Project status option for ${label}`,
1261
+ options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
1262
+ });
1263
+ }
1264
+ return {
1265
+ githubProject: projectId,
1266
+ githubProjectStatusField: fieldId,
1267
+ githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
1268
+ ...activeToken ? { githubToken: activeToken } : {}
1269
+ };
1270
+ }
937
1271
  function sleep2(ms) {
938
1272
  return new Promise((resolve7) => setTimeout(resolve7, ms));
939
1273
  }
@@ -947,12 +1281,29 @@ function apiSessionTokenFrom(payload) {
947
1281
  const token = payload.apiSessionToken;
948
1282
  return typeof token === "string" && token.trim() ? token.trim() : null;
949
1283
  }
1284
+ function cleanPayloadString(value) {
1285
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1286
+ }
1287
+ function remoteGitHubAuthMetadata(payload) {
1288
+ if (!payload)
1289
+ return {};
1290
+ const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
1291
+ return {
1292
+ ...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
1293
+ ...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
1294
+ ...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
1295
+ ...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
1296
+ ...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
1297
+ ...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
1298
+ };
1299
+ }
950
1300
  function writeRemoteGitHubAuthState(projectRoot, input) {
951
1301
  writeFileSync2(resolve6(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
952
1302
  authenticated: true,
953
1303
  source: input.source,
954
1304
  storedOnServer: true,
955
1305
  selectedRepo: input.selectedRepo,
1306
+ ...remoteGitHubAuthMetadata(input.authPayload ?? null),
956
1307
  ...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
957
1308
  updatedAt: new Date().toISOString()
958
1309
  }, null, 2)}
@@ -983,18 +1334,149 @@ async function pollDeviceAuthUntilComplete(context, pollId, firstPayload) {
983
1334
  } while (Date.now() < deadline);
984
1335
  return last;
985
1336
  }
1337
+ function runLocalFilesInit(context, options) {
1338
+ const projectRoot = context.projectRoot;
1339
+ ensureRigPrivateDirs(projectRoot);
1340
+ ensureGitignoreEntries(projectRoot);
1341
+ writeRepoConnection(projectRoot, { selected: "local", linkedAt: new Date().toISOString() });
1342
+ const configTsPath = resolve6(projectRoot, "rig.config.ts");
1343
+ const configExists = existsSync5(configTsPath) || existsSync5(resolve6(projectRoot, "rig.config.json"));
1344
+ if (configExists && !options.repair) {
1345
+ if (context.outputMode !== "json")
1346
+ console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
1347
+ } else {
1348
+ const projectName = basename(projectRoot) || "rig-project";
1349
+ writeFileSync2(configTsPath, buildRigInitConfigSource({
1350
+ projectName,
1351
+ taskSource: { kind: "files", path: "tasks" },
1352
+ useStandardPlugin: true
1353
+ }), "utf-8");
1354
+ }
1355
+ ensureRigConfigPackageDependencies(projectRoot);
1356
+ const tasksDir = resolve6(projectRoot, "tasks");
1357
+ if (!existsSync5(tasksDir)) {
1358
+ mkdirSync2(tasksDir, { recursive: true });
1359
+ writeFileSync2(resolve6(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)}
1360
+ `, "utf-8");
1361
+ }
1362
+ if (context.outputMode !== "json") {
1363
+ console.log("Initialized a local files-source Rig project (no GitHub).");
1364
+ console.log(" tasks live in tasks/*.json \xB7 server: local");
1365
+ console.log("Next: `rig task list`, then `rig task run T-1`.");
1366
+ console.log("To wire GitHub later: `rig init --repair --repo owner/repo --github-auth gh`.");
1367
+ }
1368
+ return { ok: true, group: "init", command: "init", details: { mode: "local-files", projectRoot, taskSourcePath: "tasks" } };
1369
+ }
1370
+ var DEMO_TASKS_RELATIVE_DIR = ".rig/demo-tasks";
1371
+ var DEMO_TASKS = [
1372
+ {
1373
+ id: "demo-1",
1374
+ title: "Add a hello CLI script",
1375
+ body: [
1376
+ "Create `scripts/hello.ts` that prints `Hello from Rig!` plus the current date,",
1377
+ "and add a `hello` script entry to package.json that runs it with bun.",
1378
+ "Keep it dependency-free."
1379
+ ].join(`
1380
+ `),
1381
+ status: "ready",
1382
+ labels: ["demo"]
1383
+ },
1384
+ {
1385
+ id: "demo-2",
1386
+ title: "Write a README section about this project",
1387
+ body: [
1388
+ "Add (or extend) README.md with a short `## What this is` section:",
1389
+ "two or three sentences describing the repository and how to run it.",
1390
+ "Plain prose, no badges."
1391
+ ].join(`
1392
+ `),
1393
+ status: "ready",
1394
+ labels: ["demo"]
1395
+ },
1396
+ {
1397
+ id: "demo-3",
1398
+ title: "Add a unit test for the hello script",
1399
+ body: [
1400
+ "Add `scripts/hello.test.ts` with a bun test that imports the greeting",
1401
+ "helper from the hello script and asserts it contains `Hello from Rig!`.",
1402
+ "Refactor the script to export that helper if needed."
1403
+ ].join(`
1404
+ `),
1405
+ status: "ready",
1406
+ labels: ["demo"]
1407
+ }
1408
+ ];
1409
+ function runDemoInit(context, options) {
1410
+ const projectRoot = context.projectRoot;
1411
+ ensureRigPrivateDirs(projectRoot);
1412
+ ensureGitignoreEntries(projectRoot);
1413
+ writeRepoConnection(projectRoot, { selected: "local", linkedAt: new Date().toISOString() });
1414
+ const configTsPath = resolve6(projectRoot, "rig.config.ts");
1415
+ const configExists = existsSync5(configTsPath) || existsSync5(resolve6(projectRoot, "rig.config.json"));
1416
+ let configWritten = false;
1417
+ if (configExists && !options.repair) {
1418
+ if (context.outputMode !== "json") {
1419
+ console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
1420
+ }
1421
+ } else {
1422
+ writeFileSync2(configTsPath, buildRigInitConfigSource({
1423
+ projectName: basename(projectRoot) || "rig-demo",
1424
+ taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
1425
+ useStandardPlugin: true
1426
+ }), "utf-8");
1427
+ configWritten = true;
1428
+ }
1429
+ ensureRigConfigPackageDependencies(projectRoot);
1430
+ const demoTasksDir = resolve6(projectRoot, DEMO_TASKS_RELATIVE_DIR);
1431
+ mkdirSync2(demoTasksDir, { recursive: true });
1432
+ const taskIds = [];
1433
+ for (const task of DEMO_TASKS) {
1434
+ const id = String(task.id);
1435
+ taskIds.push(id);
1436
+ const taskPath = resolve6(demoTasksDir, `${id}.json`);
1437
+ if (!existsSync5(taskPath)) {
1438
+ writeFileSync2(taskPath, `${JSON.stringify(task, null, 2)}
1439
+ `, "utf-8");
1440
+ }
1441
+ }
1442
+ if (context.outputMode !== "json") {
1443
+ console.log(`Demo Rig project ready (offline, no GitHub).`);
1444
+ console.log(` config: rig.config.ts (files task source -> ${DEMO_TASKS_RELATIVE_DIR}/)`);
1445
+ console.log(` tasks: ${taskIds.join(", ")}`);
1446
+ console.log("Next steps:");
1447
+ console.log(" 1. rig task list");
1448
+ console.log(" 2. rig task run --next");
1449
+ }
1450
+ return {
1451
+ ok: true,
1452
+ group: "init",
1453
+ command: "init",
1454
+ details: {
1455
+ mode: "demo",
1456
+ projectRoot,
1457
+ taskSourcePath: DEMO_TASKS_RELATIVE_DIR,
1458
+ demoTasksDir,
1459
+ tasks: taskIds,
1460
+ configWritten
1461
+ }
1462
+ };
1463
+ }
986
1464
  async function runControlPlaneInit(context, options) {
987
1465
  const projectRoot = context.projectRoot;
988
1466
  const detectedSlug = options.repoSlug ?? detectOriginRepoSlug(projectRoot);
989
1467
  if (!detectedSlug) {
990
- throw new CliError2("Could not detect GitHub repo slug from origin. Pass --repo owner/repo.", 1);
1468
+ const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
1469
+ if ((options.server ?? "local") === "local" && authMethod2 === "skip") {
1470
+ return runLocalFilesInit(context, options);
1471
+ }
1472
+ 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);
991
1473
  }
992
1474
  const repo = parseRepoSlug(detectedSlug);
993
1475
  const serverKind = options.server ?? "local";
994
1476
  const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : "remote");
995
1477
  if (serverKind === "remote") {
996
1478
  if (!options.remoteUrl)
997
- throw new CliError2("Missing --remote-url for --server remote.", 1);
1479
+ throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`." });
998
1480
  upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: options.remoteUrl });
999
1481
  }
1000
1482
  writeRepoConnection(projectRoot, {
@@ -1039,18 +1521,19 @@ async function runControlPlaneInit(context, options) {
1039
1521
  const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
1040
1522
  const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${options.remoteUrl ?? "the remote Rig server"}.` : null;
1041
1523
  if (remoteGhTokenWarning && !options.yes) {
1042
- throw new CliError2(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
1524
+ throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
1043
1525
  }
1044
1526
  const token = authMethod === "gh" && !options.githubToken ? readGhAuthToken() : options.githubToken?.trim();
1045
1527
  if (token) {
1046
1528
  githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
1047
1529
  const apiSessionToken = apiSessionTokenFrom(githubAuth);
1048
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
1530
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
1049
1531
  if (serverKind === "remote") {
1050
1532
  writeRemoteGitHubAuthState(projectRoot, {
1051
1533
  source: authMethod === "gh" ? "gh" : "init-token",
1052
1534
  selectedRepo: repo.slug,
1053
- apiSessionToken
1535
+ apiSessionToken,
1536
+ authPayload: githubAuth
1054
1537
  });
1055
1538
  }
1056
1539
  } else if (authMethod === "device") {
@@ -1069,9 +1552,9 @@ async function runControlPlaneInit(context, options) {
1069
1552
  if (completed) {
1070
1553
  const apiSessionToken = apiSessionTokenFrom(completed);
1071
1554
  if (apiSessionToken) {
1072
- setGitHubBearerTokenForCurrentProcess(apiSessionToken);
1555
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
1073
1556
  if (serverKind === "remote") {
1074
- writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
1557
+ writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
1075
1558
  }
1076
1559
  }
1077
1560
  deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
@@ -1089,19 +1572,25 @@ async function runControlPlaneInit(context, options) {
1089
1572
  Object.assign(checkout, preparedCheckout);
1090
1573
  }
1091
1574
  }
1575
+ const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
1576
+ if (serverKind === "remote" && checkoutPath && token) {
1577
+ githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
1578
+ const apiSessionToken = apiSessionTokenFrom(githubAuth);
1579
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
1580
+ writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
1581
+ }
1092
1582
  const registered = await registerProjectViaServer(context, {
1093
1583
  repoSlug: repo.slug,
1094
1584
  checkout
1095
1585
  });
1096
- const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
1097
1586
  const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
1098
- if (serverRootSwitch && token) {
1099
- githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath ?? undefined });
1100
- const apiSessionToken = apiSessionTokenFrom(githubAuth);
1101
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
1102
- writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken });
1103
- }
1104
1587
  const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
1588
+ const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
1589
+ ok: false,
1590
+ ready: false,
1591
+ labelsReady: false,
1592
+ error: error instanceof Error ? error.message : String(error)
1593
+ }));
1105
1594
  const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
1106
1595
  remote: true,
1107
1596
  pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
@@ -1132,6 +1621,7 @@ async function runControlPlaneInit(context, options) {
1132
1621
  githubAuth,
1133
1622
  deviceAuth,
1134
1623
  githubAuthWarning: remoteGhTokenWarning,
1624
+ labelSetup,
1135
1625
  pi,
1136
1626
  doctor
1137
1627
  };
@@ -1143,6 +1633,8 @@ async function runControlPlaneInit(context, options) {
1143
1633
  }
1144
1634
  function parseInitOptions(args) {
1145
1635
  let rest = [...args];
1636
+ const demo = takeFlag(rest, "--demo");
1637
+ rest = demo.rest;
1146
1638
  const yes = takeFlag(rest, "--yes");
1147
1639
  rest = yes.rest;
1148
1640
  const repair = takeFlag(rest, "--repair");
@@ -1172,6 +1664,7 @@ function parseInitOptions(args) {
1172
1664
  const ref = takeOption(rest, "--ref");
1173
1665
  rest = ref.rest;
1174
1666
  const options = {
1667
+ demo: demo.value,
1175
1668
  yes: yes.value,
1176
1669
  repair: repair.value,
1177
1670
  privateStateOnly: privateStateOnly.value,
@@ -1185,10 +1678,10 @@ function parseInitOptions(args) {
1185
1678
  githubProjectStatusField: githubProjectStatusField.value
1186
1679
  };
1187
1680
  if (server.value && options.server === undefined) {
1188
- throw new CliError2("--server must be local or remote.", 1);
1681
+ throw new CliError("--server must be local or remote.", 1);
1189
1682
  }
1190
1683
  if (githubAuth.value && !["gh", "token", "device", "skip"].includes(githubAuth.value)) {
1191
- throw new CliError2("--github-auth must be gh, token, device, or skip.", 1);
1684
+ throw new CliError("--github-auth must be gh, token, device, or skip.", 1);
1192
1685
  }
1193
1686
  if (remoteCheckout.value) {
1194
1687
  if (remoteCheckout.value === "managed-clone")
@@ -1199,10 +1692,10 @@ function parseInitOptions(args) {
1199
1692
  options.remoteCheckout = { kind: "uploaded-snapshot" };
1200
1693
  else if (remoteCheckout.value === "existing-path") {
1201
1694
  if (!existingPath.value)
1202
- throw new CliError2("--remote-checkout existing-path requires --existing-path <path>.", 1);
1695
+ throw new CliError("--remote-checkout existing-path requires --existing-path <path>.", 1);
1203
1696
  options.remoteCheckout = { kind: "existing-path", path: existingPath.value };
1204
1697
  } else {
1205
- throw new CliError2("--remote-checkout must be managed-clone, current-ref, uploaded-snapshot, or existing-path.", 1);
1698
+ throw new CliError("--remote-checkout must be managed-clone, current-ref, uploaded-snapshot, or existing-path.", 1);
1206
1699
  }
1207
1700
  }
1208
1701
  return { options, rest };
@@ -1238,12 +1731,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
1238
1731
  });
1239
1732
  const serverChoice = await promptSelect(prompts, {
1240
1733
  message: "Rig server",
1734
+ initialValue: "remote",
1241
1735
  options: [
1242
- { value: "local", label: "Local server", hint: "run on this machine" },
1243
- { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" }
1736
+ { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
1737
+ { value: "local", label: "Local server", hint: "run on this machine" }
1244
1738
  ]
1245
1739
  });
1246
- const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: "https://rig.example.com" }) : undefined;
1740
+ const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
1247
1741
  let remoteCheckout;
1248
1742
  if (serverChoice === "remote") {
1249
1743
  const checkout = await promptSelect(prompts, {
@@ -1275,38 +1769,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
1275
1769
  { value: "skip", label: "Skip for now" }
1276
1770
  ]
1277
1771
  });
1772
+ let remoteGhTokenConfirmed = false;
1278
1773
  if (serverChoice === "remote" && authMethod === "gh") {
1279
1774
  if (!prompts.confirm)
1280
- throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
1775
+ throw new CliError("Remote gh-token import requires explicit confirmation.", 1);
1281
1776
  const confirmed = await prompts.confirm({
1282
1777
  message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
1283
- initialValue: false
1778
+ initialValue: true
1284
1779
  });
1285
1780
  if (prompts.isCancel(confirmed) || confirmed !== true) {
1286
- throw new CliError2("Remote gh-token import cancelled.", 1);
1781
+ throw new CliError("Remote gh-token import cancelled.", 1);
1287
1782
  }
1783
+ remoteGhTokenConfirmed = true;
1288
1784
  }
1289
- const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
1290
- const projectChoice = await promptSelect(prompts, {
1291
- message: "GitHub Projects status sync",
1292
- options: [
1293
- { value: "off", label: "Off" },
1294
- { value: "configure", label: "Configure ProjectV2 status field" }
1295
- ]
1296
- });
1297
- const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
1298
- const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
1785
+ const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
1786
+ const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
1787
+ const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
1299
1788
  const result = await runControlPlaneInit(context, {
1300
1789
  server: serverChoice,
1301
1790
  remoteUrl,
1302
1791
  repoSlug,
1303
- githubToken,
1792
+ githubToken: effectiveGithubToken,
1304
1793
  githubAuthMethod: authMethod,
1305
- githubProject,
1306
- githubProjectStatusField,
1794
+ githubProject: projectConfig.githubProject,
1795
+ githubProjectStatusField: projectConfig.githubProjectStatusField,
1796
+ githubProjectStatuses: projectConfig.githubProjectStatuses,
1307
1797
  remoteCheckout,
1308
1798
  repair,
1309
- privateStateOnly
1799
+ privateStateOnly,
1800
+ yes: remoteGhTokenConfirmed || undefined
1310
1801
  });
1311
1802
  const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
1312
1803
  const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
@@ -1316,19 +1807,32 @@ async function runInteractiveControlPlaneInit(context, prompts) {
1316
1807
  }
1317
1808
  async function executeInit(context, args) {
1318
1809
  const parsed = parseInitOptions(args);
1810
+ if (parsed.options.demo) {
1811
+ if (parsed.rest.length > 0) {
1812
+ throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
1813
+ Usage: rig init --demo [--yes] [--repair]`, 1, { hint: "Run `rig init --demo` (optionally with --repair to rewrite an existing rig.config)." });
1814
+ }
1815
+ return runDemoInit(context, parsed.options);
1816
+ }
1319
1817
  if (parsed.options.yes || parsed.options.server || parsed.options.repoSlug || parsed.options.githubToken || parsed.options.privateStateOnly || parsed.options.repair || parsed.options.githubAuthMethod || parsed.options.remoteCheckout) {
1320
1818
  if (parsed.rest.length > 0)
1321
- throw new CliError2(`Unexpected arguments: ${parsed.rest.join(" ")}
1322
- Usage: rig init [--server local|remote] [--remote-url <url>] [--repo owner/repo] [--github-auth gh|token|device|skip] [--github-token <token>] [--github-project off|<project-id>] [--remote-checkout managed-clone|current-ref|uploaded-snapshot|existing-path] [--yes]`, 1);
1819
+ throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
1820
+ Usage: rig init [--demo] [--server local|remote] [--remote-url <url>] [--repo owner/repo] [--github-auth gh|token|device|skip] [--github-token <token>] [--github-project off|<project-id>] [--remote-checkout managed-clone|current-ref|uploaded-snapshot|existing-path] [--yes]`, 1);
1323
1821
  return runControlPlaneInit(context, parsed.options);
1324
1822
  }
1325
1823
  if (parsed.rest.length > 0)
1326
- throw new CliError2(`Unexpected arguments: ${parsed.rest.join(" ")}
1824
+ throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
1327
1825
  Usage: rig init`, 1);
1826
+ if (!process.stdin.isTTY) {
1827
+ throw new CliError("rig init is interactive and needs a terminal. For scripts, pass flags: rig init --yes --server local --github-auth skip [--repo owner/repo].", 1);
1828
+ }
1328
1829
  return runInteractiveControlPlaneInit(context, await loadClackPrompts());
1329
1830
  }
1330
1831
  export {
1331
1832
  runInteractiveControlPlaneInit,
1833
+ runDemoInit,
1332
1834
  executeInit,
1333
- buildRigInitConfigSource
1835
+ buildRigInitConfigSource,
1836
+ DEMO_TASKS_RELATIVE_DIR,
1837
+ DEMO_TASKS
1334
1838
  };