@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.
Files changed (105) hide show
  1. package/README.md +54 -10
  2. package/dist/cli.js +79 -72
  3. package/dist/commands/auth.js +2 -2
  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 +278 -230
  7. package/dist/commands/blueprint/logs.js +133 -37
  8. package/dist/commands/config.js +118 -0
  9. package/dist/commands/devbox/create.js +120 -40
  10. package/dist/commands/devbox/delete.js +17 -33
  11. package/dist/commands/devbox/download.js +29 -43
  12. package/dist/commands/devbox/exec.js +22 -39
  13. package/dist/commands/devbox/execAsync.js +20 -37
  14. package/dist/commands/devbox/get.js +13 -35
  15. package/dist/commands/devbox/getAsync.js +12 -34
  16. package/dist/commands/devbox/list.js +241 -402
  17. package/dist/commands/devbox/logs.js +20 -38
  18. package/dist/commands/devbox/read.js +29 -43
  19. package/dist/commands/devbox/resume.js +13 -35
  20. package/dist/commands/devbox/rsync.js +26 -78
  21. package/dist/commands/devbox/scp.js +25 -79
  22. package/dist/commands/devbox/sendStdin.js +41 -0
  23. package/dist/commands/devbox/shutdown.js +13 -35
  24. package/dist/commands/devbox/ssh.js +46 -78
  25. package/dist/commands/devbox/suspend.js +13 -35
  26. package/dist/commands/devbox/tunnel.js +37 -88
  27. package/dist/commands/devbox/upload.js +28 -36
  28. package/dist/commands/devbox/write.js +29 -44
  29. package/dist/commands/mcp-http.js +6 -5
  30. package/dist/commands/mcp-install.js +12 -10
  31. package/dist/commands/mcp.js +5 -4
  32. package/dist/commands/menu.js +26 -67
  33. package/dist/commands/object/delete.js +12 -34
  34. package/dist/commands/object/download.js +26 -74
  35. package/dist/commands/object/get.js +12 -34
  36. package/dist/commands/object/list.js +15 -93
  37. package/dist/commands/object/upload.js +35 -96
  38. package/dist/commands/snapshot/create.js +23 -39
  39. package/dist/commands/snapshot/delete.js +17 -33
  40. package/dist/commands/snapshot/get.js +16 -0
  41. package/dist/commands/snapshot/list.js +309 -80
  42. package/dist/commands/snapshot/status.js +12 -34
  43. package/dist/components/ActionsPopup.js +64 -39
  44. package/dist/components/Banner.js +7 -1
  45. package/dist/components/Breadcrumb.js +11 -48
  46. package/dist/components/DevboxActionsMenu.js +117 -207
  47. package/dist/components/DevboxCreatePage.js +12 -7
  48. package/dist/components/DevboxDetailPage.js +76 -28
  49. package/dist/components/ErrorBoundary.js +29 -0
  50. package/dist/components/ErrorMessage.js +10 -2
  51. package/dist/components/Header.js +12 -4
  52. package/dist/components/InteractiveSpawn.js +104 -0
  53. package/dist/components/LogsViewer.js +169 -0
  54. package/dist/components/MainMenu.js +37 -33
  55. package/dist/components/MetadataDisplay.js +4 -4
  56. package/dist/components/OperationsMenu.js +1 -1
  57. package/dist/components/ResourceActionsMenu.js +4 -4
  58. package/dist/components/ResourceListView.js +46 -34
  59. package/dist/components/Spinner.js +7 -2
  60. package/dist/components/StatusBadge.js +1 -1
  61. package/dist/components/SuccessMessage.js +12 -2
  62. package/dist/components/Table.js +16 -6
  63. package/dist/components/UpdateNotification.js +56 -0
  64. package/dist/hooks/useCursorPagination.js +125 -85
  65. package/dist/hooks/useExitOnCtrlC.js +15 -0
  66. package/dist/hooks/useViewportHeight.js +47 -0
  67. package/dist/mcp/server-http.js +2 -1
  68. package/dist/mcp/server.js +71 -7
  69. package/dist/router/Router.js +70 -0
  70. package/dist/router/types.js +1 -0
  71. package/dist/screens/BlueprintListScreen.js +7 -0
  72. package/dist/screens/BlueprintLogsScreen.js +74 -0
  73. package/dist/screens/DevboxActionsScreen.js +25 -0
  74. package/dist/screens/DevboxCreateScreen.js +11 -0
  75. package/dist/screens/DevboxDetailScreen.js +60 -0
  76. package/dist/screens/DevboxListScreen.js +23 -0
  77. package/dist/screens/LogsSessionScreen.js +49 -0
  78. package/dist/screens/MenuScreen.js +23 -0
  79. package/dist/screens/SSHSessionScreen.js +55 -0
  80. package/dist/screens/SnapshotListScreen.js +7 -0
  81. package/dist/services/blueprintService.js +101 -0
  82. package/dist/services/devboxService.js +215 -0
  83. package/dist/services/snapshotService.js +81 -0
  84. package/dist/store/blueprintStore.js +89 -0
  85. package/dist/store/devboxStore.js +105 -0
  86. package/dist/store/index.js +7 -0
  87. package/dist/store/navigationStore.js +101 -0
  88. package/dist/store/snapshotStore.js +87 -0
  89. package/dist/utils/client.js +4 -2
  90. package/dist/utils/config.js +22 -111
  91. package/dist/utils/interactiveCommand.js +3 -2
  92. package/dist/utils/logFormatter.js +208 -0
  93. package/dist/utils/memoryMonitor.js +85 -0
  94. package/dist/utils/output.js +153 -61
  95. package/dist/utils/process.js +106 -0
  96. package/dist/utils/processUtils.js +135 -0
  97. package/dist/utils/screen.js +61 -0
  98. package/dist/utils/ssh.js +6 -3
  99. package/dist/utils/sshSession.js +5 -29
  100. package/dist/utils/terminalDetection.js +185 -0
  101. package/dist/utils/terminalSync.js +39 -0
  102. package/dist/utils/theme.js +162 -13
  103. package/dist/utils/versionCheck.js +53 -0
  104. package/dist/version.js +12 -0
  105. package/package.json +19 -17
@@ -1,70 +1,29 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
- import { render, useApp } from "ink";
4
- import { MainMenu } from "../components/MainMenu.js";
5
- import { runSSHSession } from "../utils/sshSession.js";
6
- // Import the UI components directly
7
- import { ListDevboxesUI } from "./devbox/list.js";
8
- import { ListBlueprintsUI } from "./blueprint/list.js";
9
- import { ListSnapshotsUI } from "./snapshot/list.js";
10
- import { Box } from "ink";
11
- const App = ({ onSSHRequest, initialScreen = "menu", focusDevboxId, }) => {
12
- const { exit } = useApp();
13
- const [currentScreen, setCurrentScreen] = React.useState(initialScreen);
14
- const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
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
- // Enter alternate screen buffer once at the start
29
- process.stdout.write("\x1b[?1049h");
30
- let sshSessionConfig = null;
31
- let shouldContinue = true;
32
- let currentInitialScreen = initialScreen;
33
- let currentFocusDevboxId = focusDevboxId;
34
- while (shouldContinue) {
35
- sshSessionConfig = null;
36
- try {
37
- const { waitUntilExit } = render(_jsx(App, { onSSHRequest: (config) => {
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
- // Exit alternate screen buffer once at the end
68
- process.stdout.write("\x1b[?1049l");
69
- process.exit(0);
27
+ exitAlternateScreenBuffer();
28
+ processUtils.exit(0);
70
29
  }
@@ -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
  }