← 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})`));