#!/usr/bin/env node const http = require('http'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const PORT = 3456; const PID_FILE = path.join(__dirname, 'server.pid'); const SECRET_FILE = path.join(__dirname, '.secret'); const sseClients = new Set(); function getSecret() { try { return fs.readFileSync(SECRET_FILE, 'utf8').trim(); } catch { const secret = crypto.randomBytes(32).toString('hex'); fs.writeFileSync(SECRET_FILE, secret, { mode: 0o600 }); return secret; } } const hmacSecret = getSecret(); const server = http.createServer((req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.writeHead(204); return res.end(); } // Serve the game if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) { const htmlPath = path.join(__dirname, 'index.html'); fs.readFile(htmlPath, (err, data) => { if (err) { res.writeHead(500); return res.end('Error loading game'); } res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(data); }); return; } // SSE endpoint if (req.method === 'GET' && req.url === '/events') { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); res.write('data: connected\n\n'); sseClients.add(res); req.on('close', () => sseClients.delete(res)); return; } // Trigger announcement if (req.method === 'POST' && req.url === '/done') { let body = ''; req.on('data', chunk => { body += chunk; if (body.length > 1024) req.destroy(); }); req.on('end', () => { const message = body || 'Claude Termino'; for (const client of sseClients) { client.write(`event: done\ndata: ${message}\n\n`); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true, clients: sseClients.size })); }); return; } // Activity feed from hooks if (req.method === 'POST' && req.url === '/activity') { let body = ''; req.on('data', chunk => { body += chunk; if (body.length > 8192) req.destroy(); }); req.on('end', () => { for (const client of sseClients) { client.write(`event: activity\ndata: ${body}\n\n`); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true })); }); return; } // Pause - Claude needs user input if (req.method === 'POST' && req.url === '/pause') { for (const client of sseClients) { client.write(`event: pause\ndata: Claude needs you\n\n`); } res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ ok: true })); } // Status - check if plan mode is active if (req.method === 'GET' && req.url === '/status') { const planActive = fs.existsSync(path.join(__dirname, '.plan-active')); res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ planActive })); } // Health check if (req.method === 'GET' && req.url === '/health') { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ status: 'ok', clients: sseClients.size })); } // Share scores - generate signed URL if (req.method === 'POST' && req.url === '/share') { let body = ''; req.on('data', chunk => { body += chunk; if (body.length > 1024) req.destroy(); }); req.on('end', () => { try { const { dino, snake, flappy } = JSON.parse(body); const payload = { d: dino || 0, s: snake || 0, f: flappy || 0, t: Date.now() }; const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url'); const sig = crypto.createHmac('sha256', hmacSecret).update(payloadB64).digest('hex').slice(0, 16); const token = `${payloadB64}.${sig}`; const url = `http://localhost:${PORT}/v/${token}`; const text = [ 'Plan Mode Game Scores:', `\u{1F995} Dino Runner: ${payload.d} pts`, `\u{1F40D} Snake: ${payload.s} pts`, `\u{1F426} Flappy Bird: ${payload.f} pts`, '', `Verify → ${url}` ].join('\n'); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ url, text })); } catch { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid request' })); } }); return; } // Verify scores - render verification page if (req.method === 'GET' && req.url.startsWith('/v/')) { const token = req.url.slice(3); const dotIdx = token.lastIndexOf('.'); let valid = false, payload = null; if (dotIdx > 0) { const payloadB64 = token.slice(0, dotIdx); const sig = token.slice(dotIdx + 1); const expected = crypto.createHmac('sha256', hmacSecret).update(payloadB64).digest('hex').slice(0, 16); valid = sig === expected; try { payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString()); } catch { valid = false; } } const ts = payload && payload.t ? new Date(payload.t).toLocaleString() : 'Unknown'; const html = `
${valid ? 'These scores are authentic' : 'This score link has been tampered with'}
${valid && payload ? `| \u{1F995} Dino Runner | ${payload.d} pts |
| \u{1F40D} Snake | ${payload.s} pts |
| \u{1F426} Flappy Bird | ${payload.f} pts |