← back to Rejected Prompts Viewer
server.js
55 lines
#!/usr/bin/env node
// Rejected-Prompts Viewer — zero-dependency http server.
// Serves the viewer UI (Basic-auth gated) + the rejections dataset.
// $0 local. Data is produced by build-data.py from Claude Code transcripts.
const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = process.env.PORT || 9858;
const USER = process.env.BASIC_USER || 'admin';
const PASS = process.env.BASIC_PASS || 'DW2024!';
const ROOT = __dirname;
const MIME = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css',
'.json': 'application/json', '.svg': 'image/svg+xml' };
function unauthorized(res) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="rejected-prompts"' });
res.end('Authentication required');
}
function checkAuth(req) {
const h = req.headers.authorization || '';
if (!h.startsWith('Basic ')) return false;
const [u, p] = Buffer.from(h.slice(6), 'base64').toString().split(':');
return u === USER && p === PASS;
}
const server = http.createServer((req, res) => {
if (!checkAuth(req)) return unauthorized(res);
const url = req.url.split('?')[0];
if (url === '/api/rejections') {
const f = path.join(ROOT, 'data', 'rejections.json');
fs.readFile(f, (err, buf) => {
if (err) { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end('{"generated":null,"items":[]}'); }
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(buf);
});
return;
}
// static
let rel = url === '/' ? '/index.html' : url;
const filePath = path.join(ROOT, 'public', path.normalize(rel).replace(/^(\.\.[/\\])+/, ''));
fs.readFile(filePath, (err, buf) => {
if (err) { res.writeHead(404); return res.end('Not found'); }
res.writeHead(200, { 'Content-Type': MIME[path.extname(filePath)] || 'application/octet-stream' });
res.end(buf);
});
});
server.listen(PORT, () => console.log(`rejected-prompts-viewer on http://127.0.0.1:${PORT} (auth ${USER}/${PASS})`));