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

If --vault is a git clone, 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 the server still works — it just doesn't pull.

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.1.0.tar.gz (21.8 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.1.0-py3-none-any.whl (26.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: mdsr-0.1.0.tar.gz
  • Upload date:
  • Size: 21.8 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.1.0.tar.gz
Algorithm Hash digest
SHA256 26710d8b99c55c881ba21fcedac292d6ceb3084557c1f0bf095f3609d63b792a
MD5 2117de7a8bc42eec70078c21a7278b40
BLAKE2b-256 fec79cf7bb9679798e27aac9e31451cd12bdbb3178e01ec51915afe0ccc1941c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: mdsr-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 26.2 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ae854c7b4c7de45508a3788223b4d2fbe22a9da710c977dac1a26ccc3d70abce
MD5 65be133c2f1b7859f4ea8b67ecaf9fe1
BLAKE2b-256 64fe8139d03a5450c05aaff8b91ab63ac8e473a6a25f732b0e1889d13af5add5

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