# DailyBrief
**Repository Path**: MitchellSorin/DailyBrief
## Basic Information
- **Project Name**: DailyBrief
- **Description**: 股票:科技信息总结
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-05-28
- **Last Updated**: 2026-07-06
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 📰 daily-brief · 10 分钟拥有你自己的 AI 每日简报
**中文** · [English ↓](#en)
[](LICENSE)
[](https://nodejs.org/)
[](https://www.typescriptlang.org/)
[-orange.svg)](#-llm-后端配置)
[](#a-github-actions--pages零基础设施推荐)
[](https://leiting-eric.github.io/DailyBrief)
[](https://github.com/leiting-eric/DailyBrief)
> **你的私人 AI 每日简报,跑在你自己掌控的基础设施上。** 默认启用 26 个数据源 · LLM 摘要 · 21 个股票/加密标的**技术指标 + AI 交易点评** · 中英双语 · 6 个 LLM 后端可选。
>
> **三种部署任选**:[**🚀 5 分钟 Fork 到 GitHub Actions**](#a-github-actions--pages零基础设施推荐) · [**💻 本地一键装**](#b-本地一键装) · [**🤖 一句话让 AI Agent 帮你装**](#c-给-ai-agent-一句话装)。
**🌐 Live demos** —
[📰 leiting-eric.github.io/DailyBrief](https://leiting-eric.github.io/DailyBrief)(A 方式 · GitHub Actions + Pages)
·
[📰 daily.leiting.tech](https://daily.leiting.tech)(B 方式 · 本地服务器部署)
---
## ✨ 核心特性
- **🌍 全网多源聚合**:默认启用 26 个数据源,覆盖硅谷科技、AI 前沿、全球财经、国际时政、中文社区,一份报告通吃
- **📈 21 个标的实时行情**:美股 / 加密 / 港股 / 商品外汇 / 宏观信号,附 SMA / RSI / MACD 技术指标 + LLM 每日交易点评
- **🤖 6 个 LLM 后端可插拔**:Claude CLI / Anthropic / OpenAI / DeepSeek / MiniMax / Zhipu,一个环境变量切换,不绑死任何家
- **🌐 中英双语**:`REPORT_LOCALE=en` 一切——数据源、prompt、UI 文案、Bullish/Bearish stance 全套切英文
- **🚀 部署灵活**:GitHub Actions(零基础设施)/ 本地系统调度器 / 自托管服务器三选一,互不冲突可并存
- **🆓 数据源零 API key**:所有源走免费公开端点(RSS / 公开 JSON),不需要付费订阅
- **🔒 不绑定第三方阅读服务**:Feedly / Inoreader / Pocket 那些都不沾,你看了什么是你自己的事
- **📁 单文件 HTML**:CSS + JS 全内联,无外部依赖,scp 上服务器直接当首页
---
## 📚 信源图谱
当前 registry 共 53 个源,默认启用 26 个;按 locale 过滤后 zh 模式有效 24 个、en 模式有效 23 个,分布如下:
### 🧑💻 技术动态

- **GitHub Trending** · 热榜每日刷新
- **AI 媒体**(merged):OpenAI / DeepMind / Hugging Face Blog / TLDR AI / Smol AI / Latent Space / MIT Tech Review
- **X 推文**(attentionvc-ai):精选 AI 圈大佬动态
### 📈 市场行情(非新闻源,21 个标的)

- **美股 / ETF**:SPY / QQQ / AAPL / MSFT / NVDA / GOOGL / TSLA / META
- **加密**:BTC / ETH / SOL(附 alt.me 加密恐慌贪婪指数 + CoinGecko 总览)
- **中港**:BABA / PDD / JD / 0700.HK
- **商品外汇**:GC=F(黄金)/ CL=F(WTI 原油)/ USDCNY=X
- **宏观信号**:^VIX / ^TNX(10Y 美债)/ DXY(美元指数)
### 🌍 时政观察

**BBC / Guardian / NYT / NPR / DW 中文 / Al Jazeera / The Diplomat** — 7 家主流国际媒体的 World 频道
### 💰 财经要点

**Bloomberg / WSJ / FT / BBC Business / Economist** — 5 家全球财经主力
### 💬 社区讨论
- **zh 模式**:V2EX 热榜 / LinuxDo 热帖
- **en 模式**:Hacker News / Reddit r/stocks(自动替换上面两个)
> 完整列表 + 启用状态:`npm run sources` 查看;改源 → 编辑 [`sources.config.json`](sources.config.json)。
---
## 🚀 三种部署方式选一种
| 方式 | 适合谁 | 你需要 | 几分钟搞定 |
|---|---|---|---|
| **A. GitHub Actions + Pages** | 没服务器、不想常开电脑 | 一个 API key(Anthropic / OpenAI / DeepSeek / MiniMax / Zhipu 任一) | ~5 分钟(推荐) |
| **B. 本地一键装** | 有常开的电脑/服务器、想极致便宜 | Node 20+,可选 Claude Code 登录 | ~3 分钟 |
| **C. 给 AI Agent 一句话** | 懒、想让 Cursor / Codex / Claude Code 帮你装 | 同上 | 一句话 |
### A. GitHub Actions + Pages(零基础设施,推荐)
1. **Fork 这个 repo**(GitHub 右上角 Fork 按钮)
2. 进 Fork 的 repo → **Settings → Actions → General → Workflow permissions** 设为 **Read and write permissions**
3. **Settings → Pages → Build and deployment → Source** 选 "Deploy from a branch",分支 `gh-pages` / 路径 `/ (root)`(第一次跑完才会出现 gh-pages 分支,先建 secret 再触发一次即可)
4. **🔑 配置 LLM 后端** —— 这步是关键。每个后端都要**一个 secret + 对应的 `LLM_BACKEND` variable**(不只是 secret),按下表对照填:
> ⚠️ **最常踩的坑**:只加了 secret(比如 `DEEPSEEK_API_KEY`)忘了加 `LLM_BACKEND` variable —— workflow 默认 `LLM_BACKEND=anthropic`,所以无论你填的是哪家的 key,运行时都会报 `ANTHROPIC_API_KEY required`。两个都要加。
| 你想用 | Secrets 标签加 | Variables 标签加 `LLM_BACKEND` | 大致成本 |
|---|---|---|---|
| 🟣 **Anthropic Sonnet**(默认,prompt 按 Sonnet 调优) | `ANTHROPIC_API_KEY` | 不填或填 `anthropic` | ~$0.03-0.05 / 天,月 < $2 |
| 🐋 **DeepSeek**(便宜大碗,中文友好) | `DEEPSEEK_API_KEY` | `deepseek` | ~$0.01-0.02 / 天,月 < $1 |
| 🟢 **OpenAI** | `OPENAI_API_KEY` | `openai` | gpt-4o-mini ~$0.02 / 天 |
| 🔵 **MiniMax** | `MINIMAX_API_KEY` | `minimax` | 类似 DeepSeek 量级 |
| 🧠 **Zhipu / 智谱** | `ZHIPU_API_KEY` | `zhipu` | 看服务方定价 |
| 🌀 **中转站 / 反代 / 其他 OpenAI 兼容服务**(Moonshot / SiliconFlow / OpenRouter / 自建 Claude 反代 / 本地 Ollama / LM Studio 等) | `LLM_API_KEY` | `openai`(极少数 Anthropic 协议反代填 `anthropic`) | 看服务方定价 |
位置:**Settings → Secrets and variables → Actions**,左边切换 Secrets / Variables 两个标签。
> 🌀 **选了最后一行"中转站"的额外步骤**:Variables 还要加 `LLM_BASE_URL`(中转站给的 endpoint,如 `https://api.moonshot.cn/v1`)和 `LLM_MODEL`(如 `moonshot-v1-8k`)。不确定协议?默认 `LLM_BACKEND=openai` 覆盖 95% 中转站;如果跑挂报 404 / 协议错误再改成 `anthropic`。
5. (可选)同页 Variables 再加:
- `LLM_MODEL` —— 覆盖该 backend 的默认模型(不填用 [`.env.example`](.env.example) 里列的默认)
- `LLM_BASE_URL` —— 自定义 endpoint。**选了上面"中转站"那行的话必填**;本地 Ollama 填 `http://localhost:11434/v1`、LM Studio 填 `http://localhost:1234/v1`
- `REPORT_LOCALE` —— `zh`(默认)或 `en`,控制数据源 + UI + prompt 全套切英文
- `REPORT_TZ` —— IANA 时区名(默认 UTC),例 `Asia/Shanghai` / `America/Los_Angeles`。**同时影响触发时间和日期标签**
- `REPORT_HOUR` —— 触发的小时(基于 `REPORT_TZ`),默认 `8`(早 8 点)。逗号分隔可多次触发,如 `8,18` = 早 8 + 晚 6
- `REPORT_DAYS` —— 触发的星期(cron 风格,`0`=周日 ... `6`=周六),默认 `*`(每天)。例 `1-5` = 工作日;`1,3,5` = 周一三五
6. **Actions 标签 → 选 "Daily Brief" workflow → Run workflow** 手动触发一次
跑完后报告在 `https://<你的用户名>.github.io//`。之后**默认每天 `REPORT_TZ` 时区的 08:00 自动更新**(不设 `REPORT_TZ` 就是 UTC 08:00)。
> ⏰ **触发机制**:GitHub Actions 的 cron 只接受 UTC,所以工作流 cron 设置为**每小时探测两次**,里面有一个 `gate` 任务用 `REPORT_TZ` 把当前小时和 `REPORT_HOUR/REPORT_DAYS` 对照——匹配才往下跑 build,否则秒退。这样不论你在哪个时区都能精准命中本地时间,**夏令时也自动跟着切换**(IANA 时区数据库内置)。
**常用 schedule 配方:**
| 想要 | `REPORT_HOUR` | `REPORT_DAYS` |
|---|---|---|
| 每天 08:00(默认) | 不填或 `8` | 不填或 `*` |
| 每天早晚两次(8 + 18 点) | `8,18` | `*` |
| 工作日 09:00 | `9` | `1-5` |
| 周一/三/五 早 7 晚 9 两次 | `7,21` | `1,3,5` |
| 每 6 小时一次 | `0,6,12,18` | `*` |
只想要默认每天 08:00 本地时间,**只填 `REPORT_TZ` 一个变量就够了**(如 `Asia/Shanghai`),其他全部留空。
**💸 成本估算**:GitHub Actions 公开 repo 完全免费。Pages 公开 repo 也免费。唯一花钱的就是 LLM API 调用——DeepSeek 月成本不到 $1,Anthropic Sonnet < $2。
> ⚠️ 用 GH Actions 模式就意味着**用不了本地 `claude` CLI**——Claude Code 的 OAuth 登录在你本机,GitHub 的服务器看不到。如果你已经在 Max 订阅里,建议两条路并行:本地装(B 方式)用 Claude CLI 跑你自己的服务器版本,GH Actions 用 DeepSeek 跑 Pages 公开版本。两份报告独立,互不影响。
#### 🐛 A 方式常见坑
- **"Upgrade or make this repository public to enable Pages"** —— GitHub Pages 在 Free 账户的 Private repo 上不可用。Settings → General → Danger Zone → Change visibility 改 Public。Actions Secrets 在 Public repo 里依然加密保护,对其他人不可见
- **Variable name 报 "alphanumeric only"** —— 输入 `LLM_BACKEND` 时下划线被中文输入法替换成了全角 `_`(U+FF3F)。切到英文输入法 Shift+`-` 重打
- **第一次跑完才能选 Pages source** —— Pages 设置页要求选已存在的分支,但 `gh-pages` 是首次 workflow 跑成功后才创建出来。顺序:配 secret → 触发 workflow → 跑完 → 回 Settings → Pages 选 `gh-pages`
- **Action 红 X 怎么看具体原因** —— 点失败的 build → 左边列出每个 step → 找有红 X 的那步点开看 log。最常见两类:`401/402` = API key 拼错或没余额;`403` = workflow permissions 没设成 Read and write
- **自动触发只跑了 `gate`,后面的 build 被 skipped** —— 这是调度门禁在工作,不一定是失败。点开那次 run → `gate` → `Check schedule`,看 `Now in ...`、`Configured REPORT_HOUR=...` 和 `No match — skipping (...)`。最常见原因是没在 **Repository Variables** 里设置 `REPORT_TZ=Asia/Shanghai`,此时默认按 UTC 08:00 触发(北京时间 16:00)。如果变量放在 **Settings → Environments** 里,默认 workflow 也读不到;请移到 **Settings → Secrets and variables → Actions → Variables**,或给 `gate` / `build` 两个 job 都加同一个 `environment: `
- **报错 `ANTHROPIC_API_KEY (or generic LLM_API_KEY) is required`,但我填的是别家的 key** —— 经典 secret/variable 不配对。Workflow 默认 `LLM_BACKEND=anthropic`,光填 `DEEPSEEK_API_KEY` / `OPENAI_API_KEY` 不够,**必须同时去 Variables 标签加 `LLM_BACKEND=deepseek` / `openai`**。从 v1.x 起启动期会直接告诉你哪个 key 已设、应该把 `LLM_BACKEND` 改成什么
- **配齐了 secret + variable 还是报同样的错** —— 99% 是配置放错了作用域。GitHub 上有两个长得几乎一样的页面:
- ✅ **Settings → Secrets and variables → Actions**(页眉是 "Repository secrets" / "Repository variables")—— 本项目默认走这里
- ⚠️ **Settings → Environments → \<某个 environment\>**(页眉是 "Environment secrets" / "Environment variables")—— 这里的值**只有当 workflow job 显式声明了 `environment: ` 才会注入**;本项目默认 workflow 没声明,所以放这里运行时拿不到
排查方法:进配置页面看页眉。如果写的是 "Environment ..." 你有**两种修法**任选其一:
- **简单做法(推荐)**:把所有 secret **和** variable 从 Environment scope 移到 Repository scope(路径:Settings 左栏 → "Secrets and variables → Actions",注意**不要从 "Environments" 进**),workflow 不用改。**特别提醒**:secret(如 `DEEPSEEK_API_KEY` / `ANTHROPIC_API_KEY`)因为隐藏了值,最容易被忘掉只搬 variable 不搬 secret,导致换了个新错误("key 未设置")。两边都要搬完整
- **保留 Environment 的做法**:编辑 `.github/workflows/daily.yml`,在 `gate` 和 `build` 两个 job 各加一行 `environment: <你的环境名>`(两个都要加,因为 `gate` 也读 `vars.REPORT_TZ` 等变量),名字必须跟 Settings → Environments 里建的一字不差。适合想用 environment 做审批门禁 / branch 限制的人
- **跑了 30 秒就挂(其他情况)** —— LLM API 返 400 / key 没余额 / 配额耗尽。看 step "Generate today's report" 的 log
### B. 本地一键装
```bash
# Linux / macOS
curl -sSL https://raw.githubusercontent.com/leiting-eric/DailyBrief/main/bootstrap.mjs | node
# Windows PowerShell
irm https://raw.githubusercontent.com/leiting-eric/DailyBrief/main/bootstrap.mjs | node -
```
这条命令会自动:
1. 检查 Node / git / claude CLI 是否就位(没装 claude CLI 只发警告,可继续走 API 后端)
2. `git clone` 到 `~/daily-brief`(Windows: `%USERPROFILE%\daily-brief`)
3. `npm install`
4. 注册系统定时(Windows Task Scheduler / macOS launchd / Linux cron,默认 08:00 本地时间)
5. 写 `~/.daily-brief-config` 记录项目路径
6. 在 `~/.claude/` 建符号链接让 Claude Code 的 skill 和 slash command 全局可用
7. 跑一次 `npm run dry-run` 烟测
**Claude Code 集成**:使用 `--global` 安装后,可以在任意目录运行 `/run-daily` 和 `/check-daily`;描述问题时也会加载项目内的 `daily-brief` skill。Cursor / Codex 等其他 agent 不使用这套 skill/command 机制,但不影响系统定时任务运行。手动触发命令:
| 平台 | 手动触发 |
|---|---|
| Windows | `Start-ScheduledTask -TaskName DailyBrief` |
| macOS | `launchctl start com.daily-brief` |
| Linux | `node scripts/run-daily.mjs`(cron 不支持手动触发) |
自定义路径 / 触发时间:
```bash
node bootstrap.mjs --target /custom/path --at 07:30
```
**LLM 后端**:默认走本地 `claude` CLI(首次需在浏览器登录一次:`echo "hi" | claude --print --model sonnet`,通常会长期有效;如果遇到 401 / 认证失败,重新运行 `claude` 登录)。不用 Claude Code,或想换模型 / 换家 / 接中转,都在 `.env.local` 里改 —— 完整可复制的配置示例见下方 [🤖 LLM 后端配置](#-llm-后端配置)。
### C. 给 AI Agent 一句话装
无论你用哪个 AI Agent(Claude Code / Cursor / Codex / Continue.dev / OpenClaw 等),把下面这段发给它:
> 帮我装这个开源项目,按 README 的"本地一键装"流程跑 bootstrap,完成后告诉我下次自动触发的时间:
> https://github.com/leiting-eric/DailyBrief
项目里有 [`AGENTS.md`](AGENTS.md)(通用 agent 协议)+ [`.claude/skills/daily-brief/SKILL.md`](.claude/skills/daily-brief/SKILL.md)(Claude Code 专属,更详细),Agent 装完后能直接帮你诊断"今天报告没出来"、"加个新数据源"这类问题。
---
## 📋 前置要求
- **Node.js 20+** + **npm** + **git**(B/C 方式本地需要;A 方式不需要——GH Actions 容器自带)
- **一个能跑的 LLM**(任选其一):Claude Code CLI 已登录 / Anthropic / OpenAI / DeepSeek / MiniMax 任一家的 API key
- 平台:Windows 10/11、macOS 12+、Linux(任一平台都支持,定时机制自动适配)
---
## 🔧 手动安装
```bash
# 1. clone + 依赖
git clone https://github.com/leiting-eric/DailyBrief.git
cd DailyBrief
npm install
# 2. 配置 LLM 后端
# 默认 claude CLI(如果没登录会引导你登录):
echo "say hi in Chinese" | claude --print --model sonnet
# 或用其他 backend:cp .env.example .env.local 编辑 LLM_BACKEND 和对应 API key
# 3. 注册定时 + 启用全局 skill
node scripts/install.mjs --global
# 也可指定时间:node scripts/install.mjs --at 07:30 --global
# 4. 立即触发一次测试
# Windows: Start-ScheduledTask -TaskName DailyBrief
# macOS: launchctl start com.daily-brief
# Linux: node scripts/run-daily.mjs
```
下次触发时机:
- **🪟 Windows** — 系统会自动唤醒电脑(如在睡眠),跑完再回睡
- **🍎 macOS** — launchd 不会主动唤醒,电脑睡着的话跳过这次(需要 `pmset wake schedule` 配合)
- **🐧 Linux** — cron 同理,挂起期间不跑
---
## 🛠️ 日常命令
| 命令 | 用途 | 耗时 |
|---|---|---|
| `npm run daily` | 手动完整跑一次 | 5-8 min |
| `npm run dry-run` | 只抓取不调 LLM,验证数据源 | ~30s |
| `npm run render [date]` | 改了 CSS/排版后重渲染 | <1s |
| `npm run regen-trading [date]` | 重做交易部分 | ~2 min |
| `npm run regen-enrich [date]` | 补缺失的中文摘要 | ~30s |
| `npm run open` | 在 Chrome 打开今日报告 | 即时 |
| `npm run quota-report` | 看各 LLM backend 用量统计 | 即时 |
| `npm run sources` | 列出所有数据源(按 locale 标注启用/过滤状态)| 即时 |
| `npm run sources:check` | 仅校验 `sources.config.json` schema(适合 CI / pre-commit)| 即时 |
---
## 📊 数据源配置
数据源以 JSON 数组形式集中存储在项目根的 [`sources.config.json`](sources.config.json) —— **唯一配置入口**,不需要改 TypeScript 代码即可加/禁/调整源。每条记录的字段:
| 字段 | 必填 | 说明 |
|---|---|---|
| `id` | ✓ | 全局唯一短标识(dispatch.ts 用 id 路由到对应 fetcher)|
| `name` | ✓ | UI 显示名 |
| `type` | ✓ | `rss` / `api` / `scrape` |
| `url` | ✓ | RSS feed 或 API endpoint |
| `category` | ✓ | `tech` / `finance` / `politics`,决定 L1 tab |
| `subcategory` | | `tech` 下的 L2 分组(`github-trending` / `ai-news` / `x-viral` / `cn-community`);`finance` 下统一 `news` |
| `enabled` | | 默认 `true`,设 `false` 跳过抓取(保留记录便于回滚)|
| `useCurl` | | 若该源被 Cloudflare TLS-fingerprint 拦了 Node undici,设 `true` 让 rss.ts 走 curl 子进程 |
| `lang` | | `zh` 表示源内容是中文 — enrich 阶段会跳过它(无需把中文翻译成中文)|
| `locales` | | 数组,标明该源出现在哪些 report locale 下。默认 `["zh", "en"]` |
| `notes` | | 任意备注(如"被废弃因为 feed 死了"),运行时忽略 |
### 加一个 RSS 源
1. 编辑 `sources.config.json`,追加一条记录
2. 跑 `npm run sources:check` 校验 schema
3. `npm run dry-run` 抓一次验证拉取正常
4. 下次 `npm run daily` 自动包含
### 🌐 Locale 模式(zh / en)
通过 `REPORT_LOCALE` 环境变量切换:
```bash
# .env.local
REPORT_LOCALE=zh # 默认 — 中文 mode,含 V2EX / LinuxDo / DW 中文等中文源
# REPORT_LOCALE=en # 英文 mode — 自动过滤掉 zh-only 源,挂上 en-only 源
```
每个源在 JSON 里的 `locales` 字段决定它在哪些模式下出现:
- `["zh"]` — **仅中文 mode**(V2EX / LinuxDo / DW 中文)。英文用户读不懂,英文 mode 自动 drop
- `["en"]` — **仅英文 mode**(Hacker News / r/stocks 英文社区源,替代中文社区源)。zh mode 不显示
- `["zh", "en"]`(默认)— 两 mode 都参与(BBC / Bloomberg / WSJ / NYT 全球英文源 + AI 资讯源)
当前启用源(`enabled: true`)按 locale 分布:
| Locale | 启用源数 | 主要构成 |
|---|---|---|
| `zh` | 24 | 21 个全球英文源(附中文摘要)+ 3 个中文专属(V2EX / LinuxDo / DW 中文)|
| `en` | 23 | 21 个全球英文源 + 2 个英文社区(Hacker News + r/stocks)|
英文 mode 完整切换:HTML UI 文案、enrichment / digest / trading-commentary 三套 prompt、stance 词("偏上行/偏下行/中性" → Bullish/Bearish/Neutral)、日期格式(zh-CN → en-GB)、Markdown 输出 —— 全部跟着 `REPORT_LOCALE` 切。**中文社区源在英文 mode 下被自动过滤掉**。
---
## 🤖 LLM 后端配置
项目通过 `LLM_BACKEND` 环境变量切换后端。**默认 `claude-cli`** —— 直接复用 Claude Code 已登录的认证,不需要额外配 API key。不用 Claude Code、或想走自己的 API key,按下表切换。
### 配置文件在哪
**本地安装**:所有 LLM 配置都在仓库根目录的 `.env.local`(被 gitignore 了,安全存 key)。第一次没有这个文件就 `cp .env.example .env.local`,编辑器打开按 backend 解开对应几行即可。改完保存就生效,不用重启 shell —— 这些 TS pipeline 入口(`npm run daily` / `regen-trading` / `regen-enrich`)第一行都会 dotenv 预加载它。
**GitHub Actions 部署**:不需要 `.env.local`,改成在 Settings → Secrets and variables → Actions 里加(见上面 [A 方式](#a-github-actions--pages零基础设施推荐) 第 4 步)。
### 支持的 backend
| backend | API key 环境变量 | 默认 model | base URL |
|---|---|---|---|
| 🎯 `claude-cli` (默认)| 不需要,复用 Claude Code OAuth | `sonnet` | — |
| 🟣 `anthropic` | `ANTHROPIC_API_KEY` | `claude-sonnet-4-6` | `api.anthropic.com` |
| 🟢 `openai` | `OPENAI_API_KEY` | `gpt-4o-mini` | `api.openai.com/v1` |
| 🐋 `deepseek` | `DEEPSEEK_API_KEY` | `deepseek-v4-flash` | `api.deepseek.com/v1` |
| 🔵 `minimax` | `MINIMAX_API_KEY` | `MiniMax-M2.7` | `api.minimax.io/v1` 1 |
| 🧠 `zhipu` | `ZHIPU_API_KEY` | `claude-sonnet-4-6` | `open.bigmodel.cn/api/anthropic` |
1 中国大陆访问设 `MINIMAX_BASE_URL=https://api.minimaxi.com/v1`。
### 本地 `.env.local` 最小配置示例(任选一组复制走)
```bash
# 默认:复用本机 claude CLI(Max 订阅最划算)
# 什么都不用填,留空 .env.local 即可
# Anthropic API
LLM_BACKEND=anthropic
ANTHROPIC_API_KEY=sk-ant-...
# LLM_MODEL=claude-sonnet-4-6 # 可选,默认即此
# DeepSeek(便宜,中文友好)
LLM_BACKEND=deepseek
DEEPSEEK_API_KEY=sk-...
# LLM_MODEL=deepseek-v4-flash # 可选
# OpenAI
LLM_BACKEND=openai
OPENAI_API_KEY=sk-...
# LLM_MODEL=gpt-4o-mini # 可选;想用更大模型填 gpt-4o / gpt-4.1 等
# MiniMax
LLM_BACKEND=minimax
MINIMAX_API_KEY=...
# MINIMAX_BASE_URL=https://api.minimaxi.com/v1 # 国内访问用这条
# Zhipu / 智谱(Anthropic 协议兼容)
LLM_BACKEND=zhipu
ZHIPU_API_KEY=...
# ZHIPU_BASE_URL=https://open.bigmodel.cn/api/anthropic
# 中转 / 反代 / OpenAI 兼容服务(Moonshot / SiliconFlow / OpenRouter / Ollama / LM Studio …)
LLM_BACKEND=openai # 协议是 openai 兼容就填 openai
LLM_API_KEY=<中转给的 key>
LLM_BASE_URL=https://api.moonshot.cn/v1
LLM_MODEL=moonshot-v1-8k # 中转/自托管必须显式指定 model
# Anthropic 协议反代(claude-relay 之类)
LLM_BACKEND=anthropic
ANTHROPIC_API_KEY=<反代 key> # 也可写成 LLM_API_KEY
ANTHROPIC_BASE_URL=https://你的反代/ # 也可写成 LLM_BASE_URL
```
### 通用覆盖项
- `LLM_MODEL=` —— 任意 backend 的 model 都能用这个变量覆盖默认(如 `LLM_MODEL=gpt-4o` 走 openai 的更大模型)
- `_BASE_URL` —— 走自托管代理 / 兼容服务(如 LM Studio / Ollama 跑 OpenAI 兼容接口 → `LLM_BACKEND=openai` + `OPENAI_BASE_URL=http://localhost:1234/v1`)
- `LLM_API_KEY` / `LLM_BASE_URL` —— **通用别名**,专用变量没设时 fallback 到这里。用 Moonshot / SiliconFlow / OpenRouter / 自建反代等非预设服务时推荐用这对,避免 `OPENAI_API_KEY` 这样的语义错位
### 验证配置是否对
改完 `.env.local` 不想等 5-8 分钟的完整 daily 跑完才知道 key 通不通?跑这个 ~30 秒、只用 1 次 LLM 调用的小指令:
```bash
npm run regen-enrich -- finance:news
```
通了会看到 `[regen-enrich] enrichment done in 28s, matched 12/12`;不通会在 1 秒内被启动校验拦下并告诉你具体怎么修(哪个 key 没填 / `LLM_BACKEND` 该写啥)。
### 怎么选
| 你的情况 | 推荐 backend |
|---|---|
| 已经在用 Claude Code(任意订阅等级)| `claude-cli` — 零配置,按你订阅的等级走 |
| 不用 Claude Code,只想低成本跑日报 | `openai` 配 `gpt-4o-mini`、或 `deepseek` 配 `deepseek-v4-flash`(更便宜)|
| 中文摘要质量优先,预算可放宽 | `anthropic` 配 `claude-sonnet-4-6` |
| 国内网络访问,要规避 GFW | `deepseek`、`minimax` 或 `zhipu`(都是国内厂商)|
**切 backend 不需要改代码**:所有 prompt 都在 `lib/ai/prompts.ts` 抽离,跟 backend 无关;JSON 错误兜底(`jsonrepair`)也是 backend-agnostic。切完后跑一次 `npm run daily`,进 `logs/llm-calls.jsonl` 看新 backend 的调用记录。
---
## 🌐 自托管部署(可选)
每次 `npm run daily` 跑完,自动把新 HTML 推到自己的服务器,访客打开 `https://your-domain/` 就能看到当天最新报告。**默认不启用**,环境变量不设就跳过。
### 一次性服务器准备
假设服务器跑 Ubuntu + nginx + 已申请域名,登录用户有 sudo NOPASSWD:
```bash
# 服务器上
sudo mkdir -p /var/www/your-domain && sudo chown -R www-data:www-data /var/www/your-domain
```
`/etc/nginx/sites-available/your-domain.conf`:
```nginx
server {
listen 80;
server_name your-domain;
root /var/www/your-domain;
index index.html;
location / { try_files $uri $uri/ =404; }
}
```
启用 + 自动签 SSL:
```bash
sudo ln -s /etc/nginx/sites-available/your-domain.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d your-domain --agree-tos --redirect
```
### 本地启用 auto-deploy
`.env.local`(gitignored)加两行:
```
DEPLOY_HOST=user@your-server-ip
DEPLOY_PATH=/var/www/your-domain
```
之后:
- ✅ 每次 `npm run daily` 跑完,自动 scp 当天 HTML 到服务器 + 刷新 `index.html`
- ✅ `npm run deploy [YYYY-MM-DD]` 手动推任意一天
- ✅ 部署失败不阻断 daily 本身(HTML 已经在本地 `daily_reports/` 落盘,可重跑 `npm run deploy` 补推)
---
## 💡 Claude Code 集成
装好后**任意目录**(不必 cd 进项目)打开 Claude Code 都可用:
| 触发 | 行为 |
|---|---|
| `/run-daily` | 立即触发 daily 并后台监听到完成。从任意目录都行 |
| `/check-daily` | 查任务状态 + 报告文件 + 配额 |
| 描述问题("日报又挂了"、"X 推文为啥没更新"等)| `daily-brief` skill 的关键词触发自动加载,让 Claude 直接懂这个项目 |
**实现机制**:`scripts/install.mjs --global` 在 `~/.claude/` 下建符号链接,指向项目内的 [`.claude/skills/daily-brief/SKILL.md`](.claude/skills/daily-brief/SKILL.md) 和 [`.claude/commands/`](.claude/commands/) 文件——**单一源**,编辑项目文件等于编辑用户级 skill。当 symlink 因权限受限失败时(如 Windows 无开发者模式),自动 fallback 到 copy。`~/.daily-brief-config` 记录项目实际路径,让 slash command 在任意 cwd 都能找到项目。
---
## 📁 项目结构
```
daily-brief/
├── lib/
│ ├── sources/ # RSS / API / curl 抓取器;新加源在这里
│ ├── ai/ # 可插拔 LLM 后端 + 提示词(lib/ai/backends/ 下每个 backend)
│ ├── trading/ # Yahoo Finance + 技术指标
│ ├── output/ # 渲染层 (HTML / Markdown)
│ └── utils.ts # 小工具(todayKey / getReportTz)
├── scripts/
│ ├── _env.ts # dotenv 预加载(被 TS pipeline 入口第一个 import)
│ ├── daily.ts # 主管线
│ ├── dry-run.ts # 只抓取不调 LLM,验证数据源
│ ├── render.ts # 重渲染
│ ├── regen-*.ts # 局部重跑(trading / enrich)
│ ├── quota-report.ts # LLM 用量统计
│ ├── sources.ts # `npm run sources` — 列源 + 校验 JSON
│ ├── run-daily.mjs # OS 调度器调用的包装(含自动 deploy + open)
│ ├── open-report.mjs # 打开最新报告(跨平台)
│ ├── build-site.mjs # 生成 GH Pages 静态站 (index + archive)
│ ├── deploy.mjs # scp 到远端 nginx 服务器(可选)
│ ├── install.mjs # 注册定时任务(Win/Mac/Linux 自适应)
│ └── uninstall.mjs # 卸载
├── sources.config.json # 数据源唯一配置入口
├── daily_reports/ # 输出 (gitignored)
│ └── 2026-05-15/ # 每日一个子目录,内含 .html (主) / .json (缓存) / -articles.json (缓存)
│ # .md 默认不生成,可在 .env.local 设 OUTPUT_MARKDOWN=true 开启
├── logs/ # 运行日志 (gitignored)
├── .github/workflows/ # GitHub Actions 工作流(A 方式部署)
└── .claude/
├── skills/ # Claude Code 操作 skill
└── commands/ # slash commands
```
---
## 🗑️ 卸载
```bash
node scripts/uninstall.mjs
# 移除:定时任务 (Task Scheduler / launchd / cron) + ~/.claude/ 下的链接 + ~/.daily-brief-config
# 不动:项目文件、daily_reports/、logs/、power plan 设置
# 想彻底清理就 rm -rf 整个项目目录
```
---
## 🛠️ 自定义 / Fork
改源、改时间、改排版、加新栏目 —— 见 [FORKING.md](FORKING.md)。
---
## 🙏 致谢
本项目在 [LINUX DO](https://linux.do) 开源社区分享推广,感谢佬友们的反馈与建议。
## 📝 License
MIT
---
# 📰 daily-brief · your own AI-curated daily news brief in 10 minutes
[↑ 中文](#zh) · **English**
[](LICENSE)
[](https://nodejs.org/)
[](https://www.typescriptlang.org/)
[-orange.svg)](#-llm-backend-configuration)
[](#a-github-actions--pages-zero-infra-recommended)
[](https://leiting-eric.github.io/DailyBrief)
[](https://github.com/leiting-eric/DailyBrief)
> **Your own AI-curated daily news brief, on infrastructure you control.** 26 sources enabled by default · LLM summaries · 21-ticker market panel with SMA/RSI/MACD signals + AI commentary · bilingual (zh/en) · 6 swappable LLM backends.
>
> **Three deployment paths, pick one:** [**🚀 5-min GitHub Actions fork**](#a-github-actions--pages-zero-infra-recommended) · [**💻 local one-liner install**](#b-local-one-liner-install) · [**🤖 have an AI agent install it for you**](#c-have-an-ai-agent-install-it-for-you).
**🌐 Live demos** —
[📰 leiting-eric.github.io/DailyBrief](https://leiting-eric.github.io/DailyBrief) (path A · GitHub Actions + Pages)
·
[📰 daily.leiting.tech](https://daily.leiting.tech) (path B · self-hosted server)
---
## ✨ Core features
- **🌍 Multi-source aggregation** — 26 sources enabled by default, spanning Silicon Valley tech, AI frontier, global finance, international politics, and developer communities. One report covers it all.
- **📈 21 live tickers** — US stocks / crypto / HK / commodities / macro signals, with SMA / RSI / MACD indicators + daily LLM-written trading commentary
- **🤖 6 swappable LLM backends** — Claude CLI / Anthropic / OpenAI / DeepSeek / MiniMax / Zhipu. One env var to switch, no vendor lock-in.
- **🌐 Bilingual (zh/en)** — set `REPORT_LOCALE=en` to flip the entire stack: sources, prompts, UI text, Bullish/Bearish stance labels — all switch.
- **🚀 Flexible deployment** — GitHub Actions (zero infra) / local OS scheduler / self-hosted server — pick one or run them in parallel
- **🆓 Zero data-source API keys** — every source uses free public endpoints (RSS / public JSON), no paid subscriptions
- **🔒 No third-party reader lock-in** — Feedly / Inoreader / Pocket all profile your reading; this project never talks to them. Your reading habits stay yours.
- **📁 Single-file HTML output** — CSS and JS inlined, no external dependencies, scp'able onto a server as `index.html`
---
## 📚 Source roster
The registry currently contains 53 sources, with 26 enabled by default. After locale filtering, zh mode uses 24 active sources and en mode uses 23:
### 🧑💻 Tech

- **GitHub Trending** · refreshed daily
- **AI media** (merged): OpenAI / DeepMind / Hugging Face Blog / TLDR AI / Smol AI / Latent Space / MIT Tech Review
- **X posts** (attentionvc-ai): curated AI thought-leader feed
### 📈 Markets (21 tickers, not news)

- **US stocks / ETFs**: SPY / QQQ / AAPL / MSFT / NVDA / GOOGL / TSLA / META
- **Crypto**: BTC / ETH / SOL (+ alt.me Fear & Greed index + CoinGecko macro)
- **China / HK**: BABA / PDD / JD / 0700.HK
- **Commodities / FX**: GC=F (gold) / CL=F (WTI crude) / USDCNY=X
- **Macro signals**: ^VIX / ^TNX (10Y Treasury) / DXY
### 🌍 World

**BBC / Guardian / NYT / NPR / DW Chinese / Al Jazeera / The Diplomat** — 7 major international outlets' World feeds
### 💰 Finance

**Bloomberg / WSJ / FT / BBC Business / Economist** — 5 global finance heavyweights
### 💬 Community
- **zh mode**: V2EX top threads / LinuxDo trending
- **en mode**: Hacker News / Reddit r/stocks (auto-substituted)
> Full list + enabled status: run `npm run sources`. To edit sources, modify [`sources.config.json`](sources.config.json).
---
## 🚀 Pick one deployment path
| Path | Who it's for | What you need | Setup time |
|---|---|---|---|
| **A. GitHub Actions + Pages** | No server, don't want to keep a laptop running | One API key (Anthropic / OpenAI / DeepSeek / MiniMax / Zhipu) | ~5 min (recommended) |
| **B. Local one-liner** | Have an always-on machine; want it cheapest | Node 20+, optionally Claude Code login | ~3 min |
| **C. Have an AI agent install it** | Lazy; want Cursor / Codex / Claude Code to handle setup | Same as B | One sentence |
### A. GitHub Actions + Pages (zero-infra, recommended)
1. **Fork this repo** (Fork button, top-right of GitHub)
2. **Settings → Actions → General → Workflow permissions** → set to **Read and write permissions**
3. **Settings → Pages → Build and deployment → Source** → "Deploy from a branch" → branch `gh-pages` / path `/ (root)` (the `gh-pages` branch only exists after the first successful workflow run — configure secrets first, trigger once, then come back)
4. **🔑 Configure the LLM backend** — this is the critical step. Each backend needs **a secret AND the matching `LLM_BACKEND` variable** (not just the secret). Pick one row:
> ⚠️ **Most common mistake**: adding only the secret (e.g. `DEEPSEEK_API_KEY`) and forgetting the `LLM_BACKEND` variable. The workflow defaults `LLM_BACKEND` to `anthropic`, so whatever key you set, the run will crash with `ANTHROPIC_API_KEY required`. Both pieces are required.
| You want | Secret to add | `LLM_BACKEND` variable | Rough cost |
|---|---|---|---|
| 🟣 **Anthropic Sonnet** (default; prompts tuned for it) | `ANTHROPIC_API_KEY` | leave unset or `anthropic` | ~$0.03-0.05/day, <$2/month |
| 🐋 **DeepSeek** (cheap, China-friendly) | `DEEPSEEK_API_KEY` | `deepseek` | ~$0.01-0.02/day, <$1/month |
| 🟢 **OpenAI** | `OPENAI_API_KEY` | `openai` | gpt-4o-mini ~$0.02/day |
| 🔵 **MiniMax** | `MINIMAX_API_KEY` | `minimax` | Similar to DeepSeek |
| 🧠 **Zhipu** | `ZHIPU_API_KEY` | `zhipu` | Depends on provider |
| 🌀 **Proxy / aggregator / any OpenAI-compatible service** (Moonshot, SiliconFlow, OpenRouter, self-hosted Claude relay, local Ollama / LM Studio, ...) | `LLM_API_KEY` | `openai` (use `anthropic` only if the proxy speaks Anthropic protocol — rare) | Depends on provider |
Location: **Settings → Secrets and variables → Actions**. The page has two tabs — **Secrets** for keys, **Variables** for `LLM_BACKEND`.
> 🌀 **Extras if you picked the last "proxy" row**: also add `LLM_BASE_URL` (the endpoint your proxy gave you, e.g. `https://api.moonshot.cn/v1`) and `LLM_MODEL` (e.g. `moonshot-v1-8k`) under Variables. Not sure which protocol? Default `LLM_BACKEND=openai` — covers 95% of proxies; switch to `anthropic` only if you get 404s or protocol errors.
5. (Optional) On the same Variables tab, add:
- `LLM_MODEL` — override the backend's default model (otherwise uses the default listed in [`.env.example`](.env.example))
- `LLM_BASE_URL` — custom endpoint. **Required if you picked the "proxy" row above.** For local Ollama use `http://localhost:11434/v1`, LM Studio `http://localhost:1234/v1`
- `REPORT_LOCALE` — `zh` (default) or `en` — switches sources + UI + LLM prompts as a set
- `REPORT_TZ` — IANA timezone name (default UTC); e.g. `Asia/Shanghai` / `America/Los_Angeles`. **Drives both the trigger time and the date label.**
- `REPORT_HOUR` — hour(s) to fire in `REPORT_TZ`, default `8` (08:00). Comma-separated for multiple, e.g. `8,18` = 8 AM and 6 PM
- `REPORT_DAYS` — day-of-week filter (cron-style, `0`=Sunday ... `6`=Saturday), default `*` (every day). E.g. `1-5` = weekdays; `1,3,5` = Mon/Wed/Fri
6. **Actions tab → "Daily Brief" workflow → Run workflow** to trigger manually for the first time
Once the workflow turns green, your report lives at `https://.github.io//`. After that, **it refreshes daily at 08:00 in `REPORT_TZ`** (or 08:00 UTC if `REPORT_TZ` is unset).
> ⏰ **How the schedule works**: GitHub Actions cron is UTC-only, so the workflow probes **twice per hour** and uses a `gate` job to check if the current hour in `REPORT_TZ` matches `REPORT_HOUR` / `REPORT_DAYS`. If so, the build job proceeds; otherwise it exits in seconds. This lets the schedule track any local timezone precisely, and **handles DST transitions automatically** (via the IANA tz database).
**Common schedule recipes:**
| You want | `REPORT_HOUR` | `REPORT_DAYS` |
|---|---|---|
| Every day at 08:00 (default) | unset or `8` | unset or `*` |
| Twice daily (8 AM + 6 PM) | `8,18` | `*` |
| Weekdays at 09:00 | `9` | `1-5` |
| Mon/Wed/Fri at 7 AM + 9 PM | `7,21` | `1,3,5` |
| Every 6 hours | `0,6,12,18` | `*` |
If you just want the default (08:00 local daily), **set only `REPORT_TZ`** (e.g. `Asia/Shanghai`) and leave the rest at defaults.
**💸 Cost summary**: GitHub Actions on public repos is free. Pages on public repos is free. The only thing you pay for is LLM API calls — DeepSeek runs under $1/month, Anthropic Sonnet under $2.
> ⚠️ **GH Actions mode can't reuse a local `claude` CLI login** — your Claude Code OAuth token lives on your machine, GitHub's runners can't see it. If you have a Max subscription, run both paths side by side: path B locally (uses Claude CLI), path A on GitHub Actions (uses DeepSeek). Independent reports, no interference.
#### 🐛 Common gotchas
- **"Upgrade or make this repository public to enable Pages"** — Free-tier GitHub Pages requires a public repo. Settings → General → Danger Zone → Change visibility → Public. Your Actions Secrets remain encrypted and invisible to others even on public repos.
- **"Variable name can only contain alphanumeric characters"** — most likely the underscore in `LLM_BACKEND` got autocorrected by a CJK input method to a full-width `_` (U+FF3F). Switch to English input, retype Shift+`-`, or copy-paste.
- **Pages source dropdown doesn't show `gh-pages`** — that branch only exists after the first successful workflow run. Order: configure secret → trigger workflow → wait for green → go back to Settings → Pages.
- **Where to read a failed run** — Actions tab → click the red X → left sidebar lists each step → click the failing one to expand its log. Most common causes: `401`/`402` (API key wrong or out of credit), `403` (workflow permissions still set to "Read only").
- **Scheduled runs only execute `gate`, then build is skipped** — this is the schedule gate doing its job, not necessarily a failure. Open that run → `gate` → `Check schedule`, then inspect `Now in ...`, `Configured REPORT_HOUR=...`, and `No match — skipping (...)`. The most common cause is missing `REPORT_TZ=Asia/Shanghai` in **Repository Variables**, so the default is UTC 08:00 (16:00 in Beijing). Variables stored under **Settings → Environments** are invisible to the default workflow; move them to **Settings → Secrets and variables → Actions → Variables**, or add the same `environment: ` to both the `gate` and `build` jobs.
- **Error: `ANTHROPIC_API_KEY (or generic LLM_API_KEY) is required` — but I set a different provider's key** — classic secret-without-variable mismatch. The workflow defaults `LLM_BACKEND=anthropic`; setting `DEEPSEEK_API_KEY` / `OPENAI_API_KEY` alone is not enough — you **also need to add the matching `LLM_BACKEND=deepseek` / `openai` under Variables**. As of v1.x the startup check prints exactly which key it found and which `LLM_BACKEND` value to set.
- **I added both the secret and variable, still the same error** — 99% sure your values went into the wrong scope. GitHub has two near-identical-looking pages:
- ✅ **Settings → Secrets and variables → Actions** (header reads "Repository secrets" / "Repository variables") — this is the default this project uses
- ⚠️ **Settings → Environments → \** (header reads "Environment secrets" / "Environment variables") — values here are **only injected when a workflow job declares `environment: `**; the default workflow doesn't, so anything stored here is invisible at runtime
Open your config page and check the section header. If it says "Environment ..." you have **two fixes**, pick one:
- **Simple fix (recommended)**: move every secret **and** variable from Environment scope to Repository scope (Settings sidebar → "Secrets and variables → Actions" — **don't enter via "Environments"**). No workflow edit needed. **Watch out**: secrets like `DEEPSEEK_API_KEY` / `ANTHROPIC_API_KEY` hide their values, so it's easy to "move" only the visible variables and forget the secrets — then you trade the old error for a new one ("key is not set"). Move both, completely.
- **Keep-the-environment fix**: edit `.github/workflows/daily.yml` and add `environment: ` to BOTH the `gate` and `build` jobs (both, because `gate` reads `vars.REPORT_TZ` etc. too). The name must exactly match the environment you created under Settings → Environments. Useful if you want environment-based approval gates or branch restrictions.
- **Fails after ~30s (other cases)** — LLM API returned 400, the key has no credit, or quota is exhausted. Check the "Generate today's report" step's log.
### B. Local one-liner install
```bash
# Linux / macOS
curl -sSL https://raw.githubusercontent.com/leiting-eric/DailyBrief/main/bootstrap.mjs | node
# Windows PowerShell
irm https://raw.githubusercontent.com/leiting-eric/DailyBrief/main/bootstrap.mjs | node -
```
This script will:
1. Check that Node / git / claude CLI are on PATH (claude CLI missing is a warning, not an error — you can use an API backend instead)
2. `git clone` to `~/daily-brief` (Windows: `%USERPROFILE%\daily-brief`)
3. `npm install`
4. Register the OS scheduler (Windows Task Scheduler / macOS launchd / Linux cron, default 08:00 local time)
5. Write `~/.daily-brief-config` recording the project path
6. Symlink the Claude Code skill + slash commands into `~/.claude/` so they work from any directory
7. Run `npm run dry-run` as a smoke test
**Claude Code integration**: after installing with `--global`, Claude Code can use `/run-daily` and `/check-daily` from any directory, and plain-language troubleshooting prompts can load the bundled `daily-brief` skill. Other agents such as Cursor and Codex do not use this skill/command mechanism, but the OS scheduled task still works. Manual triggers:
| Platform | Command |
|---|---|
| Windows | `Start-ScheduledTask -TaskName DailyBrief` |
| macOS | `launchctl start com.daily-brief` |
| Linux | `node scripts/run-daily.mjs` (cron doesn't support manual trigger) |
Custom install path / time:
```bash
node bootstrap.mjs --target /custom/path --at 07:30
```
**LLM backend**: defaults to the local `claude` CLI (first time you'll need to log in once in a browser: `echo "hi" | claude --print --model sonnet`; it usually stays valid for a long time, but rerun `claude` login if you see 401 / authentication failures). To skip Claude Code, change the model, switch providers, or point at a proxy — edit `.env.local`. Copy-paste recipes are in [🤖 LLM backend configuration](#-llm-backend-configuration) below.
### C. Have an AI agent install it for you
Whichever AI agent you use (Claude Code / Cursor / Codex / Continue.dev / OpenClaw / etc.), send it this prompt:
> Please install this open-source project following the README's "local one-liner" path with bootstrap, and tell me when the next auto-trigger will fire:
> https://github.com/leiting-eric/DailyBrief
The repo includes [`AGENTS.md`](AGENTS.md) (universal agent protocol) and [`.claude/skills/daily-brief/SKILL.md`](.claude/skills/daily-brief/SKILL.md) (Claude Code-specific, more detailed). After install, the agent can help diagnose things like "today's report didn't come out" or "add a new source".
---
## 📋 Requirements
- **Node.js 20+**, **npm**, **git** (local for paths B/C; path A runs in GitHub's containers — no local install needed)
- **One working LLM** (any of): Claude Code CLI logged in, OR Anthropic / OpenAI / DeepSeek / MiniMax API key
- Platform: Windows 10/11, macOS 12+, Linux (any platform — scheduler picks the matching mechanism)
---
## 🔧 Manual install
```bash
# 1. Clone + dependencies
git clone https://github.com/leiting-eric/DailyBrief.git
cd DailyBrief
npm install
# 2. Pick an LLM backend
# Default = claude CLI (will guide you through login if not done):
echo "say hi" | claude --print --model sonnet
# Or use a different backend: cp .env.example .env.local
# and set LLM_BACKEND + the matching API key
# 3. Register the scheduler + enable the global skill
node scripts/install.mjs --global
# 4. Test trigger immediately
# Windows: Start-ScheduledTask -TaskName DailyBrief
# macOS: launchctl start com.daily-brief
# Linux: node scripts/run-daily.mjs
```
Sleep-wake behavior at next trigger time:
- **🪟 Windows** — wakes the computer if asleep, runs, returns to sleep
- **🍎 macOS** — launchd doesn't wake from deep sleep; skipped if asleep (configure `pmset wake schedule` separately if needed)
- **🐧 Linux** — cron doesn't wake either; skipped if suspended
---
## 🛠️ Daily commands
| Command | Purpose | Time |
|---|---|---|
| `npm run daily` | Full pipeline (fetch + LLM + render) | 5-8 min |
| `npm run dry-run` | Fetch only, no LLM — validates sources | ~30s |
| `npm run render [date]` | Re-render HTML after editing CSS/layout | <1s |
| `npm run regen-trading [date]` | Re-do the trading section only | ~2 min |
| `npm run regen-enrich [date]` | Fill in missing summaries for a subgroup | ~30s |
| `npm run open` | Open today's report in Chrome | instant |
| `npm run quota-report` | Per-backend LLM usage summary | instant |
| `npm run sources` | List all sources with locale / enabled status | instant |
| `npm run sources:check` | Validate `sources.config.json` schema (good for CI / pre-commit) | instant |
---
## 📊 Source configuration
Sources live as a JSON array in [`sources.config.json`](sources.config.json) at the project root — **the single source of truth**. Add / disable / re-categorize feeds without touching TypeScript. Per-entry fields:
| Field | Required | Notes |
|---|---|---|
| `id` | ✓ | Short unique identifier (used by `dispatch.ts` to route to the right fetcher) |
| `name` | ✓ | Display name in the UI |
| `type` | ✓ | `rss` / `api` / `scrape` |
| `url` | ✓ | RSS feed URL or API endpoint |
| `category` | ✓ | `tech` / `finance` / `politics` — drives the L1 tab |
| `subcategory` | | L2 grouping (`github-trending` / `ai-news` / `x-viral` / `cn-community` for tech; `news` for finance) |
| `enabled` | | Default `true`; set to `false` to skip without deleting the record |
| `useCurl` | | `true` if the source's host blocks Node's TLS fingerprint (Cloudflare); the fetcher will shell out to curl |
| `lang` | | `zh` means the source is already in Chinese — enrich skips it (no need to translate Chinese into Chinese) |
| `locales` | | Array listing which `REPORT_LOCALE` the source appears in. Default `["zh", "en"]` |
| `notes` | | Free-form (e.g. "removed because feed died"); ignored at runtime |
### Adding an RSS source
1. Append an entry to `sources.config.json`
2. Run `npm run sources:check` to validate schema
3. `npm run dry-run` to confirm the fetch works
4. The next `npm run daily` picks it up automatically
### 🌐 Locale mode (zh / en)
Switch with the `REPORT_LOCALE` environment variable:
```bash
# .env.local
REPORT_LOCALE=zh # default — Chinese mode, includes V2EX / LinuxDo / DW Chinese
# REPORT_LOCALE=en # English mode — drops zh-only sources, picks up en-only ones
```
Each source's `locales` field decides which mode it appears in:
- `["zh"]` — **zh mode only** (V2EX / LinuxDo / DW Chinese). English readers can't read these, so auto-dropped in en mode.
- `["en"]` — **en mode only** (Hacker News / r/stocks etc., picked up to replace Chinese community sources). Not shown in zh mode.
- `["zh", "en"]` (default) — appears in both modes (BBC / Bloomberg / WSJ / NYT / AI media)
Currently enabled sources by locale:
| Locale | Enabled sources | Mix |
|---|---|---|
| `zh` | 24 | 21 global / English sources (with LLM-generated Chinese summaries) + 3 Chinese-only (V2EX / LinuxDo / DW Chinese) |
| `en` | 23 | 21 global / English sources + 2 English community (Hacker News + r/stocks) |
The full en-mode switch covers: HTML UI text, the three LLM prompt sets (enrichment / digest / trading commentary), stance labels (Bullish/Bearish/Neutral vs. 偏上行/偏下行/中性), date format (`zh-CN` ↔ `en-GB`), Markdown output. **Chinese community sources are filtered out in en mode and replaced by en-only community sources** such as Hacker News and r/stocks.
---
## 🤖 LLM backend configuration
The project switches backends via the `LLM_BACKEND` environment variable. **Default is `claude-cli`** — it reuses your existing Claude Code login, no API key needed. To use your own API key with another provider, set up `.env.local`:
### Where the config lives
**Local install**: everything goes in `.env.local` at the repo root (gitignored — safe place for keys). First time? `cp .env.example .env.local`, open it in your editor, uncomment the rows you need. Changes take effect on next run, no shell restart — these TS pipeline entry scripts (`npm run daily` / `regen-trading` / `regen-enrich`) dotenv-load the file on their first line.
**GitHub Actions deploy**: no `.env.local` needed — set values under Settings → Secrets and variables → Actions instead (see [Path A](#a-github-actions--pages-zero-infra-recommended) step 4 above).
### Supported backends
| backend | API key env var | Default model | Base URL |
|---|---|---|---|
| 🎯 `claude-cli` (default) | None — reuses Claude Code OAuth | `sonnet` | — |
| 🟣 `anthropic` | `ANTHROPIC_API_KEY` | `claude-sonnet-4-6` | `api.anthropic.com` |
| 🟢 `openai` | `OPENAI_API_KEY` | `gpt-4o-mini` | `api.openai.com/v1` |
| 🐋 `deepseek` | `DEEPSEEK_API_KEY` | `deepseek-v4-flash` | `api.deepseek.com/v1` |
| 🔵 `minimax` | `MINIMAX_API_KEY` | `MiniMax-M2.7` | `api.minimax.io/v1` 1 |
| 🧠 `zhipu` | `ZHIPU_API_KEY` | `claude-sonnet-4-6` | `open.bigmodel.cn/api/anthropic` |
1 Inside mainland China, set `MINIMAX_BASE_URL=https://api.minimaxi.com/v1`.
### Minimal `.env.local` recipes (pick one and copy)
```bash
# Default: reuse local claude CLI (best deal if you have a Max subscription)
# Leave .env.local empty — done.
# Anthropic API
LLM_BACKEND=anthropic
ANTHROPIC_API_KEY=sk-ant-...
# LLM_MODEL=claude-sonnet-4-6 # optional — this is the default
# DeepSeek (cheap, strong on Chinese)
LLM_BACKEND=deepseek
DEEPSEEK_API_KEY=sk-...
# LLM_MODEL=deepseek-v4-flash # optional
# OpenAI
LLM_BACKEND=openai
OPENAI_API_KEY=sk-...
# LLM_MODEL=gpt-4o-mini # optional; use gpt-4o / gpt-4.1 for bigger
# MiniMax
LLM_BACKEND=minimax
MINIMAX_API_KEY=...
# MINIMAX_BASE_URL=https://api.minimaxi.com/v1 # use this from mainland China
# Zhipu (Anthropic-compatible protocol)
LLM_BACKEND=zhipu
ZHIPU_API_KEY=...
# ZHIPU_BASE_URL=https://open.bigmodel.cn/api/anthropic
# Proxy / aggregator / OpenAI-compatible service (Moonshot / SiliconFlow / OpenRouter / Ollama / LM Studio …)
LLM_BACKEND=openai # most proxies speak OpenAI protocol
LLM_API_KEY=
LLM_BASE_URL=https://api.moonshot.cn/v1
LLM_MODEL=moonshot-v1-8k # always explicit for proxies / self-hosted
# Anthropic-protocol relay (claude-relay etc.)
LLM_BACKEND=anthropic
ANTHROPIC_API_KEY= # or use LLM_API_KEY
ANTHROPIC_BASE_URL=https://your-relay/ # or use LLM_BASE_URL
```
### Universal overrides
- `LLM_MODEL=` — works for any backend (e.g. `LLM_MODEL=gpt-4o` to use OpenAI's bigger model)
- `_BASE_URL` — for self-hosted proxies or OpenAI-compatible services (e.g. LM Studio / Ollama → `LLM_BACKEND=openai` + `OPENAI_BASE_URL=http://localhost:1234/v1`)
- `LLM_API_KEY` / `LLM_BASE_URL` — **generic aliases**, used as fallback when the provider-specific vars aren't set. Use this pair for Moonshot / SiliconFlow / OpenRouter / self-hosted proxies — anything not in the preset list — so you don't have to misuse `OPENAI_API_KEY` just to reach a non-OpenAI service
### Verify the config is good
Don't want to wait 5-8 min for a full `npm run daily` just to learn your key is wrong? This 30-second, 1-LLM-call command tells you fast:
```bash
npm run regen-enrich -- finance:news
```
Working: `[regen-enrich] enrichment done in 28s, matched 12/12`. Broken: the startup check stops it within 1 second and prints exactly what to fix (which key is missing, which `LLM_BACKEND` value to use).
### How to pick
| Your situation | Recommended backend |
|---|---|
| Already using Claude Code (any subscription tier) | `claude-cli` — zero config, billed against your subscription |
| Not on Claude Code, want it cheapest | `openai` with `gpt-4o-mini`, or `deepseek` (cheaper still) |
| Chinese summary quality matters most | `anthropic` with `claude-sonnet-4-6` |
| Need to bypass China's network restrictions | `deepseek`, `minimax`, or `zhipu` (domestic providers) |
**Switching backends needs no code changes**: all prompts are factored out in `lib/ai/prompts.ts` independently of any backend; JSON repair fallback (`jsonrepair`) is backend-agnostic. After switching, run `npm run daily` once and look at `logs/llm-calls.jsonl` for the new backend's call log.
---
## 🌐 Self-hosted deployment (optional)
After each `npm run daily`, automatically scp the fresh HTML to your own server; visitors hit `https://your-domain/` and see today's report. **Disabled by default** — leave the env vars unset to skip.
### One-time server setup
Assumes Ubuntu + nginx, domain configured, login user has sudo NOPASSWD:
```bash
# On the server
sudo mkdir -p /var/www/your-domain && sudo chown -R www-data:www-data /var/www/your-domain
```
`/etc/nginx/sites-available/your-domain.conf`:
```nginx
server {
listen 80;
server_name your-domain;
root /var/www/your-domain;
index index.html;
location / { try_files $uri $uri/ =404; }
}
```
Enable + auto-issue SSL:
```bash
sudo ln -s /etc/nginx/sites-available/your-domain.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d your-domain --agree-tos --redirect
```
### Enable auto-deploy locally
In `.env.local` (gitignored), add:
```
DEPLOY_HOST=user@your-server-ip
DEPLOY_PATH=/var/www/your-domain
```
Then:
- ✅ Every `npm run daily` auto-scp's the new HTML to the server and refreshes `index.html`
- ✅ `npm run deploy [YYYY-MM-DD]` to push any specific date manually
- ✅ A deploy failure doesn't break the daily run itself (HTML is already on disk in `daily_reports/`; `npm run deploy` retries it)
---
## 💡 Claude Code integration
**After install, any directory** (no need to `cd` into the project) running Claude Code can use:
| Trigger | Behavior |
|---|---|
| `/run-daily` | Triggers daily immediately, monitors in the background until done. Works from any directory. |
| `/check-daily` | Checks task state + report files + quota |
| Describing a problem ("today's report didn't come out", "why didn't X posts update") | Auto-loads the `daily-brief` skill so Claude understands the project context |
**How it works**: `scripts/install.mjs --global` symlinks files in `~/.claude/` pointing at the project's [`.claude/skills/daily-brief/SKILL.md`](.claude/skills/daily-brief/SKILL.md) and [`.claude/commands/`](.claude/commands/) — **single source**, editing the project files is editing the user-level skill. If symlinks aren't permitted (Windows without Developer Mode), it falls back to copying. `~/.daily-brief-config` records the absolute project path so slash commands find it from any CWD.
---
## 📁 Project structure
```
daily-brief/
├── lib/
│ ├── sources/ # RSS / API / curl fetchers; add new sources here
│ ├── ai/ # Pluggable LLM backends + prompts (lib/ai/backends/ per backend)
│ ├── trading/ # Yahoo Finance + technical indicators
│ ├── output/ # Rendering (HTML / Markdown)
│ └── utils.ts # Tiny shared helpers (todayKey / getReportTz)
├── scripts/
│ ├── _env.ts # dotenv preload — imported FIRST by TS pipeline entry scripts
│ ├── daily.ts # Main pipeline
│ ├── dry-run.ts # Fetch-only validation (no LLM)
│ ├── render.ts # Re-render from cached data
│ ├── regen-*.ts # Targeted re-runs (trading / enrich)
│ ├── quota-report.ts # LLM usage stats
│ ├── sources.ts # `npm run sources` — list + validate sources.config.json
│ ├── run-daily.mjs # OS scheduler wrapper (daily + log + deploy + open)
│ ├── open-report.mjs # Cross-platform "open latest" helper
│ ├── build-site.mjs # GH Pages static-site generator (index + archive)
│ ├── deploy.mjs # scp HTML to a remote nginx host (opt-in)
│ ├── install.mjs # Cross-platform scheduler registration
│ └── uninstall.mjs # Removal
├── sources.config.json # Single source of truth for the source registry
├── daily_reports/ # Output (gitignored)
│ └── 2026-05-15/ # One subdir per day, contains .html (main) / .json (cache) / -articles.json (cache)
│ # .md not generated by default; set OUTPUT_MARKDOWN=true in .env.local to enable
├── logs/ # Run logs (gitignored)
├── .github/workflows/ # GitHub Actions workflow (path A deployment)
└── .claude/
├── skills/ # Claude Code operational skill
└── commands/ # Slash commands
```
---
## 🗑️ Uninstall
```bash
node scripts/uninstall.mjs
# Removes: scheduled task (Task Scheduler / launchd / cron) + ~/.claude/ symlinks + ~/.daily-brief-config
# Leaves alone: project files, daily_reports/, logs/, power plan settings
# For a full cleanup: rm -rf the project directory
```
---
## 🛠️ Customize / Fork
Change sources, schedule, layout, add new panels — see [FORKING.md](FORKING.md).
---
## 🙏 Acknowledgments
This project is shared on the [LINUX DO](https://linux.do) open-source community. Thanks to the community members for feedback and suggestions.
## 📝 License
MIT