@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,275 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * BenchmarkRunListScreen - List view for benchmark 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 { listBenchmarkRuns } from "../services/benchmarkService.js";
24
+ export function BenchmarkRunListScreen() {
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 listBenchmarkRuns({
53
+ limit: params.limit,
54
+ startingAfter: params.startingAt,
55
+ });
56
+ return {
57
+ items: result.benchmarkRuns,
58
+ hasMore: result.hasMore,
59
+ totalCount: result.totalCount,
60
+ };
61
+ }, []);
62
+ // Use the shared pagination hook
63
+ const { items: benchmarkRuns, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
64
+ fetchPage,
65
+ pageSize: PAGE_SIZE,
66
+ getItemId: (run) => run.id,
67
+ pollInterval: 5000,
68
+ pollingEnabled: !showPopup && !search.searchMode,
69
+ deps: [PAGE_SIZE],
70
+ });
71
+ // Operations for benchmark runs
72
+ const operations = React.useMemo(() => [
73
+ {
74
+ key: "view_details",
75
+ label: "View Details",
76
+ color: colors.primary,
77
+ icon: figures.pointer,
78
+ },
79
+ {
80
+ key: "view_scenarios",
81
+ label: "View Scenario Runs",
82
+ color: colors.info,
83
+ icon: figures.arrowRight,
84
+ },
85
+ {
86
+ key: "create_job",
87
+ label: "Create Job",
88
+ color: colors.success,
89
+ icon: figures.play,
90
+ },
91
+ ], []);
92
+ // Build columns
93
+ const columns = React.useMemo(() => [
94
+ createTextColumn("id", "ID", (run) => run.id, {
95
+ width: idWidth + 1,
96
+ color: colors.idColor,
97
+ dimColor: false,
98
+ bold: false,
99
+ }),
100
+ createTextColumn("name", "Name", (run) => run.name || "", {
101
+ width: nameWidth,
102
+ }),
103
+ createComponentColumn("status", "Status", (run, _index, isSelected) => {
104
+ const statusDisplay = getStatusDisplay(run.state);
105
+ const text = statusDisplay.text
106
+ .slice(0, statusWidth)
107
+ .padEnd(statusWidth, " ");
108
+ return (_jsx(Text, { color: isSelected ? colors.text : statusDisplay.color, bold: isSelected, inverse: isSelected, children: text }));
109
+ }, { width: statusWidth }),
110
+ createTextColumn("created", "Created", (run) => run.start_time_ms ? formatTimeAgo(run.start_time_ms) : "", {
111
+ width: timeWidth,
112
+ color: colors.textDim,
113
+ dimColor: false,
114
+ bold: false,
115
+ }),
116
+ ], [idWidth, nameWidth, statusWidth, timeWidth]);
117
+ // Handle Ctrl+C to exit
118
+ useExitOnCtrlC();
119
+ // Ensure selected index is within bounds
120
+ React.useEffect(() => {
121
+ if (benchmarkRuns.length > 0 && selectedIndex >= benchmarkRuns.length) {
122
+ setSelectedIndex(Math.max(0, benchmarkRuns.length - 1));
123
+ }
124
+ }, [benchmarkRuns.length, selectedIndex]);
125
+ const selectedRun = benchmarkRuns[selectedIndex];
126
+ // Calculate pagination info for display
127
+ const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
128
+ const startIndex = currentPage * PAGE_SIZE;
129
+ const endIndex = startIndex + benchmarkRuns.length;
130
+ useInput((input, key) => {
131
+ // Handle search mode input
132
+ if (search.searchMode) {
133
+ if (key.escape) {
134
+ search.cancelSearch();
135
+ }
136
+ return;
137
+ }
138
+ // Handle popup navigation
139
+ if (showPopup) {
140
+ if (key.upArrow && selectedOperation > 0) {
141
+ setSelectedOperation(selectedOperation - 1);
142
+ }
143
+ else if (key.downArrow && selectedOperation < operations.length - 1) {
144
+ setSelectedOperation(selectedOperation + 1);
145
+ }
146
+ else if (key.return) {
147
+ setShowPopup(false);
148
+ const operationKey = operations[selectedOperation].key;
149
+ if (operationKey === "view_details") {
150
+ navigate("benchmark-run-detail", {
151
+ benchmarkRunId: selectedRun.id,
152
+ });
153
+ }
154
+ else if (operationKey === "view_scenarios") {
155
+ navigate("scenario-run-list", {
156
+ benchmarkRunId: selectedRun.id,
157
+ });
158
+ }
159
+ else if (operationKey === "create_job") {
160
+ navigate("benchmark-job-create");
161
+ }
162
+ }
163
+ else if (input === "v" && selectedRun) {
164
+ setShowPopup(false);
165
+ navigate("benchmark-run-detail", {
166
+ benchmarkRunId: selectedRun.id,
167
+ });
168
+ }
169
+ else if (input === "s" && selectedRun) {
170
+ setShowPopup(false);
171
+ navigate("scenario-run-list", {
172
+ benchmarkRunId: selectedRun.id,
173
+ });
174
+ }
175
+ else if (input === "j") {
176
+ setShowPopup(false);
177
+ navigate("benchmark-job-create");
178
+ }
179
+ else if (key.escape || input === "q") {
180
+ setShowPopup(false);
181
+ setSelectedOperation(0);
182
+ }
183
+ return;
184
+ }
185
+ const pageRuns = benchmarkRuns.length;
186
+ // Handle list view navigation
187
+ if (key.upArrow && selectedIndex > 0) {
188
+ setSelectedIndex(selectedIndex - 1);
189
+ }
190
+ else if (key.downArrow && selectedIndex < pageRuns - 1) {
191
+ setSelectedIndex(selectedIndex + 1);
192
+ }
193
+ else if ((input === "n" || key.rightArrow) &&
194
+ !loading &&
195
+ !navigating &&
196
+ hasMore) {
197
+ nextPage();
198
+ setSelectedIndex(0);
199
+ }
200
+ else if ((input === "p" || key.leftArrow) &&
201
+ !loading &&
202
+ !navigating &&
203
+ hasPrev) {
204
+ prevPage();
205
+ setSelectedIndex(0);
206
+ }
207
+ else if (key.return && selectedRun) {
208
+ navigate("benchmark-run-detail", {
209
+ benchmarkRunId: selectedRun.id,
210
+ });
211
+ }
212
+ else if (input === "a" && selectedRun) {
213
+ setShowPopup(true);
214
+ setSelectedOperation(0);
215
+ }
216
+ else if (input === "j") {
217
+ // Quick shortcut to create a new job
218
+ navigate("benchmark-job-create");
219
+ }
220
+ else if (input === "/") {
221
+ search.enterSearchMode();
222
+ }
223
+ else if (key.escape) {
224
+ if (search.handleEscape()) {
225
+ return;
226
+ }
227
+ goBack();
228
+ }
229
+ });
230
+ // Loading state
231
+ if (loading && benchmarkRuns.length === 0) {
232
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
233
+ { label: "Home" },
234
+ { label: "Benchmarks" },
235
+ { label: "Benchmark Runs", active: true },
236
+ ] }), _jsx(SpinnerComponent, { message: "Loading benchmark runs..." })] }));
237
+ }
238
+ // Error state
239
+ if (error) {
240
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
241
+ { label: "Home" },
242
+ { label: "Benchmarks" },
243
+ { label: "Benchmark Runs", active: true },
244
+ ] }), _jsx(ErrorMessage, { message: "Failed to list benchmark runs", error: error })] }));
245
+ }
246
+ // Main list view
247
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
248
+ { label: "Home" },
249
+ { label: "Benchmarks" },
250
+ { label: "Benchmark Runs", active: true },
251
+ ] }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search benchmark runs..." }), !showPopup && (_jsx(Table, { data: benchmarkRuns, keyExtractor: (run) => run.id, selectedIndex: selectedIndex, title: `benchmark_runs[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No benchmark 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] })] })), showPopup && selectedRun && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedRun, operations: operations.map((op) => ({
252
+ key: op.key,
253
+ label: op.label,
254
+ color: op.color,
255
+ icon: op.icon,
256
+ shortcut: op.key === "view_details"
257
+ ? "v"
258
+ : op.key === "view_scenarios"
259
+ ? "s"
260
+ : op.key === "create_job"
261
+ ? "j"
262
+ : "",
263
+ })), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
264
+ {
265
+ icon: `${figures.arrowLeft}${figures.arrowRight}`,
266
+ label: "Page",
267
+ condition: hasMore || hasPrev,
268
+ },
269
+ { key: "Enter", label: "Details" },
270
+ { key: "j", label: "New Job" },
271
+ { key: "a", label: "Actions" },
272
+ { key: "/", label: "Search" },
273
+ { key: "Esc", label: "Back" },
274
+ ] })] }));
275
+ }
@@ -351,5 +351,9 @@ export function BlueprintDetailScreen({ blueprintId, }) {
351
351
  });
352
352
  return lines;
353
353
  };
354
- return (_jsx(ResourceDetailPage, { resource: blueprint, resourceType: "Blueprints", getDisplayName: (bp) => bp.name || bp.id, getId: (bp) => bp.id, getStatus: (bp) => bp.status, detailSections: detailSections, operations: operations, onOperation: handleOperation, onBack: goBack, buildDetailLines: buildDetailLines, pollResource: blueprint.status === "building" ? pollBlueprint : undefined }));
354
+ return (_jsx(ResourceDetailPage, { resource: blueprint, resourceType: "Blueprints", getDisplayName: (bp) => bp.name || bp.id, getId: (bp) => bp.id, getStatus: (bp) => bp.status, detailSections: detailSections, operations: operations, onOperation: handleOperation, onBack: goBack, buildDetailLines: buildDetailLines, pollResource: blueprint.status === "queued" ||
355
+ blueprint.status === "provisioning" ||
356
+ blueprint.status === "building"
357
+ ? pollBlueprint
358
+ : undefined }));
355
359
  }
@@ -2,10 +2,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useNavigation } from "../store/navigationStore.js";
3
3
  import { DevboxCreatePage } from "../components/DevboxCreatePage.js";
4
4
  export function DevboxCreateScreen() {
5
- const { goBack, navigate } = useNavigation();
5
+ const { goBack, navigate, params } = useNavigation();
6
6
  const handleCreate = (devbox) => {
7
7
  // After creation, navigate to the devbox detail page
8
8
  navigate("devbox-detail", { devboxId: devbox.id });
9
9
  };
10
- return _jsx(DevboxCreatePage, { onBack: goBack, onCreate: handleCreate });
10
+ return (_jsx(DevboxCreatePage, { onBack: goBack, onCreate: handleCreate, initialBlueprintId: params.blueprintId, initialSnapshotId: params.snapshotId }));
11
11
  }
@@ -0,0 +1,236 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * GatewayConfigDetailScreen - Detail page for gateway configs
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 { useGatewayConfigStore, } from "../store/gatewayConfigStore.js";
11
+ import { ResourceDetailPage, formatTimestamp, } from "../components/ResourceDetailPage.js";
12
+ import { getGatewayConfig, deleteGatewayConfig, } from "../services/gatewayConfigService.js";
13
+ import { SpinnerComponent } from "../components/Spinner.js";
14
+ import { ErrorMessage } from "../components/ErrorMessage.js";
15
+ import { Breadcrumb } from "../components/Breadcrumb.js";
16
+ import { ConfirmationPrompt } from "../components/ConfirmationPrompt.js";
17
+ import { GatewayConfigCreatePage } from "../components/GatewayConfigCreatePage.js";
18
+ import { colors } from "../utils/theme.js";
19
+ /**
20
+ * Get a display label for the auth mechanism type
21
+ */
22
+ function getAuthTypeLabel(authMechanism) {
23
+ if (authMechanism.type === "bearer") {
24
+ return "Bearer Token";
25
+ }
26
+ if (authMechanism.type === "header") {
27
+ return authMechanism.key ? `Header: ${authMechanism.key}` : "Header";
28
+ }
29
+ return authMechanism.type;
30
+ }
31
+ export function GatewayConfigDetailScreen({ gatewayConfigId, }) {
32
+ const { goBack } = useNavigation();
33
+ const gatewayConfigs = useGatewayConfigStore((state) => state.gatewayConfigs);
34
+ const [loading, setLoading] = React.useState(false);
35
+ const [error, setError] = React.useState(null);
36
+ const [fetchedConfig, setFetchedConfig] = React.useState(null);
37
+ const [deleting, setDeleting] = React.useState(false);
38
+ const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
39
+ const [showEditForm, setShowEditForm] = React.useState(false);
40
+ // Find config in store first
41
+ const configFromStore = gatewayConfigs.find((c) => c.id === gatewayConfigId);
42
+ // Fetch config from API if not in store or missing full details
43
+ React.useEffect(() => {
44
+ if (gatewayConfigId && !loading && !fetchedConfig) {
45
+ // Always fetch full details since store may only have basic info
46
+ setLoading(true);
47
+ setError(null);
48
+ getGatewayConfig(gatewayConfigId)
49
+ .then((config) => {
50
+ setFetchedConfig(config);
51
+ setLoading(false);
52
+ })
53
+ .catch((err) => {
54
+ setError(err);
55
+ setLoading(false);
56
+ });
57
+ }
58
+ }, [gatewayConfigId, loading, fetchedConfig]);
59
+ // Use fetched config for full details, fall back to store for basic display
60
+ const config = fetchedConfig || configFromStore;
61
+ // Show loading state while fetching or before fetch starts
62
+ if (!config && gatewayConfigId && !error) {
63
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
64
+ { label: "Gateway Configs" },
65
+ { label: "Loading...", active: true },
66
+ ] }), _jsx(SpinnerComponent, { message: "Loading gateway config details..." })] }));
67
+ }
68
+ // Show error state if fetch failed
69
+ if (error && !config) {
70
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
71
+ { label: "Gateway Configs" },
72
+ { label: "Error", active: true },
73
+ ] }), _jsx(ErrorMessage, { message: "Failed to load gateway config details", error: error })] }));
74
+ }
75
+ // Show error if no config found
76
+ if (!config) {
77
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
78
+ { label: "Gateway Configs" },
79
+ { label: "Not Found", active: true },
80
+ ] }), _jsx(ErrorMessage, { message: `Gateway config ${gatewayConfigId || "unknown"} not found`, error: new Error("Gateway config not found") })] }));
81
+ }
82
+ // Build detail sections
83
+ const detailSections = [];
84
+ // Basic details section
85
+ const basicFields = [];
86
+ if (config.description) {
87
+ basicFields.push({
88
+ label: "Description",
89
+ value: config.description,
90
+ });
91
+ }
92
+ basicFields.push({
93
+ label: "Endpoint",
94
+ value: config.endpoint,
95
+ });
96
+ if (config.create_time_ms) {
97
+ basicFields.push({
98
+ label: "Created",
99
+ value: formatTimestamp(config.create_time_ms),
100
+ });
101
+ }
102
+ if (config.account_id) {
103
+ basicFields.push({
104
+ label: "Account ID",
105
+ value: config.account_id,
106
+ });
107
+ }
108
+ if (basicFields.length > 0) {
109
+ detailSections.push({
110
+ title: "Details",
111
+ icon: figures.squareSmallFilled,
112
+ color: colors.warning,
113
+ fields: basicFields,
114
+ });
115
+ }
116
+ // Auth mechanism section
117
+ const authFields = [];
118
+ authFields.push({
119
+ label: "Auth Type",
120
+ value: (_jsx(Text, { color: colors.info, bold: true, children: getAuthTypeLabel(config.auth_mechanism) })),
121
+ });
122
+ if (config.auth_mechanism.type === "header" && config.auth_mechanism.key) {
123
+ authFields.push({
124
+ label: "Header Key",
125
+ value: config.auth_mechanism.key,
126
+ });
127
+ }
128
+ detailSections.push({
129
+ title: "Authentication",
130
+ icon: figures.arrowRight,
131
+ color: colors.info,
132
+ fields: authFields,
133
+ });
134
+ // Operations available for gateway configs
135
+ const operations = [
136
+ {
137
+ key: "edit",
138
+ label: "Edit Gateway Config",
139
+ color: colors.warning,
140
+ icon: figures.pointer,
141
+ shortcut: "e",
142
+ },
143
+ {
144
+ key: "delete",
145
+ label: "Delete Gateway Config",
146
+ color: colors.error,
147
+ icon: figures.cross,
148
+ shortcut: "d",
149
+ },
150
+ ];
151
+ // Handle operation selection
152
+ const handleOperation = async (operation, _resource) => {
153
+ switch (operation) {
154
+ case "edit":
155
+ setShowEditForm(true);
156
+ break;
157
+ case "delete":
158
+ // Show confirmation dialog
159
+ setShowDeleteConfirm(true);
160
+ break;
161
+ }
162
+ };
163
+ // Execute delete after confirmation
164
+ const executeDelete = async () => {
165
+ if (!config)
166
+ return;
167
+ setShowDeleteConfirm(false);
168
+ setDeleting(true);
169
+ try {
170
+ await deleteGatewayConfig(config.id);
171
+ goBack();
172
+ }
173
+ catch (err) {
174
+ setError(err);
175
+ setDeleting(false);
176
+ }
177
+ };
178
+ // Build detailed info lines for full details view
179
+ const buildDetailLines = (gc) => {
180
+ const lines = [];
181
+ // Core Information
182
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Gateway Config Details" }, "core-title"));
183
+ lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "ID: ", gc.id] }, "core-id"));
184
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Name: ", gc.name] }, "core-name"));
185
+ if (gc.description) {
186
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Description: ", gc.description] }, "core-desc"));
187
+ }
188
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Endpoint: ", gc.endpoint] }, "core-endpoint"));
189
+ if (gc.create_time_ms) {
190
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Created: ", new Date(gc.create_time_ms).toLocaleString()] }, "core-created"));
191
+ }
192
+ if (gc.account_id) {
193
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Account ID: ", gc.account_id] }, "core-account"));
194
+ }
195
+ lines.push(_jsx(Text, { children: " " }, "core-space"));
196
+ // Auth Mechanism
197
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Authentication" }, "auth-title"));
198
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Type: ", getAuthTypeLabel(gc.auth_mechanism)] }, "auth-type"));
199
+ if (gc.auth_mechanism.type === "header" && gc.auth_mechanism.key) {
200
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Header Key: ", gc.auth_mechanism.key] }, "auth-key"));
201
+ }
202
+ lines.push(_jsx(Text, { children: " " }, "auth-space"));
203
+ // Raw JSON
204
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Raw JSON" }, "json-title"));
205
+ const jsonLines = JSON.stringify(gc, null, 2).split("\n");
206
+ jsonLines.forEach((line, idx) => {
207
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", line] }, `json-${idx}`));
208
+ });
209
+ return lines;
210
+ };
211
+ // Show edit form
212
+ if (showEditForm && config) {
213
+ return (_jsx(GatewayConfigCreatePage, { onBack: () => setShowEditForm(false), onCreate: (updatedConfig) => {
214
+ // Update the fetched config with the new data
215
+ setFetchedConfig(updatedConfig);
216
+ setShowEditForm(false);
217
+ }, initialConfig: config }));
218
+ }
219
+ // Show delete confirmation
220
+ if (showDeleteConfirm && config) {
221
+ return (_jsx(ConfirmationPrompt, { title: "Delete Gateway Config", message: `Are you sure you want to delete "${config.name || config.id}"?`, details: "This action cannot be undone. Any devboxes using this gateway config will no longer have access to it.", breadcrumbItems: [
222
+ { label: "Gateway Configs" },
223
+ { label: config.name || config.id },
224
+ { label: "Delete", active: true },
225
+ ], onConfirm: executeDelete, onCancel: () => setShowDeleteConfirm(false) }));
226
+ }
227
+ // Show deleting state
228
+ if (deleting) {
229
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
230
+ { label: "Gateway Configs" },
231
+ { label: config.name || config.id },
232
+ { label: "Deleting...", active: true },
233
+ ] }), _jsx(SpinnerComponent, { message: "Deleting gateway config..." })] }));
234
+ }
235
+ return (_jsx(ResourceDetailPage, { resource: config, resourceType: "Gateway Configs", getDisplayName: (gc) => gc.name || gc.id, getId: (gc) => gc.id, getStatus: () => "active", detailSections: detailSections, operations: operations, onOperation: handleOperation, onBack: goBack, buildDetailLines: buildDetailLines }));
236
+ }
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useNavigation } from "../store/navigationStore.js";
3
+ import { ListGatewayConfigsUI } from "../commands/gateway-config/list.js";
4
+ export function GatewayConfigListScreen() {
5
+ const { goBack } = useNavigation();
6
+ return _jsx(ListGatewayConfigsUI, { onBack: goBack });
7
+ }
@@ -14,12 +14,15 @@ export function MenuScreen() {
14
14
  case "snapshots":
15
15
  navigate("snapshot-list");
16
16
  break;
17
- case "network-policies":
18
- navigate("network-policy-list");
17
+ case "settings":
18
+ navigate("settings-menu");
19
19
  break;
20
20
  case "objects":
21
21
  navigate("object-list");
22
22
  break;
23
+ case "benchmarks":
24
+ navigate("benchmark-menu");
25
+ break;
23
26
  default:
24
27
  // Fallback for any other screen names
25
28
  navigate(key);