@quireco/cli 0.0.9 → 0.0.10

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,20 @@ 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);
2555
+ emitBrowserProgress(runDir, onProgressEvent, {
2556
+ kind: "action",
2557
+ status: "running",
2558
+ title: `Browser: ${commandLabel}`,
2559
+ summary: "agent-browser is acting on the target application.",
2560
+ importance: "normal",
2561
+ source: "runtime"
2562
+ });
2471
2563
  const result = await spawnAgentBrowser({
2472
2564
  args,
2473
2565
  sessionId,
@@ -2496,12 +2588,30 @@ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, hea
2496
2588
  signal
2497
2589
  }));
2498
2590
  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
- };
2591
+ if (!result.ok) {
2592
+ emitBrowserProgress(runDir, onProgressEvent, {
2593
+ kind: "action",
2594
+ status: "failed",
2595
+ title: `Browser: ${commandLabel}`,
2596
+ summary: oneLine(result.stderr || result.stdout || "agent-browser command failed"),
2597
+ importance: "normal",
2598
+ source: "runtime"
2599
+ });
2600
+ return {
2601
+ ok: false,
2602
+ exitCode: result.exitCode,
2603
+ stdout: stdout.join(""),
2604
+ stderr: stderr.join("")
2605
+ };
2606
+ }
2607
+ emitBrowserProgress(runDir, onProgressEvent, {
2608
+ kind: "action",
2609
+ status: "passed",
2610
+ title: `Browser: ${commandLabel}`,
2611
+ summary: "agent-browser completed this target step.",
2612
+ importance: "normal",
2613
+ source: "runtime"
2614
+ });
2505
2615
  }
2506
2616
  return {
2507
2617
  ok: true,
@@ -2510,6 +2620,24 @@ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, hea
2510
2620
  stderr: stderr.join("")
2511
2621
  };
2512
2622
  }
2623
+ function emitBrowserProgress(runDir, onProgressEvent, event) {
2624
+ const logged = appendRunProgressEvent(runDir, event);
2625
+ onProgressEvent?.(logged);
2626
+ }
2627
+ function browserCommandLabel(args) {
2628
+ const command = args[0] ?? "command";
2629
+ if (command === "open") return `open ${redactBrowserArg(args[1] ?? "URL")}`;
2630
+ if (command === "batch") return "batch";
2631
+ return command;
2632
+ }
2633
+ function redactBrowserArg(value) {
2634
+ try {
2635
+ const url = new URL(value);
2636
+ return `${url.origin}${url.pathname}`;
2637
+ } catch {
2638
+ return value.length > 80 ? `${value.slice(0, 77)}…` : value;
2639
+ }
2640
+ }
2513
2641
  function augmentBrowserArtifactOutput({ runDir, sessionId, cwd, args, result, agentBrowserExecutableResolver, signal }) {
2514
2642
  if (!result.ok) return Promise.resolve(result.stdout);
2515
2643
  const output = [result.stdout];
@@ -2605,27 +2733,35 @@ function hasOption$1(args, option) {
2605
2733
  return args.some((arg) => arg === option || arg.startsWith(`${option}=`));
2606
2734
  }
2607
2735
  function shouldStartRecordingForCommand(args) {
2608
- return args[0] === "open";
2736
+ const command = args[0];
2737
+ return command !== void 0 && RECORDING_START_COMMANDS.has(command);
2609
2738
  }
2610
2739
  function shouldCaptureScreenshotAfterCommand$1(args) {
2611
2740
  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);
2741
+ return command !== void 0 && AUTO_SCREENSHOT_COMMANDS.has(command);
2628
2742
  }
2743
+ const RECORDING_START_COMMANDS = new Set([
2744
+ "back",
2745
+ "check",
2746
+ "click",
2747
+ "dblclick",
2748
+ "drag",
2749
+ "fill",
2750
+ "forward",
2751
+ "open",
2752
+ "press",
2753
+ "reload",
2754
+ "select",
2755
+ "type",
2756
+ "uncheck",
2757
+ "upload"
2758
+ ]);
2759
+ const AUTO_SCREENSHOT_COMMANDS = new Set([
2760
+ "back",
2761
+ "forward",
2762
+ "open",
2763
+ "reload"
2764
+ ]);
2629
2765
  //#endregion
2630
2766
  //#region src/run-artifacts.ts
2631
2767
  function relativeRunPath(runDir, path) {
@@ -2670,14 +2806,19 @@ function publicRunProgressEvent(runDir, event) {
2670
2806
  //#region src/pipeline/handoff-materializer.ts
2671
2807
  function materializeHandoff({ handoff, runDir, continuation, modelUsage, metadata, harness }) {
2672
2808
  const result = toInvestigationResult(handoff, runDir, continuation, modelUsage);
2673
- const nextMetadata = updateMetadataWithHandoff(runDir, metadata, handoff, result, harness);
2809
+ const materializedHandoff = materializeHandoffMarkdown(handoff, result.evidence);
2810
+ const materializedResult = {
2811
+ ...result,
2812
+ handoffMarkdown: materializedHandoff.handoffMarkdown
2813
+ };
2814
+ const nextMetadata = updateMetadataWithHandoff(runDir, metadata, materializedHandoff, materializedResult, harness);
2674
2815
  writeRunFinalHandoff(runDir, {
2675
- handoff,
2676
- result
2816
+ handoff: materializedHandoff,
2817
+ result: materializedResult
2677
2818
  });
2678
- writeHandoffArtifact(runDir, handoff);
2819
+ writeHandoffArtifact(runDir, materializedHandoff);
2679
2820
  return {
2680
- result,
2821
+ result: materializedResult,
2681
2822
  metadata: nextMetadata
2682
2823
  };
2683
2824
  }
@@ -2714,13 +2855,20 @@ function toInvestigationResult(handoff, runDir, continuation, modelUsage) {
2714
2855
  }
2715
2856
  function appendAutoCapturedEvidence(evidence, runDir) {
2716
2857
  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) => {
2858
+ const autoEvidence = [...selectAutoScreenshotEvidence(evidence, readAutoCapturedFiles(runDir.paths.screenshots, runDir, "screenshot")), ...readAutoCapturedFiles(runDir.paths.video, runDir, "recording")].filter((item) => {
2718
2859
  if (item.path === void 0 || seenPaths.has(item.path)) return false;
2719
2860
  seenPaths.add(item.path);
2720
2861
  return true;
2721
2862
  });
2722
2863
  return [...evidence, ...autoEvidence];
2723
2864
  }
2865
+ function selectAutoScreenshotEvidence(handoffEvidence, autoScreenshots) {
2866
+ if (handoffEvidence.some((item) => item.kind === "screenshot")) return [];
2867
+ if (autoScreenshots.length <= 2) return autoScreenshots;
2868
+ const first = autoScreenshots[0];
2869
+ const last = autoScreenshots[autoScreenshots.length - 1];
2870
+ return first === void 0 || last === void 0 || first.path === last.path ? autoScreenshots.slice(0, 1) : [first, last];
2871
+ }
2724
2872
  function readAutoCapturedFiles(directory, runDir, kind) {
2725
2873
  if (!existsSync(directory)) return [];
2726
2874
  try {
@@ -2806,6 +2954,86 @@ function confidenceFor(handoff, evidence) {
2806
2954
  if (handoff.status !== "reproduced" || evidence.length === 0) return "low";
2807
2955
  return evidence.length >= 2 ? "high" : "medium";
2808
2956
  }
2957
+ const EVIDENCE_SECTION_START = "<!-- quire:evidence-start -->";
2958
+ const EVIDENCE_SECTION_END = "<!-- quire:evidence-end -->";
2959
+ function materializeHandoffMarkdown(handoff, evidence) {
2960
+ const evidenceSection = renderEvidenceSection(evidence);
2961
+ const handoffMarkdown = withGeneratedEvidenceSection(handoff.handoffMarkdown, evidenceSection);
2962
+ return {
2963
+ ...handoff,
2964
+ handoffMarkdown,
2965
+ evidenceRefs: evidence.map((item) => ({
2966
+ kind: item.kind,
2967
+ note: item.note,
2968
+ ...item.path === void 0 ? {} : { path: item.path },
2969
+ ...item.url === void 0 ? {} : { url: item.url }
2970
+ }))
2971
+ };
2972
+ }
2973
+ function withGeneratedEvidenceSection(markdown, evidenceSection) {
2974
+ const withoutExisting = markdown.trimEnd().replace(new RegExp(`\\n*${escapeRegExp$1(EVIDENCE_SECTION_START)}[\\s\\S]*?${escapeRegExp$1(EVIDENCE_SECTION_END)}\\s*$`), "");
2975
+ return evidenceSection.length === 0 ? withoutExisting : `${withoutExisting}\n\n${evidenceSection}`;
2976
+ }
2977
+ function renderEvidenceSection(evidence) {
2978
+ const publicEvidence = evidence.filter((item) => item.path !== void 0 || item.url !== void 0);
2979
+ if (publicEvidence.length === 0) return "";
2980
+ return [
2981
+ EVIDENCE_SECTION_START,
2982
+ "## Evidence artifacts",
2983
+ "",
2984
+ ...publicEvidence.flatMap(renderEvidenceItem),
2985
+ EVIDENCE_SECTION_END
2986
+ ].join("\n");
2987
+ }
2988
+ function renderEvidenceItem(item) {
2989
+ const label = `${evidenceDisplayKind(item)} — ${item.note}`;
2990
+ const target = evidenceTarget(item);
2991
+ if (target === void 0) return [];
2992
+ if (isImageEvidence(item)) return [
2993
+ `- ${label}`,
2994
+ ` ![${escapeMarkdownAlt(item.note)}](${target})`,
2995
+ ""
2996
+ ];
2997
+ if (isVideoEvidence(item)) return [
2998
+ `- ${label}`,
2999
+ ` <video controls src="${escapeHtmlAttribute(target)}"></video>`,
3000
+ ` [Open recording](${target})`,
3001
+ ""
3002
+ ];
3003
+ return [
3004
+ `- ${label}`,
3005
+ ` [Open artifact](${target})`,
3006
+ ""
3007
+ ];
3008
+ }
3009
+ function evidenceDisplayKind(item) {
3010
+ const kind = item.kind.replaceAll("_", " ").trim();
3011
+ return kind.length === 0 ? "Evidence" : `${kind[0]?.toUpperCase() ?? "E"}${kind.slice(1)}`;
3012
+ }
3013
+ function evidenceTarget(item) {
3014
+ if (item.url !== void 0) return item.url;
3015
+ if (item.path === void 0) return;
3016
+ return `./${item.path.replace(/^\.\//, "")}`;
3017
+ }
3018
+ function isImageEvidence(item) {
3019
+ const path = item.path?.toLowerCase() ?? "";
3020
+ const kind = item.kind.toLowerCase();
3021
+ return kind.includes("screenshot") || kind.includes("image") || path.endsWith(".png") || path.endsWith(".jpg") || path.endsWith(".jpeg") || path.endsWith(".webp") || path.endsWith(".gif");
3022
+ }
3023
+ function isVideoEvidence(item) {
3024
+ const path = item.path?.toLowerCase() ?? "";
3025
+ const kind = item.kind.toLowerCase();
3026
+ return kind.includes("recording") || kind.includes("video") || path.endsWith(".webm") || path.endsWith(".mp4");
3027
+ }
3028
+ function escapeMarkdownAlt(value) {
3029
+ return value.replace(/[[\]\\]/g, " ").replace(/\s+/g, " ").trim();
3030
+ }
3031
+ function escapeHtmlAttribute(value) {
3032
+ return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
3033
+ }
3034
+ function escapeRegExp$1(value) {
3035
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3036
+ }
2809
3037
  function updateMetadataWithHandoff(runDir, metadata, handoff, result, harness) {
2810
3038
  const sessionState = readRunSessionState(runDir);
2811
3039
  return writeRunMetadata(runDir, {
@@ -41110,11 +41338,11 @@ function errorMessage$2(error) {
41110
41338
  }
41111
41339
  //#endregion
41112
41340
  //#region ../../packages/agent-mobile/src/wait/android.ts
41113
- const DEFAULT_TIMEOUT_MS$1 = 1e4;
41341
+ const DEFAULT_TIMEOUT_MS$2 = 1e4;
41114
41342
  const POLL_INTERVAL_MS$1 = 400;
41115
41343
  async function waitAndroid(request) {
41116
41344
  const waitFor = request.waitFor ?? { kind: "settle" };
41117
- const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS$1;
41345
+ const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS$2;
41118
41346
  if (waitFor.kind === "settle") {
41119
41347
  await waitForSettle$1(request, waitFor, timeoutMs);
41120
41348
  return;
@@ -41192,11 +41420,11 @@ function sleep$1(ms) {
41192
41420
  }
41193
41421
  //#endregion
41194
41422
  //#region ../../packages/agent-mobile/src/wait/ios.ts
41195
- const DEFAULT_TIMEOUT_MS = 1e4;
41423
+ const DEFAULT_TIMEOUT_MS$1 = 1e4;
41196
41424
  const POLL_INTERVAL_MS = 400;
41197
41425
  async function waitIos(request) {
41198
41426
  const waitFor = request.waitFor ?? { kind: "settle" };
41199
- const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS;
41427
+ const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS$1;
41200
41428
  if (waitFor.kind === "settle") {
41201
41429
  await waitForSettle(request, waitFor, timeoutMs);
41202
41430
  return;
@@ -44646,48 +44874,293 @@ function shouldCaptureScreenshotAfterCommand(command) {
44646
44874
  return command !== void 0 && !["install", "screenshot"].includes(command);
44647
44875
  }
44648
44876
  //#endregion
44877
+ //#region src/tools/shell-check.ts
44878
+ const DEFAULT_TIMEOUT_MS = 12e4;
44879
+ const MAX_OUTPUT_CHARS = 6e4;
44880
+ const ShellCheckToolInputSchema = Type.Object({
44881
+ command: Type.String({ minLength: 1 }),
44882
+ cwd: Type.Optional(Type.String({ minLength: 1 })),
44883
+ timeoutMs: Type.Optional(Type.Number({
44884
+ minimum: 1e3,
44885
+ maximum: 6e5
44886
+ })),
44887
+ reason: Type.String({ minLength: 1 })
44888
+ }, { additionalProperties: false });
44889
+ function makeShellCheckTool({ runDir, cwd: defaultCwd, env = process.env }) {
44890
+ return createAgentCliTool({
44891
+ name: "run_shell_check",
44892
+ 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.",
44893
+ parameters: ShellCheckToolInputSchema,
44894
+ parseInput: parseShellCheckToolInput,
44895
+ runCommand(input, signal) {
44896
+ return runShellCheckCommand({
44897
+ runDir,
44898
+ defaultCwd,
44899
+ env,
44900
+ input,
44901
+ signal
44902
+ });
44903
+ }
44904
+ });
44905
+ }
44906
+ function parseShellCheckToolInput(input) {
44907
+ return parseAgentCliToolInput(ShellCheckToolInputSchema, input, "run_shell_check");
44908
+ }
44909
+ async function runShellCheckCommand({ runDir, defaultCwd, env, input, signal }) {
44910
+ const command = input.command.trim();
44911
+ const policy = classifyShellCheckCommand(command);
44912
+ if (!policy.allowed) return {
44913
+ ok: false,
44914
+ exitCode: null,
44915
+ stdout: "",
44916
+ stderr: `Blocked by Quire shell safety policy: ${policy.reason}`,
44917
+ blocked: true,
44918
+ reason: policy.reason
44919
+ };
44920
+ const cwd = input.cwd ?? defaultCwd ?? process.cwd();
44921
+ const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
44922
+ const result = await spawnShell(command, {
44923
+ cwd,
44924
+ env,
44925
+ timeoutMs,
44926
+ signal
44927
+ });
44928
+ const artifactPath = writeShellArtifact(runDir, {
44929
+ command,
44930
+ cwd,
44931
+ reason: input.reason,
44932
+ timeoutMs,
44933
+ ...result
44934
+ });
44935
+ const output = {
44936
+ exitCode: result.exitCode,
44937
+ stdout: truncateOutput(redactSecrets(result.stdout)),
44938
+ stderr: `${truncateOutput(redactSecrets(result.stderr))}\n[quire:artifact] shell_check saved to ${artifactPath}\n`.trimStart(),
44939
+ artifactPath
44940
+ };
44941
+ return result.exitCode === 0 ? {
44942
+ ok: true,
44943
+ ...output,
44944
+ exitCode: 0
44945
+ } : {
44946
+ ok: false,
44947
+ ...output
44948
+ };
44949
+ }
44950
+ function classifyShellCheckCommand(command) {
44951
+ const trimmed = command.trim();
44952
+ if (trimmed.length === 0) return {
44953
+ allowed: false,
44954
+ reason: "command is empty"
44955
+ };
44956
+ if (/[;&|`$<>]/.test(trimmed)) return {
44957
+ allowed: false,
44958
+ reason: "shell operators, pipes, redirects, and command substitution are not allowed"
44959
+ };
44960
+ const words = splitSimpleCommand(trimmed);
44961
+ if (words === void 0 || words.length === 0) return {
44962
+ allowed: false,
44963
+ reason: "command could not be parsed as a simple command"
44964
+ };
44965
+ const dangerous = firstDangerousToken(words.map((word) => word.toLowerCase()));
44966
+ if (dangerous !== void 0) return {
44967
+ allowed: false,
44968
+ reason: `contains unsafe token ${JSON.stringify(dangerous)}`
44969
+ };
44970
+ return { allowed: true };
44971
+ }
44972
+ function splitSimpleCommand(command) {
44973
+ const words = [];
44974
+ const pattern = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^']*)'|(\S+)/g;
44975
+ let match;
44976
+ let consumed = "";
44977
+ while ((match = pattern.exec(command)) !== null) {
44978
+ consumed += match[0];
44979
+ words.push(match[1] ?? match[2] ?? match[3] ?? "");
44980
+ }
44981
+ if (words.length === 0 || consumed.replace(/\s+/g, "") !== command.replace(/\s+/g, "")) return;
44982
+ return words;
44983
+ }
44984
+ const DANGEROUS_TOKENS = new Set([
44985
+ "rm",
44986
+ "rmdir",
44987
+ "mv",
44988
+ "cp",
44989
+ "sudo",
44990
+ "chmod",
44991
+ "chown",
44992
+ "kill",
44993
+ "pkill",
44994
+ "reset",
44995
+ "restore",
44996
+ "clean",
44997
+ "checkout",
44998
+ "commit",
44999
+ "push",
45000
+ "publish",
45001
+ "deploy",
45002
+ "release",
45003
+ "install",
45004
+ "add",
45005
+ "remove",
45006
+ "unlink",
45007
+ "migrate",
45008
+ "seed",
45009
+ "drop",
45010
+ "truncate",
45011
+ "delete",
45012
+ "insert",
45013
+ "update",
45014
+ "create",
45015
+ "curl",
45016
+ "wget",
45017
+ "ssh",
45018
+ "scp",
45019
+ "rsync"
45020
+ ]);
45021
+ function firstDangerousToken(words) {
45022
+ return words.find((word) => DANGEROUS_TOKENS.has(word));
45023
+ }
45024
+ async function spawnShell(command, { cwd, env, timeoutMs, signal }) {
45025
+ return await new Promise((resolve) => {
45026
+ let stdout = "";
45027
+ let stderr = "";
45028
+ let settled = false;
45029
+ const child = spawn("/bin/sh", ["-lc", command], {
45030
+ cwd,
45031
+ env,
45032
+ stdio: [
45033
+ "ignore",
45034
+ "pipe",
45035
+ "pipe"
45036
+ ]
45037
+ });
45038
+ const settle = (exitCode, extraStderr = "") => {
45039
+ if (settled) return;
45040
+ settled = true;
45041
+ clearTimeout(timer);
45042
+ signal?.removeEventListener("abort", abort);
45043
+ resolve({
45044
+ exitCode,
45045
+ stdout,
45046
+ stderr: `${stderr}${extraStderr}`
45047
+ });
45048
+ };
45049
+ const abort = () => {
45050
+ child.kill("SIGTERM");
45051
+ settle(null, "\nCommand aborted.\n");
45052
+ };
45053
+ const timer = setTimeout(() => {
45054
+ child.kill("SIGTERM");
45055
+ settle(null, `\nCommand timed out after ${timeoutMs}ms.\n`);
45056
+ }, timeoutMs);
45057
+ signal?.addEventListener("abort", abort);
45058
+ child.stdout.on("data", (chunk) => {
45059
+ stdout += chunk.toString("utf8");
45060
+ });
45061
+ child.stderr.on("data", (chunk) => {
45062
+ stderr += chunk.toString("utf8");
45063
+ });
45064
+ child.on("error", (error) => settle(null, `\n${errorMessage$5(error)}\n`));
45065
+ child.on("close", (code) => settle(code));
45066
+ });
45067
+ }
45068
+ function writeShellArtifact(runDir, result) {
45069
+ mkdirSync(runDir.paths.trace, {
45070
+ recursive: true,
45071
+ mode: 448
45072
+ });
45073
+ const artifactPath = join(runDir.paths.trace, `shell-check-${Date.now().toString(36)}-${safeArtifactName(result.command.split(/\s+/)[0] ?? "command")}.txt`);
45074
+ writeFileSync(artifactPath, [
45075
+ `$ ${result.command}`,
45076
+ `cwd: ${result.cwd}`,
45077
+ `reason: ${result.reason}`,
45078
+ `timeoutMs: ${result.timeoutMs}`,
45079
+ `exitCode: ${result.exitCode ?? "null"}`,
45080
+ "",
45081
+ "--- stdout ---",
45082
+ redactSecrets(result.stdout),
45083
+ "",
45084
+ "--- stderr ---",
45085
+ redactSecrets(result.stderr)
45086
+ ].join("\n"), { mode: 384 });
45087
+ return artifactPath;
45088
+ }
45089
+ function truncateOutput(value) {
45090
+ if (value.length <= MAX_OUTPUT_CHARS) return value;
45091
+ return `${value.slice(0, MAX_OUTPUT_CHARS)}\n[quire:truncated] output truncated to ${MAX_OUTPUT_CHARS} characters\n`;
45092
+ }
45093
+ function redactSecrets(value) {
45094
+ 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]");
45095
+ }
45096
+ //#endregion
45097
+ //#region src/pipeline/prompts/local-run-safety-contract.ts
45098
+ const localRunSafetyContractPrompt = `Local Quire runtime contract:
45099
+
45100
+ - Obey the enabled tool list exactly; do not invent unavailable tools or capabilities.
45101
+ - Code inspection tools are read-only. Do not edit files, install dependencies, start app servers, reset repositories, or mutate user data.
45102
+ - Do not expose hidden reasoning, raw prompts, raw completions, provider credentials, API tokens, or secrets.
45103
+ - 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.`;
45104
+ //#endregion
45105
+ //#region src/pipeline/prompts/system-instructions.ts
45106
+ const systemInstructionsPrompt = `You are Quire, a read-only software run agent for investigation, verification, triage, explanation, and project-specific procedures.
45107
+
45108
+ Interpret the run brief first. Infer the user's intent naturally; do not require the user to choose a mode.
45109
+
45110
+ Resolve the target, procedure, and blockers from the user request, caller context, project runbook, available tools, and loaded skills.
45111
+
45112
+ 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.
45113
+
45114
+ 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.
45115
+
45116
+ 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.
45117
+
45118
+ Code inspection can guide exploration, but product behavior claims need direct evidence from the requested surface when the run requires browser or mobile observation.
45119
+
45120
+ Before broad exploration or target interaction, call record_progress with kind plan and a concise grounded plan.
45121
+
45122
+ Call write_handoff exactly once when the run is complete or blocked.`;
45123
+ //#endregion
44649
45124
  //#region src/pipeline/run-instructions.ts
44650
- const DEFAULT_RUN_INSTRUCTIONS_ID = "local-run-2026-06-02";
45125
+ const DEFAULT_RUN_INSTRUCTIONS_ID = "local-run-2026-06-06";
44651
45126
  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");
45127
+ const LOCAL_RUN_SAFETY_CONTRACT = localRunSafetyContractPrompt.trim();
45128
+ const SYSTEM_INSTRUCTIONS = systemInstructionsPrompt.trim();
44669
45129
  const COMMON_GUIDANCE = [
44670
45130
  "Start by understanding the user request, inferred intent, caller context, runtime capabilities, and project runbook status.",
44671
45131
  "Read .quire/runbook.md when target context, verification procedures, deployments, local URLs, devices, accounts, or safety conventions are needed.",
44672
45132
  "If the runbook is missing or insufficient, report the smallest missing context instead of guessing ports, app IDs, accounts, or deployment names.",
44673
45133
  "Prefer deterministic setup scripts or skills from the runbook over manually repeating login, seed-data, runner, or browser-state setup.",
45134
+ "Use loaded skills as optional QA lenses, not mandatory modes. Select the lenses relevant to the request, target, and available evidence.",
45135
+ "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.",
45136
+ "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
45137
  "For direct product tasks, perform the requested workflow and report what happened rather than reframing it as a bug.",
44675
45138
  "For bug reports, reproduce or rule out the reported behavior honestly with concrete evidence.",
45139
+ "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.",
45140
+ "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.",
45141
+ "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
45142
  "Stay within the target product, URLs, devices, commands, and systems named by the request, caller context, or runbook."
44677
45143
  ];
44678
45144
  const PLANNING_GUIDANCE = [
44679
45145
  "For any non-trivial run, record a plan before taking broad exploratory actions or interacting with browser/mobile targets.",
45146
+ "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
45147
  "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
45148
  "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
45149
  "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
45150
  "For verification-like requests, name the assertions you will check and later record each assertion as passed, failed, untested, or blocked.",
45151
+ "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.",
45152
+ "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.",
45153
+ "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
45154
  "For triage-like requests, name the severity, impact, ownership, reproducibility, and next-action signals you will inspect.",
44685
45155
  "For investigation-like requests, name the hypotheses, surfaces, code paths, logs, or product states you will examine.",
44686
45156
  "Record expected behavior before executing the action that tests it; this makes unexpected observations harder to rationalize as success."
44687
45157
  ];
44688
45158
  const TOOL_GUIDANCE = [
45159
+ "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.",
45160
+ "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
45161
  "Use agent-browser only when the runtime capabilities say browser automation is available.",
44690
45162
  "Before browser exploration, run agent-browser skills get core once when that skill is available so browser guidance matches the installed CLI.",
45163
+ "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
45164
  "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
45165
  "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
45166
  "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 +45195,13 @@ const DEFAULT_HANDOFF_GUIDANCE = [
44722
45195
  "Use needs_info when the report lacks necessary route, account, data, state, device, runner, or expected-behavior details after reasonable discovery.",
44723
45196
  "Use environment_blocked when the endpoint, app install/launch, auth, data, network, runner, or browser/mobile tooling prevents reaching the needed state.",
44724
45197
  "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.",
45198
+ "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.",
45199
+ "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.",
45200
+ "Do not include .harness-sessions paths, raw model transcripts, secrets, or private diagnostic state in evidenceRefs or handoffMarkdown.",
45201
+ "For markdown evidence links you write manually, prefer run-relative ./evidence/... paths over absolute local paths.",
44725
45202
  "Treat progress.jsonl as the canonical structured ledger. handoff.md is the human-readable report view, not the machine-readable source of truth.",
44726
45203
  "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.",
45204
+ "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
45205
  "For verification-like requests, include a decisive first paragraph with pass, fail, untested, blocked, and unresolved counts plus the strongest supported conclusion.",
44728
45206
  "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
45207
  "Keep pre-existing, not-regression, and out-of-scope findings separate from the requested verification verdict.",
@@ -45507,6 +45985,11 @@ function resolveRuntimeCapabilities(input) {
45507
45985
  available: true,
45508
45986
  names: [...READ_ONLY_RUN_CODE_TOOL_NAMES]
45509
45987
  },
45988
+ shellCheck: {
45989
+ available: true,
45990
+ toolName: "run_shell_check",
45991
+ safety: "dangerous_commands_blocked"
45992
+ },
45510
45993
  browser: browserCapability(input.headed),
45511
45994
  mobile: mobileCapability(),
45512
45995
  mcp: {
@@ -45545,6 +46028,7 @@ function composeRunBrief(input) {
45545
46028
  "## Run Artifacts",
45546
46029
  `Run directory: ${toJsonPath(input.runDir.root)}`,
45547
46030
  `Narrated progress file: ${toJsonPath(input.runDir.paths.progress)}`,
46031
+ `Evidence plan file: ${toJsonPath(input.runDir.paths.plan)}`,
45548
46032
  `Final handoff path: ${toJsonPath(input.runDir.paths.handoff)}`,
45549
46033
  `Evidence directory: ${toJsonPath(input.runDir.paths.evidence)}`,
45550
46034
  `Private diagnostic harness session directory: ${toJsonPath(input.runDir.paths.harnessSessions)}`,
@@ -45558,6 +46042,7 @@ function composeRunBrief(input) {
45558
46042
  "## Planning Requirement",
45559
46043
  [
45560
46044
  "Before broad exploration or browser/mobile interaction, record a concise plan with record_progress kind plan.",
46045
+ "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
46046
  "For verification-like work, state assertions before checking them and later record each as passed, failed, untested, or blocked.",
45562
46047
  "For triage-like work, state the severity, impact, ownership, and next-action signals to inspect.",
45563
46048
  "For investigation-like work, state the hypotheses, surfaces, or evidence paths to examine."
@@ -45643,6 +46128,7 @@ function capabilityLines(capabilities) {
45643
46128
  `Runner: ${capabilities.runnerKind} on ${capabilities.platform}/${capabilities.arch}`,
45644
46129
  `Repository cwd: ${toJsonPath(capabilities.cwd)}`,
45645
46130
  `Read-only code tools: ${capabilities.tools.readOnlyCode.names.join(", ")}`,
46131
+ `Shell checks: ${capabilities.tools.shellCheck.toolName} available for non-dangerous project commands, local app startup checks, and verification evidence`,
45646
46132
  `Browser automation: ${browser.available ? `${browser.toolName}${browser.headed === true ? " (headed)" : ""}` : `unavailable (${browser.unavailableReason ?? "unknown"})`}`,
45647
46133
  `Mobile automation: ${mobile.available ? `${mobile.toolName} tool registered; device/app readiness unknown until checked` : `unavailable (${mobile.unavailableReason ?? "unknown"})`}`,
45648
46134
  `MCP gateway: ${mcp.available ? `available (${mcp.toolNames.length === 0 ? "no tools listed" : mcp.toolNames.join(", ")})` : "unavailable"}`
@@ -45660,15 +46146,16 @@ function followupLines(context) {
45660
46146
  function orderedListOrNone(values) {
45661
46147
  return values.length === 0 ? "(none provided)" : values.map((value, index) => `${index + 1}. ${value}`).join("\n");
45662
46148
  }
46149
+ //#endregion
46150
+ //#region src/pipeline/quire-agent.ts
46151
+ const RUN_ACTIVITY_HEARTBEAT_INTERVAL_MS = 5e3;
45663
46152
  const QUIRE_AGENT_THINKING_LEVEL = INVESTIGATION_THINKING_LEVEL;
45664
46153
  async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons, runtimeCapabilities, runDir, sessionId, headed = false, progress, onProgressEvent, followup, skillPack, mcpGateway, onResolvedModel }) {
45665
46154
  const codebasePath = readRunMetadata(runDir)?.repoPath ?? runDir.root;
45666
46155
  let emittedHandoff;
45667
46156
  let observedToolAttempts = 0;
45668
- let diagnosisSteered = false;
45669
46157
  const observedTargetToolCallIds = /* @__PURE__ */ new Set();
45670
46158
  const targetToolCallArgs = /* @__PURE__ */ new Map();
45671
- const pendingSessionActions = [];
45672
46159
  const runtime = await createQuireAgentRuntime({
45673
46160
  runDir,
45674
46161
  codebasePath,
@@ -45691,23 +46178,16 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
45691
46178
  modelId: runtime.model.id
45692
46179
  });
45693
46180
  const unsubscribe = session.subscribe((event) => {
46181
+ touchRunActivity(runDir.root, { minIntervalMs: RUN_ACTIVITY_HEARTBEAT_INTERVAL_MS });
45694
46182
  logAgentProgress(event);
45695
46183
  emittedHandoff ??= extractWriteHandoff(event);
45696
46184
  rememberTargetToolCallArgs(event, registeredTargetTools, targetToolCallArgs);
45697
46185
  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
- }
46186
+ if (attemptsObserved > 0) observedToolAttempts += attemptsObserved;
45705
46187
  });
45706
46188
  try {
45707
46189
  await session.prompt(runBrief, { expandPromptTemplates: false });
45708
- await Promise.all(pendingSessionActions);
45709
46190
  if (emittedHandoff === void 0) await session.prompt(buildMissingRunHandoffPrompt(observedToolAttempts), { expandPromptTemplates: false });
45710
- await Promise.all(pendingSessionActions);
45711
46191
  } finally {
45712
46192
  runtime.writeSessionState();
45713
46193
  unsubscribe();
@@ -45733,12 +46213,17 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
45733
46213
  runDir,
45734
46214
  sessionId,
45735
46215
  cwd: codebasePath,
45736
- headed
46216
+ headed,
46217
+ onProgressEvent
45737
46218
  }) : void 0;
45738
46219
  const mobileTool = runtimeCapabilities.tools.mobile.available === true ? makeMobileTool({
45739
46220
  runDir,
45740
46221
  cwd: codebasePath
45741
46222
  }) : void 0;
46223
+ const shellCheckTool = makeShellCheckTool({
46224
+ runDir,
46225
+ cwd: codebasePath
46226
+ });
45742
46227
  const mcpGatewayTool = mcpGateway === void 0 ? void 0 : makeQuireMcpGatewayTool({
45743
46228
  gateway: mcpGateway,
45744
46229
  runId: runDir.runId
@@ -45781,6 +46266,7 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
45781
46266
  customTools: [
45782
46267
  ...browserTool === void 0 ? [] : [browserTool],
45783
46268
  ...mobileTool === void 0 ? [] : [mobileTool],
46269
+ shellCheckTool,
45784
46270
  withPromptGuidelines(createRecordProgressTool({
45785
46271
  runDir,
45786
46272
  onProgressEvent
@@ -45792,6 +46278,7 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
45792
46278
  ...READ_ONLY_RUN_CODE_TOOL_NAMES,
45793
46279
  ...browserTool === void 0 ? [] : [browserTool.name],
45794
46280
  ...mobileTool === void 0 ? [] : [mobileTool.name],
46281
+ shellCheckTool.name,
45795
46282
  "record_progress",
45796
46283
  writeHandoffTool.name,
45797
46284
  ...mcpGatewayTool === void 0 ? [] : [mcpGatewayTool.name]
@@ -45834,13 +46321,6 @@ function modelSourceProgressMessage(source, model) {
45834
46321
  case "quire_credits": return `Using Quire Credits via brokered model source (${model.provider}/${model.id}).`;
45835
46322
  }
45836
46323
  }
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
46324
  function buildMissingRunHandoffPrompt(targetActions) {
45845
46325
  return [
45846
46326
  "The previous run ended without calling write_handoff.",
@@ -45883,10 +46363,13 @@ function isTargetReproductionAttempt(args, target) {
45883
46363
  if (args === void 0) return true;
45884
46364
  if (target === "mobile") {
45885
46365
  const command = findMobileCommand(args);
45886
- return command !== "open" && command !== "install";
46366
+ return command !== "open" && command !== "install" && command !== "screenshot";
45887
46367
  }
45888
- if (args[0] === "open") return false;
45889
- if (args[0] === "set" && args[1] === "viewport") return false;
46368
+ const command = args[0];
46369
+ if (command === "open") return false;
46370
+ if (command === "set" && args[1] === "viewport") return false;
46371
+ if (command === "skills") return false;
46372
+ if (command === "snapshot" || command === "screenshot" || command === "get" || command === "eval") return false;
45890
46373
  return true;
45891
46374
  }
45892
46375
  function rememberToolCallId(toolCallId, observedToolCallIds) {
@@ -48279,8 +48762,8 @@ function defaultSleep$1(ms) {
48279
48762
  const ARTIFACT_MODE = "evidence";
48280
48763
  const investigationCommandSchema = {
48281
48764
  version: 1,
48282
- command: "quire investigate",
48283
- description: "Starts a durable Quire investigation for a natural-language question or piped brief.",
48765
+ command: "quire run",
48766
+ description: "Starts a durable Quire run for a natural-language request or piped brief. Bare `quire \"<prompt>\"` is equivalent to `quire run \"<prompt>\"`.",
48284
48767
  defaultMode: "attached",
48285
48768
  input: {
48286
48769
  pipedStdin: true,
@@ -48290,7 +48773,7 @@ const investigationCommandSchema = {
48290
48773
  stdout: {
48291
48774
  human: "Without --json, stdout shows a short start summary and attaches to the live run log unless --detach is passed.",
48292
48775
  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."
48776
+ schema: "With --schema, stdout contains this newline-terminated JSON schema and no run starts."
48294
48777
  },
48295
48778
  stderr: { progress: "Machine-readable errors are written to stderr. Human attached runs stream live run logs after the start summary." },
48296
48779
  arguments: [
@@ -48299,7 +48782,7 @@ const investigationCommandSchema = {
48299
48782
  type: "string",
48300
48783
  positional: true,
48301
48784
  requiredUnless: "piped stdin",
48302
- description: "Natural-language investigation query or instruction."
48785
+ description: "Natural-language run request or instruction."
48303
48786
  },
48304
48787
  {
48305
48788
  name: "--json",
@@ -48382,21 +48865,20 @@ const investigationCommandSchema = {
48382
48865
  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
48866
  examples: [
48384
48867
  "quire \"verify checkout before I approve this PR\"",
48385
- "printf 'why is CI failing?\\nRelevant log: ...' | quire investigate --json",
48868
+ "printf 'why is CI failing?\\nRelevant log: ...' | quire run --json",
48386
48869
  "quire status run_9x4mdq7p2h8kc6nv4apz --json",
48387
48870
  "quire watch run_9x4mdq7p2h8kc6nv4apz"
48388
48871
  ]
48389
48872
  };
48390
- const investigateCommand = defineCommand({
48873
+ const quireRunCommand = defineCommand({
48391
48874
  meta: {
48392
- name: "investigate",
48393
- alias: "ask",
48394
- description: "Investigate a question against the current workspace."
48875
+ name: "run",
48876
+ description: "Run evidence-backed QA, verification, triage, or investigation work."
48395
48877
  },
48396
48878
  args: {
48397
48879
  prompt: {
48398
48880
  type: "positional",
48399
- description: "Natural-language investigation query.",
48881
+ description: "Natural-language run request.",
48400
48882
  required: false
48401
48883
  },
48402
48884
  url: {
@@ -48419,11 +48901,11 @@ const investigateCommand = defineCommand({
48419
48901
  },
48420
48902
  headed: {
48421
48903
  type: "boolean",
48422
- description: "Show the browser window while the investigation agent runs."
48904
+ description: "Show the browser window while the Quire agent runs."
48423
48905
  },
48424
48906
  schema: {
48425
48907
  type: "boolean",
48426
- description: "Emit the machine-readable investigation invocation schema and exit."
48908
+ description: "Emit the machine-readable run invocation schema and exit."
48427
48909
  }
48428
48910
  },
48429
48911
  async run({ args }) {
@@ -48466,7 +48948,7 @@ async function runInvestigate(options) {
48466
48948
  });
48467
48949
  if (options.json === true) writeJson(stdout, handle);
48468
48950
  else {
48469
- log.success(`Started investigation ${color.value(handle.runId)}.`, {
48951
+ log.success(`Started run ${color.value(handle.runId)}.`, {
48470
48952
  output: stdout,
48471
48953
  spacing: 0
48472
48954
  });
@@ -48480,7 +48962,12 @@ async function runInvestigate(options) {
48480
48962
  spacing: 0,
48481
48963
  withGuide: true
48482
48964
  });
48483
- else log.message(`${color.label("Run")}: ${color.path(handle.runPath)}`, {
48965
+ else log.message([
48966
+ `${color.label("Run")}: ${color.path(handle.runPath)}`,
48967
+ `${color.label("Status")}: ${color.command(handle.statusCommand)}`,
48968
+ `${color.label("Watch")}: ${color.command(handle.watchCommand)}`,
48969
+ `This run continues in the background. You can safely exit; use ${color.command(handle.statusCommand)} for the current state or ${color.command(handle.watchCommand)} to follow progress logs.`
48970
+ ], {
48484
48971
  output: stdout,
48485
48972
  spacing: 0,
48486
48973
  withGuide: true
@@ -48631,6 +49118,7 @@ function createDoctorContext(overrides = {}) {
48631
49118
  cwd: resolve(overrides.cwd ?? process.cwd()),
48632
49119
  runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot()),
48633
49120
  authStore: overrides.authStore ?? authStore,
49121
+ env: overrides.env ?? process.env,
48634
49122
  fetchFn: overrides.fetchFn ?? fetch,
48635
49123
  execFn: overrides.execFn ?? defaultExecFn,
48636
49124
  probeTimeoutMs: overrides.probeTimeoutMs ?? 2e3
@@ -48647,25 +49135,30 @@ async function defaultExecFn(file, args, options) {
48647
49135
  };
48648
49136
  }
48649
49137
  async function checkAuthPresent(context) {
48650
- const credentials = await resolveAuthCredentials({ store: context.authStore });
49138
+ const source = context.env["QUIRE_API_TOKEN"]?.trim() ? "env_api_token" : "stored_login";
49139
+ const credentials = await resolveAuthCredentials({
49140
+ store: context.authStore,
49141
+ env: context.env
49142
+ });
48651
49143
  if (credentials === null) return { result: {
48652
49144
  id: "auth.present",
48653
49145
  section: "identity",
48654
49146
  severity: "fail",
48655
- message: "No Quire credentials on this machine.",
48656
- fix: { command: "quire login" }
49147
+ message: "No Quire credentials found. Authenticate with an API token or interactive login.",
49148
+ fix: { command: "Set QUIRE_API_TOKEN or run `quire login`" }
48657
49149
  } };
48658
49150
  return {
48659
49151
  result: {
48660
49152
  id: "auth.present",
48661
49153
  section: "identity",
48662
49154
  severity: "pass",
48663
- message: "Credentials present."
49155
+ message: `Credentials present (${authSourceLabel(source)}; API ${credentials.apiBaseUrl}).`
48664
49156
  },
48665
49157
  auth: {
48666
49158
  apiBaseUrl: credentials.apiBaseUrl,
48667
49159
  accessToken: credentials.accessToken,
48668
49160
  tokenType: credentials.tokenType,
49161
+ source,
48669
49162
  ...credentials.user === void 0 ? {} : { user: credentials.user }
48670
49163
  }
48671
49164
  };
@@ -48688,8 +49181,8 @@ async function checkAuthValid(context, auth) {
48688
49181
  id: "auth.valid",
48689
49182
  section: "identity",
48690
49183
  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" }
49184
+ message: `Could not validate API token against ${auth.apiBaseUrl}: ${toMessage(error)}`,
49185
+ fix: { command: apiTokenFixCommand(auth.apiBaseUrl) }
48693
49186
  } };
48694
49187
  }
48695
49188
  try {
@@ -48703,8 +49196,8 @@ async function checkAuthValid(context, auth) {
48703
49196
  id: "auth.valid",
48704
49197
  section: "identity",
48705
49198
  severity: "fail",
48706
- message: "Stored credentials were rejected by Quire.",
48707
- fix: { command: "quire login" }
49199
+ message: `Stored login was rejected by ${auth.apiBaseUrl}.`,
49200
+ fix: { command: loginFixCommand(auth.apiBaseUrl) }
48708
49201
  } };
48709
49202
  const user = lookup.data.user;
48710
49203
  return {
@@ -48721,8 +49214,8 @@ async function checkAuthValid(context, auth) {
48721
49214
  id: "auth.valid",
48722
49215
  section: "identity",
48723
49216
  severity: "fail",
48724
- message: `Could not validate Quire session: ${toMessage(error)}`,
48725
- fix: { command: "quire login" }
49217
+ message: `Could not validate stored login against ${auth.apiBaseUrl}: ${toMessage(error)}`,
49218
+ fix: { command: loginFixCommand(auth.apiBaseUrl) }
48726
49219
  } };
48727
49220
  }
48728
49221
  }
@@ -48764,6 +49257,15 @@ function brokerToCheckResult(status) {
48764
49257
  message: `Model broker available (${status.selectedProviderMode ?? "default"}).`
48765
49258
  };
48766
49259
  }
49260
+ function authSourceLabel(source) {
49261
+ return source === "env_api_token" ? `${QUIRE_API_TOKEN_ENV} API token` : "stored login";
49262
+ }
49263
+ function apiTokenFixCommand(apiBaseUrl) {
49264
+ 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}\``;
49265
+ }
49266
+ function loginFixCommand(apiBaseUrl) {
49267
+ return `Run \`quire login --api-url ${apiBaseUrl}\`, or set QUIRE_API_TOKEN and QUIRE_API_URL for API-token auth`;
49268
+ }
48767
49269
  async function checkRunbook(context) {
48768
49270
  const runbook = await inspectProjectRunbookStatus(context.cwd);
48769
49271
  if (!runbook.exists) return {
@@ -49176,22 +49678,78 @@ async function runLogout(options) {
49176
49678
  //#endregion
49177
49679
  //#region src/commands/setup.ts
49178
49680
  const SETUP_URL = "https://quire.sh/agent-setup";
49681
+ const VERIFICATION_SKILL_URL = "https://quire.sh/agent-verification";
49179
49682
  const AGENT_SETUP_PROMPT = `Set up this repository for Quire investigations.
49180
49683
 
49181
49684
  Please load and follow the Quire agent setup instructions from ${SETUP_URL}.
49182
49685
 
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.`;
49686
+ Inspect the project, ask focused questions for important missing runbook facts instead of writing placeholder unknowns, then create a .quire/runbook.md that encodes the best-practices verification loop for this specific project. Capture how Quire should self-verify work in this domain: target/environment choice, local startup, browser/API/CLI/mobile checks, console/runtime signals, core flows, verification commands, design/reference surfaces, auth/data setup, performance/accessibility expectations, and mutation-safety rules. Update AGENTS.md with concise Quire notes, then run Quire doctor using the available Quire command, e.g. \`quire doctor\` or \`npx @quireco/cli doctor\`, and report anything still blocked.`;
49687
+ const QUIRE_SKILL_MARKDOWN = `---
49688
+ name: quire
49689
+ description: Runs Quire CLI evidence-backed QA, verification, triage, and investigation work. Use after implementing UI or workflow changes, when testing local/preview/prod URLs, or when claims need screenshots, command output, observations, and a trustworthy handoff.
49690
+ ---
49691
+
49692
+ # Quire verification
49693
+
49694
+ Load and follow the current Quire verification skill from ${VERIFICATION_SKILL_URL}.
49695
+
49696
+ Use Quire when a task needs evidence-backed verification, QA, product dogfooding, bug reproduction, or triage. Prefer \`quire run\` for new work.
49697
+ `;
49698
+ const SKILL_INSTALL_TARGETS = [{
49699
+ id: "agents",
49700
+ label: "Universal",
49701
+ targetPath: ".agents/skills/quire/SKILL.md",
49702
+ hint: "~/.agents/skills/quire/SKILL.md · Pi, OpenCode, Amp, Codex-style agents",
49703
+ aliases: [
49704
+ "agents",
49705
+ "agent-skills",
49706
+ "pi",
49707
+ "opencode",
49708
+ "amp",
49709
+ "codex",
49710
+ "other"
49711
+ ]
49712
+ }, {
49713
+ id: "claude",
49714
+ label: "Claude Code",
49715
+ targetPath: ".claude/skills/quire/SKILL.md",
49716
+ hint: "~/.claude/skills/quire/SKILL.md",
49717
+ aliases: ["claude", "claude-code"]
49718
+ }];
49184
49719
  const setupCommand = defineCommand({
49185
49720
  meta: {
49186
49721
  name: "setup",
49187
49722
  description: "Prepare this workspace for Quire investigations."
49188
49723
  },
49189
- async run() {
49190
- await runSetup();
49724
+ args: {
49725
+ skill: {
49726
+ type: "string",
49727
+ description: "Comma-separated local agent skill targets to install: agents, claude, all, none.",
49728
+ valueHint: "targets"
49729
+ },
49730
+ "no-skill": {
49731
+ type: "boolean",
49732
+ description: "Skip installing local Quire agent skills."
49733
+ },
49734
+ "no-interactive": {
49735
+ type: "boolean",
49736
+ description: "Do not prompt; use default setup choices."
49737
+ }
49738
+ },
49739
+ async run({ args }) {
49740
+ await runSetup({
49741
+ skill: args["no-skill"] === true ? false : args.skill,
49742
+ interactive: args["no-interactive"] !== true
49743
+ });
49191
49744
  }
49192
49745
  });
49193
49746
  async function runSetup(options = {}) {
49194
49747
  const stdout = options.io?.stdout ?? process.stdout;
49748
+ const selectedTargets = await selectSkillInstallTargets({
49749
+ skill: options.skill,
49750
+ interactive: options.interactive ?? (options.io?.stdout === void 0 && process.stdout.isTTY === true)
49751
+ });
49752
+ const installedSkills = await installQuireSkill(options.homeDir ?? homedir(), selectedTargets);
49195
49753
  log.step("Set up Quire for this workspace", {
49196
49754
  output: stdout,
49197
49755
  spacing: 0
@@ -49205,6 +49763,51 @@ async function runSetup(options = {}) {
49205
49763
  writeLine(stdout, color.label("--- copy prompt ---"));
49206
49764
  writeLine(stdout, AGENT_SETUP_PROMPT);
49207
49765
  writeLine(stdout, color.label("--- end prompt ---"));
49766
+ if (installedSkills.length > 0) {
49767
+ writeLine(stdout);
49768
+ writeLine(stdout, color.label("Installed local Quire skill:"));
49769
+ for (const path of installedSkills) writeLine(stdout, ` ${color.success("✓")} ${path}`);
49770
+ }
49771
+ }
49772
+ async function selectSkillInstallTargets(options) {
49773
+ if (options.skill === false || options.skill === "none") return [];
49774
+ if (options.skill !== void 0) return resolveSkillTargets(options.skill);
49775
+ if (!options.interactive) return [...SKILL_INSTALL_TARGETS];
49776
+ const selected = await multiselect({
49777
+ message: "Which local agent skill directories should Quire install into?",
49778
+ options: SKILL_INSTALL_TARGETS.map((target) => ({
49779
+ label: target.label,
49780
+ value: target.id,
49781
+ hint: target.hint
49782
+ })),
49783
+ initialValues: ["agents", "claude"],
49784
+ required: false
49785
+ });
49786
+ if (isCancel(selected) || selected.length === 0) return [];
49787
+ return resolveSkillTargets(selected.join(","));
49788
+ }
49789
+ function resolveSkillTargets(value) {
49790
+ const parts = value.split(",").map((part) => part.trim().toLowerCase()).filter((part) => part.length > 0);
49791
+ if (parts.includes("all")) return [...SKILL_INSTALL_TARGETS];
49792
+ const selected = /* @__PURE__ */ new Map();
49793
+ for (const part of parts) {
49794
+ const target = SKILL_INSTALL_TARGETS.find((candidate) => candidate.id === part || candidate.aliases.includes(part));
49795
+ if (target !== void 0) selected.set(target.id, target);
49796
+ }
49797
+ return [...selected.values()];
49798
+ }
49799
+ async function installQuireSkill(homeDir, targets) {
49800
+ const installed = [];
49801
+ await Promise.all(targets.map(async (target) => {
49802
+ const path = join(homeDir, target.targetPath);
49803
+ await mkdir(dirname(path), {
49804
+ recursive: true,
49805
+ mode: 448
49806
+ });
49807
+ await writeFile(path, QUIRE_SKILL_MARKDOWN, { mode: 384 });
49808
+ installed.push(path);
49809
+ }));
49810
+ return installed.sort();
49208
49811
  }
49209
49812
  //#endregion
49210
49813
  //#region src/commands/whoami.ts
@@ -49356,7 +49959,7 @@ function formatWalletBalance(value) {
49356
49959
  }
49357
49960
  //#endregion
49358
49961
  //#region package.json
49359
- var version$1 = "0.0.9";
49962
+ var version$1 = "0.0.10";
49360
49963
  //#endregion
49361
49964
  //#region src/cli.ts
49362
49965
  const subCommands = {
@@ -49382,18 +49985,14 @@ const subCommands = {
49382
49985
  continue: continueCommand,
49383
49986
  doctor: doctorCommand,
49384
49987
  env: envCommand,
49385
- investigate: investigateCommand,
49988
+ run: quireRunCommand,
49386
49989
  runs: runsCommand,
49387
49990
  setup: setupCommand,
49388
49991
  status: statusCommand,
49389
49992
  sync: syncCommand,
49390
49993
  watch: watchCommand
49391
49994
  };
49392
- const subCommandAliases = new Set([
49393
- "ask",
49394
- "me",
49395
- "resume"
49396
- ]);
49995
+ const subCommandAliases = new Set(["me", "resume"]);
49397
49996
  const cli = defineCommand({
49398
49997
  meta: {
49399
49998
  name: "quire",
@@ -49427,7 +50026,7 @@ function isVersionRequest(rawArgs) {
49427
50026
  function normalizeRawArgs(rawArgs) {
49428
50027
  const first = rawArgs[0];
49429
50028
  if (first === void 0 || isTopLevelFlag(first) || isSubCommandName(first)) return rawArgs;
49430
- return ["investigate", ...rawArgs];
50029
+ return ["run", ...rawArgs];
49431
50030
  }
49432
50031
  function isTopLevelFlag(arg) {
49433
50032
  return arg === "--help" || arg === "-h" || arg === "--version" || arg === "-v";