v2: Forge Console + Open WebUI artifacts + Docker
- web/: Local chat UI (Express + WS → Codex bridge) - openwebui/: Preset, pipelines, knowledge manifest - Dockerfile + docker-compose.yml - Updated README with 3 frontend options - CLI-agnostic: works with Codex, Claude Code, Kiro, Gemini
This commit is contained in:
77
web/server.js
Normal file
77
web/server.js
Normal file
@@ -0,0 +1,77 @@
|
||||
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));
|
||||
});
|
||||
Reference in New Issue
Block a user