@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.
Files changed (96) hide show
  1. package/README.md +54 -0
  2. package/dist/cli.js +73 -60
  3. package/dist/commands/auth.js +0 -1
  4. package/dist/commands/blueprint/create.js +31 -83
  5. package/dist/commands/blueprint/get.js +29 -34
  6. package/dist/commands/blueprint/list.js +215 -213
  7. package/dist/commands/blueprint/logs.js +133 -37
  8. package/dist/commands/blueprint/preview.js +42 -38
  9. package/dist/commands/config.js +117 -0
  10. package/dist/commands/devbox/create.js +120 -40
  11. package/dist/commands/devbox/delete.js +17 -33
  12. package/dist/commands/devbox/download.js +29 -43
  13. package/dist/commands/devbox/exec.js +22 -39
  14. package/dist/commands/devbox/execAsync.js +20 -37
  15. package/dist/commands/devbox/get.js +13 -35
  16. package/dist/commands/devbox/getAsync.js +12 -34
  17. package/dist/commands/devbox/list.js +241 -402
  18. package/dist/commands/devbox/logs.js +20 -38
  19. package/dist/commands/devbox/read.js +29 -43
  20. package/dist/commands/devbox/resume.js +13 -35
  21. package/dist/commands/devbox/rsync.js +26 -78
  22. package/dist/commands/devbox/scp.js +25 -79
  23. package/dist/commands/devbox/sendStdin.js +41 -0
  24. package/dist/commands/devbox/shutdown.js +13 -35
  25. package/dist/commands/devbox/ssh.js +45 -78
  26. package/dist/commands/devbox/suspend.js +13 -35
  27. package/dist/commands/devbox/tunnel.js +36 -88
  28. package/dist/commands/devbox/upload.js +28 -36
  29. package/dist/commands/devbox/write.js +29 -44
  30. package/dist/commands/mcp-install.js +4 -3
  31. package/dist/commands/menu.js +24 -66
  32. package/dist/commands/object/delete.js +12 -34
  33. package/dist/commands/object/download.js +26 -74
  34. package/dist/commands/object/get.js +12 -34
  35. package/dist/commands/object/list.js +15 -93
  36. package/dist/commands/object/upload.js +35 -96
  37. package/dist/commands/snapshot/create.js +23 -39
  38. package/dist/commands/snapshot/delete.js +17 -33
  39. package/dist/commands/snapshot/get.js +16 -0
  40. package/dist/commands/snapshot/list.js +309 -80
  41. package/dist/commands/snapshot/status.js +12 -34
  42. package/dist/components/ActionsPopup.js +63 -39
  43. package/dist/components/Breadcrumb.js +10 -52
  44. package/dist/components/DevboxActionsMenu.js +182 -110
  45. package/dist/components/DevboxCreatePage.js +12 -7
  46. package/dist/components/DevboxDetailPage.js +76 -28
  47. package/dist/components/ErrorBoundary.js +29 -0
  48. package/dist/components/ErrorMessage.js +10 -2
  49. package/dist/components/Header.js +12 -4
  50. package/dist/components/InteractiveSpawn.js +94 -0
  51. package/dist/components/MainMenu.js +36 -32
  52. package/dist/components/MetadataDisplay.js +4 -4
  53. package/dist/components/OperationsMenu.js +1 -1
  54. package/dist/components/ResourceActionsMenu.js +4 -4
  55. package/dist/components/ResourceListView.js +46 -34
  56. package/dist/components/Spinner.js +7 -2
  57. package/dist/components/StatusBadge.js +1 -1
  58. package/dist/components/SuccessMessage.js +12 -2
  59. package/dist/components/Table.js +16 -6
  60. package/dist/hooks/useCursorPagination.js +125 -85
  61. package/dist/hooks/useExitOnCtrlC.js +14 -0
  62. package/dist/hooks/useViewportHeight.js +47 -0
  63. package/dist/mcp/server.js +65 -6
  64. package/dist/router/Router.js +68 -0
  65. package/dist/router/types.js +1 -0
  66. package/dist/screens/BlueprintListScreen.js +7 -0
  67. package/dist/screens/DevboxActionsScreen.js +25 -0
  68. package/dist/screens/DevboxCreateScreen.js +11 -0
  69. package/dist/screens/DevboxDetailScreen.js +60 -0
  70. package/dist/screens/DevboxListScreen.js +23 -0
  71. package/dist/screens/LogsSessionScreen.js +49 -0
  72. package/dist/screens/MenuScreen.js +23 -0
  73. package/dist/screens/SSHSessionScreen.js +55 -0
  74. package/dist/screens/SnapshotListScreen.js +7 -0
  75. package/dist/services/blueprintService.js +105 -0
  76. package/dist/services/devboxService.js +215 -0
  77. package/dist/services/snapshotService.js +81 -0
  78. package/dist/store/blueprintStore.js +89 -0
  79. package/dist/store/devboxStore.js +105 -0
  80. package/dist/store/index.js +7 -0
  81. package/dist/store/navigationStore.js +101 -0
  82. package/dist/store/snapshotStore.js +87 -0
  83. package/dist/utils/CommandExecutor.js +53 -24
  84. package/dist/utils/client.js +0 -2
  85. package/dist/utils/config.js +20 -90
  86. package/dist/utils/interactiveCommand.js +3 -2
  87. package/dist/utils/logFormatter.js +162 -0
  88. package/dist/utils/memoryMonitor.js +85 -0
  89. package/dist/utils/output.js +150 -59
  90. package/dist/utils/screen.js +23 -0
  91. package/dist/utils/ssh.js +3 -1
  92. package/dist/utils/sshSession.js +5 -29
  93. package/dist/utils/terminalDetection.js +97 -0
  94. package/dist/utils/terminalSync.js +39 -0
  95. package/dist/utils/theme.js +147 -13
  96. package/package.json +16 -13
@@ -1,40 +1,22 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React from "react";
3
- import { Box, Text } from "ink";
1
+ /**
2
+ * Get devbox logs command
3
+ */
4
4
  import { getClient } from "../../utils/client.js";
5
- import { Banner } from "../../components/Banner.js";
6
- import { SpinnerComponent } from "../../components/Spinner.js";
7
- import { ErrorMessage } from "../../components/ErrorMessage.js";
8
- import { createExecutor } from "../../utils/CommandExecutor.js";
9
- import { colors } from "../../utils/theme.js";
10
- 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 }));
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
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
- import { getClient } from "../../utils/client.js";
4
- import { Banner } from "../../components/Banner.js";
5
- import { SpinnerComponent } from "../../components/Spinner.js";
6
- import { SuccessMessage } from "../../components/SuccessMessage.js";
7
- import { ErrorMessage } from "../../components/ErrorMessage.js";
8
- import { createExecutor } from "../../utils/CommandExecutor.js";
1
+ /**
2
+ * Read file from devbox command (using API)
3
+ */
9
4
  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();
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
- 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 })));
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
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
1
+ /**
2
+ * Resume devbox command
3
+ */
3
4
  import { getClient } from "../../utils/client.js";
4
- import { Banner } from "../../components/Banner.js";
5
- import { SpinnerComponent } from "../../components/Spinner.js";
6
- import { SuccessMessage } from "../../components/SuccessMessage.js";
7
- import { ErrorMessage } from "../../components/ErrorMessage.js";
8
- import { createExecutor } from "../../utils/CommandExecutor.js";
9
- const 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 }));
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
- 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";
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
- const executor = createExecutor({ output: options.outputFormat });
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
- throw new Error("SSH tools (ssh, rsync, openssl) are not available on this system");
15
+ outputError("SSH tools (ssh, rsync, openssl) are not available on this system");
80
16
  }
81
- const client = executor.getClient();
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
- throw new Error("Failed to create SSH key");
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
- 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 })));
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
- 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";
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
- const executor = createExecutor({ output: options.outputFormat });
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
- throw new Error("SSH tools (ssh, scp, openssl) are not available on this system");
15
+ outputError("SSH tools (ssh, scp, openssl) are not available on this system");
82
16
  }
83
- const client = executor.getClient();
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
- throw new Error("Failed to create SSH key");
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
- 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 })));
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
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
1
+ /**
2
+ * Shutdown devbox command
3
+ */
3
4
  import { getClient } from "../../utils/client.js";
4
- import { Banner } from "../../components/Banner.js";
5
- import { SpinnerComponent } from "../../components/Spinner.js";
6
- import { SuccessMessage } from "../../components/SuccessMessage.js";
7
- import { ErrorMessage } from "../../components/ErrorMessage.js";
8
- import { createExecutor } from "../../utils/CommandExecutor.js";
9
- const 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 }));
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
  }