Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
34de3df
chore(api): add carbon ads sdk
distroinfinity Sep 19, 2026
f3230e7
feat: sponsored slot payload, feeds preference, status line panel
distroinfinity Sep 19, 2026
99750a9
feat(api): ad_impressions table + enabled_feeds preference
distroinfinity Sep 19, 2026
97c500f
feat(api): ad supply — carbon provider, house fallback, delivery rows
distroinfinity Sep 19, 2026
ecf75cc
feat(api): serve sponsored slots by enabled feeds
distroinfinity Sep 19, 2026
64dd67d
feat(api): record ad impressions and click redirect
distroinfinity Sep 19, 2026
9cd06f9
feat(api): estimated earnings endpoints
distroinfinity Sep 19, 2026
853d719
fix(api): bare ad click path redirects home
distroinfinity Sep 19, 2026
ae2bde2
feat(cli): ledger + sync ad impressions, open command
distroinfinity Sep 19, 2026
32cde58
feat(cli): feeds preference, ads-first onboarding and status
distroinfinity Sep 19, 2026
08f9d70
feat(cli): 4-decimal sub-dollar totals, dev link script
distroinfinity Sep 19, 2026
cc1d9ae
feat: short click links + simpler, bar-led sponsored panel
distroinfinity Sep 19, 2026
7abdd1f
feat(dashboard): revenue page, feeds toggle, overview earnings strip
distroinfinity Sep 19, 2026
b71b83e
docs: ads-first positioning, ads architecture, demo script
distroinfinity Sep 19, 2026
580d0f1
fix(cli): uninstall strips only distro entries, never restores a stal…
distroinfinity Sep 19, 2026
195b2af
feat(api): earning rate, view time and ranged timeseries for the live…
distroinfinity Sep 19, 2026
c908073
feat(landing): ads-first positioning with channels as the opt-in alte…
distroinfinity Sep 19, 2026
71b1aa0
feat(landing): advertisers vision page
distroinfinity Sep 19, 2026
9cc5f1b
feat(cli): sync ad impressions within seconds
distroinfinity Sep 19, 2026
90404d5
feat(dashboard): live revenue page — counting balance, agent-hour rat…
distroinfinity Sep 19, 2026
45fbcd6
docs: exchange-first, story-led positioning rule
distroinfinity Sep 19, 2026
2cb287b
feat(landing): story-led exchange positioning
distroinfinity Sep 19, 2026
9edcbe4
docs: demo script opens on the exchange thesis
distroinfinity Sep 19, 2026
444f45d
feat(landing): four-section page, revenue structure without published…
distroinfinity Sep 19, 2026
020a9f0
feat: advertiser-written direct ads — create, serve by bid, stats, pause
distroinfinity Sep 19, 2026
be33fa3
copy: product name is Distro TV; drop the 'building it' lines
distroinfinity Sep 19, 2026
47d81d1
feat(advertisers): ad portal + composer, import options first
distroinfinity Sep 19, 2026
f154952
copy: trim slot preview caption; demo script covers the ad portal
distroinfinity Sep 19, 2026
09b65b1
copy: cut the filler from the slot preview caption
distroinfinity Sep 19, 2026
3ab5e0e
feat(advertisers): monochrome Google Ads, Meta and Amazon marks on th…
distroinfinity Sep 19, 2026
f80d467
fix(api): every batch leads with the highest bid
distroinfinity Sep 19, 2026
cf47b4d
fix: live dashboard stays inside rate limits — one overview request, …
distroinfinity Sep 19, 2026
1fed420
docs: product README, demo script, source zip script
distroinfinity Sep 19, 2026
822b0dd
feat: production guards — version-gated ads, admin ad review, own hou…
distroinfinity Sep 19, 2026
318fe79
fix(onboarding): setup preview shows the real status-line panel; pair…
distroinfinity Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ docs/superpowers/plans/
# cli release staging — built per-release, never tracked
.release-staging/
frontend/public/distrotv-cli.tar.gz

# source bundles from scripts/make-source-zip.sh
*_Submission.zip
17 changes: 14 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

## Project Overview

Distro TV is an ambient **channel surface** that runs in the developer's terminal while AI coding tools work. Launch channels: **CH 01 NEWS** (HN, TechCrunch, Bloomberg, Reuters) and **CH 02 MARKETS** (stocks, crypto, FX, indices, watchlist + sparklines). Future channels (weather, build status, deploy logs, sports, calendar, crypto deep) slot into the same surface — the product is the surface, channels are the verticals. Originally launched as DevDrip (opt-in ads + USDC micropayments); pivoted to Distro TV in May 2026.
Distro is **the ad exchange for AI agent surfaces**. Agents do the work while people wait; that wait is a new attention surface with no market layer. The first surface is the developer's terminal: Distro TV shows **sponsored slots while AI coding tools work** and shares the ad revenue with the viewer (ads are the default feed). **CH 01 NEWS**, **CH 02 MARKETS** and **CH 03 UTILITIES** are opt-in channels on the same surface for people who'd rather not see ads. Long-term: an **open ad exchange for AI agent surfaces** — anyone can bid for slots the way they do for web display or a billboard; the terminal is the first inventory. History: launched as DevDrip (opt-in ads + USDC micropayments), pivoted to channels-only Distro TV in May 2026, re-centered on ads in Sep 2026 — without crypto.

## Architecture

- **CLI + Daemon** — `@distrotv/cli` distributed via GitHub Releases + `curl ... | sh` install script (NOT npm). Binary: `distro`, alias `dtv`. Hooks into Claude Code via settings.json (PreToolUse, Stop, UserPromptSubmit). Daemon on Unix socket manages slot display, key capture, local ledger (SQLite).
- **Backend API** — Express + Drizzle ORM + Railway Postgres + Upstash Redis. Auth, device registration, channels, watchlists, alerts, slot impression ingestion.
- **Dashboard** — Next.js 14, App Router, Tailwind. Reading list, watchlist management, preferences.
- **Payments** — deferred post-M1. Base Sepolia testnet targeted for M6+.
- **Ads** — API owns supply: advertiser-written direct ads (`ad_campaigns`, portal at `/advertisers/portal`, highest bid first) → Carbon Ads (sandbox) → house-ad fallback, served as `sponsored` slots through `/me/content/next`. Each served ad is a row in `ad_impressions` (the delivery record); `/ingest` marks it seen, `GET /c/:code` (short) and `GET /ads/click/:deliveryId` record the click and redirect. See `gitbook-docs/architecture/ads.md`.
- **Payments** — none. Advertisers pay Distro, Distro pays the developer a share and keeps a margin. Earnings are **estimated** internally as `AD_CPM_RATE`/1000 × `REVENUE_SHARE_DEVELOPER` per viewable ad; neither number is shown to users. The dashboard shows a disabled "Payouts — coming soon" button. No crypto, wallets, or USDC.

## Tech Stack

Expand All @@ -28,18 +29,28 @@ Distro TV is an ambient **channel surface** that runs in the developer's termina
- M6: dashboard polish (shipped)
- M7: admin dashboard (shipped)
- M8: landing page + install vector (shipped — channels positioning, curl/GH Releases install)
- M9: ad exchange (branch `ad-exchange`) — sponsored slots on by default, estimated revenue dashboard, channels opt-in, ads-first landing + `/advertisers` exchange page. local only; not released.

## Hard Rules

- <200ms slot vanish — hard requirement, measure it, log it
- hooks always exit 0 — never block Claude Code
- local ledger is ground truth — backend can be down
- no grace period before showing slots — surface as soon as Claude takes over (`GRACE_PERIOD_MS = 0`). Fast tool calls are gated by the slot vanish timer + frequency caps, not by a pre-show delay.
- **lead with _channels_ as the surface noun, never with "news + markets"** — Distro TV is a channel platform; NEWS and MARKETS are the two launch channels; future verticals slot into the same surface. Marketing, docs, and product copy must reflect this. The two-tangent "news AND market data" framing is what we explicitly pivoted away from in M8.
- **lead with the exchange, and tell the story — never pitch.** Headline: _"The ad exchange for AI agent surfaces."_ The arc: every new medium grew an ad market → AI agents created a new attention surface (the wait) → nothing serves it → Distro is the open exchange for it → the terminal is the first surface, live today. Read like infrastructure, not a consumer offer: no "get paid" headlines, no benefit-led selling. Never surface product defaults (e.g. "ads on by default") in marketing. Channels (news, markets, utilities) appear as "the slot isn't only for ads", never as the headline. Status tags must be honest: _live_ / _next_ / _planned_ — never imply bidding or self-serve exists.
- **money is always labeled _estimated_** until real payouts exist. No crypto, wallet, or USDC language anywhere.
- **never publish the split or exact rates.** The revenue structure in public copy is: _advertisers pay Distro TV, Distro TV pays the developer a share, and keeps a margin_. No "70%", no CPM figures, on the site, dashboard or CLI. The developer angle: idle agent time earns money and helps pay for the AI tools that cause it. (`REVENUE_SHARE_DEVELOPER` and `AD_CPM_RATE` stay internal.)
- **ad-platform marks (Google Ads, Meta, Amazon) appear only as monochrome icons beside their names**, in the theme's ink colour, on "import — coming soon" tiles (`frontend/components/advertisers/brand-mark.tsx`). They name a planned import, never a partnership. No full-colour logos, no marks anywhere else. Check each brand's guidelines before a public launch — Amazon in particular restricts use (its icon was pulled from simple-icons).
- **the product name is _Distro TV_, always in full** in user-facing copy (site, dashboard, CLI, docs) — never bare "Distro". Don't write "Distro is building it" style filler lines.
- **copy is short, plain and precise.** Short sentences. One idea each. Say a thing once per page: if the demo shows it, the text doesn't repeat it. The landing page is four sections — thesis, lineage, how the money moves, two doors (developers / advertisers). Don't add sections to it without removing one.
- **ad clicks go through our redirect** (`/c/:code` or `/ads/click/:deliveryId`). The redirect target comes only from the server-side delivery row, never from the request.
- **an ad pays only if it was on screen ≥ 1s and not skipped.** The same rule lives in the API (`computeEarned`) and the CLI ledger (`sumTodayOptimistic`) — change both together.
- **the sponsored panel prints the full click URL.** Terminals make a visible `http(s)://` URL cmd/ctrl-clickable; that is the only same-terminal click that needs no key capture. Never truncate it. Every panel row starts with the `▍` bar because Claude Code strips leading whitespace from status lines.
- **CLI distribution = `curl -fsSL https://get.distrotv.xyz/install.sh | sh` + GitHub Releases, never npm publish.** install.sh is served from **GitHub Pages at `get.distrotv.xyz`**, NOT Vercel — Vercel's edge firewall JS-challenges `curl` (`x-vercel-mitigated: challenge`), which `curl | sh` can't solve. Source file is `frontend/public/install.sh` (single source of truth), deployed by `.github/workflows/deploy-install.yml`; it pulls the latest tarball from `releases/latest/download/distrotv-cli.tar.gz`. Releases are triggered by pushing a `cli-v*` git tag. install.sh lays out a **versioned install** — each release lands in `~/.distrotv/versions/<v>/`; `~/.distrotv/current` symlink points at the active version; shims and Claude hook entries resolve through `current/dist/index.js`. the CLI **auto-updates** on the daemon's tick: the **version signal comes from our API** (`GET /cli/version-check`, gated by `LATEST_CLI_VERSION` env var on Railway — unset = no update advertised); tarballs still live on GitHub Releases. set `LATEST_CLI_VERSION=<version>` after each release to roll out; unset to halt. opt-out: `DISTRO_NO_AUTOUPDATE=1` or `cli.autoUpdate: false`. See `gitbook-docs/cli/releases.md`.

## Dev Rules

- local dev: `scripts/dev-link-cli.sh` points `~/.distrotv/current` + the `distro`/`dtv` shims at this checkout's CLI build (hooks, status line and daemon then run the working tree). API on `:3011`, web on `:3010`, Postgres via docker compose.
- load frontend-design skill for anything frontend
- minimal comments, crisp pointers, lowercase start
- never mention claude or ai in commits, keep messages crisp
Expand Down
104 changes: 76 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,95 @@
# Distro TV

Your terminal's news + market feed, while the agent works. An ambient slot anchored at the bottom of your terminal during AI-coding-tool idle time. Tech + finance news from HN / TechCrunch / Bloomberg / Reuters. A watchlist of stocks + crypto with sparklines and key stats.
**The ad exchange for AI agent surfaces.**

## Modes
AI agents do the work. People wait and watch. That attention has no market yet. Distro TV is
that market, starting in the terminal.

- **news** — every slot is news (HN, TechCrunch, Bloomberg, Reuters, etc.)
- **markets** — every slot is a ticker from your watchlist
- **mix** — alternates news + markets; alert-driven priority bumps a ticker when it moves >5%
Every medium grew an ad market: print, broadcast, the web, mobile. Agent tools are the first
major surface with none. While a coding agent runs, a developer watches a terminal for thirty
seconds, two minutes, ten. Distro TV puts one text ad in that wait, pays the developer a share,
and clears the instant they type.

Pick one during `distro init`; flip any time with `distro preferences`.
## How it works

## Structure
```
advertiser ──writes an ad, sets a bid──▶ Distro TV ──serves by bid──▶ developer's terminal
▲ │ │
└────── views · clicks · spend ─────────┴──── pays the viewer a share ◀┘
```

1. **Advertisers pay Distro TV.** They write a one-line text ad in the portal and set a CPM bid.
Only views of a second or more count.
2. **Distro TV runs the exchange.** Highest bid first, then network fill (Carbon Ads sandbox),
then house ads. It keeps a margin.
3. **Developers get paid.** Idle agent time earns money. It helps pay for the AI tools that
cause it.

Opt-in only. The slot clears when you type. Prefer no ads? Tune the slot to news or markets.

## What is in the box

| Part | What it does |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `packages/cli` | `distro` / `dtv`. Hooks into Claude Code, runs a small daemon, renders the ad in the status line, records views in a local SQLite ledger, syncs them within seconds. |
| `packages/api` | Express + Postgres. Ad supply, delivery records, the pay rule, click redirects, earnings, the advertiser API. |
| `frontend` | Next.js. Landing page, developer dashboard with a live revenue page, advertiser page and ad portal. |
| `packages/shared` | Types and constants shared by all three. |

The ad, as a developer sees it:

```
frontend/ # Next.js landing + dashboard (Vercel)
packages/
cli/ # @distrotv/cli — Claude Code hooks + daemon + slot renderer
api/ # Express backend — auth, channels, watchlists, alerts
shared/ # @distrotv/shared — slot payload types, DTOs
docs/ # specs, design docs
gitbook-docs/ # engineering reference; mirrors github.io
▍ AD Lumen Deploy +$0.0175
▍ Deploys that finish before your coffee does.
▍ ↗ https://api.distrotv.xyz/c/2472fa528d42 ⌘ click est. today $3.42
```

## Local dev quickstart (post-M1)
The link is a real URL on purpose: terminals make any visible `https://` link ⌘-clickable, so
the click works inside the same terminal with no key capture.

## What the exchange does today

- ad supply: advertiser-written ads served by bid, then Carbon Ads (sandbox), then house ads
- delivery records, a viewability pay rule (on screen ≥ 1s, not skipped), replay-safe ingest
- tracked click redirects with short links, and a same-terminal ⌘-click
- ads as the default feed; news and markets as opt-in channels on the same slot
- a live revenue dashboard: counting balance, per-agent-hour rate, 1h / 24h / 30d chart
- an advertiser portal: write an ad, set a bid, watch views and clicks, pause

How it relates to AI: Distro TV is infrastructure for AI agent tools. It integrates with Claude
Code through its hooks and status line, and turns the time an agent spends working into an ad
surface.

## Run it locally

Needs Node 20+, pnpm, Docker.

```bash
pnpm clean && pnpm install && pnpm build
docker compose up -d postgres
pnpm install
docker compose up -d postgres # local Postgres
cp packages/api/.env.example packages/api/.env # fill in GitHub OAuth + secrets
pnpm --filter @distrotv/api db:migrate
pnpm --filter @distrotv/api dev # one shell
node packages/cli/dist/index.js init # another shell
node packages/cli/dist/index.js daemon start
pnpm --filter @distrotv/api dev # api on :3011
cd frontend && pnpm exec next dev -p 3010 # web on :3010

pnpm --filter @distrotv/shared build && pnpm --filter @distrotv/cli build
scripts/dev-link-cli.sh # point ~/.distrotv at this checkout
DISTRO_ENV=local dtv init # pair with GitHub, install the Claude Code hooks
```

After M1, `distro init` writes hooks but slot rendering arrives in M3 (news) and M4 (tickers).
Open a Claude Code session and give the agent a task. The ad appears in the status line.

## Deploy
- Revenue: <http://localhost:3010/dashboard/revenue>
- Ad portal: <http://localhost:3010/advertisers/portal>

- API: Railway autodeploy from `main`
- Frontend: GitHub Actions → Vercel after CI succeeds
- CLI: published to npm as `@distrotv/cli` via `pnpm release`
## Status, plainly

## Specs
- Live: terminal surface, ad serving, views and clicks measured end to end, revenue dashboard,
advertiser portal.
- Sandbox: network ads come from Carbon's demo zone. All money is an **estimate**. Nothing is
charged and nothing is paid out yet.
- Planned: importing ads from Google Ads, Meta Ads and Amazon Ads; budgets and targeting; an ad
review queue; more surfaces (IDE agent panels, agent web apps).

- Active pivot spec: `docs/superpowers/specs/2026-05-05-distro-tv-pivot-design.md`
- Plan overview: `docs/superpowers/plans/2026-05-05-distro-tv-overview.md`
More detail: [`gitbook-docs/architecture/ads.md`](gitbook-docs/architecture/ads.md) ·
demo script: [`docs/demo-script.md`](docs/demo-script.md).
54 changes: 54 additions & 0 deletions docs/demo-script.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Demo script — 3 minutes

Pitch in one line: **developers watch a terminal for minutes at a time while their agent
works. We turn that attention into ad inventory, pay the developer for it, and open it up as
an exchange anyone can bid on.**

## Before you present

- [ ] `docker ps` shows `devdrip-postgres` healthy (port 5434)
- [ ] API up: `curl -s localhost:3011/health` → `"status":"ok"` (`pnpm --filter @distrotv/api dev`)
- [ ] Web up on 3010 (`cd frontend && pnpm exec next dev -p 3010`), signed in at `/dashboard`
- [ ] `dtv status` → `ads: on`, daemon running. If the CLI was rebuilt: `dtv daemon stop`
- [ ] Revenue page already has history so the chart is not empty (let an agent run for a few
minutes beforehand)
- [ ] `dtv preferences` → feeds = ads only, so the opt-in step has something to show
- [ ] Terminal font size up; window at least 100 columns wide so the ad link fits on one row
- [ ] Browser tabs ready: landing `/`, `/dashboard/revenue`, `/advertisers/portal` (signed in)
- [ ] Have one ad already running in the portal so "Your ads" shows live numbers; write the second one live

## Script

1. **The thesis (25s)** — landing page. Read the headline, then scroll to the lineage table: print, broadcast, web, mobile all grew an ad market; agents have "nothing yet". Type into the hero terminal so the slot clears — "this is the whole product in one gesture."
2. **The product (60s)** — in Claude Code, give the agent a real multi-step task.
- A sponsored slot appears in the status line the moment the agent takes over.
- Point at `+$0.0070` and `est. today`. It rotates every 12 seconds.
- **⌘-click the link in the ad.** The advertiser opens in the browser. No second terminal,
no key capture.
- Start typing: the ad is gone. It never competes with you for the keyboard.
3. **The money (40s)** — `/dashboard/revenue`. Estimated balance moved; ads seen, clicks, CTR,
served vs seen; **Payouts — coming soon**. Say plainly: sandbox ads, estimated CPM.
4. **The choice (30s)** — `dtv preferences` → feeds → add News. The terminal now alternates
ad / headline. "Don't want ads? Tune to a channel. Same surface."
5. **The other side (40s)** — `/advertisers/portal`. Google Ads and Meta Ads import come
first (coming soon: search ads are text, so they map one to one). Write an ad by hand:
brand, one line, link, bid. Watch the terminal-slot preview update as you type. Hit
**Run this ad** — it shows up in the terminal within a minute or so, and its views and
clicks tick up in "Your ads". A higher bid runs first and pays the developer more.

## If something breaks

| Symptom | Fix |
| ----------------------------- | ----------------------------------------------------------------------------------- |
| No ad in the status line | open a **new** Claude Code session; check `dtv status`; `tail ~/.distro/daemon.log` |
| Ad shows, numbers do not move | `dtv sync --force`, reload the revenue page |
| Carbon has no fill | nothing to do: house ads fill every slot |
| ⌘-click does nothing | `dtv open` from any terminal opens the ad on screen |
| API restarted mid-demo | nothing to do: deliveries live in Postgres |

## Questions you will get

- The ads are Carbon's sandbox zone plus demo creatives. No advertiser is paying yet.
- Advertisers pay Distro; Distro pays the developer a share and keeps a margin. The numbers on screen are estimates from a sandbox rate. The split and rates are not final, so don't quote them. Payouts are not built.
- The exchange (bidding, self-serve, account import) is the roadmap. What is built is the
supply side end to end: serving, viewability, clicks, attribution, reporting.
Loading
Loading