@runloop/rl-cli 0.0.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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/dist/cli.js +110 -0
  4. package/dist/commands/auth.js +27 -0
  5. package/dist/commands/blueprint/list.js +373 -0
  6. package/dist/commands/create.js +42 -0
  7. package/dist/commands/delete.js +34 -0
  8. package/dist/commands/devbox/create.js +45 -0
  9. package/dist/commands/devbox/delete.js +37 -0
  10. package/dist/commands/devbox/exec.js +35 -0
  11. package/dist/commands/devbox/list.js +302 -0
  12. package/dist/commands/devbox/upload.js +40 -0
  13. package/dist/commands/exec.js +35 -0
  14. package/dist/commands/list.js +59 -0
  15. package/dist/commands/snapshot/create.js +40 -0
  16. package/dist/commands/snapshot/delete.js +37 -0
  17. package/dist/commands/snapshot/list.js +122 -0
  18. package/dist/commands/upload.js +40 -0
  19. package/dist/components/Banner.js +11 -0
  20. package/dist/components/Breadcrumb.js +9 -0
  21. package/dist/components/DetailView.js +29 -0
  22. package/dist/components/DevboxCard.js +24 -0
  23. package/dist/components/DevboxCreatePage.js +370 -0
  24. package/dist/components/DevboxDetailPage.js +621 -0
  25. package/dist/components/ErrorMessage.js +6 -0
  26. package/dist/components/Header.js +6 -0
  27. package/dist/components/MetadataDisplay.js +24 -0
  28. package/dist/components/OperationsMenu.js +19 -0
  29. package/dist/components/Spinner.js +6 -0
  30. package/dist/components/StatusBadge.js +41 -0
  31. package/dist/components/SuccessMessage.js +6 -0
  32. package/dist/components/Table.example.js +55 -0
  33. package/dist/components/Table.js +54 -0
  34. package/dist/utils/CommandExecutor.js +115 -0
  35. package/dist/utils/client.js +13 -0
  36. package/dist/utils/config.js +17 -0
  37. package/dist/utils/output.js +115 -0
  38. package/package.json +69 -0
@@ -0,0 +1,373 @@
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, useStdout, useApp } 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 '../../components/Header.js';
8
+ import { SpinnerComponent } from '../../components/Spinner.js';
9
+ import { ErrorMessage } from '../../components/ErrorMessage.js';
10
+ import { SuccessMessage } from '../../components/SuccessMessage.js';
11
+ import { StatusBadge } from '../../components/StatusBadge.js';
12
+ import { Breadcrumb } from '../../components/Breadcrumb.js';
13
+ import { MetadataDisplay } from '../../components/MetadataDisplay.js';
14
+ import { Table, createTextColumn, createComponentColumn } from '../../components/Table.js';
15
+ import { OperationsMenu } from '../../components/OperationsMenu.js';
16
+ import { createExecutor } from '../../utils/CommandExecutor.js';
17
+ const PAGE_SIZE = 10;
18
+ const MAX_FETCH = 100;
19
+ // Format time ago
20
+ const formatTimeAgo = (timestamp) => {
21
+ const seconds = Math.floor((Date.now() - timestamp) / 1000);
22
+ if (seconds < 60)
23
+ return `${seconds}s ago`;
24
+ const minutes = Math.floor(seconds / 60);
25
+ if (minutes < 60)
26
+ return `${minutes}m ago`;
27
+ const hours = Math.floor(minutes / 60);
28
+ if (hours < 24)
29
+ return `${hours}h ago`;
30
+ const days = Math.floor(hours / 24);
31
+ if (days < 30)
32
+ return `${days}d ago`;
33
+ const months = Math.floor(days / 30);
34
+ if (months < 12)
35
+ return `${months}mo ago`;
36
+ const years = Math.floor(months / 12);
37
+ return `${years}y ago`;
38
+ };
39
+ const ListBlueprintsUI = () => {
40
+ const { stdout } = useStdout();
41
+ const { exit } = useApp();
42
+ const [loading, setLoading] = React.useState(true);
43
+ const [blueprints, setBlueprints] = React.useState([]);
44
+ const [error, setError] = React.useState(null);
45
+ const [currentPage, setCurrentPage] = React.useState(0);
46
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
47
+ const [showDetails, setShowDetails] = React.useState(false);
48
+ const [selectedOperation, setSelectedOperation] = React.useState(0);
49
+ const [executingOperation, setExecutingOperation] = React.useState(null);
50
+ const [operationInput, setOperationInput] = React.useState('');
51
+ const [operationResult, setOperationResult] = React.useState(null);
52
+ const [operationError, setOperationError] = React.useState(null);
53
+ // Calculate responsive column widths
54
+ const terminalWidth = stdout?.columns || 120;
55
+ const showDescription = terminalWidth >= 120;
56
+ const showFullId = terminalWidth >= 80;
57
+ const idWidth = 25;
58
+ const nameWidth = terminalWidth >= 120 ? 30 : 25;
59
+ const descriptionWidth = 40;
60
+ const timeWidth = 20;
61
+ const allOperations = [
62
+ {
63
+ key: 'create_devbox',
64
+ label: 'Create Devbox from Blueprint',
65
+ color: 'green',
66
+ icon: figures.play,
67
+ needsInput: true,
68
+ inputPrompt: 'Devbox name (optional):',
69
+ inputPlaceholder: 'my-devbox',
70
+ },
71
+ {
72
+ key: 'delete',
73
+ label: 'Delete Blueprint',
74
+ color: 'red',
75
+ icon: figures.cross,
76
+ },
77
+ ];
78
+ React.useEffect(() => {
79
+ const list = async () => {
80
+ try {
81
+ const client = getClient();
82
+ const allBlueprints = [];
83
+ let count = 0;
84
+ for await (const blueprint of client.blueprints.list()) {
85
+ allBlueprints.push(blueprint);
86
+ count++;
87
+ if (count >= MAX_FETCH) {
88
+ break;
89
+ }
90
+ }
91
+ setBlueprints(allBlueprints);
92
+ }
93
+ catch (err) {
94
+ setError(err);
95
+ }
96
+ finally {
97
+ setLoading(false);
98
+ }
99
+ };
100
+ list();
101
+ }, []);
102
+ // Clear console when transitioning to detail view
103
+ const prevShowDetailsRef = React.useRef(showDetails);
104
+ React.useEffect(() => {
105
+ if (showDetails && !prevShowDetailsRef.current) {
106
+ console.clear();
107
+ }
108
+ prevShowDetailsRef.current = showDetails;
109
+ }, [showDetails]);
110
+ // Auto-execute operations that don't need input
111
+ React.useEffect(() => {
112
+ if (executingOperation === 'delete' && !loading && selectedBlueprint) {
113
+ executeOperation();
114
+ }
115
+ }, [executingOperation]);
116
+ const executeOperation = async () => {
117
+ const client = getClient();
118
+ const blueprint = selectedBlueprint;
119
+ try {
120
+ setLoading(true);
121
+ switch (executingOperation) {
122
+ case 'create_devbox':
123
+ const devbox = await client.devboxes.create({
124
+ blueprint_id: blueprint.id,
125
+ name: operationInput || undefined,
126
+ });
127
+ setOperationResult(`Devbox created successfully!\n\n` +
128
+ `ID: ${devbox.id}\n` +
129
+ `Name: ${devbox.name || '(none)'}\n` +
130
+ `Status: ${devbox.status}\n\n` +
131
+ `Use 'rln devbox list' to view all devboxes.`);
132
+ break;
133
+ case 'delete':
134
+ await client.blueprints.delete(blueprint.id);
135
+ setOperationResult(`Blueprint ${blueprint.id} deleted successfully`);
136
+ break;
137
+ }
138
+ }
139
+ catch (err) {
140
+ setOperationError(err);
141
+ }
142
+ finally {
143
+ setLoading(false);
144
+ }
145
+ };
146
+ useInput((input, key) => {
147
+ const pageBlueprints = currentBlueprints.length;
148
+ // Handle operation input mode
149
+ if (executingOperation && !operationResult && !operationError) {
150
+ const currentOp = allOperations.find((op) => op.key === executingOperation);
151
+ if (currentOp?.needsInput) {
152
+ if (key.return) {
153
+ executeOperation();
154
+ }
155
+ else if (input === 'q' || key.escape) {
156
+ console.clear();
157
+ setExecutingOperation(null);
158
+ setOperationInput('');
159
+ }
160
+ }
161
+ return;
162
+ }
163
+ // Handle operation result display
164
+ if (operationResult || operationError) {
165
+ if (input === 'q' || key.escape || key.return) {
166
+ console.clear();
167
+ setOperationResult(null);
168
+ setOperationError(null);
169
+ setExecutingOperation(null);
170
+ setOperationInput('');
171
+ }
172
+ return;
173
+ }
174
+ // Handle details view
175
+ if (showDetails) {
176
+ if (input === 'q' || key.escape) {
177
+ console.clear();
178
+ setShowDetails(false);
179
+ setSelectedOperation(0);
180
+ }
181
+ else if (input === 'o' && selectedBlueprint) {
182
+ // Open in browser
183
+ const url = `https://platform.runloop.ai/blueprints/${selectedBlueprint.id}`;
184
+ const openBrowser = async () => {
185
+ const { exec } = await import('child_process');
186
+ const platform = process.platform;
187
+ let openCommand;
188
+ if (platform === 'darwin') {
189
+ openCommand = `open "${url}"`;
190
+ }
191
+ else if (platform === 'win32') {
192
+ openCommand = `start "${url}"`;
193
+ }
194
+ else {
195
+ openCommand = `xdg-open "${url}"`;
196
+ }
197
+ exec(openCommand);
198
+ };
199
+ openBrowser();
200
+ }
201
+ else if (key.upArrow && selectedOperation > 0) {
202
+ setSelectedOperation(selectedOperation - 1);
203
+ }
204
+ else if (key.downArrow && selectedOperation < operations.length - 1) {
205
+ setSelectedOperation(selectedOperation + 1);
206
+ }
207
+ else if (key.return) {
208
+ console.clear();
209
+ const op = operations[selectedOperation].key;
210
+ setExecutingOperation(op);
211
+ }
212
+ return;
213
+ }
214
+ // Handle list view
215
+ if (key.upArrow && selectedIndex > 0) {
216
+ setSelectedIndex(selectedIndex - 1);
217
+ }
218
+ else if (key.downArrow && selectedIndex < pageBlueprints - 1) {
219
+ setSelectedIndex(selectedIndex + 1);
220
+ }
221
+ else if ((input === 'n' || key.rightArrow) && currentPage < totalPages - 1) {
222
+ setCurrentPage(currentPage + 1);
223
+ setSelectedIndex(0);
224
+ }
225
+ else if ((input === 'p' || key.leftArrow) && currentPage > 0) {
226
+ setCurrentPage(currentPage - 1);
227
+ setSelectedIndex(0);
228
+ }
229
+ else if (key.return) {
230
+ console.clear();
231
+ setShowDetails(true);
232
+ }
233
+ else if (input === 'o' && selectedBlueprint) {
234
+ // Open in browser
235
+ const url = `https://platform.runloop.ai/blueprints/${selectedBlueprint.id}`;
236
+ const openBrowser = async () => {
237
+ const { exec } = await import('child_process');
238
+ const platform = process.platform;
239
+ let openCommand;
240
+ if (platform === 'darwin') {
241
+ openCommand = `open "${url}"`;
242
+ }
243
+ else if (platform === 'win32') {
244
+ openCommand = `start "${url}"`;
245
+ }
246
+ else {
247
+ openCommand = `xdg-open "${url}"`;
248
+ }
249
+ exec(openCommand);
250
+ };
251
+ openBrowser();
252
+ }
253
+ else if (input === 'q') {
254
+ process.exit(0);
255
+ }
256
+ });
257
+ const totalPages = Math.ceil(blueprints.length / PAGE_SIZE);
258
+ const startIndex = currentPage * PAGE_SIZE;
259
+ const endIndex = Math.min(startIndex + PAGE_SIZE, blueprints.length);
260
+ const currentBlueprints = blueprints.slice(startIndex, endIndex);
261
+ const selectedBlueprint = currentBlueprints[selectedIndex];
262
+ const buildComplete = blueprints.filter((b) => b.status === 'build_complete').length;
263
+ const building = blueprints.filter((b) => ['provisioning', 'building'].includes(b.status)).length;
264
+ const failed = blueprints.filter((b) => b.status === 'build_failed').length;
265
+ // Filter operations based on blueprint status
266
+ const operations = selectedBlueprint
267
+ ? allOperations.filter((op) => {
268
+ const status = selectedBlueprint.status;
269
+ // Only allow creating devbox if build is complete
270
+ if (op.key === 'create_devbox') {
271
+ return status === 'build_complete';
272
+ }
273
+ // Allow delete for any status
274
+ return true;
275
+ })
276
+ : allOperations;
277
+ // Operation result display
278
+ if (operationResult || operationError) {
279
+ const operationLabel = operations.find((o) => o.key === executingOperation)?.label || 'Operation';
280
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
281
+ { label: 'Blueprints' },
282
+ {
283
+ label: selectedBlueprint?.name || selectedBlueprint?.id || 'Blueprint',
284
+ },
285
+ { label: operationLabel, active: true },
286
+ ] }), _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" }) })] }));
287
+ }
288
+ // Operation input mode
289
+ if (executingOperation && selectedBlueprint) {
290
+ const currentOp = allOperations.find((op) => op.key === executingOperation);
291
+ const needsInput = currentOp?.needsInput;
292
+ const operationLabel = currentOp?.label || 'Operation';
293
+ if (loading) {
294
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
295
+ { label: 'Blueprints' },
296
+ { label: selectedBlueprint.name || selectedBlueprint.id },
297
+ { label: operationLabel, active: true },
298
+ ] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: "Please wait..." })] }));
299
+ }
300
+ if (!needsInput) {
301
+ const messages = {
302
+ delete: 'Deleting blueprint...',
303
+ };
304
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
305
+ { label: 'Blueprints' },
306
+ { label: selectedBlueprint.name || selectedBlueprint.id },
307
+ { label: operationLabel, active: true },
308
+ ] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: messages[executingOperation] || 'Please wait...' })] }));
309
+ }
310
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
311
+ { label: 'Blueprints' },
312
+ { label: selectedBlueprint.name || selectedBlueprint.id },
313
+ { label: operationLabel, active: true },
314
+ ] }), _jsx(Header, { title: operationLabel }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "cyan", bold: true, children: selectedBlueprint.name || selectedBlueprint.id }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: [currentOp.inputPrompt, " "] }) }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: operationInput, onChange: setOperationInput, placeholder: currentOp.inputPlaceholder || '' }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press [Enter] to execute \u2022 [q or esc] Cancel" }) })] })] }));
315
+ }
316
+ // Details view with operation selection
317
+ if (showDetails && selectedBlueprint) {
318
+ const ds = selectedBlueprint.dockerfile_setup;
319
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
320
+ { label: 'Blueprints' },
321
+ {
322
+ label: selectedBlueprint.name || selectedBlueprint.id,
323
+ active: true,
324
+ },
325
+ ] }), _jsx(Header, { title: "Blueprint Details" }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 0, children: [_jsxs(Box, { children: [_jsx(Text, { color: "cyan", bold: true, children: selectedBlueprint.name || selectedBlueprint.id }), _jsx(Text, { children: " " }), _jsx(StatusBadge, { status: selectedBlueprint.status }), _jsxs(Text, { color: "gray", dimColor: true, children: [' ', "\u2022 ", selectedBlueprint.id] })] }), _jsxs(Box, { children: [_jsx(Text, { color: "gray", dimColor: true, children: selectedBlueprint.create_time_ms
326
+ ? new Date(selectedBlueprint.create_time_ms).toLocaleString()
327
+ : '' }), _jsxs(Text, { color: "gray", dimColor: true, children: [' ', "(", selectedBlueprint.create_time_ms
328
+ ? formatTimeAgo(selectedBlueprint.create_time_ms)
329
+ : '', ")"] })] }), ds?.description && (_jsx(Box, { children: _jsx(Text, { color: "gray", dimColor: true, children: ds.description }) }))] }), ds && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 0, children: [_jsxs(Text, { color: "yellow", bold: true, children: [figures.squareSmallFilled, " Dockerfile Setup"] }), ds.base_image && (_jsxs(Text, { dimColor: true, children: ["Base Image: ", ds.base_image] })), ds.entrypoint && (_jsxs(Text, { dimColor: true, children: ["Entrypoint: ", ds.entrypoint] })), ds.system_packages && ds.system_packages.length > 0 && (_jsxs(Text, { dimColor: true, children: ["System Packages: ", ds.system_packages.join(', ')] })), ds.python_packages && ds.python_packages.length > 0 && (_jsxs(Text, { dimColor: true, children: ["Python Packages: ", ds.python_packages.join(', ')] }))] })), selectedBlueprint.metadata && Object.keys(selectedBlueprint.metadata).length > 0 && (_jsx(Box, { borderStyle: "round", borderColor: "green", paddingX: 1, paddingY: 0, children: _jsx(MetadataDisplay, { metadata: selectedBlueprint.metadata, showBorder: false }) })), selectedBlueprint.build_error && (_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: selectedBlueprint.build_error })] })), _jsx(OperationsMenu, { operations: operations, selectedIndex: selectedOperation, onNavigate: (direction) => {
330
+ if (direction === 'up' && selectedOperation > 0) {
331
+ setSelectedOperation(selectedOperation - 1);
332
+ }
333
+ else if (direction === 'down' && selectedOperation < operations.length - 1) {
334
+ setSelectedOperation(selectedOperation + 1);
335
+ }
336
+ }, onSelect: (op) => {
337
+ console.clear();
338
+ setExecutingOperation(op.key);
339
+ }, onBack: () => {
340
+ console.clear();
341
+ setShowDetails(false);
342
+ setSelectedOperation(0);
343
+ }, additionalActions: [{ key: 'o', label: 'Browser', handler: () => { } }] })] }));
344
+ }
345
+ // List view
346
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: 'Blueprints', active: true }] }), loading && _jsx(SpinnerComponent, { message: "Loading blueprints..." }), !loading && !error && blueprints.length === 0 && (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: figures.info }), _jsx(Text, { children: " No blueprints found. Try: " }), _jsx(Text, { color: "cyan", bold: true, children: "rln blueprint create" })] })), !loading && !error && blueprints.length > 0 && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: "green", children: [figures.tick, " ", buildComplete] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: "yellow", children: [figures.ellipsis, " ", building] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: "red", children: [figures.cross, " ", failed] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: "cyan", children: [figures.hamburger, " ", blueprints.length, blueprints.length >= MAX_FETCH && '+'] }), totalPages > 1 && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: " \u2022 " }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Page ", currentPage + 1, "/", totalPages] })] }))] }), _jsx(Table, { data: currentBlueprints, keyExtractor: (blueprint) => blueprint.id, selectedIndex: selectedIndex, columns: [
347
+ createComponentColumn('status', 'Status', (blueprint) => _jsx(StatusBadge, { status: blueprint.status, showText: false }), { width: 2 }),
348
+ createTextColumn('id', 'ID', (blueprint) => (showFullId ? blueprint.id : blueprint.id.slice(0, 13)), {
349
+ width: showFullId ? idWidth : 15,
350
+ color: 'gray',
351
+ dimColor: true,
352
+ bold: false,
353
+ }),
354
+ createTextColumn('name', 'Name', (blueprint) => blueprint.name || '(unnamed)', { width: nameWidth }),
355
+ createTextColumn('description', 'Description', (blueprint) => blueprint.dockerfile_setup?.description || '', {
356
+ width: descriptionWidth,
357
+ color: 'gray',
358
+ dimColor: true,
359
+ bold: false,
360
+ visible: showDescription,
361
+ }),
362
+ createTextColumn('created', 'Created', (blueprint) => blueprint.create_time_ms ? formatTimeAgo(blueprint.create_time_ms) : '', { width: timeWidth, color: 'gray', dimColor: true, bold: false }),
363
+ ] }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: "gray", dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [Enter] Operations \u2022 [o] Open in Browser \u2022"] }), totalPages > 1 && (_jsxs(Text, { color: "gray", dimColor: true, children: [' ', figures.arrowLeft, figures.arrowRight, " Page \u2022"] })), _jsxs(Text, { color: "gray", dimColor: true, children: [' ', "[q] Quit"] })] })] })), error && _jsx(ErrorMessage, { message: "Failed to list blueprints", error: error })] }));
364
+ };
365
+ export async function listBlueprints(options = {}) {
366
+ const executor = createExecutor(options);
367
+ await executor.executeList(async () => {
368
+ const client = executor.getClient();
369
+ return executor.fetchFromIterator(client.blueprints.list(), {
370
+ limit: PAGE_SIZE,
371
+ });
372
+ }, () => _jsx(ListBlueprintsUI, {}), PAGE_SIZE);
373
+ }
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { render, Box, Text } from 'ink';
4
+ import Gradient from 'ink-gradient';
5
+ import figures from 'figures';
6
+ import { getClient } from '../utils/client.js';
7
+ import { Header } from '../components/Header.js';
8
+ import { SpinnerComponent } from '../components/Spinner.js';
9
+ import { SuccessMessage } from '../components/SuccessMessage.js';
10
+ import { ErrorMessage } from '../components/ErrorMessage.js';
11
+ const CreateDevboxUI = ({ name, template }) => {
12
+ const [loading, setLoading] = React.useState(true);
13
+ const [result, setResult] = React.useState(null);
14
+ const [error, setError] = React.useState(null);
15
+ const [progress, setProgress] = React.useState('Initializing...');
16
+ React.useEffect(() => {
17
+ const create = async () => {
18
+ try {
19
+ const client = getClient();
20
+ setProgress('Requesting new devbox...');
21
+ const devbox = await client.devboxes.create({
22
+ name: name || `devbox-${Date.now()}`,
23
+ ...(template && { template }),
24
+ });
25
+ setProgress('Devbox created! Waiting for provisioning...');
26
+ setResult(devbox);
27
+ }
28
+ catch (err) {
29
+ setError(err);
30
+ }
31
+ finally {
32
+ setLoading(false);
33
+ }
34
+ };
35
+ create();
36
+ }, []);
37
+ return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Create Devbox", subtitle: "Setting up your new development environment" }), loading && (_jsxs(_Fragment, { children: [_jsx(SpinnerComponent, { message: progress }), _jsxs(Box, { borderStyle: "round", borderColor: "blue", paddingX: 3, paddingY: 1, marginY: 1, flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "cyan", bold: true, children: [figures.info, " Configuration"] }) }), _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [figures.pointer, " Name: "] }), _jsx(Text, { color: "white", children: name || '(auto-generated)' })] }), template && (_jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [figures.pointer, " Template: "] }), _jsx(Text, { color: "white", children: template })] }))] })] })] })), result && (_jsxs(_Fragment, { children: [_jsx(SuccessMessage, { message: "Devbox created successfully!", details: `ID: ${result.id}\nName: ${result.name || '(unnamed)'}\nStatus: ${result.status}` }), _jsxs(Box, { borderStyle: "double", borderColor: "green", paddingX: 3, paddingY: 1, marginY: 1, flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Gradient, { name: "summer", children: _jsxs(Text, { bold: true, children: [figures.star, " Next Steps"] }) }) }), _jsxs(Box, { flexDirection: "column", gap: 1, marginLeft: 2, children: [_jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [figures.tick, " Execute commands: "] }), _jsxs(Text, { color: "cyan", children: ["rln exec ", result.id.slice(0, 8), "... <command>"] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [figures.tick, " Upload files: "] }), _jsxs(Text, { color: "cyan", children: ["rln upload ", result.id.slice(0, 8), "... <file>"] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [figures.tick, " View all: "] }), _jsx(Text, { color: "cyan", children: "rln list" })] })] })] })] })), error && _jsx(ErrorMessage, { message: "Failed to create devbox", error: error })] }));
38
+ };
39
+ export async function createDevbox(options) {
40
+ const { waitUntilExit } = render(_jsx(CreateDevboxUI, { name: options.name, template: options.template }));
41
+ await waitUntilExit();
42
+ }
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { render } from 'ink';
4
+ import { getClient } from '../utils/client.js';
5
+ import { Header } from '../components/Header.js';
6
+ import { SpinnerComponent } from '../components/Spinner.js';
7
+ import { SuccessMessage } from '../components/SuccessMessage.js';
8
+ import { ErrorMessage } from '../components/ErrorMessage.js';
9
+ const DeleteDevboxUI = ({ id }) => {
10
+ const [loading, setLoading] = React.useState(true);
11
+ const [success, setSuccess] = React.useState(false);
12
+ const [error, setError] = React.useState(null);
13
+ React.useEffect(() => {
14
+ const deleteDevbox = async () => {
15
+ try {
16
+ const client = getClient();
17
+ await client.devboxes.shutdown(id);
18
+ setSuccess(true);
19
+ }
20
+ catch (err) {
21
+ setError(err);
22
+ }
23
+ finally {
24
+ setLoading(false);
25
+ }
26
+ };
27
+ deleteDevbox();
28
+ }, []);
29
+ return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Delete Devbox", subtitle: `Deleting devbox: ${id}` }), loading && _jsx(SpinnerComponent, { message: "Deleting devbox..." }), success && (_jsx(SuccessMessage, { message: "Devbox deleted successfully!", details: `ID: ${id}` })), error && _jsx(ErrorMessage, { message: "Failed to delete devbox", error: error })] }));
30
+ };
31
+ export async function deleteDevbox(id) {
32
+ const { waitUntilExit } = render(_jsx(DeleteDevboxUI, { id: id }));
33
+ await waitUntilExit();
34
+ }
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { getClient } from '../../utils/client.js';
5
+ import { Header } from '../../components/Header.js';
6
+ import { Banner } from '../../components/Banner.js';
7
+ import { SpinnerComponent } from '../../components/Spinner.js';
8
+ import { SuccessMessage } from '../../components/SuccessMessage.js';
9
+ import { ErrorMessage } from '../../components/ErrorMessage.js';
10
+ import { createExecutor } from '../../utils/CommandExecutor.js';
11
+ const CreateDevboxUI = ({ name, template }) => {
12
+ const [loading, setLoading] = React.useState(true);
13
+ const [result, setResult] = React.useState(null);
14
+ const [error, setError] = React.useState(null);
15
+ React.useEffect(() => {
16
+ const create = async () => {
17
+ try {
18
+ const client = getClient();
19
+ const devbox = await client.devboxes.create({
20
+ name: name || `devbox-${Date.now()}`,
21
+ ...(template && { template }),
22
+ });
23
+ setResult(devbox);
24
+ }
25
+ catch (err) {
26
+ setError(err);
27
+ }
28
+ finally {
29
+ setLoading(false);
30
+ }
31
+ };
32
+ create();
33
+ }, []);
34
+ return (_jsxs(_Fragment, { children: [_jsx(Banner, {}), _jsx(Header, { title: "Create Devbox" }), loading && _jsx(SpinnerComponent, { message: "Creating..." }), result && (_jsxs(_Fragment, { children: [_jsx(SuccessMessage, { message: "Devbox created!", details: `ID: ${result.id}\nStatus: ${result.status}` }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Try: " }), _jsxs(Text, { color: "cyan", children: ["rln devbox exec ", result.id, " ls"] })] })] })), error && _jsx(ErrorMessage, { message: "Failed to create devbox", error: error })] }));
35
+ };
36
+ export async function createDevbox(options) {
37
+ const executor = createExecutor(options);
38
+ await executor.executeAction(async () => {
39
+ const client = executor.getClient();
40
+ return client.devboxes.create({
41
+ name: options.name || `devbox-${Date.now()}`,
42
+ ...(options.template && { template: options.template }),
43
+ });
44
+ }, () => _jsx(CreateDevboxUI, { name: options.name, template: options.template }));
45
+ }
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { getClient } from '../../utils/client.js';
4
+ import { Header } from '../../components/Header.js';
5
+ import { SpinnerComponent } from '../../components/Spinner.js';
6
+ import { SuccessMessage } from '../../components/SuccessMessage.js';
7
+ import { ErrorMessage } from '../../components/ErrorMessage.js';
8
+ import { createExecutor } from '../../utils/CommandExecutor.js';
9
+ const DeleteDevboxUI = ({ id }) => {
10
+ const [loading, setLoading] = React.useState(true);
11
+ const [success, setSuccess] = React.useState(false);
12
+ const [error, setError] = React.useState(null);
13
+ React.useEffect(() => {
14
+ const deleteDevbox = async () => {
15
+ try {
16
+ const client = getClient();
17
+ await client.devboxes.shutdown(id);
18
+ setSuccess(true);
19
+ }
20
+ catch (err) {
21
+ setError(err);
22
+ }
23
+ finally {
24
+ setLoading(false);
25
+ }
26
+ };
27
+ deleteDevbox();
28
+ }, []);
29
+ return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Shutdown Devbox", subtitle: `Shutting down devbox: ${id}` }), loading && _jsx(SpinnerComponent, { message: "Shutting down devbox..." }), success && (_jsx(SuccessMessage, { message: "Devbox shut down successfully!", details: `ID: ${id}` })), error && _jsx(ErrorMessage, { message: "Failed to shutdown devbox", error: error })] }));
30
+ };
31
+ export async function deleteDevbox(id, options = {}) {
32
+ const executor = createExecutor(options);
33
+ await executor.executeDelete(async () => {
34
+ const client = executor.getClient();
35
+ await client.devboxes.shutdown(id);
36
+ }, id, () => _jsx(DeleteDevboxUI, { id: id }));
37
+ }
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { render, Box, Text } from 'ink';
4
+ import { getClient } from '../../utils/client.js';
5
+ import { Header } from '../../components/Header.js';
6
+ import { SpinnerComponent } from '../../components/Spinner.js';
7
+ import { ErrorMessage } from '../../components/ErrorMessage.js';
8
+ const ExecCommandUI = ({ id, command, }) => {
9
+ const [loading, setLoading] = React.useState(true);
10
+ const [output, setOutput] = React.useState('');
11
+ const [error, setError] = React.useState(null);
12
+ React.useEffect(() => {
13
+ const exec = async () => {
14
+ try {
15
+ const client = getClient();
16
+ const result = await client.devboxes.executeSync(id, {
17
+ command: command.join(' '),
18
+ });
19
+ setOutput(result.stdout || result.stderr || 'Command executed successfully');
20
+ }
21
+ catch (err) {
22
+ setError(err);
23
+ }
24
+ finally {
25
+ setLoading(false);
26
+ }
27
+ };
28
+ exec();
29
+ }, []);
30
+ return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Execute Command", subtitle: `Running in devbox: ${id}` }), loading && _jsx(SpinnerComponent, { message: "Executing command..." }), !loading && !error && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Box, { borderStyle: "round", borderColor: "green", padding: 1, children: _jsx(Text, { children: output }) }) })), error && _jsx(ErrorMessage, { message: "Failed to execute command", error: error })] }));
31
+ };
32
+ export async function execCommand(id, command) {
33
+ const { waitUntilExit } = render(_jsx(ExecCommandUI, { id: id, command: command }));
34
+ await waitUntilExit();
35
+ }