@posthog/wizard 2.21.0 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +14 -1
  2. package/dist/{OutroScreen-CqF6SdBo.js → AiOptInRequiredScreen-N6L80szR.js} +443 -59
  3. package/dist/AiOptInRequiredScreen-N6L80szR.js.map +1 -0
  4. package/dist/{add-mcp-server-to-clients-DQHGhzt6.js → add-mcp-server-to-clients-DqHCkHqM.js} +12 -10
  5. package/dist/add-mcp-server-to-clients-DqHCkHqM.js.map +1 -0
  6. package/dist/{agent-interface-DE7txTqh.js → agent-interface-DZmVoik2.js} +5 -5
  7. package/dist/{agent-interface-DE7txTqh.js.map → agent-interface-DZmVoik2.js.map} +1 -1
  8. package/dist/{agent-runner-DUZ5OD6e.js → agent-runner-CGFUXR97.js} +13 -9
  9. package/dist/{agent-runner-DUZ5OD6e.js.map → agent-runner-CGFUXR97.js.map} +1 -1
  10. package/dist/{analytics-Bl5DPj_0.js → analytics-C_lVPZQT.js} +28 -4
  11. package/dist/analytics-C_lVPZQT.js.map +1 -0
  12. package/dist/{api-DuA0_88V.js → api-QI1lO_Bz.js} +3 -3
  13. package/dist/{api-DuA0_88V.js.map → api-QI1lO_Bz.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-CXkKR4A-.js} +4 -4
  17. package/dist/{ci-install-BnOYI4mZ.js.map → ci-install-CXkKR4A-.js.map} +1 -1
  18. package/dist/{debug-h7Z9zEbD.js → debug-D8QAez2V.js} +58 -13
  19. package/dist/debug-D8QAez2V.js.map +1 -0
  20. package/dist/{debug-BVC48wlb.js → debug-lPpecs0J.js} +1 -1
  21. package/dist/{environment-uaLmtlH_.js → environment-CMmzgZkN.js} +3 -3
  22. package/dist/{environment-uaLmtlH_.js.map → environment-CMmzgZkN.js.map} +1 -1
  23. package/dist/{interactive-CW5gjyDd.js → interactive-Bu8YchJG.js} +2 -2
  24. package/dist/{interactive-CW5gjyDd.js.map → interactive-Bu8YchJG.js.map} +1 -1
  25. package/dist/{mcp-prompt-streaming-DMDwaark.js → mcp-prompt-streaming-mYw2LPZZ.js} +4 -4
  26. package/dist/{mcp-prompt-streaming-DMDwaark.js.map → mcp-prompt-streaming-mYw2LPZZ.js.map} +1 -1
  27. package/dist/{non-interactive-DJrVQ4nS.js → non-interactive-De3tJM1y.js} +2 -2
  28. package/dist/{non-interactive-DJrVQ4nS.js.map → non-interactive-De3tJM1y.js.map} +1 -1
  29. package/dist/{package-manager-DCUBRbr-.js → package-manager-BVJnbp1u.js} +2 -2
  30. package/dist/{package-manager-DCUBRbr-.js.map → package-manager-BVJnbp1u.js.map} +1 -1
  31. package/dist/{playground-DCVaVeVD.js → playground-wyoq1yIH.js} +82 -4
  32. package/dist/playground-wyoq1yIH.js.map +1 -0
  33. package/dist/{posthog-integration-ChdwFPMj.js → posthog-integration-mrMF-2IP.js} +48 -16
  34. package/dist/posthog-integration-mrMF-2IP.js.map +1 -0
  35. package/dist/{provisioning-GeMkBMSR.js → provisioning-4zipVpbq.js} +3 -3
  36. package/dist/{provisioning-GeMkBMSR.js.map → provisioning-4zipVpbq.js.map} +1 -1
  37. package/dist/{registry-VSSRH3sU.js → registry-BGUo4PlM.js} +7 -20
  38. package/dist/registry-BGUo4PlM.js.map +1 -0
  39. package/dist/{setup-utils-BfV4pydt.js → setup-utils-DmhPyWkp.js} +114 -58
  40. package/dist/setup-utils-DmhPyWkp.js.map +1 -0
  41. package/dist/{start-tui-BRvm5VP9.js → start-tui-DaQiY_EB.js} +310 -130
  42. package/dist/start-tui-DaQiY_EB.js.map +1 -0
  43. package/dist/{steps-DA4uvSbg.js → steps-CrUceWR5.js} +6 -6
  44. package/dist/{steps-DA4uvSbg.js.map → steps-CrUceWR5.js.map} +1 -1
  45. package/dist/telemetry-CCVjGq7l.js +68 -0
  46. package/dist/telemetry-CCVjGq7l.js.map +1 -0
  47. package/dist/{urls-B66Ib2jT.js → urls-BNFpfcN8.js} +2 -2
  48. package/dist/{urls-B66Ib2jT.js.map → urls-BNFpfcN8.js.map} +1 -1
  49. package/dist/{wizard-abort-D1_DnFjm.js → wizard-abort-BmYb0bG2.js} +3 -3
  50. package/dist/{wizard-abort-D1_DnFjm.js.map → wizard-abort-BmYb0bG2.js.map} +1 -1
  51. package/dist/{wizard-abort-gMB1eV6T.js → wizard-abort-Bp2yxYAy.js} +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 } from "./debug-D8QAez2V.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-C_lVPZQT.js";
4
+ import { i as withUtm, n as openTrackedLink } from "./telemetry-CCVjGq7l.js";
5
+ import { n as getCloudUrlFromRegion } from "./urls-BNFpfcN8.js";
6
+ import { a as fetchUserData, i as fetchSlackConnected } from "./api-QI1lO_Bz.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-DZmVoik2.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-mrMF-2IP.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 {
@@ -162,9 +195,15 @@ var WizardStore = class {
162
195
  }
163
196
  /**
164
197
  * Scan program steps for gate predicates and create gate promises.
198
+ *
199
+ * Steps are wrapped with withAiOptInGate so the injected ai-opt-in
200
+ * step's gate registers here — the agent runner awaits it (via
201
+ * WizardUI.waitForAiOptIn) before any source leaves the machine.
202
+ * Same wrapper screen-sequences.ts uses, so the gate and its screen
203
+ * can't drift apart.
165
204
  */
166
205
  _initFromProgram(program) {
167
- const steps = getProgramConfig(program).steps;
206
+ const steps = withAiOptInGate(getProgramConfig(program));
168
207
  for (const step of steps) if (step.gate) {
169
208
  let resolve;
170
209
  const promise = new Promise((r) => {
@@ -210,6 +249,7 @@ var WizardStore = class {
210
249
  setFrameworkContext: (k, v) => this.setFrameworkContext(k, v),
211
250
  setFrameworkConfig: (i, c) => this.setFrameworkConfig(i, c),
212
251
  setDetectedFramework: (l) => this.setDetectedFramework(l),
252
+ setSkillId: (id) => this.setSkillId(id),
213
253
  setUnsupportedVersion: (info) => this.setUnsupportedVersion(info),
214
254
  addDiscoveredFeature: (f) => this.addDiscoveredFeature(f),
215
255
  setDetectionComplete: () => this.setDetectionComplete()
@@ -312,6 +352,10 @@ var WizardStore = class {
312
352
  this.$session.setKey("detectedFrameworkLabel", label);
313
353
  this.emitChange();
314
354
  }
355
+ setSkillId(skillId) {
356
+ this.$session.setKey("skillId", skillId);
357
+ this.emitChange();
358
+ }
315
359
  setUnsupportedVersion(info) {
316
360
  this.$session.setKey("unsupportedVersion", info);
317
361
  this.emitChange();
@@ -456,6 +500,10 @@ var WizardStore = class {
456
500
  this.$session.setKey("authErrorDetail", detail ?? null);
457
501
  this.pushOverlay("auth-error");
458
502
  }
503
+ /** Push the session-timeout overlay (no dismiss — user must exit). */
504
+ showSessionTimeout() {
505
+ this.pushOverlay("session-timeout");
506
+ }
459
507
  addDiscoveredFeature(feature) {
460
508
  if (!this.session.discoveredFeatures.includes(feature)) {
461
509
  this.session.discoveredFeatures.push(feature);
@@ -584,6 +632,7 @@ var WizardStore = class {
584
632
  _detectTransition() {
585
633
  const next = this.router.resolve(this.session);
586
634
  const prev = this._lastScreen;
635
+ if (next !== prev) analytics.setTag("$screen_name", next);
587
636
  if (prev !== null && next !== prev) {
588
637
  const hooks = this._enterScreenHooks.get(next);
589
638
  if (hooks) for (const fn of hooks) fn();
@@ -2388,7 +2437,7 @@ const TIPS = [
2388
2437
  id: "slack",
2389
2438
  title: "Use PostHog in Slack",
2390
2439
  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"
2440
+ url: "https://posthog.com/slack"
2392
2441
  },
2393
2442
  {
2394
2443
  id: "stripe",
@@ -3810,10 +3859,10 @@ var neutralCrossSell = [{
3810
3859
  "description": "Built-in error tracking — no separate tool."
3811
3860
  }];
3812
3861
  var slackApp = {
3813
- "learnMoreUrl": "https://posthog.com/slack-app",
3862
+ "learnMoreUrl": "https://posthog.com/slack",
3814
3863
  "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.",
3864
+ "headline": "@PostHog in Slack",
3865
+ "pitch": "Ask about your product data, debug issues, and generate PRs without leaving the thread.",
3817
3866
  "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
3867
  };
3819
3868
  //#endregion
@@ -4018,25 +4067,6 @@ function getSlackAppCard() {
4018
4067
  * forces a successful login first). A defensive throw protects the
4019
4068
  * Running useEffect against a state-machine bug.
4020
4069
  */
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
4070
  const MAX_PROMPT_RUNS = 5;
4041
4071
  const FOLLOW_UP_DELAY_MS = 3e3;
4042
4072
  const McpSuggestedPromptsScreen = ({ store, services }) => {
@@ -4198,7 +4228,7 @@ const McpSuggestedPromptsScreen = ({ store, services }) => {
4198
4228
  setPhase(session.credentials ? "greeting" : "authenticating");
4199
4229
  } else if (choice === "connect-slack") {
4200
4230
  analytics.wizardCapture("mcp suggested prompts choose", { choice: "connect-slack" });
4201
- opn(getSlackAppCard().setupUrl, { wait: false }).catch(() => {});
4231
+ openTrackedLink(getSlackAppCard().setupUrl, "mcp-prompts-slack-setup");
4202
4232
  } else {
4203
4233
  analytics.wizardCapture("mcp suggested prompts choose", { choice: "exit" });
4204
4234
  enterGoodbye();
@@ -5646,8 +5676,9 @@ const AUDIT_3000_AREA_SLIDES = [
5646
5676
  //#region src/ui/tui/screens/SlackConnectScreen.tsx
5647
5677
  /**
5648
5678
  * SlackConnectScreen — the dedicated "Connect Slack" step shown after the
5649
- * MCP tutorial (`wizard mcp tutorial`) and after a successful install
5650
- * (`wizard mcp add`).
5679
+ * MCP tutorial (`wizard mcp tutorial`), after a successful install
5680
+ * (`wizard mcp add`), at the end of the integration flow, and as the whole
5681
+ * program in the standalone `wizard slack` flow.
5651
5682
  *
5652
5683
  * Presents the PostHog Slack app plus role-tailored use-cases. The copy
5653
5684
  * adapts to whether Slack is already connected (polled while the screen
@@ -5660,22 +5691,36 @@ const AUDIT_3000_AREA_SLIDES = [
5660
5691
  * who already have it aren't nagged.
5661
5692
  * "Skip" / "Done" / esc dismiss the step (`slackStepDismissed`) and let
5662
5693
  * the router advance to exit.
5694
+ *
5695
+ * The mcp and integration flows arrive here already authenticated. In the
5696
+ * standalone `wizard slack` flow the program's `onInit` runs the OAuth
5697
+ * while this screen renders the auth-wait state.
5663
5698
  */
5664
- var ChoiceValue = /* @__PURE__ */ function(ChoiceValue) {
5665
- ChoiceValue["Open"] = "open";
5666
- ChoiceValue["Skip"] = "skip";
5667
- return ChoiceValue;
5668
- }(ChoiceValue || {});
5669
5699
  const POLL_INTERVAL_MS = 3e3;
5670
5700
  const SlackConnectScreen = ({ store }) => {
5671
5701
  useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
5672
5702
  const role = store.session.roleAtOrganization;
5673
5703
  const slack = getSlackAppCard();
5674
- useEffect(() => {
5675
- analytics.wizardCapture("slack connect shown", { role });
5676
- }, []);
5677
- const connected = store.session.slackConnected === true;
5704
+ const setupUrl = withUtm(slack.setupUrl, "slack-connect-setup");
5705
+ const learnMoreUrl = withUtm(slack.learnMoreUrl, "slack-connect-learn-more");
5678
5706
  const credentials = store.session.credentials;
5707
+ const awaitingLogin = store.router.activeProgram === Program.SlackConnect && !credentials;
5708
+ const connectedState = store.session.slackConnected;
5709
+ const connected = connectedState === true;
5710
+ const known = connectedState !== null || !credentials && !awaitingLogin;
5711
+ const impressionFired = useRef(false);
5712
+ useEffect(() => {
5713
+ if (!known || impressionFired.current) return;
5714
+ impressionFired.current = true;
5715
+ analytics.wizardCapture("slack connect shown", {
5716
+ role,
5717
+ already_connected: connected
5718
+ });
5719
+ }, [
5720
+ known,
5721
+ connected,
5722
+ role
5723
+ ]);
5679
5724
  useEffect(() => {
5680
5725
  if (!credentials || connected) return;
5681
5726
  let cancelled = false;
@@ -5684,10 +5729,16 @@ const SlackConnectScreen = ({ store }) => {
5684
5729
  const check = () => {
5685
5730
  fetchSlackConnected(credentials.accessToken, credentials.projectId, credentials.host, controller.signal).then((isConnected) => {
5686
5731
  if (cancelled) return;
5687
- if (isConnected) store.setSlackConnected(true);
5688
- else timer = setTimeout(check, POLL_INTERVAL_MS);
5732
+ if (isConnected) {
5733
+ if (store.session.slackConnected === false) analytics.wizardCapture("slack connect completed", { role });
5734
+ store.setSlackConnected(true);
5735
+ } else {
5736
+ if (store.session.slackConnected === null) store.setSlackConnected(false);
5737
+ timer = setTimeout(check, POLL_INTERVAL_MS);
5738
+ }
5689
5739
  }).catch((err) => {
5690
5740
  if (cancelled) return;
5741
+ if (store.session.slackConnected === null) store.setSlackConnected(false);
5691
5742
  analytics.captureException(err instanceof Error ? err : new Error(String(err)), { step: "slack_connected_check" });
5692
5743
  });
5693
5744
  };
@@ -5703,7 +5754,7 @@ const SlackConnectScreen = ({ store }) => {
5703
5754
  store
5704
5755
  ]);
5705
5756
  const dismiss = () => {
5706
- analytics.wizardCapture("slack connect skipped", {
5757
+ analytics.wizardCapture(connected ? "slack connect done" : "slack connect skipped", {
5707
5758
  role,
5708
5759
  connected
5709
5760
  });
@@ -5712,7 +5763,7 @@ const SlackConnectScreen = ({ store }) => {
5712
5763
  const handleSelect = (value) => {
5713
5764
  if ((Array.isArray(value) ? value[0] : value) === "open") {
5714
5765
  analytics.wizardCapture("slack connect opened", { role });
5715
- opn(slack.setupUrl, { wait: false }).catch(() => {});
5766
+ openTrackedLink(setupUrl, "slack-connect-setup");
5716
5767
  return;
5717
5768
  }
5718
5769
  dismiss();
@@ -5723,6 +5774,32 @@ const SlackConnectScreen = ({ store }) => {
5723
5774
  action: connected ? "done" : "skip",
5724
5775
  handler: () => dismiss()
5725
5776
  }]);
5777
+ if (awaitingLogin) return /* @__PURE__ */ jsxs(Box, {
5778
+ flexDirection: "column",
5779
+ flexGrow: 1,
5780
+ marginTop: 1,
5781
+ children: [/* @__PURE__ */ jsx(LoadingBox, { message: "Waiting for authentication..." }), store.session.loginUrl && /* @__PURE__ */ jsx(Box, {
5782
+ marginTop: 1,
5783
+ flexDirection: "column",
5784
+ children: /* @__PURE__ */ jsxs(Text, { children: [
5785
+ /* @__PURE__ */ jsx(Text, {
5786
+ dimColor: true,
5787
+ children: "If the browser didn't open, copy and paste:"
5788
+ }),
5789
+ "\n\n",
5790
+ /* @__PURE__ */ jsx(Text, {
5791
+ color: "cyan",
5792
+ children: store.session.loginUrl
5793
+ })
5794
+ ] })
5795
+ })]
5796
+ });
5797
+ if (credentials && connectedState === null) return /* @__PURE__ */ jsx(Box, {
5798
+ flexDirection: "column",
5799
+ flexGrow: 1,
5800
+ marginTop: 1,
5801
+ children: /* @__PURE__ */ jsx(LoadingBox, { message: "Checking for an existing Slack connection..." })
5802
+ });
5726
5803
  return /* @__PURE__ */ jsx(Box, {
5727
5804
  flexDirection: "column",
5728
5805
  flexGrow: 1,
@@ -5761,13 +5838,13 @@ const SlackConnectScreen = ({ store }) => {
5761
5838
  dimColor: true,
5762
5839
  children: ["Connect it: ", /* @__PURE__ */ jsx(Text, {
5763
5840
  color: "cyan",
5764
- children: slack.setupUrl
5841
+ children: setupUrl
5765
5842
  })]
5766
5843
  }), /* @__PURE__ */ jsxs(Text, {
5767
5844
  dimColor: true,
5768
5845
  children: ["Learn more: ", /* @__PURE__ */ jsx(Text, {
5769
5846
  color: "cyan",
5770
- children: slack.learnMoreUrl
5847
+ children: learnMoreUrl
5771
5848
  })]
5772
5849
  })]
5773
5850
  }),
@@ -5781,7 +5858,7 @@ const SlackConnectScreen = ({ store }) => {
5781
5858
  label: "Open Slack setup",
5782
5859
  value: "open"
5783
5860
  }, {
5784
- label: "Skip",
5861
+ label: "Skip / Continue",
5785
5862
  value: "skip"
5786
5863
  }],
5787
5864
  onSelect: handleSelect
@@ -5879,7 +5956,7 @@ const OutroScreen = ({ store }) => {
5879
5956
  " ",
5880
5957
  /* @__PURE__ */ jsx(Text, {
5881
5958
  color: "cyan",
5882
- children: outroData.dashboardUrl
5959
+ children: withUtm(outroData.dashboardUrl, "outro-dashboard")
5883
5960
  })
5884
5961
  ] })
5885
5962
  }),
@@ -5890,23 +5967,27 @@ const OutroScreen = ({ store }) => {
5890
5967
  " ",
5891
5968
  /* @__PURE__ */ jsx(Text, {
5892
5969
  color: "cyan",
5893
- children: outroData.notebookUrl
5970
+ children: withUtm(outroData.notebookUrl, "outro-notebook")
5894
5971
  })
5895
5972
  ] })
5896
5973
  }),
5897
5974
  outroData.docsUrl && /* @__PURE__ */ jsx(Box, {
5898
5975
  marginTop: 1,
5899
- children: /* @__PURE__ */ jsxs(Text, { children: ["Learn more: ", /* @__PURE__ */ jsx(Text, {
5900
- color: "cyan",
5901
- children: outroData.docsUrl
5902
- })] })
5976
+ children: /* @__PURE__ */ jsxs(Text, { children: [
5977
+ "Learn more:",
5978
+ " ",
5979
+ /* @__PURE__ */ jsx(Text, {
5980
+ color: "cyan",
5981
+ children: withUtm(outroData.docsUrl, "outro-docs")
5982
+ })
5983
+ ] })
5903
5984
  }),
5904
5985
  outroData.continueUrl && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [
5905
5986
  "Continue onboarding:",
5906
5987
  " ",
5907
5988
  /* @__PURE__ */ jsx(Text, {
5908
5989
  color: "cyan",
5909
- children: outroData.continueUrl
5990
+ children: withUtm(outroData.continueUrl, "outro-continue")
5910
5991
  })
5911
5992
  ] }) }),
5912
5993
  /* @__PURE__ */ jsx(Box, {
@@ -5964,6 +6045,309 @@ const OutroScreen = ({ store }) => {
5964
6045
  });
5965
6046
  };
5966
6047
  //#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 };
6048
+ //#region src/ui/tui/screens/SkillSourceInfo.tsx
6049
+ /**
6050
+ * Shared "Skill: <id> / URL: <downloadUrl>" block for intro screens.
6051
+ *
6052
+ * `useSkillEntry` fetches the entry from the skill menu and re-runs when
6053
+ * `skillId` or `local` change. The previous fetch is cancelled (its result
6054
+ * is ignored) so a session that flips `local=false → true` mid-mount picks
6055
+ * up the right base URL.
6056
+ *
6057
+ * `<SkillSourceInfo>` renders the block, taking the entry as a prop so the
6058
+ * caller can reuse the same hook result for additional UI (e.g. showing
6059
+ * `skillEntry.name`) without invoking the hook twice.
6060
+ */
6061
+ /**
6062
+ * Resolve a session skillId against the skill-menu entries.
6063
+ *
6064
+ * `session.skillId` is seeded with the raw integration id during
6065
+ * detection (e.g. 'python'), but the menu publishes integration skills
6066
+ * under prefixed ids ('integration-python'); frameworks with variants
6067
+ * publish several ('integration-nextjs-app-router', '-pages-router').
6068
+ * Match chain: exact id → `integration-<id>` → unique
6069
+ * `integration-<id>-*` prefix. Ambiguous variants (≥2 prefix matches)
6070
+ * return null — the caller should point at the skills repo instead of
6071
+ * guessing the wrong variant.
6072
+ */
6073
+ function resolveSkillEntry(entries, skillId) {
6074
+ const exact = entries.find((s) => s.id === skillId);
6075
+ if (exact) return exact;
6076
+ const prefixed = entries.find((s) => s.id === `integration-${skillId}`);
6077
+ if (prefixed) return prefixed;
6078
+ const variants = entries.filter((s) => s.id.startsWith(`integration-${skillId}-`));
6079
+ return variants.length === 1 ? variants[0] : null;
6080
+ }
6081
+ function useSkillEntry(skillId, local) {
6082
+ const [skillEntry, setSkillEntry] = useState(null);
6083
+ const [fetchFailed, setFetchFailed] = useState(false);
6084
+ useEffect(() => {
6085
+ if (!skillId) {
6086
+ setFetchFailed(true);
6087
+ return;
6088
+ }
6089
+ let cancelled = false;
6090
+ setSkillEntry(null);
6091
+ setFetchFailed(false);
6092
+ fetchSkillMenu(getSkillsBaseUrl(local)).then((menu) => {
6093
+ if (cancelled) return;
6094
+ if (!menu) {
6095
+ setFetchFailed(true);
6096
+ return;
6097
+ }
6098
+ const match = resolveSkillEntry(Object.values(menu.categories).flat(), skillId);
6099
+ if (match) setSkillEntry(match);
6100
+ else setFetchFailed(true);
6101
+ });
6102
+ return () => {
6103
+ cancelled = true;
6104
+ };
6105
+ }, [skillId, local]);
6106
+ return {
6107
+ skillEntry,
6108
+ fetchFailed
6109
+ };
6110
+ }
6111
+ const SkillSourceInfo = ({ skillId, skillEntry, fetchFailed }) => /* @__PURE__ */ jsxs(Box, {
6112
+ flexDirection: "column",
6113
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
6114
+ "Skill:",
6115
+ " ",
6116
+ /* @__PURE__ */ jsx(Text, {
6117
+ italic: true,
6118
+ color: "cyan",
6119
+ children: skillId ?? "unknown"
6120
+ })
6121
+ ] }), /* @__PURE__ */ jsxs(Text, { children: [
6122
+ "URL:",
6123
+ " ",
6124
+ /* @__PURE__ */ jsx(Text, {
6125
+ color: "cyan",
6126
+ children: skillEntry?.downloadUrl ?? (fetchFailed ? "https://github.com/PostHog/context-mill/releases/latest" : "Loading...")
6127
+ })
6128
+ ] })]
6129
+ });
6130
+ //#endregion
6131
+ //#region src/ui/tui/screens/AiOptInRequiredScreen.tsx
6132
+ /**
6133
+ * AiOptInRequiredScreen — Renders when the wizard authenticates against an
6134
+ * org whose `is_ai_data_processing_approved` is not `true`. Mirrors Max's
6135
+ * strict reading: `null`, `undefined`, and `false` all block.
6136
+ *
6137
+ * Two variants selected from `apiUser.organization.membership_level`:
6138
+ * - Admin (>= 8): can fix it themselves — [O] opens settings in browser.
6139
+ * - Non-admin: needs to escalate — settings URL is displayed prominently
6140
+ * to copy and share with the admin.
6141
+ *
6142
+ * Both variants offer [S] (show skill source for BYOAI), [R] (retry —
6143
+ * re-fetches user data and re-evaluates the gate without restarting), and
6144
+ * [E] (exit).
6145
+ */
6146
+ const ORG_ADMIN_LEVEL = 8;
6147
+ const SETTINGS_PATH = "settings/organization-details";
6148
+ const SETTINGS_ANCHOR = "#organization-ai-consent";
6149
+ const AiOptInRequiredScreen = ({ store }) => {
6150
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
6151
+ const { session } = store;
6152
+ const isAdmin = ((session.apiUser?.organization)?.membership_level ?? 0) >= ORG_ADMIN_LEVEL;
6153
+ const variant = isAdmin ? "admin" : "non-admin";
6154
+ const region = session.region ?? "us";
6155
+ const projectId = session.credentials?.projectId;
6156
+ const settingsUrl = projectId != null ? `${POSTHOG_APP_URL}/project/${projectId}/${SETTINGS_PATH}${SETTINGS_ANCHOR}` : `${POSTHOG_APP_URL}/${SETTINGS_PATH}${SETTINGS_ANCHOR}`;
6157
+ const [showSkill, setShowSkill] = useState(false);
6158
+ const [retrying, setRetrying] = useState(false);
6159
+ const [retryError, setRetryError] = useState(null);
6160
+ const { skillEntry } = useSkillEntry(session.skillId, session.localMcp);
6161
+ useEffect(() => {
6162
+ analytics.wizardCapture("ai opt-in shown", { variant });
6163
+ }, [variant]);
6164
+ const handleOpenSettings = () => {
6165
+ analytics.wizardCapture("ai opt-in action", {
6166
+ variant,
6167
+ action: "open_settings"
6168
+ });
6169
+ opn(settingsUrl, { wait: false }).catch(() => {});
6170
+ };
6171
+ const handleShowSkill = () => {
6172
+ analytics.wizardCapture("ai opt-in action", {
6173
+ variant,
6174
+ action: "show_skill"
6175
+ });
6176
+ setShowSkill(true);
6177
+ };
6178
+ const handleRetry = () => {
6179
+ analytics.wizardCapture("ai opt-in action", {
6180
+ variant,
6181
+ action: "retry"
6182
+ });
6183
+ const accessToken = session.credentials?.accessToken;
6184
+ if (!accessToken) {
6185
+ setRetryError("Missing credentials — cannot retry.");
6186
+ return;
6187
+ }
6188
+ setRetrying(true);
6189
+ setRetryError(null);
6190
+ fetchUserData(accessToken, getCloudUrlFromRegion(region)).then((user) => {
6191
+ store.setApiUser(user);
6192
+ }).catch((err) => {
6193
+ setRetryError(err instanceof Error ? err.message : "Retry failed.");
6194
+ }).finally(() => {
6195
+ setRetrying(false);
6196
+ });
6197
+ };
6198
+ const handleExit = () => {
6199
+ analytics.wizardCapture("ai opt-in action", {
6200
+ variant,
6201
+ action: "exit"
6202
+ });
6203
+ process.exit(0);
6204
+ };
6205
+ useKeyBindings("ai-opt-in", [
6206
+ ...isAdmin ? [{
6207
+ match: ["o", "O"],
6208
+ label: "O",
6209
+ action: "open settings",
6210
+ handler: handleOpenSettings
6211
+ }] : [],
6212
+ {
6213
+ match: ["s", "S"],
6214
+ label: "S",
6215
+ action: "show skill",
6216
+ handler: handleShowSkill
6217
+ },
6218
+ {
6219
+ match: ["r", "R"],
6220
+ label: "R",
6221
+ action: "retry",
6222
+ handler: handleRetry
6223
+ },
6224
+ {
6225
+ match: ["e", "E"],
6226
+ label: "E",
6227
+ action: "exit",
6228
+ handler: handleExit
6229
+ }
6230
+ ]);
6231
+ return /* @__PURE__ */ jsxs(Box, {
6232
+ flexDirection: "column",
6233
+ flexGrow: 1,
6234
+ children: [
6235
+ /* @__PURE__ */ jsxs(Box, {
6236
+ flexDirection: "column",
6237
+ marginBottom: 1,
6238
+ children: [/* @__PURE__ */ jsx(Text, {
6239
+ bold: true,
6240
+ color: Colors.accent,
6241
+ children: "PostHog Setup Wizard"
6242
+ }), session.apiUser?.email && /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
6243
+ color: "green",
6244
+ children: ["✔", " "]
6245
+ }), /* @__PURE__ */ jsxs(Text, { children: ["Authenticated as ", session.apiUser.email] })] })]
6246
+ }),
6247
+ /* @__PURE__ */ jsx(Box, {
6248
+ flexDirection: "column",
6249
+ marginBottom: 1,
6250
+ children: /* @__PURE__ */ jsx(Text, {
6251
+ color: "yellow",
6252
+ bold: true,
6253
+ children: "⚠ PostHog AI services are disabled for your organization"
6254
+ })
6255
+ }),
6256
+ /* @__PURE__ */ jsx(Box, {
6257
+ flexDirection: "column",
6258
+ marginBottom: 1,
6259
+ width: 68,
6260
+ children: isAdmin ? /* @__PURE__ */ jsxs(Text, { children: [
6261
+ "The wizard uses Anthropic Claude. To proceed, enable",
6262
+ " ",
6263
+ /* @__PURE__ */ jsx(Text, {
6264
+ italic: true,
6265
+ children: "\"Enable PostHog features that use third-party AI services\""
6266
+ }),
6267
+ " ",
6268
+ "in your organization settings."
6269
+ ] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Text, { children: [
6270
+ "The wizard uses Anthropic Claude. Your organization admin needs to enable",
6271
+ " ",
6272
+ /* @__PURE__ */ jsx(Text, {
6273
+ italic: true,
6274
+ children: "\"Enable PostHog features that use third-party AI services\""
6275
+ }),
6276
+ " ",
6277
+ "in organization settings."
6278
+ ] }), /* @__PURE__ */ jsx(Box, {
6279
+ marginTop: 1,
6280
+ children: /* @__PURE__ */ jsx(Text, {
6281
+ dimColor: true,
6282
+ children: "Share this link with your admin:"
6283
+ })
6284
+ })] })
6285
+ }),
6286
+ /* @__PURE__ */ jsx(Box, {
6287
+ marginBottom: 1,
6288
+ children: /* @__PURE__ */ jsx(Text, {
6289
+ color: "cyan",
6290
+ children: settingsUrl
6291
+ })
6292
+ }),
6293
+ showSkill && /* @__PURE__ */ jsxs(Box, {
6294
+ marginBottom: 1,
6295
+ flexDirection: "column",
6296
+ children: [/* @__PURE__ */ jsxs(Text, { children: [
6297
+ "Prefer your own AI? Download",
6298
+ " ",
6299
+ skillEntry ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
6300
+ "the ",
6301
+ /* @__PURE__ */ jsx(Text, {
6302
+ bold: true,
6303
+ children: skillEntry.id
6304
+ }),
6305
+ " skill"
6306
+ ] }) : "the skill for your framework",
6307
+ " ",
6308
+ "and run it in your own agent:"
6309
+ ] }), /* @__PURE__ */ jsx(Text, {
6310
+ color: "cyan",
6311
+ children: "https://github.com/PostHog/context-mill/releases/latest"
6312
+ })]
6313
+ }),
6314
+ retrying && /* @__PURE__ */ jsx(Box, {
6315
+ marginBottom: 1,
6316
+ children: /* @__PURE__ */ jsx(LoadingBox, { message: "Re-checking organization settings..." })
6317
+ }),
6318
+ retryError && /* @__PURE__ */ jsx(Box, {
6319
+ marginBottom: 1,
6320
+ children: /* @__PURE__ */ jsx(Text, {
6321
+ color: "red",
6322
+ children: retryError
6323
+ })
6324
+ }),
6325
+ /* @__PURE__ */ jsxs(Box, {
6326
+ flexDirection: "column",
6327
+ marginTop: 1,
6328
+ children: [
6329
+ isAdmin && /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
6330
+ color: Colors.accent,
6331
+ children: "[O]"
6332
+ }), " Open settings in browser"] }),
6333
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
6334
+ color: Colors.accent,
6335
+ children: "[S]"
6336
+ }), " Show how to use your own AI"] }),
6337
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
6338
+ color: Colors.accent,
6339
+ children: "[R]"
6340
+ }), " Retry (after the toggle is enabled)"] }),
6341
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
6342
+ color: Colors.accent,
6343
+ children: "[E]"
6344
+ }), " Exit"] })
6345
+ ]
6346
+ })
6347
+ ]
6348
+ });
6349
+ };
6350
+ //#endregion
6351
+ 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
6352
 
5969
- //# sourceMappingURL=OutroScreen-CqF6SdBo.js.map
6353
+ //# sourceMappingURL=AiOptInRequiredScreen-N6L80szR.js.map