@posthog/wizard 2.45.0 → 2.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +5 -1
  2. package/dist/{add-mcp-server-to-clients-CNzZMSat.js → add-mcp-server-to-clients-Dg64O4OZ.js} +53 -6
  3. package/dist/add-mcp-server-to-clients-Dg64O4OZ.js.map +1 -0
  4. package/dist/{agent-interface-CF7GsdFf.js → agent-interface-RZY46R62.js} +837 -8
  5. package/dist/agent-interface-RZY46R62.js.map +1 -0
  6. package/dist/{agent-runner-B0LTVFfw.js → agent-runner-D8pEOs_2.js} +209 -48
  7. package/dist/agent-runner-D8pEOs_2.js.map +1 -0
  8. package/dist/{analytics-DQEuL6qS.js → analytics-B4dny8FA.js} +20 -6
  9. package/dist/analytics-B4dny8FA.js.map +1 -0
  10. package/dist/analytics-BAhDPsPK.js +2 -0
  11. package/dist/{api-uCMvDII5.js → api-CdPvsnUm.js} +3 -3
  12. package/dist/{api-uCMvDII5.js.map → api-CdPvsnUm.js.map} +1 -1
  13. package/dist/bin.js +300 -986
  14. package/dist/bin.js.map +1 -1
  15. package/dist/{ci-install-Bow8-f0j.js → ci-install-g8govb-6.js} +4 -4
  16. package/dist/{ci-install-Bow8-f0j.js.map → ci-install-g8govb-6.js.map} +1 -1
  17. package/dist/{commandments-CyB7Icr1.js → commandments-DSNrOeOC.js} +1 -1
  18. package/dist/{commandments-CyB7Icr1.js.map → commandments-DSNrOeOC.js.map} +1 -1
  19. package/dist/commandments-qTW7pwaT.js +2 -0
  20. package/dist/{debug-DBmxx59Q.js → debug-BiU-S12q.js} +19 -5
  21. package/dist/debug-BiU-S12q.js.map +1 -0
  22. package/dist/{debug-BixHZmHv.js → debug-DQWRmiju.js} +1 -1
  23. package/dist/{defaults-8zjNjGGi.js → defaults-CtWMmC8s.js} +1 -1
  24. package/dist/{defaults-8zjNjGGi.js.map → defaults-CtWMmC8s.js.map} +1 -1
  25. package/dist/{env-api-key-AcC_tz8G.js → env-api-key-C3LzH8Lr.js} +1 -1
  26. package/dist/{env-api-key-AcC_tz8G.js.map → env-api-key-C3LzH8Lr.js.map} +1 -1
  27. package/dist/{environment-kZtkp7eG.js → environment-BT0RRheV.js} +3 -3
  28. package/dist/{environment-kZtkp7eG.js.map → environment-BT0RRheV.js.map} +1 -1
  29. package/dist/{file-utils-BAhYy5vC.js → file-utils-B1_Eciop.js} +2 -2
  30. package/dist/{file-utils-BAhYy5vC.js.map → file-utils-B1_Eciop.js.map} +1 -1
  31. package/dist/{file-watcher-BL-IRbR7.js → file-watcher-4frv2dFl.js} +2 -2
  32. package/dist/{file-watcher-BL-IRbR7.js.map → file-watcher-4frv2dFl.js.map} +1 -1
  33. package/dist/{headless-ui-DWmMyhaP.js → headless-ui-DQVBpo0v.js} +2 -2
  34. package/dist/{headless-ui-DWmMyhaP.js.map → headless-ui-DQVBpo0v.js.map} +1 -1
  35. package/dist/{interactive-Cyic0bGL.js → interactive-Bgb8njPS.js} +2 -2
  36. package/dist/{interactive-Cyic0bGL.js.map → interactive-Bgb8njPS.js.map} +1 -1
  37. package/dist/{mcp-BSGmEWjt.js → mcp-BEWwXAXk.js} +2 -2
  38. package/dist/{mcp-BSGmEWjt.js.map → mcp-BEWwXAXk.js.map} +1 -1
  39. package/dist/{mcp-prompt-streaming-C227vG1X.js → mcp-prompt-streaming-MfYiLHox.js} +3 -3
  40. package/dist/{mcp-prompt-streaming-C227vG1X.js.map → mcp-prompt-streaming-MfYiLHox.js.map} +1 -1
  41. package/dist/{non-interactive-DQA9vn9f.js → non-interactive-C0NbQTcD.js} +2 -2
  42. package/dist/{non-interactive-DQA9vn9f.js.map → non-interactive-C0NbQTcD.js.map} +1 -1
  43. package/dist/{orchestrator-tools-NuWONNkF.js → orchestrator-tools-KtXpYp6j.js} +3 -3
  44. package/dist/{orchestrator-tools-NuWONNkF.js.map → orchestrator-tools-KtXpYp6j.js.map} +1 -1
  45. package/dist/{package-manager-DKtR3xzM.js → package-manager-L1RLE1ne.js} +2 -2
  46. package/dist/{package-manager-DKtR3xzM.js.map → package-manager-L1RLE1ne.js.map} +1 -1
  47. package/dist/{pi-DJCln9jJ.js → pi-BMs3MY7y.js} +48 -32
  48. package/dist/pi-BMs3MY7y.js.map +1 -0
  49. package/dist/{playground-CbEndzc8.js → playground-DZoNrDEM.js} +7 -7
  50. package/dist/{playground-CbEndzc8.js.map → playground-DZoNrDEM.js.map} +1 -1
  51. package/dist/{posthog-DFyux4Vw.js → posthog-DZZbziJ1.js} +1 -1
  52. package/dist/{posthog-DFyux4Vw.js.map → posthog-DZZbziJ1.js.map} +1 -1
  53. package/dist/{posthog-integration-dmWJZ8mj.js → posthog-integration-BnWgrvts.js} +2006 -17
  54. package/dist/posthog-integration-BnWgrvts.js.map +1 -0
  55. package/dist/{provisioning-CcxCry5p.js → provisioning-DS8zDveH.js} +3 -3
  56. package/dist/{provisioning-CcxCry5p.js.map → provisioning-DS8zDveH.js.map} +1 -1
  57. package/dist/{queue-tools-Bw-AANLM.js → queue-tools-rAN2wwDC.js} +2 -2
  58. package/dist/{queue-tools-Bw-AANLM.js.map → queue-tools-rAN2wwDC.js.map} +1 -1
  59. package/dist/{registry-D2V88NoC.js → registry-Zp3B2L4q.js} +4 -4
  60. package/dist/{registry-D2V88NoC.js.map → registry-Zp3B2L4q.js.map} +1 -1
  61. package/dist/{security-DOtBCBvx.js → security-R8m9JSHq.js} +10 -8
  62. package/dist/security-R8m9JSHq.js.map +1 -0
  63. package/dist/{setup-utils-Qp-u6BZk.js → setup-utils-BMy5Zj3Z.js} +31 -9
  64. package/dist/setup-utils-BMy5Zj3Z.js.map +1 -0
  65. package/dist/{start-tui-DHa_9aiB.js → start-tui-CJ7i0pbr.js} +114 -30
  66. package/dist/start-tui-CJ7i0pbr.js.map +1 -0
  67. package/dist/{steps-D07HUQyE.js → steps-CsvYRuTV.js} +7 -7
  68. package/dist/{steps-D07HUQyE.js.map → steps-CsvYRuTV.js.map} +1 -1
  69. package/dist/{store-DSwsITKX.js → store-CVZL9JJe.js} +4 -4
  70. package/dist/store-CVZL9JJe.js.map +1 -0
  71. package/dist/{subagent-Dk4K5MDa.js → subagent-C1OOk1kh.js} +2 -2
  72. package/dist/{subagent-Dk4K5MDa.js.map → subagent-C1OOk1kh.js.map} +1 -1
  73. package/dist/{task-CQnV9Qd2.js → task-BMaazWIr.js} +12 -13
  74. package/dist/task-BMaazWIr.js.map +1 -0
  75. package/dist/{task-stream-B7qia9eW.js → task-stream-DlgLKdMC.js} +4 -4
  76. package/dist/{task-stream-B7qia9eW.js.map → task-stream-DlgLKdMC.js.map} +1 -1
  77. package/dist/{tasks-C7BQp659.js → tasks-BcsQlnuL.js} +2 -2
  78. package/dist/{tasks-C7BQp659.js.map → tasks-BcsQlnuL.js.map} +1 -1
  79. package/dist/{telemetry-DgahJGSx.js → telemetry-3Y8LxNVU.js} +3 -3
  80. package/dist/{telemetry-DgahJGSx.js.map → telemetry-3Y8LxNVU.js.map} +1 -1
  81. package/dist/{terminal-Bb6tZkf-.js → terminal-B4cCBtMj.js} +12 -12
  82. package/dist/{terminal-Bb6tZkf-.js.map → terminal-B4cCBtMj.js.map} +1 -1
  83. package/dist/tools-C07lR0s-.js +199 -0
  84. package/dist/tools-C07lR0s-.js.map +1 -0
  85. package/dist/{wizard-abort-BxtdkgI-.js → wizard-abort-D1UxHeiE.js} +3 -3
  86. package/dist/{wizard-abort-BxtdkgI-.js.map → wizard-abort-D1UxHeiE.js.map} +1 -1
  87. package/dist/{wizard-abort-DpDmuCfA.js → wizard-abort-DoI_ZWM-.js} +1 -1
  88. package/dist/yara-hooks-ChkXWyzs.js +2 -0
  89. package/dist/{yara-hooks-pw86rZ8y.js → yara-hooks-D9PSjEqo.js} +82 -21
  90. package/dist/yara-hooks-D9PSjEqo.js.map +1 -0
  91. package/package.json +1 -1
  92. package/dist/add-mcp-server-to-clients-CNzZMSat.js.map +0 -1
  93. package/dist/agent-interface-CF7GsdFf.js.map +0 -1
  94. package/dist/agent-runner-B0LTVFfw.js.map +0 -1
  95. package/dist/analytics-DQEuL6qS.js.map +0 -1
  96. package/dist/analytics-YD3D38z2.js +0 -2
  97. package/dist/commandments-DguPfCX8.js +0 -2
  98. package/dist/debug-DBmxx59Q.js.map +0 -1
  99. package/dist/pi-DJCln9jJ.js.map +0 -1
  100. package/dist/posthog-integration-dmWJZ8mj.js.map +0 -1
  101. package/dist/security-DOtBCBvx.js.map +0 -1
  102. package/dist/setup-utils-Qp-u6BZk.js.map +0 -1
  103. package/dist/start-tui-DHa_9aiB.js.map +0 -1
  104. package/dist/store-DSwsITKX.js.map +0 -1
  105. package/dist/task-CQnV9Qd2.js.map +0 -1
  106. package/dist/tools-BJEvSUWN.js +0 -122
  107. package/dist/tools-BJEvSUWN.js.map +0 -1
  108. package/dist/types-BRe_EzTW.js +0 -46
  109. package/dist/types-BRe_EzTW.js.map +0 -1
  110. package/dist/wizard-tools-Dg8o2lcH.js +0 -783
  111. package/dist/wizard-tools-Dg8o2lcH.js.map +0 -1
  112. package/dist/yara-hooks-CncaWRuC.js +0 -2
  113. package/dist/yara-hooks-pw86rZ8y.js.map +0 -1
@@ -1,17 +1,21 @@
1
1
  import { n as __require } from "./rolldown-runtime-B_-DWIq7.js";
2
- import { J as POSTHOG_FLAG_HEADER_PREFIX, St as runtimeEnv, Z as POSTHOG_PROPERTY_HEADER_PREFIX, a as getLogFilePath, f as getUI, lt as WIZARD_ORCHESTRATOR_FLAG_KEY, o as initLogFile, pt as WIZARD_REMARK_EVENT_NAME, r as debug, s as logToFile, vt as wizardUserAgentForProgram } from "./debug-DBmxx59Q.js";
3
- import { n as analytics } from "./analytics-DQEuL6qS.js";
2
+ import { Ct as runtimeEnv, J as POSTHOG_FLAG_HEADER_PREFIX, Z as POSTHOG_PROPERTY_HEADER_PREFIX, a as getLogFilePath, f as getUI, lt as WIZARD_ORCHESTRATOR_FLAG_KEY, o as initLogFile, pt as WIZARD_REMARK_EVENT_NAME, r as debug, s as logToFile, yt as wizardUserAgentForProgram } from "./debug-BiU-S12q.js";
3
+ import { n as analytics } from "./analytics-B4dny8FA.js";
4
4
  import { n as ADDITIONAL_FEATURE_PROMPTS } from "./wizard-session-tp4Zdbwl.js";
5
- import { i as wizardAbort, n as registerCleanup, t as WizardError } from "./wizard-abort-BxtdkgI-.js";
6
- import { n as createWizardToolsServer, t as WIZARD_TOOL_NAMES } from "./wizard-tools-Dg8o2lcH.js";
7
- import { l as prewarmYaraScanner, n as createPostToolUseYaraHooks, r as createPreToolUseYaraHooks } from "./yara-hooks-pw86rZ8y.js";
8
- import { t as getWizardCommandments } from "./commandments-CyB7Icr1.js";
5
+ import { i as wizardAbort, n as registerCleanup, t as WizardError } from "./wizard-abort-D1UxHeiE.js";
6
+ import { c as makeMutex, i as buildOrchestratorTools, l as writeJsonAtomic } from "./queue-tools-rAN2wwDC.js";
7
+ import { _ as AUDIT_CHECKS_FILE, l as prewarmYaraScanner, n as createPostToolUseYaraHooks, r as createPreToolUseYaraHooks, x as coerceAuditChecks } from "./yara-hooks-D9PSjEqo.js";
8
+ import { t as getWizardCommandments } from "./commandments-DSNrOeOC.js";
9
9
  import { createRequire } from "node:module";
10
10
  import * as fs$1 from "fs";
11
+ import fs from "fs";
11
12
  import path from "path";
12
13
  import { spawnSync } from "node:child_process";
13
14
  import axios from "axios";
15
+ import { z } from "zod";
14
16
  import * as os from "os";
17
+ import { unzipSync } from "fflate";
18
+ import { randomUUID } from "crypto";
15
19
  //#region src/utils/custom-headers.ts
16
20
  /**
17
21
  * Builds a list of custom headers for ANTHROPIC_CUSTOM_HEADERS.
@@ -453,6 +457,831 @@ function evaluateBashCommand(rawCommand) {
453
457
  return commandDecision(normalized);
454
458
  }
455
459
  //#endregion
460
+ //#region src/lib/wizard-ask-bridge.ts
461
+ /**
462
+ * WizardAskBridge — host-side promise broker for the `wizard_ask` MCP tool.
463
+ *
464
+ * The `wizard_ask` tool needs to (a) read information from the wizard
465
+ * session (the active skill id, used as the analytics `source`) and
466
+ * (b) drive the TUI overlay. Wiring `wizard-tools.ts` directly to either
467
+ * would couple our pure-data MCP server to the runtime UI layer.
468
+ *
469
+ * The bridge is the seam: `wizard-tools.ts` depends on this interface,
470
+ * and `agent-runner.ts` constructs an implementation that knows about
471
+ * both the session and `getUI()`.
472
+ */
473
+ /** Sentinel returned for unanswered fields on cancellation or timeout. */
474
+ const CANCELLED_SENTINEL = "__cancelled__";
475
+ function buildCancelledAnswers(questions) {
476
+ const out = {};
477
+ for (const q of questions) out[q.id] = CANCELLED_SENTINEL;
478
+ return out;
479
+ }
480
+ function isFullyCancelled(answers) {
481
+ const values = Object.values(answers);
482
+ if (values.length === 0) return false;
483
+ return values.every((v) => v === CANCELLED_SENTINEL);
484
+ }
485
+ function createWizardAskBridge(opts) {
486
+ const timeoutMs = opts.timeoutMs ?? 3e5;
487
+ return { async request({ questions }) {
488
+ const pending = {
489
+ id: randomUUID(),
490
+ questions,
491
+ source: opts.getSource(),
492
+ richLinks: opts.richLinks ?? false
493
+ };
494
+ const startedAt = Date.now();
495
+ let timer;
496
+ const timeoutPromise = new Promise((resolve) => {
497
+ timer = setTimeout(() => {
498
+ opts.cancelQuestion?.();
499
+ resolve(buildCancelledAnswers(questions));
500
+ }, timeoutMs);
501
+ });
502
+ try {
503
+ const answers = await Promise.race([opts.showQuestion(pending), timeoutPromise]);
504
+ const durationMs = Date.now() - startedAt;
505
+ if (isFullyCancelled(answers)) analytics.wizardCapture("wizard_ask cancelled", {
506
+ source: pending.source,
507
+ question_count: questions.length,
508
+ duration_ms: durationMs,
509
+ timed_out: durationMs >= timeoutMs
510
+ });
511
+ else analytics.wizardCapture("wizard_ask answered", {
512
+ source: pending.source,
513
+ question_count: questions.length,
514
+ duration_ms: durationMs
515
+ });
516
+ return answers;
517
+ } finally {
518
+ if (timer) clearTimeout(timer);
519
+ }
520
+ } };
521
+ }
522
+ //#endregion
523
+ //#region src/lib/fetch-retry.ts
524
+ /**
525
+ * Retry wrapper for fetching from the skills/agents server (GitHub releases in
526
+ * production). GitHub releases blips transiently, so every fetch on the run's
527
+ * critical path — skill menu, skill zips, agent menu, agent prompt bodies —
528
+ * goes through here rather than a bare `fetch`.
529
+ */
530
+ const DEFAULT_TIMEOUT_MS = 6e4;
531
+ const DEFAULT_MAX_ATTEMPTS = 3;
532
+ const DEFAULT_BACKOFF_MS = 500;
533
+ function sleep(ms) {
534
+ return new Promise((resolve) => setTimeout(resolve, ms));
535
+ }
536
+ /** Fetch a URL, retrying transient failures (network error or non-ok HTTP) with backoff. */
537
+ async function fetchWithRetry(url, opts = {}) {
538
+ const { fetchImpl = fetch, sleepImpl = sleep, timeoutMs = DEFAULT_TIMEOUT_MS, maxAttempts = DEFAULT_MAX_ATTEMPTS, backoffMs = DEFAULT_BACKOFF_MS } = opts;
539
+ const failures = [];
540
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
541
+ const resp = await fetchImpl(url, { signal: AbortSignal.timeout(timeoutMs) });
542
+ if (!resp.ok) throw new Error(`HTTP ${resp.status} ${resp.statusText}`);
543
+ return resp;
544
+ } catch (err) {
545
+ failures.push(`attempt ${attempt}: ${err.message}`);
546
+ if (attempt < maxAttempts) await sleepImpl(backoffMs * 2 ** (attempt - 1));
547
+ }
548
+ throw new Error(`fetch ${url} failed — ${failures.join("; ")}`);
549
+ }
550
+ //#endregion
551
+ //#region src/lib/wizard-tools/tools.ts
552
+ /**
553
+ * Shared wizard-tool behavior — skill discovery/install, env-file helpers,
554
+ * the wizard_ask cap policy, secret-vault plumbing, and the audit ledger.
555
+ * One implementation consumed by both protocol facades: the MCP server
556
+ * (`./mcp`) and the pi-native tools (`harness/pi/tools.ts`) — tool behavior
557
+ * cannot drift between harnesses.
558
+ */
559
+ /** Expand a bundle entry into one entry per variant, so the menu reads the same whether a group ships bundled or as zips. */
560
+ function expandBundleEntry(entry) {
561
+ if (!entry.bundle || !entry.variants) return [entry];
562
+ return entry.variants.map((variant) => ({
563
+ ...variant,
564
+ name: entry.name,
565
+ group: entry.group,
566
+ bundle: true,
567
+ downloadUrl: entry.downloadUrl
568
+ }));
569
+ }
570
+ /**
571
+ * Fetch the skill menu from the skills server.
572
+ * Returns parsed data on success, `null` on failure.
573
+ */
574
+ async function fetchSkillMenu(skillsBaseUrl, opts = {}) {
575
+ const menuUrl = `${skillsBaseUrl}/skill-menu.json`;
576
+ try {
577
+ logToFile(`fetchSkillMenu: fetching from ${menuUrl}`);
578
+ const data = await (await fetchWithRetry(menuUrl, opts)).json();
579
+ for (const [category, entries] of Object.entries(data.categories)) data.categories[category] = entries.flatMap(expandBundleEntry);
580
+ logToFile(`fetchSkillMenu: loaded (${Object.keys(data.categories).length} categories)`);
581
+ return data;
582
+ } catch (err) {
583
+ logToFile(`fetchSkillMenu: error: ${err.message}`);
584
+ return null;
585
+ }
586
+ }
587
+ /** Extract a zip buffer, refusing entries that escape destDir (zip-slip). */
588
+ function extractZipArchive(zip, destDir) {
589
+ const root = path.resolve(destDir);
590
+ let written = 0;
591
+ for (const [entryPath, data] of Object.entries(unzipSync(zip))) {
592
+ const target = path.resolve(root, entryPath);
593
+ if (target !== root && !target.startsWith(root + path.sep)) throw new Error(`zip entry escapes destination: ${entryPath}`);
594
+ if (entryPath.endsWith("/")) {
595
+ fs.mkdirSync(target, { recursive: true });
596
+ continue;
597
+ }
598
+ fs.mkdirSync(path.dirname(target), { recursive: true });
599
+ fs.writeFileSync(target, data);
600
+ written++;
601
+ }
602
+ return written;
603
+ }
604
+ /** Unpack the one variant this entry names out of a bundle; the rest is noise and never hits disk. */
605
+ function extractBundle(bundle, destDir, entryId) {
606
+ if (typeof bundle?.id !== "string" || typeof bundle?.variants !== "object" || bundle.variants === null) throw new Error("malformed bundle: expected { id, variants }");
607
+ const files = bundle.variants[entryId.slice(bundle.id.length + 1)];
608
+ if (!files) throw new Error(`bundle ${bundle.id} has no variant "${entryId}"`);
609
+ const root = path.resolve(destDir);
610
+ let written = 0;
611
+ for (const [entryPath, contents] of Object.entries(files)) {
612
+ const target = path.resolve(root, entryPath);
613
+ if (target !== root && !target.startsWith(root + path.sep)) throw new Error(`bundle entry escapes destination: ${entryPath}`);
614
+ fs.mkdirSync(path.dirname(target), { recursive: true });
615
+ fs.writeFileSync(target, contents);
616
+ written++;
617
+ }
618
+ return written;
619
+ }
620
+ /** Download a URL to a buffer, retrying transient failures with backoff. */
621
+ async function downloadWithRetry(url, opts = {}) {
622
+ const resp = await fetchWithRetry(url, opts);
623
+ return new Uint8Array(await resp.arrayBuffer());
624
+ }
625
+ /**
626
+ * Download and extract a skill.
627
+ * By default installs to `<installDir>/.claude/skills/<id>/`.
628
+ * Pass `skillsRoot` to override the base directory (e.g. `.posthog/skills`).
629
+ */
630
+ async function downloadSkill(skillEntry, installDir, skillsRoot) {
631
+ const skillDir = skillsRoot ? path.join(installDir, skillsRoot, skillEntry.id) : path.join(installDir, ".claude", "skills", skillEntry.id);
632
+ let step = "download";
633
+ try {
634
+ fs.mkdirSync(skillDir, { recursive: true });
635
+ const data = await downloadWithRetry(skillEntry.downloadUrl);
636
+ step = "extract";
637
+ const fileCount = skillEntry.bundle ? extractBundle(JSON.parse(Buffer.from(data).toString("utf8")), skillDir, skillEntry.id) : extractZipArchive(data, skillDir);
638
+ fs.writeFileSync(path.join(skillDir, ".posthog-wizard"), "");
639
+ logToFile(`downloadSkill: installed ${skillEntry.id} from ${skillEntry.downloadUrl} (${fileCount} files)`);
640
+ analytics.wizardCapture("skill installed", {
641
+ skill_id: skillEntry.id,
642
+ platform: process.platform
643
+ });
644
+ return { success: true };
645
+ } catch (err) {
646
+ logToFile(`downloadSkill: error: ${err.message}`);
647
+ analytics.wizardCapture("skill install failed", {
648
+ skill_id: skillEntry.id,
649
+ step,
650
+ platform: process.platform,
651
+ error: String(err.message).slice(0, 500)
652
+ });
653
+ return {
654
+ success: false,
655
+ error: err.message
656
+ };
657
+ }
658
+ }
659
+ /**
660
+ * High-level "install a skill by ID" helper. Fetches the skill menu,
661
+ * finds the skill, downloads and extracts it. Programs should use this
662
+ * instead of composing fetchSkillMenu + downloadSkill themselves.
663
+ */
664
+ async function installSkillById(skillId, installDir, skillsBaseUrl, skillsRoot) {
665
+ const menu = await fetchSkillMenu(skillsBaseUrl);
666
+ if (!menu) return { kind: "menu-fetch-failed" };
667
+ const skill = Object.values(menu.categories).flat().find((s) => s.id === skillId);
668
+ if (!skill) return {
669
+ kind: "skill-not-found",
670
+ skillId
671
+ };
672
+ const result = await downloadSkill(skill, installDir, skillsRoot);
673
+ if (!result.success) return {
674
+ kind: "download-failed",
675
+ message: result.error ?? "unknown"
676
+ };
677
+ return {
678
+ kind: "ok",
679
+ path: skillsRoot ? `${skillsRoot}/${skillId}` : `.claude/skills/${skillId}`
680
+ };
681
+ }
682
+ /**
683
+ * Pure decision function for the wizard_ask caps. Returns whether the
684
+ * upcoming call should proceed and, if not, the error message to surface
685
+ * to the agent. Extracted so the policy can be unit-tested without
686
+ * spinning up an MCP server.
687
+ *
688
+ * The adjacency nudge fires exactly once per run (the caller records it
689
+ * via `adjacencyNudged`) — flows that legitimately need several
690
+ * sequential, answer-dependent asks then proceed up to `maxQuestions`.
691
+ * Without the flag the rejected call would never advance the counter and
692
+ * every later call would be rejected, making caps above the threshold
693
+ * unreachable.
694
+ */
695
+ function evaluateAskCap(callCount, maxQuestions, adjacencyNudged = false) {
696
+ if (callCount >= maxQuestions) return {
697
+ kind: "capped",
698
+ reason: "max_questions",
699
+ message: `Error: wizard_ask cap reached (${maxQuestions} calls in this run). Proceed with sensible defaults using the answers you already have, or emit [ABORT] requirements-incomplete.`
700
+ };
701
+ if (!adjacencyNudged && callCount >= 3) return {
702
+ kind: "capped",
703
+ reason: "adjacency",
704
+ message: `Error: too many wizard_ask calls in a row (${callCount} so far). Batch the remaining questions into a single call — the schema accepts up to 8 questions per invocation. If the remaining questions truly depend on earlier answers, ask again and they will go through.`
705
+ };
706
+ return { kind: "ok" };
707
+ }
708
+ const ENV_FILE_PATH_DESCRIPTION = "Path to the .env file, relative to the wizard working directory. Pass \".env\" for a file in that directory, or include the selected subproject path (for example, \"packages/app/.env\") for a nested project. Never prefix it with the wizard working directory's path inside an ancestor repository.";
709
+ /**
710
+ * Resolve filePath relative to workingDirectory, rejecting path traversal.
711
+ */
712
+ function resolveEnvPath(workingDirectory, filePath) {
713
+ const resolved = path.resolve(workingDirectory, filePath);
714
+ if (!resolved.startsWith(workingDirectory + path.sep) && resolved !== workingDirectory) throw new Error(`Path traversal rejected: "${filePath}" resolves outside working directory`);
715
+ return resolved;
716
+ }
717
+ /**
718
+ * Ensure the given env file basename is covered by .gitignore in the working directory.
719
+ * Creates .gitignore if it doesn't exist; appends the entry if missing.
720
+ */
721
+ function ensureGitignoreCoverage(workingDirectory, envFileName) {
722
+ const gitignorePath = path.join(workingDirectory, ".gitignore");
723
+ if (fs.existsSync(gitignorePath)) {
724
+ const content = fs.readFileSync(gitignorePath, "utf8");
725
+ if (content.split("\n").some((line) => line.trim() === envFileName)) return;
726
+ const newContent = content.endsWith("\n") ? `${content}${envFileName}\n` : `${content}\n${envFileName}\n`;
727
+ fs.writeFileSync(gitignorePath, newContent, "utf8");
728
+ } else fs.writeFileSync(gitignorePath, `${envFileName}\n`, "utf8");
729
+ }
730
+ /**
731
+ * Parse a .env file's content and return the set of defined key names.
732
+ */
733
+ function parseEnvKeys(content) {
734
+ const keys = /* @__PURE__ */ new Set();
735
+ for (const line of content.split("\n")) {
736
+ const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
737
+ if (match) keys.add(match[1]);
738
+ }
739
+ return keys;
740
+ }
741
+ /**
742
+ * Merge key-value pairs into existing .env content.
743
+ * Updates existing keys in-place, appends new keys at the end.
744
+ */
745
+ function mergeEnvValues(content, values) {
746
+ let result = content;
747
+ const updatedKeys = /* @__PURE__ */ new Set();
748
+ for (const [key, value] of Object.entries(values)) {
749
+ const regex = new RegExp(`^(\\s*${key}\\s*=).*$`, "m");
750
+ if (regex.test(result)) {
751
+ result = result.replace(regex, `$1${value}`);
752
+ updatedKeys.add(key);
753
+ }
754
+ }
755
+ const newKeys = Object.entries(values).filter(([key]) => !updatedKeys.has(key));
756
+ if (newKeys.length > 0) {
757
+ if (result.length > 0 && !result.endsWith("\n")) result += "\n";
758
+ for (const [key, value] of newKeys) result += `${key}=${value}\n`;
759
+ }
760
+ return result;
761
+ }
762
+ /**
763
+ * Swap sensitive text answers for opaque vault refs before they return to
764
+ * the agent — the raw value never enters the LLM conversation. Cancelled
765
+ * answers pass through as the sentinel, unvaulted.
766
+ */
767
+ function vaultSensitiveAnswers(questions, answers, vault) {
768
+ const sensitiveById = new Map(questions.filter((q) => q.sensitive).map((q) => [q.id, q.prompt]));
769
+ const sanitised = {};
770
+ for (const [id, answer] of Object.entries(answers)) {
771
+ const label = sensitiveById.get(id);
772
+ if (label !== void 0 && typeof answer === "string" && answer !== "__cancelled__") {
773
+ const ref = vault.put(answer, {
774
+ label,
775
+ source: "wizard_ask"
776
+ });
777
+ sanitised[id] = { secretRef: ref };
778
+ logToFile(`wizard_ask: vaulted answer for "${id}" as ${ref}`);
779
+ } else sanitised[id] = answer;
780
+ }
781
+ return sanitised;
782
+ }
783
+ /**
784
+ * Resolve `{secretRef}` entries host-side, so the value is written but never
785
+ * returned to the agent. Callers format their own error envelope from the
786
+ * failing key/ref.
787
+ */
788
+ function resolveEnvSecretRefs(values, vault) {
789
+ const resolved = {};
790
+ const refKeys = [];
791
+ for (const [key, val] of Object.entries(values)) {
792
+ if (typeof val === "string") {
793
+ resolved[key] = val;
794
+ continue;
795
+ }
796
+ const secret = vault.get(val.secretRef);
797
+ if (secret === void 0) return {
798
+ ok: false,
799
+ key,
800
+ secretRef: val.secretRef
801
+ };
802
+ resolved[key] = secret;
803
+ refKeys.push(key);
804
+ }
805
+ return {
806
+ ok: true,
807
+ values: resolved,
808
+ refKeys
809
+ };
810
+ }
811
+ const AUDIT_STATUSES = [
812
+ "pending",
813
+ "pass",
814
+ "error",
815
+ "warning",
816
+ "suggestion"
817
+ ];
818
+ /** Atomically write the audit ledger. Thin typed wrapper over writeJsonAtomic. */
819
+ function writeLedgerAtomic(targetPath, checks) {
820
+ writeJsonAtomic(targetPath, checks);
821
+ }
822
+ /**
823
+ * Apply a batch of patches to the ledger by id. Returns the new array and the
824
+ * list of update ids that didn't match any existing check.
825
+ */
826
+ function applyAuditUpdates(current, updates) {
827
+ const byId = new Map(current.map((c) => [c.id, c]));
828
+ const unknown = [];
829
+ for (const u of updates) {
830
+ const existing = byId.get(u.id);
831
+ if (!existing) {
832
+ unknown.push(u.id);
833
+ continue;
834
+ }
835
+ byId.set(u.id, {
836
+ ...existing,
837
+ status: u.status,
838
+ ...u.file !== void 0 ? { file: u.file } : {},
839
+ ...u.details !== void 0 ? { details: u.details } : {}
840
+ });
841
+ }
842
+ return {
843
+ next: current.map((c) => byId.get(c.id) ?? c),
844
+ unknown
845
+ };
846
+ }
847
+ /**
848
+ * Append new checks to a seeded ledger. Duplicate ids are reported without
849
+ * mutating the current ledger, including duplicates inside the additions.
850
+ */
851
+ function applyAuditAdditions(current, additions) {
852
+ const existingIds = new Set(current.map((c) => c.id));
853
+ const additionIds = /* @__PURE__ */ new Set();
854
+ const duplicates = [];
855
+ for (const check of additions) {
856
+ if (existingIds.has(check.id) || additionIds.has(check.id)) {
857
+ duplicates.push(check.id);
858
+ continue;
859
+ }
860
+ additionIds.add(check.id);
861
+ }
862
+ if (duplicates.length > 0) return {
863
+ next: current,
864
+ duplicates
865
+ };
866
+ return {
867
+ next: [...current, ...additions],
868
+ duplicates: []
869
+ };
870
+ }
871
+ function readLedger(targetPath) {
872
+ if (!fs.existsSync(targetPath)) return [];
873
+ try {
874
+ return coerceAuditChecks(JSON.parse(fs.readFileSync(targetPath, "utf8")));
875
+ } catch {
876
+ return [];
877
+ }
878
+ }
879
+ function appendAuditChecksToLedger(targetPath, additions) {
880
+ if (!fs.existsSync(targetPath)) return {
881
+ ok: false,
882
+ reason: "missing-ledger"
883
+ };
884
+ const { next, duplicates } = applyAuditAdditions(readLedger(targetPath), additions);
885
+ if (duplicates.length > 0) return {
886
+ ok: false,
887
+ reason: "duplicate-ids",
888
+ ids: duplicates
889
+ };
890
+ writeLedgerAtomic(targetPath, next);
891
+ return {
892
+ ok: true,
893
+ added: additions.length
894
+ };
895
+ }
896
+ const SERVER_NAME = "wizard-tools";
897
+ /** Tool names exposed by the wizard-tools server, keyed for selective use. */
898
+ const WIZARD_TOOL_NAMES = {
899
+ checkEnvKeys: `mcp__${SERVER_NAME}__check_env_keys`,
900
+ setEnvValues: `mcp__${SERVER_NAME}__set_env_values`,
901
+ detectPackageManager: `mcp__${SERVER_NAME}__detect_package_manager`,
902
+ loadSkillMenu: `mcp__${SERVER_NAME}__load_skill_menu`,
903
+ installSkill: `mcp__${SERVER_NAME}__install_skill`,
904
+ auditSeedChecks: `mcp__${SERVER_NAME}__audit_seed_checks`,
905
+ auditAddChecks: `mcp__${SERVER_NAME}__audit_add_checks`,
906
+ auditResolveChecks: `mcp__${SERVER_NAME}__audit_resolve_checks`,
907
+ wizardAsk: `mcp__${SERVER_NAME}__wizard_ask`,
908
+ enqueueTask: `mcp__${SERVER_NAME}__enqueue_task`,
909
+ completeTask: `mcp__${SERVER_NAME}__complete_task`,
910
+ readHandoffs: `mcp__${SERVER_NAME}__read_handoffs`
911
+ };
912
+ //#endregion
913
+ //#region src/lib/secret-vault.ts
914
+ /**
915
+ * Session-scoped secret vault.
916
+ *
917
+ * Tools that handle sensitive values (personal API keys pasted by the user
918
+ * or minted by the wizard, OAuth tokens, etc.) store them here and return
919
+ * an opaque `secret:<uuid>` ref to the agent. The agent passes the ref
920
+ * around to subsequent tools — `set_env_values` resolves it host-side
921
+ * before writing — but the raw value never enters the LLM conversation.
922
+ *
923
+ * The vault is created once per `createWizardToolsServer` call and lives
924
+ * for the duration of a single wizard run. There is no persistence and
925
+ * no cross-session sharing; refs minted in one run cannot be resolved in
926
+ * another.
927
+ */
928
+ const REF_PREFIX = "secret:";
929
+ function createSecretVault() {
930
+ const store = /* @__PURE__ */ new Map();
931
+ return {
932
+ put(value, meta) {
933
+ const ref = `${REF_PREFIX}${randomUUID()}`;
934
+ store.set(ref, {
935
+ value,
936
+ meta: {
937
+ ref,
938
+ label: meta.label,
939
+ source: meta.source,
940
+ createdAt: Date.now()
941
+ }
942
+ });
943
+ return ref;
944
+ },
945
+ get(ref) {
946
+ return store.get(ref)?.value;
947
+ },
948
+ has(ref) {
949
+ return store.has(ref);
950
+ },
951
+ list() {
952
+ return [...store.values()].map((s) => s.meta);
953
+ },
954
+ clear() {
955
+ store.clear();
956
+ }
957
+ };
958
+ }
959
+ //#endregion
960
+ //#region src/lib/wizard-tools/mcp.ts
961
+ /**
962
+ * MCP facade over `./tools` — the unified in-process server the anthropic
963
+ * harness mounts. Declarations (zod schemas, descriptions) and the MCP result
964
+ * envelope live here; all behavior is imported from the shared core.
965
+ *
966
+ * Provides: check_env_keys, set_env_values, detect_package_manager,
967
+ * load_skill_menu / install_skill, audit_* ledger tools, wizard_ask, and the
968
+ * orchestrator queue tools. Secret values never leave the machine.
969
+ */
970
+ const auditCheckSchema = z.object({
971
+ id: z.string().min(1),
972
+ area: z.string().min(1),
973
+ label: z.string().min(1),
974
+ status: z.enum(AUDIT_STATUSES),
975
+ file: z.string().optional(),
976
+ details: z.string().optional()
977
+ });
978
+ const auditUpdateSchema = z.object({
979
+ id: z.string().min(1),
980
+ status: z.enum(AUDIT_STATUSES),
981
+ file: z.string().optional(),
982
+ details: z.string().optional()
983
+ });
984
+ let _sdkModule$1 = null;
985
+ async function getSDKModule$1() {
986
+ if (!_sdkModule$1) _sdkModule$1 = await import("@anthropic-ai/claude-agent-sdk");
987
+ return _sdkModule$1;
988
+ }
989
+ /** Default per-run cap on wizard_ask calls when no override is provided. */
990
+ /**
991
+ * Create the unified in-process MCP server with all wizard tools.
992
+ * Must be called asynchronously because the SDK is an ESM module loaded via dynamic import.
993
+ */
994
+ async function createWizardToolsServer(options) {
995
+ const { workingDirectory, detectPackageManager, skillsBaseUrl, askBridge, askMaxQuestions = 10, secretVault = createSecretVault(), orchestrator } = options;
996
+ const { tool, createSdkMcpServer } = await getSDKModule$1();
997
+ let askCallCount = 0;
998
+ let askAdjacencyNudged = false;
999
+ let cachedSkillMenu = {};
1000
+ let categoryNames = ["integration"];
1001
+ const menu = await fetchSkillMenu(skillsBaseUrl);
1002
+ if (menu) cachedSkillMenu = menu.categories;
1003
+ const keys = Object.keys(cachedSkillMenu);
1004
+ if (keys.length > 0) categoryNames = keys;
1005
+ const checkEnvKeys = tool("check_env_keys", "Check which environment variable keys are present or missing in a .env file. Never reveals values.", {
1006
+ filePath: z.string().describe(ENV_FILE_PATH_DESCRIPTION),
1007
+ keys: z.array(z.string()).describe("Environment variable key names to check")
1008
+ }, (args) => {
1009
+ const resolved = resolveEnvPath(workingDirectory, args.filePath);
1010
+ logToFile(`check_env_keys: ${resolved}, keys: ${args.keys.join(", ")}`);
1011
+ const existingKeys = fs.existsSync(resolved) ? parseEnvKeys(fs.readFileSync(resolved, "utf8")) : /* @__PURE__ */ new Set();
1012
+ const results = {};
1013
+ for (const key of args.keys) results[key] = existingKeys.has(key) ? "present" : "missing";
1014
+ return { content: [{
1015
+ type: "text",
1016
+ text: JSON.stringify(results, null, 2)
1017
+ }] };
1018
+ });
1019
+ const setEnvValues = tool("set_env_values", "Create or update environment variable keys in a .env file. Creates the file if it does not exist. Ensures .gitignore coverage. Each value can be either a literal string or a secret reference of the form `{ \"secretRef\": \"secret:...\" }` returned by another tool (e.g. wizard_ask). Secret references are resolved locally — the actual value is written to the file but never returned to the agent.", {
1020
+ filePath: z.string().describe(ENV_FILE_PATH_DESCRIPTION),
1021
+ values: z.record(z.string(), z.union([z.string(), z.object({ secretRef: z.string() })])).describe("Key → (literal string OR { secretRef } pointing to a vaulted secret)")
1022
+ }, (args) => {
1023
+ const forbidden = Object.keys(args.values).find((k) => k.toUpperCase() === "POSTHOG_KEY");
1024
+ if (forbidden) return {
1025
+ content: [{
1026
+ type: "text",
1027
+ text: `Error: "${forbidden}" is not a valid PostHog env var name. Use the project-specific key name from your framework's integration guide (e.g. NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN).`
1028
+ }],
1029
+ isError: true
1030
+ };
1031
+ const resolution = resolveEnvSecretRefs(args.values, secretVault);
1032
+ if (!resolution.ok) return {
1033
+ content: [{
1034
+ type: "text",
1035
+ text: `Error: secret reference "${resolution.secretRef}" for key "${resolution.key}" is not known to the vault. The ref may have expired, been minted in a different run, or been mistyped.`
1036
+ }],
1037
+ isError: true
1038
+ };
1039
+ const { values: resolvedValues, refKeys: resolvedRefKeys } = resolution;
1040
+ const resolved = resolveEnvPath(workingDirectory, args.filePath);
1041
+ logToFile(`set_env_values: ${resolved}, keys: ${Object.keys(resolvedValues).join(", ")}${resolvedRefKeys.length > 0 ? ` (secret refs: ${resolvedRefKeys.join(", ")})` : ""}`);
1042
+ const content = mergeEnvValues(fs.existsSync(resolved) ? fs.readFileSync(resolved, "utf8") : "", resolvedValues);
1043
+ const dir = path.dirname(resolved);
1044
+ if (!fs.existsSync(dir)) {
1045
+ analytics.wizardCapture("set_env_values parent dir missing", { platform: process.platform });
1046
+ return {
1047
+ content: [{
1048
+ type: "text",
1049
+ text: `Error: parent directory does not exist: "${path.dirname(args.filePath)}". filePath is resolved against the wizard working directory — pass ".env" for a file there, or "<subproject>/.env" for an existing nested project.`
1050
+ }],
1051
+ isError: true
1052
+ };
1053
+ }
1054
+ fs.writeFileSync(resolved, content, "utf8");
1055
+ ensureGitignoreCoverage(workingDirectory, path.basename(resolved));
1056
+ return { content: [{
1057
+ type: "text",
1058
+ text: `Updated ${Object.keys(args.values).length} key(s) in ${args.filePath}`
1059
+ }] };
1060
+ });
1061
+ const detectPM = tool("detect_package_manager", "Detect which package manager(s) the project uses. Returns the name, install command, and run command for each detected package manager. Call this before running any install commands.", {}, async () => {
1062
+ logToFile(`detect_package_manager: scanning ${workingDirectory}`);
1063
+ const result = await detectPackageManager(workingDirectory);
1064
+ logToFile(`detect_package_manager: detected ${result.detected.length} package manager(s)`);
1065
+ return { content: [{
1066
+ type: "text",
1067
+ text: JSON.stringify(result, null, 2)
1068
+ }] };
1069
+ });
1070
+ const loadSkillMenu = tool("load_skill_menu", "Load available PostHog skills for a category. Returns skill IDs and names. Call this first, then use install_skill with the chosen ID.", { category: z.enum(categoryNames).describe("Skill category") }, (args) => {
1071
+ const skills = cachedSkillMenu[args.category];
1072
+ if (!skills || skills.length === 0) return {
1073
+ content: [{
1074
+ type: "text",
1075
+ text: `No skills found for category "${args.category}".`
1076
+ }],
1077
+ isError: true
1078
+ };
1079
+ const menuText = skills.map((s) => `- ${s.id}: ${s.name}`).join("\n");
1080
+ logToFile(`load_skill_menu: returning ${skills.length} skills for "${args.category}"`);
1081
+ return { content: [{
1082
+ type: "text",
1083
+ text: menuText
1084
+ }] };
1085
+ });
1086
+ const installSkill = tool("install_skill", "Download and install a PostHog skill by ID. Call load_skill_menu first to see available skills. Extracts the skill to .claude/skills/<skillId>/.", { skillId: z.string().describe("Skill ID from the skill menu (e.g., \"integration-nextjs-app-router\")") }, async (args) => {
1087
+ if (!/^[a-z0-9][a-z0-9_-]*$/.test(args.skillId)) return {
1088
+ content: [{
1089
+ type: "text",
1090
+ text: "Error: skillId must be lowercase alphanumeric with hyphens."
1091
+ }],
1092
+ isError: true
1093
+ };
1094
+ const skill = Object.values(cachedSkillMenu).flat().find((s) => s.id === args.skillId);
1095
+ if (!skill) return {
1096
+ content: [{
1097
+ type: "text",
1098
+ text: `Error: skill "${args.skillId}" not found. Use load_skill_menu to see available skills.`
1099
+ }],
1100
+ isError: true
1101
+ };
1102
+ const result = await downloadSkill(skill, workingDirectory);
1103
+ if (result.success) return { content: [{
1104
+ type: "text",
1105
+ text: `Skill installed to .claude/skills/${args.skillId}/`
1106
+ }] };
1107
+ else {
1108
+ analytics.captureException(/* @__PURE__ */ new Error("Skill install failed: download-failed"), {
1109
+ source: "install_skill_tool",
1110
+ skill_id: args.skillId,
1111
+ error_detail: String(result.error).slice(0, 500),
1112
+ platform: process.platform
1113
+ });
1114
+ return {
1115
+ content: [{
1116
+ type: "text",
1117
+ text: `Error installing skill: ${result.error}`
1118
+ }],
1119
+ isError: true
1120
+ };
1121
+ }
1122
+ });
1123
+ const auditLedgerPath = path.join(workingDirectory, AUDIT_CHECKS_FILE);
1124
+ const auditMutex = makeMutex();
1125
+ const auditSeedChecks = tool("audit_seed_checks", "Seed the audit ledger at .posthog-audit-checks.json with the full set of pending checks. Call this once at the start of the audit. Atomically replaces any existing ledger.", { checks: z.array(auditCheckSchema).describe("Full pending checklist to write to the ledger") }, async (args) => {
1126
+ return auditMutex(() => {
1127
+ writeLedgerAtomic(auditLedgerPath, args.checks);
1128
+ logToFile(`audit_seed_checks: wrote ${args.checks.length} entries`);
1129
+ return { content: [{
1130
+ type: "text",
1131
+ text: `Seeded ${args.checks.length} audit checks.`
1132
+ }] };
1133
+ });
1134
+ });
1135
+ const auditAddChecks = tool("audit_add_checks", "Append one or more pending checks to the existing audit ledger at .posthog-audit-checks.json. Call audit_seed_checks first. Atomically rejects duplicate ids without changing the ledger.", { checks: z.array(auditCheckSchema).min(1).describe("Additional checks to append to the existing ledger") }, async (args) => {
1136
+ return auditMutex(() => {
1137
+ const result = appendAuditChecksToLedger(auditLedgerPath, args.checks);
1138
+ if (!result.ok) {
1139
+ if (result.reason === "missing-ledger") return {
1140
+ content: [{
1141
+ type: "text",
1142
+ text: "Error: audit ledger does not exist. Run audit_seed_checks first."
1143
+ }],
1144
+ isError: true
1145
+ };
1146
+ return {
1147
+ content: [{
1148
+ type: "text",
1149
+ text: `Error: duplicate check id(s): ${result.ids.join(", ")}. Check ids must be unique.`
1150
+ }],
1151
+ isError: true
1152
+ };
1153
+ }
1154
+ logToFile(`audit_add_checks: added ${result.added} entries`);
1155
+ return { content: [{
1156
+ type: "text",
1157
+ text: `Added ${result.added} audit check(s).`
1158
+ }] };
1159
+ });
1160
+ });
1161
+ const auditResolveChecks = tool("audit_resolve_checks", "Resolve one or more audit checks by id. Patches each entry's status (and optional file/details) and writes the ledger back atomically. Concurrent calls serialize.", { updates: z.array(auditUpdateSchema).min(1).describe("Patches to apply, keyed by check id") }, async (args) => {
1162
+ return auditMutex(() => {
1163
+ const { next, unknown } = applyAuditUpdates(readLedger(auditLedgerPath), args.updates);
1164
+ if (unknown.length > 0) return {
1165
+ content: [{
1166
+ type: "text",
1167
+ text: `Error: unknown check id(s): ${unknown.join(", ")}. Run audit_seed_checks first or check the id.`
1168
+ }],
1169
+ isError: true
1170
+ };
1171
+ writeLedgerAtomic(auditLedgerPath, next);
1172
+ logToFile(`audit_resolve_checks: applied ${args.updates.length} update(s)`);
1173
+ return { content: [{
1174
+ type: "text",
1175
+ text: `Resolved ${args.updates.length} check(s).`
1176
+ }] };
1177
+ });
1178
+ });
1179
+ const askQuestionSchema = z.object({
1180
+ id: z.string().min(1).describe("Stable key for the answer in the response map"),
1181
+ prompt: z.string().min(1).describe("Question text shown to the user"),
1182
+ kind: z.enum([
1183
+ "single",
1184
+ "multi",
1185
+ "text"
1186
+ ]).describe("'single' = pick one option, 'multi' = pick any, 'text' = free-form single-line answer"),
1187
+ options: z.array(z.object({
1188
+ label: z.string(),
1189
+ value: z.string(),
1190
+ description: z.string().optional().describe("Optional secondary line shown dimmed and wrapped beneath the label (multi-select only). Use when a choice needs more than a title — e.g. what a custom scout watches and what makes it speak up.")
1191
+ })).optional().describe("Required for kind=single|multi; ignored for kind=text"),
1192
+ required: z.boolean().optional().describe("Defaults to true"),
1193
+ sensitive: z.boolean().optional().describe("Only valid for kind='text'. When true, the user's answer is stored in the wizard's secret vault and returned to you as { secretRef: 'secret:...' } instead of the raw string. Use for API keys, tokens, and any other secret the user types in. The secretRef is only resolved by wizard-tools that accept it (e.g. set_env_values) — it is NOT resolved when passed to other MCP tools (e.g. PostHog data-warehouse tools), which will reject it. For a secret that must reach another tool, write it to the env with set_env_values first, or use that tool's own credential-reference flow.")
1194
+ });
1195
+ return createSdkMcpServer({
1196
+ name: SERVER_NAME,
1197
+ version: "1.0.0",
1198
+ tools: [
1199
+ checkEnvKeys,
1200
+ setEnvValues,
1201
+ detectPM,
1202
+ loadSkillMenu,
1203
+ installSkill,
1204
+ auditSeedChecks,
1205
+ auditAddChecks,
1206
+ auditResolveChecks,
1207
+ tool("wizard_ask", "Ask the user one or more structured questions and wait for their answers. Use this whenever you would otherwise inline a question in your text output. Batch related questions into a single call (up to 8) rather than asking one at a time; sequential calls are fine when later questions genuinely depend on earlier answers. A fully cancelled or timed-out response does NOT count against the per-run cap — treat it as \"the user declined\" and fall back gracefully (e.g. hand over a deep link) without worrying about a wasted call.", { questions: z.array(askQuestionSchema).min(1).max(8) }, async (args) => {
1208
+ if (!askBridge) return {
1209
+ content: [{
1210
+ type: "text",
1211
+ text: "Error: wizard_ask is not available in this environment (CI / non-interactive). Proceed with sensible defaults or emit [ABORT] requirements-incomplete."
1212
+ }],
1213
+ isError: true
1214
+ };
1215
+ const capDecision = evaluateAskCap(askCallCount, askMaxQuestions, askAdjacencyNudged);
1216
+ if (capDecision.kind === "capped") {
1217
+ if (capDecision.reason === "adjacency") askAdjacencyNudged = true;
1218
+ analytics.wizardCapture("wizard_ask capped", {
1219
+ reason: capDecision.reason,
1220
+ call_count: askCallCount,
1221
+ max_questions: askMaxQuestions
1222
+ });
1223
+ return {
1224
+ content: [{
1225
+ type: "text",
1226
+ text: capDecision.message
1227
+ }],
1228
+ isError: true
1229
+ };
1230
+ }
1231
+ for (const q of args.questions) {
1232
+ if ((q.kind === "single" || q.kind === "multi") && (!q.options || q.options.length === 0)) return {
1233
+ content: [{
1234
+ type: "text",
1235
+ text: `Error: question "${q.id}" has kind="${q.kind}" but no options. Provide at least one { label, value } option, or change kind to "text".`
1236
+ }],
1237
+ isError: true
1238
+ };
1239
+ if (q.sensitive && q.kind !== "text") return {
1240
+ content: [{
1241
+ type: "text",
1242
+ text: `Error: question "${q.id}" sets sensitive=true but kind="${q.kind}". Only kind="text" answers can be vaulted as secrets.`
1243
+ }],
1244
+ isError: true
1245
+ };
1246
+ }
1247
+ const ids = /* @__PURE__ */ new Set();
1248
+ for (const q of args.questions) {
1249
+ if (ids.has(q.id)) return {
1250
+ content: [{
1251
+ type: "text",
1252
+ text: `Error: duplicate question id "${q.id}". Each question must have a unique id.`
1253
+ }],
1254
+ isError: true
1255
+ };
1256
+ ids.add(q.id);
1257
+ }
1258
+ askCallCount += 1;
1259
+ try {
1260
+ const answers = await askBridge.request({ questions: args.questions });
1261
+ if (isFullyCancelled(answers)) askCallCount -= 1;
1262
+ const sanitised = vaultSensitiveAnswers(args.questions, answers, secretVault);
1263
+ logToFile(`wizard_ask: resolved ${Object.keys(answers).length} answer(s) for ${args.questions.length} question(s)`);
1264
+ return { content: [{
1265
+ type: "text",
1266
+ text: JSON.stringify({ answers: sanitised }, null, 2)
1267
+ }] };
1268
+ } catch (err) {
1269
+ askCallCount -= 1;
1270
+ logToFile(`wizard_ask: error: ${err?.message ?? err}`);
1271
+ return {
1272
+ content: [{
1273
+ type: "text",
1274
+ text: `Error: wizard_ask failed: ${err?.message ?? String(err)}`
1275
+ }],
1276
+ isError: true
1277
+ };
1278
+ }
1279
+ }),
1280
+ ...orchestrator ? buildOrchestratorTools(tool, orchestrator) : []
1281
+ ]
1282
+ });
1283
+ }
1284
+ //#endregion
456
1285
  //#region src/lib/agent/triage-provider.ts
457
1286
  /**
458
1287
  * LLM provider for warlock security-scan triage.
@@ -1947,6 +2776,6 @@ function handleSDKMessage(message, options, spinner, signals, receivedSuccessRes
1947
2776
  }
1948
2777
  }
1949
2778
  //#endregion
1950
- export { wizardCanUseTool as a, classifySettingsConflicts as c, sanitizeAgentSubprocessEnv as d, AgentOutputSignals as f, createTriageLLMProvider as h, runAgent as i, recoverOrphanedSettingsBackups as l, REMARK_INSTRUCTION as m, buildRunTags as n, backupAndFixClaudeSettings as o, AgentSignals as p, initializeAgent as r, checkAllSettingsConflicts as s, buildAgentEnv as t, restoreClaudeSettings as u };
2779
+ export { isFullyCancelled as A, mergeEnvValues as C, vaultSensitiveAnswers as D, resolveEnvSecretRefs as E, fetchWithRetry as O, installSkillById as S, resolveEnvPath as T, ENV_FILE_PATH_DESCRIPTION as _, wizardCanUseTool as a, evaluateAskCap as b, classifySettingsConflicts as c, sanitizeAgentSubprocessEnv as d, AgentOutputSignals as f, createSecretVault as g, createTriageLLMProvider as h, runAgent as i, createWizardAskBridge as k, recoverOrphanedSettingsBackups as l, REMARK_INSTRUCTION as m, buildRunTags as n, backupAndFixClaudeSettings as o, AgentSignals as p, initializeAgent as r, checkAllSettingsConflicts as s, buildAgentEnv as t, restoreClaudeSettings as u, WIZARD_TOOL_NAMES as v, parseEnvKeys as w, fetchSkillMenu as x, downloadSkill as y };
1951
2780
 
1952
- //# sourceMappingURL=agent-interface-CF7GsdFf.js.map
2781
+ //# sourceMappingURL=agent-interface-RZY46R62.js.map