@runloop/rl-cli 0.0.3 → 0.1.1

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 (73) hide show
  1. package/README.md +64 -29
  2. package/dist/cli.js +401 -92
  3. package/dist/commands/auth.js +12 -11
  4. package/dist/commands/blueprint/create.js +108 -0
  5. package/dist/commands/blueprint/get.js +37 -0
  6. package/dist/commands/blueprint/list.js +293 -225
  7. package/dist/commands/blueprint/logs.js +40 -0
  8. package/dist/commands/blueprint/preview.js +45 -0
  9. package/dist/commands/devbox/create.js +10 -9
  10. package/dist/commands/devbox/delete.js +8 -8
  11. package/dist/commands/devbox/download.js +49 -0
  12. package/dist/commands/devbox/exec.js +23 -13
  13. package/dist/commands/devbox/execAsync.js +43 -0
  14. package/dist/commands/devbox/get.js +37 -0
  15. package/dist/commands/devbox/getAsync.js +37 -0
  16. package/dist/commands/devbox/list.js +328 -190
  17. package/dist/commands/devbox/logs.js +40 -0
  18. package/dist/commands/devbox/read.js +49 -0
  19. package/dist/commands/devbox/resume.js +37 -0
  20. package/dist/commands/devbox/rsync.js +118 -0
  21. package/dist/commands/devbox/scp.js +122 -0
  22. package/dist/commands/devbox/shutdown.js +37 -0
  23. package/dist/commands/devbox/ssh.js +104 -0
  24. package/dist/commands/devbox/suspend.js +37 -0
  25. package/dist/commands/devbox/tunnel.js +120 -0
  26. package/dist/commands/devbox/upload.js +10 -10
  27. package/dist/commands/devbox/write.js +51 -0
  28. package/dist/commands/mcp-http.js +37 -0
  29. package/dist/commands/mcp-install.js +120 -0
  30. package/dist/commands/mcp.js +30 -0
  31. package/dist/commands/menu.js +20 -20
  32. package/dist/commands/object/delete.js +37 -0
  33. package/dist/commands/object/download.js +88 -0
  34. package/dist/commands/object/get.js +37 -0
  35. package/dist/commands/object/list.js +112 -0
  36. package/dist/commands/object/upload.js +130 -0
  37. package/dist/commands/snapshot/create.js +12 -11
  38. package/dist/commands/snapshot/delete.js +8 -8
  39. package/dist/commands/snapshot/list.js +56 -97
  40. package/dist/commands/snapshot/status.js +37 -0
  41. package/dist/components/ActionsPopup.js +16 -13
  42. package/dist/components/Banner.js +4 -4
  43. package/dist/components/Breadcrumb.js +55 -5
  44. package/dist/components/DetailView.js +7 -4
  45. package/dist/components/DevboxActionsMenu.js +315 -178
  46. package/dist/components/DevboxCard.js +15 -14
  47. package/dist/components/DevboxCreatePage.js +147 -113
  48. package/dist/components/DevboxDetailPage.js +180 -102
  49. package/dist/components/ErrorMessage.js +5 -4
  50. package/dist/components/Header.js +4 -3
  51. package/dist/components/MainMenu.js +34 -33
  52. package/dist/components/MetadataDisplay.js +17 -9
  53. package/dist/components/OperationsMenu.js +6 -5
  54. package/dist/components/ResourceActionsMenu.js +117 -0
  55. package/dist/components/ResourceListView.js +213 -0
  56. package/dist/components/Spinner.js +5 -4
  57. package/dist/components/StatusBadge.js +81 -31
  58. package/dist/components/SuccessMessage.js +4 -3
  59. package/dist/components/Table.example.js +53 -23
  60. package/dist/components/Table.js +19 -11
  61. package/dist/hooks/useCursorPagination.js +125 -0
  62. package/dist/mcp/server-http.js +416 -0
  63. package/dist/mcp/server.js +397 -0
  64. package/dist/utils/CommandExecutor.js +16 -12
  65. package/dist/utils/client.js +7 -7
  66. package/dist/utils/config.js +130 -4
  67. package/dist/utils/interactiveCommand.js +2 -2
  68. package/dist/utils/output.js +17 -17
  69. package/dist/utils/ssh.js +160 -0
  70. package/dist/utils/sshSession.js +16 -12
  71. package/dist/utils/theme.js +22 -0
  72. package/dist/utils/url.js +4 -4
  73. package/package.json +29 -4
@@ -0,0 +1,40 @@
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";
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
+ const LogsUI = ({ devboxId }) => {
11
+ const [loading, setLoading] = React.useState(true);
12
+ const [result, setResult] = React.useState(null);
13
+ const [error, setError] = React.useState(null);
14
+ React.useEffect(() => {
15
+ const getLogs = async () => {
16
+ try {
17
+ const client = getClient();
18
+ const logs = await client.devboxes.logs.list(devboxId);
19
+ setResult(logs);
20
+ }
21
+ catch (err) {
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 }));
40
+ }
@@ -0,0 +1,49 @@
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";
9
+ import { writeFile } from "fs/promises";
10
+ const ReadFileUI = ({ devboxId, remotePath, outputPath }) => {
11
+ const [loading, setLoading] = React.useState(true);
12
+ const [result, setResult] = React.useState(null);
13
+ const [error, setError] = React.useState(null);
14
+ React.useEffect(() => {
15
+ const readFile = async () => {
16
+ try {
17
+ const client = getClient();
18
+ const contents = await client.devboxes.readFileContents(devboxId, {
19
+ file_path: remotePath,
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();
39
+ const contents = await client.devboxes.readFileContents(devboxId, {
40
+ file_path: options.remote,
41
+ });
42
+ await writeFile(options.outputPath, contents);
43
+ return {
44
+ remotePath: options.remote,
45
+ outputPath: options.outputPath,
46
+ size: contents.length,
47
+ };
48
+ }, () => (_jsx(ReadFileUI, { devboxId: devboxId, remotePath: options.remote, outputPath: options.outputPath })));
49
+ }
@@ -0,0 +1,37 @@
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";
9
+ const ResumeDevboxUI = ({ devboxId }) => {
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 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 }));
37
+ }
@@ -0,0 +1,118 @@
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";
9
+ 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 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
+ export async function rsyncFiles(devboxId, options) {
74
+ const executor = createExecutor({ output: options.outputFormat });
75
+ await executor.executeAction(async () => {
76
+ // Check if SSH tools are available
77
+ const sshToolsAvailable = await checkSSHTools();
78
+ if (!sshToolsAvailable) {
79
+ throw new Error("SSH tools (ssh, rsync, openssl) are not available on this system");
80
+ }
81
+ const client = executor.getClient();
82
+ // Get devbox details to determine user
83
+ const devbox = await client.devboxes.retrieve(devboxId);
84
+ const user = devbox.launch_parameters?.user_parameters?.username || "user";
85
+ // Get SSH key
86
+ const sshInfo = await getSSHKey(devboxId);
87
+ if (!sshInfo) {
88
+ throw new Error("Failed to create SSH key");
89
+ }
90
+ const proxyCommand = getProxyCommand();
91
+ const sshOptions = `-i ${sshInfo.keyfilePath} -o ProxyCommand='${proxyCommand}' -o StrictHostKeyChecking=no`;
92
+ const rsyncCommand = [
93
+ "rsync",
94
+ "-vrz", // v: verbose, r: recursive, z: compress
95
+ "-e",
96
+ `ssh ${sshOptions}`,
97
+ ];
98
+ if (options.rsyncOptions) {
99
+ rsyncCommand.push(...options.rsyncOptions.split(" "));
100
+ }
101
+ // Handle remote paths (starting with :)
102
+ if (options.src.startsWith(":")) {
103
+ rsyncCommand.push(`${user}@${sshInfo.url}:${options.src.slice(1)}`);
104
+ rsyncCommand.push(options.dst);
105
+ }
106
+ else {
107
+ rsyncCommand.push(options.src);
108
+ if (options.dst.startsWith(":")) {
109
+ rsyncCommand.push(`${user}@${sshInfo.url}:${options.dst.slice(1)}`);
110
+ }
111
+ else {
112
+ rsyncCommand.push(options.dst);
113
+ }
114
+ }
115
+ const { stdout, stderr } = await execAsync(rsyncCommand.join(" "));
116
+ return { src: options.src, dst: options.dst, stdout, stderr };
117
+ }, () => (_jsx(RsyncUI, { devboxId: devboxId, src: options.src, dst: options.dst, rsyncOptions: options.rsyncOptions })));
118
+ }
@@ -0,0 +1,122 @@
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";
9
+ 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 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
+ export async function scpFiles(devboxId, options) {
76
+ const executor = createExecutor({ output: options.outputFormat });
77
+ await executor.executeAction(async () => {
78
+ // Check if SSH tools are available
79
+ const sshToolsAvailable = await checkSSHTools();
80
+ if (!sshToolsAvailable) {
81
+ throw new Error("SSH tools (ssh, scp, openssl) are not available on this system");
82
+ }
83
+ const client = executor.getClient();
84
+ // Get devbox details to determine user
85
+ const devbox = await client.devboxes.retrieve(devboxId);
86
+ const user = devbox.launch_parameters?.user_parameters?.username || "user";
87
+ // Get SSH key
88
+ const sshInfo = await getSSHKey(devboxId);
89
+ if (!sshInfo) {
90
+ throw new Error("Failed to create SSH key");
91
+ }
92
+ const proxyCommand = getProxyCommand();
93
+ const scpCommand = [
94
+ "scp",
95
+ "-i",
96
+ sshInfo.keyfilePath,
97
+ "-o",
98
+ `ProxyCommand=${proxyCommand}`,
99
+ "-o",
100
+ "StrictHostKeyChecking=no",
101
+ ];
102
+ if (options.scpOptions) {
103
+ scpCommand.push(...options.scpOptions.split(" "));
104
+ }
105
+ // Handle remote paths (starting with :)
106
+ if (options.src.startsWith(":")) {
107
+ scpCommand.push(`${user}@${sshInfo.url}:${options.src.slice(1)}`);
108
+ scpCommand.push(options.dst);
109
+ }
110
+ else {
111
+ scpCommand.push(options.src);
112
+ if (options.dst.startsWith(":")) {
113
+ scpCommand.push(`${user}@${sshInfo.url}:${options.dst.slice(1)}`);
114
+ }
115
+ else {
116
+ scpCommand.push(options.dst);
117
+ }
118
+ }
119
+ const { stdout, stderr } = await execAsync(scpCommand.join(" "));
120
+ return { src: options.src, dst: options.dst, stdout, stderr };
121
+ }, () => (_jsx(SCPUI, { devboxId: devboxId, src: options.src, dst: options.dst, scpOptions: options.scpOptions })));
122
+ }
@@ -0,0 +1,37 @@
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";
9
+ const ShutdownDevboxUI = ({ devboxId }) => {
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 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 }));
37
+ }
@@ -0,0 +1,104 @@
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";
4
+ import { getClient } from "../../utils/client.js";
5
+ import { Banner } from "../../components/Banner.js";
6
+ import { SpinnerComponent } from "../../components/Spinner.js";
7
+ import { SuccessMessage } from "../../components/SuccessMessage.js";
8
+ import { ErrorMessage } from "../../components/ErrorMessage.js";
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 () => {
68
+ // Check if SSH tools are available
69
+ const sshToolsAvailable = await checkSSHTools();
70
+ if (!sshToolsAvailable) {
71
+ throw new Error("SSH tools (ssh, scp, rsync, openssl) are not available on this system");
72
+ }
73
+ const client = executor.getClient();
74
+ // Wait for devbox to be ready unless --no-wait is specified
75
+ if (!options.noWait) {
76
+ console.log(`Waiting for devbox ${devboxId} to be ready...`);
77
+ const isReady = await waitForReady(devboxId, options.timeout || 180, options.pollInterval || 3);
78
+ if (!isReady) {
79
+ throw new Error(`Devbox ${devboxId} is not ready. Please try again later.`);
80
+ }
81
+ }
82
+ // Get devbox details to determine user
83
+ const devbox = await client.devboxes.retrieve(devboxId);
84
+ const user = devbox.launch_parameters?.user_parameters?.username || "user";
85
+ // Get SSH key
86
+ const sshInfo = await getSSHKey(devboxId);
87
+ if (!sshInfo) {
88
+ throw new Error("Failed to create SSH key");
89
+ }
90
+ if (options.configOnly) {
91
+ return {
92
+ config: generateSSHConfig(devboxId, user, sshInfo.keyfilePath, sshInfo.url),
93
+ };
94
+ }
95
+ else {
96
+ return {
97
+ devboxId,
98
+ user,
99
+ keyfilePath: sshInfo.keyfilePath,
100
+ url: sshInfo.url,
101
+ };
102
+ }
103
+ }, () => _jsx(SSHDevboxUI, { devboxId: devboxId, options: options }));
104
+ }
@@ -0,0 +1,37 @@
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";
9
+ const SuspendDevboxUI = ({ devboxId }) => {
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 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 }));
37
+ }