@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,425 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * BenchmarkRunDetailScreen - Detail page for benchmark runs
4
+ * Uses the generic ResourceDetailPage component
5
+ */
6
+ import React from "react";
7
+ import { Box, Text } from "ink";
8
+ import figures from "figures";
9
+ import { useNavigation } from "../store/navigationStore.js";
10
+ import { useBenchmarkStore, } from "../store/benchmarkStore.js";
11
+ import { ResourceDetailPage, formatTimestamp, } from "../components/ResourceDetailPage.js";
12
+ import { getBenchmarkRun, listScenarioRuns, } from "../services/benchmarkService.js";
13
+ import { SpinnerComponent } from "../components/Spinner.js";
14
+ import { ErrorMessage } from "../components/ErrorMessage.js";
15
+ import { Breadcrumb } from "../components/Breadcrumb.js";
16
+ import { getStatusDisplay } from "../components/StatusBadge.js";
17
+ import { Table, createTextColumn, createComponentColumn, } from "../components/Table.js";
18
+ import { colors } from "../utils/theme.js";
19
+ export function BenchmarkRunDetailScreen({ benchmarkRunId, }) {
20
+ const { goBack, navigate } = useNavigation();
21
+ const benchmarkRuns = useBenchmarkStore((state) => state.benchmarkRuns);
22
+ const [loading, setLoading] = React.useState(false);
23
+ const [error, setError] = React.useState(null);
24
+ const [fetchedRun, setFetchedRun] = React.useState(null);
25
+ const [scenarioRuns, setScenarioRuns] = React.useState([]);
26
+ const [scenarioRunsLoading, setScenarioRunsLoading] = React.useState(false);
27
+ // Find run in store first
28
+ const runFromStore = benchmarkRuns.find((r) => r.id === benchmarkRunId);
29
+ // Polling function
30
+ const pollRun = React.useCallback(async () => {
31
+ if (!benchmarkRunId)
32
+ return null;
33
+ // Also refresh scenario runs when polling
34
+ listScenarioRuns({
35
+ limit: 10,
36
+ benchmarkRunId,
37
+ })
38
+ .then((result) => {
39
+ setScenarioRuns(result.scenarioRuns);
40
+ })
41
+ .catch(() => {
42
+ // Silently fail for scenario runs
43
+ });
44
+ return getBenchmarkRun(benchmarkRunId);
45
+ }, [benchmarkRunId]);
46
+ // Fetch run from API if not in store
47
+ React.useEffect(() => {
48
+ if (benchmarkRunId && !loading && !fetchedRun) {
49
+ setLoading(true);
50
+ setError(null);
51
+ getBenchmarkRun(benchmarkRunId)
52
+ .then((run) => {
53
+ setFetchedRun(run);
54
+ setLoading(false);
55
+ })
56
+ .catch((err) => {
57
+ setError(err);
58
+ setLoading(false);
59
+ });
60
+ }
61
+ }, [benchmarkRunId, loading, fetchedRun]);
62
+ // Fetch scenario runs for this benchmark run
63
+ React.useEffect(() => {
64
+ if (benchmarkRunId && !scenarioRunsLoading && scenarioRuns.length === 0) {
65
+ setScenarioRunsLoading(true);
66
+ listScenarioRuns({
67
+ limit: 10, // Show up to 10 scenarios
68
+ benchmarkRunId,
69
+ })
70
+ .then((result) => {
71
+ setScenarioRuns(result.scenarioRuns);
72
+ setScenarioRunsLoading(false);
73
+ })
74
+ .catch(() => {
75
+ // Silently fail for scenario runs - not critical
76
+ setScenarioRunsLoading(false);
77
+ });
78
+ }
79
+ }, [benchmarkRunId, scenarioRunsLoading, scenarioRuns.length]);
80
+ // Use fetched run for full details, fall back to store for basic display
81
+ const run = fetchedRun || runFromStore;
82
+ // Auto-refresh scenario runs every 5 seconds if benchmark run is running
83
+ React.useEffect(() => {
84
+ if (!benchmarkRunId || !run)
85
+ return;
86
+ // Only refresh if run is still running
87
+ if (run.state !== "running")
88
+ return;
89
+ const interval = setInterval(() => {
90
+ listScenarioRuns({
91
+ limit: 10,
92
+ benchmarkRunId,
93
+ })
94
+ .then((result) => {
95
+ setScenarioRuns(result.scenarioRuns);
96
+ })
97
+ .catch(() => {
98
+ // Silently fail
99
+ });
100
+ }, 5000);
101
+ return () => clearInterval(interval);
102
+ }, [benchmarkRunId, run]);
103
+ // Show loading state
104
+ if (!run && benchmarkRunId && !error) {
105
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
106
+ { label: "Home" },
107
+ { label: "Benchmarks" },
108
+ { label: "Benchmark Runs" },
109
+ { label: "Loading...", active: true },
110
+ ] }), _jsx(SpinnerComponent, { message: "Loading benchmark run details..." })] }));
111
+ }
112
+ // Show error state
113
+ if (error && !run) {
114
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
115
+ { label: "Home" },
116
+ { label: "Benchmarks" },
117
+ { label: "Benchmark Runs" },
118
+ { label: "Error", active: true },
119
+ ] }), _jsx(ErrorMessage, { message: "Failed to load benchmark run details", error: error })] }));
120
+ }
121
+ // Show not found error
122
+ if (!run) {
123
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
124
+ { label: "Home" },
125
+ { label: "Benchmarks" },
126
+ { label: "Benchmark Runs" },
127
+ { label: "Not Found", active: true },
128
+ ] }), _jsx(ErrorMessage, { message: `Benchmark run ${benchmarkRunId || "unknown"} not found`, error: new Error("Benchmark run not found") })] }));
129
+ }
130
+ // Helper to calculate overall run status based on scenarios
131
+ const calculateOverallStatus = (scenarios) => {
132
+ if (scenarios.length === 0) {
133
+ return {
134
+ status: "not-started",
135
+ label: "Not Started",
136
+ color: colors.textDim,
137
+ icon: figures.circle,
138
+ };
139
+ }
140
+ // Check for any failures or timeouts
141
+ const hasFailed = scenarios.some((s) => s.state === "failed" || s.state === "timeout");
142
+ if (hasFailed) {
143
+ return {
144
+ status: "failed",
145
+ label: "Failed",
146
+ color: colors.error,
147
+ icon: figures.cross,
148
+ };
149
+ }
150
+ // Check if all are completed
151
+ const allCompleted = scenarios.every((s) => s.state === "completed" || s.state === "scored");
152
+ if (allCompleted) {
153
+ return {
154
+ status: "pass",
155
+ label: "Complete",
156
+ color: colors.success,
157
+ icon: figures.tick,
158
+ };
159
+ }
160
+ // Check if any are running
161
+ const anyRunning = scenarios.some((s) => s.state === "running" || s.state === "scoring");
162
+ if (anyRunning) {
163
+ return {
164
+ status: "in-progress",
165
+ label: "In Progress",
166
+ color: colors.warning,
167
+ icon: figures.circleFilled,
168
+ };
169
+ }
170
+ // Default to not started
171
+ return {
172
+ status: "not-started",
173
+ label: "Not Started",
174
+ color: colors.textDim,
175
+ icon: figures.circle,
176
+ };
177
+ };
178
+ // Helper to format duration
179
+ const formatDuration = (ms) => {
180
+ if (ms < 1000)
181
+ return `${ms}ms`;
182
+ const seconds = Math.floor(ms / 1000);
183
+ if (seconds < 60)
184
+ return `${seconds}s`;
185
+ const minutes = Math.floor(seconds / 60);
186
+ const remainingSeconds = seconds % 60;
187
+ if (minutes < 60)
188
+ return `${minutes}m ${remainingSeconds}s`;
189
+ const hours = Math.floor(minutes / 60);
190
+ const remainingMinutes = minutes % 60;
191
+ return `${hours}h ${remainingMinutes}m`;
192
+ };
193
+ // Build detail sections
194
+ const detailSections = [];
195
+ // Basic details section
196
+ const basicFields = [];
197
+ if (run.benchmark_id) {
198
+ basicFields.push({
199
+ label: "Benchmark ID",
200
+ value: _jsx(Text, { color: colors.idColor, children: run.benchmark_id }),
201
+ action: {
202
+ type: "navigate",
203
+ screen: "benchmark-detail",
204
+ params: { benchmarkId: run.benchmark_id },
205
+ hint: "View Benchmark",
206
+ },
207
+ });
208
+ }
209
+ if (run.purpose) {
210
+ basicFields.push({
211
+ label: "Purpose",
212
+ value: run.purpose,
213
+ });
214
+ }
215
+ if (run.score !== undefined && run.score !== null) {
216
+ basicFields.push({
217
+ label: "Score",
218
+ value: (_jsx(Text, { color: colors.success, bold: true, children: run.score.toFixed(2) })),
219
+ });
220
+ }
221
+ if (basicFields.length > 0) {
222
+ detailSections.push({
223
+ title: "Details",
224
+ icon: figures.squareSmallFilled,
225
+ color: colors.warning,
226
+ fields: basicFields,
227
+ });
228
+ }
229
+ // Overall Status Section
230
+ const overallStatus = calculateOverallStatus(scenarioRuns);
231
+ detailSections.push({
232
+ title: "Overall Status",
233
+ icon: overallStatus.icon,
234
+ color: overallStatus.color,
235
+ fields: [
236
+ {
237
+ label: "Status",
238
+ value: (_jsx(Text, { color: overallStatus.color, bold: true, children: overallStatus.label })),
239
+ },
240
+ {
241
+ label: "Scenarios",
242
+ value: `${scenarioRuns.length} scenario${scenarioRuns.length !== 1 ? "s" : ""}`,
243
+ },
244
+ ],
245
+ });
246
+ // Scenario Runs Section
247
+ if (scenarioRuns.length > 0) {
248
+ // Define columns for scenario table
249
+ const scenarioColumns = [
250
+ createTextColumn("id", "ID", (s) => s.id, {
251
+ width: 26,
252
+ color: colors.idColor,
253
+ dimColor: false,
254
+ bold: false,
255
+ }),
256
+ createTextColumn("name", "Name", (s) => s.name || "(unnamed)", {
257
+ width: 50,
258
+ }),
259
+ createComponentColumn("status", "Status", (s, _index, isSelected) => {
260
+ const statusDisplay = getStatusDisplay(s.state);
261
+ const text = statusDisplay.text.slice(0, 12).padEnd(12, " ");
262
+ return (_jsx(Text, { color: isSelected ? colors.text : statusDisplay.color, bold: isSelected, inverse: isSelected, children: text }));
263
+ }, { width: 12 }),
264
+ createTextColumn("score", "Score", (s) => {
265
+ const score = s.scoring_contract_result?.score;
266
+ return score !== undefined ? String(score) : "";
267
+ }, {
268
+ width: 10,
269
+ color: colors.info,
270
+ }),
271
+ ];
272
+ detailSections.push({
273
+ title: "Scenario Runs",
274
+ icon: figures.pointer,
275
+ color: colors.info,
276
+ fields: [
277
+ {
278
+ label: "",
279
+ value: (_jsx(Box, { paddingTop: 1, children: _jsx(Table, { data: scenarioRuns, columns: scenarioColumns, selectedIndex: -1, keyExtractor: (s) => s.id }) })),
280
+ },
281
+ ],
282
+ });
283
+ }
284
+ // Timing section
285
+ const timingFields = [];
286
+ if (run.start_time_ms) {
287
+ timingFields.push({
288
+ label: "Started",
289
+ value: formatTimestamp(run.start_time_ms),
290
+ });
291
+ }
292
+ const endTimeMs = run.start_time_ms && run.duration_ms
293
+ ? run.start_time_ms + run.duration_ms
294
+ : undefined;
295
+ if (endTimeMs) {
296
+ timingFields.push({
297
+ label: "Ended",
298
+ value: formatTimestamp(endTimeMs),
299
+ });
300
+ }
301
+ if (run.duration_ms) {
302
+ timingFields.push({
303
+ label: "Duration",
304
+ value: _jsx(Text, { color: colors.info, children: formatDuration(run.duration_ms) }),
305
+ });
306
+ }
307
+ if (timingFields.length > 0) {
308
+ detailSections.push({
309
+ title: "Timing",
310
+ icon: figures.play,
311
+ color: colors.info,
312
+ fields: timingFields,
313
+ });
314
+ }
315
+ // Secrets Provided section (show keys only, not values)
316
+ if (run.secrets_provided && Object.keys(run.secrets_provided).length > 0) {
317
+ const secretFields = Object.entries(run.secrets_provided).map(([envVar, secretName]) => ({
318
+ label: envVar,
319
+ value: _jsxs(Text, { color: colors.warning, children: [secretName, " (secret)"] }),
320
+ action: {
321
+ type: "navigate",
322
+ screen: "secret-detail",
323
+ params: { secretId: secretName },
324
+ hint: "View Secret",
325
+ },
326
+ }));
327
+ detailSections.push({
328
+ title: "Secrets Provided",
329
+ icon: figures.warning,
330
+ color: colors.warning,
331
+ fields: secretFields,
332
+ });
333
+ }
334
+ // Metadata section
335
+ if (run.metadata && Object.keys(run.metadata).length > 0) {
336
+ const metadataFields = Object.entries(run.metadata).map(([key, value]) => ({
337
+ label: key,
338
+ value: value,
339
+ }));
340
+ detailSections.push({
341
+ title: "Metadata",
342
+ icon: figures.identical,
343
+ color: colors.secondary,
344
+ fields: metadataFields,
345
+ });
346
+ }
347
+ // Operations available for benchmark runs
348
+ const operations = [
349
+ {
350
+ key: "view-scenarios",
351
+ label: "View Scenario Runs",
352
+ color: colors.info,
353
+ icon: figures.arrowRight,
354
+ shortcut: "s",
355
+ },
356
+ ];
357
+ // Handle operation selection
358
+ const handleOperation = async (operation, resource) => {
359
+ switch (operation) {
360
+ case "view-scenarios":
361
+ navigate("scenario-run-list", { benchmarkRunId: resource.id });
362
+ break;
363
+ }
364
+ };
365
+ // Build detailed info lines for full details view
366
+ const buildDetailLines = (r) => {
367
+ const lines = [];
368
+ // Core Information
369
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Benchmark Run Details" }, "core-title"));
370
+ lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "ID: ", r.id] }, "core-id"));
371
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Name: ", r.name || "(none)"] }, "core-name"));
372
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Status: ", r.state] }, "core-status"));
373
+ if (r.benchmark_id) {
374
+ lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "Benchmark ID: ", r.benchmark_id] }, "core-benchmark"));
375
+ }
376
+ if (r.purpose) {
377
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Purpose: ", r.purpose] }, "core-purpose"));
378
+ }
379
+ if (r.score !== undefined && r.score !== null) {
380
+ lines.push(_jsxs(Text, { color: colors.success, children: [" ", "Score: ", r.score.toFixed(2)] }, "core-score"));
381
+ }
382
+ lines.push(_jsx(Text, { children: " " }, "core-space"));
383
+ // Timing
384
+ lines.push(_jsx(Text, { color: colors.info, bold: true, children: "Timing" }, "timing-title"));
385
+ if (r.start_time_ms) {
386
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Started: ", new Date(r.start_time_ms).toLocaleString()] }, "timing-started"));
387
+ }
388
+ const detailEndTimeMs = r.start_time_ms && r.duration_ms
389
+ ? r.start_time_ms + r.duration_ms
390
+ : undefined;
391
+ if (detailEndTimeMs) {
392
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Ended: ", new Date(detailEndTimeMs).toLocaleString()] }, "timing-ended"));
393
+ }
394
+ if (r.duration_ms) {
395
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Duration: ", formatDuration(r.duration_ms)] }, "timing-duration"));
396
+ }
397
+ lines.push(_jsx(Text, { children: " " }, "timing-space"));
398
+ // Secrets Provided
399
+ if (r.secrets_provided && Object.keys(r.secrets_provided).length > 0) {
400
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Secrets Provided" }, "secrets-title"));
401
+ Object.entries(r.secrets_provided).forEach(([envVar, secretName], idx) => {
402
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", envVar, " \u2192 ", secretName] }, `secret-${idx}`));
403
+ });
404
+ lines.push(_jsx(Text, { children: " " }, "secrets-space"));
405
+ }
406
+ // Metadata
407
+ if (r.metadata && Object.keys(r.metadata).length > 0) {
408
+ lines.push(_jsx(Text, { color: colors.secondary, bold: true, children: "Metadata" }, "meta-title"));
409
+ Object.entries(r.metadata).forEach(([key, value], idx) => {
410
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", key, ": ", value] }, `meta-${idx}`));
411
+ });
412
+ lines.push(_jsx(Text, { children: " " }, "meta-space"));
413
+ }
414
+ // Raw JSON
415
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Raw JSON" }, "json-title"));
416
+ const jsonLines = JSON.stringify(r, null, 2).split("\n");
417
+ jsonLines.forEach((line, idx) => {
418
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", line] }, `json-${idx}`));
419
+ });
420
+ return lines;
421
+ };
422
+ // Check if run is still in progress for polling
423
+ const isRunning = run.state === "running";
424
+ return (_jsx(ResourceDetailPage, { resource: run, resourceType: "Benchmark Runs", getDisplayName: (r) => r.name || r.id, getId: (r) => r.id, getStatus: (r) => r.state, detailSections: detailSections, operations: operations, onOperation: handleOperation, onBack: goBack, buildDetailLines: buildDetailLines, pollResource: isRunning ? pollRun : undefined, breadcrumbPrefix: [{ label: "Home" }, { label: "Benchmarks" }] }));
425
+ }