Rigor Mining is an application that helps you collect research papers and books (PDF and EPUB), organize them with tags, preview them with a built-in reader, use a browser extension for capturing papers, and sync the papers to a Kobo device for your reading pleasure.
I wrote a good portion of this code by hand back in 2021, but other priorities took over so it got shelved. I worked on it again in 2023, but again it was put on the back burner. In order to kick it out the door, in 2026 I asked Claude Code to "finish it up" and get it into a state where I can deploy it.
So if you are extremely adverse to using anything that has been touched by AI, you should probably give this one a miss, but the core of this application was written by a human.
The backend and frontend run as two separate processes in local dev - open two shells.
backend/- Go API server (gorilla/mux), SQLite (or Postgres) storage, Google OAuth login (or a no-OAuth dev login, see below).frontend/- React + TypeScript + Tailwind app: library view, item detail/metadata editing, PDF/EPUB reader, and the Kobo sync screen.extension/- a small Chrome/Firefox extension that captures the current tab (and its PDF, if it finds one) into your library via an API token.
cd backend
make install
make start
make install copies .env.template to .env (fill in real values there)
and creates datastore/ for the SQLite file and uploaded library files.
make start runs the API on http://localhost:3000.
For local dev you don't need real Google OAuth credentials - set
RM_AUTH_DEV_LOGIN=true in .env (this is the default in the template) and
visit http://localhost:3000/dev-login in your browser once to sign in as a
fixed local user. Never set this to true in a deployed environment.
cd frontend
make install
make start
The dev server runs at http://localhost:8080. Because it runs on a
different port than the API during local dev, the frontend needs to know
where to send requests - make start writes RIGORMINING_API_BASE=http://localhost:3000
into frontend/.env (gitignored) for you via the make_dev_env target.
In production the frontend is built into frontend/dist and served
directly by the Go process (RM_WEB_STATIC_DIR in the backend .env), so
RIGORMINING_API_BASE is left empty and requests just go to the same origin.
Load extension/ as an unpacked/temporary extension in Chrome or Firefox,
then open its options page and set the server URL (your backend, e.g.
http://localhost:3000) and an API token. Tokens are created on the
Settings page in the app (/settings) - they're how the extension
authenticates without doing a browser OAuth round-trip.
.agents/skills/rigormining is a skill for Claude Code and other AI coding
agents that support the .agents/skills format. It talks to a running
rigormining server's API to add papers to your library (by arXiv id or a
paper's URL - the same capture the browser extension does), and to copy
items queued with request_sync onto a
locally mounted Kobo, delete items queued with request_remove, and ack
each one back to the server - the same thing the app's own Sync-to-Kobo
screen does, just from an agent instead of a browser (useful if you don't
use Chrome/Edge, since that screen needs the File System Access API).
It's plain Python standard library - requires Python 3 to be
installed, nothing else (check with python3 --version). No Node, Go, or
extra pip packages needed.
To install it: copy (or symlink) .agents/skills/rigormining/ into
~/.agents/skills/rigormining/, then ask your agent to add a paper or sync your Kobo -
it'll ask for your server URL and an API token (from the Settings page)
the first time. It's also downloadable as a zip from the in-app "Get
Extension" page if you don't want to clone the repo just for this.
backend/cmd/import-zotero is a one-off CLI tool that reads a local Zotero
data directory directly (zotero.sqlite + storage/) and imports every
non-trashed item into a running rigormining server over its normal API -
the same POST /api/v1/items and tag endpoints the browser extension uses.
It never touches your real zotero.sqlite (it works off a throwaway copy,
so it's safe to run while Zotero is open), and file-hash dedup means it's
safe to re-run.
From backend/, with the server already running (see above) and an API
token from the app's Settings page (/settings):
go run ./cmd/import-zotero \
--zotero-dir=/path/to/Zotero \
--token=<your API token> \
--server=http://localhost:3000
--zotero-dir is the folder containing zotero.sqlite and storage/
(commonly ~/Zotero on macOS/Linux, or wherever your Zotero data
directory was set to). Add --limit=N to import just the first N items as
a dry run before doing the whole library.
For each item it carries over title, authors, DOI/ISBN, URL, abstract (as notes), venue/volume/issue/pages/publisher, and year, attaches the best local file it can find (PDF over EPUB over anything else - items with no resolvable local file still import as metadata-only, same as bookmarking an item in the app), and merges Zotero tags and collection names into this app's flat tag list. It prints a summary of created vs. already-existing vs. no-file vs. failed items when it's done.
There's a repo-root Dockerfile and Makefile that build both frontend/
and backend/ into one image (make docker_build, then make docker_push
to Docker Hub - copy .env.template to .env first and set REPOSITORY/
PROJECT). Past that point deployment is manual in the Cloud Run console,
point the service at the pushed image tag, override
the RM_* env vars under Variables & Secrets with real values (never
RM_AUTH_DEV_LOGIN=true), and add a Cloud Storage volume mounted at
/root/datastore under Volumes/Volume Mounts. That path is where
RM_DB_CONNECTION (the sqlite file) and RM_LIBRARY_DIR (uploaded
PDFs/EPUBs) both resolve by default, so mounting a bucket there is what
makes them survive redeploys instead of living on one instance's throwaway
local disk. See the comment above the docker_run target in the root
Makefile for the exact steps, and the note on BUCKET in the root
.env.template for why that bucket must not be public.
- Library: upload PDFs/EPUBs (or bookmark an item with no file yet and attach one later), a left-hand tree of your tags/"projects" to browse and filter by, drag-and-drop items onto a tag to file them, bulk-select + "add to project", search by title/author.
- Reader: in-app PDF and EPUB viewer, page jump/total, download.
- Sync to Kobo: one-click copy of selected items onto a mounted Kobo via the File System Access API (Chrome/Edge only - Firefox doesn't support it yet, so on Firefox use the per-item Download link and copy over USB by hand), with the same tag tree to narrow down what you're looking at.
- Export: any item can be exported to a Markdown file with frontmatter (title/authors/year/doi/tags/notes) for use elsewhere (e.g. Obsidian).
Kobo highlight/annotation sync (pulling highlights back off the device) is still just in the desktop application: https://github.com/robrohan/stitch. Future versions should add that code here.
GPLv3, see LICENSE.





