Vibecode Beacons
track this build6 phases, 12 steps, beginner friendly0%The link page is the Linktree build. The extras (a small store, an email capture, a media kit page) are each a sitting on top. What stays theirs is payments and email delivery at scale, and the transaction fee is the price of not touching either.
You are building a lean indie version of Beacons. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Beacons · indie build A creator link page with a till and a list: links and products from one JSON file, click tracking through your own redirect, Stripe Payment Links for anything sold (you never touch card data), and an email capture stored locally. Linktree with two honest bolt-ons. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | server-rendered page, a redirect, a signup | | Payments | Stripe Payment Links | no cart, no checkout, no card data on your server | | Hosting | A VPS behind Caddy | the redirect and signup need a process | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **Your links, bio, avatar and the products you sell** · free - Why: page.json renders exactly this. - Get it: Titles, URLs, a one-line bio, a square avatar, and each product's name, price and file. - [ ] **A Stripe account with Payment Links** · free; fees per sale - Why: Every product card opens a Payment Link. Enable the send-a-link option so Stripe delivers the file. - Get it: dashboard.stripe.com > Payment Links > New. Test mode first. Copy each link URL into page.json. - [ ] **A random salt for hashing IPs** · free - Why: Click and signup rows store hashes, not addresses. - Get it: openssl rand -hex 32 into .env. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: The page at your address. - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server ## Quick start ```sh mkdir creator-page && cd creator-page && git init && npm init -y && npm pkg set type=module mkdir -p data public && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Sending email; export into a real sender. - The media kit, brand-deal tooling and the editor. - hosted checkout with digital delivery done for you - email sending to your list - the media kit and brand-deal tooling - templates and the editor If one of those is essential to you, that is the reason to keep paying for Beacons, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Beacons The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a creator link page with a small store and an email list like Beacons. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - One Node process with node:http and node:sqlite. Server-rendered HTML, inline CSS. No framework. - Stripe Payment Links for anything sold · you never handle card data. SQLite for clicks and signups. ### Data model (create this before Phase 1) - page.json: profile (name, bio, avatar, accent), links (slug, title, url, emoji, enabled), products (slug, title, price_text, payment_link_url, image) - clicks: id, slug, clicked_at, referer, user_agent_class, ip_hash - signups: id, email (unique), created_at, ip_hash ### Phase 1 · The page Build: render profile, links and products from page.json, mobile-first, with JavaScript disabled everything still works. Validate the JSON at startup and refuse to boot with the bad entry named. Done when: the page renders at 375px with JS off and a malformed page.json stops the server with a message naming the entry. Do not build yet: tracking, store, signup. ### Phase 2 · Design Build: a typographic hierarchy, 44px tap targets, the accent color as a CSS custom property, dark mode, focus-visible rings. No external requests. Done when: zero third-party requests, no contrast failures in either scheme, keyboard operable. ### Phase 3 · Click tracking Build: /go/:slug records a row and 302s; unknown slugs go home. Bot user agents bucketed separately. /stats behind basic auth from .env with clicks per link over 7 and 30 days as inline SVG. Done when: a click lands with one row, an unknown slug lands on the homepage, and totals reconcile with a GROUP BY. ### Phase 4 · The store Build: product cards that open the Stripe Payment Link. Digital delivery is Stripe's job: enable the "send a link" option on the Payment Link. No cart, no checkout of your own. Done when: a test-mode purchase completes and the buyer receives the file link from Stripe. ### Phase 5 · Email capture Build: a signup form storing to signups with a honeypot and a per-IP rate limit, and a CSV export behind basic auth. No sending in v1. Done when: a duplicate email is a no-op, the sixth signup from one IP in an hour is refused, and the export opens in a spreadsheet. ### Phase 6 · Cards and deploy Build: OG meta and a generated OG image, a /healthz endpoint, a systemd unit, a nightly backup and the README. Done when: the link previews correctly when shared and a reader goes from clone to live on their domain. ### Out of scope (and why) - Sending email. Export into a real sender; deliverability is a whole product. - The media kit, brand-deal tooling and the editor. ### README must contain - The page.json reference, and the note that slugs are permanent. - The Stripe Payment Link setup including digital delivery. ===== AGENTS.md ===== # Agent instructions · Beacons indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, node:http and node:sqlite, Stripe Payment Links, A VPS behind Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Beacons A creator link page with a till and a list: links and products from one JSON file, click tracking through your own redirect, Stripe Payment Links for anything sold (you never touch card data), and an email capture stored locally. Linktree with two honest bolt-ons. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The page Profile, links and products from page.json; JavaScript-free; refuses to boot on a broken file. ### Steps 1. Create the project and page.json profile (name, bio, avatar, accent), links (slug, title, url, emoji, enabled), products (slug, title, price_text, payment_link_url, image). ```sh mkdir creator-page && cd creator-page && git init && npm init -y && npm pkg set type=module mkdir -p data public && cp .env.example .env ``` 2. Validate at startup and render server-side, mobile-first ### Done when - [ ] Renders at 375px with JavaScript disabled - [ ] A malformed page.json stops the server naming the entry ## Phase 2 · Design Tap targets, accent, dark mode, focus rings, no external requests. ### Steps 1. Typographic hierarchy and 44px tap targets 2. Accent custom property, prefers-color-scheme, focus-visible ### Done when - [ ] Zero third-party requests - [ ] No contrast failures - [ ] Keyboard operable ## Phase 3 · Click tracking /go/:slug records after redirecting; bots bucketed; /stats private. ### Steps 1. Implement /go/:slug with the 302 sent before the insert; unknown slugs go home 2. Bucket bot user agents and build /stats behind basic auth with inline SVG ### Done when - [ ] A click lands with one row - [ ] Totals reconcile with GROUP BY - [ ] The redirect works with the database stopped ## Phase 4 · The store Product cards open Stripe Payment Links; delivery is Stripe's job. ### Steps 1. Render product cards linking to payment_link_url 2. Enable send-a-link delivery on each Payment Link and test a purchase in test mode ### Done when - [ ] A test-mode purchase completes and the buyer receives the file link from Stripe ## Phase 5 · Email capture Signups stored locally, no sending. ### Steps 1. Signup form with honeypot and per-IP rate limit 2. CSV export behind basic auth ### Done when - [ ] A duplicate email is a no-op - [ ] The sixth signup from one IP in an hour is refused - [ ] The export opens in a spreadsheet ## Phase 6 · Cards and deploy OG image, healthz, service, backup, README. ### Steps 1. OG meta and a generated OG image; /healthz; systemd; Caddy; nightly backup 2. README: page.json reference, slugs are permanent, Stripe delivery setup Files: `README.md` ### Done when - [ ] Previews correctly when shared - [ ] The README reaches a live page on your domain ## Not in this build - Sending email; export into a real sender. - The media kit, brand-deal tooling and the editor. ## After v1, if you want it - A tiny editor for page.json - Per-link QR codes ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite for clicks and signups. DATABASE_PATH=./data/page.db # Required. Public base URL. SITE_URL=https://you.yourdomain.com # Required · secret. openssl rand -hex 32. IP_SALT=hex # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
You are building a lean indie version of Beacons. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Beacons · indie build A creator link page with a till and a list: links and products from one JSON file, click tracking through your own redirect, Stripe Payment Links for anything sold (you never touch card data), and an email capture stored locally. Linktree with two honest bolt-ons. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | server-rendered page, a redirect, a signup | | Payments | Stripe Payment Links | no cart, no checkout, no card data on your server | | Hosting | A VPS behind Caddy | the redirect and signup need a process | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **Your links, bio, avatar and the products you sell** · free - Why: page.json renders exactly this. - Get it: Titles, URLs, a one-line bio, a square avatar, and each product's name, price and file. - [ ] **A Stripe account with Payment Links** · free; fees per sale - Why: Every product card opens a Payment Link. Enable the send-a-link option so Stripe delivers the file. - Get it: dashboard.stripe.com > Payment Links > New. Test mode first. Copy each link URL into page.json. - [ ] **A random salt for hashing IPs** · free - Why: Click and signup rows store hashes, not addresses. - Get it: openssl rand -hex 32 into .env. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: The page at your address. - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server ## Quick start ```sh mkdir creator-page && cd creator-page && git init && npm init -y && npm pkg set type=module mkdir -p data public && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Sending email; export into a real sender. - The media kit, brand-deal tooling and the editor. - hosted checkout with digital delivery done for you - email sending to your list - the media kit and brand-deal tooling - templates and the editor If one of those is essential to you, that is the reason to keep paying for Beacons, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Beacons The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a creator link page with a small store and an email list like Beacons. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - One Node process with node:http and node:sqlite. Server-rendered HTML, inline CSS. No framework. - Stripe Payment Links for anything sold · you never handle card data. SQLite for clicks and signups. ### Data model (create this before Phase 1) - page.json: profile (name, bio, avatar, accent), links (slug, title, url, emoji, enabled), products (slug, title, price_text, payment_link_url, image) - clicks: id, slug, clicked_at, referer, user_agent_class, ip_hash - signups: id, email (unique), created_at, ip_hash ### Phase 1 · The page Build: render profile, links and products from page.json, mobile-first, with JavaScript disabled everything still works. Validate the JSON at startup and refuse to boot with the bad entry named. Done when: the page renders at 375px with JS off and a malformed page.json stops the server with a message naming the entry. Do not build yet: tracking, store, signup. ### Phase 2 · Design Build: a typographic hierarchy, 44px tap targets, the accent color as a CSS custom property, dark mode, focus-visible rings. No external requests. Done when: zero third-party requests, no contrast failures in either scheme, keyboard operable. ### Phase 3 · Click tracking Build: /go/:slug records a row and 302s; unknown slugs go home. Bot user agents bucketed separately. /stats behind basic auth from .env with clicks per link over 7 and 30 days as inline SVG. Done when: a click lands with one row, an unknown slug lands on the homepage, and totals reconcile with a GROUP BY. ### Phase 4 · The store Build: product cards that open the Stripe Payment Link. Digital delivery is Stripe's job: enable the "send a link" option on the Payment Link. No cart, no checkout of your own. Done when: a test-mode purchase completes and the buyer receives the file link from Stripe. ### Phase 5 · Email capture Build: a signup form storing to signups with a honeypot and a per-IP rate limit, and a CSV export behind basic auth. No sending in v1. Done when: a duplicate email is a no-op, the sixth signup from one IP in an hour is refused, and the export opens in a spreadsheet. ### Phase 6 · Cards and deploy Build: OG meta and a generated OG image, a /healthz endpoint, a systemd unit, a nightly backup and the README. Done when: the link previews correctly when shared and a reader goes from clone to live on their domain. ### Out of scope (and why) - Sending email. Export into a real sender; deliverability is a whole product. - The media kit, brand-deal tooling and the editor. ### README must contain - The page.json reference, and the note that slugs are permanent. - The Stripe Payment Link setup including digital delivery. ===== AGENTS.md ===== # Agent instructions · Beacons indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, node:http and node:sqlite, Stripe Payment Links, A VPS behind Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Beacons A creator link page with a till and a list: links and products from one JSON file, click tracking through your own redirect, Stripe Payment Links for anything sold (you never touch card data), and an email capture stored locally. Linktree with two honest bolt-ons. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The page Profile, links and products from page.json; JavaScript-free; refuses to boot on a broken file. ### Steps 1. Create the project and page.json profile (name, bio, avatar, accent), links (slug, title, url, emoji, enabled), products (slug, title, price_text, payment_link_url, image). ```sh mkdir creator-page && cd creator-page && git init && npm init -y && npm pkg set type=module mkdir -p data public && cp .env.example .env ``` 2. Validate at startup and render server-side, mobile-first ### Done when - [ ] Renders at 375px with JavaScript disabled - [ ] A malformed page.json stops the server naming the entry ## Phase 2 · Design Tap targets, accent, dark mode, focus rings, no external requests. ### Steps 1. Typographic hierarchy and 44px tap targets 2. Accent custom property, prefers-color-scheme, focus-visible ### Done when - [ ] Zero third-party requests - [ ] No contrast failures - [ ] Keyboard operable ## Phase 3 · Click tracking /go/:slug records after redirecting; bots bucketed; /stats private. ### Steps 1. Implement /go/:slug with the 302 sent before the insert; unknown slugs go home 2. Bucket bot user agents and build /stats behind basic auth with inline SVG ### Done when - [ ] A click lands with one row - [ ] Totals reconcile with GROUP BY - [ ] The redirect works with the database stopped ## Phase 4 · The store Product cards open Stripe Payment Links; delivery is Stripe's job. ### Steps 1. Render product cards linking to payment_link_url 2. Enable send-a-link delivery on each Payment Link and test a purchase in test mode ### Done when - [ ] A test-mode purchase completes and the buyer receives the file link from Stripe ## Phase 5 · Email capture Signups stored locally, no sending. ### Steps 1. Signup form with honeypot and per-IP rate limit 2. CSV export behind basic auth ### Done when - [ ] A duplicate email is a no-op - [ ] The sixth signup from one IP in an hour is refused - [ ] The export opens in a spreadsheet ## Phase 6 · Cards and deploy OG image, healthz, service, backup, README. ### Steps 1. OG meta and a generated OG image; /healthz; systemd; Caddy; nightly backup 2. README: page.json reference, slugs are permanent, Stripe delivery setup Files: `README.md` ### Done when - [ ] Previews correctly when shared - [ ] The README reaches a live page on your domain ## Not in this build - Sending email; export into a real sender. - The media kit, brand-deal tooling and the editor. ## After v1, if you want it - A tiny editor for page.json - Per-link QR codes ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite for clicks and signups. DATABASE_PATH=./data/page.db # Required. Public base URL. SITE_URL=https://you.yourdomain.com # Required · secret. openssl rand -hex 32. IP_SALT=hex # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
You are building a production product version of Beacons. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Beacons · product brief ## Problem The link page is the Linktree build. The extras (a small store, an email capture, a media kit page) are each a sitting on top. What stays theirs is payments and email delivery at scale, and the transaction fee is the price of not touching either. ## Product outcome A creator page with commerce and a list that you own end to end, with payments handled by Stripe and never by you. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a small host - a Stripe account for payment links ## Explicit non-goals for v1 - Sending email; export into a real sender. - The media kit, brand-deal tooling and the editor. - hosted checkout with digital delivery done for you - email sending to your list - the media kit and brand-deal tooling - templates and the editor ## Success criteria - Test purchase delivers the file - One restore drill performed - Lighthouse 100 ===== BRIEF.md ===== # Build brief · Beacons The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a creator link page with a small store and an email list like Beacons. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - One Node process with node:http and node:sqlite. Server-rendered HTML, inline CSS. No framework. - Stripe Payment Links for anything sold · you never handle card data. SQLite for clicks and signups. ### Data model (create this before Phase 1) - page.json: profile (name, bio, avatar, accent), links (slug, title, url, emoji, enabled), products (slug, title, price_text, payment_link_url, image) - clicks: id, slug, clicked_at, referer, user_agent_class, ip_hash - signups: id, email (unique), created_at, ip_hash ### Phase 1 · The page Build: render profile, links and products from page.json, mobile-first, with JavaScript disabled everything still works. Validate the JSON at startup and refuse to boot with the bad entry named. Done when: the page renders at 375px with JS off and a malformed page.json stops the server with a message naming the entry. Do not build yet: tracking, store, signup. ### Phase 2 · Design Build: a typographic hierarchy, 44px tap targets, the accent color as a CSS custom property, dark mode, focus-visible rings. No external requests. Done when: zero third-party requests, no contrast failures in either scheme, keyboard operable. ### Phase 3 · Click tracking Build: /go/:slug records a row and 302s; unknown slugs go home. Bot user agents bucketed separately. /stats behind basic auth from .env with clicks per link over 7 and 30 days as inline SVG. Done when: a click lands with one row, an unknown slug lands on the homepage, and totals reconcile with a GROUP BY. ### Phase 4 · The store Build: product cards that open the Stripe Payment Link. Digital delivery is Stripe's job: enable the "send a link" option on the Payment Link. No cart, no checkout of your own. Done when: a test-mode purchase completes and the buyer receives the file link from Stripe. ### Phase 5 · Email capture Build: a signup form storing to signups with a honeypot and a per-IP rate limit, and a CSV export behind basic auth. No sending in v1. Done when: a duplicate email is a no-op, the sixth signup from one IP in an hour is refused, and the export opens in a spreadsheet. ### Phase 6 · Cards and deploy Build: OG meta and a generated OG image, a /healthz endpoint, a systemd unit, a nightly backup and the README. Done when: the link previews correctly when shared and a reader goes from clone to live on their domain. ### Out of scope (and why) - Sending email. Export into a real sender; deliverability is a whole product. - The media kit, brand-deal tooling and the editor. ### README must contain - The page.json reference, and the note that slugs are permanent. - The Stripe Payment Link setup including digital delivery. ===== ARCHITECTURE.md ===== # Architecture · Beacons ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | server-rendered page, a redirect, a signup | | Payments | Stripe Payment Links | no cart, no checkout, no card data on your server | | Hosting | A VPS behind Caddy | the redirect and signup need a process | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Content | page.json | A small editor writing the file | | Redirector | /go and clicks | A serverless function | | Store | product cards and Payment Links | Lemon Squeezy links instead | | List | signups and export | A hosted sender later | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PORT` · required · Any free port. - `DATABASE_PATH` · required · SQLite for clicks and signups. - `SITE_URL` · required · Public base URL. - `IP_SALT` · required, secret · openssl rand -hex 32. - `ADMIN_USER` · required · Any username for the basic-auth admin pages. - `ADMIN_PASS` · required, secret · Generate one: openssl rand -base64 24. Never reuse a real password. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Beacons product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22, node:http and node:sqlite, Stripe Payment Links, A VPS behind Caddy. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ===== MILESTONES.md ===== # Delivery milestones · Beacons Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · The page Profile, links and products from page.json; JavaScript-free; refuses to boot on a broken file. ### Steps 1. Create the project and page.json profile (name, bio, avatar, accent), links (slug, title, url, emoji, enabled), products (slug, title, price_text, payment_link_url, image). ```sh mkdir creator-page && cd creator-page && git init && npm init -y && npm pkg set type=module mkdir -p data public && cp .env.example .env ``` 2. Validate at startup and render server-side, mobile-first ### Done when - [ ] Renders at 375px with JavaScript disabled - [ ] A malformed page.json stops the server naming the entry ## M2 · Design Tap targets, accent, dark mode, focus rings, no external requests. ### Steps 1. Typographic hierarchy and 44px tap targets 2. Accent custom property, prefers-color-scheme, focus-visible ### Done when - [ ] Zero third-party requests - [ ] No contrast failures - [ ] Keyboard operable ## M3 · Click tracking /go/:slug records after redirecting; bots bucketed; /stats private. ### Steps 1. Implement /go/:slug with the 302 sent before the insert; unknown slugs go home 2. Bucket bot user agents and build /stats behind basic auth with inline SVG ### Done when - [ ] A click lands with one row - [ ] Totals reconcile with GROUP BY - [ ] The redirect works with the database stopped ## M4 · The store Product cards open Stripe Payment Links; delivery is Stripe's job. ### Steps 1. Render product cards linking to payment_link_url 2. Enable send-a-link delivery on each Payment Link and test a purchase in test mode ### Done when - [ ] A test-mode purchase completes and the buyer receives the file link from Stripe ## M5 · Email capture Signups stored locally, no sending. ### Steps 1. Signup form with honeypot and per-IP rate limit 2. CSV export behind basic auth ### Done when - [ ] A duplicate email is a no-op - [ ] The sixth signup from one IP in an hour is refused - [ ] The export opens in a spreadsheet ## M6 · Cards and deploy OG image, healthz, service, backup, README. ### Steps 1. OG meta and a generated OG image; /healthz; systemd; Caddy; nightly backup 2. README: page.json reference, slugs are permanent, Stripe delivery setup Files: `README.md` ### Done when - [ ] Previews correctly when shared - [ ] The README reaches a live page on your domain ## M7 · Operate it like a product (production only) Only for the product-builder path: know when the page is down, never lose the database, and keep the server patched. ### Steps 1. Add a /healthz endpoint and an external uptime check against it Answer 200 with the build id and a quick database read. Point a free uptime monitor (or your own, from the Healthchecks entry on this site) at it so an outage is noticed before a user notices. 2. Write structured request logs and rotate them One JSON line per request: method, path, status, duration, no raw IPs. Rotate weekly with logrotate, keep eight. 3. Back the SQLite file up off the machine nightly and test a restore SQLite's .backup command makes a consistent copy while the app runs. Copy it to object storage or a second machine; then, once, restore it into a fresh checkout and confirm the app reads it. ```sh sqlite3 data/app.db ".backup '/tmp/app-$(date +%F).db'" rclone copy /tmp/app-$(date +%F).db remote:backups/ ``` 4. Lock the box down Firewall allowing only 22, 80 and 443; unattended security updates on; the app running as an unprivileged user under systemd with Restart=on-failure. ### Done when - [ ] Stopping the service triggers an uptime alert within a few minutes - [ ] A restore from last night's backup contains yesterday's data - [ ] A port scan from another machine shows only 22, 80 and 443 ===== OPERATIONS.md ===== # Operations · Beacons ## Backup page.json in git; SQLite .backup nightly. ## Restore Deploy from git; copy the database. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on /healthz. ## Incident checklist A wrong Payment Link is fixed in Stripe and page.json. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Test purchase delivers the file - [ ] One restore drill performed - [ ] Lighthouse 100 ## Launch constraint Do not market omitted Beacons capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite for clicks and signups. DATABASE_PATH=./data/page.db # Required. Public base URL. SITE_URL=https://you.yourdomain.com # Required · secret. openssl rand -hex 32. IP_SALT=hex # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
# Beacons · indie build A creator link page with a till and a list: links and products from one JSON file, click tracking through your own redirect, Stripe Payment Links for anything sold (you never touch card data), and an email capture stored locally. Linktree with two honest bolt-ons. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | server-rendered page, a redirect, a signup | | Payments | Stripe Payment Links | no cart, no checkout, no card data on your server | | Hosting | A VPS behind Caddy | the redirect and signup need a process | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **Your links, bio, avatar and the products you sell** · free - Why: page.json renders exactly this. - Get it: Titles, URLs, a one-line bio, a square avatar, and each product's name, price and file. - [ ] **A Stripe account with Payment Links** · free; fees per sale - Why: Every product card opens a Payment Link. Enable the send-a-link option so Stripe delivers the file. - Get it: dashboard.stripe.com > Payment Links > New. Test mode first. Copy each link URL into page.json. - [ ] **A random salt for hashing IPs** · free - Why: Click and signup rows store hashes, not addresses. - Get it: openssl rand -hex 32 into .env. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: The page at your address. - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server ## Quick start ```sh mkdir creator-page && cd creator-page && git init && npm init -y && npm pkg set type=module mkdir -p data public && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Sending email; export into a real sender. - The media kit, brand-deal tooling and the editor. - hosted checkout with digital delivery done for you - email sending to your list - the media kit and brand-deal tooling - templates and the editor If one of those is essential to you, that is the reason to keep paying for Beacons, and the README should say so rather than pretend.
# Build brief · Beacons The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a creator link page with a small store and an email list like Beacons. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - One Node process with node:http and node:sqlite. Server-rendered HTML, inline CSS. No framework. - Stripe Payment Links for anything sold · you never handle card data. SQLite for clicks and signups. ### Data model (create this before Phase 1) - page.json: profile (name, bio, avatar, accent), links (slug, title, url, emoji, enabled), products (slug, title, price_text, payment_link_url, image) - clicks: id, slug, clicked_at, referer, user_agent_class, ip_hash - signups: id, email (unique), created_at, ip_hash ### Phase 1 · The page Build: render profile, links and products from page.json, mobile-first, with JavaScript disabled everything still works. Validate the JSON at startup and refuse to boot with the bad entry named. Done when: the page renders at 375px with JS off and a malformed page.json stops the server with a message naming the entry. Do not build yet: tracking, store, signup. ### Phase 2 · Design Build: a typographic hierarchy, 44px tap targets, the accent color as a CSS custom property, dark mode, focus-visible rings. No external requests. Done when: zero third-party requests, no contrast failures in either scheme, keyboard operable. ### Phase 3 · Click tracking Build: /go/:slug records a row and 302s; unknown slugs go home. Bot user agents bucketed separately. /stats behind basic auth from .env with clicks per link over 7 and 30 days as inline SVG. Done when: a click lands with one row, an unknown slug lands on the homepage, and totals reconcile with a GROUP BY. ### Phase 4 · The store Build: product cards that open the Stripe Payment Link. Digital delivery is Stripe's job: enable the "send a link" option on the Payment Link. No cart, no checkout of your own. Done when: a test-mode purchase completes and the buyer receives the file link from Stripe. ### Phase 5 · Email capture Build: a signup form storing to signups with a honeypot and a per-IP rate limit, and a CSV export behind basic auth. No sending in v1. Done when: a duplicate email is a no-op, the sixth signup from one IP in an hour is refused, and the export opens in a spreadsheet. ### Phase 6 · Cards and deploy Build: OG meta and a generated OG image, a /healthz endpoint, a systemd unit, a nightly backup and the README. Done when: the link previews correctly when shared and a reader goes from clone to live on their domain. ### Out of scope (and why) - Sending email. Export into a real sender; deliverability is a whole product. - The media kit, brand-deal tooling and the editor. ### README must contain - The page.json reference, and the note that slugs are permanent. - The Stripe Payment Link setup including digital delivery.
# Agent instructions · Beacons indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, node:http and node:sqlite, Stripe Payment Links, A VPS behind Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs".
# Build plan · Beacons A creator link page with a till and a list: links and products from one JSON file, click tracking through your own redirect, Stripe Payment Links for anything sold (you never touch card data), and an email capture stored locally. Linktree with two honest bolt-ons. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The page Profile, links and products from page.json; JavaScript-free; refuses to boot on a broken file. ### Steps 1. Create the project and page.json profile (name, bio, avatar, accent), links (slug, title, url, emoji, enabled), products (slug, title, price_text, payment_link_url, image). ```sh mkdir creator-page && cd creator-page && git init && npm init -y && npm pkg set type=module mkdir -p data public && cp .env.example .env ``` 2. Validate at startup and render server-side, mobile-first ### Done when - [ ] Renders at 375px with JavaScript disabled - [ ] A malformed page.json stops the server naming the entry ## Phase 2 · Design Tap targets, accent, dark mode, focus rings, no external requests. ### Steps 1. Typographic hierarchy and 44px tap targets 2. Accent custom property, prefers-color-scheme, focus-visible ### Done when - [ ] Zero third-party requests - [ ] No contrast failures - [ ] Keyboard operable ## Phase 3 · Click tracking /go/:slug records after redirecting; bots bucketed; /stats private. ### Steps 1. Implement /go/:slug with the 302 sent before the insert; unknown slugs go home 2. Bucket bot user agents and build /stats behind basic auth with inline SVG ### Done when - [ ] A click lands with one row - [ ] Totals reconcile with GROUP BY - [ ] The redirect works with the database stopped ## Phase 4 · The store Product cards open Stripe Payment Links; delivery is Stripe's job. ### Steps 1. Render product cards linking to payment_link_url 2. Enable send-a-link delivery on each Payment Link and test a purchase in test mode ### Done when - [ ] A test-mode purchase completes and the buyer receives the file link from Stripe ## Phase 5 · Email capture Signups stored locally, no sending. ### Steps 1. Signup form with honeypot and per-IP rate limit 2. CSV export behind basic auth ### Done when - [ ] A duplicate email is a no-op - [ ] The sixth signup from one IP in an hour is refused - [ ] The export opens in a spreadsheet ## Phase 6 · Cards and deploy OG image, healthz, service, backup, README. ### Steps 1. OG meta and a generated OG image; /healthz; systemd; Caddy; nightly backup 2. README: page.json reference, slugs are permanent, Stripe delivery setup Files: `README.md` ### Done when - [ ] Previews correctly when shared - [ ] The README reaches a live page on your domain ## Not in this build - Sending email; export into a real sender. - The media kit, brand-deal tooling and the editor. ## After v1, if you want it - A tiny editor for page.json - Per-link QR codes
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite for clicks and signups. DATABASE_PATH=./data/page.db # Required. Public base URL. SITE_URL=https://you.yourdomain.com # Required · secret. openssl rand -hex 32. IP_SALT=hex # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
# Beacons · product brief ## Problem The link page is the Linktree build. The extras (a small store, an email capture, a media kit page) are each a sitting on top. What stays theirs is payments and email delivery at scale, and the transaction fee is the price of not touching either. ## Product outcome A creator page with commerce and a list that you own end to end, with payments handled by Stripe and never by you. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a small host - a Stripe account for payment links ## Explicit non-goals for v1 - Sending email; export into a real sender. - The media kit, brand-deal tooling and the editor. - hosted checkout with digital delivery done for you - email sending to your list - the media kit and brand-deal tooling - templates and the editor ## Success criteria - Test purchase delivers the file - One restore drill performed - Lighthouse 100
# Build brief · Beacons The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a creator link page with a small store and an email list like Beacons. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - One Node process with node:http and node:sqlite. Server-rendered HTML, inline CSS. No framework. - Stripe Payment Links for anything sold · you never handle card data. SQLite for clicks and signups. ### Data model (create this before Phase 1) - page.json: profile (name, bio, avatar, accent), links (slug, title, url, emoji, enabled), products (slug, title, price_text, payment_link_url, image) - clicks: id, slug, clicked_at, referer, user_agent_class, ip_hash - signups: id, email (unique), created_at, ip_hash ### Phase 1 · The page Build: render profile, links and products from page.json, mobile-first, with JavaScript disabled everything still works. Validate the JSON at startup and refuse to boot with the bad entry named. Done when: the page renders at 375px with JS off and a malformed page.json stops the server with a message naming the entry. Do not build yet: tracking, store, signup. ### Phase 2 · Design Build: a typographic hierarchy, 44px tap targets, the accent color as a CSS custom property, dark mode, focus-visible rings. No external requests. Done when: zero third-party requests, no contrast failures in either scheme, keyboard operable. ### Phase 3 · Click tracking Build: /go/:slug records a row and 302s; unknown slugs go home. Bot user agents bucketed separately. /stats behind basic auth from .env with clicks per link over 7 and 30 days as inline SVG. Done when: a click lands with one row, an unknown slug lands on the homepage, and totals reconcile with a GROUP BY. ### Phase 4 · The store Build: product cards that open the Stripe Payment Link. Digital delivery is Stripe's job: enable the "send a link" option on the Payment Link. No cart, no checkout of your own. Done when: a test-mode purchase completes and the buyer receives the file link from Stripe. ### Phase 5 · Email capture Build: a signup form storing to signups with a honeypot and a per-IP rate limit, and a CSV export behind basic auth. No sending in v1. Done when: a duplicate email is a no-op, the sixth signup from one IP in an hour is refused, and the export opens in a spreadsheet. ### Phase 6 · Cards and deploy Build: OG meta and a generated OG image, a /healthz endpoint, a systemd unit, a nightly backup and the README. Done when: the link previews correctly when shared and a reader goes from clone to live on their domain. ### Out of scope (and why) - Sending email. Export into a real sender; deliverability is a whole product. - The media kit, brand-deal tooling and the editor. ### README must contain - The page.json reference, and the note that slugs are permanent. - The Stripe Payment Link setup including digital delivery.
# Architecture · Beacons ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | server-rendered page, a redirect, a signup | | Payments | Stripe Payment Links | no cart, no checkout, no card data on your server | | Hosting | A VPS behind Caddy | the redirect and signup need a process | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Content | page.json | A small editor writing the file | | Redirector | /go and clicks | A serverless function | | Store | product cards and Payment Links | Lemon Squeezy links instead | | List | signups and export | A hosted sender later | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PORT` · required · Any free port. - `DATABASE_PATH` · required · SQLite for clicks and signups. - `SITE_URL` · required · Public base URL. - `IP_SALT` · required, secret · openssl rand -hex 32. - `ADMIN_USER` · required · Any username for the basic-auth admin pages. - `ADMIN_PASS` · required, secret · Generate one: openssl rand -base64 24. Never reuse a real password. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Beacons product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22, node:http and node:sqlite, Stripe Payment Links, A VPS behind Caddy. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone.
# Delivery milestones · Beacons Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · The page Profile, links and products from page.json; JavaScript-free; refuses to boot on a broken file. ### Steps 1. Create the project and page.json profile (name, bio, avatar, accent), links (slug, title, url, emoji, enabled), products (slug, title, price_text, payment_link_url, image). ```sh mkdir creator-page && cd creator-page && git init && npm init -y && npm pkg set type=module mkdir -p data public && cp .env.example .env ``` 2. Validate at startup and render server-side, mobile-first ### Done when - [ ] Renders at 375px with JavaScript disabled - [ ] A malformed page.json stops the server naming the entry ## M2 · Design Tap targets, accent, dark mode, focus rings, no external requests. ### Steps 1. Typographic hierarchy and 44px tap targets 2. Accent custom property, prefers-color-scheme, focus-visible ### Done when - [ ] Zero third-party requests - [ ] No contrast failures - [ ] Keyboard operable ## M3 · Click tracking /go/:slug records after redirecting; bots bucketed; /stats private. ### Steps 1. Implement /go/:slug with the 302 sent before the insert; unknown slugs go home 2. Bucket bot user agents and build /stats behind basic auth with inline SVG ### Done when - [ ] A click lands with one row - [ ] Totals reconcile with GROUP BY - [ ] The redirect works with the database stopped ## M4 · The store Product cards open Stripe Payment Links; delivery is Stripe's job. ### Steps 1. Render product cards linking to payment_link_url 2. Enable send-a-link delivery on each Payment Link and test a purchase in test mode ### Done when - [ ] A test-mode purchase completes and the buyer receives the file link from Stripe ## M5 · Email capture Signups stored locally, no sending. ### Steps 1. Signup form with honeypot and per-IP rate limit 2. CSV export behind basic auth ### Done when - [ ] A duplicate email is a no-op - [ ] The sixth signup from one IP in an hour is refused - [ ] The export opens in a spreadsheet ## M6 · Cards and deploy OG image, healthz, service, backup, README. ### Steps 1. OG meta and a generated OG image; /healthz; systemd; Caddy; nightly backup 2. README: page.json reference, slugs are permanent, Stripe delivery setup Files: `README.md` ### Done when - [ ] Previews correctly when shared - [ ] The README reaches a live page on your domain ## M7 · Operate it like a product (production only) Only for the product-builder path: know when the page is down, never lose the database, and keep the server patched. ### Steps 1. Add a /healthz endpoint and an external uptime check against it Answer 200 with the build id and a quick database read. Point a free uptime monitor (or your own, from the Healthchecks entry on this site) at it so an outage is noticed before a user notices. 2. Write structured request logs and rotate them One JSON line per request: method, path, status, duration, no raw IPs. Rotate weekly with logrotate, keep eight. 3. Back the SQLite file up off the machine nightly and test a restore SQLite's .backup command makes a consistent copy while the app runs. Copy it to object storage or a second machine; then, once, restore it into a fresh checkout and confirm the app reads it. ```sh sqlite3 data/app.db ".backup '/tmp/app-$(date +%F).db'" rclone copy /tmp/app-$(date +%F).db remote:backups/ ``` 4. Lock the box down Firewall allowing only 22, 80 and 443; unattended security updates on; the app running as an unprivileged user under systemd with Restart=on-failure. ### Done when - [ ] Stopping the service triggers an uptime alert within a few minutes - [ ] A restore from last night's backup contains yesterday's data - [ ] A port scan from another machine shows only 22, 80 and 443
# Operations · Beacons ## Backup page.json in git; SQLite .backup nightly. ## Restore Deploy from git; copy the database. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on /healthz. ## Incident checklist A wrong Payment Link is fixed in Stripe and page.json. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Test purchase delivers the file - [ ] One restore drill performed - [ ] Lighthouse 100 ## Launch constraint Do not market omitted Beacons capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite for clicks and signups. DATABASE_PATH=./data/page.db # Required. Public base URL. SITE_URL=https://you.yourdomain.com # Required · secret. openssl rand -hex 32. IP_SALT=hex # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
$ choose a build depth, inspect the files, then open the complete pack in your agent
Creators pay to not assemble Stripe, an email tool and a page themselves, and the free tier's fee model means most never see a bill.
xhosted checkout with digital delivery done for you
xemail sending to your list
xthe media kit and brand-deal tooling
xtemplates and the editor
Beacons pricing
creator pro$10/mo · monthly flat · $120/yr
free tierThe free plan includes the link page and a store with a 9% transaction fee.
verified 2026-09-04 · source ↗
Is Beacons free?
The free plan includes the link page and a store with a 9% transaction fee. Paid is Creator Pro at $10/mo (checked 2026-09-04).
Vibecode Beacons
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Beacons replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Beacons cost?
Beacons costs about $10/month (Creator Pro, checked 2026-09-04), which is $120 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Beacons?
Honestly: hosted checkout with digital delivery done for you; email sending to your list; the media kit and brand-deal tooling; templates and the editor. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Beacons?
Yes: LittleLink (open-source link-in-bio page). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.