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