vibe-survival
Live survival-rate tracker for vibe-coded apps shipped on Lovable, v0, Bolt.new, Replit Agent, a0.dev, Databutton, and Create.xyz. Discovers public apps from Hacker News, HEAD-checks each every 12 hours, exposes a dashboard.
What it does
- Every 30 minutes, queries the public Hacker News Algolia search API for each tracked platform's primary domain, paginates through results, normalizes each URL, and inserts any new app.
- Every 12 hours, sends a
HEADrequest (with aGET Range: bytes=0-0fallback) to every tracked app. Logs the response code and elapsed time. Updatesapps.current_aliveand, on the first transition from alive → dead, setsapps.funeral_at. - Exposes a vanilla-JS dashboard with: top-line counters, an interactive survival curve (Chart.js), a per-platform leaderboard with T+7 / T+30 / T+90 survival rates, a "recent funerals" feed, and a "long-livers" leaderboard.
Stack
- Node.js 18+ · Express · better-sqlite3 (WAL) · node-cron · helmet · compression
- Vanilla JS SPA, Chart.js via CDN, dark theme, English UI
Run locally
git clone <repo>
cd vibe-survival
cp .env.example .env
npm install
npm start
# open http://localhost:4801/vibe-survival/
Data sources (real, public, no auth)
| Name | URL | Fetch frequency | On failure |
|------------------|------------------------------------------------------------------------------------------|-----------------|---------------------------------------------|
| HN Algolia search | https://hn.algolia.com/api/v1/search?query=<domain>&restrictSearchableAttributes=url | every 30 min | per-platform error stored in run_log |
| App HEAD probe | direct HTTPS HEAD on each tracked app URL | every 12 h | row inserted in checks with error text |
Discovery domains used as HN search queries: lovable.app, v0.app, v0.dev, bolt.host, replit.app, replit.dev, a0.dev, databutton.com, create.xyz.
No mocks. No hardcoded survival numbers. No Math.random(). If discovery returns zero, the dashboard shows zero.
HTTP API (all public — no auth)
| Method | Path | Description |
|--------|-------------------------------------------------------------------|----------------------------------------------------------|
| GET | /vibe-survival/health | smoke check, always 200 |
| GET | /vibe-survival/api/stats | total / alive / dead counters + per-platform breakdown |
| GET | /vibe-survival/api/platforms | per-platform T+7 / T+30 / T+90 survival |
| GET | /vibe-survival/api/apps?platform=&status=alive\|dead&limit=&offset= | paginated app list |
| GET | /vibe-survival/api/app/:id | app detail + last 30 health-check rows |
| GET | /vibe-survival/api/funerals?limit=20 | most recent flips from alive → dead |
| GET | /vibe-survival/api/long-livers?limit=20 | oldest still-alive apps |
| GET | /vibe-survival/api/survival-curve?platform=&max_age=90 | survival-curve points for charts |
| GET | /vibe-survival/api/refresh-status | cron job state |
| POST | /vibe-survival/api/refresh?what=discover\|healthcheck\|both | kick a job async (returns 202) |
Survival math
For each age cut-off T (days), we compute alive_now / discovered_at_least_T_days_ago. This is a current-snapshot approximation — not a Kaplan–Meier estimator — but it's an honest summary of "of all apps we saw at least T days ago, what fraction is alive right now?" The methodology section in the UI explains this in plain language.
Architecture notes
db.jsopens SQLite with WAL mode and seeds the seven default platforms at boot. Extra platforms can be supplied via theEXTRA_PLATFORMSenv var (format:slug:Name:.suffix1|.suffix2,...).fetchers/hn.jsruns up to 4 platform queries concurrently, with a 120ms politeness delay between pages of each query, and a 12 s per-request timeout.fetchers/healthcheck.jsruns HEAD probes with a concurrency pool of 16 and a 10-second per-request timeout. Failures (timeout, DNS, connection refused) count as "dead" and the error is preserved inchecks.error.lib/survival.jsis the only piece of math: it walks every app, buckets by age in days, and computes the cumulative cohort percentages.
Deploy
DEPLOY_MANIFEST.json is the RNDLAB orchestrator manifest. Drop the project into the RNDLAB watcher's source tree and the orchestrator will rsync → systemd → nginx → showcase → thumbnail it.
License
MIT