@posthog/wizard 2.24.1 → 2.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-mcp-server-to-clients-CfwEQT_z.js → add-mcp-server-to-clients-C58l_KpV.js} +4 -4
- package/dist/{add-mcp-server-to-clients-CfwEQT_z.js.map → add-mcp-server-to-clients-C58l_KpV.js.map} +1 -1
- package/dist/{agent-interface-D1vtN6Wn.js → agent-interface-Dq_4h2eN.js} +435 -45
- package/dist/agent-interface-Dq_4h2eN.js.map +1 -0
- package/dist/{agent-runner-CBbkS0Ro.js → agent-runner-BNGW3osc.js} +748 -132
- package/dist/agent-runner-BNGW3osc.js.map +1 -0
- package/dist/{analytics-CUr82BDl.js → analytics-BX3LKPch.js} +51 -17
- package/dist/analytics-BX3LKPch.js.map +1 -0
- package/dist/{api-CI3Z74NG.js → api-DCHci5SD.js} +9 -5
- package/dist/api-DCHci5SD.js.map +1 -0
- package/dist/bin.js +830 -120
- package/dist/bin.js.map +1 -1
- package/dist/{ci-install-D_kxNmbJ.js → ci-install-CHIbwXio.js} +5 -5
- package/dist/{ci-install-D_kxNmbJ.js.map → ci-install-CHIbwXio.js.map} +1 -1
- package/dist/{debug-DxA_f5QT.js → debug-BizeRFR0.js} +17 -8
- package/dist/debug-BizeRFR0.js.map +1 -0
- package/dist/{debug-zMvpNYb2.js → debug-fg4BAKKA.js} +1 -1
- package/dist/{environment-CyS37cmM.js → environment-DS5Pq9Wm.js} +3 -3
- package/dist/{environment-CyS37cmM.js.map → environment-DS5Pq9Wm.js.map} +1 -1
- package/dist/{interactive-CG6FFqSw.js → interactive-DE3WDjk7.js} +3 -3
- package/dist/{interactive-CG6FFqSw.js.map → interactive-DE3WDjk7.js.map} +1 -1
- package/dist/{mcp-prompt-streaming-DQz4FSb1.js → mcp-prompt-streaming-zsYd1zJx.js} +7 -26
- package/dist/mcp-prompt-streaming-zsYd1zJx.js.map +1 -0
- package/dist/{non-interactive-DWtHX3ZR.js → non-interactive-DNah9u3t.js} +2 -2
- package/dist/{non-interactive-DWtHX3ZR.js.map → non-interactive-DNah9u3t.js.map} +1 -1
- package/dist/{package-manager-BWUS4CP0.js → package-manager-Dma9-zGs.js} +2 -2
- package/dist/{package-manager-BWUS4CP0.js.map → package-manager-Dma9-zGs.js.map} +1 -1
- package/dist/{playground-D7AhMMF5.js → playground-Cwe0Q9HW.js} +146 -49
- package/dist/playground-Cwe0Q9HW.js.map +1 -0
- package/dist/{posthog-integration-DexZ2uHU.js → posthog-integration-CAYZdk0r.js} +11 -11
- package/dist/{posthog-integration-DexZ2uHU.js.map → posthog-integration-CAYZdk0r.js.map} +1 -1
- package/dist/{provisioning-9c-AQbsa.js → provisioning-BmL4ro-o.js} +10 -6
- package/dist/{provisioning-9c-AQbsa.js.map → provisioning-BmL4ro-o.js.map} +1 -1
- package/dist/{registry-CO7JVZyE.js → registry-C3wcDM3X.js} +4 -4
- package/dist/{registry-CO7JVZyE.js.map → registry-C3wcDM3X.js.map} +1 -1
- package/dist/{setup-utils-0U-_Md2G.js → setup-utils-CNWIMZ-d.js} +71 -16
- package/dist/setup-utils-CNWIMZ-d.js.map +1 -0
- package/dist/smoke-test.sh +36 -1
- package/dist/{start-tui-WNb3ET14.js → start-tui-CS802Ww9.js} +311 -54
- package/dist/start-tui-CS802Ww9.js.map +1 -0
- package/dist/{steps-BAUXDCC4.js → steps-BX44xr30.js} +6 -6
- package/dist/{steps-BAUXDCC4.js.map → steps-BX44xr30.js.map} +1 -1
- package/dist/{task-stream-CZawuzlz.js → task-stream-BQNSp0qR.js} +4 -3
- package/dist/task-stream-BQNSp0qR.js.map +1 -0
- package/dist/{telemetry-ycqCpNPr.js → telemetry-BH-MgWPT.js} +3 -3
- package/dist/{telemetry-ycqCpNPr.js.map → telemetry-BH-MgWPT.js.map} +1 -1
- package/dist/{AiOptInRequiredScreen-_33FOcVo.js → terminal-BSiupnOQ.js} +1058 -92
- package/dist/terminal-BSiupnOQ.js.map +1 -0
- package/dist/{urls-C8aJWvgh.js → urls-BuEABcmF.js} +2 -2
- package/dist/{urls-C8aJWvgh.js.map → urls-BuEABcmF.js.map} +1 -1
- package/dist/{wizard-abort-DWXyJdws.js → wizard-abort-CR3w2Efg.js} +1 -1
- package/dist/{wizard-abort-C6gRLxUE.js → wizard-abort-Dl2MJOP9.js} +3 -3
- package/dist/{wizard-abort-C6gRLxUE.js.map → wizard-abort-Dl2MJOP9.js.map} +1 -1
- package/dist/wizard-session-G3VWD6hv.js.map +1 -1
- package/dist/{wizard-ui-YdGFRyu_.js → wizard-ui-WZ48rUgr.js} +2 -1
- package/dist/wizard-ui-WZ48rUgr.js.map +1 -0
- package/package.json +1 -1
- package/dist/AiOptInRequiredScreen-_33FOcVo.js.map +0 -1
- package/dist/agent-interface-D1vtN6Wn.js.map +0 -1
- package/dist/agent-runner-CBbkS0Ro.js.map +0 -1
- package/dist/analytics-CUr82BDl.js.map +0 -1
- package/dist/api-CI3Z74NG.js.map +0 -1
- package/dist/debug-DxA_f5QT.js.map +0 -1
- package/dist/mcp-prompt-streaming-DQz4FSb1.js.map +0 -1
- package/dist/playground-D7AhMMF5.js.map +0 -1
- package/dist/setup-utils-0U-_Md2G.js.map +0 -1
- package/dist/start-tui-WNb3ET14.js.map +0 -1
- package/dist/task-stream-CZawuzlz.js.map +0 -1
- package/dist/wizard-ui-YdGFRyu_.js.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as isTaskStatus } from "./wizard-ui-
|
|
3
|
-
import { r as sessionProperties, t as analytics } from "./analytics-
|
|
4
|
-
import { i as withUtm, n as openTrackedLink } from "./telemetry-
|
|
5
|
-
import { t as getOrAskForProjectData } from "./setup-utils-
|
|
6
|
-
import { n as getCloudUrlFromRegion } from "./urls-
|
|
7
|
-
import { a as fetchUserData, i as fetchSlackConnected } from "./api-
|
|
1
|
+
import { $ as getSkillsBaseUrl, M as POSTHOG_APP_URL, g as SERVICE_LABELS, s as logToFile, y as getBlockingServiceKeys } from "./debug-BizeRFR0.js";
|
|
2
|
+
import { n as isTaskStatus } from "./wizard-ui-WZ48rUgr.js";
|
|
3
|
+
import { r as sessionProperties, t as analytics } from "./analytics-BX3LKPch.js";
|
|
4
|
+
import { i as withUtm, n as openTrackedLink } from "./telemetry-BH-MgWPT.js";
|
|
5
|
+
import { t as getOrAskForProjectData } from "./setup-utils-CNWIMZ-d.js";
|
|
6
|
+
import { n as getCloudUrlFromRegion } from "./urls-BuEABcmF.js";
|
|
7
|
+
import { a as fetchUserData, i as fetchSlackConnected } from "./api-DCHci5SD.js";
|
|
8
8
|
import { i as buildSession } from "./wizard-session-G3VWD6hv.js";
|
|
9
|
-
import {
|
|
10
|
-
import { c as computeVisibleRange, d as isObjectBlock, f as Colors, l as isClearBlock, p as Icons, s as TextBlock, u as isLinesBlock } from "./posthog-integration-
|
|
11
|
-
import { a as
|
|
9
|
+
import { C as AUDIT_SEVERITY_STYLE, h as fetchSkillMenu } from "./agent-interface-Dq_4h2eN.js";
|
|
10
|
+
import { c as computeVisibleRange, d as isObjectBlock, f as Colors, l as isClearBlock, p as Icons, s as TextBlock, u as isLinesBlock } from "./posthog-integration-CAYZdk0r.js";
|
|
11
|
+
import { a as ConfirmButton, d as getProgramConfig, i as useKeyboardHintsContext, l as PROGRAM_REGISTRY, m as getKindMeta, n as useKeyBindings, o as PromptLabel, r as KeyboardHintsProvider, t as PickerMenu, u as Program } from "./bin.js";
|
|
12
12
|
import { n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-BNWIWzjc.js";
|
|
13
13
|
import opn from "opn";
|
|
14
14
|
import * as fs$1 from "fs";
|
|
@@ -205,6 +205,7 @@ var WizardStore = class {
|
|
|
205
205
|
$learnCardBlockIdx = atom(0);
|
|
206
206
|
$learnCardComplete = atom(false);
|
|
207
207
|
$version = atom(0);
|
|
208
|
+
$currentStage = atom(null);
|
|
208
209
|
_onTasksChanged = null;
|
|
209
210
|
/** Last screen seen — used to detect screen transitions for analytics. */
|
|
210
211
|
_lastScreen = null;
|
|
@@ -337,6 +338,19 @@ var WizardStore = class {
|
|
|
337
338
|
get eventPlan() {
|
|
338
339
|
return this.$eventPlan.get();
|
|
339
340
|
}
|
|
341
|
+
get currentStage() {
|
|
342
|
+
return this.$currentStage.get();
|
|
343
|
+
}
|
|
344
|
+
/** No-op when the stage hasn't changed, so `startedAt` survives across
|
|
345
|
+
* re-renders and tab switches and measures real stage time. */
|
|
346
|
+
setCurrentStage(stage) {
|
|
347
|
+
if (this.$currentStage.get()?.stage === stage) return;
|
|
348
|
+
this.$currentStage.set({
|
|
349
|
+
stage,
|
|
350
|
+
startedAt: Date.now()
|
|
351
|
+
});
|
|
352
|
+
this.emitChange();
|
|
353
|
+
}
|
|
340
354
|
get statusExpanded() {
|
|
341
355
|
return this.$statusExpanded.get();
|
|
342
356
|
}
|
|
@@ -804,7 +818,7 @@ const LoadingBox = ({ message }) => {
|
|
|
804
818
|
* Extracted from StatusTab logic.
|
|
805
819
|
*/
|
|
806
820
|
const ProgressList = ({ items, title }) => {
|
|
807
|
-
const
|
|
821
|
+
const resolved = items.filter((t) => t.status === "completed" || t.status === "skipped").length;
|
|
808
822
|
const total = items.length;
|
|
809
823
|
return /* @__PURE__ */ jsxs(Box, {
|
|
810
824
|
flexDirection: "column",
|
|
@@ -815,14 +829,16 @@ const ProgressList = ({ items, title }) => {
|
|
|
815
829
|
}), /* @__PURE__ */ jsx(Text, { children: " " })] }),
|
|
816
830
|
items.length === 0 && /* @__PURE__ */ jsx(LoadingBox, { message: "Analyzing project..." }),
|
|
817
831
|
items.map((item, i) => {
|
|
832
|
+
const skipped = item.status === "skipped";
|
|
818
833
|
const icon = item.status === "completed" ? Icons.squareFilled : item.status === "in_progress" ? Icons.triangleRight : Icons.squareOpen;
|
|
819
834
|
const color = item.status === "completed" ? Colors.success : item.status === "in_progress" ? Colors.primary : Colors.muted;
|
|
820
|
-
const label = item.status === "in_progress" && item.activeForm ? item.activeForm : item.label;
|
|
835
|
+
const label = skipped ? `${item.label} (skipped)` : item.status === "in_progress" && item.activeForm ? item.activeForm : item.label;
|
|
821
836
|
return /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
822
837
|
color,
|
|
823
838
|
children: icon
|
|
824
839
|
}), /* @__PURE__ */ jsxs(Text, {
|
|
825
|
-
dimColor: item.status === "pending",
|
|
840
|
+
dimColor: item.status === "pending" || skipped,
|
|
841
|
+
strikethrough: skipped,
|
|
826
842
|
children: [" ", label]
|
|
827
843
|
})] }, i);
|
|
828
844
|
}),
|
|
@@ -831,7 +847,7 @@ const ProgressList = ({ items, title }) => {
|
|
|
831
847
|
gap: 1,
|
|
832
848
|
children: [/* @__PURE__ */ jsx(Spinner, {}), /* @__PURE__ */ jsx(Text, {
|
|
833
849
|
dimColor: true,
|
|
834
|
-
children:
|
|
850
|
+
children: resolved < total ? `Progress: ${resolved}/${total} completed` : "Cleaning up..."
|
|
835
851
|
})]
|
|
836
852
|
})
|
|
837
853
|
]
|
|
@@ -872,8 +888,11 @@ function useStdoutDimensions() {
|
|
|
872
888
|
* GroupedPickerMenu — Multi-select with category headers.
|
|
873
889
|
*
|
|
874
890
|
* Renders groups of options with bold category labels.
|
|
875
|
-
* Arrow keys navigate selectable items (headers are skipped)
|
|
876
|
-
*
|
|
891
|
+
* Arrow keys navigate selectable items (headers are skipped) and the Confirm
|
|
892
|
+
* button below them; enter toggles the focused option, "a" toggles all, and
|
|
893
|
+
* moving onto the Confirm button and pressing enter submits. Space is kept as
|
|
894
|
+
* an undocumented alias for enter. Shares the interaction model with
|
|
895
|
+
* PickerMenu mode="multi".
|
|
877
896
|
*
|
|
878
897
|
* When content exceeds available terminal height, the list scrolls
|
|
879
898
|
* to keep the focused item visible with up/down indicators.
|
|
@@ -888,8 +907,12 @@ function truncateWithEllipsis(text, maxWidth) {
|
|
|
888
907
|
}
|
|
889
908
|
/** Rows consumed by chrome outside this component (title bar, screen padding, etc.) */
|
|
890
909
|
const CHROME_OVERHEAD = 10;
|
|
891
|
-
/**
|
|
892
|
-
|
|
910
|
+
/**
|
|
911
|
+
* Rows used by the prompt label + marginTop before content (hint text moved to
|
|
912
|
+
* KeyboardHintsBar) plus the Confirm button below the list: marginTop (1) +
|
|
913
|
+
* single border top/bottom (2) + button text (1).
|
|
914
|
+
*/
|
|
915
|
+
const MENU_CHROME = 6;
|
|
893
916
|
/** Count the visual rows occupied by rows[start..end), accounting for header margins. */
|
|
894
917
|
function countVisualRows(rows, start, end) {
|
|
895
918
|
let count = 0;
|
|
@@ -947,6 +970,7 @@ const GroupedPickerMenu = ({ message, groups, initialSelected, onSelect }) => {
|
|
|
947
970
|
const selectableIndices = useMemo(() => rows.map((r, i) => r.kind === "option" ? i : -1).filter((i) => i >= 0), [rows]);
|
|
948
971
|
const allValues = useMemo(() => rows.filter((r) => r.kind === "option").map((r) => r.value), [rows]);
|
|
949
972
|
const [focusedSelectable, setFocusedSelectable] = useState(0);
|
|
973
|
+
const [onButton, setOnButton] = useState(false);
|
|
950
974
|
const [selected, setSelected] = useState(() => new Set(initialSelected ?? allValues));
|
|
951
975
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
952
976
|
const focusedRowIdx = selectableIndices[focusedSelectable] ?? 0;
|
|
@@ -959,23 +983,32 @@ const GroupedPickerMenu = ({ message, groups, initialSelected, onSelect }) => {
|
|
|
959
983
|
label: "↑↓",
|
|
960
984
|
action: "navigate",
|
|
961
985
|
handler: (_input, key) => {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
setFocusedSelectable(newFocused);
|
|
986
|
+
const last = selectableIndices.length - 1;
|
|
987
|
+
const moveTo = (target) => {
|
|
988
|
+
setOnButton(false);
|
|
989
|
+
setFocusedSelectable(target);
|
|
967
990
|
if (needsScroll) {
|
|
968
|
-
const
|
|
969
|
-
setScrollOffset((prev) => adjustScrollOffset(prev,
|
|
991
|
+
const rowIdx = selectableIndices[target] ?? 0;
|
|
992
|
+
setScrollOffset((prev) => adjustScrollOffset(prev, rowIdx, rows, effectiveBudget));
|
|
970
993
|
}
|
|
971
|
-
}
|
|
994
|
+
};
|
|
995
|
+
if (key.upArrow) if (onButton) moveTo(last);
|
|
996
|
+
else if (focusedSelectable > 0) moveTo(focusedSelectable - 1);
|
|
997
|
+
else setOnButton(true);
|
|
998
|
+
if (key.downArrow) if (onButton) moveTo(0);
|
|
999
|
+
else if (focusedSelectable < last) moveTo(focusedSelectable + 1);
|
|
1000
|
+
else setOnButton(true);
|
|
972
1001
|
}
|
|
973
1002
|
},
|
|
974
1003
|
{
|
|
975
|
-
match: "space",
|
|
976
|
-
label: "
|
|
977
|
-
action: "
|
|
1004
|
+
match: ["space", "return"],
|
|
1005
|
+
label: "enter",
|
|
1006
|
+
action: "select",
|
|
978
1007
|
handler: () => {
|
|
1008
|
+
if (onButton) {
|
|
1009
|
+
onSelect([...selected]);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
979
1012
|
const row = rows[selectableIndices[focusedSelectable] ?? 0];
|
|
980
1013
|
if (row?.kind === "option") setSelected((prev) => {
|
|
981
1014
|
const next = new Set(prev);
|
|
@@ -996,14 +1029,6 @@ const GroupedPickerMenu = ({ message, groups, initialSelected, onSelect }) => {
|
|
|
996
1029
|
return new Set(allValues);
|
|
997
1030
|
});
|
|
998
1031
|
}
|
|
999
|
-
},
|
|
1000
|
-
{
|
|
1001
|
-
match: "return",
|
|
1002
|
-
label: "enter",
|
|
1003
|
-
action: "confirm",
|
|
1004
|
-
handler: () => {
|
|
1005
|
-
onSelect([...selected]);
|
|
1006
|
-
}
|
|
1007
1032
|
}
|
|
1008
1033
|
]);
|
|
1009
1034
|
const visibleStart = needsScroll ? scrollOffset : 0;
|
|
@@ -1013,56 +1038,67 @@ const GroupedPickerMenu = ({ message, groups, initialSelected, onSelect }) => {
|
|
|
1013
1038
|
const hiddenBelow = needsScroll ? selectableIndices.filter((s) => s >= visibleEnd).length : 0;
|
|
1014
1039
|
return /* @__PURE__ */ jsxs(Box, {
|
|
1015
1040
|
flexDirection: "column",
|
|
1016
|
-
children: [
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
dimColor: true,
|
|
1032
|
-
children: row.label
|
|
1033
|
-
})
|
|
1034
|
-
}, `h-${absIdx}`);
|
|
1035
|
-
const isFocused = focusedRowIdx === absIdx;
|
|
1036
|
-
const isSelected = selected.has(row.value);
|
|
1037
|
-
const checkbox = isSelected ? Icons.squareFilled : Icons.squareOpen;
|
|
1038
|
-
const label = truncateWithEllipsis(row.hint ? `${row.label} (${row.hint})` : row.label, labelWidth);
|
|
1039
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
1040
|
-
gap: 1,
|
|
1041
|
-
marginLeft: 1,
|
|
1042
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
1043
|
-
color: isSelected ? "white" : Colors.muted,
|
|
1044
|
-
dimColor: !isFocused && !isSelected,
|
|
1045
|
-
children: checkbox
|
|
1046
|
-
}), /* @__PURE__ */ jsx(Box, {
|
|
1047
|
-
flexGrow: 1,
|
|
1048
|
-
flexShrink: 1,
|
|
1049
|
-
overflow: "hidden",
|
|
1041
|
+
children: [
|
|
1042
|
+
/* @__PURE__ */ jsx(PromptLabel, { message }),
|
|
1043
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
1044
|
+
flexDirection: "column",
|
|
1045
|
+
marginTop: 1,
|
|
1046
|
+
marginLeft: 2,
|
|
1047
|
+
children: [
|
|
1048
|
+
needsScroll && /* @__PURE__ */ jsx(Text, {
|
|
1049
|
+
dimColor: true,
|
|
1050
|
+
children: hiddenAbove > 0 ? `\u2191 ${hiddenAbove} more` : " "
|
|
1051
|
+
}),
|
|
1052
|
+
visibleRows.map((row, relIdx) => {
|
|
1053
|
+
const absIdx = visibleStart + relIdx;
|
|
1054
|
+
if (row.kind === "header") return /* @__PURE__ */ jsx(Box, {
|
|
1055
|
+
marginTop: relIdx > 0 && absIdx > 0 ? 1 : 0,
|
|
1050
1056
|
children: /* @__PURE__ */ jsx(Text, {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
wrap: "truncate",
|
|
1055
|
-
children: label
|
|
1057
|
+
bold: true,
|
|
1058
|
+
dimColor: true,
|
|
1059
|
+
children: row.label
|
|
1056
1060
|
})
|
|
1057
|
-
})
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1061
|
+
}, `h-${absIdx}`);
|
|
1062
|
+
const isFocused = !onButton && focusedRowIdx === absIdx;
|
|
1063
|
+
const isSelected = selected.has(row.value);
|
|
1064
|
+
const checkbox = isSelected ? Icons.squareFilled : Icons.squareOpen;
|
|
1065
|
+
const label = truncateWithEllipsis(row.hint ? `${row.label} (${row.hint})` : row.label, labelWidth);
|
|
1066
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1067
|
+
gap: 1,
|
|
1068
|
+
marginLeft: 1,
|
|
1069
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
1070
|
+
color: isSelected ? "white" : Colors.muted,
|
|
1071
|
+
dimColor: !isFocused && !isSelected,
|
|
1072
|
+
children: checkbox
|
|
1073
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
1074
|
+
flexGrow: 1,
|
|
1075
|
+
flexShrink: 1,
|
|
1076
|
+
overflow: "hidden",
|
|
1077
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1078
|
+
color: isFocused ? Colors.accent : void 0,
|
|
1079
|
+
bold: isFocused,
|
|
1080
|
+
dimColor: !isFocused,
|
|
1081
|
+
wrap: "truncate",
|
|
1082
|
+
children: label
|
|
1083
|
+
})
|
|
1084
|
+
})]
|
|
1085
|
+
}, row.value);
|
|
1086
|
+
}),
|
|
1087
|
+
needsScroll && /* @__PURE__ */ jsx(Text, {
|
|
1088
|
+
dimColor: true,
|
|
1089
|
+
children: hiddenBelow > 0 ? `\u2193 ${hiddenBelow} more` : " "
|
|
1090
|
+
})
|
|
1091
|
+
]
|
|
1092
|
+
}),
|
|
1093
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1094
|
+
marginTop: 1,
|
|
1095
|
+
marginLeft: 2,
|
|
1096
|
+
children: /* @__PURE__ */ jsx(ConfirmButton, {
|
|
1097
|
+
focused: onButton,
|
|
1098
|
+
count: selected.size
|
|
1063
1099
|
})
|
|
1064
|
-
|
|
1065
|
-
|
|
1100
|
+
})
|
|
1101
|
+
]
|
|
1066
1102
|
});
|
|
1067
1103
|
};
|
|
1068
1104
|
//#endregion
|
|
@@ -1191,6 +1227,107 @@ const ModalOverlay = ({ borderColor, title, titleColor, width = 68, children, fe
|
|
|
1191
1227
|
});
|
|
1192
1228
|
};
|
|
1193
1229
|
//#endregion
|
|
1230
|
+
//#region src/ui/tui/primitives/link-helpers.ts
|
|
1231
|
+
/**
|
|
1232
|
+
* Link-rendering helpers for terminal prompts.
|
|
1233
|
+
*
|
|
1234
|
+
* Terminals that auto-linkify text scan *visual* lines, so a URL the TUI wraps
|
|
1235
|
+
* across lines — or pads with box-border characters — gets a wrong click
|
|
1236
|
+
* target: the terminal opens half a URL, or one stitched back together with
|
|
1237
|
+
* border glyphs and padding.
|
|
1238
|
+
*
|
|
1239
|
+
* The fix is an explicit OSC 8 hyperlink: the escape carries the exact target
|
|
1240
|
+
* out of band, independent of the visible layout, and Ink's wrap re-emits it on
|
|
1241
|
+
* every wrapped line — so the click target stays correct even when the URL
|
|
1242
|
+
* wraps to fit the overlay. Each standalone URL gets its own line so the escape
|
|
1243
|
+
* brackets exactly one URL. Terminals without OSC 8 support ignore the escape
|
|
1244
|
+
* and show the visible text.
|
|
1245
|
+
*/
|
|
1246
|
+
const ESC = String.fromCharCode(27);
|
|
1247
|
+
const BEL = String.fromCharCode(7);
|
|
1248
|
+
const OSC_8 = `${ESC}]8;;`;
|
|
1249
|
+
/** Matches an http(s) URL run (no surrounding whitespace). */
|
|
1250
|
+
const URL_RUN = /https?:\/\/[^\s]+/g;
|
|
1251
|
+
/** A line whose entire (trimmed) content is a single URL. */
|
|
1252
|
+
const STANDALONE_URL_LINE = /^https?:\/\/\S+$/;
|
|
1253
|
+
/** Trailing characters that are punctuation around a URL, not part of it. */
|
|
1254
|
+
const TRAILING_PUNCTUATION = /[.,;:!?)\]}>'"]+$/;
|
|
1255
|
+
function trimTrailingPunctuation(url) {
|
|
1256
|
+
return url.replace(TRAILING_PUNCTUATION, "");
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Wrap `label` (defaults to the URL) in an OSC 8 hyperlink escape pointing
|
|
1260
|
+
* at `url`. Terminals that support OSC 8 make the whole run clickable to the
|
|
1261
|
+
* exact `url`; terminals that don't render `label` as plain text.
|
|
1262
|
+
*/
|
|
1263
|
+
function osc8Hyperlink(url, label = url) {
|
|
1264
|
+
return `${OSC_8}${url}${BEL}${label}${OSC_8}${BEL}`;
|
|
1265
|
+
}
|
|
1266
|
+
/** Extract every http(s) URL in `text`, trailing punctuation removed. */
|
|
1267
|
+
function extractUrls(text) {
|
|
1268
|
+
return (text.match(URL_RUN) ?? []).map(trimTrailingPunctuation);
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Split prompt text into renderable segments, breaking out any line that is
|
|
1272
|
+
* *solely* a URL into its own `url` segment. Consecutive non-URL lines stay
|
|
1273
|
+
* grouped in one `text` segment (newlines preserved) so paragraph spacing is
|
|
1274
|
+
* unchanged. URLs that appear inline within prose are left in the text
|
|
1275
|
+
* segment — only standalone-line URLs are special-cased, which is how the
|
|
1276
|
+
* wizard's prompts present links a user is meant to open.
|
|
1277
|
+
*/
|
|
1278
|
+
function splitPromptIntoSegments(text) {
|
|
1279
|
+
const segments = [];
|
|
1280
|
+
let buffer = [];
|
|
1281
|
+
const flush = () => {
|
|
1282
|
+
if (buffer.length > 0) {
|
|
1283
|
+
segments.push({
|
|
1284
|
+
type: "text",
|
|
1285
|
+
value: buffer.join("\n")
|
|
1286
|
+
});
|
|
1287
|
+
buffer = [];
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
for (const line of text.split("\n")) {
|
|
1291
|
+
const trimmed = line.trim();
|
|
1292
|
+
if (STANDALONE_URL_LINE.test(trimmed)) {
|
|
1293
|
+
flush();
|
|
1294
|
+
segments.push({
|
|
1295
|
+
type: "url",
|
|
1296
|
+
value: trimTrailingPunctuation(trimmed)
|
|
1297
|
+
});
|
|
1298
|
+
} else buffer.push(line);
|
|
1299
|
+
}
|
|
1300
|
+
flush();
|
|
1301
|
+
return segments;
|
|
1302
|
+
}
|
|
1303
|
+
//#endregion
|
|
1304
|
+
//#region src/ui/tui/primitives/LinkText.tsx
|
|
1305
|
+
/**
|
|
1306
|
+
* LinkText — renders prompt text with standalone URLs as OSC 8 hyperlinks.
|
|
1307
|
+
*
|
|
1308
|
+
* Each URL gets its own line, wrapped (`wrap="wrap"`) so the full URL is shown
|
|
1309
|
+
* within the overlay instead of truncated — the user can read and select all of
|
|
1310
|
+
* it. Ink's wrap (wrap-ansi) re-emits the OSC 8 escape on every wrapped visual
|
|
1311
|
+
* line, so the click target stays the exact, full URL no matter where the
|
|
1312
|
+
* visible text breaks. A manual selection of a wrapped line still picks up the
|
|
1313
|
+
* line break, so for a clean copy `WizardAskScreen` auto-copies a sole URL to
|
|
1314
|
+
* the clipboard. Prose renders unchanged.
|
|
1315
|
+
*
|
|
1316
|
+
* Used by the `wizard_ask` overlay only for programs that opt into rich link
|
|
1317
|
+
* rendering (see `PendingQuestion.richLinks`). Other flows are untouched.
|
|
1318
|
+
*/
|
|
1319
|
+
const LinkText = ({ text }) => {
|
|
1320
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
1321
|
+
flexDirection: "column",
|
|
1322
|
+
children: splitPromptIntoSegments(text).map((segment, i) => segment.type === "url" ? /* @__PURE__ */ jsx(Text, {
|
|
1323
|
+
color: Colors.accent,
|
|
1324
|
+
underline: true,
|
|
1325
|
+
wrap: "wrap",
|
|
1326
|
+
children: osc8Hyperlink(segment.value)
|
|
1327
|
+
}, i) : /* @__PURE__ */ jsx(Text, { children: segment.value }, i))
|
|
1328
|
+
});
|
|
1329
|
+
};
|
|
1330
|
+
//#endregion
|
|
1194
1331
|
//#region src/ui/tui/primitives/LogViewer.tsx
|
|
1195
1332
|
/**
|
|
1196
1333
|
* LogViewer — Real-time log tail, pinned to available terminal height.
|
|
@@ -1203,8 +1340,10 @@ const ModalOverlay = ({ borderColor, title, titleColor, width = 68, children, fe
|
|
|
1203
1340
|
* a log that grows into the hundreds of MB, producing OOM-grade allocation
|
|
1204
1341
|
* pressure on V8's heap.
|
|
1205
1342
|
*/
|
|
1206
|
-
/** Rows consumed by TitleBar + spacer + ScreenContainer padding + status bar +
|
|
1207
|
-
|
|
1343
|
+
/** Rows consumed by TitleBar + spacer + ScreenContainer padding + status bar +
|
|
1344
|
+
* tab bar, with a couple rows of headroom so the tail never crowds the status
|
|
1345
|
+
* bar below it. */
|
|
1346
|
+
const CHROME_ROWS$1 = 10;
|
|
1208
1347
|
/** Bytes read from the end of the log per refresh — large enough to contain
|
|
1209
1348
|
* any practical visible window of lines, small enough to allocate cheaply. */
|
|
1210
1349
|
const TAIL_BYTES = 256 * 1024;
|
|
@@ -2063,7 +2202,14 @@ const LearnCard = ({ store, blocks, onComplete }) => {
|
|
|
2063
2202
|
* Reactively shows/hides tips based on discovered features.
|
|
2064
2203
|
* Supports toggling additional features via key bindings.
|
|
2065
2204
|
*/
|
|
2066
|
-
|
|
2205
|
+
/**
|
|
2206
|
+
* The default deck — generic PostHog onboarding tips, shown for any
|
|
2207
|
+
* program that doesn't supply its own via `ProgramConfig.getTips`.
|
|
2208
|
+
* Program-specific copy (e.g. the self-driving scout/source
|
|
2209
|
+
* explainers) lives in that program's content, not here — this stays the
|
|
2210
|
+
* neutral fallback.
|
|
2211
|
+
*/
|
|
2212
|
+
const DEFAULT_TIPS = [
|
|
2067
2213
|
{
|
|
2068
2214
|
id: "persons",
|
|
2069
2215
|
title: "You can also track people and groups with PostHog",
|
|
@@ -2101,9 +2247,9 @@ const TIPS = [
|
|
|
2101
2247
|
}
|
|
2102
2248
|
}
|
|
2103
2249
|
];
|
|
2104
|
-
const TipsCard = ({ store }) => {
|
|
2250
|
+
const TipsCard = ({ store, tips = DEFAULT_TIPS }) => {
|
|
2105
2251
|
useInput((input) => {
|
|
2106
|
-
for (const tip of
|
|
2252
|
+
for (const tip of tips) if (tip.toggle && input.toLowerCase() === tip.toggle.key && (!tip.visible || tip.visible(store)) && !tip.toggle.isEnabled(store)) store.enableFeature(tip.toggle.feature);
|
|
2107
2253
|
});
|
|
2108
2254
|
return /* @__PURE__ */ jsxs(Box, {
|
|
2109
2255
|
flexDirection: "column",
|
|
@@ -2115,7 +2261,7 @@ const TipsCard = ({ store }) => {
|
|
|
2115
2261
|
children: "Tips"
|
|
2116
2262
|
}),
|
|
2117
2263
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
2118
|
-
|
|
2264
|
+
tips.filter((tip) => !tip.visible || tip.visible(store)).map((tip) => /* @__PURE__ */ jsxs(Box, {
|
|
2119
2265
|
flexDirection: "column",
|
|
2120
2266
|
marginBottom: 1,
|
|
2121
2267
|
children: [/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
@@ -2157,6 +2303,782 @@ const TipsCard = ({ store }) => {
|
|
|
2157
2303
|
});
|
|
2158
2304
|
};
|
|
2159
2305
|
//#endregion
|
|
2306
|
+
//#region src/ui/tui/hooks/useTick.ts
|
|
2307
|
+
/**
|
|
2308
|
+
* useTick — forces a re-render every `intervalMs`. Returns a monotonically
|
|
2309
|
+
* increasing counter, which is rarely needed — most callers just want the
|
|
2310
|
+
* re-render side effect.
|
|
2311
|
+
*/
|
|
2312
|
+
function useTick(intervalMs) {
|
|
2313
|
+
const [tick, setTick] = useState(0);
|
|
2314
|
+
useEffect(() => {
|
|
2315
|
+
const id = setInterval(() => setTick((t) => t + 1), intervalMs);
|
|
2316
|
+
return () => clearInterval(id);
|
|
2317
|
+
}, [intervalMs]);
|
|
2318
|
+
return tick;
|
|
2319
|
+
}
|
|
2320
|
+
//#endregion
|
|
2321
|
+
//#region src/ui/tui/components/visualizer/palette.ts
|
|
2322
|
+
const VISUALIZER_PALETTE = {
|
|
2323
|
+
fade: "#0E7A0E",
|
|
2324
|
+
bright: "#7CFF7C",
|
|
2325
|
+
mid: "#22D622",
|
|
2326
|
+
head: "#E6FFE6",
|
|
2327
|
+
book: [
|
|
2328
|
+
"#22D622",
|
|
2329
|
+
"#7CFF7C",
|
|
2330
|
+
"#5BE05B",
|
|
2331
|
+
"#A0F0A0",
|
|
2332
|
+
"#36B536"
|
|
2333
|
+
],
|
|
2334
|
+
deleteRed: "#D63B22",
|
|
2335
|
+
upGreen: "#7CFF7C"
|
|
2336
|
+
};
|
|
2337
|
+
//#endregion
|
|
2338
|
+
//#region src/ui/tui/components/visualizer/panel.tsx
|
|
2339
|
+
/**
|
|
2340
|
+
* Shared scaffolding for the phase visuals — Matrix-green color, common
|
|
2341
|
+
* VisualProps shape, and the rounded `Panel` shell every visual sits in.
|
|
2342
|
+
*
|
|
2343
|
+
* Visuals each render their own grid then wrap it in `<Panel>` so phase
|
|
2344
|
+
* transitions stay visually continuous.
|
|
2345
|
+
*/
|
|
2346
|
+
const MATRIX_FADE = VISUALIZER_PALETTE.fade;
|
|
2347
|
+
const Panel = ({ children }) => /* @__PURE__ */ jsx(Box, {
|
|
2348
|
+
flexDirection: "column",
|
|
2349
|
+
borderStyle: "round",
|
|
2350
|
+
borderColor: MATRIX_FADE,
|
|
2351
|
+
children
|
|
2352
|
+
});
|
|
2353
|
+
//#endregion
|
|
2354
|
+
//#region src/ui/tui/components/visualizer/MatrixRain.tsx
|
|
2355
|
+
/**
|
|
2356
|
+
* MatrixRain — code-rain visual used for the codebase-scan phase.
|
|
2357
|
+
*
|
|
2358
|
+
* Independent of the phase orchestrator so it can be reused elsewhere
|
|
2359
|
+
* (e.g. standalone in a demo). `bordered` toggles the rounded green frame.
|
|
2360
|
+
*/
|
|
2361
|
+
const { head: MATRIX_HEAD, bright: MATRIX_BRIGHT, mid: MATRIX_MID } = VISUALIZER_PALETTE;
|
|
2362
|
+
const DEFAULT_TICK_MS = 110;
|
|
2363
|
+
const DEFAULT_MAX_TAIL = 7;
|
|
2364
|
+
const RAIN_GLYPHS = "ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン0123456789Z<>=*+:.";
|
|
2365
|
+
function pickGlyph() {
|
|
2366
|
+
return RAIN_GLYPHS[Math.floor(Math.random() * 73)];
|
|
2367
|
+
}
|
|
2368
|
+
function makeRainColumn(height, maxTail) {
|
|
2369
|
+
return {
|
|
2370
|
+
headY: -Math.random() * height,
|
|
2371
|
+
speed: .3 + Math.random() * .9,
|
|
2372
|
+
tail: 3 + Math.floor(Math.random() * (maxTail - 2)),
|
|
2373
|
+
glyphs: /* @__PURE__ */ new Map(),
|
|
2374
|
+
dormant: Math.floor(Math.random() * 18)
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
function tickRainColumn(col, height, maxTail) {
|
|
2378
|
+
if (col.dormant > 0) return {
|
|
2379
|
+
...col,
|
|
2380
|
+
dormant: col.dormant - 1
|
|
2381
|
+
};
|
|
2382
|
+
const next = col.headY + col.speed;
|
|
2383
|
+
if (next > height + col.tail) return makeRainColumn(height, maxTail);
|
|
2384
|
+
const glyphs = new Map(col.glyphs);
|
|
2385
|
+
for (let y = Math.max(0, Math.ceil(col.headY)); y <= Math.min(Math.floor(next), height - 1); y++) glyphs.set(y, pickGlyph());
|
|
2386
|
+
if (glyphs.size > 0 && Math.random() < .22) {
|
|
2387
|
+
const keys = [...glyphs.keys()];
|
|
2388
|
+
const k = keys[Math.floor(Math.random() * keys.length)];
|
|
2389
|
+
glyphs.set(k, pickGlyph());
|
|
2390
|
+
}
|
|
2391
|
+
return {
|
|
2392
|
+
...col,
|
|
2393
|
+
headY: next,
|
|
2394
|
+
glyphs
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
const MatrixRain = ({ width, height, tickMs = DEFAULT_TICK_MS, maxTail = DEFAULT_MAX_TAIL, bordered = true }) => {
|
|
2398
|
+
const columnsRef = useRef(Array.from({ length: width }, () => makeRainColumn(height, maxTail)));
|
|
2399
|
+
const [, setTick] = useState(0);
|
|
2400
|
+
useEffect(() => {
|
|
2401
|
+
const interval = setInterval(() => {
|
|
2402
|
+
columnsRef.current = columnsRef.current.map((c) => tickRainColumn(c, height, maxTail));
|
|
2403
|
+
setTick((t) => t + 1);
|
|
2404
|
+
}, tickMs);
|
|
2405
|
+
return () => clearInterval(interval);
|
|
2406
|
+
}, [
|
|
2407
|
+
height,
|
|
2408
|
+
maxTail,
|
|
2409
|
+
tickMs
|
|
2410
|
+
]);
|
|
2411
|
+
const columns = columnsRef.current;
|
|
2412
|
+
const body = Array.from({ length: height }).map((_, y) => /* @__PURE__ */ jsx(Box, { children: columns.map((col, x) => {
|
|
2413
|
+
const glyph = col.glyphs.get(y);
|
|
2414
|
+
if (!glyph) return /* @__PURE__ */ jsx(Text, { children: " " }, x);
|
|
2415
|
+
const dist = col.headY - y;
|
|
2416
|
+
if (dist < 0 || dist > col.tail) return /* @__PURE__ */ jsx(Text, { children: " " }, x);
|
|
2417
|
+
if (dist < 1) return /* @__PURE__ */ jsx(Text, {
|
|
2418
|
+
bold: true,
|
|
2419
|
+
color: MATRIX_HEAD,
|
|
2420
|
+
children: glyph
|
|
2421
|
+
}, x);
|
|
2422
|
+
if (dist < 2) return /* @__PURE__ */ jsx(Text, {
|
|
2423
|
+
color: MATRIX_BRIGHT,
|
|
2424
|
+
children: glyph
|
|
2425
|
+
}, x);
|
|
2426
|
+
if (dist < col.tail * .55) return /* @__PURE__ */ jsx(Text, {
|
|
2427
|
+
color: MATRIX_MID,
|
|
2428
|
+
children: glyph
|
|
2429
|
+
}, x);
|
|
2430
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
2431
|
+
color: MATRIX_FADE,
|
|
2432
|
+
dimColor: true,
|
|
2433
|
+
children: glyph
|
|
2434
|
+
}, x);
|
|
2435
|
+
}) }, y));
|
|
2436
|
+
if (!bordered) return /* @__PURE__ */ jsx(Box, {
|
|
2437
|
+
flexDirection: "column",
|
|
2438
|
+
children: body
|
|
2439
|
+
});
|
|
2440
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
2441
|
+
flexDirection: "column",
|
|
2442
|
+
borderStyle: "round",
|
|
2443
|
+
borderColor: MATRIX_FADE,
|
|
2444
|
+
children: body
|
|
2445
|
+
});
|
|
2446
|
+
};
|
|
2447
|
+
//#endregion
|
|
2448
|
+
//#region src/ui/tui/components/visualizer/LibraryShelf.tsx
|
|
2449
|
+
/**
|
|
2450
|
+
* LibraryShelf — skill-selection phase.
|
|
2451
|
+
*
|
|
2452
|
+
* A row of book spines. One spine peels forward each cycle, the chosen
|
|
2453
|
+
* title hovers next to it, then it slides home and the next one is picked.
|
|
2454
|
+
*/
|
|
2455
|
+
const BOOK_LABELS = [
|
|
2456
|
+
"nx",
|
|
2457
|
+
"rt",
|
|
2458
|
+
"sv",
|
|
2459
|
+
"fl",
|
|
2460
|
+
"jg",
|
|
2461
|
+
"rb",
|
|
2462
|
+
"go",
|
|
2463
|
+
"dj",
|
|
2464
|
+
"fa",
|
|
2465
|
+
"lv",
|
|
2466
|
+
"ts",
|
|
2467
|
+
"py"
|
|
2468
|
+
];
|
|
2469
|
+
const BOOK_COLORS = VISUALIZER_PALETTE.book;
|
|
2470
|
+
const LibraryShelf = ({ width, height }) => {
|
|
2471
|
+
const tick = useTick(380);
|
|
2472
|
+
const bookCount = Math.min(Math.floor((width - 2) / 2), BOOK_LABELS.length);
|
|
2473
|
+
const cyclePos = tick % (bookCount * 4);
|
|
2474
|
+
const selectedIdx = Math.floor(cyclePos / 4);
|
|
2475
|
+
const phase = cyclePos % 4;
|
|
2476
|
+
const offset = phase === 0 ? 0 : phase === 1 ? 1 : phase === 2 ? 2 : 1;
|
|
2477
|
+
const wobble = phase === 2 && tick % 2 === 0 ? 1 : 0;
|
|
2478
|
+
const shelfY = Math.floor(height / 2) - 1;
|
|
2479
|
+
const rows = Array.from({ length: height }, () => new Array(width).fill(" "));
|
|
2480
|
+
for (let i = 0; i < bookCount; i++) {
|
|
2481
|
+
const x = 1 + i * 2;
|
|
2482
|
+
const isSelected = i === selectedIdx;
|
|
2483
|
+
const shift = isSelected ? offset : 0;
|
|
2484
|
+
const wob = isSelected && wobble ? -1 : 0;
|
|
2485
|
+
if (x + shift >= width) continue;
|
|
2486
|
+
rows[shelfY - 1][x + shift] = "█";
|
|
2487
|
+
rows[shelfY][x + shift] = BOOK_LABELS[i][0];
|
|
2488
|
+
rows[shelfY + 1][x + shift] = BOOK_LABELS[i][1];
|
|
2489
|
+
rows[shelfY + 2 + wob]?.[x + shift] !== void 0 && (rows[shelfY + 2 + wob][x + shift] = "█");
|
|
2490
|
+
}
|
|
2491
|
+
const boardY = shelfY + 3;
|
|
2492
|
+
if (boardY < height) for (let x = 0; x < width; x++) rows[boardY][x] = "─";
|
|
2493
|
+
if (phase === 2) {
|
|
2494
|
+
const labelStartX = 1 + selectedIdx * 2 + 4;
|
|
2495
|
+
const labelText = BOOK_LABELS[selectedIdx] + "-app";
|
|
2496
|
+
for (let c = 0; c < labelText.length && labelStartX + c < width; c++) rows[shelfY][labelStartX + c] = labelText[c];
|
|
2497
|
+
if (labelStartX - 1 < width && labelStartX - 1 >= 0) rows[shelfY][labelStartX - 1] = "▶";
|
|
2498
|
+
}
|
|
2499
|
+
return /* @__PURE__ */ jsx(Panel, { children: rows.map((row, y) => /* @__PURE__ */ jsx(Box, { children: row.map((ch, x) => {
|
|
2500
|
+
if (ch === " ") return /* @__PURE__ */ jsx(Text, { children: " " }, x);
|
|
2501
|
+
if (ch === "─") return /* @__PURE__ */ jsx(Text, {
|
|
2502
|
+
color: MATRIX_FADE,
|
|
2503
|
+
dimColor: true,
|
|
2504
|
+
children: "─"
|
|
2505
|
+
}, x);
|
|
2506
|
+
if (ch === "▶") return /* @__PURE__ */ jsx(Text, {
|
|
2507
|
+
bold: true,
|
|
2508
|
+
color: VISUALIZER_PALETTE.head,
|
|
2509
|
+
children: "▶"
|
|
2510
|
+
}, x);
|
|
2511
|
+
const booksColor = BOOK_COLORS[(Math.floor((x - 1) / 2) + 17 * y) % BOOK_COLORS.length];
|
|
2512
|
+
const isSel = x === 1 + selectedIdx * 2 + offset;
|
|
2513
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
2514
|
+
bold: isSel,
|
|
2515
|
+
color: isSel ? VISUALIZER_PALETTE.head : booksColor,
|
|
2516
|
+
children: ch
|
|
2517
|
+
}, x);
|
|
2518
|
+
}) }, y)) });
|
|
2519
|
+
};
|
|
2520
|
+
//#endregion
|
|
2521
|
+
//#region src/ui/tui/components/visualizer/CrateStack.tsx
|
|
2522
|
+
/**
|
|
2523
|
+
* CrateStack — dependency-install phase.
|
|
2524
|
+
*
|
|
2525
|
+
* Boxes drop from the top and stack at the bottom. Each new arrival lands
|
|
2526
|
+
* with a tiny shake.
|
|
2527
|
+
*/
|
|
2528
|
+
const PACKAGE_NAMES = [
|
|
2529
|
+
"posthog-js",
|
|
2530
|
+
"posthog-py",
|
|
2531
|
+
"posthog-rb",
|
|
2532
|
+
"posthog-go",
|
|
2533
|
+
"posthog-node",
|
|
2534
|
+
"react-ph",
|
|
2535
|
+
"next-ph",
|
|
2536
|
+
"svelte-ph",
|
|
2537
|
+
"ph-mcp",
|
|
2538
|
+
"ph-ai"
|
|
2539
|
+
];
|
|
2540
|
+
const CRATE_W = Math.max(...PACKAGE_NAMES.map((n) => n.length)) + 2;
|
|
2541
|
+
const CrateStack = ({ width, height }) => {
|
|
2542
|
+
const tick = useTick(95);
|
|
2543
|
+
const state = useRef({
|
|
2544
|
+
stack: [],
|
|
2545
|
+
falling: null,
|
|
2546
|
+
spawnCooldown: 0
|
|
2547
|
+
}).current;
|
|
2548
|
+
const crateW = CRATE_W;
|
|
2549
|
+
const crateH = 1;
|
|
2550
|
+
const floorY = height - 1;
|
|
2551
|
+
if (state.falling) {
|
|
2552
|
+
state.falling.y += 1;
|
|
2553
|
+
const collisionStackHeight = state.stack.filter((c) => Math.abs(c.x - state.falling.x) < crateW - 1).length * crateH;
|
|
2554
|
+
if (state.falling.y >= floorY - collisionStackHeight) {
|
|
2555
|
+
state.stack.push({
|
|
2556
|
+
label: state.falling.label,
|
|
2557
|
+
x: state.falling.x,
|
|
2558
|
+
landedAt: tick
|
|
2559
|
+
});
|
|
2560
|
+
state.falling = null;
|
|
2561
|
+
state.spawnCooldown = 3;
|
|
2562
|
+
}
|
|
2563
|
+
} else if (state.spawnCooldown > 0) state.spawnCooldown -= 1;
|
|
2564
|
+
else if (state.stack.length < Math.floor(height / crateH) - 1) state.falling = {
|
|
2565
|
+
label: PACKAGE_NAMES[Math.floor(Math.random() * PACKAGE_NAMES.length)],
|
|
2566
|
+
x: 2 + Math.floor(Math.random() * Math.max(1, width - crateW - 4)),
|
|
2567
|
+
y: -1
|
|
2568
|
+
};
|
|
2569
|
+
else if (tick % 20 === 0) state.stack = [];
|
|
2570
|
+
const grid = Array.from({ length: height }, () => new Array(width).fill(" "));
|
|
2571
|
+
const drawCrate = (cx, cy, label, shake) => {
|
|
2572
|
+
const x = cx + shake;
|
|
2573
|
+
if (cy < 0 || cy >= height) return;
|
|
2574
|
+
const padded = `[${label.slice(0, crateW - 2)}]`.padEnd(crateW, " ");
|
|
2575
|
+
for (let i = 0; i < crateW && x + i < width; i++) if (x + i >= 0) grid[cy][x + i] = padded[i];
|
|
2576
|
+
};
|
|
2577
|
+
state.stack.forEach((c, idx) => {
|
|
2578
|
+
const cy = floorY - idx;
|
|
2579
|
+
const shake = tick - c.landedAt === 0 ? 1 : tick - c.landedAt === 1 ? -1 : 0;
|
|
2580
|
+
drawCrate(c.x, cy, c.label, shake);
|
|
2581
|
+
});
|
|
2582
|
+
if (state.falling) drawCrate(state.falling.x, state.falling.y, state.falling.label, 0);
|
|
2583
|
+
return /* @__PURE__ */ jsx(Panel, { children: grid.map((row, y) => /* @__PURE__ */ jsx(Box, { children: row.map((ch, x) => {
|
|
2584
|
+
if (ch === " ") return /* @__PURE__ */ jsx(Text, { children: " " }, x);
|
|
2585
|
+
const isFalling = state.falling && y === state.falling.y && Math.abs(x - state.falling.x) < crateW;
|
|
2586
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
2587
|
+
bold: isFalling,
|
|
2588
|
+
color: isFalling ? VISUALIZER_PALETTE.head : VISUALIZER_PALETTE.mid,
|
|
2589
|
+
children: ch
|
|
2590
|
+
}, x);
|
|
2591
|
+
}) }, y)) });
|
|
2592
|
+
};
|
|
2593
|
+
//#endregion
|
|
2594
|
+
//#region src/ui/tui/components/visualizer/DiffCascade.tsx
|
|
2595
|
+
/**
|
|
2596
|
+
* DiffCascade — code-edits phase.
|
|
2597
|
+
*
|
|
2598
|
+
* + and - code lines scroll upward continuously. Occasional comment-rewrite
|
|
2599
|
+
* gag: a "// hmm…" line appears, gets - struck out, then a + replaces it.
|
|
2600
|
+
*/
|
|
2601
|
+
const CODE_SNIPPETS = [
|
|
2602
|
+
"import posthog from 'posthog-js'",
|
|
2603
|
+
"posthog.init(KEY, { host: HOST })",
|
|
2604
|
+
"<PostHogProvider client={posthog}>",
|
|
2605
|
+
" {children}",
|
|
2606
|
+
"</PostHogProvider>",
|
|
2607
|
+
"posthog.capture('page_viewed')",
|
|
2608
|
+
"posthog.capture('signup_started')",
|
|
2609
|
+
"posthog.identify(user.id, { email })",
|
|
2610
|
+
"if (process.env.NODE_ENV !== 'test') posthog.init(KEY)",
|
|
2611
|
+
"export const posthog = new PostHog(KEY)",
|
|
2612
|
+
"// TODO: enable replay",
|
|
2613
|
+
"window.posthog = posthog"
|
|
2614
|
+
];
|
|
2615
|
+
const WHIMSY_COMMENTS = [
|
|
2616
|
+
"// hmm — that should be a hook",
|
|
2617
|
+
"// wait, refactor incoming",
|
|
2618
|
+
"// posthog says hi"
|
|
2619
|
+
];
|
|
2620
|
+
const DiffCascade = ({ width, height }) => {
|
|
2621
|
+
const tick = useTick(280);
|
|
2622
|
+
const linesRef = useRef([]);
|
|
2623
|
+
if (linesRef.current.length === 0) for (let i = 0; i < height; i++) linesRef.current.push({
|
|
2624
|
+
sign: Math.random() < .75 ? "+" : "-",
|
|
2625
|
+
text: CODE_SNIPPETS[Math.floor(Math.random() * CODE_SNIPPETS.length)]
|
|
2626
|
+
});
|
|
2627
|
+
linesRef.current.shift();
|
|
2628
|
+
const whimsy = tick % 23 === 0;
|
|
2629
|
+
linesRef.current.push({
|
|
2630
|
+
sign: whimsy ? "-" : Math.random() < .78 ? "+" : "-",
|
|
2631
|
+
text: whimsy ? WHIMSY_COMMENTS[Math.floor(Math.random() * WHIMSY_COMMENTS.length)] : CODE_SNIPPETS[Math.floor(Math.random() * CODE_SNIPPETS.length)]
|
|
2632
|
+
});
|
|
2633
|
+
const lines = linesRef.current;
|
|
2634
|
+
return /* @__PURE__ */ jsx(Panel, { children: Array.from({ length: height }).map((_, y) => {
|
|
2635
|
+
const line = lines[y];
|
|
2636
|
+
const cap = width - 2;
|
|
2637
|
+
const text = `${line.sign} ${line.text}`.slice(0, cap);
|
|
2638
|
+
return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, {
|
|
2639
|
+
color: line.sign === "+" ? VISUALIZER_PALETTE.mid : line.sign === "-" ? VISUALIZER_PALETTE.deleteRed : MATRIX_FADE,
|
|
2640
|
+
children: text.padEnd(cap, " ")
|
|
2641
|
+
}) }, y);
|
|
2642
|
+
}) });
|
|
2643
|
+
};
|
|
2644
|
+
//#endregion
|
|
2645
|
+
//#region src/ui/tui/components/visualizer/Tumblers.tsx
|
|
2646
|
+
/**
|
|
2647
|
+
* Tumblers — env-setup phase.
|
|
2648
|
+
*
|
|
2649
|
+
* Pins fall into a lock cylinder one by one; when all six align the bolt
|
|
2650
|
+
* pulses green for a beat, then the cycle restarts.
|
|
2651
|
+
*/
|
|
2652
|
+
const Tumblers = ({ width, height }) => {
|
|
2653
|
+
const tick = useTick(80);
|
|
2654
|
+
const pinCount = Math.min(Math.floor((width - 2) / 2), 6);
|
|
2655
|
+
const state = useRef({
|
|
2656
|
+
heights: new Array(pinCount).fill(0),
|
|
2657
|
+
current: 0,
|
|
2658
|
+
fallY: 0,
|
|
2659
|
+
pulse: 0
|
|
2660
|
+
}).current;
|
|
2661
|
+
const cylinderTop = 1;
|
|
2662
|
+
const cylinderBottom = height - 2;
|
|
2663
|
+
const targetForPin = (i) => cylinderBottom - 1 - i % 3 - Math.floor(i / 2);
|
|
2664
|
+
if (state.pulse > 0) {
|
|
2665
|
+
state.pulse -= 1;
|
|
2666
|
+
if (state.pulse === 0) {
|
|
2667
|
+
state.heights = new Array(pinCount).fill(0);
|
|
2668
|
+
state.current = 0;
|
|
2669
|
+
state.fallY = 0;
|
|
2670
|
+
}
|
|
2671
|
+
} else if (state.current < pinCount) {
|
|
2672
|
+
state.fallY += 1;
|
|
2673
|
+
if (state.fallY >= targetForPin(state.current)) {
|
|
2674
|
+
state.heights[state.current] = targetForPin(state.current);
|
|
2675
|
+
state.current += 1;
|
|
2676
|
+
state.fallY = cylinderTop;
|
|
2677
|
+
}
|
|
2678
|
+
} else state.pulse = 14;
|
|
2679
|
+
const grid = Array.from({ length: height }, () => new Array(width).fill(" "));
|
|
2680
|
+
for (let y = 0; y < height; y++) {
|
|
2681
|
+
grid[y][0] = "│";
|
|
2682
|
+
grid[y][width - 1] = "│";
|
|
2683
|
+
}
|
|
2684
|
+
for (let i = 0; i < pinCount; i++) {
|
|
2685
|
+
const x = 1 + i * 2 + 1;
|
|
2686
|
+
if (x < width) grid[0][x] = "▼";
|
|
2687
|
+
}
|
|
2688
|
+
for (let x = 1; x < width - 1; x++) grid[height - 1][x] = "─";
|
|
2689
|
+
for (let i = 0; i < pinCount; i++) {
|
|
2690
|
+
const pinX = 1 + i * 2 + 1;
|
|
2691
|
+
if (pinX >= width) continue;
|
|
2692
|
+
const top = state.heights[i] || cylinderTop;
|
|
2693
|
+
for (let y = top; y <= cylinderBottom; y++) grid[y][pinX] = "█";
|
|
2694
|
+
}
|
|
2695
|
+
if (state.pulse === 0 && state.current < pinCount) {
|
|
2696
|
+
const pinX = 1 + state.current * 2 + 1;
|
|
2697
|
+
if (pinX < width) grid[state.fallY][pinX] = "█";
|
|
2698
|
+
}
|
|
2699
|
+
const pulsing = state.pulse > 0;
|
|
2700
|
+
const pulseBright = pulsing && tick % 2 === 0;
|
|
2701
|
+
return /* @__PURE__ */ jsx(Panel, { children: grid.map((row, y) => /* @__PURE__ */ jsx(Box, { children: row.map((ch, x) => {
|
|
2702
|
+
if (ch === " ") return /* @__PURE__ */ jsx(Text, { children: " " }, x);
|
|
2703
|
+
if (ch === "│" || ch === "─" || ch === "▼") return /* @__PURE__ */ jsx(Text, {
|
|
2704
|
+
color: pulsing ? pulseBright ? VISUALIZER_PALETTE.bright : MATRIX_FADE : MATRIX_FADE,
|
|
2705
|
+
dimColor: !pulsing,
|
|
2706
|
+
children: ch
|
|
2707
|
+
}, x);
|
|
2708
|
+
const isFalling = state.pulse === 0 && state.current < pinCount && y === state.fallY && x === 1 + state.current * 2 + 1;
|
|
2709
|
+
const color = pulsing ? pulseBright ? VISUALIZER_PALETTE.head : VISUALIZER_PALETTE.bright : isFalling ? VISUALIZER_PALETTE.head : VISUALIZER_PALETTE.mid;
|
|
2710
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
2711
|
+
bold: pulsing || isFalling,
|
|
2712
|
+
color,
|
|
2713
|
+
children: ch
|
|
2714
|
+
}, x);
|
|
2715
|
+
}) }, y)) });
|
|
2716
|
+
};
|
|
2717
|
+
//#endregion
|
|
2718
|
+
//#region src/ui/tui/components/visualizer/DashboardGrid.tsx
|
|
2719
|
+
/**
|
|
2720
|
+
* DashboardGrid — dashboards phase.
|
|
2721
|
+
*
|
|
2722
|
+
* 2×2 grid of mini-charts to evoke a real PostHog dashboard. When the area
|
|
2723
|
+
* is too small, the layout collapses to 1×2 or 1×1 so something always
|
|
2724
|
+
* renders.
|
|
2725
|
+
*/
|
|
2726
|
+
const DASHBOARD_TILES = [
|
|
2727
|
+
{
|
|
2728
|
+
title: "Visitors",
|
|
2729
|
+
kind: "bars"
|
|
2730
|
+
},
|
|
2731
|
+
{
|
|
2732
|
+
title: "Sessions",
|
|
2733
|
+
kind: "line"
|
|
2734
|
+
},
|
|
2735
|
+
{
|
|
2736
|
+
title: "Revenue",
|
|
2737
|
+
kind: "gauge"
|
|
2738
|
+
},
|
|
2739
|
+
{
|
|
2740
|
+
title: "Errors",
|
|
2741
|
+
kind: "pulse"
|
|
2742
|
+
}
|
|
2743
|
+
];
|
|
2744
|
+
const SPARK_GLYPHS = "▁▂▃▄▅▆▇█";
|
|
2745
|
+
const DashboardGrid = ({ width, height }) => {
|
|
2746
|
+
const tick = useTick(220);
|
|
2747
|
+
const grid = Array.from({ length: height }, () => new Array(width).fill(" "));
|
|
2748
|
+
const cols = width >= 22 ? 2 : 1;
|
|
2749
|
+
const rows = height >= 7 ? 2 : 1;
|
|
2750
|
+
const vSplit = cols === 2 ? Math.floor(width / 2) : -1;
|
|
2751
|
+
const hSplit = rows === 2 ? Math.floor(height / 2) : -1;
|
|
2752
|
+
if (vSplit >= 0) for (let y = 0; y < height; y++) grid[y][vSplit] = "│";
|
|
2753
|
+
if (hSplit >= 0) {
|
|
2754
|
+
for (let x = 0; x < width; x++) grid[hSplit][x] = "─";
|
|
2755
|
+
if (vSplit >= 0) grid[hSplit][vSplit] = "┼";
|
|
2756
|
+
}
|
|
2757
|
+
const writeText = (x0, y0, maxW, text) => {
|
|
2758
|
+
if (y0 < 0 || y0 >= height) return;
|
|
2759
|
+
const slice = text.slice(0, Math.max(0, maxW));
|
|
2760
|
+
for (let i = 0; i < slice.length; i++) if (x0 + i >= 0 && x0 + i < width) grid[y0][x0 + i] = slice[i];
|
|
2761
|
+
};
|
|
2762
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
|
|
2763
|
+
const tile = DASHBOARD_TILES[r * cols + c];
|
|
2764
|
+
const tileX = c === 0 ? 0 : vSplit + 1;
|
|
2765
|
+
const tileY = r === 0 ? 0 : hSplit + 1;
|
|
2766
|
+
const tileW = cols === 2 ? c === 0 ? vSplit : width - vSplit - 1 : width;
|
|
2767
|
+
const tileH = rows === 2 ? r === 0 ? hSplit : height - hSplit - 1 : height;
|
|
2768
|
+
const innerX = tileX;
|
|
2769
|
+
const innerW = Math.max(1, tileW);
|
|
2770
|
+
const titleY = tileY;
|
|
2771
|
+
const valueY = tileY + tileH - 1;
|
|
2772
|
+
const chartY0 = tileY + 1;
|
|
2773
|
+
const chartY1 = Math.max(chartY0, valueY - 1);
|
|
2774
|
+
const chartH = Math.max(1, chartY1 - chartY0 + 1);
|
|
2775
|
+
const seed = (r * cols + c) * 13;
|
|
2776
|
+
writeText(innerX, titleY, innerW, tile.title);
|
|
2777
|
+
renderTile(grid, tile.kind, innerX, chartY0, innerW, chartH, tick, seed, width);
|
|
2778
|
+
writeText(innerX, valueY, innerW, tileValue(tile.kind, tick, seed));
|
|
2779
|
+
}
|
|
2780
|
+
return /* @__PURE__ */ jsx(Panel, { children: grid.map((row, y) => /* @__PURE__ */ jsx(Box, { children: row.map((ch, x) => {
|
|
2781
|
+
if (ch === " ") return /* @__PURE__ */ jsx(Text, { children: " " }, x);
|
|
2782
|
+
if (ch === "│" || ch === "─" || ch === "┼") return /* @__PURE__ */ jsx(Text, {
|
|
2783
|
+
color: MATRIX_FADE,
|
|
2784
|
+
dimColor: true,
|
|
2785
|
+
children: ch
|
|
2786
|
+
}, x);
|
|
2787
|
+
if (SPARK_GLYPHS.includes(ch) || ch === "█") return /* @__PURE__ */ jsx(Text, {
|
|
2788
|
+
color: VISUALIZER_PALETTE.mid,
|
|
2789
|
+
children: ch
|
|
2790
|
+
}, x);
|
|
2791
|
+
if (ch === "●" || ch === "∙" || ch === "·") {
|
|
2792
|
+
const color = ch === "●" ? VISUALIZER_PALETTE.head : ch === "∙" ? VISUALIZER_PALETTE.bright : VISUALIZER_PALETTE.mid;
|
|
2793
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
2794
|
+
bold: ch === "●",
|
|
2795
|
+
color,
|
|
2796
|
+
children: ch
|
|
2797
|
+
}, x);
|
|
2798
|
+
}
|
|
2799
|
+
if (ch === "▲" || ch === "▼") return /* @__PURE__ */ jsx(Text, {
|
|
2800
|
+
bold: true,
|
|
2801
|
+
color: ch === "▲" ? VISUALIZER_PALETTE.upGreen : VISUALIZER_PALETTE.deleteRed,
|
|
2802
|
+
children: ch
|
|
2803
|
+
}, x);
|
|
2804
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
2805
|
+
color: VISUALIZER_PALETTE.bright,
|
|
2806
|
+
children: ch
|
|
2807
|
+
}, x);
|
|
2808
|
+
}) }, y)) });
|
|
2809
|
+
};
|
|
2810
|
+
function renderTile(grid, kind, x0, y0, w, h, tick, seed, gridW) {
|
|
2811
|
+
const set = (x, y, ch) => {
|
|
2812
|
+
if (y < 0 || y >= grid.length) return;
|
|
2813
|
+
if (x < 0 || x >= gridW) return;
|
|
2814
|
+
grid[y][x] = ch;
|
|
2815
|
+
};
|
|
2816
|
+
if (kind === "bars") for (let i = 0; i < w; i++) {
|
|
2817
|
+
const t = tick * .18 + (i + seed) * .6;
|
|
2818
|
+
const level = .55 + .35 * Math.sin(t) + .1 * Math.sin(t * 2.7);
|
|
2819
|
+
const filled = Math.max(0, Math.min(h, Math.round(level * h)));
|
|
2820
|
+
for (let j = 0; j < filled; j++) set(x0 + i, y0 + h - 1 - j, "█");
|
|
2821
|
+
if (filled > 0 && filled < h) {
|
|
2822
|
+
const frac = level * h - Math.floor(level * h);
|
|
2823
|
+
const glyph = SPARK_GLYPHS[Math.floor(frac * 7)];
|
|
2824
|
+
set(x0 + i, y0 + h - filled, glyph);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
else if (kind === "line") {
|
|
2828
|
+
const spikeAt = ((tick / 12 | 0) + seed) % Math.max(1, w);
|
|
2829
|
+
for (let i = 0; i < w; i++) {
|
|
2830
|
+
const norm = i / Math.max(1, w - 1);
|
|
2831
|
+
const base = h - 1 - norm * (h - 1);
|
|
2832
|
+
let yf = base + .6 * Math.sin(i * .7 + seed + tick * .05);
|
|
2833
|
+
if (i === spikeAt) yf = Math.max(0, base - 1.5);
|
|
2834
|
+
const y = Math.max(0, Math.min(h - 1, Math.round(yf)));
|
|
2835
|
+
const dist = Math.abs(i - (w - 1));
|
|
2836
|
+
const ch = dist === 0 ? "●" : dist < 3 ? "∙" : "·";
|
|
2837
|
+
set(x0 + i, y0 + y, ch);
|
|
2838
|
+
}
|
|
2839
|
+
} else if (kind === "gauge") {
|
|
2840
|
+
const cycle = (tick + seed) % 40;
|
|
2841
|
+
const pct = cycle < 30 ? cycle / 30 : 1 - (cycle - 30) / 10;
|
|
2842
|
+
const midY = y0 + Math.floor(h / 2);
|
|
2843
|
+
const filled = Math.max(0, Math.min(w, Math.round(pct * w)));
|
|
2844
|
+
for (let i = 0; i < w; i++) set(x0 + i, midY, i < filled ? "█" : "·");
|
|
2845
|
+
} else if (kind === "pulse") for (let i = 0; i < w; i++) {
|
|
2846
|
+
const t = (i + seed + Math.floor(tick / 3)) % 17;
|
|
2847
|
+
let level;
|
|
2848
|
+
if (t === 0) level = .9;
|
|
2849
|
+
else if (t === 1 || t === 16) level = .5;
|
|
2850
|
+
else level = .12 + .06 * Math.sin((i + tick) * .5);
|
|
2851
|
+
const filled = Math.max(1, Math.min(h, Math.round(level * h)));
|
|
2852
|
+
for (let j = 0; j < filled; j++) set(x0 + i, y0 + h - 1 - j, "█");
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
function tileValue(kind, tick, seed) {
|
|
2856
|
+
const wave = .5 + .5 * Math.sin(tick * .05 + seed);
|
|
2857
|
+
if (kind === "bars") return `${(Math.round(8e3 + wave * 6e3) / 1e3).toFixed(1)}k ▲`;
|
|
2858
|
+
if (kind === "line") return `${(2 + wave * 4).toFixed(1)}% ▲`;
|
|
2859
|
+
if (kind === "gauge") return `$${Math.round(20 + wave * 60)}k`;
|
|
2860
|
+
return `${Math.round(1 + wave * 5)} ▼`;
|
|
2861
|
+
}
|
|
2862
|
+
//#endregion
|
|
2863
|
+
//#region src/ui/tui/components/PhaseVisuals.tsx
|
|
2864
|
+
/**
|
|
2865
|
+
* Phase visuals — ambient ASCII visualizations of what the agent is currently
|
|
2866
|
+
* doing. The active phase is derived from the in-progress task labels (best-
|
|
2867
|
+
* effort heuristic). Each phase has its own component (under ./visualizer);
|
|
2868
|
+
* the orchestrator `PhaseVisual` picks which one to render.
|
|
2869
|
+
*
|
|
2870
|
+
* Width and height are passed in from the parent so visuals adapt to the
|
|
2871
|
+
* column they're placed in.
|
|
2872
|
+
*/
|
|
2873
|
+
const PHASE_LABELS = {
|
|
2874
|
+
["codebase-scan"]: "Reading the Code",
|
|
2875
|
+
["skill-install"]: "Picking the Right Skill",
|
|
2876
|
+
["dep-install"]: "Installing Packages",
|
|
2877
|
+
["code-edits"]: "Editing Source",
|
|
2878
|
+
["env-setup"]: "Wiring Up Secrets",
|
|
2879
|
+
["dashboards"]: "Building Dashboards"
|
|
2880
|
+
};
|
|
2881
|
+
/** Reads the active phase from the store. The agent loop pushes it in via
|
|
2882
|
+
* `getUI().setStage(...)` whenever a new tool fires. */
|
|
2883
|
+
function useAgentPhase(store) {
|
|
2884
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
2885
|
+
return store.currentStage?.stage ?? "codebase-scan";
|
|
2886
|
+
}
|
|
2887
|
+
/**
|
|
2888
|
+
* VisualizerTab — Winamp-style fullscreen take on the phase visual.
|
|
2889
|
+
* "NOW PLAYING" header, centered visual, transport bar with elapsed time.
|
|
2890
|
+
*
|
|
2891
|
+
* Sizes itself off the *measured* container rather than the raw terminal so
|
|
2892
|
+
* the tab bar / hints / status panel above don't get pushed off-screen on
|
|
2893
|
+
* short terminals. When height runs out, chrome rows drop in order:
|
|
2894
|
+
* transport bar first, then track title, then the NOW PLAYING header.
|
|
2895
|
+
*/
|
|
2896
|
+
const VisualizerTab = ({ store }) => {
|
|
2897
|
+
const phase = useAgentPhase(store);
|
|
2898
|
+
const containerRef = useRef(null);
|
|
2899
|
+
const [size, setSize] = useState({
|
|
2900
|
+
width: 0,
|
|
2901
|
+
height: 0
|
|
2902
|
+
});
|
|
2903
|
+
useEffect(() => {
|
|
2904
|
+
if (!containerRef.current) return;
|
|
2905
|
+
const m = measureElement(containerRef.current);
|
|
2906
|
+
if (m.width !== size.width || m.height !== size.height) setSize({
|
|
2907
|
+
width: m.width,
|
|
2908
|
+
height: m.height
|
|
2909
|
+
});
|
|
2910
|
+
}, [
|
|
2911
|
+
useTick(EQ_TICK_MS),
|
|
2912
|
+
size.width,
|
|
2913
|
+
size.height
|
|
2914
|
+
]);
|
|
2915
|
+
const HEADER_ROWS = 2;
|
|
2916
|
+
const TITLE_ROWS = 2;
|
|
2917
|
+
const TRANSPORT_ROWS = 2;
|
|
2918
|
+
const BORDER_ROWS = 2;
|
|
2919
|
+
const MIN_VISUAL_H = 5;
|
|
2920
|
+
const availH = size.height;
|
|
2921
|
+
const availW = size.width;
|
|
2922
|
+
let chromeBudget = BORDER_ROWS + HEADER_ROWS + TITLE_ROWS + TRANSPORT_ROWS;
|
|
2923
|
+
let showHeader = true;
|
|
2924
|
+
let showTitle = true;
|
|
2925
|
+
let showTransport = true;
|
|
2926
|
+
if (availH > 0 && availH - chromeBudget < MIN_VISUAL_H) {
|
|
2927
|
+
showTransport = false;
|
|
2928
|
+
chromeBudget -= TRANSPORT_ROWS;
|
|
2929
|
+
}
|
|
2930
|
+
if (availH > 0 && availH - chromeBudget < MIN_VISUAL_H) {
|
|
2931
|
+
showTitle = false;
|
|
2932
|
+
chromeBudget -= TITLE_ROWS;
|
|
2933
|
+
}
|
|
2934
|
+
if (availH > 0 && availH - chromeBudget < MIN_VISUAL_H) {
|
|
2935
|
+
showHeader = false;
|
|
2936
|
+
chromeBudget -= HEADER_ROWS;
|
|
2937
|
+
}
|
|
2938
|
+
const visualH = availH > 0 ? Math.max(MIN_VISUAL_H, Math.min(18, availH - chromeBudget)) : MIN_VISUAL_H;
|
|
2939
|
+
const visualW = availW > 0 ? Math.max(20, Math.min(64, availW - 12)) : 40;
|
|
2940
|
+
const now = Date.now();
|
|
2941
|
+
const beatTimeMs = now - (store.currentStage?.startedAt ?? now);
|
|
2942
|
+
const timeStr = formatElapsed(Math.max(0, Math.floor(beatTimeMs / 1e3)));
|
|
2943
|
+
const equalizer = renderMiniEqualizer(beatTimeMs, useRef(new Array(EQ_BARS).fill(0)).current);
|
|
2944
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
2945
|
+
ref: containerRef,
|
|
2946
|
+
flexDirection: "column",
|
|
2947
|
+
flexGrow: 1,
|
|
2948
|
+
alignItems: "center",
|
|
2949
|
+
justifyContent: "center",
|
|
2950
|
+
overflow: "hidden",
|
|
2951
|
+
children: [
|
|
2952
|
+
showHeader && /* @__PURE__ */ jsxs(Box, {
|
|
2953
|
+
flexDirection: "row",
|
|
2954
|
+
marginBottom: 1,
|
|
2955
|
+
children: [
|
|
2956
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2957
|
+
color: MATRIX_FADE,
|
|
2958
|
+
children: "┌─"
|
|
2959
|
+
}),
|
|
2960
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2961
|
+
bold: true,
|
|
2962
|
+
color: VISUALIZER_PALETTE.bright,
|
|
2963
|
+
children: " ► NOW PLAYING "
|
|
2964
|
+
}),
|
|
2965
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2966
|
+
color: MATRIX_FADE,
|
|
2967
|
+
children: "─┐"
|
|
2968
|
+
})
|
|
2969
|
+
]
|
|
2970
|
+
}),
|
|
2971
|
+
showTitle && /* @__PURE__ */ jsx(Box, {
|
|
2972
|
+
marginBottom: 1,
|
|
2973
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
2974
|
+
bold: true,
|
|
2975
|
+
color: VISUALIZER_PALETTE.head,
|
|
2976
|
+
children: [PHASE_LABELS[phase], " - PostHog"]
|
|
2977
|
+
})
|
|
2978
|
+
}),
|
|
2979
|
+
/* @__PURE__ */ jsx(PhaseBody, {
|
|
2980
|
+
phase,
|
|
2981
|
+
width: visualW,
|
|
2982
|
+
height: visualH
|
|
2983
|
+
}),
|
|
2984
|
+
showTransport && /* @__PURE__ */ jsxs(Box, {
|
|
2985
|
+
flexDirection: "row",
|
|
2986
|
+
marginTop: 1,
|
|
2987
|
+
gap: 2,
|
|
2988
|
+
children: [
|
|
2989
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
2990
|
+
color: VISUALIZER_PALETTE.mid,
|
|
2991
|
+
children: [
|
|
2992
|
+
"[",
|
|
2993
|
+
timeStr,
|
|
2994
|
+
"]"
|
|
2995
|
+
]
|
|
2996
|
+
}),
|
|
2997
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2998
|
+
color: MATRIX_FADE,
|
|
2999
|
+
children: equalizer
|
|
3000
|
+
}),
|
|
3001
|
+
/* @__PURE__ */ jsx(Text, {
|
|
3002
|
+
color: VISUALIZER_PALETTE.mid,
|
|
3003
|
+
children: "WizardAmp"
|
|
3004
|
+
})
|
|
3005
|
+
]
|
|
3006
|
+
})
|
|
3007
|
+
]
|
|
3008
|
+
});
|
|
3009
|
+
};
|
|
3010
|
+
function formatElapsed(totalSec) {
|
|
3011
|
+
const m = Math.floor(totalSec / 60);
|
|
3012
|
+
const s = totalSec % 60;
|
|
3013
|
+
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
3014
|
+
}
|
|
3015
|
+
const BEAT_MS = 60 / 120 * 1e3;
|
|
3016
|
+
const EQ_BARS = 12;
|
|
3017
|
+
const EQ_TICK_MS = 67;
|
|
3018
|
+
const EQ_FALL_PER_FRAME = .08;
|
|
3019
|
+
function pulse(t, periodMs, decay, phaseMs = 0) {
|
|
3020
|
+
const since = (t - phaseMs + periodMs * 100) % periodMs;
|
|
3021
|
+
return Math.exp(-decay * since / periodMs);
|
|
3022
|
+
}
|
|
3023
|
+
const EQ_GLYPHS = "▁▂▃▄▅▆▇█";
|
|
3024
|
+
function renderMiniEqualizer(beatTimeMs, levels) {
|
|
3025
|
+
const kick = pulse(beatTimeMs, BEAT_MS, 4);
|
|
3026
|
+
const bass = pulse(beatTimeMs, BEAT_MS, 1.5);
|
|
3027
|
+
const clap = pulse(beatTimeMs, BEAT_MS * 2, 5, BEAT_MS);
|
|
3028
|
+
const hat8 = pulse(beatTimeMs, BEAT_MS / 2, 8);
|
|
3029
|
+
const hat16 = pulse(beatTimeMs, BEAT_MS / 4, 12);
|
|
3030
|
+
const pad = .2 + .15 * Math.sin(2 * Math.PI * beatTimeMs / 4e3);
|
|
3031
|
+
const mix = [
|
|
3032
|
+
bass * .7 + kick * .5 + pad,
|
|
3033
|
+
bass * .6 + kick * .6 + pad * .9,
|
|
3034
|
+
bass * .4 + kick * .7 + pad * .8,
|
|
3035
|
+
kick * .6 + clap * .3 + pad * .7,
|
|
3036
|
+
kick * .4 + clap * .5 + pad * .7,
|
|
3037
|
+
clap * .7 + hat8 * .3 + pad * .6,
|
|
3038
|
+
clap * .6 + hat8 * .5 + pad * .5,
|
|
3039
|
+
hat8 * .7 + clap * .3 + pad * .5,
|
|
3040
|
+
hat8 * .5 + hat16 * .4 + pad * .4,
|
|
3041
|
+
hat8 * .4 + hat16 * .5 + pad * .4,
|
|
3042
|
+
hat16 * .6 + hat8 * .3 + pad * .3,
|
|
3043
|
+
hat16 * .7 + pad * .3
|
|
3044
|
+
];
|
|
3045
|
+
let out = "";
|
|
3046
|
+
for (let i = 0; i < EQ_BARS; i++) {
|
|
3047
|
+
const target = Math.min(1, mix[i]);
|
|
3048
|
+
levels[i] = Math.max(target, levels[i] - EQ_FALL_PER_FRAME);
|
|
3049
|
+
out += EQ_GLYPHS[Math.floor(levels[i] * 7)];
|
|
3050
|
+
}
|
|
3051
|
+
return out;
|
|
3052
|
+
}
|
|
3053
|
+
const PhaseBody = ({ phase, width, height }) => {
|
|
3054
|
+
switch (phase) {
|
|
3055
|
+
case "codebase-scan": return /* @__PURE__ */ jsx(MatrixRain, {
|
|
3056
|
+
width,
|
|
3057
|
+
height
|
|
3058
|
+
});
|
|
3059
|
+
case "skill-install": return /* @__PURE__ */ jsx(LibraryShelf, {
|
|
3060
|
+
width,
|
|
3061
|
+
height
|
|
3062
|
+
});
|
|
3063
|
+
case "dep-install": return /* @__PURE__ */ jsx(CrateStack, {
|
|
3064
|
+
width,
|
|
3065
|
+
height
|
|
3066
|
+
});
|
|
3067
|
+
case "code-edits": return /* @__PURE__ */ jsx(DiffCascade, {
|
|
3068
|
+
width,
|
|
3069
|
+
height
|
|
3070
|
+
});
|
|
3071
|
+
case "env-setup": return /* @__PURE__ */ jsx(Tumblers, {
|
|
3072
|
+
width,
|
|
3073
|
+
height
|
|
3074
|
+
});
|
|
3075
|
+
case "dashboards": return /* @__PURE__ */ jsx(DashboardGrid, {
|
|
3076
|
+
width,
|
|
3077
|
+
height
|
|
3078
|
+
});
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
3081
|
+
//#endregion
|
|
2160
3082
|
//#region src/ui/tui/components/ServiceHealthList.tsx
|
|
2161
3083
|
/**
|
|
2162
3084
|
* ServiceHealthList — Shared component for displaying service health status.
|
|
@@ -5405,6 +6327,27 @@ const OutroScreen = ({ store }) => {
|
|
|
5405
6327
|
bold: true,
|
|
5406
6328
|
children: ["✔ ", outroData.message || "Done!"]
|
|
5407
6329
|
}),
|
|
6330
|
+
outroData.primaryLink && /* @__PURE__ */ jsx(Box, {
|
|
6331
|
+
marginTop: 1,
|
|
6332
|
+
children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
6333
|
+
outroData.primaryLink.label,
|
|
6334
|
+
":",
|
|
6335
|
+
" ",
|
|
6336
|
+
/* @__PURE__ */ jsx(Text, {
|
|
6337
|
+
color: "cyan",
|
|
6338
|
+
children: outroData.primaryLink.url
|
|
6339
|
+
})
|
|
6340
|
+
] })
|
|
6341
|
+
}),
|
|
6342
|
+
outroData.nextSteps && outroData.nextSteps.items.length > 0 && /* @__PURE__ */ jsxs(Box, {
|
|
6343
|
+
flexDirection: "column",
|
|
6344
|
+
marginTop: 1,
|
|
6345
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
6346
|
+
color: "cyan",
|
|
6347
|
+
bold: true,
|
|
6348
|
+
children: outroData.nextSteps.heading
|
|
6349
|
+
}), outroData.nextSteps.items.map((item, i) => /* @__PURE__ */ jsxs(Text, { children: ["• ", item] }, i))]
|
|
6350
|
+
}),
|
|
5408
6351
|
outroData.dashboardUrl && /* @__PURE__ */ jsx(Box, {
|
|
5409
6352
|
marginTop: 1,
|
|
5410
6353
|
children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
@@ -5850,6 +6793,29 @@ const AiOptInRequiredScreen = ({ store }) => {
|
|
|
5850
6793
|
});
|
|
5851
6794
|
};
|
|
5852
6795
|
//#endregion
|
|
5853
|
-
|
|
6796
|
+
//#region src/ui/tui/terminal.ts
|
|
6797
|
+
/**
|
|
6798
|
+
* Terminal setup shared by the wizard TUI and the primitives playground.
|
|
6799
|
+
*
|
|
6800
|
+
* Both enter the alternate screen buffer and paint a black background so
|
|
6801
|
+
* Ink renders on a consistent dark canvas regardless of the user's terminal
|
|
6802
|
+
* theme (light mode profiles included).
|
|
6803
|
+
*/
|
|
6804
|
+
const RESET_ATTRS = "\x1B[0m";
|
|
6805
|
+
const CLEAR_SCREEN = "\x1B[2J";
|
|
6806
|
+
const CURSOR_HOME = "\x1B[H";
|
|
6807
|
+
const BG_BLACK = "\x1B[48;2;0;0;0m";
|
|
6808
|
+
const ENTER_ALT_SCREEN = "\x1B[?1049h";
|
|
6809
|
+
const LEAVE_ALT_SCREEN = "\x1B[?1049l";
|
|
6810
|
+
/** Enter alt screen and paint a black background. */
|
|
6811
|
+
function enterDarkTerminal() {
|
|
6812
|
+
process.stdout.write(ENTER_ALT_SCREEN + BG_BLACK + CLEAR_SCREEN + CURSOR_HOME);
|
|
6813
|
+
}
|
|
6814
|
+
/** Leave alt screen and reset attributes. */
|
|
6815
|
+
function releaseTerminal() {
|
|
6816
|
+
process.stdout.write(RESET_ATTRS + LEAVE_ALT_SCREEN);
|
|
6817
|
+
}
|
|
6818
|
+
//#endregion
|
|
6819
|
+
export { ConfirmationInput as A, TabContainer as C, LinkText as D, LogViewer as E, SplitView as F, CardLayout as I, WizardStore as L, 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 };
|
|
5854
6820
|
|
|
5855
|
-
//# sourceMappingURL=
|
|
6821
|
+
//# sourceMappingURL=terminal-BSiupnOQ.js.map
|