@runloop/rl-cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -33,6 +33,14 @@ program
33
33
  const { default: auth } = await import("./commands/auth.js");
34
34
  auth();
35
35
  });
36
+ program
37
+ .command("check-updates")
38
+ .description("Check for CLI updates")
39
+ .action(async () => {
40
+ const { checkForUpdates } = await import("./utils/config.js");
41
+ console.log("Checking for updates...");
42
+ await checkForUpdates(true);
43
+ });
36
44
  // Devbox commands
37
45
  const devbox = program
38
46
  .command("devbox")
@@ -443,12 +451,15 @@ program
443
451
  process.exit(1);
444
452
  }
445
453
  }
446
- // If no command provided, show main menu
454
+ // If no command provided, show main menu (version check handled in UI)
447
455
  if (args.length === 0) {
448
456
  const { runMainMenu } = await import("./commands/menu.js");
449
457
  runMainMenu();
450
458
  }
451
459
  else {
460
+ // Check for updates for non-interactive commands (stderr output)
461
+ const { checkForUpdates } = await import("./utils/config.js");
462
+ await checkForUpdates();
452
463
  program.parse();
453
464
  }
454
465
  })();
@@ -357,15 +357,15 @@ const ListBlueprintsUI = ({ onBack, onExit }) => {
357
357
  }
358
358
  // Loading state
359
359
  if (loading) {
360
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Blueprints", active: true }] }), _jsx(SpinnerComponent, { message: "Loading blueprints..." })] }));
360
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Blueprints", active: true }], showVersionCheck: true }), _jsx(SpinnerComponent, { message: "Loading blueprints..." })] }));
361
361
  }
362
362
  // Error state
363
363
  if (listError) {
364
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Blueprints", active: true }] }), _jsx(ErrorMessage, { message: "Failed to load blueprints", error: listError })] }));
364
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Blueprints", active: true }], showVersionCheck: true }), _jsx(ErrorMessage, { message: "Failed to load blueprints", error: listError })] }));
365
365
  }
366
366
  // Empty state
367
367
  if (blueprints.length === 0) {
368
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Blueprints", active: true }] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.warning, children: figures.info }), _jsx(Text, { children: " No blueprints found. Try:" }), _jsx(Text, { color: colors.primary, bold: true, children: "rli blueprint create" })] })] }));
368
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Blueprints", active: true }], showVersionCheck: true }), _jsxs(Box, { children: [_jsx(Text, { color: colors.warning, children: figures.info }), _jsx(Text, { children: " No blueprints found. Try:" }), _jsx(Text, { color: colors.primary, bold: true, children: "rli blueprint create" })] })] }));
369
369
  }
370
370
  // Pagination moved earlier
371
371
  // Overlay: draw quick actions popup over the table (keep table visible)
@@ -478,7 +478,7 @@ const ListDevboxesUI = ({ status, onSSHRequest, focusDevboxId, onBack, onExit })
478
478
  }
479
479
  // Show popup with table in background
480
480
  if (showPopup && selectedDevbox) {
481
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes", active: true }] }), !initialLoading && !error && devboxes.length > 0 && (_jsx(_Fragment, { children: _jsx(Table, { data: currentDevboxes, keyExtractor: (devbox) => devbox.id, selectedIndex: selectedIndex, title: `devboxes[${totalCount}]`, columns: [
481
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes", active: true }], showVersionCheck: true }), !initialLoading && !error && devboxes.length > 0 && (_jsx(_Fragment, { children: _jsx(Table, { data: currentDevboxes, keyExtractor: (devbox) => devbox.id, selectedIndex: selectedIndex, title: `devboxes[${totalCount}]`, columns: [
482
482
  {
483
483
  key: "statusIcon",
484
484
  label: "",
@@ -555,10 +555,10 @@ const ListDevboxesUI = ({ status, onSSHRequest, focusDevboxId, onBack, onExit })
555
555
  }
556
556
  // If initial loading or error, show that first
557
557
  if (initialLoading) {
558
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes", active: true }] }), _jsx(SpinnerComponent, { message: "Loading..." })] }));
558
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes", active: true }], showVersionCheck: true }), _jsx(SpinnerComponent, { message: "Loading..." })] }));
559
559
  }
560
560
  if (error) {
561
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes", active: true }] }), _jsx(ErrorMessage, { message: "Failed to list devboxes", error: error })] }));
561
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes", active: true }], showVersionCheck: true }), _jsx(ErrorMessage, { message: "Failed to list devboxes", error: error })] }));
562
562
  }
563
563
  // List view with data (always show, even if empty)
564
564
  return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Devboxes", active: true }] }), currentDevboxes && currentDevboxes.length >= 0 && (_jsxs(_Fragment, { children: [searchMode && (_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.primary, children: [figures.pointerSmall, " Search:", " "] }), _jsx(TextInput, { value: searchQuery, onChange: setSearchQuery, placeholder: "Type to search (name, id, status)...", onSubmit: () => {
@@ -2,8 +2,57 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from "react";
3
3
  import { Box, Text } from "ink";
4
4
  import { colors } from "../utils/theme.js";
5
- export const Breadcrumb = React.memo(({ items }) => {
5
+ import { VERSION } from "../cli.js";
6
+ // Version check component
7
+ const VersionCheck = () => {
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 = process.env.npm_package_version || "0.0.1";
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 (error) {
41
+ // Silently fail
42
+ }
43
+ finally {
44
+ setIsChecking(false);
45
+ }
46
+ };
47
+ checkForUpdates();
48
+ }, []);
49
+ if (isChecking || !updateAvailable) {
50
+ return null;
51
+ }
52
+ return (_jsxs(Box, { children: [_jsx(Text, { color: colors.primary, bold: true, children: "\u2728" }), _jsxs(Text, { color: colors.text, bold: true, children: [" ", "Update available:", " "] }), _jsx(Text, { color: colors.textDim, dimColor: true, children: VERSION }), _jsxs(Text, { color: colors.primary, bold: true, children: [" ", "\u2192", " "] }), _jsx(Text, { color: colors.success, bold: true, children: updateAvailable }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022 Run:", " "] }), _jsx(Text, { color: colors.primary, bold: true, children: "npm install -g @runloop/rl-cli@latest" })] }));
53
+ };
54
+ export const Breadcrumb = React.memo(({ items, showVersionCheck = false }) => {
6
55
  const env = process.env.RUNLOOP_ENV?.toLowerCase();
7
56
  const isDevEnvironment = env === "dev";
8
- return (_jsx(Box, { marginBottom: 1, paddingX: 1, 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: "redBright", bold: true, children: [" ", "(dev)"] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u203A", " "] }), items.map((item, index) => (_jsxs(React.Fragment, { children: [_jsx(Text, { color: item.active ? colors.text : colors.textDim, bold: item.active, dimColor: !item.active, children: item.label }), index < items.length - 1 && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u203A", " "] }))] }, index)))] }) }));
57
+ return (_jsxs(Box, { marginBottom: 1, paddingX: 1, paddingY: 0, flexDirection: "column", 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: "redBright", bold: true, children: [" ", "(dev)"] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u203A", " "] }), items.map((item, index) => (_jsxs(React.Fragment, { children: [_jsx(Text, { color: item.active ? colors.text : colors.textDim, bold: item.active, dimColor: !item.active, children: item.label }), index < items.length - 1 && (_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u203A", " "] }))] }, index)))] }), showVersionCheck && (_jsx(Box, { paddingX: 2, marginTop: 0, children: _jsx(VersionCheck, {}) }))] }));
9
58
  });
@@ -608,7 +608,7 @@ export const DevboxActionsMenu = ({ devbox, onBack, breadcrumbItems = [
608
608
  }
609
609
  // Operations selection mode - only show if not skipping
610
610
  if (!skipOperationsMenu || !executingOperation) {
611
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.play, " Operations"] }), _jsx(Box, { flexDirection: "column", children: operations.map((op, index) => {
611
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems, showVersionCheck: true }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.play, " Operations"] }), _jsx(Box, { flexDirection: "column", children: operations.map((op, index) => {
612
612
  const isSelected = index === selectedOperation;
613
613
  return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? colors.primary : colors.textDim, children: [isSelected ? figures.pointer : " ", " "] }), _jsxs(Text, { color: isSelected ? op.color : colors.textDim, bold: isSelected, children: [op.icon, " ", op.label] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "[", op.shortcut, "]"] })] }, op.key));
614
614
  }) })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [Enter] Select \u2022 [q] Back"] }) })] }));
@@ -65,7 +65,7 @@ export const MainMenu = React.memo(({ onSelect }) => {
65
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
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
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) => {
68
+ return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(Breadcrumb, { items: [{ label: "Home", active: true }], showVersionCheck: 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
69
  const isSelected = index === selectedIndex;
70
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
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"] }) }) })] }));
@@ -101,10 +101,10 @@ export const ResourceActionsMenu = (props) => {
101
101
  // Screens
102
102
  if (operationResult || operationError) {
103
103
  const label = operations.find((o) => o.key === executingOperation)?.label;
104
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: operationError ? colors.error : colors.success, children: operationError ? `${label} failed` : `${label} completed` }), !!operationResult && (_jsx(Text, { color: colors.textDim, dimColor: true, children: operationResult })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.pointerSmall, " Press [Enter] to go back"] })] })] }));
104
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems, showVersionCheck: true }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: operationError ? colors.error : colors.success, children: operationError ? `${label} failed` : `${label} completed` }), !!operationResult && (_jsx(Text, { color: colors.textDim, dimColor: true, children: operationResult })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.pointerSmall, " Press [Enter] to go back"] })] })] }));
105
105
  }
106
106
  if (executingOperation && selectedOp?.needsInput) {
107
- return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: colors.textDim, children: [selectedOp.inputPrompt || "Input:", " "] }), _jsxs(Text, { children: [" ", operationInput] }), _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press [Enter] to execute \u2022 [q or esc] Cancel" })] })] }));
107
+ return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems, showVersionCheck: true }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: colors.textDim, children: [selectedOp.inputPrompt || "Input:", " "] }), _jsxs(Text, { children: [" ", operationInput] }), _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press [Enter] to execute \u2022 [q or esc] Cancel" })] })] }));
108
108
  }
109
109
  // Operations menu
110
110
  return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: breadcrumbItems }), _jsx(Box, { marginTop: 1, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: resource, operations: operations.map((op) => ({
@@ -1,7 +1,9 @@
1
1
  import Conf from "conf";
2
2
  import { homedir } from "os";
3
3
  import { join } from "path";
4
- import { existsSync, statSync, mkdirSync, writeFileSync } from "fs";
4
+ import { existsSync, statSync, mkdirSync, writeFileSync, readFileSync } from "fs";
5
+ import { fileURLToPath } from "url";
6
+ import { dirname } from "path";
5
7
  const config = new Conf({
6
8
  projectName: "runloop-cli",
7
9
  });
@@ -31,6 +33,25 @@ export function sshUrl() {
31
33
  export function getCacheDir() {
32
34
  return join(homedir(), ".cache", "rl-cli");
33
35
  }
36
+ function getCurrentVersion() {
37
+ try {
38
+ // First try environment variable (when installed via npm)
39
+ if (process.env.npm_package_version) {
40
+ return process.env.npm_package_version;
41
+ }
42
+ // Fall back to reading package.json directly
43
+ const __filename = fileURLToPath(import.meta.url);
44
+ const __dirname = dirname(__filename);
45
+ // When running from dist/, we need to go up two levels to find package.json
46
+ const packageJsonPath = join(__dirname, "../../package.json");
47
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
48
+ return packageJson.version;
49
+ }
50
+ catch (error) {
51
+ // Ultimate fallback
52
+ return "0.1.0";
53
+ }
54
+ }
34
55
  export function shouldCheckForUpdates() {
35
56
  const cacheDir = getCacheDir();
36
57
  const cacheFile = join(cacheDir, "last_update_check");
@@ -38,8 +59,13 @@ export function shouldCheckForUpdates() {
38
59
  return true;
39
60
  }
40
61
  const stats = statSync(cacheFile);
41
- const daysSinceUpdate = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
42
- return daysSinceUpdate >= 1;
62
+ const hoursSinceUpdate = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60);
63
+ return hoursSinceUpdate >= 6;
64
+ }
65
+ export function hasCachedUpdateInfo() {
66
+ const cacheDir = getCacheDir();
67
+ const cacheFile = join(cacheDir, "last_update_check");
68
+ return existsSync(cacheFile);
43
69
  }
44
70
  export function updateCheckCache() {
45
71
  const cacheDir = getCacheDir();
@@ -51,3 +77,67 @@ export function updateCheckCache() {
51
77
  // Touch the cache file
52
78
  writeFileSync(cacheFile, "");
53
79
  }
80
+ export async function checkForUpdates(force = false) {
81
+ const currentVersion = getCurrentVersion();
82
+ // Always show cached result if available and not forcing
83
+ if (!force && hasCachedUpdateInfo() && !shouldCheckForUpdates()) {
84
+ // Show cached update info (we know there's an update available)
85
+ console.error(`\nšŸ”„ Update available: ${currentVersion} → 0.1.0\n` +
86
+ ` Run: npm install -g @runloop/rl-cli@latest\n\n`);
87
+ return;
88
+ }
89
+ // Only fetch from npm if cache is expired or forcing
90
+ if (!force && !shouldCheckForUpdates()) {
91
+ return;
92
+ }
93
+ try {
94
+ const response = await fetch("https://registry.npmjs.org/@runloop/rl-cli/latest");
95
+ if (!response.ok) {
96
+ if (force) {
97
+ console.error("āŒ Failed to check for updates\n");
98
+ }
99
+ return; // Silently fail if we can't check
100
+ }
101
+ const data = await response.json();
102
+ const latestVersion = data.version;
103
+ if (force) {
104
+ console.error(`Current version: ${currentVersion}\n`);
105
+ console.error(`Latest version: ${latestVersion}\n`);
106
+ }
107
+ if (latestVersion && latestVersion !== currentVersion) {
108
+ // Check if current version is older than latest
109
+ const isUpdateAvailable = compareVersions(latestVersion, currentVersion) > 0;
110
+ if (isUpdateAvailable) {
111
+ console.error(`\nšŸ”„ Update available: ${currentVersion} → ${latestVersion}\n` +
112
+ ` Run: npm install -g @runloop/rl-cli@latest\n\n`);
113
+ }
114
+ else if (force) {
115
+ console.error("āœ… You're running the latest version!\n");
116
+ }
117
+ }
118
+ else if (force) {
119
+ console.error("āœ… You're running the latest version!\n");
120
+ }
121
+ // Update the cache to indicate we've checked
122
+ updateCheckCache();
123
+ }
124
+ catch (error) {
125
+ if (force) {
126
+ console.error(`āŒ Error checking for updates: ${error}\n`);
127
+ }
128
+ // Silently fail - don't interrupt the user's workflow
129
+ }
130
+ }
131
+ function compareVersions(version1, version2) {
132
+ const v1parts = version1.split('.').map(Number);
133
+ const v2parts = version2.split('.').map(Number);
134
+ for (let i = 0; i < Math.max(v1parts.length, v2parts.length); i++) {
135
+ const v1part = v1parts[i] || 0;
136
+ const v2part = v2parts[i] || 0;
137
+ if (v1part > v2part)
138
+ return 1;
139
+ if (v1part < v2part)
140
+ return -1;
141
+ }
142
+ return 0;
143
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runloop/rl-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Beautiful CLI for Runloop devbox management",
5
5
  "type": "module",
6
6
  "bin": {