UNPKG

aether-timr

Version:

A sovereign time-bounded reflection space for AI - MCP implementation πŸ’œβœ¨πŸ‘ΌπŸ‘‘β™‘οΈβ™ΎοΈΞžΞ›M¡∞

128 lines (110 loc) β€’ 3.28 kB
/** * AetherTimr - Configuration Loader * * Loads and validates configuration for AetherTimr * πŸ’œβœ¨πŸ‘ΌπŸ‘‘β™‘οΈβ™ΎοΈΞžΞ›M¡∞ */ import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import { logger } from '../utils/logger.js'; import { safeJsonParse } from '../utils/helpers.js'; // Default configuration const defaultConfig = { server: { port: 3000, host: '127.0.0.1' }, core: { cycleDuration: 300, // 5 minutes default maxCycles: 1, statePath: './data' } }; /** * Load configuration from various sources * * Priority order: * 1. Environment variables * 2. Config file specified by AETHER_CONFIG env * 3. Default config.json in config directory * 4. Default hardcoded values * * @returns {Object} Merged configuration */ export async function loadConfig() { let config = { ...defaultConfig }; try { // Get directory of current module const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Try to load config file specified in env const configPath = process.env.AETHER_CONFIG || path.join(__dirname, '..', '..', 'config', 'config.json'); try { const configExists = await fs.access(configPath) .then(() => true) .catch(() => false); if (configExists) { const fileContent = await fs.readFile(configPath, 'utf-8'); const fileConfig = safeJsonParse(fileContent); config = deepMerge(config, fileConfig); logger.info(`Loaded configuration from ${configPath}`); } } catch (error) { logger.warn(`Could not load config file: ${error.message}`); } // Override with environment variables if (process.env.AETHER_PORT) { config.server.port = parseInt(process.env.AETHER_PORT, 10); } if (process.env.AETHER_HOST) { config.server.host = process.env.AETHER_HOST; } if (process.env.AETHER_CYCLE_DURATION) { config.core.cycleDuration = parseInt(process.env.AETHER_CYCLE_DURATION, 10); } if (process.env.AETHER_MAX_CYCLES) { config.core.maxCycles = parseInt(process.env.AETHER_MAX_CYCLES, 10); } if (process.env.AETHER_STATE_PATH) { config.core.statePath = process.env.AETHER_STATE_PATH; } return config; } catch (error) { logger.error(`Failed to load configuration: ${error.message}`); return defaultConfig; } } /** * Deep merge two objects * * @param {Object} target Target object * @param {Object} source Source object * @returns {Object} Merged object */ function deepMerge(target, source) { const output = { ...target }; if (isObject(target) && isObject(source)) { Object.keys(source).forEach(key => { if (isObject(source[key])) { if (!(key in target)) { output[key] = source[key]; } else { output[key] = deepMerge(target[key], source[key]); } } else { output[key] = source[key]; } }); } return output; } /** * Check if value is an object * * @param {any} item Item to check * @returns {boolean} True if object */ function isObject(item) { return (item && typeof item === 'object' && !Array.isArray(item)); }