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
JavaScript
/**
* 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