Metadata-Version: 2.4
Name: mdsr
Version: 0.2.6
Summary: spaced repetition over an obsidian vault
Keywords: spaced-repetition,fsrs,obsidian,markdown
Author: Maximilian Wolf
License-Expression: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Requires-Dist: fastapi>=0.136.1
Requires-Dist: uvicorn[standard]>=0.46.0
Requires-Dist: fsrs>=6.3.1
Requires-Dist: pydantic>=2.13.4
Requires-Dist: tyro>=0.9.0
Requires-Python: >=3.13
Project-URL: Repository, https://github.com/MaxWolf-01-clanker/mdsr
Description-Content-Type: text/markdown

# mdsr

Spaced repetition for an Obsidian vault. You write flashcards as callouts inside your notes; `mdsr` parses them, schedules them with [FSRS](https://github.com/open-spaced-repetition/free-spaced-repetition-scheduler), and serves a small web UI for reviewing.


## Card syntax

```markdown
> [!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

```sh
uv tool install mdsr
```

```sh
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](https://obsidian.md/help/plugins/web-viewer) plugin, you can use mdsr directly within Obsidian.

<details>
<summary>Storage</summary>

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

```
~/.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.

</details>


## Deploy on a VPS

A single Python process. Bind it to `127.0.0.1` and reach it via [Tailscale](https://tailscale.com).

### 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:

```ini
[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
```

```sh
systemctl --user daemon-reload
systemctl --user enable --now mdsr
loginctl enable-linger $USER   # keep the unit running after logout
```

### Expose it on your tailnet

Simplest case — mdsr owns the whole hostname:
```sh
tailscale serve --bg --https=443 http://127.0.0.1:8765
```
Reachable at `https://<machine>.<tailnet>.ts.net`.

To put it under a URL path (e.g. `/sr`, leaving room for other services on the same host), tell mdsr its prefix with `--root-path` and forward the entire hostname to it:

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

Reachable at `https://<machine>.<tailnet>.ts.net/sr/`. mdsr answers at `/sr/*`; other paths 404 at the app layer. For real path-based routing across multiple services, put a reverse proxy (Caddy, nginx) between tailscale and the backends.

# 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)?
- Cosmetic: Same/configurable css styles as onyx site

## Develop

```sh
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

- The Obsidian [Spaced Repetition](https://github.com/st3v3nmw/obsidian-spaced-repetition) plugin — the original cards-in-notes Obsidian SRS, and prior art for most of what this does.
- Andy Matuschak's [notes on evergreen note-writing](https://notes.andymatuschak.org/) — concept-orientation, cards living inside concept notes, and [*How to write good prompts*](https://andymatuschak.org/prompts/).
- Michael Nielsen, [Using spaced repetition systems to see through a piece of mathematics](https://cognitivemedium.com/srs-mathematics) and [Augmenting Long-term Memory](https://augmentingcognition.com/ltm.html).
- [FSRS](https://github.com/open-spaced-repetition/free-spaced-repetition-scheduler) and its Python port [`fsrs`](https://github.com/open-spaced-repetition/py-fsrs).

