@runloop/rl-cli 0.0.3 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +64 -29
  2. package/dist/cli.js +401 -92
  3. package/dist/commands/auth.js +12 -11
  4. package/dist/commands/blueprint/create.js +108 -0
  5. package/dist/commands/blueprint/get.js +37 -0
  6. package/dist/commands/blueprint/list.js +293 -225
  7. package/dist/commands/blueprint/logs.js +40 -0
  8. package/dist/commands/blueprint/preview.js +45 -0
  9. package/dist/commands/devbox/create.js +10 -9
  10. package/dist/commands/devbox/delete.js +8 -8
  11. package/dist/commands/devbox/download.js +49 -0
  12. package/dist/commands/devbox/exec.js +23 -13
  13. package/dist/commands/devbox/execAsync.js +43 -0
  14. package/dist/commands/devbox/get.js +37 -0
  15. package/dist/commands/devbox/getAsync.js +37 -0
  16. package/dist/commands/devbox/list.js +328 -190
  17. package/dist/commands/devbox/logs.js +40 -0
  18. package/dist/commands/devbox/read.js +49 -0
  19. package/dist/commands/devbox/resume.js +37 -0
  20. package/dist/commands/devbox/rsync.js +118 -0
  21. package/dist/commands/devbox/scp.js +122 -0
  22. package/dist/commands/devbox/shutdown.js +37 -0
  23. package/dist/commands/devbox/ssh.js +104 -0
  24. package/dist/commands/devbox/suspend.js +37 -0
  25. package/dist/commands/devbox/tunnel.js +120 -0
  26. package/dist/commands/devbox/upload.js +10 -10
  27. package/dist/commands/devbox/write.js +51 -0
  28. package/dist/commands/mcp-http.js +37 -0
  29. package/dist/commands/mcp-install.js +120 -0
  30. package/dist/commands/mcp.js +30 -0
  31. package/dist/commands/menu.js +20 -20
  32. package/dist/commands/object/delete.js +37 -0
  33. package/dist/commands/object/download.js +88 -0
  34. package/dist/commands/object/get.js +37 -0
  35. package/dist/commands/object/list.js +112 -0
  36. package/dist/commands/object/upload.js +130 -0
  37. package/dist/commands/snapshot/create.js +12 -11
  38. package/dist/commands/snapshot/delete.js +8 -8
  39. package/dist/commands/snapshot/list.js +56 -97
  40. package/dist/commands/snapshot/status.js +37 -0
  41. package/dist/components/ActionsPopup.js +16 -13
  42. package/dist/components/Banner.js +4 -4
  43. package/dist/components/Breadcrumb.js +55 -5
  44. package/dist/components/DetailView.js +7 -4
  45. package/dist/components/DevboxActionsMenu.js +315 -178
  46. package/dist/components/DevboxCard.js +15 -14
  47. package/dist/components/DevboxCreatePage.js +147 -113
  48. package/dist/components/DevboxDetailPage.js +180 -102
  49. package/dist/components/ErrorMessage.js +5 -4
  50. package/dist/components/Header.js +4 -3
  51. package/dist/components/MainMenu.js +34 -33
  52. package/dist/components/MetadataDisplay.js +17 -9
  53. package/dist/components/OperationsMenu.js +6 -5
  54. package/dist/components/ResourceActionsMenu.js +117 -0
  55. package/dist/components/ResourceListView.js +213 -0
  56. package/dist/components/Spinner.js +5 -4
  57. package/dist/components/StatusBadge.js +81 -31
  58. package/dist/components/SuccessMessage.js +4 -3
  59. package/dist/components/Table.example.js +53 -23
  60. package/dist/components/Table.js +19 -11
  61. package/dist/hooks/useCursorPagination.js +125 -0
  62. package/dist/mcp/server-http.js +416 -0
  63. package/dist/mcp/server.js +397 -0
  64. package/dist/utils/CommandExecutor.js +16 -12
  65. package/dist/utils/client.js +7 -7
  66. package/dist/utils/config.js +130 -4
  67. package/dist/utils/interactiveCommand.js +2 -2
  68. package/dist/utils/output.js +17 -17
  69. package/dist/utils/ssh.js +160 -0
  70. package/dist/utils/sshSession.js +16 -12
  71. package/dist/utils/theme.js +22 -0
  72. package/dist/utils/url.js +4 -4
  73. package/package.json +29 -4
@@ -1,24 +1,25 @@
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, useApp, useStdout } from 'ink';
4
- import TextInput from 'ink-text-input';
5
- import figures from 'figures';
6
- import { getClient } from '../utils/client.js';
7
- import { Header } from './Header.js';
8
- import { SpinnerComponent } from './Spinner.js';
9
- import { ErrorMessage } from './ErrorMessage.js';
10
- import { SuccessMessage } from './SuccessMessage.js';
11
- import { Breadcrumb } from './Breadcrumb.js';
2
+ import React from "react";
3
+ import { Box, Text, useInput, useApp, useStdout } from "ink";
4
+ import TextInput from "ink-text-input";
5
+ import figures from "figures";
6
+ import { getClient } from "../utils/client.js";
7
+ import { Header } from "./Header.js";
8
+ import { SpinnerComponent } from "./Spinner.js";
9
+ import { ErrorMessage } from "./ErrorMessage.js";
10
+ import { SuccessMessage } from "./SuccessMessage.js";
11
+ import { Breadcrumb } from "./Breadcrumb.js";
12
+ import { colors } from "../utils/theme.js";
12
13
  export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
13
- { label: 'Devboxes' },
14
- { label: devbox.name || devbox.id, active: true }
14
+ { label: "Devboxes" },
15
+ { label: devbox.name || devbox.id, active: true },
15
16
  ], initialOperation, initialOperationIndex = 0, skipOperationsMenu = false, onSSHRequest, }) => {
16
17
  const { exit } = useApp();
17
18
  const { stdout } = useStdout();
18
19
  const [loading, setLoading] = React.useState(false);
19
20
  const [selectedOperation, setSelectedOperation] = React.useState(initialOperationIndex);
20
21
  const [executingOperation, setExecutingOperation] = React.useState(initialOperation || null);
21
- const [operationInput, setOperationInput] = React.useState('');
22
+ const [operationInput, setOperationInput] = React.useState("");
22
23
  const [operationResult, setOperationResult] = React.useState(null);
23
24
  const [operationError, setOperationError] = React.useState(null);
24
25
  const [logsWrapMode, setLogsWrapMode] = React.useState(false);
@@ -26,38 +27,99 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
26
27
  const [execScroll, setExecScroll] = React.useState(0);
27
28
  const [copyStatus, setCopyStatus] = React.useState(null);
28
29
  const allOperations = [
29
- { key: 'logs', label: 'View Logs', color: 'blue', icon: figures.info, shortcut: 'l' },
30
- { key: 'exec', label: 'Execute Command', color: 'green', icon: figures.play, shortcut: 'e' },
31
- { key: 'upload', label: 'Upload File', color: 'green', icon: figures.arrowUp, shortcut: 'u' },
32
- { key: 'snapshot', label: 'Create Snapshot', color: 'yellow', icon: figures.circleFilled, shortcut: 'n' },
33
- { key: 'ssh', label: 'SSH onto the box', color: 'cyan', icon: figures.arrowRight, shortcut: 's' },
34
- { key: 'tunnel', label: 'Open Tunnel', color: 'magenta', icon: figures.pointerSmall, shortcut: 't' },
35
- { key: 'suspend', label: 'Suspend Devbox', color: 'yellow', icon: figures.squareSmallFilled, shortcut: 'p' },
36
- { key: 'resume', label: 'Resume Devbox', color: 'green', icon: figures.play, shortcut: 'r' },
37
- { key: 'delete', label: 'Shutdown Devbox', color: 'red', icon: figures.cross, shortcut: 'd' },
30
+ {
31
+ key: "logs",
32
+ label: "View Logs",
33
+ color: colors.info,
34
+ icon: figures.info,
35
+ shortcut: "l",
36
+ },
37
+ {
38
+ key: "exec",
39
+ label: "Execute Command",
40
+ color: colors.success,
41
+ icon: figures.play,
42
+ shortcut: "e",
43
+ },
44
+ {
45
+ key: "upload",
46
+ label: "Upload File",
47
+ color: colors.success,
48
+ icon: figures.arrowUp,
49
+ shortcut: "u",
50
+ },
51
+ {
52
+ key: "snapshot",
53
+ label: "Create Snapshot",
54
+ color: colors.warning,
55
+ icon: figures.circleFilled,
56
+ shortcut: "n",
57
+ },
58
+ {
59
+ key: "ssh",
60
+ label: "SSH onto the box",
61
+ color: colors.primary,
62
+ icon: figures.arrowRight,
63
+ shortcut: "s",
64
+ },
65
+ {
66
+ key: "tunnel",
67
+ label: "Open Tunnel",
68
+ color: colors.secondary,
69
+ icon: figures.pointerSmall,
70
+ shortcut: "t",
71
+ },
72
+ {
73
+ key: "suspend",
74
+ label: "Suspend Devbox",
75
+ color: colors.warning,
76
+ icon: figures.squareSmallFilled,
77
+ shortcut: "p",
78
+ },
79
+ {
80
+ key: "resume",
81
+ label: "Resume Devbox",
82
+ color: colors.success,
83
+ icon: figures.play,
84
+ shortcut: "r",
85
+ },
86
+ {
87
+ key: "delete",
88
+ label: "Shutdown Devbox",
89
+ color: colors.error,
90
+ icon: figures.cross,
91
+ shortcut: "d",
92
+ },
38
93
  ];
39
94
  // Filter operations based on devbox status
40
- const operations = devbox ? allOperations.filter(op => {
41
- const status = devbox.status;
42
- // When suspended: logs and resume
43
- if (status === 'suspended') {
44
- return op.key === 'resume' || op.key === 'logs';
45
- }
46
- // When not running (shutdown, failure, etc): only logs
47
- if (status !== 'running' && status !== 'provisioning' && status !== 'initializing') {
48
- return op.key === 'logs';
49
- }
50
- // When running: everything except resume
51
- if (status === 'running') {
52
- return op.key !== 'resume';
53
- }
54
- // Default for transitional states (provisioning, initializing)
55
- return op.key === 'logs' || op.key === 'delete';
56
- }) : allOperations;
95
+ const operations = devbox
96
+ ? allOperations.filter((op) => {
97
+ const status = devbox.status;
98
+ // When suspended: logs and resume
99
+ if (status === "suspended") {
100
+ return op.key === "resume" || op.key === "logs";
101
+ }
102
+ // When not running (shutdown, failure, etc): only logs
103
+ if (status !== "running" &&
104
+ status !== "provisioning" &&
105
+ status !== "initializing") {
106
+ return op.key === "logs";
107
+ }
108
+ // When running: everything except resume
109
+ if (status === "running") {
110
+ return op.key !== "resume";
111
+ }
112
+ // Default for transitional states (provisioning, initializing)
113
+ return op.key === "logs" || op.key === "delete";
114
+ })
115
+ : allOperations;
57
116
  // Auto-execute operations that don't need input
58
117
  React.useEffect(() => {
59
- const autoExecuteOps = ['delete', 'ssh', 'logs', 'suspend', 'resume'];
60
- if (executingOperation && autoExecuteOps.includes(executingOperation) && !loading && devbox) {
118
+ const autoExecuteOps = ["delete", "ssh", "logs", "suspend", "resume"];
119
+ if (executingOperation &&
120
+ autoExecuteOps.includes(executingOperation) &&
121
+ !loading &&
122
+ devbox) {
61
123
  executeOperation();
62
124
  }
63
125
  }, [executingOperation]);
@@ -67,16 +129,16 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
67
129
  if (key.return && operationInput.trim()) {
68
130
  executeOperation();
69
131
  }
70
- else if (input === 'q' || key.escape) {
132
+ else if (input === "q" || key.escape) {
71
133
  console.clear();
72
134
  setExecutingOperation(null);
73
- setOperationInput('');
135
+ setOperationInput("");
74
136
  }
75
137
  return;
76
138
  }
77
139
  // Handle operation result display
78
140
  if (operationResult || operationError) {
79
- if (input === 'q' || key.escape || key.return) {
141
+ if (input === "q" || key.escape || key.return) {
80
142
  console.clear();
81
143
  // If skipOperationsMenu is true, go back to parent instead of operations menu
82
144
  if (skipOperationsMenu) {
@@ -86,144 +148,197 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
86
148
  setOperationResult(null);
87
149
  setOperationError(null);
88
150
  setExecutingOperation(null);
89
- setOperationInput('');
151
+ setOperationInput("");
90
152
  setLogsWrapMode(true);
91
153
  setLogsScroll(0);
92
154
  setExecScroll(0);
93
155
  setCopyStatus(null);
94
156
  }
95
157
  }
96
- else if ((key.upArrow || input === 'k') && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'exec') {
158
+ else if ((key.upArrow || input === "k") &&
159
+ operationResult &&
160
+ typeof operationResult === "object" &&
161
+ operationResult.__customRender === "exec") {
97
162
  setExecScroll(Math.max(0, execScroll - 1));
98
163
  }
99
- else if ((key.downArrow || input === 'j') && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'exec') {
164
+ else if ((key.downArrow || input === "j") &&
165
+ operationResult &&
166
+ typeof operationResult === "object" &&
167
+ operationResult.__customRender === "exec") {
100
168
  setExecScroll(execScroll + 1);
101
169
  }
102
- else if (key.pageUp && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'exec') {
170
+ else if (key.pageUp &&
171
+ operationResult &&
172
+ typeof operationResult === "object" &&
173
+ operationResult.__customRender === "exec") {
103
174
  setExecScroll(Math.max(0, execScroll - 10));
104
175
  }
105
- else if (key.pageDown && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'exec') {
176
+ else if (key.pageDown &&
177
+ operationResult &&
178
+ typeof operationResult === "object" &&
179
+ operationResult.__customRender === "exec") {
106
180
  setExecScroll(execScroll + 10);
107
181
  }
108
- else if (input === 'g' && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'exec') {
182
+ else if (input === "g" &&
183
+ operationResult &&
184
+ typeof operationResult === "object" &&
185
+ operationResult.__customRender === "exec") {
109
186
  setExecScroll(0);
110
187
  }
111
- else if (input === 'G' && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'exec') {
112
- const lines = [...(operationResult.stdout || '').split('\n'), ...(operationResult.stderr || '').split('\n')];
188
+ else if (input === "G" &&
189
+ operationResult &&
190
+ typeof operationResult === "object" &&
191
+ operationResult.__customRender === "exec") {
192
+ const lines = [
193
+ ...(operationResult.stdout || "").split("\n"),
194
+ ...(operationResult.stderr || "").split("\n"),
195
+ ];
113
196
  const terminalHeight = stdout?.rows || 30;
114
197
  const viewportHeight = Math.max(10, terminalHeight - 15);
115
198
  const maxScroll = Math.max(0, lines.length - viewportHeight);
116
199
  setExecScroll(maxScroll);
117
200
  }
118
- else if (input === 'c' && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'exec') {
201
+ else if (input === "c" &&
202
+ operationResult &&
203
+ typeof operationResult === "object" &&
204
+ operationResult.__customRender === "exec") {
119
205
  // Copy exec output to clipboard
120
- const output = (operationResult.stdout || '') + (operationResult.stderr || '');
206
+ const output = (operationResult.stdout || "") +
207
+ (operationResult.stderr || "");
121
208
  const copyToClipboard = async (text) => {
122
- const { spawn } = await import('child_process');
209
+ const { spawn } = await import("child_process");
123
210
  const platform = process.platform;
124
211
  let command;
125
212
  let args;
126
- if (platform === 'darwin') {
127
- command = 'pbcopy';
213
+ if (platform === "darwin") {
214
+ command = "pbcopy";
128
215
  args = [];
129
216
  }
130
- else if (platform === 'win32') {
131
- command = 'clip';
217
+ else if (platform === "win32") {
218
+ command = "clip";
132
219
  args = [];
133
220
  }
134
221
  else {
135
- command = 'xclip';
136
- args = ['-selection', 'clipboard'];
222
+ command = "xclip";
223
+ args = ["-selection", "clipboard"];
137
224
  }
138
225
  const proc = spawn(command, args);
139
226
  proc.stdin.write(text);
140
227
  proc.stdin.end();
141
- proc.on('exit', (code) => {
228
+ proc.on("exit", (code) => {
142
229
  if (code === 0) {
143
- setCopyStatus('Copied to clipboard!');
230
+ setCopyStatus("Copied to clipboard!");
144
231
  setTimeout(() => setCopyStatus(null), 2000);
145
232
  }
146
233
  else {
147
- setCopyStatus('Failed to copy');
234
+ setCopyStatus("Failed to copy");
148
235
  setTimeout(() => setCopyStatus(null), 2000);
149
236
  }
150
237
  });
151
- proc.on('error', () => {
152
- setCopyStatus('Copy not supported');
238
+ proc.on("error", () => {
239
+ setCopyStatus("Copy not supported");
153
240
  setTimeout(() => setCopyStatus(null), 2000);
154
241
  });
155
242
  };
156
243
  copyToClipboard(output);
157
244
  }
158
- else if ((key.upArrow || input === 'k') && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'logs') {
245
+ else if ((key.upArrow || input === "k") &&
246
+ operationResult &&
247
+ typeof operationResult === "object" &&
248
+ operationResult.__customRender === "logs") {
159
249
  setLogsScroll(Math.max(0, logsScroll - 1));
160
250
  }
161
- else if ((key.downArrow || input === 'j') && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'logs') {
251
+ else if ((key.downArrow || input === "j") &&
252
+ operationResult &&
253
+ typeof operationResult === "object" &&
254
+ operationResult.__customRender === "logs") {
162
255
  setLogsScroll(logsScroll + 1);
163
256
  }
164
- else if (key.pageUp && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'logs') {
257
+ else if (key.pageUp &&
258
+ operationResult &&
259
+ typeof operationResult === "object" &&
260
+ operationResult.__customRender === "logs") {
165
261
  setLogsScroll(Math.max(0, logsScroll - 10));
166
262
  }
167
- else if (key.pageDown && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'logs') {
263
+ else if (key.pageDown &&
264
+ operationResult &&
265
+ typeof operationResult === "object" &&
266
+ operationResult.__customRender === "logs") {
168
267
  setLogsScroll(logsScroll + 10);
169
268
  }
170
- else if (input === 'g' && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'logs') {
269
+ else if (input === "g" &&
270
+ operationResult &&
271
+ typeof operationResult === "object" &&
272
+ operationResult.__customRender === "logs") {
171
273
  setLogsScroll(0);
172
274
  }
173
- else if (input === 'G' && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'logs') {
275
+ else if (input === "G" &&
276
+ operationResult &&
277
+ typeof operationResult === "object" &&
278
+ operationResult.__customRender === "logs") {
174
279
  const logs = operationResult.__logs || [];
175
280
  const terminalHeight = stdout?.rows || 30;
176
281
  const viewportHeight = Math.max(10, terminalHeight - 10);
177
282
  const maxScroll = Math.max(0, logs.length - viewportHeight);
178
283
  setLogsScroll(maxScroll);
179
284
  }
180
- else if (input === 'w' && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'logs') {
285
+ else if (input === "w" &&
286
+ operationResult &&
287
+ typeof operationResult === "object" &&
288
+ operationResult.__customRender === "logs") {
181
289
  setLogsWrapMode(!logsWrapMode);
182
290
  }
183
- else if (input === 'c' && operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'logs') {
291
+ else if (input === "c" &&
292
+ operationResult &&
293
+ typeof operationResult === "object" &&
294
+ operationResult.__customRender === "logs") {
184
295
  // Copy logs to clipboard
185
296
  const logs = operationResult.__logs || [];
186
- const logsText = logs.map((log) => {
297
+ const logsText = logs
298
+ .map((log) => {
187
299
  const time = new Date(log.timestamp_ms).toLocaleString();
188
- const level = log.level || 'INFO';
189
- const source = log.source || 'exec';
190
- const message = log.message || '';
191
- const cmd = log.cmd ? `[${log.cmd}] ` : '';
192
- const exitCode = log.exit_code !== null && log.exit_code !== undefined ? `(${log.exit_code}) ` : '';
300
+ const level = log.level || "INFO";
301
+ const source = log.source || "exec";
302
+ const message = log.message || "";
303
+ const cmd = log.cmd ? `[${log.cmd}] ` : "";
304
+ const exitCode = log.exit_code !== null && log.exit_code !== undefined
305
+ ? `(${log.exit_code}) `
306
+ : "";
193
307
  return `${time} ${level}/${source} ${exitCode}${cmd}${message}`;
194
- }).join('\n');
308
+ })
309
+ .join("\n");
195
310
  const copyToClipboard = async (text) => {
196
- const { spawn } = await import('child_process');
311
+ const { spawn } = await import("child_process");
197
312
  const platform = process.platform;
198
313
  let command;
199
314
  let args;
200
- if (platform === 'darwin') {
201
- command = 'pbcopy';
315
+ if (platform === "darwin") {
316
+ command = "pbcopy";
202
317
  args = [];
203
318
  }
204
- else if (platform === 'win32') {
205
- command = 'clip';
319
+ else if (platform === "win32") {
320
+ command = "clip";
206
321
  args = [];
207
322
  }
208
323
  else {
209
- command = 'xclip';
210
- args = ['-selection', 'clipboard'];
324
+ command = "xclip";
325
+ args = ["-selection", "clipboard"];
211
326
  }
212
327
  const proc = spawn(command, args);
213
328
  proc.stdin.write(text);
214
329
  proc.stdin.end();
215
- proc.on('exit', (code) => {
330
+ proc.on("exit", (code) => {
216
331
  if (code === 0) {
217
- setCopyStatus('Copied to clipboard!');
332
+ setCopyStatus("Copied to clipboard!");
218
333
  setTimeout(() => setCopyStatus(null), 2000);
219
334
  }
220
335
  else {
221
- setCopyStatus('Failed to copy');
336
+ setCopyStatus("Failed to copy");
222
337
  setTimeout(() => setCopyStatus(null), 2000);
223
338
  }
224
339
  });
225
- proc.on('error', () => {
226
- setCopyStatus('Copy not supported');
340
+ proc.on("error", () => {
341
+ setCopyStatus("Copy not supported");
227
342
  setTimeout(() => setCopyStatus(null), 2000);
228
343
  });
229
344
  };
@@ -232,7 +347,7 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
232
347
  return;
233
348
  }
234
349
  // Operations selection mode
235
- if (input === 'q' || key.escape) {
350
+ if (input === "q" || key.escape) {
236
351
  console.clear();
237
352
  onBack();
238
353
  setSelectedOperation(0);
@@ -250,7 +365,7 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
250
365
  }
251
366
  else if (input) {
252
367
  // Check if input matches any operation shortcut
253
- const matchedOp = operations.find(op => op.shortcut === input);
368
+ const matchedOp = operations.find((op) => op.shortcut === input);
254
369
  if (matchedOp) {
255
370
  console.clear();
256
371
  setExecutingOperation(matchedOp.key);
@@ -262,48 +377,50 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
262
377
  try {
263
378
  setLoading(true);
264
379
  switch (executingOperation) {
265
- case 'exec':
380
+ case "exec":
266
381
  const execResult = await client.devboxes.executeSync(devbox.id, {
267
382
  command: operationInput,
268
383
  });
269
384
  // Format exec result for custom rendering
270
385
  const formattedExecResult = {
271
- __customRender: 'exec',
386
+ __customRender: "exec",
272
387
  command: operationInput,
273
- stdout: execResult.stdout || '',
274
- stderr: execResult.stderr || '',
388
+ stdout: execResult.stdout || "",
389
+ stderr: execResult.stderr || "",
275
390
  exitCode: execResult.exit_code ?? 0,
276
391
  };
277
392
  setOperationResult(formattedExecResult);
278
393
  break;
279
- case 'upload':
280
- const fs = await import('fs');
394
+ case "upload":
395
+ const fs = await import("fs");
281
396
  const fileStream = fs.createReadStream(operationInput);
282
- const filename = operationInput.split('/').pop() || 'file';
397
+ const filename = operationInput.split("/").pop() || "file";
283
398
  await client.devboxes.uploadFile(devbox.id, {
284
399
  path: filename,
285
400
  file: fileStream,
286
401
  });
287
402
  setOperationResult(`File ${filename} uploaded successfully`);
288
403
  break;
289
- case 'snapshot':
404
+ case "snapshot":
290
405
  const snapshot = await client.devboxes.snapshotDisk(devbox.id, {
291
406
  name: operationInput || `snapshot-${Date.now()}`,
292
407
  });
293
408
  setOperationResult(`Snapshot created: ${snapshot.id}`);
294
409
  break;
295
- case 'ssh':
410
+ case "ssh":
296
411
  const sshKey = await client.devboxes.createSSHKey(devbox.id);
297
- const fsModule = await import('fs');
298
- const pathModule = await import('path');
299
- const osModule = await import('os');
300
- const sshDir = pathModule.join(osModule.homedir(), '.runloop', 'ssh_keys');
412
+ const fsModule = await import("fs");
413
+ const pathModule = await import("path");
414
+ const osModule = await import("os");
415
+ const sshDir = pathModule.join(osModule.homedir(), ".runloop", "ssh_keys");
301
416
  fsModule.mkdirSync(sshDir, { recursive: true });
302
417
  const keyPath = pathModule.join(sshDir, `${devbox.id}.pem`);
303
- fsModule.writeFileSync(keyPath, sshKey.ssh_private_key, { mode: 0o600 });
304
- const sshUser = devbox.launch_parameters?.user_parameters?.username || 'user';
418
+ fsModule.writeFileSync(keyPath, sshKey.ssh_private_key, {
419
+ mode: 0o600,
420
+ });
421
+ const sshUser = devbox.launch_parameters?.user_parameters?.username || "user";
305
422
  const env = process.env.RUNLOOP_ENV?.toLowerCase();
306
- const sshHost = env === 'dev' ? 'ssh.runloop.pro' : 'ssh.runloop.ai';
423
+ const sshHost = env === "dev" ? "ssh.runloop.pro" : "ssh.runloop.ai";
307
424
  const proxyCommand = `openssl s_client -quiet -verify_quiet -servername %h -connect ${sshHost}:443 2>/dev/null`;
308
425
  const sshConfig = {
309
426
  keyPath,
@@ -311,7 +428,7 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
311
428
  sshUser,
312
429
  url: sshKey.url,
313
430
  devboxId: devbox.id,
314
- devboxName: devbox.name || devbox.id
431
+ devboxName: devbox.name || devbox.id,
315
432
  };
316
433
  // Notify parent that SSH is requested
317
434
  if (onSSHRequest) {
@@ -319,43 +436,45 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
319
436
  exit();
320
437
  }
321
438
  else {
322
- setOperationError(new Error('SSH session handler not configured'));
439
+ setOperationError(new Error("SSH session handler not configured"));
323
440
  }
324
441
  break;
325
- case 'logs':
442
+ case "logs":
326
443
  const logsResult = await client.devboxes.logs.list(devbox.id);
327
444
  if (logsResult.logs.length === 0) {
328
- setOperationResult('No logs available for this devbox.');
445
+ setOperationResult("No logs available for this devbox.");
329
446
  }
330
447
  else {
331
- logsResult.__customRender = 'logs';
448
+ logsResult.__customRender = "logs";
332
449
  logsResult.__logs = logsResult.logs;
333
450
  logsResult.__totalCount = logsResult.logs.length;
334
451
  setOperationResult(logsResult);
335
452
  }
336
453
  break;
337
- case 'tunnel':
454
+ case "tunnel":
338
455
  const port = parseInt(operationInput);
339
456
  if (isNaN(port) || port < 1 || port > 65535) {
340
- setOperationError(new Error('Invalid port number. Please enter a port between 1 and 65535.'));
457
+ setOperationError(new Error("Invalid port number. Please enter a port between 1 and 65535."));
341
458
  }
342
459
  else {
343
- const tunnel = await client.devboxes.createTunnel(devbox.id, { port });
460
+ const tunnel = await client.devboxes.createTunnel(devbox.id, {
461
+ port,
462
+ });
344
463
  setOperationResult(`Tunnel created!\n\n` +
345
464
  `Local Port: ${port}\n` +
346
465
  `Public URL: ${tunnel.url}\n\n` +
347
466
  `You can now access port ${port} on the devbox via:\n${tunnel.url}`);
348
467
  }
349
468
  break;
350
- case 'suspend':
469
+ case "suspend":
351
470
  await client.devboxes.suspend(devbox.id);
352
471
  setOperationResult(`Devbox ${devbox.id} suspended successfully`);
353
472
  break;
354
- case 'resume':
473
+ case "resume":
355
474
  await client.devboxes.resume(devbox.id);
356
475
  setOperationResult(`Devbox ${devbox.id} resumed successfully`);
357
476
  break;
358
- case 'delete':
477
+ case "delete":
359
478
  await client.devboxes.shutdown(devbox.id);
360
479
  setOperationResult(`Devbox ${devbox.id} shut down successfully`);
361
480
  break;
@@ -368,18 +487,20 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
368
487
  setLoading(false);
369
488
  }
370
489
  };
371
- const operationLabel = operations.find((o) => o.key === executingOperation)?.label || 'Operation';
490
+ const operationLabel = operations.find((o) => o.key === executingOperation)?.label || "Operation";
372
491
  // Operation result display
373
492
  if (operationResult || operationError) {
374
493
  // Check for custom exec rendering
375
- if (operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'exec') {
376
- const command = operationResult.command || '';
377
- const stdout = operationResult.stdout || '';
378
- const stderr = operationResult.stderr || '';
494
+ if (operationResult &&
495
+ typeof operationResult === "object" &&
496
+ operationResult.__customRender === "exec") {
497
+ const command = operationResult.command || "";
498
+ const stdout = operationResult.stdout || "";
499
+ const stderr = operationResult.stderr || "";
379
500
  const exitCode = operationResult.exitCode;
380
- const stdoutLines = stdout ? stdout.split('\n') : [];
381
- const stderrLines = stderr ? stderr.split('\n') : [];
382
- const allLines = [...stdoutLines, ...stderrLines].filter(line => line !== '');
501
+ const stdoutLines = stdout ? stdout.split("\n") : [];
502
+ const stderrLines = stderr ? stderr.split("\n") : [];
503
+ const allLines = [...stdoutLines, ...stderrLines].filter((line) => line !== "");
383
504
  const terminalHeight = stdout?.rows || 30;
384
505
  const viewportHeight = Math.max(10, terminalHeight - 15);
385
506
  const maxScroll = Math.max(0, allLines.length - viewportHeight);
@@ -387,16 +508,21 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
387
508
  const visibleLines = allLines.slice(actualScroll, actualScroll + viewportHeight);
388
509
  const hasMore = actualScroll + viewportHeight < allLines.length;
389
510
  const hasLess = actualScroll > 0;
390
- const exitCodeColor = exitCode === 0 ? 'green' : 'red';
391
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: 'Execute Command', active: true }] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: "cyan", bold: true, children: [figures.play, " Command:"] }), _jsx(Text, { children: " " }), _jsx(Text, { color: "white", children: command })] }), _jsxs(Box, { children: [_jsx(Text, { color: "gray", dimColor: true, children: "Exit Code: " }), _jsx(Text, { color: exitCodeColor, bold: true, children: exitCode })] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [allLines.length === 0 && (_jsx(Text, { color: "gray", dimColor: true, children: "No output" })), visibleLines.map((line, index) => {
511
+ const exitCodeColor = exitCode === 0 ? colors.success : colors.error;
512
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
513
+ ...breadcrumbItems,
514
+ { label: "Execute Command", active: true },
515
+ ] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.primary, paddingX: 1, marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.play, " Command:"] }), _jsx(Text, { children: " " }), _jsx(Text, { color: colors.text, children: command })] }), _jsxs(Box, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Exit Code:", " "] }), _jsx(Text, { color: exitCodeColor, bold: true, children: exitCode })] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.border, paddingX: 1, children: [allLines.length === 0 && (_jsx(Text, { color: colors.textDim, dimColor: true, children: "No output" })), visibleLines.map((line, index) => {
392
516
  const actualIndex = actualScroll + index;
393
517
  const isStderr = actualIndex >= stdoutLines.length;
394
- const lineColor = isStderr ? 'red' : 'white';
518
+ const lineColor = isStderr ? colors.error : colors.text;
395
519
  return (_jsx(Box, { children: _jsx(Text, { color: lineColor, children: line }) }, index));
396
- }), 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"] }) }))] }), _jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsxs(Text, { color: "cyan", bold: true, children: [figures.hamburger, " ", allLines.length] }), _jsx(Text, { color: "gray", dimColor: true, children: " lines" }), allLines.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", dimColor: true, children: " \u2022 " }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Viewing ", actualScroll + 1, "-", Math.min(actualScroll + viewportHeight, allLines.length), " of ", allLines.length] })] })), stdout && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", dimColor: true, children: " \u2022 " }), _jsxs(Text, { color: "green", dimColor: true, children: ["stdout: ", stdoutLines.length, " lines"] })] })), stderr && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", dimColor: true, children: " \u2022 " }), _jsxs(Text, { color: "red", dimColor: true, children: ["stderr: ", stderrLines.length, " lines"] })] })), copyStatus && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", dimColor: true, children: " \u2022 " }), _jsx(Text, { color: "green", bold: true, children: copyStatus })] }))] }), _jsx(Box, { marginTop: 1, paddingX: 1, children: _jsxs(Text, { color: "gray", dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [g] Top \u2022 [G] Bottom \u2022 [c] Copy \u2022 [Enter], [q], or [esc] Back"] }) })] }));
520
+ }), 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"] }) }))] }), _jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.hamburger, " ", allLines.length] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "lines"] }), allLines.length > 0 && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Viewing ", actualScroll + 1, "-", Math.min(actualScroll + viewportHeight, allLines.length), " of", " ", allLines.length] })] })), stdout && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.success, dimColor: true, children: ["stdout: ", stdoutLines.length, " lines"] })] })), stderr && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.error, dimColor: true, children: ["stderr: ", stderrLines.length, " lines"] })] })), copyStatus && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsx(Text, { color: colors.success, bold: true, children: copyStatus })] }))] }), _jsx(Box, { marginTop: 1, paddingX: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [g] Top \u2022 [G] Bottom \u2022 [c] Copy \u2022 [Enter], [q], or [esc] Back"] }) })] }));
397
521
  }
398
522
  // Check for custom logs rendering
399
- if (operationResult && typeof operationResult === 'object' && operationResult.__customRender === 'logs') {
523
+ if (operationResult &&
524
+ typeof operationResult === "object" &&
525
+ operationResult.__customRender === "logs") {
400
526
  const logs = operationResult.__logs || [];
401
527
  const totalCount = operationResult.__totalCount || 0;
402
528
  const terminalHeight = stdout?.rows || 30;
@@ -407,74 +533,85 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
407
533
  const visibleLogs = logs.slice(actualScroll, actualScroll + viewportHeight);
408
534
  const hasMore = actualScroll + viewportHeight < logs.length;
409
535
  const hasLess = actualScroll > 0;
410
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: 'Logs', active: true }] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [visibleLogs.map((log, index) => {
536
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: "Logs", active: true }] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.border, paddingX: 1, children: [visibleLogs.map((log, index) => {
411
537
  const time = new Date(log.timestamp_ms).toLocaleTimeString();
412
- const level = log.level ? log.level[0].toUpperCase() : 'I';
413
- const source = log.source ? log.source.substring(0, 8) : 'exec';
414
- const fullMessage = log.message || '';
415
- const cmd = log.cmd ? `[${log.cmd.substring(0, 40)}${log.cmd.length > 40 ? '...' : ''}] ` : '';
416
- const exitCode = log.exit_code !== null && log.exit_code !== undefined ? `(${log.exit_code}) ` : '';
417
- let levelColor = 'gray';
418
- if (level === 'E')
419
- levelColor = 'red';
420
- else if (level === 'W')
421
- levelColor = 'yellow';
422
- else if (level === 'I')
423
- levelColor = 'cyan';
538
+ const level = log.level ? log.level[0].toUpperCase() : "I";
539
+ const source = log.source ? log.source.substring(0, 8) : "exec";
540
+ const fullMessage = log.message || "";
541
+ const cmd = log.cmd
542
+ ? `[${log.cmd.substring(0, 40)}${log.cmd.length > 40 ? "..." : ""}] `
543
+ : "";
544
+ const exitCode = log.exit_code !== null && log.exit_code !== undefined
545
+ ? `(${log.exit_code}) `
546
+ : "";
547
+ let levelColor = colors.textDim;
548
+ if (level === "E")
549
+ levelColor = colors.error;
550
+ else if (level === "W")
551
+ levelColor = colors.warning;
552
+ else if (level === "I")
553
+ levelColor = colors.primary;
424
554
  if (logsWrapMode) {
425
- return (_jsxs(Box, { children: [_jsx(Text, { color: "gray", dimColor: true, children: time }), _jsx(Text, { children: " " }), _jsx(Text, { color: levelColor, bold: true, children: level }), _jsxs(Text, { color: "gray", dimColor: true, children: ["/", source] }), _jsx(Text, { children: " " }), exitCode && _jsx(Text, { color: "yellow", children: exitCode }), cmd && _jsx(Text, { color: "blue", dimColor: true, children: cmd }), _jsx(Text, { children: fullMessage })] }, index));
555
+ return (_jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, dimColor: true, children: time }), _jsx(Text, { children: " " }), _jsx(Text, { color: levelColor, bold: true, children: level }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["/", source] }), _jsx(Text, { children: " " }), exitCode && _jsx(Text, { color: colors.warning, children: exitCode }), cmd && (_jsx(Text, { color: colors.info, dimColor: true, children: cmd })), _jsx(Text, { children: fullMessage })] }, index));
426
556
  }
427
557
  else {
428
558
  const metadataWidth = 11 + 1 + 1 + 1 + 8 + 1 + exitCode.length + cmd.length + 6;
429
559
  const availableMessageWidth = Math.max(20, terminalWidth - metadataWidth);
430
560
  const truncatedMessage = fullMessage.length > availableMessageWidth
431
- ? fullMessage.substring(0, availableMessageWidth - 3) + '...'
561
+ ? fullMessage.substring(0, availableMessageWidth - 3) +
562
+ "..."
432
563
  : fullMessage;
433
- return (_jsxs(Box, { children: [_jsx(Text, { color: "gray", dimColor: true, children: time }), _jsx(Text, { children: " " }), _jsx(Text, { color: levelColor, bold: true, children: level }), _jsxs(Text, { color: "gray", dimColor: true, children: ["/", source] }), _jsx(Text, { children: " " }), exitCode && _jsx(Text, { color: "yellow", children: exitCode }), cmd && _jsx(Text, { color: "blue", dimColor: true, children: cmd }), _jsx(Text, { children: truncatedMessage })] }, index));
564
+ return (_jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, dimColor: true, children: time }), _jsx(Text, { children: " " }), _jsx(Text, { color: levelColor, bold: true, children: level }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["/", source] }), _jsx(Text, { children: " " }), exitCode && _jsx(Text, { color: colors.warning, children: exitCode }), cmd && (_jsx(Text, { color: colors.info, dimColor: true, children: cmd })), _jsx(Text, { children: truncatedMessage })] }, index));
434
565
  }
435
- }), hasLess && (_jsx(Box, { children: _jsxs(Text, { color: "cyan", children: [figures.arrowUp, " More above"] }) })), hasMore && (_jsx(Box, { children: _jsxs(Text, { color: "cyan", children: [figures.arrowDown, " More below"] }) }))] }), _jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsxs(Text, { color: "cyan", bold: true, children: [figures.hamburger, " ", totalCount] }), _jsx(Text, { color: "gray", dimColor: true, children: " total logs" }), _jsx(Text, { color: "gray", dimColor: true, children: " \u2022 " }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Viewing ", actualScroll + 1, "-", Math.min(actualScroll + viewportHeight, logs.length), " of ", logs.length] }), _jsx(Text, { color: "gray", dimColor: true, children: " \u2022 " }), _jsx(Text, { color: logsWrapMode ? 'green' : 'gray', bold: logsWrapMode, children: logsWrapMode ? 'Wrap: ON' : 'Wrap: OFF' }), copyStatus && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", dimColor: true, children: " \u2022 " }), _jsx(Text, { color: "green", bold: true, children: copyStatus })] }))] }), _jsx(Box, { marginTop: 1, paddingX: 1, children: _jsxs(Text, { color: "gray", dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [g] Top \u2022 [G] Bottom \u2022 [w] Toggle Wrap \u2022 [c] Copy \u2022 [Enter], [q], or [esc] Back"] }) })] }));
566
+ }), hasLess && (_jsx(Box, { children: _jsxs(Text, { color: colors.primary, children: [figures.arrowUp, " More above"] }) })), hasMore && (_jsx(Box, { children: _jsxs(Text, { color: colors.primary, children: [figures.arrowDown, " More below"] }) }))] }), _jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.hamburger, " ", totalCount] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "total logs"] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Viewing ", actualScroll + 1, "-", Math.min(actualScroll + viewportHeight, logs.length), " of", " ", logs.length] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsx(Text, { color: logsWrapMode ? colors.success : colors.textDim, bold: logsWrapMode, children: logsWrapMode ? "Wrap: ON" : "Wrap: OFF" }), copyStatus && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsx(Text, { color: colors.success, bold: true, children: copyStatus })] }))] }), _jsx(Box, { marginTop: 1, paddingX: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [g] Top \u2022 [G] Bottom \u2022 [w] Toggle Wrap \u2022 [c] Copy \u2022 [Enter], [q], or [esc] Back"] }) })] }));
436
567
  }
437
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: operationLabel, active: true }] }), _jsx(Header, { title: "Operation Result" }), operationResult && _jsx(SuccessMessage, { message: operationResult }), operationError && _jsx(ErrorMessage, { message: "Operation failed", error: operationError }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press [Enter], [q], or [esc] to continue" }) })] }));
568
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: operationLabel, active: true }] }), _jsx(Header, { title: "Operation Result" }), operationResult && _jsx(SuccessMessage, { message: operationResult }), operationError && (_jsx(ErrorMessage, { message: "Operation failed", error: operationError })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press [Enter], [q], or [esc] to continue" }) })] }));
438
569
  }
439
570
  // Operation input mode
440
571
  if (executingOperation && devbox) {
441
- const needsInput = executingOperation === 'exec' ||
442
- executingOperation === 'upload' ||
443
- executingOperation === 'snapshot' ||
444
- executingOperation === 'tunnel';
572
+ const needsInput = executingOperation === "exec" ||
573
+ executingOperation === "upload" ||
574
+ executingOperation === "snapshot" ||
575
+ executingOperation === "tunnel";
445
576
  if (loading) {
446
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: operationLabel, active: true }] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: "Please wait..." })] }));
577
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
578
+ ...breadcrumbItems,
579
+ { label: operationLabel, active: true },
580
+ ] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: "Please wait..." })] }));
447
581
  }
448
582
  if (!needsInput) {
449
583
  const messages = {
450
- ssh: 'Creating SSH key...',
451
- logs: 'Fetching logs...',
452
- suspend: 'Suspending devbox...',
453
- resume: 'Resuming devbox...',
454
- delete: 'Shutting down devbox...',
584
+ ssh: "Creating SSH key...",
585
+ logs: "Fetching logs...",
586
+ suspend: "Suspending devbox...",
587
+ resume: "Resuming devbox...",
588
+ delete: "Shutting down devbox...",
455
589
  };
456
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: operationLabel, active: true }] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: messages[executingOperation] || 'Please wait...' })] }));
590
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
591
+ ...breadcrumbItems,
592
+ { label: operationLabel, active: true },
593
+ ] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: messages[executingOperation] || "Please wait..." })] }));
457
594
  }
458
595
  const prompts = {
459
- exec: 'Command to execute:',
460
- upload: 'File path to upload:',
461
- snapshot: 'Snapshot name (optional):',
462
- tunnel: 'Port number to expose:',
596
+ exec: "Command to execute:",
597
+ upload: "File path to upload:",
598
+ snapshot: "Snapshot name (optional):",
599
+ tunnel: "Port number to expose:",
463
600
  };
464
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: operationLabel, active: true }] }), _jsx(Header, { title: operationLabel }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "cyan", bold: true, children: devbox.name || devbox.id }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: [prompts[executingOperation], " "] }) }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: operationInput, onChange: setOperationInput, placeholder: executingOperation === 'exec'
465
- ? 'ls -la'
466
- : executingOperation === 'upload'
467
- ? '/path/to/file'
468
- : executingOperation === 'tunnel'
469
- ? '8080'
470
- : 'my-snapshot' }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press [Enter] to execute \u2022 [q or esc] Cancel" }) })] })] }));
601
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [...breadcrumbItems, { label: operationLabel, active: true }] }), _jsx(Header, { title: operationLabel }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.primary, bold: true, children: devbox.name || devbox.id }) }), _jsx(Box, { children: _jsxs(Text, { color: colors.textDim, children: [prompts[executingOperation], " "] }) }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: operationInput, onChange: setOperationInput, placeholder: executingOperation === "exec"
602
+ ? "ls -la"
603
+ : executingOperation === "upload"
604
+ ? "/path/to/file"
605
+ : executingOperation === "tunnel"
606
+ ? "8080"
607
+ : "my-snapshot" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press [Enter] to execute \u2022 [q or esc] Cancel" }) })] })] }));
471
608
  }
472
609
  // Operations selection mode - only show if not skipping
473
610
  if (!skipOperationsMenu || !executingOperation) {
474
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "cyan", bold: true, children: [figures.play, " Operations"] }), _jsx(Box, { flexDirection: "column", children: operations.map((op, index) => {
611
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems, showVersionCheck: true }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.play, " Operations"] }), _jsx(Box, { flexDirection: "column", children: operations.map((op, index) => {
475
612
  const isSelected = index === selectedOperation;
476
- 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));
477
- }) })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "gray", dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [Enter] Select \u2022 [q] Back"] }) })] }));
613
+ 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));
614
+ }) })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [Enter] Select \u2022 [q] Back"] }) })] }));
478
615
  }
479
616
  // If skipOperationsMenu is true and executingOperation is set, show loading while it executes
480
617
  return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsx(SpinnerComponent, { message: "Loading..." })] }));