@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,82 +1,24 @@
|
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
import { createExecutor } from "../../utils/CommandExecutor.js";
|
|
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 { getSSHKey, waitForReady, generateSSHConfig, checkSSHTools, getProxyCommand, } from "../../utils/ssh.js";
|
|
8
|
+
export async function sshDevbox(devboxId, options = {}) {
|
|
9
|
+
try {
|
|
68
10
|
// Check if SSH tools are available
|
|
69
11
|
const sshToolsAvailable = await checkSSHTools();
|
|
70
12
|
if (!sshToolsAvailable) {
|
|
71
|
-
|
|
13
|
+
outputError("SSH tools (ssh, scp, rsync, openssl) are not available on this system");
|
|
72
14
|
}
|
|
73
|
-
const client =
|
|
15
|
+
const client = getClient();
|
|
74
16
|
// Wait for devbox to be ready unless --no-wait is specified
|
|
75
17
|
if (!options.noWait) {
|
|
76
|
-
console.
|
|
18
|
+
console.error(`Waiting for devbox ${devboxId} to be ready...`);
|
|
77
19
|
const isReady = await waitForReady(devboxId, options.timeout || 180, options.pollInterval || 3);
|
|
78
20
|
if (!isReady) {
|
|
79
|
-
|
|
21
|
+
outputError(`Devbox ${devboxId} is not ready. Please try again later.`);
|
|
80
22
|
}
|
|
81
23
|
}
|
|
82
24
|
// Get devbox details to determine user
|
|
@@ -85,20 +27,45 @@ export async function sshDevbox(devboxId, options) {
|
|
|
85
27
|
// Get SSH key
|
|
86
28
|
const sshInfo = await getSSHKey(devboxId);
|
|
87
29
|
if (!sshInfo) {
|
|
88
|
-
|
|
30
|
+
outputError("Failed to create SSH key");
|
|
89
31
|
}
|
|
90
32
|
if (options.configOnly) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
33
|
+
const config = generateSSHConfig(devboxId, user, sshInfo.keyfilePath, sshInfo.url);
|
|
34
|
+
output({ config }, { format: options.output, defaultFormat: "text" });
|
|
35
|
+
return;
|
|
94
36
|
}
|
|
95
|
-
|
|
96
|
-
|
|
37
|
+
// If output format is specified, just return the connection info
|
|
38
|
+
if (options.output && options.output !== "text") {
|
|
39
|
+
output({
|
|
97
40
|
devboxId,
|
|
98
41
|
user,
|
|
99
42
|
keyfilePath: sshInfo.keyfilePath,
|
|
100
43
|
url: sshInfo.url,
|
|
101
|
-
};
|
|
44
|
+
}, { format: options.output, defaultFormat: "json" });
|
|
45
|
+
return;
|
|
102
46
|
}
|
|
103
|
-
|
|
47
|
+
// Actually start SSH session
|
|
48
|
+
const proxyCommand = getProxyCommand();
|
|
49
|
+
const sshArgs = [
|
|
50
|
+
"-i",
|
|
51
|
+
sshInfo.keyfilePath,
|
|
52
|
+
"-o",
|
|
53
|
+
`ProxyCommand=${proxyCommand}`,
|
|
54
|
+
"-o",
|
|
55
|
+
"StrictHostKeyChecking=no",
|
|
56
|
+
`${user}@${sshInfo.url}`,
|
|
57
|
+
];
|
|
58
|
+
const sshProcess = spawn("/usr/bin/ssh", sshArgs, {
|
|
59
|
+
stdio: "inherit",
|
|
60
|
+
});
|
|
61
|
+
sshProcess.on("close", (code) => {
|
|
62
|
+
process.exit(code || 0);
|
|
63
|
+
});
|
|
64
|
+
sshProcess.on("error", (err) => {
|
|
65
|
+
outputError("SSH connection failed", err);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
outputError("Failed to setup SSH connection", error);
|
|
70
|
+
}
|
|
104
71
|
}
|
|
@@ -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,43 @@
|
|
|
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 { 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";
|
|
6
|
+
import { output, outputError } from "../../utils/output.js";
|
|
9
7
|
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
8
|
export async function createTunnel(devboxId, options) {
|
|
75
|
-
|
|
76
|
-
await executor.executeAction(async () => {
|
|
9
|
+
try {
|
|
77
10
|
// Check if SSH tools are available
|
|
78
11
|
const sshToolsAvailable = await checkSSHTools();
|
|
79
12
|
if (!sshToolsAvailable) {
|
|
80
|
-
|
|
13
|
+
outputError("SSH tools (ssh, openssl) are not available on this system");
|
|
81
14
|
}
|
|
82
15
|
if (!options.ports.includes(":")) {
|
|
83
|
-
|
|
16
|
+
outputError("Ports must be specified as 'local:remote'");
|
|
84
17
|
}
|
|
85
18
|
const [localPort, remotePort] = options.ports.split(":");
|
|
86
|
-
const client =
|
|
19
|
+
const client = getClient();
|
|
87
20
|
// Get devbox details to determine user
|
|
88
21
|
const devbox = await client.devboxes.retrieve(devboxId);
|
|
89
22
|
const user = devbox.launch_parameters?.user_parameters?.username || "user";
|
|
90
23
|
// Get SSH key
|
|
91
24
|
const sshInfo = await getSSHKey(devboxId);
|
|
92
25
|
if (!sshInfo) {
|
|
93
|
-
|
|
26
|
+
outputError("Failed to create SSH key");
|
|
27
|
+
}
|
|
28
|
+
// If output format is specified, just return the tunnel info
|
|
29
|
+
if (options.output && options.output !== "text") {
|
|
30
|
+
output({
|
|
31
|
+
devboxId,
|
|
32
|
+
localPort,
|
|
33
|
+
remotePort,
|
|
34
|
+
user,
|
|
35
|
+
keyfilePath: sshInfo.keyfilePath,
|
|
36
|
+
}, { format: options.output, defaultFormat: "json" });
|
|
37
|
+
return;
|
|
94
38
|
}
|
|
95
39
|
const proxyCommand = getProxyCommand();
|
|
96
|
-
const
|
|
97
|
-
"/usr/bin/ssh",
|
|
40
|
+
const tunnelArgs = [
|
|
98
41
|
"-i",
|
|
99
42
|
sshInfo.keyfilePath,
|
|
100
43
|
"-o",
|
|
@@ -108,13 +51,18 @@ export async function createTunnel(devboxId, options) {
|
|
|
108
51
|
];
|
|
109
52
|
console.log(`Starting tunnel: local port ${localPort} -> remote port ${remotePort}`);
|
|
110
53
|
console.log("Press Ctrl+C to stop the tunnel.");
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
54
|
+
const tunnelProcess = spawn("/usr/bin/ssh", tunnelArgs, {
|
|
55
|
+
stdio: "inherit",
|
|
56
|
+
});
|
|
57
|
+
tunnelProcess.on("close", (code) => {
|
|
58
|
+
console.log("\nTunnel closed.");
|
|
59
|
+
process.exit(code || 0);
|
|
60
|
+
});
|
|
61
|
+
tunnelProcess.on("error", (err) => {
|
|
62
|
+
outputError("Tunnel creation failed", err);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
outputError("Failed to create SSH tunnel", error);
|
|
67
|
+
}
|
|
120
68
|
}
|
|
@@ -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
|
}
|
|
@@ -26,7 +26,7 @@ function getRliPath() {
|
|
|
26
26
|
const path = execSync(cmd, { encoding: "utf-8" }).trim().split("\n")[0];
|
|
27
27
|
return path;
|
|
28
28
|
}
|
|
29
|
-
catch
|
|
29
|
+
catch {
|
|
30
30
|
// If rli not found in PATH, just use 'rli' and hope it works
|
|
31
31
|
return "rli";
|
|
32
32
|
}
|
|
@@ -48,7 +48,7 @@ export async function installMcpConfig() {
|
|
|
48
48
|
config.mcpServers = {};
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
catch
|
|
51
|
+
catch {
|
|
52
52
|
console.error("ā Error: Claude config file exists but is not valid JSON");
|
|
53
53
|
console.error("Please fix the file manually or delete it to create a new one");
|
|
54
54
|
process.exit(1);
|
|
@@ -103,7 +103,8 @@ export async function installMcpConfig() {
|
|
|
103
103
|
console.log('\nš” Tip: Make sure you\'ve run "rli auth" to configure your API key first!');
|
|
104
104
|
}
|
|
105
105
|
catch (error) {
|
|
106
|
-
|
|
106
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
107
|
+
console.error("\nā Error installing MCP configuration:", errorMessage);
|
|
107
108
|
console.error("\nš” You can manually add this configuration to your Claude Desktop config:");
|
|
108
109
|
console.error(`\nFile location: ${getClaudeConfigPath()}`);
|
|
109
110
|
console.error("\nConfiguration to add:");
|
package/dist/commands/menu.js
CHANGED
|
@@ -1,70 +1,28 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 { Router } from "../router/Router.js";
|
|
5
|
+
import { NavigationProvider } from "../store/navigationStore.js";
|
|
6
|
+
function AppInner() {
|
|
7
|
+
// NavigationProvider already handles initialScreen and initialParams
|
|
8
|
+
// No need for useEffect here - provider sets state on mount
|
|
9
|
+
return _jsx(Router, {});
|
|
10
|
+
}
|
|
11
|
+
function App({ initialScreen = "menu", focusDevboxId, }) {
|
|
12
|
+
return (_jsx(NavigationProvider, { initialScreen: initialScreen, initialParams: focusDevboxId ? { focusDevboxId } : {}, children: _jsx(AppInner, {}) }));
|
|
13
|
+
}
|
|
27
14
|
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
|
-
}
|
|
15
|
+
enterAlternateScreenBuffer();
|
|
16
|
+
try {
|
|
17
|
+
const { waitUntilExit } = render(_jsx(App, { initialScreen: initialScreen, focusDevboxId: focusDevboxId }, `app-${initialScreen}-${focusDevboxId}`), {
|
|
18
|
+
patchConsole: false,
|
|
19
|
+
exitOnCtrlC: false,
|
|
20
|
+
});
|
|
21
|
+
await waitUntilExit();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error("Error in menu:", error);
|
|
66
25
|
}
|
|
67
|
-
|
|
68
|
-
process.stdout.write("\x1b[?1049l");
|
|
26
|
+
exitAlternateScreenBuffer();
|
|
69
27
|
process.exit(0);
|
|
70
28
|
}
|