@runloop/rl-cli 0.4.0 → 0.5.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/dist/commands/blueprint/list.js +2 -1
- package/dist/commands/snapshot/list.js +47 -6
- package/dist/components/Breadcrumb.js +8 -8
- package/dist/components/DevboxCreatePage.js +2 -2
- package/dist/components/MainMenu.js +13 -2
- package/dist/components/UpdateNotification.js +3 -44
- package/dist/hooks/useUpdateCheck.js +54 -0
- package/dist/mcp/server.js +1 -1
- package/dist/utils/client.js +1 -1
- package/dist/utils/exec.js +22 -0
- package/package.json +1 -1
|
@@ -444,9 +444,10 @@ const ListBlueprintsUI = ({ onBack, onExit, }) => {
|
|
|
444
444
|
return (_jsx(DevboxCreatePage, { onBack: () => {
|
|
445
445
|
setShowCreateDevbox(false);
|
|
446
446
|
setSelectedBlueprint(null);
|
|
447
|
-
}, onCreate: () => {
|
|
447
|
+
}, onCreate: (devbox) => {
|
|
448
448
|
setShowCreateDevbox(false);
|
|
449
449
|
setSelectedBlueprint(null);
|
|
450
|
+
navigate("devbox-detail", { devboxId: devbox.id });
|
|
450
451
|
}, initialBlueprintId: selectedBlueprint.id }));
|
|
451
452
|
}
|
|
452
453
|
// Loading state
|
|
@@ -16,9 +16,12 @@ import { colors } from "../../utils/theme.js";
|
|
|
16
16
|
import { useViewportHeight } from "../../hooks/useViewportHeight.js";
|
|
17
17
|
import { useExitOnCtrlC } from "../../hooks/useExitOnCtrlC.js";
|
|
18
18
|
import { useCursorPagination } from "../../hooks/useCursorPagination.js";
|
|
19
|
+
import { DevboxCreatePage } from "../../components/DevboxCreatePage.js";
|
|
20
|
+
import { useNavigation } from "../../store/navigationStore.js";
|
|
19
21
|
const DEFAULT_PAGE_SIZE = 10;
|
|
20
22
|
const ListSnapshotsUI = ({ devboxId, onBack, onExit, }) => {
|
|
21
23
|
const { exit: inkExit } = useApp();
|
|
24
|
+
const { navigate } = useNavigation();
|
|
22
25
|
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
23
26
|
const [showPopup, setShowPopup] = React.useState(false);
|
|
24
27
|
const [selectedOperation, setSelectedOperation] = React.useState(0);
|
|
@@ -28,6 +31,7 @@ const ListSnapshotsUI = ({ devboxId, onBack, onExit, }) => {
|
|
|
28
31
|
const [operationResult, setOperationResult] = React.useState(null);
|
|
29
32
|
const [operationError, setOperationError] = React.useState(null);
|
|
30
33
|
const [operationLoading, setOperationLoading] = React.useState(false);
|
|
34
|
+
const [showCreateDevbox, setShowCreateDevbox] = React.useState(false);
|
|
31
35
|
// Calculate overhead for viewport height
|
|
32
36
|
const overhead = 13;
|
|
33
37
|
const { viewportHeight, terminalWidth } = useViewportHeight({
|
|
@@ -82,11 +86,17 @@ const ListSnapshotsUI = ({ devboxId, onBack, onExit, }) => {
|
|
|
82
86
|
pageSize: PAGE_SIZE,
|
|
83
87
|
getItemId: (snapshot) => snapshot.id,
|
|
84
88
|
pollInterval: 2000,
|
|
85
|
-
pollingEnabled: !showPopup && !executingOperation,
|
|
89
|
+
pollingEnabled: !showPopup && !executingOperation && !showCreateDevbox,
|
|
86
90
|
deps: [devboxId, PAGE_SIZE],
|
|
87
91
|
});
|
|
88
92
|
// Operations for snapshots
|
|
89
93
|
const operations = React.useMemo(() => [
|
|
94
|
+
{
|
|
95
|
+
key: "create_devbox",
|
|
96
|
+
label: "Create Devbox from Snapshot",
|
|
97
|
+
color: colors.success,
|
|
98
|
+
icon: figures.play,
|
|
99
|
+
},
|
|
90
100
|
{
|
|
91
101
|
key: "delete",
|
|
92
102
|
label: "Delete Snapshot",
|
|
@@ -164,6 +174,10 @@ const ListSnapshotsUI = ({ devboxId, onBack, onExit, }) => {
|
|
|
164
174
|
}
|
|
165
175
|
return;
|
|
166
176
|
}
|
|
177
|
+
// Handle create devbox view
|
|
178
|
+
if (showCreateDevbox) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
167
181
|
// Handle popup navigation
|
|
168
182
|
if (showPopup) {
|
|
169
183
|
if (key.upArrow && selectedOperation > 0) {
|
|
@@ -175,15 +189,27 @@ const ListSnapshotsUI = ({ devboxId, onBack, onExit, }) => {
|
|
|
175
189
|
else if (key.return) {
|
|
176
190
|
setShowPopup(false);
|
|
177
191
|
const operationKey = operations[selectedOperation].key;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
192
|
+
if (operationKey === "create_devbox") {
|
|
193
|
+
setSelectedSnapshot(selectedSnapshotItem);
|
|
194
|
+
setShowCreateDevbox(true);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
setSelectedSnapshot(selectedSnapshotItem);
|
|
198
|
+
setExecutingOperation(operationKey);
|
|
199
|
+
// Execute immediately after state update
|
|
200
|
+
setTimeout(() => executeOperation(), 0);
|
|
201
|
+
}
|
|
182
202
|
}
|
|
183
203
|
else if (key.escape || input === "q") {
|
|
184
204
|
setShowPopup(false);
|
|
185
205
|
setSelectedOperation(0);
|
|
186
206
|
}
|
|
207
|
+
else if (input === "c") {
|
|
208
|
+
// Create devbox hotkey
|
|
209
|
+
setShowPopup(false);
|
|
210
|
+
setSelectedSnapshot(selectedSnapshotItem);
|
|
211
|
+
setShowCreateDevbox(true);
|
|
212
|
+
}
|
|
187
213
|
else if (input === "d") {
|
|
188
214
|
// Delete hotkey
|
|
189
215
|
setShowPopup(false);
|
|
@@ -256,6 +282,17 @@ const ListSnapshotsUI = ({ devboxId, onBack, onExit, }) => {
|
|
|
256
282
|
{ label: operationLabel, active: true },
|
|
257
283
|
] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: messages[executingOperation] || "Please wait..." })] }));
|
|
258
284
|
}
|
|
285
|
+
// Create devbox screen
|
|
286
|
+
if (showCreateDevbox && selectedSnapshot) {
|
|
287
|
+
return (_jsx(DevboxCreatePage, { onBack: () => {
|
|
288
|
+
setShowCreateDevbox(false);
|
|
289
|
+
setSelectedSnapshot(null);
|
|
290
|
+
}, onCreate: (devbox) => {
|
|
291
|
+
setShowCreateDevbox(false);
|
|
292
|
+
setSelectedSnapshot(null);
|
|
293
|
+
navigate("devbox-detail", { devboxId: devbox.id });
|
|
294
|
+
}, initialSnapshotId: selectedSnapshot.id }));
|
|
295
|
+
}
|
|
259
296
|
// Loading state
|
|
260
297
|
if (loading && snapshots.length === 0) {
|
|
261
298
|
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
@@ -292,7 +329,11 @@ const ListSnapshotsUI = ({ devboxId, onBack, onExit, }) => {
|
|
|
292
329
|
label: op.label,
|
|
293
330
|
color: op.color,
|
|
294
331
|
icon: op.icon,
|
|
295
|
-
shortcut: op.key === "
|
|
332
|
+
shortcut: op.key === "create_devbox"
|
|
333
|
+
? "c"
|
|
334
|
+
: op.key === "delete"
|
|
335
|
+
? "d"
|
|
336
|
+
: "",
|
|
296
337
|
})), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate"] }), (hasMore || hasPrev) && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022 ", figures.arrowLeft, figures.arrowRight, " Page"] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022 [a] Actions"] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022 [Esc] Back"] })] })] }));
|
|
297
338
|
};
|
|
298
339
|
// Export the UI component for use in the main menu
|
|
@@ -6,12 +6,12 @@ import { UpdateNotification } from "./UpdateNotification.js";
|
|
|
6
6
|
export const Breadcrumb = ({ items, showVersionCheck = false, }) => {
|
|
7
7
|
const env = process.env.RUNLOOP_ENV?.toLowerCase();
|
|
8
8
|
const isDevEnvironment = env === "dev";
|
|
9
|
-
return (_jsxs(Box, { marginBottom: 1, paddingX: 0, paddingY: 0, children: [_jsxs(Box, { borderStyle: "round", borderColor: colors.primary, paddingX: 2, paddingY: 0, children: [_jsx(Text, { color: colors.primary, bold: true, children: "rl" }), isDevEnvironment && (_jsxs(Text, { color: colors.error, bold: true, children: [" ", "(dev)"] })), _jsx(Text, { color: colors.textDim, children: " \u203A " }), items.map((item, index) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
return (_jsxs(Box, { justifyContent: "space-between", marginBottom: 1, paddingX: 0, paddingY: 0, children: [_jsx(Box, { flexShrink: 0, children: _jsxs(Box, { borderStyle: "round", borderColor: colors.primary, paddingX: 2, paddingY: 0, children: [_jsx(Text, { color: colors.primary, bold: true, children: "rl" }), isDevEnvironment && (_jsxs(Text, { color: colors.error, bold: true, children: [" ", "(dev)"] })), _jsx(Text, { color: colors.textDim, children: " \u203A " }), items.map((item, index) => {
|
|
10
|
+
// Limit label length to prevent Yoga layout engine errors
|
|
11
|
+
const MAX_LABEL_LENGTH = 80;
|
|
12
|
+
const truncatedLabel = item.label.length > MAX_LABEL_LENGTH
|
|
13
|
+
? item.label.substring(0, MAX_LABEL_LENGTH) + "..."
|
|
14
|
+
: item.label;
|
|
15
|
+
return (_jsxs(React.Fragment, { children: [_jsx(Text, { color: item.active ? colors.primary : colors.textDim, children: truncatedLabel }), index < items.length - 1 && (_jsx(Text, { color: colors.textDim, children: " \u203A " }))] }, index));
|
|
16
|
+
})] }) }), showVersionCheck && _jsx(UpdateNotification, {})] }));
|
|
17
17
|
};
|
|
@@ -11,7 +11,7 @@ import { Breadcrumb } from "./Breadcrumb.js";
|
|
|
11
11
|
import { MetadataDisplay } from "./MetadataDisplay.js";
|
|
12
12
|
import { colors } from "../utils/theme.js";
|
|
13
13
|
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
14
|
-
export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, }) => {
|
|
14
|
+
export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, initialSnapshotId, }) => {
|
|
15
15
|
const [currentField, setCurrentField] = React.useState("create");
|
|
16
16
|
const [formData, setFormData] = React.useState({
|
|
17
17
|
name: "",
|
|
@@ -23,7 +23,7 @@ export const DevboxCreatePage = ({ onBack, onCreate, initialBlueprintId, }) => {
|
|
|
23
23
|
keep_alive: "3600",
|
|
24
24
|
metadata: {},
|
|
25
25
|
blueprint_id: initialBlueprintId || "",
|
|
26
|
-
snapshot_id: "",
|
|
26
|
+
snapshot_id: initialSnapshotId || "",
|
|
27
27
|
});
|
|
28
28
|
const [metadataKey, setMetadataKey] = React.useState("");
|
|
29
29
|
const [metadataValue, setMetadataValue] = React.useState("");
|
|
@@ -6,8 +6,10 @@ import { Banner } from "./Banner.js";
|
|
|
6
6
|
import { Breadcrumb } from "./Breadcrumb.js";
|
|
7
7
|
import { VERSION } from "../version.js";
|
|
8
8
|
import { colors } from "../utils/theme.js";
|
|
9
|
+
import { execCommand } from "../utils/exec.js";
|
|
9
10
|
import { useViewportHeight } from "../hooks/useViewportHeight.js";
|
|
10
11
|
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
12
|
+
import { useUpdateCheck } from "../hooks/useUpdateCheck.js";
|
|
11
13
|
const menuItems = [
|
|
12
14
|
{
|
|
13
15
|
key: "devboxes",
|
|
@@ -36,6 +38,8 @@ export const MainMenu = ({ onSelect }) => {
|
|
|
36
38
|
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
37
39
|
// Use centralized viewport hook for consistent layout
|
|
38
40
|
const { terminalHeight } = useViewportHeight({ overhead: 0 });
|
|
41
|
+
// Check for updates
|
|
42
|
+
const { updateAvailable } = useUpdateCheck();
|
|
39
43
|
// Handle Ctrl+C to exit
|
|
40
44
|
useExitOnCtrlC();
|
|
41
45
|
useInput((input, key) => {
|
|
@@ -60,6 +64,13 @@ export const MainMenu = ({ onSelect }) => {
|
|
|
60
64
|
else if (input === "s" || input === "3") {
|
|
61
65
|
onSelect("snapshots");
|
|
62
66
|
}
|
|
67
|
+
else if (input === "u" && updateAvailable) {
|
|
68
|
+
// Release terminal and exec into update command (never returns)
|
|
69
|
+
execCommand("sh", [
|
|
70
|
+
"-c",
|
|
71
|
+
"npm install -g @runloop/rl-cli@latest && exec rli",
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
63
74
|
});
|
|
64
75
|
// Use compact layout if terminal height is less than 20 lines (memoized)
|
|
65
76
|
const useCompactLayout = terminalHeight < 20;
|
|
@@ -67,10 +78,10 @@ export const MainMenu = ({ onSelect }) => {
|
|
|
67
78
|
return (_jsxs(Box, { flexDirection: "column", 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) => {
|
|
68
79
|
const isSelected = index === selectedIndex;
|
|
69
80
|
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));
|
|
70
|
-
}) }), _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"] }) })] }));
|
|
81
|
+
}) }), _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", updateAvailable && " • [u] Update"] }) })] }));
|
|
71
82
|
}
|
|
72
83
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Breadcrumb, { items: [{ label: "Home", active: true }], showVersionCheck: true }), _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) => {
|
|
73
84
|
const isSelected = index === selectedIndex;
|
|
74
85
|
return (_jsxs(Box, { paddingX: 2, paddingY: 0, borderStyle: "single", borderColor: isSelected ? item.color : colors.border, marginTop: index === 0 ? 1 : 0, flexShrink: 0, children: [isSelected && (_jsxs(_Fragment, { children: [_jsx(Text, { color: item.color, bold: true, children: 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 }), _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));
|
|
75
|
-
})] }), _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"] }) }) })] }));
|
|
86
|
+
})] }), _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", updateAvailable && " • [u] Update"] }) }) })] }));
|
|
76
87
|
};
|
|
@@ -1,56 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React from "react";
|
|
3
2
|
import { Box, Text } from "ink";
|
|
4
3
|
import { colors } from "../utils/theme.js";
|
|
5
|
-
import {
|
|
4
|
+
import { useUpdateCheck } from "../hooks/useUpdateCheck.js";
|
|
6
5
|
/**
|
|
7
6
|
* Version check component that checks npm for updates and displays a notification
|
|
8
7
|
* Restored from git history and enhanced with better visual styling
|
|
9
8
|
*/
|
|
10
9
|
export const UpdateNotification = () => {
|
|
11
|
-
const
|
|
12
|
-
const [isChecking, setIsChecking] = React.useState(true);
|
|
13
|
-
React.useEffect(() => {
|
|
14
|
-
const checkForUpdates = async () => {
|
|
15
|
-
try {
|
|
16
|
-
const currentVersion = VERSION;
|
|
17
|
-
const response = await fetch("https://registry.npmjs.org/@runloop/rl-cli/latest");
|
|
18
|
-
if (response.ok) {
|
|
19
|
-
const data = (await response.json());
|
|
20
|
-
const latestVersion = data.version;
|
|
21
|
-
if (latestVersion && latestVersion !== currentVersion) {
|
|
22
|
-
// Check if current version is older than latest
|
|
23
|
-
const compareVersions = (version1, version2) => {
|
|
24
|
-
const v1parts = version1.split(".").map(Number);
|
|
25
|
-
const v2parts = version2.split(".").map(Number);
|
|
26
|
-
for (let i = 0; i < Math.max(v1parts.length, v2parts.length); i++) {
|
|
27
|
-
const v1part = v1parts[i] || 0;
|
|
28
|
-
const v2part = v2parts[i] || 0;
|
|
29
|
-
if (v1part > v2part)
|
|
30
|
-
return 1;
|
|
31
|
-
if (v1part < v2part)
|
|
32
|
-
return -1;
|
|
33
|
-
}
|
|
34
|
-
return 0;
|
|
35
|
-
};
|
|
36
|
-
const isUpdateAvailable = compareVersions(latestVersion, currentVersion) > 0;
|
|
37
|
-
if (isUpdateAvailable) {
|
|
38
|
-
setUpdateAvailable(latestVersion);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
// Silently fail
|
|
45
|
-
}
|
|
46
|
-
finally {
|
|
47
|
-
setIsChecking(false);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
checkForUpdates();
|
|
51
|
-
}, []);
|
|
10
|
+
const { isChecking, updateAvailable, currentVersion } = useUpdateCheck();
|
|
52
11
|
if (isChecking || !updateAvailable) {
|
|
53
12
|
return null;
|
|
54
13
|
}
|
|
55
|
-
return (_jsxs(Box, { borderStyle: "
|
|
14
|
+
return (_jsx(Box, { children: _jsxs(Box, { borderStyle: "arrow", borderColor: colors.warning, paddingX: 1, paddingY: 0, children: [_jsx(Text, { color: colors.warning, children: "\u2728" }), _jsx(Text, { color: colors.text, children: " Update available: " }), _jsx(Text, { color: colors.warning, children: currentVersion }), _jsx(Text, { color: colors.primary, children: " \u2192 " }), _jsx(Text, { color: colors.success, children: updateAvailable }), _jsx(Text, { color: colors.textDim, children: " \u2022 Press " }), _jsx(Text, { color: colors.primary, bold: true, children: "[u]" }), _jsx(Text, { color: colors.textDim, children: " to run: " }), _jsx(Text, { color: colors.textDim, dimColor: true, children: "npm i -g @runloop/rl-cli@latest" })] }) }));
|
|
56
15
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { VERSION } from "../version.js";
|
|
3
|
+
/**
|
|
4
|
+
* Hook to check for CLI updates from npm registry
|
|
5
|
+
* Returns the latest version if an update is available
|
|
6
|
+
*/
|
|
7
|
+
export function useUpdateCheck() {
|
|
8
|
+
const [updateAvailable, setUpdateAvailable] = React.useState(null);
|
|
9
|
+
const [isChecking, setIsChecking] = React.useState(true);
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
const checkForUpdates = async () => {
|
|
12
|
+
try {
|
|
13
|
+
const currentVersion = VERSION;
|
|
14
|
+
const response = await fetch("https://registry.npmjs.org/@runloop/rl-cli/latest");
|
|
15
|
+
if (response.ok) {
|
|
16
|
+
const data = (await response.json());
|
|
17
|
+
const latestVersion = data.version;
|
|
18
|
+
if (latestVersion && latestVersion !== currentVersion) {
|
|
19
|
+
// Check if current version is older than latest
|
|
20
|
+
const compareVersions = (version1, version2) => {
|
|
21
|
+
const v1parts = version1.split(".").map(Number);
|
|
22
|
+
const v2parts = version2.split(".").map(Number);
|
|
23
|
+
for (let i = 0; i < Math.max(v1parts.length, v2parts.length); i++) {
|
|
24
|
+
const v1part = v1parts[i] || 0;
|
|
25
|
+
const v2part = v2parts[i] || 0;
|
|
26
|
+
if (v1part > v2part)
|
|
27
|
+
return 1;
|
|
28
|
+
if (v1part < v2part)
|
|
29
|
+
return -1;
|
|
30
|
+
}
|
|
31
|
+
return 0;
|
|
32
|
+
};
|
|
33
|
+
const isUpdateAvailable = compareVersions(latestVersion, currentVersion) > 0;
|
|
34
|
+
if (isUpdateAvailable) {
|
|
35
|
+
setUpdateAvailable(latestVersion);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Silently fail
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
setIsChecking(false);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
checkForUpdates();
|
|
48
|
+
}, []);
|
|
49
|
+
return {
|
|
50
|
+
isChecking,
|
|
51
|
+
updateAvailable,
|
|
52
|
+
currentVersion: VERSION,
|
|
53
|
+
};
|
|
54
|
+
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -54,7 +54,7 @@ function getClient() {
|
|
|
54
54
|
timeout: 10000, // 10 seconds instead of default 30 seconds
|
|
55
55
|
maxRetries: 2, // 2 retries instead of default 5 (only for retryable errors)
|
|
56
56
|
defaultHeaders: {
|
|
57
|
-
"User-Agent": `Runloop/JS
|
|
57
|
+
"User-Agent": `Runloop/JS - CLI MCP ${VERSION}`,
|
|
58
58
|
},
|
|
59
59
|
});
|
|
60
60
|
}
|
package/dist/utils/client.js
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import { showCursor } from "./screen.js";
|
|
3
|
+
import { processUtils } from "./processUtils.js";
|
|
4
|
+
/**
|
|
5
|
+
* Release terminal from Ink and exec into a new command (one-way, no return)
|
|
6
|
+
* This function never returns - it runs the command synchronously and exits.
|
|
7
|
+
*/
|
|
8
|
+
export function execCommand(command, args) {
|
|
9
|
+
// Release terminal from Ink's control
|
|
10
|
+
process.stdin.pause();
|
|
11
|
+
if (processUtils.stdin.isTTY && processUtils.stdin.setRawMode) {
|
|
12
|
+
processUtils.stdin.setRawMode(false);
|
|
13
|
+
}
|
|
14
|
+
if (processUtils.stdout.isTTY) {
|
|
15
|
+
processUtils.stdout.write("\x1b[0m"); // SGR reset
|
|
16
|
+
}
|
|
17
|
+
showCursor();
|
|
18
|
+
// Run the command synchronously - this blocks until complete
|
|
19
|
+
const result = spawnSync(command, args, { stdio: "inherit" });
|
|
20
|
+
// Exit with the command's exit code
|
|
21
|
+
process.exit(result.status ?? 0);
|
|
22
|
+
}
|