$npx -y skills add leiting-eric/DailyBrief --skill daily-briefOperational knowledge for the daily-brief digest pipeline (this project). RSS/API fetchers, pluggable LLM enrichment (default claude CLI on Max; also anthropic/openai/deepseek/minimax API), trading section, HTML rendering, cross-platform scheduler integration (Windows Task Schedu
| 1 | # daily-brief — Operational Skill |
| 2 | |
| 3 | This project generates a single-page HTML daily digest covering tech / finance / politics / market data / community discussion. The pipeline runs locally via the OS scheduler (Windows Task Scheduler / macOS launchd / Linux cron, default 08:00 local time) and emits `daily_reports/<YYYY-MM-DD>/<YYYY-MM-DD>.html` + sidecar files (each date gets its own subdir). The date label uses the system local timezone by default — set `REPORT_TZ` (e.g. `Asia/Shanghai`, `UTC`) in `.env.local` to override. |
| 4 | |
| 5 | Detailed architecture lives in code; this skill is a cheat sheet for **operating** and **diagnosing**, not a re-explanation of the system. |
| 6 | |
| 7 | ## Project root assumption |
| 8 | |
| 9 | All paths in this skill are **relative to the project root** (the directory that contains `package.json`, `lib/`, `scripts/`). |
| 10 | |
| 11 | **Before any command, ensure the working directory is the project root.** Two cases: |
| 12 | |
| 13 | 1. **Claude Code session opened inside the project** — already there, no action needed |
| 14 | 2. **Session opened elsewhere** — read the config file and `cd`: |
| 15 | |
| 16 | ```bash |
| 17 | # Cross-platform Node one-liner (prints the project root path): |
| 18 | node -e "const fs=require('fs'),os=require('os'),path=require('path');const cfg=path.join(os.homedir(),'.daily-brief-config');if(fs.existsSync(cfg))console.log(fs.readFileSync(cfg,'utf8').trim());else process.exit(1)" |
| 19 | ``` |
| 20 | |
| 21 | Use the printed path: `cd "$(...)"` on bash / `Set-Location (...)` in PowerShell. |
| 22 | |
| 23 | The config file is written by `node scripts/install.mjs --global`. If it's missing the user hasn't done a global install — tell them to run it. |
| 24 | |
| 25 | ## Quick command reference |
| 26 | |
| 27 | | Need | Command | Cost | |
| 28 | |---|---|---| |
| 29 | | Full pipeline | `npm run daily` | ~5-8 min, ~6 Sonnet calls | |
| 30 | | Fetch sanity only (no LLM) | `npm run dry-run` | ~30s | |
| 31 | | Re-render existing sidecar | `npm run render [date]` | <1s | |
| 32 | | Re-run trading section | `npm run regen-trading [date]` | ~2 min, 1 LLM call | |
| 33 | | Top-up missing summary | `npm run regen-enrich <cat:sub> [date]` | ~20-40s, 1 LLM call | |
| 34 | | Open today's report in Chrome | `npm run open` | instant | |
| 35 | | Sonnet quota + call history | `npm run quota-report` | instant | |
| 36 | |
| 37 | `[date]` defaults to today's date in the report timezone (system local, or `REPORT_TZ` if set). The pipeline and the OS scheduler both run in local time, so the report's date label = the date when the trigger fired in the report timezone. A user with `REPORT_TZ=Asia/Shanghai` whose machine fires the trigger at 23:00 UTC-8 will get a "next-day Shanghai" file, e.g. `daily_reports/2026-05-17/2026-05-17.html`. |
| 38 | |
| 39 | `<cat:sub>` accepted by `regen-enrich`: `finance:news`, `politics:world`, `tech:ai-news`. Single-source X 推文 (`tech:x-viral`) is enriched as part of `daily` only — no top-up path. |
| 40 | |
| 41 | ## File map — where to change what |
| 42 | |
| 43 | | Task | File | |
| 44 | |---|---| |
| 45 | | Add / disable / re-categorize a source | `sources.config.json` (project root — single source of truth; `lib/sources/registry.ts` is just a loader) | |
| 46 | | Rename L1 tab labels | `lib/output/render.ts` `CATEGORY_LABELS` | |
| 47 | | Reorder / rename L2 subcategories | `SUBCATEGORY_ORDER` + `SUBCATEGORY_LABELS` in same file | |
| 48 | | Change per-source item cap | `SOURCE_DISPLAY_LIMITS` | |
| 49 | | Change merged-timeline cap | `MERGED_SUBGROUP_LIMITS` | |
| 50 | | Add a Sonnet enrichment prompt | `lib/ai/enrich.ts` — copy `XVIRAL_SYSTEM_PROMPT` pattern | |
| 51 | | Wire an enrichment into pipeline | `scripts/daily.ts` — `await enrichXxx(articles)` in `main()` | |
| 52 | | Add a new fetcher type | New file in `lib/sources/` + branch in `lib/sources/dispatch.ts` | |
| 53 | | Adjust HTML styling | inline `<style>` block in `renderHtml()` in `lib/output/render.ts` | |
| 54 | | Change scheduler trigger time | `node scripts/install.mjs --at HH:MM` (re-registers) | |
| 55 | | Wrapper script the scheduler invokes | `scripts/run-daily.mjs` | |
| 56 | |
| 57 | ## How LLM enrichment works (mental model) |
| 58 | |
| 59 | - **Each merged L2 subcategory gets a Sonnet pass**: GH-trending (per-source), finance:news, politics:world, tech:ai-news, tech:x-viral. |
| 60 | - Each pass = **one batched Sonnet call** for all items in that subgroup. Don't iterate per-item. |
| 61 | - Sources with `lang: "zh"` in registry **skip** enrichment (already Chinese). |
| 62 | - Failures are non-fatal: skipped articles just render without `summary`. |
| 63 | |
| 64 | ## Diagnostic flow |
| 65 | |
| 66 | Order matters — top-to-botto |