delirehberi/news
- title
- delirehberi/news
- type
- toolbox
- summary
- Cloudflare-hosted news aggregator ranking HN, Lobsters and Reddit by similarity to your upvotes
- tags
- typescript, cloudflare-workers, rss, embeddings, self-hosted, nostr, watchlist
- language
- TypeScript
- license
- MIT
- created
- 2026-07-23
- updated
- 2026-07-23
A personal news aggregator that pulls from Hacker News, Lobsters and Reddit on a cron schedule, embeds every article title, and ranks the feed by cosine similarity against the embeddings of things you have upvoted before. The whole thing runs inside one Cloudflare account β Workers for the API and the cron, D1 for the relational data, Vectorize for the index, Workers AI for the embedding model β which is what makes "self-hosted personalized feed" cheap enough to be worth doing for one person.
How it works
The backend is Hono on Cloudflare Workers, the frontend is Vite plus Tailwind deployed to Pages, and the two deploy separately through a Makefile. Ingestion is a scheduled Worker that hits each source's API; individual sources can be turned off with vars entries in wrangler.jsonc ("ENABLE_REDDIT": "false").
Ranking is deliberately simple. Each article gets a 768-dimension embedding from @cf/baai/bge-base-en-v1.5, stored in Vectorize alongside metadata, and scored by cosine similarity against a profile built from your upvote history. There is no keyword channel and no reranker β it is embedding-only, not hybrid-search β which means it is good at "more things like this" and blind to exact-term queries. The profile has to be seeded before any of it works: the repo exposes endpoints for importing existing upvotes from Lobsters, Reddit, or a raw JSON export from Hacker News, and the daily cron takes over after that.
The Nostr integration is the unusual part. NIP-07 browser extensions (Alby, nos2x) are the auth mechanism β you sign your likes with your own key rather than creating an account β and the same identity lets followers re-publish your curated feed to public Nostr relays. So the personalization signal and the publishing surface are both keyed to a Nostr pubkey rather than to the app.
Setup
Deployment is a sequence of make targets against your own Cloudflare account:
make init # deps, .env and wrangler.jsonc templates, checklist
make db-init # create the D1 database
make vectorize-init # create the Vectorize index
# paste database_id and index_name into worker/wrangler.jsonc
make push-secrets # pipe local env vars into Cloudflare's secret store
make deploy # deploy-worker + deploy-frontend
Secrets come from the environment rather than interactive prompts:
export REDDIT_CLIENT_ID="..."
export REDDIT_CLIENT_SECRET="..."
export REDDIT_REFRESH_TOKEN="..."
export AUTHORIZED_NOSTR_PUBKEY="...hex..."
make push-secrets
Node 22+ and a Cloudflare account are the prerequisites; the Nostr extension is only needed for the NIP-07 features.
Limitations
"Self-hosted" here means self-owned data on somebody else's platform. Every component is a Cloudflare product, and porting off would mean replacing the vector store, the SQL layer, the embedding model and the runtime at once. The three sources are hardcoded β this is not an RSS reader, and rsshub is the tool if you want arbitrary sites. Ranking only becomes useful after a bootstrap import, so a fresh install with no upvote history has nothing to rank against.
The project is a personal one: 16 stars, one author, last pushed in early July 2026, and no releases. It reads as a working setup somebody published rather than something aiming for other users, so it goes on the watchlist β the design is worth copying even if the repo goes quiet.
Repo: github.com/delirehberi/news β ~16 stars, MIT, TypeScript.