Commands¶
Snippets below use uv run so they stay short. The operator install is
clone plus docker compose build, then
docker compose run --rm paperful … (Docker). CI builds that
image and expects doctor to exit 2 without Zotero. There is no published
image and no PyPI package. Headed session login is host-only
(uv run paperful session login …). No campus access: --preset oa.
Campus EZProxy: --preset eoi. paperful jobs lists verbs by job.
The public grow verb is snowball (there is no harvest command).
# environment check (TTY guide for amber/red)
uv run paperful doctor
uv run paperful collections
# fetch to disk only
uv run paperful run --collection interesting --no-attach
# several collections, or the whole library (resumable; Ctrl-C any time, rerun to continue)
uv run paperful run -C BBNJ -C AO
uv run paperful run --library
# attach previously downloaded PDFs
uv run paperful attach
# what happened
uv run paperful report
uv run paperful report --last-run # latest run summary only
uv run paperful report --json # agent-friendly (manifest + last run)
uv run paperful report --not-found
uv run paperful report --status error
# policy-sensitive runs (OA + campus EZProxy; no Scholar / Sci-Hub)
uv run paperful run -C BBNJ --preset eoi --dry-run
# items with only a linked PDF URL in Zotero are skipped by default
uv run paperful run --library --upgrade-linked
# retry items marked not_found / no_identifier (e.g. after EZProxy login)
uv run paperful run --library --retry-failed
uv run paperful run --collection BBNJ --retry-failed
uv run paperful run --collection BBNJ --try-all # ignore source_routing when metadata is unreliable
uv run paperful ocr -C BBNJ # list image PDFs; does not write
uv run paperful ocr -C BBNJ --apply # text layer on the out/ PDF
uv run paperful ocr --item ITEMKEY --apply --attach # also upload beside the scan
# optional LLM verbs — off until [llm].enabled; setup in docs/llm.md
# recover also auto-fires at the end of `run` when Scholar / EZProxy / htmlpdf fail
uv run paperful recover --item ITEMKEY --dry-run # browser agent: show start URL only
uv run paperful recover --item ITEMKEY # needs Python 3.11+ and paperful[browser-agent]
uv run paperful summarize --item ITEMKEY # disk HTML + tagged child note (default both)
uv run paperful summarize -C BBNJ --to disk # HTML only; Zotero tree stays clean
uv run paperful summarize --item ITEMKEY --prompt prompts/mine.md --force
uv run paperful synthesize -C BBNJ --dry-run # chunk plan from existing summary notes
uv run paperful synthesize -C BBNJ # report on disk and a note in the collection
# thicken the on-disk mirror (per-item folders). pdfs: additional | all | none
uv run paperful snapshot -C BBNJ --dry-run
uv run paperful snapshot -C BBNJ --pdfs all
uv run paperful restore -C BBNJ # dry-run unless --apply
uv run paperful restore -C BBNJ --year-from 2021 --year-to 2026 -T journalArticle
uv run paperful restore -C BBNJ --apply # create missing items; never overwrite fields
# restrict / reorder sources for one run, or cap the number of items processed
uv run paperful run -C hoops --sources unpaywall,openalex,ezproxy
uv run paperful run --library --limit 50
# year range (inclusive; undated items excluded) — e.g. full run on BBNJ 2023–2026
uv run paperful run -C BBNJ --year-from 2023 --year-to 2026
uv run paperful run -C BBNJ --year-from 2023 --year-to 2026 --dry-run
uv run paperful gaps -C BBNJ --year-from 2023 --year-to 2026
# restrict to Zotero item types (repeatable / comma-separated; friendly names ok)
uv run paperful run -C BBNJ -T journalArticle --year-from 2023 --year-to 2026
uv run paperful run -C BBNJ -T "Journal Article" -T report
uv run paperful lint -C BBNJ --type journalArticle,preprint
# the same slice as one command, or saved beside config.toml
# gaps → run --try-all --retry-failed --upgrade-linked → lint → fix-metadata --apply → summarize --apply
uv run paperful all -C BBNJ -T journalArticle --year-from 2021 --year-to 2026
uv run paperful all -C BBNJ -T journalArticle --year-from 2021 --year-to 2026 --dry-run
uv run paperful profile save bbnj-journal -C BBNJ -T journalArticle --year-from 2021 --year-to 2026 --try-all --apply
uv run paperful all --profile bbnj-journal
uv run paperful profile list
uv run paperful profile show bbnj-journal
# Sci-Hub is off unless you opt in (config `sources`, or this flag)
uv run paperful run --library --scihub
# session (optional)
uv run paperful session login ezproxy # system Chrome/Edge when present; campus SSO
uv run paperful session login scholar # same; --engine playwright to force Playwright
uv run paperful session login mendeley # Elsevier OAuth (host-only localhost redirect)
uv run paperful session status
uv run paperful ezproxy --no-open # probe the EZProxy session
uv run paperful scholar --no-open # probe Scholar
uv run paperful mirrors # which Sci-Hub mirrors are up (Sci-Hub itself stays off)
# interchange (RIS / BibTeX / EndNote XML)
uv run paperful export library.ris --library
uv run paperful import other.bib # dry-run
uv run paperful import other.bib --apply
# identifiers vs PDFs (read-only); metadata writes are a separate step
uv run paperful lint --library --json
uv run paperful lint -C BBNJ --strict # exit 1 if any finding
uv run paperful fix-metadata --library # dry-run → state/metadata-patches.jsonl
uv run paperful fix-metadata --library --apply # write DOI/title/date/venue into Zotero 10+
uv run paperful fix-metadata --library --apply --overwrite # replaces title/date/venue you may have edited; dry-run first
# duplicates, then remaining PDF gaps (review the pack before --apply)
uv run paperful dedupe -C BBNJ --dry-run
uv run paperful dedupe -C BBNJ --apply # high_doi only; add --apply-medium for title+year
uv run paperful gaps -C BBNJ
# one witness for a sequence (child reports still land in state/runs/)
uv run paperful pack open --label bbnj-journal-2021-2026
uv run paperful gaps -C BBNJ
uv run paperful pack close
uv run paperful pack show
Command |
Purpose |
|---|---|
|
Environment check (Zotero / Mendeley / EndNote, paths, email, sessions, pdftotext, ocrmypdf, Playwright, grey-lit packs, LLM, browser-agent extra). Green / amber / red. TTY guide for remediations ( |
|
Fill PDFs for items already in the library. Default attaches on Zotero 10+ ( |
|
Opt-in browser-agent PDF recovery ( |
|
Read-only identifier / PDF-DOI / title-hygiene findings ( |
|
Propose patches on disk; |
|
Text layer for scanned PDFs ( |
|
Grounded LLM summary from the PDF already on disk ( |
|
Literature review from existing summary notes ( |
|
Duplicate pack on disk ( |
|
Compare PDF attachments to |
|
Preprint and published paper as one work. Dry-run writes |
|
Counts: no stored PDF, linked PDF URL only, missing DOI. Read-only. Year/type scope flags apply. Next steps are |
|
|
|
|
|
Grow a library from a keyword, DOI, ORCID, or collection ( |
|
|
|
Collection tree with “No PDF” counts |
|
Manifest summary + latest run report ( |
|
Attach already-downloaded PDFs into the configured manager |
|
Write a per-item restore folder under |
|
Recreate missing library items from those folders. Dry-run unless |
|
Load RIS, BibTeX, or EndNote XML into the configured manager. Dry-run unless |
|
Write the scoped library to RIS, BibTeX, or EndNote XML ( |
|
Local browser vault: |
|
Wrapper: headed login (or Netscape fallback) / |
|
Wrapper: headed login (or Netscape fallback) / |
|
Ping configured Sci-Hub mirrors |
|
Print the package version |
Collections can be given as a path (BBNJ/not undermine), a unique name, or
a key. Subcollections are always included. Items in several selected
collections are written once and hard-linked into the other folders.
Scope filters¶
After collection / --library selection, these optional filters shrink the
item list further (applied before --limit). They appear in the Scope line
(e.g. BBNJ, years 2023–2026, types journalArticle).
--profile NAME loads that slice from a run config so you do not repeat
-C / years / -T on every verb. -f / --run-config FILE overlays it.
Flags you pass still win. See Workflows and
Configuration.
Flag |
Effect |
|---|---|
|
Keep items dated this year or later (inclusive). |
|
Keep items dated this year or earlier (inclusive). |
|
Keep only these Zotero item types (repeatable or comma-separated). |
Year. Open ends are fine (--year-from 2023 alone). Items with no
parsed publication year are excluded whenever either bound is set.
--year-from must be ≤ --year-to.
Type. Accepts Zotero camelCase ids (journalArticle), spaced labels
(Journal Article), and hyphen/underscore forms (journal-article). Case
insensitive. Unknown tokens exit 1. Common scholarly types:
journalArticle, preprint, conferencePaper, report, book,
bookSection, thesis, manuscript, document, webpage,
newspaperArticle, magazineArticle, blogPost, dataset, standard,
patent, presentation, …
Attachments, notes, and annotations are never in scope (Zotero skips them already). Full list: Zotero’s item-types reference; paperful rejects anything not in that set.
uv run paperful run -C BBNJ --year-from 2023 --year-to 2026 -T journalArticle
uv run paperful gaps -C BBNJ -T "Journal Article" -T report
uv run paperful lint -C BBNJ --type journalArticle,preprint --strict
Same flags on run, lint, fix-metadata, dedupe, gaps, ocr, summarize,
synthesize, snapshot, and restore.
Doctor¶
paperful doctor prints one line per check.
Colour |
Meaning |
|---|---|
green |
Ready |
amber |
Degraded but you can continue (empty |
red |
Fatal if the check is |
Unpaywall needs a real email. Missing sessions: paperful session login ezproxy or scholar (system Chrome/Edge when present). Missing pdftotext: Poppler; pypdf is the fallback. Missing ocrmypdf: brew install ocrmypdf tesseract-lang or apt install ocrmypdf tesseract-ocr-eng (the Compose image ships English). Playwright is core; Chromium installs on first session login. An amber Write API means Zotero 7–9: fetch still works, but attach, fix-metadata --apply, and dedupe --apply do not.
On a TTY (Compose sets stdin_open / tty for the build-local image), amber/red
checks open an interactive Guide: each step prints what to do, waits for
Enter, then re-runs that check. Session logins still need a headed browser on
the host when you run inside Docker. Force or skip with --guide / --no-guide.
Inside Docker, docker compose run --rm paperful with no extra args is doctor.
Dry-run¶
paperful run … --dry-run talks to Zotero only (no PDF fetches). The table’s
Would-hit column is the source lane for that item, in order. --try-all
(or source_routing = false) lists every configured source.
paperful dedupe is classify-only unless you pass --apply: it writes
state/dedupe-packs/ and does not merge or write the spare-copy line. Do not
pass --dry-run and --apply together.
Exits¶
Code |
When |
|---|---|
0 |
Success (including empty dry-run) |
1 |
User error (unknown collection, bad preset, unknown |
2 |
Environment: library unreachable on |
Run summary¶
After a real run, a Run summary table lists PDFs downloaded, attached,
deferred/skipped, sources checked, and typed errors. paperful report reprints
it. JSON: paperful report --json — field list in architecture.
Output¶
out/<collection>/<Author - Year - Title -- KEY>/record.json— restore record (paperful.item.v1). Identity, full creators, abstract, tags, Extra, type-specific fields, collection membership, attachment rows, fetch provenance, and note filenames. 0.x may add keys.runwrites this when it saves a PDF.snapshotwrites one for every scoped item, including items with no PDF.out/<collection>/<Author - Year - Title -- KEY>/<file>.pdf— the PDF, when there is one.runalways writes downloads here.snapshot --pdfs allalso exports a PDF already stored in Zotero (origin: zotero_export).additional(the default) does not.nonewrites records and notes only and does not delete PDFs already on disk.out/<collection>/…/notes/— child-note HTML. Astate/summaries/<key>.htmlfile is copied aspaperful-summary.html.out/_index.jsonl— one line per item key (dirs,has_pdf,md5).out/_collections.json— collection tree.out/_history.json— pointers at the append-only ledgers understate/(manifest, patches, dedupe, runs). Sessions, cookies, and the local API key are not copied.A flat
Author - Year - Title.pdfplus*.paperful.jsonleft from an older run is moved into the item folder onsnapshotor the nextrunthat saves that file. The legacy card is folded intorecord.json.state/manifest.jsonl— one line per item attempt; the latest line per item key wins. Statuses:ok(on disk),attached(on disk + in Zotero),not_found,no_identifier,captcha,error,attach_failed.ok/attachedare never retried;not_found/no_identifieronly with--retry-failed; the rest are retried on every run. Extra fields:library_doi(DOI as stored in the manager),doi(DOI used for this attempt),doi_verified(ok/suspect/swapped/unknown/missing),pdf_doi(extracted from the file on disk after a successful download).state/metadata-patches.jsonl— append-only audit log of proposed bibliographic patches fromfix-metadata(dry-run and--applyboth append here first). One patch per item key per invocation; inspect the file for review — it is not a selective re-apply queue.state/dedupe-packs/—dedupereview packs (.jsonand.md). Not applied until--apply.state/dedupe-applied.jsonl— one line per parent merged bydedupe --apply(children and better fields land on the keeper first).state/cites/— cached OpenAlex reference lists for the snowball “Cited by N papers in this collection.” line. Keyed by the set of DOIs already in the target collection.state/version-packs/—versionsreview packs (.jsonand.md,paperful.version_pack.v1).state/versions-applied.jsonl— one line per work updated byversions --apply.state/pdf-cache/— PDFs exported from the manager so lint can read text on disk (pdftotext, thenpypdf).state/last-run.json— latest auditablerunorrecoverreport (summary + per-item outcomes). Other commands do not replace it. Historical copies land instate/runs/<timestamp>-<command>.json(run,recover,gaps,lint,fix-metadatafor dry-run and--apply,summarize,synthesize).state/packs/<id>.json— parent witness for onepack open…pack closesequence (paperful.pack.v1). Steps point at filenames understate/runs/.state/packs/currentis the open id;PAPERFUL_PACK=offskips appending.state/summaries/— HTML summaries fromsummarizewhen dest includes disk.state/reports/—synthesizeHTML report plus a JSON sidecar of source hashes.state/sessions/— Chromium profile (chromium/) plusmeta.json(no passwords). Gitignored;chmod 700. Netscape dumps also land here and asezproxy-cookies.txt/scholar-cookies.txtfor httpx.state/zotero-local-api-key.json— the Zotero write key if you chose “Always Allow”.state/mendeley-oauth.json— Mendeley access/refresh tokens aftersession login mendeley(mode0600).state/endnote-import/<stamp>/— staged XML+PDF bundle for EndNote File → Import. Paperful never edits the.enldatabase.