import { spawn, spawnSync } from "child_process"; import { existsSync, readFileSync, writeFileSync, createWriteStream, unlinkSync } from "fs"; import { join } from "path"; // Colors for console output const colors = { green: '\x1b[32m', blue: '\x1b[34m', red: '\x1b[31m', yellow: '\x1b[33m', reset: '\x1b[0m' }; // Load .env file if it exists function loadEnvFile() { const envPath = '.env'; if (existsSync(envPath)) { console.log(`${colors.blue}📄 Loading .env file...${colors.reset}`); const envContent = readFileSync(envPath, 'utf-8'); const lines = envContent.split('\n'); for (const line of lines) { // Skip comments and empty lines if (line.trim().startsWith('#') || !line.trim()) continue; // Parse key=value const match = line.match(/^([^=]+)=(.*)$/); if (match) { const key = match[1].trim(); const value = match[2].trim(); // Only set if not already in environment if (!process.env[key]) { process.env[key] = value; } } } console.log(`${colors.green}✅ Environment variables loaded${colors.reset}`); } } // Helper to check if a command exists function commandExists(command: string): boolean { try { const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [command], { stdio: 'pipe' }); return result.status === 0; } catch { return false; } } // Helper to check if a port is in use function isPortInUse(port: number): boolean { try { const result = spawnSync('lsof', ['-Pi', `:${port}`, '-sTCP:LISTEN', '-t'], { stdio: 'pipe' }); return result.status === 0; } catch { return false; } } // Dev command - Start complete development environment export async function runDevCommand(cliDir: string) { console.log(`${colors.blue}════════════════════════════════════════════════════════${colors.reset}`); console.log(`${colors.blue} Natural Language Agreement Oracle - Quick Setup${colors.reset}`); console.log(`${colors.blue}════════════════════════════════════════════════════════${colors.reset}\n`); // Load .env file first loadEnvFile(); console.log(''); // Check prerequisites console.log(`${colors.blue}📋 Checking prerequisites...${colors.reset}\n`); // Check Bun if (!commandExists('bun')) { console.error(`${colors.red}❌ Bun is not installed${colors.reset}`); console.log('Please install it: https://bun.sh'); process.exit(1); } console.log(`${colors.green}✅ Bun installed${colors.reset}`); // Check Foundry if (!commandExists('forge')) { console.error(`${colors.red}❌ Foundry (forge) is not installed${colors.reset}`); console.log('Please install it: https://book.getfoundry.sh/getting-started/installation'); process.exit(1); } console.log(`${colors.green}✅ Foundry installed${colors.reset}`); // Check Anvil if (!commandExists('anvil')) { console.error(`${colors.red}❌ Anvil is not installed${colors.reset}`); console.log('Please install Foundry: https://book.getfoundry.sh/getting-started/installation'); process.exit(1); } console.log(`${colors.green}✅ Anvil installed${colors.reset}`); // Check LLM API keys const hasOpenAI = !!process.env.OPENAI_API_KEY; const hasAnthropic = !!process.env.ANTHROPIC_API_KEY; const hasOpenRouter = !!process.env.OPENROUTER_API_KEY; const hasPerplexity = !!process.env.PERPLEXITY_API_KEY; if (!hasOpenAI && !hasAnthropic && !hasOpenRouter && !hasPerplexity) { console.error(`${colors.red}❌ No LLM provider API key set${colors.reset}`); console.log('Please add at least one API key to your environment:'); console.log(' export OPENAI_API_KEY=sk-...'); console.log(' export ANTHROPIC_API_KEY=sk-ant-...'); console.log(' export OPENROUTER_API_KEY=sk-or-...'); process.exit(1); } if (hasOpenAI) console.log(`${colors.green}✅ OpenAI API key configured${colors.reset}`); if (hasAnthropic) console.log(`${colors.green}✅ Anthropic API key configured${colors.reset}`); if (hasOpenRouter) console.log(`${colors.green}✅ OpenRouter API key configured${colors.reset}`); if (hasPerplexity) console.log(`${colors.green}✅ Perplexity API key configured${colors.reset}`); console.log(''); // Check if Anvil is already running if (isPortInUse(8545)) { console.log(`${colors.yellow}⚠️ Anvil is already running on port 8545${colors.reset}`); console.log(`${colors.blue}Using existing Anvil instance${colors.reset}\n`); } else { // Start Anvil console.log(`${colors.blue}🔨 Starting Anvil...${colors.reset}`); const anvilProcess = spawn('anvil', [], { stdio: ['ignore', 'pipe', 'pipe'], detached: true }); // Save PID writeFileSync('.anvil.pid', anvilProcess.pid!.toString()); // Redirect output to log file const logStream = createWriteStream('anvil.log', { flags: 'a' }); anvilProcess.stdout?.pipe(logStream); anvilProcess.stderr?.pipe(logStream); anvilProcess.unref(); console.log(`${colors.green}✅ Anvil started (PID: ${anvilProcess.pid})${colors.reset}`); console.log(' Logs: tail -f anvil.log'); // Wait for Anvil to be ready await new Promise(resolve => setTimeout(resolve, 3000)); } // Deploy contracts console.log(`\n${colors.blue}📝 Deploying contracts...${colors.reset}\n`); const deployScript = join(cliDir, 'server', 'deploy.js'); const deployResult = spawnSync('bun', ['run', deployScript, '--network', 'localhost', '--rpc-url', 'http://localhost:8545'], { stdio: 'inherit', cwd: process.cwd() }); if (deployResult.status !== 0) { console.error(`${colors.red}❌ Deployment failed${colors.reset}`); process.exit(1); } // Start oracle console.log(`\n${colors.blue}🚀 Starting oracle...${colors.reset}\n`); const oracleScript = join(cliDir, 'server', 'oracle.js'); const deploymentFile = join(cliDir, 'deployments', 'localhost.json'); const oracleProcess = spawn('bun', ['run', oracleScript, '--deployment', deploymentFile], { stdio: 'inherit', cwd: process.cwd() }); // Handle cleanup on exit const cleanup = () => { console.log(`\n${colors.yellow}🛑 Shutting down...${colors.reset}`); // Kill oracle oracleProcess.kill(); // Kill Anvil try { const pidFile = '.anvil.pid'; if (existsSync(pidFile)) { const pid = readFileSync(pidFile, 'utf-8').trim(); try { process.kill(parseInt(pid)); console.log(`${colors.green}✅ Anvil stopped${colors.reset}`); } catch (e) { // Process might already be dead } unlinkSync(pidFile); } } catch (e) { // Ignore errors } process.exit(0); }; process.on('SIGINT', cleanup); process.on('SIGTERM', cleanup); // Keep process alive await new Promise(() => {}); }