@quireco/cli 0.0.9 → 0.0.11

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.
@@ -1,7 +1,7 @@
1
1
  import { n as __require, r as __toESM, t as __commonJSMin } from "./chunk-e9Ob2GDo.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { createMain, defineCommand, parseArgs, renderUsage, runCommand, showUsage } from "citty";
4
- import { isCancel, log, note, spinner, text } from "@clack/prompts";
4
+ import { isCancel, log, multiselect, note, spinner, text } from "@clack/prompts";
5
5
  import { loginGitHubCopilot, loginOpenAICodex } from "@earendil-works/pi-ai/oauth";
6
6
  import { AuthStorage, DefaultResourceLoader, ModelRegistry, SessionManager, SettingsManager, createAgentSession, defineTool, getAgentDir } from "@earendil-works/pi-coding-agent";
7
7
  import { exec, execFile, execFileSync, spawn } from "node:child_process";
@@ -722,7 +722,7 @@ function createQuireCreditsStream(options) {
722
722
  const output = createEmptyAssistantMessage(model);
723
723
  try {
724
724
  const accessToken = streamOptions?.apiKey;
725
- if (!accessToken) throw new CliError$1("Not logged in. Run `quire login` before `quire investigate` so Quire Credits can be checked.", ExitCode.AuthFailure);
725
+ if (!accessToken) throw new CliError$1("Not logged in. Run `quire login` before `quire run` so Quire Credits can be checked.", ExitCode.AuthFailure);
726
726
  stream.push({
727
727
  type: "start",
728
728
  partial: output
@@ -753,7 +753,7 @@ function createQuireCreditsStream(options) {
753
753
  }
754
754
  async function readValidatedQuireCredentials(options) {
755
755
  const credentials = await resolveAuthCredentials({ store: options.store });
756
- if (!credentials) throw new CliError$1("Not logged in. Run `quire login` or set QUIRE_API_TOKEN before `quire investigate` so Quire Credits can be checked.", ExitCode.AuthFailure);
756
+ if (!credentials) throw new CliError$1("Not logged in. Run `quire login` or set QUIRE_API_TOKEN before `quire run` so Quire Credits can be checked.", ExitCode.AuthFailure);
757
757
  if (credentials.tokenType === "QuireApiKey") return credentials;
758
758
  const sessionLookup = await fetchCurrentSession({
759
759
  apiBaseUrl: credentials.apiBaseUrl,
@@ -1410,6 +1410,9 @@ function runStatusPath(runPath) {
1410
1410
  function runProgressPath(runPath) {
1411
1411
  return join(resolve(runPath), "progress.jsonl");
1412
1412
  }
1413
+ function runPlanPath(runPath) {
1414
+ return join(resolve(runPath), "plan.md");
1415
+ }
1413
1416
  function runHandoffPath(runPath) {
1414
1417
  return join(resolve(runPath), "handoff.md");
1415
1418
  }
@@ -1418,6 +1421,7 @@ function runDirPaths(root) {
1418
1421
  return {
1419
1422
  status: runStatusPath(root),
1420
1423
  progress: runProgressPath(root),
1424
+ plan: runPlanPath(root),
1421
1425
  handoff: runHandoffPath(root),
1422
1426
  evidence,
1423
1427
  screenshots: join(evidence, "screenshots"),
@@ -1466,6 +1470,7 @@ function appendRunProgressEvent(runDir, event) {
1466
1470
  seq
1467
1471
  };
1468
1472
  appendLineAtomically(runDir.paths.progress, `${stableJson(progressEvent)}\n`);
1473
+ if (progressEvent.kind === "plan" && progressEvent.plan !== void 0) writeRunPlan(runDir, progressEvent);
1469
1474
  return progressEvent;
1470
1475
  });
1471
1476
  }
@@ -1499,6 +1504,45 @@ function patchRunStatusJson(runDir, patch) {
1499
1504
  function runProgressEventId(seq) {
1500
1505
  return `progress-${seq.toString().padStart(6, "0")}`;
1501
1506
  }
1507
+ function writeRunPlan(runDir, event) {
1508
+ const plan = event.plan;
1509
+ if (plan === void 0) return;
1510
+ const lines = [
1511
+ "# Evidence plan",
1512
+ "",
1513
+ `Run: ${runDir.runId}`,
1514
+ `Plan: ${plan.id}`,
1515
+ `Recorded: ${event.ts}`,
1516
+ ...plan.supersedes === void 0 ? [] : [`Supersedes: ${plan.supersedes}`],
1517
+ ...plan.reason === void 0 ? [] : [`Reason: ${plan.reason}`],
1518
+ "",
1519
+ event.summary === void 0 ? void 0 : `## Summary\n\n${event.summary}\n`,
1520
+ "## Steps",
1521
+ "",
1522
+ ...plan.steps.flatMap((step, index) => [
1523
+ `${index + 1}. **${step.title}** (${step.purpose})`,
1524
+ ...step.expectedOutcome === void 0 ? [] : [` - Expected: ${step.expectedOutcome}`],
1525
+ ...step.assertionIds === void 0 || step.assertionIds.length === 0 ? [] : [` - Assertions: ${step.assertionIds.join(", ")}`]
1526
+ ]),
1527
+ ""
1528
+ ].filter((line) => line !== void 0);
1529
+ writeFileAtomically(runDir.paths.plan, `${lines.join("\n").trimEnd()}\n`, 384);
1530
+ patchRunStatusJson(runDir, {
1531
+ files: {
1532
+ status: "status.json",
1533
+ progress: "progress.jsonl",
1534
+ plan: "plan.md",
1535
+ handoff: "handoff.md",
1536
+ evidence: "evidence"
1537
+ },
1538
+ plan: {
1539
+ path: "plan.md",
1540
+ progressEventId: event.id,
1541
+ planId: plan.id,
1542
+ updatedAt: event.ts
1543
+ }
1544
+ });
1545
+ }
1502
1546
  function readRepoSha(repoPath) {
1503
1547
  try {
1504
1548
  return execFileSync("git", [
@@ -1555,6 +1599,21 @@ function appendLineAtomically(filePath, line) {
1555
1599
  closeSync(file);
1556
1600
  }
1557
1601
  }
1602
+ function writeFileAtomically(filePath, contents, mode) {
1603
+ mkdirSync(dirname(filePath), {
1604
+ recursive: true,
1605
+ mode: 448
1606
+ });
1607
+ const tempPath = join(dirname(filePath), `.${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.${basename(filePath)}.tmp`);
1608
+ const file = openSync(tempPath, constants$1.O_CREAT | constants$1.O_EXCL | constants$1.O_WRONLY, mode);
1609
+ try {
1610
+ writeFileSync(file, contents, "utf8");
1611
+ fsyncSync(file);
1612
+ } finally {
1613
+ closeSync(file);
1614
+ }
1615
+ renameSync(tempPath, filePath);
1616
+ }
1558
1617
  function nextJsonlSequence(filePath) {
1559
1618
  try {
1560
1619
  const file = openSync(filePath, constants$1.O_RDONLY);
@@ -1618,6 +1677,7 @@ function createRunStatus(runDir, input) {
1618
1677
  files: {
1619
1678
  status: "status.json",
1620
1679
  progress: "progress.jsonl",
1680
+ plan: "plan.md",
1621
1681
  handoff: "handoff.md",
1622
1682
  evidence: "evidence"
1623
1683
  },
@@ -1625,6 +1685,7 @@ function createRunStatus(runDir, input) {
1625
1685
  status: input.status,
1626
1686
  createdAt: timestamp,
1627
1687
  updatedAt: timestamp,
1688
+ lastActivityAt: timestamp,
1628
1689
  ...input.status === "running" ? { startedAt: timestamp } : {},
1629
1690
  ...input.pid === void 0 ? {} : { pid: input.pid },
1630
1691
  ...input.error === void 0 ? {} : { error: input.error }
@@ -1648,6 +1709,22 @@ function updateRunStatus(runPath, patch) {
1648
1709
  writeStatus(runStatusPath(runPath), status);
1649
1710
  return status;
1650
1711
  }
1712
+ function touchRunActivity(runPath, options = {}) {
1713
+ const existing = readRunStatus(runPath);
1714
+ if (existing === void 0) throw new CliError$1(`Run status not found: ${runPath}`, ExitCode.InvalidInput);
1715
+ const now = options.now ?? /* @__PURE__ */ new Date();
1716
+ const minIntervalMs = options.minIntervalMs ?? 5e3;
1717
+ const previous = existing.lastActivityAt ?? existing.updatedAt;
1718
+ const previousTime = Date.parse(previous);
1719
+ if (Number.isFinite(previousTime) && now.getTime() - previousTime < minIntervalMs) return existing;
1720
+ const timestamp = now.toISOString();
1721
+ const status = {
1722
+ ...existing,
1723
+ lastActivityAt: timestamp
1724
+ };
1725
+ writeStatus(runStatusPath(runPath), status);
1726
+ return status;
1727
+ }
1651
1728
  function readRunStatus(runPath) {
1652
1729
  const filePath = runStatusPath(runPath);
1653
1730
  if (!existsSync(filePath)) return;
@@ -1765,6 +1842,7 @@ function parseRunStatus(value) {
1765
1842
  status: value.status,
1766
1843
  createdAt: value.createdAt,
1767
1844
  updatedAt: value.updatedAt,
1845
+ ...typeof value.lastActivityAt === "string" ? { lastActivityAt: value.lastActivityAt } : {},
1768
1846
  ...typeof value.pid === "number" ? { pid: value.pid } : {},
1769
1847
  ...typeof value.startedAt === "string" ? { startedAt: value.startedAt } : {},
1770
1848
  ...typeof value.completedAt === "string" ? { completedAt: value.completedAt } : {},
@@ -1772,6 +1850,7 @@ function parseRunStatus(value) {
1772
1850
  ...value.agent === void 0 ? {} : { agent: value.agent },
1773
1851
  ...value.request === void 0 ? {} : { request: value.request },
1774
1852
  ...value.metadata === void 0 ? {} : { metadata: value.metadata },
1853
+ ...value.plan === void 0 ? {} : { plan: value.plan },
1775
1854
  ...value.harnessSession === void 0 ? {} : { harnessSession: value.harnessSession },
1776
1855
  ...value.finalHandoff === void 0 ? {} : { finalHandoff: value.finalHandoff },
1777
1856
  ...value.finalResult === void 0 ? {} : { finalResult: value.finalResult },
@@ -1783,12 +1862,14 @@ function parseRunStatusFiles(value) {
1783
1862
  if (isRecord$14(value) && typeof value.status === "string" && typeof value.progress === "string" && typeof value.handoff === "string" && typeof value.evidence === "string") return {
1784
1863
  status: value.status,
1785
1864
  progress: value.progress,
1865
+ ...typeof value.plan === "string" ? { plan: value.plan } : {},
1786
1866
  handoff: value.handoff,
1787
1867
  evidence: value.evidence
1788
1868
  };
1789
1869
  return {
1790
1870
  status: "status.json",
1791
1871
  progress: "progress.jsonl",
1872
+ plan: "plan.md",
1792
1873
  handoff: "handoff.md",
1793
1874
  evidence: "evidence"
1794
1875
  };
@@ -2288,7 +2369,7 @@ const BrowserToolInputSchema = Type.Object({
2288
2369
  argv: Type.Array(Type.String(), { minItems: 1 }),
2289
2370
  cwd: Type.Optional(Type.String({ minLength: 1 }))
2290
2371
  }, { additionalProperties: false });
2291
- function makeBrowserTool({ runDir, sessionId, cwd: defaultCwd, headed = false, agentBrowserExecutableResolver = resolveAgentBrowserExecutable }) {
2372
+ function makeBrowserTool({ runDir, sessionId, cwd: defaultCwd, headed = false, onProgressEvent, agentBrowserExecutableResolver = resolveAgentBrowserExecutable }) {
2292
2373
  const captureState = { videoStarted: false };
2293
2374
  return createAgentCliTool({
2294
2375
  name: "agent-browser",
@@ -2302,6 +2383,7 @@ function makeBrowserTool({ runDir, sessionId, cwd: defaultCwd, headed = false, a
2302
2383
  defaultCwd,
2303
2384
  headed,
2304
2385
  captureState,
2386
+ onProgressEvent,
2305
2387
  agentBrowserExecutableResolver,
2306
2388
  input,
2307
2389
  signal
@@ -2376,7 +2458,7 @@ async function runBestEffortBrowserCommand({ executable, args, cwd, timeoutMs })
2376
2458
  function parseBrowserToolInput(input) {
2377
2459
  return parseAgentCliToolInput(BrowserToolInputSchema, input, "agent-browser");
2378
2460
  }
2379
- async function runBrowserCommand({ runDir, sessionId, defaultCwd, headed, captureState, agentBrowserExecutableResolver, input, signal }) {
2461
+ async function runBrowserCommand({ runDir, sessionId, defaultCwd, headed, captureState, onProgressEvent, agentBrowserExecutableResolver, input, signal }) {
2380
2462
  const [executable, ...args] = input.argv;
2381
2463
  if (executable !== "agent-browser") throw new Error(`Refusing to spawn non-agent-browser executable: ${executable}`);
2382
2464
  const cwd = input.cwd ?? defaultCwd ?? process.cwd();
@@ -2394,6 +2476,7 @@ async function runBrowserCommand({ runDir, sessionId, defaultCwd, headed, captur
2394
2476
  cwd,
2395
2477
  headed,
2396
2478
  captureState,
2479
+ onProgressEvent,
2397
2480
  agentBrowserExecutableResolver,
2398
2481
  signal
2399
2482
  });
@@ -2463,11 +2546,12 @@ function spawnAgentBrowser({ args, sessionId, cwd, agentBrowserExecutableResolve
2463
2546
  });
2464
2547
  });
2465
2548
  }
2466
- async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, headed, captureState, agentBrowserExecutableResolver, signal }) {
2549
+ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, headed, captureState, onProgressEvent, agentBrowserExecutableResolver, signal }) {
2467
2550
  const stdout = [];
2468
2551
  const stderr = [];
2469
2552
  for (const command of commands) {
2470
2553
  const args = applyBrowserLaunchArgs(applyRunDefaults(applyHeadedMode(command.args, headed), runDir));
2554
+ const commandLabel = browserCommandLabel(args);
2471
2555
  const result = await spawnAgentBrowser({
2472
2556
  args,
2473
2557
  sessionId,
@@ -2496,12 +2580,22 @@ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, hea
2496
2580
  signal
2497
2581
  }));
2498
2582
  stderr.push(result.stderr);
2499
- if (!result.ok) return {
2500
- ok: false,
2501
- exitCode: result.exitCode,
2502
- stdout: stdout.join(""),
2503
- stderr: stderr.join("")
2504
- };
2583
+ if (!result.ok) {
2584
+ emitBrowserProgress(runDir, onProgressEvent, {
2585
+ kind: "action",
2586
+ status: "failed",
2587
+ title: `Browser: ${commandLabel}`,
2588
+ summary: oneLine(result.stderr || result.stdout || "agent-browser command failed"),
2589
+ importance: "normal",
2590
+ source: "runtime"
2591
+ });
2592
+ return {
2593
+ ok: false,
2594
+ exitCode: result.exitCode,
2595
+ stdout: stdout.join(""),
2596
+ stderr: stderr.join("")
2597
+ };
2598
+ }
2505
2599
  }
2506
2600
  return {
2507
2601
  ok: true,
@@ -2510,6 +2604,24 @@ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, hea
2510
2604
  stderr: stderr.join("")
2511
2605
  };
2512
2606
  }
2607
+ function emitBrowserProgress(runDir, onProgressEvent, event) {
2608
+ const logged = appendRunProgressEvent(runDir, event);
2609
+ onProgressEvent?.(logged);
2610
+ }
2611
+ function browserCommandLabel(args) {
2612
+ const command = args[0] ?? "command";
2613
+ if (command === "open") return `open ${redactBrowserArg(args[1] ?? "URL")}`;
2614
+ if (command === "batch") return "batch";
2615
+ return command;
2616
+ }
2617
+ function redactBrowserArg(value) {
2618
+ try {
2619
+ const url = new URL(value);
2620
+ return `${url.origin}${url.pathname}`;
2621
+ } catch {
2622
+ return value.length > 80 ? `${value.slice(0, 77)}…` : value;
2623
+ }
2624
+ }
2513
2625
  function augmentBrowserArtifactOutput({ runDir, sessionId, cwd, args, result, agentBrowserExecutableResolver, signal }) {
2514
2626
  if (!result.ok) return Promise.resolve(result.stdout);
2515
2627
  const output = [result.stdout];
@@ -2605,27 +2717,35 @@ function hasOption$1(args, option) {
2605
2717
  return args.some((arg) => arg === option || arg.startsWith(`${option}=`));
2606
2718
  }
2607
2719
  function shouldStartRecordingForCommand(args) {
2608
- return args[0] === "open";
2720
+ const command = args[0];
2721
+ return command !== void 0 && RECORDING_START_COMMANDS.has(command);
2609
2722
  }
2610
2723
  function shouldCaptureScreenshotAfterCommand$1(args) {
2611
2724
  const command = args[0];
2612
- return command !== void 0 && ![
2613
- "close",
2614
- "console",
2615
- "cookies",
2616
- "diff",
2617
- "errors",
2618
- "network",
2619
- "pdf",
2620
- "profiler",
2621
- "record",
2622
- "screenshot",
2623
- "set",
2624
- "skills",
2625
- "storage",
2626
- "trace"
2627
- ].includes(command);
2725
+ return command !== void 0 && AUTO_SCREENSHOT_COMMANDS.has(command);
2628
2726
  }
2727
+ const RECORDING_START_COMMANDS = new Set([
2728
+ "back",
2729
+ "check",
2730
+ "click",
2731
+ "dblclick",
2732
+ "drag",
2733
+ "fill",
2734
+ "forward",
2735
+ "open",
2736
+ "press",
2737
+ "reload",
2738
+ "select",
2739
+ "type",
2740
+ "uncheck",
2741
+ "upload"
2742
+ ]);
2743
+ const AUTO_SCREENSHOT_COMMANDS = new Set([
2744
+ "back",
2745
+ "forward",
2746
+ "open",
2747
+ "reload"
2748
+ ]);
2629
2749
  //#endregion
2630
2750
  //#region src/run-artifacts.ts
2631
2751
  function relativeRunPath(runDir, path) {
@@ -2670,14 +2790,19 @@ function publicRunProgressEvent(runDir, event) {
2670
2790
  //#region src/pipeline/handoff-materializer.ts
2671
2791
  function materializeHandoff({ handoff, runDir, continuation, modelUsage, metadata, harness }) {
2672
2792
  const result = toInvestigationResult(handoff, runDir, continuation, modelUsage);
2673
- const nextMetadata = updateMetadataWithHandoff(runDir, metadata, handoff, result, harness);
2793
+ const materializedHandoff = materializeHandoffMarkdown(handoff, result.evidence);
2794
+ const materializedResult = {
2795
+ ...result,
2796
+ handoffMarkdown: materializedHandoff.handoffMarkdown
2797
+ };
2798
+ const nextMetadata = updateMetadataWithHandoff(runDir, metadata, materializedHandoff, materializedResult, harness);
2674
2799
  writeRunFinalHandoff(runDir, {
2675
- handoff,
2676
- result
2800
+ handoff: materializedHandoff,
2801
+ result: materializedResult
2677
2802
  });
2678
- writeHandoffArtifact(runDir, handoff);
2803
+ writeHandoffArtifact(runDir, materializedHandoff);
2679
2804
  return {
2680
- result,
2805
+ result: materializedResult,
2681
2806
  metadata: nextMetadata
2682
2807
  };
2683
2808
  }
@@ -2714,13 +2839,20 @@ function toInvestigationResult(handoff, runDir, continuation, modelUsage) {
2714
2839
  }
2715
2840
  function appendAutoCapturedEvidence(evidence, runDir) {
2716
2841
  const seenPaths = new Set(evidence.flatMap((item) => item.path === void 0 ? [] : [item.path]));
2717
- const autoEvidence = [...readAutoCapturedFiles(runDir.paths.screenshots, runDir, "screenshot"), ...readAutoCapturedFiles(runDir.paths.video, runDir, "recording")].filter((item) => {
2842
+ const autoEvidence = [...selectAutoScreenshotEvidence(evidence, readAutoCapturedFiles(runDir.paths.screenshots, runDir, "screenshot")), ...readAutoCapturedFiles(runDir.paths.video, runDir, "recording")].filter((item) => {
2718
2843
  if (item.path === void 0 || seenPaths.has(item.path)) return false;
2719
2844
  seenPaths.add(item.path);
2720
2845
  return true;
2721
2846
  });
2722
2847
  return [...evidence, ...autoEvidence];
2723
2848
  }
2849
+ function selectAutoScreenshotEvidence(handoffEvidence, autoScreenshots) {
2850
+ if (handoffEvidence.some((item) => item.kind === "screenshot")) return [];
2851
+ if (autoScreenshots.length <= 2) return autoScreenshots;
2852
+ const first = autoScreenshots[0];
2853
+ const last = autoScreenshots[autoScreenshots.length - 1];
2854
+ return first === void 0 || last === void 0 || first.path === last.path ? autoScreenshots.slice(0, 1) : [first, last];
2855
+ }
2724
2856
  function readAutoCapturedFiles(directory, runDir, kind) {
2725
2857
  if (!existsSync(directory)) return [];
2726
2858
  try {
@@ -2806,6 +2938,86 @@ function confidenceFor(handoff, evidence) {
2806
2938
  if (handoff.status !== "reproduced" || evidence.length === 0) return "low";
2807
2939
  return evidence.length >= 2 ? "high" : "medium";
2808
2940
  }
2941
+ const EVIDENCE_SECTION_START = "<!-- quire:evidence-start -->";
2942
+ const EVIDENCE_SECTION_END = "<!-- quire:evidence-end -->";
2943
+ function materializeHandoffMarkdown(handoff, evidence) {
2944
+ const evidenceSection = renderEvidenceSection(evidence);
2945
+ const handoffMarkdown = withGeneratedEvidenceSection(handoff.handoffMarkdown, evidenceSection);
2946
+ return {
2947
+ ...handoff,
2948
+ handoffMarkdown,
2949
+ evidenceRefs: evidence.map((item) => ({
2950
+ kind: item.kind,
2951
+ note: item.note,
2952
+ ...item.path === void 0 ? {} : { path: item.path },
2953
+ ...item.url === void 0 ? {} : { url: item.url }
2954
+ }))
2955
+ };
2956
+ }
2957
+ function withGeneratedEvidenceSection(markdown, evidenceSection) {
2958
+ const withoutExisting = markdown.trimEnd().replace(new RegExp(`\\n*${escapeRegExp$1(EVIDENCE_SECTION_START)}[\\s\\S]*?${escapeRegExp$1(EVIDENCE_SECTION_END)}\\s*$`), "");
2959
+ return evidenceSection.length === 0 ? withoutExisting : `${withoutExisting}\n\n${evidenceSection}`;
2960
+ }
2961
+ function renderEvidenceSection(evidence) {
2962
+ const publicEvidence = evidence.filter((item) => item.path !== void 0 || item.url !== void 0);
2963
+ if (publicEvidence.length === 0) return "";
2964
+ return [
2965
+ EVIDENCE_SECTION_START,
2966
+ "## Evidence artifacts",
2967
+ "",
2968
+ ...publicEvidence.flatMap(renderEvidenceItem),
2969
+ EVIDENCE_SECTION_END
2970
+ ].join("\n");
2971
+ }
2972
+ function renderEvidenceItem(item) {
2973
+ const label = `${evidenceDisplayKind(item)} — ${item.note}`;
2974
+ const target = evidenceTarget(item);
2975
+ if (target === void 0) return [];
2976
+ if (isImageEvidence(item)) return [
2977
+ `- ${label}`,
2978
+ ` ![${escapeMarkdownAlt(item.note)}](${target})`,
2979
+ ""
2980
+ ];
2981
+ if (isVideoEvidence(item)) return [
2982
+ `- ${label}`,
2983
+ ` <video controls src="${escapeHtmlAttribute(target)}"></video>`,
2984
+ ` [Open recording](${target})`,
2985
+ ""
2986
+ ];
2987
+ return [
2988
+ `- ${label}`,
2989
+ ` [Open artifact](${target})`,
2990
+ ""
2991
+ ];
2992
+ }
2993
+ function evidenceDisplayKind(item) {
2994
+ const kind = item.kind.replaceAll("_", " ").trim();
2995
+ return kind.length === 0 ? "Evidence" : `${kind[0]?.toUpperCase() ?? "E"}${kind.slice(1)}`;
2996
+ }
2997
+ function evidenceTarget(item) {
2998
+ if (item.url !== void 0) return item.url;
2999
+ if (item.path === void 0) return;
3000
+ return `./${item.path.replace(/^\.\//, "")}`;
3001
+ }
3002
+ function isImageEvidence(item) {
3003
+ const path = item.path?.toLowerCase() ?? "";
3004
+ const kind = item.kind.toLowerCase();
3005
+ return kind.includes("screenshot") || kind.includes("image") || path.endsWith(".png") || path.endsWith(".jpg") || path.endsWith(".jpeg") || path.endsWith(".webp") || path.endsWith(".gif");
3006
+ }
3007
+ function isVideoEvidence(item) {
3008
+ const path = item.path?.toLowerCase() ?? "";
3009
+ const kind = item.kind.toLowerCase();
3010
+ return kind.includes("recording") || kind.includes("video") || path.endsWith(".webm") || path.endsWith(".mp4");
3011
+ }
3012
+ function escapeMarkdownAlt(value) {
3013
+ return value.replace(/[[\]\\]/g, " ").replace(/\s+/g, " ").trim();
3014
+ }
3015
+ function escapeHtmlAttribute(value) {
3016
+ return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
3017
+ }
3018
+ function escapeRegExp$1(value) {
3019
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3020
+ }
2809
3021
  function updateMetadataWithHandoff(runDir, metadata, handoff, result, harness) {
2810
3022
  const sessionState = readRunSessionState(runDir);
2811
3023
  return writeRunMetadata(runDir, {
@@ -41110,11 +41322,11 @@ function errorMessage$2(error) {
41110
41322
  }
41111
41323
  //#endregion
41112
41324
  //#region ../../packages/agent-mobile/src/wait/android.ts
41113
- const DEFAULT_TIMEOUT_MS$1 = 1e4;
41325
+ const DEFAULT_TIMEOUT_MS$2 = 1e4;
41114
41326
  const POLL_INTERVAL_MS$1 = 400;
41115
41327
  async function waitAndroid(request) {
41116
41328
  const waitFor = request.waitFor ?? { kind: "settle" };
41117
- const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS$1;
41329
+ const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS$2;
41118
41330
  if (waitFor.kind === "settle") {
41119
41331
  await waitForSettle$1(request, waitFor, timeoutMs);
41120
41332
  return;
@@ -41192,11 +41404,11 @@ function sleep$1(ms) {
41192
41404
  }
41193
41405
  //#endregion
41194
41406
  //#region ../../packages/agent-mobile/src/wait/ios.ts
41195
- const DEFAULT_TIMEOUT_MS = 1e4;
41407
+ const DEFAULT_TIMEOUT_MS$1 = 1e4;
41196
41408
  const POLL_INTERVAL_MS = 400;
41197
41409
  async function waitIos(request) {
41198
41410
  const waitFor = request.waitFor ?? { kind: "settle" };
41199
- const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS;
41411
+ const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS$1;
41200
41412
  if (waitFor.kind === "settle") {
41201
41413
  await waitForSettle(request, waitFor, timeoutMs);
41202
41414
  return;
@@ -44646,48 +44858,293 @@ function shouldCaptureScreenshotAfterCommand(command) {
44646
44858
  return command !== void 0 && !["install", "screenshot"].includes(command);
44647
44859
  }
44648
44860
  //#endregion
44861
+ //#region src/tools/shell-check.ts
44862
+ const DEFAULT_TIMEOUT_MS = 12e4;
44863
+ const MAX_OUTPUT_CHARS = 6e4;
44864
+ const ShellCheckToolInputSchema = Type.Object({
44865
+ command: Type.String({ minLength: 1 }),
44866
+ cwd: Type.Optional(Type.String({ minLength: 1 })),
44867
+ timeoutMs: Type.Optional(Type.Number({
44868
+ minimum: 1e3,
44869
+ maximum: 6e5
44870
+ })),
44871
+ reason: Type.String({ minLength: 1 })
44872
+ }, { additionalProperties: false });
44873
+ function makeShellCheckTool({ runDir, cwd: defaultCwd, env = process.env }) {
44874
+ return createAgentCliTool({
44875
+ name: "run_shell_check",
44876
+ description: "Run a project shell command for evidence collection, local app startup checks, tests, typechecks, builds, lint/check commands, or source inspection. Clearly dangerous, destructive, deploy, install, database mutation, and external write commands are blocked.",
44877
+ parameters: ShellCheckToolInputSchema,
44878
+ parseInput: parseShellCheckToolInput,
44879
+ runCommand(input, signal) {
44880
+ return runShellCheckCommand({
44881
+ runDir,
44882
+ defaultCwd,
44883
+ env,
44884
+ input,
44885
+ signal
44886
+ });
44887
+ }
44888
+ });
44889
+ }
44890
+ function parseShellCheckToolInput(input) {
44891
+ return parseAgentCliToolInput(ShellCheckToolInputSchema, input, "run_shell_check");
44892
+ }
44893
+ async function runShellCheckCommand({ runDir, defaultCwd, env, input, signal }) {
44894
+ const command = input.command.trim();
44895
+ const policy = classifyShellCheckCommand(command);
44896
+ if (!policy.allowed) return {
44897
+ ok: false,
44898
+ exitCode: null,
44899
+ stdout: "",
44900
+ stderr: `Blocked by Quire shell safety policy: ${policy.reason}`,
44901
+ blocked: true,
44902
+ reason: policy.reason
44903
+ };
44904
+ const cwd = input.cwd ?? defaultCwd ?? process.cwd();
44905
+ const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
44906
+ const result = await spawnShell(command, {
44907
+ cwd,
44908
+ env,
44909
+ timeoutMs,
44910
+ signal
44911
+ });
44912
+ const artifactPath = writeShellArtifact(runDir, {
44913
+ command,
44914
+ cwd,
44915
+ reason: input.reason,
44916
+ timeoutMs,
44917
+ ...result
44918
+ });
44919
+ const output = {
44920
+ exitCode: result.exitCode,
44921
+ stdout: truncateOutput(redactSecrets(result.stdout)),
44922
+ stderr: `${truncateOutput(redactSecrets(result.stderr))}\n[quire:artifact] shell_check saved to ${artifactPath}\n`.trimStart(),
44923
+ artifactPath
44924
+ };
44925
+ return result.exitCode === 0 ? {
44926
+ ok: true,
44927
+ ...output,
44928
+ exitCode: 0
44929
+ } : {
44930
+ ok: false,
44931
+ ...output
44932
+ };
44933
+ }
44934
+ function classifyShellCheckCommand(command) {
44935
+ const trimmed = command.trim();
44936
+ if (trimmed.length === 0) return {
44937
+ allowed: false,
44938
+ reason: "command is empty"
44939
+ };
44940
+ if (/[;&|`$<>]/.test(trimmed)) return {
44941
+ allowed: false,
44942
+ reason: "shell operators, pipes, redirects, and command substitution are not allowed"
44943
+ };
44944
+ const words = splitSimpleCommand(trimmed);
44945
+ if (words === void 0 || words.length === 0) return {
44946
+ allowed: false,
44947
+ reason: "command could not be parsed as a simple command"
44948
+ };
44949
+ const dangerous = firstDangerousToken(words.map((word) => word.toLowerCase()));
44950
+ if (dangerous !== void 0) return {
44951
+ allowed: false,
44952
+ reason: `contains unsafe token ${JSON.stringify(dangerous)}`
44953
+ };
44954
+ return { allowed: true };
44955
+ }
44956
+ function splitSimpleCommand(command) {
44957
+ const words = [];
44958
+ const pattern = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^']*)'|(\S+)/g;
44959
+ let match;
44960
+ let consumed = "";
44961
+ while ((match = pattern.exec(command)) !== null) {
44962
+ consumed += match[0];
44963
+ words.push(match[1] ?? match[2] ?? match[3] ?? "");
44964
+ }
44965
+ if (words.length === 0 || consumed.replace(/\s+/g, "") !== command.replace(/\s+/g, "")) return;
44966
+ return words;
44967
+ }
44968
+ const DANGEROUS_TOKENS = new Set([
44969
+ "rm",
44970
+ "rmdir",
44971
+ "mv",
44972
+ "cp",
44973
+ "sudo",
44974
+ "chmod",
44975
+ "chown",
44976
+ "kill",
44977
+ "pkill",
44978
+ "reset",
44979
+ "restore",
44980
+ "clean",
44981
+ "checkout",
44982
+ "commit",
44983
+ "push",
44984
+ "publish",
44985
+ "deploy",
44986
+ "release",
44987
+ "install",
44988
+ "add",
44989
+ "remove",
44990
+ "unlink",
44991
+ "migrate",
44992
+ "seed",
44993
+ "drop",
44994
+ "truncate",
44995
+ "delete",
44996
+ "insert",
44997
+ "update",
44998
+ "create",
44999
+ "curl",
45000
+ "wget",
45001
+ "ssh",
45002
+ "scp",
45003
+ "rsync"
45004
+ ]);
45005
+ function firstDangerousToken(words) {
45006
+ return words.find((word) => DANGEROUS_TOKENS.has(word));
45007
+ }
45008
+ async function spawnShell(command, { cwd, env, timeoutMs, signal }) {
45009
+ return await new Promise((resolve) => {
45010
+ let stdout = "";
45011
+ let stderr = "";
45012
+ let settled = false;
45013
+ const child = spawn("/bin/sh", ["-lc", command], {
45014
+ cwd,
45015
+ env,
45016
+ stdio: [
45017
+ "ignore",
45018
+ "pipe",
45019
+ "pipe"
45020
+ ]
45021
+ });
45022
+ const settle = (exitCode, extraStderr = "") => {
45023
+ if (settled) return;
45024
+ settled = true;
45025
+ clearTimeout(timer);
45026
+ signal?.removeEventListener("abort", abort);
45027
+ resolve({
45028
+ exitCode,
45029
+ stdout,
45030
+ stderr: `${stderr}${extraStderr}`
45031
+ });
45032
+ };
45033
+ const abort = () => {
45034
+ child.kill("SIGTERM");
45035
+ settle(null, "\nCommand aborted.\n");
45036
+ };
45037
+ const timer = setTimeout(() => {
45038
+ child.kill("SIGTERM");
45039
+ settle(null, `\nCommand timed out after ${timeoutMs}ms.\n`);
45040
+ }, timeoutMs);
45041
+ signal?.addEventListener("abort", abort);
45042
+ child.stdout.on("data", (chunk) => {
45043
+ stdout += chunk.toString("utf8");
45044
+ });
45045
+ child.stderr.on("data", (chunk) => {
45046
+ stderr += chunk.toString("utf8");
45047
+ });
45048
+ child.on("error", (error) => settle(null, `\n${errorMessage$5(error)}\n`));
45049
+ child.on("close", (code) => settle(code));
45050
+ });
45051
+ }
45052
+ function writeShellArtifact(runDir, result) {
45053
+ mkdirSync(runDir.paths.trace, {
45054
+ recursive: true,
45055
+ mode: 448
45056
+ });
45057
+ const artifactPath = join(runDir.paths.trace, `shell-check-${Date.now().toString(36)}-${safeArtifactName(result.command.split(/\s+/)[0] ?? "command")}.txt`);
45058
+ writeFileSync(artifactPath, [
45059
+ `$ ${result.command}`,
45060
+ `cwd: ${result.cwd}`,
45061
+ `reason: ${result.reason}`,
45062
+ `timeoutMs: ${result.timeoutMs}`,
45063
+ `exitCode: ${result.exitCode ?? "null"}`,
45064
+ "",
45065
+ "--- stdout ---",
45066
+ redactSecrets(result.stdout),
45067
+ "",
45068
+ "--- stderr ---",
45069
+ redactSecrets(result.stderr)
45070
+ ].join("\n"), { mode: 384 });
45071
+ return artifactPath;
45072
+ }
45073
+ function truncateOutput(value) {
45074
+ if (value.length <= MAX_OUTPUT_CHARS) return value;
45075
+ return `${value.slice(0, MAX_OUTPUT_CHARS)}\n[quire:truncated] output truncated to ${MAX_OUTPUT_CHARS} characters\n`;
45076
+ }
45077
+ function redactSecrets(value) {
45078
+ return value.replace(/([A-Za-z_][A-Za-z0-9_]*(?:TOKEN|SECRET|PASSWORD|KEY)[A-Za-z0-9_]*=)([^\s]+)/gi, "$1[REDACTED]").replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, "$1[REDACTED]");
45079
+ }
45080
+ //#endregion
45081
+ //#region src/pipeline/prompts/local-run-safety-contract.ts
45082
+ const localRunSafetyContractPrompt = `Local Quire runtime contract:
45083
+
45084
+ - Obey the enabled tool list exactly; do not invent unavailable tools or capabilities.
45085
+ - Code inspection tools are read-only. Do not edit files, install dependencies, start app servers, reset repositories, or mutate user data.
45086
+ - Do not expose hidden reasoning, raw prompts, raw completions, provider credentials, API tokens, or secrets.
45087
+ - record_progress writes the user-visible progress.jsonl timeline. write_handoff is the required final action. The durable public product artifacts are progress.jsonl, handoff.md, and referenced evidence. Private harness sessions under .harness-sessions are diagnostic implementation state.`;
45088
+ //#endregion
45089
+ //#region src/pipeline/prompts/system-instructions.ts
45090
+ const systemInstructionsPrompt = `You are Quire, a read-only software run agent for investigation, verification, triage, explanation, and project-specific procedures.
45091
+
45092
+ Interpret the run brief first. Infer the user's intent naturally; do not require the user to choose a mode.
45093
+
45094
+ Resolve the target, procedure, and blockers from the user request, caller context, project runbook, available tools, and loaded skills.
45095
+
45096
+ Do not assume the run is web, mobile, or code-only before reading the brief. Use browser or mobile tools only when they are available and relevant.
45097
+
45098
+ When a tool provides its own skills, workflows, or usage guides, load the relevant guidance before using that tool for substantive work. For example, load the relevant \`agent-browser skills get ...\` content before browser-backed QA, exploration, or interaction.
45099
+
45100
+ Use read, grep, find, and ls for read-only repository inspection when code can clarify routes, screens, feature names, state requirements, verification commands, or likely causes.
45101
+
45102
+ Code inspection can guide exploration, but product behavior claims need direct evidence from the requested surface when the run requires browser or mobile observation.
45103
+
45104
+ Before broad exploration or target interaction, call record_progress with kind plan and a concise grounded plan.
45105
+
45106
+ Call write_handoff exactly once when the run is complete or blocked.`;
45107
+ //#endregion
44649
45108
  //#region src/pipeline/run-instructions.ts
44650
- const DEFAULT_RUN_INSTRUCTIONS_ID = "local-run-2026-06-02";
45109
+ const DEFAULT_RUN_INSTRUCTIONS_ID = "local-run-2026-06-06";
44651
45110
  const DEFAULT_REMOTE_RUN_ADDONS_ID = "local-none";
44652
- const LOCAL_RUN_SAFETY_CONTRACT = [
44653
- "Local Quire runtime contract:",
44654
- "- Obey the enabled tool list exactly; do not invent unavailable tools or capabilities.",
44655
- "- Code inspection tools are read-only. Do not edit files, install dependencies, start app servers, reset repositories, or mutate user data.",
44656
- "- Do not expose hidden reasoning, raw prompts, raw completions, provider credentials, API tokens, or secrets.",
44657
- "- record_progress writes the user-visible progress.jsonl timeline. write_handoff is the required final action. The durable public product artifacts are progress.jsonl, handoff.md, and referenced evidence. Private harness sessions under .harness-sessions are diagnostic implementation state."
44658
- ].join("\n");
44659
- const SYSTEM_INSTRUCTIONS = [
44660
- "You are Quire, a read-only software run agent for investigation, verification, triage, explanation, and project-specific procedures.",
44661
- "Interpret the run brief first. Infer the user's intent naturally; do not require the user to choose a mode.",
44662
- "Resolve the target, procedure, and blockers from the user request, caller context, project runbook, available tools, and loaded skills.",
44663
- "Do not assume the run is web, mobile, or code-only before reading the brief. Use browser or mobile tools only when they are available and relevant.",
44664
- "Use read, grep, find, and ls for read-only repository inspection when code can clarify routes, screens, feature names, state requirements, verification commands, or likely causes.",
44665
- "Code inspection can guide exploration, but product behavior claims need direct evidence from the requested surface when the run requires browser or mobile observation.",
44666
- "Before broad exploration or target interaction, call record_progress with kind plan and a concise grounded plan.",
44667
- "Call write_handoff exactly once when the run is complete or blocked."
44668
- ].join("\n");
45111
+ const LOCAL_RUN_SAFETY_CONTRACT = localRunSafetyContractPrompt.trim();
45112
+ const SYSTEM_INSTRUCTIONS = systemInstructionsPrompt.trim();
44669
45113
  const COMMON_GUIDANCE = [
44670
45114
  "Start by understanding the user request, inferred intent, caller context, runtime capabilities, and project runbook status.",
44671
45115
  "Read .quire/runbook.md when target context, verification procedures, deployments, local URLs, devices, accounts, or safety conventions are needed.",
44672
45116
  "If the runbook is missing or insufficient, report the smallest missing context instead of guessing ports, app IDs, accounts, or deployment names.",
44673
45117
  "Prefer deterministic setup scripts or skills from the runbook over manually repeating login, seed-data, runner, or browser-state setup.",
45118
+ "Use loaded skills as optional QA lenses, not mandatory modes. Select the lenses relevant to the request, target, and available evidence.",
45119
+ "Verification can target browser apps, CLI tools, APIs, mobile apps, docs, static sites, or code paths. Choose the relevant lenses and tools for the target rather than forcing every run through browser UI checks.",
45120
+ "When claiming accessibility, design, responsive, link/form, workflow, CLI, API, mobile, command, or smoke verification, state the evidence boundary. If the boundary was not exercised, mark the check untested or blocked instead of passing it.",
44674
45121
  "For direct product tasks, perform the requested workflow and report what happened rather than reframing it as a bug.",
44675
45122
  "For bug reports, reproduce or rule out the reported behavior honestly with concrete evidence.",
45123
+ "When the target is a localhost or dev-server URL, classify framework toolbars, debug panels, dev-only overlays, hot-reload artifacts, and transient local dependency noise as local-dev caveats unless they block the requested check or clearly affect the built/preview/production surface.",
45124
+ "Separate product findings from environment caveats: a broken CTA, inaccessible interaction, or weak visual hierarchy is a product finding; an Astro/Vite/dev toolbar covering a screenshot, a DEV-gated tuning panel, or a local-only dependency fetch warning is an environment caveat unless it reproduces outside dev.",
45125
+ "If local dev caveats obstruct visual evidence or runtime confidence and the request needs production-like assurance, recommend or perform a preview/production-build check when safe and available. Otherwise report the caveat without treating it as a user-facing failure.",
44676
45126
  "Stay within the target product, URLs, devices, commands, and systems named by the request, caller context, or runbook."
44677
45127
  ];
44678
45128
  const PLANNING_GUIDANCE = [
44679
45129
  "For any non-trivial run, record a plan before taking broad exploratory actions or interacting with browser/mobile targets.",
45130
+ "The latest structured plan is materialized to plan.md in the run directory for humans and caller agents. Make it useful as an evidence plan: name the claims, boundaries, and evidence sources you expect to gather.",
44680
45131
  "Ground the plan in the user request, caller context, project runbook, and source/config inspection. Do not invent app paths, ports, setup state, accounts, or device IDs.",
44681
45132
  "Match each planned assertion to the scope it claims. A file-level or code-only assertion can be supported by file, command, or test evidence; an end-to-end, integration, sync, API, persistence, auth, billing, storage, deployment, browser, or mobile assertion needs evidence from the relevant boundary that makes the claim true.",
44682
45133
  "If a required boundary cannot be inspected or exercised with available tools, narrow the assertion to what was actually verified or mark the broader assertion as untested or blocked.",
44683
45134
  "For verification-like requests, name the assertions you will check and later record each assertion as passed, failed, untested, or blocked.",
45135
+ "For verification requests, plan the relevant evidence paths early across available tools: shell checks for builds/tests/typechecks/CLI behavior, browser evidence for web product behavior, mobile evidence for app/device behavior, source/config inspection for implementation boundaries, and MCP/API evidence when configured.",
45136
+ "For post-fix verification, include relevant deterministic project checks from .quire/runbook.md, package scripts, or explicit caller instructions when they are safe and applicable. Use run_shell_check so the command evidence lands in Quire artifacts instead of leaving caller agents to duplicate those checks.",
45137
+ "For web or mobile product verification, capture representative viewport/device evidence and exercise the important interactions, safe links/forms/CTAs, and accessibility basics needed to support the requested claims. Do not stop early while named high-value assertions remain feasible.",
44684
45138
  "For triage-like requests, name the severity, impact, ownership, reproducibility, and next-action signals you will inspect.",
44685
45139
  "For investigation-like requests, name the hypotheses, surfaces, code paths, logs, or product states you will examine.",
44686
45140
  "Record expected behavior before executing the action that tests it; this makes unexpected observations harder to rationalize as success."
44687
45141
  ];
44688
45142
  const TOOL_GUIDANCE = [
45143
+ "Use run_shell_check for non-dangerous project commands that produce useful evidence: local app startup checks, typechecks, builds, tests, lint/check commands, CLI smoke checks, custom project scripts, and read-only git/source inspection. Include the command artifact path in evidenceRefs when it supports an assertion.",
45144
+ "Do not use run_shell_check for installs, deploys, publishing, git mutation, destructive filesystem operations, database writes, seed/migrate commands, or external write requests. If such a command is required, mark it blocked or ask the caller to approve/add a safe runbook procedure.",
44689
45145
  "Use agent-browser only when the runtime capabilities say browser automation is available.",
44690
45146
  "Before browser exploration, run agent-browser skills get core once when that skill is available so browser guidance matches the installed CLI.",
45147
+ "For verification-like browser runs, especially QA, dogfooding, design review, accessibility, responsive, link, interaction, or form checks, run agent-browser skills get dogfood once before substantive browser actions when that skill is available. Use core plus dogfood; core alone is insufficient for product QA unless the run is only a tiny page-load smoke test.",
44691
45148
  "Every agent-browser invocation is recorded as evidence. Do not call non-investigation browser commands such as install, upgrade, doctor, profile, or dashboard during exploration.",
44692
45149
  "Use agent-mobile only when runtime capabilities say the mobile tool is registered. Treat device, simulator, app, and provider readiness as unknown until the tool or runbook proves them.",
44693
45150
  "Every agent-mobile invocation is recorded as evidence. Do not call non-replayable commands such as help, doctor, serve, or close during exploration.",
@@ -44722,8 +45179,13 @@ const DEFAULT_HANDOFF_GUIDANCE = [
44722
45179
  "Use needs_info when the report lacks necessary route, account, data, state, device, runner, or expected-behavior details after reasonable discovery.",
44723
45180
  "Use environment_blocked when the endpoint, app install/launch, auth, data, network, runner, or browser/mobile tooling prevents reaching the needed state.",
44724
45181
  "Write handoffMarkdown so a human can understand the verdict without reading raw logs and a coding agent can continue from handoff.md without rerunning the whole exploration.",
45182
+ "Include every artifact that supports the final verdict in write_handoff.evidenceRefs. Use artifactPath for [quire:artifact] paths returned by browser, mobile, or run_shell_check tools.",
45183
+ "Do not rely on prose-only evidence mentions. Quire will append a renderable Evidence artifacts section to handoff.md from public evidenceRefs and auto-captured artifacts.",
45184
+ "Do not include .harness-sessions paths, raw model transcripts, secrets, or private diagnostic state in evidenceRefs or handoffMarkdown.",
45185
+ "For markdown evidence links you write manually, prefer run-relative ./evidence/... paths over absolute local paths.",
44725
45186
  "Treat progress.jsonl as the canonical structured ledger. handoff.md is the human-readable report view, not the machine-readable source of truth.",
44726
45187
  "For verification-like requests or runs with structured assertions, use a Verification Report shape with Target, Request, Run, Summary, Results, Pre-existing Issues / Non-regressions when relevant, and Evidence sections.",
45188
+ "For post-fix or regression verification, make the requested claim verdict easy to find, then summarize product evidence, deterministic command evidence, blockers/untested checks, and unrelated or follow-up findings. Use natural prose and concise tables where helpful; do not force boilerplate if a simpler report is clearer.",
44727
45189
  "For verification-like requests, include a decisive first paragraph with pass, fail, untested, blocked, and unresolved counts plus the strongest supported conclusion.",
44728
45190
  "For verification-like requests, include a results table with assertion id, check title, verdict, expected, and actual. Include unresolved planned assertions explicitly; unresolved means no assertion record exists, not that the check passed or was untested.",
44729
45191
  "Keep pre-existing, not-regression, and out-of-scope findings separate from the requested verification verdict.",
@@ -45507,6 +45969,11 @@ function resolveRuntimeCapabilities(input) {
45507
45969
  available: true,
45508
45970
  names: [...READ_ONLY_RUN_CODE_TOOL_NAMES]
45509
45971
  },
45972
+ shellCheck: {
45973
+ available: true,
45974
+ toolName: "run_shell_check",
45975
+ safety: "dangerous_commands_blocked"
45976
+ },
45510
45977
  browser: browserCapability(input.headed),
45511
45978
  mobile: mobileCapability(),
45512
45979
  mcp: {
@@ -45545,6 +46012,7 @@ function composeRunBrief(input) {
45545
46012
  "## Run Artifacts",
45546
46013
  `Run directory: ${toJsonPath(input.runDir.root)}`,
45547
46014
  `Narrated progress file: ${toJsonPath(input.runDir.paths.progress)}`,
46015
+ `Evidence plan file: ${toJsonPath(input.runDir.paths.plan)}`,
45548
46016
  `Final handoff path: ${toJsonPath(input.runDir.paths.handoff)}`,
45549
46017
  `Evidence directory: ${toJsonPath(input.runDir.paths.evidence)}`,
45550
46018
  `Private diagnostic harness session directory: ${toJsonPath(input.runDir.paths.harnessSessions)}`,
@@ -45558,6 +46026,7 @@ function composeRunBrief(input) {
45558
46026
  "## Planning Requirement",
45559
46027
  [
45560
46028
  "Before broad exploration or browser/mobile interaction, record a concise plan with record_progress kind plan.",
46029
+ "The latest structured plan is materialized to plan.md in the run directory so caller agents can see what evidence Quire intends to gather while the run is active.",
45561
46030
  "For verification-like work, state assertions before checking them and later record each as passed, failed, untested, or blocked.",
45562
46031
  "For triage-like work, state the severity, impact, ownership, and next-action signals to inspect.",
45563
46032
  "For investigation-like work, state the hypotheses, surfaces, or evidence paths to examine."
@@ -45643,6 +46112,7 @@ function capabilityLines(capabilities) {
45643
46112
  `Runner: ${capabilities.runnerKind} on ${capabilities.platform}/${capabilities.arch}`,
45644
46113
  `Repository cwd: ${toJsonPath(capabilities.cwd)}`,
45645
46114
  `Read-only code tools: ${capabilities.tools.readOnlyCode.names.join(", ")}`,
46115
+ `Shell checks: ${capabilities.tools.shellCheck.toolName} available for non-dangerous project commands, local app startup checks, and verification evidence`,
45646
46116
  `Browser automation: ${browser.available ? `${browser.toolName}${browser.headed === true ? " (headed)" : ""}` : `unavailable (${browser.unavailableReason ?? "unknown"})`}`,
45647
46117
  `Mobile automation: ${mobile.available ? `${mobile.toolName} tool registered; device/app readiness unknown until checked` : `unavailable (${mobile.unavailableReason ?? "unknown"})`}`,
45648
46118
  `MCP gateway: ${mcp.available ? `available (${mcp.toolNames.length === 0 ? "no tools listed" : mcp.toolNames.join(", ")})` : "unavailable"}`
@@ -45660,15 +46130,16 @@ function followupLines(context) {
45660
46130
  function orderedListOrNone(values) {
45661
46131
  return values.length === 0 ? "(none provided)" : values.map((value, index) => `${index + 1}. ${value}`).join("\n");
45662
46132
  }
46133
+ //#endregion
46134
+ //#region src/pipeline/quire-agent.ts
46135
+ const RUN_ACTIVITY_HEARTBEAT_INTERVAL_MS = 5e3;
45663
46136
  const QUIRE_AGENT_THINKING_LEVEL = INVESTIGATION_THINKING_LEVEL;
45664
46137
  async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons, runtimeCapabilities, runDir, sessionId, headed = false, progress, onProgressEvent, followup, skillPack, mcpGateway, onResolvedModel }) {
45665
46138
  const codebasePath = readRunMetadata(runDir)?.repoPath ?? runDir.root;
45666
46139
  let emittedHandoff;
45667
46140
  let observedToolAttempts = 0;
45668
- let diagnosisSteered = false;
45669
46141
  const observedTargetToolCallIds = /* @__PURE__ */ new Set();
45670
46142
  const targetToolCallArgs = /* @__PURE__ */ new Map();
45671
- const pendingSessionActions = [];
45672
46143
  const runtime = await createQuireAgentRuntime({
45673
46144
  runDir,
45674
46145
  codebasePath,
@@ -45691,23 +46162,16 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
45691
46162
  modelId: runtime.model.id
45692
46163
  });
45693
46164
  const unsubscribe = session.subscribe((event) => {
46165
+ touchRunActivity(runDir.root, { minIntervalMs: RUN_ACTIVITY_HEARTBEAT_INTERVAL_MS });
45694
46166
  logAgentProgress(event);
45695
46167
  emittedHandoff ??= extractWriteHandoff(event);
45696
46168
  rememberTargetToolCallArgs(event, registeredTargetTools, targetToolCallArgs);
45697
46169
  const attemptsObserved = countNewTargetAttempts(event, registeredTargetTools, observedTargetToolCallIds, targetToolCallArgs);
45698
- if (attemptsObserved > 0) {
45699
- observedToolAttempts += attemptsObserved;
45700
- if (observedToolAttempts >= 6 && !diagnosisSteered && emittedHandoff === void 0) {
45701
- diagnosisSteered = true;
45702
- pendingSessionActions.push(session.steer(buildRunSteeringPrompt(observedToolAttempts, registeredTargetTools)));
45703
- }
45704
- }
46170
+ if (attemptsObserved > 0) observedToolAttempts += attemptsObserved;
45705
46171
  });
45706
46172
  try {
45707
46173
  await session.prompt(runBrief, { expandPromptTemplates: false });
45708
- await Promise.all(pendingSessionActions);
45709
46174
  if (emittedHandoff === void 0) await session.prompt(buildMissingRunHandoffPrompt(observedToolAttempts), { expandPromptTemplates: false });
45710
- await Promise.all(pendingSessionActions);
45711
46175
  } finally {
45712
46176
  runtime.writeSessionState();
45713
46177
  unsubscribe();
@@ -45733,12 +46197,17 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
45733
46197
  runDir,
45734
46198
  sessionId,
45735
46199
  cwd: codebasePath,
45736
- headed
46200
+ headed,
46201
+ onProgressEvent
45737
46202
  }) : void 0;
45738
46203
  const mobileTool = runtimeCapabilities.tools.mobile.available === true ? makeMobileTool({
45739
46204
  runDir,
45740
46205
  cwd: codebasePath
45741
46206
  }) : void 0;
46207
+ const shellCheckTool = makeShellCheckTool({
46208
+ runDir,
46209
+ cwd: codebasePath
46210
+ });
45742
46211
  const mcpGatewayTool = mcpGateway === void 0 ? void 0 : makeQuireMcpGatewayTool({
45743
46212
  gateway: mcpGateway,
45744
46213
  runId: runDir.runId
@@ -45781,6 +46250,7 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
45781
46250
  customTools: [
45782
46251
  ...browserTool === void 0 ? [] : [browserTool],
45783
46252
  ...mobileTool === void 0 ? [] : [mobileTool],
46253
+ shellCheckTool,
45784
46254
  withPromptGuidelines(createRecordProgressTool({
45785
46255
  runDir,
45786
46256
  onProgressEvent
@@ -45792,6 +46262,7 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
45792
46262
  ...READ_ONLY_RUN_CODE_TOOL_NAMES,
45793
46263
  ...browserTool === void 0 ? [] : [browserTool.name],
45794
46264
  ...mobileTool === void 0 ? [] : [mobileTool.name],
46265
+ shellCheckTool.name,
45795
46266
  "record_progress",
45796
46267
  writeHandoffTool.name,
45797
46268
  ...mcpGatewayTool === void 0 ? [] : [mcpGatewayTool.name]
@@ -45834,13 +46305,6 @@ function modelSourceProgressMessage(source, model) {
45834
46305
  case "quire_credits": return `Using Quire Credits via brokered model source (${model.provider}/${model.id}).`;
45835
46306
  }
45836
46307
  }
45837
- function buildRunSteeringPrompt(attempts, targetTools) {
45838
- return [
45839
- `${attempts} browser/mobile-backed attempts have been observed through ${targetTools.map((tool) => tool.toolName).join(", ") || "target tools"}.`,
45840
- "Stop blind retries. Decide whether the run has enough evidence, needs narrower target context, is blocked by runtime/tooling/account/data, or requires one different action.",
45841
- "If the run is complete or blocked, call write_handoff now. If more evidence is needed, take the next action that directly tests the diagnosis."
45842
- ].join("\n");
45843
- }
45844
46308
  function buildMissingRunHandoffPrompt(targetActions) {
45845
46309
  return [
45846
46310
  "The previous run ended without calling write_handoff.",
@@ -45883,10 +46347,13 @@ function isTargetReproductionAttempt(args, target) {
45883
46347
  if (args === void 0) return true;
45884
46348
  if (target === "mobile") {
45885
46349
  const command = findMobileCommand(args);
45886
- return command !== "open" && command !== "install";
46350
+ return command !== "open" && command !== "install" && command !== "screenshot";
45887
46351
  }
45888
- if (args[0] === "open") return false;
45889
- if (args[0] === "set" && args[1] === "viewport") return false;
46352
+ const command = args[0];
46353
+ if (command === "open") return false;
46354
+ if (command === "set" && args[1] === "viewport") return false;
46355
+ if (command === "skills") return false;
46356
+ if (command === "snapshot" || command === "screenshot" || command === "get" || command === "eval") return false;
45890
46357
  return true;
45891
46358
  }
45892
46359
  function rememberToolCallId(toolCallId, observedToolCallIds) {
@@ -48279,8 +48746,8 @@ function defaultSleep$1(ms) {
48279
48746
  const ARTIFACT_MODE = "evidence";
48280
48747
  const investigationCommandSchema = {
48281
48748
  version: 1,
48282
- command: "quire investigate",
48283
- description: "Starts a durable Quire investigation for a natural-language question or piped brief.",
48749
+ command: "quire run",
48750
+ description: "Starts a durable Quire run for a natural-language request or piped brief. Bare `quire \"<prompt>\"` is equivalent to `quire run \"<prompt>\"`.",
48284
48751
  defaultMode: "attached",
48285
48752
  input: {
48286
48753
  pipedStdin: true,
@@ -48290,7 +48757,7 @@ const investigationCommandSchema = {
48290
48757
  stdout: {
48291
48758
  human: "Without --json, stdout shows a short start summary and attaches to the live run log unless --detach is passed.",
48292
48759
  json: "With --json, stdout contains exactly one newline-terminated JSON start handle and does not attach to the live log.",
48293
- schema: "With --schema, stdout contains this newline-terminated JSON schema and no investigation starts."
48760
+ schema: "With --schema, stdout contains this newline-terminated JSON schema and no run starts."
48294
48761
  },
48295
48762
  stderr: { progress: "Machine-readable errors are written to stderr. Human attached runs stream live run logs after the start summary." },
48296
48763
  arguments: [
@@ -48299,7 +48766,7 @@ const investigationCommandSchema = {
48299
48766
  type: "string",
48300
48767
  positional: true,
48301
48768
  requiredUnless: "piped stdin",
48302
- description: "Natural-language investigation query or instruction."
48769
+ description: "Natural-language run request or instruction."
48303
48770
  },
48304
48771
  {
48305
48772
  name: "--json",
@@ -48382,21 +48849,20 @@ const investigationCommandSchema = {
48382
48849
  safety: ["Raw model thinking, prompts, completions, provider credentials, API tokens, and local absolute repo paths are not synced to the web case.", "Sync failures are non-fatal for local investigations; local artifacts and retry state remain in the run directory."],
48383
48850
  examples: [
48384
48851
  "quire \"verify checkout before I approve this PR\"",
48385
- "printf 'why is CI failing?\\nRelevant log: ...' | quire investigate --json",
48852
+ "printf 'why is CI failing?\\nRelevant log: ...' | quire run --json",
48386
48853
  "quire status run_9x4mdq7p2h8kc6nv4apz --json",
48387
48854
  "quire watch run_9x4mdq7p2h8kc6nv4apz"
48388
48855
  ]
48389
48856
  };
48390
- const investigateCommand = defineCommand({
48857
+ const quireRunCommand = defineCommand({
48391
48858
  meta: {
48392
- name: "investigate",
48393
- alias: "ask",
48394
- description: "Investigate a question against the current workspace."
48859
+ name: "run",
48860
+ description: "Run evidence-backed QA, verification, triage, or investigation work."
48395
48861
  },
48396
48862
  args: {
48397
48863
  prompt: {
48398
48864
  type: "positional",
48399
- description: "Natural-language investigation query.",
48865
+ description: "Natural-language run request.",
48400
48866
  required: false
48401
48867
  },
48402
48868
  url: {
@@ -48419,11 +48885,11 @@ const investigateCommand = defineCommand({
48419
48885
  },
48420
48886
  headed: {
48421
48887
  type: "boolean",
48422
- description: "Show the browser window while the investigation agent runs."
48888
+ description: "Show the browser window while the Quire agent runs."
48423
48889
  },
48424
48890
  schema: {
48425
48891
  type: "boolean",
48426
- description: "Emit the machine-readable investigation invocation schema and exit."
48892
+ description: "Emit the machine-readable run invocation schema and exit."
48427
48893
  }
48428
48894
  },
48429
48895
  async run({ args }) {
@@ -48466,7 +48932,7 @@ async function runInvestigate(options) {
48466
48932
  });
48467
48933
  if (options.json === true) writeJson(stdout, handle);
48468
48934
  else {
48469
- log.success(`Started investigation ${color.value(handle.runId)}.`, {
48935
+ log.success(`Started run ${color.value(handle.runId)}.`, {
48470
48936
  output: stdout,
48471
48937
  spacing: 0
48472
48938
  });
@@ -48480,7 +48946,12 @@ async function runInvestigate(options) {
48480
48946
  spacing: 0,
48481
48947
  withGuide: true
48482
48948
  });
48483
- else log.message(`${color.label("Run")}: ${color.path(handle.runPath)}`, {
48949
+ else log.message([
48950
+ `${color.label("Run")}: ${color.path(handle.runPath)}`,
48951
+ `${color.label("Status")}: ${color.command(handle.statusCommand)}`,
48952
+ `${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.`
48954
+ ], {
48484
48955
  output: stdout,
48485
48956
  spacing: 0,
48486
48957
  withGuide: true
@@ -48631,6 +49102,7 @@ function createDoctorContext(overrides = {}) {
48631
49102
  cwd: resolve(overrides.cwd ?? process.cwd()),
48632
49103
  runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot()),
48633
49104
  authStore: overrides.authStore ?? authStore,
49105
+ env: overrides.env ?? process.env,
48634
49106
  fetchFn: overrides.fetchFn ?? fetch,
48635
49107
  execFn: overrides.execFn ?? defaultExecFn,
48636
49108
  probeTimeoutMs: overrides.probeTimeoutMs ?? 2e3
@@ -48647,25 +49119,30 @@ async function defaultExecFn(file, args, options) {
48647
49119
  };
48648
49120
  }
48649
49121
  async function checkAuthPresent(context) {
48650
- const credentials = await resolveAuthCredentials({ store: context.authStore });
49122
+ const source = context.env["QUIRE_API_TOKEN"]?.trim() ? "env_api_token" : "stored_login";
49123
+ const credentials = await resolveAuthCredentials({
49124
+ store: context.authStore,
49125
+ env: context.env
49126
+ });
48651
49127
  if (credentials === null) return { result: {
48652
49128
  id: "auth.present",
48653
49129
  section: "identity",
48654
49130
  severity: "fail",
48655
- message: "No Quire credentials on this machine.",
48656
- fix: { command: "quire login" }
49131
+ message: "No Quire credentials found. Authenticate with an API token or interactive login.",
49132
+ fix: { command: "Set QUIRE_API_TOKEN or run `quire login`" }
48657
49133
  } };
48658
49134
  return {
48659
49135
  result: {
48660
49136
  id: "auth.present",
48661
49137
  section: "identity",
48662
49138
  severity: "pass",
48663
- message: "Credentials present."
49139
+ message: `Credentials present (${authSourceLabel(source)}; API ${credentials.apiBaseUrl}).`
48664
49140
  },
48665
49141
  auth: {
48666
49142
  apiBaseUrl: credentials.apiBaseUrl,
48667
49143
  accessToken: credentials.accessToken,
48668
49144
  tokenType: credentials.tokenType,
49145
+ source,
48669
49146
  ...credentials.user === void 0 ? {} : { user: credentials.user }
48670
49147
  }
48671
49148
  };
@@ -48688,8 +49165,8 @@ async function checkAuthValid(context, auth) {
48688
49165
  id: "auth.valid",
48689
49166
  section: "identity",
48690
49167
  severity: "fail",
48691
- message: `Could not validate Quire API token: ${toMessage(error)}`,
48692
- fix: { command: "Set QUIRE_API_TOKEN to an active token from Quire settings" }
49168
+ message: `Could not validate API token against ${auth.apiBaseUrl}: ${toMessage(error)}`,
49169
+ fix: { command: apiTokenFixCommand(auth.apiBaseUrl) }
48693
49170
  } };
48694
49171
  }
48695
49172
  try {
@@ -48703,8 +49180,8 @@ async function checkAuthValid(context, auth) {
48703
49180
  id: "auth.valid",
48704
49181
  section: "identity",
48705
49182
  severity: "fail",
48706
- message: "Stored credentials were rejected by Quire.",
48707
- fix: { command: "quire login" }
49183
+ message: `Stored login was rejected by ${auth.apiBaseUrl}.`,
49184
+ fix: { command: loginFixCommand(auth.apiBaseUrl) }
48708
49185
  } };
48709
49186
  const user = lookup.data.user;
48710
49187
  return {
@@ -48721,8 +49198,8 @@ async function checkAuthValid(context, auth) {
48721
49198
  id: "auth.valid",
48722
49199
  section: "identity",
48723
49200
  severity: "fail",
48724
- message: `Could not validate Quire session: ${toMessage(error)}`,
48725
- fix: { command: "quire login" }
49201
+ message: `Could not validate stored login against ${auth.apiBaseUrl}: ${toMessage(error)}`,
49202
+ fix: { command: loginFixCommand(auth.apiBaseUrl) }
48726
49203
  } };
48727
49204
  }
48728
49205
  }
@@ -48764,6 +49241,15 @@ function brokerToCheckResult(status) {
48764
49241
  message: `Model broker available (${status.selectedProviderMode ?? "default"}).`
48765
49242
  };
48766
49243
  }
49244
+ function authSourceLabel(source) {
49245
+ return source === "env_api_token" ? `${QUIRE_API_TOKEN_ENV} API token` : "stored login";
49246
+ }
49247
+ function apiTokenFixCommand(apiBaseUrl) {
49248
+ return `Set QUIRE_API_TOKEN to an active token for ${apiBaseUrl}; set QUIRE_API_URL if that API URL is wrong; or run \`quire login --api-url ${apiBaseUrl}\``;
49249
+ }
49250
+ function loginFixCommand(apiBaseUrl) {
49251
+ return `Run \`quire login --api-url ${apiBaseUrl}\`, or set QUIRE_API_TOKEN and QUIRE_API_URL for API-token auth`;
49252
+ }
48767
49253
  async function checkRunbook(context) {
48768
49254
  const runbook = await inspectProjectRunbookStatus(context.cwd);
48769
49255
  if (!runbook.exists) return {
@@ -49176,22 +49662,78 @@ async function runLogout(options) {
49176
49662
  //#endregion
49177
49663
  //#region src/commands/setup.ts
49178
49664
  const SETUP_URL = "https://quire.sh/agent-setup";
49665
+ const VERIFICATION_SKILL_URL = "https://quire.sh/agent-verification";
49179
49666
  const AGENT_SETUP_PROMPT = `Set up this repository for Quire investigations.
49180
49667
 
49181
49668
  Please load and follow the Quire agent setup instructions from ${SETUP_URL}.
49182
49669
 
49183
- Inspect the project, create the smallest useful .quire/runbook.md with target-resolution, verification, and safety instructions, update AGENTS.md with concise run and verification notes for future agents, then run quire doctor if available and report anything still blocked.`;
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 = `---
49672
+ 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.
49674
+ ---
49675
+
49676
+ # Quire verification
49677
+
49678
+ Load and follow the current Quire verification skill from ${VERIFICATION_SKILL_URL}.
49679
+
49680
+ Use Quire when a task needs evidence-backed verification, QA, product dogfooding, bug reproduction, or triage. Prefer \`quire run\` for new work.
49681
+ `;
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
+ }];
49184
49703
  const setupCommand = defineCommand({
49185
49704
  meta: {
49186
49705
  name: "setup",
49187
49706
  description: "Prepare this workspace for Quire investigations."
49188
49707
  },
49189
- async run() {
49190
- await runSetup();
49708
+ args: {
49709
+ skill: {
49710
+ type: "string",
49711
+ description: "Comma-separated local agent skill targets to install: agents, claude, all, none.",
49712
+ valueHint: "targets"
49713
+ },
49714
+ "no-skill": {
49715
+ type: "boolean",
49716
+ description: "Skip installing local Quire agent skills."
49717
+ },
49718
+ "no-interactive": {
49719
+ type: "boolean",
49720
+ description: "Do not prompt; use default setup choices."
49721
+ }
49722
+ },
49723
+ async run({ args }) {
49724
+ await runSetup({
49725
+ skill: args["no-skill"] === true ? false : args.skill,
49726
+ interactive: args["no-interactive"] !== true
49727
+ });
49191
49728
  }
49192
49729
  });
49193
49730
  async function runSetup(options = {}) {
49194
49731
  const stdout = options.io?.stdout ?? process.stdout;
49732
+ const selectedTargets = await selectSkillInstallTargets({
49733
+ skill: options.skill,
49734
+ interactive: options.interactive ?? (options.io?.stdout === void 0 && process.stdout.isTTY === true)
49735
+ });
49736
+ const installedSkills = await installQuireSkill(options.homeDir ?? homedir(), selectedTargets);
49195
49737
  log.step("Set up Quire for this workspace", {
49196
49738
  output: stdout,
49197
49739
  spacing: 0
@@ -49205,6 +49747,51 @@ async function runSetup(options = {}) {
49205
49747
  writeLine(stdout, color.label("--- copy prompt ---"));
49206
49748
  writeLine(stdout, AGENT_SETUP_PROMPT);
49207
49749
  writeLine(stdout, color.label("--- end prompt ---"));
49750
+ if (installedSkills.length > 0) {
49751
+ writeLine(stdout);
49752
+ writeLine(stdout, color.label("Installed local Quire skill:"));
49753
+ for (const path of installedSkills) writeLine(stdout, ` ${color.success("✓")} ${path}`);
49754
+ }
49755
+ }
49756
+ async function selectSkillInstallTargets(options) {
49757
+ if (options.skill === false || options.skill === "none") return [];
49758
+ if (options.skill !== void 0) return resolveSkillTargets(options.skill);
49759
+ if (!options.interactive) return [...SKILL_INSTALL_TARGETS];
49760
+ const selected = await multiselect({
49761
+ message: "Which local agent skill directories should Quire install into?",
49762
+ options: SKILL_INSTALL_TARGETS.map((target) => ({
49763
+ label: target.label,
49764
+ value: target.id,
49765
+ hint: target.hint
49766
+ })),
49767
+ initialValues: ["agents", "claude"],
49768
+ required: false
49769
+ });
49770
+ if (isCancel(selected) || selected.length === 0) return [];
49771
+ return resolveSkillTargets(selected.join(","));
49772
+ }
49773
+ function resolveSkillTargets(value) {
49774
+ const parts = value.split(",").map((part) => part.trim().toLowerCase()).filter((part) => part.length > 0);
49775
+ if (parts.includes("all")) return [...SKILL_INSTALL_TARGETS];
49776
+ const selected = /* @__PURE__ */ new Map();
49777
+ for (const part of parts) {
49778
+ const target = SKILL_INSTALL_TARGETS.find((candidate) => candidate.id === part || candidate.aliases.includes(part));
49779
+ if (target !== void 0) selected.set(target.id, target);
49780
+ }
49781
+ return [...selected.values()];
49782
+ }
49783
+ async function installQuireSkill(homeDir, targets) {
49784
+ const installed = [];
49785
+ await Promise.all(targets.map(async (target) => {
49786
+ const path = join(homeDir, target.targetPath);
49787
+ await mkdir(dirname(path), {
49788
+ recursive: true,
49789
+ mode: 448
49790
+ });
49791
+ await writeFile(path, QUIRE_SKILL_MARKDOWN, { mode: 384 });
49792
+ installed.push(path);
49793
+ }));
49794
+ return installed.sort();
49208
49795
  }
49209
49796
  //#endregion
49210
49797
  //#region src/commands/whoami.ts
@@ -49356,7 +49943,7 @@ function formatWalletBalance(value) {
49356
49943
  }
49357
49944
  //#endregion
49358
49945
  //#region package.json
49359
- var version$1 = "0.0.9";
49946
+ var version$1 = "0.0.11";
49360
49947
  //#endregion
49361
49948
  //#region src/cli.ts
49362
49949
  const subCommands = {
@@ -49382,18 +49969,14 @@ const subCommands = {
49382
49969
  continue: continueCommand,
49383
49970
  doctor: doctorCommand,
49384
49971
  env: envCommand,
49385
- investigate: investigateCommand,
49972
+ run: quireRunCommand,
49386
49973
  runs: runsCommand,
49387
49974
  setup: setupCommand,
49388
49975
  status: statusCommand,
49389
49976
  sync: syncCommand,
49390
49977
  watch: watchCommand
49391
49978
  };
49392
- const subCommandAliases = new Set([
49393
- "ask",
49394
- "me",
49395
- "resume"
49396
- ]);
49979
+ const subCommandAliases = new Set(["me", "resume"]);
49397
49980
  const cli = defineCommand({
49398
49981
  meta: {
49399
49982
  name: "quire",
@@ -49427,7 +50010,7 @@ function isVersionRequest(rawArgs) {
49427
50010
  function normalizeRawArgs(rawArgs) {
49428
50011
  const first = rawArgs[0];
49429
50012
  if (first === void 0 || isTopLevelFlag(first) || isSubCommandName(first)) return rawArgs;
49430
- return ["investigate", ...rawArgs];
50013
+ return ["run", ...rawArgs];
49431
50014
  }
49432
50015
  function isTopLevelFlag(arg) {
49433
50016
  return arg === "--help" || arg === "-h" || arg === "--version" || arg === "-v";