@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,226 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ScenarioRunDetailScreen - Detail page for scenario runs
4
+ * Uses the generic ResourceDetailPage component
5
+ */
6
+ import React from "react";
7
+ import { 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 { getScenarioRun } 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 { colors } from "../utils/theme.js";
17
+ export function ScenarioRunDetailScreen({ scenarioRunId, benchmarkRunId, }) {
18
+ const { goBack, navigate } = useNavigation();
19
+ const scenarioRuns = useBenchmarkStore((state) => state.scenarioRuns);
20
+ const [loading, setLoading] = React.useState(false);
21
+ const [error, setError] = React.useState(null);
22
+ const [fetchedRun, setFetchedRun] = React.useState(null);
23
+ // Find run in store first
24
+ const runFromStore = scenarioRuns.find((r) => r.id === scenarioRunId);
25
+ // Polling function
26
+ const pollRun = React.useCallback(async () => {
27
+ if (!scenarioRunId)
28
+ return null;
29
+ return getScenarioRun(scenarioRunId);
30
+ }, [scenarioRunId]);
31
+ // Fetch run from API if not in store
32
+ React.useEffect(() => {
33
+ if (scenarioRunId && !loading && !fetchedRun) {
34
+ setLoading(true);
35
+ setError(null);
36
+ getScenarioRun(scenarioRunId)
37
+ .then((run) => {
38
+ setFetchedRun(run);
39
+ setLoading(false);
40
+ })
41
+ .catch((err) => {
42
+ setError(err);
43
+ setLoading(false);
44
+ });
45
+ }
46
+ }, [scenarioRunId, loading, fetchedRun]);
47
+ // Use fetched run for full details, fall back to store for basic display
48
+ const run = fetchedRun || runFromStore;
49
+ // Build breadcrumb items
50
+ const buildBreadcrumbItems = (lastLabel, active = true) => {
51
+ const items = [
52
+ { label: "Home" },
53
+ { label: "Benchmarks" },
54
+ ];
55
+ if (benchmarkRunId) {
56
+ items.push({ label: `Run: ${benchmarkRunId.substring(0, 8)}...` });
57
+ }
58
+ items.push({ label: "Scenario Runs" });
59
+ items.push({ label: lastLabel, active });
60
+ return items;
61
+ };
62
+ // Show loading state
63
+ if (!run && scenarioRunId && !error) {
64
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: buildBreadcrumbItems("Loading...") }), _jsx(SpinnerComponent, { message: "Loading scenario run details..." })] }));
65
+ }
66
+ // Show error state
67
+ if (error && !run) {
68
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: buildBreadcrumbItems("Error") }), _jsx(ErrorMessage, { message: "Failed to load scenario run details", error: error })] }));
69
+ }
70
+ // Show not found error
71
+ if (!run) {
72
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: buildBreadcrumbItems("Not Found") }), _jsx(ErrorMessage, { message: `Scenario run ${scenarioRunId || "unknown"} not found`, error: new Error("Scenario run not found") })] }));
73
+ }
74
+ // Build detail sections
75
+ const detailSections = [];
76
+ // Basic details section
77
+ const basicFields = [];
78
+ if (run.start_time_ms) {
79
+ basicFields.push({
80
+ label: "Started",
81
+ value: formatTimestamp(run.start_time_ms),
82
+ });
83
+ }
84
+ const endTimeMs = run.start_time_ms && run.duration_ms
85
+ ? run.start_time_ms + run.duration_ms
86
+ : undefined;
87
+ if (endTimeMs) {
88
+ basicFields.push({
89
+ label: "Ended",
90
+ value: formatTimestamp(endTimeMs),
91
+ });
92
+ }
93
+ if (run.scenario_id) {
94
+ basicFields.push({
95
+ label: "Scenario ID",
96
+ value: _jsx(Text, { color: colors.idColor, children: run.scenario_id }),
97
+ });
98
+ }
99
+ if (run.benchmark_run_id) {
100
+ basicFields.push({
101
+ label: "Benchmark Run ID",
102
+ value: _jsx(Text, { color: colors.idColor, children: run.benchmark_run_id }),
103
+ action: {
104
+ type: "navigate",
105
+ screen: "benchmark-run-detail",
106
+ params: { benchmarkRunId: run.benchmark_run_id },
107
+ hint: "View Run",
108
+ },
109
+ });
110
+ }
111
+ if (basicFields.length > 0) {
112
+ detailSections.push({
113
+ title: "Details",
114
+ icon: figures.squareSmallFilled,
115
+ color: colors.warning,
116
+ fields: basicFields,
117
+ });
118
+ }
119
+ // Results section
120
+ const score = run.scoring_contract_result?.score;
121
+ if (score !== undefined) {
122
+ detailSections.push({
123
+ title: "Results",
124
+ icon: figures.tick,
125
+ color: colors.success,
126
+ fields: [
127
+ {
128
+ label: "Score",
129
+ value: _jsx(Text, { color: colors.info, children: score }),
130
+ },
131
+ ],
132
+ });
133
+ }
134
+ // Metadata section
135
+ if (run.metadata && Object.keys(run.metadata).length > 0) {
136
+ const metadataFields = Object.entries(run.metadata).map(([key, value]) => ({
137
+ label: key,
138
+ value: value,
139
+ }));
140
+ detailSections.push({
141
+ title: "Metadata",
142
+ icon: figures.identical,
143
+ color: colors.secondary,
144
+ fields: metadataFields,
145
+ });
146
+ }
147
+ // Operations available for scenario runs
148
+ const operations = [];
149
+ if (run.benchmark_run_id) {
150
+ operations.push({
151
+ key: "view-benchmark-run",
152
+ label: "View Benchmark Run",
153
+ color: colors.info,
154
+ icon: figures.arrowRight,
155
+ shortcut: "b",
156
+ });
157
+ }
158
+ // Handle operation selection
159
+ const handleOperation = async (operation, resource) => {
160
+ switch (operation) {
161
+ case "view-benchmark-run":
162
+ if (resource.benchmark_run_id) {
163
+ navigate("benchmark-run-detail", {
164
+ benchmarkRunId: resource.benchmark_run_id,
165
+ });
166
+ }
167
+ break;
168
+ }
169
+ };
170
+ // Build detailed info lines for full details view
171
+ const buildDetailLines = (r) => {
172
+ const lines = [];
173
+ // Core Information
174
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Scenario Run Details" }, "core-title"));
175
+ lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "ID: ", r.id] }, "core-id"));
176
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Name: ", r.name || "(none)"] }, "core-name"));
177
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Status: ", r.state] }, "core-status"));
178
+ lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "Scenario ID: ", r.scenario_id] }, "core-scenario"));
179
+ if (r.benchmark_run_id) {
180
+ lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "Benchmark Run ID: ", r.benchmark_run_id] }, "core-benchmark-run"));
181
+ }
182
+ if (r.start_time_ms) {
183
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Started: ", new Date(r.start_time_ms).toLocaleString()] }, "core-created"));
184
+ }
185
+ const detailEndTimeMs = r.start_time_ms && r.duration_ms
186
+ ? r.start_time_ms + r.duration_ms
187
+ : undefined;
188
+ if (detailEndTimeMs) {
189
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Ended: ", new Date(detailEndTimeMs).toLocaleString()] }, "core-ended"));
190
+ }
191
+ lines.push(_jsx(Text, { children: " " }, "core-space"));
192
+ // Results
193
+ const detailScore = r.scoring_contract_result?.score;
194
+ if (detailScore !== undefined) {
195
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Results" }, "results-title"));
196
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Score: ", detailScore] }, "results-score"));
197
+ lines.push(_jsx(Text, { children: " " }, "results-space"));
198
+ }
199
+ // Metadata
200
+ if (r.metadata && Object.keys(r.metadata).length > 0) {
201
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Metadata" }, "meta-title"));
202
+ Object.entries(r.metadata).forEach(([key, value], idx) => {
203
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", key, ": ", value] }, `meta-${idx}`));
204
+ });
205
+ lines.push(_jsx(Text, { children: " " }, "meta-space"));
206
+ }
207
+ // Raw JSON
208
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Raw JSON" }, "json-title"));
209
+ const jsonLines = JSON.stringify(r, null, 2).split("\n");
210
+ jsonLines.forEach((line, idx) => {
211
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", line] }, `json-${idx}`));
212
+ });
213
+ return lines;
214
+ };
215
+ // Check if run is still in progress for polling
216
+ const isRunning = run.state === "running" || run.state === "scoring";
217
+ // Build breadcrumb prefix
218
+ const breadcrumbPrefix = [{ label: "Home" }, { label: "Benchmarks" }];
219
+ if (benchmarkRunId) {
220
+ breadcrumbPrefix.push({
221
+ label: `Run: ${benchmarkRunId.substring(0, 8)}...`,
222
+ });
223
+ }
224
+ breadcrumbPrefix.push({ label: "Scenario Runs" });
225
+ return (_jsx(ResourceDetailPage, { resource: run, resourceType: "Scenario 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: breadcrumbPrefix }));
226
+ }
@@ -0,0 +1,245 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ScenarioRunListScreen - List view for scenario runs
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 { listScenarioRuns } from "../services/benchmarkService.js";
24
+ export function ScenarioRunListScreen({ benchmarkRunId, }) {
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 scoreWidth = 8;
47
+ const timeWidth = 18;
48
+ const baseWidth = fixedWidth + idWidth + statusWidth + scoreWidth + timeWidth;
49
+ const remainingWidth = terminalWidth - baseWidth;
50
+ const nameWidth = Math.min(60, Math.max(15, remainingWidth));
51
+ // Fetch function for pagination hook
52
+ const fetchPage = React.useCallback(async (params) => {
53
+ const result = await listScenarioRuns({
54
+ limit: params.limit,
55
+ startingAfter: params.startingAt,
56
+ benchmarkRunId,
57
+ });
58
+ return {
59
+ items: result.scenarioRuns,
60
+ hasMore: result.hasMore,
61
+ totalCount: result.totalCount,
62
+ };
63
+ }, [benchmarkRunId]);
64
+ // Use the shared pagination hook
65
+ const { items: scenarioRuns, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
66
+ fetchPage,
67
+ pageSize: PAGE_SIZE,
68
+ getItemId: (run) => run.id,
69
+ pollInterval: 5000,
70
+ pollingEnabled: !showPopup && !search.searchMode,
71
+ deps: [PAGE_SIZE, benchmarkRunId],
72
+ });
73
+ // Operations for scenario runs
74
+ const operations = React.useMemo(() => [
75
+ {
76
+ key: "view_details",
77
+ label: "View Details",
78
+ color: colors.primary,
79
+ icon: figures.pointer,
80
+ },
81
+ ], []);
82
+ // Build columns
83
+ const columns = React.useMemo(() => [
84
+ createTextColumn("id", "ID", (run) => run.id, {
85
+ width: idWidth + 1,
86
+ color: colors.idColor,
87
+ dimColor: false,
88
+ bold: false,
89
+ }),
90
+ createTextColumn("name", "Name", (run) => run.name || "", {
91
+ width: nameWidth,
92
+ }),
93
+ createComponentColumn("status", "Status", (run, _index, isSelected) => {
94
+ const statusDisplay = getStatusDisplay(run.state);
95
+ const text = statusDisplay.text
96
+ .slice(0, statusWidth)
97
+ .padEnd(statusWidth, " ");
98
+ return (_jsx(Text, { color: isSelected ? colors.text : statusDisplay.color, bold: isSelected, inverse: isSelected, children: text }));
99
+ }, { width: statusWidth }),
100
+ createTextColumn("score", "Score", (run) => {
101
+ const score = run.scoring_contract_result?.score;
102
+ return score !== undefined ? String(score) : "";
103
+ }, {
104
+ width: scoreWidth,
105
+ color: colors.info,
106
+ }),
107
+ createTextColumn("created", "Created", (run) => run.start_time_ms ? formatTimeAgo(run.start_time_ms) : "", {
108
+ width: timeWidth,
109
+ color: colors.textDim,
110
+ dimColor: false,
111
+ bold: false,
112
+ }),
113
+ ], [idWidth, nameWidth, statusWidth, scoreWidth, timeWidth]);
114
+ // Handle Ctrl+C to exit
115
+ useExitOnCtrlC();
116
+ // Ensure selected index is within bounds
117
+ React.useEffect(() => {
118
+ if (scenarioRuns.length > 0 && selectedIndex >= scenarioRuns.length) {
119
+ setSelectedIndex(Math.max(0, scenarioRuns.length - 1));
120
+ }
121
+ }, [scenarioRuns.length, selectedIndex]);
122
+ const selectedRun = scenarioRuns[selectedIndex];
123
+ // Calculate pagination info for display
124
+ const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
125
+ const startIndex = currentPage * PAGE_SIZE;
126
+ const endIndex = startIndex + scenarioRuns.length;
127
+ useInput((input, key) => {
128
+ // Handle search mode input
129
+ if (search.searchMode) {
130
+ if (key.escape) {
131
+ search.cancelSearch();
132
+ }
133
+ return;
134
+ }
135
+ // Handle popup navigation
136
+ if (showPopup) {
137
+ if (key.upArrow && selectedOperation > 0) {
138
+ setSelectedOperation(selectedOperation - 1);
139
+ }
140
+ else if (key.downArrow && selectedOperation < operations.length - 1) {
141
+ setSelectedOperation(selectedOperation + 1);
142
+ }
143
+ else if (key.return) {
144
+ setShowPopup(false);
145
+ const operationKey = operations[selectedOperation].key;
146
+ if (operationKey === "view_details") {
147
+ navigate("scenario-run-detail", {
148
+ scenarioRunId: selectedRun.id,
149
+ benchmarkRunId,
150
+ });
151
+ }
152
+ }
153
+ else if (input === "v" && selectedRun) {
154
+ setShowPopup(false);
155
+ navigate("scenario-run-detail", {
156
+ scenarioRunId: selectedRun.id,
157
+ benchmarkRunId,
158
+ });
159
+ }
160
+ else if (key.escape || input === "q") {
161
+ setShowPopup(false);
162
+ setSelectedOperation(0);
163
+ }
164
+ return;
165
+ }
166
+ const pageRuns = scenarioRuns.length;
167
+ // Handle list view navigation
168
+ if (key.upArrow && selectedIndex > 0) {
169
+ setSelectedIndex(selectedIndex - 1);
170
+ }
171
+ else if (key.downArrow && selectedIndex < pageRuns - 1) {
172
+ setSelectedIndex(selectedIndex + 1);
173
+ }
174
+ else if ((input === "n" || key.rightArrow) &&
175
+ !loading &&
176
+ !navigating &&
177
+ hasMore) {
178
+ nextPage();
179
+ setSelectedIndex(0);
180
+ }
181
+ else if ((input === "p" || key.leftArrow) &&
182
+ !loading &&
183
+ !navigating &&
184
+ hasPrev) {
185
+ prevPage();
186
+ setSelectedIndex(0);
187
+ }
188
+ else if (key.return && selectedRun) {
189
+ navigate("scenario-run-detail", {
190
+ scenarioRunId: selectedRun.id,
191
+ benchmarkRunId,
192
+ });
193
+ }
194
+ else if (input === "a" && selectedRun) {
195
+ setShowPopup(true);
196
+ setSelectedOperation(0);
197
+ }
198
+ else if (input === "/") {
199
+ search.enterSearchMode();
200
+ }
201
+ else if (key.escape) {
202
+ if (search.handleEscape()) {
203
+ return;
204
+ }
205
+ goBack();
206
+ }
207
+ });
208
+ // Build breadcrumb items
209
+ const breadcrumbItems = [
210
+ { label: "Home" },
211
+ { label: "Benchmarks" },
212
+ ];
213
+ if (benchmarkRunId) {
214
+ breadcrumbItems.push({
215
+ label: `Run: ${benchmarkRunId.substring(0, 8)}...`,
216
+ });
217
+ }
218
+ breadcrumbItems.push({ label: "Scenario Runs", active: true });
219
+ // Loading state
220
+ if (loading && scenarioRuns.length === 0) {
221
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsx(SpinnerComponent, { message: "Loading scenario runs..." })] }));
222
+ }
223
+ // Error state
224
+ if (error) {
225
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsx(ErrorMessage, { message: "Failed to list scenario runs", error: error })] }));
226
+ }
227
+ // Main list view
228
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search scenario runs..." }), !showPopup && (_jsx(Table, { data: scenarioRuns, keyExtractor: (run) => run.id, selectedIndex: selectedIndex, title: `scenario_runs[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No scenario runs 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] }), benchmarkRunId && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.info, children: ["Run: ", benchmarkRunId.substring(0, 8), "..."] })] }))] })), showPopup && selectedRun && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedRun, operations: operations.map((op) => ({
229
+ key: op.key,
230
+ label: op.label,
231
+ color: op.color,
232
+ icon: op.icon,
233
+ shortcut: op.key === "view_details" ? "v" : "",
234
+ })), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
235
+ {
236
+ icon: `${figures.arrowLeft}${figures.arrowRight}`,
237
+ label: "Page",
238
+ condition: hasMore || hasPrev,
239
+ },
240
+ { key: "Enter", label: "Details" },
241
+ { key: "a", label: "Actions" },
242
+ { key: "/", label: "Search" },
243
+ { key: "Esc", label: "Back" },
244
+ ] })] }));
245
+ }
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useNavigation } from "../store/navigationStore.js";
3
+ import { SecretCreatePage } from "../components/SecretCreatePage.js";
4
+ export function SecretCreateScreen() {
5
+ const { goBack, navigate } = useNavigation();
6
+ return (_jsx(SecretCreatePage, { onBack: goBack, onCreate: (secret) => navigate("secret-detail", { secretId: secret.id }) }));
7
+ }
@@ -0,0 +1,198 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * SecretDetailScreen - Detail page for secrets
4
+ * Uses the generic ResourceDetailPage component
5
+ */
6
+ import React from "react";
7
+ import { Text } from "ink";
8
+ import figures from "figures";
9
+ import { useNavigation } from "../store/navigationStore.js";
10
+ import { ResourceDetailPage, formatTimestamp, } from "../components/ResourceDetailPage.js";
11
+ import { getClient } from "../utils/client.js";
12
+ import { SpinnerComponent } from "../components/Spinner.js";
13
+ import { ErrorMessage } from "../components/ErrorMessage.js";
14
+ import { Breadcrumb } from "../components/Breadcrumb.js";
15
+ import { ConfirmationPrompt } from "../components/ConfirmationPrompt.js";
16
+ import { colors } from "../utils/theme.js";
17
+ export function SecretDetailScreen({ secretId }) {
18
+ const { goBack } = useNavigation();
19
+ const [loading, setLoading] = React.useState(false);
20
+ const [error, setError] = React.useState(null);
21
+ const [secret, setSecret] = React.useState(null);
22
+ const [deleting, setDeleting] = React.useState(false);
23
+ const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
24
+ // Fetch secret from API
25
+ React.useEffect(() => {
26
+ if (secretId && !loading && !secret) {
27
+ setLoading(true);
28
+ setError(null);
29
+ const client = getClient();
30
+ // Secrets API doesn't have a direct get by ID, so we list all and find
31
+ client.secrets
32
+ .list({ limit: 5000 })
33
+ .then((result) => {
34
+ const found = result.secrets?.find((s) => s.id === secretId || s.name === secretId);
35
+ if (found) {
36
+ setSecret(found);
37
+ }
38
+ else {
39
+ setError(new Error("Secret not found"));
40
+ }
41
+ setLoading(false);
42
+ })
43
+ .catch((err) => {
44
+ setError(err);
45
+ setLoading(false);
46
+ });
47
+ }
48
+ }, [secretId, loading, secret]);
49
+ // Show loading state while fetching or before fetch starts
50
+ if (!secret && secretId && !error) {
51
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
52
+ { label: "Settings" },
53
+ { label: "Secrets" },
54
+ { label: "Loading...", active: true },
55
+ ] }), _jsx(SpinnerComponent, { message: "Loading secret details..." })] }));
56
+ }
57
+ // Show error state if fetch failed
58
+ if (error && !secret) {
59
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
60
+ { label: "Settings" },
61
+ { label: "Secrets" },
62
+ { label: "Error", active: true },
63
+ ] }), _jsx(ErrorMessage, { message: "Failed to load secret details", error: error })] }));
64
+ }
65
+ // Show error if no secret found
66
+ if (!secret) {
67
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
68
+ { label: "Settings" },
69
+ { label: "Secrets" },
70
+ { label: "Not Found", active: true },
71
+ ] }), _jsx(ErrorMessage, { message: `Secret ${secretId || "unknown"} not found`, error: new Error("Secret not found") })] }));
72
+ }
73
+ // Build detail sections
74
+ const detailSections = [];
75
+ // Basic details section
76
+ const basicFields = [];
77
+ basicFields.push({
78
+ label: "Name",
79
+ value: secret.name,
80
+ });
81
+ if (secret.create_time_ms) {
82
+ basicFields.push({
83
+ label: "Created",
84
+ value: formatTimestamp(secret.create_time_ms),
85
+ });
86
+ }
87
+ if (secret.update_time_ms) {
88
+ basicFields.push({
89
+ label: "Last Updated",
90
+ value: formatTimestamp(secret.update_time_ms),
91
+ });
92
+ }
93
+ detailSections.push({
94
+ title: "Details",
95
+ icon: figures.squareSmallFilled,
96
+ color: colors.warning,
97
+ fields: basicFields,
98
+ });
99
+ // Security notice section
100
+ detailSections.push({
101
+ title: "Security",
102
+ icon: figures.warning,
103
+ color: colors.info,
104
+ fields: [
105
+ {
106
+ label: "Value",
107
+ value: (_jsx(Text, { color: colors.textDim, dimColor: true, children: "Secret values are never displayed for security reasons" })),
108
+ },
109
+ ],
110
+ });
111
+ // Operations available for secrets (only delete - secrets are immutable)
112
+ const operations = [
113
+ {
114
+ key: "delete",
115
+ label: "Delete Secret",
116
+ color: colors.error,
117
+ icon: figures.cross,
118
+ shortcut: "d",
119
+ },
120
+ ];
121
+ // Handle operation selection
122
+ const handleOperation = async (operation, _resource) => {
123
+ switch (operation) {
124
+ case "delete":
125
+ // Show confirmation dialog
126
+ setShowDeleteConfirm(true);
127
+ break;
128
+ }
129
+ };
130
+ // Execute delete after confirmation
131
+ const executeDelete = async () => {
132
+ if (!secret)
133
+ return;
134
+ setShowDeleteConfirm(false);
135
+ setDeleting(true);
136
+ try {
137
+ const client = getClient();
138
+ await client.secrets.delete(secret.name);
139
+ goBack();
140
+ }
141
+ catch (err) {
142
+ setError(err);
143
+ setDeleting(false);
144
+ }
145
+ };
146
+ // Build detailed info lines for full details view
147
+ const buildDetailLines = (s) => {
148
+ const lines = [];
149
+ // Core Information
150
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Secret Details" }, "core-title"));
151
+ lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "ID: ", s.id] }, "core-id"));
152
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Name: ", s.name] }, "core-name"));
153
+ if (s.create_time_ms) {
154
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Created: ", new Date(s.create_time_ms).toLocaleString()] }, "core-created"));
155
+ }
156
+ if (s.update_time_ms) {
157
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Last Updated: ", new Date(s.update_time_ms).toLocaleString()] }, "core-updated"));
158
+ }
159
+ lines.push(_jsx(Text, { children: " " }, "core-space"));
160
+ // Security Notice
161
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Security Notice" }, "security-title"));
162
+ lines.push(_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "Secret values are write-only and cannot be retrieved."] }, "security-notice"));
163
+ lines.push(_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "To change a secret, delete it and create a new one with the same name."] }, "security-notice2"));
164
+ lines.push(_jsx(Text, { children: " " }, "security-space"));
165
+ // Raw JSON (without value)
166
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Raw JSON" }, "json-title"));
167
+ const jsonObj = {
168
+ id: s.id,
169
+ name: s.name,
170
+ create_time_ms: s.create_time_ms,
171
+ update_time_ms: s.update_time_ms,
172
+ };
173
+ const jsonLines = JSON.stringify(jsonObj, null, 2).split("\n");
174
+ jsonLines.forEach((line, idx) => {
175
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", line] }, `json-${idx}`));
176
+ });
177
+ return lines;
178
+ };
179
+ // Show delete confirmation
180
+ if (showDeleteConfirm && secret) {
181
+ return (_jsx(ConfirmationPrompt, { title: "Delete Secret", message: `Are you sure you want to delete "${secret.name}"?`, details: "This action cannot be undone. Any devboxes using this secret will no longer have access to it.", breadcrumbItems: [
182
+ { label: "Settings" },
183
+ { label: "Secrets" },
184
+ { label: secret.name || secret.id },
185
+ { label: "Delete", active: true },
186
+ ], onConfirm: executeDelete, onCancel: () => setShowDeleteConfirm(false) }));
187
+ }
188
+ // Show deleting state
189
+ if (deleting) {
190
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
191
+ { label: "Settings" },
192
+ { label: "Secrets" },
193
+ { label: secret.name || secret.id },
194
+ { label: "Deleting...", active: true },
195
+ ] }), _jsx(SpinnerComponent, { message: "Deleting secret..." })] }));
196
+ }
197
+ return (_jsx(ResourceDetailPage, { resource: secret, resourceType: "Secrets", breadcrumbPrefix: [{ label: "Settings" }], getDisplayName: (s) => s.name || s.id, getId: (s) => s.id, getStatus: () => "active", detailSections: detailSections, operations: operations, onOperation: handleOperation, onBack: goBack, buildDetailLines: buildDetailLines }));
198
+ }