@runloop/rl-cli 0.1.2 → 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.
Files changed (96) hide show
  1. package/README.md +54 -0
  2. package/dist/cli.js +73 -60
  3. package/dist/commands/auth.js +0 -1
  4. package/dist/commands/blueprint/create.js +31 -83
  5. package/dist/commands/blueprint/get.js +29 -34
  6. package/dist/commands/blueprint/list.js +215 -213
  7. package/dist/commands/blueprint/logs.js +133 -37
  8. package/dist/commands/blueprint/preview.js +42 -38
  9. package/dist/commands/config.js +117 -0
  10. package/dist/commands/devbox/create.js +120 -40
  11. package/dist/commands/devbox/delete.js +17 -33
  12. package/dist/commands/devbox/download.js +29 -43
  13. package/dist/commands/devbox/exec.js +22 -39
  14. package/dist/commands/devbox/execAsync.js +20 -37
  15. package/dist/commands/devbox/get.js +13 -35
  16. package/dist/commands/devbox/getAsync.js +12 -34
  17. package/dist/commands/devbox/list.js +241 -402
  18. package/dist/commands/devbox/logs.js +20 -38
  19. package/dist/commands/devbox/read.js +29 -43
  20. package/dist/commands/devbox/resume.js +13 -35
  21. package/dist/commands/devbox/rsync.js +26 -78
  22. package/dist/commands/devbox/scp.js +25 -79
  23. package/dist/commands/devbox/sendStdin.js +41 -0
  24. package/dist/commands/devbox/shutdown.js +13 -35
  25. package/dist/commands/devbox/ssh.js +45 -78
  26. package/dist/commands/devbox/suspend.js +13 -35
  27. package/dist/commands/devbox/tunnel.js +36 -88
  28. package/dist/commands/devbox/upload.js +28 -36
  29. package/dist/commands/devbox/write.js +29 -44
  30. package/dist/commands/mcp-install.js +4 -3
  31. package/dist/commands/menu.js +24 -66
  32. package/dist/commands/object/delete.js +12 -34
  33. package/dist/commands/object/download.js +26 -74
  34. package/dist/commands/object/get.js +12 -34
  35. package/dist/commands/object/list.js +15 -93
  36. package/dist/commands/object/upload.js +35 -96
  37. package/dist/commands/snapshot/create.js +23 -39
  38. package/dist/commands/snapshot/delete.js +17 -33
  39. package/dist/commands/snapshot/get.js +16 -0
  40. package/dist/commands/snapshot/list.js +309 -80
  41. package/dist/commands/snapshot/status.js +12 -34
  42. package/dist/components/ActionsPopup.js +63 -39
  43. package/dist/components/Breadcrumb.js +10 -48
  44. package/dist/components/DevboxActionsMenu.js +182 -110
  45. package/dist/components/DevboxCreatePage.js +12 -7
  46. package/dist/components/DevboxDetailPage.js +76 -28
  47. package/dist/components/ErrorBoundary.js +29 -0
  48. package/dist/components/ErrorMessage.js +10 -2
  49. package/dist/components/Header.js +12 -4
  50. package/dist/components/InteractiveSpawn.js +94 -0
  51. package/dist/components/MainMenu.js +36 -32
  52. package/dist/components/MetadataDisplay.js +4 -4
  53. package/dist/components/OperationsMenu.js +1 -1
  54. package/dist/components/ResourceActionsMenu.js +4 -4
  55. package/dist/components/ResourceListView.js +46 -34
  56. package/dist/components/Spinner.js +7 -2
  57. package/dist/components/StatusBadge.js +1 -1
  58. package/dist/components/SuccessMessage.js +12 -2
  59. package/dist/components/Table.js +16 -6
  60. package/dist/hooks/useCursorPagination.js +125 -85
  61. package/dist/hooks/useExitOnCtrlC.js +14 -0
  62. package/dist/hooks/useViewportHeight.js +47 -0
  63. package/dist/mcp/server.js +65 -6
  64. package/dist/router/Router.js +68 -0
  65. package/dist/router/types.js +1 -0
  66. package/dist/screens/BlueprintListScreen.js +7 -0
  67. package/dist/screens/DevboxActionsScreen.js +25 -0
  68. package/dist/screens/DevboxCreateScreen.js +11 -0
  69. package/dist/screens/DevboxDetailScreen.js +60 -0
  70. package/dist/screens/DevboxListScreen.js +23 -0
  71. package/dist/screens/LogsSessionScreen.js +49 -0
  72. package/dist/screens/MenuScreen.js +23 -0
  73. package/dist/screens/SSHSessionScreen.js +55 -0
  74. package/dist/screens/SnapshotListScreen.js +7 -0
  75. package/dist/services/blueprintService.js +105 -0
  76. package/dist/services/devboxService.js +215 -0
  77. package/dist/services/snapshotService.js +81 -0
  78. package/dist/store/blueprintStore.js +89 -0
  79. package/dist/store/devboxStore.js +105 -0
  80. package/dist/store/index.js +7 -0
  81. package/dist/store/navigationStore.js +101 -0
  82. package/dist/store/snapshotStore.js +87 -0
  83. package/dist/utils/CommandExecutor.js +53 -24
  84. package/dist/utils/client.js +0 -2
  85. package/dist/utils/config.js +22 -111
  86. package/dist/utils/interactiveCommand.js +3 -2
  87. package/dist/utils/logFormatter.js +162 -0
  88. package/dist/utils/memoryMonitor.js +85 -0
  89. package/dist/utils/output.js +150 -59
  90. package/dist/utils/screen.js +23 -0
  91. package/dist/utils/ssh.js +3 -1
  92. package/dist/utils/sshSession.js +5 -29
  93. package/dist/utils/terminalDetection.js +97 -0
  94. package/dist/utils/terminalSync.js +39 -0
  95. package/dist/utils/theme.js +147 -13
  96. 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
- process.stdout.write("\x1b[?1049l");
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
- program
37
- .command("check-updates")
38
- .description("Check for CLI updates")
37
+ // Config commands
38
+ const config = program
39
+ .command("config")
40
+ .description("Configure CLI settings")
39
41
  .action(async () => {
40
- const { checkForUpdates } = await import("./utils/config.js");
41
- console.log("Checking for updates...");
42
- await checkForUpdates(true);
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>", "Template to use")
60
- .option("--blueprint <blueprint>", "Blueprint ID to use")
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 <name>")
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: text)")
299
- .action(async (name, options) => {
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({ 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 });
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: text)")
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
- // Check if API key is configured (except for auth and mcp commands)
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 (version check handled in UI)
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
  })();
@@ -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
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
- import { getClient } from "../../utils/client.js";
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
- const CreateBlueprintUI = ({ name, dockerfile, dockerfilePath, systemSetupCommands, resources, architecture, availablePorts, root, user, }) => {
11
- const [loading, setLoading] = React.useState(true);
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
- const executor = createExecutor({ output: options.output });
74
- await executor.executeAction(async () => {
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
- throw new Error("Only one of --user or --root can be specified");
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
- throw new Error("User must be in format 'username:uid'");
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
- return client.blueprints.create({
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
- }, () => (_jsx(CreateBlueprintUI, { name: options.name, dockerfile: options.dockerfile, dockerfilePath: options.dockerfilePath, systemSetupCommands: options.systemSetupCommands, resources: options.resources, architecture: options.architecture, availablePorts: options.availablePorts, root: options.root, user: options.user })));
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
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
1
+ /**
2
+ * Get blueprint details command
3
+ */
3
4
  import { getClient } from "../../utils/client.js";
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";
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
- const executor = createExecutor({ output: options.output });
33
- await executor.executeAction(async () => {
34
- const client = executor.getClient();
35
- return client.blueprints.retrieve(options.id);
36
- }, () => _jsx(GetBlueprintUI, { blueprintId: options.id }));
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
  }