@runloop/rl-cli 0.5.0 → 0.10.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 +73 -106
- package/dist/cli.js +4 -416
- package/dist/commands/devbox/list.js +36 -10
- package/dist/components/DevboxCreatePage.js +3 -3
- package/dist/components/DevboxDetailPage.js +62 -11
- package/dist/components/MetadataDisplay.js +12 -2
- package/dist/components/ResourceListView.js +24 -11
- package/dist/components/StateHistory.js +120 -0
- package/dist/components/StatusBadge.js +51 -20
- package/dist/screens/DevboxCreateScreen.js +4 -4
- package/dist/utils/commands.js +408 -0
- package/dist/utils/config.js +21 -0
- package/dist/utils/theme.js +1 -1
- package/package.json +21 -8
- package/dist/commands/auth.js +0 -29
- package/dist/commands/blueprint/preview.js +0 -45
- package/dist/commands/config.js +0 -118
- package/dist/commands/create.js +0 -42
- package/dist/commands/delete.js +0 -34
- package/dist/commands/exec.js +0 -35
- package/dist/commands/list.js +0 -59
- package/dist/commands/upload.js +0 -40
- package/dist/components/Table.example.js +0 -85
- package/dist/screens/LogsSessionScreen.js +0 -49
- package/dist/utils/CommandExecutor.js +0 -131
- package/dist/utils/memoryMonitor.js +0 -85
- package/dist/utils/process.js +0 -106
- package/dist/utils/versionCheck.js +0 -53
package/dist/commands/config.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { render, Box, Text, useInput, useApp } from "ink";
|
|
4
|
-
import figures from "figures";
|
|
5
|
-
import { setThemePreference, getThemePreference, clearDetectedTheme, } from "../utils/config.js";
|
|
6
|
-
import { Header } from "../components/Header.js";
|
|
7
|
-
import { SuccessMessage } from "../components/SuccessMessage.js";
|
|
8
|
-
import { colors, getCurrentTheme, setThemeMode } from "../utils/theme.js";
|
|
9
|
-
import { processUtils } from "../utils/processUtils.js";
|
|
10
|
-
const themeOptions = [
|
|
11
|
-
{
|
|
12
|
-
value: "auto",
|
|
13
|
-
label: "Auto-detect",
|
|
14
|
-
description: "Automatically detect terminal background color",
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
value: "dark",
|
|
18
|
-
label: "Dark mode",
|
|
19
|
-
description: "Light text on dark background",
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
value: "light",
|
|
23
|
-
label: "Light mode",
|
|
24
|
-
description: "Dark text on light background",
|
|
25
|
-
},
|
|
26
|
-
];
|
|
27
|
-
const InteractiveThemeSelector = ({ initialTheme, }) => {
|
|
28
|
-
const { exit } = useApp();
|
|
29
|
-
const [selectedIndex, setSelectedIndex] = React.useState(() => themeOptions.findIndex((opt) => opt.value === initialTheme));
|
|
30
|
-
const [saved, setSaved] = React.useState(false);
|
|
31
|
-
const [detectedTheme] = React.useState(getCurrentTheme());
|
|
32
|
-
// Update theme preview when selection changes
|
|
33
|
-
React.useEffect(() => {
|
|
34
|
-
const newTheme = themeOptions[selectedIndex].value;
|
|
35
|
-
let targetTheme;
|
|
36
|
-
if (newTheme === "auto") {
|
|
37
|
-
// For auto mode, show the detected theme
|
|
38
|
-
targetTheme = detectedTheme;
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
// For explicit light/dark, set directly without detection
|
|
42
|
-
targetTheme = newTheme;
|
|
43
|
-
}
|
|
44
|
-
// Apply theme change for preview
|
|
45
|
-
setThemeMode(targetTheme);
|
|
46
|
-
}, [selectedIndex, detectedTheme]);
|
|
47
|
-
useInput((input, key) => {
|
|
48
|
-
if (saved) {
|
|
49
|
-
exit();
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (key.upArrow && selectedIndex > 0) {
|
|
53
|
-
setSelectedIndex(selectedIndex - 1);
|
|
54
|
-
}
|
|
55
|
-
else if (key.downArrow && selectedIndex < themeOptions.length - 1) {
|
|
56
|
-
setSelectedIndex(selectedIndex + 1);
|
|
57
|
-
}
|
|
58
|
-
else if (key.return) {
|
|
59
|
-
// Save the selected theme to config
|
|
60
|
-
const selectedTheme = themeOptions[selectedIndex].value;
|
|
61
|
-
setThemePreference(selectedTheme);
|
|
62
|
-
// If setting to 'auto', clear cached detection for re-run
|
|
63
|
-
if (selectedTheme === "auto") {
|
|
64
|
-
clearDetectedTheme();
|
|
65
|
-
}
|
|
66
|
-
setSaved(true);
|
|
67
|
-
setTimeout(() => exit(), 1500);
|
|
68
|
-
}
|
|
69
|
-
else if (key.escape || input === "q") {
|
|
70
|
-
// Restore original theme without re-running detection
|
|
71
|
-
setThemePreference(initialTheme);
|
|
72
|
-
if (initialTheme === "auto") {
|
|
73
|
-
setThemeMode(detectedTheme);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
setThemeMode(initialTheme);
|
|
77
|
-
}
|
|
78
|
-
exit();
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
if (saved) {
|
|
82
|
-
return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Theme Configuration" }), _jsx(SuccessMessage, { message: `Theme set to: ${themeOptions[selectedIndex].label}`, details: "Theme applied immediately!" })] }));
|
|
83
|
-
}
|
|
84
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { title: "Theme Configuration - Interactive" }), _jsx(Box, { marginBottom: 1, flexDirection: "column", children: _jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, children: "Current preference: " }), _jsx(Text, { color: colors.primary, bold: true, children: themeOptions[selectedIndex].label }), themeOptions[selectedIndex].value === "auto" && (_jsxs(Text, { color: colors.textDim, children: [" (detected: ", detectedTheme, ")"] }))] }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.text, bold: true, children: "Select theme mode:" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: themeOptions.map((option, index) => {
|
|
85
|
-
const isSelected = index === selectedIndex;
|
|
86
|
-
return (_jsxs(Box, { marginY: 0, children: [_jsxs(Text, { color: isSelected ? colors.primary : colors.textDim, children: [isSelected ? figures.pointer : " ", " "] }), _jsx(Text, { color: isSelected ? colors.primary : colors.text, bold: isSelected, children: option.label }), _jsxs(Text, { color: colors.textDim, children: [" - ", option.description] })] }, option.value));
|
|
87
|
-
}) })] }), _jsxs(Box, { marginTop: 2, flexDirection: "column", children: [_jsxs(Text, { color: colors.text, bold: true, children: [figures.play, " Live Preview:"] }), _jsxs(Box, { marginTop: 1, marginLeft: 2, paddingX: 2, paddingY: 1, borderStyle: "round", borderColor: colors.primary, flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.tick, " Primary"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.secondary, bold: true, children: [figures.star, " Secondary"] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: colors.success, children: [figures.tick, " Success"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.warning, children: [figures.warning, " Warning"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: colors.error, children: [figures.cross, " Error"] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.text, children: "Normal text" }), _jsx(Text, { children: " " }), _jsx(Text, { color: colors.textDim, children: "Dim text" })] })] })] }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Text, { color: colors.textDim, dimColor: true, children: [figures.arrowUp, figures.arrowDown, " Navigate \u2022 [Enter] Save \u2022 [Esc] Cancel"] }) })] }));
|
|
88
|
-
};
|
|
89
|
-
const StaticConfigUI = ({ action, value }) => {
|
|
90
|
-
const [saved, setSaved] = React.useState(false);
|
|
91
|
-
React.useEffect(() => {
|
|
92
|
-
if (action === "set" && value) {
|
|
93
|
-
setThemePreference(value);
|
|
94
|
-
// If setting to 'auto', clear the cached detection so it re-runs on next start
|
|
95
|
-
if (value === "auto") {
|
|
96
|
-
clearDetectedTheme();
|
|
97
|
-
}
|
|
98
|
-
setSaved(true);
|
|
99
|
-
setTimeout(() => processUtils.exit(0), 1500);
|
|
100
|
-
}
|
|
101
|
-
else if (action === "get" || !action) {
|
|
102
|
-
setTimeout(() => processUtils.exit(0), 2000);
|
|
103
|
-
}
|
|
104
|
-
}, [action, value]);
|
|
105
|
-
const currentPreference = getThemePreference();
|
|
106
|
-
const activeTheme = getCurrentTheme();
|
|
107
|
-
if (saved) {
|
|
108
|
-
return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Theme Configuration" }), _jsx(SuccessMessage, { message: `Theme set to: ${value}`, details: "Restart the CLI for changes to take effect" })] }));
|
|
109
|
-
}
|
|
110
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { title: "Theme Configuration" }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, children: "Current preference: " }), _jsx(Text, { color: colors.primary, bold: true, children: currentPreference })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, children: "Active theme: " }), _jsx(Text, { color: colors.success, bold: true, children: activeTheme })] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.text, bold: true, children: "Available options:" }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: colors.textDim, children: ["\u2022 ", _jsx(Text, { color: colors.primary, children: "auto" }), " - Detect terminal background automatically"] }), _jsxs(Text, { color: colors.textDim, children: ["\u2022 ", _jsx(Text, { color: colors.primary, children: "light" }), " - Force light mode (dark text on light background)"] }), _jsxs(Text, { color: colors.textDim, children: ["\u2022 ", _jsx(Text, { color: colors.primary, children: "dark" }), " - Force dark mode (light text on dark background)"] })] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.textDim, dimColor: true, children: "Usage: rli config theme [auto|light|dark]" }), _jsx(Text, { color: colors.textDim, dimColor: true, children: "Environment variable: RUNLOOP_THEME" })] })] }));
|
|
111
|
-
};
|
|
112
|
-
export function showThemeConfig() {
|
|
113
|
-
const currentTheme = getThemePreference();
|
|
114
|
-
render(_jsx(InteractiveThemeSelector, { initialTheme: currentTheme }));
|
|
115
|
-
}
|
|
116
|
-
export function setThemeConfig(theme) {
|
|
117
|
-
render(_jsx(StaticConfigUI, { action: "set", value: theme }));
|
|
118
|
-
}
|
package/dist/commands/create.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { render, Box, Text } from 'ink';
|
|
4
|
-
import Gradient from 'ink-gradient';
|
|
5
|
-
import figures from 'figures';
|
|
6
|
-
import { getClient } from '../utils/client.js';
|
|
7
|
-
import { Header } from '../components/Header.js';
|
|
8
|
-
import { SpinnerComponent } from '../components/Spinner.js';
|
|
9
|
-
import { SuccessMessage } from '../components/SuccessMessage.js';
|
|
10
|
-
import { ErrorMessage } from '../components/ErrorMessage.js';
|
|
11
|
-
const CreateDevboxUI = ({ name, template }) => {
|
|
12
|
-
const [loading, setLoading] = React.useState(true);
|
|
13
|
-
const [result, setResult] = React.useState(null);
|
|
14
|
-
const [error, setError] = React.useState(null);
|
|
15
|
-
const [progress, setProgress] = React.useState('Initializing...');
|
|
16
|
-
React.useEffect(() => {
|
|
17
|
-
const create = async () => {
|
|
18
|
-
try {
|
|
19
|
-
const client = getClient();
|
|
20
|
-
setProgress('Requesting new devbox...');
|
|
21
|
-
const devbox = await client.devboxes.create({
|
|
22
|
-
name: name || `devbox-${Date.now()}`,
|
|
23
|
-
...(template && { template }),
|
|
24
|
-
});
|
|
25
|
-
setProgress('Devbox created! Waiting for provisioning...');
|
|
26
|
-
setResult(devbox);
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
setError(err);
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
setLoading(false);
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
create();
|
|
36
|
-
}, []);
|
|
37
|
-
return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Create Devbox", subtitle: "Setting up your new development environment" }), loading && (_jsxs(_Fragment, { children: [_jsx(SpinnerComponent, { message: progress }), _jsxs(Box, { borderStyle: "round", borderColor: "blue", paddingX: 3, paddingY: 1, marginY: 1, flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "cyan", bold: true, children: [figures.info, " Configuration"] }) }), _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [figures.pointer, " Name: "] }), _jsx(Text, { color: "white", children: name || '(auto-generated)' })] }), template && (_jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [figures.pointer, " Template: "] }), _jsx(Text, { color: "white", children: template })] }))] })] })] })), result && (_jsxs(_Fragment, { children: [_jsx(SuccessMessage, { message: "Devbox created successfully!", details: `ID: ${result.id}\nName: ${result.name || '(unnamed)'}\nStatus: ${result.status}` }), _jsxs(Box, { borderStyle: "double", borderColor: "green", paddingX: 3, paddingY: 1, marginY: 1, flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Gradient, { name: "summer", children: _jsxs(Text, { bold: true, children: [figures.star, " Next Steps"] }) }) }), _jsxs(Box, { flexDirection: "column", gap: 1, marginLeft: 2, children: [_jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [figures.tick, " Execute commands: "] }), _jsxs(Text, { color: "cyan", children: ["rln exec ", result.id.slice(0, 8), "... <command>"] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [figures.tick, " Upload files: "] }), _jsxs(Text, { color: "cyan", children: ["rln upload ", result.id.slice(0, 8), "... <file>"] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [figures.tick, " View all: "] }), _jsx(Text, { color: "cyan", children: "rln list" })] })] })] })] })), error && _jsx(ErrorMessage, { message: "Failed to create devbox", error: error })] }));
|
|
38
|
-
};
|
|
39
|
-
export async function createDevbox(options) {
|
|
40
|
-
const { waitUntilExit } = render(_jsx(CreateDevboxUI, { name: options.name, template: options.template }));
|
|
41
|
-
await waitUntilExit();
|
|
42
|
-
}
|
package/dist/commands/delete.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { render } from 'ink';
|
|
4
|
-
import { getClient } from '../utils/client.js';
|
|
5
|
-
import { Header } from '../components/Header.js';
|
|
6
|
-
import { SpinnerComponent } from '../components/Spinner.js';
|
|
7
|
-
import { SuccessMessage } from '../components/SuccessMessage.js';
|
|
8
|
-
import { ErrorMessage } from '../components/ErrorMessage.js';
|
|
9
|
-
const DeleteDevboxUI = ({ id }) => {
|
|
10
|
-
const [loading, setLoading] = React.useState(true);
|
|
11
|
-
const [success, setSuccess] = React.useState(false);
|
|
12
|
-
const [error, setError] = React.useState(null);
|
|
13
|
-
React.useEffect(() => {
|
|
14
|
-
const deleteDevbox = async () => {
|
|
15
|
-
try {
|
|
16
|
-
const client = getClient();
|
|
17
|
-
await client.devboxes.shutdown(id);
|
|
18
|
-
setSuccess(true);
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
setError(err);
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
setLoading(false);
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
deleteDevbox();
|
|
28
|
-
}, []);
|
|
29
|
-
return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Delete Devbox", subtitle: `Deleting devbox: ${id}` }), loading && _jsx(SpinnerComponent, { message: "Deleting devbox..." }), success && (_jsx(SuccessMessage, { message: "Devbox deleted successfully!", details: `ID: ${id}` })), error && _jsx(ErrorMessage, { message: "Failed to delete devbox", error: error })] }));
|
|
30
|
-
};
|
|
31
|
-
export async function deleteDevbox(id) {
|
|
32
|
-
const { waitUntilExit } = render(_jsx(DeleteDevboxUI, { id: id }));
|
|
33
|
-
await waitUntilExit();
|
|
34
|
-
}
|
package/dist/commands/exec.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { render, Box, Text } from 'ink';
|
|
4
|
-
import { getClient } from '../utils/client.js';
|
|
5
|
-
import { Header } from '../components/Header.js';
|
|
6
|
-
import { SpinnerComponent } from '../components/Spinner.js';
|
|
7
|
-
import { ErrorMessage } from '../components/ErrorMessage.js';
|
|
8
|
-
const ExecCommandUI = ({ id, command, }) => {
|
|
9
|
-
const [loading, setLoading] = React.useState(true);
|
|
10
|
-
const [output, setOutput] = React.useState('');
|
|
11
|
-
const [error, setError] = React.useState(null);
|
|
12
|
-
React.useEffect(() => {
|
|
13
|
-
const exec = async () => {
|
|
14
|
-
try {
|
|
15
|
-
const client = getClient();
|
|
16
|
-
const result = await client.devboxes.executeSync(id, {
|
|
17
|
-
command: command.join(' '),
|
|
18
|
-
});
|
|
19
|
-
setOutput(result.stdout || result.stderr || 'Command executed successfully');
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
setError(err);
|
|
23
|
-
}
|
|
24
|
-
finally {
|
|
25
|
-
setLoading(false);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
exec();
|
|
29
|
-
}, []);
|
|
30
|
-
return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Execute Command", subtitle: `Running in devbox: ${id}` }), loading && _jsx(SpinnerComponent, { message: "Executing command..." }), !loading && !error && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Box, { borderStyle: "round", borderColor: "green", padding: 1, children: _jsx(Text, { children: output }) }) })), error && _jsx(ErrorMessage, { message: "Failed to execute command", error: error })] }));
|
|
31
|
-
};
|
|
32
|
-
export async function execCommand(id, command) {
|
|
33
|
-
const { waitUntilExit } = render(_jsx(ExecCommandUI, { id: id, command: command }));
|
|
34
|
-
await waitUntilExit();
|
|
35
|
-
}
|
package/dist/commands/list.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { render, Box, Text, useInput } from 'ink';
|
|
4
|
-
import Gradient from 'ink-gradient';
|
|
5
|
-
import figures from 'figures';
|
|
6
|
-
import { getClient } from '../utils/client.js';
|
|
7
|
-
import { Header } from '../components/Header.js';
|
|
8
|
-
import { SpinnerComponent } from '../components/Spinner.js';
|
|
9
|
-
import { DevboxCard } from '../components/DevboxCard.js';
|
|
10
|
-
import { ErrorMessage } from '../components/ErrorMessage.js';
|
|
11
|
-
const PAGE_SIZE = 10;
|
|
12
|
-
const ListDevboxesUI = ({ status }) => {
|
|
13
|
-
const [loading, setLoading] = React.useState(true);
|
|
14
|
-
const [devboxes, setDevboxes] = React.useState([]);
|
|
15
|
-
const [error, setError] = React.useState(null);
|
|
16
|
-
const [currentPage, setCurrentPage] = React.useState(0);
|
|
17
|
-
React.useEffect(() => {
|
|
18
|
-
const list = async () => {
|
|
19
|
-
try {
|
|
20
|
-
const client = getClient();
|
|
21
|
-
const allDevboxes = [];
|
|
22
|
-
for await (const devbox of client.devboxes.list()) {
|
|
23
|
-
if (!status || devbox.status === status) {
|
|
24
|
-
allDevboxes.push(devbox);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
setDevboxes(allDevboxes);
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
setError(err);
|
|
31
|
-
}
|
|
32
|
-
finally {
|
|
33
|
-
setLoading(false);
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
list();
|
|
37
|
-
}, []);
|
|
38
|
-
useInput((input, key) => {
|
|
39
|
-
if (input === 'n' && currentPage < totalPages - 1) {
|
|
40
|
-
setCurrentPage(currentPage + 1);
|
|
41
|
-
}
|
|
42
|
-
else if (input === 'p' && currentPage > 0) {
|
|
43
|
-
setCurrentPage(currentPage - 1);
|
|
44
|
-
}
|
|
45
|
-
else if (input === 'q') {
|
|
46
|
-
process.exit(0);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
const getStatusCount = (status) => devboxes.filter((d) => d.status === status).length;
|
|
50
|
-
const totalPages = Math.ceil(devboxes.length / PAGE_SIZE);
|
|
51
|
-
const startIndex = currentPage * PAGE_SIZE;
|
|
52
|
-
const endIndex = Math.min(startIndex + PAGE_SIZE, devboxes.length);
|
|
53
|
-
const currentDevboxes = devboxes.slice(startIndex, endIndex);
|
|
54
|
-
return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Your Devboxes", subtitle: status ? `Filtering by status: ${status}` : 'Showing all devboxes' }), loading && _jsx(SpinnerComponent, { message: "Fetching your devboxes..." }), !loading && !error && devboxes.length === 0 && (_jsxs(Box, { borderStyle: "round", borderColor: "yellow", paddingX: 3, paddingY: 2, marginY: 1, flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "yellow", bold: true, children: [figures.info, " No devboxes found"] }) }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "gray", children: "Create your first devbox with: " }), _jsx(Text, { color: "cyan", bold: true, children: "rln create" })] })] })), !loading && !error && devboxes.length > 0 && (_jsxs(_Fragment, { children: [_jsxs(Box, { borderStyle: "round", borderColor: "magenta", paddingX: 3, paddingY: 1, marginY: 1, flexDirection: "column", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Box, { children: _jsx(Gradient, { name: "passion", children: _jsxs(Text, { bold: true, children: [figures.star, " Summary"] }) }) }), _jsx(Box, { children: _jsxs(Text, { color: "cyan", children: ["Page ", currentPage + 1, "/", totalPages, " (", startIndex + 1, "-", endIndex, " of", ' ', devboxes.length, ")"] }) })] }), _jsxs(Box, { marginTop: 1, gap: 3, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "green", bold: true, children: figures.tick }), _jsx(Text, { color: "gray", children: "Running:" }), _jsx(Text, { color: "green", bold: true, children: getStatusCount('running') })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "yellow", bold: true, children: figures.ellipsis }), _jsx(Text, { color: "gray", children: "Provisioning:" }), _jsx(Text, { color: "yellow", bold: true, children: getStatusCount('provisioning') + getStatusCount('initializing') })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "gray", bold: true, children: figures.circleDotted }), _jsx(Text, { color: "gray", children: "Stopped:" }), _jsx(Text, { color: "gray", bold: true, children: getStatusCount('stopped') + getStatusCount('suspended') })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: figures.hamburger }), _jsx(Text, { color: "gray", children: "Total:" }), _jsx(Text, { color: "cyan", bold: true, children: devboxes.length })] })] })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: currentDevboxes.map((devbox, index) => (_jsx(DevboxCard, { id: devbox.id, name: devbox.name, status: devbox.status, createdAt: devbox.created_at, index: startIndex + index }, devbox.id))) }), totalPages > 1 && (_jsx(Box, { borderStyle: "round", borderColor: "blue", paddingX: 3, paddingY: 1, marginTop: 1, flexDirection: "column", children: _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Box, { gap: 3, children: [currentPage > 0 && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", bold: true, children: "[p]" }), _jsx(Text, { color: "gray", children: " Previous" })] })), currentPage < totalPages - 1 && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", bold: true, children: "[n]" }), _jsx(Text, { color: "gray", children: " Next" })] })), _jsxs(Box, { children: [_jsx(Text, { color: "red", bold: true, children: "[q]" }), _jsx(Text, { color: "gray", children: " Quit" })] })] }), _jsx(Box, { children: _jsxs(Text, { color: "gray", dimColor: true, children: [figures.arrowRight, " Press a key to navigate"] }) })] }) }))] })), error && _jsx(ErrorMessage, { message: "Failed to list devboxes", error: error })] }));
|
|
55
|
-
};
|
|
56
|
-
export async function listDevboxes(options) {
|
|
57
|
-
const { waitUntilExit } = render(_jsx(ListDevboxesUI, { status: options.status }));
|
|
58
|
-
await waitUntilExit();
|
|
59
|
-
}
|
package/dist/commands/upload.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { render } from 'ink';
|
|
4
|
-
import { createReadStream } from 'fs';
|
|
5
|
-
import { getClient } from '../utils/client.js';
|
|
6
|
-
import { Header } from '../components/Header.js';
|
|
7
|
-
import { SpinnerComponent } from '../components/Spinner.js';
|
|
8
|
-
import { SuccessMessage } from '../components/SuccessMessage.js';
|
|
9
|
-
import { ErrorMessage } from '../components/ErrorMessage.js';
|
|
10
|
-
const UploadFileUI = ({ id, file, targetPath }) => {
|
|
11
|
-
const [loading, setLoading] = React.useState(true);
|
|
12
|
-
const [success, setSuccess] = React.useState(false);
|
|
13
|
-
const [error, setError] = React.useState(null);
|
|
14
|
-
React.useEffect(() => {
|
|
15
|
-
const upload = async () => {
|
|
16
|
-
try {
|
|
17
|
-
const client = getClient();
|
|
18
|
-
const fileStream = createReadStream(file);
|
|
19
|
-
const filename = file.split('/').pop() || 'uploaded-file';
|
|
20
|
-
await client.devboxes.uploadFile(id, {
|
|
21
|
-
path: targetPath || filename,
|
|
22
|
-
file: fileStream,
|
|
23
|
-
});
|
|
24
|
-
setSuccess(true);
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
setError(err);
|
|
28
|
-
}
|
|
29
|
-
finally {
|
|
30
|
-
setLoading(false);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
upload();
|
|
34
|
-
}, []);
|
|
35
|
-
return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Upload File", subtitle: `Uploading to devbox: ${id}` }), loading && _jsx(SpinnerComponent, { message: "Uploading file..." }), success && (_jsx(SuccessMessage, { message: "File uploaded successfully!", details: `File: ${file}${targetPath ? `\nTarget: ${targetPath}` : ''}` })), error && _jsx(ErrorMessage, { message: "Failed to upload file", error: error })] }));
|
|
36
|
-
};
|
|
37
|
-
export async function uploadFile(id, file, options) {
|
|
38
|
-
const { waitUntilExit } = render(_jsx(UploadFileUI, { id: id, file: file, targetPath: options.path }));
|
|
39
|
-
await waitUntilExit();
|
|
40
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { Table, createTextColumn, createComponentColumn } from "./Table.js";
|
|
4
|
-
import { StatusBadge } from "./StatusBadge.js";
|
|
5
|
-
import figures from "figures";
|
|
6
|
-
import { colors } from "../utils/theme.js";
|
|
7
|
-
function BlueprintsTable({ blueprints, selectedIndex, terminalWidth, }) {
|
|
8
|
-
// Responsive column widths
|
|
9
|
-
const showDescription = terminalWidth >= 120;
|
|
10
|
-
const showFullId = terminalWidth >= 80;
|
|
11
|
-
return (_jsx(Table, { data: blueprints, keyExtractor: (bp) => bp.id, selectedIndex: selectedIndex, columns: [
|
|
12
|
-
// Status badge column
|
|
13
|
-
createComponentColumn("status", "Status", (bp) => _jsx(StatusBadge, { status: bp.status, showText: false }), { width: 2 }),
|
|
14
|
-
// ID column (responsive)
|
|
15
|
-
createTextColumn("id", "ID", (bp) => (showFullId ? bp.id : bp.id.slice(0, 13)), {
|
|
16
|
-
width: showFullId ? 25 : 15,
|
|
17
|
-
color: colors.textDim,
|
|
18
|
-
dimColor: true,
|
|
19
|
-
bold: false,
|
|
20
|
-
}),
|
|
21
|
-
// Name column
|
|
22
|
-
createTextColumn("name", "Name", (bp) => bp.name || "(unnamed)", { width: 30 }),
|
|
23
|
-
// Description column (optional)
|
|
24
|
-
createTextColumn("description", "Description", (bp) => bp.description || "", {
|
|
25
|
-
width: 40,
|
|
26
|
-
color: colors.textDim,
|
|
27
|
-
dimColor: true,
|
|
28
|
-
bold: false,
|
|
29
|
-
visible: showDescription,
|
|
30
|
-
}),
|
|
31
|
-
// Created time column
|
|
32
|
-
createTextColumn("created", "Created", (bp) => new Date(bp.created_at).toLocaleDateString(), { width: 15, color: colors.textDim, dimColor: true, bold: false }),
|
|
33
|
-
], emptyState: _jsx(Box, { children: _jsxs(Text, { color: colors.warning, children: [figures.info, " No blueprints found"] }) }) }));
|
|
34
|
-
}
|
|
35
|
-
function SnapshotsTable({ snapshots, selectedIndex, terminalWidth, }) {
|
|
36
|
-
// Responsive column widths
|
|
37
|
-
const showSize = terminalWidth >= 100;
|
|
38
|
-
const showFullId = terminalWidth >= 80;
|
|
39
|
-
return (_jsx(Table, { data: snapshots, keyExtractor: (snap) => snap.id, selectedIndex: selectedIndex, columns: [
|
|
40
|
-
// Status badge column
|
|
41
|
-
createComponentColumn("status", "Status", (snap) => _jsx(StatusBadge, { status: snap.status, showText: false }), { width: 2 }),
|
|
42
|
-
// ID column (responsive)
|
|
43
|
-
createTextColumn("id", "ID", (snap) => (showFullId ? snap.id : snap.id.slice(0, 13)), {
|
|
44
|
-
width: showFullId ? 25 : 15,
|
|
45
|
-
color: colors.textDim,
|
|
46
|
-
dimColor: true,
|
|
47
|
-
bold: false,
|
|
48
|
-
}),
|
|
49
|
-
// Name column
|
|
50
|
-
createTextColumn("name", "Name", (snap) => snap.name || "(unnamed)", { width: 25 }),
|
|
51
|
-
// Devbox ID column
|
|
52
|
-
createTextColumn("devbox", "Devbox", (snap) => snap.devbox_id.slice(0, 13), {
|
|
53
|
-
width: 15,
|
|
54
|
-
color: colors.primary,
|
|
55
|
-
dimColor: true,
|
|
56
|
-
bold: false,
|
|
57
|
-
}),
|
|
58
|
-
// Size column (optional)
|
|
59
|
-
createTextColumn("size", "Size", (snap) => (snap.size_gb ? `${snap.size_gb.toFixed(1)}GB` : ""), {
|
|
60
|
-
width: 10,
|
|
61
|
-
color: colors.warning,
|
|
62
|
-
dimColor: true,
|
|
63
|
-
bold: false,
|
|
64
|
-
visible: showSize,
|
|
65
|
-
}),
|
|
66
|
-
// Created time column
|
|
67
|
-
createTextColumn("created", "Created", (snap) => new Date(snap.created_at).toLocaleDateString(), { width: 15, color: colors.textDim, dimColor: true, bold: false }),
|
|
68
|
-
], emptyState: _jsx(Box, { children: _jsxs(Text, { color: colors.warning, children: [figures.info, " No snapshots found"] }) }) }));
|
|
69
|
-
}
|
|
70
|
-
// ============================================================================
|
|
71
|
-
// EXAMPLE 3: Custom Column with Complex Rendering
|
|
72
|
-
// ============================================================================
|
|
73
|
-
function CustomComplexColumn() {
|
|
74
|
-
const data = [
|
|
75
|
-
{ id: "1", name: "Item 1", tags: ["tag1", "tag2"] },
|
|
76
|
-
{ id: "2", name: "Item 2", tags: ["tag3"] },
|
|
77
|
-
];
|
|
78
|
-
return (_jsx(Table, { data: data, keyExtractor: (item) => item.id, selectedIndex: 0, columns: [
|
|
79
|
-
createTextColumn("name", "Name", (item) => item.name, {
|
|
80
|
-
width: 20,
|
|
81
|
-
}),
|
|
82
|
-
// Custom component column with complex rendering
|
|
83
|
-
createComponentColumn("tags", "Tags", (item, index, isSelected) => (_jsx(Box, { width: 30, children: _jsx(Text, { color: isSelected ? colors.primary : colors.info, dimColor: true, children: item.tags.map((tag) => `[${tag}]`).join(" ") }) })), { width: 30 }),
|
|
84
|
-
] }));
|
|
85
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* LogsSessionScreen - Logs viewer using custom InteractiveSpawn
|
|
4
|
-
* Runs the CLI logs command as a subprocess within the Ink UI without exiting
|
|
5
|
-
*/
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { Box, Text } from "ink";
|
|
8
|
-
import { InteractiveSpawn } from "../components/InteractiveSpawn.js";
|
|
9
|
-
import { useNavigation, } from "../store/navigationStore.js";
|
|
10
|
-
import { Breadcrumb } from "../components/Breadcrumb.js";
|
|
11
|
-
import { colors } from "../utils/theme.js";
|
|
12
|
-
import figures from "figures";
|
|
13
|
-
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
14
|
-
import { fileURLToPath } from "url";
|
|
15
|
-
import { dirname, join } from "path";
|
|
16
|
-
export function LogsSessionScreen() {
|
|
17
|
-
const { params, navigate } = useNavigation();
|
|
18
|
-
// Handle Ctrl+C to exit (before logs command runs or on error)
|
|
19
|
-
useExitOnCtrlC();
|
|
20
|
-
// Extract params
|
|
21
|
-
const devboxId = params.devboxId;
|
|
22
|
-
const devboxName = params.devboxName || params.devboxId || "devbox";
|
|
23
|
-
const returnScreen = params.returnScreen || "devbox-list";
|
|
24
|
-
const returnParams = params.returnParams || {};
|
|
25
|
-
// Get the path to the CLI executable
|
|
26
|
-
const cliPath = React.useMemo(() => {
|
|
27
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
28
|
-
const __dirname = dirname(__filename);
|
|
29
|
-
// When compiled, this file is in dist/screens/, so go up one level to dist/cli.js
|
|
30
|
-
return join(__dirname, "../cli.js");
|
|
31
|
-
}, []);
|
|
32
|
-
// Validate required params
|
|
33
|
-
if (!devboxId) {
|
|
34
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Logs", active: true }] }), _jsx(Box, { flexDirection: "column", paddingX: 1, children: _jsxs(Text, { color: colors.error, children: [figures.cross, " Missing devbox ID. Returning..."] }) })] }));
|
|
35
|
-
}
|
|
36
|
-
// Build CLI command args
|
|
37
|
-
const cliArgs = React.useMemo(() => ["devbox", "logs", devboxId], [devboxId]);
|
|
38
|
-
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Logs", active: true }] }), _jsxs(Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.info, " Viewing logs for ", devboxName, "..."] }), _jsx(Text, { color: colors.textDim, dimColor: true, children: "Press Ctrl+C to exit" })] }), _jsx(InteractiveSpawn, { command: "node", args: [cliPath, ...cliArgs], onExit: (_code) => {
|
|
39
|
-
// Navigate back to previous screen when logs command exits
|
|
40
|
-
setTimeout(() => {
|
|
41
|
-
navigate(returnScreen, returnParams || {});
|
|
42
|
-
}, 100);
|
|
43
|
-
}, onError: (_error) => {
|
|
44
|
-
// On error, navigate back as well
|
|
45
|
-
setTimeout(() => {
|
|
46
|
-
navigate(returnScreen, returnParams || {});
|
|
47
|
-
}, 100);
|
|
48
|
-
} })] }));
|
|
49
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared class for executing commands with different output formats
|
|
3
|
-
* Reduces code duplication across all command files
|
|
4
|
-
*/
|
|
5
|
-
import { render } from "ink";
|
|
6
|
-
import { getClient } from "./client.js";
|
|
7
|
-
import { shouldUseNonInteractiveOutput, outputList, outputResult, } from "./output.js";
|
|
8
|
-
import YAML from "yaml";
|
|
9
|
-
export class CommandExecutor {
|
|
10
|
-
options;
|
|
11
|
-
constructor(options = {}) {
|
|
12
|
-
this.options = options;
|
|
13
|
-
// Set default output format to json if none specified
|
|
14
|
-
if (!this.options.output) {
|
|
15
|
-
this.options.output = "json";
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Execute a list command with automatic format handling
|
|
20
|
-
*/
|
|
21
|
-
async executeList(fetchData, renderUI, limit = 10) {
|
|
22
|
-
if (shouldUseNonInteractiveOutput(this.options)) {
|
|
23
|
-
try {
|
|
24
|
-
const items = await fetchData();
|
|
25
|
-
// Limit results for non-interactive mode
|
|
26
|
-
const limitedItems = items.slice(0, limit);
|
|
27
|
-
outputList(limitedItems, this.options);
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
this.handleError(err);
|
|
31
|
-
}
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
// Interactive mode
|
|
35
|
-
// Enter alternate screen buffer
|
|
36
|
-
process.stdout.write("\x1b[?1049h");
|
|
37
|
-
console.clear();
|
|
38
|
-
const { waitUntilExit } = render(renderUI());
|
|
39
|
-
await waitUntilExit();
|
|
40
|
-
// Exit alternate screen buffer
|
|
41
|
-
process.stdout.write("\x1b[?1049l");
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Execute a create/action command with automatic format handling
|
|
45
|
-
*/
|
|
46
|
-
async executeAction(performAction, renderUI) {
|
|
47
|
-
if (shouldUseNonInteractiveOutput(this.options)) {
|
|
48
|
-
try {
|
|
49
|
-
const result = await performAction();
|
|
50
|
-
outputResult(result, this.options);
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
this.handleError(err);
|
|
54
|
-
}
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
// Interactive mode
|
|
58
|
-
// Enter alternate screen buffer
|
|
59
|
-
process.stdout.write("\x1b[?1049h");
|
|
60
|
-
console.clear();
|
|
61
|
-
const { waitUntilExit } = render(renderUI());
|
|
62
|
-
await waitUntilExit();
|
|
63
|
-
// Exit alternate screen buffer
|
|
64
|
-
process.stdout.write("\x1b[?1049l");
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Execute a delete command with automatic format handling
|
|
68
|
-
*/
|
|
69
|
-
async executeDelete(performDelete, id, renderUI) {
|
|
70
|
-
if (shouldUseNonInteractiveOutput(this.options)) {
|
|
71
|
-
try {
|
|
72
|
-
await performDelete();
|
|
73
|
-
outputResult({ id, status: "deleted" }, this.options);
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
this.handleError(err);
|
|
77
|
-
}
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
// Interactive mode
|
|
81
|
-
// Enter alternate screen buffer
|
|
82
|
-
process.stdout.write("\x1b[?1049h");
|
|
83
|
-
const { waitUntilExit } = render(renderUI());
|
|
84
|
-
await waitUntilExit();
|
|
85
|
-
// Exit alternate screen buffer
|
|
86
|
-
process.stdout.write("\x1b[?1049l");
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Fetch items from an async iterator with optional filtering and limits
|
|
90
|
-
*/
|
|
91
|
-
async fetchFromIterator(iterator, options = {}) {
|
|
92
|
-
const { filter, limit = 100 } = options;
|
|
93
|
-
const items = [];
|
|
94
|
-
let count = 0;
|
|
95
|
-
for await (const item of iterator) {
|
|
96
|
-
if (filter && !filter(item)) {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
items.push(item);
|
|
100
|
-
count++;
|
|
101
|
-
if (count >= limit) {
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return items;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Handle errors consistently across all commands
|
|
109
|
-
*/
|
|
110
|
-
handleError(error) {
|
|
111
|
-
if (this.options.output === "yaml") {
|
|
112
|
-
console.error(YAML.stringify({ error: error.message }));
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
console.error(JSON.stringify({ error: error.message }, null, 2));
|
|
116
|
-
}
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Get the client instance
|
|
121
|
-
*/
|
|
122
|
-
getClient() {
|
|
123
|
-
return getClient();
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Factory function to create a CommandExecutor
|
|
128
|
-
*/
|
|
129
|
-
export function createExecutor(options = {}) {
|
|
130
|
-
return new CommandExecutor(options);
|
|
131
|
-
}
|