Summarize the linked PDF of the selected publication(s) in BibDesk and attach the summary back to the entry as a linked Markdown file. Works with Anthropic, OpenAI, or a local Ollama model — chosen in a small config file.
The AppleScript is a thin wrapper: it grabs the selected entry's cite key, title, and linked PDF, hands them to a shell helper that does the text extraction and LLM call, then links the resulting .md back onto the entry.
See CHANGELOG.md for release history.
- macOS with BibDesk
curlandjq— install jq withbrew install jq- A PDF text extractor, either:
pdftotext(recommended):brew install poppler, or- Xcode command line tools (provides
swift):xcode-select --install
- For Anthropic/OpenAI: an API key. For Ollama: a running local instance.
From the project folder:
chmod +x install.sh # only needed once (downloads don't preserve the execute bit)
./install.shThis compiles the AppleScript into BibDesk's Scripts folder, places the helper in a sibling support folder, creates your config (without overwriting an existing one), and checks dependencies. Then edit the config it prints and you're set.
The helper and the compiled script go in two different places — this matters, because BibDesk lists every file in its Scripts folder as a menu entry, so only the compiled .scpt belongs there.
-
Compile the AppleScript into BibDesk's Scripts folder:
mkdir -p ~/Library/Application\ Support/BibDesk/Scripts osacompile -o ~/Library/Application\ Support/BibDesk/Scripts/BibSummary.scpt bibsummary.applescript
(Or open
bibsummary.applescriptin Script Editor and save it as a Script into that folder.) -
Put the helper in the sibling support folder (NOT the Scripts folder):
mkdir -p ~/Library/Application\ Support/BibSummary cp bibsummary.sh ~/Library/Application\ Support/BibSummary/ chmod +x ~/Library/Application\ Support/BibSummary/bibsummary.sh
-
Create your config:
mkdir -p ~/.config/bibsummary cp bibsummary.conf.example ~/.config/bibsummary/config.conf chmod 600 ~/.config/bibsummary/config.conf
Then edit
~/.config/bibsummary/config.conf— setPROVIDER, the matching model, and your API key.
- Select one or more publications in BibDesk (each must have a linked PDF).
- Run BibSummary from BibDesk's Scripts menu (the page-shaped icon). You can assign it a keyboard shortcut there too.
- A start dialog appears: Run uses your default config, Config… lets you pick an alternate profile (see Config profiles). Run is the default button, so pressing Return just goes.
- A notification reports progress. Each summary is written next to its PDF as
<citekey>-summary.mdand linked onto the entry, so it shows up in the entry's file list.
To skip the start dialog entirely and always use the default config, set property promptForProfile : false near the top of bibsummary.applescript and re-run ./install.sh.
Set PROVIDER in the config to one of:
| Provider | Needs | Notes |
|---|---|---|
anthropic |
ANTHROPIC_API_KEY |
Cloud. Set ANTHROPIC_MODEL (e.g. claude-opus-4-8). |
openai |
OPENAI_API_KEY |
Cloud. Set OPENAI_MODEL to a model your key can access. |
ollama |
Ollama running locally | Fully local & private — no key, no data leaves your machine. |
For Ollama: install from ollama.com, pull a model (ollama pull llama3.1), and make sure it's running. Point OLLAMA_HOST at it if it's not on the default http://localhost:11434.
You can keep more than one config and pick between them at run time — handy for, say, a fast/cheap default and a separate profile that sends the whole paper, or one that runs locally on Ollama.
Each profile is just another *.conf file in ~/.config/bibsummary/. Copy your config and edit the bits that differ:
cd ~/.config/bibsummary
cp config.conf config-fullpaper.conf # e.g. MAX_CHARS=450000, HTTP_TIMEOUT=300
cp config.conf config-ollama.conf # PROVIDER="ollama", a local model, no key
chmod 600 *.conf # they hold API keys — keep them privateWhen you run BibSummary, choose Config… in the start dialog and pick the profile from the list. Every *.conf in that folder (including the default config.conf) shows up automatically; the one you pick is used for that run only via the BIBSUMMARY_CONF environment variable. Run always uses config.conf.
A profile is a complete config, so each one carries its own provider, model, keys, and limits — there's nothing to keep in sync between them.
- Summary style/length — edit
PROMPTin the config. The sections, tone, and length are all driven from there. - How much text is sent —
MAX_CHARS(default 50000). Lower it to cut cost; raise it for long papers (see Long papers). - Summary length —
MAX_OUTPUT_TOKENS(default 4096). Raise it if summaries come out cut off mid-sentence. Applies toanthropicandollama; OpenAI uses its model default. - Network timeout —
HTTP_TIMEOUT(seconds). Commented out by default, which uses the built-in defaults (180 for anthropic/openai, 300 for ollama). Raise it if you pushMAX_CHARSup a lot and requests start timing out. - Where files land —
OUTPUT_DIR(alongsidethe PDF, or a fixed folder path).
By default only the first MAX_CHARS (50000) characters of extracted text are sent — roughly the first ~12k tokens, so a long paper is summarized from its opening pages only. To summarize the whole thing, raise MAX_CHARS. Check how much text a PDF actually has:
pdftotext -enc UTF-8 paper.pdf - | wc -mIf that count is well above 50000, the paper is being truncated. A typical full paper is a few hundred thousand characters (~100k tokens) — comfortably within the context window of large cloud models like claude-opus-4-8, but likely too big for small or local models, which have much smaller context windows and will error or silently truncate. Match MAX_CHARS to the model you're using.
You don't have to guess whether truncation happened: every summary's header records how much text was used (e.g. "sent 50,000 of 402,342 characters — truncated"), and when a paper is truncated the BibDesk dialog adds a note at the end of the run telling you which entries were affected.
Because raising MAX_CHARS increases both cost and request time, a dedicated config profile (e.g. config-fullpaper.conf with a high MAX_CHARS and a larger HTTP_TIMEOUT) is a convenient way to opt in per-run rather than changing your default.
The summary .md is always written next to the PDF and linked to the entry explicitly (add … to linked files). This step does not depend on any BibDesk auto-file setting — the file is created in a permanent, valid location and linked there regardless.
After linking, the script calls BibDesk's auto file on the entry (controlled by the autoFileAfterLinking property at the top of the AppleScript, default true). That's what renames the summary to match your library's local-file format — which is why the .md ends up named like the PDF.
What happens in different setups:
- You use auto-file (papers folder configured): the summary is renamed/placed per your format, alongside the PDF. This is the typical experience.
- You don't use auto-file, or no papers folder is set: the
auto filecall is wrapped in atry, so it's harmlessly skipped. The summary just stays as<citekey>-summary.mdnext to the PDF, still linked. Nothing fails. - Heads-up:
auto filefiles all of an entry's linked files, not only the new summary. So if your PDFs are not already auto-filed, leaving this on can move the original PDF into your papers folder too. If you don't want anything moved (and prefer the explicit-summaryfilename), setautoFileAfterLinkingtofalseinbibsummary.applescriptand re-run./install.sh.
- "No PDF text extractor found" — install poppler (
brew install poppler) or the Xcode command line tools. - "Extracted no text" — the PDF is likely a scan with no text layer. OCR it first (e.g. with
ocrmypdf) and retry. - "Provider returned: …" / "HTTP 4xx from …" — the API rejected the request; the message comes straight from the provider (bad key, unknown model, rate limit, prompt too long, etc.). For example, a too-large
MAX_CHARSon a small-context model surfaces here as a "prompt is too long" error. - "could not reach … network/DNS/TLS failure or timed out" — the request never completed: no connectivity, wrong
OLLAMA_HOST, or the model took longer thanHTTP_TIMEOUT. For big inputs, raiseHTTP_TIMEOUT(see Long papers). - A large paper fails — the request body is sent over stdin, so there's no command-line size limit; a failure on a big paper is almost always a provider error (prompt too long for the model's context) or a timeout. The two messages above tell you which. Run with debugging on (below) to see the exact HTTP status.
- Summary is cut off mid-sentence — the model hit its output limit. Raise
MAX_OUTPUT_TOKENSin the config (default 4096). The helper also prints a truncation warning to stderr when this happens. - Nothing happens / wrong tool path — BibDesk launches scripts with a minimal
PATH; the helper adds/opt/homebrew/binand/usr/local/bin. If your tools live elsewhere, adjust theexport PATH=line near the top ofbibsummary.sh. - Long PDFs feel slow — the run blocks BibDesk's UI until the model responds. That's expected for v1; the notification fires when done.
BibDesk hides the helper's stderr unless the whole run fails, so the quickest way to see what's happening is to turn on the log:
-
Set
BIBSUMMARY_DEBUG="1"in your config (or pass it inline when running from a terminal). The helper then appends progress, request/response sizes, and the HTTP status to a logfile —/tmp/bibsummary.logby default, or whereverBIBSUMMARY_LOGpoints.tail -f /tmp/bibsummary.log
-
To reproduce a failure outside BibDesk, run the helper directly — the real error goes to stderr:
BIBSUMMARY_DEBUG=1 bash ~/Library/Application\ Support/BibSummary/bibsummary.sh /path/to/paper.pdf testkey 2>&1 | tail -20
API keys live only in your local config file (keep it chmod 600). With the ollama provider, the PDF text and summary never leave your machine.
- Tested against the request/response shapes of the Anthropic Messages API, OpenAI Chat Completions, and the Ollama
/api/chatendpoint. Provider APIs change; if a call starts failing, check the provider's current docs and the model name in your config. - This uses extracted text for all providers (uniform behavior, and required for local models). If you only ever use Anthropic, you could adapt the helper to send the PDF natively for better handling of figures and tables.
MIT — see LICENSE. Use it, fork it, ship it.