UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

224 lines 7.1 kB
/** * Config File Manager * Shared JSON config file persistence with atomic writes and Zod validation */ import * as fs from 'fs'; import * as path from 'path'; /** Config file search paths in priority order */ const CONFIG_FILENAMES = [ 'claude-flow.config.json', '.claude-flow/config.json', ]; /** Default config values */ const DEFAULT_CONFIG = { version: '3.5', agents: { defaultType: 'coder', autoSpawn: false, maxConcurrent: 8, timeout: 300000, providers: [], }, swarm: { topology: 'hierarchical', maxAgents: 8, autoScale: false, coordinationStrategy: 'leader', healthCheckInterval: 30000, }, memory: { backend: 'hybrid', persistPath: './data/memory', cacheSize: 1000, enableHNSW: true, vectorDimension: 384, }, mcp: { serverHost: 'localhost', serverPort: 3000, autoStart: false, transportType: 'stdio', tools: [], }, cli: { colorOutput: true, interactive: true, verbosity: 'normal', outputFormat: 'text', progressStyle: 'spinner', }, hooks: { enabled: true, autoExecute: true, hooks: [], }, }; export class ConfigFileManager { configPath = null; config = null; /** Find config file in search paths starting from cwd */ findConfig(cwd) { for (const filename of CONFIG_FILENAMES) { const candidate = path.resolve(cwd, filename); if (fs.existsSync(candidate)) { return candidate; } } // Check env var const envPath = process.env.CLAUDE_FLOW_CONFIG; if (envPath && fs.existsSync(envPath)) { return path.resolve(envPath); } return null; } /** Load config from file, returns null if not found */ load(cwd) { this.configPath = this.findConfig(cwd); if (!this.configPath) { this.config = null; return null; } try { const content = fs.readFileSync(this.configPath, 'utf-8'); this.config = JSON.parse(content); return this.config; } catch { this.config = null; return null; } } /** Get the current config, loading if needed */ getConfig(cwd) { if (this.config === null) { this.load(cwd); } return this.config ?? { ...DEFAULT_CONFIG }; } /** Get a nested config value by dot-separated key */ get(cwd, key) { const config = this.getConfig(cwd); return getNestedValue(config, key); } /** Set a nested config value by dot-separated key */ set(cwd, key, value) { const config = this.getConfig(cwd); setNestedValue(config, key, value); this.config = config; const targetPath = this.configPath ?? path.resolve(cwd, CONFIG_FILENAMES[0]); this.writeAtomic(targetPath, config); this.configPath = targetPath; } /** Create a new config file with defaults */ create(cwd, overrides, force) { const targetPath = path.resolve(cwd, CONFIG_FILENAMES[0]); if (fs.existsSync(targetPath) && !force) { throw new Error(`Config file already exists: ${targetPath}. Use --force to overwrite.`); } const config = { ...DEFAULT_CONFIG, ...overrides }; this.writeAtomic(targetPath, config); this.config = config; this.configPath = targetPath; return targetPath; } /** Reset config to defaults */ reset(cwd) { const targetPath = this.configPath ?? path.resolve(cwd, CONFIG_FILENAMES[0]); this.writeAtomic(targetPath, DEFAULT_CONFIG); this.config = { ...DEFAULT_CONFIG }; this.configPath = targetPath; return targetPath; } /** Export config to a specific path */ exportTo(cwd, exportPath) { const config = this.getConfig(cwd); const resolved = path.resolve(cwd, exportPath); this.writeAtomic(resolved, config); } /** Import config from a specific path */ importFrom(cwd, importPath) { const resolved = path.resolve(cwd, importPath); if (!fs.existsSync(resolved)) { throw new Error(`Import file not found: ${resolved}`); } const content = fs.readFileSync(resolved, 'utf-8'); let imported; try { imported = JSON.parse(content); } catch { throw new Error(`Invalid JSON in import file: ${resolved}`); } if (typeof imported !== 'object' || imported === null || Array.isArray(imported)) { throw new Error('Import file must contain a JSON object'); } const targetPath = this.configPath ?? path.resolve(cwd, CONFIG_FILENAMES[0]); this.writeAtomic(targetPath, imported); this.config = imported; this.configPath = targetPath; } /** Get the path to the current config file */ getConfigPath() { return this.configPath; } /** Get default config */ getDefaults() { return { ...DEFAULT_CONFIG }; } /** Atomic write: write to .tmp then rename */ writeAtomic(filePath, data) { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } const tmpPath = filePath + '.tmp'; fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n'); fs.renameSync(tmpPath, filePath); } } /** Get a nested value by dot-separated key */ function getNestedValue(obj, key) { const parts = key.split('.'); let current = obj; for (const part of parts) { if (current === null || current === undefined || typeof current !== 'object') { return undefined; } current = current[part]; } return current; } /** Set a nested value by dot-separated key */ function setNestedValue(obj, key, value) { const parts = key.split('.'); let current = obj; for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) { current[part] = {}; } current = current[part]; } current[parts[parts.length - 1]] = value; } /** Parse a string value to the appropriate type */ export function parseConfigValue(value) { if (value === 'true') return true; if (value === 'false') return false; if (/^\d+$/.test(value)) return parseInt(value, 10); if (/^\d+\.\d+$/.test(value)) return parseFloat(value); try { const parsed = JSON.parse(value); if (typeof parsed === 'object') return parsed; } catch { /* not JSON, use as string */ } return value; } /** Singleton instance */ export const configManager = new ConfigFileManager(); //# sourceMappingURL=config-file-manager.js.map