@quireco/cli 0.0.11 → 0.0.13

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";
@@ -671,8 +671,17 @@ async function createInvestigationModelSessionDependencies({ runId, store = auth
671
671
  fetchFn
672
672
  });
673
673
  }
674
- async function createLocalModelSessionDependencies(localAuthStorage) {
675
- const localRegistries = (localAuthStorage === void 0 ? [AuthStorage.create(readQuireModelAuthPath()), AuthStorage.create()] : [localAuthStorage]).map((authStorage) => ({
674
+ async function detectLocalInvestigationModelSource(options = {}) {
675
+ const localModel = await createLocalModelSessionDependencies(options.localAuthStorage, options.env);
676
+ if (localModel === null || localModel.source === "quire_credits") return null;
677
+ return {
678
+ source: localModel.source,
679
+ provider: localModel.model.provider,
680
+ modelId: localModel.model.id
681
+ };
682
+ }
683
+ async function createLocalModelSessionDependencies(localAuthStorage, env = process.env) {
684
+ const localRegistries = (localAuthStorage === void 0 ? [AuthStorage.create(readQuireModelAuthPath(env)), AuthStorage.create()] : [localAuthStorage]).map((authStorage) => ({
676
685
  authStorage,
677
686
  modelRegistry: ModelRegistry.create(authStorage)
678
687
  }));
@@ -690,8 +699,8 @@ async function createLocalModelSessionDependencies(localAuthStorage) {
690
699
  }
691
700
  return null;
692
701
  }
693
- function readQuireModelAuthPath() {
694
- return defaultModelAuthPath();
702
+ function readQuireModelAuthPath(env = process.env) {
703
+ return defaultModelAuthPath(env);
695
704
  }
696
705
  function quireModelDefinition() {
697
706
  return {
@@ -1375,21 +1384,28 @@ const FINDING_RELATIONSHIPS_TO_REQUEST = [
1375
1384
  ];
1376
1385
  function createRunDir(repoPath, options = {}) {
1377
1386
  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) : {
1387
+ const { runsRoot, runId, root, temporaryRunsRoot } = allocateRunRoot({
1388
+ runsRoot: resolve(options.runsRoot ?? defaultRunsRoot()),
1389
+ workspaceKey,
1380
1390
  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
1391
+ runIdFactory: options.createRunId ?? createRunId,
1392
+ allowTemporaryFallback: options.runsRoot === void 0 && (process.env.QUIRE_RUNS_DIR?.trim().length ?? 0) === 0
1387
1393
  });
1394
+ const paths = runDirPaths(root);
1395
+ try {
1396
+ mkdirSync(paths.evidence, {
1397
+ recursive: true,
1398
+ mode: 448
1399
+ });
1400
+ } catch (error) {
1401
+ throw writableRunsRootError(error, runsRoot);
1402
+ }
1388
1403
  return {
1389
1404
  runId,
1390
1405
  workspaceKey,
1391
1406
  runsRoot,
1392
1407
  root,
1408
+ ...temporaryRunsRoot ? { temporaryRunsRoot: true } : {},
1393
1409
  paths
1394
1410
  };
1395
1411
  }
@@ -1416,6 +1432,9 @@ function runPlanPath(runPath) {
1416
1432
  function runHandoffPath(runPath) {
1417
1433
  return join(resolve(runPath), "handoff.md");
1418
1434
  }
1435
+ function runPendingHandoffPath(runPath) {
1436
+ return join(resolve(runPath), ".pending-handoff.json");
1437
+ }
1419
1438
  function runDirPaths(root) {
1420
1439
  const evidence = join(root, "evidence");
1421
1440
  return {
@@ -1432,6 +1451,51 @@ function runDirPaths(root) {
1432
1451
  harnessSessions: join(root, ".harness-sessions")
1433
1452
  };
1434
1453
  }
1454
+ function allocateRunRoot(input) {
1455
+ try {
1456
+ return {
1457
+ ...createRunRoot(input.runsRoot, input.workspaceKey, input.runId, input.runIdFactory),
1458
+ runsRoot: input.runsRoot,
1459
+ temporaryRunsRoot: false
1460
+ };
1461
+ } catch (error) {
1462
+ if (!isWriteAccessError$2(error)) throw error;
1463
+ if (!input.allowTemporaryFallback) throw writableRunsRootError(error, input.runsRoot);
1464
+ const runsRoot = mkdtempSync(join(tmpdir(), "quire-runs-"));
1465
+ return {
1466
+ ...createRunRoot(runsRoot, input.workspaceKey, input.runId, input.runIdFactory),
1467
+ runsRoot,
1468
+ temporaryRunsRoot: true
1469
+ };
1470
+ }
1471
+ }
1472
+ function createRunRoot(runsRoot, workspaceKey, runId, runIdFactory) {
1473
+ return runId === void 0 ? createUniqueRunRoot(runsRoot, workspaceKey, runIdFactory) : createNamedRunRoot(runsRoot, workspaceKey, runId);
1474
+ }
1475
+ function createNamedRunRoot(runsRoot, workspaceKey, runId) {
1476
+ const root = join(runsRoot, workspaceKey, runId);
1477
+ mkdirSync(join(root, "evidence"), {
1478
+ recursive: true,
1479
+ mode: 448
1480
+ });
1481
+ return {
1482
+ runId,
1483
+ root
1484
+ };
1485
+ }
1486
+ function writableRunsRootError(error, runsRoot) {
1487
+ if (!isWriteAccessError$2(error)) return error;
1488
+ return new CliError$1([
1489
+ `Quire could not write run data at ${runsRoot}: ${error.message}`,
1490
+ "In sandboxed coding-agent environments, retry with a temporary run directory:",
1491
+ " quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)",
1492
+ " QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json <your request>",
1493
+ "Use the same QUIRE_RUNS_DIR value for follow-up `quire wait`, `quire status`, `quire watch`, or `quire cancel` commands."
1494
+ ].join("\n"), ExitCode.InvalidInput);
1495
+ }
1496
+ function isWriteAccessError$2(error) {
1497
+ return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
1498
+ }
1435
1499
  function defaultRunsRoot(env = process.env) {
1436
1500
  return defaultRunsRoot$1(env);
1437
1501
  }
@@ -1483,6 +1547,20 @@ function writeRunFinalHandoff(runDir, final) {
1483
1547
  finalResult: final.result
1484
1548
  });
1485
1549
  }
1550
+ function writeRunPendingHandoff(runDir, handoff) {
1551
+ const writtenAt = (/* @__PURE__ */ new Date()).toISOString();
1552
+ writeFileAtomically(runPendingHandoffPath(runDir.root), `${stableJson({
1553
+ version: 1,
1554
+ writtenAt,
1555
+ handoff
1556
+ })}\n`, 384);
1557
+ writeFileAtomically(runDir.paths.handoff, `${handoff.handoffMarkdown.trimEnd()}\n`, 384);
1558
+ }
1559
+ function clearRunPendingHandoff(runDir) {
1560
+ try {
1561
+ unlinkSync(runPendingHandoffPath(runDir.root));
1562
+ } catch {}
1563
+ }
1486
1564
  function patchRunStatusJson(runDir, patch) {
1487
1565
  if (!existsSync(runDir.paths.status)) return;
1488
1566
  let existing;
@@ -1752,18 +1830,32 @@ function readFreshRunStatus(runPath) {
1752
1830
  if (existsSync(runHandoffPath(runPath))) return updateRunStatus(runPath, { status: "completed" });
1753
1831
  return updateRunStatus(runPath, {
1754
1832
  status: "stale",
1755
- error: "Worker process exited before marking the run as running."
1833
+ error: staleWorkerError(runPath, "Worker process exited before marking the run as running.")
1756
1834
  });
1757
1835
  }
1758
1836
  if ((status.status === "running" || status.status === "canceling") && !isPidAlive(status.pid)) {
1759
1837
  if (existsSync(runHandoffPath(runPath))) return updateRunStatus(runPath, { status: "completed" });
1760
1838
  return updateRunStatus(runPath, {
1761
1839
  status: status.status === "canceling" ? "canceled" : "stale",
1762
- error: status.status === "canceling" ? void 0 : "Worker process is no longer running."
1840
+ error: status.status === "canceling" ? void 0 : staleWorkerError(runPath, "Worker process is no longer running.")
1763
1841
  });
1764
1842
  }
1765
1843
  return status;
1766
1844
  }
1845
+ function staleWorkerError(runPath, message) {
1846
+ const workerLogPath = join(runPath, "worker.log");
1847
+ if (!existsSync(workerLogPath)) return message;
1848
+ const tail = readWorkerLogTail$1(workerLogPath);
1849
+ if (tail.length === 0) return `${message}\n\nWorker log: ${workerLogPath} (empty)`;
1850
+ return `${message}\n\nWorker log: ${workerLogPath}\n\nLast worker output:\n${tail}`;
1851
+ }
1852
+ function readWorkerLogTail$1(workerLogPath) {
1853
+ try {
1854
+ return readFileSync(workerLogPath, "utf8").slice(-12e3).trim();
1855
+ } catch {
1856
+ return "";
1857
+ }
1858
+ }
1767
1859
  function readRequiredRunStatus(runPath) {
1768
1860
  const status = readRunStatus(runPath);
1769
1861
  if (status === void 0) throw new CliError$1(`Run status not found: ${runPath}`, ExitCode.InvalidInput);
@@ -1799,10 +1891,27 @@ function writeStatus(filePath, status) {
1799
1891
  mode: 448
1800
1892
  });
1801
1893
  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));
1894
+ try {
1895
+ writeFileSync(tempPath, `${JSON.stringify(status, null, 2)}\n`, { mode: 384 });
1896
+ fsyncFile(tempPath);
1897
+ renameSync(tempPath, filePath);
1898
+ fsyncDirectory(dirname(filePath));
1899
+ } catch (error) {
1900
+ throw writableRunStatusError(error, status.runPath);
1901
+ }
1902
+ }
1903
+ function writableRunStatusError(error, runPath) {
1904
+ if (!isWriteAccessError$1(error)) return error;
1905
+ return new CliError$1([
1906
+ `Quire could not update run status at ${runPath}: ${error.message}`,
1907
+ "In sandboxed coding-agent environments, put Quire run data in a temporary directory and reuse it for every lifecycle command:",
1908
+ " quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)",
1909
+ " QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json <your request>",
1910
+ " QUIRE_RUNS_DIR=$quire_runs_dir quire wait <run-id> --json"
1911
+ ].join("\n"), ExitCode.InvalidInput);
1912
+ }
1913
+ function isWriteAccessError$1(error) {
1914
+ return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
1806
1915
  }
1807
1916
  function fsyncFile(filePath) {
1808
1917
  const file = openSync(filePath, "r");
@@ -2801,6 +2910,7 @@ function materializeHandoff({ handoff, runDir, continuation, modelUsage, metadat
2801
2910
  result: materializedResult
2802
2911
  });
2803
2912
  writeHandoffArtifact(runDir, materializedHandoff);
2913
+ clearRunPendingHandoff(runDir);
2804
2914
  return {
2805
2915
  result: materializedResult,
2806
2916
  metadata: nextMetadata
@@ -45315,6 +45425,23 @@ const writeHandoffToolDefinition = {
45315
45425
  };
45316
45426
  }
45317
45427
  };
45428
+ function makeWriteHandoffTool(options = {}) {
45429
+ return defineTool({
45430
+ ...writeHandoffToolDefinition,
45431
+ async execute(_toolCallId, params, _signal) {
45432
+ const handoff = normalizeWriteHandoffInput(params);
45433
+ await options.onHandoff?.(handoff);
45434
+ return {
45435
+ content: [{
45436
+ type: "text",
45437
+ text: JSON.stringify(handoff, null, 2)
45438
+ }],
45439
+ details: handoff,
45440
+ terminate: true
45441
+ };
45442
+ }
45443
+ });
45444
+ }
45318
45445
  function normalizeWriteHandoffInput(params) {
45319
45446
  const { status, ...rest } = params;
45320
45447
  return {
@@ -45327,7 +45454,7 @@ function normalizeHandoffStatus(status) {
45327
45454
  if (status === "no_issue_found" || status === "not_a_bug" || status === "verified_no_bug_found" || status === "verified_no_issue_found") return "not_reproduced";
45328
45455
  return status;
45329
45456
  }
45330
- const writeHandoffTool = defineTool(writeHandoffToolDefinition);
45457
+ makeWriteHandoffTool();
45331
45458
  //#endregion
45332
45459
  //#region src/utils/schema.ts
45333
45460
  function stringEnumSchema(values) {
@@ -46153,7 +46280,11 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
46153
46280
  followup,
46154
46281
  skillPack,
46155
46282
  mcpGateway,
46156
- intent
46283
+ intent,
46284
+ onHandoff: (handoff) => {
46285
+ emittedHandoff ??= handoff;
46286
+ writeRunPendingHandoff(runDir, handoff);
46287
+ }
46157
46288
  });
46158
46289
  const { session, logAgentProgress, registeredTargetTools } = runtime;
46159
46290
  onResolvedModel?.({
@@ -46161,18 +46292,31 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
46161
46292
  provider: runtime.model.provider,
46162
46293
  modelId: runtime.model.id
46163
46294
  });
46295
+ let rejectCurrentPromptOnModelError;
46164
46296
  const unsubscribe = session.subscribe((event) => {
46165
46297
  touchRunActivity(runDir.root, { minIntervalMs: RUN_ACTIVITY_HEARTBEAT_INTERVAL_MS });
46166
46298
  logAgentProgress(event);
46299
+ const sessionError = modelErrorFromSessionEvent(event);
46300
+ if (sessionError !== void 0) rejectCurrentPromptOnModelError?.(sessionError);
46167
46301
  emittedHandoff ??= extractWriteHandoff(event);
46168
46302
  rememberTargetToolCallArgs(event, registeredTargetTools, targetToolCallArgs);
46169
46303
  const attemptsObserved = countNewTargetAttempts(event, registeredTargetTools, observedTargetToolCallIds, targetToolCallArgs);
46170
46304
  if (attemptsObserved > 0) observedToolAttempts += attemptsObserved;
46171
46305
  });
46172
46306
  try {
46173
- await session.prompt(runBrief, { expandPromptTemplates: false });
46174
- if (emittedHandoff === void 0) await session.prompt(buildMissingRunHandoffPrompt(observedToolAttempts), { expandPromptTemplates: false });
46307
+ try {
46308
+ await promptOrModelError(session.prompt(runBrief, { expandPromptTemplates: false }), (reject) => {
46309
+ rejectCurrentPromptOnModelError = reject;
46310
+ });
46311
+ if (emittedHandoff === void 0) await promptOrModelError(session.prompt(buildMissingRunHandoffPrompt(observedToolAttempts), { expandPromptTemplates: false }), (reject) => {
46312
+ rejectCurrentPromptOnModelError = reject;
46313
+ });
46314
+ } catch (error) {
46315
+ if (emittedHandoff === void 0) throw error;
46316
+ progress?.("Recovered terminal write_handoff after the agent session stopped.");
46317
+ }
46175
46318
  } finally {
46319
+ rejectCurrentPromptOnModelError = void 0;
46176
46320
  runtime.writeSessionState();
46177
46321
  unsubscribe();
46178
46322
  runtime.dispose();
@@ -46182,7 +46326,23 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
46182
46326
  runDir
46183
46327
  });
46184
46328
  }
46185
- async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed, intent, progress, onProgressEvent, runInstructions, remoteAddons, runtimeCapabilities, followup, skillPack, mcpGateway }) {
46329
+ async function promptOrModelError(prompt, setRejectCurrentPromptOnModelError) {
46330
+ try {
46331
+ return await Promise.race([prompt, new Promise((_resolve, reject) => {
46332
+ setRejectCurrentPromptOnModelError(reject);
46333
+ })]);
46334
+ } finally {
46335
+ setRejectCurrentPromptOnModelError(void 0);
46336
+ }
46337
+ }
46338
+ function modelErrorFromSessionEvent(event) {
46339
+ if (!isRecord$14(event) || event.type !== "message" || !isRecord$14(event.message)) return;
46340
+ const message = event.message;
46341
+ if (message.role !== "assistant" || message.stopReason !== "error") return;
46342
+ const errorMessage = typeof message.errorMessage === "string" && message.errorMessage.trim().length > 0 ? message.errorMessage.trim() : "model request failed";
46343
+ return /* @__PURE__ */ new Error(`Quire model request failed: ${errorMessage}`);
46344
+ }
46345
+ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed, intent, progress, onProgressEvent, runInstructions, remoteAddons, runtimeCapabilities, followup, skillPack, mcpGateway, onHandoff }) {
46186
46346
  const logAgentProgress = createAgentProgressLogger({
46187
46347
  runDir,
46188
46348
  write: progress
@@ -46212,6 +46372,7 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
46212
46372
  gateway: mcpGateway,
46213
46373
  runId: runDir.runId
46214
46374
  });
46375
+ const writeHandoffTool = makeWriteHandoffTool({ onHandoff });
46215
46376
  const registeredTargetTools = [...browserTool === void 0 ? [] : [{
46216
46377
  toolName: "agent-browser",
46217
46378
  target: "web"
@@ -48183,6 +48344,8 @@ function runPath(runDir) {
48183
48344
  }
48184
48345
  //#endregion
48185
48346
  //#region src/pipeline/background-investigation.ts
48347
+ const WORKER_BOOTSTRAP_TIMEOUT_MS = 2e3;
48348
+ const WORKER_LOG_FILE = "worker.log";
48186
48349
  async function startInvestigationRun(options) {
48187
48350
  assertHasInvestigationInput(options.query, options.inputText);
48188
48351
  const repoPath = resolve(options.cwd ?? process.cwd());
@@ -48238,6 +48401,8 @@ async function startInvestigationRun(options) {
48238
48401
  runId: runDir.runId,
48239
48402
  workspaceKey: runDir.workspaceKey,
48240
48403
  runPath: runDir.root,
48404
+ runsRoot: runDir.runsRoot,
48405
+ temporaryRunsRoot: runDir.temporaryRunsRoot === true,
48241
48406
  pid: worker.pid
48242
48407
  });
48243
48408
  }
@@ -48315,6 +48480,8 @@ function workerStartedBrowserSessionIds(runDir) {
48315
48480
  function spawnInvestigationWorker({ runPath }) {
48316
48481
  const entryPoint = process.argv[1];
48317
48482
  if (entryPoint === void 0 || entryPoint.length === 0) throw new CliError$1("Unable to locate Quire CLI entrypoint for background worker.", ExitCode.InternalError);
48483
+ const workerLogPath = `${runPath}/${WORKER_LOG_FILE}`;
48484
+ const workerLogFd = openSync(workerLogPath, "a", 384);
48318
48485
  const child = spawn(process.execPath, [
48319
48486
  ...process.execArgv,
48320
48487
  entryPoint,
@@ -48327,23 +48494,89 @@ function spawnInvestigationWorker({ runPath }) {
48327
48494
  env: process.env,
48328
48495
  stdio: [
48329
48496
  "ignore",
48330
- "ignore",
48331
- "ignore"
48497
+ workerLogFd,
48498
+ workerLogFd
48332
48499
  ]
48333
48500
  });
48334
- child.unref();
48335
- return Promise.resolve({ pid: child.pid });
48501
+ closeSync(workerLogFd);
48502
+ return waitForWorkerBootstrap({
48503
+ child,
48504
+ runPath,
48505
+ workerLogPath
48506
+ }).finally(() => {
48507
+ child.unref();
48508
+ });
48509
+ }
48510
+ async function waitForWorkerBootstrap({ child, runPath, workerLogPath }) {
48511
+ const exit = new Promise((resolveExit) => {
48512
+ child.once("exit", (code, signal) => resolveExit({
48513
+ code,
48514
+ signal
48515
+ }));
48516
+ });
48517
+ const timeout = new Promise((resolveTimeout) => {
48518
+ setTimeout(() => resolveTimeout("timeout"), WORKER_BOOTSTRAP_TIMEOUT_MS).unref();
48519
+ });
48520
+ const started = waitForRunToLeaveQueued(runPath);
48521
+ const result = await Promise.race([
48522
+ exit,
48523
+ timeout,
48524
+ started.promise
48525
+ ]);
48526
+ started.stop();
48527
+ if (result === "started") return { pid: child.pid };
48528
+ const status = readRunStatus(runPath);
48529
+ if (result !== "timeout" && status?.status === "queued") throw new CliError$1(formatEarlyWorkerExitError({
48530
+ workerLogPath,
48531
+ code: result.code,
48532
+ signal: result.signal
48533
+ }), ExitCode.InternalError);
48534
+ return { pid: child.pid };
48535
+ }
48536
+ function waitForRunToLeaveQueued(runPath) {
48537
+ let stopped = false;
48538
+ return {
48539
+ promise: new Promise((resolveStarted) => {
48540
+ const checkStatus = () => {
48541
+ if (stopped) return;
48542
+ const status = readRunStatus(runPath);
48543
+ if (status !== void 0 && status.status !== "queued") {
48544
+ resolveStarted("started");
48545
+ return;
48546
+ }
48547
+ setTimeout(checkStatus, 25).unref();
48548
+ };
48549
+ checkStatus();
48550
+ }),
48551
+ stop: () => {
48552
+ stopped = true;
48553
+ }
48554
+ };
48555
+ }
48556
+ function formatEarlyWorkerExitError({ workerLogPath, code, signal }) {
48557
+ const exit = signal === null ? `exit code ${code ?? "unknown"}` : `signal ${signal}`;
48558
+ const tail = readWorkerLogTail(workerLogPath);
48559
+ 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}`}`;
48560
+ }
48561
+ function readWorkerLogTail(workerLogPath) {
48562
+ try {
48563
+ return readFileSync(workerLogPath, "utf8").slice(-12e3).trim();
48564
+ } catch {
48565
+ return "";
48566
+ }
48336
48567
  }
48337
48568
  function runHandle(input) {
48569
+ const commandPrefix = input.temporaryRunsRoot ? `QUIRE_RUNS_DIR=${shellQuote(input.runsRoot)} ` : "";
48338
48570
  return {
48339
48571
  status: "queued",
48340
48572
  runId: input.runId,
48341
48573
  workspaceKey: input.workspaceKey,
48342
48574
  runPath: toJsonPath(input.runPath),
48343
48575
  ...input.pid === void 0 ? {} : { pid: input.pid },
48344
- statusCommand: `quire status ${shellQuote(input.runId)} --json`,
48345
- watchCommand: `quire watch ${shellQuote(input.runId)}`,
48346
- cancelCommand: `quire cancel ${shellQuote(input.runId)}`
48576
+ statusCommand: `${commandPrefix}quire status ${shellQuote(input.runId)} --json`,
48577
+ waitCommand: `${commandPrefix}quire wait ${shellQuote(input.runId)} --json`,
48578
+ watchCommand: `${commandPrefix}quire watch ${shellQuote(input.runId)}`,
48579
+ cancelCommand: `${commandPrefix}quire cancel ${shellQuote(input.runId)}`
48347
48580
  };
48348
48581
  }
48349
48582
  function readWorkerRequest(runPath) {
@@ -48528,6 +48761,38 @@ const watchCommand = defineCommand({
48528
48761
  await watchRun(readRequiredString(args.run, "run"));
48529
48762
  }
48530
48763
  });
48764
+ const waitCommand = defineCommand({
48765
+ meta: {
48766
+ name: "wait",
48767
+ description: "Wait for a Quire investigation run to reach a terminal status."
48768
+ },
48769
+ args: {
48770
+ run: {
48771
+ type: "positional",
48772
+ description: "Run id or run directory.",
48773
+ required: true
48774
+ },
48775
+ json: {
48776
+ type: "boolean",
48777
+ description: "Emit terminal status JSON."
48778
+ },
48779
+ "timeout-ms": {
48780
+ type: "string",
48781
+ description: "Maximum time to wait in milliseconds. Defaults to 600000."
48782
+ },
48783
+ "interval-ms": {
48784
+ type: "string",
48785
+ description: "Polling interval in milliseconds. Defaults to 5000."
48786
+ }
48787
+ },
48788
+ async run({ args }) {
48789
+ await waitRun(readRequiredString(args.run, "run"), {
48790
+ json: args.json === true,
48791
+ timeoutMs: readPositiveIntegerOption(args["timeout-ms"], "--timeout-ms") ?? 6e5,
48792
+ intervalMs: readPositiveIntegerOption(args["interval-ms"], "--interval-ms") ?? 5e3
48793
+ });
48794
+ }
48795
+ });
48531
48796
  const cancelCommand = defineCommand({
48532
48797
  meta: {
48533
48798
  name: "cancel",
@@ -48653,6 +48918,28 @@ async function watchRun(run, options = {}) {
48653
48918
  if (sigintHandler !== void 0) process.off("SIGINT", sigintHandler);
48654
48919
  }
48655
48920
  }
48921
+ async function waitRun(run, options = {}) {
48922
+ const stdout = options.io?.stdout ?? process.stdout;
48923
+ const runPath = resolveRunPath(run, options);
48924
+ const sleep = options.sleep ?? defaultSleep$1;
48925
+ const intervalMs = options.intervalMs ?? 5e3;
48926
+ const timeoutMs = options.timeoutMs ?? 6e5;
48927
+ const now = options.now ?? Date.now;
48928
+ const deadline = now() + timeoutMs;
48929
+ while (true) {
48930
+ const status = withSyncStatus(readFreshRunStatus(runPath));
48931
+ if (isTerminalRunStatus(status.status)) {
48932
+ if (options.json === true) writeJson(stdout, status);
48933
+ else {
48934
+ writeLine(stdout, renderFinalStatus(status));
48935
+ writeHandoffSummary(stdout, runDirFromStatus(status).paths.handoff);
48936
+ }
48937
+ return status;
48938
+ }
48939
+ 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);
48940
+ await sleep(Math.min(intervalMs, Math.max(0, deadline - now())));
48941
+ }
48942
+ }
48656
48943
  async function cancelRun(run, options = {}) {
48657
48944
  const stdout = options.io?.stdout ?? process.stdout;
48658
48945
  const runPath = resolveRunPath(run, options);
@@ -48713,9 +49000,15 @@ async function syncRun(run, options = {}) {
48713
49000
  function withSyncStatus(status) {
48714
49001
  const sync = readInvestigationSyncStatus(runDirFromStatus(status));
48715
49002
  const { sync: _storedSync, ...baseStatus } = status;
48716
- return sync === void 0 ? baseStatus : {
49003
+ if (sync === void 0) return baseStatus;
49004
+ return sync.caseUrl === void 0 ? {
48717
49005
  ...baseStatus,
48718
49006
  sync
49007
+ } : {
49008
+ ...baseStatus,
49009
+ sync,
49010
+ webUrl: sync.caseUrl,
49011
+ caseUrl: sync.caseUrl
48719
49012
  };
48720
49013
  }
48721
49014
  function signalRunProcess(pid, signal) {
@@ -48738,6 +49031,13 @@ function statusColor(status) {
48738
49031
  function formatNumber(value) {
48739
49032
  return new Intl.NumberFormat("en-US").format(value);
48740
49033
  }
49034
+ function readPositiveIntegerOption(value, name) {
49035
+ if (value === void 0 || value === null || value === false) return;
49036
+ if (typeof value !== "string" || value.trim().length === 0) throw new CliError$1(`${name} must be a positive integer.`, ExitCode.InvalidInput);
49037
+ const parsed = Number(value);
49038
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new CliError$1(`${name} must be a positive integer.`, ExitCode.InvalidInput);
49039
+ return parsed;
49040
+ }
48741
49041
  function defaultSleep$1(ms) {
48742
49042
  return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
48743
49043
  }
@@ -48797,6 +49097,7 @@ const investigationCommandSchema = {
48797
49097
  "workspaceKey",
48798
49098
  "runPath",
48799
49099
  "statusCommand",
49100
+ "waitCommand",
48800
49101
  "watchCommand",
48801
49102
  "cancelCommand"
48802
49103
  ],
@@ -48810,6 +49111,7 @@ const investigationCommandSchema = {
48810
49111
  optional: true
48811
49112
  },
48812
49113
  statusCommand: { type: "string" },
49114
+ waitCommand: { type: "string" },
48813
49115
  watchCommand: { type: "string" },
48814
49116
  cancelCommand: { type: "string" }
48815
49117
  }
@@ -48817,6 +49119,7 @@ const investigationCommandSchema = {
48817
49119
  followUpCommands: {
48818
49120
  continue: "quire continue <run-id> \"additional context or instruction\"",
48819
49121
  status: "quire status <run-id> --json",
49122
+ wait: "quire wait <run-id> --json",
48820
49123
  watch: "quire watch <run-id>",
48821
49124
  cancel: "quire cancel <run-id>"
48822
49125
  },
@@ -48851,6 +49154,7 @@ const investigationCommandSchema = {
48851
49154
  "quire \"verify checkout before I approve this PR\"",
48852
49155
  "printf 'why is CI failing?\\nRelevant log: ...' | quire run --json",
48853
49156
  "quire status run_9x4mdq7p2h8kc6nv4apz --json",
49157
+ "quire wait run_9x4mdq7p2h8kc6nv4apz --json",
48854
49158
  "quire watch run_9x4mdq7p2h8kc6nv4apz"
48855
49159
  ]
48856
49160
  };
@@ -48938,7 +49242,9 @@ async function runInvestigate(options) {
48938
49242
  });
48939
49243
  if (options.detach === true) log.message([
48940
49244
  `${color.label("Run")}: ${color.path(handle.runPath)}`,
49245
+ ...handle.webUrl === void 0 ? [] : [`${color.label("Case")}: ${handle.webUrl}`],
48941
49246
  `${color.label("Status")}: ${color.command(handle.statusCommand)}`,
49247
+ `${color.label("Wait")}: ${color.command(handle.waitCommand)}`,
48942
49248
  `${color.label("Watch")}: ${color.command(handle.watchCommand)}`,
48943
49249
  `${color.label("Cancel")}: ${color.command(handle.cancelCommand)}`
48944
49250
  ], {
@@ -48949,15 +49255,16 @@ async function runInvestigate(options) {
48949
49255
  else log.message([
48950
49256
  `${color.label("Run")}: ${color.path(handle.runPath)}`,
48951
49257
  `${color.label("Status")}: ${color.command(handle.statusCommand)}`,
49258
+ `${color.label("Wait")}: ${color.command(handle.waitCommand)}`,
48952
49259
  `${color.label("Watch")}: ${color.command(handle.watchCommand)}`,
48953
- `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.`
49260
+ `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.`
48954
49261
  ], {
48955
49262
  output: stdout,
48956
49263
  spacing: 0,
48957
49264
  withGuide: true
48958
49265
  });
48959
49266
  }
48960
- if (options.json !== true && options.detach !== true) await (options.watchRunner ?? watchRun)(handle.runId, {
49267
+ if (options.json !== true && options.detach !== true) await (options.watchRunner ?? watchRun)(handle.runPath, {
48961
49268
  cwd: options.cwd,
48962
49269
  runsRoot: options.runsRoot,
48963
49270
  io: { stdout }
@@ -49098,11 +49405,13 @@ function pluralize(count, singular, plural) {
49098
49405
  //#region src/doctor/checks.ts
49099
49406
  const execFileAsync = promisify(execFile);
49100
49407
  function createDoctorContext(overrides = {}) {
49408
+ const env = overrides.env ?? process.env;
49101
49409
  return {
49102
49410
  cwd: resolve(overrides.cwd ?? process.cwd()),
49103
- runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot()),
49411
+ runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot(env)),
49412
+ runsRootExplicit: overrides.runsRootExplicit ?? (overrides.runsRoot !== void 0 || (env.QUIRE_RUNS_DIR?.trim().length ?? 0) > 0),
49104
49413
  authStore: overrides.authStore ?? authStore,
49105
- env: overrides.env ?? process.env,
49414
+ env,
49106
49415
  fetchFn: overrides.fetchFn ?? fetch,
49107
49416
  execFn: overrides.execFn ?? defaultExecFn,
49108
49417
  probeTimeoutMs: overrides.probeTimeoutMs ?? 2e3
@@ -49204,6 +49513,13 @@ async function checkAuthValid(context, auth) {
49204
49513
  }
49205
49514
  }
49206
49515
  async function checkAuthBroker(context, auth) {
49516
+ const localModelSource = await detectLocalInvestigationModelSource({ env: context.env });
49517
+ if (localModelSource !== null) return {
49518
+ id: "auth.broker",
49519
+ section: "identity",
49520
+ severity: "pass",
49521
+ message: `Local model provider connected (${formatLocalModelSource(localModelSource.source)}; ${localModelSource.provider}/${localModelSource.modelId}).`
49522
+ };
49207
49523
  try {
49208
49524
  return brokerToCheckResult(await fetchModelSourceBrokerStatus({
49209
49525
  apiBaseUrl: auth.apiBaseUrl,
@@ -49220,6 +49536,9 @@ async function checkAuthBroker(context, auth) {
49220
49536
  };
49221
49537
  }
49222
49538
  }
49539
+ function formatLocalModelSource(source) {
49540
+ return source === "local_openai_codex" ? "ChatGPT/Codex" : "GitHub Copilot";
49541
+ }
49223
49542
  function brokerToCheckResult(status) {
49224
49543
  if (status.requiredNextAction !== null) return {
49225
49544
  id: "auth.broker",
@@ -49294,6 +49613,22 @@ async function checkRunsRoot(context) {
49294
49613
  message: `Runs root writable (${context.runsRoot}).`
49295
49614
  };
49296
49615
  } catch (error) {
49616
+ if (!context.runsRootExplicit && isWriteAccessError(error)) {
49617
+ const fallbackRunsRoot = await mkdtemp(join(tmpdir(), "quire-runs-"));
49618
+ await mkdir(fallbackRunsRoot, {
49619
+ recursive: true,
49620
+ mode: 448
49621
+ });
49622
+ await access(fallbackRunsRoot, constants.W_OK);
49623
+ context.runsRoot = fallbackRunsRoot;
49624
+ return {
49625
+ id: "runs.rootWritable",
49626
+ section: "runs",
49627
+ severity: "warn",
49628
+ message: `Default runs root not writable (${toMessage(error)}); using temporary runs root ${fallbackRunsRoot}.`,
49629
+ fix: { command: `Set QUIRE_RUNS_DIR=${fallbackRunsRoot} for follow-up Quire commands in this shell.` }
49630
+ };
49631
+ }
49297
49632
  return {
49298
49633
  id: "runs.rootWritable",
49299
49634
  section: "runs",
@@ -49338,6 +49673,9 @@ function checkStuckRuns(context) {
49338
49673
  };
49339
49674
  }
49340
49675
  }
49676
+ function isWriteAccessError(error) {
49677
+ return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
49678
+ }
49341
49679
  function formatUser$2(user) {
49342
49680
  if (user.email !== void 0 && user.email !== null && user.email.length > 0) return user.email;
49343
49681
  if (user.name !== void 0 && user.name !== null && user.name.length > 0) return user.name;
@@ -49648,6 +49986,7 @@ const logoutCommand = defineCommand({
49648
49986
  async function runLogout(options) {
49649
49987
  const store = options.store ?? authStore;
49650
49988
  const stdout = options.io?.stdout ?? process.stdout;
49989
+ const env = options.env ?? process.env;
49651
49990
  const existing = await store.get();
49652
49991
  await store.clear();
49653
49992
  if (existing === null) log.info("Already logged out.", {
@@ -49658,48 +49997,379 @@ async function runLogout(options) {
49658
49997
  output: stdout,
49659
49998
  spacing: 0
49660
49999
  });
50000
+ if (env["QUIRE_API_TOKEN"]?.trim()) log.warn(`${QUIRE_API_TOKEN_ENV} is still set, so Quire commands may still authenticate.`, {
50001
+ output: stdout,
50002
+ spacing: 0
50003
+ });
49661
50004
  }
49662
50005
  //#endregion
49663
50006
  //#region src/commands/setup.ts
49664
- const SETUP_URL = "https://quire.sh/agent-setup";
49665
- const VERIFICATION_SKILL_URL = "https://quire.sh/agent-verification";
49666
- const AGENT_SETUP_PROMPT = `Set up this repository for Quire investigations.
50007
+ const DEFAULT_PUBLIC_BASE_URL = "https://quire.sh";
50008
+ const SETUP_URL_PLACEHOLDER = "__QUIRE_SETUP_URL__";
50009
+ const VERIFICATION_SKILL_URL_PLACEHOLDER = "__QUIRE_VERIFICATION_SKILL_URL__";
50010
+ const AGENT_SETUP_PROMPT = `Help me set up Quire for this repo. Use \`quire\` if available, otherwise \`npx @quireco/cli@latest\`. Load and follow ${SETUP_URL_PLACEHOLDER}. Guide any login/model setup with me, create the repo runbook, and run one setup smoke only if Quire doctor is healthy. Report the run link/evidence or exact blocker.`;
50011
+ const QUIRE_VERIFY_SKILL_MARKDOWN = `---
50012
+ name: verify
50013
+ 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.
50014
+ ---
50015
+
50016
+ # Verify
50017
+
50018
+ Load and follow the current Quire verification skill from ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
50019
+
50020
+ 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.
50021
+
50022
+ ## When to use this skill
50023
+
50024
+ 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.
49667
50025
 
49668
- Please load and follow the Quire agent setup instructions from ${SETUP_URL}.
50026
+ 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.
49669
50027
 
49670
- 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.`;
49671
- const QUIRE_SKILL_MARKDOWN = `---
50028
+ 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.
50029
+
50030
+ ## Hard requirement
50031
+
50032
+ 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.
50033
+
50034
+ Required 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.
50035
+
50036
+ 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.
50037
+
50038
+ When communicating Quire results to the user, produce a compact verification report like a QA test run summary: verdict, checks run, pass/fail/blocked counts when available, known non-regressions, synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\`, report/handoff markdown path or excerpt, and useful artifacts such as screenshots, recordings, traces, console/network output, or other evidence.
50039
+
50040
+ ## How to invoke Quire
50041
+
50042
+ Preferred order:
50043
+
50044
+ 1. If this harness exposes a native subagent named \`quire\`, delegate the verification brief to that subagent and wait for its result. This is mandatory when available.
50045
+ 2. If no native Quire subagent is available, run the CLI directly.
50046
+
50047
+ CLI fallback:
50048
+
50049
+ \`\`\`bash
50050
+ run_json=$(quire run --detach --json --url "<url>" "<verification request>")
50051
+ run_id=$(printf '%s' "$run_json" | jq -r '.runId')
50052
+ quire wait "$run_id" --json
50053
+ \`\`\`
50054
+
50055
+ 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:
50056
+
50057
+ \`\`\`bash
50058
+ quire wait "<run-id>" --json
50059
+ \`\`\`
50060
+
50061
+ 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.
50062
+
50063
+ 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.
50064
+
50065
+ 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:
50066
+
50067
+ \`\`\`bash
50068
+ quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
50069
+ QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
50070
+ QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
50071
+ \`\`\`
50072
+
50073
+ ## Verification brief template
50074
+
50075
+ - Claim/change to verify:
50076
+ - Relevant files/paths:
50077
+ - Commands already run:
50078
+ - Expected behavior:
50079
+ - Known risks or edge cases:
50080
+ - Constraints: read-only verification; do not edit code.
50081
+
50082
+ Return Quire's terminal handoff with:
50083
+
50084
+ - Verdict: pass, fail, blocked, partial, or setup-blocked
50085
+ - Quire run id
50086
+ - Quire web URL when present
50087
+ - What Quire verified, including checks run and pass/fail/blocked counts when available
50088
+ - Key evidence, artifacts, report, screenshots, recordings, traces, or handoff excerpt
50089
+ - Known non-regressions or pre-existing issues, when relevant
50090
+ - Follow-up actions for the implementation agent
50091
+ `;
50092
+ const QUIRE_DEBUG_SKILL_MARKDOWN = `---
50093
+ name: debug
50094
+ 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.
50095
+ ---
50096
+
50097
+ # Debug
50098
+
50099
+ 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.
50100
+
50101
+ ## When to use Quire during diagnosis
50102
+
50103
+ Use the native \`quire\` subagent when you need an independent read-only investigator to:
50104
+
50105
+ - reproduce a reported bug or flaky workflow;
50106
+ - verify a suspected fix against the real app, browser, API, CLI, or mobile surface;
50107
+ - gather evidence such as screenshots, recordings, console output, terminal logs, or a concise handoff;
50108
+ - check whether a failure is environment/setup-blocked versus a product bug;
50109
+ - produce a run id and terminal verdict that another agent can use in an implementation/verification loop.
50110
+
50111
+ Do not outsource implementation to Quire. Quire should not edit code. The parent/debugging agent owns hypotheses, code changes, and regression tests.
50112
+
50113
+ ## Recommended invocation
50114
+
50115
+ 1. Build or identify the best reproduction loop you can from the report.
50116
+ 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. This is mandatory when available.
50117
+ 3. If no native \`quire\` subagent is available, use the Quire CLI fallback from ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
50118
+ 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.
50119
+
50120
+ ## Quire brief template
50121
+
50122
+ - Failure or regression to reproduce:
50123
+ - User-visible symptom:
50124
+ - Target URL, command, app, or environment:
50125
+ - Steps already tried:
50126
+ - Expected behavior:
50127
+ - Actual behavior:
50128
+ - Evidence to capture:
50129
+ - Constraints: read-only investigation; do not edit code.
50130
+
50131
+ ## CLI fallback
50132
+
50133
+ Use CLI fallback only if the native \`quire\` subagent is unavailable. Prefer detached JSON mode and wait for the terminal handoff:
50134
+
50135
+ \`\`\`bash
50136
+ run_json=$(quire run --detach --json --url "<url>" "<reproduction or verification request>")
50137
+ run_id=$(printf '%s' "$run_json" | jq -r '.runId')
50138
+ quire wait "$run_id" --json
50139
+ \`\`\`
50140
+
50141
+ 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:
50142
+
50143
+ \`\`\`bash
50144
+ quire wait "<run-id>" --json
50145
+ \`\`\`
50146
+
50147
+ For more CLI details, sandbox recovery, and handoff interpretation, follow ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
50148
+ `;
50149
+ const QUIRE_AGENT_MARKDOWN = `---
49672
50150
  name: quire
49673
- 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.
50151
+ 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."
50152
+ color: green
50153
+ model: inherit
49674
50154
  ---
49675
50155
 
49676
- # Quire verification
50156
+ 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.
50157
+
50158
+ 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.
49677
50159
 
49678
- Load and follow the current Quire verification skill from ${VERIFICATION_SKILL_URL}.
50160
+ 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.
49679
50161
 
49680
- Use Quire when a task needs evidence-backed verification, QA, product dogfooding, bug reproduction, or triage. Prefer \`quire run\` for new work.
50162
+ ## Workflow
50163
+
50164
+ 1. Convert the parent request into a concise Quire verification brief.
50165
+ 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.
50166
+ 3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
50167
+ 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.
50168
+
50169
+ 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.
50170
+
50171
+ Prefer detached JSON mode for agent handoffs:
50172
+
50173
+ \`\`\`bash
50174
+ run_json=$(quire run --detach --json --url "<url>" "<verification request>")
50175
+ run_id=$(printf '%s' "$run_json" | jq -r '.runId')
50176
+ \`\`\`
50177
+
50178
+ If there is no URL, omit \`--url\` and put target context in the natural-language request.
50179
+
50180
+ 5. Wait patiently until terminal status:
50181
+
50182
+ \`\`\`bash
50183
+ quire wait "$run_id" --json
50184
+ \`\`\`
50185
+
50186
+ 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:
50187
+
50188
+ \`\`\`bash
50189
+ quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
50190
+ QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
50191
+ QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
50192
+ \`\`\`
50193
+
50194
+ 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.
50195
+
50196
+ 6. Return \`finalHandoff.handoffMarkdown\` from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present. Format the result like a QA test run summary: verdict, checks run, pass/fail/blocked counts when available, report path or excerpt, screenshots, recordings, traces, console/network output, known non-regressions, and follow-up actions.
50197
+ 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.
50198
+ 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.
50199
+
50200
+ ## Return format
50201
+
50202
+ - Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
50203
+ - Verdict: pass, fail, blocked, partial, or setup-blocked
50204
+ - Quire run id
50205
+ - Quire web URL when present
50206
+ - What Quire verified, including checks run and pass/fail/blocked counts when available
50207
+ - Key evidence, artifacts, report, screenshots, recordings, traces, or handoff excerpt
50208
+ - Known non-regressions or pre-existing issues, when relevant
50209
+ - Follow-up actions for the implementation agent
49681
50210
  `;
49682
- const SKILL_INSTALL_TARGETS = [{
49683
- id: "agents",
49684
- label: "Universal",
49685
- targetPath: ".agents/skills/quire/SKILL.md",
49686
- hint: "~/.agents/skills/quire/SKILL.md · Pi, OpenCode, Amp, Codex-style agents",
49687
- aliases: [
49688
- "agents",
49689
- "agent-skills",
49690
- "pi",
49691
- "opencode",
49692
- "amp",
49693
- "codex",
49694
- "other"
49695
- ]
49696
- }, {
49697
- id: "claude",
49698
- label: "Claude Code",
49699
- targetPath: ".claude/skills/quire/SKILL.md",
49700
- hint: "~/.claude/skills/quire/SKILL.md",
49701
- aliases: ["claude", "claude-code"]
49702
- }];
50211
+ const QUIRE_CURSOR_MARKDOWN = QUIRE_AGENT_MARKDOWN.replace("model: inherit\n---", "model: inherit\nreadonly: true\n---");
50212
+ const QUIRE_CODEX_TOML = `name = "quire"
50213
+ 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."
50214
+
50215
+ developer_instructions = """
50216
+ 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.
50217
+
50218
+ 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.
50219
+
50220
+ 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.
50221
+
50222
+ Workflow:
50223
+ 1. Convert the parent request into a concise Quire verification brief.
50224
+ 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.
50225
+ 3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
50226
+ 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.
50227
+
50228
+ 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.
50229
+
50230
+ run_json=$(quire run --detach --json --url "<url>" "<verification request>")
50231
+ run_id=$(printf '%s' "$run_json" | jq -r '.runId')
50232
+ quire wait "$run_id" --json
50233
+
50234
+ 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:
50235
+
50236
+ quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
50237
+ QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
50238
+ QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
50239
+
50240
+ 6. Return finalHandoff.handoffMarkdown from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present. Format the result like a QA test run summary: verdict, checks run, pass/fail/blocked counts when available, report path or excerpt, screenshots, recordings, traces, console/network output, known non-regressions, and follow-up actions.
50241
+ 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.
50242
+
50243
+ Return:
50244
+ - Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
50245
+ - Verdict: pass, fail, blocked, partial, or setup-blocked
50246
+ - Quire run id
50247
+ - Quire web URL when present
50248
+ - What Quire verified, including checks run and pass/fail/blocked counts when available
50249
+ - Key evidence, artifacts, report, screenshots, recordings, traces, or handoff excerpt
50250
+ - Known non-regressions or pre-existing issues, when relevant
50251
+ - Follow-up actions for the implementation agent
50252
+ """
50253
+ `;
50254
+ const QUIRE_OPENCODE_MARKDOWN = `---
50255
+ 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.
50256
+ mode: subagent
50257
+ permission:
50258
+ edit: deny
50259
+ ---
50260
+
50261
+ 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.
50262
+
50263
+ 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.
50264
+
50265
+ 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.
50266
+
50267
+ ## Workflow
50268
+
50269
+ 1. Convert the parent request into a concise Quire verification brief.
50270
+ 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.
50271
+ 3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
50272
+ 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.
50273
+
50274
+ 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.
50275
+
50276
+ \`\`\`bash
50277
+ run_json=$(quire run --detach --json --url "<url>" "<verification request>")
50278
+ run_id=$(printf '%s' "$run_json" | jq -r '.runId')
50279
+ quire wait "$run_id" --json
50280
+ \`\`\`
50281
+
50282
+ 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:
50283
+
50284
+ \`\`\`bash
50285
+ quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
50286
+ QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
50287
+ QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
50288
+ \`\`\`
50289
+
50290
+ 6. Return \`finalHandoff.handoffMarkdown\` from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present. Format the result like a QA test run summary: verdict, checks run, pass/fail/blocked counts when available, report path or excerpt, screenshots, recordings, traces, console/network output, known non-regressions, and follow-up actions.
50291
+ 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.
50292
+
50293
+ ## Return format
50294
+
50295
+ - Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
50296
+ - Verdict: pass, fail, blocked, partial, or setup-blocked
50297
+ - Quire run id
50298
+ - Quire web URL when present
50299
+ - What Quire verified, including checks run and pass/fail/blocked counts when available
50300
+ - Key evidence, artifacts, report, screenshots, recordings, traces, or handoff excerpt
50301
+ - Known non-regressions or pre-existing issues, when relevant
50302
+ - Follow-up actions for the implementation agent
50303
+ `;
50304
+ const SKILL_INSTALL_TARGETS = [
50305
+ {
50306
+ id: "agents",
50307
+ label: "Universal",
50308
+ skillPaths: [{
50309
+ path: ".agents/skills/verify/SKILL.md",
50310
+ contents: QUIRE_VERIFY_SKILL_MARKDOWN
50311
+ }, {
50312
+ path: ".agents/skills/debug/SKILL.md",
50313
+ contents: QUIRE_DEBUG_SKILL_MARKDOWN
50314
+ }],
50315
+ agentPath: ".agents/agents/quire.md",
50316
+ codexAgentPath: ".codex/agents/quire.toml",
50317
+ opencodeAgentPath: ".config/opencode/agents/quire.md",
50318
+ piAgentPath: ".pi/agent/agents/quire.md",
50319
+ hint: "~/.agents/skills/{verify,debug}/SKILL.md · plus Codex/OpenCode/Pi Quire subagents",
50320
+ aliases: [
50321
+ "agents",
50322
+ "agent-skills",
50323
+ "pi",
50324
+ "opencode",
50325
+ "amp",
50326
+ "codex",
50327
+ "other"
50328
+ ]
50329
+ },
50330
+ {
50331
+ id: "claude",
50332
+ label: "Claude Code",
50333
+ skillPaths: [{
50334
+ path: ".claude/skills/verify/SKILL.md",
50335
+ contents: QUIRE_VERIFY_SKILL_MARKDOWN
50336
+ }, {
50337
+ path: ".claude/skills/debug/SKILL.md",
50338
+ contents: QUIRE_DEBUG_SKILL_MARKDOWN
50339
+ }],
50340
+ agentPath: ".claude/agents/quire.md",
50341
+ hint: "~/.claude/skills/{verify,debug}/SKILL.md · plus native Quire subagent",
50342
+ aliases: ["claude", "claude-code"]
50343
+ },
50344
+ {
50345
+ id: "cursor",
50346
+ label: "Cursor",
50347
+ skillPaths: [{
50348
+ path: ".cursor/skills/verify/SKILL.md",
50349
+ contents: QUIRE_VERIFY_SKILL_MARKDOWN
50350
+ }, {
50351
+ path: ".cursor/skills/debug/SKILL.md",
50352
+ contents: QUIRE_DEBUG_SKILL_MARKDOWN
50353
+ }],
50354
+ agentPath: ".cursor/agents/quire.md",
50355
+ agentContents: QUIRE_CURSOR_MARKDOWN,
50356
+ hint: "~/.cursor/skills/{verify,debug}/SKILL.md · plus native Quire subagent",
50357
+ aliases: ["cursor", "cursor-cli"]
50358
+ }
50359
+ ];
50360
+ const LEGACY_QUIRE_VERIFIER_PATHS = [
50361
+ ".agents/agents/quire-verifier.md",
50362
+ ".agents/skills/quire-verifier/SKILL.md",
50363
+ ".claude/agents/quire-verifier.md",
50364
+ ".codex/agents/quire-verifier.toml",
50365
+ ".cursor/agents/quire-verifier.md",
50366
+ ".config/opencode/agents/quire-verifier.md"
50367
+ ];
50368
+ const LEGACY_QUIRE_SKILL_PATHS = [
50369
+ ".agents/skills/quire/SKILL.md",
50370
+ ".claude/skills/quire/SKILL.md",
50371
+ ".cursor/skills/quire/SKILL.md"
50372
+ ];
49703
50373
  const setupCommand = defineCommand({
49704
50374
  meta: {
49705
50375
  name: "setup",
@@ -49708,7 +50378,7 @@ const setupCommand = defineCommand({
49708
50378
  args: {
49709
50379
  skill: {
49710
50380
  type: "string",
49711
- description: "Comma-separated local agent skill targets to install: agents, claude, all, none.",
50381
+ description: "Comma-separated local agent skill targets to install: agents, claude, cursor, all, none.",
49712
50382
  valueHint: "targets"
49713
50383
  },
49714
50384
  "no-skill": {
@@ -49721,37 +50391,76 @@ const setupCommand = defineCommand({
49721
50391
  }
49722
50392
  },
49723
50393
  async run({ args }) {
50394
+ const noSkill = args["no-skill"] === true || args.noSkill === true;
50395
+ const noInteractive = args["no-interactive"] === true || args.noInteractive === true || args.interactive === false;
49724
50396
  await runSetup({
49725
- skill: args["no-skill"] === true ? false : args.skill,
49726
- interactive: args["no-interactive"] !== true
50397
+ skill: noSkill ? false : args.skill,
50398
+ interactive: !noInteractive
49727
50399
  });
49728
50400
  }
49729
50401
  });
49730
50402
  async function runSetup(options = {}) {
49731
50403
  const stdout = options.io?.stdout ?? process.stdout;
50404
+ const env = options.env ?? process.env;
50405
+ const interactive = options.interactive ?? (options.io?.stdout === void 0 && process.stdout.isTTY === true);
50406
+ const authStore$1 = options.authStore ?? authStore;
50407
+ const urls = setupUrls(env);
49732
50408
  const selectedTargets = await selectSkillInstallTargets({
49733
50409
  skill: options.skill,
49734
- interactive: options.interactive ?? (options.io?.stdout === void 0 && process.stdout.isTTY === true)
50410
+ interactive
49735
50411
  });
49736
- const installedSkills = await installQuireSkill(options.homeDir ?? homedir(), selectedTargets);
50412
+ const installedAgentFiles = await installQuireAgentFiles(options.homeDir ?? homedir(), selectedTargets, urls);
49737
50413
  log.step("Set up Quire for this workspace", {
49738
50414
  output: stdout,
49739
50415
  spacing: 0
49740
50416
  });
49741
- 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:`], {
50417
+ 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:`], {
49742
50418
  output: stdout,
49743
50419
  spacing: 0,
49744
50420
  withGuide: true
49745
50421
  });
49746
50422
  writeLine(stdout);
49747
50423
  writeLine(stdout, color.label("--- copy prompt ---"));
49748
- writeLine(stdout, AGENT_SETUP_PROMPT);
50424
+ writeLine(stdout, renderSetupTemplate(AGENT_SETUP_PROMPT, urls));
49749
50425
  writeLine(stdout, color.label("--- end prompt ---"));
49750
- if (installedSkills.length > 0) {
50426
+ if (installedAgentFiles.length > 0) {
49751
50427
  writeLine(stdout);
49752
- writeLine(stdout, color.label("Installed local Quire skill:"));
49753
- for (const path of installedSkills) writeLine(stdout, ` ${color.success("✓")} ${path}`);
50428
+ writeLine(stdout, color.label("Installed local Quire agent files:"));
50429
+ for (const path of installedAgentFiles) writeLine(stdout, ` ${color.success("✓")} ${path}`);
50430
+ }
50431
+ const authConfigured = await completeInteractiveLogin({
50432
+ interactive,
50433
+ env,
50434
+ store: authStore$1,
50435
+ login: options.login ?? runLogin,
50436
+ io: options.io
50437
+ });
50438
+ writeLine(stdout);
50439
+ writeLine(stdout, color.label("Next steps:"));
50440
+ writeLine(stdout, ` 1. Give the copy prompt above to your coding agent so it creates ${color.path(".quire/runbook.md")}.`);
50441
+ if (authConfigured) writeLine(stdout, ` 2. Run ${color.command("quire doctor")} to confirm this repo is ready.`);
50442
+ 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.`);
50443
+ writeLine(stdout, ` 3. Ask your coding agent to run a small Quire smoke verification once ${color.path(".quire/runbook.md")} and auth are ready.`);
50444
+ }
50445
+ async function completeInteractiveLogin(options) {
50446
+ if (!options.interactive) return false;
50447
+ const stdout = options.io?.stdout ?? process.stdout;
50448
+ if (await resolveAuthCredentials({
50449
+ store: options.store,
50450
+ env: options.env
50451
+ }) !== null) {
50452
+ log.success("Quire authentication is already configured.", {
50453
+ output: stdout,
50454
+ spacing: 0
50455
+ });
50456
+ return true;
49754
50457
  }
50458
+ await options.login({
50459
+ apiBaseUrl: readApiBaseUrl(options.env.QUIRE_API_URL),
50460
+ store: options.store,
50461
+ io: options.io
50462
+ });
50463
+ return true;
49755
50464
  }
49756
50465
  async function selectSkillInstallTargets(options) {
49757
50466
  if (options.skill === false || options.skill === "none") return [];
@@ -49764,7 +50473,11 @@ async function selectSkillInstallTargets(options) {
49764
50473
  value: target.id,
49765
50474
  hint: target.hint
49766
50475
  })),
49767
- initialValues: ["agents", "claude"],
50476
+ initialValues: [
50477
+ "agents",
50478
+ "claude",
50479
+ "cursor"
50480
+ ],
49768
50481
  required: false
49769
50482
  });
49770
50483
  if (isCancel(selected) || selected.length === 0) return [];
@@ -49780,19 +50493,121 @@ function resolveSkillTargets(value) {
49780
50493
  }
49781
50494
  return [...selected.values()];
49782
50495
  }
49783
- async function installQuireSkill(homeDir, targets) {
50496
+ function setupUrls(env) {
50497
+ const baseUrl = publicBaseUrl(env);
50498
+ return {
50499
+ setupUrl: new URL("/agent-setup", baseUrl).toString(),
50500
+ verificationSkillUrl: new URL("/agent-verification", baseUrl).toString()
50501
+ };
50502
+ }
50503
+ function publicBaseUrl(env) {
50504
+ const configured = env.QUIRE_SKILL_BASE_URL ?? env.QUIRE_PUBLIC_URL ?? env.QUIRE_API_URL;
50505
+ if (configured === void 0 || configured.trim().length === 0) return DEFAULT_PUBLIC_BASE_URL;
50506
+ return configured;
50507
+ }
50508
+ function renderSetupTemplate(contents, urls) {
50509
+ return contents.replaceAll(SETUP_URL_PLACEHOLDER, urls.setupUrl).replaceAll(VERIFICATION_SKILL_URL_PLACEHOLDER, urls.verificationSkillUrl);
50510
+ }
50511
+ async function installQuireAgentFiles(homeDir, targets, urls) {
49784
50512
  const installed = [];
50513
+ if (targets.length === 0) return installed;
50514
+ await removeLegacyQuireVerifierFiles(homeDir);
50515
+ await removeLegacyQuireSkillFiles(homeDir);
49785
50516
  await Promise.all(targets.map(async (target) => {
49786
- const path = join(homeDir, target.targetPath);
49787
- await mkdir(dirname(path), {
50517
+ for (const skill of target.skillPaths) {
50518
+ const skillPath = join(homeDir, skill.path);
50519
+ await mkdir(dirname(skillPath), {
50520
+ recursive: true,
50521
+ mode: 448
50522
+ });
50523
+ await writeFile(skillPath, renderSetupTemplate(skill.contents, urls), { mode: 384 });
50524
+ installed.push(skillPath);
50525
+ }
50526
+ const agentPath = join(homeDir, target.agentPath);
50527
+ await mkdir(dirname(agentPath), {
49788
50528
  recursive: true,
49789
50529
  mode: 448
49790
50530
  });
49791
- await writeFile(path, QUIRE_SKILL_MARKDOWN, { mode: 384 });
49792
- installed.push(path);
50531
+ await writeFile(agentPath, renderSetupTemplate(target.agentContents ?? QUIRE_AGENT_MARKDOWN, urls), { mode: 384 });
50532
+ installed.push(agentPath);
50533
+ if (target.codexAgentPath !== void 0) {
50534
+ const codexAgentPath = join(homeDir, target.codexAgentPath);
50535
+ await mkdir(dirname(codexAgentPath), {
50536
+ recursive: true,
50537
+ mode: 448
50538
+ });
50539
+ await writeFile(codexAgentPath, renderSetupTemplate(QUIRE_CODEX_TOML, urls), { mode: 384 });
50540
+ installed.push(codexAgentPath);
50541
+ }
50542
+ if (target.opencodeAgentPath !== void 0) {
50543
+ const opencodeAgentPath = join(homeDir, target.opencodeAgentPath);
50544
+ await mkdir(dirname(opencodeAgentPath), {
50545
+ recursive: true,
50546
+ mode: 448
50547
+ });
50548
+ await writeFile(opencodeAgentPath, renderSetupTemplate(QUIRE_OPENCODE_MARKDOWN, urls), { mode: 384 });
50549
+ installed.push(opencodeAgentPath);
50550
+ }
50551
+ if (target.piAgentPath !== void 0) {
50552
+ const piAgentPath = join(homeDir, target.piAgentPath);
50553
+ await mkdir(dirname(piAgentPath), {
50554
+ recursive: true,
50555
+ mode: 448
50556
+ });
50557
+ await writeFile(piAgentPath, renderSetupTemplate(QUIRE_AGENT_MARKDOWN, urls), { mode: 384 });
50558
+ installed.push(piAgentPath);
50559
+ }
49793
50560
  }));
49794
50561
  return installed.sort();
49795
50562
  }
50563
+ async function removeLegacyQuireVerifierFiles(homeDir) {
50564
+ await Promise.all(LEGACY_QUIRE_VERIFIER_PATHS.map(async (legacyPath) => {
50565
+ const path = join(homeDir, legacyPath);
50566
+ try {
50567
+ if (isLegacyQuireVerifierFile(await readFile(path, "utf8"))) {
50568
+ await rm(path, { force: true });
50569
+ await removeEmptyDirectory(dirname(path));
50570
+ }
50571
+ } catch (error) {
50572
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
50573
+ await removeEmptyDirectory(dirname(path));
50574
+ return;
50575
+ }
50576
+ throw error;
50577
+ }
50578
+ }));
50579
+ }
50580
+ async function removeLegacyQuireSkillFiles(homeDir) {
50581
+ await Promise.all(LEGACY_QUIRE_SKILL_PATHS.map(async (legacyPath) => {
50582
+ const path = join(homeDir, legacyPath);
50583
+ try {
50584
+ if (isLegacyQuireSkillFile(await readFile(path, "utf8"))) {
50585
+ await rm(path, { force: true });
50586
+ await removeEmptyDirectory(dirname(path));
50587
+ }
50588
+ } catch (error) {
50589
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
50590
+ await removeEmptyDirectory(dirname(path));
50591
+ return;
50592
+ }
50593
+ throw error;
50594
+ }
50595
+ }));
50596
+ }
50597
+ async function removeEmptyDirectory(path) {
50598
+ try {
50599
+ await rmdir(path);
50600
+ } catch (error) {
50601
+ if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTEMPTY" || error.code === "EEXIST" || error.code === "EISDIR")) return;
50602
+ throw error;
50603
+ }
50604
+ }
50605
+ function isLegacyQuireVerifierFile(contents) {
50606
+ return contents.includes("Quire") && contents.includes("quire wait") && (contents.includes("quire-verifier") || contents.includes("Quire verifier"));
50607
+ }
50608
+ function isLegacyQuireSkillFile(contents) {
50609
+ return contents.includes("name: quire") && contents.includes("/agent-verification") && contents.includes("native subagent named `quire`");
50610
+ }
49796
50611
  //#endregion
49797
50612
  //#region src/commands/whoami.ts
49798
50613
  const whoamiCommand = defineCommand({
@@ -49943,7 +50758,7 @@ function formatWalletBalance(value) {
49943
50758
  }
49944
50759
  //#endregion
49945
50760
  //#region package.json
49946
- var version$1 = "0.0.11";
50761
+ var version$1 = "0.0.13";
49947
50762
  //#endregion
49948
50763
  //#region src/cli.ts
49949
50764
  const subCommands = {
@@ -49974,6 +50789,7 @@ const subCommands = {
49974
50789
  setup: setupCommand,
49975
50790
  status: statusCommand,
49976
50791
  sync: syncCommand,
50792
+ wait: waitCommand,
49977
50793
  watch: watchCommand
49978
50794
  };
49979
50795
  const subCommandAliases = new Set(["me", "resume"]);