@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.
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }