Commands

Snippets below use uv run so they stay short. The operator install is clone plus docker compose build, then docker compose up for the workbench (Walkthroughs, GUI). CLI one-shots: docker compose run --rm paperful … (Docker). CI builds the light image and expects doctor to exit 2 without Zotero. heavy adds [llm] [rag] [browser-agent] (see image mode). Build that image with docker compose build. Headed session login runs on the host (uv run paperful session login …). No campus access: --preset oa. Campus EZProxy: --preset eoi. paperful jobs lists verbs by job. Grow the library with snowball. People you follow (no hop) are authorwatch.

# environment check (TTY guide for amber/red)
uv run paperful doctor
uv run paperful doctor --probe   # live Scholar / EZProxy session_ok
uv run paperful config check     # unknown TOML keys; secrets belong in env
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
uv run paperful attach --allow-short-pdf          # attach denser one-pagers held by the density gate
uv run paperful attach --allow-pdf-doi-mismatch   # attach files left by --strict-pdf-doi
uv run paperful attach --item ITEMKEY --file ~/Downloads/paper.pdf

# 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
uv run paperful run -C BBNJ --upgrade-snapshot
uv run paperful run -C BBNJ --htmlpdf gated
uv run paperful urls -C BBNJ

# 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 library index — off until [rag].enabled; setup in docs/rag.md
# reads the mirror only; never calls the reference manager
uv run paperful rag ingest -C ocean/BBNJ --dry-run   # what would be indexed
uv run paperful rag ingest --library                 # index PDFs + abstracts; OCR for scans only
uv run paperful rag status                           # index vs mirror
uv run paperful rag search "impact assessment thresholds" -C ocean/BBNJ   # passages, no chat model
uv run paperful stuff --file ./draft.docx -C ocean/BBNJ --k 3
uv run paperful stuff --file ./outline.docx --lane outline -C ocean/BBNJ
uv run paperful ask "What does the Agreement require for EIAs?" -C ocean/BBNJ
uv run paperful ask --from-file questions.txt -C ocean/BBNJ
uv run paperful rag questions -C ocean/BBNJ
uv run paperful rag answered --from-extract -C ocean/BBNJ
uv run paperful ask                                  # prompt for several questions

# structured IMRaD fields — mirror only; rules always run; no [rag].enabled
uv run paperful extract-sections -C ocean/BBNJ --dry-run
uv run paperful extract-sections -C ocean/BBNJ       # state/sections/<key>.json
uv run paperful extract-sections --library --grobid  # TEI via sidecar; needs [grobid]
uv run paperful extract-sections --library --llm     # optional LLM fill; needs [llm].enabled

# 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 recover --from-last-run --dry-run    # batch keys from state/last-run.json
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

# the on-disk mirror. Every command refreshes it first; sync does only that.
uv run paperful sync --dry-run                  # what changed in the library since last time
uv run paperful sync                            # refresh, and copy library PDFs in (pdfs = all)
uv run paperful sync --full                     # read the whole library again
uv run paperful cache clean                     # list absorbed/stale pdf-cache files
uv run paperful cache clean --apply             # delete them
uv run paperful --offline gaps -C BBNJ          # work from the mirror; do not contact Zotero
uv run paperful snapshot -C BBNJ --dry-run      # re-read one collection in full
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
uv run paperful gaps -C BBNJ --list-missing --to missing.tsv
uv run paperful gaps -C BBNJ --list-missing --handoff tabs
uv run paperful gaps -C BBNJ --list-missing --handoff watch
uv run paperful gaps -C BBNJ --list-missing --handoff walk
uv run paperful reachout -C BBNJ --to reachout.csv          # contact only; never fetches
uv run paperful reachout -C BBNJ --non-oa-only --lookup --to reachout.csv
uv run paperful reachout -C BBNJ --request-rg --handoff tabs
uv run paperful inbox watch                   # poll [inbox].dir (whole library)
uv run paperful inbox drain                   # one-shot ingest
uv run paperful capture plan --doi 10.1000/example   # mirror + Zotero read; writes nothing
uv run paperful capture install-host                 # Firefox native host, user level
uv run paperful inbox watch -C BBNJ           # optional: narrow DOI index
uv run paperful refs gap -C BBNJ              # cited in PDFs, missing from library
uv run paperful refs gap -C BBNJ --format json
uv run paperful precedent "ABMT in ABNJ" -C BBNJ   # held passages + OpenAlex; always dry-run
uv run paperful precedent "ABMT in ABNJ" -C BBNJ --format json
uv run paperful coverage --from-file briefing.md -C BBNJ   # named in briefing, not in -C
uv run paperful coverage --from-note NOTEKEY -C BBNJ
uv run paperful ingest-dois --from-file dois.txt -C BBNJ            # dry-run
uv run paperful ingest-dois --from-file dois.txt -C BBNJ --format json
uv run paperful collections add --keys-file keys.txt -C BBNJ        # dry-run
uv run paperful collections add --keys-file keys.txt -C BBNJ --apply
uv run paperful run -C BBNJ --dry-run --format json
uv run paperful inbox drain --format json
uv run paperful ask "What is BBNJ?" --format json
uv run paperful snowball search "BBNJ" --gate dry-run --format json
uv run paperful authorwatch save ocean-people
uv run paperful authorwatch add ocean-people --orcid 0000-0002-1825-0097
uv run paperful authorwatch suggest ocean-people -C ocean/BBNJ --method mix --limit 15
uv run paperful authorwatch accept ocean-people --id sug_abcd --seed-from 2025-01-01
uv run paperful authorwatch run ocean-people --backfill-from 2025-01-01
uv run paperful authorwatch apply ocean-people -C Watch/Ocean          # dry-run
uv run paperful authorwatch delete ocean-people --yes
uv run paperful network orcid 0000-0001-9025-7632 --phases bbnj --partition region
uv run paperful mcp                           # optional stdio: gaps, snowball_search, snowball_trends, export, proposal_export, refs_gap, ask, precedent, stuff
uv run paperful export-proposals PACK out.bib # BibTeX/RIS from snowball or authorwatch inbox on disk
uv run paperful serve                         # localhost HTTP (needs uv sync --extra serve)
uv run paperful ingest-dois --from-pack state/refs-gaps/<stamp> -C BBNJ
uv run paperful ingest-dois --from-pack state/coverage/<stamp> -C BBNJ
uv run paperful ingest-dois --from-pack state/precedent/<stamp> -C BBNJ
uv run paperful ingest-dois --from-file dois.txt -C BBNJ --apply --tag bbnj
uv run paperful acronyms -C BBNJ              # harvest ALL CAPS tokens (dry-run)
uv run paperful acronyms -C BBNJ --apply      # write state/acronyms/<scope>.json
uv run paperful glossary validate             # check state/glossary/*.json
uv run paperful glossary export-vale          # Vale swap YAML from aliases (stdout)
uv run paperful glossary export-vale --out styles/Paperful/swap.yml
uv run paperful authors -C BBNJ               # author/org frequency (dry-run)
uv run paperful authors -C BBNJ --apply       # report + proposed field author pack
uv run paperful inbox proposals list
uv run paperful inbox proposals apply <id>
uv run paperful notes delete -C BBNJ --type summary --except-model qwen2.5:7b
uv run paperful notes delete -C BBNJ --all --apply --yes

# 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
# dedupe + browser handoff + summarize template: docs/workflows.md § 4
# Sci-Hub is off by default; opt-in only, at your own risk — see scihub.md

# 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

# 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             # progress bar on stderr; stdout is 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

doctor

Environment check (Zotero / Mendeley / EndNote, paths, email, sessions, pdftotext, ocrmypdf, vale, paperguard, Playwright, grey-lit packs, CORE key optional, opt-in Camofox when [camofox].enabled, opt-in GROBID when [grobid].enabled, SerpApi, LLM, browser-agent extra). Green / amber / red. TTY guide for remediations (--guide / --no-guide). --probe hits Scholar / EZProxy session_ok (vault browser when available) and ambers when cookie files exist but CAS/captcha. --json prints {name, status, code, detail} and exits 2 only when a fatal row is red (zotero_down, zotero_api_off, zotero_bad_host, unpaywall_email, plus red out_dir / state_dir). Write API on Zotero 7–9 is amber (zotero_no_write) and does not exit 2. Empty email is red only when unpaywall is in sources. LLM disabled stays green. Missing ocrmypdf is amber. Missing vale is amber (optional for critique house style). Missing paperguard is amber (optional for critique --forensics). [rewrite].areas other than style with [llm] off is amber. CORE is green either way (key set vs optional).

config check

Validate config.toml: unknown top-level keys and sections (with did-you-mean hints), and secrets that should live in the environment (core_api_key, Mendeley client secret, etc.). Exit 1 when issues are found. Complements doctor (paths and services).

sr

Systematic review scaffold (local; no scheduler). init --slug ID writes state/sr/<slug>/protocol.json (paperful.sr.protocol.v1). screen --identity … --decision include|exclude|maybe appends screening.jsonl (paperful.sr.screen.v1). status prints screening counts. PRISMA flow export and workbench UI are later slices.

run

Fill PDFs for items already in the library. Default attaches on Zotero 10+ (--dry-run does not). --preset oa drops EZProxy; --preset eoi is OA + EZProxy (--upgrade-linked, --try-all, --retry-failed, --sources, --scihub, --browser-agent / --no-browser-agent, --camofox / --no-camofox, --strict-pdf-doi, --year-from / --year-to, --type / -T, --limit, --serpapi-max, --promote gated|auto). When [llm].enabled and the browser-agent extra is installed, appends browser_agent (default [browser_agent].during_run; --no-browser-agent skips it for one run). Opt-in Camofox ([camofox].enabled) injects a scripted Camoufox recover lane after vault browsers and before browser_agent (--camofox / --no-camofox). Default [fetch].order = "policy" runs Scholar (if opted in) late: one query immediately before each browser_agent call, or one late Scholar phase when the agent is off. [fetch].order = "list" keeps sources order. Opt-in SerpApi ([serpapi].enabled) is a paid Scholar search after those local lanes; --serpapi-max caps searches this run (0 = unlimited). Soft-blocked OA PDF URLs (empty httpx body) retry via the vault browser (SSO hop, citation PDF link, download control) and can hand off with `–handoff list

recover

Opt-in browser-agent PDF recovery (--item KEY repeatable, --from-last-run, --from-last-run-mode browser_agent_miss|missing|browser_agent_not_found, --limit, --dry-run, --no-attach). Also auto-appended on run when [llm].enabled and other vault browser lanes (Scholar, EZProxy, htmlpdf) fail — or when an OA lane soft-blocked a found PDF URL. Policy mode tries Scholar once immediately before that agent call. Needs Python 3.11+, paperful[browser-agent], and a session vault. Manual report: state/runs/<stamp>-recover.json. See LLM.

lint

Read-only identifier / PDF-DOI / title-hygiene findings (--json, --strict, --year-from / --year-to, --type / -T, --limit). Codes: missing_doi, suspect_doi, swappable_doi, pmid_no_doi, pdf_doi_mismatch, title_html, title_all_caps, title_filename, title_unusable, no_identifier, plus pdf_identity_mismatch when [lint].llm_pdf_match is on. Writes state/runs/<stamp>-lint.json (also for --json, before a --strict exit 1).

screen

Retraction / EoC / withdrawal (Crossref + OpenAlex), optional PubPeer ([screen].pubpeer + PUBPEER_DEVKEY), and PPS fingerprint CSVs on PDF text (tortured, scigen, mathgen, suspect). Report by default; --apply writes paperful pps: tag and paperful-pps note. --refresh bypasses caches. --strict exits 1 on any finding; --format json is the agent envelope. Same scope flags as lint. Not in paperful all. See screen.md. Writes state/runs/<stamp>-screen.json.

rewrite

Apply an existing critique pack to a draft (--pack PATH, --item KEY, or --file PATH). Never re-runs critique. Default area style (hygiene). Repeatable --area (style, cites, structure, comments, all) and --output (edits, sidecar, tracked, odt, pdf). --comments off|substantive|all. --dry-run prints the plan and writes nothing. Never overwrites the source. Optional [rewrite].openxml_audit (uv sync --extra openxml) validates office files vs the source. Not in paperful all. See rewrite.md. Writes state/rewrites/<id>/<stamp>/ and state/runs/<stamp>-rewrite.json.

critique

Local manuscript pack (--item KEY or --file PATH, XOR). Prints elapsed phase lines. Deterministic checkers, metrics, and heuristic AI-tells always (including house glossary expansions when state/glossary/ has entries). Repeatable --lane (reviewers, marg, adversary, outline, claims, verify, context, context-web) selects suggested review lanes ([critique].lanes; empty falls back to [critique].llm / [critique].advanced shims). --llm / --no-llm and --advanced / --no-advanced are aliases for reviewers / marg. --reviewer ID selects a subset of critique.reviewers; --force runs review lanes despite identity lint. --grobid / --no-grobid overlay [grobid].enabled for MARG-S / claims / verify PDF chunks. --ai-model requests an optional Binoculars extra (git overlay; CLI on-switch only). --vale requests an optional Vale house-style pass (CLI on-switch only; needs vale on PATH and a .vale.ini). --forensics requests an optional PaperGuard scan (CLI on-switch only; needs paperguard on PATH). No scores. --apply writes a paperful-critique child note for --item only. --show-dismissed keeps dismissed rows in pack.md. critique dismiss ID keeps a finding out of later pack.md. paperful jobs → completeness. Not in paperful all. See critique.md. Writes state/critiques/<id>/<stamp>/ and state/runs/<stamp>-critique.json.

acronyms

Collection-scoped harvest of all-caps tokens from mixed-case titles, abstracts, and venues (--min-count, --apply). Frequency and shape only; no model. Dry-run prints the list. --apply writes state/acronyms/<scope>.json (paperful.acronyms.v1) and keeps a hand-edited extra list. fix-metadata and parent create keep those tokens uppercase when recasing ALL CAPS titles.

glossary

House-list expansions for critique (validate, export-vale). JSON in state/glossary/ (paperful.glossary.v1): token, canonical expansion, optional aliases. Critique fuzzy-matches those phrases; export-vale writes exact Vale swap rules. See critique.

authors

Collection-scoped creator frequency (--min-count, --max-authors, --apply, --format json). People by name fingerprint; corporate name-only creators as orgs. Dry-run prints tables. --apply writes state/reports/<scope>-authors.json (paperful.authors_report.v1) and merges top people into state/author-packs/<slug>.proposed.toml (preserve existing listing URLs). Then snowball packs promote. Orgs stay report-only. Optional CRM: Twenty and SearXNG.

fix-metadata

Propose patches on disk; --apply writes them to the library (--overwrite replaces title, date, or venue even when you edited them — dry-run first; --year-from / --year-to, --type / -T, --limit, --offset to resume a long scope). Whitelist: doi, title, date, publicationTitle. HTML title cleanup, ALL CAPS → Title Case (also applied when creating parents and when adopting a DOI work title; harvested acronyms in state/acronyms/ stay uppercase), a blank or citation-shaped title replaced from the DOI work, and verified PDF-DOI adoption included; filename titles stay lint-only unless [fix_metadata].llm_title proposes a grounded title (source = "llm_title"). Dry-run and --apply both write state/runs/<stamp>-fix-metadata.json (patches_applied only after --apply).

ocr

Text layer for scanned PDFs (ocrmypdf). Dry-run unless --apply. --max-minutes stops a long pass early. Rewrites the PDF under out/ (exports a manager-only file there first). --attach uploads that file as a new attachment and does not trash the scan. Languages: [ocr].languages (default eng). Failed or missing items land in state/ocr-ignore.jsonl after --apply unless --no-auto-ignore-failures. --force-retry includes ignored keys. Not in the default all chain; add it with --steps.

ocr-ignore

Skip list for paperful ocr in any scope (list, add, remove, import-failures). Ledger: state/ocr-ignore.jsonl.

rag ingest

Build the library index from the mirror (out/): PDF text, or the abstract when there is no readable PDF. Scans get OCR first ([rag].ocr = "auto" rewrites them under out/, without --apply); PDFs with a text layer do not. --item / -C (a folder path under out/) / --library, --year-from / --year-to, --type, --limit, --dry-run, --no-ocr, --retry-failed, --force. Incremental and resumable. Never calls the reference manager. Needs [rag].enabled and paperful[rag]. See rag.md.

rag status

What the index holds and how far it is behind the mirror (items indexed, scans waiting for OCR, failures, PDFs held only by the reference manager). --json. Works with [rag].enabled = false.

rag search

Indexed passages closest to a query, with paper and pages. No chat model. -k, --item / -C, --year-from / --year-to, --type, --json.

rag questions

Extract research questions from indexed papers (rules; optional --llm / [rag].extract_questions_llm). --item / -C / --library, year/type/--limit, --dry-run. Writes state/rag/questions/<key>.json. Also on Advanced Index. See rag.md.

extract-sections

IMRaD-style fields from mirror PDF text (rules; optional GROBID sidecar; optional --llm / [extract_sections].llm). --item / -C / --library, --year-from / --year-to, --type / -T, --limit, --dry-run, --force, --grobid / --no-grobid. Skips existing state/sections/<key>.json unless --force. Writes paperful.sections.v1 (fields entries: text, provenance rule|grobid|llm, optional heading / pages; optional sections raw map; optional engine). Does not need [rag].enabled; reuses state/rag/text/ when present. Heading synonyms shared with the RAG chunker. TEI cache: state/grobid/<sha256>.tei.xml. --format json → paperful.agent.json.v1. Run report: state/runs/<stamp>-extract-sections.json. Also on Advanced Index. paperful jobs → completeness. No Zotero writes. GROBID, config.md, rag.md.

rag answered

Ask whether the corpus already answers operator or extracted questions (--from-file / --from-extract, --after-item, scope, --ask-mode, --force). Writes state/rq-answered/<stamp>/ (pack.md). Also on Advanced Index. See rag.md.

ask

Answer a question from the index, streamed, with cited sources and pages (-k, --item / -C, --year-from / --year-to, --type, --focus, --ask-mode simple|advanced, --prompt FILE, --from-file batch → state/ask-batch/, --force, --apply/--to, --no-stream, --show-context, --thread, --format json). --ask-mode overrides [rag].ask_mode for this run. JSON implies --no-stream and needs a question (no TTY loop). Prompted ask (no question) keeps a thread under state/rag/threads/; follow-ups rewrite a standalone retrieval query. One-shot CLI still answers independently unless --thread. Writes state/runs/<stamp>-ask.json. Needs [rag].enabled, [llm].enabled and a built index. Workbench Ask covers the same verbs except TTY / --show-context / run profiles — gui.md, rag.md.

summarize

Grounded LLM summary from the PDF already on disk (--item / -C / --library, --year-from / --year-to, --type / -T, --order newest|oldest|library, --limit, --max-new N / --max-minutes F (stop early), --collections-file FILE (one collection per line), --prompt FILE, --force, --to disk, zotero, or both). Default writes state/summaries/<key>.html and one child note tagged [summarize].tag. Without --force, existing summaries for the chosen dest are skipped when the footer matches the current model and prompt sha (resume-safe; a model or [summarize].style change rewrites). [summarize].style = ste enables ASD-STE100 constraints and post-check (ste_violation_policy, optional ste_word_list_path). --order (default [summarize].order, or library) sorts before --limit so a slow model can finish recent or old work first; undated items stay last under newest / oldest. --to disk skips Zotero. --apply requires the note and conflicts with --to disk. Writes state/runs/<stamp>-summarize.json. See LLM.

synthesize

Literature review from existing summary notes (--item / -C / --library, same year/type/--limit flags, --layout default|conflict|review, --prompt, --to, --dry-run, --force, --report-collection). --layout selects a bundled HTML shape; --prompt FILE still wins. Writes state/reports/<slug>.html and, unless --to disk, a standalone note in each -C collection. See LLM.

dedupe

Duplicate pack on disk. Phases (--phase high_doi|grey_id|grey_host|medium_title_year|all, default all): same DOI, same grey identifier, same grey host, then title+year. Classify-only until --apply, which writes a spare-copy line, then merges DOI extras onto the keeper and trashes the emptied parent. If that line cannot be written, the merge does not run. --apply merges high_doi and grey_id; grey_host and title+year need --apply-medium. Held when same-DOI titles diverge. --format json is the agent envelope (--json is the legacy pack-path array). Same year/type scope flags as run. See dedupe.

attachments

Compare PDF attachments to out/. Report-only reads children from the mirror (record.json) and proves stored files by MD5 in the item folder; with the manager down it notes unchecked attachments instead of calling them ghosts. Surgery needs --fix-broken, --merge-files, --rename, or --link together with --apply (exit 2 when the manager is down). Repairs use a matching file already under out/. --link is Zotero personal libraries only. Mendeley can upload and delete cloud files; it cannot link. EndNote is report-only (missing paths, duplicate PDFs, filename drift) and does not edit the library. Same MD5 on two parents is reported and left to dedupe. Not in paperful all.

versions

Preprint and published paper as one work. Dry-run writes state/version-packs/ (paperful.version_pack.v1). --apply puts the published citation and PDF on the older parent, keeps the preprint id and PDF, and trashes a sibling only after that PDF is attached. Title-only pairs are listed and not applied.

gaps

Counts: no stored PDF, linked PDF URL only, missing DOI. --list-missing prints key/title/DOI/URL/hint (openable_url / doi_only / hard_miss / author_request); --to writes .tsv or .md. `–handoff list

reachout

Contact mode for items with no stored PDF. Never fetches. Console table plus --to .csv/.tsv/.md (title, author, email, source, DOI, RG URL). Email from item extra/mailto/abstract first; Twenty state/author-contacts/ when [twenty].enabled; --lookup searches People for remaining names. --non-oa-only keeps paywalled / no_oa / license_blocked. --handoff tabs|walk opens existing ResearchGate publication URLs only (you click Request). Does not send mail.

twenty lookup

Opt-in Twenty People match for -C authors. Dry-run unless --apply (proposed author-pack websites + state/author-contacts/ only). Needs [twenty].enabled, base_url, env TWENTY_API_KEY. Does not write the CRM.

twenty sync

Create or enrich People from -C authors. Dry-run unless --apply. Unique miss → create (first + last, collection keyword, Paperful note). Unique match → fill blank homepage/email and append extras; never replaces a primary. Ambiguous names and corporate creators are skipped. --limit, --yes when many creates. --format json. Twenty and SearXNG.

network

ORCID ego co-authorship graph (orcid / show / list / export / attributes). --phases bbnj (or a [[phases]] TOML file); --partition region|discipline|sector for Gould–Fernandez roles. Writes state/network/<run-id>/ (paperful.network.v1; --out-dir override). Degree always; betweenness / constraint / brokerage / --figures / GEXF need paperful[network]. --format json is paperful.agent.json.v1. Discover → People → Network. Not the Queue citation map. Co-author network.

authorwatch

People you follow → their papers (save / add / remove / resolve / suggest / accept / delete / import / run / apply / show / briefing / digest). briefing adds OpenAlex recent works and co-authors for pollable people (--format json for agents). digest writes inbox digest.md. suggest -C ranks corpus/OpenAlex candidates; accept --id (+ optional --seed-from) adds to the list. ORCID or unique OpenAlex id required to poll. First run is a cursor baseline (proposes 0, no library open) unless --backfill-from. apply -C creates metadata parents (dry-run unless --apply; --apply needs write API) and writes ingested.md. Does not need [snowball] enabled. Social follows: saved HTML/CSV via import --file (no live scrape). Distinct from snowball watch and inbox watch. Combine with watches via keepup. Author watch.

keepup

Alert loop / what-cites (save / list / show / delete / run / digest / apply / dismiss). Sources: --watch, --authorwatch, --source cites. Configurable --interval-days (default [keepup].interval_days = 7). Baseline proposes 0; later runs merge into state/keepup/<name>/. run --apply creates parents; dismiss --doi mutes. No built-in scheduler — cron keepup run. Keepup.

inbox

PDF drop-folder sidecar (watch / drain / proposals). Needs [inbox].dir. Default match is DOI-only against missing-PDF items (whole library unless -C). Optional [inbox].match ladder: hold before quarantine, OCR-for-match, title+year, llm_when_thin. [inbox].create is attach_only (default), create_gated (proposals), or high-bar create_auto (unique DOI only). [inbox].route (off / gated / auto) picks an existing collection when -C is omitted (snowball / last scope / DOI / title, optional [inbox].llm_route). Created parents are tagged inbox-created, inbox:<dirname>, [ingest].default_tags, and --tag. --format json on drain and `proposals list

capture

Firefox add-on host. plan checks the mirror and Zotero and writes nothing. apply reads one JSON object on stdin and writes out/ only (collection, tags, note). session prints state/handoff/session.json. install-host writes the user-level native-messaging manifest (macOS, Linux, or Windows). --stdio is the host loop. See Firefox.

notes delete

Trash Paperful-owned notes only (paperful.note.v1 or known tags). Dry-run unless --apply. --type summary|review|attach|duplicate|linked|snowball|briefing|screen|capture|critique is the note kind (not Zotero -T / --item-type on parents). --model / --except-model, --all (every owned note in -C / --library / --item). --apply --all confirms on a TTY unless --yes. Never parents. --format json.

refs gap

Works cited inside collection PDFs that are not in the library fingerprint. Always dry-run. Writes state/refs-gaps/<stamp>/ (paperful.refs_gap.pack.v1 plus dois.txt) and a state/runs/*-refs gap.json report (with optional witness). --format json prints paperful.agent.json.v1. Then ingest-dois --from-file / --from-pack. Distinct from gaps (items already in scope missing a PDF).

coverage

DOIs (and optional titles) named in a briefing note or file that are not under -C. Always dry-run. --from-note (key or path) or --from-file. Writes state/coverage/<stamp>/ (paperful.coverage.pack.v1 plus dois.txt) and a state/runs/*-coverage.json report (with optional witness). --format json prints paperful.agent.json.v1. Rows: in_collection / missing / ambiguous (in library, outside -C). Then ingest-dois. Sibling of refs gap. Not an MCP tool.

precedent

Phrase → held RAG passages plus OpenAlex keyword/similar works not claiming novelty. Always dry-run. Stamps held vs public-metadata-only. Writes state/precedent/<stamp>/ (paperful.precedent.pack.v1, pack.md, dois.txt) and a state/runs/*-precedent.json report. --format json. Empty public hits mean OpenAlex returned nothing for this query/budget — not that the idea is new. Then ingest-dois --from-file / --from-pack. Distinct from ask --focus gaps (held PDFs only) and refs gap (cited-in-PDF).

stuff

Extra library citations per sentence (--lane manuscript, default) or outline section (--lane outline) from --item KEY or --file PATH (XOR). Same retriever as rag search; no Ask narrative, no invented BibTeX, no library write. --k capped by [rag].top_k. Fail closed if the index is missing or empty. --dry-run prints counts. --format json. Writes state/stuff/<id>/<stamp>/ (paperful.stuff.pack.v1, pack.md) and a state/runs/*-stuff.json report. Source item is never a hit for itself. Distinct from refs gap (cited-but-not-owned) and precedent (OpenAlex). Not in paperful all. See stuff.

ingest-dois

DOI list → metadata parents in -C. Dry-run unless --apply. Reports created / exists / unresolved / held. --format json. --tag plus [ingest].default_tags and from-<file-stem>. --from-pack accepts refs-gap, coverage, or precedent packs. Does not fetch PDFs (run after).

collections

list (or bare collections) — collection tree with “No PDF” counts. add --keys-file — file existing item keys into -C (membership only; dry-run unless --apply; added / already-in / not-found). --format json. Complements ingest-dois (create parents). Zotero / Mendeley; EndNote refuses --apply. Not an MCP tool.

mcp

Optional stdio MCP over the same JSON channel. Read-only tools: gaps, snowball_search (dry-run), snowball_trends (publication-year counts), export (BibTeX/RIS text in envelope), proposal_export (pack under state/snowball/ or state/authorwatch/), refs_gap, ask, precedent, stuff. Prefer paperful … --format json from a shell. collections add is CLI-only and is not exposed.

search

Discover grey PDFs and landings via SearXNG (--hosts, --filetype pdf|none, --max, --probe/--no-probe, --run-id, --out-dir, --list). Dry-run table unless --apply -C (metadata parents only; PDFs stay run / Wanted). Exit 2 when [searxng].base_url / SEARXNG_BASE_URL is unset. --format json is paperful.agent.json.v1. Writes state/search/<run-id>/ (paperful.search.v1). watch save / run / apply / show / list is a pull-only URL poll (state/search/watches/<name>/, paperful.search.watch.v1; --interval-days default 7; no built-in scheduler). Discover → Web. Not a run source. Not a Google/Scholar HTML scrape. Web grey search.

urls

Probes metadata and linked-PDF URLs for a scope (HEAD/GET). Report only unless --apply, which rewrites a URL only when a grey playbook already knows the PDF target. --limit, --format json.

htmlpdf

Academic page-snapshot proposals: proposals list / apply / reject. [htmlpdf].academic = gated writes state/htmlpdf/proposals/; apply attaches a snapshot-tier PDF. Printing web and news pages during run is the htmlpdf source, not this verb. See sources.

serve

Localhost workbench and JSON API (127.0.0.1:8765). Needs paperful[serve] (uv sync --extra serve; the dev group already includes Jinja and multipart for tests). Non-loopback --host requires --allow-remote-bind (no HTTP auth unless PAPERFUL_UI_TOKEN / --ui-token is set — see SECURITY.md). JSON routes: health, doctor, collections, last-run, dry-run refs-gap / ask (those do not write the library). HTML: Wanted, Discover, Ask, Library, Activity, System; Advanced Index (ingest, search, rag questions, rag answered), Briefs, Repair, Mirror, Critique, Suggest edits (/rewrite), Stuff (/stuff), Settings. Grab fetches to out/ only after a Preview of those same rows (ticked keys or Missing list). Settings [ui].attach_mode is manual (default) or auto (Grab also attaches when the library can write — Zotero 10 or newer); the Settings control appears after the first workbench Attach. The main Wanted Attach button writes attachable pending PDFs (skips DOI mismatch / short PDF unless Advanced). Advanced Attach, Discover apply, and Repair/Mirror Apply need Preview, then a review token. TTY, Sci-Hub, and collections add stay CLI. docker compose up serves this UI. heavy also has RAG/Ask extras for Ask. See gui.md.

all

gaps → run --try-all --retry-failed --upgrade-linked → lint → fix-metadata --apply → summarize --apply. Stops on the first failure. --steps / --skip choose steps; --label names the pack. --dry-run skips summarize and does not apply metadata. --browser-agent / --no-browser-agent and --camofox / --no-camofox pass through to the run step. Run-step flags also forward: --handoff, --include-doi-tabs, --downloads-dir, --promote, --htmlpdf, --upgrade-snapshot, --keep-snapshot, --ezproxy-relogin, --twenty-writeback, --request-rg, --re-request. Optional step gaps_handoff (or --after-handoff-gaps when --handoff is set) runs gaps --list-missing with --gaps-handoff or the same --handoff. Profiles may set the same keys. --profile / -f load a saved run config. Opens a pack when none is open. --format json prints one envelope for the whole sequence (nested substeps stay quiet on stdout). See Workflows.

profile

list / show / save — named run configs beside config.toml (profiles/<name>.toml or [profiles.*]). show prints the merge all would use. save does not edit config.toml.

snowball

Grow a library from one or more keywords, one or more DOIs, one or more ORCIDs, or a collection (search, hybrid, doi, orcid, collection, apply, run --profile, resume, profile save, trends, watch save / run / show / apply / rebaseline / briefing / digest, briefing --run-id, digest --run-id). trends counts works by publication year for a query or search/hybrid --profile (read-only; --format json; see snowball.md). search / hybrid take several keyword terms (AND by default; --or

matches any; a trailing * expands client-side); doi and orcid take

several positionals or --seeds-file; profile save repeats --query / --doi / --orcid (and --or for keywords). hybrid is keyword hits then one hop. continue RUN takes the next hop after Keep/Skip when a stopping rule is on (--stop-rule off by default; marginal_yield, asymptotic_coverage, budget, or any; also --stop-eps, --stop-k, --stop-max-screened). stop_tau and stop_min_points are config and profile only. continue is not resume (OpenAlex deferred.json). --format json includes continue. --direction defaults to refs+keywords (refs, cites, both, keywords, similar, or a combination such as refs+similar). similar is one ranked hop (shared references, then Semantic Scholar recommendations). --direction keywords expands OpenAlex keywords (--keyword-limit 1–5, --keyword-hop-limit a positive integer; all is refused). search / hybrid also union OpenAlex keyword tags by default (--no-search-keywords or search_keywords = false for full-text only). --cites-query limits references and cited-by to an OpenAlex search (title, abstract, or full text). --study-design (repeatable) filters OpenAlex by study design (rct, meta-analysis, systematic-review, …). --listed-in (repeatable) keeps works whose journal is on an OpenAlex source list (doaj, medline, jufo-3, …; family aliases cwts, jufo, norway, ki-jl, jpps, abdc). Membership is the maintainer’s list, not a quality score. Seeds with no keywords are reported and skipped on that side. --tag (repeatable) plus [snowball].default_tags and from-<seed-slug> are applied on --gate auto / apply creates. Dry-run unless a writing gate is set. approve-each is for short lists. fetch_pdfs is off, fast, or full on the new keys only. A saved queue with no deferred OpenAlex work resumes into create and PDF fetch. --format json on crawl (search / hybrid / doi / orcid / collection), run, resume, and apply prints one paperful.agent.json.v1 object (paths + summary; progress on stderr). Mixed apply writes exit 3. watch re-runs a profile, remembers seen works, and proposes new arrivals on disk (watch run never creates items; watch run --refresh skips the last-run date cursor and rewrites responses.json; watch apply -C [--apply] creates from inbox.jsonl and writes ingested.md). Additive prisma counts and watch_drift land on summary.json / the watch command report. briefing writes thin markdown from a queue or watch inbox (no silent creates; --apply plus -C files a collection note tagged paperful:frontier-briefing). digest writes digest.md: overlap-ranked new rows, a suggested -C, and queue / apply / resume paths. watch run --digest writes the watch digest after a successful run. Prefer keepup for the cron alert loop. run and all refuse kind = snowball profiles. How a hop is cut.

pack

open / close / show — group the run reports from one operator sequence into state/packs/<id>.json (paperful.pack.v1). show does not open the library. PAPERFUL_PACK=off keeps a command out of the open pack.

report

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

attach

Attach already-downloaded PDFs into the configured manager, or ingest a hand download with --item KEY --file PATH. --allow-pdf-doi-mismatch and --allow-short-pdf unlock holds from --strict-pdf-doi and the one-page density gate

sync

Bring out/ up to date with the library: rewrite the folders of items that changed since the last refresh, mark items that left, copy in PDFs the library holds (--pdfs all|lazy|none, default from [mirror].pdfs). --full reads the whole library. --dry-run reads and counts, writes nothing. Refuses to mark most of the mirror as gone (a different library) unless --accept-gone. Every other command runs the same refresh before it reads, without the whole-library PDF pass.

snapshot

Re-read a collection (or --library) in full and rewrite its item folders (record.json, optional PDF, notes) plus index, collection tree, and ledger pointers. --pdfs all|lazy|none. --dry-run counts without writing. Year/type scope flags apply. Needs the manager running.

restore

Recreate missing library items from those folders. Dry-run unless --apply. --apply creates missing items, attaches a local PDF when the live item has none, and adds missing notes. Does not overwrite bibliographic fields. Year/type scope flags apply.

cache

cache clean lists throwaway files under state/pdf-cache/ already absorbed into out/ or stale versus the record MD5. Dry-run unless --apply. Does not delete out/.

import

Load RIS, BibTeX, or EndNote XML into the configured manager. Dry-run unless --apply.

export

Write the scoped library to RIS, BibTeX, or EndNote XML (--pdfs copies files for XML).

export-proposals

BibTeX or RIS from a snowball run dir, watch inbox, authorwatch inbox, or a .jsonl path under state/.

session

Local browser vault: login scholar|ezproxy|mendeley (--engine is for the browser slots), status, export

playbooks

propose / promote — draft and install learned PDF recipes from state/fetch-wins.jsonl (vault clicks/rewrites and browser-agent click: / rewrite wins, including optional steps on agent rows) into grey_playbooks_dir/learned.toml. Default [playbooks].promote is gated. auto may promote flukes. probe --corpus FILE fetches each target and passes only when the first candidate is a PDF of at least --min-bytes (default 10KB). Exit 1 on any miss. --save writes a replayable JSON snapshot. The shipped public corpus is tests/fixtures/grey/corpus.toml

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

openalex-store

Optional local OpenAlex parquet snapshot helpers (build-cites; local DuckDB or SSH). Most installs leave [openalex_store] unset and use the live API. See config.

run-report-counts

One-line counts summary for a run report (utility; used by Nightshift index lines).

run-report-new-count

Count new model summaries in a run report; skips do not count (utility).

jobs

Print verbs grouped by job (library, find, completeness, mirror, control, plus utility).

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.

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).

Most verbs need -C or --library (or a profile that sets one). inbox watch / drain are the exception: with no scope they default to the whole library so a shared drop folder is not tied to one collection.

--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

--year-from YEAR

Keep items dated this year or later (inclusive).

--year-to YEAR

Keep items dated this year or earlier (inclusive).

--type / -T TYPE

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. Default session rows are file-presence only. Pass --probe to hit Scholar / EZProxy session_ok (vault Chromium when available); cookie files that still bounce to CAS or a captcha become amber.

Colour

Meaning

green

Ready

amber

Degraded but you can continue (empty email, missing or expired EZProxy/Scholar session, no pdftotext, no ocrmypdf, missing vale, Playwright/Chromium not ready, Zotero without write API, LLM enabled but daemon/model/extra not ready, small model for the browser agent)

red

Fatal if the check is Zotero :23119, Mendeley API, EndNote library, out_dir, or state_dir

Unpaywall needs a real email. Missing or expired 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). Missing vale is optional (critique house style; not in the Compose image). Playwright ships in the paperful[browser] extra (and in the Compose image); Chromium installs on first session login. doctor names the extra when it is missing. An amber Write API means Zotero 7–9: fetch still works, but attach, fix-metadata --apply, and dedupe --apply do not.

On a host TTY, amber/red checks open an interactive Guide: each step prints what to do, waits for Enter, then re-runs that check. Inside Docker, doctor prints the same fix steps once (no Enter wait — Compose owns Ctrl-C / stdin on compose up). Opt in to the interactive walk with docker compose run --rm paperful doctor --guide. Session logins still need a headed browser on the host. Force or skip with --guide / --no-guide. docker compose up starts the GUI; CLI checks use docker compose run --rm paperful 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 policy order (Scholar late unless [fetch].order = "list"). --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.

Agent channel (--format json and mcp)

Batch verbs that support --format json emit one paperful.agent.json.v1 object on stdout (schema, command, exit, summary, items, paths, flags). paperful mcp is optional stdio JSON-RPC: each tool returns the same envelope in MCP text content. Read-only MCP tools today:

Tool

CLI equivalent

Notes

gaps

gaps --format json

Counts only (no --list-missing handoff)

snowball_search

snowball search … --gate dry-run --format json

Always dry-run; never creates parents

snowball_trends

snowball trends … --format json

Publication-year counts; read-only

export

export (scoped library)

BibTeX/RIS in summary.bibliography; no file path required

proposal_export

export-proposals

Pack under state/snowball/ or state/authorwatch/

refs_gap

refs gap --format json

Always dry-run

ask

ask --format json

Needs [rag] + ingest. Optional ask_mode (simple / advanced); omitted uses [rag].ask_mode

precedent

precedent --format json

Always dry-run. Held + public-metadata-only stamps; never a novelty claim

stuff

stuff --format json

Disk-only extra cites from the local index. item or file; lane manuscript|outline

collections add, snowball apply, and every other --apply path stay CLI-only (not MCP tools). Prefer paperful … --format json from a shell when you already have a subprocess wrapper.

Exits

--format json prints one paperful.agent.json.v1 object on stdout. Progress bars and logs go to stderr. Early environment failures may print a human ladder on stdout and skip the envelope — treat unparseable stdout plus the process exit code as the result.

Code

When

0

Success (including empty dry-run). not_found / paywalled misses on run are not failures. ingest-dois held and unresolved, and collections add not-found / already-in, are findings, not exit 3.

1

User error (unknown collection, bad preset, unknown --phase, --dry-run together with --apply, --year-from > --year-to, unknown --type, --strict lint findings, unknown --item key, LLM not enabled/misconfigured for recover / summarize / synthesize, recover on Python < 3.11, note write refused, --to disk together with --apply or --report-collection, synthesize still over budget after 3 reduce passes, pack open while one is open, pack close when none is open, unknown --pdfs). Also: a batch where every attempted write failed.

2

Environment: library unreachable on sync, snapshot, restore, attach, attachments --apply, or any --apply (including authorwatch apply --apply); or unreachable on any library command when there is no mirror under out/ yet. With a mirror, read commands (collections, gaps, lint, export, attachments report-only, dry-runs, summarize --to disk, run without attach) carry on from it and say how old it is. authorwatch save / add / run without --backfill-from do not open the library; authorwatch suggest and run with --backfill-from / --seed-from do (OpenAlex budget spent is also exit 2). Prints Next steps (Zotero local API, or Mendeley login, or EndNote .enl; then paperful doctor)

3

Partial write batch: some rows succeeded and some failed. run counts attached vs attach_failed (not not_found). Also inbox drain (attached vs errors), fix-metadata --apply, dedupe --apply, snowball apply, authorwatch apply --apply, summarize, ocr --apply, notes delete --apply, ingest-dois --apply (created vs create errors), collections add --apply (added vs errors), and other batch verbs that emit paperful.agent.json.v1. The envelope has "partial": true. Dry-run mixed findings stay 0.

TTY-only (a GUI or agent must not claim these): session login, doctor --guide, mid-run EZProxy re-login, and notes delete --apply --all confirm unless --yes.

Run summary

After a real run, a Run summary table lists PDFs downloaded, attached, why a PDF was not saved (captcha, cloudflare, not found, and the other blocks), publisher prices the browser agent saw on those misses, 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. Required keys frozen; extras may be added. run writes this when it saves a PDF. A refresh writes one for every item, including items with no PDF, and rewrites it after each change in the library.

  • out/<collection>/<Author - Year - Title -- KEY>/<file>.pdf — the PDF, when there is one. run always writes downloads here. With [mirror].pdfs = "all" (the default) sync also copies in a PDF already stored in Zotero. lazy copies one the first time a command needs it. none keeps them out and does not delete PDFs already on disk.

  • out/<collection>/…/notes/ — child-note HTML. A state/summaries/<key>.html file is copied as paperful-summary.html.

  • out/_index.jsonl — one line per item key (dirs, has_pdf, md5, child keys).

  • out/_sync.json — library version the mirror was last refreshed to.

  • out/_collections.json — collection tree.

  • out/_history.json — pointers at the append-only ledgers under state/ (manifest, patches, dedupe, runs). Sessions, cookies, and the local API key are not copied.

  • 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, retryable. 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), reason (for example strict_pdf_doi or short_pdf when attach is held — batch attach skips those unless --allow-pdf-doi-mismatch or --allow-short-pdf).

  • state/metadata-patches.jsonl — append-only audit log of proposed bibliographic patches from fix-metadata (dry-run and --apply both 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/ — dedupe review packs (.json and .md). Not applied until --apply.

  • state/dedupe-applied.jsonl — one line per parent merged by dedupe --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/ — versions review packs (.json and .md, paperful.version_pack.v1).

  • state/versions-applied.jsonl — one line per work updated by versions --apply.

  • state/refs-gaps/<stamp>/ — refs gap pack (pack.json, pack.md, dois.txt). Always dry-run.

  • state/coverage/<stamp>/ — coverage pack (pack.json, pack.md, dois.txt). Always dry-run.

  • state/precedent/<stamp>/ — precedent pack (pack.json, pack.md, dois.txt). Always dry-run.

  • state/stuff/<id>/<stamp>/ — stuff pack (pack.json, pack.md). Extra library cites per claim. No manager writes.

  • state/ingest/<stamp>/ — ingest-dois summary JSON. Created only classifies unless --apply.

  • state/collections-add/<stamp>/ — collections add summary JSON (paperful.collections_add.v1). Dry-run unless --apply.

  • state/authorwatch/<name>/ — people lists (paperful.authorwatch.v1), people.jsonl, suggestions.jsonl, inbox, seen, applied. Cursor baseline does not open the library; suggest does. See authorwatch.md.

  • state/inbox/proposals/ — gated inbox create/attach proposals (paperful.inbox.proposal.v1).

  • state/pdf-cache/ — PDFs exported from the manager so lint can read text on disk (pdftotext, then pypdf) when [mirror].pdfs = "none". paperful sync moves matching files into item folders; paperful cache clean removes absorbed or stale leftovers (dry-run unless --apply).

  • state/last-run.json — latest auditable run or recover report (summary + per-item outcomes). Other commands do not replace it. Historical copies land in state/runs/<timestamp>-<command>.json (run, recover, gaps, reachout, lint, fix-metadata for dry-run and --apply, summarize, synthesize, extract-sections, refs gap, coverage, precedent, stuff, snowball apply, …). Reports may include optional witness (paperful.witness.v1).

  • state/packs/<id>.json — parent witness for one pack open … pack close sequence (paperful.pack.v1). Steps point at filenames under state/runs/ and may include witness_id (first 12 of witness.config_sha256). state/packs/current is the open id; PAPERFUL_PACK=off skips appending.

  • state/summaries/ — HTML summaries from summarize when dest includes disk.

  • state/sections/ — structured IMRaD JSON from extract-sections (paperful.sections.v1; optional state/section-headings.toml synonyms).

  • state/reports/ — synthesize HTML report plus a JSON sidecar of source hashes; authors --apply writes <scope>-authors.json (paperful.authors_report.v1).

  • state/author-packs/ — field author packs (*.proposed.toml until snowball packs promote). Seeded by authors --apply, snowball --author-site-preflight, or twenty lookup --apply.

  • state/author-contacts/ — Twenty People cache from twenty lookup --apply, twenty sync --apply, or reachout --lookup (websites / emails; no mail).

  • state/author-requests.jsonl — ResearchGate handoff ledger (handoff_opened). --re-request ignores it.

  • 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/fetch-wins.jsonl — one line per vault or browser-agent PDF (host and path only; optional steps trace on agent successes). Gitignored with the rest of state/.

  • state/playbooks-proposed.toml — draft from paperful playbooks propose. Installed only by playbooks promote into grey_playbooks_dir/learned.toml.

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

  • state/mendeley-oauth.json — Mendeley access/refresh tokens after session login mendeley (mode 0600).

  • state/endnote-import/<stamp>/ — staged XML+PDF bundle for EndNote File → Import. Paperful never edits the .enl database.