# Tutorial 4: PDF workflows
This tutorial covers `adsfolio pdf` end-to-end: identify, rename, watch, status, upgrade, and library push.
## 0) Start with `--help`
Before you apply file operations, review the full PDF command surface:
~~~bash
adsfolio pdf --help
adsfolio set --help
adsfolio cache --help
adsfolio pdf ocr --help
adsfolio pdf identify --help
adsfolio pdf refresh-ads --help
adsfolio pdf rename --help
adsfolio pdf watch --help
adsfolio pdf status --help
adsfolio pdf upgrade --help
adsfolio pdf push-library --help
adsfolio pdf export --help
adsfolio pdf obsidian --help
~~~
Key PDF options:
| Option | Meaning | When to use |
| --- | --- | --- |
| `identify --settings-scope global/library` | Load reusable global or per-library settings | Share defaults or keep one library distinct |
| `identify --recursive/--no-recursive` | Override saved recursive scanning for one run | Multi-folder paper archives |
| `identify --strategy` | Select one or more ordered evidence strategies for one run | Limit or reorder local metadata extraction and lookups |
| `identify --debut-year/--end-year` | Override publication-prefetch years for one run | Narrow a cache refresh without changing setup |
| `identify --refresh-cache` | Rebuild the publication cache first | Deliberate cache refresh |
| `identify --output-unresolved` | Move unresolved PDFs to a separate directory | Keep review queue clean |
| `ocr --output` | Route likely scanned/non-OCR PDFs | Separate files needing OCR before identify |
| `ocr --make-ocr-sh` | Generate `ocrmypdf.sh` in current directory | Run OCR in batch with one command |
| `identify --ai-csv` | Apply AI-enriched unresolved CSV directly | Non-interactive AI roundtrip |
| `identify -v` | Print per-file unresolved move logs | Debug detailed unresolved routing |
| `identify --verbose-parser` | Show raw parser warnings from malformed PDFs | Debugging problematic files or corrupt inputs |
| `--apply` | Apply the command's file moves, renames, remote additions, or upgrades | Preview the command first. This flag does not disable all catalog writes or ADS requests |
| `rename --scheme` | Template defining output filename | Enforce your citation/file naming convention |
| `rename --author-format` | Author token style (`surname`, `surname_initials`, etc.) | Match your preferred filename style |
| `watch --use-watchdog` | Event-driven watcher backend | Lower-latency updates on supported systems |
| `watch --auto-upgrade` | Periodic arXiv→published upgrade checks | Keep long-lived PDF archives canonical |
| `watch --upgrade-interval` | Hours between ADS rechecks and auto-upgrade passes | Balance freshness and API activity |
| `upgrade --overwrite-arxiv-with-official` | Replace tracked arXiv file contents | Preserve stable path/filename while updating content |
| `upgrade --arxiv-backup-if-overwritten` | Save backup before overwrite | Safety rollback path |
| `upgrade --skip-annotation-port` | Download official PDF without transferring annotations | Fast upgrades or no `pymupdf` environment |
| `upgrade --force` | Re-check entries already marked as published | Force re-scan after manual registry edits |
| `push-library --apply` | Push resolved bibcodes to ADS library | Synchronize local PDF collection with ADS library |
| `obsidian --notes-dir` | Write `.md` notes to a directory other than the PDFs | Keep notes separate from the PDF folder |
| `obsidian --force` | Regenerate existing notes and replace manual edits | Refresh metadata after re-running `identify` |
| `obsidian --apply` | Fetch/cache missing abstracts and write notes | Always run after dry-run verification |
## Prerequisite
Install optional features:
```bash
pip install 'adsfolio[full]'
```
Run `adsfolio set` once before your first `pdf identify` run. This step lets the tool cache
these items:
- your publication list (ORCID + optional names + year window)
- optional library papers
- optionally, references/citations of that library set
This cache reduces per-file ADS queries.
Run `adsfolio set` again only when token/profile values change.
## How identify works
`adsfolio pdf identify` uses the central user catalog `pdf-library.sqlite` as its
incremental state. The tool imports legacy per-root registry and JSON files. It does
not rename or remove them.
The user-level publication cache is separate. `adsfolio cache` manages it.
For each new, changed, or unresolved catalog location, `identify` does these steps.
Find new and changed files:
1. Scans the selected root top-level or recursively. When `--skip-unresolved-directory`
is enabled, it skips the configured unresolved destination before hashing.
2. Compares stored size, modification time, and filesystem identity first.
3. Computes SHA-256 only for new or changed PDFs, then reuses matching catalog attachments.
Run the evidence strategies:
4. Runs the enabled evidence strategies in order. Each stage handles only PDFs that
are still unresolved. Before it makes any ADS network request, it checks the
root's cached ADS documents and publication cache.
5. With the default strategy order, extracts and looks up:
`filename_bibcode`, `filename_doi`, `pdf_metadata_bibcode`,
`pdf_metadata_doi`, `first_page_bibcode`, `first_page_doi`,
`structured_filename`, `pdf_metadata_title`, and `first_page_title`.
It batches candidate lookups by identifier where possible. Repeat
`--strategy` to select an ordered subset for one run, for example:
```bash
adsfolio pdf identify ~/Papers \
--strategy filename_doi \
--strategy first_page_title
```
Handle the files that remain unresolved:
6. If files remain unresolved, writes `unresolved.csv` and prints an AI prompt.
7. In interactive mode, waits for an AI-enriched CSV path and applies batched
ADS bibcode/DOI lookups from that CSV.
With `--recursive`, the same central catalog still holds all tracked files.
First-page title identification collects typography candidates and checks them
against ADS titles. A larger journal masthead does not automatically become the
title. Conflicting records remain unresolved. Uniform-font OCR and titles with
mixed font sizes can still require a verified DOI or Bibcode.
Resolved PDFs with unchanged content reuse their catalog metadata without
repeat identification queries. Unresolved or changed PDFs remain eligible for
identification.
## 1) Optional: route non-OCR PDFs before identify
Dry-run:
```bash
adsfolio pdf ocr ~/Papers --recursive -o ~/Papers/ocr_required
```
Apply move:
```bash
adsfolio pdf ocr ~/Papers --recursive -o ~/Papers/ocr_required --apply
# Generate ocrmypdf.sh (uses the same --output directory as OCR input)
adsfolio pdf ocr ~/Papers --recursive -o ~/Papers/ocr_required --make-ocr-sh
bash ocrmypdf.sh
```
The command prints an `ocrmypdf` script template. If these files have annotations,
check OCR outputs carefully. You can manually move selected files out of
`ocr_required` to skip OCR for those files.
## 2) Identify PDFs and update catalog metadata
```bash
adsfolio pdf identify ~/Papers --recursive
```
This command matches the publication cache, reuses cached fallback ADS documents, and
writes the central catalog outside the library root.
Reusable Identify settings include these values:
- ordered evidence strategies
- recursive scanning
- unresolved destination
- unresolved-directory skipping
- verbosity
Personal identity and publication-year bounds are global profile data. You configure
them with `adsfolio set` or **Tools → My Publication Profile…**. They are not
library-scoped Identify behavior.
For a paper that ADS does not index, choose DOI providers and their order. This
example does not use ADS or need an ADS token:
```bash
adsfolio pdf identify /path/to/papers --provider crossref --provider datacite
adsfolio pdf rename /path/to/papers
```
DOI lookup fills missing metadata and records its provider and file digest.
Existing metadata, manual edits, and catalog history remain intact. See
[My paper is not on ADS](../local-references.md) for the desktop steps.
The default Identify scope is `global`. For values stored with the current library,
use `--settings-scope library`. Explicit options override the selected settings for
one run without mutating them:
```bash
adsfolio pdf identify ~/Papers --settings-scope library --no-recursive
adsfolio pdf identify ~/Papers --clear-output-unresolved
```
Use `--debut-year`, `--end-year`, `--clear-debut-year`, or `--clear-end-year` to
override the global profile's prefetch window for one run. `--refresh-cache`
and `--apply` are never inherited. Supply them each time.
ADS fallback follows the saved Identify setting. Use `--allow-ads` to permit
fallback without confirmation for one run. Use `--no-allow-ads` to restore
confirmation for one run. Neither flag changes the saved value.
Library exports now include the canonical
`identify_settings` payload. Older adsfolio releases ignore that new payload but
can still read the retained `identify_profile` provenance.
To inspect the state or make a backup, run:
```bash
adsfolio cache status
adsfolio pdf export ~/Papers -o pdfs-backup.json
```
If many files remain unmatched after cache/profile matching,
`identify` asks you to approve the fallback query batches before it starts them.
It also prints an estimated count of DOI batch queries.
In non-interactive runs, `identify` skips fallback ADS queries unless the saved
setting or `--allow-ads` permits them. Query-count limits still apply.
Use `adsfolio rate-limit` to inspect the limits that ADS returns for your account.
Request allowances and reset times depend on the endpoint and server response.
If unresolved files remain at the end, `identify` writes `unresolved.csv` in the scan
root. The file contains `filename`, `relative_path`, and a `first_page_text` preview.
Existing CSV files remain unchanged. New reports receive a numeric suffix when
the default filename exists. Use the report path printed by the command.
An AI CSV selected for import is never replaced by a generated report.
`identify` also prints an LLM prompt template so you can parse metadata with your
preferred AI.
See unresolved routing (dry-run, no moves):
```bash
adsfolio pdf identify ~/Papers --recursive \
--output-unresolved ~/Papers/unresolved
```
Move unresolved files:
```bash
adsfolio pdf identify ~/Papers --recursive \
--output-unresolved ~/Papers/unresolved --apply
# Optional: print per-file "Moved unresolved" lines
adsfolio pdf identify ~/Papers --recursive \
--output-unresolved ~/Papers/unresolved --apply \
-v
```
Skip files already in the unresolved destination on later recursive runs:
```bash
adsfolio pdf identify ~/Papers --recursive \
--output-unresolved ~/Papers/unresolved \
--skip-unresolved-directory
```
Identify updates catalog records even without `--apply`. It may also make ADS
requests or export an unresolved CSV without `--apply`. That flag controls file
moves, including identified PDFs returning from the unresolved subtree.
Identify counts verified saved metadata as **Already identified PDFs (verified)**.
These files do not enter the **Resolved PDFs** or **Unresolved PDFs** counts
for this run. The summary also counts unreadable PDFs separately.
**No file moves planned** means there are no moves to apply. Identify suggests
`--apply` only when it previews moves.
The skip is path-based and happens before hashing or ADS work. It does not skip
unresolved-status records elsewhere. If you omit `--output-unresolved`, the
effective skipped path is `~/Papers/unresolved`. The option defaults off.
If you already generated AI hints, pass them directly:
```bash
adsfolio pdf identify ~/Papers --recursive --ai-csv ~/Papers/unresolved_ai.csv
```
## 3) Rename safely: dry-run then apply
`adsfolio pdf refresh-ads` also renames successfully updated PDFs immediately.
An update succeeds only when the record passes the
[PDF identity check](../gui.md#pdf-identity-checks). A conflict between the
record and the PDF's own arXiv stamp or metadata DOI always stops the update.
When the PDF states no identifier that can confirm the record, pass
`--confirm-identity` with `--relative-path` after you check the paper.
The flag applies to that one PDF. The confirmation is
stored with the file's SHA-256 hash and applies only to that file content and
record.
It uses the library's saved Rename settings, or global settings when no library
override exists. To use global settings explicitly, pass `--rename-scope global`.
To supply a reference manually, use `--identifier` with `--relative-path`.
Use either format:
* `{authorLastName}-{year}-{bibstem}`: an author's last name, four-digit publication
year, and ADS journal abbreviation.
* `{bibstem}-{volume}-{page}`: ADS journal abbreviation, journal volume number, and
starting page or article number.
You can replace `{bibstem}` with the full journal name in either format.
For example, `Last-2020-Natur` means author Last, year 2020, and journal
*Nature*. `Sci-123-45` means journal *Science*, volume 123, and starting
page 45. These illustrate the input format.
Reference hints search ADS. Candidates must match identifiers from the current
PDF. A coauthor match alone does not establish identity.
If none match, recovery uses the PDF's own DOI or arXiv ID with enabled
providers. Explicit identifier conflicts still stop the update.
`--identify-scope global` selects global provider settings for an update.
The default `library` scope uses library overrides with global fallback.
The GUI forwards its selected Identify scope automatically.
Quote references that contain spaces:
```bash
adsfolio pdf refresh-ads ~/Papers --relative-path paper.pdf --identifier 'Last-2020-Natur'
adsfolio pdf refresh-ads ~/Papers --relative-path paper.pdf --identifier 'Sci-123-45'
adsfolio pdf refresh-ads ~/Papers --relative-path paper.pdf --identifier 'The Astrophysical Journal-900-100'
```
If several ADS records match, the command lists candidates without changing
the PDF. The GUI lets you select the correct candidate.
Files under the configured unresolved directory return to the library root
with their new name. Other PDFs stay in their current directory. The
`--output-unresolved` option overrides the saved unresolved directory.
`refresh-ads` does not overwrite existing destinations. The log reports skipped
renames. Those files keep the refreshed ADS metadata. Optional citation-metrics
requests run after these renames. A metrics failure does not undo completed file
updates.
To rename only selected files, repeat `--file` with paths inside the library:
```bash
adsfolio pdf rename ~/Papers --file ~/Papers/first.pdf \
--file ~/Papers/subfolder/second.pdf --apply
```
Omit `--apply` to preview those filenames. With `--allow-ads`, identification
also targets only those files. Failed matches keep their current paths.
PDF identity checks and destination-conflict checks still apply.
If a saved PDF changed, Rename can verify it against the cached ADS record.
It preserves the existing metadata and history when verification succeeds.
Preview does not save the recovered record. If verification fails, the log
states the reason. Review the PDF and retry with Identify or Update by ID.
Rename uses the exact selected template, including when metadata has no ADS bibcode.
It does not replace `{bibcode}` with `{year}`. If a selected field lacks metadata,
Rename skips the file and names the field. Add the metadata or choose another
template. Then retry the rename.
**Already matches rename rule** means the current filename already equals its target.
The log reports other skipped files individually.
Identification alone does not establish an ADS bibcode.
Available template variables: `{bibcode}`, `{year}`, `{title}`,
`{first_author}`, `{first_author_full}`, `{authors}`, `{authors_N}`, `{journal}`,
`{volume}`, `{page}`
The template becomes the filename. `{authors}` adds a trailing `+` when
`--max-authors` truncates the list. `{authors_N}` uses the same limited list
without that marker. Use `{{` and `}}` for literal braces.
Filename rendering follows these rules:
- Unknown fields stop the rename.
- The tool sanitizes unsafe filename characters.
- The tool appends `.pdf` automatically when it is absent.
- The tool limits results to 240 UTF-8 bytes.
- The tool skips a rename when its target already exists.
Dry-run:
```bash
adsfolio pdf rename ~/Papers \
--scheme "{authors}_{bibcode}-{title}" \
--author-format surname_initials \
--max-authors 3
```
Apply:
```bash
adsfolio pdf rename ~/Papers \
--scheme "{authors}_{bibcode}-{title}" \
--author-format surname_initials \
--max-authors 3 \
--apply
```
Before it renders names, `rename --allow-ads` identifies pending PDFs recursively.
It uses the same ADS workflow as `identify`.
If the scan root contains
`unresolved/`, applying the rename also moves resolved records from that
directory into the scan root. Unresolved files stay in `unresolved/`.
To route unresolved PDFs during this identify step, pass
`--output-unresolved unresolved`. Relative paths resolve below the scan root.
Author format options:
| Value | Example output |
| --- | --- |
| `surname` | `Smith` |
| `surname_initials` (default) | `SmithJ` |
| `last_comma_first` | `Smith,_J.` |
| `ads_custom:FORMAT` | delegates to ADS export API |
## 4) Watch a folder for new PDFs
Polling (default):
```bash
adsfolio pdf watch ~/Papers --recursive --interval 10 --apply
```
Watchdog backend (requires `adsfolio[full]`):
```bash
adsfolio pdf watch ~/Papers --recursive --use-watchdog --interval 60 \
--output-unresolved unresolved --apply
```
At startup, CLI watch reconciles the filesystem. It can process untracked or
changed PDFs that are already present. It skips catalog entries whose filesystem
facts still match. Later `--interval` passes reconcile filesystem facts to catch
missed events. These passes do not hash or re-identify unchanged PDFs.
Watch also rechecks existing catalog records with ADS at startup and every 24
hours. This check runs independently of `--auto-upgrade`. Set
`--upgrade-interval` to change the interval. The check uses the effective
Identify scope and requires ADS to be enabled with a token.
The check looks up identifiers in ADS in batches. It does not open a PDF when
ADS returns its stored bibcode. With `--apply`, it remembers failed identity
checks and unidentified PDFs without a metadata DOI for each library. It skips
those PDFs until the PDF, its saved identifiers, or the ADS record changes.
When ADS returns a verified new match or bibcode, Watch updates the record and
applies the saved Rename settings with `--apply`. Without `--apply`, Watch
previews the changes. With `filename_bibcode` enabled, a unique exact ADS match
can associate a PDF when its readable identifiers do not conflict. The filename
alone does not independently verify PDF contents. This check leaves the filename
unchanged.
A failed identity check or existing destination keeps the affected files in
place. Review the reported issue and retry.
If separate attachments in different roots share one catalog record, the
check leaves them unchanged and reports the issue for review.
This initial reconciliation differs from the GUI watcher. The GUI watcher's startup
scan establishes a baseline and leaves existing PDFs alone. The GUI only offers
later arrivals for watch processing.
With `--use-watchdog`, event callbacks only queue paths. One worker performs
PDF parsing, ADS queries, renames, and catalog writes.
Watch mode defers incomplete, encrypted, or concurrently changing PDFs until a
later event or reconciliation. Watch mode matches the `.pdf` suffix in any letter
case. It ignores symbolic links to PDFs outside the watched root.
Watch-mode unresolved destinations must be inside the watched root.
If a pass cannot read a folder or file (for example, permission denied), it logs
the error and skips that pass. It does not mark tracked files missing. The next
pass retries.
Filename rendering preserves Unicode by default, for example `Wesołowski`.
Use `--char-policy ascii` to request `Wesolowski`.
Without `--apply`, watch mode
reports proposed path changes but does not rename or move files.
The author separator and truncation suffix are independent settings.
`--max-authors` is inclusive. The tool shows a list at or below that size in full.
It truncates a longer list. With `--max-authors 2`, the tool truncates three or more
authors.
`--authors-when-truncated` controls how many leading
authors remain in a truncated result. It defaults to one. For example,
`--max-authors 2 --authors-when-truncated 1 --author-separator "+"
--author-truncation-suffix "+"` renders three authors as `SmithJ+`.
Setting `--authors-when-truncated 2` renders `SmithJ+DoeA+` instead.
`{authors_N}` uses the configured retained authors and separator but never adds
the truncation suffix.
The Rename dialog persists these values globally or per
library.
Use **Save Defaults** there to update the standard without renaming existing
PDFs.
### Run continuously on macOS
1. Create `~/Library/LaunchAgents/org.example.adsfolio-pdf-watch.plist` with the content below.
2. Replace every `/Users/YOU/...` path with an absolute path valid for your account.
The executable is the output of `which adsfolio`.
```xml
Labelorg.example.adsfolio-pdf-watch
ProgramArguments
/Users/YOU/path/to/venv/bin/adsfolio
pdfwatch
/Users/YOU/Papers
--recursive--use-watchdog
--interval60
--output-unresolvedunresolved
--apply
RunAtLoad
KeepAlive
StandardOutPath/Users/YOU/Library/Logs/adsfolio-pdf-watch.log
StandardErrorPath/Users/YOU/Library/Logs/adsfolio-pdf-watch.err
```
Load or remove it explicitly:
```bash
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.example.adsfolio-pdf-watch.plist
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/org.example.adsfolio-pdf-watch.plist
```
The package does not install or start this agent automatically because the
executable path, library path, naming policy, and filesystem permissions are
user-specific.
## 5) Check current state
```bash
adsfolio pdf status ~/Papers --recursive
```
This command shows identified catalog locations, unresolved files on disk, and duplicate hash
groups. With `--recursive`, all locations share the same central catalog.
Do not run identify/watch from two machines against the same library concurrently.
For portable backups, use the export command.
## 6) Upgrade arXiv PDFs to published versions
Dry-run:
```bash
adsfolio pdf upgrade ~/Papers
```
Apply:
```bash
adsfolio pdf upgrade ~/Papers --apply
```
Overwrite tracked arXiv file with official PDF and keep backups:
```bash
adsfolio pdf upgrade ~/Papers --apply \
--overwrite-arxiv-with-official \
--arxiv-backup-if-overwritten ~/Papers/arxiv-backup
```
Force re-check entries already marked as published:
```bash
adsfolio pdf upgrade ~/Papers --apply --force
```
## 7) Auto-upgrade while watching
```bash
adsfolio pdf watch ~/Papers --recursive --apply \
--auto-upgrade --upgrade-interval 24
```
## 8) Push resolved bibcodes to an ADS library
Dry-run:
```bash
adsfolio pdf push-library ~/Papers
```
Apply:
```bash
adsfolio pdf push-library ~/Papers --apply
```
## 9) Generate Obsidian-friendly notes
If the PDFs already live inside an Obsidian vault, `pdf obsidian` writes one Markdown note per
tracked PDF next to each file. Each note has YAML frontmatter with
title/authors/year/journal/bibcode, plus an `## Abstract` section. Notes link to the
PDF with an absolute `file://` link. `pdf obsidian` never copies, moves, or embeds PDFs.
Dry-run (no ADS queries, no files written):
```bash
adsfolio pdf obsidian ~/Papers
```
Apply — fetches missing abstracts from ADS in POST batches of up to 2,000
bibcodes, caches them in the central catalog, and writes the notes. Bibcodes
travel in the request body, so large libraries do not need long query URLs:
```bash
adsfolio pdf obsidian ~/Papers --apply
```
The export reads PDF highlights, underlines, strikeouts, squiggles, and text
comments. It places them in a marked `PDF annotations` section. The export
updates that section when annotations change. It preserves other note text.
It does not rewrite a note when the output is unchanged. Image-only highlights
may have no selectable text. Drawings and ink are not converted to text.
The export requires PyMuPDF from the `full` extra. An unreadable PDF or a note
with invalid section markers produces a skip. Check the warning before retrying.
ADSfolio stages the complete note before replacing the original. A write failure
keeps the original note intact. A note changed during export produces a skip.
Review the job log before retrying.
To regenerate the entire note, pass `--force`. This replaces manual edits
outside the annotation section:
```bash
adsfolio pdf obsidian ~/Papers --apply --force
```
Write notes to a separate folder instead of colocating them with the PDFs:
```bash
adsfolio pdf obsidian ~/Papers --apply --notes-dir ~/Papers/notes
```
For an Obsidian vault elsewhere, choose a folder such as
`--notes-dir ~/MyVault/papers`. The `papers` subfolder keeps generated notes
separate from notes that you write. It is optional and does not change ADS
requests.
## Next
Continue with [Tutorial 5: Operations and troubleshooting](./05-operations-and-troubleshooting.md).