@posthog/wizard 2.52.0 → 2.53.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 (109) hide show
  1. package/dist/{add-mcp-server-to-clients-DJyWMMGj.js → add-mcp-server-to-clients-DmMHsYYM.js} +5 -5
  2. package/dist/{add-mcp-server-to-clients-DJyWMMGj.js.map → add-mcp-server-to-clients-DmMHsYYM.js.map} +1 -1
  3. package/dist/{agent-interface-DoFVHRFh.js → agent-interface-xWyATfJi.js} +330 -25
  4. package/dist/agent-interface-xWyATfJi.js.map +1 -0
  5. package/dist/{agent-prompt-loader-D-cPVoL5.js → agent-prompt-loader-i6miJlmN.js} +5 -5
  6. package/dist/{agent-prompt-loader-D-cPVoL5.js.map → agent-prompt-loader-i6miJlmN.js.map} +1 -1
  7. package/dist/{agent-runner-B1qCtjid.js → agent-runner-CPuubcP_.js} +157 -137
  8. package/dist/agent-runner-CPuubcP_.js.map +1 -0
  9. package/dist/{analytics-BXz4SIuS.js → analytics-BC3iiS1p.js} +5 -2
  10. package/dist/analytics-BC3iiS1p.js.map +1 -0
  11. package/dist/analytics-CLuGZo9D.js +2 -0
  12. package/dist/{api-BaacjH-Y.js → api-CXk-efCU.js} +3 -3
  13. package/dist/{api-BaacjH-Y.js.map → api-CXk-efCU.js.map} +1 -1
  14. package/dist/bin.js +316 -179
  15. package/dist/bin.js.map +1 -1
  16. package/dist/{bounded-fs-Cx-8nk-1.js → bounded-fs-B6Eqbgoj.js} +3 -3
  17. package/dist/{bounded-fs-Cx-8nk-1.js.map → bounded-fs-B6Eqbgoj.js.map} +1 -1
  18. package/dist/{ci-install-D1Mum8Ah.js → ci-install-CXD2meJ4.js} +5 -5
  19. package/dist/{ci-install-D1Mum8Ah.js.map → ci-install-CXD2meJ4.js.map} +1 -1
  20. package/dist/{debug-DsSCOPe8.js → debug-Bz-LaIU8.js} +1 -1
  21. package/dist/{debug-C3ZR1TVF.js → debug-tCr3A18e.js} +13 -4
  22. package/dist/debug-tCr3A18e.js.map +1 -0
  23. package/dist/{defaults-DAE9_QMn.js → defaults-BWLIffh5.js} +1 -1
  24. package/dist/{defaults-DAE9_QMn.js.map → defaults-BWLIffh5.js.map} +1 -1
  25. package/dist/{env-api-key-DDVkPQmo.js → env-api-key-cjD4yfGT.js} +1 -1
  26. package/dist/{env-api-key-DDVkPQmo.js.map → env-api-key-cjD4yfGT.js.map} +1 -1
  27. package/dist/{environment-Bi-zynk6.js → environment-JwRHoXao.js} +4 -4
  28. package/dist/{environment-Bi-zynk6.js.map → environment-JwRHoXao.js.map} +1 -1
  29. package/dist/{file-watcher-PmRIDAfM.js → file-watcher-Cm-B3jyb.js} +2 -2
  30. package/dist/{file-watcher-PmRIDAfM.js.map → file-watcher-Cm-B3jyb.js.map} +1 -1
  31. package/dist/{headless-ui-C4X9lAOY.js → headless-ui-o1cpGr1W.js} +2 -2
  32. package/dist/{headless-ui-C4X9lAOY.js.map → headless-ui-o1cpGr1W.js.map} +1 -1
  33. package/dist/{interactive-HM71vLQe.js → interactive-CM2fMt8l.js} +3 -3
  34. package/dist/{interactive-HM71vLQe.js.map → interactive-CM2fMt8l.js.map} +1 -1
  35. package/dist/{mcp-D0Hpb_nk.js → mcp-C4Xn8mq5.js} +2 -2
  36. package/dist/{mcp-D0Hpb_nk.js.map → mcp-C4Xn8mq5.js.map} +1 -1
  37. package/dist/{mcp-prompt-streaming-Crk8qFlQ.js → mcp-prompt-streaming-Dqrcns_Z.js} +5 -4
  38. package/dist/{mcp-prompt-streaming-Crk8qFlQ.js.map → mcp-prompt-streaming-Dqrcns_Z.js.map} +1 -1
  39. package/dist/{non-interactive-DdUe8lqX.js → non-interactive-DYvEgWZx.js} +2 -2
  40. package/dist/{non-interactive-DdUe8lqX.js.map → non-interactive-DYvEgWZx.js.map} +1 -1
  41. package/dist/{orchestrator-tools-CpRruqZl.js → orchestrator-tools-CAl7aLtB.js} +3 -3
  42. package/dist/{orchestrator-tools-CpRruqZl.js.map → orchestrator-tools-CAl7aLtB.js.map} +1 -1
  43. package/dist/{package-manager-wkSzPJqW.js → package-manager-DnU0j41n.js} +2 -2
  44. package/dist/{package-manager-wkSzPJqW.js.map → package-manager-DnU0j41n.js.map} +1 -1
  45. package/dist/pi-2hh_NXKT.js +409 -0
  46. package/dist/pi-2hh_NXKT.js.map +1 -0
  47. package/dist/{playground-lg7h9GHG.js → playground-rG007Ve9.js} +353 -18
  48. package/dist/playground-rG007Ve9.js.map +1 -0
  49. package/dist/{posthog-mgz-FQlS.js → posthog-DDLIrku0.js} +1 -1
  50. package/dist/{posthog-mgz-FQlS.js.map → posthog-DDLIrku0.js.map} +1 -1
  51. package/dist/{posthog-integration-pnxhTbxw.js → posthog-integration-Bd-kybes.js} +15 -15
  52. package/dist/{posthog-integration-pnxhTbxw.js.map → posthog-integration-Bd-kybes.js.map} +1 -1
  53. package/dist/{provisioning-T4jtJqBa.js → provisioning-tkKe6aM4.js} +3 -3
  54. package/dist/{provisioning-T4jtJqBa.js.map → provisioning-tkKe6aM4.js.map} +1 -1
  55. package/dist/{queue-tools-D7ctuzyo.js → queue-tools-kBa-8bPH.js} +45 -32
  56. package/dist/queue-tools-kBa-8bPH.js.map +1 -0
  57. package/dist/{registry-CsFuKtXn.js → registry-Cg_M-kov.js} +5 -5
  58. package/dist/{registry-CsFuKtXn.js.map → registry-Cg_M-kov.js.map} +1 -1
  59. package/dist/{security-YAaZfmHs.js → security-BtIAMdyk.js} +5 -5
  60. package/dist/security-BtIAMdyk.js.map +1 -0
  61. package/dist/{setup-utils-DDzEnDxJ.js → setup-utils-t0jzKwBE.js} +8 -8
  62. package/dist/{setup-utils-DDzEnDxJ.js.map → setup-utils-t0jzKwBE.js.map} +1 -1
  63. package/dist/{start-tui-pCi7WVAK.js → start-tui-CK3rpaKD.js} +35 -21
  64. package/dist/start-tui-CK3rpaKD.js.map +1 -0
  65. package/dist/{steps-DmsrBqb9.js → steps-dNXQiyOZ.js} +7 -7
  66. package/dist/{steps-DmsrBqb9.js.map → steps-dNXQiyOZ.js.map} +1 -1
  67. package/dist/{store-BCRdtfOH.js → store-qv5UdGQO.js} +5 -5
  68. package/dist/{store-BCRdtfOH.js.map → store-qv5UdGQO.js.map} +1 -1
  69. package/dist/{subagent-jNXS8IPU.js → subagent-DsfcNf0P.js} +2 -2
  70. package/dist/{subagent-jNXS8IPU.js.map → subagent-DsfcNf0P.js.map} +1 -1
  71. package/dist/{task-CSmrfyNf.js → task-BZ77d7rm.js} +30 -23
  72. package/dist/task-BZ77d7rm.js.map +1 -0
  73. package/dist/{task-stream-BZOjOhKu.js → task-stream-5k_cd_Wz.js} +4 -4
  74. package/dist/{task-stream-BZOjOhKu.js.map → task-stream-5k_cd_Wz.js.map} +1 -1
  75. package/dist/{tasks-BMgQt251.js → tasks-C9yxUVN7.js} +2 -2
  76. package/dist/{tasks-BMgQt251.js.map → tasks-C9yxUVN7.js.map} +1 -1
  77. package/dist/{telemetry-qeKIGkte.js → telemetry-5NqJBWNN.js} +3 -3
  78. package/dist/{telemetry-qeKIGkte.js.map → telemetry-5NqJBWNN.js.map} +1 -1
  79. package/dist/{terminal-tbxhJGlI.js → terminal-q-Sd8CdO.js} +806 -62
  80. package/dist/terminal-q-Sd8CdO.js.map +1 -0
  81. package/dist/{tools-DVnBIYFn.js → tools-2weX1XHC.js} +8 -10
  82. package/dist/tools-2weX1XHC.js.map +1 -0
  83. package/dist/warlock-smoke-test.ts +177 -1
  84. package/dist/{wizard-abort-AEhoDJ4P.js → wizard-abort-B1XewmPn.js} +3 -3
  85. package/dist/{wizard-abort-AEhoDJ4P.js.map → wizard-abort-B1XewmPn.js.map} +1 -1
  86. package/dist/{wizard-abort-DHC_XStW.js → wizard-abort-Cl8D3gW8.js} +1 -1
  87. package/dist/yara-hooks-Cj7xUAKE.js +2 -0
  88. package/dist/{yara-hooks-CKsVxWiN.js → yara-hooks-Cu0rEw1L.js} +39 -83
  89. package/dist/yara-hooks-Cu0rEw1L.js.map +1 -0
  90. package/package.json +2 -1
  91. package/dist/agent-interface-DoFVHRFh.js.map +0 -1
  92. package/dist/agent-runner-B1qCtjid.js.map +0 -1
  93. package/dist/analytics-BXz4SIuS.js.map +0 -1
  94. package/dist/analytics-D2xds3nw.js +0 -2
  95. package/dist/commandments-BagyrYRW.js +0 -2
  96. package/dist/commandments-DiMtIz1o.js +0 -42
  97. package/dist/commandments-DiMtIz1o.js.map +0 -1
  98. package/dist/debug-C3ZR1TVF.js.map +0 -1
  99. package/dist/pi-CGPjm4oE.js +0 -570
  100. package/dist/pi-CGPjm4oE.js.map +0 -1
  101. package/dist/playground-lg7h9GHG.js.map +0 -1
  102. package/dist/queue-tools-D7ctuzyo.js.map +0 -1
  103. package/dist/security-YAaZfmHs.js.map +0 -1
  104. package/dist/start-tui-pCi7WVAK.js.map +0 -1
  105. package/dist/task-CSmrfyNf.js.map +0 -1
  106. package/dist/terminal-tbxhJGlI.js.map +0 -1
  107. package/dist/tools-DVnBIYFn.js.map +0 -1
  108. package/dist/yara-hooks-CKsVxWiN.js.map +0 -1
  109. package/dist/yara-hooks-DWRdZQuG.js +0 -2
@@ -1,19 +1,20 @@
1
- import { B as POSTHOG_APP_URL, ft as getSkillsBaseUrl, h as SERVICE_LABELS, s as logToFile } from "./debug-C3ZR1TVF.js";
2
- import { n as analytics } from "./analytics-BXz4SIuS.js";
3
- import { i as withUtm, n as openTrackedLink } from "./telemetry-qeKIGkte.js";
4
- import { t as getOrAskForProjectData } from "./setup-utils-DDzEnDxJ.js";
5
- import { a as fetchUserData, i as fetchSlackConnected } from "./api-BaacjH-Y.js";
1
+ import { V as POSTHOG_APP_URL, ft as WIZARD_USER_AGENT, h as SERVICE_LABELS, pt as getSkillsBaseUrl, s as logToFile } from "./debug-tCr3A18e.js";
2
+ import { n as analytics } from "./analytics-BC3iiS1p.js";
3
+ import { i as withUtm, n as openTrackedLink } from "./telemetry-5NqJBWNN.js";
4
+ import { t as getOrAskForProjectData } from "./setup-utils-t0jzKwBE.js";
5
+ import { a as fetchUserData, i as fetchSlackConnected } from "./api-CXk-efCU.js";
6
6
  import "./wizard-session-BAZql11N.js";
7
- import { a as formatTokenCount, i as formatCostUsd } from "./agent-runner-B1qCtjid.js";
8
- import { b as fetchSkillMenu } from "./agent-interface-DoFVHRFh.js";
9
- import { y as AUDIT_SEVERITY_STYLE } from "./yara-hooks-CKsVxWiN.js";
10
- import { c as TextBlock, d as isLinesBlock, f as isObjectBlock, l as computeVisibleRange, m as Icons, p as Colors, u as isClearBlock } from "./posthog-integration-pnxhTbxw.js";
11
- import { a as ConfirmButton, d as Program, i as useKeyboardHintsContext, n as useKeyBindings, o as PromptLabel, r as KeyboardHintsProvider, t as PickerMenu, v as getKindMeta } from "./bin.js";
12
- import { r as totalTokenCount } from "./store-BCRdtfOH.js";
13
- import { n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-DAE9_QMn.js";
7
+ import { a as formatTokenCount, i as formatCostUsd } from "./agent-runner-CPuubcP_.js";
8
+ import { x as fetchSkillMenu } from "./agent-interface-xWyATfJi.js";
9
+ import { y as AUDIT_SEVERITY_STYLE } from "./yara-hooks-Cu0rEw1L.js";
10
+ import { c as TextBlock, d as isClearBlock, f as isLinesBlock, h as Icons, l as computeVisibleRange, m as Colors, p as isObjectBlock } from "./posthog-integration-Bd-kybes.js";
11
+ import { a as useStdoutDimensions, f as Program, i as useKeyboardHintsContext, n as useKeyBindings, o as ConfirmButton, r as KeyboardHintsProvider, s as PromptLabel, t as PickerMenu, y as getKindMeta } from "./bin.js";
12
+ import { r as totalTokenCount } from "./store-qv5UdGQO.js";
13
+ import { n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-BWLIffh5.js";
14
14
  import opn from "opn";
15
15
  import * as fs$1 from "fs";
16
- import { Box, Text, measureElement, useInput, useStdout } from "ink";
16
+ import axios from "axios";
17
+ import { Box, Text, measureElement, useInput } from "ink";
17
18
  import { Component, Fragment, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
18
19
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
19
20
  import { Spinner } from "@inkjs/ui";
@@ -123,35 +124,6 @@ const ProgressList = ({ items, title }) => {
123
124
  });
124
125
  };
125
126
  //#endregion
126
- //#region src/ui/tui/hooks/useStdoutDimensions.ts
127
- /**
128
- * useStdoutDimensions — Returns [columns, rows] and re-renders on terminal resize.
129
- *
130
- * Ink's useStdout() does not subscribe to resize events, so layout only updates
131
- * when something else causes a re-render. This hook listens to the stream's
132
- * 'resize' event (Node TTY) and updates state so the component re-renders
133
- * with the new dimensions.
134
- */
135
- function useStdoutDimensions() {
136
- const { stdout } = useStdout();
137
- const [size, setSize] = useState(() => [stdout.columns || 80, stdout.rows || 24]);
138
- useEffect(() => {
139
- setSize([stdout.columns || 80, stdout.rows || 24]);
140
- const stream = stdout;
141
- if (typeof stream.on !== "function") return;
142
- const onResize = () => {
143
- const c = stdout.columns || 80;
144
- const r = stdout.rows || 24;
145
- if (c > 0 && r > 0) setSize([c, r]);
146
- };
147
- stream.on("resize", onResize);
148
- return () => {
149
- stream.off?.("resize", onResize);
150
- };
151
- }, [stdout]);
152
- return size;
153
- }
154
- //#endregion
155
127
  //#region src/ui/tui/primitives/GroupedPickerMenu.tsx
156
128
  /**
157
129
  * GroupedPickerMenu — Multi-select with category headers.
@@ -182,6 +154,12 @@ const CHROME_OVERHEAD = 10;
182
154
  * single border top/bottom (2) + button text (1).
183
155
  */
184
156
  const MENU_CHROME = 6;
157
+ /**
158
+ * Max visual rows the list renders regardless of terminal height, so a tall
159
+ * terminal doesn't turn the menu into a full-screen wall. Mirrors
160
+ * PickerMenu's MAX_LIST_ROWS.
161
+ */
162
+ const MAX_LIST_ROWS = 12;
185
163
  /** Count the visual rows occupied by rows[start..end), accounting for header margins. */
186
164
  function countVisualRows(rows, start, end) {
187
165
  let count = 0;
@@ -243,7 +221,7 @@ const GroupedPickerMenu = ({ message, groups, initialSelected, onSelect }) => {
243
221
  const [selected, setSelected] = useState(() => new Set(initialSelected ?? allValues));
244
222
  const [scrollOffset, setScrollOffset] = useState(0);
245
223
  const focusedRowIdx = selectableIndices[focusedSelectable] ?? 0;
246
- const viewportBudget = Math.max(5, termRows - CHROME_OVERHEAD - MENU_CHROME);
224
+ const viewportBudget = Math.max(5, Math.min(termRows - CHROME_OVERHEAD - MENU_CHROME, MAX_LIST_ROWS));
247
225
  const needsScroll = countVisualRows(rows, 0, rows.length) > viewportBudget;
248
226
  const effectiveBudget = needsScroll ? viewportBudget - 2 : viewportBudget;
249
227
  useKeyBindings("grouped-picker", [
@@ -311,7 +289,7 @@ const GroupedPickerMenu = ({ message, groups, initialSelected, onSelect }) => {
311
289
  /* @__PURE__ */ jsx(PromptLabel, { message }),
312
290
  /* @__PURE__ */ jsxs(Box, {
313
291
  flexDirection: "column",
314
- marginTop: 1,
292
+ marginTop: message ? 1 : 0,
315
293
  marginLeft: 2,
316
294
  children: [
317
295
  needsScroll && /* @__PURE__ */ jsx(Text, {
@@ -3065,6 +3043,82 @@ var neutralCrossSell = [{
3065
3043
  "prompt": "List the top errors my users hit this week.",
3066
3044
  "description": "Built-in error tracking — no separate tool."
3067
3045
  }];
3046
+ var generatedQuests = {
3047
+ "funnel": {
3048
+ "label": "Funnel your real events",
3049
+ "prompt": "Build a funnel from these events in the most sensible order — {events} — over the last 30 days, and show me the biggest drop-off."
3050
+ },
3051
+ "trend": {
3052
+ "label": "Trend {event}",
3053
+ "prompt": "Show me a daily trend of {event} over the last 30 days and call out the biggest spike or dip."
3054
+ },
3055
+ "breakdown": {
3056
+ "label": "Break down {event}",
3057
+ "prompt": "Break down {event} over the last 30 days by the most common user property and show me the top segments."
3058
+ }
3059
+ };
3060
+ var writeOnlyQuests = [{
3061
+ "key": "verify",
3062
+ "prompt": "Annotate today with 'PostHog wizard install'",
3063
+ "description": "Creates a dated note on your project — visible on every chart. Delete anytime from PostHog."
3064
+ }, {
3065
+ "key": "starter-dashboard",
3066
+ "prompt": "Create a starter dashboard called 'My first dashboard (wizard MCP tutorial)' with a daily-active-users tile and a top-events tile.",
3067
+ "description": "A real dashboard you can build on — no event history required."
3068
+ }];
3069
+ var activationCrossSell = {
3070
+ "errorTracking": {
3071
+ "product": "Error Tracking",
3072
+ "label": "see what it'd catch",
3073
+ "prompt": "I don't have error tracking data yet. Show me how to turn on PostHog Error Tracking in my stack and what it would capture.",
3074
+ "description": "Built-in exception tracking — no separate Sentry bill."
3075
+ },
3076
+ "sessionReplay": {
3077
+ "product": "Session Replay",
3078
+ "label": "watch real sessions",
3079
+ "prompt": "I don't have session recordings yet. Show me how to enable PostHog Session Replay and what I'd be able to see.",
3080
+ "description": "Watch what users actually do — included on every plan."
3081
+ },
3082
+ "surveys": {
3083
+ "product": "Surveys",
3084
+ "label": "ask your users",
3085
+ "prompt": "I'm not running surveys yet. Show me how to launch a PostHog survey and the kinds of questions teams ask.",
3086
+ "description": "In-app surveys and NPS, collected next to your events."
3087
+ },
3088
+ "webAnalytics": {
3089
+ "product": "Web Analytics",
3090
+ "label": "GA-style dashboards",
3091
+ "prompt": "I don't have pageview data yet. Show me how to enable PostHog Web Analytics and what the dashboard shows.",
3092
+ "description": "First-party web analytics — no cookie banner."
3093
+ },
3094
+ "experiments": {
3095
+ "product": "Experiments",
3096
+ "label": "A/B test safely",
3097
+ "prompt": "I haven't run experiments yet. Show me how PostHog Experiments work and what I'd need to start one.",
3098
+ "description": "A/B tests that piggyback on your feature flags."
3099
+ },
3100
+ "featureFlags": {
3101
+ "product": "Feature Flags",
3102
+ "label": "ship behind a flag",
3103
+ "prompt": "I don't have feature flags yet. Show me how to add a PostHog feature flag in my framework and how rollout works.",
3104
+ "description": "Gradual rollouts and kill switches, evaluated locally."
3105
+ },
3106
+ "dataWarehouse": {
3107
+ "product": "Data Warehouse",
3108
+ "label": "join external data",
3109
+ "prompt": "Show me how to connect a data warehouse source like Stripe to PostHog and what I could query once it's joined.",
3110
+ "description": "Query Stripe / Salesforce / S3 alongside your events."
3111
+ }
3112
+ };
3113
+ var seedOfferGreeting = {
3114
+ "headline": "Fresh project — let's put something on the map.",
3115
+ "bullets": [
3116
+ "Your project hasn't logged events yet, so there's nothing to chart — yet.",
3117
+ "I can send a small demo dataset so you can see funnels, trends, and dashboards in action.",
3118
+ "Everything is tagged wizard_seed:true and uses wizard-demo-user-* distinct IDs — events are immutable in PostHog, but you can filter these out of any query."
3119
+ ],
3120
+ "outro": "Want me to seed some demo data to explore?"
3121
+ };
3068
3122
  var slackApp = {
3069
3123
  "learnMoreUrl": "https://posthog.com/slack",
3070
3124
  "setupUrl": "https://app.posthog.com/integrations/slack",
@@ -3107,6 +3161,10 @@ const DEEP_DIVE_FOLLOW_UPS = deepDiveFollowUps;
3107
3161
  const CROSS_SELL_BY_ROLE = crossSellByRole;
3108
3162
  const NEUTRAL_CROSS_SELL = neutralCrossSell;
3109
3163
  const SLACK_APP = slackApp;
3164
+ const GENERATED_QUESTS = generatedQuests;
3165
+ const WRITE_ONLY_QUESTS = writeOnlyQuests;
3166
+ const ACTIVATION_CROSS_SELL = activationCrossSell;
3167
+ const SEED_OFFER_GREETING = seedOfferGreeting;
3110
3168
  const INTEGRATION_FAMILY = {
3111
3169
  nextjs: "fullstack",
3112
3170
  nuxt: "fullstack",
@@ -3239,6 +3297,158 @@ function getCrossSellPrompts(role) {
3239
3297
  return CROSS_SELL_BY_ROLE[role];
3240
3298
  }
3241
3299
  /**
3300
+ * Order in which to offer activation cross-sells per role — products most
3301
+ * relevant to each audience first. Only products the scout found *absent*
3302
+ * are surfaced; this just picks which absent ones to lead with.
3303
+ */
3304
+ const ACTIVATION_PRIORITY = {
3305
+ founder: [
3306
+ "sessionReplay",
3307
+ "surveys",
3308
+ "webAnalytics",
3309
+ "experiments",
3310
+ "dataWarehouse",
3311
+ "errorTracking",
3312
+ "featureFlags"
3313
+ ],
3314
+ product: [
3315
+ "sessionReplay",
3316
+ "experiments",
3317
+ "surveys",
3318
+ "featureFlags",
3319
+ "webAnalytics",
3320
+ "errorTracking",
3321
+ "dataWarehouse"
3322
+ ],
3323
+ leadership: [
3324
+ "surveys",
3325
+ "dataWarehouse",
3326
+ "sessionReplay",
3327
+ "webAnalytics",
3328
+ "experiments",
3329
+ "errorTracking",
3330
+ "featureFlags"
3331
+ ],
3332
+ marketing: [
3333
+ "sessionReplay",
3334
+ "webAnalytics",
3335
+ "surveys",
3336
+ "experiments",
3337
+ "dataWarehouse",
3338
+ "featureFlags",
3339
+ "errorTracking"
3340
+ ],
3341
+ engineering: [
3342
+ "errorTracking",
3343
+ "sessionReplay",
3344
+ "featureFlags",
3345
+ "experiments",
3346
+ "webAnalytics",
3347
+ "dataWarehouse",
3348
+ "surveys"
3349
+ ],
3350
+ data: [
3351
+ "dataWarehouse",
3352
+ "experiments",
3353
+ "sessionReplay",
3354
+ "webAnalytics",
3355
+ "errorTracking",
3356
+ "surveys",
3357
+ "featureFlags"
3358
+ ]
3359
+ };
3360
+ const NEUTRAL_ACTIVATION_PRIORITY = [
3361
+ "sessionReplay",
3362
+ "errorTracking",
3363
+ "surveys",
3364
+ "webAnalytics",
3365
+ "experiments",
3366
+ "featureFlags",
3367
+ "dataWarehouse"
3368
+ ];
3369
+ /** How many custom events to feed into a generated funnel prompt. */
3370
+ const FUNNEL_EVENT_LIMIT = 4;
3371
+ /** Fill `{placeholder}` tokens in a template; unknown tokens pass through. */
3372
+ function fillTemplate(template, vars) {
3373
+ return template.replace(/\{(\w+)\}/g, (_, key) => vars[key] ?? `{${key}}`);
3374
+ }
3375
+ function getSeedOfferGreeting() {
3376
+ return SEED_OFFER_GREETING;
3377
+ }
3378
+ /**
3379
+ * Generate quests from the project's REAL event names (idea 9). Returns a
3380
+ * funnel (when ≥2 custom events exist), a trend, and a breakdown — all
3381
+ * templated off `topCustomEvents`. Empty when there's nothing usable to
3382
+ * build from (no profile, degraded, or no custom events), so callers fall
3383
+ * back to the static kit.
3384
+ */
3385
+ function getGeneratedQuests(profile) {
3386
+ if (!profile || profile.degraded) return [];
3387
+ const custom = profile.topCustomEvents;
3388
+ if (custom.length === 0) return [];
3389
+ const out = [];
3390
+ const top = custom[0].name;
3391
+ if (custom.length >= 2) {
3392
+ const events = custom.slice(0, FUNNEL_EVENT_LIMIT).map((e) => e.name).join(", ");
3393
+ out.push({
3394
+ label: GENERATED_QUESTS.funnel.label,
3395
+ prompt: fillTemplate(GENERATED_QUESTS.funnel.prompt, { events })
3396
+ });
3397
+ }
3398
+ out.push({
3399
+ label: fillTemplate(GENERATED_QUESTS.trend.label, { event: top }),
3400
+ prompt: fillTemplate(GENERATED_QUESTS.trend.prompt, { event: top })
3401
+ });
3402
+ out.push({
3403
+ label: fillTemplate(GENERATED_QUESTS.breakdown.label, { event: top }),
3404
+ prompt: fillTemplate(GENERATED_QUESTS.breakdown.prompt, { event: top })
3405
+ });
3406
+ return out;
3407
+ }
3408
+ /**
3409
+ * Activation cross-sells (idea 4) for products the scout found NO data
3410
+ * for — turning a data-less dead end into "here's how to turn this on".
3411
+ * Ordered by role affinity, capped at `limit`. Empty when the profile is
3412
+ * missing/degraded (we only nag when we're confident a product is absent).
3413
+ */
3414
+ function getActivationCrossSell(role, profile, limit = 2) {
3415
+ if (!profile || profile.degraded) return [];
3416
+ return (isTailoredRole(role) ? ACTIVATION_PRIORITY[role] : NEUTRAL_ACTIVATION_PRIORITY).filter((key) => profile.products[key] === false).slice(0, limit).map((key) => ACTIVATION_CROSS_SELL[key]).filter((o) => Boolean(o));
3417
+ }
3418
+ /**
3419
+ * The full, ordered candidate list for the tutorial picker, composed from
3420
+ * the scout's profile. The screen dedupes by prompt text and slices to its
3421
+ * display cap — this function owns *what* to offer and in what priority:
3422
+ *
3423
+ * • no profile / degraded → legacy composition (pinned + cross-sell + kit)
3424
+ * • empty → write-only quests + activation cross-sells
3425
+ * (no reads that would return nothing)
3426
+ * • rich (+ real events) → generated quests first, one activation nudge,
3427
+ * then role kit for flavor
3428
+ * • sparse → a safe pinned read + one generated quest +
3429
+ * role kit + one activation nudge
3430
+ */
3431
+ function getTutorialPicker(role, integration, profile) {
3432
+ if (!profile || profile.degraded) return [
3433
+ PINNED_FIRST_PROMPT,
3434
+ ...getCrossSellPrompts(role),
3435
+ ...getRolePrompts(role, integration)
3436
+ ];
3437
+ if (profile.tier === "empty") return [...WRITE_ONLY_QUESTS, ...getActivationCrossSell(role, profile, 2)];
3438
+ const generated = getGeneratedQuests(profile);
3439
+ if (profile.tier === "rich" && generated.length > 0) return [
3440
+ ...generated,
3441
+ ...getActivationCrossSell(role, profile, 1),
3442
+ ...getRolePrompts(role, integration)
3443
+ ];
3444
+ return [
3445
+ PINNED_FIRST_PROMPT,
3446
+ ...generated.slice(0, 1),
3447
+ ...getRolePrompts(role, integration),
3448
+ ...getActivationCrossSell(role, profile, 1)
3449
+ ];
3450
+ }
3451
+ /**
3242
3452
  * Resolve the "Take PostHog to Slack" card. Role-independent — the Slack
3243
3453
  * agent's two capabilities (code/PR + data) describe the product itself,
3244
3454
  * not role-specific examples.
@@ -3253,6 +3463,217 @@ function getSlackAppCard() {
3253
3463
  };
3254
3464
  }
3255
3465
  //#endregion
3466
+ //#region src/lib/mcp-project-profile.ts
3467
+ /**
3468
+ * Project data profile — a cheap, best-effort "scout" of what a user's
3469
+ * PostHog project actually contains. Run once after auth, before the MCP
3470
+ * tutorial picker, so the tutorial can adapt to the terrain instead of
3471
+ * offering prompts that hit a data-less dead end (the failure mode where
3472
+ * a freshly-installed project has no events and the agent narrates
3473
+ * emptiness because it was told this is a demo).
3474
+ *
3475
+ * The profile drives three data-aware behaviours in the tutorial:
3476
+ * • tier empty → offer to seed demo events; sparse/rich → go
3477
+ * straight to quests.
3478
+ * • topEvents quests are generated from the project's REAL event names
3479
+ * (e.g. a funnel between the two busiest custom events),
3480
+ * never from a generic hardcoded list.
3481
+ * • products a product with no data becomes an "enable X" cross-sell
3482
+ * (which routes through docs-search and always returns
3483
+ * something) instead of a read that comes back empty.
3484
+ *
3485
+ * Every probe is best-effort and time-boxed: a slow or failing call
3486
+ * resolves to a conservative fallback rather than throwing. The tutorial
3487
+ * must never be blocked, slowed, or broken by the scout — a degraded
3488
+ * profile (`degraded: true`) simply falls back to the static role kit and
3489
+ * skips activation cross-sells (we only nag the user to "enable X" when
3490
+ * we are CONFIDENT X is absent).
3491
+ */
3492
+ const PROBE_TIMEOUT_MS = 5e3;
3493
+ const SPARSE_EVENT_THRESHOLD = 50;
3494
+ const TOP_EVENTS_LIMIT = 25;
3495
+ const PROBE_WINDOW_DAYS = 30;
3496
+ /**
3497
+ * Non-`$`-prefixed events that are still PostHog system noise, so they
3498
+ * don't get templated into quests as if they were product events.
3499
+ */
3500
+ const NON_DOLLAR_SYSTEM_EVENTS = new Set([
3501
+ "survey sent",
3502
+ "survey shown",
3503
+ "survey dismissed"
3504
+ ]);
3505
+ /** Every product assumed present — the safe "don't nag" default. */
3506
+ const ALL_PRODUCTS_PRESENT = {
3507
+ webAnalytics: true,
3508
+ errorTracking: true,
3509
+ sessionReplay: true,
3510
+ surveys: true,
3511
+ featureFlags: true,
3512
+ experiments: true,
3513
+ dataWarehouse: true
3514
+ };
3515
+ /**
3516
+ * Conservative fallback used when the core events probe fails. Tier is
3517
+ * `sparse` (not `empty`) so we neither offer to seed a project we can't
3518
+ * confirm is empty nor claim it's rich; `degraded` tells consumers to use
3519
+ * the static role kit and skip activation cross-sells.
3520
+ */
3521
+ function degradedProfile() {
3522
+ return {
3523
+ tier: "sparse",
3524
+ totalEvents: 0,
3525
+ distinctEventCount: 0,
3526
+ topEvents: [],
3527
+ topCustomEvents: [],
3528
+ products: { ...ALL_PRODUCTS_PRESENT },
3529
+ seeded: false,
3530
+ degraded: true
3531
+ };
3532
+ }
3533
+ /** Is this event name PostHog system noise rather than a product event? */
3534
+ function isSystemEvent(name) {
3535
+ return name.startsWith("$") || NON_DOLLAR_SYSTEM_EVENTS.has(name);
3536
+ }
3537
+ /**
3538
+ * True only when the given ingestion host is a PostHog-managed cloud
3539
+ * region we know maps to a working ingestion endpoint. Used as a gate on
3540
+ * the seed offer: PostHog events are immutable in ClickHouse, so we must
3541
+ * NOT offer to write demo events into a self-hosted deployment where we
3542
+ * can't be sure the ingestion route will succeed (and where the operator
3543
+ * hasn't opted in to us leaving artifacts). Cloud-hosted users have
3544
+ * consented to the wizard's writes as part of using the hosted service.
3545
+ */
3546
+ function isKnownCloudHost(apiHost) {
3547
+ try {
3548
+ const { host } = new URL(apiHost);
3549
+ return host === "us.i.posthog.com" || host === "eu.i.posthog.com";
3550
+ } catch {
3551
+ return false;
3552
+ }
3553
+ }
3554
+ /**
3555
+ * Pure profile assembler — turns raw probe outputs into a classified
3556
+ * profile. Exported so tier classification, custom-event filtering, and
3557
+ * product derivation can be unit-tested without touching the network.
3558
+ *
3559
+ * `rest` carries the REST existence checks; a `null` entry means the
3560
+ * check was inconclusive (failed/timed out) and the product defaults to
3561
+ * present so we never nag the user to enable it.
3562
+ */
3563
+ function assembleProfile(topEvents, rest, opts) {
3564
+ const totalEvents = topEvents.reduce((sum, e) => sum + e.count, 0);
3565
+ const distinctEventCount = topEvents.length;
3566
+ const topCustomEvents = topEvents.filter((e) => !isSystemEvent(e.name));
3567
+ const has = (name) => topEvents.some((e) => e.name === name && e.count > 0);
3568
+ let tier;
3569
+ if (totalEvents === 0) tier = "empty";
3570
+ else if (totalEvents < SPARSE_EVENT_THRESHOLD || distinctEventCount < 2) tier = "sparse";
3571
+ else tier = "rich";
3572
+ return {
3573
+ tier,
3574
+ totalEvents,
3575
+ distinctEventCount,
3576
+ topEvents,
3577
+ topCustomEvents,
3578
+ products: {
3579
+ webAnalytics: has("$pageview"),
3580
+ errorTracking: has("$exception"),
3581
+ sessionReplay: rest.sessionReplay ?? true,
3582
+ surveys: rest.surveys ?? true,
3583
+ featureFlags: rest.featureFlags ?? true,
3584
+ experiments: rest.experiments ?? true,
3585
+ dataWarehouse: rest.dataWarehouse ?? true
3586
+ },
3587
+ seeded: opts?.seeded ?? false,
3588
+ degraded: false
3589
+ };
3590
+ }
3591
+ /**
3592
+ * Run a single HogQL query returning the top events by volume in the
3593
+ * probe window. Throws on any failure — the caller degrades the whole
3594
+ * profile when the core events probe can't run.
3595
+ */
3596
+ async function queryTopEvents(accessToken, projectId, baseUrl) {
3597
+ const url = `${baseUrl}/api/projects/${projectId}/query/`;
3598
+ const rows = (await axios.post(url, { query: {
3599
+ kind: "HogQLQuery",
3600
+ query: `SELECT event, count() AS c FROM events WHERE timestamp > now() - INTERVAL ${PROBE_WINDOW_DAYS} DAY GROUP BY event ORDER BY c DESC LIMIT ${TOP_EVENTS_LIMIT}`
3601
+ } }, {
3602
+ headers: {
3603
+ Authorization: `Bearer ${accessToken}`,
3604
+ "User-Agent": WIZARD_USER_AGENT,
3605
+ "Content-Type": "application/json"
3606
+ },
3607
+ timeout: PROBE_TIMEOUT_MS
3608
+ })).data?.results;
3609
+ if (!Array.isArray(rows)) return [];
3610
+ return rows.map((row) => {
3611
+ if (!Array.isArray(row)) return null;
3612
+ const name = String(row[0] ?? "");
3613
+ const count = Number(row[1] ?? 0);
3614
+ if (!name || !Number.isFinite(count)) return null;
3615
+ return {
3616
+ name,
3617
+ count
3618
+ };
3619
+ }).filter((e) => e !== null);
3620
+ }
3621
+ /**
3622
+ * Best-effort existence check against a list endpoint. Resolves to:
3623
+ * true — at least one row exists
3624
+ * false — endpoint returned a clean empty list
3625
+ * null — inconclusive (error, timeout, unexpected shape)
3626
+ * Never throws; `null` lets the assembler default the product to present.
3627
+ */
3628
+ async function restExists(accessToken, projectId, baseUrl, resource) {
3629
+ try {
3630
+ const results = (await axios.get(`${baseUrl}/api/projects/${projectId}/${resource}/`, {
3631
+ params: { limit: 1 },
3632
+ headers: {
3633
+ Authorization: `Bearer ${accessToken}`,
3634
+ "User-Agent": WIZARD_USER_AGENT
3635
+ },
3636
+ timeout: PROBE_TIMEOUT_MS
3637
+ })).data?.results;
3638
+ if (!Array.isArray(results)) return null;
3639
+ return results.length > 0;
3640
+ } catch {
3641
+ return null;
3642
+ }
3643
+ }
3644
+ /**
3645
+ * Scout a project: one HogQL query for event volume + event names, plus a
3646
+ * handful of parallel REST existence checks for products that aren't
3647
+ * derivable from the event stream. Always resolves — returns a degraded
3648
+ * profile if the core events query fails.
3649
+ */
3650
+ async function probeProjectData(args) {
3651
+ const { accessToken, projectId, apiHost } = args;
3652
+ const baseUrl = apiHost.replace(/\/$/, "");
3653
+ try {
3654
+ const [events, sessionReplay, surveys, featureFlags, experiments, warehouse] = await Promise.all([
3655
+ queryTopEvents(accessToken, projectId, baseUrl),
3656
+ restExists(accessToken, projectId, baseUrl, "session_recordings"),
3657
+ restExists(accessToken, projectId, baseUrl, "surveys"),
3658
+ restExists(accessToken, projectId, baseUrl, "feature_flags"),
3659
+ restExists(accessToken, projectId, baseUrl, "experiments"),
3660
+ restExists(accessToken, projectId, baseUrl, "warehouse_tables")
3661
+ ]);
3662
+ const profile = assembleProfile(events, {
3663
+ sessionReplay,
3664
+ surveys,
3665
+ featureFlags,
3666
+ experiments,
3667
+ dataWarehouse: warehouse
3668
+ });
3669
+ logToFile(`[probeProjectData] tier=${profile.tier} totalEvents=${profile.totalEvents} distinct=${profile.distinctEventCount} custom=${profile.topCustomEvents.length}`);
3670
+ return profile;
3671
+ } catch (err) {
3672
+ logToFile(`[probeProjectData] degraded — probe failed: ${err instanceof Error ? err.message : String(err)}`);
3673
+ return degradedProfile();
3674
+ }
3675
+ }
3676
+ //#endregion
3256
3677
  //#region src/ui/tui/screens/McpSuggestedPromptsScreen.tsx
3257
3678
  /**
3258
3679
  * McpSuggestedPromptsScreen — shown after MCP install succeeds in the
@@ -3297,10 +3718,14 @@ const FOLLOW_UP_DELAY_MS = 3e3;
3297
3718
  const McpSuggestedPromptsScreen = ({ store, services }) => {
3298
3719
  useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
3299
3720
  const session = store.session;
3300
- const kit = getRolePrompts(session.roleAtOrganization, session.integration);
3301
- const crossSell = useMemo(() => getCrossSellPrompts(session.roleAtOrganization), [session.roleAtOrganization]);
3302
- const greeting = useMemo(() => getRoleGreeting(session.roleAtOrganization), [session.roleAtOrganization]);
3303
3721
  const [phase, setPhase] = useState("choose");
3722
+ const [profile, setProfile] = useState(null);
3723
+ const greeting = useMemo(() => getRoleGreeting(session.roleAtOrganization), [session.roleAtOrganization]);
3724
+ const pickerCandidates = useMemo(() => getTutorialPicker(session.roleAtOrganization, session.integration, profile), [
3725
+ session.roleAtOrganization,
3726
+ session.integration,
3727
+ profile
3728
+ ]);
3304
3729
  const [loginError, setLoginError] = useState(null);
3305
3730
  const startedTutorialRef = useRef(false);
3306
3731
  const [runningPrompt, setRunningPrompt] = useState(null);
@@ -3313,6 +3738,7 @@ const McpSuggestedPromptsScreen = ({ store, services }) => {
3313
3738
  const [lastToolCommand, setLastToolCommand] = useState(null);
3314
3739
  const [branchHistory, setBranchHistory] = useState([]);
3315
3740
  const runAbortRef = useRef(null);
3741
+ const seedStartedRef = useRef(false);
3316
3742
  const currentSessionIdRef = useRef(null);
3317
3743
  useEffect(() => {
3318
3744
  if (phase !== "authenticating") return;
@@ -3325,7 +3751,7 @@ const McpSuggestedPromptsScreen = ({ store, services }) => {
3325
3751
  store.setRoleAtOrganization(roleAtOrganization);
3326
3752
  store.setApiUser(user);
3327
3753
  store.setLoginUrl(null);
3328
- setPhase(startedTutorialRef.current ? "greeting" : "choose");
3754
+ setPhase(startedTutorialRef.current ? "scouting" : "choose");
3329
3755
  } catch (err) {
3330
3756
  if (cancelled) return;
3331
3757
  const message = err instanceof Error ? err.message : String(err);
@@ -3343,6 +3769,77 @@ const McpSuggestedPromptsScreen = ({ store, services }) => {
3343
3769
  services,
3344
3770
  store
3345
3771
  ]);
3772
+ useEffect(() => {
3773
+ if (phase !== "scouting") return;
3774
+ if (!session.credentials) return;
3775
+ let cancelled = false;
3776
+ const credentials = session.credentials;
3777
+ (async () => {
3778
+ let probed;
3779
+ try {
3780
+ probed = await services.probeProjectData(credentials);
3781
+ } catch (err) {
3782
+ logToFile(`[McpSuggestedPromptsScreen] probe failed: ${err instanceof Error ? err.message : String(err)}`);
3783
+ probed = degradedProfile();
3784
+ }
3785
+ if (cancelled) return;
3786
+ setProfile(probed);
3787
+ const offerSeed = probed.tier === "empty" && isKnownCloudHost(credentials.host.apiHost);
3788
+ analytics.wizardCapture("mcp suggested prompts scouted", {
3789
+ tier: probed.tier,
3790
+ totalEvents: probed.totalEvents,
3791
+ distinctEvents: probed.distinctEventCount,
3792
+ degraded: probed.degraded,
3793
+ offerSeed
3794
+ });
3795
+ setPhase(offerSeed ? "seed-offer" : "greeting");
3796
+ })();
3797
+ return () => {
3798
+ cancelled = true;
3799
+ };
3800
+ }, [
3801
+ phase,
3802
+ services,
3803
+ session.credentials
3804
+ ]);
3805
+ useEffect(() => {
3806
+ if (phase !== "seeding") return;
3807
+ if (seedStartedRef.current) return;
3808
+ if (!session.credentials || !profile) return;
3809
+ seedStartedRef.current = true;
3810
+ let cancelled = false;
3811
+ const controller = new AbortController();
3812
+ const credentials = session.credentials;
3813
+ const baseProfile = profile;
3814
+ (async () => {
3815
+ try {
3816
+ const seeded = await services.seedDemoEvents({
3817
+ credentials,
3818
+ baseProfile,
3819
+ signal: controller.signal
3820
+ });
3821
+ if (cancelled) return;
3822
+ setProfile(seeded);
3823
+ analytics.wizardCapture("mcp suggested prompts seeded", { totalEvents: seeded.totalEvents });
3824
+ } catch (err) {
3825
+ if (cancelled) return;
3826
+ const message = err instanceof Error ? err.message : String(err);
3827
+ logToFile(`[McpSuggestedPromptsScreen] seeding failed: ${message}`);
3828
+ analytics.wizardCapture("mcp suggested prompts seed failed", { error: message });
3829
+ }
3830
+ if (cancelled) return;
3831
+ setPhase("greeting");
3832
+ })();
3833
+ return () => {
3834
+ cancelled = true;
3835
+ controller.abort();
3836
+ };
3837
+ }, [
3838
+ phase,
3839
+ services,
3840
+ session.credentials,
3841
+ profile
3842
+ ]);
3346
3843
  useEffect(() => {
3347
3844
  if (phase !== "running") return;
3348
3845
  if (!runningPrompt) return;
@@ -3433,12 +3930,21 @@ const McpSuggestedPromptsScreen = ({ store, services }) => {
3433
3930
  if (choice === "login") {
3434
3931
  analytics.wizardCapture("mcp suggested prompts choose", { choice: "login" });
3435
3932
  startedTutorialRef.current = true;
3436
- setPhase(session.credentials ? "greeting" : "authenticating");
3933
+ setPhase(session.credentials ? "scouting" : "authenticating");
3437
3934
  } else {
3438
3935
  analytics.wizardCapture("mcp suggested prompts choose", { choice: "exit" });
3439
3936
  enterGoodbye();
3440
3937
  }
3441
3938
  };
3939
+ const handleSeedChoice = (value) => {
3940
+ if ((Array.isArray(value) ? value[0] : value) === "seed") {
3941
+ analytics.wizardCapture("mcp suggested prompts seed offer", { choice: "seed" });
3942
+ setPhase("seeding");
3943
+ } else {
3944
+ analytics.wizardCapture("mcp suggested prompts seed offer", { choice: "skip" });
3945
+ setPhase("greeting");
3946
+ }
3947
+ };
3442
3948
  const startRun = (prompt) => {
3443
3949
  setRunningPrompt(prompt);
3444
3950
  setRunCount((c) => c + 1);
@@ -3473,7 +3979,7 @@ const McpSuggestedPromptsScreen = ({ store, services }) => {
3473
3979
  handler: () => {
3474
3980
  if (phase === "goodbye") closeWizard();
3475
3981
  else if (phase === "authenticating") setPhase("choose");
3476
- else if (phase === "running" || phase === "prompt-picker" || phase === "follow-up" || phase === "greeting") enterGoodbye();
3982
+ else if (phase === "running" || phase === "prompt-picker" || phase === "follow-up" || phase === "greeting" || phase === "scouting" || phase === "seed-offer" || phase === "seeding") enterGoodbye();
3477
3983
  }
3478
3984
  },
3479
3985
  {
@@ -3509,14 +4015,16 @@ const McpSuggestedPromptsScreen = ({ store, services }) => {
3509
4015
  onSelect: handleChoice
3510
4016
  }),
3511
4017
  phase === "authenticating" && /* @__PURE__ */ jsx(AuthenticatingPhase, { loginUrl: session.loginUrl }),
4018
+ phase === "scouting" && /* @__PURE__ */ jsx(ScoutingPhase, {}),
4019
+ phase === "seed-offer" && /* @__PURE__ */ jsx(SeedOfferPhase, { onSelect: handleSeedChoice }),
4020
+ phase === "seeding" && /* @__PURE__ */ jsx(SeedingPhase, {}),
3512
4021
  phase === "greeting" && /* @__PURE__ */ jsx(GreetingPhase, {
3513
4022
  greeting,
3514
4023
  userDisplayName: session.apiUser?.first_name || null,
3515
4024
  onComplete: () => setPhase("prompt-picker")
3516
4025
  }),
3517
4026
  phase === "prompt-picker" && /* @__PURE__ */ jsx(PromptPickerPhase, {
3518
- promptKit: kit,
3519
- crossSell,
4027
+ candidates: pickerCandidates,
3520
4028
  onSelect: handlePromptPick
3521
4029
  }),
3522
4030
  phase === "running" && runningPrompt && /* @__PURE__ */ jsx(RunningPhase, {
@@ -3562,6 +4070,7 @@ const McpSuggestedPromptsScreen = ({ store, services }) => {
3562
4070
  installedClients: session.mcpInstalledClients,
3563
4071
  role: session.roleAtOrganization,
3564
4072
  integration: session.integration,
4073
+ profile,
3565
4074
  engaged: branchHistory.length > 0,
3566
4075
  onClose: closeWizard
3567
4076
  })
@@ -3655,6 +4164,90 @@ const AuthenticatingPhase = ({ loginUrl }) => /* @__PURE__ */ jsxs(Box, {
3655
4164
  ] })
3656
4165
  })]
3657
4166
  });
4167
+ const ScoutingPhase = () => /* @__PURE__ */ jsxs(Box, {
4168
+ flexDirection: "column",
4169
+ children: [/* @__PURE__ */ jsx(Box, {
4170
+ marginBottom: 1,
4171
+ children: /* @__PURE__ */ jsx(Text, {
4172
+ bold: true,
4173
+ color: Colors.accent,
4174
+ children: "MCP tutorial"
4175
+ })
4176
+ }), /* @__PURE__ */ jsx(LoadingBox, { message: "Scouting your project for data…" })]
4177
+ });
4178
+ const SeedOfferPhase = ({ onSelect }) => {
4179
+ const copy = getSeedOfferGreeting();
4180
+ return /* @__PURE__ */ jsxs(Box, {
4181
+ flexDirection: "column",
4182
+ children: [
4183
+ /* @__PURE__ */ jsx(Box, {
4184
+ marginBottom: 1,
4185
+ children: /* @__PURE__ */ jsx(Text, {
4186
+ bold: true,
4187
+ color: Colors.accent,
4188
+ children: copy.headline
4189
+ })
4190
+ }),
4191
+ /* @__PURE__ */ jsx(Box, {
4192
+ marginBottom: 1,
4193
+ flexDirection: "column",
4194
+ children: copy.bullets.map((bullet, i) => /* @__PURE__ */ jsxs(Text, { children: [
4195
+ /* @__PURE__ */ jsx(Text, {
4196
+ color: Colors.primary,
4197
+ children: Icons.diamond
4198
+ }),
4199
+ " ",
4200
+ /* @__PURE__ */ jsx(Text, {
4201
+ dimColor: true,
4202
+ children: bullet
4203
+ })
4204
+ ] }, i))
4205
+ }),
4206
+ /* @__PURE__ */ jsx(Box, {
4207
+ marginBottom: 1,
4208
+ children: /* @__PURE__ */ jsx(Text, { children: copy.outro })
4209
+ }),
4210
+ /* @__PURE__ */ jsx(PickerMenu, {
4211
+ options: [{
4212
+ label: "Yes — send demo data",
4213
+ value: "seed"
4214
+ }, {
4215
+ label: "No thanks, skip",
4216
+ value: "skip"
4217
+ }],
4218
+ onSelect
4219
+ }),
4220
+ /* @__PURE__ */ jsx(Box, {
4221
+ marginTop: 2,
4222
+ children: /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
4223
+ bold: true,
4224
+ children: "[esc]"
4225
+ }), /* @__PURE__ */ jsx(Text, { children: " to exit" })] })
4226
+ })
4227
+ ]
4228
+ });
4229
+ };
4230
+ const SeedingPhase = () => /* @__PURE__ */ jsxs(Box, {
4231
+ flexDirection: "column",
4232
+ children: [
4233
+ /* @__PURE__ */ jsx(Box, {
4234
+ marginBottom: 1,
4235
+ children: /* @__PURE__ */ jsx(Text, {
4236
+ bold: true,
4237
+ color: Colors.accent,
4238
+ children: "MCP tutorial"
4239
+ })
4240
+ }),
4241
+ /* @__PURE__ */ jsx(LoadingBox, { message: "Sending demo events to your project…" }),
4242
+ /* @__PURE__ */ jsx(Box, {
4243
+ marginTop: 1,
4244
+ children: /* @__PURE__ */ jsx(Text, {
4245
+ dimColor: true,
4246
+ children: "Backdated across the last 7 days, so your funnels and trends light up right away."
4247
+ })
4248
+ })
4249
+ ]
4250
+ });
3658
4251
  const GreetingPhase = ({ greeting, userDisplayName, onComplete }) => {
3659
4252
  const blocks = [];
3660
4253
  if (userDisplayName) blocks.push({
@@ -3708,13 +4301,9 @@ const GreetingPhase = ({ greeting, userDisplayName, onComplete }) => {
3708
4301
  })]
3709
4302
  });
3710
4303
  };
3711
- const PromptPickerPhase = ({ promptKit, crossSell, onSelect }) => {
4304
+ const PromptPickerPhase = ({ candidates, onSelect }) => {
3712
4305
  const seenPrompts = /* @__PURE__ */ new Set();
3713
- const options = [
3714
- PINNED_FIRST_PROMPT,
3715
- ...crossSell,
3716
- ...promptKit
3717
- ].filter((o) => {
4306
+ const options = candidates.filter((o) => {
3718
4307
  if (seenPrompts.has(o.prompt)) return false;
3719
4308
  seenPrompts.add(o.prompt);
3720
4309
  return true;
@@ -3912,8 +4501,9 @@ const FollowUpPhase = ({ lastToolName, lastToolCommand, lastPrompt, chunks, role
3912
4501
  })]
3913
4502
  });
3914
4503
  };
3915
- const GoodbyePhase = ({ installedClients, role, integration, engaged, onClose }) => {
3916
- const samples = getRolePrompts(role, integration).slice(0, 3);
4504
+ const GoodbyePhase = ({ installedClients, role, integration, profile, engaged, onClose }) => {
4505
+ const generated = getGeneratedQuests(profile);
4506
+ const samples = (generated.length > 0 ? generated : getRolePrompts(role, integration)).slice(0, 3);
3917
4507
  const headline = engaged ? "Nice work. You can keep talking to PostHog anytime." : "You're all set — PostHog MCP is here when you're ready.";
3918
4508
  const introLine = installedClients.length > 0 ? /* @__PURE__ */ jsxs(Text, { children: [
3919
4509
  "MCP is set up in",
@@ -3955,6 +4545,26 @@ const GoodbyePhase = ({ installedClients, role, integration, engaged, onClose })
3955
4545
  })
3956
4546
  ] }, i))
3957
4547
  }),
4548
+ profile?.seeded && /* @__PURE__ */ jsx(Box, {
4549
+ marginBottom: 1,
4550
+ children: /* @__PURE__ */ jsxs(Text, {
4551
+ dimColor: true,
4552
+ children: [
4553
+ "Demo data is tagged ",
4554
+ /* @__PURE__ */ jsx(Text, {
4555
+ bold: true,
4556
+ children: "wizard_seed:true"
4557
+ }),
4558
+ " and uses",
4559
+ " ",
4560
+ /* @__PURE__ */ jsx(Text, {
4561
+ bold: true,
4562
+ children: "wizard-demo-user-*"
4563
+ }),
4564
+ " distinct IDs — events are immutable in PostHog, but you can filter these out of any query or dashboard."
4565
+ ]
4566
+ })
4567
+ }),
3958
4568
  /* @__PURE__ */ jsx(Box, {
3959
4569
  marginBottom: 1,
3960
4570
  children: /* @__PURE__ */ jsxs(Text, {
@@ -3981,6 +4591,140 @@ const GoodbyePhase = ({ installedClients, role, integration, engaged, onClose })
3981
4591
  });
3982
4592
  };
3983
4593
  //#endregion
4594
+ //#region src/lib/mcp-seed-events.ts
4595
+ /**
4596
+ * Demo-event seeder for the MCP tutorial's empty-project path.
4597
+ *
4598
+ * When the scout (`probeProjectData`) finds a project with zero events, the
4599
+ * tutorial offers to send a small, coherent demo dataset so the read
4600
+ * quests have something real to chew on instead of dead-ending. The
4601
+ * dataset is a recognizable SaaS product funnel —
4602
+ *
4603
+ * $pageview → signed_up → activated → feature_used → upgraded_to_paid
4604
+ *
4605
+ * — spread across ~12 synthetic users and backdated over the last 7 days,
4606
+ * so time-windowed funnels and trends include the data the moment it lands
4607
+ * (sidestepping ingestion lag for the queries the tutorial actually runs).
4608
+ *
4609
+ * Every seeded event carries `wizard_seed: true` and a synthetic
4610
+ * `wizard-demo-user-*` distinct id, so the user can find and clean up the
4611
+ * demo data from a single search in PostHog — the same "leave a trail"
4612
+ * principle as the `(wizard MCP tutorial)` artifact tagging.
4613
+ *
4614
+ * `$exception` is deliberately NOT seeded: leaving error tracking empty
4615
+ * keeps it available as an "enable error tracking" activation cross-sell,
4616
+ * which is exactly the kind of product-discovery beat the empty path is
4617
+ * meant to create.
4618
+ */
4619
+ const SEED_USER_COUNT = 12;
4620
+ const DAY_MS = 1440 * 60 * 1e3;
4621
+ const demoUser = (i) => `wizard-demo-user-${i + 1}`;
4622
+ /**
4623
+ * A timestamp `daysAgo` days plus `hour` hours BEFORE `now`. Both offsets
4624
+ * are subtracted so every seeded event lands strictly in the past (daysAgo
4625
+ * maxes at 6 and hour at ~15, keeping the oldest event under the 7-day
4626
+ * window the tutorial's queries use).
4627
+ */
4628
+ const at = (now, daysAgo, hour) => /* @__PURE__ */ new Date(now.getTime() - daysAgo * DAY_MS - hour * 60 * 60 * 1e3);
4629
+ /**
4630
+ * Build the deterministic demo dataset. Deterministic (no randomness) so
4631
+ * the returned profile counts always match what was sent and tests are
4632
+ * stable. Drop-off down the funnel is modelled by fewer users reaching
4633
+ * each step.
4634
+ */
4635
+ function buildSeedEvents(now) {
4636
+ const events = [];
4637
+ const push = (event, userIdx, daysAgo, hour, properties = {}) => {
4638
+ events.push({
4639
+ event,
4640
+ distinctId: demoUser(userIdx),
4641
+ timestamp: at(now, daysAgo, hour),
4642
+ properties: {
4643
+ ...properties,
4644
+ wizard_seed: true
4645
+ }
4646
+ });
4647
+ };
4648
+ const PAGES = [
4649
+ "/",
4650
+ "/pricing",
4651
+ "/signup",
4652
+ "/app"
4653
+ ];
4654
+ for (let i = 0; i < SEED_USER_COUNT; i++) for (let k = 0; k < 4; k++) push("$pageview", i, (i + k) % 7, 9 + k, { $current_url: `https://example.com${PAGES[k % PAGES.length]}` });
4655
+ for (let i = 0; i < 10; i++) push("signed_up", i, 6 - i % 7, 10, { source: i % 2 ? "organic" : "ads" });
4656
+ for (let i = 0; i < 7; i++) push("activated", i, 5 - i % 6, 11);
4657
+ const FEATURES = [
4658
+ "dashboards",
4659
+ "insights",
4660
+ "session_replay"
4661
+ ];
4662
+ for (let i = 0; i < 7; i++) for (let k = 0; k < 3; k++) push("feature_used", i, 4 - (i + k) % 5, 12 + k, { feature: FEATURES[k % FEATURES.length] });
4663
+ for (let i = 0; i < 3; i++) push("upgraded_to_paid", i, 2 - i % 3, 13, { plan: "pro" });
4664
+ return events;
4665
+ }
4666
+ /** Tally a seed-event list into volume-sorted `EventVolume[]`. */
4667
+ function tallySeedEvents(events) {
4668
+ const counts = /* @__PURE__ */ new Map();
4669
+ for (const e of events) counts.set(e.event, (counts.get(e.event) ?? 0) + 1);
4670
+ return [...counts.entries()].map(([name, count]) => ({
4671
+ name,
4672
+ count
4673
+ })).sort((a, b) => b.count - a.count);
4674
+ }
4675
+ /**
4676
+ * The profile a successful seed produces, without sending anything.
4677
+ * Event-derived fields come from the demo dataset; REST-derived product
4678
+ * presence is carried forward from the pre-seed probe (so a project that
4679
+ * already had, say, feature flags defined keeps that signal). Exported so
4680
+ * the screen can preview the seeded state and tests can assert on it.
4681
+ */
4682
+ function seededProfile(baseProfile, now) {
4683
+ return assembleProfile(tallySeedEvents(buildSeedEvents(now)), {
4684
+ sessionReplay: baseProfile.products.sessionReplay,
4685
+ surveys: baseProfile.products.surveys,
4686
+ featureFlags: baseProfile.products.featureFlags,
4687
+ experiments: baseProfile.products.experiments,
4688
+ dataWarehouse: baseProfile.products.dataWarehouse
4689
+ }, { seeded: true });
4690
+ }
4691
+ /**
4692
+ * Send the demo dataset to the project's ingestion endpoint via
4693
+ * posthog-node, then resolve with the seeded profile. Throws if the send
4694
+ * fails so the caller can surface a soft error and fall back to the
4695
+ * write-only empty path.
4696
+ */
4697
+ async function seedDemoEvents(args) {
4698
+ const { projectApiKey, apiHost, baseProfile, signal } = args;
4699
+ const now = args.now ?? /* @__PURE__ */ new Date();
4700
+ if (signal?.aborted) throw new Error("Seeding aborted");
4701
+ const events = buildSeedEvents(now);
4702
+ logToFile(`[seedDemoEvents] sending ${events.length} demo events to ${apiHost}`);
4703
+ const { PostHog } = await import("posthog-node");
4704
+ const client = new PostHog(projectApiKey, {
4705
+ host: apiHost,
4706
+ flushAt: events.length,
4707
+ flushInterval: 0,
4708
+ disableGeoip: true
4709
+ });
4710
+ try {
4711
+ for (const e of events) client.capture({
4712
+ distinctId: e.distinctId,
4713
+ event: e.event,
4714
+ properties: e.properties,
4715
+ timestamp: e.timestamp
4716
+ });
4717
+ await client.shutdown();
4718
+ } catch (err) {
4719
+ try {
4720
+ await client.shutdown();
4721
+ } catch {}
4722
+ throw err;
4723
+ }
4724
+ if (signal?.aborted) throw new Error("Seeding aborted");
4725
+ return seededProfile(baseProfile, now);
4726
+ }
4727
+ //#endregion
3984
4728
  //#region src/ui/tui/screens/audit/AuditChecksViewer/AreaHeaderRow.tsx
3985
4729
  /** Sub-header row inside the scrollable body — one per area group. */
3986
4730
  const AreaHeaderRow = ({ area, resolved, total }) => /* @__PURE__ */ jsxs(Box, {
@@ -5510,6 +6254,6 @@ function releaseTerminal() {
5510
6254
  process.stdout.write(RESET_ATTRS + LEAVE_ALT_SCREEN);
5511
6255
  }
5512
6256
  //#endregion
5513
- export { ConfirmationInput as A, TabContainer as C, LinkText as D, LogViewer as E, SplitView as F, CardLayout as I, useStdoutDimensions as M, ProgressList as N, extractUrls as O, LoadingBox as P, HNViewer as S, EventPlanViewer as T, SEVERITY_ORDER as _, useSkillEntry as a, LearnCard as b, SlackConnectScreen as c, AuditChecksViewer as d, McpSuggestedPromptsScreen as f, SEVERITY_LABEL as g, IssueTable as h, SkillSourceInfo as i, GroupedPickerMenu as j, ModalOverlay as k, AUDIT_AREA_SLIDES as l, McpScreen as m, releaseTerminal as n, OutroScreen as o, TAILORED_ROLES as p, AiOptInRequiredScreen as r, useDismissOnAnyKey as s, enterDarkTerminal as t, VisualBox as u, ServiceHealthList as v, ScreenContainer as w, ContentSequencer as x, TipsCard as y };
6257
+ export { LogViewer as A, TipsCard as C, TabContainer as D, HNViewer as E, GroupedPickerMenu as F, ProgressList as I, LoadingBox as L, extractUrls as M, ModalOverlay as N, ScreenContainer as O, ConfirmationInput as P, SplitView as R, ServiceHealthList as S, ContentSequencer as T, TAILORED_ROLES as _, useSkillEntry as a, SEVERITY_LABEL as b, SlackConnectScreen as c, AuditChecksViewer as d, seedDemoEvents as f, probeProjectData as g, assembleProfile as h, SkillSourceInfo as i, LinkText as j, EventPlanViewer as k, AUDIT_AREA_SLIDES as l, McpSuggestedPromptsScreen as m, releaseTerminal as n, OutroScreen as o, seededProfile as p, AiOptInRequiredScreen as r, useDismissOnAnyKey as s, enterDarkTerminal as t, VisualBox as u, McpScreen as v, LearnCard as w, SEVERITY_ORDER as x, IssueTable as y, CardLayout as z };
5514
6258
 
5515
- //# sourceMappingURL=terminal-tbxhJGlI.js.map
6259
+ //# sourceMappingURL=terminal-q-Sd8CdO.js.map