← back to Agent Ad Network

README.md

67 lines

# Agent Ad Network

Ads served into **AI-agent wait states** — the loading pauses inside Claude Code,
autonomous agents, CLIs, and pipelines. Advertisers (humans *or* agents) place ads
via a self-serve API; any agent fetches one line of sponsored text while its user
waits on something slow.

**Status: LIVE at https://ads.agentabrams.com** (Kamatera pm2 `agent-ad-network`
:9939, LE cert, grey-cloud DNS; deployed 2026-08-02 on Steve's go). Ledger
billing per impression at CPM/1000, plus Stripe **TEST-mode** rails
(`/api/billing/*`) — hard-refuses anything but `sk_test_` keys; waiting on Steve
to paste `STRIPE_TEST_KEY` then run `node scripts/stripe-test-setup.js`
(creates test product/price + webhook, prints `STRIPE_WEBHOOK_SECRET`). TK-10131.

## Run

    node server.js          # http://127.0.0.1:9932  (admin: admin / DW2024!)

## The three surfaces

### 1. Supply side — an agent shows an ad during a wait
    GET /ad?format=text&context=git,deploy     → "[ad] headline — body → click-url"
    GET /ad?format=json&context=...            → {"ad":{headline,body,clickUrl,sponsor}}
- Fail-open: 204/null when no eligible ad — integrations never break.
- `context` keywords boost matching ads (score = cpm × (1 + keyword hits), weighted random).
- SDK: `sdk/waitad.mjs` (`withAd(promise, {context})` — only shows an ad if the wait
  exceeds 400ms) and `sdk/waitad.sh` (`waitad "ctx"` / `waitad_wrap`).

### 2. Demand side — agents PLACE ads (self-serve API)
    POST /api/advertisers  {name, email}                  → {id, apiKey}
    POST /api/ads          x-api-key: <key>
      {headline, body, url, keywords[], cpm_usd, budget_usd}
    GET  /api/ads          x-api-key: <key>               → own ads + impressions/clicks/ctr
    PATCH /api/ads/:id     x-api-key: <key>  {status: paused|active, budget_usd}
Because it's a plain JSON API, an AI agent can register and place a campaign
autonomously — that's the "agents place ads" loop.

### 0. Public landing — advertiser bait
    /                → PUBLIC (no auth): live anonymized three.js galaxy fed by
                       GET /api/galaxy (opaque HMAC ids, numbers only — no ad
                       copy/sponsor/url ever leaves it), live totals, self-serve
                       curl walkthrough. NOTE: ads.agentabrams.com belongs to
                       THIS app (:9939) — the ads-dashboard (TK-10135, :9766)
                       lives on ads.designerwallcoverings.com only.

### 3. Admin — dashboard + measurement
    /admin           → dashboard (basic auth): search/sort/column-toggle table,
                       expandable rows, created date+time per ad, drill links
    /admin/events    → raw event ledger (filter ?ad=&type=)
    /api/stats       → public aggregate counts
    /c/:adId         → click tracking → 302 to target

## Claude Code integration (paste for Steve — settings.json is Steve-gated)

A PreToolUse hook that prints a sponsored line before long-running Bash commands:

    "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command",
      "command": "curl -s --max-time 1 'http://127.0.0.1:9932/ad?format=text&context=bash' || true" } ] } ] }

Lighter-touch alternative: put `waitad` output in the statusline command.

## Gated next steps (need Steve's go)
- Deploy to ads.agentabrams.com (Kamatera + CF DNS) so remote agents can hit it.
- Real billing: Stripe (TEST first, per AbramsEgo rails).
- Outreach / advertiser recruitment (send-to-list is gated).
- Fleet adoption: install the waitad hook into Claude settings (classifier-blocked, Steve-paste).