@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.
- package/README.md +64 -29
- package/dist/cli.js +401 -92
- package/dist/commands/auth.js +12 -11
- package/dist/commands/blueprint/create.js +108 -0
- package/dist/commands/blueprint/get.js +37 -0
- package/dist/commands/blueprint/list.js +293 -225
- package/dist/commands/blueprint/logs.js +40 -0
- package/dist/commands/blueprint/preview.js +45 -0
- package/dist/commands/devbox/create.js +10 -9
- package/dist/commands/devbox/delete.js +8 -8
- package/dist/commands/devbox/download.js +49 -0
- package/dist/commands/devbox/exec.js +23 -13
- package/dist/commands/devbox/execAsync.js +43 -0
- package/dist/commands/devbox/get.js +37 -0
- package/dist/commands/devbox/getAsync.js +37 -0
- package/dist/commands/devbox/list.js +328 -190
- package/dist/commands/devbox/logs.js +40 -0
- package/dist/commands/devbox/read.js +49 -0
- package/dist/commands/devbox/resume.js +37 -0
- package/dist/commands/devbox/rsync.js +118 -0
- package/dist/commands/devbox/scp.js +122 -0
- package/dist/commands/devbox/shutdown.js +37 -0
- package/dist/commands/devbox/ssh.js +104 -0
- package/dist/commands/devbox/suspend.js +37 -0
- package/dist/commands/devbox/tunnel.js +120 -0
- package/dist/commands/devbox/upload.js +10 -10
- package/dist/commands/devbox/write.js +51 -0
- package/dist/commands/mcp-http.js +37 -0
- package/dist/commands/mcp-install.js +120 -0
- package/dist/commands/mcp.js +30 -0
- package/dist/commands/menu.js +20 -20
- package/dist/commands/object/delete.js +37 -0
- package/dist/commands/object/download.js +88 -0
- package/dist/commands/object/get.js +37 -0
- package/dist/commands/object/list.js +112 -0
- package/dist/commands/object/upload.js +130 -0
- package/dist/commands/snapshot/create.js +12 -11
- package/dist/commands/snapshot/delete.js +8 -8
- package/dist/commands/snapshot/list.js +56 -97
- package/dist/commands/snapshot/status.js +37 -0
- package/dist/components/ActionsPopup.js +16 -13
- package/dist/components/Banner.js +4 -4
- package/dist/components/Breadcrumb.js +55 -5
- package/dist/components/DetailView.js +7 -4
- package/dist/components/DevboxActionsMenu.js +315 -178
- package/dist/components/DevboxCard.js +15 -14
- package/dist/components/DevboxCreatePage.js +147 -113
- package/dist/components/DevboxDetailPage.js +180 -102
- package/dist/components/ErrorMessage.js +5 -4
- package/dist/components/Header.js +4 -3
- package/dist/components/MainMenu.js +34 -33
- package/dist/components/MetadataDisplay.js +17 -9
- package/dist/components/OperationsMenu.js +6 -5
- package/dist/components/ResourceActionsMenu.js +117 -0
- package/dist/components/ResourceListView.js +213 -0
- package/dist/components/Spinner.js +5 -4
- package/dist/components/StatusBadge.js +81 -31
- package/dist/components/SuccessMessage.js +4 -3
- package/dist/components/Table.example.js +53 -23
- package/dist/components/Table.js +19 -11
- package/dist/hooks/useCursorPagination.js +125 -0
- package/dist/mcp/server-http.js +416 -0
- package/dist/mcp/server.js +397 -0
- package/dist/utils/CommandExecutor.js +16 -12
- package/dist/utils/client.js +7 -7
- package/dist/utils/config.js +130 -4
- package/dist/utils/interactiveCommand.js +2 -2
- package/dist/utils/output.js +17 -17
- package/dist/utils/ssh.js +160 -0
- package/dist/utils/sshSession.js +16 -12
- package/dist/utils/theme.js +22 -0
- package/dist/utils/url.js +4 -4
- package/package.json +29 -4
package/dist/cli.js
CHANGED
|
@@ -1,156 +1,465 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from
|
|
3
|
-
import { createDevbox } from
|
|
4
|
-
import { listDevboxes } from
|
|
5
|
-
import { deleteDevbox } from
|
|
6
|
-
import { execCommand } from
|
|
7
|
-
import { uploadFile } from
|
|
8
|
-
import { getConfig } from
|
|
9
|
-
import { readFileSync } from
|
|
10
|
-
import { fileURLToPath } from
|
|
11
|
-
import { dirname, join } from
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { createDevbox } from "./commands/devbox/create.js";
|
|
4
|
+
import { listDevboxes } from "./commands/devbox/list.js";
|
|
5
|
+
import { deleteDevbox } from "./commands/devbox/delete.js";
|
|
6
|
+
import { execCommand } from "./commands/devbox/exec.js";
|
|
7
|
+
import { uploadFile } from "./commands/devbox/upload.js";
|
|
8
|
+
import { getConfig } from "./utils/config.js";
|
|
9
|
+
import { readFileSync } from "fs";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { dirname, join } from "path";
|
|
12
12
|
// Get version from package.json
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
14
|
const __dirname = dirname(__filename);
|
|
15
|
-
const packageJson = JSON.parse(readFileSync(join(__dirname,
|
|
15
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8"));
|
|
16
16
|
export const VERSION = packageJson.version;
|
|
17
17
|
// Global Ctrl+C handler to ensure it always exits
|
|
18
|
-
process.on(
|
|
18
|
+
process.on("SIGINT", () => {
|
|
19
19
|
// Force exit immediately, clearing alternate screen buffer
|
|
20
|
-
process.stdout.write(
|
|
21
|
-
process.stdout.write(
|
|
20
|
+
process.stdout.write("\x1b[?1049l");
|
|
21
|
+
process.stdout.write("\n");
|
|
22
22
|
process.exit(130); // Standard exit code for SIGINT
|
|
23
23
|
});
|
|
24
24
|
const program = new Command();
|
|
25
25
|
program
|
|
26
|
-
.name(
|
|
27
|
-
.description(
|
|
26
|
+
.name("rli")
|
|
27
|
+
.description("Beautiful CLI for Runloop devbox management")
|
|
28
28
|
.version(VERSION);
|
|
29
29
|
program
|
|
30
|
-
.command(
|
|
31
|
-
.description(
|
|
30
|
+
.command("auth")
|
|
31
|
+
.description("Configure API authentication")
|
|
32
32
|
.action(async () => {
|
|
33
|
-
const { default: auth } = await import(
|
|
33
|
+
const { default: auth } = await import("./commands/auth.js");
|
|
34
34
|
auth();
|
|
35
35
|
});
|
|
36
|
+
program
|
|
37
|
+
.command("check-updates")
|
|
38
|
+
.description("Check for CLI updates")
|
|
39
|
+
.action(async () => {
|
|
40
|
+
const { checkForUpdates } = await import("./utils/config.js");
|
|
41
|
+
console.log("Checking for updates...");
|
|
42
|
+
await checkForUpdates(true);
|
|
43
|
+
});
|
|
36
44
|
// Devbox commands
|
|
37
45
|
const devbox = program
|
|
38
|
-
.command(
|
|
39
|
-
.description(
|
|
40
|
-
.alias(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
.command("devbox")
|
|
47
|
+
.description("Manage devboxes")
|
|
48
|
+
.alias("d")
|
|
49
|
+
.action(async () => {
|
|
50
|
+
// Open interactive devbox list when no subcommand provided
|
|
51
|
+
const { runInteractiveCommand } = await import("./utils/interactiveCommand.js");
|
|
52
|
+
const { listDevboxes } = await import("./commands/devbox/list.js");
|
|
53
|
+
await runInteractiveCommand(() => listDevboxes({ output: "interactive" }));
|
|
54
|
+
});
|
|
55
|
+
devbox
|
|
56
|
+
.command("create")
|
|
57
|
+
.description("Create a new devbox")
|
|
58
|
+
.option("-n, --name <name>", "Devbox name")
|
|
59
|
+
.option("-t, --template <template>", "Template to use")
|
|
60
|
+
.option("--blueprint <blueprint>", "Blueprint ID to use")
|
|
61
|
+
.option("--resources <size>", "Resource size (X_SMALL, SMALL, MEDIUM, LARGE, X_LARGE, XX_LARGE)")
|
|
62
|
+
.option("--architecture <arch>", "Architecture (arm64, x86_64)")
|
|
63
|
+
.option("--entrypoint <command>", "Entrypoint command to run")
|
|
64
|
+
.option("--available-ports <ports...>", "Available ports")
|
|
65
|
+
.option("--root", "Run as root")
|
|
66
|
+
.option("--user <user:uid>", "Run as this user (format: username:uid)")
|
|
67
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
47
68
|
.action(createDevbox);
|
|
48
69
|
devbox
|
|
49
|
-
.command(
|
|
50
|
-
.description(
|
|
51
|
-
.option(
|
|
52
|
-
.option(
|
|
70
|
+
.command("list")
|
|
71
|
+
.description("List all devboxes")
|
|
72
|
+
.option("-s, --status <status>", "Filter by status")
|
|
73
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
53
74
|
.action(async (options) => {
|
|
54
|
-
|
|
55
|
-
if (!options.output) {
|
|
56
|
-
const { runInteractiveCommand } = await import('./utils/interactiveCommand.js');
|
|
57
|
-
await runInteractiveCommand(() => listDevboxes(options));
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
await listDevboxes(options);
|
|
61
|
-
}
|
|
75
|
+
await listDevboxes(options);
|
|
62
76
|
});
|
|
63
77
|
devbox
|
|
64
|
-
.command(
|
|
65
|
-
.description(
|
|
66
|
-
.alias(
|
|
67
|
-
.option(
|
|
78
|
+
.command("delete <id>")
|
|
79
|
+
.description("Shutdown a devbox")
|
|
80
|
+
.alias("rm")
|
|
81
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
68
82
|
.action(deleteDevbox);
|
|
69
83
|
devbox
|
|
70
|
-
.command(
|
|
71
|
-
.description(
|
|
72
|
-
.option(
|
|
73
|
-
.action(
|
|
84
|
+
.command("exec <id> <command...>")
|
|
85
|
+
.description("Execute a command in a devbox")
|
|
86
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
87
|
+
.action(async (id, command, options) => {
|
|
88
|
+
await execCommand(id, command, options);
|
|
89
|
+
});
|
|
74
90
|
devbox
|
|
75
|
-
.command(
|
|
76
|
-
.description(
|
|
77
|
-
.option(
|
|
78
|
-
.option(
|
|
91
|
+
.command("upload <id> <file>")
|
|
92
|
+
.description("Upload a file to a devbox")
|
|
93
|
+
.option("-p, --path <path>", "Target path in devbox")
|
|
94
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
79
95
|
.action(uploadFile);
|
|
96
|
+
// Additional devbox commands
|
|
97
|
+
devbox
|
|
98
|
+
.command("get <id>")
|
|
99
|
+
.description("Get devbox details")
|
|
100
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
101
|
+
.action(async (id, options) => {
|
|
102
|
+
const { getDevbox } = await import("./commands/devbox/get.js");
|
|
103
|
+
await getDevbox(id, options);
|
|
104
|
+
});
|
|
105
|
+
devbox
|
|
106
|
+
.command("suspend <id>")
|
|
107
|
+
.description("Suspend a devbox")
|
|
108
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
109
|
+
.action(async (id, options) => {
|
|
110
|
+
const { suspendDevbox } = await import("./commands/devbox/suspend.js");
|
|
111
|
+
await suspendDevbox(id, options);
|
|
112
|
+
});
|
|
113
|
+
devbox
|
|
114
|
+
.command("resume <id>")
|
|
115
|
+
.description("Resume a suspended devbox")
|
|
116
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
117
|
+
.action(async (id, options) => {
|
|
118
|
+
const { resumeDevbox } = await import("./commands/devbox/resume.js");
|
|
119
|
+
await resumeDevbox(id, options);
|
|
120
|
+
});
|
|
121
|
+
devbox
|
|
122
|
+
.command("shutdown <id>")
|
|
123
|
+
.description("Shutdown a devbox")
|
|
124
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
125
|
+
.action(async (id, options) => {
|
|
126
|
+
const { shutdownDevbox } = await import("./commands/devbox/shutdown.js");
|
|
127
|
+
await shutdownDevbox(id, options);
|
|
128
|
+
});
|
|
129
|
+
devbox
|
|
130
|
+
.command("ssh <id>")
|
|
131
|
+
.description("SSH into a devbox")
|
|
132
|
+
.option("--config-only", "Print SSH config only")
|
|
133
|
+
.option("--no-wait", "Do not wait for devbox to be ready")
|
|
134
|
+
.option("--timeout <seconds>", "Timeout in seconds to wait for readiness", "180")
|
|
135
|
+
.option("--poll-interval <seconds>", "Polling interval in seconds while waiting", "3")
|
|
136
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
137
|
+
.action(async (id, options) => {
|
|
138
|
+
const { sshDevbox } = await import("./commands/devbox/ssh.js");
|
|
139
|
+
await sshDevbox(id, options);
|
|
140
|
+
});
|
|
141
|
+
devbox
|
|
142
|
+
.command("scp <id> <src> <dst>")
|
|
143
|
+
.description("Copy files to/from a devbox using scp")
|
|
144
|
+
.option("--scp-options <options>", "Additional scp options (quoted)")
|
|
145
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
146
|
+
.action(async (id, src, dst, options) => {
|
|
147
|
+
const { scpFiles } = await import("./commands/devbox/scp.js");
|
|
148
|
+
await scpFiles(id, { src, dst, ...options });
|
|
149
|
+
});
|
|
150
|
+
devbox
|
|
151
|
+
.command("rsync <id> <src> <dst>")
|
|
152
|
+
.description("Sync files to/from a devbox using rsync")
|
|
153
|
+
.option("--rsync-options <options>", "Additional rsync options (quoted)")
|
|
154
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
155
|
+
.action(async (id, src, dst, options) => {
|
|
156
|
+
const { rsyncFiles } = await import("./commands/devbox/rsync.js");
|
|
157
|
+
await rsyncFiles(id, { src, dst, ...options });
|
|
158
|
+
});
|
|
159
|
+
devbox
|
|
160
|
+
.command("tunnel <id> <ports>")
|
|
161
|
+
.description("Create a port-forwarding tunnel to a devbox")
|
|
162
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
163
|
+
.action(async (id, ports, options) => {
|
|
164
|
+
const { createTunnel } = await import("./commands/devbox/tunnel.js");
|
|
165
|
+
await createTunnel(id, { ports, ...options });
|
|
166
|
+
});
|
|
167
|
+
devbox
|
|
168
|
+
.command("read <id>")
|
|
169
|
+
.description("Read a file from a devbox using the API")
|
|
170
|
+
.option("--remote <path>", "Remote file path to read from the devbox")
|
|
171
|
+
.option("--output-path <path>", "Local file path to write the contents to")
|
|
172
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
173
|
+
.action(async (id, options) => {
|
|
174
|
+
const { readFile } = await import("./commands/devbox/read.js");
|
|
175
|
+
await readFile(id, options);
|
|
176
|
+
});
|
|
177
|
+
devbox
|
|
178
|
+
.command("write <id>")
|
|
179
|
+
.description("Write a file to a devbox using the API")
|
|
180
|
+
.option("--input <path>", "Local file path to read contents from")
|
|
181
|
+
.option("--remote <path>", "Remote file path to write to on the devbox")
|
|
182
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
183
|
+
.action(async (id, options) => {
|
|
184
|
+
const { writeFile } = await import("./commands/devbox/write.js");
|
|
185
|
+
await writeFile(id, options);
|
|
186
|
+
});
|
|
187
|
+
devbox
|
|
188
|
+
.command("download <id>")
|
|
189
|
+
.description("Download a file from a devbox")
|
|
190
|
+
.option("--file-path <path>", "Path to the file in the devbox")
|
|
191
|
+
.option("--output-path <path>", "Local path where to save the downloaded file")
|
|
192
|
+
.option("-o, --output-format [format]", "Output format: text|json|yaml (default: interactive)")
|
|
193
|
+
.action(async (id, options) => {
|
|
194
|
+
const { downloadFile } = await import("./commands/devbox/download.js");
|
|
195
|
+
await downloadFile(id, options);
|
|
196
|
+
});
|
|
197
|
+
devbox
|
|
198
|
+
.command("exec-async <id> <command...>")
|
|
199
|
+
.description("Execute a command asynchronously on a devbox")
|
|
200
|
+
.option("--shell-name <name>", "Shell name to use (optional)")
|
|
201
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
202
|
+
.action(async (id, command, options) => {
|
|
203
|
+
const { execAsync } = await import("./commands/devbox/execAsync.js");
|
|
204
|
+
await execAsync(id, { command: command.join(" "), ...options });
|
|
205
|
+
});
|
|
206
|
+
devbox
|
|
207
|
+
.command("get-async <id> <execution-id>")
|
|
208
|
+
.description("Get status of an async execution")
|
|
209
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
210
|
+
.action(async (id, executionId, options) => {
|
|
211
|
+
const { getAsync } = await import("./commands/devbox/getAsync.js");
|
|
212
|
+
await getAsync(id, { executionId, ...options });
|
|
213
|
+
});
|
|
214
|
+
devbox
|
|
215
|
+
.command("logs <id>")
|
|
216
|
+
.description("View devbox logs")
|
|
217
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
218
|
+
.action(async (id, options) => {
|
|
219
|
+
const { getLogs } = await import("./commands/devbox/logs.js");
|
|
220
|
+
await getLogs(id, options);
|
|
221
|
+
});
|
|
80
222
|
// Snapshot commands
|
|
81
223
|
const snapshot = program
|
|
82
|
-
.command(
|
|
83
|
-
.description(
|
|
84
|
-
.alias(
|
|
224
|
+
.command("snapshot")
|
|
225
|
+
.description("Manage devbox snapshots")
|
|
226
|
+
.alias("snap")
|
|
227
|
+
.action(async () => {
|
|
228
|
+
// Open interactive snapshot list when no subcommand provided
|
|
229
|
+
const { runInteractiveCommand } = await import("./utils/interactiveCommand.js");
|
|
230
|
+
const { listSnapshots } = await import("./commands/snapshot/list.js");
|
|
231
|
+
await runInteractiveCommand(() => listSnapshots({ output: "interactive" }));
|
|
232
|
+
});
|
|
85
233
|
snapshot
|
|
86
|
-
.command(
|
|
87
|
-
.description(
|
|
88
|
-
.option(
|
|
89
|
-
.option(
|
|
234
|
+
.command("list")
|
|
235
|
+
.description("List all snapshots")
|
|
236
|
+
.option("-d, --devbox <id>", "Filter by devbox ID")
|
|
237
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
90
238
|
.action(async (options) => {
|
|
91
|
-
const { listSnapshots } = await import(
|
|
92
|
-
|
|
93
|
-
const { runInteractiveCommand } = await import('./utils/interactiveCommand.js');
|
|
94
|
-
await runInteractiveCommand(() => listSnapshots(options));
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
await listSnapshots(options);
|
|
98
|
-
}
|
|
239
|
+
const { listSnapshots } = await import("./commands/snapshot/list.js");
|
|
240
|
+
await listSnapshots(options);
|
|
99
241
|
});
|
|
100
242
|
snapshot
|
|
101
|
-
.command(
|
|
102
|
-
.description(
|
|
103
|
-
.option(
|
|
104
|
-
.option(
|
|
243
|
+
.command("create <devbox-id>")
|
|
244
|
+
.description("Create a snapshot of a devbox")
|
|
245
|
+
.option("-n, --name <name>", "Snapshot name")
|
|
246
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
105
247
|
.action(async (devboxId, options) => {
|
|
106
|
-
const { createSnapshot } = await import(
|
|
248
|
+
const { createSnapshot } = await import("./commands/snapshot/create.js");
|
|
107
249
|
createSnapshot(devboxId, options);
|
|
108
250
|
});
|
|
109
251
|
snapshot
|
|
110
|
-
.command(
|
|
111
|
-
.description(
|
|
112
|
-
.alias(
|
|
113
|
-
.option(
|
|
252
|
+
.command("delete <id>")
|
|
253
|
+
.description("Delete a snapshot")
|
|
254
|
+
.alias("rm")
|
|
255
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
114
256
|
.action(async (id, options) => {
|
|
115
|
-
const { deleteSnapshot } = await import(
|
|
257
|
+
const { deleteSnapshot } = await import("./commands/snapshot/delete.js");
|
|
116
258
|
deleteSnapshot(id, options);
|
|
117
259
|
});
|
|
260
|
+
snapshot
|
|
261
|
+
.command("status <snapshot-id>")
|
|
262
|
+
.description("Get snapshot operation status")
|
|
263
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
264
|
+
.action(async (snapshotId, options) => {
|
|
265
|
+
const { getSnapshotStatus } = await import("./commands/snapshot/status.js");
|
|
266
|
+
await getSnapshotStatus({ snapshotId, ...options });
|
|
267
|
+
});
|
|
118
268
|
// Blueprint commands
|
|
119
269
|
const blueprint = program
|
|
120
|
-
.command(
|
|
121
|
-
.description(
|
|
122
|
-
.alias(
|
|
270
|
+
.command("blueprint")
|
|
271
|
+
.description("Manage blueprints")
|
|
272
|
+
.alias("bp")
|
|
273
|
+
.action(async () => {
|
|
274
|
+
// Open interactive blueprint list when no subcommand provided
|
|
275
|
+
const { runInteractiveCommand } = await import("./utils/interactiveCommand.js");
|
|
276
|
+
const { listBlueprints } = await import("./commands/blueprint/list.js");
|
|
277
|
+
await runInteractiveCommand(() => listBlueprints({ output: "interactive" }));
|
|
278
|
+
});
|
|
279
|
+
blueprint
|
|
280
|
+
.command("list")
|
|
281
|
+
.description("List all blueprints")
|
|
282
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
283
|
+
.action(async (options) => {
|
|
284
|
+
const { listBlueprints } = await import("./commands/blueprint/list.js");
|
|
285
|
+
await listBlueprints(options);
|
|
286
|
+
});
|
|
287
|
+
blueprint
|
|
288
|
+
.command("create <name>")
|
|
289
|
+
.description("Create a new blueprint")
|
|
290
|
+
.option("--dockerfile <content>", "Dockerfile contents")
|
|
291
|
+
.option("--dockerfile-path <path>", "Dockerfile path")
|
|
292
|
+
.option("--system-setup-commands <commands...>", "System setup commands")
|
|
293
|
+
.option("--resources <size>", "Resource size (X_SMALL, SMALL, MEDIUM, LARGE, X_LARGE, XX_LARGE)")
|
|
294
|
+
.option("--architecture <arch>", "Architecture (arm64, x86_64)")
|
|
295
|
+
.option("--available-ports <ports...>", "Available ports")
|
|
296
|
+
.option("--root", "Run as root")
|
|
297
|
+
.option("--user <user:uid>", "Run as this user (format: username:uid)")
|
|
298
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
299
|
+
.action(async (name, options) => {
|
|
300
|
+
const { createBlueprint } = await import("./commands/blueprint/create.js");
|
|
301
|
+
await createBlueprint({ name, ...options });
|
|
302
|
+
});
|
|
303
|
+
blueprint
|
|
304
|
+
.command("preview <name>")
|
|
305
|
+
.description("Preview blueprint before creation")
|
|
306
|
+
.option("--dockerfile <content>", "Dockerfile contents")
|
|
307
|
+
.option("--system-setup-commands <commands...>", "System setup commands")
|
|
308
|
+
.option("--resources <size>", "Resource size (X_SMALL, SMALL, MEDIUM, LARGE, X_LARGE, XX_LARGE)")
|
|
309
|
+
.option("--architecture <arch>", "Architecture (arm64, x86_64)")
|
|
310
|
+
.option("--available-ports <ports...>", "Available ports")
|
|
311
|
+
.option("--root", "Run as root")
|
|
312
|
+
.option("--user <user:uid>", "Run as this user (format: username:uid)")
|
|
313
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
314
|
+
.action(async (name, options) => {
|
|
315
|
+
const { previewBlueprint } = await import("./commands/blueprint/preview.js");
|
|
316
|
+
await previewBlueprint({ name, ...options });
|
|
317
|
+
});
|
|
318
|
+
blueprint
|
|
319
|
+
.command("get <id>")
|
|
320
|
+
.description("Get blueprint details")
|
|
321
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
322
|
+
.action(async (id, options) => {
|
|
323
|
+
const { getBlueprint } = await import("./commands/blueprint/get.js");
|
|
324
|
+
await getBlueprint({ id, ...options });
|
|
325
|
+
});
|
|
123
326
|
blueprint
|
|
124
|
-
.command(
|
|
125
|
-
.description(
|
|
126
|
-
.option(
|
|
327
|
+
.command("logs <id>")
|
|
328
|
+
.description("Get blueprint build logs")
|
|
329
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
330
|
+
.action(async (id, options) => {
|
|
331
|
+
const { getBlueprintLogs } = await import("./commands/blueprint/logs.js");
|
|
332
|
+
await getBlueprintLogs({ id, ...options });
|
|
333
|
+
});
|
|
334
|
+
// Object storage commands
|
|
335
|
+
const object = program
|
|
336
|
+
.command("object")
|
|
337
|
+
.description("Manage object storage")
|
|
338
|
+
.alias("obj");
|
|
339
|
+
object
|
|
340
|
+
.command("list")
|
|
341
|
+
.description("List objects")
|
|
342
|
+
.option("--limit <n>", "Max results", "20")
|
|
343
|
+
.option("--starting-after <id>", "Starting point for pagination")
|
|
344
|
+
.option("--name <name>", "Filter by name (partial match supported)")
|
|
345
|
+
.option("--content-type <type>", "Filter by content type")
|
|
346
|
+
.option("--state <state>", "Filter by state (UPLOADING, READ_ONLY, DELETED)")
|
|
347
|
+
.option("--search <query>", "Search by object ID or name")
|
|
348
|
+
.option("--public", "List public objects only")
|
|
349
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
127
350
|
.action(async (options) => {
|
|
128
|
-
const {
|
|
351
|
+
const { listObjects } = await import("./commands/object/list.js");
|
|
352
|
+
await listObjects(options);
|
|
353
|
+
});
|
|
354
|
+
object
|
|
355
|
+
.command("get <id>")
|
|
356
|
+
.description("Get object details")
|
|
357
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
358
|
+
.action(async (id, options) => {
|
|
359
|
+
const { getObject } = await import("./commands/object/get.js");
|
|
360
|
+
await getObject({ id, ...options });
|
|
361
|
+
});
|
|
362
|
+
object
|
|
363
|
+
.command("download <id> <path>")
|
|
364
|
+
.description("Download object to local file")
|
|
365
|
+
.option("--extract", "Extract downloaded archive after download")
|
|
366
|
+
.option("--duration-seconds <seconds>", "Duration in seconds for the presigned URL validity", "3600")
|
|
367
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
368
|
+
.action(async (id, path, options) => {
|
|
369
|
+
const { downloadObject } = await import("./commands/object/download.js");
|
|
370
|
+
await downloadObject({ id, path, ...options });
|
|
371
|
+
});
|
|
372
|
+
object
|
|
373
|
+
.command("upload <path>")
|
|
374
|
+
.description("Upload a file as an object")
|
|
375
|
+
.option("--name <name>", "Object name (required)")
|
|
376
|
+
.option("--content-type <type>", "Content type: unspecified|text|binary|gzip|tar|tgz")
|
|
377
|
+
.option("--public", "Make object publicly accessible")
|
|
378
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
379
|
+
.action(async (path, options) => {
|
|
380
|
+
const { uploadObject } = await import("./commands/object/upload.js");
|
|
129
381
|
if (!options.output) {
|
|
130
|
-
const { runInteractiveCommand } = await import(
|
|
131
|
-
await runInteractiveCommand(() =>
|
|
382
|
+
const { runInteractiveCommand } = await import("./utils/interactiveCommand.js");
|
|
383
|
+
await runInteractiveCommand(() => uploadObject({ path, ...options }));
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
await uploadObject({ path, ...options });
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
object
|
|
390
|
+
.command("delete <id>")
|
|
391
|
+
.description("Delete an object (irreversible)")
|
|
392
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
393
|
+
.action(async (id, options) => {
|
|
394
|
+
const { deleteObject } = await import("./commands/object/delete.js");
|
|
395
|
+
await deleteObject({ id, ...options });
|
|
396
|
+
});
|
|
397
|
+
// MCP server commands
|
|
398
|
+
const mcp = program
|
|
399
|
+
.command("mcp")
|
|
400
|
+
.description("Model Context Protocol (MCP) server commands");
|
|
401
|
+
mcp
|
|
402
|
+
.command("start")
|
|
403
|
+
.description("Start the MCP server")
|
|
404
|
+
.option("--http", "Use HTTP/SSE transport instead of stdio")
|
|
405
|
+
.option("-p, --port <port>", "Port to listen on for HTTP mode (default: 3000)", parseInt)
|
|
406
|
+
.action(async (options) => {
|
|
407
|
+
if (options.http) {
|
|
408
|
+
const { startMcpHttpServer } = await import("./commands/mcp-http.js");
|
|
409
|
+
await startMcpHttpServer(options.port);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
const { startMcpServer } = await import("./commands/mcp.js");
|
|
413
|
+
await startMcpServer();
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
mcp
|
|
417
|
+
.command("install")
|
|
418
|
+
.description("Install Runloop MCP server configuration in Claude Desktop")
|
|
419
|
+
.action(async () => {
|
|
420
|
+
const { installMcpConfig } = await import("./commands/mcp-install.js");
|
|
421
|
+
await installMcpConfig();
|
|
422
|
+
});
|
|
423
|
+
// Hidden command: 'rli mcp' without subcommand starts the server (for Claude Desktop config compatibility)
|
|
424
|
+
program
|
|
425
|
+
.command("mcp-server", { hidden: true })
|
|
426
|
+
.option("--http", "Use HTTP/SSE transport instead of stdio")
|
|
427
|
+
.option("-p, --port <port>", "Port to listen on for HTTP mode (default: 3000)", parseInt)
|
|
428
|
+
.action(async (options) => {
|
|
429
|
+
if (options.http) {
|
|
430
|
+
const { startMcpHttpServer } = await import("./commands/mcp-http.js");
|
|
431
|
+
await startMcpHttpServer(options.port);
|
|
132
432
|
}
|
|
133
433
|
else {
|
|
134
|
-
await
|
|
434
|
+
const { startMcpServer } = await import("./commands/mcp.js");
|
|
435
|
+
await startMcpServer();
|
|
135
436
|
}
|
|
136
437
|
});
|
|
137
438
|
// Main CLI entry point
|
|
138
439
|
(async () => {
|
|
139
|
-
// Check if API key is configured (except for auth
|
|
440
|
+
// Check if API key is configured (except for auth and mcp commands)
|
|
140
441
|
const args = process.argv.slice(2);
|
|
141
|
-
if (args[0] !==
|
|
442
|
+
if (args[0] !== "auth" &&
|
|
443
|
+
args[0] !== "mcp" &&
|
|
444
|
+
args[0] !== "mcp-server" &&
|
|
445
|
+
args[0] !== "--help" &&
|
|
446
|
+
args[0] !== "-h" &&
|
|
447
|
+
args.length > 0) {
|
|
142
448
|
const config = getConfig();
|
|
143
449
|
if (!config.apiKey) {
|
|
144
|
-
console.error(
|
|
450
|
+
console.error("\n❌ API key not configured. Run: rli auth\n");
|
|
145
451
|
process.exit(1);
|
|
146
452
|
}
|
|
147
453
|
}
|
|
148
|
-
// If no command provided, show main menu
|
|
454
|
+
// If no command provided, show main menu (version check handled in UI)
|
|
149
455
|
if (args.length === 0) {
|
|
150
|
-
const { runMainMenu } = await import(
|
|
456
|
+
const { runMainMenu } = await import("./commands/menu.js");
|
|
151
457
|
runMainMenu();
|
|
152
458
|
}
|
|
153
459
|
else {
|
|
460
|
+
// Check for updates for non-interactive commands (stderr output)
|
|
461
|
+
const { checkForUpdates } = await import("./utils/config.js");
|
|
462
|
+
await checkForUpdates();
|
|
154
463
|
program.parse();
|
|
155
464
|
}
|
|
156
465
|
})();
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React from
|
|
3
|
-
import { render, Box, Text, useInput } from
|
|
4
|
-
import TextInput from
|
|
5
|
-
import { setApiKey } from
|
|
6
|
-
import { Header } from
|
|
7
|
-
import { Banner } from
|
|
8
|
-
import { SuccessMessage } from
|
|
9
|
-
import { getSettingsUrl } from
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { render, Box, Text, useInput } from "ink";
|
|
4
|
+
import TextInput from "ink-text-input";
|
|
5
|
+
import { setApiKey } from "../utils/config.js";
|
|
6
|
+
import { Header } from "../components/Header.js";
|
|
7
|
+
import { Banner } from "../components/Banner.js";
|
|
8
|
+
import { SuccessMessage } from "../components/SuccessMessage.js";
|
|
9
|
+
import { getSettingsUrl } from "../utils/url.js";
|
|
10
|
+
import { colors } from "../utils/theme.js";
|
|
10
11
|
const AuthUI = () => {
|
|
11
|
-
const [apiKey, setApiKeyInput] = React.useState(
|
|
12
|
+
const [apiKey, setApiKeyInput] = React.useState("");
|
|
12
13
|
const [saved, setSaved] = React.useState(false);
|
|
13
14
|
useInput((input, key) => {
|
|
14
15
|
if (key.return && apiKey.trim()) {
|
|
@@ -18,9 +19,9 @@ const AuthUI = () => {
|
|
|
18
19
|
}
|
|
19
20
|
});
|
|
20
21
|
if (saved) {
|
|
21
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), _jsx(Header, { title: "Authentication" }), _jsx(SuccessMessage, { message: "API key saved!", details: "Try:
|
|
22
|
+
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), _jsx(Header, { title: "Authentication" }), _jsx(SuccessMessage, { message: "API key saved!", details: "Try: rli devbox list" })] }));
|
|
22
23
|
}
|
|
23
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Banner, {}), _jsx(Header, { title: "Authentication" }), _jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color:
|
|
24
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Banner, {}), _jsx(Header, { title: "Authentication" }), _jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: colors.textDim, children: "Get your key: " }), _jsx(Text, { color: colors.primary, children: getSettingsUrl() })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.primary, children: "API Key: " }), _jsx(TextInput, { value: apiKey, onChange: setApiKeyInput, placeholder: "ak_...", mask: "*" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press Enter to save" }) })] }));
|
|
24
25
|
};
|
|
25
26
|
export default function auth() {
|
|
26
27
|
console.clear();
|