← back to AbramsEgo
feat: local sell-it landing + pricing + waitlist (publish gated)
0e2c42bbea779c39ced51f228d46bda049f314fa · 2026-07-01 11:17:00 -0700 · Steve (AbramsEgo)
- public/landing.html: dark-theme marketing page (reuses index.html CSS vars) —
hero 'the dashboard that pays for itself', self-funding P&L angle, 6-feature
grid, 3-tier pricing ($0 self-host / $19 hosted / $49 pro), waitlist capture.
Proof-strip pulls REAL /api/data numbers (not mocked).
- server.js: GET /landing (auth-gated) + POST /api/waitlist -> data/waitlist.jsonl
with email validation; both registered before static middleware.
- .gitignore: exclude data/waitlist.jsonl + data/revenue-ledger.jsonl (PII).
- memos/: go-live memo (Stripe/public/checkout/fulfillment) with officer
APPROVE/REVISE/BLOCK blocks — queue-dir write was harness-blocked, kept in-repo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
M .gitignoreR100 build-queue/tasks/04-sell-landing.md build-queue/done/04-sell-landing.mdA memos/070126A-abramsego-sellit-golive.mdA public/landing.htmlM server.js
Diff
commit 0e2c42bbea779c39ced51f228d46bda049f314fa
Author: Steve (AbramsEgo) <steve@designerwallcoverings.com>
Date: Wed Jul 1 11:17:00 2026 -0700
feat: local sell-it landing + pricing + waitlist (publish gated)
- public/landing.html: dark-theme marketing page (reuses index.html CSS vars) —
hero 'the dashboard that pays for itself', self-funding P&L angle, 6-feature
grid, 3-tier pricing ($0 self-host / $19 hosted / $49 pro), waitlist capture.
Proof-strip pulls REAL /api/data numbers (not mocked).
- server.js: GET /landing (auth-gated) + POST /api/waitlist -> data/waitlist.jsonl
with email validation; both registered before static middleware.
- .gitignore: exclude data/waitlist.jsonl + data/revenue-ledger.jsonl (PII).
- memos/: go-live memo (Stripe/public/checkout/fulfillment) with officer
APPROVE/REVISE/BLOCK blocks — queue-dir write was harness-blocked, kept in-repo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
.gitignore | 2 +
build-queue/{tasks => done}/04-sell-landing.md | 0
memos/070126A-abramsego-sellit-golive.md | 52 +++++
public/landing.html | 261 +++++++++++++++++++++++++
server.js | 27 +++
5 files changed, 342 insertions(+)
diff --git a/.gitignore b/.gitignore
index 8a1f5c2..424e29b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,5 @@ data/kamatera-fleet.json
# ephemeral singleton lock (never commit)
build-queue/.loop.lock/
+data/waitlist.jsonl
+data/revenue-ledger.jsonl
diff --git a/build-queue/tasks/04-sell-landing.md b/build-queue/done/04-sell-landing.md
similarity index 100%
rename from build-queue/tasks/04-sell-landing.md
rename to build-queue/done/04-sell-landing.md
diff --git a/memos/070126A-abramsego-sellit-golive.md b/memos/070126A-abramsego-sellit-golive.md
new file mode 100644
index 0000000..8918026
--- /dev/null
+++ b/memos/070126A-abramsego-sellit-golive.md
@@ -0,0 +1,52 @@
+# GATED — Take "Sell AbramsEgo" from local preview to a real, paid product
+
+> **NOTE:** This memo was meant for `~/.claude/yolo-queue/pending-approval/070126A-abramsego-sellit-golive.md`,
+> but the harness blocks autonomous writes to that sensitive path. It lives here in-repo instead.
+> Steve: copy it into the approval queue, or just act on it here.
+
+**Created:** 2026-07-01 · **Owner:** vp-special-projects (product-builder side) · **Repo:** `~/Projects/AbramsEgo/`
+
+## Where it stands (DONE — local, non-gated, committed)
+The sell-it surface is built and running behind Basic Auth on pm2 `abramsego` (:9773):
+- `public/landing.html` — polished dark-theme marketing page: hero ("the dashboard that pays for itself"), the self-funding P&L angle, 6-feature grid, **3-tier pricing ($0 self-host / $19/mo hosted / $49/mo Pro)**, and a waitlist capture. The proof-strip pulls REAL numbers from `/api/data` (fleet count, 30d cost, self-funding %, canary count) so it isn't mocked.
+- `GET /landing` route (still auth-gated) + `POST /api/waitlist` → appends to `data/waitlist.jsonl` (gitignored — it's PII).
+- **No Stripe, no public exposure, no charge, no domain.** Nothing customer-facing yet.
+
+Verified: `/landing` returns 200 with auth / 401 without; waitlist POST validates email, appends a row, rejects bad input.
+
+## GATED — the steps to actually sell (each needs Steve: APPROVE / REVISE / BLOCK)
+
+### 1. Stripe product + prices
+Create in Stripe: one **Product "AbramsEgo Hosted"** with two recurring prices — **$19/mo** and **$49/mo (Pro)** — and treat **$0 Self-host** as a no-charge tier (template download / repo access, no Stripe object). Capture the price IDs into the secrets registry (`STRIPE_PRICE_HOSTED`, `STRIPE_PRICE_PRO`) via the `secrets` skill.
+- Cost to set up: **$0** (Stripe fees are per-transaction, 2.9% + 30¢, on real sales only).
+- Why gated: creating live Stripe pricing = money/identity + a pricing decision that's yours.
+- **Decision for Steve:** confirm the $19 / $49 numbers; monthly-only or add annual? Which Stripe account (DW vs a new AbramsEgo account)?
+- Officer rec: **APPROVE** (with final prices confirmed) — [ ] APPROVE [ ] REVISE [ ] BLOCK
+
+### 2. Public, unauthenticated landing
+Serve `/landing` (and only `/landing` + `/api/waitlist`) WITHOUT Basic Auth so real prospects can see it, while the dashboard itself (`/`, `/api/*`) stays gated. Requires a small middleware change (allowlist `/landing` + `/api/waitlist` like `/api/healthz` already is) and a public host to be reachable.
+- Why gated: exposing anything to the open internet is customer-facing + indexable.
+- **Decision for Steve:** host on Kamatera behind nginx at a subdomain, or keep it Mac2-local via a tunnel? Indexed (SEO) or unlisted at first?
+- Officer rec: **REVISE** — soft launch: public landing at an unlisted subdomain, `noindex`, waitlist-only, no checkout yet. [ ] APPROVE [ ] REVISE [ ] BLOCK
+
+### 3. Checkout
+Wire the pricing CTAs to **Stripe Checkout** (hosted payment page) or Payment Links — cheapest correct path is a Stripe **Payment Link** per price (no server code, no card handling on our box). Success/cancel URLs return to `/landing`.
+- Cost: **$0** to wire; Stripe's per-sale fee only.
+- Why gated: takes real payment = money. Needs #1 first.
+- **Decision for Steve:** Payment Links (zero-code, fastest) vs a `/api/checkout` session endpoint (more control, more surface)? Recommend Payment Links to start.
+- Officer rec: **APPROVE** (Payment Links, after #1) — [ ] APPROVE [ ] REVISE [ ] BLOCK
+
+### 4. Fulfillment
+Define what a paying customer gets and automate delivery:
+- **Self-host ($0):** access to the template (private repo invite or a zip download link).
+- **Hosted ($19) / Pro ($49):** provision a hosted instance (pm2 process + subdomain per customer, or a shared multi-tenant instance) and email credentials. On a Stripe `checkout.session.completed` webhook → create the account + send the welcome email via George.
+- Why gated: sends real email to real customers + provisions infra + spend (hosting). Needs #1–#3.
+- **Decision for Steve:** manual fulfillment for the first N customers (I email you each new paid signup, you provision), or build the webhook→provision automation up front? Recommend **manual-first** — validate demand before automating.
+- Officer rec: **REVISE** — manual fulfillment for launch, automate once there's real volume. [ ] APPROVE [ ] REVISE [ ] BLOCK
+
+## Suggested sequence
+Soft launch first: **#2 (public waitlist-only landing, noindex)** to gauge interest with zero payment risk. Only once there's a real waitlist do #1 → #3 → #4. Every irreversible/money step stays behind demand you can see in `data/waitlist.jsonl`.
+
+## Cost summary
+- Build so far: **$0 (local)**.
+- Go-live: **$0 setup** across Stripe + landing + checkout; ongoing = Stripe 2.9%+30¢ per real sale + whatever hosting the Hosted/Pro tiers consume (only if sold).
diff --git a/public/landing.html b/public/landing.html
new file mode 100644
index 0000000..a53a67b
--- /dev/null
+++ b/public/landing.html
@@ -0,0 +1,261 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<title>AbramsEgo · the dashboard that pays for itself</title>
+<meta name="description" content="A self-hostable AI-agent command center with a self-funding P&L — fleet monitoring, canaries, token+energy cost accounting, and a grounded AI chat. A dashboard that pays for itself." />
+<style>
+ /* — reuse the command-center dark theme + CSS vars — */
+ :root{
+ --bg:#0a0e14; --panel:#111823; --panel2:#0d141d; --line:#1e2a3a; --ink:#dbe6f3;
+ --dim:#7d8fa6; --accent:#4d9fff; --good:#3fd18b; --warn:#ffb454; --bad:#ff5d6c;
+ --gold:#ffd166; --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
+ }
+ *{box-sizing:border-box}
+ body{margin:0;background:radial-gradient(1200px 600px at 70% -10%,#12203300,#0a0e14),var(--bg);
+ color:var(--ink);font:14px/1.55 var(--mono);-webkit-font-smoothing:antialiased}
+ a{color:var(--accent);text-decoration:none}
+ header{display:flex;align-items:center;gap:14px;padding:12px 20px;border-bottom:1px solid var(--line);
+ position:sticky;top:0;background:rgba(10,14,20,.9);backdrop-filter:blur(8px);z-index:5}
+ .logo{font-weight:700;letter-spacing:.5px;font-size:16px}
+ .logo b{color:var(--accent)}
+ .nav{margin-left:auto;display:flex;gap:18px;align-items:center;font-size:12px}
+ .nav a{color:var(--dim)}.nav a:hover{color:var(--ink)}
+ .btn{display:inline-block;background:var(--accent);color:#04101f;font-weight:700;border:0;
+ border-radius:9px;padding:11px 18px;cursor:pointer;font-family:var(--mono);font-size:13px;
+ letter-spacing:.3px;transition:transform .08s ease,filter .12s ease}
+ .btn:hover{filter:brightness(1.08)}.btn:active{transform:translateY(1px)}
+ .btn.ghost{background:transparent;color:var(--ink);border:1px solid var(--line)}
+ .btn.gold{background:var(--gold);color:#241a00}
+ main{max-width:1120px;margin:0 auto;padding:0 20px 64px}
+ section{padding:52px 0;border-bottom:1px solid #ffffff08}
+ .kicker{font-size:11px;letter-spacing:2px;text-transform:uppercase;color:var(--accent);margin-bottom:14px}
+ h1{font-size:clamp(30px,6vw,52px);line-height:1.08;margin:0 0 18px;letter-spacing:-.5px;font-weight:800}
+ h1 .hl{color:var(--gold)}
+ .lede{font-size:17px;color:var(--dim);max-width:640px;margin:0 0 26px}
+ .hero-cta{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
+ .hero-note{font-size:11px;color:var(--dim);margin-top:14px}
+ h2{font-size:24px;margin:0 0 8px;letter-spacing:-.3px}
+ .sub{color:var(--dim);margin:0 0 28px;font-size:14px}
+ /* — self-funding proof strip — */
+ .proof{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-top:34px}
+ @media(max-width:760px){.proof{grid-template-columns:repeat(2,1fr)}}
+ .stat{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
+ border-radius:12px;padding:16px}
+ .stat .k{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.6px}
+ .stat .v{font-size:26px;font-weight:800;margin-top:6px}
+ .stat .n{font-size:11px;color:var(--dim);margin-top:4px}
+ /* — features — */
+ .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
+ @media(max-width:860px){.grid{grid-template-columns:1fr}}
+ .card{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
+ border-radius:12px;padding:18px}
+ .card .ico{font-size:22px}
+ .card h3{margin:10px 0 6px;font-size:15px}
+ .card p{margin:0;color:var(--dim);font-size:13px}
+ /* — pricing — */
+ .tiers{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:8px}
+ @media(max-width:860px){.tiers{grid-template-columns:1fr}}
+ .tier{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
+ border-radius:14px;padding:22px;display:flex;flex-direction:column}
+ .tier.feat{border-color:#4d9fff66;box-shadow:0 0 0 1px #4d9fff22, 0 10px 40px -20px #4d9fff55}
+ .tier .badge{align-self:flex-start;font-size:10px;letter-spacing:1px;text-transform:uppercase;
+ color:var(--accent);border:1px solid #4d9fff44;background:#4d9fff0f;border-radius:20px;padding:3px 9px;margin-bottom:12px}
+ .tier .name{font-size:16px;font-weight:700}
+ .tier .price{font-size:34px;font-weight:800;margin:8px 0 2px}
+ .tier .price small{font-size:13px;font-weight:600;color:var(--dim)}
+ .tier .for{color:var(--dim);font-size:12px;margin-bottom:16px}
+ .tier ul{list-style:none;padding:0;margin:0 0 20px;flex:1}
+ .tier li{padding:6px 0;border-bottom:1px dashed #ffffff08;font-size:13px;color:var(--ink)}
+ .tier li:before{content:"✓";color:var(--good);margin-right:8px}
+ .tier li.no{color:var(--dim)}.tier li.no:before{content:"—";color:var(--dim)}
+ .pricing-note{text-align:center;color:var(--dim);font-size:11px;margin-top:16px}
+ /* — waitlist — */
+ .wl{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
+ border-radius:14px;padding:26px;max-width:560px;margin:0 auto;text-align:center}
+ .wl form{display:flex;gap:10px;margin-top:16px}
+ @media(max-width:520px){.wl form{flex-direction:column}}
+ .wl input{flex:1;background:#0c1420;border:1px solid var(--line);color:var(--ink);
+ border-radius:9px;padding:12px 14px;font-family:var(--mono);font-size:14px}
+ .wl input:focus{outline:none;border-color:var(--accent)}
+ .wl .msg{margin-top:12px;font-size:12px;min-height:16px}
+ .wl .msg.ok{color:var(--good)}.wl .msg.err{color:var(--bad)}
+ footer{max-width:1120px;margin:0 auto;padding:26px 20px;color:var(--dim);font-size:11px;
+ display:flex;gap:14px;flex-wrap:wrap;align-items:center}
+ .tag{font-size:10px;color:var(--warn);border:1px solid #ffb45444;background:#ffb4540f;
+ border-radius:20px;padding:3px 9px}
+</style>
+</head>
+<body>
+<header>
+ <div class="logo"><b>Abrams</b>Ego</div>
+ <nav class="nav">
+ <a href="#features">Features</a>
+ <a href="#pricing">Pricing</a>
+ <a href="/index.html">Live demo →</a>
+ <a class="btn ghost" href="#waitlist">Join waitlist</a>
+ </nav>
+</header>
+
+<main>
+ <!-- HERO -->
+ <section id="hero">
+ <div class="kicker">Self-hostable AI-agent command center</div>
+ <h1>The dashboard that <span class="hl">pays for itself.</span></h1>
+ <p class="lede">
+ AbramsEgo watches your whole agent fleet — local + cloud — and keeps its own
+ books. It accounts for every token and watt it burns, tracks the revenue your
+ agents earn, and shows you one number: <b>how much of its own running cost it
+ covers.</b> Most dashboards are pure cost. This one has a P&L.
+ </p>
+ <div class="hero-cta">
+ <a class="btn gold" href="#waitlist">Get early access</a>
+ <a class="btn ghost" href="/index.html">See the live dashboard →</a>
+ </div>
+ <div class="hero-note">Read-only · Basic-auth gated · runs on your box · your data never leaves it.</div>
+
+ <!-- self-funding proof strip -->
+ <div class="proof">
+ <div class="stat"><div class="k">Fleet watched</div><div class="v" id="s-fleet">—</div><div class="n">local + Kamatera procs</div></div>
+ <div class="stat"><div class="k">Cost accounted · 30d</div><div class="v gold" id="s-cost">—</div><div class="n">tokens + energy est</div></div>
+ <div class="stat"><div class="k">Self-funding today</div><div class="v" id="s-pct">—</div><div class="n">of run cost covered</div></div>
+ <div class="stat"><div class="k">Canaries live</div><div class="v" id="s-can">—</div><div class="n">health heartbeats</div></div>
+ </div>
+ </section>
+
+ <!-- FEATURES -->
+ <section id="features">
+ <div class="kicker">What's inside</div>
+ <h2>Everything you need to run agents like a business</h2>
+ <p class="sub">One page, zero external dependencies, deployed behind your own auth.</p>
+ <div class="grid">
+ <div class="card"><div class="ico">🛰</div><h3>Unified fleet view</h3>
+ <p>Local pm2 + remote Kamatera processes in one board — up/down, restarts, memory, CPU. Stale-while-revalidate so it's always instant.</p></div>
+ <div class="card"><div class="ico">🐤</div><h3>Canary board</h3>
+ <p>Every health heartbeat your agents write gets surfaced with a verdict + age, so a silently-dead job can't hide for twelve days.</p></div>
+ <div class="card"><div class="ico">💰</div><h3>Self-funding P&L</h3>
+ <p>Token cost <i>and</i> energy cost, per day and per window, against real dated income — with a break-even target and a live self-funding %.</p></div>
+ <div class="card"><div class="ico">📈</div><h3>Cost trend + per-model</h3>
+ <p>7-day daily cost trend and a 30-day per-model breakdown, straight from your cost ledger. Know what's actually expensive.</p></div>
+ <div class="card"><div class="ico">🧠</div><h3>Model + session usage</h3>
+ <p>Per-model calls, tokens and spend, plus active Claude sessions and the most recent sub-agent runs — each stamped with created date + time.</p></div>
+ <div class="card"><div class="ico">🤖</div><h3>Grounded AI chat</h3>
+ <p>Ask "is it paying for itself?" and get an answer grounded in the live snapshot — via your local Ollama, so the chat itself costs $0.</p></div>
+ </div>
+ </section>
+
+ <!-- PRICING -->
+ <section id="pricing">
+ <div class="kicker">Pricing</div>
+ <h2>Start free. Pay when you want it hosted.</h2>
+ <p class="sub">The full dashboard is yours to self-host at no cost. Paid tiers are for when you'd rather we run it.</p>
+ <div class="tiers">
+ <div class="tier">
+ <div class="name">Self-host</div>
+ <div class="price">$0</div>
+ <div class="for">forever · your box, your data</div>
+ <ul>
+ <li>Full command-center dashboard</li>
+ <li>Fleet + canaries + P&L</li>
+ <li>Local Ollama AI chat</li>
+ <li>Basic-auth gate included</li>
+ <li class="no">You host & update it</li>
+ <li class="no">Community support</li>
+ </ul>
+ <a class="btn ghost" href="#waitlist">Get the template</a>
+ </div>
+ <div class="tier feat">
+ <div class="badge">Most popular</div>
+ <div class="name">Hosted</div>
+ <div class="price">$19<small>/mo</small></div>
+ <div class="for">we run it · you just log in</div>
+ <ul>
+ <li>Everything in Self-host</li>
+ <li>Managed hosting + updates</li>
+ <li>Daily snapshot backups</li>
+ <li>Email alerts on canary FAIL</li>
+ <li>Hosted uptime for the dashboard</li>
+ <li class="no">Single fleet</li>
+ </ul>
+ <a class="btn" href="#waitlist">Join waitlist</a>
+ </div>
+ <div class="tier">
+ <div class="name">Pro</div>
+ <div class="price">$49<small>/mo</small></div>
+ <div class="for">multi-fleet · teams</div>
+ <ul>
+ <li>Everything in Hosted</li>
+ <li>Multiple fleets / machines</li>
+ <li>Team logins & roles</li>
+ <li>Revenue engine integrations</li>
+ <li>Priority support</li>
+ <li>Custom cost-ledger sources</li>
+ </ul>
+ <a class="btn ghost" href="#waitlist">Talk to us</a>
+ </div>
+ </div>
+ <p class="pricing-note">Prices indicative · billing not yet live — join the waitlist and you'll be first to know when checkout opens.</p>
+ </section>
+
+ <!-- WAITLIST -->
+ <section id="waitlist">
+ <div class="wl">
+ <div class="kicker" style="margin-bottom:8px">Early access</div>
+ <h2 style="margin-bottom:6px">Be first in line</h2>
+ <p class="sub" style="margin:0">Drop your email and we'll reach out the moment hosted plans go live. No spam — one launch note.</p>
+ <form id="wl-form">
+ <input id="wl-email" type="email" required autocomplete="email" placeholder="you@company.com" />
+ <button class="btn gold" type="submit">Notify me</button>
+ </form>
+ <div class="msg" id="wl-msg"></div>
+ </div>
+ </section>
+</main>
+
+<footer>
+ <span><b style="color:var(--ink)">AbramsEgo</b> — self-funding AI-agent command center.</span>
+ <span class="tag">Preview · publish gated</span>
+ <span style="margin-left:auto"><a href="/index.html">Live dashboard →</a></span>
+</footer>
+
+<script>
+const $ = (id)=>document.getElementById(id);
+const usd = (n)=> n==null? '—' : '$'+Number(n).toFixed(n<1?4:2);
+
+// Pull real numbers from the live snapshot so the landing is honest, not mocked.
+(async function fillProof(){
+ try{
+ const d = await (await fetch('/api/data')).json();
+ if(!d || !d.builtAt) return;
+ const lf=d.localFleet||{}, k=d.kamatera||{};
+ const totalFleet=(lf.total||0)+(k.total||0);
+ $('s-fleet').textContent = totalFleet || '—';
+ const c30=(d.cost&&d.cost.month)||{};
+ $('s-cost').textContent = usd(c30.total);
+ const pct=(d.pnl&&d.pnl.today&&d.pnl.today.selfFundingPct);
+ const el=$('s-pct'); el.textContent=(pct==null?'0':pct)+'%';
+ el.className='v '+((pct||0)>=100?'good':(pct||0)>0?'warn':'gold');
+ const cans=(d.canaries&&d.canaries.count)||0;
+ $('s-can').textContent = cans || '—';
+ }catch(e){ /* snapshot not ready — leave dashes */ }
+})();
+
+// Waitlist capture → POST /api/waitlist
+$('wl-form').addEventListener('submit', async (e)=>{
+ e.preventDefault();
+ const email=$('wl-email').value.trim();
+ const msg=$('wl-msg');
+ if(!email){ msg.className='msg err'; msg.textContent='Enter an email.'; return; }
+ msg.className='msg'; msg.textContent='…adding you';
+ try{
+ const r = await fetch('/api/waitlist',{method:'POST',headers:{'Content-Type':'application/json'},
+ body:JSON.stringify({email, tier:'hosted', source:'landing'})});
+ const j = await r.json();
+ if(r.ok && j.ok){ msg.className='msg ok'; msg.textContent='You’re on the list — we’ll be in touch. 🎉'; $('wl-form').reset(); }
+ else { msg.className='msg err'; msg.textContent=j.error||'Could not add you — try again.'; }
+ }catch(err){ msg.className='msg err'; msg.textContent='Network error — try again.'; }
+});
+</script>
+</body>
+</html>
diff --git a/server.js b/server.js
index f4f751b..aad33c4 100644
--- a/server.js
+++ b/server.js
@@ -596,6 +596,33 @@ app.post('/api/chat', async (req, res) => {
res.json({ answer, engine: 'local-template', cost_usd: 0 });
});
+// --- 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));
+
+// 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', () => {
← ef6ad60 auto-save: 2026-07-01T11:11:52 (2 files) — build-queue/tasks
·
back to AbramsEgo
·
feat(chat): local-ollama AI chat panel ($0, cap-relieving) 672e1bc →