claude-flow
Version:
Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)
309 lines (265 loc) • 8.31 kB
text/typescript
import { getErrorMessage } from '../utils/error-handler.js';
/**
* Claude-Flow CLI - Core implementation using Node.js
*/
import chalk from "chalk";
import fs from "fs-extra";
import path from "path";
export const VERSION = "1.0.45";
interface CommandContext {
args: string[];
flags: Record<string, unknown>;
config?: Record<string, unknown> | undefined;
}
interface Command {
name: string;
description: string;
aliases?: string[];
subcommands?: Command[];
action?: (ctx: CommandContext) => Promise<void> | void;
options?: Option[];
}
interface Option {
name: string;
short?: string;
description: string;
type?: "string" | "boolean" | "number";
default?: unknown;
required?: boolean;
}
class CLI {
private commands: Map<string, Command> = new Map();
private globalOptions: Option[] = [
{
name: "help",
short: "h",
description: "Show help",
type: "boolean",
},
{
name: "version",
short: "v",
description: "Show version",
type: "boolean",
},
{
name: "config",
short: "c",
description: "Path to configuration file",
type: "string",
},
{
name: "verbose",
description: "Enable verbose logging",
type: "boolean",
},
{
name: "log-level",
description: "Set log level (debug, info, warn, error)",
type: "string",
default: "info",
},
];
constructor(private name: string, private description: string) {}
command(cmd: Command): this {
// Handle both our Command interface and Commander.js Command objects
const cmdName = typeof cmd.name === 'function' ? cmd.name() : cmd.name;
this.commands.set(cmdName, cmd);
if (cmd.aliases && typeof cmd.aliases[Symbol.iterator] === 'function') {
for (const alias of cmd.aliases) {
this.commands.set(alias, cmd);
}
}
return this;
}
async run(args = process.argv.slice(2)): Promise<void> {
// Parse arguments manually since we're replacing the Deno parse function
const flags = this.parseArgs(args);
if (flags.version || flags.v) {
console.log(`${this.name} v${VERSION}`);
return;
}
const commandName = flags._[0]?.toString() || "";
if (!commandName || flags.help || flags.h) {
this.showHelp();
return;
}
const command = this.commands.get(commandName);
if (!command) {
console.error(chalk.red(`Unknown command: ${commandName}`));
console.log(`Run "${this.name} help" for available commands`);
process.exit(1);
}
const ctx: CommandContext = {
args: flags._.slice(1).map(String),
flags: flags as Record<string, unknown>,
config: await this.loadConfig(flags.config as string),
};
try {
if (command.action) {
await command.action(ctx);
} else {
console.log(chalk.yellow(`Command '${commandName}' has no action defined`));
}
} catch (error) {
console.error(chalk.red(`Error executing command '${commandName}':`), (error as Error).message);
if (flags.verbose) {
console.error(error);
}
process.exit(1);
}
}
private parseArgs(args: string[]): Record<string, any> {
const result: Record<string, any> = { _: [] };
let i = 0;
while (i < args.length) {
const arg = args[i];
if (arg.startsWith('--')) {
const key = arg.slice(2);
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
result[key] = args[i + 1];
i += 2;
} else {
result[key] = true;
i++;
}
} else if (arg.startsWith('-')) {
const key = arg.slice(1);
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
result[key] = args[i + 1];
i += 2;
} else {
result[key] = true;
i++;
}
} else {
result._.push(arg);
i++;
}
}
return result;
}
private async loadConfig(configPath?: string): Promise<Record<string, unknown> | undefined> {
const configFile = configPath || "claude-flow.config.json";
try {
const content = await fs.readFile(configFile, 'utf8');
return JSON.parse(content);
} catch {
return undefined;
}
}
private getBooleanFlags(): string[] {
const flags: string[] = [];
for (const opt of [...this.globalOptions, ...this.getAllOptions()]) {
if (opt.type === "boolean") {
flags.push(opt.name);
if (opt.short) flags.push(opt.short);
}
}
return flags;
}
private getStringFlags(): string[] {
const flags: string[] = [];
for (const opt of [...this.globalOptions, ...this.getAllOptions()]) {
if (opt.type === "string" || opt.type === "number") {
flags.push(opt.name);
if (opt.short) flags.push(opt.short);
}
}
return flags;
}
private getAliases(): Record<string, string> {
const aliases: Record<string, string> = {};
for (const opt of [...this.globalOptions, ...this.getAllOptions()]) {
if (opt.short) {
aliases[opt.short] = opt.name;
}
}
return aliases;
}
private getDefaults(): Record<string, unknown> {
const defaults: Record<string, unknown> = {};
for (const opt of [...this.globalOptions, ...this.getAllOptions()]) {
if (opt.default !== undefined) {
defaults[opt.name] = opt.default;
}
}
return defaults;
}
private getAllOptions(): Option[] {
const options: Option[] = [];
for (const cmd of this.commands.values()) {
if (cmd.options) {
options.push(...cmd.options);
}
}
return options;
}
private showHelp(): void {
console.log(`
${chalk.bold(chalk.blue(`🧠 ${this.name} v${VERSION}`))} - ${this.description}
${chalk.bold("USAGE:")}
${this.name} [COMMAND] [OPTIONS]
${chalk.bold("COMMANDS:")}
${this.formatCommands()}
${chalk.bold("GLOBAL OPTIONS:")}
${this.formatOptions(this.globalOptions)}
${chalk.bold("EXAMPLES:")}
${this.name} start # Start orchestrator
${this.name} agent spawn researcher --name "Bot" # Spawn research agent
${this.name} task create research "Analyze data" # Create task
${this.name} config init # Initialize config
${this.name} status # Show system status
For more detailed help on specific commands, use:
${this.name} [COMMAND] --help
Documentation: https://github.com/ruvnet/claude-code-flow
Issues: https://github.com/ruvnet/claude-code-flow/issues
Created by rUv - Built with ❤️ for the Claude community
`);
}
private formatCommands(): string {
const commands = Array.from(new Set(this.commands.values()));
return commands
.filter(cmd => cmd && cmd.name) // Filter out invalid commands
.map(cmd => ` ${String(cmd.name).padEnd(20)} ${cmd.description || ''}`)
.join("\n");
}
private formatOptions(options: Option[]): string {
return options
.map(opt => {
const flags = opt.short ? `-${opt.short}, --${opt.name}` : ` --${opt.name}`;
return ` ${flags.padEnd(25)} ${opt.description}`;
})
.join("\n");
}
}
// Helper functions
function success(message: string): void {
console.log(chalk.green(`✅ ${message}`));
}
function error(message: string): void {
console.error(chalk.red(`❌ ${message}`));
}
function warning(message: string): void {
console.warn(chalk.yellow(`⚠️ ${message}`));
}
function info(message: string): void {
console.log(chalk.blue(`ℹ️ ${message}`));
}
// Export for use in other modules
export { CLI, success, error, warning, info };
export type { Command, CommandContext, Option };
// Main CLI setup if running directly
async function main() {
if (process.argv[1] && (process.argv[1].endsWith('cli-core.js') || process.argv[1].endsWith('cli-core.ts'))) {
const cli = new CLI("claude-flow", "Advanced AI Agent Orchestration System");
// Import and register all commands
const { setupCommands } = await import("./commands/index.js");
setupCommands(cli);
// Run the CLI
await cli.run();
}
}
// Execute main if this is the entry point
main().catch(console.error);