UNPKG

@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

214 lines 8.37 kB
import { config as loadEnv } from 'dotenv'; import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import { homedir } from 'os'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; import { loadAllMCPConfigs, loadAllProviderConfigs, } from '../config/mcp-config-loader.js'; import { getConfigPath } from '../config/paths.js'; import { loadPreferences } from '../config/preferences.js'; import { defaultTheme, getThemeColors } from '../config/themes.js'; import { logError } from '../utils/message-queue.js'; // Load .env file from working directory (shell environment takes precedence) // Suppress dotenv console output by temporarily redirecting stdout const envPath = join(process.cwd(), '.env'); if (existsSync(envPath)) { const originalWrite = process.stdout.write.bind(process.stdout); process.stdout.write = () => true; try { loadEnv({ path: envPath }); } finally { process.stdout.write = originalWrite; } } // Hold a map of what config files are where export const confDirMap = {}; // Find the closest config file for the requested configuration file export function getClosestConfigFile(fileName) { try { const configDir = getConfigPath(); // If NANOCODER_CONFIG_DIR is explicitly set, skip cwd and home checks // and use only the config directory (important for tests and explicit overrides) const isExplicitConfigDir = Boolean(process.env.NANOCODER_CONFIG_DIR); if (!isExplicitConfigDir) { // First, lets check for a working directory config const cwdPath = join(process.cwd(), fileName); // nosemgrep if (existsSync(cwdPath)) { // nosemgrep confDirMap[fileName] = cwdPath; // nosemgrep return cwdPath; // nosemgrep } // Next lets check the $HOME for a hidden file. This should only be for // legacy support const homePath = join(homedir(), `.${fileName}`); // nosemgrep if (existsSync(homePath)) { // nosemgrep confDirMap[fileName] = homePath; // nosemgrep return homePath; // nosemgrep } } // Last, lets look for an user level config. // If the file doesn't exist, create it const configPath = join(configDir, fileName); // nosemgrep if (!existsSync(configPath)) { // nosemgrep createDefaultConfFile(configDir, fileName); } confDirMap[fileName] = configPath; // nosemgrep return configPath; // nosemgrep } catch (error) { logError(`Failed to load ${fileName}: ${String(error)}`); } // The code should never hit this, but it makes the TS compiler happy. return fileName; } function createDefaultConfFile(filePath, fileName) { try { // If we cant find any, lets assume this is the first user run, create the // correct file and direct the user to configure them correctly, const configFilePath = join(filePath, fileName); // nosemgrep if (!existsSync(configFilePath)) { // nosemgrep // Maybe add a better sample config? const sampleConfig = {}; mkdirSync(filePath, { recursive: true }); writeFileSync(configFilePath, // nosemgrep JSON.stringify(sampleConfig, null, 2), 'utf-8'); } } catch (error) { logError(`Failed to write ${filePath}: ${String(error)}`); } } // Try to load auto-compact config from a specific path // Returns the config if found and valid, null otherwise function tryLoadAutoCompactFromPath(configPath, defaults) { if (!existsSync(configPath)) { return null; } try { const rawData = readFileSync(configPath, 'utf-8'); const config = JSON.parse(rawData); const autoCompact = config.nanocoder?.autoCompact; if (autoCompact && typeof autoCompact === 'object') { return { enabled: autoCompact.enabled !== undefined ? Boolean(autoCompact.enabled) : defaults.enabled, threshold: validateThreshold(autoCompact.threshold ?? defaults.threshold), mode: validateMode(autoCompact.mode ?? defaults.mode), notifyUser: autoCompact.notifyUser !== undefined ? Boolean(autoCompact.notifyUser) : defaults.notifyUser, }; } } catch (error) { logError(`Failed to load auto-compact config from ${configPath}: ${String(error)}`); } return null; } // Load auto-compact configuration and Returns default config if not specified function loadAutoCompactConfig() { const defaults = { enabled: true, threshold: 60, mode: 'conservative', notifyUser: true, }; // Try to load from project-level config first const projectConfigPath = join(process.cwd(), 'agents.config.json'); const projectConfig = tryLoadAutoCompactFromPath(projectConfigPath, defaults); if (projectConfig) { return projectConfig; } // Try global config const configDir = getConfigPath(); const globalConfigPath = join(configDir, 'agents.config.json'); const globalConfig = tryLoadAutoCompactFromPath(globalConfigPath, defaults); if (globalConfig) { return globalConfig; } // Fallback to home directory const homePath = join(homedir(), '.agents.config.json'); const homeConfig = tryLoadAutoCompactFromPath(homePath, defaults); if (homeConfig) { return homeConfig; } return defaults; } // Validate and clamp threshold to valid range (50-95) function validateThreshold(threshold) { const num = typeof threshold === 'number' ? threshold : 60; return Math.max(50, Math.min(95, Math.round(num))); } // Validate compression mode function validateMode(mode) { if (mode === 'default' || mode === 'aggressive' || mode === 'conservative') { return mode; } return 'conservative'; } // Function to load app configuration from agents.config.json if it exists function loadAppConfig() { // Load providers from the new hierarchical configuration system const providers = loadAllProviderConfigs(); // Load MCP servers from the new hierarchical configuration system const mcpServersWithSource = loadAllMCPConfigs(); const mcpServers = mcpServersWithSource.map(item => item.server); // Load auto-compact configuration const autoCompact = loadAutoCompactConfig(); return { providers, mcpServers, autoCompact, }; } let _appConfig = null; /** * Lazy-loaded app config to avoid circular dependencies during module initialization * @public */ export function getAppConfig() { if (!_appConfig) { _appConfig = loadAppConfig(); } return _appConfig; } // Legacy export for backward compatibility - use a getter export const appConfig = new Proxy({}, { get(_target, prop) { return getAppConfig()[prop]; }, }); // Function to reload the app configuration (useful after config file changes) export function reloadAppConfig() { _appConfig = loadAppConfig(); } // Function to clear the cached app configuration (useful for testing) export function clearAppConfig() { _appConfig = null; } let cachedColors = null; export function getColors() { if (!cachedColors) { const preferences = loadPreferences(); const selectedTheme = preferences.selectedTheme || defaultTheme; cachedColors = getThemeColors(selectedTheme); } return cachedColors; } // Legacy export for backwards compatibility - use a getter to avoid circular dependency export const colors = new Proxy({}, { get(_target, prop) { return getColors()[prop]; }, }); // Get the package root directory (where this module is installed) const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Go up from dist/config to package root, then to source/app/prompts/main-prompt.md // This works because source/app/prompts/main-prompt.md is included in the package.json files array export const promptPath = join(__dirname, '../../source/app/prompts/main-prompt.md'); //# sourceMappingURL=index.js.map