[object Object]

← back to AbramsEgo

auto-save: 2026-07-01T18:07:58 (5 files) — build-queue/tasks/37-billable-report.md data/local-fleet.json server.js build-queue/done/37-billable-report.md public/ads.txt

3dec20ac455494d747bbc592535a880c1aa6b949 · 2026-07-01 18:07:58 -0700 · Steve Abrams

Files touched

Diff

commit 3dec20ac455494d747bbc592535a880c1aa6b949
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 1 18:07:58 2026 -0700

    auto-save: 2026-07-01T18:07:58 (5 files) — build-queue/tasks/37-billable-report.md data/local-fleet.json server.js build-queue/done/37-billable-report.md public/ads.txt
---
 build-queue/{tasks => done}/37-billable-report.md |  0
 data/local-fleet.json                             |  2 +-
 public/ads.txt                                    |  9 +++++++++
 server.js                                         | 22 ++++++++++++++++++++++
 4 files changed, 32 insertions(+), 1 deletion(-)

diff --git a/build-queue/tasks/37-billable-report.md b/build-queue/done/37-billable-report.md
similarity index 100%
rename from build-queue/tasks/37-billable-report.md
rename to build-queue/done/37-billable-report.md
diff --git a/data/local-fleet.json b/data/local-fleet.json
index efbffd8..7b15295 100644
--- a/data/local-fleet.json
+++ b/data/local-fleet.json
@@ -1 +1 @@
-{"fetchedAt":"2026-07-02T01:04:47.160Z","procs":[{"name":"api-token-dashboard","status":"online","cpu":0.2,"memMB":48,"restarts":null,"uptimeMs":7645000,"startedAt":"2026-07-01T22:57:22.160Z"},{"name":"abramsego","status":"stopped","cpu":null,"memMB":null,"restarts":null,"uptimeMs":null,"startedAt":null}]}
\ No newline at end of file
+{"fetchedAt":"2026-07-02T01:07:57.246Z","procs":[{"name":"api-token-dashboard","status":"online","cpu":0,"memMB":48,"restarts":null,"uptimeMs":7835000,"startedAt":"2026-07-01T22:57:22.246Z"},{"name":"abramsego","status":"stopped","cpu":null,"memMB":null,"restarts":null,"uptimeMs":null,"startedAt":null}]}
\ No newline at end of file
diff --git a/public/ads.txt b/public/ads.txt
new file mode 100644
index 0000000..baff3f9
--- /dev/null
+++ b/public/ads.txt
@@ -0,0 +1,9 @@
+# ads.txt — AbramsEgo (stub)
+# No ad-network account exists yet; this file is populated when Steve joins a
+# network (gated switch: env AD_NETWORK_PUB_ID). Until then the landing page
+# serves a house ad only.
+#
+# Entry format per the IAB ads.txt spec:
+#   <ad system domain>, <publisher account ID>, <DIRECT|RESELLER>, <cert authority ID>
+# Example (Google AdSense):
+#   google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0
diff --git a/server.js b/server.js
index 758b972..2170c87 100644
--- a/server.js
+++ b/server.js
@@ -33,6 +33,10 @@ 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();
 
@@ -752,6 +756,11 @@ function collectRevenue() {
   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.
+  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;
@@ -896,6 +905,9 @@ function isPublicLandingPath(req) {
     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;
     // 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';
@@ -1286,6 +1298,16 @@ app.get('/api/affiliates', (req, res) => {
   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 });
+});
+
 // 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.

← aa85d9a feat(billable): fleet health report generator + sample  ·  back to AbramsEgo  ·  feat(ads): house-ad slot + ads.txt stub e664926 →