@runloop/rl-cli 0.1.2 → 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 -48
  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 +22 -111
  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,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
+ * Download file from devbox command
3
+ */
9
4
  import { writeFileSync } from "fs";
10
- const DownloadFileUI = ({ devboxId, filePath, 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 downloadFile = async () => {
16
- try {
17
- const client = getClient();
18
- const result = await client.devboxes.downloadFile(devboxId, {
19
- path: filePath,
20
- });
21
- // The result should contain the file contents, write them to the output path
22
- writeFileSync(outputPath, result);
23
- setResult({ filePath, outputPath });
24
- }
25
- catch (err) {
26
- setError(err);
27
- }
28
- finally {
29
- setLoading(false);
30
- }
31
- };
32
- downloadFile();
33
- }, [devboxId, filePath, outputPath]);
34
- return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && (_jsx(SpinnerComponent, { message: "Downloading file from devbox..." })), result && (_jsx(SuccessMessage, { message: "File downloaded successfully", details: `Remote: ${result.filePath}\nLocal: ${result.outputPath}` })), error && (_jsx(ErrorMessage, { message: "Failed to download file", error: error }))] }));
35
- };
36
- export async function downloadFile(devboxId, options) {
37
- const executor = createExecutor({ output: options.outputFormat });
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 downloadFile(devboxId, options = {}) {
8
+ if (!options.filePath) {
9
+ outputError("--file-path is required");
10
+ }
11
+ if (!options.outputPath) {
12
+ outputError("--output-path is required");
13
+ }
14
+ try {
15
+ const client = getClient();
40
16
  const result = await client.devboxes.downloadFile(devboxId, {
41
17
  path: options.filePath,
42
18
  });
19
+ // Write the file contents to the output path
43
20
  writeFileSync(options.outputPath, result);
44
- return {
45
- filePath: options.filePath,
46
- outputPath: options.outputPath,
47
- };
48
- }, () => (_jsx(DownloadFileUI, { devboxId: devboxId, filePath: options.filePath, outputPath: options.outputPath })));
21
+ // Default: just output the local path for easy scripting
22
+ if (!options.output || options.output === "text") {
23
+ console.log(options.outputPath);
24
+ }
25
+ else {
26
+ output({
27
+ remote: options.filePath,
28
+ local: options.outputPath,
29
+ }, { format: options.output, defaultFormat: "json" });
30
+ }
31
+ }
32
+ catch (error) {
33
+ outputError("Failed to download file", error);
34
+ }
49
35
  }
@@ -1,45 +1,28 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
- import { Box, Text } from "ink";
1
+ /**
2
+ * Execute command in devbox
3
+ */
4
4
  import { getClient } from "../../utils/client.js";
5
- import { Header } from "../../components/Header.js";
6
- import { SpinnerComponent } from "../../components/Spinner.js";
7
- import { ErrorMessage } from "../../components/ErrorMessage.js";
8
- import { colors } from "../../utils/theme.js";
9
- import { createExecutor } from "../../utils/CommandExecutor.js";
10
- const ExecCommandUI = ({ id, command, }) => {
11
- const [loading, setLoading] = React.useState(true);
12
- const [output, setOutput] = React.useState("");
13
- const [error, setError] = React.useState(null);
14
- React.useEffect(() => {
15
- const exec = async () => {
16
- try {
17
- const client = getClient();
18
- const result = await client.devboxes.executeSync(id, {
19
- command: command.join(" "),
20
- });
21
- setOutput(result.stdout || result.stderr || "Command executed successfully");
22
- }
23
- catch (err) {
24
- setError(err);
25
- }
26
- finally {
27
- setLoading(false);
28
- }
29
- };
30
- exec();
31
- }, []);
32
- return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Execute Command", subtitle: `Running in devbox: ${id}` }), loading && _jsx(SpinnerComponent, { message: "Executing command..." }), !loading && !error && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Box, { borderStyle: "round", borderColor: colors.success, padding: 1, children: _jsx(Text, { children: output }) }) })), error && (_jsx(ErrorMessage, { message: "Failed to execute command", error: error }))] }));
33
- };
5
+ import { output, outputError } from "../../utils/output.js";
34
6
  export async function execCommand(id, command, options = {}) {
35
- const executor = createExecutor({ output: options.output });
36
- await executor.executeAction(async () => {
37
- const client = executor.getClient();
7
+ try {
8
+ const client = getClient();
38
9
  const result = await client.devboxes.executeSync(id, {
39
10
  command: command.join(" "),
11
+ shell_name: options.shellName || undefined,
40
12
  });
41
- return {
42
- result: result.stdout || result.stderr || "Command executed successfully",
43
- };
44
- }, () => _jsx(ExecCommandUI, { id: id, command: command }));
13
+ // For text output, just print stdout/stderr directly
14
+ if (!options.output || options.output === "text") {
15
+ if (result.stdout) {
16
+ console.log(result.stdout);
17
+ }
18
+ if (result.stderr) {
19
+ console.error(result.stderr);
20
+ }
21
+ return;
22
+ }
23
+ output(result, { format: options.output, defaultFormat: "json" });
24
+ }
25
+ catch (error) {
26
+ outputError("Failed to execute command", error);
27
+ }
45
28
  }
@@ -1,43 +1,26 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
1
+ /**
2
+ * Execute command asynchronously in devbox
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 ExecAsyncUI = ({ devboxId, command, shellName }) => {
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 execAsync = async () => {
15
- try {
16
- const client = getClient();
17
- const execution = await client.devboxes.executeAsync(devboxId, {
18
- command,
19
- shell_name: shellName || undefined,
20
- });
21
- setResult(execution);
22
- }
23
- catch (err) {
24
- setError(err);
25
- }
26
- finally {
27
- setLoading(false);
28
- }
29
- };
30
- execAsync();
31
- }, [devboxId, command, shellName]);
32
- return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Starting async execution..." }), result && (_jsx(SuccessMessage, { message: "Async execution started", details: `Execution ID: ${result.id}\nCommand: ${command}\nStatus: ${result.status}` })), error && (_jsx(ErrorMessage, { message: "Failed to start async execution", error: error }))] }));
33
- };
5
+ import { output, outputError } from "../../utils/output.js";
34
6
  export async function execAsync(devboxId, options) {
35
- const executor = createExecutor({ output: options.output });
36
- await executor.executeAction(async () => {
37
- const client = executor.getClient();
38
- return client.devboxes.executeAsync(devboxId, {
7
+ try {
8
+ const client = getClient();
9
+ const execution = await client.devboxes.executeAsync(devboxId, {
39
10
  command: options.command,
40
11
  shell_name: options.shellName || undefined,
41
12
  });
42
- }, () => (_jsx(ExecAsyncUI, { devboxId: devboxId, command: options.command, shellName: options.shellName })));
13
+ // Default: just output the execution ID for easy scripting
14
+ if (!options.output || options.output === "text") {
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ const execId = execution.execution_id || execution.id;
17
+ console.log(execId);
18
+ }
19
+ else {
20
+ output(execution, { format: options.output, defaultFormat: "json" });
21
+ }
22
+ }
23
+ catch (error) {
24
+ outputError("Failed to start async execution", error);
25
+ }
43
26
  }
@@ -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
+ * Get devbox details 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 GetDevboxUI = ({ 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 getDevbox = async () => {
15
- try {
16
- const client = getClient();
17
- const devbox = await client.devboxes.retrieve(devboxId);
18
- setResult(devbox);
19
- }
20
- catch (err) {
21
- setError(err);
22
- }
23
- finally {
24
- setLoading(false);
25
- }
26
- };
27
- getDevbox();
28
- }, [devboxId]);
29
- return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Fetching devbox details..." }), result && (_jsx(SuccessMessage, { message: "Devbox details retrieved", details: `ID: ${result.id}\nStatus: ${result.status}\nCreated: ${new Date(result.createdAt).toLocaleString()}` })), error && _jsx(ErrorMessage, { message: "Failed to get devbox", error: error })] }));
30
- };
31
- export async function getDevbox(devboxId, options) {
32
- const executor = createExecutor(options);
33
- await executor.executeAction(async () => {
34
- const client = executor.getClient();
35
- return client.devboxes.retrieve(devboxId);
36
- }, () => _jsx(GetDevboxUI, { devboxId: devboxId }));
5
+ import { output, outputError } from "../../utils/output.js";
6
+ export async function getDevbox(devboxId, options = {}) {
7
+ try {
8
+ const client = getClient();
9
+ const devbox = await client.devboxes.retrieve(devboxId);
10
+ output(devbox, { format: options.output, defaultFormat: "json" });
11
+ }
12
+ catch (error) {
13
+ outputError("Failed to get devbox", error);
14
+ }
37
15
  }
@@ -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
+ * Get async execution status
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 GetAsyncUI = ({ devboxId, executionId }) => {
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 getAsync = async () => {
15
- try {
16
- const client = getClient();
17
- const execution = await client.devboxes.executions.retrieve(executionId, devboxId);
18
- setResult(execution);
19
- }
20
- catch (err) {
21
- setError(err);
22
- }
23
- finally {
24
- setLoading(false);
25
- }
26
- };
27
- getAsync();
28
- }, [devboxId, executionId]);
29
- return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && (_jsx(SpinnerComponent, { message: "Getting async execution status..." })), result && (_jsx(SuccessMessage, { message: "Async execution status retrieved", details: `Execution ID: ${result.id}\nStatus: ${result.status}\nCommand: ${result.command}` })), error && (_jsx(ErrorMessage, { message: "Failed to get async execution status", error: error }))] }));
30
- };
5
+ import { output, outputError } from "../../utils/output.js";
31
6
  export async function getAsync(devboxId, options) {
32
- const executor = createExecutor({ output: options.output });
33
- await executor.executeAction(async () => {
34
- const client = executor.getClient();
35
- return client.devboxes.executions.retrieve(devboxId, options.executionId);
36
- }, () => _jsx(GetAsyncUI, { devboxId: devboxId, executionId: options.executionId }));
7
+ try {
8
+ const client = getClient();
9
+ const execution = await client.devboxes.executions.retrieve(devboxId, options.executionId);
10
+ output(execution, { format: options.output, defaultFormat: "json" });
11
+ }
12
+ catch (error) {
13
+ outputError("Failed to get async execution status", error);
14
+ }
37
15
  }