A PDF viewer that is only pages and scrolling, and a PDF reader for agents that never makes them swallow the document.
folio, n. — a leaf of a manuscript or book; the page number printed on it.
Two binaries, one engine:
| for | does | |
|---|---|---|
folio |
people | Opens a PDF. Scrolls. That is the whole feature list. |
folioq |
agents | Answers questions about a PDF by location, so reading costs tens of tokens instead of tens of thousands. |
curl -fsSL https://raw.githubusercontent.com/pub-struct/folio/main/install.sh | bashInstalls to ~/.local/bin, with the runtime libraries in ~/.cache/folio.
No root, nothing touched system-wide. Prebuilt binaries are used where they
exist; otherwise it builds from source with cargo.
Options and manual install
FOLIO_VERSION=v0.1.0 …| bash # pin a version
FOLIO_FROM_SOURCE=1 …| bash # always build from source
FOLIO_NO_OCR=1 …| bash # skip the ONNX Runtime download (~70 MB)
FOLIO_BIN_DIR=~/bin …| bash # install somewhere elseFrom a clone:
git clone https://github.com/pub-struct/folio && cd folio
./install.shTo uninstall: rm ~/.local/bin/folio{,q} && rm -rf ~/.cache/folio.
A 65-page manual is about 31,000 tokens of Markdown. The answer you want is
usually on two of them. Every tool in this space converts the document —
folioq is built to avoid converting the document.
$ folioq info manual.pdf
pages 65
type TextBased (confidence 1.00)
est_tokens 30885 for the whole document
headings 164
tables 18-20,25,32-37,39,65
columns 11,27,33-35,37,39,40,46-49,51-53,55,58
needs_ocr 31,46-48
ocr_applied 46-48That is ~60 tokens, and it is everything you need to plan: how big the document is, which pages hold tables, which pages have no trustworthy text. Then you locate:
$ folioq search manual.pdf "sampling rate"
p6 …Speex can encode wideband speech (16 kHz sampling rate) in addition to…
p7 …### Sampling rate The sampling rate expressed in Hertz (Hz) is the…
p9 …useful to convert audio from one sampling rate to another. There are…~100 tokens. And only then do you read:
$ folioq read manual.pdf -p 7
[p7]
# 2 Codec description
…Two pages instead of sixty-five. ~1,100 tokens instead of 30,885.
The numbers above are from one document; run it on yours:
$ ./scripts/benchmark.sh manual.pdf "jitter buffer"
$ folioq info manual.pdf
pages 65
est_tokens 30885 for the whole document
needs_ocr 31,46-48
...
$ folioq search manual.pdf "jitter buffer"
p9 …ectly provided in the codec. # 2.4 Adaptive Jitter Buffer When…
p22 …th the playback or recording process. # 6.3 Jitter Buffer The…
...
$ folioq read manual.pdf -p 22
[p22]
## 6 Speech Processing API ( libspeexdsp)
...
──────────────────────────────────────────────────────────────
read the whole document 123,879 chars ~30,969 tokens
info → search → read -p 22 5,439 chars ~1,359 tokens
same answer, 23x less contextIt runs the real commands, picks the page with the most matches, and counts characters exactly (tokens are the usual ~4-chars-per-token estimate). On a short document it will tell you plainly that you may as well read the whole thing — folio earns its keep on documents worth navigating, not on every PDF.
folioq info paper.pdf # what is this, and what would it cost
folioq outline paper.pdf # headings, with page numbers
folioq search paper.pdf "batch size" # which pages mention it, with context
folioq read paper.pdf -p 12-14 # only those pages
folioq render paper.pdf -p 7 # a page as a PNG, to actually look atEvery command takes --json. Page selection accepts ranges and lists
everywhere: -p 3, -p 1-5, -p 1-5,8,12-.
Useful flags: -r for regex search, --max-chars on read to cap output
(anything dropped is reported in truncated_pages, never silently),
--max-level on outline to see only top-level headings.
Caching. Extraction happens once per file and is keyed by content hash.
The first question about a 65-page PDF takes ~3s; every later one takes
~15ms. So ask several narrow questions rather than one broad one.
folioq cache path locates it, folioq cache clear empties it. The key
covers the file's contents, the OCR feature and the folio version, so
neither editing a file nor upgrading folio can serve you a stale extract.
Set FOLIO_CACHE_DIR to put it somewhere else.
folioq speaks MCP, exposing the same five operations as tools:
pdf_info, pdf_outline, pdf_search, pdf_read, pdf_render.
pdf_render returns the image inline, so a scanned page can simply be
looked at.
Claude Code
claude mcp add --scope user folio -- ~/.local/bin/folioq mcpClaude Desktop, Cursor, Zed, or any MCP client — add to the client's config:
{
"mcpServers": {
"folio": {
"command": "/home/you/.local/bin/folioq",
"args": ["mcp"]
}
}
}Make it the default for PDFs. Registering the server gives the agent the
tools; a line in your instructions file makes it reach for them. In
~/.claude/CLAUDE.md (or your client's equivalent):
# PDFs
Read PDFs with `folio`, never by dumping the whole file into context.
Work in this order: `pdf_info` first, then `pdf_outline` or `pdf_search`
to locate, then `pdf_read` with explicit pages. Only read a whole PDF
when `pdf_info` shows it is small and you genuinely need all of it.
If a page appears in `needs_ocr`, its text is unreliable — use
`pdf_render` and look at it instead.Agents without MCP can just shell out to folioq … --json.
folio paper.pdfNo toolbar, no sidebar, no page counter. The window is the document.
j / k / arrows scroll g / G first / last page
space / shift page down / up + / - / 0 zoom in, out, reset
PageUp / PageDown page up / down q / Esc quit
Pages lay out from their dimensions, which PDFium reports without parsing page content, so a 500-page file has a correct scrollbar the instant it opens. Only pages near the viewport are rasterised — on a background thread, at your display's real pixel density — and they are released once they scroll away. Memory tracks the window, not the document.
Pages whose text cannot be trusted — scanned images, broken font encodings,
garbled extraction — are reported in needs_ocr rather than quietly
returned as garbage.
With ONNX Runtime present (the installer fetches it), OCR runs automatically
on exactly those pages and nothing else, so a text PDF pays nothing for the
feature. Without it, extraction still succeeds, info says why OCR did not
run, and those pages stay flagged so you know to render and read them
visually.
Build with --no-default-features to drop the OCR stack entirely.
crates/foliocore document model: rasterisation, page-addressed text, cache
crates/folio the viewer
crates/folioq the CLI and MCP server
Run the tests with cargo test --workspace, and again with
--no-default-features. Both are needed: the OCR and text-only paths take
page numbers from different upstream APIs, one 1-indexed and one 0-indexed,
so a mistake in the text-only conversion is invisible when OCR is on.
Page numbers are 1-indexed throughout the public API, matching what a reader sees at the bottom of the page.
PDFium and ONNX Runtime are loaded at runtime rather than linked, and are
discovered from $PDFIUM_LIB_PATH / $ORT_DYLIB_PATH, the executable's
directory, ./target/, or ~/.cache/folio.
folio is a thin layer over other people's hard work:
- pdf-inspector (firecrawl) — text extraction, layout and table detection, OCR routing
- firecrawl-pdfium (firecrawl) — safe PDFium bindings
- GPUI (Zed) — the GPU-accelerated UI framework
- PDFium (Google) — rasterisation
- ONNX Runtime (Microsoft) — OCR inference
- rmcp — the MCP SDK
It is not a fork of any of them; they are all upstream dependencies.
MIT