Help us improve
Share bugs, ideas, or general feedback.
From openpaper
Generates personalized newspaper-style digests from any news source. Handles ingestion, curation, and rendering of HTML newspapers.
How this skill is triggered — by the user, by Claude, or both
Slash command
/openpaper:openpaperThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A personalized newspaper that knows only you will ever read it.
Share bugs, ideas, or general feedback.
A personalized newspaper that knows only you will ever read it.
Three-stage pipeline: Ingest (fetch news) → Curate (select and rank) → Present (render a newspaper). You are the editor-in-chief.
| Command | What it does |
|---|---|
| "Make my paper" | Full pipeline: fetch → curate → render |
| "Add a source" | Analyze a URL and write a fetcher |
| "Show my preferences" | Display and edit preference profile |
All scripts use uv run --project. Use ${CLAUDE_PLUGIN_ROOT} if set (plugin mode), otherwise . (standalone mode):
uv run --project ${CLAUDE_PLUGIN_ROOT:-.} skills/openpaper/scripts/<script>.py <args>
All state lives in .openpaper/ relative to the working directory:
.openpaper/
├── sources/ # one .py per source (+ _base.py auto-deployed)
├── preferences.md # user interests and feedback
├── incoming/ # raw fetched articles (JSON)
├── saved/ # archived articles from past editions
├── editions/ # rendered HTML newspapers
├── cache/ # HTTP cache for fetchers
└── seen.txt # dedup log
When .openpaper/ doesn't exist:
mkdir -p .openpaper/sources .openpaper/incoming .openpaper/saved .openpaper/editions .openpaper/cache
Say: "What do you want to read? Give me URLs, RSS feeds, or just topics." Then wait for the user's reply.
Deploy the shared base module:
cp ${CLAUDE_PLUGIN_ROOT:-.}/skills/openpaper/scripts/fetcher_base.py .openpaper/sources/_base.py
For each source:
.openpaper/sources/<name>.py. Read references/fetcher-guide.md for the interface spec and _base.py templates.uv run .openpaper/sources/<name>.py --cache-dir /tmp/openpaper-test-<name>
Cache trap: Always test with
/tmp/, NOT.openpaper/cache/. Using the real cache pollutesseen.txtdedup.
Create .openpaper/preferences.md by chatting with the user about:
Keep under 2000 characters. Example:
# My Reading Preferences
## Interests
- Technology and AI (very interested)
- Software engineering (interested)
- Open source (some interest)
## Sources
Hacker News is my primary source.
## Reading Profile
~14 articles. Include weather for Oslo.
## Feedback
Run the full pipeline and open in browser.
uv run --project ${CLAUDE_PLUGIN_ROOT:-.} skills/openpaper/scripts/fetch_all.py --data-dir .openpaper
Discovers all fetchers in .openpaper/sources/, runs in parallel, deduplicates against seen.txt, fetches content, writes new articles to .openpaper/incoming/.
Gate: Only begin after Stage 1 completes and weather/markets data is collected. Curation needs the full candidate pool.
Read references/curation-guide.md for the full process. Summary:
uv run --project ${CLAUDE_PLUGIN_ROOT:-.} skills/openpaper/scripts/pool.py --data-dir .openpaper statsuv run --project ${CLAUDE_PLUGIN_ROOT:-.} skills/openpaper/scripts/pool.py --data-dir .openpaper list --sort pointsuv run --project ${CLAUDE_PLUGIN_ROOT:-.} skills/openpaper/scripts/pool.py --data-dir .openpaper show <slug>.openpaper/preferences.mdparallel():parallel([
() => agent("Summarize this lead article: <JSON>. Write 3-4 paragraphs, deck, photo_caption..."),
() => agent("Summarize this lg article: <JSON>. Write 2 paragraphs..."),
() => agent("Write a brief for: <JSON>. Return bold + one sentence..."),
...
])
references/edition-schema.md for the schemaWrite the YAML to .openpaper/editions/draft.yaml.
mkdir -p .openpaper/saved/<date>-<edition_name>.openpaper/incoming/uv run --project ${CLAUDE_PLUGIN_ROOT:-.} skills/openpaper/scripts/render.py --data-dir .openpaper --edition .openpaper/editions/draft.yaml
Then preview:
uv run --project ${CLAUDE_PLUGIN_ROOT:-.} skills/openpaper/scripts/serve.py --data-dir .openpaper --latest
Open in browser. Say: "Here's your paper — let me know what you think."
references/fetcher-guide.md for the contract and _base.py templates.openpaper/sources/<name>.py/tmp/ cache dir (not the real one)Key rules:
_base.py detect_paywall)After the user reads their paper, update .openpaper/preferences.md:
uv run --project ${CLAUDE_PLUGIN_ROOT:-.} skills/openpaper/fetchers/weather.py \
--lat 59.91 --lon 10.75 --location-name "Oslo, Norway" \
--cache-dir .openpaper/cache/weather
uv run --project ${CLAUDE_PLUGIN_ROOT:-.} skills/openpaper/fetchers/markets.py \
--cache-dir .openpaper/cache/markets
Default symbols: Oslo Børs, USD/NOK, S&P 500, Gold, Bitcoin, Brent. Override with --symbols and --names.
See references/edition-schema.md for the full schema. Key points:
section: "col1" (string), not column: 1 (integer)sm, md, lgurl field for the original sourcelg stories can include image_url — only from fetcher data, never fabricated| Script | Purpose | Key flags |
|---|---|---|
fetch_all.py | Run all source fetchers | --data-dir, --source, --parallel |
render.py | HTML from edition YAML | --data-dir, --edition, --output |
serve.py | Preview server | --data-dir, --port, --latest |
pool.py | Inspect article pool | --data-dir + subcommands: stats, list, show |
npx claudepluginhub falense/openpaperGenerates a topic-focused briefing in HTML from public news sources for any subject (region, industry, policy issue, institution, or theme). Outputs a single self-contained HTML file optimized for browser viewing and WeChat Official Account editor.
Scrapes preset URLs, filters high-quality technical content, and generates daily Markdown reports with multi-agent orchestration and browser scraping.