const express = require('express'); const { WebSocketServer } = require('ws'); const { spawn } = require('child_process'); const path = require('path'); const http = require('http'); const PORT = process.env.PORT || 3000; const app = express(); const server = http.createServer(app); const wss = new WebSocketServer({ server, path: '/ws' }); app.use(express.static(path.join(__dirname, 'public'))); app.get('/health', (_, res) => res.json({ status: 'ok' })); wss.on('connection', (ws) => { console.log('[forge] client connected'); // Spawn codex in interactive mode, working dir = repo root const repoRoot = path.resolve(__dirname, '..'); const codex = spawn('codex', ['--quiet'], { cwd: repoRoot, shell: true, env: { ...process.env, FORCE_COLOR: '0' }, stdio: ['pipe', 'pipe', 'pipe'] }); let alive = true; codex.stdout.on('data', (data) => { if (ws.readyState === 1) { ws.send(JSON.stringify({ type: 'stdout', data: data.toString() })); } }); codex.stderr.on('data', (data) => { if (ws.readyState === 1) { ws.send(JSON.stringify({ type: 'stderr', data: data.toString() })); } }); codex.on('close', (code) => { alive = false; console.log(`[forge] codex exited (code ${code})`); if (ws.readyState === 1) { ws.send(JSON.stringify({ type: 'exit', code })); } }); codex.on('error', (err) => { alive = false; console.error('[forge] codex spawn error:', err.message); if (ws.readyState === 1) { ws.send(JSON.stringify({ type: 'error', data: err.message })); } }); ws.on('message', (msg) => { if (alive && codex.stdin.writable) { codex.stdin.write(msg.toString() + '\n'); } }); ws.on('close', () => { console.log('[forge] client disconnected'); if (alive) codex.kill('SIGTERM'); }); }); server.listen(PORT, () => { console.log(`\n 🔥 Forge Console running at http://localhost:${PORT}\n`); }); process.on('SIGINT', () => { console.log('\n[forge] shutting down...'); wss.clients.forEach((ws) => ws.close()); server.close(() => process.exit(0)); });