Skip to content

chanq-io/vultan

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

166 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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:

cargo install vultan

Or from a local clone of this repo:

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:

---
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)

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"),
)
---
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.

card_parsing_config: (
    decks_pattern:    TaggedLine(tag: "tags:"),
    deck_delimiter:   ",",
    question_pattern: TaggedLine(tag: "Q:"),
    answer_pattern:   TaggedLine(tag: "A:"),
)
---
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

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"),
)
---
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

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

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

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.

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

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 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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages