@runloop/rl-cli 0.0.3 → 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 +389 -91
  3. package/dist/commands/auth.js +12 -11
  4. package/dist/commands/blueprint/create.js +108 -0
  5. package/dist/commands/blueprint/get.js +37 -0
  6. package/dist/commands/blueprint/list.js +293 -225
  7. package/dist/commands/blueprint/logs.js +40 -0
  8. package/dist/commands/blueprint/preview.js +45 -0
  9. package/dist/commands/devbox/create.js +10 -9
  10. package/dist/commands/devbox/delete.js +8 -8
  11. package/dist/commands/devbox/download.js +49 -0
  12. package/dist/commands/devbox/exec.js +23 -13
  13. package/dist/commands/devbox/execAsync.js +43 -0
  14. package/dist/commands/devbox/get.js +37 -0
  15. package/dist/commands/devbox/getAsync.js +37 -0
  16. package/dist/commands/devbox/list.js +328 -190
  17. package/dist/commands/devbox/logs.js +40 -0
  18. package/dist/commands/devbox/read.js +49 -0
  19. package/dist/commands/devbox/resume.js +37 -0
  20. package/dist/commands/devbox/rsync.js +118 -0
  21. package/dist/commands/devbox/scp.js +122 -0
  22. package/dist/commands/devbox/shutdown.js +37 -0
  23. package/dist/commands/devbox/ssh.js +104 -0
  24. package/dist/commands/devbox/suspend.js +37 -0
  25. package/dist/commands/devbox/tunnel.js +120 -0
  26. package/dist/commands/devbox/upload.js +10 -10
  27. package/dist/commands/devbox/write.js +51 -0
  28. package/dist/commands/mcp-http.js +37 -0
  29. package/dist/commands/mcp-install.js +120 -0
  30. package/dist/commands/mcp.js +30 -0
  31. package/dist/commands/menu.js +20 -20
  32. package/dist/commands/object/delete.js +37 -0
  33. package/dist/commands/object/download.js +88 -0
  34. package/dist/commands/object/get.js +37 -0
  35. package/dist/commands/object/list.js +112 -0
  36. package/dist/commands/object/upload.js +130 -0
  37. package/dist/commands/snapshot/create.js +12 -11
  38. package/dist/commands/snapshot/delete.js +8 -8
  39. package/dist/commands/snapshot/list.js +56 -97
  40. package/dist/commands/snapshot/status.js +37 -0
  41. package/dist/components/ActionsPopup.js +16 -13
  42. package/dist/components/Banner.js +4 -4
  43. package/dist/components/Breadcrumb.js +5 -4
  44. package/dist/components/DetailView.js +7 -4
  45. package/dist/components/DevboxActionsMenu.js +315 -178
  46. package/dist/components/DevboxCard.js +15 -14
  47. package/dist/components/DevboxCreatePage.js +147 -113
  48. package/dist/components/DevboxDetailPage.js +180 -102
  49. package/dist/components/ErrorMessage.js +5 -4
  50. package/dist/components/Header.js +4 -3
  51. package/dist/components/MainMenu.js +34 -33
  52. package/dist/components/MetadataDisplay.js +17 -9
  53. package/dist/components/OperationsMenu.js +6 -5
  54. package/dist/components/ResourceActionsMenu.js +117 -0
  55. package/dist/components/ResourceListView.js +213 -0
  56. package/dist/components/Spinner.js +5 -4
  57. package/dist/components/StatusBadge.js +81 -31
  58. package/dist/components/SuccessMessage.js +4 -3
  59. package/dist/components/Table.example.js +53 -23
  60. package/dist/components/Table.js +19 -11
  61. package/dist/hooks/useCursorPagination.js +125 -0
  62. package/dist/mcp/server-http.js +416 -0
  63. package/dist/mcp/server.js +397 -0
  64. package/dist/utils/CommandExecutor.js +16 -12
  65. package/dist/utils/client.js +7 -7
  66. package/dist/utils/config.js +40 -4
  67. package/dist/utils/interactiveCommand.js +2 -2
  68. package/dist/utils/output.js +17 -17
  69. package/dist/utils/ssh.js +160 -0
  70. package/dist/utils/sshSession.js +16 -12
  71. package/dist/utils/theme.js +22 -0
  72. package/dist/utils/url.js +4 -4
  73. package/package.json +29 -4
package/dist/cli.js CHANGED
@@ -1,153 +1,451 @@
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';
9
- import { readFileSync } from 'fs';
10
- import { fileURLToPath } from 'url';
11
- import { dirname, join } from 'path';
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, '../package.json'), 'utf8'));
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('SIGINT', () => {
18
+ process.on("SIGINT", () => {
19
19
  // Force exit immediately, clearing alternate screen buffer
20
- process.stdout.write('\x1b[?1049l');
21
- process.stdout.write('\n');
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('rln')
27
- .description('Beautiful CLI for Runloop devbox management')
26
+ .name("rli")
27
+ .description("Beautiful CLI for Runloop devbox management")
28
28
  .version(VERSION);
29
29
  program
30
- .command('auth')
31
- .description('Configure API authentication')
30
+ .command("auth")
31
+ .description("Configure API authentication")
32
32
  .action(async () => {
33
- const { default: auth } = await import('./commands/auth.js');
33
+ const { default: auth } = await import("./commands/auth.js");
34
34
  auth();
35
35
  });
36
36
  // Devbox commands
37
37
  const devbox = program
38
- .command('devbox')
39
- .description('Manage devboxes')
40
- .alias('d');
41
- devbox
42
- .command('create')
43
- .description('Create a new devbox')
44
- .option('-n, --name <name>', 'Devbox name')
45
- .option('-t, --template <template>', 'Template to use')
46
- .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)")
47
60
  .action(createDevbox);
48
61
  devbox
49
- .command('list')
50
- .description('List all devboxes')
51
- .option('-s, --status <status>', 'Filter by status')
52
- .option('-o, --output [format]', 'Output format: text|json|yaml (default: interactive)')
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)")
53
66
  .action(async (options) => {
54
- // Only use alternate screen for interactive mode
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
- }
67
+ await listDevboxes(options);
62
68
  });
63
69
  devbox
64
- .command('delete <id>')
65
- .description('Shutdown a devbox')
66
- .alias('rm')
67
- .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)")
68
74
  .action(deleteDevbox);
69
75
  devbox
70
- .command('exec <id> <command...>')
71
- .description('Execute a command in a devbox')
72
- .option('-o, --output [format]', 'Output format: text|json|yaml (default: interactive)')
73
- .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
+ });
74
82
  devbox
75
- .command('upload <id> <file>')
76
- .description('Upload a file to a devbox')
77
- .option('-p, --path <path>', 'Target path in devbox')
78
- .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)")
79
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
+ });
80
214
  // Snapshot commands
81
215
  const snapshot = program
82
- .command('snapshot')
83
- .description('Manage devbox snapshots')
84
- .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
+ });
85
225
  snapshot
86
- .command('list')
87
- .description('List all snapshots')
88
- .option('-d, --devbox <id>', 'Filter by devbox ID')
89
- .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)")
90
230
  .action(async (options) => {
91
- const { listSnapshots } = await import('./commands/snapshot/list.js');
92
- if (!options.output) {
93
- const { runInteractiveCommand } = await import('./utils/interactiveCommand.js');
94
- await runInteractiveCommand(() => listSnapshots(options));
95
- }
96
- else {
97
- await listSnapshots(options);
98
- }
231
+ const { listSnapshots } = await import("./commands/snapshot/list.js");
232
+ await listSnapshots(options);
99
233
  });
100
234
  snapshot
101
- .command('create <devbox-id>')
102
- .description('Create a snapshot of a devbox')
103
- .option('-n, --name <name>', 'Snapshot name')
104
- .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)")
105
239
  .action(async (devboxId, options) => {
106
- const { createSnapshot } = await import('./commands/snapshot/create.js');
240
+ const { createSnapshot } = await import("./commands/snapshot/create.js");
107
241
  createSnapshot(devboxId, options);
108
242
  });
109
243
  snapshot
110
- .command('delete <id>')
111
- .description('Delete a snapshot')
112
- .alias('rm')
113
- .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)")
114
248
  .action(async (id, options) => {
115
- const { deleteSnapshot } = await import('./commands/snapshot/delete.js');
249
+ const { deleteSnapshot } = await import("./commands/snapshot/delete.js");
116
250
  deleteSnapshot(id, options);
117
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
+ });
118
260
  // Blueprint commands
119
261
  const blueprint = program
120
- .command('blueprint')
121
- .description('Manage blueprints')
122
- .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
+ });
123
271
  blueprint
124
- .command('list')
125
- .description('List all blueprints')
126
- .option('-o, --output [format]', 'Output format: text|json|yaml (default: interactive)')
272
+ .command("list")
273
+ .description("List all blueprints")
274
+ .option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
127
275
  .action(async (options) => {
128
- const { listBlueprints } = await import('./commands/blueprint/list.js');
276
+ const { listBlueprints } = await import("./commands/blueprint/list.js");
277
+ await listBlueprints(options);
278
+ });
279
+ blueprint
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");
129
373
  if (!options.output) {
130
- const { runInteractiveCommand } = await import('./utils/interactiveCommand.js');
131
- await runInteractiveCommand(() => listBlueprints(options));
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)
398
+ .action(async (options) => {
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);
132
424
  }
133
425
  else {
134
- await listBlueprints(options);
426
+ const { startMcpServer } = await import("./commands/mcp.js");
427
+ await startMcpServer();
135
428
  }
136
429
  });
137
430
  // Main CLI entry point
138
431
  (async () => {
139
- // Check if API key is configured (except for auth command)
432
+ // Check if API key is configured (except for auth and mcp commands)
140
433
  const args = process.argv.slice(2);
141
- if (args[0] !== 'auth' && args[0] !== '--help' && args[0] !== '-h' && args.length > 0) {
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) {
142
440
  const config = getConfig();
143
441
  if (!config.apiKey) {
144
- console.error('\n❌ API key not configured. Run: rln auth\n');
442
+ console.error("\n❌ API key not configured. Run: rli auth\n");
145
443
  process.exit(1);
146
444
  }
147
445
  }
148
446
  // If no command provided, show main menu
149
447
  if (args.length === 0) {
150
- const { runMainMenu } = await import('./commands/menu.js');
448
+ const { runMainMenu } = await import("./commands/menu.js");
151
449
  runMainMenu();
152
450
  }
153
451
  else {
@@ -1,14 +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';
9
- import { getSettingsUrl } from '../utils/url.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";
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: 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" })] }));
22
23
  }
23
- 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: getSettingsUrl() })] }), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "API Key: " }), _jsx(TextInput, { value: apiKey, onChange: setApiKeyInput, placeholder: "ak_...", 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" }) })] }));
24
25
  };
25
26
  export default function auth() {
26
27
  console.clear();