@h-rig/cli 0.0.6-alpha.64 → 0.0.6-alpha.66

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 (46) hide show
  1. package/dist/bin/rig.js +1349 -1599
  2. package/dist/src/commands/_connection-state.js +14 -5
  3. package/dist/src/commands/_doctor-checks.js +71 -11
  4. package/dist/src/commands/_help-catalog.js +99 -60
  5. package/dist/src/commands/_json-output.js +56 -0
  6. package/dist/src/commands/_operator-view.js +97 -784
  7. package/dist/src/commands/_parsers.js +18 -9
  8. package/dist/src/commands/_pi-frontend.js +96 -788
  9. package/dist/src/commands/_policy.js +12 -3
  10. package/dist/src/commands/_preflight.js +73 -13
  11. package/dist/src/commands/_run-driver-helpers.js +31 -5
  12. package/dist/src/commands/_run-replay.js +2 -2
  13. package/dist/src/commands/_server-client.js +76 -16
  14. package/dist/src/commands/_snapshot-upload.js +70 -10
  15. package/dist/src/commands/agent.js +21 -11
  16. package/dist/src/commands/browser.js +24 -15
  17. package/dist/src/commands/connect.js +22 -17
  18. package/dist/src/commands/dist.js +15 -6
  19. package/dist/src/commands/doctor.js +70 -10
  20. package/dist/src/commands/github.js +79 -19
  21. package/dist/src/commands/inbox.js +215 -131
  22. package/dist/src/commands/init.js +202 -35
  23. package/dist/src/commands/inspect.js +77 -17
  24. package/dist/src/commands/inspector.js +18 -9
  25. package/dist/src/commands/pi.js +18 -9
  26. package/dist/src/commands/plugin.js +19 -10
  27. package/dist/src/commands/profile-and-review.js +22 -13
  28. package/dist/src/commands/queue.js +19 -9
  29. package/dist/src/commands/remote.js +25 -16
  30. package/dist/src/commands/repo-git-harness.js +18 -9
  31. package/dist/src/commands/run.js +158 -805
  32. package/dist/src/commands/server.js +85 -25
  33. package/dist/src/commands/setup.js +73 -13
  34. package/dist/src/commands/stats.js +681 -0
  35. package/dist/src/commands/task-report-bug.js +24 -15
  36. package/dist/src/commands/task-run-driver.js +121 -49
  37. package/dist/src/commands/task.js +254 -893
  38. package/dist/src/commands/test.js +12 -3
  39. package/dist/src/commands/workspace.js +14 -5
  40. package/dist/src/commands.js +1339 -1650
  41. package/dist/src/index.js +1349 -1599
  42. package/dist/src/launcher.js +72 -10
  43. package/dist/src/runner.js +14 -5
  44. package/package.json +10 -7
  45. package/dist/src/commands/_pi-remote-session.js +0 -771
  46. package/dist/src/commands/_pi-worker-bridge-extension.js +0 -834
@@ -1,14 +1,19 @@
1
1
  // @bun
2
- // packages/cli/src/commands/inbox.ts
3
- import { writeFileSync as writeFileSync2 } from "fs";
4
- import { resolve as resolve3 } from "path";
5
-
6
2
  // packages/cli/src/runner.ts
7
3
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
8
- import { CliError } from "@rig/runtime/control-plane/errors";
4
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
9
5
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
10
6
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
11
- 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
+ }
12
17
  function takeOption(args, option) {
13
18
  const rest = [];
14
19
  let value;
@@ -17,7 +22,7 @@ function takeOption(args, option) {
17
22
  if (current === option) {
18
23
  const next = args[index + 1];
19
24
  if (!next || next.startsWith("-")) {
20
- throw new CliError(`Missing value for ${option}`);
25
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
21
26
  }
22
27
  value = next;
23
28
  index += 1;
@@ -36,13 +41,6 @@ Usage: ${usage}`);
36
41
  }
37
42
  }
38
43
 
39
- // packages/cli/src/commands/inbox.ts
40
- import {
41
- listAuthorityRuns,
42
- readJsonlFile,
43
- resolveAuthorityRunDir
44
- } from "@rig/runtime/control-plane/authority-files";
45
-
46
44
  // packages/cli/src/commands/_cli-format.ts
47
45
  import { log, note } from "@clack/prompts";
48
46
  import pc from "picocolors";
@@ -125,6 +123,11 @@ function formatInboxList(kind, entries) {
125
123
  `);
126
124
  }
127
125
 
126
+ // packages/cli/src/commands/_server-client.ts
127
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
128
+ import { resolve as resolve2 } from "path";
129
+ import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
130
+
128
131
  // packages/cli/src/commands/_connection-state.ts
129
132
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
130
133
  import { homedir } from "os";
@@ -147,7 +150,7 @@ function readJsonFile(path) {
147
150
  try {
148
151
  return JSON.parse(readFileSync(path, "utf8"));
149
152
  } catch (error) {
150
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
153
+ 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>`." });
151
154
  }
152
155
  }
153
156
  function writeJsonFile(path, value) {
@@ -211,7 +214,7 @@ function resolveSelectedConnection(projectRoot, options = {}) {
211
214
  const global = readGlobalConnections(options);
212
215
  const connection = global.connections[repo.selected];
213
216
  if (!connection) {
214
- throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
217
+ throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
215
218
  }
216
219
  return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
217
220
  }
@@ -223,9 +226,6 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
223
226
  }
224
227
 
225
228
  // packages/cli/src/commands/_server-client.ts
226
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
227
- import { resolve as resolve2 } from "path";
228
- import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
229
229
  var scopedGitHubBearerTokens = new Map;
230
230
  function cleanToken(value) {
231
231
  const trimmed = value?.trim();
@@ -287,7 +287,7 @@ async function ensureServerForCli(projectRoot) {
287
287
  };
288
288
  } catch (error) {
289
289
  if (error instanceof Error) {
290
- throw new CliError2(error.message, 1);
290
+ throw new CliError(error.message, 1);
291
291
  }
292
292
  throw error;
293
293
  }
@@ -337,15 +337,64 @@ function diagnosticMessage(payload) {
337
337
  });
338
338
  return messages.length > 0 ? messages.join("; ") : null;
339
339
  }
340
+ var serverReachabilityCache = new Map;
341
+ async function probeServerReachability(baseUrl, authToken) {
342
+ try {
343
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
344
+ headers: mergeHeaders(undefined, authToken),
345
+ signal: AbortSignal.timeout(1500)
346
+ });
347
+ return response.ok;
348
+ } catch {
349
+ return false;
350
+ }
351
+ }
352
+ function cachedServerReachability(projectRoot, baseUrl, authToken) {
353
+ const key = resolve2(projectRoot);
354
+ const cached = serverReachabilityCache.get(key);
355
+ if (cached)
356
+ return cached;
357
+ const probe = probeServerReachability(baseUrl, authToken);
358
+ serverReachabilityCache.set(key, probe);
359
+ return probe;
360
+ }
361
+ function describeSelectedServer(projectRoot, server) {
362
+ try {
363
+ const selected = resolveSelectedConnection(projectRoot);
364
+ if (selected) {
365
+ return {
366
+ alias: selected.alias,
367
+ target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
368
+ };
369
+ }
370
+ } catch {}
371
+ return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
372
+ }
373
+ async function buildServerFailureContext(projectRoot, server) {
374
+ const { alias, target } = describeSelectedServer(projectRoot, server);
375
+ const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
376
+ const reachability = reachable ? "server is reachable" : "server is unreachable";
377
+ return {
378
+ contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
379
+ hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
380
+ };
381
+ }
340
382
  async function requestServerJson(context, pathname, init = {}) {
341
383
  const server = await ensureServerForCli(context.projectRoot);
342
384
  const headers = mergeHeaders(init.headers, server.authToken);
343
385
  if (server.serverProjectRoot)
344
386
  headers.set("x-rig-project-root", server.serverProjectRoot);
345
- const response = await fetch(`${server.baseUrl}${pathname}`, {
346
- ...init,
347
- headers
348
- });
387
+ let response;
388
+ try {
389
+ response = await fetch(`${server.baseUrl}${pathname}`, {
390
+ ...init,
391
+ headers
392
+ });
393
+ } catch (error) {
394
+ const failure = await buildServerFailureContext(context.projectRoot, server);
395
+ throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
396
+ ${failure.contextLine}`, 1, { hint: failure.hint });
397
+ }
349
398
  const text = await response.text();
350
399
  const payload = text.trim().length > 0 ? (() => {
351
400
  try {
@@ -357,77 +406,106 @@ async function requestServerJson(context, pathname, init = {}) {
357
406
  if (!response.ok) {
358
407
  const diagnostics = diagnosticMessage(payload);
359
408
  const detail = diagnostics ?? (text || response.statusText);
360
- throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
409
+ const failure = await buildServerFailureContext(context.projectRoot, server);
410
+ throw new CliError(`Rig server request failed (${response.status}): ${detail}
411
+ ${failure.contextLine}`, 1, { hint: failure.hint });
361
412
  }
362
413
  return payload;
363
414
  }
364
- async function listRunsViaServer(context, options = {}) {
365
- const url = new URL("http://rig.local/api/runs");
366
- if (options.limit !== undefined)
367
- url.searchParams.set("limit", String(options.limit));
368
- const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
369
- const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
370
- return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
371
- }
372
- async function getRunDetailsViaServer(context, runId) {
373
- const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
374
- return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
375
- }
376
415
 
377
416
  // packages/cli/src/commands/inbox.ts
378
- function isRemoteConnectionSelected(projectRoot) {
379
- return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
380
- }
381
- function runMatches(entry, filters) {
382
- const runId = typeof entry.runId === "string" ? entry.runId : typeof entry.id === "string" ? entry.id : "";
383
- const taskId = typeof entry.taskId === "string" ? entry.taskId : "";
384
- return (!filters.run || runId === filters.run) && (!filters.task || taskId === filters.task);
385
- }
386
- function normalizeRemoteRunDetails(payload) {
387
- const run = payload.run;
388
- if (run && typeof run === "object" && !Array.isArray(run)) {
389
- return {
390
- ...run,
391
- ...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
392
- ...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
393
- ...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
394
- };
417
+ async function listInboxRecords(context, kind, filters) {
418
+ const params = new URLSearchParams;
419
+ if (filters.run)
420
+ params.set("runId", filters.run);
421
+ if (filters.task)
422
+ params.set("taskId", filters.task);
423
+ const query = params.size > 0 ? `?${params.toString()}` : "";
424
+ const payload = await requestServerJson(context, `/api/inbox/${kind}${query}`);
425
+ const records = Array.isArray(payload) ? payload : [];
426
+ return filters.pendingOnly ? records.filter((entry) => (entry.status ?? "pending") !== "resolved") : records;
427
+ }
428
+ async function readPendingInboxCounts(context) {
429
+ try {
430
+ const [approvals, inputs] = await Promise.all([
431
+ listInboxRecords(context, "approvals", { pendingOnly: true }),
432
+ listInboxRecords(context, "inputs", { pendingOnly: true })
433
+ ]);
434
+ return { approvals: approvals.length, inputs: inputs.length };
435
+ } catch {
436
+ return null;
395
437
  }
396
- return payload;
397
438
  }
398
- function remoteRecordsFromRun(run, kind) {
399
- const key = kind === "approvals" ? "approvals" : "userInputs";
400
- const direct = run[key];
401
- if (!Array.isArray(direct))
402
- return [];
403
- const runId = typeof run.runId === "string" ? run.runId : typeof run.id === "string" ? run.id : "";
404
- const taskId = typeof run.taskId === "string" ? run.taskId : "";
405
- return direct.filter((record) => Boolean(record && typeof record === "object" && !Array.isArray(record))).map((record) => ({ runId, taskId, record }));
406
- }
407
- async function listRemoteInboxRecords(context, kind, filters) {
408
- const runs = (await listRunsViaServer(context, { limit: 100 })).filter((entry) => runMatches(entry, filters));
409
- const records = [];
410
- for (const run of runs) {
411
- const runId = typeof run.runId === "string" ? run.runId : typeof run.id === "string" ? run.id : "";
412
- const detailed = runId ? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, runId).catch(() => run)) : run;
413
- records.push(...remoteRecordsFromRun(detailed, kind));
439
+ async function printPendingInboxFooter(context) {
440
+ if (context.outputMode !== "text")
441
+ return;
442
+ const counts = await readPendingInboxCounts(context);
443
+ if (!counts || counts.approvals === 0 && counts.inputs === 0)
444
+ return;
445
+ const parts = [];
446
+ if (counts.approvals > 0)
447
+ parts.push(`${counts.approvals} approval${counts.approvals === 1 ? "" : "s"}`);
448
+ if (counts.inputs > 0)
449
+ parts.push(`${counts.inputs} input request${counts.inputs === 1 ? "" : "s"}`);
450
+ console.log(`
451
+ \u26A0 ${parts.join(" and ")} pending \u2014 run \`rig inbox\` to review.`);
452
+ }
453
+ function renderList(context, kind, records) {
454
+ if (context.outputMode === "text") {
455
+ printFormattedOutput(formatInboxList(kind, records));
414
456
  }
415
- return records;
416
457
  }
417
- function listLocalInboxRecords(context, kind, filters) {
418
- const fileName = kind === "approvals" ? "approvals.jsonl" : "user-input.jsonl";
419
- const runs = listAuthorityRuns(context.projectRoot).filter((entry) => (!filters.run || entry.runId === filters.run) && (!filters.task || entry.taskId === filters.task));
420
- return runs.flatMap((entry) => readJsonlFile(resolve3(resolveAuthorityRunDir(context.projectRoot, entry.runId), fileName)).map((record) => ({
421
- runId: entry.runId,
422
- taskId: entry.taskId ?? undefined,
423
- record
424
- })));
425
- }
426
- async function listInboxRecords(context, kind, filters) {
427
- if (isRemoteConnectionSelected(context.projectRoot)) {
428
- return listRemoteInboxRecords(context, kind, filters);
458
+ async function watchInbox(context, filters) {
459
+ const render = async () => {
460
+ const [approvals, inputs] = await Promise.all([
461
+ listInboxRecords(context, "approvals", { ...filters, pendingOnly: true }),
462
+ listInboxRecords(context, "inputs", { ...filters, pendingOnly: true })
463
+ ]);
464
+ console.clear();
465
+ console.log(`rig inbox \u2014 watching (Ctrl-C to stop) \u2014 ${new Date().toLocaleTimeString()}`);
466
+ renderList(context, "approvals", approvals);
467
+ renderList(context, "inputs", inputs);
468
+ if (approvals.length === 0 && inputs.length === 0) {
469
+ console.log("Nothing pending.");
470
+ } else {
471
+ console.log(`
472
+ Resolve with: rig inbox approve --run <id> --request <id> --decision approve|reject`);
473
+ }
474
+ };
475
+ await render();
476
+ const server = await ensureServerForCli(context.projectRoot);
477
+ const relevant = /"type":"rig\.(approval|user-input)\./;
478
+ const fallbackTimer = setInterval(() => {
479
+ render();
480
+ }, 30000);
481
+ fallbackTimer.unref?.();
482
+ for (;; ) {
483
+ try {
484
+ const response = await fetch(`${server.baseUrl}/api/server/events`, {
485
+ headers: {
486
+ ...server.authToken ? { authorization: `Bearer ${server.authToken}` } : {},
487
+ ...server.serverProjectRoot ? { "x-rig-project-root": server.serverProjectRoot } : {},
488
+ accept: "text/event-stream"
489
+ }
490
+ });
491
+ if (!response.ok || !response.body)
492
+ throw new Error(`SSE ${response.status}`);
493
+ const reader = response.body.getReader();
494
+ const decoder = new TextDecoder;
495
+ for (;; ) {
496
+ const { done, value } = await reader.read();
497
+ if (done)
498
+ break;
499
+ const chunk = decoder.decode(value, { stream: true });
500
+ if (relevant.test(chunk)) {
501
+ await render();
502
+ }
503
+ }
504
+ } catch {
505
+ await render();
506
+ await new Promise((resolveSleep) => setTimeout(resolveSleep, 2000));
507
+ }
429
508
  }
430
- return listLocalInboxRecords(context, kind, filters);
431
509
  }
432
510
  async function executeInbox(context, args) {
433
511
  const [command = "approvals", ...rest] = args;
@@ -440,11 +518,20 @@ async function executeInbox(context, args) {
440
518
  pending = task.rest;
441
519
  requireNoExtraArgs(pending, "rig inbox approvals [--run <id>] [--task <id>]");
442
520
  const approvals = await listInboxRecords(context, "approvals", { run: run.value, task: task.value });
443
- if (context.outputMode === "text") {
444
- printFormattedOutput(formatInboxList("approvals", approvals));
445
- }
521
+ renderList(context, "approvals", approvals);
446
522
  return { ok: true, group: "inbox", command, details: { approvals } };
447
523
  }
524
+ case "inputs": {
525
+ let pending = rest;
526
+ const run = takeOption(pending, "--run");
527
+ pending = run.rest;
528
+ const task = takeOption(pending, "--task");
529
+ pending = task.rest;
530
+ requireNoExtraArgs(pending, "rig inbox inputs [--run <id>] [--task <id>]");
531
+ const requests = await listInboxRecords(context, "inputs", { run: run.value, task: task.value });
532
+ renderList(context, "inputs", requests);
533
+ return { ok: true, group: "inbox", command, details: { requests } };
534
+ }
448
535
  case "approve": {
449
536
  let pending = rest;
450
537
  const run = takeOption(pending, "--run");
@@ -457,35 +544,22 @@ async function executeInbox(context, args) {
457
544
  pending = note2.rest;
458
545
  requireNoExtraArgs(pending, "rig inbox approve --run <id> --request <id> --decision approve|reject [--note <text>]");
459
546
  if (!run.value || !request.value || !decision.value) {
460
- throw new CliError2("approve requires --run, --request, and --decision.");
547
+ throw new CliError("approve requires --run, --request, and --decision. List pending requests with `rig inbox approvals`.");
461
548
  }
462
549
  if (decision.value !== "approve" && decision.value !== "reject") {
463
- throw new CliError2("decision must be approve or reject.");
464
- }
465
- if (isRemoteConnectionSelected(context.projectRoot)) {
466
- throw new CliError2("Remote approval resolution is not available from this CLI yet; use the server UI/API or switch to local state for direct JSONL edits.", 2);
550
+ throw new CliError("decision must be approve or reject.", 1, { hint: "Re-run as `rig inbox approve --run <id> --request <id> --decision approve|reject`." });
467
551
  }
468
- const approvalsPath = resolve3(resolveAuthorityRunDir(context.projectRoot, run.value), "approvals.jsonl");
469
- const approvals = readJsonlFile(approvalsPath);
470
- const resolvedAt = new Date().toISOString();
471
- const next = approvals.map((entry) => entry.requestId === request.value || entry.id === request.value ? { ...entry, status: "resolved", decision: decision.value, note: note2.value ?? null, resolvedAt } : entry);
472
- writeFileSync2(approvalsPath, `${next.map((entry) => JSON.stringify(entry)).join(`
473
- `)}
474
- `, "utf8");
475
- return { ok: true, group: "inbox", command, details: { runId: run.value, requestId: request.value, decision: decision.value } };
476
- }
477
- case "inputs": {
478
- let pending = rest;
479
- const run = takeOption(pending, "--run");
480
- pending = run.rest;
481
- const task = takeOption(pending, "--task");
482
- pending = task.rest;
483
- requireNoExtraArgs(pending, "rig inbox inputs [--run <id>] [--task <id>]");
484
- const requests = await listInboxRecords(context, "inputs", { run: run.value, task: task.value });
485
- if (context.outputMode === "text") {
486
- printFormattedOutput(formatInboxList("inputs", requests));
487
- }
488
- return { ok: true, group: "inbox", command, details: { requests } };
552
+ const result = await requestServerJson(context, "/api/inbox/approvals/resolve", {
553
+ method: "POST",
554
+ headers: { "content-type": "application/json" },
555
+ body: JSON.stringify({
556
+ runId: run.value,
557
+ requestId: request.value,
558
+ decision: decision.value,
559
+ note: note2.value ?? null
560
+ })
561
+ });
562
+ return { ok: true, group: "inbox", command, details: { result } };
489
563
  }
490
564
  case "respond": {
491
565
  let pending = rest;
@@ -498,11 +572,11 @@ async function executeInbox(context, args) {
498
572
  for (let index = 0;index < pending.length; index += 1) {
499
573
  const current = pending[index];
500
574
  if (current === "--answer") {
501
- const next2 = pending[index + 1];
502
- if (!next2 || next2.startsWith("-")) {
503
- throw new CliError2("Missing value for --answer");
575
+ const next = pending[index + 1];
576
+ if (!next || next.startsWith("-")) {
577
+ throw new CliError("Missing value for --answer", 1, { hint: "Pass key=value pairs, e.g. `rig inbox respond --run <id> --request <id> --answer key=value`." });
504
578
  }
505
- answers.push(next2);
579
+ answers.push(next);
506
580
  index += 1;
507
581
  continue;
508
582
  }
@@ -512,28 +586,38 @@ async function executeInbox(context, args) {
512
586
  }
513
587
  requireNoExtraArgs(remaining, "rig inbox respond --run <id> --request <id> --answer key=value [--answer key=value]");
514
588
  if (!run.value || !request.value || answers.length === 0) {
515
- throw new CliError2("respond requires --run, --request, and at least one --answer.");
516
- }
517
- if (isRemoteConnectionSelected(context.projectRoot)) {
518
- throw new CliError2("Remote input responses are not available from this CLI yet; use the server UI/API or switch to local state for direct JSONL edits.", 2);
589
+ throw new CliError("respond requires --run, --request, and at least one --answer. List pending requests with `rig inbox inputs`.");
519
590
  }
520
591
  const parsedAnswers = Object.fromEntries(answers.map((entry) => {
521
592
  const [key, ...restValue] = entry.split("=");
522
593
  return [key, restValue.join("=")];
523
594
  }));
524
- const requestsPath = resolve3(resolveAuthorityRunDir(context.projectRoot, run.value), "user-input.jsonl");
525
- const requests = readJsonlFile(requestsPath);
526
- const resolvedAt = new Date().toISOString();
527
- const next = requests.map((entry) => entry.requestId === request.value || entry.id === request.value ? { ...entry, status: "resolved", answers: parsedAnswers, respondedAt: resolvedAt, resolvedAt } : entry);
528
- writeFileSync2(requestsPath, `${next.map((entry) => JSON.stringify(entry)).join(`
529
- `)}
530
- `, "utf8");
531
- return { ok: true, group: "inbox", command, details: { runId: run.value, requestId: request.value, answers: parsedAnswers } };
595
+ const result = await requestServerJson(context, "/api/inbox/inputs/respond", {
596
+ method: "POST",
597
+ headers: { "content-type": "application/json" },
598
+ body: JSON.stringify({
599
+ runId: run.value,
600
+ requestId: request.value,
601
+ answers: parsedAnswers
602
+ })
603
+ });
604
+ return { ok: true, group: "inbox", command, details: { result } };
605
+ }
606
+ case "watch": {
607
+ let pending = rest;
608
+ const run = takeOption(pending, "--run");
609
+ pending = run.rest;
610
+ const task = takeOption(pending, "--task");
611
+ pending = task.rest;
612
+ requireNoExtraArgs(pending, "rig inbox watch [--run <id>] [--task <id>]");
613
+ return await watchInbox(context, { run: run.value, task: task.value });
532
614
  }
533
615
  default:
534
- throw new CliError2(`Unknown inbox command: ${command}`);
616
+ throw new CliError(`Unknown inbox command: ${command}. Use approvals|inputs|approve|respond|watch.`);
535
617
  }
536
618
  }
537
619
  export {
620
+ readPendingInboxCounts,
621
+ printPendingInboxFooter,
538
622
  executeInbox
539
623
  };