@posthog/wizard 2.80.0 → 2.80.1

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 (227) hide show
  1. package/README.md +187 -57
  2. package/dist/{PickerMenu-m4CCPX4f.js → PickerMenu-BGGeYqd-.js} +104 -4
  3. package/dist/PickerMenu-BGGeYqd-.js.map +1 -0
  4. package/dist/README.md +9 -6
  5. package/dist/{agent-interface-CzyCmp7q.js → agent-interface-CemBvTAA.js} +115 -346
  6. package/dist/agent-interface-CemBvTAA.js.map +1 -0
  7. package/dist/{agent-prompt-loader-4wMxlYSX.js → agent-prompt-loader-DPnj6SwU.js} +6 -5
  8. package/dist/agent-prompt-loader-DPnj6SwU.js.map +1 -0
  9. package/dist/analytics-B3ROGuIr.js +2 -0
  10. package/dist/analytics-D21z3E5l.js +732 -0
  11. package/dist/analytics-D21z3E5l.js.map +1 -0
  12. package/dist/{api-C3Dc4LFl.js → api-DtGIqQ2U.js} +2 -3
  13. package/dist/api-DtGIqQ2U.js.map +1 -0
  14. package/dist/api-IhROOQNv.js +2 -0
  15. package/dist/api-key-login-OEntFvu5.js +2 -0
  16. package/dist/api-key-login-h3E2G3zu.js +55 -0
  17. package/dist/api-key-login-h3E2G3zu.js.map +1 -0
  18. package/dist/bin.js +2 -2
  19. package/dist/bin.js.map +1 -1
  20. package/dist/{bounded-fs-CC_NDtoP.js → bounded-fs-CAsuF6al.js} +3 -3
  21. package/dist/{bounded-fs-CC_NDtoP.js.map → bounded-fs-CAsuF6al.js.map} +1 -1
  22. package/dist/{ci-install-DfcAMhlu.js → ci-install-CEYE02t-.js} +21 -25
  23. package/dist/ci-install-CEYE02t-.js.map +1 -0
  24. package/dist/console-log-O828zyiq.js +32 -0
  25. package/dist/console-log-O828zyiq.js.map +1 -0
  26. package/dist/{debug-DyARf5TF.js → debug-BfQPD2Hg.js} +18 -52
  27. package/dist/debug-BfQPD2Hg.js.map +1 -0
  28. package/dist/{defaults-DpfMnH2q.js → defaults-DDVl3tKN.js} +2 -51
  29. package/dist/defaults-DDVl3tKN.js.map +1 -0
  30. package/dist/{emit-BSetXzDV.js → emit-RfAewcWv.js} +1 -1
  31. package/dist/{emit-BSetXzDV.js.map → emit-RfAewcWv.js.map} +1 -1
  32. package/dist/{errors-C2domSkK.js → errors-CHrJzAZb.js} +4 -4
  33. package/dist/{errors-C2domSkK.js.map → errors-CHrJzAZb.js.map} +1 -1
  34. package/dist/family-picker-BKs-tTyu.js +46 -0
  35. package/dist/family-picker-BKs-tTyu.js.map +1 -0
  36. package/dist/{gateway-BmjYUSua.js → gateway-D3gIlR6b.js} +122 -11
  37. package/dist/gateway-D3gIlR6b.js.map +1 -0
  38. package/dist/headless-BJjhP0SI.js +10 -0
  39. package/dist/headless-BJjhP0SI.js.map +1 -0
  40. package/dist/headless-DIX68UUA.js +3 -0
  41. package/dist/{host-resolution-DVSb7kXb.js → host-resolution-D8GhEVoX.js} +4 -4
  42. package/dist/{host-resolution-DVSb7kXb.js.map → host-resolution-D8GhEVoX.js.map} +1 -1
  43. package/dist/install-Bw8tvDvS.js +2 -0
  44. package/dist/{install-Bk5TNk27.js → install-paPCzPHI.js} +26 -93
  45. package/dist/install-paPCzPHI.js.map +1 -0
  46. package/dist/interactive-tdxZKWax.js +11 -0
  47. package/dist/interactive-tdxZKWax.js.map +1 -0
  48. package/dist/{links-9LWZPSCq.js → links-Cyj6v8kg.js} +3 -3
  49. package/dist/{links-9LWZPSCq.js.map → links-Cyj6v8kg.js.map} +1 -1
  50. package/dist/{main-DKPjFHEV.js → main-0p-YxUNa.js} +204 -469
  51. package/dist/main-0p-YxUNa.js.map +1 -0
  52. package/dist/{mcp-Co8VRF62.js → mcp-D3Z5MXhA.js} +3 -4
  53. package/dist/{mcp-Co8VRF62.js.map → mcp-D3Z5MXhA.js.map} +1 -1
  54. package/dist/mcp-install-smoke-test.ts +2 -2
  55. package/dist/{mcp-prompt-streaming-ljCVoQ9I.js → mcp-prompt-streaming-COVbWnHt.js} +12 -12
  56. package/dist/mcp-prompt-streaming-COVbWnHt.js.map +1 -0
  57. package/dist/mode-label-DB3ol_sj.js +9 -0
  58. package/dist/mode-label-DB3ol_sj.js.map +1 -0
  59. package/dist/{models-Dba8ITeo.js → models-D1SnCzvl.js} +2 -2
  60. package/dist/{models-Dba8ITeo.js.map → models-D1SnCzvl.js.map} +1 -1
  61. package/dist/non-interactive-DxRBc73O.js +18 -0
  62. package/dist/non-interactive-DxRBc73O.js.map +1 -0
  63. package/dist/{oauth-session-D-RfcVmr.js → oauth-session-Dde79Ycm.js} +3 -3
  64. package/dist/{oauth-session-D-RfcVmr.js.map → oauth-session-Dde79Ycm.js.map} +1 -1
  65. package/dist/{orchestrator-tools-CVJ1F9Ub.js → orchestrator-tools-BSNhzzkT.js} +3 -3
  66. package/dist/{orchestrator-tools-CVJ1F9Ub.js.map → orchestrator-tools-BSNhzzkT.js.map} +1 -1
  67. package/dist/{package-json-Cowya9-3.js → package-json-2UrhE6kP.js} +1 -1
  68. package/dist/{package-json-Cowya9-3.js.map → package-json-2UrhE6kP.js.map} +1 -1
  69. package/dist/package-json-Dblip2_X.js +2 -0
  70. package/dist/package-manager-BRXlXo0p.js +105 -0
  71. package/dist/package-manager-BRXlXo0p.js.map +1 -0
  72. package/dist/{pi-B5YTBRDv.js → pi-cM_4Gd_9.js} +16 -18
  73. package/dist/pi-cM_4Gd_9.js.map +1 -0
  74. package/dist/playground-DTKy0XHT.js +12 -0
  75. package/dist/playground-DTKy0XHT.js.map +1 -0
  76. package/dist/posthog-integration-bwHwE87a.js +10291 -0
  77. package/dist/posthog-integration-bwHwE87a.js.map +1 -0
  78. package/dist/programs-BhtyA3lE.js +3 -0
  79. package/dist/programs-SeT1qNik.js +5348 -0
  80. package/dist/programs-SeT1qNik.js.map +1 -0
  81. package/dist/provisioning-BcXfeCMc.js +2 -0
  82. package/dist/{provisioning-CLhuKqXH.js → provisioning-JbJtq7hq.js} +4 -5
  83. package/dist/{provisioning-CLhuKqXH.js.map → provisioning-JbJtq7hq.js.map} +1 -1
  84. package/dist/{queue-tools-knnPQP0e.js → queue-tools-BIZSJyt6.js} +6 -8
  85. package/dist/queue-tools-BIZSJyt6.js.map +1 -0
  86. package/dist/results-DtKICAdz.js +55 -0
  87. package/dist/results-DtKICAdz.js.map +1 -0
  88. package/dist/run-CDurA5tO.js +248 -0
  89. package/dist/run-CDurA5tO.js.map +1 -0
  90. package/dist/run-DDCvLILJ.js +246 -0
  91. package/dist/run-DDCvLILJ.js.map +1 -0
  92. package/dist/run-tool-CTq9OOdT.js +2 -0
  93. package/dist/run-tool-DML8JJvt.js +2420 -0
  94. package/dist/run-tool-DML8JJvt.js.map +1 -0
  95. package/dist/runners-Dhu4pZTN.js +253 -0
  96. package/dist/runners-Dhu4pZTN.js.map +1 -0
  97. package/dist/{security-BvdSFDuM.js → security-DEH8Rn0_.js} +5 -5
  98. package/dist/security-DEH8Rn0_.js.map +1 -0
  99. package/dist/setup-utils-C5-WM-hS.js +118 -0
  100. package/dist/setup-utils-C5-WM-hS.js.map +1 -0
  101. package/dist/smoke-test.sh +1 -1
  102. package/dist/{playground-BLsCfHmQ.js → start-playground-DL4mVEUx.js} +43 -34
  103. package/dist/start-playground-DL4mVEUx.js.map +1 -0
  104. package/dist/{subagent-D_Sqak94.js → subagent-CnFvAq6E.js} +3 -3
  105. package/dist/{subagent-D_Sqak94.js.map → subagent-CnFvAq6E.js.map} +1 -1
  106. package/dist/{task-DiTI6Sak.js → task-BOScSm4W.js} +16 -17
  107. package/dist/task-BOScSm4W.js.map +1 -0
  108. package/dist/{tasks-ChZ_F1YP.js → tasks-BfJhVky2.js} +1 -1
  109. package/dist/tasks-BfJhVky2.js.map +1 -0
  110. package/dist/{telemetry-BNKsDxNS.js → telemetry-CP4rGicd.js} +2 -2
  111. package/dist/{telemetry-BNKsDxNS.js.map → telemetry-CP4rGicd.js.map} +1 -1
  112. package/dist/terminal-J3OD3dN0.js +16162 -0
  113. package/dist/terminal-J3OD3dN0.js.map +1 -0
  114. package/dist/tools-CCMV3DmB.js +544 -0
  115. package/dist/tools-CCMV3DmB.js.map +1 -0
  116. package/dist/{tools-C8LQ8QFz.js → tools-Cz8tDptL.js} +9 -9
  117. package/dist/tools-Cz8tDptL.js.map +1 -0
  118. package/dist/tui-BUZ2PWqN.js +2 -0
  119. package/dist/tui-Csf7VJxt.js +25 -0
  120. package/dist/tui-Csf7VJxt.js.map +1 -0
  121. package/dist/{steps-Hc28zVc_.js → upload-step-CEacB2RV.js} +14 -16
  122. package/dist/upload-step-CEacB2RV.js.map +1 -0
  123. package/dist/{urls-BI4kJ4qQ.js → urls-BDELGZHq.js} +2 -2
  124. package/dist/{urls-BI4kJ4qQ.js.map → urls-BDELGZHq.js.map} +1 -1
  125. package/dist/warlock-smoke-test.ts +1 -2
  126. package/dist/{wizard-abort-DBKtfnBR.js → wizard-abort-D3-75KyV.js} +40 -33
  127. package/dist/wizard-abort-D3-75KyV.js.map +1 -0
  128. package/dist/yara-hooks-CZ1tKxy_.js +2 -0
  129. package/dist/{yara-hooks-C3L6lBij.js → yara-hooks-D8YDyX7o.js} +283 -21
  130. package/dist/yara-hooks-D8YDyX7o.js.map +1 -0
  131. package/package.json +6 -6
  132. package/dist/PickerMenu-m4CCPX4f.js.map +0 -1
  133. package/dist/agent-BkBbVuNq.js +0 -3189
  134. package/dist/agent-BkBbVuNq.js.map +0 -1
  135. package/dist/agent-QGK2YzlX.js +0 -6
  136. package/dist/agent-interface-CzyCmp7q.js.map +0 -1
  137. package/dist/agent-prompt-loader-4wMxlYSX.js.map +0 -1
  138. package/dist/analytics-B6q5PN7a.js +0 -301
  139. package/dist/analytics-B6q5PN7a.js.map +0 -1
  140. package/dist/analytics-BXtfK9o7.js +0 -2
  141. package/dist/api-C3Dc4LFl.js.map +0 -1
  142. package/dist/api-yNjrfZs2.js +0 -2
  143. package/dist/check-screens.tsx +0 -190
  144. package/dist/ci-install-DfcAMhlu.js.map +0 -1
  145. package/dist/constants-DO4X70po.js +0 -260
  146. package/dist/constants-DO4X70po.js.map +0 -1
  147. package/dist/debug-BueK5mRR.js +0 -2
  148. package/dist/debug-DyARf5TF.js.map +0 -1
  149. package/dist/defaults-DpfMnH2q.js.map +0 -1
  150. package/dist/endpoints-D-4KxZiB.js +0 -108
  151. package/dist/endpoints-D-4KxZiB.js.map +0 -1
  152. package/dist/env-api-key-CHGLe0iz.js +0 -2
  153. package/dist/env-api-key-COf0U-Au.js +0 -17
  154. package/dist/env-api-key-COf0U-Au.js.map +0 -1
  155. package/dist/environment-DrLaSsA8.js +0 -39
  156. package/dist/environment-DrLaSsA8.js.map +0 -1
  157. package/dist/environment-hBj7ngmO.js +0 -2
  158. package/dist/file-CVA9ocIC.js +0 -60
  159. package/dist/file-CVA9ocIC.js.map +0 -1
  160. package/dist/file-CmoKK_Pp.js +0 -2
  161. package/dist/file-watcher-C3Su8vXm.js +0 -123
  162. package/dist/file-watcher-C3Su8vXm.js.map +0 -1
  163. package/dist/gateway-BmjYUSua.js.map +0 -1
  164. package/dist/headless-ui-CUbv12Yx.js +0 -44
  165. package/dist/headless-ui-CUbv12Yx.js.map +0 -1
  166. package/dist/install-Bk5TNk27.js.map +0 -1
  167. package/dist/install-F4wm3RSl.js +0 -2
  168. package/dist/interactive-C5LNvDj6.js +0 -11
  169. package/dist/interactive-C5LNvDj6.js.map +0 -1
  170. package/dist/local-dev-McsurOC-.js +0 -135
  171. package/dist/local-dev-McsurOC-.js.map +0 -1
  172. package/dist/logging-ui-p6XKVmTx.js +0 -226
  173. package/dist/logging-ui-p6XKVmTx.js.map +0 -1
  174. package/dist/main-DKPjFHEV.js.map +0 -1
  175. package/dist/mcp-prompt-streaming-ljCVoQ9I.js.map +0 -1
  176. package/dist/mint-failure-C0HyvEwO.js +0 -6403
  177. package/dist/mint-failure-C0HyvEwO.js.map +0 -1
  178. package/dist/non-interactive-CLMJrj_7.js +0 -18
  179. package/dist/non-interactive-CLMJrj_7.js.map +0 -1
  180. package/dist/package-json--ppioh2F.js +0 -2
  181. package/dist/pi-B5YTBRDv.js.map +0 -1
  182. package/dist/playground-BLsCfHmQ.js.map +0 -1
  183. package/dist/posthog-B2E0hpZt.js +0 -2
  184. package/dist/posthog-B42rt_1S.js +0 -124
  185. package/dist/posthog-B42rt_1S.js.map +0 -1
  186. package/dist/project-data--L-WZyvj.js +0 -2
  187. package/dist/project-data-BO9IXn9y.js +0 -1131
  188. package/dist/project-data-BO9IXn9y.js.map +0 -1
  189. package/dist/provisioning-C01REIU2.js +0 -2
  190. package/dist/queue-tools-knnPQP0e.js.map +0 -1
  191. package/dist/registry-B3rC5U4b.js +0 -3464
  192. package/dist/registry-B3rC5U4b.js.map +0 -1
  193. package/dist/registry-BamsM7J9.js +0 -2
  194. package/dist/run-agent-legacy-CwUmPfQF.js +0 -2
  195. package/dist/run-agent-legacy-DWBajsMh.js +0 -3467
  196. package/dist/run-agent-legacy-DWBajsMh.js.map +0 -1
  197. package/dist/runners-D1PqqpjA.js +0 -834
  198. package/dist/runners-D1PqqpjA.js.map +0 -1
  199. package/dist/security-BvdSFDuM.js.map +0 -1
  200. package/dist/setup-utils-ChXXTi1m.js +0 -301
  201. package/dist/setup-utils-ChXXTi1m.js.map +0 -1
  202. package/dist/start-tui-DicLfgsj.js +0 -6964
  203. package/dist/start-tui-DicLfgsj.js.map +0 -1
  204. package/dist/steps-Hc28zVc_.js.map +0 -1
  205. package/dist/store-COCO6U0P.js +0 -5
  206. package/dist/store-D3509G87.js +0 -1030
  207. package/dist/store-D3509G87.js.map +0 -1
  208. package/dist/task-DiTI6Sak.js.map +0 -1
  209. package/dist/task-status-CmrrR7zo.js +0 -16
  210. package/dist/task-status-CmrrR7zo.js.map +0 -1
  211. package/dist/task-stream-BckhG_cB.js +0 -363
  212. package/dist/task-stream-BckhG_cB.js.map +0 -1
  213. package/dist/tasks-ChZ_F1YP.js.map +0 -1
  214. package/dist/terminal-PK9bCLRi.js +0 -7222
  215. package/dist/terminal-PK9bCLRi.js.map +0 -1
  216. package/dist/tools-C8LQ8QFz.js.map +0 -1
  217. package/dist/ui-tPNpM6vj.js +0 -15
  218. package/dist/ui-tPNpM6vj.js.map +0 -1
  219. package/dist/version-5_ydHkM6.js +0 -6
  220. package/dist/version-5_ydHkM6.js.map +0 -1
  221. package/dist/wizard-abort-BVjqLyeA.js +0 -3
  222. package/dist/wizard-abort-DBKtfnBR.js.map +0 -1
  223. package/dist/wizard-session-B7BHnay0.js +0 -2
  224. package/dist/wizard-session-BYw6LzpS.js +0 -133
  225. package/dist/wizard-session-BYw6LzpS.js.map +0 -1
  226. package/dist/yara-hooks-C3L6lBij.js.map +0 -1
  227. package/dist/yara-hooks-CF_IsoaA.js +0 -2
@@ -1,1131 +0,0 @@
1
- import { s as logToFile } from "./debug-DyARf5TF.js";
2
- import { B as WIZARD_CONTACT_EMAIL, C as ISSUES_URL, E as OAUTH_TIMEOUT_MS, J as WIZARD_PROVISIONING_SCOPES, N as POSTHOG_PROXY_CLIENT_ID, O as POSTHOG_DEV_CLIENT_ID, P as POSTHOG_STATUS_PAGE_URL, Q as WIZARD_USER_AGENT, T as OAUTH_PORTS, W as WIZARD_OAUTH_SCOPES, d as DUMMY_PROJECT_API_KEY } from "./constants-DO4X70po.js";
3
- import "./wizard-session-BYw6LzpS.js";
4
- import { n as analytics } from "./analytics-B6q5PN7a.js";
5
- import { r as withUtm, t as openTrackedLink } from "./links-9LWZPSCq.js";
6
- import { t as withProgress } from "./telemetry-BNKsDxNS.js";
7
- import { n as detectOrgAndProject, t as abort } from "./setup-utils-ChXXTi1m.js";
8
- import { t as getUI } from "./ui-tPNpM6vj.js";
9
- import { r as wizardAbort } from "./wizard-abort-DBKtfnBR.js";
10
- import { a as getOAuthUrl, s as resolveBaseUrl } from "./urls-BI4kJ4qQ.js";
11
- import { t as HostResolution } from "./host-resolution-DVSb7kXb.js";
12
- import { n as provisionNewAccount, t as ProvisionedAccountUnreadableError } from "./provisioning-CLhuKqXH.js";
13
- import { c as fetchUserData, o as fetchProjectData } from "./api-C3Dc4LFl.js";
14
- import { execSync } from "node:child_process";
15
- import axios from "axios";
16
- import * as crypto from "node:crypto";
17
- import { z } from "zod";
18
- import * as http from "node:http";
19
- //#region src/shared/utils/oauth-errors.ts
20
- /**
21
- * OAuth failure presentation — the structured error the flow throws and the
22
- * user-facing messaging built from it.
23
- *
24
- * `oauth.ts` owns the flow machinery (callback server, token exchange, port
25
- * retries); this module owns what a failure *means*: the RFC 6749 error
26
- * fields (`error`, `error_description`, `error_uri`), per-code remediation
27
- * copy, and safe rendering for the browser callback page. Everything here is
28
- * pure so the copy can be unit-tested without the HTTP machinery.
29
- */
30
- /**
31
- * A structured OAuth failure (RFC 6749 §4.1.2.1 authorize errors, §5.2 token
32
- * errors). Thrown by both the authorize callback and the token exchange so
33
- * the flow's error handler keys remediation copy and analytics fingerprints
34
- * off `code` instead of re-parsing `message`.
35
- *
36
- * The message stays exactly `OAuth error: <code>` — the analytics fingerprint
37
- * scheme (`wizard_oauth_<code>`) and the legacy message-prefix parsing both
38
- * depend on that shape. The description/uri ride along as properties.
39
- *
40
- * NAMING: never bind an instance to a variable or property whose name
41
- * contains "oauth" (use `flowError`, `callbackError`, ...). CodeQL's
42
- * clear-text-logging heuristic classifies any `*oauth*`-named variable read
43
- * as password data, and these errors are deliberately shown to the user, so
44
- * such a binding flags every log statement downstream of it (CWE-312).
45
- */
46
- var OAuthError = class extends Error {
47
- code;
48
- /** The server's human-readable explanation (`error_description`), if sent. */
49
- description;
50
- /** The server's "more info" link (`error_uri`), if sent. */
51
- uri;
52
- constructor(code, description, uri) {
53
- super(`OAuth error: ${code}`);
54
- this.name = "OAuthError";
55
- this.code = code;
56
- this.description = description;
57
- this.uri = uri;
58
- }
59
- };
60
- /**
61
- * Sanitize an OAuth error param before logging or rendering it: these arrive
62
- * on a query string (or a token-endpoint body) that anything able to hit the
63
- * local callback port can populate. Strips non-printable/non-ASCII characters
64
- * and caps the length. Returns undefined when nothing printable remains.
65
- */
66
- function sanitizeOAuthParam(value, maxLength = 200) {
67
- if (!value) return void 0;
68
- return value.replace(/[^\x20-\x7e]/g, "").slice(0, maxLength).trim() || void 0;
69
- }
70
- /** Description sentences get more room than bare codes/URIs. */
71
- const MAX_DESCRIPTION_LENGTH = 500;
72
- function escapeHtml(value) {
73
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
74
- }
75
- const OAuthErrorBodySchema = z.object({
76
- error: z.string().min(1),
77
- error_description: z.string().optional(),
78
- error_uri: z.string().optional()
79
- });
80
- /**
81
- * Build an `OAuthError` from an authorize-callback's query params.
82
- * Sanitizes every field — the callback URL is attacker-reachable input.
83
- */
84
- function oauthErrorFromCallbackParams(params) {
85
- return new OAuthError(sanitizeOAuthParam(params.get("error")) ?? "unknown", sanitizeOAuthParam(params.get("error_description"), MAX_DESCRIPTION_LENGTH), sanitizeOAuthParam(params.get("error_uri")));
86
- }
87
- /**
88
- * Build an `OAuthError` from a token-endpoint JSON error body (RFC 6749
89
- * §5.2). Returns null when the body isn't an OAuth error shape (HTML error
90
- * pages, proxy bodies, network failures) — the caller rethrows the raw
91
- * error in that case.
92
- */
93
- function oauthErrorFromTokenBody(data) {
94
- const parsed = OAuthErrorBodySchema.safeParse(data);
95
- if (!parsed.success) return null;
96
- return new OAuthError(sanitizeOAuthParam(parsed.data.error) ?? "unknown", sanitizeOAuthParam(parsed.data.error_description, MAX_DESCRIPTION_LENGTH), sanitizeOAuthParam(parsed.data.error_uri));
97
- }
98
- /**
99
- * The error paragraphs for the browser callback page. The constant framing
100
- * (title, styles, "return to your terminal") stays in `oauth.ts`; this owns
101
- * the part that varies by error, with everything HTML-escaped since the
102
- * values come straight off the callback query string.
103
- */
104
- function buildCallbackErrorHtml(error) {
105
- if (error.code === "access_denied") return "<p>Authorization cancelled.</p>";
106
- const paragraphs = [`<p>Authorization failed (${escapeHtml(error.code)}).</p>`];
107
- if (error.description) paragraphs.push(`<p>${escapeHtml(error.description)}</p>`);
108
- if (error.uri) paragraphs.push(`<p>More info: ${escapeHtml(error.uri)}</p>`);
109
- return paragraphs.join("\n");
110
- }
111
- /**
112
- * Per-code headline + "what to do" remediation for the terminal failure
113
- * message. Codes not listed fall through to a generic rendering of the
114
- * error. `access_denied` and the flow timeout never reach this — they keep
115
- * their dedicated handling in `performOAuthFlow`.
116
- */
117
- function headlineAndRemediation(params) {
118
- const { error, oauthUrl, isDevStack } = params;
119
- if (!(error instanceof OAuthError)) return { headline: error.message };
120
- switch (error.code) {
121
- case "invalid_scope": return {
122
- headline: "PostHog rejected one or more of the requested scopes (invalid_scope).",
123
- whatToDo: isDevStack ? "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." : `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.`
124
- };
125
- case "invalid_client": return {
126
- headline: `PostHog at ${oauthUrl} does not recognize the wizard's OAuth client (invalid_client).`,
127
- whatToDo: "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."
128
- };
129
- case "invalid_grant": return {
130
- headline: "The login code was rejected (invalid_grant): it expired or was already used.",
131
- whatToDo: "Re-run the wizard and complete the browser login promptly. Login codes expire after 5 minutes and can only be used once."
132
- };
133
- case "server_error": return {
134
- headline: "PostHog's OAuth server hit an internal error (server_error).",
135
- whatToDo: `This is usually transient. Wait a moment and re-run the wizard. If it keeps failing, check ${POSTHOG_STATUS_PAGE_URL}.`
136
- };
137
- case "temporarily_unavailable": return {
138
- headline: "PostHog's OAuth server is temporarily unavailable (temporarily_unavailable).",
139
- whatToDo: `This is usually transient. Wait a moment and re-run the wizard. If it keeps failing, check ${POSTHOG_STATUS_PAGE_URL}.`
140
- };
141
- default: return {
142
- headline: `PostHog returned an OAuth error: ${error.code}.`,
143
- whatToDo: "Re-run the wizard. If the error persists, include the details below when asking for help."
144
- };
145
- }
146
- }
147
- /**
148
- * Terminal message for a failed OAuth flow. Always ends with the request
149
- * context (target host, client ID, requested scopes) so a pasted bug report
150
- * is actionable without follow-up questions, plus how to get help: the
151
- * support email (Zendesk-backed) and the issues link.
152
- */
153
- function buildOAuthFailureMessage(params) {
154
- const { error, requestedScopes, clientId, oauthUrl } = params;
155
- const flowError = error instanceof OAuthError ? error : null;
156
- const { headline, whatToDo } = headlineAndRemediation(params);
157
- const sections = [`Authorization failed: ${headline}`];
158
- if (flowError?.description) sections.push(`The server said: ${flowError.description}`);
159
- if (flowError?.uri) sections.push(`More info: ${flowError.uri}`);
160
- if (whatToDo) sections.push(`What to do: ${whatToDo}`);
161
- sections.push([
162
- "Details for bug reports:",
163
- ` target host: ${oauthUrl}`,
164
- ` client ID: ${clientId}`,
165
- ` requested scopes: ${requestedScopes.join(" ")}`
166
- ].join("\n"));
167
- sections.push(`Need help? Email ${WIZARD_CONTACT_EMAIL} with the details above, or create an issue:\n${ISSUES_URL}`);
168
- return sections.join("\n\n");
169
- }
170
- //#endregion
171
- //#region src/programs/oauth/tokens.ts
172
- const OAuthTokenResponseSchema = z.object({
173
- access_token: z.string(),
174
- expires_in: z.number(),
175
- token_type: z.string(),
176
- scope: z.string(),
177
- refresh_token: z.string().optional(),
178
- scoped_teams: z.array(z.number()).optional(),
179
- scoped_organizations: z.array(z.string()).optional(),
180
- posthog_region: z.enum(["us", "eu"]).optional().catch(void 0),
181
- posthog_base_url: z.string().optional().catch(void 0)
182
- });
183
- function parseOAuthScopes(scope) {
184
- return scope.split(/\s+/).filter(Boolean);
185
- }
186
- /**
187
- * Requested scopes the grant came back without.
188
- *
189
- * A token can legitimately carry fewer scopes than the wizard asked for: the
190
- * consent screen lets the user deselect any scope the OAuth app doesn't mark
191
- * required, and anything outside the app's ceiling is clamped server-side.
192
- * Neither path is an error — `/oauth/token` just returns a narrower `scope`.
193
- * Diff it at login, where the gap is fixable, rather than letting the run
194
- * discover it as a permission failure on some API call minutes in.
195
- */
196
- function missingOAuthScopes(requested, grantedScope) {
197
- const granted = new Set(parseOAuthScopes(grantedScope));
198
- return requested.filter((scope) => !granted.has(scope));
199
- }
200
- /**
201
- * OAuth client ID for the current target. A pinned base URL (`--base-url`, or
202
- * IS_DEV's implicit localhost) means we're talking to a dev-seeded stack, which
203
- * registers the dev client; prod uses the proxy client.
204
- *
205
- * TODO: this assumes any pinned base URL is a dev-seeded instance that
206
- * registers POSTHOG_DEV_CLIENT_ID. If we ever point `--base-url` at a non-dev
207
- * instance with its own OAuth app, make the client ID configurable (e.g. a
208
- * `--oauth-client-id` flag) instead of always falling back to the dev client.
209
- */
210
- function getOAuthClientId(baseUrl) {
211
- return resolveBaseUrl(baseUrl) ? POSTHOG_DEV_CLIENT_ID : POSTHOG_PROXY_CLIENT_ID;
212
- }
213
- async function refreshAccessToken(refreshToken, baseUrl, clientId) {
214
- const oauthUrl = getOAuthUrl(baseUrl);
215
- logToFile(`[oauth] refreshing access token at ${oauthUrl}/oauth/token`);
216
- try {
217
- const response = await axios.post(`${oauthUrl}/oauth/token`, {
218
- grant_type: "refresh_token",
219
- refresh_token: refreshToken,
220
- client_id: clientId ?? getOAuthClientId(baseUrl)
221
- }, {
222
- headers: {
223
- "Content-Type": "application/json",
224
- "User-Agent": WIZARD_USER_AGENT
225
- },
226
- timeout: 3e4
227
- });
228
- const token = OAuthTokenResponseSchema.parse(response.data);
229
- logToFile("[oauth] access token refreshed");
230
- return token;
231
- } catch (e) {
232
- logToFile("[oauth] token refresh failed:", e instanceof Error ? e.message : e);
233
- throw (axios.isAxiosError(e) ? oauthErrorFromTokenBody(e.response?.data) : null) ?? e;
234
- }
235
- }
236
- //#endregion
237
- //#region src/tui/auth/oauth.ts
238
- const OAUTH_CALLBACK_STYLES = `
239
- <style>
240
- * {
241
- font-family: monospace;
242
- background-color: #1b0a00;
243
- color: #F7A502;
244
- font-weight: medium;
245
- font-size: 24px;
246
- margin: .25rem;
247
- }
248
-
249
- .blink {
250
- animation: blink-animation 1s steps(2, start) infinite;
251
- }
252
-
253
- @keyframes blink-animation {
254
- to {
255
- opacity: 0;
256
- }
257
- }
258
- </style>
259
- `;
260
- const WIZARD_COMPLETION_SCOPE = "event_definition:write";
261
- function assertWizardCompletionScope(scope) {
262
- if (parseOAuthScopes(scope).includes("event_definition:write")) return;
263
- throw new Error(`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.`);
264
- }
265
- const AUTHORIZATION_TIMEOUT_MESSAGE = "Authorization timed out";
266
- function isAuthorizationTimeout(error) {
267
- return error.message === AUTHORIZATION_TIMEOUT_MESSAGE;
268
- }
269
- function getLocalOAuthOrigin(port) {
270
- return `http://localhost:${port}`;
271
- }
272
- function getCallbackUrl(port) {
273
- return `${getLocalOAuthOrigin(port)}/callback`;
274
- }
275
- function getLocalLoginUrl(port) {
276
- return `${getLocalOAuthOrigin(port)}/authorize`;
277
- }
278
- function getLocalSignupUrl(port) {
279
- return `${getLocalLoginUrl(port)}?signup=true`;
280
- }
281
- /**
282
- * Extract an OAuth authorization code from raw user input. Accepts either the
283
- * bare code, the full callback URL the browser was redirected to
284
- * (`http://localhost:8239/callback?code=abc123&...`), or just the query
285
- * string. Returns null when no code can be found.
286
- *
287
- * This backs the manual-entry fallback: in headless/remote environments the
288
- * browser can't reach the wizard's local callback server, so the user copies
289
- * the failed callback URL (or the code from it) back into the terminal.
290
- */
291
- function extractOAuthCode(input) {
292
- const trimmed = input.trim();
293
- if (!trimmed) return null;
294
- let looksLikeUrl = false;
295
- try {
296
- const url = new URL(trimmed);
297
- looksLikeUrl = true;
298
- const code = url.searchParams.get("code");
299
- if (code) return code;
300
- } catch {}
301
- const match = trimmed.match(/[?&]?code=([^&\s]+)/);
302
- if (match) return decodeURIComponent(match[1]);
303
- if (looksLikeUrl) return null;
304
- if (!/\s/.test(trimmed)) return trimmed;
305
- return null;
306
- }
307
- function generateCodeVerifier() {
308
- return crypto.randomBytes(32).toString("base64url");
309
- }
310
- function generateCodeChallenge(verifier) {
311
- return crypto.createHash("sha256").update(verifier).digest("base64url");
312
- }
313
- async function startCallbackServer(authUrl, signupUrl, port) {
314
- return new Promise((resolve, reject) => {
315
- let callbackResolve;
316
- let callbackReject;
317
- const waitForCallback = () => new Promise((res, rej) => {
318
- callbackResolve = res;
319
- callbackReject = rej;
320
- });
321
- const server = http.createServer((req, res) => {
322
- if (!req.url) {
323
- res.writeHead(400);
324
- res.end();
325
- return;
326
- }
327
- const url = new URL(req.url, getLocalOAuthOrigin(port));
328
- if (url.pathname === "/authorize") {
329
- const redirectUrl = url.searchParams.get("signup") === "true" ? signupUrl : authUrl;
330
- res.writeHead(302, { Location: redirectUrl });
331
- res.end();
332
- return;
333
- }
334
- const code = url.searchParams.get("code");
335
- if (url.searchParams.get("error")) {
336
- const callbackError = oauthErrorFromCallbackParams(url.searchParams);
337
- const isAccessDenied = callbackError.code === "access_denied";
338
- logToFile(`[oauth] callback received with error: ${callbackError.code}` + (callbackError.description ? ` (${callbackError.description})` : ""));
339
- res.writeHead(isAccessDenied ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
340
- res.end(`
341
- <html>
342
- <head>
343
- <meta charset="UTF-8">
344
- <title>PostHog wizard - Authorization ${isAccessDenied ? "cancelled" : "failed"}</title>
345
- ${OAUTH_CALLBACK_STYLES}
346
- </head>
347
- <body>
348
- ${buildCallbackErrorHtml(callbackError)}
349
- <p>Return to your terminal. This window will close automatically.</p>
350
- <script>window.close();<\/script>
351
- </body>
352
- </html>
353
- `);
354
- callbackReject(callbackError);
355
- return;
356
- }
357
- if (code) {
358
- logToFile("[oauth] callback received with authorization code");
359
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
360
- res.end(`
361
- <html>
362
- <head>
363
- <meta charset="UTF-8">
364
- <title>PostHog wizard is ready</title>
365
- ${OAUTH_CALLBACK_STYLES}
366
- </head>
367
- <body>
368
- <p>PostHog login complete!</p>
369
- <p>Return to your terminal: the wizard is hard at work on your project<span class="blink">█</span></p>
370
- <script>window.close();<\/script>
371
- </body>
372
- </html>
373
- `);
374
- callbackResolve(code);
375
- } else {
376
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
377
- res.end(`
378
- <html>
379
- <head>
380
- <meta charset="UTF-8">
381
- <title>PostHog wizard - Invalid request</title>
382
- ${OAUTH_CALLBACK_STYLES}
383
- </head>
384
- <body>
385
- <p>Invalid request - no authorization code received.</p>
386
- <p>You can close this window.</p>
387
- </body>
388
- </html>
389
- `);
390
- }
391
- });
392
- server.on("clientError", (error, socket) => {
393
- if (socket.destroyed || socket.writableEnded) return;
394
- if (error.code === "ECONNRESET" || !socket.writable) {
395
- socket.destroy();
396
- return;
397
- }
398
- logToFile(`[oauth] local HTTP request rejected: ${error.code ?? "unknown"}`);
399
- const overflow = error.code === "HPE_HEADER_OVERFLOW";
400
- let status = "400 Bad Request";
401
- switch (error.code) {
402
- case "HPE_HEADER_OVERFLOW":
403
- status = "431 Request Header Fields Too Large";
404
- break;
405
- case "HPE_CHUNK_EXTENSIONS_OVERFLOW":
406
- status = "413 Payload Too Large";
407
- break;
408
- case "ERR_HTTP_REQUEST_TIMEOUT":
409
- status = "408 Request Timeout";
410
- break;
411
- }
412
- const body = overflow ? `<!doctype html>
413
- <html lang="en">
414
- <head>
415
- <meta charset="UTF-8">
416
- <title>PostHog wizard - Browser request too large</title>
417
- ${OAUTH_CALLBACK_STYLES}
418
- </head>
419
- <body>
420
- <p>Your browser sent more than ${http.maxHeaderSize / 1024} KiB of request headers.</p>
421
- <p>This can happen when cookies from other localhost apps accumulate.</p>
422
- <p>Clear cookies for localhost and retry, or open the login link from your terminal in a private/incognito window.</p>
423
- </body>
424
- </html>` : "";
425
- socket.end(`HTTP/1.1 ${status}\r\nContent-Type: text/html; charset=utf-8\r
426
- Content-Length: ${Buffer.byteLength(body)}\r\nConnection: close\r
427
- Cache-Control: no-store\r
428
- \r
429
- ` + body);
430
- });
431
- server.listen(port, () => {
432
- resolve({
433
- port,
434
- server,
435
- waitForCallback
436
- });
437
- });
438
- server.on("error", reject);
439
- });
440
- }
441
- function getPortProcessInfo(port) {
442
- try {
443
- const lines = execSync(`lsof -i :${port} -sTCP:LISTEN 2>/dev/null`, {
444
- encoding: "utf-8",
445
- timeout: 3e3
446
- }).trim().split("\n");
447
- if (lines.length < 2) return {
448
- command: "unknown",
449
- pid: "unknown",
450
- port,
451
- user: "unknown"
452
- };
453
- const fields = lines[1].split(/\s+/);
454
- return {
455
- command: fields[0] ?? "unknown",
456
- pid: fields[1] ?? "unknown",
457
- port,
458
- user: fields[2] ?? "unknown"
459
- };
460
- } catch {
461
- return {
462
- command: "unknown",
463
- pid: "unknown",
464
- port,
465
- user: "unknown"
466
- };
467
- }
468
- }
469
- function isPortInUseError(error) {
470
- return error instanceof Error && "code" in error && error.code === "EADDRINUSE";
471
- }
472
- async function exchangeCodeForToken(code, codeVerifier, callbackUrl, baseUrl) {
473
- const clientId = getOAuthClientId(baseUrl);
474
- const oauthUrl = getOAuthUrl(baseUrl);
475
- logToFile(`[oauth] exchanging code for token at ${oauthUrl}/oauth/token`);
476
- let response;
477
- try {
478
- response = await axios.post(`${oauthUrl}/oauth/token`, {
479
- grant_type: "authorization_code",
480
- code,
481
- redirect_uri: callbackUrl,
482
- client_id: clientId,
483
- code_verifier: codeVerifier
484
- }, { headers: {
485
- "Content-Type": "application/json",
486
- "User-Agent": WIZARD_USER_AGENT
487
- } });
488
- } catch (e) {
489
- const status = axios.isAxiosError(e) ? e.response?.status : void 0;
490
- logToFile(`[oauth] token exchange failed${status ? ` (HTTP ${status})` : ""}:`, e instanceof Error ? e.message : e);
491
- const exchangeError = axios.isAxiosError(e) ? oauthErrorFromTokenBody(e.response?.data) : null;
492
- if (exchangeError) {
493
- logToFile(`[oauth] token endpoint error: ${exchangeError.code}` + (exchangeError.description ? ` (${exchangeError.description})` : ""));
494
- throw exchangeError;
495
- }
496
- throw e;
497
- }
498
- const token = OAuthTokenResponseSchema.parse(response.data);
499
- logToFile(`[oauth] token exchange succeeded, granted scopes: ${token.scope}${token.posthog_region ? `, region: ${token.posthog_region}` : ""}${token.scoped_teams ? `, scoped_teams: [${token.scoped_teams.join(", ")}]` : ""}${token.scoped_organizations ? `, scoped_organizations: ${token.scoped_organizations.length}` : ""}`);
500
- return token;
501
- }
502
- /**
503
- * Warn — at login, while the user is still watching — when the grant came back
504
- * narrower than the request, and record the gap so narrowed runs are countable.
505
- * Non-fatal by design: deselecting an optional scope is the user's call, and
506
- * most flows survive it. The one scope the wizard cannot run without has its
507
- * own hard check (`assertWizardCompletionScope`).
508
- */
509
- function reportNarrowedGrant(requestedScopes, grantedScope) {
510
- const missing = missingOAuthScopes(requestedScopes, grantedScope);
511
- if (missing.length === 0) return;
512
- logToFile(`[oauth] grant narrower than request, missing: ${missing.join(" ")}`);
513
- analytics.wizardCapture("oauth grant narrowed", {
514
- requested_scopes: [...requestedScopes].sort().join(" "),
515
- granted_scopes: parseOAuthScopes(grantedScope).sort().join(" "),
516
- missing_scopes: missing.join(" "),
517
- missing_scope_count: missing.length
518
- });
519
- const plural = missing.length > 1;
520
- getUI().log.warn(`Your PostHog authorization is missing ${plural ? `${missing.length} permissions` : "a permission"} the wizard asked for: ${missing.join(", ")}. Setup will continue, but steps that need ${plural ? "them" : "it"} may fail. To grant ${plural ? "them" : "it"}, re-run the wizard and approve all permissions on the authorization screen. If that screen does not reappear, revoke the existing PostHog Wizard authorization in your PostHog settings first.`);
521
- }
522
- async function performOAuthFlow(config) {
523
- const clientId = getOAuthClientId(config.baseUrl);
524
- const oauthUrl = getOAuthUrl(config.baseUrl);
525
- const codeVerifier = generateCodeVerifier();
526
- const codeChallenge = generateCodeChallenge(codeVerifier);
527
- let shouldRetry = false;
528
- logToFile(`[oauth] starting flow against ${oauthUrl}, requested scopes: ${config.scopes.join(" ")}`);
529
- do {
530
- shouldRetry = false;
531
- let lastProcessInfo = null;
532
- for (const port of OAUTH_PORTS) {
533
- const callbackUrl = getCallbackUrl(port);
534
- const authUrl = new URL(`${oauthUrl}/oauth/authorize`);
535
- authUrl.searchParams.set("client_id", clientId);
536
- authUrl.searchParams.set("redirect_uri", callbackUrl);
537
- authUrl.searchParams.set("response_type", "code");
538
- authUrl.searchParams.set("code_challenge", codeChallenge);
539
- authUrl.searchParams.set("code_challenge_method", "S256");
540
- authUrl.searchParams.set("scope", config.scopes.join(" "));
541
- authUrl.searchParams.set("required_access_level", "project");
542
- if (config.projectId !== void 0) authUrl.searchParams.set("team_id", String(config.projectId));
543
- const taggedAuthUrl = withUtm(authUrl.toString(), "oauth-authorize");
544
- const signupUrl = new URL(withUtm(`${oauthUrl}/signup?next=${encodeURIComponent(taggedAuthUrl)}`, "oauth-signup"));
545
- const localSignupUrl = getLocalSignupUrl(port);
546
- const localLoginUrl = getLocalLoginUrl(port);
547
- const urlToOpen = config.signup ? localSignupUrl : localLoginUrl;
548
- logToFile(`[oauth] attempting callback server on port ${port}`);
549
- let server;
550
- let waitForCallback;
551
- try {
552
- ({server, waitForCallback} = await startCallbackServer(taggedAuthUrl, signupUrl.toString(), port));
553
- } catch (e) {
554
- if (!isPortInUseError(e)) throw e;
555
- lastProcessInfo = getPortProcessInfo(port);
556
- continue;
557
- }
558
- logToFile("[oauth] callback server ready, showing login URL");
559
- getUI().setLoginUrl(urlToOpen);
560
- getUI().setAuthorizeUrl(config.signup ? signupUrl.toString() : taggedAuthUrl);
561
- openTrackedLink(urlToOpen, "oauth", {
562
- auto: true,
563
- skipUtm: true
564
- });
565
- const loginSpinner = getUI().spinner();
566
- loginSpinner.start("Waiting for authorization...");
567
- try {
568
- const token = await exchangeCodeForToken(await Promise.race([
569
- waitForCallback(),
570
- getUI().waitForManualAuthCode(),
571
- new Promise((_, reject) => setTimeout(() => reject(new Error(AUTHORIZATION_TIMEOUT_MESSAGE)), OAUTH_TIMEOUT_MS))
572
- ]), codeVerifier, callbackUrl, config.baseUrl);
573
- server.close();
574
- getUI().setLoginUrl(null);
575
- getUI().setAuthorizeUrl(null);
576
- loginSpinner.stop("Authorization complete!");
577
- reportNarrowedGrant(config.scopes, token.scope);
578
- return token;
579
- } catch (e) {
580
- const error = e instanceof Error ? e : /* @__PURE__ */ new Error("Unknown error");
581
- const timedOut = isAuthorizationTimeout(error);
582
- const flowError = error instanceof OAuthError ? error : null;
583
- loginSpinner.stop(timedOut ? "Session timed out." : "Authorization failed.");
584
- server.close();
585
- logToFile("[oauth] flow failed:", error);
586
- if (flowError?.description) logToFile(`[oauth] server error_description: ${flowError.description}`);
587
- const accessDenied = flowError ? flowError.code === "access_denied" : error.message.includes("access_denied");
588
- if (timedOut) getUI().showSessionTimeout();
589
- else if (accessDenied) getUI().log.info(`Authorization was cancelled.\n\nYou denied access to PostHog. To use the wizard, you need to authorize access to your PostHog account.\n\nYou can try again by re-running the wizard.`);
590
- else getUI().log.error(buildOAuthFailureMessage({
591
- error,
592
- requestedScopes: config.scopes,
593
- clientId,
594
- oauthUrl,
595
- isDevStack: resolveBaseUrl(config.baseUrl) !== void 0
596
- }));
597
- const oauthErrorCode = flowError ? flowError.code : error.message.startsWith("OAuth error: ") ? error.message.slice(13) : timedOut ? "timeout" : "unknown";
598
- analytics.captureException(error, {
599
- step: "oauth_flow",
600
- oauth_error_code: oauthErrorCode,
601
- oauth_error_description: flowError?.description,
602
- client_id: clientId,
603
- requested_scopes: config.scopes.join(" "),
604
- $exception_fingerprint: `wizard_oauth_${oauthErrorCode}`
605
- });
606
- await abort();
607
- throw error;
608
- }
609
- }
610
- if (!lastProcessInfo) throw new Error("No OAuth callback ports configured");
611
- await getUI().showPortConflict(lastProcessInfo);
612
- shouldRetry = true;
613
- } while (shouldRetry);
614
- throw new Error("OAuth port retry loop exited unexpectedly");
615
- }
616
- //#endregion
617
- //#region src/programs/agent-skill/scopes.ts
618
- /**
619
- * Extra scopes the agent-skill program needs on top of `WIZARD_OAUTH_SCOPES`.
620
- *
621
- * Skills under this program (e.g. `creating-product-tours`) create feature
622
- * flags during the install flow. PostHog's consent grants exactly the scope
623
- * strings requested — `:write` does not imply `:read` — so listing existing
624
- * flags to avoid key collisions needs `feature_flag:read` explicitly.
625
- * `property_definition:read` lets the agent discover person properties when
626
- * building flag rollout filters instead of having to ask the user verbatim.
627
- */
628
- const AGENT_SKILL_SCOPE_ADDITIONS = [
629
- "feature_flag:read",
630
- "feature_flag:write",
631
- "property_definition:read"
632
- ];
633
- //#endregion
634
- //#region src/programs/replay-vision/scopes.ts
635
- /**
636
- * Extra scopes the replay-vision program needs on top of `WIZARD_OAUTH_SCOPES`.
637
- * The same set self-driving's step 6c uses, narrowed to just this flow:
638
- * • replay_scanner:read / replay_scanner:write — the scanner tasks list the
639
- * team's existing scanners and create the ones scoped to the product's key
640
- * flows (`vision-scanners-list` / `-create`, plus the advisory
641
- * `vision-scanners-estimate-create` / `vision-quota-retrieve`). The scope
642
- * OBJECT is `replay_scanner`; `vision-scanners-*` are MCP tool names.
643
- * • session_recording:read — the scanner API pairs it with
644
- * `replay_scanner:*`, since a scanner's config indirectly exposes
645
- * recording contents. Configuring a scanner fails without it.
646
- * • product_enablement:write — the enable-replay task's server half turns on
647
- * Session Replay (`products-enable`) so there are recordings to scan.
648
- *
649
- * Without these the PostHog MCP omits the tools from the catalog it serves
650
- * this token, every scanner task takes its "tool unknown" skip path, and the
651
- * run reports success having created nothing (run 69afc6f8).
652
- *
653
- * No OAuth-ceiling edit needed — all are unprivileged public scope objects
654
- * covered by the apps' `@default` sentinel, and self-driving already requests
655
- * every one of them.
656
- */
657
- const REPLAY_VISION_SCOPE_ADDITIONS = [
658
- "session_recording:read",
659
- "product_enablement:write",
660
- "replay_scanner:read",
661
- "replay_scanner:write"
662
- ];
663
- //#endregion
664
- //#region src/programs/self-driving/scopes.ts
665
- /**
666
- * Extra scopes the self-driving program needs on top of
667
- * `WIZARD_OAUTH_SCOPES`. All consumed by the PostHog MCP tools the
668
- * agent drives during the run:
669
- * • task:read / task:write — the signal source config API
670
- * (`inbox-source-configs-*`) is permissioned under the generic
671
- * `task` scope object, NOT a signals-specific one. Unrelated to
672
- * the Tasks product.
673
- * • integration:read — `integrations-list`, to check whether the
674
- * team already has a GitHub integration and to verify the connect
675
- * flow completed.
676
- * • signal_scout:read / signal_scout:write — list, sync, and tune
677
- * the Signals scout troop (`signals-scout-config-*`).
678
- * • session_recording:read / survey:read / error_tracking:read —
679
- * server-side product-usage probes (`query-session-recordings-list`,
680
- * `survey-list`, `error-issue-list`). Product usage is a
681
- * project-level fact (often instrumented in another repo or via
682
- * the snippet), so the agent asks the server instead of inferring
683
- * only from the local setup report. All three are read-only and
684
- * already in the wizard OAuth app's production scope ceiling (the
685
- * mcp-tutorial program requests them).
686
- * • external_data_source:read / external_data_source:write — the
687
- * connected-tools step creates the GitHub Issues / Linear warehouse
688
- * sources directly (`external-data-sources-create`) and verifies
689
- * what's actually connected (`external-data-sources-list`) instead
690
- * of taking the user's word for it.
691
- * • llm_skill:read / llm_skill:write — the custom-scouts step
692
- * (skill step 6b): read the seeded `authoring-signals-scouts`
693
- * guide and canonical scout bodies (`llma-skill-get` /
694
- * `llma-skill-file-get`) and author the user-approved custom
695
- * `signals-scout-*` skills (`llma-skill-create`). Canonical scout
696
- * bodies are never edited.
697
- * • product_enablement:write — the "Enable products" step turns on
698
- * Session Replay / Error Tracking / Support so their sources have
699
- * data to read (`products-enable`). A purpose-built scope: the
700
- * server owns each enable recipe, so this can flip the product
701
- * toggles without the far broader `project:write`.
702
- * • replay_scanner:read / replay_scanner:write — the Replay Vision
703
- * scanners step (skill step 6c) lists the team's existing scanners
704
- * and creates the `emits_signals` ones whose findings land in the
705
- * inbox (`vision-scanners-list` / `-create` / `-update`, and the
706
- * advisory `vision-scanners-estimate-create` / `vision-quota-retrieve`).
707
- * The scope OBJECT is `replay_scanner` — the `vision-scanners-*`
708
- * names are MCP tool names, not scopes. Configuring a scanner also
709
- * requires `session_recording:read` (the API pairs the two, since a
710
- * scanner's config indirectly exposes recording contents); that one
711
- * is already in this list for the step-2 usage probes.
712
- *
713
- * No OAuth-ceiling edit is needed for any scope here: they are all normal
714
- * public (unprivileged, non-internal, non-hidden) scope objects, and the
715
- * live wizard apps' ceiling is the `@default` sentinel, which resolves to
716
- * every such scope (`UNPRIVILEGED_SCOPES`) and auto-tracks new ones. Only a
717
- * privileged/internal/hidden object (e.g. `llm_gateway:*`) would need a
718
- * manual per-app edit. See README → "OAuth app scope ceiling".
719
- */
720
- const SELF_DRIVING_SCOPE_ADDITIONS = [
721
- "task:read",
722
- "task:write",
723
- "integration:read",
724
- "signal_scout:read",
725
- "signal_scout:write",
726
- "session_recording:read",
727
- "survey:read",
728
- "error_tracking:read",
729
- "external_data_source:read",
730
- "external_data_source:write",
731
- "llm_skill:read",
732
- "llm_skill:write",
733
- "product_enablement:write",
734
- "replay_scanner:read",
735
- "replay_scanner:write"
736
- ];
737
- //#endregion
738
- //#region src/shared/oauth-scopes.ts
739
- /**
740
- * Extra scope the Connect-Slack step needs on top of `WIZARD_OAUTH_SCOPES`.
741
- *
742
- * The step polls `/api/projects/:id/integrations/` (`fetchSlackConnected`)
743
- * to render the already-connected variant and to flip live once the user
744
- * completes the Slack OAuth step in the browser. Without `integration:read`
745
- * the first poll 403s, the screen stops polling, and an already-connected
746
- * project is nagged with the connect nudge. Used by the default integration
747
- * run (the step ends the run) and by the standalone `wizard slack` flow
748
- * (the step is the whole program).
749
- */
750
- const CONNECT_SLACK_SCOPE_ADDITIONS = ["integration:read"];
751
- //#endregion
752
- //#region src/tools/mcp/scopes.ts
753
- /**
754
- * Extra scopes the MCP tutorial needs on top of `WIZARD_OAUTH_SCOPES`.
755
- *
756
- * Every scope requested here must stay within the wizard OAuth app's
757
- * ceiling on the PostHog side (`OAuthApplication.scopes`) — the full
758
- * list lives in the README under "OAuth app scope ceiling". The
759
- * tutorial's prompts and follow-ups touch most of the read surface,
760
- * plus annotation write for the "PostHog wizard install" verify-prompt.
761
- *
762
- * Already in the base `WIZARD_OAUTH_SCOPES` (and therefore not
763
- * repeated here):
764
- * • user:read, project:read, llm_gateway:read — auth + gateway
765
- * • query:read — HogQL
766
- * • dashboard:write, insight:write, notebook:write — Phase-5 persist
767
- *
768
- * Deliberately omitted (writes on read-only product surfaces):
769
- * • feature_flag:write, experiment:write, survey:write,
770
- * cohort:write, session_recording:write, error_tracking:write,
771
- * alert:write, subscription:write
772
- */
773
- const MCP_TUTORIAL_SCOPE_ADDITIONS = [
774
- "dashboard:read",
775
- "insight:read",
776
- "notebook:read",
777
- "feature_flag:read",
778
- "experiment:read",
779
- "experiment_saved_metric:read",
780
- "survey:read",
781
- "session_recording:read",
782
- "error_tracking:read",
783
- "web_analytics:read",
784
- "llm_analytics:read",
785
- "cohort:read",
786
- "person:read",
787
- "annotation:read",
788
- "annotation:write",
789
- "activity_log:read",
790
- "property_definition:read",
791
- "event_definition:read",
792
- "action:read",
793
- "warehouse_table:read",
794
- "warehouse_view:read",
795
- "alert:read",
796
- "subscription:read",
797
- "integration:read"
798
- ];
799
- //#endregion
800
- //#region src/programs/oauth/program-scopes.ts
801
- /**
802
- * Extra scopes the warehouse-source program needs on top of
803
- * `WIZARD_OAUTH_SCOPES`. The agent creates data warehouse sources directly
804
- * (`external-data-sources-create`) and lists what's connected
805
- * (`external-data-sources-list`) to verify the result. Both are already within
806
- * the wizard OAuth app's scope ceiling — the self-driving program requests the
807
- * same pair.
808
- */
809
- const WAREHOUSE_SOURCE_SCOPE_ADDITIONS = ["external_data_source:read", "external_data_source:write"];
810
- /**
811
- * Per-program scope additions, layered on top of `WIZARD_OAUTH_SCOPES`.
812
- *
813
- * Programs not listed here request the unchanged base set. Use this
814
- * map only for programs that need *more* than the base — never for
815
- * narrowing, since narrowing risks breaking shared infrastructure
816
- * (e.g. dropping `llm_gateway:read` would 401 every agent call).
817
- *
818
- * Keyed by `ProgramId` so TypeScript catches stale entries when a
819
- * program is renamed or removed.
820
- */
821
- const PROGRAM_SCOPE_ADDITIONS = {
822
- "mcp-tutorial": MCP_TUTORIAL_SCOPE_ADDITIONS,
823
- "agent-skill": AGENT_SKILL_SCOPE_ADDITIONS,
824
- "self-driving": SELF_DRIVING_SCOPE_ADDITIONS,
825
- "warehouse-source": WAREHOUSE_SOURCE_SCOPE_ADDITIONS,
826
- "posthog-integration": [...CONNECT_SLACK_SCOPE_ADDITIONS, ...WAREHOUSE_SOURCE_SCOPE_ADDITIONS],
827
- slack: CONNECT_SLACK_SCOPE_ADDITIONS,
828
- "replay-vision": REPLAY_VISION_SCOPE_ADDITIONS
829
- };
830
- /**
831
- * Resolve the OAuth scope list to request for a given program. Returns
832
- * `WIZARD_OAUTH_SCOPES` for programs without an addition entry; for
833
- * programs that do have one, returns the union of base + additions
834
- * with duplicates dropped (declaration order preserved, base first).
835
- *
836
- * `null` / `undefined` programId falls through to the default — same
837
- * behavior as the historical hardcoded `WIZARD_OAUTH_SCOPES` reference
838
- * in `askForWizardLogin`, so call sites that haven't been updated to
839
- * pass a programId continue to work unchanged.
840
- */
841
- function getOAuthScopesForProgram(programId) {
842
- const additions = programId && PROGRAM_SCOPE_ADDITIONS[programId] || [];
843
- if (additions.length === 0) return WIZARD_OAUTH_SCOPES;
844
- const seen = /* @__PURE__ */ new Set();
845
- const merged = [];
846
- for (const s of [...WIZARD_OAUTH_SCOPES, ...additions]) {
847
- if (seen.has(s)) continue;
848
- seen.add(s);
849
- merged.push(s);
850
- }
851
- return merged;
852
- }
853
- /**
854
- * Resolve the scope list for the signup provisioning path. Same
855
- * base-plus-additions shape as `getOAuthScopesForProgram`, but layered on
856
- * `WIZARD_PROVISIONING_SCOPES` so a program's extra scopes only reach
857
- * tokens provisioned for that program.
858
- */
859
- function getProvisioningScopesForProgram(programId) {
860
- const additions = programId && PROGRAM_SCOPE_ADDITIONS[programId] || [];
861
- if (additions.length === 0) return WIZARD_PROVISIONING_SCOPES;
862
- const seen = /* @__PURE__ */ new Set();
863
- const merged = [];
864
- for (const s of [...WIZARD_PROVISIONING_SCOPES, ...additions]) {
865
- if (seen.has(s)) continue;
866
- seen.add(s);
867
- merged.push(s);
868
- }
869
- return merged;
870
- }
871
- //#endregion
872
- //#region src/shared/utils/project-resolution.ts
873
- /**
874
- * Decide which project to use after OAuth. `--project-id` (when passed) is authoritative,
875
- * but only when the user granted access to it on the consent screen; a different grant is a
876
- * mismatch the caller must surface. With no `--project-id` this returns the granted project
877
- * (`scopedTeams[0]`), preserving the pre-flag behavior for every wizard program (the main
878
- * integration flow included).
879
- */
880
- function resolveGrantedProject(requestedProjectId, scopedTeams) {
881
- if (requestedProjectId === void 0) return {
882
- ok: true,
883
- projectId: scopedTeams?.[0]
884
- };
885
- if (scopedTeams?.includes(requestedProjectId)) return {
886
- ok: true,
887
- projectId: requestedProjectId
888
- };
889
- if (scopedTeams && scopedTeams.length > 0) return {
890
- ok: false,
891
- requested: requestedProjectId,
892
- granted: scopedTeams[0]
893
- };
894
- return {
895
- ok: true,
896
- projectId: void 0
897
- };
898
- }
899
- //#endregion
900
- //#region src/shared/api-key-login.ts
901
- async function fetchProjectDataWithApiKey(apiKey, cloudUrl) {
902
- const projectId = (await fetchUserData(apiKey, cloudUrl)).team?.id;
903
- if (!projectId) throw new Error("Could not determine project ID from API key. Please ensure your API key has access to a project in this cloud region.");
904
- const projectData = await fetchProjectData(apiKey, projectId, cloudUrl);
905
- return {
906
- api_token: projectData.api_token,
907
- id: projectId,
908
- project: projectData
909
- };
910
- }
911
- async function fetchProjectDataById(apiKey, projectId, cloudUrl) {
912
- const projectData = await fetchProjectData(apiKey, projectId, cloudUrl);
913
- return {
914
- api_token: projectData.api_token,
915
- id: projectId,
916
- project: projectData
917
- };
918
- }
919
- //#endregion
920
- //#region src/tui/auth/project-data.ts
921
- /**
922
- * Get project data for the wizard via OAuth or CI API key.
923
- */
924
- async function getOrAskForProjectData(_options) {
925
- if (_options.ci && _options.apiKey) {
926
- getUI().log.info("Using provided API key (CI mode - OAuth bypassed)");
927
- const host = await HostResolution.fromAccessToken(_options.apiKey, {
928
- region: _options.region,
929
- localMcp: _options.localMcp,
930
- baseUrl: _options.baseUrl
931
- });
932
- const cloudUrl = host.appHost;
933
- const projectData = _options.projectId != null ? await fetchProjectDataById(_options.apiKey, _options.projectId, cloudUrl) : await fetchProjectDataWithApiKey(_options.apiKey, cloudUrl);
934
- let user = null;
935
- let roleAtOrganization = null;
936
- try {
937
- user = await fetchUserData(_options.apiKey, cloudUrl);
938
- roleAtOrganization = user.role_at_organization ?? null;
939
- } catch (err) {
940
- logToFile("[ci-auth] user lookup failed:", err instanceof Error ? err.message : String(err));
941
- }
942
- if (user) {
943
- analytics.identifyUser(user);
944
- logToFile("[ci-auth] identified via API key; flags evaluate as the key owner");
945
- } else getUI().log.warn("Could not resolve the API key user (key needs user:read scope) — feature flags evaluate anonymously; user-targeted flags will not match.");
946
- return {
947
- host,
948
- projectApiKey: projectData.api_token,
949
- accessToken: _options.apiKey,
950
- projectId: projectData.id,
951
- roleAtOrganization,
952
- user,
953
- project: projectData.project,
954
- missingScopes: []
955
- };
956
- }
957
- const { host, projectApiKey, accessToken, refreshToken, expiresAt, oauthClientId, projectId, roleAtOrganization, user, project, missingScopes } = await withProgress("login", () => askForWizardLogin({
958
- signup: _options.signup,
959
- email: _options.email,
960
- region: _options.region,
961
- baseUrl: _options.baseUrl,
962
- programId: _options.programId,
963
- projectId: _options.projectId,
964
- localMcp: _options.localMcp
965
- }));
966
- if (!projectApiKey) {
967
- const cloudUrl = host.appHost;
968
- getUI().log.error(`Didn't receive a project token. This shouldn't happen :(
969
-
970
- Please let us know if you think this is a bug in the wizard:
971
- ${ISSUES_URL}`);
972
- getUI().log.info(`In the meantime, we'll add a dummy project token ("${DUMMY_PROJECT_API_KEY}") for you to replace later.
973
- You can find your project token here:
974
- ${cloudUrl}/settings/project#variables`);
975
- }
976
- return {
977
- accessToken,
978
- refreshToken,
979
- expiresAt,
980
- oauthClientId,
981
- host,
982
- projectApiKey: projectApiKey || "_YOUR_POSTHOG_PROJECT_TOKEN_",
983
- projectId,
984
- roleAtOrganization: roleAtOrganization ?? null,
985
- user: user ?? null,
986
- project: project ?? null,
987
- missingScopes: missingScopes ?? []
988
- };
989
- }
990
- async function askForWizardLogin(options) {
991
- if (options.signup) return askForProvisioningSignup(options.email, options.region, options.baseUrl, options.localMcp, options.programId);
992
- const requestedScopes = [...getOAuthScopesForProgram(options.programId)];
993
- const tokenResponse = await performOAuthFlow({
994
- scopes: requestedScopes,
995
- signup: false,
996
- projectId: options.projectId,
997
- baseUrl: options.baseUrl
998
- });
999
- try {
1000
- assertWizardCompletionScope(tokenResponse.scope);
1001
- } catch (error) {
1002
- const scopeError = error instanceof Error ? error : /* @__PURE__ */ new Error("OAuth scope check failed");
1003
- const missing = missingOAuthScopes(requestedScopes, tokenResponse.scope);
1004
- analytics.captureException(scopeError, {
1005
- step: "wizard_login",
1006
- missing_scope: "event_definition:write"
1007
- });
1008
- await wizardAbort({
1009
- message: scopeError.message,
1010
- outroData: {
1011
- kind: "error",
1012
- message: "Setup needs permissions that were not granted",
1013
- body: [
1014
- "Missing permissions:",
1015
- ...missing.map((scope) => ` • ${scope}`),
1016
- "",
1017
- "Re-run the wizard and approve all permissions on the PostHog authorization screen.",
1018
- "If that screen does not reappear, revoke the existing PostHog Wizard authorization in your PostHog settings first."
1019
- ].join("\n")
1020
- }
1021
- });
1022
- }
1023
- const resolution = resolveGrantedProject(options.projectId, tokenResponse.scoped_teams);
1024
- if (!resolution.ok) {
1025
- const error = /* @__PURE__ */ new Error(`You authorized project ${resolution.granted}, but setup is targeting project ${resolution.requested} (from --project-id). 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. If it is yours, re-run and grant access to project ${resolution.requested} on the authorization screen.`);
1026
- analytics.captureException(error, {
1027
- step: "wizard_login",
1028
- requested_project_id: resolution.requested,
1029
- granted_project_id: resolution.granted
1030
- });
1031
- getUI().log.error(error.message);
1032
- await abort(error.message);
1033
- }
1034
- const projectId = resolution.ok ? resolution.projectId : void 0;
1035
- if (projectId === void 0) {
1036
- const error = /* @__PURE__ */ new Error("No project access granted. Please authorize with project-level access.");
1037
- analytics.captureException(error, {
1038
- step: "wizard_login",
1039
- has_scoped_teams: !!tokenResponse.scoped_teams
1040
- });
1041
- getUI().log.error(error.message);
1042
- await abort(error.message);
1043
- }
1044
- const host = await HostResolution.fromAccessToken(tokenResponse.access_token, {
1045
- region: tokenResponse.posthog_region,
1046
- localMcp: options.localMcp,
1047
- baseUrl: options.baseUrl
1048
- });
1049
- const cloudUrl = host.appHost;
1050
- const projectData = await fetchProjectData(tokenResponse.access_token, projectId, cloudUrl);
1051
- const userData = await fetchUserData(tokenResponse.access_token, cloudUrl);
1052
- const data = {
1053
- accessToken: tokenResponse.access_token,
1054
- refreshToken: tokenResponse.refresh_token,
1055
- expiresAt: Date.now() + tokenResponse.expires_in * 1e3,
1056
- projectApiKey: projectData.api_token,
1057
- host,
1058
- distinctId: userData.distinct_id,
1059
- projectId,
1060
- roleAtOrganization: userData.role_at_organization ?? null,
1061
- user: userData,
1062
- project: projectData,
1063
- missingScopes: missingOAuthScopes(requestedScopes, tokenResponse.scope)
1064
- };
1065
- getUI().log.success("Login complete.");
1066
- analytics.setTag("opened-wizard-link", true);
1067
- analytics.identifyUser(userData);
1068
- return data;
1069
- }
1070
- async function askForProvisioningSignup(email, region, baseUrl, localMcp, programId) {
1071
- if (!email || !email.includes("@")) {
1072
- getUI().log.error("Email is required for signup. Use --email your@email.com with --signup.");
1073
- await abort();
1074
- throw new Error("unreachable");
1075
- }
1076
- const spinner = getUI().spinner();
1077
- spinner.start("Creating your PostHog account...");
1078
- try {
1079
- const provisionRegion = (region ?? "us").toUpperCase();
1080
- const { orgName, projectName } = detectOrgAndProject(email);
1081
- const result = await provisionNewAccount(email, "", provisionRegion, {
1082
- orgName,
1083
- projectName,
1084
- baseUrl,
1085
- scopes: getProvisioningScopesForProgram(programId)
1086
- });
1087
- spinner.stop("Account created!");
1088
- getUI().log.success("Welcome to PostHog!");
1089
- const host = HostResolution.fromApiHost(result.host, { localMcp });
1090
- analytics.setTag("provisioning-signup", true);
1091
- return {
1092
- accessToken: result.accessToken,
1093
- refreshToken: result.refreshToken,
1094
- expiresAt: result.expiresAt,
1095
- oauthClientId: result.oauthClientId,
1096
- projectApiKey: result.projectApiKey,
1097
- host,
1098
- distinctId: email,
1099
- projectId: parseInt(result.projectId, 10) || 0
1100
- };
1101
- } catch (error) {
1102
- const message = error instanceof Error ? error.message : "Unknown error";
1103
- if (error instanceof ProvisionedAccountUnreadableError) {
1104
- spinner.stop("Account created, but the project could not be read back.");
1105
- getUI().log.warn(message);
1106
- getUI().log.info("Signing you in to your new account instead...");
1107
- return askForWizardLogin({
1108
- signup: false,
1109
- baseUrl,
1110
- localMcp
1111
- });
1112
- }
1113
- spinner.stop("Account creation failed.");
1114
- if (message.includes("already associated")) {
1115
- getUI().log.info("This email already has a PostHog account. Switching to login flow...");
1116
- return askForWizardLogin({
1117
- signup: false,
1118
- baseUrl,
1119
- localMcp
1120
- });
1121
- }
1122
- getUI().log.error(`Failed to create account: ${message}`);
1123
- analytics.captureException(error instanceof Error ? error : new Error(message), { step: "provisioning_signup" });
1124
- await abort();
1125
- throw error;
1126
- }
1127
- }
1128
- //#endregion
1129
- export { OAuthError as a, refreshAccessToken as i, extractOAuthCode as n, missingOAuthScopes as r, getOrAskForProjectData as t };
1130
-
1131
- //# sourceMappingURL=project-data-BO9IXn9y.js.map