@posthog/wizard 2.11.0 → 2.13.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 (86) hide show
  1. package/README.md +48 -7
  2. package/dist/{McpScreen-DvUncZBi.js → AuditChecksViewer-B0J7zcY2.js} +434 -22
  3. package/dist/AuditChecksViewer-B0J7zcY2.js.map +1 -0
  4. package/dist/{add-mcp-server-to-clients-Br1hDRiB.js → add-mcp-server-to-clients-CUNR00bB.js} +5 -5
  5. package/dist/{add-mcp-server-to-clients-Br1hDRiB.js.map → add-mcp-server-to-clients-CUNR00bB.js.map} +1 -1
  6. package/dist/{readiness-gQvQNCeL.js → agent-interface-CV0-vtxj.js} +328 -462
  7. package/dist/agent-interface-CV0-vtxj.js.map +1 -0
  8. package/dist/{agent-runner-fWYFO4H0.js → agent-runner-LvVQH31D.js} +21 -31
  9. package/dist/{agent-runner-fWYFO4H0.js.map → agent-runner-LvVQH31D.js.map} +1 -1
  10. package/dist/analytics-BH7bEHQR.js +2 -0
  11. package/dist/analytics-VM7laaFx.js +123 -0
  12. package/dist/analytics-VM7laaFx.js.map +1 -0
  13. package/dist/bin.js +529 -42
  14. package/dist/bin.js.map +1 -1
  15. package/dist/{debug-D-0xueVl.js → debug-BdcTB7EF.js} +1 -1
  16. package/dist/debug-Cqi6nVfX.js +686 -0
  17. package/dist/debug-Cqi6nVfX.js.map +1 -0
  18. package/dist/{defaults-CPH6eWhN.js → defaults-GbLPuHxj.js} +1 -1
  19. package/dist/{defaults-CPH6eWhN.js.map → defaults-GbLPuHxj.js.map} +1 -1
  20. package/dist/{detection-B7GNzve-.js → detection-CSjmal-X.js} +3 -3
  21. package/dist/{detection-B7GNzve-.js.map → detection-CSjmal-X.js.map} +1 -1
  22. package/dist/{env-api-key-DU8uIEvo.js → env-api-key-D5G2PrXW.js} +1 -1
  23. package/dist/{env-api-key-DU8uIEvo.js.map → env-api-key-D5G2PrXW.js.map} +1 -1
  24. package/dist/{file-DhSBlq-x.js → file-8iNrXHkG.js} +2 -2
  25. package/dist/{file-DhSBlq-x.js.map → file-8iNrXHkG.js.map} +1 -1
  26. package/dist/{file-utils-Dy9JncCo.js → file-utils-DnTSiTJw.js} +1 -1
  27. package/dist/{file-utils-Dy9JncCo.js.map → file-utils-DnTSiTJw.js.map} +1 -1
  28. package/dist/{package-manager-D3Lo6nXf.js → package-manager-CD8RQW-e.js} +2 -2
  29. package/dist/{package-manager-D3Lo6nXf.js.map → package-manager-CD8RQW-e.js.map} +1 -1
  30. package/dist/paths-DJS47p5x.js +26 -0
  31. package/dist/paths-DJS47p5x.js.map +1 -0
  32. package/dist/{posthog-integration-D4SRhJIQ.js → posthog-integration-BL21S3T6.js} +41 -13
  33. package/dist/posthog-integration-BL21S3T6.js.map +1 -0
  34. package/dist/{posthog-ByrpqEjN.js → posthog-vm0k9PKS.js} +1 -1
  35. package/dist/{posthog-ByrpqEjN.js.map → posthog-vm0k9PKS.js.map} +1 -1
  36. package/dist/provisioning-BdQ1ONIg.js +2 -0
  37. package/dist/provisioning-g9aoVIEd.js +166 -0
  38. package/dist/provisioning-g9aoVIEd.js.map +1 -0
  39. package/dist/{registry-DaPKstG3.js → registry-BaMEaAKd.js} +4 -5
  40. package/dist/{registry-DaPKstG3.js.map → registry-BaMEaAKd.js.map} +1 -1
  41. package/dist/{router-SgzmfLGi.js → router-COhhuIW3.js} +4 -3
  42. package/dist/router-COhhuIW3.js.map +1 -0
  43. package/dist/{setup-utils-y4s-3uKT.js → setup-utils-CNV7FSlY.js} +11 -150
  44. package/dist/setup-utils-CNV7FSlY.js.map +1 -0
  45. package/dist/setup-utils-CU4FIqjB.js +2 -0
  46. package/dist/{start-playground-g1TxpCZ5.js → start-playground-C9GWnVdM.js} +102 -7
  47. package/dist/start-playground-C9GWnVdM.js.map +1 -0
  48. package/dist/start-tui-B_zwutLe.js +4195 -0
  49. package/dist/start-tui-B_zwutLe.js.map +1 -0
  50. package/dist/{steps-D1zKDqAo.js → steps-Dawz7k3T.js} +8 -8
  51. package/dist/steps-Dawz7k3T.js.map +1 -0
  52. package/dist/{task-stream-DX_jKDQu.js → task-stream-CX7Uf6EM.js} +4 -4
  53. package/dist/{task-stream-DX_jKDQu.js.map → task-stream-CX7Uf6EM.js.map} +1 -1
  54. package/dist/{telemetry-CyUUSAYy.js → telemetry-D6bjWA-A.js} +2 -2
  55. package/dist/{telemetry-CyUUSAYy.js.map → telemetry-D6bjWA-A.js.map} +1 -1
  56. package/dist/{wizard-abort-Buodno3f.js → wizard-abort-CJkNkSjT.js} +6 -4
  57. package/dist/{wizard-abort-Buodno3f.js.map → wizard-abort-CJkNkSjT.js.map} +1 -1
  58. package/dist/{wizard-abort-DZmO_sIZ.js → wizard-abort-Dl0BkqhT.js} +1 -1
  59. package/dist/wizard-session-BQC9vy9Z.js +2 -0
  60. package/dist/{wizard-session-D5bggSsu.js → wizard-session-BcNJTl2I.js} +1 -1
  61. package/dist/{wizard-session-D5bggSsu.js.map → wizard-session-BcNJTl2I.js.map} +1 -1
  62. package/dist/{wizard-ui-BExOjdjA.js → wizard-ui-YdGFRyu_.js} +1 -1
  63. package/dist/wizard-ui-YdGFRyu_.js.map +1 -0
  64. package/npm-shrinkwrap.json +2 -2
  65. package/package.json +1 -1
  66. package/dist/McpScreen-DvUncZBi.js.map +0 -1
  67. package/dist/agent-skill-DJOzDaQV.js +0 -59
  68. package/dist/agent-skill-DJOzDaQV.js.map +0 -1
  69. package/dist/analytics-CfAUlt6-.js +0 -2
  70. package/dist/analytics-D3rY3TaN.js +0 -210
  71. package/dist/analytics-D3rY3TaN.js.map +0 -1
  72. package/dist/debug-gWEjmYVV.js +0 -203
  73. package/dist/debug-gWEjmYVV.js.map +0 -1
  74. package/dist/paths-BL-x2rFy.js +0 -16
  75. package/dist/paths-BL-x2rFy.js.map +0 -1
  76. package/dist/posthog-integration-D4SRhJIQ.js.map +0 -1
  77. package/dist/readiness-gQvQNCeL.js.map +0 -1
  78. package/dist/router-SgzmfLGi.js.map +0 -1
  79. package/dist/setup-utils-_ONxN-TT.js +0 -2
  80. package/dist/setup-utils-y4s-3uKT.js.map +0 -1
  81. package/dist/start-playground-g1TxpCZ5.js.map +0 -1
  82. package/dist/start-tui-CQef69NR.js +0 -2167
  83. package/dist/start-tui-CQef69NR.js.map +0 -1
  84. package/dist/steps-D1zKDqAo.js.map +0 -1
  85. package/dist/wizard-session-COhklXAF.js +0 -2
  86. package/dist/wizard-ui-BExOjdjA.js.map +0 -1
@@ -0,0 +1,4195 @@
1
+ import { C as Integration, D as POSTHOG_DOCS_URL, H as getSkillsBaseUrl, N as REMOTE_SKILLS_BASE_URL, R as WIZARD_TOOLS_MENU_FLAG_KEY, f as SIGNUP_WIZARD_READINESS_CONFIG, l as setUI, m as getBlockingServiceKeys, s as logToFile, w as OAUTH_PORTS } from "./debug-Cqi6nVfX.js";
2
+ import { a as relativeToInstallDir, n as WIZARD_LOG_FILE } from "./paths-DJS47p5x.js";
3
+ import { n as analytics } from "./analytics-VM7laaFx.js";
4
+ import { l as ApiError, p as getUiHostFromHost } from "./setup-utils-CNV7FSlY.js";
5
+ import { t as ADDITIONAL_FEATURE_LABELS } from "./wizard-session-BcNJTl2I.js";
6
+ import { r as wizardAbort } from "./wizard-abort-CJkNkSjT.js";
7
+ import { _ as coerceAuditChecks, d as fetchSkillMenu, g as AUDIT_SEVERITY_STYLE, m as AUDIT_CHECKS_KEY, p as AUDIT_CHECKS_FILE, u as downloadSkill, v as getAuditChecks } from "./agent-interface-CV0-vtxj.js";
8
+ import { t as EVENT_PLAN_FILE } from "./posthog-integration-BL21S3T6.js";
9
+ import { i as fetchHealthIssues, n as getWorkflowConfig, o as POSTHOG_SDKS, s as STRIPE_SDKS } from "./bin.js";
10
+ import { t as ALL_FEATURE_VALUES } from "./defaults-GbLPuHxj.js";
11
+ import { a as getSupportedClients, c as removeMCPServer, i as getInstalledClients, o as getSupportedPluginClients, s as installPlugins, u as isPluginCapable } from "./add-mcp-server-to-clients-CUNR00bB.js";
12
+ import "./router-COhhuIW3.js";
13
+ import { E as WizardStore, S as SplitView, T as Icons, _ as useStdoutDimensions, a as SEVERITY_ORDER, b as ProgressList, c as LearnCard, d as ScreenContainer, f as EventPlanViewer, h as ConfirmationInput, i as SEVERITY_LABEL, l as HNViewer, m as ModalOverlay, n as McpScreen, o as ServiceHealthList, p as LogViewer, r as IssueTable, s as TipsCard, t as AuditChecksViewer, u as TabContainer, v as PickerMenu, w as Colors, x as LoadingBox, y as useKeyBindings } from "./AuditChecksViewer-B0J7zcY2.js";
14
+ import { spawn, spawnSync } from "node:child_process";
15
+ import { join } from "node:path";
16
+ import * as fs$1 from "fs";
17
+ import path from "path";
18
+ import { Box, Text, render, useInput } from "ink";
19
+ import { Fragment, createElement, useEffect, useMemo, useState, useSyncExternalStore } from "react";
20
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
21
+ import { Spinner } from "@inkjs/ui";
22
+ import { access, readdir, rm } from "node:fs/promises";
23
+ //#region src/ui/tui/ink-ui.ts
24
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
25
+ function stripAnsi(s) {
26
+ return s.replace(ANSI_RE, "");
27
+ }
28
+ var InkUI = class {
29
+ constructor(store) {
30
+ this.store = store;
31
+ }
32
+ intro(message) {
33
+ this.store.pushStatus(message);
34
+ }
35
+ outro(message) {
36
+ this.store.pushStatus(stripAnsi(message));
37
+ const existing = this.store.session.outroData;
38
+ this.store.setOutroData(existing ?? {
39
+ kind: "success",
40
+ message: stripAnsi(message)
41
+ });
42
+ if (this.store.session.runPhase === "running") this.store.setRunPhase("completed");
43
+ }
44
+ outroError(data) {
45
+ this.store.setOutroData(data);
46
+ if (this.store.session.runPhase !== "error") this.store.setRunPhase("error");
47
+ }
48
+ waitForOutroDismissed() {
49
+ return new Promise((resolve) => {
50
+ if (this.store.session.outroDismissed) {
51
+ resolve();
52
+ return;
53
+ }
54
+ const unsub = this.store.subscribe(() => {
55
+ if (this.store.session.outroDismissed) {
56
+ unsub();
57
+ resolve();
58
+ }
59
+ });
60
+ });
61
+ }
62
+ setCredentials(credentials) {
63
+ this.store.setCredentials(credentials);
64
+ }
65
+ setDetectedFramework(label) {
66
+ this.store.setDetectedFramework(label);
67
+ }
68
+ onEnterScreen(screen, fn) {
69
+ this.store.onEnterScreen(screen, fn);
70
+ }
71
+ setLoginUrl(url) {
72
+ this.store.setLoginUrl(url);
73
+ }
74
+ showBlockingOutage(result) {
75
+ this.store.setReadinessResult(result);
76
+ return Promise.resolve();
77
+ }
78
+ setReadinessWarnings(result) {
79
+ this.store.setReadinessResult(result);
80
+ }
81
+ showPortConflict(processInfo) {
82
+ return this.store.showPortConflict(processInfo);
83
+ }
84
+ showSettingsOverride(conflicts, backupAndFix) {
85
+ return this.store.showSettingsOverride(conflicts, backupAndFix);
86
+ }
87
+ showAuthError() {
88
+ this.store.showAuthError();
89
+ }
90
+ startRun() {
91
+ this.store.setRunPhase("running");
92
+ }
93
+ cancel(message) {
94
+ this.store.pushStatus(message);
95
+ }
96
+ log = {
97
+ info: (message) => {
98
+ this.store.pushStatus(message);
99
+ },
100
+ warn: (message) => {
101
+ this.store.pushStatus(message);
102
+ },
103
+ error: (message) => {
104
+ this.store.pushStatus(message);
105
+ },
106
+ success: (message) => {
107
+ this.store.pushStatus(message);
108
+ },
109
+ step: (message) => {
110
+ this.store.pushStatus(message);
111
+ }
112
+ };
113
+ note(message) {
114
+ this.store.pushStatus(message);
115
+ }
116
+ spinner() {
117
+ return {
118
+ start: (message) => {
119
+ if (message) this.store.pushStatus(message);
120
+ },
121
+ stop: (message) => {
122
+ if (message) this.store.pushStatus(message);
123
+ },
124
+ message: (msg) => {
125
+ if (msg) this.store.pushStatus(msg);
126
+ }
127
+ };
128
+ }
129
+ pushStatus(message) {
130
+ this.store.pushStatus(message);
131
+ }
132
+ syncTodos(todos) {
133
+ this.store.syncTodos(todos);
134
+ }
135
+ setEventPlan(events) {
136
+ this.store.setEventPlan(events);
137
+ }
138
+ setFrameworkContext(key, value) {
139
+ this.store.setFrameworkContext(key, value);
140
+ }
141
+ };
142
+ //#endregion
143
+ //#region src/ui/tui/screens/health/HealthCheckScreen.tsx
144
+ /**
145
+ * HealthCheckScreen — Flow screen between Intro and Auth.
146
+ *
147
+ * Three states:
148
+ * 1. Checking: spinner while health check runs
149
+ * 2. Healthy: isComplete returns true, router auto-advances to Auth
150
+ * 3. Blocking outage: shows affected services with Continue/Exit
151
+ */
152
+ const EXAMPLE_PROMPT = "Integrate PostHog into this project using the skill files in .posthog/skills/. Read SKILL.md first, then follow the numbered workflow files in order.";
153
+ const SkillsDownloadedScreen = () => {
154
+ useInput(() => {
155
+ process.exit(0);
156
+ });
157
+ return /* @__PURE__ */ jsxs(Box, {
158
+ flexDirection: "column",
159
+ flexGrow: 1,
160
+ children: [
161
+ /* @__PURE__ */ jsxs(Text, {
162
+ color: "green",
163
+ bold: true,
164
+ children: [Icons.check, " Skills downloaded to .posthog/skills/"]
165
+ }),
166
+ /* @__PURE__ */ jsxs(Box, {
167
+ marginTop: 1,
168
+ flexDirection: "column",
169
+ children: [/* @__PURE__ */ jsx(Text, { children: "You can continue setup with another agent using this prompt:" }), /* @__PURE__ */ jsx(Box, {
170
+ marginTop: 1,
171
+ paddingLeft: 2,
172
+ children: /* @__PURE__ */ jsx(Text, {
173
+ color: "cyan",
174
+ children: EXAMPLE_PROMPT
175
+ })
176
+ })]
177
+ }),
178
+ /* @__PURE__ */ jsx(Box, {
179
+ marginTop: 1,
180
+ children: /* @__PURE__ */ jsx(Text, {
181
+ color: Colors.muted,
182
+ children: "Press any key to exit"
183
+ })
184
+ })
185
+ ]
186
+ });
187
+ };
188
+ const HealthCheckScreen = ({ store }) => {
189
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
190
+ const [downloaded, setDownloaded] = useState(false);
191
+ const [downloading, setDownloading] = useState(false);
192
+ const result = store.session.readinessResult;
193
+ if (downloaded) return /* @__PURE__ */ jsx(SkillsDownloadedScreen, {});
194
+ if (!result) return /* @__PURE__ */ jsx(Box, {
195
+ flexDirection: "column",
196
+ flexGrow: 1,
197
+ alignItems: "center",
198
+ justifyContent: "center",
199
+ children: /* @__PURE__ */ jsx(LoadingBox, { message: "Checking service status..." })
200
+ });
201
+ const isSignup = store.session.signup;
202
+ const blockingKeys = getBlockingServiceKeys(result.health, isSignup ? SIGNUP_WIZARD_READINESS_CONFIG : void 0);
203
+ const warningKeys = isSignup ? getBlockingServiceKeys(result.health).filter((k) => !blockingKeys.includes(k)) : [];
204
+ const hasHardBlock = blockingKeys.length > 0;
205
+ const displayKeys = hasHardBlock ? blockingKeys : warningKeys;
206
+ if (displayKeys.length === 0) return null;
207
+ const isGithubReleasesDown = hasHardBlock && blockingKeys.includes("githubReleases");
208
+ const canDownloadSkills = result.health.githubReleases.status === "healthy";
209
+ const integration = store.session.integration;
210
+ const title = hasHardBlock ? "Ongoing service disruptions" : "Service disruption detected";
211
+ const docsUrl = store.session.frameworkConfig?.metadata.docsUrl;
212
+ const description = isGithubReleasesDown ? "The Wizard can't download necessary skills from GitHub Releases right now." : hasHardBlock ? "The Wizard cannot start while these services are down." : "Some services are degraded. You can continue, but parts of the wizard may not work reliably.";
213
+ const handleDownloadAndExit = async () => {
214
+ if (downloading) return;
215
+ setDownloading(true);
216
+ const menu = await fetchSkillMenu(REMOTE_SKILLS_BASE_URL);
217
+ if (menu) {
218
+ const prefix = `integration-${integration}`;
219
+ const skills = (menu.categories["integration"] ?? []).filter((s) => s.id.startsWith(prefix));
220
+ for (const skill of skills) downloadSkill(skill, store.session.installDir, ".posthog/skills");
221
+ }
222
+ setDownloaded(true);
223
+ };
224
+ return /* @__PURE__ */ jsxs(ModalOverlay, {
225
+ borderColor: hasHardBlock ? "red" : "yellow",
226
+ title,
227
+ width: 72,
228
+ footer: isGithubReleasesDown ? /* @__PURE__ */ jsx(ConfirmationInput, {
229
+ message: "",
230
+ confirmLabel: "",
231
+ cancelLabel: "Exit [Esc]",
232
+ onConfirm: () => void wizardAbort({ message: "Exited due to service outage." }),
233
+ onCancel: () => void wizardAbort({ message: "Exited due to service outage." })
234
+ }) : /* @__PURE__ */ jsx(ConfirmationInput, {
235
+ message: "Continue anyway?",
236
+ confirmLabel: "Continue [Enter]",
237
+ cancelLabel: canDownloadSkills && !isGithubReleasesDown ? downloading ? "Downloading..." : "Download skills & Exit [Esc]" : "Exit [Esc]",
238
+ onConfirm: () => store.dismissOutage(),
239
+ onCancel: canDownloadSkills && !isGithubReleasesDown ? () => void handleDownloadAndExit() : () => void wizardAbort({ message: "Exited due to service outage." })
240
+ }),
241
+ children: [
242
+ /* @__PURE__ */ jsxs(Box, {
243
+ flexDirection: "column",
244
+ marginBottom: 1,
245
+ children: [/* @__PURE__ */ jsx(Box, {
246
+ marginBottom: 1,
247
+ children: /* @__PURE__ */ jsxs(Text, { children: [
248
+ /* @__PURE__ */ jsx(Text, {
249
+ color: "red",
250
+ children: Icons.squareFilled
251
+ }),
252
+ /* @__PURE__ */ jsx(Text, {
253
+ dimColor: true,
254
+ children: " Down "
255
+ }),
256
+ /* @__PURE__ */ jsx(Text, {
257
+ color: "#DC9300",
258
+ children: Icons.squareFilled
259
+ }),
260
+ /* @__PURE__ */ jsx(Text, {
261
+ dimColor: true,
262
+ children: " Degraded"
263
+ })
264
+ ] })
265
+ }), /* @__PURE__ */ jsx(ServiceHealthList, {
266
+ health: result.health,
267
+ filterKeys: displayKeys,
268
+ showHealthy: false
269
+ })]
270
+ }),
271
+ /* @__PURE__ */ jsx(Text, {
272
+ dimColor: true,
273
+ children: description
274
+ }),
275
+ isGithubReleasesDown && docsUrl && /* @__PURE__ */ jsx(Box, {
276
+ marginTop: 1,
277
+ children: /* @__PURE__ */ jsxs(Text, { children: ["Set up manually: ", /* @__PURE__ */ jsx(Text, {
278
+ color: "cyan",
279
+ children: docsUrl
280
+ })] })
281
+ }),
282
+ canDownloadSkills && !isGithubReleasesDown && /* @__PURE__ */ jsx(Box, {
283
+ marginTop: 1,
284
+ children: /* @__PURE__ */ jsx(Text, { children: "You can still download the PostHog integration skills and continue with another agent." })
285
+ })
286
+ ]
287
+ });
288
+ };
289
+ //#endregion
290
+ //#region src/ui/tui/screens/doctor/DoctorIntroScreen.tsx
291
+ const DoctorIntroScreen = ({ store }) => {
292
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
293
+ return /* @__PURE__ */ jsxs(Box, {
294
+ flexDirection: "column",
295
+ children: [
296
+ /* @__PURE__ */ jsxs(Box, {
297
+ flexDirection: "column",
298
+ marginBottom: 1,
299
+ children: [/* @__PURE__ */ jsx(Text, {
300
+ bold: true,
301
+ color: Colors.accent,
302
+ children: "PostHog Doctor"
303
+ }), /* @__PURE__ */ jsx(Text, {
304
+ dimColor: true,
305
+ children: "Scan your project configuration for issues that may need attention."
306
+ })]
307
+ }),
308
+ /* @__PURE__ */ jsxs(Box, {
309
+ flexDirection: "column",
310
+ marginBottom: 1,
311
+ children: [/* @__PURE__ */ jsx(Text, { children: "The wizard will:" }), /* @__PURE__ */ jsxs(Box, {
312
+ paddingLeft: 2,
313
+ flexDirection: "column",
314
+ children: [
315
+ /* @__PURE__ */ jsxs(Text, { children: [Icons.bullet, " Sign you in to PostHog"] }),
316
+ /* @__PURE__ */ jsxs(Text, { children: [Icons.bullet, " Fetch active health issues for your project"] }),
317
+ /* @__PURE__ */ jsxs(Text, { children: [Icons.bullet, " Show you what needs to be resolved, with docs links"] })
318
+ ]
319
+ })]
320
+ }),
321
+ /* @__PURE__ */ jsx(PickerMenu, {
322
+ options: [{
323
+ label: "Continue",
324
+ value: "continue"
325
+ }, {
326
+ label: "Cancel",
327
+ value: "cancel"
328
+ }],
329
+ onSelect: (value) => {
330
+ if (value === "cancel") process.exit(0);
331
+ else store.completeSetup();
332
+ }
333
+ })
334
+ ]
335
+ });
336
+ };
337
+ //#endregion
338
+ //#region src/ui/tui/screens/doctor/DoctorReportScreen.tsx
339
+ const DoctorReportScreen = ({ store }) => {
340
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
341
+ const { credentials } = store.session;
342
+ const accessToken = credentials?.accessToken;
343
+ const host = credentials?.host;
344
+ const projectId = credentials?.projectId;
345
+ const [state, setState] = useState({ kind: "loading" });
346
+ useEffect(() => {
347
+ if (!accessToken || !host || projectId == null) return;
348
+ let cancelled = false;
349
+ (async () => {
350
+ try {
351
+ const issues = await fetchHealthIssues(accessToken, host, projectId);
352
+ if (!cancelled) setState({
353
+ kind: "ready",
354
+ issues
355
+ });
356
+ } catch (err) {
357
+ if (!cancelled) setState({
358
+ kind: "error",
359
+ message: err instanceof ApiError && err.statusCode === 401 ? "Your PostHog session has expired. Re-run the wizard to sign in again." : err instanceof Error ? err.message : String(err)
360
+ });
361
+ }
362
+ })();
363
+ return () => {
364
+ cancelled = true;
365
+ };
366
+ }, [
367
+ accessToken,
368
+ host,
369
+ projectId
370
+ ]);
371
+ if (!credentials) return /* @__PURE__ */ jsx(LoadingBox, { message: "Waiting for authentication..." });
372
+ if (state.kind === "loading") return /* @__PURE__ */ jsxs(Box, {
373
+ flexDirection: "column",
374
+ children: [/* @__PURE__ */ jsx(Header, {
375
+ host: credentials.host,
376
+ projectId: credentials.projectId
377
+ }), /* @__PURE__ */ jsx(LoadingBox, { message: "Fetching health issues..." })]
378
+ });
379
+ const healthUrl = `${getUiHostFromHost(credentials.host)}/project/${credentials.projectId}/health`;
380
+ if (state.kind === "error") return /* @__PURE__ */ jsxs(Box, {
381
+ flexDirection: "column",
382
+ children: [
383
+ /* @__PURE__ */ jsx(Header, {
384
+ host: credentials.host,
385
+ projectId: credentials.projectId
386
+ }),
387
+ /* @__PURE__ */ jsxs(Box, {
388
+ flexDirection: "column",
389
+ marginY: 1,
390
+ children: [/* @__PURE__ */ jsxs(Text, {
391
+ color: Colors.error,
392
+ bold: true,
393
+ children: [Icons.squareFilled, " Failed to fetch health issues"]
394
+ }), /* @__PURE__ */ jsx(Text, {
395
+ dimColor: true,
396
+ children: state.message
397
+ })]
398
+ }),
399
+ /* @__PURE__ */ jsx(PickerMenu, {
400
+ options: [{
401
+ label: "Continue",
402
+ value: "continue"
403
+ }],
404
+ onSelect: () => {
405
+ store.setOutroData({
406
+ kind: "error",
407
+ message: "Failed to fetch health issues",
408
+ body: state.message,
409
+ docsUrl: POSTHOG_DOCS_URL
410
+ });
411
+ }
412
+ })
413
+ ]
414
+ });
415
+ const { issues } = state;
416
+ if (issues.length === 0) return /* @__PURE__ */ jsxs(Box, {
417
+ flexDirection: "column",
418
+ children: [
419
+ /* @__PURE__ */ jsx(Header, {
420
+ host: credentials.host,
421
+ projectId: credentials.projectId
422
+ }),
423
+ /* @__PURE__ */ jsx(Box, {
424
+ marginY: 1,
425
+ children: /* @__PURE__ */ jsxs(Text, {
426
+ color: Colors.success,
427
+ bold: true,
428
+ children: [Icons.check, " No active issues — you're all set!"]
429
+ })
430
+ }),
431
+ /* @__PURE__ */ jsx(PickerMenu, {
432
+ options: [{
433
+ label: "Continue",
434
+ value: "continue"
435
+ }],
436
+ onSelect: () => {
437
+ store.setOutroData({
438
+ kind: "success",
439
+ message: "No active issues — your project looks healthy.",
440
+ docsUrl: POSTHOG_DOCS_URL,
441
+ continueUrl: healthUrl
442
+ });
443
+ }
444
+ })
445
+ ]
446
+ });
447
+ return /* @__PURE__ */ jsxs(Box, {
448
+ flexDirection: "column",
449
+ children: [
450
+ /* @__PURE__ */ jsx(Header, {
451
+ host: credentials.host,
452
+ projectId: credentials.projectId
453
+ }),
454
+ /* @__PURE__ */ jsx(Box, {
455
+ marginTop: 1,
456
+ children: /* @__PURE__ */ jsx(Text, { children: formatSummaryLine(issues) })
457
+ }),
458
+ /* @__PURE__ */ jsx(Box, {
459
+ flexDirection: "column",
460
+ marginBottom: 1,
461
+ children: /* @__PURE__ */ jsx(IssueTable, { issues })
462
+ }),
463
+ /* @__PURE__ */ jsx(PickerMenu, {
464
+ options: [{
465
+ label: "Continue",
466
+ value: "continue"
467
+ }],
468
+ onSelect: () => {
469
+ store.setOutroData({
470
+ kind: "success",
471
+ message: `Found ${issues.length} active issue${issues.length === 1 ? "" : "s"}.`,
472
+ body: "Open the dashboard in PostHog to dismiss or resolve issues.",
473
+ docsUrl: POSTHOG_DOCS_URL,
474
+ continueUrl: healthUrl
475
+ });
476
+ }
477
+ })
478
+ ]
479
+ });
480
+ };
481
+ const Header = ({ host, projectId }) => /* @__PURE__ */ jsxs(Box, {
482
+ flexDirection: "column",
483
+ children: [/* @__PURE__ */ jsx(Text, {
484
+ bold: true,
485
+ color: Colors.accent,
486
+ children: "PostHog Doctor Report"
487
+ }), /* @__PURE__ */ jsxs(Text, {
488
+ dimColor: true,
489
+ children: [
490
+ "Project ",
491
+ projectId,
492
+ " ",
493
+ Icons.bullet,
494
+ " ",
495
+ host
496
+ ]
497
+ })]
498
+ });
499
+ function formatSummaryLine(issues) {
500
+ const parts = [];
501
+ for (const sev of SEVERITY_ORDER) {
502
+ const n = issues.filter((i) => i.severity === sev).length;
503
+ if (n > 0) parts.push(`${n} ${SEVERITY_LABEL[sev].toLowerCase()}`);
504
+ }
505
+ const suffix = parts.length > 0 ? `: ${parts.join(", ")}` : "";
506
+ return `${issues.length} active issue${issues.length === 1 ? "" : "s"}${suffix}`;
507
+ }
508
+ //#endregion
509
+ //#region src/ui/tui/screens/SettingsOverrideScreen.tsx
510
+ function sourcePath(source) {
511
+ switch (source) {
512
+ case "project": return ".claude/settings.json";
513
+ case "managed": return "/Library/Application Support/ClaudeCode/managed-settings.json";
514
+ default: return source;
515
+ }
516
+ }
517
+ const SettingsOverrideScreen = ({ store }) => {
518
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
519
+ const [feedback, setFeedback] = useState(null);
520
+ const conflicts = store.session.settingsConflicts?.filter((c) => c.writable);
521
+ if (!conflicts || conflicts.length === 0) return null;
522
+ return /* @__PURE__ */ jsxs(ModalOverlay, {
523
+ borderColor: "red",
524
+ title: `${Icons.warning} Settings conflict`,
525
+ width: 64,
526
+ feedback: feedback ? `${Icons.warning} ${feedback}` : null,
527
+ footer: /* @__PURE__ */ jsx(ConfirmationInput, {
528
+ message: "Back up to .wizard-backup and continue?",
529
+ confirmLabel: "Backup & continue [Enter]",
530
+ cancelLabel: "Exit [Esc]",
531
+ onConfirm: () => {
532
+ if (!store.backupAndFixSettingsOverride()) setFeedback("Could not back up the settings file.");
533
+ },
534
+ onCancel: () => process.exit(1)
535
+ }),
536
+ children: [conflicts.map((conflict) => /* @__PURE__ */ jsxs(Box, {
537
+ flexDirection: "column",
538
+ marginBottom: 1,
539
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
540
+ "Your settings file at",
541
+ " ",
542
+ /* @__PURE__ */ jsx(Text, {
543
+ bold: true,
544
+ children: sourcePath(conflict.source)
545
+ }),
546
+ " sets:"
547
+ ] }), /* @__PURE__ */ jsx(Box, {
548
+ flexDirection: "column",
549
+ paddingLeft: 2,
550
+ children: conflict.keys.map((key) => /* @__PURE__ */ jsxs(Text, { children: [
551
+ Icons.bullet,
552
+ " ",
553
+ /* @__PURE__ */ jsx(Text, {
554
+ color: "yellow",
555
+ bold: true,
556
+ children: key
557
+ })
558
+ ] }, key))
559
+ })]
560
+ }, conflict.source)), /* @__PURE__ */ jsx(Text, {
561
+ dimColor: true,
562
+ children: "These settings override credentials and prevent the Wizard from reaching the PostHog LLM Gateway. We can back up the file and continue."
563
+ })]
564
+ });
565
+ };
566
+ //#endregion
567
+ //#region src/ui/tui/screens/ManagedSettingsScreen.tsx
568
+ /**
569
+ * ManagedSettingsScreen — Modal when IT/org-managed settings contain overrides
570
+ * that block the Wizard from reaching the PostHog LLM Gateway.
571
+ *
572
+ * Unlike SettingsOverrideScreen, the wizard cannot back up or modify these files.
573
+ * The user must contact their IT administrator to resolve the conflict.
574
+ */
575
+ function sourceLabel(source) {
576
+ switch (source) {
577
+ case "managed": return "Managed settings (IT/org-managed)";
578
+ case "project": return ".claude/settings.json";
579
+ default: return source;
580
+ }
581
+ }
582
+ const ManagedSettingsScreen = ({ store }) => {
583
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
584
+ const readOnlyConflicts = store.session.settingsConflicts?.filter((c) => !c.writable);
585
+ if (!readOnlyConflicts || readOnlyConflicts.length === 0) return null;
586
+ return /* @__PURE__ */ jsxs(ModalOverlay, {
587
+ borderColor: "red",
588
+ title: `${Icons.warning} Organization settings conflict`,
589
+ width: 68,
590
+ footer: /* @__PURE__ */ jsx(ConfirmationInput, {
591
+ message: "Contact your IT administrator to resolve this.",
592
+ confirmLabel: "",
593
+ cancelLabel: "Exit [Esc]",
594
+ onConfirm: () => process.exit(1),
595
+ onCancel: () => process.exit(1)
596
+ }),
597
+ children: [
598
+ /* @__PURE__ */ jsx(Text, {
599
+ dimColor: true,
600
+ children: "Your organization's managed settings contain overrides that prevent the Wizard from reaching the PostHog LLM Gateway."
601
+ }),
602
+ readOnlyConflicts.map((conflict) => /* @__PURE__ */ jsxs(Box, {
603
+ flexDirection: "column",
604
+ marginTop: 1,
605
+ children: [/* @__PURE__ */ jsx(Text, {
606
+ bold: true,
607
+ children: sourceLabel(conflict.source)
608
+ }), /* @__PURE__ */ jsx(Box, {
609
+ flexDirection: "column",
610
+ paddingLeft: 2,
611
+ children: conflict.keys.map((key) => /* @__PURE__ */ jsxs(Text, { children: [
612
+ Icons.bullet,
613
+ " ",
614
+ /* @__PURE__ */ jsx(Text, {
615
+ color: "yellow",
616
+ bold: true,
617
+ children: key
618
+ })
619
+ ] }, key))
620
+ })]
621
+ }, conflict.source)),
622
+ /* @__PURE__ */ jsx(Box, {
623
+ marginTop: 1,
624
+ children: /* @__PURE__ */ jsx(Text, {
625
+ dimColor: true,
626
+ children: "Try running \"claude auth logout\" or contact your IT administrator to resolve this."
627
+ })
628
+ })
629
+ ]
630
+ });
631
+ };
632
+ //#endregion
633
+ //#region src/ui/tui/screens/PortConflictScreen.tsx
634
+ /**
635
+ * PortConflictScreen — Modal when all OAuth port candidates are occupied.
636
+ *
637
+ * Shows every port the wizard tried and asks the user to free them manually.
638
+ */
639
+ const PortConflictScreen = ({ store }) => {
640
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
641
+ if (!store.session.portConflictProcess) return null;
642
+ return /* @__PURE__ */ jsxs(ModalOverlay, {
643
+ borderColor: "#DC9300",
644
+ title: "OAuth ports in use",
645
+ width: 72,
646
+ footer: /* @__PURE__ */ jsx(ConfirmationInput, {
647
+ message: "Retry after freeing ports?",
648
+ confirmLabel: "Retry [Enter]",
649
+ cancelLabel: "Exit [Esc]",
650
+ onConfirm: () => store.resolvePortConflict(),
651
+ onCancel: () => process.exit(1)
652
+ }),
653
+ children: [
654
+ /* @__PURE__ */ jsx(Text, { children: "The wizard needs a local port for OAuth. We tried these ports which are all in use:" }),
655
+ /* @__PURE__ */ jsx(Box, {
656
+ flexDirection: "column",
657
+ marginY: 1,
658
+ paddingLeft: 2,
659
+ gap: 0,
660
+ children: OAUTH_PORTS.map((port) => /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
661
+ dimColor: true,
662
+ children: "Port "
663
+ }), /* @__PURE__ */ jsx(Text, {
664
+ bold: true,
665
+ children: port
666
+ })] }, port))
667
+ }),
668
+ /* @__PURE__ */ jsx(Text, {
669
+ dimColor: true,
670
+ children: "Please free one of these ports and retry."
671
+ })
672
+ ]
673
+ });
674
+ };
675
+ //#endregion
676
+ //#region src/ui/tui/screens/IntroScreenLayout.tsx
677
+ /**
678
+ * IntroScreenLayout ��� Shared visual shell for all workflow intro screens.
679
+ *
680
+ * Purely presentational — no store subscription. Parent components own
681
+ * the store subscription and pass derived data as props.
682
+ *
683
+ * Slots:
684
+ * body — free-form content below the title bar (copy, spinners, pickers, etc.)
685
+ * children — between detection rows and menu (extra info, warnings)
686
+ * errorView — replaces the entire body for fatal error states
687
+ */
688
+ const WizardTitle = ({ title }) => /* @__PURE__ */ jsxs(Text, {
689
+ bold: true,
690
+ children: [
691
+ /* @__PURE__ */ jsx(Text, {
692
+ color: "#1D4AFF",
693
+ children: "█"
694
+ }),
695
+ /* @__PURE__ */ jsx(Text, {
696
+ color: "#F54E00",
697
+ children: "█"
698
+ }),
699
+ /* @__PURE__ */ jsx(Text, {
700
+ color: "#F9BD2B",
701
+ children: "█"
702
+ }),
703
+ " ",
704
+ title
705
+ ]
706
+ });
707
+ const IntroScreenLayout = ({ installDir, title = "PostHog Wizard 🦔", showSubtitle = true, body, showDetection = true, detectionRows, children, menuOptions, onSelect, workflowLabel, skillId, errorView }) => {
708
+ const resolvedMenuOptions = menuOptions === void 0 ? [{
709
+ label: "Continue",
710
+ value: "continue"
711
+ }, {
712
+ label: "Cancel",
713
+ value: "cancel"
714
+ }] : menuOptions;
715
+ if (errorView) return /* @__PURE__ */ jsxs(Box, {
716
+ flexDirection: "column",
717
+ flexGrow: 1,
718
+ alignItems: "center",
719
+ justifyContent: "center",
720
+ children: [/* @__PURE__ */ jsx(Box, {
721
+ flexDirection: "column",
722
+ alignItems: "center",
723
+ marginBottom: 1,
724
+ children: /* @__PURE__ */ jsx(WizardTitle, { title })
725
+ }), errorView]
726
+ });
727
+ return /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsxs(Box, {
728
+ flexDirection: "column",
729
+ flexGrow: 1,
730
+ alignItems: "center",
731
+ justifyContent: "center",
732
+ children: [
733
+ /* @__PURE__ */ jsxs(Box, {
734
+ flexDirection: "column",
735
+ alignItems: "center",
736
+ children: [
737
+ /* @__PURE__ */ jsx(WizardTitle, { title }),
738
+ showSubtitle && /* @__PURE__ */ jsxs(Box, {
739
+ flexDirection: "column",
740
+ alignItems: "center",
741
+ marginTop: 1,
742
+ children: [/* @__PURE__ */ jsx(Text, {
743
+ dimColor: true,
744
+ children: "We'll use AI to analyze your project and complete work."
745
+ }), /* @__PURE__ */ jsx(Text, {
746
+ dimColor: true,
747
+ children: ".env* file contents will not leave your machine."
748
+ })]
749
+ }),
750
+ body && /* @__PURE__ */ jsx(Box, {
751
+ flexDirection: "column",
752
+ alignItems: "center",
753
+ marginTop: 1,
754
+ children: body
755
+ })
756
+ ]
757
+ }),
758
+ children,
759
+ showDetection && /* @__PURE__ */ jsxs(Box, {
760
+ flexDirection: "column",
761
+ marginTop: 1,
762
+ children: [
763
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, { children: [
764
+ "Directory ",
765
+ /* @__PURE__ */ jsx(Text, {
766
+ color: "green",
767
+ children: "✔"
768
+ }),
769
+ " "
770
+ ] }), /* @__PURE__ */ jsxs(Text, { children: ["/", path.basename(installDir)] })] }),
771
+ detectionRows?.map((row) => /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, { children: [
772
+ row.label,
773
+ " ",
774
+ /* @__PURE__ */ jsx(Text, {
775
+ color: "green",
776
+ children: "✔"
777
+ }),
778
+ " "
779
+ ] }), /* @__PURE__ */ jsxs(Text, { children: [row.value, row.suffix ? ` ${row.suffix}` : ""] })] }, row.label)),
780
+ workflowLabel && /* @__PURE__ */ jsxs(Text, { children: [
781
+ "Workflow",
782
+ " ",
783
+ /* @__PURE__ */ jsx(Text, {
784
+ color: "green",
785
+ children: "✔"
786
+ }),
787
+ " ",
788
+ workflowLabel
789
+ ] }),
790
+ workflowLabel === "agent-skill" && skillId && /* @__PURE__ */ jsxs(Text, { children: [
791
+ "Skill",
792
+ " ",
793
+ /* @__PURE__ */ jsx(Text, {
794
+ color: "green",
795
+ children: "✔"
796
+ }),
797
+ " ",
798
+ skillId
799
+ ] })
800
+ ]
801
+ }),
802
+ /* @__PURE__ */ jsx(Box, {
803
+ width: 24,
804
+ children: resolvedMenuOptions && onSelect && /* @__PURE__ */ jsx(Box, {
805
+ justifyContent: "center",
806
+ children: /* @__PURE__ */ jsx(PickerMenu, {
807
+ options: resolvedMenuOptions,
808
+ onSelect: (value) => {
809
+ onSelect(Array.isArray(value) ? value[0] : value);
810
+ }
811
+ }, resolvedMenuOptions.map((o) => o.value).join(","))
812
+ })
813
+ })
814
+ ]
815
+ }) });
816
+ };
817
+ //#endregion
818
+ //#region src/ui/tui/screens/SkillSourceInfo.tsx
819
+ /**
820
+ * Shared "Skill: <id> / URL: <downloadUrl>" block for intro screens.
821
+ *
822
+ * `useSkillEntry` fetches the entry from the skill menu and re-runs when
823
+ * `skillId` or `local` change. The previous fetch is cancelled (its result
824
+ * is ignored) so a session that flips `local=false → true` mid-mount picks
825
+ * up the right base URL.
826
+ *
827
+ * `<SkillSourceInfo>` renders the block, taking the entry as a prop so the
828
+ * caller can reuse the same hook result for additional UI (e.g. showing
829
+ * `skillEntry.name`) without invoking the hook twice.
830
+ */
831
+ function useSkillEntry(skillId, local) {
832
+ const [skillEntry, setSkillEntry] = useState(null);
833
+ const [fetchFailed, setFetchFailed] = useState(false);
834
+ useEffect(() => {
835
+ if (!skillId) {
836
+ setFetchFailed(true);
837
+ return;
838
+ }
839
+ let cancelled = false;
840
+ setSkillEntry(null);
841
+ setFetchFailed(false);
842
+ fetchSkillMenu(getSkillsBaseUrl(local)).then((menu) => {
843
+ if (cancelled) return;
844
+ if (!menu) {
845
+ setFetchFailed(true);
846
+ return;
847
+ }
848
+ const match = Object.values(menu.categories).flat().find((s) => s.id === skillId);
849
+ if (match) setSkillEntry(match);
850
+ else setFetchFailed(true);
851
+ });
852
+ return () => {
853
+ cancelled = true;
854
+ };
855
+ }, [skillId, local]);
856
+ return {
857
+ skillEntry,
858
+ fetchFailed
859
+ };
860
+ }
861
+ const SkillSourceInfo = ({ skillId, skillEntry, fetchFailed }) => /* @__PURE__ */ jsxs(Box, {
862
+ flexDirection: "column",
863
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
864
+ "Skill:",
865
+ " ",
866
+ /* @__PURE__ */ jsx(Text, {
867
+ italic: true,
868
+ color: "cyan",
869
+ children: skillId ?? "unknown"
870
+ })
871
+ ] }), /* @__PURE__ */ jsxs(Text, { children: [
872
+ "URL:",
873
+ " ",
874
+ /* @__PURE__ */ jsx(Text, {
875
+ color: "cyan",
876
+ children: skillEntry?.downloadUrl ?? (fetchFailed ? "unavailable" : "Loading...")
877
+ })
878
+ ] })]
879
+ });
880
+ //#endregion
881
+ //#region src/ui/tui/screens/PostHogIntegrationIntroScreen.tsx
882
+ /**
883
+ * PostHogIntegrationIntroScreen — Intro screen for the core PostHog integration.
884
+ *
885
+ * Composes IntroScreenLayout with framework-detection-specific state:
886
+ * 1. Detecting: spinner while detection runs
887
+ * 2. Detection failed: framework picker
888
+ * 3. Unsupported version: upgrade prompt
889
+ * 4. Detection succeeded: continue/change-framework/cancel
890
+ */
891
+ const TOOLS = [{
892
+ label: "Troubleshoot Integration",
893
+ command: "doctor"
894
+ }];
895
+ function launchTool(command, installDir) {
896
+ releaseTerminal();
897
+ const result = spawnSync(process.execPath, [
898
+ process.argv[1],
899
+ command,
900
+ `--install-dir=${installDir}`
901
+ ], { stdio: "inherit" });
902
+ process.exit(result.status ?? 0);
903
+ }
904
+ /** Framework picker shown when auto-detection fails. */
905
+ const FrameworkPicker = ({ store, onComplete }) => {
906
+ return /* @__PURE__ */ jsx(PickerMenu, {
907
+ centered: true,
908
+ columns: 2,
909
+ message: "Select your framework",
910
+ options: Object.values(Integration).map((value) => ({
911
+ label: value,
912
+ value
913
+ })),
914
+ onSelect: (value) => {
915
+ const integration = Array.isArray(value) ? value[0] : value;
916
+ import("./registry-BaMEaAKd.js").then((n) => n.n).then(({ FRAMEWORK_REGISTRY }) => {
917
+ const config = FRAMEWORK_REGISTRY[integration];
918
+ store.setFrameworkConfig(integration, config);
919
+ store.setDetectedFramework(config.metadata.name);
920
+ onComplete?.();
921
+ });
922
+ }
923
+ });
924
+ };
925
+ const PostHogIntegrationIntroScreen = ({ store }) => {
926
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
927
+ const [pickingFramework, setPickingFramework] = useState(false);
928
+ const [manuallySelected, setManuallySelected] = useState(false);
929
+ const [view, setView] = useState("default");
930
+ const [toolsEnabled, setToolsEnabled] = useState(false);
931
+ useEffect(() => {
932
+ let cancelled = false;
933
+ analytics.getAllFlagsForWizard().then((flags) => {
934
+ const value = flags[WIZARD_TOOLS_MENU_FLAG_KEY];
935
+ if (!cancelled && value && value !== "false") setToolsEnabled(true);
936
+ });
937
+ return () => {
938
+ cancelled = true;
939
+ };
940
+ }, []);
941
+ const { session } = store;
942
+ const config = session.frameworkConfig;
943
+ const frameworkLabel = session.detectedFrameworkLabel ?? config?.metadata.name;
944
+ const { skillEntry, fetchFailed } = useSkillEntry(session.skillId, session.localMcp);
945
+ const detecting = !session.detectionComplete;
946
+ const needsFrameworkPick = session.detectionComplete && !session.frameworkConfig;
947
+ const unsupported = session.unsupportedVersion;
948
+ const showContinue = session.frameworkConfig !== null && !detecting && !pickingFramework && view === "default" && !unsupported;
949
+ const title = detecting ? "PostHog Wizard starting up" : "PostHog Wizard 🦔";
950
+ let body = null;
951
+ if (detecting) body = /* @__PURE__ */ jsx(Box, {
952
+ marginY: 1,
953
+ children: /* @__PURE__ */ jsx(LoadingBox, { message: "Detecting project framework..." })
954
+ });
955
+ else if (needsFrameworkPick && !pickingFramework) body = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Box, {
956
+ marginY: 1,
957
+ children: /* @__PURE__ */ jsx(Text, {
958
+ dimColor: true,
959
+ children: "Could not auto-detect your framework."
960
+ })
961
+ }), /* @__PURE__ */ jsx(FrameworkPicker, {
962
+ store,
963
+ onComplete: () => setPickingFramework(false)
964
+ })] });
965
+ else if (pickingFramework) body = /* @__PURE__ */ jsx(FrameworkPicker, {
966
+ store,
967
+ onComplete: () => setPickingFramework(false)
968
+ });
969
+ else if (view === "more-info") body = /* @__PURE__ */ jsxs(Box, {
970
+ flexDirection: "column",
971
+ width: 56,
972
+ flexShrink: 0,
973
+ children: [
974
+ /* @__PURE__ */ jsxs(Text, { children: ["The wizard is an agent that executes PostHog tasks. Its code is open source: ", /* @__PURE__ */ jsx(Text, {
975
+ color: "cyan",
976
+ children: "https://github.com/PostHog/wizard"
977
+ })] }),
978
+ /* @__PURE__ */ jsx(Box, {
979
+ flexDirection: "column",
980
+ marginTop: 1,
981
+ children: /* @__PURE__ */ jsxs(Text, { children: [
982
+ "The",
983
+ " ",
984
+ /* @__PURE__ */ jsx(Text, {
985
+ italic: true,
986
+ color: "cyan",
987
+ children: session.workflowLabel
988
+ }),
989
+ " ",
990
+ "workflow installs the PostHog SDKs, instruments event tracking, and integrates the following dev tools for your application:"
991
+ ] })
992
+ }),
993
+ /* @__PURE__ */ jsxs(Box, {
994
+ flexDirection: "column",
995
+ marginTop: 1,
996
+ paddingLeft: 4,
997
+ children: [
998
+ /* @__PURE__ */ jsxs(Text, { children: [`\u2022`, " Product Analytics"] }),
999
+ /* @__PURE__ */ jsxs(Text, { children: [`\u2022`, " Web Analytics"] }),
1000
+ /* @__PURE__ */ jsxs(Text, { children: [`\u2022`, " Session Replay"] }),
1001
+ /* @__PURE__ */ jsxs(Text, { children: [`\u2022`, " Error Tracking"] })
1002
+ ]
1003
+ }),
1004
+ /* @__PURE__ */ jsxs(Box, {
1005
+ flexDirection: "column",
1006
+ marginTop: 1,
1007
+ children: [/* @__PURE__ */ jsx(Text, { children: "If you prefer your own AI setup, download the skill:" }), /* @__PURE__ */ jsx(Box, {
1008
+ marginTop: 1,
1009
+ children: /* @__PURE__ */ jsx(SkillSourceInfo, {
1010
+ skillId: session.skillId,
1011
+ skillEntry,
1012
+ fetchFailed
1013
+ })
1014
+ })]
1015
+ })
1016
+ ]
1017
+ });
1018
+ else if (showContinue) body = /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { children: "Let's do two hours of work in eight minutes." }) }) });
1019
+ const detectionRows = [];
1020
+ if (frameworkLabel) {
1021
+ const suffixParts = [];
1022
+ if (!manuallySelected) suffixParts.push("(detected)");
1023
+ if (config?.metadata.beta) suffixParts.push("[BETA]");
1024
+ detectionRows.push({
1025
+ label: "Framework",
1026
+ value: frameworkLabel,
1027
+ suffix: suffixParts.join(" ") || void 0
1028
+ });
1029
+ }
1030
+ let bodyChildren = null;
1031
+ if (config?.metadata.preRunNotice) bodyChildren = /* @__PURE__ */ jsx(Text, {
1032
+ color: "yellow",
1033
+ children: config.metadata.preRunNotice
1034
+ });
1035
+ if (unsupported) bodyChildren = /* @__PURE__ */ jsxs(Box, {
1036
+ flexDirection: "column",
1037
+ marginTop: 1,
1038
+ children: [
1039
+ /* @__PURE__ */ jsxs(Text, {
1040
+ color: "#DC9300",
1041
+ children: [
1042
+ "Version ",
1043
+ unsupported.current,
1044
+ " is not supported by the wizard. Please upgrade to ",
1045
+ unsupported.minimum,
1046
+ " or later."
1047
+ ]
1048
+ }),
1049
+ /* @__PURE__ */ jsxs(Text, {
1050
+ dimColor: true,
1051
+ children: ["Manual setup guide: ", unsupported.docsUrl]
1052
+ }),
1053
+ /* @__PURE__ */ jsx(Box, {
1054
+ marginTop: 1,
1055
+ children: /* @__PURE__ */ jsx(Text, {
1056
+ dimColor: true,
1057
+ children: "Did we get this wrong? You can also select another framework."
1058
+ })
1059
+ }),
1060
+ /* @__PURE__ */ jsx(PickerMenu, {
1061
+ options: [{
1062
+ label: "Select another framework",
1063
+ value: "framework"
1064
+ }, {
1065
+ label: "Exit",
1066
+ value: "exit"
1067
+ }],
1068
+ onSelect: (value) => {
1069
+ if ((Array.isArray(value) ? value[0] : value) === "framework") {
1070
+ setPickingFramework(true);
1071
+ setManuallySelected(true);
1072
+ } else process.exit(0);
1073
+ }
1074
+ })
1075
+ ]
1076
+ });
1077
+ let menuOptions = null;
1078
+ if (view === "tools") menuOptions = [...TOOLS.map((t) => ({
1079
+ label: t.label,
1080
+ value: t.command
1081
+ })), {
1082
+ label: "Back",
1083
+ value: "back"
1084
+ }];
1085
+ else if (view === "more-info") menuOptions = [{
1086
+ label: "Back",
1087
+ value: "back"
1088
+ }];
1089
+ else if (showContinue) menuOptions = [
1090
+ {
1091
+ label: "Continue",
1092
+ value: "continue"
1093
+ },
1094
+ {
1095
+ label: "Change framework",
1096
+ value: "framework"
1097
+ },
1098
+ ...toolsEnabled ? [{
1099
+ label: "Tools",
1100
+ value: "tools"
1101
+ }] : [],
1102
+ {
1103
+ label: "More info",
1104
+ value: "more-info"
1105
+ },
1106
+ {
1107
+ label: "Cancel",
1108
+ value: "cancel"
1109
+ }
1110
+ ];
1111
+ const handleSelect = (value) => {
1112
+ if (view === "tools") {
1113
+ if (value === "back") setView("default");
1114
+ else launchTool(value, session.installDir);
1115
+ return;
1116
+ }
1117
+ if (value === "cancel") process.exit(0);
1118
+ else if (value === "framework") {
1119
+ setPickingFramework(true);
1120
+ setManuallySelected(true);
1121
+ } else if (value === "more-info") setView("more-info");
1122
+ else if (value === "tools") setView("tools");
1123
+ else if (value === "back") setView("default");
1124
+ else store.completeSetup();
1125
+ };
1126
+ return /* @__PURE__ */ jsx(IntroScreenLayout, {
1127
+ installDir: session.installDir,
1128
+ title,
1129
+ showSubtitle: view === "default",
1130
+ body,
1131
+ showDetection: showContinue,
1132
+ detectionRows,
1133
+ menuOptions: unsupported ? null : menuOptions,
1134
+ onSelect: handleSelect,
1135
+ workflowLabel: session.workflowLabel,
1136
+ skillId: session.skillId,
1137
+ children: bodyChildren
1138
+ });
1139
+ };
1140
+ //#endregion
1141
+ //#region src/ui/tui/screens/RevenueIntroScreen.tsx
1142
+ /**
1143
+ * RevenueIntroScreen — Welcome screen for the revenue analytics flow.
1144
+ *
1145
+ * Composes IntroScreenLayout with SDK-detection-specific state:
1146
+ * - Detection succeeded: shows detected PostHog + Stripe SDKs, continue/cancel
1147
+ * - Detection failed: shows the error via errorView + exit prompt
1148
+ *
1149
+ * Reads `frameworkContext.detectError` and `frameworkContext.detectedPosthogSdks`
1150
+ * / `detectedStripeSdks` set by detectRevenuePrerequisites().
1151
+ */
1152
+ const RevenueIntroScreen = ({ store }) => {
1153
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
1154
+ const [showingMoreInfo, setShowingMoreInfo] = useState(false);
1155
+ const { session } = store;
1156
+ const detectError = session.frameworkContext.detectError;
1157
+ const detectedPosthogSdks = session.frameworkContext.detectedPosthogSdks ?? [];
1158
+ const detectedStripeSdks = session.frameworkContext.detectedStripeSdks ?? [];
1159
+ const detectedPackagePaths = session.frameworkContext.detectedPackagePaths ?? [];
1160
+ const detectionRows = [];
1161
+ if (detectedPosthogSdks.length > 0) detectionRows.push({
1162
+ label: "PostHog SDK",
1163
+ value: detectedPosthogSdks.join(", ")
1164
+ });
1165
+ if (detectedStripeSdks.length > 0) detectionRows.push({
1166
+ label: "Stripe SDK",
1167
+ value: detectedStripeSdks.join(", ")
1168
+ });
1169
+ const body = showingMoreInfo ? /* @__PURE__ */ jsxs(Box, {
1170
+ flexDirection: "column",
1171
+ width: 56,
1172
+ flexShrink: 0,
1173
+ children: [
1174
+ /* @__PURE__ */ jsxs(Text, { children: [
1175
+ "The wizard is an agent that executes PostHog tasks. Its code is open source: ",
1176
+ /* @__PURE__ */ jsx(Text, {
1177
+ color: "cyan",
1178
+ children: "https://github.com/PostHog/wizard"
1179
+ }),
1180
+ "."
1181
+ ] }),
1182
+ /* @__PURE__ */ jsx(Box, {
1183
+ flexDirection: "column",
1184
+ marginTop: 1,
1185
+ children: /* @__PURE__ */ jsxs(Text, { children: [
1186
+ "The",
1187
+ " ",
1188
+ /* @__PURE__ */ jsx(Text, {
1189
+ italic: true,
1190
+ color: "cyan",
1191
+ children: session.workflowLabel
1192
+ }),
1193
+ " ",
1194
+ "workflow links Stripe customers and purchases to PostHog product data and persons. It unlocks insights like:"
1195
+ ] })
1196
+ }),
1197
+ /* @__PURE__ */ jsxs(Box, {
1198
+ flexDirection: "column",
1199
+ marginTop: 1,
1200
+ paddingLeft: 4,
1201
+ children: [
1202
+ /* @__PURE__ */ jsxs(Text, { children: ["•", " Revenue per user"] }),
1203
+ /* @__PURE__ */ jsxs(Text, { children: ["•", " Lifetime value"] }),
1204
+ /* @__PURE__ */ jsxs(Text, { children: ["•", " MRR / churn tracking"] })
1205
+ ]
1206
+ })
1207
+ ]
1208
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Box, {
1209
+ flexDirection: "column",
1210
+ alignItems: "center",
1211
+ children: [/* @__PURE__ */ jsx(Text, { children: "Let's create revenue analytics with Stripe and PostHog." }), /* @__PURE__ */ jsx(Box, {
1212
+ flexDirection: "column",
1213
+ marginTop: 1,
1214
+ children: /* @__PURE__ */ jsx(Text, { children: "Link purchases to product data." })
1215
+ })]
1216
+ }), detectedPackagePaths.length > 1 && /* @__PURE__ */ jsxs(Box, {
1217
+ flexDirection: "column",
1218
+ marginTop: 1,
1219
+ children: [/* @__PURE__ */ jsxs(Text, {
1220
+ dimColor: true,
1221
+ children: [
1222
+ "Found in ",
1223
+ detectedPackagePaths.length,
1224
+ " packages:"
1225
+ ]
1226
+ }), detectedPackagePaths.map((p) => /* @__PURE__ */ jsxs(Text, {
1227
+ dimColor: true,
1228
+ children: [
1229
+ " ",
1230
+ "•",
1231
+ " ",
1232
+ p
1233
+ ]
1234
+ }, p))]
1235
+ })] });
1236
+ const errorView = detectError ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Box, {
1237
+ flexDirection: "column",
1238
+ marginBottom: 1,
1239
+ children: [/* @__PURE__ */ jsxs(Text, {
1240
+ color: "red",
1241
+ bold: true,
1242
+ children: ["✘", " Cannot set up revenue analytics"]
1243
+ }), /* @__PURE__ */ jsx(Box, {
1244
+ marginTop: 1,
1245
+ flexDirection: "column",
1246
+ children: /* @__PURE__ */ jsx(DetectErrorBody, { error: detectError })
1247
+ })]
1248
+ }), /* @__PURE__ */ jsx(PickerMenu, {
1249
+ options: [{
1250
+ label: "Exit",
1251
+ value: "exit"
1252
+ }],
1253
+ onSelect: () => process.exit(1)
1254
+ })] }) : void 0;
1255
+ const menuOptions = showingMoreInfo ? [{
1256
+ label: "Back",
1257
+ value: "back"
1258
+ }] : [
1259
+ {
1260
+ label: "Continue",
1261
+ value: "continue"
1262
+ },
1263
+ {
1264
+ label: "More info",
1265
+ value: "more-info"
1266
+ },
1267
+ {
1268
+ label: "Cancel",
1269
+ value: "cancel"
1270
+ }
1271
+ ];
1272
+ return /* @__PURE__ */ jsx(IntroScreenLayout, {
1273
+ installDir: session.installDir,
1274
+ showSubtitle: !showingMoreInfo,
1275
+ body,
1276
+ showDetection: !showingMoreInfo,
1277
+ detectionRows,
1278
+ errorView,
1279
+ workflowLabel: session.workflowLabel,
1280
+ skillId: session.skillId,
1281
+ menuOptions,
1282
+ onSelect: (value) => {
1283
+ if (value === "cancel") process.exit(0);
1284
+ else if (value === "more-info") setShowingMoreInfo(true);
1285
+ else if (value === "back") setShowingMoreInfo(false);
1286
+ else store.completeSetup();
1287
+ }
1288
+ });
1289
+ };
1290
+ const DetectErrorBody = ({ error }) => {
1291
+ switch (error.kind) {
1292
+ case "bad-directory": {
1293
+ const reasonText = {
1294
+ missing: "does not exist",
1295
+ "not-dir": "is not a directory",
1296
+ unreadable: "could not be accessed"
1297
+ }[error.reason];
1298
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Text, { children: [
1299
+ "This path ",
1300
+ reasonText,
1301
+ ":"
1302
+ ] }), /* @__PURE__ */ jsxs(Text, {
1303
+ dimColor: true,
1304
+ children: [" ", error.path]
1305
+ })] });
1306
+ }
1307
+ case "no-package-json": return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1308
+ /* @__PURE__ */ jsx(Text, { children: "No package.json found in this directory." }),
1309
+ /* @__PURE__ */ jsx(Text, {
1310
+ dimColor: true,
1311
+ children: "Revenue analytics currently supports Node.js / TypeScript projects."
1312
+ }),
1313
+ /* @__PURE__ */ jsx(Text, {
1314
+ dimColor: true,
1315
+ children: "Run this command from your project root."
1316
+ })
1317
+ ] });
1318
+ case "no-sdks": return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1319
+ /* @__PURE__ */ jsxs(Text, { children: [
1320
+ "Neither PostHog nor Stripe SDKs detected (scanned",
1321
+ " ",
1322
+ error.scannedCount,
1323
+ " package.json file",
1324
+ error.scannedCount === 1 ? "" : "s",
1325
+ ")."
1326
+ ] }),
1327
+ /* @__PURE__ */ jsxs(Box, {
1328
+ marginTop: 1,
1329
+ flexDirection: "column",
1330
+ children: [
1331
+ /* @__PURE__ */ jsx(Text, { children: "Revenue analytics requires:" }),
1332
+ /* @__PURE__ */ jsxs(Text, {
1333
+ dimColor: true,
1334
+ children: [
1335
+ " •",
1336
+ " A PostHog SDK (",
1337
+ POSTHOG_SDKS.slice(0, 3).join(", "),
1338
+ ", …)"
1339
+ ]
1340
+ }),
1341
+ /* @__PURE__ */ jsxs(Text, {
1342
+ dimColor: true,
1343
+ children: [
1344
+ " •",
1345
+ " A Stripe SDK (",
1346
+ STRIPE_SDKS.join(", "),
1347
+ ")"
1348
+ ]
1349
+ })
1350
+ ]
1351
+ }),
1352
+ /* @__PURE__ */ jsx(Box, {
1353
+ marginTop: 1,
1354
+ children: /* @__PURE__ */ jsxs(Text, {
1355
+ dimColor: true,
1356
+ children: [
1357
+ "Install Stripe and run ",
1358
+ /* @__PURE__ */ jsx(Text, {
1359
+ bold: true,
1360
+ children: "npx @posthog/wizard"
1361
+ }),
1362
+ " to set up PostHog."
1363
+ ]
1364
+ })
1365
+ })
1366
+ ] });
1367
+ case "missing-posthog": return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Text, { children: [
1368
+ "Found Stripe (",
1369
+ error.foundStripe.join(", "),
1370
+ ") but no PostHog SDK."
1371
+ ] }), /* @__PURE__ */ jsx(Box, {
1372
+ marginTop: 1,
1373
+ children: /* @__PURE__ */ jsxs(Text, {
1374
+ dimColor: true,
1375
+ children: [
1376
+ "Run ",
1377
+ /* @__PURE__ */ jsx(Text, {
1378
+ bold: true,
1379
+ children: "npx @posthog/wizard"
1380
+ }),
1381
+ " first to set up the base PostHog integration."
1382
+ ]
1383
+ })
1384
+ })] });
1385
+ case "missing-stripe": return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1386
+ /* @__PURE__ */ jsxs(Text, { children: [
1387
+ "Found PostHog (",
1388
+ error.foundPosthog.join(", "),
1389
+ ") but no Stripe SDK."
1390
+ ] }),
1391
+ /* @__PURE__ */ jsx(Text, {
1392
+ dimColor: true,
1393
+ children: "Revenue analytics currently supports Stripe only."
1394
+ }),
1395
+ /* @__PURE__ */ jsxs(Box, {
1396
+ marginTop: 1,
1397
+ flexDirection: "column",
1398
+ children: [/* @__PURE__ */ jsx(Text, {
1399
+ dimColor: true,
1400
+ children: "Install one of:"
1401
+ }), STRIPE_SDKS.map((sdk) => /* @__PURE__ */ jsxs(Text, {
1402
+ dimColor: true,
1403
+ children: [
1404
+ " •",
1405
+ " ",
1406
+ sdk
1407
+ ]
1408
+ }, sdk))]
1409
+ })
1410
+ ] });
1411
+ }
1412
+ };
1413
+ //#endregion
1414
+ //#region src/ui/tui/screens/AgentSkillIntroScreen.tsx
1415
+ /**
1416
+ * AgentSkillIntroScreen — Default intro for generic agent-skill workflows.
1417
+ *
1418
+ * Workflows that need a different intro ship their own screen component
1419
+ * (see audit/AuditIntroScreen.tsx).
1420
+ */
1421
+ const AgentSkillIntroScreen = ({ store }) => {
1422
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
1423
+ const [showingMoreInfo, setShowingMoreInfo] = useState(false);
1424
+ const { session } = store;
1425
+ const skillId = session.skillId ?? "unknown";
1426
+ const { skillEntry, fetchFailed } = useSkillEntry(skillId, session.localMcp);
1427
+ let body;
1428
+ if (showingMoreInfo) body = /* @__PURE__ */ jsxs(Box, {
1429
+ flexDirection: "column",
1430
+ width: 56,
1431
+ flexShrink: 0,
1432
+ children: [
1433
+ /* @__PURE__ */ jsx(Box, {
1434
+ flexDirection: "column",
1435
+ marginBottom: 1,
1436
+ children: /* @__PURE__ */ jsxs(Text, { children: ["The wizard is an agent that executes PostHog tasks. Its code is open source: ", /* @__PURE__ */ jsx(Text, {
1437
+ color: "cyan",
1438
+ children: "https://github.com/PostHog/wizard"
1439
+ })] })
1440
+ }),
1441
+ /* @__PURE__ */ jsx(SkillSourceInfo, {
1442
+ skillId,
1443
+ skillEntry,
1444
+ fetchFailed
1445
+ }),
1446
+ /* @__PURE__ */ jsx(Box, {
1447
+ marginTop: 1,
1448
+ children: /* @__PURE__ */ jsx(Text, {
1449
+ dimColor: true,
1450
+ children: skillEntry?.name ?? (fetchFailed ? skillId : "Loading...")
1451
+ })
1452
+ })
1453
+ ]
1454
+ });
1455
+ else body = /* @__PURE__ */ jsxs(Text, { children: [
1456
+ "Let's run the",
1457
+ " ",
1458
+ /* @__PURE__ */ jsx(Text, {
1459
+ italic: true,
1460
+ color: "cyan",
1461
+ children: skillId
1462
+ }),
1463
+ " ",
1464
+ "skill."
1465
+ ] });
1466
+ const menuOptions = showingMoreInfo ? [{
1467
+ label: "Back",
1468
+ value: "back"
1469
+ }] : [
1470
+ {
1471
+ label: "Continue",
1472
+ value: "continue"
1473
+ },
1474
+ {
1475
+ label: "More info",
1476
+ value: "more-info"
1477
+ },
1478
+ {
1479
+ label: "Cancel",
1480
+ value: "cancel"
1481
+ }
1482
+ ];
1483
+ const handleSelect = (value) => {
1484
+ if (value === "cancel") process.exit(0);
1485
+ else if (value === "more-info") setShowingMoreInfo(true);
1486
+ else if (value === "back") setShowingMoreInfo(false);
1487
+ else store.completeSetup();
1488
+ };
1489
+ return /* @__PURE__ */ jsx(IntroScreenLayout, {
1490
+ installDir: session.installDir,
1491
+ showSubtitle: !showingMoreInfo,
1492
+ body,
1493
+ showDetection: !showingMoreInfo,
1494
+ workflowLabel: session.workflowLabel,
1495
+ skillId: session.skillId,
1496
+ menuOptions,
1497
+ onSelect: handleSelect
1498
+ });
1499
+ };
1500
+ //#endregion
1501
+ //#region src/ui/tui/screens/audit/AuditIntroScreen.tsx
1502
+ const AUDIT_SKILL_ID = "audit";
1503
+ const AuditIntroScreen = ({ store }) => {
1504
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
1505
+ const [showingMoreInfo, setShowingMoreInfo] = useState(false);
1506
+ const { session } = store;
1507
+ const { skillEntry, fetchFailed } = useSkillEntry(AUDIT_SKILL_ID, session.localMcp);
1508
+ const body = showingMoreInfo ? /* @__PURE__ */ jsxs(Box, {
1509
+ flexDirection: "column",
1510
+ width: 56,
1511
+ children: [
1512
+ /* @__PURE__ */ jsx(Box, {
1513
+ marginBottom: 1,
1514
+ children: /* @__PURE__ */ jsxs(Text, { children: ["The wizard is an agent that executes PostHog tasks. Its code is open source: ", /* @__PURE__ */ jsx(Text, {
1515
+ color: "cyan",
1516
+ children: "https://github.com/PostHog/wizard"
1517
+ })] })
1518
+ }),
1519
+ /* @__PURE__ */ jsxs(Text, { children: [
1520
+ "The",
1521
+ " ",
1522
+ /* @__PURE__ */ jsx(Text, {
1523
+ color: "cyan",
1524
+ italic: true,
1525
+ children: AUDIT_SKILL_ID
1526
+ }),
1527
+ " ",
1528
+ "workflow reviews your project's PostHog integration against best practices to help you capture high-quality events and writes a report for suggested actions. Nothing in your project will be modified."
1529
+ ] }),
1530
+ /* @__PURE__ */ jsx(Box, {
1531
+ marginTop: 1,
1532
+ children: /* @__PURE__ */ jsx(SkillSourceInfo, {
1533
+ skillId: AUDIT_SKILL_ID,
1534
+ skillEntry,
1535
+ fetchFailed
1536
+ })
1537
+ })
1538
+ ]
1539
+ }) : /* @__PURE__ */ jsx(Box, {
1540
+ flexDirection: "column",
1541
+ alignItems: "center",
1542
+ children: /* @__PURE__ */ jsx(Text, { children: "Let's review your existing PostHog setup for best practices." })
1543
+ });
1544
+ const menuOptions = showingMoreInfo ? [{
1545
+ label: "Back",
1546
+ value: "back"
1547
+ }] : [
1548
+ {
1549
+ label: "Continue",
1550
+ value: "continue"
1551
+ },
1552
+ {
1553
+ label: "More info",
1554
+ value: "more-info"
1555
+ },
1556
+ {
1557
+ label: "Cancel",
1558
+ value: "cancel"
1559
+ }
1560
+ ];
1561
+ const handleSelect = (value) => {
1562
+ if (value === "cancel") process.exit(0);
1563
+ else if (value === "more-info") setShowingMoreInfo(true);
1564
+ else if (value === "back") setShowingMoreInfo(false);
1565
+ else store.completeSetup();
1566
+ };
1567
+ return /* @__PURE__ */ jsx(IntroScreenLayout, {
1568
+ installDir: session.installDir,
1569
+ body,
1570
+ showDetection: !showingMoreInfo,
1571
+ workflowLabel: session.workflowLabel,
1572
+ skillId: session.skillId,
1573
+ menuOptions,
1574
+ onSelect: handleSelect
1575
+ });
1576
+ };
1577
+ //#endregion
1578
+ //#region src/ui/tui/hooks/file-watcher.ts
1579
+ /**
1580
+ * File watcher (UI concern).
1581
+ *
1582
+ * `fs.watch` alone is unreliable for atomic-rename writes (which is how Claude
1583
+ * rewrites JSON files), so the watcher pairs `fs.watch` with a continuous
1584
+ * mtime-polled re-read. The poll catches missed events; the watch keeps
1585
+ * latency low when it does fire. We dedupe with `mtimeMs` so steady-state
1586
+ * polls cost a single `stat`.
1587
+ *
1588
+ * Screens that need to mirror an agent-emitted JSON file into the store call
1589
+ * `useFileWatcher(absolutePath, onUpdate)`; the watcher starts on mount and
1590
+ * tears down on unmount.
1591
+ */
1592
+ const DEFAULT_POLL_INTERVAL_MS = 5e3;
1593
+ const DEFAULT_ATTACH_RETRY_INTERVAL_MS = 1e3;
1594
+ /** Watch `path` for JSON updates and call `onUpdate(parsed)` whenever the
1595
+ * file's mtime changes and the contents are valid JSON. Caller must invoke
1596
+ * `handle.stop()` to release the watcher. */
1597
+ function startFileWatcher(path, onUpdate, options = {}) {
1598
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
1599
+ const attachRetryIntervalMs = options.attachRetryIntervalMs ?? DEFAULT_ATTACH_RETRY_INTERVAL_MS;
1600
+ const watchers = [];
1601
+ const intervals = [];
1602
+ let lastMtimeMs = 0;
1603
+ const read = (force = false) => {
1604
+ try {
1605
+ const stat = fs$1.statSync(path);
1606
+ if (!force && stat.mtimeMs === lastMtimeMs) return;
1607
+ lastMtimeMs = stat.mtimeMs;
1608
+ onUpdate(JSON.parse(fs$1.readFileSync(path, "utf-8")));
1609
+ } catch {}
1610
+ };
1611
+ intervals.push(setInterval(() => read(), pollIntervalMs));
1612
+ try {
1613
+ watchers.push(fs$1.watch(path, () => read(true)));
1614
+ read(true);
1615
+ } catch {
1616
+ const attachInterval = setInterval(() => {
1617
+ try {
1618
+ fs$1.accessSync(path);
1619
+ clearInterval(attachInterval);
1620
+ const idx = intervals.indexOf(attachInterval);
1621
+ if (idx >= 0) intervals.splice(idx, 1);
1622
+ watchers.push(fs$1.watch(path, () => read(true)));
1623
+ } catch {}
1624
+ }, attachRetryIntervalMs);
1625
+ intervals.push(attachInterval);
1626
+ }
1627
+ return { stop() {
1628
+ for (const w of watchers) w.close();
1629
+ for (const i of intervals) clearInterval(i);
1630
+ } };
1631
+ }
1632
+ /** React hook wrapping `startFileWatcher`. Starts on mount, stops on unmount
1633
+ * or when `path` changes. `onUpdate` and `options` are captured at mount
1634
+ * time — change `path` to restart with a new callback. */
1635
+ function useFileWatcher(path, onUpdate, options = {}) {
1636
+ useEffect(() => {
1637
+ const handle = startFileWatcher(path, onUpdate, options);
1638
+ return () => handle.stop();
1639
+ }, [path]);
1640
+ }
1641
+ //#endregion
1642
+ //#region src/ui/tui/screens/audit/slides/shared.tsx
1643
+ /** Narrow bordered box for the small ASCII illustrations in baseline slides. */
1644
+ const VisualBox = ({ children }) => /* @__PURE__ */ jsx(Box, {
1645
+ borderStyle: "single",
1646
+ borderColor: Colors.muted,
1647
+ paddingX: 1,
1648
+ flexDirection: "column",
1649
+ marginBottom: 1,
1650
+ children
1651
+ });
1652
+ //#endregion
1653
+ //#region src/ui/tui/screens/audit/slides/installation.tsx
1654
+ const InstallationVisual = () => /* @__PURE__ */ jsxs(VisualBox, { children: [
1655
+ /* @__PURE__ */ jsx(Text, {
1656
+ dimColor: true,
1657
+ children: "app boot"
1658
+ }),
1659
+ /* @__PURE__ */ jsxs(Text, { children: [
1660
+ /* @__PURE__ */ jsx(Text, {
1661
+ dimColor: true,
1662
+ children: " ▼ "
1663
+ }),
1664
+ /* @__PURE__ */ jsx(Text, {
1665
+ color: "green",
1666
+ children: "posthog.init(...)"
1667
+ }),
1668
+ /* @__PURE__ */ jsx(Text, {
1669
+ dimColor: true,
1670
+ children: " once"
1671
+ })
1672
+ ] }),
1673
+ /* @__PURE__ */ jsx(Text, {
1674
+ dimColor: true,
1675
+ children: " │"
1676
+ }),
1677
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
1678
+ dimColor: true,
1679
+ children: " ▼ "
1680
+ }), /* @__PURE__ */ jsx(Text, {
1681
+ color: "cyan",
1682
+ children: "posthog.capture('pageview')"
1683
+ })] }),
1684
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
1685
+ dimColor: true,
1686
+ children: " "
1687
+ }), /* @__PURE__ */ jsx(Text, {
1688
+ color: "cyan",
1689
+ children: "posthog.capture('signup')"
1690
+ })] }),
1691
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
1692
+ dimColor: true,
1693
+ children: " "
1694
+ }), /* @__PURE__ */ jsx(Text, {
1695
+ color: "cyan",
1696
+ children: "posthog.capture('purchase')"
1697
+ })] })
1698
+ ] });
1699
+ const InstallationSlide = {
1700
+ area: "Installation",
1701
+ intro: [
1702
+ "PostHog releases frequent SDK updates to fix bugs and add new features. We're checking your project's SDK version and making sure it's up to date.",
1703
+ "We're also checking that your SDK is initialized correctly and in the right part of your app's lifecycle.",
1704
+ "This ensures you won't miss any autocaptured events."
1705
+ ],
1706
+ visual: /* @__PURE__ */ jsx(InstallationVisual, {}),
1707
+ docsUrl: "https://posthog.com/docs/getting-started/install"
1708
+ };
1709
+ //#endregion
1710
+ //#region src/ui/tui/screens/audit/slides/identification.tsx
1711
+ const IdentificationVisual = () => /* @__PURE__ */ jsxs(VisualBox, { children: [
1712
+ /* @__PURE__ */ jsxs(Text, { children: [
1713
+ /* @__PURE__ */ jsx(Text, {
1714
+ bold: true,
1715
+ children: "browser "
1716
+ }),
1717
+ /* @__PURE__ */ jsx(Text, {
1718
+ dimColor: true,
1719
+ children: "capture"
1720
+ }),
1721
+ /* @__PURE__ */ jsx(Text, { children: " (" }),
1722
+ /* @__PURE__ */ jsx(Text, {
1723
+ color: "cyan",
1724
+ children: "u_42"
1725
+ }),
1726
+ /* @__PURE__ */ jsx(Text, { children: ", \"click\")" })
1727
+ ] }),
1728
+ /* @__PURE__ */ jsx(Text, {
1729
+ dimColor: true,
1730
+ children: " │"
1731
+ }),
1732
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
1733
+ dimColor: true,
1734
+ children: " ▼ "
1735
+ }), /* @__PURE__ */ jsx(Text, {
1736
+ color: "green",
1737
+ children: "same distinct_id"
1738
+ })] }),
1739
+ /* @__PURE__ */ jsx(Text, {
1740
+ dimColor: true,
1741
+ children: " │"
1742
+ }),
1743
+ /* @__PURE__ */ jsxs(Text, { children: [
1744
+ /* @__PURE__ */ jsx(Text, {
1745
+ bold: true,
1746
+ children: "server "
1747
+ }),
1748
+ /* @__PURE__ */ jsx(Text, {
1749
+ dimColor: true,
1750
+ children: "capture"
1751
+ }),
1752
+ /* @__PURE__ */ jsx(Text, { children: " (" }),
1753
+ /* @__PURE__ */ jsx(Text, {
1754
+ color: "cyan",
1755
+ children: "u_42"
1756
+ }),
1757
+ /* @__PURE__ */ jsx(Text, { children: ", \"charged\")" })
1758
+ ] })
1759
+ ] });
1760
+ const IdentificationSlide = {
1761
+ area: "Identification",
1762
+ intro: [
1763
+ "For events to be useful, they need to be reliably attributed to a user.",
1764
+ "We're checking your project's `identify()` calls to make sure they're correctly and consistently implemented.",
1765
+ "We're also checking that your `distinct_id`s are correctly passed between your client and server runtimes if applicable."
1766
+ ],
1767
+ visual: /* @__PURE__ */ jsx(IdentificationVisual, {}),
1768
+ docsUrl: "https://posthog.com/docs/product-analytics/identify"
1769
+ };
1770
+ //#endregion
1771
+ //#region src/ui/tui/screens/audit/slides/eventCapture.tsx
1772
+ const CaptureVisual = () => /* @__PURE__ */ jsxs(VisualBox, { children: [
1773
+ /* @__PURE__ */ jsxs(Text, { children: [
1774
+ /* @__PURE__ */ jsx(Text, {
1775
+ color: "cyan",
1776
+ children: "pageview "
1777
+ }),
1778
+ /* @__PURE__ */ jsx(Text, {
1779
+ color: "green",
1780
+ children: "████████████"
1781
+ }),
1782
+ /* @__PURE__ */ jsx(Text, {
1783
+ dimColor: true,
1784
+ children: " 1000"
1785
+ })
1786
+ ] }),
1787
+ /* @__PURE__ */ jsxs(Text, { children: [
1788
+ /* @__PURE__ */ jsx(Text, {
1789
+ color: "cyan",
1790
+ children: "signup "
1791
+ }),
1792
+ /* @__PURE__ */ jsx(Text, {
1793
+ color: "green",
1794
+ children: "████████"
1795
+ }),
1796
+ /* @__PURE__ */ jsx(Text, {
1797
+ dimColor: true,
1798
+ children: " 640"
1799
+ })
1800
+ ] }),
1801
+ /* @__PURE__ */ jsxs(Text, { children: [
1802
+ /* @__PURE__ */ jsx(Text, {
1803
+ color: "cyan",
1804
+ children: "activated "
1805
+ }),
1806
+ /* @__PURE__ */ jsx(Text, {
1807
+ color: "green",
1808
+ children: "█████"
1809
+ }),
1810
+ /* @__PURE__ */ jsx(Text, {
1811
+ dimColor: true,
1812
+ children: " 410"
1813
+ })
1814
+ ] }),
1815
+ /* @__PURE__ */ jsxs(Text, { children: [
1816
+ /* @__PURE__ */ jsx(Text, {
1817
+ color: "cyan",
1818
+ children: "purchased "
1819
+ }),
1820
+ /* @__PURE__ */ jsx(Text, {
1821
+ color: "green",
1822
+ children: "██"
1823
+ }),
1824
+ /* @__PURE__ */ jsx(Text, {
1825
+ dimColor: true,
1826
+ children: " 120"
1827
+ })
1828
+ ] })
1829
+ ] });
1830
+ const EventCaptureSlide = {
1831
+ area: "Event Capture",
1832
+ intro: [
1833
+ "Everything you do in PostHog starts with event captures. Every dashboard, insight, funnel, cohort, and replay is built on top of events.",
1834
+ "We're checking that your project's event capture calls cover key user actions and use sensible event names, so you can build high-quality insights and reports.",
1835
+ "We're also checking that you use a reverse proxy so your events are not blocked by ad blockers or tracking blockers."
1836
+ ],
1837
+ visual: /* @__PURE__ */ jsx(CaptureVisual, {}),
1838
+ docsUrl: "https://posthog.com/docs/product-analytics/capture-events"
1839
+ };
1840
+ //#endregion
1841
+ //#region src/ui/tui/screens/audit/slides/index.ts
1842
+ const AUDIT_AREA_SLIDES = [
1843
+ InstallationSlide,
1844
+ IdentificationSlide,
1845
+ EventCaptureSlide
1846
+ ];
1847
+ //#endregion
1848
+ //#region src/ui/tui/screens/audit/AuditAreaPane.tsx
1849
+ /**
1850
+ * AuditAreaPane — left-pane slide that follows whatever area the agent is
1851
+ * currently checking, plus a wrap-up state once every check is resolved
1852
+ * and the agent has moved on to writing the report.
1853
+ *
1854
+ * Three states, gated top-down on the ledger:
1855
+ * 1. firstPending defined → render the slide for that area
1856
+ * 2. checks empty → blank (the seed hook fires before
1857
+ * this screen mounts in practice;
1858
+ * this is just defensive)
1859
+ * 3. all checks non-pending → "writing report" wrap-up
1860
+ *
1861
+ * Pressing `O` opens the active slide's docs URL.
1862
+ */
1863
+ const FINDING_STATUSES$1 = [
1864
+ "error",
1865
+ "warning",
1866
+ "suggestion"
1867
+ ];
1868
+ const isFinding$1 = (c) => FINDING_STATUSES$1.includes(c.status);
1869
+ const fallbackSlide$1 = (area) => ({
1870
+ area,
1871
+ intro: [`Verifying ${area.toLowerCase()}…`],
1872
+ docsUrl: ""
1873
+ });
1874
+ const openLink$1 = (url) => {
1875
+ spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open", process.platform === "win32" ? [
1876
+ "/c",
1877
+ "start",
1878
+ "",
1879
+ url
1880
+ ] : [url], {
1881
+ detached: true,
1882
+ stdio: "ignore"
1883
+ }).unref();
1884
+ };
1885
+ const AuditAreaPane = ({ checks, reportPath }) => {
1886
+ const activeArea = checks.filter((c) => c.status === "pending")[0]?.area;
1887
+ const slide = activeArea ? AUDIT_AREA_SLIDES.find((s) => s.area === activeArea) ?? fallbackSlide$1(activeArea) : null;
1888
+ useInput((input) => {
1889
+ if (input.toLowerCase() === "o" && slide?.docsUrl) openLink$1(slide.docsUrl);
1890
+ });
1891
+ if (slide) return /* @__PURE__ */ jsx(ActiveSlide$1, {
1892
+ slide,
1893
+ hasFindings: checks.some(isFinding$1)
1894
+ });
1895
+ if (checks.length === 0) return null;
1896
+ return /* @__PURE__ */ jsx(WritingReport$1, { reportPath });
1897
+ };
1898
+ const ActiveSlide$1 = ({ slide, hasFindings }) => /* @__PURE__ */ jsxs(Box, {
1899
+ flexDirection: "column",
1900
+ paddingX: 1,
1901
+ children: [
1902
+ /* @__PURE__ */ jsxs(Text, {
1903
+ bold: true,
1904
+ color: Colors.accent,
1905
+ children: ["Verifying ", slide.area.toLowerCase()]
1906
+ }),
1907
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
1908
+ slide.visual,
1909
+ slide.intro.map((paragraph, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 && /* @__PURE__ */ jsx(Box, { height: 1 }), /* @__PURE__ */ jsx(Text, { children: paragraph })] }, i)),
1910
+ /* @__PURE__ */ jsx(Box, {
1911
+ marginTop: 1,
1912
+ children: /* @__PURE__ */ jsxs(Text, {
1913
+ dimColor: true,
1914
+ children: [slide.docsUrl && /* @__PURE__ */ jsxs(Fragment$1, { children: [
1915
+ "[",
1916
+ /* @__PURE__ */ jsx(Text, {
1917
+ color: Colors.accent,
1918
+ children: "O"
1919
+ }),
1920
+ "] Learn more"
1921
+ ] }), hasFindings && /* @__PURE__ */ jsxs(Fragment$1, { children: [
1922
+ slide.docsUrl && " ",
1923
+ "[",
1924
+ /* @__PURE__ */ jsx(Text, {
1925
+ color: Colors.accent,
1926
+ children: "→"
1927
+ }),
1928
+ "] View issues"
1929
+ ] })]
1930
+ })
1931
+ })
1932
+ ]
1933
+ });
1934
+ const WritingReport$1 = ({ reportPath }) => /* @__PURE__ */ jsxs(Box, {
1935
+ flexDirection: "column",
1936
+ paddingX: 1,
1937
+ children: [
1938
+ /* @__PURE__ */ jsx(Text, {
1939
+ bold: true,
1940
+ color: Colors.accent,
1941
+ children: "We've wrapped up the review."
1942
+ }),
1943
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
1944
+ /* @__PURE__ */ jsxs(Text, { children: [
1945
+ "To help you get the most out of your PostHog integration, we're preparing a report for you at ",
1946
+ /* @__PURE__ */ jsx(Text, {
1947
+ color: "cyan",
1948
+ children: reportPath
1949
+ }),
1950
+ "."
1951
+ ] }),
1952
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
1953
+ /* @__PURE__ */ jsx(Text, { children: "We'll cover what we checked and suggest where we can improve the existing integration." }),
1954
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
1955
+ /* @__PURE__ */ jsx(Text, {
1956
+ dimColor: true,
1957
+ children: "Hang tight!"
1958
+ })
1959
+ ]
1960
+ });
1961
+ //#endregion
1962
+ //#region src/ui/tui/screens/audit/PendingChecksList.tsx
1963
+ function groupByArea$1(checks) {
1964
+ const order = [];
1965
+ const map = /* @__PURE__ */ new Map();
1966
+ for (const c of checks) {
1967
+ if (!map.has(c.area)) {
1968
+ map.set(c.area, []);
1969
+ order.push(c.area);
1970
+ }
1971
+ map.get(c.area).push(c);
1972
+ }
1973
+ return order.map((area) => ({
1974
+ area,
1975
+ checks: map.get(area)
1976
+ }));
1977
+ }
1978
+ function groupIcon$1(group) {
1979
+ const total = group.checks.length;
1980
+ const complete = group.checks.filter((c) => c.status !== "pending").length;
1981
+ if (complete === 0) return {
1982
+ icon: Icons.squareOpen,
1983
+ color: Colors.muted
1984
+ };
1985
+ if (complete === total) return {
1986
+ icon: Icons.squareFilled,
1987
+ color: Colors.success
1988
+ };
1989
+ return {
1990
+ icon: Icons.triangleRight,
1991
+ color: Colors.primary
1992
+ };
1993
+ }
1994
+ const GroupHeader$1 = ({ group, showIcon, isActive }) => {
1995
+ const complete = group.checks.filter((c) => c.status !== "pending").length;
1996
+ const total = group.checks.length;
1997
+ const { icon, color } = groupIcon$1(group);
1998
+ return /* @__PURE__ */ jsxs(Box, { children: [isActive ? /* @__PURE__ */ jsx(Box, {
1999
+ marginRight: 1,
2000
+ children: /* @__PURE__ */ jsx(Spinner, {})
2001
+ }) : showIcon ? /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
2002
+ color,
2003
+ children: icon
2004
+ }), " "] }) : null, /* @__PURE__ */ jsxs(Text, { children: [
2005
+ /* @__PURE__ */ jsx(Text, {
2006
+ bold: true,
2007
+ children: group.area
2008
+ }),
2009
+ " ",
2010
+ /* @__PURE__ */ jsxs(Text, {
2011
+ dimColor: true,
2012
+ children: [
2013
+ "(",
2014
+ complete,
2015
+ "/",
2016
+ total,
2017
+ ")"
2018
+ ]
2019
+ })
2020
+ ] })] });
2021
+ };
2022
+ const CheckRow = ({ check }) => {
2023
+ const { glyph, color } = AUDIT_SEVERITY_STYLE[check.status];
2024
+ return /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
2025
+ color,
2026
+ children: glyph
2027
+ }), /* @__PURE__ */ jsxs(Text, {
2028
+ dimColor: check.status === "pending",
2029
+ children: [" ", check.label]
2030
+ })] });
2031
+ };
2032
+ const COLLAPSE_BELOW_ROWS = 24;
2033
+ const PendingChecksList = ({ checks }) => {
2034
+ const [, termRows] = useStdoutDimensions();
2035
+ if (checks.length === 0) return /* @__PURE__ */ jsxs(Box, {
2036
+ flexDirection: "column",
2037
+ children: [
2038
+ /* @__PURE__ */ jsx(Text, {
2039
+ bold: true,
2040
+ children: "Checks"
2041
+ }),
2042
+ /* @__PURE__ */ jsx(Text, { children: " " }),
2043
+ /* @__PURE__ */ jsx(LoadingBox, { message: "Seeding audit checklist..." })
2044
+ ]
2045
+ });
2046
+ const groups = groupByArea$1(checks);
2047
+ const activeIndex = groups.findIndex((g) => g.checks.some((c) => c.status === "pending"));
2048
+ return /* @__PURE__ */ jsxs(Box, {
2049
+ flexDirection: "column",
2050
+ children: [
2051
+ /* @__PURE__ */ jsx(Text, {
2052
+ bold: true,
2053
+ children: "Checks"
2054
+ }),
2055
+ /* @__PURE__ */ jsx(Text, { children: " " }),
2056
+ termRows < COLLAPSE_BELOW_ROWS ? groups.map((group, i) => /* @__PURE__ */ jsx(GroupHeader$1, {
2057
+ group,
2058
+ showIcon: true,
2059
+ isActive: i === activeIndex
2060
+ }, group.area)) : groups.map((group, i) => /* @__PURE__ */ jsxs(Box, {
2061
+ flexDirection: "column",
2062
+ marginTop: i === 0 ? 0 : 1,
2063
+ children: [/* @__PURE__ */ jsx(GroupHeader$1, {
2064
+ group,
2065
+ showIcon: false,
2066
+ isActive: i === activeIndex
2067
+ }), group.checks.map((c) => /* @__PURE__ */ jsx(CheckRow, { check: c }, c.id))]
2068
+ }, group.area))
2069
+ ]
2070
+ });
2071
+ };
2072
+ //#endregion
2073
+ //#region src/ui/tui/screens/audit/AuditRunScreen.tsx
2074
+ const AuditRunScreen = ({ store }) => {
2075
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
2076
+ useFileWatcher(join(store.session.installDir, AUDIT_CHECKS_FILE), (parsed) => store.setFrameworkContext(AUDIT_CHECKS_KEY, coerceAuditChecks(parsed)));
2077
+ const statuses = store.statusMessages.length > 0 ? store.statusMessages : void 0;
2078
+ const [columns] = useStdoutDimensions();
2079
+ const checks = getAuditChecks(store.session);
2080
+ const reportPath = `./${getWorkflowConfig(store.router.activeFlow)?.reportFile ?? "posthog-audit-report.md"}`;
2081
+ const pendingChecksList = /* @__PURE__ */ jsx(PendingChecksList, { checks });
2082
+ return /* @__PURE__ */ jsx(TabContainer, {
2083
+ tabs: [
2084
+ {
2085
+ id: "status",
2086
+ label: "Status",
2087
+ component: columns < 80 ? /* @__PURE__ */ jsx(Box, {
2088
+ flexDirection: "column",
2089
+ flexGrow: 1,
2090
+ children: pendingChecksList
2091
+ }) : /* @__PURE__ */ jsx(SplitView, {
2092
+ left: /* @__PURE__ */ jsx(AuditAreaPane, {
2093
+ checks,
2094
+ reportPath
2095
+ }),
2096
+ right: pendingChecksList
2097
+ })
2098
+ },
2099
+ {
2100
+ id: "audit-checks",
2101
+ label: "Audit plan",
2102
+ component: /* @__PURE__ */ jsx(AuditChecksViewer, { checks })
2103
+ },
2104
+ {
2105
+ id: "logs",
2106
+ label: "Tail logs",
2107
+ component: /* @__PURE__ */ jsx(LogViewer, { filePath: WIZARD_LOG_FILE })
2108
+ },
2109
+ {
2110
+ id: "hn",
2111
+ label: "HN",
2112
+ component: /* @__PURE__ */ jsx(HNViewer, {})
2113
+ }
2114
+ ],
2115
+ statusMessage: statuses,
2116
+ expandableStatus: true,
2117
+ store
2118
+ });
2119
+ };
2120
+ //#endregion
2121
+ //#region src/ui/tui/screens/audit/AuditChecksOutroSection.tsx
2122
+ const MAX_VISIBLE = 6;
2123
+ const AuditChecksOutroSection = ({ checks, installDir }) => {
2124
+ if (checks.length === 0) return null;
2125
+ const errors = checks.filter((c) => c.status === "error");
2126
+ const warnings = checks.filter((c) => c.status === "warning");
2127
+ const suggestions = checks.filter((c) => c.status === "suggestion");
2128
+ const problematic = [
2129
+ ...errors,
2130
+ ...warnings,
2131
+ ...suggestions
2132
+ ];
2133
+ const visible = problematic.slice(0, MAX_VISIBLE);
2134
+ const hidden = problematic.length - visible.length;
2135
+ return /* @__PURE__ */ jsxs(Box, {
2136
+ flexDirection: "column",
2137
+ marginTop: 1,
2138
+ children: [
2139
+ /* @__PURE__ */ jsx(Text, {
2140
+ color: "cyan",
2141
+ bold: true,
2142
+ children: "Items audited:"
2143
+ }),
2144
+ /* @__PURE__ */ jsxs(Text, {
2145
+ dimColor: true,
2146
+ children: [
2147
+ checks.length,
2148
+ " checks · ",
2149
+ errors.length,
2150
+ " errors · ",
2151
+ warnings.length,
2152
+ " ",
2153
+ "warnings · ",
2154
+ suggestions.length,
2155
+ " suggestions"
2156
+ ]
2157
+ }),
2158
+ problematic.length === 0 ? /* @__PURE__ */ jsxs(Text, {
2159
+ color: "green",
2160
+ children: ["•", " No issues found."]
2161
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [visible.map((item) => {
2162
+ const style = AUDIT_SEVERITY_STYLE[item.status];
2163
+ return /* @__PURE__ */ jsxs(Box, {
2164
+ flexDirection: "column",
2165
+ marginTop: 1,
2166
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
2167
+ /* @__PURE__ */ jsx(Text, {
2168
+ color: style.color,
2169
+ children: style.glyph
2170
+ }),
2171
+ " ",
2172
+ /* @__PURE__ */ jsx(Text, {
2173
+ bold: true,
2174
+ children: item.label
2175
+ }),
2176
+ " ",
2177
+ /* @__PURE__ */ jsxs(Text, {
2178
+ dimColor: true,
2179
+ children: [
2180
+ "(",
2181
+ item.area,
2182
+ ")"
2183
+ ]
2184
+ })
2185
+ ] }), item.file && /* @__PURE__ */ jsxs(Text, {
2186
+ dimColor: true,
2187
+ children: [" ", relativeToInstallDir(item.file, installDir)]
2188
+ })]
2189
+ }, item.id);
2190
+ }), hidden > 0 && /* @__PURE__ */ jsxs(Text, {
2191
+ dimColor: true,
2192
+ children: [
2193
+ "… and ",
2194
+ hidden,
2195
+ " more — see the report."
2196
+ ]
2197
+ })] })
2198
+ ]
2199
+ });
2200
+ };
2201
+ //#endregion
2202
+ //#region src/ui/tui/screens/audit/AuditOutroScreen.tsx
2203
+ /**
2204
+ * AuditOutroScreen — Audit-specific post-run summary. Renders the standard
2205
+ * success / error / cancel views with the audit checks summary inlined into
2206
+ * the success body. The report path shown in the success headline comes from
2207
+ * the workflow's `successMessage`, so this screen is workflow-agnostic.
2208
+ */
2209
+ const AuditOutroScreen = ({ store }) => {
2210
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
2211
+ useInput(() => {
2212
+ store.setOutroDismissed();
2213
+ });
2214
+ const outroData = store.session.outroData;
2215
+ if (!outroData) return /* @__PURE__ */ jsx(Box, {
2216
+ flexDirection: "column",
2217
+ flexGrow: 1,
2218
+ children: /* @__PURE__ */ jsx(Text, {
2219
+ dimColor: true,
2220
+ children: "Finishing up..."
2221
+ })
2222
+ });
2223
+ return /* @__PURE__ */ jsxs(Box, {
2224
+ flexDirection: "column",
2225
+ flexGrow: 1,
2226
+ children: [
2227
+ outroData.kind === "success" && /* @__PURE__ */ jsxs(Box, {
2228
+ flexDirection: "column",
2229
+ children: [
2230
+ /* @__PURE__ */ jsxs(Text, {
2231
+ color: "green",
2232
+ bold: true,
2233
+ children: ["✔ ", outroData.message || "Audit complete!"]
2234
+ }),
2235
+ outroData.reportFile && /* @__PURE__ */ jsxs(Box, {
2236
+ flexDirection: "column",
2237
+ marginTop: 1,
2238
+ children: [
2239
+ /* @__PURE__ */ jsx(Text, {
2240
+ color: "cyan",
2241
+ bold: true,
2242
+ children: "Report saved to:"
2243
+ }),
2244
+ /* @__PURE__ */ jsx(Text, { children: join(store.session.installDir, outroData.reportFile) }),
2245
+ /* @__PURE__ */ jsx(Text, {
2246
+ dimColor: true,
2247
+ children: "A markdown file in your project folder — open it in any editor to read the full audit."
2248
+ })
2249
+ ]
2250
+ }),
2251
+ /* @__PURE__ */ jsx(AuditChecksOutroSection, {
2252
+ checks: getAuditChecks(store.session),
2253
+ installDir: store.session.installDir
2254
+ }),
2255
+ outroData.docsUrl && /* @__PURE__ */ jsx(Box, {
2256
+ marginTop: 1,
2257
+ children: /* @__PURE__ */ jsxs(Text, { children: ["Learn more: ", /* @__PURE__ */ jsx(Text, {
2258
+ color: "cyan",
2259
+ children: outroData.docsUrl
2260
+ })] })
2261
+ })
2262
+ ]
2263
+ }),
2264
+ outroData.kind === "error" && /* @__PURE__ */ jsxs(Box, {
2265
+ flexDirection: "column",
2266
+ children: [/* @__PURE__ */ jsxs(Text, {
2267
+ color: "red",
2268
+ bold: true,
2269
+ children: ["✘ ", outroData.message || "An error occurred"]
2270
+ }), outroData.body && /* @__PURE__ */ jsx(Box, {
2271
+ marginTop: 1,
2272
+ children: /* @__PURE__ */ jsx(Text, {
2273
+ dimColor: true,
2274
+ children: outroData.body
2275
+ })
2276
+ })]
2277
+ }),
2278
+ outroData.kind === "cancel" && /* @__PURE__ */ jsxs(Text, {
2279
+ color: "yellow",
2280
+ children: ["■ ", outroData.message || "Cancelled"]
2281
+ }),
2282
+ /* @__PURE__ */ jsx(Box, {
2283
+ marginTop: 1,
2284
+ children: /* @__PURE__ */ jsx(Text, {
2285
+ color: Colors.muted,
2286
+ children: "Press any key to continue"
2287
+ })
2288
+ })
2289
+ ]
2290
+ });
2291
+ };
2292
+ //#endregion
2293
+ //#region src/ui/tui/screens/audit-3000/arcade-colors.ts
2294
+ const NEON_PINK$2 = "#F54E00";
2295
+ const NEON_BLUE$2 = "#1D4AFF";
2296
+ const NEON_GOLD$2 = "#F9BD2B";
2297
+ //#endregion
2298
+ //#region src/ui/tui/screens/audit-3000/Audit3000IntroScreen.tsx
2299
+ const AUDIT3000_SKILL_ID = "audit-3000";
2300
+ const ArcadeBanner = () => {
2301
+ const [blinkOn, setBlinkOn] = useState(true);
2302
+ useEffect(() => {
2303
+ const id = setInterval(() => setBlinkOn((v) => !v), 600);
2304
+ return () => clearInterval(id);
2305
+ }, []);
2306
+ const top = "┏" + "━".repeat(32) + "┓";
2307
+ const bottom = "┗" + "━".repeat(32) + "┛";
2308
+ return /* @__PURE__ */ jsxs(Box, {
2309
+ flexDirection: "column",
2310
+ alignItems: "center",
2311
+ children: [
2312
+ /* @__PURE__ */ jsx(Text, {
2313
+ bold: true,
2314
+ color: NEON_PINK$2,
2315
+ children: top
2316
+ }),
2317
+ /* @__PURE__ */ jsxs(Text, { children: [
2318
+ /* @__PURE__ */ jsx(Text, {
2319
+ bold: true,
2320
+ color: NEON_PINK$2,
2321
+ children: "┃"
2322
+ }),
2323
+ /* @__PURE__ */ jsx(Text, {
2324
+ bold: true,
2325
+ color: NEON_GOLD$2,
2326
+ children: " A U D I T "
2327
+ }),
2328
+ /* @__PURE__ */ jsx(Text, {
2329
+ bold: true,
2330
+ color: NEON_BLUE$2,
2331
+ children: "-"
2332
+ }),
2333
+ /* @__PURE__ */ jsx(Text, {
2334
+ bold: true,
2335
+ color: NEON_GOLD$2,
2336
+ children: " 3 0 0 0 "
2337
+ }),
2338
+ /* @__PURE__ */ jsx(Text, {
2339
+ bold: true,
2340
+ color: NEON_PINK$2,
2341
+ children: "┃"
2342
+ })
2343
+ ] }),
2344
+ /* @__PURE__ */ jsxs(Text, { children: [
2345
+ /* @__PURE__ */ jsx(Text, {
2346
+ bold: true,
2347
+ color: NEON_PINK$2,
2348
+ children: "┃"
2349
+ }),
2350
+ /* @__PURE__ */ jsx(Text, {
2351
+ dimColor: !blinkOn,
2352
+ color: NEON_BLUE$2,
2353
+ children: " ▶ INSERT COIN TO PLAY ◀ "
2354
+ }),
2355
+ /* @__PURE__ */ jsx(Text, {
2356
+ bold: true,
2357
+ color: NEON_PINK$2,
2358
+ children: "┃"
2359
+ })
2360
+ ] }),
2361
+ /* @__PURE__ */ jsx(Text, {
2362
+ bold: true,
2363
+ color: NEON_PINK$2,
2364
+ children: bottom
2365
+ })
2366
+ ]
2367
+ });
2368
+ };
2369
+ const Audit3000IntroScreen = ({ store }) => {
2370
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
2371
+ const [showingMoreInfo, setShowingMoreInfo] = useState(false);
2372
+ const { session } = store;
2373
+ const { skillEntry, fetchFailed } = useSkillEntry(AUDIT3000_SKILL_ID, session.localMcp);
2374
+ const body = showingMoreInfo ? /* @__PURE__ */ jsxs(Box, {
2375
+ flexDirection: "column",
2376
+ width: 56,
2377
+ children: [
2378
+ /* @__PURE__ */ jsx(Box, {
2379
+ marginBottom: 1,
2380
+ children: /* @__PURE__ */ jsxs(Text, { children: ["The wizard is an agent that executes PostHog tasks. Its code is open source: ", /* @__PURE__ */ jsx(Text, {
2381
+ color: "cyan",
2382
+ children: "https://github.com/PostHog/wizard"
2383
+ })] })
2384
+ }),
2385
+ /* @__PURE__ */ jsxs(Text, { children: [
2386
+ "The",
2387
+ " ",
2388
+ /* @__PURE__ */ jsx(Text, {
2389
+ color: "cyan",
2390
+ italic: true,
2391
+ children: AUDIT3000_SKILL_ID
2392
+ }),
2393
+ " ",
2394
+ "workflow reviews your PostHog integration across 34 checks — SDK install, identification, event capture, event quality, stale feature flag hygiene, session replay (fix + optimize), and use-case expansion across 8 PostHog products. When enrichment is available it also produces a company profile and use-case match. Nothing in your project is modified."
2395
+ ] }),
2396
+ /* @__PURE__ */ jsx(Box, {
2397
+ marginTop: 1,
2398
+ children: /* @__PURE__ */ jsxs(Text, { children: [
2399
+ "Results stream live to the",
2400
+ " ",
2401
+ /* @__PURE__ */ jsx(Text, {
2402
+ color: "cyan",
2403
+ bold: true,
2404
+ children: "Hi-score Table"
2405
+ }),
2406
+ " ",
2407
+ "tab during the run — that's your live report. When the audit finishes, the same report is also exported to",
2408
+ " ",
2409
+ /* @__PURE__ */ jsx(Text, {
2410
+ color: "cyan",
2411
+ children: "./posthog-audit-3000-report.md"
2412
+ }),
2413
+ " in your project folder."
2414
+ ] })
2415
+ }),
2416
+ /* @__PURE__ */ jsx(Box, {
2417
+ marginTop: 1,
2418
+ children: /* @__PURE__ */ jsx(SkillSourceInfo, {
2419
+ skillId: AUDIT3000_SKILL_ID,
2420
+ skillEntry,
2421
+ fetchFailed
2422
+ })
2423
+ })
2424
+ ]
2425
+ }) : /* @__PURE__ */ jsxs(Box, {
2426
+ flexDirection: "column",
2427
+ alignItems: "center",
2428
+ children: [/* @__PURE__ */ jsx(ArcadeBanner, {}), /* @__PURE__ */ jsxs(Box, {
2429
+ marginTop: 1,
2430
+ flexDirection: "column",
2431
+ alignItems: "center",
2432
+ children: [
2433
+ /* @__PURE__ */ jsx(Text, {
2434
+ bold: true,
2435
+ children: "34 checks. 9 levels. 1 final report."
2436
+ }),
2437
+ /* @__PURE__ */ jsx(Text, {
2438
+ dimColor: true,
2439
+ children: "High-score your PostHog integration before the boss fight."
2440
+ }),
2441
+ /* @__PURE__ */ jsx(Box, {
2442
+ marginTop: 1,
2443
+ children: /* @__PURE__ */ jsxs(Text, {
2444
+ dimColor: true,
2445
+ children: [
2446
+ "Live report: ",
2447
+ /* @__PURE__ */ jsx(Text, {
2448
+ color: NEON_GOLD$2,
2449
+ children: "Hi-score Table"
2450
+ }),
2451
+ " tab · Export: ./posthog-audit-3000-report.md"
2452
+ ]
2453
+ })
2454
+ })
2455
+ ]
2456
+ })]
2457
+ });
2458
+ const menuOptions = showingMoreInfo ? [{
2459
+ label: "Back",
2460
+ value: "back"
2461
+ }] : [
2462
+ {
2463
+ label: "PRESS START",
2464
+ value: "continue"
2465
+ },
2466
+ {
2467
+ label: "More info",
2468
+ value: "more-info"
2469
+ },
2470
+ {
2471
+ label: "Cancel",
2472
+ value: "cancel"
2473
+ }
2474
+ ];
2475
+ const handleSelect = (value) => {
2476
+ if (value === "cancel") process.exit(0);
2477
+ else if (value === "more-info") setShowingMoreInfo(true);
2478
+ else if (value === "back") setShowingMoreInfo(false);
2479
+ else store.completeSetup();
2480
+ };
2481
+ return /* @__PURE__ */ jsx(IntroScreenLayout, {
2482
+ installDir: session.installDir,
2483
+ body,
2484
+ showDetection: !showingMoreInfo,
2485
+ workflowLabel: session.workflowLabel,
2486
+ skillId: session.skillId,
2487
+ menuOptions,
2488
+ onSelect: handleSelect
2489
+ });
2490
+ };
2491
+ //#endregion
2492
+ //#region src/ui/tui/screens/audit-3000/slides/eventQuality.tsx
2493
+ const EventQualityVisual = () => /* @__PURE__ */ jsxs(VisualBox, { children: [
2494
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
2495
+ color: "green",
2496
+ children: "event_clicked "
2497
+ }), /* @__PURE__ */ jsx(Text, {
2498
+ color: "green",
2499
+ children: "✓"
2500
+ })] }),
2501
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
2502
+ color: "yellow",
2503
+ children: "eventClicked "
2504
+ }), /* @__PURE__ */ jsx(Text, {
2505
+ color: "yellow",
2506
+ children: "~ duplicate?"
2507
+ })] }),
2508
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
2509
+ color: "yellow",
2510
+ children: "click_event "
2511
+ }), /* @__PURE__ */ jsx(Text, {
2512
+ color: "yellow",
2513
+ children: "~ duplicate?"
2514
+ })] }),
2515
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
2516
+ color: "red",
2517
+ children: "big_kitchen_sink "
2518
+ }), /* @__PURE__ */ jsx(Text, {
2519
+ color: "red",
2520
+ children: "✗ 22 props"
2521
+ })] })
2522
+ ] });
2523
+ const EventQualitySlide = {
2524
+ area: "Event Quality",
2525
+ intro: [
2526
+ "LEVEL 5: EVENT QUALITY. The capture call-sites are clean. The events themselves are the real boss fight.",
2527
+ "Scanning for: naming inconsistencies, semantic duplicates, kitchen-sink event payloads, and (if your PostHog project is linked) which captured events actually drive insights and dashboards.",
2528
+ "4 subagents fan out in parallel. The ticker shows them clearing checks live."
2529
+ ],
2530
+ visual: /* @__PURE__ */ jsx(EventQualityVisual, {}),
2531
+ docsUrl: "https://posthog.com/docs/product-analytics/best-practices"
2532
+ };
2533
+ //#endregion
2534
+ //#region src/ui/tui/screens/audit-3000/slides/featureFlags.tsx
2535
+ const FeatureFlagsVisual = () => /* @__PURE__ */ jsxs(VisualBox, { children: [
2536
+ /* @__PURE__ */ jsxs(Text, { children: [
2537
+ /* @__PURE__ */ jsx(Text, {
2538
+ color: "red",
2539
+ children: "new-checkout-v2 "
2540
+ }),
2541
+ /* @__PURE__ */ jsx(Text, {
2542
+ dimColor: true,
2543
+ children: "no code refs "
2544
+ }),
2545
+ /* @__PURE__ */ jsx(Text, {
2546
+ color: "red",
2547
+ children: "DROP"
2548
+ })
2549
+ ] }),
2550
+ /* @__PURE__ */ jsxs(Text, { children: [
2551
+ /* @__PURE__ */ jsx(Text, {
2552
+ color: "yellow",
2553
+ children: "beta-dashboard "
2554
+ }),
2555
+ /* @__PURE__ */ jsx(Text, {
2556
+ dimColor: true,
2557
+ children: "1 ref, 100% on "
2558
+ }),
2559
+ /* @__PURE__ */ jsx(Text, {
2560
+ color: "yellow",
2561
+ children: "REVIEW"
2562
+ })
2563
+ ] }),
2564
+ /* @__PURE__ */ jsxs(Text, { children: [
2565
+ /* @__PURE__ */ jsx(Text, {
2566
+ color: "green",
2567
+ children: "killswitch-payments"
2568
+ }),
2569
+ /* @__PURE__ */ jsx(Text, {
2570
+ dimColor: true,
2571
+ children: "live experiment"
2572
+ }),
2573
+ /* @__PURE__ */ jsx(Text, {
2574
+ color: "green",
2575
+ children: "KEEP"
2576
+ })
2577
+ ] })
2578
+ ] });
2579
+ const FeatureFlagsSlide = {
2580
+ area: "Feature Flags",
2581
+ intro: [
2582
+ "LEVEL 6: STALE FLAGS. Old flags add evaluation overhead and confuse the next engineer who wonders if a flag is still live.",
2583
+ "Cross-referencing PostHog's stale-flag classification against your source tree. Each flag scored: safe-to-disable, needs-review, or unknown.",
2584
+ "The final report ships with a copy-paste cleanup prompt. We never touch a flag."
2585
+ ],
2586
+ visual: /* @__PURE__ */ jsx(FeatureFlagsVisual, {}),
2587
+ docsUrl: "https://posthog.com/docs/feature-flags"
2588
+ };
2589
+ //#endregion
2590
+ //#region src/ui/tui/screens/audit-3000/slides/expansion.tsx
2591
+ const ExpansionVisual = () => /* @__PURE__ */ jsxs(VisualBox, { children: [
2592
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
2593
+ color: "cyan",
2594
+ children: "product analytics "
2595
+ }), /* @__PURE__ */ jsx(Text, {
2596
+ color: "green",
2597
+ children: "■■■■■"
2598
+ })] }),
2599
+ /* @__PURE__ */ jsxs(Text, { children: [
2600
+ /* @__PURE__ */ jsx(Text, {
2601
+ color: "cyan",
2602
+ children: "error tracking "
2603
+ }),
2604
+ /* @__PURE__ */ jsx(Text, {
2605
+ color: "red",
2606
+ children: "□□□□□"
2607
+ }),
2608
+ /* @__PURE__ */ jsx(Text, {
2609
+ dimColor: true,
2610
+ children: " sentry detected"
2611
+ })
2612
+ ] }),
2613
+ /* @__PURE__ */ jsxs(Text, { children: [
2614
+ /* @__PURE__ */ jsx(Text, {
2615
+ color: "cyan",
2616
+ children: "session replay "
2617
+ }),
2618
+ /* @__PURE__ */ jsx(Text, {
2619
+ color: "yellow",
2620
+ children: "■■□□□"
2621
+ }),
2622
+ /* @__PURE__ */ jsx(Text, {
2623
+ dimColor: true,
2624
+ children: " partial"
2625
+ })
2626
+ ] }),
2627
+ /* @__PURE__ */ jsxs(Text, { children: [
2628
+ /* @__PURE__ */ jsx(Text, {
2629
+ color: "cyan",
2630
+ children: "llm observability "
2631
+ }),
2632
+ /* @__PURE__ */ jsx(Text, {
2633
+ color: "red",
2634
+ children: "□□□□□"
2635
+ }),
2636
+ /* @__PURE__ */ jsx(Text, {
2637
+ dimColor: true,
2638
+ children: " greenfield"
2639
+ })
2640
+ ] })
2641
+ ] });
2642
+ //#endregion
2643
+ //#region src/ui/tui/screens/audit-3000/slides/index.ts
2644
+ const AUDIT_3000_AREA_SLIDES = [
2645
+ InstallationSlide,
2646
+ IdentificationSlide,
2647
+ EventCaptureSlide,
2648
+ EventQualitySlide,
2649
+ FeatureFlagsSlide,
2650
+ {
2651
+ area: "Use Case: Expansion",
2652
+ intro: [
2653
+ "BONUS ROUND: EXPANSION. You might be paying for tools PostHog covers natively.",
2654
+ "Scanning for competitive SDKs (Sentry, LaunchDarkly, Mixpanel, Datadog, OpenTelemetry, GA4) and PostHog coverage gaps across 8 product surfaces.",
2655
+ "8 subagents in two waves of 4. Each one returns one of: cross-sell, greenfield, gap, or pass."
2656
+ ],
2657
+ visual: /* @__PURE__ */ jsx(ExpansionVisual, {}),
2658
+ docsUrl: "https://posthog.com/docs"
2659
+ }
2660
+ ];
2661
+ //#endregion
2662
+ //#region src/ui/tui/screens/audit-3000/Audit3000AreaPane.tsx
2663
+ /**
2664
+ * Audit-3000 right pane — arcade-flavoured fork of `AuditAreaPane`.
2665
+ *
2666
+ * Mirrors the audit pane's three-state logic (active slide → empty →
2667
+ * wrap-up) but routes through the audit-3000 slide registry and uses
2668
+ * "LEVEL N: <area>" framing instead of "Verifying ...".
2669
+ */
2670
+ const FINDING_STATUSES = [
2671
+ "error",
2672
+ "warning",
2673
+ "suggestion"
2674
+ ];
2675
+ const isFinding = (c) => FINDING_STATUSES.includes(c.status);
2676
+ const fallbackSlide = (area) => ({
2677
+ area,
2678
+ intro: [`Now playing: ${area.toLowerCase()}\u2026`],
2679
+ docsUrl: ""
2680
+ });
2681
+ const openLink = (url) => {
2682
+ spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open", process.platform === "win32" ? [
2683
+ "/c",
2684
+ "start",
2685
+ "",
2686
+ url
2687
+ ] : [url], {
2688
+ detached: true,
2689
+ stdio: "ignore"
2690
+ }).unref();
2691
+ };
2692
+ const Audit3000AreaPane = ({ checks, reportPath }) => {
2693
+ const activeArea = checks.filter((c) => c.status === "pending")[0]?.area;
2694
+ const slide = activeArea ? AUDIT_3000_AREA_SLIDES.find((s) => s.area === activeArea) ?? fallbackSlide(activeArea) : null;
2695
+ const levelIndex = activeArea ? AUDIT_3000_AREA_SLIDES.findIndex((s) => s.area === activeArea) : -1;
2696
+ const level = levelIndex >= 0 ? levelIndex + 1 : null;
2697
+ useInput((input) => {
2698
+ if (input.toLowerCase() === "o" && slide?.docsUrl) openLink(slide.docsUrl);
2699
+ });
2700
+ if (slide) return /* @__PURE__ */ jsx(ActiveSlide, {
2701
+ slide,
2702
+ level,
2703
+ hasFindings: checks.some(isFinding)
2704
+ });
2705
+ if (checks.length === 0) return null;
2706
+ return /* @__PURE__ */ jsx(WritingReport, { reportPath });
2707
+ };
2708
+ const ActiveSlide = ({ slide, level, hasFindings }) => /* @__PURE__ */ jsxs(Box, {
2709
+ flexDirection: "column",
2710
+ paddingX: 1,
2711
+ children: [
2712
+ /* @__PURE__ */ jsxs(Text, {
2713
+ bold: true,
2714
+ color: Colors.accent,
2715
+ children: [level ? `LEVEL ${level}: ` : "", slide.area.toUpperCase()]
2716
+ }),
2717
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2718
+ slide.visual,
2719
+ slide.intro.map((paragraph, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 && /* @__PURE__ */ jsx(Box, { height: 1 }), /* @__PURE__ */ jsx(Text, { children: paragraph })] }, i)),
2720
+ /* @__PURE__ */ jsx(Box, {
2721
+ marginTop: 1,
2722
+ children: /* @__PURE__ */ jsxs(Text, {
2723
+ dimColor: true,
2724
+ children: [slide.docsUrl && /* @__PURE__ */ jsxs(Fragment$1, { children: [
2725
+ "[",
2726
+ /* @__PURE__ */ jsx(Text, {
2727
+ color: Colors.accent,
2728
+ children: "O"
2729
+ }),
2730
+ "] Learn more"
2731
+ ] }), hasFindings && /* @__PURE__ */ jsxs(Fragment$1, { children: [
2732
+ slide.docsUrl && " ",
2733
+ "[",
2734
+ /* @__PURE__ */ jsx(Text, {
2735
+ color: Colors.accent,
2736
+ children: "→"
2737
+ }),
2738
+ "] View issues"
2739
+ ] })]
2740
+ })
2741
+ })
2742
+ ]
2743
+ });
2744
+ const WritingReport = ({ reportPath }) => /* @__PURE__ */ jsxs(Box, {
2745
+ flexDirection: "column",
2746
+ paddingX: 1,
2747
+ children: [
2748
+ /* @__PURE__ */ jsx(Text, {
2749
+ bold: true,
2750
+ color: Colors.accent,
2751
+ children: "STAGE CLEAR."
2752
+ }),
2753
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2754
+ /* @__PURE__ */ jsxs(Text, { children: [
2755
+ "All checks resolved. Compiling your high-score reel at",
2756
+ " ",
2757
+ /* @__PURE__ */ jsx(Text, {
2758
+ color: "cyan",
2759
+ children: reportPath
2760
+ }),
2761
+ "."
2762
+ ] }),
2763
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2764
+ /* @__PURE__ */ jsx(Text, { children: "The report covers everything we checked, what we found, and what to do next." }),
2765
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
2766
+ /* @__PURE__ */ jsx(Text, {
2767
+ dimColor: true,
2768
+ children: "Stand by…"
2769
+ })
2770
+ ]
2771
+ });
2772
+ //#endregion
2773
+ //#region src/ui/tui/screens/audit-3000/Audit3000ChecksPanel.tsx
2774
+ /**
2775
+ * Audit-3000 left pane on the Run screen. Arcade-flavoured fork of the
2776
+ * audit workflow's `PendingChecksList`: a running score banner sits on
2777
+ * top, then the area-level "level" headers underneath.
2778
+ *
2779
+ * Per-check rows are deliberately omitted here — the Hi-score Table tab
2780
+ * has the full check-by-check breakdown. This pane is the at-a-glance
2781
+ * stage overview.
2782
+ */
2783
+ const NEON_PINK$1 = "#F54E00";
2784
+ const NEON_GOLD$1 = "#F9BD2B";
2785
+ const NEON_BLUE$1 = "#1D4AFF";
2786
+ function groupByArea(checks) {
2787
+ const order = [];
2788
+ const map = /* @__PURE__ */ new Map();
2789
+ for (const c of checks) {
2790
+ if (!map.has(c.area)) {
2791
+ map.set(c.area, []);
2792
+ order.push(c.area);
2793
+ }
2794
+ map.get(c.area).push(c);
2795
+ }
2796
+ return order.map((area) => ({
2797
+ area,
2798
+ checks: map.get(area)
2799
+ }));
2800
+ }
2801
+ function countByStatus$1(checks) {
2802
+ const counts = {
2803
+ pending: 0,
2804
+ pass: 0,
2805
+ error: 0,
2806
+ warning: 0,
2807
+ suggestion: 0
2808
+ };
2809
+ for (const c of checks) counts[c.status] += 1;
2810
+ return counts;
2811
+ }
2812
+ const ScoreBanner = ({ checks }) => {
2813
+ const counts = countByStatus$1(checks);
2814
+ const resolved = checks.length - counts.pending;
2815
+ const issues = counts.error + counts.warning + counts.suggestion;
2816
+ return /* @__PURE__ */ jsxs(Box, {
2817
+ flexDirection: "column",
2818
+ marginBottom: 1,
2819
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
2820
+ /* @__PURE__ */ jsx(Text, {
2821
+ bold: true,
2822
+ color: NEON_PINK$1,
2823
+ children: "SCORE "
2824
+ }),
2825
+ /* @__PURE__ */ jsx(Text, {
2826
+ bold: true,
2827
+ color: NEON_GOLD$1,
2828
+ children: resolved.toString().padStart(2, "0")
2829
+ }),
2830
+ /* @__PURE__ */ jsx(Text, {
2831
+ dimColor: true,
2832
+ children: " / "
2833
+ }),
2834
+ /* @__PURE__ */ jsx(Text, {
2835
+ bold: true,
2836
+ children: checks.length.toString().padStart(2, "0")
2837
+ })
2838
+ ] }), /* @__PURE__ */ jsxs(Text, { children: [
2839
+ /* @__PURE__ */ jsx(Text, {
2840
+ color: "green",
2841
+ children: `PASS \u25B2 ${counts.pass}`
2842
+ }),
2843
+ /* @__PURE__ */ jsx(Text, { children: " " }),
2844
+ /* @__PURE__ */ jsx(Text, {
2845
+ color: NEON_PINK$1,
2846
+ children: `MISS \u25BC ${issues}`
2847
+ }),
2848
+ /* @__PURE__ */ jsx(Text, { children: " " }),
2849
+ /* @__PURE__ */ jsx(Text, {
2850
+ dimColor: true,
2851
+ children: `QUEUE \u25CB ${counts.pending}`
2852
+ })
2853
+ ] })]
2854
+ });
2855
+ };
2856
+ function groupIcon(group) {
2857
+ const total = group.checks.length;
2858
+ const complete = group.checks.filter((c) => c.status !== "pending").length;
2859
+ if (complete === 0) return {
2860
+ icon: Icons.squareOpen,
2861
+ color: Colors.muted
2862
+ };
2863
+ if (complete === total) return {
2864
+ icon: Icons.squareFilled,
2865
+ color: Colors.success
2866
+ };
2867
+ return {
2868
+ icon: Icons.triangleRight,
2869
+ color: Colors.primary
2870
+ };
2871
+ }
2872
+ const GroupHeader = ({ group, level, showIcon, isActive }) => {
2873
+ const complete = group.checks.filter((c) => c.status !== "pending").length;
2874
+ const total = group.checks.length;
2875
+ const { icon, color } = groupIcon(group);
2876
+ return /* @__PURE__ */ jsxs(Box, { children: [isActive ? /* @__PURE__ */ jsx(Box, {
2877
+ marginRight: 1,
2878
+ children: /* @__PURE__ */ jsx(Spinner, {})
2879
+ }) : showIcon ? /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
2880
+ color,
2881
+ children: icon
2882
+ }), " "] }) : null, /* @__PURE__ */ jsxs(Text, { children: [
2883
+ /* @__PURE__ */ jsx(Text, {
2884
+ color: NEON_BLUE$1,
2885
+ bold: true,
2886
+ children: `L${level} `
2887
+ }),
2888
+ /* @__PURE__ */ jsx(Text, {
2889
+ bold: true,
2890
+ children: group.area
2891
+ }),
2892
+ " ",
2893
+ /* @__PURE__ */ jsxs(Text, {
2894
+ dimColor: true,
2895
+ children: [
2896
+ "(",
2897
+ complete,
2898
+ "/",
2899
+ total,
2900
+ ")"
2901
+ ]
2902
+ })
2903
+ ] })] });
2904
+ };
2905
+ const Audit3000ChecksPanel = ({ checks }) => {
2906
+ if (checks.length === 0) return /* @__PURE__ */ jsxs(Box, {
2907
+ flexDirection: "column",
2908
+ children: [
2909
+ /* @__PURE__ */ jsx(Text, {
2910
+ bold: true,
2911
+ children: "AUDIT-3000"
2912
+ }),
2913
+ /* @__PURE__ */ jsx(Text, { children: " " }),
2914
+ /* @__PURE__ */ jsx(LoadingBox, { message: "Booting up arcade cabinet..." })
2915
+ ]
2916
+ });
2917
+ const groups = groupByArea(checks);
2918
+ const activeIndex = groups.findIndex((g) => g.checks.some((c) => c.status === "pending"));
2919
+ return /* @__PURE__ */ jsxs(Box, {
2920
+ flexDirection: "column",
2921
+ children: [
2922
+ /* @__PURE__ */ jsx(Text, {
2923
+ bold: true,
2924
+ color: NEON_PINK$1,
2925
+ children: "AUDIT-3000"
2926
+ }),
2927
+ /* @__PURE__ */ jsx(Text, { children: " " }),
2928
+ /* @__PURE__ */ jsx(ScoreBanner, { checks }),
2929
+ groups.map((group, i) => /* @__PURE__ */ jsx(GroupHeader, {
2930
+ group,
2931
+ level: i + 1,
2932
+ showIcon: true,
2933
+ isActive: i === activeIndex
2934
+ }, group.area)),
2935
+ /* @__PURE__ */ jsx(Box, {
2936
+ marginTop: 1,
2937
+ children: /* @__PURE__ */ jsxs(Text, {
2938
+ dimColor: true,
2939
+ children: [
2940
+ "Full breakdown: ",
2941
+ /* @__PURE__ */ jsx(Text, {
2942
+ color: NEON_GOLD$1,
2943
+ children: "Hi-score table (report)"
2944
+ }),
2945
+ " ",
2946
+ "tab"
2947
+ ]
2948
+ })
2949
+ })
2950
+ ]
2951
+ });
2952
+ };
2953
+ function nextRandom(seed) {
2954
+ let t = seed + 1831565813 >>> 0;
2955
+ t = Math.imul(t ^ t >>> 15, t | 1);
2956
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
2957
+ return {
2958
+ value: ((t ^ t >>> 14) >>> 0) / 4294967296,
2959
+ nextSeed: t >>> 0
2960
+ };
2961
+ }
2962
+ function randomInt(seed, min, max) {
2963
+ const { value, nextSeed } = nextRandom(seed);
2964
+ return {
2965
+ value: min + Math.floor(value * (max - min + 1)),
2966
+ nextSeed
2967
+ };
2968
+ }
2969
+ function initialState(hiScore = 0, rngSeed = 1) {
2970
+ return {
2971
+ hedgehogState: "grounded",
2972
+ hedgehogRow: 2,
2973
+ jumpFramesRemaining: 0,
2974
+ obstacles: [],
2975
+ score: 0,
2976
+ hiScore,
2977
+ isGameOver: false,
2978
+ tick: 0,
2979
+ ticksUntilNextSpawn: 6,
2980
+ rngSeed
2981
+ };
2982
+ }
2983
+ function jump(state) {
2984
+ if (state.isGameOver) return state;
2985
+ if (state.hedgehogState !== "grounded") return state;
2986
+ return {
2987
+ ...state,
2988
+ hedgehogState: "jumping",
2989
+ hedgehogRow: 1,
2990
+ jumpFramesRemaining: 8
2991
+ };
2992
+ }
2993
+ function restart(state) {
2994
+ return initialState(state.hiScore, state.rngSeed);
2995
+ }
2996
+ function tick(state) {
2997
+ if (state.isGameOver) return state;
2998
+ let { hedgehogState, hedgehogRow, jumpFramesRemaining } = state;
2999
+ if (hedgehogState === "jumping") {
3000
+ jumpFramesRemaining -= 1;
3001
+ if (jumpFramesRemaining <= 0) {
3002
+ hedgehogState = "grounded";
3003
+ hedgehogRow = 2;
3004
+ jumpFramesRemaining = 0;
3005
+ }
3006
+ }
3007
+ const movedObstacles = [];
3008
+ let scoreDelta = 1;
3009
+ let hit = false;
3010
+ for (const obs of state.obstacles) {
3011
+ const next = {
3012
+ ...obs,
3013
+ x: obs.x - 1
3014
+ };
3015
+ if (next.x < 0) continue;
3016
+ if (next.x === 4 && next.row === hedgehogRow) {
3017
+ if (next.kind === "spike") {
3018
+ hit = true;
3019
+ movedObstacles.push(next);
3020
+ continue;
3021
+ }
3022
+ scoreDelta += 5;
3023
+ continue;
3024
+ }
3025
+ movedObstacles.push(next);
3026
+ }
3027
+ let rngSeed = state.rngSeed;
3028
+ let ticksUntilNextSpawn = state.ticksUntilNextSpawn - 1;
3029
+ if (ticksUntilNextSpawn <= 0) {
3030
+ const kindRoll = nextRandom(rngSeed);
3031
+ rngSeed = kindRoll.nextSeed;
3032
+ const kind = kindRoll.value < .65 ? "spike" : "ring";
3033
+ const row = kind === "spike" ? 2 : 1;
3034
+ movedObstacles.push({
3035
+ kind,
3036
+ x: 39,
3037
+ row
3038
+ });
3039
+ const cooldown = randomInt(rngSeed, 6, 14);
3040
+ rngSeed = cooldown.nextSeed;
3041
+ ticksUntilNextSpawn = cooldown.value;
3042
+ }
3043
+ const score = state.score + scoreDelta;
3044
+ const isGameOver = hit;
3045
+ const hiScore = isGameOver ? Math.max(state.hiScore, score) : state.hiScore;
3046
+ return {
3047
+ hedgehogState,
3048
+ hedgehogRow,
3049
+ jumpFramesRemaining,
3050
+ obstacles: movedObstacles,
3051
+ score,
3052
+ hiScore,
3053
+ isGameOver,
3054
+ tick: state.tick + 1,
3055
+ ticksUntilNextSpawn,
3056
+ rngSeed
3057
+ };
3058
+ }
3059
+ //#endregion
3060
+ //#region src/ui/tui/screens/audit-3000/HedgehogRunner.tsx
3061
+ /**
3062
+ * HedgehogRunner — playable arcade game shown while the audit runs.
3063
+ *
3064
+ * Game state lives in the parent (Audit3000RunScreen) so it survives tab
3065
+ * switches. This component owns the render loop (setInterval) and key
3066
+ * bindings; when the user switches tabs the component unmounts, the
3067
+ * interval clears, and state freezes in the parent — free pause behaviour.
3068
+ */
3069
+ const TICK_MS = 150;
3070
+ const PLAYFIELD_ROWS = 3;
3071
+ const MIN_TERMINAL_COLUMNS = 50;
3072
+ const HEDGEHOG_GLYPH = "O";
3073
+ const SPIKE_GLYPH = "^";
3074
+ const RING_GLYPH = "o";
3075
+ const GROUND_GLYPH = "=";
3076
+ const pad4 = (n) => String(n).padStart(4, "0");
3077
+ const HedgehogRunner = ({ state, onChange }) => {
3078
+ const [columns] = useStdoutDimensions();
3079
+ useEffect(() => {
3080
+ const id = setInterval(() => {
3081
+ onChange((prev) => tick(prev));
3082
+ }, TICK_MS);
3083
+ return () => clearInterval(id);
3084
+ }, [onChange]);
3085
+ useKeyBindings("hedgehog-runner", [{
3086
+ match: "space",
3087
+ label: "space",
3088
+ action: "jump",
3089
+ handler: () => onChange((prev) => jump(prev))
3090
+ }, {
3091
+ match: "r",
3092
+ label: "r",
3093
+ action: "restart",
3094
+ handler: () => onChange((prev) => prev.isGameOver ? restart(prev) : prev)
3095
+ }]);
3096
+ if (columns < MIN_TERMINAL_COLUMNS) return /* @__PURE__ */ jsx(Box, {
3097
+ flexDirection: "column",
3098
+ paddingX: 1,
3099
+ children: /* @__PURE__ */ jsxs(Text, {
3100
+ dimColor: true,
3101
+ children: [
3102
+ "Widen the terminal to at least ",
3103
+ MIN_TERMINAL_COLUMNS,
3104
+ " columns to play Hedgehog Runner."
3105
+ ]
3106
+ })
3107
+ });
3108
+ return /* @__PURE__ */ jsxs(Box, {
3109
+ flexDirection: "column",
3110
+ paddingX: 1,
3111
+ children: [
3112
+ /* @__PURE__ */ jsxs(Box, { children: [
3113
+ /* @__PURE__ */ jsxs(Text, {
3114
+ bold: true,
3115
+ color: NEON_BLUE$2,
3116
+ children: ["SCORE ", pad4(state.score)]
3117
+ }),
3118
+ /* @__PURE__ */ jsx(Text, { children: " " }),
3119
+ /* @__PURE__ */ jsxs(Text, {
3120
+ bold: true,
3121
+ color: NEON_GOLD$2,
3122
+ children: ["HI ", pad4(state.hiScore)]
3123
+ }),
3124
+ state.isGameOver && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Text, { children: " " }), /* @__PURE__ */ jsx(Text, {
3125
+ bold: true,
3126
+ color: "red",
3127
+ children: "✱ GAME OVER ✱"
3128
+ })] })
3129
+ ] }),
3130
+ Array.from({ length: PLAYFIELD_ROWS }, (_, row) => /* @__PURE__ */ jsx(PlayfieldRow, {
3131
+ row,
3132
+ state
3133
+ }, row)),
3134
+ /* @__PURE__ */ jsx(Text, {
3135
+ color: Colors.muted,
3136
+ children: GROUND_GLYPH.repeat(40)
3137
+ })
3138
+ ]
3139
+ });
3140
+ };
3141
+ const PlayfieldRow = ({ row, state }) => {
3142
+ const cells = [];
3143
+ for (let x = 0; x < 40; x++) {
3144
+ if (x === 4 && row === state.hedgehogRow) {
3145
+ cells.push({
3146
+ ch: HEDGEHOG_GLYPH,
3147
+ color: NEON_PINK$2,
3148
+ bold: true
3149
+ });
3150
+ continue;
3151
+ }
3152
+ const obstacle = state.obstacles.find((o) => o.x === x && o.row === row);
3153
+ if (obstacle) {
3154
+ cells.push(obstacle.kind === "spike" ? {
3155
+ ch: SPIKE_GLYPH,
3156
+ color: "red",
3157
+ bold: true
3158
+ } : {
3159
+ ch: RING_GLYPH,
3160
+ color: NEON_GOLD$2,
3161
+ bold: true
3162
+ });
3163
+ continue;
3164
+ }
3165
+ cells.push({ ch: " " });
3166
+ }
3167
+ return /* @__PURE__ */ jsx(Text, { children: cells.map((c, i) => /* @__PURE__ */ jsx(Fragment, { children: c.color ? /* @__PURE__ */ jsx(Text, {
3168
+ color: c.color,
3169
+ bold: c.bold,
3170
+ children: c.ch
3171
+ }) : c.ch }, i)) });
3172
+ };
3173
+ //#endregion
3174
+ //#region src/ui/tui/screens/audit-3000/Audit3000RunScreen.tsx
3175
+ const AUDIT_3000_REPORT_FILE_FALLBACK = "posthog-audit-3000-report.md";
3176
+ const Audit3000RunScreen = ({ store }) => {
3177
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
3178
+ useFileWatcher(join(store.session.installDir, AUDIT_CHECKS_FILE), (parsed) => store.setFrameworkContext(AUDIT_CHECKS_KEY, coerceAuditChecks(parsed)));
3179
+ const statuses = store.statusMessages.length > 0 ? store.statusMessages : void 0;
3180
+ const [columns] = useStdoutDimensions();
3181
+ const [gameState, setGameState] = useState(() => initialState());
3182
+ const checks = getAuditChecks(store.session);
3183
+ const reportPath = `./${getWorkflowConfig(store.router.activeFlow)?.reportFile ?? AUDIT_3000_REPORT_FILE_FALLBACK}`;
3184
+ const checksPanel = /* @__PURE__ */ jsx(Audit3000ChecksPanel, { checks });
3185
+ return /* @__PURE__ */ jsx(TabContainer, {
3186
+ tabs: [
3187
+ {
3188
+ id: "status",
3189
+ label: "Arcade",
3190
+ component: columns < 80 ? /* @__PURE__ */ jsx(Box, {
3191
+ flexDirection: "column",
3192
+ flexGrow: 1,
3193
+ children: checksPanel
3194
+ }) : /* @__PURE__ */ jsx(SplitView, {
3195
+ left: /* @__PURE__ */ jsx(Audit3000AreaPane, {
3196
+ checks,
3197
+ reportPath
3198
+ }),
3199
+ right: checksPanel
3200
+ })
3201
+ },
3202
+ {
3203
+ id: "audit-checks",
3204
+ label: "Hi-score table (report)",
3205
+ component: /* @__PURE__ */ jsx(AuditChecksViewer, { checks })
3206
+ },
3207
+ {
3208
+ id: "play",
3209
+ label: "Play",
3210
+ component: /* @__PURE__ */ jsx(HedgehogRunner, {
3211
+ state: gameState,
3212
+ onChange: setGameState
3213
+ })
3214
+ },
3215
+ {
3216
+ id: "logs",
3217
+ label: "Tail logs",
3218
+ component: /* @__PURE__ */ jsx(LogViewer, { filePath: WIZARD_LOG_FILE })
3219
+ },
3220
+ {
3221
+ id: "hn",
3222
+ label: "HN",
3223
+ component: /* @__PURE__ */ jsx(HNViewer, {})
3224
+ }
3225
+ ],
3226
+ statusMessage: statuses,
3227
+ expandableStatus: true,
3228
+ store
3229
+ });
3230
+ };
3231
+ //#endregion
3232
+ //#region src/ui/tui/screens/audit-3000/Audit3000OutroScreen.tsx
3233
+ /**
3234
+ * Audit3000OutroScreen — high-score-style summary after a v3000 audit run.
3235
+ *
3236
+ * On success: arcade FINAL SCORE banner with pass / miss tallies, the
3237
+ * absolute report path, and the standard problematic-items list.
3238
+ *
3239
+ * Error and cancel branches mirror `AuditOutroScreen` so failure modes
3240
+ * stay legible without arcade dressing.
3241
+ */
3242
+ const NEON_PINK = "#F54E00";
3243
+ const NEON_GOLD = "#F9BD2B";
3244
+ const NEON_BLUE = "#1D4AFF";
3245
+ const PANEL_WIDTH = 48;
3246
+ const padCenter = (s, width) => {
3247
+ if (s.length >= width) return s;
3248
+ const total = width - s.length;
3249
+ const left = Math.floor(total / 2);
3250
+ const right = total - left;
3251
+ return " ".repeat(left) + s + " ".repeat(right);
3252
+ };
3253
+ function countByStatus(checks) {
3254
+ const counts = {
3255
+ pending: 0,
3256
+ pass: 0,
3257
+ error: 0,
3258
+ warning: 0,
3259
+ suggestion: 0
3260
+ };
3261
+ for (const c of checks) counts[c.status] += 1;
3262
+ return counts;
3263
+ }
3264
+ const FinalScorePanel = ({ checks }) => {
3265
+ const counts = countByStatus(checks);
3266
+ const resolved = checks.length - counts.pending;
3267
+ const issues = counts.error + counts.warning + counts.suggestion;
3268
+ const top = "┏" + "━".repeat(PANEL_WIDTH) + "┓";
3269
+ const bottom = "┗" + "━".repeat(PANEL_WIDTH) + "┛";
3270
+ const sep = "┠" + "─".repeat(PANEL_WIDTH) + "┨";
3271
+ const row = (content) => /* @__PURE__ */ jsxs(Text, { children: [
3272
+ /* @__PURE__ */ jsx(Text, {
3273
+ bold: true,
3274
+ color: NEON_PINK,
3275
+ children: "┃"
3276
+ }),
3277
+ /* @__PURE__ */ jsx(Text, { children: content }),
3278
+ /* @__PURE__ */ jsx(Text, {
3279
+ bold: true,
3280
+ color: NEON_PINK,
3281
+ children: "┃"
3282
+ })
3283
+ ] });
3284
+ return /* @__PURE__ */ jsxs(Box, {
3285
+ flexDirection: "column",
3286
+ marginTop: 1,
3287
+ children: [
3288
+ /* @__PURE__ */ jsx(Text, {
3289
+ bold: true,
3290
+ color: NEON_PINK,
3291
+ children: top
3292
+ }),
3293
+ row(padCenter("GAME OVER", PANEL_WIDTH)),
3294
+ /* @__PURE__ */ jsxs(Text, { children: [
3295
+ /* @__PURE__ */ jsx(Text, {
3296
+ bold: true,
3297
+ color: NEON_PINK,
3298
+ children: "┃"
3299
+ }),
3300
+ /* @__PURE__ */ jsx(Text, {
3301
+ color: NEON_GOLD,
3302
+ children: padCenter(`FINAL SCORE ${resolved} / ${checks.length}`, PANEL_WIDTH)
3303
+ }),
3304
+ /* @__PURE__ */ jsx(Text, {
3305
+ bold: true,
3306
+ color: NEON_PINK,
3307
+ children: "┃"
3308
+ })
3309
+ ] }),
3310
+ /* @__PURE__ */ jsx(Text, {
3311
+ color: NEON_PINK,
3312
+ children: sep
3313
+ }),
3314
+ /* @__PURE__ */ jsxs(Text, { children: [
3315
+ /* @__PURE__ */ jsx(Text, {
3316
+ bold: true,
3317
+ color: NEON_PINK,
3318
+ children: "┃"
3319
+ }),
3320
+ /* @__PURE__ */ jsx(Text, {
3321
+ color: "green",
3322
+ children: padCenter(`PASS \u25B2 ${counts.pass}`, PANEL_WIDTH)
3323
+ }),
3324
+ /* @__PURE__ */ jsx(Text, {
3325
+ bold: true,
3326
+ color: NEON_PINK,
3327
+ children: "┃"
3328
+ })
3329
+ ] }),
3330
+ /* @__PURE__ */ jsxs(Text, { children: [
3331
+ /* @__PURE__ */ jsx(Text, {
3332
+ bold: true,
3333
+ color: NEON_PINK,
3334
+ children: "┃"
3335
+ }),
3336
+ /* @__PURE__ */ jsx(Text, {
3337
+ color: NEON_BLUE,
3338
+ children: padCenter(`MISS \u25BC ${issues}`, PANEL_WIDTH)
3339
+ }),
3340
+ /* @__PURE__ */ jsx(Text, {
3341
+ bold: true,
3342
+ color: NEON_PINK,
3343
+ children: "┃"
3344
+ })
3345
+ ] }),
3346
+ /* @__PURE__ */ jsx(Text, {
3347
+ bold: true,
3348
+ color: NEON_PINK,
3349
+ children: bottom
3350
+ })
3351
+ ]
3352
+ });
3353
+ };
3354
+ const Audit3000OutroScreen = ({ store }) => {
3355
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
3356
+ useInput(() => {
3357
+ store.setOutroDismissed();
3358
+ });
3359
+ const outroData = store.session.outroData;
3360
+ if (!outroData) return /* @__PURE__ */ jsx(Box, {
3361
+ flexDirection: "column",
3362
+ flexGrow: 1,
3363
+ children: /* @__PURE__ */ jsx(Text, {
3364
+ dimColor: true,
3365
+ children: "Counting your tokens…"
3366
+ })
3367
+ });
3368
+ const checks = getAuditChecks(store.session);
3369
+ return /* @__PURE__ */ jsxs(Box, {
3370
+ flexDirection: "column",
3371
+ flexGrow: 1,
3372
+ children: [
3373
+ outroData.kind === "success" && /* @__PURE__ */ jsxs(Box, {
3374
+ flexDirection: "column",
3375
+ children: [
3376
+ /* @__PURE__ */ jsx(FinalScorePanel, { checks }),
3377
+ /* @__PURE__ */ jsx(Box, {
3378
+ marginTop: 1,
3379
+ children: /* @__PURE__ */ jsxs(Text, {
3380
+ bold: true,
3381
+ color: "green",
3382
+ children: [
3383
+ "✔",
3384
+ " ",
3385
+ outroData.message || "AUDIT-3000 complete!"
3386
+ ]
3387
+ })
3388
+ }),
3389
+ outroData.reportFile && /* @__PURE__ */ jsxs(Box, {
3390
+ flexDirection: "column",
3391
+ marginTop: 1,
3392
+ children: [
3393
+ /* @__PURE__ */ jsx(Text, {
3394
+ bold: true,
3395
+ color: "cyan",
3396
+ children: "High-score reel saved to:"
3397
+ }),
3398
+ /* @__PURE__ */ jsx(Text, { children: join(store.session.installDir, outroData.reportFile) }),
3399
+ /* @__PURE__ */ jsx(Text, {
3400
+ dimColor: true,
3401
+ children: "A markdown file in your project folder — open it in any editor to read the full audit."
3402
+ })
3403
+ ]
3404
+ }),
3405
+ /* @__PURE__ */ jsx(AuditChecksOutroSection, {
3406
+ checks,
3407
+ installDir: store.session.installDir
3408
+ }),
3409
+ outroData.docsUrl && /* @__PURE__ */ jsx(Box, {
3410
+ marginTop: 1,
3411
+ children: /* @__PURE__ */ jsxs(Text, { children: ["Learn more: ", /* @__PURE__ */ jsx(Text, {
3412
+ color: "cyan",
3413
+ children: outroData.docsUrl
3414
+ })] })
3415
+ })
3416
+ ]
3417
+ }),
3418
+ outroData.kind === "error" && /* @__PURE__ */ jsxs(Box, {
3419
+ flexDirection: "column",
3420
+ children: [/* @__PURE__ */ jsxs(Text, {
3421
+ color: "red",
3422
+ bold: true,
3423
+ children: [
3424
+ "✘",
3425
+ " ",
3426
+ outroData.message || "An error occurred"
3427
+ ]
3428
+ }), outroData.body && /* @__PURE__ */ jsx(Box, {
3429
+ marginTop: 1,
3430
+ children: /* @__PURE__ */ jsx(Text, {
3431
+ dimColor: true,
3432
+ children: outroData.body
3433
+ })
3434
+ })]
3435
+ }),
3436
+ outroData.kind === "cancel" && /* @__PURE__ */ jsxs(Text, {
3437
+ color: "yellow",
3438
+ children: [
3439
+ "■",
3440
+ " ",
3441
+ outroData.message || "Cancelled"
3442
+ ]
3443
+ }),
3444
+ /* @__PURE__ */ jsx(Box, {
3445
+ marginTop: 1,
3446
+ children: /* @__PURE__ */ jsx(Text, {
3447
+ color: Colors.muted,
3448
+ children: "Press any key to continue"
3449
+ })
3450
+ })
3451
+ ]
3452
+ });
3453
+ };
3454
+ //#endregion
3455
+ //#region src/ui/tui/screens/SetupScreen.tsx
3456
+ /**
3457
+ * SetupScreen — Generic framework disambiguation.
3458
+ *
3459
+ * Iterates unresolved setup questions from the FrameworkConfig
3460
+ * and renders a PickerMenu for each. If all questions are auto-resolved,
3461
+ * this screen is skipped entirely (the router skips it via its show() predicate).
3462
+ */
3463
+ const SetupScreen = ({ store }) => {
3464
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
3465
+ const config = store.session.frameworkConfig;
3466
+ const questions = config?.metadata.setup?.questions ?? [];
3467
+ const [currentIndex, setCurrentIndex] = useState(0);
3468
+ const [resolving, setResolving] = useState(true);
3469
+ useEffect(() => {
3470
+ (async () => {
3471
+ for (const q of questions) {
3472
+ if (q.key in store.session.frameworkContext) continue;
3473
+ try {
3474
+ const detected = await q.detect({ installDir: store.session.installDir });
3475
+ if (detected !== null) store.setFrameworkContext(q.key, detected);
3476
+ } catch {}
3477
+ }
3478
+ setResolving(false);
3479
+ })();
3480
+ }, []);
3481
+ if (resolving) return /* @__PURE__ */ jsx(Box, {
3482
+ flexDirection: "column",
3483
+ flexGrow: 1,
3484
+ children: /* @__PURE__ */ jsx(Text, {
3485
+ dimColor: true,
3486
+ children: "Detecting project configuration..."
3487
+ })
3488
+ });
3489
+ const unresolved = questions.filter((q) => !(q.key in store.session.frameworkContext));
3490
+ if (unresolved.length === 0) return null;
3491
+ const question = unresolved[currentIndex] ?? unresolved[0];
3492
+ if (!question) return null;
3493
+ return /* @__PURE__ */ jsxs(Box, {
3494
+ flexDirection: "column",
3495
+ flexGrow: 1,
3496
+ children: [/* @__PURE__ */ jsxs(Box, {
3497
+ flexDirection: "column",
3498
+ marginBottom: 1,
3499
+ children: [/* @__PURE__ */ jsx(Text, {
3500
+ bold: true,
3501
+ color: Colors.accent,
3502
+ children: "Project Setup"
3503
+ }), config && /* @__PURE__ */ jsxs(Text, {
3504
+ dimColor: true,
3505
+ children: [
3506
+ "Configuring ",
3507
+ config.metadata.name,
3508
+ " integration"
3509
+ ]
3510
+ })]
3511
+ }), /* @__PURE__ */ jsx(PickerMenu, {
3512
+ message: question.message,
3513
+ options: question.options.map((o) => ({
3514
+ label: o.label,
3515
+ value: o.value,
3516
+ hint: o.hint
3517
+ })),
3518
+ onSelect: (value) => {
3519
+ const selected = Array.isArray(value) ? value[0] : value;
3520
+ store.setFrameworkContext(question.key, selected);
3521
+ if (unresolved.filter((q) => q.key !== question.key && !(q.key in store.session.frameworkContext)).length > 0) setCurrentIndex((i) => i + 1);
3522
+ }
3523
+ })]
3524
+ });
3525
+ };
3526
+ //#endregion
3527
+ //#region src/ui/tui/screens/AuthScreen.tsx
3528
+ /**
3529
+ * AuthScreen — Shown while waiting for OAuth authentication.
3530
+ *
3531
+ * Displays framework detection results, beta/disclosure notices,
3532
+ * a waiting spinner, and the login URL when available.
3533
+ * The router resolves past this screen once session.credentials is set.
3534
+ */
3535
+ const AuthScreen = ({ store }) => {
3536
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
3537
+ const { session } = store;
3538
+ const config = session.frameworkConfig;
3539
+ const frameworkLabel = session.detectedFrameworkLabel ?? config?.metadata.name;
3540
+ return /* @__PURE__ */ jsxs(Box, {
3541
+ flexDirection: "column",
3542
+ flexGrow: 1,
3543
+ children: [
3544
+ /* @__PURE__ */ jsxs(Box, {
3545
+ flexDirection: "column",
3546
+ marginBottom: 1,
3547
+ children: [
3548
+ /* @__PURE__ */ jsx(Text, {
3549
+ bold: true,
3550
+ color: Colors.accent,
3551
+ children: "PostHog Setup Wizard"
3552
+ }),
3553
+ frameworkLabel && /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
3554
+ color: "green",
3555
+ children: ["✔", " "]
3556
+ }), /* @__PURE__ */ jsxs(Text, { children: ["Framework: ", frameworkLabel] })] }),
3557
+ config?.metadata.beta && /* @__PURE__ */ jsxs(Text, {
3558
+ color: "yellow",
3559
+ children: [
3560
+ "[BETA] The ",
3561
+ config.metadata.name,
3562
+ " wizard is in beta. Questions or feedback? Email wizard@posthog.com"
3563
+ ]
3564
+ }),
3565
+ config?.metadata.preRunNotice && /* @__PURE__ */ jsx(Text, {
3566
+ color: "yellow",
3567
+ children: config.metadata.preRunNotice
3568
+ })
3569
+ ]
3570
+ }),
3571
+ /* @__PURE__ */ jsx(LoadingBox, { message: "Waiting for authentication..." }),
3572
+ session.loginUrl && /* @__PURE__ */ jsxs(Box, {
3573
+ marginTop: 1,
3574
+ flexDirection: "column",
3575
+ children: [/* @__PURE__ */ jsx(Text, {
3576
+ dimColor: true,
3577
+ children: "If the browser didn't open, copy and paste this URL:"
3578
+ }), /* @__PURE__ */ jsx(Text, {
3579
+ color: "cyan",
3580
+ children: session.loginUrl
3581
+ })]
3582
+ })
3583
+ ]
3584
+ });
3585
+ };
3586
+ //#endregion
3587
+ //#region src/ui/tui/screens/RunScreen.tsx
3588
+ /**
3589
+ * RunScreen — Default observational view of the agent run.
3590
+ *
3591
+ * Tabs: Status (LearnCard + ProgressList), Event plan (when present),
3592
+ * Tail logs, HN. Workflows that need a different tab list ship their own
3593
+ * screen component (see audit/AuditRunScreen.tsx).
3594
+ */
3595
+ const RunScreen = ({ store }) => {
3596
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
3597
+ useFileWatcher(join(store.session.installDir, EVENT_PLAN_FILE), (parsed) => {
3598
+ if (!Array.isArray(parsed)) return;
3599
+ store.setEventPlan(parsed.map((e) => ({
3600
+ name: e.name ?? e.event ?? "",
3601
+ description: e.description ?? ""
3602
+ })));
3603
+ });
3604
+ const [columns] = useStdoutDimensions();
3605
+ const progressItems = store.tasks.map((t) => ({
3606
+ label: t.label,
3607
+ activeForm: t.activeForm,
3608
+ status: t.status
3609
+ }));
3610
+ const queue = store.session.additionalFeatureQueue;
3611
+ if (progressItems.length > 0 && progressItems.every((t) => t.status === "completed") && queue.length > 0) {
3612
+ const nextLabel = ADDITIONAL_FEATURE_LABELS[queue[0]];
3613
+ progressItems.push({
3614
+ label: `Set up ${nextLabel}`,
3615
+ activeForm: `Setting up ${nextLabel}...`,
3616
+ status: "in_progress"
3617
+ });
3618
+ }
3619
+ const statuses = store.statusMessages.length > 0 ? store.statusMessages : void 0;
3620
+ const leftPane = store.learnCardComplete ? /* @__PURE__ */ jsx(TipsCard, { store }) : /* @__PURE__ */ jsx(LearnCard, {
3621
+ store,
3622
+ onComplete: () => store.setLearnCardComplete()
3623
+ });
3624
+ const progressList = /* @__PURE__ */ jsx(ProgressList, {
3625
+ items: progressItems,
3626
+ title: "Tasks"
3627
+ });
3628
+ return /* @__PURE__ */ jsx(TabContainer, {
3629
+ tabs: [
3630
+ {
3631
+ id: "status",
3632
+ label: "Status",
3633
+ component: columns < 80 ? /* @__PURE__ */ jsx(Box, {
3634
+ flexDirection: "column",
3635
+ flexGrow: 1,
3636
+ children: progressList
3637
+ }) : /* @__PURE__ */ jsx(SplitView, {
3638
+ left: leftPane,
3639
+ right: progressList
3640
+ })
3641
+ },
3642
+ ...store.eventPlan.length > 0 ? [{
3643
+ id: "events",
3644
+ label: "Event plan",
3645
+ component: /* @__PURE__ */ jsx(EventPlanViewer, { events: store.eventPlan })
3646
+ }] : [],
3647
+ {
3648
+ id: "logs",
3649
+ label: "Tail logs",
3650
+ component: /* @__PURE__ */ jsx(LogViewer, { filePath: WIZARD_LOG_FILE })
3651
+ },
3652
+ {
3653
+ id: "hn",
3654
+ label: "HN",
3655
+ component: /* @__PURE__ */ jsx(HNViewer, {})
3656
+ }
3657
+ ],
3658
+ statusMessage: statuses,
3659
+ expandableStatus: true,
3660
+ store
3661
+ });
3662
+ };
3663
+ //#endregion
3664
+ //#region src/ui/tui/screens/KeepSkillsScreen.tsx
3665
+ /**
3666
+ * KeepSkillsScreen — Ask whether to keep installed skills in .claude/skills/.
3667
+ *
3668
+ * Shown after the outro summary so users see the agent's output first,
3669
+ * then decide whether to keep the skills that powered it.
3670
+ *
3671
+ * When done, calls store.setSkillsComplete() and exits the process.
3672
+ */
3673
+ const WIZARD_MARKER = ".posthog-wizard";
3674
+ const KeepSkillsScreen = ({ store }) => {
3675
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
3676
+ const [phase, setPhase] = useState("loading");
3677
+ const [skills, setSkills] = useState([]);
3678
+ const skillsDir = join(store.session.installDir, ".claude", "skills");
3679
+ useEffect(() => {
3680
+ (async () => {
3681
+ try {
3682
+ const dirs = (await readdir(skillsDir, { withFileTypes: true })).filter((e) => e.isDirectory());
3683
+ const result = [];
3684
+ for (const dir of dirs) {
3685
+ try {
3686
+ await access(join(skillsDir, dir.name, WIZARD_MARKER));
3687
+ } catch {
3688
+ continue;
3689
+ }
3690
+ const children = (await readdir(join(skillsDir, dir.name))).filter((c) => c !== WIZARD_MARKER);
3691
+ result.push({
3692
+ name: dir.name,
3693
+ children
3694
+ });
3695
+ }
3696
+ if (result.length === 0) {
3697
+ store.setSkillsComplete(true);
3698
+ process.exit(0);
3699
+ }
3700
+ setSkills(result);
3701
+ setPhase("ask");
3702
+ } catch {
3703
+ store.setSkillsComplete(true);
3704
+ process.exit(0);
3705
+ }
3706
+ })();
3707
+ }, []);
3708
+ const handleKeep = () => {
3709
+ store.setSkillsComplete(true);
3710
+ process.exit(0);
3711
+ };
3712
+ const handleRemove = async () => {
3713
+ setPhase("removing");
3714
+ for (const skill of skills) try {
3715
+ await rm(join(skillsDir, skill.name), {
3716
+ recursive: true,
3717
+ force: true
3718
+ });
3719
+ } catch {}
3720
+ try {
3721
+ if ((await readdir(skillsDir)).length === 0) await rm(skillsDir, {
3722
+ recursive: true,
3723
+ force: true
3724
+ });
3725
+ } catch {}
3726
+ setPhase("done");
3727
+ setTimeout(() => {
3728
+ store.setSkillsComplete(false);
3729
+ process.exit(0);
3730
+ }, 600);
3731
+ };
3732
+ return /* @__PURE__ */ jsxs(Box, {
3733
+ flexDirection: "column",
3734
+ flexGrow: 1,
3735
+ children: [/* @__PURE__ */ jsx(Text, {
3736
+ bold: true,
3737
+ color: Colors.accent,
3738
+ children: "Keep the skills?"
3739
+ }), /* @__PURE__ */ jsxs(Box, {
3740
+ marginTop: 1,
3741
+ flexDirection: "column",
3742
+ children: [
3743
+ phase === "loading" && /* @__PURE__ */ jsx(Text, {
3744
+ dimColor: true,
3745
+ children: "Checking installed skills..."
3746
+ }),
3747
+ phase === "ask" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
3748
+ /* @__PURE__ */ jsx(Text, {
3749
+ dimColor: true,
3750
+ children: "The wizard installed open-source skills that help AI coding agents integrate PostHog into your project:"
3751
+ }),
3752
+ /* @__PURE__ */ jsxs(Box, {
3753
+ marginTop: 1,
3754
+ flexDirection: "column",
3755
+ marginLeft: 2,
3756
+ children: [
3757
+ /* @__PURE__ */ jsx(Text, {
3758
+ dimColor: true,
3759
+ children: ".claude/"
3760
+ }),
3761
+ /* @__PURE__ */ jsx(Text, {
3762
+ dimColor: true,
3763
+ children: " skills/"
3764
+ }),
3765
+ skills.map((skill) => /* @__PURE__ */ jsxs(Box, {
3766
+ flexDirection: "column",
3767
+ children: [/* @__PURE__ */ jsxs(Text, {
3768
+ dimColor: true,
3769
+ children: [
3770
+ " ",
3771
+ skill.name,
3772
+ "/"
3773
+ ]
3774
+ }), skill.children.map((child) => /* @__PURE__ */ jsxs(Text, {
3775
+ dimColor: true,
3776
+ children: [" ", child]
3777
+ }, child))]
3778
+ }, skill.name))
3779
+ ]
3780
+ }),
3781
+ /* @__PURE__ */ jsx(Box, {
3782
+ marginTop: 1,
3783
+ children: /* @__PURE__ */ jsxs(Text, {
3784
+ dimColor: true,
3785
+ children: ["Source: ", /* @__PURE__ */ jsx(Text, {
3786
+ color: "cyan",
3787
+ children: "https://github.com/PostHog/context-mill"
3788
+ })]
3789
+ })
3790
+ }),
3791
+ /* @__PURE__ */ jsx(Box, {
3792
+ marginTop: 1,
3793
+ children: /* @__PURE__ */ jsx(ConfirmationInput, {
3794
+ message: "Keep the installed skills?",
3795
+ confirmLabel: "Keep [Enter]",
3796
+ cancelLabel: "Remove [Esc]",
3797
+ onConfirm: handleKeep,
3798
+ onCancel: () => void handleRemove()
3799
+ })
3800
+ })
3801
+ ] }),
3802
+ phase === "removing" && /* @__PURE__ */ jsx(Text, {
3803
+ dimColor: true,
3804
+ children: "Removing skills..."
3805
+ }),
3806
+ phase === "done" && /* @__PURE__ */ jsx(Text, {
3807
+ dimColor: true,
3808
+ children: "Skills removed."
3809
+ })
3810
+ ]
3811
+ })]
3812
+ });
3813
+ };
3814
+ //#endregion
3815
+ //#region src/ui/tui/screens/OutroScreen.tsx
3816
+ /**
3817
+ * OutroScreen — Default post-run summary.
3818
+ *
3819
+ * Renders the success / error / cancel views from `outroData`. Workflows
3820
+ * that need a different success view (e.g. with extra summary content)
3821
+ * ship their own screen component (see audit/AuditOutroScreen.tsx).
3822
+ */
3823
+ const OutroScreen = ({ store }) => {
3824
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
3825
+ useInput(() => {
3826
+ store.setOutroDismissed();
3827
+ });
3828
+ const outroData = store.session.outroData;
3829
+ if (!outroData) return /* @__PURE__ */ jsx(Box, {
3830
+ flexDirection: "column",
3831
+ flexGrow: 1,
3832
+ children: /* @__PURE__ */ jsx(Text, {
3833
+ dimColor: true,
3834
+ children: "Finishing up..."
3835
+ })
3836
+ });
3837
+ return /* @__PURE__ */ jsxs(Box, {
3838
+ flexDirection: "column",
3839
+ flexGrow: 1,
3840
+ children: [
3841
+ outroData.kind === "success" && /* @__PURE__ */ jsxs(Box, {
3842
+ flexDirection: "column",
3843
+ children: [
3844
+ /* @__PURE__ */ jsxs(Text, {
3845
+ color: "green",
3846
+ bold: true,
3847
+ children: ["✔ ", outroData.message || "Done!"]
3848
+ }),
3849
+ outroData.body && /* @__PURE__ */ jsx(Box, {
3850
+ marginTop: 1,
3851
+ children: /* @__PURE__ */ jsx(Text, {
3852
+ dimColor: true,
3853
+ children: outroData.body
3854
+ })
3855
+ }),
3856
+ outroData.reportFile && /* @__PURE__ */ jsx(Box, {
3857
+ marginTop: 1,
3858
+ children: /* @__PURE__ */ jsxs(Text, { children: [
3859
+ "Check ",
3860
+ /* @__PURE__ */ jsxs(Text, {
3861
+ bold: true,
3862
+ children: ["./", outroData.reportFile]
3863
+ }),
3864
+ " for details"
3865
+ ] })
3866
+ }),
3867
+ outroData.changes && outroData.changes.length > 0 && /* @__PURE__ */ jsxs(Box, {
3868
+ flexDirection: "column",
3869
+ marginTop: 1,
3870
+ children: [/* @__PURE__ */ jsx(Text, {
3871
+ color: "cyan",
3872
+ bold: true,
3873
+ children: "What the agent did:"
3874
+ }), outroData.changes.map((change, i) => /* @__PURE__ */ jsxs(Text, { children: ["• ", change] }, i))]
3875
+ }),
3876
+ store.eventPlan.length > 0 && /* @__PURE__ */ jsxs(Box, {
3877
+ flexDirection: "column",
3878
+ marginTop: 1,
3879
+ children: [/* @__PURE__ */ jsx(Text, {
3880
+ color: "cyan",
3881
+ bold: true,
3882
+ children: "Events added:"
3883
+ }), store.eventPlan.map((event) => /* @__PURE__ */ jsxs(Text, { children: [
3884
+ "• ",
3885
+ /* @__PURE__ */ jsx(Text, {
3886
+ bold: true,
3887
+ children: event.name
3888
+ }),
3889
+ /* @__PURE__ */ jsxs(Text, {
3890
+ dimColor: true,
3891
+ children: [" ", event.description]
3892
+ })
3893
+ ] }, event.name))]
3894
+ }),
3895
+ outroData.docsUrl && /* @__PURE__ */ jsx(Box, {
3896
+ marginTop: 1,
3897
+ children: /* @__PURE__ */ jsxs(Text, { children: ["Learn more: ", /* @__PURE__ */ jsx(Text, {
3898
+ color: "cyan",
3899
+ children: outroData.docsUrl
3900
+ })] })
3901
+ }),
3902
+ outroData.continueUrl && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [
3903
+ "Continue onboarding:",
3904
+ " ",
3905
+ /* @__PURE__ */ jsx(Text, {
3906
+ color: "cyan",
3907
+ children: outroData.continueUrl
3908
+ })
3909
+ ] }) }),
3910
+ /* @__PURE__ */ jsx(Box, {
3911
+ marginTop: 1,
3912
+ children: /* @__PURE__ */ jsx(Text, {
3913
+ dimColor: true,
3914
+ children: "Note: This wizard uses an LLM agent to analyze and modify your project. Please review the changes made."
3915
+ })
3916
+ }),
3917
+ /* @__PURE__ */ jsx(Text, {
3918
+ dimColor: true,
3919
+ children: "How did this work for you? Drop us a line: wizard@posthog.com"
3920
+ })
3921
+ ]
3922
+ }),
3923
+ outroData.kind === "error" && /* @__PURE__ */ jsxs(Box, {
3924
+ flexDirection: "column",
3925
+ children: [
3926
+ /* @__PURE__ */ jsxs(Text, {
3927
+ color: "red",
3928
+ bold: true,
3929
+ children: ["✘ ", outroData.message || "An error occurred"]
3930
+ }),
3931
+ outroData.body && /* @__PURE__ */ jsx(Box, {
3932
+ marginTop: 1,
3933
+ children: /* @__PURE__ */ jsx(Text, {
3934
+ dimColor: true,
3935
+ children: outroData.body
3936
+ })
3937
+ }),
3938
+ outroData.docsUrl && /* @__PURE__ */ jsx(Box, {
3939
+ marginTop: 1,
3940
+ children: /* @__PURE__ */ jsxs(Text, { children: ["Docs: ", /* @__PURE__ */ jsx(Text, {
3941
+ color: "cyan",
3942
+ children: outroData.docsUrl
3943
+ })] })
3944
+ })
3945
+ ]
3946
+ }),
3947
+ outroData.kind === "cancel" && /* @__PURE__ */ jsx(Box, {
3948
+ flexDirection: "column",
3949
+ children: /* @__PURE__ */ jsxs(Text, {
3950
+ color: "yellow",
3951
+ children: ["■ ", outroData.message || "Cancelled"]
3952
+ })
3953
+ }),
3954
+ /* @__PURE__ */ jsx(Box, {
3955
+ marginTop: 1,
3956
+ children: /* @__PURE__ */ jsx(Text, {
3957
+ color: Colors.muted,
3958
+ children: "Press any key to continue"
3959
+ })
3960
+ })
3961
+ ]
3962
+ });
3963
+ };
3964
+ //#endregion
3965
+ //#region src/ui/tui/screens/ExitScreen.tsx
3966
+ /**
3967
+ * ExitScreen — Final step in every workflow.
3968
+ *
3969
+ * Renders nothing. Immediately exits the process.
3970
+ * The cleanup handler in start-tui.ts handles the exit summary line.
3971
+ */
3972
+ const ExitScreen = () => {
3973
+ useEffect(() => {
3974
+ process.exit(0);
3975
+ }, []);
3976
+ return null;
3977
+ };
3978
+ //#endregion
3979
+ //#region src/ui/tui/screens/AuthErrorScreen.tsx
3980
+ /**
3981
+ * AuthErrorScreen — Shown when the Anthropic API returns a 401.
3982
+ *
3983
+ * Claude Code's own auth can conflict with the wizard's OAuth token.
3984
+ * This overlay tells the user to log out of Claude Code and retry.
3985
+ */
3986
+ const AuthErrorScreen = () => {
3987
+ useInput(() => {
3988
+ process.exit(1);
3989
+ });
3990
+ return /* @__PURE__ */ jsxs(Box, {
3991
+ flexDirection: "column",
3992
+ flexGrow: 1,
3993
+ children: [
3994
+ /* @__PURE__ */ jsxs(Text, {
3995
+ color: "red",
3996
+ bold: true,
3997
+ children: ["✘", " Authentication error"]
3998
+ }),
3999
+ /* @__PURE__ */ jsx(Box, {
4000
+ flexDirection: "column",
4001
+ marginTop: 1,
4002
+ children: /* @__PURE__ */ jsx(Text, { children: "The Wizard couldn't connect to the PostHog LLM Gateway. If you use Claude Code, its credentials might conflict with the Wizard." })
4003
+ }),
4004
+ /* @__PURE__ */ jsx(Box, {
4005
+ marginTop: 1,
4006
+ children: /* @__PURE__ */ jsx(Text, {
4007
+ dimColor: true,
4008
+ children: "Try logging out of Claude Code temporarily and re-running the Wizard by running:"
4009
+ })
4010
+ }),
4011
+ /* @__PURE__ */ jsx(Box, {
4012
+ flexDirection: "column",
4013
+ marginTop: 1,
4014
+ paddingLeft: 2,
4015
+ children: /* @__PURE__ */ jsx(Text, {
4016
+ color: "cyan",
4017
+ children: "claude auth logout"
4018
+ })
4019
+ }),
4020
+ /* @__PURE__ */ jsx(Box, {
4021
+ marginTop: 1,
4022
+ children: /* @__PURE__ */ jsx(Text, {
4023
+ color: Colors.muted,
4024
+ children: "Press any key to exit"
4025
+ })
4026
+ })
4027
+ ]
4028
+ });
4029
+ };
4030
+ //#endregion
4031
+ //#region src/ui/tui/services/mcp-installer.ts
4032
+ /**
4033
+ * McpInstaller — service layer between McpScreen and MCP business logic.
4034
+ *
4035
+ * Decouples the screen from step internals. Testable, swappable,
4036
+ * no dynamic imports in React components.
4037
+ */
4038
+ /**
4039
+ * Production McpInstaller backed by real MCP client detection and installation.
4040
+ */
4041
+ function createMcpInstaller() {
4042
+ let cachedClients = [];
4043
+ return {
4044
+ async detectClients() {
4045
+ const supported = await getSupportedClients();
4046
+ cachedClients = supported.map((c) => ({
4047
+ name: c.name,
4048
+ raw: c
4049
+ }));
4050
+ return supported.map((c) => ({
4051
+ name: c.name,
4052
+ supportsPlugin: isPluginCapable(c) && c.supportsPlugin()
4053
+ }));
4054
+ },
4055
+ async install(clientNames, features, apiKey) {
4056
+ const resolvedFeatures = features ?? [...ALL_FEATURE_VALUES];
4057
+ const toInstall = cachedClients.filter((c) => clientNames.includes(c.name)).map((c) => c.raw);
4058
+ if (toInstall.length === 0) {
4059
+ logToFile(`[McpInstaller] No clients matched. clientNames=${JSON.stringify(clientNames)}, cached=${JSON.stringify(cachedClients.map((c) => c.name))}`);
4060
+ return [];
4061
+ }
4062
+ const installed = [];
4063
+ for (const client of toInstall) try {
4064
+ if ((await client.addServer(apiKey, resolvedFeatures, false))?.success) installed.push(client.name);
4065
+ else logToFile(`[McpInstaller] addServer returned success=false for ${client.name}`);
4066
+ } catch (err) {
4067
+ logToFile(`[McpInstaller] addServer threw for ${client.name}: ${err instanceof Error ? err.message : String(err)}`);
4068
+ }
4069
+ return installed;
4070
+ },
4071
+ async remove() {
4072
+ const installed = await getInstalledClients();
4073
+ if (installed.length === 0) return [];
4074
+ await removeMCPServer(installed);
4075
+ return installed.map((c) => c.name);
4076
+ },
4077
+ async installPlugins(clientNames) {
4078
+ const pluginClients = getSupportedPluginClients(cachedClients.filter((c) => clientNames.includes(c.name)).map((c) => c.raw));
4079
+ const installed = await installPlugins(pluginClients);
4080
+ analytics.wizardCapture("mcp plugins installed", {
4081
+ clients: installed,
4082
+ attempted: pluginClients.map((c) => c.name)
4083
+ });
4084
+ return installed;
4085
+ }
4086
+ };
4087
+ }
4088
+ //#endregion
4089
+ //#region src/ui/tui/screen-registry.tsx
4090
+ function createServices() {
4091
+ return { mcpInstaller: createMcpInstaller() };
4092
+ }
4093
+ function createScreens(store, services) {
4094
+ return {
4095
+ ["settings-override"]: /* @__PURE__ */ jsx(SettingsOverrideScreen, { store }),
4096
+ ["managed-settings"]: /* @__PURE__ */ jsx(ManagedSettingsScreen, { store }),
4097
+ ["port-conflict"]: /* @__PURE__ */ jsx(PortConflictScreen, { store }),
4098
+ ["auth-error"]: /* @__PURE__ */ jsx(AuthErrorScreen, {}),
4099
+ ["intro"]: /* @__PURE__ */ jsx(PostHogIntegrationIntroScreen, { store }),
4100
+ ["revenue-intro"]: /* @__PURE__ */ jsx(RevenueIntroScreen, { store }),
4101
+ ["agent-skill-intro"]: /* @__PURE__ */ jsx(AgentSkillIntroScreen, { store }),
4102
+ ["audit-intro"]: /* @__PURE__ */ jsx(AuditIntroScreen, { store }),
4103
+ ["audit-run"]: /* @__PURE__ */ jsx(AuditRunScreen, { store }),
4104
+ ["audit-outro"]: /* @__PURE__ */ jsx(AuditOutroScreen, { store }),
4105
+ ["audit-3000-intro"]: /* @__PURE__ */ jsx(Audit3000IntroScreen, { store }),
4106
+ ["audit-3000-run"]: /* @__PURE__ */ jsx(Audit3000RunScreen, { store }),
4107
+ ["audit-3000-outro"]: /* @__PURE__ */ jsx(Audit3000OutroScreen, { store }),
4108
+ ["health-check"]: /* @__PURE__ */ jsx(HealthCheckScreen, { store }),
4109
+ ["doctor-intro"]: /* @__PURE__ */ jsx(DoctorIntroScreen, { store }),
4110
+ ["doctor-report"]: /* @__PURE__ */ jsx(DoctorReportScreen, { store }),
4111
+ ["setup"]: /* @__PURE__ */ jsx(SetupScreen, { store }),
4112
+ ["auth"]: /* @__PURE__ */ jsx(AuthScreen, { store }),
4113
+ ["run"]: /* @__PURE__ */ jsx(RunScreen, { store }),
4114
+ ["mcp"]: /* @__PURE__ */ jsx(McpScreen, {
4115
+ store,
4116
+ installer: services.mcpInstaller
4117
+ }),
4118
+ ["keep-skills"]: /* @__PURE__ */ jsx(KeepSkillsScreen, { store }),
4119
+ ["outro"]: /* @__PURE__ */ jsx(OutroScreen, { store }),
4120
+ ["exit"]: /* @__PURE__ */ jsx(ExitScreen, {}),
4121
+ ["mcp-add"]: /* @__PURE__ */ jsx(McpScreen, {
4122
+ store,
4123
+ installer: services.mcpInstaller
4124
+ }),
4125
+ ["mcp-remove"]: /* @__PURE__ */ jsx(McpScreen, {
4126
+ store,
4127
+ installer: services.mcpInstaller,
4128
+ mode: "remove"
4129
+ })
4130
+ };
4131
+ }
4132
+ //#endregion
4133
+ //#region src/ui/tui/App.tsx
4134
+ const App = ({ store }) => {
4135
+ const services = useMemo(() => createServices(), []);
4136
+ return /* @__PURE__ */ jsx(ScreenContainer, {
4137
+ store,
4138
+ screens: useMemo(() => createScreens(store, services), [store, services])
4139
+ });
4140
+ };
4141
+ //#endregion
4142
+ //#region src/ui/tui/start-tui.ts
4143
+ /**
4144
+ * start-tui.ts — Sets up the Ink TUI renderer and InkUI.
4145
+ *
4146
+ * Renders in the terminal's alternate screen buffer so the wizard
4147
+ * doesn't pollute scrollback history. On exit, the previous terminal
4148
+ * content is restored and a single exit summary line is printed.
4149
+ */
4150
+ const RESET_ATTRS = "\x1B[0m";
4151
+ const CLEAR_SCREEN = "\x1B[2J";
4152
+ const CURSOR_HOME = "\x1B[H";
4153
+ const BG_BLACK = "\x1B[48;2;0;0;0m";
4154
+ const ENTER_ALT_SCREEN = "\x1B[?1049h";
4155
+ const LEAVE_ALT_SCREEN = "\x1B[?1049l";
4156
+ const GREEN = "\x1B[32m";
4157
+ const BOLD = "\x1B[1m";
4158
+ const DIM = "\x1B[2m";
4159
+ function releaseTerminal() {
4160
+ process.stdout.write(RESET_ATTRS + LEAVE_ALT_SCREEN);
4161
+ }
4162
+ function getExitLine(store) {
4163
+ const outro = store.session.outroData;
4164
+ const label = store.session.workflowLabel ?? "Wizard";
4165
+ if (outro?.kind === "success") {
4166
+ const message = outro.message ?? `${label} completed successfully.`;
4167
+ return `${GREEN}${BOLD}\u2714${RESET_ATTRS} ${message}${outro.reportFile && !message.includes(outro.reportFile) ? ` Check ./${outro.reportFile} for details.` : ""}`;
4168
+ }
4169
+ return `${DIM}${label} exited.${RESET_ATTRS}`;
4170
+ }
4171
+ function startTUI(version, flow = "posthog-integration") {
4172
+ process.stdout.write(ENTER_ALT_SCREEN + BG_BLACK + CLEAR_SCREEN + CURSOR_HOME);
4173
+ const store = new WizardStore(flow);
4174
+ store.version = version;
4175
+ setUI(new InkUI(store));
4176
+ const { unmount: inkUnmount } = render(createElement(App, { store }));
4177
+ let cleaned = false;
4178
+ const cleanup = () => {
4179
+ if (cleaned) return;
4180
+ cleaned = true;
4181
+ inkUnmount();
4182
+ releaseTerminal();
4183
+ process.stdout.write(getExitLine(store) + "\n");
4184
+ };
4185
+ process.on("exit", cleanup);
4186
+ return {
4187
+ unmount: cleanup,
4188
+ store,
4189
+ waitForSetup: () => store.getGate("intro")
4190
+ };
4191
+ }
4192
+ //#endregion
4193
+ export { startTUI };
4194
+
4195
+ //# sourceMappingURL=start-tui-B_zwutLe.js.map