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
538 lines • 25.8 kB
JavaScript
/**
* V3 CLI Main Entry Point
* Modernized CLI for RuFlo V3
*
* Created with ❤️ by ruv.io
*/
// MUST be the first import — installs console filter for the cosmetic
// "[AgentDB Patch] Controller index not found" warning before any
// agentic-flow / agentdb code can load. ES module imports are evaluated
// in source order, so this file runs its side effects before any other
// import in this module's import graph (including transitive imports of
// agentic-flow via commands/index.js).
import './log-filters.js';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { commandParser } from './parser.js';
import { output } from './output.js';
import { commands, getCommandsByCategory, getCommand, getCommandAsync, getCommandNames, getLazyCommandNames, hasCommand } from './commands/index.js';
import { suggestCommand } from './suggest.js';
import { runStartupUpdateCheck } from './update/index.js';
// Read version from package.json at runtime
function getPackageVersion() {
try {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Navigate from dist/src to package root
const pkgPath = join(__dirname, '..', '..', 'package.json');
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
return pkg.version || '3.0.0';
}
catch {
return '3.0.0';
}
}
export const VERSION = getPackageVersion();
/**
* V3 CLI Application
*/
export class CLI {
name;
description;
version;
parser;
output;
interactive;
constructor(options = {}) {
this.name = options.name || 'ruflo';
this.description = options.description || 'RuFlo V3 - AI Agent Orchestration Platform';
this.version = options.version || VERSION;
this.parser = commandParser;
this.output = output;
this.interactive = options.interactive ?? process.stdin.isTTY ?? false;
// Register all core (synchronously loaded) commands with full definitions
for (const cmd of commands) {
this.parser.registerCommand(cmd);
}
// Register lazy command names so the parser can recognize them during
// argument resolution without importing their modules. Fix for #1596:
// prevents `daemon start` from being mis-routed to the `start` command.
for (const name of getLazyCommandNames()) {
this.parser.registerLazyCommandName(name);
}
}
/**
* Run the CLI with given arguments
*/
async run(args = process.argv.slice(2)) {
try {
// #1791.2 — If the user invoked a lazy command (e.g. `hive-mind task`),
// pre-load it BEFORE parsing so the parser can build scoped flag
// aliases for its subcommands. Without this, short flags defined on
// the lazy command's subcommand options (`-d` for description, etc.)
// never get into the alias map and silently fall through to global
// resolution — the user sees `[ERROR] Task description is required`
// even though they passed `-d "smoke"`.
for (const arg of args) {
if (arg.startsWith('-'))
continue;
if (this.parser.isLazyOnly(arg)) {
const cmd = await getCommandAsync(arg);
if (cmd)
this.parser.registerCommand(cmd);
}
break; // only the first non-flag positional is the command name
}
// Parse arguments
const parseResult = this.parser.parse(args);
const { command: commandPath, flags, positional } = parseResult;
// Handle global flags
if (flags.version || flags.V) {
this.showVersion();
return;
}
if (flags.noColor) {
this.output.setColorEnabled(false);
}
// Set verbosity level based on flags
if (flags.quiet) {
this.output.setVerbosity('quiet');
}
else if (flags.verbose) {
this.output.setVerbosity(process.env.DEBUG ? 'debug' : 'verbose');
}
// Verbose mode: show parsed arguments
if (this.output.isVerbose()) {
this.output.printDebug(`Command: ${commandPath.join(' ') || '(none)'}`);
this.output.printDebug(`Positional: [${positional.join(', ')}]`);
this.output.printDebug(`Flags: ${JSON.stringify(Object.fromEntries(Object.entries(flags).filter(([k]) => k !== '_')))}`);
this.output.printDebug(`CWD: ${process.cwd()}`);
}
// Run startup update check (non-blocking, silent on skip)
if (!flags.noUpdate && commandPath[0] !== 'update') {
this.checkForUpdatesOnStartup().catch(() => { });
}
// Handle lazy-loaded commands that weren't recognized by the parser
// If commandPath is empty but positional has a command name, check if it's lazy-loadable
if (commandPath.length === 0 && positional.length > 0 && !positional[0].startsWith('-')) {
const potentialCommand = positional[0];
if (hasCommand(potentialCommand)) {
// This is a lazy-loaded command, treat it as the command
commandPath.push(potentialCommand);
positional.shift();
}
}
// No command - show help or suggest correction
if (commandPath.length === 0 || flags.help || flags.h) {
if (commandPath.length > 0) {
// #1791.4 — pass the FULL command path so subcommands like
// `hive-mind spawn --help` render spawn's own options/examples
// instead of falling back to the parent's SUBCOMMANDS list.
await this.showCommandHelp(commandPath);
}
else if (positional.length > 0 && !positional[0].startsWith('-')) {
// First positional looks like an attempted command - suggest correction
const attemptedCommand = positional[0];
this.output.printError(`Unknown command: ${attemptedCommand}`);
const availableCommands = Array.from(new Set([...commands.map(c => c.name), ...getCommandNames()]));
const { message } = suggestCommand(attemptedCommand, availableCommands);
this.output.writeln(this.output.dim(` ${message}`));
process.exit(1);
}
else {
await this.showHelp();
}
return;
}
// Find and execute command
const commandName = commandPath[0];
// First check the parser's registry (for dynamically registered commands)
// Then fall back to the static registry, then try lazy loading
let command = this.parser.getCommand(commandName) || getCommand(commandName);
// If not found in sync registry, try lazy loading
if (!command && hasCommand(commandName)) {
command = await getCommandAsync(commandName);
}
if (!command) {
this.output.printError(`Unknown command: ${commandName}`);
// Smart suggestions - include lazy-loadable commands in suggestions
const availableCommands = Array.from(new Set([...commands.map(c => c.name), ...getCommandNames()]));
const { message } = suggestCommand(commandName, availableCommands);
this.output.writeln(this.output.dim(` ${message}`));
process.exit(1);
}
// Handle subcommand (supports nested subcommands)
let targetCommand = command;
let subcommandArgs = positional;
// Process command path (e.g., ['hooks', 'worker', 'list'])
// Note: When parser includes subcommand in commandPath, positional already excludes it
if (commandPath.length > 1 && command.subcommands) {
const subcommandName = commandPath[1];
const subcommand = command.subcommands.find(sc => sc.name === subcommandName || sc.aliases?.includes(subcommandName));
if (subcommand) {
targetCommand = subcommand;
// Parser already extracted subcommand from positional, so use as-is
subcommandArgs = positional;
// Check for nested subcommand (level 2)
if (commandPath.length > 2 && subcommand.subcommands) {
const nestedName = commandPath[2];
const nestedSubcommand = subcommand.subcommands.find(sc => sc.name === nestedName || sc.aliases?.includes(nestedName));
if (nestedSubcommand) {
targetCommand = nestedSubcommand;
// Parser already extracted nested subcommand too
subcommandArgs = positional;
}
}
}
}
else if (positional.length > 0 && command.subcommands) {
// Check if first positional is a subcommand
const subcommandName = positional[0];
const subcommand = command.subcommands.find(sc => sc.name === subcommandName || sc.aliases?.includes(subcommandName));
if (subcommand) {
targetCommand = subcommand;
subcommandArgs = positional.slice(1);
// Check for nested subcommand (level 2 from positional)
if (subcommandArgs.length > 0 && subcommand.subcommands) {
const nestedName = subcommandArgs[0];
const nestedSubcommand = subcommand.subcommands.find(sc => sc.name === nestedName || sc.aliases?.includes(nestedName));
if (nestedSubcommand) {
targetCommand = nestedSubcommand;
subcommandArgs = subcommandArgs.slice(1);
}
}
}
}
// Validate flags
const validationErrors = this.parser.validateFlags(flags, targetCommand);
if (validationErrors.length > 0) {
for (const error of validationErrors) {
this.output.printError(error);
}
process.exit(1);
}
// Build context
const ctx = {
args: subcommandArgs,
flags,
config: await this.loadConfig(flags.config),
cwd: process.cwd(),
interactive: this.interactive && !flags.quiet
};
// Execute command
if (targetCommand.action) {
if (this.output.isVerbose()) {
this.output.printDebug(`Executing: ${targetCommand.name}`);
}
const startTime = Date.now();
const result = await targetCommand.action(ctx);
if (this.output.isVerbose()) {
this.output.printDebug(`Completed in ${Date.now() - startTime}ms`);
}
if (result && !result.success) {
process.exit(result.exitCode || 1);
}
}
else {
// No action - show command help (full path so nested subcommands work)
await this.showCommandHelp(commandPath);
}
}
catch (error) {
// Don't re-handle if this is a process.exit error (from mocked tests)
const errorMessage = error.message;
if (errorMessage && errorMessage.startsWith('process.exit:')) {
throw error; // Re-throw so tests can capture the exit code
}
this.handleError(error);
}
}
/**
* Show main help
*/
async showHelp() {
this.output.writeln();
this.output.writeln(this.output.bold(`${this.name} v${this.version}`));
this.output.writeln(this.output.dim(this.description));
this.output.writeln();
this.output.writeln(this.output.bold('USAGE:'));
this.output.writeln(` ${this.name} <command> [subcommand] [options]`);
this.output.writeln();
// PERF-03: Load all commands by category (lazy-loaded on demand)
const categories = await getCommandsByCategory();
// Primary Commands
this.output.writeln(this.output.bold('PRIMARY COMMANDS:'));
for (const cmd of categories.primary) {
if (cmd.hidden)
continue;
const name = cmd.name.padEnd(12);
this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
}
this.output.writeln();
// Advanced Commands
if (categories.advanced.length > 0) {
this.output.writeln(this.output.bold('ADVANCED COMMANDS:'));
for (const cmd of categories.advanced) {
if (cmd.hidden)
continue;
const name = cmd.name.padEnd(12);
this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
}
this.output.writeln();
}
// Utility Commands
if (categories.utility.length > 0) {
this.output.writeln(this.output.bold('UTILITY COMMANDS:'));
for (const cmd of categories.utility) {
if (cmd.hidden)
continue;
const name = cmd.name.padEnd(12);
this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
}
this.output.writeln();
}
// Analysis Commands
if (categories.analysis.length > 0) {
this.output.writeln(this.output.bold('ANALYSIS COMMANDS:'));
for (const cmd of categories.analysis) {
if (cmd.hidden)
continue;
const name = cmd.name.padEnd(12);
this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
}
this.output.writeln();
}
// Management Commands
if (categories.management.length > 0) {
this.output.writeln(this.output.bold('MANAGEMENT COMMANDS:'));
for (const cmd of categories.management) {
if (cmd.hidden)
continue;
const name = cmd.name.padEnd(12);
this.output.writeln(` ${this.output.highlight(name)} ${cmd.description}`);
}
this.output.writeln();
}
this.output.writeln(this.output.bold('GLOBAL OPTIONS:'));
for (const opt of this.parser.getGlobalOptions()) {
const flags = opt.short ? `-${opt.short}, --${opt.name}` : ` --${opt.name}`;
this.output.writeln(` ${flags.padEnd(25)} ${opt.description}`);
}
this.output.writeln();
this.output.writeln(this.output.bold('V3 FEATURES:'));
this.output.printList([
'15-agent hierarchical mesh coordination',
'AgentDB with HNSW indexing (150x-12,500x faster)',
'Flash Attention (2.49x-7.47x speedup)',
'Unified SwarmCoordinator engine',
'Event-sourced state management',
'Domain-Driven Design architecture'
]);
this.output.writeln();
this.output.writeln(this.output.bold('EXAMPLES:'));
this.output.writeln(` ${this.name} agent spawn -t coder # Spawn a coder agent`);
this.output.writeln(` ${this.name} swarm init --v3-mode # Initialize V3 swarm`);
this.output.writeln(` ${this.name} memory search -q "auth patterns" # Semantic search`);
this.output.writeln(` ${this.name} mcp start # Start MCP server`);
this.output.writeln();
this.output.writeln(this.output.dim(`Run "${this.name} <command> --help" for command help`));
this.output.writeln();
this.output.writeln(this.output.dim('Created with ❤️ by ruv.io'));
this.output.writeln();
}
/**
* Show command-specific help.
*
* #1791.4 — accepts a FULL command path (e.g. ['hive-mind', 'spawn']) and
* walks subcommands so nested invocations show the leaf's own options /
* examples instead of always rendering the parent's SUBCOMMANDS list.
*/
async showCommandHelp(commandPathOrName) {
const commandPath = Array.isArray(commandPathOrName) ? commandPathOrName : [commandPathOrName];
if (commandPath.length === 0) {
await this.showHelp();
return;
}
const rootName = commandPath[0];
// Try sync first, then lazy load
let command = getCommand(rootName);
if (!command && hasCommand(rootName)) {
command = await getCommandAsync(rootName);
}
if (!command) {
this.output.printError(`Unknown command: ${rootName}`);
return;
}
// Walk into subcommands following the path so `hive-mind spawn --help`
// renders spawn's help, not hive-mind's parent help. We use a non-null
// local (`current`) instead of reassigning the optional `command` so
// TS can prove the value is defined for the rest of the function.
let current = command;
const titleParts = [current.name];
for (let i = 1; i < commandPath.length; i++) {
const subName = commandPath[i];
const sub = current.subcommands?.find(sc => sc.name === subName || sc.aliases?.includes(subName));
if (!sub)
break; // unknown leaf — fall back to last known
current = sub;
titleParts.push(sub.name);
}
this.output.writeln();
this.output.writeln(this.output.bold(`${this.name} ${titleParts.join(' ')}`));
this.output.writeln(current.description);
this.output.writeln();
// Subcommands
if (current.subcommands && current.subcommands.length > 0) {
this.output.writeln(this.output.bold('SUBCOMMANDS:'));
for (const sub of current.subcommands) {
if (sub.hidden)
continue;
const name = sub.name.padEnd(15);
const aliases = sub.aliases ? this.output.dim(` (${sub.aliases.join(', ')})`) : '';
this.output.writeln(` ${this.output.highlight(name)} ${sub.description}${aliases}`);
}
this.output.writeln();
}
// Options
if (current.options && current.options.length > 0) {
this.output.writeln(this.output.bold('OPTIONS:'));
for (const opt of current.options) {
const flags = opt.short ? `-${opt.short}, --${opt.name}` : ` --${opt.name}`;
const required = opt.required ? this.output.error(' (required)') : '';
const defaultVal = opt.default !== undefined ? this.output.dim(` [default: ${opt.default}]`) : '';
this.output.writeln(` ${flags.padEnd(25)} ${opt.description}${required}${defaultVal}`);
}
this.output.writeln();
}
// Examples
if (current.examples && current.examples.length > 0) {
this.output.writeln(this.output.bold('EXAMPLES:'));
for (const example of current.examples) {
this.output.writeln(` ${this.output.dim('$')} ${example.command}`);
this.output.writeln(` ${this.output.dim(example.description)}`);
}
this.output.writeln();
}
}
/**
* Show version
*/
showVersion() {
this.output.writeln(`${this.name} v${this.version}`);
}
/**
* Check for updates on startup (non-blocking)
* Shows notification if updates are available
*/
async checkForUpdatesOnStartup() {
try {
const result = await runStartupUpdateCheck({ autoUpdate: true });
// Show notifications for available updates that weren't auto-applied
if (result.checked && result.updatesAvailable.length > 0) {
const nonAutoUpdates = result.updatesAvailable.filter(u => !u.shouldAutoUpdate);
if (result.updatesApplied.length > 0) {
this.output.writeln(this.output.dim(`Auto-updated: ${result.updatesApplied.join(', ')}`));
}
if (nonAutoUpdates.length > 0) {
this.output.writeln(this.output.dim(`Updates available: ${nonAutoUpdates.map(u => `${u.package}@${u.latestVersion}`).join(', ')}`));
this.output.writeln(this.output.dim(`Run '${this.name} update check' for details`));
}
}
}
catch {
// Silently fail - don't interrupt CLI usage
}
}
/**
* Load configuration file
*/
async loadConfig(configPath) {
try {
// Import config utilities
const { loadConfig: loadSystemConfig } = await import('@claude-flow/shared');
const { systemConfigToV3Config } = await import('./config-adapter.js');
// Load configuration
const loaded = await loadSystemConfig({
file: configPath,
paths: configPath ? undefined : [process.cwd()],
});
// Convert to V3Config format
const v3Config = systemConfigToV3Config(loaded.config);
// Log warnings if any
if (loaded.warnings && loaded.warnings.length > 0) {
for (const warning of loaded.warnings) {
this.output.printWarning(warning);
}
}
return v3Config;
}
catch (error) {
// Config loading is optional - don't fail if it doesn't exist
if (process.env.DEBUG) {
this.output.writeln(this.output.dim(`Config loading failed: ${error.message}`));
}
return undefined;
}
}
/**
* Handle errors
*/
handleError(error) {
if ('code' in error) {
// CLIError
const cliError = error;
this.output.printError(cliError.message);
if (cliError.details) {
this.output.writeln(this.output.dim(JSON.stringify(cliError.details, null, 2)));
}
process.exit(cliError.exitCode);
}
else {
// Generic error
this.output.printError(error.message);
if (process.env.DEBUG) {
this.output.writeln();
this.output.writeln(this.output.dim(error.stack || ''));
}
process.exit(1);
}
}
}
// =============================================================================
// Module Exports
// =============================================================================
// Types
export * from './types.js';
// Parser
export { CommandParser, commandParser } from './parser.js';
// Output
export { OutputFormatter, output, Progress, Spinner } from './output.js';
// Prompt
export * from './prompt.js';
// Commands (internal use)
export * from './commands/index.js';
// MCP Server management
export { MCPServerManager, createMCPServerManager, getServerManager, startMCPServer, stopMCPServer, getMCPServerStatus, } from './mcp-server.js';
// Memory & Intelligence (V3 Performance Features)
export { initializeMemoryDatabase, generateEmbedding, generateBatchEmbeddings, storeEntry, searchEntries, getHNSWIndex, addToHNSWIndex, searchHNSWIndex, getHNSWStatus, clearHNSWIndex, quantizeInt8, dequantizeInt8, quantizedCosineSim, getQuantizationStats,
// Flash Attention-style batch operations
batchCosineSim, softmaxAttention, topKIndices, flashAttentionSearch, } from './memory/memory-initializer.js';
export { initializeIntelligence, recordStep, recordTrajectory, findSimilarPatterns, getIntelligenceStats, getSonaCoordinator, getReasoningBank, clearIntelligence, benchmarkAdaptation,
// RL loop API
endTrajectoryWithVerdict, distillLearning,
// Pattern persistence API
getAllPatterns, getPatternsByType, flushPatterns, deletePattern, clearAllPatterns, getNeuralDataDir, getPersistenceStatus, } from './memory/intelligence.js';
// EWC++ Consolidation (Prevents Catastrophic Forgetting)
export { EWCConsolidator, getEWCConsolidator, resetEWCConsolidator, consolidatePatterns, recordPatternOutcome, getEWCStats, } from './memory/ewc-consolidation.js';
// SONA Optimizer (Adaptive Routing via Trajectory Learning)
export { SONAOptimizer, getSONAOptimizer, resetSONAOptimizer, processTrajectory, getSuggestion, getSONAStats, } from './memory/sona-optimizer.js';
// Production Hardening
export { ErrorHandler, withErrorHandling, } from './production/error-handler.js';
export { RateLimiter, createRateLimiter, } from './production/rate-limiter.js';
export { withRetry, makeRetryable, } from './production/retry.js';
export { CircuitBreaker, getCircuitBreaker, getAllCircuitStats, resetAllCircuits, } from './production/circuit-breaker.js';
export { MonitoringHooks, createMonitor, getMonitor, } from './production/monitoring.js';
// Default export
export default CLI;
//# sourceMappingURL=index.js.map