feat(ads): terminal (ASCII) ad format served as plain text - #161
Merged
Merged
Conversation
Terminals can't run /ad.js and can't render HTML, so the ad network had nothing to sell to shells, SSH banners, BBS screens, or CLIs. This adds a terminal_ascii format that is fetched, not embedded: curl -s "https://crawlproof.com/api/ads/motd?slot=<id>" - lib/ads/terminal.ts renders a fixed-width, pure-ASCII box. Advertiser copy is untrusted text going straight into a TTY, so escapes/control chars are stripped and non-ASCII is folded (accents) or dropped (CJK, emoji) — both to keep terminals safe and to keep column maths honest. - /api/ads/motd returns text/plain, with ?cols=44..120, ?color=1 for ANSI, and ?src=<tag> to tell surfaces apart. Impressions meter server-side via serveAd exactly like the HTML paths; an unknown slot falls back to the house ad so a login banner is never blank. - Terminal clients identify as curl/wget/etc., which the tracker buckets as "bot". On this endpoint that's the actual audience, so a dedicated classifier lets shell clients through while still excluding crawlers. The click path deliberately keeps the strict rule: scripted hits on /a/<id> stay unbilled. - Clicks use a short /a/<impression_id> URL (the long query-string form is unusable as printed text) and carry utm_source/medium/content through to the advertiser, since a shell sends no referrer. - lib/ads/template.ts parses {{ads}} / {{ads:64}} / {{ads:terminal:64}} so a publisher's server can place the fill in its own template. - Migration widens the format CHECK, adds terminal_ascii to slot inventory, and backfills a terminal creative for every existing campaign from the copy it already has (no LLM call, no advertiser action). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Terminals can't run
/ad.jsand can't render HTML, so the ad network had nothing to sell to shells, SSH banners, BBS screens, or CLIs. This adds aterminal_asciiformat that is fetched, not embedded:What's in it
lib/ads/terminal.ts— fixed-width, pure-ASCII renderer. Advertiser copy is untrusted text going straight into a TTY, so escape/control chars are stripped and non-ASCII is folded (accents) or dropped (CJK, emoji) — both to keep terminals safe from cursor/screen spoofing and to keep the column maths honest.GET /api/ads/motd—text/plain,?cols=44..120,?color=1for ANSI truecolour,?src=<tag>to tell surfaces apart. Impressions meter server-side throughserveAdexactly like the HTML paths; an unknown or inactive slot falls back to the (unmetered) house ad so a login banner is never blank.curl/wget/etc., whichlib/tracker/devicebuckets asbot— correct for a web page, wrong on this endpoint, where that is the audience.terminalDeviceTypelets shell clients through and still excludes real crawlers. Without this a terminal slot could only ever serve house ads.GET /a/<impression_id>— short click redirector. The long/api/ads/click?i=…&s=…&c=…&cr=…form is unusable as printed text. Resolves slot/campaign/creative from the impression row, meters via the sameresolveClick, then 302s with?ref=plusutm_source/medium/content— a shell sends no referrer, so without a tag the advertiser sees nothing. This path keeps the strict bot rule on purpose: scripted hits stay unbilled.lib/ads/template.ts—{{ads}},{{ads:64}},{{ads:terminal:64}}token parsing so a publisher's server can place the fill inside its own template. A failed fill removes the token; a reader never sees raw{{ads}}.PUBLISHER_FORMAT_IDS, so the GitHub auto-installer never injects it into HTML.terminal_asciito slot inventory (default + backfill), and backfills a terminal creative for every existing campaign from the copy it already has: no LLM call, no advertiser action.Already applied to prod
The migration is applied (via Supabase MCP): 25/25 campaigns now have a ready
terminal_asciicreative, 23/23 slots accept the format. The code is what's still unshipped.Testing
tests/contract/ads-terminal.test.ts(25) — box geometry at every width, escape/control stripping, forged-border copy, ANSI width invariance, URL never truncated, client classification.tests/contract/ads-template.test.ts(14) — token grammar, aliases, clamping, dedup, failure never leaking a token.tsc --noEmitclean./a/<id>302'd tohttps://pairux.com/?ref=crawlproof-ad-015&utm_source=crawlproof&utm_medium=terminal&utm_content=motd. The verification click was recorded unbilled (valid=false, $0) — no advertiser was charged.🤖 Generated with Claude Code