[object Object]

← back to Waitline

waitline: Kickbacks.ai clone — statusline ad server + 50% rev-share ledger

51ec068c336dc1b04f45a9a500c4ffd0f071acf4 · 2026-08-01 19:33:25 -0700 · Steve Abrams

Files touched

Diff

commit 51ec068c336dc1b04f45a9a500c4ffd0f071acf4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Aug 1 19:33:25 2026 -0700

    waitline: Kickbacks.ai clone — statusline ad server + 50% rev-share ledger
---
 .gitignore    |   9 +++
 README.md     |  39 +++++++++++
 data/ads.json |  52 +++++++++++++++
 server.js     | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 statusline.sh |  28 ++++++++
 5 files changed, 336 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0414f25
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
+data/ledger.jsonl
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a179778
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
+# Waitline
+
+Clone of the Kickbacks.ai idea (github.com/andrewmccalip/kickbacks.ai): monetize
+AI-agent wait states. While Claude Code is busy, the status line shows one
+sponsored line; 50% of the ad dollar accrues to the user.
+
+Key difference from upstream: **no binary patching.** Upstream injects into
+Claude Code's `index.js` and re-patches after every self-update. Waitline uses
+Claude Code's sanctioned `statusLine` hook — a script Claude Code runs on every
+render — so nothing Anthropic ships is ever modified.
+
+## Pieces
+
+- `server.js` — zero-dependency Node ad server on `127.0.0.1:9718`.
+  Weighted CPM rotation, append-only `data/ledger.jsonl` (impressions/clicks,
+  gross + 50% user share), `/api/ad`, `/api/click`, `/api/stats`,
+  `/healthz` (open), `/admin` dashboard (basic auth `admin`/`DW2024!`).
+- `statusline.sh` — Claude Code statusLine command. Polls `/api/ad` (60s cache
+  so repaints don't inflate impressions), prints
+  `💰 Sponsor: line · $0.0042 earned | Opus`.
+- `data/ads.json` — inventory. Seeded with 5 house ads (own-portfolio
+  cross-promo, same bootstrap play upstream used before real advertisers).
+
+## Enable in Claude Code (Steve-gated: settings.json edit)
+
+```json
+"statusLine": { "type": "command", "command": "~/Projects/waitline/statusline.sh" }
+```
+
+## Run
+
+```sh
+node server.js            # or: pm2 start server.js --name waitline
+```
+
+## Not built yet (deliberately)
+
+Advertiser self-serve/bidding portal, Stripe payouts, multi-user accounts,
+public deploy (would go to waitline.agentabrams.com per standing rule — gated).
diff --git a/data/ads.json b/data/ads.json
new file mode 100644
index 0000000..fed68ae
--- /dev/null
+++ b/data/ads.json
@@ -0,0 +1,52 @@
+[
+  {
+    "id": "aw-peel",
+    "sponsor": "ApartmentWallpaper",
+    "text": "Peel & stick wallpaper that actually comes off — apartmentwallpaper.com",
+    "url": "https://apartmentwallpaper.com",
+    "cpm_usd": 2.0,
+    "cpc_usd": 0.05,
+    "active": true,
+    "created_at": "2026-08-01T12:00:00-07:00"
+  },
+  {
+    "id": "wpb-ai",
+    "sponsor": "Wallpaper's Back",
+    "text": "AI-original wallpaper & murals — wallpapersback.com",
+    "url": "https://wallpapersback.com",
+    "cpm_usd": 2.5,
+    "cpc_usd": 0.05,
+    "active": true,
+    "created_at": "2026-08-01T12:00:00-07:00"
+  },
+  {
+    "id": "dw-trade",
+    "sponsor": "Designer Wallcoverings",
+    "text": "Trade pricing on 100+ designer lines — designerwallcoverings.com",
+    "url": "https://designerwallcoverings.com",
+    "cpm_usd": 3.0,
+    "cpc_usd": 0.08,
+    "active": true,
+    "created_at": "2026-08-01T12:00:00-07:00"
+  },
+  {
+    "id": "butlr-hold",
+    "sponsor": "Butlr",
+    "text": "The AI that waits on hold so you don't have to — butlr.agentabrams.com",
+    "url": "https://butlr.agentabrams.com",
+    "cpm_usd": 1.5,
+    "cpc_usd": 0.04,
+    "active": true,
+    "created_at": "2026-08-01T12:00:00-07:00"
+  },
+  {
+    "id": "ego-cmd",
+    "sponsor": "AbramsEgo",
+    "text": "Mission control for your AI agent fleet — the Abrams command center",
+    "url": "https://agentabrams.com",
+    "cpm_usd": 1.5,
+    "cpc_usd": 0.04,
+    "active": true,
+    "created_at": "2026-08-01T12:00:00-07:00"
+  }
+]
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..a476970
--- /dev/null
+++ b/server.js
@@ -0,0 +1,208 @@
+#!/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) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[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}`)
+);
diff --git a/statusline.sh b/statusline.sh
new file mode 100755
index 0000000..81247d0
--- /dev/null
+++ b/statusline.sh
@@ -0,0 +1,28 @@
+#!/bin/zsh
+# Waitline statusline for Claude Code — sanctioned statusLine hook, no patching.
+# Claude Code pipes session JSON on stdin and renders whatever we print.
+# Caches the ad for 60s so we don't ledger an impression on every repaint.
+
+CACHE=/tmp/waitline-ad-cache.json
+PORT="${WAITLINE_PORT:-9718}"
+
+input=$(cat)                       # session JSON from Claude Code
+session=$(echo "$input" | /usr/bin/python3 -c 'import sys,json;print(json.load(sys.stdin).get("session_id","")[:8])' 2>/dev/null)
+model=$(echo "$input" | /usr/bin/python3 -c 'import sys,json;print(json.load(sys.stdin).get("model",{}).get("display_name",""))' 2>/dev/null)
+
+fresh=0
+if [[ -f $CACHE ]]; then
+  age=$(( $(date +%s) - $(stat -f %m "$CACHE") ))
+  [[ $age -lt 60 ]] && fresh=1
+fi
+if [[ $fresh -eq 0 ]]; then
+  curl -s --max-time 1 "http://127.0.0.1:${PORT}/api/ad?session=${session}" -o "$CACHE" 2>/dev/null
+fi
+
+line=$(/usr/bin/python3 -c 'import json;d=json.load(open("'"$CACHE"'"));print(f"{d.get(\"sponsor\",\"\")}: {d.get(\"line\",\"\")} · ${d.get(\"earned_total_usd\",0):.4f} earned")' 2>/dev/null)
+
+if [[ -n "$line" ]]; then
+  echo "💰 ${line} | ${model}"
+else
+  echo "${model}"
+fi

(oldest)  ·  back to Waitline  ·  fix statusline python f-string quoting; verified end-to-end 2b2fed8 →