/** * SettingsDefaultsManager * * Single source of truth for all default configuration values. * Provides methods to get defaults with optional environment variable overrides. */ import { readFileSync, writeFileSync, existsSync } from 'fs'; import { join } from 'path'; import { homedir } from 'os'; import { DEFAULT_OBSERVATION_TYPES_STRING, DEFAULT_OBSERVATION_CONCEPTS_STRING } from '../constants/observation-metadata.js'; import { logger } from '../utils/logger.js'; export interface SettingsDefaults { CLAUDE_MEM_MODEL: string; CLAUDE_MEM_CONTEXT_OBSERVATIONS: string; CLAUDE_MEM_WORKER_PORT: string; CLAUDE_MEM_WORKER_HOST: string; CLAUDE_MEM_SKIP_TOOLS: string; // AI Provider Configuration CLAUDE_MEM_PROVIDER: string; // 'claude' | 'gemini' CLAUDE_MEM_GEMINI_API_KEY: string; CLAUDE_MEM_GEMINI_MODEL: string; // 'gemini-2.0-flash-exp' | 'gemini-1.5-flash' | 'gemini-1.5-pro' // System Configuration CLAUDE_MEM_DATA_DIR: string; CLAUDE_MEM_LOG_LEVEL: string; CLAUDE_MEM_PYTHON_VERSION: string; CLAUDE_CODE_PATH: string; CLAUDE_MEM_MODE: string; // Token Economics CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: string; CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: string; CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: string; CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT: string; // Observation Filtering CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES: string; CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS: string; // Display Configuration CLAUDE_MEM_CONTEXT_FULL_COUNT: string; CLAUDE_MEM_CONTEXT_FULL_FIELD: string; CLAUDE_MEM_CONTEXT_SESSION_COUNT: string; // Feature Toggles CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY: string; CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE: string; } export class SettingsDefaultsManager { /** * Default values for all settings */ private static readonly DEFAULTS: SettingsDefaults = { CLAUDE_MEM_MODEL: 'claude-sonnet-4-5', CLAUDE_MEM_CONTEXT_OBSERVATIONS: '50', CLAUDE_MEM_WORKER_PORT: '37777', CLAUDE_MEM_WORKER_HOST: '127.0.0.1', CLAUDE_MEM_SKIP_TOOLS: 'ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion', // AI Provider Configuration CLAUDE_MEM_PROVIDER: 'claude', // Default to Claude CLAUDE_MEM_GEMINI_API_KEY: '', // Empty by default, can be set via UI or env CLAUDE_MEM_GEMINI_MODEL: 'gemini-2.0-flash-exp', // Default Gemini model // System Configuration CLAUDE_MEM_DATA_DIR: join(homedir(), '.claude-mem'), CLAUDE_MEM_LOG_LEVEL: 'INFO', CLAUDE_MEM_PYTHON_VERSION: '3.13', CLAUDE_CODE_PATH: '', // Empty means auto-detect via 'which claude' CLAUDE_MEM_MODE: 'code', // Default mode profile // Token Economics CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: 'true', CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: 'true', CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: 'true', CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT: 'true', // Observation Filtering CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES: DEFAULT_OBSERVATION_TYPES_STRING, CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS: DEFAULT_OBSERVATION_CONCEPTS_STRING, // Display Configuration CLAUDE_MEM_CONTEXT_FULL_COUNT: '5', CLAUDE_MEM_CONTEXT_FULL_FIELD: 'narrative', CLAUDE_MEM_CONTEXT_SESSION_COUNT: '10', // Feature Toggles CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY: 'true', CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE: 'false', }; /** * Get all defaults as an object */ static getAllDefaults(): SettingsDefaults { return { ...this.DEFAULTS }; } /** * Get a default value from defaults (no environment variable override) */ static get(key: keyof SettingsDefaults): string { return this.DEFAULTS[key]; } /** * Get an integer default value */ static getInt(key: keyof SettingsDefaults): number { const value = this.get(key); return parseInt(value, 10); } /** * Get a boolean default value */ static getBool(key: keyof SettingsDefaults): boolean { const value = this.get(key); return value === 'true'; } /** * Load settings from file with fallback to defaults * Returns merged settings with defaults as fallback * Handles all errors (missing file, corrupted JSON, permissions) by returning defaults */ static loadFromFile(settingsPath: string): SettingsDefaults { try { if (!existsSync(settingsPath)) { return this.getAllDefaults(); } const settingsData = readFileSync(settingsPath, 'utf-8'); const settings = JSON.parse(settingsData); // MIGRATION: Handle old nested schema { env: {...} } let flatSettings = settings; if (settings.env && typeof settings.env === 'object') { // Migrate from nested to flat schema flatSettings = settings.env; // Auto-migrate the file to flat schema try { writeFileSync(settingsPath, JSON.stringify(flatSettings, null, 2), 'utf-8'); logger.info('SETTINGS', 'Migrated settings file from nested to flat schema', { settingsPath }); } catch (error) { logger.warn('SETTINGS', 'Failed to auto-migrate settings file', { settingsPath }, error); // Continue with in-memory migration even if write fails } } // Merge file settings with defaults (flat schema) const result: SettingsDefaults = { ...this.DEFAULTS }; for (const key of Object.keys(this.DEFAULTS) as Array) { if (flatSettings[key] !== undefined) { result[key] = flatSettings[key]; } } return result; } catch (error) { logger.warn('SETTINGS', 'Failed to load settings, using defaults', { settingsPath }, error); return this.getAllDefaults(); } } }