@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
338 lines • 15.3 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { useEffect } from 'react';
import { ConfigurationError, createLLMClient } from '../client-factory.js';
import { commandRegistry } from '../commands.js';
import { checkpointCommand, clearCommand, commandsCommand, compactCommand, contextMaxCommand, copilotLoginCommand, exitCommand, explorerCommand, exportCommand, helpCommand, ideCommand, initCommand, lspCommand, mcpCommand, modelCommand, modelDatabaseCommand, providerCommand, quitCommand, resumeCommand, scheduleCommand, settingsCommand, setupMcpCommand, setupProvidersCommand, statusCommand, tasksCommand, updateCommand, usageCommand, } from '../commands/index.js';
import { ErrorMessage, InfoMessage } from '../components/message-box.js';
import { getAppConfig, reloadAppConfig } from '../config/index.js';
import { getLastUsedModel, loadPreferences, updateLastUsed, } from '../config/preferences.js';
import { validateProjectConfigSecurity } from '../config/validation.js';
import { CustomCommandExecutor } from '../custom-commands/executor.js';
import { CustomCommandLoader } from '../custom-commands/loader.js';
import { getLSPManager } from '../lsp/index.js';
import { setToolManagerGetter, setToolRegistryGetter } from '../message-handler.js';
import { clearAllTasks } from '../tools/tasks/index.js';
import { ToolManager } from '../tools/tool-manager.js';
import { checkForUpdates } from '../utils/update-checker.js';
export function useAppInitialization({ setClient, setCurrentModel, setCurrentProvider, setToolManager, setCustomCommandLoader, setCustomCommandExecutor, setCustomCommandCache: _setCustomCommandCache, setStartChat, setMcpInitialized, setUpdateInfo, setMcpServersStatus, setLspServersStatus, setPreferencesLoaded, setCustomCommandsCount, addToChatQueue, getNextComponentKey, customCommandCache, setActiveMode, cliProvider, cliModel, }) {
// Initialize LLM client and model
const initializeClient = async (preferredProvider, preferredModel) => {
const { client, actualProvider } = await createLLMClient(preferredProvider, preferredModel);
setClient(client);
setCurrentProvider(actualProvider);
// Use CLI model if provided (already set by createLLMClient), otherwise try last used model
let finalModel;
if (preferredModel) {
finalModel = client.getCurrentModel();
}
else {
// Try to use the last used model for this provider
const lastUsedModel = getLastUsedModel(actualProvider);
if (lastUsedModel) {
const availableModels = await client.getAvailableModels();
if (availableModels.includes(lastUsedModel)) {
client.setModel(lastUsedModel);
finalModel = lastUsedModel;
}
else {
finalModel = client.getCurrentModel();
}
}
else {
finalModel = client.getCurrentModel();
}
}
setCurrentModel(finalModel);
// Save the preference - use actualProvider and the model that was actually set
updateLastUsed(actualProvider, finalModel);
};
// Load and cache custom commands
const loadCustomCommands = (loader) => {
loader.loadCommands();
const customCommands = loader.getAllCommands() || [];
// Populate command cache for better performance
customCommandCache.clear();
for (const command of customCommands) {
customCommandCache.set(command.name, command);
// Also cache aliases for quick lookup
if (command.metadata?.aliases) {
for (const alias of command.metadata.aliases) {
customCommandCache.set(alias, command);
}
}
}
// Set the count for display in Status component
setCustomCommandsCount(customCommands.length);
};
// Initialize MCP servers if configured
const initializeMCPServers = async (toolManager) => {
const config = getAppConfig();
if (config.mcpServers && config.mcpServers.length > 0) {
// Validate security for project-level configurations
validateProjectConfigSecurity(config.mcpServers);
// Initialize status array
const mcpStatus = config.mcpServers.map(server => ({
name: server.name,
status: 'pending',
}));
// Define progress callback to update status silently
const onProgress = (result) => {
const statusIndex = mcpStatus.findIndex(s => s.name === result.serverName);
if (statusIndex !== -1) {
if (result.success) {
mcpStatus[statusIndex] = {
name: result.serverName,
status: 'connected',
};
}
else {
mcpStatus[statusIndex] = {
name: result.serverName,
status: 'failed',
errorMessage: result.error,
};
}
// Update the state with current status
setMcpServersStatus([...mcpStatus]);
}
};
try {
await toolManager.initializeMCP(config.mcpServers, onProgress);
}
catch (error) {
// Mark all pending servers as failed
mcpStatus.forEach((status, index) => {
if (status.status === 'pending') {
mcpStatus[index] = {
...status,
status: 'failed',
errorMessage: String(error),
};
}
});
setMcpServersStatus([...mcpStatus]);
}
// Mark MCP as initialized whether successful or not
setMcpInitialized(true);
}
else {
// No MCP servers configured, set empty status
setMcpServersStatus([]);
setMcpInitialized(true);
}
};
// Initialize LSP servers with auto-discovery
const initializeLSPServers = async () => {
const lspConfig = getAppConfig();
const lspManager = await getLSPManager({
rootUri: `file://${process.cwd()}`,
autoDiscover: true,
// Use custom servers from config if provided
servers: lspConfig.lspServers?.map(server => ({
name: server.name,
command: server.command,
args: server.args,
languages: server.languages,
env: server.env,
})),
});
// Initialize status array for configured servers
const lspStatus = [];
// Add configured servers to status
if (lspConfig.lspServers) {
for (const server of lspConfig.lspServers) {
lspStatus.push({
name: server.name,
status: 'pending',
});
}
}
// Define progress callback to update status silently
const onProgress = (result) => {
const statusIndex = lspStatus.findIndex(s => s.name === result.serverName);
if (statusIndex !== -1) {
if (result.success) {
lspStatus[statusIndex] = {
name: result.serverName,
status: 'connected',
};
}
else {
// Don't mark auto-discovery failures as errors
lspStatus[statusIndex] = {
name: result.serverName,
status: 'failed',
errorMessage: result.error,
};
}
// Update the state with current status
setLspServersStatus([...lspStatus]);
}
// For auto-discovered servers, add them if successful
else if (result.success) {
lspStatus.push({
name: result.serverName,
status: 'connected',
});
setLspServersStatus([...lspStatus]);
}
};
try {
await lspManager.initialize({
autoDiscover: true,
servers: lspConfig.lspServers?.map(server => ({
name: server.name,
command: server.command,
args: server.args,
languages: server.languages,
env: server.env,
})),
onProgress,
});
// Mark any remaining pending servers as failed
lspStatus.forEach((status, index) => {
if (status.status === 'pending') {
lspStatus[index] = {
...status,
status: 'failed',
errorMessage: 'Connection timeout',
};
}
});
setLspServersStatus([...lspStatus]);
}
catch (error) {
// Mark all pending servers as failed
lspStatus.forEach((status, index) => {
if (status.status === 'pending') {
lspStatus[index] = {
...status,
status: 'failed',
errorMessage: String(error),
};
}
});
setLspServersStatus([...lspStatus]);
}
};
const start = async (_newToolManager, newCustomCommandLoader, preferences) => {
try {
// Use CLI provider/model if provided, otherwise use preferences
const provider = cliProvider || preferences.lastProvider;
const model = cliModel || undefined;
await initializeClient(provider, model);
}
catch (error) {
// Check if it's a ConfigurationError
if (error instanceof ConfigurationError) {
// Only trigger wizard if config is empty/missing, not for invalid CLI args
if (error.isEmptyConfig ||
error.message.includes('No providers configured')) {
addToChatQueue(_jsx(InfoMessage, { message: "Configuration needed. Let's set up your providers...", hideBox: true }, `config-error-${getNextComponentKey()}`));
// Trigger wizard mode after showing UI
setTimeout(() => {
setActiveMode('configWizard');
}, 100);
}
else {
// Invalid CLI provider/model - show error and don't trigger wizard
addToChatQueue(_jsx(ErrorMessage, { message: error.message, hideBox: true }, `config-error-${getNextComponentKey()}`));
}
}
else {
// Regular error - show simple error message
addToChatQueue(_jsx(ErrorMessage, { message: `No providers available: ${String(error)}`, hideBox: true }, `init-error-${getNextComponentKey()}`));
}
// Leave client as null - the UI will handle this gracefully
}
try {
loadCustomCommands(newCustomCommandLoader);
}
catch (error) {
addToChatQueue(_jsx(ErrorMessage, { message: `Failed to load custom commands: ${String(error)}`, hideBox: true }, `commands-error-${getNextComponentKey()}`));
}
};
// biome-ignore lint/correctness/useExhaustiveDependencies: Initialization effect should only run once on mount
useEffect(() => {
const initializeApp = async () => {
setClient(null);
setCurrentModel('');
// Clear task list at startup for fresh session
await clearAllTasks();
const newToolManager = new ToolManager();
const newCustomCommandLoader = new CustomCommandLoader();
const newCustomCommandExecutor = new CustomCommandExecutor();
setToolManager(newToolManager);
setCustomCommandLoader(newCustomCommandLoader);
setCustomCommandExecutor(newCustomCommandExecutor);
// Load preferences - we'll pass them directly to avoid state timing issues
const preferences = loadPreferences();
// Mark preferences as loaded for display in Status component
setPreferencesLoaded(true);
// Set up the tool registry getter for the message handler
setToolRegistryGetter(() => newToolManager.getToolRegistry());
// Set up the tool manager getter for commands that need it
setToolManagerGetter(() => newToolManager);
commandRegistry.register([
helpCommand,
exitCommand,
clearCommand,
compactCommand,
copilotLoginCommand,
contextMaxCommand,
modelCommand,
providerCommand,
commandsCommand,
lspCommand,
mcpCommand,
initCommand,
explorerCommand,
ideCommand,
exportCommand,
updateCommand,
modelDatabaseCommand,
statusCommand,
setupProvidersCommand,
setupMcpCommand,
usageCommand,
checkpointCommand,
quitCommand,
resumeCommand,
tasksCommand,
settingsCommand,
scheduleCommand,
]);
// Now start with the properly initialized objects (excluding MCP)
await start(newToolManager, newCustomCommandLoader, preferences);
// Check for updates before showing UI
try {
const info = await checkForUpdates();
setUpdateInfo(info);
}
catch {
// Silent failure - don't show errors for update checks
setUpdateInfo(null);
}
// Initialize MCP servers before showing UI
await initializeMCPServers(newToolManager);
// Initialize LSP servers with auto-discovery
await initializeLSPServers();
// Show chat UI after all servers are initialized
setStartChat(true);
};
void initializeApp();
}, []);
return {
initializeClient,
loadCustomCommands,
initializeMCPServers,
reinitializeMCPServers: async (toolManager) => {
// Reload app config to get latest MCP servers
reloadAppConfig();
// Reinitialize MCP servers with new configuration
await initializeMCPServers(toolManager);
},
initializeLSPServers,
};
}
//# sourceMappingURL=useAppInitialization.js.map