@runloop/rl-cli 0.1.1 → 0.2.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 -0
- package/dist/cli.js +73 -60
- package/dist/commands/auth.js +0 -1
- package/dist/commands/blueprint/create.js +31 -83
- package/dist/commands/blueprint/get.js +29 -34
- package/dist/commands/blueprint/list.js +215 -213
- package/dist/commands/blueprint/logs.js +133 -37
- package/dist/commands/blueprint/preview.js +42 -38
- package/dist/commands/config.js +117 -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 +45 -78
- package/dist/commands/devbox/suspend.js +13 -35
- package/dist/commands/devbox/tunnel.js +36 -88
- package/dist/commands/devbox/upload.js +28 -36
- package/dist/commands/devbox/write.js +29 -44
- package/dist/commands/mcp-install.js +4 -3
- package/dist/commands/menu.js +24 -66
- 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 +63 -39
- package/dist/components/Breadcrumb.js +10 -52
- package/dist/components/DevboxActionsMenu.js +182 -110
- 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 +94 -0
- package/dist/components/MainMenu.js +36 -32
- 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/hooks/useCursorPagination.js +125 -85
- package/dist/hooks/useExitOnCtrlC.js +14 -0
- package/dist/hooks/useViewportHeight.js +47 -0
- package/dist/mcp/server.js +65 -6
- package/dist/router/Router.js +68 -0
- package/dist/router/types.js +1 -0
- package/dist/screens/BlueprintListScreen.js +7 -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 +105 -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/CommandExecutor.js +53 -24
- package/dist/utils/client.js +0 -2
- package/dist/utils/config.js +20 -90
- package/dist/utils/interactiveCommand.js +3 -2
- package/dist/utils/logFormatter.js +162 -0
- package/dist/utils/memoryMonitor.js +85 -0
- package/dist/utils/output.js +150 -59
- package/dist/utils/screen.js +23 -0
- package/dist/utils/ssh.js +3 -1
- package/dist/utils/sshSession.js +5 -29
- package/dist/utils/terminalDetection.js +97 -0
- package/dist/utils/terminalSync.js +39 -0
- package/dist/utils/theme.js +147 -13
- package/package.json +16 -13
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
|
package/dist/cli.js
CHANGED
|
@@ -14,10 +14,11 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
14
14
|
const __dirname = dirname(__filename);
|
|
15
15
|
const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8"));
|
|
16
16
|
export const VERSION = packageJson.version;
|
|
17
|
+
import { exitAlternateScreenBuffer } from "./utils/screen.js";
|
|
17
18
|
// Global Ctrl+C handler to ensure it always exits
|
|
18
19
|
process.on("SIGINT", () => {
|
|
19
20
|
// Force exit immediately, clearing alternate screen buffer
|
|
20
|
-
|
|
21
|
+
exitAlternateScreenBuffer();
|
|
21
22
|
process.stdout.write("\n");
|
|
22
23
|
process.exit(130); // Standard exit code for SIGINT
|
|
23
24
|
});
|
|
@@ -33,34 +34,50 @@ program
|
|
|
33
34
|
const { default: auth } = await import("./commands/auth.js");
|
|
34
35
|
auth();
|
|
35
36
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.
|
|
37
|
+
// Config commands
|
|
38
|
+
const config = program
|
|
39
|
+
.command("config")
|
|
40
|
+
.description("Configure CLI settings")
|
|
39
41
|
.action(async () => {
|
|
40
|
-
const {
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const { showThemeConfig } = await import("./commands/config.js");
|
|
43
|
+
showThemeConfig();
|
|
44
|
+
});
|
|
45
|
+
config
|
|
46
|
+
.command("theme [mode]")
|
|
47
|
+
.description("Get or set theme mode (auto|light|dark)")
|
|
48
|
+
.action(async (mode) => {
|
|
49
|
+
const { showThemeConfig, setThemeConfig } = await import("./commands/config.js");
|
|
50
|
+
if (!mode) {
|
|
51
|
+
showThemeConfig();
|
|
52
|
+
}
|
|
53
|
+
else if (mode === "auto" || mode === "light" || mode === "dark") {
|
|
54
|
+
setThemeConfig(mode);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.error(`\n❌ Invalid theme mode: ${mode}\nValid options: auto, light, dark\n`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
43
60
|
});
|
|
44
61
|
// Devbox commands
|
|
45
62
|
const devbox = program
|
|
46
63
|
.command("devbox")
|
|
47
64
|
.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
|
-
});
|
|
65
|
+
.alias("d");
|
|
55
66
|
devbox
|
|
56
67
|
.command("create")
|
|
57
68
|
.description("Create a new devbox")
|
|
58
69
|
.option("-n, --name <name>", "Devbox name")
|
|
59
|
-
.option("-t, --template <template>", "
|
|
60
|
-
.option("--
|
|
70
|
+
.option("-t, --template <template>", "Snapshot ID to use (alias: --snapshot)")
|
|
71
|
+
.option("-s, --snapshot <snapshot>", "Snapshot ID to use")
|
|
72
|
+
.option("--blueprint <blueprint>", "Blueprint name or ID to use")
|
|
61
73
|
.option("--resources <size>", "Resource size (X_SMALL, SMALL, MEDIUM, LARGE, X_LARGE, XX_LARGE)")
|
|
62
74
|
.option("--architecture <arch>", "Architecture (arm64, x86_64)")
|
|
63
75
|
.option("--entrypoint <command>", "Entrypoint command to run")
|
|
76
|
+
.option("--launch-commands <commands...>", "Initialization commands to run on startup")
|
|
77
|
+
.option("--env-vars <vars...>", "Environment variables (format: KEY=value)")
|
|
78
|
+
.option("--code-mounts <mounts...>", "Code mount configurations (JSON format)")
|
|
79
|
+
.option("--idle-time <seconds>", "Idle time in seconds before idle action")
|
|
80
|
+
.option("--idle-action <action>", "Action on idle (shutdown, suspend)")
|
|
64
81
|
.option("--available-ports <ports...>", "Available ports")
|
|
65
82
|
.option("--root", "Run as root")
|
|
66
83
|
.option("--user <user:uid>", "Run as this user (format: username:uid)")
|
|
@@ -69,7 +86,8 @@ devbox
|
|
|
69
86
|
devbox
|
|
70
87
|
.command("list")
|
|
71
88
|
.description("List all devboxes")
|
|
72
|
-
.option("-s, --status <status>", "Filter by status")
|
|
89
|
+
.option("-s, --status <status>", "Filter by status (initializing, running, suspending, suspended, resuming, failure, shutdown)")
|
|
90
|
+
.option("-l, --limit <n>", "Max results", "20")
|
|
73
91
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
74
92
|
.action(async (options) => {
|
|
75
93
|
await listDevboxes(options);
|
|
@@ -83,6 +101,7 @@ devbox
|
|
|
83
101
|
devbox
|
|
84
102
|
.command("exec <id> <command...>")
|
|
85
103
|
.description("Execute a command in a devbox")
|
|
104
|
+
.option("--shell-name <name>", "Shell name to use (optional)")
|
|
86
105
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
87
106
|
.action(async (id, command, options) => {
|
|
88
107
|
await execCommand(id, command, options);
|
|
@@ -211,6 +230,16 @@ devbox
|
|
|
211
230
|
const { getAsync } = await import("./commands/devbox/getAsync.js");
|
|
212
231
|
await getAsync(id, { executionId, ...options });
|
|
213
232
|
});
|
|
233
|
+
devbox
|
|
234
|
+
.command("send-stdin <id> <execution-id>")
|
|
235
|
+
.description("Send stdin to a running async execution")
|
|
236
|
+
.option("--text <text>", "Text content to send to stdin")
|
|
237
|
+
.option("--signal <signal>", "Signal to send (EOF, INTERRUPT)")
|
|
238
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
239
|
+
.action(async (id, executionId, options) => {
|
|
240
|
+
const { sendStdin } = await import("./commands/devbox/sendStdin.js");
|
|
241
|
+
await sendStdin(id, executionId, options);
|
|
242
|
+
});
|
|
214
243
|
devbox
|
|
215
244
|
.command("logs <id>")
|
|
216
245
|
.description("View devbox logs")
|
|
@@ -223,13 +252,7 @@ devbox
|
|
|
223
252
|
const snapshot = program
|
|
224
253
|
.command("snapshot")
|
|
225
254
|
.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
|
-
});
|
|
255
|
+
.alias("snap");
|
|
233
256
|
snapshot
|
|
234
257
|
.command("list")
|
|
235
258
|
.description("List all snapshots")
|
|
@@ -257,6 +280,14 @@ snapshot
|
|
|
257
280
|
const { deleteSnapshot } = await import("./commands/snapshot/delete.js");
|
|
258
281
|
deleteSnapshot(id, options);
|
|
259
282
|
});
|
|
283
|
+
snapshot
|
|
284
|
+
.command("get <id>")
|
|
285
|
+
.description("Get snapshot details")
|
|
286
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
287
|
+
.action(async (id, options) => {
|
|
288
|
+
const { getSnapshot } = await import("./commands/snapshot/get.js");
|
|
289
|
+
await getSnapshot({ id, ...options });
|
|
290
|
+
});
|
|
260
291
|
snapshot
|
|
261
292
|
.command("status <snapshot-id>")
|
|
262
293
|
.description("Get snapshot operation status")
|
|
@@ -269,24 +300,20 @@ snapshot
|
|
|
269
300
|
const blueprint = program
|
|
270
301
|
.command("blueprint")
|
|
271
302
|
.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
|
-
});
|
|
303
|
+
.alias("bp");
|
|
279
304
|
blueprint
|
|
280
305
|
.command("list")
|
|
281
306
|
.description("List all blueprints")
|
|
307
|
+
.option("-n, --name <name>", "Filter by blueprint name")
|
|
282
308
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
283
309
|
.action(async (options) => {
|
|
284
310
|
const { listBlueprints } = await import("./commands/blueprint/list.js");
|
|
285
311
|
await listBlueprints(options);
|
|
286
312
|
});
|
|
287
313
|
blueprint
|
|
288
|
-
.command("create
|
|
314
|
+
.command("create")
|
|
289
315
|
.description("Create a new blueprint")
|
|
316
|
+
.requiredOption("--name <name>", "Blueprint name (required)")
|
|
290
317
|
.option("--dockerfile <content>", "Dockerfile contents")
|
|
291
318
|
.option("--dockerfile-path <path>", "Dockerfile path")
|
|
292
319
|
.option("--system-setup-commands <commands...>", "System setup commands")
|
|
@@ -295,37 +322,22 @@ blueprint
|
|
|
295
322
|
.option("--available-ports <ports...>", "Available ports")
|
|
296
323
|
.option("--root", "Run as root")
|
|
297
324
|
.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 (
|
|
325
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
326
|
+
.action(async (options) => {
|
|
300
327
|
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 });
|
|
328
|
+
await createBlueprint(options);
|
|
317
329
|
});
|
|
318
330
|
blueprint
|
|
319
|
-
.command("get <id>")
|
|
320
|
-
.description("Get blueprint details")
|
|
321
|
-
.option("-o, --output [format]", "Output format: text|json|yaml (default:
|
|
331
|
+
.command("get <name-or-id>")
|
|
332
|
+
.description("Get blueprint details by name or ID (IDs start with bpt_)")
|
|
333
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
322
334
|
.action(async (id, options) => {
|
|
323
335
|
const { getBlueprint } = await import("./commands/blueprint/get.js");
|
|
324
336
|
await getBlueprint({ id, ...options });
|
|
325
337
|
});
|
|
326
338
|
blueprint
|
|
327
|
-
.command("logs <id>")
|
|
328
|
-
.description("Get blueprint build logs")
|
|
339
|
+
.command("logs <name-or-id>")
|
|
340
|
+
.description("Get blueprint build logs by name or ID (IDs start with bpt_)")
|
|
329
341
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
330
342
|
.action(async (id, options) => {
|
|
331
343
|
const { getBlueprintLogs } = await import("./commands/blueprint/logs.js");
|
|
@@ -437,9 +449,13 @@ program
|
|
|
437
449
|
});
|
|
438
450
|
// Main CLI entry point
|
|
439
451
|
(async () => {
|
|
440
|
-
//
|
|
452
|
+
// Initialize theme system early (before any UI rendering)
|
|
453
|
+
const { initializeTheme } = await import("./utils/theme.js");
|
|
454
|
+
await initializeTheme();
|
|
455
|
+
// Check if API key is configured (except for auth, config, and mcp commands)
|
|
441
456
|
const args = process.argv.slice(2);
|
|
442
457
|
if (args[0] !== "auth" &&
|
|
458
|
+
args[0] !== "config" &&
|
|
443
459
|
args[0] !== "mcp" &&
|
|
444
460
|
args[0] !== "mcp-server" &&
|
|
445
461
|
args[0] !== "--help" &&
|
|
@@ -451,15 +467,12 @@ program
|
|
|
451
467
|
process.exit(1);
|
|
452
468
|
}
|
|
453
469
|
}
|
|
454
|
-
// If no command provided, show main menu
|
|
470
|
+
// If no command provided, show main menu
|
|
455
471
|
if (args.length === 0) {
|
|
456
472
|
const { runMainMenu } = await import("./commands/menu.js");
|
|
457
473
|
runMainMenu();
|
|
458
474
|
}
|
|
459
475
|
else {
|
|
460
|
-
// Check for updates for non-interactive commands (stderr output)
|
|
461
|
-
const { checkForUpdates } = await import("./utils/config.js");
|
|
462
|
-
await checkForUpdates();
|
|
463
476
|
program.parse();
|
|
464
477
|
}
|
|
465
478
|
})();
|
package/dist/commands/auth.js
CHANGED
|
@@ -24,6 +24,5 @@ const AuthUI = () => {
|
|
|
24
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" }) })] }));
|
|
25
25
|
};
|
|
26
26
|
export default function auth() {
|
|
27
|
-
console.clear();
|
|
28
27
|
render(_jsx(AuthUI, {}));
|
|
29
28
|
}
|
|
@@ -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
|
}
|