papers-skill
Skill for academic research workflows: search Semantic Scholar (200M+ papers), inspect citations, download arXiv PDFs, and extract PDF text. Bundles a self-contained Python CLI.
#research
Install
npx skills add https://github.com/sickn33/agentic-awesome-skills/tree/main/skills/papers-skill
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install sickn33-agentic-awesome-skills@llmmart
git clone https://github.com/sickn33/agentic-awesome-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole sickn33/agentic-awesome-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Papers Skill
Overview
Papers Skill turns a coding agent into a literature-research assistant. It
orchestrates a bundled Python CLI (scripts/papers.py) that hits the free
Semantic Scholar and arXiv APIs, downloads arXiv PDFs, and extracts text with
PyMuPDF. The agent decides which subcommand to invoke and how to combine
results into a literature scan, a deep read of one paper, an impact analysis,
or a reading list.
This skill is the Skill-mode port of the papers-mcp MCP server by the same author. Both projects share the same feature set; this one ships as a Claude Code plugin so it can be installed with a single command and needs no long-running MCP process.
When to Use This Skill
- Use when the user asks to search academic papers by topic, author, or venue.
- Use when the user names a specific paper (by DOI, arXiv ID, or title) and wants metadata, the abstract, the TL;DR, or its reference list.
- Use when the user wants to find work that cites a known paper (impact analysis, follow-up tracking).
- Use when the user wants to download an arXiv PDF and have it summarized.
- Use when the user asks to build a reading list around a topic.
Do Not Use This Skill When
- The user wants paywalled non-arXiv full text. This skill cannot bypass publisher paywalls; it can only fetch arXiv PDFs and metadata everywhere.
- The user wants OCR over scanned PDFs. PyMuPDF extracts embedded text only; scanned image-PDFs return the fallback message and need a separate OCR step.
- The user wants real-time citation alerts or RSS-style watching. This skill is request-driven.
How It Works
Step 1: Verify dependencies
Three Python packages are required. The skill should check once per session, using the same interpreter to import-check and install so the dependency check and install target stay in sync:
python -c "import httpx, arxiv, fitz" 2>&1 || python -m pip install httpx arxiv PyMuPDF
If python is not on PATH, fall back to py (Windows launcher) or the
absolute interpreter path — and remember to invoke pip via the same
interpreter, e.g. py -m pip install httpx arxiv PyMuPDF.
Step 2: Invoke the bundled CLI
The script lives at ${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py
and is bundled with this skill (no separate install needed). Always quote the
path so it survives spaces.
python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" <subcommand> [args]
Step 3: Pick the right subcommand
| Subcommand | Purpose | Example |
|---|---|---|
search <query> [--limit N] |
Semantic Scholar search, max 20 | search "diffusion models" --limit 5 |
detail <paper_id> |
Full metadata, TL;DR, top references | detail 10.48550/arXiv.2310.06825 |
citations <paper_id> [--limit N] |
Papers citing this one, max 20 | citations <id> --limit 15 |
arxiv <query> [--max-results N] |
arXiv preprint search, max 10 | arxiv "RLHF" --max-results 5 |
download <arxiv_id> [--save-dir D] |
Save PDF locally | download 2310.06825 --save-dir ./pdfs |
read <pdf_path> [--max-pages N] |
Extract PDF text via PyMuPDF | read ./pdfs/foo.pdf --max-pages 20 |
detail and citations auto-detect the ID type: DOIs starting with 10.
are used as-is, bare numeric IDs of 10+ digits are treated as arXiv IDs, and
long hex strings are treated as Semantic Scholar paperIds.
Examples
Example 1: Literature scan on a topic
python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" search "retrieval augmented generation" --limit 10
Present results as a ranked table with # | Title | Year | Citations | ID, then ask the user which papers to dig into.
Example 2: Deep-read one paper
# 1. Confirm match
python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" detail 2005.11401
# 2. Download
python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" download 2005.11401 --save-dir ./pdfs
# 3. Extract abstract + intro + conclusion
python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" read ./pdfs/2005.11401v4.RAG.pdf --max-pages 10
Summarize as: problem · method · key result · limitations.
Example 3: Impact analysis on an anchor paper
python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" detail 10.48550/arXiv.2005.11401
python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" citations 10.48550/arXiv.2005.11401 --limit 20
Cluster the citing papers by year/theme and highlight the most-cited follow-ups.
Best Practices
- ✅ Always call
detailbeforedownloadto confirm the paper matches user intent. Skipping this leads to wrong PDFs being fetched. - ✅ Include the paper ID alongside every title in your output so the user can re-query precisely.
- ✅ Cite as
[FirstAuthor et al., Year] *Title* (cites: N). - ✅ For PDFs you download, always report the absolute save path.
- ❌ Don't crawl. The script auto-retries 429s with exponential backoff; don't pile on parallel queries.
- ❌ Don't raise
--max-pagesto 100+ without warning the user — it can consume a large amount of context.
Limitations
- The skill cannot fetch full text from paywalled publishers (Elsevier, Springer, Wiley, etc.). It can only read open arXiv PDFs.
- PyMuPDF extracts embedded text only. Scanned image-PDFs return the
fallback message
PDF无法提取文本(可能是扫描件); offer the user an alternative version or note that OCR is required. - Semantic Scholar's anonymous tier rate-limits aggressively. The script
retries 3× with exponential backoff; persistent 429s during heavy use
surface as
搜索失败: rate limit, retries exhausted. - This skill does not replace environment-specific validation, testing, or expert review. Stop and ask for clarification if required inputs are missing.
Security & Safety Notes
- The CLI performs outbound HTTPS only to
api.semanticscholar.organdarxiv.org(and the arXiv-listed mirror for the bundledarxivpackage). No authentication tokens are sent. downloadwrites a PDF to the directory the user specifies (default: the current working directory). Confirm the save path with the user before downloading to an unexpected location.readopens a local PDF file with PyMuPDF — make sure the path the user supplies is one they trust.- No credentials or API keys are needed or stored anywhere.
Common Pitfalls
Problem:
需要安装 arxiv: pip install arxivor需要安装 PyMuPDF: pip install PyMuPDF. Solution: The script returns this friendly message instead of crashing when an optional dependency is missing. Offer to run the install command.Problem:
搜索失败: rate limit, retries exhaustedfromsearchordetailorcitations. Solution: Semantic Scholar is rate-limiting. Wait ~10 seconds and retry once. For repeated runs, fall back toarxivfor arXiv-indexed work.Problem:
downloadfails with找不到 arXiv ID: …. Solution: The user gave a non-arXiv ID (likely a DOI for a non-arXiv paper). Usedetailto inspect; only papers with anexternalIds.ArXivfield can be downloaded.Problem: Garbled Chinese output on Windows. Solution: The script already forces UTF-8 stdout. If the host terminal is still misconfigured, set
PYTHONIOENCODING=utf-8in the shell environment.
Additional Resources
- Skill home (this plugin): https://github.com/xwmxcz/papers-skill
- Upstream MCP server: https://github.com/xwmxcz/papers-mcp
- Semantic Scholar API docs: https://api.semanticscholar.org/
- arXiv API docs: https://info.arxiv.org/help/api/
- PyMuPDF docs: https://pymupdf.readthedocs.io/
Files (agentic-awesome-skills)
-
scripts
-
papers.py 8.8 KB
#!/usr/bin/env python3 """ papers.py — Standalone academic paper toolkit (Skill-mode port of papers-mcp). Original MCP project: https://github.com/xwmxcz/papers-mcp Usage: python papers.py search <query> [--limit 10] python papers.py detail <paper_id> python papers.py citations <paper_id> [--limit 10] python papers.py arxiv <query> [--max-results 5] python papers.py download <arxiv_id> [--save-dir .] python papers.py read <pdf_path> [--max-pages 10] Dependencies: httpx, arxiv, PyMuPDF """ from __future__ import annotations import argparse import sys import time from pathlib import Path # Force UTF-8 stdout on Windows so Chinese strings render correctly when # called via Bash / cmd / cron (Python 3.7+). if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") import httpx S2_BASE = "https://api.semanticscholar.org/graph/v1" S2_FIELDS = "paperId,title,abstract,year,citationCount,authors,externalIds,url" S2_RETRIES = 3 S2_WAIT = 2 # seconds, exponential backoff base # ---------- HTTP helpers ---------- def _s2_get(url: str, params: dict) -> dict: """GET with rate-limit retry. Returns parsed JSON or {'error': ...}.""" for attempt in range(S2_RETRIES): try: r = httpx.get( url, params=params, timeout=30.0, headers={"User-Agent": "papers-skill/1.0"}, ) if r.status_code == 429: time.sleep(S2_WAIT * (attempt + 1)) continue r.raise_for_status() return r.json() except httpx.HTTPError as e: if attempt == S2_RETRIES - 1: return {"error": f"HTTP error: {e}"} time.sleep(S2_WAIT * (attempt + 1)) return {"error": "rate limit, retries exhausted"} def _fmt_authors(authors: list, n: int = 3) -> str: if not authors: return "(unknown)" names = [a.get("name", "?") for a in authors[:n]] suffix = " et al." if len(authors) > n else "" return ", ".join(names) + suffix # ---------- Commands ---------- def cmd_search(args) -> str: data = _s2_get( f"{S2_BASE}/paper/search", {"query": args.query, "limit": min(args.limit, 20), "fields": S2_FIELDS}, ) if "error" in data: return f"搜索失败: {data['error']}" papers = data.get("data", []) if not papers: return f"没有找到与 '{args.query}' 相关的论文" out = [f"# 搜索结果 ({len(papers)} 篇)\n"] for i, p in enumerate(papers, 1): title = p.get("title", "无标题") year = p.get("year", "?") citations = p.get("citationCount", 0) authors = _fmt_authors(p.get("authors", [])) abstract = (p.get("abstract") or "").strip()[:200] ext = p.get("externalIds") or {} arxiv_id = ext.get("ArXiv", "") out.append( f"## {i}. {title}\n" f"**Authors:** {authors} \n" f"**Year:** {year} | **Citations:** {citations} \n" f"**S2 ID:** `{p.get('paperId')}`" + (f" | **arXiv:** `{arxiv_id}`" if arxiv_id else "") + " \n" f"**Abstract:** {abstract}{'...' if abstract else '(无摘要)'}\n" ) return "\n".join(out) def cmd_detail(args) -> str: pid = args.paper_id # Auto-detect ID type if pid.startswith(("10.", "ARXIV:", "DOI:", "MAG:", "PMID:", "PMCID:")): lookup = pid elif pid.isdigit() and len(pid) >= 10: lookup = f"ARXIV:{pid}" else: lookup = pid # assume raw S2 paperId fields = S2_FIELDS + ",references.title,references.year,tldr" data = _s2_get(f"{S2_BASE}/paper/{lookup}", {"fields": fields}) if "error" in data: return f"查询失败: {data['error']}" title = data.get("title", "无标题") authors = _fmt_authors(data.get("authors", []), n=5) year = data.get("year", "?") citations = data.get("citationCount", 0) abstract = data.get("abstract") or "(无摘要)" tldr = (data.get("tldr") or {}).get("text") or "(无 TL;DR)" refs = (data.get("references") or [])[:10] out = [ f"# {title}", f"**Authors:** {authors} ", f"**Year:** {year} | **Citations:** {citations} ", f"**ID:** `{data.get('paperId')}` ", f"**URL:** {data.get('url', '')}", "", "## TL;DR", tldr, "", "## Abstract", abstract, "", f"## Top {len(refs)} References", ] for i, r in enumerate(refs, 1): out.append(f"{i}. {r.get('title', '?')} ({r.get('year', '?')})") return "\n".join(out) def cmd_citations(args) -> str: data = _s2_get( f"{S2_BASE}/paper/{args.paper_id}/citations", { "limit": min(args.limit, 20), "fields": "title,year,authors", }, ) if "error" in data: return f"查询失败: {data['error']}" cites = data.get("data", []) if not cites: return "没有找到引用此论文的记录" out = [f"# 引用此论文的论文 ({len(cites)} 篇)\n"] for i, item in enumerate(cites, 1): p = item.get("citingPaper", {}) title = p.get("title", "?") year = p.get("year", "?") authors = _fmt_authors(p.get("authors", []), n=2) out.append(f"{i}. **{title}** ({year}) — {authors}") return "\n".join(out) def cmd_arxiv(args) -> str: try: import arxiv except ImportError: return "需要安装 arxiv: pip install arxiv" search = arxiv.Search( query=args.query, max_results=min(args.max_results, 10), sort_by=arxiv.SortCriterion.Relevance, ) results = list(arxiv.Client().results(search)) if not results: return f"没有找到与 '{args.query}' 相关的 arXiv 论文" out = [f"# arXiv 搜索结果 ({len(results)} 篇)\n"] for i, p in enumerate(results, 1): arxiv_id = p.entry_id.rsplit("/", 1)[-1] out.append( f"## {i}. {p.title}\n" f"**Authors:** {', '.join(a.name for a in p.authors[:3])} \n" f"**arXiv ID:** `{arxiv_id}` \n" f"**Published:** {p.published.strftime('%Y-%m-%d')} \n" f"**Summary:** {p.summary[:200].strip()}...\n" ) return "\n".join(out) def cmd_download(args) -> str: try: import arxiv except ImportError: return "需要安装 arxiv: pip install arxiv" save_dir = Path(args.save_dir).resolve() save_dir.mkdir(parents=True, exist_ok=True) search = arxiv.Search(id_list=[args.arxiv_id]) paper = next(arxiv.Client().results(search), None) if paper is None: return f"找不到 arXiv ID: {args.arxiv_id}" path = paper.download_pdf(dirpath=str(save_dir)) return f"已下载: {path}" def cmd_read(args) -> str: try: import fitz # PyMuPDF except ImportError: return "需要安装 PyMuPDF: pip install PyMuPDF" pdf = Path(args.pdf_path) if not pdf.exists(): return f"PDF 不存在: {pdf}" doc = fitz.open(str(pdf)) pages = min(args.max_pages, doc.page_count) chunks = [] for i in range(pages): text = doc.load_page(i).get_text().strip() if text: chunks.append(f"--- Page {i + 1} ---\n{text}") doc.close() if not chunks: return "PDF无法提取文本(可能是扫描件)" return "\n\n".join(chunks) # ---------- CLI ---------- def main(): parser = argparse.ArgumentParser(prog="papers", description=__doc__) sub = parser.add_subparsers(dest="cmd", required=True) p = sub.add_parser("search", help="Semantic Scholar 搜索") p.add_argument("query") p.add_argument("--limit", type=int, default=10) p.set_defaults(fn=cmd_search) p = sub.add_parser("detail", help="论文详情 (支持 DOI / ARXIV:id / S2 paperId)") p.add_argument("paper_id") p.set_defaults(fn=cmd_detail) p = sub.add_parser("citations", help="该论文的引用列表") p.add_argument("paper_id") p.add_argument("--limit", type=int, default=10) p.set_defaults(fn=cmd_citations) p = sub.add_parser("arxiv", help="arXiv 搜索") p.add_argument("query") p.add_argument("--max-results", type=int, default=5) p.set_defaults(fn=cmd_arxiv) p = sub.add_parser("download", help="下载 arXiv PDF") p.add_argument("arxiv_id") p.add_argument("--save-dir", default=".") p.set_defaults(fn=cmd_download) p = sub.add_parser("read", help="提取 PDF 文本 (PyMuPDF)") p.add_argument("pdf_path") p.add_argument("--max-pages", type=int, default=10) p.set_defaults(fn=cmd_read) args = parser.parse_args() try: print(args.fn(args)) except Exception as e: print(f"错误: {type(e).__name__}: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()
-
-
SKILL.md 8.2 KB
--- name: papers-skill description: "Skill for academic research workflows: search Semantic Scholar (200M+ papers), inspect citations, download arXiv PDFs, and extract PDF text. Bundles a self-contained Python CLI." category: research risk: safe source: community source_repo: xwmxcz/papers-skill source_type: community date_added: "2026-06-11" author: xwmxcz tags: [research, academic, papers, citations, arxiv, semantic-scholar, pdf] tools: [claude-code, antigravity, cursor, gemini-cli, codex-cli, opencode] license: "MIT" license_source: "https://github.com/xwmxcz/papers-skill/blob/main/LICENSE" --- # Papers Skill ## Overview Papers Skill turns a coding agent into a literature-research assistant. It orchestrates a bundled Python CLI (`scripts/papers.py`) that hits the free Semantic Scholar and arXiv APIs, downloads arXiv PDFs, and extracts text with PyMuPDF. The agent decides which subcommand to invoke and how to combine results into a literature scan, a deep read of one paper, an impact analysis, or a reading list. This skill is the Skill-mode port of the [papers-mcp](https://github.com/xwmxcz/papers-mcp) MCP server by the same author. Both projects share the same feature set; this one ships as a Claude Code plugin so it can be installed with a single command and needs no long-running MCP process. ## When to Use This Skill - Use when the user asks to search academic papers by topic, author, or venue. - Use when the user names a specific paper (by DOI, arXiv ID, or title) and wants metadata, the abstract, the TL;DR, or its reference list. - Use when the user wants to find work that **cites** a known paper (impact analysis, follow-up tracking). - Use when the user wants to download an arXiv PDF and have it summarized. - Use when the user asks to build a reading list around a topic. ## Do Not Use This Skill When - The user wants paywalled non-arXiv full text. This skill cannot bypass publisher paywalls; it can only fetch arXiv PDFs and metadata everywhere. - The user wants OCR over scanned PDFs. PyMuPDF extracts embedded text only; scanned image-PDFs return the fallback message and need a separate OCR step. - The user wants real-time citation alerts or RSS-style watching. This skill is request-driven. ## How It Works ### Step 1: Verify dependencies Three Python packages are required. The skill should check once per session, using the **same interpreter** to import-check and install so the dependency check and install target stay in sync: ```bash python -c "import httpx, arxiv, fitz" 2>&1 || python -m pip install httpx arxiv PyMuPDF ``` If `python` is not on PATH, fall back to `py` (Windows launcher) or the absolute interpreter path — and remember to invoke pip via the same interpreter, e.g. `py -m pip install httpx arxiv PyMuPDF`. ### Step 2: Invoke the bundled CLI The script lives at `${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py` and is bundled with this skill (no separate install needed). Always quote the path so it survives spaces. ```bash python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" <subcommand> [args] ``` ### Step 3: Pick the right subcommand | Subcommand | Purpose | Example | |---|---|---| | `search <query> [--limit N]` | Semantic Scholar search, max 20 | `search "diffusion models" --limit 5` | | `detail <paper_id>` | Full metadata, TL;DR, top references | `detail 10.48550/arXiv.2310.06825` | | `citations <paper_id> [--limit N]` | Papers citing this one, max 20 | `citations <id> --limit 15` | | `arxiv <query> [--max-results N]` | arXiv preprint search, max 10 | `arxiv "RLHF" --max-results 5` | | `download <arxiv_id> [--save-dir D]` | Save PDF locally | `download 2310.06825 --save-dir ./pdfs` | | `read <pdf_path> [--max-pages N]` | Extract PDF text via PyMuPDF | `read ./pdfs/foo.pdf --max-pages 20` | `detail` and `citations` auto-detect the ID type: DOIs starting with `10.` are used as-is, bare numeric IDs of 10+ digits are treated as arXiv IDs, and long hex strings are treated as Semantic Scholar `paperId`s. ## Examples ### Example 1: Literature scan on a topic ```bash python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" search "retrieval augmented generation" --limit 10 ``` Present results as a ranked table with **# | Title | Year | Citations | ID**, then ask the user which papers to dig into. ### Example 2: Deep-read one paper ```bash # 1. Confirm match python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" detail 2005.11401 # 2. Download python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" download 2005.11401 --save-dir ./pdfs # 3. Extract abstract + intro + conclusion python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" read ./pdfs/2005.11401v4.RAG.pdf --max-pages 10 ``` Summarize as: **problem · method · key result · limitations**. ### Example 3: Impact analysis on an anchor paper ```bash python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" detail 10.48550/arXiv.2005.11401 python "${CLAUDE_PLUGIN_ROOT}/skills/papers-skill/scripts/papers.py" citations 10.48550/arXiv.2005.11401 --limit 20 ``` Cluster the citing papers by year/theme and highlight the most-cited follow-ups. ## Best Practices - ✅ Always call `detail` before `download` to confirm the paper matches user intent. Skipping this leads to wrong PDFs being fetched. - ✅ Include the paper ID alongside every title in your output so the user can re-query precisely. - ✅ Cite as `[FirstAuthor et al., Year] *Title* (cites: N)`. - ✅ For PDFs you download, always report the absolute save path. - ❌ Don't crawl. The script auto-retries 429s with exponential backoff; don't pile on parallel queries. - ❌ Don't raise `--max-pages` to 100+ without warning the user — it can consume a large amount of context. ## Limitations - The skill cannot fetch full text from paywalled publishers (Elsevier, Springer, Wiley, etc.). It can only read open arXiv PDFs. - PyMuPDF extracts embedded text only. Scanned image-PDFs return the fallback message `PDF无法提取文本(可能是扫描件)`; offer the user an alternative version or note that OCR is required. - Semantic Scholar's anonymous tier rate-limits aggressively. The script retries 3× with exponential backoff; persistent 429s during heavy use surface as `搜索失败: rate limit, retries exhausted`. - This skill does not replace environment-specific validation, testing, or expert review. Stop and ask for clarification if required inputs are missing. ## Security & Safety Notes - The CLI performs **outbound HTTPS only** to `api.semanticscholar.org` and `arxiv.org` (and the arXiv-listed mirror for the bundled `arxiv` package). No authentication tokens are sent. - `download` writes a PDF to the directory the user specifies (default: the current working directory). Confirm the save path with the user before downloading to an unexpected location. - `read` opens a local PDF file with PyMuPDF — make sure the path the user supplies is one they trust. - No credentials or API keys are needed or stored anywhere. ## Common Pitfalls - **Problem:** `需要安装 arxiv: pip install arxiv` or `需要安装 PyMuPDF: pip install PyMuPDF`. **Solution:** The script returns this friendly message instead of crashing when an optional dependency is missing. Offer to run the install command. - **Problem:** `搜索失败: rate limit, retries exhausted` from `search` or `detail` or `citations`. **Solution:** Semantic Scholar is rate-limiting. Wait ~10 seconds and retry once. For repeated runs, fall back to `arxiv` for arXiv-indexed work. - **Problem:** `download` fails with `找不到 arXiv ID: …`. **Solution:** The user gave a non-arXiv ID (likely a DOI for a non-arXiv paper). Use `detail` to inspect; only papers with an `externalIds.ArXiv` field can be downloaded. - **Problem:** Garbled Chinese output on Windows. **Solution:** The script already forces UTF-8 stdout. If the host terminal is still misconfigured, set `PYTHONIOENCODING=utf-8` in the shell environment. ## Additional Resources - Skill home (this plugin): https://github.com/xwmxcz/papers-skill - Upstream MCP server: https://github.com/xwmxcz/papers-mcp - Semantic Scholar API docs: https://api.semanticscholar.org/ - arXiv API docs: https://info.arxiv.org/help/api/ - PyMuPDF docs: https://pymupdf.readthedocs.io/
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.