@posthog/wizard 2.16.0 → 2.17.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 (88) hide show
  1. package/README.md +0 -4
  2. package/dist/{TextBlock-DJVhBkr3.js → TextBlock-D0Ep3zC9.js} +2 -2
  3. package/dist/{TextBlock-DJVhBkr3.js.map → TextBlock-D0Ep3zC9.js.map} +1 -1
  4. package/dist/{add-mcp-server-to-clients-9jQjc-CO.js → add-mcp-server-to-clients-D4PK6ulR.js} +39 -6
  5. package/dist/add-mcp-server-to-clients-D4PK6ulR.js.map +1 -0
  6. package/dist/{agent-interface-pBnqJL8P.js → agent-interface-7t5DBo2A.js} +34 -8
  7. package/dist/agent-interface-7t5DBo2A.js.map +1 -0
  8. package/dist/{agent-runner-H1FP6XTc.js → agent-runner-CTkKLVhp.js} +17 -21
  9. package/dist/{agent-runner-H1FP6XTc.js.map → agent-runner-CTkKLVhp.js.map} +1 -1
  10. package/dist/{analytics-DZaUgJte.js → analytics-DN_Gy87F.js} +3 -3
  11. package/dist/{analytics-DZaUgJte.js.map → analytics-DN_Gy87F.js.map} +1 -1
  12. package/dist/api-serd0SMY.js +148 -0
  13. package/dist/api-serd0SMY.js.map +1 -0
  14. package/dist/bin.js +764 -497
  15. package/dist/bin.js.map +1 -1
  16. package/dist/ci-install-BbJ7c3WK.js +73 -0
  17. package/dist/ci-install-BbJ7c3WK.js.map +1 -0
  18. package/dist/{debug-B6rX6xye.js → debug-BI-Js0PB.js} +1 -1
  19. package/dist/{debug-C4jRuzny.js → debug-Bx7nvCWW.js} +12 -6
  20. package/dist/debug-Bx7nvCWW.js.map +1 -0
  21. package/dist/{defaults-GbLPuHxj.js → defaults-CPH6eWhN.js} +1 -1
  22. package/dist/{defaults-GbLPuHxj.js.map → defaults-CPH6eWhN.js.map} +1 -1
  23. package/dist/{env-api-key-DU8uIEvo.js → env-api-key-B3gE9Un0.js} +4 -2
  24. package/dist/{env-api-key-DU8uIEvo.js.map → env-api-key-B3gE9Un0.js.map} +1 -1
  25. package/dist/environment-CiZVSSYt.js +22 -0
  26. package/dist/environment-CiZVSSYt.js.map +1 -0
  27. package/dist/{file-utils-DnTSiTJw.js → file-utils-Dy9JncCo.js} +1 -1
  28. package/dist/{file-utils-DnTSiTJw.js.map → file-utils-Dy9JncCo.js.map} +1 -1
  29. package/dist/interactive-BwIzklw0.js +11 -0
  30. package/dist/interactive-BwIzklw0.js.map +1 -0
  31. package/dist/{mcp-prompt-streaming-DKiaymMt.js → mcp-prompt-streaming-8U9Qs9EV.js} +62 -12
  32. package/dist/mcp-prompt-streaming-8U9Qs9EV.js.map +1 -0
  33. package/dist/non-interactive-DTaZnVq_.js +12 -0
  34. package/dist/non-interactive-DTaZnVq_.js.map +1 -0
  35. package/dist/{package-json-v_g2YlN1.js → package-json-DCuoye-H.js} +8 -2
  36. package/dist/{package-json-v_g2YlN1.js.map → package-json-DCuoye-H.js.map} +1 -1
  37. package/dist/{package-manager-DLt75bit.js → package-manager-CKQLR20D.js} +2 -2
  38. package/dist/{package-manager-DLt75bit.js.map → package-manager-CKQLR20D.js.map} +1 -1
  39. package/dist/{start-playground-B40O4tye.js → playground-CR81Mwe3.js} +31 -14
  40. package/dist/playground-CR81Mwe3.js.map +1 -0
  41. package/dist/{posthog-7B92c2Ed.js → posthog-BrLFkaji.js} +1 -1
  42. package/dist/{posthog-7B92c2Ed.js.map → posthog-BrLFkaji.js.map} +1 -1
  43. package/dist/{posthog-integration-CukaeYil.js → posthog-integration-Bv7987YJ.js} +230 -21
  44. package/dist/posthog-integration-Bv7987YJ.js.map +1 -0
  45. package/dist/{provisioning-C_ETLiZE.js → provisioning-C96Kw-9D.js} +9 -4
  46. package/dist/{provisioning-C_ETLiZE.js.map → provisioning-C96Kw-9D.js.map} +1 -1
  47. package/dist/{registry-DqbwO5EL.js → registry-B9k73FKR.js} +5 -5
  48. package/dist/{registry-DqbwO5EL.js.map → registry-B9k73FKR.js.map} +1 -1
  49. package/dist/{setup-utils-DdAdxUTV.js → setup-utils-Bpfsap9L.js} +80 -175
  50. package/dist/setup-utils-Bpfsap9L.js.map +1 -0
  51. package/dist/skill-CPqcV8zp.js +29 -0
  52. package/dist/skill-CPqcV8zp.js.map +1 -0
  53. package/dist/{slides-Dpj4j0w_.js → slides-DRbBgsdd.js} +1733 -384
  54. package/dist/slides-DRbBgsdd.js.map +1 -0
  55. package/dist/{start-tui-CH_ZzQXx.js → start-tui-BZ7rEf3e.js} +432 -31
  56. package/dist/start-tui-BZ7rEf3e.js.map +1 -0
  57. package/dist/{steps-0d9XqvI6.js → steps-DDx35170.js} +6 -6
  58. package/dist/{steps-0d9XqvI6.js.map → steps-DDx35170.js.map} +1 -1
  59. package/dist/{task-stream-CoEsidgG.js → task-stream-BI8rJg9H.js} +3 -3
  60. package/dist/{task-stream-CoEsidgG.js.map → task-stream-BI8rJg9H.js.map} +1 -1
  61. package/dist/{telemetry-jn2Daxl2.js → telemetry-ByYtIfW0.js} +2 -2
  62. package/dist/{telemetry-jn2Daxl2.js.map → telemetry-ByYtIfW0.js.map} +1 -1
  63. package/dist/urls-CTCJIxbR.js +35 -0
  64. package/dist/urls-CTCJIxbR.js.map +1 -0
  65. package/dist/{wizard-abort-BjLIgu2s.js → wizard-abort-CY0ibdq1.js} +3 -3
  66. package/dist/{wizard-abort-BjLIgu2s.js.map → wizard-abort-CY0ibdq1.js.map} +1 -1
  67. package/dist/{wizard-abort-BlYGA1Jk.js → wizard-abort-QdRxGQp_.js} +1 -1
  68. package/dist/{wizard-session-Bi95IYca.js → wizard-session-d27JGRGi.js} +2 -3
  69. package/dist/wizard-session-d27JGRGi.js.map +1 -0
  70. package/dist/{wizard-session-DPGTaJ4W.js → wizard-session-y304gEEI.js} +1 -1
  71. package/dist/wizard-ui-YdGFRyu_.js.map +1 -1
  72. package/package.json +1 -1
  73. package/dist/add-mcp-server-to-clients-9jQjc-CO.js.map +0 -1
  74. package/dist/agent-interface-pBnqJL8P.js.map +0 -1
  75. package/dist/analytics-DqeW7XYt.js +0 -2
  76. package/dist/debug-C4jRuzny.js.map +0 -1
  77. package/dist/detection-4eukp9HD.js +0 -206
  78. package/dist/detection-4eukp9HD.js.map +0 -1
  79. package/dist/mcp-prompt-streaming-DKiaymMt.js.map +0 -1
  80. package/dist/package-json-Cttzi3C8.js +0 -2
  81. package/dist/posthog-integration-CukaeYil.js.map +0 -1
  82. package/dist/provisioning-Ch6i8dRV.js +0 -2
  83. package/dist/setup-utils-C5uZ9g60.js +0 -2
  84. package/dist/setup-utils-DdAdxUTV.js.map +0 -1
  85. package/dist/slides-Dpj4j0w_.js.map +0 -1
  86. package/dist/start-playground-B40O4tye.js.map +0 -1
  87. package/dist/start-tui-CH_ZzQXx.js.map +0 -1
  88. package/dist/wizard-session-Bi95IYca.js.map +0 -1
@@ -1,11 +1,11 @@
1
- import { g as SERVICE_LABELS, s as logToFile } from "./debug-C4jRuzny.js";
1
+ import { g as SERVICE_LABELS, s as logToFile } from "./debug-Bx7nvCWW.js";
2
2
  import { n as isTaskStatus } from "./wizard-ui-YdGFRyu_.js";
3
- import { n as analytics, r as sessionProperties } from "./analytics-DZaUgJte.js";
4
- import { i as buildSession } from "./wizard-session-Bi95IYca.js";
5
- import { _ as AUDIT_SEVERITY_STYLE } from "./agent-interface-pBnqJL8P.js";
6
- import { a as isObjectBlock, i as isLinesBlock, n as computeVisibleRange, o as Colors, r as isClearBlock, s as Icons, t as TextBlock } from "./TextBlock-DJVhBkr3.js";
7
- import { n as Program, r as getProgramConfig, s as getKindMeta, t as PROGRAM_REGISTRY } from "./bin.js";
8
- import { n as AVAILABLE_FEATURES, t as ALL_FEATURE_VALUES } from "./defaults-GbLPuHxj.js";
3
+ import { n as sessionProperties, t as analytics } from "./analytics-DN_Gy87F.js";
4
+ import { i as buildSession } from "./wizard-session-d27JGRGi.js";
5
+ import { v as AUDIT_SEVERITY_STYLE } from "./agent-interface-7t5DBo2A.js";
6
+ import { a as isObjectBlock, i as isLinesBlock, n as computeVisibleRange, o as Colors, r as isClearBlock, s as Icons, t as TextBlock } from "./TextBlock-D0Ep3zC9.js";
7
+ import { a as getProgramConfig, i as Program, l as getKindMeta, r as PROGRAM_REGISTRY } from "./bin.js";
8
+ import { n as AVAILABLE_FEATURES, t as ALL_FEATURE_VALUES } from "./defaults-CPH6eWhN.js";
9
9
  import * as fs$1 from "fs";
10
10
  import { Box, Text, measureElement, useInput, useStdout } from "ink";
11
11
  import { Component, Fragment, createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
@@ -487,9 +487,15 @@ var WizardStore = class {
487
487
  this.emitChange();
488
488
  }
489
489
  setDashboardUrl(url) {
490
+ logToFile(`store.setDashboardUrl: ${url}`);
490
491
  this.$session.setKey("dashboardUrl", url);
491
492
  this.emitChange();
492
493
  }
494
+ setNotebookUrl(url) {
495
+ logToFile(`store.setNotebookUrl: ${url}`);
496
+ this.$session.setKey("notebookUrl", url);
497
+ this.emitChange();
498
+ }
493
499
  setFrameworkContext(key, value) {
494
500
  const ctx = {
495
501
  ...this.$session.get().frameworkContext,
@@ -786,22 +792,18 @@ function deduplicateAndSort(hints) {
786
792
  * KeyboardHintsProvider — Context for collecting and displaying keyboard hints.
787
793
  *
788
794
  * Input components register their hints via useKeyBindings. The provider
789
- * flattens, deduplicates, and sorts them. It auto-dismisses 3s after the
790
- * first keypress and resets when the hint set changes (screen navigation).
795
+ * flattens, deduplicates, and sorts them. The hints bar stays visible for as
796
+ * long as a screen has registered hints it never auto-dismisses.
791
797
  */
792
798
  const KeyboardHintsContext = createContext({
793
799
  register: () => void 0,
794
800
  unregister: () => void 0,
795
- hints: [],
796
- visible: false
801
+ hints: []
797
802
  });
798
803
  const useKeyboardHintsContext = () => useContext(KeyboardHintsContext);
799
- const DISMISS_DELAY = 3e3;
800
804
  const KeyboardHintsProvider = ({ children }) => {
801
805
  const registrationsRef = useRef(/* @__PURE__ */ new Map());
802
806
  const [hints, setHints] = useState([]);
803
- const [visible, setVisible] = useState(true);
804
- const timerRef = useRef(null);
805
807
  const prevHintsKeyRef = useRef("");
806
808
  const recompute = useCallback(() => {
807
809
  const all = [];
@@ -811,13 +813,6 @@ const KeyboardHintsProvider = ({ children }) => {
811
813
  if (newKey !== prevHintsKeyRef.current) {
812
814
  prevHintsKeyRef.current = newKey;
813
815
  setHints(deduped);
814
- if (newKey.length > 0) {
815
- setVisible(true);
816
- if (timerRef.current) {
817
- clearTimeout(timerRef.current);
818
- timerRef.current = null;
819
- }
820
- }
821
816
  }
822
817
  }, []);
823
818
  const register = useCallback((id, h) => {
@@ -828,25 +823,11 @@ const KeyboardHintsProvider = ({ children }) => {
828
823
  registrationsRef.current.delete(id);
829
824
  recompute();
830
825
  }, [recompute]);
831
- useInput(() => {
832
- if (!visible) return;
833
- if (timerRef.current) return;
834
- timerRef.current = setTimeout(() => {
835
- setVisible(false);
836
- timerRef.current = null;
837
- }, DISMISS_DELAY);
838
- });
839
- useEffect(() => {
840
- return () => {
841
- if (timerRef.current) clearTimeout(timerRef.current);
842
- };
843
- }, []);
844
826
  return /* @__PURE__ */ jsx(KeyboardHintsContext.Provider, {
845
827
  value: {
846
828
  register,
847
829
  unregister,
848
- hints,
849
- visible
830
+ hints
850
831
  },
851
832
  children
852
833
  });
@@ -1457,40 +1438,92 @@ const ModalOverlay = ({ borderColor, title, titleColor, width = 68, children, fe
1457
1438
  /**
1458
1439
  * LogViewer — Real-time log tail, pinned to available terminal height.
1459
1440
  * Only renders the last N lines that fit on screen.
1441
+ *
1442
+ * Reads only the last TAIL_BYTES of the file on each refresh and throttles
1443
+ * fs.watch callbacks to one refresh per WATCH_THROTTLE_MS. Without this,
1444
+ * fs.readFileSync allocates a string the size of the entire log on every
1445
+ * append — during subagent fan-out that's tens of writes per second against
1446
+ * a log that grows into the hundreds of MB, producing OOM-grade allocation
1447
+ * pressure on V8's heap.
1460
1448
  */
1461
1449
  /** Rows consumed by TitleBar + spacer + ScreenContainer padding + status bar + tab bar */
1462
1450
  const CHROME_ROWS$1 = 8;
1451
+ /** Bytes read from the end of the log per refresh — large enough to contain
1452
+ * any practical visible window of lines, small enough to allocate cheaply. */
1453
+ const TAIL_BYTES = 256 * 1024;
1454
+ /** Minimum gap between watch-triggered refreshes. fs.watch fires on every
1455
+ * append */
1456
+ const WATCH_THROTTLE_MS = 250;
1457
+ function readTailLines(filePath, visibleLines) {
1458
+ const stat = fs$1.statSync(filePath);
1459
+ if (stat.size === 0) return [];
1460
+ const start = Math.max(0, stat.size - TAIL_BYTES);
1461
+ const length = stat.size - start;
1462
+ const buf = Buffer.alloc(length);
1463
+ const fd = fs$1.openSync(filePath, "r");
1464
+ try {
1465
+ fs$1.readSync(fd, buf, 0, length, start);
1466
+ } finally {
1467
+ fs$1.closeSync(fd);
1468
+ }
1469
+ let text = buf.toString("utf-8");
1470
+ if (start > 0) {
1471
+ const firstNewline = text.indexOf("\n");
1472
+ if (firstNewline >= 0) text = text.slice(firstNewline + 1);
1473
+ }
1474
+ return text.split("\n").slice(-visibleLines);
1475
+ }
1463
1476
  const LogViewer = ({ filePath, height }) => {
1464
1477
  const [, rows] = useStdoutDimensions();
1465
1478
  const visibleLines = height ?? Math.max(5, rows - CHROME_ROWS$1);
1466
1479
  const [lines, setLines] = useState([]);
1467
1480
  useEffect(() => {
1481
+ let lastReadAt = 0;
1482
+ let pendingTimer;
1468
1483
  const readTail = () => {
1469
1484
  try {
1470
- setLines(fs$1.readFileSync(filePath, "utf-8").split("\n").slice(-visibleLines));
1485
+ setLines(readTailLines(filePath, visibleLines));
1471
1486
  } catch {
1472
1487
  setLines(["(No log file found)"]);
1473
1488
  }
1474
1489
  };
1490
+ const scheduleRead = () => {
1491
+ const now = Date.now();
1492
+ const elapsed = now - lastReadAt;
1493
+ if (elapsed >= WATCH_THROTTLE_MS) {
1494
+ lastReadAt = now;
1495
+ readTail();
1496
+ return;
1497
+ }
1498
+ if (pendingTimer) return;
1499
+ pendingTimer = setTimeout(() => {
1500
+ pendingTimer = void 0;
1501
+ lastReadAt = Date.now();
1502
+ readTail();
1503
+ }, WATCH_THROTTLE_MS - elapsed);
1504
+ };
1475
1505
  readTail();
1506
+ lastReadAt = Date.now();
1476
1507
  let watcher;
1477
1508
  try {
1478
- watcher = fs$1.watch(filePath, () => {
1479
- readTail();
1480
- });
1509
+ watcher = fs$1.watch(filePath, () => scheduleRead());
1481
1510
  } catch {
1482
1511
  const interval = setInterval(() => {
1483
1512
  try {
1484
1513
  fs$1.accessSync(filePath);
1485
1514
  readTail();
1486
1515
  clearInterval(interval);
1487
- watcher = fs$1.watch(filePath, () => readTail());
1516
+ watcher = fs$1.watch(filePath, () => scheduleRead());
1488
1517
  } catch {}
1489
1518
  }, 1e3);
1490
- return () => clearInterval(interval);
1519
+ return () => {
1520
+ clearInterval(interval);
1521
+ if (pendingTimer) clearTimeout(pendingTimer);
1522
+ };
1491
1523
  }
1492
1524
  return () => {
1493
1525
  watcher?.close();
1526
+ if (pendingTimer) clearTimeout(pendingTimer);
1494
1527
  };
1495
1528
  }, [filePath, visibleLines]);
1496
1529
  return /* @__PURE__ */ jsx(Box, {
@@ -1672,16 +1705,15 @@ const DissolveTransition = ({ transitionKey, width, height, children, direction
1672
1705
  /**
1673
1706
  * KeyboardHintsBar — Row showing active keyboard shortcuts.
1674
1707
  *
1675
- * Always reserves its row to prevent layout shift. When hints are
1676
- * visible, renders them in dimmed grey text. When dismissed, renders
1677
- * an empty reserved row.
1708
+ * Always reserves its row to prevent layout shift, and always renders the
1709
+ * active hints (in dimmed grey text) while a screen has registered them.
1678
1710
  */
1679
1711
  const KeyboardHintsBar = () => {
1680
- const { hints, visible } = useKeyboardHintsContext();
1712
+ const { hints } = useKeyboardHintsContext();
1681
1713
  return /* @__PURE__ */ jsx(Box, {
1682
1714
  height: 1,
1683
1715
  paddingX: 1,
1684
- children: visible && hints.length > 0 && hints.map((hint, i) => /* @__PURE__ */ jsxs(Box, {
1716
+ children: hints.map((hint, i) => /* @__PURE__ */ jsxs(Box, {
1685
1717
  marginRight: i < hints.length - 1 ? 2 : 0,
1686
1718
  children: [/* @__PURE__ */ jsx(Text, {
1687
1719
  bold: true,
@@ -2596,8 +2628,17 @@ const McpScreen = ({ store, installer, mode = "install" }) => {
2596
2628
  }, [installer]);
2597
2629
  const proceedToFeatureSelectOrInstall = (clientNames) => {
2598
2630
  setSelectedClientNames(clientNames);
2599
- if (store.session.mcpFeatures) doInstall(clientNames, store.session.mcpFeatures);
2600
- else setPhase("feature-select");
2631
+ if (store.session.mcpFeatures) {
2632
+ doInstall(clientNames, store.session.mcpFeatures);
2633
+ return;
2634
+ }
2635
+ if (!clientNames.some((name) => {
2636
+ return !clients.find((c) => c.name === name)?.finish;
2637
+ })) {
2638
+ doInstall(clientNames, []);
2639
+ return;
2640
+ }
2641
+ setPhase("feature-select");
2601
2642
  };
2602
2643
  const handleConfirm = () => {
2603
2644
  if (isRemove) doRemove();
@@ -2636,6 +2677,17 @@ const McpScreen = ({ store, installer, mode = "install" }) => {
2636
2677
  const outcome = result.length > 0 ? "installed" : "failed";
2637
2678
  setTimeout(() => markDone(store, outcome, result), 2e3);
2638
2679
  };
2680
+ const installValueBullets = [
2681
+ "Ask your agent: \"List my feature flags\" — and it does.",
2682
+ "Run SQL, build dashboards, ship flags, all from your IDE.",
2683
+ "No copy-pasting tokens or context. Your agent has the keys."
2684
+ ];
2685
+ const finishNotes = clients.flatMap((c) => c.finish && resultClients.includes(c.name) ? [{
2686
+ name: c.name,
2687
+ url: c.finish.url,
2688
+ instruction: c.finish.instruction
2689
+ }] : []);
2690
+ const installedNow = resultClients.filter((name) => !finishNotes.some((n) => n.name === name));
2639
2691
  return /* @__PURE__ */ jsxs(Box, {
2640
2692
  flexDirection: "column",
2641
2693
  flexGrow: 1,
@@ -2663,11 +2715,7 @@ const McpScreen = ({ store, installer, mode = "install" }) => {
2663
2715
  !isRemove && /* @__PURE__ */ jsx(Box, {
2664
2716
  flexDirection: "column",
2665
2717
  marginBottom: 1,
2666
- children: [
2667
- "Ask your agent: \"List my feature flags\" — and it does.",
2668
- "Run SQL, build dashboards, ship flags, all from your IDE.",
2669
- "No copy-pasting tokens or context. Your agent has the keys."
2670
- ].map((bullet) => /* @__PURE__ */ jsxs(Text, {
2718
+ children: installValueBullets.map((bullet) => /* @__PURE__ */ jsxs(Text, {
2671
2719
  dimColor: true,
2672
2720
  children: [
2673
2721
  "•",
@@ -2716,7 +2764,10 @@ const McpScreen = ({ store, installer, mode = "install" }) => {
2716
2764
  }),
2717
2765
  phase === "done" && /* @__PURE__ */ jsx(Box, {
2718
2766
  flexDirection: "column",
2719
- children: resultClients.length > 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Text, {
2767
+ children: installedNow.length === 0 && finishNotes.length === 0 ? /* @__PURE__ */ jsxs(Text, {
2768
+ dimColor: true,
2769
+ children: [isRemove ? "Removal" : "Installation", " skipped."]
2770
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [installedNow.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Text, {
2720
2771
  color: "green",
2721
2772
  bold: true,
2722
2773
  children: [
@@ -2727,277 +2778,1344 @@ const McpScreen = ({ store, installer, mode = "install" }) => {
2727
2778
  isRemove ? "removed from" : "installed for",
2728
2779
  ":"
2729
2780
  ]
2730
- }), resultClients.map((name, i) => /* @__PURE__ */ jsxs(Text, { children: [
2781
+ }), installedNow.map((name, i) => /* @__PURE__ */ jsxs(Text, { children: [
2731
2782
  " ",
2732
2783
  "•",
2733
2784
  " ",
2734
2785
  name
2735
- ] }, i))] }) : /* @__PURE__ */ jsxs(Text, {
2736
- dimColor: true,
2737
- children: [isRemove ? "Removal" : "Installation", " skipped."]
2738
- })
2786
+ ] }, i))] }), finishNotes.map((note) => /* @__PURE__ */ jsxs(Box, {
2787
+ flexDirection: "column",
2788
+ marginTop: 1,
2789
+ children: [
2790
+ /* @__PURE__ */ jsxs(Text, {
2791
+ color: "green",
2792
+ bold: true,
2793
+ children: [note.name, " \\u2014 finish in your browser:"]
2794
+ }),
2795
+ /* @__PURE__ */ jsxs(Text, { children: [
2796
+ " ",
2797
+ "Opened ",
2798
+ /* @__PURE__ */ jsx(Text, {
2799
+ color: "cyan",
2800
+ children: note.url
2801
+ })
2802
+ ] }),
2803
+ /* @__PURE__ */ jsxs(Text, {
2804
+ dimColor: true,
2805
+ children: [" ", note.instruction]
2806
+ }),
2807
+ /* @__PURE__ */ jsxs(Text, {
2808
+ dimColor: true,
2809
+ children: [" ", "(If it didn't open, paste the URL above.)"]
2810
+ })
2811
+ ]
2812
+ }, note.name))] })
2739
2813
  })
2740
2814
  ]
2741
2815
  })]
2742
2816
  });
2743
2817
  };
2744
2818
  //#endregion
2745
- //#region src/lib/mcp-role-prompts.ts
2746
- /**
2747
- * Roles that ship from `role_at_organization` on the PostHog user object.
2748
- * `security` isn't in the enum upstream the engineering kit covers that audience.
2749
- */
2750
- const TAILORED_ROLES = [
2751
- "founder",
2752
- "product",
2753
- "leadership",
2754
- "marketing",
2755
- "engineering",
2756
- "data"
2757
- ];
2758
- const STOCK_MCP_SUGGESTED_PROMPTS = [
2819
+ //#region src/lib/mcp-role-prompts.copy.json
2820
+ var pinnedFirstPrompt = {
2821
+ "prompt": "Show me my top 5 events from the last 7 days",
2822
+ "description": "A safe first pick works on any project regardless of role or setup."
2823
+ };
2824
+ var defaultKit = [
2825
+ {
2826
+ "key": "verify",
2827
+ "prompt": "Annotate today with 'PostHog wizard install'",
2828
+ "description": "Creates a dated note on your project — visible on every chart. Delete anytime from PostHog."
2829
+ },
2830
+ {
2831
+ "key": "top-events",
2832
+ "prompt": "Show me my top 5 events from the last 7 days",
2833
+ "description": "Get a feel for what your project is tracking."
2834
+ },
2759
2835
  {
2760
- prompt: "What are my busiest 10 events and when did each last fire?",
2761
- description: "Inventories your project’s event stream so you can see what’s being captured at a glance."
2836
+ "key": "main-funnel",
2837
+ "prompt": "Build me a funnel for my main user journey and show where the drop-off is",
2838
+ "description": "Insight discovery — your agent picks the events."
2762
2839
  },
2763
2840
  {
2764
- prompt: "Show me daily event volume for the last 30 days.",
2765
- description: "Charts your event count day by day a quick read on volume and trend."
2841
+ "key": "flags-inventory",
2842
+ "prompt": "Show me my feature flags and what each is currently rolled out to",
2843
+ "description": "Inventory the rollout state of every flag in your project."
2766
2844
  },
2767
2845
  {
2768
- prompt: "Create a dashboard with my top 10 events broken down by day for the last 7 days.",
2769
- description: "Builds a saved dashboard you can pin and share written back to your project."
2846
+ "key": "error-trend",
2847
+ "prompt": "Show me daily error count for the last 30 days and flag anything that looks like a spike",
2848
+ "description": "Pulse-check on stability — no dashboard setup required."
2770
2849
  }
2771
2850
  ];
2772
- //#endregion
2773
- //#region src/ui/tui/screens/McpSuggestedPromptsScreen.tsx
2774
- /**
2775
- * McpSuggestedPromptsScreen — shown after MCP install succeeds in the
2776
- * standalone `wizard mcp add` program.
2777
- *
2778
- * Phases:
2779
- * 1. Choose — opens with a Log in / Exit picker, framed by a
2780
- * hardcoded teaser of three example prompts.
2781
- * 2. Authenticating runs `services.performLogin()` (OAuth in
2782
- * production, canned values in the playground).
2783
- * Renders a spinner + login URL inline while the
2784
- * promise is pending. Errors return to Choose
2785
- * with an inline error line.
2786
- * 3. PromptPicker — lists the role-tailored kit; user picks one to
2787
- * run. The picker has its own "Exit" entry so
2788
- * dismissal is discoverable without a hidden
2789
- * hotkey.
2790
- * 4. Running — streams the agent's response inline via
2791
- * `services.runPromptStreaming`. `[esc]` aborts
2792
- * and returns to the picker; `[enter]` after
2793
- * completion goes back to the picker so the user
2794
- * can run another or exit.
2795
- *
2796
- * Credentials are guaranteed non-null once PromptPicker / Running are
2797
- * reached (the Choose Authenticating gate forces a successful login
2798
- * before getting there). A defensive throw protects the Running
2799
- * useEffect against a state-machine bug.
2800
- */
2801
- const MAX_PROMPT_RUNS = 3;
2802
- const McpSuggestedPromptsScreen = ({ store, services }) => {
2803
- useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
2804
- const session = store.session;
2805
- const kit = STOCK_MCP_SUGGESTED_PROMPTS;
2806
- const [phase, setPhase] = useState("choose");
2807
- const [loginError, setLoginError] = useState(null);
2808
- const [runningPrompt, setRunningPrompt] = useState(null);
2809
- const [runChunks, setRunChunks] = useState([]);
2810
- const [runStartedAt, setRunStartedAt] = useState(null);
2811
- const [runCount, setRunCount] = useState(0);
2812
- const canPickAnother = runCount < MAX_PROMPT_RUNS;
2813
- const runAbortRef = useRef(null);
2814
- useEffect(() => {
2815
- if (phase !== "authenticating") return;
2816
- let cancelled = false;
2817
- (async () => {
2818
- try {
2819
- const { credentials, roleAtOrganization, user } = await services.performLogin();
2820
- if (cancelled) return;
2821
- store.setCredentials(credentials);
2822
- store.setRoleAtOrganization(roleAtOrganization);
2823
- store.setApiUser(user);
2824
- store.setLoginUrl(null);
2825
- setPhase("prompt-picker");
2826
- } catch (err) {
2827
- if (cancelled) return;
2828
- const message = err instanceof Error ? err.message : String(err);
2829
- logToFile(`[McpSuggestedPromptsScreen] login failed: ${message}`);
2830
- store.setLoginUrl(null);
2831
- setLoginError(message);
2832
- setPhase("choose");
2833
- }
2834
- })();
2835
- return () => {
2836
- cancelled = true;
2837
- };
2838
- }, [
2839
- phase,
2840
- services,
2841
- store
2842
- ]);
2843
- useEffect(() => {
2844
- if (phase !== "running") return;
2845
- if (!runningPrompt) return;
2846
- if (!session.credentials) throw new Error("[McpSuggestedPromptsScreen] Running phase reached without credentials. The Choose gate should have prevented this.");
2847
- const controller = new AbortController();
2848
- runAbortRef.current = controller;
2849
- const startedAt = Date.now();
2850
- setRunStartedAt(startedAt);
2851
- setRunChunks([]);
2852
- (async () => {
2853
- const credentials = session.credentials;
2854
- if (!credentials) return;
2855
- try {
2856
- for await (const chunk of services.runPromptStreaming({
2857
- prompt: runningPrompt,
2858
- credentials,
2859
- signal: controller.signal
2860
- })) {
2861
- if (controller.signal.aborted) return;
2862
- setRunChunks((prev) => [...prev, chunk]);
2863
- if (chunk.kind === "done") {
2864
- analytics.wizardCapture("mcp suggested prompts run", {
2865
- prompt: runningPrompt,
2866
- durationMs: Date.now() - startedAt
2867
- });
2868
- return;
2869
- }
2870
- if (chunk.kind === "error") {
2871
- analytics.wizardCapture("mcp suggested prompts run failed", {
2872
- prompt: runningPrompt,
2873
- error: chunk.text
2874
- });
2875
- return;
2876
- }
2877
- }
2878
- } catch (err) {
2879
- if (controller.signal.aborted) return;
2880
- const text = err instanceof Error ? err.message : String(err);
2881
- setRunChunks((prev) => [...prev, {
2882
- kind: "error",
2883
- text
2884
- }]);
2885
- analytics.wizardCapture("mcp suggested prompts run failed", {
2886
- prompt: runningPrompt,
2887
- error: text
2888
- });
2889
- }
2890
- })();
2891
- return () => {
2892
- controller.abort();
2893
- if (runAbortRef.current === controller) runAbortRef.current = null;
2894
- };
2895
- }, [
2896
- phase,
2897
- runningPrompt,
2898
- services,
2899
- session.credentials
2900
- ]);
2901
- const dismiss = () => {
2902
- setPhase("done");
2903
- setTimeout(() => {
2904
- store.setMcpSuggestedPromptsDismissed();
2905
- }, 0);
2906
- };
2907
- const handleChoice = (value) => {
2908
- const choice = Array.isArray(value) ? value[0] : value;
2909
- setLoginError(null);
2910
- if (choice === "login") {
2911
- analytics.wizardCapture("mcp suggested prompts choose", { choice: "login" });
2912
- setPhase("authenticating");
2913
- } else {
2914
- analytics.wizardCapture("mcp suggested prompts choose", { choice: "exit" });
2915
- dismiss();
2851
+ var roleKits = {
2852
+ "founder": [
2853
+ {
2854
+ "key": "verify",
2855
+ "prompt": "Annotate today with 'PostHog wizard install'",
2856
+ "description": "Creates a dated note on your project — visible on every chart. Delete anytime from PostHog."
2857
+ },
2858
+ {
2859
+ "key": "exec-dashboard",
2860
+ "prompt": "Build me an exec dashboard with MRR, MAU, churn, and top events, then save it",
2861
+ "description": "A one-glance view of the business you can pin and share."
2862
+ },
2863
+ {
2864
+ "key": "wau",
2865
+ "prompt": "Show me weekly active users for the last 90 days",
2866
+ "description": "The trendline you actually care about."
2867
+ },
2868
+ {
2869
+ "key": "mau-trend",
2870
+ "prompt": "Show me weekly MAU for the last 12 weeks and where the inflection points are",
2871
+ "description": "See where growth bent up or down — without setting up alerts."
2872
+ },
2873
+ {
2874
+ "key": "nps-summary",
2875
+ "prompt": "Show me NPS responses from my paid users and summarize the themes",
2876
+ "description": "Pulse-check on the people paying you."
2916
2877
  }
2917
- };
2918
- const handlePromptPick = (value) => {
2919
- setRunningPrompt(Array.isArray(value) ? value[0] : value);
2920
- setRunCount((c) => c + 1);
2921
- setPhase("running");
2922
- };
2923
- useKeyBindings("mcp-suggested-prompts", [{
2924
- match: "escape",
2925
- label: "esc",
2926
- action: phase === "prompt-picker" ? "exit" : "exit",
2927
- handler: () => {
2928
- if (phase === "running") {
2929
- runAbortRef.current?.abort();
2930
- dismiss();
2931
- } else if (phase === "prompt-picker") dismiss();
2878
+ ],
2879
+ "product": [
2880
+ {
2881
+ "key": "verify",
2882
+ "prompt": "Annotate today with 'PostHog wizard install'",
2883
+ "description": "Creates a dated note on your project — visible on every chart. Delete anytime from PostHog."
2884
+ },
2885
+ {
2886
+ "key": "onboarding",
2887
+ "prompt": "Build a funnel for my onboarding flow and show me the biggest drop-off step",
2888
+ "description": "See where new users drop off in their first session."
2889
+ },
2890
+ {
2891
+ "key": "pricing-flag-state",
2892
+ "prompt": "Show me feature flags scoped to the pricing page and who's currently in each",
2893
+ "description": "Inspect rollout state of pricing experiments without changing anything."
2894
+ },
2895
+ {
2896
+ "key": "cta-compare",
2897
+ "prompt": "Show me how my upgrade CTA variants are converting across my recent experiments",
2898
+ "description": "Read the verdict on CTA tests without spinning up a new one."
2899
+ },
2900
+ {
2901
+ "key": "retention",
2902
+ "prompt": "Compute week-1 retention split by acquisition channel",
2903
+ "description": "Find the channel that actually retains users."
2932
2904
  }
2933
- }, {
2934
- match: "p",
2935
- label: "p",
2936
- action: canPickAnother ? "pick new prompt" : "cap reached",
2937
- handler: () => {
2938
- if (phase !== "running") return;
2939
- if (!canPickAnother) return;
2940
- runAbortRef.current?.abort();
2941
- setPhase("prompt-picker");
2905
+ ],
2906
+ "leadership": [
2907
+ {
2908
+ "key": "verify",
2909
+ "prompt": "Annotate today with 'PostHog wizard install'",
2910
+ "description": "Creates a dated note on your project — visible on every chart. Delete anytime from PostHog."
2911
+ },
2912
+ {
2913
+ "key": "board-dashboard",
2914
+ "prompt": "Build a board dashboard with revenue, MAU, churn, and support backlog, then save it",
2915
+ "description": "Pre-board prep in one prompt."
2916
+ },
2917
+ {
2918
+ "key": "mau-growth",
2919
+ "prompt": "Show MAU growth over the last 4 quarters",
2920
+ "description": "The chart for the next leadership slide."
2921
+ },
2922
+ {
2923
+ "key": "churn-trend",
2924
+ "prompt": "Show me churn over the last 8 weeks and where it moved most",
2925
+ "description": "See the trend without configuring a notification."
2926
+ },
2927
+ {
2928
+ "key": "upgrade-drivers",
2929
+ "prompt": "Which features drive the most upgrades?",
2930
+ "description": "Ranked breakdown of what actually moves the needle."
2942
2931
  }
2943
- }]);
2944
- return /* @__PURE__ */ jsx(Box, {
2945
- flexDirection: "column",
2946
- flexGrow: 1,
2947
- children: /* @__PURE__ */ jsxs(Box, {
2948
- marginTop: 1,
2949
- flexDirection: "column",
2950
- children: [
2951
- phase === "choose" && /* @__PURE__ */ jsx(ChoosePhase, {
2952
- error: loginError,
2953
- onSelect: handleChoice
2954
- }),
2955
- phase === "authenticating" && /* @__PURE__ */ jsx(AuthenticatingPhase, { loginUrl: session.loginUrl }),
2956
- phase === "prompt-picker" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Box, {
2957
- marginBottom: 1,
2958
- children: /* @__PURE__ */ jsx(Text, {
2959
- bold: true,
2960
- color: Colors.accent,
2961
- children: "MCP tutorial"
2962
- })
2963
- }), /* @__PURE__ */ jsx(PromptPickerPhase, {
2964
- promptKit: kit,
2965
- userDisplayName: session.apiUser?.first_name || null,
2966
- onSelect: handlePromptPick
2967
- })] }),
2968
- phase === "running" && runningPrompt && /* @__PURE__ */ jsx(RunningPhase, {
2969
- prompt: runningPrompt,
2970
- chunks: runChunks,
2971
- startedAt: runStartedAt,
2972
- canPickAnother,
2973
- runCount,
2974
- maxRuns: MAX_PROMPT_RUNS
2975
- })
2976
- ]
2977
- })
2978
- });
2979
- };
2980
- const ChoosePhase = ({ error, onSelect }) => /* @__PURE__ */ jsxs(Box, {
2981
- flexDirection: "column",
2982
- children: [
2983
- /* @__PURE__ */ jsx(Text, {
2984
- bold: true,
2985
- color: Colors.accent,
2986
- children: "PostHog MCP"
2987
- }),
2988
- /* @__PURE__ */ jsx(Box, {
2989
- marginTop: 1,
2990
- children: /* @__PURE__ */ jsx(Text, { children: "With MCP your agent works directly with the PostHog platform. You can prompt it to:" })
2991
- }),
2992
- /* @__PURE__ */ jsxs(Box, {
2993
- marginTop: 1,
2994
- flexDirection: "column",
2995
- children: [
2996
- /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
2997
- color: "cyan",
2998
- children: Icons.diamond
2999
- }), " Build dashboards"] }),
3000
- /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
2932
+ ],
2933
+ "marketing": [
2934
+ {
2935
+ "key": "verify",
2936
+ "prompt": "Annotate today with 'PostHog wizard install'",
2937
+ "description": "Creates a dated note on your project — visible on every chart. Delete anytime from PostHog."
2938
+ },
2939
+ {
2940
+ "key": "pricing-leavers",
2941
+ "prompt": "Show me users who saw pricing but didn't sign up — what did they do next?",
2942
+ "description": "Identify high-intent visitors and what they bounced to."
2943
+ },
2944
+ {
2945
+ "key": "hero-compare",
2946
+ "prompt": "Show me how my landing page hero variants performed — which group converted best?",
2947
+ "description": "Read the verdict on hero copy tests."
2948
+ },
2949
+ {
2950
+ "key": "newsletter-clicks",
2951
+ "prompt": "Find users who clicked our last newsletter and show me what they did next",
2952
+ "description": "See the downstream behavior of your last campaign."
2953
+ },
2954
+ {
2955
+ "key": "landing-annotation",
2956
+ "prompt": "Annotate today as the launch of the new landing page",
2957
+ "description": "Pin the deploy on every chart so future you can find it."
2958
+ }
2959
+ ],
2960
+ "engineering": [
2961
+ {
2962
+ "key": "verify",
2963
+ "prompt": "Annotate today with 'PostHog wizard install'",
2964
+ "description": "Creates a dated note on your project — visible on every chart. Delete anytime from PostHog."
2965
+ },
2966
+ {
2967
+ "key": "stale-flags",
2968
+ "prompt": "List flags rolled out to 100% — they're probably safe to delete",
2969
+ "description": "Dead-code hunt for your feature flag config."
2970
+ },
2971
+ {
2972
+ "key": "top-errors",
2973
+ "prompt": "Show me the top 5 unresolved errors this week",
2974
+ "description": "Triage queue without opening another tab."
2975
+ },
2976
+ {
2977
+ "key": "reliability-trend",
2978
+ "prompt": "Show me 5xx error rate over the last 24 hours by endpoint",
2979
+ "description": "See where reliability is drifting, no alert setup."
2980
+ },
2981
+ {
2982
+ "key": "zero-rollout-flags",
2983
+ "prompt": "Show me feature flags currently rolled out at 0% — anything ready to retire?",
2984
+ "description": "Find dead kill-switch flags you can clean up later."
2985
+ }
2986
+ ],
2987
+ "data": [
2988
+ {
2989
+ "key": "verify",
2990
+ "prompt": "Annotate today with 'PostHog wizard install'",
2991
+ "description": "Creates a dated note on your project — visible on every chart. Delete anytime from PostHog."
2992
+ },
2993
+ {
2994
+ "key": "top-events-24h",
2995
+ "prompt": "Top 5 events by volume in the last 24 hours",
2996
+ "description": "Smoke test for ingestion + a sanity check on volumes."
2997
+ },
2998
+ {
2999
+ "key": "paid-retention",
3000
+ "prompt": "Retention curve for paid users by signup month",
3001
+ "description": "The cohort chart you'd build first anyway."
3002
+ },
3003
+ {
3004
+ "key": "full-funnel",
3005
+ "prompt": "Funnel: signup → activated → first power feature → paid",
3006
+ "description": "Drop-off across the full journey, ready to slice."
3007
+ },
3008
+ {
3009
+ "key": "power-users-query",
3010
+ "prompt": "Show me users with 5+ sessions per week over the last month and what they have in common",
3011
+ "description": "Profile your power-user segment without materializing a cohort."
3012
+ }
3013
+ ]
3014
+ };
3015
+ var roleFamilyOverrides = {
3016
+ "product": {
3017
+ "frontend-web": { "cta-compare": {
3018
+ "prompt": "Compare conversion across the variants of my last upgrade CTA experiment (control, red, green)",
3019
+ "description": "Read the verdict on a three-arm frontend experiment."
3020
+ } },
3021
+ "mobile": {
3022
+ "onboarding": {
3023
+ "prompt": "Build a funnel app_open → onboarding_complete → first_session_complete and show me the drop-off",
3024
+ "description": "Mobile-flavored onboarding funnel with sensible defaults."
3025
+ },
3026
+ "cta-compare": {
3027
+ "prompt": "Show me feature flags gated on app version — who's in each release tier?",
3028
+ "description": "See which clients see what, without changing anything."
3029
+ }
3030
+ },
3031
+ "backend": { "onboarding": {
3032
+ "prompt": "Funnel of signup → first API call → paid for last 30 days",
3033
+ "description": "Backend funnel that reflects what your service actually sees."
3034
+ } }
3035
+ },
3036
+ "engineering": {
3037
+ "frontend-web": { "top-errors": {
3038
+ "prompt": "Top 5 JS errors by occurrence count this week, with affected URLs",
3039
+ "description": "Frontend-specific error triage — sorted by blast radius."
3040
+ } },
3041
+ "mobile": {
3042
+ "top-errors": {
3043
+ "prompt": "Top crashes this week by app version, sorted by affected users",
3044
+ "description": "Mobile crash triage straight from the same data PostHog has."
3045
+ },
3046
+ "reliability-trend": {
3047
+ "prompt": "Show me crash-free sessions over the last 7 days by app version",
3048
+ "description": "Crash-free trend per release — the one mobile metric that matters."
3049
+ }
3050
+ },
3051
+ "backend": {
3052
+ "top-errors": {
3053
+ "prompt": "Top 5 server-side errors this week, grouped by endpoint",
3054
+ "description": "Backend error triage by route, sorted by frequency."
3055
+ },
3056
+ "reliability-trend": {
3057
+ "prompt": "Show me p95 response time over the last 24 hours by endpoint",
3058
+ "description": "Latency trend from the data you already collect."
3059
+ }
3060
+ }
3061
+ },
3062
+ "data": { "backend": { "full-funnel": {
3063
+ "prompt": "Funnel: api_signup → first_api_call → first_paid_event over last 30 days",
3064
+ "description": "Backend conversion funnel — captures the value your service delivers."
3065
+ } } }
3066
+ };
3067
+ var roleGreetings = {
3068
+ "founder": {
3069
+ "headline": "Founders use MCP to keep a hand on growth.",
3070
+ "bullets": [
3071
+ "Weekly active users, retention, and revenue without leaving your IDE.",
3072
+ "Spot stalls in your trends without setting up dashboards by hand.",
3073
+ "Pin annotations on every chart so you remember what shipped."
3074
+ ],
3075
+ "outro": "Pick a prompt — your agent will run it on your project's real data."
3076
+ },
3077
+ "product": {
3078
+ "headline": "PMs use MCP to learn faster and decide quicker.",
3079
+ "bullets": [
3080
+ "Funnels for every onboarding flow you want to inspect.",
3081
+ "Inspect feature flags and experiment outcomes without leaving your IDE.",
3082
+ "Retention sliced by acquisition channel in seconds."
3083
+ ],
3084
+ "outro": "Pick a prompt — your agent will do the legwork."
3085
+ },
3086
+ "leadership": {
3087
+ "headline": "Read the business from your terminal.",
3088
+ "bullets": [
3089
+ "Board-ready dashboards in one prompt.",
3090
+ "Trend lines for MAU, churn, and revenue, one query away.",
3091
+ "The numbers for the next leadership slide, on tap."
3092
+ ],
3093
+ "outro": "Pick a prompt to see PostHog work for you."
3094
+ },
3095
+ "marketing": {
3096
+ "headline": "Inspect campaigns, end to end.",
3097
+ "bullets": [
3098
+ "Find high-intent visitors and what they did next.",
3099
+ "Compare landing-copy experiments and see which arm is winning.",
3100
+ "Tie every campaign to revenue with annotated launches."
3101
+ ],
3102
+ "outro": "Pick a prompt to try it on your data."
3103
+ },
3104
+ "engineering": {
3105
+ "headline": "MCP is your shortest path from bug to root cause.",
3106
+ "bullets": [
3107
+ "Top errors this week, sorted by blast radius.",
3108
+ "Latency and crash-free trends checked against real data.",
3109
+ "Audit which flags are stale or fully rolled out."
3110
+ ],
3111
+ "outro": "Pick a prompt — your agent has read access across your project."
3112
+ },
3113
+ "data": {
3114
+ "headline": "Data work without leaving the terminal.",
3115
+ "bullets": [
3116
+ "Profile any segment in seconds.",
3117
+ "Retention curves by signup month, sliced any way you want.",
3118
+ "Run SQL against your event stream — no copy-paste, no exports."
3119
+ ],
3120
+ "outro": "Pick a prompt — every result is real data from your project."
3121
+ }
3122
+ };
3123
+ var neutralGreeting = {
3124
+ "headline": "PostHog MCP turns your agent into a product analyst.",
3125
+ "bullets": [
3126
+ "Run queries, build insights, save dashboards — straight from your IDE.",
3127
+ "Every result is real data from your project.",
3128
+ "No copy-pasting tokens, no context switching."
3129
+ ],
3130
+ "outro": "Pick a prompt to see what MCP can do."
3131
+ };
3132
+ var toolFollowUps = {
3133
+ "query-error-tracking-issue": [
3134
+ {
3135
+ "label": "Stack trace for the top error",
3136
+ "prompt": "Show me the stack trace and recent occurrences for the top error."
3137
+ },
3138
+ {
3139
+ "label": "Who is most affected?",
3140
+ "prompt": "Which users have hit that error most often in the last 7 days?"
3141
+ },
3142
+ {
3143
+ "label": "When did it start?",
3144
+ "prompt": "Show me when that error first appeared and any deploy that landed nearby."
3145
+ },
3146
+ {
3147
+ "label": "Find related sessions",
3148
+ "prompt": "Find session recordings that hit that error so I can see what users were doing."
3149
+ },
3150
+ {
3151
+ "label": "Save the top-errors view",
3152
+ "prompt": "Save this top-errors view as an insight I can come back to."
3153
+ },
3154
+ {
3155
+ "label": "Pin to engineering dashboard",
3156
+ "prompt": "Pin this errors view to my engineering dashboard."
3157
+ }
3158
+ ],
3159
+ "query-trends": [
3160
+ {
3161
+ "label": "Break down by property",
3162
+ "prompt": "Break that trend down by the most common user property."
3163
+ },
3164
+ {
3165
+ "label": "Find the outlier day",
3166
+ "prompt": "Which day stood out the most and what else was going on?"
3167
+ },
3168
+ {
3169
+ "label": "Compare to last month",
3170
+ "prompt": "Compare that against the same period last month."
3171
+ },
3172
+ {
3173
+ "label": "Build a funnel from it",
3174
+ "prompt": "Build a funnel using the top events from that trend."
3175
+ },
3176
+ {
3177
+ "label": "Save as an insight",
3178
+ "prompt": "Save that trend as an insight named 'Trends'."
3179
+ },
3180
+ {
3181
+ "label": "Pin to main dashboard",
3182
+ "prompt": "Pin that trend to my main dashboard."
3183
+ }
3184
+ ],
3185
+ "query-funnel": [
3186
+ {
3187
+ "label": "Biggest drop-off",
3188
+ "prompt": "Which step has the biggest drop-off, and who falls out there?"
3189
+ },
3190
+ {
3191
+ "label": "Completion time",
3192
+ "prompt": "How long does it take users who complete that funnel?"
3193
+ },
3194
+ {
3195
+ "label": "Slice by platform",
3196
+ "prompt": "Show that funnel split by mobile vs desktop."
3197
+ },
3198
+ {
3199
+ "label": "Find drop-off sessions",
3200
+ "prompt": "Find session recordings of users who dropped out at the biggest step."
3201
+ },
3202
+ {
3203
+ "label": "Save the funnel",
3204
+ "prompt": "Save that funnel as an insight."
3205
+ },
3206
+ {
3207
+ "label": "Pin to dashboard",
3208
+ "prompt": "Pin that funnel to my main dashboard."
3209
+ }
3210
+ ],
3211
+ "query-retention": [
3212
+ {
3213
+ "label": "Best-retaining cohort",
3214
+ "prompt": "Which cohort retains the longest in that curve?"
3215
+ },
3216
+ {
3217
+ "label": "Worst-retaining cohort",
3218
+ "prompt": "Which cohort drops off fastest in that curve?"
3219
+ },
3220
+ {
3221
+ "label": "Slice by acquisition channel",
3222
+ "prompt": "Re-run that retention split by acquisition channel."
3223
+ },
3224
+ {
3225
+ "label": "Find churned users",
3226
+ "prompt": "Find session recordings of users who churned during week 1."
3227
+ },
3228
+ {
3229
+ "label": "Save the retention chart",
3230
+ "prompt": "Save that retention chart as an insight."
3231
+ },
3232
+ {
3233
+ "label": "Pin to growth dashboard",
3234
+ "prompt": "Pin that retention chart to my growth dashboard."
3235
+ }
3236
+ ],
3237
+ "query-feature-flag": [
3238
+ {
3239
+ "label": "Who's in this flag?",
3240
+ "prompt": "Show me which users are currently in the rollout for that flag."
3241
+ },
3242
+ {
3243
+ "label": "What changed recently?",
3244
+ "prompt": "Show me the rollout history for that flag — when did it last change?"
3245
+ },
3246
+ {
3247
+ "label": "Compare against another flag",
3248
+ "prompt": "Show me the audience overlap between that flag and one related flag."
3249
+ },
3250
+ {
3251
+ "label": "Find sessions for that flag",
3252
+ "prompt": "Find recent session recordings from users currently in that flag."
3253
+ },
3254
+ {
3255
+ "label": "Save flag inventory",
3256
+ "prompt": "Save this flag inventory as an insight."
3257
+ },
3258
+ {
3259
+ "label": "Pin to release dashboard",
3260
+ "prompt": "Pin this flag view to my release dashboard."
3261
+ }
3262
+ ],
3263
+ "query-survey-responses": [
3264
+ {
3265
+ "label": "Summarize the themes",
3266
+ "prompt": "Summarize the themes from those survey responses."
3267
+ },
3268
+ {
3269
+ "label": "Score distribution",
3270
+ "prompt": "Show me the score distribution across those responses."
3271
+ },
3272
+ {
3273
+ "label": "Who are the detractors?",
3274
+ "prompt": "Show me users who left a low score and what they did next."
3275
+ },
3276
+ {
3277
+ "label": "Find their sessions",
3278
+ "prompt": "Find session recordings from users who left a low score."
3279
+ },
3280
+ {
3281
+ "label": "Save the response summary",
3282
+ "prompt": "Save this response summary as an insight."
3283
+ },
3284
+ {
3285
+ "label": "Add to research notebook",
3286
+ "prompt": "Add this survey summary to my user research notebook."
3287
+ }
3288
+ ],
3289
+ "query-experiment": [
3290
+ {
3291
+ "label": "Which variant is winning?",
3292
+ "prompt": "Show me the conversion rate of each variant in that experiment."
3293
+ },
3294
+ {
3295
+ "label": "Slice by segment",
3296
+ "prompt": "Show me how each variant performed by user segment."
3297
+ },
3298
+ {
3299
+ "label": "Statistical significance",
3300
+ "prompt": "Has that experiment reached statistical significance yet?"
3301
+ },
3302
+ {
3303
+ "label": "Find variant sessions",
3304
+ "prompt": "Find session recordings from users in the winning variant."
3305
+ },
3306
+ {
3307
+ "label": "Save the readout",
3308
+ "prompt": "Save that experiment readout as an insight."
3309
+ },
3310
+ {
3311
+ "label": "Add to experiment notebook",
3312
+ "prompt": "Add this experiment readout to my experiments notebook."
3313
+ }
3314
+ ],
3315
+ "query-session-recordings-list": [
3316
+ {
3317
+ "label": "Summarize what users did",
3318
+ "prompt": "Summarize what users did in those sessions."
3319
+ },
3320
+ {
3321
+ "label": "Find common drop-offs",
3322
+ "prompt": "What's the most common step where users got stuck in those sessions?"
3323
+ },
3324
+ {
3325
+ "label": "Errors in those sessions",
3326
+ "prompt": "Which errors fired most often across those sessions?"
3327
+ },
3328
+ {
3329
+ "label": "Properties of those users",
3330
+ "prompt": "Show me the most common user properties across those sessions."
3331
+ },
3332
+ {
3333
+ "label": "Save the session summary",
3334
+ "prompt": "Save the summary of those sessions as an insight."
3335
+ },
3336
+ {
3337
+ "label": "Add to UX notebook",
3338
+ "prompt": "Add these session findings to my UX research notebook."
3339
+ }
3340
+ ],
3341
+ "execute-sql": [
3342
+ {
3343
+ "label": "Add p50/p90/p99",
3344
+ "prompt": "Re-run that query with p50/p90/p99 added."
3345
+ },
3346
+ {
3347
+ "label": "Slice differently",
3348
+ "prompt": "Re-run that query grouped by the most common user property."
3349
+ },
3350
+ {
3351
+ "label": "Find the outliers",
3352
+ "prompt": "Re-run that query and surface the top 5 outliers."
3353
+ },
3354
+ {
3355
+ "label": "Compare to last week",
3356
+ "prompt": "Compare that query result to the same window last week."
3357
+ },
3358
+ {
3359
+ "label": "Save as an insight",
3360
+ "prompt": "Turn that query result into a saved insight."
3361
+ },
3362
+ {
3363
+ "label": "Pin to data dashboard",
3364
+ "prompt": "Pin that query result to my data dashboard."
3365
+ }
3366
+ ],
3367
+ "create-dashboard": [
3368
+ {
3369
+ "label": "Add another tile",
3370
+ "prompt": "Add a tile showing daily active users to that dashboard."
3371
+ },
3372
+ {
3373
+ "label": "Add a leaderboard tile",
3374
+ "prompt": "Add a top-5 users tile to that dashboard."
3375
+ },
3376
+ {
3377
+ "label": "Annotate today",
3378
+ "prompt": "Annotate today on that dashboard as the launch baseline."
3379
+ },
3380
+ {
3381
+ "label": "Compare to last quarter",
3382
+ "prompt": "Add a tile comparing this quarter to the last on the same dashboard."
3383
+ },
3384
+ {
3385
+ "label": "Add an errors tile",
3386
+ "prompt": "Add a tile showing the top 3 errors this week to that dashboard."
3387
+ },
3388
+ {
3389
+ "label": "Add to dashboards notebook",
3390
+ "prompt": "Add a link to that dashboard in my dashboards notebook."
3391
+ }
3392
+ ],
3393
+ "create-insight": [
3394
+ {
3395
+ "label": "Pin to main dashboard",
3396
+ "prompt": "Pin that insight to my main dashboard."
3397
+ },
3398
+ {
3399
+ "label": "Split by user property",
3400
+ "prompt": "Re-run that insight split by the most common user property."
3401
+ },
3402
+ {
3403
+ "label": "Compare to a control",
3404
+ "prompt": "Re-run that insight comparing paid vs free users side-by-side."
3405
+ },
3406
+ {
3407
+ "label": "Save the underlying query",
3408
+ "prompt": "Save the underlying query for that insight so I can edit it later."
3409
+ },
3410
+ {
3411
+ "label": "Add to notebook",
3412
+ "prompt": "Add that insight to my analytics notebook."
3413
+ },
3414
+ {
3415
+ "label": "Annotate the moment",
3416
+ "prompt": "Annotate today on the chart for that insight."
3417
+ }
3418
+ ]
3419
+ };
3420
+ var roleFollowUps = {
3421
+ "founder": [
3422
+ {
3423
+ "label": "Pin to exec dashboard",
3424
+ "prompt": "Add that result to my exec dashboard."
3425
+ },
3426
+ {
3427
+ "label": "Tie it to revenue",
3428
+ "prompt": "How does that correlate with paid conversions?"
3429
+ },
3430
+ {
3431
+ "label": "Compare to last quarter",
3432
+ "prompt": "How does that compare against the same period last quarter?"
3433
+ },
3434
+ {
3435
+ "label": "Save for board update",
3436
+ "prompt": "Save that as an insight I can attach to the next board update."
3437
+ }
3438
+ ],
3439
+ "product": [
3440
+ {
3441
+ "label": "Build a funnel around it",
3442
+ "prompt": "Build a funnel that includes that step."
3443
+ },
3444
+ {
3445
+ "label": "Find high-intent users",
3446
+ "prompt": "Show me which users in that group also completed activation."
3447
+ },
3448
+ {
3449
+ "label": "Check related experiments",
3450
+ "prompt": "Show me how this metric trended across my recent experiments."
3451
+ },
3452
+ {
3453
+ "label": "Save to product notebook",
3454
+ "prompt": "Add this finding to my product analytics notebook."
3455
+ }
3456
+ ],
3457
+ "leadership": [
3458
+ {
3459
+ "label": "Compare to last quarter",
3460
+ "prompt": "How does that compare against the same period last quarter?"
3461
+ },
3462
+ {
3463
+ "label": "Pin to leadership dashboard",
3464
+ "prompt": "Pin this view to my leadership dashboard."
3465
+ },
3466
+ {
3467
+ "label": "Save for next meeting",
3468
+ "prompt": "Save this as an insight I can pull up in the next leadership meeting."
3469
+ }
3470
+ ],
3471
+ "marketing": [
3472
+ {
3473
+ "label": "Annotate the launch",
3474
+ "prompt": "Annotate today as the campaign launch on that chart."
3475
+ },
3476
+ {
3477
+ "label": "What did they do next?",
3478
+ "prompt": "Show me what users in that group did next."
3479
+ },
3480
+ {
3481
+ "label": "Tie back to channel",
3482
+ "prompt": "Split that result by acquisition channel."
3483
+ },
3484
+ {
3485
+ "label": "Compare to landing tests",
3486
+ "prompt": "Compare this result across my recent landing-page experiments."
3487
+ }
3488
+ ],
3489
+ "engineering": [
3490
+ {
3491
+ "label": "Did a deploy land?",
3492
+ "prompt": "Did that change land alongside a deploy in the last 24 hours?"
3493
+ },
3494
+ {
3495
+ "label": "Flag changes that fit",
3496
+ "prompt": "Show me which feature flag changes correlate with that change in metric."
3497
+ },
3498
+ {
3499
+ "label": "Group by release",
3500
+ "prompt": "Re-run that broken down by app version or release."
3501
+ },
3502
+ {
3503
+ "label": "Save to incident notebook",
3504
+ "prompt": "Save this analysis to my incident notebook."
3505
+ }
3506
+ ],
3507
+ "data": [
3508
+ {
3509
+ "label": "Add percentiles",
3510
+ "prompt": "Add p50/p90/p99 distributions to that result."
3511
+ },
3512
+ {
3513
+ "label": "Compare to last month",
3514
+ "prompt": "Show me how that result trended over the last month."
3515
+ },
3516
+ {
3517
+ "label": "Save as an insight",
3518
+ "prompt": "Save that query result as an insight."
3519
+ },
3520
+ {
3521
+ "label": "Pin to data dashboard",
3522
+ "prompt": "Pin this result to my data team dashboard."
3523
+ }
3524
+ ]
3525
+ };
3526
+ var genericFollowUps = [
3527
+ {
3528
+ "label": "Go one level deeper",
3529
+ "prompt": "Run that same question one level deeper."
3530
+ },
3531
+ {
3532
+ "label": "Take a different angle",
3533
+ "prompt": "Look at the same question from a completely different angle."
3534
+ },
3535
+ {
3536
+ "label": "Find the surprise",
3537
+ "prompt": "What's the most surprising thing in that result?"
3538
+ },
3539
+ {
3540
+ "label": "Slice by user",
3541
+ "prompt": "Re-run that split by the highest-value user segment."
3542
+ },
3543
+ {
3544
+ "label": "Compare with last month",
3545
+ "prompt": "How does that look compared to the same window a month ago?"
3546
+ },
3547
+ {
3548
+ "label": "Save as an insight",
3549
+ "prompt": "Save that result as an insight I can come back to."
3550
+ },
3551
+ {
3552
+ "label": "Pin to a dashboard",
3553
+ "prompt": "Pin this view to my main dashboard."
3554
+ },
3555
+ {
3556
+ "label": "Add to a notebook",
3557
+ "prompt": "Add this finding to my notebook."
3558
+ }
3559
+ ];
3560
+ var deepDiveFollowUps = [
3561
+ {
3562
+ "label": "Save this exploration",
3563
+ "prompt": "Save the most useful chart from this session as a dashboard I can come back to."
3564
+ },
3565
+ {
3566
+ "label": "Summarize what we found",
3567
+ "prompt": "Summarize the key findings from everything we just looked at in 3 bullets."
3568
+ },
3569
+ {
3570
+ "label": "Pin a session summary",
3571
+ "prompt": "Pin a summary of this session to my main dashboard."
3572
+ },
3573
+ {
3574
+ "label": "Write to a notebook",
3575
+ "prompt": "Write everything we just covered into a notebook entry I can revisit."
3576
+ }
3577
+ ];
3578
+ var crossSellByRole = {
3579
+ "founder": [{
3580
+ "product": "Session Replay",
3581
+ "prompt": "Find 3 recent sessions where a user looked at pricing but did not sign up.",
3582
+ "description": "Watch what users see — replay turns funnel drop-offs into video."
3583
+ }, {
3584
+ "product": "Surveys",
3585
+ "prompt": "Show me how my NPS results have trended over the last quarter.",
3586
+ "description": "Quantitative pulse check on the survey side of PostHog."
3587
+ }],
3588
+ "product": [{
3589
+ "product": "Experiments",
3590
+ "prompt": "Show me results from my latest onboarding experiment — which variant is winning?",
3591
+ "description": "Experiments piggyback on flags — same SDK, all readable here."
3592
+ }, {
3593
+ "product": "Session Replay",
3594
+ "prompt": "Find sessions where users got stuck on the empty state in onboarding.",
3595
+ "description": "See what funnels can't show you."
3596
+ }],
3597
+ "leadership": [{
3598
+ "product": "Surveys",
3599
+ "prompt": "Show me NPS scores from the last quarter — who are the detractors?",
3600
+ "description": "Read the survey data PostHog already collects for you."
3601
+ }, {
3602
+ "product": "Data Warehouse",
3603
+ "prompt": "Compare MRR by signup source using Stripe data joined with event data.",
3604
+ "description": "Query revenue alongside events when warehouse is connected."
3605
+ }],
3606
+ "marketing": [{
3607
+ "product": "Session Replay",
3608
+ "prompt": "Watch 5 sessions from users who came via our last campaign and converted.",
3609
+ "description": "See campaign visitors behave — beyond aggregate numbers."
3610
+ }, {
3611
+ "product": "Web Analytics",
3612
+ "prompt": "Show me top traffic sources to the pricing page this week.",
3613
+ "description": "GA-style first-party web analytics, no cookie banner."
3614
+ }],
3615
+ "engineering": [{
3616
+ "product": "Error Tracking",
3617
+ "prompt": "Show me the top 5 errors this week and who is affected.",
3618
+ "description": "Built-in error tracking — no Sentry subscription."
3619
+ }, {
3620
+ "product": "Session Replay",
3621
+ "prompt": "Replay the last 3 sessions that hit a 5xx error.",
3622
+ "description": "Stack trace meets replay — see what the user did."
3623
+ }],
3624
+ "data": [{
3625
+ "product": "Data Warehouse",
3626
+ "prompt": "Join my event stream with Stripe subscriptions to surface churn signals.",
3627
+ "description": "Connect Stripe / Salesforce / S3, query everything with SQL."
3628
+ }, {
3629
+ "product": "LLM Observability",
3630
+ "prompt": "Show me the top 5 LLM prompts by cost over the last 7 days.",
3631
+ "description": "Track LLM calls, latency, and cost next to product events."
3632
+ }]
3633
+ };
3634
+ var neutralCrossSell = [{
3635
+ "product": "Session Replay",
3636
+ "prompt": "Show me 5 recent sessions where users dropped off before completing signup.",
3637
+ "description": "Replay what users actually do — included on every plan."
3638
+ }, {
3639
+ "product": "Error Tracking",
3640
+ "prompt": "List the top errors my users hit this week.",
3641
+ "description": "Built-in error tracking — no separate tool."
3642
+ }];
3643
+ //#endregion
3644
+ //#region src/lib/mcp-role-prompts.ts
3645
+ /**
3646
+ * Roles that ship from `role_at_organization` on the PostHog user object.
3647
+ * `security` isn't in the enum upstream — the engineering kit covers
3648
+ * that audience.
3649
+ */
3650
+ const TAILORED_ROLES = [
3651
+ "founder",
3652
+ "product",
3653
+ "leadership",
3654
+ "marketing",
3655
+ "engineering",
3656
+ "data"
3657
+ ];
3658
+ const FOLLOW_UP_EXIT_SENTINEL = "__follow_up_exit__";
3659
+ /**
3660
+ * Always shown as the picker's first option regardless of role —
3661
+ * a safe generic read that works on any project setup. The screen
3662
+ * prepends it and dedupes against the role kit so it never appears
3663
+ * twice when DEFAULT_KIT happens to include it.
3664
+ */
3665
+ const PINNED_FIRST_PROMPT = pinnedFirstPrompt;
3666
+ const DEFAULT_KIT = defaultKit;
3667
+ const ROLE_KITS = roleKits;
3668
+ const ROLE_FAMILY_OVERRIDES = roleFamilyOverrides;
3669
+ const ROLE_GREETINGS = roleGreetings;
3670
+ const NEUTRAL_GREETING = neutralGreeting;
3671
+ const TOOL_FOLLOW_UPS = toolFollowUps;
3672
+ const ROLE_FOLLOW_UPS = roleFollowUps;
3673
+ const GENERIC_FOLLOW_UPS = genericFollowUps;
3674
+ const DEEP_DIVE_FOLLOW_UPS = deepDiveFollowUps;
3675
+ const CROSS_SELL_BY_ROLE = crossSellByRole;
3676
+ const NEUTRAL_CROSS_SELL = neutralCrossSell;
3677
+ const INTEGRATION_FAMILY = {
3678
+ nextjs: "fullstack",
3679
+ nuxt: "fullstack",
3680
+ "tanstack-start": "fullstack",
3681
+ astro: "fullstack",
3682
+ sveltekit: "fullstack",
3683
+ vue: "frontend-web",
3684
+ angular: "frontend-web",
3685
+ "react-router": "frontend-web",
3686
+ "tanstack-router": "frontend-web",
3687
+ javascript_web: "frontend-web",
3688
+ "react-native": "mobile",
3689
+ swift: "mobile",
3690
+ android: "mobile",
3691
+ django: "backend",
3692
+ flask: "backend",
3693
+ fastapi: "backend",
3694
+ python: "backend",
3695
+ laravel: "backend",
3696
+ rails: "backend",
3697
+ ruby: "backend",
3698
+ javascript_node: "backend"
3699
+ };
3700
+ const EXIT_FOLLOW_UP = {
3701
+ label: "I'm done — exit",
3702
+ prompt: FOLLOW_UP_EXIT_SENTINEL
3703
+ };
3704
+ function isTailoredRole(role) {
3705
+ return typeof role === "string" && TAILORED_ROLES.includes(role);
3706
+ }
3707
+ /**
3708
+ * Strip MCP tool-name prefixes so lookup keys can stay short. Real MCP
3709
+ * tool names arrive as `mcp__<server>__<tool>`; the agent SDK also
3710
+ * sometimes drops the prefix. We take the substring after the last
3711
+ * double-underscore (or the input untouched if there's none).
3712
+ */
3713
+ function normalizeToolName(toolName) {
3714
+ if (!toolName) return null;
3715
+ const idx = toolName.lastIndexOf("__");
3716
+ return idx >= 0 ? toolName.slice(idx + 2) : toolName;
3717
+ }
3718
+ /** Pick `n` items from a pool starting at a rotation offset. */
3719
+ function pickRotated(pool, n, rotation) {
3720
+ if (pool.length === 0) return [];
3721
+ if (pool.length <= n) return pool;
3722
+ const start = (Math.floor(rotation) % pool.length + pool.length) % pool.length;
3723
+ const result = [];
3724
+ for (let i = 0; i < n; i++) result.push(pool[(start + i) % pool.length]);
3725
+ return result;
3726
+ }
3727
+ /** Drop duplicates while preserving order (by prompt text). */
3728
+ function dedupeFollowUps(list) {
3729
+ const seen = /* @__PURE__ */ new Set();
3730
+ const out = [];
3731
+ for (const f of list) {
3732
+ if (seen.has(f.prompt)) continue;
3733
+ seen.add(f.prompt);
3734
+ out.push(f);
3735
+ }
3736
+ return out;
3737
+ }
3738
+ function getFrameworkFamily(integration) {
3739
+ if (!integration) return "unknown";
3740
+ return INTEGRATION_FAMILY[integration] ?? "unknown";
3741
+ }
3742
+ /**
3743
+ * Resolve the right kit given everything we know about the user + project.
3744
+ * Always returns at least DEFAULT_KIT; never throws.
3745
+ */
3746
+ function getRolePrompts(role, integration) {
3747
+ const family = getFrameworkFamily(integration);
3748
+ if (!isTailoredRole(role)) return DEFAULT_KIT;
3749
+ const baseKit = ROLE_KITS[role];
3750
+ const overridesForFamily = ROLE_FAMILY_OVERRIDES[role]?.[family];
3751
+ if (!overridesForFamily) return baseKit;
3752
+ return baseKit.map((entry) => {
3753
+ return (entry.key ? overridesForFamily[entry.key] : void 0) ?? entry;
3754
+ });
3755
+ }
3756
+ function getRoleGreeting(role) {
3757
+ if (!isTailoredRole(role)) return NEUTRAL_GREETING;
3758
+ return ROLE_GREETINGS[role];
3759
+ }
3760
+ /**
3761
+ * Resolve `FOLLOW_UP_COUNT` context-aware follow-ups + an always-present
3762
+ * exit entry. Pulls from up to four pools — tool-specific, role-specific,
3763
+ * deep-dive (only after the user has explored a few steps), and generic —
3764
+ * dedupes, filters out anything already in `branchHistory`, then picks
3765
+ * `FOLLOW_UP_COUNT` with a rotation offset driven by `branchHistory.length`
3766
+ * so successive visits surface different slices.
3767
+ */
3768
+ function getFollowUps(args) {
3769
+ const { lastToolName, role, branchHistory } = args;
3770
+ const normalized = normalizeToolName(lastToolName);
3771
+ const depth = branchHistory.length;
3772
+ const candidates = [];
3773
+ if (normalized && TOOL_FOLLOW_UPS[normalized]) candidates.push(...TOOL_FOLLOW_UPS[normalized]);
3774
+ if (isTailoredRole(role)) candidates.push(...ROLE_FOLLOW_UPS[role]);
3775
+ if (depth >= 3) candidates.push(...DEEP_DIVE_FOLLOW_UPS);
3776
+ candidates.push(...GENERIC_FOLLOW_UPS);
3777
+ const deduped = dedupeFollowUps(candidates);
3778
+ const seen = new Set(branchHistory);
3779
+ return [...pickRotated(deduped.filter((f) => !seen.has(f.prompt)), 3, depth), EXIT_FOLLOW_UP];
3780
+ }
3781
+ /**
3782
+ * Cross-sell prompts to surface above the role kit in PromptPicker.
3783
+ * Filtered by role so the recommendations stay coherent (founders see
3784
+ * the "exec-friendly" cross-sells, engineers see "debug-friendly", etc).
3785
+ */
3786
+ function getCrossSellPrompts(role) {
3787
+ if (!isTailoredRole(role)) return NEUTRAL_CROSS_SELL;
3788
+ return CROSS_SELL_BY_ROLE[role];
3789
+ }
3790
+ //#endregion
3791
+ //#region src/ui/tui/screens/McpSuggestedPromptsScreen.tsx
3792
+ /**
3793
+ * McpSuggestedPromptsScreen — shown after MCP install succeeds in the
3794
+ * standalone `wizard mcp add` program, and as the entry point for
3795
+ * `wizard mcp tutorial`.
3796
+ *
3797
+ * Phases:
3798
+ * 1. Choose — opens with a Log in / Exit picker, framed by a
3799
+ * teaser of what MCP can do.
3800
+ * 2. Authenticating — runs `services.performLogin()` (OAuth in
3801
+ * production, canned values in the playground).
3802
+ * Renders a spinner + login URL inline while the
3803
+ * promise is pending. Errors return to Choose
3804
+ * with an inline error line.
3805
+ * 3. Greeting — role-tuned welcome via `getRoleGreeting`. A
3806
+ * ContentSequencer animates the headline,
3807
+ * bullets, and outro, then hands off to
3808
+ * PromptPicker. Only fires once per session
3809
+ * (returning via `[p]` skips it).
3810
+ * 4. PromptPicker — lists the role-tailored kit from
3811
+ * `getRolePrompts`; user picks one to run.
3812
+ * 5. Running — streams the agent's response inline via
3813
+ * `services.runPromptStreaming`. Text chunks
3814
+ * typewrite in; tool calls and results render
3815
+ * as styled badges. `[esc]` aborts; `[p]`
3816
+ * returns to the picker. On `done`/`error`,
3817
+ * auto-advances to FollowUp.
3818
+ * 6. FollowUp — surfaces 3 context-aware next prompts inferred
3819
+ * from the last tool the agent used (via
3820
+ * `getFollowUps`), plus an explicit exit.
3821
+ * Picking a follow-up re-enters Running; the
3822
+ * conversation tree grows as deep as
3823
+ * MAX_PROMPT_RUNS allows.
3824
+ *
3825
+ * Credentials are guaranteed non-null once Greeting / PromptPicker /
3826
+ * Running / FollowUp are reached (the Choose → Authenticating gate
3827
+ * forces a successful login first). A defensive throw protects the
3828
+ * Running useEffect against a state-machine bug.
3829
+ */
3830
+ const MAX_PROMPT_RUNS = 5;
3831
+ const FOLLOW_UP_DELAY_MS = 3e3;
3832
+ const McpSuggestedPromptsScreen = ({ store, services }) => {
3833
+ useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
3834
+ const session = store.session;
3835
+ const kit = getRolePrompts(session.roleAtOrganization, session.integration);
3836
+ const crossSell = useMemo(() => getCrossSellPrompts(session.roleAtOrganization), [session.roleAtOrganization]);
3837
+ const greeting = useMemo(() => getRoleGreeting(session.roleAtOrganization), [session.roleAtOrganization]);
3838
+ const [phase, setPhase] = useState("choose");
3839
+ const [loginError, setLoginError] = useState(null);
3840
+ const [runningPrompt, setRunningPrompt] = useState(null);
3841
+ const [runChunks, setRunChunks] = useState([]);
3842
+ const [runStartedAt, setRunStartedAt] = useState(null);
3843
+ const [runDurationSecs, setRunDurationSecs] = useState(null);
3844
+ const [runCount, setRunCount] = useState(0);
3845
+ const canPickAnother = runCount < MAX_PROMPT_RUNS;
3846
+ const [lastToolName, setLastToolName] = useState(null);
3847
+ const [branchHistory, setBranchHistory] = useState([]);
3848
+ const runAbortRef = useRef(null);
3849
+ const currentSessionIdRef = useRef(null);
3850
+ useEffect(() => {
3851
+ if (phase !== "authenticating") return;
3852
+ let cancelled = false;
3853
+ (async () => {
3854
+ try {
3855
+ const { credentials, roleAtOrganization, user } = await services.performLogin();
3856
+ if (cancelled) return;
3857
+ store.setCredentials(credentials);
3858
+ store.setRoleAtOrganization(roleAtOrganization);
3859
+ store.setApiUser(user);
3860
+ store.setLoginUrl(null);
3861
+ setPhase("greeting");
3862
+ } catch (err) {
3863
+ if (cancelled) return;
3864
+ const message = err instanceof Error ? err.message : String(err);
3865
+ logToFile(`[McpSuggestedPromptsScreen] login failed: ${message}`);
3866
+ store.setLoginUrl(null);
3867
+ setLoginError(message);
3868
+ setPhase("choose");
3869
+ }
3870
+ })();
3871
+ return () => {
3872
+ cancelled = true;
3873
+ };
3874
+ }, [
3875
+ phase,
3876
+ services,
3877
+ store
3878
+ ]);
3879
+ useEffect(() => {
3880
+ if (phase !== "running") return;
3881
+ if (!runningPrompt) return;
3882
+ if (!session.credentials) throw new Error("[McpSuggestedPromptsScreen] Running phase reached without credentials. The Choose gate should have prevented this.");
3883
+ const controller = new AbortController();
3884
+ runAbortRef.current = controller;
3885
+ const startedAt = Date.now();
3886
+ setRunStartedAt(startedAt);
3887
+ setRunChunks([]);
3888
+ setLastToolName(null);
3889
+ setRunDurationSecs(null);
3890
+ const finishStream = (kind, durationMs, errorText) => {
3891
+ if (controller.signal.aborted) return;
3892
+ setRunDurationSecs(Math.round(durationMs / 1e3));
3893
+ if (kind === "done") analytics.wizardCapture("mcp suggested prompts run", {
3894
+ prompt: runningPrompt,
3895
+ durationMs
3896
+ });
3897
+ else analytics.wizardCapture("mcp suggested prompts run failed", {
3898
+ prompt: runningPrompt,
3899
+ error: errorText
3900
+ });
3901
+ setTimeout(() => {
3902
+ if (controller.signal.aborted) return;
3903
+ setPhase("follow-up");
3904
+ }, FOLLOW_UP_DELAY_MS);
3905
+ };
3906
+ (async () => {
3907
+ const credentials = session.credentials;
3908
+ if (!credentials) return;
3909
+ try {
3910
+ for await (const chunk of services.runPromptStreaming({
3911
+ prompt: runningPrompt,
3912
+ credentials,
3913
+ signal: controller.signal,
3914
+ resumeSessionId: currentSessionIdRef.current ?? void 0
3915
+ })) {
3916
+ if (controller.signal.aborted) return;
3917
+ setRunChunks((prev) => [...prev, chunk]);
3918
+ if (chunk.kind === "tool-call") setLastToolName(chunk.toolName);
3919
+ if (chunk.kind === "done") {
3920
+ if (chunk.sessionId) currentSessionIdRef.current = chunk.sessionId;
3921
+ finishStream("done", Date.now() - startedAt);
3922
+ return;
3923
+ }
3924
+ if (chunk.kind === "error") {
3925
+ finishStream("error", Date.now() - startedAt, chunk.text);
3926
+ return;
3927
+ }
3928
+ }
3929
+ } catch (err) {
3930
+ if (controller.signal.aborted) return;
3931
+ const text = err instanceof Error ? err.message : String(err);
3932
+ setRunChunks((prev) => [...prev, {
3933
+ kind: "error",
3934
+ text
3935
+ }]);
3936
+ finishStream("error", Date.now() - startedAt, text);
3937
+ }
3938
+ })();
3939
+ return () => {
3940
+ controller.abort();
3941
+ if (runAbortRef.current === controller) runAbortRef.current = null;
3942
+ };
3943
+ }, [
3944
+ phase,
3945
+ runningPrompt,
3946
+ services,
3947
+ session.credentials
3948
+ ]);
3949
+ const enterGoodbye = () => {
3950
+ runAbortRef.current?.abort();
3951
+ setPhase("goodbye");
3952
+ };
3953
+ const closeWizard = () => {
3954
+ setPhase("done");
3955
+ setTimeout(() => {
3956
+ store.setMcpSuggestedPromptsDismissed();
3957
+ }, 0);
3958
+ };
3959
+ const handleChoice = (value) => {
3960
+ const choice = Array.isArray(value) ? value[0] : value;
3961
+ setLoginError(null);
3962
+ if (choice === "login") {
3963
+ analytics.wizardCapture("mcp suggested prompts choose", { choice: "login" });
3964
+ setPhase("authenticating");
3965
+ } else {
3966
+ analytics.wizardCapture("mcp suggested prompts choose", { choice: "exit" });
3967
+ enterGoodbye();
3968
+ }
3969
+ };
3970
+ const startRun = (prompt) => {
3971
+ setRunningPrompt(prompt);
3972
+ setRunCount((c) => c + 1);
3973
+ setBranchHistory((h) => [...h, prompt]);
3974
+ setPhase("running");
3975
+ };
3976
+ const handlePromptPick = (value) => {
3977
+ startRun(Array.isArray(value) ? value[0] : value);
3978
+ };
3979
+ const handleFollowUpPick = (value) => {
3980
+ const picked = Array.isArray(value) ? value[0] : value;
3981
+ if (picked === "__follow_up_exit__") {
3982
+ analytics.wizardCapture("mcp suggested prompts follow-up", {
3983
+ choice: "exit",
3984
+ depth: branchHistory.length
3985
+ });
3986
+ enterGoodbye();
3987
+ return;
3988
+ }
3989
+ analytics.wizardCapture("mcp suggested prompts follow-up", {
3990
+ choice: "continue",
3991
+ depth: branchHistory.length,
3992
+ lastToolName
3993
+ });
3994
+ startRun(picked);
3995
+ };
3996
+ useKeyBindings("mcp-suggested-prompts", [
3997
+ {
3998
+ match: "escape",
3999
+ label: "esc",
4000
+ action: phase === "goodbye" ? "close" : "exit",
4001
+ handler: () => {
4002
+ if (phase === "goodbye") closeWizard();
4003
+ else if (phase === "running" || phase === "prompt-picker" || phase === "follow-up" || phase === "greeting") enterGoodbye();
4004
+ }
4005
+ },
4006
+ {
4007
+ match: "p",
4008
+ label: "p",
4009
+ action: canPickAnother ? "pick new prompt" : "cap reached",
4010
+ handler: () => {
4011
+ if (phase !== "running" && phase !== "follow-up") return;
4012
+ if (!canPickAnother) return;
4013
+ runAbortRef.current?.abort();
4014
+ currentSessionIdRef.current = null;
4015
+ setPhase("prompt-picker");
4016
+ }
4017
+ },
4018
+ ...phase === "greeting" ? [{
4019
+ match: "return",
4020
+ label: "enter",
4021
+ action: "continue",
4022
+ handler: () => {
4023
+ setPhase("prompt-picker");
4024
+ }
4025
+ }] : []
4026
+ ]);
4027
+ return /* @__PURE__ */ jsx(Box, {
4028
+ flexDirection: "column",
4029
+ flexGrow: 1,
4030
+ children: /* @__PURE__ */ jsxs(Box, {
4031
+ marginTop: 1,
4032
+ flexDirection: "column",
4033
+ children: [
4034
+ phase === "choose" && /* @__PURE__ */ jsx(ChoosePhase, {
4035
+ error: loginError,
4036
+ onSelect: handleChoice
4037
+ }),
4038
+ phase === "authenticating" && /* @__PURE__ */ jsx(AuthenticatingPhase, { loginUrl: session.loginUrl }),
4039
+ phase === "greeting" && /* @__PURE__ */ jsx(GreetingPhase, {
4040
+ greeting,
4041
+ userDisplayName: session.apiUser?.first_name || null,
4042
+ onComplete: () => setPhase("prompt-picker")
4043
+ }),
4044
+ phase === "prompt-picker" && /* @__PURE__ */ jsx(PromptPickerPhase, {
4045
+ promptKit: kit,
4046
+ crossSell,
4047
+ onSelect: handlePromptPick
4048
+ }),
4049
+ phase === "running" && runningPrompt && /* @__PURE__ */ jsx(RunningPhase, {
4050
+ prompt: runningPrompt,
4051
+ chunks: runChunks,
4052
+ startedAt: runStartedAt,
4053
+ frozenDurationSecs: runDurationSecs,
4054
+ runCount,
4055
+ maxRuns: MAX_PROMPT_RUNS
4056
+ }),
4057
+ phase === "follow-up" && /* @__PURE__ */ jsxs(Box, {
4058
+ flexDirection: "column",
4059
+ flexGrow: 1,
4060
+ children: [runningPrompt && /* @__PURE__ */ jsx(Box, {
4061
+ flexDirection: "column",
4062
+ flexShrink: 1,
4063
+ children: /* @__PURE__ */ jsx(RunningPhase, {
4064
+ prompt: runningPrompt,
4065
+ chunks: runChunks,
4066
+ startedAt: runStartedAt,
4067
+ frozenDurationSecs: runDurationSecs,
4068
+ runCount,
4069
+ maxRuns: MAX_PROMPT_RUNS
4070
+ })
4071
+ }), /* @__PURE__ */ jsx(Box, {
4072
+ marginTop: 1,
4073
+ flexShrink: 0,
4074
+ flexDirection: "column",
4075
+ children: /* @__PURE__ */ jsx(FollowUpPhase, {
4076
+ lastToolName,
4077
+ lastPrompt: runningPrompt,
4078
+ chunks: runChunks,
4079
+ role: session.roleAtOrganization,
4080
+ branchHistory,
4081
+ canPickAnother,
4082
+ maxRuns: MAX_PROMPT_RUNS,
4083
+ onSelect: handleFollowUpPick
4084
+ })
4085
+ })]
4086
+ }),
4087
+ phase === "goodbye" && /* @__PURE__ */ jsx(GoodbyePhase, {
4088
+ installedClients: session.mcpInstalledClients,
4089
+ role: session.roleAtOrganization,
4090
+ integration: session.integration,
4091
+ engaged: branchHistory.length > 0,
4092
+ onClose: closeWizard
4093
+ })
4094
+ ]
4095
+ })
4096
+ });
4097
+ };
4098
+ const ChoosePhase = ({ error, onSelect }) => /* @__PURE__ */ jsxs(Box, {
4099
+ flexDirection: "column",
4100
+ children: [
4101
+ /* @__PURE__ */ jsx(Text, {
4102
+ bold: true,
4103
+ color: Colors.accent,
4104
+ children: "PostHog MCP"
4105
+ }),
4106
+ /* @__PURE__ */ jsx(Box, {
4107
+ marginTop: 1,
4108
+ children: /* @__PURE__ */ jsx(Text, { children: "With MCP your agent works directly with the PostHog platform. You can prompt it to:" })
4109
+ }),
4110
+ /* @__PURE__ */ jsxs(Box, {
4111
+ marginTop: 1,
4112
+ flexDirection: "column",
4113
+ children: [
4114
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
4115
+ color: "cyan",
4116
+ children: Icons.diamond
4117
+ }), " Build dashboards"] }),
4118
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
3001
4119
  color: "cyan",
3002
4120
  children: Icons.diamond
3003
4121
  }), " Run SQL queries"] }),
@@ -3061,56 +4179,109 @@ const AuthenticatingPhase = ({ loginUrl }) => /* @__PURE__ */ jsxs(Box, {
3061
4179
  ] })
3062
4180
  })]
3063
4181
  });
3064
- const PromptPickerPhase = ({ promptKit, userDisplayName, onSelect }) => {
3065
- const options = promptKit.map((p) => ({
3066
- label: p.prompt,
3067
- value: p.prompt
3068
- }));
3069
- return /* @__PURE__ */ jsx(Box, {
4182
+ const GreetingPhase = ({ greeting, userDisplayName, onComplete }) => {
4183
+ const blocks = [];
4184
+ if (userDisplayName) blocks.push({
4185
+ content: `Hi ${userDisplayName}!`,
4186
+ mode: 0,
4187
+ animationInterval: 70,
4188
+ pause: 1200
4189
+ });
4190
+ blocks.push({
4191
+ content: greeting.headline,
4192
+ mode: 0,
4193
+ animationInterval: 45,
4194
+ pause: 2e3
4195
+ });
4196
+ blocks.push({
4197
+ type: "lines",
4198
+ lines: greeting.bullets.map((bullet, i) => /* @__PURE__ */ jsxs(Text, { children: [
4199
+ /* @__PURE__ */ jsx(Text, {
4200
+ color: Colors.primary,
4201
+ children: Icons.diamond
4202
+ }),
4203
+ " ",
4204
+ /* @__PURE__ */ jsx(Text, {
4205
+ dimColor: true,
4206
+ children: bullet
4207
+ })
4208
+ ] }, i)),
4209
+ interval: 700,
4210
+ pause: 2200
4211
+ });
4212
+ blocks.push({
4213
+ content: greeting.outro,
4214
+ mode: 0,
4215
+ animationInterval: 38,
4216
+ pause: 1800
4217
+ });
4218
+ return /* @__PURE__ */ jsxs(Box, {
3070
4219
  flexDirection: "column",
3071
- children: /* @__PURE__ */ jsx(ContentSequencer, {
3072
- blocks: [
3073
- {
3074
- content: `Hello there, ${userDisplayName || "there"}!`,
3075
- mode: 0,
3076
- animationInterval: 100,
3077
- pause: 1200,
3078
- dimWhenComplete: false
3079
- },
3080
- {
3081
- content: "Pick a prompt to see the PostHog MCP in action.",
3082
- mode: 0,
3083
- animationInterval: 50,
3084
- pause: 1e3,
3085
- dimWhenComplete: false
3086
- },
3087
- {
3088
- content: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(PickerMenu, {
3089
- options,
3090
- optionMarginBottom: 1,
3091
- onSelect
3092
- }), /* @__PURE__ */ jsx(Box, {
3093
- marginTop: 2,
3094
- children: /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
3095
- bold: true,
3096
- children: "[esc]"
3097
- }), /* @__PURE__ */ jsx(Text, { children: " to exit" })] })
3098
- })] }),
3099
- persist: true
3100
- }
3101
- ],
4220
+ children: [/* @__PURE__ */ jsx(Box, {
4221
+ marginBottom: 1,
4222
+ children: /* @__PURE__ */ jsx(Text, {
4223
+ bold: true,
4224
+ color: Colors.accent,
4225
+ children: "MCP tutorial"
4226
+ })
4227
+ }), /* @__PURE__ */ jsx(ContentSequencer, {
4228
+ blocks,
3102
4229
  mode: 0,
3103
- blockInterval: 350
3104
- })
4230
+ blockInterval: 500,
4231
+ onSequenceComplete: onComplete
4232
+ })]
4233
+ });
4234
+ };
4235
+ const PromptPickerPhase = ({ promptKit, crossSell, onSelect }) => {
4236
+ const seenPrompts = /* @__PURE__ */ new Set();
4237
+ const options = [
4238
+ PINNED_FIRST_PROMPT,
4239
+ ...crossSell,
4240
+ ...promptKit
4241
+ ].filter((o) => {
4242
+ if (seenPrompts.has(o.prompt)) return false;
4243
+ seenPrompts.add(o.prompt);
4244
+ return true;
4245
+ }).slice(0, 4).map((o) => ({
4246
+ label: o.product ? `Try ${o.product} — ${o.label ?? o.prompt}` : o.label ?? o.prompt,
4247
+ value: o.prompt
4248
+ }));
4249
+ return /* @__PURE__ */ jsxs(Box, {
4250
+ flexDirection: "column",
4251
+ children: [
4252
+ /* @__PURE__ */ jsx(Box, {
4253
+ marginBottom: 1,
4254
+ children: /* @__PURE__ */ jsx(Text, {
4255
+ bold: true,
4256
+ color: Colors.accent,
4257
+ children: "MCP tutorial"
4258
+ })
4259
+ }),
4260
+ /* @__PURE__ */ jsx(Box, {
4261
+ marginBottom: 1,
4262
+ children: /* @__PURE__ */ jsx(Text, { children: "Pick a prompt to see the PostHog MCP in action." })
4263
+ }),
4264
+ /* @__PURE__ */ jsx(PickerMenu, {
4265
+ options,
4266
+ optionMarginBottom: 1,
4267
+ onSelect
4268
+ }),
4269
+ /* @__PURE__ */ jsx(Box, {
4270
+ marginTop: 2,
4271
+ children: /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
4272
+ bold: true,
4273
+ children: "[esc]"
4274
+ }), /* @__PURE__ */ jsx(Text, { children: " to exit" })] })
4275
+ })
4276
+ ]
3105
4277
  });
3106
4278
  };
3107
- const RunningPhase = ({ prompt, chunks, startedAt, canPickAnother, runCount, maxRuns }) => {
4279
+ const RunningPhase = ({ prompt, chunks, startedAt, frozenDurationSecs, runCount, maxRuns }) => {
3108
4280
  const isDone = chunks.some((c) => c.kind === "done");
3109
4281
  const errorChunk = chunks.find((c) => c.kind === "error");
3110
4282
  const finished = isDone || !!errorChunk;
3111
- const elapsed = startedAt ? Math.round((Date.now() - startedAt) / 1e3) : 0;
3112
- const visibleChunks = finished ? chunks.filter((c) => c.kind === "text" || c.kind === "error") : chunks;
3113
- const cappedChunks = finished ? capTextChunks(visibleChunks) : visibleChunks;
4283
+ const elapsed = frozenDurationSecs ?? (startedAt ? Math.round((Date.now() - startedAt) / 1e3) : 0);
4284
+ const visibleChunks = finished ? capTextChunks(collapseToFinalAnswer(chunks)) : chunks;
3114
4285
  return /* @__PURE__ */ jsxs(Box, {
3115
4286
  flexDirection: "column",
3116
4287
  children: [
@@ -3149,52 +4320,63 @@ const RunningPhase = ({ prompt, chunks, startedAt, canPickAnother, runCount, max
3149
4320
  /* @__PURE__ */ jsx(Box, {
3150
4321
  marginTop: 1,
3151
4322
  flexDirection: "column",
3152
- children: cappedChunks.map((chunk, idx) => /* @__PURE__ */ jsx(ChunkLine, { chunk }, idx))
3153
- }),
3154
- finished && !canPickAnother && /* @__PURE__ */ jsx(Box, {
3155
- marginTop: 1,
3156
- children: /* @__PURE__ */ jsxs(Text, { children: [
3157
- /* @__PURE__ */ jsxs(Text, {
3158
- dimColor: true,
3159
- children: [
3160
- "You've hit the ",
3161
- maxRuns,
3162
- "-prompt tutorial cap. Press",
3163
- " "
3164
- ]
3165
- }),
3166
- /* @__PURE__ */ jsx(Text, {
3167
- bold: true,
3168
- dimColor: true,
3169
- children: "[esc]"
3170
- }),
3171
- /* @__PURE__ */ jsx(Text, {
3172
- dimColor: true,
3173
- children: " to exit."
3174
- })
3175
- ] })
4323
+ children: visibleChunks.map((chunk, idx) => /* @__PURE__ */ jsx(ChunkLine, { chunk }, idx))
3176
4324
  })
3177
4325
  ]
3178
4326
  });
3179
4327
  };
3180
4328
  /**
4329
+ * Strip everything except the agent's final answer + any error chunks.
4330
+ * Drops tool-call / tool-result chatter (their work is done once the
4331
+ * stream completes) and any text blocks emitted BEFORE the last text
4332
+ * block — those are typically Sonnet's "I'll query X…" preamble that
4333
+ * arrives alongside the first tool_use and adds noise above the picker.
4334
+ *
4335
+ * If the run produced no text at all (pure tool calls, or only errors),
4336
+ * fall through to whatever chunks survived so the user isn't left with
4337
+ * a blank result.
4338
+ */
4339
+ function collapseToFinalAnswer(chunks) {
4340
+ const textChunks = chunks.filter((c) => c.kind === "text");
4341
+ const errors = chunks.filter((c) => c.kind === "error");
4342
+ if (textChunks.length === 0) return errors;
4343
+ return [textChunks[textChunks.length - 1], ...errors];
4344
+ }
4345
+ /**
3181
4346
  * Belt-and-suspenders fallback for runs where Claude ignored the
3182
4347
  * terminal-fit system prompt and produced an overlong response. Joins
3183
- * all text chunks, slices to the last N lines that fit in the current
3184
- * terminal, and prepends an indicator showing how many lines got cut.
3185
- * Errors are preserved separately so failures don't disappear into the
3186
- * truncation.
4348
+ * all text chunks, then walks them from the bottom keeping only as many
4349
+ * lines as fit in the visual row budget — wide lines that wrap to
4350
+ * multiple rows on a narrow terminal cost their wrapped row count, not
4351
+ * 1. Prepends an indicator showing how many source lines got cut. Tool
4352
+ * calls, results, and errors are preserved separately so they don't
4353
+ * disappear into the truncation.
4354
+ *
4355
+ * Visual-row-aware truncation is what makes the FollowUp picker feel
4356
+ * pinned: a 5-row table that wraps to 12 visual rows on a 60-col
4357
+ * terminal correctly counts as 12, so the cap leaves exactly the room
4358
+ * the picker needs.
3187
4359
  */
3188
4360
  function capTextChunks(chunks) {
3189
4361
  const rows = process.stdout.rows ?? 24;
3190
- const maxMessageRows = Math.max(6, rows - 8);
4362
+ const cols = process.stdout.columns ?? 120;
4363
+ const maxVisualRows = Math.max(3, rows - 19);
3191
4364
  const textChunks = chunks.filter((c) => c.kind === "text");
3192
4365
  const errors = chunks.filter((c) => c.kind === "error");
3193
4366
  if (textChunks.length === 0) return chunks;
3194
4367
  const lines = textChunks.map((c) => c.text).join("").split("\n");
3195
- if (lines.length <= maxMessageRows) return chunks;
3196
- const hidden = lines.length - maxMessageRows;
3197
- const tail = lines.slice(-maxMessageRows).join("\n");
4368
+ const visualRows = (line) => Math.max(1, Math.ceil(line.length / cols));
4369
+ let used = 0;
4370
+ let keepFrom = lines.length;
4371
+ for (let i = lines.length - 1; i >= 0; i--) {
4372
+ const cost = visualRows(lines[i]);
4373
+ if (used + cost > maxVisualRows) break;
4374
+ used += cost;
4375
+ keepFrom = i;
4376
+ }
4377
+ if (keepFrom === 0) return chunks;
4378
+ const hidden = keepFrom;
4379
+ const tail = lines.slice(keepFrom).join("\n");
3198
4380
  return [{
3199
4381
  kind: "text",
3200
4382
  text: `[${hidden} line${hidden === 1 ? "" : "s"} above — expand terminal to see more]\n\n${tail}`
@@ -3225,6 +4407,101 @@ const ChunkLine = ({ chunk }) => {
3225
4407
  });
3226
4408
  return null;
3227
4409
  };
4410
+ const FollowUpPhase = ({ lastToolName, lastPrompt, chunks, role, branchHistory, canPickAnother, maxRuns, onSelect }) => {
4411
+ const followUps = useMemo(() => getFollowUps({
4412
+ lastToolName,
4413
+ lastPrompt: lastPrompt || "",
4414
+ role,
4415
+ branchHistory
4416
+ }), [
4417
+ lastToolName,
4418
+ lastPrompt,
4419
+ role,
4420
+ branchHistory
4421
+ ]);
4422
+ const options = canPickAnother ? followUps.map((f) => ({
4423
+ label: f.label ?? f.prompt,
4424
+ value: f.prompt
4425
+ })) : [{
4426
+ label: "Exit",
4427
+ value: FOLLOW_UP_EXIT_SENTINEL
4428
+ }];
4429
+ return /* @__PURE__ */ jsxs(Box, {
4430
+ flexDirection: "column",
4431
+ children: [/* @__PURE__ */ jsx(Text, { children: chunks.find((c) => c.kind === "error") ? "That one errored out — try a different angle?" : !canPickAnother ? `You've hit the ${maxRuns}-prompt tutorial cap.` : `Want to keep exploring? Select a follow-up prompt.` }), /* @__PURE__ */ jsx(PickerMenu, {
4432
+ options,
4433
+ onSelect
4434
+ })]
4435
+ });
4436
+ };
4437
+ const GoodbyePhase = ({ installedClients, role, integration, engaged, onClose }) => {
4438
+ const samples = getRolePrompts(role, integration).slice(0, 3);
4439
+ const headline = engaged ? "Nice work. You can keep talking to PostHog anytime." : "You're all set — PostHog MCP is here when you're ready.";
4440
+ const introLine = installedClients.length > 0 ? /* @__PURE__ */ jsxs(Text, { children: [
4441
+ "MCP is set up in",
4442
+ " ",
4443
+ /* @__PURE__ */ jsx(Text, {
4444
+ bold: true,
4445
+ color: Colors.primary,
4446
+ children: installedClients.join(", ")
4447
+ }),
4448
+ ". Open one and try a prompt like:"
4449
+ ] }) : /* @__PURE__ */ jsx(Text, { children: "Wherever you have MCP set up (Claude Code, Cursor, VS Code, Windsurf, Zed, etc.), open the agent and try a prompt like:" });
4450
+ return /* @__PURE__ */ jsxs(Box, {
4451
+ flexDirection: "column",
4452
+ children: [
4453
+ /* @__PURE__ */ jsx(Box, {
4454
+ marginBottom: 1,
4455
+ children: /* @__PURE__ */ jsx(Text, {
4456
+ bold: true,
4457
+ color: Colors.accent,
4458
+ children: headline
4459
+ })
4460
+ }),
4461
+ /* @__PURE__ */ jsx(Box, {
4462
+ marginBottom: 1,
4463
+ children: introLine
4464
+ }),
4465
+ /* @__PURE__ */ jsx(Box, {
4466
+ marginBottom: 1,
4467
+ flexDirection: "column",
4468
+ children: samples.map((p, i) => /* @__PURE__ */ jsxs(Box, { children: [
4469
+ /* @__PURE__ */ jsx(Text, {
4470
+ color: Colors.primary,
4471
+ children: Icons.triangleSmallRight
4472
+ }),
4473
+ /* @__PURE__ */ jsx(Text, { children: " " }),
4474
+ /* @__PURE__ */ jsx(Text, {
4475
+ dimColor: true,
4476
+ children: p.prompt
4477
+ })
4478
+ ] }, i))
4479
+ }),
4480
+ /* @__PURE__ */ jsx(Box, {
4481
+ marginBottom: 1,
4482
+ children: /* @__PURE__ */ jsxs(Text, {
4483
+ dimColor: true,
4484
+ children: [
4485
+ "Re-run this tutorial anytime with",
4486
+ " ",
4487
+ /* @__PURE__ */ jsx(Text, {
4488
+ bold: true,
4489
+ children: "npx @posthog/wizard mcp tutorial"
4490
+ }),
4491
+ "."
4492
+ ]
4493
+ })
4494
+ }),
4495
+ /* @__PURE__ */ jsx(PickerMenu, {
4496
+ options: [{
4497
+ label: "Close",
4498
+ value: "close"
4499
+ }],
4500
+ onSelect: onClose
4501
+ })
4502
+ ]
4503
+ });
4504
+ };
3228
4505
  //#endregion
3229
4506
  //#region src/ui/tui/screens/audit/AuditChecksViewer/AreaHeaderRow.tsx
3230
4507
  /** Sub-header row inside the scrollable body — one per area group. */
@@ -3837,11 +5114,83 @@ const EventCaptureSlide = {
3837
5114
  docsUrl: "https://posthog.com/docs/product-analytics/capture-events"
3838
5115
  };
3839
5116
  //#endregion
5117
+ //#region src/ui/tui/screens/audit/slides/writeReport.tsx
5118
+ const ReportVisual = () => /* @__PURE__ */ jsxs(VisualBox, { children: [
5119
+ /* @__PURE__ */ jsx(Text, {
5120
+ dimColor: true,
5121
+ children: "posthog-audit-report.md"
5122
+ }),
5123
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
5124
+ dimColor: true,
5125
+ children: " # "
5126
+ }), /* @__PURE__ */ jsx(Text, { children: "Summary" })] }),
5127
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
5128
+ dimColor: true,
5129
+ children: " # "
5130
+ }), /* @__PURE__ */ jsx(Text, { children: "Recommended actions" })] }),
5131
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
5132
+ dimColor: true,
5133
+ children: " # "
5134
+ }), /* @__PURE__ */ jsx(Text, { children: "Full audit" })] })
5135
+ ] });
5136
+ const WriteReportSlide = {
5137
+ area: "Write report",
5138
+ intro: [
5139
+ "Now we write an audit report at ./posthog-audit-report.md. that summarizes our findings.",
5140
+ "The report leads with a summary, then a prioritized list of fixes with file:line citations, then every check we ran grouped by area so nothing is hidden.",
5141
+ "We will upload the report into a PostHog notebook in the next step."
5142
+ ],
5143
+ visual: /* @__PURE__ */ jsx(ReportVisual, {}),
5144
+ docsUrl: "https://posthog.com/docs/product-analytics/best-practices"
5145
+ };
5146
+ //#endregion
5147
+ //#region src/ui/tui/screens/audit/slides/uploadNotebook.tsx
5148
+ const NotebookVisual = () => /* @__PURE__ */ jsxs(VisualBox, { children: [
5149
+ /* @__PURE__ */ jsx(Text, {
5150
+ dimColor: true,
5151
+ children: "posthog-audit-report.md"
5152
+ }),
5153
+ /* @__PURE__ */ jsx(Text, {
5154
+ dimColor: true,
5155
+ children: " │"
5156
+ }),
5157
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
5158
+ dimColor: true,
5159
+ children: " ▼ "
5160
+ }), /* @__PURE__ */ jsx(Text, {
5161
+ color: "cyan",
5162
+ children: "PostHog notebook"
5163
+ })] }),
5164
+ /* @__PURE__ */ jsx(Text, {
5165
+ dimColor: true,
5166
+ children: " # Summary"
5167
+ }),
5168
+ /* @__PURE__ */ jsx(Text, {
5169
+ dimColor: true,
5170
+ children: " # Recommended actions"
5171
+ }),
5172
+ /* @__PURE__ */ jsx(Text, {
5173
+ dimColor: true,
5174
+ children: " # Full audit"
5175
+ })
5176
+ ] });
5177
+ //#endregion
3840
5178
  //#region src/ui/tui/screens/audit/slides/index.ts
3841
5179
  const AUDIT_AREA_SLIDES = [
3842
5180
  InstallationSlide,
3843
5181
  IdentificationSlide,
3844
- EventCaptureSlide
5182
+ EventCaptureSlide,
5183
+ WriteReportSlide,
5184
+ {
5185
+ area: "Upload notebook",
5186
+ intro: [
5187
+ "Next we upload the report into a PostHog notebook so you can share it with your team as a URL.",
5188
+ "Hang tight.",
5189
+ "The markdown file on disk is still there for you to read locally."
5190
+ ],
5191
+ visual: /* @__PURE__ */ jsx(NotebookVisual, {}),
5192
+ docsUrl: "https://posthog.com/docs/notebooks"
5193
+ }
3845
5194
  ];
3846
5195
  //#endregion
3847
5196
  //#region src/ui/tui/screens/audit-3000/slides/eventQuality.tsx
@@ -4014,6 +5363,6 @@ const AUDIT_3000_AREA_SLIDES = [
4014
5363
  }
4015
5364
  ];
4016
5365
  //#endregion
4017
- export { PickerMenu as C, SplitView as D, LoadingBox as E, CardLayout as O, useStdoutDimensions as S, ProgressList as T, EventPlanViewer as _, TAILORED_ROLES as a, ConfirmationInput as b, SEVERITY_LABEL as c, TipsCard as d, LearnCard as f, ScreenContainer as g, TabContainer as h, McpSuggestedPromptsScreen as i, WizardStore as k, SEVERITY_ORDER as l, HNViewer as m, AUDIT_AREA_SLIDES as n, McpScreen as o, ContentSequencer as p, AuditChecksViewer as r, IssueTable as s, AUDIT_3000_AREA_SLIDES as t, ServiceHealthList as u, LogViewer as v, useKeyBindings as w, GroupedPickerMenu as x, ModalOverlay as y };
5366
+ export { WizardStore as A, useStdoutDimensions as C, LoadingBox as D, ProgressList as E, SplitView as O, GroupedPickerMenu as S, useKeyBindings as T, ScreenContainer as _, McpSuggestedPromptsScreen as a, ModalOverlay as b, IssueTable as c, ServiceHealthList as d, TipsCard as f, TabContainer as g, HNViewer as h, AuditChecksViewer as i, CardLayout as k, SEVERITY_LABEL as l, ContentSequencer as m, AUDIT_AREA_SLIDES as n, TAILORED_ROLES as o, LearnCard as p, VisualBox as r, McpScreen as s, AUDIT_3000_AREA_SLIDES as t, SEVERITY_ORDER as u, EventPlanViewer as v, PickerMenu as w, ConfirmationInput as x, LogViewer as y };
4018
5367
 
4019
- //# sourceMappingURL=slides-Dpj4j0w_.js.map
5368
+ //# sourceMappingURL=slides-DRbBgsdd.js.map