@posthog/wizard 2.21.0 → 2.22.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 (65) hide show
  1. package/README.md +14 -1
  2. package/dist/{OutroScreen-CqF6SdBo.js → AiOptInRequiredScreen-B8mgZbe5.js} +482 -59
  3. package/dist/AiOptInRequiredScreen-B8mgZbe5.js.map +1 -0
  4. package/dist/{add-mcp-server-to-clients-DQHGhzt6.js → add-mcp-server-to-clients-lCxOS1A1.js} +12 -10
  5. package/dist/add-mcp-server-to-clients-lCxOS1A1.js.map +1 -0
  6. package/dist/{agent-interface-DE7txTqh.js → agent-interface-BPCzPvK-.js} +6 -5
  7. package/dist/{agent-interface-DE7txTqh.js.map → agent-interface-BPCzPvK-.js.map} +1 -1
  8. package/dist/{agent-runner-DUZ5OD6e.js → agent-runner-DgzF2mga.js} +13 -9
  9. package/dist/{agent-runner-DUZ5OD6e.js.map → agent-runner-DgzF2mga.js.map} +1 -1
  10. package/dist/{analytics-Bl5DPj_0.js → analytics-BZv-cKyn.js} +28 -4
  11. package/dist/analytics-BZv-cKyn.js.map +1 -0
  12. package/dist/{api-DuA0_88V.js → api-BkLZ8BWm.js} +3 -3
  13. package/dist/{api-DuA0_88V.js.map → api-BkLZ8BWm.js.map} +1 -1
  14. package/dist/bin.js +160 -49
  15. package/dist/bin.js.map +1 -1
  16. package/dist/{ci-install-BnOYI4mZ.js → ci-install-DBARq5LX.js} +4 -4
  17. package/dist/{ci-install-BnOYI4mZ.js.map → ci-install-DBARq5LX.js.map} +1 -1
  18. package/dist/{debug-h7Z9zEbD.js → debug-Ckumcrye.js} +189 -40
  19. package/dist/debug-Ckumcrye.js.map +1 -0
  20. package/dist/{debug-BVC48wlb.js → debug-DLsuyfln.js} +1 -1
  21. package/dist/{environment-uaLmtlH_.js → environment-DUh_8hqW.js} +3 -3
  22. package/dist/{environment-uaLmtlH_.js.map → environment-DUh_8hqW.js.map} +1 -1
  23. package/dist/{interactive-CW5gjyDd.js → interactive-DeiHgviS.js} +2 -2
  24. package/dist/{interactive-CW5gjyDd.js.map → interactive-DeiHgviS.js.map} +1 -1
  25. package/dist/{mcp-prompt-streaming-DMDwaark.js → mcp-prompt-streaming-kEJgmB30.js} +4 -4
  26. package/dist/{mcp-prompt-streaming-DMDwaark.js.map → mcp-prompt-streaming-kEJgmB30.js.map} +1 -1
  27. package/dist/{non-interactive-DJrVQ4nS.js → non-interactive-I4ifOSau.js} +2 -2
  28. package/dist/{non-interactive-DJrVQ4nS.js.map → non-interactive-I4ifOSau.js.map} +1 -1
  29. package/dist/{package-manager-DCUBRbr-.js → package-manager-fUeLORHg.js} +2 -2
  30. package/dist/{package-manager-DCUBRbr-.js.map → package-manager-fUeLORHg.js.map} +1 -1
  31. package/dist/{playground-DCVaVeVD.js → playground-DLLIz4Ql.js} +135 -19
  32. package/dist/playground-DLLIz4Ql.js.map +1 -0
  33. package/dist/{posthog-integration-ChdwFPMj.js → posthog-integration-COcPewVt.js} +48 -16
  34. package/dist/posthog-integration-COcPewVt.js.map +1 -0
  35. package/dist/{provisioning-GeMkBMSR.js → provisioning-7xU12_S9.js} +3 -3
  36. package/dist/{provisioning-GeMkBMSR.js.map → provisioning-7xU12_S9.js.map} +1 -1
  37. package/dist/{registry-VSSRH3sU.js → registry-YwaF-aD8.js} +7 -20
  38. package/dist/registry-YwaF-aD8.js.map +1 -0
  39. package/dist/{setup-utils-BfV4pydt.js → setup-utils-Cr4FxJDl.js} +114 -58
  40. package/dist/setup-utils-Cr4FxJDl.js.map +1 -0
  41. package/dist/{start-tui-BRvm5VP9.js → start-tui--E4PXdwG.js} +323 -134
  42. package/dist/start-tui--E4PXdwG.js.map +1 -0
  43. package/dist/{steps-DA4uvSbg.js → steps-CxUxdK4V.js} +6 -6
  44. package/dist/{steps-DA4uvSbg.js.map → steps-CxUxdK4V.js.map} +1 -1
  45. package/dist/telemetry-BS7yw3TP.js +68 -0
  46. package/dist/telemetry-BS7yw3TP.js.map +1 -0
  47. package/dist/{urls-B66Ib2jT.js → urls-BW23_XbC.js} +2 -2
  48. package/dist/{urls-B66Ib2jT.js.map → urls-BW23_XbC.js.map} +1 -1
  49. package/dist/{wizard-abort-gMB1eV6T.js → wizard-abort-BPsnXKY5.js} +1 -1
  50. package/dist/{wizard-abort-D1_DnFjm.js → wizard-abort-E66_R4S7.js} +3 -3
  51. package/dist/{wizard-abort-D1_DnFjm.js.map → wizard-abort-E66_R4S7.js.map} +1 -1
  52. package/dist/wizard-session-G3VWD6hv.js.map +1 -1
  53. package/dist/wizard-ui-YdGFRyu_.js.map +1 -1
  54. package/package.json +1 -1
  55. package/dist/OutroScreen-CqF6SdBo.js.map +0 -1
  56. package/dist/add-mcp-server-to-clients-DQHGhzt6.js.map +0 -1
  57. package/dist/analytics-Bl5DPj_0.js.map +0 -1
  58. package/dist/debug-h7Z9zEbD.js.map +0 -1
  59. package/dist/playground-DCVaVeVD.js.map +0 -1
  60. package/dist/posthog-integration-ChdwFPMj.js.map +0 -1
  61. package/dist/registry-VSSRH3sU.js.map +0 -1
  62. package/dist/setup-utils-BfV4pydt.js.map +0 -1
  63. package/dist/start-tui-BRvm5VP9.js.map +0 -1
  64. package/dist/telemetry-BRAonUea.js +0 -13
  65. package/dist/telemetry-BRAonUea.js.map +0 -1
@@ -1,14 +1,16 @@
1
- import { g as SERVICE_LABELS, s as logToFile } from "./debug-h7Z9zEbD.js";
1
+ import { M as POSTHOG_APP_URL, Q as getSkillsBaseUrl, g as SERVICE_LABELS, s as logToFile, y as getBlockingServiceKeys } from "./debug-Ckumcrye.js";
2
2
  import { n as isTaskStatus } from "./wizard-ui-YdGFRyu_.js";
3
- import { r as sessionProperties, t as analytics } from "./analytics-Bl5DPj_0.js";
4
- import { i as fetchSlackConnected } from "./api-DuA0_88V.js";
3
+ import { r as sessionProperties, t as analytics } from "./analytics-BZv-cKyn.js";
4
+ import { i as withUtm, n as openTrackedLink } from "./telemetry-BS7yw3TP.js";
5
+ import { n as getCloudUrlFromRegion } from "./urls-BW23_XbC.js";
6
+ import { a as fetchUserData, i as fetchSlackConnected } from "./api-BkLZ8BWm.js";
5
7
  import { i as buildSession } from "./wizard-session-G3VWD6hv.js";
6
- import { y as AUDIT_SEVERITY_STYLE } from "./agent-interface-DE7txTqh.js";
7
- import { c as computeVisibleRange, d as isObjectBlock, f as Colors, l as isClearBlock, p as Icons, s as TextBlock, u as isLinesBlock } from "./posthog-integration-ChdwFPMj.js";
8
+ import { m as fetchSkillMenu, y as AUDIT_SEVERITY_STYLE } from "./agent-interface-BPCzPvK-.js";
9
+ import { c as computeVisibleRange, d as isObjectBlock, f as Colors, l as isClearBlock, p as Icons, s as TextBlock, u as isLinesBlock } from "./posthog-integration-COcPewVt.js";
8
10
  import { a as getProgramConfig, i as Program, l as getKindMeta, r as PROGRAM_REGISTRY } from "./bin.js";
9
11
  import { n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-BNWIWzjc.js";
10
- import * as fs$1 from "fs";
11
12
  import opn from "opn";
13
+ import * as fs$1 from "fs";
12
14
  import { Box, Text, measureElement, useInput, useStdout } from "ink";
13
15
  import { Component, Fragment, createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
14
16
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -41,9 +43,40 @@ function createProgramSequence(steps) {
41
43
  return entries;
42
44
  }
43
45
  //#endregion
46
+ //#region src/lib/programs/ai-opt-in-gate.ts
47
+ /** Step id — also the ScreenId.AiOptIn enum value in screen-sequences. */
48
+ const AI_OPT_IN_STEP_ID = "ai-opt-in";
49
+ function aiApproved(session) {
50
+ return !!session.apiUser?.organization?.is_ai_data_processing_approved;
51
+ }
52
+ /**
53
+ * Returns the program's steps with the AI opt-in gate injected after
54
+ * `auth`. Programs with `requiresAi: false` or no auth step pass
55
+ * through unchanged — without auth, `apiUser` would never be populated
56
+ * for evaluation anyway.
57
+ */
58
+ function withAiOptInGate(config) {
59
+ if (config.requiresAi === false) return config.steps;
60
+ const authIdx = config.steps.findIndex((s) => s.id === "auth");
61
+ if (authIdx === -1) return config.steps;
62
+ const gateStep = {
63
+ id: AI_OPT_IN_STEP_ID,
64
+ label: "AI opt-in check",
65
+ screenId: AI_OPT_IN_STEP_ID,
66
+ show: (session) => !session.ci && session.apiUser != null && !aiApproved(session),
67
+ isComplete: (session) => session.ci || aiApproved(session),
68
+ gate: (session) => session.ci || aiApproved(session)
69
+ };
70
+ return [
71
+ ...config.steps.slice(0, authIdx + 1),
72
+ gateStep,
73
+ ...config.steps.slice(authIdx + 1)
74
+ ];
75
+ }
76
+ //#endregion
44
77
  //#region src/ui/tui/screen-sequences.ts
45
78
  /** All program screen sequences keyed by program id. */
46
- const PROGRAM_SEQUENCES = Object.fromEntries(PROGRAM_REGISTRY.map((c) => [c.id, createProgramSequence(c.steps)]));
79
+ const PROGRAM_SEQUENCES = Object.fromEntries(PROGRAM_REGISTRY.map((c) => [c.id, createProgramSequence(withAiOptInGate(c))]));
47
80
  //#endregion
48
81
  //#region src/ui/tui/router.ts
49
82
  var WizardRouter = class {
@@ -128,6 +161,40 @@ var WizardRouter = class {
128
161
  * the cap is tied to the window it feeds.
129
162
  */
130
163
  const MAX_STATUS_MESSAGES = 10;
164
+ /**
165
+ * Fired once per blocked readiness result, so we can quantify how often
166
+ * the wizard refuses to start and — crucially — split that between
167
+ * confirmed PostHog outages and probe-level reachability failures that
168
+ * are most likely the user's network. Helps us decide whether the
169
+ * health-check UX is over-firing.
170
+ */
171
+ function captureHealthCheckBlocked(result) {
172
+ try {
173
+ const health = result.health;
174
+ const blockingKeys = getBlockingServiceKeys(health);
175
+ const blockingStatuses = blockingKeys.map((k) => health[k]?.status);
176
+ const allNoConnection = blockingStatuses.length > 0 && blockingStatuses.every((s) => s === "no-connection");
177
+ const decision = blockingKeys.length === 1 && blockingKeys[0] === "githubReleases" ? "github-releases-down" : allNoConnection ? "no-connection" : "confirmed-outage";
178
+ const posthogStatus = health.posthogOverall?.status;
179
+ const retriesUsed = Math.max(0, ...[
180
+ "llmGateway",
181
+ "mcp",
182
+ "githubReleases"
183
+ ].map((k) => {
184
+ const m = (health[k]?.rawIndicator ?? "").match(/attempts=(\d+)/);
185
+ return m ? Number(m[1]) - 1 : 0;
186
+ }));
187
+ analytics.wizardCapture("health check blocked", {
188
+ decision,
189
+ blocking_keys: blockingKeys,
190
+ posthog_status_reachable: posthogStatus !== "no-connection",
191
+ posthog_status_reports_incident: posthogStatus === "down" || posthogStatus === "degraded",
192
+ retries_used: retriesUsed
193
+ });
194
+ } catch (err) {
195
+ logToFile(`[health-checks] failed to capture analytics: ${err instanceof Error ? err.message : String(err)}`);
196
+ }
197
+ }
131
198
  var WizardStore = class {
132
199
  $session = map(buildSession({}));
133
200
  $statusMessages = atom([]);
@@ -162,9 +229,15 @@ var WizardStore = class {
162
229
  }
163
230
  /**
164
231
  * Scan program steps for gate predicates and create gate promises.
232
+ *
233
+ * Steps are wrapped with withAiOptInGate so the injected ai-opt-in
234
+ * step's gate registers here — the agent runner awaits it (via
235
+ * WizardUI.waitForAiOptIn) before any source leaves the machine.
236
+ * Same wrapper screen-sequences.ts uses, so the gate and its screen
237
+ * can't drift apart.
165
238
  */
166
239
  _initFromProgram(program) {
167
- const steps = getProgramConfig(program).steps;
240
+ const steps = withAiOptInGate(getProgramConfig(program));
168
241
  for (const step of steps) if (step.gate) {
169
242
  let resolve;
170
243
  const promise = new Promise((r) => {
@@ -210,6 +283,7 @@ var WizardStore = class {
210
283
  setFrameworkContext: (k, v) => this.setFrameworkContext(k, v),
211
284
  setFrameworkConfig: (i, c) => this.setFrameworkConfig(i, c),
212
285
  setDetectedFramework: (l) => this.setDetectedFramework(l),
286
+ setSkillId: (id) => this.setSkillId(id),
213
287
  setUnsupportedVersion: (info) => this.setUnsupportedVersion(info),
214
288
  addDiscoveredFeature: (f) => this.addDiscoveredFeature(f),
215
289
  setDetectionComplete: () => this.setDetectionComplete()
@@ -312,6 +386,10 @@ var WizardStore = class {
312
386
  this.$session.setKey("detectedFrameworkLabel", label);
313
387
  this.emitChange();
314
388
  }
389
+ setSkillId(skillId) {
390
+ this.$session.setKey("skillId", skillId);
391
+ this.emitChange();
392
+ }
315
393
  setUnsupportedVersion(info) {
316
394
  this.$session.setKey("unsupportedVersion", info);
317
395
  this.emitChange();
@@ -326,6 +404,7 @@ var WizardStore = class {
326
404
  }
327
405
  setReadinessResult(result) {
328
406
  this.$session.setKey("readinessResult", result);
407
+ if (result && result.decision === "no") captureHealthCheckBlocked(result);
329
408
  this.emitChange();
330
409
  }
331
410
  /** User dismissed the blocking outage screen. Gate resolves via _checkGates(). */
@@ -456,6 +535,10 @@ var WizardStore = class {
456
535
  this.$session.setKey("authErrorDetail", detail ?? null);
457
536
  this.pushOverlay("auth-error");
458
537
  }
538
+ /** Push the session-timeout overlay (no dismiss — user must exit). */
539
+ showSessionTimeout() {
540
+ this.pushOverlay("session-timeout");
541
+ }
459
542
  addDiscoveredFeature(feature) {
460
543
  if (!this.session.discoveredFeatures.includes(feature)) {
461
544
  this.session.discoveredFeatures.push(feature);
@@ -584,6 +667,7 @@ var WizardStore = class {
584
667
  _detectTransition() {
585
668
  const next = this.router.resolve(this.session);
586
669
  const prev = this._lastScreen;
670
+ if (next !== prev) analytics.setTag("$screen_name", next);
587
671
  if (prev !== null && next !== prev) {
588
672
  const hooks = this._enterScreenHooks.get(next);
589
673
  if (hooks) for (const fn of hooks) fn();
@@ -2388,7 +2472,7 @@ const TIPS = [
2388
2472
  id: "slack",
2389
2473
  title: "Use PostHog in Slack",
2390
2474
  description: "Connect the PostHog Slack app to analyze data and ship product changes — deploy flags, open PRs, run queries — just by tagging @PostHog:",
2391
- url: "https://posthog.com/slack-app"
2475
+ url: "https://posthog.com/slack"
2392
2476
  },
2393
2477
  {
2394
2478
  id: "stripe",
@@ -2495,6 +2579,10 @@ function statusIcon(status) {
2495
2579
  icon: Icons.squareFilled,
2496
2580
  color: "#DC9300"
2497
2581
  };
2582
+ case "no-connection": return {
2583
+ icon: Icons.squareFilled,
2584
+ color: "gray"
2585
+ };
2498
2586
  case "healthy": return {
2499
2587
  icon: Icons.check,
2500
2588
  color: "green"
@@ -3810,10 +3898,10 @@ var neutralCrossSell = [{
3810
3898
  "description": "Built-in error tracking — no separate tool."
3811
3899
  }];
3812
3900
  var slackApp = {
3813
- "learnMoreUrl": "https://posthog.com/slack-app",
3901
+ "learnMoreUrl": "https://posthog.com/slack",
3814
3902
  "setupUrl": "https://app.posthog.com/settings/project-integrations#integration-slack",
3815
- "headline": "Take PostHog to Slack",
3816
- "pitch": "You can also analyze product data and ship changes.",
3903
+ "headline": "@PostHog in Slack",
3904
+ "pitch": "Ask about your product data, debug issues, and generate PRs without leaving the thread.",
3817
3905
  "capabilities": ["Tag @PostHog with a bug, edit, or a feature idea. It will spin up a sandboxed environment, plan, edit files, run tests, and open a draft PR.", "Tag @PostHog with any data question. It's the same SQL-writing, statistically-minded assistant as PostHog AI, but it responds where you send work memes."]
3818
3906
  };
3819
3907
  //#endregion
@@ -4018,25 +4106,6 @@ function getSlackAppCard() {
4018
4106
  * forces a successful login first). A defensive throw protects the
4019
4107
  * Running useEffect against a state-machine bug.
4020
4108
  */
4021
- var Phase = /* @__PURE__ */ function(Phase) {
4022
- Phase["Choose"] = "choose";
4023
- Phase["Authenticating"] = "authenticating";
4024
- Phase["Greeting"] = "greeting";
4025
- Phase["PromptPicker"] = "prompt-picker";
4026
- Phase["Running"] = "running";
4027
- Phase["FollowUp"] = "follow-up";
4028
- /** Final beat on every dismissal — reminds the user how to keep
4029
- * talking to PostHog after the tutorial ends. */
4030
- Phase["Goodbye"] = "goodbye";
4031
- Phase["Done"] = "done";
4032
- return Phase;
4033
- }(Phase || {});
4034
- var ChoiceValue$1 = /* @__PURE__ */ function(ChoiceValue) {
4035
- ChoiceValue["Login"] = "login";
4036
- ChoiceValue["ConnectSlack"] = "connect-slack";
4037
- ChoiceValue["Exit"] = "exit";
4038
- return ChoiceValue;
4039
- }(ChoiceValue$1 || {});
4040
4109
  const MAX_PROMPT_RUNS = 5;
4041
4110
  const FOLLOW_UP_DELAY_MS = 3e3;
4042
4111
  const McpSuggestedPromptsScreen = ({ store, services }) => {
@@ -4198,7 +4267,7 @@ const McpSuggestedPromptsScreen = ({ store, services }) => {
4198
4267
  setPhase(session.credentials ? "greeting" : "authenticating");
4199
4268
  } else if (choice === "connect-slack") {
4200
4269
  analytics.wizardCapture("mcp suggested prompts choose", { choice: "connect-slack" });
4201
- opn(getSlackAppCard().setupUrl, { wait: false }).catch(() => {});
4270
+ openTrackedLink(getSlackAppCard().setupUrl, "mcp-prompts-slack-setup");
4202
4271
  } else {
4203
4272
  analytics.wizardCapture("mcp suggested prompts choose", { choice: "exit" });
4204
4273
  enterGoodbye();
@@ -5646,8 +5715,9 @@ const AUDIT_3000_AREA_SLIDES = [
5646
5715
  //#region src/ui/tui/screens/SlackConnectScreen.tsx
5647
5716
  /**
5648
5717
  * SlackConnectScreen — the dedicated "Connect Slack" step shown after the
5649
- * MCP tutorial (`wizard mcp tutorial`) and after a successful install
5650
- * (`wizard mcp add`).
5718
+ * MCP tutorial (`wizard mcp tutorial`), after a successful install
5719
+ * (`wizard mcp add`), at the end of the integration flow, and as the whole
5720
+ * program in the standalone `wizard slack` flow.
5651
5721
  *
5652
5722
  * Presents the PostHog Slack app plus role-tailored use-cases. The copy
5653
5723
  * adapts to whether Slack is already connected (polled while the screen
@@ -5660,22 +5730,36 @@ const AUDIT_3000_AREA_SLIDES = [
5660
5730
  * who already have it aren't nagged.
5661
5731
  * "Skip" / "Done" / esc dismiss the step (`slackStepDismissed`) and let
5662
5732
  * the router advance to exit.
5733
+ *
5734
+ * The mcp and integration flows arrive here already authenticated. In the
5735
+ * standalone `wizard slack` flow the program's `onInit` runs the OAuth
5736
+ * while this screen renders the auth-wait state.
5663
5737
  */
5664
- var ChoiceValue = /* @__PURE__ */ function(ChoiceValue) {
5665
- ChoiceValue["Open"] = "open";
5666
- ChoiceValue["Skip"] = "skip";
5667
- return ChoiceValue;
5668
- }(ChoiceValue || {});
5669
5738
  const POLL_INTERVAL_MS = 3e3;
5670
5739
  const SlackConnectScreen = ({ store }) => {
5671
5740
  useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
5672
5741
  const role = store.session.roleAtOrganization;
5673
5742
  const slack = getSlackAppCard();
5674
- useEffect(() => {
5675
- analytics.wizardCapture("slack connect shown", { role });
5676
- }, []);
5677
- const connected = store.session.slackConnected === true;
5743
+ const setupUrl = withUtm(slack.setupUrl, "slack-connect-setup");
5744
+ const learnMoreUrl = withUtm(slack.learnMoreUrl, "slack-connect-learn-more");
5678
5745
  const credentials = store.session.credentials;
5746
+ const awaitingLogin = store.router.activeProgram === Program.SlackConnect && !credentials;
5747
+ const connectedState = store.session.slackConnected;
5748
+ const connected = connectedState === true;
5749
+ const known = connectedState !== null || !credentials && !awaitingLogin;
5750
+ const impressionFired = useRef(false);
5751
+ useEffect(() => {
5752
+ if (!known || impressionFired.current) return;
5753
+ impressionFired.current = true;
5754
+ analytics.wizardCapture("slack connect shown", {
5755
+ role,
5756
+ already_connected: connected
5757
+ });
5758
+ }, [
5759
+ known,
5760
+ connected,
5761
+ role
5762
+ ]);
5679
5763
  useEffect(() => {
5680
5764
  if (!credentials || connected) return;
5681
5765
  let cancelled = false;
@@ -5684,10 +5768,16 @@ const SlackConnectScreen = ({ store }) => {
5684
5768
  const check = () => {
5685
5769
  fetchSlackConnected(credentials.accessToken, credentials.projectId, credentials.host, controller.signal).then((isConnected) => {
5686
5770
  if (cancelled) return;
5687
- if (isConnected) store.setSlackConnected(true);
5688
- else timer = setTimeout(check, POLL_INTERVAL_MS);
5771
+ if (isConnected) {
5772
+ if (store.session.slackConnected === false) analytics.wizardCapture("slack connect completed", { role });
5773
+ store.setSlackConnected(true);
5774
+ } else {
5775
+ if (store.session.slackConnected === null) store.setSlackConnected(false);
5776
+ timer = setTimeout(check, POLL_INTERVAL_MS);
5777
+ }
5689
5778
  }).catch((err) => {
5690
5779
  if (cancelled) return;
5780
+ if (store.session.slackConnected === null) store.setSlackConnected(false);
5691
5781
  analytics.captureException(err instanceof Error ? err : new Error(String(err)), { step: "slack_connected_check" });
5692
5782
  });
5693
5783
  };
@@ -5703,7 +5793,7 @@ const SlackConnectScreen = ({ store }) => {
5703
5793
  store
5704
5794
  ]);
5705
5795
  const dismiss = () => {
5706
- analytics.wizardCapture("slack connect skipped", {
5796
+ analytics.wizardCapture(connected ? "slack connect done" : "slack connect skipped", {
5707
5797
  role,
5708
5798
  connected
5709
5799
  });
@@ -5712,7 +5802,7 @@ const SlackConnectScreen = ({ store }) => {
5712
5802
  const handleSelect = (value) => {
5713
5803
  if ((Array.isArray(value) ? value[0] : value) === "open") {
5714
5804
  analytics.wizardCapture("slack connect opened", { role });
5715
- opn(slack.setupUrl, { wait: false }).catch(() => {});
5805
+ openTrackedLink(setupUrl, "slack-connect-setup");
5716
5806
  return;
5717
5807
  }
5718
5808
  dismiss();
@@ -5723,6 +5813,32 @@ const SlackConnectScreen = ({ store }) => {
5723
5813
  action: connected ? "done" : "skip",
5724
5814
  handler: () => dismiss()
5725
5815
  }]);
5816
+ if (awaitingLogin) return /* @__PURE__ */ jsxs(Box, {
5817
+ flexDirection: "column",
5818
+ flexGrow: 1,
5819
+ marginTop: 1,
5820
+ children: [/* @__PURE__ */ jsx(LoadingBox, { message: "Waiting for authentication..." }), store.session.loginUrl && /* @__PURE__ */ jsx(Box, {
5821
+ marginTop: 1,
5822
+ flexDirection: "column",
5823
+ children: /* @__PURE__ */ jsxs(Text, { children: [
5824
+ /* @__PURE__ */ jsx(Text, {
5825
+ dimColor: true,
5826
+ children: "If the browser didn't open, copy and paste:"
5827
+ }),
5828
+ "\n\n",
5829
+ /* @__PURE__ */ jsx(Text, {
5830
+ color: "cyan",
5831
+ children: store.session.loginUrl
5832
+ })
5833
+ ] })
5834
+ })]
5835
+ });
5836
+ if (credentials && connectedState === null) return /* @__PURE__ */ jsx(Box, {
5837
+ flexDirection: "column",
5838
+ flexGrow: 1,
5839
+ marginTop: 1,
5840
+ children: /* @__PURE__ */ jsx(LoadingBox, { message: "Checking for an existing Slack connection..." })
5841
+ });
5726
5842
  return /* @__PURE__ */ jsx(Box, {
5727
5843
  flexDirection: "column",
5728
5844
  flexGrow: 1,
@@ -5761,13 +5877,13 @@ const SlackConnectScreen = ({ store }) => {
5761
5877
  dimColor: true,
5762
5878
  children: ["Connect it: ", /* @__PURE__ */ jsx(Text, {
5763
5879
  color: "cyan",
5764
- children: slack.setupUrl
5880
+ children: setupUrl
5765
5881
  })]
5766
5882
  }), /* @__PURE__ */ jsxs(Text, {
5767
5883
  dimColor: true,
5768
5884
  children: ["Learn more: ", /* @__PURE__ */ jsx(Text, {
5769
5885
  color: "cyan",
5770
- children: slack.learnMoreUrl
5886
+ children: learnMoreUrl
5771
5887
  })]
5772
5888
  })]
5773
5889
  }),
@@ -5781,7 +5897,7 @@ const SlackConnectScreen = ({ store }) => {
5781
5897
  label: "Open Slack setup",
5782
5898
  value: "open"
5783
5899
  }, {
5784
- label: "Skip",
5900
+ label: "Skip / Continue",
5785
5901
  value: "skip"
5786
5902
  }],
5787
5903
  onSelect: handleSelect
@@ -5879,7 +5995,7 @@ const OutroScreen = ({ store }) => {
5879
5995
  " ",
5880
5996
  /* @__PURE__ */ jsx(Text, {
5881
5997
  color: "cyan",
5882
- children: outroData.dashboardUrl
5998
+ children: withUtm(outroData.dashboardUrl, "outro-dashboard")
5883
5999
  })
5884
6000
  ] })
5885
6001
  }),
@@ -5890,23 +6006,27 @@ const OutroScreen = ({ store }) => {
5890
6006
  " ",
5891
6007
  /* @__PURE__ */ jsx(Text, {
5892
6008
  color: "cyan",
5893
- children: outroData.notebookUrl
6009
+ children: withUtm(outroData.notebookUrl, "outro-notebook")
5894
6010
  })
5895
6011
  ] })
5896
6012
  }),
5897
6013
  outroData.docsUrl && /* @__PURE__ */ jsx(Box, {
5898
6014
  marginTop: 1,
5899
- children: /* @__PURE__ */ jsxs(Text, { children: ["Learn more: ", /* @__PURE__ */ jsx(Text, {
5900
- color: "cyan",
5901
- children: outroData.docsUrl
5902
- })] })
6015
+ children: /* @__PURE__ */ jsxs(Text, { children: [
6016
+ "Learn more:",
6017
+ " ",
6018
+ /* @__PURE__ */ jsx(Text, {
6019
+ color: "cyan",
6020
+ children: withUtm(outroData.docsUrl, "outro-docs")
6021
+ })
6022
+ ] })
5903
6023
  }),
5904
6024
  outroData.continueUrl && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [
5905
6025
  "Continue onboarding:",
5906
6026
  " ",
5907
6027
  /* @__PURE__ */ jsx(Text, {
5908
6028
  color: "cyan",
5909
- children: outroData.continueUrl
6029
+ children: withUtm(outroData.continueUrl, "outro-continue")
5910
6030
  })
5911
6031
  ] }) }),
5912
6032
  /* @__PURE__ */ jsx(Box, {
@@ -5964,6 +6084,309 @@ const OutroScreen = ({ store }) => {
5964
6084
  });
5965
6085
  };
5966
6086
  //#endregion
5967
- export { SplitView as A, ConfirmationInput as C, useKeyBindings as D, PickerMenu as E, WizardStore as M, ProgressList as O, ModalOverlay as S, useStdoutDimensions as T, HNViewer as _, VisualBox as a, EventPlanViewer as b, TAILORED_ROLES as c, SEVERITY_LABEL as d, SEVERITY_ORDER as f, ContentSequencer as g, LearnCard as h, AUDIT_AREA_SLIDES as i, CardLayout as j, LoadingBox as k, McpScreen as l, TipsCard as m, SlackConnectScreen as n, AuditChecksViewer as o, ServiceHealthList as p, AUDIT_3000_AREA_SLIDES as r, McpSuggestedPromptsScreen as s, OutroScreen as t, IssueTable as u, TabContainer as v, GroupedPickerMenu as w, LogViewer as x, ScreenContainer as y };
6087
+ //#region src/ui/tui/screens/SkillSourceInfo.tsx
6088
+ /**
6089
+ * Shared "Skill: <id> / URL: <downloadUrl>" block for intro screens.
6090
+ *
6091
+ * `useSkillEntry` fetches the entry from the skill menu and re-runs when
6092
+ * `skillId` or `local` change. The previous fetch is cancelled (its result
6093
+ * is ignored) so a session that flips `local=false → true` mid-mount picks
6094
+ * up the right base URL.
6095
+ *
6096
+ * `<SkillSourceInfo>` renders the block, taking the entry as a prop so the
6097
+ * caller can reuse the same hook result for additional UI (e.g. showing
6098
+ * `skillEntry.name`) without invoking the hook twice.
6099
+ */
6100
+ /**
6101
+ * Resolve a session skillId against the skill-menu entries.
6102
+ *
6103
+ * `session.skillId` is seeded with the raw integration id during
6104
+ * detection (e.g. 'python'), but the menu publishes integration skills
6105
+ * under prefixed ids ('integration-python'); frameworks with variants
6106
+ * publish several ('integration-nextjs-app-router', '-pages-router').
6107
+ * Match chain: exact id → `integration-<id>` → unique
6108
+ * `integration-<id>-*` prefix. Ambiguous variants (≥2 prefix matches)
6109
+ * return null — the caller should point at the skills repo instead of
6110
+ * guessing the wrong variant.
6111
+ */
6112
+ function resolveSkillEntry(entries, skillId) {
6113
+ const exact = entries.find((s) => s.id === skillId);
6114
+ if (exact) return exact;
6115
+ const prefixed = entries.find((s) => s.id === `integration-${skillId}`);
6116
+ if (prefixed) return prefixed;
6117
+ const variants = entries.filter((s) => s.id.startsWith(`integration-${skillId}-`));
6118
+ return variants.length === 1 ? variants[0] : null;
6119
+ }
6120
+ function useSkillEntry(skillId, local) {
6121
+ const [skillEntry, setSkillEntry] = useState(null);
6122
+ const [fetchFailed, setFetchFailed] = useState(false);
6123
+ useEffect(() => {
6124
+ if (!skillId) {
6125
+ setFetchFailed(true);
6126
+ return;
6127
+ }
6128
+ let cancelled = false;
6129
+ setSkillEntry(null);
6130
+ setFetchFailed(false);
6131
+ fetchSkillMenu(getSkillsBaseUrl(local)).then((menu) => {
6132
+ if (cancelled) return;
6133
+ if (!menu) {
6134
+ setFetchFailed(true);
6135
+ return;
6136
+ }
6137
+ const match = resolveSkillEntry(Object.values(menu.categories).flat(), skillId);
6138
+ if (match) setSkillEntry(match);
6139
+ else setFetchFailed(true);
6140
+ });
6141
+ return () => {
6142
+ cancelled = true;
6143
+ };
6144
+ }, [skillId, local]);
6145
+ return {
6146
+ skillEntry,
6147
+ fetchFailed
6148
+ };
6149
+ }
6150
+ const SkillSourceInfo = ({ skillId, skillEntry, fetchFailed }) => /* @__PURE__ */ jsxs(Box, {
6151
+ flexDirection: "column",
6152
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
6153
+ "Skill:",
6154
+ " ",
6155
+ /* @__PURE__ */ jsx(Text, {
6156
+ italic: true,
6157
+ color: "cyan",
6158
+ children: skillId ?? "unknown"
6159
+ })
6160
+ ] }), /* @__PURE__ */ jsxs(Text, { children: [
6161
+ "URL:",
6162
+ " ",
6163
+ /* @__PURE__ */ jsx(Text, {
6164
+ color: "cyan",
6165
+ children: skillEntry?.downloadUrl ?? (fetchFailed ? "https://github.com/PostHog/context-mill/releases/latest" : "Loading...")
6166
+ })
6167
+ ] })]
6168
+ });
6169
+ //#endregion
6170
+ //#region src/ui/tui/screens/AiOptInRequiredScreen.tsx
6171
+ /**
6172
+ * AiOptInRequiredScreen — Renders when the wizard authenticates against an
6173
+ * org whose `is_ai_data_processing_approved` is not `true`. Mirrors Max's
6174
+ * strict reading: `null`, `undefined`, and `false` all block.
6175
+ *
6176
+ * Two variants selected from `apiUser.organization.membership_level`:
6177
+ * - Admin (>= 8): can fix it themselves — [O] opens settings in browser.
6178
+ * - Non-admin: needs to escalate — settings URL is displayed prominently
6179
+ * to copy and share with the admin.
6180
+ *
6181
+ * Both variants offer [S] (show skill source for BYOAI), [R] (retry —
6182
+ * re-fetches user data and re-evaluates the gate without restarting), and
6183
+ * [E] (exit).
6184
+ */
6185
+ const ORG_ADMIN_LEVEL = 8;
6186
+ const SETTINGS_PATH = "settings/organization-details";
6187
+ const SETTINGS_ANCHOR = "#organization-ai-consent";
6188
+ const AiOptInRequiredScreen = ({ store }) => {
6189
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
6190
+ const { session } = store;
6191
+ const isAdmin = ((session.apiUser?.organization)?.membership_level ?? 0) >= ORG_ADMIN_LEVEL;
6192
+ const variant = isAdmin ? "admin" : "non-admin";
6193
+ const region = session.region ?? "us";
6194
+ const projectId = session.credentials?.projectId;
6195
+ const settingsUrl = projectId != null ? `${POSTHOG_APP_URL}/project/${projectId}/${SETTINGS_PATH}${SETTINGS_ANCHOR}` : `${POSTHOG_APP_URL}/${SETTINGS_PATH}${SETTINGS_ANCHOR}`;
6196
+ const [showSkill, setShowSkill] = useState(false);
6197
+ const [retrying, setRetrying] = useState(false);
6198
+ const [retryError, setRetryError] = useState(null);
6199
+ const { skillEntry } = useSkillEntry(session.skillId, session.localMcp);
6200
+ useEffect(() => {
6201
+ analytics.wizardCapture("ai opt-in shown", { variant });
6202
+ }, [variant]);
6203
+ const handleOpenSettings = () => {
6204
+ analytics.wizardCapture("ai opt-in action", {
6205
+ variant,
6206
+ action: "open_settings"
6207
+ });
6208
+ opn(settingsUrl, { wait: false }).catch(() => {});
6209
+ };
6210
+ const handleShowSkill = () => {
6211
+ analytics.wizardCapture("ai opt-in action", {
6212
+ variant,
6213
+ action: "show_skill"
6214
+ });
6215
+ setShowSkill(true);
6216
+ };
6217
+ const handleRetry = () => {
6218
+ analytics.wizardCapture("ai opt-in action", {
6219
+ variant,
6220
+ action: "retry"
6221
+ });
6222
+ const accessToken = session.credentials?.accessToken;
6223
+ if (!accessToken) {
6224
+ setRetryError("Missing credentials — cannot retry.");
6225
+ return;
6226
+ }
6227
+ setRetrying(true);
6228
+ setRetryError(null);
6229
+ fetchUserData(accessToken, getCloudUrlFromRegion(region)).then((user) => {
6230
+ store.setApiUser(user);
6231
+ }).catch((err) => {
6232
+ setRetryError(err instanceof Error ? err.message : "Retry failed.");
6233
+ }).finally(() => {
6234
+ setRetrying(false);
6235
+ });
6236
+ };
6237
+ const handleExit = () => {
6238
+ analytics.wizardCapture("ai opt-in action", {
6239
+ variant,
6240
+ action: "exit"
6241
+ });
6242
+ process.exit(0);
6243
+ };
6244
+ useKeyBindings("ai-opt-in", [
6245
+ ...isAdmin ? [{
6246
+ match: ["o", "O"],
6247
+ label: "O",
6248
+ action: "open settings",
6249
+ handler: handleOpenSettings
6250
+ }] : [],
6251
+ {
6252
+ match: ["s", "S"],
6253
+ label: "S",
6254
+ action: "show skill",
6255
+ handler: handleShowSkill
6256
+ },
6257
+ {
6258
+ match: ["r", "R"],
6259
+ label: "R",
6260
+ action: "retry",
6261
+ handler: handleRetry
6262
+ },
6263
+ {
6264
+ match: ["e", "E"],
6265
+ label: "E",
6266
+ action: "exit",
6267
+ handler: handleExit
6268
+ }
6269
+ ]);
6270
+ return /* @__PURE__ */ jsxs(Box, {
6271
+ flexDirection: "column",
6272
+ flexGrow: 1,
6273
+ children: [
6274
+ /* @__PURE__ */ jsxs(Box, {
6275
+ flexDirection: "column",
6276
+ marginBottom: 1,
6277
+ children: [/* @__PURE__ */ jsx(Text, {
6278
+ bold: true,
6279
+ color: Colors.accent,
6280
+ children: "PostHog Setup Wizard"
6281
+ }), session.apiUser?.email && /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
6282
+ color: "green",
6283
+ children: ["✔", " "]
6284
+ }), /* @__PURE__ */ jsxs(Text, { children: ["Authenticated as ", session.apiUser.email] })] })]
6285
+ }),
6286
+ /* @__PURE__ */ jsx(Box, {
6287
+ flexDirection: "column",
6288
+ marginBottom: 1,
6289
+ children: /* @__PURE__ */ jsx(Text, {
6290
+ color: "yellow",
6291
+ bold: true,
6292
+ children: "⚠ PostHog AI services are disabled for your organization"
6293
+ })
6294
+ }),
6295
+ /* @__PURE__ */ jsx(Box, {
6296
+ flexDirection: "column",
6297
+ marginBottom: 1,
6298
+ width: 68,
6299
+ children: isAdmin ? /* @__PURE__ */ jsxs(Text, { children: [
6300
+ "The wizard uses Anthropic Claude. To proceed, enable",
6301
+ " ",
6302
+ /* @__PURE__ */ jsx(Text, {
6303
+ italic: true,
6304
+ children: "\"Enable PostHog features that use third-party AI services\""
6305
+ }),
6306
+ " ",
6307
+ "in your organization settings."
6308
+ ] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Text, { children: [
6309
+ "The wizard uses Anthropic Claude. Your organization admin needs to enable",
6310
+ " ",
6311
+ /* @__PURE__ */ jsx(Text, {
6312
+ italic: true,
6313
+ children: "\"Enable PostHog features that use third-party AI services\""
6314
+ }),
6315
+ " ",
6316
+ "in organization settings."
6317
+ ] }), /* @__PURE__ */ jsx(Box, {
6318
+ marginTop: 1,
6319
+ children: /* @__PURE__ */ jsx(Text, {
6320
+ dimColor: true,
6321
+ children: "Share this link with your admin:"
6322
+ })
6323
+ })] })
6324
+ }),
6325
+ /* @__PURE__ */ jsx(Box, {
6326
+ marginBottom: 1,
6327
+ children: /* @__PURE__ */ jsx(Text, {
6328
+ color: "cyan",
6329
+ children: settingsUrl
6330
+ })
6331
+ }),
6332
+ showSkill && /* @__PURE__ */ jsxs(Box, {
6333
+ marginBottom: 1,
6334
+ flexDirection: "column",
6335
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
6336
+ "Prefer your own AI? Download",
6337
+ " ",
6338
+ skillEntry ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
6339
+ "the ",
6340
+ /* @__PURE__ */ jsx(Text, {
6341
+ bold: true,
6342
+ children: skillEntry.id
6343
+ }),
6344
+ " skill"
6345
+ ] }) : "the skill for your framework",
6346
+ " ",
6347
+ "and run it in your own agent:"
6348
+ ] }), /* @__PURE__ */ jsx(Text, {
6349
+ color: "cyan",
6350
+ children: "https://github.com/PostHog/context-mill/releases/latest"
6351
+ })]
6352
+ }),
6353
+ retrying && /* @__PURE__ */ jsx(Box, {
6354
+ marginBottom: 1,
6355
+ children: /* @__PURE__ */ jsx(LoadingBox, { message: "Re-checking organization settings..." })
6356
+ }),
6357
+ retryError && /* @__PURE__ */ jsx(Box, {
6358
+ marginBottom: 1,
6359
+ children: /* @__PURE__ */ jsx(Text, {
6360
+ color: "red",
6361
+ children: retryError
6362
+ })
6363
+ }),
6364
+ /* @__PURE__ */ jsxs(Box, {
6365
+ flexDirection: "column",
6366
+ marginTop: 1,
6367
+ children: [
6368
+ isAdmin && /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
6369
+ color: Colors.accent,
6370
+ children: "[O]"
6371
+ }), " Open settings in browser"] }),
6372
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
6373
+ color: Colors.accent,
6374
+ children: "[S]"
6375
+ }), " Show how to use your own AI"] }),
6376
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
6377
+ color: Colors.accent,
6378
+ children: "[R]"
6379
+ }), " Retry (after the toggle is enabled)"] }),
6380
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
6381
+ color: Colors.accent,
6382
+ children: "[E]"
6383
+ }), " Exit"] })
6384
+ ]
6385
+ })
6386
+ ]
6387
+ });
6388
+ };
6389
+ //#endregion
6390
+ export { useKeyBindings as A, EventPlanViewer as C, GroupedPickerMenu as D, ConfirmationInput as E, WizardStore as F, LoadingBox as M, SplitView as N, useStdoutDimensions as O, CardLayout as P, ScreenContainer as S, ModalOverlay as T, TipsCard as _, SlackConnectScreen as a, HNViewer as b, VisualBox as c, TAILORED_ROLES as d, McpScreen as f, ServiceHealthList as g, SEVERITY_ORDER as h, OutroScreen as i, ProgressList as j, PickerMenu as k, AuditChecksViewer as l, SEVERITY_LABEL as m, SkillSourceInfo as n, AUDIT_3000_AREA_SLIDES as o, IssueTable as p, useSkillEntry as r, AUDIT_AREA_SLIDES as s, AiOptInRequiredScreen as t, McpSuggestedPromptsScreen as u, LearnCard as v, LogViewer as w, TabContainer as x, ContentSequencer as y };
5968
6391
 
5969
- //# sourceMappingURL=OutroScreen-CqF6SdBo.js.map
6392
+ //# sourceMappingURL=AiOptInRequiredScreen-B8mgZbe5.js.map