@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
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Network Policy Service - Handles all network policy API calls
3
+ */
4
+ import { getClient } from "../utils/client.js";
5
+ /**
6
+ * List network policies with pagination
7
+ */
8
+ export async function listNetworkPolicies(options) {
9
+ const client = getClient();
10
+ const queryParams = {
11
+ limit: options.limit,
12
+ };
13
+ if (options.startingAfter) {
14
+ queryParams.starting_after = options.startingAfter;
15
+ }
16
+ if (options.search) {
17
+ queryParams.name = options.search;
18
+ }
19
+ const pagePromise = client.networkPolicies.list(queryParams);
20
+ const page = (await pagePromise);
21
+ const networkPolicies = [];
22
+ if (page.network_policies && Array.isArray(page.network_policies)) {
23
+ page.network_policies.forEach((p) => {
24
+ // CRITICAL: Truncate all strings to prevent Yoga crashes
25
+ const MAX_ID_LENGTH = 100;
26
+ const MAX_NAME_LENGTH = 200;
27
+ const MAX_DESC_LENGTH = 500;
28
+ networkPolicies.push({
29
+ id: String(p.id || "").substring(0, MAX_ID_LENGTH),
30
+ name: String(p.name || "").substring(0, MAX_NAME_LENGTH),
31
+ description: p.description
32
+ ? String(p.description).substring(0, MAX_DESC_LENGTH)
33
+ : undefined,
34
+ create_time_ms: p.create_time_ms,
35
+ update_time_ms: p.update_time_ms,
36
+ egress: {
37
+ allow_all: p.egress.allow_all,
38
+ allow_devbox_to_devbox: p.egress.allow_devbox_to_devbox,
39
+ allowed_hostnames: p.egress.allowed_hostnames || [],
40
+ },
41
+ });
42
+ });
43
+ }
44
+ const result = {
45
+ networkPolicies,
46
+ totalCount: page.total_count || networkPolicies.length,
47
+ hasMore: page.has_more || false,
48
+ };
49
+ return result;
50
+ }
51
+ /**
52
+ * Get a single network policy by ID
53
+ */
54
+ export async function getNetworkPolicy(id) {
55
+ const client = getClient();
56
+ const policy = await client.networkPolicies.retrieve(id);
57
+ return {
58
+ id: policy.id,
59
+ name: policy.name,
60
+ description: policy.description ?? undefined,
61
+ create_time_ms: policy.create_time_ms,
62
+ update_time_ms: policy.update_time_ms,
63
+ egress: {
64
+ allow_all: policy.egress.allow_all,
65
+ allow_devbox_to_devbox: policy.egress.allow_devbox_to_devbox,
66
+ allowed_hostnames: policy.egress.allowed_hostnames || [],
67
+ },
68
+ };
69
+ }
70
+ /**
71
+ * Delete a network policy
72
+ */
73
+ export async function deleteNetworkPolicy(id) {
74
+ const client = getClient();
75
+ await client.networkPolicies.delete(id);
76
+ }
77
+ export async function createNetworkPolicy(params) {
78
+ const client = getClient();
79
+ const policy = await client.networkPolicies.create(params);
80
+ return {
81
+ id: policy.id,
82
+ name: policy.name,
83
+ description: policy.description ?? undefined,
84
+ create_time_ms: policy.create_time_ms,
85
+ update_time_ms: policy.update_time_ms,
86
+ egress: {
87
+ allow_all: policy.egress.allow_all,
88
+ allow_devbox_to_devbox: policy.egress.allow_devbox_to_devbox,
89
+ allowed_hostnames: policy.egress.allowed_hostnames || [],
90
+ },
91
+ };
92
+ }
93
+ export async function updateNetworkPolicy(id, params) {
94
+ const client = getClient();
95
+ const policy = await client.networkPolicies.update(id, params);
96
+ return {
97
+ id: policy.id,
98
+ name: policy.name,
99
+ description: policy.description ?? undefined,
100
+ create_time_ms: policy.create_time_ms,
101
+ update_time_ms: policy.update_time_ms,
102
+ egress: {
103
+ allow_all: policy.egress.allow_all,
104
+ allow_devbox_to_devbox: policy.egress.allow_devbox_to_devbox,
105
+ allowed_hostnames: policy.egress.allowed_hostnames || [],
106
+ },
107
+ };
108
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Object Service - Handles all storage object API calls
3
+ */
4
+ import { getClient } from "../utils/client.js";
5
+ /**
6
+ * List storage objects with pagination
7
+ */
8
+ export async function listObjects(options) {
9
+ const client = getClient();
10
+ const queryParams = {
11
+ limit: options.limit,
12
+ };
13
+ if (options.startingAfter) {
14
+ queryParams.starting_after = options.startingAfter;
15
+ }
16
+ if (options.name) {
17
+ queryParams.name = options.name;
18
+ }
19
+ if (options.contentType) {
20
+ queryParams.content_type = options.contentType;
21
+ }
22
+ if (options.state) {
23
+ queryParams.state = options.state;
24
+ }
25
+ if (options.isPublic !== undefined) {
26
+ queryParams.is_public = options.isPublic;
27
+ }
28
+ const result = await client.objects.list(queryParams);
29
+ const objects = [];
30
+ if (result.objects && Array.isArray(result.objects)) {
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ result.objects.forEach((obj) => {
33
+ // CRITICAL: Truncate all strings to prevent Yoga crashes
34
+ const MAX_ID_LENGTH = 100;
35
+ const MAX_NAME_LENGTH = 200;
36
+ const MAX_CONTENT_TYPE_LENGTH = 100;
37
+ const MAX_STATE_LENGTH = 50;
38
+ objects.push({
39
+ id: String(obj.id || "").substring(0, MAX_ID_LENGTH),
40
+ name: String(obj.name || "").substring(0, MAX_NAME_LENGTH),
41
+ content_type: obj.content_type || "unspecified",
42
+ create_time_ms: obj.create_time_ms || 0,
43
+ state: obj.state || "UPLOADING",
44
+ size_bytes: obj.size_bytes,
45
+ delete_after_time_ms: obj.delete_after_time_ms,
46
+ // UI-specific extended fields
47
+ is_public: obj.is_public,
48
+ });
49
+ });
50
+ }
51
+ // Access pagination properties from the result
52
+ const pageResult = result;
53
+ return {
54
+ objects,
55
+ totalCount: pageResult.total_count || objects.length,
56
+ hasMore: pageResult.has_more || false,
57
+ };
58
+ }
59
+ /**
60
+ * Get full object details by ID
61
+ */
62
+ export async function getObject(id) {
63
+ const client = getClient();
64
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
+ const obj = await client.objects.retrieve(id);
66
+ return {
67
+ id: obj.id,
68
+ name: obj.name || "",
69
+ content_type: obj.content_type || "unspecified",
70
+ create_time_ms: obj.create_time_ms || 0,
71
+ state: obj.state || "UPLOADING",
72
+ size_bytes: obj.size_bytes,
73
+ delete_after_time_ms: obj.delete_after_time_ms,
74
+ // UI-specific extended fields
75
+ is_public: obj.is_public,
76
+ download_url: obj.download_url || undefined,
77
+ metadata: obj.metadata,
78
+ };
79
+ }
80
+ /**
81
+ * Delete an object
82
+ */
83
+ export async function deleteObject(id) {
84
+ const client = getClient();
85
+ await client.objects.delete(id);
86
+ }
87
+ /**
88
+ * Format file size in human-readable format
89
+ */
90
+ export function formatFileSize(bytes) {
91
+ if (bytes === undefined || bytes === null)
92
+ return "Unknown";
93
+ const units = ["B", "KB", "MB", "GB", "TB"];
94
+ let size = bytes;
95
+ let unitIndex = 0;
96
+ while (size >= 1024 && unitIndex < units.length - 1) {
97
+ size /= 1024;
98
+ unitIndex++;
99
+ }
100
+ return `${size.toFixed(unitIndex > 0 ? 2 : 0)} ${units[unitIndex]}`;
101
+ }
@@ -33,11 +33,14 @@ export async function listSnapshots(options) {
33
33
  name: snapshotView.name
34
34
  ? String(snapshotView.name).substring(0, MAX_NAME_LENGTH)
35
35
  : undefined,
36
+ create_time_ms: snapshotView.create_time_ms,
37
+ metadata: snapshotView.metadata || {},
38
+ source_devbox_id: String(snapshotView.source_devbox_id || "").substring(0, MAX_ID_LENGTH),
39
+ // UI-specific extended fields
36
40
  devbox_id: String(snapshotView.source_devbox_id || "").substring(0, MAX_ID_LENGTH),
37
41
  status: snapshotView.status
38
42
  ? String(snapshotView.status).substring(0, MAX_STATUS_LENGTH)
39
43
  : "",
40
- create_time_ms: snapshotView.create_time_ms,
41
44
  });
42
45
  });
43
46
  }
@@ -56,6 +59,36 @@ export async function getSnapshotStatus(id) {
56
59
  const status = await client.devboxes.diskSnapshots.queryStatus(id);
57
60
  return status;
58
61
  }
62
+ /**
63
+ * Get full snapshot details by ID
64
+ */
65
+ export async function getSnapshot(id) {
66
+ const client = getClient();
67
+ const statusResponse = await client.devboxes.diskSnapshots.queryStatus(id);
68
+ // The queryStatus returns a status wrapper with snapshot data inside
69
+ const snapshot = statusResponse.snapshot;
70
+ const operationStatus = statusResponse.status; // 'in_progress', 'error', 'complete', 'deleted'
71
+ if (!snapshot) {
72
+ // If no snapshot data yet, return minimal info based on operation status
73
+ return {
74
+ id: id,
75
+ create_time_ms: Date.now(),
76
+ metadata: {},
77
+ source_devbox_id: "",
78
+ status: operationStatus === "in_progress" ? "pending" : operationStatus,
79
+ };
80
+ }
81
+ return {
82
+ id: snapshot.id,
83
+ name: snapshot.name || undefined,
84
+ create_time_ms: snapshot.create_time_ms,
85
+ metadata: snapshot.metadata || {},
86
+ source_devbox_id: snapshot.source_devbox_id || "",
87
+ // UI-specific extended fields
88
+ devbox_id: snapshot.source_devbox_id || undefined,
89
+ status: operationStatus === "complete" ? "ready" : operationStatus,
90
+ };
91
+ }
59
92
  /**
60
93
  * Create a snapshot
61
94
  */
@@ -67,9 +100,12 @@ export async function createSnapshot(devboxId, name) {
67
100
  return {
68
101
  id: snapshot.id,
69
102
  name: snapshot.name || undefined,
70
- devbox_id: snapshot.devbox_id || devboxId,
71
- status: snapshot.status || "pending",
72
103
  create_time_ms: snapshot.create_time_ms,
104
+ metadata: snapshot.metadata || {},
105
+ source_devbox_id: snapshot.source_devbox_id || devboxId,
106
+ // UI-specific extended fields
107
+ devbox_id: snapshot.source_devbox_id || devboxId,
108
+ status: "pending",
73
109
  };
74
110
  }
75
111
  /**
@@ -38,16 +38,10 @@ export const useBlueprintStore = create((set, get) => ({
38
38
  lastIdCache.delete(oldestKey);
39
39
  }
40
40
  }
41
- // Create plain data objects to avoid SDK references
42
- const plainData = data.map((b) => ({
43
- id: b.id,
44
- name: b.name,
45
- status: b.status,
46
- create_time_ms: b.create_time_ms,
47
- build_status: b.build_status,
48
- architecture: b.architecture,
49
- resources: b.resources,
50
- }));
41
+ // Deep copy all fields to avoid SDK references
42
+ const plainData = data.map((d) => {
43
+ return JSON.parse(JSON.stringify(d));
44
+ });
51
45
  pageCache.set(page, plainData);
52
46
  lastIdCache.set(page, lastId);
53
47
  set({});
@@ -5,3 +5,4 @@ export { useNavigation, useNavigationStore, NavigationProvider, } from "./naviga
5
5
  export { useDevboxStore } from "./devboxStore.js";
6
6
  export { useBlueprintStore } from "./blueprintStore.js";
7
7
  export { useSnapshotStore } from "./snapshotStore.js";
8
+ export { useObjectStore } from "./objectStore.js";
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Network Policy Store - Manages network policy list state, pagination, and caching
3
+ */
4
+ import { create } from "zustand";
5
+ const MAX_CACHE_SIZE = 10;
6
+ export const useNetworkPolicyStore = create((set, get) => ({
7
+ networkPolicies: [],
8
+ loading: false,
9
+ initialLoading: true,
10
+ error: null,
11
+ currentPage: 0,
12
+ pageSize: 10,
13
+ totalCount: 0,
14
+ hasMore: false,
15
+ pageCache: new Map(),
16
+ lastIdCache: new Map(),
17
+ searchQuery: "",
18
+ selectedIndex: 0,
19
+ setNetworkPolicies: (policies) => set({ networkPolicies: policies }),
20
+ setLoading: (loading) => set({ loading }),
21
+ setInitialLoading: (loading) => set({ initialLoading: loading }),
22
+ setError: (error) => set({ error }),
23
+ setCurrentPage: (page) => set({ currentPage: page }),
24
+ setPageSize: (size) => set({ pageSize: size }),
25
+ setTotalCount: (count) => set({ totalCount: count }),
26
+ setHasMore: (hasMore) => set({ hasMore }),
27
+ setSearchQuery: (query) => set({ searchQuery: query }),
28
+ setSelectedIndex: (index) => set({ selectedIndex: index }),
29
+ cachePageData: (page, data, lastId) => {
30
+ const state = get();
31
+ const pageCache = state.pageCache;
32
+ const lastIdCache = state.lastIdCache;
33
+ // Aggressive LRU eviction
34
+ if (pageCache.size >= MAX_CACHE_SIZE) {
35
+ const oldestKey = pageCache.keys().next().value;
36
+ if (oldestKey !== undefined) {
37
+ pageCache.delete(oldestKey);
38
+ lastIdCache.delete(oldestKey);
39
+ }
40
+ }
41
+ // Deep copy all fields to avoid SDK references
42
+ const plainData = data.map((d) => {
43
+ return JSON.parse(JSON.stringify(d));
44
+ });
45
+ pageCache.set(page, plainData);
46
+ lastIdCache.set(page, lastId);
47
+ set({});
48
+ },
49
+ getCachedPage: (page) => {
50
+ return get().pageCache.get(page);
51
+ },
52
+ clearCache: () => {
53
+ const state = get();
54
+ state.pageCache.clear();
55
+ state.lastIdCache.clear();
56
+ set({
57
+ pageCache: new Map(),
58
+ lastIdCache: new Map(),
59
+ });
60
+ },
61
+ clearAll: () => {
62
+ const state = get();
63
+ state.pageCache.clear();
64
+ state.lastIdCache.clear();
65
+ set({
66
+ networkPolicies: [],
67
+ loading: false,
68
+ initialLoading: true,
69
+ error: null,
70
+ currentPage: 0,
71
+ totalCount: 0,
72
+ hasMore: false,
73
+ pageCache: new Map(),
74
+ lastIdCache: new Map(),
75
+ searchQuery: "",
76
+ selectedIndex: 0,
77
+ });
78
+ },
79
+ getSelectedNetworkPolicy: () => {
80
+ const state = get();
81
+ return state.networkPolicies[state.selectedIndex];
82
+ },
83
+ }));
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Object Store - Manages storage object list state, pagination, and caching
3
+ */
4
+ import { create } from "zustand";
5
+ const MAX_CACHE_SIZE = 10;
6
+ export const useObjectStore = create((set, get) => ({
7
+ objects: [],
8
+ loading: false,
9
+ initialLoading: true,
10
+ error: null,
11
+ currentPage: 0,
12
+ pageSize: 10,
13
+ totalCount: 0,
14
+ hasMore: false,
15
+ pageCache: new Map(),
16
+ lastIdCache: new Map(),
17
+ nameFilter: undefined,
18
+ contentTypeFilter: undefined,
19
+ stateFilter: undefined,
20
+ isPublicFilter: undefined,
21
+ selectedIndex: 0,
22
+ setObjects: (objects) => set({ objects }),
23
+ setLoading: (loading) => set({ loading }),
24
+ setInitialLoading: (loading) => set({ initialLoading: loading }),
25
+ setError: (error) => set({ error }),
26
+ setCurrentPage: (page) => set({ currentPage: page }),
27
+ setPageSize: (size) => set({ pageSize: size }),
28
+ setTotalCount: (count) => set({ totalCount: count }),
29
+ setHasMore: (hasMore) => set({ hasMore }),
30
+ setNameFilter: (name) => set({ nameFilter: name }),
31
+ setContentTypeFilter: (contentType) => set({ contentTypeFilter: contentType }),
32
+ setStateFilter: (state) => set({ stateFilter: state }),
33
+ setIsPublicFilter: (isPublic) => set({ isPublicFilter: isPublic }),
34
+ setSelectedIndex: (index) => set({ selectedIndex: index }),
35
+ cachePageData: (page, data, lastId) => {
36
+ const state = get();
37
+ const pageCache = state.pageCache;
38
+ const lastIdCache = state.lastIdCache;
39
+ // Aggressive LRU eviction
40
+ if (pageCache.size >= MAX_CACHE_SIZE) {
41
+ const oldestKey = pageCache.keys().next().value;
42
+ if (oldestKey !== undefined) {
43
+ pageCache.delete(oldestKey);
44
+ lastIdCache.delete(oldestKey);
45
+ }
46
+ }
47
+ // Deep copy all fields to avoid SDK references
48
+ const plainData = data.map((d) => {
49
+ return JSON.parse(JSON.stringify(d));
50
+ });
51
+ pageCache.set(page, plainData);
52
+ lastIdCache.set(page, lastId);
53
+ set({});
54
+ },
55
+ getCachedPage: (page) => {
56
+ return get().pageCache.get(page);
57
+ },
58
+ clearCache: () => {
59
+ const state = get();
60
+ state.pageCache.clear();
61
+ state.lastIdCache.clear();
62
+ set({
63
+ pageCache: new Map(),
64
+ lastIdCache: new Map(),
65
+ });
66
+ },
67
+ clearAll: () => {
68
+ const state = get();
69
+ state.pageCache.clear();
70
+ state.lastIdCache.clear();
71
+ set({
72
+ objects: [],
73
+ loading: false,
74
+ initialLoading: true,
75
+ error: null,
76
+ currentPage: 0,
77
+ totalCount: 0,
78
+ hasMore: false,
79
+ pageCache: new Map(),
80
+ lastIdCache: new Map(),
81
+ nameFilter: undefined,
82
+ contentTypeFilter: undefined,
83
+ stateFilter: undefined,
84
+ isPublicFilter: undefined,
85
+ selectedIndex: 0,
86
+ });
87
+ },
88
+ getSelectedObject: () => {
89
+ const state = get();
90
+ return state.objects[state.selectedIndex];
91
+ },
92
+ }));
@@ -38,14 +38,10 @@ export const useSnapshotStore = create((set, get) => ({
38
38
  lastIdCache.delete(oldestKey);
39
39
  }
40
40
  }
41
- // Create plain data objects to avoid SDK references
42
- const plainData = data.map((s) => ({
43
- id: s.id,
44
- name: s.name,
45
- devbox_id: s.devbox_id,
46
- status: s.status,
47
- create_time_ms: s.create_time_ms,
48
- }));
41
+ // Deep copy all fields to avoid SDK references
42
+ const plainData = data.map((d) => {
43
+ return JSON.parse(JSON.stringify(d));
44
+ });
49
45
  pageCache.set(page, plainData);
50
46
  lastIdCache.set(page, lastId);
51
47
  set({});
@@ -38,6 +38,7 @@ export function createProgram() {
38
38
  .option("--available-ports <ports...>", "Available ports")
39
39
  .option("--root", "Run as root")
40
40
  .option("--user <user:uid>", "Run as this user (format: username:uid)")
41
+ .option("--network-policy <id>", "Network policy ID to apply")
41
42
  .option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
42
43
  .action(createDevbox);
43
44
  devbox
@@ -311,6 +312,24 @@ export function createProgram() {
311
312
  const { pruneBlueprints } = await import("../commands/blueprint/prune.js");
312
313
  await pruneBlueprints(name, options);
313
314
  });
315
+ blueprint
316
+ .command("from-dockerfile")
317
+ .description("Create a blueprint from a Dockerfile with build context support")
318
+ .requiredOption("--name <name>", "Blueprint name (required)")
319
+ .option("--build-context <path>", "Build context directory (default: current directory)")
320
+ .option("--dockerfile <path>", "Dockerfile path (default: Dockerfile in build context)")
321
+ .option("--system-setup-commands <commands...>", "System setup commands")
322
+ .option("--resources <size>", "Resource size (X_SMALL, SMALL, MEDIUM, LARGE, X_LARGE, XX_LARGE)")
323
+ .option("--architecture <arch>", "Architecture (arm64, x86_64)")
324
+ .option("--available-ports <ports...>", "Available ports")
325
+ .option("--root", "Run as root")
326
+ .option("--user <user:uid>", "Run as this user (format: username:uid)")
327
+ .option("--ttl <seconds>", "TTL in seconds for the build context object (default: 3600)")
328
+ .option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
329
+ .action(async (options) => {
330
+ const { createBlueprintFromDockerfile } = await import("../commands/blueprint/from-dockerfile.js");
331
+ await createBlueprintFromDockerfile(options);
332
+ });
314
333
  // Object storage commands
315
334
  const object = program
316
335
  .command("object")
@@ -374,6 +393,52 @@ export function createProgram() {
374
393
  const { deleteObject } = await import("../commands/object/delete.js");
375
394
  await deleteObject({ id, ...options });
376
395
  });
396
+ // Network policy commands
397
+ const networkPolicy = program
398
+ .command("network-policy")
399
+ .description("Manage network policies")
400
+ .alias("np");
401
+ networkPolicy
402
+ .command("list")
403
+ .description("List network policies")
404
+ .option("--limit <n>", "Max results", "20")
405
+ .option("--starting-after <id>", "Starting point for pagination")
406
+ .option("--name <name>", "Filter by name")
407
+ .option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
408
+ .action(async (options) => {
409
+ const { listNetworkPolicies } = await import("../commands/network-policy/list.js");
410
+ await listNetworkPolicies(options);
411
+ });
412
+ networkPolicy
413
+ .command("get <id>")
414
+ .description("Get network policy details")
415
+ .option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
416
+ .action(async (id, options) => {
417
+ const { getNetworkPolicy } = await import("../commands/network-policy/get.js");
418
+ await getNetworkPolicy({ id, ...options });
419
+ });
420
+ networkPolicy
421
+ .command("create")
422
+ .description("Create a new network policy")
423
+ .requiredOption("--name <name>", "Policy name (required)")
424
+ .option("--description <description>", "Policy description")
425
+ .option("--allow-all", "Allow all egress traffic")
426
+ .option("--allow-devbox-to-devbox", "Allow devbox-to-devbox communication")
427
+ .option("--allowed-hostnames <hostnames...>", "List of allowed hostnames for egress")
428
+ .option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
429
+ .action(async (options) => {
430
+ const { createNetworkPolicy } = await import("../commands/network-policy/create.js");
431
+ await createNetworkPolicy(options);
432
+ });
433
+ networkPolicy
434
+ .command("delete <id>")
435
+ .description("Delete a network policy")
436
+ .alias("rm")
437
+ .option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
438
+ .action(async (id, options) => {
439
+ const { deleteNetworkPolicy } = await import("../commands/network-policy/delete.js");
440
+ await deleteNetworkPolicy(id, options);
441
+ });
377
442
  // MCP server commands
378
443
  const mcp = program
379
444
  .command("mcp")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runloop/rl-cli",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Beautiful CLI for the Runloop platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "dependencies": {
69
69
  "@modelcontextprotocol/sdk": "^1.19.1",
70
- "@runloop/api-client": "^1.0.0",
70
+ "@runloop/api-client": "1.3.0",
71
71
  "@types/express": "^5.0.3",
72
72
  "chalk": "^5.3.0",
73
73
  "commander": "^14.0.1",