@posthog/wizard 2.36.0 → 2.38.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 (95) hide show
  1. package/README.md +6 -3
  2. package/dist/{add-mcp-server-to-clients-D5e3JbUC.js → add-mcp-server-to-clients-CeoyDLC-.js} +5 -5
  3. package/dist/{add-mcp-server-to-clients-D5e3JbUC.js.map → add-mcp-server-to-clients-CeoyDLC-.js.map} +1 -1
  4. package/dist/{agent-interface-CPn9pZ8j.js → agent-interface-BAodys0H.js} +66 -1055
  5. package/dist/agent-interface-BAodys0H.js.map +1 -0
  6. package/dist/{agent-runner-Cic1uZZ8.js → agent-runner-BMuJP8fb.js} +2574 -1715
  7. package/dist/agent-runner-BMuJP8fb.js.map +1 -0
  8. package/dist/{analytics-Be_wRgxD.js → analytics-CvIK_07d.js} +3 -2
  9. package/dist/{analytics-Be_wRgxD.js.map → analytics-CvIK_07d.js.map} +1 -1
  10. package/dist/{api-C_R_ybiA.js → api-CyA47qug.js} +3 -3
  11. package/dist/{api-C_R_ybiA.js.map → api-CyA47qug.js.map} +1 -1
  12. package/dist/bin.js +82 -54
  13. package/dist/bin.js.map +1 -1
  14. package/dist/{ci-install-B0BHTkGI.js → ci-install-C_o0Q7Fg.js} +4 -4
  15. package/dist/{ci-install-B0BHTkGI.js.map → ci-install-C_o0Q7Fg.js.map} +1 -1
  16. package/dist/{debug-DJS2KPGb.js → debug-BhGq9rn9.js} +1 -1
  17. package/dist/{debug-BpWrz3hX.js → debug-CgdI7Rd-.js} +55 -3
  18. package/dist/debug-CgdI7Rd-.js.map +1 -0
  19. package/dist/{defaults-UswXMiTx.js → defaults-b2Bo9Wid.js} +1 -1
  20. package/dist/{defaults-UswXMiTx.js.map → defaults-b2Bo9Wid.js.map} +1 -1
  21. package/dist/{env-api-key-Db-mNPK2.js → env-api-key-Bezg_uXQ.js} +1 -1
  22. package/dist/{env-api-key-Db-mNPK2.js.map → env-api-key-Bezg_uXQ.js.map} +1 -1
  23. package/dist/{environment-Dc6VTWyK.js → environment-v22_Genm.js} +3 -3
  24. package/dist/{environment-Dc6VTWyK.js.map → environment-v22_Genm.js.map} +1 -1
  25. package/dist/{file-utils-Buj9-umf.js → file-utils-Gvc2Ysa_.js} +2 -2
  26. package/dist/{file-utils-Buj9-umf.js.map → file-utils-Gvc2Ysa_.js.map} +1 -1
  27. package/dist/{headless-ui-BplqZRcO.js → headless-ui-BtBcuxOj.js} +2 -2
  28. package/dist/{headless-ui-BplqZRcO.js.map → headless-ui-BtBcuxOj.js.map} +1 -1
  29. package/dist/{host-resolution-BD6TcZG0.js → host-resolution-CUjt6efz.js} +3 -3
  30. package/dist/{host-resolution-BD6TcZG0.js.map → host-resolution-CUjt6efz.js.map} +1 -1
  31. package/dist/{interactive-BspTioP_.js → interactive-CRdgBMxk.js} +2 -2
  32. package/dist/{interactive-BspTioP_.js.map → interactive-CRdgBMxk.js.map} +1 -1
  33. package/dist/mcp-XWzcKTeK.js +108 -0
  34. package/dist/mcp-XWzcKTeK.js.map +1 -0
  35. package/dist/{mcp-prompt-streaming-BCNzyL12.js → mcp-prompt-streaming-DeC85Icy.js} +5 -5
  36. package/dist/mcp-prompt-streaming-DeC85Icy.js.map +1 -0
  37. package/dist/{non-interactive-CFikUAzO.js → non-interactive-B-Ku4EOx.js} +2 -2
  38. package/dist/{non-interactive-CFikUAzO.js.map → non-interactive-B-Ku4EOx.js.map} +1 -1
  39. package/dist/{package-manager-L7gsAbE0.js → package-manager-Dj6dTKlK.js} +2 -2
  40. package/dist/{package-manager-L7gsAbE0.js.map → package-manager-Dj6dTKlK.js.map} +1 -1
  41. package/dist/{playground-Bl2iPxdt.js → playground-CmKFAlCE.js} +783 -7
  42. package/dist/playground-CmKFAlCE.js.map +1 -0
  43. package/dist/{posthog-BrLFkaji.js → posthog-Dnl6wsYc.js} +1 -1
  44. package/dist/{posthog-BrLFkaji.js.map → posthog-Dnl6wsYc.js.map} +1 -1
  45. package/dist/{posthog-integration-B6pTHVcn.js → posthog-integration-CcJWXPDa.js} +18 -17
  46. package/dist/posthog-integration-CcJWXPDa.js.map +1 -0
  47. package/dist/{provisioning-C9ts56R5.js → provisioning-BIuAPRcH.js} +4 -4
  48. package/dist/{provisioning-C9ts56R5.js.map → provisioning-BIuAPRcH.js.map} +1 -1
  49. package/dist/{registry-DFazVUSE.js → registry-ByrVTIE9.js} +4 -4
  50. package/dist/{registry-DFazVUSE.js.map → registry-ByrVTIE9.js.map} +1 -1
  51. package/dist/security-uSP4HiC0.js +431 -0
  52. package/dist/security-uSP4HiC0.js.map +1 -0
  53. package/dist/{setup-utils-BxdW2R0r.js → setup-utils-DhmigSLO.js} +202 -22
  54. package/dist/setup-utils-DhmigSLO.js.map +1 -0
  55. package/dist/{start-tui-oGWfxdTd.js → start-tui-DKHnPg5a.js} +63 -38
  56. package/dist/start-tui-DKHnPg5a.js.map +1 -0
  57. package/dist/{steps-O0v5UU1G.js → steps-BBzIrOMY.js} +7 -7
  58. package/dist/{steps-O0v5UU1G.js.map → steps-BBzIrOMY.js.map} +1 -1
  59. package/dist/{store-Di9OvMql.js → store-BV1a-8Wp.js} +68 -6
  60. package/dist/store-BV1a-8Wp.js.map +1 -0
  61. package/dist/subagent-fP1QmYBX.js +108 -0
  62. package/dist/subagent-fP1QmYBX.js.map +1 -0
  63. package/dist/{task-stream-DkbGqxZv.js → task-stream-C6s7qUCE.js} +3 -3
  64. package/dist/{task-stream-DkbGqxZv.js.map → task-stream-C6s7qUCE.js.map} +1 -1
  65. package/dist/tasks-YFkqfEio.js +112 -0
  66. package/dist/tasks-YFkqfEio.js.map +1 -0
  67. package/dist/{telemetry-D5WQ4-Ic.js → telemetry-wOdXbZLp.js} +3 -3
  68. package/dist/{telemetry-D5WQ4-Ic.js.map → telemetry-wOdXbZLp.js.map} +1 -1
  69. package/dist/{terminal-Bd-k7pCi.js → terminal-IAHPQoAT.js} +143 -793
  70. package/dist/terminal-IAHPQoAT.js.map +1 -0
  71. package/dist/tools-DdzmiBKF.js +122 -0
  72. package/dist/tools-DdzmiBKF.js.map +1 -0
  73. package/dist/{urls-DpTCPWLD.js → urls-BR7jQgQp.js} +2 -2
  74. package/dist/{urls-DpTCPWLD.js.map → urls-BR7jQgQp.js.map} +1 -1
  75. package/dist/{wizard-abort-t41wO4Bh.js → wizard-abort-bulz-_og.js} +4 -4
  76. package/dist/{wizard-abort-t41wO4Bh.js.map → wizard-abort-bulz-_og.js.map} +1 -1
  77. package/dist/{wizard-abort-C1t9M-JN.js → wizard-abort-lbH6n2WG.js} +1 -1
  78. package/dist/{wizard-session-xzqycI7i.js → wizard-session-B1ZM4OBv.js} +1 -1
  79. package/dist/{wizard-session-CqihOtk3.js → wizard-session-DWeUEncV.js} +4 -1
  80. package/dist/wizard-session-DWeUEncV.js.map +1 -0
  81. package/dist/wizard-tools-CGrE1O_b.js +1107 -0
  82. package/dist/wizard-tools-CGrE1O_b.js.map +1 -0
  83. package/dist/wizard-ui-WZ48rUgr.js.map +1 -1
  84. package/package.json +7 -2
  85. package/dist/agent-interface-CPn9pZ8j.js.map +0 -1
  86. package/dist/agent-runner-Cic1uZZ8.js.map +0 -1
  87. package/dist/debug-BpWrz3hX.js.map +0 -1
  88. package/dist/mcp-prompt-streaming-BCNzyL12.js.map +0 -1
  89. package/dist/playground-Bl2iPxdt.js.map +0 -1
  90. package/dist/posthog-integration-B6pTHVcn.js.map +0 -1
  91. package/dist/setup-utils-BxdW2R0r.js.map +0 -1
  92. package/dist/start-tui-oGWfxdTd.js.map +0 -1
  93. package/dist/store-Di9OvMql.js.map +0 -1
  94. package/dist/terminal-Bd-k7pCi.js.map +0 -1
  95. package/dist/wizard-session-CqihOtk3.js.map +0 -1
@@ -0,0 +1,1107 @@
1
+ import { f as skillTmpPath, s as logToFile } from "./debug-CgdI7Rd-.js";
2
+ import { t as analytics } from "./analytics-CvIK_07d.js";
3
+ import * as fs$1 from "fs";
4
+ import fs from "fs";
5
+ import * as path$1 from "path";
6
+ import path from "path";
7
+ import { z } from "zod";
8
+ import { execFileSync } from "child_process";
9
+ import { randomUUID } from "crypto";
10
+ //#region src/utils/atomic-ledger.ts
11
+ /**
12
+ * Small shared primitives for on-disk ledgers: an atomic JSON writer and a
13
+ * single-chain async mutex. Used by the audit tools and by the orchestrator
14
+ * queue. Lifted here so both share one implementation.
15
+ */
16
+ /**
17
+ * Atomically write JSON: write to a `.tmp` file then rename over the target. The
18
+ * rename bumps the file's mtime in one step, which is what a file watcher polls.
19
+ */
20
+ function writeJsonAtomic(targetPath, data) {
21
+ const tmpPath = `${targetPath}.tmp`;
22
+ fs$1.writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf8");
23
+ fs$1.renameSync(tmpPath, targetPath);
24
+ }
25
+ /**
26
+ * A single async mutex. Serializes read-modify-write cycles so concurrent callers
27
+ * (parallel task agents, audit tool calls) never interleave a mutation.
28
+ */
29
+ function makeMutex() {
30
+ let chain = Promise.resolve();
31
+ return async function run(fn) {
32
+ const next = chain.then(() => fn());
33
+ chain = next.catch(() => void 0);
34
+ return next;
35
+ };
36
+ }
37
+ //#endregion
38
+ //#region src/lib/programs/audit/types.ts
39
+ /** Single source of truth for status glyph + color across audit views. */
40
+ const AUDIT_SEVERITY_STYLE = {
41
+ pending: {
42
+ glyph: "◌",
43
+ color: "gray"
44
+ },
45
+ pass: {
46
+ glyph: "✔",
47
+ color: "green"
48
+ },
49
+ error: {
50
+ glyph: "✘",
51
+ color: "red"
52
+ },
53
+ warning: {
54
+ glyph: "⚠",
55
+ color: "yellow"
56
+ },
57
+ suggestion: {
58
+ glyph: "•",
59
+ color: "cyan"
60
+ }
61
+ };
62
+ const AUDIT_CHECKS_FILE = ".posthog-audit-checks.json";
63
+ const AUDIT_REPORT_FILE = "posthog-audit-report.md";
64
+ const AUDIT_CHECKS_KEY = "auditChecks";
65
+ function getAuditChecks(session) {
66
+ const raw = session.frameworkContext[AUDIT_CHECKS_KEY];
67
+ return Array.isArray(raw) ? raw : [];
68
+ }
69
+ /**
70
+ * Read the audit checks ledger off disk. Validation lives at write time —
71
+ * every writer (`audit_seed_checks` / `audit_add_checks` / `audit_resolve_checks`
72
+ * MCP tools, `seedAuditLedger`) zod-parses entries before the atomic write,
73
+ * so by the time the file watcher fires we trust the shape and only guard
74
+ * against the file not being a JSON array (corrupted / hand-edited / not yet
75
+ * seeded).
76
+ */
77
+ function coerceAuditChecks(parsed) {
78
+ return Array.isArray(parsed) ? parsed : [];
79
+ }
80
+ //#endregion
81
+ //#region src/lib/wizard-ask-bridge.ts
82
+ /**
83
+ * WizardAskBridge — host-side promise broker for the `wizard_ask` MCP tool.
84
+ *
85
+ * The `wizard_ask` tool needs to (a) read information from the wizard
86
+ * session (the active skill id, used as the analytics `source`) and
87
+ * (b) drive the TUI overlay. Wiring `wizard-tools.ts` directly to either
88
+ * would couple our pure-data MCP server to the runtime UI layer.
89
+ *
90
+ * The bridge is the seam: `wizard-tools.ts` depends on this interface,
91
+ * and `agent-runner.ts` constructs an implementation that knows about
92
+ * both the session and `getUI()`.
93
+ */
94
+ /** Sentinel returned for unanswered fields on cancellation or timeout. */
95
+ const CANCELLED_SENTINEL = "__cancelled__";
96
+ function buildCancelledAnswers(questions) {
97
+ const out = {};
98
+ for (const q of questions) out[q.id] = CANCELLED_SENTINEL;
99
+ return out;
100
+ }
101
+ function isFullyCancelled(answers) {
102
+ const values = Object.values(answers);
103
+ if (values.length === 0) return false;
104
+ return values.every((v) => v === CANCELLED_SENTINEL);
105
+ }
106
+ function createWizardAskBridge(opts) {
107
+ const timeoutMs = opts.timeoutMs ?? 3e5;
108
+ return { async request({ questions }) {
109
+ const pending = {
110
+ id: randomUUID(),
111
+ questions,
112
+ source: opts.getSource(),
113
+ richLinks: opts.richLinks ?? false
114
+ };
115
+ const startedAt = Date.now();
116
+ let timer;
117
+ const timeoutPromise = new Promise((resolve) => {
118
+ timer = setTimeout(() => {
119
+ resolve(buildCancelledAnswers(questions));
120
+ }, timeoutMs);
121
+ });
122
+ try {
123
+ const answers = await Promise.race([opts.showQuestion(pending), timeoutPromise]);
124
+ const durationMs = Date.now() - startedAt;
125
+ if (isFullyCancelled(answers)) analytics.wizardCapture("wizard_ask cancelled", {
126
+ source: pending.source,
127
+ question_count: questions.length,
128
+ duration_ms: durationMs,
129
+ timed_out: durationMs >= timeoutMs
130
+ });
131
+ else analytics.wizardCapture("wizard_ask answered", {
132
+ source: pending.source,
133
+ question_count: questions.length,
134
+ duration_ms: durationMs
135
+ });
136
+ return answers;
137
+ } finally {
138
+ if (timer) clearTimeout(timer);
139
+ }
140
+ } };
141
+ }
142
+ //#endregion
143
+ //#region src/lib/secret-vault.ts
144
+ /**
145
+ * Session-scoped secret vault.
146
+ *
147
+ * Tools that handle sensitive values (personal API keys pasted by the user
148
+ * or minted by the wizard, OAuth tokens, etc.) store them here and return
149
+ * an opaque `secret:<uuid>` ref to the agent. The agent passes the ref
150
+ * around to subsequent tools — `set_env_values` resolves it host-side
151
+ * before writing — but the raw value never enters the LLM conversation.
152
+ *
153
+ * The vault is created once per `createWizardToolsServer` call and lives
154
+ * for the duration of a single wizard run. There is no persistence and
155
+ * no cross-session sharing; refs minted in one run cannot be resolved in
156
+ * another.
157
+ */
158
+ const REF_PREFIX = "secret:";
159
+ function createSecretVault() {
160
+ const store = /* @__PURE__ */ new Map();
161
+ return {
162
+ put(value, meta) {
163
+ const ref = `${REF_PREFIX}${randomUUID()}`;
164
+ store.set(ref, {
165
+ value,
166
+ meta: {
167
+ ref,
168
+ label: meta.label,
169
+ source: meta.source,
170
+ createdAt: Date.now()
171
+ }
172
+ });
173
+ return ref;
174
+ },
175
+ get(ref) {
176
+ return store.get(ref)?.value;
177
+ },
178
+ has(ref) {
179
+ return store.has(ref);
180
+ },
181
+ list() {
182
+ return [...store.values()].map((s) => s.meta);
183
+ },
184
+ clear() {
185
+ store.clear();
186
+ }
187
+ };
188
+ }
189
+ //#endregion
190
+ //#region src/lib/agent/runner/sequence/orchestrator/queue.ts
191
+ /**
192
+ * The orchestrator task queue.
193
+ *
194
+ * In memory, synchronous, single-owner: one Node process drives the run, so
195
+ * there is no locking. The queue imposes no execution policy — `nextRunnable`
196
+ * returns every pending task whose dependencies are satisfied, and how many of
197
+ * those run at once is decided by the task graph, not the queue.
198
+ *
199
+ * Every transition rewrites `<installDir>/.posthog-wizard-cache/queue.json`, a
200
+ * small file holding the whole queue, handoffs included. It is the run's log
201
+ * and the report's source. The whole cache folder is run-scoped and wiped when
202
+ * the run ends.
203
+ */
204
+ const TaskStatus = {
205
+ Pending: "pending",
206
+ Running: "running",
207
+ Done: "done",
208
+ Skipped: "skipped",
209
+ Failed: "failed"
210
+ };
211
+ const QUEUE_DIR_NAME = ".posthog-wizard-cache";
212
+ const DEFAULT_MAX_ATTEMPTS = 2;
213
+ function nowIso() {
214
+ return (/* @__PURE__ */ new Date()).toISOString();
215
+ }
216
+ /** Dropped in the cache folder so an orphaned copy explains itself. */
217
+ const DELETE_ME_FILE = ".DELETE-ME.md";
218
+ const DELETE_ME_BODY = `# Safe to delete
219
+
220
+ This folder contains run artifacts from the PostHog Wizard. This should have
221
+ been deleted if the Wizard has finished running. If this wasn't deleted for
222
+ some reason, you can safely delete the entire \`${QUEUE_DIR_NAME}/\` folder.
223
+ `;
224
+ var QueueStore = class {
225
+ tasks = [];
226
+ onTransition;
227
+ runId;
228
+ queuePath;
229
+ constructor(installDir, runId, opts) {
230
+ this.onTransition = opts?.onTransition;
231
+ this.runId = runId;
232
+ const dir = path$1.join(installDir, QUEUE_DIR_NAME);
233
+ this.queuePath = path$1.join(dir, "queue.json");
234
+ fs$1.mkdirSync(dir, { recursive: true });
235
+ fs$1.writeFileSync(path$1.join(dir, DELETE_ME_FILE), DELETE_ME_BODY);
236
+ }
237
+ list() {
238
+ return this.tasks;
239
+ }
240
+ get(id) {
241
+ return this.tasks.find((t) => t.id === id);
242
+ }
243
+ /**
244
+ * Every pending task whose dependencies are all satisfied (`done` or
245
+ * `skipped`). A skipped dependency does not block downstream work.
246
+ */
247
+ nextRunnable() {
248
+ const doneIds = new Set(this.tasks.filter((t) => t.status === TaskStatus.Done || t.status === TaskStatus.Skipped).map((t) => t.id));
249
+ return this.tasks.filter((t) => t.status === TaskStatus.Pending && t.dependsOn.every((d) => doneIds.has(d)));
250
+ }
251
+ /**
252
+ * True when no task is running and none can be started. Either everything
253
+ * is terminal, or the only pending tasks are blocked by a failed dependency.
254
+ */
255
+ isDrained() {
256
+ if (this.tasks.some((t) => t.status === TaskStatus.Running)) return false;
257
+ return this.nextRunnable().length === 0;
258
+ }
259
+ summary() {
260
+ const counts = {
261
+ [TaskStatus.Pending]: 0,
262
+ [TaskStatus.Running]: 0,
263
+ [TaskStatus.Done]: 0,
264
+ [TaskStatus.Skipped]: 0,
265
+ [TaskStatus.Failed]: 0
266
+ };
267
+ for (const t of this.tasks) counts[t.status] += 1;
268
+ return {
269
+ ...counts,
270
+ total: this.tasks.length
271
+ };
272
+ }
273
+ readHandoff(id) {
274
+ return this.get(id)?.handoff ?? null;
275
+ }
276
+ /** Handoffs of completed tasks of a given type, oldest first. */
277
+ readHandoffsByType(type) {
278
+ return this.tasks.filter((t) => t.type === type && t.handoff).map((t) => t.handoff);
279
+ }
280
+ enqueue(input) {
281
+ const task = {
282
+ id: randomUUID(),
283
+ type: input.type,
284
+ label: input.label,
285
+ status: TaskStatus.Pending,
286
+ dependsOn: input.dependsOn ?? [],
287
+ inputs: input.inputs ?? {},
288
+ model: input.model,
289
+ attempts: 0,
290
+ maxAttempts: input.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
291
+ enqueuedBy: input.enqueuedBy ?? "orchestrator",
292
+ createdAt: nowIso()
293
+ };
294
+ this.tasks.push(task);
295
+ this.reflect();
296
+ this.notify("enqueue", task);
297
+ return task;
298
+ }
299
+ start(id) {
300
+ const t = this.require(id);
301
+ t.status = TaskStatus.Running;
302
+ t.startedAt = nowIso();
303
+ t.attempts += 1;
304
+ this.reflect();
305
+ this.notify("start", t);
306
+ return t;
307
+ }
308
+ complete(id, handoff) {
309
+ return this.finish(id, TaskStatus.Done, handoff);
310
+ }
311
+ /** Terminal: the agent could not do the task. Not done, not failed. */
312
+ skip(id, handoff) {
313
+ return this.finish(id, TaskStatus.Skipped, handoff);
314
+ }
315
+ fail(id, error, handoff) {
316
+ const t = this.require(id);
317
+ t.error = error;
318
+ return this.finish(id, TaskStatus.Failed, handoff);
319
+ }
320
+ /** Put a failed/running task back to pending for a retry within the run. */
321
+ requeue(id) {
322
+ const t = this.require(id);
323
+ t.status = TaskStatus.Pending;
324
+ t.startedAt = void 0;
325
+ t.finishedAt = void 0;
326
+ this.reflect();
327
+ this.notify("requeue", t);
328
+ return t;
329
+ }
330
+ finish(id, status, handoff) {
331
+ const t = this.require(id);
332
+ if (handoff) t.handoff = handoff;
333
+ t.status = status;
334
+ t.finishedAt = nowIso();
335
+ this.reflect();
336
+ this.notify(status === TaskStatus.Done ? "complete" : status === TaskStatus.Skipped ? "skip" : "fail", t);
337
+ return t;
338
+ }
339
+ reflect() {
340
+ const file = {
341
+ version: 1,
342
+ runId: this.runId,
343
+ tasks: this.tasks
344
+ };
345
+ writeJsonAtomic(this.queuePath, file);
346
+ }
347
+ notify(event, task) {
348
+ try {
349
+ this.onTransition?.(event, task);
350
+ } catch (error) {
351
+ analytics.captureException(error instanceof Error ? error : new Error(String(error)), {
352
+ step: "orchestrator_queue_listener",
353
+ event
354
+ });
355
+ }
356
+ }
357
+ require(id) {
358
+ const t = this.get(id);
359
+ if (!t) throw new Error(`No task ${id} in the queue`);
360
+ return t;
361
+ }
362
+ };
363
+ //#endregion
364
+ //#region src/lib/agent/runner/sequence/orchestrator/queue-tools.ts
365
+ /**
366
+ * Orchestrator MCP tools, registered into the existing `wizard-tools` server when
367
+ * a queue is present. They let the orchestrator agent and task agents grow the
368
+ * queue, report completion with a structured handoff, and read prior handoffs.
369
+ *
370
+ * The guard logic and the apply functions are plain, exported, and unit-tested.
371
+ * `buildOrchestratorTools` wraps them in the SDK `tool()` shape.
372
+ */
373
+ function stableStringify(value) {
374
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
375
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
376
+ return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
377
+ }
378
+ function dedupKey(type, inputs) {
379
+ return `${type}::${stableStringify(inputs)}`;
380
+ }
381
+ /**
382
+ * A backstop on total queue size. Tasks can enqueue tasks, so a misbehaving
383
+ * type could grow the queue without bound. Keeping the graph small is the job
384
+ * of good agent and skill design, not this number — it only stops a runaway.
385
+ * The real flow is ~9 tasks, so this sits well clear of it.
386
+ */
387
+ const MAX_QUEUE_TASKS = 30;
388
+ /**
389
+ * Validate an enqueue. Structural checks only — a real type, real dependencies,
390
+ * not a literal duplicate, and not past the runaway backstop. How much runs,
391
+ * and in what shape, is the task graph's business, not a knob's.
392
+ */
393
+ function checkEnqueueGuards(ctx, args) {
394
+ const tasks = ctx.store.list();
395
+ if (tasks.length >= MAX_QUEUE_TASKS) return {
396
+ ok: false,
397
+ guard: "queue-full",
398
+ message: `The queue already holds ${tasks.length} tasks (cap ${MAX_QUEUE_TASKS}). Refine the existing tasks rather than adding more.`
399
+ };
400
+ if (!ctx.validTypes.includes(args.type)) return {
401
+ ok: false,
402
+ guard: "unknown-type",
403
+ message: `Unknown task type "${args.type}". Valid types: ${ctx.validTypes.join(", ")}.`
404
+ };
405
+ for (const dep of args.dependsOn ?? []) if (!ctx.store.get(dep)) return {
406
+ ok: false,
407
+ guard: "unknown-dep",
408
+ message: `Dependency "${dep}" is not a known task id.`
409
+ };
410
+ const key = dedupKey(args.type, args.inputs ?? {});
411
+ if (tasks.some((t) => t.status !== TaskStatus.Failed && dedupKey(t.type, t.inputs) === key)) return {
412
+ ok: false,
413
+ guard: "dedup",
414
+ message: `A "${args.type}" task with these inputs already exists.`
415
+ };
416
+ return { ok: true };
417
+ }
418
+ function applyEnqueue(ctx, args) {
419
+ const guard = checkEnqueueGuards(ctx, args);
420
+ if (!guard.ok) return guard;
421
+ return {
422
+ ok: true,
423
+ task: ctx.store.enqueue({
424
+ type: args.type,
425
+ label: args.label,
426
+ inputs: args.inputs ?? {},
427
+ dependsOn: args.dependsOn ?? [],
428
+ model: args.model,
429
+ enqueuedBy: ctx.currentTaskId ?? "orchestrator"
430
+ })
431
+ };
432
+ }
433
+ function applyComplete(ctx, args) {
434
+ const id = ctx.currentTaskId;
435
+ if (!id) return {
436
+ ok: false,
437
+ message: "complete_task can only be called by a running task agent."
438
+ };
439
+ if (args.status === TaskStatus.Failed) ctx.store.fail(id, {
440
+ type: "self-reported",
441
+ message: args.handoff.forNextAgent
442
+ }, args.handoff);
443
+ else if (args.status === TaskStatus.Skipped) ctx.store.skip(id, args.handoff);
444
+ else ctx.store.complete(id, args.handoff);
445
+ return { ok: true };
446
+ }
447
+ function applyReadHandoffs(ctx, args) {
448
+ if (args.taskId) {
449
+ const h = ctx.store.readHandoff(args.taskId);
450
+ return h ? [h] : [];
451
+ }
452
+ if (args.type) return ctx.store.readHandoffsByType(args.type);
453
+ const currentId = ctx.currentTaskId;
454
+ const current = currentId ? ctx.store.get(currentId) : void 0;
455
+ if (!current) return [];
456
+ return current.dependsOn.map((depId) => ctx.store.readHandoff(depId)).filter((h) => h !== null);
457
+ }
458
+ const HANDOFF_SHAPE = {
459
+ goals: z.string().describe("What this task was asked to achieve."),
460
+ did: z.string().describe("What you actually did."),
461
+ forNextAgent: z.string().describe("What the next agent should know."),
462
+ filesTouched: z.array(z.string()).optional(),
463
+ conflict: z.string().optional().describe("A one-line summary of any conflict you could not cleanly resolve (e.g. a dependency or build conflict). Put full detail in your work; this line is surfaced to the user.")
464
+ };
465
+ function textResult(text, isError = false) {
466
+ return {
467
+ isError,
468
+ content: [{
469
+ type: "text",
470
+ text
471
+ }]
472
+ };
473
+ }
474
+ /**
475
+ * Build the orchestrator tools in the SDK `tool()` shape. Called from
476
+ * createWizardToolsServer only when a queue context is present.
477
+ */
478
+ function buildOrchestratorTools(tool, ctx) {
479
+ return [
480
+ tool("enqueue_task", "Add a task to the orchestrator queue. Use it to seed work and to enqueue follow-up work you discover. Keep tasks small and discrete.", {
481
+ type: z.string().describe(`The task type. One of: ${ctx.validTypes.join(", ")}.`),
482
+ label: z.string().optional().describe("A short label for the UI — the action in a few words (e.g. \"Add the PostHog SDK\", \"Initialize PostHog\"). Leave out file names, class names, and other specifics."),
483
+ inputs: z.record(z.unknown()).optional(),
484
+ dependsOn: z.array(z.string()).optional().describe("Task ids that must be done before this task runs."),
485
+ model: z.string().optional(),
486
+ reason: z.string().describe("One line on why this task is needed.")
487
+ }, ((args) => {
488
+ const res = applyEnqueue(ctx, args);
489
+ if (!res.ok) {
490
+ analytics.wizardCapture("orchestrator guard tripped", {
491
+ guard: res.guard,
492
+ type: args.type
493
+ });
494
+ return textResult(res.message, true);
495
+ }
496
+ return textResult(JSON.stringify({ id: res.task.id }));
497
+ })),
498
+ tool("complete_task", "Report the outcome of your task. Always call this exactly once when you finish, with a structured handoff for the next agent. Use status 'skipped' when the task does not apply to this project and you cannot do it (say why in the handoff) — not 'done'.", {
499
+ status: z.enum([
500
+ "done",
501
+ "failed",
502
+ "skipped"
503
+ ]),
504
+ handoff: z.object(HANDOFF_SHAPE)
505
+ }, ((args) => {
506
+ const res = applyComplete(ctx, args);
507
+ if (!res.ok) return textResult(res.message, true);
508
+ return textResult("ok");
509
+ })),
510
+ tool("read_handoffs", "Read structured handoffs from earlier tasks. With no argument, returns the handoffs of your dependencies.", {
511
+ type: z.string().optional(),
512
+ taskId: z.string().optional()
513
+ }, ((args) => {
514
+ const handoffs = applyReadHandoffs(ctx, args);
515
+ return textResult(JSON.stringify(handoffs, null, 2));
516
+ }))
517
+ ];
518
+ }
519
+ //#endregion
520
+ //#region src/lib/wizard-tools.ts
521
+ /**
522
+ * Unified in-process MCP server for the PostHog wizard.
523
+ *
524
+ * Provides tools that run locally (secret values never leave the machine):
525
+ * - check_env_keys: Check which env var keys exist in a .env file
526
+ * - set_env_values: Create/update env vars in a .env file
527
+ * - detect_package_manager: Detect the project's package manager(s)
528
+ * - load_skill_menu / install_skill: Skill installation
529
+ * - audit_seed_checks / audit_add_checks / audit_resolve_checks: Audit ledger ownership
530
+ */
531
+ let _sdkModule = null;
532
+ async function getSDKModule() {
533
+ if (!_sdkModule) _sdkModule = await import("@anthropic-ai/claude-agent-sdk");
534
+ return _sdkModule;
535
+ }
536
+ /**
537
+ * Fetch the skill menu from the skills server.
538
+ * Returns parsed data on success, `null` on failure.
539
+ */
540
+ async function fetchSkillMenu(skillsBaseUrl) {
541
+ try {
542
+ const menuUrl = `${skillsBaseUrl}/skill-menu.json`;
543
+ logToFile(`fetchSkillMenu: fetching from ${menuUrl}`);
544
+ const resp = await fetch(menuUrl);
545
+ if (resp.ok) {
546
+ const data = await resp.json();
547
+ logToFile(`fetchSkillMenu: loaded (${Object.keys(data.categories).length} categories)`);
548
+ return data;
549
+ }
550
+ logToFile(`fetchSkillMenu: failed with HTTP ${resp.status}`);
551
+ return null;
552
+ } catch (err) {
553
+ logToFile(`fetchSkillMenu: error: ${err.message}`);
554
+ return null;
555
+ }
556
+ }
557
+ /**
558
+ * Download and extract a skill.
559
+ * By default installs to `<installDir>/.claude/skills/<id>/`.
560
+ * Pass `skillsRoot` to override the base directory (e.g. `.posthog/skills`).
561
+ */
562
+ function downloadSkill(skillEntry, installDir, skillsRoot) {
563
+ const skillDir = skillsRoot ? path.join(installDir, skillsRoot, skillEntry.id) : path.join(installDir, ".claude", "skills", skillEntry.id);
564
+ const tmpFile = skillTmpPath(skillEntry.id);
565
+ try {
566
+ fs.mkdirSync(skillDir, { recursive: true });
567
+ execFileSync("curl", [
568
+ "-sL",
569
+ skillEntry.downloadUrl,
570
+ "-o",
571
+ tmpFile
572
+ ], { timeout: 3e4 });
573
+ execFileSync("unzip", [
574
+ "-o",
575
+ tmpFile,
576
+ "-d",
577
+ skillDir
578
+ ], { timeout: 3e4 });
579
+ fs.writeFileSync(path.join(skillDir, ".posthog-wizard"), "");
580
+ try {
581
+ fs.unlinkSync(tmpFile);
582
+ } catch {}
583
+ logToFile(`downloadSkill: installed ${skillEntry.id} from ${skillEntry.downloadUrl}`);
584
+ return { success: true };
585
+ } catch (err) {
586
+ logToFile(`downloadSkill: error: ${err.message}`);
587
+ return {
588
+ success: false,
589
+ error: err.message
590
+ };
591
+ }
592
+ }
593
+ /**
594
+ * High-level "install a skill by ID" helper. Fetches the skill menu,
595
+ * finds the skill, downloads and extracts it. Programs should use this
596
+ * instead of composing fetchSkillMenu + downloadSkill themselves.
597
+ */
598
+ async function installSkillById(skillId, installDir, skillsBaseUrl, skillsRoot) {
599
+ const menu = await fetchSkillMenu(skillsBaseUrl);
600
+ if (!menu) return { kind: "menu-fetch-failed" };
601
+ const skill = Object.values(menu.categories).flat().find((s) => s.id === skillId);
602
+ if (!skill) return {
603
+ kind: "skill-not-found",
604
+ skillId
605
+ };
606
+ const result = downloadSkill(skill, installDir, skillsRoot);
607
+ if (!result.success) return {
608
+ kind: "download-failed",
609
+ message: result.error ?? "unknown"
610
+ };
611
+ return {
612
+ kind: "ok",
613
+ path: skillsRoot ? `${skillsRoot}/${skillId}` : `.claude/skills/${skillId}`
614
+ };
615
+ }
616
+ /**
617
+ * Pure decision function for the wizard_ask caps. Returns whether the
618
+ * upcoming call should proceed and, if not, the error message to surface
619
+ * to the agent. Extracted so the policy can be unit-tested without
620
+ * spinning up an MCP server.
621
+ *
622
+ * The adjacency nudge fires exactly once per run (the caller records it
623
+ * via `adjacencyNudged`) — flows that legitimately need several
624
+ * sequential, answer-dependent asks then proceed up to `maxQuestions`.
625
+ * Without the flag the rejected call would never advance the counter and
626
+ * every later call would be rejected, making caps above the threshold
627
+ * unreachable.
628
+ */
629
+ function evaluateAskCap(callCount, maxQuestions, adjacencyNudged = false) {
630
+ if (callCount >= maxQuestions) return {
631
+ kind: "capped",
632
+ reason: "max_questions",
633
+ 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.`
634
+ };
635
+ if (!adjacencyNudged && callCount >= 3) return {
636
+ kind: "capped",
637
+ reason: "adjacency",
638
+ 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.`
639
+ };
640
+ return { kind: "ok" };
641
+ }
642
+ /**
643
+ * Resolve filePath relative to workingDirectory, rejecting path traversal.
644
+ */
645
+ function resolveEnvPath(workingDirectory, filePath) {
646
+ const resolved = path.resolve(workingDirectory, filePath);
647
+ if (!resolved.startsWith(workingDirectory + path.sep) && resolved !== workingDirectory) throw new Error(`Path traversal rejected: "${filePath}" resolves outside working directory`);
648
+ return resolved;
649
+ }
650
+ /**
651
+ * Ensure the given env file basename is covered by .gitignore in the working directory.
652
+ * Creates .gitignore if it doesn't exist; appends the entry if missing.
653
+ */
654
+ function ensureGitignoreCoverage(workingDirectory, envFileName) {
655
+ const gitignorePath = path.join(workingDirectory, ".gitignore");
656
+ if (fs.existsSync(gitignorePath)) {
657
+ const content = fs.readFileSync(gitignorePath, "utf8");
658
+ if (content.split("\n").some((line) => line.trim() === envFileName)) return;
659
+ const newContent = content.endsWith("\n") ? `${content}${envFileName}\n` : `${content}\n${envFileName}\n`;
660
+ fs.writeFileSync(gitignorePath, newContent, "utf8");
661
+ } else fs.writeFileSync(gitignorePath, `${envFileName}\n`, "utf8");
662
+ }
663
+ /**
664
+ * Parse a .env file's content and return the set of defined key names.
665
+ */
666
+ function parseEnvKeys(content) {
667
+ const keys = /* @__PURE__ */ new Set();
668
+ for (const line of content.split("\n")) {
669
+ const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
670
+ if (match) keys.add(match[1]);
671
+ }
672
+ return keys;
673
+ }
674
+ /**
675
+ * Merge key-value pairs into existing .env content.
676
+ * Updates existing keys in-place, appends new keys at the end.
677
+ */
678
+ function mergeEnvValues(content, values) {
679
+ let result = content;
680
+ const updatedKeys = /* @__PURE__ */ new Set();
681
+ for (const [key, value] of Object.entries(values)) {
682
+ const regex = new RegExp(`^(\\s*${key}\\s*=).*$`, "m");
683
+ if (regex.test(result)) {
684
+ result = result.replace(regex, `$1${value}`);
685
+ updatedKeys.add(key);
686
+ }
687
+ }
688
+ const newKeys = Object.entries(values).filter(([key]) => !updatedKeys.has(key));
689
+ if (newKeys.length > 0) {
690
+ if (result.length > 0 && !result.endsWith("\n")) result += "\n";
691
+ for (const [key, value] of newKeys) result += `${key}=${value}\n`;
692
+ }
693
+ return result;
694
+ }
695
+ const AUDIT_STATUSES = [
696
+ "pending",
697
+ "pass",
698
+ "error",
699
+ "warning",
700
+ "suggestion"
701
+ ];
702
+ const auditCheckSchema = z.object({
703
+ id: z.string().min(1),
704
+ area: z.string().min(1),
705
+ label: z.string().min(1),
706
+ status: z.enum(AUDIT_STATUSES),
707
+ file: z.string().optional(),
708
+ details: z.string().optional()
709
+ });
710
+ const auditUpdateSchema = z.object({
711
+ id: z.string().min(1),
712
+ status: z.enum(AUDIT_STATUSES),
713
+ file: z.string().optional(),
714
+ details: z.string().optional()
715
+ });
716
+ /** Atomically write the audit ledger. Thin typed wrapper over writeJsonAtomic. */
717
+ function writeLedgerAtomic(targetPath, checks) {
718
+ writeJsonAtomic(targetPath, checks);
719
+ }
720
+ /**
721
+ * Apply a batch of patches to the ledger by id. Returns the new array and the
722
+ * list of update ids that didn't match any existing check.
723
+ */
724
+ function applyAuditUpdates(current, updates) {
725
+ const byId = new Map(current.map((c) => [c.id, c]));
726
+ const unknown = [];
727
+ for (const u of updates) {
728
+ const existing = byId.get(u.id);
729
+ if (!existing) {
730
+ unknown.push(u.id);
731
+ continue;
732
+ }
733
+ byId.set(u.id, {
734
+ ...existing,
735
+ status: u.status,
736
+ ...u.file !== void 0 ? { file: u.file } : {},
737
+ ...u.details !== void 0 ? { details: u.details } : {}
738
+ });
739
+ }
740
+ return {
741
+ next: current.map((c) => byId.get(c.id) ?? c),
742
+ unknown
743
+ };
744
+ }
745
+ /**
746
+ * Append new checks to a seeded ledger. Duplicate ids are reported without
747
+ * mutating the current ledger, including duplicates inside the additions.
748
+ */
749
+ function applyAuditAdditions(current, additions) {
750
+ const existingIds = new Set(current.map((c) => c.id));
751
+ const additionIds = /* @__PURE__ */ new Set();
752
+ const duplicates = [];
753
+ for (const check of additions) {
754
+ if (existingIds.has(check.id) || additionIds.has(check.id)) {
755
+ duplicates.push(check.id);
756
+ continue;
757
+ }
758
+ additionIds.add(check.id);
759
+ }
760
+ if (duplicates.length > 0) return {
761
+ next: current,
762
+ duplicates
763
+ };
764
+ return {
765
+ next: [...current, ...additions],
766
+ duplicates: []
767
+ };
768
+ }
769
+ function readLedger(targetPath) {
770
+ if (!fs.existsSync(targetPath)) return [];
771
+ try {
772
+ return coerceAuditChecks(JSON.parse(fs.readFileSync(targetPath, "utf8")));
773
+ } catch {
774
+ return [];
775
+ }
776
+ }
777
+ function appendAuditChecksToLedger(targetPath, additions) {
778
+ if (!fs.existsSync(targetPath)) return {
779
+ ok: false,
780
+ reason: "missing-ledger"
781
+ };
782
+ const { next, duplicates } = applyAuditAdditions(readLedger(targetPath), additions);
783
+ if (duplicates.length > 0) return {
784
+ ok: false,
785
+ reason: "duplicate-ids",
786
+ ids: duplicates
787
+ };
788
+ writeLedgerAtomic(targetPath, next);
789
+ return {
790
+ ok: true,
791
+ added: additions.length
792
+ };
793
+ }
794
+ const SERVER_NAME = "wizard-tools";
795
+ /**
796
+ * Create the unified in-process MCP server with all wizard tools.
797
+ * Must be called asynchronously because the SDK is an ESM module loaded via dynamic import.
798
+ */
799
+ async function createWizardToolsServer(options) {
800
+ const { workingDirectory, detectPackageManager, skillsBaseUrl, askBridge, askMaxQuestions = 10, secretVault = createSecretVault(), orchestrator } = options;
801
+ const { tool, createSdkMcpServer } = await getSDKModule();
802
+ let askCallCount = 0;
803
+ let askAdjacencyNudged = false;
804
+ let cachedSkillMenu = {};
805
+ let categoryNames = ["integration"];
806
+ const menu = await fetchSkillMenu(skillsBaseUrl);
807
+ if (menu) cachedSkillMenu = menu.categories;
808
+ const keys = Object.keys(cachedSkillMenu);
809
+ if (keys.length > 0) categoryNames = keys;
810
+ const checkEnvKeys = tool("check_env_keys", "Check which environment variable keys are present or missing in a .env file. Never reveals values.", {
811
+ filePath: z.string().describe("Path to the .env file, relative to the project root"),
812
+ keys: z.array(z.string()).describe("Environment variable key names to check")
813
+ }, (args) => {
814
+ const resolved = resolveEnvPath(workingDirectory, args.filePath);
815
+ logToFile(`check_env_keys: ${resolved}, keys: ${args.keys.join(", ")}`);
816
+ const existingKeys = fs.existsSync(resolved) ? parseEnvKeys(fs.readFileSync(resolved, "utf8")) : /* @__PURE__ */ new Set();
817
+ const results = {};
818
+ for (const key of args.keys) results[key] = existingKeys.has(key) ? "present" : "missing";
819
+ return { content: [{
820
+ type: "text",
821
+ text: JSON.stringify(results, null, 2)
822
+ }] };
823
+ });
824
+ 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.", {
825
+ filePath: z.string().describe("Path to the .env file, relative to the project root"),
826
+ 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)")
827
+ }, (args) => {
828
+ const forbidden = Object.keys(args.values).find((k) => k.toUpperCase() === "POSTHOG_KEY");
829
+ if (forbidden) return {
830
+ content: [{
831
+ type: "text",
832
+ 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).`
833
+ }],
834
+ isError: true
835
+ };
836
+ const resolvedValues = {};
837
+ const resolvedRefKeys = [];
838
+ for (const [key, val] of Object.entries(args.values)) if (typeof val === "string") resolvedValues[key] = val;
839
+ else {
840
+ const secret = secretVault.get(val.secretRef);
841
+ if (secret === void 0) return {
842
+ content: [{
843
+ type: "text",
844
+ text: `Error: secret reference "${val.secretRef}" for key "${key}" is not known to the vault. The ref may have expired, been minted in a different run, or been mistyped.`
845
+ }],
846
+ isError: true
847
+ };
848
+ resolvedValues[key] = secret;
849
+ resolvedRefKeys.push(key);
850
+ }
851
+ const resolved = resolveEnvPath(workingDirectory, args.filePath);
852
+ logToFile(`set_env_values: ${resolved}, keys: ${Object.keys(resolvedValues).join(", ")}${resolvedRefKeys.length > 0 ? ` (secret refs: ${resolvedRefKeys.join(", ")})` : ""}`);
853
+ const content = mergeEnvValues(fs.existsSync(resolved) ? fs.readFileSync(resolved, "utf8") : "", resolvedValues);
854
+ const dir = path.dirname(resolved);
855
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
856
+ fs.writeFileSync(resolved, content, "utf8");
857
+ ensureGitignoreCoverage(workingDirectory, path.basename(resolved));
858
+ return { content: [{
859
+ type: "text",
860
+ text: `Updated ${Object.keys(args.values).length} key(s) in ${args.filePath}`
861
+ }] };
862
+ });
863
+ 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 () => {
864
+ logToFile(`detect_package_manager: scanning ${workingDirectory}`);
865
+ const result = await detectPackageManager(workingDirectory);
866
+ logToFile(`detect_package_manager: detected ${result.detected.length} package manager(s)`);
867
+ return { content: [{
868
+ type: "text",
869
+ text: JSON.stringify(result, null, 2)
870
+ }] };
871
+ });
872
+ 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) => {
873
+ const skills = cachedSkillMenu[args.category];
874
+ if (!skills || skills.length === 0) return {
875
+ content: [{
876
+ type: "text",
877
+ text: `No skills found for category "${args.category}".`
878
+ }],
879
+ isError: true
880
+ };
881
+ const menuText = skills.map((s) => `- ${s.id}: ${s.name}`).join("\n");
882
+ logToFile(`load_skill_menu: returning ${skills.length} skills for "${args.category}"`);
883
+ return { content: [{
884
+ type: "text",
885
+ text: menuText
886
+ }] };
887
+ });
888
+ 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\")") }, (args) => {
889
+ if (!/^[a-z0-9][a-z0-9_-]*$/.test(args.skillId)) return {
890
+ content: [{
891
+ type: "text",
892
+ text: "Error: skillId must be lowercase alphanumeric with hyphens."
893
+ }],
894
+ isError: true
895
+ };
896
+ const skill = Object.values(cachedSkillMenu).flat().find((s) => s.id === args.skillId);
897
+ if (!skill) return {
898
+ content: [{
899
+ type: "text",
900
+ text: `Error: skill "${args.skillId}" not found. Use load_skill_menu to see available skills.`
901
+ }],
902
+ isError: true
903
+ };
904
+ const result = downloadSkill(skill, workingDirectory);
905
+ if (result.success) return { content: [{
906
+ type: "text",
907
+ text: `Skill installed to .claude/skills/${args.skillId}/`
908
+ }] };
909
+ else return {
910
+ content: [{
911
+ type: "text",
912
+ text: `Error installing skill: ${result.error}`
913
+ }],
914
+ isError: true
915
+ };
916
+ });
917
+ const auditLedgerPath = path.join(workingDirectory, AUDIT_CHECKS_FILE);
918
+ const auditMutex = makeMutex();
919
+ 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) => {
920
+ return auditMutex(() => {
921
+ writeLedgerAtomic(auditLedgerPath, args.checks);
922
+ logToFile(`audit_seed_checks: wrote ${args.checks.length} entries`);
923
+ return { content: [{
924
+ type: "text",
925
+ text: `Seeded ${args.checks.length} audit checks.`
926
+ }] };
927
+ });
928
+ });
929
+ 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) => {
930
+ return auditMutex(() => {
931
+ const result = appendAuditChecksToLedger(auditLedgerPath, args.checks);
932
+ if (!result.ok) {
933
+ if (result.reason === "missing-ledger") return {
934
+ content: [{
935
+ type: "text",
936
+ text: "Error: audit ledger does not exist. Run audit_seed_checks first."
937
+ }],
938
+ isError: true
939
+ };
940
+ return {
941
+ content: [{
942
+ type: "text",
943
+ text: `Error: duplicate check id(s): ${result.ids.join(", ")}. Check ids must be unique.`
944
+ }],
945
+ isError: true
946
+ };
947
+ }
948
+ logToFile(`audit_add_checks: added ${result.added} entries`);
949
+ return { content: [{
950
+ type: "text",
951
+ text: `Added ${result.added} audit check(s).`
952
+ }] };
953
+ });
954
+ });
955
+ 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) => {
956
+ return auditMutex(() => {
957
+ const { next, unknown } = applyAuditUpdates(readLedger(auditLedgerPath), args.updates);
958
+ if (unknown.length > 0) return {
959
+ content: [{
960
+ type: "text",
961
+ text: `Error: unknown check id(s): ${unknown.join(", ")}. Run audit_seed_checks first or check the id.`
962
+ }],
963
+ isError: true
964
+ };
965
+ writeLedgerAtomic(auditLedgerPath, next);
966
+ logToFile(`audit_resolve_checks: applied ${args.updates.length} update(s)`);
967
+ return { content: [{
968
+ type: "text",
969
+ text: `Resolved ${args.updates.length} check(s).`
970
+ }] };
971
+ });
972
+ });
973
+ const askQuestionSchema = z.object({
974
+ id: z.string().min(1).describe("Stable key for the answer in the response map"),
975
+ prompt: z.string().min(1).describe("Question text shown to the user"),
976
+ kind: z.enum([
977
+ "single",
978
+ "multi",
979
+ "text"
980
+ ]).describe("'single' = pick one option, 'multi' = pick any, 'text' = free-form single-line answer"),
981
+ options: z.array(z.object({
982
+ label: z.string(),
983
+ value: z.string(),
984
+ 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.")
985
+ })).optional().describe("Required for kind=single|multi; ignored for kind=text"),
986
+ required: z.boolean().optional().describe("Defaults to true"),
987
+ 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.")
988
+ });
989
+ return createSdkMcpServer({
990
+ name: SERVER_NAME,
991
+ version: "1.0.0",
992
+ tools: [
993
+ checkEnvKeys,
994
+ setEnvValues,
995
+ detectPM,
996
+ loadSkillMenu,
997
+ installSkill,
998
+ auditSeedChecks,
999
+ auditAddChecks,
1000
+ auditResolveChecks,
1001
+ 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) => {
1002
+ if (!askBridge) return {
1003
+ content: [{
1004
+ type: "text",
1005
+ text: "Error: wizard_ask is not available in this environment (CI / non-interactive). Proceed with sensible defaults or emit [ABORT] requirements-incomplete."
1006
+ }],
1007
+ isError: true
1008
+ };
1009
+ const capDecision = evaluateAskCap(askCallCount, askMaxQuestions, askAdjacencyNudged);
1010
+ if (capDecision.kind === "capped") {
1011
+ if (capDecision.reason === "adjacency") askAdjacencyNudged = true;
1012
+ analytics.wizardCapture("wizard_ask capped", {
1013
+ reason: capDecision.reason,
1014
+ call_count: askCallCount,
1015
+ max_questions: askMaxQuestions
1016
+ });
1017
+ return {
1018
+ content: [{
1019
+ type: "text",
1020
+ text: capDecision.message
1021
+ }],
1022
+ isError: true
1023
+ };
1024
+ }
1025
+ for (const q of args.questions) {
1026
+ if ((q.kind === "single" || q.kind === "multi") && (!q.options || q.options.length === 0)) return {
1027
+ content: [{
1028
+ type: "text",
1029
+ text: `Error: question "${q.id}" has kind="${q.kind}" but no options. Provide at least one { label, value } option, or change kind to "text".`
1030
+ }],
1031
+ isError: true
1032
+ };
1033
+ if (q.sensitive && q.kind !== "text") return {
1034
+ content: [{
1035
+ type: "text",
1036
+ text: `Error: question "${q.id}" sets sensitive=true but kind="${q.kind}". Only kind="text" answers can be vaulted as secrets.`
1037
+ }],
1038
+ isError: true
1039
+ };
1040
+ }
1041
+ const ids = /* @__PURE__ */ new Set();
1042
+ for (const q of args.questions) {
1043
+ if (ids.has(q.id)) return {
1044
+ content: [{
1045
+ type: "text",
1046
+ text: `Error: duplicate question id "${q.id}". Each question must have a unique id.`
1047
+ }],
1048
+ isError: true
1049
+ };
1050
+ ids.add(q.id);
1051
+ }
1052
+ askCallCount += 1;
1053
+ try {
1054
+ const answers = await askBridge.request({ questions: args.questions });
1055
+ if (isFullyCancelled(answers)) askCallCount -= 1;
1056
+ const sensitiveById = new Map(args.questions.filter((q) => q.sensitive).map((q) => [q.id, q.prompt]));
1057
+ const sanitised = {};
1058
+ for (const [id, answer] of Object.entries(answers)) {
1059
+ const label = sensitiveById.get(id);
1060
+ if (label !== void 0 && typeof answer === "string" && answer !== "__cancelled__") {
1061
+ const ref = secretVault.put(answer, {
1062
+ label,
1063
+ source: "wizard_ask"
1064
+ });
1065
+ sanitised[id] = { secretRef: ref };
1066
+ logToFile(`wizard_ask: vaulted answer for "${id}" as ${ref}`);
1067
+ } else sanitised[id] = answer;
1068
+ }
1069
+ logToFile(`wizard_ask: resolved ${Object.keys(answers).length} answer(s) for ${args.questions.length} question(s)`);
1070
+ return { content: [{
1071
+ type: "text",
1072
+ text: JSON.stringify({ answers: sanitised }, null, 2)
1073
+ }] };
1074
+ } catch (err) {
1075
+ logToFile(`wizard_ask: error: ${err?.message ?? err}`);
1076
+ return {
1077
+ content: [{
1078
+ type: "text",
1079
+ text: `Error: wizard_ask failed: ${err?.message ?? String(err)}`
1080
+ }],
1081
+ isError: true
1082
+ };
1083
+ }
1084
+ }),
1085
+ ...orchestrator ? buildOrchestratorTools(tool, orchestrator) : []
1086
+ ]
1087
+ });
1088
+ }
1089
+ /** Tool names exposed by the wizard-tools server, keyed for selective use. */
1090
+ const WIZARD_TOOL_NAMES = {
1091
+ checkEnvKeys: `mcp__${SERVER_NAME}__check_env_keys`,
1092
+ setEnvValues: `mcp__${SERVER_NAME}__set_env_values`,
1093
+ detectPackageManager: `mcp__${SERVER_NAME}__detect_package_manager`,
1094
+ loadSkillMenu: `mcp__${SERVER_NAME}__load_skill_menu`,
1095
+ installSkill: `mcp__${SERVER_NAME}__install_skill`,
1096
+ auditSeedChecks: `mcp__${SERVER_NAME}__audit_seed_checks`,
1097
+ auditAddChecks: `mcp__${SERVER_NAME}__audit_add_checks`,
1098
+ auditResolveChecks: `mcp__${SERVER_NAME}__audit_resolve_checks`,
1099
+ wizardAsk: `mcp__${SERVER_NAME}__wizard_ask`,
1100
+ enqueueTask: `mcp__${SERVER_NAME}__enqueue_task`,
1101
+ completeTask: `mcp__${SERVER_NAME}__complete_task`,
1102
+ readHandoffs: `mcp__${SERVER_NAME}__read_handoffs`
1103
+ };
1104
+ //#endregion
1105
+ export { coerceAuditChecks as _, installSkillById as a, resolveEnvPath as c, TaskStatus as d, createWizardAskBridge as f, AUDIT_SEVERITY_STYLE as g, AUDIT_REPORT_FILE as h, fetchSkillMenu as i, QUEUE_DIR_NAME as l, AUDIT_CHECKS_KEY as m, createWizardToolsServer as n, mergeEnvValues as o, AUDIT_CHECKS_FILE as p, downloadSkill as r, parseEnvKeys as s, WIZARD_TOOL_NAMES as t, QueueStore as u, getAuditChecks as v };
1106
+
1107
+ //# sourceMappingURL=wizard-tools-CGrE1O_b.js.map