跳到正文
GitHub·· 7 天前AI 评分22

hoeltke.com:一个自动写博客的个人网站,每周用 LLM 生成 AI 周报

fabianh001/hoeltke.com

AI 导读

hoeltke.com 是一个会自己写博客的个人网站,每周五 07:00 UTC 由流水线抓取近 7 天 AI 新闻,交给 LLM 筛选并总结后自动发布 AI Weekly 摘要,全程无人工介入。

正文

A personal site that writes its own blog.

image

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 with DIGEST_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=true the weekly issue lands in a PR instead of on main — 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