Skip to content
affromeroPublic

About

Self-hosted, filesystem-first paper library for capture, annotation, and private AI-assisted reading.

Topics

Resources

Security policy

Stars

4 stars

Watchers

1 watching

Forks

Latest commit

 

History

504 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

papernook logo

papernook

Your papers, annotated and understood, on your own server.

Capture a paper from any browser. Annotate the original PDF with Apple Pencil. Ask your own AI grounded questions, then share a revocable, view-only reading.

Your papers, your ink, your conversations, on a stack you control.


Self-hostable BYOA Release Mac App Store License: MIT CI CodeQL gitleaks Dependabot Chrome Web Store Safari Manifest v3 Next.js TypeScript SQLite

The filesystem is the source of truth. Delete the index, keep your library.

Click an arXiv paper directly with the browser extension enabled, then annotate and understand it in Papernook:

An arXiv paper open in Papernook with grounded code and chat context

How it works · Quick start · Workflow · Highlights · Integrations · Documentation

How it works

One server on a machine you control, one folder of files, and whichever AI you already pay for.

flowchart LR
    ext["Browser extension<br/>Safari · Chrome"]
    ipad["iPad or phone<br/>Pencil ink"]
    server["papernook<br/>Next.js, your machine"]
    disk[("data/<br/>PDFs · metadata · chats<br/>plain files")]
    index[("SQLite FTS5<br/>rebuildable index")]
    ai["Your AI<br/>Claude Code · Codex<br/>API · local model"]

    ext -->|"PDF navigation"| server
    ipad --> server
    server <--> disk
    server <--> ai
    disk --> index
    index -->|"search"| server
Loading
  1. Capture. Open a PDF on arXiv or a publisher page. The extension sends the navigation to your server, which downloads the paper, asks your AI for a topic, tags, and a summary, and files it.
  2. Annotate. Highlight and draw in the browser. The ink is written into the PDF itself, so any reader on any device opens it later, with or without Papernook.
  3. Ask. The chat sits beside the paper and answers from the paper. Its conversations are JSONL files next to the PDF, not rows in someone's database.

Nothing leaves your machine except the requests you configure to an AI provider. Delete the SQLite index and the library rebuilds from the files.

Quick start

curl -fsSL https://raw.githubusercontent.com/affromero/papernook/main/scripts/install.sh | bash
# clones the repo, detects Codex/Claude when available, writes .env, docker compose up

Open http://localhost:3000 and create a profile. The two-minute wizard checks the configured agent—or, when none is configured, selects a ready local Codex/Claude CLI—creates personal capture tools, and shows optional WebDAV compatibility when configured. The admin can also add the tldraw production key used by visual paper canvases. An explicit provider that is unavailable is reported, never replaced.

For a reproducible production install, use a release tag:

git clone --branch v0.3.0 --depth 1 https://github.com/affromero/papernook.git
cd papernook
./scripts/install.sh

Updating

The installer puts a papernook command in ~/.local/bin, so updates are one word from anywhere:

papernook update          # newest release tag: back up, rebuild, check health
papernook update --check  # what an update would move to, changing nothing
papernook update --main   # follow the development branch instead of releases
papernook status          # running version, newer release, health

An update refuses to run on a clone with uncommitted changes, backs the library up with scripts/backup.sh first (--no-backup skips it), and deepens a --depth 1 install so releases stay comparable. In a clone installed before the command existed, run ./scripts/papernook link once.

Papernook library with topic navigation, search, and paper cards

The reading loop

Papernook web reader with PDF annotation tools and grounded chat

Step What happens
1. Capture Save arXiv, PDF, and publisher pages from Safari, Chrome, or Papernook. AI proposes the topic, tags, summary, and links.
2. Annotate Highlight, add text, or draw in the web reader. Apple Pencil selects drawing automatically, and changes save to the PDF.
3. Understand Chat with the paper beside the reader or switch between Reading and Chat on a tablet.
4. Practice Turn an answer into a Pencil-ready exercise PDF, add margins, or append blank pages without shifting existing ink.
5. Share Send a revocable annotated reading. Conversation snapshots are explicit, immutable, and off by default.
6. Explore Discover proposes related work grounded in what you already hold, and the graph links papers, authors, topics, and tags.

Capture, end to end — the browser extension redirects an arXiv PDF into the reader, citations preview on hover, and two clicks file the paper into the library:

Browser extension capturing an arXiv PDF: redirect into the reader, hover reference preview, and filing into the library

Where to get the extension

Browser Install Status
Safari Mac App Store live
Chrome Chrome Web Store live

Both browsers run the same extension/ source. To run an unreleased build, npm run build:chrome produces a loadable zip, and every release ships one.

Why Papernook

A paper you actually work through ends up in three places at once: the tablet holding the ink, the reader holding the highlights, and a chat tab holding everything you asked about it. The annotations live in someone else's database, the chat history belongs to whichever model you used that month, and none of it survives leaving the product.

Papernook keeps those three in one place you own. Ink goes into the PDF itself, so any reader on any device still opens it. Metadata, summaries, and conversations are plain files on your disk. The AI is a setting, not a dependency.

Your workflow Papernook's approach
Files stay portable PDFs hold their own annotations; metadata, summaries, and chats use plain files.
Your AI stays replaceable Choose Claude Code, Codex, an API provider, or a local OpenAI-compatible server.
Reading stays connected Search, citation previews, grounded chat, and exercises live beside the paper.
Readers stay organized The library is shared; profiles separate chats, capture tokens, and Zotero connections by reader.
Sharing stays intentional Links are revocable and view-only; conversations appear only when explicitly snapshotted.
The index stays disposable SQLite accelerates search, but the filesystem remains the source of truth and rebuilds the index.
Full module map and data flow

Every node is a real module in this repository.

The PDF is the portable annotation layer. The web reader writes highlights, text, and ink back with version checks so another device cannot be overwritten silently.

Reading surface Source of truth Sync boundary
Native PDF annotations data/papers/<topic>/<slug>.pdf Autosave from the web reader and flow to shared readings and optional WebDAV.
Reference preview Current PDF Opens the cited entry beside the citation via embedded links or, when the PDF has none, text-recognized citations (numeric and author-year).
Conversations Per-profile JSONL Stay out of shares unless selected, but anyone with instance access can switch profiles and read them.
flowchart LR
    subgraph capture["Capture"]
      add["/add route<br/>src/app/add"]
      capi["/api/v1/capture<br/>session-authed"]
      pipe["capture pipeline<br/>src/lib/capture<br/>normalize · download · analyze"]
      jobs["jobs/index.ts<br/>background capture · /inbox review"]
      discover["discover.ts<br/>related work not yet in the library"]
      zotero["zotero.ts<br/>metadata-only refresh · personal/group"]
      zcatalog["zotero-catalog.json<br/>per-profile · compact · atomic"]
    end

    subgraph agent["Agent layer (src/lib/agent)"]
      registry["Sidedoor provider registry"]
      claude["claude-code.ts"]
      codex["codex.ts"]
      api["api.ts<br/>anthropic · openai · local"]
      attach["attachments.ts<br/>paths · scp · base64"]
    end

    subgraph library["Library (src/lib/library)"]
      papers["papers.ts<br/>data/papers + data/library"]
      chats["chats.ts<br/>jsonl per account"]
      index["index-db.ts<br/>SQLite FTS5"]
      scanner["scanner.ts<br/>chokidar, disk wins"]
      expand["expand.ts<br/>pdf-lib growth"]
      pdffile["pdf/file.ts<br/>versioned · locked · atomic saves"]
      exercises["exercises.ts<br/>md → exercises.pdf"]
      shares["shares.ts<br/>revocable reading snapshots"]
      graphlib["graph.ts<br/>papers · authors · topics · tags"]
      citations["citations/<br/>CSL · RIS · BibTeX · APA · Harvard · Vancouver"]
      libctx["context/<br/>related papers + reference→library matching"]
    end

    subgraph ui["App (src/app + src/components)"]
      picker["ProfilePicker"]
      libview["LibraryView"]
      chatpanel["ChatPanel<br/>markdown + KaTeX · ThreeSandbox iframe"]
      pdfreader["PdfReader<br/>pdf.js annotations + reference previews"]
      viewer["/viewer + ViewerShell<br/>external PDFs via /api/v1/viewer/pdf proxy"]
      wizard["WelcomeFlow"]
      shareview["ShareButton + /share<br/>view-only reading"]
      citeui["CitationActions + export routes"]
      canvasui["CanvasBoard<br/>tldraw canvas per paper"]
      graphui["LibraryGraph + /graph<br/>cytoscape-fcose"]
      discoverui["DiscoverClient + /discover"]
      inboxui["CaptureJobs + ReviewStrip<br/>/inbox, unconfirmed captures"]
    end

    gate["Sidedoor access (src/lib/auth)<br/>shared password · passkeys · sessions"]

    browserext["Safari + Chrome extension<br/>extension/<br/>PDF navigations → /viewer"]
    dav["rclone WebDAV sidecar<br/>docker-compose.yml<br/>serves data/papers ONLY"]
    authsync["credential-sync sidecar<br/>networkless · auth JSON only"]
    hostauth["host CLI homes<br/>read-only"]
    ipad["iPad browser<br/>Pencil ink + chat"]
    sidedoor["thesidedoor<br/>PWA + QR reach"]

    gate --> ui
    add --> pipe --> papers
    inboxui --> jobs --> pipe
    discoverui --> discover --> registry
    discover -->|"suggested url"| add
    browserext --> viewer
    viewer --> pdfreader
    viewer --> capi --> pipe
    zotero --> zcatalog
    zcatalog -->|"explicit one-paper import"| pipe
    pipe --> registry
    chatpanel --> registry
    registry --> claude & codex & api
    hostauth --> authsync --> claude & codex
    claude & codex & api --> attach
    papers --> index
    scanner --> index
    papers <--> dav <--> ipad
    exercises --> papers
    shares --> papers
    shareview --> shares
    citeui --> citations --> papers
    citations --> index
    canvasui --> papers
    graphui --> graphlib --> papers
    chatpanel --> libctx --> index
    pdfreader --> libctx
    expand --> pdffile --> papers
    pdfreader -->|"ETag + If-Match"| pdffile
    ipad --> pdfreader
    libview --> index
    picker --> wizard
    sidedoor --> ui
Loading

How Papernook is different

Papernook focuses on the self-hosted annotated-paper-to-understanding loop. Zotero remains substantially stronger for live citations, thousands of styles, and mature group reference management.

Compare built-in capabilities

The matrix counts only officially documented, built-in behavior that matches the capability exactly, without third-party plugins or handoffs.

Capability Papernook Zotero Paperpile Readwise Reader NotebookLM
One-click capture from supported publisher/PDF pages ✓ ✓ ✓ ✓ ✗
Organize with collections/folders, tags, and search ✓ ✓ ✓ ✓ ✗
Built-in web PDF highlighting and annotation editor ✓ ✓ ✓ ✓ ✗
Export reference metadata as RIS or BibTeX ✓ ✓ ✓ ✗ ✗
Live citations and bibliographies in writing tools ✗ ✓ ✓ ✗ ✗
Dedicated collaborative group libraries with roles ✗ ✓ ✓ ✗ ✗
Import one selected Zotero PDF into an AI workspace ✓ ✗ ✗ ✗ ✗
Use Zotero annotations as per-profile AI context ✓ ✗ ✗ ✗ ✗
Self-host the complete app and data ✓ ✗ ✗ ✗ ✗
Annotations are saved directly into the standard PDF ✓ ✗ ✓ ✗ ✗
Edit the same PDF from iPad apps over WebDAV ✓ ✗ ✗ ✗ ✗
Preview linked references without losing your place ✓ ✗ ✗ ✗ ✗
Grounded document chat inside the reading app ✓ ✗ ✗ ✓ ✓
Choose a local, SSH, or API AI backend ✓ ✗ ✗ ✗ ✗
Generate printable exercise PDFs from a paper ✓ ✗ ✗ ✗ ✗
Grow PDF margins or add pages without shifting ink ✓ ✗ ✗ ✗ ✗
Whole-library visual relationship graph ✓ ✗ ✗ ✗ ✗
Share an uploaded, annotated PDF by link ✓ ✗ ✓ ✗ ✗
Share selected owner conversations beside that PDF ✓ ✗ ✗ ✗ ✗

Sources: Zotero collections, search, and citation styles, PDF reader and annotation editor, word processor integration, standardized formats, and annotation storage; Paperpile exports, embedded annotations, and annotation sharing; Readwise chat, exports, and sharing limits; and NotebookLM study outputs and quizzes.

Bring your own agent

Choose a provider in browser setup or Settings. Sidedoor stores provider credentials, models, and endpoints in the instance configuration. You can use a local CLI, run a CLI over SSH, provide an API key, or connect a local model server.

SSH execution requires Python 3 and the selected CLI on the remote host. The shared supervisor limits execution time and stops its process group when the connection closes. A network failure can prevent confirmation of remote cleanup.

Providers and configuration
Mode Configuration Keyless
Claude Code CLI, local Select in setup or Settings ✓
Codex CLI, local Select in setup or Settings ✓
Claude Code over SSH Save the SSH connection in setup or Settings ✓
Codex over SSH Save the SSH connection in setup or Settings ✓
Anthropic API Save the API key in setup or Settings ✗
OpenAI API Save the API key in setup or Settings ✗
Ollama Save the endpoint and model in Settings ✓
llama.cpp server Save the endpoint and model in Settings ✓
vLLM server Save the endpoint and model in Settings ✓

Settings detects local endpoints and installed models. Docker reaches local servers through host.docker.internal; Papernook does not publish model ports. Images use the native attachment mechanism for each transport. Local image chat requires a vision-capable model, and paper capture needs enough context for roughly 60,000 characters. Provider errors surface without silent fallback.

Web access is on by default and can be disabled by an admin in Settings. Claude Code, Codex, Anthropic, and the OpenAI Responses API use their native search tools. Ollama, llama.cpp, vLLM, and custom OpenAI-compatible endpoints use Papernook's bounded web_search / SSRF-guarded web_fetch tool loop. Docker Compose includes a SearXNG service for those searches, exposed only on host loopback by default (127.0.0.1:8888). Set WEB_SEARCH_BASE_URL to use a different SearXNG instance. Local models must support OpenAI-compatible function calling; llama.cpp and vLLM also need their tool-call parser/template enabled for the selected model.

The installer prefers an authenticated local Codex CLI, then Claude Code. In Docker, Compose gives a networkless sidecar read-only access to the selected CLI credential directory and copies only its auth JSON into a private volume. Use Reload CLI login in Settings after signing in or out; active chats are not interrupted. CLI history, sessions, and project configuration stay unmounted. An installed but logged-out CLI is shown as needing login, not ready. macOS keeps Claude credentials in Keychain, which containers cannot read; run claude setup-token and set CLAUDE_CODE_OAUTH_TOKEN in .env when using Claude Code through Docker.

Configure both CODEX_AUTH_DIR and CLAUDE_AUTH_DIR only when both local CLIs should be selectable.

Integrations

Canvas

Each paper has a shared visual canvas for screenshots, links, videos, diagrams, and freehand notes. The canvas uses the tldraw SDK, which requires a hobby, trial, or commercial license key on production domains. The admin can get and save a key from the setup wizard or Settings → Canvas; no rebuild or restart is required. TLDRAW_LICENSE_KEY remains available for environment-managed deployments, and a key saved in Settings takes precedence.

License keys are public and validated by tldraw in the browser. If a key is missing, invalid, expired, or issued for another domain, Papernook shows an actionable setup panel instead of an empty canvas.

Zotero

Each profile independently connects one personal or accessible group Zotero library. Papernook refreshes a compact, per-profile catalog of metadata and annotations every 30 minutes or on demand. It can catalog the whole library or selected collections; subcollections are included automatically.

Connecting never downloads PDFs and never calls an AI provider. Browse or search the catalog in Settings, then explicitly import one paper when you want it in Papernook. That action downloads only that paper (100 MB maximum), runs the configured AI filing flow once, and adds it to the shared library. Existing Zotero annotations for that paper become per-profile context for the connected profile's conversations. They are treated as untrusted quoted source material and are never added directly to shared links.

Create a dedicated key at zotero.org/settings/keys, enable library read access, then open Settings → Zotero sync. File access is required only to import stored PDFs; write access is never required. Imports join the shared Papernook paper library, while connections, catalog associations, annotation context, and chats remain per-profile. Deduplication scopes Zotero item keys to their source library and also checks DOI and arXiv identity.

The catalog is a disposable cache under data/users/<username>/zotero-catalog.json; Zotero remains its source of truth. The size of Zotero file storage—even libraries larger than 1 GB—does not cause Papernook to copy those files: refresh transfers metadata only. The local catalog is capped at 200,000 records and 64 MB; if metadata exceeds either limit, refresh fails visibly without advancing the sync cursor or replacing the last valid catalog. Incremental refreshes process metadata updates and deletions without deleting already imported Papernook papers. Later Zotero edits refresh only Zotero-owned metadata, leaving the PDF, Pencil ink, topic, summary, chats, canvas, and exercises untouched. See Zotero's versioned sync API, annotation storage model, and full-text API.

Every paper’s Cite menu copies APA, Harvard, or Vancouver bibliography entries, or downloads CSL JSON, RIS, or BibTeX. Library exports respect the active search, topic, and tag filters.

Other reference managers
Tool Sync How
Zotero ✓ built in Metadata-first catalog; explicitly import individual PDFs for AI
Mendeley possible Public REST API still up, but OAuth app registration adds friction
Readwise Reader possible Clean token-authed V3 API exposes saved documents
Paperpile workaround only No public API; its Google Drive folder sync could feed a watched folder
EndNote, ReadCube ✗ No usable public API

Self-host

Docker Compose runs the Next.js app, a networkless CLI credential-sync sidecar, and an rclone WebDAV sidecar. The web reader is the primary annotation surface on desktop and iPad. WebDAV remains an optional compatibility route for external PDF apps when fully configured.

The WebDAV sidecar serves data/papers only; chats, crops, canvases, and unconfirmed captures stay private. Connect through Tailscale or a hardened custom domain. Settings generates the correct device QR code for the address in use.

The installer creates the first Admin profile and shared password locally. After entering the password, a reader can save a passkey with Apple Passwords or another WebAuthn manager before choosing a profile. This step is optional. A saved passkey opens the picker on later visits. Anyone admitted can choose Admin and manage settings. Choosing another profile removes Admin access. Admin can manage passkeys and sessions in settings. If the shared password is lost, the server owner runs papernook access reset locally.

Share links remain readable without a login because the unguessable share id is the capability for one paper. For custom domains, Caddy terminates TLS in front of app and WebDAV ports that bind to 127.0.0.1 by default. See share household access and custom-domain setup.

Documentation

Start with the visual documentation home.

I want to… Guide
Learn the everyday workflow User guide
Share access by domain or Tailscale Share household access
Capture from Chrome or Safari desktop Browser extension
Capture from an iPhone or iPad Add to papernook Shortcut
Annotate from an iPad iPad annotation guide
Serve the app on a custom domain Custom domain setup
Back up, restore, or watch the server Operations
Understand the security model Security policy

Development

npm install          # postinstall patches tldraw, copies the sidedoor service worker, vendors three
npm run dev
npm run build:chrome # Web Store-ready zip in build/
npm run test:chrome  # packaged extension in real Chromium
npm run ci           # lint + typecheck + vitest + build (pre-push runs this too)
npm run test:e2e     # Playwright journeys against committed docs screenshots
npm run screenshots # intentionally refresh docs/images
pre-commit install --install-hooks   # two-tier local gate

License

Papernook is available under the MIT License. Third-party dependencies and assets remain subject to their respective licenses.

Avatars shared with Sotto · reach by sidedoor

Dead-code checks

The Dead code workflow runs on pull requests, main-branch pushes, and weekly. It uses the pinned shared maintenance action to reject new findings, stale reviewed exceptions, and scanner failures. Reports are attached to each workflow run. Dependabot updates the shared action and Knip.

Keep runtime entry points in knip.json. Any retained finding in .maintenance-exceptions.json needs an exact identity and a review reason.

About

Self-hosted, filesystem-first paper library for capture, annotation, and private AI-assisted reading.

Topics

Resources

Security policy

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages