# delirehberi/news

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:

```bash
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:

```bash
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 [[toolbox/watchlist]] — the design is worth copying even if the repo goes quiet.

Repo: [github.com/delirehberi/news](https://github.com/delirehberi/news) — ~16 stars, MIT, TypeScript.
