rmprint
by marioallaa
README.md
<div align="center">
<img src="assets/logo.png" width="104" alt="">
# rmprint
**Your reMarkable, from the terminal and from Claude.**
A virtual printer, a CLI, and an MCP server. One required dependency.
Windows, macOS and Linux.
</div>
---
```bash
git clone https://github.com/marioallaa/rmprint
cd rmprint
python install.py # Windows: double-click install.bat
```
That's it. A window opens and walks you through four steps: connect your
account, add the printer, keep it running, hook it up to Claude.
<div align="center"><br>
**Then just press Ctrl+P and choose `reMarkable`.**
</div>
And with Claude:
> **You:** what did I write in my notes about the supplier options?
> **Claude:** *reads the handwritten pages* — You compared three: Northwind…
## What it does
**Sends.** PDFs and EPUBs, into any folder. Or typeset text straight from
stdin — no browser, no LaTeX, no fonts to install.
**Prints.** `rmprint printer install` adds a real system printer. Ctrl+P from
any application, pick **reMarkable**, and it lands on the tablet with its
proper document name.
**Reads.** Lists your library, pulls text out of PDFs and EPUBs, and renders
handwritten pages as images — including annotations drawn over the original.
**Talks to Claude.** Seven MCP tools, plus two Claude Skills that teach it how
to write for a tablet and how to read handwriting properly.
What it deliberately cannot do: rename, move, delete, or create folders. It
never writes to the sync tree, so the worst a bug in here can do is fail.
## Install
Needs Python 3.10 or newer. Nothing else, and no administrator rights.
| | |
|---|---|
| **Windows** | Double-click `install.bat` |
| **macOS / Linux** | `./install.sh` |
| **Any** | `python install.py` |
The installer adds rmprint to your applications menu, sets it to start when
you sign in, and opens the window. Open it again any time from the menu, or
with `rmprint`.
<div align="center">
<img src="assets/screenshot.png" width="440" alt="The rmprint window">
</div>
To undo everything: `python install.py --uninstall`.
**Prefer the terminal?** `pip install ".[notes]"` and use the commands below —
`pipx` and `uv tool install` work too. Drop `[notes]` if you only want to
*send* documents; that extra is what enables *reading* them back.
### Connect your account
The window's step 1 does this, but by hand it's:
1. Open <https://my.remarkable.com/device/desktop/connect> and sign in.
2. Copy the eight-character code. It expires in a few minutes.
3. `rmprint pair <code>`
Already use [rmapi](https://github.com/ddvk/rmapi)? rmprint reads its token
from `~/.rmapi`, so you can skip pairing entirely. On Windows it will also use
the reMarkable desktop app's login if that's signed in.
## The printer
The window's step 2 adds it. By hand:
```bash
rmprint printer install --folder Reading
```
Then print to **reMarkable** from any application.
An agent runs in the background to upload what you print. It starts when you
sign in, restarts itself if anything goes wrong, and logs to
`agent.log` next to your config. You can start it by hand with
`rmprint agent`, and see whether it's up in the window or with
`rmprint printer status`.
**On Windows** no driver is installed. Windows already ships *Microsoft Print
To PDF*, and the Local Port monitor accepts a *file path as the port name* —
point the driver at one and it renders straight to that file with no Save As
dialog. A watcher then recovers the document title from the print spooler and
uploads. No administrator rights needed.
**On macOS and Linux** CUPS is already a PDF pipeline, so the printer is a
queue plus a small backend that pipes each job to `rmprint send`. Installing a
backend writes into `/usr/lib/cups/backend`, so:
```bash
sudo rmprint printer install --folder Reading
```
No watcher is needed there — CUPS hands the job over directly.
> The Windows path is tested on real hardware. The CUPS backend follows the
> documented contract but has not been tested by the author. Reports welcome.
## Commands
```
rmprint open the window (same as `rmprint gui`)
rmprint pair <code> connect to a reMarkable account
rmprint status check the connection
rmprint folders list folders
rmprint send FILE send a PDF or EPUB
rmprint text [TEXT] typeset text (or stdin) and send it
rmprint ls [FOLDER] list documents
rmprint read DOC print a document's text
rmprint page DOC render a page as PNG, handwriting included
rmprint pull DOC download the original PDF or EPUB
rmprint printer install|remove|status|watch
rmprint agent the always-on uploader (starts at login)
rmprint mcp run the MCP server over stdio
rmprint install-claude register the MCP server with Claude Desktop
```
`DOC` can be an id, an exact title, or any distinctive fragment of one.
```bash
rmprint send contract.pdf -f Clients -t "Acme — MSA v3"
cat notes.md | rmprint text --title "Meeting notes" -f Work
rmprint text --title "Preview" -o out.pdf < notes.md # local, don't send
rmprint read "board pack" | head -40
rmprint page "board pack" --page 3 --width 1800
```
## Using it with Claude
```bash
rmprint install-claude # --dry-run to see the change first
```
Restart Claude Desktop. Any existing config is backed up alongside the
original before it's touched.
By hand, or for another MCP client:
```json
{
"mcpServers": {
"rmprint": { "command": "python", "args": ["-m", "rmprint", "mcp"] }
}
}
```
| | |
|---|---|
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Linux | `~/.config/Claude/claude_desktop_config.json` |
### The tools
| Tool | Does |
|---|---|
| `status` | Is this connected, and to which account. |
| `list_folders` | Where a document could go. |
| `send_text` | Typeset text and send it. |
| `send_document` | Send a PDF or EPUB from disk. |
| `list_documents` | What's on the tablet, with page counts. |
| `read_document` | The text layer of a PDF, EPUB, or typed notebook text. |
| `get_page_image` | A page as an image — handwriting and annotations. |
Handwriting is never transcribed by rmprint. It renders the page and lets
Claude read it, which works far better than any OCR that could ship in here.
### The Skills
Two Claude Skills ship in [`skills/`](skills):
- **[remarkable-notes](skills/remarkable-notes/SKILL.md)** — how to write for a
device that gets read away from a screen and annotated by hand, and how to
file things. It also tells Claude to *remember how you like it done*: which
folder work reading goes in, how you title client documents, whether you want
a summary before the bullets.
- **[remarkable-reading](skills/remarkable-reading/SKILL.md)** — how to read
handwriting properly: page by page, transcribe before summarising, say what
you can't make out, and preserve the structure that boxes and arrows carry.
**Install the skills** in the window (step 4) copies both into
`~/.claude/skills/`. Or copy the directories there yourself.
## How it works
There is no public reMarkable API. This was worked out by watching the official
desktop app.
**Pairing.** A one-time code is traded for a permanent device token:
```
POST webapp.cloud.remarkable.com/token/json/2/device/new
{"code": "...", "deviceDesc": "desktop-windows", "deviceID": "<uuid>"}
```
**Sessions.** The device token buys a user token good for about three hours.
Its `tectonic` claim is your storage region (`eu`, `us`, …), which picks the
sync host `<region>.tectonic.remarkable.com`.
**Uploading.** The document goes up whole, with title and destination in a
header; the cloud unpacks it into the sync tree itself:
```
POST internal.cloud.remarkable.com/doc/v2/files
Content-Type: application/pdf
rm-meta: base64({"file_name": "...", "parent": "<folder uuid>"})
```
**Reading** is a read-only walk of `/sync/v3/`. Two details there cost the most
time, so to save you the trouble:
- Every blob request needs an **`rm-filename` header whose extension the server
validates** — `root.docSchema` for the root index, `<uuid>.docSchema` for a
document index, `<uuid>.metadata` for metadata. Anything else returns
`400 unexpected 'rm-filename' http header`, which is *also* what you get when
the header is missing entirely, so the message misleads.
- That header is matched **case-sensitively**. Python's `urllib` title-cases
header names and sends `Rm-Filename`, which is rejected — hence `http.client`
for those calls.
Index blobs are plain text: a schema line `3`, then
`<sha256>:<type>:<name>:<subfiles>:<size>` per entry, where type `80000000`
marks a document index and `0` a plain file.
**Handwriting** is `.rm` v6, parsed by [rmscene](https://github.com/ricklupton/rmscene)
and rendered to SVG by [rmc](https://github.com/ricklupton/rmc), then rasterised
with PyMuPDF and composited over the original page.
## Where things are kept
One file, holding one device token:
| | |
|---|---|
| Windows | `%APPDATA%\rmprint\config.json` |
| macOS | `~/Library/Application Support/rmprint/config.json` |
| Linux | `~/.config/rmprint/config.json` |
`chmod 600` on POSIX. Set `RMPRINT_HOME` to move it. Delete it to disconnect;
there is nothing else to clean up.
## Development
```bash
pip install -e ".[dev,notes]"
pytest
```
The suite covers the PDF writer — including that the xref offsets actually
point at their objects, which is the classic way to emit a file readers reject
— and the pure helpers. Nothing in it touches the network.
## Notes
Undocumented endpoints, so reMarkable can break this whenever they like. Your
documents go to reMarkable's cloud and nowhere else: no server in the middle,
no telemetry.
Related work worth knowing: [rmapi](https://github.com/ddvk/rmapi) (a
full-featured Go CLI), [rmfakecloud](https://github.com/ddvk/rmfakecloud) (host
the cloud yourself), and [awesome-reMarkable](https://github.com/reHackable/awesome-reMarkable).
Not affiliated with reMarkable AS.
## Licence
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues