@runloop/rl-cli 1.7.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +19 -5
  2. package/dist/cli.js +0 -0
  3. package/dist/commands/blueprint/delete.js +21 -0
  4. package/dist/commands/blueprint/list.js +226 -174
  5. package/dist/commands/blueprint/prune.js +13 -28
  6. package/dist/commands/devbox/create.js +41 -0
  7. package/dist/commands/devbox/list.js +125 -109
  8. package/dist/commands/devbox/tunnel.js +4 -19
  9. package/dist/commands/gateway-config/create.js +44 -0
  10. package/dist/commands/gateway-config/delete.js +21 -0
  11. package/dist/commands/gateway-config/get.js +15 -0
  12. package/dist/commands/gateway-config/list.js +493 -0
  13. package/dist/commands/gateway-config/update.js +60 -0
  14. package/dist/commands/menu.js +2 -1
  15. package/dist/commands/secret/list.js +379 -4
  16. package/dist/commands/snapshot/list.js +11 -2
  17. package/dist/commands/snapshot/prune.js +265 -0
  18. package/dist/components/BenchmarkMenu.js +108 -0
  19. package/dist/components/DetailedInfoView.js +20 -0
  20. package/dist/components/DevboxActionsMenu.js +9 -61
  21. package/dist/components/DevboxCreatePage.js +531 -14
  22. package/dist/components/DevboxDetailPage.js +27 -22
  23. package/dist/components/GatewayConfigCreatePage.js +265 -0
  24. package/dist/components/LogsViewer.js +6 -40
  25. package/dist/components/MainMenu.js +63 -22
  26. package/dist/components/ResourceDetailPage.js +143 -160
  27. package/dist/components/ResourceListView.js +3 -33
  28. package/dist/components/ResourcePicker.js +220 -0
  29. package/dist/components/SecretCreatePage.js +183 -0
  30. package/dist/components/SettingsMenu.js +95 -0
  31. package/dist/components/StateHistory.js +1 -20
  32. package/dist/components/StatusBadge.js +80 -0
  33. package/dist/components/StreamingLogsViewer.js +8 -42
  34. package/dist/components/form/FormTextInput.js +4 -2
  35. package/dist/components/resourceDetailTypes.js +18 -0
  36. package/dist/hooks/useInputHandler.js +103 -0
  37. package/dist/router/Router.js +99 -2
  38. package/dist/screens/BenchmarkDetailScreen.js +163 -0
  39. package/dist/screens/BenchmarkJobCreateScreen.js +524 -0
  40. package/dist/screens/BenchmarkJobDetailScreen.js +614 -0
  41. package/dist/screens/BenchmarkJobListScreen.js +479 -0
  42. package/dist/screens/BenchmarkListScreen.js +266 -0
  43. package/dist/screens/BenchmarkMenuScreen.js +29 -0
  44. package/dist/screens/BenchmarkRunDetailScreen.js +425 -0
  45. package/dist/screens/BenchmarkRunListScreen.js +275 -0
  46. package/dist/screens/BlueprintDetailScreen.js +5 -1
  47. package/dist/screens/DevboxCreateScreen.js +2 -2
  48. package/dist/screens/GatewayConfigDetailScreen.js +236 -0
  49. package/dist/screens/GatewayConfigListScreen.js +7 -0
  50. package/dist/screens/MenuScreen.js +5 -2
  51. package/dist/screens/ScenarioRunDetailScreen.js +226 -0
  52. package/dist/screens/ScenarioRunListScreen.js +245 -0
  53. package/dist/screens/SecretCreateScreen.js +7 -0
  54. package/dist/screens/SecretDetailScreen.js +198 -0
  55. package/dist/screens/SecretListScreen.js +7 -0
  56. package/dist/screens/SettingsMenuScreen.js +26 -0
  57. package/dist/screens/SnapshotDetailScreen.js +6 -0
  58. package/dist/services/agentService.js +42 -0
  59. package/dist/services/benchmarkJobService.js +122 -0
  60. package/dist/services/benchmarkService.js +120 -0
  61. package/dist/services/gatewayConfigService.js +114 -0
  62. package/dist/services/scenarioService.js +34 -0
  63. package/dist/store/benchmarkJobStore.js +66 -0
  64. package/dist/store/benchmarkStore.js +183 -0
  65. package/dist/store/betaFeatureStore.js +47 -0
  66. package/dist/store/gatewayConfigStore.js +83 -0
  67. package/dist/store/index.js +1 -0
  68. package/dist/utils/browser.js +22 -0
  69. package/dist/utils/clipboard.js +41 -0
  70. package/dist/utils/commands.js +80 -0
  71. package/dist/utils/config.js +8 -0
  72. package/dist/utils/time.js +121 -0
  73. package/package.json +42 -43
@@ -0,0 +1,266 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * BenchmarkListScreen - List view for benchmark definitions
4
+ */
5
+ import React from "react";
6
+ import { Box, Text, useInput, useApp } from "ink";
7
+ import figures from "figures";
8
+ import { useNavigation } from "../store/navigationStore.js";
9
+ import { SpinnerComponent } from "../components/Spinner.js";
10
+ import { ErrorMessage } from "../components/ErrorMessage.js";
11
+ import { Breadcrumb } from "../components/Breadcrumb.js";
12
+ import { NavigationTips } from "../components/NavigationTips.js";
13
+ import { Table, createTextColumn, createComponentColumn, } from "../components/Table.js";
14
+ import { ActionsPopup } from "../components/ActionsPopup.js";
15
+ import { formatTimeAgo } from "../components/ResourceListView.js";
16
+ import { SearchBar } from "../components/SearchBar.js";
17
+ import { getStatusDisplay } from "../components/StatusBadge.js";
18
+ import { colors } from "../utils/theme.js";
19
+ import { useViewportHeight } from "../hooks/useViewportHeight.js";
20
+ import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
21
+ import { useCursorPagination } from "../hooks/useCursorPagination.js";
22
+ import { useListSearch } from "../hooks/useListSearch.js";
23
+ import { listBenchmarks } from "../services/benchmarkService.js";
24
+ export function BenchmarkListScreen() {
25
+ const { exit: inkExit } = useApp();
26
+ const { navigate, goBack } = useNavigation();
27
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
28
+ const [showPopup, setShowPopup] = React.useState(false);
29
+ const [selectedOperation, setSelectedOperation] = React.useState(0);
30
+ // Search state
31
+ const search = useListSearch({
32
+ onSearchSubmit: () => setSelectedIndex(0),
33
+ onSearchClear: () => setSelectedIndex(0),
34
+ });
35
+ // Calculate overhead for viewport height
36
+ const overhead = 13 + search.getSearchOverhead();
37
+ const { viewportHeight, terminalWidth } = useViewportHeight({
38
+ overhead,
39
+ minHeight: 5,
40
+ });
41
+ const PAGE_SIZE = viewportHeight;
42
+ // Column widths
43
+ const fixedWidth = 6;
44
+ const idWidth = 25;
45
+ const statusWidth = 12;
46
+ const timeWidth = 18;
47
+ const baseWidth = fixedWidth + idWidth + statusWidth + timeWidth;
48
+ const remainingWidth = terminalWidth - baseWidth;
49
+ const nameWidth = Math.min(80, Math.max(15, remainingWidth));
50
+ // Fetch function for pagination hook
51
+ const fetchPage = React.useCallback(async (params) => {
52
+ const result = await listBenchmarks({
53
+ limit: params.limit,
54
+ startingAfter: params.startingAt,
55
+ search: search.submittedSearchQuery || undefined,
56
+ });
57
+ return {
58
+ items: result.benchmarks,
59
+ hasMore: result.hasMore,
60
+ totalCount: result.totalCount,
61
+ };
62
+ }, [search.submittedSearchQuery]);
63
+ // Use the shared pagination hook
64
+ const { items: benchmarks, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, } = useCursorPagination({
65
+ fetchPage,
66
+ pageSize: PAGE_SIZE,
67
+ getItemId: (benchmark) => benchmark.id,
68
+ pollInterval: 5000,
69
+ pollingEnabled: !showPopup && !search.searchMode,
70
+ deps: [PAGE_SIZE, search.submittedSearchQuery],
71
+ });
72
+ // Operations for benchmarks
73
+ const operations = React.useMemo(() => [
74
+ {
75
+ key: "view_details",
76
+ label: "View Details",
77
+ color: colors.primary,
78
+ icon: figures.pointer,
79
+ },
80
+ {
81
+ key: "create_job",
82
+ label: "Create Benchmark Job",
83
+ color: colors.success,
84
+ icon: figures.play,
85
+ },
86
+ ], []);
87
+ // Build columns
88
+ const columns = React.useMemo(() => [
89
+ createTextColumn("id", "ID", (benchmark) => benchmark.id, {
90
+ width: idWidth + 1,
91
+ color: colors.idColor,
92
+ dimColor: false,
93
+ bold: false,
94
+ }),
95
+ createTextColumn("name", "Name", (benchmark) => benchmark.name || "", {
96
+ width: nameWidth,
97
+ }),
98
+ createComponentColumn("status", "Status", (benchmark, _index, isSelected) => {
99
+ const status = benchmark.status || "active";
100
+ const statusDisplay = getStatusDisplay(status);
101
+ const text = statusDisplay.text
102
+ .slice(0, statusWidth)
103
+ .padEnd(statusWidth, " ");
104
+ return (_jsx(Text, { color: isSelected ? colors.text : statusDisplay.color, bold: isSelected, inverse: isSelected, children: text }));
105
+ }, { width: statusWidth }),
106
+ createTextColumn("created", "Created", (benchmark) => benchmark.created_at
107
+ ? formatTimeAgo(benchmark.created_at)
108
+ : "", {
109
+ width: timeWidth,
110
+ color: colors.textDim,
111
+ dimColor: false,
112
+ bold: false,
113
+ }),
114
+ ], [idWidth, nameWidth, statusWidth, timeWidth]);
115
+ // Handle Ctrl+C to exit
116
+ useExitOnCtrlC();
117
+ // Ensure selected index is within bounds
118
+ React.useEffect(() => {
119
+ if (benchmarks.length > 0 && selectedIndex >= benchmarks.length) {
120
+ setSelectedIndex(Math.max(0, benchmarks.length - 1));
121
+ }
122
+ }, [benchmarks.length, selectedIndex]);
123
+ const selectedBenchmark = benchmarks[selectedIndex];
124
+ // Calculate pagination info for display
125
+ const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
126
+ const startIndex = currentPage * PAGE_SIZE;
127
+ const endIndex = startIndex + benchmarks.length;
128
+ useInput((input, key) => {
129
+ // Handle search mode input
130
+ if (search.searchMode) {
131
+ if (key.escape) {
132
+ search.cancelSearch();
133
+ }
134
+ return;
135
+ }
136
+ // Handle popup navigation
137
+ if (showPopup) {
138
+ if (key.upArrow && selectedOperation > 0) {
139
+ setSelectedOperation(selectedOperation - 1);
140
+ }
141
+ else if (key.downArrow && selectedOperation < operations.length - 1) {
142
+ setSelectedOperation(selectedOperation + 1);
143
+ }
144
+ else if (key.return) {
145
+ setShowPopup(false);
146
+ const operationKey = operations[selectedOperation].key;
147
+ if (operationKey === "view_details") {
148
+ navigate("benchmark-detail", {
149
+ benchmarkId: selectedBenchmark.id,
150
+ });
151
+ }
152
+ else if (operationKey === "create_job") {
153
+ navigate("benchmark-job-create", {
154
+ initialBenchmarkIds: selectedBenchmark.id,
155
+ });
156
+ }
157
+ }
158
+ else if (input === "v" && selectedBenchmark) {
159
+ setShowPopup(false);
160
+ navigate("benchmark-detail", {
161
+ benchmarkId: selectedBenchmark.id,
162
+ });
163
+ }
164
+ else if (input === "c" && selectedBenchmark) {
165
+ setShowPopup(false);
166
+ navigate("benchmark-job-create", {
167
+ initialBenchmarkIds: selectedBenchmark.id,
168
+ });
169
+ }
170
+ else if (key.escape || input === "q") {
171
+ setShowPopup(false);
172
+ setSelectedOperation(0);
173
+ }
174
+ return;
175
+ }
176
+ const pageBenchmarks = benchmarks.length;
177
+ // Handle list view navigation
178
+ if (key.upArrow && selectedIndex > 0) {
179
+ setSelectedIndex(selectedIndex - 1);
180
+ }
181
+ else if (key.downArrow && selectedIndex < pageBenchmarks - 1) {
182
+ setSelectedIndex(selectedIndex + 1);
183
+ }
184
+ else if ((input === "n" || key.rightArrow) &&
185
+ !loading &&
186
+ !navigating &&
187
+ hasMore) {
188
+ nextPage();
189
+ setSelectedIndex(0);
190
+ }
191
+ else if ((input === "p" || key.leftArrow) &&
192
+ !loading &&
193
+ !navigating &&
194
+ hasPrev) {
195
+ prevPage();
196
+ setSelectedIndex(0);
197
+ }
198
+ else if (key.return && selectedBenchmark) {
199
+ navigate("benchmark-detail", {
200
+ benchmarkId: selectedBenchmark.id,
201
+ });
202
+ }
203
+ else if (input === "a" && selectedBenchmark) {
204
+ setShowPopup(true);
205
+ setSelectedOperation(0);
206
+ }
207
+ else if (input === "c" && selectedBenchmark) {
208
+ // Quick shortcut to create a job
209
+ navigate("benchmark-job-create", {
210
+ initialBenchmarkIds: selectedBenchmark.id,
211
+ });
212
+ }
213
+ else if (input === "/") {
214
+ search.enterSearchMode();
215
+ }
216
+ else if (key.escape) {
217
+ if (search.handleEscape()) {
218
+ return;
219
+ }
220
+ goBack();
221
+ }
222
+ });
223
+ // Loading state
224
+ if (loading && benchmarks.length === 0) {
225
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
226
+ { label: "Home" },
227
+ { label: "Benchmarks" },
228
+ { label: "Benchmark Definitions", active: true },
229
+ ] }), _jsx(SpinnerComponent, { message: "Loading benchmarks..." })] }));
230
+ }
231
+ // Error state
232
+ if (error) {
233
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
234
+ { label: "Home" },
235
+ { label: "Benchmarks" },
236
+ { label: "Benchmark Definitions", active: true },
237
+ ] }), _jsx(ErrorMessage, { message: "Failed to list benchmarks", error: error })] }));
238
+ }
239
+ // Main list view
240
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
241
+ { label: "Home" },
242
+ { label: "Benchmarks" },
243
+ { label: "Benchmark Definitions", active: true },
244
+ ] }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search benchmarks..." }), !showPopup && (_jsx(Table, { data: benchmarks, keyExtractor: (benchmark) => benchmark.id, selectedIndex: selectedIndex, title: `benchmarks[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No benchmarks found"] }) })), !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] })] })), showPopup && selectedBenchmark && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedBenchmark, operations: operations.map((op) => ({
245
+ key: op.key,
246
+ label: op.label,
247
+ color: op.color,
248
+ icon: op.icon,
249
+ shortcut: op.key === "view_details"
250
+ ? "v"
251
+ : op.key === "create_job"
252
+ ? "s"
253
+ : "",
254
+ })), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
255
+ {
256
+ icon: `${figures.arrowLeft}${figures.arrowRight}`,
257
+ label: "Page",
258
+ condition: hasMore || hasPrev,
259
+ },
260
+ { key: "Enter", label: "Details" },
261
+ { key: "c", label: "Create Job" },
262
+ { key: "a", label: "Actions" },
263
+ { key: "/", label: "Search" },
264
+ { key: "Esc", label: "Back" },
265
+ ] })] }));
266
+ }
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useNavigation } from "../store/navigationStore.js";
3
+ import { BenchmarkMenu } from "../components/BenchmarkMenu.js";
4
+ export function BenchmarkMenuScreen() {
5
+ const { navigate, goBack } = useNavigation();
6
+ const handleSelect = (key) => {
7
+ switch (key) {
8
+ case "benchmarks":
9
+ navigate("benchmark-list");
10
+ break;
11
+ case "benchmark-runs":
12
+ navigate("benchmark-run-list");
13
+ break;
14
+ case "benchmark-jobs":
15
+ navigate("benchmark-job-list");
16
+ break;
17
+ case "scenario-runs":
18
+ navigate("scenario-run-list");
19
+ break;
20
+ default:
21
+ // Fallback for any other screen names
22
+ navigate(key);
23
+ }
24
+ };
25
+ const handleBack = () => {
26
+ goBack();
27
+ };
28
+ return _jsx(BenchmarkMenu, { onSelect: handleSelect, onBack: handleBack });
29
+ }