An automated daily content curation system that aggregates articles from Hacker News, Reddit, and RSS feeds, uses Claude AI to select the best picks across five interest categories, and serves them through a personal web UI with feedback-driven learning.
- Fetch β Pulls articles from Hacker News, configured subreddits, and RSS feeds in parallel
- Deduplicate β Filters out URLs seen in the past 30 days and scores articles by topic relevance
- Curate β Sends a compact prompt to Claude, which selects 5 articles per category + 1 wildcard pick
- Learn β Reads feedback markers from yesterday's report and updates keyword weights for next time
- Publish β Renders a Markdown report, uploads it to the hosted web UI via SFTP
Runs automatically at 3 AM daily via macOS launchd.
- AI & LLMs
- Software Development
- Geopolitics & World News
- Robotics, Electronics & 3D Printing
- Science & Technology
- Wildcard (one article outside all categories)
| Layer | Technology |
|---|---|
| Runtime | Bun |
| Language | TypeScript (strict) |
| AI | Claude Code CLI tool |
| Feed parsing | fast-xml-parser |
| Remote sync | ssh2-sftp-client (IONOS SFTP) |
| Web UI | PHP + Vanilla JS (hosted on IONOS) |
| Auth | Google OAuth 2.0 (ensures only allowed email address can access reports) |
| Scheduler | macOS launchd |
- Bun runtime (for TypeScript execution)
- Claude Code CLI tool (for article curation)
- Install Claude Code from Anthropic
- Ensure
claudecommand is available in PATH - Set
CLAUDE_BINandCLAUDE_MODELin your.envfile (e.g.,CLAUDE_BIN=claude,CLAUDE_MODEL=haiku)
- IONOS SFTP account (for hosting and report sync)
- Google OAuth credentials (for web UI authentication - only allows access from configured email address)
- macOS (for launchd automation)
βββ src/
β βββ index.ts # Pipeline orchestrator
β βββ curator.ts # Claude Code CLI integration (compact ID-based prompting)
β βββ feedback.ts # Parse feedback markers from yesterday's report, update weights
β βββ prefilter.ts # Topic scoring, deduplication, candidate selection
β βββ report.ts # Markdown report renderer
β βββ seen.ts # 30-day rolling URL deduplication store
β βββ sftp.ts # Download yesterday / upload today via IONOS SFTP
β βββ deploy.ts # One-time deployment of web UI to IONOS
β βββ server.ts # Local dev server (port 3001)
β βββ fetchers/
β βββ hackernews.ts # HN top stories API
β βββ reddit.ts # Reddit hot posts (public JSON)
β βββ rss.ts # Generic RSS/Atom parser
β
βββ config/
β βββ interests.yaml # Topics, keywords, subreddits, RSS feeds
β βββ feedback-weights.json # Learned keyword weights (auto-updated)
β βββ seen-urls.json # 30-day dedup store (auto-updated)
β
βββ public/
β βββ index.php # Report viewer SPA
β βββ api.php # REST API (reports, voting, settings, deletion)
β βββ auth.php # Google OAuth handler
β βββ .htaccess # IONOS routing config
β
βββ scripts/
β βββ install-launchd.sh # Install macOS launchd 3 AM job + persistent wake schedule
β βββ run.sh # Wrapper: loads .env, sets PATH, runs pipeline
β βββ monitor.sh # Colourised live log viewer
β
βββ reports/ # Generated Markdown reports (YYYY-MM-DD.md)
βββ logs/ # Execution logs (dailyreport.log, dailyreport.err)
curl -fsSL https://bun.sh/install | bashFollow the installation instructions from Anthropic Claude Code docs.
bun install# Claude Code CLI
CLAUDE_BIN=claude
CLAUDE_MODEL=haiku
# IONOS SFTP
FTP_HOST=your-sftp-host
FTP_USER=your-username
FTP_PASS=your-password
FTP_REMOTE_REPORTS_DIR=/path/to/remote/reports
TARGET_DIR=/path/to/remote/web-root
# Google OAuth (for web UI login)
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
ALLOWED_EMAIL=your@email.com
REDIRECT_URI=https://your-site.com/projects/dailyreport/auth.phpEdit config/interests.yaml to set your topics, keywords, subreddits, and RSS feeds.
bun run deployUploads index.php, api.php, auth.php, .htaccess, and a generated config.php (with OAuth secrets) to IONOS.
bun run generateFetches articles, calls Claude, writes reports/YYYY-MM-DD.md, and uploads it to IONOS.
bun run dry-runFetches and pre-filters articles without spending API credits. Useful for testing source config.
bun run serveStarts a dev server at http://localhost:3001 with the report viewer (no OAuth required).
bash scripts/install-launchd.shInstalls a launchd job that runs the pipeline at 3:00 AM every day, and sets a persistent daily wake at 2:55 AM via pmset repeat so the machine is awake in time. The wake schedule survives reboots and macOS updates.
Note:
install-launchd.shrequires sudo to set the wake schedule. If it can't prompt for a password (e.g. first run), set it manually:sudo pmset repeat wake MTWRFSU 02:55:00
View logs (colourised):
bash scripts/monitor.shView full log history:
bash scripts/monitor.sh --allRaw logs:
tail -f logs/dailyreport.logUninstall:
launchctl unload ~/Library/LaunchAgents/com.dailyreport.generate.plist
rm ~/Library/LaunchAgents/com.dailyreport.generate.plistVote on articles in the web UI with π / π. The next morning, the pipeline:
- Downloads yesterday's report (which now contains your votes as
<!-- vote:+1 -->or<!-- vote:-1 -->markers) - Extracts keywords from voted article titles
- Nudges
config/feedback-weights.jsonby Β±0.1 per keyword, clamped to[-1.0, 1.0] - Applies those weights to scoring during the next pre-filter pass
Over time, the report adapts to surface articles you actually want to read.
- Compact prompting β Articles are sent to Claude as
ID|Title|Source|Snippetlines. Claude returns only IDs + reasons, minimising token usage by ~60% vs full JSON. - 30-day deduplication β A rolling URL window (
seen-urls.json) prevents the same article appearing twice within a month. - SFTP sync model β The pipeline pulls yesterday's report to read votes, generates today's report, then pushes it. No database required.
- Keyword weights β Simpler and more inspectable than embedding-based preference learning; the full weight map is readable JSON.