@posthog/wizard 2.74.1 → 2.76.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.
- package/README.md +31 -17
- package/dist/README.md +10 -0
- package/dist/{add-mcp-server-to-clients-CBtInAoH.js → add-mcp-server-to-clients-Exddicp2.js} +5 -5
- package/dist/{add-mcp-server-to-clients-CBtInAoH.js.map → add-mcp-server-to-clients-Exddicp2.js.map} +1 -1
- package/dist/{agent-interface-lCIi_alS.js → agent-interface-CZWRgZMc.js} +204 -376
- package/dist/agent-interface-CZWRgZMc.js.map +1 -0
- package/dist/{agent-prompt-loader-CSxm81TM.js → agent-prompt-loader-BobuLDIg.js} +5 -5
- package/dist/{agent-prompt-loader-CSxm81TM.js.map → agent-prompt-loader-BobuLDIg.js.map} +1 -1
- package/dist/{agent-runner-DVrkOFDA.js → agent-runner-C_R1X_UT.js} +158 -45
- package/dist/agent-runner-C_R1X_UT.js.map +1 -0
- package/dist/{analytics-CeVMOExk.js → analytics-C_RyE8fz.js} +7 -4
- package/dist/analytics-C_RyE8fz.js.map +1 -0
- package/dist/analytics-DXvdQqyp.js +2 -0
- package/dist/{api-2nAofyKY.js → api-k5aGQpIZ.js} +3 -3
- package/dist/{api-2nAofyKY.js.map → api-k5aGQpIZ.js.map} +1 -1
- package/dist/bin.js +164 -4087
- package/dist/bin.js.map +1 -1
- package/dist/{bounded-fs-DmcmFPRq.js → bounded-fs-BHLy83N9.js} +3 -3
- package/dist/{bounded-fs-DmcmFPRq.js.map → bounded-fs-BHLy83N9.js.map} +1 -1
- package/dist/{ci-install-lwYLQ4Yw.js → ci-install-CXt4c41q.js} +4 -4
- package/dist/{ci-install-lwYLQ4Yw.js.map → ci-install-CXt4c41q.js.map} +1 -1
- package/dist/{debug-BvheXnxB.js → debug-BxMMpepn.js} +1 -1
- package/dist/{debug-GxjVplPx.js → debug-C5Zz8hmm.js} +6 -4
- package/dist/debug-C5Zz8hmm.js.map +1 -0
- package/dist/{defaults-DI642M3n.js → defaults-DulzMJpN.js} +1 -1
- package/dist/{defaults-DI642M3n.js.map → defaults-DulzMJpN.js.map} +1 -1
- package/dist/{env-api-key-BMXH7_4u.js → env-api-key-Ca-CrkgV.js} +2 -2
- package/dist/{env-api-key-BMXH7_4u.js.map → env-api-key-Ca-CrkgV.js.map} +1 -1
- package/dist/{environment-C3nfPBu-.js → environment-B0uTamFf.js} +4 -4
- package/dist/{environment-C3nfPBu-.js.map → environment-B0uTamFf.js.map} +1 -1
- package/dist/file-CNstcW8F.js +60 -0
- package/dist/file-CNstcW8F.js.map +1 -0
- package/dist/{file-watcher-BGElIpJi.js → file-watcher-CiT-oZpu.js} +2 -2
- package/dist/{file-watcher-BGElIpJi.js.map → file-watcher-CiT-oZpu.js.map} +1 -1
- package/dist/gateway-session-BIVN_Xfr.js +2 -0
- package/dist/gateway-session-C3Uqwc4R.js +271 -0
- package/dist/gateway-session-C3Uqwc4R.js.map +1 -0
- package/dist/{headless-ui-jkS2Dm_G.js → headless-ui-TpSp-F43.js} +10 -5
- package/dist/headless-ui-TpSp-F43.js.map +1 -0
- package/dist/{interactive-CrQWwMr2.js → interactive-B72L-sO5.js} +2 -2
- package/dist/{interactive-CrQWwMr2.js.map → interactive-B72L-sO5.js.map} +1 -1
- package/dist/{mcp-S3yX8Uz1.js → mcp-CNc6CLK0.js} +2 -2
- package/dist/{mcp-S3yX8Uz1.js.map → mcp-CNc6CLK0.js.map} +1 -1
- package/dist/{mcp-prompt-streaming-BvvCZBYa.js → mcp-prompt-streaming-BvBNkzjv.js} +6 -5
- package/dist/{mcp-prompt-streaming-BvvCZBYa.js.map → mcp-prompt-streaming-BvBNkzjv.js.map} +1 -1
- package/dist/mint-failure-CZ5FZ0Jd.js +5739 -0
- package/dist/mint-failure-CZ5FZ0Jd.js.map +1 -0
- package/dist/{non-interactive-B-MIn3vt.js → non-interactive-UeQlnU0U.js} +2 -2
- package/dist/{non-interactive-B-MIn3vt.js.map → non-interactive-UeQlnU0U.js.map} +1 -1
- package/dist/{orchestrator-tools-CefodRf2.js → orchestrator-tools-CLADku26.js} +5 -5
- package/dist/orchestrator-tools-CLADku26.js.map +1 -0
- package/dist/{package-manager-pB6S0gyu.js → package-manager-CppzeIgu.js} +2 -2
- package/dist/{package-manager-pB6S0gyu.js.map → package-manager-CppzeIgu.js.map} +1 -1
- package/dist/{pi-Dbw8VakI.js → pi-Bs1Tbt0C.js} +35 -24
- package/dist/pi-Bs1Tbt0C.js.map +1 -0
- package/dist/{playground-C7DwGktN.js → playground-CZgZX4j0.js} +8 -8
- package/dist/{playground-C7DwGktN.js.map → playground-CZgZX4j0.js.map} +1 -1
- package/dist/{posthog-B3RuLfxH.js → posthog-BZ3NNCyh.js} +2 -2
- package/dist/{posthog-B3RuLfxH.js.map → posthog-BZ3NNCyh.js.map} +1 -1
- package/dist/{provisioning-B4Lr3-aS.js → provisioning-Ba7GWKzK.js} +3 -3
- package/dist/{provisioning-B4Lr3-aS.js.map → provisioning-Ba7GWKzK.js.map} +1 -1
- package/dist/{queue-tools-BveMVXQm.js → queue-tools-D09PNS0X.js} +17 -5
- package/dist/queue-tools-D09PNS0X.js.map +1 -0
- package/dist/{registry-D8MIZ9kJ.js → registry-COcpEsMD.js} +5 -5
- package/dist/{registry-D8MIZ9kJ.js.map → registry-COcpEsMD.js.map} +1 -1
- package/dist/{security-B_Eo15wD.js → security-CpAT0fZa.js} +5 -5
- package/dist/{security-B_Eo15wD.js.map → security-CpAT0fZa.js.map} +1 -1
- package/dist/{setup-utils-D3OK2RE_.js → setup-utils-BUAoRjw3.js} +48 -9
- package/dist/setup-utils-BUAoRjw3.js.map +1 -0
- package/dist/smoke-test-ci.sh +9 -14
- package/dist/{start-tui-B7u962uh.js → start-tui-Bz23_yym.js} +848 -127
- package/dist/start-tui-Bz23_yym.js.map +1 -0
- package/dist/{steps-D4mHkYX4.js → steps-DbsDish5.js} +7 -7
- package/dist/{steps-D4mHkYX4.js.map → steps-DbsDish5.js.map} +1 -1
- package/dist/{store-Cn1VK6U0.js → store-D5ROpUje.js} +63 -10
- package/dist/store-D5ROpUje.js.map +1 -0
- package/dist/{subagent-1UEmD7bE.js → subagent-CSBZnhgK.js} +2 -2
- package/dist/{subagent-1UEmD7bE.js.map → subagent-CSBZnhgK.js.map} +1 -1
- package/dist/{task-DNHNZMO0.js → task-D8sTR7jZ.js} +23 -23
- package/dist/task-D8sTR7jZ.js.map +1 -0
- package/dist/{task-stream-BJrRzR9K.js → task-stream-Dl0XEsZ9.js} +49 -7
- package/dist/task-stream-Dl0XEsZ9.js.map +1 -0
- package/dist/{tasks-DSEJ8GZC.js → tasks-BDt427kT.js} +2 -2
- package/dist/{tasks-DSEJ8GZC.js.map → tasks-BDt427kT.js.map} +1 -1
- package/dist/{telemetry-DWkTy6TJ.js → telemetry-Dp6zjZBj.js} +3 -3
- package/dist/{telemetry-DWkTy6TJ.js.map → telemetry-Dp6zjZBj.js.map} +1 -1
- package/dist/{terminal-DNnNkvFu.js → terminal-pInlleVD.js} +20 -18
- package/dist/{terminal-DNnNkvFu.js.map → terminal-pInlleVD.js.map} +1 -1
- package/dist/{tools-HHK4XbnC.js → tools-MkyMOjQI.js} +98 -28
- package/dist/tools-MkyMOjQI.js.map +1 -0
- package/dist/{wizard-abort-BVmjqUAQ.js → wizard-abort-BCnUgT9z.js} +4 -4
- package/dist/{wizard-abort-BVmjqUAQ.js.map → wizard-abort-BCnUgT9z.js.map} +1 -1
- package/dist/{wizard-abort-CG_xzEFN.js → wizard-abort-BHLKVKLN.js} +1 -1
- package/dist/{wizard-session-BWAQoi9q.js → wizard-session-CYvyYnti.js} +1 -1
- package/dist/{wizard-session-GdDN_aMS.js → wizard-session-FSJCkTcP.js} +9 -2
- package/dist/wizard-session-FSJCkTcP.js.map +1 -0
- package/dist/{yara-hooks-qRkdjYiV.js → yara-hooks-BJWVgTpU.js} +4 -4
- package/dist/{yara-hooks-qRkdjYiV.js.map → yara-hooks-BJWVgTpU.js.map} +1 -1
- package/dist/yara-hooks-Dp82xJGd.js +2 -0
- package/package.json +1 -1
- package/dist/agent-interface-lCIi_alS.js.map +0 -1
- package/dist/agent-runner-DVrkOFDA.js.map +0 -1
- package/dist/analytics-3H_AlflZ.js +0 -2
- package/dist/analytics-CeVMOExk.js.map +0 -1
- package/dist/debug-GxjVplPx.js.map +0 -1
- package/dist/headless-ui-jkS2Dm_G.js.map +0 -1
- package/dist/orchestrator-tools-CefodRf2.js.map +0 -1
- package/dist/pi-Dbw8VakI.js.map +0 -1
- package/dist/posthog-integration-su2LXi3-.js +0 -1475
- package/dist/posthog-integration-su2LXi3-.js.map +0 -1
- package/dist/queue-tools-BveMVXQm.js.map +0 -1
- package/dist/setup-utils-D3OK2RE_.js.map +0 -1
- package/dist/start-tui-B7u962uh.js.map +0 -1
- package/dist/store-Cn1VK6U0.js.map +0 -1
- package/dist/task-DNHNZMO0.js.map +0 -1
- package/dist/task-stream-BJrRzR9K.js.map +0 -1
- package/dist/tools-HHK4XbnC.js.map +0 -1
- package/dist/wizard-session-GdDN_aMS.js.map +0 -1
- package/dist/yara-hooks-BOMbtTOr.js +0 -2
package/dist/bin.js
CHANGED
|
@@ -1,39 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { d as ErrorCodes, i as detectErrorCode,
|
|
3
|
-
import {
|
|
2
|
+
import { d as ErrorCodes, i as detectErrorCode, t as classifyRunFailure, u as emitWizardError } from "./errors--OqLxmWR.js";
|
|
3
|
+
import { Ct as isHeadless, K as POSTHOG_DOCS_URL, St as headlessOption, a as getLogFilePath, gt as VERSION, h as LoggingUI, m as setUI, mt as getSkillsBaseUrl, p as getUI, s as logToFile, wt as regionOption } from "./debug-C5Zz8hmm.js";
|
|
4
4
|
import { a as getLocalDev, i as checkLocalServices, o as initLocalDev, r as POSTHOG_LOCAL_URL, s as localMcpSkillsNotice } from "./local-dev-DPZiIPCK.js";
|
|
5
|
-
import "./wizard-session-
|
|
6
|
-
import { n as analytics } from "./analytics-
|
|
7
|
-
import { r as setEntryCommand } from "./telemetry-
|
|
8
|
-
import {
|
|
9
|
-
import { n as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { A as
|
|
14
|
-
import {
|
|
15
|
-
import { i as SPINNER_MESSAGE, t as FRAMEWORK_REGISTRY } from "./registry-D8MIZ9kJ.js";
|
|
16
|
-
import { S as SETUP_REPORT_FILE, _ as AUDIT_CHECKS_KEY, g as AUDIT_CHECKS_FILE, v as AUDIT_REPORT_FILE } from "./yara-hooks-qRkdjYiV.js";
|
|
17
|
-
import { S as writeJsonAtomic } from "./queue-tools-BveMVXQm.js";
|
|
18
|
-
import { _ as detectIntegrationProjects, a as LINE_CHART_BLOCK, d as isClearBlock, g as HEALTH_CHECK_STEP, h as Icons, i as FUNNEL_BLOCK, m as Colors, n as integrationRunStep, o as PRODUCT_SUITE_BLOCK, r as posthogIntegrationConfig, s as StatusPeekTrigger, u as wordWrap, v as scopeInstallDirToProject } from "./posthog-integration-su2LXi3-.js";
|
|
19
|
-
import { n as readApiKeyFromEnv } from "./env-api-key-BMXH7_4u.js";
|
|
5
|
+
import "./wizard-session-FSJCkTcP.js";
|
|
6
|
+
import { n as analytics } from "./analytics-C_RyE8fz.js";
|
|
7
|
+
import { r as setEntryCommand } from "./telemetry-Dp6zjZBj.js";
|
|
8
|
+
import { r as runCleanups } from "./wizard-abort-BCnUgT9z.js";
|
|
9
|
+
import { n as isNonInteractiveEnvironment } from "./environment-B0uTamFf.js";
|
|
10
|
+
import { G as fetchSkillMenu, R as WIZARD_TOOL_NAMES, u as recoverOrphanedSettingsBackups } from "./agent-interface-CZWRgZMc.js";
|
|
11
|
+
import { c as maybeStampAiSdkDetected, n as runAgent, x as authenticate } from "./agent-runner-C_R1X_UT.js";
|
|
12
|
+
import { g as AUDIT_CHECKS_FILE, x as getAuditChecks } from "./yara-hooks-BJWVgTpU.js";
|
|
13
|
+
import { $ as wordWrap, A as installOrUpdatePostHogCli, B as webAnalyticsDoctorConfig, D as CLI_STEERING_TARGETS, E as errorTrackingUploadSourceMapsConfig, G as createSkillProgram, H as getKindMeta, K as warehouseSourceConfig, O as detectTargets, S as errorTrackingConfig, T as replayVisionConfig, U as fetchHealthIssues, V as posthogDoctorConfig, W as auditConfig, d as metricsConfig, f as aiObservabilityConfig, i as isRunFailure, it as Icons, j as installSteeringSnippet, k as findTarget, m as selfDrivingConfig, o as Program, p as mcpAnalyticsConfig, q as revenueAnalyticsConfig, rt as Colors, s as agentSkillConfig, u as getProgramConfig, z as migrationConfig } from "./mint-failure-CZ5FZ0Jd.js";
|
|
14
|
+
import { n as readApiKeyFromEnv } from "./env-api-key-Ca-CrkgV.js";
|
|
20
15
|
import { satisfies } from "semver";
|
|
21
16
|
import { Agent, setGlobalDispatcher } from "undici";
|
|
22
17
|
import yargs from "yargs";
|
|
23
18
|
import { hideBin } from "yargs/helpers";
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import * as os from "node:os";
|
|
27
|
-
import * as path$2 from "node:path";
|
|
28
|
-
import { join as join$1 } from "node:path";
|
|
29
|
-
import { spawnSync } from "node:child_process";
|
|
30
|
-
import * as fs$1 from "node:fs";
|
|
31
|
-
import axios from "axios";
|
|
32
|
-
import { z } from "zod";
|
|
19
|
+
import * as path$1 from "node:path";
|
|
20
|
+
import { join } from "node:path";
|
|
33
21
|
import { Box, Text, render, useInput, useStdout } from "ink";
|
|
34
22
|
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
35
23
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
36
|
-
import { access, rm } from "node:fs/promises";
|
|
37
24
|
import Fuse from "fuse.js";
|
|
38
25
|
import * as readline from "node:readline/promises";
|
|
39
26
|
//#region src/commands/command.ts
|
|
@@ -243,7 +230,7 @@ function runProvision(argv) {
|
|
|
243
230
|
}
|
|
244
231
|
async function provision({ email, region, name, baseUrl, jsonMode }) {
|
|
245
232
|
try {
|
|
246
|
-
const { provisionNewAccount } = await import("./provisioning-
|
|
233
|
+
const { provisionNewAccount } = await import("./provisioning-Ba7GWKzK.js").then((n) => n.r);
|
|
247
234
|
if (!jsonMode) getUI().log.info(`Provisioning account for ${email} in ${region}...`);
|
|
248
235
|
emitResult(await provisionNewAccount(email, name, region, { baseUrl }), jsonMode);
|
|
249
236
|
process.exit(0);
|
|
@@ -310,3997 +297,27 @@ const basicIntegrationCommand = {
|
|
|
310
297
|
setEntryCommand("integrate");
|
|
311
298
|
(async () => {
|
|
312
299
|
if (isHeadless(argv)) {
|
|
313
|
-
const { runHeadlessInstall } = await import("./ci-install-
|
|
300
|
+
const { runHeadlessInstall } = await import("./ci-install-CXt4c41q.js");
|
|
314
301
|
return runHeadlessInstall(argv);
|
|
315
302
|
}
|
|
316
303
|
if (argv.ci) {
|
|
317
|
-
const { runCIInstall } = await import("./ci-install-
|
|
304
|
+
const { runCIInstall } = await import("./ci-install-CXt4c41q.js");
|
|
318
305
|
return runCIInstall(argv);
|
|
319
306
|
}
|
|
320
307
|
if (isNonInteractiveEnvironment()) {
|
|
321
|
-
const { failNonInteractive } = await import("./non-interactive-
|
|
308
|
+
const { failNonInteractive } = await import("./non-interactive-UeQlnU0U.js");
|
|
322
309
|
return failNonInteractive();
|
|
323
310
|
}
|
|
324
311
|
if (argv.playground) {
|
|
325
|
-
const { runPlayground } = await import("./playground-
|
|
312
|
+
const { runPlayground } = await import("./playground-CZgZX4j0.js");
|
|
326
313
|
return runPlayground();
|
|
327
314
|
}
|
|
328
|
-
const { runInteractive } = await import("./interactive-
|
|
315
|
+
const { runInteractive } = await import("./interactive-B72L-sO5.js");
|
|
329
316
|
runInteractive(argv);
|
|
330
317
|
})();
|
|
331
318
|
}
|
|
332
319
|
};
|
|
333
320
|
//#endregion
|
|
334
|
-
//#region src/lib/programs/shared/package-scanning.ts
|
|
335
|
-
const POSTHOG_SDKS = new Set([
|
|
336
|
-
"posthog-js",
|
|
337
|
-
"posthog-node",
|
|
338
|
-
"posthog-react-native",
|
|
339
|
-
"posthog-android",
|
|
340
|
-
"posthog-ios"
|
|
341
|
-
]);
|
|
342
|
-
const STRIPE_SDKS = new Set([
|
|
343
|
-
"stripe",
|
|
344
|
-
"@stripe/stripe-js",
|
|
345
|
-
"@stripe/react-stripe-js"
|
|
346
|
-
]);
|
|
347
|
-
const MAX_PACKAGE_MATCHES = 500;
|
|
348
|
-
/**
|
|
349
|
-
* Find all package.json files under installDir via the shared bounded walk
|
|
350
|
-
* (depth cap, ignored dirs, symlink-loop protection, per-dir entry cap).
|
|
351
|
-
* Returns matches with detected SDKs.
|
|
352
|
-
*/
|
|
353
|
-
function findPackageJsons(installDir, maxDepth = 3) {
|
|
354
|
-
const matches = [];
|
|
355
|
-
walkProjectFiles(installDir, (name, fullPath) => {
|
|
356
|
-
if (name !== "package.json" || matches.length >= MAX_PACKAGE_MATCHES) return;
|
|
357
|
-
const content = safeReadFile(fullPath);
|
|
358
|
-
if (content === null) return;
|
|
359
|
-
try {
|
|
360
|
-
const pkg = JSON.parse(content);
|
|
361
|
-
const depNames = [...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})];
|
|
362
|
-
const posthogSdks = depNames.filter((d) => POSTHOG_SDKS.has(d));
|
|
363
|
-
const stripeSdks = depNames.filter((d) => STRIPE_SDKS.has(d));
|
|
364
|
-
matches.push({
|
|
365
|
-
path: relative(installDir, fullPath) || "package.json",
|
|
366
|
-
posthogSdks,
|
|
367
|
-
stripeSdks
|
|
368
|
-
});
|
|
369
|
-
} catch {}
|
|
370
|
-
}, maxDepth);
|
|
371
|
-
return matches;
|
|
372
|
-
}
|
|
373
|
-
//#endregion
|
|
374
|
-
//#region src/lib/programs/revenue-analytics/detect.ts
|
|
375
|
-
/**
|
|
376
|
-
* Revenue analytics prerequisite detection.
|
|
377
|
-
*
|
|
378
|
-
* Scans the project for PostHog + Stripe SDKs and writes results
|
|
379
|
-
* into frameworkContext for the intro screen to render.
|
|
380
|
-
*/
|
|
381
|
-
/** `[ABORT] <reason>` cases the revenue analytics skill can emit. */
|
|
382
|
-
const REVENUE_ABORT_CASES = [{
|
|
383
|
-
match: /^could not find a posthog distinct_id$/i,
|
|
384
|
-
message: "Could not find a PostHog distinct_id",
|
|
385
|
-
body: "The agent could not find PostHog distinct_id usage in your codebase. Your users must be identified in PostHog before they can be tagged in Stripe. Please identify your users and try again.",
|
|
386
|
-
docsUrl: "https://posthog.com/docs/product-analytics/identify"
|
|
387
|
-
}, {
|
|
388
|
-
match: /^could not find a stripe integration$/i,
|
|
389
|
-
message: "Could not find a Stripe integration",
|
|
390
|
-
body: "The Wizard could not find an existing Stripe customer, charge, subscription, or other Stripe operations. Please run the Revenue Analytics Wizard on a project with an existing Stripe integration.",
|
|
391
|
-
docsUrl: "https://posthog.com/docs/revenue-analytics"
|
|
392
|
-
}];
|
|
393
|
-
/**
|
|
394
|
-
* Scan `session.installDir` for PostHog + Stripe SDKs. Writes detection
|
|
395
|
-
* results into frameworkContext via the callback — either the detected
|
|
396
|
-
* SDK lists (for the intro screen) or a `RevenueDetectError` on failure.
|
|
397
|
-
*
|
|
398
|
-
* The skill install happens later in the bootstrap runner, not here.
|
|
399
|
-
*/
|
|
400
|
-
function detectRevenuePrerequisites(session, setFrameworkContext) {
|
|
401
|
-
const fail = (error) => setFrameworkContext("detectError", error);
|
|
402
|
-
const installDir = session.installDir;
|
|
403
|
-
if (!existsSync(installDir)) {
|
|
404
|
-
fail({
|
|
405
|
-
kind: "bad-directory",
|
|
406
|
-
path: installDir,
|
|
407
|
-
reason: "missing"
|
|
408
|
-
});
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
try {
|
|
412
|
-
if (!statSync(installDir).isDirectory()) {
|
|
413
|
-
fail({
|
|
414
|
-
kind: "bad-directory",
|
|
415
|
-
path: installDir,
|
|
416
|
-
reason: "not-dir"
|
|
417
|
-
});
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
} catch {
|
|
421
|
-
fail({
|
|
422
|
-
kind: "bad-directory",
|
|
423
|
-
path: installDir,
|
|
424
|
-
reason: "unreadable"
|
|
425
|
-
});
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
const matches = findPackageJsons(installDir);
|
|
429
|
-
if (matches.length === 0) {
|
|
430
|
-
fail({ kind: "no-package-json" });
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
const allPosthogSdks = /* @__PURE__ */ new Set();
|
|
434
|
-
const allStripeSdks = /* @__PURE__ */ new Set();
|
|
435
|
-
for (const match of matches) {
|
|
436
|
-
for (const sdk of match.posthogSdks) allPosthogSdks.add(sdk);
|
|
437
|
-
for (const sdk of match.stripeSdks) allStripeSdks.add(sdk);
|
|
438
|
-
}
|
|
439
|
-
const detectedPosthogSdks = [...allPosthogSdks];
|
|
440
|
-
const detectedStripeSdks = [...allStripeSdks];
|
|
441
|
-
if (detectedPosthogSdks.length === 0 && detectedStripeSdks.length === 0) {
|
|
442
|
-
fail({
|
|
443
|
-
kind: "no-sdks",
|
|
444
|
-
scannedCount: matches.length
|
|
445
|
-
});
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
if (detectedPosthogSdks.length === 0) {
|
|
449
|
-
fail({
|
|
450
|
-
kind: "missing-posthog",
|
|
451
|
-
foundStripe: detectedStripeSdks
|
|
452
|
-
});
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
if (detectedStripeSdks.length === 0) {
|
|
456
|
-
fail({
|
|
457
|
-
kind: "missing-stripe",
|
|
458
|
-
foundPosthog: detectedPosthogSdks
|
|
459
|
-
});
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
setFrameworkContext("detectedPosthogSdks", detectedPosthogSdks);
|
|
463
|
-
setFrameworkContext("detectedStripeSdks", detectedStripeSdks);
|
|
464
|
-
setFrameworkContext("detectedPackagePaths", matches.filter((m) => m.posthogSdks.length > 0 || m.stripeSdks.length > 0).map((m) => m.path));
|
|
465
|
-
}
|
|
466
|
-
//#endregion
|
|
467
|
-
//#region src/lib/programs/revenue-analytics/steps.ts
|
|
468
|
-
const REVENUE_ANALYTICS_PROGRAM = [
|
|
469
|
-
{
|
|
470
|
-
id: "detect",
|
|
471
|
-
label: "Detecting prerequisites",
|
|
472
|
-
onReady: (ctx) => detectRevenuePrerequisites(ctx.session, ctx.setFrameworkContext)
|
|
473
|
-
},
|
|
474
|
-
{
|
|
475
|
-
id: "intro",
|
|
476
|
-
label: "Welcome",
|
|
477
|
-
screenId: "revenue-intro",
|
|
478
|
-
gate: (session) => session.setupConfirmed
|
|
479
|
-
},
|
|
480
|
-
HEALTH_CHECK_STEP,
|
|
481
|
-
{
|
|
482
|
-
id: "auth",
|
|
483
|
-
label: "Authentication",
|
|
484
|
-
screenId: "auth",
|
|
485
|
-
isComplete: (session) => session.credentials !== null
|
|
486
|
-
},
|
|
487
|
-
{
|
|
488
|
-
id: "run",
|
|
489
|
-
label: "Revenue analytics",
|
|
490
|
-
screenId: "run",
|
|
491
|
-
isComplete: (session) => session.runPhase === "completed" || session.runPhase === "error"
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
id: "outro",
|
|
495
|
-
label: "Done",
|
|
496
|
-
screenId: "outro",
|
|
497
|
-
isComplete: (session) => session.outroDismissed
|
|
498
|
-
},
|
|
499
|
-
{
|
|
500
|
-
id: "skills",
|
|
501
|
-
label: "Skills",
|
|
502
|
-
screenId: "keep-skills"
|
|
503
|
-
}
|
|
504
|
-
];
|
|
505
|
-
//#endregion
|
|
506
|
-
//#region src/lib/programs/agent-skill/content/index.tsx
|
|
507
|
-
/**
|
|
508
|
-
* Agent-skill learn-deck — the short three-line sequence shown while a
|
|
509
|
-
* skill-based program (audit, revenue-analytics, agent-skill, etc.)
|
|
510
|
-
* runs. Skill programs don't need the full PostHog onboarding narrative.
|
|
511
|
-
*/
|
|
512
|
-
const getContentBlocks$3 = (store) => {
|
|
513
|
-
return [
|
|
514
|
-
{
|
|
515
|
-
content: "Welcome.",
|
|
516
|
-
pause: 3e3,
|
|
517
|
-
mode: 0,
|
|
518
|
-
animationInterval: 160
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
content: "The Wizard is an agent.",
|
|
522
|
-
pause: 4e3
|
|
523
|
-
},
|
|
524
|
-
{
|
|
525
|
-
pause: 6e4,
|
|
526
|
-
content: /* @__PURE__ */ jsxs(Text, { children: [
|
|
527
|
-
"Running the ",
|
|
528
|
-
/* @__PURE__ */ jsx(Text, {
|
|
529
|
-
color: "cyan",
|
|
530
|
-
children: store?.session.skillId ?? "unknown"
|
|
531
|
-
}),
|
|
532
|
-
" skill..."
|
|
533
|
-
] })
|
|
534
|
-
}
|
|
535
|
-
];
|
|
536
|
-
};
|
|
537
|
-
//#endregion
|
|
538
|
-
//#region src/lib/programs/revenue-analytics/index.ts
|
|
539
|
-
const revenueAnalyticsConfig = {
|
|
540
|
-
command: "revenue-analytics",
|
|
541
|
-
description: "Set up PostHog revenue analytics (e.g. Stripe integration)",
|
|
542
|
-
id: "revenue-analytics-setup",
|
|
543
|
-
skillId: "revenue-analytics-setup",
|
|
544
|
-
steps: REVENUE_ANALYTICS_PROGRAM,
|
|
545
|
-
getContentBlocks: getContentBlocks$3,
|
|
546
|
-
allowedTools: ["Agent"],
|
|
547
|
-
disallowedTools: [WIZARD_TOOL_NAMES.wizardAsk],
|
|
548
|
-
run: {
|
|
549
|
-
skillId: "revenue-analytics-setup",
|
|
550
|
-
integrationLabel: "revenue-analytics-setup",
|
|
551
|
-
customPrompt: () => "Set up revenue analytics for this project.",
|
|
552
|
-
successMessage: "Revenue analytics configured!",
|
|
553
|
-
reportFile: "posthog-revenue-report.md",
|
|
554
|
-
docsUrl: "https://posthog.com/docs/revenue-analytics",
|
|
555
|
-
spinnerMessage: "Setting up revenue analytics...",
|
|
556
|
-
estimatedDurationMinutes: 5,
|
|
557
|
-
abortCases: REVENUE_ABORT_CASES
|
|
558
|
-
},
|
|
559
|
-
requires: ["posthog-integration"]
|
|
560
|
-
};
|
|
561
|
-
//#endregion
|
|
562
|
-
//#region src/lib/programs/warehouse-source/steps.ts
|
|
563
|
-
const WAREHOUSE_SOURCE_PROGRAM = [
|
|
564
|
-
{
|
|
565
|
-
id: "detect",
|
|
566
|
-
label: "Detecting data sources",
|
|
567
|
-
onReady: (ctx) => detectWarehousePrerequisites(ctx.session, ctx.setFrameworkContext)
|
|
568
|
-
},
|
|
569
|
-
{
|
|
570
|
-
id: "intro",
|
|
571
|
-
label: "Welcome",
|
|
572
|
-
screenId: "warehouse-intro",
|
|
573
|
-
gate: (session) => session.setupConfirmed
|
|
574
|
-
},
|
|
575
|
-
{
|
|
576
|
-
id: "auth",
|
|
577
|
-
label: "Authentication",
|
|
578
|
-
screenId: "auth",
|
|
579
|
-
isComplete: (session) => session.credentials !== null
|
|
580
|
-
},
|
|
581
|
-
{
|
|
582
|
-
id: "run",
|
|
583
|
-
label: "Data warehouse",
|
|
584
|
-
screenId: "run",
|
|
585
|
-
isComplete: (session) => session.runPhase === "completed" || session.runPhase === "error"
|
|
586
|
-
},
|
|
587
|
-
{
|
|
588
|
-
id: "outro",
|
|
589
|
-
label: "Done",
|
|
590
|
-
screenId: "outro",
|
|
591
|
-
isComplete: (session) => session.outroDismissed
|
|
592
|
-
},
|
|
593
|
-
{
|
|
594
|
-
id: "skills",
|
|
595
|
-
label: "Skills",
|
|
596
|
-
screenId: "keep-skills"
|
|
597
|
-
}
|
|
598
|
-
];
|
|
599
|
-
//#endregion
|
|
600
|
-
//#region src/lib/programs/warehouse-source/index.ts
|
|
601
|
-
/**
|
|
602
|
-
* Inject the detected sources (and their creation mode) into the prompt so the
|
|
603
|
-
* skill knows what to set up. The *how* — in-CLI creation vs deep-link, field
|
|
604
|
-
* collection, validation — lives in the skill, not here.
|
|
605
|
-
*/
|
|
606
|
-
function buildPrompt(session) {
|
|
607
|
-
const sources = getDetectedWarehouseSources(session);
|
|
608
|
-
if (sources.length === 0) return "Set up a data warehouse source for this project.";
|
|
609
|
-
return [
|
|
610
|
-
"The wizard detected the following data warehouse sources in this project:",
|
|
611
|
-
...sources.map((s) => `- ${s.label} (kind: ${s.kind}, mode: ${s.mode}) — ${s.matchedSignal}`),
|
|
612
|
-
"",
|
|
613
|
-
"Each signal names the file it came from. Trust that path — the wizard read it. To confirm a key is set, call `check_env_keys` with the key names and no `filePath`; it scans every `.env` file in the project.",
|
|
614
|
-
"",
|
|
615
|
-
"Set these up in PostHog following the skill instructions: create `in-cli` sources directly via the PostHog MCP after collecting credentials; for `deep-link` sources, provide the user the pre-filled new-source URL.",
|
|
616
|
-
"Use the `kind` string exactly as printed above for `source_type` — PostHog rejects the display label."
|
|
617
|
-
].join("\n");
|
|
618
|
-
}
|
|
619
|
-
const warehouseSourceConfig = {
|
|
620
|
-
command: "warehouse",
|
|
621
|
-
description: "Detect and connect a data warehouse source (Postgres, Stripe, …)",
|
|
622
|
-
id: "warehouse-source",
|
|
623
|
-
skillId: "data-warehouse-source-setup",
|
|
624
|
-
steps: WAREHOUSE_SOURCE_PROGRAM,
|
|
625
|
-
getContentBlocks: getContentBlocks$3,
|
|
626
|
-
reportFile: "posthog-warehouse-report.md",
|
|
627
|
-
allowedTools: ["Agent"],
|
|
628
|
-
run: (session) => Promise.resolve({
|
|
629
|
-
skillId: "data-warehouse-source-setup",
|
|
630
|
-
integrationLabel: "data-warehouse-source-setup",
|
|
631
|
-
customPrompt: () => buildPrompt(session),
|
|
632
|
-
successMessage: "Data warehouse source connected!",
|
|
633
|
-
reportFile: "posthog-warehouse-report.md",
|
|
634
|
-
docsUrl: "https://posthog.com/docs/data-warehouse",
|
|
635
|
-
spinnerMessage: "Connecting your data source...",
|
|
636
|
-
estimatedDurationMinutes: 5,
|
|
637
|
-
abortCases: WAREHOUSE_ABORT_CASES
|
|
638
|
-
}),
|
|
639
|
-
requires: ["posthog-integration"]
|
|
640
|
-
};
|
|
641
|
-
//#endregion
|
|
642
|
-
//#region src/lib/programs/agent-skill/steps.ts
|
|
643
|
-
const AGENT_SKILL_STEPS = [
|
|
644
|
-
{
|
|
645
|
-
id: "intro",
|
|
646
|
-
label: "Welcome",
|
|
647
|
-
screenId: "agent-skill-intro",
|
|
648
|
-
gate: (session) => session.setupConfirmed
|
|
649
|
-
},
|
|
650
|
-
HEALTH_CHECK_STEP,
|
|
651
|
-
{
|
|
652
|
-
id: "auth",
|
|
653
|
-
label: "Authentication",
|
|
654
|
-
screenId: "auth",
|
|
655
|
-
isComplete: (session) => session.credentials !== null
|
|
656
|
-
},
|
|
657
|
-
{
|
|
658
|
-
id: "run",
|
|
659
|
-
label: "Running",
|
|
660
|
-
screenId: "run",
|
|
661
|
-
isComplete: (session) => session.runPhase === "completed" || session.runPhase === "error"
|
|
662
|
-
},
|
|
663
|
-
{
|
|
664
|
-
id: "outro",
|
|
665
|
-
label: "Done",
|
|
666
|
-
screenId: "outro",
|
|
667
|
-
isComplete: (session) => session.outroDismissed
|
|
668
|
-
},
|
|
669
|
-
{
|
|
670
|
-
id: "skills",
|
|
671
|
-
label: "Skills",
|
|
672
|
-
screenId: "keep-skills"
|
|
673
|
-
}
|
|
674
|
-
];
|
|
675
|
-
//#endregion
|
|
676
|
-
//#region src/lib/programs/agent-skill/index.ts
|
|
677
|
-
function createSkillProgram(opts) {
|
|
678
|
-
return {
|
|
679
|
-
command: opts.command,
|
|
680
|
-
description: opts.description,
|
|
681
|
-
id: opts.id,
|
|
682
|
-
skillId: opts.skillId,
|
|
683
|
-
steps: AGENT_SKILL_STEPS,
|
|
684
|
-
reportFile: opts.reportFile,
|
|
685
|
-
getContentBlocks: getContentBlocks$3,
|
|
686
|
-
run: {
|
|
687
|
-
skillId: opts.skillId,
|
|
688
|
-
integrationLabel: opts.integrationLabel,
|
|
689
|
-
customPrompt: opts.customPrompt ? () => opts.customPrompt : void 0,
|
|
690
|
-
successMessage: opts.successMessage,
|
|
691
|
-
reportFile: opts.reportFile,
|
|
692
|
-
docsUrl: opts.docsUrl,
|
|
693
|
-
spinnerMessage: opts.spinnerMessage,
|
|
694
|
-
estimatedDurationMinutes: opts.estimatedDurationMinutes,
|
|
695
|
-
buildOutroData: opts.buildOutroData,
|
|
696
|
-
abortCases: opts.abortCases
|
|
697
|
-
},
|
|
698
|
-
requires: opts.requires
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
//#endregion
|
|
702
|
-
//#region src/lib/programs/audit/detect.ts
|
|
703
|
-
/** `[ABORT] <reason>` cases the audit skill can emit. Reason strings are
|
|
704
|
-
* defined in the skill's `Abort statuses` section. */
|
|
705
|
-
const AUDIT_ABORT_CASES = [{
|
|
706
|
-
match: /^no posthog sdk found$/i,
|
|
707
|
-
errorCode: ErrorCodes.DetectNoPosthogSdk,
|
|
708
|
-
message: "No PostHog SDK found",
|
|
709
|
-
body: "The audit needs an existing PostHog integration to review. No PostHog SDK appears in this project’s dependency manifests. Run the basic integration program to install PostHog first, then re-run the audit.",
|
|
710
|
-
docsUrl: "https://posthog.com/docs/getting-started/install"
|
|
711
|
-
}];
|
|
712
|
-
//#endregion
|
|
713
|
-
//#region src/lib/programs/audit/seed.ts
|
|
714
|
-
/**
|
|
715
|
-
* The 10 source-tree checks the audit runs, plus one row for the PostHog-side
|
|
716
|
-
* sweep and two workflow rows at the end (so the skill's
|
|
717
|
-
* `audit_resolve_checks` calls for `write-report` and `upload-notebook`
|
|
718
|
-
* succeed — the skill writes the report to disk, then mirrors it into a
|
|
719
|
-
* PostHog notebook as its final step).
|
|
720
|
-
*
|
|
721
|
-
* `posthog-side-findings` is a sweep row, not a rule: the skill reads what
|
|
722
|
-
* PostHog itself already computed for this project (error-tracking
|
|
723
|
-
* recommendations, health issues) and appends one row per open finding via
|
|
724
|
-
* `audit_add_checks`. That's why the seed can't enumerate them — the list is
|
|
725
|
-
* whatever the project's data says today, and it grows as PostHog ships new
|
|
726
|
-
* checks, with no wizard release needed.
|
|
727
|
-
*/
|
|
728
|
-
const AUDIT_SEED_CHECKS = [
|
|
729
|
-
{
|
|
730
|
-
id: "sdk-installed",
|
|
731
|
-
area: "Installation",
|
|
732
|
-
label: "PostHog SDK installed",
|
|
733
|
-
status: "pending"
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
id: "sdk-up-to-date",
|
|
737
|
-
area: "Installation",
|
|
738
|
-
label: "SDK version up to date",
|
|
739
|
-
status: "pending"
|
|
740
|
-
},
|
|
741
|
-
{
|
|
742
|
-
id: "init-correct",
|
|
743
|
-
area: "Installation",
|
|
744
|
-
label: "Initialization is correct",
|
|
745
|
-
status: "pending"
|
|
746
|
-
},
|
|
747
|
-
{
|
|
748
|
-
id: "identify-stable-distinct-id",
|
|
749
|
-
area: "Identification",
|
|
750
|
-
label: "Stable distinct_id (not session UUID)",
|
|
751
|
-
status: "pending"
|
|
752
|
-
},
|
|
753
|
-
{
|
|
754
|
-
id: "identify-not-late",
|
|
755
|
-
area: "Identification",
|
|
756
|
-
label: "identify() called before captures / flag evals",
|
|
757
|
-
status: "pending"
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
id: "cross-runtime-distinct-id",
|
|
761
|
-
area: "Identification",
|
|
762
|
-
label: "Same distinct_id across client and server",
|
|
763
|
-
status: "pending"
|
|
764
|
-
},
|
|
765
|
-
{
|
|
766
|
-
id: "identify-reset-on-logout",
|
|
767
|
-
area: "Identification",
|
|
768
|
-
label: "reset() called on logout / account switch",
|
|
769
|
-
status: "pending"
|
|
770
|
-
},
|
|
771
|
-
{
|
|
772
|
-
id: "capture-event-names-static",
|
|
773
|
-
area: "Event Capture",
|
|
774
|
-
label: "Event names are static and consistent",
|
|
775
|
-
status: "pending"
|
|
776
|
-
},
|
|
777
|
-
{
|
|
778
|
-
id: "capture-uses-proxy",
|
|
779
|
-
area: "Event Capture",
|
|
780
|
-
label: "Captures route through a reverse proxy",
|
|
781
|
-
status: "pending"
|
|
782
|
-
},
|
|
783
|
-
{
|
|
784
|
-
id: "capture-growth-events",
|
|
785
|
-
area: "Event Capture",
|
|
786
|
-
label: "Key activation events captured",
|
|
787
|
-
status: "pending"
|
|
788
|
-
},
|
|
789
|
-
{
|
|
790
|
-
id: "live-data-findings",
|
|
791
|
-
area: "Live Data",
|
|
792
|
-
label: "Open findings in PostHog",
|
|
793
|
-
status: "pending"
|
|
794
|
-
},
|
|
795
|
-
{
|
|
796
|
-
id: "write-report",
|
|
797
|
-
area: "Write report",
|
|
798
|
-
label: "Create posthog-audit-report.md",
|
|
799
|
-
status: "pending"
|
|
800
|
-
},
|
|
801
|
-
{
|
|
802
|
-
id: "upload-notebook",
|
|
803
|
-
area: "Upload notebook",
|
|
804
|
-
label: "Write the report into a PostHog notebook",
|
|
805
|
-
status: "pending"
|
|
806
|
-
}
|
|
807
|
-
];
|
|
808
|
-
/**
|
|
809
|
-
* Atomically write a seeded ledger to the project's audit checks file.
|
|
810
|
-
*
|
|
811
|
-
* Each audit-flavored program (doctor, events-audit) owns its own seed
|
|
812
|
-
* shape — pass the seed in so this writer stays program-agnostic.
|
|
813
|
-
*/
|
|
814
|
-
function seedAuditLedger(installDir, checks = AUDIT_SEED_CHECKS) {
|
|
815
|
-
const target = path.join(installDir, AUDIT_CHECKS_FILE);
|
|
816
|
-
writeJsonAtomic(target, checks);
|
|
817
|
-
logToFile(`seedAuditLedger: wrote ${checks.length} entries to ${target}`);
|
|
818
|
-
}
|
|
819
|
-
//#endregion
|
|
820
|
-
//#region src/lib/programs/audit/index.ts
|
|
821
|
-
/** Audit-specific screens for the shared agent-skill pipeline. */
|
|
822
|
-
const AUDIT_SCREEN_BY_STEP = {
|
|
823
|
-
intro: "audit-intro",
|
|
824
|
-
run: "audit-run",
|
|
825
|
-
outro: "audit-outro"
|
|
826
|
-
};
|
|
827
|
-
const seedBeforeAuditRun = (session) => {
|
|
828
|
-
seedAuditLedger(session.installDir);
|
|
829
|
-
session.frameworkContext[AUDIT_CHECKS_KEY] = AUDIT_SEED_CHECKS;
|
|
830
|
-
};
|
|
831
|
-
const withAuditScreens = (steps) => steps.map((step) => {
|
|
832
|
-
const override = AUDIT_SCREEN_BY_STEP[step.id];
|
|
833
|
-
return override ? {
|
|
834
|
-
...step,
|
|
835
|
-
screenId: override
|
|
836
|
-
} : step;
|
|
837
|
-
});
|
|
838
|
-
const auditSteps = withAuditScreens(AGENT_SKILL_STEPS);
|
|
839
|
-
const baseConfig = createSkillProgram({
|
|
840
|
-
skillId: "audit",
|
|
841
|
-
command: "audit",
|
|
842
|
-
id: "audit",
|
|
843
|
-
description: "Audit an existing PostHog integration for correctness and best practices",
|
|
844
|
-
integrationLabel: "audit",
|
|
845
|
-
customPrompt: "Run a comprehensive audit of the existing PostHog integration. Follow the skill program steps in order. Do not modify any project files — only create the final audit report.",
|
|
846
|
-
successMessage: "Audit complete! You can view the audit report at ./posthog-audit-report.md",
|
|
847
|
-
reportFile: AUDIT_REPORT_FILE,
|
|
848
|
-
docsUrl: "https://posthog.com/docs/product-analytics/best-practices",
|
|
849
|
-
spinnerMessage: "Auditing PostHog integration...",
|
|
850
|
-
estimatedDurationMinutes: 5,
|
|
851
|
-
requires: ["posthog-integration"],
|
|
852
|
-
abortCases: AUDIT_ABORT_CASES
|
|
853
|
-
});
|
|
854
|
-
const auditRun = async (session) => {
|
|
855
|
-
seedBeforeAuditRun(session);
|
|
856
|
-
if (!baseConfig.run) throw new Error("Audit program has no run configuration.");
|
|
857
|
-
const baseRun = typeof baseConfig.run === "function" ? await baseConfig.run(session) : baseConfig.run;
|
|
858
|
-
return {
|
|
859
|
-
...baseRun,
|
|
860
|
-
buildOutroData: (sess, credentials) => {
|
|
861
|
-
const cloudUrl = credentials.host.appHost;
|
|
862
|
-
const continueUrl = sess.signup ? `${cloudUrl}/products?source=wizard` : void 0;
|
|
863
|
-
return {
|
|
864
|
-
kind: "success",
|
|
865
|
-
message: baseRun.successMessage,
|
|
866
|
-
reportFile: baseRun.reportFile,
|
|
867
|
-
docsUrl: baseRun.docsUrl,
|
|
868
|
-
continueUrl,
|
|
869
|
-
dashboardUrl: session.dashboardUrl ?? void 0,
|
|
870
|
-
notebookUrl: session.notebookUrl ?? void 0
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
};
|
|
874
|
-
};
|
|
875
|
-
const auditConfig = {
|
|
876
|
-
...baseConfig,
|
|
877
|
-
steps: auditSteps,
|
|
878
|
-
run: auditRun,
|
|
879
|
-
allowedTools: ["Agent"],
|
|
880
|
-
disallowedTools: [WIZARD_TOOL_NAMES.wizardAsk],
|
|
881
|
-
cliOptions: {
|
|
882
|
-
...headlessOption,
|
|
883
|
-
...regionOption
|
|
884
|
-
}
|
|
885
|
-
};
|
|
886
|
-
//#endregion
|
|
887
|
-
//#region src/lib/programs/events-audit/steps.ts
|
|
888
|
-
function needsSetup(session) {
|
|
889
|
-
const config = session.frameworkConfig;
|
|
890
|
-
if (!config?.metadata.setup?.questions) return false;
|
|
891
|
-
return config.metadata.setup.questions.some((q) => !(q.key in session.frameworkContext));
|
|
892
|
-
}
|
|
893
|
-
const EVENTS_AUDIT_PROGRAM = [
|
|
894
|
-
{
|
|
895
|
-
id: "intro",
|
|
896
|
-
label: "Welcome",
|
|
897
|
-
screenId: "audit-intro",
|
|
898
|
-
gate: (session) => session.setupConfirmed
|
|
899
|
-
},
|
|
900
|
-
HEALTH_CHECK_STEP,
|
|
901
|
-
{
|
|
902
|
-
id: "setup",
|
|
903
|
-
label: "Setup",
|
|
904
|
-
screenId: "setup",
|
|
905
|
-
show: needsSetup,
|
|
906
|
-
isComplete: (session) => !needsSetup(session)
|
|
907
|
-
},
|
|
908
|
-
{
|
|
909
|
-
id: "auth",
|
|
910
|
-
label: "Authentication",
|
|
911
|
-
screenId: "auth",
|
|
912
|
-
isComplete: (session) => session.credentials !== null
|
|
913
|
-
},
|
|
914
|
-
{
|
|
915
|
-
id: "run",
|
|
916
|
-
label: "Events audit",
|
|
917
|
-
screenId: "audit-run",
|
|
918
|
-
isComplete: (session) => session.runPhase === "completed" || session.runPhase === "error"
|
|
919
|
-
},
|
|
920
|
-
{
|
|
921
|
-
id: "mcp",
|
|
922
|
-
label: "MCP servers",
|
|
923
|
-
screenId: "mcp",
|
|
924
|
-
isComplete: (session) => session.mcpComplete
|
|
925
|
-
},
|
|
926
|
-
{
|
|
927
|
-
id: "outro",
|
|
928
|
-
label: "Done",
|
|
929
|
-
screenId: "audit-outro",
|
|
930
|
-
isComplete: (session) => session.outroDismissed
|
|
931
|
-
},
|
|
932
|
-
{
|
|
933
|
-
id: "keep-skills",
|
|
934
|
-
label: "Keep Skills",
|
|
935
|
-
screenId: "keep-skills"
|
|
936
|
-
}
|
|
937
|
-
];
|
|
938
|
-
//#endregion
|
|
939
|
-
//#region src/lib/programs/events-audit/seed.ts
|
|
940
|
-
/**
|
|
941
|
-
* The 7 phases the events-audit skill marches through. One check per area
|
|
942
|
-
* so PendingChecksList renders a clean linear pipeline (area = bold header,
|
|
943
|
-
* single row = the active spinner).
|
|
944
|
-
*
|
|
945
|
-
* Phase ids match what the skill's step files resolve via
|
|
946
|
-
* `mcp__wizard-tools__audit_resolve_checks` as each phase completes. The
|
|
947
|
-
* skill's step 1 also seeds these same ids — keep both in sync so the
|
|
948
|
-
* wizard pre-seed and the skill's MCP seed agree.
|
|
949
|
-
*/
|
|
950
|
-
const EVENTS_AUDIT_SEED_CHECKS = [
|
|
951
|
-
{
|
|
952
|
-
id: "detect-sdk",
|
|
953
|
-
area: "Detect SDK",
|
|
954
|
-
label: "Identify PostHog SDK(s) in dependencies",
|
|
955
|
-
status: "pending"
|
|
956
|
-
},
|
|
957
|
-
{
|
|
958
|
-
id: "scan-sites",
|
|
959
|
-
area: "Scan capture sites",
|
|
960
|
-
label: "Grep capture/identify/group call sites",
|
|
961
|
-
status: "pending"
|
|
962
|
-
},
|
|
963
|
-
{
|
|
964
|
-
id: "enrich-sites",
|
|
965
|
-
area: "Enrich",
|
|
966
|
-
label: "Subagent fan-out to read capture files",
|
|
967
|
-
status: "pending"
|
|
968
|
-
},
|
|
969
|
-
{
|
|
970
|
-
id: "query-volume",
|
|
971
|
-
area: "Query PostHog",
|
|
972
|
-
label: "30-day volume + last_seen via MCP",
|
|
973
|
-
status: "pending"
|
|
974
|
-
},
|
|
975
|
-
{
|
|
976
|
-
id: "write-report",
|
|
977
|
-
area: "Write report",
|
|
978
|
-
label: "Create posthog-events-audit-report.md",
|
|
979
|
-
status: "pending"
|
|
980
|
-
},
|
|
981
|
-
{
|
|
982
|
-
id: "create-dashboard",
|
|
983
|
-
area: "Create dashboard",
|
|
984
|
-
label: "Optional: dashboard for resolved events",
|
|
985
|
-
status: "pending"
|
|
986
|
-
},
|
|
987
|
-
{
|
|
988
|
-
id: "upload-notebook",
|
|
989
|
-
area: "Upload notebook",
|
|
990
|
-
label: "Write the report into a PostHog notebook",
|
|
991
|
-
status: "pending"
|
|
992
|
-
}
|
|
993
|
-
];
|
|
994
|
-
//#endregion
|
|
995
|
-
//#region src/lib/programs/events-audit/index.ts
|
|
996
|
-
const DOCS_URL$3 = "https://posthog.com/docs/product-analytics/best-practices";
|
|
997
|
-
const eventsAuditConfig = {
|
|
998
|
-
command: "events-audit",
|
|
999
|
-
description: "Audit PostHog event tracking in this project",
|
|
1000
|
-
id: "events-audit",
|
|
1001
|
-
skillId: "events-audit",
|
|
1002
|
-
steps: EVENTS_AUDIT_PROGRAM,
|
|
1003
|
-
reportFile: SETUP_REPORT_FILE,
|
|
1004
|
-
allowedTools: ["Agent"],
|
|
1005
|
-
disallowedTools: [WIZARD_TOOL_NAMES.wizardAsk],
|
|
1006
|
-
run: (session) => {
|
|
1007
|
-
const typeScriptDetected = isUsingTypeScript({ installDir: session.installDir });
|
|
1008
|
-
session.typescript = typeScriptDetected;
|
|
1009
|
-
seedAuditLedger(session.installDir, EVENTS_AUDIT_SEED_CHECKS);
|
|
1010
|
-
session.frameworkContext[AUDIT_CHECKS_KEY] = EVENTS_AUDIT_SEED_CHECKS;
|
|
1011
|
-
return Promise.resolve({
|
|
1012
|
-
skillId: "events-audit",
|
|
1013
|
-
integrationLabel: "events-audit",
|
|
1014
|
-
spinnerMessage: SPINNER_MESSAGE,
|
|
1015
|
-
successMessage: "Events audit complete! You can view the report at ./posthog-events-audit-report.md",
|
|
1016
|
-
estimatedDurationMinutes: 5,
|
|
1017
|
-
reportFile: SETUP_REPORT_FILE,
|
|
1018
|
-
docsUrl: DOCS_URL$3,
|
|
1019
|
-
errorMessage: "Events audit failed",
|
|
1020
|
-
additionalFeatureQueue: session.additionalFeatureQueue,
|
|
1021
|
-
customPrompt: (ctx) => `Audit PostHog event capture in this project. Do not modify any project files — produce a read-only report only.
|
|
1022
|
-
|
|
1023
|
-
Project context:
|
|
1024
|
-
- PostHog Project ID: ${ctx.projectId}
|
|
1025
|
-
- TypeScript: ${typeScriptDetected ? "Yes" : "No"}
|
|
1026
|
-
- PostHog public token: ${ctx.projectApiKey}
|
|
1027
|
-
- PostHog Host: ${ctx.host.apiHost}
|
|
1028
|
-
`,
|
|
1029
|
-
buildOutroData: (sess, credentials) => {
|
|
1030
|
-
const cloudUrl = credentials.host.appHost;
|
|
1031
|
-
return {
|
|
1032
|
-
kind: "success",
|
|
1033
|
-
message: "Your events audit was successful",
|
|
1034
|
-
reportFile: SETUP_REPORT_FILE,
|
|
1035
|
-
changes: [],
|
|
1036
|
-
docsUrl: DOCS_URL$3,
|
|
1037
|
-
continueUrl: sess.signup ? `${cloudUrl}/products?source=wizard` : void 0,
|
|
1038
|
-
dashboardUrl: sess.dashboardUrl ?? (cloudUrl ? `${cloudUrl}/dashboard` : void 0),
|
|
1039
|
-
notebookUrl: sess.notebookUrl ?? void 0
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
};
|
|
1045
|
-
//#endregion
|
|
1046
|
-
//#region src/lib/programs/posthog-doctor/steps.ts
|
|
1047
|
-
const POSTHOG_DOCTOR_PROGRAM = [
|
|
1048
|
-
{
|
|
1049
|
-
id: "intro",
|
|
1050
|
-
label: "Welcome",
|
|
1051
|
-
screenId: "doctor-intro",
|
|
1052
|
-
gate: (session) => session.setupConfirmed
|
|
1053
|
-
},
|
|
1054
|
-
HEALTH_CHECK_STEP,
|
|
1055
|
-
{
|
|
1056
|
-
id: "auth",
|
|
1057
|
-
label: "Authentication",
|
|
1058
|
-
screenId: "auth",
|
|
1059
|
-
isComplete: (session) => session.credentials !== null
|
|
1060
|
-
},
|
|
1061
|
-
{
|
|
1062
|
-
id: "report",
|
|
1063
|
-
label: "Doctor report",
|
|
1064
|
-
screenId: "doctor-report",
|
|
1065
|
-
isComplete: (session) => session.outroData !== null
|
|
1066
|
-
},
|
|
1067
|
-
{
|
|
1068
|
-
id: "outro",
|
|
1069
|
-
label: "Done",
|
|
1070
|
-
screenId: "outro",
|
|
1071
|
-
isComplete: (session) => session.outroDismissed
|
|
1072
|
-
}
|
|
1073
|
-
];
|
|
1074
|
-
//#endregion
|
|
1075
|
-
//#region src/lib/programs/posthog-doctor/types.ts
|
|
1076
|
-
const HealthIssueSeveritySchema = z.enum([
|
|
1077
|
-
"critical",
|
|
1078
|
-
"warning",
|
|
1079
|
-
"info"
|
|
1080
|
-
]);
|
|
1081
|
-
const HealthIssueStatusSchema = z.enum(["active", "resolved"]);
|
|
1082
|
-
const HealthIssueSchema = z.object({
|
|
1083
|
-
id: z.string(),
|
|
1084
|
-
kind: z.string(),
|
|
1085
|
-
severity: HealthIssueSeveritySchema,
|
|
1086
|
-
status: HealthIssueStatusSchema,
|
|
1087
|
-
dismissed: z.boolean(),
|
|
1088
|
-
created_at: z.string(),
|
|
1089
|
-
updated_at: z.string(),
|
|
1090
|
-
resolved_at: z.string().nullable().optional()
|
|
1091
|
-
});
|
|
1092
|
-
const HealthIssueListResponseSchema = z.object({
|
|
1093
|
-
results: z.array(HealthIssueSchema),
|
|
1094
|
-
count: z.number().optional(),
|
|
1095
|
-
next: z.string().nullable().optional(),
|
|
1096
|
-
previous: z.string().nullable().optional()
|
|
1097
|
-
});
|
|
1098
|
-
//#endregion
|
|
1099
|
-
//#region src/lib/programs/posthog-doctor/fetch.ts
|
|
1100
|
-
async function fetchHealthIssues(accessToken, baseUrl, projectId) {
|
|
1101
|
-
const endpoint = `/api/environments/${projectId}/health_issues/`;
|
|
1102
|
-
const url = `${baseUrl}${endpoint}?status=active&dismissed=false&limit=250`;
|
|
1103
|
-
try {
|
|
1104
|
-
const response = await axios.get(url, { headers: {
|
|
1105
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1106
|
-
"User-Agent": WIZARD_USER_AGENT
|
|
1107
|
-
} });
|
|
1108
|
-
return HealthIssueListResponseSchema.parse(response.data).results;
|
|
1109
|
-
} catch (error) {
|
|
1110
|
-
const apiError = handleApiError(error, "fetch health issues");
|
|
1111
|
-
analytics.captureException(apiError, {
|
|
1112
|
-
endpoint,
|
|
1113
|
-
baseUrl,
|
|
1114
|
-
projectId
|
|
1115
|
-
});
|
|
1116
|
-
throw apiError;
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
//#endregion
|
|
1120
|
-
//#region src/lib/programs/posthog-doctor/kind-metadata.ts
|
|
1121
|
-
const KIND_METADATA = {
|
|
1122
|
-
ingestion_lag: {
|
|
1123
|
-
title: "Ingestion is delayed",
|
|
1124
|
-
description: "Events are being received but are taking longer than usual to appear.",
|
|
1125
|
-
docsUrl: `${POSTHOG_DOCS_URL}/support/troubleshooting`
|
|
1126
|
-
},
|
|
1127
|
-
ingestion_warning: {
|
|
1128
|
-
title: "Ingestion warnings on recent events",
|
|
1129
|
-
description: "Some recent events were rejected or flagged by the ingestion pipeline.",
|
|
1130
|
-
docsUrl: `${POSTHOG_DOCS_URL}/support/troubleshooting`
|
|
1131
|
-
},
|
|
1132
|
-
sdk_outdated: {
|
|
1133
|
-
title: "SDK version is out of date",
|
|
1134
|
-
description: "One or more SDKs are running an old version. Upgrade to get the latest fixes.",
|
|
1135
|
-
docsUrl: `${POSTHOG_DOCS_URL}/libraries`
|
|
1136
|
-
},
|
|
1137
|
-
no_live_events: {
|
|
1138
|
-
title: "No $pageview or $screen events in the last 30 days",
|
|
1139
|
-
description: "PostHog is not receiving page or screen events from this project.",
|
|
1140
|
-
docsUrl: `${POSTHOG_DOCS_URL}/getting-started/install`
|
|
1141
|
-
},
|
|
1142
|
-
no_pageleave_events: {
|
|
1143
|
-
title: "$pageleave events not being sent",
|
|
1144
|
-
description: "Enable pageleave tracking to power bounce rate and session duration.",
|
|
1145
|
-
docsUrl: `${POSTHOG_DOCS_URL}/libraries/js#config`
|
|
1146
|
-
},
|
|
1147
|
-
scroll_depth: {
|
|
1148
|
-
title: "Scroll depth tracking disabled",
|
|
1149
|
-
description: "Turn on scroll depth to capture how far users read each page.",
|
|
1150
|
-
docsUrl: `${POSTHOG_DOCS_URL}/libraries/js#config`
|
|
1151
|
-
},
|
|
1152
|
-
authorized_urls: {
|
|
1153
|
-
title: "No authorized URLs configured",
|
|
1154
|
-
description: "Some web analytics filters require at least one authorized URL to work.",
|
|
1155
|
-
docsUrl: `${POSTHOG_DOCS_URL}/web-analytics/faq`
|
|
1156
|
-
},
|
|
1157
|
-
reverse_proxy: {
|
|
1158
|
-
title: "No reverse proxy detected",
|
|
1159
|
-
description: "A reverse proxy reduces data loss from ad blockers.",
|
|
1160
|
-
docsUrl: `${POSTHOG_DOCS_URL}/advanced/proxy`
|
|
1161
|
-
},
|
|
1162
|
-
web_vitals: {
|
|
1163
|
-
title: "Web Vitals tracking disabled",
|
|
1164
|
-
description: "Enable Web Vitals to capture LCP, CLS and other performance metrics.",
|
|
1165
|
-
docsUrl: `${POSTHOG_DOCS_URL}/web-analytics/web-vitals`
|
|
1166
|
-
},
|
|
1167
|
-
materialized_view_failure: {
|
|
1168
|
-
title: "A materialized view is failing",
|
|
1169
|
-
description: "A data modeling pipeline failed its most recent run.",
|
|
1170
|
-
docsUrl: `${POSTHOG_DOCS_URL}/data-warehouse`
|
|
1171
|
-
},
|
|
1172
|
-
external_data_failure: {
|
|
1173
|
-
title: "External data source is failing",
|
|
1174
|
-
description: "An external data source sync failed and data may be stale.",
|
|
1175
|
-
docsUrl: `${POSTHOG_DOCS_URL}/data-warehouse/sources`
|
|
1176
|
-
}
|
|
1177
|
-
};
|
|
1178
|
-
const UNKNOWN_KIND_META = {
|
|
1179
|
-
title: "Unknown issue",
|
|
1180
|
-
description: "PostHog reported an issue kind the wizard does not yet recognize.",
|
|
1181
|
-
docsUrl: POSTHOG_DOCS_URL
|
|
1182
|
-
};
|
|
1183
|
-
function getKindMeta(kind) {
|
|
1184
|
-
return KIND_METADATA[kind] ?? {
|
|
1185
|
-
...UNKNOWN_KIND_META,
|
|
1186
|
-
title: kind
|
|
1187
|
-
};
|
|
1188
|
-
}
|
|
1189
|
-
//#endregion
|
|
1190
|
-
//#region src/lib/programs/posthog-doctor/index.ts
|
|
1191
|
-
const posthogDoctorConfig = {
|
|
1192
|
-
command: "doctor",
|
|
1193
|
-
description: "Diagnose your PostHog project for configuration issues and setup warnings",
|
|
1194
|
-
id: "posthog-doctor",
|
|
1195
|
-
requiresAi: false,
|
|
1196
|
-
steps: POSTHOG_DOCTOR_PROGRAM,
|
|
1197
|
-
allowedTools: ["Agent"],
|
|
1198
|
-
disallowedTools: [WIZARD_TOOL_NAMES.wizardAsk]
|
|
1199
|
-
};
|
|
1200
|
-
//#endregion
|
|
1201
|
-
//#region src/lib/programs/web-analytics-doctor/detect.ts
|
|
1202
|
-
const WEB_ANALYTICS_ABORT_CASES = [
|
|
1203
|
-
{
|
|
1204
|
-
match: /^no web analytics events$/i,
|
|
1205
|
-
message: "No web analytics events",
|
|
1206
|
-
body: "The doctor found no $pageview events in the last 30 days, so there is nothing to audit yet. Make sure PostHog is initialized and capturing pageviews, then run the doctor again.",
|
|
1207
|
-
docsUrl: "https://posthog.com/docs/web-analytics/getting-started"
|
|
1208
|
-
},
|
|
1209
|
-
{
|
|
1210
|
-
match: /^insufficient permissions$/i,
|
|
1211
|
-
errorCode: ErrorCodes.AuthMissingScope,
|
|
1212
|
-
message: "Insufficient permissions",
|
|
1213
|
-
body: "The doctor could not query your project — the authenticated token is missing query access. Re-run the wizard to sign in again, or use a key with read access to your events.",
|
|
1214
|
-
docsUrl: "https://posthog.com/docs/web-analytics"
|
|
1215
|
-
},
|
|
1216
|
-
{
|
|
1217
|
-
match: /^posthog sdk not installed$/i,
|
|
1218
|
-
errorCode: ErrorCodes.DetectNoPosthogSdk,
|
|
1219
|
-
message: "PostHog SDK not installed",
|
|
1220
|
-
body: "The doctor could not find a PostHog SDK in this project. Install and configure PostHog first (run `npx @posthog/wizard`), then run the doctor to check your web analytics setup.",
|
|
1221
|
-
docsUrl: "https://posthog.com/docs/libraries/js"
|
|
1222
|
-
}
|
|
1223
|
-
];
|
|
1224
|
-
function detectWebAnalyticsPrerequisites(session, setFrameworkContext) {
|
|
1225
|
-
const fail = (error) => setFrameworkContext("detectError", error);
|
|
1226
|
-
const installDir = session.installDir;
|
|
1227
|
-
if (!existsSync(installDir)) {
|
|
1228
|
-
fail({
|
|
1229
|
-
kind: "bad-directory",
|
|
1230
|
-
path: installDir,
|
|
1231
|
-
reason: "missing"
|
|
1232
|
-
});
|
|
1233
|
-
return;
|
|
1234
|
-
}
|
|
1235
|
-
try {
|
|
1236
|
-
if (!statSync(installDir).isDirectory()) {
|
|
1237
|
-
fail({
|
|
1238
|
-
kind: "bad-directory",
|
|
1239
|
-
path: installDir,
|
|
1240
|
-
reason: "not-dir"
|
|
1241
|
-
});
|
|
1242
|
-
return;
|
|
1243
|
-
}
|
|
1244
|
-
} catch {
|
|
1245
|
-
fail({
|
|
1246
|
-
kind: "bad-directory",
|
|
1247
|
-
path: installDir,
|
|
1248
|
-
reason: "unreadable"
|
|
1249
|
-
});
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
const matches = findPackageJsons(installDir);
|
|
1253
|
-
if (matches.length === 0) {
|
|
1254
|
-
fail({ kind: "no-package-json" });
|
|
1255
|
-
return;
|
|
1256
|
-
}
|
|
1257
|
-
const sdks = [...new Set(matches.flatMap((m) => m.posthogSdks))];
|
|
1258
|
-
if (sdks.length === 0) {
|
|
1259
|
-
fail({
|
|
1260
|
-
kind: "no-posthog",
|
|
1261
|
-
scannedCount: matches.length
|
|
1262
|
-
});
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
setFrameworkContext("detectedPosthogSdks", sdks);
|
|
1266
|
-
}
|
|
1267
|
-
//#endregion
|
|
1268
|
-
//#region src/lib/programs/web-analytics-doctor/steps.ts
|
|
1269
|
-
const WEB_ANALYTICS_DOCTOR_PROGRAM = [{
|
|
1270
|
-
id: "detect",
|
|
1271
|
-
label: "Detecting prerequisites",
|
|
1272
|
-
onReady: (ctx) => detectWebAnalyticsPrerequisites(ctx.session, ctx.setFrameworkContext)
|
|
1273
|
-
}, ...AGENT_SKILL_STEPS];
|
|
1274
|
-
const webAnalyticsDoctorConfig = {
|
|
1275
|
-
...createSkillProgram({
|
|
1276
|
-
skillId: "web-analytics-doctor",
|
|
1277
|
-
command: "web-analytics",
|
|
1278
|
-
id: "web-analytics-doctor",
|
|
1279
|
-
description: "Audit and fix your PostHog web analytics setup",
|
|
1280
|
-
integrationLabel: "web-analytics-doctor",
|
|
1281
|
-
customPrompt: "Run the web-analytics-doctor skill to check this project's PostHog web analytics setup. Audit read-only first, then present the findings to the user with a single wizard_ask multi-select and apply only the fixes they choose — editing project code and/or PostHog project settings via the MCP — before writing the report.",
|
|
1282
|
-
successMessage: "Web analytics check complete! You can view the report at ./posthog-web-analytics-report.md",
|
|
1283
|
-
reportFile: "posthog-web-analytics-report.md",
|
|
1284
|
-
docsUrl: "https://posthog.com/docs/web-analytics",
|
|
1285
|
-
spinnerMessage: "Checking your web analytics setup...",
|
|
1286
|
-
estimatedDurationMinutes: 5,
|
|
1287
|
-
requires: ["posthog-integration"],
|
|
1288
|
-
abortCases: WEB_ANALYTICS_ABORT_CASES
|
|
1289
|
-
}),
|
|
1290
|
-
steps: WEB_ANALYTICS_DOCTOR_PROGRAM,
|
|
1291
|
-
parentCommand: "audit"
|
|
1292
|
-
};
|
|
1293
|
-
//#endregion
|
|
1294
|
-
//#region src/lib/programs/migration/steps.ts
|
|
1295
|
-
const MIGRATION_PROGRAM = [
|
|
1296
|
-
{
|
|
1297
|
-
id: "intro",
|
|
1298
|
-
label: "Welcome",
|
|
1299
|
-
screenId: "migration-intro",
|
|
1300
|
-
gate: (session) => session.setupConfirmed
|
|
1301
|
-
},
|
|
1302
|
-
HEALTH_CHECK_STEP,
|
|
1303
|
-
{
|
|
1304
|
-
id: "auth",
|
|
1305
|
-
label: "Authentication",
|
|
1306
|
-
screenId: "auth",
|
|
1307
|
-
isComplete: (session) => session.credentials !== null
|
|
1308
|
-
},
|
|
1309
|
-
{
|
|
1310
|
-
id: "run",
|
|
1311
|
-
label: "Migration",
|
|
1312
|
-
screenId: "run",
|
|
1313
|
-
isComplete: (session) => session.runPhase === "completed" || session.runPhase === "error"
|
|
1314
|
-
},
|
|
1315
|
-
{
|
|
1316
|
-
id: "outro",
|
|
1317
|
-
label: "Done",
|
|
1318
|
-
screenId: "outro",
|
|
1319
|
-
isComplete: (session) => session.outroDismissed
|
|
1320
|
-
},
|
|
1321
|
-
{
|
|
1322
|
-
id: "skills",
|
|
1323
|
-
label: "Skills",
|
|
1324
|
-
screenId: "keep-skills"
|
|
1325
|
-
}
|
|
1326
|
-
];
|
|
1327
|
-
//#endregion
|
|
1328
|
-
//#region src/lib/programs/migration/content/vendor-stack.tsx
|
|
1329
|
-
/**
|
|
1330
|
-
* Vendor cost stack — the multi-tool baseline a typical migration target has
|
|
1331
|
-
* before consolidating onto PostHog. Numbers from each vendor's published
|
|
1332
|
-
* starter pricing.
|
|
1333
|
-
*/
|
|
1334
|
-
const VENDOR_STACK_BLOCK = {
|
|
1335
|
-
type: "lines",
|
|
1336
|
-
interval: 600,
|
|
1337
|
-
pause: 9e3,
|
|
1338
|
-
lines: [
|
|
1339
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1340
|
-
bold: true,
|
|
1341
|
-
children: " Typical pre-migration stack"
|
|
1342
|
-
}),
|
|
1343
|
-
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1344
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1345
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1346
|
-
color: "gray",
|
|
1347
|
-
children: " Sentry"
|
|
1348
|
-
}),
|
|
1349
|
-
/* @__PURE__ */ jsx(Text, { children: " error tracking " }),
|
|
1350
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1351
|
-
color: "red",
|
|
1352
|
-
children: "$26/mo+"
|
|
1353
|
-
})
|
|
1354
|
-
] }),
|
|
1355
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1356
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1357
|
-
color: "gray",
|
|
1358
|
-
children: " LaunchDarkly"
|
|
1359
|
-
}),
|
|
1360
|
-
/* @__PURE__ */ jsx(Text, { children: " feature flags " }),
|
|
1361
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1362
|
-
color: "red",
|
|
1363
|
-
children: "$8.33/mo+"
|
|
1364
|
-
})
|
|
1365
|
-
] }),
|
|
1366
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1367
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1368
|
-
color: "gray",
|
|
1369
|
-
children: " Amplitude"
|
|
1370
|
-
}),
|
|
1371
|
-
/* @__PURE__ */ jsx(Text, { children: " product analytics " }),
|
|
1372
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1373
|
-
color: "red",
|
|
1374
|
-
children: "$49/mo+"
|
|
1375
|
-
})
|
|
1376
|
-
] }),
|
|
1377
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1378
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1379
|
-
color: "gray",
|
|
1380
|
-
children: " Braintrust"
|
|
1381
|
-
}),
|
|
1382
|
-
/* @__PURE__ */ jsx(Text, { children: " AI observability " }),
|
|
1383
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1384
|
-
color: "red",
|
|
1385
|
-
children: "$50/mo+"
|
|
1386
|
-
})
|
|
1387
|
-
] }),
|
|
1388
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1389
|
-
color: "gray",
|
|
1390
|
-
children: " ─────────────────────────────────────"
|
|
1391
|
-
}),
|
|
1392
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1393
|
-
/* @__PURE__ */ jsx(Text, { children: " Total" }),
|
|
1394
|
-
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1395
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1396
|
-
bold: true,
|
|
1397
|
-
color: "red",
|
|
1398
|
-
children: "$133/mo+"
|
|
1399
|
-
})
|
|
1400
|
-
] }),
|
|
1401
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1402
|
-
dimColor: true,
|
|
1403
|
-
children: " plus ~450KB of JavaScript SDKs"
|
|
1404
|
-
})
|
|
1405
|
-
]
|
|
1406
|
-
};
|
|
1407
|
-
//#endregion
|
|
1408
|
-
//#region src/lib/programs/migration/content/free-tier.tsx
|
|
1409
|
-
/**
|
|
1410
|
-
* PostHog free-tier highlights — the numbers a migrating team gets back when
|
|
1411
|
-
* they consolidate. Sourced from posthog.com/pricing.md.
|
|
1412
|
-
*/
|
|
1413
|
-
const FREE_TIER_BLOCK = {
|
|
1414
|
-
type: "lines",
|
|
1415
|
-
interval: 400,
|
|
1416
|
-
pause: 9e3,
|
|
1417
|
-
lines: [
|
|
1418
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1419
|
-
bold: true,
|
|
1420
|
-
children: " Free every month, on every product"
|
|
1421
|
-
}),
|
|
1422
|
-
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1423
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1424
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1425
|
-
color: Colors.accent,
|
|
1426
|
-
children: " 1,000,000 "
|
|
1427
|
-
}),
|
|
1428
|
-
/* @__PURE__ */ jsx(Text, { children: "events " }),
|
|
1429
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1430
|
-
dimColor: true,
|
|
1431
|
-
children: "product analytics"
|
|
1432
|
-
})
|
|
1433
|
-
] }),
|
|
1434
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1435
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1436
|
-
color: Colors.accent,
|
|
1437
|
-
children: " 1,000,000 "
|
|
1438
|
-
}),
|
|
1439
|
-
/* @__PURE__ */ jsx(Text, { children: "requests " }),
|
|
1440
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1441
|
-
dimColor: true,
|
|
1442
|
-
children: "feature flags + experiments"
|
|
1443
|
-
})
|
|
1444
|
-
] }),
|
|
1445
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1446
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1447
|
-
color: Colors.accent,
|
|
1448
|
-
children: " 5,000 "
|
|
1449
|
-
}),
|
|
1450
|
-
/* @__PURE__ */ jsx(Text, { children: "recordings " }),
|
|
1451
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1452
|
-
dimColor: true,
|
|
1453
|
-
children: "session replay"
|
|
1454
|
-
})
|
|
1455
|
-
] }),
|
|
1456
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1457
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1458
|
-
color: Colors.accent,
|
|
1459
|
-
children: " 100,000 "
|
|
1460
|
-
}),
|
|
1461
|
-
/* @__PURE__ */ jsx(Text, { children: "exceptions " }),
|
|
1462
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1463
|
-
dimColor: true,
|
|
1464
|
-
children: "error tracking"
|
|
1465
|
-
})
|
|
1466
|
-
] }),
|
|
1467
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1468
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1469
|
-
color: Colors.accent,
|
|
1470
|
-
children: " 100,000 "
|
|
1471
|
-
}),
|
|
1472
|
-
/* @__PURE__ */ jsx(Text, { children: "events " }),
|
|
1473
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1474
|
-
dimColor: true,
|
|
1475
|
-
children: "AI observability"
|
|
1476
|
-
})
|
|
1477
|
-
] }),
|
|
1478
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1479
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1480
|
-
color: Colors.accent,
|
|
1481
|
-
children: " 50 GB "
|
|
1482
|
-
}),
|
|
1483
|
-
/* @__PURE__ */ jsx(Text, { children: "logs " }),
|
|
1484
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1485
|
-
dimColor: true,
|
|
1486
|
-
children: "logs"
|
|
1487
|
-
})
|
|
1488
|
-
] }),
|
|
1489
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1490
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1491
|
-
color: Colors.accent,
|
|
1492
|
-
children: " 1,500 "
|
|
1493
|
-
}),
|
|
1494
|
-
/* @__PURE__ */ jsx(Text, { children: "responses " }),
|
|
1495
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1496
|
-
dimColor: true,
|
|
1497
|
-
children: "surveys"
|
|
1498
|
-
})
|
|
1499
|
-
] }),
|
|
1500
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1501
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1502
|
-
color: Colors.accent,
|
|
1503
|
-
children: " 1,000,000 "
|
|
1504
|
-
}),
|
|
1505
|
-
/* @__PURE__ */ jsx(Text, { children: "rows " }),
|
|
1506
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1507
|
-
dimColor: true,
|
|
1508
|
-
children: "data warehouse"
|
|
1509
|
-
})
|
|
1510
|
-
] })
|
|
1511
|
-
]
|
|
1512
|
-
};
|
|
1513
|
-
//#endregion
|
|
1514
|
-
//#region src/lib/programs/migration/content/pricing-structure.tsx
|
|
1515
|
-
/**
|
|
1516
|
-
* Pricing structure block — what happens after the free tier.
|
|
1517
|
-
*/
|
|
1518
|
-
const PRICING_STRUCTURE_BLOCK = {
|
|
1519
|
-
type: "lines",
|
|
1520
|
-
interval: 500,
|
|
1521
|
-
pause: 8e3,
|
|
1522
|
-
lines: [
|
|
1523
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1524
|
-
bold: true,
|
|
1525
|
-
children: " After the free tier"
|
|
1526
|
-
}),
|
|
1527
|
-
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1528
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
1529
|
-
color: Colors.accent,
|
|
1530
|
-
children: " $0 "
|
|
1531
|
-
}), /* @__PURE__ */ jsx(Text, { children: "base price · pay only for what you use" })] }),
|
|
1532
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
1533
|
-
color: Colors.accent,
|
|
1534
|
-
children: " ◆ "
|
|
1535
|
-
}), /* @__PURE__ */ jsx(Text, { children: "per-event prices decrease with volume" })] }),
|
|
1536
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
1537
|
-
color: Colors.accent,
|
|
1538
|
-
children: " ◆ "
|
|
1539
|
-
}), /* @__PURE__ */ jsx(Text, { children: "no per-seat charges — your whole team is included" })] }),
|
|
1540
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
1541
|
-
color: Colors.accent,
|
|
1542
|
-
children: " ◆ "
|
|
1543
|
-
}), /* @__PURE__ */ jsx(Text, { children: "web analytics bundled with product analytics" })] }),
|
|
1544
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
1545
|
-
color: Colors.accent,
|
|
1546
|
-
children: " ◆ "
|
|
1547
|
-
}), /* @__PURE__ */ jsx(Text, { children: "experiments bundled with feature flags" })] }),
|
|
1548
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
1549
|
-
color: Colors.accent,
|
|
1550
|
-
children: " ◆ "
|
|
1551
|
-
}), /* @__PURE__ */ jsx(Text, { children: "revenue analytics bundled with data warehouse" })] })
|
|
1552
|
-
]
|
|
1553
|
-
};
|
|
1554
|
-
//#endregion
|
|
1555
|
-
//#region src/lib/programs/migration/content/index.tsx
|
|
1556
|
-
/**
|
|
1557
|
-
* Migration learn deck (statsig variant). Statsig is the only `migrate`
|
|
1558
|
-
* variant today, so this deck plays as-is when the wizard runs
|
|
1559
|
-
* `migrate --product=statsig`. Three movements:
|
|
1560
|
-
*
|
|
1561
|
-
* 1. Welcome and reassure.
|
|
1562
|
-
* 2. What to expect — the migration is replacement-only, takes a few
|
|
1563
|
-
* minutes, leaves the build green.
|
|
1564
|
-
* 3. What's a little different — how flags and experiments work in
|
|
1565
|
-
* PostHog, presented as right-way guidance rather than gotchas.
|
|
1566
|
-
*
|
|
1567
|
-
* FF/experiments guidance paraphrased from PostHog public docs:
|
|
1568
|
-
* - posthog.com/docs/feature-flags/best-practices
|
|
1569
|
-
* - posthog.com/docs/feature-flags/common-questions
|
|
1570
|
-
* - posthog.com/docs/experiments/best-practices
|
|
1571
|
-
*/
|
|
1572
|
-
const getContentBlocks$2 = (store) => [
|
|
1573
|
-
{
|
|
1574
|
-
content: "Hello.",
|
|
1575
|
-
pause: 3e3,
|
|
1576
|
-
mode: 0,
|
|
1577
|
-
animationInterval: 160
|
|
1578
|
-
},
|
|
1579
|
-
{
|
|
1580
|
-
content: "The Wizard is an agent.",
|
|
1581
|
-
pause: 4e3
|
|
1582
|
-
},
|
|
1583
|
-
{
|
|
1584
|
-
content: "As we speak, it’s making a plan to migrate from Statsig to PostHog.",
|
|
1585
|
-
pause: 6e3
|
|
1586
|
-
},
|
|
1587
|
-
{
|
|
1588
|
-
content: "PostHog covers the cost of running this agent.",
|
|
1589
|
-
pause: 4e3
|
|
1590
|
-
},
|
|
1591
|
-
{
|
|
1592
|
-
type: "clear",
|
|
1593
|
-
pause: 2e3
|
|
1594
|
-
},
|
|
1595
|
-
{
|
|
1596
|
-
pause: 5e3,
|
|
1597
|
-
persist: true,
|
|
1598
|
-
content: /* @__PURE__ */ jsx(StatusPeekTrigger, { store })
|
|
1599
|
-
},
|
|
1600
|
-
{
|
|
1601
|
-
pause: 6e3,
|
|
1602
|
-
persist: true,
|
|
1603
|
-
content: /* @__PURE__ */ jsxs(Text, { children: [
|
|
1604
|
-
"Press",
|
|
1605
|
-
" ",
|
|
1606
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1607
|
-
color: Colors.accent,
|
|
1608
|
-
bold: true,
|
|
1609
|
-
children: "S"
|
|
1610
|
-
}),
|
|
1611
|
-
" ",
|
|
1612
|
-
"to expand or collapse the status."
|
|
1613
|
-
] })
|
|
1614
|
-
},
|
|
1615
|
-
{
|
|
1616
|
-
type: "clear",
|
|
1617
|
-
pause: 2e3
|
|
1618
|
-
},
|
|
1619
|
-
{
|
|
1620
|
-
content: "Here’s what to expect.",
|
|
1621
|
-
pause: 3e3
|
|
1622
|
-
},
|
|
1623
|
-
{
|
|
1624
|
-
content: "The migration takes about ten minutes.",
|
|
1625
|
-
pause: 3e3
|
|
1626
|
-
},
|
|
1627
|
-
{
|
|
1628
|
-
content: "Every Statsig call gets replaced in place with its PostHog equivalent.",
|
|
1629
|
-
pause: 5500
|
|
1630
|
-
},
|
|
1631
|
-
{
|
|
1632
|
-
content: "Nothing new gets added. No extra captures, no surprise instrumentation.",
|
|
1633
|
-
pause: 5500
|
|
1634
|
-
},
|
|
1635
|
-
{
|
|
1636
|
-
content: "The Statsig package gets removed at the end. We’ll run build and lint to clean up after ourselves.",
|
|
1637
|
-
pause: 6500
|
|
1638
|
-
},
|
|
1639
|
-
{
|
|
1640
|
-
type: "clear",
|
|
1641
|
-
pause: 2e3
|
|
1642
|
-
},
|
|
1643
|
-
{
|
|
1644
|
-
content: "A few things work a little differently in PostHog.",
|
|
1645
|
-
pause: 4500
|
|
1646
|
-
},
|
|
1647
|
-
{
|
|
1648
|
-
content: /* @__PURE__ */ jsxs(Text, { children: [
|
|
1649
|
-
"Flags evaluate against a stable user. Call",
|
|
1650
|
-
" ",
|
|
1651
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1652
|
-
bold: true,
|
|
1653
|
-
color: Colors.accent,
|
|
1654
|
-
children: "identify()"
|
|
1655
|
-
}),
|
|
1656
|
-
" ",
|
|
1657
|
-
"first, then check the flag."
|
|
1658
|
-
] }),
|
|
1659
|
-
pause: 6e3,
|
|
1660
|
-
persist: true
|
|
1661
|
-
},
|
|
1662
|
-
{
|
|
1663
|
-
content: "For anything in the first paint, evaluate server-side and bootstrap the values into the client.",
|
|
1664
|
-
pause: 6500
|
|
1665
|
-
},
|
|
1666
|
-
{
|
|
1667
|
-
content: /* @__PURE__ */ jsxs(Text, { children: [
|
|
1668
|
-
"In production, route requests through a reverse proxy to avoid ad blockers breaking your flags.",
|
|
1669
|
-
"\n",
|
|
1670
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1671
|
-
dimColor: true,
|
|
1672
|
-
children: "https://posthog.com/docs/advanced/proxy"
|
|
1673
|
-
})
|
|
1674
|
-
] }),
|
|
1675
|
-
pause: 6500,
|
|
1676
|
-
persist: true
|
|
1677
|
-
},
|
|
1678
|
-
{
|
|
1679
|
-
content: "When a flag reaches 100% rollout, retire it. Flags are signals, not switches.",
|
|
1680
|
-
pause: 5500
|
|
1681
|
-
},
|
|
1682
|
-
{
|
|
1683
|
-
content: /* @__PURE__ */ jsxs(Text, { children: [
|
|
1684
|
-
"Name flags descriptively. No double negatives. Reflect the return type.",
|
|
1685
|
-
" ",
|
|
1686
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1687
|
-
dimColor: true,
|
|
1688
|
-
children: "For example "
|
|
1689
|
-
}),
|
|
1690
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1691
|
-
bold: true,
|
|
1692
|
-
children: "show-new-checkout"
|
|
1693
|
-
}),
|
|
1694
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1695
|
-
dimColor: true,
|
|
1696
|
-
children: "."
|
|
1697
|
-
})
|
|
1698
|
-
] }),
|
|
1699
|
-
pause: 6500,
|
|
1700
|
-
persist: true
|
|
1701
|
-
},
|
|
1702
|
-
{
|
|
1703
|
-
type: "clear",
|
|
1704
|
-
pause: 1500
|
|
1705
|
-
},
|
|
1706
|
-
{
|
|
1707
|
-
content: /* @__PURE__ */ jsx(Text, {
|
|
1708
|
-
bold: true,
|
|
1709
|
-
color: Colors.accent,
|
|
1710
|
-
children: "Experiments"
|
|
1711
|
-
}),
|
|
1712
|
-
pause: 2500,
|
|
1713
|
-
persist: true
|
|
1714
|
-
},
|
|
1715
|
-
{
|
|
1716
|
-
content: "Change one thing per variant. Multiple changes in one variant blur the result.",
|
|
1717
|
-
pause: 5500
|
|
1718
|
-
},
|
|
1719
|
-
{
|
|
1720
|
-
content: "Decide the running time up front. PostHog includes a sample-size and duration calculator in the setup flow.",
|
|
1721
|
-
pause: 6500
|
|
1722
|
-
},
|
|
1723
|
-
{
|
|
1724
|
-
content: "Roll out to 5–10% first. Watch the metrics. Then increase.",
|
|
1725
|
-
pause: 5e3
|
|
1726
|
-
},
|
|
1727
|
-
{
|
|
1728
|
-
content: "Exclude users who already completed the flow. They can’t be affected by the test.",
|
|
1729
|
-
pause: 5500
|
|
1730
|
-
},
|
|
1731
|
-
{
|
|
1732
|
-
type: "clear",
|
|
1733
|
-
pause: 1500
|
|
1734
|
-
},
|
|
1735
|
-
{
|
|
1736
|
-
content: "Flags and experiments live alongside the rest of your data.",
|
|
1737
|
-
pause: 4500
|
|
1738
|
-
},
|
|
1739
|
-
{
|
|
1740
|
-
content: "Ship behind a flag, watch replays, check analytics for impact.",
|
|
1741
|
-
pause: 4500
|
|
1742
|
-
},
|
|
1743
|
-
{
|
|
1744
|
-
type: "clear",
|
|
1745
|
-
pause: 1500
|
|
1746
|
-
},
|
|
1747
|
-
{
|
|
1748
|
-
content: "PostHog also provides every other analytics and AI tool to build your product.",
|
|
1749
|
-
pause: 4500
|
|
1750
|
-
},
|
|
1751
|
-
PRODUCT_SUITE_BLOCK,
|
|
1752
|
-
{
|
|
1753
|
-
type: "clear",
|
|
1754
|
-
pause: 1500
|
|
1755
|
-
},
|
|
1756
|
-
{
|
|
1757
|
-
content: "And consolidating onto one platform saves real money.",
|
|
1758
|
-
pause: 4500
|
|
1759
|
-
},
|
|
1760
|
-
{
|
|
1761
|
-
content: "Here’s the math.",
|
|
1762
|
-
pause: 1500
|
|
1763
|
-
},
|
|
1764
|
-
VENDOR_STACK_BLOCK,
|
|
1765
|
-
{
|
|
1766
|
-
type: "clear",
|
|
1767
|
-
pause: 1500
|
|
1768
|
-
},
|
|
1769
|
-
{
|
|
1770
|
-
content: "Pricing is usage-based, with a generous free tier.",
|
|
1771
|
-
pause: 4e3
|
|
1772
|
-
},
|
|
1773
|
-
FREE_TIER_BLOCK,
|
|
1774
|
-
{
|
|
1775
|
-
type: "clear",
|
|
1776
|
-
pause: 1500
|
|
1777
|
-
},
|
|
1778
|
-
PRICING_STRUCTURE_BLOCK,
|
|
1779
|
-
{
|
|
1780
|
-
type: "clear",
|
|
1781
|
-
pause: 1500
|
|
1782
|
-
},
|
|
1783
|
-
{
|
|
1784
|
-
content: "Gain clarity and really understand your users.",
|
|
1785
|
-
pause: 4e3
|
|
1786
|
-
},
|
|
1787
|
-
{
|
|
1788
|
-
content: "Use trends to measure growth.",
|
|
1789
|
-
pause: 2500
|
|
1790
|
-
},
|
|
1791
|
-
LINE_CHART_BLOCK,
|
|
1792
|
-
{
|
|
1793
|
-
type: "clear",
|
|
1794
|
-
pause: 500
|
|
1795
|
-
},
|
|
1796
|
-
{
|
|
1797
|
-
content: "Use funnels to reveal bottlenecks.",
|
|
1798
|
-
pause: 2500
|
|
1799
|
-
},
|
|
1800
|
-
FUNNEL_BLOCK
|
|
1801
|
-
];
|
|
1802
|
-
//#endregion
|
|
1803
|
-
//#region src/lib/programs/migration/index.ts
|
|
1804
|
-
const MIGRATION_REPORT_FILE = "migration-report.md";
|
|
1805
|
-
const MIGRATION_ABORT_CASES = [{
|
|
1806
|
-
match: /^no source-sdk calls found$/i,
|
|
1807
|
-
message: "No source-SDK calls found",
|
|
1808
|
-
body: "The migration needs an existing third-party SDK to migrate from. No calls to the source SDK appear anywhere in this project. If you haven't installed PostHog yet, you don't need this command — run `npx @posthog/wizard@latest` to add PostHog from scratch."
|
|
1809
|
-
}];
|
|
1810
|
-
const DEFAULT_MIGRATE_SKILL_ID = "migrate-statsig";
|
|
1811
|
-
const migrationConfig = {
|
|
1812
|
-
command: "migrate",
|
|
1813
|
-
description: "Migrate to PostHog from another analytics provider",
|
|
1814
|
-
id: "migration",
|
|
1815
|
-
skillId: DEFAULT_MIGRATE_SKILL_ID,
|
|
1816
|
-
steps: MIGRATION_PROGRAM,
|
|
1817
|
-
reportFile: MIGRATION_REPORT_FILE,
|
|
1818
|
-
getContentBlocks: getContentBlocks$2,
|
|
1819
|
-
allowedTools: ["Agent"],
|
|
1820
|
-
disallowedTools: [WIZARD_TOOL_NAMES.wizardAsk],
|
|
1821
|
-
run: {
|
|
1822
|
-
skillId: DEFAULT_MIGRATE_SKILL_ID,
|
|
1823
|
-
integrationLabel: "migration",
|
|
1824
|
-
customPrompt: () => `Migrate this project from its existing third-party analytics, feature-flag, and observability tools to PostHog. Run the \`migrate\` skill end-to-end: follow the step chain starting at references/1-presence.md. Only replace existing source-SDK call sites with PostHog equivalents — make zero unrelated changes and no net-new instrumentation. The final report is written to ./${MIGRATION_REPORT_FILE}.`,
|
|
1825
|
-
successMessage: `Migration complete! View the report at ./${MIGRATION_REPORT_FILE}`,
|
|
1826
|
-
reportFile: MIGRATION_REPORT_FILE,
|
|
1827
|
-
docsUrl: "",
|
|
1828
|
-
spinnerMessage: "Migrating to PostHog...",
|
|
1829
|
-
estimatedDurationMinutes: 8,
|
|
1830
|
-
abortCases: MIGRATION_ABORT_CASES
|
|
1831
|
-
},
|
|
1832
|
-
requires: ["posthog-integration"]
|
|
1833
|
-
};
|
|
1834
|
-
//#endregion
|
|
1835
|
-
//#region src/lib/programs/error-tracking-upload-source-maps/detect.ts
|
|
1836
|
-
const DISPLAY_NAME = {
|
|
1837
|
-
web: "Web (JavaScript)",
|
|
1838
|
-
nextjs: "Next.js",
|
|
1839
|
-
node: "Node.js",
|
|
1840
|
-
react: "React",
|
|
1841
|
-
angular: "Angular",
|
|
1842
|
-
nuxt: "Nuxt",
|
|
1843
|
-
"react-native": "React Native",
|
|
1844
|
-
android: "Android",
|
|
1845
|
-
flutter: "Flutter",
|
|
1846
|
-
ios: "iOS",
|
|
1847
|
-
vite: "Vite",
|
|
1848
|
-
webpack: "Webpack",
|
|
1849
|
-
rollup: "Rollup",
|
|
1850
|
-
go: "Go",
|
|
1851
|
-
rust: "Rust"
|
|
1852
|
-
};
|
|
1853
|
-
/**
|
|
1854
|
-
* Variants the wizard can wire up source-map upload for automatically. Go and
|
|
1855
|
-
* Rust upload native debug symbols (`posthog-cli symbol-sets upload`) instead
|
|
1856
|
-
* of source maps, but the wiring is the same shape.
|
|
1857
|
-
*
|
|
1858
|
-
* Invariant: every variant listed here must have a published
|
|
1859
|
-
* `error-tracking-upload-source-maps-<variant>` skill in context-mill —
|
|
1860
|
-
* STEP 2 of the run installs that exact id, so an entry without a shipped
|
|
1861
|
-
* skill fails every run for that stack.
|
|
1862
|
-
*/
|
|
1863
|
-
const AUTOMATABLE_VARIANTS = [
|
|
1864
|
-
"android",
|
|
1865
|
-
"ios",
|
|
1866
|
-
"react-native",
|
|
1867
|
-
"flutter",
|
|
1868
|
-
"go",
|
|
1869
|
-
"rust",
|
|
1870
|
-
"web",
|
|
1871
|
-
"nextjs",
|
|
1872
|
-
"node",
|
|
1873
|
-
"react",
|
|
1874
|
-
"angular",
|
|
1875
|
-
"nuxt",
|
|
1876
|
-
"vite",
|
|
1877
|
-
"webpack",
|
|
1878
|
-
"rollup"
|
|
1879
|
-
];
|
|
1880
|
-
/**
|
|
1881
|
-
* Variants the wizard pre-installs a machine-global `posthog-cli` for — their
|
|
1882
|
-
* build shells out to it with no npx / local-dep fallback. Web JS variants stay
|
|
1883
|
-
* out. React Native counts as native here — its Xcode build phases and Gradle
|
|
1884
|
-
* tasks invoke the global CLI. Flutter counts too despite emitting plain
|
|
1885
|
-
* `.js.map` files: a Flutter project has no `package.json`, so there is no npx
|
|
1886
|
-
* or local dependency to fall back to when the post-build step invokes the CLI.
|
|
1887
|
-
*/
|
|
1888
|
-
const VARIANTS_REQUIRING_POSTHOG_CLI = new Set([
|
|
1889
|
-
"ios",
|
|
1890
|
-
"android",
|
|
1891
|
-
"react-native",
|
|
1892
|
-
"flutter",
|
|
1893
|
-
"go",
|
|
1894
|
-
"rust"
|
|
1895
|
-
]);
|
|
1896
|
-
/**
|
|
1897
|
-
* Automatable variants whose SDK the wizard's default flow cannot install —
|
|
1898
|
-
* remediation for a missing SDK is a manual install (posthog-go /
|
|
1899
|
-
* posthog-rs), not `npx @posthog/wizard`. Drives the missing-SDK copy in the
|
|
1900
|
-
* picker and the classifier.
|
|
1901
|
-
*/
|
|
1902
|
-
const MANUAL_SDK_INSTALL = {
|
|
1903
|
-
go: "add the posthog-go module first",
|
|
1904
|
-
rust: "add the posthog-rs crate first"
|
|
1905
|
-
};
|
|
1906
|
-
const MANUAL_SDK_VARIANTS = Object.keys(MANUAL_SDK_INSTALL);
|
|
1907
|
-
/** Dependency string that identifies the Rust SDK in Cargo.toml. */
|
|
1908
|
-
const RUST_SDK_CRATE = "posthog-rs";
|
|
1909
|
-
/** Module path that identifies the Go SDK in go.mod. */
|
|
1910
|
-
const GO_SDK_MODULE = "github.com/posthog/posthog-go";
|
|
1911
|
-
/** `[ABORT] <reason>` cases the source maps skill can emit. */
|
|
1912
|
-
const SOURCE_MAPS_ABORT_CASES = [
|
|
1913
|
-
{
|
|
1914
|
-
match: /^no posthog sdk detected$/i,
|
|
1915
|
-
errorCode: ErrorCodes.DetectNoPosthogSdk,
|
|
1916
|
-
message: "No PostHog SDK detected",
|
|
1917
|
-
body: "The agent could not find a PostHog SDK in your project. Source map upload requires the SDK to already be installed so it can report errors. Run `npx @posthog/wizard` first to install the SDK (for Go and Rust, add posthog-go / posthog-rs by hand first — the wizard cannot install the SDK for those stacks yet).",
|
|
1918
|
-
docsUrl: "https://posthog.com/docs/error-tracking"
|
|
1919
|
-
},
|
|
1920
|
-
{
|
|
1921
|
-
match: /^build command not found$/i,
|
|
1922
|
-
message: "Build command not found",
|
|
1923
|
-
body: "The agent could not identify how to build your project. Source map upload runs as part of the production build. Add a build script to your project and run this wizard again.",
|
|
1924
|
-
docsUrl: "https://posthog.com/docs/error-tracking/upload-source-maps"
|
|
1925
|
-
},
|
|
1926
|
-
{
|
|
1927
|
-
match: /^bare react native not supported$/i,
|
|
1928
|
-
errorCode: ErrorCodes.DetectUnsupportedPlatform,
|
|
1929
|
-
message: "Bare React Native is not supported",
|
|
1930
|
-
body: "Source-map upload for React Native requires Expo, and this project has no `expo` package. Bare React Native builds cannot inject the chunk IDs PostHog needs to resolve stack traces.",
|
|
1931
|
-
docsUrl: "https://posthog.com/docs/error-tracking/upload-source-maps/react-native"
|
|
1932
|
-
}
|
|
1933
|
-
];
|
|
1934
|
-
const SOURCE_MAPS_CONTEXT_KEYS = {
|
|
1935
|
-
skillVariant: "sourceMapsSkillVariant",
|
|
1936
|
-
displayName: "sourceMapsDisplayName",
|
|
1937
|
-
packagePaths: "sourceMapsPackagePaths",
|
|
1938
|
-
detectError: "detectError",
|
|
1939
|
-
selectedVariant: "sourceMapsSelectedVariant",
|
|
1940
|
-
selectedDisplayName: "sourceMapsSelectedDisplayName",
|
|
1941
|
-
selectedPath: "sourceMapsSelectedPath"
|
|
1942
|
-
};
|
|
1943
|
-
//#endregion
|
|
1944
|
-
//#region src/lib/programs/error-tracking-upload-source-maps/steps.ts
|
|
1945
|
-
function projectSelected(session) {
|
|
1946
|
-
return session.frameworkContext[SOURCE_MAPS_CONTEXT_KEYS.selectedVariant] != null;
|
|
1947
|
-
}
|
|
1948
|
-
const ERROR_TRACKING_UPLOAD_SOURCE_MAPS_PROGRAM = [
|
|
1949
|
-
{
|
|
1950
|
-
id: "intro",
|
|
1951
|
-
label: "Welcome",
|
|
1952
|
-
screenId: "source-maps-intro",
|
|
1953
|
-
gate: (session) => session.setupConfirmed
|
|
1954
|
-
},
|
|
1955
|
-
{
|
|
1956
|
-
id: "auth",
|
|
1957
|
-
label: "Authentication",
|
|
1958
|
-
screenId: "auth",
|
|
1959
|
-
isComplete: (session) => session.credentials !== null
|
|
1960
|
-
},
|
|
1961
|
-
{
|
|
1962
|
-
id: "detect",
|
|
1963
|
-
label: "Detecting projects",
|
|
1964
|
-
screenId: "source-maps-detect",
|
|
1965
|
-
isComplete: projectSelected,
|
|
1966
|
-
gate: projectSelected
|
|
1967
|
-
},
|
|
1968
|
-
{
|
|
1969
|
-
id: "run",
|
|
1970
|
-
label: "Upload source maps",
|
|
1971
|
-
screenId: "run",
|
|
1972
|
-
isComplete: (session) => session.runPhase === "completed" || session.runPhase === "error"
|
|
1973
|
-
},
|
|
1974
|
-
{
|
|
1975
|
-
id: "outro",
|
|
1976
|
-
label: "Done",
|
|
1977
|
-
screenId: "source-maps-outro",
|
|
1978
|
-
isComplete: (session) => session.outroDismissed
|
|
1979
|
-
},
|
|
1980
|
-
{
|
|
1981
|
-
id: "skills",
|
|
1982
|
-
label: "Skills",
|
|
1983
|
-
screenId: "keep-skills"
|
|
1984
|
-
}
|
|
1985
|
-
];
|
|
1986
|
-
//#endregion
|
|
1987
|
-
//#region src/lib/programs/error-tracking-upload-source-maps/prompt.ts
|
|
1988
|
-
const SOURCE_MAPS_DETECTION_FAILED_PROMPT = `Detection did not pick a source maps skill variant for this project.
|
|
1989
|
-
Emit: ${AgentSignals.ABORT} unsupported-platform
|
|
1990
|
-
Then halt.`;
|
|
1991
|
-
function buildSourceMapsUploadPrompt(params) {
|
|
1992
|
-
const { displayName, variant, skillId, projectPath, projectId, host, settingsUrl, uiHost, reportFile } = params;
|
|
1993
|
-
const platformLabel = displayName ?? variant;
|
|
1994
|
-
const inSubproject = projectPath != null && projectPath !== ".";
|
|
1995
|
-
const projectLine = inSubproject ? `- Project directory (relative to the wizard's working directory): ${projectPath}` : "- Project directory: the wizard's working directory (even when it sits inside a larger git repo, this directory is the project root)";
|
|
1996
|
-
const credentialSteps = `STEP 4 — Make the credentials readable at build time. (skill: "Make credentials available at build time")
|
|
1997
|
-
Follow the skill's step. Wizard-specific: if it calls for a loader (e.g.
|
|
1998
|
-
\`dotenv\`), install it SILENTLY — do NOT ask the user or call wizard_ask.
|
|
1999
|
-
Skip this step entirely if the platform already auto-loads .env.
|
|
2000
|
-
|
|
2001
|
-
STEP 5 — Write the credentials to the env file. (skill: "Write credentials to the env file")
|
|
2002
|
-
Use the wizard-tools MCP server. Reuse the env file the skill tells you to
|
|
2003
|
-
pick — the prerequisite PostHog integration usually already wrote
|
|
2004
|
-
POSTHOG_* vars to one, so seed your keys alongside them.
|
|
2005
|
-
- First call check_env_keys with the key names and that file as filePath.
|
|
2006
|
-
It answers { status: "present" | "missing", foundIn: [paths] } per key —
|
|
2007
|
-
names and paths only, never values, so don't read the file directly.
|
|
2008
|
-
"present" means a real env file sets the key; a key found only in a
|
|
2009
|
-
committed template (.env.example and friends) reads as "missing",
|
|
2010
|
-
because a template documents a key rather than setting it.
|
|
2011
|
-
- Env tool path rule: ${inSubproject ? `Tool filePaths are relative to the wizard's working directory, not the selected project directory. Treat the skill's env path as relative to the selected project and prefix it with \`${projectPath}/\` when calling the tools: for example, pass \`${projectPath}/.env\`, not \`.env\` (which would target the wizard's working directory).${variant === "rust" ? ` Cargo workspace exception: when the skill places the env file at the workspace root instead, pass the path of the ROOT manifest's directory relative to the wizard's working directory (e.g. \`rust/.env\` for a workspace root at \`rust/\`, or plain \`.env\` when the workspace root IS the working directory) — not the member path.` : ""}` : `Tool filePaths are relative to the wizard's working directory. For an env file at this project root, pass \`.env\`; never prefix it with this directory's path inside an ancestor repository.`}
|
|
2012
|
-
- Then call set_env_values, passing the STEP 1 secretRef as a value
|
|
2013
|
-
object, not a literal string. Take the three variable NAMES from the
|
|
2014
|
-
skill's per-uploader list in "Write credentials to the env file" — they
|
|
2015
|
-
differ by uploader, and only the shape below is fixed:
|
|
2016
|
-
values: {
|
|
2017
|
-
"<key var for the uploader you wired>": { secretRef: "<the ref from STEP 1>" },
|
|
2018
|
-
"<project id var>": "${projectId}",
|
|
2019
|
-
"<host var>": "${uiHost}"
|
|
2020
|
-
}
|
|
2021
|
-
Write that one set only. Do not also write another uploader's variables
|
|
2022
|
-
as a fallback — a credential nothing reads is an unused secret sitting
|
|
2023
|
-
in the user's env file.
|
|
2024
|
-
The wizard resolves the ref locally before writing, so you never see the
|
|
2025
|
-
key value.`;
|
|
2026
|
-
return `You are wiring up PostHog Error Tracking source map upload for this ${platformLabel} project.
|
|
2027
|
-
|
|
2028
|
-
Project context:
|
|
2029
|
-
- PostHog Project ID: ${projectId}
|
|
2030
|
-
- PostHog Host: ${host}
|
|
2031
|
-
- Detected platform: ${platformLabel}
|
|
2032
|
-
- Skill to use: ${skillId}
|
|
2033
|
-
${projectLine}
|
|
2034
|
-
- Personal API keys settings page: ${settingsUrl}
|
|
2035
|
-
|
|
2036
|
-
All file changes, build/run commands, and config edits target the project directory above${inSubproject ? ` — this is a monorepo, so scope your work to \`${projectPath}\` and do not touch other packages` : ""}.${variant === "rust" && inSubproject ? `
|
|
2037
|
-
Cargo workspace exception: if the selected project is a member of a Cargo
|
|
2038
|
-
workspace (resolve the root with \`cargo locate-project --workspace
|
|
2039
|
-
--message-format plain\`), the workspace root's Cargo.toml (for \`[profile.*]\`
|
|
2040
|
-
edits), the workspace-level \`target/\` directory, and a \`.env\` next to the
|
|
2041
|
-
root manifest are IN SCOPE — treat them as part of the selected project, and
|
|
2042
|
-
follow the skill's workspace guidance for which paths to use. Every other
|
|
2043
|
-
package stays off-limits.` : ""}
|
|
2044
|
-
|
|
2045
|
-
The skill you install in STEP 2 is the source of truth for the HOW of every
|
|
2046
|
-
step: its "## Steps" section has an overview, tips and per-technology
|
|
2047
|
-
examples for each named step, and its reference files carry the exact
|
|
2048
|
-
per-framework API. The STEPS below give the order, the conditionals, and the
|
|
2049
|
-
wizard-specific mechanics (which MCP tool to call, signals to emit) — read
|
|
2050
|
-
the matching skill step (named in parentheses) before doing the work, and do
|
|
2051
|
-
not invent steps the skill doesn't describe.
|
|
2052
|
-
|
|
2053
|
-
Follow these steps IN ORDER. Do not skip or reorder.
|
|
2054
|
-
|
|
2055
|
-
Your FIRST message must contain ONLY parallel tool calls, in this order:
|
|
2056
|
-
- the STEP 1 wizard_ask call FIRST — tool calls execute as they stream,
|
|
2057
|
-
so this puts the API-key prompt in front of the user within seconds —
|
|
2058
|
-
then
|
|
2059
|
-
- one TaskCreate call PER task below, all in this same message (the tool
|
|
2060
|
-
takes a single task per call). The TUI shows only the subject, so keep
|
|
2061
|
-
every description to a few words — never a sentence.
|
|
2062
|
-
Do not read files, explore the project, or write any text first, and keep
|
|
2063
|
-
any thinking before the calls to a single short sentence.
|
|
2064
|
-
|
|
2065
|
-
Use exactly these tasks, in this order — do not collapse, rename, or omit
|
|
2066
|
-
any of them. Getting the API key is NOT a task — its prompt is already on
|
|
2067
|
-
screen by the time the list renders:
|
|
2068
|
-
1. Install source maps skill
|
|
2069
|
-
2. Apply build-config changes (per skill)
|
|
2070
|
-
3. Make credentials readable at build time
|
|
2071
|
-
4. Write keys to .env
|
|
2072
|
-
5. Identify build & run commands
|
|
2073
|
-
6. Set up CI for auto-upload
|
|
2074
|
-
7. Test the local setup
|
|
2075
|
-
8. Summarise & hand off
|
|
2076
|
-
Drive the list with TaskUpdate — mark a task in_progress when you start it
|
|
2077
|
-
and completed when done. ALWAYS keep task 7 ("Test the local setup") in the
|
|
2078
|
-
list even if the user declines it in STEP 8: mark it completed rather than
|
|
2079
|
-
deleting it, so the user can see it was offered.
|
|
2080
|
-
|
|
2081
|
-
STEP 1 — Get a personal API key from the user. (skill: "Get a personal API key")
|
|
2082
|
-
This wizard_ask call ships in your FIRST message, per the rule above.
|
|
2083
|
-
The wizard cannot mint keys — never call the PostHog API or any tool to
|
|
2084
|
-
create one. Ask the user with the wizard_ask MCP tool; you receive the
|
|
2085
|
-
answer as a vaulted secretRef (never the raw value), which you reuse in
|
|
2086
|
-
STEP 5:
|
|
2087
|
-
{
|
|
2088
|
-
id: "api-key",
|
|
2089
|
-
prompt: "Paste your PostHog personal API key below.\\n\\nDon't have one yet? Create one here:\\n${settingsUrl}\\n\\nWhen creating the key, choose the 'Source map upload' preset, then come back and paste it here.",
|
|
2090
|
-
kind: "text",
|
|
2091
|
-
sensitive: true
|
|
2092
|
-
}
|
|
2093
|
-
Keep the \\n line breaks exactly as written — Ink's <Text> renders them
|
|
2094
|
-
as separate lines. The answer is { secretRef: "secret:..." }.
|
|
2095
|
-
If wizard_ask is unavailable (CI / non-interactive), emit
|
|
2096
|
-
${AgentSignals.ABORT} requires-interactive-mode and halt.
|
|
2097
|
-
|
|
2098
|
-
STEP 2 — Install the skill.
|
|
2099
|
-
Call install_skill (wizard-tools MCP server) with skillId "${skillId}".
|
|
2100
|
-
Do NOT run shell commands to install skills. Then read the installed
|
|
2101
|
-
SKILL.md and its reference files — they drive STEPS 3-9.
|
|
2102
|
-
If install fails, emit ${AgentSignals.ERROR_RESOURCE_MISSING} skill ${skillId} could not be installed.
|
|
2103
|
-
|
|
2104
|
-
STEP 3 — Apply build-config changes. (skill: "Apply build-config changes")
|
|
2105
|
-
Make the bundler / build-config changes the skill's step instructs. The
|
|
2106
|
-
skill and its reference are the source of truth for this platform.
|
|
2107
|
-
|
|
2108
|
-
${credentialSteps}
|
|
2109
|
-
|
|
2110
|
-
STEP 6 — Identify the build AND run commands. (skill: "Identify the build and run commands")
|
|
2111
|
-
Per the skill, resolve the production BUILD command and the RUN command
|
|
2112
|
-
for THIS project (use detect_package_manager for the package manager). Do
|
|
2113
|
-
NOT run either yourself — the user runs them. If you cannot identify a
|
|
2114
|
-
build command, emit ${AgentSignals.ABORT} build command not found.
|
|
2115
|
-
|
|
2116
|
-
STEP 7 — Set up CI for automatic uploads. (skill: "Set up CI for automatic uploads")
|
|
2117
|
-
Source maps only upload when the production build runs, so the build's
|
|
2118
|
-
CI/CD must carry the same upload credentials you wrote in STEP 5. Do this
|
|
2119
|
-
step without asking — there is no opt-in question for it. Follow the
|
|
2120
|
-
skill's "Set up CI for automatic uploads" step — it is the source of
|
|
2121
|
-
truth for tracing where the production build runs and wiring the
|
|
2122
|
-
credentials through every layer, whatever the CI provider.
|
|
2123
|
-
Wizard-specific rules on top:
|
|
2124
|
-
- Trace the deploy path by reading the project's files — do NOT ask the
|
|
2125
|
-
user, and do NOT invent config that isn't there.
|
|
2126
|
-
- Carry every manual follow-up the skill has you hand off (secrets the
|
|
2127
|
-
user must create, an untraceable build path) into STEP 9.
|
|
2128
|
-
|
|
2129
|
-
STEP 8 — Offer to test the local setup. (skill: "Test the local setup")
|
|
2130
|
-
Call wizard_ask:
|
|
2131
|
-
{
|
|
2132
|
-
id: "test-affordance",
|
|
2133
|
-
prompt: "Want me to help you test your local setup? I'll add a temporary test button (or route) to your app so you can confirm errors show up in Error Tracking with readable stack traces after your next build. I'll remove it once you've confirmed it works.",
|
|
2134
|
-
kind: "single",
|
|
2135
|
-
options: [
|
|
2136
|
-
{ label: "Yes, help me test it", value: "yes" },
|
|
2137
|
-
{ label: "No, I'll test on my own later", value: "no" }
|
|
2138
|
-
]
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
If "no", skip to STEP 9.
|
|
2142
|
-
|
|
2143
|
-
If "yes", follow the skill's "Test the local setup" step for the
|
|
2144
|
-
platform-appropriate affordance, the captureException shape, the
|
|
2145
|
-
placement, and the read-before-edit / always-revert rules. Then pause for
|
|
2146
|
-
the user with wizard_ask, baking the build-and-run flow (and the exact
|
|
2147
|
-
button label / route) into the prompt as literal, copy-pasteable
|
|
2148
|
-
numbered steps:
|
|
2149
|
-
- Steps 1-2: the production build, then launching the app and
|
|
2150
|
-
triggering the test affordance. Source them from STEP 6 and the
|
|
2151
|
-
skill's "Test the local setup" step — when the skill gives the
|
|
2152
|
-
platform's test flow as verbatim steps (IDE-driven platforms like
|
|
2153
|
-
Xcode do), use its wording and do NOT invent CLI build commands,
|
|
2154
|
-
debugger steps, or relaunch steps it doesn't state. Quote CLI
|
|
2155
|
-
commands verbatim. When build and run are one action (an IDE Run),
|
|
2156
|
-
fold them into step 1 and make step 2 just triggering the affordance.
|
|
2157
|
-
- The last step is always the Error Tracking check, exactly as in the
|
|
2158
|
-
template.
|
|
2159
|
-
Separate each numbered step with \\n\\n so the TUI renders them as
|
|
2160
|
-
distinct lines:
|
|
2161
|
-
{
|
|
2162
|
-
id: "test-done",
|
|
2163
|
-
prompt: "1) <production build step — it uploads source maps and builds the app with the test affordance>\\n\\n2) <run step>, then click the \\"<your test button label>\\" button (or hit \`<your test route>\`).\\n\\n3) Open Error Tracking in PostHog (${uiHost}/project/${projectId}/error_tracking) and confirm the test error appears with a source-resolved stack trace pointing at real source files (not minified bundle paths).\\n\\nWhen you're done, select Continue and I'll revert the test code.",
|
|
2164
|
-
kind: "single",
|
|
2165
|
-
options: [{ label: "Continue (revert test code)", value: "continue" }]
|
|
2166
|
-
}
|
|
2167
|
-
After the user continues, revert the test code per the skill's rules and
|
|
2168
|
-
surface any failure in STEP 9.
|
|
2169
|
-
|
|
2170
|
-
STEP 9 — Summarise and hand off. (skill: "Verify and hand off")
|
|
2171
|
-
Follow the skill's "Verify and hand off" step. Write the hand-off to
|
|
2172
|
-
\`${reportFile}\` at the WIZARD'S WORKING DIRECTORY — pass exactly
|
|
2173
|
-
\`${reportFile}\` as the file path, never prefixed with the selected
|
|
2174
|
-
project directory; this file is the one exception to the project-scope
|
|
2175
|
-
rule above. Cover: the files you changed (paths only), the exact build
|
|
2176
|
-
and upload commands, every CI secret the user still has to create, and
|
|
2177
|
-
how to verify the upload — then give the same summary in chat. Never
|
|
2178
|
-
write secret values into the report, only variable names. The success
|
|
2179
|
-
screen points the user at this file, so do not skip it. The Symbol sets page for this project — where the user
|
|
2180
|
-
confirms the upload landed — is:
|
|
2181
|
-
${uiHost}/project/${projectId}/error_tracking/configuration
|
|
2182
|
-
`;
|
|
2183
|
-
}
|
|
2184
|
-
//#endregion
|
|
2185
|
-
//#region src/lib/programs/error-tracking-upload-source-maps/content/index.tsx
|
|
2186
|
-
/**
|
|
2187
|
-
* Source-maps learn-deck — the narrative played in the run screen's left
|
|
2188
|
-
* pane (LearnCard) while the agent wires source-map upload into the build.
|
|
2189
|
-
*
|
|
2190
|
-
* It educates the user on what source maps are and why uploading them
|
|
2191
|
-
* matters, built around a before/after stack-trace contrast: a minified
|
|
2192
|
-
* production trace nobody can read, then the same trace resolved back to
|
|
2193
|
-
* real source. Program-owned; wired onto the program's getContentBlocks.
|
|
2194
|
-
*
|
|
2195
|
-
* Lines stay narrow (~36 cols) because this renders in the left half of a
|
|
2196
|
-
* split pane — see LearnCard's paneWidth math.
|
|
2197
|
-
*/
|
|
2198
|
-
/**
|
|
2199
|
-
* Per-slide dwell multiplier. Each block stays on screen for `pause * SLIDE_PACE`
|
|
2200
|
-
* ms after it finishes animating, before the deck advances. Bump this single
|
|
2201
|
-
* knob to give every slide more reading time. Clear (page-break) blocks are
|
|
2202
|
-
* left untouched so the blank gap between slides stays snappy.
|
|
2203
|
-
*/
|
|
2204
|
-
const SLIDE_PACE = 1.5;
|
|
2205
|
-
const withPace = (block) => {
|
|
2206
|
-
if (typeof block === "string" || isClearBlock(block) || block.pause == null) return block;
|
|
2207
|
-
return {
|
|
2208
|
-
...block,
|
|
2209
|
-
pause: Math.round(block.pause * SLIDE_PACE)
|
|
2210
|
-
};
|
|
2211
|
-
};
|
|
2212
|
-
/** Apply the dwell multiplier to every block in a deck. */
|
|
2213
|
-
const pace = (blocks) => blocks.map(withPace);
|
|
2214
|
-
/**
|
|
2215
|
-
* A minified production stack trace — the problem source maps solve. Framed as
|
|
2216
|
-
* a labelled, muted example (no error-red ✘) so a glance reads it as
|
|
2217
|
-
* illustrative content, not as the wizard itself having errored mid-run.
|
|
2218
|
-
*/
|
|
2219
|
-
const MINIFIED_TRACE = {
|
|
2220
|
-
type: "lines",
|
|
2221
|
-
interval: 400,
|
|
2222
|
-
pause: 7e3,
|
|
2223
|
-
lines: [
|
|
2224
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2225
|
-
dimColor: true,
|
|
2226
|
-
children: "example — minified production trace"
|
|
2227
|
-
}),
|
|
2228
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2229
|
-
color: Colors.muted,
|
|
2230
|
-
children: " TypeError: cart is undefined"
|
|
2231
|
-
}),
|
|
2232
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2233
|
-
dimColor: true,
|
|
2234
|
-
children: " at t.min.js:1:48213"
|
|
2235
|
-
}),
|
|
2236
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2237
|
-
dimColor: true,
|
|
2238
|
-
children: " at t.min.js:1:9402"
|
|
2239
|
-
}),
|
|
2240
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2241
|
-
dimColor: true,
|
|
2242
|
-
children: " at t.min.js:1:71150"
|
|
2243
|
-
})
|
|
2244
|
-
]
|
|
2245
|
-
};
|
|
2246
|
-
/** The same trace, resolved through uploaded source maps. */
|
|
2247
|
-
const RESOLVED_TRACE = {
|
|
2248
|
-
type: "lines",
|
|
2249
|
-
interval: 400,
|
|
2250
|
-
pause: 8e3,
|
|
2251
|
-
lines: [
|
|
2252
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2253
|
-
dimColor: true,
|
|
2254
|
-
children: "example — resolved with source maps"
|
|
2255
|
-
}),
|
|
2256
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2257
|
-
color: Colors.success,
|
|
2258
|
-
children: " ✔ TypeError: cart is undefined"
|
|
2259
|
-
}),
|
|
2260
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
2261
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2262
|
-
dimColor: true,
|
|
2263
|
-
children: " at "
|
|
2264
|
-
}),
|
|
2265
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2266
|
-
color: "cyan",
|
|
2267
|
-
children: "Cart.tsx:42"
|
|
2268
|
-
}),
|
|
2269
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2270
|
-
dimColor: true,
|
|
2271
|
-
children: " loadCart"
|
|
2272
|
-
})
|
|
2273
|
-
] }),
|
|
2274
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
2275
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2276
|
-
dimColor: true,
|
|
2277
|
-
children: " at "
|
|
2278
|
-
}),
|
|
2279
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2280
|
-
color: "cyan",
|
|
2281
|
-
children: "App.tsx:88"
|
|
2282
|
-
}),
|
|
2283
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2284
|
-
dimColor: true,
|
|
2285
|
-
children: " render"
|
|
2286
|
-
})
|
|
2287
|
-
] }),
|
|
2288
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
2289
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2290
|
-
dimColor: true,
|
|
2291
|
-
children: " at "
|
|
2292
|
-
}),
|
|
2293
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2294
|
-
color: "cyan",
|
|
2295
|
-
children: "index.tsx:5"
|
|
2296
|
-
}),
|
|
2297
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2298
|
-
dimColor: true,
|
|
2299
|
-
children: " main"
|
|
2300
|
-
})
|
|
2301
|
-
] })
|
|
2302
|
-
]
|
|
2303
|
-
};
|
|
2304
|
-
/**
|
|
2305
|
-
* How a bundle is tied to its map: PostHog injects a chunk-ID marker into the
|
|
2306
|
-
* built JS and stamps the matching source map with the same ID.
|
|
2307
|
-
*/
|
|
2308
|
-
const CHUNK_ID_LINK = {
|
|
2309
|
-
type: "lines",
|
|
2310
|
-
interval: 450,
|
|
2311
|
-
pause: 7500,
|
|
2312
|
-
lines: [
|
|
2313
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2314
|
-
dimColor: true,
|
|
2315
|
-
children: "app.min.js"
|
|
2316
|
-
}),
|
|
2317
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2318
|
-
dimColor: true,
|
|
2319
|
-
children: " …minified code…"
|
|
2320
|
-
}),
|
|
2321
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
2322
|
-
color: "cyan",
|
|
2323
|
-
children: " //# chunkId=a1b2c3d4"
|
|
2324
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
2325
|
-
dimColor: true,
|
|
2326
|
-
children: " ← injected"
|
|
2327
|
-
})] }),
|
|
2328
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2329
|
-
dimColor: true,
|
|
2330
|
-
children: " ↕ matched by id"
|
|
2331
|
-
}),
|
|
2332
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
2333
|
-
dimColor: true,
|
|
2334
|
-
children: "app.min.js.map"
|
|
2335
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
2336
|
-
dimColor: true,
|
|
2337
|
-
children: " ← uploaded"
|
|
2338
|
-
})] })
|
|
2339
|
-
]
|
|
2340
|
-
};
|
|
2341
|
-
/** Many similar exceptions collapse into a single issue. */
|
|
2342
|
-
const GROUPING = {
|
|
2343
|
-
type: "lines",
|
|
2344
|
-
interval: 450,
|
|
2345
|
-
pause: 7e3,
|
|
2346
|
-
lines: [
|
|
2347
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2348
|
-
dimColor: true,
|
|
2349
|
-
children: "exception ─┐"
|
|
2350
|
-
}),
|
|
2351
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
2352
|
-
dimColor: true,
|
|
2353
|
-
children: "exception ─┼──→ "
|
|
2354
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
2355
|
-
color: Colors.accent,
|
|
2356
|
-
bold: true,
|
|
2357
|
-
children: "1 issue"
|
|
2358
|
-
})] }),
|
|
2359
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2360
|
-
dimColor: true,
|
|
2361
|
-
children: "exception ─┘"
|
|
2362
|
-
})
|
|
2363
|
-
]
|
|
2364
|
-
};
|
|
2365
|
-
const getContentBlocks$1 = (store) => pace([
|
|
2366
|
-
{
|
|
2367
|
-
content: "Welcome.",
|
|
2368
|
-
pause: 3e3,
|
|
2369
|
-
mode: 0,
|
|
2370
|
-
animationInterval: 160
|
|
2371
|
-
},
|
|
2372
|
-
{
|
|
2373
|
-
content: "I'm wiring PostHog Error Tracking into your build.",
|
|
2374
|
-
pause: 5e3
|
|
2375
|
-
},
|
|
2376
|
-
{
|
|
2377
|
-
type: "clear",
|
|
2378
|
-
pause: 1500
|
|
2379
|
-
},
|
|
2380
|
-
{
|
|
2381
|
-
content: "When you ship to production, your code gets minified.",
|
|
2382
|
-
pause: 5e3
|
|
2383
|
-
},
|
|
2384
|
-
{
|
|
2385
|
-
content: "Thousands of readable lines collapse into one dense bundle.",
|
|
2386
|
-
pause: 5e3
|
|
2387
|
-
},
|
|
2388
|
-
{
|
|
2389
|
-
content: "So a thrown error gives you a stack trace like this:",
|
|
2390
|
-
pause: 2e3
|
|
2391
|
-
},
|
|
2392
|
-
MINIFIED_TRACE,
|
|
2393
|
-
{
|
|
2394
|
-
content: "Just offsets into a file no human can read.",
|
|
2395
|
-
pause: 5e3
|
|
2396
|
-
},
|
|
2397
|
-
{
|
|
2398
|
-
type: "clear",
|
|
2399
|
-
pause: 1500
|
|
2400
|
-
},
|
|
2401
|
-
{
|
|
2402
|
-
content: "Source maps are the key.",
|
|
2403
|
-
pause: 3500
|
|
2404
|
-
},
|
|
2405
|
-
{
|
|
2406
|
-
content: "They map every position in that bundle back to your original source — the real file, line, and function.",
|
|
2407
|
-
pause: 6e3
|
|
2408
|
-
},
|
|
2409
|
-
{
|
|
2410
|
-
content: "Right now I'm hooking source-map generation and upload into your build, tied to each release you ship.",
|
|
2411
|
-
pause: 6e3
|
|
2412
|
-
},
|
|
2413
|
-
{
|
|
2414
|
-
type: "clear",
|
|
2415
|
-
pause: 1500
|
|
2416
|
-
},
|
|
2417
|
-
{
|
|
2418
|
-
content: "But how does PostHog know which map belongs to which build?",
|
|
2419
|
-
pause: 4500
|
|
2420
|
-
},
|
|
2421
|
-
{
|
|
2422
|
-
content: "During the build, it injects a unique chunk ID into each bundle:",
|
|
2423
|
-
pause: 2500
|
|
2424
|
-
},
|
|
2425
|
-
CHUNK_ID_LINK,
|
|
2426
|
-
{
|
|
2427
|
-
content: "The matching source map is stamped with that same ID before it ships to PostHog.",
|
|
2428
|
-
pause: 6e3
|
|
2429
|
-
},
|
|
2430
|
-
{
|
|
2431
|
-
content: "When an error comes in, PostHog reads the chunk ID off the bundle, finds the map with the exact same ID, and uses it to map each frame back to your source — even for a release you shipped weeks ago.",
|
|
2432
|
-
pause: 8e3
|
|
2433
|
-
},
|
|
2434
|
-
{
|
|
2435
|
-
type: "clear",
|
|
2436
|
-
pause: 1500
|
|
2437
|
-
},
|
|
2438
|
-
{
|
|
2439
|
-
content: "So that same error becomes:",
|
|
2440
|
-
pause: 2e3
|
|
2441
|
-
},
|
|
2442
|
-
RESOLVED_TRACE,
|
|
2443
|
-
{
|
|
2444
|
-
content: "Readable stack traces, straight from production.",
|
|
2445
|
-
pause: 5e3
|
|
2446
|
-
},
|
|
2447
|
-
{
|
|
2448
|
-
content: "You debug a live error like it happened on your own machine.",
|
|
2449
|
-
pause: 6e3
|
|
2450
|
-
},
|
|
2451
|
-
{
|
|
2452
|
-
type: "clear",
|
|
2453
|
-
pause: 1500
|
|
2454
|
-
},
|
|
2455
|
-
{
|
|
2456
|
-
content: "Zooming out — this is how Error Tracking works.",
|
|
2457
|
-
pause: 4e3
|
|
2458
|
-
},
|
|
2459
|
-
{
|
|
2460
|
-
content: "Every error your app throws is captured as an exception.",
|
|
2461
|
-
pause: 5e3
|
|
2462
|
-
},
|
|
2463
|
-
{
|
|
2464
|
-
content: "PostHog groups similar exceptions into a single issue:",
|
|
2465
|
-
pause: 2500
|
|
2466
|
-
},
|
|
2467
|
-
GROUPING,
|
|
2468
|
-
{
|
|
2469
|
-
content: "So a bug that fires ten thousand times is one issue to triage — not ten thousand alerts.",
|
|
2470
|
-
pause: 6500
|
|
2471
|
-
},
|
|
2472
|
-
{
|
|
2473
|
-
type: "clear",
|
|
2474
|
-
pause: 1500
|
|
2475
|
-
},
|
|
2476
|
-
{
|
|
2477
|
-
content: "And this is where source maps earn their keep again.",
|
|
2478
|
-
pause: 4500
|
|
2479
|
-
},
|
|
2480
|
-
{
|
|
2481
|
-
content: "Grouping reads the stack trace. Minified frames all look alike — so unrelated crashes get merged, and one real bug scatters across many issues.",
|
|
2482
|
-
pause: 8e3
|
|
2483
|
-
},
|
|
2484
|
-
{
|
|
2485
|
-
content: "With source maps, PostHog groups on your real frames — so each distinct bug lands as one clean issue.",
|
|
2486
|
-
pause: 7e3
|
|
2487
|
-
},
|
|
2488
|
-
{
|
|
2489
|
-
type: "clear",
|
|
2490
|
-
pause: 1500
|
|
2491
|
-
},
|
|
2492
|
-
{
|
|
2493
|
-
pause: 5e3,
|
|
2494
|
-
persist: true,
|
|
2495
|
-
content: /* @__PURE__ */ jsx(StatusPeekTrigger, { store })
|
|
2496
|
-
},
|
|
2497
|
-
{
|
|
2498
|
-
pause: 9e4,
|
|
2499
|
-
content: /* @__PURE__ */ jsxs(Text, { children: [
|
|
2500
|
-
"Press",
|
|
2501
|
-
" ",
|
|
2502
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2503
|
-
color: Colors.accent,
|
|
2504
|
-
bold: true,
|
|
2505
|
-
children: "S"
|
|
2506
|
-
}),
|
|
2507
|
-
" ",
|
|
2508
|
-
"to follow along — or sit tight, I'll let you know when it's done."
|
|
2509
|
-
] })
|
|
2510
|
-
}
|
|
2511
|
-
]);
|
|
2512
|
-
//#endregion
|
|
2513
|
-
//#region src/steps/install-cli-steering/index.ts
|
|
2514
|
-
const dirExists = (...segments) => {
|
|
2515
|
-
try {
|
|
2516
|
-
return fs$1.existsSync(path$2.join(os.homedir(), ...segments));
|
|
2517
|
-
} catch {
|
|
2518
|
-
return false;
|
|
2519
|
-
}
|
|
2520
|
-
};
|
|
2521
|
-
const CLI_STEERING_TARGETS = [
|
|
2522
|
-
{
|
|
2523
|
-
id: "claude-code",
|
|
2524
|
-
name: "Claude Code",
|
|
2525
|
-
instructionsPath: () => path$2.join(os.homedir(), ".claude", "CLAUDE.md"),
|
|
2526
|
-
isDetected: () => dirExists(".claude")
|
|
2527
|
-
},
|
|
2528
|
-
{
|
|
2529
|
-
id: "codex",
|
|
2530
|
-
name: "Codex",
|
|
2531
|
-
instructionsPath: () => path$2.join(os.homedir(), ".codex", "AGENTS.md"),
|
|
2532
|
-
isDetected: () => dirExists(".codex")
|
|
2533
|
-
},
|
|
2534
|
-
{
|
|
2535
|
-
id: "gemini",
|
|
2536
|
-
name: "Gemini CLI",
|
|
2537
|
-
instructionsPath: () => path$2.join(os.homedir(), ".gemini", "GEMINI.md"),
|
|
2538
|
-
isDetected: () => dirExists(".gemini")
|
|
2539
|
-
}
|
|
2540
|
-
];
|
|
2541
|
-
function findTarget(id) {
|
|
2542
|
-
return CLI_STEERING_TARGETS.find((target) => target.id === id);
|
|
2543
|
-
}
|
|
2544
|
-
function detectTargets() {
|
|
2545
|
-
return CLI_STEERING_TARGETS.filter((target) => target.isDetected());
|
|
2546
|
-
}
|
|
2547
|
-
const spawnOptions = {
|
|
2548
|
-
encoding: "utf-8",
|
|
2549
|
-
shell: process.platform === "win32"
|
|
2550
|
-
};
|
|
2551
|
-
/**
|
|
2552
|
-
* Install or update the PostHog CLI in the user's environment. `npm install
|
|
2553
|
-
* --global @posthog/cli@latest` covers both first-time installs and upgrades
|
|
2554
|
-
* for existing npm-installed CLIs.
|
|
2555
|
-
*/
|
|
2556
|
-
function installOrUpdatePostHogCli() {
|
|
2557
|
-
const args = [
|
|
2558
|
-
"install",
|
|
2559
|
-
"--global",
|
|
2560
|
-
"@posthog/cli@latest"
|
|
2561
|
-
];
|
|
2562
|
-
debug(`Running npm ${args.join(" ")}`);
|
|
2563
|
-
const result = spawnSync("npm", args, spawnOptions);
|
|
2564
|
-
if (result.error) return {
|
|
2565
|
-
success: false,
|
|
2566
|
-
error: `Failed to run npm: ${result.error.message}. Is Node.js installed?`,
|
|
2567
|
-
errorObject: result.error
|
|
2568
|
-
};
|
|
2569
|
-
if (result.status !== 0) {
|
|
2570
|
-
const message = (result.stderr || result.stdout || "").trim() || `npm install --global @posthog/cli@latest exited with status ${result.status ?? "unknown"}`;
|
|
2571
|
-
return {
|
|
2572
|
-
success: false,
|
|
2573
|
-
error: message,
|
|
2574
|
-
errorObject: new Error(message)
|
|
2575
|
-
};
|
|
2576
|
-
}
|
|
2577
|
-
return { success: true };
|
|
2578
|
-
}
|
|
2579
|
-
/**
|
|
2580
|
-
* Delegate the actual write to the installed `posthog-cli api agents-md
|
|
2581
|
-
* install`. The steering snippet lives in the CLI (its single source of truth),
|
|
2582
|
-
* so the command should run only after `installOrUpdatePostHogCli` refreshes
|
|
2583
|
-
* the CLI to the latest release.
|
|
2584
|
-
*/
|
|
2585
|
-
function installSteeringSnippet(filePath) {
|
|
2586
|
-
const args = [
|
|
2587
|
-
"api",
|
|
2588
|
-
"agents-md",
|
|
2589
|
-
"install",
|
|
2590
|
-
"--path",
|
|
2591
|
-
filePath
|
|
2592
|
-
];
|
|
2593
|
-
debug(`Running posthog-cli ${args.join(" ")}`);
|
|
2594
|
-
const result = spawnSync("posthog-cli", args, {
|
|
2595
|
-
...spawnOptions,
|
|
2596
|
-
env: {
|
|
2597
|
-
...process.env,
|
|
2598
|
-
POSTHOG_CLI_EXPERIMENTAL_API: "1"
|
|
2599
|
-
}
|
|
2600
|
-
});
|
|
2601
|
-
if (result.error) return {
|
|
2602
|
-
success: false,
|
|
2603
|
-
filePath,
|
|
2604
|
-
error: `Failed to run posthog-cli: ${result.error.message}. Make sure npm's global bin directory is on your PATH.`
|
|
2605
|
-
};
|
|
2606
|
-
if (result.status !== 0) return {
|
|
2607
|
-
success: false,
|
|
2608
|
-
filePath,
|
|
2609
|
-
error: (result.stderr || result.stdout || "").trim() || `posthog-cli exited with status ${result.status ?? "unknown"}`
|
|
2610
|
-
};
|
|
2611
|
-
return {
|
|
2612
|
-
success: true,
|
|
2613
|
-
filePath
|
|
2614
|
-
};
|
|
2615
|
-
}
|
|
2616
|
-
//#endregion
|
|
2617
|
-
//#region src/lib/programs/error-tracking-upload-source-maps/index.ts
|
|
2618
|
-
const REPORT_FILE$1 = "posthog-source-maps-report.md";
|
|
2619
|
-
const DOCS_URL$1 = "https://posthog.com/docs/error-tracking/upload-source-maps";
|
|
2620
|
-
let postHogCliInstallAttempted = false;
|
|
2621
|
-
/**
|
|
2622
|
-
* Pre-install posthog-cli for variants that need a machine-global copy
|
|
2623
|
-
* (`VARIANTS_REQUIRING_POSTHOG_CLI`). The agent can't — warlock blocks
|
|
2624
|
-
* `npm install -g` — so the wizard does it in-process. Warn, don't fail.
|
|
2625
|
-
*/
|
|
2626
|
-
function ensurePostHogCli(variant) {
|
|
2627
|
-
if (postHogCliInstallAttempted) return;
|
|
2628
|
-
postHogCliInstallAttempted = true;
|
|
2629
|
-
const result = installOrUpdatePostHogCli();
|
|
2630
|
-
if (!result.success) {
|
|
2631
|
-
analytics.wizardCapture("source maps posthog-cli preinstall failed", {
|
|
2632
|
-
variant,
|
|
2633
|
-
error: String(result.error).slice(0, 500)
|
|
2634
|
-
});
|
|
2635
|
-
analytics.captureException(result.errorObject ?? /* @__PURE__ */ new Error(`posthog-cli pre-install failed: ${result.error}`), {
|
|
2636
|
-
source: "source_maps_cli_preinstall",
|
|
2637
|
-
variant
|
|
2638
|
-
});
|
|
2639
|
-
getUI().log.warn(`Could not pre-install posthog-cli (${result.error}). Your release build will fail to upload debug symbols until it's installed: npm install -g @posthog/cli@latest`);
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
const errorTrackingUploadSourceMapsConfig = {
|
|
2643
|
-
command: "upload-source-maps",
|
|
2644
|
-
description: "Upload source maps to PostHog Error Tracking",
|
|
2645
|
-
id: "error-tracking-upload-source-maps",
|
|
2646
|
-
requiresAi: true,
|
|
2647
|
-
steps: ERROR_TRACKING_UPLOAD_SOURCE_MAPS_PROGRAM,
|
|
2648
|
-
reportFile: REPORT_FILE$1,
|
|
2649
|
-
getContentBlocks: getContentBlocks$1,
|
|
2650
|
-
requires: ["posthog-integration"],
|
|
2651
|
-
run: (_session) => {
|
|
2652
|
-
const readSelection = () => {
|
|
2653
|
-
const variant = getUI().getFrameworkContext(SOURCE_MAPS_CONTEXT_KEYS.selectedVariant);
|
|
2654
|
-
return {
|
|
2655
|
-
variant,
|
|
2656
|
-
displayName: getUI().getFrameworkContext(SOURCE_MAPS_CONTEXT_KEYS.selectedDisplayName),
|
|
2657
|
-
projectPath: getUI().getFrameworkContext(SOURCE_MAPS_CONTEXT_KEYS.selectedPath),
|
|
2658
|
-
skillId: variant ? `error-tracking-upload-source-maps-${variant}` : void 0
|
|
2659
|
-
};
|
|
2660
|
-
};
|
|
2661
|
-
return Promise.resolve({
|
|
2662
|
-
integrationLabel: "error-tracking-upload-source-maps",
|
|
2663
|
-
successMessage: "Source maps wired up!",
|
|
2664
|
-
reportFile: REPORT_FILE$1,
|
|
2665
|
-
docsUrl: DOCS_URL$1,
|
|
2666
|
-
spinnerMessage: "Wiring up source maps...",
|
|
2667
|
-
estimatedDurationMinutes: 3,
|
|
2668
|
-
abortCases: SOURCE_MAPS_ABORT_CASES,
|
|
2669
|
-
askTimeoutMs: 1800 * 1e3,
|
|
2670
|
-
customPrompt: (ctx) => {
|
|
2671
|
-
const { variant, displayName, projectPath, skillId } = readSelection();
|
|
2672
|
-
if (!skillId || !variant) return SOURCE_MAPS_DETECTION_FAILED_PROMPT;
|
|
2673
|
-
if (VARIANTS_REQUIRING_POSTHOG_CLI.has(variant)) ensurePostHogCli(variant);
|
|
2674
|
-
const uiHost = ctx.host.appHost.replace(/\/$/, "");
|
|
2675
|
-
return buildSourceMapsUploadPrompt({
|
|
2676
|
-
displayName,
|
|
2677
|
-
variant,
|
|
2678
|
-
skillId,
|
|
2679
|
-
projectPath,
|
|
2680
|
-
projectId: ctx.projectId,
|
|
2681
|
-
host: ctx.host.apiHost,
|
|
2682
|
-
settingsUrl: `${uiHost}/project/${ctx.projectId}/settings/user-api-keys`,
|
|
2683
|
-
uiHost,
|
|
2684
|
-
reportFile: REPORT_FILE$1
|
|
2685
|
-
});
|
|
2686
|
-
},
|
|
2687
|
-
postRun: () => {
|
|
2688
|
-
const { variant } = readSelection();
|
|
2689
|
-
if (variant) getUI().setFrameworkContext("sourceMapsCompletedVariant", variant);
|
|
2690
|
-
return Promise.resolve();
|
|
2691
|
-
},
|
|
2692
|
-
buildOutroData: () => {
|
|
2693
|
-
return {
|
|
2694
|
-
kind: "success",
|
|
2695
|
-
message: "Source maps wired up!",
|
|
2696
|
-
reportFile: REPORT_FILE$1,
|
|
2697
|
-
docsUrl: DOCS_URL$1
|
|
2698
|
-
};
|
|
2699
|
-
}
|
|
2700
|
-
});
|
|
2701
|
-
}
|
|
2702
|
-
};
|
|
2703
|
-
//#endregion
|
|
2704
|
-
//#region src/lib/programs/self-driving/detect.ts
|
|
2705
|
-
/**
|
|
2706
|
-
* Self-driving prerequisite detection + abort vocabulary.
|
|
2707
|
-
*
|
|
2708
|
-
* The only thing worth verifying before auth is local and cheap: that
|
|
2709
|
-
* `session.installDir` is a real, readable directory. We deliberately do
|
|
2710
|
-
* NOT require the base posthog-integration report to be present — it is a
|
|
2711
|
-
* report many users never commit, and `requires: ['posthog-integration']`
|
|
2712
|
-
* is metadata, not a hard runtime gate.
|
|
2713
|
-
*
|
|
2714
|
-
* Self-driving is now in OPEN beta — available to every team — so STEP 1
|
|
2715
|
-
* no longer probes the Signals API as an access gate; it completes
|
|
2716
|
-
* instantly so the run opens with a fast first checkmark. The
|
|
2717
|
-
* `self-driving is not available for this project` abort below is kept
|
|
2718
|
-
* only as a safety net: if the Signals API genuinely can't be reached
|
|
2719
|
-
* during the run (a hard error that is unexpected in open beta), the skill
|
|
2720
|
-
* emits it and the wizard renders a friendly "try again" screen — now with
|
|
2721
|
-
* open-beta wording, not the old closed, per-team "join the beta" copy. The
|
|
2722
|
-
* PostHog-side flags (`product-autonomy`, `signals-scout`) are unchanged by
|
|
2723
|
-
* the wizard-side "self-driving" rename.
|
|
2724
|
-
*/
|
|
2725
|
-
/** frameworkContext key holding the deterministic PostHog-presence result. */
|
|
2726
|
-
const POSTHOG_PRESENT_KEY = "postHogPresent";
|
|
2727
|
-
/**
|
|
2728
|
-
* frameworkContext key holding the repo-relative path of the project the user
|
|
2729
|
-
* picked on the integration-detect screen ("." for the repo root). The
|
|
2730
|
-
* integrate-run phase scopes its install dir to this so a monorepo integrates
|
|
2731
|
-
* into the chosen sub-app, not the root.
|
|
2732
|
-
*/
|
|
2733
|
-
const SELF_DRIVING_INTEGRATE_PATH_KEY = "selfDrivingIntegratePath";
|
|
2734
|
-
/** Self-driving's own detected-tools key — not the warehouse one, which the integration program reads. */
|
|
2735
|
-
const SELF_DRIVING_DETECTED_TOOLS_KEY = "selfDrivingDetectedTools";
|
|
2736
|
-
/** Read the detected tools out of frameworkContext. */
|
|
2737
|
-
function getSelfDrivingDetectedTools(session) {
|
|
2738
|
-
return session.frameworkContext["selfDrivingDetectedTools"] ?? [];
|
|
2739
|
-
}
|
|
2740
|
-
const POSTHOG_PACKAGE_RE = /(^|["'\s/=:.@])posthog/im;
|
|
2741
|
-
const POSTHOG_MANIFESTS = [
|
|
2742
|
-
"package.json",
|
|
2743
|
-
"requirements.txt",
|
|
2744
|
-
"pyproject.toml",
|
|
2745
|
-
"Pipfile",
|
|
2746
|
-
"setup.py",
|
|
2747
|
-
"Gemfile",
|
|
2748
|
-
"go.mod",
|
|
2749
|
-
"composer.json",
|
|
2750
|
-
"pubspec.yaml",
|
|
2751
|
-
"Package.swift",
|
|
2752
|
-
"Podfile",
|
|
2753
|
-
"Podfile.lock",
|
|
2754
|
-
"project.yml",
|
|
2755
|
-
"build.gradle",
|
|
2756
|
-
"build.gradle.kts",
|
|
2757
|
-
"gradle/libs.versions.toml",
|
|
2758
|
-
"pom.xml",
|
|
2759
|
-
"mix.exs",
|
|
2760
|
-
"Cargo.toml"
|
|
2761
|
-
];
|
|
2762
|
-
const POSTHOG_DIRS = [
|
|
2763
|
-
".",
|
|
2764
|
-
"app",
|
|
2765
|
-
"frontend",
|
|
2766
|
-
"backend",
|
|
2767
|
-
"ios",
|
|
2768
|
-
"android",
|
|
2769
|
-
"android/app"
|
|
2770
|
-
];
|
|
2771
|
-
const WALK_SKIP_DIRS = new Set([
|
|
2772
|
-
"node_modules",
|
|
2773
|
-
"Pods",
|
|
2774
|
-
"Carthage",
|
|
2775
|
-
"DerivedData",
|
|
2776
|
-
".build",
|
|
2777
|
-
"build",
|
|
2778
|
-
"dist",
|
|
2779
|
-
"out",
|
|
2780
|
-
".next",
|
|
2781
|
-
"coverage",
|
|
2782
|
-
"vendor",
|
|
2783
|
-
".venv",
|
|
2784
|
-
"site-packages",
|
|
2785
|
-
"target",
|
|
2786
|
-
".git"
|
|
2787
|
-
]);
|
|
2788
|
-
const WALK_MAX_DEPTH = 2;
|
|
2789
|
-
/**
|
|
2790
|
-
* The install dir plus every directory within WALK_MAX_DEPTH levels (skipping
|
|
2791
|
-
* heavy/vendored trees and dotdirs), unioned with the explicit POSTHOG_DIRS. A
|
|
2792
|
-
* full shallow walk on top of the named dirs, so a monorepo's nested apps
|
|
2793
|
-
* (e.g. `apps/mobile`) are seen without hardcoding the path.
|
|
2794
|
-
*/
|
|
2795
|
-
function scanDirs(installDir) {
|
|
2796
|
-
const rels = new Set(POSTHOG_DIRS);
|
|
2797
|
-
const stack = [{
|
|
2798
|
-
rel: ".",
|
|
2799
|
-
depth: 0
|
|
2800
|
-
}];
|
|
2801
|
-
while (stack.length > 0) {
|
|
2802
|
-
const { rel, depth } = stack.pop();
|
|
2803
|
-
rels.add(rel);
|
|
2804
|
-
if (depth >= WALK_MAX_DEPTH) continue;
|
|
2805
|
-
let entries;
|
|
2806
|
-
try {
|
|
2807
|
-
entries = readdirSync(join(installDir, rel), { withFileTypes: true });
|
|
2808
|
-
} catch {
|
|
2809
|
-
continue;
|
|
2810
|
-
}
|
|
2811
|
-
for (const e of entries) {
|
|
2812
|
-
if (!e.isDirectory()) continue;
|
|
2813
|
-
if (e.name.startsWith(".") || WALK_SKIP_DIRS.has(e.name)) continue;
|
|
2814
|
-
if (e.name.endsWith(".xcodeproj") || e.name.endsWith(".xcworkspace")) continue;
|
|
2815
|
-
stack.push({
|
|
2816
|
-
rel: rel === "." ? e.name : `${rel}/${e.name}`,
|
|
2817
|
-
depth: depth + 1
|
|
2818
|
-
});
|
|
2819
|
-
}
|
|
2820
|
-
}
|
|
2821
|
-
return [...rels];
|
|
2822
|
-
}
|
|
2823
|
-
/**
|
|
2824
|
-
* True if a `*.xcodeproj/project.pbxproj` or `*.csproj`/`*.fsproj` in
|
|
2825
|
-
* `dirPath` references PostHog.
|
|
2826
|
-
*/
|
|
2827
|
-
function projectFileHasPostHog(dirPath) {
|
|
2828
|
-
let entries;
|
|
2829
|
-
try {
|
|
2830
|
-
entries = readdirSync(dirPath);
|
|
2831
|
-
} catch {
|
|
2832
|
-
return false;
|
|
2833
|
-
}
|
|
2834
|
-
for (const entry of entries) {
|
|
2835
|
-
const target = entry.endsWith(".xcodeproj") ? join(dirPath, entry, "project.pbxproj") : entry.endsWith(".csproj") || entry.endsWith(".fsproj") ? join(dirPath, entry) : null;
|
|
2836
|
-
if (!target || !existsSync(target)) continue;
|
|
2837
|
-
try {
|
|
2838
|
-
if (POSTHOG_PACKAGE_RE.test(readFileSync(target, "utf8"))) return true;
|
|
2839
|
-
} catch {}
|
|
2840
|
-
}
|
|
2841
|
-
return false;
|
|
2842
|
-
}
|
|
2843
|
-
/**
|
|
2844
|
-
* Deterministic, offline check: does the project already have a PostHog SDK?
|
|
2845
|
-
* Scans the common dependency manifests at the install dir for a `posthog`
|
|
2846
|
-
* package — the same signal the agentic detector reports as `hasPostHog`, but
|
|
2847
|
-
* instant and credential-free. Drives whether self-driving asks to integrate
|
|
2848
|
-
* first ("not found") or proceeds straight to setup ("found").
|
|
2849
|
-
*/
|
|
2850
|
-
function detectPostHogPresent(installDir) {
|
|
2851
|
-
for (const dir of scanDirs(installDir)) {
|
|
2852
|
-
const dirPath = join(installDir, dir);
|
|
2853
|
-
for (const name of POSTHOG_MANIFESTS) {
|
|
2854
|
-
const path = join(dirPath, name);
|
|
2855
|
-
if (!existsSync(path)) continue;
|
|
2856
|
-
try {
|
|
2857
|
-
if (POSTHOG_PACKAGE_RE.test(readFileSync(path, "utf8"))) return true;
|
|
2858
|
-
} catch {}
|
|
2859
|
-
}
|
|
2860
|
-
if (projectFileHasPostHog(dirPath)) return true;
|
|
2861
|
-
}
|
|
2862
|
-
return false;
|
|
2863
|
-
}
|
|
2864
|
-
/**
|
|
2865
|
-
* `[ABORT] <reason>` cases the self-driving skill can emit. The
|
|
2866
|
-
* reason strings are part of the skill contract — the context-mill
|
|
2867
|
-
* `self-driving-setup` skill emits these exact strings.
|
|
2868
|
-
*/
|
|
2869
|
-
/** Headline shown when the run ends because GitHub was not connected. */
|
|
2870
|
-
const GITHUB_REQUIRED_MESSAGE = "GitHub connection required";
|
|
2871
|
-
/** Body for {@link GITHUB_REQUIRED_MESSAGE}. Shared by the pre-run gate and the abort case. */
|
|
2872
|
-
const GITHUB_REQUIRED_BODY = "Self-driving needs GitHub access to research issues in your code and open fixes, so setup cannot finish without it. Nothing was changed. When you are ready to install the PostHog GitHub App, run the wizard again.";
|
|
2873
|
-
const SELF_DRIVING_ABORT_CASES = [
|
|
2874
|
-
{
|
|
2875
|
-
match: /^self-driving is not available for this project$/i,
|
|
2876
|
-
message: "PostHog Self-driving is not available for this project",
|
|
2877
|
-
body: "Self-driving is in open beta and available to every team, so this is unexpected — the PostHog Signals API could not be reached for this project. Nothing was changed. Try again in a moment, and if it keeps happening reach out to wizard@posthog.com."
|
|
2878
|
-
},
|
|
2879
|
-
{
|
|
2880
|
-
match: /^github connection declined$/i,
|
|
2881
|
-
message: GITHUB_REQUIRED_MESSAGE,
|
|
2882
|
-
body: GITHUB_REQUIRED_BODY
|
|
2883
|
-
},
|
|
2884
|
-
{
|
|
2885
|
-
match: /^requires-interactive-mode$/i,
|
|
2886
|
-
errorCode: ErrorCodes.CliInteractiveRequired,
|
|
2887
|
-
message: "Interactive terminal required",
|
|
2888
|
-
body: "Self-driving setup asks questions along the way (GitHub and issue trackers), so it needs an interactive terminal. Run the wizard outside CI / non-interactive mode."
|
|
2889
|
-
},
|
|
2890
|
-
{
|
|
2891
|
-
match: /^requirements-incomplete$/i,
|
|
2892
|
-
message: "Setup needs your input",
|
|
2893
|
-
body: "The wizard could not collect the answers this setup needs (the environment was non-interactive, or the question budget ran out). Nothing was left half-configured. Run the wizard again in an interactive terminal."
|
|
2894
|
-
}
|
|
2895
|
-
];
|
|
2896
|
-
/**
|
|
2897
|
-
* Verify `session.installDir` is a readable directory. Writes a
|
|
2898
|
-
* `SelfDrivingDetectError` to frameworkContext on failure — the intro
|
|
2899
|
-
* screen renders it and blocks.
|
|
2900
|
-
*/
|
|
2901
|
-
function detectSelfDrivingPrerequisites(session, setFrameworkContext) {
|
|
2902
|
-
const fail = (error) => setFrameworkContext("detectError", error);
|
|
2903
|
-
const installDir = session.installDir;
|
|
2904
|
-
if (!existsSync(installDir)) {
|
|
2905
|
-
fail({
|
|
2906
|
-
kind: "bad-directory",
|
|
2907
|
-
path: installDir,
|
|
2908
|
-
reason: "missing"
|
|
2909
|
-
});
|
|
2910
|
-
return;
|
|
2911
|
-
}
|
|
2912
|
-
try {
|
|
2913
|
-
if (!statSync(installDir).isDirectory()) {
|
|
2914
|
-
fail({
|
|
2915
|
-
kind: "bad-directory",
|
|
2916
|
-
path: installDir,
|
|
2917
|
-
reason: "not-dir"
|
|
2918
|
-
});
|
|
2919
|
-
return;
|
|
2920
|
-
}
|
|
2921
|
-
} catch {
|
|
2922
|
-
fail({
|
|
2923
|
-
kind: "bad-directory",
|
|
2924
|
-
path: installDir,
|
|
2925
|
-
reason: "unreadable"
|
|
2926
|
-
});
|
|
2927
|
-
return;
|
|
2928
|
-
}
|
|
2929
|
-
setFrameworkContext(POSTHOG_PRESENT_KEY, detectPostHogPresent(installDir));
|
|
2930
|
-
detectConnectedTools(installDir, setFrameworkContext);
|
|
2931
|
-
}
|
|
2932
|
-
/** Step 5's ask options intersected with `SOURCE_DETECTORS` — reconcile when the skill's catalog grows. */
|
|
2933
|
-
const SELF_DRIVING_TOOL_KINDS = new Set([
|
|
2934
|
-
"Github",
|
|
2935
|
-
"GitLab",
|
|
2936
|
-
"Gitea",
|
|
2937
|
-
"Linear",
|
|
2938
|
-
"Jira",
|
|
2939
|
-
"Shortcut",
|
|
2940
|
-
"Sentry",
|
|
2941
|
-
"Rollbar",
|
|
2942
|
-
"Bugsnag",
|
|
2943
|
-
"Honeybadger",
|
|
2944
|
-
"Raygun",
|
|
2945
|
-
"Zendesk",
|
|
2946
|
-
"Freshdesk",
|
|
2947
|
-
"Front",
|
|
2948
|
-
"Gorgias",
|
|
2949
|
-
"Kustomer",
|
|
2950
|
-
"Plain",
|
|
2951
|
-
"Snyk",
|
|
2952
|
-
"Canny",
|
|
2953
|
-
"Productboard",
|
|
2954
|
-
"GoogleSearchConsole"
|
|
2955
|
-
]);
|
|
2956
|
-
/** Scan for inbox-connectable tools so STEP 5 can surface them first. Best-effort — never blocks detection. */
|
|
2957
|
-
function detectConnectedTools(installDir, setFrameworkContext) {
|
|
2958
|
-
try {
|
|
2959
|
-
const tools = detectWarehouseSources(installDir).filter((s) => SELF_DRIVING_TOOL_KINDS.has(s.kind));
|
|
2960
|
-
analytics.setTag("connected_tools_detected_count", tools.length);
|
|
2961
|
-
if (tools.length === 0) return;
|
|
2962
|
-
analytics.setTag("connected_tools_detected", tools.map((s) => s.kind).join(","));
|
|
2963
|
-
setFrameworkContext(SELF_DRIVING_DETECTED_TOOLS_KEY, tools);
|
|
2964
|
-
} catch (error) {
|
|
2965
|
-
analytics.setTag("connected_tools_detected_count", -1);
|
|
2966
|
-
analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "detectConnectedTools" });
|
|
2967
|
-
}
|
|
2968
|
-
}
|
|
2969
|
-
//#endregion
|
|
2970
|
-
//#region src/lib/programs/self-driving/detect-agentic.ts
|
|
2971
|
-
const INTEGRATION_IDS = new Set(Object.values(Integration));
|
|
2972
|
-
function classify(integration, hasPostHog) {
|
|
2973
|
-
if (integration == null) return {
|
|
2974
|
-
instrumentable: false,
|
|
2975
|
-
reason: "Not a framework the wizard can set up yet"
|
|
2976
|
-
};
|
|
2977
|
-
if (hasPostHog) return {
|
|
2978
|
-
instrumentable: false,
|
|
2979
|
-
reason: "Already has the PostHog SDK"
|
|
2980
|
-
};
|
|
2981
|
-
return { instrumentable: true };
|
|
2982
|
-
}
|
|
2983
|
-
/** Map a detection report into classified projects (exported for tests). */
|
|
2984
|
-
function toIntegrationReport(report) {
|
|
2985
|
-
return {
|
|
2986
|
-
repoType: report.repoType,
|
|
2987
|
-
projects: report.projects.map((p) => {
|
|
2988
|
-
const integration = p.targetId && INTEGRATION_IDS.has(p.targetId) ? p.targetId : null;
|
|
2989
|
-
return {
|
|
2990
|
-
path: p.path,
|
|
2991
|
-
framework: p.framework,
|
|
2992
|
-
integration,
|
|
2993
|
-
hasPostHog: p.hasPostHog,
|
|
2994
|
-
continuable: p.hasPostHog,
|
|
2995
|
-
...classify(integration, p.hasPostHog)
|
|
2996
|
-
};
|
|
2997
|
-
})
|
|
2998
|
-
};
|
|
2999
|
-
}
|
|
3000
|
-
/** Run the Haiku detector over the repo and classify projects for integration. */
|
|
3001
|
-
async function detectSelfDrivingIntegrationProjects(session, onEvent) {
|
|
3002
|
-
return toIntegrationReport(await detectIntegrationProjects(session, {
|
|
3003
|
-
programId: "self-driving",
|
|
3004
|
-
onEvent
|
|
3005
|
-
}));
|
|
3006
|
-
}
|
|
3007
|
-
/**
|
|
3008
|
-
* Prepare the integration phase after the user picked a project on the
|
|
3009
|
-
* integration-detect screen (which set `session.integration`, `frameworkConfig`,
|
|
3010
|
-
* and the chosen path). Scopes `installDir` to the chosen project — so a
|
|
3011
|
-
* monorepo integrates into the picked sub-app — and gathers that project's
|
|
3012
|
-
* framework context, mirroring posthog-integration's `ciPreRun`. Used as the
|
|
3013
|
-
* integrate-run step's `onRunPrep`.
|
|
3014
|
-
*/
|
|
3015
|
-
async function prepSelfDrivingIntegration(session) {
|
|
3016
|
-
const frameworkConfig = session.frameworkConfig;
|
|
3017
|
-
if (!frameworkConfig) return;
|
|
3018
|
-
const context = await gatherFrameworkContext(frameworkConfig, {
|
|
3019
|
-
installDir: session.installDir,
|
|
3020
|
-
debug: session.debug,
|
|
3021
|
-
signup: session.signup,
|
|
3022
|
-
ci: session.ci,
|
|
3023
|
-
benchmark: session.benchmark,
|
|
3024
|
-
yaraReport: session.yaraReport
|
|
3025
|
-
});
|
|
3026
|
-
for (const [key, value] of Object.entries(context)) if (!(key in session.frameworkContext)) session.frameworkContext[key] = value;
|
|
3027
|
-
}
|
|
3028
|
-
//#endregion
|
|
3029
|
-
//#region src/lib/programs/self-driving/steps.ts
|
|
3030
|
-
/** True once detection found PostHog already present in the project. */
|
|
3031
|
-
const postHogPresent = (session) => session.frameworkContext[POSTHOG_PRESENT_KEY] === true;
|
|
3032
|
-
/** Absolute dir to integrate into: the picked sub-app (LLM output — the shared resolver clamps escapes), else the repo root. */
|
|
3033
|
-
const integrationDir = (session) => resolveProjectDir(session.installDir, session.frameworkContext[SELF_DRIVING_INTEGRATE_PATH_KEY]);
|
|
3034
|
-
const SELF_DRIVING_PROGRAM = [
|
|
3035
|
-
{
|
|
3036
|
-
id: "detect",
|
|
3037
|
-
label: "Detecting prerequisites",
|
|
3038
|
-
onReady: (ctx) => detectSelfDrivingPrerequisites(ctx.session, ctx.setFrameworkContext)
|
|
3039
|
-
},
|
|
3040
|
-
{
|
|
3041
|
-
id: "intro",
|
|
3042
|
-
label: "Welcome",
|
|
3043
|
-
screenId: "self-driving-intro",
|
|
3044
|
-
gate: (session) => session.setupConfirmed
|
|
3045
|
-
},
|
|
3046
|
-
{
|
|
3047
|
-
id: "integration-check",
|
|
3048
|
-
label: "Integration",
|
|
3049
|
-
screenId: "self-driving-integration-check",
|
|
3050
|
-
show: (session) => !postHogPresent(session) && session.integrate === null,
|
|
3051
|
-
isComplete: (session) => postHogPresent(session) || session.integrate !== null,
|
|
3052
|
-
gate: (session) => postHogPresent(session) || session.integrate !== null
|
|
3053
|
-
},
|
|
3054
|
-
HEALTH_CHECK_STEP,
|
|
3055
|
-
{
|
|
3056
|
-
id: "auth",
|
|
3057
|
-
label: "Authentication",
|
|
3058
|
-
screenId: "auth",
|
|
3059
|
-
isComplete: (session) => session.credentials !== null
|
|
3060
|
-
},
|
|
3061
|
-
{
|
|
3062
|
-
id: "integrate-detect",
|
|
3063
|
-
label: "Detecting",
|
|
3064
|
-
screenId: "self-driving-integration-detect",
|
|
3065
|
-
show: (session) => session.integrate === true && session.integration == null,
|
|
3066
|
-
isComplete: (session) => session.integration != null || session.integrate === false
|
|
3067
|
-
},
|
|
3068
|
-
{
|
|
3069
|
-
...integrationRunStep,
|
|
3070
|
-
id: "integrate-run",
|
|
3071
|
-
onRunPrep: prepSelfDrivingIntegration,
|
|
3072
|
-
targetDir: integrationDir,
|
|
3073
|
-
show: (session) => session.integrate === true,
|
|
3074
|
-
isComplete: (session) => session.completedRuns.includes("integrate-run")
|
|
3075
|
-
},
|
|
3076
|
-
{
|
|
3077
|
-
id: "self-driving-handoff",
|
|
3078
|
-
label: "Ready",
|
|
3079
|
-
screenId: "self-driving-handoff",
|
|
3080
|
-
show: (session) => session.integrate === true,
|
|
3081
|
-
isComplete: (session) => session.selfDrivingHandoffConfirmed
|
|
3082
|
-
},
|
|
3083
|
-
{
|
|
3084
|
-
id: "self-driving-github",
|
|
3085
|
-
label: "GitHub",
|
|
3086
|
-
screenId: "self-driving-github",
|
|
3087
|
-
isComplete: (session) => session.githubConnected === true || session.githubDeclined,
|
|
3088
|
-
gate: (session) => session.githubConnected === true || session.githubDeclined
|
|
3089
|
-
},
|
|
3090
|
-
{
|
|
3091
|
-
id: "run",
|
|
3092
|
-
label: "Self-driving",
|
|
3093
|
-
screenId: "run",
|
|
3094
|
-
show: (session) => !session.githubDeclined,
|
|
3095
|
-
isComplete: (session) => session.runPhase === "completed" || session.runPhase === "error"
|
|
3096
|
-
},
|
|
3097
|
-
{
|
|
3098
|
-
id: "outro",
|
|
3099
|
-
label: "Done",
|
|
3100
|
-
screenId: "outro",
|
|
3101
|
-
isComplete: (session) => session.outroDismissed
|
|
3102
|
-
}
|
|
3103
|
-
];
|
|
3104
|
-
//#endregion
|
|
3105
|
-
//#region src/lib/programs/self-driving/prompt.ts
|
|
3106
|
-
/**
|
|
3107
|
-
* Render the deterministic codebase-tool scan for the prompt. STEP 4 and
|
|
3108
|
-
* STEP 5 read this instead of the agent doing its own flaky "light scan":
|
|
3109
|
-
* enable the matching signal sources, and surface detected tools first in the
|
|
3110
|
-
* connected-tools ask rather than dumping the full ~500-source catalog. When
|
|
3111
|
-
* nothing is detected we still say so explicitly, so the agent falls back to
|
|
3112
|
-
* the skill's default ordering instead of inventing a scan.
|
|
3113
|
-
*/
|
|
3114
|
-
function renderDetectedTools(sources) {
|
|
3115
|
-
if (sources.length === 0) return `Tools detected in this codebase: none found by the dependency + env scan. Use the skill's default ordering for STEP 5.`;
|
|
3116
|
-
return [
|
|
3117
|
-
"Tools detected in this codebase (deterministic dependency + env-key scan — this is evidence, not a guess):",
|
|
3118
|
-
...sources.map((s) => `- ${s.label} (source_type: ${s.kind}) — ${s.matchedSignal}`),
|
|
3119
|
-
"",
|
|
3120
|
-
"Use this list in STEP 4 (enable the matching signal sources) and STEP 5 (surface these detected tools first in the connected-tools ask, ahead of the SaaS basics and the \"others\" option), exactly as the skill describes. Never enable or pre-select a tool the user has not confirmed."
|
|
3121
|
-
].join("\n");
|
|
3122
|
-
}
|
|
3123
|
-
/**
|
|
3124
|
-
* Build the self-driving run prompt. The installed
|
|
3125
|
-
* `self-driving-setup` skill is the source of truth for the HOW of
|
|
3126
|
-
* every step (which MCP tools to call, which sources/scouts apply, how
|
|
3127
|
-
* to verify); this prompt carries the order, the wizard-specific
|
|
3128
|
-
* mechanics (wizard_ask, abort signals), and the project URLs.
|
|
3129
|
-
*
|
|
3130
|
-
* `detectedSources` is the deterministic codebase scan (from the detect step);
|
|
3131
|
-
* it drives STEP 4/STEP 5 tool prioritisation. Empty is fine — the block then
|
|
3132
|
-
* tells the agent to use the skill's default ordering.
|
|
3133
|
-
*
|
|
3134
|
-
* Integration (when the project has no PostHog yet) runs as a separate phase
|
|
3135
|
-
* before this — the real integration program, with its own screens and task
|
|
3136
|
-
* list — so this prompt only covers the Self-driving steps.
|
|
3137
|
-
*/
|
|
3138
|
-
function buildSelfDrivingPrompt(ctx, detectedSources = []) {
|
|
3139
|
-
const uiHost = ctx.host.appHost.replace(/\/$/, "");
|
|
3140
|
-
const projectBase = `${uiHost}/project/${ctx.projectId}`;
|
|
3141
|
-
const integrationsSettingsUrl = `${projectBase}/settings/environment-integrations`;
|
|
3142
|
-
const orgAiSettingsUrl = `${uiHost}/settings/organization#organization-ai-consent`;
|
|
3143
|
-
const newWarehouseSourceUrl = `${projectBase}/pipeline/new/source`;
|
|
3144
|
-
const inboxUrl = `${projectBase}/inbox`;
|
|
3145
|
-
const optIn = (value) => value === true ? "ON" : value === false ? "OFF" : "unknown";
|
|
3146
|
-
const optIns = ctx.teamProductOptIns;
|
|
3147
|
-
return `You are setting up PostHog Self-driving for this project: you will enable the right signal sources, tune the scout troop, design custom scouts for what this product uniquely needs, put Replay Vision scanners on its key flows, and hand the user a configured inbox.
|
|
3148
|
-
|
|
3149
|
-
Project URLs:
|
|
3150
|
-
- Integrations settings: ${integrationsSettingsUrl}
|
|
3151
|
-
- Organization AI settings: ${orgAiSettingsUrl}
|
|
3152
|
-
- New data warehouse source (Linear / Zendesk / GitHub issues / pganalyze): ${newWarehouseSourceUrl}
|
|
3153
|
-
- Self-driving inbox: ${inboxUrl}
|
|
3154
|
-
|
|
3155
|
-
Project state read at auth time (PostHog project settings — authoritative
|
|
3156
|
-
for whether a product is enabled, regardless of what this repo
|
|
3157
|
-
instruments; products are often instrumented from other repos or the
|
|
3158
|
-
snippet, so repo evidence may rule a product IN but never OUT):
|
|
3159
|
-
- Session replay recording: ${optIn(optIns?.sessionReplay)}
|
|
3160
|
-
- Exception autocapture (error tracking): ${optIn(optIns?.exceptionAutocapture)}
|
|
3161
|
-
- Surveys: ${optIn(optIns?.surveys)}
|
|
3162
|
-
|
|
3163
|
-
${renderDetectedTools(detectedSources)}
|
|
3164
|
-
|
|
3165
|
-
The installed skill is the source of truth for the HOW of every step:
|
|
3166
|
-
which MCP tools to call, which sources and scouts apply to this product,
|
|
3167
|
-
and how to verify each change. The STEPS below give the order and the
|
|
3168
|
-
wizard-specific mechanics — read the matching skill reference before
|
|
3169
|
-
doing the work, and do not invent steps the skill doesn't describe.
|
|
3170
|
-
|
|
3171
|
-
Before doing any work, create your FULL task list in a single TaskCreate
|
|
3172
|
-
call so the user can follow your progress in the TUI. Use exactly these
|
|
3173
|
-
tasks, in this order:
|
|
3174
|
-
1. Check Self-driving access
|
|
3175
|
-
2. Read project and current Self-driving state
|
|
3176
|
-
3. Enable products (replay, error tracking, support)
|
|
3177
|
-
4. Enable signal sources
|
|
3178
|
-
5. Offer issue-tracker integrations
|
|
3179
|
-
6. Configure the scout troop
|
|
3180
|
-
6b. Design custom scouts
|
|
3181
|
-
6c. Set up Replay Vision scanners
|
|
3182
|
-
7. Write report and hand off
|
|
3183
|
-
Drive the list with TaskUpdate — mark a task in_progress when you start
|
|
3184
|
-
it and completed when done. If a step turns out to be a no-op (e.g. a
|
|
3185
|
-
product is already enabled), still mark its task completed.
|
|
3186
|
-
|
|
3187
|
-
The PostHog GitHub App is already connected — the wizard verified it
|
|
3188
|
-
before this run started, so never ask the user to connect or install it.
|
|
3189
|
-
|
|
3190
|
-
Wizard mechanics:
|
|
3191
|
-
- Ask the user things ONLY with the wizard_ask MCP tool, and batch
|
|
3192
|
-
related questions (e.g. one multi-select for all issue trackers, not
|
|
3193
|
-
one ask per tool). The per-run ask budget is limited.
|
|
3194
|
-
- If wizard_ask is unavailable (CI / non-interactive), emit
|
|
3195
|
-
${AgentSignals.ABORT} requires-interactive-mode and halt.
|
|
3196
|
-
- When a step requires the user to do something in the browser, give
|
|
3197
|
-
them the exact URL inside the wizard_ask prompt text — do not try to
|
|
3198
|
-
open a browser yourself.
|
|
3199
|
-
- Emit ${AgentSignals.STATUS} lines as you complete each step so the
|
|
3200
|
-
user sees progress.
|
|
3201
|
-
|
|
3202
|
-
Follow these steps IN ORDER. Do not skip or reorder.
|
|
3203
|
-
|
|
3204
|
-
STEP 1 — Check Self-driving access. (skill: "Check access")
|
|
3205
|
-
Self-driving is in open beta and available to every team, so there is
|
|
3206
|
-
no access gate to probe. Do NOT call any MCP tool here — mark this task
|
|
3207
|
-
in_progress and then completed right away and emit the
|
|
3208
|
-
${AgentSignals.STATUS} line, so the user sees an immediate first step.
|
|
3209
|
-
Only if the Signals API later turns out to be genuinely unreachable for
|
|
3210
|
-
this project (a hard error on every Signals call, unexpected in open
|
|
3211
|
-
beta) should you emit
|
|
3212
|
-
${AgentSignals.ABORT} self-driving is not available for this project
|
|
3213
|
-
and halt.
|
|
3214
|
-
|
|
3215
|
-
STEP 2 — Read project and current Signals state. (skill: "Read context")
|
|
3216
|
-
If ./posthog-setup-report.md exists, read it as a strong hint for what
|
|
3217
|
-
THIS repo instruments — but it is often absent (users frequently don't
|
|
3218
|
-
commit it), so do NOT depend on it. Combine whatever you find with the
|
|
3219
|
-
project-state block above and the skill's server-side usage probes —
|
|
3220
|
-
repo evidence rules products in, never out. Do a light scan ONLY for
|
|
3221
|
-
what neither covers. List the currently enabled signal sources so every
|
|
3222
|
-
later write is idempotent.
|
|
3223
|
-
|
|
3224
|
-
STEP 3 — Enable products. (skill: "Enable products")
|
|
3225
|
-
Turn ON the PostHog products Signals reads from — Session Replay,
|
|
3226
|
-
Error Tracking, and Support — so the sources you enable next, and the
|
|
3227
|
-
Replay Vision scanners in STEP 6c, have data to read. These are
|
|
3228
|
-
server-side enables with conservative defaults (owned by the server,
|
|
3229
|
-
not you). The project-state block above covers
|
|
3230
|
-
only Session Replay and Error Tracking, so you can skip those if they
|
|
3231
|
-
are already ON; it does not show Support, but every enable is idempotent
|
|
3232
|
-
so enabling any of them again is safe regardless. For a web app, also
|
|
3233
|
-
make sure this repo's posthog-js
|
|
3234
|
-
init doesn't disable them; for a pure backend or mobile app the server
|
|
3235
|
-
flip is inert, so just record that for the report and move on. The skill
|
|
3236
|
-
names the exact tool and the per-platform detail.
|
|
3237
|
-
|
|
3238
|
-
STEP 4 — Enable signal sources. (skill: "Enable sources")
|
|
3239
|
-
Enable the sources that match what this product actually uses, per
|
|
3240
|
-
the skill. Never enable a source for a tool the user hasn't
|
|
3241
|
-
confirmed they use.
|
|
3242
|
-
|
|
3243
|
-
STEP 5 — Offer issue-tracker integrations. (skill: "Connected tools")
|
|
3244
|
-
One batched multi-select wizard_ask for the external tools the skill
|
|
3245
|
-
lists. Order it per the skill: the tools from the "Tools detected in
|
|
3246
|
-
this codebase" list above come first, then the SaaS basics, then an
|
|
3247
|
-
"others" option for the long tail — never dump the whole catalog.
|
|
3248
|
-
The run auto-connects the ones it can (GitHub Issues, and
|
|
3249
|
-
Linear via a one-click OAuth link), verifying each with a single
|
|
3250
|
-
silent check — never nudge. For GitHub Issues: when the GitHub
|
|
3251
|
-
integration has exactly one repository connected, use that repo by
|
|
3252
|
-
default and skip repo research entirely — research which repo
|
|
3253
|
-
matches this project only when several are connected. It arms the
|
|
3254
|
-
rest as dormant responders to
|
|
3255
|
-
finish later: for tools it can't auto-connect (Zendesk, pganalyze) it
|
|
3256
|
-
never sends the user to paste credentials and never re-prompts. Enable
|
|
3257
|
-
a source only for a tool the user picked.
|
|
3258
|
-
|
|
3259
|
-
STEP 6 — Configure the scout troop. (skill: "Scouts")
|
|
3260
|
-
Materialize the troop, read the project's enforced scout-run budget
|
|
3261
|
-
(100 runs a day by default), then enable a selective set — the
|
|
3262
|
-
"general" scout plus the three to five specialists for the products
|
|
3263
|
-
this project uses most — and disable the rest, per the skill. The
|
|
3264
|
-
whole troop, including step 6b's custom scouts, stays at or under
|
|
3265
|
-
ten enabled scouts.
|
|
3266
|
-
|
|
3267
|
-
Then tell the user what the troop is, before STEP 6b asks them to add
|
|
3268
|
-
to it. Scouts are PostHog's built-in ones, and the user has not seen
|
|
3269
|
-
the list: without it, STEP 6b asks them to approve additions to a
|
|
3270
|
-
baseline they cannot picture. Send one short message naming each
|
|
3271
|
-
scout you enabled with a plain one-line description of what it
|
|
3272
|
-
watches, and say how many you left disabled and that they can turn
|
|
3273
|
-
those on later from the inbox. No wizard_ask here, it is not a
|
|
3274
|
-
question. Keep each line to one sentence and do not restate the
|
|
3275
|
-
skill's internals.
|
|
3276
|
-
|
|
3277
|
-
STEP 6b — Design custom scouts for this product. (skill: "Custom scouts")
|
|
3278
|
-
You are the only actor that has read this repo — turn that into
|
|
3279
|
-
coverage per the skill: a real gap analysis of the project's
|
|
3280
|
-
watchable surfaces against what the built-in troop already covers,
|
|
3281
|
-
then custom scouts for the uncovered ones. Start the gap analysis
|
|
3282
|
-
from the repo's for-agents context when present (AGENTS.md,
|
|
3283
|
-
CLAUDE.md, ARCHITECTURE.md, .cursor/rules, agent-facing docs) — it
|
|
3284
|
-
is a maintained map of the product's surfaces and vocabulary, so
|
|
3285
|
-
read it before scanning source. Keep scout bodies
|
|
3286
|
-
high-level: describe the behavior and signal conditions to watch,
|
|
3287
|
-
referencing repo evidence by file/function name — never paste raw
|
|
3288
|
-
source, secrets, env values, or customer data into a scout body.
|
|
3289
|
-
Never edit built-in scout bodies. Propose all candidates in ONE
|
|
3290
|
-
batched wizard_ask
|
|
3291
|
-
before creating anything; the user declining everything (or finding
|
|
3292
|
-
no gap at all) is a valid outcome, not an abort. Mark the task
|
|
3293
|
-
completed either way.
|
|
3294
|
-
|
|
3295
|
-
STEP 6c — Set up Replay Vision scanners. (skill: "Replay Vision scanners")
|
|
3296
|
-
Create the scanner skeletons the skill defines, filling the per-product
|
|
3297
|
-
blanks it leaves you from this repo's code. Scanners need Session Replay
|
|
3298
|
-
on (STEP 3). Every failure here — no recordings yet, a backend-only
|
|
3299
|
-
project, the scanner API missing — is a follow-up, never an abort; mark
|
|
3300
|
-
the task completed regardless.
|
|
3301
|
-
|
|
3302
|
-
STEP 7 — Write the report and hand off. (skill: "Report")
|
|
3303
|
-
Write the report per the skill, including follow-ups for anything
|
|
3304
|
-
deferred. Tell the user findings will start appearing in their inbox
|
|
3305
|
-
at ${inboxUrl} within about 30 minutes.`;
|
|
3306
|
-
}
|
|
3307
|
-
//#endregion
|
|
3308
|
-
//#region src/lib/programs/self-driving/step-keys.ts
|
|
3309
|
-
/**
|
|
3310
|
-
* First match wins, so order is the rule. Entity words come before verbs: "Checking GitHub
|
|
3311
|
-
* connection" is the GitHub step, not the access-check step, and "Connecting issue trackers" must
|
|
3312
|
-
* not fall through to GitHub just because a later run words it "Connecting GitHub issues".
|
|
3313
|
-
*/
|
|
3314
|
-
const RULES = [
|
|
3315
|
-
{
|
|
3316
|
-
key: "connect_issue_trackers",
|
|
3317
|
-
match: /issue[\s-]?track/i
|
|
3318
|
-
},
|
|
3319
|
-
{
|
|
3320
|
-
key: "setup_replay_vision",
|
|
3321
|
-
match: /replay vision|vision scanner/i
|
|
3322
|
-
},
|
|
3323
|
-
{
|
|
3324
|
-
key: "design_custom_scouts",
|
|
3325
|
-
match: /custom scout|designing scout/i
|
|
3326
|
-
},
|
|
3327
|
-
{
|
|
3328
|
-
key: "configure_scouts",
|
|
3329
|
-
match: /scout troop|tun(e|ing) .*scout|configur\w* .*scout/i
|
|
3330
|
-
},
|
|
3331
|
-
{
|
|
3332
|
-
key: "enable_signal_sources",
|
|
3333
|
-
match: /signal source/i
|
|
3334
|
-
},
|
|
3335
|
-
{
|
|
3336
|
-
key: "connect_github",
|
|
3337
|
-
match: /github/i
|
|
3338
|
-
},
|
|
3339
|
-
{
|
|
3340
|
-
key: "enable_products",
|
|
3341
|
-
match: /product/i
|
|
3342
|
-
},
|
|
3343
|
-
{
|
|
3344
|
-
key: "write_report",
|
|
3345
|
-
match: /report/i
|
|
3346
|
-
},
|
|
3347
|
-
{
|
|
3348
|
-
key: "check_access",
|
|
3349
|
-
match: /access/i
|
|
3350
|
-
},
|
|
3351
|
-
{
|
|
3352
|
-
key: "read_context",
|
|
3353
|
-
match: /read|context|project state/i
|
|
3354
|
-
}
|
|
3355
|
-
];
|
|
3356
|
-
/**
|
|
3357
|
-
* The canonical key for an agent-authored step label, or `undefined` when nothing matches — an
|
|
3358
|
-
* unmatched step is left unkeyed rather than bucketed, so a new step shows up as a gap to name
|
|
3359
|
-
* instead of quietly inflating whichever key it landed nearest.
|
|
3360
|
-
*/
|
|
3361
|
-
function resolveSelfDrivingStepKey(stepName) {
|
|
3362
|
-
if (!stepName) return void 0;
|
|
3363
|
-
return RULES.find((rule) => rule.match.test(stepName))?.key;
|
|
3364
|
-
}
|
|
3365
|
-
/** One line, for a screen that only has room to say what it costs. */
|
|
3366
|
-
const PRICING_SHORT = `Agents charge a flat $15 per pull request they ship.`;
|
|
3367
|
-
/** The same fact at length, plus the bit people are surprised by later. */
|
|
3368
|
-
const PRICING_LONG = `A report that ships a pull request costs a flat $15. Everything else follows your usual PostHog usage-based pricing.`;
|
|
3369
|
-
/**
|
|
3370
|
-
* Nothing caps the spend unless you set it, which is worth saying out loud. Checked against
|
|
3371
|
-
* billing: `custom_limits_map` starts empty and a product with no custom limit resolves to
|
|
3372
|
-
* `usage_limit: None`, i.e. uncapped. A free plan is capped by its own allocation instead,
|
|
3373
|
-
* hence the qualifier.
|
|
3374
|
-
*/
|
|
3375
|
-
const NO_DEFAULT_LIMIT = "A paid plan has no monthly cap until you set one. Add a PR limit in your inbox sidebar, and agents pause when they hit it.";
|
|
3376
|
-
//#endregion
|
|
3377
|
-
//#region src/lib/programs/self-driving/content/tips.ts
|
|
3378
|
-
const SELF_DRIVING_TIPS = [
|
|
3379
|
-
{
|
|
3380
|
-
id: "signal-source",
|
|
3381
|
-
title: "Signal source",
|
|
3382
|
-
description: "A stream PostHog watches: errors, replays, support, GitHub or Linear issues."
|
|
3383
|
-
},
|
|
3384
|
-
{
|
|
3385
|
-
id: "scout",
|
|
3386
|
-
title: "Scout",
|
|
3387
|
-
description: "An agent on a schedule, watching your sources for anomalies and patterns. Your project gets up to 100 scout runs a day by default."
|
|
3388
|
-
},
|
|
3389
|
-
{
|
|
3390
|
-
id: "scanner",
|
|
3391
|
-
title: "Scanner",
|
|
3392
|
-
description: "Where a scout reads your data, a scanner watches session recordings — an LLM spotting what broke on screen. It spends Replay Vision quota, so it stays scoped to your key flows."
|
|
3393
|
-
},
|
|
3394
|
-
{
|
|
3395
|
-
id: "signal-report",
|
|
3396
|
-
title: "Signal → report",
|
|
3397
|
-
description: "One finding with evidence and a suggested action. Signals group into prioritized reports in your inbox."
|
|
3398
|
-
},
|
|
3399
|
-
{
|
|
3400
|
-
id: "the-loop",
|
|
3401
|
-
title: "The loop",
|
|
3402
|
-
description: "After a fix ships, PostHog measures the result. If the pattern persists, a new signal reopens the work."
|
|
3403
|
-
},
|
|
3404
|
-
{
|
|
3405
|
-
id: "pricing",
|
|
3406
|
-
title: "What it costs",
|
|
3407
|
-
description: `${PRICING_LONG} ${NO_DEFAULT_LIMIT}`
|
|
3408
|
-
},
|
|
3409
|
-
{
|
|
3410
|
-
id: "work-anywhere",
|
|
3411
|
-
title: "Work anywhere",
|
|
3412
|
-
description: "Your inbox is in the PostHog app, Slack (tag @PostHog), and MCP. You can work from anywhere."
|
|
3413
|
-
}
|
|
3414
|
-
];
|
|
3415
|
-
const getTips = () => SELF_DRIVING_TIPS;
|
|
3416
|
-
//#endregion
|
|
3417
|
-
//#region src/lib/programs/self-driving/content/pipeline-diagram.tsx
|
|
3418
|
-
/**
|
|
3419
|
-
* PIPELINE_BLOCK — ASCII diagram of the Self-driving loop: sources feed
|
|
3420
|
-
* scouts, scouts raise signals, signals group into reports, reports land
|
|
3421
|
-
* in the inbox, shipped fixes get measured, and misses loop back as new
|
|
3422
|
-
* signals. Kept under ~38 chars wide so it fits the LearnCard pane at an
|
|
3423
|
-
* 80-column terminal.
|
|
3424
|
-
*/
|
|
3425
|
-
const PIPELINE_BLOCK = {
|
|
3426
|
-
type: "lines",
|
|
3427
|
-
interval: 300,
|
|
3428
|
-
pause: 8e3,
|
|
3429
|
-
lines: [
|
|
3430
|
-
/* @__PURE__ */ jsxs(Text, { children: [" ", /* @__PURE__ */ jsx(Text, {
|
|
3431
|
-
bold: true,
|
|
3432
|
-
color: "cyan",
|
|
3433
|
-
children: "signal sources"
|
|
3434
|
-
})] }),
|
|
3435
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3436
|
-
dimColor: true,
|
|
3437
|
-
children: " errors · replays · support"
|
|
3438
|
-
}),
|
|
3439
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
3440
|
-
color: "gray",
|
|
3441
|
-
children: " ↓ "
|
|
3442
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
3443
|
-
bold: true,
|
|
3444
|
-
color: Colors.accent,
|
|
3445
|
-
children: "scouts"
|
|
3446
|
-
})] }),
|
|
3447
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3448
|
-
dimColor: true,
|
|
3449
|
-
children: " watching on a schedule"
|
|
3450
|
-
}),
|
|
3451
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
3452
|
-
color: "gray",
|
|
3453
|
-
children: " ↓ "
|
|
3454
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
3455
|
-
bold: true,
|
|
3456
|
-
color: Colors.accent,
|
|
3457
|
-
children: "signals"
|
|
3458
|
-
})] }),
|
|
3459
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3460
|
-
dimColor: true,
|
|
3461
|
-
children: " finding + evidence + action"
|
|
3462
|
-
}),
|
|
3463
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
3464
|
-
color: "gray",
|
|
3465
|
-
children: " ↓ "
|
|
3466
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
3467
|
-
bold: true,
|
|
3468
|
-
color: Colors.accent,
|
|
3469
|
-
children: "reports"
|
|
3470
|
-
})] }),
|
|
3471
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3472
|
-
dimColor: true,
|
|
3473
|
-
children: " grouped and prioritized"
|
|
3474
|
-
}),
|
|
3475
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
3476
|
-
color: "gray",
|
|
3477
|
-
children: " ↓ "
|
|
3478
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
3479
|
-
bold: true,
|
|
3480
|
-
color: "green",
|
|
3481
|
-
children: "your inbox"
|
|
3482
|
-
})] }),
|
|
3483
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3484
|
-
dimColor: true,
|
|
3485
|
-
children: " investigate · tag · ship a PR"
|
|
3486
|
-
}),
|
|
3487
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
3488
|
-
color: "gray",
|
|
3489
|
-
children: " ↓ "
|
|
3490
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
3491
|
-
bold: true,
|
|
3492
|
-
color: Colors.accent,
|
|
3493
|
-
children: "measured"
|
|
3494
|
-
})] }),
|
|
3495
|
-
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
3496
|
-
color: "cyan",
|
|
3497
|
-
children: " ↺ "
|
|
3498
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
3499
|
-
dimColor: true,
|
|
3500
|
-
children: "didn't fix it? a new signal"
|
|
3501
|
-
})] })
|
|
3502
|
-
]
|
|
3503
|
-
};
|
|
3504
|
-
//#endregion
|
|
3505
|
-
//#region src/lib/programs/self-driving/content/index.tsx
|
|
3506
|
-
/**
|
|
3507
|
-
* Self-driving learn-deck — the narrative script played while the agent
|
|
3508
|
-
* sets up Self-driving. Teaches the vocabulary ladder (signal source →
|
|
3509
|
-
* scout → signal → report → action → measured loop) before the agent's
|
|
3510
|
-
* questions use those nouns, and explains why GitHub is required before
|
|
3511
|
-
* the connect ask lands.
|
|
3512
|
-
*
|
|
3513
|
-
* One deck for every self-driving run. On the integrate path it plays
|
|
3514
|
-
* during the install run (the pane belongs to whichever run screen shows
|
|
3515
|
-
* first), so it doubles as the wait-time read.
|
|
3516
|
-
*/
|
|
3517
|
-
const CLEAR = {
|
|
3518
|
-
type: "clear",
|
|
3519
|
-
pause: 2e3
|
|
3520
|
-
};
|
|
3521
|
-
const getContentBlocks = (store) => {
|
|
3522
|
-
return [
|
|
3523
|
-
{
|
|
3524
|
-
content: "Welcome.",
|
|
3525
|
-
pause: 3e3,
|
|
3526
|
-
mode: 0,
|
|
3527
|
-
animationInterval: 160
|
|
3528
|
-
},
|
|
3529
|
-
{
|
|
3530
|
-
content: "The Wizard is an agent.",
|
|
3531
|
-
pause: 4e3
|
|
3532
|
-
},
|
|
3533
|
-
{
|
|
3534
|
-
content: "It's setting up PostHog Self-driving for this project.",
|
|
3535
|
-
pause: 5e3
|
|
3536
|
-
},
|
|
3537
|
-
{
|
|
3538
|
-
content: "We'll occasionally need your input. Stick around.",
|
|
3539
|
-
pause: 6e3
|
|
3540
|
-
},
|
|
3541
|
-
CLEAR,
|
|
3542
|
-
{
|
|
3543
|
-
pause: 5e3,
|
|
3544
|
-
persist: true,
|
|
3545
|
-
content: /* @__PURE__ */ jsx(StatusPeekTrigger, { store })
|
|
3546
|
-
},
|
|
3547
|
-
{
|
|
3548
|
-
pause: 6e3,
|
|
3549
|
-
content: /* @__PURE__ */ jsxs(Text, { children: [
|
|
3550
|
-
"Press",
|
|
3551
|
-
" ",
|
|
3552
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3553
|
-
color: Colors.accent,
|
|
3554
|
-
bold: true,
|
|
3555
|
-
children: "S"
|
|
3556
|
-
}),
|
|
3557
|
-
" ",
|
|
3558
|
-
"to expand or collapse the status."
|
|
3559
|
-
] })
|
|
3560
|
-
},
|
|
3561
|
-
CLEAR,
|
|
3562
|
-
{
|
|
3563
|
-
content: "Your PostHog tools all share one data layer.",
|
|
3564
|
-
pause: 4e3
|
|
3565
|
-
},
|
|
3566
|
-
{
|
|
3567
|
-
content: "Analytics, replays, errors, flags: they all share the same users, mapped across every tool.",
|
|
3568
|
-
pause: 6e3
|
|
3569
|
-
},
|
|
3570
|
-
{
|
|
3571
|
-
content: "Together, they show how people really use your product.",
|
|
3572
|
-
pause: 5e3
|
|
3573
|
-
},
|
|
3574
|
-
{
|
|
3575
|
-
content: "Piecing that together used to be manual work. That's what Self-driving automates.",
|
|
3576
|
-
pause: 6e3
|
|
3577
|
-
},
|
|
3578
|
-
CLEAR,
|
|
3579
|
-
{
|
|
3580
|
-
content: "A coding agent sees your code, but not how your product is used.",
|
|
3581
|
-
pause: 5e3
|
|
3582
|
-
},
|
|
3583
|
-
{
|
|
3584
|
-
content: "PostHog knows what your users actually do.",
|
|
3585
|
-
pause: 4e3
|
|
3586
|
-
},
|
|
3587
|
-
{
|
|
3588
|
-
content: "Self-driving puts an agent in the loop.",
|
|
3589
|
-
pause: 5e3
|
|
3590
|
-
},
|
|
3591
|
-
{
|
|
3592
|
-
content: "That's why setup will ask to connect GitHub: so Self-driving can access your code and your context, together.",
|
|
3593
|
-
pause: 6e3
|
|
3594
|
-
},
|
|
3595
|
-
CLEAR,
|
|
3596
|
-
{
|
|
3597
|
-
content: "Each PostHog tool you enable is a signal source.",
|
|
3598
|
-
pause: 4e3
|
|
3599
|
-
},
|
|
3600
|
-
{
|
|
3601
|
-
content: "Errors, replays, support tickets, GitHub or Linear issues: they're all streams of product usage.",
|
|
3602
|
-
pause: 6e3
|
|
3603
|
-
},
|
|
3604
|
-
CLEAR,
|
|
3605
|
-
{
|
|
3606
|
-
content: "Scouts are agents that watch those sources on a schedule.",
|
|
3607
|
-
pause: 5e3
|
|
3608
|
-
},
|
|
3609
|
-
{
|
|
3610
|
-
content: "They look for anomalies and patterns: a spike in errors or a funnel quietly dropping.",
|
|
3611
|
-
pause: 6e3
|
|
3612
|
-
},
|
|
3613
|
-
{
|
|
3614
|
-
content: "Setup tunes your scouts to fit your product.",
|
|
3615
|
-
pause: 4e3
|
|
3616
|
-
},
|
|
3617
|
-
CLEAR,
|
|
3618
|
-
{
|
|
3619
|
-
content: "When a scout finds something worth your attention, that's a signal.",
|
|
3620
|
-
pause: 5e3
|
|
3621
|
-
},
|
|
3622
|
-
{
|
|
3623
|
-
content: "Each signal carries the finding, the evidence, and a suggested action.",
|
|
3624
|
-
pause: 5e3
|
|
3625
|
-
},
|
|
3626
|
-
{
|
|
3627
|
-
content: "Related signals group into reports, sorted by priority.",
|
|
3628
|
-
pause: 4e3
|
|
3629
|
-
},
|
|
3630
|
-
CLEAR,
|
|
3631
|
-
{
|
|
3632
|
-
content: "Here's the whole loop.",
|
|
3633
|
-
pause: 1e3
|
|
3634
|
-
},
|
|
3635
|
-
PIPELINE_BLOCK,
|
|
3636
|
-
CLEAR,
|
|
3637
|
-
{
|
|
3638
|
-
content: "Reports land in your Self-driving inbox.",
|
|
3639
|
-
pause: 4e3
|
|
3640
|
-
},
|
|
3641
|
-
{
|
|
3642
|
-
content: "Investigate with the agent, tag teammates, or kick off a PR.",
|
|
3643
|
-
pause: 6e3
|
|
3644
|
-
},
|
|
3645
|
-
{
|
|
3646
|
-
content: PRICING_LONG,
|
|
3647
|
-
pause: 5e3
|
|
3648
|
-
},
|
|
3649
|
-
{
|
|
3650
|
-
content: NO_DEFAULT_LIMIT,
|
|
3651
|
-
pause: 5e3
|
|
3652
|
-
},
|
|
3653
|
-
CLEAR,
|
|
3654
|
-
{
|
|
3655
|
-
content: "And after the PR ships, PostHog measures the result.",
|
|
3656
|
-
pause: 5e3
|
|
3657
|
-
},
|
|
3658
|
-
{
|
|
3659
|
-
content: "Did the fix actually address the pattern?",
|
|
3660
|
-
pause: 4e3
|
|
3661
|
-
},
|
|
3662
|
-
{
|
|
3663
|
-
content: "If not, a new signal surfaces, and the loop keeps working.",
|
|
3664
|
-
pause: 6e3
|
|
3665
|
-
},
|
|
3666
|
-
CLEAR,
|
|
3667
|
-
{
|
|
3668
|
-
content: "Work with Self-driving wherever you already are:",
|
|
3669
|
-
pause: 2e3
|
|
3670
|
-
},
|
|
3671
|
-
{
|
|
3672
|
-
type: "lines",
|
|
3673
|
-
interval: 800,
|
|
3674
|
-
pause: 6e3,
|
|
3675
|
-
lines: [
|
|
3676
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
3677
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3678
|
-
color: "cyan",
|
|
3679
|
-
children: " ◆ "
|
|
3680
|
-
}),
|
|
3681
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3682
|
-
bold: true,
|
|
3683
|
-
children: "Inbox"
|
|
3684
|
-
}),
|
|
3685
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3686
|
-
dimColor: true,
|
|
3687
|
-
children: " the PostHog web app"
|
|
3688
|
-
})
|
|
3689
|
-
] }),
|
|
3690
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
3691
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3692
|
-
color: "cyan",
|
|
3693
|
-
children: " ◆ "
|
|
3694
|
-
}),
|
|
3695
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3696
|
-
bold: true,
|
|
3697
|
-
children: "Slack"
|
|
3698
|
-
}),
|
|
3699
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3700
|
-
dimColor: true,
|
|
3701
|
-
children: " tag @PostHog"
|
|
3702
|
-
})
|
|
3703
|
-
] }),
|
|
3704
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
3705
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3706
|
-
color: "cyan",
|
|
3707
|
-
children: " ◆ "
|
|
3708
|
-
}),
|
|
3709
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3710
|
-
bold: true,
|
|
3711
|
-
children: "MCP"
|
|
3712
|
-
}),
|
|
3713
|
-
/* @__PURE__ */ jsx(Text, {
|
|
3714
|
-
dimColor: true,
|
|
3715
|
-
children: " your agents and editors"
|
|
3716
|
-
})
|
|
3717
|
-
] })
|
|
3718
|
-
]
|
|
3719
|
-
},
|
|
3720
|
-
{
|
|
3721
|
-
content: "Product usage data becomes real, shippable change.",
|
|
3722
|
-
pause: 5e3
|
|
3723
|
-
},
|
|
3724
|
-
{
|
|
3725
|
-
content: "Your product drives itself.",
|
|
3726
|
-
pause: 1e4
|
|
3727
|
-
}
|
|
3728
|
-
];
|
|
3729
|
-
};
|
|
3730
|
-
//#endregion
|
|
3731
|
-
//#region src/lib/programs/self-driving/index.ts
|
|
3732
|
-
const SELF_DRIVING_SKILL_ID = "self-driving-setup";
|
|
3733
|
-
const REPORT_FILE = "posthog-self-driving-report.md";
|
|
3734
|
-
const DOCS_URL = "https://posthog.com/docs";
|
|
3735
|
-
const SUCCESS_MESSAGE = "Self-driving is on. PostHog is scanning your project; first findings hit your inbox within ~30 minutes.";
|
|
3736
|
-
const WIZARD_MARKER = ".posthog-wizard";
|
|
3737
|
-
/**
|
|
3738
|
-
* Remove the installed setup skill. It is transient orchestration
|
|
3739
|
-
* knowledge (unlike integration skills such as the Next.js one, which
|
|
3740
|
-
* are worth keeping for the user's coding agents), so the program
|
|
3741
|
-
* cleans it up instead of showing the keep-skills prompt. Marker-
|
|
3742
|
-
* guarded: only directories the wizard installed are touched.
|
|
3743
|
-
*/
|
|
3744
|
-
async function removeInstalledSkill(installDir) {
|
|
3745
|
-
const skillDir = join(installDir, ".claude", "skills", SELF_DRIVING_SKILL_ID);
|
|
3746
|
-
try {
|
|
3747
|
-
await access(join(skillDir, WIZARD_MARKER));
|
|
3748
|
-
} catch {
|
|
3749
|
-
return;
|
|
3750
|
-
}
|
|
3751
|
-
await rm(skillDir, {
|
|
3752
|
-
recursive: true,
|
|
3753
|
-
force: true
|
|
3754
|
-
}).catch(() => void 0);
|
|
3755
|
-
}
|
|
3756
|
-
const buildRun = (session) => Promise.resolve({
|
|
3757
|
-
skillId: SELF_DRIVING_SKILL_ID,
|
|
3758
|
-
integrationLabel: SELF_DRIVING_SKILL_ID,
|
|
3759
|
-
customPrompt: (ctx) => buildSelfDrivingPrompt(ctx, getSelfDrivingDetectedTools(session)),
|
|
3760
|
-
successMessage: SUCCESS_MESSAGE,
|
|
3761
|
-
reportFile: REPORT_FILE,
|
|
3762
|
-
docsUrl: DOCS_URL,
|
|
3763
|
-
spinnerMessage: "Setting up PostHog Self-driving...",
|
|
3764
|
-
estimatedDurationMinutes: 10,
|
|
3765
|
-
abortCases: SELF_DRIVING_ABORT_CASES,
|
|
3766
|
-
maxQuestions: 13,
|
|
3767
|
-
richLinks: true,
|
|
3768
|
-
askTimeoutMs: 1800 * 1e3,
|
|
3769
|
-
trackStepProgress: true,
|
|
3770
|
-
resolveStepKey: resolveSelfDrivingStepKey,
|
|
3771
|
-
postRun: async (session) => {
|
|
3772
|
-
await removeInstalledSkill(session.installDir);
|
|
3773
|
-
},
|
|
3774
|
-
buildOutroData: (_session, credentials) => {
|
|
3775
|
-
return {
|
|
3776
|
-
kind: "success",
|
|
3777
|
-
message: SUCCESS_MESSAGE,
|
|
3778
|
-
primaryLink: {
|
|
3779
|
-
label: "Your Self-driving inbox",
|
|
3780
|
-
url: `${credentials.host.appHost.replace(/\/$/, "")}/project/${credentials.projectId}/inbox`
|
|
3781
|
-
},
|
|
3782
|
-
nextSteps: {
|
|
3783
|
-
heading: "In your inbox you can:",
|
|
3784
|
-
items: [
|
|
3785
|
-
"Investigate reports with the agent",
|
|
3786
|
-
"Tag teammates to loop them in",
|
|
3787
|
-
`Kick off a PR when you like the proposed fix ($15 flat)`,
|
|
3788
|
-
"Cap the spend with a monthly PR limit in the sidebar",
|
|
3789
|
-
"Or work from Slack (tag @PostHog) and MCP"
|
|
3790
|
-
]
|
|
3791
|
-
},
|
|
3792
|
-
body: `${PRICING_LONG} ${NO_DEFAULT_LIMIT}`,
|
|
3793
|
-
reportFile: REPORT_FILE
|
|
3794
|
-
};
|
|
3795
|
-
}
|
|
3796
|
-
});
|
|
3797
|
-
const selfDrivingConfig = {
|
|
3798
|
-
...createSkillProgram({
|
|
3799
|
-
skillId: SELF_DRIVING_SKILL_ID,
|
|
3800
|
-
command: "self-driving",
|
|
3801
|
-
id: "self-driving",
|
|
3802
|
-
description: "Set up PostHog Self-driving for this project",
|
|
3803
|
-
integrationLabel: SELF_DRIVING_SKILL_ID,
|
|
3804
|
-
successMessage: SUCCESS_MESSAGE,
|
|
3805
|
-
reportFile: REPORT_FILE,
|
|
3806
|
-
docsUrl: DOCS_URL,
|
|
3807
|
-
spinnerMessage: "Setting up PostHog Self-driving...",
|
|
3808
|
-
estimatedDurationMinutes: 10,
|
|
3809
|
-
requires: ["posthog-integration"],
|
|
3810
|
-
abortCases: SELF_DRIVING_ABORT_CASES
|
|
3811
|
-
}),
|
|
3812
|
-
steps: SELF_DRIVING_PROGRAM,
|
|
3813
|
-
run: buildRun,
|
|
3814
|
-
getTips,
|
|
3815
|
-
getContentBlocks
|
|
3816
|
-
};
|
|
3817
|
-
//#endregion
|
|
3818
|
-
//#region src/lib/programs/mcp/index.ts
|
|
3819
|
-
const mcpAddConfig = {
|
|
3820
|
-
id: "mcp-add",
|
|
3821
|
-
requiresAi: false,
|
|
3822
|
-
description: "Add PostHog MCP server to supported clients",
|
|
3823
|
-
steps: [
|
|
3824
|
-
{
|
|
3825
|
-
id: "mcp-add",
|
|
3826
|
-
label: "Add MCP server",
|
|
3827
|
-
screenId: "mcp-add",
|
|
3828
|
-
isComplete: (s) => s.mcpComplete
|
|
3829
|
-
},
|
|
3830
|
-
{
|
|
3831
|
-
id: "slack-connect",
|
|
3832
|
-
label: "Connect Slack",
|
|
3833
|
-
screenId: "slack-connect",
|
|
3834
|
-
show: (s) => s.mcpOutcome === "installed",
|
|
3835
|
-
isComplete: (s) => s.slackStepDismissed
|
|
3836
|
-
},
|
|
3837
|
-
{
|
|
3838
|
-
id: "mcp-suggested-prompts",
|
|
3839
|
-
label: "Suggested prompts",
|
|
3840
|
-
screenId: "mcp-suggested-prompts",
|
|
3841
|
-
show: (s) => s.mcpOutcome === "installed",
|
|
3842
|
-
isComplete: (s) => s.mcpSuggestedPromptsDismissed,
|
|
3843
|
-
reportsAsProgramId: "mcp-tutorial"
|
|
3844
|
-
}
|
|
3845
|
-
]
|
|
3846
|
-
};
|
|
3847
|
-
/**
|
|
3848
|
-
* `wizard mcp remove` — single-step uninstall flow.
|
|
3849
|
-
*
|
|
3850
|
-
* DO NOT append `mcp-suggested-prompts` (or any other tutorial-shaped
|
|
3851
|
-
* step) here. A user who just removed MCP is opting OUT of the agent having
|
|
3852
|
-
* access to PostHog; immediately pivoting into a tutorial that asks
|
|
3853
|
-
* them to log in and try prompts is wrong on intent and confusing on
|
|
3854
|
-
* UX. The screen also reads `session.mcpInstalledClients` for its
|
|
3855
|
-
* Choose-phase copy ("MCP is installed for X") — that array is empty
|
|
3856
|
-
* post-remove, so the copy would be a lie.
|
|
3857
|
-
*
|
|
3858
|
-
* If you want a "did you mean to keep it?" confirmation, build that as
|
|
3859
|
-
* a screen earlier in this program — don't reuse the tutorial.
|
|
3860
|
-
*/
|
|
3861
|
-
const mcpRemoveConfig = {
|
|
3862
|
-
id: "mcp-remove",
|
|
3863
|
-
requiresAi: false,
|
|
3864
|
-
description: "Remove PostHog MCP server from supported clients",
|
|
3865
|
-
steps: [{
|
|
3866
|
-
id: "mcp-remove",
|
|
3867
|
-
label: "Remove MCP server",
|
|
3868
|
-
screenId: "mcp-remove",
|
|
3869
|
-
isComplete: (s) => s.mcpComplete
|
|
3870
|
-
}]
|
|
3871
|
-
};
|
|
3872
|
-
/**
|
|
3873
|
-
* Standalone tutorial flow — boots directly into the Choose phase of
|
|
3874
|
-
* McpSuggestedPromptsScreen without going through MCP install first.
|
|
3875
|
-
* Useful for users who already installed MCP and want to revisit the
|
|
3876
|
-
* tutorial, or anyone who just wants to try the agent against PostHog
|
|
3877
|
-
* without touching their IDE config.
|
|
3878
|
-
*
|
|
3879
|
-
* The screen handles its own OAuth (via services.performLogin) so this
|
|
3880
|
-
* program doesn't pre-populate credentials.
|
|
3881
|
-
*/
|
|
3882
|
-
const mcpTutorialConfig = {
|
|
3883
|
-
id: "mcp-tutorial",
|
|
3884
|
-
requiresAi: false,
|
|
3885
|
-
description: "Try the PostHog MCP with your agent — no install needed",
|
|
3886
|
-
steps: [{
|
|
3887
|
-
id: "mcp-suggested-prompts",
|
|
3888
|
-
label: "MCP tutorial",
|
|
3889
|
-
screenId: "mcp-suggested-prompts",
|
|
3890
|
-
isComplete: (s) => s.mcpSuggestedPromptsDismissed
|
|
3891
|
-
}, {
|
|
3892
|
-
id: "slack-connect",
|
|
3893
|
-
label: "Connect Slack",
|
|
3894
|
-
screenId: "slack-connect",
|
|
3895
|
-
isComplete: (s) => s.slackStepDismissed
|
|
3896
|
-
}]
|
|
3897
|
-
};
|
|
3898
|
-
//#endregion
|
|
3899
|
-
//#region src/lib/programs/mcp-analytics/index.ts
|
|
3900
|
-
const MCP_ANALYTICS_REPORT_FILE = "posthog-mcp-analytics-report.md";
|
|
3901
|
-
/**
|
|
3902
|
-
* `[ABORT]` reasons the mcp-analytics skill emits when the project can't be
|
|
3903
|
-
* instrumented. Kept in sync with the stop conditions in the skill's
|
|
3904
|
-
* `description.md` (context-mill `context/skills/mcp-analytics`).
|
|
3905
|
-
*/
|
|
3906
|
-
const MCP_ANALYTICS_ABORT_CASES = [
|
|
3907
|
-
{
|
|
3908
|
-
match: /^unsupported language for mcp analytics$/i,
|
|
3909
|
-
errorCode: ErrorCodes.DetectUnsupportedPlatform,
|
|
3910
|
-
message: "Unsupported language for MCP analytics",
|
|
3911
|
-
body: "MCP analytics supports TypeScript/JavaScript (`@posthog/mcp`) and Python (`posthog.mcp`, shipped inside the `posthog` package). This project doesn't look like either, so there's nothing to instrument. See https://posthog.com/docs/mcp-analytics for the supported setups."
|
|
3912
|
-
},
|
|
3913
|
-
{
|
|
3914
|
-
match: /^no mcp server found$/i,
|
|
3915
|
-
message: "No MCP server found",
|
|
3916
|
-
body: "This command instruments an existing MCP server with PostHog analytics, but no MCP server was found in this project. If you just want PostHog product analytics, run `npx @posthog/wizard` instead."
|
|
3917
|
-
},
|
|
3918
|
-
{
|
|
3919
|
-
match: /^could not locate the server entry point$/i,
|
|
3920
|
-
message: "Could not locate the MCP server entry point",
|
|
3921
|
-
body: "This project has MCP signals, but the agent couldn't find where the server is constructed or requests are dispatched, so there's nowhere safe to add instrumentation. See https://posthog.com/docs/mcp-analytics for the supported server styles, or point the wizard at the package that defines the server if it's in a monorepo subdirectory."
|
|
3922
|
-
}
|
|
3923
|
-
];
|
|
3924
|
-
/**
|
|
3925
|
-
* `wizard mcp-analytics` — flat skill command.
|
|
3926
|
-
*
|
|
3927
|
-
* Instruments the user's own MCP server with the `@posthog/mcp` SDK so it
|
|
3928
|
-
* reports `$mcp_*` analytics about itself. This is the opposite of
|
|
3929
|
-
* `wizard mcp add` (which installs the PostHog MCP *server* into a coding
|
|
3930
|
-
* agent) — keep the two distinct.
|
|
3931
|
-
*
|
|
3932
|
-
* Flat while instrumenting is the only action. If an uninstrument / `remove`
|
|
3933
|
-
* leaf ever lands, restructure into a family with `familyCommandFactory` and
|
|
3934
|
-
* publish each leaf as a `cliEntries` entry with `parentCommand:
|
|
3935
|
-
* 'mcp-analytics'` from context-mill — a deliberate breaking change, done then,
|
|
3936
|
-
* not pre-emptively.
|
|
3937
|
-
*/
|
|
3938
|
-
const mcpAnalyticsConfig = createSkillProgram({
|
|
3939
|
-
skillId: "mcp-analytics",
|
|
3940
|
-
command: "mcp-analytics",
|
|
3941
|
-
id: "mcp-analytics",
|
|
3942
|
-
description: "Add PostHog MCP analytics to your MCP server",
|
|
3943
|
-
integrationLabel: "mcp-analytics",
|
|
3944
|
-
customPrompt: `Instrument this project's MCP server with PostHog MCP analytics. Run the \`mcp-analytics\` skill end-to-end: detect the server style, install \`@posthog/mcp\` and \`posthog-node\`, wrap the server (or use \`PostHogMCP\` for a custom dispatcher), wire the project API key and host, and verify. Make only additive changes — do not alter tool behavior. The final report is written to ./${MCP_ANALYTICS_REPORT_FILE}.`,
|
|
3945
|
-
successMessage: `MCP analytics configured! View the report at ./${MCP_ANALYTICS_REPORT_FILE}`,
|
|
3946
|
-
reportFile: MCP_ANALYTICS_REPORT_FILE,
|
|
3947
|
-
docsUrl: "https://posthog.com/docs/mcp-analytics",
|
|
3948
|
-
spinnerMessage: "Setting up MCP analytics...",
|
|
3949
|
-
estimatedDurationMinutes: 5,
|
|
3950
|
-
abortCases: MCP_ANALYTICS_ABORT_CASES
|
|
3951
|
-
});
|
|
3952
|
-
//#endregion
|
|
3953
|
-
//#region src/lib/programs/replay-vision/index.ts
|
|
3954
|
-
const REPLAY_VISION_REPORT_FILE = "posthog-replay-vision-report.md";
|
|
3955
|
-
/**
|
|
3956
|
-
* The platforms session replay can actually record on. Replay vision watches
|
|
3957
|
-
* recordings, so a platform with no recordings has nothing to set up — the
|
|
3958
|
-
* run must stop before any work, not after a pointless agent run.
|
|
3959
|
-
*
|
|
3960
|
-
* Web frameworks record through posthog-js (server-rendered frameworks
|
|
3961
|
-
* included — they serve pages), and the mobile SDKs with replay support are
|
|
3962
|
-
* React Native, Android, iOS, and Flutter. Excluded: pure backend targets
|
|
3963
|
-
* (`javascript_node`, `python`, `ruby`) and KMP, which has no replay support
|
|
3964
|
-
* yet.
|
|
3965
|
-
*/
|
|
3966
|
-
const REPLAY_VISION_SUPPORTED = new Set([
|
|
3967
|
-
"nextjs",
|
|
3968
|
-
"nuxt",
|
|
3969
|
-
"vue",
|
|
3970
|
-
"react-router",
|
|
3971
|
-
"tanstack-start",
|
|
3972
|
-
"tanstack-router",
|
|
3973
|
-
"angular",
|
|
3974
|
-
"astro",
|
|
3975
|
-
"sveltekit",
|
|
3976
|
-
"javascript_web",
|
|
3977
|
-
"django",
|
|
3978
|
-
"flask",
|
|
3979
|
-
"fastapi",
|
|
3980
|
-
"laravel",
|
|
3981
|
-
"rails",
|
|
3982
|
-
"react-native",
|
|
3983
|
-
"android",
|
|
3984
|
-
"swift",
|
|
3985
|
-
"flutter"
|
|
3986
|
-
]);
|
|
3987
|
-
async function abortUnsupportedPlatform(integration) {
|
|
3988
|
-
const name = FRAMEWORK_REGISTRY[integration]?.metadata.name ?? integration;
|
|
3989
|
-
analytics.wizardCapture("replay-vision unsupported platform", { integration });
|
|
3990
|
-
await wizardAbort({
|
|
3991
|
-
code: ErrorCodes.DetectUnsupportedPlatform,
|
|
3992
|
-
message: `Session replay isn't available for ${name} projects, and Replay vision needs session recordings to watch — so there is nothing to set up here.
|
|
3993
|
-
|
|
3994
|
-
If this repo also contains a web or mobile app, run the command from that project directory instead. See what replay supports at:
|
|
3995
|
-
https://posthog.com/docs/session-replay`
|
|
3996
|
-
});
|
|
3997
|
-
}
|
|
3998
|
-
/**
|
|
3999
|
-
* `[ABORT]` reasons the replay-vision skill emits when the run can't proceed.
|
|
4000
|
-
* Kept in sync with the stop conditions in the skill's `description.md`
|
|
4001
|
-
* (context-mill `context/skills/replay-vision`).
|
|
4002
|
-
*/
|
|
4003
|
-
const REPLAY_VISION_ABORT_CASES = [{
|
|
4004
|
-
match: /^replay vision not available for this project$/i,
|
|
4005
|
-
message: "Replay vision is not available for this project",
|
|
4006
|
-
body: "Every Replay vision scanner endpoint reported that the feature is not available here yet. Session replay setup done so far is kept. See https://posthog.com/docs/replay-vision for availability."
|
|
4007
|
-
}];
|
|
4008
|
-
/**
|
|
4009
|
-
* Framework detection ahead of the run, exactly like the default integration
|
|
4010
|
-
* program. The orchestrator requires it: `session.skillId` must hold the
|
|
4011
|
-
* detected framework id before the run arm starts, because the runner
|
|
4012
|
-
* resolves the reference integration skill and every task's mini-skill
|
|
4013
|
-
* variants (`integration-v2-install`, `integration-v2-init`, …) against it in
|
|
4014
|
-
* preflight. Without this step the session would still carry the program's
|
|
4015
|
-
* own skill id and preflight would abort.
|
|
4016
|
-
*/
|
|
4017
|
-
const DETECT_STEP = {
|
|
4018
|
-
id: "detect",
|
|
4019
|
-
label: "Detecting framework",
|
|
4020
|
-
onReady: async (ctx) => {
|
|
4021
|
-
const integration = await detectFramework(ctx.session.installDir);
|
|
4022
|
-
if (integration && !REPLAY_VISION_SUPPORTED.has(integration)) {
|
|
4023
|
-
await abortUnsupportedPlatform(integration);
|
|
4024
|
-
return;
|
|
4025
|
-
}
|
|
4026
|
-
await detectPostHogIntegration(ctx);
|
|
4027
|
-
}
|
|
4028
|
-
};
|
|
4029
|
-
/**
|
|
4030
|
-
* `wizard replay-vision` — flat skill command on the orchestrator sequence.
|
|
4031
|
-
*
|
|
4032
|
-
* Makes session replay record (server toggle + client init check), then
|
|
4033
|
-
* creates vision scanners scoped to the product's key flows, read out of the
|
|
4034
|
-
* repo. The orchestrator runs the `replay-vision` agent flow (context-mill
|
|
4035
|
-
* `context/agents/replay-vision`): the seed enqueues the install/init tasks
|
|
4036
|
-
* (copied from integration-v2, sharing its step-skills) when the project has
|
|
4037
|
-
* no PostHog yet, so the command works on uninstrumented repos instead of
|
|
4038
|
-
* aborting.
|
|
4039
|
-
*
|
|
4040
|
-
* Departures from a plain `createSkillProgram`:
|
|
4041
|
-
* - `DETECT_STEP` in front, so `session.skillId` carries the framework id the
|
|
4042
|
-
* orchestrator's preflight resolves reference + mini-skill variants with.
|
|
4043
|
-
* - `agentFlow` pinned (the id would default to the same value — explicit so
|
|
4044
|
-
* renaming the program can't silently detach the flow).
|
|
4045
|
-
* - `ciPreRun` mirrors the default integration program: scope the install dir
|
|
4046
|
-
* to the right project (monorepos), then detect the framework — the
|
|
4047
|
-
* headless equivalent of the detect step's onReady hook.
|
|
4048
|
-
*/
|
|
4049
|
-
const replayVisionConfig = {
|
|
4050
|
-
...createSkillProgram({
|
|
4051
|
-
skillId: "replay-vision-setup",
|
|
4052
|
-
command: "replay-vision",
|
|
4053
|
-
id: "replay-vision",
|
|
4054
|
-
description: "Set up PostHog Replay vision scanners for your product",
|
|
4055
|
-
integrationLabel: "replay-vision",
|
|
4056
|
-
customPrompt: `Set up PostHog Replay vision. Run the \`replay-vision\` skill end-to-end: make sure session replay is recording (server-side enable plus a posthog-js init check), then create the vision scanners the skill defines, scoped to this product's key flows read out of the repo. If PostHog is not integrated yet, install and initialize the SDK first as the skill instructs — do not abort. The final report is written to ./${REPLAY_VISION_REPORT_FILE}.`,
|
|
4057
|
-
successMessage: `Replay vision configured! View the report at ./${REPLAY_VISION_REPORT_FILE}`,
|
|
4058
|
-
reportFile: REPLAY_VISION_REPORT_FILE,
|
|
4059
|
-
docsUrl: "https://posthog.com/docs/replay-vision",
|
|
4060
|
-
spinnerMessage: "Setting up Replay vision...",
|
|
4061
|
-
estimatedDurationMinutes: 6,
|
|
4062
|
-
abortCases: REPLAY_VISION_ABORT_CASES
|
|
4063
|
-
}),
|
|
4064
|
-
agentFlow: "replay-vision",
|
|
4065
|
-
steps: [DETECT_STEP, ...AGENT_SKILL_STEPS],
|
|
4066
|
-
ciPreRun: async (session) => {
|
|
4067
|
-
await scopeInstallDirToProject(session);
|
|
4068
|
-
const integration = await detectFramework(session.installDir);
|
|
4069
|
-
if (!integration) {
|
|
4070
|
-
await wizardAbort({
|
|
4071
|
-
code: ErrorCodes.DetectNoFramework,
|
|
4072
|
-
message: "Could not auto-detect your framework for this project."
|
|
4073
|
-
});
|
|
4074
|
-
return;
|
|
4075
|
-
}
|
|
4076
|
-
if (!REPLAY_VISION_SUPPORTED.has(integration)) {
|
|
4077
|
-
await abortUnsupportedPlatform(integration);
|
|
4078
|
-
return;
|
|
4079
|
-
}
|
|
4080
|
-
session.integration = integration;
|
|
4081
|
-
analytics.setTag("integration", integration);
|
|
4082
|
-
const frameworkConfig = FRAMEWORK_REGISTRY[integration];
|
|
4083
|
-
session.frameworkConfig = frameworkConfig;
|
|
4084
|
-
session.skillId = integration;
|
|
4085
|
-
const context = await gatherFrameworkContext(frameworkConfig, {
|
|
4086
|
-
installDir: session.installDir,
|
|
4087
|
-
debug: session.debug,
|
|
4088
|
-
signup: session.signup,
|
|
4089
|
-
ci: true,
|
|
4090
|
-
benchmark: session.benchmark,
|
|
4091
|
-
yaraReport: session.yaraReport
|
|
4092
|
-
});
|
|
4093
|
-
for (const [key, value] of Object.entries(context)) if (!(key in session.frameworkContext)) session.frameworkContext[key] = value;
|
|
4094
|
-
}
|
|
4095
|
-
};
|
|
4096
|
-
//#endregion
|
|
4097
|
-
//#region src/lib/programs/ai-observability/index.ts
|
|
4098
|
-
const AI_OBSERVABILITY_STEPS = AGENT_SKILL_STEPS.map((step) => step.id === "intro" ? {
|
|
4099
|
-
...step,
|
|
4100
|
-
screenId: "ai-observability-intro"
|
|
4101
|
-
} : step);
|
|
4102
|
-
const AI_OBSERVABILITY_REPORT_FILE = "posthog-ai-observability-report.md";
|
|
4103
|
-
/**
|
|
4104
|
-
* `wizard ai-observability` — wrap the project's LLM client calls so they emit
|
|
4105
|
-
* `$ai_generation` events into LLM Analytics.
|
|
4106
|
-
*
|
|
4107
|
-
* No `run.skillId`: the context-mill `ai-observability` group ships one variant
|
|
4108
|
-
* per (LLM provider × language) and the wizard does no provider detection —
|
|
4109
|
-
* the agent loads the menu, matches the manifest's vendor SDK, and installs
|
|
4110
|
-
* the right variant itself (see `customPrompt`). Stays flat while a single
|
|
4111
|
-
* "add AIO to a project" flow is the only action.
|
|
4112
|
-
*/
|
|
4113
|
-
const aiObservabilityConfig = {
|
|
4114
|
-
command: "ai-observability",
|
|
4115
|
-
description: "Add PostHog AI Observability to your LLM calls",
|
|
4116
|
-
id: "ai-observability",
|
|
4117
|
-
steps: AI_OBSERVABILITY_STEPS,
|
|
4118
|
-
reportFile: AI_OBSERVABILITY_REPORT_FILE,
|
|
4119
|
-
getContentBlocks: getContentBlocks$3,
|
|
4120
|
-
run: {
|
|
4121
|
-
integrationLabel: "ai-observability",
|
|
4122
|
-
customPrompt: () => `Instrument this project's LLM calls with PostHog AI Observability.
|
|
4123
|
-
|
|
4124
|
-
This flow has no pre-installed skill — you install the right one yourself:
|
|
4125
|
-
|
|
4126
|
-
1. Call \`load_skill_menu\` with \`category: "ai-observability"\`. The menu is
|
|
4127
|
-
the source of truth: one variant per (LLM provider × language), plus a
|
|
4128
|
-
\`manual-capture\` variant for projects with no vendor SDK.
|
|
4129
|
-
|
|
4130
|
-
2. Scan the project manifest (\`package.json\`, \`pyproject.toml\`,
|
|
4131
|
-
\`requirements.txt\`) for a vendor LLM SDK and pick the variant that matches
|
|
4132
|
-
it — the language follows the manifest (\`package.json\` → Node, Python
|
|
4133
|
-
tooling → Python). Multiple SDKs (e.g. LangChain wrapping OpenAI) → prefer
|
|
4134
|
-
the higher abstraction. No vendor SDK → the \`manual-capture\` variant.
|
|
4135
|
-
Genuinely ambiguous → \`wizard_ask\` with a multi-choice picker.
|
|
4136
|
-
|
|
4137
|
-
3. Call \`install_skill\` with the picked variant id. Then follow that skill's
|
|
4138
|
-
\`SKILL.md\` and references end-to-end. The skill itself will install
|
|
4139
|
-
packages, wire OTel, set env vars, and describe verification.
|
|
4140
|
-
|
|
4141
|
-
Make only additive changes — do not touch existing PostHog init, identify
|
|
4142
|
-
calls, event capture, or dashboards. Those belong to other skills. The
|
|
4143
|
-
final report is written to ./${AI_OBSERVABILITY_REPORT_FILE}.`,
|
|
4144
|
-
successMessage: `AI Observability configured! View the report at ./${AI_OBSERVABILITY_REPORT_FILE}`,
|
|
4145
|
-
reportFile: AI_OBSERVABILITY_REPORT_FILE,
|
|
4146
|
-
docsUrl: "https://posthog.com/docs/ai-observability",
|
|
4147
|
-
spinnerMessage: "Setting up AI Observability...",
|
|
4148
|
-
estimatedDurationMinutes: 5
|
|
4149
|
-
}
|
|
4150
|
-
};
|
|
4151
|
-
//#endregion
|
|
4152
|
-
//#region src/lib/programs/metrics/index.ts
|
|
4153
|
-
const METRICS_STEPS = AGENT_SKILL_STEPS.map((step) => step.id === "intro" ? {
|
|
4154
|
-
...step,
|
|
4155
|
-
screenId: "metrics-intro"
|
|
4156
|
-
} : step);
|
|
4157
|
-
const METRICS_REPORT_FILE = "posthog-metrics-report.md";
|
|
4158
|
-
/**
|
|
4159
|
-
* `wizard metrics` — instrument the project with PostHog application metrics
|
|
4160
|
-
* (`posthog.metrics` counters, gauges, and histograms).
|
|
4161
|
-
*
|
|
4162
|
-
* No `run.skillId`: the context-mill `metrics` group ships one variant per
|
|
4163
|
-
* platform (python, nodejs, javascript, kubernetes, other/OTLP) and the agent
|
|
4164
|
-
* pulls the matching one itself — the flow's tasks and the linear
|
|
4165
|
-
* `customPrompt` both pick from the menu and install it. Stays flat while a
|
|
4166
|
-
* single "add metrics to a project" flow is the only action.
|
|
4167
|
-
*/
|
|
4168
|
-
const metricsConfig = {
|
|
4169
|
-
command: "metrics",
|
|
4170
|
-
description: "Add PostHog application metrics to your project",
|
|
4171
|
-
id: "metrics",
|
|
4172
|
-
agentFlow: "metrics",
|
|
4173
|
-
steps: METRICS_STEPS,
|
|
4174
|
-
reportFile: METRICS_REPORT_FILE,
|
|
4175
|
-
getContentBlocks: getContentBlocks$3,
|
|
4176
|
-
run: {
|
|
4177
|
-
integrationLabel: "metrics",
|
|
4178
|
-
customPrompt: () => `Instrument this project with PostHog application metrics.
|
|
4179
|
-
|
|
4180
|
-
This flow has no pre-installed skill — you install the right one yourself:
|
|
4181
|
-
|
|
4182
|
-
1. Call \`load_skill_menu\` with \`category: "metrics"\`. The menu is the
|
|
4183
|
-
source of truth: one variant per platform.
|
|
4184
|
-
|
|
4185
|
-
2. Pick the variant that matches the project:
|
|
4186
|
-
- Python tooling (\`pyproject.toml\`, \`requirements.txt\`, \`Pipfile\`) →
|
|
4187
|
-
\`metrics-python\` (needs \`posthog\` >= 7.23.0)
|
|
4188
|
-
- \`package.json\` with server-side Node code → \`metrics-nodejs\`
|
|
4189
|
-
(needs \`posthog-node\` >= 5.43.0)
|
|
4190
|
-
- \`package.json\` that is browser-only → \`metrics-javascript\`
|
|
4191
|
-
(needs \`posthog-js\` >= 1.399.0)
|
|
4192
|
-
- Kubernetes manifests / Helm charts and the user wants cluster-level
|
|
4193
|
-
scraping → \`metrics-kubernetes\`
|
|
4194
|
-
- Any other language → \`metrics-other\` (plain OTLP exporter)
|
|
4195
|
-
A full-stack app (e.g. Next.js) usually wants the server variant — metrics
|
|
4196
|
-
measure service work, not user actions. Genuinely ambiguous →
|
|
4197
|
-
\`wizard_ask\` with a multi-choice picker.
|
|
4198
|
-
|
|
4199
|
-
3. Call \`install_skill\` with the picked variant id. Then follow that skill's
|
|
4200
|
-
\`SKILL.md\` and references end-to-end — it covers where to place metrics
|
|
4201
|
-
(middleware, background jobs, external calls, business commit sites) and
|
|
4202
|
-
the low-cardinality attribute rules.
|
|
4203
|
-
|
|
4204
|
-
Make only additive changes — reuse an existing PostHog client by adding the
|
|
4205
|
-
\`metrics\` config to it rather than constructing a second client, and do not
|
|
4206
|
-
touch existing identify calls, event capture, or dashboards. The final report
|
|
4207
|
-
is written to ./${METRICS_REPORT_FILE}.`,
|
|
4208
|
-
successMessage: `Application metrics configured! View the report at ./${METRICS_REPORT_FILE}`,
|
|
4209
|
-
reportFile: METRICS_REPORT_FILE,
|
|
4210
|
-
docsUrl: "https://posthog.com/docs/metrics",
|
|
4211
|
-
spinnerMessage: "Setting up application metrics...",
|
|
4212
|
-
estimatedDurationMinutes: 5
|
|
4213
|
-
}
|
|
4214
|
-
};
|
|
4215
|
-
//#endregion
|
|
4216
|
-
//#region src/lib/programs/slack/index.ts
|
|
4217
|
-
const slackConnectConfig = {
|
|
4218
|
-
id: "slack",
|
|
4219
|
-
description: "Connect PostHog to your Slack",
|
|
4220
|
-
steps: [{
|
|
4221
|
-
id: "slack-connect",
|
|
4222
|
-
label: "Connect Slack",
|
|
4223
|
-
screenId: "slack-connect",
|
|
4224
|
-
isComplete: (s) => s.slackStepDismissed
|
|
4225
|
-
}]
|
|
4226
|
-
};
|
|
4227
|
-
//#endregion
|
|
4228
|
-
//#region src/lib/programs/program-registry.ts
|
|
4229
|
-
const agentSkillConfig = {
|
|
4230
|
-
id: "agent-skill",
|
|
4231
|
-
description: "Run an arbitrary context-mill skill",
|
|
4232
|
-
steps: AGENT_SKILL_STEPS,
|
|
4233
|
-
getContentBlocks: getContentBlocks$3,
|
|
4234
|
-
allowedTools: ["Agent"],
|
|
4235
|
-
run: (session) => {
|
|
4236
|
-
const skillId = session.skillId ?? "agent-skill";
|
|
4237
|
-
return Promise.resolve({
|
|
4238
|
-
skillId,
|
|
4239
|
-
integrationLabel: skillId,
|
|
4240
|
-
spinnerMessage: `Running ${skillId}...`,
|
|
4241
|
-
successMessage: `${skillId} complete!`,
|
|
4242
|
-
estimatedDurationMinutes: 5,
|
|
4243
|
-
reportFile: `posthog-${skillId}-report.md`,
|
|
4244
|
-
docsUrl: POSTHOG_DOCS_URL
|
|
4245
|
-
});
|
|
4246
|
-
}
|
|
4247
|
-
};
|
|
4248
|
-
const PROGRAM_REGISTRY = [
|
|
4249
|
-
posthogIntegrationConfig,
|
|
4250
|
-
revenueAnalyticsConfig,
|
|
4251
|
-
warehouseSourceConfig,
|
|
4252
|
-
errorTrackingUploadSourceMapsConfig,
|
|
4253
|
-
auditConfig,
|
|
4254
|
-
eventsAuditConfig,
|
|
4255
|
-
posthogDoctorConfig,
|
|
4256
|
-
webAnalyticsDoctorConfig,
|
|
4257
|
-
migrationConfig,
|
|
4258
|
-
selfDrivingConfig,
|
|
4259
|
-
agentSkillConfig,
|
|
4260
|
-
mcpAddConfig,
|
|
4261
|
-
mcpRemoveConfig,
|
|
4262
|
-
mcpTutorialConfig,
|
|
4263
|
-
mcpAnalyticsConfig,
|
|
4264
|
-
replayVisionConfig,
|
|
4265
|
-
aiObservabilityConfig,
|
|
4266
|
-
metricsConfig,
|
|
4267
|
-
slackConnectConfig
|
|
4268
|
-
];
|
|
4269
|
-
/**
|
|
4270
|
-
* Typed program names. Values come from each config's `id`, so there's
|
|
4271
|
-
* no parallel string list to keep in sync — adding `Program.Foo` here is
|
|
4272
|
-
* just exposing `fooConfig.id` under a friendly name for call sites.
|
|
4273
|
-
*/
|
|
4274
|
-
const Program = {
|
|
4275
|
-
PostHogIntegration: posthogIntegrationConfig.id,
|
|
4276
|
-
RevenueAnalyticsSetup: revenueAnalyticsConfig.id,
|
|
4277
|
-
WarehouseSource: warehouseSourceConfig.id,
|
|
4278
|
-
ErrorTrackingUploadSourceMaps: errorTrackingUploadSourceMapsConfig.id,
|
|
4279
|
-
Migration: migrationConfig.id,
|
|
4280
|
-
Audit: auditConfig.id,
|
|
4281
|
-
EventsAudit: eventsAuditConfig.id,
|
|
4282
|
-
PosthogDoctor: posthogDoctorConfig.id,
|
|
4283
|
-
WebAnalyticsDoctor: webAnalyticsDoctorConfig.id,
|
|
4284
|
-
SelfDriving: selfDrivingConfig.id,
|
|
4285
|
-
AgentSkill: agentSkillConfig.id,
|
|
4286
|
-
McpAdd: mcpAddConfig.id,
|
|
4287
|
-
McpRemove: mcpRemoveConfig.id,
|
|
4288
|
-
McpTutorial: mcpTutorialConfig.id,
|
|
4289
|
-
McpAnalytics: mcpAnalyticsConfig.id,
|
|
4290
|
-
ReplayVision: replayVisionConfig.id,
|
|
4291
|
-
AiObservability: aiObservabilityConfig.id,
|
|
4292
|
-
Metrics: metricsConfig.id,
|
|
4293
|
-
SlackConnect: slackConnectConfig.id
|
|
4294
|
-
};
|
|
4295
|
-
/**
|
|
4296
|
-
* Look up a program config by its id. `ProgramId` is a union of every
|
|
4297
|
-
* registered id, so the lookup is statically guaranteed to find a match
|
|
4298
|
-
* — the `!` is a load-bearing assertion of that invariant, not a hope.
|
|
4299
|
-
*/
|
|
4300
|
-
function getProgramConfig(id) {
|
|
4301
|
-
return PROGRAM_REGISTRY.find((c) => c.id === id);
|
|
4302
|
-
}
|
|
4303
|
-
//#endregion
|
|
4304
321
|
//#region src/commands/mcp/tui-availability.ts
|
|
4305
322
|
/**
|
|
4306
323
|
* Ink throws "Raw mode is not supported" when stdin has no TTY (piped input,
|
|
@@ -4337,7 +354,7 @@ const mcpAddCommand = {
|
|
|
4337
354
|
function runMcpAdd(argv) {
|
|
4338
355
|
const features = parseFeatures(argv.features);
|
|
4339
356
|
(async () => {
|
|
4340
|
-
const { readApiKeyFromEnv } = await import("./env-api-key-
|
|
357
|
+
const { readApiKeyFromEnv } = await import("./env-api-key-Ca-CrkgV.js").then((n) => n.t);
|
|
4341
358
|
const apiKey = argv.apiKey || readApiKeyFromEnv();
|
|
4342
359
|
const debug = argv.debug;
|
|
4343
360
|
const localMcp = argv.local;
|
|
@@ -4351,8 +368,8 @@ function runMcpAdd(argv) {
|
|
|
4351
368
|
return;
|
|
4352
369
|
}
|
|
4353
370
|
try {
|
|
4354
|
-
const { startTUI } = await import("./start-tui-
|
|
4355
|
-
const { buildSession } = await import("./wizard-session-
|
|
371
|
+
const { startTUI } = await import("./start-tui-Bz23_yym.js");
|
|
372
|
+
const { buildSession } = await import("./wizard-session-CYvyYnti.js");
|
|
4356
373
|
const tui = startTUI(VERSION, Program.McpAdd);
|
|
4357
374
|
tui.store.session = buildSession({
|
|
4358
375
|
debug,
|
|
@@ -4369,7 +386,7 @@ function runMcpAdd(argv) {
|
|
|
4369
386
|
}
|
|
4370
387
|
async function runHeadlessAdd(args) {
|
|
4371
388
|
setUI(new LoggingUI());
|
|
4372
|
-
const { addMCPServerToClientsStep } = await import("./add-mcp-server-to-clients-
|
|
389
|
+
const { addMCPServerToClientsStep } = await import("./add-mcp-server-to-clients-Exddicp2.js").then((n) => n.r);
|
|
4373
390
|
const { installed, failed } = await addMCPServerToClientsStep(args);
|
|
4374
391
|
if (failed.length > 0 || installed.length === 0) process.exitCode = 1;
|
|
4375
392
|
}
|
|
@@ -4401,8 +418,8 @@ function runMcpRemove(argv) {
|
|
|
4401
418
|
return;
|
|
4402
419
|
}
|
|
4403
420
|
try {
|
|
4404
|
-
const { startTUI } = await import("./start-tui-
|
|
4405
|
-
const { buildSession } = await import("./wizard-session-
|
|
421
|
+
const { startTUI } = await import("./start-tui-Bz23_yym.js");
|
|
422
|
+
const { buildSession } = await import("./wizard-session-CYvyYnti.js");
|
|
4406
423
|
const tui = startTUI(VERSION, Program.McpRemove);
|
|
4407
424
|
tui.store.session = buildSession({
|
|
4408
425
|
debug,
|
|
@@ -4418,7 +435,7 @@ function runMcpRemove(argv) {
|
|
|
4418
435
|
/** No exit code on an empty result: nothing to remove is the requested end state. */
|
|
4419
436
|
async function runHeadlessRemove(local) {
|
|
4420
437
|
setUI(new LoggingUI());
|
|
4421
|
-
const { removeMCPServerFromClientsStep } = await import("./add-mcp-server-to-clients-
|
|
438
|
+
const { removeMCPServerFromClientsStep } = await import("./add-mcp-server-to-clients-Exddicp2.js").then((n) => n.r);
|
|
4422
439
|
await removeMCPServerFromClientsStep({ local });
|
|
4423
440
|
}
|
|
4424
441
|
//#endregion
|
|
@@ -4438,8 +455,8 @@ function runMcpTutorial(argv) {
|
|
|
4438
455
|
const debug = argv.debug;
|
|
4439
456
|
const localMcp = argv.local;
|
|
4440
457
|
try {
|
|
4441
|
-
const { startTUI } = await import("./start-tui-
|
|
4442
|
-
const { buildSession } = await import("./wizard-session-
|
|
458
|
+
const { startTUI } = await import("./start-tui-Bz23_yym.js");
|
|
459
|
+
const { buildSession } = await import("./wizard-session-CYvyYnti.js");
|
|
4443
460
|
const tui = startTUI(VERSION, Program.McpTutorial);
|
|
4444
461
|
tui.store.session = buildSession({
|
|
4445
462
|
debug,
|
|
@@ -4526,17 +543,18 @@ function runWizard(config, options) {
|
|
|
4526
543
|
(async () => {
|
|
4527
544
|
try {
|
|
4528
545
|
const installDir = options.installDir || process.cwd();
|
|
4529
|
-
const { startTUI } = await import("./start-tui-
|
|
4530
|
-
const { buildSession, RunPhase } = await import("./wizard-session-
|
|
4531
|
-
const { TaskStreamPush } = await import("./task-stream-
|
|
4532
|
-
const { PostHogDestination } = await import("./posthog-
|
|
546
|
+
const { startTUI } = await import("./start-tui-Bz23_yym.js");
|
|
547
|
+
const { buildSession, RunPhase } = await import("./wizard-session-CYvyYnti.js");
|
|
548
|
+
const { TaskStreamPush } = await import("./task-stream-Dl0XEsZ9.js");
|
|
549
|
+
const { PostHogDestination } = await import("./posthog-BZ3NNCyh.js");
|
|
550
|
+
const { createFileDestination } = await import("./file-CNstcW8F.js");
|
|
4533
551
|
const local = getLocalDev();
|
|
4534
552
|
const localServicesError = await checkLocalServices({
|
|
4535
553
|
...local,
|
|
4536
554
|
localPosthog: local.localPosthog && !options.baseUrl
|
|
4537
555
|
});
|
|
4538
556
|
if (localServicesError) {
|
|
4539
|
-
const { wizardAbort } = await import("./wizard-abort-
|
|
557
|
+
const { wizardAbort } = await import("./wizard-abort-BHLKVKLN.js");
|
|
4540
558
|
await wizardAbort({ message: localServicesError });
|
|
4541
559
|
return;
|
|
4542
560
|
}
|
|
@@ -4567,19 +585,6 @@ function runWizard(config, options) {
|
|
|
4567
585
|
if (options.skillId) session.skillId = options.skillId;
|
|
4568
586
|
else if (config.skillId) session.skillId = config.skillId;
|
|
4569
587
|
activeTui.store.session = session;
|
|
4570
|
-
const taskStreamEnabled = !session.noTelemetry;
|
|
4571
|
-
taskStream = new TaskStreamPush({
|
|
4572
|
-
store: activeTui.store,
|
|
4573
|
-
programId: config.id,
|
|
4574
|
-
destinations: [new PostHogDestination({
|
|
4575
|
-
getCredentials: () => activeTui.store.session.credentials,
|
|
4576
|
-
onError: (err) => logToFile("[task-stream-push]", err.message)
|
|
4577
|
-
})],
|
|
4578
|
-
eventPlanPath: config.eventPlanFile ? join$1(session.installDir, config.eventPlanFile) : void 0,
|
|
4579
|
-
enabled: taskStreamEnabled
|
|
4580
|
-
});
|
|
4581
|
-
const activeStream = taskStream;
|
|
4582
|
-
activeStream.attach();
|
|
4583
588
|
let signalled = false;
|
|
4584
589
|
onSignal = () => {
|
|
4585
590
|
if (signalled || exitInProgress) return;
|
|
@@ -4587,27 +592,54 @@ function runWizard(config, options) {
|
|
|
4587
592
|
logToFile("[run-wizard] signal received, flushing task stream");
|
|
4588
593
|
runCleanups();
|
|
4589
594
|
if (activeTui.store.session.runPhase === RunPhase.Running) activeTui.store.setRunPhase(RunPhase.Error);
|
|
4590
|
-
|
|
595
|
+
const teardown = () => {
|
|
4591
596
|
try {
|
|
4592
597
|
activeTui.unmount();
|
|
4593
598
|
} catch {}
|
|
4594
599
|
process.exit(130);
|
|
4595
|
-
}
|
|
600
|
+
};
|
|
601
|
+
const stream = taskStream;
|
|
602
|
+
if (!stream) {
|
|
603
|
+
teardown();
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
stream.shutdown(2e3).catch((e) => logToFile("[run-wizard] task stream shutdown error on signal:", e)).finally(teardown);
|
|
4596
607
|
};
|
|
4597
608
|
process.on("SIGINT", onSignal);
|
|
4598
609
|
process.on("SIGTERM", onSignal);
|
|
4599
|
-
|
|
4600
|
-
|
|
610
|
+
for (;;) {
|
|
611
|
+
await activeTui.store.runReadyHooks();
|
|
612
|
+
await activeTui.store.getGate("intro");
|
|
613
|
+
const active = activeTui.store.router.activeProgram;
|
|
614
|
+
if (active === config.id) break;
|
|
615
|
+
config = getProgramConfig(active);
|
|
616
|
+
}
|
|
617
|
+
const fileDestination = createFileDestination(options.taskStreamLog);
|
|
618
|
+
const destinations = [...session.noTelemetry ? [] : [new PostHogDestination({
|
|
619
|
+
getCredentials: () => activeTui.store.session.credentials,
|
|
620
|
+
onError: (err) => logToFile("[task-stream-push]", err.message)
|
|
621
|
+
})], ...fileDestination ? [fileDestination] : []];
|
|
622
|
+
const taskStreamEnabled = destinations.length > 0;
|
|
623
|
+
const activeStream = new TaskStreamPush({
|
|
624
|
+
store: activeTui.store,
|
|
625
|
+
programId: config.streamWorkflowId ?? config.id,
|
|
626
|
+
destinations,
|
|
627
|
+
eventPlanPath: config.eventPlanFile ? join(session.installDir, config.eventPlanFile) : void 0,
|
|
628
|
+
auditChecks: config.auditLedgerFile ? () => getAuditChecks(activeTui.store.session) : void 0,
|
|
629
|
+
enabled: taskStreamEnabled
|
|
630
|
+
});
|
|
631
|
+
taskStream = activeStream;
|
|
632
|
+
activeStream.attach();
|
|
4601
633
|
await activeTui.store.getGate("integration-check");
|
|
4602
634
|
await activeTui.store.getGate("health-check");
|
|
4603
635
|
const skipAgent = config.run == null;
|
|
4604
636
|
const shown = (s) => !s.show || s.show(activeTui.store.session);
|
|
4605
|
-
if (config.steps.some((s) => s.run)) for (const step of config.steps) {
|
|
637
|
+
if (config.steps.some((s) => s.run || s.targetDir)) for (const step of config.steps) {
|
|
4606
638
|
if (step.screenId === "outro") break;
|
|
4607
639
|
if (shown(step)) await advanceStep(step, activeTui.store, config);
|
|
4608
640
|
}
|
|
4609
641
|
else if (skipAgent) {
|
|
4610
|
-
const { getOrAskForProjectData } = await import("./setup-utils-
|
|
642
|
+
const { getOrAskForProjectData } = await import("./setup-utils-BUAoRjw3.js").then((n) => n.r);
|
|
4611
643
|
const { projectApiKey, host, accessToken, projectId } = await getOrAskForProjectData({
|
|
4612
644
|
signup: session.signup,
|
|
4613
645
|
ci: session.ci,
|
|
@@ -4622,26 +654,32 @@ function runWizard(config, options) {
|
|
|
4622
654
|
host,
|
|
4623
655
|
projectId
|
|
4624
656
|
});
|
|
4625
|
-
} else
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
const
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
657
|
+
} else try {
|
|
658
|
+
await runAgent(config, activeTui.store.session);
|
|
659
|
+
} catch (error) {
|
|
660
|
+
const failure = classifyRunFailure(error);
|
|
661
|
+
logToFile("[run-wizard] run failed, handing off:", error);
|
|
662
|
+
runCleanups();
|
|
663
|
+
analytics.captureException(error instanceof Error ? error : new Error(String(error)), { error_code: failure.code });
|
|
664
|
+
getUI().outroError({
|
|
665
|
+
kind: "error",
|
|
666
|
+
errorCode: failure.code,
|
|
667
|
+
message: failure.message
|
|
4633
668
|
});
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
669
|
+
}
|
|
670
|
+
const runFailed = isRunFailure(activeTui.store.session);
|
|
671
|
+
await activeTui.store.waitUntil((s) => {
|
|
672
|
+
if (s.mintHandoff === "exit") return true;
|
|
673
|
+
if (skipAgent && !runFailed) return s.outroDismissed;
|
|
674
|
+
return s.skillsComplete;
|
|
4638
675
|
});
|
|
4639
676
|
exitInProgress = true;
|
|
4640
677
|
await activeStream.shutdown(2e3);
|
|
4641
678
|
process.off("SIGINT", onSignal);
|
|
4642
679
|
process.off("SIGTERM", onSignal);
|
|
680
|
+
if (runFailed) await analytics.shutdown("error");
|
|
4643
681
|
activeTui.unmount();
|
|
4644
|
-
process.exit(0);
|
|
682
|
+
process.exit(runFailed ? 1 : 0);
|
|
4645
683
|
} catch (err) {
|
|
4646
684
|
logToFile("[run-wizard] FATAL:", err);
|
|
4647
685
|
runCleanups();
|
|
@@ -4721,14 +759,13 @@ function runNonInteractive(config, options, mode) {
|
|
|
4721
759
|
setUI(new LoggingUI());
|
|
4722
760
|
validateNonInteractiveOptions(options, mode);
|
|
4723
761
|
analytics.setTag("build", mode);
|
|
4724
|
-
setLegacyGatewayFallback(mode === "ci");
|
|
4725
762
|
(async () => {
|
|
4726
763
|
const path = await import("path");
|
|
4727
|
-
const { buildSession, RunPhase, OutroKind } = await import("./wizard-session-
|
|
4728
|
-
const { readEnvironment } = await import("./environment-
|
|
4729
|
-
const { readApiKeyFromEnv } = await import("./env-api-key-
|
|
4730
|
-
const { configureLogFileFromEnvironment, logToFile } = await import("./debug-
|
|
4731
|
-
const { wizardAbort, WizardError } = await import("./wizard-abort-
|
|
764
|
+
const { buildSession, RunPhase, OutroKind } = await import("./wizard-session-CYvyYnti.js");
|
|
765
|
+
const { readEnvironment } = await import("./environment-B0uTamFf.js").then((n) => n.t);
|
|
766
|
+
const { readApiKeyFromEnv } = await import("./env-api-key-Ca-CrkgV.js").then((n) => n.t);
|
|
767
|
+
const { configureLogFileFromEnvironment, logToFile } = await import("./debug-BxMMpepn.js");
|
|
768
|
+
const { wizardAbort, WizardError } = await import("./wizard-abort-BHLKVKLN.js");
|
|
4732
769
|
configureLogFileFromEnvironment();
|
|
4733
770
|
const env = readEnvironment();
|
|
4734
771
|
const apiKey = options.apiKey ?? readApiKeyFromEnv() ?? void 0;
|
|
@@ -4774,25 +811,31 @@ function runNonInteractive(config, options, mode) {
|
|
|
4774
811
|
}
|
|
4775
812
|
let store = null;
|
|
4776
813
|
let taskStream = null;
|
|
4777
|
-
|
|
4778
|
-
const { WizardStore } = await import("./store-
|
|
4779
|
-
const { HeadlessUI } = await import("./headless-ui-
|
|
4780
|
-
const { TaskStreamPush, PostHogDestination } = await import("./task-stream-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
814
|
+
{
|
|
815
|
+
const { WizardStore } = await import("./store-D5ROpUje.js").then((n) => n.n);
|
|
816
|
+
const { HeadlessUI } = await import("./headless-ui-TpSp-F43.js");
|
|
817
|
+
const { TaskStreamPush, PostHogDestination, createFileDestination } = await import("./task-stream-Dl0XEsZ9.js");
|
|
818
|
+
const fileDestination = createFileDestination(mode === "ci" ? options.taskStreamLog ?? "" : options.taskStreamLog);
|
|
819
|
+
const posthogDestination = mode === "headless" && !session.noTelemetry ? new PostHogDestination({
|
|
820
|
+
getCredentials: () => session.credentials,
|
|
821
|
+
onError: (e) => logToFile("[headless task-stream]", e.message)
|
|
822
|
+
}) : null;
|
|
823
|
+
const destinations = [...posthogDestination ? [posthogDestination] : [], ...fileDestination ? [fileDestination] : []];
|
|
824
|
+
const headlessStore = new WizardStore(config.id);
|
|
825
|
+
store = headlessStore;
|
|
826
|
+
headlessStore.session = session;
|
|
827
|
+
setUI(new HeadlessUI(headlessStore));
|
|
4784
828
|
taskStream = new TaskStreamPush({
|
|
4785
|
-
store,
|
|
4786
|
-
programId: config.id,
|
|
4787
|
-
destinations
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
eventPlanPath: config.eventPlanFile ? join$1(session.installDir, config.eventPlanFile) : void 0,
|
|
4792
|
-
enabled: !session.noTelemetry
|
|
829
|
+
store: headlessStore,
|
|
830
|
+
programId: config.streamWorkflowId ?? config.id,
|
|
831
|
+
destinations,
|
|
832
|
+
eventPlanPath: config.eventPlanFile ? join(session.installDir, config.eventPlanFile) : void 0,
|
|
833
|
+
auditChecks: config.auditLedgerFile ? () => getAuditChecks(headlessStore.session) : void 0,
|
|
834
|
+
enabled: destinations.length > 0
|
|
4793
835
|
});
|
|
4794
836
|
taskStream.attach();
|
|
4795
|
-
|
|
837
|
+
headlessStore.setRunPhase(RunPhase.Running);
|
|
838
|
+
if (fileDestination) logToFile(`[task-stream] ${mode} dump: ${fileDestination.path}`);
|
|
4796
839
|
}
|
|
4797
840
|
const settleStream = async (phase, outroData) => {
|
|
4798
841
|
if (!store || !taskStream) return;
|
|
@@ -4801,6 +844,10 @@ function runNonInteractive(config, options, mode) {
|
|
|
4801
844
|
await taskStream.shutdown(2e3);
|
|
4802
845
|
};
|
|
4803
846
|
try {
|
|
847
|
+
if (mode === "ci") {
|
|
848
|
+
const { configureGatewayFromCIEnvironment } = await import("./gateway-session-BIVN_Xfr.js");
|
|
849
|
+
configureGatewayFromCIEnvironment(Number(session.projectId), session.region ?? "us");
|
|
850
|
+
}
|
|
4804
851
|
if (config.ciPreRun) await config.ciPreRun(session);
|
|
4805
852
|
else {
|
|
4806
853
|
const readyCtx = {
|
|
@@ -4817,7 +864,10 @@ function runNonInteractive(config, options, mode) {
|
|
|
4817
864
|
session.unsupportedVersion = info;
|
|
4818
865
|
},
|
|
4819
866
|
addDiscoveredFeature: () => void 0,
|
|
4820
|
-
setDetectionComplete: () => void 0
|
|
867
|
+
setDetectionComplete: () => void 0,
|
|
868
|
+
setPosthogSdkDetected: (detected) => {
|
|
869
|
+
session.posthogSdkDetected = detected;
|
|
870
|
+
}
|
|
4821
871
|
};
|
|
4822
872
|
for (const step of config.steps) if (step.onReady) await step.onReady(readyCtx);
|
|
4823
873
|
const detectError = session.frameworkContext.detectError;
|
|
@@ -4860,7 +910,7 @@ function runNonInteractive(config, options, mode) {
|
|
|
4860
910
|
});
|
|
4861
911
|
}
|
|
4862
912
|
}
|
|
4863
|
-
const { runAgent } = await import("./agent-runner-
|
|
913
|
+
const { runAgent } = await import("./agent-runner-C_R1X_UT.js").then((n) => n.t);
|
|
4864
914
|
await runAgent(config, session);
|
|
4865
915
|
await settleStream(RunPhase.Completed);
|
|
4866
916
|
} catch (error) {
|
|
@@ -5091,12 +1141,25 @@ const NATIVE_HANDLERS = { audit: { "web-analytics": webAnalyticsDoctorConfig } }
|
|
|
5091
1141
|
* `skillId` injected. The comprehensive `audit all` is the one exception —
|
|
5092
1142
|
* skillId 'audit' triggers the specialized auditConfig (custom hooks,
|
|
5093
1143
|
* content blocks, screens).
|
|
1144
|
+
*
|
|
1145
|
+
* This is the one place that knows a subcommand belongs to `audit`, so the
|
|
1146
|
+
* generic skill program picks up the ledger here rather than for every skill.
|
|
5094
1147
|
*/
|
|
5095
|
-
function configForCliEntry(entry) {
|
|
1148
|
+
function configForCliEntry(entry, family) {
|
|
5096
1149
|
if (entry.skillId === "audit") return auditConfig;
|
|
5097
1150
|
return {
|
|
5098
1151
|
...agentSkillConfig,
|
|
5099
|
-
skillId: entry.skillId
|
|
1152
|
+
skillId: entry.skillId,
|
|
1153
|
+
...family === "audit" ? {
|
|
1154
|
+
auditLedgerFile: AUDIT_CHECKS_FILE,
|
|
1155
|
+
streamWorkflowId: family,
|
|
1156
|
+
allowedTools: [
|
|
1157
|
+
...agentSkillConfig.allowedTools ?? [],
|
|
1158
|
+
WIZARD_TOOL_NAMES.auditSeedChecks,
|
|
1159
|
+
WIZARD_TOOL_NAMES.auditAddChecks,
|
|
1160
|
+
WIZARD_TOOL_NAMES.auditResolveChecks
|
|
1161
|
+
]
|
|
1162
|
+
} : {}
|
|
5100
1163
|
};
|
|
5101
1164
|
}
|
|
5102
1165
|
function familyEntries(family, entries) {
|
|
@@ -5128,7 +1191,7 @@ async function dispatchFamily(family, argv) {
|
|
|
5128
1191
|
const entries = menu.cliEntries ?? [];
|
|
5129
1192
|
const entry = familyEntries(family, entries).find((e) => e.command === sub);
|
|
5130
1193
|
if (entry) {
|
|
5131
|
-
dispatchProgram(configForCliEntry(entry), argv);
|
|
1194
|
+
dispatchProgram(configForCliEntry(entry, family), argv);
|
|
5132
1195
|
return;
|
|
5133
1196
|
}
|
|
5134
1197
|
const available = [...Object.keys(NATIVE_HANDLERS[family] ?? {}), ...familyEntries(family, entries).map((e) => e.command)].sort();
|
|
@@ -5600,10 +1663,13 @@ function filterBindings(filter, setFilter) {
|
|
|
5600
1663
|
}];
|
|
5601
1664
|
}
|
|
5602
1665
|
/** The filter row above a filterable list: what's typed, and how much it left. */
|
|
5603
|
-
const FilterRow = ({ filter, shown, total }) => /* @__PURE__ */ jsx(Box, {
|
|
5604
|
-
|
|
5605
|
-
children:
|
|
5606
|
-
|
|
1666
|
+
const FilterRow = ({ filter, shown, total }) => /* @__PURE__ */ jsx(Box, {
|
|
1667
|
+
paddingLeft: 1,
|
|
1668
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1669
|
+
dimColor: true,
|
|
1670
|
+
children: filter ? `Filter: ${filter} (${shown} of ${total})` : `Type to filter ${total} options`
|
|
1671
|
+
})
|
|
1672
|
+
});
|
|
5607
1673
|
/** Custom single-select with triangle indicator and accent highlight. */
|
|
5608
1674
|
const SinglePickerMenu = ({ message, options, centered = false, columns = 1, optionMarginBottom = 0, filter, setFilter, totalCount, onSelect }) => {
|
|
5609
1675
|
const [focused, setFocused] = useState(() => firstEnabled(options));
|
|
@@ -6108,7 +2174,7 @@ async function runDoctorCI(options) {
|
|
|
6108
2174
|
getUI().intro("Welcome to the PostHog setup wizard");
|
|
6109
2175
|
getUI().log.info("Running posthog-doctor in CI mode");
|
|
6110
2176
|
try {
|
|
6111
|
-
const { getOrAskForProjectData } = await import("./setup-utils-
|
|
2177
|
+
const { getOrAskForProjectData } = await import("./setup-utils-BUAoRjw3.js").then((n) => n.r);
|
|
6112
2178
|
const { host, accessToken, projectId } = await getOrAskForProjectData({
|
|
6113
2179
|
signup: false,
|
|
6114
2180
|
ci: true,
|
|
@@ -6126,7 +2192,7 @@ async function runDoctorCI(options) {
|
|
|
6126
2192
|
for (const issue of sorted) getUI().log.info(` • [${issue.severity}] ${getKindMeta(issue.kind).title}`);
|
|
6127
2193
|
process.exit(1);
|
|
6128
2194
|
} catch (error) {
|
|
6129
|
-
const { ApiError } = await import("./api-
|
|
2195
|
+
const { ApiError } = await import("./api-k5aGQpIZ.js").then((n) => n.n);
|
|
6130
2196
|
const message = error instanceof ApiError && error.statusCode === 401 ? "Your PostHog API key is invalid or expired." : error instanceof Error ? error.message : String(error);
|
|
6131
2197
|
getUI().log.error(`Doctor failed: ${message}`);
|
|
6132
2198
|
emitWizardError({
|
|
@@ -6212,8 +2278,8 @@ function runSlackConnect(argv) {
|
|
|
6212
2278
|
(async () => {
|
|
6213
2279
|
const debug = argv.debug;
|
|
6214
2280
|
try {
|
|
6215
|
-
const { startTUI } = await import("./start-tui-
|
|
6216
|
-
const { buildSession } = await import("./wizard-session-
|
|
2281
|
+
const { startTUI } = await import("./start-tui-Bz23_yym.js");
|
|
2282
|
+
const { buildSession } = await import("./wizard-session-CYvyYnti.js");
|
|
6217
2283
|
const tui = startTUI(VERSION, Program.SlackConnect);
|
|
6218
2284
|
tui.store.session = buildSession({
|
|
6219
2285
|
debug,
|
|
@@ -6250,6 +2316,17 @@ const uploadSourcemapsCommand = {
|
|
|
6250
2316
|
}
|
|
6251
2317
|
};
|
|
6252
2318
|
//#endregion
|
|
2319
|
+
//#region src/commands/error-tracking.ts
|
|
2320
|
+
/**
|
|
2321
|
+
* `wizard error-tracking` — flat skill command, set up error tracking today.
|
|
2322
|
+
*
|
|
2323
|
+
* Wires up exception capture and — where the platform needs it — source-map /
|
|
2324
|
+
* debug-symbol upload. Runs the `error-tracking` orchestrator flow, which
|
|
2325
|
+
* reuses the integration-v2 install/init mini-agents when the repo has no
|
|
2326
|
+
* PostHog integration yet, so it works on uninstrumented projects too.
|
|
2327
|
+
*/
|
|
2328
|
+
const errorTrackingCommand = nativeCommandFactory(errorTrackingConfig);
|
|
2329
|
+
//#endregion
|
|
6253
2330
|
//#region src/commands/basic-integration/skill.ts
|
|
6254
2331
|
/** Run an arbitrary context-mill skill by id (`wizard skill <id>`, headless with `--ci`). */
|
|
6255
2332
|
function runSkillMode(argv) {
|
|
@@ -6463,7 +2540,7 @@ async function runCliAdd(argv) {
|
|
|
6463
2540
|
/** Resolve which instruction files to write, from flags, detection, or a prompt. */
|
|
6464
2541
|
async function resolveTargetFiles(argv) {
|
|
6465
2542
|
const ui = getUI();
|
|
6466
|
-
if (typeof argv.path === "string" && argv.path.trim()) return [path$
|
|
2543
|
+
if (typeof argv.path === "string" && argv.path.trim()) return [path$1.resolve(argv.path.trim())];
|
|
6467
2544
|
if (typeof argv.agent === "string") {
|
|
6468
2545
|
const target = findTarget(argv.agent);
|
|
6469
2546
|
if (!target) {
|
|
@@ -6556,8 +2633,8 @@ function resolveInstallDir() {
|
|
|
6556
2633
|
if (inline) return inline.slice(14);
|
|
6557
2634
|
return process.env.POSTHOG_WIZARD_INSTALL_DIR ?? process.cwd();
|
|
6558
2635
|
}
|
|
6559
|
-
Wizard.use(basicIntegrationCommand).use(mcpCommand).use(mcpAnalyticsCommand).use(replayVisionCommand).use(aiObservabilityCommand).use(metricsCommand).use(cliCommand).use(auditCommand).use(doctorCommand).use(migrateCommand).use(revenueCommand).use(warehouseCommand).use(selfDrivingCommand).use(slackCommand).use(uploadSourcemapsCommand).use(skillCommand).init();
|
|
2636
|
+
Wizard.use(basicIntegrationCommand).use(mcpCommand).use(mcpAnalyticsCommand).use(replayVisionCommand).use(aiObservabilityCommand).use(metricsCommand).use(cliCommand).use(auditCommand).use(doctorCommand).use(migrateCommand).use(revenueCommand).use(warehouseCommand).use(selfDrivingCommand).use(slackCommand).use(uploadSourcemapsCommand).use(errorTrackingCommand).use(skillCommand).init();
|
|
6560
2637
|
//#endregion
|
|
6561
|
-
export {
|
|
2638
|
+
export { useStdoutDimensions as a, runWizardHeadless as c, runWizard as d, useKeyboardHintsContext as i, runWizardCI as l, useKeyBindings as n, ConfirmButton as o, KeyboardHintsProvider as r, PromptLabel as s, PickerMenu as t, API_KEY_HINT as u };
|
|
6562
2639
|
|
|
6563
2640
|
//# sourceMappingURL=bin.js.map
|