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
418 lines • 14.9 kB
JavaScript
/**
* V3 CLI Start Command
* System startup for Claude Flow orchestration
*/
import { output } from '../output.js';
import { confirm } from '../prompt.js';
import { callMCPTool, MCPClientError } from '../mcp-client.js';
import * as fs from 'fs';
import * as path from 'path';
// Default configuration
const DEFAULT_PORT = 3000;
const DEFAULT_TOPOLOGY = 'hierarchical-mesh';
const DEFAULT_MAX_AGENTS = 15;
// Check if project is initialized
function isInitialized(cwd) {
const configPath = path.join(cwd, '.claude-flow', 'config.yaml');
return fs.existsSync(configPath);
}
// Simple YAML parser for config (basic implementation)
function parseSimpleYaml(content) {
const result = {};
const lines = content.split('\n');
const stack = [
{ indent: -1, obj: result }
];
for (const line of lines) {
// Skip comments and empty lines
if (line.trim().startsWith('#') || line.trim() === '')
continue;
const match = line.match(/^(\s*)(\w+):\s*(.*)$/);
if (!match)
continue;
const indent = match[1].length;
const key = match[2];
let value = match[3].trim();
// Parse value
if (value === '' || value === undefined) {
value = {};
}
else if (value === 'true') {
value = true;
}
else if (value === 'false') {
value = false;
}
else if (value === 'null') {
value = null;
}
else if (!isNaN(Number(value)) && value !== '') {
value = Number(value);
}
else if (typeof value === 'string' && value.startsWith('"') && value.endsWith('"')) {
value = value.slice(1, -1);
}
// Find parent based on indentation
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
stack.pop();
}
const parent = stack[stack.length - 1].obj;
if (typeof value === 'object' && value !== null) {
parent[key] = value;
stack.push({ indent, obj: value, key });
}
else {
parent[key] = value;
}
}
return result;
}
// Load configuration
function loadConfig(cwd) {
const configPath = path.join(cwd, '.claude-flow', 'config.yaml');
if (!fs.existsSync(configPath))
return null;
try {
const content = fs.readFileSync(configPath, 'utf-8');
return parseSimpleYaml(content);
}
catch {
return null;
}
}
// Main start action
const startAction = async (ctx) => {
const daemon = ctx.flags.daemon;
const port = ctx.flags.port || DEFAULT_PORT;
const topology = ctx.flags.topology || DEFAULT_TOPOLOGY;
const skipMcp = ctx.flags['skip-mcp'];
const cwd = ctx.cwd;
// Check initialization
if (!isInitialized(cwd)) {
output.printError('Claude Flow is not initialized in this directory');
output.printInfo('Run "claude-flow init" first to initialize');
return { success: false, exitCode: 1 };
}
// Load configuration
const config = loadConfig(cwd);
const swarmConfig = config?.swarm || {};
const mcpConfig = config?.mcp || {};
const finalTopology = topology || swarmConfig.topology || DEFAULT_TOPOLOGY;
const maxAgents = swarmConfig.maxAgents || DEFAULT_MAX_AGENTS;
const autoStartMcp = mcpConfig.autoStart !== false && !skipMcp;
const mcpPort = port || mcpConfig.serverPort || DEFAULT_PORT;
output.writeln();
output.writeln(output.bold('Starting Claude Flow V3'));
output.writeln();
const spinner = output.createSpinner({ text: 'Initializing system...' });
try {
// Step 1: Initialize swarm
spinner.start();
spinner.setText('Initializing V3 swarm...');
const swarmResult = await callMCPTool('swarm_init', {
topology: finalTopology,
maxAgents,
autoScaling: swarmConfig.autoScale !== false,
v3Mode: true
});
spinner.succeed(`Swarm initialized (${finalTopology})`);
// Step 2: Start MCP server if configured
let mcpResult = null;
if (autoStartMcp) {
spinner.setText('Starting MCP server...');
spinner.start();
try {
mcpResult = await callMCPTool('mcp_start', {
port: mcpPort,
transport: mcpConfig.transportType || 'stdio',
tools: mcpConfig.tools || ['agent', 'swarm', 'memory', 'task']
});
spinner.succeed(`MCP server started on port ${mcpPort}`);
}
catch (error) {
spinner.fail('MCP server failed to start');
output.printWarning(error instanceof MCPClientError
? error.message
: String(error));
// Continue without MCP
}
}
// Step 3: Run health check
spinner.setText('Running health checks...');
spinner.start();
const healthResult = await callMCPTool('swarm_health', {
swarmId: swarmResult.swarmId
});
if (healthResult.status === 'healthy') {
spinner.succeed('Health checks passed');
}
else {
spinner.fail(`Health check: ${healthResult.status}`);
}
// Success output
output.writeln();
output.printSuccess('Claude Flow V3 is running!');
output.writeln();
// Status display
output.printBox([
`Swarm ID: ${swarmResult.swarmId}`,
`Topology: ${finalTopology}`,
`Max Agents: ${maxAgents}`,
`MCP Server: ${autoStartMcp ? `localhost:${mcpPort}` : 'disabled'}`,
`Mode: ${daemon ? 'Daemon' : 'Foreground'}`,
`Health: ${healthResult.status}`
].join('\n'), 'System Status');
output.writeln();
output.writeln(output.bold('Quick Commands:'));
output.printList([
`${output.highlight('claude-flow status')} - View system status`,
`${output.highlight('claude-flow agent spawn -t coder')} - Spawn an agent`,
`${output.highlight('claude-flow swarm status')} - View swarm details`,
`${output.highlight('claude-flow stop')} - Stop the system`
]);
// Daemon mode
if (daemon) {
output.writeln();
output.printInfo('Running in daemon mode. Use "claude-flow stop" to stop.');
// Store PID for daemon management
const daemonPidPath = path.join(cwd, '.claude-flow', 'daemon.pid');
fs.writeFileSync(daemonPidPath, String(process.pid));
// Detach from parent process for true daemon behavior
if (process.platform !== 'win32') {
// Unix-like systems: create new session
try {
process.stdin.unref?.();
process.stdout.unref?.();
process.stderr.unref?.();
}
catch {
// Ignore errors if streams can't be unref'd
}
}
// Keep process alive in daemon mode
const keepAlive = setInterval(() => {
// Heartbeat - check if we should still be running
if (!fs.existsSync(daemonPidPath)) {
clearInterval(keepAlive);
process.exit(0);
}
}, 5000);
keepAlive.unref(); // Don't prevent process from exiting if no other work
}
const result = {
swarmId: swarmResult.swarmId,
topology: finalTopology,
maxAgents,
mcp: mcpResult ? {
port: mcpPort,
transport: mcpConfig.transportType || 'stdio'
} : null,
health: healthResult.status,
daemon,
startedAt: new Date().toISOString()
};
if (ctx.flags.format === 'json') {
output.printJson(result);
}
return { success: true, data: result };
}
catch (error) {
spinner.fail('Startup failed');
if (error instanceof MCPClientError) {
output.printError(`Failed to start: ${error.message}`);
}
else {
output.printError(`Unexpected error: ${String(error)}`);
}
return { success: false, exitCode: 1 };
}
};
// Stop subcommand
const stopCommand = {
name: 'stop',
description: 'Stop the Claude Flow system',
options: [
{
name: 'force',
short: 'f',
description: 'Force stop without graceful shutdown',
type: 'boolean',
default: false
},
{
name: 'timeout',
description: 'Shutdown timeout in seconds',
type: 'number',
default: 30
}
],
action: async (ctx) => {
const force = ctx.flags.force;
const timeout = ctx.flags.timeout;
output.writeln();
output.writeln(output.bold('Stopping Claude Flow'));
output.writeln();
if (!force && ctx.interactive) {
const confirmed = await confirm({
message: 'Are you sure you want to stop Claude Flow?',
default: false
});
if (!confirmed) {
output.printInfo('Operation cancelled');
return { success: true };
}
}
const spinner = output.createSpinner({ text: 'Stopping system...' });
spinner.start();
try {
// Stop MCP server
spinner.setText('Stopping MCP server...');
try {
await callMCPTool('mcp_stop', { graceful: !force, timeout });
spinner.succeed('MCP server stopped');
}
catch {
spinner.fail('MCP server was not running');
}
// Stop swarm
spinner.setText('Stopping swarm...');
spinner.start();
try {
await callMCPTool('swarm_stop', {
graceful: !force,
timeout,
saveState: true
});
spinner.succeed('Swarm stopped');
}
catch {
spinner.fail('Swarm was not running');
}
// Clean up daemon PID
const daemonPidPath = path.join(ctx.cwd, '.claude-flow', 'daemon.pid');
if (fs.existsSync(daemonPidPath)) {
fs.unlinkSync(daemonPidPath);
}
output.writeln();
output.printSuccess('Claude Flow stopped successfully');
return {
success: true,
data: { stopped: true, force, stoppedAt: new Date().toISOString() }
};
}
catch (error) {
spinner.fail('Stop failed');
output.printError(`Failed to stop: ${error instanceof Error ? error.message : String(error)}`);
return { success: false, exitCode: 1 };
}
}
};
// Restart subcommand
const restartCommand = {
name: 'restart',
description: 'Restart the Claude Flow system',
options: [
{
name: 'force',
short: 'f',
description: 'Force restart',
type: 'boolean',
default: false
}
],
action: async (ctx) => {
output.writeln();
output.writeln(output.bold('Restarting Claude Flow'));
output.writeln();
// Stop first
const stopCtx = { ...ctx, flags: { ...ctx.flags } };
const stopResult = await stopCommand.action(stopCtx);
if (stopResult && !stopResult.success) {
output.printWarning('Stop failed, attempting to start anyway...');
}
// Wait briefly
await new Promise(resolve => setTimeout(resolve, 1000));
// Start again
const startResult = await startAction(ctx);
return {
success: startResult.success,
data: {
restarted: startResult.success,
restartedAt: new Date().toISOString()
}
};
}
};
// Quick start subcommand
const quickCommand = {
name: 'quick',
aliases: ['q'],
description: 'Quick start with default settings',
action: async (ctx) => {
// Initialize if needed
if (!isInitialized(ctx.cwd)) {
output.printInfo('Project not initialized, running init first...');
output.writeln();
// Call init with minimal settings
const { initCommand } = await import('./init.js');
const initCtx = {
...ctx,
flags: { ...ctx.flags, minimal: true }
};
await initCommand.action(initCtx);
output.writeln();
}
// Start with defaults
return startAction({
...ctx,
flags: { ...ctx.flags, topology: 'mesh' }
});
}
};
// Main start command
export const startCommand = {
name: 'start',
description: 'Start the Claude Flow orchestration system',
subcommands: [stopCommand, restartCommand, quickCommand],
options: [
{
name: 'daemon',
short: 'd',
description: 'Run as daemon in background',
type: 'boolean',
default: false
},
{
name: 'port',
short: 'p',
description: 'MCP server port',
type: 'number',
default: DEFAULT_PORT
},
{
name: 'topology',
short: 't',
description: 'Swarm topology (hierarchical-mesh, mesh, hierarchical, ring, star)',
type: 'string',
choices: ['hierarchical-mesh', 'mesh', 'hierarchical', 'ring', 'star']
},
{
name: 'skip-mcp',
description: 'Skip starting MCP server',
type: 'boolean',
default: false
}
],
examples: [
{ command: 'claude-flow start', description: 'Start with configuration defaults' },
{ command: 'claude-flow start --daemon', description: 'Start as background daemon' },
{ command: 'claude-flow start --port 3001', description: 'Start MCP on custom port' },
{ command: 'claude-flow start --topology mesh', description: 'Start with mesh topology' },
{ command: 'claude-flow start --skip-mcp', description: 'Start without MCP server' },
{ command: 'claude-flow start quick', description: 'Quick start with defaults' },
{ command: 'claude-flow start stop', description: 'Stop the running system' }
],
action: startAction
};
export default startCommand;
//# sourceMappingURL=start.js.map