@posthog/wizard 2.71.0 → 2.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +10 -2
  2. package/dist/{add-mcp-server-to-clients-B6-ZL--P.js → add-mcp-server-to-clients-B8t2xAPf.js} +4 -4
  3. package/dist/{add-mcp-server-to-clients-B6-ZL--P.js.map → add-mcp-server-to-clients-B8t2xAPf.js.map} +1 -1
  4. package/dist/{agent-interface-DFm4jB4d.js → agent-interface-tcCEmyPQ.js} +467 -382
  5. package/dist/agent-interface-tcCEmyPQ.js.map +1 -0
  6. package/dist/{agent-prompt-loader-Bi3IzSBW.js → agent-prompt-loader-V2smIztP.js} +5 -5
  7. package/dist/{agent-prompt-loader-Bi3IzSBW.js.map → agent-prompt-loader-V2smIztP.js.map} +1 -1
  8. package/dist/{agent-runner-Cp6iLqDk.js → agent-runner-DUyO1Ehj.js} +3178 -18
  9. package/dist/agent-runner-DUyO1Ehj.js.map +1 -0
  10. package/dist/analytics-DEL7clQK.js +2 -0
  11. package/dist/{analytics-D54mLptm.js → analytics-DsMH06jo.js} +3 -3
  12. package/dist/{analytics-D54mLptm.js.map → analytics-DsMH06jo.js.map} +1 -1
  13. package/dist/{api-BNBVOguz.js → api-R6bXIwII.js} +3 -3
  14. package/dist/{api-BNBVOguz.js.map → api-R6bXIwII.js.map} +1 -1
  15. package/dist/bin.js +56 -52
  16. package/dist/bin.js.map +1 -1
  17. package/dist/{bounded-fs-DHIaH3Sc.js → bounded-fs-BV-KhOtU.js} +3 -3
  18. package/dist/{bounded-fs-DHIaH3Sc.js.map → bounded-fs-BV-KhOtU.js.map} +1 -1
  19. package/dist/{ci-install-Can8LRdJ.js → ci-install-mR8CeIt2.js} +4 -4
  20. package/dist/{ci-install-Can8LRdJ.js.map → ci-install-mR8CeIt2.js.map} +1 -1
  21. package/dist/{debug-C4RPcEIX.js → debug-PfLVvLhT.js} +1 -1
  22. package/dist/{debug-YQdeZulR.js → debug-aXxgvtal.js} +50 -16
  23. package/dist/debug-aXxgvtal.js.map +1 -0
  24. package/dist/{env-api-key-9yogO8OI.js → env-api-key-_lBt9tHl.js} +2 -2
  25. package/dist/{env-api-key-9yogO8OI.js.map → env-api-key-_lBt9tHl.js.map} +1 -1
  26. package/dist/{environment-6ZuAWRpQ.js → environment-DRhZbhxU.js} +4 -4
  27. package/dist/{environment-6ZuAWRpQ.js.map → environment-DRhZbhxU.js.map} +1 -1
  28. package/dist/{file-watcher-CeWdvMVT.js → file-watcher-BZw1_Y84.js} +2 -2
  29. package/dist/{file-watcher-CeWdvMVT.js.map → file-watcher-BZw1_Y84.js.map} +1 -1
  30. package/dist/{headless-ui-BxSVgNoT.js → headless-ui-Cdqfufhk.js} +2 -2
  31. package/dist/{headless-ui-BxSVgNoT.js.map → headless-ui-Cdqfufhk.js.map} +1 -1
  32. package/dist/{interactive-CRR9zPl4.js → interactive-BCI9b-dp.js} +2 -2
  33. package/dist/{interactive-CRR9zPl4.js.map → interactive-BCI9b-dp.js.map} +1 -1
  34. package/dist/{mcp-D_rRzruj.js → mcp-CzB3lWsm.js} +2 -2
  35. package/dist/{mcp-D_rRzruj.js.map → mcp-CzB3lWsm.js.map} +1 -1
  36. package/dist/{mcp-prompt-streaming-DRAK8CMP.js → mcp-prompt-streaming-RZGAH4YO.js} +4 -4
  37. package/dist/{mcp-prompt-streaming-DRAK8CMP.js.map → mcp-prompt-streaming-RZGAH4YO.js.map} +1 -1
  38. package/dist/{non-interactive-1EJRlN6u.js → non-interactive-CgjTZZ9J.js} +2 -2
  39. package/dist/{non-interactive-1EJRlN6u.js.map → non-interactive-CgjTZZ9J.js.map} +1 -1
  40. package/dist/{orchestrator-tools-DjZWqWmd.js → orchestrator-tools-OWgPinUt.js} +21 -13
  41. package/dist/orchestrator-tools-OWgPinUt.js.map +1 -0
  42. package/dist/{package-manager-DMbzbdkL.js → package-manager-B-Z3IVYu.js} +2 -2
  43. package/dist/{package-manager-DMbzbdkL.js.map → package-manager-B-Z3IVYu.js.map} +1 -1
  44. package/dist/{pi-D4JQMC-7.js → pi-CLLkGrND.js} +11 -11
  45. package/dist/{pi-D4JQMC-7.js.map → pi-CLLkGrND.js.map} +1 -1
  46. package/dist/{playground-Y1nSGXpn.js → playground-D1TgX0EU.js} +9 -9
  47. package/dist/playground-D1TgX0EU.js.map +1 -0
  48. package/dist/{posthog-D9VwcWvJ.js → posthog-CMef8KLR.js} +2 -2
  49. package/dist/{posthog-D9VwcWvJ.js.map → posthog-CMef8KLR.js.map} +1 -1
  50. package/dist/posthog-integration-wiunwp8X.js +1475 -0
  51. package/dist/posthog-integration-wiunwp8X.js.map +1 -0
  52. package/dist/{provisioning-f7lA29tW.js → provisioning-94OneD64.js} +3 -3
  53. package/dist/{provisioning-f7lA29tW.js.map → provisioning-94OneD64.js.map} +1 -1
  54. package/dist/{queue-tools-DGaHf3n-.js → queue-tools--k2v8QYY.js} +108 -44
  55. package/dist/queue-tools--k2v8QYY.js.map +1 -0
  56. package/dist/{registry-D1By1SgH.js → registry-C60oJv02.js} +5 -5
  57. package/dist/{registry-D1By1SgH.js.map → registry-C60oJv02.js.map} +1 -1
  58. package/dist/{security-C2c3_Ycy.js → security-DnOP36w-.js} +5 -5
  59. package/dist/{security-C2c3_Ycy.js.map → security-DnOP36w-.js.map} +1 -1
  60. package/dist/{setup-utils-BRrCeajl.js → setup-utils-BwX6QmL_.js} +9 -9
  61. package/dist/{setup-utils-BRrCeajl.js.map → setup-utils-BwX6QmL_.js.map} +1 -1
  62. package/dist/{start-tui-r4Spgptv.js → start-tui-C3PdKFtP.js} +53 -32
  63. package/dist/start-tui-C3PdKFtP.js.map +1 -0
  64. package/dist/{steps-BZd65XfI.js → steps-BpCRk8Ef.js} +7 -7
  65. package/dist/{steps-BZd65XfI.js.map → steps-BpCRk8Ef.js.map} +1 -1
  66. package/dist/{store-DGVXu7xt.js → store-EzqWyb60.js} +7 -8
  67. package/dist/store-EzqWyb60.js.map +1 -0
  68. package/dist/{subagent-BaMxO7OT.js → subagent-DwdrVprk.js} +2 -2
  69. package/dist/{subagent-BaMxO7OT.js.map → subagent-DwdrVprk.js.map} +1 -1
  70. package/dist/{task-DpKoEZ5I.js → task-CrXgNEUB.js} +12 -12
  71. package/dist/{task-DpKoEZ5I.js.map → task-CrXgNEUB.js.map} +1 -1
  72. package/dist/{task-stream-CdrBraqP.js → task-stream-ZBbfHo7l.js} +5 -5
  73. package/dist/{task-stream-CdrBraqP.js.map → task-stream-ZBbfHo7l.js.map} +1 -1
  74. package/dist/{tasks-KsP66Cis.js → tasks-CbMEKWZ9.js} +2 -2
  75. package/dist/{tasks-KsP66Cis.js.map → tasks-CbMEKWZ9.js.map} +1 -1
  76. package/dist/{telemetry-DedDTtSa.js → telemetry-B2dz2-PT.js} +3 -3
  77. package/dist/{telemetry-DedDTtSa.js.map → telemetry-B2dz2-PT.js.map} +1 -1
  78. package/dist/{terminal-BH8yLclG.js → terminal-DTe59M4j.js} +12 -12
  79. package/dist/{terminal-BH8yLclG.js.map → terminal-DTe59M4j.js.map} +1 -1
  80. package/dist/{tools-CJw5vnZd.js → tools-DZ9lZgJI.js} +6 -6
  81. package/dist/{tools-CJw5vnZd.js.map → tools-DZ9lZgJI.js.map} +1 -1
  82. package/dist/{wizard-abort-D54j2gmb.js → wizard-abort-CUT8MXXt.js} +4 -4
  83. package/dist/{wizard-abort-D54j2gmb.js.map → wizard-abort-CUT8MXXt.js.map} +1 -1
  84. package/dist/{wizard-abort-Q1NiYELE.js → wizard-abort-CfKnrSlX.js} +1 -1
  85. package/dist/{wizard-session-Dwc1Rh_7.js → wizard-session-BWAQoi9q.js} +1 -1
  86. package/dist/{wizard-session-CwQPZN4y.js → wizard-session-GdDN_aMS.js} +2 -1
  87. package/dist/wizard-session-GdDN_aMS.js.map +1 -0
  88. package/dist/yara-hooks-CLeMu2EY.js +2 -0
  89. package/dist/{yara-hooks-CL2LVEgO.js → yara-hooks-VvwkZn5s.js} +7 -6
  90. package/dist/yara-hooks-VvwkZn5s.js.map +1 -0
  91. package/package.json +2 -1
  92. package/dist/agent-interface-DFm4jB4d.js.map +0 -1
  93. package/dist/agent-runner-Cp6iLqDk.js.map +0 -1
  94. package/dist/analytics-Ge1LsCNg.js +0 -2
  95. package/dist/debug-YQdeZulR.js.map +0 -1
  96. package/dist/orchestrator-tools-DjZWqWmd.js.map +0 -1
  97. package/dist/playground-Y1nSGXpn.js.map +0 -1
  98. package/dist/posthog-integration-B8f4xRBd.js +0 -4618
  99. package/dist/posthog-integration-B8f4xRBd.js.map +0 -1
  100. package/dist/queue-tools-DGaHf3n-.js.map +0 -1
  101. package/dist/start-tui-r4Spgptv.js.map +0 -1
  102. package/dist/store-DGVXu7xt.js.map +0 -1
  103. package/dist/wizard-session-CwQPZN4y.js.map +0 -1
  104. package/dist/yara-hooks-BI6WY0Sl.js +0 -2
  105. package/dist/yara-hooks-CL2LVEgO.js.map +0 -1
@@ -1,4618 +0,0 @@
1
- import { a as AgentSignals, l as ErrorCodes } from "./errors-u8z27V-x.js";
2
- import { L as Integration, N as HAIKU_MODEL, O as DETECTION_TIMEOUT_MS, _ as evaluateWizardReadiness, at as WIZARD_INTERACTION_EVENT_NAME, f as getUI, g as SIGNUP_WIZARD_READINESS_CONFIG, mt as getSkillsBaseUrl, s as logToFile, v as getBlockingServiceKeys, x as AGENTIC_DETECTION_TIMEOUT_MS } from "./debug-YQdeZulR.js";
3
- import { o as mayReportScanResults } from "./wizard-session-CwQPZN4y.js";
4
- import { n as analytics } from "./analytics-D54mLptm.js";
5
- import { i as withUtm, n as openTrackedLink } from "./telemetry-DedDTtSa.js";
6
- import { a as safeReadFile, i as readProjectFile, o as walkProjectFiles } from "./bounded-fs-DHIaH3Sc.js";
7
- import { i as tryGetPackageJson, n as isUsingTypeScript } from "./setup-utils-BRrCeajl.js";
8
- import { i as requestDeepLink } from "./provisioning-f7lA29tW.js";
9
- import { i as wizardAbort } from "./wizard-abort-D54j2gmb.js";
10
- import { E as WIZARD_TOOL_NAMES, H as isEnvFileName, U as parseEnvKeyNames, V as MAX_ENV_KEY_SET, W as toPromptSafeRelativePath, i as runAgent, n as buildRunTags, r as initializeAgent } from "./agent-interface-DFm4jB4d.js";
11
- import { n as runAgent$1, r as shouldDisableAsk, s as authenticate } from "./agent-runner-Cp6iLqDk.js";
12
- import { h as EVENT_PLAN_FILE } from "./yara-hooks-CL2LVEgO.js";
13
- import { a as detectNodePackageManagers } from "./package-manager-DMbzbdkL.js";
14
- import { i as SPINNER_MESSAGE, t as FRAMEWORK_REGISTRY } from "./registry-D1By1SgH.js";
15
- import * as semver from "semver";
16
- import { existsSync, statSync } from "fs";
17
- import { isAbsolute, join, resolve, sep } from "path";
18
- import { Text } from "ink";
19
- import { useEffect, useMemo, useRef, useState } from "react";
20
- import { jsx, jsxs } from "react/jsx-runtime";
21
- //#region src/lib/detection/framework.ts
22
- /**
23
- * Framework detection — identify which PostHog-supported framework
24
- * is present in the project directory.
25
- *
26
- * Pure function: takes an install dir, returns the detected integration
27
- * (or undefined). No store mutations, no UI calls.
28
- */
29
- /**
30
- * Loop through all registered frameworks and return the first one
31
- * whose `detect()` predicate matches the given directory.
32
- * Returns undefined if no framework is detected or detection times out.
33
- */
34
- async function detectFramework(installDir) {
35
- for (const integration of Object.values(Integration)) {
36
- const config = FRAMEWORK_REGISTRY[integration];
37
- try {
38
- if (await Promise.race([config.detection.detect({ installDir }), new Promise((resolve) => setTimeout(() => resolve(false), 1e4))])) return integration;
39
- } catch {}
40
- }
41
- }
42
- //#endregion
43
- //#region src/lib/detection/features.ts
44
- /**
45
- * Feature discovery — scan project dependencies for known SDK patterns
46
- * that indicate additional PostHog programs are relevant.
47
- *
48
- * Pure function: takes an install dir, returns a set of discovered features.
49
- * No store mutations, no UI calls.
50
- */
51
- const STRIPE_PACKAGES = new Set(["stripe", "@stripe/stripe-js"]);
52
- const LLM_PACKAGES = new Set([
53
- "openai",
54
- "@anthropic-ai/sdk",
55
- "ai",
56
- "@ai-sdk/openai",
57
- "langchain",
58
- "@langchain/openai",
59
- "@langchain/langgraph",
60
- "@google/generative-ai",
61
- "@google/genai",
62
- "@instructor-ai/instructor",
63
- "@mastra/core",
64
- "portkey-ai"
65
- ]);
66
- const PYTHON_LLM_PACKAGES = new Set([
67
- "openai",
68
- "anthropic",
69
- "langchain",
70
- "langchain-openai",
71
- "langchain-anthropic",
72
- "langchain-google-genai",
73
- "langgraph",
74
- "litellm",
75
- "llama-index",
76
- "pydantic-ai",
77
- "crewai",
78
- "instructor",
79
- "dspy-ai",
80
- "mistralai",
81
- "cohere",
82
- "google-generativeai",
83
- "google-genai",
84
- "portkey-ai"
85
- ]);
86
- function discoverFeatures(installDir) {
87
- const features = [];
88
- discoverNodeFeatures(installDir, features);
89
- discoverPythonFeatures(installDir, features);
90
- return features;
91
- }
92
- function discoverNodeFeatures(installDir, features) {
93
- const packageJsonText = safeRead(installDir, "package.json");
94
- if (!packageJsonText) return;
95
- let packageJson;
96
- try {
97
- packageJson = JSON.parse(packageJsonText);
98
- } catch {
99
- return;
100
- }
101
- const depNames = Object.keys({
102
- ...packageJson.dependencies,
103
- ...packageJson.devDependencies
104
- });
105
- if (depNames.some((depName) => STRIPE_PACKAGES.has(depName))) features.push("stripe");
106
- if (depNames.some((depName) => LLM_PACKAGES.has(depName))) features.push("llm");
107
- }
108
- function discoverPythonFeatures(installDir, features) {
109
- if (features.includes("llm")) return;
110
- const depNames = [];
111
- const requirementsTxt = safeRead(installDir, "requirements.txt");
112
- if (requirementsTxt) depNames.push(...parseRequirementsTxt(requirementsTxt));
113
- const pyprojectToml = safeRead(installDir, "pyproject.toml");
114
- if (pyprojectToml) depNames.push(...parsePyprojectToml(pyprojectToml));
115
- const pipfile = safeRead(installDir, "Pipfile");
116
- if (pipfile) depNames.push(...parsePipfile(pipfile));
117
- if (depNames.some((depName) => PYTHON_LLM_PACKAGES.has(depName))) features.push("llm");
118
- }
119
- function safeRead(installDir, file) {
120
- return readProjectFile(join(installDir, file));
121
- }
122
- function normalizePyName(name) {
123
- return name.toLowerCase().replace(/_/g, "-");
124
- }
125
- function parseRequirementsTxt(content) {
126
- return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("-")).map((line) => line.replace(/\[[^\]]*\]/, "")).map((line) => line.split(/[<>=!~;\s]/)[0]).filter(Boolean).map(normalizePyName);
127
- }
128
- function parsePyprojectToml(content) {
129
- const depNames = [];
130
- for (const arrayMatch of content.matchAll(/dependencies\s*=\s*\[([^\]]*)\]/g)) {
131
- const arrayBody = arrayMatch[1];
132
- for (const quotedMatch of arrayBody.matchAll(/["']([^"']+)["']/g)) {
133
- const name = quotedMatch[1].replace(/\[[^\]]*\]/, "").split(/[<>=!~;\s]/)[0];
134
- if (name) depNames.push(normalizePyName(name));
135
- }
136
- }
137
- for (const sectionMatch of content.matchAll(/\[tool\.poetry\.(?:dev-dependencies|dependencies|group\.[^.\]]+\.dependencies)\]([\s\S]*?)(?=\n\[|$)/g)) {
138
- const sectionBody = sectionMatch[1];
139
- depNames.push(...extractTomlSectionKeys(sectionBody, { skip: ["python"] }));
140
- }
141
- return depNames;
142
- }
143
- function parsePipfile(content) {
144
- const depNames = [];
145
- for (const sectionMatch of content.matchAll(/\[(packages|dev-packages)\]([\s\S]*?)(?=\n\[|$)/g)) {
146
- const sectionBody = sectionMatch[2];
147
- depNames.push(...extractTomlSectionKeys(sectionBody));
148
- }
149
- return depNames;
150
- }
151
- function extractTomlSectionKeys(sectionBody, opts = {}) {
152
- const skipNames = new Set(opts.skip ?? []);
153
- const depNames = [];
154
- for (const line of sectionBody.split("\n")) {
155
- const trimmed = line.trim();
156
- if (!trimmed || trimmed.startsWith("#")) continue;
157
- const equalsIndex = trimmed.indexOf("=");
158
- if (equalsIndex <= 0) continue;
159
- const name = trimmed.slice(0, equalsIndex).trim();
160
- if (!/^[a-zA-Z0-9._-]+$/.test(name)) continue;
161
- if (skipNames.has(name.toLowerCase())) continue;
162
- depNames.push(normalizePyName(name));
163
- }
164
- return depNames;
165
- }
166
- //#endregion
167
- //#region src/lib/detection/context.ts
168
- /**
169
- * Framework context gathering — run gatherContext and version checks
170
- * for a detected framework.
171
- *
172
- * Pure functions: take a framework config and options, return results.
173
- * No store mutations, no UI calls.
174
- */
175
- /**
176
- * Run a framework's `gatherContext()` to collect variant-specific
177
- * metadata (e.g., router type for Next.js, Expo vs bare for React Native).
178
- *
179
- * Returns the gathered context, or an empty object on failure/timeout.
180
- */
181
- async function gatherFrameworkContext(config, options) {
182
- if (!config.metadata.gatherContext) return {};
183
- try {
184
- return await Promise.race([config.metadata.gatherContext(options), new Promise((resolve) => setTimeout(() => resolve({}), DETECTION_TIMEOUT_MS))]);
185
- } catch {
186
- return {};
187
- }
188
- }
189
- /**
190
- * Check whether the installed framework version meets the minimum requirement.
191
- *
192
- * Returns `{ supported: true }` if the version is fine (or no check is needed).
193
- * Returns the version details if unsupported.
194
- */
195
- async function checkFrameworkVersion(config, options) {
196
- if (!config.detection.minimumVersion || !config.detection.getInstalledVersion) return { supported: true };
197
- const version = await config.detection.getInstalledVersion(options);
198
- if (!version) return { supported: true };
199
- const coerced = semver.coerce(version);
200
- if (coerced && semver.lt(coerced, config.detection.minimumVersion)) return { supported: {
201
- current: version,
202
- minimum: config.detection.minimumVersion,
203
- docsUrl: config.metadata.unsupportedVersionDocsUrl ?? config.metadata.docsUrl
204
- } };
205
- return { supported: true };
206
- }
207
- //#endregion
208
- //#region src/lib/detection/agentic.ts
209
- /**
210
- * Agentic project detection.
211
- *
212
- * A reusable detection tool that drives a Haiku agent over the repo: it finds
213
- * project roots, resolves monorepo/workspace markers, reads package manifests,
214
- * classifies each project against a caller-supplied set of targets, and reports
215
- * which projects already have a PostHog SDK installed.
216
- *
217
- * Product-knowledge-free by design — the caller passes the targets to classify
218
- * into (e.g. source-map skill variants) and maps the result back. Sits next to
219
- * the other detection tools (framework, features, package-manager) so it's
220
- * discoverable. Runs AFTER auth: it uses the same agent loop every program
221
- * uses, which needs credentials.
222
- */
223
- /**
224
- * Every project-manifest / workspace-marker filename the wizard's frameworks
225
- * are identified by. One brace-expansion Glob over all of these locates every
226
- * project root in the repo in a single call, regardless of language.
227
- *
228
- * Counterpart: POSTHOG_MANIFESTS in @lib/programs/self-driving/detect (SDK
229
- * grep); keep the two in sync.
230
- */
231
- const PROJECT_MANIFESTS = [
232
- "package.json",
233
- "pnpm-workspace.yaml",
234
- "turbo.json",
235
- "nx.json",
236
- "lerna.json",
237
- "requirements.txt",
238
- "pyproject.toml",
239
- "setup.py",
240
- "Pipfile",
241
- "manage.py",
242
- "Gemfile",
243
- "composer.json",
244
- "pom.xml",
245
- "Cargo.toml",
246
- "mix.exs",
247
- "go.mod",
248
- "*.csproj",
249
- "Package.swift",
250
- "Podfile",
251
- "project.yml",
252
- "project.pbxproj",
253
- "pubspec.yaml",
254
- "build.gradle",
255
- "build.gradle.kts",
256
- "settings.gradle",
257
- "settings.gradle.kts",
258
- "gradle/libs.versions.toml"
259
- ];
260
- /** The single brace-expansion glob covering every supported manifest. */
261
- function manifestGlob() {
262
- return `**/{${PROJECT_MANIFESTS.join(",")}}`;
263
- }
264
- function buildPrompt(cwd, targets, purpose, recommend) {
265
- const targetList = targets.map((t) => `- ${t.id} → ${t.name}`).join("\n");
266
- const projectShape = `{"path":string,"framework":string,"matchingTargets":string[],"targetId":string|null,"hasPostHog":boolean,"evidence":string${recommend ? ",\"recommended\":boolean" : ""}}`;
267
- return [
268
- `You are scanning a code repository to ${purpose}. Be fast and mechanical — do not over-explore. Keep any reasoning to one short sentence, then run the tool calls and output the JSON.`,
269
- "",
270
- `Working directory: ${cwd}`,
271
- "",
272
- "A \"project\" is a directory containing one or more of the manifest files below. Find projects by their manifests, NOT by walking directories — never report a directory that has no manifest.",
273
- "",
274
- "Do exactly this:",
275
- `1. Run Glob ONCE with this pattern to find every project manifest in the repo in a single call: "${manifestGlob()}". Discard any result whose path contains node_modules/, dist/, build/, .next/, out/, coverage/, vendor/, .venv/, site-packages/, target/, Pods/, Carthage/, or DerivedData/. Group the remaining results by directory — each directory is one project. Three exceptions to "directory = project": a project.pbxproj lives inside a "<Name>.xcodeproj/" wrapper, so the project root is the PARENT of that .xcodeproj directory; a project.yml at a directory root is an XcodeGen-generated Xcode app rooted at that directory; a gradle/libs.versions.toml is a version catalog belonging to the gradle project rooted at the PARENT of that gradle/ directory (read it alongside the build.gradle when deciding hasPostHog), never its own project.`,
276
- "2. Decide repoType: \"monorepo\" if the root package.json has a \"workspaces\" field OR a pnpm-workspace.yaml / turbo.json / nx.json / lerna.json was found at the root, else \"single\".",
277
- `3. For EACH project directory, ONE AT A TIME: Read its manifest(s) ONCE, decide the fields below, then IMMEDIATELY — before reading any other project — write that project's verdict as one JSON line of shape ${projectShape}. Never write a verdict from memory of an earlier Read; the manifest you just read is the only source. Decide from its dependency lists:`,
278
- " - the human-readable framework name (e.g. \"Next.js\", \"Django\", \"Rails\"),",
279
- " - matchingTargets: EVERY target id from the list below whose technology appears in THIS manifest, in the priority order of the list. An id belongs here only if you can point at the dependency or setting in the manifest you just read — never carry one over from another project,",
280
- " - targetId: the FIRST entry of matchingTargets (the list is ordered by priority, most specific first). null when matchingTargets is empty,",
281
- ` - hasPostHog: true if any dependency is a PostHog SDK. This includes: a name containing "posthog" in any ecosystem (e.g. posthog-js, posthog-node, @posthog/*, posthog for pip/gem/hex, a com.posthog:* gradle/maven coordinate, a PostHog NuGet PackageReference); an SPM package named "PostHog" or a repositoryURL of github.com/PostHog/posthog-ios (in Package.swift or a .pbxproj); or a "pod 'PostHog'" line in a Podfile. Else false.`,
282
- " - evidence: the manifest fact that decided targetId, with the file it came from (e.g. \"rollup in devDependencies of backend/package.json\"). When targetId is null, name the closest fact you saw. One short clause, quoted from the manifest you just read.",
283
- " Do NOT read any file other than these manifests.",
284
- ...recommend ? ["4. Pick exactly ONE project as recommended — the main user-facing client application. Prefer a frontend web app or a mobile app over backend servers/APIs, libraries, CLIs, tooling, and example/demo/docs apps. If several client apps exist, pick the primary production app; if no client app exists, pick the primary application project."] : [],
285
- "",
286
- "Target ids (id → name), in priority order — most specific first; if several match a project, keep the one listed earliest:",
287
- targetList,
288
- "",
289
- "Output requirements:",
290
- "- After the last verdict line, respond with ONLY a single JSON object assembling the verdict lines you wrote, copied VERBATIM — same path, framework, targetId and evidence per project. No prose, no markdown code fences.",
291
- `- Shape: {"repoType":"monorepo"|"single","projects":[${projectShape}]}`,
292
- "- \"path\" is the project directory relative to the working directory; use \".\" for the repo root.",
293
- "- \"targetId\" MUST be exactly one of the target ids above when it matches; if several match, use the one listed earliest; otherwise null.",
294
- "- Include projects whose stack matches no target too (targetId: null).",
295
- ...recommend ? ["- Exactly one project has \"recommended\": true; every other project has \"recommended\": false."] : [],
296
- `- If there are no manifests at all, respond with exactly: ${AgentSignals.ABORT} detection failed`
297
- ].join("\n");
298
- }
299
- /**
300
- * Build the detection report from the agent's output, or null when it holds
301
- * no verdicts. Exported for testing.
302
- *
303
- * The verdict lines are far more reliable than the model's final assembly
304
- * (prose, pretty-printing, per-object fences), so every parseable line
305
- * contributes: objects with a `path` merge by path (last wins), and a
306
- * one-line assembly's `projects` merge the same way. `repoType` is
307
- * approximated from the count — it only feeds telemetry and display.
308
- */
309
- function deriveReportJson(text) {
310
- const byPath = /* @__PURE__ */ new Map();
311
- for (const line of text.split("\n")) {
312
- const start = line.indexOf("{");
313
- const end = line.lastIndexOf("}");
314
- if (start === -1 || end <= start) continue;
315
- let parsed;
316
- try {
317
- parsed = JSON.parse(line.slice(start, end + 1));
318
- } catch {
319
- continue;
320
- }
321
- const obj = parsed ?? {};
322
- const candidates = Array.isArray(obj.projects) ? obj.projects : [obj];
323
- for (const candidate of candidates) {
324
- const p = candidate ?? {};
325
- if (typeof p.path === "string") byPath.set(p.path, p);
326
- }
327
- }
328
- if (byPath.size === 0) return null;
329
- const projects = [...byPath.values()];
330
- return {
331
- repoType: projects.length > 1 ? "monorepo" : "single",
332
- projects
333
- };
334
- }
335
- /**
336
- * Clamp an agent-reported project path to a safe repo-relative dir. The path
337
- * is LLM output: absolute paths or `..` segments could steer later phases
338
- * (e.g. integrate-run's targetDir) outside the repo, so they clamp to '.'.
339
- */
340
- function coercePath(raw) {
341
- if (typeof raw !== "string" || raw.length === 0) return ".";
342
- if (isAbsolute(raw) || raw.split(/[\\/]/).includes("..")) return ".";
343
- return raw;
344
- }
345
- /** Resolve an agent-reported path to an absolute dir clamped to the root — the caller-side counterpart of coercePath. */
346
- function resolveProjectDir(installDir, rel) {
347
- if (typeof rel !== "string" || rel === ".") return installDir;
348
- const root = resolve(installDir);
349
- const dir = resolve(root, rel);
350
- return dir === root || dir.startsWith(root + sep) ? dir : root;
351
- }
352
- /**
353
- * Validate the agent's raw JSON into a report, clamping each targetId to the
354
- * supplied set and each path to a repo-relative dir. Exported for testing.
355
- * `recommended` is stripped unless requested; at most one (the first) survives.
356
- */
357
- function coerceAgenticReport(parsed, validTargetIds, options) {
358
- const recommend = options?.recommend === true;
359
- const rerankIds = options?.rerankIds ?? [];
360
- const obj = parsed ?? {};
361
- const repoType = obj.repoType === "monorepo" ? "monorepo" : "single";
362
- const rawProjects = Array.isArray(obj.projects) ? obj.projects : [];
363
- let recommendedSeen = false;
364
- return {
365
- repoType,
366
- projects: rawProjects.map((raw) => {
367
- const p = raw ?? {};
368
- const pick = typeof p.targetId === "string" && validTargetIds.includes(p.targetId) ? p.targetId : null;
369
- const enumerated = Array.isArray(p.matchingTargets) ? validTargetIds.find((id) => p.matchingTargets.includes(id)) ?? null : null;
370
- const targetId = pick === null ? enumerated : enumerated !== null && rerankIds.includes(enumerated) && rerankIds.includes(pick) ? enumerated : pick;
371
- const recommended = recommend && !recommendedSeen && p.recommended === true;
372
- recommendedSeen ||= recommended;
373
- return {
374
- path: coercePath(p.path),
375
- framework: typeof p.framework === "string" ? p.framework : "Unknown",
376
- targetId,
377
- hasPostHog: p.hasPostHog === true,
378
- ...recommend ? { recommended } : {}
379
- };
380
- })
381
- };
382
- }
383
- function formatToolUse(block) {
384
- const name = typeof block?.name === "string" ? block.name : "tool";
385
- const input = block?.input ?? {};
386
- const detail = input.file_path || input.pattern || input.path || "";
387
- return detail ? `${name} ${detail}` : name;
388
- }
389
- function sessionToWizardOptions(session) {
390
- return {
391
- installDir: session.installDir,
392
- ci: session.ci,
393
- debug: session.debug,
394
- benchmark: session.benchmark,
395
- yaraReport: session.yaraReport,
396
- signup: session.signup,
397
- apiKey: session.apiKey,
398
- projectId: session.projectId
399
- };
400
- }
401
- const NOOP_SPINNER = {
402
- start: () => void 0,
403
- stop: () => void 0,
404
- message: () => void 0
405
- };
406
- /**
407
- * Drive the wizard's agent loop on HAIKU_MODEL to scan the repo and return a
408
- * structured detection report. Reuses the same setup every program uses, so
409
- * MCP, tools, and credentials are wired identically.
410
- */
411
- async function detectProjectsWithAgent(session, options) {
412
- if (!session.credentials) throw new Error("Detection requires authenticated credentials.");
413
- const { targets, programId, purpose = "set up a PostHog integration", recommend = false, rerankIds, onEvent } = options;
414
- const { accessToken, host } = session.credentials;
415
- const cwd = session.installDir;
416
- const runOptions = sessionToWizardOptions(session);
417
- const wizardMetadata = {
418
- ...buildRunTags({
419
- programId,
420
- integration: "agentic-detect",
421
- runId: analytics.runId,
422
- build: analytics.build
423
- }),
424
- call_type: "detection"
425
- };
426
- const agent = await initializeAgent({
427
- workingDirectory: cwd,
428
- posthogMcpUrl: host.mcpUrl,
429
- posthogApiKey: accessToken,
430
- host,
431
- detectPackageManager: detectNodePackageManagers,
432
- skillsBaseUrl: getSkillsBaseUrl(),
433
- programId,
434
- integrationLabel: "agentic-detect",
435
- wizardMetadata,
436
- allowedTools: [
437
- "Read",
438
- "Grep",
439
- "Glob"
440
- ],
441
- modelOverride: HAIKU_MODEL
442
- }, runOptions);
443
- const MAX_TRANSCRIPT_CHARS = 256 * 1024;
444
- const collected = [];
445
- let collectedChars = 0;
446
- const collect = (text) => {
447
- collected.push(text);
448
- collectedChars += text.length;
449
- while (collectedChars > MAX_TRANSCRIPT_CHARS && collected.length > 1) collectedChars -= collected.shift().length;
450
- };
451
- let resultText = "";
452
- const result = await runAgent(agent, buildPrompt(cwd, targets, purpose, recommend), runOptions, NOOP_SPINNER, {
453
- spinnerMessage: "Scanning the repo...",
454
- successMessage: "Detection complete",
455
- errorMessage: "Detection failed",
456
- requestRemark: false
457
- }, {
458
- onMessage: (message) => {
459
- if (message?.type === "assistant") {
460
- for (const block of message.message?.content ?? []) if (block?.type === "text" && typeof block.text === "string") {
461
- collect(block.text);
462
- const line = block.text.trim();
463
- if (line && onEvent) onEvent(line.length > 100 ? `${line.slice(0, 100)}…` : line);
464
- } else if (block?.type === "tool_use") onEvent?.(formatToolUse(block));
465
- } else if (message?.type === "result" && typeof message.result === "string") resultText = message.result;
466
- },
467
- finalize: (_resultMessage, _durationMs) => void 0
468
- });
469
- if (result.error) throw new Error(result.message || `Agent error: ${result.error}`);
470
- const output = `${collected.join("\n")}\n${resultText}`;
471
- const derived = deriveReportJson(output);
472
- if (derived === null) {
473
- if (output.includes(AgentSignals.ABORT)) return {
474
- repoType: "single",
475
- projects: []
476
- };
477
- throw new Error("Agent did not return a JSON object");
478
- }
479
- return coerceAgenticReport(derived, targets.map((t) => t.id), {
480
- recommend,
481
- rerankIds
482
- });
483
- }
484
- //#endregion
485
- //#region src/lib/detection/project-scope.ts
486
- /** Non-interactive project scoping — scan the repo agentically and pick which project the run integrates. */
487
- /** Integration framework targets for the agentic detector (id → display name). */
488
- const INTEGRATION_TARGETS = Object.entries(FRAMEWORK_REGISTRY).map(([id, config]) => ({
489
- id,
490
- name: config.metadata.name
491
- }));
492
- /** Run the agentic detector for the wizard's integration frameworks — the single home of targets + purpose. */
493
- async function detectIntegrationProjects(session, options) {
494
- return detectProjectsWithAgent(session, {
495
- ...options,
496
- targets: INTEGRATION_TARGETS,
497
- purpose: "set up a PostHog SDK integration"
498
- });
499
- }
500
- /** Pick the recommended-if-supported project (even with PostHog — the main app wins), else the first supported PostHog-free one. */
501
- function chooseIntegrationProject(projects) {
502
- return projects.find((p) => p.recommended === true && p.targetId != null) ?? projects.find((p) => p.targetId != null && !p.hasPostHog);
503
- }
504
- /** Sentinel for a scan that outran AGENTIC_DETECTION_TIMEOUT_MS. */
505
- const TIMED_OUT = Symbol("timed-out");
506
- function captureOutcome(outcome, properties = {}) {
507
- analytics.wizardCapture("agentic detection", {
508
- outcome,
509
- ...properties
510
- });
511
- }
512
- /** Flag-gated non-interactive monorepo phase: scan, auto-choose the recommended project, re-point session.installDir; every failure leaves the session untouched. */
513
- async function scopeInstallDirToProject(session) {
514
- await authenticate(session, "posthog-integration");
515
- if ((await analytics.getAllFlagsForWizard())["wizard-basic-integration-agentic-detection"] !== "true") {
516
- captureOutcome("flag-off");
517
- return;
518
- }
519
- getUI().log.info("Scanning the repo for projects...");
520
- const startedAt = Date.now();
521
- let report;
522
- try {
523
- report = await Promise.race([detectIntegrationProjects(session, {
524
- programId: "posthog-integration",
525
- recommend: true,
526
- onEvent: (line) => logToFile("[agentic detect]", line)
527
- }), new Promise((resolve) => setTimeout(() => resolve(TIMED_OUT), AGENTIC_DETECTION_TIMEOUT_MS))]);
528
- } catch (err) {
529
- const error = err instanceof Error ? err : new Error(String(err));
530
- analytics.captureException(error, { step: "agentic_detection" });
531
- captureOutcome("error", {
532
- duration_ms: Date.now() - startedAt,
533
- error_message: error.message
534
- });
535
- getUI().log.warn(`Project scan failed (${error.message}); continuing with the install dir as-is.`);
536
- return;
537
- }
538
- if (report === TIMED_OUT) {
539
- captureOutcome("timeout", { duration_ms: Date.now() - startedAt });
540
- getUI().log.warn(`Project scan timed out after ${AGENTIC_DETECTION_TIMEOUT_MS / 1e3}s; continuing with the install dir as-is.`);
541
- return;
542
- }
543
- const { projects } = report;
544
- const recommended = projects.find((p) => p.recommended === true);
545
- const scanProperties = {
546
- duration_ms: Date.now() - startedAt,
547
- repo_type: report.repoType,
548
- project_count: projects.length,
549
- supported_count: projects.filter((p) => p.targetId != null).length,
550
- has_recommendation: recommended !== void 0,
551
- recommended_path: recommended?.path ?? null,
552
- projects: projects.slice(0, 25)
553
- };
554
- const project = chooseIntegrationProject(projects);
555
- if (!project) {
556
- captureOutcome("no-project", scanProperties);
557
- getUI().log.info("The scan found no supported project; continuing with the install dir as-is.");
558
- return;
559
- }
560
- session.installDir = resolveProjectDir(session.installDir, project.path);
561
- captureOutcome(project.recommended ? "recommended" : "first-instrumentable", {
562
- ...scanProperties,
563
- chosen_framework: project.targetId,
564
- chosen_path: project.path
565
- });
566
- getUI().log.info(`Continuing with ${project.path} (${project.framework}).`);
567
- }
568
- //#endregion
569
- //#region src/lib/warehouse-sources/registry.ts
570
- const CORE_SOURCE_DETECTORS = [
571
- {
572
- kind: "Postgres",
573
- label: "PostgreSQL",
574
- mode: "in-cli",
575
- signals: {
576
- npm: [
577
- "pg",
578
- "postgres",
579
- "postgres.js",
580
- "knex",
581
- "sequelize"
582
- ],
583
- python: [
584
- "psycopg",
585
- "psycopg2",
586
- "psycopg2-binary",
587
- "asyncpg"
588
- ],
589
- ruby: ["pg"],
590
- envKeys: [
591
- /^DATABASE_URL$/,
592
- /^POSTGRES_/,
593
- /^PG(HOST|DATABASE|USER|PORT)$/
594
- ]
595
- }
596
- },
597
- {
598
- kind: "MySQL",
599
- label: "MySQL",
600
- mode: "in-cli",
601
- signals: {
602
- npm: ["mysql", "mysql2"],
603
- python: [
604
- "pymysql",
605
- "mysqlclient",
606
- "mysql-connector-python"
607
- ],
608
- ruby: ["mysql2"],
609
- envKeys: [/^MYSQL_/]
610
- }
611
- },
612
- {
613
- kind: "MongoDB",
614
- label: "MongoDB",
615
- mode: "in-cli",
616
- signals: {
617
- npm: ["mongodb", "mongoose"],
618
- python: ["pymongo", "motor"],
619
- ruby: ["mongo", "mongoid"],
620
- envKeys: [/^MONGO(DB)?_/]
621
- }
622
- },
623
- {
624
- kind: "Snowflake",
625
- label: "Snowflake",
626
- mode: "in-cli",
627
- signals: {
628
- npm: ["snowflake-sdk"],
629
- python: ["snowflake-connector-python", "snowflake-sqlalchemy"],
630
- envKeys: [/^SNOWFLAKE_/]
631
- }
632
- },
633
- {
634
- kind: "BigQuery",
635
- label: "BigQuery",
636
- mode: "in-cli",
637
- signals: {
638
- npm: ["@google-cloud/bigquery"],
639
- python: ["google-cloud-bigquery"],
640
- envKeys: [/^BIGQUERY_/]
641
- }
642
- },
643
- {
644
- kind: "Redshift",
645
- label: "Redshift",
646
- mode: "in-cli",
647
- signals: {
648
- npm: ["node-redshift"],
649
- python: ["redshift-connector"],
650
- envKeys: [/^REDSHIFT_/]
651
- }
652
- },
653
- {
654
- kind: "MSSQL",
655
- label: "SQL Server",
656
- mode: "in-cli",
657
- signals: {
658
- npm: ["mssql", "tedious"],
659
- python: ["pyodbc", "pymssql"],
660
- envKeys: [/^MSSQL_/]
661
- }
662
- },
663
- {
664
- kind: "Supabase",
665
- label: "Supabase",
666
- mode: "in-cli",
667
- signals: {
668
- npm: ["@supabase/supabase-js"],
669
- python: ["supabase"],
670
- envKeys: [/^SUPABASE_/]
671
- }
672
- },
673
- {
674
- kind: "ClickHouse",
675
- label: "ClickHouse",
676
- mode: "in-cli",
677
- signals: {
678
- npm: ["@clickhouse/client"],
679
- python: ["clickhouse-connect", "clickhouse-driver"],
680
- envKeys: [/^CLICKHOUSE_/]
681
- }
682
- },
683
- {
684
- kind: "Convex",
685
- label: "Convex",
686
- mode: "in-cli",
687
- signals: {
688
- npm: ["convex"],
689
- python: ["convex"],
690
- envKeys: [/^CONVEX_/, /^NEXT_PUBLIC_CONVEX_URL$/]
691
- }
692
- },
693
- {
694
- kind: "Firebase",
695
- label: "Firebase",
696
- mode: "deep-link",
697
- signals: {
698
- npm: ["firebase-admin", "firebase"],
699
- python: ["firebase-admin"],
700
- envKeys: [/^FIREBASE_/, /^NEXT_PUBLIC_FIREBASE_/]
701
- }
702
- },
703
- {
704
- kind: "Neon",
705
- label: "Neon",
706
- mode: "in-cli",
707
- signals: {
708
- npm: ["@neondatabase/serverless"],
709
- envKeys: [/^NEON_/]
710
- }
711
- },
712
- {
713
- kind: "PlanetScaleMySQL",
714
- label: "PlanetScale MySQL",
715
- mode: "in-cli",
716
- signals: {
717
- npm: ["@planetscale/database"],
718
- envKeys: [/^PLANETSCALE_/]
719
- }
720
- },
721
- {
722
- kind: "DynamoDB",
723
- label: "DynamoDB",
724
- mode: "in-cli",
725
- signals: {
726
- npm: ["@aws-sdk/client-dynamodb", "@aws-sdk/lib-dynamodb"],
727
- python: ["pynamodb"]
728
- }
729
- },
730
- {
731
- kind: "Motherduck",
732
- label: "MotherDuck",
733
- mode: "in-cli",
734
- signals: {
735
- npm: ["@motherduck/wasm-client"],
736
- envKeys: [/^MOTHERDUCK_/]
737
- }
738
- },
739
- {
740
- kind: "Databricks",
741
- label: "Databricks",
742
- mode: "in-cli",
743
- signals: {
744
- npm: ["@databricks/sql"],
745
- python: ["databricks-sql-connector", "databricks-sdk"],
746
- envKeys: [/^DATABRICKS_/]
747
- }
748
- },
749
- {
750
- kind: "Singlestore",
751
- label: "SingleStore",
752
- mode: "in-cli",
753
- signals: {
754
- python: ["singlestoredb"],
755
- envKeys: [/^SINGLESTORE_/]
756
- }
757
- },
758
- {
759
- kind: "Stripe",
760
- label: "Stripe",
761
- mode: "in-cli",
762
- signals: {
763
- npm: [
764
- "stripe",
765
- "@stripe/stripe-js",
766
- "@stripe/react-stripe-js"
767
- ],
768
- python: ["stripe"],
769
- ruby: ["stripe"],
770
- envKeys: [/^STRIPE_(SECRET|API)_KEY$/]
771
- }
772
- },
773
- {
774
- kind: "Clerk",
775
- label: "Clerk",
776
- mode: "in-cli",
777
- signals: {
778
- npm: [
779
- "@clerk/nextjs",
780
- "@clerk/clerk-react",
781
- "@clerk/backend",
782
- "@clerk/express",
783
- "@clerk/fastify",
784
- "@clerk/remix"
785
- ],
786
- envKeys: [/^CLERK_SECRET_KEY$/, /^NEXT_PUBLIC_CLERK_/]
787
- }
788
- },
789
- {
790
- kind: "Resend",
791
- label: "Resend",
792
- mode: "in-cli",
793
- signals: {
794
- npm: ["resend"],
795
- python: ["resend"],
796
- envKeys: [/^RESEND_API_KEY$/]
797
- }
798
- },
799
- {
800
- kind: "Shopify",
801
- label: "Shopify",
802
- mode: "in-cli",
803
- signals: {
804
- npm: ["@shopify/shopify-api", "shopify-api-node"],
805
- python: ["shopifyapi"],
806
- envKeys: [/^SHOPIFY_/]
807
- }
808
- },
809
- {
810
- kind: "Klaviyo",
811
- label: "Klaviyo",
812
- mode: "in-cli",
813
- signals: {
814
- npm: ["klaviyo-api"],
815
- python: ["klaviyo-api"],
816
- envKeys: [/^KLAVIYO_/]
817
- }
818
- },
819
- {
820
- kind: "Chargebee",
821
- label: "Chargebee",
822
- mode: "in-cli",
823
- signals: {
824
- npm: ["chargebee"],
825
- python: ["chargebee"],
826
- envKeys: [/^CHARGEBEE_/]
827
- }
828
- },
829
- {
830
- kind: "Paddle",
831
- label: "Paddle",
832
- mode: "in-cli",
833
- signals: {
834
- npm: ["@paddle/paddle-node-sdk", "@paddle/paddle-js"],
835
- envKeys: [/^PADDLE_/]
836
- }
837
- },
838
- {
839
- kind: "Polar",
840
- label: "Polar",
841
- mode: "in-cli",
842
- signals: {
843
- npm: ["@polar-sh/sdk", "@polar-sh/nextjs"],
844
- envKeys: [/^POLAR_/]
845
- }
846
- },
847
- {
848
- kind: "Mailchimp",
849
- label: "Mailchimp",
850
- mode: "in-cli",
851
- signals: {
852
- npm: ["@mailchimp/mailchimp_marketing"],
853
- python: ["mailchimp-marketing"],
854
- envKeys: [/^MAILCHIMP_/]
855
- }
856
- },
857
- {
858
- kind: "CustomerIO",
859
- label: "Customer.io",
860
- mode: "in-cli",
861
- signals: {
862
- npm: ["customerio-node"],
863
- python: ["customerio"],
864
- envKeys: [/^CUSTOMER_?IO_/]
865
- }
866
- },
867
- {
868
- kind: "Typeform",
869
- label: "Typeform",
870
- mode: "in-cli",
871
- signals: {
872
- npm: ["@typeform/api-client"],
873
- envKeys: [/^TYPEFORM_/]
874
- }
875
- },
876
- {
877
- kind: "Sentry",
878
- label: "Sentry",
879
- mode: "in-cli",
880
- signals: {
881
- npm: [
882
- "@sentry/node",
883
- "@sentry/browser",
884
- "@sentry/react",
885
- "@sentry/nextjs"
886
- ],
887
- python: ["sentry-sdk"],
888
- ruby: ["sentry-ruby"]
889
- }
890
- },
891
- {
892
- kind: "Plaid",
893
- label: "Plaid",
894
- mode: "in-cli",
895
- signals: {
896
- npm: ["plaid"],
897
- python: ["plaid-python", "plaid"],
898
- ruby: ["plaid"],
899
- envKeys: [/^PLAID_/]
900
- }
901
- },
902
- {
903
- kind: "Braintree",
904
- label: "Braintree",
905
- mode: "in-cli",
906
- signals: {
907
- npm: ["braintree"],
908
- python: ["braintree"],
909
- ruby: ["braintree"],
910
- envKeys: [/^BRAINTREE_/]
911
- }
912
- },
913
- {
914
- kind: "Square",
915
- label: "Square",
916
- mode: "in-cli",
917
- signals: {
918
- npm: ["square"],
919
- python: ["squareup"],
920
- ruby: ["square"],
921
- envKeys: [/^SQUARE_/]
922
- }
923
- },
924
- {
925
- kind: "GoCardless",
926
- label: "GoCardless",
927
- mode: "in-cli",
928
- signals: {
929
- npm: ["gocardless-nodejs"],
930
- python: ["gocardless-pro"],
931
- ruby: ["gocardless_pro"],
932
- envKeys: [/^GOCARDLESS_/]
933
- }
934
- },
935
- {
936
- kind: "Mollie",
937
- label: "Mollie",
938
- mode: "in-cli",
939
- signals: {
940
- npm: ["@mollie/api-client"],
941
- python: ["mollie-api-python"],
942
- envKeys: [/^MOLLIE_/]
943
- }
944
- },
945
- {
946
- kind: "CheckoutCom",
947
- label: "Checkout.com",
948
- mode: "in-cli",
949
- signals: {
950
- npm: ["checkout-sdk-node"],
951
- python: ["checkout-sdk"],
952
- envKeys: [/^CKO_/, /^CHECKOUT_COM_/]
953
- }
954
- },
955
- {
956
- kind: "Recurly",
957
- label: "Recurly",
958
- mode: "in-cli",
959
- signals: {
960
- npm: ["recurly"],
961
- python: ["recurly"],
962
- ruby: ["recurly"],
963
- envKeys: [/^RECURLY_/]
964
- }
965
- },
966
- {
967
- kind: "RevenueCat",
968
- label: "RevenueCat",
969
- mode: "in-cli",
970
- signals: {
971
- npm: [
972
- "@revenuecat/purchases-js",
973
- "react-native-purchases",
974
- "@revenuecat/purchases-capacitor"
975
- ],
976
- envKeys: [/^REVENUE_?CAT_/]
977
- }
978
- },
979
- {
980
- kind: "Twilio",
981
- label: "Twilio",
982
- mode: "in-cli",
983
- signals: {
984
- npm: ["twilio"],
985
- python: ["twilio"],
986
- ruby: ["twilio-ruby"],
987
- envKeys: [/^TWILIO_/]
988
- }
989
- },
990
- {
991
- kind: "SendGrid",
992
- label: "SendGrid",
993
- mode: "in-cli",
994
- signals: {
995
- npm: ["@sendgrid/mail", "@sendgrid/client"],
996
- python: ["sendgrid"],
997
- ruby: ["sendgrid-ruby"],
998
- envKeys: [/^SENDGRID_/]
999
- }
1000
- },
1001
- {
1002
- kind: "Mailgun",
1003
- label: "Mailgun",
1004
- mode: "in-cli",
1005
- signals: {
1006
- npm: ["mailgun.js", "mailgun-js"],
1007
- python: ["mailgun"],
1008
- ruby: ["mailgun-ruby"],
1009
- envKeys: [/^MAILGUN_/]
1010
- }
1011
- },
1012
- {
1013
- kind: "Postmark",
1014
- label: "Postmark",
1015
- mode: "in-cli",
1016
- signals: {
1017
- npm: ["postmark"],
1018
- python: ["postmarker"],
1019
- ruby: ["postmark"],
1020
- envKeys: [/^POSTMARK_/]
1021
- }
1022
- },
1023
- {
1024
- kind: "Brevo",
1025
- label: "Brevo",
1026
- mode: "in-cli",
1027
- signals: {
1028
- npm: ["@getbrevo/brevo", "sib-api-v3-sdk"],
1029
- python: ["sib-api-v3-sdk"],
1030
- envKeys: [/^BREVO_/]
1031
- }
1032
- },
1033
- {
1034
- kind: "MailerLite",
1035
- label: "MailerLite",
1036
- mode: "in-cli",
1037
- signals: {
1038
- npm: ["@mailerlite/mailerlite-nodejs"],
1039
- python: ["mailerlite"],
1040
- envKeys: [/^MAILERLITE_/]
1041
- }
1042
- },
1043
- {
1044
- kind: "Mailjet",
1045
- label: "Mailjet",
1046
- mode: "in-cli",
1047
- signals: {
1048
- npm: ["node-mailjet"],
1049
- python: ["mailjet-rest"],
1050
- envKeys: [/^MAILJET_/, /^MJ_APIKEY_/]
1051
- }
1052
- },
1053
- {
1054
- kind: "LaunchDarkly",
1055
- label: "LaunchDarkly",
1056
- mode: "in-cli",
1057
- signals: {
1058
- npm: [
1059
- "launchdarkly-node-server-sdk",
1060
- "@launchdarkly/node-server-sdk",
1061
- "launchdarkly-js-client-sdk",
1062
- "launchdarkly-react-client-sdk"
1063
- ],
1064
- python: ["launchdarkly-server-sdk"],
1065
- ruby: ["launchdarkly-server-sdk"],
1066
- envKeys: [/^LAUNCHDARKLY_/, /^LD_SDK_KEY$/]
1067
- }
1068
- },
1069
- {
1070
- kind: "Optimizely",
1071
- label: "Optimizely",
1072
- mode: "in-cli",
1073
- signals: {
1074
- npm: ["@optimizely/optimizely-sdk"],
1075
- python: ["optimizely-sdk"],
1076
- envKeys: [/^OPTIMIZELY_/]
1077
- }
1078
- },
1079
- {
1080
- kind: "Braze",
1081
- label: "Braze",
1082
- mode: "in-cli",
1083
- signals: {
1084
- npm: ["@braze/web-sdk", "@braze/react-native-sdk"],
1085
- python: ["braze-client"],
1086
- envKeys: [/^BRAZE_/]
1087
- }
1088
- },
1089
- {
1090
- kind: "Rollbar",
1091
- label: "Rollbar",
1092
- mode: "in-cli",
1093
- signals: {
1094
- npm: ["rollbar"],
1095
- python: ["rollbar", "pyrollbar"],
1096
- ruby: ["rollbar"],
1097
- envKeys: [/^ROLLBAR_/]
1098
- }
1099
- },
1100
- {
1101
- kind: "Okta",
1102
- label: "Okta",
1103
- mode: "in-cli",
1104
- signals: {
1105
- npm: [
1106
- "@okta/okta-sdk-nodejs",
1107
- "@okta/okta-auth-js",
1108
- "@okta/okta-react"
1109
- ],
1110
- python: ["okta"],
1111
- envKeys: [/^OKTA_/]
1112
- }
1113
- },
1114
- {
1115
- kind: "WorkOS",
1116
- label: "WorkOS",
1117
- mode: "in-cli",
1118
- signals: {
1119
- npm: ["@workos-inc/node"],
1120
- python: ["workos"],
1121
- envKeys: [/^WORKOS_/]
1122
- }
1123
- },
1124
- {
1125
- kind: "Notion",
1126
- label: "Notion",
1127
- mode: "in-cli",
1128
- signals: {
1129
- npm: ["@notionhq/client"],
1130
- python: ["notion-client"],
1131
- envKeys: [/^NOTION_/]
1132
- }
1133
- },
1134
- {
1135
- kind: "FullStory",
1136
- label: "FullStory",
1137
- mode: "in-cli",
1138
- signals: {
1139
- npm: ["@fullstory/browser", "@fullstory/react-native"],
1140
- envKeys: [/^FULLSTORY_/]
1141
- }
1142
- },
1143
- {
1144
- kind: "Amplitude",
1145
- label: "Amplitude",
1146
- mode: "in-cli",
1147
- signals: {
1148
- npm: [
1149
- "@amplitude/analytics-browser",
1150
- "@amplitude/analytics-node",
1151
- "@amplitude/analytics-react-native",
1152
- "amplitude-js"
1153
- ],
1154
- python: ["amplitude-analytics"],
1155
- envKeys: [/^AMPLITUDE_/]
1156
- }
1157
- },
1158
- {
1159
- kind: "Mixpanel",
1160
- label: "Mixpanel",
1161
- mode: "in-cli",
1162
- signals: {
1163
- npm: ["mixpanel", "mixpanel-browser"],
1164
- python: ["mixpanel"],
1165
- ruby: ["mixpanel-ruby"],
1166
- envKeys: [/^MIXPANEL_/]
1167
- }
1168
- },
1169
- {
1170
- kind: "Pendo",
1171
- label: "Pendo",
1172
- mode: "in-cli",
1173
- signals: {
1174
- npm: ["@pendo/agent"],
1175
- envKeys: [/^PENDO_/]
1176
- }
1177
- },
1178
- {
1179
- kind: "Salesforce",
1180
- label: "Salesforce",
1181
- mode: "deep-link",
1182
- signals: {
1183
- npm: ["jsforce"],
1184
- python: ["simple-salesforce"],
1185
- envKeys: [/^SALESFORCE_/]
1186
- }
1187
- },
1188
- {
1189
- kind: "Hubspot",
1190
- label: "HubSpot",
1191
- mode: "deep-link",
1192
- signals: {
1193
- npm: ["@hubspot/api-client"],
1194
- python: ["hubspot-api-client"],
1195
- envKeys: [/^HUBSPOT_/]
1196
- }
1197
- },
1198
- {
1199
- kind: "Zendesk",
1200
- label: "Zendesk",
1201
- mode: "deep-link",
1202
- signals: {
1203
- npm: ["node-zendesk"],
1204
- python: ["zenpy"],
1205
- envKeys: [/^ZENDESK_/]
1206
- }
1207
- },
1208
- {
1209
- kind: "Intercom",
1210
- label: "Intercom",
1211
- mode: "deep-link",
1212
- signals: {
1213
- npm: ["intercom-client", "@intercom/messenger-js-sdk"],
1214
- python: ["python-intercom"],
1215
- envKeys: [/^INTERCOM_/]
1216
- }
1217
- },
1218
- {
1219
- kind: "Linear",
1220
- label: "Linear",
1221
- mode: "deep-link",
1222
- signals: {
1223
- npm: ["@linear/sdk"],
1224
- envKeys: [/^LINEAR_API_KEY$/]
1225
- }
1226
- },
1227
- {
1228
- kind: "Slack",
1229
- label: "Slack",
1230
- mode: "deep-link",
1231
- signals: {
1232
- npm: ["@slack/web-api", "@slack/bolt"],
1233
- python: ["slack-sdk", "slack-bolt"],
1234
- envKeys: [/^SLACK_(BOT|APP|SIGNING|CLIENT)_/]
1235
- }
1236
- },
1237
- {
1238
- kind: "Github",
1239
- label: "GitHub",
1240
- mode: "deep-link",
1241
- signals: {
1242
- npm: [
1243
- "@octokit/rest",
1244
- "@octokit/core",
1245
- "@octokit/graphql",
1246
- "octokit"
1247
- ],
1248
- python: ["pygithub"],
1249
- ruby: ["octokit"]
1250
- }
1251
- }
1252
- ];
1253
- const LLM_SOURCE_DETECTORS = [
1254
- {
1255
- kind: "OpenAI",
1256
- label: "OpenAI",
1257
- mode: "in-cli",
1258
- signals: {
1259
- npm: ["openai"],
1260
- python: ["openai"],
1261
- envKeys: [/^OPENAI_/]
1262
- }
1263
- },
1264
- {
1265
- kind: "Anthropic",
1266
- label: "Anthropic",
1267
- mode: "in-cli",
1268
- signals: {
1269
- npm: ["@anthropic-ai/sdk"],
1270
- python: ["anthropic"],
1271
- envKeys: [/^ANTHROPIC_/]
1272
- }
1273
- },
1274
- {
1275
- kind: "Cohere",
1276
- label: "Cohere",
1277
- mode: "in-cli",
1278
- signals: {
1279
- npm: ["cohere-ai"],
1280
- python: ["cohere"],
1281
- envKeys: [/^COHERE_/]
1282
- }
1283
- },
1284
- {
1285
- kind: "MistralAI",
1286
- label: "Mistral AI",
1287
- mode: "in-cli",
1288
- signals: {
1289
- npm: ["@mistralai/mistralai"],
1290
- python: ["mistralai"],
1291
- envKeys: [/^MISTRAL_/]
1292
- }
1293
- },
1294
- {
1295
- kind: "Groq",
1296
- label: "Groq",
1297
- mode: "in-cli",
1298
- signals: {
1299
- npm: ["groq-sdk"],
1300
- python: ["groq"],
1301
- envKeys: [/^GROQ_/]
1302
- }
1303
- },
1304
- {
1305
- kind: "TogetherAI",
1306
- label: "Together AI",
1307
- mode: "in-cli",
1308
- signals: {
1309
- npm: ["together-ai"],
1310
- python: ["together"],
1311
- envKeys: [/^TOGETHER_/]
1312
- }
1313
- },
1314
- {
1315
- kind: "FireworksAI",
1316
- label: "Fireworks AI",
1317
- mode: "in-cli",
1318
- signals: {
1319
- python: ["fireworks-ai"],
1320
- envKeys: [/^FIREWORKS_/]
1321
- }
1322
- },
1323
- {
1324
- kind: "Replicate",
1325
- label: "Replicate",
1326
- mode: "in-cli",
1327
- signals: {
1328
- npm: ["replicate"],
1329
- python: ["replicate"],
1330
- envKeys: [/^REPLICATE_/]
1331
- }
1332
- },
1333
- {
1334
- kind: "HuggingFace",
1335
- label: "Hugging Face",
1336
- mode: "in-cli",
1337
- signals: {
1338
- npm: ["@huggingface/inference"],
1339
- python: ["huggingface-hub"],
1340
- envKeys: [/^HUGGINGFACE_/, /^HF_TOKEN$/]
1341
- }
1342
- },
1343
- {
1344
- kind: "ElevenLabs",
1345
- label: "ElevenLabs",
1346
- mode: "in-cli",
1347
- signals: {
1348
- npm: ["@elevenlabs/elevenlabs-js", "elevenlabs"],
1349
- python: ["elevenlabs"],
1350
- envKeys: [/^ELEVENLABS_/, /^ELEVEN_LABS_/]
1351
- }
1352
- },
1353
- {
1354
- kind: "Deepgram",
1355
- label: "Deepgram",
1356
- mode: "in-cli",
1357
- signals: {
1358
- npm: ["@deepgram/sdk"],
1359
- python: ["deepgram-sdk"],
1360
- envKeys: [/^DEEPGRAM_/]
1361
- }
1362
- },
1363
- {
1364
- kind: "AssemblyAI",
1365
- label: "AssemblyAI",
1366
- mode: "in-cli",
1367
- signals: {
1368
- npm: ["assemblyai"],
1369
- python: ["assemblyai"],
1370
- envKeys: [/^ASSEMBLYAI_/]
1371
- }
1372
- },
1373
- {
1374
- kind: "Langfuse",
1375
- label: "Langfuse",
1376
- mode: "in-cli",
1377
- signals: {
1378
- npm: ["langfuse"],
1379
- python: ["langfuse"],
1380
- envKeys: [/^LANGFUSE_/]
1381
- }
1382
- },
1383
- {
1384
- kind: "LangSmith",
1385
- label: "LangSmith",
1386
- mode: "in-cli",
1387
- signals: {
1388
- npm: ["langsmith"],
1389
- python: ["langsmith"],
1390
- envKeys: [/^LANGSMITH_/, /^LANGCHAIN_API_KEY$/]
1391
- }
1392
- },
1393
- {
1394
- kind: "Firecrawl",
1395
- label: "Firecrawl",
1396
- mode: "in-cli",
1397
- signals: {
1398
- npm: ["@mendable/firecrawl-js"],
1399
- python: ["firecrawl-py"],
1400
- envKeys: [/^FIRECRAWL_/]
1401
- }
1402
- },
1403
- {
1404
- kind: "Mem0",
1405
- label: "Mem0",
1406
- mode: "in-cli",
1407
- signals: {
1408
- npm: ["mem0ai"],
1409
- python: ["mem0ai"],
1410
- envKeys: [/^MEM0_/]
1411
- }
1412
- },
1413
- {
1414
- kind: "Vellum",
1415
- label: "Vellum",
1416
- mode: "in-cli",
1417
- signals: {
1418
- npm: ["vellum-ai"],
1419
- python: ["vellum-ai"],
1420
- envKeys: [/^VELLUM_/]
1421
- }
1422
- },
1423
- {
1424
- kind: "Helicone",
1425
- label: "Helicone",
1426
- mode: "in-cli",
1427
- signals: {
1428
- npm: ["@helicone/helpers"],
1429
- envKeys: [/^HELICONE_/]
1430
- }
1431
- },
1432
- {
1433
- kind: "OpenRouter",
1434
- label: "OpenRouter",
1435
- mode: "in-cli",
1436
- signals: {
1437
- npm: ["@openrouter/ai-sdk-provider"],
1438
- envKeys: [/^OPENROUTER_/]
1439
- }
1440
- },
1441
- {
1442
- kind: "Browserbase",
1443
- label: "Browserbase",
1444
- mode: "in-cli",
1445
- signals: {
1446
- npm: ["@browserbasehq/sdk", "@browserbasehq/stagehand"],
1447
- python: ["browserbase"],
1448
- envKeys: [/^BROWSERBASE_/]
1449
- }
1450
- },
1451
- {
1452
- kind: "E2B",
1453
- label: "E2B",
1454
- mode: "in-cli",
1455
- signals: {
1456
- npm: ["e2b", "@e2b/code-interpreter"],
1457
- python: ["e2b", "e2b-code-interpreter"],
1458
- envKeys: [/^E2B_/]
1459
- }
1460
- },
1461
- {
1462
- kind: "WeightsAndBiases",
1463
- label: "Weights & Biases",
1464
- mode: "in-cli",
1465
- signals: {
1466
- python: ["wandb"],
1467
- envKeys: [/^WANDB_/]
1468
- }
1469
- },
1470
- {
1471
- kind: "LlamaCloud",
1472
- label: "LlamaCloud",
1473
- mode: "in-cli",
1474
- signals: {
1475
- npm: ["llama-cloud-services"],
1476
- python: ["llama-cloud-services"],
1477
- envKeys: [/^LLAMA_CLOUD_/]
1478
- }
1479
- },
1480
- {
1481
- kind: "Zep",
1482
- label: "Zep",
1483
- mode: "in-cli",
1484
- signals: {
1485
- npm: ["@getzep/zep-cloud"],
1486
- python: ["zep-cloud"],
1487
- envKeys: [/^ZEP_API_KEY$/]
1488
- }
1489
- }
1490
- ];
1491
- const OTHER_SOURCE_DETECTORS = [
1492
- {
1493
- kind: "Paystack",
1494
- label: "Paystack",
1495
- mode: "in-cli",
1496
- signals: {
1497
- python: ["paystackapi"],
1498
- envKeys: [/^PAYSTACK_/]
1499
- }
1500
- },
1501
- {
1502
- kind: "Orb",
1503
- label: "Orb",
1504
- mode: "in-cli",
1505
- signals: {
1506
- npm: ["orb-billing"],
1507
- python: ["orb-billing"],
1508
- envKeys: [/^ORB_/]
1509
- }
1510
- },
1511
- {
1512
- kind: "Lago",
1513
- label: "Lago",
1514
- mode: "in-cli",
1515
- signals: {
1516
- npm: ["lago-javascript-client"],
1517
- python: ["lago-python-client"],
1518
- envKeys: [/^LAGO_/]
1519
- }
1520
- },
1521
- {
1522
- kind: "Zuora",
1523
- label: "Zuora",
1524
- mode: "in-cli",
1525
- signals: { envKeys: [/^ZUORA_/] }
1526
- },
1527
- {
1528
- kind: "Stigg",
1529
- label: "Stigg",
1530
- mode: "in-cli",
1531
- signals: {
1532
- npm: ["@stigg/node-server-sdk"],
1533
- envKeys: [/^STIGG_/]
1534
- }
1535
- },
1536
- {
1537
- kind: "Ramp",
1538
- label: "Ramp",
1539
- mode: "in-cli",
1540
- signals: { envKeys: [/^RAMP_/] }
1541
- },
1542
- {
1543
- kind: "Brex",
1544
- label: "Brex",
1545
- mode: "in-cli",
1546
- signals: { envKeys: [/^BREX_/] }
1547
- },
1548
- {
1549
- kind: "Tremendous",
1550
- label: "Tremendous",
1551
- mode: "in-cli",
1552
- signals: { envKeys: [/^TREMENDOUS_/] }
1553
- },
1554
- {
1555
- kind: "Maxio",
1556
- label: "Maxio",
1557
- mode: "in-cli",
1558
- signals: { envKeys: [/^MAXIO_/] }
1559
- },
1560
- {
1561
- kind: "Chargify",
1562
- label: "Chargify",
1563
- mode: "in-cli",
1564
- signals: { envKeys: [/^CHARGIFY_/] }
1565
- },
1566
- {
1567
- kind: "Invoiced",
1568
- label: "Invoiced",
1569
- mode: "in-cli",
1570
- signals: {
1571
- npm: ["invoiced"],
1572
- python: ["invoiced"],
1573
- envKeys: [/^INVOICED_/]
1574
- }
1575
- },
1576
- {
1577
- kind: "LemonSqueezy",
1578
- label: "Lemon Squeezy",
1579
- mode: "in-cli",
1580
- signals: {
1581
- npm: ["@lemonsqueezy/lemonsqueezy.js"],
1582
- envKeys: [/^LEMONSQUEEZY_/, /^LEMON_SQUEEZY_/]
1583
- }
1584
- },
1585
- {
1586
- kind: "Razorpay",
1587
- label: "Razorpay",
1588
- mode: "in-cli",
1589
- signals: {
1590
- npm: ["razorpay"],
1591
- python: ["razorpay"],
1592
- ruby: ["razorpay"],
1593
- envKeys: [/^RAZORPAY_/]
1594
- }
1595
- },
1596
- {
1597
- kind: "Adyen",
1598
- label: "Adyen",
1599
- mode: "in-cli",
1600
- signals: {
1601
- npm: ["@adyen/api-library"],
1602
- python: ["adyen"],
1603
- envKeys: [/^ADYEN_/]
1604
- }
1605
- },
1606
- {
1607
- kind: "ChartMogul",
1608
- label: "ChartMogul",
1609
- mode: "in-cli",
1610
- signals: {
1611
- npm: ["chartmogul-node"],
1612
- python: ["chartmogul"],
1613
- envKeys: [/^CHARTMOGUL_/]
1614
- }
1615
- },
1616
- {
1617
- kind: "Airwallex",
1618
- label: "Airwallex",
1619
- mode: "in-cli",
1620
- signals: {
1621
- npm: ["airwallex-payment-elements"],
1622
- envKeys: [/^AIRWALLEX_/]
1623
- }
1624
- },
1625
- {
1626
- kind: "Flutterwave",
1627
- label: "Flutterwave",
1628
- mode: "in-cli",
1629
- signals: {
1630
- npm: ["flutterwave-node-v3"],
1631
- python: ["flutterwave"],
1632
- envKeys: [/^FLUTTERWAVE_/, /^FLW_(PUBLIC|SECRET)_KEY$/]
1633
- }
1634
- },
1635
- {
1636
- kind: "DodoPayments",
1637
- label: "Dodo Payments",
1638
- mode: "in-cli",
1639
- signals: {
1640
- npm: ["dodopayments"],
1641
- python: ["dodopayments"],
1642
- envKeys: [/^DODO_PAYMENTS_/]
1643
- }
1644
- },
1645
- {
1646
- kind: "Whop",
1647
- label: "Whop",
1648
- mode: "in-cli",
1649
- signals: {
1650
- npm: ["@whop/api", "@whop-apps/sdk"],
1651
- envKeys: [/^WHOP_/]
1652
- }
1653
- },
1654
- {
1655
- kind: "SparkPost",
1656
- label: "SparkPost",
1657
- mode: "in-cli",
1658
- signals: {
1659
- npm: ["sparkpost"],
1660
- python: ["sparkpost"],
1661
- envKeys: [/^SPARKPOST_/]
1662
- }
1663
- },
1664
- {
1665
- kind: "MailerSend",
1666
- label: "MailerSend",
1667
- mode: "in-cli",
1668
- signals: {
1669
- npm: ["mailersend"],
1670
- python: ["mailersend"],
1671
- envKeys: [/^MAILERSEND_/]
1672
- }
1673
- },
1674
- {
1675
- kind: "Iterable",
1676
- label: "Iterable",
1677
- mode: "in-cli",
1678
- signals: { envKeys: [/^ITERABLE_/] }
1679
- },
1680
- {
1681
- kind: "ConvertKit",
1682
- label: "ConvertKit",
1683
- mode: "in-cli",
1684
- signals: { envKeys: [/^CONVERTKIT_/] }
1685
- },
1686
- {
1687
- kind: "Drip",
1688
- label: "Drip",
1689
- mode: "in-cli",
1690
- signals: {
1691
- npm: ["drip-nodejs"],
1692
- envKeys: [/^DRIP_/]
1693
- }
1694
- },
1695
- {
1696
- kind: "Omnisend",
1697
- label: "Omnisend",
1698
- mode: "in-cli",
1699
- signals: { envKeys: [/^OMNISEND_/] }
1700
- },
1701
- {
1702
- kind: "EmailOctopus",
1703
- label: "EmailOctopus",
1704
- mode: "in-cli",
1705
- signals: { envKeys: [/^EMAILOCTOPUS_/, /^EMAIL_OCTOPUS_/] }
1706
- },
1707
- {
1708
- kind: "Elasticemail",
1709
- label: "Elastic Email",
1710
- mode: "in-cli",
1711
- signals: { envKeys: [/^ELASTICEMAIL_/, /^ELASTIC_EMAIL_/] }
1712
- },
1713
- {
1714
- kind: "CampaignMonitor",
1715
- label: "Campaign Monitor",
1716
- mode: "in-cli",
1717
- signals: {
1718
- npm: ["createsend-node"],
1719
- envKeys: [/^CAMPAIGN_MONITOR_/, /^CREATESEND_/]
1720
- }
1721
- },
1722
- {
1723
- kind: "Lemlist",
1724
- label: "Lemlist",
1725
- mode: "in-cli",
1726
- signals: { envKeys: [/^LEMLIST_/] }
1727
- },
1728
- {
1729
- kind: "Loops",
1730
- label: "Loops",
1731
- mode: "in-cli",
1732
- signals: {
1733
- npm: ["loops"],
1734
- envKeys: [/^LOOPS_/]
1735
- }
1736
- },
1737
- {
1738
- kind: "Knock",
1739
- label: "Knock",
1740
- mode: "in-cli",
1741
- signals: {
1742
- npm: ["@knocklabs/node", "@knocklabs/client"],
1743
- python: ["knockapi"],
1744
- envKeys: [/^KNOCK_/]
1745
- }
1746
- },
1747
- {
1748
- kind: "AwsSes",
1749
- label: "Amazon SES",
1750
- mode: "in-cli",
1751
- signals: {
1752
- npm: ["@aws-sdk/client-ses", "@aws-sdk/client-sesv2"],
1753
- envKeys: [/^AWS_SES_/]
1754
- }
1755
- },
1756
- {
1757
- kind: "Beehiiv",
1758
- label: "beehiiv",
1759
- mode: "in-cli",
1760
- signals: { envKeys: [/^BEEHIIV_/] }
1761
- },
1762
- {
1763
- kind: "Mailtrap",
1764
- label: "Mailtrap",
1765
- mode: "in-cli",
1766
- signals: {
1767
- npm: ["mailtrap"],
1768
- python: ["mailtrap"],
1769
- envKeys: [/^MAILTRAP_/]
1770
- }
1771
- },
1772
- {
1773
- kind: "Plunk",
1774
- label: "Plunk",
1775
- mode: "in-cli",
1776
- signals: {
1777
- npm: ["@plunk/node"],
1778
- envKeys: [/^PLUNK_/]
1779
- }
1780
- },
1781
- {
1782
- kind: "Ortto",
1783
- label: "Ortto",
1784
- mode: "in-cli",
1785
- signals: { envKeys: [/^ORTTO_/] }
1786
- },
1787
- {
1788
- kind: "Postscript",
1789
- label: "Postscript",
1790
- mode: "in-cli",
1791
- signals: { envKeys: [/^POSTSCRIPT_/] }
1792
- },
1793
- {
1794
- kind: "Marketo",
1795
- label: "Marketo",
1796
- mode: "in-cli",
1797
- signals: {
1798
- python: ["marketorestpython"],
1799
- envKeys: [/^MARKETO_/]
1800
- }
1801
- },
1802
- {
1803
- kind: "Lob",
1804
- label: "Lob",
1805
- mode: "in-cli",
1806
- signals: {
1807
- npm: ["lob"],
1808
- python: ["lob"],
1809
- envKeys: [/^LOB_API_KEY$/]
1810
- }
1811
- },
1812
- {
1813
- kind: "Segment",
1814
- label: "Segment",
1815
- mode: "in-cli",
1816
- signals: {
1817
- npm: ["@segment/analytics-node", "analytics-node"],
1818
- python: ["segment-analytics-python", "analytics-python"],
1819
- envKeys: [/^SEGMENT_/]
1820
- }
1821
- },
1822
- {
1823
- kind: "Snowplow",
1824
- label: "Snowplow Analytics",
1825
- mode: "in-cli",
1826
- signals: {
1827
- npm: ["@snowplow/node-tracker"],
1828
- python: ["snowplow-tracker"],
1829
- envKeys: [/^SNOWPLOW_/]
1830
- }
1831
- },
1832
- {
1833
- kind: "Matomo",
1834
- label: "Matomo",
1835
- mode: "in-cli",
1836
- signals: {
1837
- npm: ["matomo-tracker"],
1838
- envKeys: [/^MATOMO_/]
1839
- }
1840
- },
1841
- {
1842
- kind: "Plausible",
1843
- label: "Plausible",
1844
- mode: "in-cli",
1845
- signals: {
1846
- npm: ["plausible-tracker"],
1847
- envKeys: [/^PLAUSIBLE_/]
1848
- }
1849
- },
1850
- {
1851
- kind: "ConfigCat",
1852
- label: "ConfigCat",
1853
- mode: "in-cli",
1854
- signals: {
1855
- npm: ["configcat-node", "configcat-js"],
1856
- python: ["configcat-client"],
1857
- envKeys: [/^CONFIGCAT_/]
1858
- }
1859
- },
1860
- {
1861
- kind: "Flagsmith",
1862
- label: "Flagsmith",
1863
- mode: "in-cli",
1864
- signals: {
1865
- npm: ["flagsmith-nodejs", "flagsmith"],
1866
- python: ["flagsmith"],
1867
- envKeys: [/^FLAGSMITH_/]
1868
- }
1869
- },
1870
- {
1871
- kind: "Unleash",
1872
- label: "Unleash",
1873
- mode: "in-cli",
1874
- signals: {
1875
- npm: ["unleash-client"],
1876
- python: ["unleashclient"],
1877
- envKeys: [/^UNLEASH_/]
1878
- }
1879
- },
1880
- {
1881
- kind: "SplitIo",
1882
- label: "Split.io",
1883
- mode: "in-cli",
1884
- signals: {
1885
- npm: ["@splitsoftware/splitio"],
1886
- python: ["splitio-client"],
1887
- envKeys: [/^SPLITIO_/, /^SPLIT_IO_/]
1888
- }
1889
- },
1890
- {
1891
- kind: "Stytch",
1892
- label: "Stytch",
1893
- mode: "in-cli",
1894
- signals: {
1895
- npm: ["stytch"],
1896
- python: ["stytch"],
1897
- envKeys: [/^STYTCH_/]
1898
- }
1899
- },
1900
- {
1901
- kind: "Doppler",
1902
- label: "Doppler",
1903
- mode: "in-cli",
1904
- signals: {
1905
- npm: ["@dopplerhq/node-sdk"],
1906
- envKeys: [/^DOPPLER_/]
1907
- }
1908
- },
1909
- {
1910
- kind: "Infisical",
1911
- label: "Infisical",
1912
- mode: "in-cli",
1913
- signals: {
1914
- npm: ["@infisical/sdk"],
1915
- python: ["infisicalsdk"],
1916
- envKeys: [/^INFISICAL_/]
1917
- }
1918
- },
1919
- {
1920
- kind: "OnePassword",
1921
- label: "1Password",
1922
- mode: "in-cli",
1923
- signals: {
1924
- npm: ["@1password/sdk"],
1925
- python: ["onepassword-sdk"],
1926
- envKeys: [/^ONEPASSWORD_/, /^OP_SERVICE_ACCOUNT_TOKEN$/]
1927
- }
1928
- },
1929
- {
1930
- kind: "Persona",
1931
- label: "Persona",
1932
- mode: "in-cli",
1933
- signals: { envKeys: [/^PERSONA_/] }
1934
- },
1935
- {
1936
- kind: "Svix",
1937
- label: "Svix",
1938
- mode: "in-cli",
1939
- signals: {
1940
- npm: ["svix"],
1941
- python: ["svix"],
1942
- envKeys: [/^SVIX_/]
1943
- }
1944
- },
1945
- {
1946
- kind: "Mux",
1947
- label: "Mux",
1948
- mode: "in-cli",
1949
- signals: {
1950
- npm: ["@mux/mux-node"],
1951
- python: ["mux-python"],
1952
- envKeys: [/^MUX_/]
1953
- }
1954
- },
1955
- {
1956
- kind: "Vapi",
1957
- label: "Vapi",
1958
- mode: "in-cli",
1959
- signals: {
1960
- npm: ["@vapi-ai/server-sdk"],
1961
- python: ["vapi-server-sdk"],
1962
- envKeys: [/^VAPI_/]
1963
- }
1964
- },
1965
- {
1966
- kind: "Aircall",
1967
- label: "Aircall",
1968
- mode: "in-cli",
1969
- signals: { envKeys: [/^AIRCALL_/] }
1970
- },
1971
- {
1972
- kind: "JustCall",
1973
- label: "JustCall",
1974
- mode: "in-cli",
1975
- signals: { envKeys: [/^JUSTCALL_/] }
1976
- },
1977
- {
1978
- kind: "Telnyx",
1979
- label: "Telnyx",
1980
- mode: "in-cli",
1981
- signals: {
1982
- npm: ["telnyx"],
1983
- python: ["telnyx"],
1984
- envKeys: [/^TELNYX_/]
1985
- }
1986
- },
1987
- {
1988
- kind: "Plivo",
1989
- label: "Plivo",
1990
- mode: "in-cli",
1991
- signals: {
1992
- npm: ["plivo"],
1993
- python: ["plivo"],
1994
- envKeys: [/^PLIVO_/]
1995
- }
1996
- },
1997
- {
1998
- kind: "Courier",
1999
- label: "Courier",
2000
- mode: "in-cli",
2001
- signals: {
2002
- npm: ["@trycourier/courier"],
2003
- python: ["trycourier"],
2004
- envKeys: [/^COURIER_AUTH_TOKEN$/]
2005
- }
2006
- },
2007
- {
2008
- kind: "WooCommerce",
2009
- label: "WooCommerce",
2010
- mode: "in-cli",
2011
- signals: {
2012
- npm: ["@woocommerce/woocommerce-rest-api"],
2013
- python: ["woocommerce"],
2014
- envKeys: [/^WOOCOMMERCE_/]
2015
- }
2016
- },
2017
- {
2018
- kind: "Recharge",
2019
- label: "Recharge",
2020
- mode: "in-cli",
2021
- signals: { envKeys: [/^RECHARGE_/] }
2022
- },
2023
- {
2024
- kind: "Commercetools",
2025
- label: "commercetools",
2026
- mode: "in-cli",
2027
- signals: {
2028
- npm: ["@commercetools/platform-sdk"],
2029
- python: ["commercetools"],
2030
- envKeys: [/^CTP_/, /^COMMERCETOOLS_/]
2031
- }
2032
- },
2033
- {
2034
- kind: "Webflow",
2035
- label: "Webflow",
2036
- mode: "in-cli",
2037
- signals: {
2038
- npm: ["webflow-api"],
2039
- envKeys: [/^WEBFLOW_/]
2040
- }
2041
- },
2042
- {
2043
- kind: "Easypost",
2044
- label: "EasyPost",
2045
- mode: "in-cli",
2046
- signals: {
2047
- npm: ["@easypost/api"],
2048
- python: ["easypost"],
2049
- envKeys: [/^EASYPOST_/]
2050
- }
2051
- },
2052
- {
2053
- kind: "Shippo",
2054
- label: "Shippo",
2055
- mode: "in-cli",
2056
- signals: {
2057
- npm: ["shippo"],
2058
- python: ["shippo"],
2059
- envKeys: [/^SHIPPO_/]
2060
- }
2061
- },
2062
- {
2063
- kind: "ShipStation",
2064
- label: "ShipStation",
2065
- mode: "in-cli",
2066
- signals: { envKeys: [/^SHIPSTATION_/] }
2067
- },
2068
- {
2069
- kind: "Squarespace",
2070
- label: "Squarespace",
2071
- mode: "in-cli",
2072
- signals: { envKeys: [/^SQUARESPACE_/] }
2073
- },
2074
- {
2075
- kind: "Printify",
2076
- label: "Printify",
2077
- mode: "in-cli",
2078
- signals: { envKeys: [/^PRINTIFY_/] }
2079
- },
2080
- {
2081
- kind: "BigCommerce",
2082
- label: "BigCommerce",
2083
- mode: "in-cli",
2084
- signals: {
2085
- npm: ["node-bigcommerce"],
2086
- envKeys: [/^BIGCOMMERCE_/]
2087
- }
2088
- },
2089
- {
2090
- kind: "Freshdesk",
2091
- label: "Freshdesk",
2092
- mode: "in-cli",
2093
- signals: {
2094
- python: ["python-freshdesk"],
2095
- envKeys: [/^FRESHDESK_/]
2096
- }
2097
- },
2098
- {
2099
- kind: "Front",
2100
- label: "Front",
2101
- mode: "in-cli",
2102
- signals: { envKeys: [/^FRONTAPP_/, /^FRONT_API_/] }
2103
- },
2104
- {
2105
- kind: "Gorgias",
2106
- label: "Gorgias",
2107
- mode: "in-cli",
2108
- signals: { envKeys: [/^GORGIAS_/] }
2109
- },
2110
- {
2111
- kind: "Chatwoot",
2112
- label: "Chatwoot",
2113
- mode: "in-cli",
2114
- signals: { envKeys: [/^CHATWOOT_/] }
2115
- },
2116
- {
2117
- kind: "Kustomer",
2118
- label: "Kustomer",
2119
- mode: "in-cli",
2120
- signals: { envKeys: [/^KUSTOMER_/] }
2121
- },
2122
- {
2123
- kind: "Plain",
2124
- label: "Plain",
2125
- mode: "in-cli",
2126
- signals: {
2127
- npm: ["@team-plain/typescript-sdk"],
2128
- envKeys: [/^PLAIN_/]
2129
- }
2130
- },
2131
- {
2132
- kind: "Canny",
2133
- label: "Canny",
2134
- mode: "in-cli",
2135
- signals: { envKeys: [/^CANNY_/] }
2136
- },
2137
- {
2138
- kind: "Pylon",
2139
- label: "Pylon",
2140
- mode: "in-cli",
2141
- signals: { envKeys: [/^PYLON_/] }
2142
- },
2143
- {
2144
- kind: "GitLab",
2145
- label: "GitLab",
2146
- mode: "in-cli",
2147
- signals: {
2148
- npm: ["@gitbeaker/rest"],
2149
- python: ["python-gitlab"],
2150
- ruby: ["gitlab"],
2151
- envKeys: [/^GITLAB_/]
2152
- }
2153
- },
2154
- {
2155
- kind: "Bitbucket",
2156
- label: "Atlassian Bitbucket Cloud",
2157
- mode: "in-cli",
2158
- signals: {
2159
- npm: ["bitbucket"],
2160
- envKeys: [/^BITBUCKET_/]
2161
- }
2162
- },
2163
- {
2164
- kind: "Gitea",
2165
- label: "Gitea",
2166
- mode: "in-cli",
2167
- signals: {
2168
- npm: ["gitea-js"],
2169
- envKeys: [/^GITEA_/]
2170
- }
2171
- },
2172
- {
2173
- kind: "Datadog",
2174
- label: "Datadog",
2175
- mode: "in-cli",
2176
- signals: {
2177
- npm: ["@datadog/datadog-api-client", "dd-trace"],
2178
- python: ["datadog", "datadog-api-client"],
2179
- ruby: ["dogapi"],
2180
- envKeys: [
2181
- /^DATADOG_/,
2182
- /^DD_API_KEY$/,
2183
- /^DD_APP_KEY$/
2184
- ]
2185
- }
2186
- },
2187
- {
2188
- kind: "NewRelic",
2189
- label: "New Relic",
2190
- mode: "in-cli",
2191
- signals: {
2192
- npm: ["newrelic"],
2193
- python: ["newrelic"],
2194
- ruby: ["newrelic_rpm"],
2195
- envKeys: [/^NEW_RELIC_/, /^NEWRELIC_/]
2196
- }
2197
- },
2198
- {
2199
- kind: "Cloudflare",
2200
- label: "Cloudflare",
2201
- mode: "in-cli",
2202
- signals: {
2203
- npm: ["cloudflare"],
2204
- python: ["cloudflare"],
2205
- envKeys: [/^CLOUDFLARE_/, /^CF_API_TOKEN$/]
2206
- }
2207
- },
2208
- {
2209
- kind: "Elasticsearch",
2210
- label: "Elasticsearch",
2211
- mode: "in-cli",
2212
- signals: {
2213
- npm: ["@elastic/elasticsearch"],
2214
- python: ["elasticsearch"],
2215
- ruby: ["elasticsearch"],
2216
- envKeys: [/^ELASTICSEARCH_/]
2217
- }
2218
- },
2219
- {
2220
- kind: "Snyk",
2221
- label: "Snyk",
2222
- mode: "in-cli",
2223
- signals: {
2224
- npm: ["snyk"],
2225
- python: ["pysnyk"],
2226
- envKeys: [/^SNYK_/]
2227
- }
2228
- },
2229
- {
2230
- kind: "PagerDuty",
2231
- label: "PagerDuty",
2232
- mode: "in-cli",
2233
- signals: {
2234
- npm: ["@pagerduty/pdjs"],
2235
- python: ["pdpyras"],
2236
- envKeys: [/^PAGERDUTY_/, /^PD_API_KEY$/]
2237
- }
2238
- },
2239
- {
2240
- kind: "Opsgenie",
2241
- label: "Opsgenie",
2242
- mode: "in-cli",
2243
- signals: { envKeys: [/^OPSGENIE_/] }
2244
- },
2245
- {
2246
- kind: "IncidentIo",
2247
- label: "incident.io",
2248
- mode: "in-cli",
2249
- signals: { envKeys: [/^INCIDENT_IO_/, /^INCIDENTIO_/] }
2250
- },
2251
- {
2252
- kind: "Bugsnag",
2253
- label: "Bugsnag",
2254
- mode: "in-cli",
2255
- signals: {
2256
- npm: ["@bugsnag/js"],
2257
- python: ["bugsnag"],
2258
- ruby: ["bugsnag"],
2259
- envKeys: [/^BUGSNAG_/]
2260
- }
2261
- },
2262
- {
2263
- kind: "Honeybadger",
2264
- label: "Honeybadger",
2265
- mode: "in-cli",
2266
- signals: {
2267
- npm: ["@honeybadger-io/js"],
2268
- python: ["honeybadger"],
2269
- ruby: ["honeybadger"],
2270
- envKeys: [/^HONEYBADGER_/]
2271
- }
2272
- },
2273
- {
2274
- kind: "Raygun",
2275
- label: "Raygun",
2276
- mode: "in-cli",
2277
- signals: {
2278
- npm: ["raygun"],
2279
- python: ["raygun4py"],
2280
- ruby: ["raygun4ruby"],
2281
- envKeys: [/^RAYGUN_/]
2282
- }
2283
- },
2284
- {
2285
- kind: "CircleCI",
2286
- label: "CircleCI",
2287
- mode: "in-cli",
2288
- signals: { envKeys: [/^CIRCLECI_/, /^CIRCLE_TOKEN$/] }
2289
- },
2290
- {
2291
- kind: "Upstash",
2292
- label: "Upstash",
2293
- mode: "in-cli",
2294
- signals: {
2295
- npm: ["@upstash/redis"],
2296
- python: ["upstash-redis"],
2297
- envKeys: [/^UPSTASH_/]
2298
- }
2299
- },
2300
- {
2301
- kind: "Vercel",
2302
- label: "Vercel",
2303
- mode: "in-cli",
2304
- signals: {
2305
- npm: ["@vercel/sdk"],
2306
- envKeys: [/^VERCEL_API_TOKEN$/]
2307
- }
2308
- },
2309
- {
2310
- kind: "Algolia",
2311
- label: "Algolia",
2312
- mode: "in-cli",
2313
- signals: {
2314
- npm: ["algoliasearch"],
2315
- python: ["algoliasearch"],
2316
- ruby: ["algolia"],
2317
- envKeys: [/^ALGOLIA_/]
2318
- }
2319
- },
2320
- {
2321
- kind: "Inngest",
2322
- label: "Inngest",
2323
- mode: "in-cli",
2324
- signals: {
2325
- npm: ["inngest"],
2326
- python: ["inngest"],
2327
- envKeys: [/^INNGEST_/]
2328
- }
2329
- },
2330
- {
2331
- kind: "TriggerDev",
2332
- label: "Trigger.dev",
2333
- mode: "in-cli",
2334
- signals: {
2335
- npm: ["@trigger.dev/sdk"],
2336
- envKeys: [/^TRIGGER_(SECRET_KEY|API_KEY|PROJECT_ID)$/]
2337
- }
2338
- },
2339
- {
2340
- kind: "Ably",
2341
- label: "Ably",
2342
- mode: "in-cli",
2343
- signals: {
2344
- npm: ["ably"],
2345
- python: ["ably"],
2346
- envKeys: [/^ABLY_/]
2347
- }
2348
- },
2349
- {
2350
- kind: "Netlify",
2351
- label: "Netlify",
2352
- mode: "in-cli",
2353
- signals: {
2354
- npm: ["@netlify/functions"],
2355
- envKeys: [/^NETLIFY_/]
2356
- }
2357
- },
2358
- {
2359
- kind: "Railway",
2360
- label: "Railway",
2361
- mode: "in-cli",
2362
- signals: { envKeys: [/^RAILWAY_/] }
2363
- },
2364
- {
2365
- kind: "Render",
2366
- label: "Render",
2367
- mode: "in-cli",
2368
- signals: { envKeys: [/^RENDER_API_KEY$/] }
2369
- },
2370
- {
2371
- kind: "FlyIo",
2372
- label: "Fly.io",
2373
- mode: "in-cli",
2374
- signals: { envKeys: [/^FLY_API_TOKEN$/, /^FLY_APP_NAME$/] }
2375
- },
2376
- {
2377
- kind: "Heroku",
2378
- label: "Heroku",
2379
- mode: "in-cli",
2380
- signals: { envKeys: [/^HEROKU_/] }
2381
- },
2382
- {
2383
- kind: "DigitalOcean",
2384
- label: "DigitalOcean",
2385
- mode: "in-cli",
2386
- signals: { envKeys: [/^DIGITALOCEAN_/] }
2387
- },
2388
- {
2389
- kind: "Grafana",
2390
- label: "Grafana",
2391
- mode: "in-cli",
2392
- signals: {
2393
- python: ["grafana-client"],
2394
- envKeys: [/^GRAFANA_/]
2395
- }
2396
- },
2397
- {
2398
- kind: "BetterStack",
2399
- label: "Better Stack",
2400
- mode: "in-cli",
2401
- signals: {
2402
- npm: ["@logtail/node", "@logtail/js"],
2403
- envKeys: [
2404
- /^BETTERSTACK_/,
2405
- /^BETTER_STACK_/,
2406
- /^LOGTAIL_/
2407
- ]
2408
- }
2409
- },
2410
- {
2411
- kind: "Honeycomb",
2412
- label: "Honeycomb",
2413
- mode: "in-cli",
2414
- signals: {
2415
- npm: ["@honeycombio/opentelemetry-node", "honeycomb-beeline"],
2416
- python: ["honeycomb-beeline", "libhoney"],
2417
- envKeys: [/^HONEYCOMB_/]
2418
- }
2419
- },
2420
- {
2421
- kind: "Descope",
2422
- label: "Descope",
2423
- mode: "in-cli",
2424
- signals: {
2425
- npm: [
2426
- "@descope/node-sdk",
2427
- "@descope/nextjs-sdk",
2428
- "@descope/react-sdk"
2429
- ],
2430
- python: ["descope"],
2431
- envKeys: [/^DESCOPE_/]
2432
- }
2433
- },
2434
- {
2435
- kind: "FusionAuth",
2436
- label: "FusionAuth",
2437
- mode: "in-cli",
2438
- signals: {
2439
- npm: ["@fusionauth/typescript-client"],
2440
- python: ["fusionauth-client"],
2441
- envKeys: [/^FUSIONAUTH_/, /^FUSION_AUTH_/]
2442
- }
2443
- },
2444
- {
2445
- kind: "Hookdeck",
2446
- label: "Hookdeck",
2447
- mode: "in-cli",
2448
- signals: {
2449
- npm: ["@hookdeck/sdk"],
2450
- envKeys: [/^HOOKDECK_/]
2451
- }
2452
- },
2453
- {
2454
- kind: "N8n",
2455
- label: "n8n",
2456
- mode: "in-cli",
2457
- signals: {
2458
- npm: ["n8n"],
2459
- envKeys: [/^N8N_/]
2460
- }
2461
- },
2462
- {
2463
- kind: "Dbt",
2464
- label: "dbt",
2465
- mode: "in-cli",
2466
- signals: {
2467
- python: [
2468
- "dbt-core",
2469
- "dbt-snowflake",
2470
- "dbt-postgres",
2471
- "dbt-bigquery",
2472
- "dbt-redshift",
2473
- "dbt-databricks"
2474
- ],
2475
- envKeys: [/^DBT_/]
2476
- }
2477
- },
2478
- {
2479
- kind: "Fastly",
2480
- label: "Fastly",
2481
- mode: "in-cli",
2482
- signals: {
2483
- npm: ["fastly"],
2484
- python: ["fastly"],
2485
- envKeys: [/^FASTLY_/]
2486
- }
2487
- },
2488
- {
2489
- kind: "Bunny",
2490
- label: "Bunny.net",
2491
- mode: "in-cli",
2492
- signals: { envKeys: [/^BUNNY_/] }
2493
- },
2494
- {
2495
- kind: "Codecov",
2496
- label: "Codecov",
2497
- mode: "in-cli",
2498
- signals: {
2499
- npm: ["codecov"],
2500
- envKeys: [/^CODECOV_/]
2501
- }
2502
- },
2503
- {
2504
- kind: "Sourcegraph",
2505
- label: "Sourcegraph",
2506
- mode: "in-cli",
2507
- signals: { envKeys: [/^SOURCEGRAPH_/, /^SRC_ACCESS_TOKEN$/] }
2508
- },
2509
- {
2510
- kind: "TemporalIO",
2511
- label: "Temporal.io",
2512
- mode: "in-cli",
2513
- signals: {
2514
- npm: ["@temporalio/client", "@temporalio/worker"],
2515
- python: ["temporalio"],
2516
- envKeys: [/^TEMPORAL_/]
2517
- }
2518
- },
2519
- {
2520
- kind: "Hatchet",
2521
- label: "Hatchet",
2522
- mode: "in-cli",
2523
- signals: {
2524
- npm: ["@hatchet-dev/typescript-sdk"],
2525
- python: ["hatchet-sdk"],
2526
- envKeys: [/^HATCHET_/]
2527
- }
2528
- },
2529
- {
2530
- kind: "Pipedrive",
2531
- label: "Pipedrive",
2532
- mode: "in-cli",
2533
- signals: {
2534
- npm: ["pipedrive"],
2535
- envKeys: [/^PIPEDRIVE_/]
2536
- }
2537
- },
2538
- {
2539
- kind: "Close",
2540
- label: "Close",
2541
- mode: "in-cli",
2542
- signals: {
2543
- python: ["closeio"],
2544
- envKeys: [/^CLOSE_API_KEY$/]
2545
- }
2546
- },
2547
- {
2548
- kind: "Copper",
2549
- label: "Copper",
2550
- mode: "in-cli",
2551
- signals: { envKeys: [/^COPPER_/] }
2552
- },
2553
- {
2554
- kind: "Insightly",
2555
- label: "Insightly",
2556
- mode: "in-cli",
2557
- signals: { envKeys: [/^INSIGHTLY_/] }
2558
- },
2559
- {
2560
- kind: "Salesflare",
2561
- label: "Salesflare",
2562
- mode: "in-cli",
2563
- signals: { envKeys: [/^SALESFLARE_/] }
2564
- },
2565
- {
2566
- kind: "SalesLoft",
2567
- label: "Salesloft",
2568
- mode: "in-cli",
2569
- signals: { envKeys: [/^SALESLOFT_/] }
2570
- },
2571
- {
2572
- kind: "Attio",
2573
- label: "Attio",
2574
- mode: "in-cli",
2575
- signals: {
2576
- npm: ["attio"],
2577
- envKeys: [/^ATTIO_/]
2578
- }
2579
- },
2580
- {
2581
- kind: "ActiveCampaign",
2582
- label: "ActiveCampaign",
2583
- mode: "in-cli",
2584
- signals: { envKeys: [/^ACTIVECAMPAIGN_/, /^ACTIVE_CAMPAIGN_/] }
2585
- },
2586
- {
2587
- kind: "Vitally",
2588
- label: "Vitally",
2589
- mode: "in-cli",
2590
- signals: { envKeys: [/^VITALLY_/] }
2591
- },
2592
- {
2593
- kind: "Docusign",
2594
- label: "DocuSign",
2595
- mode: "in-cli",
2596
- signals: {
2597
- npm: ["docusign-esign"],
2598
- python: ["docusign-esign"],
2599
- envKeys: [/^DOCUSIGN_/]
2600
- }
2601
- },
2602
- {
2603
- kind: "PandaDoc",
2604
- label: "PandaDoc",
2605
- mode: "in-cli",
2606
- signals: { envKeys: [/^PANDADOC_/] }
2607
- },
2608
- {
2609
- kind: "Gong",
2610
- label: "Gong",
2611
- mode: "in-cli",
2612
- signals: { envKeys: [/^GONG_/] }
2613
- },
2614
- {
2615
- kind: "Asana",
2616
- label: "Asana",
2617
- mode: "in-cli",
2618
- signals: {
2619
- npm: ["asana"],
2620
- python: ["asana"],
2621
- envKeys: [/^ASANA_/]
2622
- }
2623
- },
2624
- {
2625
- kind: "Trello",
2626
- label: "Trello",
2627
- mode: "in-cli",
2628
- signals: {
2629
- npm: ["trello"],
2630
- envKeys: [/^TRELLO_/]
2631
- }
2632
- },
2633
- {
2634
- kind: "Todoist",
2635
- label: "Todoist",
2636
- mode: "in-cli",
2637
- signals: {
2638
- npm: ["@doist/todoist-api-typescript"],
2639
- python: ["todoist-api-python"],
2640
- envKeys: [/^TODOIST_/]
2641
- }
2642
- },
2643
- {
2644
- kind: "Monday",
2645
- label: "monday.com",
2646
- mode: "in-cli",
2647
- signals: {
2648
- npm: ["@mondaydotcomorg/api"],
2649
- envKeys: [/^MONDAY_/]
2650
- }
2651
- },
2652
- {
2653
- kind: "ClickUp",
2654
- label: "ClickUp",
2655
- mode: "in-cli",
2656
- signals: { envKeys: [/^CLICKUP_/] }
2657
- },
2658
- {
2659
- kind: "Height",
2660
- label: "Height",
2661
- mode: "in-cli",
2662
- signals: { envKeys: [/^HEIGHT_API_KEY$/] }
2663
- },
2664
- {
2665
- kind: "Shortcut",
2666
- label: "Shortcut",
2667
- mode: "in-cli",
2668
- signals: {
2669
- npm: ["@shortcut/client"],
2670
- envKeys: [/^SHORTCUT_/]
2671
- }
2672
- },
2673
- {
2674
- kind: "Jira",
2675
- label: "Jira",
2676
- mode: "in-cli",
2677
- signals: {
2678
- npm: ["jira-client"],
2679
- python: ["jira", "atlassian-python-api"],
2680
- envKeys: [/^JIRA_/]
2681
- }
2682
- },
2683
- {
2684
- kind: "Confluence",
2685
- label: "Confluence",
2686
- mode: "in-cli",
2687
- signals: { envKeys: [/^CONFLUENCE_/] }
2688
- },
2689
- {
2690
- kind: "Airtable",
2691
- label: "Airtable",
2692
- mode: "in-cli",
2693
- signals: {
2694
- npm: ["airtable"],
2695
- python: ["pyairtable"],
2696
- envKeys: [/^AIRTABLE_/]
2697
- }
2698
- },
2699
- {
2700
- kind: "Smartsheet",
2701
- label: "Smartsheet",
2702
- mode: "in-cli",
2703
- signals: {
2704
- npm: ["smartsheet"],
2705
- python: ["smartsheet-python-sdk"],
2706
- envKeys: [/^SMARTSHEET_/]
2707
- }
2708
- },
2709
- {
2710
- kind: "Coda",
2711
- label: "Coda",
2712
- mode: "in-cli",
2713
- signals: { envKeys: [/^CODA_API_/] }
2714
- },
2715
- {
2716
- kind: "Productboard",
2717
- label: "Productboard",
2718
- mode: "in-cli",
2719
- signals: { envKeys: [/^PRODUCTBOARD_/] }
2720
- },
2721
- {
2722
- kind: "GoogleSheets",
2723
- label: "Google Sheets",
2724
- mode: "in-cli",
2725
- signals: {
2726
- npm: ["google-spreadsheet"],
2727
- python: ["gspread"],
2728
- envKeys: [/^GOOGLE_SHEETS_/]
2729
- }
2730
- },
2731
- {
2732
- kind: "Formbricks",
2733
- label: "Formbricks",
2734
- mode: "in-cli",
2735
- signals: {
2736
- npm: ["@formbricks/js"],
2737
- envKeys: [/^FORMBRICKS_/]
2738
- }
2739
- },
2740
- {
2741
- kind: "Tally",
2742
- label: "Tally",
2743
- mode: "in-cli",
2744
- signals: { envKeys: [/^TALLY_API_KEY$/] }
2745
- },
2746
- {
2747
- kind: "Jotform",
2748
- label: "Jotform",
2749
- mode: "in-cli",
2750
- signals: {
2751
- python: ["jotform"],
2752
- envKeys: [/^JOTFORM_/]
2753
- }
2754
- },
2755
- {
2756
- kind: "GitBook",
2757
- label: "GitBook",
2758
- mode: "in-cli",
2759
- signals: {
2760
- npm: ["@gitbook/api"],
2761
- envKeys: [/^GITBOOK_/]
2762
- }
2763
- },
2764
- {
2765
- kind: "Wordpress",
2766
- label: "WordPress",
2767
- mode: "in-cli",
2768
- signals: {
2769
- npm: ["wpapi"],
2770
- envKeys: [/^WORDPRESS_/]
2771
- }
2772
- },
2773
- {
2774
- kind: "Clockify",
2775
- label: "Clockify",
2776
- mode: "in-cli",
2777
- signals: { envKeys: [/^CLOCKIFY_/] }
2778
- },
2779
- {
2780
- kind: "Calendly",
2781
- label: "Calendly",
2782
- mode: "in-cli",
2783
- signals: { envKeys: [/^CALENDLY_/] }
2784
- },
2785
- {
2786
- kind: "CalCom",
2787
- label: "Cal.com",
2788
- mode: "in-cli",
2789
- signals: { envKeys: [/^CALCOM_/, /^CAL_API_KEY$/] }
2790
- },
2791
- {
2792
- kind: "Eventbrite",
2793
- label: "Eventbrite",
2794
- mode: "in-cli",
2795
- signals: {
2796
- python: ["eventbrite"],
2797
- envKeys: [/^EVENTBRITE_/]
2798
- }
2799
- },
2800
- {
2801
- kind: "BambooHR",
2802
- label: "BambooHR",
2803
- mode: "in-cli",
2804
- signals: {
2805
- python: ["pybamboohr"],
2806
- envKeys: [/^BAMBOOHR_/, /^BAMBOO_HR_/]
2807
- }
2808
- },
2809
- {
2810
- kind: "Personio",
2811
- label: "Personio",
2812
- mode: "in-cli",
2813
- signals: { envKeys: [/^PERSONIO_/] }
2814
- },
2815
- {
2816
- kind: "Rippling",
2817
- label: "Rippling",
2818
- mode: "in-cli",
2819
- signals: { envKeys: [/^RIPPLING_/] }
2820
- },
2821
- {
2822
- kind: "Greenhouse",
2823
- label: "Greenhouse",
2824
- mode: "in-cli",
2825
- signals: { envKeys: [/^GREENHOUSE_/] }
2826
- },
2827
- {
2828
- kind: "Ashby",
2829
- label: "Ashby",
2830
- mode: "in-cli",
2831
- signals: { envKeys: [/^ASHBY_/] }
2832
- },
2833
- {
2834
- kind: "Workable",
2835
- label: "Workable",
2836
- mode: "in-cli",
2837
- signals: { envKeys: [/^WORKABLE_/] }
2838
- },
2839
- {
2840
- kind: "GoogleAds",
2841
- label: "Google Ads",
2842
- mode: "deep-link",
2843
- signals: {
2844
- npm: ["google-ads-api"],
2845
- python: ["google-ads"],
2846
- envKeys: [/^GOOGLE_ADS_/]
2847
- }
2848
- },
2849
- {
2850
- kind: "MetaAds",
2851
- label: "Meta Ads",
2852
- mode: "deep-link",
2853
- signals: {
2854
- npm: ["facebook-nodejs-business-sdk"],
2855
- python: ["facebook-business"],
2856
- envKeys: [/^META_ADS_/, /^FACEBOOK_ADS_/]
2857
- }
2858
- },
2859
- {
2860
- kind: "LinkedinAds",
2861
- label: "LinkedIn Ads",
2862
- mode: "deep-link",
2863
- signals: { envKeys: [/^LINKEDIN_ADS_/] }
2864
- },
2865
- {
2866
- kind: "TikTokAds",
2867
- label: "TikTok Ads",
2868
- mode: "deep-link",
2869
- signals: { envKeys: [/^TIKTOK_ADS_/] }
2870
- },
2871
- {
2872
- kind: "RedditAds",
2873
- label: "Reddit Ads",
2874
- mode: "deep-link",
2875
- signals: { envKeys: [/^REDDIT_ADS_/] }
2876
- },
2877
- {
2878
- kind: "PinterestAds",
2879
- label: "Pinterest Ads",
2880
- mode: "deep-link",
2881
- signals: { envKeys: [/^PINTEREST_ADS_/] }
2882
- },
2883
- {
2884
- kind: "BingAds",
2885
- label: "Bing Ads",
2886
- mode: "deep-link",
2887
- signals: {
2888
- python: ["bingads"],
2889
- envKeys: [/^BING_ADS_/, /^MICROSOFT_ADS_/]
2890
- }
2891
- },
2892
- {
2893
- kind: "AmazonAds",
2894
- label: "Amazon Ads",
2895
- mode: "deep-link",
2896
- signals: { envKeys: [/^AMAZON_ADS_/] }
2897
- },
2898
- {
2899
- kind: "GoogleAnalytics",
2900
- label: "Google Analytics",
2901
- mode: "deep-link",
2902
- signals: {
2903
- npm: ["react-ga4"],
2904
- python: ["google-analytics-data"],
2905
- envKeys: [/^GOOGLE_ANALYTICS_/, /^GA4_/]
2906
- }
2907
- },
2908
- {
2909
- kind: "GoogleSearchConsole",
2910
- label: "Google Search Console",
2911
- mode: "deep-link",
2912
- signals: { envKeys: [/^SEARCH_CONSOLE_/, /^GSC_/] }
2913
- },
2914
- {
2915
- kind: "AppsFlyer",
2916
- label: "AppsFlyer",
2917
- mode: "in-cli",
2918
- signals: {
2919
- npm: ["react-native-appsflyer"],
2920
- envKeys: [/^APPSFLYER_/]
2921
- }
2922
- },
2923
- {
2924
- kind: "Dub",
2925
- label: "Dub",
2926
- mode: "in-cli",
2927
- signals: {
2928
- npm: ["dub"],
2929
- python: ["dub"],
2930
- envKeys: [/^DUB_API_KEY$/]
2931
- }
2932
- }
2933
- ];
2934
- const SOURCE_DETECTORS = [
2935
- ...CORE_SOURCE_DETECTORS,
2936
- ...LLM_SOURCE_DETECTORS,
2937
- ...OTHER_SOURCE_DETECTORS
2938
- ];
2939
- /** Kinds from the LLM / AI section above — the only ones that may set `wizard_ai_sdk_detected`. */
2940
- const AI_SOURCE_KINDS = new Set(LLM_SOURCE_DETECTORS.map((detector) => detector.kind));
2941
- //#endregion
2942
- //#region src/lib/warehouse-sources/detect.ts
2943
- /**
2944
- * Data warehouse source detection.
2945
- *
2946
- * Scans a project for codebase signals (npm/python/ruby deps, `.env` key
2947
- * names) and matches them against the `SOURCE_DETECTORS` registry. Pure
2948
- * function: no store mutations, no UI calls. Reads files locally — only the
2949
- * wizard process does this; the agent never sees secret values.
2950
- *
2951
- * Note we only read `.env` KEY NAMES, never values.
2952
- */
2953
- const MAX_DEPTH = 3;
2954
- const MAX_SIGNAL_SET = MAX_ENV_KEY_SET;
2955
- function addCapped(set, value) {
2956
- if (set.size < MAX_SIGNAL_SET) set.add(value);
2957
- }
2958
- /**
2959
- * Detect which warehouse sources the project at `installDir` appears to use.
2960
- * Returns one `DetectedSource` per matched registry entry (kinds are unique,
2961
- * so the result is naturally deduped).
2962
- */
2963
- function detectWarehouseSources(installDir) {
2964
- const signals = collectSignals(installDir);
2965
- const detected = [];
2966
- for (const detector of SOURCE_DETECTORS) {
2967
- const match = matchDetector(detector, signals);
2968
- if (match) detected.push({
2969
- kind: detector.kind,
2970
- label: detector.label,
2971
- mode: detector.mode,
2972
- matchedSignal: match
2973
- });
2974
- }
2975
- return detected;
2976
- }
2977
- /** Returns a human-readable description of the first matching signal, or null. */
2978
- function matchDetector(detector, signals) {
2979
- const { npm, python, ruby, envKeys } = detector.signals;
2980
- const npmHit = npm?.find((dep) => signals.npm.has(dep));
2981
- if (npmHit) return `found \`${npmHit}\` in package.json`;
2982
- const pyHit = python?.find((dep) => signals.python.has(dep));
2983
- if (pyHit) return `found \`${pyHit}\` in Python dependencies`;
2984
- const rubyHit = ruby?.find((gem) => signals.ruby.has(gem));
2985
- if (rubyHit) return `found \`${rubyHit}\` in Gemfile`;
2986
- if (envKeys) {
2987
- for (const [key, file] of signals.envKeys) if (envKeys.some((re) => re.test(key))) return `found \`${key}\` in \`${file}\``;
2988
- }
2989
- return null;
2990
- }
2991
- function collectSignals(installDir) {
2992
- const signals = {
2993
- npm: /* @__PURE__ */ new Set(),
2994
- python: /* @__PURE__ */ new Set(),
2995
- ruby: /* @__PURE__ */ new Set(),
2996
- envKeys: /* @__PURE__ */ new Map()
2997
- };
2998
- walkProjectFiles(installDir, (name, fullPath) => ingestFile(installDir, name, fullPath, signals), MAX_DEPTH);
2999
- return signals;
3000
- }
3001
- /**
3002
- * Route a file to the right parser BY NAME, reading its contents only when the
3003
- * name matches one of the ~6 manifests we care about. Checking the name first
3004
- * avoids slurping every file in the tree (lockfiles, binaries, assets) into
3005
- * memory just to discard it.
3006
- */
3007
- function ingestFile(installDir, name, fullPath, signals) {
3008
- const ingest = ingestorFor(name);
3009
- if (!ingest) return;
3010
- const content = safeReadFile(fullPath);
3011
- if (content === null) return;
3012
- ingest(content, signals, toPromptSafeRelativePath(installDir, fullPath));
3013
- }
3014
- /** Pick the parser for a manifest filename, or null if it's not one we read. */
3015
- function ingestorFor(name) {
3016
- if (name === "package.json") return addNpmDeps;
3017
- if (name === "requirements.txt") return (c, s) => parseRequirementsTxt(c).forEach((d) => addCapped(s.python, d));
3018
- if (name === "pyproject.toml") return (c, s) => parsePyprojectToml(c).forEach((d) => addCapped(s.python, d));
3019
- if (name === "Pipfile") return (c, s) => parsePipfile(c).forEach((d) => addCapped(s.python, d));
3020
- if (name === "Gemfile") return (c, s) => parseGemfile(c).forEach((g) => addCapped(s.ruby, g));
3021
- if (isEnvFileName(name)) return (c, s, relativePath) => addEnvKeys(c, s, relativePath);
3022
- return null;
3023
- }
3024
- /**
3025
- * Record each key NAME against the file that defined it. The first file wins,
3026
- * so the reported signal stays stable when several `.env*` files repeat a key.
3027
- * Values are never read.
3028
- */
3029
- function addEnvKeys(content, signals, relativePath) {
3030
- for (const key of parseEnvKeyNames(content)) {
3031
- if (signals.envKeys.has(key)) continue;
3032
- if (signals.envKeys.size >= MAX_SIGNAL_SET) return;
3033
- signals.envKeys.set(key, relativePath);
3034
- }
3035
- }
3036
- function addNpmDeps(content, signals) {
3037
- try {
3038
- const pkg = JSON.parse(content);
3039
- for (const dep of Object.keys({
3040
- ...pkg.dependencies,
3041
- ...pkg.devDependencies
3042
- })) addCapped(signals.npm, dep);
3043
- } catch (error) {
3044
- analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "detectWarehouseSources.parsePackageJson" });
3045
- }
3046
- }
3047
- /** Extract gem names from `gem 'name'` declarations. */
3048
- function parseGemfile(content) {
3049
- const gems = [];
3050
- for (const match of content.matchAll(/^\s*gem\s+['"]([^'"]+)['"]/gm)) gems.push(match[1]);
3051
- return gems;
3052
- }
3053
- //#endregion
3054
- //#region src/lib/programs/warehouse-source/detect.ts
3055
- /**
3056
- * Warehouse-source program detection step.
3057
- *
3058
- * Thin adapter over `detectWarehouseSources` that writes results into
3059
- * frameworkContext for the intro screen, plus the `[ABORT]` cases the
3060
- * data-warehouse-source-setup skill can emit.
3061
- */
3062
- /** frameworkContext key holding the detected sources (set on success). */
3063
- const DETECTED_WAREHOUSE_SOURCES_KEY = "detectedWarehouseSources";
3064
- /**
3065
- * Read the detected sources out of frameworkContext. Single accessor shared by
3066
- * the intro screen and the prompt builder so the key + cast live in one place.
3067
- */
3068
- function getDetectedWarehouseSources(session) {
3069
- return session.frameworkContext["detectedWarehouseSources"] ?? [];
3070
- }
3071
- /** `[ABORT] <reason>` cases the skill can emit. */
3072
- const WAREHOUSE_ABORT_CASES = [{
3073
- match: /^no data sources? detected\.?$/i,
3074
- message: "No data source detected",
3075
- body: "The agent could not confirm a data warehouse source to connect. Run this command from a project that uses a supported source (a database, Stripe, etc.).",
3076
- docsUrl: "https://posthog.com/docs/data-warehouse"
3077
- }, {
3078
- match: /^source creation failed\.?$/i,
3079
- message: "Source creation failed",
3080
- body: "PostHog could not create the data warehouse source with the credentials provided. Double-check the connection details and try again, or set the source up directly in the PostHog app.",
3081
- docsUrl: "https://posthog.com/docs/data-warehouse"
3082
- }];
3083
- /**
3084
- * Scan `session.installDir` for warehouse-source signals. Writes the detected
3085
- * sources (or a `detectError`) into frameworkContext for the intro screen.
3086
- */
3087
- function detectWarehousePrerequisites(session, setFrameworkContext) {
3088
- const fail = (error) => setFrameworkContext("detectError", error);
3089
- const installDir = session.installDir;
3090
- if (!existsSync(installDir)) {
3091
- fail({
3092
- kind: "bad-directory",
3093
- path: installDir,
3094
- reason: "missing"
3095
- });
3096
- return;
3097
- }
3098
- try {
3099
- if (!statSync(installDir).isDirectory()) {
3100
- fail({
3101
- kind: "bad-directory",
3102
- path: installDir,
3103
- reason: "not-dir"
3104
- });
3105
- return;
3106
- }
3107
- } catch (error) {
3108
- analytics.captureException(error instanceof Error ? error : new Error(String(error)), {
3109
- step: "detectWarehousePrerequisites.stat",
3110
- path: installDir
3111
- });
3112
- fail({
3113
- kind: "bad-directory",
3114
- path: installDir,
3115
- reason: "unreadable"
3116
- });
3117
- return;
3118
- }
3119
- const sources = detectWarehouseSources(installDir);
3120
- if (sources.length === 0) {
3121
- fail({ kind: "no-sources" });
3122
- return;
3123
- }
3124
- analytics.setTag("warehouse_source_kinds", sources.map((s) => s.kind).join(","));
3125
- analytics.setTag("warehouse_source_modes", sources.map((s) => `${s.kind}:${s.mode}`).join(","));
3126
- analytics.setTag("warehouse_source_count", sources.length);
3127
- setFrameworkContext(DETECTED_WAREHOUSE_SOURCES_KEY, sources);
3128
- }
3129
- //#endregion
3130
- //#region src/lib/programs/shared/health-check-step.ts
3131
- function healthCheckReady(session) {
3132
- if (!session.readinessResult) return false;
3133
- if (session.signup) {
3134
- const hardBlocking = getBlockingServiceKeys(session.readinessResult.health, SIGNUP_WIZARD_READINESS_CONFIG);
3135
- const defaultBlocking = getBlockingServiceKeys(session.readinessResult.health);
3136
- if (hardBlocking.length === 0 && defaultBlocking.length === 0) return true;
3137
- return session.outageDismissed;
3138
- }
3139
- if (session.readinessResult.decision === "no") return session.outageDismissed;
3140
- return true;
3141
- }
3142
- const HEALTH_CHECK_STEP = {
3143
- id: "health-check",
3144
- label: "Health check",
3145
- screenId: "health-check",
3146
- gate: healthCheckReady,
3147
- onInit: (ctx) => {
3148
- evaluateWizardReadiness().then((readiness) => {
3149
- logToFile(`[health-checks] TUI pre-flight complete: decision=${readiness.decision}`);
3150
- ctx.setReadinessResult(readiness);
3151
- }).catch((err) => {
3152
- logToFile("[health-checks] TUI pre-flight failed:", err);
3153
- ctx.setReadinessResult({
3154
- decision: "yes",
3155
- health: {},
3156
- reasons: []
3157
- });
3158
- });
3159
- }
3160
- };
3161
- //#endregion
3162
- //#region src/lib/programs/posthog-integration/detect.ts
3163
- async function detectPostHogIntegration(ctx) {
3164
- const session = ctx.session;
3165
- const installDir = session.installDir;
3166
- const detectedIntegration = await detectFramework(installDir);
3167
- if (detectedIntegration) {
3168
- const config = FRAMEWORK_REGISTRY[detectedIntegration];
3169
- const sessionOptions = {
3170
- installDir,
3171
- debug: session.debug,
3172
- signup: session.signup,
3173
- ci: session.ci,
3174
- benchmark: session.benchmark,
3175
- yaraReport: session.yaraReport
3176
- };
3177
- const context = await gatherFrameworkContext(config, sessionOptions);
3178
- for (const [key, value] of Object.entries(context)) if (!(key in session.frameworkContext)) ctx.setFrameworkContext(key, value);
3179
- ctx.setFrameworkConfig(detectedIntegration, config);
3180
- ctx.setSkillId(detectedIntegration);
3181
- if (!session.detectedFrameworkLabel) ctx.setDetectedFramework(config.metadata.name);
3182
- const versionResult = await checkFrameworkVersion(config, sessionOptions);
3183
- if (versionResult.supported !== true) ctx.setUnsupportedVersion(versionResult.supported);
3184
- }
3185
- for (const feature of discoverFeatures(installDir)) ctx.addDiscoveredFeature(feature);
3186
- detectWarehouseSourcesForSuggestion(ctx, installDir);
3187
- ctx.setDetectionComplete();
3188
- }
3189
- /**
3190
- * How the scan went, for the reporter to read later. Three states, and the
3191
- * absent one carries the most weight:
3192
- *
3193
- * absent this program never scanned. Only `posthog-integration` runs the
3194
- * scan below, but every program's intro screen resolves consent
3195
- * through the same store method, so the reporter is reached on runs
3196
- * where no scan ever happened.
3197
- * 'failed' the scan threw. A zero-count row would read as "scanned, found
3198
- * nothing", which is a different fact.
3199
- * 'ok' the scan ran. Zero sources is a real result and gets reported.
3200
- */
3201
- const WAREHOUSE_SCAN_STATE_KEY = "warehouseScanState";
3202
- /**
3203
- * Scan for data warehouse source signals (Postgres, Stripe, Hubspot, …) and,
3204
- * when found, stash them for a `wizard warehouse` suggestion on the outro.
3205
- * Runs unconditionally during `detect`, before the intro screen has asked
3206
- * about sharing. Reporting what this finds is a separate, later concern.
3207
- * See `reportWarehouseSourcesDetected` below.
3208
- *
3209
- * Deliberately a suggestion, not an inline agent run: a second credential-
3210
- * collecting agent run before the outro could `process.exit()` on any of its
3211
- * failure paths and cost the user the success outro on a run where PostHog
3212
- * installed fine.
3213
- *
3214
- * Calls `detectWarehouseSources` (a pure scan) rather than the warehouse
3215
- * program's `detectWarehousePrerequisites`, which writes a `detectError` when
3216
- * it finds nothing — that's right for `wizard warehouse` but here would show a
3217
- * spurious error and trip the CI prerequisites abort. No sources is a no-op.
3218
- *
3219
- * Best-effort: never let a scan failure break the wizard flow.
3220
- */
3221
- function detectWarehouseSourcesForSuggestion(ctx, installDir) {
3222
- try {
3223
- const sources = detectWarehouseSources(installDir);
3224
- ctx.setFrameworkContext(WAREHOUSE_SCAN_STATE_KEY, "ok");
3225
- if (sources.length === 0) return;
3226
- ctx.setFrameworkContext(DETECTED_WAREHOUSE_SOURCES_KEY, sources);
3227
- } catch (error) {
3228
- ctx.setFrameworkContext(WAREHOUSE_SCAN_STATE_KEY, "failed");
3229
- analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "detectWarehouseSourcesForSuggestion" });
3230
- }
3231
- }
3232
- function hasAiSdkEvidence(session, sources) {
3233
- return sources.some((s) => AI_SOURCE_KINDS.has(s.kind)) || session.discoveredFeatures.includes("llm");
3234
- }
3235
- /**
3236
- * Boolean only, on the org, never the list of kinds or any non-AI tool: a
3237
- * decline must not leak even the shape of what local detection saw.
3238
- */
3239
- function stampAiSdkDetected(session, sources) {
3240
- const organizationId = session.apiUser?.organization?.id;
3241
- if (!organizationId) return;
3242
- if (!hasAiSdkEvidence(session, sources)) return;
3243
- analytics.groupIdentify("organization", organizationId, { wizard_ai_sdk_detected: true });
3244
- }
3245
- /**
3246
- * The single place scan results become telemetry. Called from
3247
- * `WizardStore.completeSetup()`, the point consent becomes final — the privacy
3248
- * panel's choice is reversible until then, so nothing may report earlier.
3249
- *
3250
- * Returns true when consent has resolved and the caller should latch
3251
- * `warehouseSourcesReported`, which is not the same as "this sent something":
3252
- * a decline, a failed scan, and a program that never scanned all resolve
3253
- * without sending.
3254
- */
3255
- function reportWarehouseSourcesDetected(session) {
3256
- if (session.warehouseSourcesReported) return false;
3257
- if (session.scanConsent === "undecided") return false;
3258
- if (mayReportScanResults(session)) {
3259
- const sources = getDetectedWarehouseSources(session);
3260
- stampAiSdkDetected(session, sources);
3261
- if (session.frameworkContext[WAREHOUSE_SCAN_STATE_KEY] !== "ok") return true;
3262
- analytics.wizardCapture("warehouse sources detected", {
3263
- warehouse_source_count: sources.length,
3264
- warehouse_source_kinds: sources.map((s) => s.kind),
3265
- warehouse_source_modes: sources.map((s) => s.mode)
3266
- });
3267
- if (sources.length > 0) {
3268
- analytics.setTag("warehouse_source_kinds", sources.map((s) => s.kind).join(","));
3269
- analytics.setTag("warehouse_source_count", sources.length);
3270
- }
3271
- }
3272
- return true;
3273
- }
3274
- //#endregion
3275
- //#region src/lib/programs/posthog-integration/steps.ts
3276
- function needsSetup(session) {
3277
- const config = session.frameworkConfig;
3278
- if (!config?.metadata.setup?.questions) return false;
3279
- return config.metadata.setup.questions.some((q) => !(q.key in session.frameworkContext));
3280
- }
3281
- const POSTHOG_INTEGRATION_PROGRAM = [
3282
- {
3283
- id: "detect",
3284
- label: "Detecting framework",
3285
- onReady: (ctx) => detectPostHogIntegration(ctx)
3286
- },
3287
- {
3288
- id: "intro",
3289
- label: "Welcome",
3290
- screenId: "intro",
3291
- gate: (session) => session.setupConfirmed
3292
- },
3293
- HEALTH_CHECK_STEP,
3294
- {
3295
- id: "setup",
3296
- label: "Setup",
3297
- screenId: "setup",
3298
- show: needsSetup,
3299
- isComplete: (session) => !needsSetup(session)
3300
- },
3301
- {
3302
- id: "auth",
3303
- label: "Authentication",
3304
- screenId: "auth",
3305
- isComplete: (session) => session.credentials !== null
3306
- },
3307
- {
3308
- id: "run",
3309
- label: "Integration",
3310
- screenId: "run",
3311
- isComplete: (session) => session.runPhase === "completed" || session.runPhase === "error"
3312
- },
3313
- {
3314
- id: "outro",
3315
- label: "Done",
3316
- screenId: "outro",
3317
- isComplete: (session) => session.outroDismissed
3318
- },
3319
- {
3320
- id: "mcp",
3321
- label: "MCP servers",
3322
- screenId: "mcp",
3323
- isComplete: (session) => session.mcpComplete
3324
- },
3325
- {
3326
- id: "slack-connect",
3327
- label: "Connect Slack",
3328
- screenId: "slack-connect",
3329
- isComplete: (session) => session.slackStepDismissed
3330
- },
3331
- {
3332
- id: "keep-skills",
3333
- label: "Keep Skills",
3334
- screenId: "keep-skills"
3335
- }
3336
- ];
3337
- //#endregion
3338
- //#region src/ui/tui/styles.ts
3339
- const Colors = {
3340
- primary: "cyan",
3341
- accent: "#DC9300",
3342
- titleColor: "#3D2800",
3343
- success: "green",
3344
- error: "red",
3345
- muted: "gray"
3346
- };
3347
- const Icons = {
3348
- diamond: "◆",
3349
- diamondOpen: "◇",
3350
- check: "✔",
3351
- warning: "⚠",
3352
- squareFilled: "◼",
3353
- squareOpen: "◻",
3354
- triangleRight: "▶",
3355
- triangleSmallRight: "▸",
3356
- bullet: "•"
3357
- };
3358
- //#endregion
3359
- //#region src/ui/tui/primitives/text-helpers.ts
3360
- /**
3361
- * Text-splitting helpers for sentence boundary detection.
3362
- * Used by TextBlock for animation pauses at punctuation.
3363
- */
3364
- /** Split text into sentences (keeps the delimiter attached) */
3365
- function splitSentences(text) {
3366
- const parts = [];
3367
- const re = /[^.!?]*[.!?]+\s*/g;
3368
- let match;
3369
- let lastIndex = 0;
3370
- while ((match = re.exec(text)) !== null) {
3371
- parts.push(match[0]);
3372
- lastIndex = re.lastIndex;
3373
- }
3374
- if (lastIndex < text.length) parts.push(text.slice(lastIndex));
3375
- return parts;
3376
- }
3377
- /** Build a set of character indices where sentences end (for typewriter pause) */
3378
- function sentenceEndChars(text) {
3379
- const ends = /* @__PURE__ */ new Set();
3380
- const sentences = splitSentences(text);
3381
- let pos = 0;
3382
- for (const s of sentences) {
3383
- pos += s.length;
3384
- ends.add(pos - 1);
3385
- }
3386
- return ends;
3387
- }
3388
- /** Build a set of word indices where sentences end (for word-by-word pause) */
3389
- function sentenceEndWords(text) {
3390
- const ends = /* @__PURE__ */ new Set();
3391
- const sentences = splitSentences(text);
3392
- let wordCount = 0;
3393
- for (const s of sentences) {
3394
- const words = s.trim().split(/\s+/).filter(Boolean);
3395
- wordCount += words.length;
3396
- ends.add(wordCount - 1);
3397
- }
3398
- return ends;
3399
- }
3400
- //#endregion
3401
- //#region src/ui/tui/primitives/content-types.ts
3402
- /** Type guard for lines blocks. */
3403
- function isLinesBlock(block) {
3404
- return typeof block !== "string" && "type" in block && block.type === "lines";
3405
- }
3406
- /** Type guard for clear blocks. */
3407
- function isClearBlock(block) {
3408
- return typeof block !== "string" && "type" in block && block.type === "clear";
3409
- }
3410
- /** Type guard for object blocks (text or node content). */
3411
- function isObjectBlock(block) {
3412
- return typeof block !== "string" && !("type" in block);
3413
- }
3414
- //#endregion
3415
- //#region src/ui/tui/primitives/layout-helpers.ts
3416
- /**
3417
- * Estimate the number of terminal rows a content block will occupy,
3418
- * including 1 row of marginBottom.
3419
- */
3420
- function estimateBlockHeight(block, availableWidth) {
3421
- if (typeof block === "string") return wordWrap(block, availableWidth).length + 1;
3422
- if (isClearBlock(block)) return 0;
3423
- if (isLinesBlock(block)) return block.lines.length + 1;
3424
- if (isObjectBlock(block)) {
3425
- if (typeof block.content === "string") return wordWrap(block.content, availableWidth).length + 1;
3426
- return 4;
3427
- }
3428
- return 1;
3429
- }
3430
- /**
3431
- * Given all blocks, the active index, available width, and a row budget,
3432
- * return [startIdx, endIdx] — the range of blocks to render.
3433
- *
3434
- * Always includes activeIdx. Walks backward to include as many completed
3435
- * blocks as fit within maxHeight.
3436
- */
3437
- function computeVisibleRange(blocks, activeIdx, availableWidth, maxHeight) {
3438
- const budget = Math.max(4, maxHeight - 2);
3439
- let totalHeight = estimateBlockHeight(blocks[activeIdx], availableWidth);
3440
- let start = activeIdx;
3441
- for (let i = activeIdx - 1; i >= 0; i--) {
3442
- if (isClearBlock(blocks[i])) break;
3443
- const h = estimateBlockHeight(blocks[i], availableWidth);
3444
- if (totalHeight + h > budget) break;
3445
- totalHeight += h;
3446
- start = i;
3447
- }
3448
- return [start, activeIdx];
3449
- }
3450
- /**
3451
- * Word-wrap text at clean word boundaries. Always returns pre-wrapped text
3452
- * joined with \n — this avoids Ink's native wrap which can leave leading
3453
- * spaces on continuation lines.
3454
- *
3455
- * Uses a 1-char safety margin so slight width estimate mismatches don't
3456
- * cause Ink to re-wrap our already-wrapped lines.
3457
- */
3458
- function wordWrap(text, availableWidth) {
3459
- const safeWidth = Math.max(10, availableWidth - 1);
3460
- const words = text.split(/\s+/);
3461
- const lines = [];
3462
- let currentLine = "";
3463
- for (const word of words) if (currentLine.length + word.length + 1 > safeWidth && currentLine.length > 0) {
3464
- lines.push(currentLine);
3465
- currentLine = word;
3466
- } else currentLine += (currentLine.length > 0 ? " " : "") + word;
3467
- if (currentLine.length > 0) lines.push(currentLine);
3468
- return lines;
3469
- }
3470
- /**
3471
- * Word-wrap text and return only the last `maxRows` lines.
3472
- * Used for intra-block truncation when a single text block exceeds the viewport.
3473
- * Also used for normal rendering to avoid Ink's leading-space wrap artifacts.
3474
- */
3475
- function wrapAndTruncate(text, availableWidth, maxRows) {
3476
- const lines = wordWrap(text, availableWidth);
3477
- if (lines.length <= maxRows) return lines.join("\n");
3478
- return lines.slice(-maxRows).join("\n");
3479
- }
3480
- //#endregion
3481
- //#region src/ui/tui/primitives/TextBlock.tsx
3482
- /**
3483
- * TextBlock — Animates a single string paragraph.
3484
- *
3485
- * Self-contained: owns its own animIdx and timer.
3486
- * Calls onComplete() when the animation finishes.
3487
- *
3488
- * Five animation modes:
3489
- * 1. Typewriter — character-by-character reveal
3490
- * 2. Word by word — each word appears in order
3491
- * 3. Sentence by sentence — sentences appear one at a time
3492
- * 4. Paragraph fade — paragraph appears at full opacity immediately
3493
- * 5. Sentence fade — paragraph dim, sentences light up in order
3494
- */
3495
- /** Default interval per mode (ms) */
3496
- const TEXT_REVEAL_MODE_DEFAULTS = {
3497
- [1]: 240,
3498
- [0]: 32,
3499
- [2]: 1800,
3500
- [3]: 4800,
3501
- [4]: 2400
3502
- };
3503
- const TextBlock = ({ text, active, completed, onComplete, mode, bullet, animationInterval, sentenceInterval = 1600, maxHeight, availableWidth, dimWhenComplete = true }) => {
3504
- const speed = animationInterval ?? TEXT_REVEAL_MODE_DEFAULTS[mode];
3505
- const [animIdx, setAnimIdx] = useState(mode === 4 ? 1 : 0);
3506
- const resetRef = useRef(0);
3507
- const prevMode = useRef(mode);
3508
- if (prevMode.current !== mode) {
3509
- prevMode.current = mode;
3510
- resetRef.current += 1;
3511
- setAnimIdx(mode === 4 ? 1 : 0);
3512
- }
3513
- const words = text.split(/\s+/);
3514
- const sentences = splitSentences(text);
3515
- const sentenceCharEnds = useMemo(() => sentenceEndChars(text), [text]);
3516
- const sentenceWordEnds = useMemo(() => sentenceEndWords(text), [text]);
3517
- const isDone = mode === 0 ? animIdx >= text.length : mode === 3 ? true : mode === 1 ? animIdx >= words.length : mode === 4 || mode === 2 ? animIdx >= sentences.length : true;
3518
- useEffect(() => {
3519
- if (isDone && active) onComplete();
3520
- }, [
3521
- isDone,
3522
- active,
3523
- onComplete
3524
- ]);
3525
- useEffect(() => {
3526
- if (!active || mode === 3 || isDone) return;
3527
- const token = resetRef.current;
3528
- const isFirstTick = animIdx === 0;
3529
- let delay = isFirstTick ? 0 : speed;
3530
- if (!isFirstTick && mode === 0 && animIdx > 0 && sentenceCharEnds.has(animIdx - 1)) delay = sentenceInterval;
3531
- else if (!isFirstTick && mode === 1 && animIdx > 0 && sentenceWordEnds.has(animIdx - 1)) delay = sentenceInterval;
3532
- const timer = setTimeout(() => {
3533
- if (token !== resetRef.current) return;
3534
- setAnimIdx((c) => c + 1);
3535
- }, delay);
3536
- return () => clearTimeout(timer);
3537
- }, [
3538
- active,
3539
- mode,
3540
- animIdx,
3541
- isDone,
3542
- speed,
3543
- sentenceInterval,
3544
- sentenceCharEnds,
3545
- sentenceWordEnds
3546
- ]);
3547
- const wrap = (visibleText) => {
3548
- if (availableWidth == null) return visibleText;
3549
- if (maxHeight == null) return wrapAndTruncate(visibleText, availableWidth, Infinity);
3550
- return wrapAndTruncate(visibleText, availableWidth, maxHeight);
3551
- };
3552
- if (completed) return /* @__PURE__ */ jsxs(Text, {
3553
- dimColor: dimWhenComplete,
3554
- children: [bullet, wrap(text)]
3555
- });
3556
- if (mode === 0) {
3557
- const revealed = text.slice(0, animIdx);
3558
- return /* @__PURE__ */ jsxs(Text, { children: [
3559
- bullet,
3560
- wrap(/[.!?]\s*$/.test(revealed) ? revealed.trimEnd() : revealed),
3561
- /* @__PURE__ */ jsx(Text, {
3562
- color: Colors.muted,
3563
- children: "▌"
3564
- })
3565
- ] });
3566
- }
3567
- if (mode === 1) return /* @__PURE__ */ jsxs(Text, { children: [bullet, wrap(words.slice(0, animIdx).join(" "))] });
3568
- if (mode === 3) return /* @__PURE__ */ jsxs(Text, { children: [bullet, wrap(text)] });
3569
- if (mode === 2) return /* @__PURE__ */ jsxs(Text, { children: [bullet, wrap(sentences.slice(0, animIdx).join(""))] });
3570
- return /* @__PURE__ */ jsxs(Text, { children: [bullet, sentences.map((s, si) => /* @__PURE__ */ jsx(Text, {
3571
- dimColor: si >= animIdx,
3572
- children: s
3573
- }, si))] });
3574
- };
3575
- //#endregion
3576
- //#region src/ui/tui/components/StatusPeekTrigger.tsx
3577
- /**
3578
- * StatusPeekTrigger — Fires the status-bar expansion once, renders a hint.
3579
- *
3580
- * Module-level `peekedOnce` guards against re-mounts (resize, tab switch)
3581
- * so the peek only happens a single time per process.
3582
- */
3583
- let peekedOnce = false;
3584
- const StatusPeekTrigger = ({ store, duration = 1e4 }) => {
3585
- useEffect(() => {
3586
- if (peekedOnce) return;
3587
- peekedOnce = true;
3588
- store?.setStatusExpanded(true);
3589
- setTimeout(() => {
3590
- store?.setStatusExpanded(false);
3591
- }, duration);
3592
- }, [store, duration]);
3593
- return /* @__PURE__ */ jsx(Text, { children: "You can view the Wizard's status below." });
3594
- };
3595
- //#endregion
3596
- //#region src/lib/programs/posthog-integration/content/data-flow.tsx
3597
- /**
3598
- * POSTHOG_DATA_FLOW — ASCII diagram of the PostHog event ingestion flow.
3599
- */
3600
- const POSTHOG_DATA_FLOW = {
3601
- type: "lines",
3602
- interval: 500,
3603
- pause: 8e3,
3604
- lines: [
3605
- /* @__PURE__ */ jsx(Text, {
3606
- color: "gray",
3607
- children: " ┌──────────────────────────────┐"
3608
- }),
3609
- /* @__PURE__ */ jsxs(Text, { children: [
3610
- /* @__PURE__ */ jsx(Text, {
3611
- color: "gray",
3612
- children: " │ "
3613
- }),
3614
- /* @__PURE__ */ jsx(Text, {
3615
- bold: true,
3616
- color: "cyan",
3617
- children: "Your App"
3618
- }),
3619
- /* @__PURE__ */ jsx(Text, {
3620
- color: "gray",
3621
- children: " │"
3622
- })
3623
- ] }),
3624
- /* @__PURE__ */ jsxs(Text, { children: [
3625
- /* @__PURE__ */ jsx(Text, {
3626
- color: "gray",
3627
- children: " │ │ "
3628
- }),
3629
- /* @__PURE__ */ jsx(Text, { children: "posthog.capture()" }),
3630
- /* @__PURE__ */ jsx(Text, {
3631
- color: "gray",
3632
- children: " │"
3633
- })
3634
- ] }),
3635
- /* @__PURE__ */ jsxs(Text, { children: [
3636
- /* @__PURE__ */ jsx(Text, {
3637
- color: "gray",
3638
- children: " │ │ "
3639
- }),
3640
- /* @__PURE__ */ jsx(Text, {
3641
- dimColor: true,
3642
- children: "custom events"
3643
- }),
3644
- /* @__PURE__ */ jsx(Text, {
3645
- color: "gray",
3646
- children: " │"
3647
- })
3648
- ] }),
3649
- /* @__PURE__ */ jsxs(Text, { children: [
3650
- /* @__PURE__ */ jsx(Text, {
3651
- color: "gray",
3652
- children: " │ │ "
3653
- }),
3654
- /* @__PURE__ */ jsx(Text, {
3655
- dimColor: true,
3656
- children: "custom properties"
3657
- }),
3658
- /* @__PURE__ */ jsx(Text, {
3659
- color: "gray",
3660
- children: " │"
3661
- })
3662
- ] }),
3663
- /* @__PURE__ */ jsxs(Text, { children: [
3664
- /* @__PURE__ */ jsx(Text, {
3665
- color: "gray",
3666
- children: " │ │ "
3667
- }),
3668
- /* @__PURE__ */ jsx(Text, {
3669
- dimColor: true,
3670
- children: "person profiles"
3671
- }),
3672
- /* @__PURE__ */ jsx(Text, {
3673
- color: "gray",
3674
- children: " │"
3675
- })
3676
- ] }),
3677
- /* @__PURE__ */ jsxs(Text, { children: [
3678
- /* @__PURE__ */ jsx(Text, {
3679
- color: "gray",
3680
- children: " │ ↓ "
3681
- }),
3682
- /* @__PURE__ */ jsx(Text, {
3683
- dimColor: true,
3684
- children: "groups"
3685
- }),
3686
- /* @__PURE__ */ jsx(Text, {
3687
- color: "gray",
3688
- children: " │"
3689
- })
3690
- ] }),
3691
- /* @__PURE__ */ jsxs(Text, { children: [
3692
- /* @__PURE__ */ jsx(Text, {
3693
- color: "gray",
3694
- children: " │ "
3695
- }),
3696
- /* @__PURE__ */ jsx(Text, {
3697
- bold: true,
3698
- color: Colors.accent,
3699
- children: "PostHog SDK"
3700
- }),
3701
- /* @__PURE__ */ jsx(Text, {
3702
- color: "gray",
3703
- children: " │"
3704
- })
3705
- ] }),
3706
- /* @__PURE__ */ jsxs(Text, { children: [
3707
- /* @__PURE__ */ jsx(Text, {
3708
- color: "gray",
3709
- children: " │ ↓ "
3710
- }),
3711
- /* @__PURE__ */ jsx(Text, { children: "HTTP" }),
3712
- /* @__PURE__ */ jsx(Text, {
3713
- color: "gray",
3714
- children: " │"
3715
- })
3716
- ] }),
3717
- /* @__PURE__ */ jsxs(Text, { children: [
3718
- /* @__PURE__ */ jsx(Text, {
3719
- color: "gray",
3720
- children: " │ "
3721
- }),
3722
- /* @__PURE__ */ jsx(Text, {
3723
- bold: true,
3724
- color: Colors.accent,
3725
- children: "PostHog Cloud"
3726
- }),
3727
- /* @__PURE__ */ jsx(Text, {
3728
- color: "gray",
3729
- children: " │"
3730
- })
3731
- ] }),
3732
- /* @__PURE__ */ jsxs(Text, { children: [
3733
- /* @__PURE__ */ jsx(Text, {
3734
- color: "gray",
3735
- children: " │ ↓ "
3736
- }),
3737
- /* @__PURE__ */ jsx(Text, { children: "query + visualize" }),
3738
- /* @__PURE__ */ jsx(Text, {
3739
- color: "gray",
3740
- children: " │"
3741
- })
3742
- ] }),
3743
- /* @__PURE__ */ jsxs(Text, { children: [
3744
- /* @__PURE__ */ jsx(Text, {
3745
- color: "gray",
3746
- children: " │ "
3747
- }),
3748
- /* @__PURE__ */ jsx(Text, {
3749
- bold: true,
3750
- color: "green",
3751
- children: "Dashboards & Insights"
3752
- }),
3753
- /* @__PURE__ */ jsx(Text, {
3754
- color: "gray",
3755
- children: " │"
3756
- })
3757
- ] }),
3758
- /* @__PURE__ */ jsx(Text, {
3759
- color: "gray",
3760
- children: " └──────────────────────────────┘"
3761
- })
3762
- ]
3763
- };
3764
- //#endregion
3765
- //#region src/lib/programs/posthog-integration/content/product-suite.tsx
3766
- /**
3767
- * PRODUCT_SUITE_BLOCK — Two-column listing of PostHog products.
3768
- */
3769
- const PRODUCT_SUITE_BLOCK = {
3770
- type: "lines",
3771
- interval: 1e3,
3772
- pause: 15e3,
3773
- lines: [
3774
- /* @__PURE__ */ jsxs(Text, { children: [
3775
- /* @__PURE__ */ jsx(Text, {
3776
- color: "cyan",
3777
- children: " ◆ "
3778
- }),
3779
- "Product Analytics ",
3780
- /* @__PURE__ */ jsx(Text, {
3781
- color: "cyan",
3782
- children: "◆ "
3783
- }),
3784
- "Error Tracking"
3785
- ] }),
3786
- /* @__PURE__ */ jsxs(Text, { children: [
3787
- /* @__PURE__ */ jsx(Text, {
3788
- color: "cyan",
3789
- children: " ◆ "
3790
- }),
3791
- "Web Analytics ",
3792
- /* @__PURE__ */ jsx(Text, {
3793
- color: "cyan",
3794
- children: "◆ "
3795
- }),
3796
- "Session Replay"
3797
- ] }),
3798
- /* @__PURE__ */ jsxs(Text, { children: [
3799
- /* @__PURE__ */ jsx(Text, {
3800
- color: "cyan",
3801
- children: " ◆ "
3802
- }),
3803
- "Feature Flags ",
3804
- /* @__PURE__ */ jsx(Text, {
3805
- color: "cyan",
3806
- children: "◆ "
3807
- }),
3808
- "Data Pipelines"
3809
- ] }),
3810
- /* @__PURE__ */ jsxs(Text, { children: [
3811
- /* @__PURE__ */ jsx(Text, {
3812
- color: "cyan",
3813
- children: " ◆ "
3814
- }),
3815
- "Experiments ",
3816
- /* @__PURE__ */ jsx(Text, {
3817
- color: "cyan",
3818
- children: "◆ "
3819
- }),
3820
- "Data Warehouse"
3821
- ] }),
3822
- /* @__PURE__ */ jsxs(Text, { children: [
3823
- /* @__PURE__ */ jsx(Text, {
3824
- color: "cyan",
3825
- children: " ◆ "
3826
- }),
3827
- "AI Observability ",
3828
- /* @__PURE__ */ jsx(Text, {
3829
- color: "cyan",
3830
- children: "◆ "
3831
- }),
3832
- "Surveys"
3833
- ] }),
3834
- /* @__PURE__ */ jsxs(Text, { children: [
3835
- /* @__PURE__ */ jsx(Text, {
3836
- color: "cyan",
3837
- children: " ◆ "
3838
- }),
3839
- "Workflows ",
3840
- /* @__PURE__ */ jsx(Text, {
3841
- color: "cyan",
3842
- children: "◆ "
3843
- }),
3844
- "Logs"
3845
- ] }),
3846
- /* @__PURE__ */ jsxs(Text, { children: [
3847
- /* @__PURE__ */ jsx(Text, {
3848
- color: "cyan",
3849
- children: " ◆ "
3850
- }),
3851
- "Product Tours ",
3852
- /* @__PURE__ */ jsx(Text, {
3853
- color: "cyan",
3854
- children: "◆ "
3855
- }),
3856
- "Support"
3857
- ] }),
3858
- /* @__PURE__ */ jsxs(Text, { children: [
3859
- /* @__PURE__ */ jsx(Text, {
3860
- color: "cyan",
3861
- children: " ◆ "
3862
- }),
3863
- "Revenue Analytics ",
3864
- /* @__PURE__ */ jsx(Text, {
3865
- color: "cyan",
3866
- children: "◆ "
3867
- }),
3868
- "Endpoints"
3869
- ] }),
3870
- /* @__PURE__ */ jsxs(Text, { children: [
3871
- /* @__PURE__ */ jsx(Text, {
3872
- color: "cyan",
3873
- children: " ◆ "
3874
- }),
3875
- "Customer Analytics ",
3876
- /* @__PURE__ */ jsx(Text, {
3877
- color: "cyan",
3878
- children: "◆ "
3879
- }),
3880
- "PostHog Code"
3881
- ] })
3882
- ]
3883
- };
3884
- //#endregion
3885
- //#region src/lib/programs/posthog-integration/content/line-chart.tsx
3886
- /**
3887
- * LINE_CHART_BLOCK — ASCII trends chart illustrating an insight.
3888
- */
3889
- const LINE_CHART_BLOCK = {
3890
- type: "lines",
3891
- interval: 300,
3892
- pause: 6e3,
3893
- lines: [
3894
- /* @__PURE__ */ jsx(Text, {
3895
- bold: true,
3896
- children: " Trends · user signups (monthly)"
3897
- }),
3898
- /* @__PURE__ */ jsx(Text, { children: " " }),
3899
- /* @__PURE__ */ jsxs(Text, { children: [
3900
- /* @__PURE__ */ jsx(Text, {
3901
- color: "gray",
3902
- children: " 10k ┤"
3903
- }),
3904
- " ",
3905
- /* @__PURE__ */ jsx(Text, {
3906
- color: "cyan",
3907
- children: "╭──"
3908
- }),
3909
- /* @__PURE__ */ jsx(Text, {
3910
- dimColor: true,
3911
- children: " 9,575"
3912
- })
3913
- ] }),
3914
- /* @__PURE__ */ jsxs(Text, { children: [
3915
- /* @__PURE__ */ jsx(Text, {
3916
- color: "gray",
3917
- children: " │"
3918
- }),
3919
- " ",
3920
- /* @__PURE__ */ jsx(Text, {
3921
- color: "cyan",
3922
- children: "╭╯"
3923
- })
3924
- ] }),
3925
- /* @__PURE__ */ jsxs(Text, { children: [
3926
- /* @__PURE__ */ jsx(Text, {
3927
- color: "gray",
3928
- children: " 7.5k ┤"
3929
- }),
3930
- " ",
3931
- /* @__PURE__ */ jsx(Text, {
3932
- color: "cyan",
3933
- children: "╭╯"
3934
- })
3935
- ] }),
3936
- /* @__PURE__ */ jsxs(Text, { children: [
3937
- /* @__PURE__ */ jsx(Text, {
3938
- color: "gray",
3939
- children: " │"
3940
- }),
3941
- " ",
3942
- /* @__PURE__ */ jsx(Text, {
3943
- color: "cyan",
3944
- children: "╭─╯"
3945
- })
3946
- ] }),
3947
- /* @__PURE__ */ jsxs(Text, { children: [
3948
- /* @__PURE__ */ jsx(Text, {
3949
- color: "gray",
3950
- children: " 5k ┤"
3951
- }),
3952
- " ",
3953
- /* @__PURE__ */ jsx(Text, {
3954
- color: "cyan",
3955
- children: "╭─╯"
3956
- })
3957
- ] }),
3958
- /* @__PURE__ */ jsxs(Text, { children: [
3959
- /* @__PURE__ */ jsx(Text, {
3960
- color: "gray",
3961
- children: " │"
3962
- }),
3963
- " ",
3964
- /* @__PURE__ */ jsx(Text, {
3965
- color: "cyan",
3966
- children: "╭──╯"
3967
- })
3968
- ] }),
3969
- /* @__PURE__ */ jsxs(Text, { children: [
3970
- /* @__PURE__ */ jsx(Text, {
3971
- color: "gray",
3972
- children: " 2.5k ┤"
3973
- }),
3974
- " ",
3975
- /* @__PURE__ */ jsx(Text, {
3976
- color: "cyan",
3977
- children: "╭───╯"
3978
- })
3979
- ] }),
3980
- /* @__PURE__ */ jsxs(Text, { children: [
3981
- /* @__PURE__ */ jsx(Text, {
3982
- color: "gray",
3983
- children: " │"
3984
- }),
3985
- " ",
3986
- /* @__PURE__ */ jsx(Text, {
3987
- color: "cyan",
3988
- children: "╭──────╯"
3989
- })
3990
- ] }),
3991
- /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
3992
- color: "gray",
3993
- children: " 0 ┤"
3994
- }), /* @__PURE__ */ jsx(Text, {
3995
- color: "cyan",
3996
- children: "──────╯"
3997
- })] }),
3998
- /* @__PURE__ */ jsx(Text, {
3999
- color: "gray",
4000
- children: " └┬─────┬─────┬─────┬─────┬──"
4001
- }),
4002
- /* @__PURE__ */ jsx(Text, {
4003
- dimColor: true,
4004
- children: " May Aug Nov Feb May"
4005
- })
4006
- ]
4007
- };
4008
- //#endregion
4009
- //#region src/lib/programs/posthog-integration/content/funnel.tsx
4010
- /**
4011
- * FUNNEL_BLOCK — ASCII funnel illustrating step conversion.
4012
- */
4013
- const FUNNEL_BLOCK = {
4014
- type: "lines",
4015
- interval: 200,
4016
- pause: 8e3,
4017
- lines: [
4018
- /* @__PURE__ */ jsx(Text, {
4019
- bold: true,
4020
- children: " Funnel · ride conversion"
4021
- }),
4022
- /* @__PURE__ */ jsx(Text, { children: " " }),
4023
- /* @__PURE__ */ jsxs(Text, { children: [
4024
- " ",
4025
- /* @__PURE__ */ jsx(Text, {
4026
- bold: true,
4027
- children: "1"
4028
- }),
4029
- " app_launched",
4030
- " ",
4031
- /* @__PURE__ */ jsx(Text, {
4032
- bold: true,
4033
- color: "green",
4034
- children: "100.00%"
4035
- })
4036
- ] }),
4037
- /* @__PURE__ */ jsx(Text, {
4038
- color: "cyan",
4039
- children: " ██████████████████████████████"
4040
- }),
4041
- /* @__PURE__ */ jsx(Text, {
4042
- dimColor: true,
4043
- children: " → 1,200 users"
4044
- }),
4045
- /* @__PURE__ */ jsx(Text, { children: " " }),
4046
- /* @__PURE__ */ jsxs(Text, { children: [
4047
- " ",
4048
- /* @__PURE__ */ jsx(Text, {
4049
- bold: true,
4050
- children: "2"
4051
- }),
4052
- " ride_requested",
4053
- " ",
4054
- /* @__PURE__ */ jsx(Text, {
4055
- dimColor: true,
4056
- children: "avg 2m 30s"
4057
- }),
4058
- " ",
4059
- /* @__PURE__ */ jsx(Text, {
4060
- bold: true,
4061
- color: "green",
4062
- children: "72.00%"
4063
- })
4064
- ] }),
4065
- /* @__PURE__ */ jsxs(Text, { children: [
4066
- " ",
4067
- /* @__PURE__ */ jsx(Text, {
4068
- color: "cyan",
4069
- children: "██████████████████████"
4070
- }),
4071
- /* @__PURE__ */ jsx(Text, {
4072
- dimColor: true,
4073
- children: "░░░░░░░░░"
4074
- })
4075
- ] }),
4076
- /* @__PURE__ */ jsxs(Text, { children: [
4077
- " ",
4078
- /* @__PURE__ */ jsx(Text, {
4079
- dimColor: true,
4080
- children: "→ 864 users"
4081
- }),
4082
- " ",
4083
- /* @__PURE__ */ jsx(Text, {
4084
- color: "red",
4085
- children: "↘"
4086
- }),
4087
- /* @__PURE__ */ jsx(Text, {
4088
- dimColor: true,
4089
- children: " 336 (28%)"
4090
- })
4091
- ] }),
4092
- /* @__PURE__ */ jsx(Text, { children: " " }),
4093
- /* @__PURE__ */ jsxs(Text, { children: [
4094
- " ",
4095
- /* @__PURE__ */ jsx(Text, {
4096
- bold: true,
4097
- children: "3"
4098
- }),
4099
- " ride_accepted",
4100
- " ",
4101
- /* @__PURE__ */ jsx(Text, {
4102
- dimColor: true,
4103
- children: "avg 5m 12s"
4104
- }),
4105
- " ",
4106
- /* @__PURE__ */ jsx(Text, {
4107
- bold: true,
4108
- color: "green",
4109
- children: "51.00%"
4110
- })
4111
- ] }),
4112
- /* @__PURE__ */ jsxs(Text, { children: [
4113
- " ",
4114
- /* @__PURE__ */ jsx(Text, {
4115
- color: "cyan",
4116
- children: "██████████████████"
4117
- }),
4118
- /* @__PURE__ */ jsx(Text, {
4119
- dimColor: true,
4120
- children: "░░░░░░░░░░░░░"
4121
- })
4122
- ] }),
4123
- /* @__PURE__ */ jsxs(Text, { children: [
4124
- " ",
4125
- /* @__PURE__ */ jsx(Text, {
4126
- dimColor: true,
4127
- children: "→ 612 users"
4128
- }),
4129
- " ",
4130
- /* @__PURE__ */ jsx(Text, {
4131
- color: "red",
4132
- children: "↘"
4133
- }),
4134
- /* @__PURE__ */ jsx(Text, {
4135
- dimColor: true,
4136
- children: " 252 (29%)"
4137
- })
4138
- ] }),
4139
- /* @__PURE__ */ jsx(Text, { children: " " }),
4140
- /* @__PURE__ */ jsxs(Text, { children: [
4141
- " ",
4142
- /* @__PURE__ */ jsx(Text, {
4143
- bold: true,
4144
- children: "4"
4145
- }),
4146
- " ride_started",
4147
- " ",
4148
- /* @__PURE__ */ jsx(Text, {
4149
- dimColor: true,
4150
- children: "avg 1m 45s"
4151
- }),
4152
- " ",
4153
- /* @__PURE__ */ jsx(Text, {
4154
- bold: true,
4155
- color: "green",
4156
- children: "38.00%"
4157
- })
4158
- ] }),
4159
- /* @__PURE__ */ jsxs(Text, { children: [
4160
- " ",
4161
- /* @__PURE__ */ jsx(Text, {
4162
- color: "cyan",
4163
- children: "█████████████"
4164
- }),
4165
- /* @__PURE__ */ jsx(Text, {
4166
- dimColor: true,
4167
- children: "░░░░░░░░░░░░░░░░░░"
4168
- })
4169
- ] }),
4170
- /* @__PURE__ */ jsxs(Text, { children: [
4171
- " ",
4172
- /* @__PURE__ */ jsx(Text, {
4173
- dimColor: true,
4174
- children: "→ 456 users"
4175
- }),
4176
- " ",
4177
- /* @__PURE__ */ jsx(Text, {
4178
- color: "red",
4179
- children: "↘"
4180
- }),
4181
- /* @__PURE__ */ jsx(Text, {
4182
- dimColor: true,
4183
- children: " 156 (25%)"
4184
- })
4185
- ] })
4186
- ]
4187
- };
4188
- //#endregion
4189
- //#region src/lib/programs/posthog-integration/content/index.tsx
4190
- /**
4191
- * Integration learn-deck — the narrative script played while the agent
4192
- * runs the PostHog integration flow. Weaves typewriter lines, pauses,
4193
- * `clear` markers, and the diagram blocks into one sequence.
4194
- */
4195
- const getContentBlocks = (store) => [
4196
- {
4197
- content: store?.session.apiUser?.first_name ? `Welcome, ${store.session.apiUser.first_name}.` : "Welcome.",
4198
- pause: 3e3,
4199
- mode: 0,
4200
- animationInterval: 160
4201
- },
4202
- {
4203
- content: "The Wizard is an agent.",
4204
- pause: 4e3
4205
- },
4206
- {
4207
- content: "It handles the entire PostHog setup process on your behalf.",
4208
- pause: 5e3
4209
- },
4210
- {
4211
- content: "As we speak, it's building a plan to set up PostHog in your project.",
4212
- pause: 6e3
4213
- },
4214
- {
4215
- type: "clear",
4216
- pause: 2e3
4217
- },
4218
- {
4219
- pause: 5e3,
4220
- persist: true,
4221
- content: /* @__PURE__ */ jsx(StatusPeekTrigger, { store })
4222
- },
4223
- {
4224
- pause: 6e3,
4225
- content: /* @__PURE__ */ jsxs(Text, { children: [
4226
- "Press",
4227
- " ",
4228
- /* @__PURE__ */ jsx(Text, {
4229
- color: Colors.accent,
4230
- bold: true,
4231
- children: "S"
4232
- }),
4233
- " ",
4234
- "to expand or collapse the status."
4235
- ] })
4236
- },
4237
- {
4238
- type: "clear",
4239
- pause: 2e3
4240
- },
4241
- {
4242
- content: "It takes about eight minutes.",
4243
- pause: 2e3
4244
- },
4245
- {
4246
- content: "So grab some coffee ☕️.",
4247
- pause: 2e3
4248
- },
4249
- {
4250
- content: "Or stick around and learn about PostHog.",
4251
- pause: 5e3
4252
- },
4253
- {
4254
- type: "clear",
4255
- pause: 3e3
4256
- },
4257
- {
4258
- content: "Events are the foundation of the PostHog platform.",
4259
- pause: 4e3
4260
- },
4261
- {
4262
- content: "Every time an action is performed in your codebase — like button clicks, function calls, or thrown errors — we can capture an event.",
4263
- pause: 6e3
4264
- },
4265
- {
4266
- content: "Events are sent to PostHog and joined with other product data.",
4267
- pause: 6e3
4268
- },
4269
- {
4270
- type: "clear",
4271
- pause: 1e3
4272
- },
4273
- {
4274
- content: "Here's the flow.",
4275
- pause: 1e3
4276
- },
4277
- POSTHOG_DATA_FLOW,
4278
- {
4279
- type: "clear",
4280
- pause: 2e3
4281
- },
4282
- {
4283
- content: "With enough event data, you can answer powerful questions about your product.",
4284
- pause: 4e3
4285
- },
4286
- {
4287
- content: "And create insights.",
4288
- pause: 4e3
4289
- },
4290
- {
4291
- type: "clear",
4292
- pause: 500
4293
- },
4294
- {
4295
- content: "Like trends to measure growth.",
4296
- pause: 2500
4297
- },
4298
- LINE_CHART_BLOCK,
4299
- {
4300
- type: "clear",
4301
- pause: 500
4302
- },
4303
- {
4304
- content: "Or funnels to reveal bottlenecks.",
4305
- pause: 2500
4306
- },
4307
- FUNNEL_BLOCK,
4308
- {
4309
- type: "clear",
4310
- pause: 1e3
4311
- },
4312
- {
4313
- content: "Use those signals to decide what to build next.",
4314
- pause: 4e3
4315
- },
4316
- {
4317
- content: "PostHog has all the dev tools you need.",
4318
- pause: 3e3
4319
- },
4320
- PRODUCT_SUITE_BLOCK
4321
- ];
4322
- //#endregion
4323
- //#region src/lib/programs/posthog-integration/handoff.ts
4324
- /**
4325
- * The one-line prompt the operator pastes into their own coding agent after a
4326
- * run: open the setup-report notebook, work its "Verify before merging"
4327
- * checklist. Surfaced via `OutroData.handoffPrompt` (printed to scrollback on
4328
- * exit); only exists when the run captured a notebook URL, since no report
4329
- * file is written. The wording demands investigate-then-approval, and stops
4330
- * there — the operator's own agent governs how changes land. Background:
4331
- * https://github.com/PostHog/wizard/issues/447
4332
- */
4333
- function buildCodingAgentPrompt(notebookUrl) {
4334
- return `Open ${notebookUrl} (the PostHog setup report) and work through its "Verify before merging" checklist: investigate each item, then list the changes you'd make and get my approval before applying any of them.`;
4335
- }
4336
- //#endregion
4337
- //#region src/lib/programs/posthog-integration/index.ts
4338
- const DASHBOARD_DEEP_LINK_KEY = "dashboardDeepLink";
4339
- const WAREHOUSE_SOURCES_DOCS_URL = "https://posthog.com/docs/data-warehouse/sources";
4340
- function resolveContinueUrl(sess, host, deepLink) {
4341
- if (!sess.signup) return void 0;
4342
- if (typeof deepLink === "string" && deepLink) return deepLink;
4343
- return withUtm(`${host.appHost}/products?source=wizard`, "outro-continue");
4344
- }
4345
- /** Sources listed with their own link before the outro falls back to a summary line. */
4346
- const WAREHOUSE_LINK_LIMIT = 3;
4347
- /**
4348
- * The app's new-source page, pre-selected to one source kind.
4349
- *
4350
- * `kind` is matched case-insensitively against the connector list, and a kind
4351
- * the app cannot resolve lands on the source catalog rather than erroring. So a
4352
- * source we detect but the app has not shipped a connector for still takes the
4353
- * user somewhere useful.
4354
- */
4355
- function warehouseSourceUrl(host, projectId, kind) {
4356
- return withUtm(`${host.appHost}/project/${projectId}/data-warehouse/new-source?kind=${encodeURIComponent(kind)}`, "outro-warehouse");
4357
- }
4358
- /**
4359
- * Outro suggestion for data sources found in the project but not connected.
4360
- *
4361
- * A pointer at the app, not an inline flow. Connecting a source needs
4362
- * interactive credential collection, and chaining that as a second agent run
4363
- * before the outro would let any of its terminal failure paths `process.exit()`
4364
- * — costing the user the success outro and the post-outro MCP / Slack steps on
4365
- * a run where PostHog installed fine.
4366
- *
4367
- * Each source gets its own pre-filled link, because the alternative we shipped
4368
- * first — naming the `wizard warehouse` command — asks the user to start a
4369
- * second CLI run before they can connect anything. A link opens the form for
4370
- * that one source. The command line stays for anyone who wants every source in
4371
- * one pass, and it is the only route offered once the list is too long to read.
4372
- *
4373
- * Returns undefined when nothing was detected, so the outro is unchanged for
4374
- * projects with no connectable source.
4375
- */
4376
- function buildWarehouseNextSteps(sess, host, projectId) {
4377
- const sources = getDetectedWarehouseSources(sess);
4378
- if (sources.length === 0) return void 0;
4379
- const listed = sources.slice(0, WAREHOUSE_LINK_LIMIT);
4380
- const items = listed.map((s) => `Connect ${s.label}: ${warehouseSourceUrl(host, projectId, s.kind)}`);
4381
- const remaining = sources.length - listed.length;
4382
- if (remaining > 0) items.push(`And ${remaining} more we found in this project.`);
4383
- items.push("Connect them all at once with: npx @posthog/wizard warehouse");
4384
- return {
4385
- heading: "Query your other data in PostHog:",
4386
- items
4387
- };
4388
- }
4389
- /**
4390
- * Prompt fragment asking the agent to note the detected data sources in the
4391
- * setup report's checklist.
4392
- *
4393
- * Empty string when nothing was detected, so the prompt is byte-identical to
4394
- * today for projects with no connectable source. Best-effort by nature — the
4395
- * agent may word it differently or skip it. That is acceptable here precisely
4396
- * because it is a note in a report: the outro `nextSteps` bullet carries the
4397
- * same information deterministically, so nothing is lost if the agent drops it.
4398
- */
4399
- function warehouseReportInstruction(sess) {
4400
- const sources = getDetectedWarehouseSources(sess);
4401
- if (sources.length === 0) return "";
4402
- return `Finally: this project also contains data sources PostHog can import (${sources.map((s) => s.label).join(", ")}). In the setup report's "Verify before merging" checklist, add one item noting these were found and that \`npx @posthog/wizard warehouse\` will connect them to PostHog's data warehouse. Do not attempt to set them up yourself in this run.`;
4403
- }
4404
- /**
4405
- * The orchestrator task that connects the data sources detection found in the
4406
- * project — the one step of the flow that stops to ask the user for
4407
- * credentials. Queued here rather than by the planner: whether it belongs in
4408
- * the run is a fact about the project the wizard already scanned, so a model
4409
- * can neither invent it nor forget it.
4410
- *
4411
- * It runs at the end of the queue, not the start. Where exactly is the agent
4412
- * prompt's business (`dependsOn` in the warehouse agent's frontmatter, resolved
4413
- * by `seeded-deps.ts` once the planner has run) — this file only decides whether
4414
- * the task belongs in the run at all.
4415
- *
4416
- * Empty when nothing was detected, and in CI, signup, and any other run where
4417
- * `wizard_ask` is disabled — a credential prompt nobody can answer would burn
4418
- * the task's whole timeout and then fail the run.
4419
- */
4420
- const warehouseSeedTasks = (sess) => {
4421
- if (shouldDisableAsk(sess)) return [];
4422
- const sources = getDetectedWarehouseSources(sess);
4423
- if (sources.length === 0) return [];
4424
- if (mayReportScanResults(sess)) analytics.wizardCapture("orchestrator warehouse task queued", {
4425
- warehouse_source_count: sources.length,
4426
- warehouse_source_kinds: sources.map((s) => s.kind)
4427
- });
4428
- return [{
4429
- type: "warehouse",
4430
- inputs: { sources: sources.map((s) => ({
4431
- kind: s.kind,
4432
- label: s.label,
4433
- mode: s.mode,
4434
- matchedSignal: s.matchedSignal
4435
- })) },
4436
- notice: {
4437
- title: "Connect your data sources",
4438
- body: ["We detected some warehouse sources we can connect to enrich your PostHog data. Answer now, and we connect them at the end of the run, after your code changes. We will ask you for the credentials at that point, and beep when we do.", "You can select [Skip] if you'd like to do this later in PostHog."],
4439
- items: sources.map((s) => s.label),
4440
- docsLabel: "Learn more about warehouse sources",
4441
- docsUrl: WAREHOUSE_SOURCES_DOCS_URL,
4442
- prompt: "Connect these during setup?",
4443
- confirmLabel: "Continue [Enter]",
4444
- cancelLabel: "Skip [Esc]"
4445
- }
4446
- }];
4447
- };
4448
- const SETUP_REPORT_FILE = "posthog-setup-report.md";
4449
- const posthogIntegrationConfig = {
4450
- command: "integrate",
4451
- description: "Set up PostHog SDK integration",
4452
- id: "posthog-integration",
4453
- agentFlow: "integration-v2",
4454
- eventPlanFile: EVENT_PLAN_FILE,
4455
- steps: POSTHOG_INTEGRATION_PROGRAM,
4456
- getContentBlocks,
4457
- disallowedTools: [WIZARD_TOOL_NAMES.wizardAsk],
4458
- seedTasks: warehouseSeedTasks,
4459
- ciPreRun: async (session) => {
4460
- await scopeInstallDirToProject(session);
4461
- const integration = await detectFramework(session.installDir);
4462
- if (!integration) {
4463
- await wizardAbort({
4464
- code: ErrorCodes.DetectNoFramework,
4465
- message: "Could not auto-detect your framework for this project."
4466
- });
4467
- return;
4468
- }
4469
- session.integration = integration;
4470
- analytics.setTag("integration", integration);
4471
- const frameworkConfig = FRAMEWORK_REGISTRY[integration];
4472
- session.frameworkConfig = frameworkConfig;
4473
- const context = await gatherFrameworkContext(frameworkConfig, {
4474
- installDir: session.installDir,
4475
- debug: session.debug,
4476
- signup: session.signup,
4477
- ci: true,
4478
- benchmark: session.benchmark,
4479
- yaraReport: session.yaraReport
4480
- });
4481
- for (const [key, value] of Object.entries(context)) if (!(key in session.frameworkContext)) session.frameworkContext[key] = value;
4482
- },
4483
- run: async (session) => {
4484
- const config = session.frameworkConfig;
4485
- const typeScriptDetected = isUsingTypeScript({ installDir: session.installDir });
4486
- session.typescript = typeScriptDetected;
4487
- analytics.setTag("typescript", typeScriptDetected);
4488
- const usesPackageJson = config.detection.usesPackageJson !== false;
4489
- let frameworkVersion;
4490
- if (usesPackageJson) {
4491
- const packageJson = await tryGetPackageJson({ installDir: session.installDir });
4492
- if (packageJson) {
4493
- const { hasDeclaredDependency } = await import("./package-json-xt3eeyBI.js").then((n) => n.i);
4494
- if (!hasDeclaredDependency(config.detection.packageName, packageJson)) getUI().log.warn(`${config.detection.packageDisplayName} does not seem to be installed. Continuing anyway — the agent will handle it.`);
4495
- frameworkVersion = config.detection.getVersion(packageJson);
4496
- } else getUI().log.warn("Could not find package.json. Continuing anyway — the agent will handle it.");
4497
- } else frameworkVersion = config.detection.getVersion(null);
4498
- if (frameworkVersion && config.detection.getVersionBucket) {
4499
- const versionBucket = config.detection.getVersionBucket(frameworkVersion);
4500
- analytics.setTag(`${config.metadata.integration}-version`, versionBucket);
4501
- }
4502
- const frameworkContext = session.frameworkContext;
4503
- const contextTags = config.analytics.getTags(frameworkContext);
4504
- Object.entries(contextTags).forEach(([key, value]) => {
4505
- analytics.setTag(key, value);
4506
- });
4507
- return {
4508
- integrationLabel: config.metadata.integration,
4509
- additionalMcpServers: config.metadata.additionalMcpServers,
4510
- detectPackageManager: config.detection.detectPackageManager,
4511
- spinnerMessage: SPINNER_MESSAGE,
4512
- successMessage: config.ui.successMessage,
4513
- estimatedDurationMinutes: config.ui.estimatedDurationMinutes,
4514
- reportFile: SETUP_REPORT_FILE,
4515
- docsUrl: config.metadata.docsUrl,
4516
- errorMessage: "Integration failed",
4517
- additionalFeatureQueue: session.additionalFeatureQueue,
4518
- richLinks: true,
4519
- customPrompt: (ctx) => {
4520
- const additionalLines = config.prompts.getAdditionalContextLines ? config.prompts.getAdditionalContextLines(frameworkContext) : [];
4521
- const additionalContext = additionalLines.length > 0 ? "\n" + additionalLines.map((line) => `- ${line}`).join("\n") : "";
4522
- return `You have access to the PostHog MCP server which provides skills to integrate PostHog into this ${config.metadata.name} project.
4523
-
4524
- Project context:
4525
- - PostHog Project ID: ${ctx.projectId}
4526
- - Framework: ${config.metadata.name} ${frameworkVersion || "latest"}
4527
- - TypeScript: ${typeScriptDetected ? "Yes" : "No"}
4528
- - PostHog public token: ${ctx.projectApiKey}
4529
- - PostHog Host: ${ctx.host.apiHost}
4530
- - Project type: ${config.prompts.projectTypeDetection}
4531
- - Package installation: ${config.prompts.packageInstallation ?? "Use the detect_package_manager tool to determine the package manager. Do not manually edit package.json; the package manager handles it automatically."}${additionalContext}
4532
-
4533
- Instructions (follow these steps IN ORDER - do not skip or reorder):
4534
-
4535
- STEP 1: Call load_skill_menu (from the wizard-tools MCP server) to see available skills.
4536
- If the tool fails, emit: ${AgentSignals.ERROR_MCP_MISSING} Could not load skill menu and halt.
4537
-
4538
- Choose a skill from the \`integration\` category that matches this project's framework. Do NOT pick skills from other categories (llm-analytics, error-tracking, feature-flags, omnibus, etc.) — those are handled separately.
4539
- If no suitable integration skill is found, emit: ${AgentSignals.ERROR_RESOURCE_MISSING} Could not find a suitable skill for this project.
4540
-
4541
- STEP 2: Call install_skill (from the wizard-tools MCP server) with the chosen skill ID (e.g., "integration-nextjs-app-router").
4542
- Do NOT run any shell commands to install skills.
4543
- If install_skill fails, emit on its own line: ${AgentSignals.SKILL_INSTALL_FAILED} <skill id — one-line reason>. Then CONTINUE and SKIP to STEP 5 the integration without the skill, following these steps and your knowledge of ${config.metadata.name} and PostHog's official docs, and note in the setup report that the skill could not be installed.
4544
-
4545
- STEP 3: Load the installed skill's SKILL.md file to understand what references are available.
4546
-
4547
- STEP 4: Follow the skill's program files in sequence. Look for numbered program files in the references (e.g., files with patterns like "1-", "2-", "3-"). Start with the first one and proceed through each step until completion. Each program file will tell you what to do and which file comes next. Never directly write PostHog tokens directly to code files; always use environment variables.
4548
-
4549
- STEP 5: Set up environment variables for PostHog using the wizard-tools MCP server (this runs locally — secret values never leave the machine):
4550
- - Use check_env_keys to see which keys the project already sets, and where. Omit filePath and it scans every .env file in the project, so you don't have to guess between .env, .env.local and a nested one. It answers { status, foundIn } per key: "present" means a real env file sets the key, while a key found only in a committed template (.env.example and friends) reads as "missing" — a template documents a key rather than setting it, and is never a file to write credentials into.
4551
- - Use set_env_values to create or update the PostHog public token and host, using the appropriate environment variable naming convention for ${config.metadata.name}, which you'll find in example code. The tool will also ensure .gitignore coverage. Don't assume the presence of keys means the value is up to date. Write the correct value each time.
4552
- - Reference these environment variables in the code files you create instead of hardcoding the public token and host.
4553
-
4554
- Important: Use the detect_package_manager tool (from the wizard-tools MCP server) to determine which package manager the project uses, then run its install command to add the SDK. Do not manually search for lockfiles or config files. If a file already EXISTS, read it immediately before you edit or overwrite it — writing from a stale read causes a tool failure. Creating a brand-new file needs no prior read: never read a path that does not exist yet; just write it.
4555
-
4556
- ${warehouseReportInstruction(session)}
4557
- `;
4558
- },
4559
- postRun: async (sess, credentials) => {
4560
- const envVars = config.environment.getEnvVars(credentials.projectApiKey, credentials.host.apiHost);
4561
- if (config.environment.uploadToHosting) {
4562
- const { uploadEnvironmentVariablesStep } = await import("./steps-BZd65XfI.js");
4563
- const uploadedEnvVars = await uploadEnvironmentVariablesStep(envVars, {
4564
- integration: config.metadata.integration,
4565
- session: sess
4566
- });
4567
- if (uploadedEnvVars.length > 0) analytics.capture(WIZARD_INTERACTION_EVENT_NAME, {
4568
- action: "wizard_env_vars_uploaded",
4569
- integration: config.metadata.integration,
4570
- variable_count: uploadedEnvVars.length,
4571
- variable_keys: uploadedEnvVars
4572
- });
4573
- }
4574
- if (sess.signup) {
4575
- const deepLink = await requestDeepLink(credentials.accessToken, credentials.host);
4576
- if (deepLink) {
4577
- const taggedDeepLink = withUtm(deepLink, "dashboard-deeplink");
4578
- sess.frameworkContext[DASHBOARD_DEEP_LINK_KEY] = taggedDeepLink;
4579
- openTrackedLink(taggedDeepLink, "dashboard-deeplink", { auto: true });
4580
- }
4581
- }
4582
- },
4583
- buildOutroData: (sess, credentials) => {
4584
- const envVars = config.environment.getEnvVars(credentials.projectApiKey, credentials.host.apiHost);
4585
- const deepLink = sess.frameworkContext[DASHBOARD_DEEP_LINK_KEY];
4586
- const continueUrl = resolveContinueUrl(sess, credentials.host, deepLink);
4587
- return {
4588
- kind: "success",
4589
- message: "Successfully installed PostHog!",
4590
- changes: [...config.ui.getOutroChanges(frameworkContext), Object.keys(envVars).length > 0 ? "Added environment variables to .env file" : ""].filter(Boolean),
4591
- docsUrl: config.metadata.docsUrl,
4592
- continueUrl,
4593
- nextSteps: buildWarehouseNextSteps(sess, credentials.host, credentials.projectId),
4594
- notebookUrl: sess.notebookUrl ?? void 0,
4595
- handoffPrompt: sess.notebookUrl ? buildCodingAgentPrompt(sess.notebookUrl) : void 0
4596
- };
4597
- }
4598
- };
4599
- }
4600
- };
4601
- /**
4602
- * Self-contained run step that runs the integration agent. Other programs
4603
- * import this and splice it into their own step list to compose the
4604
- * integration's work as one of their run steps — self-driving sets up PostHog
4605
- * this way before its own run. The host program supplies `show`/`onRunPrep`/
4606
- * `targetDir`; this carries the run.
4607
- */
4608
- const integrationRunStep = {
4609
- id: "run",
4610
- label: "Integration",
4611
- screenId: "run",
4612
- run: (session) => runAgent$1(posthogIntegrationConfig, session, { composed: true }),
4613
- isComplete: (session) => session.runPhase === "completed" || session.runPhase === "error"
4614
- };
4615
- //#endregion
4616
- export { detectIntegrationProjects as C, gatherFrameworkContext as D, resolveProjectDir as E, detectFramework as O, detectWarehouseSources as S, detectProjectsWithAgent as T, reportWarehouseSourcesDetected as _, LINE_CHART_BLOCK as a, detectWarehousePrerequisites as b, TextBlock as c, isClearBlock as d, isLinesBlock as f, detectPostHogIntegration as g, Icons as h, FUNNEL_BLOCK as i, computeVisibleRange as l, Colors as m, integrationRunStep as n, PRODUCT_SUITE_BLOCK as o, isObjectBlock as p, posthogIntegrationConfig as r, StatusPeekTrigger as s, SETUP_REPORT_FILE as t, wordWrap as u, HEALTH_CHECK_STEP as v, scopeInstallDirToProject as w, getDetectedWarehouseSources as x, WAREHOUSE_ABORT_CASES as y };
4617
-
4618
- //# sourceMappingURL=posthog-integration-B8f4xRBd.js.map