English | 中文
A CLI tool that gives AI agents (Claude Code, Codex, Opencode, etc.) the ability to search, download, and read academic papers and scientific literature. Ships with a SKILL that teaches agents how to pick sources and construct commands.
One command, one source, zero configuration. Parallel multi-source search is handled by the agent spawning multiple processes.
Homebrew (macOS / Linux)
brew install zhangyee/tap/fastpaperShell script (macOS / Linux)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/zhangyee/fastpaper-cli/releases/latest/download/fastpaper-cli-installer.sh | shPowerShell (Windows)
powershell -ExecutionPolicy Bypass -c "irm https://github.com/zhangyee/fastpaper-cli/releases/latest/download/fastpaper-cli-installer.ps1 | iex"Cargo
cargo install fastpaper-cliInstall the skill so your AI agent knows how to use fastpaper. Uses Vercel Skills, a tool that installs SKILL.md files into agents:
npx skills add zhangyee/fastpaper-cli --skill fastpaperThe SKILL.md teaches the agent how to pick sources by domain and construct commands. Use --format json for structured output. All JSON fields use null for missing values (never omitted), so the schema is stable.
# Search arXiv
fastpaper search arxiv "transformer attention mechanism"
# Search with filters -- each source declares which ones it can honour
fastpaper search arxiv "large language model" --after 2024-01-01 --field cs.CL --limit 20
# Fetch a paper by DOI (source auto-detected)
fastpaper get 10.1038/nature12373
# Fetch by arXiv ID
fastpaper get 2301.08745
# Name the source explicitly to control which one answers
fastpaper get openalex 10.1038/nature12373
# Download a PDF into ./papers
fastpaper download arxiv 2301.08745
# ...or let the identifier pick the source
fastpaper download 2301.08745
# Read a PDF you already downloaded
fastpaper read papers/2301.08745.pdf
# Read a specific section of it
fastpaper read papers/2301.08745.pdf --section methods
# JSON output for scripting / AI agents
fastpaper search semantic "CRISPR gene editing" --format json
# Parallel multi-source search
fastpaper search arxiv "protein folding" --format json &
fastpaper search pubmed "protein folding" --format json &
fastpaper search semantic "protein folding" --format json &
wait26 academic sources, each accessed independently per command.
read is not listed here: it works on a PDF already on disk, so it applies
equally to anything the download column can fetch.
| Source | Full name | search | get | download | cite | figures | Domain |
|---|---|---|---|---|---|---|---|
arxiv |
arXiv | yes | yes | yes | yes | Physics, math, CS, statistics, EE, q-bio, q-fin, econ | |
pubmed |
PubMed | yes | yes | Biomedical & life sciences (metadata only) | |||
pmc |
PubMed Central | yes | yes | yes | Biomedical & life sciences (full text) | ||
europepmc |
Europe PMC | yes | yes | yes | yes | Life sciences superset of PMC; adds preprints, patents, guidelines | |
semantic |
Semantic Scholar | yes | yes | yes | yes | All disciplines, AI-powered citation graph | |
crossref |
CrossRef | yes | yes | DOI metadata, all disciplines | |||
openalex |
OpenAlex | yes | yes | yes | Open metadata index, 200M+ works | ||
dblp |
DBLP | yes | Computer science | ||||
core |
CORE | yes | yes | yes | Open access aggregator (needs CORE_API_KEY) |
||
openaire |
OpenAIRE | yes | yes | EU open science | |||
doaj |
DOAJ | yes | yes | Open access journals, all subjects (links go to publisher pages, not PDFs) | |||
unpaywall |
Unpaywall | yes | OA link resolver (requires UNPAYWALL_EMAIL) |
||||
zenodo |
Zenodo | yes | yes | yes | All disciplines (datasets, software, papers) | ||
hal |
HAL | yes | yes | yes | Multi-disciplinary, French national archive | ||
osf |
OSF Preprints | yes | yes | yes | Social science, psychology, education, law preprints (~32 communities; title-only search) | ||
inspire |
INSPIRE-HEP | yes | yes | High-energy physics (reliable citation counts; files live on arXiv) | |||
zbmath |
zbMATH Open | yes | yes | Mathematics (published record + reviews; no abstracts or files) | |||
eric |
ERIC | yes | yes | yes | Education research (files for the ERIC-hosted subset) | ||
osti |
OSTI.GOV | yes | yes | yes | US Dept. of Energy technical reports | ||
ntrs |
NASA NTRS | yes | yes | yes | NASA aerospace reports | ||
datacite |
DataCite | yes | yes | DOI registry for datasets, software and theses (complements crossref) | |||
huggingface |
Hugging Face Papers | yes | yes | AI community attention: daily / weekly / monthly lists ranked by upvotes (--top), rolling --trending; ids are arXiv ids |
|||
openreview |
OpenReview | yes | ML conference submissions with their decision (ICLR, NeurIPS, …); search only | ||||
jstage |
J-STAGE | yes | yes | Japanese society journals (Japanese titles where they exist; no abstracts) | |||
oapen |
OAPEN Library | yes | yes | yes | Peer-reviewed open access books, mostly humanities and social sciences | ||
ads |
NASA ADS (SciX) | yes | yes | yes | yes | Astronomy and physics, with a citation graph (requires ADS_API_TOKEN) |
figures fetches the authors' original figure files rather than PDF pages —
see below. Only arxiv (its e-print source package) and europepmc (its
supplementary package) can provide them; a PMC ID or a DOI both route to
europepmc.
Sources differ in which search filters they can honour, and in their per-request
result caps. fastpaper sources --capabilities prints the full matrix along
with each source's caveats.
Each command does exactly one thing: search finds papers, get reads
metadata, download saves a PDF, read extracts text from a PDF on disk.
The source is required: a free-text query has no identifier shape to infer one from.
fastpaper search <SOURCE> [QUERY] [OPTIONS]
Options:
-n, --limit <N> Max results [default: 10]
--offset <N> Skip first N results [default: 0]
--sort <FIELD> Sort by: relevance, date, citations
--order <DIR> asc or desc [default: desc]
--author <NAME> Filter by author
--after <DATE> Papers published on or after YYYY-MM-DD
--before <DATE> Papers published on or before YYYY-MM-DD
--year <YEAR> Papers in a specific year
--field <FIELD> Field of study / category (e.g. cs.CL)
--open-access Only open access papers
--patents Patents only (europepmc)
--trending Hugging Face's rolling "hot right now" list instead of a search
--top <PERIOD> Hugging Face's list for one period, ranked by upvotes: a day (2026-09-18), an ISO week (2026-W38) or a month (2026-08)
-f, --format <FMT> table, json, jsonl, csv, bibtex [default: table]
-o, --output <PATH> Write results to file
Filters map onto each source's own API parameters. A source that cannot honour
one fails with an error naming the filters it does support, and the sources
that do have the one you asked for, rather than dropping it silently — a
filter that vanishes yields results that look right and are not. The same goes
for -n above a source's per-request cap. Run
fastpaper sources --capabilities to see what each source accepts.
One argument is an identifier and the source is inferred from its shape. Two arguments name the source explicitly.
fastpaper get <IDENTIFIER>
fastpaper get <SOURCE> <IDENTIFIER>
Routing: arXiv→arxiv, PMC→pmc, PMID→pubmed, DOI→crossref, S2:→semantic.
Note this differs from download, which sends a DOI to semantic — the two
commands look at records from different sources. Crossref registers DOIs and
carries the most authoritative metadata but serves no files, so get <DOI>
returns pdf_url: null no matter what; download <DOI> resolves an open access
copy through Semantic Scholar instead. Do not read pdf_url off get <DOI> to
predict whether download will succeed.
Same two forms as get. Saves as <identifier>.pdf.
fastpaper download <IDENTIFIER> [OPTIONS]
fastpaper download <SOURCE> <IDENTIFIER> [OPTIONS]
Options:
-d, --dir <PATH> Download directory [default: ./papers]
-s, --max-size <SIZE> Largest response body to accept, e.g. 10MiB, 200MB.
A bare number is bytes, so `--max-size 10` means ten
bytes. 0 means unlimited [default: 100MiB]
--overwrite Overwrite an existing file
Same two forms as get. Fetches a paper's original figure files — an arXiv
source package or a Europe PMC supplementary package — and saves them under a
subdirectory named for the identifier you typed (/ flattened to _, same
as download).
fastpaper figures <IDENTIFIER> [OPTIONS]
fastpaper figures <SOURCE> <IDENTIFIER> [OPTIONS]
Options:
-d, --dir <PATH> Download directory [default: ./papers]
-s, --max-size <SIZE> Largest archive to accept, e.g. 10MiB, 200MB.
A bare number is bytes. 0 means unlimited [default: 100MiB]
--overwrite Overwrite existing files
Only arxiv and europepmc can provide figures. A PMC ID or a DOI routes to
europepmc; a DOI is resolved to a PMC ID via NCBI's ID converter first.
Naming any other source fails with exit 1. A paper that has no figure files
at a supported source fails with exit 4.
This does not extract images from the PDF, render anything, or parse figure
numbers — it saves exactly what is in the archive, under the name it has
there. Filenames do not correspond to figure numbers: for 2511.11035,
the file 3.pdf is actually Figure 1.
Measured against a 39-paper corpus, verified end-to-end against the live services on 2026-08-20: 31 papers (79%) yielded figure files. The 8 misses were 4 HTTP 404s, 3 packages with no images (not open access), and 1 arXiv paper submitted as a PDF with no source package. All 22 DOIs in that corpus resolved to a PMC ID.
fastpaper figures 2511.11035 -d papers/ # -> papers/2511.11035/{1,2,3}.pdf
fastpaper figures PMC7075534 -d papers/ # -> papers/PMC7075534/*.jpgSame two forms as get. Returns the papers on the other end of a citation
edge, in the usual result shape. Three sources hold edges: semantic and
openalex, plus ads for astronomy and physics, which takes an ADS bibcode
and needs ADS_API_TOKEN. A bare DOI routes to openalex, which needs no API
key, while arXiv and S2: identifiers route to semantic.
fastpaper cite <IDENTIFIER> [OPTIONS]
fastpaper cite <SOURCE> <IDENTIFIER> [OPTIONS]
Options:
--direction <DIR> incoming (papers citing this one) or outgoing
(papers it cites) [default: incoming]
-n, --limit <N> Max edges [default: 20]
-o, --output <PATH> Write to a file instead of stdout
Takes a path, never the network. Download first, then read what landed.
fastpaper read <PATH> [OPTIONS]
Options:
--section <SEC> abstract, introduction, methods, results,
discussion, conclusion, references, full [default: full]
--list-sections List the sections found in the PDF instead of reading it
--grep <PATTERN> Search the text for a regular expression.
Case-insensitive; use (?-i) for a case-sensitive one
--context <N> Characters of context on each side of a match
(requires --grep) [default: 500]
--max-matches <N> Show at most N matches (requires --grep) [default: 10]
--max-length <N> Truncate output to N characters
-o, --output <PATH> Write to file
fastpaper download 2301.08745 # -> ./papers/2301.08745.pdf
fastpaper read papers/2301.08745.pdf --section abstract
fastpaper read papers/2301.08745.pdf --grep 'limitations?'A PDF records no section structure, so --section reads the typography: a
heading is a line that holds nothing else and is set no smaller than the body.
That handles the layouts journals actually use — a numbered II.METHODS, a
Materials and methods, Nature running results before methods — but a paper
set unusually can leave a section unfound, and --section then exits 4 rather
than guessing. --list-sections says what was found before you ask for one:
fastpaper read papers/2301.08745.pdf --list-sectionsabstract p1 @105 12.0pt Abstract
introduction p1 @1763 12.0pt 1Introduction
conclusion p8 @30510 12.0pt 5Conclusion
references p9 @31793 12.0pt References
Four, on a paper that has more: one of its sections is headed Analysis,
which --section does not take. The page is there so the reading can be
checked against the print, which is the only way to know it is right.
When the list has no abstract or introduction, that is usually the paper
rather than the reading — Nature and its family print the abstract with no
heading over it at all. Read the opening instead, which is the same few
thousand characters the section would have given you:
fastpaper read papers/2301.08745.pdf --max-length 3000With --format json, a --section read also reports the heading its slice
started at, as heading.text, heading.offset and heading.font_size. That
is what a caller quoting a section can check the slice against.
Two things read does not do. It leaves running heads, folios and journal
footers in the text, so a quoted passage can have one spliced through it — they
are only reliably distinguishable from content on layouts we could measure, and
guessing would delete real text silently. And it reports no confidence: a
section that was found is not thereby the right one.
--grep searches the section --section selected, or the whole paper when it
is not given, so "try the section, fall back to full text" is one command with
one flag changed. Matches are reported with their character offset, and windows
that overlap are merged rather than printed twice. No match exits 4.
--max-length bounds the output without erasing the hit: an excerpt too big for
the budget is cut around its match rather than from its start, and only matches
still shown are counted. When something was left out, the trailing line names
the flag that did it — raising --max-matches does nothing when --max-length
was what bound — and --format json carries the same answer in truncated_by.
fastpaper sources [--capabilities]
fastpaper completions fish > ~/.config/fish/completions/fastpaper.fish
fastpaper completions zsh > ~/.zfunc/_fastpaper
fastpaper completions bash >> ~/.bashrc
All optional except where noted. 24 of 26 sources work with zero configuration.
| Variable | Purpose |
|---|---|
FASTPAPER_DOWNLOAD_DIR |
Default download directory (otherwise ./papers) |
FASTPAPER_MAX_DOWNLOAD_SIZE |
Largest response body download will accept, and the largest archive figures will accept (otherwise 100MiB); 0 means unlimited |
FASTPAPER_EMAIL |
Contact address sent to CrossRef, OpenAlex and NCBI. Unset means the parameter is omitted, which all three accept |
SEMANTIC_SCHOLAR_API_KEY |
Higher rate limit for Semantic Scholar |
OPENALEX_API_KEY |
Larger free tier for OpenAlex, which has metered usage since 2026-02 |
CORE_API_KEY |
Higher rate limit for CORE |
NCBI_API_KEY |
Higher rate limit for PubMed / PMC |
UNPAYWALL_EMAIL |
Required for Unpaywall, and it must be a real address |
ADS_API_TOKEN |
Required for ads; free from https://scixplorer.org/user/settings/token |
HF_TOKEN |
Higher rate limit for Hugging Face (anonymous: 500 requests per 5 minutes) |
Every source also takes FASTPAPER_<SOURCE>_URL to override its base URL —
FASTPAPER_ARXIV_URL, FASTPAPER_PUBMED_URL and so on — which is what the
tests point at a local mock server. Sources whose files live on a different host
than their API have a second override for that host: FASTPAPER_ARXIV_PDF_URL,
FASTPAPER_PMC_DL_URL (which points at the PMC
Cloud Service on AWS Open Data, not the article pages), FASTPAPER_JSTAGE_PDF_URL,
FASTPAPER_ADS_PDF_URL.
One override does not fit that pattern, because it is not a source:
FASTPAPER_IDCONV_URL points at NCBI's ID converter (default
https://pmc.ncbi.nlm.nih.gov), which figures uses to turn a DOI into a
PMC ID. Only the DOI route touches it — an arXiv ID or a PMC ID never does.
| Code | Meaning |
|---|---|
0 |
Success |
1 |
General error (rejected arguments, parse failure, network failure) |
2 |
Usage error from argument parsing: unknown flag, bad value, or a flag whose partner is missing (--context without --grep) |
4 |
Nothing to return: no such paper, no --grep match, no such --section, no PDF for this paper at this source, no figure files for this paper |
Those four are the whole set. Anything a caller can branch on lives here — a
rate limit, a 403 and a refused --max-size all arrive as 1, distinguished by
the message rather than the code.
Exit 4 is the one worth branching on: it means the request was well-formed and
this source simply has nothing, so the next move is another source rather than
another spelling.
Commands that take -o print a receipt to stderr once the file is written —
Saved: results.json (12 results) — so a caller never has to read the file back
to find out what landed. -q suppresses it. Zero results still print, since
that is the case most worth seeing.
download prints Saved: papers/2301.08745.pdf (2.2 MiB) on stderr as well,
and figures prints Saved: 3 figure files to papers/2511.11035 (382.2 KiB).
-q suppresses neither: both write nothing to stdout, and neither path is
derivable from the arguments, since a / in a DOI becomes _.
Both also exit 0 rather than failing when a target file is already there,
printing File already exists: ... instead — rerunning is safe, and 1
stays reserved for something actually going wrong. Pass --overwrite to
replace what is on disk.
Contributions are welcome! See CONTRIBUTING.md and the developer docs under docs/.
This project was inspired by paper-search-mcp, an MCP server for searching and downloading academic papers from multiple sources. Many thanks to its authors for showing what a multi-source paper tool can look like.