UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

513 lines 17.4 kB
/** * Node.js-compatible Configuration management for Claude-Flow */ import { promises as fs } from "fs"; import path from "path"; import os from "os"; /** * Check if we're running in a SEA (Single Executable Application) environment */ function isSEA() { // Check for process.isSEA property (Node.js 20+) if (process.isSEA) { return true; } // Fallback: check if we're running from a binary that's not node const { execPath } = process; const isNodeBinary = execPath.includes("node") && !execPath.includes("claude-flow"); // If we're not running from a node binary, we're likely in a SEA return !isNodeBinary; } /** * Get default memory backend based on environment */ function getDefaultMemoryBackend() { // In SEA mode, use markdown to avoid SQLite native module issues if (isSEA()) { return "markdown"; } return "hybrid"; } /** * Default configuration values */ const DEFAULT_CONFIG = { orchestrator: { maxConcurrentAgents: 10, taskQueueSize: 100, healthCheckInterval: 30000, shutdownTimeout: 30000, }, terminal: { type: "auto", poolSize: 5, recycleAfter: 10, healthCheckInterval: 60000, commandTimeout: 300000, }, memory: { backend: "hybrid", // Will be overridden in createDefaultConfig for SEA cacheSizeMB: 100, syncInterval: 5000, conflictResolution: "crdt", retentionDays: 30, }, coordination: { maxRetries: 3, retryDelay: 1000, deadlockDetection: true, resourceTimeout: 60000, messageTimeout: 30000, }, mcp: { transport: "stdio", port: 3000, tlsEnabled: false, }, logging: { level: "info", format: "json", destination: "console", }, }; /** * Configuration validation error */ export class ConfigError extends Error { constructor(message) { super(message); this.name = "ConfigError"; } } /** * Configuration manager for Node.js */ export class ConfigManager { static instance; config; configPath; userConfigDir; constructor() { this.config = this.deepClone(DEFAULT_CONFIG); this.userConfigDir = path.join(os.homedir(), ".claude-flow"); } /** * Gets the singleton instance */ static getInstance() { if (!ConfigManager.instance) { ConfigManager.instance = new ConfigManager(); } return ConfigManager.instance; } /** * Initialize configuration from file or create default */ async init(configPath = "claude-flow.config.json") { try { await this.load(configPath); console.log(`✅ Configuration loaded from: ${configPath}`); } catch (error) { // Create default config file if it doesn't exist await this.createDefaultConfig(configPath); console.log(`✅ Default configuration created: ${configPath}`); } } /** * Creates a default configuration file */ async createDefaultConfig(configPath) { const config = this.deepClone(DEFAULT_CONFIG); // Override memory backend for SEA mode const defaultBackend = getDefaultMemoryBackend(); if (defaultBackend !== config.memory.backend) { config.memory.backend = defaultBackend; console.log(`ℹ️ Using ${defaultBackend} backend for SEA (Single Executable) compatibility`); } const content = JSON.stringify(config, null, 2); await fs.writeFile(configPath, content, "utf8"); this.configPath = configPath; } /** * Loads configuration from file */ async load(configPath) { if (configPath) { this.configPath = configPath; } if (!this.configPath) { throw new ConfigError("No configuration file path specified"); } try { const content = await fs.readFile(this.configPath, "utf8"); const fileConfig = JSON.parse(content); // Merge with defaults this.config = this.deepMerge(DEFAULT_CONFIG, fileConfig); // Load environment variables this.loadFromEnv(); // Validate this.validate(this.config); return this.config; } catch (error) { if (error.code === "ENOENT") { throw new ConfigError(`Configuration file not found: ${this.configPath}`); } throw new ConfigError(`Failed to load configuration: ${error.message}`); } } /** * Shows current configuration */ show() { return this.deepClone(this.config); } /** * Gets a configuration value by path */ get(path) { const keys = path.split("."); let current = this.config; for (const key of keys) { if (current && typeof current === "object" && key in current) { current = current[key]; } else { return undefined; } } return current; } /** * Sets a configuration value by path */ set(path, value) { const keys = path.split("."); let current = this.config; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; if (!(key in current)) { current[key] = {}; } current = current[key]; } const lastKey = keys[keys.length - 1]; current[lastKey] = value; // Validate after setting this.validate(this.config); } /** * Saves current configuration to file */ async save(configPath) { const savePath = configPath || this.configPath; if (!savePath) { throw new ConfigError("No configuration file path specified"); } const content = JSON.stringify(this.config, null, 2); await fs.writeFile(savePath, content, "utf8"); } /** * Validates the configuration */ validate(config) { // Orchestrator validation if (config.orchestrator.maxConcurrentAgents < 1 || config.orchestrator.maxConcurrentAgents > 100) { throw new ConfigError("orchestrator.maxConcurrentAgents must be between 1 and 100"); } if (config.orchestrator.taskQueueSize < 1 || config.orchestrator.taskQueueSize > 10000) { throw new ConfigError("orchestrator.taskQueueSize must be between 1 and 10000"); } // Terminal validation if (!["auto", "vscode", "native"].includes(config.terminal.type)) { throw new ConfigError("terminal.type must be one of: auto, vscode, native"); } if (config.terminal.poolSize < 1 || config.terminal.poolSize > 50) { throw new ConfigError("terminal.poolSize must be between 1 and 50"); } // Memory validation if (!["sqlite", "markdown", "hybrid"].includes(config.memory.backend)) { throw new ConfigError("memory.backend must be one of: sqlite, markdown, hybrid"); } if (config.memory.cacheSizeMB < 1 || config.memory.cacheSizeMB > 10000) { throw new ConfigError("memory.cacheSizeMB must be between 1 and 10000"); } // Coordination validation if (config.coordination.maxRetries < 0 || config.coordination.maxRetries > 100) { throw new ConfigError("coordination.maxRetries must be between 0 and 100"); } // MCP validation if (!["stdio", "http", "websocket"].includes(config.mcp.transport)) { throw new ConfigError("mcp.transport must be one of: stdio, http, websocket"); } if (config.mcp.port < 1 || config.mcp.port > 65535) { throw new ConfigError("mcp.port must be between 1 and 65535"); } // Logging validation if (!["debug", "info", "warn", "error"].includes(config.logging.level)) { throw new ConfigError("logging.level must be one of: debug, info, warn, error"); } if (!["json", "text"].includes(config.logging.format)) { throw new ConfigError("logging.format must be one of: json, text"); } if (!["console", "file"].includes(config.logging.destination)) { throw new ConfigError("logging.destination must be one of: console, file"); } } /** * Loads configuration from environment variables */ loadFromEnv() { // Orchestrator settings const maxAgents = process.env.CLAUDE_FLOW_MAX_AGENTS; if (maxAgents) { this.config.orchestrator.maxConcurrentAgents = parseInt(maxAgents, 10); } // Terminal settings const terminalType = process.env.CLAUDE_FLOW_TERMINAL_TYPE; if (terminalType === "vscode" || terminalType === "native" || terminalType === "auto") { this.config.terminal.type = terminalType; } // Memory settings const memoryBackend = process.env.CLAUDE_FLOW_MEMORY_BACKEND; if (memoryBackend === "sqlite" || memoryBackend === "markdown" || memoryBackend === "hybrid") { this.config.memory.backend = memoryBackend; } // MCP settings const mcpTransport = process.env.CLAUDE_FLOW_MCP_TRANSPORT; if (mcpTransport === "stdio" || mcpTransport === "http" || mcpTransport === "websocket") { this.config.mcp.transport = mcpTransport; } const mcpPort = process.env.CLAUDE_FLOW_MCP_PORT; if (mcpPort) { this.config.mcp.port = parseInt(mcpPort, 10); } // Logging settings const logLevel = process.env.CLAUDE_FLOW_LOG_LEVEL; if (logLevel === "debug" || logLevel === "info" || logLevel === "warn" || logLevel === "error") { this.config.logging.level = logLevel; } } /** * Deep clone helper */ deepClone(obj) { return JSON.parse(JSON.stringify(obj)); } /** * Deep merge helper */ deepMerge(target, source) { const result = this.deepClone(target); if (source.orchestrator) { result.orchestrator = { ...result.orchestrator, ...source.orchestrator }; } if (source.terminal) { result.terminal = { ...result.terminal, ...source.terminal }; } if (source.memory) { result.memory = { ...result.memory, ...source.memory }; } if (source.coordination) { result.coordination = { ...result.coordination, ...source.coordination }; } if (source.mcp) { result.mcp = { ...result.mcp, ...source.mcp }; } if (source.logging) { result.logging = { ...result.logging, ...source.logging }; } return result; } /** * Gets available configuration templates */ getAvailableTemplates() { return ["minimal", "default", "development", "production"]; } /** * Creates a configuration from a template */ createTemplate(templateName) { const templates = { minimal: { orchestrator: { maxConcurrentAgents: 5, taskQueueSize: 50, healthCheckInterval: 60000, shutdownTimeout: 10000, }, terminal: { type: "auto", poolSize: 2, recycleAfter: 5, healthCheckInterval: 120000, commandTimeout: 300000, }, }, development: { logging: { level: "debug", format: "text", destination: "console", }, orchestrator: { maxConcurrentAgents: 20, taskQueueSize: 200, healthCheckInterval: 15000, shutdownTimeout: 5000, }, }, production: { logging: { level: "warn", format: "json", destination: "file", }, memory: { backend: "markdown", cacheSizeMB: 500, syncInterval: 10000, conflictResolution: "timestamp", retentionDays: 90, }, }, }; const template = templates[templateName] || {}; return this.deepMerge(DEFAULT_CONFIG, template); } /** * Gets format parsers for different config file formats */ getFormatParsers() { return { json: { stringify: (obj) => JSON.stringify(obj, null, 2), parse: (str) => JSON.parse(str), }, yaml: { stringify: (obj) => { // Simple YAML stringification (real implementation would use a YAML library) return this.toYAML(obj); }, parse: (str) => { // Simple YAML parsing (real implementation would use a YAML library) throw new Error("YAML parsing not implemented"); }, }, toml: { stringify: (obj) => { // Simple TOML stringification (real implementation would use a TOML library) throw new Error("TOML stringification not implemented"); }, parse: (str) => { // Simple TOML parsing (real implementation would use a TOML library) throw new Error("TOML parsing not implemented"); }, }, }; } /** * Validates a configuration file */ async validateFile(configPath) { const errors = []; try { const content = await fs.readFile(configPath, "utf8"); const config = JSON.parse(content); // Run validation try { this.validate(config); } catch (error) { errors.push(error.message); } // Additional validations if (!config.orchestrator) { errors.push("Missing required section: orchestrator"); } if (!config.terminal) { errors.push("Missing required section: terminal"); } if (!config.memory) { errors.push("Missing required section: memory"); } return { valid: errors.length === 0, errors, }; } catch (error) { errors.push(`Failed to read or parse file: ${error.message}`); return { valid: false, errors, }; } } /** * Backs up the current configuration */ async backup(backupPath) { const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); const backupFile = backupPath || path.join(this.userConfigDir, `backup-${timestamp}.json`); await fs.mkdir(path.dirname(backupFile), { recursive: true }); await fs.writeFile(backupFile, JSON.stringify(this.config, null, 2), "utf8"); return backupFile; } /** * Restores configuration from a backup */ async restore(backupPath) { const content = await fs.readFile(backupPath, "utf8"); const backupConfig = JSON.parse(content); // Validate the backup configuration this.validate(backupConfig); // Apply the backup this.config = backupConfig; // Save to current config file if (this.configPath) { await this.save(); } } /** * Gets configuration path history */ getPathHistory() { // In a real implementation, this would track all config paths used return this.configPath ? [this.configPath] : []; } /** * Gets configuration change history */ getChangeHistory() { // In a real implementation, this would track all configuration changes return []; } /** * Simple YAML converter (basic implementation) */ toYAML(obj, indent = 0) { const spaces = " ".repeat(indent); let result = ""; for (const [key, value] of Object.entries(obj)) { if (typeof value === "object" && value !== null && !Array.isArray(value)) { result += `${spaces}${key}:\n${this.toYAML(value, indent + 1)}`; } else if (Array.isArray(value)) { result += `${spaces}${key}:\n`; value.forEach(item => { result += `${spaces} - ${JSON.stringify(item)}\n`; }); } else { result += `${spaces}${key}: ${JSON.stringify(value)}\n`; } } return result; } } // Export singleton instance export const configManager = ConfigManager.getInstance(); //# sourceMappingURL=config-manager.js.map