@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
|
@@ -1,82 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* SSH into devbox command
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from "child_process";
|
|
4
5
|
import { getClient } from "../../utils/client.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { colors } from "../../utils/theme.js";
|
|
11
|
-
import { getSSHKey, waitForReady, generateSSHConfig, checkSSHTools, } from "../../utils/ssh.js";
|
|
12
|
-
const SSHDevboxUI = ({ devboxId, options }) => {
|
|
13
|
-
const [loading, setLoading] = React.useState(true);
|
|
14
|
-
const [result, setResult] = React.useState(null);
|
|
15
|
-
const [error, setError] = React.useState(null);
|
|
16
|
-
React.useEffect(() => {
|
|
17
|
-
const connectSSH = async () => {
|
|
18
|
-
try {
|
|
19
|
-
// Check if SSH tools are available
|
|
20
|
-
const sshToolsAvailable = await checkSSHTools();
|
|
21
|
-
if (!sshToolsAvailable) {
|
|
22
|
-
throw new Error("SSH tools (ssh, scp, rsync, openssl) are not available on this system");
|
|
23
|
-
}
|
|
24
|
-
const client = getClient();
|
|
25
|
-
// Wait for devbox to be ready unless --no-wait is specified
|
|
26
|
-
if (!options.noWait) {
|
|
27
|
-
console.log(`Waiting for devbox ${devboxId} to be ready...`);
|
|
28
|
-
const isReady = await waitForReady(devboxId, options.timeout || 180, options.pollInterval || 3);
|
|
29
|
-
if (!isReady) {
|
|
30
|
-
throw new Error(`Devbox ${devboxId} is not ready. Please try again later.`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
// Get devbox details to determine user
|
|
34
|
-
const devbox = await client.devboxes.retrieve(devboxId);
|
|
35
|
-
const user = devbox.launch_parameters?.user_parameters?.username || "user";
|
|
36
|
-
// Get SSH key
|
|
37
|
-
const sshInfo = await getSSHKey(devboxId);
|
|
38
|
-
if (!sshInfo) {
|
|
39
|
-
throw new Error("Failed to create SSH key");
|
|
40
|
-
}
|
|
41
|
-
if (options.configOnly) {
|
|
42
|
-
const config = generateSSHConfig(devboxId, user, sshInfo.keyfilePath, sshInfo.url);
|
|
43
|
-
setResult({ config });
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
setResult({
|
|
47
|
-
devboxId,
|
|
48
|
-
user,
|
|
49
|
-
keyfilePath: sshInfo.keyfilePath,
|
|
50
|
-
url: sshInfo.url,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
catch (err) {
|
|
55
|
-
setError(err);
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
setLoading(false);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
connectSSH();
|
|
62
|
-
}, [devboxId, options]);
|
|
63
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Setting up SSH connection..." }), result && result.config && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.primary, children: "SSH Config:" }), _jsx(Text, { children: result.config })] })), result && !result.config && (_jsx(SuccessMessage, { message: "SSH connection ready", details: `Devbox: ${result.devboxId}\nUser: ${result.user}\nKey: ${result.keyfilePath}` })), error && (_jsx(ErrorMessage, { message: "Failed to setup SSH connection", error: error }))] }));
|
|
64
|
-
};
|
|
65
|
-
export async function sshDevbox(devboxId, options) {
|
|
66
|
-
const executor = createExecutor(options);
|
|
67
|
-
await executor.executeAction(async () => {
|
|
6
|
+
import { output, outputError } from "../../utils/output.js";
|
|
7
|
+
import { processUtils } from "../../utils/processUtils.js";
|
|
8
|
+
import { getSSHKey, waitForReady, generateSSHConfig, checkSSHTools, getProxyCommand, } from "../../utils/ssh.js";
|
|
9
|
+
export async function sshDevbox(devboxId, options = {}) {
|
|
10
|
+
try {
|
|
68
11
|
// Check if SSH tools are available
|
|
69
12
|
const sshToolsAvailable = await checkSSHTools();
|
|
70
13
|
if (!sshToolsAvailable) {
|
|
71
|
-
|
|
14
|
+
outputError("SSH tools (ssh, scp, rsync, openssl) are not available on this system");
|
|
72
15
|
}
|
|
73
|
-
const client =
|
|
16
|
+
const client = getClient();
|
|
74
17
|
// Wait for devbox to be ready unless --no-wait is specified
|
|
75
18
|
if (!options.noWait) {
|
|
76
|
-
console.
|
|
19
|
+
console.error(`Waiting for devbox ${devboxId} to be ready...`);
|
|
77
20
|
const isReady = await waitForReady(devboxId, options.timeout || 180, options.pollInterval || 3);
|
|
78
21
|
if (!isReady) {
|
|
79
|
-
|
|
22
|
+
outputError(`Devbox ${devboxId} is not ready. Please try again later.`);
|
|
80
23
|
}
|
|
81
24
|
}
|
|
82
25
|
// Get devbox details to determine user
|
|
@@ -85,20 +28,45 @@ export async function sshDevbox(devboxId, options) {
|
|
|
85
28
|
// Get SSH key
|
|
86
29
|
const sshInfo = await getSSHKey(devboxId);
|
|
87
30
|
if (!sshInfo) {
|
|
88
|
-
|
|
31
|
+
outputError("Failed to create SSH key");
|
|
89
32
|
}
|
|
90
33
|
if (options.configOnly) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
34
|
+
const config = generateSSHConfig(devboxId, user, sshInfo.keyfilePath, sshInfo.url);
|
|
35
|
+
output({ config }, { format: options.output, defaultFormat: "text" });
|
|
36
|
+
return;
|
|
94
37
|
}
|
|
95
|
-
|
|
96
|
-
|
|
38
|
+
// If output format is specified, just return the connection info
|
|
39
|
+
if (options.output && options.output !== "text") {
|
|
40
|
+
output({
|
|
97
41
|
devboxId,
|
|
98
42
|
user,
|
|
99
43
|
keyfilePath: sshInfo.keyfilePath,
|
|
100
44
|
url: sshInfo.url,
|
|
101
|
-
};
|
|
45
|
+
}, { format: options.output, defaultFormat: "json" });
|
|
46
|
+
return;
|
|
102
47
|
}
|
|
103
|
-
|
|
48
|
+
// Actually start SSH session
|
|
49
|
+
const proxyCommand = getProxyCommand();
|
|
50
|
+
const sshArgs = [
|
|
51
|
+
"-i",
|
|
52
|
+
sshInfo.keyfilePath,
|
|
53
|
+
"-o",
|
|
54
|
+
`ProxyCommand=${proxyCommand}`,
|
|
55
|
+
"-o",
|
|
56
|
+
"StrictHostKeyChecking=no",
|
|
57
|
+
`${user}@${sshInfo.url}`,
|
|
58
|
+
];
|
|
59
|
+
const sshProcess = spawn("/usr/bin/ssh", sshArgs, {
|
|
60
|
+
stdio: "inherit",
|
|
61
|
+
});
|
|
62
|
+
sshProcess.on("close", (code) => {
|
|
63
|
+
processUtils.exit(code || 0);
|
|
64
|
+
});
|
|
65
|
+
sshProcess.on("error", (err) => {
|
|
66
|
+
outputError("SSH connection failed", err);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
outputError("Failed to setup SSH connection", error);
|
|
71
|
+
}
|
|
104
72
|
}
|
|
@@ -1,37 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Suspend devbox command
|
|
3
|
+
*/
|
|
3
4
|
import { getClient } from "../../utils/client.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const suspendDevbox = async () => {
|
|
15
|
-
try {
|
|
16
|
-
const client = getClient();
|
|
17
|
-
const devbox = await client.devboxes.suspend(devboxId);
|
|
18
|
-
setResult(devbox);
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
setError(err);
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
setLoading(false);
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
suspendDevbox();
|
|
28
|
-
}, [devboxId]);
|
|
29
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Suspending devbox..." }), result && (_jsx(SuccessMessage, { message: "Devbox suspended", details: `ID: ${result.id}\nStatus: ${result.status}` })), error && (_jsx(ErrorMessage, { message: "Failed to suspend devbox", error: error }))] }));
|
|
30
|
-
};
|
|
31
|
-
export async function suspendDevbox(devboxId, options) {
|
|
32
|
-
const executor = createExecutor(options);
|
|
33
|
-
await executor.executeAction(async () => {
|
|
34
|
-
const client = executor.getClient();
|
|
35
|
-
return client.devboxes.suspend(devboxId);
|
|
36
|
-
}, () => _jsx(SuspendDevboxUI, { devboxId: devboxId }));
|
|
5
|
+
import { output, outputError } from "../../utils/output.js";
|
|
6
|
+
export async function suspendDevbox(devboxId, options = {}) {
|
|
7
|
+
try {
|
|
8
|
+
const client = getClient();
|
|
9
|
+
const devbox = await client.devboxes.suspend(devboxId);
|
|
10
|
+
output(devbox, { format: options.output, defaultFormat: "json" });
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
outputError("Failed to suspend devbox", error);
|
|
14
|
+
}
|
|
37
15
|
}
|
|
@@ -1,100 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Create SSH tunnel to devbox command
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from "child_process";
|
|
3
5
|
import { getClient } from "../../utils/client.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { SuccessMessage } from "../../components/SuccessMessage.js";
|
|
7
|
-
import { ErrorMessage } from "../../components/ErrorMessage.js";
|
|
8
|
-
import { createExecutor } from "../../utils/CommandExecutor.js";
|
|
6
|
+
import { output, outputError } from "../../utils/output.js";
|
|
7
|
+
import { processUtils } from "../../utils/processUtils.js";
|
|
9
8
|
import { getSSHKey, getProxyCommand, checkSSHTools } from "../../utils/ssh.js";
|
|
10
|
-
import { exec } from "child_process";
|
|
11
|
-
import { promisify } from "util";
|
|
12
|
-
const execAsync = promisify(exec);
|
|
13
|
-
const TunnelUI = ({ devboxId, ports }) => {
|
|
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 createTunnel = 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, openssl) are not available on this system");
|
|
24
|
-
}
|
|
25
|
-
if (!ports.includes(":")) {
|
|
26
|
-
throw new Error("Ports must be specified as 'local:remote'");
|
|
27
|
-
}
|
|
28
|
-
const [localPort, remotePort] = ports.split(":");
|
|
29
|
-
const client = getClient();
|
|
30
|
-
// Get devbox details to determine user
|
|
31
|
-
const devbox = await client.devboxes.retrieve(devboxId);
|
|
32
|
-
const user = devbox.launch_parameters?.user_parameters?.username || "user";
|
|
33
|
-
// Get SSH key
|
|
34
|
-
const sshInfo = await getSSHKey(devboxId);
|
|
35
|
-
if (!sshInfo) {
|
|
36
|
-
throw new Error("Failed to create SSH key");
|
|
37
|
-
}
|
|
38
|
-
const proxyCommand = getProxyCommand();
|
|
39
|
-
const tunnelCommand = [
|
|
40
|
-
"/usr/bin/ssh",
|
|
41
|
-
"-i",
|
|
42
|
-
sshInfo.keyfilePath,
|
|
43
|
-
"-o",
|
|
44
|
-
`ProxyCommand=${proxyCommand}`,
|
|
45
|
-
"-o",
|
|
46
|
-
"StrictHostKeyChecking=no",
|
|
47
|
-
"-N", // Do not execute a remote command
|
|
48
|
-
"-L",
|
|
49
|
-
`${localPort}:localhost:${remotePort}`,
|
|
50
|
-
`${user}@${sshInfo.url}`,
|
|
51
|
-
];
|
|
52
|
-
console.log(`Starting tunnel: local port ${localPort} -> remote port ${remotePort}`);
|
|
53
|
-
console.log("Press Ctrl+C to stop the tunnel.");
|
|
54
|
-
// Set up signal handler for graceful shutdown
|
|
55
|
-
const signalHandler = () => {
|
|
56
|
-
console.log("\nStopping tunnel...");
|
|
57
|
-
process.exit(0);
|
|
58
|
-
};
|
|
59
|
-
process.on("SIGINT", signalHandler);
|
|
60
|
-
const { stdout, stderr } = await execAsync(tunnelCommand.join(" "));
|
|
61
|
-
setResult({ localPort, remotePort, stdout, stderr });
|
|
62
|
-
}
|
|
63
|
-
catch (err) {
|
|
64
|
-
setError(err);
|
|
65
|
-
}
|
|
66
|
-
finally {
|
|
67
|
-
setLoading(false);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
createTunnel();
|
|
71
|
-
}, [devboxId, ports]);
|
|
72
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Creating SSH tunnel..." }), result && (_jsx(SuccessMessage, { message: "SSH tunnel created", details: `Local port: ${result.localPort}\nRemote port: ${result.remotePort}` })), error && (_jsx(ErrorMessage, { message: "Failed to create SSH tunnel", error: error }))] }));
|
|
73
|
-
};
|
|
74
9
|
export async function createTunnel(devboxId, options) {
|
|
75
|
-
|
|
76
|
-
await executor.executeAction(async () => {
|
|
10
|
+
try {
|
|
77
11
|
// Check if SSH tools are available
|
|
78
12
|
const sshToolsAvailable = await checkSSHTools();
|
|
79
13
|
if (!sshToolsAvailable) {
|
|
80
|
-
|
|
14
|
+
outputError("SSH tools (ssh, openssl) are not available on this system");
|
|
81
15
|
}
|
|
82
16
|
if (!options.ports.includes(":")) {
|
|
83
|
-
|
|
17
|
+
outputError("Ports must be specified as 'local:remote'");
|
|
84
18
|
}
|
|
85
19
|
const [localPort, remotePort] = options.ports.split(":");
|
|
86
|
-
const client =
|
|
20
|
+
const client = getClient();
|
|
87
21
|
// Get devbox details to determine user
|
|
88
22
|
const devbox = await client.devboxes.retrieve(devboxId);
|
|
89
23
|
const user = devbox.launch_parameters?.user_parameters?.username || "user";
|
|
90
24
|
// Get SSH key
|
|
91
25
|
const sshInfo = await getSSHKey(devboxId);
|
|
92
26
|
if (!sshInfo) {
|
|
93
|
-
|
|
27
|
+
outputError("Failed to create SSH key");
|
|
28
|
+
}
|
|
29
|
+
// If output format is specified, just return the tunnel info
|
|
30
|
+
if (options.output && options.output !== "text") {
|
|
31
|
+
output({
|
|
32
|
+
devboxId,
|
|
33
|
+
localPort,
|
|
34
|
+
remotePort,
|
|
35
|
+
user,
|
|
36
|
+
keyfilePath: sshInfo.keyfilePath,
|
|
37
|
+
}, { format: options.output, defaultFormat: "json" });
|
|
38
|
+
return;
|
|
94
39
|
}
|
|
95
40
|
const proxyCommand = getProxyCommand();
|
|
96
|
-
const
|
|
97
|
-
"/usr/bin/ssh",
|
|
41
|
+
const tunnelArgs = [
|
|
98
42
|
"-i",
|
|
99
43
|
sshInfo.keyfilePath,
|
|
100
44
|
"-o",
|
|
@@ -108,13 +52,18 @@ export async function createTunnel(devboxId, options) {
|
|
|
108
52
|
];
|
|
109
53
|
console.log(`Starting tunnel: local port ${localPort} -> remote port ${remotePort}`);
|
|
110
54
|
console.log("Press Ctrl+C to stop the tunnel.");
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
55
|
+
const tunnelProcess = spawn("/usr/bin/ssh", tunnelArgs, {
|
|
56
|
+
stdio: "inherit",
|
|
57
|
+
});
|
|
58
|
+
tunnelProcess.on("close", (code) => {
|
|
59
|
+
console.log("\nTunnel closed.");
|
|
60
|
+
processUtils.exit(code || 0);
|
|
61
|
+
});
|
|
62
|
+
tunnelProcess.on("error", (err) => {
|
|
63
|
+
outputError("Tunnel creation failed", err);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
outputError("Failed to create SSH tunnel", error);
|
|
68
|
+
}
|
|
120
69
|
}
|
|
@@ -1,40 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Upload file to devbox command
|
|
3
|
+
*/
|
|
4
4
|
import { createReadStream } from "fs";
|
|
5
5
|
import { getClient } from "../../utils/client.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
await client.devboxes.uploadFile(id, {
|
|
21
|
-
path: targetPath || filename,
|
|
22
|
-
file: fileStream,
|
|
23
|
-
});
|
|
24
|
-
setSuccess(true);
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
setError(err);
|
|
28
|
-
}
|
|
29
|
-
finally {
|
|
30
|
-
setLoading(false);
|
|
31
|
-
}
|
|
6
|
+
import { output, outputError } from "../../utils/output.js";
|
|
7
|
+
export async function uploadFile(id, file, options = {}) {
|
|
8
|
+
try {
|
|
9
|
+
const client = getClient();
|
|
10
|
+
const fileStream = createReadStream(file);
|
|
11
|
+
const filename = file.split("/").pop() || "uploaded-file";
|
|
12
|
+
await client.devboxes.uploadFile(id, {
|
|
13
|
+
path: options.path || filename,
|
|
14
|
+
file: fileStream,
|
|
15
|
+
});
|
|
16
|
+
const result = {
|
|
17
|
+
file,
|
|
18
|
+
target: options.path || filename,
|
|
19
|
+
devboxId: id,
|
|
32
20
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
21
|
+
// Default: just output the target path for easy scripting
|
|
22
|
+
if (!options.output || options.output === "text") {
|
|
23
|
+
console.log(options.path || filename);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
output(result, { format: options.output, defaultFormat: "json" });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
outputError("Failed to upload file", error);
|
|
31
|
+
}
|
|
40
32
|
}
|
|
@@ -1,51 +1,36 @@
|
|
|
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
|
+
* Write file to devbox command (using API)
|
|
3
|
+
*/
|
|
9
4
|
import { readFile } from "fs/promises";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
contents,
|
|
22
|
-
});
|
|
23
|
-
setResult({ inputPath, remotePath, size: contents.length });
|
|
24
|
-
}
|
|
25
|
-
catch (err) {
|
|
26
|
-
setError(err);
|
|
27
|
-
}
|
|
28
|
-
finally {
|
|
29
|
-
setLoading(false);
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
writeFile();
|
|
33
|
-
}, [devboxId, inputPath, remotePath]);
|
|
34
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Writing file to devbox..." }), result && (_jsx(SuccessMessage, { message: "File written successfully", details: `Local: ${result.inputPath}\nRemote: ${result.remotePath}\nSize: ${result.size} bytes` })), error && _jsx(ErrorMessage, { message: "Failed to write file", error: error })] }));
|
|
35
|
-
};
|
|
36
|
-
export async function writeFile(devboxId, options) {
|
|
37
|
-
const executor = createExecutor({ output: options.output });
|
|
38
|
-
await executor.executeAction(async () => {
|
|
39
|
-
const client = executor.getClient();
|
|
5
|
+
import { getClient } from "../../utils/client.js";
|
|
6
|
+
import { output, outputError } from "../../utils/output.js";
|
|
7
|
+
export async function writeFile(devboxId, options = {}) {
|
|
8
|
+
if (!options.input) {
|
|
9
|
+
outputError("--input is required");
|
|
10
|
+
}
|
|
11
|
+
if (!options.remote) {
|
|
12
|
+
outputError("--remote is required");
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const client = getClient();
|
|
40
16
|
const contents = await readFile(options.input, "utf-8");
|
|
41
17
|
await client.devboxes.writeFileContents(devboxId, {
|
|
42
18
|
file_path: options.remote,
|
|
43
19
|
contents,
|
|
44
20
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
21
|
+
// Default: just output the remote path for easy scripting
|
|
22
|
+
if (!options.output || options.output === "text") {
|
|
23
|
+
console.log(options.remote);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
output({
|
|
27
|
+
local: options.input,
|
|
28
|
+
remote: options.remote,
|
|
29
|
+
size: contents.length,
|
|
30
|
+
}, { format: options.output, defaultFormat: "json" });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
outputError("Failed to write file", error);
|
|
35
|
+
}
|
|
51
36
|
}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
import { dirname, join } from "path";
|
|
5
|
+
import { processUtils } from "../utils/processUtils.js";
|
|
5
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
7
|
const __dirname = dirname(__filename);
|
|
7
8
|
export async function startMcpHttpServer(port) {
|
|
8
9
|
// Get the path to the compiled MCP HTTP server
|
|
9
10
|
const serverPath = join(__dirname, "../mcp/server-http.js");
|
|
10
|
-
const env = { ...
|
|
11
|
+
const env = { ...processUtils.env };
|
|
11
12
|
if (port) {
|
|
12
13
|
env.PORT = port.toString();
|
|
13
14
|
}
|
|
@@ -20,18 +21,18 @@ export async function startMcpHttpServer(port) {
|
|
|
20
21
|
});
|
|
21
22
|
serverProcess.on("error", (error) => {
|
|
22
23
|
console.error("Failed to start MCP HTTP server:", error);
|
|
23
|
-
|
|
24
|
+
processUtils.exit(1);
|
|
24
25
|
});
|
|
25
26
|
serverProcess.on("exit", (code) => {
|
|
26
27
|
if (code !== 0) {
|
|
27
28
|
console.error(`MCP HTTP server exited with code ${code}`);
|
|
28
|
-
|
|
29
|
+
processUtils.exit(code || 1);
|
|
29
30
|
}
|
|
30
31
|
});
|
|
31
32
|
// Handle Ctrl+C
|
|
32
|
-
|
|
33
|
+
processUtils.on("SIGINT", () => {
|
|
33
34
|
console.log("\nShutting down MCP HTTP server...");
|
|
34
35
|
serverProcess.kill("SIGINT");
|
|
35
|
-
|
|
36
|
+
processUtils.exit(0);
|
|
36
37
|
});
|
|
37
38
|
}
|
|
@@ -3,13 +3,14 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
|
3
3
|
import { homedir, platform } from "os";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { execSync } from "child_process";
|
|
6
|
+
import { processUtils } from "../utils/processUtils.js";
|
|
6
7
|
function getClaudeConfigPath() {
|
|
7
8
|
const plat = platform();
|
|
8
9
|
if (plat === "darwin") {
|
|
9
10
|
return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
10
11
|
}
|
|
11
12
|
else if (plat === "win32") {
|
|
12
|
-
const appData =
|
|
13
|
+
const appData = processUtils.env.APPDATA;
|
|
13
14
|
if (!appData) {
|
|
14
15
|
throw new Error("APPDATA environment variable not found");
|
|
15
16
|
}
|
|
@@ -26,7 +27,7 @@ function getRliPath() {
|
|
|
26
27
|
const path = execSync(cmd, { encoding: "utf-8" }).trim().split("\n")[0];
|
|
27
28
|
return path;
|
|
28
29
|
}
|
|
29
|
-
catch
|
|
30
|
+
catch {
|
|
30
31
|
// If rli not found in PATH, just use 'rli' and hope it works
|
|
31
32
|
return "rli";
|
|
32
33
|
}
|
|
@@ -48,10 +49,10 @@ export async function installMcpConfig() {
|
|
|
48
49
|
config.mcpServers = {};
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
|
-
catch
|
|
52
|
+
catch {
|
|
52
53
|
console.error("❌ Error: Claude config file exists but is not valid JSON");
|
|
53
54
|
console.error("Please fix the file manually or delete it to create a new one");
|
|
54
|
-
|
|
55
|
+
processUtils.exit(1);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
else {
|
|
@@ -71,20 +72,20 @@ export async function installMcpConfig() {
|
|
|
71
72
|
// Ask if they want to overwrite
|
|
72
73
|
console.log("\n❓ Do you want to overwrite it? (y/N): ");
|
|
73
74
|
// For non-interactive mode, just exit
|
|
74
|
-
if (
|
|
75
|
+
if (processUtils.stdin.isTTY) {
|
|
75
76
|
const response = await new Promise((resolve) => {
|
|
76
|
-
|
|
77
|
+
processUtils.stdin.on("data", (data) => {
|
|
77
78
|
resolve(data.toString().trim().toLowerCase());
|
|
78
79
|
});
|
|
79
80
|
});
|
|
80
81
|
if (response !== "y" && response !== "yes") {
|
|
81
82
|
console.log("\n✓ Keeping existing configuration");
|
|
82
|
-
|
|
83
|
+
processUtils.exit(0);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
else {
|
|
86
87
|
console.log("\n✓ Keeping existing configuration (non-interactive mode)");
|
|
87
|
-
|
|
88
|
+
processUtils.exit(0);
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
// Add runloop MCP server config
|
|
@@ -103,7 +104,8 @@ export async function installMcpConfig() {
|
|
|
103
104
|
console.log('\n💡 Tip: Make sure you\'ve run "rli auth" to configure your API key first!');
|
|
104
105
|
}
|
|
105
106
|
catch (error) {
|
|
106
|
-
|
|
107
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
108
|
+
console.error("\n❌ Error installing MCP configuration:", errorMessage);
|
|
107
109
|
console.error("\n💡 You can manually add this configuration to your Claude Desktop config:");
|
|
108
110
|
console.error(`\nFile location: ${getClaudeConfigPath()}`);
|
|
109
111
|
console.error("\nConfiguration to add:");
|
|
@@ -115,6 +117,6 @@ export async function installMcpConfig() {
|
|
|
115
117
|
},
|
|
116
118
|
},
|
|
117
119
|
}, null, 2));
|
|
118
|
-
|
|
120
|
+
processUtils.exit(1);
|
|
119
121
|
}
|
|
120
122
|
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
import { dirname, join } from "path";
|
|
5
|
+
import { processUtils } from "../utils/processUtils.js";
|
|
5
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
7
|
const __dirname = dirname(__filename);
|
|
7
8
|
export async function startMcpServer() {
|
|
@@ -14,17 +15,17 @@ export async function startMcpServer() {
|
|
|
14
15
|
});
|
|
15
16
|
serverProcess.on("error", (error) => {
|
|
16
17
|
console.error("Failed to start MCP server:", error);
|
|
17
|
-
|
|
18
|
+
processUtils.exit(1);
|
|
18
19
|
});
|
|
19
20
|
serverProcess.on("exit", (code) => {
|
|
20
21
|
if (code !== 0) {
|
|
21
22
|
console.error(`MCP server exited with code ${code}`);
|
|
22
|
-
|
|
23
|
+
processUtils.exit(code || 1);
|
|
23
24
|
}
|
|
24
25
|
});
|
|
25
26
|
// Handle Ctrl+C
|
|
26
|
-
|
|
27
|
+
processUtils.on("SIGINT", () => {
|
|
27
28
|
serverProcess.kill("SIGINT");
|
|
28
|
-
|
|
29
|
+
processUtils.exit(0);
|
|
29
30
|
});
|
|
30
31
|
}
|