hoeltke.com:一个自动写博客的个人网站,每周用 LLM 生成 AI 周报
fabianh001/hoeltke.com
hoeltke.com 是一个会自己写博客的个人网站,每周五 07:00 UTC 由流水线抓取近 7 天 AI 新闻,交给 LLM 筛选并总结后自动发布 AI Weekly 摘要,全程无人工介入。
A personal site that writes its own blog.
Every Friday at 07:00 UTC, a pipeline reads the week's AI news — research blogs,
Hacker News, the usual suspects — hands everything to an LLM, and publishes
AI Weekly: one digest of the stories that actually matter, with sources.
No humans in the loop. Read it on the web, subscribe by email, or grab the RSS
feed. The homepage is a terminal. Type help.
Live at hoeltke.com · RSS
How it works
┌────────────────────────────────────────────┐
RSS feeds ──────▶ │
Hacker News ────▶ scripts/generate-digest.ts │
│ · collect last 7 days │
│ · an LLM picks + summarizes top stories │
│ · validates every cited URL │
└──────────────┬─────────────────────────────┘
│ commits src/content/digest/YYYY-WW.md
▼
┌────────────────────────────────────────────┐
│ Astro build → rsync → Caddy on a VPS │
└──────────────┬─────────────────────────────┘
│ same run emails the new issue
▼
┌────────────────────────────────────────────┐
│ send-newsletter → Resend broadcast │
│ → subscribers (dark, on-brand email) │
└────────────────────────────────────────────┘
Readers subscribe on the site; a zero-dependency Node service on the VPS
(server/subscribe.mjs, behind Caddy) handles HMAC double opt-in and adds
confirmed contacts to Resend.
- Site: Astro + React islands + Tailwind CSS v4 — static HTML, one island (the terminal)
- Digest: any model via OpenRouter (default
anthropic/claude-sonnet-4-6, override withDIGEST_MODEL) using structured outputs; the pipeline refuses to publish if a story cites a URL it wasn't given - Newsletter: each issue also goes out by email via Resend as a dark, on-brand broadcast built from the site's own design tokens; $0 on the free tier at this scale
- CI: GitHub Actions —
weekly-digest.yml(cron: generate),newsletter.yml(email an issue),deploy.yml(rsync the site + subscribe service over SSH) - Review gate: with repo variable
NEWSLETTER_REQUIRE_APPROVAL=truethe weekly issue lands in a PR instead of onmain— edit the copy, then merge to deploy the site and email subscribers; unset it and the issue publishes and sends automatically - Honesty: every issue is labeled as auto-curated & AI-summarized, sources linked
Local development
npm install npm run dev # dev server npm run build # static build to dist/ npm test # unit + component tests npm run digest -- --dry-run # test feed collection without an API key OPENROUTER_API_KEY=... npm run digest -- --preview # generate + print + write a gitignored preview (npm run dev → /digest/preview) OPENROUTER_API_KEY=... npm run digest # generate this week's issue (writes the file) OPENROUTER_API_KEY=... npm run digest -- --slug 2026-24-extra # extra issue under a custom slug DIGEST_MODEL=openai/gpt-5.1 OPENROUTER_API_KEY=... npm run digest -- --preview # try another model npm run send-newsletter -- --dry-run # build the most recently generated issue's email and print, no send (no key needed)
Sources live in scripts/sources.json — PRs with good feeds welcome.
Repetition guard: the generator reads the last 4 issues, drops any collected item whose URL was already cited, and hands the model the past headlines with an instruction to skip already-covered stories unless there is a genuinely new development (LOOKBACK_ISSUES in scripts/generate-digest.ts).
Server + DNS setup: docs/server-setup.md.
Newsletter approval and audio
With NEWSLETTER_REQUIRE_APPROVAL=true, Friday's workflow opens a PR containing
only the newsletter text. Review and edit it, then merge to approve the final
copy. The Newsletter workflow generates audio from that merged commit, commits
the MP3 and its content fingerprint to main, deploys the site, verifies the
published audio matches the generated bytes, and sends the email.
GitHub Actions shows Generate and publish audio → Publish the site and audio
→ Verify audio and send, followed by a Sendout status summary with a listen
link. A failed generation, publication, deployment, or verification holds the
sendout. Choose Re-run failed jobs to retry the same approved revision:
completed audio is restored from main or the Actions cache, and previously sent
issues are skipped. If the issue text changed on main, start a new Newsletter
run on main with the issue slug instead. Pushes containing multiple issues are
held; release each separately using its slug.
Without approval mode, the same audio/deploy/send pipeline runs after the text is
published directly to main. Local npm run digest writes text only. To generate
local audio explicitly, use npm run digest:tts -- --slug 2026-41; use --force
when changing narration settings without changing the text.
The workflow needs the existing OpenRouter, Resend, and deployment secrets plus
permission for GITHUB_TOKEN to commit audio to main. Optional repository
variables DIGEST_SPEECH_MODEL, TTS_MODEL, TTS_VOICE, and TTS_BITRATE configure
the narration. Audio files remain versioned so future deployments retain them. Real sends via
npm run send-newsletter use the same published-audio guard; --draft and
--dry-run still support reviewing text before audio exists.
来源:GitHub · github.com
