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

411 lines 16.5 kB
/** * Config MCP Tools for CLI * * Tool definitions for configuration management with file persistence. */ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'; import { join } from 'node:path'; import { getProjectCwd } from './types.js'; import { validateIdentifier, validateText } from './validate-input.js'; // Storage paths const STORAGE_DIR = '.claude-flow'; const CONFIG_FILE = 'config.json'; const DEFAULT_CONFIG = { 'swarm.topology': 'mesh', 'swarm.maxAgents': 10, 'swarm.autoScale': true, 'memory.persistInterval': 60000, 'memory.maxEntries': 10000, 'session.autoSave': true, 'session.saveInterval': 300000, 'logging.level': 'info', 'logging.format': 'json', 'security.sandboxEnabled': true, 'security.pathValidation': true, }; function getConfigDir() { return join(getProjectCwd(), STORAGE_DIR); } function getConfigPath() { return join(getConfigDir(), CONFIG_FILE); } function ensureConfigDir() { const dir = getConfigDir(); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true, mode: 0o700 }); } } function loadConfigStore() { try { const path = getConfigPath(); if (existsSync(path)) { const data = readFileSync(path, 'utf-8'); return JSON.parse(data); } } catch { // Return default store on error } return { values: { ...DEFAULT_CONFIG }, scopes: {}, version: '3.0.0', updatedAt: new Date().toISOString(), }; } function saveConfigStore(store) { ensureConfigDir(); store.updatedAt = new Date().toISOString(); writeFileSync(getConfigPath(), JSON.stringify(store, null, 2), 'utf-8'); } function getNestedValue(obj, key) { const parts = key.split('.'); let current = obj; for (const part of parts) { if (current && typeof current === 'object' && part in current) { current = current[part]; } else { return undefined; } } return current; } const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']); function filterDangerousKeys(obj) { const filtered = {}; for (const [key, value] of Object.entries(obj)) { if (!DANGEROUS_KEYS.has(key)) { filtered[key] = value; } } return filtered; } function setNestedValue(obj, key, value) { const MAX_NESTING_DEPTH = 10; const parts = key.split('.'); if (parts.length > MAX_NESTING_DEPTH) { throw new Error(`Key exceeds maximum nesting depth of ${MAX_NESTING_DEPTH}`); } for (const part of parts) { if (DANGEROUS_KEYS.has(part)) { throw new Error(`Dangerous key segment rejected: ${part}`); } } 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] = {}; } current = current[part]; } current[parts[parts.length - 1]] = value; } export const configTools = [ { name: 'config_get', description: 'Get configuration value Use when native settings.json edits are wrong because the values need to be read by the Ruflo runtime (daemon, MCP server, neural router) — those load via the config_* path, not by re-reading settings.json. For .gitignore / .editorconfig style files, native Edit is fine.', category: 'config', inputSchema: { type: 'object', properties: { key: { type: 'string', description: 'Configuration key (dot notation supported)' }, scope: { type: 'string', description: 'Configuration scope (project, user, system)' }, }, required: ['key'], }, handler: async (input) => { // Validate user-provided input (#1425) const vKey = validateText(input.key, 'key', 256); if (!vKey.valid) return { success: false, error: vKey.error }; if (input.scope) { const v = validateIdentifier(input.scope, 'scope'); if (!v.valid) return { success: false, error: v.error }; } const store = loadConfigStore(); const key = input.key; const scope = input.scope || 'default'; let value; // Check scope first, then default values if (scope !== 'default' && store.scopes[scope]) { value = store.scopes[scope][key]; } if (value === undefined) { value = store.values[key]; } if (value === undefined) { value = DEFAULT_CONFIG[key]; } return { key, value, scope, exists: value !== undefined, source: value !== undefined ? (store.values[key] !== undefined ? 'stored' : 'default') : 'none', }; }, }, { name: 'config_set', description: 'Set configuration value Use when native settings.json edits are wrong because the values need to be read by the Ruflo runtime (daemon, MCP server, neural router) — those load via the config_* path, not by re-reading settings.json. For .gitignore / .editorconfig style files, native Edit is fine.', category: 'config', inputSchema: { type: 'object', properties: { key: { type: 'string', description: 'Configuration key (dot notation supported)' }, value: { description: 'Configuration value' }, scope: { type: 'string', description: 'Configuration scope (project, user, system)' }, }, required: ['key', 'value'], }, handler: async (input) => { // Validate user-provided input (#1425) const vKey = validateText(input.key, 'key', 256); if (!vKey.valid) return { success: false, error: vKey.error }; if (input.scope) { const v = validateIdentifier(input.scope, 'scope'); if (!v.valid) return { success: false, error: v.error }; } const store = loadConfigStore(); const key = input.key; const value = input.value; const scope = input.scope || 'default'; const previousValue = store.values[key]; if (scope === 'default') { store.values[key] = value; } else { if (!store.scopes[scope]) { store.scopes[scope] = {}; } store.scopes[scope][key] = value; } saveConfigStore(store); return { success: true, key, value, previousValue, scope, path: getConfigPath(), }; }, }, { name: 'config_list', description: 'List configuration values Use when native settings.json edits are wrong because the values need to be read by the Ruflo runtime (daemon, MCP server, neural router) — those load via the config_* path, not by re-reading settings.json. For .gitignore / .editorconfig style files, native Edit is fine.', category: 'config', inputSchema: { type: 'object', properties: { scope: { type: 'string', description: 'Configuration scope' }, prefix: { type: 'string', description: 'Key prefix filter' }, includeDefaults: { type: 'boolean', description: 'Include default values' }, }, }, handler: async (input) => { // Validate user-provided input (#1425) if (input.scope) { const v = validateIdentifier(input.scope, 'scope'); if (!v.valid) return { success: false, error: v.error }; } if (input.prefix) { const v = validateText(input.prefix, 'prefix', 256); if (!v.valid) return { success: false, error: v.error }; } const store = loadConfigStore(); const scope = input.scope || 'default'; const prefix = input.prefix; const includeDefaults = input.includeDefaults !== false; const merged = new Map(); if (includeDefaults) { for (const [key, value] of Object.entries(DEFAULT_CONFIG)) { merged.set(key, { value, source: 'default' }); } } for (const [key, value] of Object.entries(store.values)) { merged.set(key, { value, source: 'stored' }); } // Always include keys from every scope so they're discoverable; the // scope filter only narrows which set is used as the *winner*. for (const [scopeName, scopeValues] of Object.entries(store.scopes)) { for (const [key, value] of Object.entries(scopeValues)) { if (scope === scopeName || scope === 'default') { merged.set(key, { value, source: `scope:${scopeName}` }); } else if (!merged.has(key)) { // Surface scoped keys that aren't shadowed when listing default scope merged.set(key, { value, source: `scope:${scopeName}` }); } } } let entries = Array.from(merged.entries()); if (prefix) { entries = entries.filter(([key]) => key.startsWith(prefix)); } entries.sort(([a], [b]) => a.localeCompare(b)); return { configs: entries.map(([key, { value, source }]) => ({ key, value, source })), total: entries.length, scope, updatedAt: store.updatedAt, }; }, }, { name: 'config_reset', description: 'Reset configuration to defaults Use when native settings.json edits are wrong because the values need to be read by the Ruflo runtime (daemon, MCP server, neural router) — those load via the config_* path, not by re-reading settings.json. For .gitignore / .editorconfig style files, native Edit is fine.', category: 'config', inputSchema: { type: 'object', properties: { scope: { type: 'string', description: 'Configuration scope' }, key: { type: 'string', description: 'Specific key to reset (omit to reset all)' }, }, }, handler: async (input) => { // Validate user-provided input (#1425) if (input.scope) { const v = validateIdentifier(input.scope, 'scope'); if (!v.valid) return { success: false, error: v.error }; } if (input.key) { const v = validateText(input.key, 'key', 256); if (!v.valid) return { success: false, error: v.error }; } const store = loadConfigStore(); const scope = input.scope || 'default'; const key = input.key; let resetKeys = []; if (key) { // Reset specific key if (scope === 'default') { if (key in store.values) { delete store.values[key]; resetKeys.push(key); } } else if (store.scopes[scope] && key in store.scopes[scope]) { delete store.scopes[scope][key]; resetKeys.push(key); } } else { // Reset all keys in scope if (scope === 'default') { resetKeys = Object.keys(store.values); store.values = { ...DEFAULT_CONFIG }; } else if (store.scopes[scope]) { resetKeys = Object.keys(store.scopes[scope]); delete store.scopes[scope]; } } saveConfigStore(store); return { success: true, scope, reset: key || 'all', resetKeys, count: resetKeys.length, }; }, }, { name: 'config_export', description: 'Export configuration to JSON Use when native settings.json edits are wrong because the values need to be read by the Ruflo runtime (daemon, MCP server, neural router) — those load via the config_* path, not by re-reading settings.json. For .gitignore / .editorconfig style files, native Edit is fine.', category: 'config', inputSchema: { type: 'object', properties: { scope: { type: 'string', description: 'Configuration scope' }, includeDefaults: { type: 'boolean', description: 'Include default values' }, }, }, handler: async (input) => { // Validate user-provided input (#1425) if (input.scope) { const v = validateIdentifier(input.scope, 'scope'); if (!v.valid) return { success: false, error: v.error }; } const store = loadConfigStore(); const scope = input.scope || 'default'; const includeDefaults = input.includeDefaults !== false; let exportData = {}; if (includeDefaults) { exportData = { ...DEFAULT_CONFIG }; } Object.assign(exportData, store.values); if (scope !== 'default' && store.scopes[scope]) { Object.assign(exportData, store.scopes[scope]); } return { config: exportData, scope, version: store.version, exportedAt: new Date().toISOString(), count: Object.keys(exportData).length, }; }, }, { name: 'config_import', description: 'Import configuration from JSON Use when native settings.json edits are wrong because the values need to be read by the Ruflo runtime (daemon, MCP server, neural router) — those load via the config_* path, not by re-reading settings.json. For .gitignore / .editorconfig style files, native Edit is fine.', category: 'config', inputSchema: { type: 'object', properties: { config: { type: 'object', description: 'Configuration object to import' }, scope: { type: 'string', description: 'Configuration scope' }, merge: { type: 'boolean', description: 'Merge with existing (true) or replace (false)' }, }, required: ['config'], }, handler: async (input) => { // Validate user-provided input (#1425) if (input.scope) { const v = validateIdentifier(input.scope, 'scope'); if (!v.valid) return { success: false, error: v.error }; } const store = loadConfigStore(); const config = filterDangerousKeys(input.config); const scope = input.scope || 'default'; const merge = input.merge !== false; const importedKeys = Object.keys(config); if (scope === 'default') { if (merge) { Object.assign(store.values, config); } else { store.values = { ...DEFAULT_CONFIG, ...config }; } } else { if (!store.scopes[scope] || !merge) { store.scopes[scope] = {}; } Object.assign(store.scopes[scope], config); } saveConfigStore(store); return { success: true, scope, imported: importedKeys.length, keys: importedKeys, merge, }; }, }, ]; //# sourceMappingURL=config-tools.js.map