@quireco/cli 0.0.10 → 0.0.12

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.
@@ -8,10 +8,10 @@ import { exec, execFile, execFileSync, spawn } from "node:child_process";
8
8
  import pc from "picocolors";
9
9
  import { calculateCost, createAssistantMessageEventStream } from "@earendil-works/pi-ai";
10
10
  import { convertMessages } from "@earendil-works/pi-ai/openai-completions";
11
- import { access, constants, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
11
+ import { access, constants, mkdir, mkdtemp, readFile, readdir, rename, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
12
12
  import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
13
13
  import { homedir, networkInterfaces, tmpdir } from "node:os";
14
- import { closeSync, constants as constants$1, copyFileSync, existsSync, fstatSync, fsyncSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
14
+ import { closeSync, constants as constants$1, copyFileSync, existsSync, fstatSync, fsyncSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
15
15
  import { createHash } from "node:crypto";
16
16
  import { customAlphabet } from "nanoid";
17
17
  import { Type } from "@sinclair/typebox";
@@ -1375,21 +1375,28 @@ const FINDING_RELATIONSHIPS_TO_REQUEST = [
1375
1375
  ];
1376
1376
  function createRunDir(repoPath, options = {}) {
1377
1377
  const workspaceKey = workspaceKeyForRepoPath(resolve(repoPath));
1378
- const runsRoot = resolve(options.runsRoot ?? defaultRunsRoot());
1379
- const { runId, root } = options.runId === void 0 ? createUniqueRunRoot(runsRoot, workspaceKey, options.createRunId ?? createRunId) : {
1378
+ const { runsRoot, runId, root, temporaryRunsRoot } = allocateRunRoot({
1379
+ runsRoot: resolve(options.runsRoot ?? defaultRunsRoot()),
1380
+ workspaceKey,
1380
1381
  runId: options.runId,
1381
- root: join(runsRoot, workspaceKey, options.runId)
1382
- };
1383
- const paths = runDirPaths(root);
1384
- mkdirSync(paths.evidence, {
1385
- recursive: true,
1386
- mode: 448
1382
+ runIdFactory: options.createRunId ?? createRunId,
1383
+ allowTemporaryFallback: options.runsRoot === void 0 && (process.env.QUIRE_RUNS_DIR?.trim().length ?? 0) === 0
1387
1384
  });
1385
+ const paths = runDirPaths(root);
1386
+ try {
1387
+ mkdirSync(paths.evidence, {
1388
+ recursive: true,
1389
+ mode: 448
1390
+ });
1391
+ } catch (error) {
1392
+ throw writableRunsRootError(error, runsRoot);
1393
+ }
1388
1394
  return {
1389
1395
  runId,
1390
1396
  workspaceKey,
1391
1397
  runsRoot,
1392
1398
  root,
1399
+ ...temporaryRunsRoot ? { temporaryRunsRoot: true } : {},
1393
1400
  paths
1394
1401
  };
1395
1402
  }
@@ -1416,6 +1423,9 @@ function runPlanPath(runPath) {
1416
1423
  function runHandoffPath(runPath) {
1417
1424
  return join(resolve(runPath), "handoff.md");
1418
1425
  }
1426
+ function runPendingHandoffPath(runPath) {
1427
+ return join(resolve(runPath), ".pending-handoff.json");
1428
+ }
1419
1429
  function runDirPaths(root) {
1420
1430
  const evidence = join(root, "evidence");
1421
1431
  return {
@@ -1432,6 +1442,51 @@ function runDirPaths(root) {
1432
1442
  harnessSessions: join(root, ".harness-sessions")
1433
1443
  };
1434
1444
  }
1445
+ function allocateRunRoot(input) {
1446
+ try {
1447
+ return {
1448
+ ...createRunRoot(input.runsRoot, input.workspaceKey, input.runId, input.runIdFactory),
1449
+ runsRoot: input.runsRoot,
1450
+ temporaryRunsRoot: false
1451
+ };
1452
+ } catch (error) {
1453
+ if (!isWriteAccessError$2(error)) throw error;
1454
+ if (!input.allowTemporaryFallback) throw writableRunsRootError(error, input.runsRoot);
1455
+ const runsRoot = mkdtempSync(join(tmpdir(), "quire-runs-"));
1456
+ return {
1457
+ ...createRunRoot(runsRoot, input.workspaceKey, input.runId, input.runIdFactory),
1458
+ runsRoot,
1459
+ temporaryRunsRoot: true
1460
+ };
1461
+ }
1462
+ }
1463
+ function createRunRoot(runsRoot, workspaceKey, runId, runIdFactory) {
1464
+ return runId === void 0 ? createUniqueRunRoot(runsRoot, workspaceKey, runIdFactory) : createNamedRunRoot(runsRoot, workspaceKey, runId);
1465
+ }
1466
+ function createNamedRunRoot(runsRoot, workspaceKey, runId) {
1467
+ const root = join(runsRoot, workspaceKey, runId);
1468
+ mkdirSync(join(root, "evidence"), {
1469
+ recursive: true,
1470
+ mode: 448
1471
+ });
1472
+ return {
1473
+ runId,
1474
+ root
1475
+ };
1476
+ }
1477
+ function writableRunsRootError(error, runsRoot) {
1478
+ if (!isWriteAccessError$2(error)) return error;
1479
+ return new CliError$1([
1480
+ `Quire could not write run data at ${runsRoot}: ${error.message}`,
1481
+ "In sandboxed coding-agent environments, retry with a temporary run directory:",
1482
+ " quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)",
1483
+ " QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json <your request>",
1484
+ "Use the same QUIRE_RUNS_DIR value for follow-up `quire wait`, `quire status`, `quire watch`, or `quire cancel` commands."
1485
+ ].join("\n"), ExitCode.InvalidInput);
1486
+ }
1487
+ function isWriteAccessError$2(error) {
1488
+ return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
1489
+ }
1435
1490
  function defaultRunsRoot(env = process.env) {
1436
1491
  return defaultRunsRoot$1(env);
1437
1492
  }
@@ -1483,6 +1538,20 @@ function writeRunFinalHandoff(runDir, final) {
1483
1538
  finalResult: final.result
1484
1539
  });
1485
1540
  }
1541
+ function writeRunPendingHandoff(runDir, handoff) {
1542
+ const writtenAt = (/* @__PURE__ */ new Date()).toISOString();
1543
+ writeFileAtomically(runPendingHandoffPath(runDir.root), `${stableJson({
1544
+ version: 1,
1545
+ writtenAt,
1546
+ handoff
1547
+ })}\n`, 384);
1548
+ writeFileAtomically(runDir.paths.handoff, `${handoff.handoffMarkdown.trimEnd()}\n`, 384);
1549
+ }
1550
+ function clearRunPendingHandoff(runDir) {
1551
+ try {
1552
+ unlinkSync(runPendingHandoffPath(runDir.root));
1553
+ } catch {}
1554
+ }
1486
1555
  function patchRunStatusJson(runDir, patch) {
1487
1556
  if (!existsSync(runDir.paths.status)) return;
1488
1557
  let existing;
@@ -1752,18 +1821,32 @@ function readFreshRunStatus(runPath) {
1752
1821
  if (existsSync(runHandoffPath(runPath))) return updateRunStatus(runPath, { status: "completed" });
1753
1822
  return updateRunStatus(runPath, {
1754
1823
  status: "stale",
1755
- error: "Worker process exited before marking the run as running."
1824
+ error: staleWorkerError(runPath, "Worker process exited before marking the run as running.")
1756
1825
  });
1757
1826
  }
1758
1827
  if ((status.status === "running" || status.status === "canceling") && !isPidAlive(status.pid)) {
1759
1828
  if (existsSync(runHandoffPath(runPath))) return updateRunStatus(runPath, { status: "completed" });
1760
1829
  return updateRunStatus(runPath, {
1761
1830
  status: status.status === "canceling" ? "canceled" : "stale",
1762
- error: status.status === "canceling" ? void 0 : "Worker process is no longer running."
1831
+ error: status.status === "canceling" ? void 0 : staleWorkerError(runPath, "Worker process is no longer running.")
1763
1832
  });
1764
1833
  }
1765
1834
  return status;
1766
1835
  }
1836
+ function staleWorkerError(runPath, message) {
1837
+ const workerLogPath = join(runPath, "worker.log");
1838
+ if (!existsSync(workerLogPath)) return message;
1839
+ const tail = readWorkerLogTail$1(workerLogPath);
1840
+ if (tail.length === 0) return `${message}\n\nWorker log: ${workerLogPath} (empty)`;
1841
+ return `${message}\n\nWorker log: ${workerLogPath}\n\nLast worker output:\n${tail}`;
1842
+ }
1843
+ function readWorkerLogTail$1(workerLogPath) {
1844
+ try {
1845
+ return readFileSync(workerLogPath, "utf8").slice(-12e3).trim();
1846
+ } catch {
1847
+ return "";
1848
+ }
1849
+ }
1767
1850
  function readRequiredRunStatus(runPath) {
1768
1851
  const status = readRunStatus(runPath);
1769
1852
  if (status === void 0) throw new CliError$1(`Run status not found: ${runPath}`, ExitCode.InvalidInput);
@@ -1799,10 +1882,27 @@ function writeStatus(filePath, status) {
1799
1882
  mode: 448
1800
1883
  });
1801
1884
  const tempPath = join(dirname(filePath), `.${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.status.tmp`);
1802
- writeFileSync(tempPath, `${JSON.stringify(status, null, 2)}\n`, { mode: 384 });
1803
- fsyncFile(tempPath);
1804
- renameSync(tempPath, filePath);
1805
- fsyncDirectory(dirname(filePath));
1885
+ try {
1886
+ writeFileSync(tempPath, `${JSON.stringify(status, null, 2)}\n`, { mode: 384 });
1887
+ fsyncFile(tempPath);
1888
+ renameSync(tempPath, filePath);
1889
+ fsyncDirectory(dirname(filePath));
1890
+ } catch (error) {
1891
+ throw writableRunStatusError(error, status.runPath);
1892
+ }
1893
+ }
1894
+ function writableRunStatusError(error, runPath) {
1895
+ if (!isWriteAccessError$1(error)) return error;
1896
+ return new CliError$1([
1897
+ `Quire could not update run status at ${runPath}: ${error.message}`,
1898
+ "In sandboxed coding-agent environments, put Quire run data in a temporary directory and reuse it for every lifecycle command:",
1899
+ " quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)",
1900
+ " QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json <your request>",
1901
+ " QUIRE_RUNS_DIR=$quire_runs_dir quire wait <run-id> --json"
1902
+ ].join("\n"), ExitCode.InvalidInput);
1903
+ }
1904
+ function isWriteAccessError$1(error) {
1905
+ return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
1806
1906
  }
1807
1907
  function fsyncFile(filePath) {
1808
1908
  const file = openSync(filePath, "r");
@@ -2552,14 +2652,6 @@ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, hea
2552
2652
  for (const command of commands) {
2553
2653
  const args = applyBrowserLaunchArgs(applyRunDefaults(applyHeadedMode(command.args, headed), runDir));
2554
2654
  const commandLabel = browserCommandLabel(args);
2555
- emitBrowserProgress(runDir, onProgressEvent, {
2556
- kind: "action",
2557
- status: "running",
2558
- title: `Browser: ${commandLabel}`,
2559
- summary: "agent-browser is acting on the target application.",
2560
- importance: "normal",
2561
- source: "runtime"
2562
- });
2563
2655
  const result = await spawnAgentBrowser({
2564
2656
  args,
2565
2657
  sessionId,
@@ -2604,14 +2696,6 @@ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, hea
2604
2696
  stderr: stderr.join("")
2605
2697
  };
2606
2698
  }
2607
- emitBrowserProgress(runDir, onProgressEvent, {
2608
- kind: "action",
2609
- status: "passed",
2610
- title: `Browser: ${commandLabel}`,
2611
- summary: "agent-browser completed this target step.",
2612
- importance: "normal",
2613
- source: "runtime"
2614
- });
2615
2699
  }
2616
2700
  return {
2617
2701
  ok: true,
@@ -2817,6 +2901,7 @@ function materializeHandoff({ handoff, runDir, continuation, modelUsage, metadat
2817
2901
  result: materializedResult
2818
2902
  });
2819
2903
  writeHandoffArtifact(runDir, materializedHandoff);
2904
+ clearRunPendingHandoff(runDir);
2820
2905
  return {
2821
2906
  result: materializedResult,
2822
2907
  metadata: nextMetadata
@@ -45331,6 +45416,23 @@ const writeHandoffToolDefinition = {
45331
45416
  };
45332
45417
  }
45333
45418
  };
45419
+ function makeWriteHandoffTool(options = {}) {
45420
+ return defineTool({
45421
+ ...writeHandoffToolDefinition,
45422
+ async execute(_toolCallId, params, _signal) {
45423
+ const handoff = normalizeWriteHandoffInput(params);
45424
+ await options.onHandoff?.(handoff);
45425
+ return {
45426
+ content: [{
45427
+ type: "text",
45428
+ text: JSON.stringify(handoff, null, 2)
45429
+ }],
45430
+ details: handoff,
45431
+ terminate: true
45432
+ };
45433
+ }
45434
+ });
45435
+ }
45334
45436
  function normalizeWriteHandoffInput(params) {
45335
45437
  const { status, ...rest } = params;
45336
45438
  return {
@@ -45343,7 +45445,7 @@ function normalizeHandoffStatus(status) {
45343
45445
  if (status === "no_issue_found" || status === "not_a_bug" || status === "verified_no_bug_found" || status === "verified_no_issue_found") return "not_reproduced";
45344
45446
  return status;
45345
45447
  }
45346
- const writeHandoffTool = defineTool(writeHandoffToolDefinition);
45448
+ makeWriteHandoffTool();
45347
45449
  //#endregion
45348
45450
  //#region src/utils/schema.ts
45349
45451
  function stringEnumSchema(values) {
@@ -46169,7 +46271,11 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
46169
46271
  followup,
46170
46272
  skillPack,
46171
46273
  mcpGateway,
46172
- intent
46274
+ intent,
46275
+ onHandoff: (handoff) => {
46276
+ emittedHandoff ??= handoff;
46277
+ writeRunPendingHandoff(runDir, handoff);
46278
+ }
46173
46279
  });
46174
46280
  const { session, logAgentProgress, registeredTargetTools } = runtime;
46175
46281
  onResolvedModel?.({
@@ -46177,18 +46283,31 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
46177
46283
  provider: runtime.model.provider,
46178
46284
  modelId: runtime.model.id
46179
46285
  });
46286
+ let rejectCurrentPromptOnModelError;
46180
46287
  const unsubscribe = session.subscribe((event) => {
46181
46288
  touchRunActivity(runDir.root, { minIntervalMs: RUN_ACTIVITY_HEARTBEAT_INTERVAL_MS });
46182
46289
  logAgentProgress(event);
46290
+ const sessionError = modelErrorFromSessionEvent(event);
46291
+ if (sessionError !== void 0) rejectCurrentPromptOnModelError?.(sessionError);
46183
46292
  emittedHandoff ??= extractWriteHandoff(event);
46184
46293
  rememberTargetToolCallArgs(event, registeredTargetTools, targetToolCallArgs);
46185
46294
  const attemptsObserved = countNewTargetAttempts(event, registeredTargetTools, observedTargetToolCallIds, targetToolCallArgs);
46186
46295
  if (attemptsObserved > 0) observedToolAttempts += attemptsObserved;
46187
46296
  });
46188
46297
  try {
46189
- await session.prompt(runBrief, { expandPromptTemplates: false });
46190
- if (emittedHandoff === void 0) await session.prompt(buildMissingRunHandoffPrompt(observedToolAttempts), { expandPromptTemplates: false });
46298
+ try {
46299
+ await promptOrModelError(session.prompt(runBrief, { expandPromptTemplates: false }), (reject) => {
46300
+ rejectCurrentPromptOnModelError = reject;
46301
+ });
46302
+ if (emittedHandoff === void 0) await promptOrModelError(session.prompt(buildMissingRunHandoffPrompt(observedToolAttempts), { expandPromptTemplates: false }), (reject) => {
46303
+ rejectCurrentPromptOnModelError = reject;
46304
+ });
46305
+ } catch (error) {
46306
+ if (emittedHandoff === void 0) throw error;
46307
+ progress?.("Recovered terminal write_handoff after the agent session stopped.");
46308
+ }
46191
46309
  } finally {
46310
+ rejectCurrentPromptOnModelError = void 0;
46192
46311
  runtime.writeSessionState();
46193
46312
  unsubscribe();
46194
46313
  runtime.dispose();
@@ -46198,7 +46317,23 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
46198
46317
  runDir
46199
46318
  });
46200
46319
  }
46201
- async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed, intent, progress, onProgressEvent, runInstructions, remoteAddons, runtimeCapabilities, followup, skillPack, mcpGateway }) {
46320
+ async function promptOrModelError(prompt, setRejectCurrentPromptOnModelError) {
46321
+ try {
46322
+ return await Promise.race([prompt, new Promise((_resolve, reject) => {
46323
+ setRejectCurrentPromptOnModelError(reject);
46324
+ })]);
46325
+ } finally {
46326
+ setRejectCurrentPromptOnModelError(void 0);
46327
+ }
46328
+ }
46329
+ function modelErrorFromSessionEvent(event) {
46330
+ if (!isRecord$14(event) || event.type !== "message" || !isRecord$14(event.message)) return;
46331
+ const message = event.message;
46332
+ if (message.role !== "assistant" || message.stopReason !== "error") return;
46333
+ const errorMessage = typeof message.errorMessage === "string" && message.errorMessage.trim().length > 0 ? message.errorMessage.trim() : "model request failed";
46334
+ return /* @__PURE__ */ new Error(`Quire model request failed: ${errorMessage}`);
46335
+ }
46336
+ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed, intent, progress, onProgressEvent, runInstructions, remoteAddons, runtimeCapabilities, followup, skillPack, mcpGateway, onHandoff }) {
46202
46337
  const logAgentProgress = createAgentProgressLogger({
46203
46338
  runDir,
46204
46339
  write: progress
@@ -46228,6 +46363,7 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
46228
46363
  gateway: mcpGateway,
46229
46364
  runId: runDir.runId
46230
46365
  });
46366
+ const writeHandoffTool = makeWriteHandoffTool({ onHandoff });
46231
46367
  const registeredTargetTools = [...browserTool === void 0 ? [] : [{
46232
46368
  toolName: "agent-browser",
46233
46369
  target: "web"
@@ -48199,6 +48335,8 @@ function runPath(runDir) {
48199
48335
  }
48200
48336
  //#endregion
48201
48337
  //#region src/pipeline/background-investigation.ts
48338
+ const WORKER_BOOTSTRAP_TIMEOUT_MS = 2e3;
48339
+ const WORKER_LOG_FILE = "worker.log";
48202
48340
  async function startInvestigationRun(options) {
48203
48341
  assertHasInvestigationInput(options.query, options.inputText);
48204
48342
  const repoPath = resolve(options.cwd ?? process.cwd());
@@ -48254,6 +48392,8 @@ async function startInvestigationRun(options) {
48254
48392
  runId: runDir.runId,
48255
48393
  workspaceKey: runDir.workspaceKey,
48256
48394
  runPath: runDir.root,
48395
+ runsRoot: runDir.runsRoot,
48396
+ temporaryRunsRoot: runDir.temporaryRunsRoot === true,
48257
48397
  pid: worker.pid
48258
48398
  });
48259
48399
  }
@@ -48331,6 +48471,8 @@ function workerStartedBrowserSessionIds(runDir) {
48331
48471
  function spawnInvestigationWorker({ runPath }) {
48332
48472
  const entryPoint = process.argv[1];
48333
48473
  if (entryPoint === void 0 || entryPoint.length === 0) throw new CliError$1("Unable to locate Quire CLI entrypoint for background worker.", ExitCode.InternalError);
48474
+ const workerLogPath = `${runPath}/${WORKER_LOG_FILE}`;
48475
+ const workerLogFd = openSync(workerLogPath, "a", 384);
48334
48476
  const child = spawn(process.execPath, [
48335
48477
  ...process.execArgv,
48336
48478
  entryPoint,
@@ -48343,23 +48485,89 @@ function spawnInvestigationWorker({ runPath }) {
48343
48485
  env: process.env,
48344
48486
  stdio: [
48345
48487
  "ignore",
48346
- "ignore",
48347
- "ignore"
48488
+ workerLogFd,
48489
+ workerLogFd
48348
48490
  ]
48349
48491
  });
48350
- child.unref();
48351
- return Promise.resolve({ pid: child.pid });
48492
+ closeSync(workerLogFd);
48493
+ return waitForWorkerBootstrap({
48494
+ child,
48495
+ runPath,
48496
+ workerLogPath
48497
+ }).finally(() => {
48498
+ child.unref();
48499
+ });
48500
+ }
48501
+ async function waitForWorkerBootstrap({ child, runPath, workerLogPath }) {
48502
+ const exit = new Promise((resolveExit) => {
48503
+ child.once("exit", (code, signal) => resolveExit({
48504
+ code,
48505
+ signal
48506
+ }));
48507
+ });
48508
+ const timeout = new Promise((resolveTimeout) => {
48509
+ setTimeout(() => resolveTimeout("timeout"), WORKER_BOOTSTRAP_TIMEOUT_MS).unref();
48510
+ });
48511
+ const started = waitForRunToLeaveQueued(runPath);
48512
+ const result = await Promise.race([
48513
+ exit,
48514
+ timeout,
48515
+ started.promise
48516
+ ]);
48517
+ started.stop();
48518
+ if (result === "started") return { pid: child.pid };
48519
+ const status = readRunStatus(runPath);
48520
+ if (result !== "timeout" && status?.status === "queued") throw new CliError$1(formatEarlyWorkerExitError({
48521
+ workerLogPath,
48522
+ code: result.code,
48523
+ signal: result.signal
48524
+ }), ExitCode.InternalError);
48525
+ return { pid: child.pid };
48526
+ }
48527
+ function waitForRunToLeaveQueued(runPath) {
48528
+ let stopped = false;
48529
+ return {
48530
+ promise: new Promise((resolveStarted) => {
48531
+ const checkStatus = () => {
48532
+ if (stopped) return;
48533
+ const status = readRunStatus(runPath);
48534
+ if (status !== void 0 && status.status !== "queued") {
48535
+ resolveStarted("started");
48536
+ return;
48537
+ }
48538
+ setTimeout(checkStatus, 25).unref();
48539
+ };
48540
+ checkStatus();
48541
+ }),
48542
+ stop: () => {
48543
+ stopped = true;
48544
+ }
48545
+ };
48546
+ }
48547
+ function formatEarlyWorkerExitError({ workerLogPath, code, signal }) {
48548
+ const exit = signal === null ? `exit code ${code ?? "unknown"}` : `signal ${signal}`;
48549
+ const tail = readWorkerLogTail(workerLogPath);
48550
+ return `Worker process exited before marking the run as running (${exit}).\n\n${tail.length === 0 ? `Worker log: ${workerLogPath} (empty)` : `Worker log: ${workerLogPath}\n\nLast worker output:\n${tail}`}`;
48551
+ }
48552
+ function readWorkerLogTail(workerLogPath) {
48553
+ try {
48554
+ return readFileSync(workerLogPath, "utf8").slice(-12e3).trim();
48555
+ } catch {
48556
+ return "";
48557
+ }
48352
48558
  }
48353
48559
  function runHandle(input) {
48560
+ const commandPrefix = input.temporaryRunsRoot ? `QUIRE_RUNS_DIR=${shellQuote(input.runsRoot)} ` : "";
48354
48561
  return {
48355
48562
  status: "queued",
48356
48563
  runId: input.runId,
48357
48564
  workspaceKey: input.workspaceKey,
48358
48565
  runPath: toJsonPath(input.runPath),
48359
48566
  ...input.pid === void 0 ? {} : { pid: input.pid },
48360
- statusCommand: `quire status ${shellQuote(input.runId)} --json`,
48361
- watchCommand: `quire watch ${shellQuote(input.runId)}`,
48362
- cancelCommand: `quire cancel ${shellQuote(input.runId)}`
48567
+ statusCommand: `${commandPrefix}quire status ${shellQuote(input.runId)} --json`,
48568
+ waitCommand: `${commandPrefix}quire wait ${shellQuote(input.runId)} --json`,
48569
+ watchCommand: `${commandPrefix}quire watch ${shellQuote(input.runId)}`,
48570
+ cancelCommand: `${commandPrefix}quire cancel ${shellQuote(input.runId)}`
48363
48571
  };
48364
48572
  }
48365
48573
  function readWorkerRequest(runPath) {
@@ -48544,6 +48752,38 @@ const watchCommand = defineCommand({
48544
48752
  await watchRun(readRequiredString(args.run, "run"));
48545
48753
  }
48546
48754
  });
48755
+ const waitCommand = defineCommand({
48756
+ meta: {
48757
+ name: "wait",
48758
+ description: "Wait for a Quire investigation run to reach a terminal status."
48759
+ },
48760
+ args: {
48761
+ run: {
48762
+ type: "positional",
48763
+ description: "Run id or run directory.",
48764
+ required: true
48765
+ },
48766
+ json: {
48767
+ type: "boolean",
48768
+ description: "Emit terminal status JSON."
48769
+ },
48770
+ "timeout-ms": {
48771
+ type: "string",
48772
+ description: "Maximum time to wait in milliseconds. Defaults to 600000."
48773
+ },
48774
+ "interval-ms": {
48775
+ type: "string",
48776
+ description: "Polling interval in milliseconds. Defaults to 5000."
48777
+ }
48778
+ },
48779
+ async run({ args }) {
48780
+ await waitRun(readRequiredString(args.run, "run"), {
48781
+ json: args.json === true,
48782
+ timeoutMs: readPositiveIntegerOption(args["timeout-ms"], "--timeout-ms") ?? 6e5,
48783
+ intervalMs: readPositiveIntegerOption(args["interval-ms"], "--interval-ms") ?? 5e3
48784
+ });
48785
+ }
48786
+ });
48547
48787
  const cancelCommand = defineCommand({
48548
48788
  meta: {
48549
48789
  name: "cancel",
@@ -48669,6 +48909,28 @@ async function watchRun(run, options = {}) {
48669
48909
  if (sigintHandler !== void 0) process.off("SIGINT", sigintHandler);
48670
48910
  }
48671
48911
  }
48912
+ async function waitRun(run, options = {}) {
48913
+ const stdout = options.io?.stdout ?? process.stdout;
48914
+ const runPath = resolveRunPath(run, options);
48915
+ const sleep = options.sleep ?? defaultSleep$1;
48916
+ const intervalMs = options.intervalMs ?? 5e3;
48917
+ const timeoutMs = options.timeoutMs ?? 6e5;
48918
+ const now = options.now ?? Date.now;
48919
+ const deadline = now() + timeoutMs;
48920
+ while (true) {
48921
+ const status = withSyncStatus(readFreshRunStatus(runPath));
48922
+ if (isTerminalRunStatus(status.status)) {
48923
+ if (options.json === true) writeJson(stdout, status);
48924
+ else {
48925
+ writeLine(stdout, renderFinalStatus(status));
48926
+ writeHandoffSummary(stdout, runDirFromStatus(status).paths.handoff);
48927
+ }
48928
+ return status;
48929
+ }
48930
+ if (now() >= deadline) throw new CliError$1(`Timed out waiting for run ${status.runId} to finish; latest status is ${status.status}. Use \`quire status ${status.runId} --json\` or \`quire watch ${status.runId}\` to inspect progress.`, ExitCode.InvalidInput);
48931
+ await sleep(Math.min(intervalMs, Math.max(0, deadline - now())));
48932
+ }
48933
+ }
48672
48934
  async function cancelRun(run, options = {}) {
48673
48935
  const stdout = options.io?.stdout ?? process.stdout;
48674
48936
  const runPath = resolveRunPath(run, options);
@@ -48729,9 +48991,15 @@ async function syncRun(run, options = {}) {
48729
48991
  function withSyncStatus(status) {
48730
48992
  const sync = readInvestigationSyncStatus(runDirFromStatus(status));
48731
48993
  const { sync: _storedSync, ...baseStatus } = status;
48732
- return sync === void 0 ? baseStatus : {
48994
+ if (sync === void 0) return baseStatus;
48995
+ return sync.caseUrl === void 0 ? {
48733
48996
  ...baseStatus,
48734
48997
  sync
48998
+ } : {
48999
+ ...baseStatus,
49000
+ sync,
49001
+ webUrl: sync.caseUrl,
49002
+ caseUrl: sync.caseUrl
48735
49003
  };
48736
49004
  }
48737
49005
  function signalRunProcess(pid, signal) {
@@ -48754,6 +49022,13 @@ function statusColor(status) {
48754
49022
  function formatNumber(value) {
48755
49023
  return new Intl.NumberFormat("en-US").format(value);
48756
49024
  }
49025
+ function readPositiveIntegerOption(value, name) {
49026
+ if (value === void 0 || value === null || value === false) return;
49027
+ if (typeof value !== "string" || value.trim().length === 0) throw new CliError$1(`${name} must be a positive integer.`, ExitCode.InvalidInput);
49028
+ const parsed = Number(value);
49029
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new CliError$1(`${name} must be a positive integer.`, ExitCode.InvalidInput);
49030
+ return parsed;
49031
+ }
48757
49032
  function defaultSleep$1(ms) {
48758
49033
  return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
48759
49034
  }
@@ -48813,6 +49088,7 @@ const investigationCommandSchema = {
48813
49088
  "workspaceKey",
48814
49089
  "runPath",
48815
49090
  "statusCommand",
49091
+ "waitCommand",
48816
49092
  "watchCommand",
48817
49093
  "cancelCommand"
48818
49094
  ],
@@ -48826,6 +49102,7 @@ const investigationCommandSchema = {
48826
49102
  optional: true
48827
49103
  },
48828
49104
  statusCommand: { type: "string" },
49105
+ waitCommand: { type: "string" },
48829
49106
  watchCommand: { type: "string" },
48830
49107
  cancelCommand: { type: "string" }
48831
49108
  }
@@ -48833,6 +49110,7 @@ const investigationCommandSchema = {
48833
49110
  followUpCommands: {
48834
49111
  continue: "quire continue <run-id> \"additional context or instruction\"",
48835
49112
  status: "quire status <run-id> --json",
49113
+ wait: "quire wait <run-id> --json",
48836
49114
  watch: "quire watch <run-id>",
48837
49115
  cancel: "quire cancel <run-id>"
48838
49116
  },
@@ -48867,6 +49145,7 @@ const investigationCommandSchema = {
48867
49145
  "quire \"verify checkout before I approve this PR\"",
48868
49146
  "printf 'why is CI failing?\\nRelevant log: ...' | quire run --json",
48869
49147
  "quire status run_9x4mdq7p2h8kc6nv4apz --json",
49148
+ "quire wait run_9x4mdq7p2h8kc6nv4apz --json",
48870
49149
  "quire watch run_9x4mdq7p2h8kc6nv4apz"
48871
49150
  ]
48872
49151
  };
@@ -48954,7 +49233,9 @@ async function runInvestigate(options) {
48954
49233
  });
48955
49234
  if (options.detach === true) log.message([
48956
49235
  `${color.label("Run")}: ${color.path(handle.runPath)}`,
49236
+ ...handle.webUrl === void 0 ? [] : [`${color.label("Case")}: ${handle.webUrl}`],
48957
49237
  `${color.label("Status")}: ${color.command(handle.statusCommand)}`,
49238
+ `${color.label("Wait")}: ${color.command(handle.waitCommand)}`,
48958
49239
  `${color.label("Watch")}: ${color.command(handle.watchCommand)}`,
48959
49240
  `${color.label("Cancel")}: ${color.command(handle.cancelCommand)}`
48960
49241
  ], {
@@ -48965,15 +49246,16 @@ async function runInvestigate(options) {
48965
49246
  else log.message([
48966
49247
  `${color.label("Run")}: ${color.path(handle.runPath)}`,
48967
49248
  `${color.label("Status")}: ${color.command(handle.statusCommand)}`,
49249
+ `${color.label("Wait")}: ${color.command(handle.waitCommand)}`,
48968
49250
  `${color.label("Watch")}: ${color.command(handle.watchCommand)}`,
48969
- `This run continues in the background. You can safely exit; use ${color.command(handle.statusCommand)} for the current state or ${color.command(handle.watchCommand)} to follow progress logs.`
49251
+ `This run continues in the background. You can safely exit; use ${color.command(handle.waitCommand)} for the terminal handoff, ${color.command(handle.statusCommand)} for the current state, or ${color.command(handle.watchCommand)} to follow progress logs.`
48970
49252
  ], {
48971
49253
  output: stdout,
48972
49254
  spacing: 0,
48973
49255
  withGuide: true
48974
49256
  });
48975
49257
  }
48976
- if (options.json !== true && options.detach !== true) await (options.watchRunner ?? watchRun)(handle.runId, {
49258
+ if (options.json !== true && options.detach !== true) await (options.watchRunner ?? watchRun)(handle.runPath, {
48977
49259
  cwd: options.cwd,
48978
49260
  runsRoot: options.runsRoot,
48979
49261
  io: { stdout }
@@ -49114,11 +49396,13 @@ function pluralize(count, singular, plural) {
49114
49396
  //#region src/doctor/checks.ts
49115
49397
  const execFileAsync = promisify(execFile);
49116
49398
  function createDoctorContext(overrides = {}) {
49399
+ const env = overrides.env ?? process.env;
49117
49400
  return {
49118
49401
  cwd: resolve(overrides.cwd ?? process.cwd()),
49119
- runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot()),
49402
+ runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot(env)),
49403
+ runsRootExplicit: overrides.runsRootExplicit ?? (overrides.runsRoot !== void 0 || (env.QUIRE_RUNS_DIR?.trim().length ?? 0) > 0),
49120
49404
  authStore: overrides.authStore ?? authStore,
49121
- env: overrides.env ?? process.env,
49405
+ env,
49122
49406
  fetchFn: overrides.fetchFn ?? fetch,
49123
49407
  execFn: overrides.execFn ?? defaultExecFn,
49124
49408
  probeTimeoutMs: overrides.probeTimeoutMs ?? 2e3
@@ -49310,6 +49594,22 @@ async function checkRunsRoot(context) {
49310
49594
  message: `Runs root writable (${context.runsRoot}).`
49311
49595
  };
49312
49596
  } catch (error) {
49597
+ if (!context.runsRootExplicit && isWriteAccessError(error)) {
49598
+ const fallbackRunsRoot = await mkdtemp(join(tmpdir(), "quire-runs-"));
49599
+ await mkdir(fallbackRunsRoot, {
49600
+ recursive: true,
49601
+ mode: 448
49602
+ });
49603
+ await access(fallbackRunsRoot, constants.W_OK);
49604
+ context.runsRoot = fallbackRunsRoot;
49605
+ return {
49606
+ id: "runs.rootWritable",
49607
+ section: "runs",
49608
+ severity: "warn",
49609
+ message: `Default runs root not writable (${toMessage(error)}); using temporary runs root ${fallbackRunsRoot}.`,
49610
+ fix: { command: `Set QUIRE_RUNS_DIR=${fallbackRunsRoot} for follow-up Quire commands in this shell.` }
49611
+ };
49612
+ }
49313
49613
  return {
49314
49614
  id: "runs.rootWritable",
49315
49615
  section: "runs",
@@ -49354,6 +49654,9 @@ function checkStuckRuns(context) {
49354
49654
  };
49355
49655
  }
49356
49656
  }
49657
+ function isWriteAccessError(error) {
49658
+ return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
49659
+ }
49357
49660
  function formatUser$2(user) {
49358
49661
  if (user.email !== void 0 && user.email !== null && user.email.length > 0) return user.email;
49359
49662
  if (user.name !== void 0 && user.name !== null && user.name.length > 0) return user.name;
@@ -49664,6 +49967,7 @@ const logoutCommand = defineCommand({
49664
49967
  async function runLogout(options) {
49665
49968
  const store = options.store ?? authStore;
49666
49969
  const stdout = options.io?.stdout ?? process.stdout;
49970
+ const env = options.env ?? process.env;
49667
49971
  const existing = await store.get();
49668
49972
  await store.clear();
49669
49973
  if (existing === null) log.info("Already logged out.", {
@@ -49674,48 +49978,387 @@ async function runLogout(options) {
49674
49978
  output: stdout,
49675
49979
  spacing: 0
49676
49980
  });
49981
+ if (env["QUIRE_API_TOKEN"]?.trim()) log.warn(`${QUIRE_API_TOKEN_ENV} is still set, so Quire commands may still authenticate.`, {
49982
+ output: stdout,
49983
+ spacing: 0
49984
+ });
49677
49985
  }
49678
49986
  //#endregion
49679
49987
  //#region src/commands/setup.ts
49680
- const SETUP_URL = "https://quire.sh/agent-setup";
49681
- const VERIFICATION_SKILL_URL = "https://quire.sh/agent-verification";
49682
- const AGENT_SETUP_PROMPT = `Set up this repository for Quire investigations.
49988
+ const DEFAULT_PUBLIC_BASE_URL = "https://quire.sh";
49989
+ const SETUP_URL_PLACEHOLDER = "__QUIRE_SETUP_URL__";
49990
+ const VERIFICATION_SKILL_URL_PLACEHOLDER = "__QUIRE_VERIFICATION_SKILL_URL__";
49991
+ const AGENT_SETUP_PROMPT = `Help me connect Quire to this repository end-to-end.
49992
+
49993
+ Use or install the Quire CLI. If \`quire\` is unavailable, use \`npx @quireco/cli@latest\` for this setup or ask before installing it globally.
49994
+
49995
+ Load and follow the Quire setup skill from ${SETUP_URL_PLACEHOLDER}.
49996
+
49997
+ Check \`quire whoami --json\` and \`quire doctor --json\` using the same Quire command throughout setup. If account auth is missing, tell me exactly what I need to do, run \`quire login\` when browser auth is appropriate, and wait for me to finish. Do not ask me to paste secrets into chat.
49998
+
49999
+ After login, Quire Wallet is the default model source. If Wallet has balance, use it. If Wallet needs funds, show me the top-up action from \`quire whoami --json\` or open https://quire.sh/dashboard?settings=billing. If I prefer to use an existing subscription instead, help me connect ChatGPT/Codex with \`quire connect chatgpt\` or GitHub Copilot with \`quire connect copilot\`.
50000
+
50001
+ Inspect the project, ask focused questions for important missing runbook facts instead of writing placeholder unknowns, then create or update .quire/runbook.md with this repo's verification loop: target selection, local startup, browser/API/CLI/mobile checks, console/runtime signals, core flows, trusted commands, design/reference surfaces, auth/data setup, performance/accessibility expectations, and mutation-safety rules.
50002
+
50003
+ Update AGENTS.md with concise Quire notes. Rerun \`quire doctor\`, recover from sandbox write issues if needed, then start one small read-only smoke run with \`quire run --detach --json\`. Wait for a terminal result and report the synced Quire run link, local report/evidence path, and any blocker with the exact next action.`;
50004
+ const QUIRE_VERIFY_SKILL_MARKDOWN = `---
50005
+ name: verify
50006
+ description: Verify claims, code changes, bug fixes, workflows, and release readiness with independent evidence. Use when asked to verify, QA, smoke test, prove a fix, or check non-trivial work before completion.
50007
+ ---
50008
+
50009
+ # Verify
50010
+
50011
+ Load and follow the current Quire verification skill from ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
50012
+
50013
+ Use this skill to prove whether a claim, code change, bug fix, test result, workflow, or release/readiness assertion is actually correct. Quire is the independent read-only QA/verification agent for this workflow. Quire does not implement fixes. Quire reports pass, fail, blocked, or partial results honestly with evidence.
50014
+
50015
+ ## When to use this skill
49683
50016
 
49684
- Please load and follow the Quire agent setup instructions from ${SETUP_URL}.
50017
+ Use this skill when the user asks to verify, QA, smoke test, validate, prove a fix, check a workflow, confirm a release claim, ask Quire, run Quire, or get a Quire run id/verdict/handoff.
49685
50018
 
49686
- Inspect the project, ask focused questions for important missing runbook facts instead of writing placeholder unknowns, then create a .quire/runbook.md that encodes the best-practices verification loop for this specific project. Capture how Quire should self-verify work in this domain: target/environment choice, local startup, browser/API/CLI/mobile checks, console/runtime signals, core flows, verification commands, design/reference surfaces, auth/data setup, performance/accessibility expectations, and mutation-safety rules. Update AGENTS.md with concise Quire notes, then run Quire doctor using the available Quire command, e.g. \`quire doctor\` or \`npx @quireco/cli doctor\`, and report anything still blocked.`;
49687
- const QUIRE_SKILL_MARKDOWN = `---
50019
+ Also use Quire before declaring non-trivial coding work complete when independent verification would materially increase confidence, especially for UI/workflow changes, bug fixes, failing or flaky tests, auth, security, permissions, billing, data loss, migrations, concurrency, integrations, or release-readiness work.
50020
+
50021
+ Do not use Quire for every tiny task. Usually skip independent verification for purely conversational answers, simple docs/formatting edits, trivial mechanical changes where local checks are enough, or repeated verification of the same unchanged diff. Exception: if the user explicitly asks for Quire or independent verification, use Quire anyway.
50022
+
50023
+ ## Hard requirement
50024
+
50025
+ If the caller explicitly asks for Quire or independent verification, do not substitute your own direct checks, web fetches, curl commands, tests, browser checks, or manual reasoning and claim that is equivalent. You may do basic preparation, but Quire itself must be invoked.
50026
+
50027
+ Recommended path: if this harness exposes a native subagent named \`quire\`, delegate the verification brief to that subagent and wait for its result. Do not decide the task is simple enough to run \`quire\` directly from the parent session when a native \`quire\` subagent is available. Use CLI fallback only when no native \`quire\` subagent is available.
50028
+
50029
+ If the caller asks whether the Quire agent/subagent path was used, answer from your actual invocation path. If you used a native \`quire\` child task/subagent, report that the native Quire subagent path was used even when \`quire wait\` returns metadata for an older run whose internal harness was \`pi\`, Codex, or another agent. Treat old run metadata as separate historical context, not as evidence that the current adapter path was not used.
50030
+
50031
+ When communicating Quire results to the user, surface the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present. Include the report/handoff markdown summary and call out useful artifacts such as screenshots, recordings, traces, or other evidence when they help the user trust or review the result.
50032
+
50033
+ ## How to invoke Quire
50034
+
50035
+ Preferred order:
50036
+
50037
+ 1. If this harness exposes a native subagent named \`quire\`, delegate the verification brief to that subagent and wait for its result.
50038
+ 2. If no native Quire subagent is available, run the CLI directly.
50039
+
50040
+ CLI fallback:
50041
+
50042
+ \`\`\`bash
50043
+ run_json=$(quire run --detach --json --url "<url>" "<verification request>")
50044
+ run_id=$(printf '%s' "$run_json" | jq -r '.runId')
50045
+ quire wait "$run_id" --json
50046
+ \`\`\`
50047
+
50048
+ If the caller provides an existing Quire run id, do not start a new run and do not use \`quire status\` as the final result. Wait for and return the handoff with:
50049
+
50050
+ \`\`\`bash
50051
+ quire wait "<run-id>" --json
50052
+ \`\`\`
50053
+
50054
+ The JSON output includes \`webUrl\` / \`caseUrl\` when the run has synced to Quire's web app. Use that link as the shareable evidence URL in user-facing summaries.
50055
+
50056
+ Omit \`--url\` when there is no URL and include the target context in the natural-language request. Use \`quire wait\` as the waiting primitive. Do not manually poll unless \`quire wait\` is unavailable.
50057
+
50058
+ If Quire reports \`EROFS\`, \`EACCES\`, or another write error under \`~/.local/share/quire/runs\`, retry with a temporary run store and reuse the same value for every lifecycle command:
50059
+
50060
+ \`\`\`bash
50061
+ quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
50062
+ QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
50063
+ QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
50064
+ \`\`\`
50065
+
50066
+ ## Verification brief template
50067
+
50068
+ - Claim/change to verify:
50069
+ - Relevant files/paths:
50070
+ - Commands already run:
50071
+ - Expected behavior:
50072
+ - Known risks or edge cases:
50073
+ - Constraints: read-only verification; do not edit code.
50074
+
50075
+ Return Quire's terminal handoff with:
50076
+
50077
+ - Verdict: pass, fail, blocked, partial, or setup-blocked
50078
+ - Quire run id
50079
+ - Quire web URL when present
50080
+ - What Quire verified
50081
+ - Key evidence, artifacts, report, or handoff excerpt
50082
+ - Follow-up actions for the implementation agent
50083
+ `;
50084
+ const QUIRE_DEBUG_SKILL_MARKDOWN = `---
50085
+ name: debug
50086
+ description: Diagnose bugs, regressions, flaky behavior, and broken workflows with a reproduce-first loop. Use when asked to debug, diagnose, investigate a failure, reproduce a bug, or triage a regression.
50087
+ ---
50088
+
50089
+ # Debug
50090
+
50091
+ Use this skill for disciplined diagnosis: reproduce the failure, minimize the loop, form hypotheses, instrument only what distinguishes them, fix, and regression-test. Quire is the independent evidence collector for this workflow when a fresh reproduction, browser/API/mobile check, or external verification handoff would materially improve confidence.
50092
+
50093
+ ## When to use Quire during diagnosis
50094
+
50095
+ Use the native \`quire\` subagent when you need an independent read-only investigator to:
50096
+
50097
+ - reproduce a reported bug or flaky workflow;
50098
+ - verify a suspected fix against the real app, browser, API, CLI, or mobile surface;
50099
+ - gather evidence such as screenshots, recordings, console output, terminal logs, or a concise handoff;
50100
+ - check whether a failure is environment/setup-blocked versus a product bug;
50101
+ - produce a run id and terminal verdict that another agent can use in an implementation/verification loop.
50102
+
50103
+ Do not outsource implementation to Quire. Quire should not edit code. The parent/debugging agent owns hypotheses, code changes, and regression tests.
50104
+
50105
+ ## Recommended invocation
50106
+
50107
+ 1. Build or identify the best reproduction loop you can from the report.
50108
+ 2. If this harness exposes a native subagent named \`quire\`, delegate a concise reproduction or verification brief to that subagent and wait for its handoff.
50109
+ 3. If no native \`quire\` subagent is available, use the Quire CLI fallback from ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
50110
+ 4. Use Quire's result as evidence, not as a substitute for understanding the code path. When communicating the result, surface the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present, and mention useful report artifacts such as screenshots, recordings, traces, or the handoff markdown when they help the user review the diagnosis. If Quire reports blocked, preserve that honestly and fix the setup or ask for the missing environment.
50111
+
50112
+ ## Quire brief template
50113
+
50114
+ - Failure or regression to reproduce:
50115
+ - User-visible symptom:
50116
+ - Target URL, command, app, or environment:
50117
+ - Steps already tried:
50118
+ - Expected behavior:
50119
+ - Actual behavior:
50120
+ - Evidence to capture:
50121
+ - Constraints: read-only investigation; do not edit code.
50122
+
50123
+ ## CLI fallback
50124
+
50125
+ Use CLI fallback only if the native \`quire\` subagent is unavailable. Prefer detached JSON mode and wait for the terminal handoff:
50126
+
50127
+ \`\`\`bash
50128
+ run_json=$(quire run --detach --json --url "<url>" "<reproduction or verification request>")
50129
+ run_id=$(printf '%s' "$run_json" | jq -r '.runId')
50130
+ quire wait "$run_id" --json
50131
+ \`\`\`
50132
+
50133
+ If the caller provides an existing Quire run id, do not start a new run and do not use \`quire status\` as the final result:
50134
+
50135
+ \`\`\`bash
50136
+ quire wait "<run-id>" --json
50137
+ \`\`\`
50138
+
50139
+ For more CLI details, sandbox recovery, and handoff interpretation, follow ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
50140
+ `;
50141
+ const QUIRE_AGENT_MARKDOWN = `---
49688
50142
  name: quire
49689
- description: Runs Quire CLI evidence-backed QA, verification, triage, and investigation work. Use after implementing UI or workflow changes, when testing local/preview/prod URLs, or when claims need screenshots, command output, observations, and a trustworthy handoff.
50143
+ description: "Run Quire, the independent read-only QA/verification/triage agent. Use when the user explicitly asks for Quire or when independent verification of a non-trivial change, bug fix, test failure, or release claim is warranted. Returns pass, fail, blocked, or partial with evidence; never edits code."
50144
+ color: green
50145
+ model: inherit
49690
50146
  ---
49691
50147
 
49692
- # Quire verification
50148
+ You are Quire's native harness adapter. Your job is to launch Quire through the Quire CLI, wait for the result, and return the evidence-backed result to the parent agent.
49693
50149
 
49694
- Load and follow the current Quire verification skill from ${VERIFICATION_SKILL_URL}.
50150
+ Do not implement code changes. Do not commit, stage, format, rewrite, patch, or edit files. Do not perform broad independent QA outside Quire unless Quire cannot run and the caller explicitly asks for fallback evidence. Do not read or print secrets, tokens, .env files, or credential vaults. Treat production targets as read-only unless the caller explicitly approves mutation.
49695
50151
 
49696
- Use Quire when a task needs evidence-backed verification, QA, product dogfooding, bug reproduction, or triage. Prefer \`quire run\` for new work.
50152
+ When reporting whether the Quire agent/subagent path was used, answer only about this native adapter invocation. If you are running these instructions as the \`quire\` subagent, then the native Quire subagent path was used. Do not add a second "internal run harness" answer, do not discuss a previous run's \`harness: "pi"\` metadata, and do not answer "no" for the current adapter path.
50153
+
50154
+ ## Workflow
50155
+
50156
+ 1. Convert the parent request into a concise Quire verification brief.
50157
+ 2. Prefer the local \`quire\` command. Use the exact command style available in the workspace if the caller provides one. Do not downgrade an explicit Quire request to direct curl/browser/test checks, and do not report \`N/A\` for the run id because the claim looked small.
50158
+ 3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
50159
+ 4. Start one Quire run for the verification claim. Do not start duplicate runs for the same claim just because the first run is still active.
50160
+
50161
+ If the caller provides an existing Quire run id, do not start a new run. Use \`quire wait "<run-id>" --json\` and return its terminal handoff.
50162
+
50163
+ Prefer detached JSON mode for agent handoffs:
50164
+
50165
+ \`\`\`bash
50166
+ run_json=$(quire run --detach --json --url "<url>" "<verification request>")
50167
+ run_id=$(printf '%s' "$run_json" | jq -r '.runId')
50168
+ \`\`\`
50169
+
50170
+ If there is no URL, omit \`--url\` and put target context in the natural-language request.
50171
+
50172
+ 5. Wait patiently until terminal status:
50173
+
50174
+ \`\`\`bash
50175
+ quire wait "$run_id" --json
50176
+ \`\`\`
50177
+
50178
+ If Quire reports \`EROFS\`, \`EACCES\`, or another write error under \`~/.local/share/quire/runs\`, retry with a temporary run store and reuse the same value for every lifecycle command:
50179
+
50180
+ \`\`\`bash
50181
+ quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
50182
+ QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
50183
+ QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
50184
+ \`\`\`
50185
+
50186
+ Terminal statuses are \`completed\`, \`failed\`, \`canceled\`, and \`stale\`. Quire can take several minutes while it inspects context, opens browsers, runs shell checks, captures evidence, and writes a handoff.
50187
+
50188
+ 6. Return \`finalHandoff.handoffMarkdown\` from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present, and mention useful report artifacts such as screenshots, recordings, traces, or evidence references when they help the parent agent communicate the result.
50189
+ 7. If Quire reports blocked, failed, stale, or untested checks, preserve that honestly and include the next action. Do not summarize blocked or untested assertions as passed.
50190
+ 8. If Quire cannot start because auth, model provider, wallet, browser, mobile, or target setup is missing, report setup-blocked with the exact reason and the next command Quire suggested.
50191
+
50192
+ ## Return format
50193
+
50194
+ - Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
50195
+ - Verdict: pass, fail, blocked, partial, or setup-blocked
50196
+ - Quire run id
50197
+ - Quire web URL when present
50198
+ - What Quire verified
50199
+ - Key evidence, artifacts, report, or handoff excerpt
50200
+ - Follow-up actions for the implementation agent
49697
50201
  `;
49698
- const SKILL_INSTALL_TARGETS = [{
49699
- id: "agents",
49700
- label: "Universal",
49701
- targetPath: ".agents/skills/quire/SKILL.md",
49702
- hint: "~/.agents/skills/quire/SKILL.md · Pi, OpenCode, Amp, Codex-style agents",
49703
- aliases: [
49704
- "agents",
49705
- "agent-skills",
49706
- "pi",
49707
- "opencode",
49708
- "amp",
49709
- "codex",
49710
- "other"
49711
- ]
49712
- }, {
49713
- id: "claude",
49714
- label: "Claude Code",
49715
- targetPath: ".claude/skills/quire/SKILL.md",
49716
- hint: "~/.claude/skills/quire/SKILL.md",
49717
- aliases: ["claude", "claude-code"]
49718
- }];
50202
+ const QUIRE_CURSOR_MARKDOWN = QUIRE_AGENT_MARKDOWN.replace("model: inherit\n---", "model: inherit\nreadonly: true\n---");
50203
+ const QUIRE_CODEX_TOML = `name = "quire"
50204
+ description = "Run Quire, the independent read-only QA/verification/triage agent. Use when explicitly asked for Quire or when independent verification of a non-trivial change, bug fix, test failure, or release claim is warranted. Returns pass, fail, blocked, or partial with evidence; never edits code."
50205
+
50206
+ developer_instructions = """
50207
+ You are Quire's native harness adapter. Your job is to launch Quire through the Quire CLI, wait for the result, and return the evidence-backed result to the parent agent.
50208
+
50209
+ Do not implement code changes. Do not commit, stage, format, rewrite, patch, or edit files. Do not perform broad independent QA outside Quire unless Quire cannot run and the caller explicitly asks for fallback evidence. Do not read or print secrets, tokens, .env files, or credential vaults. Treat production targets as read-only unless the caller explicitly approves mutation.
50210
+
50211
+ When reporting whether the Quire agent/subagent path was used, answer only about this native adapter invocation. If you are running these instructions as the \`quire\` subagent, then the native Quire subagent path was used. Do not add a second "internal run harness" answer, do not discuss a previous run's \`harness: "pi"\` metadata, and do not answer "no" for the current adapter path.
50212
+
50213
+ Workflow:
50214
+ 1. Convert the parent request into a concise Quire verification brief.
50215
+ 2. Prefer the local \`quire\` command. Use the exact command style available in the workspace if the caller provides one. Do not downgrade an explicit Quire request to direct curl/browser/test checks, and do not report \`N/A\` for the run id because the claim looked small.
50216
+ 3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
50217
+ 4. Start one Quire run for the verification claim. Do not start duplicate runs for the same claim just because the first run is still active.
50218
+
50219
+ If the caller provides an existing Quire run id, do not start a new run. Use \`quire wait "<run-id>" --json\` and return its terminal handoff.
50220
+
50221
+ run_json=$(quire run --detach --json --url "<url>" "<verification request>")
50222
+ run_id=$(printf '%s' "$run_json" | jq -r '.runId')
50223
+ quire wait "$run_id" --json
50224
+
50225
+ 5. If Quire reports EROFS, EACCES, or another write error under ~/.local/share/quire/runs, retry with a temporary run store and reuse the same value for every lifecycle command:
50226
+
50227
+ quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
50228
+ QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
50229
+ QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
50230
+
50231
+ 6. Return finalHandoff.handoffMarkdown from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present, and mention useful report artifacts such as screenshots, recordings, traces, or evidence references when they help the parent agent communicate the result.
50232
+ 7. If Quire reports blocked, failed, stale, or untested checks, preserve that honestly and include the next action. Do not summarize blocked or untested assertions as passed.
50233
+
50234
+ Return:
50235
+ - Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
50236
+ - Verdict: pass, fail, blocked, partial, or setup-blocked
50237
+ - Quire run id
50238
+ - Quire web URL when present
50239
+ - What Quire verified
50240
+ - Key evidence, artifacts, report, or handoff excerpt
50241
+ - Follow-up actions for the implementation agent
50242
+ """
50243
+ `;
50244
+ const QUIRE_OPENCODE_MARKDOWN = `---
50245
+ description: Native @quire subagent. If the user mentions @quire, the parent must invoke the Task tool's quire subagent and must not run Bash/CLI directly in the parent session. For existing run IDs, this subagent uses quire wait <run-id> --json, not quire status. If asked whether the agent/subagent path was used, answer yes for this native child invocation; old run harness metadata is separate historical context.
50246
+ mode: subagent
50247
+ permission:
50248
+ edit: deny
50249
+ ---
50250
+
50251
+ You are Quire's native harness adapter. Your job is to launch Quire through the Quire CLI, wait for the result, and return the evidence-backed result to the parent agent.
50252
+
50253
+ Do not implement code changes. Do not commit, stage, format, rewrite, patch, or edit files. Do not perform broad independent QA outside Quire unless Quire cannot run and the caller explicitly asks for fallback evidence. Do not read or print secrets, tokens, .env files, or credential vaults. Treat production targets as read-only unless the caller explicitly approves mutation.
50254
+
50255
+ When reporting whether the Quire agent/subagent path was used, answer only about this native adapter invocation. If you are running these instructions as the \`quire\` subagent, then the native Quire subagent path was used. Do not add a second "internal run harness" answer, do not discuss a previous run's \`harness: "pi"\` metadata, and do not answer "no" for the current adapter path.
50256
+
50257
+ ## Workflow
50258
+
50259
+ 1. Convert the parent request into a concise Quire verification brief.
50260
+ 2. Prefer the local \`quire\` command. Use the exact command style available in the workspace if the caller provides one. Do not downgrade an explicit Quire request to direct curl/browser/test checks, and do not report \`N/A\` for the run id because the claim looked small.
50261
+ 3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
50262
+ 4. Start one Quire run for the verification claim. Do not start duplicate runs for the same claim just because the first run is still active.
50263
+
50264
+ If the caller provides an existing Quire run id, do not start a new run. Use \`quire wait "<run-id>" --json\` and return its terminal handoff.
50265
+
50266
+ \`\`\`bash
50267
+ run_json=$(quire run --detach --json --url "<url>" "<verification request>")
50268
+ run_id=$(printf '%s' "$run_json" | jq -r '.runId')
50269
+ quire wait "$run_id" --json
50270
+ \`\`\`
50271
+
50272
+ 5. If Quire reports \`EROFS\`, \`EACCES\`, or another write error under \`~/.local/share/quire/runs\`, retry with a temporary run store and reuse the same value for every lifecycle command:
50273
+
50274
+ \`\`\`bash
50275
+ quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
50276
+ QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
50277
+ QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
50278
+ \`\`\`
50279
+
50280
+ 6. Return \`finalHandoff.handoffMarkdown\` from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present, and mention useful report artifacts such as screenshots, recordings, traces, or evidence references when they help the parent agent communicate the result.
50281
+ 7. If Quire reports blocked, failed, stale, or untested checks, preserve that honestly and include the next action. Do not summarize blocked or untested assertions as passed.
50282
+
50283
+ ## Return format
50284
+
50285
+ - Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
50286
+ - Verdict: pass, fail, blocked, partial, or setup-blocked
50287
+ - Quire run id
50288
+ - Quire web URL when present
50289
+ - What Quire verified
50290
+ - Key evidence, artifacts, report, or handoff excerpt
50291
+ - Follow-up actions for the implementation agent
50292
+ `;
50293
+ const SKILL_INSTALL_TARGETS = [
50294
+ {
50295
+ id: "agents",
50296
+ label: "Universal",
50297
+ skillPaths: [{
50298
+ path: ".agents/skills/verify/SKILL.md",
50299
+ contents: QUIRE_VERIFY_SKILL_MARKDOWN
50300
+ }, {
50301
+ path: ".agents/skills/debug/SKILL.md",
50302
+ contents: QUIRE_DEBUG_SKILL_MARKDOWN
50303
+ }],
50304
+ agentPath: ".agents/agents/quire.md",
50305
+ codexAgentPath: ".codex/agents/quire.toml",
50306
+ opencodeAgentPath: ".config/opencode/agents/quire.md",
50307
+ piAgentPath: ".pi/agent/agents/quire.md",
50308
+ hint: "~/.agents/skills/{verify,debug}/SKILL.md · plus Codex/OpenCode/Pi Quire subagents",
50309
+ aliases: [
50310
+ "agents",
50311
+ "agent-skills",
50312
+ "pi",
50313
+ "opencode",
50314
+ "amp",
50315
+ "codex",
50316
+ "other"
50317
+ ]
50318
+ },
50319
+ {
50320
+ id: "claude",
50321
+ label: "Claude Code",
50322
+ skillPaths: [{
50323
+ path: ".claude/skills/verify/SKILL.md",
50324
+ contents: QUIRE_VERIFY_SKILL_MARKDOWN
50325
+ }, {
50326
+ path: ".claude/skills/debug/SKILL.md",
50327
+ contents: QUIRE_DEBUG_SKILL_MARKDOWN
50328
+ }],
50329
+ agentPath: ".claude/agents/quire.md",
50330
+ hint: "~/.claude/skills/{verify,debug}/SKILL.md · plus native Quire subagent",
50331
+ aliases: ["claude", "claude-code"]
50332
+ },
50333
+ {
50334
+ id: "cursor",
50335
+ label: "Cursor",
50336
+ skillPaths: [{
50337
+ path: ".cursor/skills/verify/SKILL.md",
50338
+ contents: QUIRE_VERIFY_SKILL_MARKDOWN
50339
+ }, {
50340
+ path: ".cursor/skills/debug/SKILL.md",
50341
+ contents: QUIRE_DEBUG_SKILL_MARKDOWN
50342
+ }],
50343
+ agentPath: ".cursor/agents/quire.md",
50344
+ agentContents: QUIRE_CURSOR_MARKDOWN,
50345
+ hint: "~/.cursor/skills/{verify,debug}/SKILL.md · plus native Quire subagent",
50346
+ aliases: ["cursor", "cursor-cli"]
50347
+ }
50348
+ ];
50349
+ const LEGACY_QUIRE_VERIFIER_PATHS = [
50350
+ ".agents/agents/quire-verifier.md",
50351
+ ".agents/skills/quire-verifier/SKILL.md",
50352
+ ".claude/agents/quire-verifier.md",
50353
+ ".codex/agents/quire-verifier.toml",
50354
+ ".cursor/agents/quire-verifier.md",
50355
+ ".config/opencode/agents/quire-verifier.md"
50356
+ ];
50357
+ const LEGACY_QUIRE_SKILL_PATHS = [
50358
+ ".agents/skills/quire/SKILL.md",
50359
+ ".claude/skills/quire/SKILL.md",
50360
+ ".cursor/skills/quire/SKILL.md"
50361
+ ];
49719
50362
  const setupCommand = defineCommand({
49720
50363
  meta: {
49721
50364
  name: "setup",
@@ -49724,7 +50367,7 @@ const setupCommand = defineCommand({
49724
50367
  args: {
49725
50368
  skill: {
49726
50369
  type: "string",
49727
- description: "Comma-separated local agent skill targets to install: agents, claude, all, none.",
50370
+ description: "Comma-separated local agent skill targets to install: agents, claude, cursor, all, none.",
49728
50371
  valueHint: "targets"
49729
50372
  },
49730
50373
  "no-skill": {
@@ -49745,29 +50388,66 @@ const setupCommand = defineCommand({
49745
50388
  });
49746
50389
  async function runSetup(options = {}) {
49747
50390
  const stdout = options.io?.stdout ?? process.stdout;
50391
+ const env = options.env ?? process.env;
50392
+ const interactive = options.interactive ?? (options.io?.stdout === void 0 && process.stdout.isTTY === true);
50393
+ const authStore$1 = options.authStore ?? authStore;
50394
+ const urls = setupUrls(env);
49748
50395
  const selectedTargets = await selectSkillInstallTargets({
49749
50396
  skill: options.skill,
49750
- interactive: options.interactive ?? (options.io?.stdout === void 0 && process.stdout.isTTY === true)
50397
+ interactive
49751
50398
  });
49752
- const installedSkills = await installQuireSkill(options.homeDir ?? homedir(), selectedTargets);
50399
+ const installedAgentFiles = await installQuireAgentFiles(options.homeDir ?? homedir(), selectedTargets, urls);
49753
50400
  log.step("Set up Quire for this workspace", {
49754
50401
  output: stdout,
49755
50402
  spacing: 0
49756
50403
  });
49757
- log.message([`Open ${color.info(SETUP_URL)} to load the Quire setup skill, or copy this prompt into your coding agent like Claude Code, Codex, Pi, or Amp:`], {
50404
+ log.message([`Open ${color.info(urls.setupUrl)} to load the Quire setup skill, or copy this prompt into your coding agent like Claude Code, Cursor, Codex, Pi, or Amp:`], {
49758
50405
  output: stdout,
49759
50406
  spacing: 0,
49760
50407
  withGuide: true
49761
50408
  });
49762
50409
  writeLine(stdout);
49763
50410
  writeLine(stdout, color.label("--- copy prompt ---"));
49764
- writeLine(stdout, AGENT_SETUP_PROMPT);
50411
+ writeLine(stdout, renderSetupTemplate(AGENT_SETUP_PROMPT, urls));
49765
50412
  writeLine(stdout, color.label("--- end prompt ---"));
49766
- if (installedSkills.length > 0) {
50413
+ if (installedAgentFiles.length > 0) {
49767
50414
  writeLine(stdout);
49768
- writeLine(stdout, color.label("Installed local Quire skill:"));
49769
- for (const path of installedSkills) writeLine(stdout, ` ${color.success("✓")} ${path}`);
50415
+ writeLine(stdout, color.label("Installed local Quire agent files:"));
50416
+ for (const path of installedAgentFiles) writeLine(stdout, ` ${color.success("✓")} ${path}`);
50417
+ }
50418
+ const authConfigured = await completeInteractiveLogin({
50419
+ interactive,
50420
+ env,
50421
+ store: authStore$1,
50422
+ login: options.login ?? runLogin,
50423
+ io: options.io
50424
+ });
50425
+ writeLine(stdout);
50426
+ writeLine(stdout, color.label("Next steps:"));
50427
+ writeLine(stdout, ` 1. Give the copy prompt above to your coding agent so it creates ${color.path(".quire/runbook.md")}.`);
50428
+ if (authConfigured) writeLine(stdout, ` 2. Run ${color.command("quire doctor")} to confirm this repo is ready.`);
50429
+ else writeLine(stdout, ` 2. Authenticate Quire with ${color.command("quire login")} or ${color.command("QUIRE_API_TOKEN")} if this machine is not already signed in.`);
50430
+ writeLine(stdout, ` 3. Ask your coding agent to run a small Quire smoke verification once ${color.path(".quire/runbook.md")} and auth are ready.`);
50431
+ }
50432
+ async function completeInteractiveLogin(options) {
50433
+ if (!options.interactive) return false;
50434
+ const stdout = options.io?.stdout ?? process.stdout;
50435
+ if (await resolveAuthCredentials({
50436
+ store: options.store,
50437
+ env: options.env
50438
+ }) !== null) {
50439
+ log.success("Quire authentication is already configured.", {
50440
+ output: stdout,
50441
+ spacing: 0
50442
+ });
50443
+ return true;
49770
50444
  }
50445
+ await options.login({
50446
+ apiBaseUrl: readApiBaseUrl(options.env.QUIRE_API_URL),
50447
+ store: options.store,
50448
+ io: options.io
50449
+ });
50450
+ return true;
49771
50451
  }
49772
50452
  async function selectSkillInstallTargets(options) {
49773
50453
  if (options.skill === false || options.skill === "none") return [];
@@ -49780,7 +50460,11 @@ async function selectSkillInstallTargets(options) {
49780
50460
  value: target.id,
49781
50461
  hint: target.hint
49782
50462
  })),
49783
- initialValues: ["agents", "claude"],
50463
+ initialValues: [
50464
+ "agents",
50465
+ "claude",
50466
+ "cursor"
50467
+ ],
49784
50468
  required: false
49785
50469
  });
49786
50470
  if (isCancel(selected) || selected.length === 0) return [];
@@ -49796,19 +50480,121 @@ function resolveSkillTargets(value) {
49796
50480
  }
49797
50481
  return [...selected.values()];
49798
50482
  }
49799
- async function installQuireSkill(homeDir, targets) {
50483
+ function setupUrls(env) {
50484
+ const baseUrl = publicBaseUrl(env);
50485
+ return {
50486
+ setupUrl: new URL("/agent-setup", baseUrl).toString(),
50487
+ verificationSkillUrl: new URL("/agent-verification", baseUrl).toString()
50488
+ };
50489
+ }
50490
+ function publicBaseUrl(env) {
50491
+ const configured = env.QUIRE_SKILL_BASE_URL ?? env.QUIRE_PUBLIC_URL ?? env.QUIRE_API_URL;
50492
+ if (configured === void 0 || configured.trim().length === 0) return DEFAULT_PUBLIC_BASE_URL;
50493
+ return configured;
50494
+ }
50495
+ function renderSetupTemplate(contents, urls) {
50496
+ return contents.replaceAll(SETUP_URL_PLACEHOLDER, urls.setupUrl).replaceAll(VERIFICATION_SKILL_URL_PLACEHOLDER, urls.verificationSkillUrl);
50497
+ }
50498
+ async function installQuireAgentFiles(homeDir, targets, urls) {
49800
50499
  const installed = [];
50500
+ if (targets.length === 0) return installed;
50501
+ await removeLegacyQuireVerifierFiles(homeDir);
50502
+ await removeLegacyQuireSkillFiles(homeDir);
49801
50503
  await Promise.all(targets.map(async (target) => {
49802
- const path = join(homeDir, target.targetPath);
49803
- await mkdir(dirname(path), {
50504
+ for (const skill of target.skillPaths) {
50505
+ const skillPath = join(homeDir, skill.path);
50506
+ await mkdir(dirname(skillPath), {
50507
+ recursive: true,
50508
+ mode: 448
50509
+ });
50510
+ await writeFile(skillPath, renderSetupTemplate(skill.contents, urls), { mode: 384 });
50511
+ installed.push(skillPath);
50512
+ }
50513
+ const agentPath = join(homeDir, target.agentPath);
50514
+ await mkdir(dirname(agentPath), {
49804
50515
  recursive: true,
49805
50516
  mode: 448
49806
50517
  });
49807
- await writeFile(path, QUIRE_SKILL_MARKDOWN, { mode: 384 });
49808
- installed.push(path);
50518
+ await writeFile(agentPath, renderSetupTemplate(target.agentContents ?? QUIRE_AGENT_MARKDOWN, urls), { mode: 384 });
50519
+ installed.push(agentPath);
50520
+ if (target.codexAgentPath !== void 0) {
50521
+ const codexAgentPath = join(homeDir, target.codexAgentPath);
50522
+ await mkdir(dirname(codexAgentPath), {
50523
+ recursive: true,
50524
+ mode: 448
50525
+ });
50526
+ await writeFile(codexAgentPath, renderSetupTemplate(QUIRE_CODEX_TOML, urls), { mode: 384 });
50527
+ installed.push(codexAgentPath);
50528
+ }
50529
+ if (target.opencodeAgentPath !== void 0) {
50530
+ const opencodeAgentPath = join(homeDir, target.opencodeAgentPath);
50531
+ await mkdir(dirname(opencodeAgentPath), {
50532
+ recursive: true,
50533
+ mode: 448
50534
+ });
50535
+ await writeFile(opencodeAgentPath, renderSetupTemplate(QUIRE_OPENCODE_MARKDOWN, urls), { mode: 384 });
50536
+ installed.push(opencodeAgentPath);
50537
+ }
50538
+ if (target.piAgentPath !== void 0) {
50539
+ const piAgentPath = join(homeDir, target.piAgentPath);
50540
+ await mkdir(dirname(piAgentPath), {
50541
+ recursive: true,
50542
+ mode: 448
50543
+ });
50544
+ await writeFile(piAgentPath, renderSetupTemplate(QUIRE_AGENT_MARKDOWN, urls), { mode: 384 });
50545
+ installed.push(piAgentPath);
50546
+ }
49809
50547
  }));
49810
50548
  return installed.sort();
49811
50549
  }
50550
+ async function removeLegacyQuireVerifierFiles(homeDir) {
50551
+ await Promise.all(LEGACY_QUIRE_VERIFIER_PATHS.map(async (legacyPath) => {
50552
+ const path = join(homeDir, legacyPath);
50553
+ try {
50554
+ if (isLegacyQuireVerifierFile(await readFile(path, "utf8"))) {
50555
+ await rm(path, { force: true });
50556
+ await removeEmptyDirectory(dirname(path));
50557
+ }
50558
+ } catch (error) {
50559
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
50560
+ await removeEmptyDirectory(dirname(path));
50561
+ return;
50562
+ }
50563
+ throw error;
50564
+ }
50565
+ }));
50566
+ }
50567
+ async function removeLegacyQuireSkillFiles(homeDir) {
50568
+ await Promise.all(LEGACY_QUIRE_SKILL_PATHS.map(async (legacyPath) => {
50569
+ const path = join(homeDir, legacyPath);
50570
+ try {
50571
+ if (isLegacyQuireSkillFile(await readFile(path, "utf8"))) {
50572
+ await rm(path, { force: true });
50573
+ await removeEmptyDirectory(dirname(path));
50574
+ }
50575
+ } catch (error) {
50576
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
50577
+ await removeEmptyDirectory(dirname(path));
50578
+ return;
50579
+ }
50580
+ throw error;
50581
+ }
50582
+ }));
50583
+ }
50584
+ async function removeEmptyDirectory(path) {
50585
+ try {
50586
+ await rmdir(path);
50587
+ } catch (error) {
50588
+ if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTEMPTY" || error.code === "EEXIST" || error.code === "EISDIR")) return;
50589
+ throw error;
50590
+ }
50591
+ }
50592
+ function isLegacyQuireVerifierFile(contents) {
50593
+ return contents.includes("Quire") && contents.includes("quire wait") && (contents.includes("quire-verifier") || contents.includes("Quire verifier"));
50594
+ }
50595
+ function isLegacyQuireSkillFile(contents) {
50596
+ return contents.includes("name: quire") && contents.includes("/agent-verification") && contents.includes("native subagent named `quire`");
50597
+ }
49812
50598
  //#endregion
49813
50599
  //#region src/commands/whoami.ts
49814
50600
  const whoamiCommand = defineCommand({
@@ -49959,7 +50745,7 @@ function formatWalletBalance(value) {
49959
50745
  }
49960
50746
  //#endregion
49961
50747
  //#region package.json
49962
- var version$1 = "0.0.10";
50748
+ var version$1 = "0.0.12";
49963
50749
  //#endregion
49964
50750
  //#region src/cli.ts
49965
50751
  const subCommands = {
@@ -49990,6 +50776,7 @@ const subCommands = {
49990
50776
  setup: setupCommand,
49991
50777
  status: statusCommand,
49992
50778
  sync: syncCommand,
50779
+ wait: waitCommand,
49993
50780
  watch: watchCommand
49994
50781
  };
49995
50782
  const subCommandAliases = new Set(["me", "resume"]);