@runloop/rl-cli 0.1.2 → 0.3.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.
- package/README.md +54 -10
- package/dist/cli.js +79 -72
- package/dist/commands/auth.js +2 -2
- package/dist/commands/blueprint/create.js +31 -83
- package/dist/commands/blueprint/get.js +29 -34
- package/dist/commands/blueprint/list.js +278 -230
- package/dist/commands/blueprint/logs.js +133 -37
- package/dist/commands/config.js +118 -0
- package/dist/commands/devbox/create.js +120 -40
- package/dist/commands/devbox/delete.js +17 -33
- package/dist/commands/devbox/download.js +29 -43
- package/dist/commands/devbox/exec.js +22 -39
- package/dist/commands/devbox/execAsync.js +20 -37
- package/dist/commands/devbox/get.js +13 -35
- package/dist/commands/devbox/getAsync.js +12 -34
- package/dist/commands/devbox/list.js +241 -402
- package/dist/commands/devbox/logs.js +20 -38
- package/dist/commands/devbox/read.js +29 -43
- package/dist/commands/devbox/resume.js +13 -35
- package/dist/commands/devbox/rsync.js +26 -78
- package/dist/commands/devbox/scp.js +25 -79
- package/dist/commands/devbox/sendStdin.js +41 -0
- package/dist/commands/devbox/shutdown.js +13 -35
- package/dist/commands/devbox/ssh.js +46 -78
- package/dist/commands/devbox/suspend.js +13 -35
- package/dist/commands/devbox/tunnel.js +37 -88
- package/dist/commands/devbox/upload.js +28 -36
- package/dist/commands/devbox/write.js +29 -44
- package/dist/commands/mcp-http.js +6 -5
- package/dist/commands/mcp-install.js +12 -10
- package/dist/commands/mcp.js +5 -4
- package/dist/commands/menu.js +26 -67
- package/dist/commands/object/delete.js +12 -34
- package/dist/commands/object/download.js +26 -74
- package/dist/commands/object/get.js +12 -34
- package/dist/commands/object/list.js +15 -93
- package/dist/commands/object/upload.js +35 -96
- package/dist/commands/snapshot/create.js +23 -39
- package/dist/commands/snapshot/delete.js +17 -33
- package/dist/commands/snapshot/get.js +16 -0
- package/dist/commands/snapshot/list.js +309 -80
- package/dist/commands/snapshot/status.js +12 -34
- package/dist/components/ActionsPopup.js +64 -39
- package/dist/components/Banner.js +7 -1
- package/dist/components/Breadcrumb.js +11 -48
- package/dist/components/DevboxActionsMenu.js +117 -207
- package/dist/components/DevboxCreatePage.js +12 -7
- package/dist/components/DevboxDetailPage.js +76 -28
- package/dist/components/ErrorBoundary.js +29 -0
- package/dist/components/ErrorMessage.js +10 -2
- package/dist/components/Header.js +12 -4
- package/dist/components/InteractiveSpawn.js +104 -0
- package/dist/components/LogsViewer.js +169 -0
- package/dist/components/MainMenu.js +37 -33
- package/dist/components/MetadataDisplay.js +4 -4
- package/dist/components/OperationsMenu.js +1 -1
- package/dist/components/ResourceActionsMenu.js +4 -4
- package/dist/components/ResourceListView.js +46 -34
- package/dist/components/Spinner.js +7 -2
- package/dist/components/StatusBadge.js +1 -1
- package/dist/components/SuccessMessage.js +12 -2
- package/dist/components/Table.js +16 -6
- package/dist/components/UpdateNotification.js +56 -0
- package/dist/hooks/useCursorPagination.js +125 -85
- package/dist/hooks/useExitOnCtrlC.js +15 -0
- package/dist/hooks/useViewportHeight.js +47 -0
- package/dist/mcp/server-http.js +2 -1
- package/dist/mcp/server.js +71 -7
- package/dist/router/Router.js +70 -0
- package/dist/router/types.js +1 -0
- package/dist/screens/BlueprintListScreen.js +7 -0
- package/dist/screens/BlueprintLogsScreen.js +74 -0
- package/dist/screens/DevboxActionsScreen.js +25 -0
- package/dist/screens/DevboxCreateScreen.js +11 -0
- package/dist/screens/DevboxDetailScreen.js +60 -0
- package/dist/screens/DevboxListScreen.js +23 -0
- package/dist/screens/LogsSessionScreen.js +49 -0
- package/dist/screens/MenuScreen.js +23 -0
- package/dist/screens/SSHSessionScreen.js +55 -0
- package/dist/screens/SnapshotListScreen.js +7 -0
- package/dist/services/blueprintService.js +101 -0
- package/dist/services/devboxService.js +215 -0
- package/dist/services/snapshotService.js +81 -0
- package/dist/store/blueprintStore.js +89 -0
- package/dist/store/devboxStore.js +105 -0
- package/dist/store/index.js +7 -0
- package/dist/store/navigationStore.js +101 -0
- package/dist/store/snapshotStore.js +87 -0
- package/dist/utils/client.js +4 -2
- package/dist/utils/config.js +22 -111
- package/dist/utils/interactiveCommand.js +3 -2
- package/dist/utils/logFormatter.js +208 -0
- package/dist/utils/memoryMonitor.js +85 -0
- package/dist/utils/output.js +153 -61
- package/dist/utils/process.js +106 -0
- package/dist/utils/processUtils.js +135 -0
- package/dist/utils/screen.js +61 -0
- package/dist/utils/ssh.js +6 -3
- package/dist/utils/sshSession.js +5 -29
- package/dist/utils/terminalDetection.js +185 -0
- package/dist/utils/terminalSync.js +39 -0
- package/dist/utils/theme.js +162 -13
- package/dist/utils/versionCheck.js +53 -0
- package/dist/version.js +12 -0
- package/package.json +19 -17
package/README.md
CHANGED
|
@@ -63,6 +63,60 @@ export RUNLOOP_API_KEY=your_api_key_here
|
|
|
63
63
|
|
|
64
64
|
The CLI will automatically use `RUNLOOP_API_KEY` if set, otherwise it will use the stored configuration.
|
|
65
65
|
|
|
66
|
+
### Theme Configuration
|
|
67
|
+
|
|
68
|
+
The CLI supports both light and dark terminal themes with automatic detection:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Interactive theme selector with live preview
|
|
72
|
+
rli config theme
|
|
73
|
+
|
|
74
|
+
# Or set theme directly
|
|
75
|
+
rli config theme auto # Auto-detect terminal background (default)
|
|
76
|
+
rli config theme light # Force light mode (dark text on light background)
|
|
77
|
+
rli config theme dark # Force dark mode (light text on dark background)
|
|
78
|
+
|
|
79
|
+
# Or use environment variable
|
|
80
|
+
export RUNLOOP_THEME=light
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Interactive Mode:**
|
|
84
|
+
|
|
85
|
+
- When you run `rli config theme` without arguments, you get an interactive selector
|
|
86
|
+
- Use arrow keys to navigate between auto/light/dark options
|
|
87
|
+
- See live preview of colors as you navigate
|
|
88
|
+
- Press Enter to save, Esc to cancel
|
|
89
|
+
|
|
90
|
+
**How it works:**
|
|
91
|
+
|
|
92
|
+
- **auto** (default): Uses dark mode by default (theme detection is disabled to prevent terminal flashing)
|
|
93
|
+
- **light**: Optimized for light-themed terminals (uses dark text colors)
|
|
94
|
+
- **dark**: Optimized for dark-themed terminals (uses light text colors)
|
|
95
|
+
|
|
96
|
+
**Terminal Compatibility:**
|
|
97
|
+
|
|
98
|
+
- Works with all modern terminals (iTerm2, Terminal.app, VS Code integrated terminal, tmux)
|
|
99
|
+
- The CLI defaults to dark mode for the best experience
|
|
100
|
+
- You can manually set light or dark mode based on your terminal theme
|
|
101
|
+
|
|
102
|
+
**Note on Auto-Detection:**
|
|
103
|
+
|
|
104
|
+
- Auto theme detection is **disabled by default** to prevent screen flashing
|
|
105
|
+
- To enable it, set `RUNLOOP_ENABLE_THEME_DETECTION=1`
|
|
106
|
+
- If you use a light terminal, we recommend setting: `rli config theme light`
|
|
107
|
+
- The result is cached, so subsequent runs are instant (no flashing!)
|
|
108
|
+
- If you change your terminal theme, you can re-detect by running:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
rli config theme auto
|
|
112
|
+
```
|
|
113
|
+
- To manually set your theme without detection:
|
|
114
|
+
```bash
|
|
115
|
+
export RUNLOOP_THEME=dark # or light
|
|
116
|
+
# Or disable auto-detection entirely:
|
|
117
|
+
export RUNLOOP_DISABLE_THEME_DETECTION=1
|
|
118
|
+
```
|
|
119
|
+
|
|
66
120
|
### Devbox Commands
|
|
67
121
|
|
|
68
122
|
```bash
|
|
@@ -199,16 +253,6 @@ npm run dev
|
|
|
199
253
|
npm start -- <command>
|
|
200
254
|
```
|
|
201
255
|
|
|
202
|
-
## Tech Stack
|
|
203
|
-
|
|
204
|
-
- [Ink](https://github.com/vadimdemedes/ink) - React for CLIs
|
|
205
|
-
- [Ink Gradient](https://github.com/sindresorhus/ink-gradient) - Gradient text
|
|
206
|
-
- [Ink Big Text](https://github.com/sindresorhus/ink-big-text) - ASCII art
|
|
207
|
-
- [Commander.js](https://github.com/tj/commander.js) - CLI framework
|
|
208
|
-
- [@runloop/api-client](https://github.com/runloopai/api-client-ts) - Runloop API client
|
|
209
|
-
- TypeScript - Type safety
|
|
210
|
-
- [Figures](https://github.com/sindresorhus/figures) - Unicode symbols
|
|
211
|
-
|
|
212
256
|
## Publishing
|
|
213
257
|
|
|
214
258
|
To publish a new version to npm:
|
package/dist/cli.js
CHANGED
|
@@ -6,20 +6,15 @@ import { deleteDevbox } from "./commands/devbox/delete.js";
|
|
|
6
6
|
import { execCommand } from "./commands/devbox/exec.js";
|
|
7
7
|
import { uploadFile } from "./commands/devbox/upload.js";
|
|
8
8
|
import { getConfig } from "./utils/config.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
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;
|
|
9
|
+
import { VERSION } from "./version.js";
|
|
10
|
+
import { exitAlternateScreenBuffer } from "./utils/screen.js";
|
|
11
|
+
import { processUtils } from "./utils/processUtils.js";
|
|
17
12
|
// Global Ctrl+C handler to ensure it always exits
|
|
18
|
-
|
|
13
|
+
processUtils.on("SIGINT", () => {
|
|
19
14
|
// Force exit immediately, clearing alternate screen buffer
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
exitAlternateScreenBuffer();
|
|
16
|
+
processUtils.stdout.write("\n");
|
|
17
|
+
processUtils.exit(130); // Standard exit code for SIGINT
|
|
23
18
|
});
|
|
24
19
|
const program = new Command();
|
|
25
20
|
program
|
|
@@ -33,34 +28,50 @@ program
|
|
|
33
28
|
const { default: auth } = await import("./commands/auth.js");
|
|
34
29
|
auth();
|
|
35
30
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.
|
|
31
|
+
// Config commands
|
|
32
|
+
const config = program
|
|
33
|
+
.command("config")
|
|
34
|
+
.description("Configure CLI settings")
|
|
39
35
|
.action(async () => {
|
|
40
|
-
const {
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
const { showThemeConfig } = await import("./commands/config.js");
|
|
37
|
+
showThemeConfig();
|
|
38
|
+
});
|
|
39
|
+
config
|
|
40
|
+
.command("theme [mode]")
|
|
41
|
+
.description("Get or set theme mode (auto|light|dark)")
|
|
42
|
+
.action(async (mode) => {
|
|
43
|
+
const { showThemeConfig, setThemeConfig } = await import("./commands/config.js");
|
|
44
|
+
if (!mode) {
|
|
45
|
+
showThemeConfig();
|
|
46
|
+
}
|
|
47
|
+
else if (mode === "auto" || mode === "light" || mode === "dark") {
|
|
48
|
+
setThemeConfig(mode);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.error(`\n❌ Invalid theme mode: ${mode}\nValid options: auto, light, dark\n`);
|
|
52
|
+
processUtils.exit(1);
|
|
53
|
+
}
|
|
43
54
|
});
|
|
44
55
|
// Devbox commands
|
|
45
56
|
const devbox = program
|
|
46
57
|
.command("devbox")
|
|
47
58
|
.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
|
-
});
|
|
59
|
+
.alias("d");
|
|
55
60
|
devbox
|
|
56
61
|
.command("create")
|
|
57
62
|
.description("Create a new devbox")
|
|
58
63
|
.option("-n, --name <name>", "Devbox name")
|
|
59
|
-
.option("-t, --template <template>", "
|
|
60
|
-
.option("--
|
|
64
|
+
.option("-t, --template <template>", "Snapshot ID to use (alias: --snapshot)")
|
|
65
|
+
.option("-s, --snapshot <snapshot>", "Snapshot ID to use")
|
|
66
|
+
.option("--blueprint <blueprint>", "Blueprint name or ID to use")
|
|
61
67
|
.option("--resources <size>", "Resource size (X_SMALL, SMALL, MEDIUM, LARGE, X_LARGE, XX_LARGE)")
|
|
62
68
|
.option("--architecture <arch>", "Architecture (arm64, x86_64)")
|
|
63
69
|
.option("--entrypoint <command>", "Entrypoint command to run")
|
|
70
|
+
.option("--launch-commands <commands...>", "Initialization commands to run on startup")
|
|
71
|
+
.option("--env-vars <vars...>", "Environment variables (format: KEY=value)")
|
|
72
|
+
.option("--code-mounts <mounts...>", "Code mount configurations (JSON format)")
|
|
73
|
+
.option("--idle-time <seconds>", "Idle time in seconds before idle action")
|
|
74
|
+
.option("--idle-action <action>", "Action on idle (shutdown, suspend)")
|
|
64
75
|
.option("--available-ports <ports...>", "Available ports")
|
|
65
76
|
.option("--root", "Run as root")
|
|
66
77
|
.option("--user <user:uid>", "Run as this user (format: username:uid)")
|
|
@@ -69,7 +80,8 @@ devbox
|
|
|
69
80
|
devbox
|
|
70
81
|
.command("list")
|
|
71
82
|
.description("List all devboxes")
|
|
72
|
-
.option("-s, --status <status>", "Filter by status")
|
|
83
|
+
.option("-s, --status <status>", "Filter by status (initializing, running, suspending, suspended, resuming, failure, shutdown)")
|
|
84
|
+
.option("-l, --limit <n>", "Max results", "20")
|
|
73
85
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
74
86
|
.action(async (options) => {
|
|
75
87
|
await listDevboxes(options);
|
|
@@ -83,6 +95,7 @@ devbox
|
|
|
83
95
|
devbox
|
|
84
96
|
.command("exec <id> <command...>")
|
|
85
97
|
.description("Execute a command in a devbox")
|
|
98
|
+
.option("--shell-name <name>", "Shell name to use (optional)")
|
|
86
99
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
87
100
|
.action(async (id, command, options) => {
|
|
88
101
|
await execCommand(id, command, options);
|
|
@@ -211,6 +224,16 @@ devbox
|
|
|
211
224
|
const { getAsync } = await import("./commands/devbox/getAsync.js");
|
|
212
225
|
await getAsync(id, { executionId, ...options });
|
|
213
226
|
});
|
|
227
|
+
devbox
|
|
228
|
+
.command("send-stdin <id> <execution-id>")
|
|
229
|
+
.description("Send stdin to a running async execution")
|
|
230
|
+
.option("--text <text>", "Text content to send to stdin")
|
|
231
|
+
.option("--signal <signal>", "Signal to send (EOF, INTERRUPT)")
|
|
232
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
233
|
+
.action(async (id, executionId, options) => {
|
|
234
|
+
const { sendStdin } = await import("./commands/devbox/sendStdin.js");
|
|
235
|
+
await sendStdin(id, executionId, options);
|
|
236
|
+
});
|
|
214
237
|
devbox
|
|
215
238
|
.command("logs <id>")
|
|
216
239
|
.description("View devbox logs")
|
|
@@ -223,13 +246,7 @@ devbox
|
|
|
223
246
|
const snapshot = program
|
|
224
247
|
.command("snapshot")
|
|
225
248
|
.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
|
-
});
|
|
249
|
+
.alias("snap");
|
|
233
250
|
snapshot
|
|
234
251
|
.command("list")
|
|
235
252
|
.description("List all snapshots")
|
|
@@ -257,6 +274,14 @@ snapshot
|
|
|
257
274
|
const { deleteSnapshot } = await import("./commands/snapshot/delete.js");
|
|
258
275
|
deleteSnapshot(id, options);
|
|
259
276
|
});
|
|
277
|
+
snapshot
|
|
278
|
+
.command("get <id>")
|
|
279
|
+
.description("Get snapshot details")
|
|
280
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
281
|
+
.action(async (id, options) => {
|
|
282
|
+
const { getSnapshot } = await import("./commands/snapshot/get.js");
|
|
283
|
+
await getSnapshot({ id, ...options });
|
|
284
|
+
});
|
|
260
285
|
snapshot
|
|
261
286
|
.command("status <snapshot-id>")
|
|
262
287
|
.description("Get snapshot operation status")
|
|
@@ -269,24 +294,20 @@ snapshot
|
|
|
269
294
|
const blueprint = program
|
|
270
295
|
.command("blueprint")
|
|
271
296
|
.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
|
-
});
|
|
297
|
+
.alias("bp");
|
|
279
298
|
blueprint
|
|
280
299
|
.command("list")
|
|
281
300
|
.description("List all blueprints")
|
|
301
|
+
.option("-n, --name <name>", "Filter by blueprint name")
|
|
282
302
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
283
303
|
.action(async (options) => {
|
|
284
304
|
const { listBlueprints } = await import("./commands/blueprint/list.js");
|
|
285
305
|
await listBlueprints(options);
|
|
286
306
|
});
|
|
287
307
|
blueprint
|
|
288
|
-
.command("create
|
|
308
|
+
.command("create")
|
|
289
309
|
.description("Create a new blueprint")
|
|
310
|
+
.requiredOption("--name <name>", "Blueprint name (required)")
|
|
290
311
|
.option("--dockerfile <content>", "Dockerfile contents")
|
|
291
312
|
.option("--dockerfile-path <path>", "Dockerfile path")
|
|
292
313
|
.option("--system-setup-commands <commands...>", "System setup commands")
|
|
@@ -295,37 +316,22 @@ blueprint
|
|
|
295
316
|
.option("--available-ports <ports...>", "Available ports")
|
|
296
317
|
.option("--root", "Run as root")
|
|
297
318
|
.option("--user <user:uid>", "Run as this user (format: username:uid)")
|
|
298
|
-
.option("-o, --output [format]", "Output format: text|json|yaml (default:
|
|
299
|
-
.action(async (
|
|
319
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
320
|
+
.action(async (options) => {
|
|
300
321
|
const { createBlueprint } = await import("./commands/blueprint/create.js");
|
|
301
|
-
await createBlueprint(
|
|
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 });
|
|
322
|
+
await createBlueprint(options);
|
|
317
323
|
});
|
|
318
324
|
blueprint
|
|
319
|
-
.command("get <id>")
|
|
320
|
-
.description("Get blueprint details")
|
|
321
|
-
.option("-o, --output [format]", "Output format: text|json|yaml (default:
|
|
325
|
+
.command("get <name-or-id>")
|
|
326
|
+
.description("Get blueprint details by name or ID (IDs start with bpt_)")
|
|
327
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
322
328
|
.action(async (id, options) => {
|
|
323
329
|
const { getBlueprint } = await import("./commands/blueprint/get.js");
|
|
324
330
|
await getBlueprint({ id, ...options });
|
|
325
331
|
});
|
|
326
332
|
blueprint
|
|
327
|
-
.command("logs <id>")
|
|
328
|
-
.description("Get blueprint build logs")
|
|
333
|
+
.command("logs <name-or-id>")
|
|
334
|
+
.description("Get blueprint build logs by name or ID (IDs start with bpt_)")
|
|
329
335
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
330
336
|
.action(async (id, options) => {
|
|
331
337
|
const { getBlueprintLogs } = await import("./commands/blueprint/logs.js");
|
|
@@ -437,9 +443,13 @@ program
|
|
|
437
443
|
});
|
|
438
444
|
// Main CLI entry point
|
|
439
445
|
(async () => {
|
|
440
|
-
//
|
|
446
|
+
// Initialize theme system early (before any UI rendering)
|
|
447
|
+
const { initializeTheme } = await import("./utils/theme.js");
|
|
448
|
+
await initializeTheme();
|
|
449
|
+
// Check if API key is configured (except for auth, config, and mcp commands)
|
|
441
450
|
const args = process.argv.slice(2);
|
|
442
451
|
if (args[0] !== "auth" &&
|
|
452
|
+
args[0] !== "config" &&
|
|
443
453
|
args[0] !== "mcp" &&
|
|
444
454
|
args[0] !== "mcp-server" &&
|
|
445
455
|
args[0] !== "--help" &&
|
|
@@ -448,18 +458,15 @@ program
|
|
|
448
458
|
const config = getConfig();
|
|
449
459
|
if (!config.apiKey) {
|
|
450
460
|
console.error("\n❌ API key not configured. Run: rli auth\n");
|
|
451
|
-
|
|
461
|
+
processUtils.exit(1);
|
|
452
462
|
}
|
|
453
463
|
}
|
|
454
|
-
// If no command provided, show main menu
|
|
464
|
+
// If no command provided, show main menu
|
|
455
465
|
if (args.length === 0) {
|
|
456
466
|
const { runMainMenu } = await import("./commands/menu.js");
|
|
457
467
|
runMainMenu();
|
|
458
468
|
}
|
|
459
469
|
else {
|
|
460
|
-
// Check for updates for non-interactive commands (stderr output)
|
|
461
|
-
const { checkForUpdates } = await import("./utils/config.js");
|
|
462
|
-
await checkForUpdates();
|
|
463
470
|
program.parse();
|
|
464
471
|
}
|
|
465
472
|
})();
|
package/dist/commands/auth.js
CHANGED
|
@@ -8,6 +8,7 @@ import { Banner } from "../components/Banner.js";
|
|
|
8
8
|
import { SuccessMessage } from "../components/SuccessMessage.js";
|
|
9
9
|
import { getSettingsUrl } from "../utils/url.js";
|
|
10
10
|
import { colors } from "../utils/theme.js";
|
|
11
|
+
import { processUtils } from "../utils/processUtils.js";
|
|
11
12
|
const AuthUI = () => {
|
|
12
13
|
const [apiKey, setApiKeyInput] = React.useState("");
|
|
13
14
|
const [saved, setSaved] = React.useState(false);
|
|
@@ -15,7 +16,7 @@ const AuthUI = () => {
|
|
|
15
16
|
if (key.return && apiKey.trim()) {
|
|
16
17
|
setApiKey(apiKey.trim());
|
|
17
18
|
setSaved(true);
|
|
18
|
-
setTimeout(() =>
|
|
19
|
+
setTimeout(() => processUtils.exit(0), 1000);
|
|
19
20
|
}
|
|
20
21
|
});
|
|
21
22
|
if (saved) {
|
|
@@ -24,6 +25,5 @@ const AuthUI = () => {
|
|
|
24
25
|
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" }) })] }));
|
|
25
26
|
};
|
|
26
27
|
export default function auth() {
|
|
27
|
-
console.clear();
|
|
28
28
|
render(_jsx(AuthUI, {}));
|
|
29
29
|
}
|
|
@@ -1,78 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { Banner } from "../../components/Banner.js";
|
|
5
|
-
import { SpinnerComponent } from "../../components/Spinner.js";
|
|
6
|
-
import { SuccessMessage } from "../../components/SuccessMessage.js";
|
|
7
|
-
import { ErrorMessage } from "../../components/ErrorMessage.js";
|
|
8
|
-
import { createExecutor } from "../../utils/CommandExecutor.js";
|
|
1
|
+
/**
|
|
2
|
+
* Create blueprint command
|
|
3
|
+
*/
|
|
9
4
|
import { readFile } from "fs/promises";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const [result, setResult] = React.useState(null);
|
|
13
|
-
const [error, setError] = React.useState(null);
|
|
14
|
-
React.useEffect(() => {
|
|
15
|
-
const createBlueprint = async () => {
|
|
16
|
-
try {
|
|
17
|
-
const client = getClient();
|
|
18
|
-
// Read dockerfile from file if path is provided
|
|
19
|
-
let dockerfileContents = dockerfile;
|
|
20
|
-
if (dockerfilePath) {
|
|
21
|
-
dockerfileContents = await readFile(dockerfilePath, "utf-8");
|
|
22
|
-
}
|
|
23
|
-
// Parse user parameters
|
|
24
|
-
let userParameters = undefined;
|
|
25
|
-
if (user && root) {
|
|
26
|
-
throw new Error("Only one of --user or --root can be specified");
|
|
27
|
-
}
|
|
28
|
-
else if (user) {
|
|
29
|
-
const [username, uid] = user.split(":");
|
|
30
|
-
if (!username || !uid) {
|
|
31
|
-
throw new Error("User must be in format 'username:uid'");
|
|
32
|
-
}
|
|
33
|
-
userParameters = { username, uid: parseInt(uid) };
|
|
34
|
-
}
|
|
35
|
-
else if (root) {
|
|
36
|
-
userParameters = { username: "root", uid: 0 };
|
|
37
|
-
}
|
|
38
|
-
const blueprint = await client.blueprints.create({
|
|
39
|
-
name,
|
|
40
|
-
dockerfile: dockerfileContents,
|
|
41
|
-
system_setup_commands: systemSetupCommands,
|
|
42
|
-
launch_parameters: {
|
|
43
|
-
resource_size_request: resources,
|
|
44
|
-
architecture: architecture,
|
|
45
|
-
available_ports: availablePorts?.map((port) => parseInt(port, 10)),
|
|
46
|
-
user_parameters: userParameters,
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
setResult(blueprint);
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
setError(err);
|
|
53
|
-
}
|
|
54
|
-
finally {
|
|
55
|
-
setLoading(false);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
createBlueprint();
|
|
59
|
-
}, [
|
|
60
|
-
name,
|
|
61
|
-
dockerfile,
|
|
62
|
-
dockerfilePath,
|
|
63
|
-
systemSetupCommands,
|
|
64
|
-
resources,
|
|
65
|
-
architecture,
|
|
66
|
-
availablePorts,
|
|
67
|
-
root,
|
|
68
|
-
user,
|
|
69
|
-
]);
|
|
70
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Creating blueprint..." }), result && (_jsx(SuccessMessage, { message: "Blueprint created successfully", details: `ID: ${result.id}\nName: ${result.name}\nStatus: ${result.status}` })), error && (_jsx(ErrorMessage, { message: "Failed to create blueprint", error: error }))] }));
|
|
71
|
-
};
|
|
5
|
+
import { getClient } from "../../utils/client.js";
|
|
6
|
+
import { output, outputError } from "../../utils/output.js";
|
|
72
7
|
export async function createBlueprint(options) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const client = executor.getClient();
|
|
8
|
+
try {
|
|
9
|
+
const client = getClient();
|
|
76
10
|
// Read dockerfile from file if path is provided
|
|
77
11
|
let dockerfileContents = options.dockerfile;
|
|
78
12
|
if (options.dockerfilePath) {
|
|
@@ -81,28 +15,42 @@ export async function createBlueprint(options) {
|
|
|
81
15
|
// Parse user parameters
|
|
82
16
|
let userParameters = undefined;
|
|
83
17
|
if (options.user && options.root) {
|
|
84
|
-
|
|
18
|
+
outputError("Only one of --user or --root can be specified");
|
|
85
19
|
}
|
|
86
20
|
else if (options.user) {
|
|
87
21
|
const [username, uid] = options.user.split(":");
|
|
88
22
|
if (!username || !uid) {
|
|
89
|
-
|
|
23
|
+
outputError("User must be in format 'username:uid'");
|
|
90
24
|
}
|
|
91
25
|
userParameters = { username, uid: parseInt(uid) };
|
|
92
26
|
}
|
|
93
27
|
else if (options.root) {
|
|
94
28
|
userParameters = { username: "root", uid: 0 };
|
|
95
29
|
}
|
|
96
|
-
|
|
30
|
+
// Build launch parameters
|
|
31
|
+
const launchParameters = {};
|
|
32
|
+
if (options.resources) {
|
|
33
|
+
launchParameters.resource_size_request = options.resources;
|
|
34
|
+
}
|
|
35
|
+
if (options.architecture) {
|
|
36
|
+
launchParameters.architecture = options.architecture;
|
|
37
|
+
}
|
|
38
|
+
if (options.availablePorts) {
|
|
39
|
+
launchParameters.available_ports = options.availablePorts.map((port) => parseInt(port, 10));
|
|
40
|
+
}
|
|
41
|
+
if (userParameters) {
|
|
42
|
+
launchParameters.user_parameters = userParameters;
|
|
43
|
+
}
|
|
44
|
+
const blueprint = await client.blueprints.create({
|
|
97
45
|
name: options.name,
|
|
98
46
|
dockerfile: dockerfileContents,
|
|
99
47
|
system_setup_commands: options.systemSetupCommands,
|
|
100
|
-
launch_parameters:
|
|
101
|
-
resource_size_request: options.resources,
|
|
102
|
-
architecture: options.architecture,
|
|
103
|
-
available_ports: options.availablePorts?.map((port) => parseInt(port, 10)),
|
|
104
|
-
user_parameters: userParameters,
|
|
105
|
-
},
|
|
48
|
+
launch_parameters: launchParameters,
|
|
106
49
|
});
|
|
107
|
-
|
|
50
|
+
// Default: output JSON
|
|
51
|
+
output(blueprint, { format: options.output, defaultFormat: "json" });
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
outputError("Failed to create blueprint", error);
|
|
55
|
+
}
|
|
108
56
|
}
|
|
@@ -1,37 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Get blueprint details command
|
|
3
|
+
*/
|
|
3
4
|
import { getClient } from "../../utils/client.js";
|
|
4
|
-
import {
|
|
5
|
-
import { SpinnerComponent } from "../../components/Spinner.js";
|
|
6
|
-
import { SuccessMessage } from "../../components/SuccessMessage.js";
|
|
7
|
-
import { ErrorMessage } from "../../components/ErrorMessage.js";
|
|
8
|
-
import { createExecutor } from "../../utils/CommandExecutor.js";
|
|
9
|
-
const GetBlueprintUI = ({ blueprintId }) => {
|
|
10
|
-
const [loading, setLoading] = React.useState(true);
|
|
11
|
-
const [result, setResult] = React.useState(null);
|
|
12
|
-
const [error, setError] = React.useState(null);
|
|
13
|
-
React.useEffect(() => {
|
|
14
|
-
const getBlueprint = async () => {
|
|
15
|
-
try {
|
|
16
|
-
const client = getClient();
|
|
17
|
-
const blueprint = await client.blueprints.retrieve(blueprintId);
|
|
18
|
-
setResult(blueprint);
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
setError(err);
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
setLoading(false);
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
getBlueprint();
|
|
28
|
-
}, [blueprintId]);
|
|
29
|
-
return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), loading && _jsx(SpinnerComponent, { message: "Fetching blueprint details..." }), result && (_jsx(SuccessMessage, { message: "Blueprint details retrieved", details: `ID: ${result.id}\nName: ${result.name}\nStatus: ${result.status}\nCreated: ${new Date(result.createdAt).toLocaleString()}` })), error && (_jsx(ErrorMessage, { message: "Failed to get blueprint", error: error }))] }));
|
|
30
|
-
};
|
|
5
|
+
import { output, outputError } from "../../utils/output.js";
|
|
31
6
|
export async function getBlueprint(options) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
7
|
+
try {
|
|
8
|
+
const client = getClient();
|
|
9
|
+
let blueprint;
|
|
10
|
+
// Check if it's an ID (starts with bpt_) or a name
|
|
11
|
+
if (options.id.startsWith("bpt_")) {
|
|
12
|
+
// It's an ID, retrieve directly
|
|
13
|
+
blueprint = await client.blueprints.retrieve(options.id);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
// It's a name, search for it
|
|
17
|
+
const result = await client.blueprints.list({ name: options.id });
|
|
18
|
+
const blueprints = result.blueprints || [];
|
|
19
|
+
if (blueprints.length === 0) {
|
|
20
|
+
outputError(`Blueprint not found: ${options.id}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Return the first exact match, or first result if no exact match
|
|
24
|
+
blueprint =
|
|
25
|
+
blueprints.find((b) => b.name === options.id) || blueprints[0];
|
|
26
|
+
}
|
|
27
|
+
output(blueprint, { format: options.output, defaultFormat: "json" });
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
outputError("Failed to get blueprint", error);
|
|
31
|
+
}
|
|
37
32
|
}
|