@runloop/rl-cli 0.1.1 → 0.2.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 (96) hide show
  1. package/README.md +54 -0
  2. package/dist/cli.js +73 -60
  3. package/dist/commands/auth.js +0 -1
  4. package/dist/commands/blueprint/create.js +31 -83
  5. package/dist/commands/blueprint/get.js +29 -34
  6. package/dist/commands/blueprint/list.js +215 -213
  7. package/dist/commands/blueprint/logs.js +133 -37
  8. package/dist/commands/blueprint/preview.js +42 -38
  9. package/dist/commands/config.js +117 -0
  10. package/dist/commands/devbox/create.js +120 -40
  11. package/dist/commands/devbox/delete.js +17 -33
  12. package/dist/commands/devbox/download.js +29 -43
  13. package/dist/commands/devbox/exec.js +22 -39
  14. package/dist/commands/devbox/execAsync.js +20 -37
  15. package/dist/commands/devbox/get.js +13 -35
  16. package/dist/commands/devbox/getAsync.js +12 -34
  17. package/dist/commands/devbox/list.js +241 -402
  18. package/dist/commands/devbox/logs.js +20 -38
  19. package/dist/commands/devbox/read.js +29 -43
  20. package/dist/commands/devbox/resume.js +13 -35
  21. package/dist/commands/devbox/rsync.js +26 -78
  22. package/dist/commands/devbox/scp.js +25 -79
  23. package/dist/commands/devbox/sendStdin.js +41 -0
  24. package/dist/commands/devbox/shutdown.js +13 -35
  25. package/dist/commands/devbox/ssh.js +45 -78
  26. package/dist/commands/devbox/suspend.js +13 -35
  27. package/dist/commands/devbox/tunnel.js +36 -88
  28. package/dist/commands/devbox/upload.js +28 -36
  29. package/dist/commands/devbox/write.js +29 -44
  30. package/dist/commands/mcp-install.js +4 -3
  31. package/dist/commands/menu.js +24 -66
  32. package/dist/commands/object/delete.js +12 -34
  33. package/dist/commands/object/download.js +26 -74
  34. package/dist/commands/object/get.js +12 -34
  35. package/dist/commands/object/list.js +15 -93
  36. package/dist/commands/object/upload.js +35 -96
  37. package/dist/commands/snapshot/create.js +23 -39
  38. package/dist/commands/snapshot/delete.js +17 -33
  39. package/dist/commands/snapshot/get.js +16 -0
  40. package/dist/commands/snapshot/list.js +309 -80
  41. package/dist/commands/snapshot/status.js +12 -34
  42. package/dist/components/ActionsPopup.js +63 -39
  43. package/dist/components/Breadcrumb.js +10 -52
  44. package/dist/components/DevboxActionsMenu.js +182 -110
  45. package/dist/components/DevboxCreatePage.js +12 -7
  46. package/dist/components/DevboxDetailPage.js +76 -28
  47. package/dist/components/ErrorBoundary.js +29 -0
  48. package/dist/components/ErrorMessage.js +10 -2
  49. package/dist/components/Header.js +12 -4
  50. package/dist/components/InteractiveSpawn.js +94 -0
  51. package/dist/components/MainMenu.js +36 -32
  52. package/dist/components/MetadataDisplay.js +4 -4
  53. package/dist/components/OperationsMenu.js +1 -1
  54. package/dist/components/ResourceActionsMenu.js +4 -4
  55. package/dist/components/ResourceListView.js +46 -34
  56. package/dist/components/Spinner.js +7 -2
  57. package/dist/components/StatusBadge.js +1 -1
  58. package/dist/components/SuccessMessage.js +12 -2
  59. package/dist/components/Table.js +16 -6
  60. package/dist/hooks/useCursorPagination.js +125 -85
  61. package/dist/hooks/useExitOnCtrlC.js +14 -0
  62. package/dist/hooks/useViewportHeight.js +47 -0
  63. package/dist/mcp/server.js +65 -6
  64. package/dist/router/Router.js +68 -0
  65. package/dist/router/types.js +1 -0
  66. package/dist/screens/BlueprintListScreen.js +7 -0
  67. package/dist/screens/DevboxActionsScreen.js +25 -0
  68. package/dist/screens/DevboxCreateScreen.js +11 -0
  69. package/dist/screens/DevboxDetailScreen.js +60 -0
  70. package/dist/screens/DevboxListScreen.js +23 -0
  71. package/dist/screens/LogsSessionScreen.js +49 -0
  72. package/dist/screens/MenuScreen.js +23 -0
  73. package/dist/screens/SSHSessionScreen.js +55 -0
  74. package/dist/screens/SnapshotListScreen.js +7 -0
  75. package/dist/services/blueprintService.js +105 -0
  76. package/dist/services/devboxService.js +215 -0
  77. package/dist/services/snapshotService.js +81 -0
  78. package/dist/store/blueprintStore.js +89 -0
  79. package/dist/store/devboxStore.js +105 -0
  80. package/dist/store/index.js +7 -0
  81. package/dist/store/navigationStore.js +101 -0
  82. package/dist/store/snapshotStore.js +87 -0
  83. package/dist/utils/CommandExecutor.js +53 -24
  84. package/dist/utils/client.js +0 -2
  85. package/dist/utils/config.js +20 -90
  86. package/dist/utils/interactiveCommand.js +3 -2
  87. package/dist/utils/logFormatter.js +162 -0
  88. package/dist/utils/memoryMonitor.js +85 -0
  89. package/dist/utils/output.js +150 -59
  90. package/dist/utils/screen.js +23 -0
  91. package/dist/utils/ssh.js +3 -1
  92. package/dist/utils/sshSession.js +5 -29
  93. package/dist/utils/terminalDetection.js +97 -0
  94. package/dist/utils/terminalSync.js +39 -0
  95. package/dist/utils/theme.js +147 -13
  96. package/package.json +16 -13
@@ -1,37 +1,15 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
1
+ /**
2
+ * Delete object command
3
+ */
3
4
  import { getClient } from "../../utils/client.js";
4
- import { Banner } from "../../components/Banner.js";
5
- import { SpinnerComponent } from "../../components/Spinner.js";
6
- import { SuccessMessage } from "../../components/SuccessMessage.js";
7
- import { ErrorMessage } from "../../components/ErrorMessage.js";
8
- import { createExecutor } from "../../utils/CommandExecutor.js";
9
- const DeleteObjectUI = ({ objectId }) => {
10
- const [loading, setLoading] = React.useState(true);
11
- const [result, setResult] = React.useState(null);
12
- const [error, setError] = React.useState(null);
13
- React.useEffect(() => {
14
- const deleteObject = async () => {
15
- try {
16
- const client = getClient();
17
- const deletedObject = await client.objects.delete(objectId);
18
- setResult(deletedObject);
19
- }
20
- catch (err) {
21
- setError(err);
22
- }
23
- finally {
24
- setLoading(false);
25
- }
26
- };
27
- deleteObject();
28
- }, [objectId]);
29
- return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Deleting object..." }), result && (_jsx(SuccessMessage, { message: "Object deleted successfully", details: `ID: ${result.id}\nName: ${result.name}\nThis action is irreversible` })), error && (_jsx(ErrorMessage, { message: "Failed to delete object", error: error }))] }));
30
- };
5
+ import { output, outputError } from "../../utils/output.js";
31
6
  export async function deleteObject(options) {
32
- const executor = createExecutor({ output: options.outputFormat });
33
- await executor.executeAction(async () => {
34
- const client = executor.getClient();
35
- return client.objects.delete(options.id);
36
- }, () => _jsx(DeleteObjectUI, { objectId: options.id }));
7
+ try {
8
+ const client = getClient();
9
+ const deletedObject = await client.objects.delete(options.id);
10
+ output(deletedObject, { format: options.output, defaultFormat: "json" });
11
+ }
12
+ catch (error) {
13
+ outputError("Failed to delete object", error);
14
+ }
37
15
  }
@@ -1,62 +1,12 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
1
+ /**
2
+ * Download object command
3
+ */
4
+ import { writeFile } from "fs/promises";
3
5
  import { getClient } from "../../utils/client.js";
4
- import { Banner } from "../../components/Banner.js";
5
- import { SpinnerComponent } from "../../components/Spinner.js";
6
- import { SuccessMessage } from "../../components/SuccessMessage.js";
7
- import { ErrorMessage } from "../../components/ErrorMessage.js";
8
- import { createExecutor } from "../../utils/CommandExecutor.js";
9
- const DownloadObjectUI = ({ objectId, path, extract, durationSeconds }) => {
10
- const [loading, setLoading] = React.useState(true);
11
- const [result, setResult] = React.useState(null);
12
- const [error, setError] = React.useState(null);
13
- React.useEffect(() => {
14
- const downloadObject = async () => {
15
- try {
16
- const client = getClient();
17
- // Get the object metadata first
18
- const object = await client.objects.retrieve(objectId);
19
- // Get the download URL
20
- const downloadUrlResponse = await client.objects.download(objectId, {
21
- duration_seconds: durationSeconds || 3600,
22
- });
23
- // Download the file
24
- const response = await fetch(downloadUrlResponse.download_url);
25
- if (!response.ok) {
26
- throw new Error(`Download failed: HTTP ${response.status}`);
27
- }
28
- // Handle extraction if requested
29
- if (extract) {
30
- // For now, just save to the specified path
31
- // In a full implementation, you'd handle archive extraction here
32
- const arrayBuffer = await response.arrayBuffer();
33
- const buffer = Buffer.from(arrayBuffer);
34
- await import("fs/promises").then((fs) => fs.writeFile(path, buffer));
35
- }
36
- else {
37
- const arrayBuffer = await response.arrayBuffer();
38
- const buffer = Buffer.from(arrayBuffer);
39
- await import("fs/promises").then((fs) => fs.writeFile(path, buffer));
40
- }
41
- setResult({ objectId, path, extract });
42
- }
43
- catch (err) {
44
- setError(err);
45
- }
46
- finally {
47
- setLoading(false);
48
- }
49
- };
50
- downloadObject();
51
- }, [objectId, path, extract, durationSeconds]);
52
- return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Downloading object..." }), result && (_jsx(SuccessMessage, { message: "Object downloaded successfully", details: `Object ID: ${result.objectId}\nPath: ${result.path}\nExtracted: ${result.extract ? "Yes" : "No"}` })), error && (_jsx(ErrorMessage, { message: "Failed to download object", error: error }))] }));
53
- };
6
+ import { output, outputError } from "../../utils/output.js";
54
7
  export async function downloadObject(options) {
55
- const executor = createExecutor({ output: options.outputFormat });
56
- await executor.executeAction(async () => {
57
- const client = executor.getClient();
58
- // Get the object metadata first
59
- const object = await client.objects.retrieve(options.id);
8
+ try {
9
+ const client = getClient();
60
10
  // Get the download URL
61
11
  const downloadUrlResponse = await client.objects.download(options.id, {
62
12
  duration_seconds: options.durationSeconds || 3600,
@@ -64,25 +14,27 @@ export async function downloadObject(options) {
64
14
  // Download the file
65
15
  const response = await fetch(downloadUrlResponse.download_url);
66
16
  if (!response.ok) {
67
- throw new Error(`Download failed: HTTP ${response.status}`);
17
+ outputError(`Download failed: HTTP ${response.status}`);
68
18
  }
69
- // Handle extraction if requested
70
- if (options.extract) {
71
- // For now, just save to the specified path
72
- // In a full implementation, you'd handle archive extraction here
73
- const arrayBuffer = await response.arrayBuffer();
74
- const buffer = Buffer.from(arrayBuffer);
75
- await import("fs/promises").then((fs) => fs.writeFile(options.path, buffer));
19
+ // Save the file
20
+ const arrayBuffer = await response.arrayBuffer();
21
+ const buffer = Buffer.from(arrayBuffer);
22
+ await writeFile(options.path, buffer);
23
+ // TODO: Handle extraction if requested (options.extract)
24
+ const result = {
25
+ id: options.id,
26
+ path: options.path,
27
+ extracted: options.extract || false,
28
+ };
29
+ // Default: just output the local path for easy scripting
30
+ if (!options.output || options.output === "text") {
31
+ console.log(options.path);
76
32
  }
77
33
  else {
78
- const arrayBuffer = await response.arrayBuffer();
79
- const buffer = Buffer.from(arrayBuffer);
80
- await import("fs/promises").then((fs) => fs.writeFile(options.path, buffer));
34
+ output(result, { format: options.output, defaultFormat: "json" });
81
35
  }
82
- return {
83
- objectId: options.id,
84
- path: options.path,
85
- extract: options.extract,
86
- };
87
- }, () => (_jsx(DownloadObjectUI, { objectId: options.id, path: options.path, extract: options.extract, durationSeconds: options.durationSeconds })));
36
+ }
37
+ catch (error) {
38
+ outputError("Failed to download object", error);
39
+ }
88
40
  }
@@ -1,37 +1,15 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
1
+ /**
2
+ * Get object details command
3
+ */
3
4
  import { getClient } from "../../utils/client.js";
4
- import { Banner } from "../../components/Banner.js";
5
- import { SpinnerComponent } from "../../components/Spinner.js";
6
- import { SuccessMessage } from "../../components/SuccessMessage.js";
7
- import { ErrorMessage } from "../../components/ErrorMessage.js";
8
- import { createExecutor } from "../../utils/CommandExecutor.js";
9
- const GetObjectUI = ({ objectId }) => {
10
- const [loading, setLoading] = React.useState(true);
11
- const [result, setResult] = React.useState(null);
12
- const [error, setError] = React.useState(null);
13
- React.useEffect(() => {
14
- const getObject = async () => {
15
- try {
16
- const client = getClient();
17
- const object = await client.objects.retrieve(objectId);
18
- setResult(object);
19
- }
20
- catch (err) {
21
- setError(err);
22
- }
23
- finally {
24
- setLoading(false);
25
- }
26
- };
27
- getObject();
28
- }, [objectId]);
29
- return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Fetching object details..." }), result && (_jsx(SuccessMessage, { message: "Object details retrieved", details: `ID: ${result.id}\nName: ${result.name}\nType: ${result.contentType}\nState: ${result.state}\nSize: ${result.sizeBytes ? `${result.sizeBytes} bytes` : "Unknown"}` })), error && _jsx(ErrorMessage, { message: "Failed to get object", error: error })] }));
30
- };
5
+ import { output, outputError } from "../../utils/output.js";
31
6
  export async function getObject(options) {
32
- const executor = createExecutor({ output: options.outputFormat });
33
- await executor.executeAction(async () => {
34
- const client = executor.getClient();
35
- return client.objects.retrieve(options.id);
36
- }, () => _jsx(GetObjectUI, { objectId: options.id }));
7
+ try {
8
+ const client = getClient();
9
+ const object = await client.objects.retrieve(options.id);
10
+ output(object, { format: options.output, defaultFormat: "json" });
11
+ }
12
+ catch (error) {
13
+ outputError("Failed to get object", error);
14
+ }
37
15
  }
@@ -1,94 +1,12 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React from "react";
3
- import { Box, Text } from "ink";
1
+ /**
2
+ * List objects command
3
+ */
4
4
  import { getClient } from "../../utils/client.js";
5
- import { Banner } from "../../components/Banner.js";
6
- import { SpinnerComponent } from "../../components/Spinner.js";
7
- import { ErrorMessage } from "../../components/ErrorMessage.js";
8
- import { createExecutor } from "../../utils/CommandExecutor.js";
9
- import { colors } from "../../utils/theme.js";
10
- import { Table } from "../../components/Table.js";
11
- const ListObjectsUI = ({ options }) => {
12
- const [loading, setLoading] = React.useState(true);
13
- const [result, setResult] = React.useState(null);
14
- const [error, setError] = React.useState(null);
15
- React.useEffect(() => {
16
- const listObjects = async () => {
17
- try {
18
- const client = getClient();
19
- const params = {};
20
- if (options.limit)
21
- params.limit = options.limit;
22
- if (options.startingAfter)
23
- params.startingAfter = options.startingAfter;
24
- if (options.name)
25
- params.name = options.name;
26
- if (options.contentType)
27
- params.contentType = options.contentType;
28
- if (options.state)
29
- params.state = options.state;
30
- if (options.search)
31
- params.search = options.search;
32
- if (options.public)
33
- params.isPublic = true;
34
- const objects = options.public
35
- ? await client.objects.listPublic(params)
36
- : await client.objects.list(params);
37
- setResult(objects);
38
- }
39
- catch (err) {
40
- setError(err);
41
- }
42
- finally {
43
- setLoading(false);
44
- }
45
- };
46
- listObjects();
47
- }, [options]);
48
- const formatSize = (bytes) => {
49
- if (bytes < 1024)
50
- return `${bytes} B`;
51
- if (bytes < 1024 * 1024)
52
- return `${(bytes / 1024).toFixed(1)} KB`;
53
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
54
- };
55
- return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Fetching objects..." }), result && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.primary, children: "Objects:" }), result.objects && result.objects.length > 0 ? (_jsx(Table, { data: result.objects, keyExtractor: (item) => item.id, columns: [
56
- {
57
- key: "id",
58
- label: "ID",
59
- width: 20,
60
- render: (row) => (_jsx(Text, { color: colors.text, children: row.id })),
61
- },
62
- {
63
- key: "name",
64
- label: "Name",
65
- width: 30,
66
- render: (row) => (_jsx(Text, { color: colors.text, children: row.name })),
67
- },
68
- {
69
- key: "type",
70
- label: "Type",
71
- width: 15,
72
- render: (row) => (_jsx(Text, { color: colors.text, children: row.content_type })),
73
- },
74
- {
75
- key: "state",
76
- label: "State",
77
- width: 15,
78
- render: (row) => (_jsx(Text, { color: colors.text, children: row.state })),
79
- },
80
- {
81
- key: "size",
82
- label: "Size",
83
- width: 10,
84
- render: (row) => (_jsx(Text, { color: colors.text, children: row.size_bytes ? formatSize(row.size_bytes) : "N/A" })),
85
- },
86
- ] })) : (_jsx(Text, { color: colors.textDim, children: "No objects found" }))] })), error && _jsx(ErrorMessage, { message: "Failed to list objects", error: error })] }));
87
- };
88
- export async function listObjects(options) {
89
- const executor = createExecutor({ output: options.output });
90
- await executor.executeList(async () => {
91
- const client = executor.getClient();
5
+ import { output, outputError } from "../../utils/output.js";
6
+ export async function listObjects(options = {}) {
7
+ try {
8
+ const client = getClient();
9
+ // Build params
92
10
  const params = {};
93
11
  if (options.limit)
94
12
  params.limit = options.limit;
@@ -104,9 +22,13 @@ export async function listObjects(options) {
104
22
  params.search = options.search;
105
23
  if (options.public)
106
24
  params.isPublic = true;
107
- const objects = options.public
25
+ const result = options.public
108
26
  ? await client.objects.listPublic(params)
109
27
  : await client.objects.list(params);
110
- return objects.objects || [];
111
- }, () => _jsx(ListObjectsUI, { options: options }), options.limit || 20);
28
+ const objects = result.objects || [];
29
+ output(objects, { format: options.output, defaultFormat: "json" });
30
+ }
31
+ catch (error) {
32
+ outputError("Failed to list objects", error);
33
+ }
112
34
  }
@@ -1,106 +1,35 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
- import { getClient } from "../../utils/client.js";
4
- import { Banner } from "../../components/Banner.js";
5
- import { SpinnerComponent } from "../../components/Spinner.js";
6
- import { SuccessMessage } from "../../components/SuccessMessage.js";
7
- import { ErrorMessage } from "../../components/ErrorMessage.js";
8
- import { createExecutor } from "../../utils/CommandExecutor.js";
1
+ /**
2
+ * Upload object command
3
+ */
9
4
  import { readFile, stat } from "fs/promises";
10
5
  import { extname } from "path";
11
- const UploadObjectUI = ({ path, name, contentType }) => {
12
- const [loading, setLoading] = React.useState(true);
13
- const [result, setResult] = React.useState(null);
14
- const [error, setError] = React.useState(null);
15
- React.useEffect(() => {
16
- const uploadObject = async () => {
17
- try {
18
- const client = getClient();
19
- // Check if file exists
20
- const stats = await stat(path);
21
- const fileBuffer = await readFile(path);
22
- // Auto-detect content type if not provided
23
- let detectedContentType = contentType;
24
- if (!detectedContentType) {
25
- const ext = extname(path).toLowerCase();
26
- const contentTypeMap = {
27
- ".txt": "text",
28
- ".html": "text",
29
- ".css": "text",
30
- ".js": "text",
31
- ".json": "text",
32
- ".yaml": "text",
33
- ".yml": "text",
34
- ".md": "text",
35
- ".gz": "gzip",
36
- ".tar": "tar",
37
- ".tgz": "tgz",
38
- ".tar.gz": "tgz",
39
- };
40
- detectedContentType = contentTypeMap[ext] || "unspecified";
41
- }
42
- // Step 1: Create the object
43
- const createResponse = await client.objects.create({
44
- name,
45
- content_type: detectedContentType,
46
- });
47
- // Step 2: Upload the file
48
- const uploadResponse = await fetch(createResponse.upload_url, {
49
- method: "PUT",
50
- body: fileBuffer,
51
- headers: {
52
- "Content-Length": fileBuffer.length.toString(),
53
- },
54
- });
55
- if (!uploadResponse.ok) {
56
- throw new Error(`Upload failed: HTTP ${uploadResponse.status}`);
57
- }
58
- // Step 3: Complete the upload
59
- await client.objects.complete(createResponse.id);
60
- setResult({
61
- id: createResponse.id,
62
- name,
63
- contentType: detectedContentType,
64
- size: stats.size,
65
- });
66
- }
67
- catch (err) {
68
- setError(err);
69
- }
70
- finally {
71
- setLoading(false);
72
- }
73
- };
74
- uploadObject();
75
- }, [path, name, contentType]);
76
- return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Uploading object..." }), result && (_jsx(SuccessMessage, { message: "Object uploaded successfully", details: `ID: ${result.id}\nName: ${result.name}\nType: ${result.contentType}\nSize: ${result.size} bytes` })), error && (_jsx(ErrorMessage, { message: "Failed to upload object", error: error }))] }));
6
+ import { getClient } from "../../utils/client.js";
7
+ import { output, outputError } from "../../utils/output.js";
8
+ const CONTENT_TYPE_MAP = {
9
+ ".txt": "text",
10
+ ".html": "text",
11
+ ".css": "text",
12
+ ".js": "text",
13
+ ".json": "text",
14
+ ".yaml": "text",
15
+ ".yml": "text",
16
+ ".md": "text",
17
+ ".gz": "gzip",
18
+ ".tar": "tar",
19
+ ".tgz": "tgz",
20
+ ".tar.gz": "tgz",
77
21
  };
78
22
  export async function uploadObject(options) {
79
- const executor = createExecutor({ output: options.output });
80
- await executor.executeAction(async () => {
81
- const client = executor.getClient();
82
- // Check if file exists
23
+ try {
24
+ const client = getClient();
25
+ // Check if file exists and get stats
83
26
  const stats = await stat(options.path);
84
27
  const fileBuffer = await readFile(options.path);
85
28
  // Auto-detect content type if not provided
86
29
  let detectedContentType = options.contentType;
87
30
  if (!detectedContentType) {
88
31
  const ext = extname(options.path).toLowerCase();
89
- const contentTypeMap = {
90
- ".txt": "text",
91
- ".html": "text",
92
- ".css": "text",
93
- ".js": "text",
94
- ".json": "text",
95
- ".yaml": "text",
96
- ".yml": "text",
97
- ".md": "text",
98
- ".gz": "gzip",
99
- ".tar": "tar",
100
- ".tgz": "tgz",
101
- ".tar.gz": "tgz",
102
- };
103
- detectedContentType = contentTypeMap[ext] || "unspecified";
32
+ detectedContentType = CONTENT_TYPE_MAP[ext] || "unspecified";
104
33
  }
105
34
  // Step 1: Create the object
106
35
  const createResponse = await client.objects.create({
@@ -116,15 +45,25 @@ export async function uploadObject(options) {
116
45
  },
117
46
  });
118
47
  if (!uploadResponse.ok) {
119
- throw new Error(`Upload failed: HTTP ${uploadResponse.status}`);
48
+ outputError(`Upload failed: HTTP ${uploadResponse.status}`);
120
49
  }
121
50
  // Step 3: Complete the upload
122
51
  await client.objects.complete(createResponse.id);
123
- return {
52
+ const result = {
124
53
  id: createResponse.id,
125
54
  name: options.name,
126
55
  contentType: detectedContentType,
127
56
  size: stats.size,
128
57
  };
129
- }, () => (_jsx(UploadObjectUI, { path: options.path, name: options.name, contentType: options.contentType })));
58
+ // Default: just output the ID for easy scripting
59
+ if (!options.output || options.output === "text") {
60
+ console.log(result.id);
61
+ }
62
+ else {
63
+ output(result, { format: options.output, defaultFormat: "json" });
64
+ }
65
+ }
66
+ catch (error) {
67
+ outputError("Failed to upload object", error);
68
+ }
130
69
  }
@@ -1,41 +1,25 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React from "react";
3
- import { render, Box, Text } from "ink";
4
- import Gradient from "ink-gradient";
5
- import figures from "figures";
1
+ /**
2
+ * Create snapshot command
3
+ */
6
4
  import { getClient } from "../../utils/client.js";
7
- import { Header } from "../../components/Header.js";
8
- import { Banner } from "../../components/Banner.js";
9
- import { SpinnerComponent } from "../../components/Spinner.js";
10
- import { SuccessMessage } from "../../components/SuccessMessage.js";
11
- import { ErrorMessage } from "../../components/ErrorMessage.js";
12
- import { colors } from "../../utils/theme.js";
13
- const CreateSnapshotUI = ({ devboxId, name }) => {
14
- const [loading, setLoading] = React.useState(true);
15
- const [result, setResult] = React.useState(null);
16
- const [error, setError] = React.useState(null);
17
- React.useEffect(() => {
18
- const create = async () => {
19
- try {
20
- const client = getClient();
21
- const snapshot = await client.devboxes.snapshotDisk(devboxId, {
22
- ...(name && { name }),
23
- });
24
- setResult(snapshot);
25
- }
26
- catch (err) {
27
- setError(err);
28
- }
29
- finally {
30
- setLoading(false);
31
- }
32
- };
33
- create();
34
- }, []);
35
- return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), _jsx(Header, { title: "Create Snapshot", subtitle: "Taking a snapshot of your devbox..." }), loading && (_jsxs(_Fragment, { children: [_jsx(SpinnerComponent, { message: "Creating snapshot..." }), _jsxs(Box, { borderStyle: "round", borderColor: colors.info, paddingX: 3, paddingY: 1, marginY: 1, flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: colors.primary, bold: true, children: [figures.info, " Configuration"] }) }), _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.textDim, children: [figures.pointer, " Devbox ID:", " "] }), _jsx(Text, { color: colors.text, children: devboxId })] }), name && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.textDim, children: [figures.pointer, " Name: "] }), _jsx(Text, { color: colors.text, children: name })] }))] })] })] })), result && (_jsxs(_Fragment, { children: [_jsx(SuccessMessage, { message: "Snapshot created successfully!", details: `ID: ${result.id}\nName: ${result.name || "(unnamed)"}\nStatus: ${result.status}` }), _jsxs(Box, { borderStyle: "double", borderColor: colors.success, paddingX: 3, paddingY: 1, marginY: 1, flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Gradient, { name: "summer", children: _jsxs(Text, { bold: true, children: [figures.star, " Next Steps"] }) }) }), _jsxs(Box, { flexDirection: "column", gap: 1, marginLeft: 2, children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.textDim, children: [figures.tick, " View snapshots:", " "] }), _jsx(Text, { color: colors.primary, children: "rli snapshot list" })] }), _jsxs(Box, { children: [_jsxs(Text, { color: colors.textDim, children: [figures.tick, " Create devbox from snapshot:", " "] }), _jsxs(Text, { color: colors.primary, children: ["rli devbox create -t ", result.id] })] })] })] })] })), error && (_jsx(ErrorMessage, { message: "Failed to create snapshot", error: error }))] }));
36
- };
37
- export async function createSnapshot(devboxId, options) {
38
- console.clear();
39
- const { waitUntilExit } = render(_jsx(CreateSnapshotUI, { devboxId: devboxId, name: options.name }));
40
- await waitUntilExit();
5
+ import { output, outputError } from "../../utils/output.js";
6
+ export async function createSnapshot(devboxId, options = {}) {
7
+ try {
8
+ const client = getClient();
9
+ const snapshot = await client.devboxes.snapshotDisk(devboxId, {
10
+ ...(options.name && { name: options.name }),
11
+ });
12
+ // Default: just output the ID for easy scripting
13
+ if (!options.output || options.output === "text") {
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ const snapshotId = snapshot.id || snapshot.snapshot_id;
16
+ console.log(snapshotId);
17
+ }
18
+ else {
19
+ output(snapshot, { format: options.output, defaultFormat: "json" });
20
+ }
21
+ }
22
+ catch (error) {
23
+ outputError("Failed to create snapshot", error);
24
+ }
41
25
  }
@@ -1,37 +1,21 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
1
+ /**
2
+ * Delete snapshot command
3
+ */
3
4
  import { getClient } from "../../utils/client.js";
4
- import { Header } from "../../components/Header.js";
5
- import { SpinnerComponent } from "../../components/Spinner.js";
6
- import { SuccessMessage } from "../../components/SuccessMessage.js";
7
- import { ErrorMessage } from "../../components/ErrorMessage.js";
8
- import { createExecutor } from "../../utils/CommandExecutor.js";
9
- const DeleteSnapshotUI = ({ id }) => {
10
- const [loading, setLoading] = React.useState(true);
11
- const [success, setSuccess] = React.useState(false);
12
- const [error, setError] = React.useState(null);
13
- React.useEffect(() => {
14
- const deleteSnapshot = async () => {
15
- try {
16
- const client = getClient();
17
- await client.devboxes.diskSnapshots.delete(id);
18
- setSuccess(true);
19
- }
20
- catch (err) {
21
- setError(err);
22
- }
23
- finally {
24
- setLoading(false);
25
- }
26
- };
27
- deleteSnapshot();
28
- }, []);
29
- return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Delete Snapshot", subtitle: `Deleting snapshot: ${id}` }), loading && _jsx(SpinnerComponent, { message: "Deleting snapshot..." }), success && (_jsx(SuccessMessage, { message: "Snapshot deleted successfully!", details: `ID: ${id}` })), error && (_jsx(ErrorMessage, { message: "Failed to delete snapshot", error: error }))] }));
30
- };
5
+ import { output, outputError } from "../../utils/output.js";
31
6
  export async function deleteSnapshot(id, options = {}) {
32
- const executor = createExecutor(options);
33
- await executor.executeDelete(async () => {
34
- const client = executor.getClient();
7
+ try {
8
+ const client = getClient();
35
9
  await client.devboxes.diskSnapshots.delete(id);
36
- }, id, () => _jsx(DeleteSnapshotUI, { id: 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 snapshot", error);
20
+ }
37
21
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Get snapshot details command
3
+ */
4
+ import { getClient } from "../../utils/client.js";
5
+ import { output, outputError } from "../../utils/output.js";
6
+ export async function getSnapshot(options) {
7
+ try {
8
+ const client = getClient();
9
+ // This is the way to get snapshot details
10
+ const snapshotDetails = await client.devboxes.diskSnapshots.queryStatus(options.id);
11
+ output(snapshotDetails, { format: options.output, defaultFormat: "json" });
12
+ }
13
+ catch (error) {
14
+ outputError("Failed to get snapshot", error);
15
+ }
16
+ }