Commands

# 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

# 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

# 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   # headed Chromium, campus SSO
uv run paperful session login scholar    # same profile; solve Scholar CAPTCHA here
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)

# 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   # also replace title/date/venue

Command

Purpose

doctor

Environment check (Zotero, paths, email, sessions, pdftotext, grey-lit packs). Green / amber / red.

run

Find and download missing PDFs (--dry-run, --preset eoi, --upgrade-linked, --try-all, --retry-failed, --sources, --scihub, --limit). Never rewrites bibliographic fields.

lint

Read-only identifier / PDF-DOI findings (--json, --strict, --limit). Codes: missing_doi, suspect_doi, swappable_doi, pmid_no_doi, pdf_doi_mismatch, no_identifier

fix-metadata

Propose patches on disk; --apply writes them to the library (--overwrite for title/date/venue). Whitelist: doi, title, date, publicationTitle

collections

Collection tree with “No PDF” counts

report

Manifest summary + latest run report (--last-run, --json, --not-found, --status)

attach

Attach already-downloaded PDFs into Zotero

session

Local Chromium vault: `login scholar

ezproxy

Wrapper: headed login (or Netscape fallback) / --no-open probe

scholar

Wrapper: headed login (or Netscape fallback) / --no-open probe

mirrors

Ping configured Sci-Hub mirrors

version

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.

Doctor

paperful doctor prints one line per check.

Colour

Meaning

green

Ready

amber

Degraded but you can continue (empty email, missing EZProxy/Scholar session, no pdftotext, Zotero without write API)

red

Fatal if the check is Zotero :23119, out_dir, or state_dir

Unpaywall needs a real email. Missing sessions: paperful session login ezproxy or scholar. Missing pdftotext: Poppler; pypdf is the fallback.

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.

Exits

Code

When

0

Success (including empty dry-run)

1

User error (unknown collection, bad preset, --strict lint findings)

2

Environment: Zotero unreachable on collections / run / attach. Prints Next steps (start Zotero, enable local API, paperful doctor)

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 path>/Author - Year - Title.pdf

  • 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 / attached are never retried; not_found / no_identifier only 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 — proposed bibliographic patches from fix-metadata (dry-run and --apply both append here first).

  • state/pdf-cache/ — PDFs exported from the manager so lint can read text on disk (pdftotext, then pypdf).

  • state/last-run.json — latest auditable run report (summary + per-item outcomes). Historical copies land in state/runs/<timestamp>-<command>.json (run, or fix-metadata after --apply). fix-metadata --apply does not overwrite last-run.json.

  • state/sessions/ — Chromium profile (chromium/) plus meta.json (no passwords). Gitignored; chmod 700. Netscape dumps also land here and as ezproxy-cookies.txt / scholar-cookies.txt for httpx.

  • state/zotero-local-api-key.json — the Zotero write key if you chose “Always Allow”.