@posthog/wizard 2.18.0 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/{add-mcp-server-to-clients-DnPwZl1P.js → add-mcp-server-to-clients-CjnvTVj0.js} +72 -13
  2. package/dist/add-mcp-server-to-clients-CjnvTVj0.js.map +1 -0
  3. package/dist/{agent-interface-C2VEF-BD.js → agent-interface-CQU6x4Hj.js} +8 -6
  4. package/dist/agent-interface-CQU6x4Hj.js.map +1 -0
  5. package/dist/{agent-runner-Dw8cjZoN.js → agent-runner-Cj7saDkL.js} +14 -10
  6. package/dist/{agent-runner-Dw8cjZoN.js.map → agent-runner-Cj7saDkL.js.map} +1 -1
  7. package/dist/{analytics-C-zcTO6g.js → analytics-Df-Xb81i.js} +2 -2
  8. package/dist/{analytics-C-zcTO6g.js.map → analytics-Df-Xb81i.js.map} +1 -1
  9. package/dist/{api-B3MWP3vm.js → api-Dw6_orDE.js} +3 -3
  10. package/dist/{api-B3MWP3vm.js.map → api-Dw6_orDE.js.map} +1 -1
  11. package/dist/bin.js +33 -31
  12. package/dist/bin.js.map +1 -1
  13. package/dist/{ci-install-DLuSmSq6.js → ci-install-BKAvFfK6.js} +4 -4
  14. package/dist/{ci-install-DLuSmSq6.js.map → ci-install-BKAvFfK6.js.map} +1 -1
  15. package/dist/{debug--gQGudnY.js → debug-Cp_wNn8i.js} +1 -1
  16. package/dist/{debug-BorYMfpE.js → debug-DnMO6O8O.js} +36 -21
  17. package/dist/debug-DnMO6O8O.js.map +1 -0
  18. package/dist/{defaults-DA3-9dHT.js → defaults-BNWIWzjc.js} +34 -8
  19. package/dist/defaults-BNWIWzjc.js.map +1 -0
  20. package/dist/{environment-DIOtLqTQ.js → environment-Ls0H9ljT.js} +3 -3
  21. package/dist/{environment-DIOtLqTQ.js.map → environment-Ls0H9ljT.js.map} +1 -1
  22. package/dist/{interactive-DjGjlvY3.js → interactive-D15byhpc.js} +2 -2
  23. package/dist/{interactive-DjGjlvY3.js.map → interactive-D15byhpc.js.map} +1 -1
  24. package/dist/{mcp-prompt-streaming-Dm47tmiy.js → mcp-prompt-streaming-DQOTQfW1.js} +4 -4
  25. package/dist/{mcp-prompt-streaming-Dm47tmiy.js.map → mcp-prompt-streaming-DQOTQfW1.js.map} +1 -1
  26. package/dist/{non-interactive-C2f3Gwva.js → non-interactive-DcFLJtl_.js} +2 -2
  27. package/dist/{non-interactive-C2f3Gwva.js.map → non-interactive-DcFLJtl_.js.map} +1 -1
  28. package/dist/{package-manager-Bl2KOUFK.js → package-manager-DUPgLGpQ.js} +2 -2
  29. package/dist/{package-manager-Bl2KOUFK.js.map → package-manager-DUPgLGpQ.js.map} +1 -1
  30. package/dist/{playground-ZLG68cvx.js → playground-BZ0hGjbL.js} +12 -4
  31. package/dist/playground-BZ0hGjbL.js.map +1 -0
  32. package/dist/{posthog-integration-B_DLodqr.js → posthog-integration-C8qhJnI3.js} +13 -11
  33. package/dist/posthog-integration-C8qhJnI3.js.map +1 -0
  34. package/dist/{provisioning-Bk4E6VYn.js → provisioning-C-2ExcqY.js} +3 -3
  35. package/dist/{provisioning-Bk4E6VYn.js.map → provisioning-C-2ExcqY.js.map} +1 -1
  36. package/dist/{registry-DMM3UmZD.js → registry-hBUgaWFx.js} +4 -4
  37. package/dist/{registry-DMM3UmZD.js.map → registry-hBUgaWFx.js.map} +1 -1
  38. package/dist/{setup-utils-Df9ezAjZ.js → setup-utils-DetnhXo0.js} +33 -19
  39. package/dist/setup-utils-DetnhXo0.js.map +1 -0
  40. package/dist/{slides-DwvXZ8iS.js → slides-mT2s9wM_.js} +163 -70
  41. package/dist/slides-mT2s9wM_.js.map +1 -0
  42. package/dist/{start-tui-P9aMwBzt.js → start-tui-BfXoErKg.js} +15 -14
  43. package/dist/start-tui-BfXoErKg.js.map +1 -0
  44. package/dist/{steps-RCRZbLjZ.js → steps-SoDXSUxe.js} +6 -6
  45. package/dist/{steps-RCRZbLjZ.js.map → steps-SoDXSUxe.js.map} +1 -1
  46. package/dist/{telemetry-CMbVbpaY.js → telemetry-CPcMFxcO.js} +2 -2
  47. package/dist/{telemetry-CMbVbpaY.js.map → telemetry-CPcMFxcO.js.map} +1 -1
  48. package/dist/{urls-BzG_Jtw9.js → urls-BO7doNJG.js} +2 -2
  49. package/dist/{urls-BzG_Jtw9.js.map → urls-BO7doNJG.js.map} +1 -1
  50. package/dist/{wizard-abort-QuKm_B5z.js → wizard-abort-CDXufkqJ.js} +5 -3
  51. package/dist/wizard-abort-CDXufkqJ.js.map +1 -0
  52. package/dist/{wizard-abort-Dl8WJQgJ.js → wizard-abort-CtMY57ZE.js} +1 -1
  53. package/package.json +1 -1
  54. package/dist/add-mcp-server-to-clients-DnPwZl1P.js.map +0 -1
  55. package/dist/agent-interface-C2VEF-BD.js.map +0 -1
  56. package/dist/debug-BorYMfpE.js.map +0 -1
  57. package/dist/defaults-DA3-9dHT.js.map +0 -1
  58. package/dist/playground-ZLG68cvx.js.map +0 -1
  59. package/dist/posthog-integration-B_DLodqr.js.map +0 -1
  60. package/dist/setup-utils-Df9ezAjZ.js.map +0 -1
  61. package/dist/slides-DwvXZ8iS.js.map +0 -1
  62. package/dist/start-tui-P9aMwBzt.js.map +0 -1
  63. package/dist/wizard-abort-QuKm_B5z.js.map +0 -1
@@ -1,11 +1,11 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-B_-DWIq7.js";
2
- import { A as OAUTH_TIMEOUT_MS, B as WIZARD_OAUTH_SCOPES, D as ISSUES_URL, E as DUMMY_PROJECT_API_KEY, I as POSTHOG_PROXY_CLIENT_ID, P as POSTHOG_OAUTH_URL, W as WIZARD_USER_AGENT, k as OAUTH_PORTS, p as getUI, s as logToFile, w as DEFAULT_HOST_URL } from "./debug-BorYMfpE.js";
3
- import { t as analytics } from "./analytics-C-zcTO6g.js";
4
- import { t as withProgress } from "./telemetry-CMbVbpaY.js";
5
- import { n as getCloudUrlFromRegion, r as getHostFromRegion, t as detectRegionFromToken } from "./urls-BzG_Jtw9.js";
6
- import { t as provisionNewAccount } from "./provisioning-Bk4E6VYn.js";
7
- import { i as fetchUserData, r as fetchProjectData } from "./api-B3MWP3vm.js";
8
- import { r as wizardAbort } from "./wizard-abort-QuKm_B5z.js";
2
+ import { A as OAUTH_TIMEOUT_MS, B as WIZARD_OAUTH_SCOPES, D as ISSUES_URL, E as DUMMY_PROJECT_API_KEY, I as POSTHOG_PROXY_CLIENT_ID, P as POSTHOG_OAUTH_URL, W as WIZARD_USER_AGENT, k as OAUTH_PORTS, p as getUI, s as logToFile, w as DEFAULT_HOST_URL } from "./debug-DnMO6O8O.js";
3
+ import { t as analytics } from "./analytics-Df-Xb81i.js";
4
+ import { t as withProgress } from "./telemetry-CPcMFxcO.js";
5
+ import { n as getCloudUrlFromRegion, r as getHostFromRegion, t as detectRegionFromToken } from "./urls-BO7doNJG.js";
6
+ import { t as provisionNewAccount } from "./provisioning-C-2ExcqY.js";
7
+ import { i as fetchUserData, r as fetchProjectData } from "./api-Dw6_orDE.js";
8
+ import { r as wizardAbort } from "./wizard-abort-CDXufkqJ.js";
9
9
  import { major, minVersion } from "semver";
10
10
  import * as childProcess from "node:child_process";
11
11
  import { execSync } from "node:child_process";
@@ -295,6 +295,7 @@ async function startCallbackServer(authUrl, signupUrl, port) {
295
295
  const error = url.searchParams.get("error");
296
296
  if (error) {
297
297
  const isAccessDenied = error === "access_denied";
298
+ logToFile(`[oauth] callback received with error: ${error.replace(/[^\x20-\x7e]/g, "").slice(0, 200)}`);
298
299
  res.writeHead(isAccessDenied ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
299
300
  res.end(`
300
301
  <html>
@@ -314,6 +315,7 @@ async function startCallbackServer(authUrl, signupUrl, port) {
314
315
  return;
315
316
  }
316
317
  if (code) {
318
+ logToFile("[oauth] callback received with authorization code");
317
319
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
318
320
  res.end(`
319
321
  <html>
@@ -390,23 +392,34 @@ function isPortInUseError(error) {
390
392
  }
391
393
  async function exchangeCodeForToken(code, codeVerifier, callbackUrl) {
392
394
  const clientId = POSTHOG_PROXY_CLIENT_ID;
393
- const response = await axios.post(`${POSTHOG_OAUTH_URL}/oauth/token`, {
394
- grant_type: "authorization_code",
395
- code,
396
- redirect_uri: callbackUrl,
397
- client_id: clientId,
398
- code_verifier: codeVerifier
399
- }, { headers: {
400
- "Content-Type": "application/json",
401
- "User-Agent": WIZARD_USER_AGENT
402
- } });
403
- return OAuthTokenResponseSchema.parse(response.data);
395
+ logToFile(`[oauth] exchanging code for token at ${POSTHOG_OAUTH_URL}/oauth/token`);
396
+ let response;
397
+ try {
398
+ response = await axios.post(`${POSTHOG_OAUTH_URL}/oauth/token`, {
399
+ grant_type: "authorization_code",
400
+ code,
401
+ redirect_uri: callbackUrl,
402
+ client_id: clientId,
403
+ code_verifier: codeVerifier
404
+ }, { headers: {
405
+ "Content-Type": "application/json",
406
+ "User-Agent": WIZARD_USER_AGENT
407
+ } });
408
+ } catch (e) {
409
+ const status = axios.isAxiosError(e) ? e.response?.status : void 0;
410
+ logToFile(`[oauth] token exchange failed${status ? ` (HTTP ${status})` : ""}:`, e instanceof Error ? e.message : e);
411
+ throw e;
412
+ }
413
+ const token = OAuthTokenResponseSchema.parse(response.data);
414
+ logToFile(`[oauth] token exchange succeeded, granted scopes: ${token.scope}${token.scoped_teams ? `, scoped_teams: [${token.scoped_teams.join(", ")}]` : ""}${token.scoped_organizations ? `, scoped_organizations: ${token.scoped_organizations.length}` : ""}`);
415
+ return token;
404
416
  }
405
417
  async function performOAuthFlow(config) {
406
418
  const clientId = POSTHOG_PROXY_CLIENT_ID;
407
419
  const codeVerifier = generateCodeVerifier();
408
420
  const codeChallenge = generateCodeChallenge(codeVerifier);
409
421
  let shouldRetry = false;
422
+ logToFile(`[oauth] starting flow against ${POSTHOG_OAUTH_URL} (prod client), requested scopes: ${config.scopes.join(" ")}`);
410
423
  do {
411
424
  shouldRetry = false;
412
425
  let lastProcessInfo = null;
@@ -455,6 +468,7 @@ async function performOAuthFlow(config) {
455
468
  loginSpinner.stop("Authorization failed.");
456
469
  server.close();
457
470
  const error = e instanceof Error ? e : /* @__PURE__ */ new Error("Unknown error");
471
+ logToFile("[oauth] flow failed:", error);
458
472
  if (error.message.includes("timeout")) getUI().log.error("Authorization timed out. Please try again.");
459
473
  else if (error.message.includes("access_denied")) getUI().log.info(`Authorization was cancelled.\n\nYou denied access to PostHog. To use the wizard, you need to authorize access to your PostHog account.\n\nYou can try again by re-running the wizard.`);
460
474
  else getUI().log.error(`Authorization failed:\n\n${error.message}\n\nIf you think this is a bug in the PostHog wizard, please create an issue:\n${ISSUES_URL}`);
@@ -784,4 +798,4 @@ async function askForProvisioningSignup(email, region) {
784
798
  //#endregion
785
799
  export { createVersionBucket as a, tryGetPackageJson as i, isUsingTypeScript as n, extractOAuthCode as o, setup_utils_exports as r, detectAllPackageManagers as s, getOrAskForProjectData as t };
786
800
 
787
- //# sourceMappingURL=setup-utils-Df9ezAjZ.js.map
801
+ //# sourceMappingURL=setup-utils-DetnhXo0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-utils-DetnhXo0.js","names":["fs","path","fs"],"sources":["../src/utils/package-manager.ts","../src/lib/oauth/program-scopes.ts","../src/utils/oauth.ts","../src/utils/semver.ts","../src/utils/setup-utils.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { withProgress } from '../telemetry';\nimport { getPackageDotJson, updatePackageDotJson } from './setup-utils';\nimport type { PackageJson } from './package-json';\nimport { analytics } from './analytics';\nimport type { WizardRunOptions } from './types';\n\ntype InstallDirOpt = Pick<WizardRunOptions, 'installDir'>;\n\nexport interface PackageManager {\n name: string;\n label: string;\n installCommand: string;\n buildCommand: string;\n /** Command the manager uses to execute a `package.json` script. */\n runScriptCommand: string;\n flags: string;\n detect: (opts: InstallDirOpt) => boolean;\n addOverride: (\n pkgName: string,\n pkgVersion: string,\n opts: InstallDirOpt,\n ) => Promise<void>;\n}\n\nfunction hasLockfile(installDir: string, file: string): boolean {\n return fs.existsSync(path.join(installDir, file));\n}\n\nfunction lockfileHeaderContains(\n installDir: string,\n file: string,\n needle: string,\n): boolean {\n try {\n const head = fs\n .readFileSync(path.join(installDir, file), 'utf-8')\n .slice(0, 500);\n return head.includes(needle);\n } catch {\n return false;\n }\n}\n\ntype OverrideSlot = 'npm' | 'yarn' | 'pnpm';\n\nasync function writeOverride(\n slot: OverrideSlot,\n pkgName: string,\n pkgVersion: string,\n { installDir }: InstallDirOpt,\n): Promise<void> {\n const pkg = await getPackageDotJson({ installDir });\n let next: PackageJson;\n if (slot === 'yarn') {\n next = {\n ...pkg,\n resolutions: { ...(pkg.resolutions ?? {}), [pkgName]: pkgVersion },\n };\n } else if (slot === 'pnpm') {\n next = {\n ...pkg,\n pnpm: {\n ...(pkg.pnpm ?? {}),\n overrides: { ...(pkg.pnpm?.overrides ?? {}), [pkgName]: pkgVersion },\n },\n };\n } else {\n next = {\n ...pkg,\n overrides: { ...(pkg.overrides ?? {}), [pkgName]: pkgVersion },\n };\n }\n await updatePackageDotJson(next, { installDir });\n}\n\nexport const BUN: PackageManager = {\n name: 'bun',\n label: 'Bun',\n installCommand: 'bun add',\n buildCommand: 'bun run build',\n runScriptCommand: 'bun run',\n flags: '',\n detect: ({ installDir }) =>\n hasLockfile(installDir, 'bun.lockb') || hasLockfile(installDir, 'bun.lock'),\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('npm', pkgName, pkgVersion, opts),\n};\n\nexport const YARN_V1: PackageManager = {\n name: 'yarn',\n label: 'Yarn V1',\n installCommand: 'yarn add',\n buildCommand: 'yarn build',\n runScriptCommand: 'yarn',\n flags: '--ignore-workspace-root-check',\n detect: ({ installDir }) =>\n lockfileHeaderContains(installDir, 'yarn.lock', 'yarn lockfile v1'),\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('yarn', pkgName, pkgVersion, opts),\n};\n\n/** YARN V2/3/4 */\nexport const YARN_V2: PackageManager = {\n name: 'yarn',\n label: 'Yarn V2/3/4',\n installCommand: 'yarn add',\n buildCommand: 'yarn build',\n runScriptCommand: 'yarn',\n flags: '',\n detect: ({ installDir }) =>\n lockfileHeaderContains(installDir, 'yarn.lock', '__metadata'),\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('yarn', pkgName, pkgVersion, opts),\n};\n\nexport const PNPM: PackageManager = {\n name: 'pnpm',\n label: 'pnpm',\n installCommand: 'pnpm add',\n buildCommand: 'pnpm build',\n runScriptCommand: 'pnpm',\n flags: '--ignore-workspace-root-check',\n detect: ({ installDir }) => hasLockfile(installDir, 'pnpm-lock.yaml'),\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('pnpm', pkgName, pkgVersion, opts),\n};\n\nexport const NPM: PackageManager = {\n name: 'npm',\n label: 'npm',\n installCommand: 'npm add',\n buildCommand: 'npm run build',\n runScriptCommand: 'npm run',\n flags: '',\n detect: ({ installDir }) => hasLockfile(installDir, 'package-lock.json'),\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('npm', pkgName, pkgVersion, opts),\n};\n\n// Expo is selected by upstream config (app.json / app.config.*) rather than\n// a lockfile, so its detect always returns false here.\nexport const EXPO: PackageManager = {\n name: 'expo',\n label: 'Expo',\n installCommand: 'npx expo install',\n buildCommand: 'npx expo build',\n runScriptCommand: 'npx expo run',\n flags: '',\n detect: () => false,\n addOverride: (pkgName, pkgVersion, opts) =>\n writeOverride('npm', pkgName, pkgVersion, opts),\n};\n\nexport const packageManagers: PackageManager[] = [\n BUN,\n YARN_V1,\n YARN_V2,\n PNPM,\n NPM,\n EXPO,\n];\n\nexport function detectAllPackageManagers({\n installDir,\n}: InstallDirOpt): PackageManager[] {\n return withProgress('detect-package-manager', () => {\n const matches = packageManagers.filter((pm) => pm.detect({ installDir }));\n if (matches.length === 0) {\n analytics.setTag('package-manager', 'not-detected');\n }\n return matches;\n });\n}\n","/**\n * OAuth scope resolver — every program starts from the shared\n * `WIZARD_OAUTH_SCOPES` base set and a program can layer additional\n * scopes on top via `PROGRAM_SCOPE_ADDITIONS`.\n *\n * final scope set = WIZARD_OAUTH_SCOPES ∪ programAdditions\n *\n * Additions are merged in declaration order and deduped, so a program\n * never accidentally weakens the base set — only widens it. Programs\n * not listed in `PROGRAM_SCOPE_ADDITIONS` request the unchanged\n * base set, exactly like before.\n *\n * Today only `McpTutorial` adds anything: read-only on every product\n * surface (feature flags, experiments, surveys, replays, errors, web\n * analytics, LLM analytics, cohorts, persons) plus read/write on\n * annotations. Persistence writes (dashboard:write, insight:write,\n * notebook:write, query:read) come for free from the base set, so the\n * tutorial's \"save as insight / pin to dashboard / add to notebook\"\n * follow-ups keep working.\n *\n * Add a new program override by extending `PROGRAM_SCOPE_ADDITIONS`\n * below — no other call-site changes required as long as the program's\n * `programId` is threaded into `getOrAskForProjectData`.\n */\n\n// IMPORTANT: type-only import. A value import would create a circular\n// dependency (setup-utils → program-scopes → program-registry →\n// posthog-integration → ... → setup-utils), and `Program` would be\n// read as `undefined` at module init. Keep this type-only and reference\n// program IDs by their string-literal value below — TypeScript still\n// catches renames via the `Partial<Record<ProgramId, ...>>` keying.\nimport type { ProgramId } from '@lib/programs/program-registry';\nimport { WIZARD_OAUTH_SCOPES } from '@lib/constants';\n\n/**\n * Extra scopes the MCP tutorial needs on top of `WIZARD_OAUTH_SCOPES`.\n *\n * Mirrors the wizard partner's full OAuth ceiling on the PostHog side\n * (see the comma-delimited list in the wizard OAuth app's\n * `OAuthApplication.scopes`). The tutorial's prompts and follow-ups\n * touch most of the read surface, plus annotation write for the\n * \"PostHog wizard install\" verify-prompt.\n *\n * Already in the base `WIZARD_OAUTH_SCOPES` (and therefore not\n * repeated here):\n * • user:read, project:read, llm_gateway:read — auth + gateway\n * • query:read — HogQL\n * • dashboard:write, insight:write, notebook:write — Phase-5 persist\n *\n * Deliberately omitted (writes on read-only product surfaces):\n * • feature_flag:write, experiment:write, survey:write,\n * cohort:write, session_recording:write, error_tracking:write,\n * alert:write, subscription:write\n */\nexport const MCP_TUTORIAL_SCOPE_ADDITIONS = [\n // Explicit reads on the persistence surfaces. `*:write` usually\n // implies read on PostHog, but the consent flow grants exactly the\n // strings requested — explicit reads avoid a 403 when the agent\n // lists existing dashboards/insights/notebooks before saving.\n 'dashboard:read',\n 'insight:read',\n 'notebook:read',\n\n // Read on every product surface the tutorial demos.\n 'feature_flag:read',\n 'experiment:read',\n 'experiment_saved_metric:read',\n 'survey:read',\n 'session_recording:read',\n 'error_tracking:read',\n 'web_analytics:read',\n 'llm_analytics:read',\n 'cohort:read',\n 'person:read',\n\n // Annotation read + write — the verify prompt's \"annotate today\"\n // is the only mutation the tutorial performs outside the\n // dashboard/insight/notebook persistence triplet.\n 'annotation:read',\n 'annotation:write',\n\n // Metadata / exploration reads — for \"break down by user property\",\n // \"did that change land alongside a deploy\", autocapture actions,\n // etc. Otherwise the agent 403s on the supporting catalog calls\n // even though the parent query has `query:read`.\n 'activity_log:read',\n 'property_definition:read',\n 'event_definition:read',\n 'action:read',\n\n // Data warehouse reads — for the data-role cross-sells that join\n // event data with Stripe / Salesforce / S3.\n 'warehouse_table:read',\n 'warehouse_view:read',\n\n // Inspection-only — we don't write alerts or subscriptions, but the\n // model might want to read existing ones (e.g. \"is there already an\n // alert on this metric?\").\n 'alert:read',\n 'subscription:read',\n] as const;\n\n/**\n * Extra scopes the agent-skill program needs on top of `WIZARD_OAUTH_SCOPES`.\n *\n * Skills under this program (e.g. `creating-product-tours`) create feature\n * flags during the install flow. PostHog's consent grants exactly the scope\n * strings requested — `:write` does not imply `:read` — so listing existing\n * flags to avoid key collisions needs `feature_flag:read` explicitly.\n * `property_definition:read` lets the agent discover person properties when\n * building flag rollout filters instead of having to ask the user verbatim.\n */\nexport const AGENT_SKILL_SCOPE_ADDITIONS = [\n 'feature_flag:read',\n 'feature_flag:write',\n 'property_definition:read',\n] as const;\n\n/**\n * Per-program scope additions, layered on top of `WIZARD_OAUTH_SCOPES`.\n *\n * Programs not listed here request the unchanged base set. Use this\n * map only for programs that need *more* than the base — never for\n * narrowing, since narrowing risks breaking shared infrastructure\n * (e.g. dropping `llm_gateway:read` would 401 every agent call).\n *\n * Keyed by `ProgramId` so TypeScript catches stale entries when a\n * program is renamed or removed.\n */\nconst PROGRAM_SCOPE_ADDITIONS: Partial<Record<ProgramId, readonly string[]>> = {\n // String literal (not `Program.McpTutorial`) to avoid a runtime cycle\n // with `program-registry.ts`. The `Partial<Record<ProgramId, ...>>`\n // key constraint catches renames at compile time — if `mcpTutorialConfig.id`\n // ever changes, this line will fail to type-check.\n 'mcp-tutorial': MCP_TUTORIAL_SCOPE_ADDITIONS,\n 'agent-skill': AGENT_SKILL_SCOPE_ADDITIONS,\n};\n\n/**\n * Resolve the OAuth scope list to request for a given program. Returns\n * `WIZARD_OAUTH_SCOPES` for programs without an addition entry; for\n * programs that do have one, returns the union of base + additions\n * with duplicates dropped (declaration order preserved, base first).\n *\n * `null` / `undefined` programId falls through to the default — same\n * behavior as the historical hardcoded `WIZARD_OAUTH_SCOPES` reference\n * in `askForWizardLogin`, so call sites that haven't been updated to\n * pass a programId continue to work unchanged.\n */\nexport function getOAuthScopesForProgram(\n programId: ProgramId | null | undefined,\n): readonly string[] {\n const additions = (programId && PROGRAM_SCOPE_ADDITIONS[programId]) || [];\n if (additions.length === 0) {\n return WIZARD_OAUTH_SCOPES;\n }\n // Dedupe while preserving order; base scopes appear first so the\n // consent screen shows them in their familiar slot.\n const seen = new Set<string>();\n const merged: string[] = [];\n for (const s of [...WIZARD_OAUTH_SCOPES, ...additions]) {\n if (seen.has(s)) continue;\n seen.add(s);\n merged.push(s);\n }\n return merged;\n}\n","import * as crypto from 'node:crypto';\nimport * as http from 'node:http';\nimport { execSync } from 'node:child_process';\nimport axios from 'axios';\nimport { logToFile } from './debug';\nimport opn from 'opn';\nimport { z } from 'zod';\nimport { getUI } from '@ui';\nimport {\n IS_DEV,\n ISSUES_URL,\n OAUTH_PORTS,\n OAUTH_TIMEOUT_MS,\n POSTHOG_DEV_CLIENT_ID,\n POSTHOG_OAUTH_URL,\n POSTHOG_PROXY_CLIENT_ID,\n WIZARD_USER_AGENT,\n} from '@lib/constants';\nimport { NODE_ENV } from '@env';\nimport { abort } from './setup-utils';\nimport { analytics } from './analytics';\n\nconst OAUTH_CALLBACK_STYLES = `\n <style>\n * {\n font-family: monospace;\n background-color: #1b0a00;\n color: #F7A502;\n font-weight: medium;\n font-size: 24px;\n margin: .25rem;\n }\n\n .blink {\n animation: blink-animation 1s steps(2, start) infinite;\n }\n\n @keyframes blink-animation {\n to {\n opacity: 0;\n }\n }\n </style>\n`;\n\nconst OAuthTokenResponseSchema = z.object({\n access_token: z.string(),\n expires_in: z.number(),\n token_type: z.string(),\n scope: z.string(),\n refresh_token: z.string().optional(),\n scoped_teams: z.array(z.number()).optional(),\n scoped_organizations: z.array(z.string()).optional(),\n});\n\nexport type OAuthTokenResponse = z.infer<typeof OAuthTokenResponseSchema>;\n\ninterface OAuthConfig {\n scopes: string[];\n signup?: boolean;\n}\n\nfunction getLocalOAuthOrigin(port: number): string {\n return `http://localhost:${port}`;\n}\n\nfunction getCallbackUrl(port: number): string {\n return `${getLocalOAuthOrigin(port)}/callback`;\n}\n\nfunction getLocalLoginUrl(port: number): string {\n return `${getLocalOAuthOrigin(port)}/authorize`;\n}\n\nfunction getLocalSignupUrl(port: number): string {\n return `${getLocalLoginUrl(port)}?signup=true`;\n}\n\n/**\n * Extract an OAuth authorization code from raw user input. Accepts either the\n * bare code, the full callback URL the browser was redirected to\n * (`http://localhost:8239/callback?code=abc123&...`), or just the query\n * string. Returns null when no code can be found.\n *\n * This backs the manual-entry fallback: in headless/remote environments the\n * browser can't reach the wizard's local callback server, so the user copies\n * the failed callback URL (or the code from it) back into the terminal.\n */\nexport function extractOAuthCode(input: string): string | null {\n const trimmed = input.trim();\n if (!trimmed) return null;\n\n // Full URL — pull the `code` query param.\n let looksLikeUrl = false;\n try {\n const url = new URL(trimmed);\n looksLikeUrl = true;\n const code = url.searchParams.get('code');\n if (code) return code;\n } catch {\n // Not a parseable URL — fall through to the looser checks below.\n }\n\n // A pasted query string or `code=...` fragment.\n const match = trimmed.match(/[?&]?code=([^&\\s]+)/);\n if (match) return decodeURIComponent(match[1]);\n\n // A URL with no code is invalid — don't mistake the whole URL for a code.\n if (looksLikeUrl) return null;\n\n // Otherwise treat the whole input as the bare code (no embedded whitespace).\n if (!/\\s/.test(trimmed)) return trimmed;\n\n return null;\n}\n\nfunction generateCodeVerifier(): string {\n return crypto.randomBytes(32).toString('base64url');\n}\n\nfunction generateCodeChallenge(verifier: string): string {\n return crypto.createHash('sha256').update(verifier).digest('base64url');\n}\n\nasync function startCallbackServer(\n authUrl: string,\n signupUrl: string,\n port: number,\n): Promise<{\n port: number;\n server: http.Server;\n waitForCallback: () => Promise<string>;\n}> {\n return new Promise((resolve, reject) => {\n let callbackResolve: (code: string) => void;\n let callbackReject: (error: Error) => void;\n\n const waitForCallback = () =>\n new Promise<string>((res, rej) => {\n callbackResolve = res;\n callbackReject = rej;\n });\n\n const server = http.createServer((req, res) => {\n if (!req.url) {\n res.writeHead(400);\n res.end();\n return;\n }\n const url = new URL(req.url, getLocalOAuthOrigin(port));\n\n if (url.pathname === '/authorize') {\n const isSignup = url.searchParams.get('signup') === 'true';\n const redirectUrl = isSignup ? signupUrl : authUrl;\n res.writeHead(302, { Location: redirectUrl });\n res.end();\n return;\n }\n\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n\n if (error) {\n const isAccessDenied = error === 'access_denied';\n const safeError = error.replace(/[^\\x20-\\x7e]/g, '').slice(0, 200);\n logToFile(`[oauth] callback received with error: ${safeError}`);\n res.writeHead(isAccessDenied ? 200 : 400, {\n 'Content-Type': 'text/html; charset=utf-8',\n });\n res.end(`\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <title>PostHog wizard - Authorization ${\n isAccessDenied ? 'cancelled' : 'failed'\n }</title>\n ${OAUTH_CALLBACK_STYLES}\n </head>\n <body>\n <p>${\n isAccessDenied\n ? 'Authorization cancelled.'\n : `Authorization failed.`\n }</p>\n <p>Return to your terminal. This window will close automatically.</p>\n <script>window.close();</script>\n </body>\n </html>\n `);\n callbackReject(new Error(`OAuth error: ${error}`));\n return;\n }\n\n if (code) {\n logToFile('[oauth] callback received with authorization code');\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(`\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <title>PostHog wizard is ready</title>\n ${OAUTH_CALLBACK_STYLES}\n </head>\n <body>\n <p>PostHog login complete!</p>\n <p>Return to your terminal: the wizard is hard at work on your project<span class=\"blink\">█</span></p>\n <script>window.close();</script>\n </body>\n </html>\n `);\n callbackResolve(code);\n } else {\n res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(`\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <title>PostHog wizard - Invalid request</title>\n ${OAUTH_CALLBACK_STYLES}\n </head>\n <body>\n <p>Invalid request - no authorization code received.</p>\n <p>You can close this window.</p>\n </body>\n </html>\n `);\n }\n });\n\n server.listen(port, () => {\n resolve({ port, server, waitForCallback });\n });\n\n server.on('error', reject);\n });\n}\n\nfunction getPortProcessInfo(port: number): {\n command: string;\n pid: string;\n port: number;\n user: string;\n} {\n try {\n const output = execSync(`lsof -i :${port} -sTCP:LISTEN 2>/dev/null`, {\n encoding: 'utf-8',\n timeout: 3000,\n }).trim();\n const lines = output.split('\\n');\n // First line is header, second is the process\n if (lines.length < 2)\n return { command: 'unknown', pid: 'unknown', port, user: 'unknown' };\n const fields = lines[1].split(/\\s+/);\n // lsof columns: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\n const command = fields[0] ?? 'unknown';\n const pid = fields[1] ?? 'unknown';\n const user = fields[2] ?? 'unknown';\n return { command, pid, port, user };\n } catch {\n return { command: 'unknown', pid: 'unknown', port, user: 'unknown' };\n }\n}\n\nfunction isPortInUseError(error: unknown): boolean {\n return (\n error instanceof Error &&\n 'code' in error &&\n (error as NodeJS.ErrnoException).code === 'EADDRINUSE'\n );\n}\n\nasync function exchangeCodeForToken(\n code: string,\n codeVerifier: string,\n callbackUrl: string,\n): Promise<OAuthTokenResponse> {\n const clientId = IS_DEV ? POSTHOG_DEV_CLIENT_ID : POSTHOG_PROXY_CLIENT_ID;\n\n logToFile(\n `[oauth] exchanging code for token at ${POSTHOG_OAUTH_URL}/oauth/token`,\n );\n let response;\n try {\n response = await axios.post(\n `${POSTHOG_OAUTH_URL}/oauth/token`,\n {\n grant_type: 'authorization_code',\n code,\n redirect_uri: callbackUrl,\n client_id: clientId,\n code_verifier: codeVerifier,\n },\n {\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': WIZARD_USER_AGENT,\n },\n },\n );\n } catch (e) {\n const status = axios.isAxiosError(e) ? e.response?.status : undefined;\n logToFile(\n `[oauth] token exchange failed${status ? ` (HTTP ${status})` : ''}:`,\n e instanceof Error ? e.message : e,\n );\n throw e;\n }\n\n const token = OAuthTokenResponseSchema.parse(response.data);\n logToFile(\n `[oauth] token exchange succeeded, granted scopes: ${token.scope}` +\n `${\n token.scoped_teams\n ? `, scoped_teams: [${token.scoped_teams.join(', ')}]`\n : ''\n }` +\n `${\n token.scoped_organizations\n ? `, scoped_organizations: ${token.scoped_organizations.length}`\n : ''\n }`,\n );\n return token;\n}\n\nexport async function performOAuthFlow(\n config: OAuthConfig,\n): Promise<OAuthTokenResponse> {\n const clientId = IS_DEV ? POSTHOG_DEV_CLIENT_ID : POSTHOG_PROXY_CLIENT_ID;\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n let shouldRetry = false;\n\n logToFile(\n `[oauth] starting flow against ${POSTHOG_OAUTH_URL} ` +\n `(${\n IS_DEV ? 'dev' : 'prod'\n } client), requested scopes: ${config.scopes.join(' ')}`,\n );\n\n do {\n shouldRetry = false;\n let lastProcessInfo: {\n command: string;\n pid: string;\n port: number;\n user: string;\n } | null = null;\n\n for (const port of OAUTH_PORTS) {\n const callbackUrl = getCallbackUrl(port);\n const authUrl = new URL(`${POSTHOG_OAUTH_URL}/oauth/authorize`);\n authUrl.searchParams.set('client_id', clientId);\n authUrl.searchParams.set('redirect_uri', callbackUrl);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('code_challenge', codeChallenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n authUrl.searchParams.set('scope', config.scopes.join(' '));\n authUrl.searchParams.set('required_access_level', 'project');\n\n const signupUrl = new URL(\n `${POSTHOG_OAUTH_URL}/signup?next=${encodeURIComponent(\n authUrl.toString(),\n )}`,\n );\n const localSignupUrl = getLocalSignupUrl(port);\n const localLoginUrl = getLocalLoginUrl(port);\n const urlToOpen = config.signup ? localSignupUrl : localLoginUrl;\n\n logToFile(`[oauth] attempting callback server on port ${port}`);\n\n let server: http.Server;\n let waitForCallback: () => Promise<string>;\n try {\n ({ server, waitForCallback } = await startCallbackServer(\n authUrl.toString(),\n signupUrl.toString(),\n port,\n ));\n } catch (e) {\n if (!isPortInUseError(e)) throw e;\n lastProcessInfo = getPortProcessInfo(port);\n continue;\n }\n\n logToFile('[oauth] callback server ready, showing login URL');\n\n getUI().setLoginUrl(urlToOpen);\n // The localhost proxy above only works on this machine. Surface the\n // direct PostHog authorize URL too, for the manual-paste modal — on a\n // remote/headless box the user opens it from another machine, where\n // localhost:<port> is unreachable.\n getUI().setAuthorizeUrl(\n config.signup ? signupUrl.toString() : authUrl.toString(),\n );\n\n if (NODE_ENV !== 'test') {\n opn(urlToOpen, { wait: false }).catch(() => {\n // opn throws in environments without a browser\n });\n }\n\n const loginSpinner = getUI().spinner();\n loginSpinner.start('Waiting for authorization...');\n\n try {\n // Race the local callback server against a manually-pasted code. The\n // manual path is the fallback for headless/remote shells where the\n // browser can't reach localhost — the user opens the auth screen's\n // paste modal and submits the callback URL or code by hand.\n const code = await Promise.race([\n waitForCallback(),\n getUI().waitForManualAuthCode(),\n new Promise<never>((_, reject) =>\n setTimeout(\n () => reject(new Error('Authorization timed out')),\n OAUTH_TIMEOUT_MS,\n ),\n ),\n ]);\n\n const token = await exchangeCodeForToken(\n code,\n codeVerifier,\n callbackUrl,\n );\n\n server.close();\n getUI().setLoginUrl(null);\n getUI().setAuthorizeUrl(null);\n loginSpinner.stop('Authorization complete!');\n\n return token;\n } catch (e) {\n loginSpinner.stop('Authorization failed.');\n server.close();\n\n const error = e instanceof Error ? e : new Error('Unknown error');\n logToFile('[oauth] flow failed:', error);\n\n if (error.message.includes('timeout')) {\n getUI().log.error('Authorization timed out. Please try again.');\n } else if (error.message.includes('access_denied')) {\n getUI().log.info(\n `Authorization was cancelled.\\n\\nYou denied access to PostHog. To use the wizard, you need to authorize access to your PostHog account.\\n\\nYou can try again by re-running the wizard.`,\n );\n } else {\n getUI().log.error(\n `Authorization failed:\\n\\n${error.message}\\n\\nIf you think this is a bug in the PostHog wizard, please create an issue:\\n${ISSUES_URL}`,\n );\n }\n\n const oauthErrorCode = error.message.startsWith('OAuth error: ')\n ? error.message.slice('OAuth error: '.length)\n : error.message.includes('timeout')\n ? 'timeout'\n : 'unknown';\n\n analytics.captureException(error, {\n step: 'oauth_flow',\n oauth_error_code: oauthErrorCode,\n client_id: clientId,\n requested_scopes: config.scopes.join(' '),\n // Collapse OAuth callback failures of the same kind into one issue\n // instead of fragmenting by each user's install path in the stack trace.\n $exception_fingerprint: `wizard_oauth_${oauthErrorCode}`,\n });\n\n await abort();\n throw error;\n }\n }\n\n if (!lastProcessInfo) {\n throw new Error('No OAuth callback ports configured');\n }\n\n await getUI().showPortConflict(lastProcessInfo);\n shouldRetry = true;\n } while (shouldRetry);\n\n throw new Error('OAuth port retry loop exited unexpectedly');\n}\n","import {\n major,\n minVersion,\n satisfies,\n subset,\n valid,\n validRange,\n} from 'semver';\n\n/**\n * Version strings from package.json that are not semver ranges.\n * URLs, git refs, dist-tags, local paths, workspace protocol, npm aliases, etc.\n * These should be rejected early — we can't determine a clear version from them.\n */\nfunction isNonSemverVersion(version: string): boolean {\n const v = version.trim();\n return (\n v === '' ||\n v.startsWith('http://') ||\n v.startsWith('https://') ||\n v.startsWith('git+') ||\n v.startsWith('git://') ||\n v.startsWith('file:') ||\n v.startsWith('npm:') ||\n v.startsWith('workspace:') ||\n v.startsWith('/') ||\n v.includes('/') // user/repo shorthand\n );\n}\n\nexport function versionSatisfiesRange({\n version,\n acceptableVersions,\n canBeLatest,\n}: {\n version: string;\n acceptableVersions: string;\n canBeLatest: boolean;\n}): boolean {\n if (version === 'latest') return canBeLatest;\n if (isNonSemverVersion(version)) return false;\n\n const concrete = valid(version);\n if (concrete !== null) {\n return satisfies(concrete, acceptableVersions);\n }\n\n const userRange = validRange(version);\n if (userRange === null) return false;\n return subset(userRange, acceptableVersions);\n}\n\n/**\n * Creates a version bucket function for analytics.\n * Converts versions like \"1.2.3\" to \"1.x\" for grouping in analytics.\n *\n * @param minMajorVersion - Optional minimum major version threshold.\n * Versions below this will be bucketed as \"<{min}.0.0\"\n *\n * @example\n * const getVersionBucket = createVersionBucket(); // no minimum\n * getVersionBucket(\"1.2.3\") // \"1.x\"\n *\n * const getVersionBucket = createVersionBucket(11);\n * getVersionBucket(\"15.3.0\") // \"15.x\"\n * getVersionBucket(\"10.0.0\") // \"<11.0.0\"\n */\nexport function createVersionBucket(minMajorVersion?: number) {\n return (version: string | undefined): string => {\n if (!version) {\n return 'none';\n }\n\n if (isNonSemverVersion(version)) {\n return 'unknown';\n }\n\n try {\n const minVer = minVersion(version);\n if (!minVer) {\n return 'invalid';\n }\n const majorVersion = major(minVer);\n if (minMajorVersion !== undefined && majorVersion < minMajorVersion) {\n return `<${minMajorVersion}.0.0`;\n }\n return `${majorVersion}.x`;\n } catch {\n return 'unknown';\n }\n };\n}\n","import * as childProcess from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport { basename, isAbsolute, join, relative } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { withProgress } from '../telemetry';\nimport { debug } from './debug';\nimport type { PackageJson } from './package-json';\nimport {\n type PackageManager,\n detectAllPackageManagers,\n NPM as npm,\n} from './package-manager';\nimport type { CloudRegion, WizardRunOptions } from './types';\nimport { getDeclaredVersion } from './package-json';\nimport {\n DEFAULT_HOST_URL,\n DUMMY_PROJECT_API_KEY,\n ISSUES_URL,\n} from '@lib/constants';\nimport { getOAuthScopesForProgram } from '@lib/oauth/program-scopes';\nimport type { ProgramId } from '@lib/programs/program-registry';\nimport { analytics } from './analytics';\nimport { getUI } from '@ui';\nimport {\n getCloudUrlFromRegion,\n getHostFromRegion,\n detectRegionFromToken,\n} from './urls';\nimport { performOAuthFlow } from './oauth';\nimport { provisionNewAccount } from './provisioning';\nimport { fetchUserData, fetchProjectData, type ApiUser } from '@lib/api';\nimport { versionSatisfiesRange } from './semver';\nimport { wizardAbort } from './wizard-abort';\n\ninterface ProjectData {\n projectApiKey: string;\n accessToken: string;\n host: string;\n distinctId: string;\n projectId: number;\n /**\n * Optional `role_at_organization` from `/api/users/@me/`. Drives the\n * role-tailored prompt suggestions on the McpSuggestedPromptsScreen. Null\n * for signup flows (no role picked yet) and older accounts.\n */\n roleAtOrganization?: string | null;\n /**\n * Full user payload from `/api/users/@me/`. Carried through so\n * `getOrAskForProjectData` can forward it to the session as\n * `session.apiUser`. Null when the request failed or the CI key\n * lacked permissions.\n */\n user?: ApiUser | null;\n}\n\nexport interface CliSetupConfig {\n filename: string;\n name: string;\n gitignore: boolean;\n\n likelyAlreadyHasAuthToken(contents: string): boolean;\n tokenContent(authToken: string): string;\n\n likelyAlreadyHasOrgAndProject(contents: string): boolean;\n orgAndProjContent(org: string, project: string): string;\n\n likelyAlreadyHasUrl?(contents: string): boolean;\n urlContent?(url: string): string;\n}\n\nexport interface CliSetupConfigContent {\n authToken: string;\n org?: string;\n project?: string;\n url?: string;\n}\n\n/** @deprecated Use wizardAbort() directly for new code. */\nexport async function abort(message?: string, status?: number): Promise<never> {\n return wizardAbort({ message, exitCode: status });\n}\n\nexport function isInGitRepo(): boolean {\n try {\n childProcess.execSync('git rev-parse --show-toplevel', {\n stdio: 'ignore',\n });\n } catch {\n return false;\n }\n return true;\n}\n\nconst FREEMAIL_DOMAINS = new Set([\n 'gmail.com',\n 'googlemail.com',\n 'hotmail.com',\n 'outlook.com',\n 'yahoo.com',\n 'icloud.com',\n 'me.com',\n 'mail.com',\n 'protonmail.com',\n 'proton.me',\n 'live.com',\n 'aol.com',\n 'yandex.com',\n 'zoho.com',\n 'gmx.com',\n 'fastmail.com',\n]);\n\nfunction parseGitRemote(): { org: string; repo: string } | null {\n try {\n const url = childProcess\n .execSync('git remote get-url origin', {\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString()\n .trim();\n // git@github.com:acme-corp/my-app.git or https://github.com/acme-corp/my-app.git\n const match = url.match(/[/:]([\\w.-]+)\\/([\\w.-]+?)(?:\\.git)?$/);\n if (match) return { org: match[1], repo: match[2] };\n } catch {\n // not in a git repo or no remote\n }\n return null;\n}\n\nexport function detectOrgAndProject(email: string): {\n orgName: string | undefined;\n projectName: string | undefined;\n} {\n const remote = parseGitRemote();\n\n // Project name: git repo name > directory name\n const projectName = remote?.repo || basename(process.cwd()) || undefined;\n\n // Org name: git remote org > email domain (skip freemail)\n let orgName: string | undefined;\n if (remote?.org) {\n orgName = remote.org;\n } else {\n const domain = email.split('@')[1]?.toLowerCase();\n if (domain && !FREEMAIL_DOMAINS.has(domain)) {\n orgName = domain.split('.')[0];\n }\n }\n\n return { orgName, projectName };\n}\n\nexport function getUncommittedOrUntrackedFiles(): string[] {\n let gitStatus: string;\n try {\n gitStatus = childProcess\n .execSync('git status --porcelain=v1', {\n // we only care about stdout\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString();\n } catch {\n return [];\n }\n\n const result: string[] = [];\n for (const rawLine of gitStatus.split(os.EOL)) {\n const line = rawLine.trim();\n if (!line) continue;\n const match = /^\\S+\\s+(\\S+)/.exec(line);\n result.push(`- ${match?.[1]}`);\n }\n return result;\n}\n\nexport async function isReact19Installed({\n installDir,\n}: Pick<WizardRunOptions, 'installDir'>): Promise<boolean> {\n try {\n const packageJson = await tryGetPackageJson({ installDir });\n if (!packageJson) return false;\n const reactVersion = getDeclaredVersion('react', packageJson);\n\n if (!reactVersion) {\n return false;\n }\n\n return versionSatisfiesRange({\n version: reactVersion,\n acceptableVersions: '>=19.0.0',\n canBeLatest: true,\n });\n } catch {\n return false;\n }\n}\n\n/**\n * Installs or updates a package with the user's package manager.\n *\n * IMPORTANT: This function modifies the `package.json`! Be sure to re-read\n * it if you make additional modifications to it after calling this function!\n */\nexport async function installPackage({\n packageName,\n alreadyInstalled,\n packageNameDisplayLabel,\n packageManager,\n integration,\n installDir,\n}: {\n packageName: string;\n alreadyInstalled: boolean;\n packageNameDisplayLabel?: string;\n packageManager?: PackageManager;\n integration?: string;\n installDir: string;\n}): Promise<{ packageManager?: PackageManager }> {\n return withProgress('install-package', async () => {\n const sdkInstallSpinner = getUI().spinner();\n\n const pkgManager =\n packageManager || (await getPackageManager({ installDir }));\n\n const isReact19 = await isReact19Installed({ installDir });\n const legacyPeerDepsFlag =\n isReact19 && pkgManager.name === 'npm' ? '--legacy-peer-deps' : '';\n\n sdkInstallSpinner.start(\n `${alreadyInstalled ? 'Updating' : 'Installing'} ${\n packageNameDisplayLabel ?? packageName\n } with ${pkgManager.label}.`,\n );\n\n const execAsync = promisify(childProcess.exec);\n const installCommand =\n `${pkgManager.installCommand} ${packageName} ${pkgManager.flags} ${legacyPeerDepsFlag}`.trim();\n\n try {\n await execAsync(installCommand, { cwd: installDir });\n } catch (e) {\n const { stdout = '', stderr = '' } = (e ?? {}) as {\n stdout?: string;\n stderr?: string;\n };\n fs.writeFileSync(\n join(\n process.cwd(),\n `posthog-wizard-installation-error-${Date.now()}.log`,\n ),\n JSON.stringify({ stdout, stderr }),\n { encoding: 'utf8' },\n );\n sdkInstallSpinner.stop('Installation failed.');\n getUI().log.error(\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n `Encountered the following error during installation:\\n\\n${e}\\n\\nThe wizard has created a \\`posthog-wizard-installation-error-*.log\\` file. If you think this issue is caused by the PostHog wizard, create an issue on GitHub and include the log file's content:\\n${ISSUES_URL}`,\n );\n await abort();\n }\n\n sdkInstallSpinner.stop(\n `${alreadyInstalled ? 'Updated' : 'Installed'} ${\n packageNameDisplayLabel ?? packageName\n } with ${pkgManager.label}.`,\n );\n\n analytics.wizardCapture('package installed', {\n package_name: packageName,\n package_manager: pkgManager.name,\n integration,\n });\n\n return { packageManager: pkgManager };\n });\n}\n\n/**\n * Get package.json or abort the wizard if not found.\n * Only use where package.json is required (e.g., package install, overrides).\n * For detection/version-checks, use tryGetPackageJson() instead.\n */\nexport async function getPackageDotJson({\n installDir,\n}: Pick<WizardRunOptions, 'installDir'>): Promise<PackageJson> {\n const pkgPath = join(installDir, 'package.json');\n\n let raw: string;\n try {\n raw = await fs.promises.readFile(pkgPath, 'utf8');\n } catch {\n getUI().log.error(\n 'Could not find package.json. Make sure to run the wizard in the root of your app!',\n );\n await abort();\n return {};\n }\n\n try {\n const parsed = JSON.parse(raw) as PackageJson | null;\n return parsed ?? {};\n } catch {\n getUI().log.error(\n `Unable to parse your package.json. Make sure it has a valid format!`,\n );\n await abort();\n return {};\n }\n}\n\n/**\n * Try to get package.json, returning null if it doesn't exist.\n * Use this for detection purposes where missing package.json is expected (e.g., Python projects).\n */\nexport async function tryGetPackageJson({\n installDir,\n}: Pick<WizardRunOptions, 'installDir'>): Promise<PackageJson | null> {\n try {\n const packageJsonFileContents = await fs.promises.readFile(\n join(installDir, 'package.json'),\n 'utf8',\n );\n return JSON.parse(packageJsonFileContents) as PackageJson;\n } catch {\n return null;\n }\n}\n\nexport async function updatePackageDotJson(\n packageDotJson: PackageJson,\n { installDir }: Pick<WizardRunOptions, 'installDir'>,\n): Promise<void> {\n const pkgPath = join(installDir, 'package.json');\n const serialized = JSON.stringify(packageDotJson, null, 2);\n\n try {\n await fs.promises.writeFile(pkgPath, serialized, {\n encoding: 'utf8',\n flag: 'w',\n });\n return;\n } catch {\n getUI().log.error(`Unable to update your package.json.`);\n await abort();\n }\n}\n\n/**\n * Detect and return the package manager. Pure — no prompts.\n * Falls back to first detected or npm if ambiguous.\n */\n// eslint-disable-next-line @typescript-eslint/require-await\nexport async function getPackageManager(\n options: Pick<WizardRunOptions, 'installDir'> & { ci?: boolean },\n): Promise<PackageManager> {\n const detectedPackageManagers = detectAllPackageManagers({\n installDir: options.installDir,\n });\n\n if (detectedPackageManagers.length >= 1) {\n const selected = detectedPackageManagers[0];\n analytics.setTag('package-manager', selected.name);\n return selected;\n }\n\n // No package manager detected — default to npm\n analytics.setTag('package-manager', npm.name);\n return npm;\n}\n\nexport function isUsingTypeScript({\n installDir,\n}: Pick<WizardRunOptions, 'installDir'>): boolean {\n try {\n fs.accessSync(join(installDir, 'tsconfig.json'));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get project data for the wizard via OAuth or CI API key.\n */\nexport async function getOrAskForProjectData(\n _options: Pick<WizardRunOptions, 'signup' | 'ci' | 'apiKey' | 'projectId'> & {\n email?: string;\n region?: CloudRegion;\n /** Optional — picks the OAuth scope set via\n * `getOAuthScopesForProgram`. Omitted → default\n * `WIZARD_OAUTH_SCOPES`. Threaded into `askForWizardLogin`. */\n programId?: ProgramId | null;\n },\n): Promise<{\n host: string;\n projectApiKey: string;\n accessToken: string;\n projectId: number;\n cloudRegion: CloudRegion;\n roleAtOrganization: string | null;\n user: ApiUser | null;\n}> {\n // CI mode: bypass OAuth, use personal API key for LLM gateway\n if (_options.ci && _options.apiKey) {\n getUI().log.info('Using provided API key (CI mode - OAuth bypassed)');\n\n const cloudRegion = await detectRegionFromToken(_options.apiKey);\n const host = getHostFromRegion(cloudRegion);\n const cloudUrl = getCloudUrlFromRegion(cloudRegion);\n\n const projectData =\n _options.projectId != null\n ? await fetchProjectDataById(\n _options.apiKey,\n _options.projectId,\n cloudUrl,\n )\n : await fetchProjectDataWithApiKey(_options.apiKey, cloudUrl);\n\n // Best-effort user fetch — CI flows may run with project-scoped keys\n // that 403 on /api/users/@me/, so swallow errors and continue with\n // a null user (and null role).\n let user: ApiUser | null = null;\n let roleAtOrganization: string | null = null;\n try {\n user = await fetchUserData(_options.apiKey, cloudUrl);\n roleAtOrganization = user.role_at_organization ?? null;\n } catch {\n // best-effort\n }\n\n return {\n host,\n projectApiKey: projectData.api_token,\n accessToken: _options.apiKey,\n projectId: projectData.id,\n cloudRegion,\n roleAtOrganization,\n user,\n };\n }\n\n const {\n host,\n projectApiKey,\n accessToken,\n projectId,\n cloudRegion,\n roleAtOrganization,\n user,\n } = await withProgress('login', () =>\n askForWizardLogin({\n signup: _options.signup,\n email: _options.email,\n region: _options.region,\n programId: _options.programId,\n }),\n );\n\n if (!projectApiKey) {\n const cloudUrl = getCloudUrlFromRegion(cloudRegion);\n getUI().log.error(`Didn't receive a project token. This shouldn't happen :(\n\nPlease let us know if you think this is a bug in the wizard:\n${ISSUES_URL}`);\n\n getUI().log\n .info(`In the meantime, we'll add a dummy project token (\"${DUMMY_PROJECT_API_KEY}\") for you to replace later.\nYou can find your project token here:\n${cloudUrl}/settings/project#variables`);\n }\n\n return {\n accessToken,\n host: host || DEFAULT_HOST_URL,\n projectApiKey: projectApiKey || DUMMY_PROJECT_API_KEY,\n projectId,\n cloudRegion,\n roleAtOrganization: roleAtOrganization ?? null,\n user: user ?? null,\n };\n}\n\nasync function fetchProjectDataWithApiKey(\n apiKey: string,\n cloudUrl: string,\n): Promise<{ api_token: string; id: number }> {\n const userData = await fetchUserData(apiKey, cloudUrl);\n const projectId = userData.team?.id;\n\n if (!projectId) {\n throw new Error(\n 'Could not determine project ID from API key. Please ensure your API key has access to a project in this cloud region.',\n );\n }\n\n const projectData = await fetchProjectData(apiKey, projectId, cloudUrl);\n return {\n api_token: projectData.api_token,\n id: projectId,\n };\n}\n\nasync function fetchProjectDataById(\n apiKey: string,\n projectId: number,\n cloudUrl: string,\n): Promise<{ api_token: string; id: number }> {\n const projectData = await fetchProjectData(apiKey, projectId, cloudUrl);\n return {\n api_token: projectData.api_token,\n id: projectId,\n };\n}\n\nasync function askForWizardLogin(options: {\n signup: boolean;\n email?: string;\n region?: CloudRegion;\n /** Used to pick the right scope set via `getOAuthScopesForProgram`.\n * Omitted → default `WIZARD_OAUTH_SCOPES`. */\n programId?: ProgramId | null;\n}): Promise<ProjectData & { cloudRegion: CloudRegion }> {\n if (options.signup) {\n return askForProvisioningSignup(options.email, options.region);\n }\n\n const tokenResponse = await performOAuthFlow({\n scopes: [...getOAuthScopesForProgram(options.programId)],\n signup: false,\n });\n\n const projectId = tokenResponse.scoped_teams?.[0];\n\n if (projectId === undefined) {\n const error = new Error(\n 'No project access granted. Please authorize with project-level access.',\n );\n analytics.captureException(error, {\n step: 'wizard_login',\n has_scoped_teams: !!tokenResponse.scoped_teams,\n });\n getUI().log.error(error.message);\n await abort();\n }\n\n const cloudRegion = await detectRegionFromToken(tokenResponse.access_token);\n const cloudUrl = getCloudUrlFromRegion(cloudRegion);\n const host = getHostFromRegion(cloudRegion);\n\n const projectData = await fetchProjectData(\n tokenResponse.access_token,\n projectId!,\n cloudUrl,\n );\n const userData = await fetchUserData(tokenResponse.access_token, cloudUrl);\n\n const data = {\n accessToken: tokenResponse.access_token,\n projectApiKey: projectData.api_token,\n host,\n distinctId: userData.distinct_id,\n projectId: projectId!,\n cloudRegion,\n roleAtOrganization: userData.role_at_organization ?? null,\n user: userData,\n };\n\n getUI().log.success('Login complete.');\n analytics.setTag('opened-wizard-link', true);\n analytics.setDistinctId(data.distinctId);\n\n return data;\n}\n\nasync function askForProvisioningSignup(\n email?: string,\n region?: CloudRegion,\n): Promise<ProjectData & { cloudRegion: CloudRegion }> {\n if (!email || !email.includes('@')) {\n getUI().log.error(\n 'Email is required for signup. Use --email your@email.com with --signup.',\n );\n await abort();\n throw new Error('unreachable');\n }\n\n const spinner = getUI().spinner();\n spinner.start('Creating your PostHog account...');\n\n try {\n const provisionRegion = (region ?? 'us').toUpperCase() as 'US' | 'EU';\n const { orgName, projectName } = detectOrgAndProject(email);\n const result = await provisionNewAccount(email, '', provisionRegion, {\n orgName,\n projectName,\n });\n\n spinner.stop('Account created!');\n getUI().log.success('Welcome to PostHog!');\n\n const host = result.host;\n const cloudRegion: CloudRegion = host.includes('eu.') ? 'eu' : 'us';\n\n analytics.setTag('provisioning-signup', true);\n\n return {\n accessToken: result.accessToken,\n projectApiKey: result.projectApiKey,\n host,\n distinctId: email,\n projectId: parseInt(result.projectId, 10) || 0,\n cloudRegion,\n };\n } catch (error) {\n spinner.stop('Account creation failed.');\n const message = error instanceof Error ? error.message : 'Unknown error';\n\n if (message.includes('already associated')) {\n getUI().log.info(\n 'This email already has a PostHog account. Switching to login flow...',\n );\n return askForWizardLogin({ signup: false });\n }\n\n getUI().log.error(`Failed to create account: ${message}`);\n analytics.captureException(\n error instanceof Error ? error : new Error(message),\n { step: 'provisioning_signup' },\n );\n await abort();\n throw error;\n }\n}\n\n/**\n * Creates a new config file with the given filepath and codeSnippet.\n */\nexport async function createNewConfigFile(\n filepath: string,\n codeSnippet: string,\n { installDir }: Pick<WizardRunOptions, 'installDir'>,\n moreInformation?: string,\n): Promise<boolean> {\n if (!isAbsolute(filepath)) {\n debug(`createNewConfigFile: filepath is not absolute: ${filepath}`);\n return false;\n }\n\n const prettyFilename = relative(installDir, filepath);\n\n try {\n await fs.promises.writeFile(filepath, codeSnippet);\n\n getUI().log.success(`Added new ${prettyFilename} file.`);\n\n if (moreInformation) {\n getUI().log.info(moreInformation);\n }\n\n return true;\n } catch (e) {\n debug(e);\n getUI().log.warn(\n `Could not create a new ${prettyFilename} file. Please create one manually and follow the instructions below.`,\n );\n }\n\n return false;\n}\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
- import { g as SERVICE_LABELS, s as logToFile } from "./debug-BorYMfpE.js";
1
+ import { g as SERVICE_LABELS, s as logToFile } from "./debug-DnMO6O8O.js";
2
2
  import { n as isTaskStatus } from "./wizard-ui-YdGFRyu_.js";
3
- import { r as sessionProperties, t as analytics } from "./analytics-C-zcTO6g.js";
3
+ import { r as sessionProperties, t as analytics } from "./analytics-Df-Xb81i.js";
4
4
  import { i as buildSession } from "./wizard-session-d27JGRGi.js";
5
- import { v as AUDIT_SEVERITY_STYLE } from "./agent-interface-C2VEF-BD.js";
6
- import { c as computeVisibleRange, d as isObjectBlock, f as Colors, l as isClearBlock, p as Icons, s as TextBlock, u as isLinesBlock } from "./posthog-integration-B_DLodqr.js";
5
+ import { v as AUDIT_SEVERITY_STYLE } from "./agent-interface-CQU6x4Hj.js";
6
+ import { c as computeVisibleRange, d as isObjectBlock, f as Colors, l as isClearBlock, p as Icons, s as TextBlock, u as isLinesBlock } from "./posthog-integration-C8qhJnI3.js";
7
7
  import { a as getProgramConfig, i as Program, l as getKindMeta, r as PROGRAM_REGISTRY } from "./bin.js";
8
- import { n as AVAILABLE_FEATURES, t as ALL_FEATURE_VALUES } from "./defaults-DA3-9dHT.js";
8
+ import { n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-BNWIWzjc.js";
9
9
  import * as fs$1 from "fs";
10
10
  import { Box, Text, measureElement, useInput, useStdout } from "ink";
11
11
  import { Component, Fragment, createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
@@ -320,6 +320,7 @@ var WizardStore = class {
320
320
  }
321
321
  /** User dismissed the blocking outage screen. Gate resolves via _checkGates(). */
322
322
  dismissOutage() {
323
+ logToFile("[health-checks] user dismissed outage screen, continuing");
323
324
  this.$session.setKey("outageDismissed", true);
324
325
  this.emitChange();
325
326
  }
@@ -461,13 +462,15 @@ var WizardStore = class {
461
462
  analytics.wizardCapture("feature enabled", { feature });
462
463
  this.emitChange();
463
464
  }
464
- setMcpComplete(outcome = "skipped", installedClients = []) {
465
+ setMcpComplete(outcome = "skipped", installedClients = [], featuresSelected) {
465
466
  this.$session.setKey("mcpComplete", true);
466
467
  this.$session.setKey("mcpOutcome", outcome);
467
468
  this.$session.setKey("mcpInstalledClients", installedClients);
469
+ const featuresPayload = outcome === "installed" && featuresSelected !== void 0 ? { mcp_features_selected: featuresSelected } : {};
468
470
  analytics.wizardCapture("mcp complete", {
469
471
  mcp_outcome: outcome,
470
472
  mcp_installed_clients: installedClients,
473
+ ...featuresPayload,
471
474
  ...sessionProperties(this.session)
472
475
  });
473
476
  this.emitChange();
@@ -1014,8 +1017,13 @@ const MultiPickerMenu = ({ message, options, centered = false, columns = 1, opti
1014
1017
  handler: () => {
1015
1018
  setSelected((prev) => {
1016
1019
  const next = new Set(prev);
1017
- if (next.has(focused)) next.delete(focused);
1018
- else next.add(focused);
1020
+ if (next.has(focused)) {
1021
+ next.delete(focused);
1022
+ return next;
1023
+ }
1024
+ if (options[focused]?.exclusive) return new Set([focused]);
1025
+ for (const i of next) if (options[i]?.exclusive) next.delete(i);
1026
+ next.add(focused);
1019
1027
  return next;
1020
1028
  });
1021
1029
  }
@@ -1748,6 +1756,7 @@ var ScreenErrorBoundary = class extends Component {
1748
1756
  }
1749
1757
  componentDidCatch(error) {
1750
1758
  const { store } = this.props;
1759
+ logToFile("[screen-error-boundary]", error);
1751
1760
  console.error("[ScreenErrorBoundary]", error.message, error.stack);
1752
1761
  store.setOutroData({
1753
1762
  kind: "error",
@@ -2613,8 +2622,22 @@ const IssueRow = ({ issue, docsWidth }) => {
2613
2622
  *
2614
2623
  * When done, calls store.setMcpComplete(). The router resolves to outro.
2615
2624
  */
2616
- const markDone = (store, outcome, clients = []) => {
2617
- store.setMcpComplete(outcome, clients);
2625
+ const markDone = (store, outcome, clients = [], featuresSelected) => {
2626
+ store.setMcpComplete(outcome, clients, featuresSelected);
2627
+ };
2628
+ const reportFeatures = (features) => isAllFeaturesSelected(features) ? "all" : features;
2629
+ /**
2630
+ * Connector step prompt — Enter continues (opens the connector page). There's
2631
+ * no skip: picking the connector commits to opening it.
2632
+ */
2633
+ const ConnectorContinue = ({ onContinue }) => {
2634
+ useInput((_input, key) => {
2635
+ if (key.return) onContinue();
2636
+ });
2637
+ return /* @__PURE__ */ jsxs(Text, {
2638
+ color: Colors.primary,
2639
+ children: ["Press enter to continue ", Icons.triangleRight]
2640
+ });
2618
2641
  };
2619
2642
  const McpScreen = ({ store, installer, mode = "install" }) => {
2620
2643
  useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
@@ -2625,6 +2648,7 @@ const McpScreen = ({ store, installer, mode = "install" }) => {
2625
2648
  const [selectedClientNames, setSelectedClientNames] = useState([]);
2626
2649
  const [resultClients, setResultClients] = useState([]);
2627
2650
  const [pluginClients, setPluginClients] = useState([]);
2651
+ const [installMode, setInstallMode] = useState("custom");
2628
2652
  useEffect(() => {
2629
2653
  (async () => {
2630
2654
  try {
@@ -2642,23 +2666,34 @@ const McpScreen = ({ store, installer, mode = "install" }) => {
2642
2666
  }
2643
2667
  })();
2644
2668
  }, [installer]);
2645
- const proceedToFeatureSelectOrInstall = (clientNames) => {
2669
+ const proceedAfterClientPick = (clientNames, chosenMode) => {
2646
2670
  setSelectedClientNames(clientNames);
2671
+ if (chosenMode === "all") {
2672
+ doInstall(clientNames, [...ALL_FEATURE_VALUES]);
2673
+ return;
2674
+ }
2647
2675
  if (store.session.mcpFeatures) {
2648
2676
  doInstall(clientNames, store.session.mcpFeatures);
2649
2677
  return;
2650
2678
  }
2651
- if (!clientNames.some((name) => {
2652
- return !clients.find((c) => c.name === name)?.finish;
2653
- })) {
2654
- doInstall(clientNames, []);
2679
+ if (clientNames.some((name) => clients.find((c) => c.name === name)?.finish)) {
2680
+ setPhase("connector");
2655
2681
  return;
2656
2682
  }
2657
2683
  setPhase("feature-select");
2658
2684
  };
2659
2685
  const handleConfirm = () => {
2660
2686
  if (isRemove) doRemove();
2661
- else if (clients.length === 1) proceedToFeatureSelectOrInstall(clients.map((c) => c.name));
2687
+ else if (clients.length === 1) proceedAfterClientPick([clients[0].name], "custom");
2688
+ else setPhase("pick");
2689
+ };
2690
+ const handleTriStateChoice = (choice) => {
2691
+ if (choice === "skip") {
2692
+ handleSkip();
2693
+ return;
2694
+ }
2695
+ setInstallMode(choice);
2696
+ if (clients.length === 1) proceedAfterClientPick([clients[0].name], choice);
2662
2697
  else setPhase("pick");
2663
2698
  };
2664
2699
  const handleSkip = () => {
@@ -2668,17 +2703,25 @@ const McpScreen = ({ store, installer, mode = "install" }) => {
2668
2703
  setPhase("working");
2669
2704
  let mcpResult = [];
2670
2705
  let pluginResult = [];
2671
- try {
2706
+ const pluginCapableSet = new Set(clients.filter((c) => c.supportsPlugin).map((c) => c.name));
2707
+ const pluginCapableNames = names.filter((n) => pluginCapableSet.has(n));
2708
+ const directNames = names.filter((n) => !pluginCapableSet.has(n));
2709
+ if (installMode === "all") {
2710
+ try {
2711
+ mcpResult = await installer.install(directNames, features, store.session.apiKey);
2712
+ } catch {}
2713
+ try {
2714
+ pluginResult = await installer.installPlugins(pluginCapableNames);
2715
+ } catch {}
2716
+ } else try {
2672
2717
  mcpResult = await installer.install(names, features, store.session.apiKey);
2673
2718
  } catch {}
2674
- try {
2675
- pluginResult = await installer.installPlugins(names);
2676
- } catch {}
2677
2719
  setResultClients(mcpResult);
2678
2720
  setPluginClients(pluginResult);
2679
2721
  setPhase("done");
2680
- const outcome = mcpResult.length > 0 ? "installed" : "failed";
2681
- setTimeout(() => markDone(store, outcome, mcpResult), 2e3);
2722
+ const outcome = mcpResult.length + pluginResult.length > 0 ? "installed" : "failed";
2723
+ const featuresReport = reportFeatures(features ?? [...ALL_FEATURE_VALUES]);
2724
+ setTimeout(() => markDone(store, outcome, [...mcpResult, ...pluginResult], featuresReport), 2e3);
2682
2725
  };
2683
2726
  const doRemove = async () => {
2684
2727
  setPhase("working");
@@ -2746,7 +2789,26 @@ const McpScreen = ({ store, installer, mode = "install" }) => {
2746
2789
  }),
2747
2790
  /* @__PURE__ */ jsx(Box, {
2748
2791
  marginTop: 1,
2749
- children: /* @__PURE__ */ jsx(ConfirmationInput, {
2792
+ children: !isRemove && !store.session.mcpFeatures ? /* @__PURE__ */ jsx(PickerMenu, {
2793
+ message: `Install the PostHog MCP server${clients.some((c) => c.supportsPlugin) ? " and plugin" : ""}?`,
2794
+ options: [
2795
+ {
2796
+ label: "Install with all features",
2797
+ value: "all",
2798
+ hint: "recommended"
2799
+ },
2800
+ {
2801
+ label: "Customize features",
2802
+ value: "custom"
2803
+ },
2804
+ {
2805
+ label: "No thanks",
2806
+ value: "skip"
2807
+ }
2808
+ ],
2809
+ mode: "single",
2810
+ onSelect: (choice) => handleTriStateChoice(choice)
2811
+ }) : /* @__PURE__ */ jsx(ConfirmationInput, {
2750
2812
  message: `${isRemove ? "Remove" : "Install"} the PostHog MCP server${clients.some((c) => c.supportsPlugin) ? " and plugin" : ""}?`,
2751
2813
  confirmLabel: isRemove ? "Remove" : "Install",
2752
2814
  cancelLabel: "No thanks",
@@ -2756,76 +2818,107 @@ const McpScreen = ({ store, installer, mode = "install" }) => {
2756
2818
  })
2757
2819
  ] }),
2758
2820
  phase === "pick" && /* @__PURE__ */ jsx(PickerMenu, {
2759
- message: "Select editor to install MCP server",
2821
+ message: installMode === "all" ? "Select editor to install" : "Select editor to install MCP server",
2760
2822
  options: clients.map((c) => ({
2761
2823
  label: c.name,
2762
- value: c.name
2824
+ value: c.name,
2825
+ exclusive: Boolean(c.finish),
2826
+ hint: installMode === "all" ? c.finish ? "connector" : c.supportsPlugin ? "plugin" : "MCP" : void 0
2763
2827
  })),
2764
2828
  mode: "multi",
2765
2829
  onSelect: (selected) => {
2766
- proceedToFeatureSelectOrInstall(Array.isArray(selected) ? selected : [selected]);
2830
+ proceedAfterClientPick(Array.isArray(selected) ? selected : [selected], installMode);
2767
2831
  }
2768
2832
  }),
2769
2833
  phase === "feature-select" && /* @__PURE__ */ jsx(GroupedPickerMenu, {
2770
2834
  message: "Select features to enable",
2771
2835
  groups: AVAILABLE_FEATURES,
2772
- initialSelected: [...ALL_FEATURE_VALUES],
2836
+ initialSelected: [],
2773
2837
  onSelect: (features) => {
2774
2838
  doInstall(selectedClientNames, features);
2775
2839
  }
2776
2840
  }),
2841
+ phase === "connector" && /* @__PURE__ */ jsxs(Box, {
2842
+ flexDirection: "column",
2843
+ children: [/* @__PURE__ */ jsx(Box, {
2844
+ marginBottom: 1,
2845
+ children: /* @__PURE__ */ jsx(Text, {
2846
+ dimColor: true,
2847
+ children: "You'll choose which features and tools to enable in Claude's UI after connecting."
2848
+ })
2849
+ }), /* @__PURE__ */ jsx(ConnectorContinue, { onContinue: () => void doInstall(selectedClientNames, []) })]
2850
+ }),
2777
2851
  phase === "working" && /* @__PURE__ */ jsxs(Text, {
2778
2852
  dimColor: true,
2779
2853
  children: [isRemove ? "Removing" : "Installing", " MCP server..."]
2780
2854
  }),
2781
2855
  phase === "done" && /* @__PURE__ */ jsx(Box, {
2782
2856
  flexDirection: "column",
2783
- children: installedNow.length === 0 && finishNotes.length === 0 ? /* @__PURE__ */ jsxs(Text, {
2857
+ children: installedNow.length + pluginClients.length + finishNotes.length === 0 ? /* @__PURE__ */ jsxs(Text, {
2784
2858
  dimColor: true,
2785
2859
  children: [isRemove ? "Removal" : "Installation", " skipped."]
2786
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [installedNow.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Text, {
2787
- color: "green",
2788
- bold: true,
2789
- children: [
2790
- "✔",
2791
- " MCP server",
2792
- !isRemove && pluginClients.length > 0 ? " and plugin" : "",
2860
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
2861
+ pluginClients.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Text, {
2862
+ color: "green",
2863
+ bold: true,
2864
+ children: ["✔", " Plugin installed for:"]
2865
+ }), pluginClients.map((name, i) => /* @__PURE__ */ jsxs(Text, { children: [
2793
2866
  " ",
2794
- isRemove ? "removed from" : "installed for",
2795
- ":"
2796
- ]
2797
- }), installedNow.map((name, i) => /* @__PURE__ */ jsxs(Text, { children: [
2798
- " ",
2799
- "",
2800
- " ",
2801
- name
2802
- ] }, i))] }), finishNotes.map((note) => /* @__PURE__ */ jsxs(Box, {
2803
- flexDirection: "column",
2804
- marginTop: 1,
2805
- children: [
2806
- /* @__PURE__ */ jsxs(Text, {
2807
- color: "green",
2808
- bold: true,
2809
- children: [note.name, " \\u2014 finish in your browser:"]
2810
- }),
2811
- /* @__PURE__ */ jsxs(Text, { children: [
2812
- " ",
2813
- "Opened ",
2814
- /* @__PURE__ */ jsx(Text, {
2815
- color: "cyan",
2816
- children: note.url
2867
+ "",
2868
+ " ",
2869
+ name
2870
+ ] }, `p-${i}`))] }),
2871
+ installedNow.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Text, {
2872
+ color: "green",
2873
+ bold: true,
2874
+ children: [
2875
+ "✔",
2876
+ " MCP server",
2877
+ " ",
2878
+ isRemove ? "removed from" : "installed for",
2879
+ ":"
2880
+ ]
2881
+ }), installedNow.map((name, i) => /* @__PURE__ */ jsxs(Text, { children: [
2882
+ " ",
2883
+ "•",
2884
+ " ",
2885
+ name
2886
+ ] }, `m-${i}`))] }),
2887
+ finishNotes.map((note) => /* @__PURE__ */ jsxs(Box, {
2888
+ flexDirection: "column",
2889
+ marginTop: 1,
2890
+ children: [
2891
+ /* @__PURE__ */ jsxs(Text, {
2892
+ color: "green",
2893
+ bold: true,
2894
+ children: [
2895
+ "✔",
2896
+ " ",
2897
+ note.name,
2898
+ " ",
2899
+ "—",
2900
+ " installs a PostHog connector:"
2901
+ ]
2902
+ }),
2903
+ /* @__PURE__ */ jsxs(Text, { children: [
2904
+ " ",
2905
+ "Opened ",
2906
+ /* @__PURE__ */ jsx(Text, {
2907
+ color: "cyan",
2908
+ children: note.url
2909
+ })
2910
+ ] }),
2911
+ /* @__PURE__ */ jsxs(Text, {
2912
+ dimColor: true,
2913
+ children: [" ", note.instruction]
2914
+ }),
2915
+ /* @__PURE__ */ jsxs(Text, {
2916
+ dimColor: true,
2917
+ children: [" ", "(If it didn't open, paste the URL above.)"]
2817
2918
  })
2818
- ] }),
2819
- /* @__PURE__ */ jsxs(Text, {
2820
- dimColor: true,
2821
- children: [" ", note.instruction]
2822
- }),
2823
- /* @__PURE__ */ jsxs(Text, {
2824
- dimColor: true,
2825
- children: [" ", "(If it didn't open, paste the URL above.)"]
2826
- })
2827
- ]
2828
- }, note.name))] })
2919
+ ]
2920
+ }, note.name))
2921
+ ] })
2829
2922
  })
2830
2923
  ]
2831
2924
  })]
@@ -5381,4 +5474,4 @@ const AUDIT_3000_AREA_SLIDES = [
5381
5474
  //#endregion
5382
5475
  export { WizardStore as A, useStdoutDimensions as C, LoadingBox as D, ProgressList as E, SplitView as O, GroupedPickerMenu as S, useKeyBindings as T, ScreenContainer as _, McpSuggestedPromptsScreen as a, ModalOverlay as b, IssueTable as c, ServiceHealthList as d, TipsCard as f, TabContainer as g, HNViewer as h, AuditChecksViewer as i, CardLayout as k, SEVERITY_LABEL as l, ContentSequencer as m, AUDIT_AREA_SLIDES as n, TAILORED_ROLES as o, LearnCard as p, VisualBox as r, McpScreen as s, AUDIT_3000_AREA_SLIDES as t, SEVERITY_ORDER as u, EventPlanViewer as v, PickerMenu as w, ConfirmationInput as x, LogViewer as y };
5383
5476
 
5384
- //# sourceMappingURL=slides-DwvXZ8iS.js.map
5477
+ //# sourceMappingURL=slides-mT2s9wM_.js.map