@posthog/wizard 2.78.0 → 2.79.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 (163) hide show
  1. package/README.md +20 -11
  2. package/dist/PickerMenu-dG5DQCZA.js +758 -0
  3. package/dist/PickerMenu-dG5DQCZA.js.map +1 -0
  4. package/dist/{add-mcp-server-to-clients-JMv8LP-O.js → add-mcp-server-to-clients-DIaTRWMY.js} +7 -18
  5. package/dist/{add-mcp-server-to-clients-JMv8LP-O.js.map → add-mcp-server-to-clients-DIaTRWMY.js.map} +1 -1
  6. package/dist/add-mcp-server-to-clients-artmN7mz.js +2 -0
  7. package/dist/agent-BEjARD2a.js +6 -0
  8. package/dist/{agent-bCZ3i3fh.js → agent-BWFhuLgo.js} +131 -38
  9. package/dist/agent-BWFhuLgo.js.map +1 -0
  10. package/dist/{agent-interface-BAOIHuXS.js → agent-interface-FkbaBJwz.js} +66 -28
  11. package/dist/agent-interface-FkbaBJwz.js.map +1 -0
  12. package/dist/{agent-prompt-loader-7Pd4CfZO.js → agent-prompt-loader-D0weQ-7k.js} +5 -5
  13. package/dist/{agent-prompt-loader-7Pd4CfZO.js.map → agent-prompt-loader-D0weQ-7k.js.map} +1 -1
  14. package/dist/analytics-DFtbRjYh.js +2 -0
  15. package/dist/{analytics-CHCZ_qUR.js → analytics-DXK1gc12.js} +4 -4
  16. package/dist/{analytics-CHCZ_qUR.js.map → analytics-DXK1gc12.js.map} +1 -1
  17. package/dist/api-Cm9UtKZh.js +2 -0
  18. package/dist/{api-Diy_2fp8.js → api-CrbCIQOz.js} +4 -16
  19. package/dist/{api-Diy_2fp8.js.map → api-CrbCIQOz.js.map} +1 -1
  20. package/dist/bin.js +5 -2957
  21. package/dist/bin.js.map +1 -1
  22. package/dist/{bounded-fs-Bx9zTe6m.js → bounded-fs-vAq-T-K7.js} +3 -3
  23. package/dist/{bounded-fs-Bx9zTe6m.js.map → bounded-fs-vAq-T-K7.js.map} +1 -1
  24. package/dist/{ci-install-CDdQYsPe.js → ci-install-COcV8SY2.js} +8 -7
  25. package/dist/ci-install-COcV8SY2.js.map +1 -0
  26. package/dist/{constants-jFI2SaE1.js → constants-DHCBSfnx.js} +3 -3
  27. package/dist/{constants-jFI2SaE1.js.map → constants-DHCBSfnx.js.map} +1 -1
  28. package/dist/{debug-M2gV2iDq.js → debug-BuYHGr6_.js} +1 -1
  29. package/dist/{debug-Ch4BDOtD.js → debug-kAHwWGg7.js} +2 -2
  30. package/dist/{debug-Ch4BDOtD.js.map → debug-kAHwWGg7.js.map} +1 -1
  31. package/dist/{defaults-C6J7yL4O.js → defaults-a9TelDhJ.js} +1 -1
  32. package/dist/{defaults-C6J7yL4O.js.map → defaults-a9TelDhJ.js.map} +1 -1
  33. package/dist/emit-BSetXzDV.js +69 -0
  34. package/dist/emit-BSetXzDV.js.map +1 -0
  35. package/dist/{endpoints-DCVbM06L.js → endpoints-BVMYm9F4.js} +3 -3
  36. package/dist/{endpoints-DCVbM06L.js.map → endpoints-BVMYm9F4.js.map} +1 -1
  37. package/dist/{env-api-key-Qx8NnYHm.js → env-api-key-BP-NFFXt.js} +3 -5
  38. package/dist/{env-api-key-Qx8NnYHm.js.map → env-api-key-BP-NFFXt.js.map} +1 -1
  39. package/dist/env-api-key-CaWEDj_9.js +2 -0
  40. package/dist/{environment-B07Bmxae.js → environment-BATlQxcQ.js} +5 -10
  41. package/dist/{environment-B07Bmxae.js.map → environment-BATlQxcQ.js.map} +1 -1
  42. package/dist/environment-CwD8OPYj.js +2 -0
  43. package/dist/errors-C2domSkK.js +79 -0
  44. package/dist/errors-C2domSkK.js.map +1 -0
  45. package/dist/file-C5JMsyR1.js +2 -0
  46. package/dist/{file-BcLTjU16.js → file-DcRKycV8.js} +4 -4
  47. package/dist/file-DcRKycV8.js.map +1 -0
  48. package/dist/{file-watcher-DWc_bTs6.js → file-watcher-Brq2nnHV.js} +2 -2
  49. package/dist/{file-watcher-DWc_bTs6.js.map → file-watcher-Brq2nnHV.js.map} +1 -1
  50. package/dist/{gateway-CjoShdLa.js → gateway-C9-GlLO9.js} +39 -8
  51. package/dist/gateway-C9-GlLO9.js.map +1 -0
  52. package/dist/{headless-ui-BFFmYC4v.js → headless-ui-CqI2gXlD.js} +2 -2
  53. package/dist/{headless-ui-BFFmYC4v.js.map → headless-ui-CqI2gXlD.js.map} +1 -1
  54. package/dist/{interactive-D6zV7brJ.js → interactive-B_Ti_CsS.js} +3 -3
  55. package/dist/interactive-B_Ti_CsS.js.map +1 -0
  56. package/dist/{logging-ui-_TwoMNbG.js → logging-ui-Dza4D0MK.js} +5 -5
  57. package/dist/{logging-ui-_TwoMNbG.js.map → logging-ui-Dza4D0MK.js.map} +1 -1
  58. package/dist/main-CgKxbwMP.js +1396 -0
  59. package/dist/main-CgKxbwMP.js.map +1 -0
  60. package/dist/{mcp-DAgaoGk5.js → mcp-DnAyW0-w.js} +4 -4
  61. package/dist/{mcp-DAgaoGk5.js.map → mcp-DnAyW0-w.js.map} +1 -1
  62. package/dist/{mcp-prompt-streaming-I2U_X7BC.js → mcp-prompt-streaming-DDWQ-FCA.js} +6 -6
  63. package/dist/mcp-prompt-streaming-DDWQ-FCA.js.map +1 -0
  64. package/dist/{mint-failure-D4nd2XmZ.js → mint-failure-u8xQKNOn.js} +689 -126
  65. package/dist/mint-failure-u8xQKNOn.js.map +1 -0
  66. package/dist/{models-DDdBJKwT.js → models-WcwUnSKb.js} +2 -2
  67. package/dist/{models-DDdBJKwT.js.map → models-WcwUnSKb.js.map} +1 -1
  68. package/dist/{non-interactive-Dzow7YKY.js → non-interactive-DyVC1AGc.js} +4 -3
  69. package/dist/{non-interactive-Dzow7YKY.js.map → non-interactive-DyVC1AGc.js.map} +1 -1
  70. package/dist/{oauth-session-DJ8h6GFa.js → oauth-session-DYM2Hrl2.js} +2 -2
  71. package/dist/{oauth-session-DJ8h6GFa.js.map → oauth-session-DYM2Hrl2.js.map} +1 -1
  72. package/dist/{orchestrator-tools-szcdscwn.js → orchestrator-tools-DbgNBFGA.js} +3 -3
  73. package/dist/{orchestrator-tools-szcdscwn.js.map → orchestrator-tools-DbgNBFGA.js.map} +1 -1
  74. package/dist/{package-json-DY70kkNp.js → package-json-Atg34fa4.js} +2 -8
  75. package/dist/{package-json-DY70kkNp.js.map → package-json-Atg34fa4.js.map} +1 -1
  76. package/dist/package-json-D8Ga03tK.js +2 -0
  77. package/dist/{pi-D3h5Ok0i.js → pi-8KsYsPmM.js} +145 -45
  78. package/dist/pi-8KsYsPmM.js.map +1 -0
  79. package/dist/{playground-5AB4WgNF.js → playground-BlYRBMj7.js} +11 -11
  80. package/dist/playground-BlYRBMj7.js.map +1 -0
  81. package/dist/{posthog-DNn574nW.js → posthog-Bt0Aplb8.js} +4 -9
  82. package/dist/posthog-Bt0Aplb8.js.map +1 -0
  83. package/dist/posthog-CUgCEhH0.js +2 -0
  84. package/dist/{provisioning-B8uSqRZk.js → provisioning-Bo2NRxFz.js} +5 -11
  85. package/dist/{provisioning-B8uSqRZk.js.map → provisioning-Bo2NRxFz.js.map} +1 -1
  86. package/dist/provisioning-OGFGlzf7.js +2 -0
  87. package/dist/{queue-tools-9OOVx5TE.js → queue-tools-C8mXOh7e.js} +3 -3
  88. package/dist/{queue-tools-9OOVx5TE.js.map → queue-tools-C8mXOh7e.js.map} +1 -1
  89. package/dist/registry-C06e_x-p.js +2 -0
  90. package/dist/{registry-8YG-kuXk.js → registry-CD5RQt38.js} +51 -53
  91. package/dist/registry-CD5RQt38.js.map +1 -0
  92. package/dist/run-agent-legacy-ChacZImU.js +2 -0
  93. package/dist/{run-agent-legacy-d7rWlzQR.js → run-agent-legacy-DOlM6DQu.js} +823 -1022
  94. package/dist/run-agent-legacy-DOlM6DQu.js.map +1 -0
  95. package/dist/runners-ov3EXF1n.js +834 -0
  96. package/dist/runners-ov3EXF1n.js.map +1 -0
  97. package/dist/{security-Cg_4a7vB.js → security-C71pkwqH.js} +16 -6
  98. package/dist/security-C71pkwqH.js.map +1 -0
  99. package/dist/{setup-utils-CmTCTPkX.js → setup-utils-B26Lf2yF.js} +12 -22
  100. package/dist/setup-utils-B26Lf2yF.js.map +1 -0
  101. package/dist/setup-utils-CVS86NYR.js +2 -0
  102. package/dist/{start-tui-CB2PZs1x.js → start-tui-Bo2L0bar.js} +30 -28
  103. package/dist/start-tui-Bo2L0bar.js.map +1 -0
  104. package/dist/{steps-BZcXRJNY.js → steps-BPcg3_3Q.js} +7 -7
  105. package/dist/{steps-BZcXRJNY.js.map → steps-BPcg3_3Q.js.map} +1 -1
  106. package/dist/{store-UN7c-ASf.js → store-BTzcvRGT.js} +12 -18
  107. package/dist/store-BTzcvRGT.js.map +1 -0
  108. package/dist/store-BpqySPXm.js +5 -0
  109. package/dist/{subagent-BofIDK-O.js → subagent-D1TrTchE.js} +3 -3
  110. package/dist/{subagent-BofIDK-O.js.map → subagent-D1TrTchE.js.map} +1 -1
  111. package/dist/{task-r9t1UhQP.js → task-DqRlQBfs.js} +18 -16
  112. package/dist/task-DqRlQBfs.js.map +1 -0
  113. package/dist/{task-stream-CMUilu54.js → task-stream-Bw6JaLCX.js} +10 -10
  114. package/dist/task-stream-Bw6JaLCX.js.map +1 -0
  115. package/dist/{tasks-Bbv_lOBG.js → tasks-ChWufsdO.js} +1 -1
  116. package/dist/{tasks-Bbv_lOBG.js.map → tasks-ChWufsdO.js.map} +1 -1
  117. package/dist/{telemetry-D6ruCtnv.js → telemetry-B7zJdRmA.js} +3 -3
  118. package/dist/{telemetry-D6ruCtnv.js.map → telemetry-B7zJdRmA.js.map} +1 -1
  119. package/dist/{terminal-DrGyci_a.js → terminal-zgkEfoCL.js} +17 -17
  120. package/dist/terminal-zgkEfoCL.js.map +1 -0
  121. package/dist/{tools-Dyfh9aCD.js → tools-CkJ2y9Wt.js} +9 -9
  122. package/dist/tools-CkJ2y9Wt.js.map +1 -0
  123. package/dist/{ui-BBVuTM1v.js → ui-Q6-RvERF.js} +3 -3
  124. package/dist/{ui-BBVuTM1v.js.map → ui-Q6-RvERF.js.map} +1 -1
  125. package/dist/{version-aXxpgRKx.js → version-CHhJjz_H.js} +2 -2
  126. package/dist/{version-aXxpgRKx.js.map → version-CHhJjz_H.js.map} +1 -1
  127. package/dist/{wizard-abort-BBkiUxfS.js → wizard-abort-BPdv07np.js} +7 -6
  128. package/dist/{wizard-abort-BBkiUxfS.js.map → wizard-abort-BPdv07np.js.map} +1 -1
  129. package/dist/wizard-abort-Br1qMDNa.js +3 -0
  130. package/dist/wizard-session-2w4Ru-Wz.js.map +1 -1
  131. package/dist/{wizard-ui-DoyOZxK8.js → wizard-ui-DVqrYaOM.js} +1 -1
  132. package/dist/{wizard-ui-DoyOZxK8.js.map → wizard-ui-DVqrYaOM.js.map} +1 -1
  133. package/dist/{yara-hooks-DCeMDd1o.js → yara-hooks-BnxejtXK.js} +5 -5
  134. package/dist/{yara-hooks-DCeMDd1o.js.map → yara-hooks-BnxejtXK.js.map} +1 -1
  135. package/dist/yara-hooks-DSuExwb4.js +2 -0
  136. package/package.json +2 -1
  137. package/dist/agent-bCZ3i3fh.js.map +0 -1
  138. package/dist/agent-interface-BAOIHuXS.js.map +0 -1
  139. package/dist/analytics-BcV-WCM4.js +0 -2
  140. package/dist/ci-install-CDdQYsPe.js.map +0 -1
  141. package/dist/errors-CQ9kttJD.js +0 -185
  142. package/dist/errors-CQ9kttJD.js.map +0 -1
  143. package/dist/file-BcLTjU16.js.map +0 -1
  144. package/dist/gateway-CjoShdLa.js.map +0 -1
  145. package/dist/interactive-D6zV7brJ.js.map +0 -1
  146. package/dist/mcp-prompt-streaming-I2U_X7BC.js.map +0 -1
  147. package/dist/mint-failure-D4nd2XmZ.js.map +0 -1
  148. package/dist/pi-D3h5Ok0i.js.map +0 -1
  149. package/dist/playground-5AB4WgNF.js.map +0 -1
  150. package/dist/posthog-DNn574nW.js.map +0 -1
  151. package/dist/registry-8YG-kuXk.js.map +0 -1
  152. package/dist/rolldown-runtime-B_-DWIq7.js +0 -15
  153. package/dist/run-agent-legacy-d7rWlzQR.js.map +0 -1
  154. package/dist/security-Cg_4a7vB.js.map +0 -1
  155. package/dist/setup-utils-CmTCTPkX.js.map +0 -1
  156. package/dist/start-tui-CB2PZs1x.js.map +0 -1
  157. package/dist/store-UN7c-ASf.js.map +0 -1
  158. package/dist/task-r9t1UhQP.js.map +0 -1
  159. package/dist/task-stream-CMUilu54.js.map +0 -1
  160. package/dist/terminal-DrGyci_a.js.map +0 -1
  161. package/dist/tools-Dyfh9aCD.js.map +0 -1
  162. package/dist/wizard-abort-BU4aLyvh.js +0 -3
  163. package/dist/yara-hooks-DSGuCU9A.js +0 -2
@@ -0,0 +1,758 @@
1
+ import { $ as wordWrap, it as Icons, rt as Colors } from "./mint-failure-u8xQKNOn.js";
2
+ import { Box, Text, useInput, useStdout } from "ink";
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import Fuse from "fuse.js";
6
+ //#region src/ui/tui/primitives/picker-filter.ts
7
+ /**
8
+ * Filter-as-you-type matching for PickerMenu. Split out from the component so the
9
+ * ranking is testable on its own — the component only decides when to call it.
10
+ */
11
+ /**
12
+ * Fuzzy fallback config. `ignoreLocation` because a term matching at the end of a
13
+ * description is as good as one at the start, and Fuse's default distance window
14
+ * would score the late match away. 0.5 is the loosest threshold that still bottoms
15
+ * out: below it `gthb` misses `GitHub` entirely, which is the whole point of having
16
+ * a fuzzy pass; above it the tail grows without surfacing anything new at the top.
17
+ */
18
+ const FUSE_OPTIONS = {
19
+ keys: [
20
+ {
21
+ name: "label",
22
+ weight: 3
23
+ },
24
+ {
25
+ name: "hint",
26
+ weight: 1
27
+ },
28
+ {
29
+ name: "description",
30
+ weight: 1
31
+ }
32
+ ],
33
+ ignoreLocation: true,
34
+ threshold: .5,
35
+ includeScore: true
36
+ };
37
+ function buildPickerIndex(options) {
38
+ return new Fuse(options, FUSE_OPTIONS);
39
+ }
40
+ function haystack(option) {
41
+ return `${option.label} ${option.hint ?? ""} ${option.description ?? ""}`.toLowerCase();
42
+ }
43
+ /**
44
+ * Rank options against a query, best first, or `null` for an empty query.
45
+ *
46
+ * Two passes, literal before fuzzy. Anything that literally contains every
47
+ * whitespace-separated term wins outright, in the list's own order — that covers how
48
+ * people actually type (`git iss` → `GitHub issues`) and keeps the common case as
49
+ * tight as a plain substring filter, which fuzzy matching on its own is not: Fuse at
50
+ * a threshold loose enough to forgive `gthb` also lets a third of the list through
51
+ * on `replay`.
52
+ *
53
+ * Only when nothing matches literally does Fuse get a turn, so a typo degrades to a
54
+ * ranked guess instead of an empty list. Terms are AND-ed there too, each matched
55
+ * fuzzily on its own — Fuse's bitap pass over the whole query would otherwise need
56
+ * `git iss` to be a near-edit of the text it's matching. Scores are distances (0 is
57
+ * exact) and sum across terms, hence the ascending sort; ties keep the order the
58
+ * first term put them in, since the sort is stable.
59
+ */
60
+ function rankOptions(fuse, options, query) {
61
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
62
+ if (!terms.length) return null;
63
+ const literal = options.filter((option) => terms.every((term) => haystack(option).includes(term)));
64
+ if (literal.length) return literal;
65
+ let surviving = null;
66
+ for (const term of terms) {
67
+ const hits = new Map(fuse.search(term).map(({ item, score }) => [item, score ?? 0]));
68
+ if (surviving === null) {
69
+ surviving = hits;
70
+ continue;
71
+ }
72
+ for (const [option, total] of surviving) {
73
+ const score = hits.get(option);
74
+ if (score === void 0) surviving.delete(option);
75
+ else surviving.set(option, total + score);
76
+ }
77
+ }
78
+ return [...surviving ?? []].sort(([, a], [, b]) => a - b).map(([option]) => option);
79
+ }
80
+ //#endregion
81
+ //#region src/ui/tui/primitives/PromptLabel.tsx
82
+ /**
83
+ * PromptLabel — Compact inline label for input prompts.
84
+ *
85
+ * Renders: [!] message
86
+ * where [!] is black text on accent background.
87
+ */
88
+ const PromptLabel = ({ message }) => {
89
+ if (!message) return null;
90
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, {
91
+ bold: true,
92
+ color: Colors.accent,
93
+ children: [" ", message]
94
+ }) });
95
+ };
96
+ //#endregion
97
+ //#region src/ui/tui/primitives/ConfirmButton.tsx
98
+ /**
99
+ * ConfirmButton — the confirm row used to submit a selection.
100
+ *
101
+ * Pure render. Multi-select menus (PickerMenu mode="multi", GroupedPickerMenu)
102
+ * append this below their options as the final focusable row: the user toggles
103
+ * options with enter, then arrows down onto this row and presses enter to
104
+ * submit. This replaces the older "enter anywhere submits" pattern, which
105
+ * confused people who expected enter to toggle the focused item.
106
+ *
107
+ * Renders flat, mirroring the option rows — a focus triangle and the label,
108
+ * accent and bold when focused, dimmed otherwise — so it lines up under the
109
+ * options instead of sitting in a separate boxed target.
110
+ */
111
+ const ConfirmButton = ({ label = "Confirm", focused, count }) => {
112
+ const text = count && count > 0 ? `${label} (${count})` : label;
113
+ return /* @__PURE__ */ jsxs(Text, {
114
+ color: focused ? Colors.accent : void 0,
115
+ bold: focused,
116
+ dimColor: !focused,
117
+ children: [
118
+ focused ? Icons.triangleSmallRight : " ",
119
+ " ",
120
+ text
121
+ ]
122
+ });
123
+ };
124
+ //#endregion
125
+ //#region src/ui/tui/hooks/useStdoutDimensions.ts
126
+ /**
127
+ * useStdoutDimensions — Returns [columns, rows] and re-renders on terminal resize.
128
+ *
129
+ * Ink's useStdout() does not subscribe to resize events, so layout only updates
130
+ * when something else causes a re-render. This hook listens to the stream's
131
+ * 'resize' event (Node TTY) and updates state so the component re-renders
132
+ * with the new dimensions.
133
+ */
134
+ function useStdoutDimensions() {
135
+ const { stdout } = useStdout();
136
+ const [size, setSize] = useState(() => [stdout.columns || 80, stdout.rows || 24]);
137
+ useEffect(() => {
138
+ setSize([stdout.columns || 80, stdout.rows || 24]);
139
+ const stream = stdout;
140
+ if (typeof stream.on !== "function") return;
141
+ const onResize = () => {
142
+ const c = stdout.columns || 80;
143
+ const r = stdout.rows || 24;
144
+ if (c > 0 && r > 0) setSize([c, r]);
145
+ };
146
+ stream.on("resize", onResize);
147
+ return () => {
148
+ stream.off?.("resize", onResize);
149
+ };
150
+ }, [stdout]);
151
+ return size;
152
+ }
153
+ //#endregion
154
+ //#region src/ui/tui/hooks/keyboard-hints-utils.ts
155
+ /** Default priorities by key type. */
156
+ const DEFAULT_PRIORITY = {
157
+ ["upArrow"]: 0,
158
+ ["downArrow"]: 0,
159
+ ["leftArrow"]: 1,
160
+ ["rightArrow"]: 1,
161
+ ["space"]: 10,
162
+ ["printable"]: 12,
163
+ ["backspace"]: 13,
164
+ ["escape"]: 20,
165
+ ["return"]: 21
166
+ };
167
+ /** Get the default display priority for a key match. */
168
+ function getDefaultPriority(match) {
169
+ return DEFAULT_PRIORITY[Array.isArray(match) ? match[0] : match] ?? 15;
170
+ }
171
+ /** Check if a KeyMatchOrChar matches the given input string and key flags. */
172
+ function matchesKey(m, input, key) {
173
+ switch (m) {
174
+ case "upArrow": return !!key.upArrow;
175
+ case "downArrow": return !!key.downArrow;
176
+ case "leftArrow": return !!key.leftArrow;
177
+ case "rightArrow": return !!key.rightArrow;
178
+ case "return": return !!key.return;
179
+ case "escape": return !!key.escape;
180
+ case "space": return input === " ";
181
+ case "backspace": return !!key.backspace || !!key.delete;
182
+ case "printable": return input.length === 1 && input >= " " && input !== "" && Object.entries(key).every(([flag, on]) => !on || flag === "shift");
183
+ default: return input === m;
184
+ }
185
+ }
186
+ /** Serialize hints for comparison. */
187
+ function hintsKey(hints) {
188
+ return hints.map((h) => `${h.label}:${h.action}`).join("|");
189
+ }
190
+ /** Deduplicate hints by label+action and sort by priority. */
191
+ function deduplicateAndSort(hints) {
192
+ const seen = /* @__PURE__ */ new Set();
193
+ const deduped = [];
194
+ for (const hint of hints) {
195
+ const k = `${hint.label}:${hint.action}`;
196
+ if (!seen.has(k)) {
197
+ seen.add(k);
198
+ deduped.push(hint);
199
+ }
200
+ }
201
+ deduped.sort((a, b) => a.priority - b.priority);
202
+ return deduped;
203
+ }
204
+ //#endregion
205
+ //#region src/ui/tui/hooks/useKeyboardHints.tsx
206
+ /**
207
+ * KeyboardHintsProvider — Context for collecting and displaying keyboard hints.
208
+ *
209
+ * Input components register their hints via useKeyBindings. The provider
210
+ * flattens, deduplicates, and sorts them. The hints bar stays visible for as
211
+ * long as a screen has registered hints — it never auto-dismisses.
212
+ */
213
+ const KeyboardHintsContext = createContext({
214
+ register: () => void 0,
215
+ unregister: () => void 0,
216
+ hints: []
217
+ });
218
+ const useKeyboardHintsContext = () => useContext(KeyboardHintsContext);
219
+ const KeyboardHintsProvider = ({ children }) => {
220
+ const registrationsRef = useRef(/* @__PURE__ */ new Map());
221
+ const [hints, setHints] = useState([]);
222
+ const prevHintsKeyRef = useRef("");
223
+ const recompute = useCallback(() => {
224
+ const all = [];
225
+ for (const h of registrationsRef.current.values()) all.push(...h);
226
+ const deduped = deduplicateAndSort(all);
227
+ const newKey = hintsKey(deduped);
228
+ if (newKey !== prevHintsKeyRef.current) {
229
+ prevHintsKeyRef.current = newKey;
230
+ setHints(deduped);
231
+ }
232
+ }, []);
233
+ const register = useCallback((id, h) => {
234
+ registrationsRef.current.set(id, h);
235
+ recompute();
236
+ }, [recompute]);
237
+ const unregister = useCallback((id) => {
238
+ registrationsRef.current.delete(id);
239
+ recompute();
240
+ }, [recompute]);
241
+ return /* @__PURE__ */ jsx(KeyboardHintsContext.Provider, {
242
+ value: {
243
+ register,
244
+ unregister,
245
+ hints
246
+ },
247
+ children
248
+ });
249
+ };
250
+ //#endregion
251
+ //#region src/ui/tui/hooks/useKeyBindings.ts
252
+ /**
253
+ * useKeyBindings — Declarative keyboard input + automatic hint registration.
254
+ *
255
+ * Replaces raw `useInput` in input components. Define bindings as data;
256
+ * the hook wires up the Ink input handler AND registers hints in the
257
+ * KeyboardHintsProvider. One source of truth for keys and their labels.
258
+ */
259
+ /**
260
+ * Declarative key bindings hook. Replaces `useInput` in input components.
261
+ * Registers hints automatically with the KeyboardHintsProvider.
262
+ *
263
+ * @param id Unique identifier for this component's hints registration
264
+ * @param bindings Array of key binding definitions
265
+ */
266
+ function useKeyBindings(id, bindings) {
267
+ const ctx = useKeyboardHintsContext();
268
+ const hintsRef = useRef("");
269
+ const hints = bindings.map((b) => ({
270
+ label: b.label,
271
+ action: b.action,
272
+ priority: b.priority ?? getDefaultPriority(b.match)
273
+ }));
274
+ const serialized = hints.map((h) => `${h.label}:${h.action}:${h.priority}`).join("|");
275
+ useEffect(() => {
276
+ if (serialized !== hintsRef.current) {
277
+ hintsRef.current = serialized;
278
+ ctx.register(id, hints);
279
+ }
280
+ return () => ctx.unregister(id);
281
+ }, [id, serialized]);
282
+ useInput((input, key) => {
283
+ for (const binding of bindings) if ((Array.isArray(binding.match) ? binding.match : [binding.match]).some((m) => matchesKey(m, input, key))) {
284
+ binding.handler(input, key);
285
+ return;
286
+ }
287
+ });
288
+ }
289
+ //#endregion
290
+ //#region src/ui/tui/primitives/PickerMenu.tsx
291
+ /**
292
+ * PickerMenu — Single and multi select.
293
+ * Single mode: custom renderer with small triangle indicator; enter selects.
294
+ * Multi mode: checkbox glyphs toggled with enter, plus a focusable
295
+ * Confirm button below the options. The cursor moves onto the button and
296
+ * enter submits — see MultiPickerMenu for the rationale.
297
+ *
298
+ * Key bindings are declared via useKeyBindings, which auto-registers
299
+ * hints in the KeyboardHintsBar.
300
+ */
301
+ /**
302
+ * Step through a column's options in `dir`, wrapping, until an enabled
303
+ * option is found. Returns `from` unchanged if the column is entirely
304
+ * disabled.
305
+ */
306
+ function stepEnabled(options, rows, from, dir) {
307
+ const colStart = Math.floor(from / rows) * rows;
308
+ const colLen = Math.min(rows, options.length - colStart);
309
+ let row = from % rows;
310
+ for (let i = 0; i < colLen; i++) {
311
+ row = (row + dir + colLen) % colLen;
312
+ const idx = colStart + row;
313
+ if (!options[idx]?.disabled) return idx;
314
+ }
315
+ return from;
316
+ }
317
+ /** First enabled option in [start, end), for initial focus and for entering
318
+ * a page from the Confirm button. */
319
+ function firstEnabled(options, start = 0, end = options.length) {
320
+ for (let i = start; i < end; i++) if (!options[i]?.disabled) return i;
321
+ return start;
322
+ }
323
+ /** Last enabled option in [start, end), for wrapping from the button onto
324
+ * the bottom of the page. */
325
+ function lastEnabled(options, start = 0, end = options.length) {
326
+ for (let i = end - 1; i >= start; i--) if (!options[i]?.disabled) return i;
327
+ return end - 1;
328
+ }
329
+ /**
330
+ * Rows the surrounding screen or overlay consumes above and below a picker —
331
+ * border + padding, title, prompt text, and the keyboard-hints bar. A
332
+ * deliberately generous estimate: overshooting just shows a few fewer rows,
333
+ * whereas undershooting lets a long list overflow the viewport (the bug this
334
+ * windowing guards against). Mirrors GroupedPickerMenu's budgeting.
335
+ */
336
+ const CHROME_OVERHEAD = 13;
337
+ /**
338
+ * Max visual rows a picker renders regardless of terminal height. Without a
339
+ * ceiling a tall terminal lets a long list fill the whole viewport, which
340
+ * reads as a wall of options; ~12 rows keeps the menu scannable and leaves
341
+ * known breathing room above and below.
342
+ */
343
+ const MAX_LIST_ROWS = 12;
344
+ /** Extra rows a multi-select adds below its options: marginTop + Confirm button. */
345
+ const CONFIRM_CHROME = 3;
346
+ /** Lists shorter than this never page — tall rows would split 2-3 options into pages of one. */
347
+ const MIN_COUNT_TO_PAGE = 5;
348
+ /** Width the multi-select wraps option descriptions to (matches the render). */
349
+ const DESCRIPTION_WIDTH = 56;
350
+ /**
351
+ * Pages a single-column option list to the terminal height. The visible page
352
+ * is derived from the focused index — no scroll state — so ↑/↓ flip pages as
353
+ * focus crosses a page edge and n/p jump a whole page. Pages hold a fixed
354
+ * option count sized to the tallest row (`rowCost`), trading a sparser page
355
+ * on mixed-height lists for arithmetic-only paging. Engages only for
356
+ * single-column pickers — multi-column grids already compress vertically.
357
+ */
358
+ function usePickerViewport(count, rowCost, chromeBelow, enabled, focused) {
359
+ const [, termRows] = useStdoutDimensions();
360
+ const budget = Math.max(5, Math.min(termRows - CHROME_OVERHEAD - chromeBelow, MAX_LIST_ROWS));
361
+ const needsScroll = enabled && count >= MIN_COUNT_TO_PAGE && count * rowCost > budget;
362
+ const perPage = needsScroll ? Math.max(1, Math.floor((budget - 2) / rowCost)) : count;
363
+ const pageCount = Math.max(1, Math.ceil(count / perPage));
364
+ const start = Math.floor(focused / perPage) * perPage;
365
+ const end = Math.min(start + perPage, count);
366
+ return {
367
+ needsScroll,
368
+ start,
369
+ end,
370
+ hiddenAbove: start,
371
+ hiddenBelow: count - end,
372
+ pageStep: (f, dir) => (Math.floor(f / perPage) + dir + pageCount) % pageCount * perPage
373
+ };
374
+ }
375
+ /** Lists at least this long get filtering. Shorter ones fit on a page or two,
376
+ * where a filter row costs more rows than it saves. */
377
+ const FILTER_MIN_OPTIONS = 10;
378
+ const PickerMenu = ({ message, options, mode = "single", centered = false, columns = 1, optionMarginBottom = 0, filterable, onSelect }) => {
379
+ const canFilter = filterable ?? options.length >= FILTER_MIN_OPTIONS;
380
+ const [filter, setFilter] = useState("");
381
+ const fuse = useMemo(() => canFilter ? buildPickerIndex(options) : null, [options, canFilter]);
382
+ const visible = useMemo(() => fuse ? rankOptions(fuse, options, filter) ?? options : options, [
383
+ fuse,
384
+ options,
385
+ filter
386
+ ]);
387
+ const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
388
+ const shared = {
389
+ message,
390
+ options: visible,
391
+ centered,
392
+ optionMarginBottom,
393
+ onSelect,
394
+ filter: canFilter ? filter : null,
395
+ setFilter,
396
+ totalCount: options.length
397
+ };
398
+ const key = String(filter);
399
+ if (mode === "multi") return /* @__PURE__ */ jsx(MultiPickerMenu, {
400
+ ...shared,
401
+ columns,
402
+ allOptions: options,
403
+ selected,
404
+ setSelected
405
+ }, key);
406
+ return /* @__PURE__ */ jsx(SinglePickerMenu, {
407
+ ...shared,
408
+ columns
409
+ }, key);
410
+ };
411
+ /**
412
+ * Backspace and free typing. Append these last: `Printable` matches almost any
413
+ * key, and bindings resolve in order, so anything specific has to be registered
414
+ * first to keep working. Both share a label/action so the hints bar shows one.
415
+ */
416
+ function filterBindings(filter, setFilter) {
417
+ return [{
418
+ match: "backspace",
419
+ label: "type",
420
+ action: "filter",
421
+ handler: () => setFilter(filter.slice(0, -1))
422
+ }, {
423
+ match: "printable",
424
+ label: "type",
425
+ action: "filter",
426
+ handler: (input) => setFilter(filter + input)
427
+ }];
428
+ }
429
+ /** The filter row above a filterable list: what's typed, and how much it left. */
430
+ const FilterRow = ({ filter, shown, total }) => /* @__PURE__ */ jsx(Box, {
431
+ paddingLeft: 1,
432
+ children: /* @__PURE__ */ jsx(Text, {
433
+ dimColor: true,
434
+ children: filter ? `Filter: ${filter} (${shown} of ${total})` : `Type to filter ${total} options`
435
+ })
436
+ });
437
+ /** Custom single-select with triangle indicator and accent highlight. */
438
+ const SinglePickerMenu = ({ message, options, centered = false, columns = 1, optionMarginBottom = 0, filter, setFilter, totalCount, onSelect }) => {
439
+ const [focused, setFocused] = useState(() => firstEnabled(options));
440
+ const rows = Math.ceil(options.length / columns);
441
+ const viewport = usePickerViewport(options.length, 1 + optionMarginBottom, filter !== null ? 1 : 0, columns === 1, focused);
442
+ useEffect(() => {
443
+ if (focused >= options.length || options[focused]?.disabled) setFocused(firstEnabled(options));
444
+ }, [options, focused]);
445
+ const bindings = [
446
+ {
447
+ match: ["upArrow", "downArrow"],
448
+ label: "↑↓",
449
+ action: "navigate",
450
+ handler: (_input, key) => {
451
+ setFocused(stepEnabled(options, rows, focused, key.upArrow ? -1 : 1));
452
+ }
453
+ },
454
+ ...viewport.needsScroll && filter === null ? [{
455
+ match: ["n", "p"],
456
+ label: "n/p",
457
+ action: "page",
458
+ handler: (input) => {
459
+ const target = viewport.pageStep(focused, input === "n" ? 1 : -1);
460
+ setFocused(options[target]?.disabled ? stepEnabled(options, rows, target, 1) : target);
461
+ }
462
+ }] : [],
463
+ {
464
+ match: "return",
465
+ label: "enter",
466
+ action: "select",
467
+ handler: () => {
468
+ const selected = options[focused];
469
+ if (selected && !selected.disabled) onSelect(selected.value);
470
+ }
471
+ }
472
+ ];
473
+ if (columns > 1) bindings.splice(1, 0, {
474
+ match: ["leftArrow", "rightArrow"],
475
+ label: "←→",
476
+ action: "navigate",
477
+ handler: (_input, key) => {
478
+ const col = Math.floor(focused / rows);
479
+ const row = focused % rows;
480
+ let next = focused;
481
+ if (key.leftArrow) {
482
+ const prevCol = col > 0 ? col - 1 : columns - 1;
483
+ next = Math.min(prevCol * rows + row, options.length - 1);
484
+ }
485
+ if (key.rightArrow) {
486
+ const nextCol = col < columns - 1 ? col + 1 : 0;
487
+ next = Math.min(nextCol * rows + row, options.length - 1);
488
+ }
489
+ if (options[next]?.disabled) next = stepEnabled(options, rows, next, 1);
490
+ setFocused(next);
491
+ }
492
+ });
493
+ if (filter !== null) bindings.push(...filterBindings(filter, setFilter));
494
+ useKeyBindings("single-picker", bindings);
495
+ const columnArrays = [];
496
+ for (let c = 0; c < columns; c++) columnArrays.push(options.slice(c * rows, c * rows + rows));
497
+ const align = centered ? "center" : void 0;
498
+ const renderOption = (opt, flatIdx) => {
499
+ const isFocused = flatIdx === focused;
500
+ const base = opt.hint ? `${opt.label} (${opt.hint})` : opt.label;
501
+ const label = opt.indent ? ` ${base}` : base;
502
+ return /* @__PURE__ */ jsxs(Box, {
503
+ gap: 1,
504
+ marginBottom: optionMarginBottom,
505
+ children: [
506
+ /* @__PURE__ */ jsx(Text, {
507
+ color: isFocused ? Colors.accent : void 0,
508
+ dimColor: !isFocused,
509
+ children: isFocused && !opt.header ? Icons.triangleSmallRight : " "
510
+ }),
511
+ opt.icon && /* @__PURE__ */ jsx(Text, {
512
+ color: opt.icon.color,
513
+ children: opt.icon.glyph
514
+ }),
515
+ /* @__PURE__ */ jsx(Text, {
516
+ color: opt.header ? void 0 : opt.disabled ? Colors.muted : isFocused ? Colors.accent : void 0,
517
+ bold: opt.header || isFocused && !opt.disabled,
518
+ dimColor: !opt.header && (!isFocused || opt.disabled),
519
+ children: label
520
+ })
521
+ ]
522
+ }, flatIdx);
523
+ };
524
+ return /* @__PURE__ */ jsxs(Box, {
525
+ flexDirection: "column",
526
+ alignItems: align,
527
+ children: [
528
+ /* @__PURE__ */ jsx(PromptLabel, { message }),
529
+ filter !== null && /* @__PURE__ */ jsx(FilterRow, {
530
+ filter,
531
+ shown: options.length,
532
+ total: totalCount
533
+ }),
534
+ filter && options.length === 0 ? /* @__PURE__ */ jsx(Text, {
535
+ dimColor: true,
536
+ children: "No options match. Backspace to widen the filter."
537
+ }) : viewport.needsScroll ? /* @__PURE__ */ jsxs(Box, {
538
+ flexDirection: "column",
539
+ children: [
540
+ /* @__PURE__ */ jsx(Text, {
541
+ dimColor: true,
542
+ children: viewport.hiddenAbove > 0 ? `↑ ${viewport.hiddenAbove} more${filter === null ? " [P] for previous page" : ""}` : " "
543
+ }),
544
+ options.slice(viewport.start, viewport.end).map((opt, relIdx) => renderOption(opt, viewport.start + relIdx)),
545
+ /* @__PURE__ */ jsx(Text, {
546
+ dimColor: true,
547
+ children: viewport.hiddenBelow > 0 ? `↓ ${viewport.hiddenBelow} more${filter === null ? " [N] for next page" : ""}` : " "
548
+ })
549
+ ]
550
+ }) : /* @__PURE__ */ jsx(Box, {
551
+ flexDirection: "row",
552
+ gap: 4,
553
+ children: columnArrays.map((colOpts, colIdx) => /* @__PURE__ */ jsx(Box, {
554
+ flexDirection: "column",
555
+ children: colOpts.map((opt, rowIdx) => renderOption(opt, colIdx * rows + rowIdx))
556
+ }, colIdx))
557
+ })
558
+ ]
559
+ });
560
+ };
561
+ /**
562
+ * Custom multi-select with checkbox glyphs and accent highlight.
563
+ *
564
+ * Interaction model (shared with GroupedPickerMenu):
565
+ * - \u2191\u2193 move the cursor through the options AND onto the Confirm button,
566
+ * which lives just past the last option.
567
+ * - enter toggles the focused option (no more "space toggles but enter
568
+ * advances" split that tripped people up). Space is kept as an
569
+ * undocumented alias, but the hints bar advertises only enter.
570
+ * - moving onto the Confirm button and pressing enter submits the
571
+ * current selection.
572
+ */
573
+ const MultiPickerMenu = ({ message, options, centered = false, columns = 1, optionMarginBottom = 0, filter, setFilter, totalCount, allOptions, selected, setSelected, onSelect }) => {
574
+ const [focused, setFocused] = useState(() => firstEnabled(options));
575
+ const [onButton, setOnButton] = useState(false);
576
+ const rows = Math.ceil(options.length / columns);
577
+ const rowCost = options.reduce((max, opt) => Math.max(max, 1 + optionMarginBottom + (opt.description ? wordWrap(opt.description, DESCRIPTION_WIDTH).length : 0)), 1);
578
+ const viewport = usePickerViewport(options.length, rowCost, CONFIRM_CHROME + (filter !== null ? 1 : 0), columns === 1, focused);
579
+ useEffect(() => {
580
+ if (focused >= options.length || options[focused]?.disabled) setFocused(firstEnabled(options));
581
+ }, [options, focused]);
582
+ const confirm = () => {
583
+ onSelect(allOptions.filter((option) => selected.has(option.label)).map((option) => option.value));
584
+ };
585
+ const bindings = [
586
+ {
587
+ match: ["upArrow", "downArrow"],
588
+ label: "↑↓",
589
+ action: "navigate",
590
+ handler: (_input, key) => {
591
+ const dir = key.upArrow ? -1 : 1;
592
+ if (onButton) {
593
+ setOnButton(false);
594
+ setFocused(dir === -1 ? lastEnabled(options, viewport.start, viewport.end) : firstEnabled(options, viewport.start, viewport.end));
595
+ return;
596
+ }
597
+ const col = Math.floor(focused / rows);
598
+ const row = focused % rows;
599
+ const colLen = Math.min(rows, options.length - col * rows);
600
+ let r = row + dir;
601
+ while (r >= 0 && r < colLen && options[col * rows + r]?.disabled) r += dir;
602
+ if (r >= 0 && r < colLen) setFocused(col * rows + r);
603
+ else setOnButton(true);
604
+ }
605
+ },
606
+ ...viewport.needsScroll && filter === null ? [{
607
+ match: ["n", "p"],
608
+ label: "n/p",
609
+ action: "page",
610
+ handler: (input) => {
611
+ const target = viewport.pageStep(focused, input === "n" ? 1 : -1);
612
+ setOnButton(false);
613
+ setFocused(options[target]?.disabled ? stepEnabled(options, rows, target, 1) : target);
614
+ }
615
+ }] : [],
616
+ {
617
+ match: filter !== null ? "return" : ["space", "return"],
618
+ label: "enter",
619
+ action: "select",
620
+ handler: () => {
621
+ if (onButton) {
622
+ confirm();
623
+ return;
624
+ }
625
+ const option = options[focused];
626
+ if (!option || option.disabled) return;
627
+ const label = option.label;
628
+ setSelected((prev) => {
629
+ const next = new Set(prev);
630
+ if (next.has(label)) {
631
+ next.delete(label);
632
+ return next;
633
+ }
634
+ if (option.exclusive) return new Set([label]);
635
+ for (const picked of next) if (allOptions.find((o) => o.label === picked)?.exclusive) next.delete(picked);
636
+ next.add(label);
637
+ return next;
638
+ });
639
+ }
640
+ }
641
+ ];
642
+ if (columns > 1) bindings.splice(1, 0, {
643
+ match: ["leftArrow", "rightArrow"],
644
+ label: "←→",
645
+ action: "navigate",
646
+ handler: (_input, key) => {
647
+ if (onButton) return;
648
+ const col = Math.floor(focused / rows);
649
+ const row = focused % rows;
650
+ let next = focused;
651
+ if (key.leftArrow) {
652
+ const prevCol = col > 0 ? col - 1 : columns - 1;
653
+ next = Math.min(prevCol * rows + row, options.length - 1);
654
+ }
655
+ if (key.rightArrow) {
656
+ const nextCol = col < columns - 1 ? col + 1 : 0;
657
+ next = Math.min(nextCol * rows + row, options.length - 1);
658
+ }
659
+ if (options[next]?.disabled) next = stepEnabled(options, rows, next, 1);
660
+ setFocused(next);
661
+ }
662
+ });
663
+ if (filter !== null) bindings.push(...filterBindings(filter, setFilter));
664
+ useKeyBindings("multi-picker", bindings);
665
+ const columnArrays = [];
666
+ for (let c = 0; c < columns; c++) columnArrays.push(options.slice(c * rows, c * rows + rows));
667
+ const renderOption = (opt, flatIdx) => {
668
+ const isFocused = !onButton && flatIdx === focused;
669
+ const isSelected = selected.has(opt.label);
670
+ const label = opt.hint ? `${opt.label} (${opt.hint})` : opt.label;
671
+ const checkbox = isSelected ? Icons.squareFilled : Icons.squareOpen;
672
+ return /* @__PURE__ */ jsxs(Box, {
673
+ flexDirection: "column",
674
+ marginBottom: optionMarginBottom,
675
+ children: [/* @__PURE__ */ jsxs(Box, {
676
+ gap: 1,
677
+ children: [
678
+ /* @__PURE__ */ jsx(Text, {
679
+ color: isSelected ? "white" : Colors.muted,
680
+ dimColor: !isFocused && !isSelected,
681
+ children: checkbox
682
+ }),
683
+ opt.icon && /* @__PURE__ */ jsx(Text, {
684
+ color: opt.icon.color,
685
+ children: opt.icon.glyph
686
+ }),
687
+ /* @__PURE__ */ jsx(Text, {
688
+ color: opt.disabled ? Colors.muted : isFocused ? Colors.accent : void 0,
689
+ bold: isFocused && !opt.disabled,
690
+ dimColor: !isFocused || opt.disabled,
691
+ children: label
692
+ })
693
+ ]
694
+ }), opt.description && /* @__PURE__ */ jsx(Box, {
695
+ marginLeft: 4,
696
+ width: DESCRIPTION_WIDTH,
697
+ children: /* @__PURE__ */ jsx(Text, {
698
+ dimColor: true,
699
+ wrap: "wrap",
700
+ children: opt.description
701
+ })
702
+ })]
703
+ }, flatIdx);
704
+ };
705
+ return /* @__PURE__ */ jsxs(Box, {
706
+ flexDirection: "column",
707
+ alignItems: centered ? "center" : void 0,
708
+ children: [
709
+ /* @__PURE__ */ jsx(PromptLabel, { message }),
710
+ filter !== null && /* @__PURE__ */ jsx(FilterRow, {
711
+ filter,
712
+ shown: options.length,
713
+ total: totalCount
714
+ }),
715
+ filter && options.length === 0 && /* @__PURE__ */ jsx(Text, {
716
+ dimColor: true,
717
+ children: "No options match. Backspace to widen the filter."
718
+ }),
719
+ viewport.needsScroll ? /* @__PURE__ */ jsxs(Box, {
720
+ flexDirection: "column",
721
+ marginLeft: centered ? 0 : 2,
722
+ marginTop: message ? 1 : 0,
723
+ children: [
724
+ /* @__PURE__ */ jsx(Text, {
725
+ dimColor: true,
726
+ children: viewport.hiddenAbove > 0 ? `↑ ${viewport.hiddenAbove} more${filter === null ? " [P] for previous page" : ""}` : " "
727
+ }),
728
+ options.slice(viewport.start, viewport.end).map((opt, relIdx) => renderOption(opt, viewport.start + relIdx)),
729
+ /* @__PURE__ */ jsx(Text, {
730
+ dimColor: true,
731
+ children: viewport.hiddenBelow > 0 ? `↓ ${viewport.hiddenBelow} more${filter === null ? " [N] for next page" : ""}` : " "
732
+ })
733
+ ]
734
+ }) : /* @__PURE__ */ jsx(Box, {
735
+ flexDirection: "row",
736
+ gap: 4,
737
+ marginLeft: centered ? 0 : 2,
738
+ marginTop: message ? 1 : 0,
739
+ children: columnArrays.map((colOpts, colIdx) => /* @__PURE__ */ jsx(Box, {
740
+ flexDirection: "column",
741
+ children: colOpts.map((opt, rowIdx) => renderOption(opt, colIdx * rows + rowIdx))
742
+ }, colIdx))
743
+ }),
744
+ /* @__PURE__ */ jsx(Box, {
745
+ marginTop: 1,
746
+ marginLeft: centered ? 0 : 2,
747
+ children: /* @__PURE__ */ jsx(ConfirmButton, {
748
+ focused: onButton,
749
+ count: selected.size
750
+ })
751
+ })
752
+ ]
753
+ });
754
+ };
755
+ //#endregion
756
+ export { useStdoutDimensions as a, useKeyboardHintsContext as i, useKeyBindings as n, ConfirmButton as o, KeyboardHintsProvider as r, PromptLabel as s, PickerMenu as t };
757
+
758
+ //# sourceMappingURL=PickerMenu-dG5DQCZA.js.map