@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.
Files changed (73) hide show
  1. package/README.md +64 -29
  2. package/dist/cli.js +401 -92
  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 +55 -5
  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 +130 -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,156 +1,465 @@
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
+ 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('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)')
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('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)')
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
- // 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
- }
75
+ await listDevboxes(options);
62
76
  });
63
77
  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)')
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('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);
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('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)')
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('snapshot')
83
- .description('Manage devbox snapshots')
84
- .alias('snap');
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('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)')
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('./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
- }
239
+ const { listSnapshots } = await import("./commands/snapshot/list.js");
240
+ await listSnapshots(options);
99
241
  });
100
242
  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)')
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('./commands/snapshot/create.js');
248
+ const { createSnapshot } = await import("./commands/snapshot/create.js");
107
249
  createSnapshot(devboxId, options);
108
250
  });
109
251
  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)')
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('./commands/snapshot/delete.js');
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('blueprint')
121
- .description('Manage blueprints')
122
- .alias('bp');
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('list')
125
- .description('List all blueprints')
126
- .option('-o, --output [format]', 'Output format: text|json|yaml (default: interactive)')
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 { listBlueprints } = await import('./commands/blueprint/list.js');
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('./utils/interactiveCommand.js');
131
- await runInteractiveCommand(() => listBlueprints(options));
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 listBlueprints(options);
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 command)
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] !== 'auth' && args[0] !== '--help' && args[0] !== '-h' && args.length > 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('\n❌ API key not configured. Run: rln auth\n');
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('./commands/menu.js');
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
  })();
@@ -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();