UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

192 lines (173 loc) 5.05 kB
import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import YAML from 'yaml'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const DEFAULT_CONFIG = { daemon: { heartbeat_interval_seconds: 30, max_parallel_actions: 3, action_timeout_minutes: 120, log: { max_size_mb: 50, max_files: 5, level: 'info' } }, watch: { enabled: true, paths: [ { path: '.aiwg/', events: ['create', 'modify', 'delete'], ignore: ['*.tmp', 'working/**', 'daemon/**'] }, { path: 'src/', events: ['modify'], extensions: ['.ts', '.js', '.mjs'] }, { path: 'test/', events: ['modify'], extensions: ['.test.ts', '.test.js'] } ], debounce_ms: 2000 }, webhook: { enabled: false, port: 9471, host: '127.0.0.1', secret_env: 'AIWG_WEBHOOK_SECRET', endpoints: [ { path: '/ci', method: 'POST' }, { path: '/health', method: 'GET' } ] }, schedule: { enabled: true, jobs: [ { id: 'health-check', cron: '0 */6 * * *', action: 'doctor' }, { id: 'artifact-audit', cron: '0 9 * * 1', action: 'validate-metadata' } ] }, supervisor: { max_concurrent: 4, max_queue_depth: 20, restart_intensity: { max_restarts: 3, window_seconds: 300 }, circuit_breaker: { failure_threshold: 5, cooldown_ms: 120000 }, daily_budget_usd: 0, behaviors: ['ops-toolset'], }, interface: { web: { enabled: false, port: 7474, host: '127.0.0.1', }, }, rules: [] }; export class Config { constructor(configPath = '.aiwg/daemon.json') { this.configPath = configPath; this.config = null; } load() { // Try YAML first, then JSON, then defaults const yamlPath = this.configPath.replace(/\.json$/, '.yaml'); const ymlPath = this.configPath.replace(/\.json$/, '.yml'); try { if (fs.existsSync(yamlPath)) { const content = fs.readFileSync(yamlPath, 'utf8'); this.config = YAML.parse(content); this.configPath = yamlPath; } else if (fs.existsSync(ymlPath)) { const content = fs.readFileSync(ymlPath, 'utf8'); this.config = YAML.parse(content); this.configPath = ymlPath; } else if (fs.existsSync(this.configPath)) { const content = fs.readFileSync(this.configPath, 'utf8'); this.config = JSON.parse(content); } else { this.config = DEFAULT_CONFIG; this.save(); } } catch (error) { console.error(`Failed to load config from ${this.configPath}:`, error.message); this.config = DEFAULT_CONFIG; } return this.config; } save() { try { const dir = path.dirname(this.configPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2)); } catch (error) { console.error(`Failed to save config to ${this.configPath}:`, error.message); } } validate() { const errors = []; if (!this.config.daemon) { errors.push('Missing daemon configuration section'); } if (this.config.watch?.enabled && !Array.isArray(this.config.watch.paths)) { errors.push('watch.paths must be an array'); } if (this.config.webhook?.enabled) { if (!this.config.webhook.port || this.config.webhook.port < 1 || this.config.webhook.port > 65535) { errors.push('webhook.port must be between 1 and 65535'); } } if (this.config.schedule?.enabled && !Array.isArray(this.config.schedule.jobs)) { errors.push('schedule.jobs must be an array'); } // Validate modes section if (this.config.modes?.autonomous?.enabled) { if (!Array.isArray(this.config.modes.autonomous.allowed_actions)) { errors.push('modes.autonomous.allowed_actions must be an array'); } if (typeof this.config.modes.autonomous.budget_cap_usd !== 'number' || this.config.modes.autonomous.budget_cap_usd < 0) { errors.push('modes.autonomous.budget_cap_usd must be a non-negative number'); } } // Validate messaging rooms if (this.config.messaging) { for (const [platform, pConfig] of Object.entries(this.config.messaging)) { if (pConfig?.rooms && !Array.isArray(pConfig.rooms)) { errors.push(`messaging.${platform}.rooms must be an array`); } } } // Validate docker section if (this.config.docker?.enabled) { if (!this.config.docker.image) { errors.push('docker.image is required when docker is enabled'); } } return errors; } get(key) { const keys = key.split('.'); let value = this.config; for (const k of keys) { if (value === undefined || value === null) return undefined; value = value[k]; } return value; } } export default Config;