explainroo
explainroo is a free, open source tool that lets your AI agent make explainer videos with a voice. It runs on your own computer and needs no API key.
AI-curated tech news monitor: Playwright scraping -> Claude enrichment -> PostgreSQL -> Slack alerts + FastAPI dashboard
Translation pending
MediaPython · CSSGitHub @StorkBisonOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗An AI-curated tech news monitor. TechPulse scrapes Hacker News on a schedule with a headless browser, uses Claude to classify, score and summarise every story against your interests, stores everything in PostgreSQL, and sends a Slack alert only when something matters. A FastAPI back end serves a REST API and a live dashboard.
Built as a compact portfolio project covering browser automation, LLM integration, API design, data modelling, testing and containerised delivery.
🎬 Demo video (4:33): docs/techpulse-demo.mp4 (regenerate with python demo/record_demo.py)
flowchart LR
S[APScheduler<br/>every 30 min] --> P
A[POST /api/runs] --> P
subgraph P[Pipeline · one run at a time]
direction LR
SC[Scrape<br/>Playwright or httpx + retries] --> ST[Upsert<br/>SQLAlchemy]
ST --> EN[Enrich<br/>Claude structured outputs]
EN --> AL[Alert<br/>Slack webhook]
end
ST --- DB[(PostgreSQL)]
DB --- API[FastAPI REST API]
API --- UI[Dashboard]
| Stage | What happens | Tech |
|---|---|---|
| Schedule | Interval trigger, or on demand via API. An asyncio.Lock prevents overlapping runs. | APScheduler |
| Scrape | Headless browser loads N pages and extracts rank, title, URL, points, author and comments in one in-page JS pass. Images, fonts and CSS are blocked. Retries use exponential backoff. | Playwright, tenacity |
| Store | Idempotent upsert: new stories are inserted, and existing ones get fresh points and comments. | SQLAlchemy 2, PostgreSQL / SQLite |
| Enrich | Batches of 30 un-enriched stories are sent to Claude. The response is parsed directly into Pydantic models (category enum, relevance 1–10, summary, tags). | Anthropic SDK, structured outputs |
| Alert | Stories at or above ALERT_THRESHOLD that haven't been alerted are posted to Slack, exactly once. | httpx |
| Audit | Every run is recorded with its trigger, status, counts, enricher, duration and error. | runs table |
relevance IS NULL and are picked up on the next run.alerted flag is set when an alert is sent.ANTHROPIC_API_KEY a deterministic keyword enricher takes over. With SCRAPER_SOURCE=fixture, the real Playwright extraction runs against a saved page, which CI and demos use.cp .env.example .env # optional: add ANTHROPIC_API_KEY, SLACK_WEBHOOK_URL
docker compose up --build
Open http://localhost:8000 for the dashboard, or http://localhost:8000/docs for the API docs.
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev,browser]"
python -m playwright install chromium # or set BROWSER_CHANNEL=chrome
cp .env.example .env
techpulse run-once # one pipeline pass, prints a summary
techpulse serve # API + dashboard + scheduler on :8000
The same code runs as a Vercel Function. When VERCEL=1 is set, the app adapts automatically:
| Concern | Docker / local | Vercel |
|---|---|---|
| Scraper | Playwright (headless Chromium) | httpx + HTML parser. Same output; both are tested against the same fixture. |
| Scheduling | In-process APScheduler | Vercel Cron (daily on Hobby), plus an optional GitHub Actions workflow every 2 h, both calling GET /api/cron with Authorization: Bearer $CRON_SECRET |
| "Run now" | Background task (202) | Runs inline, with MANUAL_RUN_COOLDOWN_SECONDS to prevent abuse |
| Database | SQLite / Compose Postgres | Neon Postgres via DATABASE_URL (falls back to per-instance /tmp SQLite) |
pyproject.toml sets [tool.vercel] entrypoint).DATABASE_URL; postgres:// URLs are normalised automatically.CRON_SECRET (random, ≥16 chars), MANUAL_RUN_COOLDOWN_SECONDS=300, and optionally ANTHROPIC_API_KEY, SLACK_WEBHOOK_URL, SCRAPER_PAGES=2, SCHEDULE_LABEL="Vercel Cron · daily".TECHPULSE_URL and CRON_SECRET to enable .github/workflows/scheduled-run.yml.All settings come from environment variables or .env, and Pydantic validates them.
| Variable | Default | Purpose |
|---|---|---|
ANTHROPIC_API_KEY | – | Enables Claude enrichment. Without it, the keyword fallback is used. |
CLAUDE_MODEL | claude-opus-5-5 | Model used for enrichment |
INTERESTS | AI/LLMs, automation, Python, … | What "relevant" means to you |
ALERT_THRESHOLD | 8 | Minimum relevance (1–10) that triggers an alert |
SLACK_WEBHOOK_URL | – | Slack incoming webhook. If unset, alerts go to the log. |
SCHEDULE_ENABLED / SCHEDULE_INTERVAL_MINUTES | true / 30 | Scheduler |
SCRAPER_ENGINE | auto | browser (Playwright), http (httpx), or auto (browser if installed and not serverless) |
SCRAPER_SOURCE | live | live or fixture (offline) |
CRON_SECRET | – | Required for GET /api/cron (sent as Authorization: Bearer …) |
MANUAL_RUN_COOLDOWN_SECONDS | 0 | Minimum gap between runs triggered with the Run button or API |
SCRAPER_PAGES | 1 | HN pages per run (1–5) |
BROWSER_CHANNEL | – | Use an installed chrome/msedge instead of the bundled Chromium |
DATABASE_URL | sqlite:///./data/techpulse.db | Any SQLAlchemy URL. Compose sets PostgreSQL. |
| Method | Path | Description |
|---|---|---|
GET | /api/stories | Filter with category, min_relevance, q, sort=relevance|points|recent, limit |
GET | /api/stories/{hn_id} | A single story |
GET | /api/runs | Run history |
POST | /api/runs | Trigger a run (202; 409 if one is already running; 429 during cooldown) |
GET | /api/cron | Scheduled run for cron callers. Requires Bearer CRON_SECRET. Returns the finished run. |
GET | /api/stats | KPIs, category breakdown, last and next run, active configuration |
GET | /health | Liveness and DB check |
pytest -v # 29 tests
ruff check . && ruff format --check .
TestClient, including validation errors.tests/fixtures/hn_front_page.html, plus a check that they produce identical results./api/cron auth, inline runs, cooldown, Postgres URL normalisation, Vercel defaults.GitHub Actions (.github/workflows/ci.yml) runs lint, then tests, then a Docker build, then a smoke test against the running container in fixture mode.
techpulse/
config.py settings (pydantic-settings)
db.py engine/session wrapper
models.py Story, Run (SQLAlchemy 2 typed ORM)
scraper.py Playwright scraper + retry
enricher.py ClaudeEnricher (structured outputs) + KeywordEnricher fallback
notifier.py Slack / log alerts
pipeline.py scrape → upsert → enrich → alert, audited per run
api.py FastAPI app factory, scheduler lifespan, routes
cli.py `techpulse serve | run-once`
templates/ static/ dashboard (vanilla JS on the public API)
tests/ pytest suite + HN HTML fixture
demo/ slides + scripted, narrated demo-video recorder
demo/record_demo.py produces the walkthrough video automatically:
edge-tts)imageio-ffmpeg)pip install -e ".[video]"
python demo/record_demo.py # -> demo/out/techpulse-demo.mp4
Set ANTHROPIC_API_KEY before recording to show Claude enrichment instead of the offline fallback.