@runloop/rl-cli 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +64 -29
  2. package/dist/cli.js +420 -76
  3. package/dist/commands/auth.js +12 -10
  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 +303 -224
  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 +390 -205
  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 +70 -0
  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 +59 -91
  40. package/dist/commands/snapshot/status.js +37 -0
  41. package/dist/components/ActionsPopup.js +16 -13
  42. package/dist/components/Banner.js +5 -8
  43. package/dist/components/Breadcrumb.js +6 -6
  44. package/dist/components/DetailView.js +7 -4
  45. package/dist/components/DevboxActionsMenu.js +347 -189
  46. package/dist/components/DevboxCard.js +15 -14
  47. package/dist/components/DevboxCreatePage.js +147 -113
  48. package/dist/components/DevboxDetailPage.js +182 -103
  49. package/dist/components/ErrorMessage.js +5 -4
  50. package/dist/components/Header.js +4 -3
  51. package/dist/components/MainMenu.js +72 -0
  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 +22 -6
  65. package/dist/utils/client.js +20 -3
  66. package/dist/utils/config.js +40 -4
  67. package/dist/utils/interactiveCommand.js +14 -0
  68. package/dist/utils/output.js +17 -17
  69. package/dist/utils/ssh.js +160 -0
  70. package/dist/utils/sshSession.js +29 -0
  71. package/dist/utils/theme.js +22 -0
  72. package/dist/utils/url.js +39 -0
  73. package/package.json +29 -4
@@ -1,12 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { Box, Text, useInput, useStdout } from 'ink';
4
- import figures from 'figures';
5
- import { Header } from './Header.js';
6
- import { StatusBadge } from './StatusBadge.js';
7
- import { MetadataDisplay } from './MetadataDisplay.js';
8
- import { Breadcrumb } from './Breadcrumb.js';
9
- import { DevboxActionsMenu } from './DevboxActionsMenu.js';
2
+ import React from "react";
3
+ import { Box, Text, useInput, useStdout } from "ink";
4
+ import figures from "figures";
5
+ import { Header } from "./Header.js";
6
+ import { StatusBadge } from "./StatusBadge.js";
7
+ import { MetadataDisplay } from "./MetadataDisplay.js";
8
+ import { Breadcrumb } from "./Breadcrumb.js";
9
+ import { DevboxActionsMenu } from "./DevboxActionsMenu.js";
10
+ import { getDevboxUrl } from "../utils/url.js";
11
+ import { colors } from "../utils/theme.js";
10
12
  // Format time ago in a succinct way
11
13
  const formatTimeAgo = (timestamp) => {
12
14
  const seconds = Math.floor((Date.now() - timestamp) / 1000);
@@ -27,7 +29,7 @@ const formatTimeAgo = (timestamp) => {
27
29
  const years = Math.floor(months / 12);
28
30
  return `${years}y ago`;
29
31
  };
30
- export const DevboxDetailPage = ({ devbox: initialDevbox, onBack }) => {
32
+ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack, onSSHRequest, }) => {
31
33
  const { stdout } = useStdout();
32
34
  const [showDetailedInfo, setShowDetailedInfo] = React.useState(false);
33
35
  const [detailScroll, setDetailScroll] = React.useState(0);
@@ -35,37 +37,99 @@ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack }) => {
35
37
  const [selectedOperation, setSelectedOperation] = React.useState(0);
36
38
  const selectedDevbox = initialDevbox;
37
39
  const allOperations = [
38
- { key: 'logs', label: 'View Logs', color: 'blue', icon: figures.info, shortcut: 'l' },
39
- { key: 'exec', label: 'Execute Command', color: 'green', icon: figures.play, shortcut: 'e' },
40
- { key: 'upload', label: 'Upload File', color: 'green', icon: figures.arrowUp, shortcut: 'u' },
41
- { key: 'snapshot', label: 'Create Snapshot', color: 'yellow', icon: figures.circleFilled, shortcut: 'n' },
42
- { key: 'ssh', label: 'SSH onto the box', color: 'cyan', icon: figures.arrowRight, shortcut: 's' },
43
- { key: 'tunnel', label: 'Open Tunnel', color: 'magenta', icon: figures.pointerSmall, shortcut: 't' },
44
- { key: 'suspend', label: 'Suspend Devbox', color: 'yellow', icon: figures.squareSmallFilled, shortcut: 'p' },
45
- { key: 'resume', label: 'Resume Devbox', color: 'green', icon: figures.play, shortcut: 'r' },
46
- { key: 'delete', label: 'Shutdown Devbox', color: 'red', icon: figures.cross, shortcut: 'd' },
40
+ {
41
+ key: "logs",
42
+ label: "View Logs",
43
+ color: colors.info,
44
+ icon: figures.info,
45
+ shortcut: "l",
46
+ },
47
+ {
48
+ key: "exec",
49
+ label: "Execute Command",
50
+ color: colors.success,
51
+ icon: figures.play,
52
+ shortcut: "e",
53
+ },
54
+ {
55
+ key: "upload",
56
+ label: "Upload File",
57
+ color: colors.success,
58
+ icon: figures.arrowUp,
59
+ shortcut: "u",
60
+ },
61
+ {
62
+ key: "snapshot",
63
+ label: "Create Snapshot",
64
+ color: colors.warning,
65
+ icon: figures.circleFilled,
66
+ shortcut: "n",
67
+ },
68
+ {
69
+ key: "ssh",
70
+ label: "SSH onto the box",
71
+ color: colors.primary,
72
+ icon: figures.arrowRight,
73
+ shortcut: "s",
74
+ },
75
+ {
76
+ key: "tunnel",
77
+ label: "Open Tunnel",
78
+ color: colors.secondary,
79
+ icon: figures.pointerSmall,
80
+ shortcut: "t",
81
+ },
82
+ {
83
+ key: "suspend",
84
+ label: "Suspend Devbox",
85
+ color: colors.warning,
86
+ icon: figures.squareSmallFilled,
87
+ shortcut: "p",
88
+ },
89
+ {
90
+ key: "resume",
91
+ label: "Resume Devbox",
92
+ color: colors.success,
93
+ icon: figures.play,
94
+ shortcut: "r",
95
+ },
96
+ {
97
+ key: "delete",
98
+ label: "Shutdown Devbox",
99
+ color: colors.error,
100
+ icon: figures.cross,
101
+ shortcut: "d",
102
+ },
47
103
  ];
48
104
  // Filter operations based on devbox status
49
- const operations = selectedDevbox ? allOperations.filter(op => {
50
- const status = selectedDevbox.status;
51
- // When suspended: logs and resume
52
- if (status === 'suspended') {
53
- return op.key === 'resume' || op.key === 'logs';
54
- }
55
- // When not running (shutdown, failure, etc): only logs
56
- if (status !== 'running' && status !== 'provisioning' && status !== 'initializing') {
57
- return op.key === 'logs';
58
- }
59
- // When running: everything except resume
60
- if (status === 'running') {
61
- return op.key !== 'resume';
62
- }
63
- // Default for transitional states (provisioning, initializing)
64
- return op.key === 'logs' || op.key === 'delete';
65
- }) : allOperations;
105
+ const operations = selectedDevbox
106
+ ? allOperations.filter((op) => {
107
+ const status = selectedDevbox.status;
108
+ // When suspended: logs and resume
109
+ if (status === "suspended") {
110
+ return op.key === "resume" || op.key === "logs";
111
+ }
112
+ // When not running (shutdown, failure, etc): only logs
113
+ if (status !== "running" &&
114
+ status !== "provisioning" &&
115
+ status !== "initializing") {
116
+ return op.key === "logs";
117
+ }
118
+ // When running: everything except resume
119
+ if (status === "running") {
120
+ return op.key !== "resume";
121
+ }
122
+ // Default for transitional states (provisioning, initializing)
123
+ return op.key === "logs" || op.key === "delete";
124
+ })
125
+ : allOperations;
66
126
  // Memoize time-based values to prevent re-rendering on every tick
67
- const formattedCreateTime = React.useMemo(() => selectedDevbox.create_time_ms ? new Date(selectedDevbox.create_time_ms).toLocaleString() : '', [selectedDevbox.create_time_ms]);
68
- const createTimeAgo = React.useMemo(() => selectedDevbox.create_time_ms ? formatTimeAgo(selectedDevbox.create_time_ms) : '', [selectedDevbox.create_time_ms]);
127
+ const formattedCreateTime = React.useMemo(() => selectedDevbox.create_time_ms
128
+ ? new Date(selectedDevbox.create_time_ms).toLocaleString()
129
+ : "", [selectedDevbox.create_time_ms]);
130
+ const createTimeAgo = React.useMemo(() => selectedDevbox.create_time_ms
131
+ ? formatTimeAgo(selectedDevbox.create_time_ms)
132
+ : "", [selectedDevbox.create_time_ms]);
69
133
  useInput((input, key) => {
70
134
  // Skip input handling when in actions view
71
135
  if (showActions) {
@@ -73,15 +137,15 @@ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack }) => {
73
137
  }
74
138
  // Handle detailed info mode
75
139
  if (showDetailedInfo) {
76
- if (input === 'q' || key.escape) {
140
+ if (input === "q" || key.escape) {
77
141
  setShowDetailedInfo(false);
78
142
  setDetailScroll(0);
79
143
  }
80
- else if (input === 'j' || input === 's' || key.downArrow) {
144
+ else if (input === "j" || input === "s" || key.downArrow) {
81
145
  // Scroll down in detailed info
82
146
  setDetailScroll(detailScroll + 1);
83
147
  }
84
- else if (input === 'k' || input === 'w' || key.upArrow) {
148
+ else if (input === "k" || input === "w" || key.upArrow) {
85
149
  // Scroll up in detailed info
86
150
  setDetailScroll(Math.max(0, detailScroll - 1));
87
151
  }
@@ -96,11 +160,11 @@ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack }) => {
96
160
  return;
97
161
  }
98
162
  // Main view input handling
99
- if (input === 'q' || key.escape) {
163
+ if (input === "q" || key.escape) {
100
164
  console.clear();
101
165
  onBack();
102
166
  }
103
- else if (input === 'i') {
167
+ else if (input === "i") {
104
168
  setShowDetailedInfo(true);
105
169
  setDetailScroll(0);
106
170
  }
@@ -110,30 +174,30 @@ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack }) => {
110
174
  else if (key.downArrow && selectedOperation < operations.length - 1) {
111
175
  setSelectedOperation(selectedOperation + 1);
112
176
  }
113
- else if (key.return || input === 'a') {
177
+ else if (key.return || input === "a") {
114
178
  console.clear();
115
179
  setShowActions(true);
116
180
  }
117
181
  else if (input) {
118
182
  // Check if input matches any operation shortcut
119
- const matchedOpIndex = operations.findIndex(op => op.shortcut === input);
183
+ const matchedOpIndex = operations.findIndex((op) => op.shortcut === input);
120
184
  if (matchedOpIndex !== -1) {
121
185
  setSelectedOperation(matchedOpIndex);
122
186
  console.clear();
123
187
  setShowActions(true);
124
188
  }
125
189
  }
126
- if (input === 'o') {
190
+ if (input === "o") {
127
191
  // Open in browser
128
- const url = `https://platform.runloop.ai/devboxes/${selectedDevbox.id}`;
192
+ const url = getDevboxUrl(selectedDevbox.id);
129
193
  const openBrowser = async () => {
130
- const { exec } = await import('child_process');
194
+ const { exec } = await import("child_process");
131
195
  const platform = process.platform;
132
196
  let openCommand;
133
- if (platform === 'darwin') {
197
+ if (platform === "darwin") {
134
198
  openCommand = `open "${url}"`;
135
199
  }
136
- else if (platform === 'win32') {
200
+ else if (platform === "win32") {
137
201
  openCommand = `start "${url}"`;
138
202
  }
139
203
  else {
@@ -152,137 +216,139 @@ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack }) => {
152
216
  const lines = [];
153
217
  const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
154
218
  // Core Information
155
- lines.push(_jsx(Text, { color: "yellow", bold: true, children: "Devbox Details" }, "core-title"));
156
- lines.push(_jsxs(Text, { dimColor: true, children: [" ID: ", selectedDevbox.id] }, "core-id"));
157
- lines.push(_jsxs(Text, { dimColor: true, children: [" Name: ", selectedDevbox.name || '(none)'] }, "core-name"));
158
- lines.push(_jsxs(Text, { dimColor: true, children: [" Status: ", capitalize(selectedDevbox.status)] }, "core-status"));
159
- lines.push(_jsxs(Text, { dimColor: true, children: [" Created: ", new Date(selectedDevbox.create_time_ms).toLocaleString()] }, "core-created"));
219
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Devbox Details" }, "core-title"));
220
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "ID: ", selectedDevbox.id] }, "core-id"));
221
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Name: ", selectedDevbox.name || "(none)"] }, "core-name"));
222
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Status: ", capitalize(selectedDevbox.status)] }, "core-status"));
223
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Created: ", new Date(selectedDevbox.create_time_ms).toLocaleString()] }, "core-created"));
160
224
  if (selectedDevbox.end_time_ms) {
161
- lines.push(_jsxs(Text, { dimColor: true, children: [" Ended: ", new Date(selectedDevbox.end_time_ms).toLocaleString()] }, "core-ended"));
225
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Ended: ", new Date(selectedDevbox.end_time_ms).toLocaleString()] }, "core-ended"));
162
226
  }
163
227
  lines.push(_jsx(Text, { children: " " }, "core-space"));
164
228
  // Capabilities
165
229
  if (selectedDevbox.capabilities && selectedDevbox.capabilities.length > 0) {
166
- lines.push(_jsx(Text, { color: "yellow", bold: true, children: "Capabilities" }, "cap-title"));
230
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Capabilities" }, "cap-title"));
167
231
  selectedDevbox.capabilities.forEach((cap, idx) => {
168
- lines.push(_jsxs(Text, { dimColor: true, children: [" ", figures.pointer, " ", cap] }, `cap-${idx}`));
232
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", figures.pointer, " ", cap] }, `cap-${idx}`));
169
233
  });
170
234
  lines.push(_jsx(Text, { children: " " }, "cap-space"));
171
235
  }
172
236
  // Launch Parameters
173
237
  if (selectedDevbox.launch_parameters) {
174
- lines.push(_jsx(Text, { color: "yellow", bold: true, children: "Launch Parameters" }, "launch-title"));
238
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Launch Parameters" }, "launch-title"));
175
239
  const lp = selectedDevbox.launch_parameters;
176
240
  if (lp.resource_size_request) {
177
- lines.push(_jsxs(Text, { dimColor: true, children: [" Resource Size Request: ", lp.resource_size_request] }, "launch-size-req"));
241
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Resource Size Request: ", lp.resource_size_request] }, "launch-size-req"));
178
242
  }
179
243
  if (lp.architecture) {
180
- lines.push(_jsxs(Text, { dimColor: true, children: [" Architecture: ", lp.architecture] }, "launch-arch"));
244
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Architecture: ", lp.architecture] }, "launch-arch"));
181
245
  }
182
246
  if (lp.custom_cpu_cores) {
183
- lines.push(_jsxs(Text, { dimColor: true, children: [" CPU Cores: ", lp.custom_cpu_cores] }, "launch-cpu"));
247
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "CPU Cores: ", lp.custom_cpu_cores] }, "launch-cpu"));
184
248
  }
185
249
  if (lp.custom_gb_memory) {
186
- lines.push(_jsxs(Text, { dimColor: true, children: [" Memory: ", lp.custom_gb_memory, "GB"] }, "launch-memory"));
250
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Memory: ", lp.custom_gb_memory, "GB"] }, "launch-memory"));
187
251
  }
188
252
  if (lp.custom_disk_size) {
189
- lines.push(_jsxs(Text, { dimColor: true, children: [" Disk Size: ", lp.custom_disk_size, "GB"] }, "launch-disk"));
253
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Disk Size: ", lp.custom_disk_size, "GB"] }, "launch-disk"));
190
254
  }
191
255
  if (lp.keep_alive_time_seconds) {
192
- lines.push(_jsxs(Text, { dimColor: true, children: [" Keep Alive: ", lp.keep_alive_time_seconds, "s (", Math.floor(lp.keep_alive_time_seconds / 60), "m)"] }, "launch-keepalive"));
256
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Keep Alive: ", lp.keep_alive_time_seconds, "s (", Math.floor(lp.keep_alive_time_seconds / 60), "m)"] }, "launch-keepalive"));
193
257
  }
194
258
  if (lp.after_idle) {
195
- lines.push(_jsxs(Text, { dimColor: true, children: [" After Idle: ", lp.after_idle.on_idle, " after ", lp.after_idle.idle_time_seconds, "s"] }, "launch-afteridle"));
259
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "After Idle: ", lp.after_idle.on_idle, " after", " ", lp.after_idle.idle_time_seconds, "s"] }, "launch-afteridle"));
196
260
  }
197
261
  if (lp.available_ports && lp.available_ports.length > 0) {
198
- lines.push(_jsxs(Text, { dimColor: true, children: [" Available Ports: ", lp.available_ports.join(', ')] }, "launch-ports"));
262
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Available Ports: ", lp.available_ports.join(", ")] }, "launch-ports"));
199
263
  }
200
264
  if (lp.launch_commands && lp.launch_commands.length > 0) {
201
- lines.push(_jsx(Text, { dimColor: true, children: " Launch Commands:" }, "launch-launch-cmds"));
265
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Launch Commands:"] }, "launch-launch-cmds"));
202
266
  lp.launch_commands.forEach((cmd, idx) => {
203
- lines.push(_jsxs(Text, { dimColor: true, children: [" ", figures.pointer, " ", cmd] }, `launch-cmd-${idx}`));
267
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", figures.pointer, " ", cmd] }, `launch-cmd-${idx}`));
204
268
  });
205
269
  }
206
270
  if (lp.required_services && lp.required_services.length > 0) {
207
- lines.push(_jsxs(Text, { dimColor: true, children: [" Required Services: ", lp.required_services.join(', ')] }, "launch-services"));
271
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Required Services: ", lp.required_services.join(", ")] }, "launch-services"));
208
272
  }
209
273
  if (lp.user_parameters) {
210
- lines.push(_jsx(Text, { dimColor: true, children: " User Parameters:" }, "launch-user"));
274
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "User Parameters:"] }, "launch-user"));
211
275
  if (lp.user_parameters.username) {
212
- lines.push(_jsxs(Text, { dimColor: true, children: [" Username: ", lp.user_parameters.username] }, "user-name"));
276
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Username: ", lp.user_parameters.username] }, "user-name"));
213
277
  }
214
278
  if (lp.user_parameters.uid) {
215
- lines.push(_jsxs(Text, { dimColor: true, children: [" UID: ", lp.user_parameters.uid] }, "user-uid"));
279
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "UID: ", lp.user_parameters.uid] }, "user-uid"));
216
280
  }
217
281
  }
218
282
  lines.push(_jsx(Text, { children: " " }, "launch-space"));
219
283
  }
220
284
  // Source
221
285
  if (selectedDevbox.blueprint_id || selectedDevbox.snapshot_id) {
222
- lines.push(_jsx(Text, { color: "yellow", bold: true, children: "Source" }, "source-title"));
286
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Source" }, "source-title"));
223
287
  if (selectedDevbox.blueprint_id) {
224
- lines.push(_jsxs(Text, { dimColor: true, children: [" Blueprint: ", selectedDevbox.blueprint_id] }, "source-bp"));
288
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Blueprint: ", selectedDevbox.blueprint_id] }, "source-bp"));
225
289
  }
226
290
  if (selectedDevbox.snapshot_id) {
227
- lines.push(_jsxs(Text, { dimColor: true, children: [" Snapshot: ", selectedDevbox.snapshot_id] }, "source-snap"));
291
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Snapshot: ", selectedDevbox.snapshot_id] }, "source-snap"));
228
292
  }
229
293
  lines.push(_jsx(Text, { children: " " }, "source-space"));
230
294
  }
231
295
  // Initiator
232
296
  if (selectedDevbox.initiator_type) {
233
- lines.push(_jsx(Text, { color: "yellow", bold: true, children: "Initiator" }, "init-title"));
234
- lines.push(_jsxs(Text, { dimColor: true, children: [" Type: ", selectedDevbox.initiator_type] }, "init-type"));
297
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Initiator" }, "init-title"));
298
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Type: ", selectedDevbox.initiator_type] }, "init-type"));
235
299
  if (selectedDevbox.initiator_id) {
236
- lines.push(_jsxs(Text, { dimColor: true, children: [" ID: ", selectedDevbox.initiator_id] }, "init-id"));
300
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "ID: ", selectedDevbox.initiator_id] }, "init-id"));
237
301
  }
238
302
  lines.push(_jsx(Text, { children: " " }, "init-space"));
239
303
  }
240
304
  // Status Details
241
305
  if (selectedDevbox.failure_reason || selectedDevbox.shutdown_reason) {
242
- lines.push(_jsx(Text, { color: "yellow", bold: true, children: "Status Details" }, "status-title"));
306
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Status Details" }, "status-title"));
243
307
  if (selectedDevbox.failure_reason) {
244
- lines.push(_jsxs(Text, { color: "red", dimColor: true, children: [" Failure Reason: ", selectedDevbox.failure_reason] }, "status-fail"));
308
+ lines.push(_jsxs(Text, { color: colors.error, dimColor: true, children: [" ", "Failure Reason: ", selectedDevbox.failure_reason] }, "status-fail"));
245
309
  }
246
310
  if (selectedDevbox.shutdown_reason) {
247
- lines.push(_jsxs(Text, { dimColor: true, children: [" Shutdown Reason: ", selectedDevbox.shutdown_reason] }, "status-shut"));
311
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Shutdown Reason: ", selectedDevbox.shutdown_reason] }, "status-shut"));
248
312
  }
249
313
  lines.push(_jsx(Text, { children: " " }, "status-space"));
250
314
  }
251
315
  // Metadata
252
- if (selectedDevbox.metadata && Object.keys(selectedDevbox.metadata).length > 0) {
253
- lines.push(_jsx(Text, { color: "yellow", bold: true, children: "Metadata" }, "meta-title"));
316
+ if (selectedDevbox.metadata &&
317
+ Object.keys(selectedDevbox.metadata).length > 0) {
318
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Metadata" }, "meta-title"));
254
319
  Object.entries(selectedDevbox.metadata).forEach(([key, value], idx) => {
255
- lines.push(_jsxs(Text, { dimColor: true, children: [" ", key, ": ", value] }, `meta-${idx}`));
320
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", key, ": ", value] }, `meta-${idx}`));
256
321
  });
257
322
  lines.push(_jsx(Text, { children: " " }, "meta-space"));
258
323
  }
259
324
  // State Transitions
260
- if (selectedDevbox.state_transitions && selectedDevbox.state_transitions.length > 0) {
261
- lines.push(_jsx(Text, { color: "yellow", bold: true, children: "State History" }, "state-title"));
325
+ if (selectedDevbox.state_transitions &&
326
+ selectedDevbox.state_transitions.length > 0) {
327
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "State History" }, "state-title"));
262
328
  selectedDevbox.state_transitions.forEach((transition, idx) => {
263
- const text = `${idx + 1}. ${capitalize(transition.status)}${transition.transition_time_ms ? ` at ${new Date(transition.transition_time_ms).toLocaleString()}` : ''}`;
264
- lines.push(_jsxs(Text, { dimColor: true, children: [" ", text] }, `state-${idx}`));
329
+ const text = `${idx + 1}. ${capitalize(transition.status)}${transition.transition_time_ms ? ` at ${new Date(transition.transition_time_ms).toLocaleString()}` : ""}`;
330
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", text] }, `state-${idx}`));
265
331
  });
266
332
  lines.push(_jsx(Text, { children: " " }, "state-space"));
267
333
  }
268
334
  // Raw JSON (full)
269
- lines.push(_jsx(Text, { color: "yellow", bold: true, children: "Raw JSON" }, "json-title"));
270
- const jsonLines = JSON.stringify(selectedDevbox, null, 2).split('\n');
335
+ lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Raw JSON" }, "json-title"));
336
+ const jsonLines = JSON.stringify(selectedDevbox, null, 2).split("\n");
271
337
  jsonLines.forEach((line, idx) => {
272
- lines.push(_jsxs(Text, { dimColor: true, children: [" ", line] }, `json-${idx}`));
338
+ lines.push(_jsxs(Text, { dimColor: true, children: [" ", line] }, `json-${idx}`));
273
339
  });
274
340
  return lines;
275
341
  };
276
342
  // Actions view - show the DevboxActionsMenu when an action is triggered
277
343
  if (showActions) {
344
+ const selectedOp = operations[selectedOperation];
278
345
  return (_jsx(DevboxActionsMenu, { devbox: selectedDevbox, onBack: () => {
279
346
  setShowActions(false);
280
347
  setSelectedOperation(0);
281
348
  }, breadcrumbItems: [
282
- { label: 'Devboxes' },
349
+ { label: "Devboxes" },
283
350
  { label: selectedDevbox.name || selectedDevbox.id },
284
- { label: 'Actions', active: true }
285
- ] }));
351
+ ], initialOperation: selectedOp?.key, skipOperationsMenu: true, onSSHRequest: onSSHRequest }));
286
352
  }
287
353
  // Detailed info mode - full screen
288
354
  if (showDetailedInfo) {
@@ -295,19 +361,32 @@ export const DevboxDetailPage = ({ devbox: initialDevbox, onBack }) => {
295
361
  const hasMore = actualScroll + viewportHeight < detailLines.length;
296
362
  const hasLess = actualScroll > 0;
297
363
  return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
298
- { label: 'Devboxes' },
364
+ { label: "Devboxes" },
299
365
  { label: selectedDevbox.name || selectedDevbox.id },
300
- { label: 'Full Details', active: true }
301
- ] }), _jsx(Header, { title: `${selectedDevbox.name || selectedDevbox.id} - Complete Information` }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsxs(Box, { marginBottom: 1, children: [_jsx(StatusBadge, { status: selectedDevbox.status }), _jsx(Text, { children: " " }), _jsx(Text, { color: "gray", dimColor: true, children: selectedDevbox.id })] }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Box, { flexDirection: "column", children: visibleLines }), hasLess && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "cyan", children: [figures.arrowUp, " More above"] }) })), hasMore && (_jsx(Box, { marginTop: hasLess ? 0 : 1, children: _jsxs(Text, { color: "cyan", children: [figures.arrowDown, " More below"] }) }))] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "gray", dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Scroll \u2022 [q or esc] Back to Details \u2022 Line ", actualScroll + 1, "-", Math.min(actualScroll + viewportHeight, detailLines.length), " of ", detailLines.length] }) })] }));
366
+ { label: "Full Details", active: true },
367
+ ] }), _jsx(Header, { title: `${selectedDevbox.name || selectedDevbox.id} - Complete Information` }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsxs(Box, { marginBottom: 1, children: [_jsx(StatusBadge, { status: selectedDevbox.status }), _jsx(Text, { children: " " }), _jsx(Text, { color: colors.textDim, dimColor: true, children: selectedDevbox.id })] }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, borderStyle: "round", borderColor: colors.border, paddingX: 2, paddingY: 1, children: [_jsx(Box, { flexDirection: "column", children: visibleLines }), hasLess && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.primary, children: [figures.arrowUp, " More above"] }) })), hasMore && (_jsx(Box, { marginTop: hasLess ? 0 : 1, children: _jsxs(Text, { color: colors.primary, children: [figures.arrowDown, " More below"] }) }))] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Scroll \u2022 [q or esc] Back to Details \u2022 Line", " ", actualScroll + 1, "-", Math.min(actualScroll + viewportHeight, detailLines.length), " of", " ", detailLines.length] }) })] }));
302
368
  }
303
369
  // Main detail view
304
370
  const lp = selectedDevbox.launch_parameters;
305
- const hasCapabilities = selectedDevbox.capabilities && selectedDevbox.capabilities.filter((c) => c !== 'unknown').length > 0;
371
+ const hasCapabilities = selectedDevbox.capabilities &&
372
+ selectedDevbox.capabilities.filter((c) => c !== "unknown").length >
373
+ 0;
306
374
  return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
307
- { label: 'Devboxes' },
308
- { label: selectedDevbox.name || selectedDevbox.id, active: true }
309
- ] }), _jsx(Header, { title: "Devbox Details" }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 0, children: [_jsxs(Box, { children: [_jsx(Text, { color: "cyan", bold: true, children: selectedDevbox.name || selectedDevbox.id }), _jsx(Text, { children: " " }), _jsx(StatusBadge, { status: selectedDevbox.status }), _jsxs(Text, { color: "gray", dimColor: true, children: [" \u2022 ", selectedDevbox.id] })] }), _jsxs(Box, { children: [_jsx(Text, { color: "gray", dimColor: true, children: formattedCreateTime }), _jsxs(Text, { color: "gray", dimColor: true, children: [" (", createTimeAgo, ")"] })] }), uptime !== null && selectedDevbox.status === 'running' && (_jsxs(Box, { children: [_jsxs(Text, { color: "green", dimColor: true, children: ["Uptime: ", uptime < 60 ? `${uptime}m` : `${Math.floor(uptime / 60)}h ${uptime % 60}m`] }), lp?.keep_alive_time_seconds && (_jsxs(Text, { color: "gray", dimColor: true, children: [" \u2022 Keep-alive: ", Math.floor(lp.keep_alive_time_seconds / 60), "m"] }))] }))] }), _jsxs(Box, { flexDirection: "row", gap: 1, children: [(lp?.resource_size_request || lp?.custom_cpu_cores || lp?.custom_gb_memory || lp?.custom_disk_size || lp?.architecture) && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 0, flexGrow: 1, children: [_jsxs(Text, { color: "yellow", bold: true, children: [figures.squareSmallFilled, " Resources"] }), _jsxs(Text, { dimColor: true, children: [lp?.resource_size_request && `${lp.resource_size_request}`, lp?.architecture && ` • ${lp.architecture}`, lp?.custom_cpu_cores && ` • ${lp.custom_cpu_cores}VCPU`, lp?.custom_gb_memory && ` • ${lp.custom_gb_memory}GB RAM`, lp?.custom_disk_size && ` • ${lp.custom_disk_size}GB DISC`] })] })), hasCapabilities && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "blue", paddingX: 1, paddingY: 0, flexGrow: 1, children: [_jsxs(Text, { color: "blue", bold: true, children: [figures.tick, " Capabilities"] }), _jsx(Text, { dimColor: true, children: selectedDevbox.capabilities.filter((c) => c !== 'unknown').join(', ') })] })), (selectedDevbox.blueprint_id || selectedDevbox.snapshot_id) && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, paddingY: 0, flexGrow: 1, children: [_jsxs(Text, { color: "magenta", bold: true, children: [figures.circleFilled, " Source"] }), _jsxs(Text, { dimColor: true, children: [selectedDevbox.blueprint_id && `BP: ${selectedDevbox.blueprint_id}`, selectedDevbox.snapshot_id && `Snap: ${selectedDevbox.snapshot_id}`] })] }))] }), selectedDevbox.metadata && Object.keys(selectedDevbox.metadata).length > 0 && (_jsx(Box, { borderStyle: "round", borderColor: "green", paddingX: 1, paddingY: 0, children: _jsx(MetadataDisplay, { metadata: selectedDevbox.metadata, showBorder: false }) })), selectedDevbox.failure_reason && (_jsxs(Box, { borderStyle: "round", borderColor: "red", paddingX: 1, paddingY: 0, children: [_jsxs(Text, { color: "red", bold: true, children: [figures.cross, " "] }), _jsx(Text, { color: "red", dimColor: true, children: selectedDevbox.failure_reason })] })), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: "cyan", bold: true, children: [figures.play, " Actions"] }), _jsx(Box, { flexDirection: "column", children: operations.map((op, index) => {
375
+ { label: "Devboxes" },
376
+ { label: selectedDevbox.name || selectedDevbox.id, active: true },
377
+ ] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.primary, bold: true, children: selectedDevbox.name || selectedDevbox.id }), _jsx(Text, { children: " " }), _jsx(StatusBadge, { status: selectedDevbox.status }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022 ", selectedDevbox.id] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, dimColor: true, children: formattedCreateTime }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "(", createTimeAgo, ")"] })] }), uptime !== null && selectedDevbox.status === "running" && (_jsxs(Box, { children: [_jsxs(Text, { color: colors.success, dimColor: true, children: ["Uptime:", " ", uptime < 60
378
+ ? `${uptime}m`
379
+ : `${Math.floor(uptime / 60)}h ${uptime % 60}m`] }), lp?.keep_alive_time_seconds && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022 Keep-alive: ", Math.floor(lp.keep_alive_time_seconds / 60), "m"] }))] }))] }), _jsxs(Box, { flexDirection: "row", gap: 1, marginBottom: 1, children: [(lp?.resource_size_request ||
380
+ lp?.custom_cpu_cores ||
381
+ lp?.custom_gb_memory ||
382
+ lp?.custom_disk_size ||
383
+ lp?.architecture) && (_jsxs(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [_jsxs(Text, { color: colors.warning, bold: true, children: [figures.squareSmallFilled, " Resources"] }), _jsxs(Text, { dimColor: true, children: [lp?.resource_size_request && `${lp.resource_size_request}`, lp?.architecture && ` • ${lp.architecture}`, lp?.custom_cpu_cores && ` • ${lp.custom_cpu_cores}VCPU`, lp?.custom_gb_memory && ` • ${lp.custom_gb_memory}GB RAM`, lp?.custom_disk_size && ` • ${lp.custom_disk_size}GB DISC`] })] })), hasCapabilities && (_jsxs(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [_jsxs(Text, { color: colors.info, bold: true, children: [figures.tick, " Capabilities"] }), _jsx(Text, { dimColor: true, children: selectedDevbox.capabilities
384
+ .filter((c) => c !== "unknown")
385
+ .join(", ") })] })), (selectedDevbox.blueprint_id || selectedDevbox.snapshot_id) && (_jsxs(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [_jsxs(Text, { color: colors.secondary, bold: true, children: [figures.circleFilled, " Source"] }), _jsxs(Text, { dimColor: true, children: [selectedDevbox.blueprint_id &&
386
+ `BP: ${selectedDevbox.blueprint_id}`, selectedDevbox.snapshot_id &&
387
+ `Snap: ${selectedDevbox.snapshot_id}`] })] }))] }), selectedDevbox.metadata &&
388
+ Object.keys(selectedDevbox.metadata).length > 0 && (_jsx(Box, { marginBottom: 1, paddingX: 1, children: _jsx(MetadataDisplay, { metadata: selectedDevbox.metadata, showBorder: false }) })), selectedDevbox.failure_reason && (_jsxs(Box, { marginBottom: 1, paddingX: 1, children: [_jsxs(Text, { color: colors.error, bold: true, children: [figures.cross, " "] }), _jsx(Text, { color: colors.error, dimColor: true, children: selectedDevbox.failure_reason })] })), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.play, " Actions"] }), _jsx(Box, { flexDirection: "column", children: operations.map((op, index) => {
310
389
  const isSelected = index === selectedOperation;
311
- return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? 'cyan' : 'gray', children: [isSelected ? figures.pointer : ' ', " "] }), _jsxs(Text, { color: isSelected ? op.color : 'gray', bold: isSelected, children: [op.icon, " ", op.label] }), _jsxs(Text, { color: "gray", dimColor: true, children: [" [", op.shortcut, "]"] })] }, op.key));
312
- }) })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "gray", dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [Enter] Execute \u2022 [i] Full Details \u2022 [o] Browser \u2022 [q] Back"] }) })] }));
390
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? colors.primary : colors.textDim, children: [isSelected ? figures.pointer : " ", " "] }), _jsxs(Text, { color: isSelected ? op.color : colors.textDim, bold: isSelected, children: [op.icon, " ", op.label] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", op.shortcut, "]"] })] }, op.key));
391
+ }) })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [Enter] Execute \u2022 [i] Full Details \u2022 [o] Browser \u2022 [q] Back"] }) })] }));
313
392
  };
@@ -1,6 +1,7 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
3
- import figures from 'figures';
4
- export const ErrorMessage = ({ message, error }) => {
5
- return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: "red", bold: true, children: [figures.cross, " ", message] }) }), error && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", dimColor: true, children: error.message }) }))] }));
2
+ import { Box, Text } from "ink";
3
+ import figures from "figures";
4
+ import { colors } from "../utils/theme.js";
5
+ export const ErrorMessage = ({ message, error, }) => {
6
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: colors.error, bold: true, children: [figures.cross, " ", message] }) }), error && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: error.message }) }))] }));
6
7
  };
@@ -1,6 +1,7 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { Box, Text } from 'ink';
2
+ import React from "react";
3
+ import { Box, Text } from "ink";
4
+ import { colors } from "../utils/theme.js";
4
5
  export const Header = React.memo(({ title, subtitle }) => {
5
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { bold: true, color: "#0a4d3a", children: ["\u258C", title] }), subtitle && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "gray", dimColor: true, children: subtitle })] }))] }), _jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: "#0a4d3a", children: ''.repeat(title.length + 1) }) })] }));
6
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { bold: true, color: colors.accent3, children: ["\u258C", title] }), subtitle && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: colors.textDim, dimColor: true, children: subtitle })] }))] }), _jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: colors.accent3, children: "".repeat(title.length + 1) }) })] }));
6
7
  });
@@ -0,0 +1,72 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { Box, Text, useInput, useApp } from "ink";
4
+ import figures from "figures";
5
+ import { Banner } from "./Banner.js";
6
+ import { Breadcrumb } from "./Breadcrumb.js";
7
+ import { VERSION } from "../cli.js";
8
+ import { colors } from "../utils/theme.js";
9
+ export const MainMenu = React.memo(({ onSelect }) => {
10
+ const { exit } = useApp();
11
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
12
+ // Calculate terminal height once at mount and memoize
13
+ const terminalHeight = React.useMemo(() => process.stdout.rows || 24, []);
14
+ const menuItems = React.useMemo(() => [
15
+ {
16
+ key: "devboxes",
17
+ label: "Devboxes",
18
+ description: "Manage cloud development environments",
19
+ icon: "◉",
20
+ color: colors.accent1,
21
+ },
22
+ {
23
+ key: "blueprints",
24
+ label: "Blueprints",
25
+ description: "Create and manage devbox templates",
26
+ icon: "▣",
27
+ color: colors.accent2,
28
+ },
29
+ {
30
+ key: "snapshots",
31
+ label: "Snapshots",
32
+ description: "Save and restore devbox states",
33
+ icon: "◈",
34
+ color: colors.accent3,
35
+ },
36
+ ], []);
37
+ useInput((input, key) => {
38
+ if (key.upArrow && selectedIndex > 0) {
39
+ setSelectedIndex(selectedIndex - 1);
40
+ }
41
+ else if (key.downArrow && selectedIndex < menuItems.length - 1) {
42
+ setSelectedIndex(selectedIndex + 1);
43
+ }
44
+ else if (key.return) {
45
+ onSelect(menuItems[selectedIndex].key);
46
+ }
47
+ else if (key.escape) {
48
+ exit();
49
+ }
50
+ else if (input === "d" || input === "1") {
51
+ onSelect("devboxes");
52
+ }
53
+ else if (input === "b" || input === "2") {
54
+ onSelect("blueprints");
55
+ }
56
+ else if (input === "s" || input === "3") {
57
+ onSelect("snapshots");
58
+ }
59
+ });
60
+ // Use compact layout if terminal height is less than 20 lines (memoized)
61
+ const useCompactLayout = React.useMemo(() => terminalHeight < 20, [terminalHeight]);
62
+ if (useCompactLayout) {
63
+ return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsxs(Box, { paddingX: 2, marginBottom: 1, children: [_jsx(Text, { color: colors.primary, bold: true, children: "RUNLOOP.ai" }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022 Cloud development environments \u2022 v", VERSION] })] }), _jsx(Box, { flexDirection: "column", paddingX: 2, children: menuItems.map((item, index) => {
64
+ const isSelected = index === selectedIndex;
65
+ return (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: isSelected ? item.color : colors.textDim, children: isSelected ? figures.pointer : " " }), _jsx(Text, { children: " " }), _jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "- ", item.description] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", index + 1, "]"] })] }, item.key));
66
+ }) }), _jsx(Box, { paddingX: 2, marginTop: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [1-3] Quick select \u2022 [Enter] Select \u2022 [Esc] Quit"] }) })] }));
67
+ }
68
+ return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(Breadcrumb, { items: [{ label: "Home", active: true }] }), _jsx(Box, { flexShrink: 0, children: _jsx(Banner, {}) }), _jsx(Box, { flexDirection: "column", paddingX: 2, flexShrink: 0, children: _jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Cloud development environments for your team \u2022 v", VERSION] }) }) }), _jsxs(Box, { flexDirection: "column", paddingX: 2, marginTop: 1, flexGrow: 1, children: [_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(Text, { color: colors.text, bold: true, children: "Select a resource:" }) }), menuItems.map((item, index) => {
69
+ const isSelected = index === selectedIndex;
70
+ return (_jsxs(Box, { paddingX: 2, paddingY: 0, borderStyle: isSelected ? "round" : "single", borderColor: isSelected ? item.color : colors.border, marginTop: index === 0 ? 1 : 0, flexShrink: 0, children: [_jsx(Text, { color: item.color, bold: true, children: item.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: isSelected ? item.color : colors.text, bold: isSelected, children: item.label }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.textDim, dimColor: true, children: item.description }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["[", index + 1, "]"] })] }, item.key));
71
+ })] }), _jsx(Box, { paddingX: 2, flexShrink: 0, children: _jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [1-3] Quick select \u2022 [Enter] Select \u2022 [Esc] Quit"] }) }) })] }));
72
+ });