A self-hosted daily news brief that reads like a newspaper, not a feed.
Every day it gathers headlines from the RSS sources you choose, asks an LLM to pick and summarize the most significant stories into clean sections, and publishes a single, fast, mobile-friendly web page — complete with location-aware weather and a playable Wordle clone that matches the official NYT answer. It then optionally pings a webhook so you know the new edition is up.
Live demo: https://tejas1364.github.io/Tomo-News/ — the rendered brief (the
weather reflects your location, not the author's). A static copy is also in
examples/sample-brief.html.
Most news apps are built to maximize time-on-app: infinite feeds, notifications, ads. Tomo News is the opposite. It produces a finite edition once a day that you read in a couple of minutes and then you're done. No tracking, no ads, no algorithmic rabbit hole — just the day's signal on a page that's pleasant to read.
- Curated, sectioned brief. You define the sections — the default set is Tech, Startups & VC, Business, Health, Science, Local news, and Global — and their feeds; an LLM selects the top stories per section, deduplicates overlapping coverage, and writes a one-line "why it matters" for each.
- Newspaper design. A warm, light, broadsheet layout: serif type, masthead, drop-capped lead, pull-quote, and lettered section heads. Responsive — full width on desktop (two-column front page), edge-to-edge on mobile.
- Location-aware weather. The page asks the browser for the reader's location and shows local weather, falling back to a configurable city.
- Daily Word. A playable Wordle clone whose answer matches the official NYT puzzle for the day (with a local fallback word if the source is unreachable).
- One static page, deployed to Vercel. Loads instantly; nothing to maintain.
- Optional Hotline. Receive each edition as a text, and/or put an AI assistant in your texts — channel-agnostic (Telegram, iMessage, …). See below.
- No dependencies. Pure Python standard library, Python 3.8+.
flowchart LR
A[RSS / Atom feeds] --> D[gather]
C[NYT Wordle] --> D
D --> E[LLM: select + summarize → JSON]
E --> F[render newspaper HTML]
F --> G[deploy to Vercel]
G --> H[optional webhook ping]
Data is gathered in Python, the LLM only transforms text it is handed (it is given no tools and no web access), the page is rendered from a fixed template, and the result is pushed to Vercel via its REST API. Weather is fetched client-side in the browser so it can reflect the reader's location.
Requirements: Python 3.8+, an Anthropic API key, and a Vercel account + token.
git clone https://github.com/<you>/tomo-news.git
cd tomo-news
cp config.example.json config.json # edit sections, feeds, location, masthead
cp .env.example .env # add ANTHROPIC_API_KEY and VERCEL_TOKEN
python3 tomo_news.py --dry-run # build site/index.html locally (no deploy)
python3 tomo_news.py # build + deploy to VercelOpen site/index.html from the dry run to preview, then deploy for real.
Everything is driven by config.json — masthead, sections, feeds, quotes, the
LLM provider/model, the Vercel project, and the weather fallback city. See
docs/configuration.md for every field.
Secrets live only in .env (gitignored): ANTHROPIC_API_KEY, VERCEL_TOKEN,
and an optional NOTIFY_WEBHOOK.
The script runs once and exits, so schedule it however you like — cron,
launchd, or a GitHub Actions cron (a ready-to-use workflow is in
.github/workflows/daily.yml). Details and the
Vercel setup are in docs/deployment.md.
hotline.py is an optional companion with two uses:
- Receive the brief as a message. Point the daily job at a Telegram chat and it texts you the headline and link whenever a new edition publishes — handy for following along on your phone. No extra process required.
- Chat with an AI over text. Run the hotline as a small daemon and it answers incoming messages on your channel using an LLM and a persona you set.
It's channel-agnostic: a two-method adapter (fetch_new / send) drives the
loop. Telegram (cross-platform, the easy default) and iMessage (macOS)
ship in the box; WhatsApp/Slack/SMS are a documented extension point.
python3 hotline.py # run the assistant on your configured channel
python3 hotline.py --send "hello" # push one message and exitFull setup (creating a Telegram bot, getting your chat id, the macOS iMessage
path, personas, and adding new channels) is in docs/hotline.md.
- Untrusted feed text is HTML-escaped before rendering; article links are
restricted to
http(s)and carryrel="noopener noreferrer". - The LLM call runs with all tools disabled so an injected headline can't trigger file or shell access, and it is handed only the gathered text.
- Secrets are read from the environment (
.env), never from tracked config. - The published page is public — don't put anything private in it. If you set a specific city instead of geolocation, note the page reveals that location.
tomo-news/
├── tomo_news.py # the brief pipeline (single file, stdlib only)
├── hotline.py # optional: AI in your texts + brief delivery
├── config.example.json # copy to config.json and edit
├── .env.example # copy to .env and fill in secrets
├── examples/sample-brief.html# rendered sample output
├── docs/
│ ├── architecture.md
│ ├── configuration.md
│ ├── deployment.md
│ └── hotline.md
└── .github/workflows/
├── daily.yml # scheduled build + deploy to Vercel
└── pages.yml # publish the live demo to GitHub Pages
- Weather is US-only. It uses the U.S. National Weather Service; readers outside the U.S. (or who deny location access) see the fallback city.
- LLM cost. Each edition is one modest LLM call (a few cents on Sonnet-class
models). The
claude_cliprovider is supported as an alternative. - The brief reflects the feeds and model you choose; "most significant" is the model's judgment over your sources, not an objective ranking.
MIT — see LICENSE.