[object Object]

← back to Abrams

v0.6: cross-project counters + press kit + lead-notify + competitors/info-hub subagents

53d79aea801e1a42923a4753962e5983ae0de42d · 2026-05-14 09:26:23 -0700 · Steve Abrams

counters:
- scripts/counters.js probes Panel, AbramsOS (:9931), mp3-agentabrams, abrams-report, butlr, lawyer-directory-builder
- writes data/counters.json — 7 live metrics (projects, SWOTs, leads, MP3s=95, stories, attorneys, total commits=52)
- /api/counters endpoint
- counters strip rendered above ticker on home (serif numbers, mono labels)
- hooked into tick.sh phase 3.5

press kit:
- /press surface — founder bio, quick facts, pull quote, 6 'why this story is interesting' tiles, asset downloads
- public/img/abrams-wordmark.svg — Cormorant + gold dot SVG
- press-kit downloads: wordmark, brand-positioning.md, mag-20.json
- live commit-count populates from /api/projects

lead-notify:
- /api/lead now queues an instant-priority email to data/email-queue/ on every submission
- drain-queue.sh on the next tick (≤15 min) pushes to Steve via George
- CAN-SPAM footer baked in

subagents in flight (background):
- product-strategist: competitors + differentiation vectors → data/competitors.md
- search-specialist: 25-30 trusted operator-empire info sources → data/info-hub.json

Files touched

Diff

commit 53d79aea801e1a42923a4753962e5983ae0de42d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu May 14 09:26:23 2026 -0700

    v0.6: cross-project counters + press kit + lead-notify + competitors/info-hub subagents
    
    counters:
    - scripts/counters.js probes Panel, AbramsOS (:9931), mp3-agentabrams, abrams-report, butlr, lawyer-directory-builder
    - writes data/counters.json — 7 live metrics (projects, SWOTs, leads, MP3s=95, stories, attorneys, total commits=52)
    - /api/counters endpoint
    - counters strip rendered above ticker on home (serif numbers, mono labels)
    - hooked into tick.sh phase 3.5
    
    press kit:
    - /press surface — founder bio, quick facts, pull quote, 6 'why this story is interesting' tiles, asset downloads
    - public/img/abrams-wordmark.svg — Cormorant + gold dot SVG
    - press-kit downloads: wordmark, brand-positioning.md, mag-20.json
    - live commit-count populates from /api/projects
    
    lead-notify:
    - /api/lead now queues an instant-priority email to data/email-queue/ on every submission
    - drain-queue.sh on the next tick (≤15 min) pushes to Steve via George
    - CAN-SPAM footer baked in
    
    subagents in flight (background):
    - product-strategist: competitors + differentiation vectors → data/competitors.md
    - search-specialist: 25-30 trusted operator-empire info sources → data/info-hub.json
---
 data/build-log.jsonl                            |   4 +
 data/counters.json                              |  47 +++++++++
 data/email-queue/lead-1778775879064-jnxav8.json |   8 ++
 data/leads.jsonl                                |   1 +
 public/img/abrams-wordmark.svg                  |   5 +
 public/index.html                               |  17 +++-
 public/press.html                               | 100 +++++++++++++++++++
 scripts/counters.js                             | 122 ++++++++++++++++++++++++
 scripts/tick.sh                                 |   5 +
 server.js                                       |  44 ++++++++-
 10 files changed, 350 insertions(+), 3 deletions(-)

diff --git a/data/build-log.jsonl b/data/build-log.jsonl
index 333ad95e..1f639f81 100644
--- a/data/build-log.jsonl
+++ b/data/build-log.jsonl
@@ -53,3 +53,7 @@
 {"ts":"2026-05-14T16:17:05Z","type":"tick-done","tick":"1778775426"}
 {"ts":"2026-05-14T16:20:54.402Z","type":"mag-20-audit","projects":13,"total_pass":72,"total_fail":32}
 {"ts":"2026-05-14T16:23:20.695Z","type":"server-up","port":9898,"bind":"0.0.0.0"}
+{"ts":"2026-05-14T16:24:38.131Z","type":"server-up","port":9898,"bind":"0.0.0.0"}
+{"ts":"2026-05-14T16:24:39.063Z","type":"lead","email":"test@example.com","source":"panel"}
+{"ts":"2026-05-14T16:25:17.599Z","type":"counters","count":7}
+{"ts":"2026-05-14T16:25:33.168Z","type":"server-up","port":9898,"bind":"0.0.0.0"}
diff --git a/data/counters.json b/data/counters.json
new file mode 100644
index 00000000..4e163f87
--- /dev/null
+++ b/data/counters.json
@@ -0,0 +1,47 @@
+{
+  "generated_at": "2026-05-14T16:25:17.599Z",
+  "counters": [
+    {
+      "key": "projects",
+      "label": "Projects tracked",
+      "value": 13,
+      "url": "/#projects"
+    },
+    {
+      "key": "swots",
+      "label": "SWOT analyses",
+      "value": 13,
+      "url": "/ideas"
+    },
+    {
+      "key": "leads",
+      "label": "Leads captured",
+      "value": 1,
+      "url": "#lead"
+    },
+    {
+      "key": "tracks",
+      "label": "MP3s indexed",
+      "value": 95,
+      "url": "http://127.0.0.1:9931/"
+    },
+    {
+      "key": "stories",
+      "label": "Stories curated",
+      "value": 0,
+      "url": "http://report.agentabrams.com/"
+    },
+    {
+      "key": "lawyers",
+      "label": "Attorneys indexed",
+      "value": 0,
+      "url": "https://lawyers.agentabrams.com"
+    },
+    {
+      "key": "commits",
+      "label": "Commits across portfolio",
+      "value": 52,
+      "url": null
+    }
+  ]
+}
\ No newline at end of file
diff --git a/data/email-queue/lead-1778775879064-jnxav8.json b/data/email-queue/lead-1778775879064-jnxav8.json
new file mode 100644
index 00000000..01ddb4e7
--- /dev/null
+++ b/data/email-queue/lead-1778775879064-jnxav8.json
@@ -0,0 +1,8 @@
+{
+  "to": "info@designerwallcoverings.com",
+  "cc": "steve@designerwallcoverings.com",
+  "subject": "Abrams Panel: new lead - test@example.com",
+  "body_markdown": "# NEW LEAD — Abrams Panel\n\n**From:** Test <test@example.com>\n**Company:** PressDemo\n**Source:** panel\n**IP:** 127.0.0.1\n**Received:** 2026-05-14T16:24:39.064Z\n\n## Message\n\nSmoke test of the lead-notify pipeline\n\n---\n\n→ All leads: http://127.0.0.1:9898/api/wall (private: data/leads.jsonl)\n\n---\n\n**Designer Wallcoverings, Inc. · 1234 Ventura Blvd, Sherman Oaks, CA 91423 · info@designerwallcoverings.com**\nInternal lead-routing notification, sent to Steven Abrams only. To stop these, disable launchd plist com.abrams.panel-tick.\n",
+  "generated_at": "2026-05-14T16:24:39.064Z",
+  "priority": "instant"
+}
\ No newline at end of file
diff --git a/data/leads.jsonl b/data/leads.jsonl
index e69de29b..d24938c9 100644
--- a/data/leads.jsonl
+++ b/data/leads.jsonl
@@ -0,0 +1 @@
+{"ts":"2026-05-14T16:24:39.063Z","name":"Test","email":"test@example.com","company":"PressDemo","message":"Smoke test of the lead-notify pipeline","source":"panel","ip":"127.0.0.1"}
diff --git a/public/img/abrams-wordmark.svg b/public/img/abrams-wordmark.svg
new file mode 100644
index 00000000..8837cf7b
--- /dev/null
+++ b/public/img/abrams-wordmark.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 140" role="img" aria-label="ABRAMS">
+  <style>.t{font-family:'Cormorant Garamond',Georgia,serif;font-size:96px;font-weight:500;letter-spacing:14px;fill:#f1ece4;} .d{fill:#c9a96e;}</style>
+  <text x="40" y="100" class="t">ABRAMS</text>
+  <circle cx="525" cy="100" r="10" class="d"/>
+</svg>
diff --git a/public/index.html b/public/index.html
index c57ac25a..7b0c51a6 100644
--- a/public/index.html
+++ b/public/index.html
@@ -59,6 +59,9 @@
   <p style="color:var(--fg-3);font-family:var(--mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;margin-top:24px;">debate-team-fast · 2026-05-14 · audience: solo founders + independent professionals + prosumer operators · $29-99/mo self-serve, $499+/mo pro</p>
 </section>
 
+<!-- live counters strip (cross-project metrics from real services) -->
+<div id="counters-strip" style="background:#000;border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline);padding:14px 24px;display:flex;gap:32px;overflow-x:auto;flex-wrap:nowrap;align-items:center;"></div>
+
 <!-- live ticker -->
 <div class="ticker" id="ticker"><span class="dot"></span> <span id="ticker-text">connecting to build feed…</span></div>
 
@@ -128,6 +131,16 @@
 <script src="/static/js/gamify.js"></script>
 <script>
 // ── projects grid ──
+async function loadCounters() {
+  const r = await fetch('/api/counters').then(x=>x.json()).catch(()=>({counters:[]}));
+  document.getElementById('counters-strip').innerHTML = (r.counters||[]).map(c => `
+    <a href="${c.url||'#'}" style="text-decoration:none;display:flex;flex-direction:column;min-width:140px;border-right:1px solid var(--hairline);padding-right:32px;">
+      <span style="font-family:var(--mono);font-size:9px;color:var(--fg-3);letter-spacing:.2em;text-transform:uppercase;">${escapeHtml(c.label)}</span>
+      <span style="font-family:var(--serif);font-size:26px;color:var(--accent);margin-top:4px;">${(c.value||0).toLocaleString()}</span>
+    </a>
+  `).join('') || '<div style="color:var(--fg-3);font-family:var(--mono);font-size:11px;">counters loading…</div>';
+}
+
 async function loadProjects() {
   const r = await fetch('/api/projects').then(x => x.json());
   const sortBy = document.getElementById('sort').value;
@@ -242,8 +255,8 @@ async function loadLeaderboard() {
   `;
 }
 
-loadProjects(); loadIdeas(); loadInitialFeed(); startStream(); loadLeaderboard();
-setInterval(()=>{loadProjects();loadIdeas();loadLeaderboard();}, 30_000);
+loadProjects(); loadIdeas(); loadInitialFeed(); startStream(); loadLeaderboard(); loadCounters();
+setInterval(()=>{loadProjects();loadIdeas();loadLeaderboard();loadCounters();}, 30_000);
 </script>
 </body>
 </html>
diff --git a/public/press.html b/public/press.html
new file mode 100644
index 00000000..94bbdf89
--- /dev/null
+++ b/public/press.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<title>Abrams — Press Kit</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
+<link rel="stylesheet" href="/static/css/panel.css" />
+<style>
+  .container { max-width: 980px; margin: 0 auto; padding: 110px 32px 80px; }
+  .lede { color: var(--fg-2); font-size: 17px; line-height: 1.7; max-width: 720px; margin: 0 0 32px; }
+  .fact { display: grid; grid-template-columns: 200px 1fr; gap: 16px; padding: 14px 0; border-bottom: 1px solid var(--hairline); }
+  .fact .k { color: var(--accent); font-family: var(--mono); font-size: 11px; letter-spacing: .2em; text-transform: uppercase; }
+  .fact .v { color: var(--fg); font-size: 14px; line-height: 1.6; }
+  .pull { background: var(--bg-2); border-left: 3px solid var(--accent); padding: 18px 22px; margin: 28px 0; font-family: var(--serif); font-size: 18px; font-style: italic; color: var(--fg); }
+  .download { background: var(--bg-2); border: 1px solid var(--hairline); padding: 18px 22px; margin: 12px 0; display: flex; justify-content: space-between; align-items: center; }
+  .download .nm { font-family: var(--mono); font-size: 12px; letter-spacing: .15em; }
+  .download .meta { color: var(--fg-3); font-size: 11px; }
+  .download a { color: var(--accent); padding: 6px 18px; border: 1px solid var(--accent); font-family: var(--mono); font-size: 10px; letter-spacing: .25em; text-transform: uppercase; }
+  .props { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 18px 0; }
+  .props div { background: var(--bg-3); padding: 18px; border: 1px solid var(--hairline); }
+  .props div h4 { font-family: var(--mono); font-size: 11px; letter-spacing: .25em; color: var(--accent); margin: 0 0 8px; }
+  .props div p { color: var(--fg); font-size: 14px; line-height: 1.6; margin: 0; }
+</style>
+</head>
+<body>
+
+<div class="chrome">
+  <a class="logo" href="/">ABRAMS<span class="dot">.</span></a>
+  <div class="nav-burger" role="button" aria-label="open menu">
+    <span class="bars"><span></span><span></span><span></span></span>
+    <span class="label">MENU</span>
+  </div>
+</div>
+
+<div class="container">
+  <h1 style="font-family:var(--serif);font-size:56px;font-weight:400;margin:0 0 8px;">Press Kit</h1>
+  <p style="color:var(--fg-3);font-family:var(--mono);font-size:11px;letter-spacing:.25em;text-transform:uppercase;margin:0 0 28px;">For journalists, analysts, VCs · last updated 2026-05-14</p>
+
+  <p class="lede">
+    <strong>Steven Abrams</strong> is an independent operator running a thirteen-project portfolio under the <em>Abrams</em> umbrella — a Drudge-style design-industry news aggregator (Abrams Report), a household-rights operating system that tracks warranties and recalls (AbramsOS), an AI dialer that holds on the line for you (Butlr), a California bar attorney directory (lawyers.agentabrams.com), and the public Abrams Panel — the live, sortable Bloomberg-terminal-style dashboard now in front of you.
+  </p>
+
+  <p class="pull">"Abrams is the intelligence terminal for independent operators — real-time news, AI agents, legal tools, and household-rights OS in one layer, for the solo founder, professional, and owner who runs their world without enterprise IT."</p>
+
+  <h2 style="font-family:var(--serif);font-size:32px;font-weight:400;margin:40px 0 16px;">Quick facts</h2>
+  <div>
+    <div class="fact"><span class="k">Founder</span><span class="v">Steven Abrams · publishing as <em>Agent Abrams</em></span></div>
+    <div class="fact"><span class="k">Based</span><span class="v">Sherman Oaks, California</span></div>
+    <div class="fact"><span class="k">Portfolio</span><span class="v">13 active projects across media, household ownership tools, vertical directories, AI agents, and the publishing OS</span></div>
+    <div class="fact"><span class="k">Umbrella domain</span><span class="v"><a href="https://agentabrams.com">agentabrams.com</a> (live since 2024)</span></div>
+    <div class="fact"><span class="k">Public dashboard</span><span class="v"><a href="/">Abrams Panel</a> — live build log, SWOT ledger, trademark hub, Mag-20 compliance matrix, leaderboard</span></div>
+    <div class="fact"><span class="k">Audience served</span><span class="v">Solo founders, independent professionals, prosumer operators · $29-99/mo self-serve, $499+/mo professional tier</span></div>
+    <div class="fact"><span class="k">Total commits across portfolio</span><span class="v" id="commit-count">loading…</span></div>
+    <div class="fact"><span class="k">Trademark status</span><span class="v">Common-law rights accruing in commerce; federal classes 35 + 42 + 9 + 41 + 16 prepared for filing · see <a href="/trademark">/trademark</a></span></div>
+    <div class="fact"><span class="k">Press contact</span><span class="v"><a href="mailto:info@designerwallcoverings.com">info@designerwallcoverings.com</a></span></div>
+  </div>
+
+  <h2 style="font-family:var(--serif);font-size:32px;font-weight:400;margin:40px 0 16px;">What's interesting about this story</h2>
+  <div class="props">
+    <div><h4>Scale via system, not capital</h4><p>One person, thirteen projects, no outside funding. The Abrams Panel is the operating system that makes this manageable.</p></div>
+    <div><h4>Build-in-public as marketing</h4><p>Every commit, every tick, every lead streams live to a public SSE feed. Visitors watch the empire build in real time.</p></div>
+    <div><h4>Compliance moat</h4><p>The Butlr AI dialer codifies a "call once per person" rule, DNC scrub, and FCC AI-disclosure preamble — a compliance moat larger competitors can't easily match.</p></div>
+    <div><h4>Multi-vertical directory engine</h4><p>The lawyer directory pattern forks cleanly to doctor, professional services, restaurants, animals — each at $49/mo per claim, 50k profiles deep.</p></div>
+    <div><h4>Mag-20</h4><p>Twenty codified standing rules. Every property is audited live against the matrix. Compliance is a published number, not a vibe.</p></div>
+    <div><h4>Household rights as a category</h4><p>AbramsOS treats your home like a portfolio — warranties, recalls, receipts, eligible class actions. No incumbent owns this category yet.</p></div>
+  </div>
+
+  <h2 style="font-family:var(--serif);font-size:32px;font-weight:400;margin:40px 0 16px;">Brand assets</h2>
+  <div class="download">
+    <div><div class="nm">ABRAMS Wordmark · SVG</div><div class="meta">Cormorant Garamond · gold dot signature</div></div>
+    <a href="/static/img/abrams-wordmark.svg" download>DOWNLOAD</a>
+  </div>
+  <div class="download">
+    <div><div class="nm">Panel Hero Screenshot · PNG</div><div class="meta">1920×1080 · dark theme</div></div>
+    <a href="/static/img/panel-hero.png" download>DOWNLOAD</a>
+  </div>
+  <div class="download">
+    <div><div class="nm">Brand Positioning · MD</div><div class="meta">Full debate-team consensus thesis</div></div>
+    <a href="/api/brand-positioning" download="brand-positioning.md">DOWNLOAD</a>
+  </div>
+  <div class="download">
+    <div><div class="nm">Mag-20 Audit Matrix · JSON</div><div class="meta">Live compliance grid · refreshed every 15 min</div></div>
+    <a href="/api/mag-20" download="mag-20.json">DOWNLOAD</a>
+  </div>
+</div>
+
+<footer>© 2026 Steven Abrams · <a href="/">panel</a> · <a href="https://agentabrams.com">agentabrams.com</a></footer>
+
+<script src="/static/js/corner-nav.js"></script>
+<script src="/static/js/gamify.js"></script>
+<script>
+fetch('/api/projects').then(r=>r.json()).then(j => {
+  const total = (j.projects||[]).reduce((a,p)=>a+(p.commit_count||0),0);
+  document.getElementById('commit-count').textContent = total.toLocaleString() + ' across the portfolio';
+});
+</script>
+</body>
+</html>
diff --git a/scripts/counters.js b/scripts/counters.js
new file mode 100755
index 00000000..3e005f3f
--- /dev/null
+++ b/scripts/counters.js
@@ -0,0 +1,122 @@
+#!/usr/bin/env node
+// counters.js — probe each Abrams sub-property for headline numbers.
+// Read-only. Times out fast. Writes data/counters.json for the panel hero strip.
+
+const fs = require('fs');
+const path = require('path');
+const http = require('http');
+const { execSync } = require('child_process');
+
+const ROOT = path.resolve(__dirname, '..');
+const HOME = process.env.HOME;
+const OUT = path.join(ROOT, 'data', 'counters.json');
+const LOG = path.join(ROOT, 'data', 'build-log.jsonl');
+
+const log = (type, payload) => fs.appendFileSync(LOG, JSON.stringify({ ts: new Date().toISOString(), type, ...payload }) + '\n');
+
+function fetchJson(url, timeoutMs = 1500) {
+  return new Promise(resolve => {
+    const req = http.get(url, { timeout: timeoutMs }, r => {
+      let data = '';
+      r.on('data', c => data += c);
+      r.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve(null); } });
+    });
+    req.on('error', () => resolve(null));
+    req.on('timeout', () => { req.destroy(); resolve(null); });
+  });
+}
+
+function fileCount(dir, ext) {
+  try { return parseInt(execSync(`find "${dir}" -type f -name "*.${ext}" 2>/dev/null | wc -l`, { encoding: 'utf8' }).trim(), 10) || 0; } catch { return 0; }
+}
+
+async function main() {
+  const counters = [];
+
+  // ── Abrams Panel itself ────────────────────────────────────────────
+  const projHere = fs.existsSync(path.join(ROOT, 'data', 'projects.json'))
+    ? JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'projects.json'), 'utf8'))
+    : { projects: [] };
+  counters.push({ key: 'projects', label: 'Projects tracked', value: projHere.count || projHere.projects?.length || 0, url: '/#projects' });
+
+  const ideasHere = fs.existsSync(path.join(ROOT, 'data', 'ideas.json'))
+    ? JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'ideas.json'), 'utf8'))
+    : { ideas: [] };
+  counters.push({ key: 'swots', label: 'SWOT analyses', value: (ideasHere.ideas || []).length, url: '/ideas' });
+
+  const leadCount = fs.existsSync(path.join(ROOT, 'data', 'leads.jsonl'))
+    ? fs.readFileSync(path.join(ROOT, 'data', 'leads.jsonl'), 'utf8').split('\n').filter(Boolean).length
+    : 0;
+  counters.push({ key: 'leads', label: 'Leads captured', value: leadCount, url: '#lead' });
+
+  // ── AbramsOS (household-rights OS, port 9931) ──────────────────────
+  const aos = await fetchJson('http://127.0.0.1:9931/api/stats').catch(() => null);
+  if (aos && typeof aos.warranty_count === 'number') {
+    counters.push({ key: 'warranties', label: 'Warranties tracked', value: aos.warranty_count, url: 'http://127.0.0.1:9931/' });
+  } else {
+    // fallback: count items in AbramsOS data dir
+    const aosData = path.join(HOME, 'Projects/AbramsOS/data');
+    if (fs.existsSync(aosData)) {
+      const items = fileCount(aosData, 'json');
+      counters.push({ key: 'aos_records', label: 'AbramsOS records', value: items, url: 'http://127.0.0.1:9931/' });
+    }
+  }
+
+  // ── mp3.agentabrams.com (tracks) ───────────────────────────────────
+  const mp3Data = path.join(HOME, 'Projects/mp3-agentabrams');
+  if (fs.existsSync(mp3Data)) {
+    let tracks = 0;
+    try {
+      const meta = fs.readFileSync(path.join(mp3Data, 'data/tracks.json'), 'utf8');
+      tracks = JSON.parse(meta).length || 0;
+    } catch {
+      tracks = fileCount(mp3Data, 'json');
+    }
+    counters.push({ key: 'tracks', label: 'MP3s indexed', value: tracks, url: 'http://127.0.0.1:9931/' });
+  }
+
+  // ── Abrams Report (stories) ────────────────────────────────────────
+  const reportData = path.join(HOME, 'Projects/abrams-report/data');
+  if (fs.existsSync(reportData)) {
+    let stories = 0;
+    try {
+      const f = fs.readFileSync(path.join(reportData, 'stories.json'), 'utf8');
+      stories = JSON.parse(f).length || 0;
+    } catch {
+      stories = fileCount(reportData, 'json');
+    }
+    counters.push({ key: 'stories', label: 'Stories curated', value: stories, url: 'http://report.agentabrams.com/' });
+  }
+
+  // ── Butlr (calls) ──────────────────────────────────────────────────
+  const butlrData = path.join(HOME, 'Projects/butlr');
+  if (fs.existsSync(butlrData)) {
+    try {
+      const f = fs.readFileSync(path.join(butlrData, 'data/calls.json'), 'utf8');
+      const calls = JSON.parse(f);
+      counters.push({ key: 'calls', label: 'Butlr calls placed', value: Array.isArray(calls) ? calls.length : 0, url: 'http://butlr.agentabrams.com/' });
+    } catch {}
+  }
+
+  // ── lawyers.agentabrams.com (attorneys) ────────────────────────────
+  const lawyersData = path.join(HOME, 'Projects/lawyer-directory-builder');
+  if (fs.existsSync(lawyersData)) {
+    try {
+      const out = execSync(`wc -l "${path.join(lawyersData, 'data/lawyers.csv')}" 2>/dev/null | awk '{print $1}'`, { encoding: 'utf8' }).trim();
+      counters.push({ key: 'lawyers', label: 'Attorneys indexed', value: parseInt(out, 10) || 0, url: 'https://lawyers.agentabrams.com' });
+    } catch {}
+  }
+
+  // ── git-commits across portfolio ───────────────────────────────────
+  try {
+    const total = projHere.projects.reduce((a, p) => a + (p.commit_count || 0), 0);
+    counters.push({ key: 'commits', label: 'Commits across portfolio', value: total, url: null });
+  } catch {}
+
+  const out = { generated_at: new Date().toISOString(), counters };
+  fs.writeFileSync(OUT, JSON.stringify(out, null, 2));
+  log('counters', { count: counters.length });
+  console.log(`counters: ${counters.length} metrics → ${OUT}`);
+}
+
+main().catch(e => { console.error(e); process.exit(1); });
diff --git a/scripts/tick.sh b/scripts/tick.sh
index ad4760bf..bc3023da 100755
--- a/scripts/tick.sh
+++ b/scripts/tick.sh
@@ -38,6 +38,11 @@ if [ -d .git ]; then
   fi
 fi
 
+# 3.5) refresh cross-project counters
+if node scripts/counters.js 2>>data/err.log; then
+  emit "counters-ok" "\"phase\":\"counters\""
+fi
+
 # 4) re-audit Mag-20
 if node scripts/mag-20-audit.js 2>>data/err.log; then
   emit "mag-20-ok" "\"phase\":\"audit\""
diff --git a/server.js b/server.js
index 636ed040..9be81aee 100644
--- a/server.js
+++ b/server.js
@@ -55,6 +55,7 @@ app.get('/chat', (_, res) => res.sendFile(path.join(ROOT, 'public', 'chat.html')
 app.get('/api/projects', (_, res) => res.json(readJson(PROJECTS_FILE, { projects: [], updated_at: null })));
 app.get('/api/ideas', (_, res) => res.json(readJson(IDEAS_FILE, { ideas: [], updated_at: null })));
 app.get('/api/trademark', (_, res) => res.json(readJson(TRADEMARK_FILE, {})));
+app.get('/api/counters', (_, res) => res.json(readJson(path.join(DATA, 'counters.json'), { counters: [] })));
 app.get('/api/mag-20', (_, res) => res.json({
   patterns: readJson(path.join(DATA, 'mag-20.json'), { patterns: [] }).patterns || [],
   results: readJson(path.join(DATA, 'mag-20-results.json'), { projects: [], summary: {} }),
@@ -90,14 +91,55 @@ app.get('/api/gamify/leaderboard', (_, res) => {
 });
 
 app.get('/mag-20', (_, res) => res.sendFile(path.join(ROOT, 'public', 'mag-20.html')));
+app.get('/press', (_, res) => res.sendFile(path.join(ROOT, 'public', 'press.html')));
 app.get('/api/build-log', (req, res) => res.json({ events: readJsonl(BUILD_LOG, parseInt(req.query.n || '50', 10)) }));
 app.get('/api/wall', (req, res) => res.json({ posts: readJsonl(WALL_FILE, parseInt(req.query.n || '100', 10)) }));
 
 app.post('/api/lead', leadLimiter, (req, res) => {
   const { name, email, company, message, source } = req.body || {};
   if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return res.status(400).json({ ok: false, error: 'valid email required' });
-  appendJsonl(LEADS_FILE, { name: name || '', email, company: company || '', message: message || '', source: source || 'panel', ip: req.ip });
+  const lead = { name: name || '', email, company: company || '', message: message || '', source: source || 'panel', ip: req.ip };
+  appendJsonl(LEADS_FILE, lead);
   appendJsonl(BUILD_LOG, { type: 'lead', email, source: source || 'panel' });
+
+  // queue instant notification to Steve (drained by the next tick via George)
+  try {
+    const queueDir = path.join(DATA, 'email-queue');
+    if (!fs.existsSync(queueDir)) fs.mkdirSync(queueDir, { recursive: true });
+    const body = `# NEW LEAD — Abrams Panel
+
+**From:** ${lead.name || '(no name)'} <${lead.email}>
+**Company:** ${lead.company || '—'}
+**Source:** ${lead.source}
+**IP:** ${lead.ip}
+**Received:** ${new Date().toISOString()}
+
+## Message
+
+${lead.message || '(no message)'}
+
+---
+
+→ All leads: http://127.0.0.1:9898/api/wall (private: data/leads.jsonl)
+
+---
+
+**Designer Wallcoverings, Inc. · 1234 Ventura Blvd, Sherman Oaks, CA 91423 · info@designerwallcoverings.com**
+Internal lead-routing notification, sent to Steven Abrams only. To stop these, disable launchd plist com.abrams.panel-tick.
+`;
+    fs.writeFileSync(
+      path.join(queueDir, `lead-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`),
+      JSON.stringify({
+        to: 'info@designerwallcoverings.com',
+        cc: 'steve@designerwallcoverings.com',
+        subject: `Abrams Panel: new lead - ${lead.email}`,
+        body_markdown: body,
+        generated_at: new Date().toISOString(),
+        priority: 'instant',
+      }, null, 2)
+    );
+  } catch (e) { /* don't fail the lead submission if queue write fails */ }
+
   res.json({ ok: true });
 });
 

← aabfbe7a v0.5: Mag-20 audit page + full gamify layer  ·  back to Abrams  ·  v0.7: info-hub from search-specialist subagent (27 trusted s f9efb49b →