Hacker News For Me
A personal Hacker News reader. Create an account, save your interests, and get a feed of relevant stories in a familiar HN-style interface. Matching runs on the backend using Codiv’s OpenJev model; users don't need API keys.
Run
Copy .env.example to .env if you don't already have one, then set CODIV_API_KEY to your Codiv API key.
docker compose up --build -d
Open localhost:8000, create an account, and save your interests. Accounts work without an API key, but story matching requires one.
For local development, use Python 3.13:
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
fastapi dev main.pyBoth options read .env. See .env.example for concurrency, model, and daily inference limits.
How it works
- Frontend: plain HTML, CSS, and JavaScript, served by FastAPI.
- Accounts: username/password login, scrypt password hashes, and HTTP-only session cookies. Password recovery is not implemented.
- Storage: SQLite at
data/app.sqlite3. Docker keeps it in a persistent volume. - Collection: a background scheduler queues matching every five minutes. Stories come from the Hacker News Algolia API; collection and article content are shared across accounts.
- Matching: Codiv screens titles, then scores article relevance against saved interests. A bounded thread pool processes four stories concurrently by default.
- Deduplication: accepted and rejected decisions persist per user and interest revision. Repeat fetches and restarts skip decided stories; changing interests allows reassessment. Cached inference results also reduce work when retrying incomplete jobs.
Run one application process/replica. For public deployment, use HTTPS, set APP_ORIGIN to your public origin, and set COOKIE_SECURE=true. Keep .env private. Back up the database; docker compose down -v deletes the persistent volume.
Tests
pip install -r requirements-dev.txt python -m unittest discover -s tests -v
Tests use temporary databases and mocked external services, with no paid inference calls.
Matched stories show their closest saved interest. Article relevance and interest selection share one Codiv request; older saved matches remain untagged.
Guests can save interests and receive matches without an account. A private cookie remembers them for 30 days in the same browser; clearing cookies loses access. Signing up preserves the guest feed and usage allowance. Logging into an existing account uses that account’s feed. Expired guests stop receiving scheduled matches.
Switch inference providers
Codiv is the default. For a TypeSafe Jev demo, set these in .env:
INFERENCE_PROVIDER=typesafe TYPESAFE_API_KEY=your-typesafe-key TYPESAFE_MODEL=jev-latest
Restart the local server, or run docker compose up --build -d to recreate the container with the changed environment. Switch back with INFERENCE_PROVIDER=codiv; Codiv uses CODIV_API_KEY and CODIV_MODEL. Keys stay on the backend. Unknown providers fail at startup, and a missing key never falls back to another provider.
Scores, feeds, and saved decisions are separated by provider and configured model. Switching back reuses that provider’s decisions. Article downloads are shared, and daily call limits apply across providers. Old decisions without model metadata are preserved separately; recent stories are reassessed once after upgrading. Pin a model version for reproducible demos: a latest alias can change without invalidating saved decisions. Thresholds remain 0.7 for titles and 0.9 for article relevance for both providers.