@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
485 lines • 20.4 kB
JavaScript
/**
* Unified start command implementation with robust service management
*/
import { Command } from "../../cliffy-compat.js";
import chalk from "chalk";
import { ProcessManager } from "./process-manager.js";
import { ProcessUI } from "./process-ui.js";
import { SystemMonitor } from "./system-monitor.js";
import { eventBus } from "../../../core/event-bus.js";
import { logger } from "../../../core/logger.js";
// import { formatDuration } from "../../formatter.js"; // Reserved for duration formatting
import inquirer from "inquirer";
import { promises as fs } from "node:fs";
// Color compatibility
const colors = {
gray: chalk.gray,
yellow: chalk.yellow,
red: chalk.red,
green: chalk.green,
cyan: chalk.cyan,
blue: chalk.blue,
bold: chalk.bold,
white: chalk.white,
};
// Confirm prompt wrapper
const Confirm = async (message) => {
const answer = await inquirer.prompt([{
type: "confirm",
name: "result",
message,
}]);
return answer.result;
};
// Extract the start action logic into a separate function for reuse
export async function startAction(options) {
console.log(colors.cyan("🧠 Claude-Flow Orchestration System"));
console.log(colors.gray("─".repeat(60)));
try {
// Configure cleaner logging for start command
await logger.configure({
level: options.verbose ? "debug" : "info",
format: options.verbose ? "json" : "text", // Use text format by default for cleaner output
destination: "console",
});
// Initialize process manager with timeout
const processManager = new ProcessManager();
console.log(colors.blue("Initializing system components..."));
const initPromise = processManager.initialize(options.config);
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Initialization timeout")), (options.timeout ?? 30) * 1000));
await Promise.race([initPromise, timeoutPromise]);
// Check if already running
if (!options.force && await isSystemRunning()) {
console.log(colors.yellow("⚠ Claude-Flow is already running"));
const shouldContinue = await Confirm("Stop existing instance and restart?");
if (!shouldContinue) {
console.log(colors.gray("Use --force to override or \"claude-flow stop\" first"));
return;
}
await stopExistingInstance();
}
// Perform pre-flight checks
if (options.healthCheck) {
console.log(colors.blue("Running pre-flight health checks..."));
await performHealthChecks();
}
// Initialize system monitor with enhanced monitoring
const systemMonitor = new SystemMonitor(processManager);
systemMonitor.start();
// Setup system event handlers
setupSystemEventHandlers(processManager, systemMonitor, options);
// Override MCP settings from CLI options
if (options.port) {
const mcpProcess = processManager.getProcess("mcp-server");
if (mcpProcess) {
mcpProcess.config = { ...mcpProcess.config, port: options.port };
}
}
// Configure transport settings
if (options.mcpTransport) {
const mcpProcess = processManager.getProcess("mcp-server");
if (mcpProcess) {
mcpProcess.config = { ...mcpProcess.config, transport: options.mcpTransport };
}
}
// Setup event listeners for logging
if (options.verbose) {
setupVerboseLogging(systemMonitor);
}
// Launch UI mode
if (options.ui) {
// Auto-start processes if specified before launching UI
if (options.autoStart) {
console.log(colors.blue("Auto-starting all processes..."));
await startWithProgress(processManager, "all");
console.log(colors.green.bold("✓"), "All processes started");
console.log();
}
const ui = new ProcessUI(processManager);
await ui.start();
// Cleanup on exit
systemMonitor.stop();
await processManager.stopAll();
console.log(colors.green.bold("✓"), "Shutdown complete");
process.exit(0);
}
// Default mode - minimal setup and launch Claude interactive shell
else if (!options.background && !options.daemon) {
console.log(colors.cyan("Starting Claude-Flow and launching Claude..."));
console.log();
// Start only essential services quickly
console.log(colors.blue("Starting essential services..."));
// Just start the most basic services needed
const essentialProcesses = ["event-bus", "memory-manager"];
for (let i = 0; i < essentialProcesses.length; i++) {
const processId = essentialProcesses[i];
console.log(`[${i + 1}/${essentialProcesses.length}] Starting ${processId}...`);
try {
await processManager.startProcess(processId);
console.log(`[${i + 1}/${essentialProcesses.length}] ✓ ${processId} started`);
}
catch (error) {
console.log(colors.yellow(`⚠ ${processId} failed to start, continuing...`));
}
}
console.log(colors.green.bold("✓"), "Essential services ready");
console.log(colors.gray("Launching Claude..."));
console.log();
// Try to launch Claude with minimal overhead
const claudeCommands = [
"/Users/sethford/.bun/bin/claude",
"claude",
"npx claude",
];
let claudeLaunched = false;
for (const cmd of claudeCommands) {
try {
// Quick check if command exists
const { execSync } = await import("child_process");
try {
execSync(`which ${cmd.split(" ")[0]}`, { stdio: "ignore" });
}
catch {
continue;
}
console.log(colors.green(`✓ Launching ${cmd}...`));
// Launch Claude with minimal process overhead
const { spawn } = await import("child_process");
const claudeProcess = spawn(cmd.split(" ")[0], cmd.split(" ").slice(1), {
stdio: "inherit",
detached: false,
});
claudeLaunched = true;
// Minimal waiting - just wait for process to exit
claudeProcess.on("close", () => {
console.log(colors.gray("\nClaude session ended"));
});
claudeProcess.on("error", () => {
console.log(colors.gray("\nClaude session ended"));
});
// Wait for Claude process
await new Promise((resolve) => {
claudeProcess.on("close", resolve);
claudeProcess.on("error", resolve);
});
break;
}
catch (error) {
continue;
}
}
if (!claudeLaunched) {
console.log(colors.yellow("⚠ Claude CLI not found"));
console.log(colors.gray("Install with: npm install -g @anthropic-ai/claude-cli"));
console.log(colors.gray("Services are running - use --ui flag for management"));
}
// Quick cleanup
console.log(colors.yellow("Cleaning up..."));
await processManager.stopAll();
console.log(colors.green("✓ Done"));
process.exit(0);
}
// Background mode - start processes and exit
else if (options.background) {
console.log(colors.yellow("Starting in background mode..."));
console.log(colors.blue("Starting all system processes..."));
await startWithProgress(processManager, "all");
// Wait for services to be fully ready
await waitForSystemReady(processManager);
console.log(colors.green.bold("✓"), "All processes started successfully");
console.log(colors.gray("Claude-Flow services are running in the background"));
console.log(colors.gray("Use \"claude-flow status\" to check system status"));
console.log(colors.gray("Your terminal is ready for use"));
// Exit cleanly, processes will continue running
process.exit(0);
}
// Daemon mode
else if (options.daemon) {
console.log(colors.yellow("Starting in daemon mode..."));
// Auto-start all processes
if (options.autoStart) {
console.log(colors.blue("Starting all system processes..."));
await startWithProgress(processManager, "all");
}
else {
// Start only core processes
console.log(colors.blue("Starting core processes..."));
await startWithProgress(processManager, "core");
}
// Create PID file with metadata
const pid = process.pid || 0;
const pidData = {
pid,
startTime: Date.now(),
config: options.config || "default",
processes: processManager.getAllProcesses().map(p => ({ id: p.id, status: p.status })),
};
await fs.writeFile(".claude-flow.pid", JSON.stringify(pidData, null, 2));
console.log(colors.gray(`Process ID: ${pid}`));
// Wait for services to be fully ready
await waitForSystemReady(processManager);
console.log(colors.green.bold("✓"), "Daemon started successfully");
console.log(colors.gray("Use \"claude-flow status\" to check system status"));
console.log(colors.gray("Use \"claude-flow monitor\" for real-time monitoring"));
// Keep process running
await new Promise(() => { });
}
// Interactive mode (fallback)
else {
console.log(colors.cyan("Starting in interactive mode..."));
console.log();
// Show available options
console.log(colors.white.bold("Quick Actions:"));
console.log(" [1] Start all processes");
console.log(" [2] Start core processes only");
console.log(" [3] Launch process management UI");
console.log(" [4] Show system status");
console.log(" [q] Quit");
console.log();
console.log(colors.gray("Press a key to select an option..."));
// Handle user input
const decoder = new TextDecoder();
while (true) {
const buf = new Uint8Array(1);
// Note: This would need to be adapted for Node.js stdin handling
// For now, we'll break out of the loop
break;
}
}
}
catch (error) {
console.error(colors.red("Failed to start Claude-Flow:"), error.message);
if (options.verbose) {
console.error(colors.gray("Stack trace:"));
console.error(error);
}
await cleanupOnFailure();
process.exit(1);
}
}
export const startCommand = new Command()
.name("start")
.description("Start the Claude-Flow orchestration system")
.option("-d, --daemon", "Run as daemon in background")
.option("-b, --background", "Start all processes and exit (leaves terminal free)")
.option("-p, --port <port>", "MCP server port", "3000")
.option("--mcp-transport <transport>", "MCP transport type (stdio, http)", "stdio")
.option("-u, --ui", "Launch interactive process management UI")
.option("-v, --verbose", "Enable verbose logging")
.option("--auto-start", "Automatically start all processes")
.option("--config <path>", "Configuration file path")
.option("--force", "Force start even if already running")
.option("--health-check", "Perform health checks before starting")
.option("--timeout <seconds>", "Startup timeout in seconds", "60")
.action(startAction);
// Enhanced helper functions
async function isSystemRunning() {
try {
const pidData = await fs.readFile(".claude-flow.pid", "utf-8");
const data = JSON.parse(pidData);
// Check if process is still running
try {
process.kill(data.pid, 0); // Check if process exists
return true; // Process exists
}
catch {
return false; // Process not found
}
}
catch {
return false; // No PID file
}
}
async function stopExistingInstance() {
try {
const pidData = await fs.readFile(".claude-flow.pid", "utf-8");
const data = JSON.parse(pidData);
console.log(colors.yellow("Stopping existing instance..."));
try {
process.kill(data.pid, "SIGTERM");
}
catch {
// Process might already be stopped
}
// Wait for graceful shutdown
await new Promise(resolve => setTimeout(resolve, 2000));
// Force kill if still running
try {
process.kill(data.pid, "SIGKILL");
}
catch {
// Process already stopped
}
await fs.unlink(".claude-flow.pid").catch(() => { });
console.log(colors.green("✓ Existing instance stopped"));
}
catch (error) {
console.warn(colors.yellow("Warning: Could not stop existing instance"), error.message);
}
}
async function performHealthChecks() {
const checks = [
{ name: "Disk Space", check: checkDiskSpace },
{ name: "Memory Available", check: checkMemoryAvailable },
{ name: "Network Connectivity", check: checkNetworkConnectivity },
{ name: "Required Dependencies", check: checkDependencies },
];
for (const { name, check } of checks) {
try {
console.log(colors.gray(` Checking ${name}...`));
await check();
console.log(colors.green(` ✓ ${name} OK`));
}
catch (error) {
console.log(colors.red(` ✗ ${name} Failed: ${error.message}`));
throw error;
}
}
}
async function checkDiskSpace() {
// Basic disk space check - would need platform-specific implementation
const stats = await fs.stat(".");
if (!stats.isDirectory) {
throw new Error("Current directory is not accessible");
}
}
async function checkMemoryAvailable() {
// Memory check - use Node.js process.memoryUsage()
const memoryInfo = process.memoryUsage();
if (memoryInfo.heapUsed > 500 * 1024 * 1024) { // 500MB threshold
throw new Error("High memory usage detected");
}
}
async function checkNetworkConnectivity() {
// Basic network check
try {
const response = await fetch("https://httpbin.org/status/200", {
method: "GET",
signal: AbortSignal.timeout(5000),
});
if (!response.ok) {
throw new Error(`Network check failed: ${response.status}`);
}
}
catch {
console.log(colors.yellow(" ⚠ Network connectivity check skipped (offline mode?)"));
}
}
async function checkDependencies() {
// Check for required directories and files
const requiredDirs = [".claude-flow", "memory", "logs"];
for (const dir of requiredDirs) {
try {
await fs.mkdir(dir, { recursive: true });
}
catch (_error) {
throw new Error(`Cannot create required directory: ${dir}`);
}
}
}
function setupSystemEventHandlers(processManager, systemMonitor, options) {
// Handle graceful shutdown signals
const shutdownHandler = async () => {
console.log(`\n${colors.yellow("Received shutdown signal, shutting down gracefully...")}`);
systemMonitor.stop();
await processManager.stopAll();
await cleanupOnShutdown();
console.log(colors.green("✓ Shutdown complete"));
process.exit(0);
};
// Setup signal handlers for Node.js
process.on("SIGINT", shutdownHandler);
process.on("SIGTERM", shutdownHandler);
// Setup verbose logging if requested
if (options.verbose) {
setupVerboseLogging(systemMonitor);
}
// Monitor for critical errors
processManager.on("processError", (event) => {
console.error(colors.red(`Process error in ${event.processId}:`), event.error.message);
if (event.processId === "orchestrator") {
console.error(colors.red.bold("Critical process failed, initiating recovery..."));
// Could implement auto-recovery logic here
}
});
}
async function startWithProgress(processManager, mode) {
const processes = mode === "all"
? ["event-bus", "memory-manager", "terminal-pool", "coordinator", "mcp-server", "orchestrator"]
: ["event-bus", "memory-manager", "mcp-server"];
for (let i = 0; i < processes.length; i++) {
const processId = processes[i];
const progress = `[${i + 1}/${processes.length}]`;
console.log(colors.gray(`${progress} Starting ${processId}...`));
try {
await processManager.startProcess(processId);
console.log(colors.green(`${progress} ✓ ${processId} started`));
}
catch (error) {
console.log(colors.red(`${progress} ✗ ${processId} failed: ${error.message}`));
if (processId === "orchestrator" || processId === "mcp-server") {
throw error; // Critical processes
}
}
// Brief delay between starts
if (i < processes.length - 1) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
}
async function waitForSystemReady(processManager) {
console.log(colors.blue("Waiting for system to be ready..."));
const maxWait = 30000; // 30 seconds
const checkInterval = 1000; // 1 second
let waited = 0;
while (waited < maxWait) {
const stats = processManager.getSystemStats();
if (stats.errorProcesses === 0 && stats.runningProcesses >= 3) {
console.log(colors.green("✓ System ready"));
return;
}
await new Promise(resolve => setTimeout(resolve, checkInterval));
waited += checkInterval;
}
console.log(colors.yellow("⚠ System startup completed but some processes may not be fully ready"));
}
async function cleanupOnFailure() {
try {
await fs.unlink(".claude-flow.pid").catch(() => { });
console.log(colors.gray("Cleaned up PID file"));
}
catch {
// Ignore cleanup errors
}
}
async function cleanupOnShutdown() {
try {
await fs.unlink(".claude-flow.pid").catch(() => { });
console.log(colors.gray("Cleaned up PID file"));
}
catch {
// Ignore cleanup errors
}
}
function setupVerboseLogging(monitor) {
// Enhanced verbose logging
console.log(colors.gray("Verbose logging enabled"));
// Periodically print system health
setInterval(() => {
console.log();
console.log(colors.cyan("--- System Health Report ---"));
monitor.printSystemHealth();
console.log(colors.cyan("--- End Report ---"));
}, 30000);
// Log critical events
eventBus.on("process:started", (data) => {
console.log(colors.green(`[VERBOSE] Process started: ${data.processId}`));
});
eventBus.on("process:stopped", (data) => {
console.log(colors.yellow(`[VERBOSE] Process stopped: ${data.processId}`));
});
eventBus.on("process:error", (data) => {
console.log(colors.red(`[VERBOSE] Process error: ${data.processId} - ${data.error.message}`));
});
}
//# sourceMappingURL=start-command.js.map