Skip to main content

spaced repetition over an obsidian vault

Project description

mdsr

Spaced repetition for an Obsidian vault. You write flashcards as callouts inside your notes; mdsr parses them, schedules them with FSRS, and serves a small web UI for reviewing.

mdsr only reads from the vault. Schedule state lives in SQLite under your XDG data dir.

Card syntax

> [!sr] What is ...?
> The ...

> [!sr-bi] Cat
> Katze
  • [!sr] — one card, front (the question) → back.
  • [!sr-bi] — two cards, front ↔ back, marked as siblings (reviewing one hides the other until the next study day).
  • Obsidian's fold markers work: [!sr]- (collapsed) and [!sr]+ (expanded). Both are equivalent to [!sr].
  • LaTeX, code, images, wikilinks — anything Obsidian renders works here too.

Card identity is sha256(front). Editing the back keeps the schedule. Editing the front creates a new card (the old one is trashed; you can merge its schedule onto the new card from the sidebar).

Run

uv tool install mdsr
sr --vault /path/to/your/vault
# or, clone-on-first-run into $XDG_DATA_HOME/mdsr/vaults/:
sr --vault git@github.com:you/notes.git

If --vault is a git clone (or a git URL, which gets cloned on first run and reused after), the server runs git pull --ff-only every --poll-seconds (default 60), re-parses changed files, and updates card state. You can let trash get auto-purged after --trash-purge-days (default 30). If --vault is a plain directory it skips the pull and detects changes via per-file mtime instead.

Open http://127.0.0.1:8765, sr --help lists all flags.

[!TIP] With the Obsidian web viewer plugin, you can use mdsr directly within Obsidian.

Storage

One SQLite file per vault, under XDG data home:

~/.local/share/mdsr/<vault-basename>-<hash8>.db

The hash is the first 8 chars of sha256(resolved-vault-path), so running against two different vaults gives you two independent DBs automatically.

Override with --db-path if you want a specific location.

Deploy on a VPS

A single Python process. Bind it to 127.0.0.1 and reach it via Tailscale.

Keep it running with systemd

The sr command runs in the foreground and exits when its shell dies, so on a server you want something to (re)start it: daemonize at boot, restart on crash, survive logout. A user-level systemd unit:

[Unit]
Description=mdsr review server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=%h/.local/bin/sr --vault %h/vault --host 127.0.0.1 --port 8765
Restart=on-failure
RestartSec=10

[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user enable --now mdsr
loginctl enable-linger $USER   # keep the unit running after logout

Expose it on your tailnet

The simplest setup — one service on one machine — is one port:

tailscale serve --bg --https=443 http://127.0.0.1:8765

You'll then have it at https://<machine>.<tailnet>.ts.net.

If you want several services on the same machine, mount them under different URL paths. Tailscale forwards the prefix as-is, so the app has to know its own prefix — pass it via --root-path:

sr --vault ~/vault --port 8765 --root-path /sr
tailscale serve --bg --https=443 --set-path=/sr http://127.0.0.1:8765

Reachable at https://<machine>.<tailnet>.ts.net/sr/.

TODO

  • Core: Properly render note content,images (parsing and rendering step via https://onyx.md)
  • Cosmetic: Make wikilinks resolve to published vault (once vault is published via onyx)?

Develop

make dev      # uv sync --group dev
make test
make check    # lint + format
make fix      # ruff fix + format
make build    # uv build → dist/
make release-{patch,minor,major} # bumps pyproject.toml version, commits and git tags
make publish  # requires UV_PUBLISH_TOKEN

Inspiration

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mdsr-0.2.0.tar.gz (24.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mdsr-0.2.0-py3-none-any.whl (29.1 kB view details)

Uploaded Python 3

File details

Details for the file mdsr-0.2.0.tar.gz.

File metadata

  • Download URL: mdsr-0.2.0.tar.gz
  • Upload date:
  • Size: 24.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.4 {"installer":{"name":"uv","version":"0.11.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mdsr-0.2.0.tar.gz
Algorithm Hash digest
SHA256 775e83e8591a428e2428c873f9af676c8628f6dbe01fbc0ed84043f29e9a2590
MD5 fc69fd9f7dd59271aaf624ba188691fd
BLAKE2b-256 cf614a4e38ed52a70e277275a97995aa580649b439ae96ac72f23e8818ff62b2

See more details on using hashes here.

File details

Details for the file mdsr-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: mdsr-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 29.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.4 {"installer":{"name":"uv","version":"0.11.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mdsr-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a9d371f9439d0cdaa32e746068e25cf4213c9ecb0e486a4cdb3c2cb4cf28e60c
MD5 126cb37b66897a1ea5249f38d56706d7
BLAKE2b-256 3abe922f635e773687eb40b3a464edcaa4d277161b14c4f1294ffacc170b54fc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page