@posthog/wizard 2.76.1 → 2.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -15
- package/dist/README.md +16 -15
- package/dist/{add-mcp-server-to-clients-4yHaRvOo.js → add-mcp-server-to-clients-JMv8LP-O.js} +429 -114
- package/dist/add-mcp-server-to-clients-JMv8LP-O.js.map +1 -0
- package/dist/agent-bCZ3i3fh.js +3085 -0
- package/dist/agent-bCZ3i3fh.js.map +1 -0
- package/dist/{agent-interface-DPaPdIjS.js → agent-interface-BAOIHuXS.js} +452 -377
- package/dist/agent-interface-BAOIHuXS.js.map +1 -0
- package/dist/{agent-prompt-loader-BlhabNM2.js → agent-prompt-loader-7Pd4CfZO.js} +24 -8
- package/dist/agent-prompt-loader-7Pd4CfZO.js.map +1 -0
- package/dist/analytics-BcV-WCM4.js +2 -0
- package/dist/{analytics-DSVyjIVF.js → analytics-CHCZ_qUR.js} +10 -6
- package/dist/analytics-CHCZ_qUR.js.map +1 -0
- package/dist/{api-Ci0dZWLR.js → api-Diy_2fp8.js} +4 -17
- package/dist/api-Diy_2fp8.js.map +1 -0
- package/dist/bin.js +410 -68
- package/dist/bin.js.map +1 -1
- package/dist/{bounded-fs-FbZJ_vJ_.js → bounded-fs-Bx9zTe6m.js} +4 -4
- package/dist/bounded-fs-Bx9zTe6m.js.map +1 -0
- package/dist/check-screens.tsx +1 -1
- package/dist/{ci-install-CEVCqahf.js → ci-install-CDdQYsPe.js} +6 -5
- package/dist/ci-install-CDdQYsPe.js.map +1 -0
- package/dist/constants-jFI2SaE1.js +260 -0
- package/dist/constants-jFI2SaE1.js.map +1 -0
- package/dist/debug-Ch4BDOtD.js +171 -0
- package/dist/debug-Ch4BDOtD.js.map +1 -0
- package/dist/{debug-C6KWXbQi.js → debug-M2gV2iDq.js} +1 -1
- package/dist/{defaults-DulzMJpN.js → defaults-C6J7yL4O.js} +26 -3
- package/dist/defaults-C6J7yL4O.js.map +1 -0
- package/dist/endpoints-DCVbM06L.js +108 -0
- package/dist/endpoints-DCVbM06L.js.map +1 -0
- package/dist/{env-api-key-DwmkB3fm.js → env-api-key-Qx8NnYHm.js} +3 -3
- package/dist/env-api-key-Qx8NnYHm.js.map +1 -0
- package/dist/{environment-1wA7uwAS.js → environment-B07Bmxae.js} +6 -6
- package/dist/environment-B07Bmxae.js.map +1 -0
- package/dist/{errors--OqLxmWR.js → errors-CQ9kttJD.js} +30 -35
- package/dist/errors-CQ9kttJD.js.map +1 -0
- package/dist/{file-C3HGL1IM.js → file-BcLTjU16.js} +2 -2
- package/dist/{file-C3HGL1IM.js.map → file-BcLTjU16.js.map} +1 -1
- package/dist/{file-watcher-D8s0b-VT.js → file-watcher-DWc_bTs6.js} +2 -2
- package/dist/{file-watcher-D8s0b-VT.js.map → file-watcher-DWc_bTs6.js.map} +1 -1
- package/dist/{gateway-session-OqzCMpZ9.js → gateway-CjoShdLa.js} +175 -20
- package/dist/gateway-CjoShdLa.js.map +1 -0
- package/dist/generate-version.cjs +1 -1
- package/dist/{headless-ui-DRGWDg9Z.js → headless-ui-BFFmYC4v.js} +13 -2
- package/dist/headless-ui-BFFmYC4v.js.map +1 -0
- package/dist/{interactive-B6OlvhSf.js → interactive-D6zV7brJ.js} +2 -2
- package/dist/{interactive-B6OlvhSf.js.map → interactive-D6zV7brJ.js.map} +1 -1
- package/dist/{local-dev-DPZiIPCK.js → local-dev-McsurOC-.js} +2 -2
- package/dist/local-dev-McsurOC-.js.map +1 -0
- package/dist/logging-ui-_TwoMNbG.js +226 -0
- package/dist/logging-ui-_TwoMNbG.js.map +1 -0
- package/dist/mcp-DAgaoGk5.js +97 -0
- package/dist/mcp-DAgaoGk5.js.map +1 -0
- package/dist/{mcp-prompt-streaming-E1d8DJcX.js → mcp-prompt-streaming-I2U_X7BC.js} +7 -6
- package/dist/mcp-prompt-streaming-I2U_X7BC.js.map +1 -0
- package/dist/{mint-failure-C82j0RMC.js → mint-failure-D4nd2XmZ.js} +134 -48
- package/dist/mint-failure-D4nd2XmZ.js.map +1 -0
- package/dist/models-DDdBJKwT.js +83 -0
- package/dist/models-DDdBJKwT.js.map +1 -0
- package/dist/{non-interactive-DGT2H_n4.js → non-interactive-Dzow7YKY.js} +3 -3
- package/dist/non-interactive-Dzow7YKY.js.map +1 -0
- package/dist/oauth-session-DJ8h6GFa.js +74 -0
- package/dist/oauth-session-DJ8h6GFa.js.map +1 -0
- package/dist/{orchestrator-tools-BBBNV1z5.js → orchestrator-tools-szcdscwn.js} +5 -5
- package/dist/orchestrator-tools-szcdscwn.js.map +1 -0
- package/dist/{package-json-xt3eeyBI.js → package-json-DY70kkNp.js} +2 -2
- package/dist/package-json-DY70kkNp.js.map +1 -0
- package/dist/{pi-fG5fT50Y.js → pi-D3h5Ok0i.js} +329 -65
- package/dist/pi-D3h5Ok0i.js.map +1 -0
- package/dist/{playground-UFSHP-Yn.js → playground-5AB4WgNF.js} +18 -10
- package/dist/playground-5AB4WgNF.js.map +1 -0
- package/dist/{posthog-O9kKcnda.js → posthog-DNn574nW.js} +8 -3
- package/dist/posthog-DNn574nW.js.map +1 -0
- package/dist/{provisioning-B-tzjG4r.js → provisioning-B8uSqRZk.js} +6 -5
- package/dist/provisioning-B8uSqRZk.js.map +1 -0
- package/dist/{queue-tools-7Oso-fpt.js → queue-tools-9OOVx5TE.js} +11 -86
- package/dist/queue-tools-9OOVx5TE.js.map +1 -0
- package/dist/{registry-D1L5e-WQ.js → registry-8YG-kuXk.js} +265 -6
- package/dist/registry-8YG-kuXk.js.map +1 -0
- package/dist/run-agent-legacy-d7rWlzQR.js +3739 -0
- package/dist/run-agent-legacy-d7rWlzQR.js.map +1 -0
- package/dist/{security-n3-xdINN.js → security-Cg_4a7vB.js} +6 -6
- package/dist/security-Cg_4a7vB.js.map +1 -0
- package/dist/{setup-utils-Bj0gejEf.js → setup-utils-CmTCTPkX.js} +46 -18
- package/dist/setup-utils-CmTCTPkX.js.map +1 -0
- package/dist/smoke-test.sh +1 -1
- package/dist/{start-tui-BowrTPXm.js → start-tui-CB2PZs1x.js} +42 -48
- package/dist/start-tui-CB2PZs1x.js.map +1 -0
- package/dist/{steps-CpNTGu9N.js → steps-BZcXRJNY.js} +7 -7
- package/dist/{steps-CpNTGu9N.js.map → steps-BZcXRJNY.js.map} +1 -1
- package/dist/{store-CVkJKlny.js → store-UN7c-ASf.js} +18 -55
- package/dist/store-UN7c-ASf.js.map +1 -0
- package/dist/{subagent-SxmvfDfp.js → subagent-BofIDK-O.js} +8 -3
- package/dist/subagent-BofIDK-O.js.map +1 -0
- package/dist/{task-nF7Ry8ff.js → task-r9t1UhQP.js} +124 -36
- package/dist/task-r9t1UhQP.js.map +1 -0
- package/dist/{task-stream-C9BLiAhz.js → task-stream-CMUilu54.js} +45 -15
- package/dist/task-stream-CMUilu54.js.map +1 -0
- package/dist/{tasks-BEEVIBcT.js → tasks-Bbv_lOBG.js} +17 -13
- package/dist/tasks-Bbv_lOBG.js.map +1 -0
- package/dist/{telemetry-PD7bhcda.js → telemetry-D6ruCtnv.js} +5 -5
- package/dist/telemetry-D6ruCtnv.js.map +1 -0
- package/dist/{terminal-NhNl-BIA.js → terminal-DrGyci_a.js} +63 -94
- package/dist/terminal-DrGyci_a.js.map +1 -0
- package/dist/{tools-BBdpgcIf.js → tools-Dyfh9aCD.js} +11 -11
- package/dist/tools-Dyfh9aCD.js.map +1 -0
- package/dist/ui-BBVuTM1v.js +15 -0
- package/dist/ui-BBVuTM1v.js.map +1 -0
- package/dist/version-aXxpgRKx.js +6 -0
- package/dist/version-aXxpgRKx.js.map +1 -0
- package/dist/warlock-smoke-test.ts +2 -2
- package/dist/{wizard-abort-hqHF-9xD.js → wizard-abort-BBkiUxfS.js} +23 -20
- package/dist/wizard-abort-BBkiUxfS.js.map +1 -0
- package/dist/wizard-abort-BU4aLyvh.js +3 -0
- package/dist/{wizard-session-FSJCkTcP.js → wizard-session-2w4Ru-Wz.js} +12 -16
- package/dist/wizard-session-2w4Ru-Wz.js.map +1 -0
- package/dist/wizard-session-DGaUHOF3.js +2 -0
- package/dist/wizard-ui-DoyOZxK8.js +16 -0
- package/dist/wizard-ui-DoyOZxK8.js.map +1 -0
- package/dist/{yara-hooks-BaPKsm-B.js → yara-hooks-DCeMDd1o.js} +19 -68
- package/dist/yara-hooks-DCeMDd1o.js.map +1 -0
- package/dist/yara-hooks-DSGuCU9A.js +2 -0
- package/package.json +8 -1
- package/dist/add-mcp-server-to-clients-4yHaRvOo.js.map +0 -1
- package/dist/agent-interface-DPaPdIjS.js.map +0 -1
- package/dist/agent-prompt-loader-BlhabNM2.js.map +0 -1
- package/dist/agent-runner-JFwQQj2k.js +0 -6100
- package/dist/agent-runner-JFwQQj2k.js.map +0 -1
- package/dist/analytics-CcHoyhE_.js +0 -2
- package/dist/analytics-DSVyjIVF.js.map +0 -1
- package/dist/api-Ci0dZWLR.js.map +0 -1
- package/dist/bounded-fs-FbZJ_vJ_.js.map +0 -1
- package/dist/ci-install-CEVCqahf.js.map +0 -1
- package/dist/debug-DWYDVp0B.js +0 -750
- package/dist/debug-DWYDVp0B.js.map +0 -1
- package/dist/defaults-DulzMJpN.js.map +0 -1
- package/dist/env-api-key-DwmkB3fm.js.map +0 -1
- package/dist/environment-1wA7uwAS.js.map +0 -1
- package/dist/errors--OqLxmWR.js.map +0 -1
- package/dist/gateway-session-OqzCMpZ9.js.map +0 -1
- package/dist/gateway-session-s1xr8b48.js +0 -2
- package/dist/headless-ui-DRGWDg9Z.js.map +0 -1
- package/dist/local-dev-DPZiIPCK.js.map +0 -1
- package/dist/mcp-BprhJJ2c.js +0 -64
- package/dist/mcp-BprhJJ2c.js.map +0 -1
- package/dist/mcp-prompt-streaming-E1d8DJcX.js.map +0 -1
- package/dist/mint-failure-C82j0RMC.js.map +0 -1
- package/dist/non-interactive-DGT2H_n4.js.map +0 -1
- package/dist/orchestrator-tools-BBBNV1z5.js.map +0 -1
- package/dist/package-json-xt3eeyBI.js.map +0 -1
- package/dist/package-manager-DU3aVif4.js +0 -290
- package/dist/package-manager-DU3aVif4.js.map +0 -1
- package/dist/pi-fG5fT50Y.js.map +0 -1
- package/dist/playground-UFSHP-Yn.js.map +0 -1
- package/dist/posthog-O9kKcnda.js.map +0 -1
- package/dist/provisioning-B-tzjG4r.js.map +0 -1
- package/dist/queue-tools-7Oso-fpt.js.map +0 -1
- package/dist/registry-D1L5e-WQ.js.map +0 -1
- package/dist/security-n3-xdINN.js.map +0 -1
- package/dist/setup-utils-Bj0gejEf.js.map +0 -1
- package/dist/start-tui-BowrTPXm.js.map +0 -1
- package/dist/store-CVkJKlny.js.map +0 -1
- package/dist/subagent-SxmvfDfp.js.map +0 -1
- package/dist/task-nF7Ry8ff.js.map +0 -1
- package/dist/task-stream-C9BLiAhz.js.map +0 -1
- package/dist/tasks-BEEVIBcT.js.map +0 -1
- package/dist/telemetry-PD7bhcda.js.map +0 -1
- package/dist/terminal-NhNl-BIA.js.map +0 -1
- package/dist/tools-BBdpgcIf.js.map +0 -1
- package/dist/wizard-abort-D3YA1hXX.js +0 -2
- package/dist/wizard-abort-hqHF-9xD.js.map +0 -1
- package/dist/wizard-session-CYvyYnti.js +0 -2
- package/dist/wizard-session-FSJCkTcP.js.map +0 -1
- package/dist/yara-hooks-BaPKsm-B.js.map +0 -1
- package/dist/yara-hooks-CwfQuEl3.js +0 -2
|
@@ -0,0 +1,3739 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-B_-DWIq7.js";
|
|
2
|
+
import { a as AgentSignals, d as ErrorCodes } from "./errors-CQ9kttJD.js";
|
|
3
|
+
import { i as enableDebugLogs, o as initLogFile, s as logToFile } from "./debug-Ch4BDOtD.js";
|
|
4
|
+
import { $ as getSkillsBaseUrl, G as WIZARD_ORCHESTRATOR_FLAG_KEY, W as WIZARD_OAUTH_SCOPES, Z as WIZARD_SELF_DRIVING_USE_PI_HARNESS_FLAG_KEY, b as HAIKU_MODEL, n as AGENTIC_DETECTION_RETRY_TIMEOUT_MS, t as AGENTIC_DETECTION_FIRST_ATTEMPT_TIMEOUT_MS, u as DETECTION_TIMEOUT_MS, w as Integration } from "./constants-jFI2SaE1.js";
|
|
5
|
+
import { r as mayReportScanResults } from "./wizard-session-2w4Ru-Wz.js";
|
|
6
|
+
import { n as analytics, r as groupsFromUser } from "./analytics-CHCZ_qUR.js";
|
|
7
|
+
import { a as safeReadFile, i as readProjectFile, o as walkProjectFiles } from "./bounded-fs-Bx9zTe6m.js";
|
|
8
|
+
import { c as refreshAccessToken, l as OAuthError, p as detectNodePackageManagers, s as missingOAuthScopes, t as getOrAskForProjectData } from "./setup-utils-CmTCTPkX.js";
|
|
9
|
+
import { a as getBlockingServiceKeys, i as evaluateWizardReadiness, n as SERVICE_LABELS, r as SIGNUP_WIZARD_READINESS_CONFIG } from "./logging-ui-_TwoMNbG.js";
|
|
10
|
+
import { t as getUI } from "./ui-BBVuTM1v.js";
|
|
11
|
+
import { i as wizardAbort, t as registerCleanup } from "./wizard-abort-BBkiUxfS.js";
|
|
12
|
+
import { n as isNonInteractiveEnvironment } from "./environment-B07Bmxae.js";
|
|
13
|
+
import { i as oauthCredentials, t as configureOAuthSession } from "./oauth-session-DJ8h6GFa.js";
|
|
14
|
+
import { at as MAX_ENV_KEY_SET, c as checkAllSettingsConflicts, ct as toPromptSafeRelativePath, d as restoreClaudeSettings, h as markGrantRevoked, i as runAgent, l as classifySettingsConflicts, n as buildRunTags, ot as isEnvFileName, r as initializeAgent, s as backupAndFixClaudeSettings, st as parseEnvKeyNames } from "./agent-interface-BAOIHuXS.js";
|
|
15
|
+
import { n as runAgent$1, r as resolveBinding } from "./agent-bCZ3i3fh.js";
|
|
16
|
+
import { _ as coerceAuditChecks, a as flushScanReport } from "./yara-hooks-DCeMDd1o.js";
|
|
17
|
+
import { g as TASK_OUTCOMES_KEY } from "./queue-tools-9OOVx5TE.js";
|
|
18
|
+
import { t as FRAMEWORK_REGISTRY } from "./registry-8YG-kuXk.js";
|
|
19
|
+
import { t as startFileWatcher } from "./file-watcher-DWc_bTs6.js";
|
|
20
|
+
import * as semver from "semver";
|
|
21
|
+
import fs, { existsSync, statSync } from "fs";
|
|
22
|
+
import path, { isAbsolute, join, relative, resolve, sep } from "path";
|
|
23
|
+
//#region src/ui/agent-progress.ts
|
|
24
|
+
/**
|
|
25
|
+
* The inverse of the agent's former `getUI()` calls: one event, one
|
|
26
|
+
* `WizardUI` method, synchronous, in emission order. Because each case maps
|
|
27
|
+
* back to exactly the call the agent used to make, the frame and flow goldens
|
|
28
|
+
* hold without regeneration.
|
|
29
|
+
*/
|
|
30
|
+
function createUiReducer(ui) {
|
|
31
|
+
let spinner;
|
|
32
|
+
return (event) => {
|
|
33
|
+
switch (event.kind) {
|
|
34
|
+
case "lifecycle":
|
|
35
|
+
if (event.phase === "started") ui.startRun();
|
|
36
|
+
else ui.outro(event.message);
|
|
37
|
+
break;
|
|
38
|
+
case "spinner":
|
|
39
|
+
(spinner ??= ui.spinner())[event.action](event.message);
|
|
40
|
+
break;
|
|
41
|
+
case "log":
|
|
42
|
+
ui.log[event.level](event.message);
|
|
43
|
+
break;
|
|
44
|
+
case "status":
|
|
45
|
+
ui.pushStatus(event.message);
|
|
46
|
+
break;
|
|
47
|
+
case "tasks":
|
|
48
|
+
ui.syncTodos(event.tasks);
|
|
49
|
+
break;
|
|
50
|
+
case "stage":
|
|
51
|
+
ui.setStage(event.stage);
|
|
52
|
+
break;
|
|
53
|
+
case "url":
|
|
54
|
+
if (event.which === "dashboard") ui.setDashboardUrl(event.url);
|
|
55
|
+
else ui.setNotebookUrl(event.url);
|
|
56
|
+
break;
|
|
57
|
+
case "usage":
|
|
58
|
+
ui.addTokenUsage(event.delta);
|
|
59
|
+
break;
|
|
60
|
+
case "finalCost":
|
|
61
|
+
ui.setFinalTokenCostUsd(event.usd);
|
|
62
|
+
break;
|
|
63
|
+
case "authError":
|
|
64
|
+
ui.showAuthError(event.detail);
|
|
65
|
+
break;
|
|
66
|
+
case "handoff":
|
|
67
|
+
ui.setHandoffText(event.text);
|
|
68
|
+
break;
|
|
69
|
+
case "completion":
|
|
70
|
+
ui.setOutroData(event.outro);
|
|
71
|
+
break;
|
|
72
|
+
default: throw new Error(`Unhandled agent progress: ${JSON.stringify(event)}`);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** The agent's questions, answered wherever `getUI()` answers them today. */
|
|
77
|
+
function uiInteraction(ui) {
|
|
78
|
+
return {
|
|
79
|
+
ask: (question, { signal }) => dismissOnAbort(ui.requestQuestion(question), signal, () => ui.cancelPendingQuestion()),
|
|
80
|
+
taskNotice: (notice, { signal }) => dismissOnAbort(ui.showTaskNotice(notice), signal, () => ui.cancelTaskNotice())
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Dismiss one open request on abort; a settled one leaves the UI alone. A
|
|
85
|
+
* throw inside an abort listener reaches no caller: Node rethrows it as an
|
|
86
|
+
* uncaught exception, so a broken overlay is logged here instead.
|
|
87
|
+
*/
|
|
88
|
+
function dismissOnAbort(open, signal, dismiss) {
|
|
89
|
+
const onAbort = () => {
|
|
90
|
+
try {
|
|
91
|
+
dismiss();
|
|
92
|
+
} catch (error) {
|
|
93
|
+
logToFile("[agent-progress] dismissing an aborted request failed", error);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
if (signal.aborted) onAbort();
|
|
97
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
98
|
+
return open.finally(() => signal.removeEventListener("abort", onAbort));
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/lib/programs/program-step.ts
|
|
102
|
+
/**
|
|
103
|
+
* Project program steps into the narrower Screen shape the router consumes.
|
|
104
|
+
*
|
|
105
|
+
* Two things happen here:
|
|
106
|
+
* 1. Headless steps (no `screenId`) are filtered out. The router walks
|
|
107
|
+
* visible screens; gate-only steps like `detect` are store concerns.
|
|
108
|
+
* 2. The step is narrowed to just { id, show, isComplete } — the
|
|
109
|
+
* router has no business touching gate, onInit, or label.
|
|
110
|
+
*
|
|
111
|
+
* This intentional separation keeps the router focused on one question:
|
|
112
|
+
* "Which screen should be rendered right now?"
|
|
113
|
+
*/
|
|
114
|
+
/**
|
|
115
|
+
* The gated steps the agent runner awaits after `auth` and before `run`, in
|
|
116
|
+
* step order. Empty when a program has no auth step or runs before it.
|
|
117
|
+
*/
|
|
118
|
+
function postAuthGateSteps(steps) {
|
|
119
|
+
const authIndex = steps.findIndex((s) => s.screenId === "auth");
|
|
120
|
+
const runIndex = steps.findIndex((s) => s.screenId === "run");
|
|
121
|
+
if (authIndex === -1 || runIndex <= authIndex) return [];
|
|
122
|
+
return steps.slice(authIndex + 1, runIndex).filter((s) => s.gate);
|
|
123
|
+
}
|
|
124
|
+
function createProgramSequence(steps) {
|
|
125
|
+
const entries = steps.filter((step) => step.screenId != null).map((step) => ({
|
|
126
|
+
id: step.screenId,
|
|
127
|
+
show: step.show,
|
|
128
|
+
isComplete: step.isComplete ?? step.gate
|
|
129
|
+
}));
|
|
130
|
+
entries.push({
|
|
131
|
+
id: "exit",
|
|
132
|
+
show: void 0,
|
|
133
|
+
isComplete: void 0
|
|
134
|
+
});
|
|
135
|
+
return entries;
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/lib/programs/authenticate.ts
|
|
139
|
+
async function authenticate(session, programId) {
|
|
140
|
+
if (session.credentials) return;
|
|
141
|
+
logToFile("[agent-runner] starting OAuth");
|
|
142
|
+
const { projectApiKey, host, accessToken, refreshToken, expiresAt, oauthClientId, projectId, roleAtOrganization, user, project, missingScopes } = await getOrAskForProjectData({
|
|
143
|
+
signup: session.signup,
|
|
144
|
+
ci: session.ci,
|
|
145
|
+
apiKey: session.apiKey,
|
|
146
|
+
projectId: session.projectId,
|
|
147
|
+
email: session.email,
|
|
148
|
+
region: session.region,
|
|
149
|
+
baseUrl: session.baseUrl,
|
|
150
|
+
localMcp: session.localMcp,
|
|
151
|
+
programId
|
|
152
|
+
});
|
|
153
|
+
session.credentials = {
|
|
154
|
+
accessToken,
|
|
155
|
+
refreshToken,
|
|
156
|
+
expiresAt,
|
|
157
|
+
oauthClientId,
|
|
158
|
+
projectApiKey,
|
|
159
|
+
host,
|
|
160
|
+
projectId,
|
|
161
|
+
missingScopes
|
|
162
|
+
};
|
|
163
|
+
session.apiProject = project;
|
|
164
|
+
session.roleAtOrganization = roleAtOrganization;
|
|
165
|
+
session.apiUser = user;
|
|
166
|
+
getUI().setCredentials(session.credentials);
|
|
167
|
+
getUI().setRoleAtOrganization(roleAtOrganization);
|
|
168
|
+
getUI().setApiUser(user);
|
|
169
|
+
if (user) analytics.identifyUser(user);
|
|
170
|
+
analytics.setGroups(groupsFromUser(user, host.apiHost));
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Grants the token endpoint refuses permanently. A dead grant means the login
|
|
174
|
+
* is gone, not that the network blipped, so only these mark the session.
|
|
175
|
+
*/
|
|
176
|
+
const DEAD_GRANT_CODES = new Set(["invalid_grant", "invalid_client"]);
|
|
177
|
+
async function refreshAccessTokenIfNeeded(session) {
|
|
178
|
+
if (!session.credentials) return;
|
|
179
|
+
configureOAuthSession(session.credentials, {
|
|
180
|
+
rotate: (credentials) => rotateCredentials(credentials, session.baseUrl),
|
|
181
|
+
onRefreshed: (refreshed) => {
|
|
182
|
+
session.credentials = refreshed;
|
|
183
|
+
getUI().setAccessToken(refreshed);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
await oauthCredentials();
|
|
187
|
+
}
|
|
188
|
+
async function rotateCredentials(credentials, baseUrl) {
|
|
189
|
+
if (!credentials.refreshToken) return credentials;
|
|
190
|
+
try {
|
|
191
|
+
const token = await refreshAccessToken(credentials.refreshToken, baseUrl, credentials.oauthClientId);
|
|
192
|
+
return {
|
|
193
|
+
...credentials,
|
|
194
|
+
accessToken: token.access_token,
|
|
195
|
+
missingScopes: missingOAuthScopes([...WIZARD_OAUTH_SCOPES, ...credentials.missingScopes ?? []], token.scope),
|
|
196
|
+
refreshToken: token.refresh_token ?? credentials.refreshToken,
|
|
197
|
+
expiresAt: Date.now() + token.expires_in * 1e3
|
|
198
|
+
};
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (error instanceof OAuthError && DEAD_GRANT_CODES.has(error.code)) {
|
|
201
|
+
markGrantRevoked();
|
|
202
|
+
analytics.wizardCapture("auth session expired", { reason: error.code });
|
|
203
|
+
}
|
|
204
|
+
logToFile("[oauth] token refresh failed, continuing with the existing token:", error instanceof Error ? error.message : error);
|
|
205
|
+
return credentials;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/lib/detection/framework.ts
|
|
210
|
+
/**
|
|
211
|
+
* Framework detection — identify which PostHog-supported framework
|
|
212
|
+
* is present in the project directory.
|
|
213
|
+
*
|
|
214
|
+
* Pure function: takes an install dir, returns the detected integration
|
|
215
|
+
* (or undefined). No store mutations, no UI calls.
|
|
216
|
+
*/
|
|
217
|
+
/**
|
|
218
|
+
* Loop through all registered frameworks and return the first one
|
|
219
|
+
* whose `detect()` predicate matches the given directory.
|
|
220
|
+
* Returns undefined if no framework is detected or detection times out.
|
|
221
|
+
*/
|
|
222
|
+
async function detectFramework(installDir) {
|
|
223
|
+
for (const integration of Object.values(Integration)) {
|
|
224
|
+
const config = FRAMEWORK_REGISTRY[integration];
|
|
225
|
+
try {
|
|
226
|
+
if (await Promise.race([config.detection.detect({ installDir }), new Promise((resolve) => setTimeout(() => resolve(false), 1e4))])) return integration;
|
|
227
|
+
} catch {}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region src/lib/detection/features.ts
|
|
232
|
+
/**
|
|
233
|
+
* Feature discovery — scan project dependencies for known SDK patterns
|
|
234
|
+
* that indicate additional PostHog programs are relevant.
|
|
235
|
+
*
|
|
236
|
+
* Pure function: takes an install dir, returns a set of discovered features.
|
|
237
|
+
* No store mutations, no UI calls.
|
|
238
|
+
*/
|
|
239
|
+
const STRIPE_PACKAGES = new Set(["stripe", "@stripe/stripe-js"]);
|
|
240
|
+
const LLM_PACKAGES = new Set([
|
|
241
|
+
"openai",
|
|
242
|
+
"@anthropic-ai/sdk",
|
|
243
|
+
"ai",
|
|
244
|
+
"@ai-sdk/openai",
|
|
245
|
+
"langchain",
|
|
246
|
+
"@langchain/openai",
|
|
247
|
+
"@langchain/langgraph",
|
|
248
|
+
"@google/generative-ai",
|
|
249
|
+
"@google/genai",
|
|
250
|
+
"@instructor-ai/instructor",
|
|
251
|
+
"@mastra/core",
|
|
252
|
+
"portkey-ai"
|
|
253
|
+
]);
|
|
254
|
+
const PYTHON_LLM_PACKAGES = new Set([
|
|
255
|
+
"openai",
|
|
256
|
+
"anthropic",
|
|
257
|
+
"langchain",
|
|
258
|
+
"langchain-openai",
|
|
259
|
+
"langchain-anthropic",
|
|
260
|
+
"langchain-google-genai",
|
|
261
|
+
"langgraph",
|
|
262
|
+
"litellm",
|
|
263
|
+
"llama-index",
|
|
264
|
+
"pydantic-ai",
|
|
265
|
+
"crewai",
|
|
266
|
+
"instructor",
|
|
267
|
+
"dspy-ai",
|
|
268
|
+
"mistralai",
|
|
269
|
+
"cohere",
|
|
270
|
+
"google-generativeai",
|
|
271
|
+
"google-genai",
|
|
272
|
+
"portkey-ai"
|
|
273
|
+
]);
|
|
274
|
+
function discoverFeatures(installDir) {
|
|
275
|
+
const features = [];
|
|
276
|
+
discoverNodeFeatures(installDir, features);
|
|
277
|
+
discoverPythonFeatures(installDir, features);
|
|
278
|
+
return features;
|
|
279
|
+
}
|
|
280
|
+
function discoverNodeFeatures(installDir, features) {
|
|
281
|
+
const packageJsonText = safeRead(installDir, "package.json");
|
|
282
|
+
if (!packageJsonText) return;
|
|
283
|
+
let packageJson;
|
|
284
|
+
try {
|
|
285
|
+
packageJson = JSON.parse(packageJsonText);
|
|
286
|
+
} catch {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const depNames = Object.keys({
|
|
290
|
+
...packageJson.dependencies,
|
|
291
|
+
...packageJson.devDependencies
|
|
292
|
+
});
|
|
293
|
+
if (depNames.some((depName) => STRIPE_PACKAGES.has(depName))) features.push("stripe");
|
|
294
|
+
if (depNames.some((depName) => LLM_PACKAGES.has(depName))) features.push("llm");
|
|
295
|
+
}
|
|
296
|
+
function discoverPythonFeatures(installDir, features) {
|
|
297
|
+
if (features.includes("llm")) return;
|
|
298
|
+
const depNames = [];
|
|
299
|
+
const requirementsTxt = safeRead(installDir, "requirements.txt");
|
|
300
|
+
if (requirementsTxt) depNames.push(...parseRequirementsTxt(requirementsTxt));
|
|
301
|
+
const pyprojectToml = safeRead(installDir, "pyproject.toml");
|
|
302
|
+
if (pyprojectToml) depNames.push(...parsePyprojectToml(pyprojectToml));
|
|
303
|
+
const pipfile = safeRead(installDir, "Pipfile");
|
|
304
|
+
if (pipfile) depNames.push(...parsePipfile(pipfile));
|
|
305
|
+
if (depNames.some((depName) => PYTHON_LLM_PACKAGES.has(depName))) features.push("llm");
|
|
306
|
+
}
|
|
307
|
+
function safeRead(installDir, file) {
|
|
308
|
+
return readProjectFile(join(installDir, file));
|
|
309
|
+
}
|
|
310
|
+
function normalizePyName(name) {
|
|
311
|
+
return name.toLowerCase().replace(/_/g, "-");
|
|
312
|
+
}
|
|
313
|
+
function parseRequirementsTxt(content) {
|
|
314
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("-")).map((line) => line.replace(/\[[^\]]*\]/, "")).map((line) => line.split(/[<>=!~;\s]/)[0]).filter(Boolean).map(normalizePyName);
|
|
315
|
+
}
|
|
316
|
+
function parsePyprojectToml(content) {
|
|
317
|
+
const depNames = [];
|
|
318
|
+
for (const arrayMatch of content.matchAll(/dependencies\s*=\s*\[([^\]]*)\]/g)) {
|
|
319
|
+
const arrayBody = arrayMatch[1];
|
|
320
|
+
for (const quotedMatch of arrayBody.matchAll(/["']([^"']+)["']/g)) {
|
|
321
|
+
const name = quotedMatch[1].replace(/\[[^\]]*\]/, "").split(/[<>=!~;\s]/)[0];
|
|
322
|
+
if (name) depNames.push(normalizePyName(name));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
for (const sectionMatch of content.matchAll(/\[tool\.poetry\.(?:dev-dependencies|dependencies|group\.[^.\]]+\.dependencies)\]([\s\S]*?)(?=\n\[|$)/g)) {
|
|
326
|
+
const sectionBody = sectionMatch[1];
|
|
327
|
+
depNames.push(...extractTomlSectionKeys(sectionBody, { skip: ["python"] }));
|
|
328
|
+
}
|
|
329
|
+
return depNames;
|
|
330
|
+
}
|
|
331
|
+
function parsePipfile(content) {
|
|
332
|
+
const depNames = [];
|
|
333
|
+
for (const sectionMatch of content.matchAll(/\[(packages|dev-packages)\]([\s\S]*?)(?=\n\[|$)/g)) {
|
|
334
|
+
const sectionBody = sectionMatch[2];
|
|
335
|
+
depNames.push(...extractTomlSectionKeys(sectionBody));
|
|
336
|
+
}
|
|
337
|
+
return depNames;
|
|
338
|
+
}
|
|
339
|
+
function extractTomlSectionKeys(sectionBody, opts = {}) {
|
|
340
|
+
const skipNames = new Set(opts.skip ?? []);
|
|
341
|
+
const depNames = [];
|
|
342
|
+
for (const line of sectionBody.split("\n")) {
|
|
343
|
+
const trimmed = line.trim();
|
|
344
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
345
|
+
const equalsIndex = trimmed.indexOf("=");
|
|
346
|
+
if (equalsIndex <= 0) continue;
|
|
347
|
+
const name = trimmed.slice(0, equalsIndex).trim();
|
|
348
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(name)) continue;
|
|
349
|
+
if (skipNames.has(name.toLowerCase())) continue;
|
|
350
|
+
depNames.push(normalizePyName(name));
|
|
351
|
+
}
|
|
352
|
+
return depNames;
|
|
353
|
+
}
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/lib/detection/context.ts
|
|
356
|
+
/**
|
|
357
|
+
* Framework context gathering — run gatherContext and version checks
|
|
358
|
+
* for a detected framework.
|
|
359
|
+
*
|
|
360
|
+
* Pure functions: take a framework config and options, return results.
|
|
361
|
+
* No store mutations, no UI calls.
|
|
362
|
+
*/
|
|
363
|
+
/**
|
|
364
|
+
* Run a framework's `gatherContext()` to collect variant-specific
|
|
365
|
+
* metadata (e.g., router type for Next.js, Expo vs bare for React Native).
|
|
366
|
+
*
|
|
367
|
+
* Returns the gathered context, or an empty object on failure/timeout.
|
|
368
|
+
*/
|
|
369
|
+
async function gatherFrameworkContext(config, options) {
|
|
370
|
+
if (!config.metadata.gatherContext) return {};
|
|
371
|
+
try {
|
|
372
|
+
return await Promise.race([config.metadata.gatherContext(options), new Promise((resolve) => setTimeout(() => resolve({}), DETECTION_TIMEOUT_MS))]);
|
|
373
|
+
} catch {
|
|
374
|
+
return {};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Check whether the installed framework version meets the minimum requirement.
|
|
379
|
+
*
|
|
380
|
+
* Returns `{ supported: true }` if the version is fine (or no check is needed).
|
|
381
|
+
* Returns the version details if unsupported.
|
|
382
|
+
*/
|
|
383
|
+
async function checkFrameworkVersion(config, options) {
|
|
384
|
+
if (!config.detection.minimumVersion || !config.detection.getInstalledVersion) return { supported: true };
|
|
385
|
+
const version = await config.detection.getInstalledVersion(options);
|
|
386
|
+
if (!version) return { supported: true };
|
|
387
|
+
const coerced = semver.coerce(version);
|
|
388
|
+
if (coerced && semver.lt(coerced, config.detection.minimumVersion)) return { supported: {
|
|
389
|
+
current: version,
|
|
390
|
+
minimum: config.detection.minimumVersion,
|
|
391
|
+
docsUrl: config.metadata.unsupportedVersionDocsUrl ?? config.metadata.docsUrl
|
|
392
|
+
} };
|
|
393
|
+
return { supported: true };
|
|
394
|
+
}
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/lib/detection/agentic.ts
|
|
397
|
+
/**
|
|
398
|
+
* Agentic project detection.
|
|
399
|
+
*
|
|
400
|
+
* A reusable detection tool that drives a Haiku agent over the repo: it finds
|
|
401
|
+
* project roots, resolves monorepo/workspace markers, reads package manifests,
|
|
402
|
+
* classifies each project against a caller-supplied set of targets, and reports
|
|
403
|
+
* which projects already have a PostHog SDK installed.
|
|
404
|
+
*
|
|
405
|
+
* Product-knowledge-free by design — the caller passes the targets to classify
|
|
406
|
+
* into (e.g. source-map skill variants) and maps the result back. Sits next to
|
|
407
|
+
* the other detection tools (framework, features, package-manager) so it's
|
|
408
|
+
* discoverable. Runs AFTER auth: it uses the same agent loop every program
|
|
409
|
+
* uses, which needs credentials.
|
|
410
|
+
*/
|
|
411
|
+
var AgenticDetectionTimeoutError = class extends Error {
|
|
412
|
+
constructor(attempt, timeoutMs) {
|
|
413
|
+
super(`Project scan attempt ${attempt} timed out after ${timeoutMs / 1e3}s`);
|
|
414
|
+
this.name = "AgenticDetectionTimeoutError";
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
/**
|
|
418
|
+
* Every project-manifest / workspace-marker filename the wizard's frameworks
|
|
419
|
+
* are identified by. One brace-expansion Glob over all of these locates every
|
|
420
|
+
* project root in the repo in a single call, regardless of language.
|
|
421
|
+
*
|
|
422
|
+
* Counterpart: POSTHOG_MANIFESTS in @lib/programs/self-driving/detect (SDK
|
|
423
|
+
* grep); keep the two in sync.
|
|
424
|
+
*/
|
|
425
|
+
const PROJECT_MANIFESTS = [
|
|
426
|
+
"package.json",
|
|
427
|
+
"pnpm-workspace.yaml",
|
|
428
|
+
"turbo.json",
|
|
429
|
+
"nx.json",
|
|
430
|
+
"lerna.json",
|
|
431
|
+
"requirements.txt",
|
|
432
|
+
"pyproject.toml",
|
|
433
|
+
"setup.py",
|
|
434
|
+
"Pipfile",
|
|
435
|
+
"manage.py",
|
|
436
|
+
"Gemfile",
|
|
437
|
+
"composer.json",
|
|
438
|
+
"pom.xml",
|
|
439
|
+
"Cargo.toml",
|
|
440
|
+
"mix.exs",
|
|
441
|
+
"go.mod",
|
|
442
|
+
"*.csproj",
|
|
443
|
+
"Package.swift",
|
|
444
|
+
"Podfile",
|
|
445
|
+
"project.yml",
|
|
446
|
+
"project.pbxproj",
|
|
447
|
+
"pubspec.yaml",
|
|
448
|
+
"build.gradle",
|
|
449
|
+
"build.gradle.kts",
|
|
450
|
+
"settings.gradle",
|
|
451
|
+
"settings.gradle.kts",
|
|
452
|
+
"gradle/libs.versions.toml"
|
|
453
|
+
];
|
|
454
|
+
/** The single brace-expansion glob covering every supported manifest. */
|
|
455
|
+
function manifestGlob() {
|
|
456
|
+
return `**/{${PROJECT_MANIFESTS.join(",")}}`;
|
|
457
|
+
}
|
|
458
|
+
function buildPrompt(cwd, targets, purpose, recommend) {
|
|
459
|
+
const targetList = targets.map((t) => `- ${t.id} → ${t.name}`).join("\n");
|
|
460
|
+
const projectShape = `{"path":string,"framework":string,"matchingTargets":string[],"targetId":string|null,"hasPostHog":boolean,"evidence":string${recommend ? ",\"recommended\":boolean" : ""}}`;
|
|
461
|
+
return [
|
|
462
|
+
`You are scanning a code repository to ${purpose}. Be fast and mechanical — do not over-explore. Keep any reasoning to one short sentence, then run the tool calls and output the JSON.`,
|
|
463
|
+
"",
|
|
464
|
+
`Working directory: ${cwd}`,
|
|
465
|
+
"",
|
|
466
|
+
"A \"project\" is a directory containing one or more of the manifest files below. Find projects by their manifests, NOT by walking directories — never report a directory that has no manifest.",
|
|
467
|
+
"",
|
|
468
|
+
"Do exactly this:",
|
|
469
|
+
`1. Run Glob ONCE with this pattern to find every project manifest in the repo in a single call: "${manifestGlob()}". Discard any result whose path contains node_modules/, dist/, build/, .next/, out/, coverage/, vendor/, .venv/, site-packages/, target/, Pods/, Carthage/, or DerivedData/. Group the remaining results by directory — each directory is one project. Three exceptions to "directory = project": a project.pbxproj lives inside a "<Name>.xcodeproj/" wrapper, so the project root is the PARENT of that .xcodeproj directory; a project.yml at a directory root is an XcodeGen-generated Xcode app rooted at that directory; a gradle/libs.versions.toml is a version catalog belonging to the gradle project rooted at the PARENT of that gradle/ directory (read it alongside the build.gradle when deciding hasPostHog), never its own project.`,
|
|
470
|
+
"2. Decide repoType: \"monorepo\" if the root package.json has a \"workspaces\" field OR a pnpm-workspace.yaml / turbo.json / nx.json / lerna.json was found at the root, else \"single\".",
|
|
471
|
+
`3. For EACH project directory, ONE AT A TIME: Read its manifest(s) ONCE, decide the fields below, then IMMEDIATELY — before reading any other project — write that project's verdict as one JSON line of shape ${projectShape}. Never write a verdict from memory of an earlier Read; the manifest you just read is the only source. Decide from its dependency lists:`,
|
|
472
|
+
" - the human-readable framework name (e.g. \"Next.js\", \"Django\", \"Rails\"),",
|
|
473
|
+
" - matchingTargets: EVERY target id from the list below whose technology appears in THIS manifest, in the priority order of the list. An id belongs here only if you can point at the dependency or setting in the manifest you just read — never carry one over from another project,",
|
|
474
|
+
" - targetId: the FIRST entry of matchingTargets (the list is ordered by priority, most specific first). null when matchingTargets is empty,",
|
|
475
|
+
` - hasPostHog: true if any dependency is a PostHog SDK. This includes: a name containing "posthog" in any ecosystem (e.g. posthog-js, posthog-node, @posthog/*, posthog for pip/gem/hex, a com.posthog:* gradle/maven coordinate, a PostHog NuGet PackageReference); an SPM package named "PostHog" or a repositoryURL of github.com/PostHog/posthog-ios (in Package.swift or a .pbxproj); or a "pod 'PostHog'" line in a Podfile. Else false.`,
|
|
476
|
+
" - evidence: the manifest fact that decided targetId, with the file it came from (e.g. \"rollup in devDependencies of backend/package.json\"). When targetId is null, name the closest fact you saw. One short clause, quoted from the manifest you just read.",
|
|
477
|
+
" Do NOT read any file other than these manifests.",
|
|
478
|
+
...recommend ? ["4. Pick exactly ONE project as recommended — the main user-facing client application. Prefer a frontend web app or a mobile app over backend servers/APIs, libraries, CLIs, tooling, and example/demo/docs apps. If several client apps exist, pick the primary production app; if no client app exists, pick the primary application project."] : [],
|
|
479
|
+
"",
|
|
480
|
+
"Target ids (id → name), in priority order — most specific first; if several match a project, keep the one listed earliest:",
|
|
481
|
+
targetList,
|
|
482
|
+
"",
|
|
483
|
+
"Output requirements:",
|
|
484
|
+
"- After the last verdict line, respond with ONLY a single JSON object assembling the verdict lines you wrote, copied VERBATIM — same path, framework, targetId and evidence per project. No prose, no markdown code fences.",
|
|
485
|
+
`- Shape: {"repoType":"monorepo"|"single","projects":[${projectShape}]}`,
|
|
486
|
+
"- \"path\" is the project directory relative to the working directory; use \".\" for the repo root.",
|
|
487
|
+
"- \"targetId\" MUST be exactly one of the target ids above when it matches; if several match, use the one listed earliest; otherwise null.",
|
|
488
|
+
"- Include projects whose stack matches no target too (targetId: null).",
|
|
489
|
+
...recommend ? ["- Exactly one project has \"recommended\": true; every other project has \"recommended\": false."] : [],
|
|
490
|
+
`- If there are no manifests at all, respond with exactly: ${AgentSignals.ABORT} detection failed`
|
|
491
|
+
].join("\n");
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Build the detection report from the agent's output, or null when it holds
|
|
495
|
+
* no verdicts. Exported for testing.
|
|
496
|
+
*
|
|
497
|
+
* The verdict lines are far more reliable than the model's final assembly
|
|
498
|
+
* (prose, pretty-printing, per-object fences), so every parseable line
|
|
499
|
+
* contributes: objects with a `path` merge by path (last wins), and a
|
|
500
|
+
* one-line assembly's `projects` merge the same way. `repoType` is
|
|
501
|
+
* approximated from the count — it only feeds telemetry and display.
|
|
502
|
+
*/
|
|
503
|
+
function deriveReportJson(text) {
|
|
504
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
505
|
+
for (const line of text.split("\n")) {
|
|
506
|
+
const start = line.indexOf("{");
|
|
507
|
+
const end = line.lastIndexOf("}");
|
|
508
|
+
if (start === -1 || end <= start) continue;
|
|
509
|
+
let parsed;
|
|
510
|
+
try {
|
|
511
|
+
parsed = JSON.parse(line.slice(start, end + 1));
|
|
512
|
+
} catch {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
const obj = parsed ?? {};
|
|
516
|
+
const candidates = Array.isArray(obj.projects) ? obj.projects : [obj];
|
|
517
|
+
for (const candidate of candidates) {
|
|
518
|
+
const p = candidate ?? {};
|
|
519
|
+
if (typeof p.path === "string") byPath.set(p.path, p);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (byPath.size === 0) return null;
|
|
523
|
+
const projects = [...byPath.values()];
|
|
524
|
+
return {
|
|
525
|
+
repoType: projects.length > 1 ? "monorepo" : "single",
|
|
526
|
+
projects
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Clamp an agent-reported project path to a safe repo-relative dir. The path
|
|
531
|
+
* is LLM output: absolute paths or `..` segments could steer later phases
|
|
532
|
+
* (e.g. integrate-run's targetDir) outside the repo, so they clamp to '.'.
|
|
533
|
+
*/
|
|
534
|
+
function coercePath(raw) {
|
|
535
|
+
if (typeof raw !== "string" || raw.length === 0) return ".";
|
|
536
|
+
if (isAbsolute(raw) || raw.split(/[\\/]/).includes("..")) return ".";
|
|
537
|
+
return raw;
|
|
538
|
+
}
|
|
539
|
+
/** Resolve an agent-reported path to an absolute dir clamped to the root — the caller-side counterpart of coercePath. */
|
|
540
|
+
function resolveProjectDir(installDir, rel) {
|
|
541
|
+
if (typeof rel !== "string" || rel === ".") return installDir;
|
|
542
|
+
const root = resolve(installDir);
|
|
543
|
+
const dir = resolve(root, rel);
|
|
544
|
+
return dir === root || dir.startsWith(root + sep) ? dir : root;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Validate the agent's raw JSON into a report, clamping each targetId to the
|
|
548
|
+
* supplied set and each path to a repo-relative dir. Exported for testing.
|
|
549
|
+
* `recommended` is stripped unless requested; at most one (the first) survives.
|
|
550
|
+
*/
|
|
551
|
+
function coerceAgenticReport(parsed, validTargetIds, options) {
|
|
552
|
+
const recommend = options?.recommend === true;
|
|
553
|
+
const rerankIds = options?.rerankIds ?? [];
|
|
554
|
+
const obj = parsed ?? {};
|
|
555
|
+
const repoType = obj.repoType === "monorepo" ? "monorepo" : "single";
|
|
556
|
+
const rawProjects = Array.isArray(obj.projects) ? obj.projects : [];
|
|
557
|
+
let recommendedSeen = false;
|
|
558
|
+
return {
|
|
559
|
+
repoType,
|
|
560
|
+
projects: rawProjects.map((raw) => {
|
|
561
|
+
const p = raw ?? {};
|
|
562
|
+
const pick = typeof p.targetId === "string" && validTargetIds.includes(p.targetId) ? p.targetId : null;
|
|
563
|
+
const enumerated = Array.isArray(p.matchingTargets) ? validTargetIds.find((id) => p.matchingTargets.includes(id)) ?? null : null;
|
|
564
|
+
const targetId = pick === null ? enumerated : enumerated !== null && rerankIds.includes(enumerated) && rerankIds.includes(pick) ? enumerated : pick;
|
|
565
|
+
const recommended = recommend && !recommendedSeen && p.recommended === true;
|
|
566
|
+
recommendedSeen ||= recommended;
|
|
567
|
+
return {
|
|
568
|
+
path: coercePath(p.path),
|
|
569
|
+
framework: typeof p.framework === "string" ? p.framework : "Unknown",
|
|
570
|
+
targetId,
|
|
571
|
+
hasPostHog: p.hasPostHog === true,
|
|
572
|
+
...recommend ? { recommended } : {}
|
|
573
|
+
};
|
|
574
|
+
})
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function formatToolUse(block) {
|
|
578
|
+
const name = typeof block?.name === "string" ? block.name : "tool";
|
|
579
|
+
const input = block?.input ?? {};
|
|
580
|
+
const detail = input.file_path || input.pattern || input.path || "";
|
|
581
|
+
return detail ? `${name} ${detail}` : name;
|
|
582
|
+
}
|
|
583
|
+
function sessionToWizardOptions(session) {
|
|
584
|
+
return {
|
|
585
|
+
installDir: session.installDir,
|
|
586
|
+
ci: session.ci,
|
|
587
|
+
debug: session.debug,
|
|
588
|
+
benchmark: session.benchmark,
|
|
589
|
+
yaraReport: session.yaraReport,
|
|
590
|
+
signup: session.signup,
|
|
591
|
+
apiKey: session.apiKey,
|
|
592
|
+
projectId: session.projectId
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
const NOOP_SPINNER = {
|
|
596
|
+
start: () => void 0,
|
|
597
|
+
stop: () => void 0,
|
|
598
|
+
message: () => void 0
|
|
599
|
+
};
|
|
600
|
+
/**
|
|
601
|
+
* Drive the wizard's agent loop on HAIKU_MODEL to scan the repo and return a
|
|
602
|
+
* structured detection report. Reuses the same setup every program uses, so
|
|
603
|
+
* MCP, tools, and credentials are wired identically.
|
|
604
|
+
*/
|
|
605
|
+
async function detectProjectsWithAgent(session, options) {
|
|
606
|
+
if (!session.credentials) throw new Error("Detection requires authenticated credentials.");
|
|
607
|
+
const { targets, programId, purpose = "set up a PostHog integration", recommend = false, rerankIds, onEvent } = options;
|
|
608
|
+
const { accessToken, host } = session.credentials;
|
|
609
|
+
const cwd = session.installDir;
|
|
610
|
+
const runOptions = sessionToWizardOptions(session);
|
|
611
|
+
const wizardMetadata = {
|
|
612
|
+
...buildRunTags({
|
|
613
|
+
programId,
|
|
614
|
+
integration: "agentic-detect",
|
|
615
|
+
runId: analytics.runId,
|
|
616
|
+
build: analytics.build
|
|
617
|
+
}),
|
|
618
|
+
call_type: "detection"
|
|
619
|
+
};
|
|
620
|
+
const prompt = buildPrompt(cwd, targets, purpose, recommend);
|
|
621
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
622
|
+
const timeoutMs = attempt === 0 ? AGENTIC_DETECTION_FIRST_ATTEMPT_TIMEOUT_MS : AGENTIC_DETECTION_RETRY_TIMEOUT_MS;
|
|
623
|
+
const agent = await initializeAgent({
|
|
624
|
+
emit: createUiReducer(getUI()),
|
|
625
|
+
workingDirectory: cwd,
|
|
626
|
+
posthogMcpUrl: host.mcpUrl,
|
|
627
|
+
posthogApiKey: accessToken,
|
|
628
|
+
host,
|
|
629
|
+
detectPackageManager: detectNodePackageManagers,
|
|
630
|
+
skillsBaseUrl: getSkillsBaseUrl(),
|
|
631
|
+
programId,
|
|
632
|
+
integrationLabel: "agentic-detect",
|
|
633
|
+
wizardMetadata,
|
|
634
|
+
allowedTools: [
|
|
635
|
+
"Read",
|
|
636
|
+
"Grep",
|
|
637
|
+
"Glob"
|
|
638
|
+
],
|
|
639
|
+
modelOverride: HAIKU_MODEL
|
|
640
|
+
}, runOptions);
|
|
641
|
+
const MAX_TRANSCRIPT_CHARS = 256 * 1024;
|
|
642
|
+
const collected = [];
|
|
643
|
+
let collectedChars = 0;
|
|
644
|
+
const collect = (text) => {
|
|
645
|
+
collected.push(text);
|
|
646
|
+
collectedChars += text.length;
|
|
647
|
+
while (collectedChars > MAX_TRANSCRIPT_CHARS && collected.length > 1) collectedChars -= collected.shift().length;
|
|
648
|
+
};
|
|
649
|
+
let resultText = "";
|
|
650
|
+
const result = await runAgent(agent, prompt, runOptions, NOOP_SPINNER, {
|
|
651
|
+
spinnerMessage: "Scanning the repo...",
|
|
652
|
+
successMessage: "Detection complete",
|
|
653
|
+
errorMessage: "Detection failed",
|
|
654
|
+
requestRemark: false,
|
|
655
|
+
timeoutMs
|
|
656
|
+
}, {
|
|
657
|
+
onMessage: (message) => {
|
|
658
|
+
if (message?.type === "assistant") {
|
|
659
|
+
for (const block of message.message?.content ?? []) if (block?.type === "text" && typeof block.text === "string") {
|
|
660
|
+
collect(block.text);
|
|
661
|
+
const line = block.text.trim();
|
|
662
|
+
if (line && onEvent) onEvent(line.length > 100 ? `${line.slice(0, 100)}…` : line);
|
|
663
|
+
} else if (block?.type === "tool_use") onEvent?.(formatToolUse(block));
|
|
664
|
+
} else if (message?.type === "result" && typeof message.result === "string") resultText = message.result;
|
|
665
|
+
},
|
|
666
|
+
finalize: (_resultMessage, _durationMs) => void 0
|
|
667
|
+
});
|
|
668
|
+
if (result.kind === "failure" && result.classification === "WIZARD_AGENTIC_DETECTION_TIMEOUT") {
|
|
669
|
+
if (attempt === 0) {
|
|
670
|
+
onEvent?.("Project scan timed out; retrying...");
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
throw new AgenticDetectionTimeoutError(attempt + 1, timeoutMs);
|
|
674
|
+
}
|
|
675
|
+
if (result.kind !== "success") {
|
|
676
|
+
if (result.kind === "decided_failure") throw result.failure.error ?? new Error(result.failure.message);
|
|
677
|
+
throw result.error ?? new Error(result.message || `Agent error: ${result.classification}`);
|
|
678
|
+
}
|
|
679
|
+
const output = `${collected.join("\n")}\n${resultText}`;
|
|
680
|
+
const derived = deriveReportJson(output);
|
|
681
|
+
if (derived !== null) return coerceAgenticReport(derived, targets.map((t) => t.id), {
|
|
682
|
+
recommend,
|
|
683
|
+
rerankIds
|
|
684
|
+
});
|
|
685
|
+
if (output.includes(AgentSignals.ABORT)) return {
|
|
686
|
+
repoType: "single",
|
|
687
|
+
projects: []
|
|
688
|
+
};
|
|
689
|
+
if (attempt === 0) onEvent?.("Retrying project scan...");
|
|
690
|
+
}
|
|
691
|
+
throw new Error("Agent did not return a JSON object after retry");
|
|
692
|
+
}
|
|
693
|
+
//#endregion
|
|
694
|
+
//#region src/lib/warehouse-sources/registry.ts
|
|
695
|
+
const CORE_SOURCE_DETECTORS = [
|
|
696
|
+
{
|
|
697
|
+
kind: "Postgres",
|
|
698
|
+
label: "PostgreSQL",
|
|
699
|
+
mode: "in-cli",
|
|
700
|
+
signals: {
|
|
701
|
+
npm: [
|
|
702
|
+
"pg",
|
|
703
|
+
"postgres",
|
|
704
|
+
"postgres.js",
|
|
705
|
+
"knex",
|
|
706
|
+
"sequelize"
|
|
707
|
+
],
|
|
708
|
+
python: [
|
|
709
|
+
"psycopg",
|
|
710
|
+
"psycopg2",
|
|
711
|
+
"psycopg2-binary",
|
|
712
|
+
"asyncpg"
|
|
713
|
+
],
|
|
714
|
+
ruby: ["pg"],
|
|
715
|
+
envKeys: [
|
|
716
|
+
/^DATABASE_URL$/,
|
|
717
|
+
/^POSTGRES_/,
|
|
718
|
+
/^PG(HOST|DATABASE|USER|PORT)$/
|
|
719
|
+
]
|
|
720
|
+
},
|
|
721
|
+
supersededBy: ["Neon", "Supabase"]
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
kind: "MySQL",
|
|
725
|
+
label: "MySQL",
|
|
726
|
+
mode: "in-cli",
|
|
727
|
+
signals: {
|
|
728
|
+
npm: ["mysql", "mysql2"],
|
|
729
|
+
python: [
|
|
730
|
+
"pymysql",
|
|
731
|
+
"mysqlclient",
|
|
732
|
+
"mysql-connector-python"
|
|
733
|
+
],
|
|
734
|
+
ruby: ["mysql2"],
|
|
735
|
+
envKeys: [/^MYSQL_/]
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
kind: "MongoDB",
|
|
740
|
+
label: "MongoDB",
|
|
741
|
+
mode: "in-cli",
|
|
742
|
+
signals: {
|
|
743
|
+
npm: ["mongodb", "mongoose"],
|
|
744
|
+
python: ["pymongo", "motor"],
|
|
745
|
+
ruby: ["mongo", "mongoid"],
|
|
746
|
+
envKeys: [/^MONGO(DB)?_/]
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
kind: "Snowflake",
|
|
751
|
+
label: "Snowflake",
|
|
752
|
+
mode: "in-cli",
|
|
753
|
+
signals: {
|
|
754
|
+
npm: ["snowflake-sdk"],
|
|
755
|
+
python: ["snowflake-connector-python", "snowflake-sqlalchemy"],
|
|
756
|
+
envKeys: [/^SNOWFLAKE_/]
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
kind: "BigQuery",
|
|
761
|
+
label: "BigQuery",
|
|
762
|
+
mode: "deep-link",
|
|
763
|
+
signals: {
|
|
764
|
+
npm: ["@google-cloud/bigquery"],
|
|
765
|
+
python: ["google-cloud-bigquery"],
|
|
766
|
+
envKeys: [/^BIGQUERY_/]
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
kind: "Redshift",
|
|
771
|
+
label: "Redshift",
|
|
772
|
+
mode: "in-cli",
|
|
773
|
+
signals: {
|
|
774
|
+
npm: ["node-redshift"],
|
|
775
|
+
python: ["redshift-connector"],
|
|
776
|
+
envKeys: [/^REDSHIFT_/]
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
kind: "MSSQL",
|
|
781
|
+
label: "SQL Server",
|
|
782
|
+
mode: "in-cli",
|
|
783
|
+
signals: {
|
|
784
|
+
npm: ["mssql", "tedious"],
|
|
785
|
+
python: ["pyodbc", "pymssql"],
|
|
786
|
+
envKeys: [/^MSSQL_/]
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
kind: "Supabase",
|
|
791
|
+
label: "Supabase",
|
|
792
|
+
mode: "in-cli",
|
|
793
|
+
signals: {
|
|
794
|
+
npm: ["@supabase/supabase-js"],
|
|
795
|
+
python: ["supabase"],
|
|
796
|
+
envKeys: [/^SUPABASE_/]
|
|
797
|
+
}
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
kind: "ClickHouse",
|
|
801
|
+
label: "ClickHouse",
|
|
802
|
+
mode: "in-cli",
|
|
803
|
+
signals: {
|
|
804
|
+
npm: ["@clickhouse/client"],
|
|
805
|
+
python: ["clickhouse-connect", "clickhouse-driver"],
|
|
806
|
+
envKeys: [/^CLICKHOUSE_/]
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
kind: "Convex",
|
|
811
|
+
label: "Convex",
|
|
812
|
+
mode: "in-cli",
|
|
813
|
+
signals: {
|
|
814
|
+
npm: ["convex"],
|
|
815
|
+
python: ["convex"],
|
|
816
|
+
envKeys: [/^CONVEX_/, /^NEXT_PUBLIC_CONVEX_URL$/]
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
kind: "Firebase",
|
|
821
|
+
label: "Firebase",
|
|
822
|
+
mode: "deep-link",
|
|
823
|
+
signals: {
|
|
824
|
+
npm: ["firebase-admin", "firebase"],
|
|
825
|
+
python: ["firebase-admin"],
|
|
826
|
+
envKeys: [/^FIREBASE_/, /^NEXT_PUBLIC_FIREBASE_/]
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
kind: "Neon",
|
|
831
|
+
label: "Neon",
|
|
832
|
+
mode: "in-cli",
|
|
833
|
+
signals: {
|
|
834
|
+
npm: ["@neondatabase/serverless"],
|
|
835
|
+
envKeys: [/^NEON_/]
|
|
836
|
+
}
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
kind: "PlanetScaleMySQL",
|
|
840
|
+
label: "PlanetScale MySQL",
|
|
841
|
+
mode: "in-cli",
|
|
842
|
+
signals: {
|
|
843
|
+
npm: ["@planetscale/database"],
|
|
844
|
+
envKeys: [/^PLANETSCALE_/]
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
kind: "DynamoDB",
|
|
849
|
+
label: "DynamoDB",
|
|
850
|
+
mode: "in-cli",
|
|
851
|
+
signals: {
|
|
852
|
+
npm: ["@aws-sdk/client-dynamodb", "@aws-sdk/lib-dynamodb"],
|
|
853
|
+
python: ["pynamodb"]
|
|
854
|
+
}
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
kind: "Motherduck",
|
|
858
|
+
label: "MotherDuck",
|
|
859
|
+
mode: "in-cli",
|
|
860
|
+
signals: {
|
|
861
|
+
npm: ["@motherduck/wasm-client"],
|
|
862
|
+
envKeys: [/^MOTHERDUCK_/]
|
|
863
|
+
}
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
kind: "Databricks",
|
|
867
|
+
label: "Databricks",
|
|
868
|
+
mode: "in-cli",
|
|
869
|
+
signals: {
|
|
870
|
+
npm: ["@databricks/sql"],
|
|
871
|
+
python: ["databricks-sql-connector", "databricks-sdk"],
|
|
872
|
+
envKeys: [/^DATABRICKS_/]
|
|
873
|
+
}
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
kind: "Singlestore",
|
|
877
|
+
label: "SingleStore",
|
|
878
|
+
mode: "in-cli",
|
|
879
|
+
signals: {
|
|
880
|
+
python: ["singlestoredb"],
|
|
881
|
+
envKeys: [/^SINGLESTORE_/]
|
|
882
|
+
}
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
kind: "Stripe",
|
|
886
|
+
label: "Stripe",
|
|
887
|
+
mode: "in-cli",
|
|
888
|
+
signals: {
|
|
889
|
+
npm: [
|
|
890
|
+
"stripe",
|
|
891
|
+
"@stripe/stripe-js",
|
|
892
|
+
"@stripe/react-stripe-js"
|
|
893
|
+
],
|
|
894
|
+
python: ["stripe"],
|
|
895
|
+
ruby: ["stripe"],
|
|
896
|
+
envKeys: [/^STRIPE_(SECRET|API)_KEY$/]
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
kind: "Clerk",
|
|
901
|
+
label: "Clerk",
|
|
902
|
+
mode: "in-cli",
|
|
903
|
+
signals: {
|
|
904
|
+
npm: [
|
|
905
|
+
"@clerk/nextjs",
|
|
906
|
+
"@clerk/clerk-react",
|
|
907
|
+
"@clerk/backend",
|
|
908
|
+
"@clerk/express",
|
|
909
|
+
"@clerk/fastify",
|
|
910
|
+
"@clerk/remix"
|
|
911
|
+
],
|
|
912
|
+
envKeys: [/^CLERK_SECRET_KEY$/, /^NEXT_PUBLIC_CLERK_/]
|
|
913
|
+
}
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
kind: "Resend",
|
|
917
|
+
label: "Resend",
|
|
918
|
+
mode: "in-cli",
|
|
919
|
+
signals: {
|
|
920
|
+
npm: ["resend"],
|
|
921
|
+
python: ["resend"],
|
|
922
|
+
envKeys: [/^RESEND_API_KEY$/]
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
kind: "Shopify",
|
|
927
|
+
label: "Shopify",
|
|
928
|
+
mode: "in-cli",
|
|
929
|
+
signals: {
|
|
930
|
+
npm: ["@shopify/shopify-api", "shopify-api-node"],
|
|
931
|
+
python: ["shopifyapi"],
|
|
932
|
+
envKeys: [/^SHOPIFY_/]
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
kind: "Klaviyo",
|
|
937
|
+
label: "Klaviyo",
|
|
938
|
+
mode: "in-cli",
|
|
939
|
+
signals: {
|
|
940
|
+
npm: ["klaviyo-api"],
|
|
941
|
+
python: ["klaviyo-api"],
|
|
942
|
+
envKeys: [/^KLAVIYO_/]
|
|
943
|
+
}
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
kind: "Chargebee",
|
|
947
|
+
label: "Chargebee",
|
|
948
|
+
mode: "in-cli",
|
|
949
|
+
signals: {
|
|
950
|
+
npm: ["chargebee"],
|
|
951
|
+
python: ["chargebee"],
|
|
952
|
+
envKeys: [/^CHARGEBEE_/]
|
|
953
|
+
}
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
kind: "Paddle",
|
|
957
|
+
label: "Paddle",
|
|
958
|
+
mode: "in-cli",
|
|
959
|
+
signals: {
|
|
960
|
+
npm: ["@paddle/paddle-node-sdk", "@paddle/paddle-js"],
|
|
961
|
+
envKeys: [/^PADDLE_/]
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
kind: "Polar",
|
|
966
|
+
label: "Polar",
|
|
967
|
+
mode: "in-cli",
|
|
968
|
+
signals: {
|
|
969
|
+
npm: ["@polar-sh/sdk", "@polar-sh/nextjs"],
|
|
970
|
+
envKeys: [/^POLAR_/]
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
kind: "Mailchimp",
|
|
975
|
+
label: "Mailchimp",
|
|
976
|
+
mode: "in-cli",
|
|
977
|
+
signals: {
|
|
978
|
+
npm: ["@mailchimp/mailchimp_marketing"],
|
|
979
|
+
python: ["mailchimp-marketing"],
|
|
980
|
+
envKeys: [/^MAILCHIMP_/]
|
|
981
|
+
}
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
kind: "CustomerIO",
|
|
985
|
+
label: "Customer.io",
|
|
986
|
+
mode: "in-cli",
|
|
987
|
+
signals: {
|
|
988
|
+
npm: ["customerio-node"],
|
|
989
|
+
python: ["customerio"],
|
|
990
|
+
envKeys: [/^CUSTOMER_?IO_/]
|
|
991
|
+
}
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
kind: "Typeform",
|
|
995
|
+
label: "Typeform",
|
|
996
|
+
mode: "in-cli",
|
|
997
|
+
signals: {
|
|
998
|
+
npm: ["@typeform/api-client"],
|
|
999
|
+
envKeys: [/^TYPEFORM_/]
|
|
1000
|
+
}
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
kind: "Sentry",
|
|
1004
|
+
label: "Sentry",
|
|
1005
|
+
mode: "in-cli",
|
|
1006
|
+
signals: {
|
|
1007
|
+
npm: [
|
|
1008
|
+
"@sentry/node",
|
|
1009
|
+
"@sentry/browser",
|
|
1010
|
+
"@sentry/react",
|
|
1011
|
+
"@sentry/nextjs"
|
|
1012
|
+
],
|
|
1013
|
+
python: ["sentry-sdk"],
|
|
1014
|
+
ruby: ["sentry-ruby"]
|
|
1015
|
+
}
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
kind: "Plaid",
|
|
1019
|
+
label: "Plaid",
|
|
1020
|
+
mode: "in-cli",
|
|
1021
|
+
signals: {
|
|
1022
|
+
npm: ["plaid"],
|
|
1023
|
+
python: ["plaid-python", "plaid"],
|
|
1024
|
+
ruby: ["plaid"],
|
|
1025
|
+
envKeys: [/^PLAID_/]
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
kind: "Braintree",
|
|
1030
|
+
label: "Braintree",
|
|
1031
|
+
mode: "in-cli",
|
|
1032
|
+
signals: {
|
|
1033
|
+
npm: ["braintree"],
|
|
1034
|
+
python: ["braintree"],
|
|
1035
|
+
ruby: ["braintree"],
|
|
1036
|
+
envKeys: [/^BRAINTREE_/]
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
kind: "Square",
|
|
1041
|
+
label: "Square",
|
|
1042
|
+
mode: "in-cli",
|
|
1043
|
+
signals: {
|
|
1044
|
+
npm: ["square"],
|
|
1045
|
+
python: ["squareup"],
|
|
1046
|
+
ruby: ["square"],
|
|
1047
|
+
envKeys: [/^SQUARE_/]
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
kind: "GoCardless",
|
|
1052
|
+
label: "GoCardless",
|
|
1053
|
+
mode: "in-cli",
|
|
1054
|
+
signals: {
|
|
1055
|
+
npm: ["gocardless-nodejs"],
|
|
1056
|
+
python: ["gocardless-pro"],
|
|
1057
|
+
ruby: ["gocardless_pro"],
|
|
1058
|
+
envKeys: [/^GOCARDLESS_/]
|
|
1059
|
+
}
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
kind: "Mollie",
|
|
1063
|
+
label: "Mollie",
|
|
1064
|
+
mode: "in-cli",
|
|
1065
|
+
signals: {
|
|
1066
|
+
npm: ["@mollie/api-client"],
|
|
1067
|
+
python: ["mollie-api-python"],
|
|
1068
|
+
envKeys: [/^MOLLIE_/]
|
|
1069
|
+
}
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
kind: "CheckoutCom",
|
|
1073
|
+
label: "Checkout.com",
|
|
1074
|
+
mode: "in-cli",
|
|
1075
|
+
signals: {
|
|
1076
|
+
npm: ["checkout-sdk-node"],
|
|
1077
|
+
python: ["checkout-sdk"],
|
|
1078
|
+
envKeys: [/^CKO_/, /^CHECKOUT_COM_/]
|
|
1079
|
+
}
|
|
1080
|
+
},
|
|
1081
|
+
{
|
|
1082
|
+
kind: "Recurly",
|
|
1083
|
+
label: "Recurly",
|
|
1084
|
+
mode: "in-cli",
|
|
1085
|
+
signals: {
|
|
1086
|
+
npm: ["recurly"],
|
|
1087
|
+
python: ["recurly"],
|
|
1088
|
+
ruby: ["recurly"],
|
|
1089
|
+
envKeys: [/^RECURLY_/]
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
kind: "RevenueCat",
|
|
1094
|
+
label: "RevenueCat",
|
|
1095
|
+
mode: "in-cli",
|
|
1096
|
+
signals: {
|
|
1097
|
+
npm: [
|
|
1098
|
+
"@revenuecat/purchases-js",
|
|
1099
|
+
"react-native-purchases",
|
|
1100
|
+
"@revenuecat/purchases-capacitor"
|
|
1101
|
+
],
|
|
1102
|
+
envKeys: [/^REVENUE_?CAT_/]
|
|
1103
|
+
}
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
kind: "Twilio",
|
|
1107
|
+
label: "Twilio",
|
|
1108
|
+
mode: "in-cli",
|
|
1109
|
+
signals: {
|
|
1110
|
+
npm: ["twilio"],
|
|
1111
|
+
python: ["twilio"],
|
|
1112
|
+
ruby: ["twilio-ruby"],
|
|
1113
|
+
envKeys: [/^TWILIO_/]
|
|
1114
|
+
}
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
kind: "SendGrid",
|
|
1118
|
+
label: "SendGrid",
|
|
1119
|
+
mode: "in-cli",
|
|
1120
|
+
signals: {
|
|
1121
|
+
npm: ["@sendgrid/mail", "@sendgrid/client"],
|
|
1122
|
+
python: ["sendgrid"],
|
|
1123
|
+
ruby: ["sendgrid-ruby"],
|
|
1124
|
+
envKeys: [/^SENDGRID_/]
|
|
1125
|
+
}
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
kind: "Mailgun",
|
|
1129
|
+
label: "Mailgun",
|
|
1130
|
+
mode: "in-cli",
|
|
1131
|
+
signals: {
|
|
1132
|
+
npm: ["mailgun.js", "mailgun-js"],
|
|
1133
|
+
python: ["mailgun"],
|
|
1134
|
+
ruby: ["mailgun-ruby"],
|
|
1135
|
+
envKeys: [/^MAILGUN_/]
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
kind: "Postmark",
|
|
1140
|
+
label: "Postmark",
|
|
1141
|
+
mode: "in-cli",
|
|
1142
|
+
signals: {
|
|
1143
|
+
npm: ["postmark"],
|
|
1144
|
+
python: ["postmarker"],
|
|
1145
|
+
ruby: ["postmark"],
|
|
1146
|
+
envKeys: [/^POSTMARK_/]
|
|
1147
|
+
}
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
kind: "Brevo",
|
|
1151
|
+
label: "Brevo",
|
|
1152
|
+
mode: "in-cli",
|
|
1153
|
+
signals: {
|
|
1154
|
+
npm: ["@getbrevo/brevo", "sib-api-v3-sdk"],
|
|
1155
|
+
python: ["sib-api-v3-sdk"],
|
|
1156
|
+
envKeys: [/^BREVO_/]
|
|
1157
|
+
}
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
kind: "MailerLite",
|
|
1161
|
+
label: "MailerLite",
|
|
1162
|
+
mode: "in-cli",
|
|
1163
|
+
signals: {
|
|
1164
|
+
npm: ["@mailerlite/mailerlite-nodejs"],
|
|
1165
|
+
python: ["mailerlite"],
|
|
1166
|
+
envKeys: [/^MAILERLITE_/]
|
|
1167
|
+
}
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
kind: "Mailjet",
|
|
1171
|
+
label: "Mailjet",
|
|
1172
|
+
mode: "in-cli",
|
|
1173
|
+
signals: {
|
|
1174
|
+
npm: ["node-mailjet"],
|
|
1175
|
+
python: ["mailjet-rest"],
|
|
1176
|
+
envKeys: [/^MAILJET_/, /^MJ_APIKEY_/]
|
|
1177
|
+
}
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
kind: "LaunchDarkly",
|
|
1181
|
+
label: "LaunchDarkly",
|
|
1182
|
+
mode: "in-cli",
|
|
1183
|
+
signals: {
|
|
1184
|
+
npm: [
|
|
1185
|
+
"launchdarkly-node-server-sdk",
|
|
1186
|
+
"@launchdarkly/node-server-sdk",
|
|
1187
|
+
"launchdarkly-js-client-sdk",
|
|
1188
|
+
"launchdarkly-react-client-sdk"
|
|
1189
|
+
],
|
|
1190
|
+
python: ["launchdarkly-server-sdk"],
|
|
1191
|
+
ruby: ["launchdarkly-server-sdk"],
|
|
1192
|
+
envKeys: [/^LAUNCHDARKLY_/, /^LD_SDK_KEY$/]
|
|
1193
|
+
}
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
kind: "Optimizely",
|
|
1197
|
+
label: "Optimizely",
|
|
1198
|
+
mode: "in-cli",
|
|
1199
|
+
signals: {
|
|
1200
|
+
npm: ["@optimizely/optimizely-sdk"],
|
|
1201
|
+
python: ["optimizely-sdk"],
|
|
1202
|
+
envKeys: [/^OPTIMIZELY_/]
|
|
1203
|
+
}
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
kind: "Braze",
|
|
1207
|
+
label: "Braze",
|
|
1208
|
+
mode: "in-cli",
|
|
1209
|
+
signals: {
|
|
1210
|
+
npm: ["@braze/web-sdk", "@braze/react-native-sdk"],
|
|
1211
|
+
python: ["braze-client"],
|
|
1212
|
+
envKeys: [/^BRAZE_/]
|
|
1213
|
+
}
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
kind: "Rollbar",
|
|
1217
|
+
label: "Rollbar",
|
|
1218
|
+
mode: "in-cli",
|
|
1219
|
+
signals: {
|
|
1220
|
+
npm: ["rollbar"],
|
|
1221
|
+
python: ["rollbar", "pyrollbar"],
|
|
1222
|
+
ruby: ["rollbar"],
|
|
1223
|
+
envKeys: [/^ROLLBAR_/]
|
|
1224
|
+
}
|
|
1225
|
+
},
|
|
1226
|
+
{
|
|
1227
|
+
kind: "Okta",
|
|
1228
|
+
label: "Okta",
|
|
1229
|
+
mode: "in-cli",
|
|
1230
|
+
signals: {
|
|
1231
|
+
npm: [
|
|
1232
|
+
"@okta/okta-sdk-nodejs",
|
|
1233
|
+
"@okta/okta-auth-js",
|
|
1234
|
+
"@okta/okta-react"
|
|
1235
|
+
],
|
|
1236
|
+
python: ["okta"],
|
|
1237
|
+
envKeys: [/^OKTA_/]
|
|
1238
|
+
}
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
kind: "WorkOS",
|
|
1242
|
+
label: "WorkOS",
|
|
1243
|
+
mode: "in-cli",
|
|
1244
|
+
signals: {
|
|
1245
|
+
npm: ["@workos-inc/node"],
|
|
1246
|
+
python: ["workos"],
|
|
1247
|
+
envKeys: [/^WORKOS_/]
|
|
1248
|
+
}
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
kind: "Notion",
|
|
1252
|
+
label: "Notion",
|
|
1253
|
+
mode: "in-cli",
|
|
1254
|
+
signals: {
|
|
1255
|
+
npm: ["@notionhq/client"],
|
|
1256
|
+
python: ["notion-client"],
|
|
1257
|
+
envKeys: [/^NOTION_/]
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
kind: "FullStory",
|
|
1262
|
+
label: "FullStory",
|
|
1263
|
+
mode: "in-cli",
|
|
1264
|
+
signals: {
|
|
1265
|
+
npm: ["@fullstory/browser", "@fullstory/react-native"],
|
|
1266
|
+
envKeys: [/^FULLSTORY_/]
|
|
1267
|
+
}
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
kind: "Amplitude",
|
|
1271
|
+
label: "Amplitude",
|
|
1272
|
+
mode: "in-cli",
|
|
1273
|
+
signals: {
|
|
1274
|
+
npm: [
|
|
1275
|
+
"@amplitude/analytics-browser",
|
|
1276
|
+
"@amplitude/analytics-node",
|
|
1277
|
+
"@amplitude/analytics-react-native",
|
|
1278
|
+
"amplitude-js"
|
|
1279
|
+
],
|
|
1280
|
+
python: ["amplitude-analytics"],
|
|
1281
|
+
envKeys: [/^AMPLITUDE_/]
|
|
1282
|
+
}
|
|
1283
|
+
},
|
|
1284
|
+
{
|
|
1285
|
+
kind: "Mixpanel",
|
|
1286
|
+
label: "Mixpanel",
|
|
1287
|
+
mode: "in-cli",
|
|
1288
|
+
signals: {
|
|
1289
|
+
npm: ["mixpanel", "mixpanel-browser"],
|
|
1290
|
+
python: ["mixpanel"],
|
|
1291
|
+
ruby: ["mixpanel-ruby"],
|
|
1292
|
+
envKeys: [/^MIXPANEL_/]
|
|
1293
|
+
}
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
kind: "Pendo",
|
|
1297
|
+
label: "Pendo",
|
|
1298
|
+
mode: "in-cli",
|
|
1299
|
+
signals: {
|
|
1300
|
+
npm: ["@pendo/agent"],
|
|
1301
|
+
envKeys: [/^PENDO_/]
|
|
1302
|
+
}
|
|
1303
|
+
},
|
|
1304
|
+
{
|
|
1305
|
+
kind: "Salesforce",
|
|
1306
|
+
label: "Salesforce",
|
|
1307
|
+
mode: "deep-link",
|
|
1308
|
+
signals: {
|
|
1309
|
+
npm: ["jsforce"],
|
|
1310
|
+
python: ["simple-salesforce"],
|
|
1311
|
+
envKeys: [/^SALESFORCE_/]
|
|
1312
|
+
}
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
kind: "Hubspot",
|
|
1316
|
+
label: "HubSpot",
|
|
1317
|
+
mode: "deep-link",
|
|
1318
|
+
signals: {
|
|
1319
|
+
npm: ["@hubspot/api-client"],
|
|
1320
|
+
python: ["hubspot-api-client"],
|
|
1321
|
+
envKeys: [/^HUBSPOT_/]
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
kind: "Zendesk",
|
|
1326
|
+
label: "Zendesk",
|
|
1327
|
+
mode: "deep-link",
|
|
1328
|
+
signals: {
|
|
1329
|
+
npm: ["node-zendesk"],
|
|
1330
|
+
python: ["zenpy"],
|
|
1331
|
+
envKeys: [/^ZENDESK_/]
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
kind: "Intercom",
|
|
1336
|
+
label: "Intercom",
|
|
1337
|
+
mode: "deep-link",
|
|
1338
|
+
signals: {
|
|
1339
|
+
npm: ["intercom-client", "@intercom/messenger-js-sdk"],
|
|
1340
|
+
python: ["python-intercom"],
|
|
1341
|
+
envKeys: [/^INTERCOM_/]
|
|
1342
|
+
}
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
kind: "Linear",
|
|
1346
|
+
label: "Linear",
|
|
1347
|
+
mode: "deep-link",
|
|
1348
|
+
signals: {
|
|
1349
|
+
npm: ["@linear/sdk"],
|
|
1350
|
+
envKeys: [/^LINEAR_API_KEY$/]
|
|
1351
|
+
}
|
|
1352
|
+
},
|
|
1353
|
+
{
|
|
1354
|
+
kind: "Slack",
|
|
1355
|
+
label: "Slack",
|
|
1356
|
+
mode: "deep-link",
|
|
1357
|
+
signals: {
|
|
1358
|
+
npm: ["@slack/web-api", "@slack/bolt"],
|
|
1359
|
+
python: ["slack-sdk", "slack-bolt"],
|
|
1360
|
+
envKeys: [/^SLACK_(BOT|APP|SIGNING|CLIENT)_/]
|
|
1361
|
+
}
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
kind: "Github",
|
|
1365
|
+
label: "GitHub",
|
|
1366
|
+
mode: "deep-link",
|
|
1367
|
+
signals: {
|
|
1368
|
+
npm: [
|
|
1369
|
+
"@octokit/rest",
|
|
1370
|
+
"@octokit/core",
|
|
1371
|
+
"@octokit/graphql",
|
|
1372
|
+
"octokit"
|
|
1373
|
+
],
|
|
1374
|
+
python: ["pygithub"],
|
|
1375
|
+
ruby: ["octokit"]
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
];
|
|
1379
|
+
const LLM_SOURCE_DETECTORS = [
|
|
1380
|
+
{
|
|
1381
|
+
kind: "OpenAI",
|
|
1382
|
+
label: "OpenAI",
|
|
1383
|
+
mode: "in-cli",
|
|
1384
|
+
signals: {
|
|
1385
|
+
npm: ["openai"],
|
|
1386
|
+
python: ["openai"],
|
|
1387
|
+
envKeys: [/^OPENAI_/]
|
|
1388
|
+
}
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
kind: "Anthropic",
|
|
1392
|
+
label: "Anthropic",
|
|
1393
|
+
mode: "in-cli",
|
|
1394
|
+
signals: {
|
|
1395
|
+
npm: ["@anthropic-ai/sdk"],
|
|
1396
|
+
python: ["anthropic"],
|
|
1397
|
+
envKeys: [/^ANTHROPIC_/]
|
|
1398
|
+
}
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
kind: "Cohere",
|
|
1402
|
+
label: "Cohere",
|
|
1403
|
+
mode: "in-cli",
|
|
1404
|
+
signals: {
|
|
1405
|
+
npm: ["cohere-ai"],
|
|
1406
|
+
python: ["cohere"],
|
|
1407
|
+
envKeys: [/^COHERE_/]
|
|
1408
|
+
}
|
|
1409
|
+
},
|
|
1410
|
+
{
|
|
1411
|
+
kind: "MistralAI",
|
|
1412
|
+
label: "Mistral AI",
|
|
1413
|
+
mode: "in-cli",
|
|
1414
|
+
signals: {
|
|
1415
|
+
npm: ["@mistralai/mistralai"],
|
|
1416
|
+
python: ["mistralai"],
|
|
1417
|
+
envKeys: [/^MISTRAL_/]
|
|
1418
|
+
}
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
kind: "Groq",
|
|
1422
|
+
label: "Groq",
|
|
1423
|
+
mode: "in-cli",
|
|
1424
|
+
signals: {
|
|
1425
|
+
npm: ["groq-sdk"],
|
|
1426
|
+
python: ["groq"],
|
|
1427
|
+
envKeys: [/^GROQ_/]
|
|
1428
|
+
}
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
kind: "TogetherAI",
|
|
1432
|
+
label: "Together AI",
|
|
1433
|
+
mode: "in-cli",
|
|
1434
|
+
signals: {
|
|
1435
|
+
npm: ["together-ai"],
|
|
1436
|
+
python: ["together"],
|
|
1437
|
+
envKeys: [/^TOGETHER_/]
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
kind: "FireworksAI",
|
|
1442
|
+
label: "Fireworks AI",
|
|
1443
|
+
mode: "in-cli",
|
|
1444
|
+
signals: {
|
|
1445
|
+
python: ["fireworks-ai"],
|
|
1446
|
+
envKeys: [/^FIREWORKS_/]
|
|
1447
|
+
}
|
|
1448
|
+
},
|
|
1449
|
+
{
|
|
1450
|
+
kind: "Replicate",
|
|
1451
|
+
label: "Replicate",
|
|
1452
|
+
mode: "in-cli",
|
|
1453
|
+
signals: {
|
|
1454
|
+
npm: ["replicate"],
|
|
1455
|
+
python: ["replicate"],
|
|
1456
|
+
envKeys: [/^REPLICATE_/]
|
|
1457
|
+
}
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
kind: "HuggingFace",
|
|
1461
|
+
label: "Hugging Face",
|
|
1462
|
+
mode: "in-cli",
|
|
1463
|
+
signals: {
|
|
1464
|
+
npm: ["@huggingface/inference"],
|
|
1465
|
+
python: ["huggingface-hub"],
|
|
1466
|
+
envKeys: [/^HUGGINGFACE_/, /^HF_TOKEN$/]
|
|
1467
|
+
}
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
kind: "ElevenLabs",
|
|
1471
|
+
label: "ElevenLabs",
|
|
1472
|
+
mode: "in-cli",
|
|
1473
|
+
signals: {
|
|
1474
|
+
npm: ["@elevenlabs/elevenlabs-js", "elevenlabs"],
|
|
1475
|
+
python: ["elevenlabs"],
|
|
1476
|
+
envKeys: [/^ELEVENLABS_/, /^ELEVEN_LABS_/]
|
|
1477
|
+
}
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
kind: "Deepgram",
|
|
1481
|
+
label: "Deepgram",
|
|
1482
|
+
mode: "in-cli",
|
|
1483
|
+
signals: {
|
|
1484
|
+
npm: ["@deepgram/sdk"],
|
|
1485
|
+
python: ["deepgram-sdk"],
|
|
1486
|
+
envKeys: [/^DEEPGRAM_/]
|
|
1487
|
+
}
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
kind: "AssemblyAI",
|
|
1491
|
+
label: "AssemblyAI",
|
|
1492
|
+
mode: "in-cli",
|
|
1493
|
+
signals: {
|
|
1494
|
+
npm: ["assemblyai"],
|
|
1495
|
+
python: ["assemblyai"],
|
|
1496
|
+
envKeys: [/^ASSEMBLYAI_/]
|
|
1497
|
+
}
|
|
1498
|
+
},
|
|
1499
|
+
{
|
|
1500
|
+
kind: "Langfuse",
|
|
1501
|
+
label: "Langfuse",
|
|
1502
|
+
mode: "in-cli",
|
|
1503
|
+
signals: {
|
|
1504
|
+
npm: ["langfuse"],
|
|
1505
|
+
python: ["langfuse"],
|
|
1506
|
+
envKeys: [/^LANGFUSE_/]
|
|
1507
|
+
}
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
kind: "LangSmith",
|
|
1511
|
+
label: "LangSmith",
|
|
1512
|
+
mode: "in-cli",
|
|
1513
|
+
signals: {
|
|
1514
|
+
npm: ["langsmith"],
|
|
1515
|
+
python: ["langsmith"],
|
|
1516
|
+
envKeys: [/^LANGSMITH_/, /^LANGCHAIN_API_KEY$/]
|
|
1517
|
+
}
|
|
1518
|
+
},
|
|
1519
|
+
{
|
|
1520
|
+
kind: "Firecrawl",
|
|
1521
|
+
label: "Firecrawl",
|
|
1522
|
+
mode: "in-cli",
|
|
1523
|
+
signals: {
|
|
1524
|
+
npm: ["@mendable/firecrawl-js"],
|
|
1525
|
+
python: ["firecrawl-py"],
|
|
1526
|
+
envKeys: [/^FIRECRAWL_/]
|
|
1527
|
+
}
|
|
1528
|
+
},
|
|
1529
|
+
{
|
|
1530
|
+
kind: "Mem0",
|
|
1531
|
+
label: "Mem0",
|
|
1532
|
+
mode: "in-cli",
|
|
1533
|
+
signals: {
|
|
1534
|
+
npm: ["mem0ai"],
|
|
1535
|
+
python: ["mem0ai"],
|
|
1536
|
+
envKeys: [/^MEM0_/]
|
|
1537
|
+
}
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
kind: "Vellum",
|
|
1541
|
+
label: "Vellum",
|
|
1542
|
+
mode: "in-cli",
|
|
1543
|
+
signals: {
|
|
1544
|
+
npm: ["vellum-ai"],
|
|
1545
|
+
python: ["vellum-ai"],
|
|
1546
|
+
envKeys: [/^VELLUM_/]
|
|
1547
|
+
}
|
|
1548
|
+
},
|
|
1549
|
+
{
|
|
1550
|
+
kind: "Helicone",
|
|
1551
|
+
label: "Helicone",
|
|
1552
|
+
mode: "in-cli",
|
|
1553
|
+
signals: {
|
|
1554
|
+
npm: ["@helicone/helpers"],
|
|
1555
|
+
envKeys: [/^HELICONE_/]
|
|
1556
|
+
}
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
kind: "OpenRouter",
|
|
1560
|
+
label: "OpenRouter",
|
|
1561
|
+
mode: "in-cli",
|
|
1562
|
+
signals: {
|
|
1563
|
+
npm: ["@openrouter/ai-sdk-provider"],
|
|
1564
|
+
envKeys: [/^OPENROUTER_/]
|
|
1565
|
+
}
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
kind: "Browserbase",
|
|
1569
|
+
label: "Browserbase",
|
|
1570
|
+
mode: "in-cli",
|
|
1571
|
+
signals: {
|
|
1572
|
+
npm: ["@browserbasehq/sdk", "@browserbasehq/stagehand"],
|
|
1573
|
+
python: ["browserbase"],
|
|
1574
|
+
envKeys: [/^BROWSERBASE_/]
|
|
1575
|
+
}
|
|
1576
|
+
},
|
|
1577
|
+
{
|
|
1578
|
+
kind: "E2B",
|
|
1579
|
+
label: "E2B",
|
|
1580
|
+
mode: "in-cli",
|
|
1581
|
+
signals: {
|
|
1582
|
+
npm: ["e2b", "@e2b/code-interpreter"],
|
|
1583
|
+
python: ["e2b", "e2b-code-interpreter"],
|
|
1584
|
+
envKeys: [/^E2B_/]
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
kind: "WeightsAndBiases",
|
|
1589
|
+
label: "Weights & Biases",
|
|
1590
|
+
mode: "in-cli",
|
|
1591
|
+
signals: {
|
|
1592
|
+
python: ["wandb"],
|
|
1593
|
+
envKeys: [/^WANDB_/]
|
|
1594
|
+
}
|
|
1595
|
+
},
|
|
1596
|
+
{
|
|
1597
|
+
kind: "LlamaCloud",
|
|
1598
|
+
label: "LlamaCloud",
|
|
1599
|
+
mode: "in-cli",
|
|
1600
|
+
signals: {
|
|
1601
|
+
npm: ["llama-cloud-services"],
|
|
1602
|
+
python: ["llama-cloud-services"],
|
|
1603
|
+
envKeys: [/^LLAMA_CLOUD_/]
|
|
1604
|
+
}
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
kind: "Zep",
|
|
1608
|
+
label: "Zep",
|
|
1609
|
+
mode: "in-cli",
|
|
1610
|
+
signals: {
|
|
1611
|
+
npm: ["@getzep/zep-cloud"],
|
|
1612
|
+
python: ["zep-cloud"],
|
|
1613
|
+
envKeys: [/^ZEP_API_KEY$/]
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
];
|
|
1617
|
+
const OTHER_SOURCE_DETECTORS = [
|
|
1618
|
+
{
|
|
1619
|
+
kind: "Paystack",
|
|
1620
|
+
label: "Paystack",
|
|
1621
|
+
mode: "in-cli",
|
|
1622
|
+
signals: {
|
|
1623
|
+
python: ["paystackapi"],
|
|
1624
|
+
envKeys: [/^PAYSTACK_/]
|
|
1625
|
+
}
|
|
1626
|
+
},
|
|
1627
|
+
{
|
|
1628
|
+
kind: "Orb",
|
|
1629
|
+
label: "Orb",
|
|
1630
|
+
mode: "in-cli",
|
|
1631
|
+
signals: {
|
|
1632
|
+
npm: ["orb-billing"],
|
|
1633
|
+
python: ["orb-billing"],
|
|
1634
|
+
envKeys: [/^ORB_/]
|
|
1635
|
+
}
|
|
1636
|
+
},
|
|
1637
|
+
{
|
|
1638
|
+
kind: "Lago",
|
|
1639
|
+
label: "Lago",
|
|
1640
|
+
mode: "in-cli",
|
|
1641
|
+
signals: {
|
|
1642
|
+
npm: ["lago-javascript-client"],
|
|
1643
|
+
python: ["lago-python-client"],
|
|
1644
|
+
envKeys: [/^LAGO_/]
|
|
1645
|
+
}
|
|
1646
|
+
},
|
|
1647
|
+
{
|
|
1648
|
+
kind: "Zuora",
|
|
1649
|
+
label: "Zuora",
|
|
1650
|
+
mode: "in-cli",
|
|
1651
|
+
signals: { envKeys: [/^ZUORA_/] }
|
|
1652
|
+
},
|
|
1653
|
+
{
|
|
1654
|
+
kind: "Stigg",
|
|
1655
|
+
label: "Stigg",
|
|
1656
|
+
mode: "in-cli",
|
|
1657
|
+
signals: {
|
|
1658
|
+
npm: ["@stigg/node-server-sdk"],
|
|
1659
|
+
envKeys: [/^STIGG_/]
|
|
1660
|
+
}
|
|
1661
|
+
},
|
|
1662
|
+
{
|
|
1663
|
+
kind: "Ramp",
|
|
1664
|
+
label: "Ramp",
|
|
1665
|
+
mode: "in-cli",
|
|
1666
|
+
signals: { envKeys: [/^RAMP_/] }
|
|
1667
|
+
},
|
|
1668
|
+
{
|
|
1669
|
+
kind: "Brex",
|
|
1670
|
+
label: "Brex",
|
|
1671
|
+
mode: "in-cli",
|
|
1672
|
+
signals: { envKeys: [/^BREX_/] }
|
|
1673
|
+
},
|
|
1674
|
+
{
|
|
1675
|
+
kind: "Tremendous",
|
|
1676
|
+
label: "Tremendous",
|
|
1677
|
+
mode: "in-cli",
|
|
1678
|
+
signals: { envKeys: [/^TREMENDOUS_/] }
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
kind: "Maxio",
|
|
1682
|
+
label: "Maxio",
|
|
1683
|
+
mode: "in-cli",
|
|
1684
|
+
signals: { envKeys: [/^MAXIO_/] }
|
|
1685
|
+
},
|
|
1686
|
+
{
|
|
1687
|
+
kind: "Chargify",
|
|
1688
|
+
label: "Chargify",
|
|
1689
|
+
mode: "in-cli",
|
|
1690
|
+
signals: { envKeys: [/^CHARGIFY_/] }
|
|
1691
|
+
},
|
|
1692
|
+
{
|
|
1693
|
+
kind: "Invoiced",
|
|
1694
|
+
label: "Invoiced",
|
|
1695
|
+
mode: "in-cli",
|
|
1696
|
+
signals: {
|
|
1697
|
+
npm: ["invoiced"],
|
|
1698
|
+
python: ["invoiced"],
|
|
1699
|
+
envKeys: [/^INVOICED_/]
|
|
1700
|
+
}
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
kind: "LemonSqueezy",
|
|
1704
|
+
label: "Lemon Squeezy",
|
|
1705
|
+
mode: "in-cli",
|
|
1706
|
+
signals: {
|
|
1707
|
+
npm: ["@lemonsqueezy/lemonsqueezy.js"],
|
|
1708
|
+
envKeys: [/^LEMONSQUEEZY_/, /^LEMON_SQUEEZY_/]
|
|
1709
|
+
}
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
kind: "Razorpay",
|
|
1713
|
+
label: "Razorpay",
|
|
1714
|
+
mode: "in-cli",
|
|
1715
|
+
signals: {
|
|
1716
|
+
npm: ["razorpay"],
|
|
1717
|
+
python: ["razorpay"],
|
|
1718
|
+
ruby: ["razorpay"],
|
|
1719
|
+
envKeys: [/^RAZORPAY_/]
|
|
1720
|
+
}
|
|
1721
|
+
},
|
|
1722
|
+
{
|
|
1723
|
+
kind: "Adyen",
|
|
1724
|
+
label: "Adyen",
|
|
1725
|
+
mode: "in-cli",
|
|
1726
|
+
signals: {
|
|
1727
|
+
npm: ["@adyen/api-library"],
|
|
1728
|
+
python: ["adyen"],
|
|
1729
|
+
envKeys: [/^ADYEN_/]
|
|
1730
|
+
}
|
|
1731
|
+
},
|
|
1732
|
+
{
|
|
1733
|
+
kind: "ChartMogul",
|
|
1734
|
+
label: "ChartMogul",
|
|
1735
|
+
mode: "in-cli",
|
|
1736
|
+
signals: {
|
|
1737
|
+
npm: ["chartmogul-node"],
|
|
1738
|
+
python: ["chartmogul"],
|
|
1739
|
+
envKeys: [/^CHARTMOGUL_/]
|
|
1740
|
+
}
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
kind: "Airwallex",
|
|
1744
|
+
label: "Airwallex",
|
|
1745
|
+
mode: "in-cli",
|
|
1746
|
+
signals: {
|
|
1747
|
+
npm: ["airwallex-payment-elements"],
|
|
1748
|
+
envKeys: [/^AIRWALLEX_/]
|
|
1749
|
+
}
|
|
1750
|
+
},
|
|
1751
|
+
{
|
|
1752
|
+
kind: "Flutterwave",
|
|
1753
|
+
label: "Flutterwave",
|
|
1754
|
+
mode: "in-cli",
|
|
1755
|
+
signals: {
|
|
1756
|
+
npm: ["flutterwave-node-v3"],
|
|
1757
|
+
python: ["flutterwave"],
|
|
1758
|
+
envKeys: [/^FLUTTERWAVE_/, /^FLW_(PUBLIC|SECRET)_KEY$/]
|
|
1759
|
+
}
|
|
1760
|
+
},
|
|
1761
|
+
{
|
|
1762
|
+
kind: "DodoPayments",
|
|
1763
|
+
label: "Dodo Payments",
|
|
1764
|
+
mode: "in-cli",
|
|
1765
|
+
signals: {
|
|
1766
|
+
npm: ["dodopayments"],
|
|
1767
|
+
python: ["dodopayments"],
|
|
1768
|
+
envKeys: [/^DODO_PAYMENTS_/]
|
|
1769
|
+
}
|
|
1770
|
+
},
|
|
1771
|
+
{
|
|
1772
|
+
kind: "Whop",
|
|
1773
|
+
label: "Whop",
|
|
1774
|
+
mode: "in-cli",
|
|
1775
|
+
signals: {
|
|
1776
|
+
npm: ["@whop/api", "@whop-apps/sdk"],
|
|
1777
|
+
envKeys: [/^WHOP_/]
|
|
1778
|
+
}
|
|
1779
|
+
},
|
|
1780
|
+
{
|
|
1781
|
+
kind: "SparkPost",
|
|
1782
|
+
label: "SparkPost",
|
|
1783
|
+
mode: "in-cli",
|
|
1784
|
+
signals: {
|
|
1785
|
+
npm: ["sparkpost"],
|
|
1786
|
+
python: ["sparkpost"],
|
|
1787
|
+
envKeys: [/^SPARKPOST_/]
|
|
1788
|
+
}
|
|
1789
|
+
},
|
|
1790
|
+
{
|
|
1791
|
+
kind: "MailerSend",
|
|
1792
|
+
label: "MailerSend",
|
|
1793
|
+
mode: "in-cli",
|
|
1794
|
+
signals: {
|
|
1795
|
+
npm: ["mailersend"],
|
|
1796
|
+
python: ["mailersend"],
|
|
1797
|
+
envKeys: [/^MAILERSEND_/]
|
|
1798
|
+
}
|
|
1799
|
+
},
|
|
1800
|
+
{
|
|
1801
|
+
kind: "Iterable",
|
|
1802
|
+
label: "Iterable",
|
|
1803
|
+
mode: "in-cli",
|
|
1804
|
+
signals: { envKeys: [/^ITERABLE_/] }
|
|
1805
|
+
},
|
|
1806
|
+
{
|
|
1807
|
+
kind: "ConvertKit",
|
|
1808
|
+
label: "ConvertKit",
|
|
1809
|
+
mode: "in-cli",
|
|
1810
|
+
signals: { envKeys: [/^CONVERTKIT_/] }
|
|
1811
|
+
},
|
|
1812
|
+
{
|
|
1813
|
+
kind: "Drip",
|
|
1814
|
+
label: "Drip",
|
|
1815
|
+
mode: "in-cli",
|
|
1816
|
+
signals: {
|
|
1817
|
+
npm: ["drip-nodejs"],
|
|
1818
|
+
envKeys: [/^DRIP_/]
|
|
1819
|
+
}
|
|
1820
|
+
},
|
|
1821
|
+
{
|
|
1822
|
+
kind: "Omnisend",
|
|
1823
|
+
label: "Omnisend",
|
|
1824
|
+
mode: "in-cli",
|
|
1825
|
+
signals: { envKeys: [/^OMNISEND_/] }
|
|
1826
|
+
},
|
|
1827
|
+
{
|
|
1828
|
+
kind: "EmailOctopus",
|
|
1829
|
+
label: "EmailOctopus",
|
|
1830
|
+
mode: "in-cli",
|
|
1831
|
+
signals: { envKeys: [/^EMAILOCTOPUS_/, /^EMAIL_OCTOPUS_/] }
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
kind: "Elasticemail",
|
|
1835
|
+
label: "Elastic Email",
|
|
1836
|
+
mode: "in-cli",
|
|
1837
|
+
signals: { envKeys: [/^ELASTICEMAIL_/, /^ELASTIC_EMAIL_/] }
|
|
1838
|
+
},
|
|
1839
|
+
{
|
|
1840
|
+
kind: "CampaignMonitor",
|
|
1841
|
+
label: "Campaign Monitor",
|
|
1842
|
+
mode: "in-cli",
|
|
1843
|
+
signals: {
|
|
1844
|
+
npm: ["createsend-node"],
|
|
1845
|
+
envKeys: [/^CAMPAIGN_MONITOR_/, /^CREATESEND_/]
|
|
1846
|
+
}
|
|
1847
|
+
},
|
|
1848
|
+
{
|
|
1849
|
+
kind: "Lemlist",
|
|
1850
|
+
label: "Lemlist",
|
|
1851
|
+
mode: "in-cli",
|
|
1852
|
+
signals: { envKeys: [/^LEMLIST_/] }
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
kind: "Loops",
|
|
1856
|
+
label: "Loops",
|
|
1857
|
+
mode: "in-cli",
|
|
1858
|
+
signals: {
|
|
1859
|
+
npm: ["loops"],
|
|
1860
|
+
envKeys: [/^LOOPS_/]
|
|
1861
|
+
}
|
|
1862
|
+
},
|
|
1863
|
+
{
|
|
1864
|
+
kind: "Knock",
|
|
1865
|
+
label: "Knock",
|
|
1866
|
+
mode: "in-cli",
|
|
1867
|
+
signals: {
|
|
1868
|
+
npm: ["@knocklabs/node", "@knocklabs/client"],
|
|
1869
|
+
python: ["knockapi"],
|
|
1870
|
+
envKeys: [/^KNOCK_/]
|
|
1871
|
+
}
|
|
1872
|
+
},
|
|
1873
|
+
{
|
|
1874
|
+
kind: "AwsSes",
|
|
1875
|
+
label: "Amazon SES",
|
|
1876
|
+
mode: "in-cli",
|
|
1877
|
+
signals: {
|
|
1878
|
+
npm: ["@aws-sdk/client-ses", "@aws-sdk/client-sesv2"],
|
|
1879
|
+
envKeys: [/^AWS_SES_/]
|
|
1880
|
+
}
|
|
1881
|
+
},
|
|
1882
|
+
{
|
|
1883
|
+
kind: "Beehiiv",
|
|
1884
|
+
label: "beehiiv",
|
|
1885
|
+
mode: "in-cli",
|
|
1886
|
+
signals: { envKeys: [/^BEEHIIV_/] }
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
kind: "Mailtrap",
|
|
1890
|
+
label: "Mailtrap",
|
|
1891
|
+
mode: "in-cli",
|
|
1892
|
+
signals: {
|
|
1893
|
+
npm: ["mailtrap"],
|
|
1894
|
+
python: ["mailtrap"],
|
|
1895
|
+
envKeys: [/^MAILTRAP_/]
|
|
1896
|
+
}
|
|
1897
|
+
},
|
|
1898
|
+
{
|
|
1899
|
+
kind: "Plunk",
|
|
1900
|
+
label: "Plunk",
|
|
1901
|
+
mode: "in-cli",
|
|
1902
|
+
signals: {
|
|
1903
|
+
npm: ["@plunk/node"],
|
|
1904
|
+
envKeys: [/^PLUNK_/]
|
|
1905
|
+
}
|
|
1906
|
+
},
|
|
1907
|
+
{
|
|
1908
|
+
kind: "Ortto",
|
|
1909
|
+
label: "Ortto",
|
|
1910
|
+
mode: "in-cli",
|
|
1911
|
+
signals: { envKeys: [/^ORTTO_/] }
|
|
1912
|
+
},
|
|
1913
|
+
{
|
|
1914
|
+
kind: "Postscript",
|
|
1915
|
+
label: "Postscript",
|
|
1916
|
+
mode: "in-cli",
|
|
1917
|
+
signals: { envKeys: [/^POSTSCRIPT_/] }
|
|
1918
|
+
},
|
|
1919
|
+
{
|
|
1920
|
+
kind: "Marketo",
|
|
1921
|
+
label: "Marketo",
|
|
1922
|
+
mode: "in-cli",
|
|
1923
|
+
signals: {
|
|
1924
|
+
python: ["marketorestpython"],
|
|
1925
|
+
envKeys: [/^MARKETO_/]
|
|
1926
|
+
}
|
|
1927
|
+
},
|
|
1928
|
+
{
|
|
1929
|
+
kind: "Lob",
|
|
1930
|
+
label: "Lob",
|
|
1931
|
+
mode: "in-cli",
|
|
1932
|
+
signals: {
|
|
1933
|
+
npm: ["lob"],
|
|
1934
|
+
python: ["lob"],
|
|
1935
|
+
envKeys: [/^LOB_API_KEY$/]
|
|
1936
|
+
}
|
|
1937
|
+
},
|
|
1938
|
+
{
|
|
1939
|
+
kind: "Segment",
|
|
1940
|
+
label: "Segment",
|
|
1941
|
+
mode: "in-cli",
|
|
1942
|
+
signals: {
|
|
1943
|
+
npm: ["@segment/analytics-node", "analytics-node"],
|
|
1944
|
+
python: ["segment-analytics-python", "analytics-python"],
|
|
1945
|
+
envKeys: [/^SEGMENT_/]
|
|
1946
|
+
}
|
|
1947
|
+
},
|
|
1948
|
+
{
|
|
1949
|
+
kind: "Snowplow",
|
|
1950
|
+
label: "Snowplow Analytics",
|
|
1951
|
+
mode: "in-cli",
|
|
1952
|
+
signals: {
|
|
1953
|
+
npm: ["@snowplow/node-tracker"],
|
|
1954
|
+
python: ["snowplow-tracker"],
|
|
1955
|
+
envKeys: [/^SNOWPLOW_/]
|
|
1956
|
+
}
|
|
1957
|
+
},
|
|
1958
|
+
{
|
|
1959
|
+
kind: "Matomo",
|
|
1960
|
+
label: "Matomo",
|
|
1961
|
+
mode: "in-cli",
|
|
1962
|
+
signals: {
|
|
1963
|
+
npm: ["matomo-tracker"],
|
|
1964
|
+
envKeys: [/^MATOMO_/]
|
|
1965
|
+
}
|
|
1966
|
+
},
|
|
1967
|
+
{
|
|
1968
|
+
kind: "Plausible",
|
|
1969
|
+
label: "Plausible",
|
|
1970
|
+
mode: "in-cli",
|
|
1971
|
+
signals: {
|
|
1972
|
+
npm: ["plausible-tracker"],
|
|
1973
|
+
envKeys: [/^PLAUSIBLE_/]
|
|
1974
|
+
}
|
|
1975
|
+
},
|
|
1976
|
+
{
|
|
1977
|
+
kind: "ConfigCat",
|
|
1978
|
+
label: "ConfigCat",
|
|
1979
|
+
mode: "in-cli",
|
|
1980
|
+
signals: {
|
|
1981
|
+
npm: ["configcat-node", "configcat-js"],
|
|
1982
|
+
python: ["configcat-client"],
|
|
1983
|
+
envKeys: [/^CONFIGCAT_/]
|
|
1984
|
+
}
|
|
1985
|
+
},
|
|
1986
|
+
{
|
|
1987
|
+
kind: "Flagsmith",
|
|
1988
|
+
label: "Flagsmith",
|
|
1989
|
+
mode: "in-cli",
|
|
1990
|
+
signals: {
|
|
1991
|
+
npm: ["flagsmith-nodejs", "flagsmith"],
|
|
1992
|
+
python: ["flagsmith"],
|
|
1993
|
+
envKeys: [/^FLAGSMITH_/]
|
|
1994
|
+
}
|
|
1995
|
+
},
|
|
1996
|
+
{
|
|
1997
|
+
kind: "Unleash",
|
|
1998
|
+
label: "Unleash",
|
|
1999
|
+
mode: "in-cli",
|
|
2000
|
+
signals: {
|
|
2001
|
+
npm: ["unleash-client"],
|
|
2002
|
+
python: ["unleashclient"],
|
|
2003
|
+
envKeys: [/^UNLEASH_/]
|
|
2004
|
+
}
|
|
2005
|
+
},
|
|
2006
|
+
{
|
|
2007
|
+
kind: "SplitIo",
|
|
2008
|
+
label: "Split.io",
|
|
2009
|
+
mode: "in-cli",
|
|
2010
|
+
signals: {
|
|
2011
|
+
npm: ["@splitsoftware/splitio"],
|
|
2012
|
+
python: ["splitio-client"],
|
|
2013
|
+
envKeys: [/^SPLITIO_/, /^SPLIT_IO_/]
|
|
2014
|
+
}
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
kind: "Stytch",
|
|
2018
|
+
label: "Stytch",
|
|
2019
|
+
mode: "in-cli",
|
|
2020
|
+
signals: {
|
|
2021
|
+
npm: ["stytch"],
|
|
2022
|
+
python: ["stytch"],
|
|
2023
|
+
envKeys: [/^STYTCH_/]
|
|
2024
|
+
}
|
|
2025
|
+
},
|
|
2026
|
+
{
|
|
2027
|
+
kind: "Doppler",
|
|
2028
|
+
label: "Doppler",
|
|
2029
|
+
mode: "in-cli",
|
|
2030
|
+
signals: {
|
|
2031
|
+
npm: ["@dopplerhq/node-sdk"],
|
|
2032
|
+
envKeys: [/^DOPPLER_/]
|
|
2033
|
+
}
|
|
2034
|
+
},
|
|
2035
|
+
{
|
|
2036
|
+
kind: "Infisical",
|
|
2037
|
+
label: "Infisical",
|
|
2038
|
+
mode: "in-cli",
|
|
2039
|
+
signals: {
|
|
2040
|
+
npm: ["@infisical/sdk"],
|
|
2041
|
+
python: ["infisicalsdk"],
|
|
2042
|
+
envKeys: [/^INFISICAL_/]
|
|
2043
|
+
}
|
|
2044
|
+
},
|
|
2045
|
+
{
|
|
2046
|
+
kind: "OnePassword",
|
|
2047
|
+
label: "1Password",
|
|
2048
|
+
mode: "in-cli",
|
|
2049
|
+
signals: {
|
|
2050
|
+
npm: ["@1password/sdk"],
|
|
2051
|
+
python: ["onepassword-sdk"],
|
|
2052
|
+
envKeys: [/^ONEPASSWORD_/, /^OP_SERVICE_ACCOUNT_TOKEN$/]
|
|
2053
|
+
}
|
|
2054
|
+
},
|
|
2055
|
+
{
|
|
2056
|
+
kind: "Persona",
|
|
2057
|
+
label: "Persona",
|
|
2058
|
+
mode: "in-cli",
|
|
2059
|
+
signals: { envKeys: [/^PERSONA_/] }
|
|
2060
|
+
},
|
|
2061
|
+
{
|
|
2062
|
+
kind: "Svix",
|
|
2063
|
+
label: "Svix",
|
|
2064
|
+
mode: "in-cli",
|
|
2065
|
+
signals: {
|
|
2066
|
+
npm: ["svix"],
|
|
2067
|
+
python: ["svix"],
|
|
2068
|
+
envKeys: [/^SVIX_/]
|
|
2069
|
+
}
|
|
2070
|
+
},
|
|
2071
|
+
{
|
|
2072
|
+
kind: "Mux",
|
|
2073
|
+
label: "Mux",
|
|
2074
|
+
mode: "in-cli",
|
|
2075
|
+
signals: {
|
|
2076
|
+
npm: ["@mux/mux-node"],
|
|
2077
|
+
python: ["mux-python"],
|
|
2078
|
+
envKeys: [/^MUX_/]
|
|
2079
|
+
}
|
|
2080
|
+
},
|
|
2081
|
+
{
|
|
2082
|
+
kind: "Vapi",
|
|
2083
|
+
label: "Vapi",
|
|
2084
|
+
mode: "in-cli",
|
|
2085
|
+
signals: {
|
|
2086
|
+
npm: ["@vapi-ai/server-sdk"],
|
|
2087
|
+
python: ["vapi-server-sdk"],
|
|
2088
|
+
envKeys: [/^VAPI_/]
|
|
2089
|
+
}
|
|
2090
|
+
},
|
|
2091
|
+
{
|
|
2092
|
+
kind: "Aircall",
|
|
2093
|
+
label: "Aircall",
|
|
2094
|
+
mode: "in-cli",
|
|
2095
|
+
signals: { envKeys: [/^AIRCALL_/] }
|
|
2096
|
+
},
|
|
2097
|
+
{
|
|
2098
|
+
kind: "JustCall",
|
|
2099
|
+
label: "JustCall",
|
|
2100
|
+
mode: "in-cli",
|
|
2101
|
+
signals: { envKeys: [/^JUSTCALL_/] }
|
|
2102
|
+
},
|
|
2103
|
+
{
|
|
2104
|
+
kind: "Telnyx",
|
|
2105
|
+
label: "Telnyx",
|
|
2106
|
+
mode: "in-cli",
|
|
2107
|
+
signals: {
|
|
2108
|
+
npm: ["telnyx"],
|
|
2109
|
+
python: ["telnyx"],
|
|
2110
|
+
envKeys: [/^TELNYX_/]
|
|
2111
|
+
}
|
|
2112
|
+
},
|
|
2113
|
+
{
|
|
2114
|
+
kind: "Plivo",
|
|
2115
|
+
label: "Plivo",
|
|
2116
|
+
mode: "in-cli",
|
|
2117
|
+
signals: {
|
|
2118
|
+
npm: ["plivo"],
|
|
2119
|
+
python: ["plivo"],
|
|
2120
|
+
envKeys: [/^PLIVO_/]
|
|
2121
|
+
}
|
|
2122
|
+
},
|
|
2123
|
+
{
|
|
2124
|
+
kind: "Courier",
|
|
2125
|
+
label: "Courier",
|
|
2126
|
+
mode: "in-cli",
|
|
2127
|
+
signals: {
|
|
2128
|
+
npm: ["@trycourier/courier"],
|
|
2129
|
+
python: ["trycourier"],
|
|
2130
|
+
envKeys: [/^COURIER_AUTH_TOKEN$/]
|
|
2131
|
+
}
|
|
2132
|
+
},
|
|
2133
|
+
{
|
|
2134
|
+
kind: "WooCommerce",
|
|
2135
|
+
label: "WooCommerce",
|
|
2136
|
+
mode: "in-cli",
|
|
2137
|
+
signals: {
|
|
2138
|
+
npm: ["@woocommerce/woocommerce-rest-api"],
|
|
2139
|
+
python: ["woocommerce"],
|
|
2140
|
+
envKeys: [/^WOOCOMMERCE_/]
|
|
2141
|
+
}
|
|
2142
|
+
},
|
|
2143
|
+
{
|
|
2144
|
+
kind: "Recharge",
|
|
2145
|
+
label: "Recharge",
|
|
2146
|
+
mode: "in-cli",
|
|
2147
|
+
signals: { envKeys: [/^RECHARGE_/] }
|
|
2148
|
+
},
|
|
2149
|
+
{
|
|
2150
|
+
kind: "Commercetools",
|
|
2151
|
+
label: "commercetools",
|
|
2152
|
+
mode: "in-cli",
|
|
2153
|
+
signals: {
|
|
2154
|
+
npm: ["@commercetools/platform-sdk"],
|
|
2155
|
+
python: ["commercetools"],
|
|
2156
|
+
envKeys: [/^CTP_/, /^COMMERCETOOLS_/]
|
|
2157
|
+
}
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
kind: "Webflow",
|
|
2161
|
+
label: "Webflow",
|
|
2162
|
+
mode: "in-cli",
|
|
2163
|
+
signals: {
|
|
2164
|
+
npm: ["webflow-api"],
|
|
2165
|
+
envKeys: [/^WEBFLOW_/]
|
|
2166
|
+
}
|
|
2167
|
+
},
|
|
2168
|
+
{
|
|
2169
|
+
kind: "Easypost",
|
|
2170
|
+
label: "EasyPost",
|
|
2171
|
+
mode: "in-cli",
|
|
2172
|
+
signals: {
|
|
2173
|
+
npm: ["@easypost/api"],
|
|
2174
|
+
python: ["easypost"],
|
|
2175
|
+
envKeys: [/^EASYPOST_/]
|
|
2176
|
+
}
|
|
2177
|
+
},
|
|
2178
|
+
{
|
|
2179
|
+
kind: "Shippo",
|
|
2180
|
+
label: "Shippo",
|
|
2181
|
+
mode: "in-cli",
|
|
2182
|
+
signals: {
|
|
2183
|
+
npm: ["shippo"],
|
|
2184
|
+
python: ["shippo"],
|
|
2185
|
+
envKeys: [/^SHIPPO_/]
|
|
2186
|
+
}
|
|
2187
|
+
},
|
|
2188
|
+
{
|
|
2189
|
+
kind: "ShipStation",
|
|
2190
|
+
label: "ShipStation",
|
|
2191
|
+
mode: "in-cli",
|
|
2192
|
+
signals: { envKeys: [/^SHIPSTATION_/] }
|
|
2193
|
+
},
|
|
2194
|
+
{
|
|
2195
|
+
kind: "Squarespace",
|
|
2196
|
+
label: "Squarespace",
|
|
2197
|
+
mode: "in-cli",
|
|
2198
|
+
signals: { envKeys: [/^SQUARESPACE_/] }
|
|
2199
|
+
},
|
|
2200
|
+
{
|
|
2201
|
+
kind: "Printify",
|
|
2202
|
+
label: "Printify",
|
|
2203
|
+
mode: "in-cli",
|
|
2204
|
+
signals: { envKeys: [/^PRINTIFY_/] }
|
|
2205
|
+
},
|
|
2206
|
+
{
|
|
2207
|
+
kind: "BigCommerce",
|
|
2208
|
+
label: "BigCommerce",
|
|
2209
|
+
mode: "in-cli",
|
|
2210
|
+
signals: {
|
|
2211
|
+
npm: ["node-bigcommerce"],
|
|
2212
|
+
envKeys: [/^BIGCOMMERCE_/]
|
|
2213
|
+
}
|
|
2214
|
+
},
|
|
2215
|
+
{
|
|
2216
|
+
kind: "Freshdesk",
|
|
2217
|
+
label: "Freshdesk",
|
|
2218
|
+
mode: "in-cli",
|
|
2219
|
+
signals: {
|
|
2220
|
+
python: ["python-freshdesk"],
|
|
2221
|
+
envKeys: [/^FRESHDESK_/]
|
|
2222
|
+
}
|
|
2223
|
+
},
|
|
2224
|
+
{
|
|
2225
|
+
kind: "Front",
|
|
2226
|
+
label: "Front",
|
|
2227
|
+
mode: "in-cli",
|
|
2228
|
+
signals: { envKeys: [/^FRONTAPP_/, /^FRONT_API_/] }
|
|
2229
|
+
},
|
|
2230
|
+
{
|
|
2231
|
+
kind: "Gorgias",
|
|
2232
|
+
label: "Gorgias",
|
|
2233
|
+
mode: "in-cli",
|
|
2234
|
+
signals: { envKeys: [/^GORGIAS_/] }
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
kind: "Chatwoot",
|
|
2238
|
+
label: "Chatwoot",
|
|
2239
|
+
mode: "in-cli",
|
|
2240
|
+
signals: { envKeys: [/^CHATWOOT_/] }
|
|
2241
|
+
},
|
|
2242
|
+
{
|
|
2243
|
+
kind: "Kustomer",
|
|
2244
|
+
label: "Kustomer",
|
|
2245
|
+
mode: "in-cli",
|
|
2246
|
+
signals: { envKeys: [/^KUSTOMER_/] }
|
|
2247
|
+
},
|
|
2248
|
+
{
|
|
2249
|
+
kind: "Plain",
|
|
2250
|
+
label: "Plain",
|
|
2251
|
+
mode: "in-cli",
|
|
2252
|
+
signals: {
|
|
2253
|
+
npm: ["@team-plain/typescript-sdk"],
|
|
2254
|
+
envKeys: [/^PLAIN_/]
|
|
2255
|
+
}
|
|
2256
|
+
},
|
|
2257
|
+
{
|
|
2258
|
+
kind: "Canny",
|
|
2259
|
+
label: "Canny",
|
|
2260
|
+
mode: "in-cli",
|
|
2261
|
+
signals: { envKeys: [/^CANNY_/] }
|
|
2262
|
+
},
|
|
2263
|
+
{
|
|
2264
|
+
kind: "Pylon",
|
|
2265
|
+
label: "Pylon",
|
|
2266
|
+
mode: "in-cli",
|
|
2267
|
+
signals: { envKeys: [/^PYLON_/] }
|
|
2268
|
+
},
|
|
2269
|
+
{
|
|
2270
|
+
kind: "GitLab",
|
|
2271
|
+
label: "GitLab",
|
|
2272
|
+
mode: "in-cli",
|
|
2273
|
+
signals: {
|
|
2274
|
+
npm: ["@gitbeaker/rest"],
|
|
2275
|
+
python: ["python-gitlab"],
|
|
2276
|
+
ruby: ["gitlab"],
|
|
2277
|
+
envKeys: [/^GITLAB_/]
|
|
2278
|
+
}
|
|
2279
|
+
},
|
|
2280
|
+
{
|
|
2281
|
+
kind: "Bitbucket",
|
|
2282
|
+
label: "Atlassian Bitbucket Cloud",
|
|
2283
|
+
mode: "in-cli",
|
|
2284
|
+
signals: {
|
|
2285
|
+
npm: ["bitbucket"],
|
|
2286
|
+
envKeys: [/^BITBUCKET_/]
|
|
2287
|
+
}
|
|
2288
|
+
},
|
|
2289
|
+
{
|
|
2290
|
+
kind: "Gitea",
|
|
2291
|
+
label: "Gitea",
|
|
2292
|
+
mode: "in-cli",
|
|
2293
|
+
signals: {
|
|
2294
|
+
npm: ["gitea-js"],
|
|
2295
|
+
envKeys: [/^GITEA_/]
|
|
2296
|
+
}
|
|
2297
|
+
},
|
|
2298
|
+
{
|
|
2299
|
+
kind: "Datadog",
|
|
2300
|
+
label: "Datadog",
|
|
2301
|
+
mode: "in-cli",
|
|
2302
|
+
signals: {
|
|
2303
|
+
npm: ["@datadog/datadog-api-client", "dd-trace"],
|
|
2304
|
+
python: ["datadog", "datadog-api-client"],
|
|
2305
|
+
ruby: ["dogapi"],
|
|
2306
|
+
envKeys: [
|
|
2307
|
+
/^DATADOG_/,
|
|
2308
|
+
/^DD_API_KEY$/,
|
|
2309
|
+
/^DD_APP_KEY$/
|
|
2310
|
+
]
|
|
2311
|
+
}
|
|
2312
|
+
},
|
|
2313
|
+
{
|
|
2314
|
+
kind: "NewRelic",
|
|
2315
|
+
label: "New Relic",
|
|
2316
|
+
mode: "in-cli",
|
|
2317
|
+
signals: {
|
|
2318
|
+
npm: ["newrelic"],
|
|
2319
|
+
python: ["newrelic"],
|
|
2320
|
+
ruby: ["newrelic_rpm"],
|
|
2321
|
+
envKeys: [/^NEW_RELIC_/, /^NEWRELIC_/]
|
|
2322
|
+
}
|
|
2323
|
+
},
|
|
2324
|
+
{
|
|
2325
|
+
kind: "Cloudflare",
|
|
2326
|
+
label: "Cloudflare",
|
|
2327
|
+
mode: "in-cli",
|
|
2328
|
+
signals: {
|
|
2329
|
+
npm: ["cloudflare"],
|
|
2330
|
+
python: ["cloudflare"],
|
|
2331
|
+
envKeys: [/^CLOUDFLARE_/, /^CF_API_TOKEN$/]
|
|
2332
|
+
}
|
|
2333
|
+
},
|
|
2334
|
+
{
|
|
2335
|
+
kind: "Elasticsearch",
|
|
2336
|
+
label: "Elasticsearch",
|
|
2337
|
+
mode: "in-cli",
|
|
2338
|
+
signals: {
|
|
2339
|
+
npm: ["@elastic/elasticsearch"],
|
|
2340
|
+
python: ["elasticsearch"],
|
|
2341
|
+
ruby: ["elasticsearch"],
|
|
2342
|
+
envKeys: [/^ELASTICSEARCH_/]
|
|
2343
|
+
}
|
|
2344
|
+
},
|
|
2345
|
+
{
|
|
2346
|
+
kind: "Snyk",
|
|
2347
|
+
label: "Snyk",
|
|
2348
|
+
mode: "in-cli",
|
|
2349
|
+
signals: {
|
|
2350
|
+
npm: ["snyk"],
|
|
2351
|
+
python: ["pysnyk"],
|
|
2352
|
+
envKeys: [/^SNYK_/]
|
|
2353
|
+
}
|
|
2354
|
+
},
|
|
2355
|
+
{
|
|
2356
|
+
kind: "PagerDuty",
|
|
2357
|
+
label: "PagerDuty",
|
|
2358
|
+
mode: "in-cli",
|
|
2359
|
+
signals: {
|
|
2360
|
+
npm: ["@pagerduty/pdjs"],
|
|
2361
|
+
python: ["pdpyras"],
|
|
2362
|
+
envKeys: [/^PAGERDUTY_/, /^PD_API_KEY$/]
|
|
2363
|
+
}
|
|
2364
|
+
},
|
|
2365
|
+
{
|
|
2366
|
+
kind: "Opsgenie",
|
|
2367
|
+
label: "Opsgenie",
|
|
2368
|
+
mode: "in-cli",
|
|
2369
|
+
signals: { envKeys: [/^OPSGENIE_/] }
|
|
2370
|
+
},
|
|
2371
|
+
{
|
|
2372
|
+
kind: "IncidentIo",
|
|
2373
|
+
label: "incident.io",
|
|
2374
|
+
mode: "in-cli",
|
|
2375
|
+
signals: { envKeys: [/^INCIDENT_IO_/, /^INCIDENTIO_/] }
|
|
2376
|
+
},
|
|
2377
|
+
{
|
|
2378
|
+
kind: "Bugsnag",
|
|
2379
|
+
label: "Bugsnag",
|
|
2380
|
+
mode: "in-cli",
|
|
2381
|
+
signals: {
|
|
2382
|
+
npm: ["@bugsnag/js"],
|
|
2383
|
+
python: ["bugsnag"],
|
|
2384
|
+
ruby: ["bugsnag"],
|
|
2385
|
+
envKeys: [/^BUGSNAG_/]
|
|
2386
|
+
}
|
|
2387
|
+
},
|
|
2388
|
+
{
|
|
2389
|
+
kind: "Honeybadger",
|
|
2390
|
+
label: "Honeybadger",
|
|
2391
|
+
mode: "in-cli",
|
|
2392
|
+
signals: {
|
|
2393
|
+
npm: ["@honeybadger-io/js"],
|
|
2394
|
+
python: ["honeybadger"],
|
|
2395
|
+
ruby: ["honeybadger"],
|
|
2396
|
+
envKeys: [/^HONEYBADGER_/]
|
|
2397
|
+
}
|
|
2398
|
+
},
|
|
2399
|
+
{
|
|
2400
|
+
kind: "Raygun",
|
|
2401
|
+
label: "Raygun",
|
|
2402
|
+
mode: "in-cli",
|
|
2403
|
+
signals: {
|
|
2404
|
+
npm: ["raygun"],
|
|
2405
|
+
python: ["raygun4py"],
|
|
2406
|
+
ruby: ["raygun4ruby"],
|
|
2407
|
+
envKeys: [/^RAYGUN_/]
|
|
2408
|
+
}
|
|
2409
|
+
},
|
|
2410
|
+
{
|
|
2411
|
+
kind: "CircleCI",
|
|
2412
|
+
label: "CircleCI",
|
|
2413
|
+
mode: "in-cli",
|
|
2414
|
+
signals: { envKeys: [/^CIRCLECI_/, /^CIRCLE_TOKEN$/] }
|
|
2415
|
+
},
|
|
2416
|
+
{
|
|
2417
|
+
kind: "Upstash",
|
|
2418
|
+
label: "Upstash",
|
|
2419
|
+
mode: "in-cli",
|
|
2420
|
+
signals: {
|
|
2421
|
+
npm: ["@upstash/redis"],
|
|
2422
|
+
python: ["upstash-redis"],
|
|
2423
|
+
envKeys: [/^UPSTASH_/]
|
|
2424
|
+
}
|
|
2425
|
+
},
|
|
2426
|
+
{
|
|
2427
|
+
kind: "Vercel",
|
|
2428
|
+
label: "Vercel",
|
|
2429
|
+
mode: "in-cli",
|
|
2430
|
+
signals: {
|
|
2431
|
+
npm: ["@vercel/sdk"],
|
|
2432
|
+
envKeys: [/^VERCEL_API_TOKEN$/]
|
|
2433
|
+
}
|
|
2434
|
+
},
|
|
2435
|
+
{
|
|
2436
|
+
kind: "Algolia",
|
|
2437
|
+
label: "Algolia",
|
|
2438
|
+
mode: "in-cli",
|
|
2439
|
+
signals: {
|
|
2440
|
+
npm: ["algoliasearch"],
|
|
2441
|
+
python: ["algoliasearch"],
|
|
2442
|
+
ruby: ["algolia"],
|
|
2443
|
+
envKeys: [/^ALGOLIA_/]
|
|
2444
|
+
}
|
|
2445
|
+
},
|
|
2446
|
+
{
|
|
2447
|
+
kind: "Inngest",
|
|
2448
|
+
label: "Inngest",
|
|
2449
|
+
mode: "in-cli",
|
|
2450
|
+
signals: {
|
|
2451
|
+
npm: ["inngest"],
|
|
2452
|
+
python: ["inngest"],
|
|
2453
|
+
envKeys: [/^INNGEST_/]
|
|
2454
|
+
}
|
|
2455
|
+
},
|
|
2456
|
+
{
|
|
2457
|
+
kind: "TriggerDev",
|
|
2458
|
+
label: "Trigger.dev",
|
|
2459
|
+
mode: "in-cli",
|
|
2460
|
+
signals: {
|
|
2461
|
+
npm: ["@trigger.dev/sdk"],
|
|
2462
|
+
envKeys: [/^TRIGGER_(SECRET_KEY|API_KEY|PROJECT_ID)$/]
|
|
2463
|
+
}
|
|
2464
|
+
},
|
|
2465
|
+
{
|
|
2466
|
+
kind: "Ably",
|
|
2467
|
+
label: "Ably",
|
|
2468
|
+
mode: "in-cli",
|
|
2469
|
+
signals: {
|
|
2470
|
+
npm: ["ably"],
|
|
2471
|
+
python: ["ably"],
|
|
2472
|
+
envKeys: [/^ABLY_/]
|
|
2473
|
+
}
|
|
2474
|
+
},
|
|
2475
|
+
{
|
|
2476
|
+
kind: "Netlify",
|
|
2477
|
+
label: "Netlify",
|
|
2478
|
+
mode: "in-cli",
|
|
2479
|
+
signals: {
|
|
2480
|
+
npm: ["@netlify/functions"],
|
|
2481
|
+
envKeys: [/^NETLIFY_/]
|
|
2482
|
+
}
|
|
2483
|
+
},
|
|
2484
|
+
{
|
|
2485
|
+
kind: "Railway",
|
|
2486
|
+
label: "Railway",
|
|
2487
|
+
mode: "in-cli",
|
|
2488
|
+
signals: { envKeys: [/^RAILWAY_/] }
|
|
2489
|
+
},
|
|
2490
|
+
{
|
|
2491
|
+
kind: "Render",
|
|
2492
|
+
label: "Render",
|
|
2493
|
+
mode: "in-cli",
|
|
2494
|
+
signals: { envKeys: [/^RENDER_API_KEY$/] }
|
|
2495
|
+
},
|
|
2496
|
+
{
|
|
2497
|
+
kind: "FlyIo",
|
|
2498
|
+
label: "Fly.io",
|
|
2499
|
+
mode: "in-cli",
|
|
2500
|
+
signals: { envKeys: [/^FLY_API_TOKEN$/, /^FLY_APP_NAME$/] }
|
|
2501
|
+
},
|
|
2502
|
+
{
|
|
2503
|
+
kind: "Heroku",
|
|
2504
|
+
label: "Heroku",
|
|
2505
|
+
mode: "in-cli",
|
|
2506
|
+
signals: { envKeys: [/^HEROKU_/] }
|
|
2507
|
+
},
|
|
2508
|
+
{
|
|
2509
|
+
kind: "DigitalOcean",
|
|
2510
|
+
label: "DigitalOcean",
|
|
2511
|
+
mode: "in-cli",
|
|
2512
|
+
signals: { envKeys: [/^DIGITALOCEAN_/] }
|
|
2513
|
+
},
|
|
2514
|
+
{
|
|
2515
|
+
kind: "Grafana",
|
|
2516
|
+
label: "Grafana",
|
|
2517
|
+
mode: "in-cli",
|
|
2518
|
+
signals: {
|
|
2519
|
+
python: ["grafana-client"],
|
|
2520
|
+
envKeys: [/^GRAFANA_/]
|
|
2521
|
+
}
|
|
2522
|
+
},
|
|
2523
|
+
{
|
|
2524
|
+
kind: "BetterStack",
|
|
2525
|
+
label: "Better Stack",
|
|
2526
|
+
mode: "in-cli",
|
|
2527
|
+
signals: {
|
|
2528
|
+
npm: ["@logtail/node", "@logtail/js"],
|
|
2529
|
+
envKeys: [
|
|
2530
|
+
/^BETTERSTACK_/,
|
|
2531
|
+
/^BETTER_STACK_/,
|
|
2532
|
+
/^LOGTAIL_/
|
|
2533
|
+
]
|
|
2534
|
+
}
|
|
2535
|
+
},
|
|
2536
|
+
{
|
|
2537
|
+
kind: "Honeycomb",
|
|
2538
|
+
label: "Honeycomb",
|
|
2539
|
+
mode: "in-cli",
|
|
2540
|
+
signals: {
|
|
2541
|
+
npm: ["@honeycombio/opentelemetry-node", "honeycomb-beeline"],
|
|
2542
|
+
python: ["honeycomb-beeline", "libhoney"],
|
|
2543
|
+
envKeys: [/^HONEYCOMB_/]
|
|
2544
|
+
}
|
|
2545
|
+
},
|
|
2546
|
+
{
|
|
2547
|
+
kind: "Descope",
|
|
2548
|
+
label: "Descope",
|
|
2549
|
+
mode: "in-cli",
|
|
2550
|
+
signals: {
|
|
2551
|
+
npm: [
|
|
2552
|
+
"@descope/node-sdk",
|
|
2553
|
+
"@descope/nextjs-sdk",
|
|
2554
|
+
"@descope/react-sdk"
|
|
2555
|
+
],
|
|
2556
|
+
python: ["descope"],
|
|
2557
|
+
envKeys: [/^DESCOPE_/]
|
|
2558
|
+
}
|
|
2559
|
+
},
|
|
2560
|
+
{
|
|
2561
|
+
kind: "FusionAuth",
|
|
2562
|
+
label: "FusionAuth",
|
|
2563
|
+
mode: "in-cli",
|
|
2564
|
+
signals: {
|
|
2565
|
+
npm: ["@fusionauth/typescript-client"],
|
|
2566
|
+
python: ["fusionauth-client"],
|
|
2567
|
+
envKeys: [/^FUSIONAUTH_/, /^FUSION_AUTH_/]
|
|
2568
|
+
}
|
|
2569
|
+
},
|
|
2570
|
+
{
|
|
2571
|
+
kind: "Hookdeck",
|
|
2572
|
+
label: "Hookdeck",
|
|
2573
|
+
mode: "in-cli",
|
|
2574
|
+
signals: {
|
|
2575
|
+
npm: ["@hookdeck/sdk"],
|
|
2576
|
+
envKeys: [/^HOOKDECK_/]
|
|
2577
|
+
}
|
|
2578
|
+
},
|
|
2579
|
+
{
|
|
2580
|
+
kind: "N8n",
|
|
2581
|
+
label: "n8n",
|
|
2582
|
+
mode: "in-cli",
|
|
2583
|
+
signals: {
|
|
2584
|
+
npm: ["n8n"],
|
|
2585
|
+
envKeys: [/^N8N_/]
|
|
2586
|
+
}
|
|
2587
|
+
},
|
|
2588
|
+
{
|
|
2589
|
+
kind: "Dbt",
|
|
2590
|
+
label: "dbt",
|
|
2591
|
+
mode: "in-cli",
|
|
2592
|
+
signals: {
|
|
2593
|
+
python: [
|
|
2594
|
+
"dbt-core",
|
|
2595
|
+
"dbt-snowflake",
|
|
2596
|
+
"dbt-postgres",
|
|
2597
|
+
"dbt-bigquery",
|
|
2598
|
+
"dbt-redshift",
|
|
2599
|
+
"dbt-databricks"
|
|
2600
|
+
],
|
|
2601
|
+
envKeys: [/^DBT_/]
|
|
2602
|
+
}
|
|
2603
|
+
},
|
|
2604
|
+
{
|
|
2605
|
+
kind: "Fastly",
|
|
2606
|
+
label: "Fastly",
|
|
2607
|
+
mode: "in-cli",
|
|
2608
|
+
signals: {
|
|
2609
|
+
npm: ["fastly"],
|
|
2610
|
+
python: ["fastly"],
|
|
2611
|
+
envKeys: [/^FASTLY_/]
|
|
2612
|
+
}
|
|
2613
|
+
},
|
|
2614
|
+
{
|
|
2615
|
+
kind: "Bunny",
|
|
2616
|
+
label: "Bunny.net",
|
|
2617
|
+
mode: "in-cli",
|
|
2618
|
+
signals: { envKeys: [/^BUNNY_/] }
|
|
2619
|
+
},
|
|
2620
|
+
{
|
|
2621
|
+
kind: "Codecov",
|
|
2622
|
+
label: "Codecov",
|
|
2623
|
+
mode: "in-cli",
|
|
2624
|
+
signals: {
|
|
2625
|
+
npm: ["codecov"],
|
|
2626
|
+
envKeys: [/^CODECOV_/]
|
|
2627
|
+
}
|
|
2628
|
+
},
|
|
2629
|
+
{
|
|
2630
|
+
kind: "Sourcegraph",
|
|
2631
|
+
label: "Sourcegraph",
|
|
2632
|
+
mode: "in-cli",
|
|
2633
|
+
signals: { envKeys: [/^SOURCEGRAPH_/, /^SRC_ACCESS_TOKEN$/] }
|
|
2634
|
+
},
|
|
2635
|
+
{
|
|
2636
|
+
kind: "TemporalIO",
|
|
2637
|
+
label: "Temporal.io",
|
|
2638
|
+
mode: "in-cli",
|
|
2639
|
+
signals: {
|
|
2640
|
+
npm: ["@temporalio/client", "@temporalio/worker"],
|
|
2641
|
+
python: ["temporalio"],
|
|
2642
|
+
envKeys: [/^TEMPORAL_/]
|
|
2643
|
+
}
|
|
2644
|
+
},
|
|
2645
|
+
{
|
|
2646
|
+
kind: "Hatchet",
|
|
2647
|
+
label: "Hatchet",
|
|
2648
|
+
mode: "in-cli",
|
|
2649
|
+
signals: {
|
|
2650
|
+
npm: ["@hatchet-dev/typescript-sdk"],
|
|
2651
|
+
python: ["hatchet-sdk"],
|
|
2652
|
+
envKeys: [/^HATCHET_/]
|
|
2653
|
+
}
|
|
2654
|
+
},
|
|
2655
|
+
{
|
|
2656
|
+
kind: "Pipedrive",
|
|
2657
|
+
label: "Pipedrive",
|
|
2658
|
+
mode: "in-cli",
|
|
2659
|
+
signals: {
|
|
2660
|
+
npm: ["pipedrive"],
|
|
2661
|
+
envKeys: [/^PIPEDRIVE_/]
|
|
2662
|
+
}
|
|
2663
|
+
},
|
|
2664
|
+
{
|
|
2665
|
+
kind: "Close",
|
|
2666
|
+
label: "Close",
|
|
2667
|
+
mode: "in-cli",
|
|
2668
|
+
signals: {
|
|
2669
|
+
python: ["closeio"],
|
|
2670
|
+
envKeys: [/^CLOSE_API_KEY$/]
|
|
2671
|
+
}
|
|
2672
|
+
},
|
|
2673
|
+
{
|
|
2674
|
+
kind: "Copper",
|
|
2675
|
+
label: "Copper",
|
|
2676
|
+
mode: "in-cli",
|
|
2677
|
+
signals: { envKeys: [/^COPPER_/] }
|
|
2678
|
+
},
|
|
2679
|
+
{
|
|
2680
|
+
kind: "Insightly",
|
|
2681
|
+
label: "Insightly",
|
|
2682
|
+
mode: "in-cli",
|
|
2683
|
+
signals: { envKeys: [/^INSIGHTLY_/] }
|
|
2684
|
+
},
|
|
2685
|
+
{
|
|
2686
|
+
kind: "Salesflare",
|
|
2687
|
+
label: "Salesflare",
|
|
2688
|
+
mode: "in-cli",
|
|
2689
|
+
signals: { envKeys: [/^SALESFLARE_/] }
|
|
2690
|
+
},
|
|
2691
|
+
{
|
|
2692
|
+
kind: "SalesLoft",
|
|
2693
|
+
label: "Salesloft",
|
|
2694
|
+
mode: "in-cli",
|
|
2695
|
+
signals: { envKeys: [/^SALESLOFT_/] }
|
|
2696
|
+
},
|
|
2697
|
+
{
|
|
2698
|
+
kind: "Attio",
|
|
2699
|
+
label: "Attio",
|
|
2700
|
+
mode: "in-cli",
|
|
2701
|
+
signals: {
|
|
2702
|
+
npm: ["attio"],
|
|
2703
|
+
envKeys: [/^ATTIO_/]
|
|
2704
|
+
}
|
|
2705
|
+
},
|
|
2706
|
+
{
|
|
2707
|
+
kind: "ActiveCampaign",
|
|
2708
|
+
label: "ActiveCampaign",
|
|
2709
|
+
mode: "in-cli",
|
|
2710
|
+
signals: { envKeys: [/^ACTIVECAMPAIGN_/, /^ACTIVE_CAMPAIGN_/] }
|
|
2711
|
+
},
|
|
2712
|
+
{
|
|
2713
|
+
kind: "Vitally",
|
|
2714
|
+
label: "Vitally",
|
|
2715
|
+
mode: "in-cli",
|
|
2716
|
+
signals: { envKeys: [/^VITALLY_/] }
|
|
2717
|
+
},
|
|
2718
|
+
{
|
|
2719
|
+
kind: "Docusign",
|
|
2720
|
+
label: "DocuSign",
|
|
2721
|
+
mode: "in-cli",
|
|
2722
|
+
signals: {
|
|
2723
|
+
npm: ["docusign-esign"],
|
|
2724
|
+
python: ["docusign-esign"],
|
|
2725
|
+
envKeys: [/^DOCUSIGN_/]
|
|
2726
|
+
}
|
|
2727
|
+
},
|
|
2728
|
+
{
|
|
2729
|
+
kind: "PandaDoc",
|
|
2730
|
+
label: "PandaDoc",
|
|
2731
|
+
mode: "in-cli",
|
|
2732
|
+
signals: { envKeys: [/^PANDADOC_/] }
|
|
2733
|
+
},
|
|
2734
|
+
{
|
|
2735
|
+
kind: "Gong",
|
|
2736
|
+
label: "Gong",
|
|
2737
|
+
mode: "in-cli",
|
|
2738
|
+
signals: { envKeys: [/^GONG_/] }
|
|
2739
|
+
},
|
|
2740
|
+
{
|
|
2741
|
+
kind: "Asana",
|
|
2742
|
+
label: "Asana",
|
|
2743
|
+
mode: "in-cli",
|
|
2744
|
+
signals: {
|
|
2745
|
+
npm: ["asana"],
|
|
2746
|
+
python: ["asana"],
|
|
2747
|
+
envKeys: [/^ASANA_/]
|
|
2748
|
+
}
|
|
2749
|
+
},
|
|
2750
|
+
{
|
|
2751
|
+
kind: "Trello",
|
|
2752
|
+
label: "Trello",
|
|
2753
|
+
mode: "in-cli",
|
|
2754
|
+
signals: {
|
|
2755
|
+
npm: ["trello"],
|
|
2756
|
+
envKeys: [/^TRELLO_/]
|
|
2757
|
+
}
|
|
2758
|
+
},
|
|
2759
|
+
{
|
|
2760
|
+
kind: "Todoist",
|
|
2761
|
+
label: "Todoist",
|
|
2762
|
+
mode: "in-cli",
|
|
2763
|
+
signals: {
|
|
2764
|
+
npm: ["@doist/todoist-api-typescript"],
|
|
2765
|
+
python: ["todoist-api-python"],
|
|
2766
|
+
envKeys: [/^TODOIST_/]
|
|
2767
|
+
}
|
|
2768
|
+
},
|
|
2769
|
+
{
|
|
2770
|
+
kind: "Monday",
|
|
2771
|
+
label: "monday.com",
|
|
2772
|
+
mode: "in-cli",
|
|
2773
|
+
signals: {
|
|
2774
|
+
npm: ["@mondaydotcomorg/api"],
|
|
2775
|
+
envKeys: [/^MONDAY_/]
|
|
2776
|
+
}
|
|
2777
|
+
},
|
|
2778
|
+
{
|
|
2779
|
+
kind: "ClickUp",
|
|
2780
|
+
label: "ClickUp",
|
|
2781
|
+
mode: "in-cli",
|
|
2782
|
+
signals: { envKeys: [/^CLICKUP_/] }
|
|
2783
|
+
},
|
|
2784
|
+
{
|
|
2785
|
+
kind: "Height",
|
|
2786
|
+
label: "Height",
|
|
2787
|
+
mode: "in-cli",
|
|
2788
|
+
signals: { envKeys: [/^HEIGHT_API_KEY$/] }
|
|
2789
|
+
},
|
|
2790
|
+
{
|
|
2791
|
+
kind: "Shortcut",
|
|
2792
|
+
label: "Shortcut",
|
|
2793
|
+
mode: "in-cli",
|
|
2794
|
+
signals: {
|
|
2795
|
+
npm: ["@shortcut/client"],
|
|
2796
|
+
envKeys: [/^SHORTCUT_/]
|
|
2797
|
+
}
|
|
2798
|
+
},
|
|
2799
|
+
{
|
|
2800
|
+
kind: "Jira",
|
|
2801
|
+
label: "Jira",
|
|
2802
|
+
mode: "in-cli",
|
|
2803
|
+
signals: {
|
|
2804
|
+
npm: ["jira-client"],
|
|
2805
|
+
python: ["jira", "atlassian-python-api"],
|
|
2806
|
+
envKeys: [/^JIRA_/]
|
|
2807
|
+
}
|
|
2808
|
+
},
|
|
2809
|
+
{
|
|
2810
|
+
kind: "Confluence",
|
|
2811
|
+
label: "Confluence",
|
|
2812
|
+
mode: "in-cli",
|
|
2813
|
+
signals: { envKeys: [/^CONFLUENCE_/] }
|
|
2814
|
+
},
|
|
2815
|
+
{
|
|
2816
|
+
kind: "Airtable",
|
|
2817
|
+
label: "Airtable",
|
|
2818
|
+
mode: "in-cli",
|
|
2819
|
+
signals: {
|
|
2820
|
+
npm: ["airtable"],
|
|
2821
|
+
python: ["pyairtable"],
|
|
2822
|
+
envKeys: [/^AIRTABLE_/]
|
|
2823
|
+
}
|
|
2824
|
+
},
|
|
2825
|
+
{
|
|
2826
|
+
kind: "Smartsheet",
|
|
2827
|
+
label: "Smartsheet",
|
|
2828
|
+
mode: "in-cli",
|
|
2829
|
+
signals: {
|
|
2830
|
+
npm: ["smartsheet"],
|
|
2831
|
+
python: ["smartsheet-python-sdk"],
|
|
2832
|
+
envKeys: [/^SMARTSHEET_/]
|
|
2833
|
+
}
|
|
2834
|
+
},
|
|
2835
|
+
{
|
|
2836
|
+
kind: "Coda",
|
|
2837
|
+
label: "Coda",
|
|
2838
|
+
mode: "in-cli",
|
|
2839
|
+
signals: { envKeys: [/^CODA_API_/] }
|
|
2840
|
+
},
|
|
2841
|
+
{
|
|
2842
|
+
kind: "Productboard",
|
|
2843
|
+
label: "Productboard",
|
|
2844
|
+
mode: "in-cli",
|
|
2845
|
+
signals: { envKeys: [/^PRODUCTBOARD_/] }
|
|
2846
|
+
},
|
|
2847
|
+
{
|
|
2848
|
+
kind: "GoogleSheets",
|
|
2849
|
+
label: "Google Sheets",
|
|
2850
|
+
mode: "in-cli",
|
|
2851
|
+
signals: {
|
|
2852
|
+
npm: ["google-spreadsheet"],
|
|
2853
|
+
python: ["gspread"],
|
|
2854
|
+
envKeys: [/^GOOGLE_SHEETS_/]
|
|
2855
|
+
}
|
|
2856
|
+
},
|
|
2857
|
+
{
|
|
2858
|
+
kind: "Formbricks",
|
|
2859
|
+
label: "Formbricks",
|
|
2860
|
+
mode: "in-cli",
|
|
2861
|
+
signals: {
|
|
2862
|
+
npm: ["@formbricks/js"],
|
|
2863
|
+
envKeys: [/^FORMBRICKS_/]
|
|
2864
|
+
}
|
|
2865
|
+
},
|
|
2866
|
+
{
|
|
2867
|
+
kind: "Tally",
|
|
2868
|
+
label: "Tally",
|
|
2869
|
+
mode: "in-cli",
|
|
2870
|
+
signals: { envKeys: [/^TALLY_API_KEY$/] }
|
|
2871
|
+
},
|
|
2872
|
+
{
|
|
2873
|
+
kind: "Jotform",
|
|
2874
|
+
label: "Jotform",
|
|
2875
|
+
mode: "in-cli",
|
|
2876
|
+
signals: {
|
|
2877
|
+
python: ["jotform"],
|
|
2878
|
+
envKeys: [/^JOTFORM_/]
|
|
2879
|
+
}
|
|
2880
|
+
},
|
|
2881
|
+
{
|
|
2882
|
+
kind: "GitBook",
|
|
2883
|
+
label: "GitBook",
|
|
2884
|
+
mode: "in-cli",
|
|
2885
|
+
signals: {
|
|
2886
|
+
npm: ["@gitbook/api"],
|
|
2887
|
+
envKeys: [/^GITBOOK_/]
|
|
2888
|
+
}
|
|
2889
|
+
},
|
|
2890
|
+
{
|
|
2891
|
+
kind: "Wordpress",
|
|
2892
|
+
label: "WordPress",
|
|
2893
|
+
mode: "in-cli",
|
|
2894
|
+
signals: {
|
|
2895
|
+
npm: ["wpapi"],
|
|
2896
|
+
envKeys: [/^WORDPRESS_/]
|
|
2897
|
+
}
|
|
2898
|
+
},
|
|
2899
|
+
{
|
|
2900
|
+
kind: "Clockify",
|
|
2901
|
+
label: "Clockify",
|
|
2902
|
+
mode: "in-cli",
|
|
2903
|
+
signals: { envKeys: [/^CLOCKIFY_/] }
|
|
2904
|
+
},
|
|
2905
|
+
{
|
|
2906
|
+
kind: "Calendly",
|
|
2907
|
+
label: "Calendly",
|
|
2908
|
+
mode: "in-cli",
|
|
2909
|
+
signals: { envKeys: [/^CALENDLY_/] }
|
|
2910
|
+
},
|
|
2911
|
+
{
|
|
2912
|
+
kind: "CalCom",
|
|
2913
|
+
label: "Cal.com",
|
|
2914
|
+
mode: "in-cli",
|
|
2915
|
+
signals: { envKeys: [/^CALCOM_/, /^CAL_API_KEY$/] }
|
|
2916
|
+
},
|
|
2917
|
+
{
|
|
2918
|
+
kind: "Eventbrite",
|
|
2919
|
+
label: "Eventbrite",
|
|
2920
|
+
mode: "in-cli",
|
|
2921
|
+
signals: {
|
|
2922
|
+
python: ["eventbrite"],
|
|
2923
|
+
envKeys: [/^EVENTBRITE_/]
|
|
2924
|
+
}
|
|
2925
|
+
},
|
|
2926
|
+
{
|
|
2927
|
+
kind: "BambooHR",
|
|
2928
|
+
label: "BambooHR",
|
|
2929
|
+
mode: "in-cli",
|
|
2930
|
+
signals: {
|
|
2931
|
+
python: ["pybamboohr"],
|
|
2932
|
+
envKeys: [/^BAMBOOHR_/, /^BAMBOO_HR_/]
|
|
2933
|
+
}
|
|
2934
|
+
},
|
|
2935
|
+
{
|
|
2936
|
+
kind: "Personio",
|
|
2937
|
+
label: "Personio",
|
|
2938
|
+
mode: "in-cli",
|
|
2939
|
+
signals: { envKeys: [/^PERSONIO_/] }
|
|
2940
|
+
},
|
|
2941
|
+
{
|
|
2942
|
+
kind: "Rippling",
|
|
2943
|
+
label: "Rippling",
|
|
2944
|
+
mode: "in-cli",
|
|
2945
|
+
signals: { envKeys: [/^RIPPLING_/] }
|
|
2946
|
+
},
|
|
2947
|
+
{
|
|
2948
|
+
kind: "Greenhouse",
|
|
2949
|
+
label: "Greenhouse",
|
|
2950
|
+
mode: "in-cli",
|
|
2951
|
+
signals: { envKeys: [/^GREENHOUSE_/] }
|
|
2952
|
+
},
|
|
2953
|
+
{
|
|
2954
|
+
kind: "Ashby",
|
|
2955
|
+
label: "Ashby",
|
|
2956
|
+
mode: "in-cli",
|
|
2957
|
+
signals: { envKeys: [/^ASHBY_/] }
|
|
2958
|
+
},
|
|
2959
|
+
{
|
|
2960
|
+
kind: "Workable",
|
|
2961
|
+
label: "Workable",
|
|
2962
|
+
mode: "in-cli",
|
|
2963
|
+
signals: { envKeys: [/^WORKABLE_/] }
|
|
2964
|
+
},
|
|
2965
|
+
{
|
|
2966
|
+
kind: "GoogleAds",
|
|
2967
|
+
label: "Google Ads",
|
|
2968
|
+
mode: "deep-link",
|
|
2969
|
+
signals: {
|
|
2970
|
+
npm: ["google-ads-api"],
|
|
2971
|
+
python: ["google-ads"],
|
|
2972
|
+
envKeys: [/^GOOGLE_ADS_/]
|
|
2973
|
+
}
|
|
2974
|
+
},
|
|
2975
|
+
{
|
|
2976
|
+
kind: "MetaAds",
|
|
2977
|
+
label: "Meta Ads",
|
|
2978
|
+
mode: "deep-link",
|
|
2979
|
+
signals: {
|
|
2980
|
+
npm: ["facebook-nodejs-business-sdk"],
|
|
2981
|
+
python: ["facebook-business"],
|
|
2982
|
+
envKeys: [/^META_ADS_/, /^FACEBOOK_ADS_/]
|
|
2983
|
+
}
|
|
2984
|
+
},
|
|
2985
|
+
{
|
|
2986
|
+
kind: "LinkedinAds",
|
|
2987
|
+
label: "LinkedIn Ads",
|
|
2988
|
+
mode: "deep-link",
|
|
2989
|
+
signals: { envKeys: [/^LINKEDIN_ADS_/] }
|
|
2990
|
+
},
|
|
2991
|
+
{
|
|
2992
|
+
kind: "TikTokAds",
|
|
2993
|
+
label: "TikTok Ads",
|
|
2994
|
+
mode: "deep-link",
|
|
2995
|
+
signals: { envKeys: [/^TIKTOK_ADS_/] }
|
|
2996
|
+
},
|
|
2997
|
+
{
|
|
2998
|
+
kind: "RedditAds",
|
|
2999
|
+
label: "Reddit Ads",
|
|
3000
|
+
mode: "deep-link",
|
|
3001
|
+
signals: { envKeys: [/^REDDIT_ADS_/] }
|
|
3002
|
+
},
|
|
3003
|
+
{
|
|
3004
|
+
kind: "PinterestAds",
|
|
3005
|
+
label: "Pinterest Ads",
|
|
3006
|
+
mode: "deep-link",
|
|
3007
|
+
signals: { envKeys: [/^PINTEREST_ADS_/] }
|
|
3008
|
+
},
|
|
3009
|
+
{
|
|
3010
|
+
kind: "BingAds",
|
|
3011
|
+
label: "Bing Ads",
|
|
3012
|
+
mode: "deep-link",
|
|
3013
|
+
signals: {
|
|
3014
|
+
python: ["bingads"],
|
|
3015
|
+
envKeys: [/^BING_ADS_/, /^MICROSOFT_ADS_/]
|
|
3016
|
+
}
|
|
3017
|
+
},
|
|
3018
|
+
{
|
|
3019
|
+
kind: "AmazonAds",
|
|
3020
|
+
label: "Amazon Ads",
|
|
3021
|
+
mode: "deep-link",
|
|
3022
|
+
signals: { envKeys: [/^AMAZON_ADS_/] }
|
|
3023
|
+
},
|
|
3024
|
+
{
|
|
3025
|
+
kind: "GoogleAnalytics",
|
|
3026
|
+
label: "Google Analytics",
|
|
3027
|
+
mode: "deep-link",
|
|
3028
|
+
signals: {
|
|
3029
|
+
npm: ["react-ga4"],
|
|
3030
|
+
python: ["google-analytics-data"],
|
|
3031
|
+
envKeys: [/^GOOGLE_ANALYTICS_/, /^GA4_/]
|
|
3032
|
+
}
|
|
3033
|
+
},
|
|
3034
|
+
{
|
|
3035
|
+
kind: "GoogleSearchConsole",
|
|
3036
|
+
label: "Google Search Console",
|
|
3037
|
+
mode: "deep-link",
|
|
3038
|
+
signals: { envKeys: [/^SEARCH_CONSOLE_/, /^GSC_/] }
|
|
3039
|
+
},
|
|
3040
|
+
{
|
|
3041
|
+
kind: "AppsFlyer",
|
|
3042
|
+
label: "AppsFlyer",
|
|
3043
|
+
mode: "in-cli",
|
|
3044
|
+
signals: {
|
|
3045
|
+
npm: ["react-native-appsflyer"],
|
|
3046
|
+
envKeys: [/^APPSFLYER_/]
|
|
3047
|
+
}
|
|
3048
|
+
},
|
|
3049
|
+
{
|
|
3050
|
+
kind: "Dub",
|
|
3051
|
+
label: "Dub",
|
|
3052
|
+
mode: "in-cli",
|
|
3053
|
+
signals: {
|
|
3054
|
+
npm: ["dub"],
|
|
3055
|
+
python: ["dub"],
|
|
3056
|
+
envKeys: [/^DUB_API_KEY$/]
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
];
|
|
3060
|
+
const SOURCE_DETECTORS = [
|
|
3061
|
+
...CORE_SOURCE_DETECTORS,
|
|
3062
|
+
...LLM_SOURCE_DETECTORS,
|
|
3063
|
+
...OTHER_SOURCE_DETECTORS
|
|
3064
|
+
];
|
|
3065
|
+
/** Kinds from the LLM / AI section above — the only ones that may set `wizard_ai_sdk_detected`. */
|
|
3066
|
+
const AI_SOURCE_KINDS = new Set(LLM_SOURCE_DETECTORS.map((detector) => detector.kind));
|
|
3067
|
+
//#endregion
|
|
3068
|
+
//#region src/lib/warehouse-sources/detect.ts
|
|
3069
|
+
/**
|
|
3070
|
+
* Data warehouse source detection.
|
|
3071
|
+
*
|
|
3072
|
+
* Scans a project for codebase signals (npm/python/ruby deps, `.env` key
|
|
3073
|
+
* names) and matches them against the `SOURCE_DETECTORS` registry. Pure
|
|
3074
|
+
* function: no store mutations, no UI calls. Reads files locally — only the
|
|
3075
|
+
* wizard process does this; the agent never sees secret values.
|
|
3076
|
+
*
|
|
3077
|
+
* Note we only read `.env` KEY NAMES, never values.
|
|
3078
|
+
*/
|
|
3079
|
+
const MAX_DEPTH = 3;
|
|
3080
|
+
const MAX_SIGNAL_SET = MAX_ENV_KEY_SET;
|
|
3081
|
+
function addCapped(set, value) {
|
|
3082
|
+
if (set.size < MAX_SIGNAL_SET) set.add(value);
|
|
3083
|
+
}
|
|
3084
|
+
/**
|
|
3085
|
+
* Detect which warehouse sources the project at `installDir` appears to use.
|
|
3086
|
+
* Returns one `DetectedSource` per matched registry entry (kinds are unique,
|
|
3087
|
+
* so the result is naturally deduped), minus the entries a more specific
|
|
3088
|
+
* match supersedes.
|
|
3089
|
+
*/
|
|
3090
|
+
function detectWarehouseSources(installDir) {
|
|
3091
|
+
const signals = collectSignals(installDir);
|
|
3092
|
+
const matched = [];
|
|
3093
|
+
for (const detector of SOURCE_DETECTORS) {
|
|
3094
|
+
const signal = matchDetector(detector, signals);
|
|
3095
|
+
if (signal) matched.push({
|
|
3096
|
+
detector,
|
|
3097
|
+
signal
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
const kinds = new Set(matched.map((m) => m.detector.kind));
|
|
3101
|
+
return matched.filter(({ detector }) => !isSuperseded(detector, kinds)).map(({ detector, signal }) => ({
|
|
3102
|
+
kind: detector.kind,
|
|
3103
|
+
label: detector.label,
|
|
3104
|
+
mode: detector.mode,
|
|
3105
|
+
matchedSignal: signal
|
|
3106
|
+
}));
|
|
3107
|
+
}
|
|
3108
|
+
/**
|
|
3109
|
+
* Whether a more specific match covers this detector's data. Every consumer
|
|
3110
|
+
* treats a detected kind as a source to create, so a project whose Postgres
|
|
3111
|
+
* lives on Neon or Supabase would otherwise be offered both and end up with
|
|
3112
|
+
* the same tables imported twice.
|
|
3113
|
+
*/
|
|
3114
|
+
function isSuperseded(detector, detectedKinds) {
|
|
3115
|
+
return (detector.supersededBy ?? []).some((kind) => detectedKinds.has(kind));
|
|
3116
|
+
}
|
|
3117
|
+
/** Returns a human-readable description of the first matching signal, or null. */
|
|
3118
|
+
function matchDetector(detector, signals) {
|
|
3119
|
+
const { npm, python, ruby, envKeys } = detector.signals;
|
|
3120
|
+
const npmHit = npm?.find((dep) => signals.npm.has(dep));
|
|
3121
|
+
if (npmHit) return `found \`${npmHit}\` in package.json`;
|
|
3122
|
+
const pyHit = python?.find((dep) => signals.python.has(dep));
|
|
3123
|
+
if (pyHit) return `found \`${pyHit}\` in Python dependencies`;
|
|
3124
|
+
const rubyHit = ruby?.find((gem) => signals.ruby.has(gem));
|
|
3125
|
+
if (rubyHit) return `found \`${rubyHit}\` in Gemfile`;
|
|
3126
|
+
if (envKeys) {
|
|
3127
|
+
for (const [key, file] of signals.envKeys) if (envKeys.some((re) => re.test(key))) return `found \`${key}\` in \`${file}\``;
|
|
3128
|
+
}
|
|
3129
|
+
return null;
|
|
3130
|
+
}
|
|
3131
|
+
function collectSignals(installDir) {
|
|
3132
|
+
const signals = {
|
|
3133
|
+
npm: /* @__PURE__ */ new Set(),
|
|
3134
|
+
python: /* @__PURE__ */ new Set(),
|
|
3135
|
+
ruby: /* @__PURE__ */ new Set(),
|
|
3136
|
+
envKeys: /* @__PURE__ */ new Map()
|
|
3137
|
+
};
|
|
3138
|
+
walkProjectFiles(installDir, (name, fullPath) => ingestFile(installDir, name, fullPath, signals), MAX_DEPTH);
|
|
3139
|
+
return signals;
|
|
3140
|
+
}
|
|
3141
|
+
/**
|
|
3142
|
+
* Route a file to the right parser BY NAME, reading its contents only when the
|
|
3143
|
+
* name matches one of the ~6 manifests we care about. Checking the name first
|
|
3144
|
+
* avoids slurping every file in the tree (lockfiles, binaries, assets) into
|
|
3145
|
+
* memory just to discard it.
|
|
3146
|
+
*/
|
|
3147
|
+
function ingestFile(installDir, name, fullPath, signals) {
|
|
3148
|
+
const ingest = ingestorFor(name);
|
|
3149
|
+
if (!ingest) return;
|
|
3150
|
+
const content = safeReadFile(fullPath);
|
|
3151
|
+
if (content === null) return;
|
|
3152
|
+
ingest(content, signals, toPromptSafeRelativePath(installDir, fullPath));
|
|
3153
|
+
}
|
|
3154
|
+
/** Pick the parser for a manifest filename, or null if it's not one we read. */
|
|
3155
|
+
function ingestorFor(name) {
|
|
3156
|
+
if (name === "package.json") return addNpmDeps;
|
|
3157
|
+
if (name === "requirements.txt") return (c, s) => parseRequirementsTxt(c).forEach((d) => addCapped(s.python, d));
|
|
3158
|
+
if (name === "pyproject.toml") return (c, s) => parsePyprojectToml(c).forEach((d) => addCapped(s.python, d));
|
|
3159
|
+
if (name === "Pipfile") return (c, s) => parsePipfile(c).forEach((d) => addCapped(s.python, d));
|
|
3160
|
+
if (name === "Gemfile") return (c, s) => parseGemfile(c).forEach((g) => addCapped(s.ruby, g));
|
|
3161
|
+
if (isEnvFileName(name)) return (c, s, relativePath) => addEnvKeys(c, s, relativePath);
|
|
3162
|
+
return null;
|
|
3163
|
+
}
|
|
3164
|
+
/**
|
|
3165
|
+
* Record each key NAME against the file that defined it. The first file wins,
|
|
3166
|
+
* so the reported signal stays stable when several `.env*` files repeat a key.
|
|
3167
|
+
* Values are never read.
|
|
3168
|
+
*/
|
|
3169
|
+
function addEnvKeys(content, signals, relativePath) {
|
|
3170
|
+
for (const key of parseEnvKeyNames(content)) {
|
|
3171
|
+
if (signals.envKeys.has(key)) continue;
|
|
3172
|
+
if (signals.envKeys.size >= MAX_SIGNAL_SET) return;
|
|
3173
|
+
signals.envKeys.set(key, relativePath);
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
function addNpmDeps(content, signals) {
|
|
3177
|
+
try {
|
|
3178
|
+
const pkg = JSON.parse(content);
|
|
3179
|
+
for (const dep of Object.keys({
|
|
3180
|
+
...pkg.dependencies,
|
|
3181
|
+
...pkg.devDependencies
|
|
3182
|
+
})) addCapped(signals.npm, dep);
|
|
3183
|
+
} catch (error) {
|
|
3184
|
+
analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "detectWarehouseSources.parsePackageJson" });
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
/** Extract gem names from `gem 'name'` declarations. */
|
|
3188
|
+
function parseGemfile(content) {
|
|
3189
|
+
const gems = [];
|
|
3190
|
+
for (const match of content.matchAll(/^\s*gem\s+['"]([^'"]+)['"]/gm)) gems.push(match[1]);
|
|
3191
|
+
return gems;
|
|
3192
|
+
}
|
|
3193
|
+
//#endregion
|
|
3194
|
+
//#region src/lib/programs/warehouse-source/detect.ts
|
|
3195
|
+
/**
|
|
3196
|
+
* Warehouse-source program detection step.
|
|
3197
|
+
*
|
|
3198
|
+
* Thin adapter over `detectWarehouseSources` that writes results into
|
|
3199
|
+
* frameworkContext for the intro screen, plus the `[ABORT]` cases the
|
|
3200
|
+
* data-warehouse-source-setup skill can emit.
|
|
3201
|
+
*/
|
|
3202
|
+
/** frameworkContext key holding the detected sources (set on success). */
|
|
3203
|
+
const DETECTED_WAREHOUSE_SOURCES_KEY = "detectedWarehouseSources";
|
|
3204
|
+
/**
|
|
3205
|
+
* Read the detected sources out of frameworkContext. Single accessor shared by
|
|
3206
|
+
* the intro screen and the prompt builder so the key + cast live in one place.
|
|
3207
|
+
*/
|
|
3208
|
+
function getDetectedWarehouseSources(session) {
|
|
3209
|
+
return session.frameworkContext["detectedWarehouseSources"] ?? [];
|
|
3210
|
+
}
|
|
3211
|
+
/** `[ABORT] <reason>` cases the skill can emit. */
|
|
3212
|
+
const WAREHOUSE_ABORT_CASES = [{
|
|
3213
|
+
match: /^no data sources? detected\.?$/i,
|
|
3214
|
+
message: "No data source detected",
|
|
3215
|
+
body: "The agent could not confirm a data warehouse source to connect. Run this command from a project that uses a supported source (a database, Stripe, etc.).",
|
|
3216
|
+
docsUrl: "https://posthog.com/docs/data-warehouse"
|
|
3217
|
+
}, {
|
|
3218
|
+
match: /^source creation failed\.?$/i,
|
|
3219
|
+
message: "Source creation failed",
|
|
3220
|
+
body: "PostHog could not create the data warehouse source with the credentials provided. Double-check the connection details and try again, or set the source up directly in the PostHog app.",
|
|
3221
|
+
docsUrl: "https://posthog.com/docs/data-warehouse"
|
|
3222
|
+
}];
|
|
3223
|
+
/**
|
|
3224
|
+
* Scan `session.installDir` for warehouse-source signals. Writes the detected
|
|
3225
|
+
* sources (or a `detectError`) into frameworkContext for the intro screen.
|
|
3226
|
+
*/
|
|
3227
|
+
function detectWarehousePrerequisites(session, setFrameworkContext) {
|
|
3228
|
+
const fail = (error) => setFrameworkContext("detectError", error);
|
|
3229
|
+
const installDir = session.installDir;
|
|
3230
|
+
if (!existsSync(installDir)) {
|
|
3231
|
+
fail({
|
|
3232
|
+
kind: "bad-directory",
|
|
3233
|
+
path: installDir,
|
|
3234
|
+
reason: "missing"
|
|
3235
|
+
});
|
|
3236
|
+
return;
|
|
3237
|
+
}
|
|
3238
|
+
try {
|
|
3239
|
+
if (!statSync(installDir).isDirectory()) {
|
|
3240
|
+
fail({
|
|
3241
|
+
kind: "bad-directory",
|
|
3242
|
+
path: installDir,
|
|
3243
|
+
reason: "not-dir"
|
|
3244
|
+
});
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
} catch (error) {
|
|
3248
|
+
analytics.captureException(error instanceof Error ? error : new Error(String(error)), {
|
|
3249
|
+
step: "detectWarehousePrerequisites.stat",
|
|
3250
|
+
path: installDir
|
|
3251
|
+
});
|
|
3252
|
+
fail({
|
|
3253
|
+
kind: "bad-directory",
|
|
3254
|
+
path: installDir,
|
|
3255
|
+
reason: "unreadable"
|
|
3256
|
+
});
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
const sources = detectWarehouseSources(installDir);
|
|
3260
|
+
if (sources.length === 0) {
|
|
3261
|
+
fail({ kind: "no-sources" });
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
analytics.setTag("warehouse_source_kinds", sources.map((s) => s.kind).join(","));
|
|
3265
|
+
analytics.setTag("warehouse_source_modes", sources.map((s) => `${s.kind}:${s.mode}`).join(","));
|
|
3266
|
+
analytics.setTag("warehouse_source_count", sources.length);
|
|
3267
|
+
setFrameworkContext(DETECTED_WAREHOUSE_SOURCES_KEY, sources);
|
|
3268
|
+
}
|
|
3269
|
+
//#endregion
|
|
3270
|
+
//#region src/lib/programs/shared/package-scanning.ts
|
|
3271
|
+
const POSTHOG_SDKS = new Set([
|
|
3272
|
+
"posthog-js",
|
|
3273
|
+
"posthog-node",
|
|
3274
|
+
"posthog-react-native",
|
|
3275
|
+
"posthog-android",
|
|
3276
|
+
"posthog-ios"
|
|
3277
|
+
]);
|
|
3278
|
+
const STRIPE_SDKS = new Set([
|
|
3279
|
+
"stripe",
|
|
3280
|
+
"@stripe/stripe-js",
|
|
3281
|
+
"@stripe/react-stripe-js"
|
|
3282
|
+
]);
|
|
3283
|
+
const MAX_PACKAGE_MATCHES = 500;
|
|
3284
|
+
/**
|
|
3285
|
+
* Find all package.json files under installDir via the shared bounded walk
|
|
3286
|
+
* (depth cap, ignored dirs, symlink-loop protection, per-dir entry cap).
|
|
3287
|
+
* Returns matches with detected SDKs.
|
|
3288
|
+
*/
|
|
3289
|
+
function findPackageJsons(installDir, maxDepth = 3) {
|
|
3290
|
+
const matches = [];
|
|
3291
|
+
walkProjectFiles(installDir, (name, fullPath) => {
|
|
3292
|
+
if (name !== "package.json" || matches.length >= MAX_PACKAGE_MATCHES) return;
|
|
3293
|
+
const content = safeReadFile(fullPath);
|
|
3294
|
+
if (content === null) return;
|
|
3295
|
+
try {
|
|
3296
|
+
const pkg = JSON.parse(content);
|
|
3297
|
+
const depNames = [...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})];
|
|
3298
|
+
const posthogSdks = depNames.filter((d) => POSTHOG_SDKS.has(d));
|
|
3299
|
+
const stripeSdks = depNames.filter((d) => STRIPE_SDKS.has(d));
|
|
3300
|
+
matches.push({
|
|
3301
|
+
path: relative(installDir, fullPath) || "package.json",
|
|
3302
|
+
posthogSdks,
|
|
3303
|
+
stripeSdks
|
|
3304
|
+
});
|
|
3305
|
+
} catch {}
|
|
3306
|
+
}, maxDepth);
|
|
3307
|
+
return matches;
|
|
3308
|
+
}
|
|
3309
|
+
//#endregion
|
|
3310
|
+
//#region src/lib/programs/posthog-integration/detect.ts
|
|
3311
|
+
async function detectPostHogIntegration(ctx) {
|
|
3312
|
+
const session = ctx.session;
|
|
3313
|
+
const installDir = session.installDir;
|
|
3314
|
+
const detectedIntegration = await detectFramework(installDir);
|
|
3315
|
+
if (detectedIntegration) {
|
|
3316
|
+
const config = FRAMEWORK_REGISTRY[detectedIntegration];
|
|
3317
|
+
const sessionOptions = {
|
|
3318
|
+
installDir,
|
|
3319
|
+
debug: session.debug,
|
|
3320
|
+
signup: session.signup,
|
|
3321
|
+
ci: session.ci,
|
|
3322
|
+
benchmark: session.benchmark,
|
|
3323
|
+
yaraReport: session.yaraReport
|
|
3324
|
+
};
|
|
3325
|
+
const context = await gatherFrameworkContext(config, sessionOptions);
|
|
3326
|
+
for (const [key, value] of Object.entries(context)) if (!(key in session.frameworkContext)) ctx.setFrameworkContext(key, value);
|
|
3327
|
+
ctx.setFrameworkConfig(detectedIntegration, config);
|
|
3328
|
+
ctx.setSkillId(detectedIntegration);
|
|
3329
|
+
if (!session.detectedFrameworkLabel) ctx.setDetectedFramework(config.metadata.name);
|
|
3330
|
+
const versionResult = await checkFrameworkVersion(config, sessionOptions);
|
|
3331
|
+
if (versionResult.supported !== true) ctx.setUnsupportedVersion(versionResult.supported);
|
|
3332
|
+
}
|
|
3333
|
+
for (const feature of discoverFeatures(installDir)) ctx.addDiscoveredFeature(feature);
|
|
3334
|
+
detectWarehouseSourcesForSuggestion(ctx, installDir);
|
|
3335
|
+
detectExistingPostHog(ctx, installDir);
|
|
3336
|
+
ctx.setDetectionComplete();
|
|
3337
|
+
}
|
|
3338
|
+
/**
|
|
3339
|
+
* How the scan went, for the reporter to read later. Three states, and the
|
|
3340
|
+
* absent one carries the most weight:
|
|
3341
|
+
*
|
|
3342
|
+
* absent this program never scanned. Only `posthog-integration` runs the
|
|
3343
|
+
* scan below, but every program's intro screen resolves consent
|
|
3344
|
+
* through the same store method, so the reporter is reached on runs
|
|
3345
|
+
* where no scan ever happened.
|
|
3346
|
+
* 'failed' the scan threw. A zero-count row would read as "scanned, found
|
|
3347
|
+
* nothing", which is a different fact.
|
|
3348
|
+
* 'ok' the scan ran. Zero sources is a real result and gets reported.
|
|
3349
|
+
*/
|
|
3350
|
+
const WAREHOUSE_SCAN_STATE_KEY = "warehouseScanState";
|
|
3351
|
+
/**
|
|
3352
|
+
* Scan for data warehouse source signals (Postgres, Stripe, Hubspot, …) and,
|
|
3353
|
+
* when found, stash them for a `wizard warehouse` suggestion on the outro.
|
|
3354
|
+
* Runs unconditionally during `detect`, before the intro screen has asked
|
|
3355
|
+
* about sharing. Reporting what this finds is a separate, later concern.
|
|
3356
|
+
* See `reportWarehouseSourcesDetected` below.
|
|
3357
|
+
*
|
|
3358
|
+
* Deliberately a suggestion, not an inline agent run: a second credential-
|
|
3359
|
+
* collecting agent run before the outro could `process.exit()` on any of its
|
|
3360
|
+
* failure paths and cost the user the success outro on a run where PostHog
|
|
3361
|
+
* installed fine.
|
|
3362
|
+
*
|
|
3363
|
+
* Calls `detectWarehouseSources` (a pure scan) rather than the warehouse
|
|
3364
|
+
* program's `detectWarehousePrerequisites`, which writes a `detectError` when
|
|
3365
|
+
* it finds nothing — that's right for `wizard warehouse` but here would show a
|
|
3366
|
+
* spurious error and trip the CI prerequisites abort. No sources is a no-op.
|
|
3367
|
+
*
|
|
3368
|
+
* Best-effort: never let a scan failure break the wizard flow.
|
|
3369
|
+
*/
|
|
3370
|
+
function detectWarehouseSourcesForSuggestion(ctx, installDir) {
|
|
3371
|
+
try {
|
|
3372
|
+
const sources = detectWarehouseSources(installDir);
|
|
3373
|
+
ctx.setFrameworkContext(WAREHOUSE_SCAN_STATE_KEY, "ok");
|
|
3374
|
+
if (sources.length === 0) return;
|
|
3375
|
+
ctx.setFrameworkContext(DETECTED_WAREHOUSE_SOURCES_KEY, sources);
|
|
3376
|
+
} catch (error) {
|
|
3377
|
+
ctx.setFrameworkContext(WAREHOUSE_SCAN_STATE_KEY, "failed");
|
|
3378
|
+
analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "detectWarehouseSourcesForSuggestion" });
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
function hasAiSdkEvidence(session, sources) {
|
|
3382
|
+
return sources.some((s) => AI_SOURCE_KINDS.has(s.kind)) || session.discoveredFeatures.includes("llm");
|
|
3383
|
+
}
|
|
3384
|
+
/**
|
|
3385
|
+
* Boolean only, on the org, never the list of kinds or any non-AI tool: a
|
|
3386
|
+
* decline must not leak even the shape of what local detection saw.
|
|
3387
|
+
*/
|
|
3388
|
+
function stampAiSdkDetected(session, sources) {
|
|
3389
|
+
const organizationId = session.apiUser?.organization?.id;
|
|
3390
|
+
if (!organizationId) return;
|
|
3391
|
+
if (!hasAiSdkEvidence(session, sources)) return;
|
|
3392
|
+
analytics.groupIdentify("organization", organizationId, { wizard_ai_sdk_detected: true });
|
|
3393
|
+
}
|
|
3394
|
+
/**
|
|
3395
|
+
* Fires the org stamp once per session, right after `authenticate()` succeeds
|
|
3396
|
+
* — never from the consent path, since consent on the intro screen resolves
|
|
3397
|
+
* before login and `session.apiUser` is unset there. Called right after
|
|
3398
|
+
* `authenticate()` from run-wizard.ts's auth step and from bootstrap.ts,
|
|
3399
|
+
* whichever completes it first for a given program; a no-op on every call
|
|
3400
|
+
* after that. In the `--ci` path, project-scope.ts authenticates first as a
|
|
3401
|
+
* prerequisite (no evidence gathered yet, so nothing would stamp there
|
|
3402
|
+
* anyway) and this only ever runs from the later, idempotent bootstrap.ts
|
|
3403
|
+
* call — still correctly finding no evidence, since CI skips the detect step.
|
|
3404
|
+
*/
|
|
3405
|
+
function maybeStampAiSdkDetected(session) {
|
|
3406
|
+
if (session.aiSdkStampReported) return;
|
|
3407
|
+
session.aiSdkStampReported = true;
|
|
3408
|
+
if (!mayReportScanResults(session)) return;
|
|
3409
|
+
stampAiSdkDetected(session, getDetectedWarehouseSources(session));
|
|
3410
|
+
}
|
|
3411
|
+
/**
|
|
3412
|
+
* The single place scan results become telemetry. Called from
|
|
3413
|
+
* `WizardStore.completeSetup()`, the point consent becomes final — the privacy
|
|
3414
|
+
* panel's choice is reversible until then, so nothing may report earlier.
|
|
3415
|
+
* The org stamp is a separate concern: see `maybeStampAiSdkDetected`, which
|
|
3416
|
+
* runs post-auth rather than at consent resolution.
|
|
3417
|
+
*
|
|
3418
|
+
* Returns true when consent has resolved and the caller should latch
|
|
3419
|
+
* `warehouseSourcesReported`, which is not the same as "this sent something":
|
|
3420
|
+
* a decline, a failed scan, and a program that never scanned all resolve
|
|
3421
|
+
* without sending.
|
|
3422
|
+
*/
|
|
3423
|
+
function reportWarehouseSourcesDetected(session) {
|
|
3424
|
+
if (session.warehouseSourcesReported) return false;
|
|
3425
|
+
if (session.scanConsent === "undecided") return false;
|
|
3426
|
+
if (mayReportScanResults(session)) {
|
|
3427
|
+
const sources = getDetectedWarehouseSources(session);
|
|
3428
|
+
if (session.frameworkContext[WAREHOUSE_SCAN_STATE_KEY] !== "ok") return true;
|
|
3429
|
+
analytics.wizardCapture("warehouse sources detected", {
|
|
3430
|
+
warehouse_source_count: sources.length,
|
|
3431
|
+
warehouse_source_kinds: sources.map((s) => s.kind),
|
|
3432
|
+
warehouse_source_modes: sources.map((s) => s.mode)
|
|
3433
|
+
});
|
|
3434
|
+
if (sources.length > 0) {
|
|
3435
|
+
analytics.setTag("warehouse_source_kinds", sources.map((s) => s.kind).join(","));
|
|
3436
|
+
analytics.setTag("warehouse_source_count", sources.length);
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
return true;
|
|
3440
|
+
}
|
|
3441
|
+
/** Dependency-level signal, not a verified install. A failed scan reports false. */
|
|
3442
|
+
function detectExistingPostHog(ctx, installDir) {
|
|
3443
|
+
try {
|
|
3444
|
+
const pkgJsons = findPackageJsons(installDir);
|
|
3445
|
+
ctx.setPosthogSdkDetected(pkgJsons.some((p) => p.posthogSdks.length > 0));
|
|
3446
|
+
} catch (error) {
|
|
3447
|
+
analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "detectExistingPosthog" });
|
|
3448
|
+
ctx.setPosthogSdkDetected(false);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
//#endregion
|
|
3452
|
+
//#region src/lib/programs/audit/types.ts
|
|
3453
|
+
/** Single source of truth for status glyph + color across audit views. */
|
|
3454
|
+
const AUDIT_SEVERITY_STYLE = {
|
|
3455
|
+
pending: {
|
|
3456
|
+
glyph: "◌",
|
|
3457
|
+
color: "gray"
|
|
3458
|
+
},
|
|
3459
|
+
pass: {
|
|
3460
|
+
glyph: "✔",
|
|
3461
|
+
color: "green"
|
|
3462
|
+
},
|
|
3463
|
+
error: {
|
|
3464
|
+
glyph: "✘",
|
|
3465
|
+
color: "red"
|
|
3466
|
+
},
|
|
3467
|
+
warning: {
|
|
3468
|
+
glyph: "⚠",
|
|
3469
|
+
color: "yellow"
|
|
3470
|
+
},
|
|
3471
|
+
suggestion: {
|
|
3472
|
+
glyph: "•",
|
|
3473
|
+
color: "cyan"
|
|
3474
|
+
}
|
|
3475
|
+
};
|
|
3476
|
+
const AUDIT_CHECKS_KEY = "auditChecks";
|
|
3477
|
+
function getAuditChecks(session) {
|
|
3478
|
+
const raw = session.frameworkContext[AUDIT_CHECKS_KEY];
|
|
3479
|
+
return Array.isArray(raw) ? raw : [];
|
|
3480
|
+
}
|
|
3481
|
+
//#endregion
|
|
3482
|
+
//#region src/lib/programs/audit/ledger-watcher.ts
|
|
3483
|
+
/**
|
|
3484
|
+
* Mirrors the agent's `.posthog-audit-checks.json` into the session, so the TUI
|
|
3485
|
+
* screens and the task stream read one value. `runProgramAgent` owns the
|
|
3486
|
+
* lifecycle and removes the file at run end, so every path gets it — including
|
|
3487
|
+
* the e2e host, which builds no task stream.
|
|
3488
|
+
*/
|
|
3489
|
+
const MAX_LEDGER_FILE_BYTES = 256 * 1024;
|
|
3490
|
+
function startAuditLedgerWatcher(installDir, file, options = {}) {
|
|
3491
|
+
const target = path.join(installDir, file);
|
|
3492
|
+
logToFile(`[audit-ledger] watching ${target}`);
|
|
3493
|
+
return startFileWatcher(target, (parsed) => getUI().setFrameworkContext(AUDIT_CHECKS_KEY, coerceAuditChecks(parsed)), {
|
|
3494
|
+
ignoreInitialFile: true,
|
|
3495
|
+
maxFileSizeBytes: MAX_LEDGER_FILE_BYTES,
|
|
3496
|
+
...options
|
|
3497
|
+
});
|
|
3498
|
+
}
|
|
3499
|
+
/** The program declared the ledger, so it removes it, agent `rm` step or not. */
|
|
3500
|
+
function removeAuditLedger(installDir, file) {
|
|
3501
|
+
const target = path.join(installDir, file);
|
|
3502
|
+
try {
|
|
3503
|
+
fs.rmSync(target, { force: true });
|
|
3504
|
+
} catch (error) {
|
|
3505
|
+
logToFile(`[audit-ledger] could not remove ${target}: ${String(error)}`);
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
//#endregion
|
|
3509
|
+
//#region src/lib/programs/run-agent-legacy.ts
|
|
3510
|
+
var run_agent_legacy_exports = /* @__PURE__ */ __exportAll({ runProgramAgent: () => runProgramAgent });
|
|
3511
|
+
/**
|
|
3512
|
+
* Resolve a ProgramConfig's agent run definition and execute the pipeline.
|
|
3513
|
+
* Entry point for the runners and for composed run steps.
|
|
3514
|
+
*/
|
|
3515
|
+
async function runProgramAgent(programConfig, session, options = {}) {
|
|
3516
|
+
if (!programConfig.run) throw new Error(`Program "${programConfig.id}" has no run configuration.`);
|
|
3517
|
+
const ledgerFile = programConfig.auditLedgerFile;
|
|
3518
|
+
const ledger = ledgerFile ? startAuditLedgerWatcher(session.installDir, ledgerFile) : null;
|
|
3519
|
+
const releaseLedger = () => {
|
|
3520
|
+
ledger?.refresh();
|
|
3521
|
+
ledger?.stop();
|
|
3522
|
+
if (ledgerFile) removeAuditLedger(session.installDir, ledgerFile);
|
|
3523
|
+
};
|
|
3524
|
+
if (ledger) registerCleanup(releaseLedger);
|
|
3525
|
+
try {
|
|
3526
|
+
await runProgram(session, typeof programConfig.run === "function" ? await programConfig.run(session) : programConfig.run, programConfig, options.composed ?? false);
|
|
3527
|
+
} finally {
|
|
3528
|
+
releaseLedger();
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
/**
|
|
3532
|
+
* Gates → authenticate → flags → binding → the functional run → apply result.
|
|
3533
|
+
* Every step happens in the order it did inside the agent's bootstrap.
|
|
3534
|
+
*/
|
|
3535
|
+
async function runProgram(session, run, programConfig, composed) {
|
|
3536
|
+
initLogFile();
|
|
3537
|
+
session.skillId = run.skillId ?? run.integrationLabel;
|
|
3538
|
+
logToFile(`[agent-runner] START ${run.integrationLabel} build=${analytics.build}${session.ci ? " (non-interactive)" : ""}`);
|
|
3539
|
+
if (session.debug) enableDebugLogs();
|
|
3540
|
+
await runHealthGate(session, programConfig);
|
|
3541
|
+
await runSettingsGate(session);
|
|
3542
|
+
analytics.wizardCapture("agent started", {
|
|
3543
|
+
integration: run.integrationLabel,
|
|
3544
|
+
program_id: programConfig.id,
|
|
3545
|
+
skill_id: run.skillId ?? null
|
|
3546
|
+
});
|
|
3547
|
+
await authenticate(session, programConfig.id);
|
|
3548
|
+
maybeStampAiSdkDetected(session);
|
|
3549
|
+
logToFile("[agent-runner] checking AI opt-in gate");
|
|
3550
|
+
await getUI().waitForAiOptIn();
|
|
3551
|
+
logToFile("[agent-runner] AI opt-in gate cleared");
|
|
3552
|
+
for (const step of postAuthGateSteps(programConfig.steps)) {
|
|
3553
|
+
logToFile(`[agent-runner] awaiting post-auth gate: ${step.id}`);
|
|
3554
|
+
await getUI().waitForGate(step.id);
|
|
3555
|
+
logToFile(`[agent-runner] post-auth gate cleared: ${step.id}`);
|
|
3556
|
+
}
|
|
3557
|
+
const wizardFlags = await analytics.getAllFlagsForWizard();
|
|
3558
|
+
const wizardFlagPayloads = analytics.getWizardFlagPayloads();
|
|
3559
|
+
const wizardMetadata = buildRunTags({
|
|
3560
|
+
programId: programConfig.id,
|
|
3561
|
+
integration: run.integrationLabel,
|
|
3562
|
+
runId: analytics.runId,
|
|
3563
|
+
build: analytics.build,
|
|
3564
|
+
skillId: run.skillId
|
|
3565
|
+
});
|
|
3566
|
+
await refreshAccessTokenIfNeeded(session);
|
|
3567
|
+
const credentials = session.credentials;
|
|
3568
|
+
const switchboard = {
|
|
3569
|
+
program: programConfig.id,
|
|
3570
|
+
composed,
|
|
3571
|
+
flags: wizardFlags,
|
|
3572
|
+
flagPayloads: wizardFlagPayloads,
|
|
3573
|
+
cliHarness: session.harness,
|
|
3574
|
+
cliSequence: session.sequence,
|
|
3575
|
+
cliModel: session.model
|
|
3576
|
+
};
|
|
3577
|
+
const binding = resolveBinding(switchboard);
|
|
3578
|
+
analytics.setTag("sequence", binding.sequence);
|
|
3579
|
+
analytics.setTag("harness", binding.harness);
|
|
3580
|
+
wizardMetadata.SEQUENCE = binding.sequence;
|
|
3581
|
+
wizardMetadata.HARNESS = binding.harness;
|
|
3582
|
+
captureSwitchboardDecision(switchboard, binding);
|
|
3583
|
+
const ui = getUI();
|
|
3584
|
+
registerCleanup(() => {
|
|
3585
|
+
const report = flushScanReport({ yaraReport: session.yaraReport });
|
|
3586
|
+
if (report) ui.log.info(report);
|
|
3587
|
+
});
|
|
3588
|
+
if (binding.sequence === "linear") ui.onEnterScreen("outro", () => restoreClaudeSettings(session.installDir));
|
|
3589
|
+
const framework = session.integration ?? session.skillId ?? void 0;
|
|
3590
|
+
const result = await runAgent$1({
|
|
3591
|
+
programId: programConfig.id,
|
|
3592
|
+
run,
|
|
3593
|
+
composed,
|
|
3594
|
+
binding,
|
|
3595
|
+
switchboard,
|
|
3596
|
+
skillsBaseUrl: getSkillsBaseUrl(),
|
|
3597
|
+
wizardFlags,
|
|
3598
|
+
wizardFlagPayloads,
|
|
3599
|
+
wizardMetadata,
|
|
3600
|
+
allowedTools: programConfig.allowedTools,
|
|
3601
|
+
disallowedTools: programConfig.disallowedTools,
|
|
3602
|
+
agentFlow: programConfig.agentFlow,
|
|
3603
|
+
excludedTaskTypes: programConfig.excludedTaskTypes,
|
|
3604
|
+
seedTasks: programConfig.seedTasks ? () => programConfig.seedTasks(session) : void 0,
|
|
3605
|
+
hooks: {
|
|
3606
|
+
postRun: run.postRun ? (creds) => run.postRun(session, creds) : void 0,
|
|
3607
|
+
buildOutroData: run.buildOutroData ? (creds) => run.buildOutroData(session, creds) ?? void 0 : void 0,
|
|
3608
|
+
buildOutroNextSteps: run.buildOutroNextSteps ? (creds, completed) => run.buildOutroNextSteps(session, creds, completed) : void 0,
|
|
3609
|
+
recordTaskOutcomes: (outcomes) => {
|
|
3610
|
+
session.frameworkContext[TASK_OUTCOMES_KEY] = outcomes;
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
}, {
|
|
3614
|
+
installDir: session.installDir,
|
|
3615
|
+
credentials,
|
|
3616
|
+
project: session.apiProject,
|
|
3617
|
+
apiUser: session.apiUser,
|
|
3618
|
+
skillId: session.skillId ?? void 0,
|
|
3619
|
+
integration: session.integration,
|
|
3620
|
+
frameworkDocsUrl: framework ? FRAMEWORK_REGISTRY[framework]?.metadata.docsUrl : void 0,
|
|
3621
|
+
flags: {
|
|
3622
|
+
ci: session.ci,
|
|
3623
|
+
signup: session.signup,
|
|
3624
|
+
debug: session.debug,
|
|
3625
|
+
e2eAsk: session.e2eAsk,
|
|
3626
|
+
localMcp: session.localMcp,
|
|
3627
|
+
captureAio: session.captureAio,
|
|
3628
|
+
benchmark: session.benchmark,
|
|
3629
|
+
yaraReport: session.yaraReport
|
|
3630
|
+
},
|
|
3631
|
+
host: {
|
|
3632
|
+
baseUrl: session.baseUrl,
|
|
3633
|
+
region: session.region,
|
|
3634
|
+
email: session.email,
|
|
3635
|
+
projectId: session.projectId,
|
|
3636
|
+
apiKey: session.apiKey
|
|
3637
|
+
}
|
|
3638
|
+
}, {
|
|
3639
|
+
onProgress: createUiReducer(ui),
|
|
3640
|
+
interaction: uiInteraction(ui)
|
|
3641
|
+
});
|
|
3642
|
+
if (result.outcome === "crashed") throw result.failure.error;
|
|
3643
|
+
if (result.outcome !== "success") {
|
|
3644
|
+
if (result.failure.authErrorDetail) ui.showAuthError(result.failure.authErrorDetail);
|
|
3645
|
+
await wizardAbort({
|
|
3646
|
+
...result.failure,
|
|
3647
|
+
status: result.outcome === "aborted" ? "cancelled" : "error"
|
|
3648
|
+
});
|
|
3649
|
+
} else if (!composed) try {
|
|
3650
|
+
await analytics.shutdown("success");
|
|
3651
|
+
} catch (error) {
|
|
3652
|
+
logToFile("[agent-runner] analytics shutdown failed:", error);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
async function runHealthGate(session, programConfig) {
|
|
3656
|
+
const hasHealthCheckScreen = programConfig.steps.some((s) => s.screenId === "health-check");
|
|
3657
|
+
if (session.readinessResult) logToFile(`[agent-runner] readiness pre-computed by TUI: decision=${session.readinessResult.decision}${session.outageDismissed ? " (outage dismissed by user)" : ""} — skipping re-check`);
|
|
3658
|
+
if (!hasHealthCheckScreen || session.readinessResult) return;
|
|
3659
|
+
logToFile("[agent-runner] evaluating wizard readiness");
|
|
3660
|
+
const readinessConfig = session.signup ? SIGNUP_WIZARD_READINESS_CONFIG : void 0;
|
|
3661
|
+
const readiness = await evaluateWizardReadiness(readinessConfig);
|
|
3662
|
+
logToFile(`[agent-runner] readiness=${readiness.decision}`);
|
|
3663
|
+
if (readiness.decision === "no") {
|
|
3664
|
+
const blockingLabels = getBlockingServiceKeys(readiness.health, readinessConfig).map((k) => `${SERVICE_LABELS[k]} (${readiness.health[k].status})`);
|
|
3665
|
+
logToFile(`[agent-runner] blocked by: ${blockingLabels.join(", ")}`);
|
|
3666
|
+
await getUI().showBlockingOutage(readiness);
|
|
3667
|
+
if (!isNonInteractiveEnvironment()) await wizardAbort({
|
|
3668
|
+
code: ErrorCodes.EnvServiceOutage,
|
|
3669
|
+
message: "Cannot start — external services are down:\n" + blockingLabels.map((l) => ` - ${l}`).join("\n") + "\n\nPlease try again later."
|
|
3670
|
+
});
|
|
3671
|
+
} else if (readiness.decision === "yes_with_warnings") getUI().setReadinessWarnings(readiness);
|
|
3672
|
+
}
|
|
3673
|
+
async function runSettingsGate(session) {
|
|
3674
|
+
const settingsConflicts = checkAllSettingsConflicts(session.installDir);
|
|
3675
|
+
logToFile(`[agent-runner] settings conflicts: ${settingsConflicts.length > 0 ? settingsConflicts.map((c) => `${c.source}(${c.keys.join(",")})`).join("; ") : "none"}`);
|
|
3676
|
+
if (settingsConflicts.length === 0) return;
|
|
3677
|
+
for (const conflict of settingsConflicts) {
|
|
3678
|
+
const level = conflict.source === "managed" ? "org" : conflict.source;
|
|
3679
|
+
analytics.wizardCapture("settings conflict detected", {
|
|
3680
|
+
level,
|
|
3681
|
+
keys: conflict.keys
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
const { autoFix, failClosed, warnOnly } = classifySettingsConflicts(settingsConflicts);
|
|
3685
|
+
for (const conflict of warnOnly) {
|
|
3686
|
+
logToFile(`[agent-runner] settings conflict in ${conflict.source} (${conflict.path}) neutralized by settingSources:['project'] — not blocking`);
|
|
3687
|
+
analytics.wizardCapture("settings conflict neutralized", {
|
|
3688
|
+
level: conflict.source,
|
|
3689
|
+
keys: conflict.keys
|
|
3690
|
+
});
|
|
3691
|
+
}
|
|
3692
|
+
let unfixable = failClosed;
|
|
3693
|
+
if (autoFix.length > 0) if (backupAndFixClaudeSettings(session.installDir)) {
|
|
3694
|
+
logToFile("[agent-runner] auto-neutralized writable settings conflict");
|
|
3695
|
+
analytics.wizardCapture("settings conflict auto-neutralized", { keys: autoFix.flatMap((c) => c.keys) });
|
|
3696
|
+
} else {
|
|
3697
|
+
logToFile("[agent-runner] could not back up writable settings conflict — failing closed");
|
|
3698
|
+
unfixable = [...failClosed, ...autoFix];
|
|
3699
|
+
}
|
|
3700
|
+
if (unfixable.length > 0) {
|
|
3701
|
+
if (isNonInteractiveEnvironment()) await wizardAbort({
|
|
3702
|
+
code: ErrorCodes.SettingsUnfixableConflict,
|
|
3703
|
+
message: "Cannot start — a Claude settings file redirects the agent away from the PostHog gateway and cannot be neutralized automatically:\n" + unfixable.map((c) => ` - ${c.source} (${c.path}): ${c.keys.join(", ")}`).join("\n") + "\n\nRemove the conflicting keys and re-run the wizard."
|
|
3704
|
+
});
|
|
3705
|
+
await getUI().showSettingsOverride(unfixable, () => backupAndFixClaudeSettings(session.installDir));
|
|
3706
|
+
logToFile("[agent-runner] settings override resolved");
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
/**
|
|
3710
|
+
* One event + one log line per run: what entered the switchboard, which
|
|
3711
|
+
* precedence rung decided each axis, and the final pick.
|
|
3712
|
+
*/
|
|
3713
|
+
function captureSwitchboardDecision(ctx, binding) {
|
|
3714
|
+
const trace = ctx.trace ?? {};
|
|
3715
|
+
const perTaskModel = binding.sequence === "orchestrator" && trace.model === "binding";
|
|
3716
|
+
const model = perTaskModel ? "chosen-per-task" : binding.model;
|
|
3717
|
+
const modelSource = perTaskModel ? "agent-prompts" : trace.model;
|
|
3718
|
+
analytics.wizardCapture("switchboard resolved", {
|
|
3719
|
+
program: ctx.program,
|
|
3720
|
+
flag_self_driving_use_pi_harness: ctx.flags[WIZARD_SELF_DRIVING_USE_PI_HARNESS_FLAG_KEY],
|
|
3721
|
+
flag_self_driving_pi_payload: JSON.stringify(ctx.flagPayloads?.["wizard-self-driving-use-pi-harness"] ?? null),
|
|
3722
|
+
flag_orchestrator: ctx.flags[WIZARD_ORCHESTRATOR_FLAG_KEY],
|
|
3723
|
+
cli_harness: ctx.cliHarness,
|
|
3724
|
+
cli_sequence: ctx.cliSequence,
|
|
3725
|
+
cli_model: ctx.cliModel,
|
|
3726
|
+
harness_source: trace.harness,
|
|
3727
|
+
model_source: modelSource,
|
|
3728
|
+
sequence_source: trace.sequence,
|
|
3729
|
+
harness: binding.harness,
|
|
3730
|
+
model,
|
|
3731
|
+
thinking_level: binding.thinkingLevel,
|
|
3732
|
+
sequence: binding.sequence
|
|
3733
|
+
});
|
|
3734
|
+
logToFile(`[switchboard] decision: program=${ctx.program} in(orchestrator=${ctx.flags["wizard-orchestrator"] ?? "-"}, cli=${ctx.cliHarness ?? "-"}/${ctx.cliSequence ?? "-"}/${ctx.cliModel ?? "-"}) → harness=${binding.harness} (${trace.harness ?? "?"}) model=${model} (${modelSource ?? "?"}) sequence=${binding.sequence} (${trace.sequence ?? "?"})`);
|
|
3735
|
+
}
|
|
3736
|
+
//#endregion
|
|
3737
|
+
export { createProgramSequence as S, detectProjectsWithAgent as _, getAuditChecks as a, detectFramework as b, reportWarehouseSourcesDetected as c, findPackageJsons as d, WAREHOUSE_ABORT_CASES as f, AgenticDetectionTimeoutError as g, detectWarehouseSources as h, AUDIT_SEVERITY_STYLE as i, POSTHOG_SDKS as l, getDetectedWarehouseSources as m, run_agent_legacy_exports as n, detectPostHogIntegration as o, detectWarehousePrerequisites as p, AUDIT_CHECKS_KEY as r, maybeStampAiSdkDetected as s, runProgramAgent as t, STRIPE_SDKS as u, resolveProjectDir as v, authenticate as x, gatherFrameworkContext as y };
|
|
3738
|
+
|
|
3739
|
+
//# sourceMappingURL=run-agent-legacy-d7rWlzQR.js.map
|