@posthog/wizard 2.83.0 → 2.84.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 (145) hide show
  1. package/README.md +1 -29
  2. package/dist/{PickerMenu-BGGeYqd-.js → PickerMenu-BtheKi1o.js} +1 -1
  3. package/dist/{PickerMenu-BGGeYqd-.js.map → PickerMenu-BtheKi1o.js.map} +1 -1
  4. package/dist/_dev-only-stub-B0db0Klc.js +1 -0
  5. package/dist/{agent-interface-fcDXqb-_.js → agent-interface-BNssNetq.js} +12 -12
  6. package/dist/agent-interface-BNssNetq.js.map +1 -0
  7. package/dist/{agent-prompt-loader-F9gGPMt0.js → agent-prompt-loader-iV11XVEF.js} +16 -8
  8. package/dist/agent-prompt-loader-iV11XVEF.js.map +1 -0
  9. package/dist/{analytics-GQv--mQm.js → analytics-Drox5LSW.js} +6 -5
  10. package/dist/analytics-Drox5LSW.js.map +1 -0
  11. package/dist/analytics-Ix9HEra3.js +2 -0
  12. package/dist/{api-WUOSLXPJ.js → api-Dfu655Qt.js} +2 -2
  13. package/dist/{api-WUOSLXPJ.js.map → api-Dfu655Qt.js.map} +1 -1
  14. package/dist/api-H2UaPgjv.js +2 -0
  15. package/dist/{api-key-login-tysMeZnR.js → api-key-login-BgwfAXfv.js} +5 -5
  16. package/dist/{api-key-login-tysMeZnR.js.map → api-key-login-BgwfAXfv.js.map} +1 -1
  17. package/dist/api-key-login-Cow3UDAN.js +2 -0
  18. package/dist/bin.js +1 -1
  19. package/dist/{bounded-fs-B31zq2ir.js → bounded-fs-TRL6IsoU.js} +32 -16
  20. package/dist/bounded-fs-TRL6IsoU.js.map +1 -0
  21. package/dist/{ci-install-CYSFbFXd.js → ci-install-nc1IKuX2.js} +6 -6
  22. package/dist/{ci-install-CYSFbFXd.js.map → ci-install-nc1IKuX2.js.map} +1 -1
  23. package/dist/{console-log-DJiTqYT2.js → console-log-BMhBoX-o.js} +2 -2
  24. package/dist/{console-log-DJiTqYT2.js.map → console-log-BMhBoX-o.js.map} +1 -1
  25. package/dist/{debug-BTUMatFI.js → debug-BfO_Txo8.js} +3 -3
  26. package/dist/debug-BfO_Txo8.js.map +1 -0
  27. package/dist/{defaults-DDVl3tKN.js → defaults-BWLIffh5.js} +1 -1
  28. package/dist/{defaults-DDVl3tKN.js.map → defaults-BWLIffh5.js.map} +1 -1
  29. package/dist/{errors-DhFeR8Sp.js → errors-BXlWujEw.js} +1 -1
  30. package/dist/{errors-DhFeR8Sp.js.map → errors-BXlWujEw.js.map} +1 -1
  31. package/dist/{family-picker-BKs-tTyu.js → family-picker-CAW_VtYf.js} +2 -2
  32. package/dist/{family-picker-BKs-tTyu.js.map → family-picker-CAW_VtYf.js.map} +1 -1
  33. package/dist/{gateway-CJ8Jqbll.js → gateway-CLQQb96F.js} +25 -9
  34. package/dist/{gateway-CJ8Jqbll.js.map → gateway-CLQQb96F.js.map} +1 -1
  35. package/dist/headless-CHGNEzO3.js +3 -0
  36. package/dist/{headless-le-QqOuj.js → headless-DMO8N_FD.js} +2 -2
  37. package/dist/{headless-le-QqOuj.js.map → headless-DMO8N_FD.js.map} +1 -1
  38. package/dist/{host-resolution-Cht8bL39.js → host-resolution-DftfSjJD.js} +4 -4
  39. package/dist/{host-resolution-Cht8bL39.js.map → host-resolution-DftfSjJD.js.map} +1 -1
  40. package/dist/{install-BhnFP3p3.js → install-C9FPM37m.js} +6 -6
  41. package/dist/{install-BhnFP3p3.js.map → install-C9FPM37m.js.map} +1 -1
  42. package/dist/{install-55JbRsO-.js → install-MWoo1hXL.js} +1 -1
  43. package/dist/{interactive-9qRL8259.js → interactive-DOnolJHB.js} +3 -3
  44. package/dist/{interactive-9qRL8259.js.map → interactive-DOnolJHB.js.map} +1 -1
  45. package/dist/{links-5Blqk4Im.js → links-CatXPRxH.js} +3 -3
  46. package/dist/{links-5Blqk4Im.js.map → links-CatXPRxH.js.map} +1 -1
  47. package/dist/{main-CUIcozEG.js → main-CINmZtoW.js} +23 -23
  48. package/dist/{main-CUIcozEG.js.map → main-CINmZtoW.js.map} +1 -1
  49. package/dist/{mcp-D73aCOHK.js → mcp-KVV6Z_bq.js} +3 -3
  50. package/dist/{mcp-D73aCOHK.js.map → mcp-KVV6Z_bq.js.map} +1 -1
  51. package/dist/{mcp-prompt-streaming-B5lIQ0l1.js → mcp-prompt-streaming-M6UnLgGX.js} +5 -5
  52. package/dist/{mcp-prompt-streaming-B5lIQ0l1.js.map → mcp-prompt-streaming-M6UnLgGX.js.map} +1 -1
  53. package/dist/{mode-label-DB3ol_sj.js → mode-label--bJuXny5.js} +1 -1
  54. package/dist/{mode-label-DB3ol_sj.js.map → mode-label--bJuXny5.js.map} +1 -1
  55. package/dist/{models-CLTglHFZ.js → models-B0huHItG.js} +2 -2
  56. package/dist/{models-CLTglHFZ.js.map → models-B0huHItG.js.map} +1 -1
  57. package/dist/{non-interactive-DkldwWDM.js → non-interactive-DiEu4_6l.js} +3 -3
  58. package/dist/{non-interactive-DkldwWDM.js.map → non-interactive-DiEu4_6l.js.map} +1 -1
  59. package/dist/{oauth-session-CfkZ339p.js → oauth-session-9EU3ky0v.js} +2 -2
  60. package/dist/{oauth-session-CfkZ339p.js.map → oauth-session-9EU3ky0v.js.map} +1 -1
  61. package/dist/{orchestrator-tools-lvNVQhzs.js → orchestrator-tools-QWyDLu_7.js} +3 -3
  62. package/dist/{orchestrator-tools-lvNVQhzs.js.map → orchestrator-tools-QWyDLu_7.js.map} +1 -1
  63. package/dist/package-json-BAtJRHSq.js +2 -0
  64. package/dist/{package-json-2UrhE6kP.js → package-json-z1agAYXs.js} +1 -1
  65. package/dist/{package-json-2UrhE6kP.js.map → package-json-z1agAYXs.js.map} +1 -1
  66. package/dist/{package-manager-BqRNp5y_.js → package-manager-B-9I_fAT.js} +4 -4
  67. package/dist/{package-manager-BqRNp5y_.js.map → package-manager-B-9I_fAT.js.map} +1 -1
  68. package/dist/{pi-2pYz2OY3.js → pi-BSnSk6rv.js} +19 -20
  69. package/dist/pi-BSnSk6rv.js.map +1 -0
  70. package/dist/{playground-jxZNxRwC.js → playground-Drz1qszs.js} +4 -4
  71. package/dist/{playground-jxZNxRwC.js.map → playground-Drz1qszs.js.map} +1 -1
  72. package/dist/{posthog-integration-DxRkw5DI.js → posthog-integration-mIQGBc1f.js} +882 -2512
  73. package/dist/posthog-integration-mIQGBc1f.js.map +1 -0
  74. package/dist/programs-Bv0_YHzE.js +3 -0
  75. package/dist/{programs-eTv2T2rs.js → programs-mxhRXwP-.js} +194 -88
  76. package/dist/programs-mxhRXwP-.js.map +1 -0
  77. package/dist/provisioning-BVsxSNve.js +2 -0
  78. package/dist/{provisioning-c5JJ30KX.js → provisioning-CHUjae9V.js} +4 -4
  79. package/dist/{provisioning-c5JJ30KX.js.map → provisioning-CHUjae9V.js.map} +1 -1
  80. package/dist/{queue-tools-10w8lhEs.js → queue-tools-PFG6H93Q.js} +4 -4
  81. package/dist/{queue-tools-10w8lhEs.js.map → queue-tools-PFG6H93Q.js.map} +1 -1
  82. package/dist/{results-C45prSKD.js → results-5yCKf2cI.js} +2 -2
  83. package/dist/{results-C45prSKD.js.map → results-5yCKf2cI.js.map} +1 -1
  84. package/dist/{run-jugssomh.js → run-D5TKJuSL.js} +10 -10
  85. package/dist/{run-jugssomh.js.map → run-D5TKJuSL.js.map} +1 -1
  86. package/dist/{run-CM24OgPF.js → run-W03b6jNM.js} +12 -12
  87. package/dist/{run-CM24OgPF.js.map → run-W03b6jNM.js.map} +1 -1
  88. package/dist/{run-tool-DZrZFoGq.js → run-tool-DTQF8mmI.js} +17 -17
  89. package/dist/{run-tool-DZrZFoGq.js.map → run-tool-DTQF8mmI.js.map} +1 -1
  90. package/dist/run-tool-DjERhu7G.js +2 -0
  91. package/dist/{runners-CoIYt0iI.js → runners-B_84kPyf.js} +11 -11
  92. package/dist/{runners-CoIYt0iI.js.map → runners-B_84kPyf.js.map} +1 -1
  93. package/dist/{security-ppK2MwCn.js → security-pApVawYb.js} +5 -5
  94. package/dist/{security-ppK2MwCn.js.map → security-pApVawYb.js.map} +1 -1
  95. package/dist/{setup-utils-B5E1rXLa.js → setup-utils-CQY8PHjR.js} +3 -3
  96. package/dist/{setup-utils-B5E1rXLa.js.map → setup-utils-CQY8PHjR.js.map} +1 -1
  97. package/dist/{start-playground-DujiUNFT.js → start-playground-DAw1GbLt.js} +8 -8
  98. package/dist/{start-playground-DujiUNFT.js.map → start-playground-DAw1GbLt.js.map} +1 -1
  99. package/dist/{subagent-BdDWA8j5.js → subagent-YLGSvgoZ.js} +4 -4
  100. package/dist/subagent-YLGSvgoZ.js.map +1 -0
  101. package/dist/{task-Y8lWoDE7.js → task-3hRSovmK.js} +19 -20
  102. package/dist/task-3hRSovmK.js.map +1 -0
  103. package/dist/{tasks-BfJhVky2.js → tasks-C03QbXTx.js} +1 -1
  104. package/dist/{tasks-BfJhVky2.js.map → tasks-C03QbXTx.js.map} +1 -1
  105. package/dist/{telemetry-Cwqf0iur.js → telemetry-BPBzqsb_.js} +2 -2
  106. package/dist/{telemetry-Cwqf0iur.js.map → telemetry-BPBzqsb_.js.map} +1 -1
  107. package/dist/{terminal-BLL-X6YP.js → terminal-DZsYFbm9.js} +92 -63
  108. package/dist/terminal-DZsYFbm9.js.map +1 -0
  109. package/dist/{tools-C8k90ISV.js → tools-D_AtrhbO.js} +15 -15
  110. package/dist/{tools-C8k90ISV.js.map → tools-D_AtrhbO.js.map} +1 -1
  111. package/dist/{tools-g42e5kJz.js → tools-DyzRi_dQ.js} +8 -8
  112. package/dist/{tools-g42e5kJz.js.map → tools-DyzRi_dQ.js.map} +1 -1
  113. package/dist/{tui-CBnRWnrm.js → tui-CRp0iCuT.js} +5 -5
  114. package/dist/{tui-CBnRWnrm.js.map → tui-CRp0iCuT.js.map} +1 -1
  115. package/dist/{tui-BPWJ-J-k.js → tui-UI6ctBQJ.js} +1 -1
  116. package/dist/{upload-step-CFoxwd5t.js → upload-step-DrYXnpzI.js} +3 -3
  117. package/dist/{upload-step-CFoxwd5t.js.map → upload-step-DrYXnpzI.js.map} +1 -1
  118. package/dist/{urls-BTZhm5eA.js → urls-WeqfBLP4.js} +2 -2
  119. package/dist/{urls-BTZhm5eA.js.map → urls-WeqfBLP4.js.map} +1 -1
  120. package/dist/{wizard-abort-HtpiSsQO.js → wizard-abort-BwEefm6P.js} +5 -5
  121. package/dist/{wizard-abort-HtpiSsQO.js.map → wizard-abort-BwEefm6P.js.map} +1 -1
  122. package/dist/yara-hooks-MDtFbuoM.js +2 -0
  123. package/dist/{yara-hooks-BPYhN9WM.js → yara-hooks-aDbdsitJ.js} +41 -5
  124. package/dist/{yara-hooks-BPYhN9WM.js.map → yara-hooks-aDbdsitJ.js.map} +1 -1
  125. package/package.json +9 -7
  126. package/dist/agent-interface-fcDXqb-_.js.map +0 -1
  127. package/dist/agent-prompt-loader-F9gGPMt0.js.map +0 -1
  128. package/dist/analytics-C9QnGvuc.js +0 -2
  129. package/dist/analytics-GQv--mQm.js.map +0 -1
  130. package/dist/api-CsXfZKkq.js +0 -2
  131. package/dist/api-key-login-CDSxsYtP.js +0 -2
  132. package/dist/bounded-fs-B31zq2ir.js.map +0 -1
  133. package/dist/debug-BTUMatFI.js.map +0 -1
  134. package/dist/headless-DZ3mq07K.js +0 -3
  135. package/dist/package-json-Dblip2_X.js +0 -2
  136. package/dist/pi-2pYz2OY3.js.map +0 -1
  137. package/dist/posthog-integration-DxRkw5DI.js.map +0 -1
  138. package/dist/programs-D2793-PK.js +0 -3
  139. package/dist/programs-eTv2T2rs.js.map +0 -1
  140. package/dist/provisioning-CtQ0GEHa.js +0 -2
  141. package/dist/run-tool-BZFgFb_2.js +0 -2
  142. package/dist/subagent-BdDWA8j5.js.map +0 -1
  143. package/dist/task-Y8lWoDE7.js.map +0 -1
  144. package/dist/terminal-BLL-X6YP.js.map +0 -1
  145. package/dist/yara-hooks-BwhEDXIt.js +0 -2
@@ -1,25 +1,26 @@
1
1
  import { n as ErrorCodes } from "./emit-BqLCdV8Y.js";
2
- import { c as WIZARD_LOG_FILE, f as VERSION, h as RUN_SURFACE, i as getLogFilePath, o as logToFile, s as WIZARD_BENCHMARK_FILE, t as configureLogFile, v as runtimeEnv } from "./debug-BTUMatFI.js";
3
- import { $ as WIZARD_PROVISIONING_SCOPES, E as Harness, H as SETUP_REPORT_FILE, J as WIZARD_INTERACTION_EVENT_NAME, K as WIZARD_CONTACT_EMAIL, N as POSTHOG_DOCS_URL, O as Integration, Q as WIZARD_ORCHESTRATOR_SEEDED_TASKS_FLAG_KEY, S as GPT5_6_TERRA_MODEL, U as SONNET_5_MODEL, W as Sequence, X as WIZARD_ORCHESTRATOR_FLAG_KEY, Y as WIZARD_OAUTH_SCOPES, Z as WIZARD_ORCHESTRATOR_OVERRIDE_FLAG_KEY, a as ciExcludedTaskTypes, b as GPT5_6_LUNA_MODEL, c as AGENTIC_DETECTION_RETRY_TIMEOUT_MS, it as getSkillsBaseUrl, n as analytics, nt as WIZARD_SELF_DRIVING_USE_PI_HARNESS_FLAG_KEY, o as mayReportScanResults, p as DETECTION_TIMEOUT_MS, s as AGENTIC_DETECTION_FIRST_ATTEMPT_TIMEOUT_MS, v as EVENT_PLAN_FILE, w as HAIKU_MODEL, x as GPT5_6_SOL_MODEL } from "./analytics-GQv--mQm.js";
4
- import { a as WizardError, r as skillErrorCode, t as classifyRunFailure } from "./errors-DhFeR8Sp.js";
5
- import { r as withUtm, t as openTrackedLink } from "./links-5Blqk4Im.js";
6
- import { a as safeReadFile, i as readProjectFile, n as boundedGlob, o as walkProjectFiles } from "./bounded-fs-B31zq2ir.js";
7
- import { n as detectNodePackageManagers, t as detectAllPackageManagers } from "./package-manager-BqRNp5y_.js";
8
- import { n as getInstalledPackageVersion, r as hasDeclaredDependency, t as getDeclaredVersion } from "./package-json-2UrhE6kP.js";
9
- import { i as createVersionBucket, n as isUsingTypeScript, r as tryGetPackageJson } from "./setup-utils-B5E1rXLa.js";
10
- import { n as currentAccessToken } from "./oauth-session-CfkZ339p.js";
11
- import { f as gatewayAuth, m as useRunGatewayCredential, s as AgentSignals } from "./gateway-CJ8Jqbll.js";
12
- import { a as requireKnownModel, o as triageModelFor, r as isValidModel } from "./models-CLTglHFZ.js";
13
- import { R as WIZARD_TOOL_NAMES, at as isEnvFileName, ct as createTriageLLMProvider, et as createWizardAskBridge, i as runAgent$1, it as MAX_ENV_KEY_SET, n as buildRunTags, nt as LONGER_ASK_TIMEOUT_MS, ot as parseEnvKeyNames, p as registerCleanup, r as initializeAgent, rt as shouldDisableAsk, st as toPromptSafeRelativePath } from "./agent-interface-fcDXqb-_.js";
14
- import { n as mcpUrlFor } from "./host-resolution-Cht8bL39.js";
15
- import { a as piBackend, d as createEmitLog, f as createEmitSpinner, p as createProgressCollector, u as createAioCapture } from "./pi-2pYz2OY3.js";
16
- import { _ as TaskStatus, f as uncoveredBySink, g as SkipReason, h as QueueStore, m as QUEUE_DIR_NAME, u as dependencyClosure } from "./queue-tools-10w8lhEs.js";
17
- import { a as flushScanReport, p as scanInstalledSkill$1, s as formatYaraAbortMessage, v as installSkillById, y as fetchSkillMenu } from "./yara-hooks-BPYhN9WM.js";
18
- import { a as assembleTaskPrompt, d as taskModelSpec, i as assembleSeedPrompt, n as agentRunTools, o as loadAgentRegistry, s as promptModelFor, t as ASK_TOOL, u as resolveTask } from "./agent-prompt-loader-F9gGPMt0.js";
19
- import { r as requestDeepLink } from "./provisioning-c5JJ30KX.js";
2
+ import { c as WIZARD_LOG_FILE, f as VERSION, h as RUN_SURFACE, i as getLogFilePath, o as logToFile, s as WIZARD_BENCHMARK_FILE, t as configureLogFile, v as runtimeEnv } from "./debug-BfO_Txo8.js";
3
+ import { $ as WIZARD_ORCHESTRATOR_SEEDED_TASKS_FLAG_KEY, C as GPT5_6_TERRA_MODEL, D as Harness, G as Sequence, P as POSTHOG_DOCS_URL, Q as WIZARD_ORCHESTRATOR_OVERRIDE_FLAG_KEY, S as GPT5_6_SOL_MODEL, T as HAIKU_MODEL, U as SETUP_REPORT_FILE, W as SONNET_5_MODEL, X as WIZARD_OAUTH_SCOPES, Y as WIZARD_INTERACTION_EVENT_NAME, Z as WIZARD_ORCHESTRATOR_FLAG_KEY, a as ciExcludedTaskTypes, at as getSkillsBaseUrl, c as AGENTIC_DETECTION_FIRST_ATTEMPT_TIMEOUT_MS, et as WIZARD_PROVISIONING_SCOPES, it as WIZARD_USER_AGENT, k as Integration, l as AGENTIC_DETECTION_RETRY_TIMEOUT_MS, m as DETECTION_TIMEOUT_MS, n as analytics, o as mayReportScanResults, q as WIZARD_CONTACT_EMAIL, rt as WIZARD_SELF_DRIVING_USE_PI_HARNESS_FLAG_KEY, s as reportableDiscoveredFeatures, x as GPT5_6_LUNA_MODEL, y as EVENT_PLAN_FILE } from "./analytics-Drox5LSW.js";
4
+ import { a as WizardError, r as skillErrorCode, t as classifyRunFailure } from "./errors-BXlWujEw.js";
5
+ import { r as withUtm, t as openTrackedLink } from "./links-CatXPRxH.js";
6
+ import { a as safeReadFile, i as readProjectFile, n as boundedGlob, o as walkProjectFiles } from "./bounded-fs-TRL6IsoU.js";
7
+ import { n as detectNodePackageManagers, t as detectAllPackageManagers } from "./package-manager-B-9I_fAT.js";
8
+ import { n as getInstalledPackageVersion, r as hasDeclaredDependency, t as getDeclaredVersion } from "./package-json-z1agAYXs.js";
9
+ import { i as createVersionBucket, n as isUsingTypeScript, r as tryGetPackageJson } from "./setup-utils-CQY8PHjR.js";
10
+ import { n as currentAccessToken } from "./oauth-session-9EU3ky0v.js";
11
+ import { c as AgentSignals, h as useRunGatewayCredential, p as gatewayAuth } from "./gateway-CLQQb96F.js";
12
+ import { a as requireKnownModel, o as triageModelFor, r as isValidModel } from "./models-B0huHItG.js";
13
+ import { R as WIZARD_TOOL_NAMES, at as isEnvFileName, ct as createTriageLLMProvider, et as createWizardAskBridge, i as runAgent$1, it as MAX_ENV_KEY_SET, n as buildRunTags, nt as LONGER_ASK_TIMEOUT_MS, ot as parseEnvKeyNames, p as registerCleanup, r as initializeAgent, rt as shouldDisableAsk, st as toPromptSafeRelativePath } from "./agent-interface-BNssNetq.js";
14
+ import { s as resolveBaseUrl } from "./urls-WeqfBLP4.js";
15
+ import { n as mcpUrlFor } from "./host-resolution-DftfSjJD.js";
16
+ import { a as piBackend, d as createEmitLog, f as createEmitSpinner, p as createProgressCollector, u as createAioCapture } from "./pi-BSnSk6rv.js";
17
+ import { _ as TaskStatus, f as uncoveredBySink, g as SkipReason, h as QueueStore, m as QUEUE_DIR_NAME, u as dependencyClosure } from "./queue-tools-PFG6H93Q.js";
18
+ import { a as flushScanReport, b as fetchSkillMenu, m as scanInstalledSkill$1, s as formatYaraAbortMessage, y as installSkillById } from "./yara-hooks-aDbdsitJ.js";
19
+ import { a as assembleTaskPrompt, c as promptModelFor, d as resolveTask, f as taskModelSpec, i as assembleSeedPrompt, n as agentRunTools, o as detectedFacts, s as loadAgentRegistry, t as ASK_TOOL } from "./agent-prompt-loader-iV11XVEF.js";
20
+ import { r as requestDeepLink } from "./provisioning-CHUjae9V.js";
20
21
  import * as semver from "semver";
21
22
  import { major, minVersion } from "semver";
22
- import fs, { cpSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "fs";
23
+ import fs, { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "fs";
23
24
  import * as path$3 from "path";
24
25
  import path, { isAbsolute, join, relative, resolve, sep } from "path";
25
26
  import * as path$2 from "node:path";
@@ -27,7 +28,7 @@ import { execSync } from "node:child_process";
27
28
  import * as fs$2 from "node:fs";
28
29
  import fg from "fast-glob";
29
30
  import fs$1 from "fs/promises";
30
- import { randomUUID } from "crypto";
31
+ import { createHash, randomUUID } from "crypto";
31
32
  import { z } from "zod";
32
33
  import { zodToJsonSchema } from "zod-to-json-schema";
33
34
  //#region src/agent/runner/shared/bootstrap.ts
@@ -1473,7 +1474,7 @@ Important: You must read a file immediately before attempting to write it, even
1473
1474
  */
1474
1475
  function assemblePrompt(runDef, ctx) {
1475
1476
  const parts = [];
1476
- parts.push(defaultProjectPrompt(ctx));
1477
+ parts.push(defaultProjectPrompt(ctx) + detectedFacts(ctx.facts));
1477
1478
  if (runDef.customPrompt) parts.push(runDef.customPrompt(ctx));
1478
1479
  if (ctx.skillPath) parts.push(skillPrompt(ctx.skillPath, runDef.reportFile));
1479
1480
  return parts.join("\n\n");
@@ -1583,6 +1584,7 @@ async function executeLinear({ config, input, boot, emit, interaction, signal, t
1583
1584
  projectApiKey,
1584
1585
  host,
1585
1586
  skillPath,
1587
+ facts: config.projectFacts?.() ?? [],
1586
1588
  orgAiDataProcessingApproved: input.apiUser?.organization?.is_ai_data_processing_approved ?? null,
1587
1589
  teamProductOptIns: project ? {
1588
1590
  sessionReplay: project.session_recording_opt_in ?? null,
@@ -2517,7 +2519,8 @@ or integrate manually here:
2517
2519
  projectApiKey: boot.credentials.projectApiKey,
2518
2520
  host: boot.credentials.host,
2519
2521
  examplePath,
2520
- commandmentsPath
2522
+ commandmentsPath,
2523
+ facts: config.projectFacts?.() ?? []
2521
2524
  };
2522
2525
  logToFile(`[orchestrator] START program=${programId} dir=${input.installDir} run=${runId}`);
2523
2526
  analytics.wizardCapture("orchestrator started", { program_id: programId });
@@ -3247,7 +3250,7 @@ function safeErrorMessage(error) {
3247
3250
  * chunk does not grow.
3248
3251
  */
3249
3252
  async function scanInstalledSkill(skillDir, llmProvider) {
3250
- return (await import("./yara-hooks-BwhEDXIt.js")).scanInstalledSkill(skillDir, llmProvider);
3253
+ return (await import("./yara-hooks-MDtFbuoM.js")).scanInstalledSkill(skillDir, llmProvider);
3251
3254
  }
3252
3255
  /**
3253
3256
  * The MCP tutorial's prompt stream: one prompt against the PostHog MCP server,
@@ -3256,7 +3259,7 @@ async function scanInstalledSkill(skillDir, llmProvider) {
3256
3259
  * startup chunk does not grow.
3257
3260
  */
3258
3261
  async function* streamMcpPrompt(args) {
3259
- yield* (await import("./mcp-prompt-streaming-B5lIQ0l1.js")).streamMcpPrompt(args);
3262
+ yield* (await import("./mcp-prompt-streaming-M6UnLgGX.js")).streamMcpPrompt(args);
3260
3263
  }
3261
3264
  //#endregion
3262
3265
  //#region src/programs/frameworks/python/utils.ts
@@ -6754,6 +6757,42 @@ async function loadWizardFlags() {
6754
6757
  };
6755
6758
  }
6756
6759
  //#endregion
6760
+ //#region src/programs/codebase-scan/tags.ts
6761
+ /**
6762
+ * Analytics tags a program derives from the codebase scan. They are scan
6763
+ * results, so they leave the machine under the same consent as the scan
6764
+ * report. Local suggestions read the scan itself and never wait on this.
6765
+ */
6766
+ /** frameworkContext key holding the tags, kept until sharing is granted. */
6767
+ const SCAN_TAGS_KEY = "codebaseScanTags";
6768
+ /** Scan results may leave the machine: sharing is granted and telemetry is on. */
6769
+ function maySendScanResults(session) {
6770
+ return !session.noTelemetry && mayReportScanResults(session);
6771
+ }
6772
+ function getScanTags(session) {
6773
+ return session.frameworkContext["codebaseScanTags"] ?? {};
6774
+ }
6775
+ function setTags(tags) {
6776
+ for (const [key, value] of Object.entries(tags)) analytics.setTag(key, value);
6777
+ }
6778
+ /**
6779
+ * Tag later analytics events with what the scan found. A CI or headless run
6780
+ * starts with sharing granted, so the tags apply at once. An interactive run
6781
+ * keeps them until `reportCodebaseScan` sees the user grant sharing.
6782
+ */
6783
+ function tagScanResults(session, setFrameworkContext, tags) {
6784
+ const merged = {
6785
+ ...getScanTags(session),
6786
+ ...tags
6787
+ };
6788
+ setFrameworkContext(SCAN_TAGS_KEY, merged);
6789
+ if (maySendScanResults(session)) setTags(merged);
6790
+ }
6791
+ /** Apply the kept tags once sharing is granted. Setting a tag twice is harmless. */
6792
+ function applyScanTags(session) {
6793
+ if (maySendScanResults(session)) setTags(getScanTags(session));
6794
+ }
6795
+ //#endregion
6757
6796
  //#region src/programs/detection/agentic.ts
6758
6797
  /**
6759
6798
  * Agentic project detection.
@@ -6981,8 +7020,14 @@ function reachesUi(event) {
6981
7020
  /** Scan through `runAgent` with the bound triage model, then the SDK fallback. */
6982
7021
  async function detectProjectsWithAgent(session, options) {
6983
7022
  if (!session.credentials) throw new Error("Detection requires authenticated credentials.");
6984
- const { targets, programId, purpose = "set up a PostHog integration", recommend = false, rerankIds, onEvent, onProgress } = options;
6985
7023
  const flags = await loadWizardFlags();
7024
+ null?.getDecisionsMode(flags.flags);
7025
+ return await scanProjectsWithAgent(session, options, flags);
7026
+ }
7027
+ /** The agent run behind {@link detectProjectsWithAgent}. */
7028
+ async function scanProjectsWithAgent(session, options, flags) {
7029
+ if (!session.credentials) throw new Error("Detection requires authenticated credentials.");
7030
+ const { targets, programId, purpose = "set up a PostHog integration", recommend = false, rerankIds, onEvent, onProgress } = options;
6986
7031
  const reportSchema = detectionReportSchema(recommend);
6987
7032
  const schema = zodToJsonSchema(reportSchema);
6988
7033
  const config = {
@@ -7291,2475 +7336,645 @@ function extractTomlSectionKeys(sectionBody, opts = {}) {
7291
7336
  return depNames;
7292
7337
  }
7293
7338
  //#endregion
7294
- //#region src/programs/warehouse-sources/registry.ts
7295
- const CORE_SOURCE_DETECTORS = [
7296
- {
7297
- kind: "Postgres",
7298
- label: "PostgreSQL",
7299
- mode: "in-cli",
7300
- signals: {
7301
- npm: [
7302
- "pg",
7303
- "postgres",
7304
- "postgres.js",
7305
- "knex",
7306
- "sequelize"
7307
- ],
7308
- python: [
7309
- "psycopg",
7310
- "psycopg2",
7311
- "psycopg2-binary",
7312
- "asyncpg"
7313
- ],
7314
- ruby: ["pg"],
7315
- envKeys: [
7316
- /^DATABASE_URL$/,
7317
- /^POSTGRES_/,
7318
- /^PG(HOST|DATABASE|USER|PORT)$/
7319
- ]
7320
- },
7321
- supersededBy: ["Neon", "Supabase"]
7322
- },
7323
- {
7324
- kind: "MySQL",
7325
- label: "MySQL",
7326
- mode: "in-cli",
7327
- signals: {
7328
- npm: ["mysql", "mysql2"],
7329
- python: [
7330
- "pymysql",
7331
- "mysqlclient",
7332
- "mysql-connector-python"
7333
- ],
7334
- ruby: ["mysql2"],
7335
- envKeys: [/^MYSQL_/]
7336
- }
7337
- },
7338
- {
7339
- kind: "MongoDB",
7340
- label: "MongoDB",
7341
- mode: "in-cli",
7342
- signals: {
7343
- npm: ["mongodb", "mongoose"],
7344
- python: ["pymongo", "motor"],
7345
- ruby: ["mongo", "mongoid"],
7346
- envKeys: [/^MONGO(DB)?_/]
7347
- }
7348
- },
7349
- {
7350
- kind: "Snowflake",
7351
- label: "Snowflake",
7352
- mode: "in-cli",
7353
- signals: {
7354
- npm: ["snowflake-sdk"],
7355
- python: ["snowflake-connector-python", "snowflake-sqlalchemy"],
7356
- envKeys: [/^SNOWFLAKE_/]
7357
- }
7358
- },
7359
- {
7360
- kind: "BigQuery",
7361
- label: "BigQuery",
7362
- mode: "deep-link",
7363
- signals: {
7364
- npm: ["@google-cloud/bigquery"],
7365
- python: ["google-cloud-bigquery"],
7366
- envKeys: [/^BIGQUERY_/]
7367
- }
7368
- },
7369
- {
7370
- kind: "Redshift",
7371
- label: "Redshift",
7372
- mode: "in-cli",
7373
- signals: {
7374
- npm: ["node-redshift"],
7375
- python: ["redshift-connector"],
7376
- envKeys: [/^REDSHIFT_/]
7377
- }
7378
- },
7379
- {
7380
- kind: "MSSQL",
7381
- label: "SQL Server",
7382
- mode: "in-cli",
7383
- signals: {
7384
- npm: ["mssql", "tedious"],
7385
- python: ["pyodbc", "pymssql"],
7386
- envKeys: [/^MSSQL_/]
7387
- }
7388
- },
7389
- {
7390
- kind: "Supabase",
7391
- label: "Supabase",
7392
- mode: "in-cli",
7393
- signals: {
7394
- npm: ["@supabase/supabase-js"],
7395
- python: ["supabase"],
7396
- envKeys: [/^SUPABASE_/]
7397
- }
7398
- },
7399
- {
7400
- kind: "ClickHouse",
7401
- label: "ClickHouse",
7402
- mode: "in-cli",
7403
- signals: {
7404
- npm: ["@clickhouse/client"],
7405
- python: ["clickhouse-connect", "clickhouse-driver"],
7406
- envKeys: [/^CLICKHOUSE_/]
7407
- }
7408
- },
7409
- {
7410
- kind: "Convex",
7411
- label: "Convex",
7412
- mode: "in-cli",
7413
- signals: {
7414
- npm: ["convex"],
7415
- python: ["convex"],
7416
- envKeys: [/^CONVEX_/, /^NEXT_PUBLIC_CONVEX_URL$/]
7417
- }
7418
- },
7419
- {
7420
- kind: "Firebase",
7421
- label: "Firebase",
7422
- mode: "deep-link",
7423
- signals: {
7424
- npm: ["firebase-admin", "firebase"],
7425
- python: ["firebase-admin"],
7426
- envKeys: [/^FIREBASE_/, /^NEXT_PUBLIC_FIREBASE_/]
7427
- }
7428
- },
7429
- {
7430
- kind: "Neon",
7431
- label: "Neon",
7432
- mode: "in-cli",
7433
- signals: {
7434
- npm: ["@neondatabase/serverless"],
7435
- envKeys: [/^NEON_/]
7436
- }
7437
- },
7438
- {
7439
- kind: "PlanetScaleMySQL",
7440
- label: "PlanetScale MySQL",
7441
- mode: "in-cli",
7442
- signals: {
7443
- npm: ["@planetscale/database"],
7444
- envKeys: [/^PLANETSCALE_/]
7445
- }
7446
- },
7447
- {
7448
- kind: "DynamoDB",
7449
- label: "DynamoDB",
7450
- mode: "in-cli",
7451
- signals: {
7452
- npm: ["@aws-sdk/client-dynamodb", "@aws-sdk/lib-dynamodb"],
7453
- python: ["pynamodb"]
7454
- }
7455
- },
7456
- {
7457
- kind: "Motherduck",
7458
- label: "MotherDuck",
7459
- mode: "in-cli",
7460
- signals: {
7461
- npm: ["@motherduck/wasm-client"],
7462
- envKeys: [/^MOTHERDUCK_/]
7463
- }
7464
- },
7465
- {
7466
- kind: "Databricks",
7467
- label: "Databricks",
7468
- mode: "in-cli",
7469
- signals: {
7470
- npm: ["@databricks/sql"],
7471
- python: ["databricks-sql-connector", "databricks-sdk"],
7472
- envKeys: [/^DATABRICKS_/]
7473
- }
7474
- },
7475
- {
7476
- kind: "Singlestore",
7477
- label: "SingleStore",
7478
- mode: "in-cli",
7479
- signals: {
7480
- python: ["singlestoredb"],
7481
- envKeys: [/^SINGLESTORE_/]
7482
- }
7483
- },
7484
- {
7485
- kind: "Stripe",
7486
- label: "Stripe",
7487
- mode: "in-cli",
7488
- signals: {
7489
- npm: [
7490
- "stripe",
7491
- "@stripe/stripe-js",
7492
- "@stripe/react-stripe-js"
7493
- ],
7494
- python: ["stripe"],
7495
- ruby: ["stripe"],
7496
- envKeys: [/^STRIPE_(SECRET|API)_KEY$/]
7497
- }
7498
- },
7499
- {
7500
- kind: "Clerk",
7501
- label: "Clerk",
7502
- mode: "in-cli",
7503
- signals: {
7504
- npm: [
7505
- "@clerk/nextjs",
7506
- "@clerk/clerk-react",
7507
- "@clerk/backend",
7508
- "@clerk/express",
7509
- "@clerk/fastify",
7510
- "@clerk/remix"
7511
- ],
7512
- envKeys: [/^CLERK_SECRET_KEY$/, /^NEXT_PUBLIC_CLERK_/]
7513
- }
7514
- },
7515
- {
7516
- kind: "Resend",
7517
- label: "Resend",
7518
- mode: "in-cli",
7519
- signals: {
7520
- npm: ["resend"],
7521
- python: ["resend"],
7522
- envKeys: [/^RESEND_API_KEY$/]
7523
- }
7524
- },
7525
- {
7526
- kind: "Shopify",
7527
- label: "Shopify",
7528
- mode: "in-cli",
7529
- signals: {
7530
- npm: ["@shopify/shopify-api", "shopify-api-node"],
7531
- python: ["shopifyapi"],
7532
- envKeys: [/^SHOPIFY_/]
7533
- }
7534
- },
7535
- {
7536
- kind: "Klaviyo",
7537
- label: "Klaviyo",
7538
- mode: "in-cli",
7539
- signals: {
7540
- npm: ["klaviyo-api"],
7541
- python: ["klaviyo-api"],
7542
- envKeys: [/^KLAVIYO_/]
7543
- }
7544
- },
7545
- {
7546
- kind: "Chargebee",
7547
- label: "Chargebee",
7548
- mode: "in-cli",
7549
- signals: {
7550
- npm: ["chargebee"],
7551
- python: ["chargebee"],
7552
- envKeys: [/^CHARGEBEE_/]
7553
- }
7554
- },
7555
- {
7556
- kind: "Paddle",
7557
- label: "Paddle",
7558
- mode: "in-cli",
7559
- signals: {
7560
- npm: ["@paddle/paddle-node-sdk", "@paddle/paddle-js"],
7561
- envKeys: [/^PADDLE_/]
7562
- }
7563
- },
7564
- {
7565
- kind: "Polar",
7566
- label: "Polar",
7567
- mode: "in-cli",
7568
- signals: {
7569
- npm: ["@polar-sh/sdk", "@polar-sh/nextjs"],
7570
- envKeys: [/^POLAR_/]
7571
- }
7572
- },
7573
- {
7574
- kind: "Mailchimp",
7575
- label: "Mailchimp",
7576
- mode: "in-cli",
7577
- signals: {
7578
- npm: ["@mailchimp/mailchimp_marketing"],
7579
- python: ["mailchimp-marketing"],
7580
- envKeys: [/^MAILCHIMP_/]
7581
- }
7582
- },
7583
- {
7584
- kind: "CustomerIO",
7585
- label: "Customer.io",
7586
- mode: "in-cli",
7587
- signals: {
7588
- npm: ["customerio-node"],
7589
- python: ["customerio"],
7590
- envKeys: [/^CUSTOMER_?IO_/]
7339
+ //#region src/programs/codebase-scan/fingerprint.ts
7340
+ /**
7341
+ * `host/path` for a git remote, lowercased, with no scheme, user, port or
7342
+ * `.git`: `git@github.com:PostHog/wizard.git` → `github.com/posthog/wizard`.
7343
+ */
7344
+ function normalizeGitRemote(remote) {
7345
+ let value = remote.trim().toLowerCase();
7346
+ const scheme = /^[a-z][a-z0-9+.-]*:\/\//.exec(value);
7347
+ if (scheme) value = value.slice(scheme[0].length);
7348
+ else value = value.replace(/^([^/:]+):/, "$1/");
7349
+ value = value.replace(/^[^@/]+@/, "");
7350
+ value = value.replace(/^([^/:]+):\d+\//, "$1/");
7351
+ return value.replace(/\/+$/, "").replace(/\.git$/, "");
7352
+ }
7353
+ /** How far up from the install dir to look for the repository root. */
7354
+ const MAX_PARENT_DIRS = 64;
7355
+ /** Git's own files are small. Anything larger at these paths is not one. */
7356
+ const MAX_GIT_FILE_BYTES = 64 * 1024;
7357
+ /**
7358
+ * A git file's text, or null unless it is a regular file under the size cap.
7359
+ * The project controls these paths, and a FIFO or device there would block
7360
+ * the read.
7361
+ */
7362
+ function readGitFile(file) {
7363
+ try {
7364
+ const stats = statSync(file);
7365
+ if (!stats.isFile() || stats.size > MAX_GIT_FILE_BYTES) return null;
7366
+ return readFileSync(file, "utf8");
7367
+ } catch {
7368
+ return null;
7369
+ }
7370
+ }
7371
+ /** The real path of `target` when it is `realRoot` or inside it, else null. */
7372
+ function realPathWithin(target, realRoot) {
7373
+ try {
7374
+ const real = realpathSync(target);
7375
+ const relative = path.relative(realRoot, real);
7376
+ return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative) ? real : null;
7377
+ } catch {
7378
+ return null;
7379
+ }
7380
+ }
7381
+ function isDirectory(target) {
7382
+ try {
7383
+ return statSync(target).isDirectory();
7384
+ } catch {
7385
+ return false;
7386
+ }
7387
+ }
7388
+ /**
7389
+ * The repository root above `installDir` and the git directory holding its
7390
+ * config, found by walking up. A `.git` file (worktree or submodule) points at
7391
+ * the real git directory, and a worktree's `commondir` points at the shared one.
7392
+ *
7393
+ * The project chooses what those pointers say, so one is followed only to a
7394
+ * place inside the install dir. Otherwise there is no repository to read, and
7395
+ * the fingerprint falls back to the install dir's path.
7396
+ */
7397
+ function findGitConfigDir(installDir) {
7398
+ let realInstallDir;
7399
+ try {
7400
+ realInstallDir = realpathSync(installDir);
7401
+ } catch {
7402
+ return null;
7403
+ }
7404
+ let dir = path.resolve(installDir);
7405
+ for (let depth = 0; depth < MAX_PARENT_DIRS; depth += 1) {
7406
+ const dotGit = path.join(dir, ".git");
7407
+ let gitDir = null;
7408
+ if (isDirectory(dotGit)) gitDir = dotGit;
7409
+ else {
7410
+ const pointer = /^gitdir:\s*(.+)$/m.exec(readGitFile(dotGit) ?? "");
7411
+ if (pointer) {
7412
+ gitDir = realPathWithin(path.resolve(dir, pointer[1].trim()), realInstallDir);
7413
+ if (!gitDir) return null;
7414
+ }
7591
7415
  }
7592
- },
7593
- {
7594
- kind: "Typeform",
7595
- label: "Typeform",
7596
- mode: "in-cli",
7597
- signals: {
7598
- npm: ["@typeform/api-client"],
7599
- envKeys: [/^TYPEFORM_/]
7416
+ if (gitDir) {
7417
+ const common = readGitFile(path.join(gitDir, "commondir"))?.trim();
7418
+ if (!common) return {
7419
+ root: dir,
7420
+ gitDir
7421
+ };
7422
+ const commonDir = realPathWithin(path.resolve(gitDir, common), realInstallDir);
7423
+ return commonDir ? {
7424
+ root: dir,
7425
+ gitDir: commonDir
7426
+ } : null;
7600
7427
  }
7601
- },
7602
- {
7603
- kind: "Sentry",
7604
- label: "Sentry",
7605
- mode: "in-cli",
7606
- signals: {
7607
- npm: [
7608
- "@sentry/node",
7609
- "@sentry/browser",
7610
- "@sentry/react",
7611
- "@sentry/nextjs"
7612
- ],
7613
- python: ["sentry-sdk"],
7614
- ruby: ["sentry-ruby"]
7428
+ const parent = path.dirname(dir);
7429
+ if (parent === dir) return null;
7430
+ dir = parent;
7431
+ }
7432
+ return null;
7433
+ }
7434
+ /**
7435
+ * The repository's root and `origin` URL, read from its config file. Read
7436
+ * directly rather than through `git`: on Windows, running a bare `git` from
7437
+ * the project directory can launch a `git.exe` committed to the repository.
7438
+ */
7439
+ function readRepository(installDir) {
7440
+ const found = findGitConfigDir(installDir);
7441
+ if (!found) return null;
7442
+ const config = readGitFile(path.join(found.gitDir, "config"));
7443
+ if (!config) return null;
7444
+ let inOrigin = false;
7445
+ for (const raw of config.split("\n")) {
7446
+ const line = raw.trim();
7447
+ const section = /^\[([^\]]+)\]$/.exec(line);
7448
+ if (section) {
7449
+ inOrigin = /^remote\s+"origin"$/.test(section[1].trim());
7450
+ continue;
7615
7451
  }
7616
- },
7617
- {
7618
- kind: "Plaid",
7619
- label: "Plaid",
7620
- mode: "in-cli",
7621
- signals: {
7622
- npm: ["plaid"],
7623
- python: ["plaid-python", "plaid"],
7624
- ruby: ["plaid"],
7625
- envKeys: [/^PLAID_/]
7452
+ if (!inOrigin) continue;
7453
+ const remote = /^url\s*=\s*(.+)$/.exec(line)?.[1].trim();
7454
+ if (remote) return {
7455
+ remote,
7456
+ root: found.root
7457
+ };
7458
+ }
7459
+ return null;
7460
+ }
7461
+ /**
7462
+ * Identifies the scanned codebase within a project, so two repos, or two apps
7463
+ * in one monorepo, sending scans to one project don't mark each other's tools
7464
+ * as removed. Salted with the project ID so the hash can't be matched to a
7465
+ * public repo name across projects.
7466
+ */
7467
+ function workspaceFingerprint(projectId, installDir, findRepository = readRepository) {
7468
+ const absolute = path.resolve(installDir);
7469
+ const repository = findRepository(absolute);
7470
+ const workspace = repository ? `${normalizeGitRemote(repository.remote)}:${repositoryPath(repository.root, absolute)}` : absolute;
7471
+ return createHash("sha256").update(`${projectId}:${workspace}`).digest("hex");
7472
+ }
7473
+ /** The install dir's path from the repository root, with POSIX separators; `.` for the root. */
7474
+ function repositoryPath(root, installDir) {
7475
+ const relative = path.relative(root, installDir);
7476
+ return relative ? relative.split(path.sep).join("/") : ".";
7477
+ }
7478
+ //#endregion
7479
+ //#region src/programs/warehouse-sources/manifests.ts
7480
+ /**
7481
+ * Dependency-name parsers for the manifests of every ecosystem the wizard
7482
+ * installs into. Each returns package names in the form the PostHog tool
7483
+ * catalog lists them for that ecosystem. Regex-based on purpose: a manifest is
7484
+ * read for names only, so a full parser per language buys nothing, and a
7485
+ * malformed file yields fewer names instead of an error.
7486
+ */
7487
+ /**
7488
+ * Crate names from `Cargo.toml` dependency tables: `[dependencies]`,
7489
+ * `[dev-dependencies]`, `[build-dependencies]`, `[workspace.dependencies]`,
7490
+ * target-specific tables, and the `[dependencies.<name>]` form. Dotted keys
7491
+ * (`sentry.workspace = true`) name their crate. A renamed dependency
7492
+ * (`alias = { package = "real" }`, `alias.package = "real"`, or a
7493
+ * `[dependencies.alias]` table with `package = "real"`) reports the real crate.
7494
+ */
7495
+ function parseCargoToml(content) {
7496
+ const crates = /* @__PURE__ */ new Map();
7497
+ const add = (key, crate) => {
7498
+ if (crate !== void 0) crates.set(key, crate);
7499
+ else if (!crates.has(key)) crates.set(key, key);
7500
+ };
7501
+ let inDeps = false;
7502
+ let tableDep = null;
7503
+ for (const raw of content.split("\n")) {
7504
+ const line = raw.trim();
7505
+ const header = /^\[\[?([^\]]+)\]\]?\s*(?:#.*)?$/.exec(line);
7506
+ if (header) {
7507
+ const table = header[1].trim();
7508
+ const named = /(?:^|\.)(?:dev-|build-)?dependencies\.([A-Za-z0-9_-]+)$/.exec(table);
7509
+ tableDep = named ? named[1] : null;
7510
+ if (tableDep) add(tableDep);
7511
+ inDeps = /(?:^|\.)(?:dev-|build-)?dependencies$/.test(table);
7512
+ continue;
7626
7513
  }
7627
- },
7628
- {
7629
- kind: "Braintree",
7630
- label: "Braintree",
7631
- mode: "in-cli",
7632
- signals: {
7633
- npm: ["braintree"],
7634
- python: ["braintree"],
7635
- ruby: ["braintree"],
7636
- envKeys: [/^BRAINTREE_/]
7514
+ if (tableDep) {
7515
+ const renamed = /^package\s*=\s*"([^"]+)"/.exec(line);
7516
+ if (renamed) add(tableDep, renamed[1]);
7517
+ continue;
7637
7518
  }
7638
- },
7639
- {
7640
- kind: "Square",
7641
- label: "Square",
7642
- mode: "in-cli",
7643
- signals: {
7644
- npm: ["square"],
7645
- python: ["squareup"],
7646
- ruby: ["square"],
7647
- envKeys: [/^SQUARE_/]
7519
+ if (!inDeps) continue;
7520
+ const entry = /^([A-Za-z0-9_-]+)(\.[A-Za-z0-9_-]+)?\s*=(.*)$/.exec(line);
7521
+ if (!entry) continue;
7522
+ const [, key, subkey, value] = entry;
7523
+ let renamed = null;
7524
+ if (subkey === ".package") renamed = /^\s*"([^"]+)"/.exec(value);
7525
+ else if (!subkey) renamed = /\bpackage\s*=\s*"([^"]+)"/.exec(value);
7526
+ add(key, renamed?.[1]);
7527
+ }
7528
+ return [...new Set(crates.values())];
7529
+ }
7530
+ /** Pod names from a `Podfile`; a subspec (`Firebase/Analytics`) reports its pod. */
7531
+ function parsePodfile(content) {
7532
+ return [...content.matchAll(/^\s*pod\s+['"]([^'"/]+)/gm)].map((m) => m[1]);
7533
+ }
7534
+ /** `composer.json` `require` and `require-dev` keys, minus PHP itself and extensions. */
7535
+ function parseComposerJson(content) {
7536
+ let manifest;
7537
+ try {
7538
+ manifest = JSON.parse(content);
7539
+ } catch {
7540
+ return [];
7541
+ }
7542
+ if (typeof manifest !== "object" || manifest === null) return [];
7543
+ const names = [];
7544
+ for (const section of ["require", "require-dev"]) {
7545
+ const deps = manifest[section];
7546
+ if (typeof deps !== "object" || deps === null) continue;
7547
+ for (const name of Object.keys(deps)) {
7548
+ if (name === "php" || name.startsWith("ext-")) continue;
7549
+ names.push(name.toLowerCase());
7550
+ }
7551
+ }
7552
+ return names;
7553
+ }
7554
+ /**
7555
+ * Module paths from `go.mod` `require` lines and blocks, without `// indirect`
7556
+ * requirements (a dependency of a dependency says little about the project).
7557
+ * A final `/vN` major-version element is dropped, so `stripe-go/v82` matches
7558
+ * the catalog's `stripe-go`. Case is kept: Go module paths are case-sensitive.
7559
+ */
7560
+ function parseGoMod(content) {
7561
+ const names = [];
7562
+ let inBlock = false;
7563
+ for (const raw of content.split("\n")) {
7564
+ const line = raw.trim();
7565
+ if (inBlock) {
7566
+ if (line.startsWith(")")) {
7567
+ inBlock = false;
7568
+ continue;
7569
+ }
7570
+ addGoRequirement(line, names);
7571
+ } else if (/^require\s*\(\s*(?:\/\/.*)?$/.test(line)) inBlock = true;
7572
+ else if (line.startsWith("require ")) addGoRequirement(line.slice(8).trim(), names);
7573
+ }
7574
+ return names;
7575
+ }
7576
+ function addGoRequirement(line, names) {
7577
+ if (!line || line.startsWith("//") || /\/\/\s*indirect\b/.test(line)) return;
7578
+ const modulePath = line.split(/\s+/)[0];
7579
+ names.push(modulePath.replace(/\/v\d+$/, ""));
7580
+ }
7581
+ /** Hex package names from `mix.exs` dependency tuples, e.g. `{:sentry, "~> 10.0"}`. */
7582
+ function parseMixExs(content) {
7583
+ return [...content.matchAll(/\{\s*:([a-z][a-z0-9_]*)\s*,/g)].map((m) => m[1]);
7584
+ }
7585
+ const MAVEN_COORDINATE = /['"]([A-Za-z0-9_.-]+):([A-Za-z0-9_.-]+)(?::[^'"\s]*)?['"]/g;
7586
+ const GRADLE_MAP_NOTATION = /group\s*[:=]\s*['"]([^'"]+)['"]\s*,\s*name\s*[:=]\s*['"]([^'"]+)['"]/g;
7587
+ /** `group:artifact` coordinates from `build.gradle` or `build.gradle.kts`. */
7588
+ function parseGradleBuild(content) {
7589
+ const names = [];
7590
+ for (const m of content.matchAll(MAVEN_COORDINATE)) names.push(`${m[1]}:${m[2]}`);
7591
+ for (const m of content.matchAll(GRADLE_MAP_NOTATION)) names.push(`${m[1]}:${m[2]}`);
7592
+ return names;
7593
+ }
7594
+ /** `group:artifact` coordinates from a Gradle version catalog (`libs.versions.toml`). */
7595
+ function parseGradleVersionCatalog(content) {
7596
+ const names = [];
7597
+ for (const m of content.matchAll(/\bmodule\s*=\s*"([^":]+):([^":]+)"/g)) names.push(`${m[1]}:${m[2]}`);
7598
+ for (const m of content.matchAll(GRADLE_MAP_NOTATION)) names.push(`${m[1]}:${m[2]}`);
7599
+ for (const m of content.matchAll(/^\s*[A-Za-z0-9_.-]+\s*=\s*"([^":\s]+):([^":\s]+):[^"]*"/gm)) names.push(`${m[1]}:${m[2]}`);
7600
+ return names;
7601
+ }
7602
+ /**
7603
+ * `groupId:artifactId` for each `<dependency>` in a `pom.xml`. Split on the
7604
+ * opening tag rather than matched lazily, which would rescan the rest of the
7605
+ * file for every unclosed `<dependency>`.
7606
+ */
7607
+ function parsePomXml(content) {
7608
+ const names = [];
7609
+ for (const piece of content.split("<dependency>").slice(1)) {
7610
+ const close = piece.indexOf("</dependency>");
7611
+ if (close === -1) continue;
7612
+ const block = piece.slice(0, close);
7613
+ const group = /<groupId>\s*([^<\s]+)\s*<\/groupId>/.exec(block);
7614
+ const artifact = /<artifactId>\s*([^<\s]+)\s*<\/artifactId>/.exec(block);
7615
+ if (group && artifact) names.push(`${group[1]}:${artifact[1]}`);
7616
+ }
7617
+ return names;
7618
+ }
7619
+ /** Package names under the top-level `dependencies` and `dev_dependencies` of a `pubspec.yaml`. */
7620
+ function parsePubspecYaml(content) {
7621
+ const names = [];
7622
+ let inDeps = false;
7623
+ let entryIndent = null;
7624
+ for (const raw of content.split("\n")) {
7625
+ if (!raw.trim() || raw.trim().startsWith("#")) continue;
7626
+ const indent = raw.length - raw.trimStart().length;
7627
+ if (indent === 0) {
7628
+ inDeps = /^(dev_)?dependencies:\s*(?:#.*)?$/.test(raw.trim());
7629
+ entryIndent = null;
7630
+ continue;
7648
7631
  }
7649
- },
7650
- {
7651
- kind: "GoCardless",
7652
- label: "GoCardless",
7653
- mode: "in-cli",
7654
- signals: {
7655
- npm: ["gocardless-nodejs"],
7656
- python: ["gocardless-pro"],
7657
- ruby: ["gocardless_pro"],
7658
- envKeys: [/^GOCARDLESS_/]
7632
+ if (!inDeps) continue;
7633
+ entryIndent ??= indent;
7634
+ if (indent !== entryIndent) continue;
7635
+ const key = /^([A-Za-z0-9_]+)\s*:/.exec(raw.trim());
7636
+ if (key) names.push(key[1]);
7637
+ }
7638
+ return names;
7639
+ }
7640
+ /**
7641
+ * Package URLs from `Package.swift`, as `host/owner/repo`: lowercased, with no
7642
+ * scheme and no `.git`, e.g. `github.com/getsentry/sentry-cocoa`.
7643
+ */
7644
+ function parsePackageSwift(content) {
7645
+ return [...content.matchAll(/\burl\s*:\s*"([^"]+)"/g)].map((m) => normalizeGitRemote(m[1]));
7646
+ }
7647
+ //#endregion
7648
+ //#region src/programs/warehouse-sources/detect.ts
7649
+ /**
7650
+ * Data warehouse source detection.
7651
+ *
7652
+ * Scans a project for codebase signals (package manifests, `.env` key
7653
+ * names, third-party script hosts in templates) and matches them against the
7654
+ * detectors the caller passes in, from `loadSourceDetectors`. Pure function:
7655
+ * no store mutations, no UI calls. Reads files locally — only the wizard
7656
+ * process does this; the agent never sees secret values.
7657
+ *
7658
+ * Note we only read `.env` KEY NAMES, never values.
7659
+ */
7660
+ const MAX_DEPTH = 3;
7661
+ const MAX_SIGNAL_SET = MAX_ENV_KEY_SET;
7662
+ const MAX_SIGNALS_PER_TOOL = 50;
7663
+ function addPackage(signals, ecosystem, name, file) {
7664
+ let names = signals.packages.get(ecosystem);
7665
+ if (!names) {
7666
+ names = /* @__PURE__ */ new Map();
7667
+ signals.packages.set(ecosystem, names);
7668
+ }
7669
+ if (names.size < MAX_SIGNAL_SET && !names.has(name)) names.set(name, file);
7670
+ }
7671
+ /** Where `matchedSignal` says a package was found, per ecosystem. */
7672
+ const PACKAGE_SIGNAL_LOCATIONS = {
7673
+ npm: "package.json",
7674
+ pypi: "Python dependencies",
7675
+ rubygems: "Gemfile"
7676
+ };
7677
+ /**
7678
+ * Detect every catalog tool the project at `installDir` appears to use,
7679
+ * including tools the wizard never offers as a warehouse source. Each carries
7680
+ * the signals that matched it, for reporting.
7681
+ */
7682
+ function detectTools(installDir, detectors) {
7683
+ const signals = collectSignals(installDir, catalogScriptHosts(detectors));
7684
+ const detected = [];
7685
+ for (const detector of detectors) {
7686
+ const matched = matchDetector(detector, signals);
7687
+ if (matched.length === 0) continue;
7688
+ detected.push({
7689
+ kind: detector.kind,
7690
+ label: detector.label,
7691
+ warehouseSourceType: detector.warehouseSourceType,
7692
+ mode: detector.mode,
7693
+ matchedSignal: describeSignal(matched[0]),
7694
+ signals: matched
7695
+ });
7696
+ }
7697
+ return {
7698
+ tools: detected,
7699
+ complete: signals.complete
7700
+ };
7701
+ }
7702
+ /**
7703
+ * The detected tools a user can import as warehouse sources, minus the ones a
7704
+ * more specific match supersedes. Kinds are unique, so the result is deduped.
7705
+ */
7706
+ function toWarehouseSources(tools, detectors) {
7707
+ const kinds = new Set(tools.map((tool) => tool.kind));
7708
+ const supersededBy = new Map(detectors.map((detector) => [detector.kind, detector.supersededBy ?? []]));
7709
+ const sources = [];
7710
+ for (const tool of tools) {
7711
+ const { mode, warehouseSourceType } = tool;
7712
+ if (mode === null || warehouseSourceType === null) continue;
7713
+ if (isSuperseded(supersededBy.get(tool.kind) ?? [], kinds)) continue;
7714
+ sources.push({
7715
+ kind: warehouseSourceType,
7716
+ label: tool.label,
7717
+ mode,
7718
+ matchedSignal: tool.matchedSignal
7719
+ });
7720
+ }
7721
+ return sources;
7722
+ }
7723
+ /**
7724
+ * Whether a more specific match covers this detector's data. Every consumer
7725
+ * treats a detected kind as a source to create, so a project whose Postgres
7726
+ * lives on Neon or Supabase would otherwise be offered both and end up with
7727
+ * the same tables imported twice.
7728
+ */
7729
+ function isSuperseded(supersededBy, detectedKinds) {
7730
+ return supersededBy.some((other) => detectedKinds.has(other));
7731
+ }
7732
+ /**
7733
+ * Every signal that matches the detector, capped per tool. Packages and script
7734
+ * hosts come before `.env` keys: they show the code uses the tool, while a key
7735
+ * can outlive it.
7736
+ */
7737
+ function matchDetector(detector, signals) {
7738
+ const { envKeys, scriptHosts, packages } = detector.signals;
7739
+ const matched = [];
7740
+ for (const [ecosystem, names] of Object.entries(packages)) {
7741
+ const found = signals.packages.get(ecosystem);
7742
+ if (!found) continue;
7743
+ for (const name of names) {
7744
+ const file = found.get(name);
7745
+ if (file !== void 0) matched.push({
7746
+ source: ecosystem,
7747
+ name,
7748
+ file
7749
+ });
7659
7750
  }
7751
+ }
7752
+ for (const host of scriptHosts) {
7753
+ const file = signals.scriptHosts.get(host);
7754
+ if (file !== void 0) matched.push({
7755
+ source: SCRIPT_HOST_SIGNAL_SOURCE,
7756
+ name: host,
7757
+ file
7758
+ });
7759
+ }
7760
+ if (envKeys.length > 0) {
7761
+ for (const [key, file] of signals.envKeys) if (envKeys.some((re) => re.test(key))) matched.push({
7762
+ source: ENV_KEY_SIGNAL_SOURCE,
7763
+ name: key,
7764
+ file
7765
+ });
7766
+ }
7767
+ return matched.slice(0, MAX_SIGNALS_PER_TOOL);
7768
+ }
7769
+ /** Signal source for a `.env` key name. */
7770
+ const ENV_KEY_SIGNAL_SOURCE = "env_key";
7771
+ /** Signal source for a third-party script hostname found in a template. */
7772
+ const SCRIPT_HOST_SIGNAL_SOURCE = "script_host";
7773
+ /**
7774
+ * The human-readable form of a signal, shown on screens and given to the agent.
7775
+ *
7776
+ * For an env key, name the file that actually matched. The agent acts on this
7777
+ * string, so pointing it at `.env` when the key lives in `apps/api/.env.local`
7778
+ * sends it to look in the wrong place and conclude the credential is absent.
7779
+ *
7780
+ * Both interpolated parts are repository-derived, so both are quoted as code
7781
+ * and both are constrained. `parseEnvKeyNames` limits the key to
7782
+ * `[A-Za-z_][A-Za-z0-9_]*`. The path is the unconstrained part: the repository
7783
+ * names its own directories, so it arrives already sanitised from
7784
+ * `toPromptSafeRelativePath` and cannot break out of the code span.
7785
+ */
7786
+ function describeSignal(signal) {
7787
+ if (signal.source === ENV_KEY_SIGNAL_SOURCE) return `found \`${signal.name}\` in \`${signal.file ?? ".env"}\``;
7788
+ if (signal.source === SCRIPT_HOST_SIGNAL_SOURCE) return `found a \`${signal.name}\` script in \`${signal.file ?? "a template"}\``;
7789
+ const location = PACKAGE_SIGNAL_LOCATIONS[signal.source] ?? `${signal.source} dependencies`;
7790
+ return `found \`${signal.name}\` in ${location}`;
7791
+ }
7792
+ function collectSignals(installDir, catalogScriptHosts) {
7793
+ const signals = {
7794
+ catalogScriptHosts,
7795
+ envKeys: /* @__PURE__ */ new Map(),
7796
+ scriptHosts: /* @__PURE__ */ new Map(),
7797
+ templatesRead: 0,
7798
+ packages: /* @__PURE__ */ new Map(),
7799
+ complete: true
7800
+ };
7801
+ signals.complete = walkProjectFiles(installDir, (name, fullPath) => ingestFile(installDir, name, fullPath, signals), MAX_DEPTH, { followSymlinks: false });
7802
+ return signals;
7803
+ }
7804
+ /**
7805
+ * Route a file to the right parser BY NAME, reading its contents only when the
7806
+ * name matches a manifest, an env file or a template we read. Checking the
7807
+ * name first avoids slurping every file in the tree (lockfiles, binaries,
7808
+ * assets) into memory just to discard it.
7809
+ */
7810
+ function ingestFile(installDir, name, fullPath, signals) {
7811
+ const ingest = ingestorFor(name, signals.catalogScriptHosts.length > 0);
7812
+ if (!ingest) return;
7813
+ if (ingest === addScriptHosts && signals.templatesRead >= MAX_TEMPLATE_FILES) return;
7814
+ const content = safeReadFile(fullPath);
7815
+ if (content === null) return;
7816
+ ingest(content, signals, toPromptSafeRelativePath(installDir, fullPath));
7817
+ }
7818
+ /** Pick the parser for a manifest filename, or null if it's not one we read. */
7819
+ function ingestorFor(name, readTemplates) {
7820
+ if (isEnvFileName(name)) return (c, s, relativePath) => addEnvKeys(c, s, relativePath);
7821
+ if (readTemplates && isTemplateFileName(name)) return addScriptHosts;
7822
+ if (name === "package.json") return addNpmDeps;
7823
+ if (name === "requirements.txt") return (c, s, file) => parseRequirementsTxt(c).forEach((d) => addPackage(s, "pypi", d, file));
7824
+ if (name === "pyproject.toml") return (c, s, file) => parsePyprojectToml(c).forEach((d) => addPackage(s, "pypi", d, file));
7825
+ if (name === "Pipfile") return (c, s, file) => parsePipfile(c).forEach((d) => addPackage(s, "pypi", d, file));
7826
+ const manifest = MANIFEST_PARSERS[name];
7827
+ if (manifest) return (c, s, file) => manifest.parse(c).forEach((d) => addPackage(s, manifest.ecosystem, d, file));
7828
+ return null;
7829
+ }
7830
+ /** Manifests of every ecosystem after npm and pypi, by file name, in ecosystem order. */
7831
+ const MANIFEST_PARSERS = {
7832
+ "Cargo.toml": {
7833
+ ecosystem: "cargo",
7834
+ parse: parseCargoToml
7660
7835
  },
7661
- {
7662
- kind: "Mollie",
7663
- label: "Mollie",
7664
- mode: "in-cli",
7665
- signals: {
7666
- npm: ["@mollie/api-client"],
7667
- python: ["mollie-api-python"],
7668
- envKeys: [/^MOLLIE_/]
7669
- }
7836
+ Podfile: {
7837
+ ecosystem: "cocoapods",
7838
+ parse: parsePodfile
7670
7839
  },
7671
- {
7672
- kind: "CheckoutCom",
7673
- label: "Checkout.com",
7674
- mode: "in-cli",
7675
- signals: {
7676
- npm: ["checkout-sdk-node"],
7677
- python: ["checkout-sdk"],
7678
- envKeys: [/^CKO_/, /^CHECKOUT_COM_/]
7679
- }
7840
+ "composer.json": {
7841
+ ecosystem: "composer",
7842
+ parse: parseComposerJson
7680
7843
  },
7681
- {
7682
- kind: "Recurly",
7683
- label: "Recurly",
7684
- mode: "in-cli",
7685
- signals: {
7686
- npm: ["recurly"],
7687
- python: ["recurly"],
7688
- ruby: ["recurly"],
7689
- envKeys: [/^RECURLY_/]
7690
- }
7844
+ "go.mod": {
7845
+ ecosystem: "go",
7846
+ parse: parseGoMod
7691
7847
  },
7692
- {
7693
- kind: "RevenueCat",
7694
- label: "RevenueCat",
7695
- mode: "in-cli",
7696
- signals: {
7697
- npm: [
7698
- "@revenuecat/purchases-js",
7699
- "react-native-purchases",
7700
- "@revenuecat/purchases-capacitor"
7701
- ],
7702
- envKeys: [/^REVENUE_?CAT_/]
7703
- }
7848
+ "mix.exs": {
7849
+ ecosystem: "hex",
7850
+ parse: parseMixExs
7704
7851
  },
7705
- {
7706
- kind: "Twilio",
7707
- label: "Twilio",
7708
- mode: "in-cli",
7709
- signals: {
7710
- npm: ["twilio"],
7711
- python: ["twilio"],
7712
- ruby: ["twilio-ruby"],
7713
- envKeys: [/^TWILIO_/]
7714
- }
7852
+ "build.gradle": {
7853
+ ecosystem: "maven",
7854
+ parse: parseGradleBuild
7715
7855
  },
7716
- {
7717
- kind: "SendGrid",
7718
- label: "SendGrid",
7719
- mode: "in-cli",
7720
- signals: {
7721
- npm: ["@sendgrid/mail", "@sendgrid/client"],
7722
- python: ["sendgrid"],
7723
- ruby: ["sendgrid-ruby"],
7724
- envKeys: [/^SENDGRID_/]
7725
- }
7856
+ "build.gradle.kts": {
7857
+ ecosystem: "maven",
7858
+ parse: parseGradleBuild
7726
7859
  },
7727
- {
7728
- kind: "Mailgun",
7729
- label: "Mailgun",
7730
- mode: "in-cli",
7731
- signals: {
7732
- npm: ["mailgun.js", "mailgun-js"],
7733
- python: ["mailgun"],
7734
- ruby: ["mailgun-ruby"],
7735
- envKeys: [/^MAILGUN_/]
7736
- }
7860
+ "libs.versions.toml": {
7861
+ ecosystem: "maven",
7862
+ parse: parseGradleVersionCatalog
7737
7863
  },
7738
- {
7739
- kind: "Postmark",
7740
- label: "Postmark",
7741
- mode: "in-cli",
7742
- signals: {
7743
- npm: ["postmark"],
7744
- python: ["postmarker"],
7745
- ruby: ["postmark"],
7746
- envKeys: [/^POSTMARK_/]
7747
- }
7864
+ "pom.xml": {
7865
+ ecosystem: "maven",
7866
+ parse: parsePomXml
7748
7867
  },
7749
- {
7750
- kind: "Brevo",
7751
- label: "Brevo",
7752
- mode: "in-cli",
7753
- signals: {
7754
- npm: ["@getbrevo/brevo", "sib-api-v3-sdk"],
7755
- python: ["sib-api-v3-sdk"],
7756
- envKeys: [/^BREVO_/]
7757
- }
7868
+ "pubspec.yaml": {
7869
+ ecosystem: "pub",
7870
+ parse: parsePubspecYaml
7758
7871
  },
7759
- {
7760
- kind: "MailerLite",
7761
- label: "MailerLite",
7762
- mode: "in-cli",
7763
- signals: {
7764
- npm: ["@mailerlite/mailerlite-nodejs"],
7765
- python: ["mailerlite"],
7766
- envKeys: [/^MAILERLITE_/]
7767
- }
7872
+ Gemfile: {
7873
+ ecosystem: "rubygems",
7874
+ parse: parseGemfile
7768
7875
  },
7769
- {
7770
- kind: "Mailjet",
7771
- label: "Mailjet",
7772
- mode: "in-cli",
7773
- signals: {
7774
- npm: ["node-mailjet"],
7775
- python: ["mailjet-rest"],
7776
- envKeys: [/^MAILJET_/, /^MJ_APIKEY_/]
7876
+ "Package.swift": {
7877
+ ecosystem: "swiftpm",
7878
+ parse: parsePackageSwift
7879
+ }
7880
+ };
7881
+ function catalogScriptHosts(detectors) {
7882
+ return [...new Set(detectors.flatMap((d) => d.signals.scriptHosts))];
7883
+ }
7884
+ /**
7885
+ * A scan matches script hosts in at most this many templates with a `<script`
7886
+ * tag, and skips any larger than {@link MAX_TEMPLATE_BYTES}.
7887
+ */
7888
+ const MAX_TEMPLATE_FILES = 300;
7889
+ const MAX_TEMPLATE_BYTES = 512 * 1024;
7890
+ const TEMPLATE_SUFFIXES = [
7891
+ ".html",
7892
+ ".htm",
7893
+ ".erb",
7894
+ ".ejs",
7895
+ ".hbs",
7896
+ ".jinja",
7897
+ ".j2",
7898
+ ".twig",
7899
+ ".blade.php",
7900
+ ".heex",
7901
+ ".liquid",
7902
+ ".vue",
7903
+ ".svelte",
7904
+ ".astro",
7905
+ ".jsx",
7906
+ ".tsx"
7907
+ ];
7908
+ function isTemplateFileName(name) {
7909
+ return TEMPLATE_SUFFIXES.some((suffix) => name.endsWith(suffix));
7910
+ }
7911
+ function hostMatches(host, catalogHost) {
7912
+ return host === catalogHost || host.endsWith(`.${catalogHost}`);
7913
+ }
7914
+ const URL_HOST = /(?:https?:)?\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)+)/gi;
7915
+ /**
7916
+ * The text of each `<script>` element in a template: its attributes and its
7917
+ * inline body. Case-insensitive, so a Next.js `<Script>` counts. A plain
7918
+ * scan with `indexOf`, so a template full of unclosed tags stays linear.
7919
+ */
7920
+ function scriptElements(content) {
7921
+ const lower = content.toLowerCase();
7922
+ const elements = [];
7923
+ let from = 0;
7924
+ for (;;) {
7925
+ const start = lower.indexOf("<script", from);
7926
+ if (start === -1) return elements;
7927
+ const after = lower.charAt(start + 7);
7928
+ if (after !== "" && !/[\s>/]/.test(after)) {
7929
+ from = start + 1;
7930
+ continue;
7777
7931
  }
7778
- },
7779
- {
7780
- kind: "LaunchDarkly",
7781
- label: "LaunchDarkly",
7782
- mode: "in-cli",
7783
- signals: {
7784
- npm: [
7785
- "launchdarkly-node-server-sdk",
7786
- "@launchdarkly/node-server-sdk",
7787
- "launchdarkly-js-client-sdk",
7788
- "launchdarkly-react-client-sdk"
7789
- ],
7790
- python: ["launchdarkly-server-sdk"],
7791
- ruby: ["launchdarkly-server-sdk"],
7792
- envKeys: [/^LAUNCHDARKLY_/, /^LD_SDK_KEY$/]
7793
- }
7794
- },
7795
- {
7796
- kind: "Optimizely",
7797
- label: "Optimizely",
7798
- mode: "in-cli",
7799
- signals: {
7800
- npm: ["@optimizely/optimizely-sdk"],
7801
- python: ["optimizely-sdk"],
7802
- envKeys: [/^OPTIMIZELY_/]
7803
- }
7804
- },
7805
- {
7806
- kind: "Braze",
7807
- label: "Braze",
7808
- mode: "in-cli",
7809
- signals: {
7810
- npm: ["@braze/web-sdk", "@braze/react-native-sdk"],
7811
- python: ["braze-client"],
7812
- envKeys: [/^BRAZE_/]
7813
- }
7814
- },
7815
- {
7816
- kind: "Rollbar",
7817
- label: "Rollbar",
7818
- mode: "in-cli",
7819
- signals: {
7820
- npm: ["rollbar"],
7821
- python: ["rollbar", "pyrollbar"],
7822
- ruby: ["rollbar"],
7823
- envKeys: [/^ROLLBAR_/]
7824
- }
7825
- },
7826
- {
7827
- kind: "Okta",
7828
- label: "Okta",
7829
- mode: "in-cli",
7830
- signals: {
7831
- npm: [
7832
- "@okta/okta-sdk-nodejs",
7833
- "@okta/okta-auth-js",
7834
- "@okta/okta-react"
7835
- ],
7836
- python: ["okta"],
7837
- envKeys: [/^OKTA_/]
7838
- }
7839
- },
7840
- {
7841
- kind: "WorkOS",
7842
- label: "WorkOS",
7843
- mode: "in-cli",
7844
- signals: {
7845
- npm: ["@workos-inc/node"],
7846
- python: ["workos"],
7847
- envKeys: [/^WORKOS_/]
7848
- }
7849
- },
7850
- {
7851
- kind: "Notion",
7852
- label: "Notion",
7853
- mode: "in-cli",
7854
- signals: {
7855
- npm: ["@notionhq/client"],
7856
- python: ["notion-client"],
7857
- envKeys: [/^NOTION_/]
7858
- }
7859
- },
7860
- {
7861
- kind: "FullStory",
7862
- label: "FullStory",
7863
- mode: "in-cli",
7864
- signals: {
7865
- npm: ["@fullstory/browser", "@fullstory/react-native"],
7866
- envKeys: [/^FULLSTORY_/]
7867
- }
7868
- },
7869
- {
7870
- kind: "Amplitude",
7871
- label: "Amplitude",
7872
- mode: "in-cli",
7873
- signals: {
7874
- npm: [
7875
- "@amplitude/analytics-browser",
7876
- "@amplitude/analytics-node",
7877
- "@amplitude/analytics-react-native",
7878
- "amplitude-js"
7879
- ],
7880
- python: ["amplitude-analytics"],
7881
- envKeys: [/^AMPLITUDE_/]
7882
- }
7883
- },
7884
- {
7885
- kind: "Mixpanel",
7886
- label: "Mixpanel",
7887
- mode: "in-cli",
7888
- signals: {
7889
- npm: ["mixpanel", "mixpanel-browser"],
7890
- python: ["mixpanel"],
7891
- ruby: ["mixpanel-ruby"],
7892
- envKeys: [/^MIXPANEL_/]
7893
- }
7894
- },
7895
- {
7896
- kind: "Pendo",
7897
- label: "Pendo",
7898
- mode: "in-cli",
7899
- signals: {
7900
- npm: ["@pendo/agent"],
7901
- envKeys: [/^PENDO_/]
7902
- }
7903
- },
7904
- {
7905
- kind: "Salesforce",
7906
- label: "Salesforce",
7907
- mode: "deep-link",
7908
- signals: {
7909
- npm: ["jsforce"],
7910
- python: ["simple-salesforce"],
7911
- envKeys: [/^SALESFORCE_/]
7912
- }
7913
- },
7914
- {
7915
- kind: "Hubspot",
7916
- label: "HubSpot",
7917
- mode: "deep-link",
7918
- signals: {
7919
- npm: ["@hubspot/api-client"],
7920
- python: ["hubspot-api-client"],
7921
- envKeys: [/^HUBSPOT_/]
7922
- }
7923
- },
7924
- {
7925
- kind: "Zendesk",
7926
- label: "Zendesk",
7927
- mode: "deep-link",
7928
- signals: {
7929
- npm: ["node-zendesk"],
7930
- python: ["zenpy"],
7931
- envKeys: [/^ZENDESK_/]
7932
- }
7933
- },
7934
- {
7935
- kind: "Intercom",
7936
- label: "Intercom",
7937
- mode: "deep-link",
7938
- signals: {
7939
- npm: ["intercom-client", "@intercom/messenger-js-sdk"],
7940
- python: ["python-intercom"],
7941
- envKeys: [/^INTERCOM_/]
7942
- }
7943
- },
7944
- {
7945
- kind: "Linear",
7946
- label: "Linear",
7947
- mode: "deep-link",
7948
- signals: {
7949
- npm: ["@linear/sdk"],
7950
- envKeys: [/^LINEAR_API_KEY$/]
7951
- }
7952
- },
7953
- {
7954
- kind: "Slack",
7955
- label: "Slack",
7956
- mode: "deep-link",
7957
- signals: {
7958
- npm: ["@slack/web-api", "@slack/bolt"],
7959
- python: ["slack-sdk", "slack-bolt"],
7960
- envKeys: [/^SLACK_(BOT|APP|SIGNING|CLIENT)_/]
7961
- }
7962
- },
7963
- {
7964
- kind: "Github",
7965
- label: "GitHub",
7966
- mode: "deep-link",
7967
- signals: {
7968
- npm: [
7969
- "@octokit/rest",
7970
- "@octokit/core",
7971
- "@octokit/graphql",
7972
- "octokit"
7973
- ],
7974
- python: ["pygithub"],
7975
- ruby: ["octokit"]
7976
- }
7977
- }
7978
- ];
7979
- const LLM_SOURCE_DETECTORS = [
7980
- {
7981
- kind: "OpenAI",
7982
- label: "OpenAI",
7983
- mode: "in-cli",
7984
- signals: {
7985
- npm: ["openai"],
7986
- python: ["openai"],
7987
- envKeys: [/^OPENAI_/]
7988
- }
7989
- },
7990
- {
7991
- kind: "Anthropic",
7992
- label: "Anthropic",
7993
- mode: "in-cli",
7994
- signals: {
7995
- npm: ["@anthropic-ai/sdk"],
7996
- python: ["anthropic"],
7997
- envKeys: [/^ANTHROPIC_/]
7998
- }
7999
- },
8000
- {
8001
- kind: "Cohere",
8002
- label: "Cohere",
8003
- mode: "in-cli",
8004
- signals: {
8005
- npm: ["cohere-ai"],
8006
- python: ["cohere"],
8007
- envKeys: [/^COHERE_/]
8008
- }
8009
- },
8010
- {
8011
- kind: "MistralAI",
8012
- label: "Mistral AI",
8013
- mode: "in-cli",
8014
- signals: {
8015
- npm: ["@mistralai/mistralai"],
8016
- python: ["mistralai"],
8017
- envKeys: [/^MISTRAL_/]
8018
- }
8019
- },
8020
- {
8021
- kind: "Groq",
8022
- label: "Groq",
8023
- mode: "in-cli",
8024
- signals: {
8025
- npm: ["groq-sdk"],
8026
- python: ["groq"],
8027
- envKeys: [/^GROQ_/]
8028
- }
8029
- },
8030
- {
8031
- kind: "TogetherAI",
8032
- label: "Together AI",
8033
- mode: "in-cli",
8034
- signals: {
8035
- npm: ["together-ai"],
8036
- python: ["together"],
8037
- envKeys: [/^TOGETHER_/]
8038
- }
8039
- },
8040
- {
8041
- kind: "FireworksAI",
8042
- label: "Fireworks AI",
8043
- mode: "in-cli",
8044
- signals: {
8045
- python: ["fireworks-ai"],
8046
- envKeys: [/^FIREWORKS_/]
8047
- }
8048
- },
8049
- {
8050
- kind: "Replicate",
8051
- label: "Replicate",
8052
- mode: "in-cli",
8053
- signals: {
8054
- npm: ["replicate"],
8055
- python: ["replicate"],
8056
- envKeys: [/^REPLICATE_/]
8057
- }
8058
- },
8059
- {
8060
- kind: "HuggingFace",
8061
- label: "Hugging Face",
8062
- mode: "in-cli",
8063
- signals: {
8064
- npm: ["@huggingface/inference"],
8065
- python: ["huggingface-hub"],
8066
- envKeys: [/^HUGGINGFACE_/, /^HF_TOKEN$/]
8067
- }
8068
- },
8069
- {
8070
- kind: "ElevenLabs",
8071
- label: "ElevenLabs",
8072
- mode: "in-cli",
8073
- signals: {
8074
- npm: ["@elevenlabs/elevenlabs-js", "elevenlabs"],
8075
- python: ["elevenlabs"],
8076
- envKeys: [/^ELEVENLABS_/, /^ELEVEN_LABS_/]
8077
- }
8078
- },
8079
- {
8080
- kind: "Deepgram",
8081
- label: "Deepgram",
8082
- mode: "in-cli",
8083
- signals: {
8084
- npm: ["@deepgram/sdk"],
8085
- python: ["deepgram-sdk"],
8086
- envKeys: [/^DEEPGRAM_/]
8087
- }
8088
- },
8089
- {
8090
- kind: "AssemblyAI",
8091
- label: "AssemblyAI",
8092
- mode: "in-cli",
8093
- signals: {
8094
- npm: ["assemblyai"],
8095
- python: ["assemblyai"],
8096
- envKeys: [/^ASSEMBLYAI_/]
8097
- }
8098
- },
8099
- {
8100
- kind: "Langfuse",
8101
- label: "Langfuse",
8102
- mode: "in-cli",
8103
- signals: {
8104
- npm: ["langfuse"],
8105
- python: ["langfuse"],
8106
- envKeys: [/^LANGFUSE_/]
8107
- }
8108
- },
8109
- {
8110
- kind: "LangSmith",
8111
- label: "LangSmith",
8112
- mode: "in-cli",
8113
- signals: {
8114
- npm: ["langsmith"],
8115
- python: ["langsmith"],
8116
- envKeys: [/^LANGSMITH_/, /^LANGCHAIN_API_KEY$/]
8117
- }
8118
- },
8119
- {
8120
- kind: "Firecrawl",
8121
- label: "Firecrawl",
8122
- mode: "in-cli",
8123
- signals: {
8124
- npm: ["@mendable/firecrawl-js"],
8125
- python: ["firecrawl-py"],
8126
- envKeys: [/^FIRECRAWL_/]
8127
- }
8128
- },
8129
- {
8130
- kind: "Mem0",
8131
- label: "Mem0",
8132
- mode: "in-cli",
8133
- signals: {
8134
- npm: ["mem0ai"],
8135
- python: ["mem0ai"],
8136
- envKeys: [/^MEM0_/]
8137
- }
8138
- },
8139
- {
8140
- kind: "Vellum",
8141
- label: "Vellum",
8142
- mode: "in-cli",
8143
- signals: {
8144
- npm: ["vellum-ai"],
8145
- python: ["vellum-ai"],
8146
- envKeys: [/^VELLUM_/]
8147
- }
8148
- },
8149
- {
8150
- kind: "Helicone",
8151
- label: "Helicone",
8152
- mode: "in-cli",
8153
- signals: {
8154
- npm: ["@helicone/helpers"],
8155
- envKeys: [/^HELICONE_/]
8156
- }
8157
- },
8158
- {
8159
- kind: "OpenRouter",
8160
- label: "OpenRouter",
8161
- mode: "in-cli",
8162
- signals: {
8163
- npm: ["@openrouter/ai-sdk-provider"],
8164
- envKeys: [/^OPENROUTER_/]
8165
- }
8166
- },
8167
- {
8168
- kind: "Browserbase",
8169
- label: "Browserbase",
8170
- mode: "in-cli",
8171
- signals: {
8172
- npm: ["@browserbasehq/sdk", "@browserbasehq/stagehand"],
8173
- python: ["browserbase"],
8174
- envKeys: [/^BROWSERBASE_/]
8175
- }
8176
- },
8177
- {
8178
- kind: "E2B",
8179
- label: "E2B",
8180
- mode: "in-cli",
8181
- signals: {
8182
- npm: ["e2b", "@e2b/code-interpreter"],
8183
- python: ["e2b", "e2b-code-interpreter"],
8184
- envKeys: [/^E2B_/]
8185
- }
8186
- },
8187
- {
8188
- kind: "WeightsAndBiases",
8189
- label: "Weights & Biases",
8190
- mode: "in-cli",
8191
- signals: {
8192
- python: ["wandb"],
8193
- envKeys: [/^WANDB_/]
8194
- }
8195
- },
8196
- {
8197
- kind: "LlamaCloud",
8198
- label: "LlamaCloud",
8199
- mode: "in-cli",
8200
- signals: {
8201
- npm: ["llama-cloud-services"],
8202
- python: ["llama-cloud-services"],
8203
- envKeys: [/^LLAMA_CLOUD_/]
8204
- }
8205
- },
8206
- {
8207
- kind: "Zep",
8208
- label: "Zep",
8209
- mode: "in-cli",
8210
- signals: {
8211
- npm: ["@getzep/zep-cloud"],
8212
- python: ["zep-cloud"],
8213
- envKeys: [/^ZEP_API_KEY$/]
8214
- }
8215
- }
8216
- ];
8217
- const OTHER_SOURCE_DETECTORS = [
8218
- {
8219
- kind: "Paystack",
8220
- label: "Paystack",
8221
- mode: "in-cli",
8222
- signals: {
8223
- python: ["paystackapi"],
8224
- envKeys: [/^PAYSTACK_/]
8225
- }
8226
- },
8227
- {
8228
- kind: "Orb",
8229
- label: "Orb",
8230
- mode: "in-cli",
8231
- signals: {
8232
- npm: ["orb-billing"],
8233
- python: ["orb-billing"],
8234
- envKeys: [/^ORB_/]
8235
- }
8236
- },
8237
- {
8238
- kind: "Lago",
8239
- label: "Lago",
8240
- mode: "in-cli",
8241
- signals: {
8242
- npm: ["lago-javascript-client"],
8243
- python: ["lago-python-client"],
8244
- envKeys: [/^LAGO_/]
8245
- }
8246
- },
8247
- {
8248
- kind: "Zuora",
8249
- label: "Zuora",
8250
- mode: "in-cli",
8251
- signals: { envKeys: [/^ZUORA_/] }
8252
- },
8253
- {
8254
- kind: "Stigg",
8255
- label: "Stigg",
8256
- mode: "in-cli",
8257
- signals: {
8258
- npm: ["@stigg/node-server-sdk"],
8259
- envKeys: [/^STIGG_/]
8260
- }
8261
- },
8262
- {
8263
- kind: "Ramp",
8264
- label: "Ramp",
8265
- mode: "in-cli",
8266
- signals: { envKeys: [/^RAMP_/] }
8267
- },
8268
- {
8269
- kind: "Brex",
8270
- label: "Brex",
8271
- mode: "in-cli",
8272
- signals: { envKeys: [/^BREX_/] }
8273
- },
8274
- {
8275
- kind: "Tremendous",
8276
- label: "Tremendous",
8277
- mode: "in-cli",
8278
- signals: { envKeys: [/^TREMENDOUS_/] }
8279
- },
8280
- {
8281
- kind: "Maxio",
8282
- label: "Maxio",
8283
- mode: "in-cli",
8284
- signals: { envKeys: [/^MAXIO_/] }
8285
- },
8286
- {
8287
- kind: "Chargify",
8288
- label: "Chargify",
8289
- mode: "in-cli",
8290
- signals: { envKeys: [/^CHARGIFY_/] }
8291
- },
8292
- {
8293
- kind: "Invoiced",
8294
- label: "Invoiced",
8295
- mode: "in-cli",
8296
- signals: {
8297
- npm: ["invoiced"],
8298
- python: ["invoiced"],
8299
- envKeys: [/^INVOICED_/]
8300
- }
8301
- },
8302
- {
8303
- kind: "LemonSqueezy",
8304
- label: "Lemon Squeezy",
8305
- mode: "in-cli",
8306
- signals: {
8307
- npm: ["@lemonsqueezy/lemonsqueezy.js"],
8308
- envKeys: [/^LEMONSQUEEZY_/, /^LEMON_SQUEEZY_/]
8309
- }
8310
- },
8311
- {
8312
- kind: "Razorpay",
8313
- label: "Razorpay",
8314
- mode: "in-cli",
8315
- signals: {
8316
- npm: ["razorpay"],
8317
- python: ["razorpay"],
8318
- ruby: ["razorpay"],
8319
- envKeys: [/^RAZORPAY_/]
8320
- }
8321
- },
8322
- {
8323
- kind: "Adyen",
8324
- label: "Adyen",
8325
- mode: "in-cli",
8326
- signals: {
8327
- npm: ["@adyen/api-library"],
8328
- python: ["adyen"],
8329
- envKeys: [/^ADYEN_/]
8330
- }
8331
- },
8332
- {
8333
- kind: "ChartMogul",
8334
- label: "ChartMogul",
8335
- mode: "in-cli",
8336
- signals: {
8337
- npm: ["chartmogul-node"],
8338
- python: ["chartmogul"],
8339
- envKeys: [/^CHARTMOGUL_/]
8340
- }
8341
- },
8342
- {
8343
- kind: "Airwallex",
8344
- label: "Airwallex",
8345
- mode: "in-cli",
8346
- signals: {
8347
- npm: ["airwallex-payment-elements"],
8348
- envKeys: [/^AIRWALLEX_/]
8349
- }
8350
- },
8351
- {
8352
- kind: "Flutterwave",
8353
- label: "Flutterwave",
8354
- mode: "in-cli",
8355
- signals: {
8356
- npm: ["flutterwave-node-v3"],
8357
- python: ["flutterwave"],
8358
- envKeys: [/^FLUTTERWAVE_/, /^FLW_(PUBLIC|SECRET)_KEY$/]
8359
- }
8360
- },
8361
- {
8362
- kind: "DodoPayments",
8363
- label: "Dodo Payments",
8364
- mode: "in-cli",
8365
- signals: {
8366
- npm: ["dodopayments"],
8367
- python: ["dodopayments"],
8368
- envKeys: [/^DODO_PAYMENTS_/]
8369
- }
8370
- },
8371
- {
8372
- kind: "Whop",
8373
- label: "Whop",
8374
- mode: "in-cli",
8375
- signals: {
8376
- npm: ["@whop/api", "@whop-apps/sdk"],
8377
- envKeys: [/^WHOP_/]
8378
- }
8379
- },
8380
- {
8381
- kind: "SparkPost",
8382
- label: "SparkPost",
8383
- mode: "in-cli",
8384
- signals: {
8385
- npm: ["sparkpost"],
8386
- python: ["sparkpost"],
8387
- envKeys: [/^SPARKPOST_/]
8388
- }
8389
- },
8390
- {
8391
- kind: "MailerSend",
8392
- label: "MailerSend",
8393
- mode: "in-cli",
8394
- signals: {
8395
- npm: ["mailersend"],
8396
- python: ["mailersend"],
8397
- envKeys: [/^MAILERSEND_/]
8398
- }
8399
- },
8400
- {
8401
- kind: "Iterable",
8402
- label: "Iterable",
8403
- mode: "in-cli",
8404
- signals: { envKeys: [/^ITERABLE_/] }
8405
- },
8406
- {
8407
- kind: "ConvertKit",
8408
- label: "ConvertKit",
8409
- mode: "in-cli",
8410
- signals: { envKeys: [/^CONVERTKIT_/] }
8411
- },
8412
- {
8413
- kind: "Drip",
8414
- label: "Drip",
8415
- mode: "in-cli",
8416
- signals: {
8417
- npm: ["drip-nodejs"],
8418
- envKeys: [/^DRIP_/]
8419
- }
8420
- },
8421
- {
8422
- kind: "Omnisend",
8423
- label: "Omnisend",
8424
- mode: "in-cli",
8425
- signals: { envKeys: [/^OMNISEND_/] }
8426
- },
8427
- {
8428
- kind: "EmailOctopus",
8429
- label: "EmailOctopus",
8430
- mode: "in-cli",
8431
- signals: { envKeys: [/^EMAILOCTOPUS_/, /^EMAIL_OCTOPUS_/] }
8432
- },
8433
- {
8434
- kind: "Elasticemail",
8435
- label: "Elastic Email",
8436
- mode: "in-cli",
8437
- signals: { envKeys: [/^ELASTICEMAIL_/, /^ELASTIC_EMAIL_/] }
8438
- },
8439
- {
8440
- kind: "CampaignMonitor",
8441
- label: "Campaign Monitor",
8442
- mode: "in-cli",
8443
- signals: {
8444
- npm: ["createsend-node"],
8445
- envKeys: [/^CAMPAIGN_MONITOR_/, /^CREATESEND_/]
8446
- }
8447
- },
8448
- {
8449
- kind: "Lemlist",
8450
- label: "Lemlist",
8451
- mode: "in-cli",
8452
- signals: { envKeys: [/^LEMLIST_/] }
8453
- },
8454
- {
8455
- kind: "Loops",
8456
- label: "Loops",
8457
- mode: "in-cli",
8458
- signals: {
8459
- npm: ["loops"],
8460
- envKeys: [/^LOOPS_/]
8461
- }
8462
- },
8463
- {
8464
- kind: "Knock",
8465
- label: "Knock",
8466
- mode: "in-cli",
8467
- signals: {
8468
- npm: ["@knocklabs/node", "@knocklabs/client"],
8469
- python: ["knockapi"],
8470
- envKeys: [/^KNOCK_/]
8471
- }
8472
- },
8473
- {
8474
- kind: "AwsSes",
8475
- label: "Amazon SES",
8476
- mode: "in-cli",
8477
- signals: {
8478
- npm: ["@aws-sdk/client-ses", "@aws-sdk/client-sesv2"],
8479
- envKeys: [/^AWS_SES_/]
8480
- }
8481
- },
8482
- {
8483
- kind: "Beehiiv",
8484
- label: "beehiiv",
8485
- mode: "in-cli",
8486
- signals: { envKeys: [/^BEEHIIV_/] }
8487
- },
8488
- {
8489
- kind: "Mailtrap",
8490
- label: "Mailtrap",
8491
- mode: "in-cli",
8492
- signals: {
8493
- npm: ["mailtrap"],
8494
- python: ["mailtrap"],
8495
- envKeys: [/^MAILTRAP_/]
8496
- }
8497
- },
8498
- {
8499
- kind: "Plunk",
8500
- label: "Plunk",
8501
- mode: "in-cli",
8502
- signals: {
8503
- npm: ["@plunk/node"],
8504
- envKeys: [/^PLUNK_/]
8505
- }
8506
- },
8507
- {
8508
- kind: "Ortto",
8509
- label: "Ortto",
8510
- mode: "in-cli",
8511
- signals: { envKeys: [/^ORTTO_/] }
8512
- },
8513
- {
8514
- kind: "Postscript",
8515
- label: "Postscript",
8516
- mode: "in-cli",
8517
- signals: { envKeys: [/^POSTSCRIPT_/] }
8518
- },
8519
- {
8520
- kind: "Marketo",
8521
- label: "Marketo",
8522
- mode: "in-cli",
8523
- signals: {
8524
- python: ["marketorestpython"],
8525
- envKeys: [/^MARKETO_/]
8526
- }
8527
- },
8528
- {
8529
- kind: "Lob",
8530
- label: "Lob",
8531
- mode: "in-cli",
8532
- signals: {
8533
- npm: ["lob"],
8534
- python: ["lob"],
8535
- envKeys: [/^LOB_API_KEY$/]
8536
- }
8537
- },
8538
- {
8539
- kind: "Segment",
8540
- label: "Segment",
8541
- mode: "in-cli",
8542
- signals: {
8543
- npm: ["@segment/analytics-node", "analytics-node"],
8544
- python: ["segment-analytics-python", "analytics-python"],
8545
- envKeys: [/^SEGMENT_/]
8546
- }
8547
- },
8548
- {
8549
- kind: "Snowplow",
8550
- label: "Snowplow Analytics",
8551
- mode: "in-cli",
8552
- signals: {
8553
- npm: ["@snowplow/node-tracker"],
8554
- python: ["snowplow-tracker"],
8555
- envKeys: [/^SNOWPLOW_/]
8556
- }
8557
- },
8558
- {
8559
- kind: "Matomo",
8560
- label: "Matomo",
8561
- mode: "in-cli",
8562
- signals: {
8563
- npm: ["matomo-tracker"],
8564
- envKeys: [/^MATOMO_/]
8565
- }
8566
- },
8567
- {
8568
- kind: "Plausible",
8569
- label: "Plausible",
8570
- mode: "in-cli",
8571
- signals: {
8572
- npm: ["plausible-tracker"],
8573
- envKeys: [/^PLAUSIBLE_/]
8574
- }
8575
- },
8576
- {
8577
- kind: "ConfigCat",
8578
- label: "ConfigCat",
8579
- mode: "in-cli",
8580
- signals: {
8581
- npm: ["configcat-node", "configcat-js"],
8582
- python: ["configcat-client"],
8583
- envKeys: [/^CONFIGCAT_/]
8584
- }
8585
- },
8586
- {
8587
- kind: "Flagsmith",
8588
- label: "Flagsmith",
8589
- mode: "in-cli",
8590
- signals: {
8591
- npm: ["flagsmith-nodejs", "flagsmith"],
8592
- python: ["flagsmith"],
8593
- envKeys: [/^FLAGSMITH_/]
8594
- }
8595
- },
8596
- {
8597
- kind: "Unleash",
8598
- label: "Unleash",
8599
- mode: "in-cli",
8600
- signals: {
8601
- npm: ["unleash-client"],
8602
- python: ["unleashclient"],
8603
- envKeys: [/^UNLEASH_/]
8604
- }
8605
- },
8606
- {
8607
- kind: "SplitIo",
8608
- label: "Split.io",
8609
- mode: "in-cli",
8610
- signals: {
8611
- npm: ["@splitsoftware/splitio"],
8612
- python: ["splitio-client"],
8613
- envKeys: [/^SPLITIO_/, /^SPLIT_IO_/]
8614
- }
8615
- },
8616
- {
8617
- kind: "Stytch",
8618
- label: "Stytch",
8619
- mode: "in-cli",
8620
- signals: {
8621
- npm: ["stytch"],
8622
- python: ["stytch"],
8623
- envKeys: [/^STYTCH_/]
8624
- }
8625
- },
8626
- {
8627
- kind: "Doppler",
8628
- label: "Doppler",
8629
- mode: "in-cli",
8630
- signals: {
8631
- npm: ["@dopplerhq/node-sdk"],
8632
- envKeys: [/^DOPPLER_/]
8633
- }
8634
- },
8635
- {
8636
- kind: "Infisical",
8637
- label: "Infisical",
8638
- mode: "in-cli",
8639
- signals: {
8640
- npm: ["@infisical/sdk"],
8641
- python: ["infisicalsdk"],
8642
- envKeys: [/^INFISICAL_/]
8643
- }
8644
- },
8645
- {
8646
- kind: "OnePassword",
8647
- label: "1Password",
8648
- mode: "in-cli",
8649
- signals: {
8650
- npm: ["@1password/sdk"],
8651
- python: ["onepassword-sdk"],
8652
- envKeys: [/^ONEPASSWORD_/, /^OP_SERVICE_ACCOUNT_TOKEN$/]
8653
- }
8654
- },
8655
- {
8656
- kind: "Persona",
8657
- label: "Persona",
8658
- mode: "in-cli",
8659
- signals: { envKeys: [/^PERSONA_/] }
8660
- },
8661
- {
8662
- kind: "Svix",
8663
- label: "Svix",
8664
- mode: "in-cli",
8665
- signals: {
8666
- npm: ["svix"],
8667
- python: ["svix"],
8668
- envKeys: [/^SVIX_/]
8669
- }
8670
- },
8671
- {
8672
- kind: "Mux",
8673
- label: "Mux",
8674
- mode: "in-cli",
8675
- signals: {
8676
- npm: ["@mux/mux-node"],
8677
- python: ["mux-python"],
8678
- envKeys: [/^MUX_/]
8679
- }
8680
- },
8681
- {
8682
- kind: "Vapi",
8683
- label: "Vapi",
8684
- mode: "in-cli",
8685
- signals: {
8686
- npm: ["@vapi-ai/server-sdk"],
8687
- python: ["vapi-server-sdk"],
8688
- envKeys: [/^VAPI_/]
8689
- }
8690
- },
8691
- {
8692
- kind: "Aircall",
8693
- label: "Aircall",
8694
- mode: "in-cli",
8695
- signals: { envKeys: [/^AIRCALL_/] }
8696
- },
8697
- {
8698
- kind: "JustCall",
8699
- label: "JustCall",
8700
- mode: "in-cli",
8701
- signals: { envKeys: [/^JUSTCALL_/] }
8702
- },
8703
- {
8704
- kind: "Telnyx",
8705
- label: "Telnyx",
8706
- mode: "in-cli",
8707
- signals: {
8708
- npm: ["telnyx"],
8709
- python: ["telnyx"],
8710
- envKeys: [/^TELNYX_/]
8711
- }
8712
- },
8713
- {
8714
- kind: "Plivo",
8715
- label: "Plivo",
8716
- mode: "in-cli",
8717
- signals: {
8718
- npm: ["plivo"],
8719
- python: ["plivo"],
8720
- envKeys: [/^PLIVO_/]
8721
- }
8722
- },
8723
- {
8724
- kind: "Courier",
8725
- label: "Courier",
8726
- mode: "in-cli",
8727
- signals: {
8728
- npm: ["@trycourier/courier"],
8729
- python: ["trycourier"],
8730
- envKeys: [/^COURIER_AUTH_TOKEN$/]
8731
- }
8732
- },
8733
- {
8734
- kind: "WooCommerce",
8735
- label: "WooCommerce",
8736
- mode: "in-cli",
8737
- signals: {
8738
- npm: ["@woocommerce/woocommerce-rest-api"],
8739
- python: ["woocommerce"],
8740
- envKeys: [/^WOOCOMMERCE_/]
8741
- }
8742
- },
8743
- {
8744
- kind: "Recharge",
8745
- label: "Recharge",
8746
- mode: "in-cli",
8747
- signals: { envKeys: [/^RECHARGE_/] }
8748
- },
8749
- {
8750
- kind: "Commercetools",
8751
- label: "commercetools",
8752
- mode: "in-cli",
8753
- signals: {
8754
- npm: ["@commercetools/platform-sdk"],
8755
- python: ["commercetools"],
8756
- envKeys: [/^CTP_/, /^COMMERCETOOLS_/]
8757
- }
8758
- },
8759
- {
8760
- kind: "Webflow",
8761
- label: "Webflow",
8762
- mode: "in-cli",
8763
- signals: {
8764
- npm: ["webflow-api"],
8765
- envKeys: [/^WEBFLOW_/]
8766
- }
8767
- },
8768
- {
8769
- kind: "Easypost",
8770
- label: "EasyPost",
8771
- mode: "in-cli",
8772
- signals: {
8773
- npm: ["@easypost/api"],
8774
- python: ["easypost"],
8775
- envKeys: [/^EASYPOST_/]
8776
- }
8777
- },
8778
- {
8779
- kind: "Shippo",
8780
- label: "Shippo",
8781
- mode: "in-cli",
8782
- signals: {
8783
- npm: ["shippo"],
8784
- python: ["shippo"],
8785
- envKeys: [/^SHIPPO_/]
8786
- }
8787
- },
8788
- {
8789
- kind: "ShipStation",
8790
- label: "ShipStation",
8791
- mode: "in-cli",
8792
- signals: { envKeys: [/^SHIPSTATION_/] }
8793
- },
8794
- {
8795
- kind: "Squarespace",
8796
- label: "Squarespace",
8797
- mode: "in-cli",
8798
- signals: { envKeys: [/^SQUARESPACE_/] }
8799
- },
8800
- {
8801
- kind: "Printify",
8802
- label: "Printify",
8803
- mode: "in-cli",
8804
- signals: { envKeys: [/^PRINTIFY_/] }
8805
- },
8806
- {
8807
- kind: "BigCommerce",
8808
- label: "BigCommerce",
8809
- mode: "in-cli",
8810
- signals: {
8811
- npm: ["node-bigcommerce"],
8812
- envKeys: [/^BIGCOMMERCE_/]
8813
- }
8814
- },
8815
- {
8816
- kind: "Freshdesk",
8817
- label: "Freshdesk",
8818
- mode: "in-cli",
8819
- signals: {
8820
- python: ["python-freshdesk"],
8821
- envKeys: [/^FRESHDESK_/]
8822
- }
8823
- },
8824
- {
8825
- kind: "Front",
8826
- label: "Front",
8827
- mode: "in-cli",
8828
- signals: { envKeys: [/^FRONTAPP_/, /^FRONT_API_/] }
8829
- },
8830
- {
8831
- kind: "Gorgias",
8832
- label: "Gorgias",
8833
- mode: "in-cli",
8834
- signals: { envKeys: [/^GORGIAS_/] }
8835
- },
8836
- {
8837
- kind: "Chatwoot",
8838
- label: "Chatwoot",
8839
- mode: "in-cli",
8840
- signals: { envKeys: [/^CHATWOOT_/] }
8841
- },
8842
- {
8843
- kind: "Kustomer",
8844
- label: "Kustomer",
8845
- mode: "in-cli",
8846
- signals: { envKeys: [/^KUSTOMER_/] }
8847
- },
8848
- {
8849
- kind: "Plain",
8850
- label: "Plain",
8851
- mode: "in-cli",
8852
- signals: {
8853
- npm: ["@team-plain/typescript-sdk"],
8854
- envKeys: [/^PLAIN_/]
8855
- }
8856
- },
8857
- {
8858
- kind: "Canny",
8859
- label: "Canny",
8860
- mode: "in-cli",
8861
- signals: { envKeys: [/^CANNY_/] }
8862
- },
8863
- {
8864
- kind: "Pylon",
8865
- label: "Pylon",
8866
- mode: "in-cli",
8867
- signals: { envKeys: [/^PYLON_/] }
8868
- },
8869
- {
8870
- kind: "GitLab",
8871
- label: "GitLab",
8872
- mode: "in-cli",
8873
- signals: {
8874
- npm: ["@gitbeaker/rest"],
8875
- python: ["python-gitlab"],
8876
- ruby: ["gitlab"],
8877
- envKeys: [/^GITLAB_/]
8878
- }
8879
- },
8880
- {
8881
- kind: "Bitbucket",
8882
- label: "Atlassian Bitbucket Cloud",
8883
- mode: "in-cli",
8884
- signals: {
8885
- npm: ["bitbucket"],
8886
- envKeys: [/^BITBUCKET_/]
8887
- }
8888
- },
8889
- {
8890
- kind: "Gitea",
8891
- label: "Gitea",
8892
- mode: "in-cli",
8893
- signals: {
8894
- npm: ["gitea-js"],
8895
- envKeys: [/^GITEA_/]
8896
- }
8897
- },
8898
- {
8899
- kind: "Datadog",
8900
- label: "Datadog",
8901
- mode: "in-cli",
8902
- signals: {
8903
- npm: ["@datadog/datadog-api-client", "dd-trace"],
8904
- python: ["datadog", "datadog-api-client"],
8905
- ruby: ["dogapi"],
8906
- envKeys: [
8907
- /^DATADOG_/,
8908
- /^DD_API_KEY$/,
8909
- /^DD_APP_KEY$/
8910
- ]
8911
- }
8912
- },
8913
- {
8914
- kind: "NewRelic",
8915
- label: "New Relic",
8916
- mode: "in-cli",
8917
- signals: {
8918
- npm: ["newrelic"],
8919
- python: ["newrelic"],
8920
- ruby: ["newrelic_rpm"],
8921
- envKeys: [/^NEW_RELIC_/, /^NEWRELIC_/]
8922
- }
8923
- },
8924
- {
8925
- kind: "Cloudflare",
8926
- label: "Cloudflare",
8927
- mode: "in-cli",
8928
- signals: {
8929
- npm: ["cloudflare"],
8930
- python: ["cloudflare"],
8931
- envKeys: [/^CLOUDFLARE_/, /^CF_API_TOKEN$/]
8932
- }
8933
- },
8934
- {
8935
- kind: "Elasticsearch",
8936
- label: "Elasticsearch",
8937
- mode: "in-cli",
8938
- signals: {
8939
- npm: ["@elastic/elasticsearch"],
8940
- python: ["elasticsearch"],
8941
- ruby: ["elasticsearch"],
8942
- envKeys: [/^ELASTICSEARCH_/]
8943
- }
8944
- },
8945
- {
8946
- kind: "Snyk",
8947
- label: "Snyk",
8948
- mode: "in-cli",
8949
- signals: {
8950
- npm: ["snyk"],
8951
- python: ["pysnyk"],
8952
- envKeys: [/^SNYK_/]
8953
- }
8954
- },
8955
- {
8956
- kind: "PagerDuty",
8957
- label: "PagerDuty",
8958
- mode: "in-cli",
8959
- signals: {
8960
- npm: ["@pagerduty/pdjs"],
8961
- python: ["pdpyras"],
8962
- envKeys: [/^PAGERDUTY_/, /^PD_API_KEY$/]
8963
- }
8964
- },
8965
- {
8966
- kind: "Opsgenie",
8967
- label: "Opsgenie",
8968
- mode: "in-cli",
8969
- signals: { envKeys: [/^OPSGENIE_/] }
8970
- },
8971
- {
8972
- kind: "IncidentIo",
8973
- label: "incident.io",
8974
- mode: "in-cli",
8975
- signals: { envKeys: [/^INCIDENT_IO_/, /^INCIDENTIO_/] }
8976
- },
8977
- {
8978
- kind: "Bugsnag",
8979
- label: "Bugsnag",
8980
- mode: "in-cli",
8981
- signals: {
8982
- npm: ["@bugsnag/js"],
8983
- python: ["bugsnag"],
8984
- ruby: ["bugsnag"],
8985
- envKeys: [/^BUGSNAG_/]
8986
- }
8987
- },
8988
- {
8989
- kind: "Honeybadger",
8990
- label: "Honeybadger",
8991
- mode: "in-cli",
8992
- signals: {
8993
- npm: ["@honeybadger-io/js"],
8994
- python: ["honeybadger"],
8995
- ruby: ["honeybadger"],
8996
- envKeys: [/^HONEYBADGER_/]
8997
- }
8998
- },
8999
- {
9000
- kind: "Raygun",
9001
- label: "Raygun",
9002
- mode: "in-cli",
9003
- signals: {
9004
- npm: ["raygun"],
9005
- python: ["raygun4py"],
9006
- ruby: ["raygun4ruby"],
9007
- envKeys: [/^RAYGUN_/]
9008
- }
9009
- },
9010
- {
9011
- kind: "CircleCI",
9012
- label: "CircleCI",
9013
- mode: "in-cli",
9014
- signals: { envKeys: [/^CIRCLECI_/, /^CIRCLE_TOKEN$/] }
9015
- },
9016
- {
9017
- kind: "Upstash",
9018
- label: "Upstash",
9019
- mode: "in-cli",
9020
- signals: {
9021
- npm: ["@upstash/redis"],
9022
- python: ["upstash-redis"],
9023
- envKeys: [/^UPSTASH_/]
9024
- }
9025
- },
9026
- {
9027
- kind: "Vercel",
9028
- label: "Vercel",
9029
- mode: "in-cli",
9030
- signals: {
9031
- npm: ["@vercel/sdk"],
9032
- envKeys: [/^VERCEL_API_TOKEN$/]
9033
- }
9034
- },
9035
- {
9036
- kind: "Algolia",
9037
- label: "Algolia",
9038
- mode: "in-cli",
9039
- signals: {
9040
- npm: ["algoliasearch"],
9041
- python: ["algoliasearch"],
9042
- ruby: ["algolia"],
9043
- envKeys: [/^ALGOLIA_/]
9044
- }
9045
- },
9046
- {
9047
- kind: "Inngest",
9048
- label: "Inngest",
9049
- mode: "in-cli",
9050
- signals: {
9051
- npm: ["inngest"],
9052
- python: ["inngest"],
9053
- envKeys: [/^INNGEST_/]
9054
- }
9055
- },
9056
- {
9057
- kind: "TriggerDev",
9058
- label: "Trigger.dev",
9059
- mode: "in-cli",
9060
- signals: {
9061
- npm: ["@trigger.dev/sdk"],
9062
- envKeys: [/^TRIGGER_(SECRET_KEY|API_KEY|PROJECT_ID)$/]
9063
- }
9064
- },
9065
- {
9066
- kind: "Ably",
9067
- label: "Ably",
9068
- mode: "in-cli",
9069
- signals: {
9070
- npm: ["ably"],
9071
- python: ["ably"],
9072
- envKeys: [/^ABLY_/]
9073
- }
9074
- },
9075
- {
9076
- kind: "Netlify",
9077
- label: "Netlify",
9078
- mode: "in-cli",
9079
- signals: {
9080
- npm: ["@netlify/functions"],
9081
- envKeys: [/^NETLIFY_/]
9082
- }
9083
- },
9084
- {
9085
- kind: "Railway",
9086
- label: "Railway",
9087
- mode: "in-cli",
9088
- signals: { envKeys: [/^RAILWAY_/] }
9089
- },
9090
- {
9091
- kind: "Render",
9092
- label: "Render",
9093
- mode: "in-cli",
9094
- signals: { envKeys: [/^RENDER_API_KEY$/] }
9095
- },
9096
- {
9097
- kind: "FlyIo",
9098
- label: "Fly.io",
9099
- mode: "in-cli",
9100
- signals: { envKeys: [/^FLY_API_TOKEN$/, /^FLY_APP_NAME$/] }
9101
- },
9102
- {
9103
- kind: "Heroku",
9104
- label: "Heroku",
9105
- mode: "in-cli",
9106
- signals: { envKeys: [/^HEROKU_/] }
9107
- },
9108
- {
9109
- kind: "DigitalOcean",
9110
- label: "DigitalOcean",
9111
- mode: "in-cli",
9112
- signals: { envKeys: [/^DIGITALOCEAN_/] }
9113
- },
9114
- {
9115
- kind: "Grafana",
9116
- label: "Grafana",
9117
- mode: "in-cli",
9118
- signals: {
9119
- python: ["grafana-client"],
9120
- envKeys: [/^GRAFANA_/]
9121
- }
9122
- },
9123
- {
9124
- kind: "BetterStack",
9125
- label: "Better Stack",
9126
- mode: "in-cli",
9127
- signals: {
9128
- npm: ["@logtail/node", "@logtail/js"],
9129
- envKeys: [
9130
- /^BETTERSTACK_/,
9131
- /^BETTER_STACK_/,
9132
- /^LOGTAIL_/
9133
- ]
9134
- }
9135
- },
9136
- {
9137
- kind: "Honeycomb",
9138
- label: "Honeycomb",
9139
- mode: "in-cli",
9140
- signals: {
9141
- npm: ["@honeycombio/opentelemetry-node", "honeycomb-beeline"],
9142
- python: ["honeycomb-beeline", "libhoney"],
9143
- envKeys: [/^HONEYCOMB_/]
9144
- }
9145
- },
9146
- {
9147
- kind: "Descope",
9148
- label: "Descope",
9149
- mode: "in-cli",
9150
- signals: {
9151
- npm: [
9152
- "@descope/node-sdk",
9153
- "@descope/nextjs-sdk",
9154
- "@descope/react-sdk"
9155
- ],
9156
- python: ["descope"],
9157
- envKeys: [/^DESCOPE_/]
9158
- }
9159
- },
9160
- {
9161
- kind: "FusionAuth",
9162
- label: "FusionAuth",
9163
- mode: "in-cli",
9164
- signals: {
9165
- npm: ["@fusionauth/typescript-client"],
9166
- python: ["fusionauth-client"],
9167
- envKeys: [/^FUSIONAUTH_/, /^FUSION_AUTH_/]
9168
- }
9169
- },
9170
- {
9171
- kind: "Hookdeck",
9172
- label: "Hookdeck",
9173
- mode: "in-cli",
9174
- signals: {
9175
- npm: ["@hookdeck/sdk"],
9176
- envKeys: [/^HOOKDECK_/]
9177
- }
9178
- },
9179
- {
9180
- kind: "N8n",
9181
- label: "n8n",
9182
- mode: "in-cli",
9183
- signals: {
9184
- npm: ["n8n"],
9185
- envKeys: [/^N8N_/]
9186
- }
9187
- },
9188
- {
9189
- kind: "Dbt",
9190
- label: "dbt",
9191
- mode: "in-cli",
9192
- signals: {
9193
- python: [
9194
- "dbt-core",
9195
- "dbt-snowflake",
9196
- "dbt-postgres",
9197
- "dbt-bigquery",
9198
- "dbt-redshift",
9199
- "dbt-databricks"
9200
- ],
9201
- envKeys: [/^DBT_/]
9202
- }
9203
- },
9204
- {
9205
- kind: "Fastly",
9206
- label: "Fastly",
9207
- mode: "in-cli",
9208
- signals: {
9209
- npm: ["fastly"],
9210
- python: ["fastly"],
9211
- envKeys: [/^FASTLY_/]
9212
- }
9213
- },
9214
- {
9215
- kind: "Bunny",
9216
- label: "Bunny.net",
9217
- mode: "in-cli",
9218
- signals: { envKeys: [/^BUNNY_/] }
9219
- },
9220
- {
9221
- kind: "Codecov",
9222
- label: "Codecov",
9223
- mode: "in-cli",
9224
- signals: {
9225
- npm: ["codecov"],
9226
- envKeys: [/^CODECOV_/]
9227
- }
9228
- },
9229
- {
9230
- kind: "Sourcegraph",
9231
- label: "Sourcegraph",
9232
- mode: "in-cli",
9233
- signals: { envKeys: [/^SOURCEGRAPH_/, /^SRC_ACCESS_TOKEN$/] }
9234
- },
9235
- {
9236
- kind: "TemporalIO",
9237
- label: "Temporal.io",
9238
- mode: "in-cli",
9239
- signals: {
9240
- npm: ["@temporalio/client", "@temporalio/worker"],
9241
- python: ["temporalio"],
9242
- envKeys: [/^TEMPORAL_/]
9243
- }
9244
- },
9245
- {
9246
- kind: "Hatchet",
9247
- label: "Hatchet",
9248
- mode: "in-cli",
9249
- signals: {
9250
- npm: ["@hatchet-dev/typescript-sdk"],
9251
- python: ["hatchet-sdk"],
9252
- envKeys: [/^HATCHET_/]
9253
- }
9254
- },
9255
- {
9256
- kind: "Pipedrive",
9257
- label: "Pipedrive",
9258
- mode: "in-cli",
9259
- signals: {
9260
- npm: ["pipedrive"],
9261
- envKeys: [/^PIPEDRIVE_/]
9262
- }
9263
- },
9264
- {
9265
- kind: "Close",
9266
- label: "Close",
9267
- mode: "in-cli",
9268
- signals: {
9269
- python: ["closeio"],
9270
- envKeys: [/^CLOSE_API_KEY$/]
9271
- }
9272
- },
9273
- {
9274
- kind: "Copper",
9275
- label: "Copper",
9276
- mode: "in-cli",
9277
- signals: { envKeys: [/^COPPER_/] }
9278
- },
9279
- {
9280
- kind: "Insightly",
9281
- label: "Insightly",
9282
- mode: "in-cli",
9283
- signals: { envKeys: [/^INSIGHTLY_/] }
9284
- },
9285
- {
9286
- kind: "Salesflare",
9287
- label: "Salesflare",
9288
- mode: "in-cli",
9289
- signals: { envKeys: [/^SALESFLARE_/] }
9290
- },
9291
- {
9292
- kind: "SalesLoft",
9293
- label: "Salesloft",
9294
- mode: "in-cli",
9295
- signals: { envKeys: [/^SALESLOFT_/] }
9296
- },
9297
- {
9298
- kind: "Attio",
9299
- label: "Attio",
9300
- mode: "in-cli",
9301
- signals: {
9302
- npm: ["attio"],
9303
- envKeys: [/^ATTIO_/]
9304
- }
9305
- },
9306
- {
9307
- kind: "ActiveCampaign",
9308
- label: "ActiveCampaign",
9309
- mode: "in-cli",
9310
- signals: { envKeys: [/^ACTIVECAMPAIGN_/, /^ACTIVE_CAMPAIGN_/] }
9311
- },
9312
- {
9313
- kind: "Vitally",
9314
- label: "Vitally",
9315
- mode: "in-cli",
9316
- signals: { envKeys: [/^VITALLY_/] }
9317
- },
9318
- {
9319
- kind: "Docusign",
9320
- label: "DocuSign",
9321
- mode: "in-cli",
9322
- signals: {
9323
- npm: ["docusign-esign"],
9324
- python: ["docusign-esign"],
9325
- envKeys: [/^DOCUSIGN_/]
9326
- }
9327
- },
9328
- {
9329
- kind: "PandaDoc",
9330
- label: "PandaDoc",
9331
- mode: "in-cli",
9332
- signals: { envKeys: [/^PANDADOC_/] }
9333
- },
9334
- {
9335
- kind: "Gong",
9336
- label: "Gong",
9337
- mode: "in-cli",
9338
- signals: { envKeys: [/^GONG_/] }
9339
- },
9340
- {
9341
- kind: "Asana",
9342
- label: "Asana",
9343
- mode: "in-cli",
9344
- signals: {
9345
- npm: ["asana"],
9346
- python: ["asana"],
9347
- envKeys: [/^ASANA_/]
9348
- }
9349
- },
9350
- {
9351
- kind: "Trello",
9352
- label: "Trello",
9353
- mode: "in-cli",
9354
- signals: {
9355
- npm: ["trello"],
9356
- envKeys: [/^TRELLO_/]
9357
- }
9358
- },
9359
- {
9360
- kind: "Todoist",
9361
- label: "Todoist",
9362
- mode: "in-cli",
9363
- signals: {
9364
- npm: ["@doist/todoist-api-typescript"],
9365
- python: ["todoist-api-python"],
9366
- envKeys: [/^TODOIST_/]
9367
- }
9368
- },
9369
- {
9370
- kind: "Monday",
9371
- label: "monday.com",
9372
- mode: "in-cli",
9373
- signals: {
9374
- npm: ["@mondaydotcomorg/api"],
9375
- envKeys: [/^MONDAY_/]
9376
- }
9377
- },
9378
- {
9379
- kind: "ClickUp",
9380
- label: "ClickUp",
9381
- mode: "in-cli",
9382
- signals: { envKeys: [/^CLICKUP_/] }
9383
- },
9384
- {
9385
- kind: "Height",
9386
- label: "Height",
9387
- mode: "in-cli",
9388
- signals: { envKeys: [/^HEIGHT_API_KEY$/] }
9389
- },
9390
- {
9391
- kind: "Shortcut",
9392
- label: "Shortcut",
9393
- mode: "in-cli",
9394
- signals: {
9395
- npm: ["@shortcut/client"],
9396
- envKeys: [/^SHORTCUT_/]
9397
- }
9398
- },
9399
- {
9400
- kind: "Jira",
9401
- label: "Jira",
9402
- mode: "in-cli",
9403
- signals: {
9404
- npm: ["jira-client"],
9405
- python: ["jira", "atlassian-python-api"],
9406
- envKeys: [/^JIRA_/]
9407
- }
9408
- },
9409
- {
9410
- kind: "Confluence",
9411
- label: "Confluence",
9412
- mode: "in-cli",
9413
- signals: { envKeys: [/^CONFLUENCE_/] }
9414
- },
9415
- {
9416
- kind: "Airtable",
9417
- label: "Airtable",
9418
- mode: "in-cli",
9419
- signals: {
9420
- npm: ["airtable"],
9421
- python: ["pyairtable"],
9422
- envKeys: [/^AIRTABLE_/]
9423
- }
9424
- },
9425
- {
9426
- kind: "Smartsheet",
9427
- label: "Smartsheet",
9428
- mode: "in-cli",
9429
- signals: {
9430
- npm: ["smartsheet"],
9431
- python: ["smartsheet-python-sdk"],
9432
- envKeys: [/^SMARTSHEET_/]
9433
- }
9434
- },
9435
- {
9436
- kind: "Coda",
9437
- label: "Coda",
9438
- mode: "in-cli",
9439
- signals: { envKeys: [/^CODA_API_/] }
9440
- },
9441
- {
9442
- kind: "Productboard",
9443
- label: "Productboard",
9444
- mode: "in-cli",
9445
- signals: { envKeys: [/^PRODUCTBOARD_/] }
9446
- },
9447
- {
9448
- kind: "GoogleSheets",
9449
- label: "Google Sheets",
9450
- mode: "in-cli",
9451
- signals: {
9452
- npm: ["google-spreadsheet"],
9453
- python: ["gspread"],
9454
- envKeys: [/^GOOGLE_SHEETS_/]
9455
- }
9456
- },
9457
- {
9458
- kind: "Formbricks",
9459
- label: "Formbricks",
9460
- mode: "in-cli",
9461
- signals: {
9462
- npm: ["@formbricks/js"],
9463
- envKeys: [/^FORMBRICKS_/]
9464
- }
9465
- },
9466
- {
9467
- kind: "Tally",
9468
- label: "Tally",
9469
- mode: "in-cli",
9470
- signals: { envKeys: [/^TALLY_API_KEY$/] }
9471
- },
9472
- {
9473
- kind: "Jotform",
9474
- label: "Jotform",
9475
- mode: "in-cli",
9476
- signals: {
9477
- python: ["jotform"],
9478
- envKeys: [/^JOTFORM_/]
9479
- }
9480
- },
9481
- {
9482
- kind: "GitBook",
9483
- label: "GitBook",
9484
- mode: "in-cli",
9485
- signals: {
9486
- npm: ["@gitbook/api"],
9487
- envKeys: [/^GITBOOK_/]
9488
- }
9489
- },
9490
- {
9491
- kind: "Wordpress",
9492
- label: "WordPress",
9493
- mode: "in-cli",
9494
- signals: {
9495
- npm: ["wpapi"],
9496
- envKeys: [/^WORDPRESS_/]
9497
- }
9498
- },
9499
- {
9500
- kind: "Clockify",
9501
- label: "Clockify",
9502
- mode: "in-cli",
9503
- signals: { envKeys: [/^CLOCKIFY_/] }
9504
- },
9505
- {
9506
- kind: "Calendly",
9507
- label: "Calendly",
9508
- mode: "in-cli",
9509
- signals: { envKeys: [/^CALENDLY_/] }
9510
- },
9511
- {
9512
- kind: "CalCom",
9513
- label: "Cal.com",
9514
- mode: "in-cli",
9515
- signals: { envKeys: [/^CALCOM_/, /^CAL_API_KEY$/] }
9516
- },
9517
- {
9518
- kind: "Eventbrite",
9519
- label: "Eventbrite",
9520
- mode: "in-cli",
9521
- signals: {
9522
- python: ["eventbrite"],
9523
- envKeys: [/^EVENTBRITE_/]
9524
- }
9525
- },
9526
- {
9527
- kind: "BambooHR",
9528
- label: "BambooHR",
9529
- mode: "in-cli",
9530
- signals: {
9531
- python: ["pybamboohr"],
9532
- envKeys: [/^BAMBOOHR_/, /^BAMBOO_HR_/]
9533
- }
9534
- },
9535
- {
9536
- kind: "Personio",
9537
- label: "Personio",
9538
- mode: "in-cli",
9539
- signals: { envKeys: [/^PERSONIO_/] }
9540
- },
9541
- {
9542
- kind: "Rippling",
9543
- label: "Rippling",
9544
- mode: "in-cli",
9545
- signals: { envKeys: [/^RIPPLING_/] }
9546
- },
9547
- {
9548
- kind: "Greenhouse",
9549
- label: "Greenhouse",
9550
- mode: "in-cli",
9551
- signals: { envKeys: [/^GREENHOUSE_/] }
9552
- },
9553
- {
9554
- kind: "Ashby",
9555
- label: "Ashby",
9556
- mode: "in-cli",
9557
- signals: { envKeys: [/^ASHBY_/] }
9558
- },
9559
- {
9560
- kind: "Workable",
9561
- label: "Workable",
9562
- mode: "in-cli",
9563
- signals: { envKeys: [/^WORKABLE_/] }
9564
- },
9565
- {
9566
- kind: "GoogleAds",
9567
- label: "Google Ads",
9568
- mode: "deep-link",
9569
- signals: {
9570
- npm: ["google-ads-api"],
9571
- python: ["google-ads"],
9572
- envKeys: [/^GOOGLE_ADS_/]
9573
- }
9574
- },
9575
- {
9576
- kind: "MetaAds",
9577
- label: "Meta Ads",
9578
- mode: "deep-link",
9579
- signals: {
9580
- npm: ["facebook-nodejs-business-sdk"],
9581
- python: ["facebook-business"],
9582
- envKeys: [/^META_ADS_/, /^FACEBOOK_ADS_/]
9583
- }
9584
- },
9585
- {
9586
- kind: "LinkedinAds",
9587
- label: "LinkedIn Ads",
9588
- mode: "deep-link",
9589
- signals: { envKeys: [/^LINKEDIN_ADS_/] }
9590
- },
9591
- {
9592
- kind: "TikTokAds",
9593
- label: "TikTok Ads",
9594
- mode: "deep-link",
9595
- signals: { envKeys: [/^TIKTOK_ADS_/] }
9596
- },
9597
- {
9598
- kind: "RedditAds",
9599
- label: "Reddit Ads",
9600
- mode: "deep-link",
9601
- signals: { envKeys: [/^REDDIT_ADS_/] }
9602
- },
9603
- {
9604
- kind: "PinterestAds",
9605
- label: "Pinterest Ads",
9606
- mode: "deep-link",
9607
- signals: { envKeys: [/^PINTEREST_ADS_/] }
9608
- },
9609
- {
9610
- kind: "BingAds",
9611
- label: "Bing Ads",
9612
- mode: "deep-link",
9613
- signals: {
9614
- python: ["bingads"],
9615
- envKeys: [/^BING_ADS_/, /^MICROSOFT_ADS_/]
9616
- }
9617
- },
9618
- {
9619
- kind: "AmazonAds",
9620
- label: "Amazon Ads",
9621
- mode: "deep-link",
9622
- signals: { envKeys: [/^AMAZON_ADS_/] }
9623
- },
9624
- {
9625
- kind: "GoogleAnalytics",
9626
- label: "Google Analytics",
9627
- mode: "deep-link",
9628
- signals: {
9629
- npm: ["react-ga4"],
9630
- python: ["google-analytics-data"],
9631
- envKeys: [/^GOOGLE_ANALYTICS_/, /^GA4_/]
9632
- }
9633
- },
9634
- {
9635
- kind: "GoogleSearchConsole",
9636
- label: "Google Search Console",
9637
- mode: "deep-link",
9638
- signals: { envKeys: [/^SEARCH_CONSOLE_/, /^GSC_/] }
9639
- },
9640
- {
9641
- kind: "AppsFlyer",
9642
- label: "AppsFlyer",
9643
- mode: "in-cli",
9644
- signals: {
9645
- npm: ["react-native-appsflyer"],
9646
- envKeys: [/^APPSFLYER_/]
9647
- }
9648
- },
9649
- {
9650
- kind: "Dub",
9651
- label: "Dub",
9652
- mode: "in-cli",
9653
- signals: {
9654
- npm: ["dub"],
9655
- python: ["dub"],
9656
- envKeys: [/^DUB_API_KEY$/]
7932
+ const tagEnd = endOfTag(content, start);
7933
+ let end = tagEnd;
7934
+ if (content.charAt(tagEnd - 1) !== "/") {
7935
+ const close = lower.indexOf("<\/script", tagEnd);
7936
+ end = close === -1 ? content.length : close;
9657
7937
  }
7938
+ elements.push(content.slice(start, end));
7939
+ if (end >= content.length) return elements;
7940
+ from = end + 1;
9658
7941
  }
9659
- ];
9660
- const SOURCE_DETECTORS = [
9661
- ...CORE_SOURCE_DETECTORS,
9662
- ...LLM_SOURCE_DETECTORS,
9663
- ...OTHER_SOURCE_DETECTORS
9664
- ];
9665
- /** Kinds from the LLM / AI section above — the only ones that may set `wizard_ai_sdk_detected`. */
9666
- const AI_SOURCE_KINDS = new Set(LLM_SOURCE_DETECTORS.map((detector) => detector.kind));
9667
- //#endregion
9668
- //#region src/programs/warehouse-sources/detect.ts
9669
- /**
9670
- * Data warehouse source detection.
9671
- *
9672
- * Scans a project for codebase signals (npm/python/ruby deps, `.env` key
9673
- * names) and matches them against the `SOURCE_DETECTORS` registry. Pure
9674
- * function: no store mutations, no UI calls. Reads files locally — only the
9675
- * wizard process does this; the agent never sees secret values.
9676
- *
9677
- * Note we only read `.env` KEY NAMES, never values.
9678
- */
9679
- const MAX_DEPTH = 3;
9680
- const MAX_SIGNAL_SET = MAX_ENV_KEY_SET;
9681
- function addCapped(set, value) {
9682
- if (set.size < MAX_SIGNAL_SET) set.add(value);
9683
7942
  }
9684
7943
  /**
9685
- * Detect which warehouse sources the project at `installDir` appears to use.
9686
- * Returns one `DetectedSource` per matched registry entry (kinds are unique,
9687
- * so the result is naturally deduped), minus the entries a more specific
9688
- * match supersedes.
7944
+ * The index of the `>` that closes the tag opened at `start`, or the end of
7945
+ * the content. Skips a `>` inside a quoted value or a JSX `{...}` expression,
7946
+ * such as an arrow function in a `<Script onLoad={() => ...}>` prop.
9689
7947
  */
9690
- function detectWarehouseSources(installDir) {
9691
- const signals = collectSignals(installDir);
9692
- const matched = [];
9693
- for (const detector of SOURCE_DETECTORS) {
9694
- const signal = matchDetector(detector, signals);
9695
- if (signal) matched.push({
9696
- detector,
9697
- signal
9698
- });
7948
+ function endOfTag(content, start) {
7949
+ let quote = null;
7950
+ let braces = 0;
7951
+ for (let i = start; i < content.length; i += 1) {
7952
+ const char = content[i];
7953
+ if (quote) {
7954
+ if (char === quote) quote = null;
7955
+ } else if (char === "\"" || char === "'" || char === "`") quote = char;
7956
+ else if (char === "{") braces += 1;
7957
+ else if (char === "}") braces = Math.max(0, braces - 1);
7958
+ else if (char === ">" && braces === 0) return i;
9699
7959
  }
9700
- const kinds = new Set(matched.map((m) => m.detector.kind));
9701
- return matched.filter(({ detector }) => !isSuperseded(detector, kinds)).map(({ detector, signal }) => ({
9702
- kind: detector.kind,
9703
- label: detector.label,
9704
- mode: detector.mode,
9705
- matchedSignal: signal
9706
- }));
7960
+ return content.length;
9707
7961
  }
9708
7962
  /**
9709
- * Whether a more specific match covers this detector's data. Every consumer
9710
- * treats a detected kind as a source to create, so a project whose Postgres
9711
- * lives on Neon or Supabase would otherwise be offered both and end up with
9712
- * the same tables imported twice.
7963
+ * Record the catalog script hosts a template loads from. Only `<script>`
7964
+ * elements count: a URL in a link or in text loads nothing. A hostname is kept
7965
+ * as the catalog host it matched, never as written, so a customer subdomain
7966
+ * such as `acme.zendesk.com` never leaves the machine. Every other hostname
7967
+ * is dropped as soon as it is read.
9713
7968
  */
9714
- function isSuperseded(detector, detectedKinds) {
9715
- return (detector.supersededBy ?? []).some((kind) => detectedKinds.has(kind));
9716
- }
9717
- /** Returns a human-readable description of the first matching signal, or null. */
9718
- function matchDetector(detector, signals) {
9719
- const { npm, python, ruby, envKeys } = detector.signals;
9720
- const npmHit = npm?.find((dep) => signals.npm.has(dep));
9721
- if (npmHit) return `found \`${npmHit}\` in package.json`;
9722
- const pyHit = python?.find((dep) => signals.python.has(dep));
9723
- if (pyHit) return `found \`${pyHit}\` in Python dependencies`;
9724
- const rubyHit = ruby?.find((gem) => signals.ruby.has(gem));
9725
- if (rubyHit) return `found \`${rubyHit}\` in Gemfile`;
9726
- if (envKeys) {
9727
- for (const [key, file] of signals.envKeys) if (envKeys.some((re) => re.test(key))) return `found \`${key}\` in \`${file}\``;
7969
+ function addScriptHosts(content, signals, file) {
7970
+ if (signals.templatesRead >= MAX_TEMPLATE_FILES) return;
7971
+ if (content.length > MAX_TEMPLATE_BYTES) return;
7972
+ if (!/<script\b/i.test(content)) return;
7973
+ signals.templatesRead += 1;
7974
+ for (const element of scriptElements(content)) for (const match of element.matchAll(URL_HOST)) {
7975
+ const host = match[1].toLowerCase();
7976
+ for (const catalogHost of signals.catalogScriptHosts) if (hostMatches(host, catalogHost) && !signals.scriptHosts.has(catalogHost)) signals.scriptHosts.set(catalogHost, file);
9728
7977
  }
9729
- return null;
9730
- }
9731
- function collectSignals(installDir) {
9732
- const signals = {
9733
- npm: /* @__PURE__ */ new Set(),
9734
- python: /* @__PURE__ */ new Set(),
9735
- ruby: /* @__PURE__ */ new Set(),
9736
- envKeys: /* @__PURE__ */ new Map()
9737
- };
9738
- walkProjectFiles(installDir, (name, fullPath) => ingestFile(installDir, name, fullPath, signals), MAX_DEPTH);
9739
- return signals;
9740
- }
9741
- /**
9742
- * Route a file to the right parser BY NAME, reading its contents only when the
9743
- * name matches one of the ~6 manifests we care about. Checking the name first
9744
- * avoids slurping every file in the tree (lockfiles, binaries, assets) into
9745
- * memory just to discard it.
9746
- */
9747
- function ingestFile(installDir, name, fullPath, signals) {
9748
- const ingest = ingestorFor(name);
9749
- if (!ingest) return;
9750
- const content = safeReadFile(fullPath);
9751
- if (content === null) return;
9752
- ingest(content, signals, toPromptSafeRelativePath(installDir, fullPath));
9753
- }
9754
- /** Pick the parser for a manifest filename, or null if it's not one we read. */
9755
- function ingestorFor(name) {
9756
- if (name === "package.json") return addNpmDeps;
9757
- if (name === "requirements.txt") return (c, s) => parseRequirementsTxt(c).forEach((d) => addCapped(s.python, d));
9758
- if (name === "pyproject.toml") return (c, s) => parsePyprojectToml(c).forEach((d) => addCapped(s.python, d));
9759
- if (name === "Pipfile") return (c, s) => parsePipfile(c).forEach((d) => addCapped(s.python, d));
9760
- if (name === "Gemfile") return (c, s) => parseGemfile(c).forEach((g) => addCapped(s.ruby, g));
9761
- if (isEnvFileName(name)) return (c, s, relativePath) => addEnvKeys(c, s, relativePath);
9762
- return null;
9763
7978
  }
9764
7979
  /**
9765
7980
  * Record each key NAME against the file that defined it. The first file wins,
@@ -9773,13 +7988,13 @@ function addEnvKeys(content, signals, relativePath) {
9773
7988
  signals.envKeys.set(key, relativePath);
9774
7989
  }
9775
7990
  }
9776
- function addNpmDeps(content, signals) {
7991
+ function addNpmDeps(content, signals, file) {
9777
7992
  try {
9778
7993
  const pkg = JSON.parse(content);
9779
7994
  for (const dep of Object.keys({
9780
7995
  ...pkg.dependencies,
9781
7996
  ...pkg.devDependencies
9782
- })) addCapped(signals.npm, dep);
7997
+ })) addPackage(signals, "npm", dep, file);
9783
7998
  } catch (error) {
9784
7999
  analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "detectWarehouseSources.parsePackageJson" });
9785
8000
  }
@@ -9800,6 +8015,169 @@ function getDetectedWarehouseSources(session) {
9800
8015
  return session.frameworkContext["detectedWarehouseSources"] ?? [];
9801
8016
  }
9802
8017
  //#endregion
8018
+ //#region src/programs/warehouse-sources/registry.ts
8019
+ /**
8020
+ * Registry of data warehouse source detectors, built from PostHog's tool
8021
+ * catalog. The wizard downloads the catalog at runtime, so a tool added in
8022
+ * PostHog reaches every installed wizard without a release. Edit the catalog
8023
+ * in PostHog, not here.
8024
+ */
8025
+ const CATALOG_API_PATH = "/api/wizard/tool_catalog/";
8026
+ /** Tried in order after a pinned PostHog host. Both regions serve the same catalog. */
8027
+ const PUBLIC_CATALOG_SOURCES = [{
8028
+ name: "us",
8029
+ url: `https://us.posthog.com${CATALOG_API_PATH}`
8030
+ }, {
8031
+ name: "eu",
8032
+ url: `https://eu.posthog.com${CATALOG_API_PATH}`
8033
+ }];
8034
+ const FETCH_TIMEOUT_MS = 5e3;
8035
+ const KIND = /^[A-Za-z0-9]{1,64}$/;
8036
+ const catalogToolSchema = z.object({
8037
+ kind: z.string().regex(KIND),
8038
+ label: z.string().regex(/^[\w .,&()+/'!-]{1,80}$/),
8039
+ warehouse_source_type: z.string().regex(KIND).nullable(),
8040
+ wizard_setup_mode: z.enum(["in-cli", "deep-link"]).nullable(),
8041
+ superseded_by: z.array(z.string().regex(KIND)),
8042
+ signals: z.object({
8043
+ env_key_patterns: z.array(z.string().regex(/^\^(?:(?:[A-Za-z0-9_]|\((?:[A-Za-z0-9_]+\|)*[A-Za-z0-9_]+\))\??)+\$?$/)),
8044
+ script_hosts: z.array(z.string().regex(/^[a-z0-9.-]{1,253}$/)),
8045
+ packages: z.record(z.string(), z.array(z.string().regex(/^[A-Za-z0-9@/._:+~-]{1,214}$/)))
8046
+ })
8047
+ });
8048
+ const catalogEnvelopeSchema = z.object({
8049
+ schema_version: z.literal(1),
8050
+ tools: z.array(z.unknown())
8051
+ });
8052
+ function toDetector(tool) {
8053
+ return {
8054
+ kind: tool.kind,
8055
+ label: tool.label,
8056
+ warehouseSourceType: tool.warehouse_source_type,
8057
+ mode: tool.wizard_setup_mode,
8058
+ signals: {
8059
+ envKeys: tool.signals.env_key_patterns.map((pattern) => new RegExp(pattern)),
8060
+ scriptHosts: tool.signals.script_hosts,
8061
+ packages: tool.signals.packages
8062
+ },
8063
+ supersededBy: tool.superseded_by
8064
+ };
8065
+ }
8066
+ /**
8067
+ * Reads a downloaded catalog body. Throws when the body is not a version 1
8068
+ * catalog. Skips a tool entry that fails validation, and logs it.
8069
+ */
8070
+ function toSourceDetectors(body) {
8071
+ const catalog = catalogEnvelopeSchema.safeParse(body);
8072
+ if (!catalog.success) throw new Error(`unreadable catalog: ${catalog.error.message}`);
8073
+ const detectors = [];
8074
+ catalog.data.tools.forEach((entry, index) => {
8075
+ const tool = catalogToolSchema.safeParse(entry);
8076
+ if (tool.success) detectors.push(toDetector(tool.data));
8077
+ else logToFile(`[tool-catalog] skipped tool ${index}: ${tool.error.message.slice(0, 500)}`);
8078
+ });
8079
+ return detectors;
8080
+ }
8081
+ function catalogSources(baseUrl) {
8082
+ const pinned = resolveBaseUrl(baseUrl);
8083
+ if (!pinned) return [...PUBLIC_CATALOG_SOURCES];
8084
+ return [{
8085
+ name: "pinned",
8086
+ url: `${pinned.replace(/\/+$/, "")}${CATALOG_API_PATH}`
8087
+ }, ...PUBLIC_CATALOG_SOURCES];
8088
+ }
8089
+ async function fetchDetectors(url) {
8090
+ const response = await fetch(url, {
8091
+ headers: { "User-Agent": WIZARD_USER_AGENT },
8092
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
8093
+ });
8094
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
8095
+ return toSourceDetectors(await response.json());
8096
+ }
8097
+ async function downloadDetectors(baseUrl) {
8098
+ const failures = {};
8099
+ for (const { name, url } of catalogSources(baseUrl)) try {
8100
+ const detectors = await fetchDetectors(url);
8101
+ logToFile(`[tool-catalog] loaded ${detectors.length} tools from ${url}`);
8102
+ return detectors;
8103
+ } catch (error) {
8104
+ const message = error instanceof Error ? error.message : String(error);
8105
+ logToFile(`[tool-catalog] ${url} failed: ${message}`);
8106
+ failures[name] = message.slice(0, 200);
8107
+ }
8108
+ analytics.wizardCapture("tool catalog unavailable", { failures });
8109
+ return [];
8110
+ }
8111
+ let loading = null;
8112
+ /**
8113
+ * The detectors from PostHog's tool catalog. Tries a pinned PostHog host
8114
+ * (`--base-url`, `--local-posthog`, dev builds) first, then US and EU.
8115
+ * Resolves to an empty list when every source fails, and never rejects, so
8116
+ * the run goes on without codebase detection.
8117
+ *
8118
+ * Every caller in the process shares the first call's download. The base URL
8119
+ * is fixed for a run, so the first caller's `baseUrl` is everyone's.
8120
+ */
8121
+ function loadSourceDetectors(baseUrl) {
8122
+ loading ??= downloadDetectors(baseUrl);
8123
+ return loading;
8124
+ }
8125
+ //#endregion
8126
+ //#region src/programs/codebase-scan/scan.ts
8127
+ /**
8128
+ * The run's one codebase scan. Every program that sets `scansCodebase` reads
8129
+ * this result instead of scanning on its own, so local suggestions, the
8130
+ * warehouse flow, the self-driving prompt and the report all agree. Each
8131
+ * program derives its own view from it in `onReady`; the scan writes no
8132
+ * program's keys, so a composed run sees only what its own program chose.
8133
+ *
8134
+ * Scanning always runs; consent only gates reporting (see `report.ts`).
8135
+ */
8136
+ /** frameworkContext key holding the full scan, every detected tool included. */
8137
+ const CODEBASE_SCAN_KEY = "codebaseScan";
8138
+ /** Scan once per run, for a program that sets `scansCodebase`. Best-effort: never throws. */
8139
+ async function scanCodebaseFor(config, ctx) {
8140
+ if (!config.scansCodebase) return;
8141
+ if (getCodebaseScan(ctx.session)) return;
8142
+ const detectors = await loadSourceDetectors(ctx.session.baseUrl);
8143
+ const scannedAt = (/* @__PURE__ */ new Date()).toISOString();
8144
+ const scannedDir = ctx.session.installDir;
8145
+ const failed = {
8146
+ program: config.id,
8147
+ scannedAt,
8148
+ scannedDir,
8149
+ tools: [],
8150
+ complete: false,
8151
+ failed: true
8152
+ };
8153
+ if (detectors.length === 0) {
8154
+ ctx.setFrameworkContext(CODEBASE_SCAN_KEY, failed);
8155
+ return;
8156
+ }
8157
+ try {
8158
+ const { tools, complete } = detectTools(scannedDir, detectors);
8159
+ ctx.setFrameworkContext(CODEBASE_SCAN_KEY, {
8160
+ program: config.id,
8161
+ scannedAt,
8162
+ scannedDir,
8163
+ tools,
8164
+ complete
8165
+ });
8166
+ } catch (error) {
8167
+ ctx.setFrameworkContext(CODEBASE_SCAN_KEY, failed);
8168
+ analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "scanCodebase" });
8169
+ }
8170
+ }
8171
+ /** The warehouse sources the scan found, for a program that offers them. */
8172
+ async function scannedWarehouseSources(session) {
8173
+ const scan = getCodebaseScan(session);
8174
+ if (!scan) return [];
8175
+ return toWarehouseSources(scan.tools, await loadSourceDetectors(session.baseUrl));
8176
+ }
8177
+ function getCodebaseScan(session) {
8178
+ return session.frameworkContext[CODEBASE_SCAN_KEY];
8179
+ }
8180
+ //#endregion
9803
8181
  //#region src/programs/shared/package-scanning.ts
9804
8182
  const POSTHOG_SDKS = new Set([
9805
8183
  "posthog-js",
@@ -9840,22 +8218,6 @@ function findPackageJsons(installDir, maxDepth = 3) {
9840
8218
  return matches;
9841
8219
  }
9842
8220
  //#endregion
9843
- //#region src/programs/detection/ai-sdk-stamp.ts
9844
- function hasAiSdkEvidence(evidence) {
9845
- return evidence.warehouseSources.some((s) => AI_SOURCE_KINDS.has(s.kind)) || evidence.discoveredFeatures.includes("llm");
9846
- }
9847
- /**
9848
- * Boolean only, on the org, never the list of kinds or any non-AI tool: a
9849
- * decline must not leak even the shape of what local detection saw.
9850
- */
9851
- function stampAiSdkDetected(evidence) {
9852
- if (!evidence.mayReportScanResults) return;
9853
- const organizationId = evidence.apiUser?.organization?.id;
9854
- if (!organizationId) return;
9855
- if (!hasAiSdkEvidence(evidence)) return;
9856
- analytics.groupIdentify("organization", organizationId, { wizard_ai_sdk_detected: true });
9857
- }
9858
- //#endregion
9859
8221
  //#region src/programs/detection/integration.ts
9860
8222
  async function detectPostHogIntegration(ctx) {
9861
8223
  const session = ctx.session;
@@ -9882,7 +8244,7 @@ async function detectPostHogIntegration(ctx) {
9882
8244
  if (versionResult.supported !== true) ctx.setUnsupportedVersion(versionResult.supported);
9883
8245
  }
9884
8246
  for (const feature of discoverFeatures(installDir)) ctx.addDiscoveredFeature(feature);
9885
- detectWarehouseSourcesForSuggestion(ctx, installDir);
8247
+ await offerScannedWarehouseSources(ctx);
9886
8248
  detectExistingPostHog(ctx, installDir);
9887
8249
  ctx.setDetectionComplete();
9888
8250
  }
@@ -9894,40 +8256,35 @@ async function detectPostHogIntegration(ctx) {
9894
8256
  * scan below, but every program's intro screen resolves consent
9895
8257
  * through the same store method, so the reporter is reached on runs
9896
8258
  * where no scan ever happened.
9897
- * 'failed' the scan threw. A zero-count row would read as "scanned, found
9898
- * nothing", which is a different fact.
8259
+ * 'failed' the scan threw, or had no tool catalog to match against. A
8260
+ * zero-count row would read as "scanned, found nothing", which is
8261
+ * a different fact.
9899
8262
  * 'ok' the scan ran. Zero sources is a real result and gets reported.
9900
8263
  */
9901
8264
  const WAREHOUSE_SCAN_STATE_KEY = "warehouseScanState";
9902
8265
  /**
9903
- * Scan for data warehouse source signals (Postgres, Stripe, Hubspot, …) and,
9904
- * when found, stash them for a `wizard warehouse` suggestion on the outro.
9905
- * Runs unconditionally during `detect`, before the intro screen has asked
9906
- * about sharing. Reporting what this finds is a separate, later concern.
9907
- * See `reportWarehouseSourcesDetected` below.
8266
+ * Stash the run's scanned warehouse sources (Postgres, Stripe, Hubspot, …)
8267
+ * for a `wizard warehouse` suggestion on the outro. The scan itself runs
8268
+ * unconditionally before `onReady`, before the intro screen has asked about
8269
+ * sharing. Reporting what it found is a separate, later concern. See
8270
+ * `reportWarehouseSourcesDetected` below.
9908
8271
  *
9909
8272
  * Deliberately a suggestion, not an inline agent run: a second credential-
9910
8273
  * collecting agent run before the outro could end the run on any of its
9911
8274
  * failure paths and cost the user the success outro on a run where PostHog
9912
8275
  * installed fine.
9913
- *
9914
- * Calls `detectWarehouseSources` (a pure scan) rather than the warehouse
9915
- * program's `detectWarehousePrerequisites`, which writes a `detectError` when
9916
- * it finds nothing — that's right for `wizard warehouse` but here would show a
9917
- * spurious error and trip the CI prerequisites abort. No sources is a no-op.
9918
- *
9919
- * Best-effort: never let a scan failure break the wizard flow.
9920
8276
  */
9921
- function detectWarehouseSourcesForSuggestion(ctx, installDir) {
9922
- try {
9923
- const sources = detectWarehouseSources(installDir);
9924
- ctx.setFrameworkContext(WAREHOUSE_SCAN_STATE_KEY, "ok");
9925
- if (sources.length === 0) return;
9926
- ctx.setFrameworkContext(DETECTED_WAREHOUSE_SOURCES_KEY, sources);
9927
- } catch (error) {
8277
+ async function offerScannedWarehouseSources(ctx) {
8278
+ const scan = getCodebaseScan(ctx.session);
8279
+ if (!scan) return;
8280
+ if (scan.failed) {
9928
8281
  ctx.setFrameworkContext(WAREHOUSE_SCAN_STATE_KEY, "failed");
9929
- analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "detectWarehouseSourcesForSuggestion" });
8282
+ return;
9930
8283
  }
8284
+ ctx.setFrameworkContext(WAREHOUSE_SCAN_STATE_KEY, "ok");
8285
+ const sources = await scannedWarehouseSources(ctx.session);
8286
+ if (sources.length === 0) return;
8287
+ ctx.setFrameworkContext(DETECTED_WAREHOUSE_SOURCES_KEY, sources);
9931
8288
  }
9932
8289
  /**
9933
8290
  * The single place scan results become telemetry. Called from
@@ -9947,10 +8304,12 @@ function reportWarehouseSourcesDetected(session) {
9947
8304
  if (mayReportScanResults(session)) {
9948
8305
  const sources = getDetectedWarehouseSources(session);
9949
8306
  if (session.frameworkContext[WAREHOUSE_SCAN_STATE_KEY] !== "ok") return true;
8307
+ const discoveredFeatures = reportableDiscoveredFeatures(session);
9950
8308
  analytics.wizardCapture("warehouse sources detected", {
9951
8309
  warehouse_source_count: sources.length,
9952
8310
  warehouse_source_kinds: sources.map((s) => s.kind),
9953
- warehouse_source_modes: sources.map((s) => s.mode)
8311
+ warehouse_source_modes: sources.map((s) => s.mode),
8312
+ ...discoveredFeatures ? { discovered_features: discoveredFeatures } : {}
9954
8313
  });
9955
8314
  if (sources.length > 0) {
9956
8315
  analytics.setTag("warehouse_source_kinds", sources.map((s) => s.kind).join(","));
@@ -10196,14 +8555,25 @@ const config = {
10196
8555
  agentFlow: "integration-v2",
10197
8556
  handoffNotebook: true,
10198
8557
  eventPlanFile: EVENT_PLAN_FILE,
8558
+ scansCodebase: true,
10199
8559
  onReady: (ctx) => detectPostHogIntegration(ctx),
10200
8560
  disallowedTools: [WIZARD_TOOL_NAMES.wizardAsk],
10201
8561
  oauthScopeAdditions: [...WAREHOUSE_SOURCE_SCOPE_ADDITIONS],
10202
8562
  seedTasks: warehouseSeedTasks,
8563
+ decisions: [
8564
+ "framework",
8565
+ "is_monorepo",
8566
+ "product_category",
8567
+ "calls_llm_api",
8568
+ "structured_logging",
8569
+ "otel_exporter",
8570
+ "server_runtime",
8571
+ "observability_stack"
8572
+ ],
10203
8573
  excludedTaskTypes: (flags) => flags["wizard-default-aio-logs"] === "false" ? ["ai-observability", "logs"] : [],
10204
8574
  ciPreRun: async (session, runner) => {
10205
8575
  await scopeInstallDirToProject(session, runner);
10206
- const integration = await detectFramework(session.installDir);
8576
+ const integration = await detectFramework(session.installDir) ?? void 0;
10207
8577
  if (!integration) abortNoFrameworkDetected();
10208
8578
  session.integration = integration;
10209
8579
  analytics.setTag("integration", integration);
@@ -10231,7 +8601,7 @@ const config = {
10231
8601
  if (usesPackageJson) {
10232
8602
  const packageJson = await tryGetPackageJson({ installDir: session.installDir });
10233
8603
  if (packageJson) {
10234
- const { hasDeclaredDependency } = await import("./package-json-Dblip2_X.js");
8604
+ const { hasDeclaredDependency } = await import("./package-json-BAtJRHSq.js");
10235
8605
  if (!hasDeclaredDependency(config.detection.packageName, packageJson)) runner.log.warn(`${config.detection.packageDisplayName} does not seem to be installed. Continuing anyway — the agent will handle it.`);
10236
8606
  frameworkVersion = config.detection.getVersion(packageJson);
10237
8607
  } else runner.log.warn("Could not find package.json. Continuing anyway — the agent will handle it.");
@@ -10300,7 +8670,7 @@ ${warehouseReportInstruction(session)}
10300
8670
  postRun: async (sess, credentials) => {
10301
8671
  const envVars = config.environment.getEnvVars(credentials.projectApiKey, credentials.host.apiHost);
10302
8672
  if (config.environment.uploadToHosting) {
10303
- const { uploadEnvironmentVariablesStep } = await import("./upload-step-CFoxwd5t.js");
8673
+ const { uploadEnvironmentVariablesStep } = await import("./upload-step-DrYXnpzI.js");
10304
8674
  const uploadedEnvVars = await uploadEnvironmentVariablesStep(envVars, {
10305
8675
  integration: config.metadata.integration,
10306
8676
  installDir: sess.installDir,
@@ -10342,6 +8712,6 @@ ${warehouseReportInstruction(session)}
10342
8712
  }
10343
8713
  };
10344
8714
  //#endregion
10345
- export { formatCostUsd as A, FRAMEWORK_REGISTRY as C, runAgent as D, streamMcpPrompt as E, DEFAULT_BINDING as O, detectFramework as S, scanInstalledSkill as T, detectProjectsWithAgent as _, detectPostHogIntegration as a, noteDetectedFramework as b, POSTHOG_SDKS as c, DETECTED_WAREHOUSE_SOURCES_KEY as d, detectWarehouseSources as f, toIntegrationCandidates as g, scopeInstallDirToProject as h, ProgramAbort as i, formatTokenCount as j, computeTokenCostUsd as k, STRIPE_SDKS as l, detectIntegrationProjects as m, WAREHOUSE_SOURCE_SCOPE_ADDITIONS as n, reportWarehouseSourcesDetected as o, getDetectedWarehouseSources as p, abortNoFrameworkDetected as r, stampAiSdkDetected as s, config as t, findPackageJsons as u, resolveProjectDir as v, SPINNER_MESSAGE as w, gatherFrameworkContext as x, loadWizardFlags as y };
8715
+ export { streamMcpPrompt as A, loadWizardFlags as C, FRAMEWORK_REGISTRY as D, detectFramework as E, formatTokenCount as F, DEFAULT_BINDING as M, computeTokenCostUsd as N, SPINNER_MESSAGE as O, formatCostUsd as P, tagScanResults as S, gatherFrameworkContext as T, scopeInstallDirToProject as _, detectPostHogIntegration as a, resolveProjectDir as b, STRIPE_SDKS as c, scanCodebaseFor as d, scannedWarehouseSources as f, detectIntegrationProjects as g, workspaceFingerprint as h, ProgramAbort as i, runAgent as j, scanInstalledSkill as k, findPackageJsons as l, getDetectedWarehouseSources as m, WAREHOUSE_SOURCE_SCOPE_ADDITIONS as n, reportWarehouseSourcesDetected as o, DETECTED_WAREHOUSE_SOURCES_KEY as p, abortNoFrameworkDetected as r, POSTHOG_SDKS as s, config as t, getCodebaseScan as u, toIntegrationCandidates as v, noteDetectedFramework as w, applyScanTags as x, detectProjectsWithAgent as y };
10346
8716
 
10347
- //# sourceMappingURL=posthog-integration-DxRkw5DI.js.map
8717
+ //# sourceMappingURL=posthog-integration-mIQGBc1f.js.map