@runloop/rl-cli 0.1.2 → 0.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.
- package/README.md +54 -10
- package/dist/cli.js +79 -72
- package/dist/commands/auth.js +2 -2
- package/dist/commands/blueprint/create.js +31 -83
- package/dist/commands/blueprint/get.js +29 -34
- package/dist/commands/blueprint/list.js +278 -230
- package/dist/commands/blueprint/logs.js +133 -37
- package/dist/commands/config.js +118 -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 +46 -78
- package/dist/commands/devbox/suspend.js +13 -35
- package/dist/commands/devbox/tunnel.js +37 -88
- package/dist/commands/devbox/upload.js +28 -36
- package/dist/commands/devbox/write.js +29 -44
- package/dist/commands/mcp-http.js +6 -5
- package/dist/commands/mcp-install.js +12 -10
- package/dist/commands/mcp.js +5 -4
- package/dist/commands/menu.js +26 -67
- 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 +64 -39
- package/dist/components/Banner.js +7 -1
- package/dist/components/Breadcrumb.js +11 -48
- package/dist/components/DevboxActionsMenu.js +117 -207
- 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 +104 -0
- package/dist/components/LogsViewer.js +169 -0
- package/dist/components/MainMenu.js +37 -33
- 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/components/UpdateNotification.js +56 -0
- package/dist/hooks/useCursorPagination.js +125 -85
- package/dist/hooks/useExitOnCtrlC.js +15 -0
- package/dist/hooks/useViewportHeight.js +47 -0
- package/dist/mcp/server-http.js +2 -1
- package/dist/mcp/server.js +71 -7
- package/dist/router/Router.js +70 -0
- package/dist/router/types.js +1 -0
- package/dist/screens/BlueprintListScreen.js +7 -0
- package/dist/screens/BlueprintLogsScreen.js +74 -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 +101 -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/client.js +4 -2
- package/dist/utils/config.js +22 -111
- package/dist/utils/interactiveCommand.js +3 -2
- package/dist/utils/logFormatter.js +208 -0
- package/dist/utils/memoryMonitor.js +85 -0
- package/dist/utils/output.js +153 -61
- package/dist/utils/process.js +106 -0
- package/dist/utils/processUtils.js +135 -0
- package/dist/utils/screen.js +61 -0
- package/dist/utils/ssh.js +6 -3
- package/dist/utils/sshSession.js +5 -29
- package/dist/utils/terminalDetection.js +185 -0
- package/dist/utils/terminalSync.js +39 -0
- package/dist/utils/theme.js +162 -13
- package/dist/utils/versionCheck.js +53 -0
- package/dist/version.js +12 -0
- package/package.json +19 -17
package/dist/commands/menu.js
CHANGED
|
@@ -1,70 +1,29 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const handleMenuSelect = (key) => {
|
|
16
|
-
setCurrentScreen(key);
|
|
17
|
-
};
|
|
18
|
-
const handleBack = () => {
|
|
19
|
-
setCurrentScreen("menu");
|
|
20
|
-
};
|
|
21
|
-
const handleExit = () => {
|
|
22
|
-
exit();
|
|
23
|
-
};
|
|
24
|
-
// Wrap everything in a full-height container
|
|
25
|
-
return (_jsxs(Box, { flexDirection: "column", minHeight: process.stdout.rows || 24, children: [currentScreen === "menu" && _jsx(MainMenu, { onSelect: handleMenuSelect }), currentScreen === "devboxes" && (_jsx(ListDevboxesUI, { onBack: handleBack, onExit: handleExit, onSSHRequest: onSSHRequest, focusDevboxId: focusDevboxId })), currentScreen === "blueprints" && (_jsx(ListBlueprintsUI, { onBack: handleBack, onExit: handleExit })), currentScreen === "snapshots" && (_jsx(ListSnapshotsUI, { onBack: handleBack, onExit: handleExit }))] }));
|
|
26
|
-
};
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render } from "ink";
|
|
3
|
+
import { enterAlternateScreenBuffer, exitAlternateScreenBuffer, } from "../utils/screen.js";
|
|
4
|
+
import { processUtils } from "../utils/processUtils.js";
|
|
5
|
+
import { Router } from "../router/Router.js";
|
|
6
|
+
import { NavigationProvider } from "../store/navigationStore.js";
|
|
7
|
+
function AppInner() {
|
|
8
|
+
// NavigationProvider already handles initialScreen and initialParams
|
|
9
|
+
// No need for useEffect here - provider sets state on mount
|
|
10
|
+
return _jsx(Router, {});
|
|
11
|
+
}
|
|
12
|
+
function App({ initialScreen = "menu", focusDevboxId, }) {
|
|
13
|
+
return (_jsx(NavigationProvider, { initialScreen: initialScreen, initialParams: focusDevboxId ? { focusDevboxId } : {}, children: _jsx(AppInner, {}) }));
|
|
14
|
+
}
|
|
27
15
|
export async function runMainMenu(initialScreen = "menu", focusDevboxId) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
sshSessionConfig = config;
|
|
39
|
-
}, initialScreen: currentInitialScreen, focusDevboxId: currentFocusDevboxId }));
|
|
40
|
-
await waitUntilExit();
|
|
41
|
-
shouldContinue = false;
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
console.error("Error in menu:", error);
|
|
45
|
-
shouldContinue = false;
|
|
46
|
-
}
|
|
47
|
-
// If SSH was requested, handle it now after Ink has exited
|
|
48
|
-
if (sshSessionConfig) {
|
|
49
|
-
// Exit alternate screen buffer for SSH
|
|
50
|
-
process.stdout.write("\x1b[?1049l");
|
|
51
|
-
const result = await runSSHSession(sshSessionConfig);
|
|
52
|
-
if (result.shouldRestart) {
|
|
53
|
-
console.clear();
|
|
54
|
-
console.log(`\nSSH session ended. Returning to menu...\n`);
|
|
55
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
56
|
-
// Re-enter alternate screen buffer and return to devboxes list
|
|
57
|
-
process.stdout.write("\x1b[?1049h");
|
|
58
|
-
currentInitialScreen = "devboxes";
|
|
59
|
-
currentFocusDevboxId = result.returnToDevboxId;
|
|
60
|
-
shouldContinue = true;
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
shouldContinue = false;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
16
|
+
enterAlternateScreenBuffer();
|
|
17
|
+
try {
|
|
18
|
+
const { waitUntilExit } = render(_jsx(App, { initialScreen: initialScreen, focusDevboxId: focusDevboxId }, `app-${initialScreen}-${focusDevboxId}`), {
|
|
19
|
+
patchConsole: false,
|
|
20
|
+
exitOnCtrlC: false,
|
|
21
|
+
});
|
|
22
|
+
await waitUntilExit();
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error("Error in menu:", error);
|
|
66
26
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
process.exit(0);
|
|
27
|
+
exitAlternateScreenBuffer();
|
|
28
|
+
processUtils.exit(0);
|
|
70
29
|
}
|
|
@@ -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
|
}
|