A self-hosted bot that builds you a personalized morning tech digest. It continuously ingests primary sources (Hacker News, RSS, Reddit, GitHub releases, SEC EDGAR, Product Hunt, arXiv), clusters related items into stories, scores them against your interest profile, and every morning sends a single composed brief to your Telegram. Design principle: fast primary sources detect, slower press confirms β the bot fuses both.
- Personalized β you set the topics and weights; the digest reflects them.
- It learns β π/π buttons under each story tune what you see next.
- No repeats β a story shown once never appears again.
- Cheap & light β one Claude API call/day (~$0.05β0.10); clustering runs locally with a lightweight embedding model (no PyTorch), so it fits free hosts.
- Yours alone β everyone runs their own copy with their own keys. No shared server, nothing phones home. Apache-2.0 licensed.
Each morning: a short intro (the three things worth knowing if you read nothing else), then one message per story with π/π buttons right underneath. Tapping tunes tomorrow's ranking.
A real morning's digest. Each π΄ / π / π’ / βͺ tag is the story's "breaking level"; the italic "Why it matters to you" line ties each story back to your profile.
Want it delivered every morning without keeping a computer on, for free? Fork this repo, then follow HOSTING.md β GitHub Actions. You paste four keys into your repo's Actions Secrets and it runs itself daily in GitHub's cloud. No laptop, no server, ~15 minutes. (HOSTING.md also covers Replit, PythonAnywhere, and an always-on free Oracle VM for instant feedback.)
To try it locally first, or to develop it, follow the first-time setup below.
-
Clone the repo and set up the virtualenv (needs Python 3.11+):
git clone <repo-url> tech-digest && cd tech-digest python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt
The first run downloads a ~90 MB embedding model (fastembed / ONNX) used for clustering β once, then cached.
-
Get an Anthropic API key at console.anthropic.com. Fund it with a small prepaid credit and turn auto-reload OFF β the worst case is then "runs out of credit and stops," never a surprise bill.
-
Create a Telegram bot via @BotFather and note the bot token. Send your new bot any message first (Telegram only returns a chat's updates after you've messaged it), then run
make chat-idto fetch yourTELEGRAM_CHAT_ID. -
Configure:
cp .env.example .env
Fill in
ANTHROPIC_API_KEY,TELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_ID, andEDGAR_CONTACT_EMAIL. SetTIMEZONEto yours. Leave the rest at defaults. -
Initialize and smoke-test:
make init-db make run-once # ingest β cluster β score β compose β deliver, onceExpect a digest in Telegram within a few minutes. The first day is thin by design β scoring needs a couple days of history to warm up.
-
Run it for real β pick a host from HOSTING.md. For the full-featured always-on path (instant π/π, live
/pause/status), run the persistent scheduler on a VPS / Raspberry Pi / free Oracle VM:make install-service && sudo systemctl enable --now tech-digest
(Or
make print-crontabfor a cron-only schedule with no live commands.)
The digest is only as good as the profile behind it. Three files control it:
config/profile.yamlβ keyword weights for scoring (positive boosts a topic, negative suppresses it). The shipped list is an example β retune it.src/compose.py(SYSTEM_PROMPT) β the composer's tone and focus areas.config/sources.yamlβ which feeds, subreddits, repos, and EDGAR companies get polled.
Your π/π taps refine the weights over time on top of whatever you set here,
stored in a separate learned layer (keyword_adjustments) so your hand-set
profile.yaml is never overwritten.
- A few seed RSS URLs in
config/sources.yamlmay be stale (feeds move) β a dead feed is logged and skipped, never breaking a cycle. Prune/update to taste. - Reddit blocks unauthenticated JSON from many hosts (HTTP 403), so the Reddit source is often skipped. Reddit OAuth is a possible future add.
- Intraday breaking-news alerts are scaffolded but disabled
(
ALERTS_ENABLED=false); the breaking-level buckets are untuned defaults until there's a week of real data to calibrate against. - On GitHub Actions (batch) hosting, π/π taps apply to the next morning's digest, not instantly β instant feedback needs an always-on host (see HOSTING.md).
Softest to nuclear. A stopped or paused bot costs exactly $0 in API spend.
| Action | Effect | Cost |
|---|---|---|
/pause from Telegram |
Every stage no-ops before any network/API call; /resume restores |
$0 |
make pause |
Same pause-file, from the host | $0 |
systemctl stop tech-digest |
Scheduler stops entirely | $0 |
make uninstall-service |
Removes the systemd unit | $0 |
| Revoke the API key | Even a misbehaving process can't spend | $0 |
Restarting after any pause is safe β every stage is idempotent, so it just catches up on the next cycle. (On GitHub Actions, "pause" is simply disabling the workflow in the Actions tab.)
Prepaid, auto-reload off, so a paused/stopped bot spends $0. Running
normally: ~$0.05β0.10/day on claude-sonnet-5 (one composition call, plus
an occasional retry). MAX_DAILY_SPEND_USD (default 0.50) is a bug tripwire,
not a budget β crossing it skips further API calls and sends a Telegram
warning. It should never trigger in normal use.
| Target | What it does |
|---|---|
make init-db |
Create/upgrade the SQLite schema |
make run-once |
Run the full pipeline once (ingest β cluster β score β compose β deliver) |
make pause / make resume |
Write / remove data/PAUSED |
make chat-id |
Look up your Telegram chat ID from recent bot messages |
make print-crontab |
Print a cron schedule (no live Telegram commands) |
make install-service / make uninstall-service |
Install / remove the systemd unit |
make test |
Run the test suite |
Also runnable standalone for batch hosts: python -m src.run_once (one full
pipeline pass) and python -m src.scheduler (the persistent always-on process).

