@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
526 lines • 20.6 kB
JavaScript
/**
* Enterprise Configuration Management Commands
* Features: Security masking, multi-format support, validation, change tracking
*/
import { Command } from "commander";
import chalk from "chalk";
import inquirer from "inquirer";
import { configManager } from "../../core/config.js";
import { deepMerge } from "../../utils/helpers.js";
export const configCommand = new Command()
.name("config")
.description("Manage Claude-Flow configuration")
.action(() => {
configCommand.help();
})
.command("show")
.description("Show current configuration")
.option("--format <format>", "Output format (json, yaml)", "json")
.option("--diff", "Show only differences from defaults")
.option("--profile", "Include profile information")
.action((options) => {
if (options.diff) {
const diff = configManager.getDiff();
console.log(JSON.stringify(diff, null, 2));
}
else if (options.profile) {
const exported = configManager.export();
console.log(JSON.stringify(exported, null, 2));
}
else {
const config = configManager.get();
if (options.format === "json") {
console.log(JSON.stringify(config, null, 2));
}
else {
console.log(chalk.yellow("YAML format not yet implemented"));
console.log(JSON.stringify(config, null, 2));
}
}
})
.command("get")
.description("Get a specific configuration value")
.arguments("<path>")
.action((path) => {
try {
const value = configManager.getValue(path);
if (value === undefined) {
console.error(chalk.red(`Configuration path not found: ${path}`));
process.exit(1);
}
else {
console.log(JSON.stringify(value, null, 2));
}
}
catch (error) {
console.error(chalk.red("Failed to get configuration value:"), error.message);
process.exit(1);
}
})
.command("set")
.description("Set a configuration value with validation and change tracking")
.arguments("<path:string> <value:string>")
.option("--type <type>", "Value type (string, number, boolean, json)", "auto")
.option("--reason <reason>", "Reason for the change (for audit trail)")
.option("--force", "Skip validation warnings")
.action((path, value, options) => {
try {
let parsedValue;
switch (options.type) {
case "string":
parsedValue = value;
break;
case "number":
parsedValue = parseFloat(value);
if (isNaN(parsedValue)) {
throw new Error("Invalid number format");
}
break;
case "boolean":
parsedValue = value.toLowerCase() === "true";
break;
case "json":
try {
parsedValue = JSON.parse(value);
}
catch (error) {
throw new Error(`Invalid JSON format: ${error.message}`);
}
break;
default:
// Auto-detect type
try {
parsedValue = JSON.parse(value);
}
catch {
parsedValue = value;
}
}
// Get user info for change tracking
const user = process.env.USER ?? process.env.USERNAME ?? "unknown";
const { reason } = options;
configManager.set(path, parsedValue, { user, reason, source: "cli" });
console.log(chalk.green("✓"), `Set ${path} = ${JSON.stringify(parsedValue)}`);
if (reason) {
console.log(chalk.gray(`Reason: ${reason}`));
}
}
catch (error) {
console.error(chalk.red("Failed to set configuration:"), error.message);
process.exit(1);
}
})
.command("reset")
.description("Reset configuration to defaults")
.option("--confirm", "Skip confirmation prompt")
.action(async (options) => {
if (!options.confirm) {
const answers = await inquirer.prompt([{
type: "confirm",
name: "confirmed",
message: "Reset configuration to defaults?",
default: false,
}]);
const { confirmed } = answers;
if (!confirmed) {
console.log(chalk.gray("Reset cancelled"));
return;
}
}
configManager.reset();
console.log(chalk.green("✓ Configuration reset to defaults"));
})
.command("init")
.description("Initialize a new configuration file with enterprise templates")
.arguments("[output-file:string]")
.option("--force", "Overwrite existing file")
.option("--template <template>", "Configuration template (default, development, production, minimal, testing, enterprise)", "default")
.option("--format <format>", "Output format (json, yaml, toml)", "json")
.option("--interactive", "Interactive template selection")
.action(async (outputFile = "claude-flow.config.json", options) => {
try {
// Check if file exists
try {
await import("fs").then(fs => fs.promises.stat(outputFile));
if (!options.force) {
console.error(chalk.red(`File already exists: ${outputFile}`));
console.log(chalk.gray("Use --force to overwrite"));
return;
}
}
catch {
// File doesn't exist, which is what we want
}
let templateName = options.template;
// Interactive template selection
if (options.interactive) {
const availableTemplates = configManager.getAvailableTemplates();
const templateAnswer = await inquirer.prompt([{
type: "list",
name: "template",
message: "Select configuration template:",
choices: availableTemplates,
}]);
templateName = templateAnswer.template;
}
const config = configManager.createTemplate(templateName || "default");
// Detect format from file extension or use option
const ext = outputFile.split(".").pop()?.toLowerCase();
const format = options.format ?? (ext === "yaml" || ext === "yml" ? "yaml" : ext === "toml" ? "toml" : "json");
const formatParsers = configManager.getFormatParsers();
const parser = formatParsers[format];
const content = parser ? parser.stringify(config) : JSON.stringify(config, null, 2);
await import("fs").then(fs => fs.promises.writeFile(outputFile, content, "utf-8"));
console.log(chalk.green("✓"), `Configuration file created: ${outputFile}`);
console.log(chalk.gray(`Template: ${templateName}`));
console.log(chalk.gray(`Format: ${format}`));
}
catch (error) {
console.error(chalk.red("Failed to create configuration file:"), error.message);
process.exit(1);
}
})
.command("validate")
.description("Validate a configuration file")
.arguments("<config-file>")
.option("--strict", "Use strict validation")
.action(async (configFile, options) => {
try {
await configManager.load(configFile);
console.log(chalk.blue("Validating configuration file:"), configFile);
// Use the new comprehensive validation method
const result = await configManager.validateFile(configFile);
if (result.valid) {
console.log(chalk.green("✓"), "Configuration is valid");
if (options.strict) {
console.log(chalk.gray("✓ Strict validation passed"));
}
}
else {
console.error(chalk.red("✗"), "Configuration validation failed:");
result.errors.forEach((error) => {
console.error(chalk.red(` • ${error}`));
});
process.exit(1);
}
}
catch (error) {
console.error(chalk.red("✗"), "Configuration validation failed:");
console.error(error.message);
process.exit(1);
}
})
.command("profile")
.description("Manage configuration profiles")
.action(() => {
console.log(chalk.gray("Usage: config profile <list|save|load|delete> [options]"));
})
.command("list")
.description("List all configuration profiles")
.action(async () => {
try {
const profiles = await configManager.listProfiles();
const currentProfile = configManager.getCurrentProfile();
if (profiles.length === 0) {
console.log(chalk.gray("No profiles found"));
return;
}
console.log(chalk.cyan.bold(`Configuration Profiles (${profiles.length})`));
console.log("─".repeat(40));
for (const profile of profiles) {
const indicator = profile === currentProfile ? chalk.green("● ") : " ";
console.log(`${indicator}${profile}`);
}
if (currentProfile) {
console.log();
console.log(chalk.gray(`Current: ${currentProfile}`));
}
}
catch (error) {
console.error(chalk.red("Failed to list profiles:"), error.message);
}
})
.command("save")
.description("Save current configuration as a profile")
.arguments("<profile-name>")
.option("--force", "Overwrite existing profile")
.action(async (profileName, options) => {
try {
const existing = await configManager.getProfile(profileName);
if (existing && !options.force) {
console.error(chalk.red(`Profile '${profileName}' already exists`));
console.log(chalk.gray("Use --force to overwrite"));
return;
}
await configManager.saveProfile(profileName);
console.log(chalk.green("✓"), `Profile '${profileName}' saved`);
}
catch (error) {
console.error(chalk.red("Failed to save profile:"), error.message);
}
})
.command("load")
.description("Load a configuration profile")
.arguments("<profile-name>")
.action(async (profileName, options) => {
try {
await configManager.applyProfile(profileName);
console.log(chalk.green("✓"), `Profile '${profileName}' loaded`);
}
catch (error) {
console.error(chalk.red("Failed to load profile:"), error.message);
}
})
.command("delete")
.description("Delete a configuration profile")
.arguments("<profile-name>")
.option("--force", "Skip confirmation prompt")
.action(async (profileName, options) => {
try {
if (!options.force) {
const answers = await inquirer.prompt([{
type: "confirm",
name: "confirmed",
message: `Delete profile '${profileName}'?`,
default: false,
}]);
const { confirmed } = answers;
if (!confirmed) {
console.log(chalk.gray("Delete cancelled"));
return;
}
}
await configManager.deleteProfile(profileName);
console.log(chalk.green("✓"), `Profile '${profileName}' deleted`);
}
catch (error) {
console.error(chalk.red("Failed to delete profile:"), error.message);
}
})
.command("show")
.description("Show profile configuration")
.arguments("<profile-name>")
.action(async (profileName, options) => {
try {
const profile = await configManager.getProfile(profileName);
if (!profile) {
console.error(chalk.red(`Profile '${profileName}' not found`));
return;
}
console.log(JSON.stringify(profile, null, 2));
}
catch (error) {
console.error(chalk.red("Failed to show profile:"), error.message);
}
})
.command("export")
.description("Export configuration")
.arguments("<output-file>")
.option("--include-defaults", "Include default values")
.action(async (outputFile, options) => {
try {
let data;
if (options.includeDefaults) {
data = configManager.export();
}
else {
data = {
version: "1.0.0",
exported: new Date().toISOString(),
profile: configManager.getCurrentProfile(),
config: configManager.getDiff(),
};
}
await import("fs").then(fs => fs.promises.writeFile(outputFile, JSON.stringify(data, null, 2)));
console.log(chalk.green("✓"), `Configuration exported to ${outputFile}`);
}
catch (error) {
console.error(chalk.red("Failed to export configuration:"), error.message);
}
})
.command("import")
.description("Import configuration")
.arguments("<input-file>")
.option("--merge", "Merge with current configuration")
.action(async (inputFile, options) => {
try {
const content = await import("fs").then(fs => fs.promises.readFile(inputFile, "utf-8"));
const data = JSON.parse(content);
if (options.merge) {
const current = configManager.get();
data.config = deepMerge(current, data.config ?? {});
}
configManager.import(data);
console.log(chalk.green("✓"), "Configuration imported successfully");
if (data.profile) {
console.log(chalk.gray(`Profile: ${data.profile}`));
}
}
catch (error) {
console.error(chalk.red("Failed to import configuration:"), error.message);
}
})
.command("schema")
.description("Show configuration schema")
.option("--path <path>", "Show schema for specific path")
.action((options) => {
const schema = configManager.getSchema();
if (options.path) {
const value = getValueByPath(schema, options.path);
if (value === undefined) {
console.error(chalk.red(`Schema path not found: ${options.path}`));
return;
}
console.log(JSON.stringify(value, null, 2));
}
else {
console.log(JSON.stringify(schema, null, 2));
}
})
.command("history")
.description("Show configuration change history")
.option("--path <path>", "Show history for specific configuration path")
.option("--limit <limit:number>", "Maximum number of changes to show", "20")
.option("--format <format>", "Output format (json, table)", "table")
.action((options) => {
try {
const changes = options.path
? configManager.getPathHistory()
: configManager.getChangeHistory();
if (changes.length === 0) {
console.log(chalk.gray("No configuration changes found"));
return;
}
if (options.format === "json") {
console.log(JSON.stringify(changes, null, 2));
}
else {
console.log(chalk.cyan.bold(`Configuration Change History (${changes.length} changes)`));
console.log("─".repeat(80));
changes.reverse().forEach((change, index) => {
const timestamp = change.timestamp instanceof Date ? change.timestamp.toLocaleString() : new Date(change.timestamp).toLocaleString();
const user = change.user ?? "system";
const source = change.source ?? "unknown";
console.log(`${chalk.green(timestamp)} | ${chalk.blue(user)} | ${chalk.yellow(source)}`);
console.log(`Path: ${chalk.cyan(change.path)}`);
if (change.reason) {
console.log(`Reason: ${chalk.gray(change.reason)}`);
}
if (change.oldValue !== undefined && change.newValue !== undefined) {
console.log(`Old: ${chalk.red(JSON.stringify(change.oldValue))}`);
console.log(`New: ${chalk.green(JSON.stringify(change.newValue))}`);
}
if (index < changes.length - 1) {
console.log("");
}
});
}
}
catch (error) {
console.error(chalk.red("Failed to get change history:"), error.message);
}
})
.command("backup")
.description("Backup current configuration")
.arguments("[backup-path:string]")
.option("--auto-name", "Generate automatic backup filename")
.action(async (backupPath, options) => {
try {
const finalPath = backupPath ?? (options.autoName ? undefined : "config-backup.json");
const savedPath = await configManager.backup(finalPath);
console.log(chalk.green("✓"), `Configuration backed up to: ${savedPath}`);
console.log(chalk.gray("Backup includes configuration and recent change history"));
}
catch (error) {
console.error(chalk.red("Failed to backup configuration:"), error.message);
process.exit(1);
}
})
.command("restore")
.description("Restore configuration from backup")
.arguments("<backup-path>")
.option("--force", "Skip confirmation prompt")
.action(async (backupPath, options) => {
try {
if (!options.force) {
const answers = await inquirer.prompt([{
type: "confirm",
name: "confirmed",
message: `Restore configuration from ${backupPath}? This will overwrite current configuration.`,
default: false,
}]);
const { confirmed } = answers;
if (!confirmed) {
console.log(chalk.gray("Restore cancelled"));
return;
}
}
await configManager.restore(backupPath);
console.log(chalk.green("✓"), "Configuration restored successfully");
console.log(chalk.yellow("⚠️"), "You may need to restart the application for changes to take effect");
}
catch (error) {
console.error(chalk.red("Failed to restore configuration:"), error.message);
process.exit(1);
}
})
.command("templates")
.description("List available configuration templates")
.option("--detailed", "Show detailed template information")
.action((options) => {
try {
const templates = configManager.getAvailableTemplates();
console.log(chalk.cyan.bold(`Available Configuration Templates (${templates.length})`));
console.log("─".repeat(50));
for (const template of templates) {
console.log(chalk.green("●"), chalk.bold(template));
if (options.detailed) {
try {
const config = configManager.createTemplate(template);
const description = getTemplateDescription(template);
console.log(` ${chalk.gray(description)}`);
if (config.orchestrator) {
console.log(` Max Agents: ${chalk.cyan(config.orchestrator.maxConcurrentAgents)}`);
}
if (config.logging) {
console.log(` Log Level: ${chalk.cyan(config.logging.level)}`);
}
}
catch (error) {
console.log(` ${chalk.red("Error loading template")}`);
}
}
console.log("");
}
}
catch (error) {
console.error(chalk.red("Failed to list templates:"), error.message);
}
});
// Helper function for template descriptions
function getTemplateDescription(templateName) {
const descriptions = {
default: "Standard configuration with balanced settings",
development: "Optimized for development with debug logging and lower limits",
production: "Production-ready with enhanced security and performance",
minimal: "Minimal resource usage for constrained environments",
testing: "Optimized for testing with fast feedback and lower retention",
enterprise: "Enterprise-grade with maximum security and scalability",
};
return descriptions[templateName] ?? "Custom configuration template";
}
function getValueByPath(obj, path) {
const parts = path.split(".");
let current = obj;
for (const part of parts) {
if (current && typeof current === "object" && !Array.isArray(current) && part in current) {
current = (current)[part];
}
else {
return undefined;
}
}
return current;
}
// Legacy function removed - now replaced by configManager.createTemplate()
//# sourceMappingURL=config.js.map