@runloop/rl-cli 1.7.1 → 1.8.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.
@@ -4,13 +4,14 @@ import { enterAlternateScreenBuffer, exitAlternateScreenBuffer, clearScreen, } f
4
4
  import { processUtils } from "../utils/processUtils.js";
5
5
  import { Router } from "../router/Router.js";
6
6
  import { NavigationProvider } from "../store/navigationStore.js";
7
+ import { BetaFeatureProvider } from "../store/betaFeatureStore.js";
7
8
  function AppInner() {
8
9
  // NavigationProvider already handles initialScreen and initialParams
9
10
  // No need for useEffect here - provider sets state on mount
10
11
  return _jsx(Router, {});
11
12
  }
12
13
  function App({ initialScreen = "menu", focusDevboxId, }) {
13
- return (_jsx(NavigationProvider, { initialScreen: initialScreen, initialParams: focusDevboxId ? { focusDevboxId } : {}, children: _jsx(AppInner, {}) }));
14
+ return (_jsx(BetaFeatureProvider, { children: _jsx(NavigationProvider, { initialScreen: initialScreen, initialParams: focusDevboxId ? { focusDevboxId } : {}, children: _jsx(AppInner, {}) }) }));
14
15
  }
15
16
  export async function runMainMenu(initialScreen = "menu", focusDevboxId) {
16
17
  enterAlternateScreenBuffer();
@@ -1,9 +1,384 @@
1
- /**
2
- * List secrets command
3
- */
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { Box, Text, useInput, useApp } from "ink";
4
+ import figures from "figures";
4
5
  import { getClient } from "../../utils/client.js";
6
+ import { Header } from "../../components/Header.js";
7
+ import { SpinnerComponent } from "../../components/Spinner.js";
8
+ import { ErrorMessage } from "../../components/ErrorMessage.js";
9
+ import { SuccessMessage } from "../../components/SuccessMessage.js";
10
+ import { Breadcrumb } from "../../components/Breadcrumb.js";
11
+ import { NavigationTips } from "../../components/NavigationTips.js";
12
+ import { Table, createTextColumn } from "../../components/Table.js";
13
+ import { ActionsPopup } from "../../components/ActionsPopup.js";
14
+ import { formatTimeAgo } from "../../components/ResourceListView.js";
15
+ import { SearchBar } from "../../components/SearchBar.js";
5
16
  import { output, outputError } from "../../utils/output.js";
6
- const DEFAULT_PAGE_SIZE = 20;
17
+ import { colors } from "../../utils/theme.js";
18
+ import { useViewportHeight } from "../../hooks/useViewportHeight.js";
19
+ import { useExitOnCtrlC } from "../../hooks/useExitOnCtrlC.js";
20
+ import { useCursorPagination } from "../../hooks/useCursorPagination.js";
21
+ import { useListSearch } from "../../hooks/useListSearch.js";
22
+ import { useNavigation } from "../../store/navigationStore.js";
23
+ import { ConfirmationPrompt } from "../../components/ConfirmationPrompt.js";
24
+ const DEFAULT_PAGE_SIZE = 10;
25
+ const ListSecretsUI = ({ onBack, onExit, }) => {
26
+ const { exit: inkExit } = useApp();
27
+ const { navigate } = useNavigation();
28
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
29
+ const [showPopup, setShowPopup] = React.useState(false);
30
+ const [selectedOperation, setSelectedOperation] = React.useState(0);
31
+ const [selectedSecret, setSelectedSecret] = React.useState(null);
32
+ const [executingOperation, setExecutingOperation] = React.useState(null);
33
+ const [operationResult, setOperationResult] = React.useState(null);
34
+ const [operationError, setOperationError] = React.useState(null);
35
+ const [operationLoading, setOperationLoading] = React.useState(false);
36
+ const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
37
+ // Search state
38
+ const search = useListSearch({
39
+ onSearchSubmit: () => setSelectedIndex(0),
40
+ onSearchClear: () => setSelectedIndex(0),
41
+ });
42
+ // Calculate overhead for viewport height
43
+ const overhead = 13 + search.getSearchOverhead();
44
+ const { viewportHeight, terminalWidth } = useViewportHeight({
45
+ overhead,
46
+ minHeight: 5,
47
+ });
48
+ const PAGE_SIZE = viewportHeight;
49
+ // All width constants
50
+ const fixedWidth = 6; // border + padding
51
+ const idWidth = 30;
52
+ const timeWidth = 20;
53
+ // Name width uses remaining space after fixed columns
54
+ const baseWidth = fixedWidth + idWidth + timeWidth;
55
+ const remainingWidth = terminalWidth - baseWidth;
56
+ const nameWidth = Math.min(80, Math.max(15, remainingWidth));
57
+ // Fetch function for pagination hook
58
+ const fetchPage = React.useCallback(async (params) => {
59
+ const client = getClient();
60
+ const pageSecrets = [];
61
+ // Secrets API doesn't support cursor pagination, fetch all and paginate client-side
62
+ const result = await client.secrets.list({ limit: 5000 });
63
+ // Extract data and filter by search if needed
64
+ if (result.secrets && Array.isArray(result.secrets)) {
65
+ let filtered = result.secrets;
66
+ // Client-side search filtering
67
+ if (search.submittedSearchQuery) {
68
+ const query = search.submittedSearchQuery.toLowerCase();
69
+ filtered = filtered.filter((s) => s.name?.toLowerCase().includes(query) ||
70
+ s.id?.toLowerCase().includes(query));
71
+ }
72
+ // Client-side pagination
73
+ const startIdx = params.startingAt
74
+ ? filtered.findIndex((s) => s.id === params.startingAt) + 1
75
+ : 0;
76
+ const pageItems = filtered.slice(startIdx, startIdx + params.limit);
77
+ pageItems.forEach((s) => {
78
+ pageSecrets.push({
79
+ id: s.id,
80
+ name: s.name,
81
+ create_time_ms: s.create_time_ms,
82
+ update_time_ms: s.update_time_ms,
83
+ });
84
+ });
85
+ return {
86
+ items: pageSecrets,
87
+ hasMore: startIdx + params.limit < filtered.length,
88
+ totalCount: filtered.length,
89
+ };
90
+ }
91
+ return {
92
+ items: pageSecrets,
93
+ hasMore: false,
94
+ totalCount: 0,
95
+ };
96
+ }, [search.submittedSearchQuery]);
97
+ // Use the shared pagination hook
98
+ const { items: secrets, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
99
+ fetchPage,
100
+ pageSize: PAGE_SIZE,
101
+ getItemId: (secret) => secret.id,
102
+ pollInterval: 10000,
103
+ pollingEnabled: !showPopup &&
104
+ !executingOperation &&
105
+ !showDeleteConfirm &&
106
+ !search.searchMode,
107
+ deps: [PAGE_SIZE, search.submittedSearchQuery],
108
+ });
109
+ // Operations for a specific secret (shown in popup)
110
+ const operations = React.useMemo(() => [
111
+ {
112
+ key: "view_details",
113
+ label: "View Details",
114
+ color: colors.primary,
115
+ icon: figures.pointer,
116
+ },
117
+ {
118
+ key: "delete",
119
+ label: "Delete Secret",
120
+ color: colors.error,
121
+ icon: figures.cross,
122
+ },
123
+ ], []);
124
+ // Build columns
125
+ const columns = React.useMemo(() => [
126
+ createTextColumn("id", "ID", (secret) => secret.id, {
127
+ width: idWidth + 1,
128
+ color: colors.idColor,
129
+ dimColor: false,
130
+ bold: false,
131
+ }),
132
+ createTextColumn("name", "Name", (secret) => secret.name || "", {
133
+ width: nameWidth,
134
+ }),
135
+ createTextColumn("created", "Created", (secret) => secret.create_time_ms ? formatTimeAgo(secret.create_time_ms) : "-", {
136
+ width: timeWidth,
137
+ color: colors.textDim,
138
+ dimColor: false,
139
+ bold: false,
140
+ }),
141
+ ], [idWidth, nameWidth, timeWidth]);
142
+ // Handle Ctrl+C to exit
143
+ useExitOnCtrlC();
144
+ // Ensure selected index is within bounds
145
+ React.useEffect(() => {
146
+ if (secrets.length > 0 && selectedIndex >= secrets.length) {
147
+ setSelectedIndex(Math.max(0, secrets.length - 1));
148
+ }
149
+ }, [secrets.length, selectedIndex]);
150
+ const selectedSecretItem = secrets[selectedIndex];
151
+ // Calculate pagination info for display
152
+ const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
153
+ const startIndex = currentPage * PAGE_SIZE;
154
+ const endIndex = startIndex + secrets.length;
155
+ const executeOperation = async (secret, operationKey) => {
156
+ const client = getClient();
157
+ if (!secret)
158
+ return;
159
+ try {
160
+ setOperationLoading(true);
161
+ switch (operationKey) {
162
+ case "delete":
163
+ await client.secrets.delete(secret.name);
164
+ setOperationResult(`Secret "${secret.name}" deleted successfully`);
165
+ break;
166
+ }
167
+ }
168
+ catch (err) {
169
+ setOperationError(err);
170
+ }
171
+ finally {
172
+ setOperationLoading(false);
173
+ }
174
+ };
175
+ useInput((input, key) => {
176
+ // Handle search mode input
177
+ if (search.searchMode) {
178
+ if (key.escape) {
179
+ search.cancelSearch();
180
+ }
181
+ return;
182
+ }
183
+ // Handle operation result display
184
+ if (operationResult || operationError) {
185
+ if (input === "q" || key.escape || key.return) {
186
+ const wasDelete = executingOperation === "delete";
187
+ const hadError = operationError !== null;
188
+ setOperationResult(null);
189
+ setOperationError(null);
190
+ setExecutingOperation(null);
191
+ setSelectedSecret(null);
192
+ // Refresh the list after delete to show updated data
193
+ if (wasDelete && !hadError) {
194
+ setTimeout(() => refresh(), 0);
195
+ }
196
+ }
197
+ return;
198
+ }
199
+ // Handle popup navigation
200
+ if (showPopup) {
201
+ if (key.upArrow && selectedOperation > 0) {
202
+ setSelectedOperation(selectedOperation - 1);
203
+ }
204
+ else if (key.downArrow && selectedOperation < operations.length - 1) {
205
+ setSelectedOperation(selectedOperation + 1);
206
+ }
207
+ else if (key.return) {
208
+ setShowPopup(false);
209
+ const operationKey = operations[selectedOperation].key;
210
+ if (operationKey === "view_details") {
211
+ navigate("secret-detail", {
212
+ secretId: selectedSecretItem.id,
213
+ });
214
+ }
215
+ else if (operationKey === "delete") {
216
+ // Show delete confirmation
217
+ setSelectedSecret(selectedSecretItem);
218
+ setShowDeleteConfirm(true);
219
+ }
220
+ else {
221
+ setSelectedSecret(selectedSecretItem);
222
+ setExecutingOperation(operationKey);
223
+ // Execute immediately with values passed directly
224
+ executeOperation(selectedSecretItem, operationKey);
225
+ }
226
+ }
227
+ else if (input === "c") {
228
+ // Create hotkey
229
+ setShowPopup(false);
230
+ navigate("secret-create");
231
+ }
232
+ else if (input === "v" && selectedSecretItem) {
233
+ // View details hotkey
234
+ setShowPopup(false);
235
+ navigate("secret-detail", {
236
+ secretId: selectedSecretItem.id,
237
+ });
238
+ }
239
+ else if (key.escape || input === "q") {
240
+ setShowPopup(false);
241
+ setSelectedOperation(0);
242
+ }
243
+ else if (input === "d") {
244
+ // Delete hotkey - show confirmation
245
+ setShowPopup(false);
246
+ setSelectedSecret(selectedSecretItem);
247
+ setShowDeleteConfirm(true);
248
+ }
249
+ return;
250
+ }
251
+ const pageSecrets = secrets.length;
252
+ // Handle list view navigation
253
+ if (key.upArrow && selectedIndex > 0) {
254
+ setSelectedIndex(selectedIndex - 1);
255
+ }
256
+ else if (key.downArrow && selectedIndex < pageSecrets - 1) {
257
+ setSelectedIndex(selectedIndex + 1);
258
+ }
259
+ else if ((input === "n" || key.rightArrow) &&
260
+ !loading &&
261
+ !navigating &&
262
+ hasMore) {
263
+ nextPage();
264
+ setSelectedIndex(0);
265
+ }
266
+ else if ((input === "p" || key.leftArrow) &&
267
+ !loading &&
268
+ !navigating &&
269
+ hasPrev) {
270
+ prevPage();
271
+ setSelectedIndex(0);
272
+ }
273
+ else if (key.return && selectedSecretItem) {
274
+ // Enter key navigates to detail view
275
+ navigate("secret-detail", {
276
+ secretId: selectedSecretItem.id,
277
+ });
278
+ }
279
+ else if (input === "a") {
280
+ setShowPopup(true);
281
+ setSelectedOperation(0);
282
+ }
283
+ else if (input === "c") {
284
+ // Create shortcut
285
+ navigate("secret-create");
286
+ }
287
+ else if (input === "/") {
288
+ search.enterSearchMode();
289
+ }
290
+ else if (key.escape) {
291
+ if (search.handleEscape()) {
292
+ return;
293
+ }
294
+ if (onBack) {
295
+ onBack();
296
+ }
297
+ else if (onExit) {
298
+ onExit();
299
+ }
300
+ else {
301
+ inkExit();
302
+ }
303
+ }
304
+ });
305
+ // Delete confirmation
306
+ if (showDeleteConfirm && selectedSecret) {
307
+ return (_jsx(ConfirmationPrompt, { title: "Delete Secret", message: `Are you sure you want to delete "${selectedSecret.name}"?`, details: "This action cannot be undone. Any devboxes using this secret will no longer have access to it.", breadcrumbItems: [
308
+ { label: "Settings" },
309
+ { label: "Secrets" },
310
+ { label: selectedSecret.name || selectedSecret.id },
311
+ { label: "Delete", active: true },
312
+ ], onConfirm: () => {
313
+ setShowDeleteConfirm(false);
314
+ setExecutingOperation("delete");
315
+ executeOperation(selectedSecret, "delete");
316
+ }, onCancel: () => {
317
+ setShowDeleteConfirm(false);
318
+ setSelectedSecret(null);
319
+ } }));
320
+ }
321
+ // Operation result display
322
+ if (operationResult || operationError) {
323
+ const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
324
+ "Operation";
325
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
326
+ { label: "Settings" },
327
+ { label: "Secrets" },
328
+ {
329
+ label: selectedSecret?.name || selectedSecret?.id || "Secret",
330
+ },
331
+ { label: operationLabel, active: true },
332
+ ] }), _jsx(Header, { title: "Operation Result" }), operationResult && _jsx(SuccessMessage, { message: operationResult }), operationError && (_jsx(ErrorMessage, { message: "Operation failed", error: operationError })), _jsx(NavigationTips, { tips: [{ key: "Enter/q/esc", label: "Continue" }] })] }));
333
+ }
334
+ // Operation loading state
335
+ if (operationLoading && selectedSecret) {
336
+ const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
337
+ "Operation";
338
+ const messages = {
339
+ delete: "Deleting secret...",
340
+ };
341
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
342
+ { label: "Settings" },
343
+ { label: "Secrets" },
344
+ { label: selectedSecret.name || selectedSecret.id },
345
+ { label: operationLabel, active: true },
346
+ ] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: messages[executingOperation] || "Please wait..." })] }));
347
+ }
348
+ // Loading state
349
+ if (loading && secrets.length === 0) {
350
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Settings" }, { label: "Secrets", active: true }] }), _jsx(SpinnerComponent, { message: "Loading secrets..." })] }));
351
+ }
352
+ // Error state
353
+ if (error) {
354
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Settings" }, { label: "Secrets", active: true }] }), _jsx(ErrorMessage, { message: "Failed to list secrets", error: error })] }));
355
+ }
356
+ // Main list view
357
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Settings" }, { label: "Secrets", active: true }] }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search secrets..." }), !showPopup && (_jsx(Table, { data: secrets, keyExtractor: (secret) => secret.id, selectedIndex: selectedIndex, title: `secrets[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No secrets found. Press [c] to create one."] }) })), !showPopup && (_jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.hamburger, " ", totalCount] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "total"] }), totalPages > 1 && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), navigating ? (_jsxs(Text, { color: colors.warning, children: [figures.pointer, " Loading page ", currentPage + 1, "..."] })) : (_jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Page ", currentPage + 1, " of ", totalPages] }))] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Showing ", startIndex + 1, "-", endIndex, " of ", totalCount] }), search.submittedSearchQuery && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.warning, children: ["Filtered: \"", search.submittedSearchQuery, "\""] })] }))] })), showPopup && selectedSecretItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedSecretItem, operations: operations.map((op) => ({
358
+ key: op.key,
359
+ label: op.label,
360
+ color: op.color,
361
+ icon: op.icon,
362
+ shortcut: op.key === "view_details"
363
+ ? "v"
364
+ : op.key === "delete"
365
+ ? "d"
366
+ : "",
367
+ })), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
368
+ {
369
+ icon: `${figures.arrowLeft}${figures.arrowRight}`,
370
+ label: "Page",
371
+ condition: hasMore || hasPrev,
372
+ },
373
+ { key: "Enter", label: "Details" },
374
+ { key: "c", label: "Create" },
375
+ { key: "a", label: "Actions" },
376
+ { key: "/", label: "Search" },
377
+ { key: "Esc", label: "Back" },
378
+ ] })] }));
379
+ };
380
+ // Export the UI component for use in the main menu
381
+ export { ListSecretsUI };
7
382
  export async function listSecrets(options = {}) {
8
383
  try {
9
384
  const client = getClient();
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * BenchmarkMenu - Sub-menu for benchmark-related resources
4
+ */
5
+ import React from "react";
6
+ import { Box, Text, useInput, useApp, useStdout } from "ink";
7
+ import figures from "figures";
8
+ import { Breadcrumb } from "./Breadcrumb.js";
9
+ import { NavigationTips } from "./NavigationTips.js";
10
+ import { colors } from "../utils/theme.js";
11
+ import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
12
+ const benchmarkMenuItems = [
13
+ {
14
+ key: "benchmark-runs",
15
+ label: "Benchmark Runs",
16
+ description: "View and manage benchmark executions",
17
+ icon: "▶",
18
+ color: colors.success,
19
+ },
20
+ {
21
+ key: "scenario-runs",
22
+ label: "Scenario Runs",
23
+ description: "View individual scenario results",
24
+ icon: "◈",
25
+ color: colors.info,
26
+ },
27
+ ];
28
+ export const BenchmarkMenu = ({ onSelect, onBack }) => {
29
+ const { exit } = useApp();
30
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
31
+ const { stdout } = useStdout();
32
+ // Get terminal dimensions for responsive layout
33
+ const getTerminalDimensions = React.useCallback(() => {
34
+ return {
35
+ height: stdout?.rows && stdout.rows > 0 ? stdout.rows : 20,
36
+ width: stdout?.columns && stdout.columns > 0 ? stdout.columns : 80,
37
+ };
38
+ }, [stdout]);
39
+ const [terminalDimensions, setTerminalDimensions] = React.useState(getTerminalDimensions);
40
+ React.useEffect(() => {
41
+ setTerminalDimensions(getTerminalDimensions());
42
+ if (!stdout)
43
+ return;
44
+ const handleResize = () => {
45
+ setTerminalDimensions(getTerminalDimensions());
46
+ };
47
+ stdout.on("resize", handleResize);
48
+ return () => {
49
+ stdout.off("resize", handleResize);
50
+ };
51
+ }, [stdout, getTerminalDimensions]);
52
+ const terminalWidth = terminalDimensions.width;
53
+ const isNarrow = terminalWidth < 70;
54
+ // Handle Ctrl+C to exit
55
+ useExitOnCtrlC();
56
+ useInput((input, key) => {
57
+ if (key.upArrow && selectedIndex > 0) {
58
+ setSelectedIndex(selectedIndex - 1);
59
+ }
60
+ else if (key.downArrow && selectedIndex < benchmarkMenuItems.length - 1) {
61
+ setSelectedIndex(selectedIndex + 1);
62
+ }
63
+ else if (key.return) {
64
+ onSelect(benchmarkMenuItems[selectedIndex].key);
65
+ }
66
+ else if (key.escape) {
67
+ onBack();
68
+ }
69
+ else if (input === "b" || input === "1") {
70
+ onSelect("benchmark-runs");
71
+ }
72
+ else if (input === "s" || input === "2") {
73
+ onSelect("scenario-runs");
74
+ }
75
+ else if (input === "q") {
76
+ exit();
77
+ }
78
+ });
79
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Breadcrumb, { items: [{ label: "Home" }, { label: "Benchmarks", active: true }] }), _jsxs(Box, { paddingX: 2, marginBottom: 1, children: [_jsx(Text, { color: colors.primary, bold: true, children: "Benchmarks" }), _jsx(Text, { color: colors.textDim, dimColor: true, children: isNarrow ? "" : " • Performance testing and evaluation" })] }), _jsx(Box, { flexDirection: "column", paddingX: 2, children: benchmarkMenuItems.map((item, index) => {
80
+ const isSelected = index === selectedIndex;
81
+ return (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsx(Text, { children: " " }), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), !isNarrow && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "- ", item.description] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
82
+ }) }), _jsx(NavigationTips, { showArrows: true, paddingX: 2, tips: [
83
+ { key: "1-2", label: "Quick select" },
84
+ { key: "Enter", label: "Select" },
85
+ { key: "Esc", label: "Back" },
86
+ { key: "q", label: "Quit" },
87
+ ] })] }));
88
+ };