@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.
- package/README.md +54 -0
- package/dist/cli.js +73 -60
- package/dist/commands/auth.js +0 -1
- package/dist/commands/blueprint/create.js +31 -83
- package/dist/commands/blueprint/get.js +29 -34
- package/dist/commands/blueprint/list.js +215 -213
- package/dist/commands/blueprint/logs.js +133 -37
- package/dist/commands/blueprint/preview.js +42 -38
- package/dist/commands/config.js +117 -0
- package/dist/commands/devbox/create.js +120 -40
- package/dist/commands/devbox/delete.js +17 -33
- package/dist/commands/devbox/download.js +29 -43
- package/dist/commands/devbox/exec.js +22 -39
- package/dist/commands/devbox/execAsync.js +20 -37
- package/dist/commands/devbox/get.js +13 -35
- package/dist/commands/devbox/getAsync.js +12 -34
- package/dist/commands/devbox/list.js +241 -402
- package/dist/commands/devbox/logs.js +20 -38
- package/dist/commands/devbox/read.js +29 -43
- package/dist/commands/devbox/resume.js +13 -35
- package/dist/commands/devbox/rsync.js +26 -78
- package/dist/commands/devbox/scp.js +25 -79
- package/dist/commands/devbox/sendStdin.js +41 -0
- package/dist/commands/devbox/shutdown.js +13 -35
- package/dist/commands/devbox/ssh.js +45 -78
- package/dist/commands/devbox/suspend.js +13 -35
- package/dist/commands/devbox/tunnel.js +36 -88
- package/dist/commands/devbox/upload.js +28 -36
- package/dist/commands/devbox/write.js +29 -44
- package/dist/commands/mcp-install.js +4 -3
- package/dist/commands/menu.js +24 -66
- package/dist/commands/object/delete.js +12 -34
- package/dist/commands/object/download.js +26 -74
- package/dist/commands/object/get.js +12 -34
- package/dist/commands/object/list.js +15 -93
- package/dist/commands/object/upload.js +35 -96
- package/dist/commands/snapshot/create.js +23 -39
- package/dist/commands/snapshot/delete.js +17 -33
- package/dist/commands/snapshot/get.js +16 -0
- package/dist/commands/snapshot/list.js +309 -80
- package/dist/commands/snapshot/status.js +12 -34
- package/dist/components/ActionsPopup.js +63 -39
- package/dist/components/Breadcrumb.js +10 -52
- package/dist/components/DevboxActionsMenu.js +182 -110
- package/dist/components/DevboxCreatePage.js +12 -7
- package/dist/components/DevboxDetailPage.js +76 -28
- package/dist/components/ErrorBoundary.js +29 -0
- package/dist/components/ErrorMessage.js +10 -2
- package/dist/components/Header.js +12 -4
- package/dist/components/InteractiveSpawn.js +94 -0
- package/dist/components/MainMenu.js +36 -32
- package/dist/components/MetadataDisplay.js +4 -4
- package/dist/components/OperationsMenu.js +1 -1
- package/dist/components/ResourceActionsMenu.js +4 -4
- package/dist/components/ResourceListView.js +46 -34
- package/dist/components/Spinner.js +7 -2
- package/dist/components/StatusBadge.js +1 -1
- package/dist/components/SuccessMessage.js +12 -2
- package/dist/components/Table.js +16 -6
- package/dist/hooks/useCursorPagination.js +125 -85
- package/dist/hooks/useExitOnCtrlC.js +14 -0
- package/dist/hooks/useViewportHeight.js +47 -0
- package/dist/mcp/server.js +65 -6
- package/dist/router/Router.js +68 -0
- package/dist/router/types.js +1 -0
- package/dist/screens/BlueprintListScreen.js +7 -0
- package/dist/screens/DevboxActionsScreen.js +25 -0
- package/dist/screens/DevboxCreateScreen.js +11 -0
- package/dist/screens/DevboxDetailScreen.js +60 -0
- package/dist/screens/DevboxListScreen.js +23 -0
- package/dist/screens/LogsSessionScreen.js +49 -0
- package/dist/screens/MenuScreen.js +23 -0
- package/dist/screens/SSHSessionScreen.js +55 -0
- package/dist/screens/SnapshotListScreen.js +7 -0
- package/dist/services/blueprintService.js +105 -0
- package/dist/services/devboxService.js +215 -0
- package/dist/services/snapshotService.js +81 -0
- package/dist/store/blueprintStore.js +89 -0
- package/dist/store/devboxStore.js +105 -0
- package/dist/store/index.js +7 -0
- package/dist/store/navigationStore.js +101 -0
- package/dist/store/snapshotStore.js +87 -0
- package/dist/utils/CommandExecutor.js +53 -24
- package/dist/utils/client.js +0 -2
- package/dist/utils/config.js +20 -90
- package/dist/utils/interactiveCommand.js +3 -2
- package/dist/utils/logFormatter.js +162 -0
- package/dist/utils/memoryMonitor.js +85 -0
- package/dist/utils/output.js +150 -59
- package/dist/utils/screen.js +23 -0
- package/dist/utils/ssh.js +3 -1
- package/dist/utils/sshSession.js +5 -29
- package/dist/utils/terminalDetection.js +97 -0
- package/dist/utils/terminalSync.js +39 -0
- package/dist/utils/theme.js +147 -13
- package/package.json +16 -13
|
@@ -1,37 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Delete object command
|
|
3
|
+
*/
|
|
3
4
|
import { getClient } from "../../utils/client.js";
|
|
4
|
-
import {
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
}
|
|
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
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Download object command
|
|
3
|
+
*/
|
|
4
|
+
import { writeFile } from "fs/promises";
|
|
3
5
|
import { getClient } from "../../utils/client.js";
|
|
4
|
-
import {
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
17
|
+
outputError(`Download failed: HTTP ${response.status}`);
|
|
68
18
|
}
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Get object details command
|
|
3
|
+
*/
|
|
3
4
|
import { getClient } from "../../utils/client.js";
|
|
4
|
-
import {
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
}
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* List objects command
|
|
3
|
+
*/
|
|
4
4
|
import { getClient } from "../../utils/client.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
25
|
+
const result = options.public
|
|
108
26
|
? await client.objects.listPublic(params)
|
|
109
27
|
: await client.objects.list(params);
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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 {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Delete snapshot command
|
|
3
|
+
*/
|
|
3
4
|
import { getClient } from "../../utils/client.js";
|
|
4
|
-
import {
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
const client = executor.getClient();
|
|
7
|
+
try {
|
|
8
|
+
const client = getClient();
|
|
35
9
|
await client.devboxes.diskSnapshots.delete(id);
|
|
36
|
-
|
|
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
|
+
}
|