@runloop/rl-cli 1.2.0 → 1.4.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 (57) hide show
  1. package/README.md +29 -8
  2. package/dist/commands/blueprint/from-dockerfile.js +182 -0
  3. package/dist/commands/blueprint/list.js +97 -28
  4. package/dist/commands/blueprint/prune.js +7 -19
  5. package/dist/commands/devbox/create.js +3 -0
  6. package/dist/commands/devbox/list.js +44 -65
  7. package/dist/commands/menu.js +2 -1
  8. package/dist/commands/network-policy/create.js +27 -0
  9. package/dist/commands/network-policy/delete.js +21 -0
  10. package/dist/commands/network-policy/get.js +15 -0
  11. package/dist/commands/network-policy/list.js +494 -0
  12. package/dist/commands/object/list.js +516 -24
  13. package/dist/commands/snapshot/list.js +90 -29
  14. package/dist/components/Banner.js +109 -8
  15. package/dist/components/ConfirmationPrompt.js +45 -0
  16. package/dist/components/DevboxActionsMenu.js +42 -6
  17. package/dist/components/DevboxCard.js +1 -1
  18. package/dist/components/DevboxCreatePage.js +174 -168
  19. package/dist/components/DevboxDetailPage.js +218 -272
  20. package/dist/components/LogsViewer.js +8 -1
  21. package/dist/components/MainMenu.js +35 -4
  22. package/dist/components/NavigationTips.js +24 -0
  23. package/dist/components/NetworkPolicyCreatePage.js +263 -0
  24. package/dist/components/OperationsMenu.js +9 -1
  25. package/dist/components/ResourceActionsMenu.js +5 -1
  26. package/dist/components/ResourceDetailPage.js +204 -0
  27. package/dist/components/ResourceListView.js +19 -2
  28. package/dist/components/StatusBadge.js +2 -2
  29. package/dist/components/Table.js +6 -8
  30. package/dist/components/form/FormActionButton.js +7 -0
  31. package/dist/components/form/FormField.js +7 -0
  32. package/dist/components/form/FormListManager.js +112 -0
  33. package/dist/components/form/FormSelect.js +34 -0
  34. package/dist/components/form/FormTextInput.js +8 -0
  35. package/dist/components/form/index.js +8 -0
  36. package/dist/hooks/useViewportHeight.js +38 -20
  37. package/dist/router/Router.js +23 -1
  38. package/dist/screens/BlueprintDetailScreen.js +355 -0
  39. package/dist/screens/DevboxDetailScreen.js +4 -4
  40. package/dist/screens/MenuScreen.js +6 -0
  41. package/dist/screens/NetworkPolicyCreateScreen.js +7 -0
  42. package/dist/screens/NetworkPolicyDetailScreen.js +247 -0
  43. package/dist/screens/NetworkPolicyListScreen.js +7 -0
  44. package/dist/screens/ObjectDetailScreen.js +377 -0
  45. package/dist/screens/ObjectListScreen.js +7 -0
  46. package/dist/screens/SnapshotDetailScreen.js +208 -0
  47. package/dist/services/blueprintService.js +30 -11
  48. package/dist/services/networkPolicyService.js +108 -0
  49. package/dist/services/objectService.js +101 -0
  50. package/dist/services/snapshotService.js +39 -3
  51. package/dist/store/blueprintStore.js +4 -10
  52. package/dist/store/index.js +1 -0
  53. package/dist/store/networkPolicyStore.js +83 -0
  54. package/dist/store/objectStore.js +92 -0
  55. package/dist/store/snapshotStore.js +4 -8
  56. package/dist/utils/commands.js +65 -0
  57. package/package.json +2 -2
@@ -1,34 +1,526 @@
1
- /**
2
- * List objects command
3
- */
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 TextInput from "ink-text-input";
5
+ import figures from "figures";
6
+ import { writeFile } from "fs/promises";
4
7
  import { getClient } from "../../utils/client.js";
8
+ import { Header } from "../../components/Header.js";
9
+ import { SpinnerComponent } from "../../components/Spinner.js";
10
+ import { ErrorMessage } from "../../components/ErrorMessage.js";
11
+ import { SuccessMessage } from "../../components/SuccessMessage.js";
12
+ import { Breadcrumb } from "../../components/Breadcrumb.js";
13
+ import { NavigationTips } from "../../components/NavigationTips.js";
14
+ import { Table, createTextColumn } from "../../components/Table.js";
15
+ import { ActionsPopup } from "../../components/ActionsPopup.js";
16
+ import { formatTimeAgo } from "../../components/ResourceListView.js";
5
17
  import { output, outputError } from "../../utils/output.js";
6
- export async function listObjects(options = {}) {
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 { useNavigation } from "../../store/navigationStore.js";
23
+ import { formatFileSize } from "../../services/objectService.js";
24
+ import { ConfirmationPrompt } from "../../components/ConfirmationPrompt.js";
25
+ const DEFAULT_PAGE_SIZE = 10;
26
+ const ListObjectsUI = ({ onBack, onExit, }) => {
27
+ const { exit: inkExit } = useApp();
28
+ const { navigate } = useNavigation();
29
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
30
+ const [showPopup, setShowPopup] = React.useState(false);
31
+ const [selectedOperation, setSelectedOperation] = React.useState(0);
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ const [selectedObject, setSelectedObject] = React.useState(null);
34
+ const [executingOperation, setExecutingOperation] = React.useState(null);
35
+ const [operationResult, setOperationResult] = React.useState(null);
36
+ const [operationError, setOperationError] = React.useState(null);
37
+ const [operationLoading, setOperationLoading] = React.useState(false);
38
+ const [showDownloadPrompt, setShowDownloadPrompt] = React.useState(false);
39
+ const [downloadPath, setDownloadPath] = React.useState("");
40
+ const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
41
+ // Calculate overhead for viewport height
42
+ const overhead = 13;
43
+ const { viewportHeight, terminalWidth } = useViewportHeight({
44
+ overhead,
45
+ minHeight: 5,
46
+ });
47
+ const PAGE_SIZE = viewportHeight;
48
+ // All width constants
49
+ const fixedWidth = 6; // border + padding
50
+ const idWidth = 25;
51
+ const stateWidth = 12;
52
+ const typeWidth = 15;
53
+ const sizeWidth = 12;
54
+ const timeWidth = 20;
55
+ const ttlWidth = 14;
56
+ const showTypeColumn = terminalWidth >= 100;
57
+ const showSizeColumn = terminalWidth >= 90;
58
+ const showTtlColumn = terminalWidth >= 80;
59
+ // Name width uses remaining space after fixed columns
60
+ const baseWidth = fixedWidth + idWidth + stateWidth + timeWidth;
61
+ const optionalWidth = (showTypeColumn ? typeWidth : 0) +
62
+ (showSizeColumn ? sizeWidth : 0) +
63
+ (showTtlColumn ? ttlWidth : 0);
64
+ const remainingWidth = terminalWidth - baseWidth - optionalWidth;
65
+ const nameWidth = Math.min(80, Math.max(15, remainingWidth));
66
+ // Helper to format TTL remaining time
67
+ const formatTtl = (deleteAfterMs) => {
68
+ if (!deleteAfterMs)
69
+ return "";
70
+ const now = Date.now();
71
+ const remainingMs = deleteAfterMs - now;
72
+ if (remainingMs <= 0)
73
+ return "Expired";
74
+ const remainingMinutes = Math.floor(remainingMs / 60000);
75
+ if (remainingMinutes < 60) {
76
+ return `${remainingMinutes}m left`;
77
+ }
78
+ const hours = Math.floor(remainingMinutes / 60);
79
+ const mins = remainingMinutes % 60;
80
+ if (hours < 24) {
81
+ return `${hours}h ${mins}m left`;
82
+ }
83
+ const days = Math.floor(hours / 24);
84
+ const remainingHours = hours % 24;
85
+ return `${days}d ${remainingHours}h left`;
86
+ };
87
+ // Fetch function for pagination hook
88
+ const fetchPage = React.useCallback(async (params) => {
89
+ const client = getClient();
90
+ const pageObjects = [];
91
+ // Build query params
92
+ const queryParams = {
93
+ limit: params.limit,
94
+ };
95
+ if (params.startingAt) {
96
+ queryParams.starting_after = params.startingAt;
97
+ }
98
+ // Fetch ONE page only
99
+ const result = await client.objects.list(queryParams);
100
+ // Extract data and create defensive copies
101
+ if (result.objects && Array.isArray(result.objects)) {
102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
+ result.objects.forEach((obj) => {
104
+ pageObjects.push({
105
+ id: obj.id,
106
+ name: obj.name,
107
+ content_type: obj.content_type,
108
+ size_bytes: obj.size_bytes,
109
+ state: obj.state,
110
+ is_public: obj.is_public,
111
+ create_time_ms: obj.create_time_ms,
112
+ delete_after_time_ms: obj.delete_after_time_ms,
113
+ });
114
+ });
115
+ }
116
+ // Access pagination properties from the result
117
+ const pageResult = result;
118
+ return {
119
+ items: pageObjects,
120
+ hasMore: pageResult.has_more || false,
121
+ totalCount: pageResult.total_count || pageObjects.length,
122
+ };
123
+ }, []);
124
+ // Use the shared pagination hook
125
+ const { items: objects, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
126
+ fetchPage,
127
+ pageSize: PAGE_SIZE,
128
+ getItemId: (obj) => obj.id,
129
+ pollInterval: 2000,
130
+ pollingEnabled: !showPopup &&
131
+ !executingOperation &&
132
+ !showDownloadPrompt &&
133
+ !showDeleteConfirm,
134
+ deps: [PAGE_SIZE],
135
+ });
136
+ // Operations for objects
137
+ const operations = React.useMemo(() => [
138
+ {
139
+ key: "view_details",
140
+ label: "View Details",
141
+ color: colors.primary,
142
+ icon: figures.pointer,
143
+ },
144
+ {
145
+ key: "download",
146
+ label: "Download",
147
+ color: colors.success,
148
+ icon: figures.arrowDown,
149
+ },
150
+ {
151
+ key: "delete",
152
+ label: "Delete",
153
+ color: colors.error,
154
+ icon: figures.cross,
155
+ },
156
+ ], []);
157
+ // Build columns
158
+ const columns = React.useMemo(() => [
159
+ createTextColumn("id", "ID", (obj) => obj.id, {
160
+ width: idWidth + 1,
161
+ color: colors.idColor,
162
+ dimColor: false,
163
+ bold: false,
164
+ }),
165
+ createTextColumn("name", "Name", (obj) => obj.name || "", {
166
+ width: nameWidth,
167
+ }),
168
+ createTextColumn("state", "State", (obj) => obj.state || "", {
169
+ width: stateWidth,
170
+ color: colors.warning,
171
+ dimColor: false,
172
+ bold: false,
173
+ }),
174
+ createTextColumn("type", "Type", (obj) => obj.content_type || "", {
175
+ width: typeWidth,
176
+ color: colors.textDim,
177
+ dimColor: false,
178
+ bold: false,
179
+ visible: showTypeColumn,
180
+ }),
181
+ createTextColumn("size", "Size", (obj) => formatFileSize(obj.size_bytes), {
182
+ width: sizeWidth,
183
+ color: colors.textDim,
184
+ dimColor: false,
185
+ bold: false,
186
+ visible: showSizeColumn,
187
+ }),
188
+ createTextColumn("created", "Created", (obj) => obj.create_time_ms ? formatTimeAgo(obj.create_time_ms) : "", {
189
+ width: timeWidth,
190
+ color: colors.textDim,
191
+ dimColor: false,
192
+ bold: false,
193
+ }),
194
+ createTextColumn("ttl", "Expires", (obj) => formatTtl(obj.delete_after_time_ms), {
195
+ width: ttlWidth,
196
+ color: colors.warning,
197
+ dimColor: false,
198
+ bold: false,
199
+ visible: showTtlColumn,
200
+ }),
201
+ ], [
202
+ idWidth,
203
+ nameWidth,
204
+ stateWidth,
205
+ typeWidth,
206
+ sizeWidth,
207
+ timeWidth,
208
+ ttlWidth,
209
+ showTypeColumn,
210
+ showSizeColumn,
211
+ showTtlColumn,
212
+ formatTtl,
213
+ ]);
214
+ // Handle Ctrl+C to exit
215
+ useExitOnCtrlC();
216
+ // Ensure selected index is within bounds
217
+ React.useEffect(() => {
218
+ if (objects.length > 0 && selectedIndex >= objects.length) {
219
+ setSelectedIndex(Math.max(0, objects.length - 1));
220
+ }
221
+ }, [objects.length, selectedIndex]);
222
+ const selectedObjectItem = objects[selectedIndex];
223
+ // Calculate pagination info for display
224
+ const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
225
+ const startIndex = currentPage * PAGE_SIZE;
226
+ const endIndex = startIndex + objects.length;
227
+ const executeOperation = async (obj, operationKey, targetPath) => {
228
+ const client = getClient();
229
+ if (!obj)
230
+ return;
231
+ try {
232
+ setOperationLoading(true);
233
+ switch (operationKey) {
234
+ case "delete":
235
+ await client.objects.delete(obj.id);
236
+ setOperationResult(`Storage object ${obj.id} deleted successfully`);
237
+ break;
238
+ case "download": {
239
+ if (!targetPath) {
240
+ setOperationError(new Error("No download path specified"));
241
+ break;
242
+ }
243
+ // Get download URL
244
+ const downloadUrlResponse = await client.objects.download(obj.id, {
245
+ duration_seconds: 3600,
246
+ });
247
+ // Download the file
248
+ const response = await fetch(downloadUrlResponse.download_url);
249
+ if (!response.ok) {
250
+ throw new Error(`Download failed: HTTP ${response.status}`);
251
+ }
252
+ // Save the file
253
+ const arrayBuffer = await response.arrayBuffer();
254
+ const buffer = Buffer.from(arrayBuffer);
255
+ await writeFile(targetPath, buffer);
256
+ setOperationResult(`Downloaded to ${targetPath}`);
257
+ break;
258
+ }
259
+ }
260
+ }
261
+ catch (err) {
262
+ setOperationError(err);
263
+ }
264
+ finally {
265
+ setOperationLoading(false);
266
+ }
267
+ };
268
+ // Handle download submission
269
+ const handleDownloadSubmit = () => {
270
+ if (downloadPath.trim() && selectedObject) {
271
+ setShowDownloadPrompt(false);
272
+ setExecutingOperation("download");
273
+ executeOperation(selectedObject, "download", downloadPath.trim());
274
+ }
275
+ };
276
+ useInput((input, key) => {
277
+ // Handle operation result display
278
+ if (operationResult || operationError) {
279
+ if (input === "q" || key.escape || key.return) {
280
+ const wasDelete = executingOperation === "delete";
281
+ const hadError = operationError !== null;
282
+ setOperationResult(null);
283
+ setOperationError(null);
284
+ setExecutingOperation(null);
285
+ setSelectedObject(null);
286
+ // Refresh the list after delete to show updated data
287
+ // Use setTimeout to ensure state updates are applied first
288
+ if (wasDelete && !hadError) {
289
+ setTimeout(() => refresh(), 0);
290
+ }
291
+ }
292
+ return;
293
+ }
294
+ // Handle download prompt
295
+ if (showDownloadPrompt) {
296
+ if (key.escape) {
297
+ setShowDownloadPrompt(false);
298
+ setDownloadPath("");
299
+ setSelectedObject(null);
300
+ }
301
+ else if (key.return) {
302
+ handleDownloadSubmit();
303
+ }
304
+ return;
305
+ }
306
+ // Handle popup navigation
307
+ if (showPopup) {
308
+ if (key.upArrow && selectedOperation > 0) {
309
+ setSelectedOperation(selectedOperation - 1);
310
+ }
311
+ else if (key.downArrow && selectedOperation < operations.length - 1) {
312
+ setSelectedOperation(selectedOperation + 1);
313
+ }
314
+ else if (key.return) {
315
+ setShowPopup(false);
316
+ const operationKey = operations[selectedOperation].key;
317
+ if (operationKey === "view_details") {
318
+ navigate("object-detail", {
319
+ objectId: selectedObjectItem.id,
320
+ });
321
+ }
322
+ else if (operationKey === "download") {
323
+ // Show download prompt
324
+ setSelectedObject(selectedObjectItem);
325
+ const defaultName = selectedObjectItem.name || selectedObjectItem.id;
326
+ setDownloadPath(`./${defaultName}`);
327
+ setShowDownloadPrompt(true);
328
+ }
329
+ else if (operationKey === "delete") {
330
+ // Show delete confirmation
331
+ setSelectedObject(selectedObjectItem);
332
+ setShowDeleteConfirm(true);
333
+ }
334
+ else {
335
+ setSelectedObject(selectedObjectItem);
336
+ setExecutingOperation(operationKey);
337
+ // Execute immediately with the object and operation passed directly
338
+ executeOperation(selectedObjectItem, operationKey);
339
+ }
340
+ }
341
+ else if (input === "v" && selectedObjectItem) {
342
+ // View details hotkey
343
+ setShowPopup(false);
344
+ navigate("object-detail", {
345
+ objectId: selectedObjectItem.id,
346
+ });
347
+ }
348
+ else if (key.escape || input === "q") {
349
+ setShowPopup(false);
350
+ setSelectedOperation(0);
351
+ }
352
+ else if (input === "w") {
353
+ // Download hotkey - show prompt
354
+ setShowPopup(false);
355
+ setSelectedObject(selectedObjectItem);
356
+ const defaultName = selectedObjectItem.name || selectedObjectItem.id;
357
+ setDownloadPath(`./${defaultName}`);
358
+ setShowDownloadPrompt(true);
359
+ }
360
+ else if (input === "d") {
361
+ // Delete hotkey - show confirmation
362
+ setShowPopup(false);
363
+ setSelectedObject(selectedObjectItem);
364
+ setShowDeleteConfirm(true);
365
+ }
366
+ return;
367
+ }
368
+ const pageObjects = objects.length;
369
+ // Handle list view navigation
370
+ if (key.upArrow && selectedIndex > 0) {
371
+ setSelectedIndex(selectedIndex - 1);
372
+ }
373
+ else if (key.downArrow && selectedIndex < pageObjects - 1) {
374
+ setSelectedIndex(selectedIndex + 1);
375
+ }
376
+ else if ((input === "n" || key.rightArrow) &&
377
+ !loading &&
378
+ !navigating &&
379
+ hasMore) {
380
+ nextPage();
381
+ setSelectedIndex(0);
382
+ }
383
+ else if ((input === "p" || key.leftArrow) &&
384
+ !loading &&
385
+ !navigating &&
386
+ hasPrev) {
387
+ prevPage();
388
+ setSelectedIndex(0);
389
+ }
390
+ else if (key.return && selectedObjectItem) {
391
+ // Enter key navigates to detail view
392
+ navigate("object-detail", {
393
+ objectId: selectedObjectItem.id,
394
+ });
395
+ }
396
+ else if (input === "a" && selectedObjectItem) {
397
+ setShowPopup(true);
398
+ setSelectedOperation(0);
399
+ }
400
+ else if (key.escape) {
401
+ if (onBack) {
402
+ onBack();
403
+ }
404
+ else if (onExit) {
405
+ onExit();
406
+ }
407
+ else {
408
+ inkExit();
409
+ }
410
+ }
411
+ });
412
+ // Operation result display
413
+ if (operationResult || operationError) {
414
+ const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
415
+ "Operation";
416
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
417
+ { label: "Storage Objects" },
418
+ {
419
+ label: selectedObject?.name || selectedObject?.id || "Object",
420
+ },
421
+ { label: operationLabel, active: true },
422
+ ] }), _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" }] })] }));
423
+ }
424
+ // Download prompt
425
+ if (showDownloadPrompt && selectedObject) {
426
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
427
+ { label: "Storage Objects" },
428
+ { label: selectedObject.name || selectedObject.id },
429
+ { label: "Download", active: true },
430
+ ] }), _jsx(Header, { title: "Download Storage Object" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: colors.text, children: [figures.arrowRight, " Downloading:", " ", _jsx(Text, { color: colors.primary, children: selectedObject.name || selectedObject.id })] }), selectedObject.size_bytes && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.info, " Size: ", formatFileSize(selectedObject.size_bytes)] }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.text, children: "Save to path:" }), _jsxs(Box, { marginTop: 0, children: [_jsxs(Text, { color: colors.primary, children: [figures.pointer, " "] }), _jsx(TextInput, { value: downloadPath, onChange: setDownloadPath, placeholder: "./filename" })] })] }), _jsx(NavigationTips, { tips: [
431
+ { key: "Enter", label: "Download" },
432
+ { key: "Esc", label: "Cancel" },
433
+ ] })] }));
434
+ }
435
+ // Delete confirmation
436
+ if (showDeleteConfirm && selectedObject) {
437
+ return (_jsx(ConfirmationPrompt, { title: "Delete Storage Object", message: `Are you sure you want to delete "${selectedObject.name || selectedObject.id}"?`, details: "This action cannot be undone.", breadcrumbItems: [
438
+ { label: "Storage Objects" },
439
+ { label: selectedObject.name || selectedObject.id },
440
+ { label: "Delete", active: true },
441
+ ], onConfirm: () => {
442
+ setShowDeleteConfirm(false);
443
+ setExecutingOperation("delete");
444
+ executeOperation(selectedObject, "delete");
445
+ }, onCancel: () => {
446
+ setShowDeleteConfirm(false);
447
+ setSelectedObject(null);
448
+ } }));
449
+ }
450
+ // Operation loading state
451
+ if (operationLoading && selectedObject) {
452
+ const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
453
+ "Operation";
454
+ const messages = {
455
+ delete: "Deleting storage object...",
456
+ download: "Downloading...",
457
+ };
458
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
459
+ { label: "Storage Objects" },
460
+ { label: selectedObject.name || selectedObject.id },
461
+ { label: operationLabel, active: true },
462
+ ] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: messages[executingOperation] || "Please wait..." })] }));
463
+ }
464
+ // Loading state
465
+ if (loading && objects.length === 0) {
466
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Storage Objects", active: true }] }), _jsx(SpinnerComponent, { message: "Loading storage objects..." })] }));
467
+ }
468
+ // Error state
469
+ if (error) {
470
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Storage Objects", active: true }] }), _jsx(ErrorMessage, { message: "Failed to list storage objects", error: error })] }));
471
+ }
472
+ // Main list view
473
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Storage Objects", active: true }] }), !showPopup && (_jsx(Table, { data: objects, keyExtractor: (obj) => obj.id, selectedIndex: selectedIndex, title: `storage_objects[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No storage objects found. Try: rli object upload", " ", "<file>"] }) })), !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 && selectedObjectItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedObjectItem, operations: operations.map((op) => ({
474
+ key: op.key,
475
+ label: op.label,
476
+ color: op.color,
477
+ icon: op.icon,
478
+ shortcut: op.key === "view_details"
479
+ ? "v"
480
+ : op.key === "download"
481
+ ? "w"
482
+ : op.key === "delete"
483
+ ? "d"
484
+ : "",
485
+ })), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
486
+ {
487
+ icon: `${figures.arrowLeft}${figures.arrowRight}`,
488
+ label: "Page",
489
+ condition: hasMore || hasPrev,
490
+ },
491
+ { key: "Enter", label: "Details" },
492
+ { key: "a", label: "Actions" },
493
+ { key: "Esc", label: "Back" },
494
+ ] })] }));
495
+ };
496
+ // Export the UI component for use in the main menu
497
+ export { ListObjectsUI };
498
+ export async function listObjects(options) {
7
499
  try {
8
500
  const client = getClient();
9
- // Build params
10
- const params = {};
11
- if (options.limit)
12
- params.limit = options.limit;
13
- if (options.startingAfter)
14
- params.startingAfter = options.startingAfter;
15
- if (options.name)
16
- params.name = options.name;
17
- if (options.contentType)
18
- params.contentType = options.contentType;
19
- if (options.state)
20
- params.state = options.state;
21
- if (options.search)
22
- params.search = options.search;
23
- if (options.public)
24
- params.isPublic = true;
25
- const result = options.public
26
- ? await client.objects.listPublic(params)
27
- : await client.objects.list(params);
501
+ // Build query params
502
+ const queryParams = {
503
+ limit: DEFAULT_PAGE_SIZE,
504
+ };
505
+ if (options.name) {
506
+ queryParams.name = options.name;
507
+ }
508
+ if (options.contentType) {
509
+ queryParams.content_type = options.contentType;
510
+ }
511
+ if (options.state) {
512
+ queryParams.state = options.state;
513
+ }
514
+ if (options.public !== undefined) {
515
+ queryParams.is_public = options.public;
516
+ }
517
+ // Fetch objects
518
+ const result = await client.objects.list(queryParams);
519
+ // Extract objects array
28
520
  const objects = result.objects || [];
29
521
  output(objects, { format: options.output, defaultFormat: "json" });
30
522
  }
31
523
  catch (error) {
32
- outputError("Failed to list objects", error);
524
+ outputError("Failed to list storage objects", error);
33
525
  }
34
526
  }