@runloop/rl-cli 1.8.0 → 1.10.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.
- package/README.md +21 -7
- package/dist/cli.js +0 -0
- package/dist/commands/blueprint/delete.js +21 -0
- package/dist/commands/blueprint/list.js +226 -174
- package/dist/commands/blueprint/prune.js +13 -28
- package/dist/commands/devbox/create.js +41 -0
- package/dist/commands/devbox/list.js +142 -110
- package/dist/commands/devbox/rsync.js +69 -41
- package/dist/commands/devbox/scp.js +180 -39
- package/dist/commands/devbox/tunnel.js +4 -19
- package/dist/commands/gateway-config/create.js +53 -0
- package/dist/commands/gateway-config/delete.js +21 -0
- package/dist/commands/gateway-config/get.js +18 -0
- package/dist/commands/gateway-config/list.js +493 -0
- package/dist/commands/gateway-config/update.js +70 -0
- package/dist/commands/snapshot/list.js +11 -2
- package/dist/commands/snapshot/prune.js +265 -0
- package/dist/components/BenchmarkMenu.js +23 -3
- package/dist/components/DetailedInfoView.js +20 -0
- package/dist/components/DevboxActionsMenu.js +26 -62
- package/dist/components/DevboxCreatePage.js +763 -15
- package/dist/components/DevboxDetailPage.js +73 -24
- package/dist/components/GatewayConfigCreatePage.js +272 -0
- package/dist/components/LogsViewer.js +6 -40
- package/dist/components/ResourceDetailPage.js +143 -160
- package/dist/components/ResourceListView.js +3 -33
- package/dist/components/ResourcePicker.js +234 -0
- package/dist/components/SecretCreatePage.js +71 -27
- package/dist/components/SettingsMenu.js +12 -2
- package/dist/components/StateHistory.js +1 -20
- package/dist/components/StatusBadge.js +9 -2
- package/dist/components/StreamingLogsViewer.js +8 -42
- package/dist/components/form/FormTextInput.js +4 -2
- package/dist/components/resourceDetailTypes.js +18 -0
- package/dist/hooks/useInputHandler.js +103 -0
- package/dist/router/Router.js +79 -2
- package/dist/screens/BenchmarkDetailScreen.js +163 -0
- package/dist/screens/BenchmarkJobCreateScreen.js +524 -0
- package/dist/screens/BenchmarkJobDetailScreen.js +614 -0
- package/dist/screens/BenchmarkJobListScreen.js +479 -0
- package/dist/screens/BenchmarkListScreen.js +266 -0
- package/dist/screens/BenchmarkMenuScreen.js +6 -0
- package/dist/screens/BenchmarkRunDetailScreen.js +258 -22
- package/dist/screens/BenchmarkRunListScreen.js +21 -1
- package/dist/screens/BlueprintDetailScreen.js +5 -1
- package/dist/screens/DevboxCreateScreen.js +2 -2
- package/dist/screens/GatewayConfigDetailScreen.js +236 -0
- package/dist/screens/GatewayConfigListScreen.js +7 -0
- package/dist/screens/ScenarioRunDetailScreen.js +6 -0
- package/dist/screens/SecretDetailScreen.js +26 -2
- package/dist/screens/SettingsMenuScreen.js +3 -0
- package/dist/screens/SnapshotDetailScreen.js +6 -0
- package/dist/services/agentService.js +42 -0
- package/dist/services/benchmarkJobService.js +122 -0
- package/dist/services/benchmarkService.js +47 -0
- package/dist/services/gatewayConfigService.js +153 -0
- package/dist/services/scenarioService.js +34 -0
- package/dist/store/benchmarkJobStore.js +66 -0
- package/dist/store/benchmarkStore.js +63 -0
- package/dist/store/gatewayConfigStore.js +83 -0
- package/dist/utils/browser.js +22 -0
- package/dist/utils/clipboard.js +41 -0
- package/dist/utils/commands.js +105 -9
- package/dist/utils/gatewayConfigValidation.js +58 -0
- package/dist/utils/time.js +121 -0
- package/package.json +43 -43
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text, useInput, useApp } from "ink";
|
|
4
|
+
import figures from "figures";
|
|
5
|
+
import { getClient } from "../../utils/client.js";
|
|
6
|
+
import { Header } from "../../components/Header.js";
|
|
7
|
+
import { SpinnerComponent } from "../../components/Spinner.js";
|
|
8
|
+
import { ErrorMessage } from "../../components/ErrorMessage.js";
|
|
9
|
+
import { SuccessMessage } from "../../components/SuccessMessage.js";
|
|
10
|
+
import { Breadcrumb } from "../../components/Breadcrumb.js";
|
|
11
|
+
import { NavigationTips } from "../../components/NavigationTips.js";
|
|
12
|
+
import { Table, createTextColumn } from "../../components/Table.js";
|
|
13
|
+
import { ActionsPopup } from "../../components/ActionsPopup.js";
|
|
14
|
+
import { formatTimeAgo } from "../../components/ResourceListView.js";
|
|
15
|
+
import { SearchBar } from "../../components/SearchBar.js";
|
|
16
|
+
import { output, outputError } from "../../utils/output.js";
|
|
17
|
+
import { colors } from "../../utils/theme.js";
|
|
18
|
+
import { useViewportHeight } from "../../hooks/useViewportHeight.js";
|
|
19
|
+
import { useExitOnCtrlC } from "../../hooks/useExitOnCtrlC.js";
|
|
20
|
+
import { useCursorPagination } from "../../hooks/useCursorPagination.js";
|
|
21
|
+
import { useListSearch } from "../../hooks/useListSearch.js";
|
|
22
|
+
import { useNavigation } from "../../store/navigationStore.js";
|
|
23
|
+
import { GatewayConfigCreatePage } from "../../components/GatewayConfigCreatePage.js";
|
|
24
|
+
import { ConfirmationPrompt } from "../../components/ConfirmationPrompt.js";
|
|
25
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
26
|
+
/**
|
|
27
|
+
* Get a display label for the auth mechanism type
|
|
28
|
+
*/
|
|
29
|
+
function getAuthTypeLabel(authMechanism) {
|
|
30
|
+
if (authMechanism.type === "bearer") {
|
|
31
|
+
return "Bearer";
|
|
32
|
+
}
|
|
33
|
+
if (authMechanism.type === "header") {
|
|
34
|
+
return authMechanism.key ? `Header: ${authMechanism.key}` : "Header";
|
|
35
|
+
}
|
|
36
|
+
return authMechanism.type;
|
|
37
|
+
}
|
|
38
|
+
const ListGatewayConfigsUI = ({ onBack, onExit, }) => {
|
|
39
|
+
const { exit: inkExit } = useApp();
|
|
40
|
+
const { navigate } = useNavigation();
|
|
41
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
42
|
+
const [showPopup, setShowPopup] = React.useState(false);
|
|
43
|
+
const [selectedOperation, setSelectedOperation] = React.useState(0);
|
|
44
|
+
const [selectedConfig, setSelectedConfig] = React.useState(null);
|
|
45
|
+
const [executingOperation, setExecutingOperation] = React.useState(null);
|
|
46
|
+
const [operationResult, setOperationResult] = React.useState(null);
|
|
47
|
+
const [operationError, setOperationError] = React.useState(null);
|
|
48
|
+
const [operationLoading, setOperationLoading] = React.useState(false);
|
|
49
|
+
const [showCreateConfig, setShowCreateConfig] = React.useState(false);
|
|
50
|
+
const [showEditConfig, setShowEditConfig] = React.useState(false);
|
|
51
|
+
const [editingConfig, setEditingConfig] = React.useState(null);
|
|
52
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
|
53
|
+
// Search state
|
|
54
|
+
const search = useListSearch({
|
|
55
|
+
onSearchSubmit: () => setSelectedIndex(0),
|
|
56
|
+
onSearchClear: () => setSelectedIndex(0),
|
|
57
|
+
});
|
|
58
|
+
// Calculate overhead for viewport height
|
|
59
|
+
const overhead = 13 + search.getSearchOverhead();
|
|
60
|
+
const { viewportHeight, terminalWidth } = useViewportHeight({
|
|
61
|
+
overhead,
|
|
62
|
+
minHeight: 5,
|
|
63
|
+
});
|
|
64
|
+
const PAGE_SIZE = viewportHeight;
|
|
65
|
+
// All width constants
|
|
66
|
+
const fixedWidth = 6; // border + padding
|
|
67
|
+
const idWidth = 25;
|
|
68
|
+
const authWidth = 15;
|
|
69
|
+
const timeWidth = 20;
|
|
70
|
+
const showEndpoint = terminalWidth >= 100;
|
|
71
|
+
const endpointWidth = Math.max(20, terminalWidth >= 140 ? 40 : 25);
|
|
72
|
+
// Name width uses remaining space after fixed columns
|
|
73
|
+
const baseWidth = fixedWidth + idWidth + authWidth + timeWidth;
|
|
74
|
+
const optionalWidth = showEndpoint ? endpointWidth : 0;
|
|
75
|
+
const remainingWidth = terminalWidth - baseWidth - optionalWidth;
|
|
76
|
+
const nameWidth = Math.min(80, Math.max(15, remainingWidth));
|
|
77
|
+
// Fetch function for pagination hook
|
|
78
|
+
const fetchPage = React.useCallback(async (params) => {
|
|
79
|
+
const client = getClient();
|
|
80
|
+
const pageConfigs = [];
|
|
81
|
+
// Build query params
|
|
82
|
+
const queryParams = {
|
|
83
|
+
limit: params.limit,
|
|
84
|
+
};
|
|
85
|
+
if (params.startingAt) {
|
|
86
|
+
queryParams.starting_after = params.startingAt;
|
|
87
|
+
}
|
|
88
|
+
if (search.submittedSearchQuery) {
|
|
89
|
+
queryParams.name = search.submittedSearchQuery;
|
|
90
|
+
}
|
|
91
|
+
// Fetch ONE page only
|
|
92
|
+
const page = (await client.gatewayConfigs.list(queryParams));
|
|
93
|
+
// Extract data and create defensive copies
|
|
94
|
+
if (page.gateway_configs && Array.isArray(page.gateway_configs)) {
|
|
95
|
+
page.gateway_configs.forEach((g) => {
|
|
96
|
+
pageConfigs.push({
|
|
97
|
+
id: g.id,
|
|
98
|
+
name: g.name,
|
|
99
|
+
description: g.description,
|
|
100
|
+
endpoint: g.endpoint,
|
|
101
|
+
create_time_ms: g.create_time_ms,
|
|
102
|
+
auth_mechanism: {
|
|
103
|
+
type: g.auth_mechanism.type,
|
|
104
|
+
key: g.auth_mechanism.key,
|
|
105
|
+
},
|
|
106
|
+
account_id: g.account_id,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const result = {
|
|
111
|
+
items: pageConfigs,
|
|
112
|
+
hasMore: page.has_more || false,
|
|
113
|
+
totalCount: page.total_count || pageConfigs.length,
|
|
114
|
+
};
|
|
115
|
+
return result;
|
|
116
|
+
}, [search.submittedSearchQuery]);
|
|
117
|
+
// Use the shared pagination hook
|
|
118
|
+
const { items: configs, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
|
|
119
|
+
fetchPage,
|
|
120
|
+
pageSize: PAGE_SIZE,
|
|
121
|
+
getItemId: (config) => config.id,
|
|
122
|
+
pollInterval: 5000,
|
|
123
|
+
pollingEnabled: !showPopup &&
|
|
124
|
+
!executingOperation &&
|
|
125
|
+
!showCreateConfig &&
|
|
126
|
+
!showEditConfig &&
|
|
127
|
+
!showDeleteConfirm &&
|
|
128
|
+
!search.searchMode,
|
|
129
|
+
deps: [PAGE_SIZE, search.submittedSearchQuery],
|
|
130
|
+
});
|
|
131
|
+
// Operations for a specific gateway config (shown in popup)
|
|
132
|
+
const operations = React.useMemo(() => [
|
|
133
|
+
{
|
|
134
|
+
key: "view_details",
|
|
135
|
+
label: "View Details",
|
|
136
|
+
color: colors.primary,
|
|
137
|
+
icon: figures.pointer,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
key: "edit",
|
|
141
|
+
label: "Edit AI Gateway Config",
|
|
142
|
+
color: colors.warning,
|
|
143
|
+
icon: figures.pointer,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
key: "delete",
|
|
147
|
+
label: "Delete AI Gateway Config",
|
|
148
|
+
color: colors.error,
|
|
149
|
+
icon: figures.cross,
|
|
150
|
+
},
|
|
151
|
+
], []);
|
|
152
|
+
// Build columns
|
|
153
|
+
const columns = React.useMemo(() => [
|
|
154
|
+
createTextColumn("id", "ID", (config) => config.id, {
|
|
155
|
+
width: idWidth + 1,
|
|
156
|
+
color: colors.idColor,
|
|
157
|
+
dimColor: false,
|
|
158
|
+
bold: false,
|
|
159
|
+
}),
|
|
160
|
+
createTextColumn("name", "Name", (config) => config.name || "", {
|
|
161
|
+
width: nameWidth,
|
|
162
|
+
}),
|
|
163
|
+
...(showEndpoint
|
|
164
|
+
? [
|
|
165
|
+
createTextColumn("endpoint", "Endpoint", (config) => config.endpoint || "", {
|
|
166
|
+
width: endpointWidth,
|
|
167
|
+
color: colors.textDim,
|
|
168
|
+
dimColor: false,
|
|
169
|
+
bold: false,
|
|
170
|
+
}),
|
|
171
|
+
]
|
|
172
|
+
: []),
|
|
173
|
+
createTextColumn("auth", "Auth", (config) => getAuthTypeLabel(config.auth_mechanism), {
|
|
174
|
+
width: authWidth,
|
|
175
|
+
color: colors.info,
|
|
176
|
+
dimColor: false,
|
|
177
|
+
bold: false,
|
|
178
|
+
}),
|
|
179
|
+
createTextColumn("created", "Created", (config) => config.create_time_ms ? formatTimeAgo(config.create_time_ms) : "", {
|
|
180
|
+
width: timeWidth,
|
|
181
|
+
color: colors.textDim,
|
|
182
|
+
dimColor: false,
|
|
183
|
+
bold: false,
|
|
184
|
+
}),
|
|
185
|
+
], [idWidth, nameWidth, endpointWidth, authWidth, timeWidth, showEndpoint]);
|
|
186
|
+
// Handle Ctrl+C to exit
|
|
187
|
+
useExitOnCtrlC();
|
|
188
|
+
// Ensure selected index is within bounds
|
|
189
|
+
React.useEffect(() => {
|
|
190
|
+
if (configs.length > 0 && selectedIndex >= configs.length) {
|
|
191
|
+
setSelectedIndex(Math.max(0, configs.length - 1));
|
|
192
|
+
}
|
|
193
|
+
}, [configs.length, selectedIndex]);
|
|
194
|
+
const selectedConfigItem = configs[selectedIndex];
|
|
195
|
+
// Calculate pagination info for display
|
|
196
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
|
|
197
|
+
const startIndex = currentPage * PAGE_SIZE;
|
|
198
|
+
const endIndex = startIndex + configs.length;
|
|
199
|
+
const executeOperation = async (config, operationKey) => {
|
|
200
|
+
const client = getClient();
|
|
201
|
+
if (!config)
|
|
202
|
+
return;
|
|
203
|
+
try {
|
|
204
|
+
setOperationLoading(true);
|
|
205
|
+
switch (operationKey) {
|
|
206
|
+
case "delete":
|
|
207
|
+
await client.gatewayConfigs.delete(config.id);
|
|
208
|
+
setOperationResult(`AI gateway config "${config.name}" deleted successfully`);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
setOperationError(err);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
setOperationLoading(false);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
useInput((input, key) => {
|
|
220
|
+
// Handle search mode input
|
|
221
|
+
if (search.searchMode) {
|
|
222
|
+
if (key.escape) {
|
|
223
|
+
search.cancelSearch();
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// Handle operation result display
|
|
228
|
+
if (operationResult || operationError) {
|
|
229
|
+
if (input === "q" || key.escape || key.return) {
|
|
230
|
+
const wasDelete = executingOperation === "delete";
|
|
231
|
+
const hadError = operationError !== null;
|
|
232
|
+
setOperationResult(null);
|
|
233
|
+
setOperationError(null);
|
|
234
|
+
setExecutingOperation(null);
|
|
235
|
+
setSelectedConfig(null);
|
|
236
|
+
// Refresh the list after delete to show updated data
|
|
237
|
+
if (wasDelete && !hadError) {
|
|
238
|
+
setTimeout(() => refresh(), 0);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// Handle create config screen
|
|
244
|
+
if (showCreateConfig) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// Handle edit config screen
|
|
248
|
+
if (showEditConfig) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Handle popup navigation
|
|
252
|
+
if (showPopup) {
|
|
253
|
+
if (key.upArrow && selectedOperation > 0) {
|
|
254
|
+
setSelectedOperation(selectedOperation - 1);
|
|
255
|
+
}
|
|
256
|
+
else if (key.downArrow && selectedOperation < operations.length - 1) {
|
|
257
|
+
setSelectedOperation(selectedOperation + 1);
|
|
258
|
+
}
|
|
259
|
+
else if (key.return) {
|
|
260
|
+
setShowPopup(false);
|
|
261
|
+
const operationKey = operations[selectedOperation].key;
|
|
262
|
+
if (operationKey === "create") {
|
|
263
|
+
setShowCreateConfig(true);
|
|
264
|
+
}
|
|
265
|
+
else if (operationKey === "view_details") {
|
|
266
|
+
navigate("gateway-config-detail", {
|
|
267
|
+
gatewayConfigId: selectedConfigItem.id,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else if (operationKey === "edit") {
|
|
271
|
+
// Show edit form
|
|
272
|
+
setEditingConfig(selectedConfigItem);
|
|
273
|
+
setShowEditConfig(true);
|
|
274
|
+
}
|
|
275
|
+
else if (operationKey === "delete") {
|
|
276
|
+
// Show delete confirmation
|
|
277
|
+
setSelectedConfig(selectedConfigItem);
|
|
278
|
+
setShowDeleteConfirm(true);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
setSelectedConfig(selectedConfigItem);
|
|
282
|
+
setExecutingOperation(operationKey);
|
|
283
|
+
// Execute immediately with values passed directly
|
|
284
|
+
executeOperation(selectedConfigItem, operationKey);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else if (input === "c") {
|
|
288
|
+
// Create hotkey
|
|
289
|
+
setShowPopup(false);
|
|
290
|
+
setShowCreateConfig(true);
|
|
291
|
+
}
|
|
292
|
+
else if (input === "v" && selectedConfigItem) {
|
|
293
|
+
// View details hotkey
|
|
294
|
+
setShowPopup(false);
|
|
295
|
+
navigate("gateway-config-detail", {
|
|
296
|
+
gatewayConfigId: selectedConfigItem.id,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
else if (input === "e" && selectedConfigItem) {
|
|
300
|
+
// Edit hotkey
|
|
301
|
+
setShowPopup(false);
|
|
302
|
+
setEditingConfig(selectedConfigItem);
|
|
303
|
+
setShowEditConfig(true);
|
|
304
|
+
}
|
|
305
|
+
else if (key.escape || input === "q") {
|
|
306
|
+
setShowPopup(false);
|
|
307
|
+
setSelectedOperation(0);
|
|
308
|
+
}
|
|
309
|
+
else if (input === "d") {
|
|
310
|
+
// Delete hotkey - show confirmation
|
|
311
|
+
setShowPopup(false);
|
|
312
|
+
setSelectedConfig(selectedConfigItem);
|
|
313
|
+
setShowDeleteConfirm(true);
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const pageConfigs = configs.length;
|
|
318
|
+
// Handle list view navigation
|
|
319
|
+
if (key.upArrow && selectedIndex > 0) {
|
|
320
|
+
setSelectedIndex(selectedIndex - 1);
|
|
321
|
+
}
|
|
322
|
+
else if (key.downArrow && selectedIndex < pageConfigs - 1) {
|
|
323
|
+
setSelectedIndex(selectedIndex + 1);
|
|
324
|
+
}
|
|
325
|
+
else if ((input === "n" || key.rightArrow) &&
|
|
326
|
+
!loading &&
|
|
327
|
+
!navigating &&
|
|
328
|
+
hasMore) {
|
|
329
|
+
nextPage();
|
|
330
|
+
setSelectedIndex(0);
|
|
331
|
+
}
|
|
332
|
+
else if ((input === "p" || key.leftArrow) &&
|
|
333
|
+
!loading &&
|
|
334
|
+
!navigating &&
|
|
335
|
+
hasPrev) {
|
|
336
|
+
prevPage();
|
|
337
|
+
setSelectedIndex(0);
|
|
338
|
+
}
|
|
339
|
+
else if (key.return && selectedConfigItem) {
|
|
340
|
+
// Enter key navigates to detail view
|
|
341
|
+
navigate("gateway-config-detail", {
|
|
342
|
+
gatewayConfigId: selectedConfigItem.id,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
else if (input === "a") {
|
|
346
|
+
setShowPopup(true);
|
|
347
|
+
setSelectedOperation(0);
|
|
348
|
+
}
|
|
349
|
+
else if (input === "c") {
|
|
350
|
+
// Create shortcut
|
|
351
|
+
setShowCreateConfig(true);
|
|
352
|
+
}
|
|
353
|
+
else if (input === "e" && selectedConfigItem) {
|
|
354
|
+
// Edit shortcut
|
|
355
|
+
setEditingConfig(selectedConfigItem);
|
|
356
|
+
setShowEditConfig(true);
|
|
357
|
+
}
|
|
358
|
+
else if (input === "/") {
|
|
359
|
+
search.enterSearchMode();
|
|
360
|
+
}
|
|
361
|
+
else if (key.escape) {
|
|
362
|
+
if (search.handleEscape()) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (onBack) {
|
|
366
|
+
onBack();
|
|
367
|
+
}
|
|
368
|
+
else if (onExit) {
|
|
369
|
+
onExit();
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
inkExit();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
// Delete confirmation
|
|
377
|
+
if (showDeleteConfirm && selectedConfig) {
|
|
378
|
+
return (_jsx(ConfirmationPrompt, { title: "Delete AI Gateway Config", message: `Are you sure you want to delete "${selectedConfig.name}"?`, details: "This action cannot be undone. Any devboxes using this AI gateway config will no longer have access to it.", breadcrumbItems: [
|
|
379
|
+
{ label: "AI Gateway Configs" },
|
|
380
|
+
{ label: selectedConfig.name || selectedConfig.id },
|
|
381
|
+
{ label: "Delete", active: true },
|
|
382
|
+
], onConfirm: () => {
|
|
383
|
+
setShowDeleteConfirm(false);
|
|
384
|
+
setExecutingOperation("delete");
|
|
385
|
+
executeOperation(selectedConfig, "delete");
|
|
386
|
+
}, onCancel: () => {
|
|
387
|
+
setShowDeleteConfirm(false);
|
|
388
|
+
setSelectedConfig(null);
|
|
389
|
+
} }));
|
|
390
|
+
}
|
|
391
|
+
// Operation result display
|
|
392
|
+
if (operationResult || operationError) {
|
|
393
|
+
const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
|
|
394
|
+
"Operation";
|
|
395
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
396
|
+
{ label: "AI Gateway Configs" },
|
|
397
|
+
{
|
|
398
|
+
label: selectedConfig?.name || selectedConfig?.id || "Config",
|
|
399
|
+
},
|
|
400
|
+
{ label: operationLabel, active: true },
|
|
401
|
+
] }), _jsx(Header, { title: "Operation Result" }), operationResult && _jsx(SuccessMessage, { message: operationResult }), operationError && (_jsx(ErrorMessage, { message: "Operation failed", error: operationError })), _jsx(NavigationTips, { tips: [{ key: "Enter/q/esc", label: "Continue" }] })] }));
|
|
402
|
+
}
|
|
403
|
+
// Operation loading state
|
|
404
|
+
if (operationLoading && selectedConfig) {
|
|
405
|
+
const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
|
|
406
|
+
"Operation";
|
|
407
|
+
const messages = {
|
|
408
|
+
delete: "Deleting AI gateway config...",
|
|
409
|
+
};
|
|
410
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
411
|
+
{ label: "AI Gateway Configs" },
|
|
412
|
+
{ label: selectedConfig.name || selectedConfig.id },
|
|
413
|
+
{ label: operationLabel, active: true },
|
|
414
|
+
] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: messages[executingOperation] || "Please wait..." })] }));
|
|
415
|
+
}
|
|
416
|
+
// Create config screen
|
|
417
|
+
if (showCreateConfig) {
|
|
418
|
+
return (_jsx(GatewayConfigCreatePage, { onBack: () => setShowCreateConfig(false), onCreate: (config) => {
|
|
419
|
+
setShowCreateConfig(false);
|
|
420
|
+
navigate("gateway-config-detail", { gatewayConfigId: config.id });
|
|
421
|
+
} }));
|
|
422
|
+
}
|
|
423
|
+
// Edit config screen
|
|
424
|
+
if (showEditConfig && editingConfig) {
|
|
425
|
+
return (_jsx(GatewayConfigCreatePage, { onBack: () => {
|
|
426
|
+
setShowEditConfig(false);
|
|
427
|
+
setEditingConfig(null);
|
|
428
|
+
}, onCreate: () => {
|
|
429
|
+
setShowEditConfig(false);
|
|
430
|
+
setEditingConfig(null);
|
|
431
|
+
// Refresh the list to show updated data
|
|
432
|
+
setTimeout(() => refresh(), 0);
|
|
433
|
+
}, initialConfig: editingConfig }));
|
|
434
|
+
}
|
|
435
|
+
// Loading state
|
|
436
|
+
if (loading && configs.length === 0) {
|
|
437
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "AI Gateway Configs", active: true }] }), _jsx(SpinnerComponent, { message: "Loading AI gateway configs..." })] }));
|
|
438
|
+
}
|
|
439
|
+
// Error state
|
|
440
|
+
if (error) {
|
|
441
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "AI Gateway Configs", active: true }] }), _jsx(ErrorMessage, { message: "Failed to list AI gateway configs", error: error })] }));
|
|
442
|
+
}
|
|
443
|
+
// Main list view
|
|
444
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "AI Gateway Configs", active: true }] }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search AI gateway configs..." }), !showPopup && (_jsx(Table, { data: configs, keyExtractor: (config) => config.id, selectedIndex: selectedIndex, title: `gateway_configs[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No AI gateway configs found. Press [c] to create one."] }) })), !showPopup && (_jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.hamburger, " ", totalCount] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "total"] }), totalPages > 1 && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), navigating ? (_jsxs(Text, { color: colors.warning, children: [figures.pointer, " Loading page ", currentPage + 1, "..."] })) : (_jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Page ", currentPage + 1, " of ", totalPages] }))] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Showing ", startIndex + 1, "-", endIndex, " of ", totalCount] }), search.submittedSearchQuery && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.warning, children: ["Filtered: \"", search.submittedSearchQuery, "\""] })] }))] })), showPopup && selectedConfigItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedConfigItem, operations: operations.map((op) => ({
|
|
445
|
+
key: op.key,
|
|
446
|
+
label: op.label,
|
|
447
|
+
color: op.color,
|
|
448
|
+
icon: op.icon,
|
|
449
|
+
shortcut: op.key === "create"
|
|
450
|
+
? "c"
|
|
451
|
+
: op.key === "view_details"
|
|
452
|
+
? "v"
|
|
453
|
+
: op.key === "edit"
|
|
454
|
+
? "e"
|
|
455
|
+
: op.key === "delete"
|
|
456
|
+
? "d"
|
|
457
|
+
: "",
|
|
458
|
+
})), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
|
|
459
|
+
{
|
|
460
|
+
icon: `${figures.arrowLeft}${figures.arrowRight}`,
|
|
461
|
+
label: "Page",
|
|
462
|
+
condition: hasMore || hasPrev,
|
|
463
|
+
},
|
|
464
|
+
{ key: "Enter", label: "Details" },
|
|
465
|
+
{ key: "c", label: "Create" },
|
|
466
|
+
{ key: "e", label: "Edit" },
|
|
467
|
+
{ key: "a", label: "Actions" },
|
|
468
|
+
{ key: "/", label: "Search" },
|
|
469
|
+
{ key: "Esc", label: "Back" },
|
|
470
|
+
] })] }));
|
|
471
|
+
};
|
|
472
|
+
// Export the UI component for use in the main menu
|
|
473
|
+
export { ListGatewayConfigsUI };
|
|
474
|
+
export async function listGatewayConfigs(options = {}) {
|
|
475
|
+
try {
|
|
476
|
+
const client = getClient();
|
|
477
|
+
// Build query params
|
|
478
|
+
const queryParams = {
|
|
479
|
+
limit: DEFAULT_PAGE_SIZE,
|
|
480
|
+
};
|
|
481
|
+
if (options.name) {
|
|
482
|
+
queryParams.name = options.name;
|
|
483
|
+
}
|
|
484
|
+
// Fetch gateway configs
|
|
485
|
+
const page = (await client.gatewayConfigs.list(queryParams));
|
|
486
|
+
// Extract gateway configs array
|
|
487
|
+
const gatewayConfigs = page.gateway_configs || [];
|
|
488
|
+
output(gatewayConfigs, { format: options.output, defaultFormat: "json" });
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
outputError("Failed to list gateway configs", error);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update gateway config command
|
|
3
|
+
*/
|
|
4
|
+
import { getClient } from "../../utils/client.js";
|
|
5
|
+
import { output, outputError } from "../../utils/output.js";
|
|
6
|
+
import { validateGatewayConfig } from "../../utils/gatewayConfigValidation.js";
|
|
7
|
+
export async function updateGatewayConfig(options) {
|
|
8
|
+
try {
|
|
9
|
+
const client = getClient();
|
|
10
|
+
// Validate that at most one auth type is specified
|
|
11
|
+
if (options.bearerAuth && options.headerAuth) {
|
|
12
|
+
outputError("Cannot specify both --bearer-auth and --header-auth. Choose one.");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// Determine auth type if specified
|
|
16
|
+
const authType = options.bearerAuth
|
|
17
|
+
? "bearer"
|
|
18
|
+
: options.headerAuth
|
|
19
|
+
? "header"
|
|
20
|
+
: undefined;
|
|
21
|
+
// Validate provided fields using shared validation
|
|
22
|
+
const validation = validateGatewayConfig({
|
|
23
|
+
name: options.name,
|
|
24
|
+
endpoint: options.endpoint,
|
|
25
|
+
authType,
|
|
26
|
+
authKey: options.headerAuth,
|
|
27
|
+
}, { requireName: false, requireEndpoint: false });
|
|
28
|
+
if (!validation.valid) {
|
|
29
|
+
outputError(validation.errors.join("\n"));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const { sanitized } = validation;
|
|
33
|
+
// Build update params - only include fields that are provided
|
|
34
|
+
const updateParams = {};
|
|
35
|
+
if (sanitized.name) {
|
|
36
|
+
updateParams.name = sanitized.name;
|
|
37
|
+
}
|
|
38
|
+
if (sanitized.endpoint) {
|
|
39
|
+
updateParams.endpoint = sanitized.endpoint;
|
|
40
|
+
}
|
|
41
|
+
if (options.description !== undefined) {
|
|
42
|
+
updateParams.description = options.description.trim() || undefined;
|
|
43
|
+
}
|
|
44
|
+
// Handle auth mechanism update
|
|
45
|
+
if (sanitized.authType === "bearer") {
|
|
46
|
+
updateParams.auth_mechanism = { type: "bearer" };
|
|
47
|
+
}
|
|
48
|
+
else if (sanitized.authType === "header" && sanitized.authKey) {
|
|
49
|
+
updateParams.auth_mechanism = {
|
|
50
|
+
type: "header",
|
|
51
|
+
key: sanitized.authKey,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (Object.keys(updateParams).length === 0) {
|
|
55
|
+
outputError("No update options provided. Use --name, --endpoint, --bearer-auth, --header-auth, or --description");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const config = await client.gatewayConfigs.update(options.id, updateParams);
|
|
59
|
+
// Default: just output the ID for easy scripting
|
|
60
|
+
if (!options.output || options.output === "text") {
|
|
61
|
+
console.log(config.id);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
output(config, { format: options.output, defaultFormat: "json" });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
outputError("Failed to update gateway config", error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -436,8 +436,17 @@ export async function listSnapshots(options) {
|
|
|
436
436
|
}
|
|
437
437
|
// Fetch snapshots
|
|
438
438
|
const page = (await client.devboxes.listDiskSnapshots(queryParams));
|
|
439
|
-
// Extract snapshots array
|
|
440
|
-
|
|
439
|
+
// Extract snapshots array and strip to plain objects to avoid
|
|
440
|
+
// camelCase aliases added by the API client library
|
|
441
|
+
const snapshots = (page.snapshots || []).map((s) => ({
|
|
442
|
+
id: s.id,
|
|
443
|
+
name: s.name ?? undefined,
|
|
444
|
+
create_time_ms: s.create_time_ms,
|
|
445
|
+
metadata: s.metadata,
|
|
446
|
+
source_devbox_id: s.source_devbox_id,
|
|
447
|
+
source_blueprint_id: s.source_blueprint_id ?? undefined,
|
|
448
|
+
commit_message: s.commit_message ?? undefined,
|
|
449
|
+
}));
|
|
441
450
|
output(snapshots, { format: options.output, defaultFormat: "json" });
|
|
442
451
|
}
|
|
443
452
|
catch (error) {
|