← back to Waitline
server.js
209 lines
#!/usr/bin/env node
/**
* Waitline — sponsored status line for AI-agent wait states (Kickbacks.ai clone).
* Zero-dependency Node server: ad rotation, impression/click ledger, 50% user
* rev-share accounting, basic-auth admin dashboard.
*
* Integration: Claude Code statusLine command (statusline.sh) polls /api/ad.
* No patching of Claude Code binaries — sanctioned hook only.
*/
const http = require("http");
const fs = require("fs");
const path = require("path");
const PORT = process.env.PORT || 9718;
const DATA = path.join(__dirname, "data");
const ADS_FILE = path.join(DATA, "ads.json");
const LEDGER = path.join(DATA, "ledger.jsonl");
const AUTH_USER = process.env.ADMIN_USER || "admin";
const AUTH_PASS = process.env.ADMIN_PASS || "DW2024!";
const USER_SPLIT = 0.5; // 50% of gross accrues to the user, like upstream
fs.mkdirSync(DATA, { recursive: true });
if (!fs.existsSync(LEDGER)) fs.writeFileSync(LEDGER, "");
function loadAds() {
try { return JSON.parse(fs.readFileSync(ADS_FILE, "utf8")); } catch { return []; }
}
function saveAds(ads) { fs.writeFileSync(ADS_FILE, JSON.stringify(ads, null, 2)); }
function appendLedger(ev) {
fs.appendFileSync(LEDGER, JSON.stringify(ev) + "\n");
}
function readLedger() {
const raw = fs.readFileSync(LEDGER, "utf8").trim();
if (!raw) return [];
return raw.split("\n").map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
}
// Weighted random pick by CPM among active ads.
function pickAd(ads) {
const active = ads.filter((a) => a.active);
if (!active.length) return null;
const total = active.reduce((s, a) => s + (a.cpm_usd || 0.01), 0);
let r = Math.random() * total;
for (const a of active) {
r -= a.cpm_usd || 0.01;
if (r <= 0) return a;
}
return active[active.length - 1];
}
function totals(events) {
let gross = 0, user = 0, impressions = 0, clicks = 0;
const byAd = {};
for (const e of events) {
const b = (byAd[e.ad_id] ||= { impressions: 0, clicks: 0, gross: 0, user: 0 });
if (e.type === "impression") { impressions++; b.impressions++; }
if (e.type === "click") { clicks++; b.clicks++; }
gross += e.gross_usd || 0; user += e.user_share_usd || 0;
b.gross += e.gross_usd || 0; b.user += e.user_share_usd || 0;
}
return { gross, user, impressions, clicks, byAd };
}
function json(res, code, obj) {
res.writeHead(code, { "Content-Type": "application/json" });
res.end(JSON.stringify(obj));
}
function requireAuth(req, res) {
const h = req.headers.authorization || "";
const ok = h.startsWith("Basic ") &&
Buffer.from(h.slice(6), "base64").toString() === `${AUTH_USER}:${AUTH_PASS}`;
if (!ok) {
res.writeHead(401, { "WWW-Authenticate": 'Basic realm="Waitline"' });
res.end("auth required");
}
return ok;
}
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
const fmtDate = (iso) => new Date(iso).toLocaleString(undefined, {
year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "2-digit",
});
function adminPage(url) {
const ads = loadAds();
const events = readLedger();
const t = totals(events);
const filterAd = url.searchParams.get("ad");
const shown = filterAd ? events.filter((e) => e.ad_id === filterAd) : events;
const recent = shown.slice(-50).reverse();
const rows = ads.map((a) => {
const b = t.byAd[a.id] || { impressions: 0, clicks: 0, gross: 0, user: 0 };
return `<tr>
<td><a href="/admin?ad=${esc(a.id)}">${esc(a.id)}</a></td>
<td>${esc(a.sponsor)}</td>
<td>${esc(a.text)}</td>
<td>$${(a.cpm_usd || 0).toFixed(2)}</td>
<td><a href="/admin?ad=${esc(a.id)}">${b.impressions}</a></td>
<td><a href="/admin?ad=${esc(a.id)}">${b.clicks}</a></td>
<td>$${b.gross.toFixed(4)}</td>
<td>$${b.user.toFixed(4)}</td>
<td>${a.active ? "🟢" : "⚫"}</td>
<td class="when" title="${esc(a.created_at)}">🕓 ${fmtDate(a.created_at)}</td>
</tr>`;
}).join("");
const ledgerRows = recent.map((e) => `<tr>
<td class="when" title="${esc(e.ts)}">🕓 ${fmtDate(e.ts)}</td>
<td>${esc(e.type)}</td>
<td><a href="/admin?ad=${esc(e.ad_id)}">${esc(e.ad_id)}</a></td>
<td>${esc(e.session || "-")}</td>
<td>$${(e.gross_usd || 0).toFixed(5)}</td>
<td>$${(e.user_share_usd || 0).toFixed(5)}</td>
</tr>`).join("");
return `<!doctype html><html><head><meta charset="utf-8"><title>Waitline Admin</title>
<style>
body{font-family:-apple-system,system-ui,sans-serif;margin:24px;background:#0f1722;color:#dbe7e0}
h1{font-size:22px} h2{font-size:16px;margin-top:28px;color:#7fd6a4}
.cards{display:flex;gap:14px;flex-wrap:wrap;margin:16px 0}
.card{background:#17222f;border:1px solid #23364a;border-radius:10px;padding:14px 18px;min-width:130px}
.card .n{font-size:22px;font-weight:700;color:#2aa44f} .card .l{font-size:12px;color:#8a96a7}
table{border-collapse:collapse;width:100%;font-size:13px}
th,td{border-bottom:1px solid #23364a;padding:6px 10px;text-align:left}
th{color:#8a96a7;font-weight:600} a{color:#5bc98a;text-decoration:none} a:hover{text-decoration:underline}
.when{color:#8a96a7;font-size:12px;white-space:nowrap}
.filter{background:#2a1f0f;border:1px solid #6b4e12;padding:6px 12px;border-radius:8px;display:inline-block;margin-bottom:10px}
</style></head><body>
<h1>Waitline — sponsored wait-state admin</h1>
${filterAd ? `<div class="filter">Filtered to ad <b>${esc(filterAd)}</b> — <a href="/admin">clear</a></div>` : ""}
<div class="cards">
<div class="card"><div class="n"><a href="/admin">$${t.gross.toFixed(4)}</a></div><div class="l">gross revenue</div></div>
<div class="card"><div class="n"><a href="/admin">$${t.user.toFixed(4)}</a></div><div class="l">user share (50%)</div></div>
<div class="card"><div class="n"><a href="/admin">${t.impressions}</a></div><div class="l">impressions</div></div>
<div class="card"><div class="n"><a href="/admin">${t.clicks}</a></div><div class="l">clicks</div></div>
</div>
<h2>Ad inventory</h2>
<table><tr><th>id</th><th>sponsor</th><th>line</th><th>CPM</th><th>impr</th><th>clicks</th><th>gross</th><th>user</th><th>live</th><th>created</th></tr>${rows}</table>
<h2>Ledger (last 50${filterAd ? ", filtered" : ""})</h2>
<table><tr><th>when</th><th>type</th><th>ad</th><th>session</th><th>gross</th><th>user share</th></tr>${ledgerRows}</table>
</body></html>`;
}
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://localhost:${PORT}`);
if (url.pathname === "/healthz") return json(res, 200, { ok: true, service: "waitline" });
if (url.pathname === "/api/ad") {
const ads = loadAds();
const ad = pickAd(ads);
if (!ad) return json(res, 200, { line: null });
const gross = (ad.cpm_usd || 0) / 1000;
appendLedger({
ts: new Date().toISOString(), type: "impression", ad_id: ad.id,
session: url.searchParams.get("session") || null,
gross_usd: gross, user_share_usd: gross * USER_SPLIT,
});
const t = totals(readLedger());
return json(res, 200, {
id: ad.id, sponsor: ad.sponsor, line: ad.text, url: ad.url,
earned_total_usd: +t.user.toFixed(5),
});
}
if (url.pathname === "/api/click" && req.method === "POST") {
let body = "";
req.on("data", (c) => (body += c));
req.on("end", () => {
let adId = null;
try { adId = JSON.parse(body).ad_id; } catch {}
const ad = loadAds().find((a) => a.id === adId);
if (!ad) return json(res, 404, { error: "unknown ad" });
const gross = ad.cpc_usd || 0.05;
appendLedger({
ts: new Date().toISOString(), type: "click", ad_id: ad.id,
gross_usd: gross, user_share_usd: gross * USER_SPLIT,
});
json(res, 200, { ok: true });
});
return;
}
if (url.pathname === "/api/stats") {
const t = totals(readLedger());
return json(res, 200, {
gross_usd: +t.gross.toFixed(5), user_usd: +t.user.toFixed(5),
impressions: t.impressions, clicks: t.clicks, split: USER_SPLIT,
});
}
if (url.pathname === "/api/ads") return json(res, 200, loadAds());
if (url.pathname === "/admin" || url.pathname === "/") {
if (!requireAuth(req, res)) return;
res.writeHead(200, { "Content-Type": "text/html" });
return res.end(adminPage(url));
}
json(res, 404, { error: "not found" });
});
server.listen(PORT, "127.0.0.1", () =>
console.log(`waitline listening on http://127.0.0.1:${PORT}`)
);