@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.
- package/dist/{add-mcp-server-to-clients-DJyWMMGj.js → add-mcp-server-to-clients-DmMHsYYM.js} +5 -5
- package/dist/{add-mcp-server-to-clients-DJyWMMGj.js.map → add-mcp-server-to-clients-DmMHsYYM.js.map} +1 -1
- package/dist/{agent-interface-DoFVHRFh.js → agent-interface-xWyATfJi.js} +330 -25
- package/dist/agent-interface-xWyATfJi.js.map +1 -0
- package/dist/{agent-prompt-loader-D-cPVoL5.js → agent-prompt-loader-i6miJlmN.js} +5 -5
- package/dist/{agent-prompt-loader-D-cPVoL5.js.map → agent-prompt-loader-i6miJlmN.js.map} +1 -1
- package/dist/{agent-runner-B1qCtjid.js → agent-runner-CPuubcP_.js} +157 -137
- package/dist/agent-runner-CPuubcP_.js.map +1 -0
- package/dist/{analytics-BXz4SIuS.js → analytics-BC3iiS1p.js} +5 -2
- package/dist/analytics-BC3iiS1p.js.map +1 -0
- package/dist/analytics-CLuGZo9D.js +2 -0
- package/dist/{api-BaacjH-Y.js → api-CXk-efCU.js} +3 -3
- package/dist/{api-BaacjH-Y.js.map → api-CXk-efCU.js.map} +1 -1
- package/dist/bin.js +316 -179
- package/dist/bin.js.map +1 -1
- package/dist/{bounded-fs-Cx-8nk-1.js → bounded-fs-B6Eqbgoj.js} +3 -3
- package/dist/{bounded-fs-Cx-8nk-1.js.map → bounded-fs-B6Eqbgoj.js.map} +1 -1
- package/dist/{ci-install-D1Mum8Ah.js → ci-install-CXD2meJ4.js} +5 -5
- package/dist/{ci-install-D1Mum8Ah.js.map → ci-install-CXD2meJ4.js.map} +1 -1
- package/dist/{debug-DsSCOPe8.js → debug-Bz-LaIU8.js} +1 -1
- package/dist/{debug-C3ZR1TVF.js → debug-tCr3A18e.js} +13 -4
- package/dist/debug-tCr3A18e.js.map +1 -0
- package/dist/{defaults-DAE9_QMn.js → defaults-BWLIffh5.js} +1 -1
- package/dist/{defaults-DAE9_QMn.js.map → defaults-BWLIffh5.js.map} +1 -1
- package/dist/{env-api-key-DDVkPQmo.js → env-api-key-cjD4yfGT.js} +1 -1
- package/dist/{env-api-key-DDVkPQmo.js.map → env-api-key-cjD4yfGT.js.map} +1 -1
- package/dist/{environment-Bi-zynk6.js → environment-JwRHoXao.js} +4 -4
- package/dist/{environment-Bi-zynk6.js.map → environment-JwRHoXao.js.map} +1 -1
- package/dist/{file-watcher-PmRIDAfM.js → file-watcher-Cm-B3jyb.js} +2 -2
- package/dist/{file-watcher-PmRIDAfM.js.map → file-watcher-Cm-B3jyb.js.map} +1 -1
- package/dist/{headless-ui-C4X9lAOY.js → headless-ui-o1cpGr1W.js} +2 -2
- package/dist/{headless-ui-C4X9lAOY.js.map → headless-ui-o1cpGr1W.js.map} +1 -1
- package/dist/{interactive-HM71vLQe.js → interactive-CM2fMt8l.js} +3 -3
- package/dist/{interactive-HM71vLQe.js.map → interactive-CM2fMt8l.js.map} +1 -1
- package/dist/{mcp-D0Hpb_nk.js → mcp-C4Xn8mq5.js} +2 -2
- package/dist/{mcp-D0Hpb_nk.js.map → mcp-C4Xn8mq5.js.map} +1 -1
- package/dist/{mcp-prompt-streaming-Crk8qFlQ.js → mcp-prompt-streaming-Dqrcns_Z.js} +5 -4
- package/dist/{mcp-prompt-streaming-Crk8qFlQ.js.map → mcp-prompt-streaming-Dqrcns_Z.js.map} +1 -1
- package/dist/{non-interactive-DdUe8lqX.js → non-interactive-DYvEgWZx.js} +2 -2
- package/dist/{non-interactive-DdUe8lqX.js.map → non-interactive-DYvEgWZx.js.map} +1 -1
- package/dist/{orchestrator-tools-CpRruqZl.js → orchestrator-tools-CAl7aLtB.js} +3 -3
- package/dist/{orchestrator-tools-CpRruqZl.js.map → orchestrator-tools-CAl7aLtB.js.map} +1 -1
- package/dist/{package-manager-wkSzPJqW.js → package-manager-DnU0j41n.js} +2 -2
- package/dist/{package-manager-wkSzPJqW.js.map → package-manager-DnU0j41n.js.map} +1 -1
- package/dist/pi-2hh_NXKT.js +409 -0
- package/dist/pi-2hh_NXKT.js.map +1 -0
- package/dist/{playground-lg7h9GHG.js → playground-rG007Ve9.js} +353 -18
- package/dist/playground-rG007Ve9.js.map +1 -0
- package/dist/{posthog-mgz-FQlS.js → posthog-DDLIrku0.js} +1 -1
- package/dist/{posthog-mgz-FQlS.js.map → posthog-DDLIrku0.js.map} +1 -1
- package/dist/{posthog-integration-pnxhTbxw.js → posthog-integration-Bd-kybes.js} +15 -15
- package/dist/{posthog-integration-pnxhTbxw.js.map → posthog-integration-Bd-kybes.js.map} +1 -1
- package/dist/{provisioning-T4jtJqBa.js → provisioning-tkKe6aM4.js} +3 -3
- package/dist/{provisioning-T4jtJqBa.js.map → provisioning-tkKe6aM4.js.map} +1 -1
- package/dist/{queue-tools-D7ctuzyo.js → queue-tools-kBa-8bPH.js} +45 -32
- package/dist/queue-tools-kBa-8bPH.js.map +1 -0
- package/dist/{registry-CsFuKtXn.js → registry-Cg_M-kov.js} +5 -5
- package/dist/{registry-CsFuKtXn.js.map → registry-Cg_M-kov.js.map} +1 -1
- package/dist/{security-YAaZfmHs.js → security-BtIAMdyk.js} +5 -5
- package/dist/security-BtIAMdyk.js.map +1 -0
- package/dist/{setup-utils-DDzEnDxJ.js → setup-utils-t0jzKwBE.js} +8 -8
- package/dist/{setup-utils-DDzEnDxJ.js.map → setup-utils-t0jzKwBE.js.map} +1 -1
- package/dist/{start-tui-pCi7WVAK.js → start-tui-CK3rpaKD.js} +35 -21
- package/dist/start-tui-CK3rpaKD.js.map +1 -0
- package/dist/{steps-DmsrBqb9.js → steps-dNXQiyOZ.js} +7 -7
- package/dist/{steps-DmsrBqb9.js.map → steps-dNXQiyOZ.js.map} +1 -1
- package/dist/{store-BCRdtfOH.js → store-qv5UdGQO.js} +5 -5
- package/dist/{store-BCRdtfOH.js.map → store-qv5UdGQO.js.map} +1 -1
- package/dist/{subagent-jNXS8IPU.js → subagent-DsfcNf0P.js} +2 -2
- package/dist/{subagent-jNXS8IPU.js.map → subagent-DsfcNf0P.js.map} +1 -1
- package/dist/{task-CSmrfyNf.js → task-BZ77d7rm.js} +30 -23
- package/dist/task-BZ77d7rm.js.map +1 -0
- package/dist/{task-stream-BZOjOhKu.js → task-stream-5k_cd_Wz.js} +4 -4
- package/dist/{task-stream-BZOjOhKu.js.map → task-stream-5k_cd_Wz.js.map} +1 -1
- package/dist/{tasks-BMgQt251.js → tasks-C9yxUVN7.js} +2 -2
- package/dist/{tasks-BMgQt251.js.map → tasks-C9yxUVN7.js.map} +1 -1
- package/dist/{telemetry-qeKIGkte.js → telemetry-5NqJBWNN.js} +3 -3
- package/dist/{telemetry-qeKIGkte.js.map → telemetry-5NqJBWNN.js.map} +1 -1
- package/dist/{terminal-tbxhJGlI.js → terminal-q-Sd8CdO.js} +806 -62
- package/dist/terminal-q-Sd8CdO.js.map +1 -0
- package/dist/{tools-DVnBIYFn.js → tools-2weX1XHC.js} +8 -10
- package/dist/tools-2weX1XHC.js.map +1 -0
- package/dist/warlock-smoke-test.ts +177 -1
- package/dist/{wizard-abort-AEhoDJ4P.js → wizard-abort-B1XewmPn.js} +3 -3
- package/dist/{wizard-abort-AEhoDJ4P.js.map → wizard-abort-B1XewmPn.js.map} +1 -1
- package/dist/{wizard-abort-DHC_XStW.js → wizard-abort-Cl8D3gW8.js} +1 -1
- package/dist/yara-hooks-Cj7xUAKE.js +2 -0
- package/dist/{yara-hooks-CKsVxWiN.js → yara-hooks-Cu0rEw1L.js} +39 -83
- package/dist/yara-hooks-Cu0rEw1L.js.map +1 -0
- package/package.json +2 -1
- package/dist/agent-interface-DoFVHRFh.js.map +0 -1
- package/dist/agent-runner-B1qCtjid.js.map +0 -1
- package/dist/analytics-BXz4SIuS.js.map +0 -1
- package/dist/analytics-D2xds3nw.js +0 -2
- package/dist/commandments-BagyrYRW.js +0 -2
- package/dist/commandments-DiMtIz1o.js +0 -42
- package/dist/commandments-DiMtIz1o.js.map +0 -1
- package/dist/debug-C3ZR1TVF.js.map +0 -1
- package/dist/pi-CGPjm4oE.js +0 -570
- package/dist/pi-CGPjm4oE.js.map +0 -1
- package/dist/playground-lg7h9GHG.js.map +0 -1
- package/dist/queue-tools-D7ctuzyo.js.map +0 -1
- package/dist/security-YAaZfmHs.js.map +0 -1
- package/dist/start-tui-pCi7WVAK.js.map +0 -1
- package/dist/task-CSmrfyNf.js.map +0 -1
- package/dist/terminal-tbxhJGlI.js.map +0 -1
- package/dist/tools-DVnBIYFn.js.map +0 -1
- package/dist/yara-hooks-CKsVxWiN.js.map +0 -1
- package/dist/yara-hooks-DWRdZQuG.js +0 -2
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as analytics } from "./analytics-
|
|
3
|
-
import { i as withUtm, n as openTrackedLink } from "./telemetry-
|
|
4
|
-
import { t as getOrAskForProjectData } from "./setup-utils-
|
|
5
|
-
import { a as fetchUserData, i as fetchSlackConnected } from "./api-
|
|
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-
|
|
8
|
-
import {
|
|
9
|
-
import { y as AUDIT_SEVERITY_STYLE } from "./yara-hooks-
|
|
10
|
-
import { c as TextBlock, d as
|
|
11
|
-
import { a as
|
|
12
|
-
import { r as totalTokenCount } from "./store-
|
|
13
|
-
import { n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-
|
|
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
|
|
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 ? "
|
|
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 ? "
|
|
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
|
-
|
|
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 = ({
|
|
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
|
|
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 {
|
|
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-
|
|
6259
|
+
//# sourceMappingURL=terminal-q-Sd8CdO.js.map
|