carrel
A research paper library for you and your AI.
Add papers by PDF, DOI, arXiv id or title. carrel finds their metadata, turns each PDF into text and figures an AI can read, keeps a .bib file for every paper, and lets an AI search, read and summarize your library, with you approving what it writes.
carrel add 2006.11239 · carrel search diffusion author:ho · carrel bib -p thesis > refs.bib
A carrel is the private study desk in a university library.
What it does
- Add papers by PDF, DOI, arXiv id or title, or import a
.bibfrom a LaTeX project, Zotero or JabRef. Metadata comes from doi.org, Crossref, Semantic Scholar and arXiv; arXiv PDFs are downloaded. - Plain files you own. Each paper is a folder named by its citation key, holding
<key>.bib,<key>.pdf, the extracted text and figures, your notes and supplementary material. Sync it, put it in git, edit it by hand. - Readable by AI. Every PDF becomes
fulltext.mdplusfigures/, so an assistant can read papers without PDF tools of its own. - AI writes drafts, you decide. Summaries, keywords and notes an AI proposes wait in
drafts/until you approve them. - Projects and bibliographies. Group papers into projects, keep them in sync with the citations in your LaTeX or Typst files, and get each project’s
.bibin one command. - Search everything with a full-text index over titles, authors, keywords, abstracts, summaries, notes and full texts, with field filters like
author:andyear:. - Follow citations: the references and the citing papers of any paper, marked when they are already in your library.
- A TUI, a CLI and an MCP server on one core library, so other frontends can follow.
How it fits together
you your AI assistant
│ │
TUI · CLI MCP server (carrel mcp)
└──────────────┬───────────────────┘
│
carrel library ── doi.org, Crossref, Semantic Scholar, arXiv
│
~/Documents/carrel/<key>/<key>.bib, <key>.pdf, fulltext.md, figures/, summary.md, notes.md, drafts/
Start with Installation and the Quick start.
Installation
carrel runs on Linux (x86_64 and arm64) and macOS (Apple silicon).
Prebuilt binaries
Download the archive for your platform from the latest release, unpack it and put carrel on your PATH:
tar xzf carrel-v*-x86_64-unknown-linux-musl.tar.gz
install carrel-v*/carrel ~/.local/bin/
| Platform | Archive |
|---|---|
| Linux, x86_64 | x86_64-unknown-linux-musl (static, runs on any distribution) |
| Linux, arm64 | aarch64-unknown-linux-musl (static) |
| macOS, Apple silicon | aarch64-apple-darwin |
Each archive comes with a .sha256 checksum.
With bin
bin picks the right archive for your platform, unpacks it and keeps track of the version:
bin install github.com/arunoruto/carrel
bin update carrel # later, to get a new release
Nix
The repository is a flake:
nix run github:arunoruto/carrel # try it
nix profile install github:arunoruto/carrel
or as a flake input, using its package or overlay:
{
inputs.carrel.url = "github:arunoruto/carrel";
# environment.systemPackages = [ inputs.carrel.packages.${pkgs.system}.default ];
# or: nixpkgs.overlays = [ inputs.carrel.overlays.default ]; then pkgs.carrel
}
The Nix package comes with its text extractor (Python with pymupdf4llm from nixpkgs), so nothing is downloaded at runtime and extraction works offline.
With Cargo
cargo install --git https://github.com/arunoruto/carrel
The text extractor
Turning PDFs into text and figures is done by an external command (see Configuration). Outside of Nix the default runs pymupdf4llm through uv, so install uv; it fetches pymupdf4llm on first use. Without it, papers are still added, just without their text.
Quick start
Add a few papers:
carrel add 1706.03762 # an arXiv id: metadata and PDF
carrel add 10.1038/nature14539 # a DOI: metadata
carrel add ~/Downloads/some-paper.pdf # a PDF: its DOI or title is looked up
carrel add "Deep Residual Learning for Image Recognition"
Each becomes a folder in ~/Documents/carrel named by its citation key, such as vaswani2017attention.
Look around:
carrel list
carrel search attention author:vaswani
carrel show vaswani2017attention
carrel # the TUI
Connect your AI assistant (here Claude Code; see Working with an AI for others):
claude mcp add --scope user carrel -- carrel mcp
and ask it: “Read the papers in my library that have no summary yet and propose summaries and keywords.” Its proposals arrive as drafts:
carrel drafts
carrel approve vaswani2017attention summary # or reject; the TUI shows them side by side (d)
Write a paper with them:
carrel project sync thesis ~/thesis/*.tex # the papers your LaTeX cites
carrel bib -p thesis > ~/thesis/refs.bib
Adding papers
carrel add takes any mix of:
| Source | Example | What happens |
|---|---|---|
| arXiv id or URL | 2006.11239, arXiv:hep-th/9711200, https://arxiv.org/abs/2006.11239v2 | metadata from arXiv’s DOI, the PDF downloaded |
| DOI or URL | 10.1038/nature14539, https://doi.org/…, publisher URLs containing the DOI | metadata from doi.org (Crossref, DataCite, …) |
paper.pdf, '~/Downloads/a paper.pdf' | text extracted; its DOI or arXiv id read from the first page, or else its title looked up | |
| exact title | "Attention Is All You Need" | looked up on Crossref, Semantic Scholar and arXiv |
A title that doesn’t match exactly prints the closest papers with the id to add each by. In the TUI, a with a title shows the results to pick from.
If an arXiv version exists, its PDF is downloaded, while the metadata comes from the published DOI when there is one.
Duplicates
A paper is the same as one in the library when its DOI, its arXiv id or its title (ignoring case, punctuation and accents) matches. Adding it again is refused, except to give a paper without a PDF its PDF.
PDFs later
Papers added by DOI or title can get their PDF afterwards:
carrel attach lecun2015deep ~/Downloads/lecun.pdf
carrel attach kingma2014adam # no file: downloaded from arXiv
In the TUI, p opens a file picker, newest PDFs first.
Supplementary material
carrel addto ho2020denoising appendix.pdf https://example.org/code.zip
Files land in the paper’s supplementary/ folder. PDFs among them get their text extracted next to them (appendix.pdf → appendix.md), which search and your AI can read too. In the TUI: s.
Importing a .bib
carrel import ~/thesis/refs.bib -p thesis --pdfs
Every entry becomes a paper with its key unchanged, so your documents’ citations keep working. Entries already in the library are matched instead, @string macros are expanded, keys that can’t name a folder get a new one (reported), and with --pdfs entries with an arXiv id get their PDF.
When there is no identifier
A PDF without DOI or arXiv id whose title isn’t found still gets added, with its title from the PDF. Tell carrel which paper it is later:
carrel update attention 10.48550/arXiv.1706.03762 # or an arXiv id, or the exact title
carrel rename attention # then a key from the new metadata
The library
The library is a folder of plain files, by default ~/Documents/carrel (see Configuration). carrel reads them fresh every time, so you can edit, sync or version them with any tool.
~/Documents/carrel/
vaswani2017attention/
vaswani2017attention.bib metadata and approved keywords; the key is the folder name
vaswani2017attention.pdf
fulltext.md the extracted text
figures/ the extracted figures, named by page
summary.md an approved summary
notes.md your notes, and approved AI notes
drafts/ AI proposals waiting for review
supplementary/ extra files; PDFs among them get their text next to them
projects/
thesis.txt a project: one paper id per line
Citation keys
A paper’s key names its folder, its .bib and PDF, and its BibTeX entry, so \cite{vaswani2017attention} and Typst’s @vaswani2017attention work directly.
By default a key is the first author’s family name, the year and the first meaningful title word, in ASCII: vaswani2017attention, muller2021uber. A second paper with the same key gets b, c, … appended. The format is a template you can change (Configuration).
Keys never change on their own, so citations keep working. carrel rename <id> [new] changes one on purpose: folder, files, .bib key and project lists together.
The .bib files
Each paper’s metadata is a single BibTeX entry. carrel keeps values exactly as written, so hand edits and fields it doesn’t know survive. Text from the web is escaped for LaTeX, while math such as $\beta$ passes through untouched. Approved keywords go into the entry’s keywords field.
carrel edit <id> opens the .bib in $VISUAL/$EDITOR and checks it afterwards.
Search
carrel search (and / in the TUI) ranks papers by where words appear: titles count most, then keywords, authors, abstracts and summaries, notes, and the full text including supplementary material. Words match while you type (diffu finds diffusion) and by stem (network finds networks). If no paper has every word, any word counts.
Search a single field with field:word: title, author, keyword, year, venue, doi, abstract, summary, notes, text. Search a project with project:name.
The index lives in SQLite in ~/.cache/carrel and only mirrors the files; carrel updates it before each search and rebuilds it if it is ever damaged.
Projects and bibliographies
A project groups papers, such as everything a thesis chapter or a talk cites. It is a text file, projects/<name>.txt, with one paper id per line; # starts a comment.
carrel project add thesis vaswani2017attention ho2020denoising
carrel project # list projects
carrel project show thesis
carrel project remove thesis ho2020denoising # the paper stays in the library
carrel project delete thesis # the papers stay in the library
In the TUI, P chooses which project to show and +/- put the selected paper into a project or take it out.
Bibliographies
A project’s bibliography is its papers’ .bib files joined:
carrel bib -p thesis > ~/thesis/refs.bib
carrel bib vaswani2017attention ho2020denoising # just these
carrel bib # the whole library
Keeping a project in step with your writing
carrel project sync thesis ~/thesis/*.tex ~/talk/slides.typ
reads the citations of LaTeX (\cite, \citep, \parencite, \nocite, …) and Typst (@key, #cite(<key>)) files, ignoring commented-out ones, and
- puts every cited paper that’s in the library into the project,
- lists cited keys that aren’t in the library (in Typst this includes
@fig:…labels, which look the same), - lists the project’s papers that nothing cites.
Working with an AI
carrel mcp serves the library over the Model Context Protocol, so assistants such as Claude can search it, read papers and figures, follow citations and propose summaries, keywords and notes.
Connecting
Claude Code:
claude mcp add --scope user carrel -- carrel mcp
Claude Desktop, and other clients configured with JSON:
{ "mcpServers": { "carrel": { "command": "carrel", "args": ["mcp"] } } }
Clients that don’t pass your shell’s environment on need settings in carrel’s config file rather than in environment variables.
How an assistant reads a paper
The server tells the assistant to search, then get_paper (metadata, abstract, approved summary and keywords, figure list), and only then read_paper, which returns the full text in chunks so long papers don’t flood its context. Figures come as images (get_figure), supplementary text through read_paper’s file option. The MCP tools page lists everything.
Drafts: the AI proposes, you decide
Everything an assistant writes (a summary, keywords, notes) becomes a draft in the paper’s drafts/ folder; nothing changes in your library until you approve it:
carrel drafts
carrel approve ho2020denoising summary # replaces the summary
carrel approve ho2020denoising keywords # replaces the .bib's keywords
carrel approve ho2020denoising notes # appended to your notes
carrel reject ho2020denoising summary
In the TUI, d shows each draft next to the current version: a approves, r rejects, e edits it first.
Things to ask
- “Read the papers in my thesis project that have no summary yet and propose summaries and keywords.”
- “Which papers cited by ho2020denoising am I missing?”
- “Find what my library says about classifier-free guidance, with page references.”
- “Write the related-work paragraph for these five papers and give me their BibTeX.”
The TUI
Run carrel without a command. The left pane lists papers, the right one shows the selected paper: metadata, keywords, projects, supplementary files, its summary, abstract and notes.
| Key | Action |
|---|---|
j/k, arrows | move |
J/K, PgDn/PgUp | scroll the details |
/ | search (live); Esc clears it |
a | add: a PDF path, DOI, arXiv id, or a title to look up online |
i | import a PDF with the file picker |
p | attach a PDF to the selected paper |
s | add a supplementary file |
u | update the selected paper’s metadata |
o | open its PDF |
e | edit its .bib |
n | edit its notes |
r / R | its references / the papers citing it (Enter adds one) |
P | choose a project to show |
+ / - | put the paper into a project / take it out of the one shown |
d | review drafts: a approve, r reject, e edit |
q | quit |
Adding, downloading and looking things up run in the background; the header shows what’s going on. The file picker starts in your Downloads folder with the newest PDFs first: Enter opens a folder or picks a file, Backspace goes up, ~ goes home.
Keeping the library healthy
carrel doctor
carrel doctor # problems, each with the command that fixes it
carrel doctor --all # also each paper without a PDF and each arXiv preprint
carrel doctor --json # for scripts (and the MCP tool `doctor`)
| Check | Level | Meaning |
|---|---|---|
broken | error | a .bib that is missing, invalid, or whose key differs from its folder |
fields | warning | missing title, author, year, or the journal/booktitle its type needs |
markup | warning | HTML tags left in a field |
macro | warning | an @string macro, undefined outside its original .bib |
duplicate | warning | the same DOI, arXiv id or title as another paper |
text | warning | a PDF whose text couldn’t be extracted, so an AI can’t read it |
project | warning | a project listing a paper that isn’t in the library |
pdf | info | no PDF |
preprint | info | an arXiv preprint that may have been published since |
carrel update
carrel update girshick2013rich # fetch the metadata again
carrel update --all
carrel update smith2020 10.1145/123456 # say which paper it is (DOI, arXiv id or title)
Fetched fields replace those whose text changed; your keywords and own fields stay, and so does the key. An arXiv preprint switches to its published version when one is known. Every change is printed, and if the key template would now give a different key, carrel says so without renaming.
Commands
Run carrel <command> --help for every option.
| Command | What it does |
|---|---|
carrel | the TUI |
add <source>… | add papers by PDF, DOI, arXiv id/URL or exact title |
attach <id> [pdf] | give a paper its PDF; without a file, from arXiv |
addto <id> <file or URL>… | add supplementary files |
import <file.bib> [-p project] [--pdfs] | import a .bib, keeping its keys |
list | all papers |
search <query>… | full-text search, with field: and project: filters |
show <id> | a paper’s details |
open <id> | open its PDF |
edit <id> | edit its .bib |
rename <id> [new-id] | change its key everywhere; without a new id, from the key template |
update <id> [source] / update --all | fetch metadata again |
refs <id> [--cited-by] | its references, or the papers citing it |
extract [ids…] [--all] | run the text extraction again |
drafts | AI proposals waiting for review |
approve <id> <summary|keywords|notes> | accept a draft |
reject <id> <summary|keywords|notes> | discard a draft |
project [list|show|add|remove|delete|sync] | projects |
bib [ids…] [-p project] | BibTeX for papers, a project, or the library |
doctor [--all] [--json] | check the library |
rm <id> | delete a paper and its files |
config | where the config, library and index are |
mcp | the MCP server on stdin/stdout |
Configuration
Settings live in ~/.config/carrel/config.toml (or $XDG_CONFIG_HOME/carrel/config.toml), on Linux and macOS alike; every one is optional. carrel config shows the paths in use.
library = "~/Documents/carrel"
key = "{{ author | lower }}{{ year }}{{ word | lower }}"
# extractor = "..."
The library
library is the library folder. The environment variable CARREL_HOME overrides it. The default is carrel in your Documents folder.
Citation keys
key is a Jinja template for the keys of new papers (and for carrel rename <id> without a new id). Its variables, all ASCII:
| Variable | Value |
|---|---|
author | the first author’s family name |
authors | all family names |
year | the year |
title | the title |
word | the first meaningful title word (articles and short words in English, German, French, Spanish and Italian are skipped) |
words | all meaningful title words |
venue, doi, arxiv, type | journal or conference, identifiers, entry type |
Missing values are empty. The result keeps ASCII letters, digits and -_.:.
For He, Zhang, Ren and Sun (2016), Deep Residual Learning for Image Recognition:
| Template | Key |
|---|---|
{{ author | lower }}{{ year }}{{ word | lower }} (default) | he2016deep |
{{ author }}:{{ year }} | He:2016 |
{{ authors[:2] | join('-') | lower }}_{{ year }} | he-zhang_2016 |
{{ author | lower }}{{ year }}{{ words[:3] | join | lower }} | he2016deepresiduallearning |
The extractor
extractor is the shell command that turns a PDF into text and figures. It runs in an empty folder with the PDF’s path in $PDF and must write fulltext.md there, and optionally figure images into figures/. CARREL_EXTRACTOR overrides it.
The default runs pymupdf4llm through uv; the Nix package uses pymupdf4llm from nixpkgs instead. A wrapper around docling or marker works too, for instance.
Environment variables
| Variable | Purpose |
|---|---|
CARREL_HOME | the library folder |
CARREL_EXTRACTOR | the extractor command |
S2_API_KEY | a free Semantic Scholar API key; without one, references, citations and some lookups are often rate-limited |
VISUAL, EDITOR | the editor for .bib files and notes |
MCP tools
What carrel mcp offers an assistant. Paper ids are citation keys. The assistant can read everything; the only thing it can write is drafts.
| Tool | Arguments | Returns |
|---|---|---|
search | query, limit | best matches first: id, title, authors, year, keywords, where the words matched. field:word and project:name work |
list_papers | project | all papers, or a project’s |
list_projects | each project with its paper ids | |
get_paper | id | metadata, abstract, summary, keywords, notes, figure and supplementary lists, pending drafts, file paths |
read_paper | id, offset, length, file | the full text (or a supplementary text file) in chunks, headed by where to continue |
get_figure | id, name | a figure as an image |
related | id, cited_by | the papers it cites, or that cite it, marked when in the library, with the id to add each by |
propose | id, field, content | stores a summary, keywords or notes as a draft for you to review |
bibtex | ids or project | BibTeX entries |
doctor | the library’s problems, each with the command that fixes it |
Paper ids, figure names and file names are checked against the library, so an assistant can’t read files outside it.
Contributing
carrel is written in Rust. The code is a library (src/lib.rs and its modules) with three frontends: the CLI (src/main.rs), the TUI (src/tui.rs) and the MCP server (src/mcp.rs).
Setup
With Nix, nix develop gives you Rust, clippy, rustfmt, rust-analyzer, mdBook and uv. Otherwise install a recent Rust (1.88 or newer) and uv.
cargo run -- --help
cargo fmt && cargo clippy --all-targets -- -D warnings
Tests
cargo test # offline: unit tests, the library, the CLI binary and the MCP server
cargo test -- --ignored # the tests that need internet (and uv), in tests/network.rs
The internet tests are marked #[ignore = "needs internet"], so a plain cargo test, which is also what the Nix build runs in its sandbox, skips them. The offline tests use a fake extractor and keep their library, index and config in a temporary folder, so they need neither Python nor a home directory. Semantic Scholar and arXiv rate-limit; if internet tests fail with a 429, set S2_API_KEY or wait a few minutes.
Docs
This site is an mdBook in docs/:
mdbook serve docs --open
Releases
Commit messages follow Conventional Commits: fix: makes a patch release, feat: a minor one, a ! (or BREAKING CHANGE:) a major one (before 1.0: a minor one). On every push to main, release-please keeps a release pull request with the next version and changelog up to date; merging it tags the release, and the release workflow attaches binaries for Linux (x86_64, arm64) and macOS (Apple silicon).