@posthog/wizard 2.70.1 → 2.72.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 (111) hide show
  1. package/dist/{add-mcp-server-to-clients-BCnjUqnW.js → add-mcp-server-to-clients-QZyr6NuZ.js} +4 -4
  2. package/dist/{add-mcp-server-to-clients-BCnjUqnW.js.map → add-mcp-server-to-clients-QZyr6NuZ.js.map} +1 -1
  3. package/dist/{agent-interface-Lu7tnLr0.js → agent-interface-Yjknz6Al.js} +476 -383
  4. package/dist/agent-interface-Yjknz6Al.js.map +1 -0
  5. package/dist/{agent-prompt-loader-y5GxgEkc.js → agent-prompt-loader-CDXm4C35.js} +5 -5
  6. package/dist/{agent-prompt-loader-y5GxgEkc.js.map → agent-prompt-loader-CDXm4C35.js.map} +1 -1
  7. package/dist/{agent-runner-vKtpgqVI.js → agent-runner-CL7rCY0G.js} +3213 -20
  8. package/dist/agent-runner-CL7rCY0G.js.map +1 -0
  9. package/dist/analytics-CkNv3N4o.js +2 -0
  10. package/dist/{analytics-DTyJClPr.js → analytics-DjCPD6u-.js} +3 -3
  11. package/dist/{analytics-DTyJClPr.js.map → analytics-DjCPD6u-.js.map} +1 -1
  12. package/dist/{api-j-OfJ8Dz.js → api-xwQeBMcX.js} +23 -4
  13. package/dist/{api-j-OfJ8Dz.js.map → api-xwQeBMcX.js.map} +1 -1
  14. package/dist/bin.js +85 -75
  15. package/dist/bin.js.map +1 -1
  16. package/dist/{bounded-fs-BWX25ixc.js → bounded-fs-BtxN0nYO.js} +3 -3
  17. package/dist/{bounded-fs-BWX25ixc.js.map → bounded-fs-BtxN0nYO.js.map} +1 -1
  18. package/dist/{ci-install-zNWgp1TL.js → ci-install-BVzTnIBw.js} +5 -5
  19. package/dist/{ci-install-zNWgp1TL.js.map → ci-install-BVzTnIBw.js.map} +1 -1
  20. package/dist/{debug-xr2Pq5VA.js → debug-BoadIH6Q.js} +1 -1
  21. package/dist/{debug-BM0fZaGl.js → debug-D0T44879.js} +4 -3
  22. package/dist/debug-D0T44879.js.map +1 -0
  23. package/dist/{env-api-key-DlxCZWU0.js → env-api-key-Bj9butHX.js} +2 -2
  24. package/dist/{env-api-key-DlxCZWU0.js.map → env-api-key-Bj9butHX.js.map} +1 -1
  25. package/dist/{environment-o-bTOuzK.js → environment-vpmKreMf.js} +4 -4
  26. package/dist/{environment-o-bTOuzK.js.map → environment-vpmKreMf.js.map} +1 -1
  27. package/dist/{errors-BSdz2lsl.js → errors-u8z27V-x.js} +4 -2
  28. package/dist/errors-u8z27V-x.js.map +1 -0
  29. package/dist/{file-watcher-D7G4f5sP.js → file-watcher-BPH4wR8R.js} +2 -2
  30. package/dist/{file-watcher-D7G4f5sP.js.map → file-watcher-BPH4wR8R.js.map} +1 -1
  31. package/dist/{headless-ui-C46DctKG.js → headless-ui-vA7lv8m2.js} +2 -2
  32. package/dist/{headless-ui-C46DctKG.js.map → headless-ui-vA7lv8m2.js.map} +1 -1
  33. package/dist/{interactive-B5zy3Gxk.js → interactive-jk3IGgns.js} +2 -2
  34. package/dist/{interactive-B5zy3Gxk.js.map → interactive-jk3IGgns.js.map} +1 -1
  35. package/dist/{mcp-lJRwE-YW.js → mcp-DE2AonGF.js} +2 -2
  36. package/dist/{mcp-lJRwE-YW.js.map → mcp-DE2AonGF.js.map} +1 -1
  37. package/dist/{mcp-prompt-streaming-Dsos09hT.js → mcp-prompt-streaming-pPEIwc78.js} +5 -4
  38. package/dist/mcp-prompt-streaming-pPEIwc78.js.map +1 -0
  39. package/dist/{non-interactive-qcCCtiYQ.js → non-interactive-CEGz0nDY.js} +3 -3
  40. package/dist/{non-interactive-qcCCtiYQ.js.map → non-interactive-CEGz0nDY.js.map} +1 -1
  41. package/dist/{orchestrator-tools-ezFTbAIp.js → orchestrator-tools-BZ4Nzw6a.js} +21 -13
  42. package/dist/orchestrator-tools-BZ4Nzw6a.js.map +1 -0
  43. package/dist/{package-manager-CGfN9UhH.js → package-manager-BZjKEDO2.js} +2 -2
  44. package/dist/{package-manager-CGfN9UhH.js.map → package-manager-BZjKEDO2.js.map} +1 -1
  45. package/dist/{pi-DzQLHIcQ.js → pi-D48qAwlf.js} +18 -15
  46. package/dist/pi-D48qAwlf.js.map +1 -0
  47. package/dist/{playground-DLN31BPV.js → playground-DYGJwugo.js} +43 -8
  48. package/dist/playground-DYGJwugo.js.map +1 -0
  49. package/dist/{posthog-Dxr8XK4x.js → posthog-BTvVhvBY.js} +2 -2
  50. package/dist/{posthog-Dxr8XK4x.js.map → posthog-BTvVhvBY.js.map} +1 -1
  51. package/dist/posthog-integration-BQh-YQAP.js +1475 -0
  52. package/dist/posthog-integration-BQh-YQAP.js.map +1 -0
  53. package/dist/{provisioning-Di5GNV9k.js → provisioning-Dw3B2T_b.js} +14 -5
  54. package/dist/provisioning-Dw3B2T_b.js.map +1 -0
  55. package/dist/{queue-tools-KosDptd6.js → queue-tools-DEbzMDrF.js} +108 -44
  56. package/dist/queue-tools-DEbzMDrF.js.map +1 -0
  57. package/dist/{registry-BBdaIFWq.js → registry-Dv6JVm5r.js} +5 -5
  58. package/dist/{registry-BBdaIFWq.js.map → registry-Dv6JVm5r.js.map} +1 -1
  59. package/dist/{security-BwOvjMaq.js → security-BaggjIE4.js} +5 -5
  60. package/dist/{security-BwOvjMaq.js.map → security-BaggjIE4.js.map} +1 -1
  61. package/dist/{setup-utils-Bo3WZt3a.js → setup-utils-CnW0Zsu8.js} +42 -11
  62. package/dist/setup-utils-CnW0Zsu8.js.map +1 -0
  63. package/dist/{start-tui-B1LrB7k1.js → start-tui-SpyGz7wM.js} +279 -24
  64. package/dist/start-tui-SpyGz7wM.js.map +1 -0
  65. package/dist/{steps-BMrQJpQh.js → steps-ClfUajTv.js} +7 -7
  66. package/dist/{steps-BMrQJpQh.js.map → steps-ClfUajTv.js.map} +1 -1
  67. package/dist/{store-DhNTBWQR.js → store-Cmy0pN1R.js} +24 -6
  68. package/dist/store-Cmy0pN1R.js.map +1 -0
  69. package/dist/{subagent-q6M-mBHo.js → subagent-tYlbbkJt.js} +2 -2
  70. package/dist/{subagent-q6M-mBHo.js.map → subagent-tYlbbkJt.js.map} +1 -1
  71. package/dist/{task-CgAOxod7.js → task-B8QfEVcu.js} +13 -13
  72. package/dist/{task-CgAOxod7.js.map → task-B8QfEVcu.js.map} +1 -1
  73. package/dist/{task-stream-rxR1IsdJ.js → task-stream-BRSWhbXQ.js} +6 -6
  74. package/dist/{task-stream-rxR1IsdJ.js.map → task-stream-BRSWhbXQ.js.map} +1 -1
  75. package/dist/{tasks-lzvWF__k.js → tasks-DjEvyWQJ.js} +2 -2
  76. package/dist/{tasks-lzvWF__k.js.map → tasks-DjEvyWQJ.js.map} +1 -1
  77. package/dist/{telemetry-CickTk_7.js → telemetry-EPB2n_f6.js} +3 -3
  78. package/dist/{telemetry-CickTk_7.js.map → telemetry-EPB2n_f6.js.map} +1 -1
  79. package/dist/{terminal-BjUFxn_x.js → terminal-DJ4Gaupr.js} +107 -61
  80. package/dist/terminal-DJ4Gaupr.js.map +1 -0
  81. package/dist/{tools-MKXGXFSk.js → tools-CB0Vfc6r.js} +6 -6
  82. package/dist/{tools-MKXGXFSk.js.map → tools-CB0Vfc6r.js.map} +1 -1
  83. package/dist/{wizard-abort-CW_C2EXr.js → wizard-abort-DD-kkQaq.js} +1 -1
  84. package/dist/{wizard-abort-CooWEh9N.js → wizard-abort-Db_YNkB2.js} +5 -5
  85. package/dist/{wizard-abort-CooWEh9N.js.map → wizard-abort-Db_YNkB2.js.map} +1 -1
  86. package/dist/{wizard-session-gbYCXdBf.js → wizard-session-BWAQoi9q.js} +1 -1
  87. package/dist/{wizard-session-Bki7dQhv.js → wizard-session-GdDN_aMS.js} +4 -1
  88. package/dist/wizard-session-GdDN_aMS.js.map +1 -0
  89. package/dist/{yara-hooks-BpngckdZ.js → yara-hooks-BvyFD2Nb.js} +4 -4
  90. package/dist/{yara-hooks-BpngckdZ.js.map → yara-hooks-BvyFD2Nb.js.map} +1 -1
  91. package/dist/yara-hooks-CuW_b30M.js +2 -0
  92. package/package.json +2 -1
  93. package/dist/agent-interface-Lu7tnLr0.js.map +0 -1
  94. package/dist/agent-runner-vKtpgqVI.js.map +0 -1
  95. package/dist/analytics-D-bMEYjD.js +0 -2
  96. package/dist/debug-BM0fZaGl.js.map +0 -1
  97. package/dist/errors-BSdz2lsl.js.map +0 -1
  98. package/dist/mcp-prompt-streaming-Dsos09hT.js.map +0 -1
  99. package/dist/orchestrator-tools-ezFTbAIp.js.map +0 -1
  100. package/dist/pi-DzQLHIcQ.js.map +0 -1
  101. package/dist/playground-DLN31BPV.js.map +0 -1
  102. package/dist/posthog-integration-MDfRO911.js +0 -4618
  103. package/dist/posthog-integration-MDfRO911.js.map +0 -1
  104. package/dist/provisioning-Di5GNV9k.js.map +0 -1
  105. package/dist/queue-tools-KosDptd6.js.map +0 -1
  106. package/dist/setup-utils-Bo3WZt3a.js.map +0 -1
  107. package/dist/start-tui-B1LrB7k1.js.map +0 -1
  108. package/dist/store-DhNTBWQR.js.map +0 -1
  109. package/dist/terminal-BjUFxn_x.js.map +0 -1
  110. package/dist/wizard-session-Bki7dQhv.js.map +0 -1
  111. package/dist/yara-hooks-C-qG6EJt.js +0 -2
@@ -1 +0,0 @@
1
- {"version":3,"file":"provisioning-Di5GNV9k.js","names":[],"sources":["../src/utils/urls.ts","../src/utils/provisioning.ts"],"sourcesContent":["import axios from 'axios';\nimport { IS_DEV, WIZARD_USER_AGENT } from '@lib/constants';\nimport type { CloudRegion } from './types';\n\n/**\n * Resolve a pinned PostHog base URL from an optional override. When present, it\n * becomes the single source of truth for every PostHog origin — the\n * API/ingestion host, the cloud/app URL, and the OAuth server — bypassing\n * region resolution entirely.\n *\n * The override lives on the session (`session.baseUrl`, set by `--base-url`) and\n * is threaded in by callers, so there is no hidden module-level state. A `--base-url`\n * value wins; otherwise a dev/test build implies `localhost:8010`; otherwise the\n * result is `undefined`, meaning region-based resolution applies.\n *\n * This is the runtime equivalent of the `IS_DEV` localhost routing: `IS_DEV` is a\n * build-time constant that tsdown compiles out of production builds, so it can't\n * point a shipped wizard at a local stack — `--base-url` can. Both feed this one\n * resolver so every URL helper only asks the question once.\n */\nexport const resolveBaseUrl = (override?: string): string | undefined => {\n return override?.trim() || (IS_DEV ? 'http://localhost:8010' : undefined);\n};\n\nexport const getUiHostFromHost = (host: string) => {\n if (host.includes('us.i.posthog.com')) {\n return 'https://us.posthog.com';\n }\n\n if (host.includes('eu.i.posthog.com')) {\n return 'https://eu.posthog.com';\n }\n\n return host;\n};\n\nexport const getHost = (region: CloudRegion, baseUrl?: string) => {\n const override = resolveBaseUrl(baseUrl);\n if (override) {\n return override;\n }\n\n if (region === 'eu') {\n return 'https://eu.i.posthog.com';\n }\n\n return 'https://us.i.posthog.com';\n};\n\nexport const getCloudUrl = (region: CloudRegion, baseUrl?: string) => {\n const override = resolveBaseUrl(baseUrl);\n if (override) {\n return override;\n }\n\n if (region === 'eu') {\n return 'https://eu.posthog.com';\n }\n\n return 'https://us.posthog.com';\n};\n\nexport async function detectRegion(\n accessToken: string,\n baseUrl?: string,\n): Promise<CloudRegion> {\n // With a pinned base URL there is only one instance to talk to — skip the\n // us/eu probe and default the region label to 'us'. (Covers IS_DEV too, via\n // resolveBaseUrl.) The URLs themselves come from the override, not this label.\n if (resolveBaseUrl(baseUrl)) {\n return 'us';\n }\n\n const headers = {\n Authorization: `Bearer ${accessToken}`,\n 'User-Agent': WIZARD_USER_AGENT,\n };\n\n const [usResult, euResult] = await Promise.allSettled([\n axios.get('https://us.posthog.com/api/users/@me/', { headers }),\n axios.get('https://eu.posthog.com/api/users/@me/', { headers }),\n ]);\n\n if (usResult.status === 'fulfilled') return 'us';\n if (euResult.status === 'fulfilled') return 'eu';\n\n throw new Error(\n 'Could not determine cloud region from access token. Please check your PostHog account.',\n );\n}\n\nexport const getLlmGatewayUrl = (host: string) => {\n if (host.includes('host.docker.internal')) {\n return 'http://host.docker.internal:3308/wizard';\n }\n\n if (host.includes('localhost')) {\n return 'http://localhost:3308/wizard';\n }\n\n if (host.includes('eu.posthog.com') || host.includes('eu.i.posthog.com')) {\n return 'https://gateway.eu.posthog.com/wizard';\n }\n\n return 'https://gateway.us.posthog.com/wizard';\n};\n\n/** Region-agnostic prod OAuth server. Resolves to the right region server-side. */\nconst PROD_OAUTH_URL = 'https://oauth.posthog.com';\n\n/**\n * OAuth server URL. Follows the base-URL override (and thus IS_DEV → localhost),\n * otherwise the region-agnostic prod OAuth host.\n */\nexport const getOAuthUrl = (baseUrl?: string): string =>\n resolveBaseUrl(baseUrl) ?? PROD_OAUTH_URL;\n","/**\n * Provisioning API client for creating new PostHog accounts.\n *\n * Uses the agentic provisioning API with PKCE auth:\n * 1. POST /account_requests - create account, get auth code\n * 2. POST /oauth/token - exchange code for tokens (with PKCE)\n * 3. POST /resources - provision project, get API key\n */\n\nimport * as crypto from 'node:crypto';\nimport axios from 'axios';\nimport { z } from 'zod';\nimport {\n POSTHOG_DEV_CLIENT_ID,\n POSTHOG_EU_CLIENT_ID,\n POSTHOG_US_CLIENT_ID,\n WIZARD_PROVISIONING_SCOPES,\n WIZARD_USER_AGENT,\n} from '@lib/constants';\nimport { resolveBaseUrl } from './urls';\nimport { logToFile } from './debug';\nimport { analytics } from './analytics';\nimport type { HostResolution } from '@lib/host-resolution';\n\nconst API_VERSION = '0.1d';\n\n/**\n * Provisioning host. Follows a `--base-url` override (and IS_DEV → localhost),\n * else the prod provisioning host for the target region. Unlike the login OAuth\n * flow (which goes through the region-agnostic `oauth.posthog.com` proxy), the\n * provisioning API is region-specific: an EU account must be created against\n * `eu.posthog.com`, and the subsequent token-exchange / resources calls stay on\n * the same host (they carry the account's bearer token).\n */\nconst getProvisioningBaseUrl = (\n region: 'US' | 'EU',\n baseUrl?: string,\n): string => {\n const override = resolveBaseUrl(baseUrl);\n if (override) return override;\n return region === 'EU' ? 'https://eu.posthog.com' : 'https://us.posthog.com';\n};\n\n/**\n * OAuth client ID for provisioning. A pinned base URL means a dev-seeded stack\n * that registers the dev client; prod uses the client registered for the target\n * region (the wizard OAuth app is registered separately per region).\n *\n * TODO: same assumption as `getOAuthClientId` in oauth.ts — a pinned base URL is\n * treated as a dev-seeded instance. Make configurable if we ever point\n * `--base-url` at a non-dev instance with its own OAuth app.\n */\nconst getProvisioningClientId = (\n region: 'US' | 'EU',\n baseUrl?: string,\n): string => {\n if (resolveBaseUrl(baseUrl)) return POSTHOG_DEV_CLIENT_ID;\n return region === 'EU' ? POSTHOG_EU_CLIENT_ID : POSTHOG_US_CLIENT_ID;\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\n// --- Response schemas ---\n//\n// These schemas validate only the fields the wizard actually reads. The rule is load\n// bearing, not stylistic: the wizard ships to npm and old versions keep running against\n// a provisioning API that moves independently, so a field we require but never consume\n// is a crash we cannot fix in the field. `service_id` was exactly that — required here,\n// read nowhere, and every signup broke the day the API stopped echoing it. Zod strips\n// unknown keys, so fields the API adds are tolerated for free; the only way to break is\n// to demand something back.\n\nconst AccountRequestResponseSchema = z.object({\n id: z.string(),\n type: z.enum(['oauth', 'requires_auth', 'error']),\n oauth: z\n .object({\n code: z.string(),\n })\n .optional(),\n error: z\n .object({\n code: z.string(),\n message: z.string(),\n })\n .optional(),\n});\n\nconst TokenResponseSchema = z.object({\n token_type: z.string(),\n access_token: z.string(),\n refresh_token: z.string(),\n expires_in: z.number(),\n account: z\n .object({\n id: z.string(),\n })\n .optional(),\n});\n\nconst AccessConfigurationSchema = z.object({\n api_key: z.string(),\n host: z.string(),\n personal_api_key: z.string().optional(),\n});\n\nconst ResourceResponseSchema = z.object({\n status: z.string(),\n id: z.string(),\n complete: z\n .object({\n access_configuration: AccessConfigurationSchema,\n })\n .optional(),\n});\n\n/**\n * Teams the account can reach, from the token exchange. Parsed separately from\n * `TokenResponseSchema` and as loosely as possible: it is only used to recover from a\n * response we already failed to read, so it must never become a new way to fail.\n */\nconst AvailableTeamsSchema = z.array(\n z.object({ id: z.union([z.number(), z.string()]) }),\n);\n\n/**\n * Thrown when the provisioning API completed the work but the wizard could not read the\n * project back out of the response. The account, organization and project exist — callers\n * must not tell the user their account wasn't created.\n */\nexport class ProvisionedAccountUnreadableError extends Error {\n readonly accountCreated = true;\n\n constructor(message: string) {\n super(message);\n this.name = 'ProvisionedAccountUnreadableError';\n }\n}\n\n/** The field paths Zod rejected — never the values, which carry tokens and API keys. */\nfunction issuePaths(error: z.ZodError): string[] {\n return error.issues.map((issue) => issue.path.join('.') || '(root)');\n}\n\n/**\n * Report a response the wizard couldn't read. Silently degrading on a shape mismatch\n * turns the next contract change into a week of guesswork, so the rejected paths go to\n * the debug log and to error tracking before anything else happens.\n */\nfunction reportUnreadableResponse(step: string, error: z.ZodError): Error {\n const paths = issuePaths(error);\n const reported = new Error(\n `Unexpected ${step} response from the provisioning API (missing or invalid: ${paths.join(\n ', ',\n )})`,\n );\n\n logToFile(\n `[provisioning] unexpected ${step} response shape: ${paths.join(', ')}`,\n );\n analytics.captureException(reported, {\n step: `provisioning_${step}`,\n issue_paths: paths,\n });\n\n return reported;\n}\n\nfunction parseOrThrow<T extends z.ZodTypeAny>(\n schema: T,\n data: unknown,\n step: string,\n): z.infer<T> {\n const parsed = schema.safeParse(data);\n if (!parsed.success) {\n throw reportUnreadableResponse(step, parsed.error);\n }\n return parsed.data;\n}\n\ntype ResourceRead =\n | {\n ok: true;\n projectId: string;\n access: z.infer<typeof AccessConfigurationSchema>;\n }\n | { ok: false; reason: 'unreadable' | 'incomplete' };\n\nfunction readResource(data: unknown, step: string): ResourceRead {\n const parsed = ResourceResponseSchema.safeParse(data);\n if (!parsed.success) {\n reportUnreadableResponse(step, parsed.error);\n return { ok: false, reason: 'unreadable' };\n }\n\n if (parsed.data.status !== 'complete' || !parsed.data.complete) {\n logToFile(`[provisioning] ${step} returned status=${parsed.data.status}`);\n return { ok: false, reason: 'incomplete' };\n }\n\n return {\n ok: true,\n projectId: parsed.data.id,\n access: parsed.data.complete.access_configuration,\n };\n}\n\n/**\n * Recover the project after an unreadable `POST /resources` response.\n *\n * By the time that call returns 2xx the account, organization and project exist, so\n * aborting would abandon a provisioned account and leave the user with an orphaned\n * project. `GET /resources/:id` returns the same access configuration, so read it back\n * instead. Best-effort by design: any failure returns null and the caller reports the\n * original drift. The detail endpoint never mints a personal API key, so a recovered\n * result has none — `personalApiKey` is optional for every consumer.\n */\nasync function readBackResource(\n provisioningBaseUrl: string,\n accessToken: string,\n tokenPayload: unknown,\n): Promise<ResourceRead | null> {\n const teams = AvailableTeamsSchema.safeParse(\n (tokenPayload as { account?: { available_teams?: unknown } } | undefined)\n ?.account?.available_teams,\n );\n\n // A freshly provisioned account owns exactly one team. With any other count we can't\n // tell which team the create call made, and guessing would capture into the wrong one.\n if (!teams.success || teams.data.length !== 1) {\n logToFile(\n '[provisioning] no unambiguous team to read the resource back from',\n );\n return null;\n }\n\n const resourceId = String(teams.data[0].id);\n\n try {\n const res = await axios.get(\n `${provisioningBaseUrl}/api/agentic/provisioning/resources/${resourceId}`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'API-Version': API_VERSION,\n 'User-Agent': WIZARD_USER_AGENT,\n },\n timeout: 30_000,\n },\n );\n\n logToFile(\n `[provisioning] read resource ${resourceId} back after unreadable create`,\n );\n return readResource(res.data, 'resource_readback');\n } catch {\n logToFile(`[provisioning] read-back of resource ${resourceId} failed`);\n return null;\n }\n}\n\nexport interface ProvisioningResult {\n accessToken: string;\n refreshToken: string;\n projectApiKey: string;\n host: string;\n personalApiKey?: string;\n projectId: string;\n accountId: string;\n}\n\n/**\n * Create a new PostHog account and provision a project via the provisioning API.\n *\n * This is the \"no browser\" signup path: the wizard collects the email,\n * calls the provisioning API to create the account, and gets back\n * credentials without opening a browser.\n */\nexport async function provisionNewAccount(\n email: string,\n name: string,\n region: 'US' | 'EU' = 'US',\n opts?: {\n orgName?: string;\n projectName?: string;\n baseUrl?: string;\n /** Scope list to request; defaults to `WIZARD_PROVISIONING_SCOPES`. */\n scopes?: readonly string[];\n },\n): Promise<ProvisioningResult> {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n const provisioningBaseUrl = getProvisioningBaseUrl(region, opts?.baseUrl);\n\n logToFile('[provisioning] starting account creation');\n\n // Step 1: Create account\n const accountRes = await axios.post(\n `${provisioningBaseUrl}/api/agentic/provisioning/account_requests`,\n {\n id: crypto.randomUUID(),\n email,\n name,\n client_id: getProvisioningClientId(region, opts?.baseUrl),\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n scopes: opts?.scopes ?? WIZARD_PROVISIONING_SCOPES,\n configuration: {\n region,\n ...(opts?.orgName ? { organization_name: opts.orgName } : {}),\n },\n },\n {\n headers: {\n 'Content-Type': 'application/json',\n 'API-Version': API_VERSION,\n 'User-Agent': WIZARD_USER_AGENT,\n },\n timeout: 30_000,\n },\n );\n\n const accountData = parseOrThrow(\n AccountRequestResponseSchema,\n accountRes.data,\n 'account_requests',\n );\n\n if (accountData.type === 'error') {\n const msg = accountData.error?.message ?? 'Account creation failed';\n analytics.captureException(new Error(msg), {\n step: 'provisioning_account_request',\n error_code: accountData.error?.code,\n });\n throw new Error(msg);\n }\n\n if (accountData.type === 'requires_auth') {\n throw new Error(\n 'This email is already associated with a PostHog account. Please use the login flow instead.',\n );\n }\n\n const code = accountData.oauth?.code;\n if (!code) {\n throw new Error('No authorization code received from account creation');\n }\n\n logToFile('[provisioning] account created, exchanging code for tokens');\n\n // Step 2: Exchange code for tokens\n const tokenRes = await axios.post(\n `${provisioningBaseUrl}/api/agentic/oauth/token`,\n new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n code_verifier: codeVerifier,\n }).toString(),\n {\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'API-Version': API_VERSION,\n 'User-Agent': WIZARD_USER_AGENT,\n },\n timeout: 30_000,\n },\n );\n\n const tokenData = parseOrThrow(\n TokenResponseSchema,\n tokenRes.data,\n 'oauth_token',\n );\n\n logToFile('[provisioning] tokens received, provisioning resources');\n\n // Step 3: Provision resources\n const resourceRes = await axios.post(\n `${provisioningBaseUrl}/api/agentic/provisioning/resources`,\n {\n ...(opts?.projectName\n ? { configuration: { project_name: opts.projectName } }\n : {}),\n },\n {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${tokenData.access_token}`,\n 'API-Version': API_VERSION,\n 'User-Agent': WIZARD_USER_AGENT,\n },\n timeout: 30_000,\n },\n );\n\n // `/resources` is synchronous: it answers `status: \"complete\"` or an error envelope.\n // So an unreadable body means the contract moved, not that provisioning is still\n // running — and the project already exists either way, which is why this recovers\n // instead of aborting.\n let resource = readResource(resourceRes.data, 'resources');\n\n if (!resource.ok && resource.reason === 'unreadable') {\n resource =\n (await readBackResource(\n provisioningBaseUrl,\n tokenData.access_token,\n tokenRes.data,\n )) ?? resource;\n }\n\n if (!resource.ok) {\n if (resource.reason === 'incomplete') {\n throw new Error('Resource provisioning did not complete');\n }\n throw new ProvisionedAccountUnreadableError(\n 'Your PostHog project was created, but the provisioning API returned a response the wizard could not read',\n );\n }\n\n logToFile('[provisioning] resources provisioned successfully');\n\n return {\n accessToken: tokenData.access_token,\n refreshToken: tokenData.refresh_token,\n projectApiKey: resource.access.api_key,\n host: resource.access.host,\n personalApiKey: resource.access.personal_api_key,\n projectId: resource.projectId,\n accountId: tokenData.account?.id ?? '',\n };\n}\n\n/**\n * Request a one-time deep link URL that logs the user into PostHog\n * and redirects to their project dashboard — or, when `opts.path` is\n * given, to any safe in-app relative path (e.g. the Signals inbox).\n */\nexport async function requestDeepLink(\n accessToken: string,\n host: HostResolution,\n opts?: { purpose?: string; path?: string },\n): Promise<string | null> {\n try {\n const baseUrl = host.appHost;\n\n const res = await axios.post(\n `${baseUrl}/api/agentic/provisioning/deep_links`,\n {\n purpose: opts?.purpose ?? 'dashboard',\n ...(opts?.path ? { path: opts.path } : {}),\n },\n {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${accessToken}`,\n 'API-Version': API_VERSION,\n 'User-Agent': WIZARD_USER_AGENT,\n },\n timeout: 10_000,\n },\n );\n\n const url = res.data?.url;\n if (typeof url === 'string') {\n logToFile(`[provisioning] deep link created: ${url}`);\n return url;\n }\n return null;\n } catch {\n logToFile('[provisioning] deep link request failed, skipping');\n return null;\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"queue-tools-KosDptd6.js","names":["path"],"sources":["../src/lib/agent/runner/switchboard/models.ts","../src/utils/atomic-ledger.ts","../src/lib/agent/runner/sequence/orchestrator/queue.ts","../src/lib/agent/runner/sequence/orchestrator/queue-tools.ts"],"sourcesContent":["/**\n * Model capabilities — the traits a harness needs that a bare gateway model id\n * doesn't carry. The switchboard resolves *which* model (harness.ts); this\n * resolves *what the model can do*, so a harness never hardcodes it.\n *\n * `reasoning` gates whether a harness requests reasoning at all; `thinkingLevel`\n * sets how much. Non-reasoning openai-completions models reject the reasoning\n * params (gpt-4o → gateway `UnsupportedParamsError` → the pi run no-ops), and\n * effort trades speed for depth (flagship gpt-5 at high effort runs long). Both\n * are silent when wrong, so they live here as one configurable table.\n */\nimport {\n DEFAULT_AGENT_MODEL,\n SONNET_5_MODEL,\n OPUS_MODEL,\n HAIKU_MODEL,\n GPT5_6_LUNA_MODEL,\n GPT5_6_SOL_MODEL,\n GPT5_6_TERRA_MODEL,\n HAIKU_TRIAGE_MODEL,\n Harness,\n} from '@lib/constants';\n\n/** Reasoning effort. pi maps it to `reasoning_effort` for openai-completions. */\nconst THINKING_LEVELS = [\n 'off',\n 'minimal',\n 'low',\n 'medium',\n 'high',\n 'xhigh',\n] as const;\nexport type ThinkingLevel = (typeof THINKING_LEVELS)[number];\n\n/** Whether a value (e.g. remote prompt frontmatter) names a valid effort. */\nexport function isThinkingLevel(value: unknown): value is ThinkingLevel {\n return (THINKING_LEVELS as readonly unknown[]).includes(value);\n}\n\n/**\n * An effort *override* is always a positive level — `'off'` is a model trait\n * (`reasoning: false`), not something a flag may force onto a reasoning model.\n */\nexport type EffortLevel = Exclude<ThinkingLevel, 'off'>;\n\nexport interface ModelCapabilities {\n /** Model supports reasoning; safe to request reasoning effort. */\n reasoning: boolean;\n /** Effort to request when reasoning. Omit for the harness/provider default. */\n thinkingLevel?: ThinkingLevel;\n}\n\n/** Explicit per-model traits. Anything absent falls back to `defaultCaps`. */\nexport const MODEL_CAPABILITIES: Record<string, ModelCapabilities> = {\n [DEFAULT_AGENT_MODEL]: { reasoning: true }, // claude-sonnet-4-6\n [SONNET_5_MODEL]: { reasoning: true },\n [OPUS_MODEL]: { reasoning: true },\n [HAIKU_MODEL]: { reasoning: true },\n // The openai 5.6 line; all reasoning models, so they must opt in past the\n // openai-completions default (reasoning off). Luna stays low for cheap,\n // short-context mechanical work; terra runs medium as the sonnet-tier parallel\n // — enough reasoning depth for the judgment tasks without high's latency blowup.\n [GPT5_6_LUNA_MODEL]: { reasoning: true, thinkingLevel: 'low' },\n [GPT5_6_TERRA_MODEL]: { reasoning: true, thinkingLevel: 'medium' },\n [GPT5_6_SOL_MODEL]: { reasoning: true, thinkingLevel: 'low' },\n};\n\n/** The only models the wizard may dispatch on. */\nexport const VALID_MODELS: ReadonlySet<string> = new Set(\n Object.keys(MODEL_CAPABILITIES),\n);\n\n/** Whether the value is an allow-listed model. */\nexport function isValidModel(model: unknown): model is string {\n return typeof model === 'string' && VALID_MODELS.has(model);\n}\n\n/** First allow-listed candidate, else throw so a bad id fails loud. */\nexport function requireKnownModel(\n ...candidates: (string | undefined)[]\n): string {\n for (const c of candidates) if (isValidModel(c)) return c;\n throw new Error(\n `No valid model to dispatch on (tried ${JSON.stringify(candidates)}). ` +\n `Allowed: ${[...VALID_MODELS].join(', ')}.`,\n );\n}\n\n/**\n * Default for a model not in the table: reasoning on for anthropic-messages\n * models, off for openai-completions — the non-reasoning openai models reject\n * reasoning effort, so off is the safe default (a reasoning openai model opts\n * back in via the table above). Transport is inferred the same way the pi\n * harness infers it (`openai/` prefix → openai-completions).\n */\nfunction defaultCaps(modelId: string): ModelCapabilities {\n return { reasoning: !modelId.startsWith('openai/') };\n}\n\n/**\n * Scan-triage classifier per harness: the cheapest tier of the line that harness\n * already speaks. Undated ids on purpose — triage is a boolean classifier, so it\n * should follow the current release rather than pin one, and these are not\n * dispatchable agent models (absent from MODEL_CAPABILITIES by design).\n */\nexport const TRIAGE_MODELS: Record<Harness, string> = {\n [Harness.anthropic]: HAIKU_TRIAGE_MODEL,\n [Harness.pi]: GPT5_6_LUNA_MODEL,\n};\n\nexport function triageModelFor(harness: Harness): string {\n return TRIAGE_MODELS[harness];\n}\n\n/**\n * Capabilities for a gateway model id, table override then transport default.\n * `effortOverride` is a switchboard-resolved effort (e.g. from a pi effort\n * flag); it applies only when the model reasons at all — a non-reasoning model\n * rejects reasoning effort, so the override is dropped.\n */\nexport function modelCapabilities(\n modelId: string,\n effortOverride?: EffortLevel,\n): ModelCapabilities {\n const caps = MODEL_CAPABILITIES[modelId] ?? defaultCaps(modelId);\n if (caps.reasoning && effortOverride) {\n return { ...caps, thinkingLevel: effortOverride };\n }\n return caps;\n}\n","/**\n * Small shared primitives for on-disk ledgers: an atomic JSON writer and a\n * single-chain async mutex. Used by the audit tools and by the orchestrator\n * queue. Lifted here so both share one implementation.\n */\nimport * as fs from 'fs';\n\n/**\n * Atomically write JSON: write to a `.tmp` file then rename over the target. The\n * rename bumps the file's mtime in one step, which is what a file watcher polls.\n */\nexport function writeJsonAtomic(targetPath: string, data: unknown): void {\n const tmpPath = `${targetPath}.tmp`;\n fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf8');\n fs.renameSync(tmpPath, targetPath);\n}\n\n/**\n * A single async mutex. Serializes read-modify-write cycles so concurrent callers\n * (parallel task agents, audit tool calls) never interleave a mutation.\n */\nexport function makeMutex() {\n let chain: Promise<unknown> = Promise.resolve();\n return async function run<T>(fn: () => Promise<T> | T): Promise<T> {\n const next = chain.then(() => fn());\n chain = next.catch(() => undefined);\n return next;\n };\n}\n","/**\n * The orchestrator task queue.\n *\n * In memory, synchronous, single-owner: one Node process drives the run, so\n * there is no locking. The queue imposes no execution policy — `nextRunnable`\n * returns every pending task whose dependencies are satisfied, and how many of\n * those run at once is decided by the task graph, not the queue.\n *\n * Every transition rewrites `<installDir>/.posthog-wizard-cache/queue.json`, a\n * small file holding the whole queue, handoffs included. It is the run's log\n * and the report's source. The whole cache folder is run-scoped and wiped when\n * the run ends.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { randomUUID } from 'crypto';\nimport { writeJsonAtomic } from '@utils/atomic-ledger';\nimport { analytics } from '@utils/analytics';\n\nexport const TaskStatus = {\n Pending: 'pending',\n Running: 'running',\n Done: 'done',\n Skipped: 'not needed',\n Failed: 'failed',\n} as const;\n\nexport type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];\n\n/**\n * Why a task ended as skipped.\n *\n * A skip has several causes that look identical from outside: an agent finding\n * the step does not apply, a user declining it, and an offer nobody answered.\n * They mean opposite things — the first is a no-op, the last two are the user\n * telling us something — and telling them apart needs the reason recorded at\n * the moment of the skip, not inferred later from timings.\n *\n * Recorded on the task like `error` is on a failed one, so it reaches the\n * skipped-task event and the run's queue.json alike.\n */\nexport const SkipReason = {\n /** The user answered the step's notice with Skip. */\n UserDeclined: 'user-declined',\n /** The notice went unanswered until it timed out. */\n NoticeTimeout: 'notice-timeout',\n /** The notice could not be shown at all, so consent failed closed. */\n NoticeError: 'notice-error',\n /** The task agent reported `not needed`: the step did not apply here. */\n AgentNotNeeded: 'agent-not-needed',\n} as const;\n\nexport type SkipReason = (typeof SkipReason)[keyof typeof SkipReason];\n\nexport interface QueuedTask {\n id: string;\n type: string;\n /** Human-readable label for the TUI, set by the enqueuing agent. */\n label?: string;\n status: TaskStatus;\n /**\n * Ids of tasks that must finish before this one runs. Ids are generated at\n * enqueue, so a task can only depend on tasks created before it — the graph is\n * a DAG by construction, cycles cannot form. Unknown ids are rejected by the\n * enqueue_task guard.\n *\n * One sanctioned exception: {@link QueueStore.addDependencies} adds edges to a\n * still-pending task, for a runner-seeded task that was queued before the\n * planner ran and so could not name its dependencies then. It is additive,\n * pending-only, and cycle-checked per edge, so the DAG property above still\n * holds. Nothing else mutates this field.\n */\n dependsOn: string[];\n inputs: Record<string, unknown>;\n model?: string;\n attempts: number;\n maxAttempts: number;\n /** The structured handoff the task reported on completion. */\n handoff?: TaskHandoff;\n /** 'orchestrator' for seeded tasks, or the id of the task that enqueued this one. */\n enqueuedBy: string;\n /** Wizard-seeded only: terminal failure unblocks dependents and never fails the run. */\n optional?: boolean;\n createdAt: string;\n startedAt?: string;\n finishedAt?: string;\n error?: { type: string; message: string };\n /** Set when `status` is `not needed`: why. The failed-task `error` of a skip. */\n skipReason?: SkipReason;\n}\n\nexport interface QueueFile {\n version: 1;\n runId: string;\n tasks: QueuedTask[];\n}\n\n/** The structured handoff a task leaves for the next agent. */\nexport interface TaskHandoff {\n goals: string;\n did: string;\n forNextAgent: string;\n filesTouched?: string[];\n /** How the agent knows it worked — what it ran or observed. */\n evidence?: string;\n /** What the agent assumed about the app and could not verify. */\n assumptions?: string;\n /** A one-line summary of any unresolved conflict, surfaced in the outro. */\n conflict?: string;\n /** A finished section for the run's report, written by the task that owns the subject. */\n reportSection?: string;\n}\n\nexport interface EnqueueInput {\n type: string;\n label?: string;\n inputs?: Record<string, unknown>;\n dependsOn?: string[];\n model?: string;\n maxAttempts?: number;\n enqueuedBy?: string;\n optional?: boolean;\n}\n\nexport const QUEUE_DIR_NAME = '.posthog-wizard-cache';\nconst DEFAULT_MAX_ATTEMPTS = 2;\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\n/** Dropped in the cache folder so an orphaned copy explains itself. */\nconst DELETE_ME_FILE = '.DELETE-ME.md';\nconst DELETE_ME_BODY = `# Safe to delete\n\nThis folder contains run artifacts from the PostHog Wizard. This should have\nbeen deleted if the Wizard has finished running. If this wasn't deleted for\nsome reason, you can safely delete the entire \\`${QUEUE_DIR_NAME}/\\` folder.\n`;\n\n/** Every queue transition, in the order it is reflected. */\nexport type TransitionEvent =\n | 'enqueue'\n | 'start'\n | 'complete'\n | 'skip'\n | 'fail'\n | 'requeue';\n\nexport interface QueueStoreOptions {\n /**\n * Called on every transition with the task's post-transition state. The\n * runner uses it for telemetry; the store itself stays analytics-free.\n * Listener errors are reported but cannot break a transition.\n */\n onTransition?: (event: TransitionEvent, task: QueuedTask) => void;\n}\n\nexport class QueueStore {\n private tasks: QueuedTask[] = [];\n private readonly onTransition?: (\n event: TransitionEvent,\n task: QueuedTask,\n ) => void;\n\n readonly runId: string;\n readonly queuePath: string;\n\n constructor(installDir: string, runId: string, opts?: QueueStoreOptions) {\n this.onTransition = opts?.onTransition;\n this.runId = runId;\n const dir = path.join(installDir, QUEUE_DIR_NAME);\n this.queuePath = path.join(dir, 'queue.json');\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(path.join(dir, DELETE_ME_FILE), DELETE_ME_BODY);\n }\n\n // ── Reads ───────────────────────────────────────────────────────────\n\n list(): readonly QueuedTask[] {\n return this.tasks;\n }\n\n get(id: string): QueuedTask | undefined {\n return this.tasks.find((t) => t.id === id);\n }\n\n /**\n * Every pending task whose dependencies are all satisfied (`done` or\n * `skipped`). A skipped dependency does not block downstream work.\n */\n nextRunnable(): QueuedTask[] {\n // A TERMINALLY failed optional dep satisfies like skipped; retryable failure still blocks.\n const doneIds = new Set(\n this.tasks\n .filter(\n (t) =>\n t.status === TaskStatus.Done ||\n t.status === TaskStatus.Skipped ||\n (t.status === TaskStatus.Failed &&\n t.optional === true &&\n t.attempts >= t.maxAttempts),\n )\n .map((t) => t.id),\n );\n return this.tasks.filter(\n (t) =>\n t.status === TaskStatus.Pending &&\n t.dependsOn.every((d) => doneIds.has(d)),\n );\n }\n\n /**\n * True when no task is running and none can be started. Either everything\n * is terminal, or the only pending tasks are blocked by a failed dependency.\n */\n isDrained(): boolean {\n if (this.tasks.some((t) => t.status === TaskStatus.Running)) return false;\n return this.nextRunnable().length === 0;\n }\n\n summary(): Record<TaskStatus, number> & { total: number } {\n const counts: Record<TaskStatus, number> = {\n [TaskStatus.Pending]: 0,\n [TaskStatus.Running]: 0,\n [TaskStatus.Done]: 0,\n [TaskStatus.Skipped]: 0,\n [TaskStatus.Failed]: 0,\n };\n for (const t of this.tasks) counts[t.status] += 1;\n return { ...counts, total: this.tasks.length };\n }\n\n readHandoff(id: string): TaskHandoff | null {\n return this.get(id)?.handoff ?? null;\n }\n\n /** Handoffs of completed tasks of a given type, oldest first. */\n readHandoffsByType(type: string): TaskHandoff[] {\n return this.tasks\n .filter((t) => t.type === type && t.handoff)\n .map((t) => t.handoff as TaskHandoff);\n }\n\n // ── Transitions (each one reflected to queue.json) ──────────────────\n\n enqueue(input: EnqueueInput): QueuedTask {\n const task: QueuedTask = {\n id: randomUUID(),\n type: input.type,\n label: input.label,\n status: TaskStatus.Pending,\n dependsOn: input.dependsOn ?? [],\n inputs: input.inputs ?? {},\n model: input.model,\n attempts: 0,\n maxAttempts: input.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,\n enqueuedBy: input.enqueuedBy ?? 'orchestrator',\n optional: input.optional,\n createdAt: nowIso(),\n };\n this.tasks.push(task);\n this.reflect();\n this.notify('enqueue', task);\n return task;\n }\n\n /**\n * Add dependency edges to a pending task — the one sanctioned exception to\n * `dependsOn` being immutable (see the field's doc).\n *\n * A runner-seeded task is enqueued before the planner runs, so the tasks it\n * should wait for do not exist yet and it cannot name them. This closes that\n * gap once, between planning and the drain.\n *\n * Three rules keep the graph a DAG: additive only (never removes an edge),\n * pending only (a task that already started keeps the graph it ran under), and\n * every edge cycle-checked — a dep that already reaches this task through its\n * own chain is refused. Refusals come back in the result rather than throwing:\n * one bad edge is a bug worth reporting, not a reason to end a run that is\n * otherwise fine.\n */\n addDependencies(\n id: string,\n depIds: readonly string[],\n ): { added: string[]; refused: string[] } {\n const task = this.require(id);\n if (task.status !== TaskStatus.Pending) {\n return { added: [], refused: [...depIds] };\n }\n\n const added: string[] = [];\n const refused: string[] = [];\n for (const depId of depIds) {\n // Already an edge, or a self-loop: nothing to do and nothing wrong.\n if (depId === id || task.dependsOn.includes(depId)) continue;\n // Checked against the graph as it stands, so two edges added in one call\n // cannot together form a cycle the first check missed.\n if (!this.get(depId) || this.reaches(depId, id)) {\n refused.push(depId);\n continue;\n }\n task.dependsOn.push(depId);\n added.push(depId);\n }\n if (added.length > 0) this.reflect();\n return { added, refused };\n }\n\n start(id: string): QueuedTask {\n const t = this.require(id);\n t.status = TaskStatus.Running;\n t.startedAt = nowIso();\n t.attempts += 1;\n this.reflect();\n this.notify('start', t);\n return t;\n }\n\n complete(id: string, handoff?: TaskHandoff): QueuedTask {\n return this.finish(id, TaskStatus.Done, handoff);\n }\n\n /**\n * Terminal: the task was not done, and that is not a failure.\n *\n * The reason is required, and sits before the optional handoff for that\n * reason. A skip carrying no reason is what let a five-minute auto-decline\n * hide inside the same event as an agent deciding a step did not apply.\n */\n skip(id: string, reason: SkipReason, handoff?: TaskHandoff): QueuedTask {\n const t = this.require(id);\n t.skipReason = reason;\n return this.finish(id, TaskStatus.Skipped, handoff);\n }\n\n fail(\n id: string,\n error: { type: string; message: string },\n handoff?: TaskHandoff,\n ): QueuedTask {\n const t = this.require(id);\n t.error = error;\n return this.finish(id, TaskStatus.Failed, handoff);\n }\n\n /** Put a failed/running task back to pending for a retry within the run. */\n requeue(id: string): QueuedTask {\n const t = this.require(id);\n t.status = TaskStatus.Pending;\n t.startedAt = undefined;\n t.finishedAt = undefined;\n this.reflect();\n this.notify('requeue', t);\n return t;\n }\n\n // ── Internals ───────────────────────────────────────────────────────\n\n private finish(\n id: string,\n status: 'done' | 'not needed' | 'failed',\n handoff?: TaskHandoff,\n ): QueuedTask {\n const t = this.require(id);\n if (handoff) t.handoff = handoff;\n t.status = status;\n t.finishedAt = nowIso();\n this.reflect();\n this.notify(\n status === TaskStatus.Done\n ? 'complete'\n : status === TaskStatus.Skipped\n ? 'skip'\n : 'fail',\n t,\n );\n return t;\n }\n\n private reflect(): void {\n const file: QueueFile = {\n version: 1,\n runId: this.runId,\n tasks: this.tasks,\n };\n writeJsonAtomic(this.queuePath, file);\n }\n\n private notify(event: TransitionEvent, task: QueuedTask): void {\n try {\n this.onTransition?.(event, task);\n } catch (error) {\n // A listener must never break a transition, but its failure is a bug.\n analytics.captureException(\n error instanceof Error ? error : new Error(String(error)),\n { step: 'orchestrator_queue_listener', event },\n );\n }\n }\n\n /** Whether `fromId` reaches `targetId` by following dependsOn edges. */\n private reaches(fromId: string, targetId: string): boolean {\n const seen = new Set<string>();\n const stack = [fromId];\n while (stack.length > 0) {\n const current = stack.pop() as string;\n if (current === targetId) return true;\n if (seen.has(current)) continue;\n seen.add(current);\n stack.push(...(this.get(current)?.dependsOn ?? []));\n }\n return false;\n }\n\n private require(id: string): QueuedTask {\n const t = this.get(id);\n if (!t) throw new Error(`No task ${id} in the queue`);\n return t;\n }\n}\n","/**\n * Orchestrator MCP tools, registered into the existing `wizard-tools` server when\n * a queue is present. They let the orchestrator agent and task agents grow the\n * queue, report completion with a structured handoff, and read prior handoffs.\n *\n * The guard logic and the apply functions are plain, exported, and unit-tested.\n * `buildOrchestratorTools` wraps them in the SDK `tool()` shape.\n */\nimport { z } from 'zod';\nimport { analytics } from '@utils/analytics';\nimport {\n isValidModel,\n VALID_MODELS,\n} from '@lib/agent/runner/switchboard/models';\nimport {\n SkipReason,\n TaskStatus,\n type QueueStore,\n type QueuedTask,\n type TaskHandoff,\n} from './queue';\n\n/** The per-task remark ask, shared by both harnesses' complete_task schemas. */\nexport const REMARK_ASK =\n 'What information or guidance would have been useful to have in the integration prompt or documentation for this task — specifically anything that would have prevented tool failures, erroneous edits, or other wasted turns.';\n\nexport interface OrchestratorToolsContext {\n store: QueueStore;\n /** Task types the registry knows about. enqueue_task rejects anything else. */\n validTypes: readonly string[];\n /**\n * Types marked `sink: true` — the ones that run last and must therefore\n * depend, transitively, on every other task in the queue. Enqueuing one with\n * an incomplete closure is rejected, so a task the runner seeded before the\n * planner ran can never be left un-awaited.\n */\n sinkTypes?: readonly string[];\n /**\n * Types the wizard queues itself, before the planner runs. They are deferred\n * to the end of the drain and may stop to ask the user for input, so the edge\n * to them is one-way: the sink depends on them, nothing else may. See\n * {@link seededDepViolations}.\n */\n runnerSeededTypes?: readonly string[];\n /**\n * The id of the task this tool server is bound to. Each task agent gets its\n * own wizard-tools server, so attribution holds when independent tasks run\n * in parallel. Absent for the seed, which is not a task.\n */\n currentTaskId?: string;\n}\n\nexport interface EnqueueArgs {\n type: string;\n label?: string;\n inputs?: Record<string, unknown>;\n dependsOn?: string[];\n model?: string;\n reason: string;\n}\n\nexport type GuardResult =\n | { ok: true }\n | { ok: false; guard: string; message: string };\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;\n const entries = Object.entries(value as Record<string, unknown>).sort(\n ([a], [b]) => a.localeCompare(b),\n );\n return `{${entries\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .join(',')}}`;\n}\n\nfunction dedupKey(type: string, inputs: Record<string, unknown>): string {\n return `${type}::${stableStringify(inputs)}`;\n}\n\n/**\n * A backstop on total queue size. Tasks can enqueue tasks, so a misbehaving\n * type could grow the queue without bound. Keeping the graph small is the job\n * of good agent and skill design, not this number — it only stops a runaway.\n * The real flow is ~9 tasks, so this sits well clear of it.\n */\nconst MAX_QUEUE_TASKS = 30;\n\n/** Every task id reachable from `roots` through dependsOn edges, roots included. */\nexport function dependencyClosure(\n store: QueueStore,\n roots: readonly string[],\n): Set<string> {\n const seen = new Set<string>();\n const visit = (id: string): void => {\n if (seen.has(id)) return;\n seen.add(id);\n for (const dep of store.get(id)?.dependsOn ?? []) visit(dep);\n };\n for (const id of roots) visit(id);\n return seen;\n}\n\n/**\n * Tasks a sink would fail to wait for. A sink runs last by definition, so any\n * queued task outside its dependency closure is work whose handoff the sink\n * would miss — the exact failure mode of a task seeded before the planner ran.\n * Empty for a non-sink type, and for a sink that covers everything.\n */\nexport function uncoveredBySink(\n ctx: OrchestratorToolsContext,\n args: Pick<EnqueueArgs, 'type' | 'dependsOn'>,\n): QueuedTask[] {\n if (!ctx.sinkTypes?.includes(args.type)) return [];\n const covered = dependencyClosure(ctx.store, args.dependsOn ?? []);\n return ctx.store.list().filter((t) => !covered.has(t.id));\n}\n\n/**\n * Runner-seeded tasks a non-sink task would end up waiting for.\n *\n * The edge to a runner-seeded task is one-way. Such a task is deferred to the\n * end of the drain and may stop to ask the user for input, so anything that\n * waits on it inherits that wait — a coding step gated on someone typing a\n * database password. The sink is the sole exception: it runs last by\n * definition and must depend on every task, this one included.\n *\n * The whole closure is checked, not just direct dependencies, so the rule\n * cannot be reached through an intermediate hop.\n *\n * Empty for a sink, and for any task that does not reach a seeded task.\n */\nexport function seededDepViolations(\n ctx: OrchestratorToolsContext,\n args: Pick<EnqueueArgs, 'type' | 'dependsOn'>,\n): QueuedTask[] {\n const seeded = ctx.runnerSeededTypes ?? [];\n if (seeded.length === 0) return [];\n if (ctx.sinkTypes?.includes(args.type)) return [];\n const reached = dependencyClosure(ctx.store, args.dependsOn ?? []);\n return ctx.store\n .list()\n .filter((t) => reached.has(t.id) && seeded.includes(t.type));\n}\n\n/**\n * Validate an enqueue. Structural checks only — a real type, real dependencies,\n * a sink that waits for everything, no non-sink task waiting on a runner-seeded\n * one, not a literal duplicate, and not past the runaway backstop. How much\n * runs, and in what shape, is the task graph's business, not a knob's.\n */\nexport function checkEnqueueGuards(\n ctx: OrchestratorToolsContext,\n args: EnqueueArgs,\n): GuardResult {\n const tasks = ctx.store.list();\n\n if (tasks.length >= MAX_QUEUE_TASKS) {\n return {\n ok: false,\n guard: 'queue-full',\n message: `The queue already holds ${tasks.length} tasks (cap ${MAX_QUEUE_TASKS}). Refine the existing tasks rather than adding more.`,\n };\n }\n\n if (!ctx.validTypes.includes(args.type)) {\n return {\n ok: false,\n guard: 'unknown-type',\n message: `Unknown task type \"${\n args.type\n }\". Valid types: ${ctx.validTypes.join(', ')}.`,\n };\n }\n\n // Reject an agent pinning a task to a non-allow-listed model.\n if (args.model !== undefined && !isValidModel(args.model)) {\n return {\n ok: false,\n guard: 'invalid-model',\n message: `Model \"${\n args.model\n }\" is not allowed. Omit model to use the task default, or pick one of: ${[\n ...VALID_MODELS,\n ].join(', ')}.`,\n };\n }\n\n for (const dep of args.dependsOn ?? []) {\n if (!ctx.store.get(dep)) {\n return {\n ok: false,\n guard: 'unknown-dep',\n message: `Dependency \"${dep}\" is not a known task id.`,\n };\n }\n }\n\n const seededDeps = seededDepViolations(ctx, args);\n if (seededDeps.length > 0) {\n return {\n ok: false,\n guard: 'seeded-dep',\n message: `A \"${args.type}\" task must not wait for ${seededDeps\n .map((t) => `${t.type} (${t.id})`)\n .join(\n ', ',\n )} — directly or through its dependencies. The wizard placed those, runs them at the end, and they stop to ask the user for input, so anything waiting on one waits on a person. Remove them from dependsOn and enqueue it again; only the final reporting task may depend on them.`,\n };\n }\n\n const uncovered = uncoveredBySink(ctx, args);\n if (uncovered.length > 0) {\n const seededUncovered = uncovered.filter((t) =>\n (ctx.runnerSeededTypes ?? []).includes(t.type),\n );\n // \"Or to a task already in dependsOn\" is the right advice for an ordinary\n // task and exactly wrong for a runner-seeded one — routing it through an\n // intermediate satisfies the closure while breaking the one-way rule above.\n // So a seeded task is named with the only placement that is legal for it.\n const how =\n seededUncovered.length === uncovered.length\n ? \"Add them to this task's own dependsOn — for a task the wizard placed, that is the only legal spot — and enqueue it again.\"\n : 'Add them to dependsOn, or to a task already in dependsOn, and enqueue it again.';\n return {\n ok: false,\n guard: 'sink-closure',\n message: `A \"${\n args.type\n }\" task runs last, so it must depend on every other task — directly or through its dependencies. These are not covered: ${uncovered\n .map((t) => `${t.type} (${t.id})`)\n .join(', ')}. ${how}`,\n };\n }\n\n const key = dedupKey(args.type, args.inputs ?? {});\n if (\n tasks.some(\n (t) =>\n t.status !== TaskStatus.Failed && dedupKey(t.type, t.inputs) === key,\n )\n ) {\n return {\n ok: false,\n guard: 'dedup',\n message: `A \"${args.type}\" task with these inputs already exists.`,\n };\n }\n\n return { ok: true };\n}\n\nexport type EnqueueResult =\n | { ok: true; task: QueuedTask }\n | { ok: false; guard: string; message: string };\n\nexport function applyEnqueue(\n ctx: OrchestratorToolsContext,\n args: EnqueueArgs,\n): EnqueueResult {\n const guard = checkEnqueueGuards(ctx, args);\n if (!guard.ok) return guard;\n\n const task = ctx.store.enqueue({\n type: args.type,\n label: args.label,\n inputs: args.inputs ?? {},\n dependsOn: args.dependsOn ?? [],\n model: args.model,\n enqueuedBy: ctx.currentTaskId ?? 'orchestrator',\n });\n return { ok: true, task };\n}\n\nexport type CompleteResult = { ok: true } | { ok: false; message: string };\n\nexport function applyComplete(\n ctx: OrchestratorToolsContext,\n args: {\n status: 'done' | 'failed' | 'not needed';\n handoff: TaskHandoff;\n remark?: string;\n },\n): CompleteResult {\n const id = ctx.currentTaskId;\n if (!id) {\n return {\n ok: false,\n message: 'complete_task can only be called by a running task agent.',\n };\n }\n if (args.remark) {\n analytics.wizardCapture('orchestrator remark', {\n task_type: ctx.store.get(id)?.type,\n remark: args.remark,\n });\n }\n if (args.status === TaskStatus.Failed) {\n ctx.store.fail(\n id,\n { type: 'self-reported', message: args.handoff.forNextAgent },\n args.handoff,\n );\n } else if (args.status === TaskStatus.Skipped) {\n // The agent's own words stay in the handoff and out of telemetry. This flow\n // reaches live database and API credentials, and the repo has no redaction\n // pass for handoff prose, so the event carries the reason and the task type\n // only — enough to separate an agent no-op from a user decline.\n ctx.store.skip(id, SkipReason.AgentNotNeeded, args.handoff);\n } else {\n ctx.store.complete(id, args.handoff);\n }\n return { ok: true };\n}\n\nexport function applyReadHandoffs(\n ctx: OrchestratorToolsContext,\n args: { type?: string; taskId?: string },\n): TaskHandoff[] {\n if (args.taskId) {\n const h = ctx.store.readHandoff(args.taskId);\n return h ? [h] : [];\n }\n if (args.type) {\n return ctx.store.readHandoffsByType(args.type);\n }\n // No filter: every handoff of a dependency of the current task.\n const currentId = ctx.currentTaskId;\n const current = currentId ? ctx.store.get(currentId) : undefined;\n if (!current) return [];\n return current.dependsOn\n .map((depId) => ctx.store.readHandoff(depId))\n .filter((h): h is TaskHandoff => h !== null);\n}\n\n// Caps each LLM-authored free-text field; queue.json rewrites whole per transition.\nconst HANDOFF_TEXT_MAX = 8_000;\n\n/**\n * The one description of every handoff field, shared by both harnesses'\n * `complete_task` schemas — the zod shape below and the typebox mirror in\n * `harness/pi/orchestrator-tools.ts`. A field an agent cannot see is a field it\n * cannot fill, so the two drifting silently drops whatever the missing field\n * carried; `__tests__/handoff-schema-parity.test.ts` holds them level.\n */\nexport const HANDOFF_FIELDS = {\n goals: 'What this task was asked to achieve.',\n did: 'What you actually did — for each file you edited: the change, the intention behind it, and the analytics it should feed (the insight, funnel, or dashboard tile it becomes part of).',\n forNextAgent: 'What the next agent should know.',\n filesTouched: 'Paths of every file you edited.',\n evidence:\n 'How you know it worked — what you ran or observed, not what you expect.',\n assumptions: 'What you assumed about the app and could not verify.',\n conflict:\n 'A one-line summary of any conflict you could not cleanly resolve (e.g. a dependency or build conflict). Put full detail in your work; this line is surfaced to the user.',\n reportSection:\n 'A finished markdown section about your work for the run report, written only when your task instructions ask for one. The reporting task includes it as its own section instead of rewriting it.',\n} as const satisfies Record<keyof Required<TaskHandoff>, string>;\n\nconst HANDOFF_SHAPE = {\n goals: z.string().max(HANDOFF_TEXT_MAX).describe(HANDOFF_FIELDS.goals),\n did: z.string().max(HANDOFF_TEXT_MAX).describe(HANDOFF_FIELDS.did),\n forNextAgent: z\n .string()\n .max(HANDOFF_TEXT_MAX)\n .describe(HANDOFF_FIELDS.forNextAgent),\n filesTouched: z\n .array(z.string().max(1_000))\n .max(200)\n .optional()\n .describe(HANDOFF_FIELDS.filesTouched),\n evidence: z\n .string()\n .max(HANDOFF_TEXT_MAX)\n .optional()\n .describe(HANDOFF_FIELDS.evidence),\n assumptions: z\n .string()\n .max(HANDOFF_TEXT_MAX)\n .optional()\n .describe(HANDOFF_FIELDS.assumptions),\n conflict: z\n .string()\n .max(HANDOFF_TEXT_MAX)\n .optional()\n .describe(HANDOFF_FIELDS.conflict),\n reportSection: z\n .string()\n .max(HANDOFF_TEXT_MAX)\n .optional()\n .describe(HANDOFF_FIELDS.reportSection),\n};\n\n/** Exported so the parity test can compare both harnesses' field sets. */\nexport const HANDOFF_SHAPE_KEYS: readonly string[] = Object.keys(HANDOFF_SHAPE);\n\ntype SdkTool = (\n name: string,\n description: string,\n // The SDK accepts a plain object of zod fields as the schema.\n schema: Record<string, z.ZodTypeAny>,\n handler: (args: never) => unknown,\n) => unknown;\n\nfunction textResult(text: string, isError = false) {\n return { isError, content: [{ type: 'text' as const, text }] };\n}\n\n/**\n * Build the orchestrator tools in the SDK `tool()` shape. Called from\n * createWizardToolsServer only when a queue context is present.\n */\nexport function buildOrchestratorTools(\n tool: SdkTool,\n ctx: OrchestratorToolsContext,\n): unknown[] {\n const enqueueTask = tool(\n 'enqueue_task',\n 'Add a task to the orchestrator queue. Use it to seed work and to enqueue follow-up work you discover. Keep tasks small and discrete.',\n {\n type: z\n .string()\n .describe(`The task type. One of: ${ctx.validTypes.join(', ')}.`),\n label: z\n .string()\n .optional()\n .describe(\n 'A short label for the UI — the action in a few words (e.g. \"Add the PostHog SDK\", \"Initialize PostHog\"). Leave out file names, class names, and other specifics.',\n ),\n inputs: z.record(z.unknown()).optional(),\n dependsOn: z\n .array(z.string())\n .optional()\n .describe('Task ids that must be done before this task runs.'),\n model: z.string().optional(),\n reason: z.string().describe('One line on why this task is needed.'),\n },\n ((args: EnqueueArgs) => {\n const res = applyEnqueue(ctx, args);\n if (!res.ok) {\n analytics.wizardCapture('orchestrator guard tripped', {\n guard: res.guard,\n type: args.type,\n });\n return textResult(res.message, true);\n }\n return textResult(JSON.stringify({ id: res.task.id }));\n }) as (args: never) => unknown,\n );\n\n const completeTask = tool(\n 'complete_task',\n \"Report the outcome of your task. Always call this exactly once when you finish, with a structured handoff for the next agent. Use status 'not needed' when the task does not apply to this project and you cannot do it (say why in the handoff) — not 'done'.\",\n {\n status: z.enum(['done', 'failed', 'not needed']),\n handoff: z.object(HANDOFF_SHAPE),\n remark: z.string().optional().describe(REMARK_ASK),\n },\n ((args: {\n status: 'done' | 'failed' | 'not needed';\n handoff: TaskHandoff;\n remark?: string;\n }) => {\n const res = applyComplete(ctx, args);\n if (!res.ok) return textResult(res.message, true);\n return textResult('ok');\n }) as (args: never) => unknown,\n );\n\n const readHandoffs = tool(\n 'read_handoffs',\n 'Read structured handoffs from earlier tasks. With no argument, returns the handoffs of your dependencies.',\n {\n type: z.string().optional(),\n taskId: z.string().optional(),\n },\n ((args: { type?: string; taskId?: string }) => {\n const handoffs = applyReadHandoffs(ctx, args);\n return textResult(JSON.stringify(handoffs, null, 2));\n }) as (args: never) => unknown,\n );\n\n return [enqueueTask, completeTask, readHandoffs];\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"setup-utils-Bo3WZt3a.js","names":["fs","path","join","fs"],"sources":["../src/utils/package-manager.ts","../src/lib/oauth/program-scopes.ts","../src/lib/host-resolution.ts","../src/utils/oauth-errors.ts","../src/utils/oauth.ts","../src/utils/project-resolution.ts","../src/utils/semver.ts","../src/utils/setup-utils.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { readFileHead } from './bounded-fs';\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 // Reads only the first 500 bytes of the lockfile.\n return (\n readFileHead(path.join(installDir, file), 500)?.includes(needle) ?? 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 * Current additions: `McpTutorial` layers 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; `AgentSkill` adds feature-flag read/write; the default\n * `PostHogIntegration` run and the standalone `slack` flow add\n * `integration:read` for the Connect-Slack step. Persistence writes (dashboard:write,\n * insight:write, notebook:write, query:read) come for free from the\n * base set, so the tutorial's \"save as insight / pin to dashboard /\n * add to notebook\" 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 {\n WIZARD_OAUTH_SCOPES,\n WIZARD_PROVISIONING_SCOPES,\n} from '@lib/constants';\n\n/**\n * Extra scopes the MCP tutorial needs on top of `WIZARD_OAUTH_SCOPES`.\n *\n * Every scope requested here must stay within the wizard OAuth app's\n * ceiling on the PostHog side (`OAuthApplication.scopes`) — the full\n * list lives in the README under \"OAuth app scope ceiling\". The\n * tutorial's prompts and follow-ups touch most of the read surface,\n * plus annotation write for the \"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 'integration: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 * Extra scopes the self-driving program needs on top of\n * `WIZARD_OAUTH_SCOPES`. All consumed by the PostHog MCP tools the\n * agent drives during the run:\n * • task:read / task:write — the signal source config API\n * (`inbox-source-configs-*`) is permissioned under the generic\n * `task` scope object, NOT a signals-specific one. Unrelated to\n * the Tasks product.\n * • integration:read — `integrations-list`, to check whether the\n * team already has a GitHub integration and to verify the connect\n * flow completed.\n * • signal_scout:read / signal_scout:write — list, sync, and tune\n * the Signals scout troop (`signals-scout-config-*`).\n * • session_recording:read / survey:read / error_tracking:read —\n * server-side product-usage probes (`query-session-recordings-list`,\n * `survey-list`, `error-issue-list`). Product usage is a\n * project-level fact (often instrumented in another repo or via\n * the snippet), so the agent asks the server instead of inferring\n * only from the local setup report. All three are read-only and\n * already in the wizard OAuth app's production scope ceiling (the\n * mcp-tutorial program requests them).\n * • external_data_source:read / external_data_source:write — the\n * connected-tools step creates the GitHub Issues / Linear warehouse\n * sources directly (`external-data-sources-create`) and verifies\n * what's actually connected (`external-data-sources-list`) instead\n * of taking the user's word for it.\n * • llm_skill:read / llm_skill:write — the custom-scouts step\n * (skill step 6b): read the seeded `authoring-signals-scouts`\n * guide and canonical scout bodies (`llma-skill-get` /\n * `llma-skill-file-get`) and author the user-approved custom\n * `signals-scout-*` skills (`llma-skill-create`). Canonical scout\n * bodies are never edited.\n * • product_enablement:write — the \"Enable products\" step turns on\n * Session Replay / Error Tracking / Support so their sources have\n * data to read (`products-enable`). A purpose-built scope: the\n * server owns each enable recipe, so this can flip the product\n * toggles without the far broader `project:write`.\n * • replay_scanner:read / replay_scanner:write — the Replay Vision\n * scanners step (skill step 6c) lists the team's existing scanners\n * and creates the `emits_signals` ones whose findings land in the\n * inbox (`vision-scanners-list` / `-create` / `-update`, and the\n * advisory `vision-scanners-estimate-create` / `vision-quota-retrieve`).\n * The scope OBJECT is `replay_scanner` — the `vision-scanners-*`\n * names are MCP tool names, not scopes. Configuring a scanner also\n * requires `session_recording:read` (the API pairs the two, since a\n * scanner's config indirectly exposes recording contents); that one\n * is already in this list for the step-2 usage probes.\n *\n * No OAuth-ceiling edit is needed for any scope here: they are all normal\n * public (unprivileged, non-internal, non-hidden) scope objects, and the\n * live wizard apps' ceiling is the `@default` sentinel, which resolves to\n * every such scope (`UNPRIVILEGED_SCOPES`) and auto-tracks new ones. Only a\n * privileged/internal/hidden object (e.g. `llm_gateway:*`) would need a\n * manual per-app edit. See README → \"OAuth app scope ceiling\".\n */\nexport const SELF_DRIVING_SCOPE_ADDITIONS = [\n 'task:read',\n 'task:write',\n 'integration:read',\n 'signal_scout:read',\n 'signal_scout:write',\n 'session_recording:read',\n 'survey:read',\n 'error_tracking:read',\n 'external_data_source:read',\n 'external_data_source:write',\n 'llm_skill:read',\n 'llm_skill:write',\n 'product_enablement:write',\n 'replay_scanner:read',\n 'replay_scanner:write',\n] as const;\n\n/**\n * Extra scopes the warehouse-source program needs on top of\n * `WIZARD_OAUTH_SCOPES`. The agent creates data warehouse sources directly\n * (`external-data-sources-create`) and lists what's connected\n * (`external-data-sources-list`) to verify the result. Both are already within\n * the wizard OAuth app's scope ceiling — the self-driving program requests the\n * same pair.\n */\nexport const WAREHOUSE_SOURCE_SCOPE_ADDITIONS = [\n 'external_data_source:read',\n 'external_data_source:write',\n] as const;\n\n/**\n * Extra scope the Connect-Slack step needs on top of `WIZARD_OAUTH_SCOPES`.\n *\n * The step polls `/api/projects/:id/integrations/` (`fetchSlackConnected`)\n * to render the already-connected variant and to flip live once the user\n * completes the Slack OAuth step in the browser. Without `integration:read`\n * the first poll 403s, the screen stops polling, and an already-connected\n * project is nagged with the connect nudge. Used by the default integration\n * run (the step ends the run) and by the standalone `wizard slack` flow\n * (the step is the whole program).\n */\nexport const CONNECT_SLACK_SCOPE_ADDITIONS = ['integration:read'] as const;\n\n/**\n * Extra scopes the replay-vision program needs on top of `WIZARD_OAUTH_SCOPES`.\n * The same set self-driving's step 6c uses, narrowed to just this flow:\n * • replay_scanner:read / replay_scanner:write — the scanner tasks list the\n * team's existing scanners and create the ones scoped to the product's key\n * flows (`vision-scanners-list` / `-create`, plus the advisory\n * `vision-scanners-estimate-create` / `vision-quota-retrieve`). The scope\n * OBJECT is `replay_scanner`; `vision-scanners-*` are MCP tool names.\n * • session_recording:read — the scanner API pairs it with\n * `replay_scanner:*`, since a scanner's config indirectly exposes\n * recording contents. Configuring a scanner fails without it.\n * • product_enablement:write — the enable-replay task's server half turns on\n * Session Replay (`products-enable`) so there are recordings to scan.\n *\n * Without these the PostHog MCP omits the tools from the catalog it serves\n * this token, every scanner task takes its \"tool unknown\" skip path, and the\n * run reports success having created nothing (run 69afc6f8).\n *\n * No OAuth-ceiling edit needed — all are unprivileged public scope objects\n * covered by the apps' `@default` sentinel, and self-driving already requests\n * every one of them.\n */\nexport const REPLAY_VISION_SCOPE_ADDITIONS = [\n 'session_recording:read',\n 'product_enablement:write',\n 'replay_scanner:read',\n 'replay_scanner:write',\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 'self-driving': SELF_DRIVING_SCOPE_ADDITIONS,\n 'warehouse-source': WAREHOUSE_SOURCE_SCOPE_ADDITIONS,\n // The integration run carries the Slack outro step, and — when detection\n // finds data sources — the orchestrator's warehouse task, which creates\n // sources through `external-data-sources-create`. Without the warehouse pair\n // that call 403s on a token the user already granted.\n 'posthog-integration': [\n ...CONNECT_SLACK_SCOPE_ADDITIONS,\n ...WAREHOUSE_SOURCE_SCOPE_ADDITIONS,\n ],\n slack: CONNECT_SLACK_SCOPE_ADDITIONS,\n 'replay-vision': REPLAY_VISION_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\n/**\n * Resolve the scope list for the signup provisioning path. Same\n * base-plus-additions shape as `getOAuthScopesForProgram`, but layered on\n * `WIZARD_PROVISIONING_SCOPES` so a program's extra scopes only reach\n * tokens provisioned for that program.\n */\nexport function getProvisioningScopesForProgram(\n programId: ProgramId | null | undefined,\n): readonly string[] {\n const additions = (programId && PROGRAM_SCOPE_ADDITIONS[programId]) || [];\n if (additions.length === 0) {\n return WIZARD_PROVISIONING_SCOPES;\n }\n const seen = new Set<string>();\n const merged: string[] = [];\n for (const s of [...WIZARD_PROVISIONING_SCOPES, ...additions]) {\n if (seen.has(s)) continue;\n seen.add(s);\n merged.push(s);\n }\n return merged;\n}\n","/**\n * HostResolution — the single, immutable snapshot of where the wizard talks to\n * PostHog for one run.\n *\n * One cloud region (or a pinned `--base-url`) implies a whole family of hosts:\n * the event-ingestion/API host, the user-facing web app host, the CDN asset\n * host, the LLM gateway, and the MCP server. Rather than re-deriving each of\n * these at every call site and threading `region` + `baseUrl` around as loose\n * params, resolve once at auth time and pass this frozen object around — read\n * the property you need.\n *\n * The actual region→URL math (and the `--base-url` override) lives in\n * `@utils/urls`; this class is a thin, immutable façade over those resolvers so\n * the override flows through every field without callers having to know about\n * it. The OAuth-server URL stays in `@utils/urls` (`getOAuthUrl`) because it is\n * needed *before* a region is known, so it can't come from this post-auth object.\n */\n\nimport {\n getHost,\n getCloudUrl,\n getLlmGatewayUrl,\n getUiHostFromHost,\n detectRegion,\n resolveBaseUrl,\n} from '@utils/urls';\nimport { IS_PRODUCTION_BUILD, runtimeEnv } from '@env';\nimport { MCP_LOCAL_URL } from '@lib/local-dev';\nimport type { CloudRegion } from '@utils/types';\n\n// The wizard's client gets CLI mode (a single `exec` tool) by server default,\n// so no mode is pinned; the dev env override (see env.ts) is taken verbatim.\nconst MCP_PROD_URL = 'https://mcp.posthog.com/mcp';\n\n/** Construction-time inputs that aren't implied by the region. */\nexport interface HostResolutionOptions {\n /** `--local-mcp`: the MCP url only; skills follow `--local-context-mill`. */\n localMcp?: boolean;\n /** `--base-url`: pin every PostHog origin to one URL, bypassing region resolution. */\n baseUrl?: string;\n /** `--region`: trust the explicitly-provided region. */\n region?: CloudRegion;\n}\n\nfunction assetHostFor(region: CloudRegion, baseUrl?: string): string {\n const override = resolveBaseUrl(baseUrl);\n if (override) return override;\n return region === 'eu'\n ? 'https://eu-assets.i.posthog.com'\n : 'https://us-assets.i.posthog.com';\n}\n\nfunction assetHostFromApiHost(apiHost: string): string {\n if (apiHost.includes('us.i.posthog.com')) {\n return 'https://us-assets.i.posthog.com';\n }\n if (apiHost.includes('eu.i.posthog.com')) {\n return 'https://eu-assets.i.posthog.com';\n }\n return apiHost;\n}\n\n/**\n * Dev/CI only: the env override (see env.ts) wins even under --local-mcp;\n * published builds strip the read. Don't name that env var in comments here —\n * smoke-test.sh greps dist/*.js for it, and JSDoc survives bundling.\n */\nexport function mcpUrlFor(localMcp: boolean): string {\n const override = IS_PRODUCTION_BUILD ? undefined : runtimeEnv('MCP_URL');\n if (override) return override;\n return localMcp ? MCP_LOCAL_URL : MCP_PROD_URL;\n}\n\nexport class HostResolution {\n /** The resolved cloud region. `'us'` when a base URL is pinned. */\n readonly region: CloudRegion;\n /**\n * Event-ingestion / REST API host (e.g. `https://us.i.posthog.com`, or the\n * pinned `--base-url`). The SDK `host` written into the user's `.env`, the\n * base for the wizard-session REST calls, and the host shown to the agent.\n */\n readonly apiHost: string;\n /**\n * User-facing web app host (e.g. `https://us.posthog.com`). Use for any link\n * we hand to the user or open in their browser (dashboards, settings, inbox,\n * deep-link base).\n */\n readonly appHost: string;\n /** CDN asset host (e.g. `https://us-assets.i.posthog.com`). */\n readonly assetHost: string;\n /** PostHog LLM gateway URL the agent SDK authenticates its model calls against. */\n readonly gatewayUrl: string;\n /**\n * PostHog MCP server URL the agent connects to. Region-independent — the\n * server resolves the user's region from the bearer token — so this is driven\n * only by `--local-mcp` / `--local-dev` and the dev env override, not by\n * region/base-url.\n */\n readonly mcpUrl: string;\n\n private constructor(fields: {\n region: CloudRegion;\n apiHost: string;\n appHost: string;\n assetHost: string;\n gatewayUrl: string;\n mcpUrl: string;\n }) {\n this.region = fields.region;\n this.apiHost = fields.apiHost;\n this.appHost = fields.appHost;\n this.assetHost = fields.assetHost;\n this.gatewayUrl = fields.gatewayUrl;\n this.mcpUrl = fields.mcpUrl;\n Object.freeze(this);\n }\n\n /**\n * Canonical path: build the host family from a resolved region. A pinned\n * `--base-url` (via `opts.baseUrl`) wins for every origin; otherwise the\n * region's standard hosts are used. Honors IS_DEV through `resolveBaseUrl`.\n */\n static fromRegion(\n region: CloudRegion,\n opts: HostResolutionOptions = {},\n ): HostResolution {\n const apiHost = getHost(region, opts.baseUrl);\n return new HostResolution({\n region,\n apiHost,\n appHost: getCloudUrl(region, opts.baseUrl),\n assetHost: assetHostFor(region, opts.baseUrl),\n gatewayUrl: getLlmGatewayUrl(apiHost),\n mcpUrl: mcpUrlFor(opts.localMcp ?? false),\n });\n }\n\n /**\n * Build from an ingestion host string (the provisioning API returns one). The\n * given host is preserved verbatim as `apiHost`; the region and the derived\n * app/asset/gateway hosts are inferred from it.\n */\n static fromApiHost(\n apiHost: string,\n opts: Pick<HostResolutionOptions, 'localMcp'> = {},\n ): HostResolution {\n const region: CloudRegion = apiHost.includes('eu.') ? 'eu' : 'us';\n return new HostResolution({\n region,\n apiHost,\n appHost: getUiHostFromHost(apiHost),\n assetHost: assetHostFromApiHost(apiHost),\n gatewayUrl: getLlmGatewayUrl(apiHost),\n mcpUrl: mcpUrlFor(opts.localMcp ?? false),\n });\n }\n\n /**\n * Resolve the region from an access token (the us/eu probe, skipped when a\n * base URL is pinned), then build the host family. Used after OAuth and after\n * CI-mode API-key auth.\n */\n static async fromAccessToken(\n accessToken: string,\n opts: HostResolutionOptions = {},\n ): Promise<HostResolution> {\n const region =\n opts.region ?? (await detectRegion(accessToken, opts.baseUrl));\n return HostResolution.fromRegion(region, opts);\n }\n}\n","/**\n * OAuth failure presentation — the structured error the flow throws and the\n * user-facing messaging built from it.\n *\n * `oauth.ts` owns the flow machinery (callback server, token exchange, port\n * retries); this module owns what a failure *means*: the RFC 6749 error\n * fields (`error`, `error_description`, `error_uri`), per-code remediation\n * copy, and safe rendering for the browser callback page. Everything here is\n * pure so the copy can be unit-tested without the HTTP machinery.\n */\n\nimport { z } from 'zod';\nimport {\n ISSUES_URL,\n POSTHOG_STATUS_PAGE_URL,\n WIZARD_CONTACT_EMAIL,\n} from '@lib/constants';\n\n/**\n * A structured OAuth failure (RFC 6749 §4.1.2.1 authorize errors, §5.2 token\n * errors). Thrown by both the authorize callback and the token exchange so\n * the flow's error handler keys remediation copy and analytics fingerprints\n * off `code` instead of re-parsing `message`.\n *\n * The message stays exactly `OAuth error: <code>` — the analytics fingerprint\n * scheme (`wizard_oauth_<code>`) and the legacy message-prefix parsing both\n * depend on that shape. The description/uri ride along as properties.\n *\n * NAMING: never bind an instance to a variable or property whose name\n * contains \"oauth\" (use `flowError`, `callbackError`, ...). CodeQL's\n * clear-text-logging heuristic classifies any `*oauth*`-named variable read\n * as password data, and these errors are deliberately shown to the user, so\n * such a binding flags every log statement downstream of it (CWE-312).\n */\nexport class OAuthError extends Error {\n readonly code: string;\n /** The server's human-readable explanation (`error_description`), if sent. */\n readonly description?: string;\n /** The server's \"more info\" link (`error_uri`), if sent. */\n readonly uri?: string;\n\n constructor(code: string, description?: string, uri?: string) {\n super(`OAuth error: ${code}`);\n this.name = 'OAuthError';\n this.code = code;\n this.description = description;\n this.uri = uri;\n }\n}\n\n/**\n * Sanitize an OAuth error param before logging or rendering it: these arrive\n * on a query string (or a token-endpoint body) that anything able to hit the\n * local callback port can populate. Strips non-printable/non-ASCII characters\n * and caps the length. Returns undefined when nothing printable remains.\n */\nexport function sanitizeOAuthParam(\n value: string | null | undefined,\n maxLength = 200,\n): string | undefined {\n if (!value) return undefined;\n const cleaned = value\n .replace(/[^\\x20-\\x7e]/g, '')\n .slice(0, maxLength)\n .trim();\n return cleaned || undefined;\n}\n\n/** Description sentences get more room than bare codes/URIs. */\nconst MAX_DESCRIPTION_LENGTH = 500;\n\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\nconst OAuthErrorBodySchema = z.object({\n error: z.string().min(1),\n error_description: z.string().optional(),\n error_uri: z.string().optional(),\n});\n\n/**\n * Build an `OAuthError` from an authorize-callback's query params.\n * Sanitizes every field — the callback URL is attacker-reachable input.\n */\nexport function oauthErrorFromCallbackParams(\n params: URLSearchParams,\n): OAuthError {\n return new OAuthError(\n sanitizeOAuthParam(params.get('error')) ?? 'unknown',\n sanitizeOAuthParam(params.get('error_description'), MAX_DESCRIPTION_LENGTH),\n sanitizeOAuthParam(params.get('error_uri')),\n );\n}\n\n/**\n * Build an `OAuthError` from a token-endpoint JSON error body (RFC 6749\n * §5.2). Returns null when the body isn't an OAuth error shape (HTML error\n * pages, proxy bodies, network failures) — the caller rethrows the raw\n * error in that case.\n */\nexport function oauthErrorFromTokenBody(data: unknown): OAuthError | null {\n const parsed = OAuthErrorBodySchema.safeParse(data);\n if (!parsed.success) return null;\n return new OAuthError(\n sanitizeOAuthParam(parsed.data.error) ?? 'unknown',\n sanitizeOAuthParam(parsed.data.error_description, MAX_DESCRIPTION_LENGTH),\n sanitizeOAuthParam(parsed.data.error_uri),\n );\n}\n\n/**\n * The error paragraphs for the browser callback page. The constant framing\n * (title, styles, \"return to your terminal\") stays in `oauth.ts`; this owns\n * the part that varies by error, with everything HTML-escaped since the\n * values come straight off the callback query string.\n */\nexport function buildCallbackErrorHtml(error: OAuthError): string {\n if (error.code === 'access_denied') {\n return '<p>Authorization cancelled.</p>';\n }\n const paragraphs = [\n `<p>Authorization failed (${escapeHtml(error.code)}).</p>`,\n ];\n if (error.description) {\n paragraphs.push(`<p>${escapeHtml(error.description)}</p>`);\n }\n if (error.uri) {\n paragraphs.push(`<p>More info: ${escapeHtml(error.uri)}</p>`);\n }\n return paragraphs.join('\\n');\n}\n\ninterface FailureMessageParams {\n error: Error;\n /** Scopes the flow requested (base set + program additions). */\n requestedScopes: readonly string[];\n clientId: string;\n /** OAuth server origin the flow targeted. */\n oauthUrl: string;\n /**\n * True when the flow targets a dev-seeded stack (IS_DEV build or a pinned\n * `--base-url`) — selects the \"fix your local OAuth app\" remediation over\n * the production-runbook one. Same condition that selects the dev client ID.\n */\n isDevStack: boolean;\n}\n\n/**\n * Per-code headline + \"what to do\" remediation for the terminal failure\n * message. Codes not listed fall through to a generic rendering of the\n * error. `access_denied` and the flow timeout never reach this — they keep\n * their dedicated handling in `performOAuthFlow`.\n */\nfunction headlineAndRemediation(params: FailureMessageParams): {\n headline: string;\n whatToDo?: string;\n} {\n const { error, oauthUrl, isDevStack } = params;\n if (!(error instanceof OAuthError)) {\n return { headline: error.message };\n }\n\n switch (error.code) {\n case 'invalid_scope':\n return {\n headline:\n 'PostHog rejected one or more of the requested scopes (invalid_scope).',\n whatToDo: isDevStack\n ? 'The wizard OAuth app on your local stack allows fewer scopes than the wizard requests (its OAuthApplication.scopes ceiling). Re-run your local wizard OAuth app setup so it allows every scope listed below, or add the missing ones in Django admin, then try again.'\n : `The wizard OAuth app's server-side scope ceiling (OAuthApplication.scopes) is missing scopes the wizard requests. This needs a fix on the PostHog side, so email ${WIZARD_CONTACT_EMAIL} with the details below (PostHog staff: the \"scope-ceiling-invalid-scope\" runbook in PostHog/runbooks has the fix). Re-running the wizard will not help until the ceiling is updated.`,\n };\n case 'invalid_client':\n return {\n headline: `PostHog at ${oauthUrl} does not recognize the wizard's OAuth client (invalid_client).`,\n whatToDo:\n 'The client ID below is not registered on the target instance. If you pointed the wizard at a local or self-hosted stack (--base-url), seed its wizard OAuth app first; otherwise re-run without --base-url to use PostHog Cloud.',\n };\n case 'invalid_grant':\n return {\n headline:\n 'The login code was rejected (invalid_grant): it expired or was already used.',\n whatToDo:\n 'Re-run the wizard and complete the browser login promptly. Login codes expire after 5 minutes and can only be used once.',\n };\n case 'server_error':\n return {\n headline:\n \"PostHog's OAuth server hit an internal error (server_error).\",\n whatToDo: `This is usually transient. Wait a moment and re-run the wizard. If it keeps failing, check ${POSTHOG_STATUS_PAGE_URL}.`,\n };\n case 'temporarily_unavailable':\n return {\n headline:\n \"PostHog's OAuth server is temporarily unavailable (temporarily_unavailable).\",\n whatToDo: `This is usually transient. Wait a moment and re-run the wizard. If it keeps failing, check ${POSTHOG_STATUS_PAGE_URL}.`,\n };\n default:\n return {\n headline: `PostHog returned an OAuth error: ${error.code}.`,\n whatToDo:\n 'Re-run the wizard. If the error persists, include the details below when asking for help.',\n };\n }\n}\n\n/**\n * Terminal message for a failed OAuth flow. Always ends with the request\n * context (target host, client ID, requested scopes) so a pasted bug report\n * is actionable without follow-up questions, plus how to get help: the\n * support email (Zendesk-backed) and the issues link.\n */\nexport function buildOAuthFailureMessage(params: FailureMessageParams): string {\n const { error, requestedScopes, clientId, oauthUrl } = params;\n const flowError = error instanceof OAuthError ? error : null;\n const { headline, whatToDo } = headlineAndRemediation(params);\n\n const sections = [`Authorization failed: ${headline}`];\n if (flowError?.description) {\n sections.push(`The server said: ${flowError.description}`);\n }\n if (flowError?.uri) {\n sections.push(`More info: ${flowError.uri}`);\n }\n if (whatToDo) {\n sections.push(`What to do: ${whatToDo}`);\n }\n sections.push(\n [\n 'Details for bug reports:',\n ` target host: ${oauthUrl}`,\n ` client ID: ${clientId}`,\n ` requested scopes: ${requestedScopes.join(' ')}`,\n ].join('\\n'),\n );\n sections.push(\n `Need help? Email ${WIZARD_CONTACT_EMAIL} with the details above, or create an issue:\\n${ISSUES_URL}`,\n );\n return sections.join('\\n\\n');\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 { z } from 'zod';\nimport { getUI } from '@ui';\nimport {\n OAUTH_PORTS,\n OAUTH_TIMEOUT_MS,\n POSTHOG_DEV_CLIENT_ID,\n POSTHOG_PROXY_CLIENT_ID,\n WIZARD_USER_AGENT,\n} from '@lib/constants';\nimport { getOAuthUrl, resolveBaseUrl } from './urls';\nimport { abort } from './setup-utils';\nimport { openTrackedLink, withUtm } from './links';\nimport { analytics } from './analytics';\nimport {\n OAuthError,\n buildCallbackErrorHtml,\n buildOAuthFailureMessage,\n oauthErrorFromCallbackParams,\n oauthErrorFromTokenBody,\n} from './oauth-errors';\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\nexport const 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 // Sent by PostHog Cloud (and passed through the oauth.posthog.com proxy); absent on\n // self-hosted. `.catch(undefined)` so an unrecognized value degrades to the probe\n // fallback instead of failing the whole login.\n posthog_region: z.enum(['us', 'eu']).optional().catch(undefined),\n posthog_base_url: z.string().optional().catch(undefined),\n});\n\nexport type OAuthTokenResponse = z.infer<typeof OAuthTokenResponseSchema>;\n\nexport const WIZARD_COMPLETION_SCOPE = 'event_definition:write';\n\nexport function parseOAuthScopes(scope: string): string[] {\n return scope.split(/\\s+/).filter(Boolean);\n}\n\n/**\n * Requested scopes the grant came back without.\n *\n * A token can legitimately carry fewer scopes than the wizard asked for: the\n * consent screen lets the user deselect any scope the OAuth app doesn't mark\n * required, and anything outside the app's ceiling is clamped server-side.\n * Neither path is an error — `/oauth/token` just returns a narrower `scope`.\n * Diff it at login, where the gap is fixable, rather than letting the run\n * discover it as a permission failure on some API call minutes in.\n */\nexport function missingOAuthScopes(\n requested: readonly string[],\n grantedScope: string,\n): string[] {\n const granted = new Set(parseOAuthScopes(grantedScope));\n return requested.filter((scope) => !granted.has(scope));\n}\n\nexport function assertWizardCompletionScope(scope: string): void {\n if (parseOAuthScopes(scope).includes(WIZARD_COMPLETION_SCOPE)) return;\n\n throw new Error(\n `This run was authorized without the ${WIZARD_COMPLETION_SCOPE} permission, which the wizard needs to finish setup. Please try again, approving all permissions on the PostHog authorization screen. If that screen does not reappear, revoke the existing PostHog Wizard authorization in your PostHog settings first.`,\n );\n}\n\n// Stable marker for the authorization-flow timeout. Detection keys off the exact\n// message rather than a loose substring — `.includes('timeout')` never matched\n// `'timed out'`, which silently routed timeouts to the generic failure message.\nconst AUTHORIZATION_TIMEOUT_MESSAGE = 'Authorization timed out';\n\nexport function isAuthorizationTimeout(error: Error): boolean {\n return error.message === AUTHORIZATION_TIMEOUT_MESSAGE;\n}\n\ninterface OAuthConfig {\n scopes: string[];\n signup?: boolean;\n /** Project to pre-select on the consent screen (the `--project-id` flag). */\n projectId?: number;\n /**\n * Explicit base URL override (`--base-url`, from `session.baseUrl`). Pins the\n * OAuth server and selects the matching client ID.\n */\n baseUrl?: string;\n}\n\n/**\n * OAuth client ID for the current target. A pinned base URL (`--base-url`, or\n * IS_DEV's implicit localhost) means we're talking to a dev-seeded stack, which\n * registers the dev client; prod uses the proxy client.\n *\n * TODO: this assumes any pinned base URL is a dev-seeded instance that\n * registers POSTHOG_DEV_CLIENT_ID. If we ever point `--base-url` at a non-dev\n * instance with its own OAuth app, make the client ID configurable (e.g. a\n * `--oauth-client-id` flag) instead of always falling back to the dev client.\n */\nfunction getOAuthClientId(baseUrl?: string): string {\n return resolveBaseUrl(baseUrl)\n ? POSTHOG_DEV_CLIENT_ID\n : POSTHOG_PROXY_CLIENT_ID;\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 // Carries error_description / error_uri (RFC 6749 §4.1.2.1) along\n // with the code, so the terminal message can show the server's own\n // explanation instead of just the bare code.\n const callbackError = oauthErrorFromCallbackParams(url.searchParams);\n const isAccessDenied = callbackError.code === 'access_denied';\n logToFile(\n `[oauth] callback received with error: ${callbackError.code}` +\n (callbackError.description\n ? ` (${callbackError.description})`\n : ''),\n );\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 ${buildCallbackErrorHtml(callbackError)}\n <p>Return to your terminal. This window will close automatically.</p>\n <script>window.close();</script>\n </body>\n </html>\n `);\n callbackReject(callbackError);\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 baseUrl?: string,\n): Promise<OAuthTokenResponse> {\n const clientId = getOAuthClientId(baseUrl);\n const oauthUrl = getOAuthUrl(baseUrl);\n\n logToFile(`[oauth] exchanging code for token at ${oauthUrl}/oauth/token`);\n let response;\n try {\n response = await axios.post(\n `${oauthUrl}/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 // Surface the OAuth error body (RFC 6749 §5.2) when the token endpoint\n // sent one — otherwise `invalid_grant`, PKCE mismatches, etc. reach the\n // user as a bare axios \"Request failed with status code 400\".\n const exchangeError = axios.isAxiosError(e)\n ? oauthErrorFromTokenBody(e.response?.data)\n : null;\n if (exchangeError) {\n logToFile(\n `[oauth] token endpoint error: ${exchangeError.code}` +\n (exchangeError.description ? ` (${exchangeError.description})` : ''),\n );\n throw exchangeError;\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 `${token.posthog_region ? `, region: ${token.posthog_region}` : ''}` +\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\n/**\n * Warn — at login, while the user is still watching — when the grant came back\n * narrower than the request, and record the gap so narrowed runs are countable.\n * Non-fatal by design: deselecting an optional scope is the user's call, and\n * most flows survive it. The one scope the wizard cannot run without has its\n * own hard check (`assertWizardCompletionScope`).\n */\nfunction reportNarrowedGrant(\n requestedScopes: readonly string[],\n grantedScope: string,\n): void {\n const missing = missingOAuthScopes(requestedScopes, grantedScope);\n if (missing.length === 0) return;\n\n logToFile(\n `[oauth] grant narrower than request, missing: ${missing.join(' ')}`,\n );\n analytics.wizardCapture('oauth grant narrowed', {\n requested_scopes: [...requestedScopes].sort().join(' '),\n granted_scopes: parseOAuthScopes(grantedScope).sort().join(' '),\n missing_scopes: missing.join(' '),\n missing_scope_count: missing.length,\n });\n const plural = missing.length > 1;\n getUI().log.warn(\n `Your PostHog authorization is missing ${\n plural ? `${missing.length} permissions` : 'a permission'\n } the wizard asked for: ${missing.join(', ')}. ` +\n `Setup will continue, but steps that need ${\n plural ? 'them' : 'it'\n } may fail. ` +\n `To grant ${\n plural ? 'them' : 'it'\n }, re-run the wizard and approve all permissions on the ` +\n 'authorization screen. If that screen does not reappear, revoke the ' +\n 'existing PostHog Wizard authorization in your PostHog settings first.',\n );\n}\n\nexport async function performOAuthFlow(\n config: OAuthConfig,\n): Promise<OAuthTokenResponse> {\n const clientId = getOAuthClientId(config.baseUrl);\n const oauthUrl = getOAuthUrl(config.baseUrl);\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n let shouldRetry = false;\n\n logToFile(\n `[oauth] starting flow against ${oauthUrl}, ` +\n `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(`${oauthUrl}/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 if (config.projectId !== undefined) {\n // Pre-select this project on the consent screen so the user just clicks Authorize.\n authUrl.searchParams.set('team_id', String(config.projectId));\n }\n\n // UTM-tag both kickoff URLs so the journey into the app is\n // attributable to the wizard command that started it.\n const taggedAuthUrl = withUtm(authUrl.toString(), 'oauth-authorize');\n const signupUrl = new URL(\n withUtm(\n `${oauthUrl}/signup?next=${encodeURIComponent(taggedAuthUrl)}`,\n 'oauth-signup',\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 taggedAuthUrl,\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() : taggedAuthUrl,\n );\n\n // The localhost proxy URL stays untagged — the PostHog destination\n // it redirects to carries the UTMs.\n openTrackedLink(urlToOpen, 'oauth', { auto: true, skipUtm: true });\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_TIMEOUT_MESSAGE)),\n OAUTH_TIMEOUT_MS,\n ),\n ),\n ]);\n\n const token = await exchangeCodeForToken(\n code,\n codeVerifier,\n callbackUrl,\n config.baseUrl,\n );\n\n server.close();\n getUI().setLoginUrl(null);\n getUI().setAuthorizeUrl(null);\n loginSpinner.stop('Authorization complete!');\n\n reportNarrowedGrant(config.scopes, token.scope);\n\n return token;\n } catch (e) {\n const error = e instanceof Error ? e : new Error('Unknown error');\n const timedOut = isAuthorizationTimeout(error);\n const flowError = error instanceof OAuthError ? error : null;\n\n loginSpinner.stop(\n timedOut ? 'Session timed out.' : 'Authorization failed.',\n );\n server.close();\n\n logToFile('[oauth] flow failed:', error);\n if (flowError?.description) {\n logToFile(\n `[oauth] server error_description: ${flowError.description}`,\n );\n }\n\n const accessDenied = flowError\n ? flowError.code === 'access_denied'\n : error.message.includes('access_denied');\n\n if (timedOut) {\n // Overlay bypasses the auth-step gating (which never completes\n // without credentials), so the user sees the failure instead of a\n // spinner that never stops; any key exits.\n getUI().showSessionTimeout();\n } else if (accessDenied) {\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 buildOAuthFailureMessage({\n error,\n requestedScopes: config.scopes,\n clientId,\n oauthUrl,\n // Same condition that selects the dev client ID: a resolvable\n // base URL means a dev-seeded stack, where \"fix your local\n // OAuth app\" beats pointing at the production runbook.\n isDevStack: resolveBaseUrl(config.baseUrl) !== undefined,\n }),\n );\n }\n\n const oauthErrorCode = flowError\n ? flowError.code\n : error.message.startsWith('OAuth error: ')\n ? error.message.slice('OAuth error: '.length)\n : timedOut\n ? 'timeout'\n : 'unknown';\n\n analytics.captureException(error, {\n step: 'oauth_flow',\n oauth_error_code: oauthErrorCode,\n oauth_error_description: flowError?.description,\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","export type GrantedProjectResolution =\n | { ok: true; projectId: number | undefined }\n | { ok: false; requested: number; granted: number };\n\n/**\n * Decide which project to use after OAuth. `--project-id` (when passed) is authoritative,\n * but only when the user granted access to it on the consent screen; a different grant is a\n * mismatch the caller must surface. With no `--project-id` this returns the granted project\n * (`scopedTeams[0]`), preserving the pre-flag behavior for every wizard program (the main\n * integration flow included).\n */\nexport function resolveGrantedProject(\n requestedProjectId: number | undefined,\n scopedTeams: number[] | undefined,\n): GrantedProjectResolution {\n // No --project-id: use whatever the user granted, exactly as before the flag existed.\n if (requestedProjectId === undefined) {\n return { ok: true, projectId: scopedTeams?.[0] };\n }\n // --project-id was among the granted teams: honor it.\n if (scopedTeams?.includes(requestedProjectId)) {\n return { ok: true, projectId: requestedProjectId };\n }\n // --project-id requested but a different project was authorized: mismatch.\n if (scopedTeams && scopedTeams.length > 0) {\n return {\n ok: false,\n requested: requestedProjectId,\n granted: scopedTeams[0],\n };\n }\n // Nothing granted at all — let the caller's \"no project access\" guard handle it.\n return { ok: true, projectId: undefined };\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, logToFile } 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 { DUMMY_PROJECT_API_KEY, ISSUES_URL } from '@lib/constants';\nimport {\n getOAuthScopesForProgram,\n getProvisioningScopesForProgram,\n} from '@lib/oauth/program-scopes';\nimport type { ProgramId } from '@lib/programs/program-registry';\nimport { analytics } from './analytics';\nimport { getUI } from '@ui';\nimport { HostResolution } from '@lib/host-resolution';\nimport {\n assertWizardCompletionScope,\n missingOAuthScopes,\n performOAuthFlow,\n} from './oauth';\nimport { resolveGrantedProject } from './project-resolution';\nimport {\n ProvisionedAccountUnreadableError,\n provisionNewAccount,\n} from './provisioning';\nimport {\n fetchUserData,\n fetchProjectData,\n type ApiUser,\n type ApiProject,\n} from '@lib/api';\nimport { versionSatisfiesRange } from './semver';\nimport { wizardAbort } from './wizard-abort';\nimport { OutroKind } from '@lib/wizard-session';\n\ninterface ProjectData {\n projectApiKey: string;\n accessToken: string;\n host: HostResolution;\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 * Full project payload from `/api/projects/:id/`. Carries the team's\n * product opt-ins (replay, exception autocapture, surveys) so prompts\n * can state project-level product enablement instead of agents\n * inferring it from repo evidence. Null on signup flows.\n */\n project?: ApiProject | null;\n /**\n * Requested OAuth scopes the grant came back without (consent deselection\n * or ceiling clamp). Forwarded to `session.credentials.missingScopes` so\n * runs can degrade scope-gated steps instead of failing on a 403. Empty on\n * CI api-key and signup-provisioning paths.\n */\n missingScopes?: readonly string[];\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 /** Explicit base URL override (`--base-url`, from `session.baseUrl`). When\n * set, pins every PostHog origin and bypasses region resolution. */\n baseUrl?: string;\n /** `--local-mcp`: forwarded into the resolved host so `host.mcpUrl` is local. */\n localMcp?: boolean;\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: HostResolution;\n projectApiKey: string;\n accessToken: string;\n projectId: number;\n roleAtOrganization: string | null;\n user: ApiUser | null;\n project: ApiProject | null;\n /** Requested OAuth scopes the grant came back without. Empty on CI/signup paths. */\n missingScopes: readonly string[];\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 host = await HostResolution.fromAccessToken(_options.apiKey, {\n region: _options.region,\n localMcp: _options.localMcp,\n baseUrl: _options.baseUrl,\n });\n const cloudUrl = host.appHost;\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 (err) {\n logToFile(\n '[ci-auth] user lookup failed:',\n err instanceof Error ? err.message : String(err),\n );\n }\n if (user) {\n analytics.identifyUser(user);\n logToFile(\n '[ci-auth] identified via API key; flags evaluate as the key owner',\n );\n } else {\n getUI().log.warn(\n 'Could not resolve the API key user (key needs user:read scope) — feature flags evaluate anonymously; user-targeted flags will not match.',\n );\n }\n\n return {\n host,\n projectApiKey: projectData.api_token,\n accessToken: _options.apiKey,\n projectId: projectData.id,\n roleAtOrganization,\n user,\n project: projectData.project,\n // A personal API key carries whatever scopes it carries — there is no\n // per-run scope request to diff against.\n missingScopes: [],\n };\n }\n\n const {\n host,\n projectApiKey,\n accessToken,\n projectId,\n roleAtOrganization,\n user,\n project,\n missingScopes,\n } = await withProgress('login', () =>\n askForWizardLogin({\n signup: _options.signup,\n email: _options.email,\n region: _options.region,\n baseUrl: _options.baseUrl,\n programId: _options.programId,\n projectId: _options.projectId,\n localMcp: _options.localMcp,\n }),\n );\n\n if (!projectApiKey) {\n const cloudUrl = host.appHost;\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,\n projectApiKey: projectApiKey || DUMMY_PROJECT_API_KEY,\n projectId,\n roleAtOrganization: roleAtOrganization ?? null,\n user: user ?? null,\n project: project ?? null,\n missingScopes: missingScopes ?? [],\n };\n}\n\nasync function fetchProjectDataWithApiKey(\n apiKey: string,\n cloudUrl: string,\n): Promise<{ api_token: string; id: number; project: ApiProject }> {\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 project: projectData,\n };\n}\n\nasync function fetchProjectDataById(\n apiKey: string,\n projectId: number,\n cloudUrl: string,\n): Promise<{ api_token: string; id: number; project: ApiProject }> {\n const projectData = await fetchProjectData(apiKey, projectId, cloudUrl);\n return {\n api_token: projectData.api_token,\n id: projectId,\n project: projectData,\n };\n}\n\nasync function askForWizardLogin(options: {\n signup: boolean;\n email?: string;\n region?: CloudRegion;\n /** Explicit base URL override (`--base-url`); pins every PostHog origin. */\n baseUrl?: string;\n /** Used to pick the right scope set via `getOAuthScopesForProgram`.\n * Omitted → default `WIZARD_OAUTH_SCOPES`. */\n programId?: ProgramId | null;\n /** `--project-id`, if passed. When the user granted access to it on the consent\n * screen we use it directly; otherwise we fall back to the first granted team. */\n projectId?: number;\n /** `--local-mcp`: forwarded into the resolved host so `host.mcpUrl` is local. */\n localMcp?: boolean;\n}): Promise<ProjectData> {\n if (options.signup) {\n return askForProvisioningSignup(\n options.email,\n options.region,\n options.baseUrl,\n options.localMcp,\n options.programId,\n );\n }\n\n const requestedScopes = [...getOAuthScopesForProgram(options.programId)];\n const tokenResponse = await performOAuthFlow({\n scopes: requestedScopes,\n signup: false,\n projectId: options.projectId,\n baseUrl: options.baseUrl,\n });\n\n try {\n assertWizardCompletionScope(tokenResponse.scope);\n } catch (error) {\n const scopeError =\n error instanceof Error ? error : new Error('OAuth scope check failed');\n const missing = missingOAuthScopes(requestedScopes, tokenResponse.scope);\n analytics.captureException(scopeError, {\n step: 'wizard_login',\n missing_scope: 'event_definition:write',\n });\n await wizardAbort({\n message: scopeError.message,\n outroData: {\n kind: OutroKind.Error,\n message: 'Setup needs permissions that were not granted',\n body: [\n 'Missing permissions:',\n ...missing.map((scope) => ` • ${scope}`),\n '',\n 'Re-run the wizard and approve all permissions on the PostHog authorization screen.',\n 'If that screen does not reappear, revoke the existing PostHog Wizard authorization in your PostHog settings first.',\n ].join('\\n'),\n },\n });\n }\n\n // `--project-id`, when provided, is authoritative — but only if the user actually\n // granted access to it on the consent screen. If they authorized a different\n // project, fail loudly instead of silently capturing into the wrong one. With no\n // `--project-id` this falls back to the granted project, unchanged for every program.\n const resolution = resolveGrantedProject(\n options.projectId,\n tokenResponse.scoped_teams,\n );\n if (!resolution.ok) {\n const error = new Error(\n `You authorized project ${resolution.granted}, but setup is targeting project ${resolution.requested} (from --project-id). ` +\n `If ${resolution.requested} is not a project you own — a copy-pasted example value, say — re-run without --project-id, or with the id shown in your PostHog project settings. ` +\n `If it is yours, re-run and grant access to project ${resolution.requested} on the authorization screen.`,\n );\n analytics.captureException(error, {\n step: 'wizard_login',\n requested_project_id: resolution.requested,\n granted_project_id: resolution.granted,\n });\n getUI().log.error(error.message);\n await abort(error.message);\n }\n\n const projectId = resolution.ok ? resolution.projectId : undefined;\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(error.message);\n }\n\n // The issuing region comes with the token; the us/eu @me probe only runs when omitted.\n const host = await HostResolution.fromAccessToken(\n tokenResponse.access_token,\n {\n region: tokenResponse.posthog_region,\n localMcp: options.localMcp,\n baseUrl: options.baseUrl,\n },\n );\n const cloudUrl = host.appHost;\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 roleAtOrganization: userData.role_at_organization ?? null,\n user: userData,\n project: projectData,\n // What the user declined at consent (or the ceiling clamped) — carried to\n // the session so runs degrade scope-gated steps instead of 403ing blind.\n missingScopes: missingOAuthScopes(requestedScopes, tokenResponse.scope),\n };\n\n getUI().log.success('Login complete.');\n analytics.setTag('opened-wizard-link', true);\n analytics.identifyUser(userData);\n\n return data;\n}\n\nasync function askForProvisioningSignup(\n email?: string,\n region?: CloudRegion,\n baseUrl?: string,\n localMcp?: boolean,\n programId?: ProgramId | null,\n): Promise<ProjectData> {\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 baseUrl,\n scopes: getProvisioningScopesForProgram(programId),\n });\n\n spinner.stop('Account created!');\n getUI().log.success('Welcome to PostHog!');\n\n const host = HostResolution.fromApiHost(result.host, { localMcp });\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 };\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n\n // The account exists — reporting a failed signup would send the user off to create a\n // second one on top of the org they already own.\n if (error instanceof ProvisionedAccountUnreadableError) {\n spinner.stop('Account created, but the project could not be read back.');\n getUI().log.warn(message);\n getUI().log.info('Signing you in to your new account instead...');\n\n return askForWizardLogin({ signup: false, baseUrl, localMcp });\n }\n\n spinner.stop('Account creation failed.');\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\n return askForWizardLogin({ signup: false, baseUrl, localMcp });\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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"}