UNPKG

llm-checker

Version:

Intelligent CLI tool with AI-powered model selection that analyzes your hardware and recommends optimal LLM models for your system

356 lines (309 loc) 11.4 kB
const fs = require('fs'); const path = require('path'); const os = require('os'); class ConfigManager { constructor() { this.configDir = path.join(os.homedir(), '.llm-checker'); this.configFile = path.join(this.configDir, 'config.json'); this.defaultConfig = this.getDefaultConfig(); this.config = null; this.ensureConfigDirectory(); } getDefaultConfig() { return { version: "2.0", ollama: { baseURL: process.env.OLLAMA_BASE_URL || "http://localhost:11434", timeout: 30000, enabled: true, autoDetect: true, retryAttempts: 3 }, analysis: { includeCloudModels: false, defaultUseCase: "general", performanceTesting: false, detailedHardwareInfo: false, cacheResults: true, cacheExpiry: 300000 }, display: { maxModelsPerTable: 10, showEmojis: !process.env.NO_COLOR, colorOutput: !process.env.NO_COLOR, compactMode: false, showScores: true, showInstallCommands: true }, quantization: { preferredLevel: "auto", availableLevels: ["Q2_K", "Q3_K_M", "Q4_0", "Q4_K_M", "Q5_0", "Q5_K_M", "Q6_K", "Q8_0"], hardwareBased: { ultra_low: ["Q2_K", "Q3_K_M"], low: ["Q4_0", "Q4_K_M"], medium: ["Q4_K_M", "Q5_0"], high: ["Q5_K_M", "Q6_K"], ultra_high: ["Q8_0", "Q6_K"] } }, filters: { excludeModels: [], includeOnlyFrameworks: [], excludeCategories: [], minCompatibilityScore: 60, maxModelSize: null, yearFilter: null }, hardware: { overrides: { ram: process.env.LLM_CHECKER_RAM_GB ? parseInt(process.env.LLM_CHECKER_RAM_GB) : null, vram: process.env.LLM_CHECKER_VRAM_GB ? parseInt(process.env.LLM_CHECKER_VRAM_GB) : null, cpuCores: process.env.LLM_CHECKER_CPU_CORES ? parseInt(process.env.LLM_CHECKER_CPU_CORES) : null, architecture: process.env.LLM_CHECKER_ARCHITECTURE || null }, ignoreIntegratedGPU: process.env.LLM_CHECKER_NO_GPU === 'true', preferDedicatedGPU: true, cacheHardwareInfo: true }, recommendations: { maxRecommendations: 5, includeUpgradeSuggestions: true, showPerformanceEstimates: true, groupByCategory: true, prioritizeOllamaSupport: true }, logging: { level: process.env.LLM_CHECKER_LOG_LEVEL || "info", file: null, enableDebug: process.env.DEBUG === '1', enableVerbose: false, saveReports: false, reportsDirectory: path.join(os.homedir(), '.llm-checker', 'reports') }, updates: { checkForUpdates: true, autoUpdateDatabase: true, updateChannel: "stable", notifyNewModels: true }, customModels: [] }; } ensureConfigDirectory() { if (!fs.existsSync(this.configDir)) { try { fs.mkdirSync(this.configDir, { recursive: true }); } catch (error) { console.warn(`Warning: Could not create config directory: ${error.message}`); } } } loadConfig() { if (this.config) { return this.config; } let userConfig = {}; // Try to load user config file if (fs.existsSync(this.configFile)) { try { const configContent = fs.readFileSync(this.configFile, 'utf8'); userConfig = JSON.parse(configContent); } catch (error) { console.warn(`Warning: Could not parse config file: ${error.message}`); console.warn('Using default configuration'); } } // Merge with defaults this.config = this.mergeConfigs(this.defaultConfig, userConfig); // Apply environment variable overrides this.applyEnvironmentOverrides(); return this.config; } saveConfig(config = null) { const configToSave = config || this.config || this.defaultConfig; try { const configContent = JSON.stringify(configToSave, null, 2); fs.writeFileSync(this.configFile, configContent, 'utf8'); this.config = configToSave; return true; } catch (error) { console.error(`Error saving config: ${error.message}`); return false; } } mergeConfigs(defaultConfig, userConfig) { const merged = { ...defaultConfig }; for (const [key, value] of Object.entries(userConfig)) { if (typeof value === 'object' && value !== null && !Array.isArray(value)) { merged[key] = this.mergeConfigs(defaultConfig[key] || {}, value); } else { merged[key] = value; } } return merged; } applyEnvironmentOverrides() { if (!this.config) return; // Hardware overrides if (process.env.LLM_CHECKER_RAM_GB) { this.config.hardware.overrides.ram = parseInt(process.env.LLM_CHECKER_RAM_GB); } if (process.env.LLM_CHECKER_VRAM_GB) { this.config.hardware.overrides.vram = parseInt(process.env.LLM_CHECKER_VRAM_GB); } if (process.env.LLM_CHECKER_CPU_CORES) { this.config.hardware.overrides.cpuCores = parseInt(process.env.LLM_CHECKER_CPU_CORES); } // Ollama overrides if (process.env.OLLAMA_BASE_URL) { this.config.ollama.baseURL = process.env.OLLAMA_BASE_URL; } // Display overrides if (process.env.NO_COLOR) { this.config.display.colorOutput = false; this.config.display.showEmojis = false; } // Debug overrides if (process.env.DEBUG === '1') { this.config.logging.enableDebug = true; this.config.logging.level = 'debug'; } // GPU overrides if (process.env.LLM_CHECKER_NO_GPU === 'true') { this.config.hardware.ignoreIntegratedGPU = true; } } get(keyPath, defaultValue = null) { const config = this.loadConfig(); const keys = keyPath.split('.'); let value = config; for (const key of keys) { if (value && typeof value === 'object' && key in value) { value = value[key]; } else { return defaultValue; } } return value; } set(keyPath, value) { const config = this.loadConfig(); const keys = keyPath.split('.'); const lastKey = keys.pop(); let current = config; for (const key of keys) { if (!current[key] || typeof current[key] !== 'object') { current[key] = {}; } current = current[key]; } current[lastKey] = value; this.saveConfig(config); return true; } reset() { this.config = null; if (fs.existsSync(this.configFile)) { try { fs.unlinkSync(this.configFile); } catch (error) { console.error(`Error removing config file: ${error.message}`); return false; } } return true; } exportConfig() { const config = this.loadConfig(); return JSON.stringify(config, null, 2); } importConfig(configString) { try { const config = JSON.parse(configString); return this.saveConfig(config); } catch (error) { console.error(`Error importing config: ${error.message}`); return false; } } validateConfig(config = null) { const configToValidate = config || this.loadConfig(); const errors = []; // Validate required sections const requiredSections = ['ollama', 'analysis', 'display', 'hardware']; for (const section of requiredSections) { if (!configToValidate[section]) { errors.push(`Missing required section: ${section}`); } } // Validate Ollama URL if (configToValidate.ollama?.baseURL) { try { new URL(configToValidate.ollama.baseURL); } catch (error) { errors.push(`Invalid Ollama URL: ${configToValidate.ollama.baseURL}`); } } // Validate numeric values if (configToValidate.ollama?.timeout && configToValidate.ollama.timeout < 1000) { errors.push('Ollama timeout should be at least 1000ms'); } if (configToValidate.display?.maxModelsPerTable && configToValidate.display.maxModelsPerTable < 1) { errors.push('maxModelsPerTable should be at least 1'); } return { valid: errors.length === 0, errors }; } getConfigPath() { return this.configFile; } getConfigDirectory() { return this.configDir; } createBackup() { if (!fs.existsSync(this.configFile)) { return null; } const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupFile = path.join(this.configDir, `config.backup.${timestamp}.json`); try { fs.copyFileSync(this.configFile, backupFile); return backupFile; } catch (error) { console.error(`Error creating backup: ${error.message}`); return null; } } listBackups() { try { const files = fs.readdirSync(this.configDir); return files .filter(file => file.startsWith('config.backup.') && file.endsWith('.json')) .map(file => ({ name: file, path: path.join(this.configDir, file), created: fs.statSync(path.join(this.configDir, file)).mtime })) .sort((a, b) => b.created - a.created); } catch (error) { console.error(`Error listing backups: ${error.message}`); return []; } } restoreBackup(backupName) { const backupPath = path.join(this.configDir, backupName); if (!fs.existsSync(backupPath)) { throw new Error(`Backup file not found: ${backupName}`); } try { fs.copyFileSync(backupPath, this.configFile); this.config = null; // Force reload return true; } catch (error) { throw new Error(`Error restoring backup: ${error.message}`); } } } module.exports = ConfigManager;