improve
Create a structured GitHub issue from a session reflection or improvement idea using the gh CLI. Use this skill when the user asks to create an issue from a…
Generate a personalized newspaper-style digest from any news sources. Use this skill whenever the user wants to read news, set up news sources, get a daily digest, curate articles, generate a newspaper, create a morning briefing, manage their reading preferences, or says things
$ npx -y skills add falense/openpaper --skill openpaper --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/openpaperContext preview
The summary Claude sees to decide when to auto-load this skill.
Generate a personalized newspaper-style digest from any news sources. Use this skill whenever the user wants to read news, set up news sources, get a daily digest, curate articles, generate a newspaper, create a morning briefing, manage their reading preferences, or says things
name: openpaper description: > Generate a personalized newspaper-style digest from any news sources. Use this skill whenever the user wants to read news, set up news sources, get a daily digest, curate articles, generate a newspaper, create a morning briefing, manage their reading preferences, or says things like "make my paper", "what's new today", "add a news source", "show me the news", or "update my preferences". Also use when the user mentions OpenPaper, newspaper layout, or news curation. This skill handles the full pipeline: adding sources (writing fetchers), curating content, and rendering beautiful HTML newspaper editions.
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.pyFor each source: 1. **Analyze** — visit the URL, determine type (RSS, HTML, JS-rendered, API) 2. **Write a fetcher** — create `.openpaper/sources/<name>.py`. Read `references/fetcher-guide.md` for the interface spec and `_base.py` templates. 3. **Test** — `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 pollutes `seen.txt` dedup. 4. **Show** the user what it found
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 .openpaperDiscovers 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:
1. **Inspect the pool:**
2. **Read** `.openpaper/preferences.md` 3. **Score and rank** articles against user interests 4. **Select** articles (typically 10-18), enforce topic/source diversity 5. **Assign editorial weight** — lead (1), major/lg (2-3), mid/md (3-5), briefs (4-8) 6. **Summarize in parallel** — spawn one subagent per article via `parallel()`:
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..."),
...
])7. **Assemble edition YAML** — see `references/edition-schema.md` for the schema
Write the YAML to `.openpaper/editions/draft.yaml`.
1. `mkdir -p .openpaper/saved/<date>-<edition_name>` 2. Move selected article JSONs to the archive 3. Delete remaining from `.openpaper/incoming/`
uv run --project ${CLAUDE_PLUGIN_ROOT:-.} skills/openpaper/scripts/render.py --data-dir .openpaper --edition .openpaper/editions/draft.yamlThen preview:
uv run --project ${CLAUDE_PLUGIN_ROOT:-.} skills/openpaper/scripts/serve.py --data-dir .openpaper --latestOpen in browser. Say: "Here's your paper — let me know what you think."
1. Visit the URL (screenshot or fetch) 2. Determine type: RSS, HTML, JS-rendered, API 3. Read `references/fetcher-guide.md` for the contract and `_base.py` templates 4. Write `.openpaper/sources/<name>.py` 5. Test with `/tmp/` cache dir (not the real one) 6. Show results to the user
**Key rules:**
A personalized newspaper that knows only you will ever read it. OpenPaper is a Claude Code plugin that turns any collection of news sources into a single-reader daily newspaper.
Repo: falense/openpaper