Skip to content

rix1/r3

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

R3

R3 is a personal Readwise Reader -> reMarkable -> Readwise highlight loop.

The problem it tries to solve is narrow: Readwise Reader is where saved articles arrive, but the reMarkable is where longer reading and highlighting feels best. R3 sends Reader articles to the tablet, lets reading happen there at its own pace, then pulls text-backed tablet highlights back into Readwise. Reflect and other note systems can stay downstream through Readwise's existing integrations.

This repo is an experimental spike, not a polished product. The current direction is a pragmatic "Send to reMarkable" workflow rather than full two-way highlight sync between platforms.

Current shape

  • Source of truth for saved articles: Readwise Reader.
  • Delivery format for the tablet: EPUB, prefixed as [RW] {title}.epub.
  • Transport: reMarkable USB Web at http://10.11.99.1.
  • Readwise integration: the maintained readwise CLI, not direct HTTP calls.
  • Local state: SQLite in state/r3.db.
  • Annotation recovery: reMarkable OS 3.26 .rmdoc archives, reading .rm v6 SceneGlyphItemBlock text highlights via rmscene.
  • Downstream notes: handled outside R3 by Readwise integrations.

What r3 sync does in one pass

  1. Lists Readwise Reader docs in inbox + later updated within the last 6 months (configurable via --cutoff-months).
  2. Uploads the ones the tablet doesn't already have, named [RW] {title}.epub.
  3. Downloads any tablet doc that's been modified since the last sync, extracts text-backed highlights from the OS 3.26 .rm files.
  4. Writes recovered highlights back to Readwise via readwise reader-create-highlight (with a generic-highlight fallback when the recovered text doesn't match a Reader HTML element).
  5. Surfaces a macOS notification with the run summary; per-run details land in state/logs/sync-<timestamp>.log.

Ordering: new uploads are sequenced by Reader's last_moved_at ascending, so the most recently moved/saved Reader doc ends up with the freshest tablet ModifiedClient — i.e. on top of the tablet's "Most recent" view, matching Reader's ordering.

Known v1 limitation: moving a doc in Reader after it's already on the tablet doesn't re-rank the existing tablet copy (its ModifiedClient is locked at upload time, and USB Web has no delete endpoint to clear a re-upload's orphan). v2 cloud sync via rmapi will fix this — until then, re-ranking requires a manual delete on the tablet followed by r3-sync --resend <reader_id>.

Another intentional limitation: R3 does not try to mirror highlights created directly in Readwise back onto the tablet. For now, tablet highlights are the write-back source.

Dedup rules:

  • Tablet delete is sticky. If r3 finds a doc it previously uploaded is now gone from the tablet, it marks the doc removed-by-user and will not re-upload. Use r3 sync --resend <reader_document_id> or --resend all-missing to re-send.
  • Writebacks cap at 3 attempts. Failed writebacks are persisted with attempts and last_error. After 3 attempts, they're skipped permanently. Use r3 sync --retry-writebacks to clear non-applied rows and retry.

Setup

Requirements:

  • Python 3.11+
  • uv
  • authenticated readwise CLI
  • a reMarkable tablet with USB Web enabled
uv sync
readwise --version       # Readwise CLI must be installed and authenticated
readwise config show

On the tablet: connect by USB, unlock, enable Settings -> Storage -> USB web interface.

The tablet's USB Web interface is only active while the device is awake. If r3 sync reports "USB Web unreachable", tap the tablet screen to wake it, then re-run.

Optional fish wrapper (so you can run r3-sync from anywhere):

ln -sfn "$(pwd)/scripts/r3-sync.fish" ~/.config/fish/functions/r3-sync.fish

Usage

r3-sync                        # full sync pass
r3-sync -v                     # stream all log lines to stderr while running
r3-sync --dry-run              # plan without rendering, uploading, or writing
r3-sync --no-notify            # suppress the macOS notification

r3-sync --resend <reader_id>   # un-stick a manually-deleted tablet doc
r3-sync --resend all-missing
r3-sync --retry-writebacks     # retry failed writebacks past the 3-attempt cap

r3-sync --cutoff-months 3      # narrow the source set

While running, r3 sync prints ==> ... progress lines for each phase boundary and per-document step (e.g. ==> [3/12] uploading: The Paper Loop). With -v, the full INFO stream from the per-run log file is also echoed to stderr.

Equivalent without the wrapper:

uv run python -m r3 sync [flags]

The first successful run creates state/r3.db, renders EPUBs under output/sync/, uploads missing Reader articles to the tablet, and records the tablet document IDs for future extraction/write-back passes.

Inspection commands

uv run python -m r3 db list-documents       # what r3 knows about
uv run python -m r3 db list-highlights      # recovered highlights
uv run python -m r3 db writeback-stats      # writeback status counts
uv run python -m r3 db reset                # drop and recreate the DB

uv run python -m r3 readwise list --location new --updated-after 2026-01-01T00:00:00Z
uv run python -m r3 remarkable status
uv run python -m r3 remarkable list

Tests

uv run python -m unittest discover -s tests

Repo structure

src/r3/                         # the package
  cli.py                        #   argparse, lock acquisition, notification dispatch
  sync.py                       #   run_sync orchestration + SyncSummary
  config.py                     #   SyncConfig + default paths
  storage.py                    #   SQLite schema + upsert/state helpers
  readwise.py                   #   thin wrapper around the readwise CLI
  remarkable.py                 #   .rmdoc -> snapshot -> recovered highlights
  usb.py                        #   reMarkable USB Web client (10.11.99.1)
  rendering.py                  #   Reader HTML -> EPUB / PDF / preview HTML
  html_matching.py              #   recovered text -> Reader html_content element
  writeback.py                  #   build readwise-create-highlight actions
  article.py                    #   Article dataclass + Reader payload normalize
  notify.py                     #   osascript notification dispatch

tests/test_r3.py                # unit tests against the fixtures
fixtures/                       # synthetic Reader payload + sanitized .rm snapshot
scripts/r3-sync.fish            # fish wrapper (symlink into ~/.config/fish/functions)
docs/                           # spike plan, snapshot contract
state/                          # local state (gitignored)
  r3.db                         #   SQLite sync state
  r3.lock                       #   fcntl flock for r3 sync
  logs/sync-<timestamp>.log     #   per-run log
output/                         # rendered artifacts (gitignored)
  sync/<reader_id>.epub         #   EPUBs uploaded by r3 sync

Docs

  • docs/spike-plan.md records the PDF -> EPUB spike and product direction.
  • docs/remarkable-snapshot-contract.md documents the OS 3.26 annotation shape this extractor currently understands.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages