vultan 1.0.1

Terminal-based, Anki-compatible spaced-repetition study tool that reads flashcards from a directory of markdown notes.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# Vultan

A terminal-based, Anki-compatible spaced-repetition study tool that reads your
flashcards directly from a directory of markdown notes.

Vultan keeps your decks where your knowledge already lives - a zettelkasten or
notes folder - rather than in a proprietary database. Each `# Question` /
`# Answer` block in a `.md` file becomes a card; review state (due dates,
intervals, memorisation factor, review history, study streak) is persisted as
a single RON file alongside the notes (`.vultan.ron`) so it travels with the
source.

## Features

- **Markdown is the source of truth.** No proprietary database. Add or edit
  cards in any editor; vultan picks up changes on next refresh.
- **Configurable card format.** The defaults match a Q/A markdown layout, but
  any combination of tagged-line / wrapped-multi-line markers and deck
  delimiter is editable from in-app settings (no hand-editing of config
  files).
- **TUI dashboard** with per-deck due/week/mastered/forgotten/total counts,
  30-day review-activity and upcoming-due sparklines, and study streak.
- **Tunable SRS** per deck (pass / easy / fail coefficients) plus
  app-wide mastered/forgotten thresholds. SM-2 style with sane defaults.
- **Card-content search** (`F`) finds any card by substring of its question
  or answer; opens the hit in `$EDITOR` or shows its revision history.
- **In-app card editing.** Add a card with `N` (deck-tag frontmatter
  pre-filled), edit the current card during a review with `E`, no leaving
  the TUI.
- **Skip / Bury / Undo** during study, plus a per-card history view (`H`)
  showing every past review's score, post-transform factor, and interval.
- **Anki interop.** Import `.apkg` files (including modern zstd-compressed
  Anki 2.1.50+ exports); export your decks back to `.apkg` with optional
  `--with-state` to preserve factor/interval/due.
- **Safe persistence.** A 3-generation ring-buffer backup of `.vultan.ron`
  on every save, so a bad write or manual edit is recoverable. Optional
  auto-commit to git after each session.
- **Daily review limit** (cap session size to the most-overdue N cards).
- **Shell completions** for zsh, bash, and fish via
  `vultan completions <shell>`.

## Installing

From [crates.io](https://crates.io/crates/vultan):

```sh
cargo install vultan
```

Or from a local clone of this repo:

```sh
cargo install --path .
```

Either form drops a `vultan` binary on your `$PATH`. (You can also run from
source with `cargo run --bin vultan -- <args>`.)

## Card format

By default vultan looks for cards shaped like this:

```markdown
---
title: composite-number
tags: :math:algebra:
---
# Question
What is a composite number?
# Answer
A natural number that can be factored by at least one number other
than 1 and itself.
----
```

- Decks are derived from `tags:` (split on `:`).
- The question/answer markers are configurable - see below.
- Files that don't match are listed under `vultan problems` and skipped (and
  are reachable via `[P]` on the dashboard for in-app triage).

The format is described by four settings on the `card_parsing_config` field
of `.vultan.ron`, all four of which you can edit live from the dashboard's
*Global settings* screen (`[G]`) - no need to hand-edit the file. The four
fields are:

| Field | What it picks out |
|-------|-------------------|
| `decks_pattern` | The line / block listing the card's decks (tags) |
| `deck_delimiter` | How that line is split into individual deck names |
| `question_pattern` | Where the card's question text starts and ends |
| `answer_pattern` | Where the card's answer text starts and ends |

`decks_pattern`, `question_pattern`, and `answer_pattern` are each one of two
shapes:

- **Tagged-line** - a single `tag:` prefix; everything to the end of the line
  is the value. Good for one-line fields.
- **Wrapped multi-line** - an `opening_tag` and a `closing_tag` bracketing the
  field. Good for multi-line / multi-paragraph fields.

### Configuring card parsing

There are two ways to change the parsing rules:

1. **In-app** - open the dashboard, press `[G]`, navigate to one of the
   *Card parsing* rows, press `Enter`. The pattern editor lets you toggle
   between *Tagged-line* and *Wrapped* with `Tab` and edit the literal
   strings; saving validates and writes back to `.vultan.ron`.
2. **Edit `.vultan.ron` directly** - only if you really want to. The schema
   is RON; a malformed value will refuse to load and you'll see the error on
   the next run. Vultan keeps a ring of three backups (`.vultan.ron.bak.0..2`)
   on each save, so a bad write is recoverable.

#### Examples

**Default (multi-line wrapped Q/A, colon-separated tags)**

```ron
card_parsing_config: (
    decks_pattern:    TaggedLine(tag: "tags:"),
    deck_delimiter:   ":",
    question_pattern: WrappedMultiLine(opening_tag: "# Question", closing_tag: "# Answer"),
    answer_pattern:   WrappedMultiLine(opening_tag: "# Answer", closing_tag: "----\n"),
)
```

```markdown
---
tags: :math:algebra:
---
# Question
What is a composite number?
# Answer
A natural number that has more divisors than 1 and itself.
----
```

**Tagged-line Q/A, comma-separated decks**

Good if you prefer terse one-liners and don't need multi-paragraph answers.

```ron
card_parsing_config: (
    decks_pattern:    TaggedLine(tag: "tags:"),
    deck_delimiter:   ",",
    question_pattern: TaggedLine(tag: "Q:"),
    answer_pattern:   TaggedLine(tag: "A:"),
)
```

```markdown
---
tags: math, algebra
---
Q: What is a composite number?
A: A natural number that has more divisors than 1 and itself.
```

Note: `TaggedLine` only captures up to the end of the line - multi-line
question / answer text won't survive. Use `WrappedMultiLine` if you need it.

**Anki-flavoured Front/Back with horizontal-rule terminator**

```ron
card_parsing_config: (
    decks_pattern:    TaggedLine(tag: "deck:"),
    deck_delimiter:   "/",
    question_pattern: WrappedMultiLine(opening_tag: "## Front", closing_tag: "## Back"),
    answer_pattern:   WrappedMultiLine(opening_tag: "## Back",  closing_tag: "***\n"),
)
```

```markdown
---
deck: math/algebra
---
## Front
What is a composite number?

## Back
A natural number that has more divisors than 1 and itself.

***
```

The `closing_tag` for the answer needs *some* sentinel after the answer body
(here: a horizontal rule on its own line); without one vultan can't tell
where the answer ends.

## Quick start

```sh
vultan                         # opens the dashboard (first run prompts for notes dir)
vultan study --deck-name math  # study a single deck directly
vultan study --deck-name all   # study every due card across decks
vultan list-decks              # plain decks + due counts
vultan stats                   # rich per-deck breakdown
vultan problems                # paths of unparsable cards
```

The first time you run `vultan` with no subcommand, it prompts you for your
notes directory (Tab-completion supported). The path is saved to
`$XDG_CONFIG_HOME/vultan/config.ron` (falls back to
`~/.config/vultan/config.ron`) so subsequent runs go straight to the dashboard.

You can override the saved path on any subcommand with `--notes-dirpath`. An
explicit override is also persisted as the new default.

## Dashboard

```
┌─ VULTAN ─────────────────────────────────────────────────┐
│  Notes:    /Users/you/Code/personal/zettelkasten/study   │
│  Cards:    128 (24 errors)                               │
│  Streak:   3d (best: 14d)                                │
│  Last 7d:  41 reviews                                    │
└──────────────────────────────────────────────────────────┘
┌─ DECKS ──────────────────────────────────────────────────┐
│  DECK            DUE   WEEK   MASTERED   FORGOTTEN  TOTAL│
│ ▶algebra          75      4         12           5     80│
│  math            117      6         18           7    125│
│  ...                                                     │
│  « all due »     117      6         30          12    128│
└──────────────────────────────────────────────────────────┘
```

| Key | Action |
|-----|--------|
| ``/``, `j`/`k` | Move selection |
| `Enter` | Study the selected deck (`Enter` on `« all due »` ⇒ study every due card) |
| `/` | Filter decks by name (typing narrows the list) |
| `N` | New card in the selected deck - opens `$EDITOR` on a fresh `.md` file with deck-tag frontmatter pre-filled |
| `F` | **Find** card by content (substring on Q+A); `Enter` opens the match in `$EDITOR`, `F2` opens the card's revision history |
| `R` | Random card from the selected deck (read-only browse, doesn't affect SRS state) |
| `P` | Open the Problems screen (unparsable cards) |
| `,` | Open per-deck Settings (SRS coefficients) |
| `G` | Open Global settings (notes dir, daily review limit, auto-commit, card parsing) |
| `F5` | Re-read state from disk (pick up changes made externally) |
| `?` | Toggle keybinds overlay |
| `Q` / `Esc` | Quit |

## Study view

The session-info side panel shows live counters: cards passed, in-session
streak, elapsed time, reviews-per-minute pace, and the source `.md` file of
the current card.

Question mode:

| Key | Action |
|-----|--------|
| `A` | Reveal answer |
| `E` | Edit current card in `$EDITOR` |
| `S` | Skip - push to back of queue |
| `B` | Bury - defer this card until tomorrow |
| `U` | Undo last score |
| `H` | View this card's revision history (per-review score + factor + interval) |
| `?` | Toggle keybinds overlay |
| `Q` | Quit (with confirmation) |

Answer mode shows the predicted next-review interval next to each scoring key:

| Key | Action |
|-----|--------|
| `1` | Fail   |
| `2` | Hard   |
| `3` | Pass   |
| `4` | Easy   |

## Problems screen

Reachable with `P` from the dashboard.

| Key | Action |
|-----|--------|
| ``/``, `j`/`k` | Move selection |
| `E` | Open the selected file in `$EDITOR` |
| `M` | Move all unparsable files to a directory (Tab-complete the path) |
| `Q` / `Esc` | Back to dashboard |

A summary of each move (`moved 19 of 21`) appears in-screen - no stderr
smearing across the alt-screen.

## Per-deck Settings (`,`)

Reachable with `,` from the dashboard for the currently-selected deck. Edits
SRS coefficients and mastered/forgotten thresholds *for that deck*. App-wide
settings live in *Global settings* (`G`) instead.

| Key | Action |
|-----|--------|
| ``/``, `j`/`k` | Move between fields |
| ``/`` or `-`/`+` | Adjust value |
| `R` | Reset field to global default |
| `Enter` | Save (writes to `.vultan.ron`) |
| `Esc` | Cancel |

Fields: `Pass coefficient`, `Easy coefficient`, `Fail coefficient`,
`Mastered: factor`, `Mastered: interval`, `Forgotten: factor`.

## Global settings (`G`)

The unified hub for app-wide settings. From the dashboard press `[G]`.

```
┌─ GLOBAL SETTINGS ────────────────────────────────────────┐
│ App                                                       │
│   Notes directory       /Users/you/.../zettelkasten/study│
│   Daily review limit    unlimited                         │
│   Auto-commit state     off                               │
│                                                           │
│ Card parsing                                              │
│   Decks pattern         tagged-line "tags:"               │
│   Deck delimiter        ":"                               │
│   Question pattern      wrapped "# Question" ↔ "# Answer" │
│   Answer pattern        wrapped "# Answer"  ↔ "----\n"    │
└───────────────────────────────────────────────────────────┘
```

| Key | Action |
|-----|--------|
| ``/``, `j`/`k` | Move between rows |
| `Enter` | Edit the selected row (dispatches to a focused sub-editor) |
| `Esc` / `Q` | Back to dashboard (parsing-config edits are written on exit) |

Each row has its own editor:

- **Notes directory** - text input with `Tab`-completion of directory paths.
- **Daily review limit** - small text input (blank = unlimited).
- **Auto-commit state** - toggles on/off in place.
- **Pattern rows** - pattern editor: `Tab` toggles *Wrapped**Tagged-line*,
  ``/`` moves between visible text fields, type to edit, `Enter` validates
  (rejects empty), `Esc` cancels.
- **Deck delimiter** - small text input.

## Persistence model

- `.vultan.ron` (in your notes dir): card parsing config + per-card review
  state. Per-card `path` is relative to the notes dir, so the file is
  portable across machines. Also stores `last_study_date`, `current_streak`,
  `longest_streak`, per-card `review_history` (one date entry per scoring
  event), and per-card `review_log` (full snapshot of score + post-transform
  factor/interval). On every save vultan rotates a ring of three backups
  (`.vultan.ron.bak.0` is most recent, `.bak.2` is oldest), so a bad write or
  manual edit is recoverable.
- `~/.config/vultan/config.ron` (or `$XDG_CONFIG_HOME/vultan/config.ron`):
  user preferences (notes-dir path, daily review limit, auto-commit toggle).

`.vultan.ron` is safe (and recommended) to commit to your notes repo.

## Configuration files

Most users never need to hand-edit either file - the *Global settings* screen
covers everything in `config.ron` plus the `card_parsing_config` chunk of
`.vultan.ron`. For the curious:

`~/.config/vultan/config.ron`:

```ron
(
    notes_dirpath: Some("/Users/you/Code/personal/zettelkasten/study"),
    daily_review_limit: Some(50),
    auto_commit_state: true,
)
```

- `daily_review_limit` - when set, each session deals at most N cards (the
  most-overdue ones). `None` = unlimited.
- `auto_commit_state` - when true and the notes dir is a git repo, vultan
  runs `git add .vultan.ron && git commit -m "vultan: study session DATE"`
  after each session. Only the state file is staged; your notes are never
  touched by vultan.

CLI shortcuts to inspect or set the saved config without opening the TUI:

```sh
vultan config show                       # print the current config
vultan config set-notes-dir <path>       # persist a new notes dir
```

`--notes-dirpath` on any subcommand is a one-shot override and does **not**
update the saved default - use `config set-notes-dir` (or *Global settings*
in-app) for that.

## Anki interop

Vultan can both consume and produce Anki `.apkg` files for sharing decks
with Anki users.

```sh
vultan import-anki --apkg deck.apkg --into ~/notes/anki-import
vultan export-anki --apkg out.apkg --deck-name math
vultan export-anki --apkg out.apkg --with-state    # preserve factor/ivl/due
```

Without `--with-state` (the default), exported cards land in Anki as new -
the right behaviour for sharing. With `--with-state`, each card's
factor/interval/due is mapped onto Anki's review queue so the recipient
picks up where you left off.

Import handles modern Anki's zstd-compressed `collection.anki21b` format
(Anki 2.1.50+) - it'll skip the "please update Anki" decoy card that
modern Anki bundles for back-compat and pull the real cards out.

## Shell completions

```sh
vultan completions zsh > ~/.zfunc/_vultan
# add `fpath=(~/.zfunc $fpath)` and `autoload -U compinit && compinit` to ~/.zshrc
```

Bash and fish are also supported (`vultan completions bash` /
`vultan completions fish`).

## Status

Work in progress, but daily-driver usable. The CLI surface and config schema
may still shift; nothing on disk is encrypted or versioned beyond what your
notes-dir VCS already provides, so back up your zettelkasten the way you
already do.

## Notice

Vultan is an independent project. It is not affiliated with, endorsed by, or
sponsored by Ankitects Pty Ltd or the Anki project. "Anki" is a trademark of
Ankitects; the name appears in this README and in the `import-anki` /
`export-anki` subcommands solely to identify the [`.apkg` file
format](https://docs.ankiweb.net/) that vultan reads and writes for
interoperability. No Anki source code is bundled, linked, or derived;
vultan's own code is licensed MIT (see `LICENSE`), independent of Anki's
AGPL-3.0 license.