@runloop/rl-cli 1.2.0 → 1.3.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 (55) hide show
  1. package/README.md +28 -8
  2. package/dist/commands/blueprint/list.js +97 -28
  3. package/dist/commands/blueprint/prune.js +7 -19
  4. package/dist/commands/devbox/create.js +3 -0
  5. package/dist/commands/devbox/list.js +44 -65
  6. package/dist/commands/menu.js +2 -1
  7. package/dist/commands/network-policy/create.js +27 -0
  8. package/dist/commands/network-policy/delete.js +21 -0
  9. package/dist/commands/network-policy/get.js +15 -0
  10. package/dist/commands/network-policy/list.js +494 -0
  11. package/dist/commands/object/list.js +516 -24
  12. package/dist/commands/snapshot/list.js +90 -29
  13. package/dist/components/Banner.js +109 -8
  14. package/dist/components/ConfirmationPrompt.js +45 -0
  15. package/dist/components/DevboxActionsMenu.js +42 -6
  16. package/dist/components/DevboxCard.js +1 -1
  17. package/dist/components/DevboxCreatePage.js +95 -81
  18. package/dist/components/DevboxDetailPage.js +218 -272
  19. package/dist/components/LogsViewer.js +8 -1
  20. package/dist/components/MainMenu.js +35 -4
  21. package/dist/components/NavigationTips.js +24 -0
  22. package/dist/components/NetworkPolicyCreatePage.js +264 -0
  23. package/dist/components/OperationsMenu.js +9 -1
  24. package/dist/components/ResourceActionsMenu.js +5 -1
  25. package/dist/components/ResourceDetailPage.js +204 -0
  26. package/dist/components/ResourceListView.js +19 -2
  27. package/dist/components/StatusBadge.js +2 -2
  28. package/dist/components/Table.js +6 -8
  29. package/dist/components/form/FormActionButton.js +7 -0
  30. package/dist/components/form/FormField.js +7 -0
  31. package/dist/components/form/FormListManager.js +112 -0
  32. package/dist/components/form/FormSelect.js +34 -0
  33. package/dist/components/form/FormTextInput.js +8 -0
  34. package/dist/components/form/index.js +8 -0
  35. package/dist/hooks/useViewportHeight.js +38 -20
  36. package/dist/router/Router.js +23 -1
  37. package/dist/screens/BlueprintDetailScreen.js +337 -0
  38. package/dist/screens/MenuScreen.js +6 -0
  39. package/dist/screens/NetworkPolicyCreateScreen.js +7 -0
  40. package/dist/screens/NetworkPolicyDetailScreen.js +247 -0
  41. package/dist/screens/NetworkPolicyListScreen.js +7 -0
  42. package/dist/screens/ObjectDetailScreen.js +377 -0
  43. package/dist/screens/ObjectListScreen.js +7 -0
  44. package/dist/screens/SnapshotDetailScreen.js +208 -0
  45. package/dist/services/blueprintService.js +30 -11
  46. package/dist/services/networkPolicyService.js +108 -0
  47. package/dist/services/objectService.js +101 -0
  48. package/dist/services/snapshotService.js +39 -3
  49. package/dist/store/blueprintStore.js +4 -10
  50. package/dist/store/index.js +1 -0
  51. package/dist/store/networkPolicyStore.js +83 -0
  52. package/dist/store/objectStore.js +92 -0
  53. package/dist/store/snapshotStore.js +4 -8
  54. package/dist/utils/commands.js +47 -0
  55. package/package.json +2 -2
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Delete network policy command
3
+ */
4
+ import { getClient } from "../../utils/client.js";
5
+ import { output, outputError } from "../../utils/output.js";
6
+ export async function deleteNetworkPolicy(id, options = {}) {
7
+ try {
8
+ const client = getClient();
9
+ await client.networkPolicies.delete(id);
10
+ // Default: just output the ID for easy scripting
11
+ if (!options.output || options.output === "text") {
12
+ console.log(id);
13
+ }
14
+ else {
15
+ output({ id, status: "deleted" }, { format: options.output, defaultFormat: "json" });
16
+ }
17
+ }
18
+ catch (error) {
19
+ outputError("Failed to delete network policy", error);
20
+ }
21
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Get network policy details command
3
+ */
4
+ import { getClient } from "../../utils/client.js";
5
+ import { output, outputError } from "../../utils/output.js";
6
+ export async function getNetworkPolicy(options) {
7
+ try {
8
+ const client = getClient();
9
+ const policy = await client.networkPolicies.retrieve(options.id);
10
+ output(policy, { format: options.output, defaultFormat: "json" });
11
+ }
12
+ catch (error) {
13
+ outputError("Failed to get network policy", error);
14
+ }
15
+ }
@@ -0,0 +1,494 @@
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 { output, outputError } from "../../utils/output.js";
16
+ import { colors } from "../../utils/theme.js";
17
+ import { useViewportHeight } from "../../hooks/useViewportHeight.js";
18
+ import { useExitOnCtrlC } from "../../hooks/useExitOnCtrlC.js";
19
+ import { useCursorPagination } from "../../hooks/useCursorPagination.js";
20
+ import { useNavigation } from "../../store/navigationStore.js";
21
+ import { NetworkPolicyCreatePage } from "../../components/NetworkPolicyCreatePage.js";
22
+ import { ConfirmationPrompt } from "../../components/ConfirmationPrompt.js";
23
+ const DEFAULT_PAGE_SIZE = 10;
24
+ /**
25
+ * Get a display label for the egress policy type
26
+ */
27
+ function getEgressTypeLabel(egress) {
28
+ if (egress.allow_all) {
29
+ return "Allow All";
30
+ }
31
+ if (egress.allowed_hostnames.length === 0) {
32
+ return "Deny All";
33
+ }
34
+ return `Custom (${egress.allowed_hostnames.length})`;
35
+ }
36
+ /**
37
+ * Get color for egress type
38
+ */
39
+ function getEgressTypeColor(egress) {
40
+ if (egress.allow_all) {
41
+ return colors.success;
42
+ }
43
+ if (egress.allowed_hostnames.length === 0) {
44
+ return colors.error;
45
+ }
46
+ return colors.warning;
47
+ }
48
+ const ListNetworkPoliciesUI = ({ onBack, onExit, }) => {
49
+ const { exit: inkExit } = useApp();
50
+ const { navigate } = useNavigation();
51
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
52
+ const [showPopup, setShowPopup] = React.useState(false);
53
+ const [selectedOperation, setSelectedOperation] = React.useState(0);
54
+ const [selectedPolicy, setSelectedPolicy] = React.useState(null);
55
+ const [executingOperation, setExecutingOperation] = React.useState(null);
56
+ const [operationResult, setOperationResult] = React.useState(null);
57
+ const [operationError, setOperationError] = React.useState(null);
58
+ const [operationLoading, setOperationLoading] = React.useState(false);
59
+ const [showCreatePolicy, setShowCreatePolicy] = React.useState(false);
60
+ const [showEditPolicy, setShowEditPolicy] = React.useState(false);
61
+ const [editingPolicy, setEditingPolicy] = React.useState(null);
62
+ const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
63
+ // Calculate overhead for viewport height
64
+ const overhead = 13;
65
+ const { viewportHeight, terminalWidth } = useViewportHeight({
66
+ overhead,
67
+ minHeight: 5,
68
+ });
69
+ const PAGE_SIZE = viewportHeight;
70
+ // All width constants
71
+ const fixedWidth = 6; // border + padding
72
+ const idWidth = 25;
73
+ const egressWidth = 15;
74
+ const timeWidth = 20;
75
+ const showDescription = terminalWidth >= 100;
76
+ const descriptionWidth = Math.max(20, terminalWidth >= 140 ? 40 : 25);
77
+ // Name width uses remaining space after fixed columns
78
+ const baseWidth = fixedWidth + idWidth + egressWidth + timeWidth;
79
+ const optionalWidth = showDescription ? descriptionWidth : 0;
80
+ const remainingWidth = terminalWidth - baseWidth - optionalWidth;
81
+ const nameWidth = Math.min(80, Math.max(15, remainingWidth));
82
+ // Fetch function for pagination hook
83
+ const fetchPage = React.useCallback(async (params) => {
84
+ const client = getClient();
85
+ const pagePolicies = [];
86
+ // Build query params
87
+ const queryParams = {
88
+ limit: params.limit,
89
+ };
90
+ if (params.startingAt) {
91
+ queryParams.starting_after = params.startingAt;
92
+ }
93
+ // Fetch ONE page only
94
+ const page = (await client.networkPolicies.list(queryParams));
95
+ // Extract data and create defensive copies
96
+ if (page.network_policies && Array.isArray(page.network_policies)) {
97
+ page.network_policies.forEach((p) => {
98
+ pagePolicies.push({
99
+ id: p.id,
100
+ name: p.name,
101
+ description: p.description,
102
+ create_time_ms: p.create_time_ms,
103
+ update_time_ms: p.update_time_ms,
104
+ egress: {
105
+ allow_all: p.egress.allow_all,
106
+ allow_devbox_to_devbox: p.egress.allow_devbox_to_devbox,
107
+ allowed_hostnames: [...(p.egress.allowed_hostnames || [])],
108
+ },
109
+ });
110
+ });
111
+ }
112
+ const result = {
113
+ items: pagePolicies,
114
+ hasMore: page.has_more || false,
115
+ totalCount: page.total_count || pagePolicies.length,
116
+ };
117
+ return result;
118
+ }, []);
119
+ // Use the shared pagination hook
120
+ const { items: policies, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
121
+ fetchPage,
122
+ pageSize: PAGE_SIZE,
123
+ getItemId: (policy) => policy.id,
124
+ pollInterval: 5000,
125
+ pollingEnabled: !showPopup &&
126
+ !executingOperation &&
127
+ !showCreatePolicy &&
128
+ !showEditPolicy &&
129
+ !showDeleteConfirm,
130
+ deps: [PAGE_SIZE],
131
+ });
132
+ // Operations for a specific network policy (shown in popup)
133
+ const operations = React.useMemo(() => [
134
+ {
135
+ key: "view_details",
136
+ label: "View Details",
137
+ color: colors.primary,
138
+ icon: figures.pointer,
139
+ },
140
+ {
141
+ key: "edit",
142
+ label: "Edit Network Policy",
143
+ color: colors.warning,
144
+ icon: figures.pointer,
145
+ },
146
+ {
147
+ key: "delete",
148
+ label: "Delete Network Policy",
149
+ color: colors.error,
150
+ icon: figures.cross,
151
+ },
152
+ ], []);
153
+ // Build columns
154
+ const columns = React.useMemo(() => [
155
+ createTextColumn("id", "ID", (policy) => policy.id, {
156
+ width: idWidth + 1,
157
+ color: colors.idColor,
158
+ dimColor: false,
159
+ bold: false,
160
+ }),
161
+ createTextColumn("name", "Name", (policy) => policy.name || "", {
162
+ width: nameWidth,
163
+ }),
164
+ ...(showDescription
165
+ ? [
166
+ createTextColumn("description", "Description", (policy) => policy.description || "", {
167
+ width: descriptionWidth,
168
+ color: colors.textDim,
169
+ dimColor: false,
170
+ bold: false,
171
+ }),
172
+ ]
173
+ : []),
174
+ {
175
+ key: "egress",
176
+ label: "Egress",
177
+ width: egressWidth,
178
+ render: (policy, _index, isSelected) => {
179
+ const label = getEgressTypeLabel(policy.egress);
180
+ const color = getEgressTypeColor(policy.egress);
181
+ const safeWidth = Math.max(1, egressWidth);
182
+ const truncated = label.slice(0, safeWidth);
183
+ const padded = truncated.padEnd(safeWidth, " ");
184
+ return (_jsx(Text, { color: isSelected ? "white" : color, bold: true, dimColor: false, inverse: isSelected, wrap: "truncate", children: padded }));
185
+ },
186
+ },
187
+ createTextColumn("created", "Created", (policy) => policy.create_time_ms ? formatTimeAgo(policy.create_time_ms) : "", {
188
+ width: timeWidth,
189
+ color: colors.textDim,
190
+ dimColor: false,
191
+ bold: false,
192
+ }),
193
+ ], [
194
+ idWidth,
195
+ nameWidth,
196
+ descriptionWidth,
197
+ egressWidth,
198
+ timeWidth,
199
+ showDescription,
200
+ ]);
201
+ // Handle Ctrl+C to exit
202
+ useExitOnCtrlC();
203
+ // Ensure selected index is within bounds
204
+ React.useEffect(() => {
205
+ if (policies.length > 0 && selectedIndex >= policies.length) {
206
+ setSelectedIndex(Math.max(0, policies.length - 1));
207
+ }
208
+ }, [policies.length, selectedIndex]);
209
+ const selectedPolicyItem = policies[selectedIndex];
210
+ // Calculate pagination info for display
211
+ const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
212
+ const startIndex = currentPage * PAGE_SIZE;
213
+ const endIndex = startIndex + policies.length;
214
+ const executeOperation = async (policy, operationKey) => {
215
+ const client = getClient();
216
+ if (!policy)
217
+ return;
218
+ try {
219
+ setOperationLoading(true);
220
+ switch (operationKey) {
221
+ case "delete":
222
+ await client.networkPolicies.delete(policy.id);
223
+ setOperationResult(`Network policy "${policy.name}" deleted successfully`);
224
+ break;
225
+ }
226
+ }
227
+ catch (err) {
228
+ setOperationError(err);
229
+ }
230
+ finally {
231
+ setOperationLoading(false);
232
+ }
233
+ };
234
+ useInput((input, key) => {
235
+ // Handle operation result display
236
+ if (operationResult || operationError) {
237
+ if (input === "q" || key.escape || key.return) {
238
+ const wasDelete = executingOperation === "delete";
239
+ const hadError = operationError !== null;
240
+ setOperationResult(null);
241
+ setOperationError(null);
242
+ setExecutingOperation(null);
243
+ setSelectedPolicy(null);
244
+ // Refresh the list after delete to show updated data
245
+ if (wasDelete && !hadError) {
246
+ setTimeout(() => refresh(), 0);
247
+ }
248
+ }
249
+ return;
250
+ }
251
+ // Handle create policy screen
252
+ if (showCreatePolicy) {
253
+ return;
254
+ }
255
+ // Handle edit policy screen
256
+ if (showEditPolicy) {
257
+ return;
258
+ }
259
+ // Handle popup navigation
260
+ if (showPopup) {
261
+ if (key.upArrow && selectedOperation > 0) {
262
+ setSelectedOperation(selectedOperation - 1);
263
+ }
264
+ else if (key.downArrow && selectedOperation < operations.length - 1) {
265
+ setSelectedOperation(selectedOperation + 1);
266
+ }
267
+ else if (key.return) {
268
+ setShowPopup(false);
269
+ const operationKey = operations[selectedOperation].key;
270
+ if (operationKey === "create") {
271
+ setShowCreatePolicy(true);
272
+ }
273
+ else if (operationKey === "view_details") {
274
+ navigate("network-policy-detail", {
275
+ networkPolicyId: selectedPolicyItem.id,
276
+ });
277
+ }
278
+ else if (operationKey === "edit") {
279
+ // Show edit form
280
+ setEditingPolicy(selectedPolicyItem);
281
+ setShowEditPolicy(true);
282
+ }
283
+ else if (operationKey === "delete") {
284
+ // Show delete confirmation
285
+ setSelectedPolicy(selectedPolicyItem);
286
+ setShowDeleteConfirm(true);
287
+ }
288
+ else {
289
+ setSelectedPolicy(selectedPolicyItem);
290
+ setExecutingOperation(operationKey);
291
+ // Execute immediately with values passed directly
292
+ executeOperation(selectedPolicyItem, operationKey);
293
+ }
294
+ }
295
+ else if (input === "c") {
296
+ // Create hotkey
297
+ setShowPopup(false);
298
+ setShowCreatePolicy(true);
299
+ }
300
+ else if (input === "v" && selectedPolicyItem) {
301
+ // View details hotkey
302
+ setShowPopup(false);
303
+ navigate("network-policy-detail", {
304
+ networkPolicyId: selectedPolicyItem.id,
305
+ });
306
+ }
307
+ else if (input === "e" && selectedPolicyItem) {
308
+ // Edit hotkey
309
+ setShowPopup(false);
310
+ setEditingPolicy(selectedPolicyItem);
311
+ setShowEditPolicy(true);
312
+ }
313
+ else if (key.escape || input === "q") {
314
+ setShowPopup(false);
315
+ setSelectedOperation(0);
316
+ }
317
+ else if (input === "d") {
318
+ // Delete hotkey - show confirmation
319
+ setShowPopup(false);
320
+ setSelectedPolicy(selectedPolicyItem);
321
+ setShowDeleteConfirm(true);
322
+ }
323
+ return;
324
+ }
325
+ const pagePolicies = policies.length;
326
+ // Handle list view navigation
327
+ if (key.upArrow && selectedIndex > 0) {
328
+ setSelectedIndex(selectedIndex - 1);
329
+ }
330
+ else if (key.downArrow && selectedIndex < pagePolicies - 1) {
331
+ setSelectedIndex(selectedIndex + 1);
332
+ }
333
+ else if ((input === "n" || key.rightArrow) &&
334
+ !loading &&
335
+ !navigating &&
336
+ hasMore) {
337
+ nextPage();
338
+ setSelectedIndex(0);
339
+ }
340
+ else if ((input === "p" || key.leftArrow) &&
341
+ !loading &&
342
+ !navigating &&
343
+ hasPrev) {
344
+ prevPage();
345
+ setSelectedIndex(0);
346
+ }
347
+ else if (key.return && selectedPolicyItem) {
348
+ // Enter key navigates to detail view
349
+ navigate("network-policy-detail", {
350
+ networkPolicyId: selectedPolicyItem.id,
351
+ });
352
+ }
353
+ else if (input === "a") {
354
+ setShowPopup(true);
355
+ setSelectedOperation(0);
356
+ }
357
+ else if (input === "c") {
358
+ // Create shortcut
359
+ setShowCreatePolicy(true);
360
+ }
361
+ else if (input === "e" && selectedPolicyItem) {
362
+ // Edit shortcut
363
+ setEditingPolicy(selectedPolicyItem);
364
+ setShowEditPolicy(true);
365
+ }
366
+ else if (key.escape) {
367
+ if (onBack) {
368
+ onBack();
369
+ }
370
+ else if (onExit) {
371
+ onExit();
372
+ }
373
+ else {
374
+ inkExit();
375
+ }
376
+ }
377
+ });
378
+ // Delete confirmation
379
+ if (showDeleteConfirm && selectedPolicy) {
380
+ return (_jsx(ConfirmationPrompt, { title: "Delete Network Policy", message: `Are you sure you want to delete "${selectedPolicy.name}"?`, details: "This action cannot be undone. Any devboxes using this policy will lose their network restrictions.", breadcrumbItems: [
381
+ { label: "Network Policies" },
382
+ { label: selectedPolicy.name || selectedPolicy.id },
383
+ { label: "Delete", active: true },
384
+ ], onConfirm: () => {
385
+ setShowDeleteConfirm(false);
386
+ setExecutingOperation("delete");
387
+ executeOperation(selectedPolicy, "delete");
388
+ }, onCancel: () => {
389
+ setShowDeleteConfirm(false);
390
+ setSelectedPolicy(null);
391
+ } }));
392
+ }
393
+ // Operation result display
394
+ if (operationResult || operationError) {
395
+ const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
396
+ "Operation";
397
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
398
+ { label: "Network Policies" },
399
+ {
400
+ label: selectedPolicy?.name || selectedPolicy?.id || "Policy",
401
+ },
402
+ { label: operationLabel, active: true },
403
+ ] }), _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" }] })] }));
404
+ }
405
+ // Operation loading state
406
+ if (operationLoading && selectedPolicy) {
407
+ const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
408
+ "Operation";
409
+ const messages = {
410
+ delete: "Deleting network policy...",
411
+ };
412
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
413
+ { label: "Network Policies" },
414
+ { label: selectedPolicy.name || selectedPolicy.id },
415
+ { label: operationLabel, active: true },
416
+ ] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: messages[executingOperation] || "Please wait..." })] }));
417
+ }
418
+ // Create policy screen
419
+ if (showCreatePolicy) {
420
+ return (_jsx(NetworkPolicyCreatePage, { onBack: () => setShowCreatePolicy(false), onCreate: (policy) => {
421
+ setShowCreatePolicy(false);
422
+ navigate("network-policy-detail", { networkPolicyId: policy.id });
423
+ } }));
424
+ }
425
+ // Edit policy screen
426
+ if (showEditPolicy && editingPolicy) {
427
+ return (_jsx(NetworkPolicyCreatePage, { onBack: () => {
428
+ setShowEditPolicy(false);
429
+ setEditingPolicy(null);
430
+ }, onCreate: () => {
431
+ setShowEditPolicy(false);
432
+ setEditingPolicy(null);
433
+ // Refresh the list to show updated data
434
+ setTimeout(() => refresh(), 0);
435
+ }, initialPolicy: editingPolicy }));
436
+ }
437
+ // Loading state
438
+ if (loading && policies.length === 0) {
439
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Network Policies", active: true }] }), _jsx(SpinnerComponent, { message: "Loading network policies..." })] }));
440
+ }
441
+ // Error state
442
+ if (error) {
443
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Network Policies", active: true }] }), _jsx(ErrorMessage, { message: "Failed to list network policies", error: error })] }));
444
+ }
445
+ // Main list view
446
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Network Policies", active: true }] }), !showPopup && (_jsx(Table, { data: policies, keyExtractor: (policy) => policy.id, selectedIndex: selectedIndex, title: `network_policies[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No network policies 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] })] })), showPopup && selectedPolicyItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedPolicyItem, operations: operations.map((op) => ({
447
+ key: op.key,
448
+ label: op.label,
449
+ color: op.color,
450
+ icon: op.icon,
451
+ shortcut: op.key === "create"
452
+ ? "c"
453
+ : op.key === "view_details"
454
+ ? "v"
455
+ : op.key === "edit"
456
+ ? "e"
457
+ : op.key === "delete"
458
+ ? "d"
459
+ : "",
460
+ })), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
461
+ {
462
+ icon: `${figures.arrowLeft}${figures.arrowRight}`,
463
+ label: "Page",
464
+ condition: hasMore || hasPrev,
465
+ },
466
+ { key: "Enter", label: "Details" },
467
+ { key: "c", label: "Create" },
468
+ { key: "e", label: "Edit" },
469
+ { key: "a", label: "Actions" },
470
+ { key: "Esc", label: "Back" },
471
+ ] })] }));
472
+ };
473
+ // Export the UI component for use in the main menu
474
+ export { ListNetworkPoliciesUI };
475
+ export async function listNetworkPolicies(options = {}) {
476
+ try {
477
+ const client = getClient();
478
+ // Build query params
479
+ const queryParams = {
480
+ limit: DEFAULT_PAGE_SIZE,
481
+ };
482
+ if (options.name) {
483
+ queryParams.name = options.name;
484
+ }
485
+ // Fetch network policies
486
+ const page = (await client.networkPolicies.list(queryParams));
487
+ // Extract network policies array
488
+ const networkPolicies = page.network_policies || [];
489
+ output(networkPolicies, { format: options.output, defaultFormat: "json" });
490
+ }
491
+ catch (error) {
492
+ outputError("Failed to list network policies", error);
493
+ }
494
+ }