@runloop/rl-cli 0.1.2 ā 0.3.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 +54 -10
- package/dist/cli.js +79 -72
- package/dist/commands/auth.js +2 -2
- package/dist/commands/blueprint/create.js +31 -83
- package/dist/commands/blueprint/get.js +29 -34
- package/dist/commands/blueprint/list.js +278 -230
- package/dist/commands/blueprint/logs.js +133 -37
- package/dist/commands/config.js +118 -0
- package/dist/commands/devbox/create.js +120 -40
- package/dist/commands/devbox/delete.js +17 -33
- package/dist/commands/devbox/download.js +29 -43
- package/dist/commands/devbox/exec.js +22 -39
- package/dist/commands/devbox/execAsync.js +20 -37
- package/dist/commands/devbox/get.js +13 -35
- package/dist/commands/devbox/getAsync.js +12 -34
- package/dist/commands/devbox/list.js +241 -402
- package/dist/commands/devbox/logs.js +20 -38
- package/dist/commands/devbox/read.js +29 -43
- package/dist/commands/devbox/resume.js +13 -35
- package/dist/commands/devbox/rsync.js +26 -78
- package/dist/commands/devbox/scp.js +25 -79
- package/dist/commands/devbox/sendStdin.js +41 -0
- package/dist/commands/devbox/shutdown.js +13 -35
- package/dist/commands/devbox/ssh.js +46 -78
- package/dist/commands/devbox/suspend.js +13 -35
- package/dist/commands/devbox/tunnel.js +37 -88
- package/dist/commands/devbox/upload.js +28 -36
- package/dist/commands/devbox/write.js +29 -44
- package/dist/commands/mcp-http.js +6 -5
- package/dist/commands/mcp-install.js +12 -10
- package/dist/commands/mcp.js +5 -4
- package/dist/commands/menu.js +26 -67
- package/dist/commands/object/delete.js +12 -34
- package/dist/commands/object/download.js +26 -74
- package/dist/commands/object/get.js +12 -34
- package/dist/commands/object/list.js +15 -93
- package/dist/commands/object/upload.js +35 -96
- package/dist/commands/snapshot/create.js +23 -39
- package/dist/commands/snapshot/delete.js +17 -33
- package/dist/commands/snapshot/get.js +16 -0
- package/dist/commands/snapshot/list.js +309 -80
- package/dist/commands/snapshot/status.js +12 -34
- package/dist/components/ActionsPopup.js +64 -39
- package/dist/components/Banner.js +7 -1
- package/dist/components/Breadcrumb.js +11 -48
- package/dist/components/DevboxActionsMenu.js +117 -207
- package/dist/components/DevboxCreatePage.js +12 -7
- package/dist/components/DevboxDetailPage.js +76 -28
- package/dist/components/ErrorBoundary.js +29 -0
- package/dist/components/ErrorMessage.js +10 -2
- package/dist/components/Header.js +12 -4
- package/dist/components/InteractiveSpawn.js +104 -0
- package/dist/components/LogsViewer.js +169 -0
- package/dist/components/MainMenu.js +37 -33
- package/dist/components/MetadataDisplay.js +4 -4
- package/dist/components/OperationsMenu.js +1 -1
- package/dist/components/ResourceActionsMenu.js +4 -4
- package/dist/components/ResourceListView.js +46 -34
- package/dist/components/Spinner.js +7 -2
- package/dist/components/StatusBadge.js +1 -1
- package/dist/components/SuccessMessage.js +12 -2
- package/dist/components/Table.js +16 -6
- package/dist/components/UpdateNotification.js +56 -0
- package/dist/hooks/useCursorPagination.js +125 -85
- package/dist/hooks/useExitOnCtrlC.js +15 -0
- package/dist/hooks/useViewportHeight.js +47 -0
- package/dist/mcp/server-http.js +2 -1
- package/dist/mcp/server.js +71 -7
- package/dist/router/Router.js +70 -0
- package/dist/router/types.js +1 -0
- package/dist/screens/BlueprintListScreen.js +7 -0
- package/dist/screens/BlueprintLogsScreen.js +74 -0
- package/dist/screens/DevboxActionsScreen.js +25 -0
- package/dist/screens/DevboxCreateScreen.js +11 -0
- package/dist/screens/DevboxDetailScreen.js +60 -0
- package/dist/screens/DevboxListScreen.js +23 -0
- package/dist/screens/LogsSessionScreen.js +49 -0
- package/dist/screens/MenuScreen.js +23 -0
- package/dist/screens/SSHSessionScreen.js +55 -0
- package/dist/screens/SnapshotListScreen.js +7 -0
- package/dist/services/blueprintService.js +101 -0
- package/dist/services/devboxService.js +215 -0
- package/dist/services/snapshotService.js +81 -0
- package/dist/store/blueprintStore.js +89 -0
- package/dist/store/devboxStore.js +105 -0
- package/dist/store/index.js +7 -0
- package/dist/store/navigationStore.js +101 -0
- package/dist/store/snapshotStore.js +87 -0
- package/dist/utils/client.js +4 -2
- package/dist/utils/config.js +22 -111
- package/dist/utils/interactiveCommand.js +3 -2
- package/dist/utils/logFormatter.js +208 -0
- package/dist/utils/memoryMonitor.js +85 -0
- package/dist/utils/output.js +153 -61
- package/dist/utils/process.js +106 -0
- package/dist/utils/processUtils.js +135 -0
- package/dist/utils/screen.js +61 -0
- package/dist/utils/ssh.js +6 -3
- package/dist/utils/sshSession.js +5 -29
- package/dist/utils/terminalDetection.js +185 -0
- package/dist/utils/terminalSync.js +39 -0
- package/dist/utils/theme.js +162 -13
- package/dist/utils/versionCheck.js +53 -0
- package/dist/version.js +12 -0
- package/package.json +19 -17
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blueprint Store - Manages blueprint list state, pagination, and caching
|
|
3
|
+
*/
|
|
4
|
+
import { create } from "zustand";
|
|
5
|
+
const MAX_CACHE_SIZE = 10;
|
|
6
|
+
export const useBlueprintStore = create((set, get) => ({
|
|
7
|
+
blueprints: [],
|
|
8
|
+
loading: false,
|
|
9
|
+
initialLoading: true,
|
|
10
|
+
error: null,
|
|
11
|
+
currentPage: 0,
|
|
12
|
+
pageSize: 10,
|
|
13
|
+
totalCount: 0,
|
|
14
|
+
hasMore: false,
|
|
15
|
+
pageCache: new Map(),
|
|
16
|
+
lastIdCache: new Map(),
|
|
17
|
+
searchQuery: "",
|
|
18
|
+
selectedIndex: 0,
|
|
19
|
+
setBlueprints: (blueprints) => set({ blueprints }),
|
|
20
|
+
setLoading: (loading) => set({ loading }),
|
|
21
|
+
setInitialLoading: (loading) => set({ initialLoading: loading }),
|
|
22
|
+
setError: (error) => set({ error }),
|
|
23
|
+
setCurrentPage: (page) => set({ currentPage: page }),
|
|
24
|
+
setPageSize: (size) => set({ pageSize: size }),
|
|
25
|
+
setTotalCount: (count) => set({ totalCount: count }),
|
|
26
|
+
setHasMore: (hasMore) => set({ hasMore }),
|
|
27
|
+
setSearchQuery: (query) => set({ searchQuery: query }),
|
|
28
|
+
setSelectedIndex: (index) => set({ selectedIndex: index }),
|
|
29
|
+
cachePageData: (page, data, lastId) => {
|
|
30
|
+
const state = get();
|
|
31
|
+
const pageCache = state.pageCache;
|
|
32
|
+
const lastIdCache = state.lastIdCache;
|
|
33
|
+
// Aggressive LRU eviction
|
|
34
|
+
if (pageCache.size >= MAX_CACHE_SIZE) {
|
|
35
|
+
const oldestKey = pageCache.keys().next().value;
|
|
36
|
+
if (oldestKey !== undefined) {
|
|
37
|
+
pageCache.delete(oldestKey);
|
|
38
|
+
lastIdCache.delete(oldestKey);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Create plain data objects to avoid SDK references
|
|
42
|
+
const plainData = data.map((b) => ({
|
|
43
|
+
id: b.id,
|
|
44
|
+
name: b.name,
|
|
45
|
+
status: b.status,
|
|
46
|
+
create_time_ms: b.create_time_ms,
|
|
47
|
+
build_status: b.build_status,
|
|
48
|
+
architecture: b.architecture,
|
|
49
|
+
resources: b.resources,
|
|
50
|
+
}));
|
|
51
|
+
pageCache.set(page, plainData);
|
|
52
|
+
lastIdCache.set(page, lastId);
|
|
53
|
+
set({});
|
|
54
|
+
},
|
|
55
|
+
getCachedPage: (page) => {
|
|
56
|
+
return get().pageCache.get(page);
|
|
57
|
+
},
|
|
58
|
+
clearCache: () => {
|
|
59
|
+
const state = get();
|
|
60
|
+
state.pageCache.clear();
|
|
61
|
+
state.lastIdCache.clear();
|
|
62
|
+
set({
|
|
63
|
+
pageCache: new Map(),
|
|
64
|
+
lastIdCache: new Map(),
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
clearAll: () => {
|
|
68
|
+
const state = get();
|
|
69
|
+
state.pageCache.clear();
|
|
70
|
+
state.lastIdCache.clear();
|
|
71
|
+
set({
|
|
72
|
+
blueprints: [],
|
|
73
|
+
loading: false,
|
|
74
|
+
initialLoading: true,
|
|
75
|
+
error: null,
|
|
76
|
+
currentPage: 0,
|
|
77
|
+
totalCount: 0,
|
|
78
|
+
hasMore: false,
|
|
79
|
+
pageCache: new Map(),
|
|
80
|
+
lastIdCache: new Map(),
|
|
81
|
+
searchQuery: "",
|
|
82
|
+
selectedIndex: 0,
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
getSelectedBlueprint: () => {
|
|
86
|
+
const state = get();
|
|
87
|
+
return state.blueprints[state.selectedIndex];
|
|
88
|
+
},
|
|
89
|
+
}));
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devbox Store - Manages devbox list state, pagination, and caching
|
|
3
|
+
* Replaces useState/useRef from ListDevboxesUI
|
|
4
|
+
*/
|
|
5
|
+
import { create } from "zustand";
|
|
6
|
+
const MAX_CACHE_SIZE = 10; // Limit cache to 10 pages
|
|
7
|
+
export const useDevboxStore = create((set, get) => ({
|
|
8
|
+
// Initial state
|
|
9
|
+
devboxes: [],
|
|
10
|
+
loading: false,
|
|
11
|
+
initialLoading: true,
|
|
12
|
+
error: null,
|
|
13
|
+
currentPage: 0,
|
|
14
|
+
pageSize: 10,
|
|
15
|
+
totalCount: 0,
|
|
16
|
+
hasMore: false,
|
|
17
|
+
pageCache: new Map(),
|
|
18
|
+
lastIdCache: new Map(),
|
|
19
|
+
searchQuery: "",
|
|
20
|
+
statusFilter: undefined,
|
|
21
|
+
selectedIndex: 0,
|
|
22
|
+
// Actions
|
|
23
|
+
setDevboxes: (devboxes) => {
|
|
24
|
+
const state = get();
|
|
25
|
+
const maxIndex = devboxes.length > 0 ? devboxes.length - 1 : 0;
|
|
26
|
+
const clampedIndex = Math.max(0, Math.min(state.selectedIndex, maxIndex));
|
|
27
|
+
set({ devboxes, selectedIndex: clampedIndex });
|
|
28
|
+
},
|
|
29
|
+
setLoading: (loading) => set({ loading }),
|
|
30
|
+
setInitialLoading: (loading) => set({ initialLoading: loading }),
|
|
31
|
+
setError: (error) => set({ error }),
|
|
32
|
+
setCurrentPage: (page) => set({ currentPage: page }),
|
|
33
|
+
setPageSize: (size) => set({ pageSize: size }),
|
|
34
|
+
setTotalCount: (count) => set({ totalCount: count }),
|
|
35
|
+
setHasMore: (hasMore) => set({ hasMore }),
|
|
36
|
+
setSearchQuery: (query) => set({ searchQuery: query }),
|
|
37
|
+
setStatusFilter: (status) => set({ statusFilter: status }),
|
|
38
|
+
setSelectedIndex: (index) => {
|
|
39
|
+
const state = get();
|
|
40
|
+
const maxIndex = state.devboxes.length > 0 ? state.devboxes.length - 1 : 0;
|
|
41
|
+
const clampedIndex = Math.max(0, Math.min(index, maxIndex));
|
|
42
|
+
set({ selectedIndex: clampedIndex });
|
|
43
|
+
},
|
|
44
|
+
// Cache management with LRU eviction - FIXED: No shallow copies
|
|
45
|
+
cachePageData: (page, data, lastId) => {
|
|
46
|
+
const state = get();
|
|
47
|
+
const pageCache = state.pageCache;
|
|
48
|
+
const lastIdCache = state.lastIdCache;
|
|
49
|
+
// Aggressive LRU eviction: Remove oldest entries if at limit
|
|
50
|
+
if (pageCache.size >= MAX_CACHE_SIZE) {
|
|
51
|
+
const oldestKey = pageCache.keys().next().value;
|
|
52
|
+
if (oldestKey !== undefined) {
|
|
53
|
+
pageCache.delete(oldestKey);
|
|
54
|
+
lastIdCache.delete(oldestKey);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Deep copy all fields to avoid SDK references
|
|
58
|
+
const plainData = data.map((d) => {
|
|
59
|
+
// Create a deep copy using JSON serialization for safety
|
|
60
|
+
return JSON.parse(JSON.stringify(d));
|
|
61
|
+
});
|
|
62
|
+
pageCache.set(page, plainData);
|
|
63
|
+
lastIdCache.set(page, lastId);
|
|
64
|
+
// Trigger update without creating new Map
|
|
65
|
+
set({});
|
|
66
|
+
},
|
|
67
|
+
getCachedPage: (page) => {
|
|
68
|
+
return get().pageCache.get(page);
|
|
69
|
+
},
|
|
70
|
+
clearCache: () => {
|
|
71
|
+
const state = get();
|
|
72
|
+
// Explicitly clear all entries before reassigning
|
|
73
|
+
state.pageCache.clear();
|
|
74
|
+
state.lastIdCache.clear();
|
|
75
|
+
set({
|
|
76
|
+
pageCache: new Map(),
|
|
77
|
+
lastIdCache: new Map(),
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
// Aggressive memory cleanup
|
|
81
|
+
clearAll: () => {
|
|
82
|
+
const state = get();
|
|
83
|
+
// Clear existing structures first to release references
|
|
84
|
+
state.pageCache.clear();
|
|
85
|
+
state.lastIdCache.clear();
|
|
86
|
+
set({
|
|
87
|
+
devboxes: [],
|
|
88
|
+
loading: false,
|
|
89
|
+
initialLoading: true,
|
|
90
|
+
error: null,
|
|
91
|
+
currentPage: 0,
|
|
92
|
+
totalCount: 0,
|
|
93
|
+
hasMore: false,
|
|
94
|
+
pageCache: new Map(),
|
|
95
|
+
lastIdCache: new Map(),
|
|
96
|
+
searchQuery: "",
|
|
97
|
+
selectedIndex: 0,
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
// Getters
|
|
101
|
+
getSelectedDevbox: () => {
|
|
102
|
+
const state = get();
|
|
103
|
+
return state.devboxes[state.selectedIndex];
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root Store - Exports all stores for easy importing
|
|
3
|
+
*/
|
|
4
|
+
export { useNavigation, useNavigationStore, NavigationProvider, } from "./navigationStore.js";
|
|
5
|
+
export { useDevboxStore } from "./devboxStore.js";
|
|
6
|
+
export { useBlueprintStore } from "./blueprintStore.js";
|
|
7
|
+
export { useSnapshotStore } from "./snapshotStore.js";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
const NavigationContext = React.createContext(null);
|
|
4
|
+
export function NavigationProvider({ initialScreen = "menu", initialParams = {}, children, }) {
|
|
5
|
+
// Use a single state object to avoid timing issues
|
|
6
|
+
const [state, setState] = React.useState({
|
|
7
|
+
currentScreen: initialScreen,
|
|
8
|
+
params: initialParams,
|
|
9
|
+
history: [],
|
|
10
|
+
});
|
|
11
|
+
const navigate = React.useCallback((screen, newParams = {}) => {
|
|
12
|
+
setState((prev) => ({
|
|
13
|
+
currentScreen: screen,
|
|
14
|
+
params: newParams,
|
|
15
|
+
history: [
|
|
16
|
+
...prev.history,
|
|
17
|
+
{ screen: prev.currentScreen, params: prev.params },
|
|
18
|
+
],
|
|
19
|
+
}));
|
|
20
|
+
}, []);
|
|
21
|
+
const push = React.useCallback((screen, newParams = {}) => {
|
|
22
|
+
setState((prev) => ({
|
|
23
|
+
currentScreen: screen,
|
|
24
|
+
params: newParams,
|
|
25
|
+
history: [
|
|
26
|
+
...prev.history,
|
|
27
|
+
{ screen: prev.currentScreen, params: prev.params },
|
|
28
|
+
],
|
|
29
|
+
}));
|
|
30
|
+
}, []);
|
|
31
|
+
const replace = React.useCallback((screen, newParams = {}) => {
|
|
32
|
+
setState((prev) => ({
|
|
33
|
+
...prev,
|
|
34
|
+
currentScreen: screen,
|
|
35
|
+
params: newParams,
|
|
36
|
+
}));
|
|
37
|
+
}, []);
|
|
38
|
+
const goBack = React.useCallback(() => {
|
|
39
|
+
setState((prev) => {
|
|
40
|
+
if (prev.history.length > 0) {
|
|
41
|
+
const newHistory = [...prev.history];
|
|
42
|
+
const previousScreen = newHistory.pop();
|
|
43
|
+
return {
|
|
44
|
+
currentScreen: previousScreen.screen,
|
|
45
|
+
params: previousScreen.params,
|
|
46
|
+
history: newHistory,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// If no history, go to menu
|
|
51
|
+
return {
|
|
52
|
+
currentScreen: "menu",
|
|
53
|
+
params: {},
|
|
54
|
+
history: [],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}, []);
|
|
59
|
+
const reset = React.useCallback(() => {
|
|
60
|
+
setState({
|
|
61
|
+
currentScreen: "menu",
|
|
62
|
+
params: {},
|
|
63
|
+
history: [],
|
|
64
|
+
});
|
|
65
|
+
}, []);
|
|
66
|
+
const canGoBack = React.useCallback(() => state.history.length > 0, [state.history.length]);
|
|
67
|
+
const value = React.useMemo(() => ({
|
|
68
|
+
currentScreen: state.currentScreen,
|
|
69
|
+
params: state.params,
|
|
70
|
+
navigate,
|
|
71
|
+
push,
|
|
72
|
+
replace,
|
|
73
|
+
goBack,
|
|
74
|
+
reset,
|
|
75
|
+
canGoBack,
|
|
76
|
+
}), [
|
|
77
|
+
state.currentScreen,
|
|
78
|
+
state.params,
|
|
79
|
+
navigate,
|
|
80
|
+
push,
|
|
81
|
+
replace,
|
|
82
|
+
goBack,
|
|
83
|
+
reset,
|
|
84
|
+
canGoBack,
|
|
85
|
+
]);
|
|
86
|
+
return (_jsx(NavigationContext.Provider, { value: value, children: children }));
|
|
87
|
+
}
|
|
88
|
+
export function useNavigation() {
|
|
89
|
+
const context = React.useContext(NavigationContext);
|
|
90
|
+
if (!context) {
|
|
91
|
+
throw new Error("useNavigation must be used within NavigationProvider");
|
|
92
|
+
}
|
|
93
|
+
return context;
|
|
94
|
+
}
|
|
95
|
+
export function useNavigationStore(selector) {
|
|
96
|
+
const context = useNavigation();
|
|
97
|
+
if (!selector) {
|
|
98
|
+
return context;
|
|
99
|
+
}
|
|
100
|
+
return selector(context);
|
|
101
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot Store - Manages snapshot list state, pagination, and caching
|
|
3
|
+
*/
|
|
4
|
+
import { create } from "zustand";
|
|
5
|
+
const MAX_CACHE_SIZE = 10;
|
|
6
|
+
export const useSnapshotStore = create((set, get) => ({
|
|
7
|
+
snapshots: [],
|
|
8
|
+
loading: false,
|
|
9
|
+
initialLoading: true,
|
|
10
|
+
error: null,
|
|
11
|
+
currentPage: 0,
|
|
12
|
+
pageSize: 10,
|
|
13
|
+
totalCount: 0,
|
|
14
|
+
hasMore: false,
|
|
15
|
+
pageCache: new Map(),
|
|
16
|
+
lastIdCache: new Map(),
|
|
17
|
+
devboxIdFilter: undefined,
|
|
18
|
+
selectedIndex: 0,
|
|
19
|
+
setSnapshots: (snapshots) => set({ snapshots }),
|
|
20
|
+
setLoading: (loading) => set({ loading }),
|
|
21
|
+
setInitialLoading: (loading) => set({ initialLoading: loading }),
|
|
22
|
+
setError: (error) => set({ error }),
|
|
23
|
+
setCurrentPage: (page) => set({ currentPage: page }),
|
|
24
|
+
setPageSize: (size) => set({ pageSize: size }),
|
|
25
|
+
setTotalCount: (count) => set({ totalCount: count }),
|
|
26
|
+
setHasMore: (hasMore) => set({ hasMore }),
|
|
27
|
+
setDevboxIdFilter: (devboxId) => set({ devboxIdFilter: devboxId }),
|
|
28
|
+
setSelectedIndex: (index) => set({ selectedIndex: index }),
|
|
29
|
+
cachePageData: (page, data, lastId) => {
|
|
30
|
+
const state = get();
|
|
31
|
+
const pageCache = state.pageCache;
|
|
32
|
+
const lastIdCache = state.lastIdCache;
|
|
33
|
+
// Aggressive LRU eviction
|
|
34
|
+
if (pageCache.size >= MAX_CACHE_SIZE) {
|
|
35
|
+
const oldestKey = pageCache.keys().next().value;
|
|
36
|
+
if (oldestKey !== undefined) {
|
|
37
|
+
pageCache.delete(oldestKey);
|
|
38
|
+
lastIdCache.delete(oldestKey);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Create plain data objects to avoid SDK references
|
|
42
|
+
const plainData = data.map((s) => ({
|
|
43
|
+
id: s.id,
|
|
44
|
+
name: s.name,
|
|
45
|
+
devbox_id: s.devbox_id,
|
|
46
|
+
status: s.status,
|
|
47
|
+
create_time_ms: s.create_time_ms,
|
|
48
|
+
}));
|
|
49
|
+
pageCache.set(page, plainData);
|
|
50
|
+
lastIdCache.set(page, lastId);
|
|
51
|
+
set({});
|
|
52
|
+
},
|
|
53
|
+
getCachedPage: (page) => {
|
|
54
|
+
return get().pageCache.get(page);
|
|
55
|
+
},
|
|
56
|
+
clearCache: () => {
|
|
57
|
+
const state = get();
|
|
58
|
+
state.pageCache.clear();
|
|
59
|
+
state.lastIdCache.clear();
|
|
60
|
+
set({
|
|
61
|
+
pageCache: new Map(),
|
|
62
|
+
lastIdCache: new Map(),
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
clearAll: () => {
|
|
66
|
+
const state = get();
|
|
67
|
+
state.pageCache.clear();
|
|
68
|
+
state.lastIdCache.clear();
|
|
69
|
+
set({
|
|
70
|
+
snapshots: [],
|
|
71
|
+
loading: false,
|
|
72
|
+
initialLoading: true,
|
|
73
|
+
error: null,
|
|
74
|
+
currentPage: 0,
|
|
75
|
+
totalCount: 0,
|
|
76
|
+
hasMore: false,
|
|
77
|
+
pageCache: new Map(),
|
|
78
|
+
lastIdCache: new Map(),
|
|
79
|
+
devboxIdFilter: undefined,
|
|
80
|
+
selectedIndex: 0,
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
getSelectedSnapshot: () => {
|
|
84
|
+
const state = get();
|
|
85
|
+
return state.snapshots[state.selectedIndex];
|
|
86
|
+
},
|
|
87
|
+
}));
|
package/dist/utils/client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Runloop from "@runloop/api-client";
|
|
2
|
+
import { VERSION } from "@runloop/api-client/version.js";
|
|
2
3
|
import { getConfig } from "./config.js";
|
|
3
4
|
/**
|
|
4
5
|
* Get the base URL based on RUNLOOP_ENV environment variable
|
|
@@ -24,7 +25,8 @@ export function getClient() {
|
|
|
24
25
|
return new Runloop({
|
|
25
26
|
bearerToken: config.apiKey,
|
|
26
27
|
baseURL,
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
defaultHeaders: {
|
|
29
|
+
"User-Agent": `Runloop/JS ${VERSION} - CLI`,
|
|
30
|
+
},
|
|
29
31
|
});
|
|
30
32
|
}
|
package/dist/utils/config.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import Conf from "conf";
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
import { existsSync, statSync, mkdirSync, writeFileSync
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
|
-
import { dirname } from "path";
|
|
4
|
+
import { existsSync, statSync, mkdirSync, writeFileSync } from "fs";
|
|
7
5
|
const config = new Conf({
|
|
8
6
|
projectName: "runloop-cli",
|
|
9
7
|
});
|
|
@@ -33,25 +31,6 @@ export function sshUrl() {
|
|
|
33
31
|
export function getCacheDir() {
|
|
34
32
|
return join(homedir(), ".cache", "rl-cli");
|
|
35
33
|
}
|
|
36
|
-
export 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
|
-
}
|
|
55
34
|
export function shouldCheckForUpdates() {
|
|
56
35
|
const cacheDir = getCacheDir();
|
|
57
36
|
const cacheFile = join(cacheDir, "last_update_check");
|
|
@@ -59,104 +38,36 @@ export function shouldCheckForUpdates() {
|
|
|
59
38
|
return true;
|
|
60
39
|
}
|
|
61
40
|
const stats = statSync(cacheFile);
|
|
62
|
-
const
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
export function hasCachedUpdateInfo() {
|
|
66
|
-
const cacheDir = getCacheDir();
|
|
67
|
-
const cacheFile = join(cacheDir, "last_update_check");
|
|
68
|
-
return existsSync(cacheFile);
|
|
41
|
+
const daysSinceUpdate = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
|
|
42
|
+
return daysSinceUpdate >= 1;
|
|
69
43
|
}
|
|
70
|
-
export function updateCheckCache(
|
|
44
|
+
export function updateCheckCache() {
|
|
71
45
|
const cacheDir = getCacheDir();
|
|
72
46
|
const cacheFile = join(cacheDir, "last_update_check");
|
|
73
47
|
// Create cache directory if it doesn't exist
|
|
74
48
|
if (!existsSync(cacheDir)) {
|
|
75
49
|
mkdirSync(cacheDir, { recursive: true });
|
|
76
50
|
}
|
|
77
|
-
//
|
|
78
|
-
writeFileSync(cacheFile,
|
|
51
|
+
// Touch the cache file
|
|
52
|
+
writeFileSync(cacheFile, "");
|
|
79
53
|
}
|
|
80
|
-
export function
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
return readFileSync(cacheFile, 'utf-8').trim();
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
return null;
|
|
54
|
+
export function getThemePreference() {
|
|
55
|
+
// Check environment variable first, then fall back to stored config
|
|
56
|
+
const envTheme = process.env.RUNLOOP_THEME?.toLowerCase();
|
|
57
|
+
if (envTheme === "light" || envTheme === "dark" || envTheme === "auto") {
|
|
58
|
+
return envTheme;
|
|
91
59
|
}
|
|
60
|
+
return config.get("theme") || "auto";
|
|
92
61
|
}
|
|
93
|
-
export
|
|
94
|
-
|
|
95
|
-
// Always show cached result if available and not forcing
|
|
96
|
-
if (!force && hasCachedUpdateInfo() && !shouldCheckForUpdates()) {
|
|
97
|
-
const cachedLatestVersion = getCachedLatestVersion();
|
|
98
|
-
if (cachedLatestVersion && cachedLatestVersion !== currentVersion) {
|
|
99
|
-
// Check if current version is older than cached latest
|
|
100
|
-
const isUpdateAvailable = compareVersions(cachedLatestVersion, currentVersion) > 0;
|
|
101
|
-
if (isUpdateAvailable) {
|
|
102
|
-
console.error(`\nš Update available: ${currentVersion} ā ${cachedLatestVersion}\n` +
|
|
103
|
-
` Run: npm install -g @runloop/rl-cli@latest\n\n`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
// Only fetch from npm if cache is expired or forcing
|
|
109
|
-
if (!force && !shouldCheckForUpdates()) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
const response = await fetch("https://registry.npmjs.org/@runloop/rl-cli/latest");
|
|
114
|
-
if (!response.ok) {
|
|
115
|
-
if (force) {
|
|
116
|
-
console.error("ā Failed to check for updates\n");
|
|
117
|
-
}
|
|
118
|
-
return; // Silently fail if we can't check
|
|
119
|
-
}
|
|
120
|
-
const data = await response.json();
|
|
121
|
-
const latestVersion = data.version;
|
|
122
|
-
if (force) {
|
|
123
|
-
console.error(`Current version: ${currentVersion}\n`);
|
|
124
|
-
console.error(`Latest version: ${latestVersion}\n`);
|
|
125
|
-
}
|
|
126
|
-
if (latestVersion && latestVersion !== currentVersion) {
|
|
127
|
-
// Check if current version is older than latest
|
|
128
|
-
const isUpdateAvailable = compareVersions(latestVersion, currentVersion) > 0;
|
|
129
|
-
if (isUpdateAvailable) {
|
|
130
|
-
console.error(`\nš Update available: ${currentVersion} ā ${latestVersion}\n` +
|
|
131
|
-
` Run: npm install -g @runloop/rl-cli@latest\n\n`);
|
|
132
|
-
}
|
|
133
|
-
else if (force) {
|
|
134
|
-
console.error("ā
You're running the latest version!\n");
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
else if (force) {
|
|
138
|
-
console.error("ā
You're running the latest version!\n");
|
|
139
|
-
}
|
|
140
|
-
// Update the cache with the latest version
|
|
141
|
-
updateCheckCache(latestVersion);
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
if (force) {
|
|
145
|
-
console.error(`ā Error checking for updates: ${error}\n`);
|
|
146
|
-
}
|
|
147
|
-
// Silently fail - don't interrupt the user's workflow
|
|
148
|
-
}
|
|
62
|
+
export function setThemePreference(theme) {
|
|
63
|
+
config.set("theme", theme);
|
|
149
64
|
}
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (v1part < v2part)
|
|
159
|
-
return -1;
|
|
160
|
-
}
|
|
161
|
-
return 0;
|
|
65
|
+
export function getDetectedTheme() {
|
|
66
|
+
return config.get("detectedTheme") || null;
|
|
67
|
+
}
|
|
68
|
+
export function setDetectedTheme(theme) {
|
|
69
|
+
config.set("detectedTheme", theme);
|
|
70
|
+
}
|
|
71
|
+
export function clearDetectedTheme() {
|
|
72
|
+
config.delete("detectedTheme");
|
|
162
73
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import { enterAlternateScreenBuffer, exitAlternateScreenBuffer, } from "./screen.js";
|
|
1
2
|
/**
|
|
2
3
|
* Wrapper for interactive commands that need alternate screen buffer management
|
|
3
4
|
*/
|
|
4
5
|
export async function runInteractiveCommand(command) {
|
|
5
6
|
// Enter alternate screen buffer
|
|
6
|
-
|
|
7
|
+
enterAlternateScreenBuffer();
|
|
7
8
|
try {
|
|
8
9
|
await command();
|
|
9
10
|
}
|
|
10
11
|
finally {
|
|
11
12
|
// Exit alternate screen buffer
|
|
12
|
-
|
|
13
|
+
exitAlternateScreenBuffer();
|
|
13
14
|
}
|
|
14
15
|
}
|