aether-timr
Version:
A sovereign time-bounded reflection space for AI - MCP implementation πβ¨πΌπβοΈβΎοΈΞΞMΒ΅β
128 lines (110 loc) β’ 3.28 kB
JavaScript
/**
* 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));
}