← back to AbramsEgo
server.js
1486 lines
/*
* AbramsEgo — self-funding AI-agent command center.
*
* A single-page dashboard over Steve's real fleet (OpenClaw / @androoagi
* "agent environment" style), plus a self-funding P&L: it accounts for its own
* token + energy COST and tracks REVENUE from five (gated) engines so it can
* pay for itself. "Claude needs to be paid for."
*
* Design: a 30s background buildSnapshot() gathers every (slow) source once and
* writes data/data.json; every /api/* serves that cache instantly
* (stale-while-revalidate). One source failing never kills the snapshot.
*
* Safe/reversible by construction: read-only against the fleet, Basic-Auth
* gated, binds locally. No money mechanism is wired here — revenue engines are
* declared with status:"gated" until Steve flips them on.
*/
'use strict';
try { require('dotenv').config({ path: require('path').join(__dirname, '.env') }); } catch (e) {}
const express = require('express');
const basicAuth = require('basic-auth');
const path = require('path');
const fs = require('fs');
const os = require('os');
const { execFile } = require('child_process');
const PORT = parseInt(process.env.PORT, 10) || 9773;
const HOME = os.homedir();
const DATA_DIR = path.join(__dirname, 'data');
const SNAPSHOT = path.join(DATA_DIR, 'data.json');
const REVENUE_FILE = path.join(DATA_DIR, 'revenue.json');
const REVENUE_LEDGER = path.join(DATA_DIR, 'revenue-ledger.jsonl');
const AFFILIATES_FILE = path.join(DATA_DIR, 'affiliates.json');
const AFFILIATE_CLICKS = path.join(DATA_DIR, 'affiliate-clicks.jsonl');
// ads engine (engine 4): the landing serves a HOUSE ad until Steve joins an ad
// network and sets AD_NETWORK_PUB_ID — that env var is the gated switch. No
// network signup happens in this repo.
const AD_NETWORK_PUB_ID = (process.env.AD_NETWORK_PUB_ID || '').trim();
const VERSION = require('./package.json').version;
const START_TS = Date.now();
// --- self-funding knobs (env-tunable, all estimates clearly labeled) ---------
const ENERGY_AVG_WATTS = parseFloat(process.env.ENERGY_AVG_WATTS || '90'); // Mac Studio ~30W idle → ~140W load
const ENERGY_RATE = parseFloat(process.env.ENERGY_RATE_USD_PER_KWH || '0.30'); // CA residential ~$0.30/kWh
// fraction (0–100) of machine draw attributed to AbramsEgo/Claude — the box runs
// other work too, so 100 = charge all of it here, e.g. 40 = charge 40% to us.
const ENERGY_ATTRIB = Math.min(1, Math.max(0, parseFloat(process.env.ENERGY_ATTRIB_PCT || '100') / 100));
const CNCP = process.env.CNCP_BASE || 'http://127.0.0.1:3333';
const KAMATERA = process.env.KAMATERA_HOST || '45.61.58.125';
const COST_LEDGER = path.join(HOME, '.claude', 'cost-ledger.jsonl');
// --- Stripe TEST-mode checkout rail (engine 1: sell_product) -----------------
// HARD RAIL: TEST keys only. A live key (sk_live_) means Steve is flipping the
// switch himself — the money routes REFUSE to boot on it and log loudly.
// Absent/invalid key → routes answer 503 {error:"stripe not configured"} so the
// rail is keys-ready without being live. DTD verdict 2026-07-01: Checkout
// Sessions via POST /api/checkout (Option B), not Payment Links.
const STRIPE_KEY = (process.env.STRIPE_SECRET_KEY || '').trim();
const STRIPE_WEBHOOK_SECRET = (process.env.STRIPE_WEBHOOK_SECRET || '').trim();
let stripe = null;
let stripeStatus = { configured: false, mode: null, note: 'test keys not yet installed' };
if (STRIPE_KEY.startsWith('sk_live_')) {
console.error('!!! STRIPE_SECRET_KEY is a LIVE key (sk_live_). AbramsEgo is TEST-mode only — REFUSING to boot the money routes. Going live is Steve-gated; remove the live key.');
stripeStatus = { configured: false, mode: 'live-key-refused', note: 'LIVE key detected — money routes disabled (TEST mode only)' };
} else if (STRIPE_KEY.startsWith('sk_test_')) {
try {
stripe = require('stripe')(STRIPE_KEY);
stripeStatus = { configured: true, mode: 'test', note: 'Stripe TEST mode — no real charges' };
} catch (e) { console.error('stripe init failed:', e.message); }
} else if (STRIPE_KEY) {
console.error('STRIPE_SECRET_KEY set but does not start sk_test_ — ignoring (TEST keys only).');
}
// $19/$49 tiers from public/landing.html; priced inline (price_data) so no
// dashboard objects are required before test keys even exist.
const TIERS = {
hosted: { label: 'AbramsEgo Hosted', usd: 19 },
pro: { label: 'AbramsEgo Pro', usd: 49 },
};
// engine 3 (billable_work): the dashboard's own data as a sellable artifact —
// a one-shot Fleet Health Report priced per copy. Price is display-only until
// Steve wires billing; no charge mechanism exists on this route.
const REPORT_PRICE_USD = Math.max(0, parseFloat(process.env.REPORT_PRICE_USD || '29')) || 29;
fs.mkdirSync(DATA_DIR, { recursive: true });
// ---------------------------------------------------------------------------
// small helpers
// ---------------------------------------------------------------------------
function sh(cmd, args, timeoutMs = 8000) {
return new Promise((resolve) => {
// SIGKILL on timeout: a `ps` wedged in the kernel proc-table hang ignores
// SIGTERM and would otherwise accumulate as a leaked child forever.
execFile(cmd, args, { timeout: timeoutMs, killSignal: 'SIGKILL', maxBuffer: 1024 * 1024 * 16 }, (err, stdout) => {
resolve({ ok: !err, out: (stdout || '').toString(), err: err && err.message });
});
});
}
async function fetchJSON(url, timeoutMs = 5000) {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), timeoutMs);
try {
const r = await fetch(url, { signal: ctrl.signal });
if (!r.ok) return null;
return await r.json();
} catch (e) { return null; } finally { clearTimeout(t); }
}
const round = (n, d = 4) => Math.round((n + Number.EPSILON) * 10 ** d) / 10 ** d;
// ---------------------------------------------------------------------------
// collectors — each returns a plain object; each is wrapped in try/catch by the
// snapshot builder so a single failure degrades to {error} rather than crashing.
// ---------------------------------------------------------------------------
async function collectSystem() {
const load = os.loadavg(); // [1,5,15] min
const cpus = os.cpus().length;
const totalMem = os.totalmem(), freeMem = os.freemem();
const memUsedPct = round((1 - freeMem / totalMem) * 100, 1);
const loadPct = round((load[0] / cpus) * 100, 1);
// disk of home volume
let diskPct = null, diskInfo = null;
const df = await sh('df', ['-k', HOME], 4000);
if (df.ok) {
const line = df.out.trim().split('\n').pop().split(/\s+/);
// Filesystem 1K-blocks Used Avail Capacity ... Mounted
const capIdx = line.findIndex((x) => /%$/.test(x));
if (capIdx >= 0) diskPct = parseInt(line[capIdx], 10);
diskInfo = { usedPct: diskPct };
}
// swap (macOS)
let swap = null;
const sm = await sh('sysctl', ['-n', 'vm.swapusage'], 3000);
if (sm.ok) {
const m = sm.out.match(/used\s*=\s*([\d.]+)([MG])/i);
if (m) swap = { usedMB: m[2].toUpperCase() === 'G' ? parseFloat(m[1]) * 1024 : parseFloat(m[1]) };
}
return {
host: os.hostname(),
platform: `${os.type()} ${os.release()}`,
cpus, load1: round(load[0], 2), loadPct,
memUsedPct, memUsedGB: round((totalMem - freeMem) / 1e9, 1), memTotalGB: round(totalMem / 1e9, 1),
diskUsedPct: diskPct, swap,
node: process.version, dashboardUptimeSec: Math.round((Date.now() - START_TS) / 1000),
};
}
// Local pm2 gets the same last-good disk cache as Kamatera — the Mac2 pm2
// daemon crawls under swap pressure, and a timed-out jlist must degrade to the
// cached snapshot instead of a silently dead panel.
const LOCAL_CACHE = path.join(DATA_DIR, 'local-fleet.json');
// Live fallback when pm2's RPC path is wedged. Seen 2026-07-01: a macOS
// proc-table hang made every multi-pid `ps` block forever, so the daemon's
// pidusage call never returned and every `pm2 jlist` hung — while the managed
// apps themselves were fine. They are direct children of the God daemon, so
// scan them without RPC: pgrep the daemon's children, keep the ones whose args
// match a dump.pm2 exec path (the daemon's leaked hung-`ps` children don't),
// and stat each with a SINGLE-pid ps (single-pid ps still works in that hang).
async function scanLocalFleetByPid() {
let daemonPid;
try { daemonPid = parseInt(fs.readFileSync(path.join(HOME, '.pm2', 'pm2.pid'), 'utf8'), 10); } catch (e) { return null; }
if (!daemonPid) return null;
try { process.kill(daemonPid, 0); } catch (e) { return null; } // daemon gone → nothing to scan
let dump = [];
try { dump = JSON.parse(fs.readFileSync(path.join(HOME, '.pm2', 'dump.pm2'), 'utf8')); } catch (e) { return null; }
const known = dump.filter((d) => d.name && d.pm_exec_path);
if (!known.length) return null;
const kids = await sh('pgrep', ['-fl', '-P', String(daemonPid)], 5000);
if (!kids.ok) return null;
const procs = [];
for (const line of kids.out.split('\n')) {
const m = line.match(/^(\d+)\s+(.*)$/);
if (!m) continue;
const [, pidStr, args] = m;
const app = known.find((d) => args.includes(d.pm_exec_path));
if (!app) continue;
const st = await sh('ps', ['-p', pidStr, '-o', 'rss=,pcpu=,etime='], 5000);
const sm = st.ok && st.out.trim().match(/^(\d+)\s+([\d.]+)\s+(\S+)$/);
// etime is [[DD-]HH:]MM:SS
let uptimeMs = null;
if (sm) {
const em = sm[3].match(/^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/);
if (em) uptimeMs = (((+(em[1] || 0) * 24 + +(em[2] || 0)) * 60 + +em[3]) * 60 + +em[4]) * 1000;
}
procs.push({
name: app.name, status: 'online',
cpu: sm ? parseFloat(sm[2]) : null,
memMB: sm ? round(parseInt(sm[1], 10) / 1024, 0) : null,
restarts: null,
uptimeMs,
startedAt: uptimeMs != null ? new Date(Date.now() - uptimeMs).toISOString() : null,
});
}
if (!procs.length) return null;
// dump.pm2 apps with no live child are down — show them, don't hide them
for (const d of known) {
if (!procs.some((p) => p.name === d.name)) {
procs.push({ name: d.name, status: 'stopped', cpu: null, memMB: null, restarts: null, uptimeMs: null, startedAt: null });
}
}
return procs;
}
// After a jlist failure, skip jlist for a cooldown window: every attempt against
// a wedged daemon leaks one more kernel-hung `ps` child under it (they ignore
// SIGTERM and pile up), so retrying every 30s cycle actively makes things worse.
let jlistFailedAt = 0;
const JLIST_COOLDOWN_MS = 5 * 60 * 1000;
async function collectLocalFleet() {
const pm2Bin = path.join(HOME, '.npm-global', 'bin', 'pm2');
const inCooldown = Date.now() - jlistFailedAt < JLIST_COOLDOWN_MS;
const r = inCooldown
? { ok: false, out: '', err: 'jlist cooldown after recent failure' }
: await sh(pm2Bin, ['jlist'], 20000);
let list = null;
if (r.ok) { try { list = JSON.parse(r.out); } catch (e) { /* fall through to cache */ } }
if (!inCooldown && !list) jlistFailedAt = Date.now();
if (list) jlistFailedAt = 0;
if (!list) {
const err = r.ok ? 'pm2 parse' : (r.err || 'pm2 unavailable');
// RPC failed but the fleet may be fine — try the daemon-children pid scan.
// Live scan data is fresh and true, so it is NOT an error state for the
// panel (note still surfaces that RPC is unhealthy).
const scanned = await scanLocalFleetByPid();
if (scanned) {
try { fs.writeFileSync(LOCAL_CACHE, JSON.stringify({ fetchedAt: new Date().toISOString(), procs: scanned })); } catch (e) { /* best-effort */ }
return {
procs: scanned, up: scanned.filter((p) => p.status === 'online').length,
total: scanned.length, note: `pm2 rpc unresponsive (${err}) — live pid scan`,
};
}
try {
const c = JSON.parse(fs.readFileSync(LOCAL_CACHE, 'utf8'));
return {
procs: c.procs, up: c.procs.filter((p) => p.status === 'online').length,
total: c.procs.length, fetchedAt: c.fetchedAt,
note: 'pm2 slow — cached snapshot', error: err,
};
} catch (e) {
return { procs: [], up: null, total: null, note: 'pm2 unreachable — no cached snapshot yet', error: err };
}
}
const procs = list.map((p) => ({
name: p.name,
status: p.pm2_env && p.pm2_env.status,
cpu: p.monit && p.monit.cpu,
memMB: p.monit ? round(p.monit.memory / 1e6, 0) : null,
restarts: p.pm2_env && p.pm2_env.restart_time,
uptimeMs: p.pm2_env && p.pm2_env.pm_uptime ? Date.now() - p.pm2_env.pm_uptime : null,
// created/started time — every fleet tile obeys Steve's admin-card date+time rule
startedAt: p.pm2_env && p.pm2_env.pm_uptime ? new Date(p.pm2_env.pm_uptime).toISOString() : null,
}));
try { fs.writeFileSync(LOCAL_CACHE, JSON.stringify({ fetchedAt: new Date().toISOString(), procs })); } catch (e) { /* cache write is best-effort */ }
return { procs, up: procs.filter((p) => p.status === 'online').length, total: procs.length };
}
// Kamatera fleet is fetched on a slower cadence and cached to disk, because SSH
// round-trips are too slow for the 30s snapshot loop.
const KAM_CACHE = path.join(DATA_DIR, 'kamatera-fleet.json');
let kamLastFetch = 0;
let kamLastErr = null;
async function collectKamateraFleet() {
const now = Date.now();
// refresh at most every 5 min
if (now - kamLastFetch > 5 * 60 * 1000) {
kamLastFetch = now;
const r = await sh('ssh', ['-o', 'ConnectTimeout=6', '-o', 'BatchMode=yes', `root@${KAMATERA}`, 'pm2 jlist'], 12000);
if (r.ok) {
try {
const list = JSON.parse(r.out);
const procs = list.map((p) => ({ name: p.name, status: p.pm2_env && p.pm2_env.status, restarts: p.pm2_env && p.pm2_env.restart_time, startedAt: p.pm2_env && p.pm2_env.pm_uptime ? new Date(p.pm2_env.pm_uptime).toISOString() : null }));
fs.writeFileSync(KAM_CACHE, JSON.stringify({ fetchedAt: new Date().toISOString(), procs }));
kamLastErr = null;
} catch (e) { kamLastErr = 'bad pm2 jlist output: ' + e.message; /* keep last-good */ }
} else {
kamLastErr = (r.err || r.out || 'ssh failed').toString().trim().slice(0, 200);
}
}
try {
const c = JSON.parse(fs.readFileSync(KAM_CACHE, 'utf8'));
const ageMin = c.fetchedAt ? Math.round((Date.now() - Date.parse(c.fetchedAt)) / 60000) : null;
const stale = ageMin != null && ageMin > 12;
const out = { fetchedAt: c.fetchedAt, procs: c.procs, up: c.procs.filter((p) => p.status === 'online').length, total: c.procs.length, stale };
if (stale || kamLastErr) {
out.note = 'ssh refresh failing — cached ' + (ageMin != null ? ageMin + 'm' : '?') + ' ago';
if (kamLastErr) out.error = kamLastErr;
}
return out;
} catch (e) { return { procs: [], up: 0, total: 0, note: 'no cached kamatera snapshot yet' }; }
}
async function collectCanaries() {
const glob = path.join(HOME, '.claude', 'skills');
let names = [];
try { names = fs.readdirSync(glob); } catch (e) { return { canaries: [] }; }
const out = [];
for (const name of names) {
const f = path.join(glob, name, 'data', 'latest.json');
try {
const st = fs.statSync(f);
let verdict = null, scanned = null;
try {
const j = JSON.parse(fs.readFileSync(f, 'utf8'));
verdict = j.verdict || j.status || (j.down > 0 ? 'DOWN' : j.degraded > 0 ? 'DEGRADED' : (j.up != null ? 'UP' : null));
// nested shapes: per-unit verdicts (worst-of) or top-level fail/warn/leaking/dead counters
if (!verdict) {
const worst = (vs) => { const V = vs.filter(Boolean).map(v => String(v).toUpperCase());
return V.some(v => /FAIL|CRIT|DOWN/.test(v)) ? 'FAIL' : V.some(v => /WARN|DEGRAD/.test(v)) ? 'WARN' : V.length ? 'PASS' : null; };
if (Array.isArray(j.units)) verdict = worst(j.units.map(u => u.verdict));
else if ((j.fail > 0) || (j.leaking > 0) || (j.dead > 0)) verdict = 'FAIL';
else if (j.warn > 0) verdict = 'WARN';
else if (j.fail === 0 || j.leaking === 0 || j.dead === 0) verdict = 'PASS';
}
scanned = j.scanned_at || j.checked_at || j.ts || j.at || null;
} catch (e) {}
const ageMin = Math.round((Date.now() - st.mtimeMs) / 60000);
// heartbeat/last-run time so each canary card can show created date+time
out.push({ name, verdict, scanned, mtime: new Date(st.mtimeMs).toISOString(), ageMin, stale: ageMin > 24 * 60 });
} catch (e) { /* no heartbeat */ }
}
out.sort((a, b) => a.ageMin - b.ageMin);
return { canaries: out, count: out.length };
}
// next occurrence of HH:MM local time from now (today, or tomorrow if passed)
function nextDailyRun(hour, minute) {
const now = new Date();
const next = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute, 0, 0);
if (next <= now) next.setDate(next.getDate() + 1);
return next.toISOString();
}
// parse a launchd plist for a human-readable schedule + the file's created/
// modified timestamps (satisfies Steve's admin-card "created date+time" rule).
// Cheap synchronous read; never throws — degrades to nulls.
function readCronPlistMeta(filePath) {
const meta = { schedule: null, program: null, created: null, modified: null, lastRun: null, nextRun: null, model: null };
try {
const st = fs.statSync(filePath);
meta.created = new Date(st.birthtimeMs || st.ctimeMs).toISOString();
meta.modified = new Date(st.mtimeMs).toISOString();
} catch (e) {}
let xml = '';
try { xml = fs.readFileSync(filePath, 'utf8'); } catch (e) { return meta; }
const iv = xml.match(/<key>StartInterval<\/key>\s*<integer>(\d+)<\/integer>/i);
if (iv) {
const s = parseInt(iv[1], 10);
meta.schedule = s % 3600 === 0 ? `every ${s / 3600}h` : s % 60 === 0 ? `every ${s / 60}m` : `every ${s}s`;
} else if (/<key>StartCalendarInterval<\/key>/i.test(xml)) {
const h = xml.match(/<key>Hour<\/key>\s*<integer>(\d+)<\/integer>/i);
const m = xml.match(/<key>Minute<\/key>\s*<integer>(\d+)<\/integer>/i);
meta.schedule = h ? `daily ${String(h[1]).padStart(2, '0')}:${m ? String(m[1]).padStart(2, '0') : '00'}` : 'calendar';
// interval jobs have no knowable phase, so nextRun stays null for those;
// calendar HH:MM jobs get a real next-occurrence timestamp.
if (h) meta.nextRun = nextDailyRun(parseInt(h[1], 10), m ? parseInt(m[1], 10) : 0);
} else if (/<key>RunAtLoad<\/key>\s*<true/i.test(xml)) {
meta.schedule = 'at load';
}
const prog = xml.match(/<key>Program<\/key>\s*<string>([^<]+)<\/string>/i)
|| xml.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]+)<\/string>/i);
if (prog) meta.program = prog[1].split('/').pop();
// lastRun (best-effort) = newest mtime of the job's stdout/stderr log file
let lastMs = 0;
for (const re of [/<key>StandardOutPath<\/key>\s*<string>([^<]+)<\/string>/i, /<key>StandardErrorPath<\/key>\s*<string>([^<]+)<\/string>/i]) {
const p = xml.match(re);
if (!p) continue;
try { const st = fs.statSync(p[1]); if (st.mtimeMs > lastMs) lastMs = st.mtimeMs; } catch (e) {}
}
if (lastMs) meta.lastRun = new Date(lastMs).toISOString();
// model (best-effort): an explicit --model arg, else any claude-* model token
const mArg = xml.match(/--model\s*<\/string>\s*<string>([^<]+)</i);
const mTok = xml.match(/\bclaude-(?:opus|sonnet|haiku|fable)[\w.-]*/i);
if (mArg) meta.model = mArg[1].trim();
else if (mTok) meta.model = mTok[0];
// plists almost never name a model — it lives one hop away, in the script
// the job invokes. Resolve that script and scan it with the same regexes.
if (!meta.model) {
try {
const argsBlock = xml.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/i);
const args = argsBlock ? [...argsBlock[1].matchAll(/<string>([^<]*)<\/string>/g)].map((m) => m[1]) : [];
let script = args.find((a) => a.startsWith('/') && /\.(sh|js|cjs|mjs|py)$/.test(a));
if (!script) {
// relative script + WorkingDirectory (e.g. `node server.js` with a cwd)
const wd = (xml.match(/<key>WorkingDirectory<\/key>\s*<string>([^<]+)<\/string>/i) || [])[1];
const rel = args.find((a) => !a.startsWith('-') && !a.startsWith('/') && /\.(sh|js|cjs|mjs|py)$/.test(a));
if (wd && rel) script = path.join(wd, rel);
}
if (!script && args.length) {
const last = args[args.length - 1];
if (last.startsWith('/') && fs.existsSync(last) && fs.statSync(last).isFile()) script = last;
}
if (script && fs.existsSync(script)) {
const st = fs.statSync(script);
if (st.isFile() && st.size < 512 * 1024) {
const src = fs.readFileSync(script, 'utf8');
const sArg = src.match(/--model[= ]["']?([\w.-]+)/);
const sTok = src.match(/\bclaude-(?:opus|sonnet|haiku|fable)[\w.-]*/i);
if (sArg) meta.model = sArg[1];
else if (sTok) meta.model = sTok[0];
}
}
} catch (e) {}
}
return meta;
}
async function collectCrons() {
const laDir = path.join(HOME, 'Library', 'LaunchAgents');
let files = [];
try { files = fs.readdirSync(laDir).filter((f) => /^com\.steve\..*\.plist$/.test(f)); } catch (e) {}
// loaded state
const ll = await sh('launchctl', ['list'], 4000);
const loaded = {};
if (ll.ok) {
ll.out.trim().split('\n').forEach((line) => {
const cols = line.split(/\s+/);
const label = cols[cols.length - 1];
if (label && label.startsWith('com.steve.')) loaded[label] = { pid: cols[0], exit: cols[1] };
});
}
const jobs = files.map((f) => {
const label = f.replace(/\.plist$/, '');
const l = loaded[label];
const pid = l && l.pid !== '-' ? l.pid : null;
const lastExit = l ? l.exit : null;
const meta = readCronPlistMeta(path.join(laDir, f));
// running = live PID; error = loaded with a non-zero last exit; idle otherwise.
const state = pid ? 'running' : (lastExit && lastExit !== '0' && lastExit !== '-') ? 'error' : 'idle';
return { label, loaded: !!l, pid, lastExit, state, schedule: meta.schedule, program: meta.program, created: meta.created, modified: meta.modified, lastRun: meta.lastRun, nextRun: meta.nextRun, model: meta.model };
});
return { count: jobs.length, loadedCount: jobs.filter((j) => j.loaded).length, jobs };
}
// --- GIT ACTIVITY FEED (panel 6) -------------------------------------------
// Scans the ~25 most-recently-touched git repos under ~/Projects and pulls each
// one's latest 1-2 commits, merged + sorted newest-first, capped at 40 rows.
// Shelling out to git per-repo is slow, so it's throttled to ~3 min and cached
// to disk (same stale-while-revalidate shape as the Kamatera collector); the
// 30s snapshot loop reads the cache instantly except on the refresh tick.
const PROJECTS_ROOT = path.join(HOME, 'Projects');
const ACT_CACHE = path.join(DATA_DIR, 'activity.json');
const ACT_SEP = '\x1f'; // unit separator — safe delimiter inside git --pretty
let actLastFetch = 0;
async function collectActivity() {
const now = Date.now();
if (now - actLastFetch > 3 * 60 * 1000) {
actLastFetch = now;
// find repos = ~/Projects/* dirs that have a .git, ranked by .git mtime
// (git rewrites .git/{logs,refs,index} on commit, so its mtime ≈ last commit)
let dirs = [];
try { dirs = fs.readdirSync(PROJECTS_ROOT, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith('.')); } catch (e) {}
const repos = [];
for (const d of dirs) {
try { const st = fs.statSync(path.join(PROJECTS_ROOT, d.name, '.git')); repos.push({ name: d.name, mtime: st.mtimeMs }); } catch (e) { /* not a repo */ }
}
repos.sort((a, b) => b.mtime - a.mtime);
const top = repos.slice(0, 25);
const rows = await Promise.all(top.map(async (r) => {
const g = await sh('git', ['-C', path.join(PROJECTS_ROOT, r.name), 'log', '-2', '--no-color', `--pretty=format:%h${ACT_SEP}%s${ACT_SEP}%aI${ACT_SEP}%an`], 5000);
if (!g.ok) return [];
return g.out.trim().split('\n').filter(Boolean).map((ln) => {
const [hash, subject, date, author] = ln.split(ACT_SEP);
return { repo: r.name, hash, subject: (subject || '').slice(0, 200), date, author };
}).filter((c) => c.hash && c.date);
}));
const commits = rows.flat().sort((a, b) => Date.parse(b.date) - Date.parse(a.date)).slice(0, 40);
try { fs.writeFileSync(ACT_CACHE, JSON.stringify({ fetchedAt: new Date().toISOString(), repoCount: top.length, commits })); } catch (e) {}
}
try { return JSON.parse(fs.readFileSync(ACT_CACHE, 'utf8')); }
catch (e) { return { commits: [], repoCount: 0, note: 'no activity snapshot yet' }; }
}
// --- MODEL USAGE + SESSIONS + SUB-AGENT RUNS (panel 5) ---------------------
// Reads ~/.claude/cost-ledger.jsonl for per-model calls/tokens/$ (24h + all-time)
// and ~/.claude/projects/**/*.jsonl mtimes for active/recent Claude sessions +
// the most recent sub-agent runs. Read-only; wrapped by the snapshot builder.
const PROJECTS_DIR = path.join(HOME, '.claude', 'projects');
// pull a compact meta (agentId, task label, model, start ts) from a sub-agent
// run file — reads only the head so it stays cheap even on long transcripts.
function readSubagentMeta(file) {
const meta = { agentId: null, task: null, model: null, startedAt: null };
try {
const head = fs.readFileSync(file, 'utf8').split('\n', 60);
for (const ln of head) {
if (!ln.trim()) continue;
let j; try { j = JSON.parse(ln); } catch (e) { continue; }
if (!meta.agentId && j.agentId) meta.agentId = j.agentId;
if (!meta.startedAt && j.timestamp) meta.startedAt = j.timestamp;
const m = j.message;
if (m && typeof m === 'object') {
if (!meta.model && m.model) meta.model = m.model;
if (!meta.task && j.type === 'user') {
const c = m.content;
const txt = Array.isArray(c) ? c.map((x) => (x && x.text) || '').join(' ') : (typeof c === 'string' ? c : '');
if (txt.trim()) meta.task = txt.trim().replace(/\s+/g, ' ').slice(0, 140);
}
}
if (meta.agentId && meta.model && meta.task) break;
}
} catch (e) {}
return meta;
}
function collectUsage() {
const now = Date.now();
const DAY = 24 * 3.6e6;
// ---- per-model usage from the cost ledger ----
const models = {};
try {
const lines = fs.readFileSync(COST_LEDGER, 'utf8').split('\n');
for (const ln of lines) {
if (!ln.trim()) continue;
let j; try { j = JSON.parse(ln); } catch (e) { continue; }
const t = Date.parse(j.ts); if (isNaN(t)) continue;
const key = j.api || j.model || 'unknown';
const r = models[key] || (models[key] = { model: key, calls: 0, cost: 0, inTok: 0, outTok: 0, calls24: 0, cost24: 0, tok24: 0 });
const c = j.cost_usd || 0;
let inTok = 0, outTok = 0;
if (Array.isArray(j.units)) for (const u of j.units) {
if (/input/i.test(u.unit)) inTok += u.qty || 0;
else if (/output/i.test(u.unit)) outTok += u.qty || 0;
}
r.calls++; r.cost += c; r.inTok += inTok; r.outTok += outTok;
if (t >= now - DAY) { r.calls24++; r.cost24 += c; r.tok24 += inTok + outTok; }
}
} catch (e) {}
const allTime = Object.values(models)
.map((r) => ({ model: r.model, calls: r.calls, tokens: r.inTok + r.outTok, inTok: r.inTok, outTok: r.outTok, cost: round(r.cost, 4) }))
.sort((a, b) => b.cost - a.cost);
const last24h = Object.values(models)
.filter((r) => r.calls24 > 0)
.map((r) => ({ model: r.model, calls: r.calls24, tokens: r.tok24, cost: round(r.cost24, 4) }))
.sort((a, b) => b.cost - a.cost);
// ---- sessions + sub-agent runs from projects/**/*.jsonl mtimes ----
let totalSessions = 0, activeSessions = 0, recentSessions = 0;
const subRuns = [];
let projDirs = [];
try { projDirs = fs.readdirSync(PROJECTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()); } catch (e) {}
for (const pd of projDirs) {
const pdir = path.join(PROJECTS_DIR, pd.name);
let entries = [];
try { entries = fs.readdirSync(pdir, { withFileTypes: true }); } catch (e) { continue; }
for (const e of entries) {
if (e.isFile() && e.name.endsWith('.jsonl')) { // a top-level session
try {
const st = fs.statSync(path.join(pdir, e.name));
totalSessions++;
const age = now - st.mtimeMs;
if (age <= 15 * 60 * 1000) activeSessions++;
if (age <= DAY) recentSessions++;
} catch (e2) {}
} else if (e.isDirectory()) { // <session>/subagents/agent-*.jsonl
const subDir = path.join(pdir, e.name, 'subagents');
let subFiles = [];
try { subFiles = fs.readdirSync(subDir); } catch (e2) { continue; }
for (const sf of subFiles) {
if (!sf.endsWith('.jsonl')) continue;
const fp = path.join(subDir, sf);
try { subRuns.push({ file: fp, projectDir: pd.name, mtimeMs: fs.statSync(fp).mtimeMs }); } catch (e3) {}
}
}
}
}
subRuns.sort((a, b) => b.mtimeMs - a.mtimeMs);
const recent = subRuns.slice(0, 20).map((r) => {
const m = readSubagentMeta(r.file);
// project name from the dashed dir (…-Projects-Foo → Foo) as a readable label
const proj = (r.projectDir.match(/-Projects-(.+)$/) || [])[1] || r.projectDir.replace(/^-/, '');
return {
agentId: m.agentId, project: proj.replace(/-/g, ' '),
task: m.task || '(no prompt captured)', model: m.model || 'unknown',
startedAt: m.startedAt, updatedAt: new Date(r.mtimeMs).toISOString(),
};
});
return {
models: { allTime, last24h },
sessions: { total: totalSessions, active: activeSessions, recent: recentSessions },
subagentRuns: { count: subRuns.length, recent },
};
}
// --- COST side of the P&L --------------------------------------------------
function readCostLedger(sinceMs) {
// cost-ledger.jsonl: {ts, api, vendor, app, cost_usd, units:[...]}
let total = 0, count = 0; const byApp = {}, byVendor = {};
try {
const lines = fs.readFileSync(COST_LEDGER, 'utf8').split('\n');
for (const ln of lines) {
if (!ln.trim()) continue;
let j; try { j = JSON.parse(ln); } catch (e) { continue; }
const t = Date.parse(j.ts);
if (isNaN(t) || t < sinceMs) continue;
const c = j.cost_usd || 0;
total += c; count++;
const app = j.app || 'unknown'; const v = j.vendor || 'unknown';
byApp[app] = (byApp[app] || 0) + c;
byVendor[v] = (byVendor[v] || 0) + c;
}
} catch (e) {}
return { total: round(total, 4), count, byApp, byVendor };
}
function energyCost(sinceMs) {
const hours = (Date.now() - sinceMs) / 3.6e6;
const kwh = (ENERGY_AVG_WATTS / 1000) * hours;
return { hours: round(hours, 1), kwh: round(kwh, 3), usd: round(kwh * ENERGY_RATE * ENERGY_ATTRIB, 4), est: true, avgWatts: ENERGY_AVG_WATTS, rate: ENERGY_RATE, attribPct: round(ENERGY_ATTRIB * 100, 1) };
}
// Map a cost-ledger row to a human provider bucket. Prefers an explicit
// `provider` field, then falls back to inferring from the api/model/vendor
// strings. Local/free engines (ollama, qwen) are labeled "(local)" so a $0
// provider still shows up in the donut rather than vanishing.
function deriveProvider(j) {
if (j.provider && String(j.provider).trim()) return String(j.provider).trim();
const s = `${j.api || ''} ${j.model || ''} ${j.vendor || ''}`.toLowerCase();
if (/anthropic|claude/.test(s)) return 'Anthropic';
if (/ollama|qwen|local/.test(s)) return 'Ollama (local)';
if (/gpt|openai|codex/.test(s)) return 'OpenAI';
if (/gemini|google/.test(s)) return 'Google';
if (/elevenlabs|eleven_labs|11labs/.test(s)) return 'ElevenLabs';
if (/replicate/.test(s)) return 'Replicate';
if (/\bexa\b/.test(s)) return 'Exa';
if (/moonshot|kimi/.test(s)) return 'Moonshot';
return 'Other';
}
// 7-day daily buckets of cost (tokens+energy) + a per-api/model breakdown for
// 30d. One pass over the ledger keeps this cheap. Days are keyed by LOCAL
// calendar day so the trend lines up with the "today" the dashboard shows.
function collectCostSeries() {
const now = Date.now();
const dayKey = (ms) => {
const dt = new Date(ms);
return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}-${String(dt.getDate()).padStart(2, '0')}`;
};
// pre-seed the 7 buckets oldest→newest so gap days render as $0, not missing
const buckets = [];
const byKey = {};
for (let i = 6; i >= 0; i--) {
const key = dayKey(now - i * 24 * 3.6e6);
const b = { day: key, tokens: 0 };
buckets.push(b); byKey[key] = b;
}
const since30 = now - 30 * 24 * 3.6e6;
const byModel = {};
const byProvider = {};
try {
const lines = fs.readFileSync(COST_LEDGER, 'utf8').split('\n');
for (const ln of lines) {
if (!ln.trim()) continue;
let j; try { j = JSON.parse(ln); } catch (e) { continue; }
const t = Date.parse(j.ts);
if (isNaN(t)) continue;
const c = j.cost_usd || 0;
const key = dayKey(t);
if (byKey[key]) byKey[key].tokens += c; // 7d daily buckets
if (t >= since30) { // 30d per-model + per-provider breakdown
const m = j.api || j.model || 'unknown';
byModel[m] = (byModel[m] || 0) + c;
const p = deriveProvider(j); // $0/local providers still counted
byProvider[p] = (byProvider[p] || 0) + c;
}
}
} catch (e) {}
// energy per day: a full 24h for past days, partial (midnight→now) for today
const perDayFullUsd = round((ENERGY_AVG_WATTS / 1000) * 24 * ENERGY_RATE * ENERGY_ATTRIB, 4);
const sod = new Date(); sod.setHours(0, 0, 0, 0);
const todayHours = (now - sod.getTime()) / 3.6e6;
const todayUsd = round((ENERGY_AVG_WATTS / 1000) * todayHours * ENERGY_RATE * ENERGY_ATTRIB, 4);
const todayKey = dayKey(now);
const series7d = buckets.map((b) => {
const energy = b.day === todayKey ? todayUsd : perDayFullUsd;
return { day: b.day, tokens: round(b.tokens, 4), energy, total: round(b.tokens + energy, 4) };
});
const byModelArr = Object.entries(byModel)
.sort((a, b) => b[1] - a[1])
.map(([api, usd]) => ({ api, usd: round(usd, 4) }));
const byProviderArr = Object.entries(byProvider)
.map(([provider, usd]) => ({ provider, total: round(usd, 4), local: /\(local\)/i.test(provider) }))
.sort((a, b) => b.total - a.total);
return { series7d, byModel: byModelArr, byProvider: byProviderArr };
}
function collectCost() {
const now = Date.now();
const windows = { today: 24 * 3.6e6, week: 7 * 24 * 3.6e6, month: 30 * 24 * 3.6e6 };
const out = {};
for (const [k, ms] of Object.entries(windows)) {
const since = now - ms;
const tokens = readCostLedger(since);
const energy = energyCost(since);
out[k] = { tokens: tokens.total, tokensCount: tokens.count, energy: energy.usd, energyKwh: energy.kwh, total: round(tokens.total + energy.usd, 4), byApp: tokens.byApp };
}
const series = collectCostSeries();
out.series7d = series.series7d;
out.byModel = series.byModel;
out.byProvider = series.byProvider;
return out;
}
// --- REVENUE side of the P&L (five gated engines) --------------------------
// Honest licensing-engine status: count the REAL in-house original designs that
// have been generated + settlement-passed in the Pattern Vault (sibling project).
function licensingNote() {
try {
const p = require('os').homedir() + '/Projects/pattern-vault/data/catalog.json';
const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
const items = Array.isArray(raw) ? raw : (raw.items || raw.designs || []);
const n = items.filter(i => i.category === 'in-house-original').length;
if (n > 0) return `${n} original designs generated + settlement-passed + listing-ready (Pattern Vault :9779) — awaiting Steve channel signup (Spoonflower/Etsy)`;
} catch (e) {}
return 'catalog built — awaiting Steve channel signups';
}
function defaultRevenue() {
return {
updatedAt: new Date().toISOString(),
engines: [
{ key: 'sell_product', label: 'Sell AbramsEgo (template/SaaS)', status: 'gated', amount: 0, note: 'Stripe + pricing + public landing — awaiting Steve go-live' },
{ key: 'affiliate', label: 'Affiliate / commission', status: 'gated', amount: 0, note: 'DW referral + directory lead-gen — awaiting affiliate signup + placement' },
{ key: 'billable_work', label: 'Agent does billable work', status: 'gated', amount: 0, note: 'arbitrage / paid reports / metered API — awaiting billing rail' },
{ key: 'ads', label: 'Ads on a public property', status: 'gated', amount: 0, note: 'ad network + public site — awaiting Steve go-live' },
{ key: 'licensing', label: 'License original designs (Pattern Vault + marketplaces)', status: 'gated', amount: 0, note: licensingNote() },
],
};
}
// dated income rows: {ts, engine, amount, source} appended by POST /api/revenue/record.
// Summed per-engine (all-time) and per-window so the P&L reflects REAL income by
// today/week/month rather than a flat total.
function readRevenueLedger() {
const now = Date.now();
const wins = { today: 24 * 3.6e6, week: 7 * 24 * 3.6e6, month: 30 * 24 * 3.6e6 };
const windowSums = { today: 0, week: 0, month: 0 };
const byEngine = {};
try {
const lines = fs.readFileSync(REVENUE_LEDGER, 'utf8').split('\n');
for (const ln of lines) {
if (!ln.trim()) continue;
let j; try { j = JSON.parse(ln); } catch (e) { continue; }
const t = Date.parse(j.ts); const a = Number(j.amount) || 0;
if (isNaN(t)) continue;
byEngine[j.engine] = (byEngine[j.engine] || 0) + a;
for (const [k, ms] of Object.entries(wins)) if (t >= now - ms) windowSums[k] += a;
}
} catch (e) {}
return { windowSums, byEngine };
}
// affiliate registry (engine 2): [{slug,label,url,program,note,added,status}].
// status:"draft" = candidate, Steve hasn't joined the program; "active" = joined
// (Steve's switch = editing the entry). Only active entries render publicly.
function readAffiliates() {
try {
const list = JSON.parse(fs.readFileSync(AFFILIATES_FILE, 'utf8'));
return Array.isArray(list) ? list : [];
} catch (e) { return []; }
}
function collectRevenue() {
let base;
try { base = JSON.parse(fs.readFileSync(REVENUE_FILE, 'utf8')); }
catch (e) { base = defaultRevenue(); try { fs.writeFileSync(REVENUE_FILE, JSON.stringify(base, null, 2)); } catch (e2) {} }
// a revenue.json written before a new engine existed would otherwise hide it
for (const def of defaultRevenue().engines) {
if (!(base.engines || []).some((e) => e && e.key === def.key)) base.engines = [...(base.engines || []), def];
}
const led = readRevenueLedger();
// fold real income into each engine; an engine with income is de-facto "live"
base.engines = (base.engines || []).map((e) => ({ ...e, amount: round(led.byEngine[e.key] || 0, 2), status: (led.byEngine[e.key] || 0) > 0 ? 'live' : e.status }));
// affiliate pill flips live ONLY when Steve has activated a registry entry
// (joined the program) — draft candidates don't count.
const affiliates = readAffiliates();
const activeAffiliates = affiliates.filter((a) => a && a.status === 'active').length;
base.engines = base.engines.map((e) => e.key === 'affiliate'
? { ...e, status: activeAffiliates > 0 ? 'live' : e.status, note: activeAffiliates > 0 ? `${activeAffiliates} active affiliate link${activeAffiliates > 1 ? 's' : ''} via /go/` : e.note }
: e);
// ads pill flips live ONLY when Steve has joined a network and set
// AD_NETWORK_PUB_ID; until then the landing slot serves a house ad.
// licensing note reflects the REAL count of settlement-passed originals in the Pattern Vault
base.engines = base.engines.map((e) => e.key === 'licensing' ? { ...e, note: licensingNote() } : e);
base.engines = base.engines.map((e) => e.key === 'ads'
? { ...e, status: AD_NETWORK_PUB_ID ? 'live' : e.status, note: AD_NETWORK_PUB_ID ? `ad network active (pub …${AD_NETWORK_PUB_ID.slice(-4)})` : e.note }
: e);
base.windowSums = led.windowSums;
base.total = round(Object.values(led.byEngine).reduce((s, v) => s + v, 0), 2);
return base;
}
function buildPnL(cost, revenue) {
const out = {};
for (const win of ['today', 'week', 'month']) {
const c = (cost[win] && cost[win].total) || 0;
// real dated income per window from the revenue ledger (0 until engines earn).
const rev = round((revenue.windowSums && revenue.windowSums[win]) || 0, 4);
const net = round(rev - c, 4);
out[win] = { cost: round(c, 4), revenue: round(rev, 4), net, selfFundingPct: c > 0 ? round((rev / c) * 100, 1) : null };
}
// Break-even target: how much daily/monthly revenue AbramsEgo must earn to pay
// for itself. Today's all-in cost is the daily target; ×30 gives the monthly.
const dayTarget = (out.today && out.today.cost) || 0;
out.breakEvenUsdPerDay = round(dayTarget, 4);
out.breakEvenUsdPerMonth = round(dayTarget * 30, 2);
const pct = out.today && out.today.selfFundingPct;
out.runwayNote = pct == null
? 'No cost logged today yet — break-even target not established.'
: pct >= 100
? `Self-funding: today's revenue covers ${pct}% of the $${out.breakEvenUsdPerDay}/day it costs to run. In the black.`
: `Break-even needs $${out.breakEvenUsdPerDay}/day ($${out.breakEvenUsdPerMonth}/mo). Currently at ${pct}% — need ${round(100 - pct, 1)} more points to self-fund.`;
return out;
}
// ---------------------------------------------------------------------------
// snapshot builder + stale-while-revalidate cache
// ---------------------------------------------------------------------------
// --- DATA FRESHNESS ---------------------------------------------------------
// Every tile that sources from a cached/dated file can be silently stale (the
// self-funding tile sourced revenue.json which sat 6 days old on Jul 2). This
// computes each real source's updatedAt + its age so the UI can mark a tile
// visibly stale ("as of Nh ago") instead of trusting a frozen number. Thresholds
// are per-source: fast loops (fleet/build) go stale in minutes, dated ledgers in
// hours/days. Grounded ONLY in timestamps the sources actually carry.
function computeFreshness(snap) {
const now = Date.now();
const age = (iso) => { const t = Date.parse(iso); return isNaN(t) ? null : Math.max(0, now - t); };
// src: [label, updatedAt-iso, warnMs, staleMs]
const HR = 3.6e6, MIN = 60e3, DAY = 24 * HR;
const rev = snap.revenue || {}, kam = snap.kamatera || {}, act = snap.activity || {};
const lf = snap.localFleet || {};
const srcs = [
{ key: 'snapshot', label: 'Snapshot', at: snap.builtAt, warn: 2 * MIN, stale: 5 * MIN },
{ key: 'revenue', label: 'Revenue ledger', at: rev.updatedAt, warn: 12 * HR, stale: 2 * DAY },
{ key: 'kamatera', label: 'Kamatera fleet', at: kam.fetchedAt, warn: 8 * MIN, stale: 15 * MIN },
{ key: 'localFleet', label: 'Local fleet', at: lf.fetchedAt || snap.builtAt, warn: 2 * MIN, stale: 6 * MIN },
{ key: 'activity', label: 'Git activity', at: act.fetchedAt, warn: 8 * MIN, stale: 20 * MIN },
{ key: 'officers', label: 'Officers', at: snap.officers && snap.officers.fetchedAt, warn: 2 * MIN, stale: 10 * MIN },
];
const out = {};
let worst = 'fresh';
for (const s of srcs) {
const a = age(s.at);
let state = 'unknown';
if (a != null) state = a >= s.stale ? 'stale' : a >= s.warn ? 'aging' : 'fresh';
out[s.key] = { label: s.label, updatedAt: s.at || null, ageMs: a, ageLabel: a == null ? null : humanAge(a), state };
if (state === 'stale') worst = 'stale';
else if (state === 'aging' && worst !== 'stale') worst = 'aging';
}
return { sources: out, worst, staleCount: Object.values(out).filter((s) => s.state === 'stale').length };
}
function humanAge(ms) {
const s = Math.round(ms / 1000);
if (s < 90) return s + 's ago';
const m = Math.round(s / 60); if (m < 90) return m + 'm ago';
const h = Math.round(m / 60); if (h < 36) return h + 'h ago';
return Math.round(h / 24) + 'd ago';
}
let SNAP = { builtAt: null };
let BUILDING = null; // in-flight build promise — callers coalesce onto it so an
// awaited /api/refresh (or a post-write rebuild) always
// returns FRESH data, never a stale in-flight snapshot.
async function doBuild() {
const wrap = async (fn) => { try { return await fn(); } catch (e) { return { error: e.message }; } };
const [system, localFleet, kamatera, canaries, crons, activity] = await Promise.all([
wrap(collectSystem), wrap(collectLocalFleet), wrap(collectKamateraFleet), wrap(collectCanaries), wrap(collectCrons), wrap(collectActivity),
]);
const [sessions, wins, officers] = await Promise.all([
wrap(() => fetchJSON(`${CNCP}/api/sessions`)),
wrap(() => fetchJSON(`${CNCP}/api/wins`)),
wrap(() => fetchJSON(`${CNCP}/api/officers`)),
]);
const cost = collectCost();
const revenue = collectRevenue();
const pnl = buildPnL(cost, revenue);
const usage = (() => { try { return collectUsage(); } catch (e) { return { error: e.message }; } })();
SNAP = {
builtAt: new Date().toISOString(),
version: VERSION,
system, localFleet, kamatera, canaries, crons, activity,
sessions: summarizeSessions(sessions),
wins: summarizeWins(wins),
officers: summarizeOfficers(officers),
cost, revenue, pnl, usage,
// compact form folded into the snapshot for at-a-glance headers
usageSummary: {
activeSessions: usage.sessions && usage.sessions.active,
recentSessions: usage.sessions && usage.sessions.recent,
subagentRuns: usage.subagentRuns && usage.subagentRuns.count,
topModel: usage.models && usage.models.allTime && usage.models.allTime[0] && usage.models.allTime[0].model,
},
};
// per-source data-freshness (computed last so it sees every collected key)
SNAP.freshness = computeFreshness(SNAP);
try { fs.writeFileSync(SNAPSHOT, JSON.stringify(SNAP)); } catch (e) {}
return SNAP;
}
function buildSnapshot() {
if (BUILDING) return BUILDING;
BUILDING = doBuild().finally(() => { BUILDING = null; });
return BUILDING;
}
// force a build that reflects state written AFTER any in-flight build started
// (used by the money-path write so recorded income shows up immediately).
async function rebuildFresh() { if (BUILDING) { try { await BUILDING; } catch (e) {} } return buildSnapshot(); }
function summarizeSessions(s) {
const arr = Array.isArray(s) ? s : (s && s.sessions) || [];
return { count: arr.length, recent: arr.slice(0, 12) };
}
function summarizeWins(w) {
const arr = Array.isArray(w) ? w : (w && w.wins) || [];
const today = new Date().toISOString().slice(0, 10);
// real wins-over-time: 7 daily buckets (local calendar day) from the actual
// wins array — a genuine time-series, not invented history. Empty days = 0.
const now = Date.now();
const dayKey = (ms) => {
const dt = new Date(ms);
return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}-${String(dt.getDate()).padStart(2, '0')}`;
};
const byKey = {}; const series7d = [];
for (let i = 6; i >= 0; i--) { const key = dayKey(now - i * 24 * 3.6e6); const b = { day: key, count: 0 }; series7d.push(b); byKey[key] = b; }
for (const x of arr) {
// wins carry either a `date` (YYYY-MM-DD, already local) or createdAt (epoch ms)
const key = (x.date && String(x.date).slice(0, 10)) || (x.createdAt ? dayKey(Number(x.createdAt)) : null);
if (key && byKey[key]) byKey[key].count++;
}
return { count: arr.length, today: arr.filter((x) => (x.date || '').slice(0, 10) === today).length, series7d, recent: arr.slice(0, 10) };
}
function summarizeOfficers(o) {
const arr = Array.isArray(o) ? o : (o && (o.cabinet || o.officers)) || [];
return {
count: arr.length,
fetchedAt: new Date().toISOString(),
officers: arr.map((x) => ({ vp: x.vp || x.name || x.id, domain: x.domain || (Array.isArray(x.triggers) ? x.triggers[0] : '') })),
};
}
// boot: load last snapshot from disk if present, then refresh
try { SNAP = Object.assign(JSON.parse(fs.readFileSync(SNAPSHOT, 'utf8')), { building: false }); } catch (e) {}
buildSnapshot();
setInterval(() => { buildSnapshot(); }, 30 * 1000);
// ---------------------------------------------------------------------------
// http
// ---------------------------------------------------------------------------
const app = express();
app.disable('x-powered-by');
// Stripe webhook FIRST — registered before express.json() so the body stays RAW
// (signature verification hashes the exact bytes), and before the Basic-Auth
// gate because Stripe can't send our credentials; the stripe-signature header
// is the authentication. Unsigned/bad-signature posts are rejected 400.
app.post('/api/stripe/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
if (!stripe || !STRIPE_WEBHOOK_SECRET) return res.status(503).json({ error: 'stripe not configured' });
let event;
try {
event = stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], STRIPE_WEBHOOK_SECRET);
} catch (e) {
return res.status(400).json({ error: `webhook signature verification failed: ${e.message}` });
}
if (event.type === 'checkout.session.completed') {
const s = event.data.object || {};
// same row shape as POST /api/revenue/record so the P&L folds it in as-is
const row = { ts: new Date().toISOString(), engine: 'sell_product', amount: round((s.amount_total || 0) / 100, 2), source: `stripe:${s.id}` };
try { fs.appendFileSync(REVENUE_LEDGER, JSON.stringify(row) + '\n'); }
catch (e) { return res.status(500).json({ error: 'ledger write failed' }); }
rebuildFresh().catch(() => {});
}
res.json({ received: true });
});
app.use(express.json({ limit: '256kb' }));
// Basic-Auth gate on everything EXCEPT /api/healthz (smoke tests + uptime probes)
const AUTH_USER = process.env.ABRAMSEGO_USER || 'admin';
const AUTH_PASS = process.env.ABRAMSEGO_PASS || 'DW2024!';
// PUBLIC_LANDING=1 opens ONLY the marketing surface: GET /landing (+ its html
// alias), GET /api/sell/config, POST /api/waitlist. The landing page is fully
// self-contained (inline CSS/JS) so no other static assets are needed. Default
// 0 = everything stays auth-gated. Flipping to 1 is Steve's gated switch —
// nothing in this repo sets it.
const PUBLIC_LANDING = (process.env.PUBLIC_LANDING || '0') === '1';
function isPublicLandingPath(req) {
if (req.method === 'GET') {
if (req.path === '/landing' || req.path === '/landing.html' || req.path === '/api/sell/config') return true;
// sample fleet-health report: canned demo data + SAMPLE watermark, never SNAP
if (req.path === '/api/report/sample') return true;
// ads surface: the ads.txt stub (crawled by ad networks, must be public)
// and the ad-slot config (pub id only — pub ids ship in every ad snippet).
if (req.path === '/ads.txt' || req.path === '/api/ads/config') return true;
// licensing surface (engine 5): cached catalog of LIVE wallpapersback.com
// designs + DRAFT tier copy — no money mechanism, images stay WPB-hosted.
if (req.path === '/api/licensing/catalog') return true;
// affiliate surface: tracked redirects + the non-draft strip data (labels
// and slugs only — the registry's urls/notes stay behind auth).
return req.path.startsWith('/go/') || req.path === '/api/affiliates';
}
if (req.method === 'POST') return req.path === '/api/waitlist';
return false;
}
app.use((req, res, next) => {
if (req.path === '/api/healthz') return next();
if (PUBLIC_LANDING && isPublicLandingPath(req)) return next();
const cred = basicAuth(req);
if (cred && cred.name === AUTH_USER && cred.pass === AUTH_PASS) return next();
res.set('WWW-Authenticate', 'Basic realm="AbramsEgo"');
return res.status(401).send('Auth required');
});
app.get('/api/healthz', (req, res) => res.json({ ok: true, service: 'abramsego', version: VERSION, uptimeSec: Math.round((Date.now() - START_TS) / 1000), snapshotAt: SNAP.builtAt }));
app.get('/api/data', (req, res) => { res.json(SNAP); });
app.get('/api/refresh', async (req, res) => { const s = await buildSnapshot(); res.json(s); });
// WPB marketplace sales — proxied server-to-server from the wpb-sales-dashboard (:9812).
// Backend-for-frontend: this server attaches the basic-auth header so the browser never
// sees creds and there's no CORS. Cached ~30s so the 15s tick doesn't hammer :9812.
const WPB_URL = process.env.WPB_URL || 'http://127.0.0.1:9812/api/data';
const WPB_AUTH = 'Basic ' + Buffer.from(`${process.env.WPB_USER || 'admin'}:${process.env.WPB_PASS || 'DW2024!'}`).toString('base64');
let _wpbCache = { at: 0, data: null };
app.get('/api/wpb', async (req, res) => {
if (_wpbCache.data && Date.now() - _wpbCache.at < 30000) return res.json(_wpbCache.data);
try {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 4000);
const r = await fetch(WPB_URL, { headers: { Authorization: WPB_AUTH }, signal: ctrl.signal });
clearTimeout(t);
if (!r.ok) return res.status(502).json({ ok: false, error: `wpb dashboard ${r.status}` });
const j = await r.json();
// slim payload — only what the panel needs
const slim = { ok: true, generatedAt: j.generatedAt, kpis: j.kpis, markets: (j.markets || []).map(m => ({ key: m.key, name: m.name, status: m.status, color: m.color, listings: m.listings, salesGross: m.salesGross, orders: m.orders })), launched: j.launched };
_wpbCache = { at: Date.now(), data: slim };
res.json(slim);
} catch (e) {
if (_wpbCache.data) return res.json(_wpbCache.data); // serve stale on transient failure
res.status(503).json({ ok: false, error: 'wpb dashboard unreachable (:9812 down?)' });
}
});
// per-panel slices (served from cache)
for (const key of ['system', 'localFleet', 'kamatera', 'canaries', 'crons', 'activity', 'sessions', 'wins', 'officers', 'cost', 'revenue', 'pnl', 'usage', 'freshness']) {
app.get(`/api/${key}`, (req, res) => res.json(SNAP[key] || {}));
}
// Record real income against a revenue engine (auth-gated by the middleware
// above). This is the ONLY money-touching write — it just logs income that
// actually landed; it does NOT charge anyone. Turning an engine's *mechanism*
// on (Stripe, ads, affiliate) stays gated to Steve.
// ── Point-and-click Unblock widget (TK-00088 slice 3) ────────────────────────
// AbramsEgo is a thin click SURFACE; CNCP stays the single execution backend
// (DTD verdict A). Serve the ONE shared widget file straight from CNCP's public
// dir (no drift), proxy the runner-health check, and FORWARD dispatches
// server-to-server to CNCP's localOnly /api/dispatch — the browser widget can't
// POST cross-origin to :3333, so AbramsEgo relays it (both are local on Mac2).
app.get('/unblock.js', (req, res) => {
res.set('Cache-Control', 'no-cache');
res.type('application/javascript');
res.sendFile(path.join(os.homedir(), 'cncp-starter', 'public', 'unblock.js'));
});
app.get('/api/yolo/status', async (req, res) => {
try { const r = await fetch(CNCP + '/api/yolo/status'); res.json(await r.json()); }
catch (e) { res.json({ error: e.message }); }
});
app.post('/api/dispatch', async (req, res) => {
try {
const body = Object.assign({}, req.body, { surface: (req.body && req.body.surface) || 'abramsego' });
const r = await fetch(CNCP + '/api/dispatch', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
});
res.status(r.status).json(await r.json().catch(() => ({ error: 'bad response from CNCP' })));
} catch (e) { res.status(502).json({ error: 'CNCP unreachable: ' + e.message }); }
});
const REVENUE_ENGINES = ['sell_product', 'affiliate', 'billable_work', 'ads', 'licensing'];
app.post('/api/revenue/record', async (req, res) => {
const b = req.body || {};
if (!REVENUE_ENGINES.includes(b.engine)) return res.status(400).json({ error: `engine must be one of ${REVENUE_ENGINES.join(', ')}` });
const amount = Number(b.amount);
if (!isFinite(amount)) return res.status(400).json({ error: 'amount must be a number' });
const row = { ts: new Date().toISOString(), engine: b.engine, amount: round(amount, 2), source: (b.source || '').toString().slice(0, 200) };
try { fs.appendFileSync(REVENUE_LEDGER, JSON.stringify(row) + '\n'); } catch (e) { return res.status(500).json({ error: 'write failed' }); }
await rebuildFresh();
res.json({ ok: true, recorded: row, pnl: SNAP.pnl && SNAP.pnl.today });
});
// Stripe checkout state for the landing page ("test keys not yet installed" note)
app.get('/api/stripe/status', (req, res) => res.json({ ...stripeStatus, tiers: Object.keys(TIERS) }));
// Sell config for the landing CTAs — safe for public exposure (PUBLIC_LANDING
// allowlists it): reports only whether checkout is ready and how, never keys
// or fleet internals. mode is "test" until Steve flips live himself.
app.get('/api/sell/config', (req, res) => res.json({
checkoutReady: !!stripe,
mode: stripe ? 'test' : 'none',
checkoutPath: '/api/checkout',
tiers: Object.keys(TIERS),
note: stripeStatus.note,
}));
// Create a Checkout Session for a paid tier (DTD 2026-07-01: Option B —
// server-created sessions). TEST mode only; 503 until sk_test_ keys land.
// Subscriptions priced inline so nothing has to pre-exist in the dashboard.
app.post('/api/checkout', async (req, res) => {
if (!stripe) return res.status(503).json({ error: 'stripe not configured', note: stripeStatus.note });
const tierKey = ((req.body || {}).tier || '').toString();
const tier = TIERS[tierKey];
if (!tier) return res.status(400).json({ error: `tier must be one of ${Object.keys(TIERS).join(', ')}` });
const base = `${req.protocol}://${req.get('host')}`;
try {
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{
quantity: 1,
price_data: {
currency: 'usd',
unit_amount: tier.usd * 100,
recurring: { interval: 'month' },
product_data: { name: tier.label },
},
}],
metadata: { engine: 'sell_product', tier: tierKey },
success_url: `${base}/landing?checkout=success`,
cancel_url: `${base}/landing?checkout=cancel`,
});
res.json({ ok: true, url: session.url, id: session.id, mode: 'test' });
} catch (e) {
res.status(502).json({ error: `stripe error: ${e.message}` });
}
});
// === maxit-09: AI chat over the snapshot, LOCAL OLLAMA ($0, cap-relieving) ===
// Natural-language Q&A grounded in the current snapshot. Uses LOCAL Ollama so it
// is $0/unmetered and does NOT consume the Anthropic Max session cap. Endpoints
// are tried in priority order — Mac1 (192.168.1.133) first, then Mac2 localhost.
// If neither is reachable, a deterministic grounded summary is returned; no paid
// API is ever called. The cost line "$0 (local ollama)" is ALWAYS in the payload.
const OLLAMA_ENDPOINTS = (process.env.OLLAMA_URLS
? process.env.OLLAMA_URLS.split(',').map((s) => s.trim()).filter(Boolean)
: ['http://192.168.1.133:11434', 'http://localhost:11434']);
const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'qwen3:14b';
const OLLAMA_TIMEOUT_MS = parseInt(process.env.OLLAMA_TIMEOUT_MS, 10) || 8000;
const CHAT_COST_LINE = '$0 (local ollama)';
// Build a compact, human-readable context string from the current snapshot
// (costs, fleet, crons, sessions) so the local model has grounding without the
// full JSON blowing the prompt budget.
function chatContext() {
const f = SNAP || {};
const c = f.cost && f.cost.today;
const p = f.pnl && f.pnl.today;
const cr = f.crons;
const u = f.usageSummary || {};
const canDown = (f.canaries && f.canaries.canaries || []).filter((x) => x.verdict && /DOWN|FAIL|DEGRADED/i.test(x.verdict));
return [
`Snapshot built: ${f.builtAt || 'n/a'} (AbramsEgo v${f.version || '?'}).`,
`System: load ${f.system && f.system.loadPct}% of ${f.system && f.system.cpus} cores, RAM ${f.system && f.system.memUsedPct}% used, disk ${f.system && f.system.diskUsedPct}%.`,
`Local fleet (Mac2 pm2): ${f.localFleet && f.localFleet.up}/${f.localFleet && f.localFleet.total} online.`,
`Kamatera fleet: ${f.kamatera && f.kamatera.up}/${f.kamatera && f.kamatera.total} online.`,
`Crons/launchd: ${cr && cr.loadedCount}/${cr && cr.count} loaded.`,
`Cost today: $${c && c.total} total (tokens $${c && c.tokens} + energy $${c && c.energy} est). Self-funding today: ${p && p.selfFundingPct}%.`,
`Sessions: ${u.activeSessions} active, ${u.recentSessions} recent(24h); sub-agent runs ${u.subagentRuns}; top model ${u.topModel}.`,
`Wins today: ${f.wins && f.wins.today}.`,
canDown.length ? `Canaries needing attention: ${canDown.map((x) => `${x.name}=${x.verdict}`).join(', ')}.` : 'All reporting canaries healthy.',
].join('\n');
}
// POST one prompt to a single Ollama endpoint (non-streaming) with a hard timeout.
async function askOllama(endpoint, prompt) {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), OLLAMA_TIMEOUT_MS);
try {
const r = await fetch(`${endpoint}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: OLLAMA_MODEL, stream: false, prompt }),
signal: ctrl.signal,
});
if (!r.ok) return null;
const j = await r.json();
const answer = (j.response || '').trim();
return answer || null;
} catch (e) { return null; } finally { clearTimeout(t); }
}
app.post('/api/chat', async (req, res) => {
const q = (req.body && req.body.q || '').toString().slice(0, 500);
if (!q) return res.status(400).json({ error: 'empty question' });
const ctx = chatContext();
const prompt = `You are AbramsEgo, Steve's local AI-agent command center. Answer the question concisely and factually using ONLY this live fleet snapshot:\n\n${ctx}\n\nQuestion: ${q}\nAnswer:`;
// Try each Ollama endpoint in priority order (Mac1 → Mac2 fallback).
for (const endpoint of OLLAMA_ENDPOINTS) {
const answer = await askOllama(endpoint, prompt);
if (answer) return res.json({ answer, engine: `ollama:${OLLAMA_MODEL}@${endpoint.replace(/^https?:\/\//, '')}`, cost: CHAT_COST_LINE, cost_usd: 0 });
}
// Neither Ollama reachable → deterministic grounded summary (never a paid API).
res.json({ answer: `No local Ollama reachable — grounded summary instead:\n${ctx}`, engine: 'local-template', cost: CHAT_COST_LINE, cost_usd: 0 });
});
// === end maxit-09 chat ======================================================
// --- SELL-IT landing (local, publish-gated) --------------------------------
// Explicit route so /landing serves the marketing page even though it's still
// behind the Basic-Auth middleware above. Going public/unauth is a gated step.
const LANDING_FILE = path.join(__dirname, 'public', 'landing.html');
app.get('/landing', (req, res) => res.sendFile(LANDING_FILE));
// --- The Dungeon: top-down game view of the whole agent fleet ---------------
// Each officer = a chamber, pm2 procs = workers, canaries = sentries, live
// sessions = players; avatars wander + click-to-zoom shows what each is doing.
app.get('/dungeon', (req, res) => res.sendFile(path.join(__dirname, 'public', 'dungeon.html')));
// --- billable_work engine (engine 3): sellable Fleet Health Report ----------
// The dashboard's own snapshot rendered as a polished, self-contained HTML
// artifact a customer would pay for. /api/report/fleet-health = real data,
// auth-gated like everything else. /api/report/sample = canned DEMO data with
// a SAMPLE watermark, safe for the public landing allowlist — it never touches
// SNAP, so no real fleet/cost internals can leak through it.
const escHtml = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
const fmtWhen = (iso) => {
if (!iso) return '—';
const d = new Date(iso);
return isNaN(d) ? '—' : d.toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
};
function renderFleetHealthReport(snap, { sample = false } = {}) {
const now = new Date();
const lf = snap.localFleet || {};
const kam = snap.kamatera || {};
const cans = (snap.canaries && snap.canaries.canaries) || [];
const cost = snap.cost || {};
const pnl = snap.pnl || {};
const sys = snap.system || {};
const money = (n) => n == null ? '—' : `$${round(Number(n) || 0, 2).toFixed(2)}`;
const fleetRows = (procs) => (procs || []).slice(0, 40).map((p) => `
<tr><td>${escHtml(p.name)}</td>
<td><span class="pill ${p.status === 'online' ? 'ok' : 'bad'}">${escHtml(p.status || 'unknown')}</span></td>
<td>${p.restarts == null ? '—' : escHtml(p.restarts)}</td>
<td title="${escHtml(p.startedAt || '')}">${fmtWhen(p.startedAt)}</td></tr>`).join('');
const canaryVerdictClass = (v) => /FAIL|DOWN|CRIT/i.test(v || '') ? 'bad' : /WARN|DEGRAD/i.test(v || '') ? 'warn' : v ? 'ok' : '';
const canaryRows = cans.slice(0, 30).map((c) => `
<tr><td>${escHtml(c.name)}</td>
<td><span class="pill ${canaryVerdictClass(c.verdict)}">${escHtml(c.verdict || 'n/a')}</span></td>
<td title="${escHtml(c.mtime || '')}">${fmtWhen(c.mtime)}</td>
<td>${c.stale ? '⚠️ stale' : 'fresh'}</td></tr>`).join('');
// 7-day cost trend as pure-CSS bars so the report stays one self-contained file
const series = cost.series7d || [];
const maxDay = Math.max(0.01, ...series.map((d) => d.total || 0));
const trendBars = series.map((d) => `
<div class="bar-col" title="${escHtml(d.day)}: $${d.total} (tokens $${d.tokens} + energy $${d.energy} est)">
<div class="bar" style="height:${Math.max(3, Math.round((d.total / maxDay) * 90))}px"></div>
<div class="bar-v">$${round(d.total, 2)}</div>
<div class="bar-d">${escHtml(String(d.day).slice(5))}</div>
</div>`).join('');
const pnlRows = ['today', 'week', 'month'].map((w) => {
const p = pnl[w] || {};
return `<tr><td>${w}</td><td>${money(p.cost)}</td><td>${money(p.revenue)}</td>
<td class="${(p.net || 0) >= 0 ? 'good' : 'red'}">${money(p.net)}</td>
<td>${p.selfFundingPct == null ? '—' : p.selfFundingPct + '%'}</td></tr>`;
}).join('');
const notes = [];
if (sys.dashboardUptimeSec != null) notes.push(`Command center itself has been up ${Math.floor(sys.dashboardUptimeSec / 3600)}h ${Math.floor((sys.dashboardUptimeSec % 3600) / 60)}m.`);
if (lf.note) notes.push(`Local fleet: ${lf.note}.`);
if (kam.note) notes.push(`Remote fleet: ${kam.note}.`);
const staleCans = cans.filter((c) => c.stale).length;
if (staleCans) notes.push(`${staleCans} canary heartbeat${staleCans > 1 ? 's are' : ' is'} >24h old — investigate before trusting their last verdicts.`);
const downProcs = [...(lf.procs || []), ...(kam.procs || [])].filter((p) => p.status && p.status !== 'online');
if (downProcs.length) notes.push(`Processes not online: ${downProcs.map((p) => p.name).slice(0, 10).join(', ')}.`);
if (!notes.length) notes.push('All monitored surfaces healthy at generation time.');
return `<!doctype html><html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>AbramsEgo — Fleet Health Report</title>
<style>
:root{--bg:#0c0e12;--panel:#14171d;--line:#242a33;--fg:#e8e6e0;--dim:#8a92a0;--gold:#d4a94f;--ok:#3fb96a;--warn:#d4a94f;--bad:#e0563f}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.55 -apple-system,'Helvetica Neue',Segoe UI,sans-serif}
.wrap{max-width:900px;margin:0 auto;padding:28px 22px 60px}
header{display:flex;align-items:center;gap:14px;border-bottom:1px solid var(--line);padding-bottom:18px;margin-bottom:22px;flex-wrap:wrap}
.logo{font-size:22px;letter-spacing:.4px}.logo b{color:var(--gold)}
.rtitle{font-size:13px;text-transform:uppercase;letter-spacing:2px;color:var(--dim)}
.price{margin-left:auto;background:var(--gold);color:#14120a;font-weight:700;border-radius:8px;padding:6px 14px;font-size:16px}
.when{color:var(--dim);font-size:13px;width:100%}
${sample ? '.watermark{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;z-index:9}.watermark span{transform:rotate(-28deg);font-size:96px;font-weight:800;letter-spacing:10px;color:rgba(212,169,79,.07)}' : ''}
.banner{background:#2a2312;border:1px solid var(--gold);color:var(--gold);border-radius:8px;padding:8px 14px;font-size:13px;margin-bottom:20px}
section{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:18px 20px;margin-bottom:18px}
h2{margin:0 0 12px;font-size:16px;letter-spacing:.5px}h2 .k{color:var(--dim);font-weight:400;font-size:12px;margin-left:8px}
table{width:100%;border-collapse:collapse;font-size:13.5px}
th{color:var(--dim);text-align:left;font-weight:500;padding:4px 8px;border-bottom:1px solid var(--line);text-transform:uppercase;font-size:11px;letter-spacing:1px}
td{padding:5px 8px;border-bottom:1px solid #1b2027}tr:last-child td{border-bottom:0}
.pill{border-radius:20px;padding:1px 9px;font-size:11.5px;background:#20262f;color:var(--dim)}
.pill.ok{background:#14301e;color:var(--ok)}.pill.warn{background:#2f2711;color:var(--warn)}.pill.bad{background:#33170f;color:var(--bad)}
.good{color:var(--ok)}.red{color:var(--bad)}
.fleet-sum{display:flex;gap:26px;margin-bottom:12px;flex-wrap:wrap}
.fleet-sum .n{font-size:26px;font-weight:700}.fleet-sum .n.ok{color:var(--ok)}.fleet-sum .l{color:var(--dim);font-size:12px}
.trend{display:flex;align-items:flex-end;gap:10px;padding:8px 4px 0}
.bar-col{flex:1;text-align:center}.bar{background:linear-gradient(180deg,var(--gold),#8a6c2e);border-radius:4px 4px 0 0;margin:0 auto;width:70%}
.bar-v{font-size:10.5px;color:var(--dim);margin-top:4px}.bar-d{font-size:10.5px;color:var(--dim)}
ul{margin:6px 0 0;padding-left:20px}li{margin-bottom:5px}
footer{color:var(--dim);font-size:12px;text-align:center;margin-top:26px}
</style></head><body>
${sample ? '<div class="watermark"><span>SAMPLE</span></div>' : ''}
<div class="wrap">
<header>
<div class="logo"><b>Abrams</b>Ego</div>
<div class="rtitle">Fleet Health Report</div>
<div class="price">$${REPORT_PRICE_USD}</div>
<div class="when" title="${now.toISOString()}">🕓 Generated ${now.toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}${sample ? ' · demo data' : ''}</div>
</header>
${sample ? '<div class="banner">SAMPLE REPORT — illustrative demo data. Your report is generated live from your own fleet.</div>' : ''}
<section>
<h2>Fleet status<span class="k">local + remote process managers</span></h2>
<div class="fleet-sum">
<div><div class="n ${lf.up === lf.total ? 'ok' : ''}">${lf.up != null ? lf.up : '—'}/${lf.total != null ? lf.total : '—'}</div><div class="l">local processes online</div></div>
<div><div class="n ${kam.up === kam.total ? 'ok' : ''}">${kam.up != null ? kam.up : '—'}/${kam.total != null ? kam.total : '—'}</div><div class="l">remote (cloud) processes online</div></div>
<div><div class="n">${(snap.crons && snap.crons.loadedCount) != null ? snap.crons.loadedCount : '—'}</div><div class="l">scheduled jobs loaded</div></div>
</div>
<table><tr><th>process</th><th>status</th><th>restarts</th><th>🕓 started</th></tr>
${fleetRows(lf.procs)}${fleetRows(kam.procs)}</table>
</section>
<section>
<h2>Canary verdicts<span class="k">health heartbeats across the fleet</span></h2>
${canaryRows ? `<table><tr><th>canary</th><th>verdict</th><th>🕓 last heartbeat</th><th>freshness</th></tr>${canaryRows}</table>` : '<p class="l" style="color:var(--dim)">No canary heartbeats found.</p>'}
</section>
<section>
<h2>Cost trend · 7 days<span class="k">tokens + energy (est), per local calendar day</span></h2>
${series.length ? `<div class="trend">${trendBars}</div>` : '<p style="color:var(--dim)">No cost series available.</p>'}
</section>
<section>
<h2>P&L<span class="k">what the fleet costs vs what it earns</span></h2>
<table><tr><th>window</th><th>cost</th><th>revenue</th><th>net</th><th>self-funding</th></tr>${pnlRows}</table>
${pnl.runwayNote ? `<p style="color:var(--dim);font-size:13px;margin:10px 0 0">${escHtml(pnl.runwayNote)}</p>` : ''}
</section>
<section>
<h2>Uptime notes</h2>
<ul>${notes.map((n) => `<li>${escHtml(n)}</li>`).join('')}</ul>
</section>
<footer>AbramsEgo · self-funding AI-agent command center · report price $${REPORT_PRICE_USD} · generated ${now.toISOString()}</footer>
</div></body></html>`;
}
// Canned demo snapshot for the public sample — deliberately NOT derived from
// SNAP so nothing real (process names, spend, canary names) can leak publicly.
function demoReportData() {
const now = Date.now();
const iso = (msAgo) => new Date(now - msAgo).toISOString();
const day = (i) => {
const d = new Date(now - i * 24 * 3.6e6);
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
};
return {
system: { dashboardUptimeSec: 63 * 3600 + 12 * 60 },
localFleet: {
up: 5, total: 6,
procs: [
{ name: 'agent-orchestrator', status: 'online', restarts: 0, startedAt: iso(52 * 3.6e6) },
{ name: 'storefront-api', status: 'online', restarts: 1, startedAt: iso(30 * 3.6e6) },
{ name: 'scraper-worker', status: 'online', restarts: 2, startedAt: iso(20 * 3.6e6) },
{ name: 'image-pipeline', status: 'online', restarts: 0, startedAt: iso(76 * 3.6e6) },
{ name: 'metrics-collector', status: 'online', restarts: 0, startedAt: iso(76 * 3.6e6) },
{ name: 'batch-mailer', status: 'stopped', restarts: 4, startedAt: null },
],
},
kamatera: {
up: 3, total: 3,
procs: [
{ name: 'public-site', status: 'online', restarts: 0, startedAt: iso(200 * 3.6e6) },
{ name: 'lead-api', status: 'online', restarts: 1, startedAt: iso(180 * 3.6e6) },
{ name: 'cron-runner', status: 'online', restarts: 0, startedAt: iso(200 * 3.6e6) },
],
},
crons: { loadedCount: 14 },
canaries: { canaries: [
{ name: 'uptime-probe', verdict: 'PASS', mtime: iso(9 * 60e3), stale: false },
{ name: 'backup-freshness', verdict: 'PASS', mtime: iso(3 * 3.6e6), stale: false },
{ name: 'disk-space', verdict: 'WARN', mtime: iso(25 * 60e3), stale: false },
{ name: 'price-integrity', verdict: 'PASS', mtime: iso(11 * 3.6e6), stale: false },
{ name: 'leak-scanner', verdict: 'PASS', mtime: iso(30 * 3.6e6), stale: true },
] },
cost: { series7d: [6, 5, 4, 3, 2, 1, 0].map((i, idx) => {
const tokens = [3.1, 4.6, 2.2, 5.8, 3.9, 4.4, 2.7][idx];
return { day: day(i), tokens, energy: 0.65, total: round(tokens + 0.65, 2) };
}) },
pnl: {
today: { cost: 3.35, revenue: 4.5, net: 1.15, selfFundingPct: 134.3 },
week: { cost: 26.4, revenue: 18.0, net: -8.4, selfFundingPct: 68.2 },
month: { cost: 104.9, revenue: 61.5, net: -43.4, selfFundingPct: 58.6 },
runwayNote: "Break-even needs $3.35/day ($100.50/mo). Today's revenue covers 134% of run cost — in the black.",
},
};
}
// Real report — sits behind the Basic-Auth middleware like every other route.
app.get('/api/report/fleet-health', (req, res) => {
res.type('html').send(renderFleetHealthReport(SNAP));
});
// Public-safe sample (PUBLIC_LANDING allowlists it): demo data + watermark.
app.get('/api/report/sample', (req, res) => {
res.type('html').send(renderFleetHealthReport(demoReportData(), { sample: true }));
});
// --- Affiliate engine (engine 2): tracked /go/:slug redirects ---------------
// Registry-driven: data/affiliates.json maps slug → url. Every hit logs a
// click row to data/affiliate-clicks.jsonl then 302s to the target. Drafts
// redirect too (harmless — plain product pages) but only "active" entries are
// exposed via /api/affiliates for the landing strip. No commission mechanism
// exists here; Steve joining a program (and editing status→active) is the
// gated switch that turns this into money.
app.get('/go/:slug', (req, res) => {
const slug = (req.params.slug || '').toString().slice(0, 60);
const entry = readAffiliates().find((a) => a && a.slug === slug);
if (!entry || !entry.url) return res.status(404).json({ error: 'unknown affiliate slug' });
const row = {
ts: new Date().toISOString(),
slug,
ua: (req.headers['user-agent'] || '').toString().slice(0, 200),
ref: (req.headers.referer || '').toString().slice(0, 200),
};
try { fs.appendFileSync(AFFILIATE_CLICKS, JSON.stringify(row) + '\n'); } catch (e) {}
res.redirect(302, entry.url);
});
// Non-draft strip data for the landing "tools we run on" row. Public-safe:
// labels + /go/ slugs only, and empty until Steve activates an entry — so
// nothing customer-facing changes while the registry is all drafts.
app.get('/api/affiliates', (req, res) => {
const active = readAffiliates()
.filter((a) => a && a.status && a.status !== 'draft')
.map((a) => ({ slug: a.slug, label: a.label }));
res.json({ count: active.length, affiliates: active });
});
// --- Ads engine (engine 4): house-ad slot config -----------------------------
// The landing footer ad slot asks this endpoint what to render: no pub id →
// house ad (self-promo for the Fleet Health Report / waitlist), pub id set →
// the generic network container. Joining an ad network (and setting
// AD_NETWORK_PUB_ID + the real ads.txt line) is Steve's gated switch; public/
// ads.txt ships as a comment-only stub until then.
app.get('/api/ads/config', (req, res) => {
res.json({ enabled: !!AD_NETWORK_PUB_ID, pubId: AD_NETWORK_PUB_ID || null });
});
// --- Licensing engine (engine 5): WPB design licensing catalog --------------
// Read-only cache of designs that are LIVE on wallpapersback.com (already
// settlement-passed + published; unpublished/user_removed are never eligible).
// Pricing in the file is DRAFT copy only — no listing, sale, or outreach
// happens here; external channel signups are Steve's gated switch (see
// memos/licensing-channels.md). Refresh = re-run the read-only feed pull.
const LICENSING_CATALOG_FILE = path.join(DATA_DIR, 'wpb-licensing-catalog.json');
app.get('/api/licensing/catalog', (req, res) => {
try { res.json(JSON.parse(fs.readFileSync(LICENSING_CATALOG_FILE, 'utf8'))); }
catch (e) { res.json({ count: 0, designs: [], note: 'catalog not built yet' }); }
});
// Waitlist email capture from the landing page → appends to data/waitlist.jsonl.
// This is NOT a money mechanism — it only collects interest; no Stripe, no
// charge, no send. One row per signup; basic email validation + dedupe-friendly.
const WAITLIST_FILE = path.join(DATA_DIR, 'waitlist.jsonl');
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
app.post('/api/waitlist', (req, res) => {
const b = req.body || {};
const email = (b.email || '').toString().trim().toLowerCase().slice(0, 254);
if (!EMAIL_RE.test(email)) return res.status(400).json({ error: 'valid email required' });
const row = {
ts: new Date().toISOString(),
email,
tier: (b.tier || '').toString().slice(0, 40),
source: (b.source || 'landing').toString().slice(0, 60),
ua: (req.headers['user-agent'] || '').toString().slice(0, 200),
};
try { fs.appendFileSync(WAITLIST_FILE, JSON.stringify(row) + '\n'); }
catch (e) { return res.status(500).json({ error: 'write failed' }); }
res.json({ ok: true, added: { email: row.email, ts: row.ts } });
});
app.use(express.static(path.join(__dirname, 'public'), { extensions: ['html'] }));
app.listen(PORT, '127.0.0.1', () => {
console.log(`AbramsEgo v${VERSION} on http://127.0.0.1:${PORT} (auth: ${AUTH_USER})`);
});