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