@nanocollective/nanocoder
Version:
A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter
160 lines • 8.68 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { Box, Text, useInput } from 'ink';
import SelectInput from 'ink-select-input';
import React, { useEffect, useState } from 'react';
import { defaultTheme, getThemeColors } from '../config/themes.js';
import { TIMEOUT_VSCODE_EXTENSION_SKIP_MS } from '../constants.js';
import { getExtensionStatus, installExtension, isVSCodeCliAvailable, } from '../vscode/extension-installer.js';
var InstallOption;
(function (InstallOption) {
InstallOption["Yes"] = "yes";
InstallOption["No"] = "no";
InstallOption["Select"] = "select";
})(InstallOption || (InstallOption = {}));
/**
* Ink component that prompts the user to install the VS Code extension
* when running with --vscode flag and the extension isn't installed
*/
export function VSCodeExtensionPrompt({ onComplete, onSkip, }) {
const [state, setState] = useState('checking');
const [statuses, setStatuses] = useState([]);
const [message, setMessage] = useState('');
const [selectedClis, setSelectedClis] = useState([]);
const [isSelecting, setIsSelecting] = useState(false);
const colors = getThemeColors(defaultTheme);
// Check status on mount
useEffect(() => {
async function check() {
const available = await isVSCodeCliAvailable();
if (!available) {
setState('no-cli');
return;
}
const currentStatuses = await getExtensionStatus();
setStatuses(currentStatuses);
const missing = currentStatuses.filter(s => !s.extensionInstalled);
if (missing.length === 0) {
onComplete();
}
else {
setSelectedClis(missing.map(s => s.cli));
setState('prompt');
}
}
if (state === 'checking') {
void check();
}
}, [state, onComplete]);
const handleInstall = React.useCallback(async (clis) => {
setState('installing');
const result = await installExtension(clis);
setMessage(result.message);
if (result.success) {
setState('success');
}
else {
setState('error');
// Auto-continue after showing error
setTimeout(onSkip, TIMEOUT_VSCODE_EXTENSION_SKIP_MS);
}
}, [onSkip]);
// Handle Enter key press in success state
useInput((_input, key) => {
if (state === 'success' && key.return) {
onComplete();
}
}, { isActive: state === 'success' });
// Handle no-cli case - auto-skip after showing message
useEffect(() => {
if (state === 'no-cli') {
const timer = setTimeout(onSkip, TIMEOUT_VSCODE_EXTENSION_SKIP_MS);
return () => clearTimeout(timer);
}
}, [state, onSkip]);
const availableMissing = statuses.filter(s => !s.extensionInstalled);
const items = [
{
label: availableMissing.length > 1
? `Yes, install to all (${availableMissing.map(s => s.cli).join(', ')})`
: `Yes, install to ${availableMissing[0]?.cli}`,
value: InstallOption.Yes,
},
...(availableMissing.length > 1
? [
{
label: 'Choose editors...',
value: InstallOption.Select,
},
]
: []),
{
label: 'No, skip for now',
value: InstallOption.No,
},
];
const handleSelect = (item) => {
if (item.value === InstallOption.Yes) {
void handleInstall(availableMissing.map(s => s.cli));
}
else if (item.value === InstallOption.Select) {
setIsSelecting(true);
}
else {
onSkip();
}
};
const handleCliToggle = (cli) => {
setSelectedClis(prev => prev.includes(cli) ? prev.filter(c => c !== cli) : [...prev, cli]);
};
if (state === 'checking') {
return (_jsx(Box, { flexDirection: "column", paddingY: 1, children: _jsx(Text, { color: colors.primary, children: "Checking VS Code extension..." }) }));
}
if (state === 'no-cli') {
return (_jsxs(Box, { flexDirection: "column", paddingY: 1, children: [_jsx(Text, { color: colors.warning, children: "No supported VS Code flavor (Code, Cursor, VSCodium, Windsurf, Trae) found in PATH." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.text, children: "To enable VS Code integration:" }) }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: colors.secondary, children: "1. Open VS Code or your preferred editor" }), _jsx(Text, { color: colors.secondary, children: "2. Press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Windows/Linux)" }), _jsx(Text, { color: colors.secondary, children: "3. Search for \"Shell Command: Install 'code' command in PATH\"" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.secondary, children: "Continuing without VS Code integration..." }) })] }));
}
if (state === 'prompt') {
if (isSelecting) {
const selectionItems = availableMissing.map(s => ({
label: `${selectedClis.includes(s.cli) ? '[x]' : '[ ]'} ${s.cli}`,
value: s.cli,
}));
return (_jsxs(Box, { flexDirection: "column", paddingY: 1, children: [_jsx(Text, { color: colors.primary, bold: true, children: "Select Editors" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: [
...selectionItems,
{ label: '--- Confirm ---', value: 'confirm' },
{ label: '--- Back ---', value: 'back' },
], onSelect: item => {
if (item.value === 'confirm') {
if (selectedClis.length > 0) {
void handleInstall(selectedClis);
}
}
else if (item.value === 'back') {
setIsSelecting(false);
}
else {
handleCliToggle(item.value);
}
} }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Space/Enter to toggle, select Confirm to proceed" }) })] }));
}
return (_jsxs(Box, { flexDirection: "column", paddingY: 1, children: [_jsx(Text, { color: colors.primary, bold: true, children: "VS Code Extension" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.text, children: "The VS Code extension enables live diff previews when Nanocoder modifies files." }) }), statuses.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.secondary, children: "Detected editors:" }), statuses.map(s => (_jsxs(Text, { color: s.extensionInstalled ? 'gray' : 'white', children: [s.extensionInstalled ? ' ✓' : ' !', " ", s.cli, ' ', s.extensionInstalled ? '(Installed)' : '(Missing)'] }, s.cli)))] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.text, children: "Install the extension now?" }) }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: items, onSelect: handleSelect }) })] }));
}
if (state === 'installing') {
return (_jsx(Box, { flexDirection: "column", paddingY: 1, children: _jsx(Text, { color: colors.primary, children: "Installing VS Code extension..." }) }));
}
if (state === 'success') {
return (_jsxs(Box, { flexDirection: "column", paddingY: 1, children: [_jsxs(Text, { color: colors.success, children: ["\u2713 ", message] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.secondary, children: "Press Enter to continue..." }) })] }));
}
if (state === 'error') {
return (_jsxs(Box, { flexDirection: "column", paddingY: 1, children: [_jsxs(Text, { color: colors.error, children: ["\u2717 ", message] }), _jsx(Text, { color: colors.secondary, children: "Continuing without VS Code integration..." })] }));
}
return null;
}
/**
* Check if we should show the extension install prompt
* Returns true if --vscode flag is present
* The component itself will check if it's already installed
*/
export function shouldPromptExtensionInstall() {
return process.argv.includes('--vscode');
}
//# sourceMappingURL=vscode-extension-prompt.js.map