@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,40 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Get devbox logs command
|
|
3
|
+
*/
|
|
4
4
|
import { getClient } from "../../utils/client.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
setError(err);
|
|
23
|
-
}
|
|
24
|
-
finally {
|
|
25
|
-
setLoading(false);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
getLogs();
|
|
29
|
-
}, [devboxId]);
|
|
30
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Fetching devbox logs..." }), result && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.primary, children: "Devbox Logs:" }), result.logs && result.logs.length > 0 ? (result.logs.map((log, index) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: colors.textDim, children: log.timestampMs
|
|
31
|
-
? new Date(log.timestampMs).toISOString()
|
|
32
|
-
: "" }), log.source && (_jsxs(Text, { color: colors.textDim, children: [" [", log.source, "]"] })), log.cmd && (_jsxs(Text, { color: colors.primary, children: [" ", "->", " ", log.cmd] })), log.message && _jsxs(Text, { children: [" ", log.message] }), log.exitCode !== null && (_jsxs(Text, { color: colors.warning, children: [" ", "->", " exit_code=", log.exitCode] }))] }, index)))) : (_jsx(Text, { color: colors.textDim, children: "No logs available" }))] })), error && (_jsx(ErrorMessage, { message: "Failed to get devbox logs", error: error }))] }));
|
|
33
|
-
};
|
|
34
|
-
export async function getLogs(devboxId, options) {
|
|
35
|
-
const executor = createExecutor({ output: options.output });
|
|
36
|
-
await executor.executeAction(async () => {
|
|
37
|
-
const client = executor.getClient();
|
|
38
|
-
return client.devboxes.logs.list(devboxId);
|
|
39
|
-
}, () => _jsx(LogsUI, { devboxId: devboxId }));
|
|
5
|
+
import { output, outputError } from "../../utils/output.js";
|
|
6
|
+
import { formatLogsForCLI } from "../../utils/logFormatter.js";
|
|
7
|
+
export async function getLogs(devboxId, options = {}) {
|
|
8
|
+
try {
|
|
9
|
+
const client = getClient();
|
|
10
|
+
const logs = await client.devboxes.logs.list(devboxId);
|
|
11
|
+
// Pretty print for text output, JSON for others
|
|
12
|
+
if (!options.output || options.output === "text") {
|
|
13
|
+
formatLogsForCLI(logs);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
output(logs, { format: options.output, defaultFormat: "json" });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
outputError("Failed to get devbox logs", error);
|
|
21
|
+
}
|
|
40
22
|
}
|
|
@@ -1,49 +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
|
+
* Read file from devbox command (using API)
|
|
3
|
+
*/
|
|
9
4
|
import { writeFile } from "fs/promises";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
await writeFile(outputPath, contents);
|
|
22
|
-
setResult({ remotePath, outputPath, size: contents.length });
|
|
23
|
-
}
|
|
24
|
-
catch (err) {
|
|
25
|
-
setError(err);
|
|
26
|
-
}
|
|
27
|
-
finally {
|
|
28
|
-
setLoading(false);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
readFile();
|
|
32
|
-
}, [devboxId, remotePath, outputPath]);
|
|
33
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Reading file from devbox..." }), result && (_jsx(SuccessMessage, { message: "File read successfully", details: `Remote: ${result.remotePath}\nLocal: ${result.outputPath}\nSize: ${result.size} bytes` })), error && _jsx(ErrorMessage, { message: "Failed to read file", error: error })] }));
|
|
34
|
-
};
|
|
35
|
-
export async function readFile(devboxId, options) {
|
|
36
|
-
const executor = createExecutor({ output: options.output });
|
|
37
|
-
await executor.executeAction(async () => {
|
|
38
|
-
const client = executor.getClient();
|
|
5
|
+
import { getClient } from "../../utils/client.js";
|
|
6
|
+
import { output, outputError } from "../../utils/output.js";
|
|
7
|
+
export async function readFile(devboxId, options = {}) {
|
|
8
|
+
if (!options.remote) {
|
|
9
|
+
outputError("--remote is required");
|
|
10
|
+
}
|
|
11
|
+
if (!options.outputPath) {
|
|
12
|
+
outputError("--output-path is required");
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const client = getClient();
|
|
39
16
|
const contents = await client.devboxes.readFileContents(devboxId, {
|
|
40
17
|
file_path: options.remote,
|
|
41
18
|
});
|
|
42
19
|
await writeFile(options.outputPath, contents);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
20
|
+
// Default: just output the local path for easy scripting
|
|
21
|
+
if (!options.output || options.output === "text") {
|
|
22
|
+
console.log(options.outputPath);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
output({
|
|
26
|
+
remote: options.remote,
|
|
27
|
+
local: options.outputPath,
|
|
28
|
+
size: contents.length,
|
|
29
|
+
}, { format: options.output, defaultFormat: "json" });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
outputError("Failed to read file", error);
|
|
34
|
+
}
|
|
49
35
|
}
|
|
@@ -1,37 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Resume devbox command
|
|
3
|
+
*/
|
|
3
4
|
import { getClient } from "../../utils/client.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const resumeDevbox = async () => {
|
|
15
|
-
try {
|
|
16
|
-
const client = getClient();
|
|
17
|
-
const devbox = await client.devboxes.resume(devboxId);
|
|
18
|
-
setResult(devbox);
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
setError(err);
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
setLoading(false);
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
resumeDevbox();
|
|
28
|
-
}, [devboxId]);
|
|
29
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Resuming devbox..." }), result && (_jsx(SuccessMessage, { message: "Devbox resumed", details: `ID: ${result.id}\nStatus: ${result.status}` })), error && (_jsx(ErrorMessage, { message: "Failed to resume devbox", error: error }))] }));
|
|
30
|
-
};
|
|
31
|
-
export async function resumeDevbox(devboxId, options) {
|
|
32
|
-
const executor = createExecutor(options);
|
|
33
|
-
await executor.executeAction(async () => {
|
|
34
|
-
const client = executor.getClient();
|
|
35
|
-
return client.devboxes.resume(devboxId);
|
|
36
|
-
}, () => _jsx(ResumeDevboxUI, { devboxId: devboxId }));
|
|
5
|
+
import { output, outputError } from "../../utils/output.js";
|
|
6
|
+
export async function resumeDevbox(devboxId, options = {}) {
|
|
7
|
+
try {
|
|
8
|
+
const client = getClient();
|
|
9
|
+
const devbox = await client.devboxes.resume(devboxId);
|
|
10
|
+
output(devbox, { format: options.output, defaultFormat: "text" });
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
outputError("Failed to resume devbox", error);
|
|
14
|
+
}
|
|
37
15
|
}
|
|
@@ -1,91 +1,27 @@
|
|
|
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";
|
|
9
|
-
import { getSSHKey, getProxyCommand, checkSSHTools } from "../../utils/ssh.js";
|
|
1
|
+
/**
|
|
2
|
+
* Rsync files to/from devbox command
|
|
3
|
+
*/
|
|
10
4
|
import { exec } from "child_process";
|
|
11
5
|
import { promisify } from "util";
|
|
6
|
+
import { getClient } from "../../utils/client.js";
|
|
7
|
+
import { output, outputError } from "../../utils/output.js";
|
|
8
|
+
import { getSSHKey, getProxyCommand, checkSSHTools } from "../../utils/ssh.js";
|
|
12
9
|
const execAsync = promisify(exec);
|
|
13
|
-
const RsyncUI = ({ devboxId, src, dst, rsyncOptions }) => {
|
|
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 performRsync = async () => {
|
|
19
|
-
try {
|
|
20
|
-
// Check if SSH tools are available
|
|
21
|
-
const sshToolsAvailable = await checkSSHTools();
|
|
22
|
-
if (!sshToolsAvailable) {
|
|
23
|
-
throw new Error("SSH tools (ssh, rsync, openssl) are not available on this system");
|
|
24
|
-
}
|
|
25
|
-
const client = getClient();
|
|
26
|
-
// Get devbox details to determine user
|
|
27
|
-
const devbox = await client.devboxes.retrieve(devboxId);
|
|
28
|
-
const user = devbox.launch_parameters?.user_parameters?.username || "user";
|
|
29
|
-
// Get SSH key
|
|
30
|
-
const sshInfo = await getSSHKey(devboxId);
|
|
31
|
-
if (!sshInfo) {
|
|
32
|
-
throw new Error("Failed to create SSH key");
|
|
33
|
-
}
|
|
34
|
-
const proxyCommand = getProxyCommand();
|
|
35
|
-
const sshOptions = `-i ${sshInfo.keyfilePath} -o ProxyCommand='${proxyCommand}' -o StrictHostKeyChecking=no`;
|
|
36
|
-
const rsyncCommand = [
|
|
37
|
-
"rsync",
|
|
38
|
-
"-vrz", // v: verbose, r: recursive, z: compress
|
|
39
|
-
"-e",
|
|
40
|
-
`ssh ${sshOptions}`,
|
|
41
|
-
];
|
|
42
|
-
if (rsyncOptions) {
|
|
43
|
-
rsyncCommand.push(...rsyncOptions.split(" "));
|
|
44
|
-
}
|
|
45
|
-
// Handle remote paths (starting with :)
|
|
46
|
-
if (src.startsWith(":")) {
|
|
47
|
-
rsyncCommand.push(`${user}@${sshInfo.url}:${src.slice(1)}`);
|
|
48
|
-
rsyncCommand.push(dst);
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
rsyncCommand.push(src);
|
|
52
|
-
if (dst.startsWith(":")) {
|
|
53
|
-
rsyncCommand.push(`${user}@${sshInfo.url}:${dst.slice(1)}`);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
rsyncCommand.push(dst);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const { stdout, stderr } = await execAsync(rsyncCommand.join(" "));
|
|
60
|
-
setResult({ src, dst, stdout, stderr });
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
setError(err);
|
|
64
|
-
}
|
|
65
|
-
finally {
|
|
66
|
-
setLoading(false);
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
performRsync();
|
|
70
|
-
}, [devboxId, src, dst, rsyncOptions]);
|
|
71
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Syncing files with rsync..." }), result && (_jsx(SuccessMessage, { message: "Rsync operation completed", details: `Source: ${result.src}\nDestination: ${result.dst}` })), error && _jsx(ErrorMessage, { message: "Rsync operation failed", error: error })] }));
|
|
72
|
-
};
|
|
73
10
|
export async function rsyncFiles(devboxId, options) {
|
|
74
|
-
|
|
75
|
-
await executor.executeAction(async () => {
|
|
11
|
+
try {
|
|
76
12
|
// Check if SSH tools are available
|
|
77
13
|
const sshToolsAvailable = await checkSSHTools();
|
|
78
14
|
if (!sshToolsAvailable) {
|
|
79
|
-
|
|
15
|
+
outputError("SSH tools (ssh, rsync, openssl) are not available on this system");
|
|
80
16
|
}
|
|
81
|
-
const client =
|
|
17
|
+
const client = getClient();
|
|
82
18
|
// Get devbox details to determine user
|
|
83
19
|
const devbox = await client.devboxes.retrieve(devboxId);
|
|
84
20
|
const user = devbox.launch_parameters?.user_parameters?.username || "user";
|
|
85
21
|
// Get SSH key
|
|
86
22
|
const sshInfo = await getSSHKey(devboxId);
|
|
87
23
|
if (!sshInfo) {
|
|
88
|
-
|
|
24
|
+
outputError("Failed to create SSH key");
|
|
89
25
|
}
|
|
90
26
|
const proxyCommand = getProxyCommand();
|
|
91
27
|
const sshOptions = `-i ${sshInfo.keyfilePath} -o ProxyCommand='${proxyCommand}' -o StrictHostKeyChecking=no`;
|
|
@@ -93,7 +29,7 @@ export async function rsyncFiles(devboxId, options) {
|
|
|
93
29
|
"rsync",
|
|
94
30
|
"-vrz", // v: verbose, r: recursive, z: compress
|
|
95
31
|
"-e",
|
|
96
|
-
`ssh ${sshOptions}`,
|
|
32
|
+
`"ssh ${sshOptions}"`,
|
|
97
33
|
];
|
|
98
34
|
if (options.rsyncOptions) {
|
|
99
35
|
rsyncCommand.push(...options.rsyncOptions.split(" "));
|
|
@@ -112,7 +48,19 @@ export async function rsyncFiles(devboxId, options) {
|
|
|
112
48
|
rsyncCommand.push(options.dst);
|
|
113
49
|
}
|
|
114
50
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
51
|
+
await execAsync(rsyncCommand.join(" "));
|
|
52
|
+
// Default: just output the destination for easy scripting
|
|
53
|
+
if (!options.output || options.output === "text") {
|
|
54
|
+
console.log(options.dst);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
output({
|
|
58
|
+
source: options.src,
|
|
59
|
+
destination: options.dst,
|
|
60
|
+
}, { format: options.output, defaultFormat: "json" });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
outputError("Rsync operation failed", error);
|
|
65
|
+
}
|
|
118
66
|
}
|
|
@@ -1,93 +1,27 @@
|
|
|
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";
|
|
9
|
-
import { getSSHKey, getProxyCommand, checkSSHTools } from "../../utils/ssh.js";
|
|
1
|
+
/**
|
|
2
|
+
* SCP files to/from devbox command
|
|
3
|
+
*/
|
|
10
4
|
import { exec } from "child_process";
|
|
11
5
|
import { promisify } from "util";
|
|
6
|
+
import { getClient } from "../../utils/client.js";
|
|
7
|
+
import { output, outputError } from "../../utils/output.js";
|
|
8
|
+
import { getSSHKey, getProxyCommand, checkSSHTools } from "../../utils/ssh.js";
|
|
12
9
|
const execAsync = promisify(exec);
|
|
13
|
-
const SCPUI = ({ devboxId, src, dst, scpOptions }) => {
|
|
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 performSCP = async () => {
|
|
19
|
-
try {
|
|
20
|
-
// Check if SSH tools are available
|
|
21
|
-
const sshToolsAvailable = await checkSSHTools();
|
|
22
|
-
if (!sshToolsAvailable) {
|
|
23
|
-
throw new Error("SSH tools (ssh, scp, openssl) are not available on this system");
|
|
24
|
-
}
|
|
25
|
-
const client = getClient();
|
|
26
|
-
// Get devbox details to determine user
|
|
27
|
-
const devbox = await client.devboxes.retrieve(devboxId);
|
|
28
|
-
const user = devbox.launch_parameters?.user_parameters?.username || "user";
|
|
29
|
-
// Get SSH key
|
|
30
|
-
const sshInfo = await getSSHKey(devboxId);
|
|
31
|
-
if (!sshInfo) {
|
|
32
|
-
throw new Error("Failed to create SSH key");
|
|
33
|
-
}
|
|
34
|
-
const proxyCommand = getProxyCommand();
|
|
35
|
-
const scpCommand = [
|
|
36
|
-
"scp",
|
|
37
|
-
"-i",
|
|
38
|
-
sshInfo.keyfilePath,
|
|
39
|
-
"-o",
|
|
40
|
-
`ProxyCommand=${proxyCommand}`,
|
|
41
|
-
"-o",
|
|
42
|
-
"StrictHostKeyChecking=no",
|
|
43
|
-
];
|
|
44
|
-
if (scpOptions) {
|
|
45
|
-
scpCommand.push(...scpOptions.split(" "));
|
|
46
|
-
}
|
|
47
|
-
// Handle remote paths (starting with :)
|
|
48
|
-
if (src.startsWith(":")) {
|
|
49
|
-
scpCommand.push(`${user}@${sshInfo.url}:${src.slice(1)}`);
|
|
50
|
-
scpCommand.push(dst);
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
scpCommand.push(src);
|
|
54
|
-
if (dst.startsWith(":")) {
|
|
55
|
-
scpCommand.push(`${user}@${sshInfo.url}:${dst.slice(1)}`);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
scpCommand.push(dst);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const { stdout, stderr } = await execAsync(scpCommand.join(" "));
|
|
62
|
-
setResult({ src, dst, stdout, stderr });
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
setError(err);
|
|
66
|
-
}
|
|
67
|
-
finally {
|
|
68
|
-
setLoading(false);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
performSCP();
|
|
72
|
-
}, [devboxId, src, dst, scpOptions]);
|
|
73
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Copying files with SCP..." }), result && (_jsx(SuccessMessage, { message: "SCP operation completed", details: `Source: ${result.src}\nDestination: ${result.dst}` })), error && _jsx(ErrorMessage, { message: "SCP operation failed", error: error })] }));
|
|
74
|
-
};
|
|
75
10
|
export async function scpFiles(devboxId, options) {
|
|
76
|
-
|
|
77
|
-
await executor.executeAction(async () => {
|
|
11
|
+
try {
|
|
78
12
|
// Check if SSH tools are available
|
|
79
13
|
const sshToolsAvailable = await checkSSHTools();
|
|
80
14
|
if (!sshToolsAvailable) {
|
|
81
|
-
|
|
15
|
+
outputError("SSH tools (ssh, scp, openssl) are not available on this system");
|
|
82
16
|
}
|
|
83
|
-
const client =
|
|
17
|
+
const client = getClient();
|
|
84
18
|
// Get devbox details to determine user
|
|
85
19
|
const devbox = await client.devboxes.retrieve(devboxId);
|
|
86
20
|
const user = devbox.launch_parameters?.user_parameters?.username || "user";
|
|
87
21
|
// Get SSH key
|
|
88
22
|
const sshInfo = await getSSHKey(devboxId);
|
|
89
23
|
if (!sshInfo) {
|
|
90
|
-
|
|
24
|
+
outputError("Failed to create SSH key");
|
|
91
25
|
}
|
|
92
26
|
const proxyCommand = getProxyCommand();
|
|
93
27
|
const scpCommand = [
|
|
@@ -116,7 +50,19 @@ export async function scpFiles(devboxId, options) {
|
|
|
116
50
|
scpCommand.push(options.dst);
|
|
117
51
|
}
|
|
118
52
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
53
|
+
await execAsync(scpCommand.join(" "));
|
|
54
|
+
// Default: just output the destination for easy scripting
|
|
55
|
+
if (!options.output || options.output === "text") {
|
|
56
|
+
console.log(options.dst);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
output({
|
|
60
|
+
source: options.src,
|
|
61
|
+
destination: options.dst,
|
|
62
|
+
}, { format: options.output, defaultFormat: "json" });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
outputError("SCP operation failed", error);
|
|
67
|
+
}
|
|
122
68
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send stdin to a running async execution
|
|
3
|
+
*/
|
|
4
|
+
import { getClient } from "../../utils/client.js";
|
|
5
|
+
import { output, outputError } from "../../utils/output.js";
|
|
6
|
+
export async function sendStdin(devboxId, executionId, options = {}) {
|
|
7
|
+
try {
|
|
8
|
+
// Validate that either text or signal is provided, but not both
|
|
9
|
+
if (!options.text && !options.signal) {
|
|
10
|
+
outputError("Either --text or --signal must be specified");
|
|
11
|
+
}
|
|
12
|
+
if (options.text && options.signal) {
|
|
13
|
+
outputError("Only one of --text or --signal can be specified");
|
|
14
|
+
}
|
|
15
|
+
const client = getClient();
|
|
16
|
+
// Build the request body
|
|
17
|
+
const requestBody = {};
|
|
18
|
+
if (options.text) {
|
|
19
|
+
requestBody.text = options.text;
|
|
20
|
+
}
|
|
21
|
+
if (options.signal) {
|
|
22
|
+
requestBody.signal = options.signal;
|
|
23
|
+
}
|
|
24
|
+
const result = await client.devboxes.executions.sendStdIn(devboxId, executionId, requestBody);
|
|
25
|
+
// Default: just confirm success for text output
|
|
26
|
+
if (!options.output || options.output === "text") {
|
|
27
|
+
if (options.text) {
|
|
28
|
+
console.log(`Sent text to execution ${executionId}`);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.log(`Sent ${options.signal} signal to execution ${executionId}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
output(result, { format: options.output, defaultFormat: "json" });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
outputError("Failed to send stdin", error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,37 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Shutdown devbox command
|
|
3
|
+
*/
|
|
3
4
|
import { getClient } from "../../utils/client.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const shutdownDevbox = async () => {
|
|
15
|
-
try {
|
|
16
|
-
const client = getClient();
|
|
17
|
-
const devbox = await client.devboxes.shutdown(devboxId);
|
|
18
|
-
setResult(devbox);
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
setError(err);
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
setLoading(false);
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
shutdownDevbox();
|
|
28
|
-
}, [devboxId]);
|
|
29
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Shutting down devbox..." }), result && (_jsx(SuccessMessage, { message: "Devbox shutdown", details: `ID: ${result.id}\nStatus: ${result.status}` })), error && (_jsx(ErrorMessage, { message: "Failed to shutdown devbox", error: error }))] }));
|
|
30
|
-
};
|
|
31
|
-
export async function shutdownDevbox(devboxId, options) {
|
|
32
|
-
const executor = createExecutor(options);
|
|
33
|
-
await executor.executeAction(async () => {
|
|
34
|
-
const client = executor.getClient();
|
|
35
|
-
return client.devboxes.shutdown(devboxId);
|
|
36
|
-
}, () => _jsx(ShutdownDevboxUI, { devboxId: devboxId }));
|
|
5
|
+
import { output, outputError } from "../../utils/output.js";
|
|
6
|
+
export async function shutdownDevbox(devboxId, options = {}) {
|
|
7
|
+
try {
|
|
8
|
+
const client = getClient();
|
|
9
|
+
const devbox = await client.devboxes.shutdown(devboxId);
|
|
10
|
+
output(devbox, { format: options.output, defaultFormat: "json" });
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
outputError("Failed to shutdown devbox", error);
|
|
14
|
+
}
|
|
37
15
|
}
|