@posthog/wizard 2.37.0 → 2.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/{add-mcp-server-to-clients-CNn9br9l.js → add-mcp-server-to-clients-BB1_aH5M.js} +5 -5
  2. package/dist/{add-mcp-server-to-clients-CNn9br9l.js.map → add-mcp-server-to-clients-BB1_aH5M.js.map} +1 -1
  3. package/dist/{agent-interface-D8jnkEnB.js → agent-interface-kOuzXg84.js} +69 -1119
  4. package/dist/agent-interface-kOuzXg84.js.map +1 -0
  5. package/dist/{agent-runner-BxIYcxmA.js → agent-runner-DZhaiwD5.js} +2571 -1648
  6. package/dist/agent-runner-DZhaiwD5.js.map +1 -0
  7. package/dist/analytics-B1SC2jaf.js +2 -0
  8. package/dist/{analytics-Du8Z_3oX.js → analytics-Z_2kCTpB.js} +4 -3
  9. package/dist/{analytics-Du8Z_3oX.js.map → analytics-Z_2kCTpB.js.map} +1 -1
  10. package/dist/{api-BqKMQVhj.js → api-CwhOyprf.js} +3 -3
  11. package/dist/{api-BqKMQVhj.js.map → api-CwhOyprf.js.map} +1 -1
  12. package/dist/bin.js +64 -50
  13. package/dist/bin.js.map +1 -1
  14. package/dist/{ci-install-UlWb3u5t.js → ci-install-CWDY86uB.js} +4 -4
  15. package/dist/{ci-install-UlWb3u5t.js.map → ci-install-CWDY86uB.js.map} +1 -1
  16. package/dist/{debug-B4AH8Ahp.js → debug-BmdpjMub.js} +1 -1
  17. package/dist/{debug-CZwmzyDx.js → debug-CaRHQrDY.js} +91 -13
  18. package/dist/debug-CaRHQrDY.js.map +1 -0
  19. package/dist/{defaults-UswXMiTx.js → defaults-a0e6IRjP.js} +1 -1
  20. package/dist/{defaults-UswXMiTx.js.map → defaults-a0e6IRjP.js.map} +1 -1
  21. package/dist/{env-api-key-Db-mNPK2.js → env-api-key-DEl3LJBv.js} +1 -1
  22. package/dist/{env-api-key-Db-mNPK2.js.map → env-api-key-DEl3LJBv.js.map} +1 -1
  23. package/dist/{environment-BRXwshJO.js → environment-knibpXF2.js} +3 -3
  24. package/dist/{environment-BRXwshJO.js.map → environment-knibpXF2.js.map} +1 -1
  25. package/dist/{file-utils-D6CWLaOm.js → file-utils-DWYX_G1o.js} +2 -2
  26. package/dist/{file-utils-D6CWLaOm.js.map → file-utils-DWYX_G1o.js.map} +1 -1
  27. package/dist/{headless-ui-DvkogF0b.js → headless-ui-C4JCi9Zz.js} +2 -2
  28. package/dist/{headless-ui-DvkogF0b.js.map → headless-ui-C4JCi9Zz.js.map} +1 -1
  29. package/dist/{host-resolution-DrOzdKdY.js → host-resolution-C18FwVrM.js} +3 -3
  30. package/dist/{host-resolution-DrOzdKdY.js.map → host-resolution-C18FwVrM.js.map} +1 -1
  31. package/dist/{interactive-CXL8oJwb.js → interactive-46qgS-UA.js} +2 -2
  32. package/dist/{interactive-CXL8oJwb.js.map → interactive-46qgS-UA.js.map} +1 -1
  33. package/dist/mcp-QWL5Obf3.js +108 -0
  34. package/dist/mcp-QWL5Obf3.js.map +1 -0
  35. package/dist/{mcp-prompt-streaming-Dumivcgi.js → mcp-prompt-streaming-BpNkFksF.js} +5 -5
  36. package/dist/mcp-prompt-streaming-BpNkFksF.js.map +1 -0
  37. package/dist/{non-interactive-BI5b25U7.js → non-interactive-3_T1d0cj.js} +2 -2
  38. package/dist/{non-interactive-BI5b25U7.js.map → non-interactive-3_T1d0cj.js.map} +1 -1
  39. package/dist/{package-json-DCuoye-H.js → package-json-CDs1ZVKu.js} +1 -1
  40. package/dist/{package-json-DCuoye-H.js.map → package-json-CDs1ZVKu.js.map} +1 -1
  41. package/dist/{package-manager-9nhQBKAy.js → package-manager-C5VVQHQ-.js} +2 -2
  42. package/dist/{package-manager-9nhQBKAy.js.map → package-manager-C5VVQHQ-.js.map} +1 -1
  43. package/dist/{playground-DDYYs2ub.js → playground-CbzUpMuN.js} +783 -7
  44. package/dist/playground-CbzUpMuN.js.map +1 -0
  45. package/dist/{posthog-BrLFkaji.js → posthog-BESrI6-T.js} +1 -1
  46. package/dist/{posthog-BrLFkaji.js.map → posthog-BESrI6-T.js.map} +1 -1
  47. package/dist/{posthog-integration-Cx4RvC7E.js → posthog-integration-6vdi4wRB.js} +19 -18
  48. package/dist/posthog-integration-6vdi4wRB.js.map +1 -0
  49. package/dist/{provisioning-PosLaOt4.js → provisioning-DK0tS98H.js} +4 -4
  50. package/dist/{provisioning-PosLaOt4.js.map → provisioning-DK0tS98H.js.map} +1 -1
  51. package/dist/{registry-B43CNAAx.js → registry-DfXVjxsP.js} +5 -5
  52. package/dist/{registry-B43CNAAx.js.map → registry-DfXVjxsP.js.map} +1 -1
  53. package/dist/security-BMVH_bjk.js +431 -0
  54. package/dist/security-BMVH_bjk.js.map +1 -0
  55. package/dist/{setup-utils-CR8X8Fmf.js → setup-utils-v9I6PQyP.js} +192 -20
  56. package/dist/setup-utils-v9I6PQyP.js.map +1 -0
  57. package/dist/{start-tui-DeewAqJg.js → start-tui-CylEL6i_.js} +63 -38
  58. package/dist/start-tui-CylEL6i_.js.map +1 -0
  59. package/dist/{steps-CPvI-2Ms.js → steps-DQdpexNM.js} +7 -7
  60. package/dist/{steps-CPvI-2Ms.js.map → steps-DQdpexNM.js.map} +1 -1
  61. package/dist/{store-0uzey9Am.js → store-Db0X2V5D.js} +68 -6
  62. package/dist/store-Db0X2V5D.js.map +1 -0
  63. package/dist/subagent-Pk9jRXRf.js +108 -0
  64. package/dist/subagent-Pk9jRXRf.js.map +1 -0
  65. package/dist/{task-stream-DkbGqxZv.js → task-stream-rC4IEUHC.js} +3 -3
  66. package/dist/{task-stream-DkbGqxZv.js.map → task-stream-rC4IEUHC.js.map} +1 -1
  67. package/dist/tasks-B46phMJu.js +112 -0
  68. package/dist/tasks-B46phMJu.js.map +1 -0
  69. package/dist/{telemetry-DJNpvkYP.js → telemetry-BJAHymMq.js} +3 -3
  70. package/dist/{telemetry-DJNpvkYP.js.map → telemetry-BJAHymMq.js.map} +1 -1
  71. package/dist/{terminal-Bd6q58zn.js → terminal-BTQSAkC-.js} +143 -793
  72. package/dist/terminal-BTQSAkC-.js.map +1 -0
  73. package/dist/tools-C_m5knXt.js +122 -0
  74. package/dist/tools-C_m5knXt.js.map +1 -0
  75. package/dist/{urls-B1a2v93j.js → urls-BCmeQMIb.js} +2 -2
  76. package/dist/{urls-B1a2v93j.js.map → urls-BCmeQMIb.js.map} +1 -1
  77. package/dist/{wizard-abort-DyAGpw-u.js → wizard-abort-CMzW4LVM.js} +1 -1
  78. package/dist/{wizard-abort-BG8VNOI9.js → wizard-abort-DkngXmxN.js} +4 -4
  79. package/dist/{wizard-abort-BG8VNOI9.js.map → wizard-abort-DkngXmxN.js.map} +1 -1
  80. package/dist/{wizard-session-CqihOtk3.js → wizard-session-BoXAZEOf.js} +4 -1
  81. package/dist/wizard-session-BoXAZEOf.js.map +1 -0
  82. package/dist/{wizard-session-xzqycI7i.js → wizard-session-C8W_6X0j.js} +1 -1
  83. package/dist/wizard-tools-C8nVWLeI.js +1162 -0
  84. package/dist/wizard-tools-C8nVWLeI.js.map +1 -0
  85. package/dist/wizard-ui-WZ48rUgr.js.map +1 -1
  86. package/package.json +7 -2
  87. package/dist/agent-interface-D8jnkEnB.js.map +0 -1
  88. package/dist/agent-runner-BxIYcxmA.js.map +0 -1
  89. package/dist/debug-CZwmzyDx.js.map +0 -1
  90. package/dist/mcp-prompt-streaming-Dumivcgi.js.map +0 -1
  91. package/dist/playground-DDYYs2ub.js.map +0 -1
  92. package/dist/posthog-integration-Cx4RvC7E.js.map +0 -1
  93. package/dist/setup-utils-CR8X8Fmf.js.map +0 -1
  94. package/dist/start-tui-DeewAqJg.js.map +0 -1
  95. package/dist/store-0uzey9Am.js.map +0 -1
  96. package/dist/terminal-Bd6q58zn.js.map +0 -1
  97. package/dist/wizard-session-CqihOtk3.js.map +0 -1
@@ -1,15 +1,16 @@
1
- import { $ as getSkillsBaseUrl, P as POSTHOG_APP_URL, g as SERVICE_LABELS, s as logToFile } from "./debug-CZwmzyDx.js";
2
- import { t as analytics } from "./analytics-Du8Z_3oX.js";
3
- import { i as withUtm, n as openTrackedLink } from "./telemetry-DJNpvkYP.js";
4
- import { t as getOrAskForProjectData } from "./setup-utils-CR8X8Fmf.js";
5
- import { t as HostResolution } from "./host-resolution-DrOzdKdY.js";
6
- import { a as fetchUserData, i as fetchSlackConnected } from "./api-BqKMQVhj.js";
7
- import "./wizard-session-CqihOtk3.js";
8
- import { A as AUDIT_SEVERITY_STYLE, x as fetchSkillMenu } from "./agent-interface-D8jnkEnB.js";
9
- import { c as TextBlock, d as isLinesBlock, f as isObjectBlock, l as computeVisibleRange, m as Icons, p as Colors, u as isClearBlock } from "./posthog-integration-Cx4RvC7E.js";
1
+ import { dt as getSkillsBaseUrl, g as SERVICE_LABELS, s as logToFile, z as POSTHOG_APP_URL } from "./debug-CaRHQrDY.js";
2
+ import { n as analytics } from "./analytics-Z_2kCTpB.js";
3
+ import { i as withUtm, n as openTrackedLink } from "./telemetry-BJAHymMq.js";
4
+ import { t as getOrAskForProjectData } from "./setup-utils-v9I6PQyP.js";
5
+ import { t as HostResolution } from "./host-resolution-C18FwVrM.js";
6
+ import { a as fetchUserData, i as fetchSlackConnected } from "./api-CwhOyprf.js";
7
+ import "./wizard-session-BoXAZEOf.js";
8
+ import { a as formatTokenCount, i as formatCostUsd } from "./agent-runner-DZhaiwD5.js";
9
+ import { g as AUDIT_SEVERITY_STYLE, i as fetchSkillMenu } from "./wizard-tools-C8nVWLeI.js";
10
+ import { c as TextBlock, d as isLinesBlock, f as isObjectBlock, l as computeVisibleRange, m as Icons, p as Colors, u as isClearBlock } from "./posthog-integration-6vdi4wRB.js";
10
11
  import { a as ConfirmButton, d as Program, i as useKeyboardHintsContext, n as useKeyBindings, o as PromptLabel, r as KeyboardHintsProvider, t as PickerMenu, v as getKindMeta } from "./bin.js";
11
- import "./store-0uzey9Am.js";
12
- import { n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-UswXMiTx.js";
12
+ import { r as totalTokenCount } from "./store-Db0X2V5D.js";
13
+ import { n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-a0e6IRjP.js";
13
14
  import opn from "opn";
14
15
  import * as fs$1 from "fs";
15
16
  import { Box, Text, measureElement, useInput, useStdout } from "ink";
@@ -787,6 +788,92 @@ const TitleBar = ({ version, width }) => {
787
788
  });
788
789
  };
789
790
  //#endregion
791
+ //#region src/ui/tui/components/TokenCostHud.tsx
792
+ /**
793
+ * TokenCostHud — Ctrl+T panel showing the running (then final) LLM
794
+ * token/cost estimate for this wizard run. Toggled from `ScreenContainer`;
795
+ * deliberately not registered via `useKeyBindings`, so the shortcut never
796
+ * shows in the `KeyboardHintsBar` in production. Defaults visible for
797
+ * local/dev/test runs (`WizardStore`'s `$tokenHudVisible` initial value),
798
+ * so contributors see it without needing to know the shortcut; defaults
799
+ * hidden in the published build. Once shown, the panel itself names the
800
+ * shortcut, so it's self-documenting either way.
801
+ *
802
+ * One row when "Ctrl+T to hide" fits on the same row as the cost line
803
+ * (right-aligned, with a minimum gap), two when the terminal is too narrow
804
+ * for both — see `tokenCostHudRowCount`, which `ScreenContainer` calls with
805
+ * the same inputs so its height budget always matches what actually renders
806
+ * (plus the blank spacer `ScreenContainer` renders below either way).
807
+ */
808
+ /** Self-documents the hidden shortcut once the panel is showing. */
809
+ const HINT_TEXT = "Ctrl+T to hide";
810
+ /** Minimum blank columns between the cost line and the hint when they
811
+ * share a row, so they never visually run together. */
812
+ const MIN_GAP = 2;
813
+ function tokenCostLineParts(usage) {
814
+ const costPart = `${usage.costIsFinal ? "Final cost" : "Cost (running)"}: ${formatCostUsd(usage.costUsd)}`;
815
+ if (totalTokenCount(usage) === 0) return {
816
+ costPart,
817
+ breakdownPart: " · no agent turns yet"
818
+ };
819
+ return {
820
+ costPart,
821
+ breakdownPart: ` · in ${formatTokenCount(usage.inputTokens)} · out ${formatTokenCount(usage.outputTokens)} · cache read ${formatTokenCount(usage.cacheReadTokens)} · cache write ${formatTokenCount(usage.cacheCreationTokens)}`
822
+ };
823
+ }
824
+ /** Single source of truth for both `tokenCostHudRowCount` (so
825
+ * `ScreenContainer`'s height budget can never disagree with what renders)
826
+ * and the component's own render — computed once per call rather than
827
+ * separately in each. */
828
+ function layoutTokenCostLine(usage, width) {
829
+ const { costPart, breakdownPart } = tokenCostLineParts(usage);
830
+ const lineLength = costPart.length + breakdownPart.length;
831
+ return {
832
+ costPart,
833
+ breakdownPart,
834
+ hintFitsInline: lineLength + MIN_GAP + 14 <= width,
835
+ gap: Math.max(MIN_GAP, width - lineLength - 14)
836
+ };
837
+ }
838
+ /**
839
+ * How many rows `TokenCostHud` renders for `usage` at `width` columns of
840
+ * available text space — 1 when the hint fits on the cost line's row, 2
841
+ * when it needs its own row below. `ScreenContainer` calls this with the
842
+ * exact same `usage`/`width` it passes to the component.
843
+ */
844
+ function tokenCostHudRowCount(usage, width) {
845
+ return layoutTokenCostLine(usage, width).hintFitsInline ? 1 : 2;
846
+ }
847
+ const TokenCostHud = ({ usage, width }) => {
848
+ const { costPart, breakdownPart, hintFitsInline, gap } = layoutTokenCostLine(usage, width);
849
+ return /* @__PURE__ */ jsxs(Box, {
850
+ flexDirection: "column",
851
+ paddingX: 1,
852
+ children: [/* @__PURE__ */ jsxs(Text, {
853
+ wrap: "truncate",
854
+ children: [
855
+ /* @__PURE__ */ jsx(Text, {
856
+ color: Colors.accent,
857
+ children: costPart
858
+ }),
859
+ /* @__PURE__ */ jsx(Text, {
860
+ dimColor: true,
861
+ children: breakdownPart
862
+ }),
863
+ hintFitsInline && /* @__PURE__ */ jsx(Text, { children: " ".repeat(gap) }),
864
+ hintFitsInline && /* @__PURE__ */ jsx(Text, {
865
+ dimColor: true,
866
+ children: HINT_TEXT
867
+ })
868
+ ]
869
+ }), !hintFitsInline && /* @__PURE__ */ jsx(Text, {
870
+ dimColor: true,
871
+ wrap: "truncate",
872
+ children: HINT_TEXT
873
+ })]
874
+ });
875
+ };
876
+ //#endregion
790
877
  //#region src/ui/tui/primitives/DissolveTransition.tsx
791
878
  /**
792
879
  * DissolveTransition — Column-sweep inspired by TTE's Sweep effect.
@@ -986,9 +1073,15 @@ function getContentWidth(terminalColumns) {
986
1073
  const ScreenContainer = ({ store, screens }) => {
987
1074
  const [columns, rows] = useStdoutDimensions();
988
1075
  useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
1076
+ useInput((input, key) => {
1077
+ if (key.ctrl && input === "t") store.toggleTokenHud();
1078
+ });
989
1079
  const terminalWidth = columns;
990
1080
  const width = getContentWidth(terminalWidth);
991
- const contentHeight = Math.max(5, rows - 3);
1081
+ const hudVisible = store.tokenHudVisible;
1082
+ const hudContentWidth = Math.max(1, width - 2);
1083
+ const hudRows = hudVisible ? tokenCostHudRowCount(store.tokenUsage, hudContentWidth) + 1 : 0;
1084
+ const contentHeight = Math.max(5, rows - 3 - hudRows);
992
1085
  const contentAreaWidth = Math.max(10, width - 2);
993
1086
  const direction = store.lastNavDirection === "pop" ? "right" : "left";
994
1087
  const activeScreen = screens[store.currentScreen] ?? null;
@@ -1008,6 +1101,10 @@ const ScreenContainer = ({ store, screens }) => {
1008
1101
  width
1009
1102
  }),
1010
1103
  /* @__PURE__ */ jsx(Box, { height: 1 }),
1104
+ hudVisible && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(TokenCostHud, {
1105
+ usage: store.tokenUsage,
1106
+ width: hudContentWidth
1107
+ }), /* @__PURE__ */ jsx(Box, { height: 1 })] }),
1011
1108
  /* @__PURE__ */ jsx(Box, {
1012
1109
  flexDirection: "column",
1013
1110
  flexGrow: 1,
@@ -1607,782 +1704,6 @@ const TipsCard = ({ store, tips = DEFAULT_TIPS }) => {
1607
1704
  });
1608
1705
  };
1609
1706
  //#endregion
1610
- //#region src/ui/tui/hooks/useTick.ts
1611
- /**
1612
- * useTick — forces a re-render every `intervalMs`. Returns a monotonically
1613
- * increasing counter, which is rarely needed — most callers just want the
1614
- * re-render side effect.
1615
- */
1616
- function useTick(intervalMs) {
1617
- const [tick, setTick] = useState(0);
1618
- useEffect(() => {
1619
- const id = setInterval(() => setTick((t) => t + 1), intervalMs);
1620
- return () => clearInterval(id);
1621
- }, [intervalMs]);
1622
- return tick;
1623
- }
1624
- //#endregion
1625
- //#region src/ui/tui/components/visualizer/palette.ts
1626
- const VISUALIZER_PALETTE = {
1627
- fade: "#0E7A0E",
1628
- bright: "#7CFF7C",
1629
- mid: "#22D622",
1630
- head: "#E6FFE6",
1631
- book: [
1632
- "#22D622",
1633
- "#7CFF7C",
1634
- "#5BE05B",
1635
- "#A0F0A0",
1636
- "#36B536"
1637
- ],
1638
- deleteRed: "#D63B22",
1639
- upGreen: "#7CFF7C"
1640
- };
1641
- //#endregion
1642
- //#region src/ui/tui/components/visualizer/panel.tsx
1643
- /**
1644
- * Shared scaffolding for the phase visuals — Matrix-green color, common
1645
- * VisualProps shape, and the rounded `Panel` shell every visual sits in.
1646
- *
1647
- * Visuals each render their own grid then wrap it in `<Panel>` so phase
1648
- * transitions stay visually continuous.
1649
- */
1650
- const MATRIX_FADE = VISUALIZER_PALETTE.fade;
1651
- const Panel = ({ children }) => /* @__PURE__ */ jsx(Box, {
1652
- flexDirection: "column",
1653
- borderStyle: "round",
1654
- borderColor: MATRIX_FADE,
1655
- children
1656
- });
1657
- //#endregion
1658
- //#region src/ui/tui/components/visualizer/MatrixRain.tsx
1659
- /**
1660
- * MatrixRain — code-rain visual used for the codebase-scan phase.
1661
- *
1662
- * Independent of the phase orchestrator so it can be reused elsewhere
1663
- * (e.g. standalone in a demo). `bordered` toggles the rounded green frame.
1664
- */
1665
- const { head: MATRIX_HEAD, bright: MATRIX_BRIGHT, mid: MATRIX_MID } = VISUALIZER_PALETTE;
1666
- const DEFAULT_TICK_MS = 110;
1667
- const DEFAULT_MAX_TAIL = 7;
1668
- const RAIN_GLYPHS = "ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン0123456789Z<>=*+:.";
1669
- function pickGlyph() {
1670
- return RAIN_GLYPHS[Math.floor(Math.random() * 73)];
1671
- }
1672
- function makeRainColumn(height, maxTail) {
1673
- return {
1674
- headY: -Math.random() * height,
1675
- speed: .3 + Math.random() * .9,
1676
- tail: 3 + Math.floor(Math.random() * (maxTail - 2)),
1677
- glyphs: /* @__PURE__ */ new Map(),
1678
- dormant: Math.floor(Math.random() * 18)
1679
- };
1680
- }
1681
- function tickRainColumn(col, height, maxTail) {
1682
- if (col.dormant > 0) return {
1683
- ...col,
1684
- dormant: col.dormant - 1
1685
- };
1686
- const next = col.headY + col.speed;
1687
- if (next > height + col.tail) return makeRainColumn(height, maxTail);
1688
- const glyphs = new Map(col.glyphs);
1689
- for (let y = Math.max(0, Math.ceil(col.headY)); y <= Math.min(Math.floor(next), height - 1); y++) glyphs.set(y, pickGlyph());
1690
- if (glyphs.size > 0 && Math.random() < .22) {
1691
- const keys = [...glyphs.keys()];
1692
- const k = keys[Math.floor(Math.random() * keys.length)];
1693
- glyphs.set(k, pickGlyph());
1694
- }
1695
- return {
1696
- ...col,
1697
- headY: next,
1698
- glyphs
1699
- };
1700
- }
1701
- const MatrixRain = ({ width, height, tickMs = DEFAULT_TICK_MS, maxTail = DEFAULT_MAX_TAIL, bordered = true }) => {
1702
- const columnsRef = useRef(Array.from({ length: width }, () => makeRainColumn(height, maxTail)));
1703
- const [, setTick] = useState(0);
1704
- useEffect(() => {
1705
- const interval = setInterval(() => {
1706
- columnsRef.current = columnsRef.current.map((c) => tickRainColumn(c, height, maxTail));
1707
- setTick((t) => t + 1);
1708
- }, tickMs);
1709
- return () => clearInterval(interval);
1710
- }, [
1711
- height,
1712
- maxTail,
1713
- tickMs
1714
- ]);
1715
- const columns = columnsRef.current;
1716
- const body = Array.from({ length: height }).map((_, y) => /* @__PURE__ */ jsx(Box, { children: columns.map((col, x) => {
1717
- const glyph = col.glyphs.get(y);
1718
- if (!glyph) return /* @__PURE__ */ jsx(Text, { children: " " }, x);
1719
- const dist = col.headY - y;
1720
- if (dist < 0 || dist > col.tail) return /* @__PURE__ */ jsx(Text, { children: " " }, x);
1721
- if (dist < 1) return /* @__PURE__ */ jsx(Text, {
1722
- bold: true,
1723
- color: MATRIX_HEAD,
1724
- children: glyph
1725
- }, x);
1726
- if (dist < 2) return /* @__PURE__ */ jsx(Text, {
1727
- color: MATRIX_BRIGHT,
1728
- children: glyph
1729
- }, x);
1730
- if (dist < col.tail * .55) return /* @__PURE__ */ jsx(Text, {
1731
- color: MATRIX_MID,
1732
- children: glyph
1733
- }, x);
1734
- return /* @__PURE__ */ jsx(Text, {
1735
- color: MATRIX_FADE,
1736
- dimColor: true,
1737
- children: glyph
1738
- }, x);
1739
- }) }, y));
1740
- if (!bordered) return /* @__PURE__ */ jsx(Box, {
1741
- flexDirection: "column",
1742
- children: body
1743
- });
1744
- return /* @__PURE__ */ jsx(Box, {
1745
- flexDirection: "column",
1746
- borderStyle: "round",
1747
- borderColor: MATRIX_FADE,
1748
- children: body
1749
- });
1750
- };
1751
- //#endregion
1752
- //#region src/ui/tui/components/visualizer/LibraryShelf.tsx
1753
- /**
1754
- * LibraryShelf — skill-selection phase.
1755
- *
1756
- * A row of book spines. One spine peels forward each cycle, the chosen
1757
- * title hovers next to it, then it slides home and the next one is picked.
1758
- */
1759
- const BOOK_LABELS = [
1760
- "nx",
1761
- "rt",
1762
- "sv",
1763
- "fl",
1764
- "jg",
1765
- "rb",
1766
- "go",
1767
- "dj",
1768
- "fa",
1769
- "lv",
1770
- "ts",
1771
- "py"
1772
- ];
1773
- const BOOK_COLORS = VISUALIZER_PALETTE.book;
1774
- const LibraryShelf = ({ width, height }) => {
1775
- const tick = useTick(380);
1776
- const bookCount = Math.min(Math.floor((width - 2) / 2), BOOK_LABELS.length);
1777
- const cyclePos = tick % (bookCount * 4);
1778
- const selectedIdx = Math.floor(cyclePos / 4);
1779
- const phase = cyclePos % 4;
1780
- const offset = phase === 0 ? 0 : phase === 1 ? 1 : phase === 2 ? 2 : 1;
1781
- const wobble = phase === 2 && tick % 2 === 0 ? 1 : 0;
1782
- const shelfY = Math.floor(height / 2) - 1;
1783
- const rows = Array.from({ length: height }, () => new Array(width).fill(" "));
1784
- for (let i = 0; i < bookCount; i++) {
1785
- const x = 1 + i * 2;
1786
- const isSelected = i === selectedIdx;
1787
- const shift = isSelected ? offset : 0;
1788
- const wob = isSelected && wobble ? -1 : 0;
1789
- if (x + shift >= width) continue;
1790
- rows[shelfY - 1][x + shift] = "█";
1791
- rows[shelfY][x + shift] = BOOK_LABELS[i][0];
1792
- rows[shelfY + 1][x + shift] = BOOK_LABELS[i][1];
1793
- rows[shelfY + 2 + wob]?.[x + shift] !== void 0 && (rows[shelfY + 2 + wob][x + shift] = "█");
1794
- }
1795
- const boardY = shelfY + 3;
1796
- if (boardY < height) for (let x = 0; x < width; x++) rows[boardY][x] = "─";
1797
- if (phase === 2) {
1798
- const labelStartX = 1 + selectedIdx * 2 + 4;
1799
- const labelText = BOOK_LABELS[selectedIdx] + "-app";
1800
- for (let c = 0; c < labelText.length && labelStartX + c < width; c++) rows[shelfY][labelStartX + c] = labelText[c];
1801
- if (labelStartX - 1 < width && labelStartX - 1 >= 0) rows[shelfY][labelStartX - 1] = "▶";
1802
- }
1803
- return /* @__PURE__ */ jsx(Panel, { children: rows.map((row, y) => /* @__PURE__ */ jsx(Box, { children: row.map((ch, x) => {
1804
- if (ch === " ") return /* @__PURE__ */ jsx(Text, { children: " " }, x);
1805
- if (ch === "─") return /* @__PURE__ */ jsx(Text, {
1806
- color: MATRIX_FADE,
1807
- dimColor: true,
1808
- children: "─"
1809
- }, x);
1810
- if (ch === "▶") return /* @__PURE__ */ jsx(Text, {
1811
- bold: true,
1812
- color: VISUALIZER_PALETTE.head,
1813
- children: "▶"
1814
- }, x);
1815
- const booksColor = BOOK_COLORS[(Math.floor((x - 1) / 2) + 17 * y) % BOOK_COLORS.length];
1816
- const isSel = x === 1 + selectedIdx * 2 + offset;
1817
- return /* @__PURE__ */ jsx(Text, {
1818
- bold: isSel,
1819
- color: isSel ? VISUALIZER_PALETTE.head : booksColor,
1820
- children: ch
1821
- }, x);
1822
- }) }, y)) });
1823
- };
1824
- //#endregion
1825
- //#region src/ui/tui/components/visualizer/CrateStack.tsx
1826
- /**
1827
- * CrateStack — dependency-install phase.
1828
- *
1829
- * Boxes drop from the top and stack at the bottom. Each new arrival lands
1830
- * with a tiny shake.
1831
- */
1832
- const PACKAGE_NAMES = [
1833
- "posthog-js",
1834
- "posthog-py",
1835
- "posthog-rb",
1836
- "posthog-go",
1837
- "posthog-node",
1838
- "react-ph",
1839
- "next-ph",
1840
- "svelte-ph",
1841
- "ph-mcp",
1842
- "ph-ai"
1843
- ];
1844
- const CRATE_W = Math.max(...PACKAGE_NAMES.map((n) => n.length)) + 2;
1845
- const CrateStack = ({ width, height }) => {
1846
- const tick = useTick(95);
1847
- const state = useRef({
1848
- stack: [],
1849
- falling: null,
1850
- spawnCooldown: 0
1851
- }).current;
1852
- const crateW = CRATE_W;
1853
- const crateH = 1;
1854
- const floorY = height - 1;
1855
- if (state.falling) {
1856
- state.falling.y += 1;
1857
- const collisionStackHeight = state.stack.filter((c) => Math.abs(c.x - state.falling.x) < crateW - 1).length * crateH;
1858
- if (state.falling.y >= floorY - collisionStackHeight) {
1859
- state.stack.push({
1860
- label: state.falling.label,
1861
- x: state.falling.x,
1862
- landedAt: tick
1863
- });
1864
- state.falling = null;
1865
- state.spawnCooldown = 3;
1866
- }
1867
- } else if (state.spawnCooldown > 0) state.spawnCooldown -= 1;
1868
- else if (state.stack.length < Math.floor(height / crateH) - 1) state.falling = {
1869
- label: PACKAGE_NAMES[Math.floor(Math.random() * PACKAGE_NAMES.length)],
1870
- x: 2 + Math.floor(Math.random() * Math.max(1, width - crateW - 4)),
1871
- y: -1
1872
- };
1873
- else if (tick % 20 === 0) state.stack = [];
1874
- const grid = Array.from({ length: height }, () => new Array(width).fill(" "));
1875
- const drawCrate = (cx, cy, label, shake) => {
1876
- const x = cx + shake;
1877
- if (cy < 0 || cy >= height) return;
1878
- const padded = `[${label.slice(0, crateW - 2)}]`.padEnd(crateW, " ");
1879
- for (let i = 0; i < crateW && x + i < width; i++) if (x + i >= 0) grid[cy][x + i] = padded[i];
1880
- };
1881
- state.stack.forEach((c, idx) => {
1882
- const cy = floorY - idx;
1883
- const shake = tick - c.landedAt === 0 ? 1 : tick - c.landedAt === 1 ? -1 : 0;
1884
- drawCrate(c.x, cy, c.label, shake);
1885
- });
1886
- if (state.falling) drawCrate(state.falling.x, state.falling.y, state.falling.label, 0);
1887
- return /* @__PURE__ */ jsx(Panel, { children: grid.map((row, y) => /* @__PURE__ */ jsx(Box, { children: row.map((ch, x) => {
1888
- if (ch === " ") return /* @__PURE__ */ jsx(Text, { children: " " }, x);
1889
- const isFalling = state.falling && y === state.falling.y && Math.abs(x - state.falling.x) < crateW;
1890
- return /* @__PURE__ */ jsx(Text, {
1891
- bold: isFalling,
1892
- color: isFalling ? VISUALIZER_PALETTE.head : VISUALIZER_PALETTE.mid,
1893
- children: ch
1894
- }, x);
1895
- }) }, y)) });
1896
- };
1897
- //#endregion
1898
- //#region src/ui/tui/components/visualizer/DiffCascade.tsx
1899
- /**
1900
- * DiffCascade — code-edits phase.
1901
- *
1902
- * + and - code lines scroll upward continuously. Occasional comment-rewrite
1903
- * gag: a "// hmm…" line appears, gets - struck out, then a + replaces it.
1904
- */
1905
- const CODE_SNIPPETS = [
1906
- "import posthog from 'posthog-js'",
1907
- "posthog.init(KEY, { host: HOST })",
1908
- "<PostHogProvider client={posthog}>",
1909
- " {children}",
1910
- "</PostHogProvider>",
1911
- "posthog.capture('page_viewed')",
1912
- "posthog.capture('signup_started')",
1913
- "posthog.identify(user.id, { email })",
1914
- "if (process.env.NODE_ENV !== 'test') posthog.init(KEY)",
1915
- "export const posthog = new PostHog(KEY)",
1916
- "// TODO: enable replay",
1917
- "window.posthog = posthog"
1918
- ];
1919
- const WHIMSY_COMMENTS = [
1920
- "// hmm — that should be a hook",
1921
- "// wait, refactor incoming",
1922
- "// posthog says hi"
1923
- ];
1924
- const DiffCascade = ({ width, height }) => {
1925
- const tick = useTick(280);
1926
- const linesRef = useRef([]);
1927
- if (linesRef.current.length === 0) for (let i = 0; i < height; i++) linesRef.current.push({
1928
- sign: Math.random() < .75 ? "+" : "-",
1929
- text: CODE_SNIPPETS[Math.floor(Math.random() * CODE_SNIPPETS.length)]
1930
- });
1931
- linesRef.current.shift();
1932
- const whimsy = tick % 23 === 0;
1933
- linesRef.current.push({
1934
- sign: whimsy ? "-" : Math.random() < .78 ? "+" : "-",
1935
- text: whimsy ? WHIMSY_COMMENTS[Math.floor(Math.random() * WHIMSY_COMMENTS.length)] : CODE_SNIPPETS[Math.floor(Math.random() * CODE_SNIPPETS.length)]
1936
- });
1937
- const lines = linesRef.current;
1938
- return /* @__PURE__ */ jsx(Panel, { children: Array.from({ length: height }).map((_, y) => {
1939
- const line = lines[y];
1940
- const cap = width - 2;
1941
- const text = `${line.sign} ${line.text}`.slice(0, cap);
1942
- return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, {
1943
- color: line.sign === "+" ? VISUALIZER_PALETTE.mid : line.sign === "-" ? VISUALIZER_PALETTE.deleteRed : MATRIX_FADE,
1944
- children: text.padEnd(cap, " ")
1945
- }) }, y);
1946
- }) });
1947
- };
1948
- //#endregion
1949
- //#region src/ui/tui/components/visualizer/Tumblers.tsx
1950
- /**
1951
- * Tumblers — env-setup phase.
1952
- *
1953
- * Pins fall into a lock cylinder one by one; when all six align the bolt
1954
- * pulses green for a beat, then the cycle restarts.
1955
- */
1956
- const Tumblers = ({ width, height }) => {
1957
- const tick = useTick(80);
1958
- const pinCount = Math.min(Math.floor((width - 2) / 2), 6);
1959
- const state = useRef({
1960
- heights: new Array(pinCount).fill(0),
1961
- current: 0,
1962
- fallY: 0,
1963
- pulse: 0
1964
- }).current;
1965
- const cylinderTop = 1;
1966
- const cylinderBottom = height - 2;
1967
- const targetForPin = (i) => cylinderBottom - 1 - i % 3 - Math.floor(i / 2);
1968
- if (state.pulse > 0) {
1969
- state.pulse -= 1;
1970
- if (state.pulse === 0) {
1971
- state.heights = new Array(pinCount).fill(0);
1972
- state.current = 0;
1973
- state.fallY = 0;
1974
- }
1975
- } else if (state.current < pinCount) {
1976
- state.fallY += 1;
1977
- if (state.fallY >= targetForPin(state.current)) {
1978
- state.heights[state.current] = targetForPin(state.current);
1979
- state.current += 1;
1980
- state.fallY = cylinderTop;
1981
- }
1982
- } else state.pulse = 14;
1983
- const grid = Array.from({ length: height }, () => new Array(width).fill(" "));
1984
- for (let y = 0; y < height; y++) {
1985
- grid[y][0] = "│";
1986
- grid[y][width - 1] = "│";
1987
- }
1988
- for (let i = 0; i < pinCount; i++) {
1989
- const x = 1 + i * 2 + 1;
1990
- if (x < width) grid[0][x] = "▼";
1991
- }
1992
- for (let x = 1; x < width - 1; x++) grid[height - 1][x] = "─";
1993
- for (let i = 0; i < pinCount; i++) {
1994
- const pinX = 1 + i * 2 + 1;
1995
- if (pinX >= width) continue;
1996
- const top = state.heights[i] || cylinderTop;
1997
- for (let y = top; y <= cylinderBottom; y++) grid[y][pinX] = "█";
1998
- }
1999
- if (state.pulse === 0 && state.current < pinCount) {
2000
- const pinX = 1 + state.current * 2 + 1;
2001
- if (pinX < width) grid[state.fallY][pinX] = "█";
2002
- }
2003
- const pulsing = state.pulse > 0;
2004
- const pulseBright = pulsing && tick % 2 === 0;
2005
- return /* @__PURE__ */ jsx(Panel, { children: grid.map((row, y) => /* @__PURE__ */ jsx(Box, { children: row.map((ch, x) => {
2006
- if (ch === " ") return /* @__PURE__ */ jsx(Text, { children: " " }, x);
2007
- if (ch === "│" || ch === "─" || ch === "▼") return /* @__PURE__ */ jsx(Text, {
2008
- color: pulsing ? pulseBright ? VISUALIZER_PALETTE.bright : MATRIX_FADE : MATRIX_FADE,
2009
- dimColor: !pulsing,
2010
- children: ch
2011
- }, x);
2012
- const isFalling = state.pulse === 0 && state.current < pinCount && y === state.fallY && x === 1 + state.current * 2 + 1;
2013
- const color = pulsing ? pulseBright ? VISUALIZER_PALETTE.head : VISUALIZER_PALETTE.bright : isFalling ? VISUALIZER_PALETTE.head : VISUALIZER_PALETTE.mid;
2014
- return /* @__PURE__ */ jsx(Text, {
2015
- bold: pulsing || isFalling,
2016
- color,
2017
- children: ch
2018
- }, x);
2019
- }) }, y)) });
2020
- };
2021
- //#endregion
2022
- //#region src/ui/tui/components/visualizer/DashboardGrid.tsx
2023
- /**
2024
- * DashboardGrid — dashboards phase.
2025
- *
2026
- * 2×2 grid of mini-charts to evoke a real PostHog dashboard. When the area
2027
- * is too small, the layout collapses to 1×2 or 1×1 so something always
2028
- * renders.
2029
- */
2030
- const DASHBOARD_TILES = [
2031
- {
2032
- title: "Visitors",
2033
- kind: "bars"
2034
- },
2035
- {
2036
- title: "Sessions",
2037
- kind: "line"
2038
- },
2039
- {
2040
- title: "Revenue",
2041
- kind: "gauge"
2042
- },
2043
- {
2044
- title: "Errors",
2045
- kind: "pulse"
2046
- }
2047
- ];
2048
- const SPARK_GLYPHS = "▁▂▃▄▅▆▇█";
2049
- const DashboardGrid = ({ width, height }) => {
2050
- const tick = useTick(220);
2051
- const grid = Array.from({ length: height }, () => new Array(width).fill(" "));
2052
- const cols = width >= 22 ? 2 : 1;
2053
- const rows = height >= 7 ? 2 : 1;
2054
- const vSplit = cols === 2 ? Math.floor(width / 2) : -1;
2055
- const hSplit = rows === 2 ? Math.floor(height / 2) : -1;
2056
- if (vSplit >= 0) for (let y = 0; y < height; y++) grid[y][vSplit] = "│";
2057
- if (hSplit >= 0) {
2058
- for (let x = 0; x < width; x++) grid[hSplit][x] = "─";
2059
- if (vSplit >= 0) grid[hSplit][vSplit] = "┼";
2060
- }
2061
- const writeText = (x0, y0, maxW, text) => {
2062
- if (y0 < 0 || y0 >= height) return;
2063
- const slice = text.slice(0, Math.max(0, maxW));
2064
- for (let i = 0; i < slice.length; i++) if (x0 + i >= 0 && x0 + i < width) grid[y0][x0 + i] = slice[i];
2065
- };
2066
- for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
2067
- const tile = DASHBOARD_TILES[r * cols + c];
2068
- const tileX = c === 0 ? 0 : vSplit + 1;
2069
- const tileY = r === 0 ? 0 : hSplit + 1;
2070
- const tileW = cols === 2 ? c === 0 ? vSplit : width - vSplit - 1 : width;
2071
- const tileH = rows === 2 ? r === 0 ? hSplit : height - hSplit - 1 : height;
2072
- const innerX = tileX;
2073
- const innerW = Math.max(1, tileW);
2074
- const titleY = tileY;
2075
- const valueY = tileY + tileH - 1;
2076
- const chartY0 = tileY + 1;
2077
- const chartY1 = Math.max(chartY0, valueY - 1);
2078
- const chartH = Math.max(1, chartY1 - chartY0 + 1);
2079
- const seed = (r * cols + c) * 13;
2080
- writeText(innerX, titleY, innerW, tile.title);
2081
- renderTile(grid, tile.kind, innerX, chartY0, innerW, chartH, tick, seed, width);
2082
- writeText(innerX, valueY, innerW, tileValue(tile.kind, tick, seed));
2083
- }
2084
- return /* @__PURE__ */ jsx(Panel, { children: grid.map((row, y) => /* @__PURE__ */ jsx(Box, { children: row.map((ch, x) => {
2085
- if (ch === " ") return /* @__PURE__ */ jsx(Text, { children: " " }, x);
2086
- if (ch === "│" || ch === "─" || ch === "┼") return /* @__PURE__ */ jsx(Text, {
2087
- color: MATRIX_FADE,
2088
- dimColor: true,
2089
- children: ch
2090
- }, x);
2091
- if (SPARK_GLYPHS.includes(ch) || ch === "█") return /* @__PURE__ */ jsx(Text, {
2092
- color: VISUALIZER_PALETTE.mid,
2093
- children: ch
2094
- }, x);
2095
- if (ch === "●" || ch === "∙" || ch === "·") {
2096
- const color = ch === "●" ? VISUALIZER_PALETTE.head : ch === "∙" ? VISUALIZER_PALETTE.bright : VISUALIZER_PALETTE.mid;
2097
- return /* @__PURE__ */ jsx(Text, {
2098
- bold: ch === "●",
2099
- color,
2100
- children: ch
2101
- }, x);
2102
- }
2103
- if (ch === "▲" || ch === "▼") return /* @__PURE__ */ jsx(Text, {
2104
- bold: true,
2105
- color: ch === "▲" ? VISUALIZER_PALETTE.upGreen : VISUALIZER_PALETTE.deleteRed,
2106
- children: ch
2107
- }, x);
2108
- return /* @__PURE__ */ jsx(Text, {
2109
- color: VISUALIZER_PALETTE.bright,
2110
- children: ch
2111
- }, x);
2112
- }) }, y)) });
2113
- };
2114
- function renderTile(grid, kind, x0, y0, w, h, tick, seed, gridW) {
2115
- const set = (x, y, ch) => {
2116
- if (y < 0 || y >= grid.length) return;
2117
- if (x < 0 || x >= gridW) return;
2118
- grid[y][x] = ch;
2119
- };
2120
- if (kind === "bars") for (let i = 0; i < w; i++) {
2121
- const t = tick * .18 + (i + seed) * .6;
2122
- const level = .55 + .35 * Math.sin(t) + .1 * Math.sin(t * 2.7);
2123
- const filled = Math.max(0, Math.min(h, Math.round(level * h)));
2124
- for (let j = 0; j < filled; j++) set(x0 + i, y0 + h - 1 - j, "█");
2125
- if (filled > 0 && filled < h) {
2126
- const frac = level * h - Math.floor(level * h);
2127
- const glyph = SPARK_GLYPHS[Math.floor(frac * 7)];
2128
- set(x0 + i, y0 + h - filled, glyph);
2129
- }
2130
- }
2131
- else if (kind === "line") {
2132
- const spikeAt = ((tick / 12 | 0) + seed) % Math.max(1, w);
2133
- for (let i = 0; i < w; i++) {
2134
- const norm = i / Math.max(1, w - 1);
2135
- const base = h - 1 - norm * (h - 1);
2136
- let yf = base + .6 * Math.sin(i * .7 + seed + tick * .05);
2137
- if (i === spikeAt) yf = Math.max(0, base - 1.5);
2138
- const y = Math.max(0, Math.min(h - 1, Math.round(yf)));
2139
- const dist = Math.abs(i - (w - 1));
2140
- const ch = dist === 0 ? "●" : dist < 3 ? "∙" : "·";
2141
- set(x0 + i, y0 + y, ch);
2142
- }
2143
- } else if (kind === "gauge") {
2144
- const cycle = (tick + seed) % 40;
2145
- const pct = cycle < 30 ? cycle / 30 : 1 - (cycle - 30) / 10;
2146
- const midY = y0 + Math.floor(h / 2);
2147
- const filled = Math.max(0, Math.min(w, Math.round(pct * w)));
2148
- for (let i = 0; i < w; i++) set(x0 + i, midY, i < filled ? "█" : "·");
2149
- } else if (kind === "pulse") for (let i = 0; i < w; i++) {
2150
- const t = (i + seed + Math.floor(tick / 3)) % 17;
2151
- let level;
2152
- if (t === 0) level = .9;
2153
- else if (t === 1 || t === 16) level = .5;
2154
- else level = .12 + .06 * Math.sin((i + tick) * .5);
2155
- const filled = Math.max(1, Math.min(h, Math.round(level * h)));
2156
- for (let j = 0; j < filled; j++) set(x0 + i, y0 + h - 1 - j, "█");
2157
- }
2158
- }
2159
- function tileValue(kind, tick, seed) {
2160
- const wave = .5 + .5 * Math.sin(tick * .05 + seed);
2161
- if (kind === "bars") return `${(Math.round(8e3 + wave * 6e3) / 1e3).toFixed(1)}k ▲`;
2162
- if (kind === "line") return `${(2 + wave * 4).toFixed(1)}% ▲`;
2163
- if (kind === "gauge") return `$${Math.round(20 + wave * 60)}k`;
2164
- return `${Math.round(1 + wave * 5)} ▼`;
2165
- }
2166
- //#endregion
2167
- //#region src/ui/tui/components/PhaseVisuals.tsx
2168
- /**
2169
- * Phase visuals — ambient ASCII visualizations of what the agent is currently
2170
- * doing. The active phase is derived from the in-progress task labels (best-
2171
- * effort heuristic). Each phase has its own component (under ./visualizer);
2172
- * the orchestrator `PhaseVisual` picks which one to render.
2173
- *
2174
- * Width and height are passed in from the parent so visuals adapt to the
2175
- * column they're placed in.
2176
- */
2177
- const PHASE_LABELS = {
2178
- ["codebase-scan"]: "Reading the Code",
2179
- ["skill-install"]: "Picking the Right Skill",
2180
- ["dep-install"]: "Installing Packages",
2181
- ["code-edits"]: "Editing Source",
2182
- ["env-setup"]: "Wiring Up Secrets",
2183
- ["dashboards"]: "Building Dashboards"
2184
- };
2185
- /** Reads the active phase from the store. The agent loop pushes it in via
2186
- * `getUI().setStage(...)` whenever a new tool fires. */
2187
- function useAgentPhase(store) {
2188
- useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
2189
- return store.currentStage?.stage ?? "codebase-scan";
2190
- }
2191
- /**
2192
- * VisualizerTab — Winamp-style fullscreen take on the phase visual.
2193
- * "NOW PLAYING" header, centered visual, transport bar with elapsed time.
2194
- *
2195
- * Sizes itself off the *measured* container rather than the raw terminal so
2196
- * the tab bar / hints / status panel above don't get pushed off-screen on
2197
- * short terminals. When height runs out, chrome rows drop in order:
2198
- * transport bar first, then track title, then the NOW PLAYING header.
2199
- */
2200
- const VisualizerTab = ({ store }) => {
2201
- const phase = useAgentPhase(store);
2202
- const containerRef = useRef(null);
2203
- const [size, setSize] = useState({
2204
- width: 0,
2205
- height: 0
2206
- });
2207
- useEffect(() => {
2208
- if (!containerRef.current) return;
2209
- const m = measureElement(containerRef.current);
2210
- if (m.width !== size.width || m.height !== size.height) setSize({
2211
- width: m.width,
2212
- height: m.height
2213
- });
2214
- }, [
2215
- useTick(EQ_TICK_MS),
2216
- size.width,
2217
- size.height
2218
- ]);
2219
- const HEADER_ROWS = 2;
2220
- const TITLE_ROWS = 2;
2221
- const TRANSPORT_ROWS = 2;
2222
- const BORDER_ROWS = 2;
2223
- const MIN_VISUAL_H = 5;
2224
- const availH = size.height;
2225
- const availW = size.width;
2226
- let chromeBudget = BORDER_ROWS + HEADER_ROWS + TITLE_ROWS + TRANSPORT_ROWS;
2227
- let showHeader = true;
2228
- let showTitle = true;
2229
- let showTransport = true;
2230
- if (availH > 0 && availH - chromeBudget < MIN_VISUAL_H) {
2231
- showTransport = false;
2232
- chromeBudget -= TRANSPORT_ROWS;
2233
- }
2234
- if (availH > 0 && availH - chromeBudget < MIN_VISUAL_H) {
2235
- showTitle = false;
2236
- chromeBudget -= TITLE_ROWS;
2237
- }
2238
- if (availH > 0 && availH - chromeBudget < MIN_VISUAL_H) {
2239
- showHeader = false;
2240
- chromeBudget -= HEADER_ROWS;
2241
- }
2242
- const visualH = availH > 0 ? Math.max(MIN_VISUAL_H, Math.min(18, availH - chromeBudget)) : MIN_VISUAL_H;
2243
- const visualW = availW > 0 ? Math.max(20, Math.min(64, availW - 12)) : 40;
2244
- const now = Date.now();
2245
- const beatTimeMs = now - (store.currentStage?.startedAt ?? now);
2246
- const timeStr = formatElapsed(Math.max(0, Math.floor(beatTimeMs / 1e3)));
2247
- const equalizer = renderMiniEqualizer(beatTimeMs, useRef(new Array(EQ_BARS).fill(0)).current);
2248
- return /* @__PURE__ */ jsxs(Box, {
2249
- ref: containerRef,
2250
- flexDirection: "column",
2251
- flexGrow: 1,
2252
- alignItems: "center",
2253
- justifyContent: "center",
2254
- overflow: "hidden",
2255
- children: [
2256
- showHeader && /* @__PURE__ */ jsxs(Box, {
2257
- flexDirection: "row",
2258
- marginBottom: 1,
2259
- children: [
2260
- /* @__PURE__ */ jsx(Text, {
2261
- color: MATRIX_FADE,
2262
- children: "┌─"
2263
- }),
2264
- /* @__PURE__ */ jsx(Text, {
2265
- bold: true,
2266
- color: VISUALIZER_PALETTE.bright,
2267
- children: " ► NOW PLAYING "
2268
- }),
2269
- /* @__PURE__ */ jsx(Text, {
2270
- color: MATRIX_FADE,
2271
- children: "─┐"
2272
- })
2273
- ]
2274
- }),
2275
- showTitle && /* @__PURE__ */ jsx(Box, {
2276
- marginBottom: 1,
2277
- children: /* @__PURE__ */ jsxs(Text, {
2278
- bold: true,
2279
- color: VISUALIZER_PALETTE.head,
2280
- children: [PHASE_LABELS[phase], " - PostHog"]
2281
- })
2282
- }),
2283
- /* @__PURE__ */ jsx(PhaseBody, {
2284
- phase,
2285
- width: visualW,
2286
- height: visualH
2287
- }),
2288
- showTransport && /* @__PURE__ */ jsxs(Box, {
2289
- flexDirection: "row",
2290
- marginTop: 1,
2291
- gap: 2,
2292
- children: [
2293
- /* @__PURE__ */ jsxs(Text, {
2294
- color: VISUALIZER_PALETTE.mid,
2295
- children: [
2296
- "[",
2297
- timeStr,
2298
- "]"
2299
- ]
2300
- }),
2301
- /* @__PURE__ */ jsx(Text, {
2302
- color: MATRIX_FADE,
2303
- children: equalizer
2304
- }),
2305
- /* @__PURE__ */ jsx(Text, {
2306
- color: VISUALIZER_PALETTE.mid,
2307
- children: "WizardAmp"
2308
- })
2309
- ]
2310
- })
2311
- ]
2312
- });
2313
- };
2314
- function formatElapsed(totalSec) {
2315
- const m = Math.floor(totalSec / 60);
2316
- const s = totalSec % 60;
2317
- return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
2318
- }
2319
- const BEAT_MS = 60 / 120 * 1e3;
2320
- const EQ_BARS = 12;
2321
- const EQ_TICK_MS = 67;
2322
- const EQ_FALL_PER_FRAME = .08;
2323
- function pulse(t, periodMs, decay, phaseMs = 0) {
2324
- const since = (t - phaseMs + periodMs * 100) % periodMs;
2325
- return Math.exp(-decay * since / periodMs);
2326
- }
2327
- const EQ_GLYPHS = "▁▂▃▄▅▆▇█";
2328
- function renderMiniEqualizer(beatTimeMs, levels) {
2329
- const kick = pulse(beatTimeMs, BEAT_MS, 4);
2330
- const bass = pulse(beatTimeMs, BEAT_MS, 1.5);
2331
- const clap = pulse(beatTimeMs, BEAT_MS * 2, 5, BEAT_MS);
2332
- const hat8 = pulse(beatTimeMs, BEAT_MS / 2, 8);
2333
- const hat16 = pulse(beatTimeMs, BEAT_MS / 4, 12);
2334
- const pad = .2 + .15 * Math.sin(2 * Math.PI * beatTimeMs / 4e3);
2335
- const mix = [
2336
- bass * .7 + kick * .5 + pad,
2337
- bass * .6 + kick * .6 + pad * .9,
2338
- bass * .4 + kick * .7 + pad * .8,
2339
- kick * .6 + clap * .3 + pad * .7,
2340
- kick * .4 + clap * .5 + pad * .7,
2341
- clap * .7 + hat8 * .3 + pad * .6,
2342
- clap * .6 + hat8 * .5 + pad * .5,
2343
- hat8 * .7 + clap * .3 + pad * .5,
2344
- hat8 * .5 + hat16 * .4 + pad * .4,
2345
- hat8 * .4 + hat16 * .5 + pad * .4,
2346
- hat16 * .6 + hat8 * .3 + pad * .3,
2347
- hat16 * .7 + pad * .3
2348
- ];
2349
- let out = "";
2350
- for (let i = 0; i < EQ_BARS; i++) {
2351
- const target = Math.min(1, mix[i]);
2352
- levels[i] = Math.max(target, levels[i] - EQ_FALL_PER_FRAME);
2353
- out += EQ_GLYPHS[Math.floor(levels[i] * 7)];
2354
- }
2355
- return out;
2356
- }
2357
- const PhaseBody = ({ phase, width, height }) => {
2358
- switch (phase) {
2359
- case "codebase-scan": return /* @__PURE__ */ jsx(MatrixRain, {
2360
- width,
2361
- height
2362
- });
2363
- case "skill-install": return /* @__PURE__ */ jsx(LibraryShelf, {
2364
- width,
2365
- height
2366
- });
2367
- case "dep-install": return /* @__PURE__ */ jsx(CrateStack, {
2368
- width,
2369
- height
2370
- });
2371
- case "code-edits": return /* @__PURE__ */ jsx(DiffCascade, {
2372
- width,
2373
- height
2374
- });
2375
- case "env-setup": return /* @__PURE__ */ jsx(Tumblers, {
2376
- width,
2377
- height
2378
- });
2379
- case "dashboards": return /* @__PURE__ */ jsx(DashboardGrid, {
2380
- width,
2381
- height
2382
- });
2383
- }
2384
- };
2385
- //#endregion
2386
1707
  //#region src/ui/tui/components/ServiceHealthList.tsx
2387
1708
  /**
2388
1709
  * ServiceHealthList — Shared component for displaying service health status.
@@ -5597,6 +4918,37 @@ const SlackConnectScreen = ({ store }) => {
5597
4918
  });
5598
4919
  };
5599
4920
  //#endregion
4921
+ //#region src/ui/tui/hooks/useDismissOnAnyKey.ts
4922
+ /**
4923
+ * useDismissOnAnyKey — "press any key to continue/exit" screens.
4924
+ *
4925
+ * Wraps `useInput` for the recurring "any key dismisses/exits this screen"
4926
+ * pattern (outro screens, terminal error/timeout screens, etc.), ignoring
4927
+ * modifier-combo keypresses (Ctrl/Meta) so a global hidden shortcut — e.g.
4928
+ * Ctrl+T toggling the token/cost HUD — can't also dismiss the screen
4929
+ * underneath it. Ink calls every mounted `useInput` callback for a given
4930
+ * keypress with no `stopPropagation`, so a bare `useInput(() => handler())`
4931
+ * would otherwise fire on every registered shortcut too, not just an
4932
+ * ordinary keypress.
4933
+ *
4934
+ * Use this instead of raw `useInput` for any screen where any ordinary
4935
+ * keypress should proceed, but a modifier combo shouldn't be able to.
4936
+ */
4937
+ /**
4938
+ * True for a keypress that should NOT count as "any key" for a dismiss
4939
+ * handler — currently just modifier combos. Extracted as a pure predicate
4940
+ * so it's unit-testable without rendering through Ink.
4941
+ */
4942
+ function isIgnoredForDismiss(key) {
4943
+ return key.ctrl || key.meta;
4944
+ }
4945
+ function useDismissOnAnyKey(handler) {
4946
+ useInput((_input, key) => {
4947
+ if (isIgnoredForDismiss(key)) return;
4948
+ handler();
4949
+ });
4950
+ }
4951
+ //#endregion
5600
4952
  //#region src/ui/tui/screens/OutroScreen.tsx
5601
4953
  /**
5602
4954
  * OutroScreen — Default post-run summary.
@@ -5607,9 +4959,7 @@ const SlackConnectScreen = ({ store }) => {
5607
4959
  */
5608
4960
  const OutroScreen = ({ store }) => {
5609
4961
  useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
5610
- useInput(() => {
5611
- store.setOutroDismissed();
5612
- });
4962
+ useDismissOnAnyKey(() => store.setOutroDismissed());
5613
4963
  const outroData = store.session.outroData;
5614
4964
  if (!outroData) return /* @__PURE__ */ jsx(Box, {
5615
4965
  flexDirection: "column",
@@ -6120,6 +5470,6 @@ function releaseTerminal() {
6120
5470
  process.stdout.write(RESET_ATTRS + LEAVE_ALT_SCREEN);
6121
5471
  }
6122
5472
  //#endregion
6123
- export { ConfirmationInput as A, TabContainer as C, LinkText as D, LogViewer as E, SplitView as F, CardLayout as I, useStdoutDimensions as M, ProgressList as N, extractUrls as O, LoadingBox as P, HNViewer as S, EventPlanViewer as T, ServiceHealthList as _, useSkillEntry as a, LearnCard as b, AUDIT_AREA_SLIDES as c, McpSuggestedPromptsScreen as d, TAILORED_ROLES as f, SEVERITY_ORDER as g, SEVERITY_LABEL as h, SkillSourceInfo as i, GroupedPickerMenu as j, ModalOverlay as k, VisualBox as l, IssueTable as m, releaseTerminal as n, OutroScreen as o, McpScreen as p, AiOptInRequiredScreen as r, SlackConnectScreen as s, enterDarkTerminal as t, AuditChecksViewer as u, VisualizerTab as v, ScreenContainer as w, ContentSequencer as x, TipsCard as y };
5473
+ export { ConfirmationInput as A, TabContainer as C, LinkText as D, LogViewer as E, SplitView as F, CardLayout as I, useStdoutDimensions as M, ProgressList as N, extractUrls as O, LoadingBox as P, HNViewer as S, EventPlanViewer as T, SEVERITY_ORDER as _, useSkillEntry as a, LearnCard as b, SlackConnectScreen as c, AuditChecksViewer as d, McpSuggestedPromptsScreen as f, SEVERITY_LABEL as g, IssueTable as h, SkillSourceInfo as i, GroupedPickerMenu as j, ModalOverlay as k, AUDIT_AREA_SLIDES as l, McpScreen as m, releaseTerminal as n, OutroScreen as o, TAILORED_ROLES as p, AiOptInRequiredScreen as r, useDismissOnAnyKey as s, enterDarkTerminal as t, VisualBox as u, ServiceHealthList as v, ScreenContainer as w, ContentSequencer as x, TipsCard as y };
6124
5474
 
6125
- //# sourceMappingURL=terminal-Bd6q58zn.js.map
5475
+ //# sourceMappingURL=terminal-BTQSAkC-.js.map