@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, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
3
- import figures from 'figures';
2
+ import { Box, Text } from "ink";
3
+ import figures from "figures";
4
+ import { colors } from "../utils/theme.js";
4
5
  export const DevboxCard = ({ id, name, status, createdAt, }) => {
5
6
  const getStatusDisplay = (status) => {
6
7
  switch (status) {
7
- case 'running':
8
- return { icon: figures.tick, color: 'green' };
9
- case 'provisioning':
10
- case 'initializing':
11
- return { icon: figures.ellipsis, color: 'yellow' };
12
- case 'stopped':
13
- case 'suspended':
14
- return { icon: figures.circle, color: 'gray' };
15
- case 'failed':
16
- return { icon: figures.cross, color: 'red' };
8
+ case "running":
9
+ return { icon: figures.tick, color: colors.success };
10
+ case "provisioning":
11
+ case "initializing":
12
+ return { icon: figures.ellipsis, color: colors.warning };
13
+ case "stopped":
14
+ case "suspended":
15
+ return { icon: figures.circle, color: colors.textDim };
16
+ case "failed":
17
+ return { icon: figures.cross, color: colors.error };
17
18
  default:
18
- return { icon: figures.circle, color: 'gray' };
19
+ return { icon: figures.circle, color: colors.textDim };
19
20
  }
20
21
  };
21
22
  const statusDisplay = getStatusDisplay(status);
22
23
  const displayName = name || id;
23
- return (_jsxs(Box, { children: [_jsx(Text, { color: statusDisplay.color, children: statusDisplay.icon }), _jsx(Text, { children: " " }), _jsx(Box, { width: 20, children: _jsx(Text, { color: "cyan", bold: true, children: displayName.slice(0, 18) }) }), _jsx(Text, { color: "gray", dimColor: true, children: id }), createdAt && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "gray", dimColor: true, children: new Date(createdAt).toLocaleDateString() })] }))] }));
24
+ return (_jsxs(Box, { children: [_jsx(Text, { color: statusDisplay.color, children: statusDisplay.icon }), _jsx(Text, { children: " " }), _jsx(Box, { width: 20, children: _jsx(Text, { color: colors.primary, bold: true, children: displayName.slice(0, 18) }) }), _jsx(Text, { color: colors.textDim, dimColor: true, children: id }), createdAt && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: colors.textDim, dimColor: true, children: new Date(createdAt).toLocaleDateString() })] }))] }));
24
25
  };
@@ -1,30 +1,31 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { Box, Text, useInput } from 'ink';
4
- import TextInput from 'ink-text-input';
5
- import figures from 'figures';
6
- import { getClient } from '../utils/client.js';
7
- import { SpinnerComponent } from './Spinner.js';
8
- import { ErrorMessage } from './ErrorMessage.js';
9
- import { SuccessMessage } from './SuccessMessage.js';
10
- import { Breadcrumb } from './Breadcrumb.js';
11
- import { MetadataDisplay } from './MetadataDisplay.js';
12
- export const DevboxCreatePage = ({ onBack, onCreate }) => {
13
- const [currentField, setCurrentField] = React.useState('create');
2
+ import React from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import TextInput from "ink-text-input";
5
+ import figures from "figures";
6
+ import { getClient } from "../utils/client.js";
7
+ import { SpinnerComponent } from "./Spinner.js";
8
+ import { ErrorMessage } from "./ErrorMessage.js";
9
+ import { SuccessMessage } from "./SuccessMessage.js";
10
+ import { Breadcrumb } from "./Breadcrumb.js";
11
+ import { MetadataDisplay } from "./MetadataDisplay.js";
12
+ import { colors } from "../utils/theme.js";
13
+ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, }) => {
14
+ const [currentField, setCurrentField] = React.useState("create");
14
15
  const [formData, setFormData] = React.useState({
15
- name: '',
16
- architecture: 'arm64',
17
- resource_size: 'SMALL',
18
- custom_cpu: '',
19
- custom_memory: '',
20
- custom_disk: '',
21
- keep_alive: '3600',
16
+ name: "",
17
+ architecture: "arm64",
18
+ resource_size: "SMALL",
19
+ custom_cpu: "",
20
+ custom_memory: "",
21
+ custom_disk: "",
22
+ keep_alive: "3600",
22
23
  metadata: {},
23
- blueprint_id: '',
24
- snapshot_id: '',
24
+ blueprint_id: initialBlueprintId || "",
25
+ snapshot_id: "",
25
26
  });
26
- const [metadataKey, setMetadataKey] = React.useState('');
27
- const [metadataValue, setMetadataValue] = React.useState('');
27
+ const [metadataKey, setMetadataKey] = React.useState("");
28
+ const [metadataValue, setMetadataValue] = React.useState("");
28
29
  const [inMetadataSection, setInMetadataSection] = React.useState(false);
29
30
  const [metadataInputMode, setMetadataInputMode] = React.useState(null);
30
31
  const [selectedMetadataIndex, setSelectedMetadataIndex] = React.useState(-1); // -1 means "add new" row
@@ -32,33 +33,45 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
32
33
  const [result, setResult] = React.useState(null);
33
34
  const [error, setError] = React.useState(null);
34
35
  const baseFields = [
35
- { key: 'create', label: 'Devbox Create', type: 'action' },
36
- { key: 'name', label: 'Name', type: 'text' },
37
- { key: 'architecture', label: 'Architecture', type: 'select' },
38
- { key: 'resource_size', label: 'Resource Size', type: 'select' },
36
+ { key: "create", label: "Devbox Create", type: "action" },
37
+ { key: "name", label: "Name", type: "text" },
38
+ { key: "architecture", label: "Architecture", type: "select" },
39
+ { key: "resource_size", label: "Resource Size", type: "select" },
39
40
  ];
40
41
  // Add custom resource fields if CUSTOM_SIZE is selected
41
- const customFields = formData.resource_size === 'CUSTOM_SIZE'
42
+ const customFields = formData.resource_size === "CUSTOM_SIZE"
42
43
  ? [
43
- { key: 'custom_cpu', label: 'CPU Cores (2-16, even)', type: 'text' },
44
- { key: 'custom_memory', label: 'Memory GB (2-64, even)', type: 'text' },
45
- { key: 'custom_disk', label: 'Disk GB (2-64, even)', type: 'text' },
44
+ { key: "custom_cpu", label: "CPU Cores (2-16, even)", type: "text" },
45
+ {
46
+ key: "custom_memory",
47
+ label: "Memory GB (2-64, even)",
48
+ type: "text",
49
+ },
50
+ { key: "custom_disk", label: "Disk GB (2-64, even)", type: "text" },
46
51
  ]
47
52
  : [];
48
53
  const remainingFields = [
49
- { key: 'keep_alive', label: 'Keep Alive (seconds)', type: 'text' },
50
- { key: 'blueprint_id', label: 'Blueprint ID (optional)', type: 'text' },
51
- { key: 'snapshot_id', label: 'Snapshot ID (optional)', type: 'text' },
52
- { key: 'metadata', label: 'Metadata (optional)', type: 'metadata' },
54
+ { key: "keep_alive", label: "Keep Alive (seconds)", type: "text" },
55
+ { key: "blueprint_id", label: "Blueprint ID (optional)", type: "text" },
56
+ { key: "snapshot_id", label: "Snapshot ID (optional)", type: "text" },
57
+ { key: "metadata", label: "Metadata (optional)", type: "metadata" },
53
58
  ];
54
59
  const fields = [...baseFields, ...customFields, ...remainingFields];
55
- const architectures = ['arm64', 'x86_64'];
56
- const resourceSizes = ['X_SMALL', 'SMALL', 'MEDIUM', 'LARGE', 'X_LARGE', 'XX_LARGE', 'CUSTOM_SIZE'];
60
+ const architectures = ["arm64", "x86_64"];
61
+ const resourceSizes = [
62
+ "X_SMALL",
63
+ "SMALL",
64
+ "MEDIUM",
65
+ "LARGE",
66
+ "X_LARGE",
67
+ "XX_LARGE",
68
+ "CUSTOM_SIZE",
69
+ ];
57
70
  const currentFieldIndex = fields.findIndex((f) => f.key === currentField);
58
71
  useInput((input, key) => {
59
72
  // Handle result screen
60
73
  if (result) {
61
- if (input === 'q' || key.escape || key.return) {
74
+ if (input === "q" || key.escape || key.return) {
62
75
  if (onCreate) {
63
76
  onCreate(result);
64
77
  }
@@ -68,11 +81,11 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
68
81
  }
69
82
  // Handle error screen
70
83
  if (error) {
71
- if (input === 'r' || key.return) {
84
+ if (input === "r" || key.return) {
72
85
  // Retry - clear error and return to form
73
86
  setError(null);
74
87
  }
75
- else if (input === 'q' || key.escape) {
88
+ else if (input === "q" || key.escape) {
76
89
  // Quit - go back to list
77
90
  onBack();
78
91
  }
@@ -83,18 +96,18 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
83
96
  return;
84
97
  }
85
98
  // Back to list
86
- if (input === 'q' || key.escape) {
99
+ if (input === "q" || key.escape) {
87
100
  console.clear();
88
101
  onBack();
89
102
  return;
90
103
  }
91
104
  // Submit form
92
- if (input === 's' && key.ctrl) {
105
+ if (input === "s" && key.ctrl) {
93
106
  handleCreate();
94
107
  return;
95
108
  }
96
109
  // Handle Enter on create field
97
- if (currentField === 'create' && key.return) {
110
+ if (currentField === "create" && key.return) {
98
111
  handleCreate();
99
112
  return;
100
113
  }
@@ -105,11 +118,11 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
105
118
  const maxIndex = metadataKeys.length + 1;
106
119
  // Handle input mode (typing key or value)
107
120
  if (metadataInputMode) {
108
- if (metadataInputMode === 'key' && key.return && metadataKey.trim()) {
109
- setMetadataInputMode('value');
121
+ if (metadataInputMode === "key" && key.return && metadataKey.trim()) {
122
+ setMetadataInputMode("value");
110
123
  return;
111
124
  }
112
- else if (metadataInputMode === 'value' && key.return) {
125
+ else if (metadataInputMode === "value" && key.return) {
113
126
  if (metadataKey.trim() && metadataValue.trim()) {
114
127
  setFormData({
115
128
  ...formData,
@@ -119,22 +132,22 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
119
132
  },
120
133
  });
121
134
  }
122
- setMetadataKey('');
123
- setMetadataValue('');
135
+ setMetadataKey("");
136
+ setMetadataValue("");
124
137
  setMetadataInputMode(null);
125
138
  setSelectedMetadataIndex(0); // Back to "add new" row
126
139
  return;
127
140
  }
128
141
  else if (key.escape) {
129
142
  // Cancel input
130
- setMetadataKey('');
131
- setMetadataValue('');
143
+ setMetadataKey("");
144
+ setMetadataValue("");
132
145
  setMetadataInputMode(null);
133
146
  return;
134
147
  }
135
148
  else if (key.tab) {
136
149
  // Tab between key and value
137
- setMetadataInputMode(metadataInputMode === 'key' ? 'value' : 'key');
150
+ setMetadataInputMode(metadataInputMode === "key" ? "value" : "key");
138
151
  return;
139
152
  }
140
153
  return; // Don't process other keys while in input mode
@@ -149,31 +162,34 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
149
162
  else if (key.return) {
150
163
  if (selectedMetadataIndex === 0) {
151
164
  // Add new
152
- setMetadataKey('');
153
- setMetadataValue('');
154
- setMetadataInputMode('key');
165
+ setMetadataKey("");
166
+ setMetadataValue("");
167
+ setMetadataInputMode("key");
155
168
  }
156
169
  else if (selectedMetadataIndex === maxIndex) {
157
170
  // Done - exit metadata section
158
171
  setInMetadataSection(false);
159
172
  setSelectedMetadataIndex(0);
160
- setMetadataKey('');
161
- setMetadataValue('');
173
+ setMetadataKey("");
174
+ setMetadataValue("");
162
175
  setMetadataInputMode(null);
163
176
  }
164
- else if (selectedMetadataIndex >= 1 && selectedMetadataIndex <= metadataKeys.length) {
177
+ else if (selectedMetadataIndex >= 1 &&
178
+ selectedMetadataIndex <= metadataKeys.length) {
165
179
  // Edit existing (selectedMetadataIndex - 1 gives array index)
166
180
  const keyToEdit = metadataKeys[selectedMetadataIndex - 1];
167
- setMetadataKey(keyToEdit || '');
168
- setMetadataValue(formData.metadata[keyToEdit] || '');
181
+ setMetadataKey(keyToEdit || "");
182
+ setMetadataValue(formData.metadata[keyToEdit] || "");
169
183
  // Remove old entry
170
184
  const newMetadata = { ...formData.metadata };
171
185
  delete newMetadata[keyToEdit];
172
186
  setFormData({ ...formData, metadata: newMetadata });
173
- setMetadataInputMode('key');
187
+ setMetadataInputMode("key");
174
188
  }
175
189
  }
176
- else if ((input === 'd' || key.delete) && selectedMetadataIndex >= 1 && selectedMetadataIndex <= metadataKeys.length) {
190
+ else if ((input === "d" || key.delete) &&
191
+ selectedMetadataIndex >= 1 &&
192
+ selectedMetadataIndex <= metadataKeys.length) {
177
193
  // Delete selected item (selectedMetadataIndex - 1 gives array index)
178
194
  const keyToDelete = metadataKeys[selectedMetadataIndex - 1];
179
195
  const newMetadata = { ...formData.metadata };
@@ -185,12 +201,12 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
185
201
  setSelectedMetadataIndex(Math.max(0, newLength));
186
202
  }
187
203
  }
188
- else if (key.escape || input === 'q') {
204
+ else if (key.escape || input === "q") {
189
205
  // Exit metadata section
190
206
  setInMetadataSection(false);
191
207
  setSelectedMetadataIndex(0);
192
- setMetadataKey('');
193
- setMetadataValue('');
208
+ setMetadataKey("");
209
+ setMetadataValue("");
194
210
  setMetadataInputMode(null);
195
211
  }
196
212
  return;
@@ -207,46 +223,55 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
207
223
  return;
208
224
  }
209
225
  // Enter key on metadata field to enter metadata section
210
- if (currentField === 'metadata' && key.return) {
226
+ if (currentField === "metadata" && key.return) {
211
227
  setInMetadataSection(true);
212
228
  setSelectedMetadataIndex(0); // Start at "add new" row
213
229
  return;
214
230
  }
215
231
  // Handle select fields
216
- if (field && field.type === 'select' && (key.leftArrow || key.rightArrow)) {
217
- if (currentField === 'architecture') {
232
+ if (field && field.type === "select" && (key.leftArrow || key.rightArrow)) {
233
+ if (currentField === "architecture") {
218
234
  const currentIndex = architectures.indexOf(formData.architecture);
219
235
  const newIndex = key.leftArrow
220
236
  ? Math.max(0, currentIndex - 1)
221
237
  : Math.min(architectures.length - 1, currentIndex + 1);
222
- setFormData({ ...formData, architecture: architectures[newIndex] });
238
+ setFormData({
239
+ ...formData,
240
+ architecture: architectures[newIndex],
241
+ });
223
242
  }
224
- else if (currentField === 'resource_size') {
243
+ else if (currentField === "resource_size") {
225
244
  const currentIndex = resourceSizes.indexOf(formData.resource_size);
226
245
  const newIndex = key.leftArrow
227
246
  ? Math.max(0, currentIndex - 1)
228
247
  : Math.min(resourceSizes.length - 1, currentIndex + 1);
229
- setFormData({ ...formData, resource_size: resourceSizes[newIndex] });
248
+ setFormData({
249
+ ...formData,
250
+ resource_size: resourceSizes[newIndex],
251
+ });
230
252
  }
231
253
  return;
232
254
  }
233
255
  });
234
256
  // Validate custom resource configuration
235
257
  const validateCustomResources = () => {
236
- if (formData.resource_size !== 'CUSTOM_SIZE') {
258
+ if (formData.resource_size !== "CUSTOM_SIZE") {
237
259
  return null;
238
260
  }
239
261
  const cpu = parseInt(formData.custom_cpu);
240
262
  const memory = parseInt(formData.custom_memory);
241
263
  const disk = parseInt(formData.custom_disk);
242
- if (formData.custom_cpu && (isNaN(cpu) || cpu < 2 || cpu > 16 || cpu % 2 !== 0)) {
243
- return 'CPU cores must be an even number between 2 and 16';
264
+ if (formData.custom_cpu &&
265
+ (isNaN(cpu) || cpu < 2 || cpu > 16 || cpu % 2 !== 0)) {
266
+ return "CPU cores must be an even number between 2 and 16";
244
267
  }
245
- if (formData.custom_memory && (isNaN(memory) || memory < 2 || memory > 64 || memory % 2 !== 0)) {
246
- return 'Memory must be an even number between 2 and 64 GB';
268
+ if (formData.custom_memory &&
269
+ (isNaN(memory) || memory < 2 || memory > 64 || memory % 2 !== 0)) {
270
+ return "Memory must be an even number between 2 and 64 GB";
247
271
  }
248
- if (formData.custom_disk && (isNaN(disk) || disk < 2 || disk > 64 || disk % 2 !== 0)) {
249
- return 'Disk must be an even number between 2 and 64 GB';
272
+ if (formData.custom_disk &&
273
+ (isNaN(disk) || disk < 2 || disk > 64 || disk % 2 !== 0)) {
274
+ return "Disk must be an even number between 2 and 64 GB";
250
275
  }
251
276
  // Validate CPU to memory ratio (1:2 to 1:8)
252
277
  if (formData.custom_cpu && formData.custom_memory) {
@@ -275,7 +300,7 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
275
300
  if (formData.resource_size) {
276
301
  launchParameters.resource_size_request = formData.resource_size;
277
302
  }
278
- if (formData.resource_size === 'CUSTOM_SIZE') {
303
+ if (formData.resource_size === "CUSTOM_SIZE") {
279
304
  if (formData.custom_cpu)
280
305
  launchParameters.custom_cpu_cores = parseInt(formData.custom_cpu);
281
306
  if (formData.custom_memory)
@@ -314,65 +339,74 @@ export const DevboxCreatePage = ({ onBack, onCreate }) => {
314
339
  };
315
340
  // Result screen
316
341
  if (result) {
317
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
318
- { label: 'Devboxes' },
319
- { label: 'Create', active: true }
320
- ] }), _jsx(SuccessMessage, { message: "Devbox created successfully!", details: `ID: ${result.id}\nName: ${result.name || '(none)'}\nStatus: ${result.status}` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press [Enter], [q], or [esc] to return to list" }) })] }));
342
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes" }, { label: "Create", active: true }] }), _jsx(SuccessMessage, { message: "Devbox created successfully!", details: `ID: ${result.id}\nName: ${result.name || "(none)"}\nStatus: ${result.status}` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press [Enter], [q], or [esc] to return to list" }) })] }));
321
343
  }
322
344
  // Error screen
323
345
  if (error) {
324
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
325
- { label: 'Devboxes' },
326
- { label: 'Create', active: true }
327
- ] }), _jsx(ErrorMessage, { message: "Failed to create devbox", error: error }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press [Enter] or [r] to retry \u2022 [q] or [esc] to cancel" }) })] }));
346
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes" }, { label: "Create", active: true }] }), _jsx(ErrorMessage, { message: "Failed to create devbox", error: error }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press [Enter] or [r] to retry \u2022 [q] or [esc] to cancel" }) })] }));
328
347
  }
329
348
  // Creating screen
330
349
  if (creating) {
331
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
332
- { label: 'Devboxes' },
333
- { label: 'Create', active: true }
334
- ] }), _jsx(SpinnerComponent, { message: "Creating devbox..." })] }));
350
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes" }, { label: "Create", active: true }] }), _jsx(SpinnerComponent, { message: "Creating devbox..." })] }));
335
351
  }
336
352
  // Form screen
337
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
338
- { label: 'Devboxes' },
339
- { label: 'Create', active: true }
340
- ] }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: fields.map((field, index) => {
353
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes" }, { label: "Create", active: true }] }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: fields.map((field, index) => {
341
354
  const isActive = currentField === field.key;
342
355
  const fieldData = formData[field.key];
343
- if (field.type === 'action') {
344
- return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ? 'green' : 'gray', bold: isActive, children: [isActive ? figures.pointer : ' ', " ", field.label] }), isActive && (_jsxs(Text, { color: "gray", dimColor: true, children: [' ', "[Enter to create]"] }))] }, field.key));
356
+ if (field.type === "action") {
357
+ return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ? colors.success : colors.textDim, bold: isActive, children: [isActive ? figures.pointer : " ", " ", field.label] }), isActive && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[Enter to create]"] }))] }, field.key));
345
358
  }
346
- if (field.type === 'text') {
347
- return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ? 'cyan' : 'gray', children: [isActive ? figures.pointer : ' ', " ", field.label, ":", ' '] }), isActive ? (_jsx(TextInput, { value: String(fieldData || ''), onChange: (value) => {
359
+ if (field.type === "text") {
360
+ return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ? colors.primary : colors.textDim, children: [isActive ? figures.pointer : " ", " ", field.label, ":", " "] }), isActive ? (_jsx(TextInput, { value: String(fieldData || ""), onChange: (value) => {
348
361
  setFormData({ ...formData, [field.key]: value });
349
- }, placeholder: field.key === 'name' ? 'my-devbox' :
350
- field.key === 'keep_alive' ? '3600' :
351
- field.key === 'blueprint_id' ? 'bp_xxx' :
352
- field.key === 'snapshot_id' ? 'snap_xxx' :
353
- '' })) : (_jsx(Text, { color: "white", children: String(fieldData || '(empty)') }))] }, field.key));
362
+ }, placeholder: field.key === "name"
363
+ ? "my-devbox"
364
+ : field.key === "keep_alive"
365
+ ? "3600"
366
+ : field.key === "blueprint_id"
367
+ ? "bp_xxx"
368
+ : field.key === "snapshot_id"
369
+ ? "snap_xxx"
370
+ : "" })) : (_jsx(Text, { color: colors.text, children: String(fieldData || "(empty)") }))] }, field.key));
354
371
  }
355
- if (field.type === 'select') {
372
+ if (field.type === "select") {
356
373
  const value = fieldData;
357
- return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ? 'cyan' : 'gray', children: [isActive ? figures.pointer : ' ', " ", field.label, ":"] }), _jsxs(Text, { color: isActive ? 'cyan' : 'white', bold: isActive, children: [' ', value || '(none)'] }), isActive && (_jsxs(Text, { color: "gray", dimColor: true, children: [' ', "[", figures.arrowLeft, figures.arrowRight, " to change]"] }))] }, field.key));
374
+ return (_jsxs(Box, { marginBottom: 0, children: [_jsxs(Text, { color: isActive ? colors.primary : colors.textDim, children: [isActive ? figures.pointer : " ", " ", field.label, ":"] }), _jsxs(Text, { color: isActive ? colors.primary : colors.text, bold: isActive, children: [" ", value || "(none)"] }), isActive && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", figures.arrowLeft, figures.arrowRight, " to change]"] }))] }, field.key));
358
375
  }
359
- if (field.type === 'metadata') {
376
+ if (field.type === "metadata") {
360
377
  if (!inMetadataSection) {
361
378
  // Collapsed view
362
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [_jsxs(Box, { children: [_jsxs(Text, { color: isActive ? 'cyan' : 'gray', children: [isActive ? figures.pointer : ' ', " ", field.label, ":", ' '] }), _jsxs(Text, { color: "white", children: [Object.keys(formData.metadata).length, " item(s)"] }), isActive && (_jsx(Text, { color: "gray", dimColor: true, children: " [Enter to manage]" }))] }), Object.keys(formData.metadata).length > 0 && (_jsx(Box, { marginLeft: 2, children: _jsx(MetadataDisplay, { metadata: formData.metadata, showBorder: false }) }))] }, field.key));
379
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [_jsxs(Box, { children: [_jsxs(Text, { color: isActive ? colors.primary : colors.textDim, children: [isActive ? figures.pointer : " ", " ", field.label, ":", " "] }), _jsxs(Text, { color: colors.text, children: [Object.keys(formData.metadata).length, " item(s)"] }), isActive && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[Enter to manage]"] }))] }), Object.keys(formData.metadata).length > 0 && (_jsx(Box, { marginLeft: 2, children: _jsx(MetadataDisplay, { metadata: formData.metadata, showBorder: false }) }))] }, field.key));
363
380
  }
364
381
  // Expanded metadata section view
365
382
  const metadataKeys = Object.keys(formData.metadata);
366
383
  // Selection model: 0 = "Add new", 1..n = Existing items, n+1 = "Done"
367
384
  const maxIndex = metadataKeys.length + 1;
368
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, marginBottom: 1, children: [_jsxs(Text, { color: "cyan", bold: true, children: [figures.hamburger, " Manage Metadata"] }), metadataInputMode && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: selectedMetadataIndex === 0 ? 'green' : 'yellow', paddingX: 1, children: [_jsx(Text, { color: selectedMetadataIndex === 0 ? 'green' : 'yellow', bold: true, children: selectedMetadataIndex === 0 ? 'Adding New' : 'Editing' }), _jsx(Box, { children: metadataInputMode === 'key' ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: "cyan", children: "Key: " }), _jsx(TextInput, { value: metadataKey || '', onChange: setMetadataKey, placeholder: "env" })] })) : (_jsxs(Text, { dimColor: true, children: ["Key: ", metadataKey || ''] })) }), _jsx(Box, { children: metadataInputMode === 'value' ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: "cyan", children: "Value: " }), _jsx(TextInput, { value: metadataValue || '', onChange: setMetadataValue, placeholder: "production" })] })) : (_jsxs(Text, { dimColor: true, children: ["Value: ", metadataValue || ''] })) })] })), !metadataInputMode && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: selectedMetadataIndex === 0 ? 'cyan' : 'gray', children: [selectedMetadataIndex === 0 ? figures.pointer : ' ', ' '] }), _jsx(Text, { color: selectedMetadataIndex === 0 ? 'green' : 'gray', bold: selectedMetadataIndex === 0, children: "+ Add new metadata" })] }), metadataKeys.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: metadataKeys.map((key, index) => {
385
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.primary, paddingX: 1, paddingY: 1, marginBottom: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.hamburger, " Manage Metadata"] }), metadataInputMode && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: selectedMetadataIndex === 0
386
+ ? colors.success
387
+ : colors.warning, paddingX: 1, children: [_jsx(Text, { color: selectedMetadataIndex === 0
388
+ ? colors.success
389
+ : colors.warning, bold: true, children: selectedMetadataIndex === 0 ? "Adding New" : "Editing" }), _jsx(Box, { children: metadataInputMode === "key" ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.primary, children: "Key: " }), _jsx(TextInput, { value: metadataKey || "", onChange: setMetadataKey, placeholder: "env" })] })) : (_jsxs(Text, { dimColor: true, children: ["Key: ", metadataKey || ""] })) }), _jsx(Box, { children: metadataInputMode === "value" ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: colors.primary, children: "Value: " }), _jsx(TextInput, { value: metadataValue || "", onChange: setMetadataValue, placeholder: "production" })] })) : (_jsxs(Text, { dimColor: true, children: ["Value: ", metadataValue || ""] })) })] })), !metadataInputMode && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: selectedMetadataIndex === 0
390
+ ? colors.primary
391
+ : colors.textDim, children: [selectedMetadataIndex === 0
392
+ ? figures.pointer
393
+ : " ", " "] }), _jsx(Text, { color: selectedMetadataIndex === 0
394
+ ? colors.success
395
+ : colors.textDim, bold: selectedMetadataIndex === 0, children: "+ Add new metadata" })] }), metadataKeys.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: metadataKeys.map((key, index) => {
369
396
  const itemIndex = index + 1; // Items are at indices 1..n
370
397
  const isSelected = selectedMetadataIndex === itemIndex;
371
- return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? 'cyan' : 'gray', children: [isSelected ? figures.pointer : ' ', ' '] }), _jsxs(Text, { color: isSelected ? 'cyan' : 'gray', bold: isSelected, children: [key, ": ", formData.metadata[key]] })] }, key));
372
- }) })), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: selectedMetadataIndex === maxIndex ? 'cyan' : 'gray', children: [selectedMetadataIndex === maxIndex ? figures.pointer : ' ', ' '] }), _jsxs(Text, { color: selectedMetadataIndex === maxIndex ? 'green' : 'gray', bold: selectedMetadataIndex === maxIndex, children: [figures.tick, " Done"] })] })] })), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: metadataInputMode
373
- ? `[Tab] Switch field • [Enter] ${metadataInputMode === 'key' ? 'Next' : 'Save'} • [esc] Cancel`
374
- : `${figures.arrowUp}${figures.arrowDown} Navigate [Enter] ${selectedMetadataIndex === 0 ? 'Add' : selectedMetadataIndex === maxIndex ? 'Done' : 'Edit'} • [d] Delete • [esc] Back` }) })] }, field.key));
398
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? colors.primary : colors.textDim, children: [isSelected ? figures.pointer : " ", " "] }), _jsxs(Text, { color: isSelected ? colors.primary : colors.textDim, bold: isSelected, children: [key, ": ", formData.metadata[key]] })] }, key));
399
+ }) })), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: selectedMetadataIndex === maxIndex
400
+ ? colors.primary
401
+ : colors.textDim, children: [selectedMetadataIndex === maxIndex
402
+ ? figures.pointer
403
+ : " ", " "] }), _jsxs(Text, { color: selectedMetadataIndex === maxIndex
404
+ ? colors.success
405
+ : colors.textDim, bold: selectedMetadataIndex === maxIndex, children: [figures.tick, " Done"] })] })] })), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: colors.border, paddingX: 1, children: _jsx(Text, { color: colors.textDim, dimColor: true, children: metadataInputMode
406
+ ? `[Tab] Switch field • [Enter] ${metadataInputMode === "key" ? "Next" : "Save"} • [esc] Cancel`
407
+ : `${figures.arrowUp}${figures.arrowDown} Navigate • [Enter] ${selectedMetadataIndex === 0 ? "Add" : selectedMetadataIndex === maxIndex ? "Done" : "Edit"} • [d] Delete • [esc] Back` }) })] }, field.key));
375
408
  }
376
409
  return null;
377
- }) }), formData.resource_size === 'CUSTOM_SIZE' && validateCustomResources() && (_jsxs(Box, { borderStyle: "round", borderColor: "red", paddingX: 1, paddingY: 0, marginTop: 1, children: [_jsxs(Text, { color: "red", bold: true, children: [figures.cross, " Validation Error"] }), _jsx(Text, { color: "red", dimColor: true, children: validateCustomResources() })] })), !inMetadataSection && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "gray", dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [Enter] Create \u2022 [q] Cancel"] }) }))] }));
410
+ }) }), formData.resource_size === "CUSTOM_SIZE" &&
411
+ validateCustomResources() && (_jsxs(Box, { borderStyle: "round", borderColor: colors.error, paddingX: 1, paddingY: 0, marginTop: 1, children: [_jsxs(Text, { color: colors.error, bold: true, children: [figures.cross, " Validation Error"] }), _jsx(Text, { color: colors.error, dimColor: true, children: validateCustomResources() })] })), !inMetadataSection && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [Enter] Create \u2022 [q] Cancel"] }) }))] }));
378
412
  };