Installation
Pick the method that matches your platform or workflow.
Install Script (Linux & macOS)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/shaankhosla/repeater/releases/latest/download/repeater-installer.sh | sh
Homebrew (macOS)
brew install shaankhosla/tap/repeater
Windows (PowerShell)
irm https://github.com/shaankhosla/repeater/releases/latest/download/repeater-installer.ps1 | iex
npm
npm install @shaankhosla/repeater
Optional: add a rpt shortcut
Use repeater in docs and scripts so examples stay canonical. If you prefer a shorter command locally, add rpt with one of these snippets.
macOS / Linux
Bash (~/.bashrc or ~/.bash_profile)
alias rpt='repeater'
Reload:
source ~/.bashrc # or: source ~/.bash_profile
Zsh (~/.zshrc)
alias rpt='repeater'
Reload:
source ~/.zshrc
Fish (~/.config/fish/config.fish)
alias rpt repeater
Reload:
source ~/.config/fish/config.fish
Optional: symlink instead of alias (any shell)
ln -s "$(command -v repeater)" /usr/local/bin/rpt
On Apple Silicon you may prefer /opt/homebrew/bin; add sudo if permissions require it.
Windows
PowerShell (profile)
notepad $PROFILE
Add this line, save, and restart PowerShell:
Set-Alias rpt repeater
Command Prompt (per session)
doskey rpt=repeater $*
Optional: rpt.cmd shim (permanent)
Create rpt.cmd on your PATH containing:
@echo off
repeater %*
Quick Start
-
Create a deck in Markdown (
cards/neuro.md).You can put your normal notes here, `repeater` will ignore them. Once a "Q:,A:,C:" block is detected, it will automatically turn it into a card. Q: What does a synaptic vesicle store? A: Neurotransmitters awaiting release. --- Use a separator to mark the end of a card^ Then feel free to go back to adding regular notes. C: Speech is [produced] in [Broca's] area.Alternatively, launch the built-in editor with:
repeater create cards/neuro.md -
Index the cards and start a drill session.
repeater drill cardsSpace/Enter: reveal the answer or cloze.O: open the first media file (image/audio/video) referenced in the current card before revealing the answer.F: mark asFail,Space/Enter: mark asPass.EscorCtrl+C: end the session early (progress so far is saved).
-
Check your collection status.
repeater check cardsThis launches the full-screen dashboard that shows totals, due/overdue cards, and upcoming workload; press
EscorCtrl+Cwhen you want to exit. Use--plainif you prefer a plain-text summary for scripts.
Card Format
Store decks anywhere, for example:
flashcards/
math.md
science/
physics.md
chemistry.md
Cards live in everyday Markdown. repeater scans for tagged sections and turns them into flashcards, so you can mix active-recall prompts with your normal notes.
-
Basic cards
Q: What is Coulomb's constant? A: The proportionality constant of the electric force. -
Cloze cards
C: The [order] of a group is [the cardinality of its underlying set].
Parsing Logic
- Cards are detected by the presence of a
Q:/A:orC:block. A horizontal rule (---) or the start of another card marks the end. - Each card gets a hash (think fingerprint) built from its letters, numbers, and any
+/-signs. Punctuation, spacing, and capitalization are ignored, so only meaningful text changes create a new history. - Metadata lives in
cards.dbunder your OS data directory (for example,~/Library/Application Support/repeater/cards.dbon macOS). Delete this file to reset history; the Markdown decks remain untouched. - Multi-line content is supported.
Edge case examples
- Markers must start at column 0. Indented
Q:,C:, or---lines are ignored by the scanner, so the snippet below produces zero cards.Q: Skipped A: Because the tag is indented - Next marker auto-closes the previous card. A new
Q:orC:flushes the current buffer even without---.Q: First? A: Ends here Q: Second starts now - Notes need a separator. Without a flush-left
---, trailing notes remain part of the last card.Q: Term? A: Definition This line still belongs to the answer - Basic cards require both tags. Missing or blank
Q:/A:blocks throw a parse error for that card.Q: What is ATP? --- ← rejected; no answer was captured - Cloze blocks need real
[hidden]text. Empty brackets or unmatched[/]abort parsing.C: Bad [] ← rejected C: Half [good ← rejected
Media in Cards
repeater scans every rendered card for media references (images, audio, and video). When the drill UI detects at least one supported file, you can press O before revealing the answer to open the first attachment in your operating system’s default viewer/player. This keeps cards lightweight in the terminal while still letting you jump into richer references on demand.
Supported formats
The following file extensions are detected:
- Images:
jpg,jpeg,png,gif,webp,bmp - Audio:
mp3,wav,ogg,flac,m4a - Video:
mp4,webm,mkv,mov,avi
Other links remain untouched so regular hyperlinks still work in your Markdown outside the drill UI.
Referencing media in Markdown
Use normal Markdown syntax for images () or links ([label](path/to/file.mp3)). repeater reads the destination path and decides if it looks like media based on the extension.
Relative paths are resolved from the directory that contains the deck file. For example, if your deck lives at notes/physics/waves.md, then:

[audio](../audio/tone.mp3)
will look for notes/physics/figures/wave.png and notes/audio/tone.mp3. Absolute paths work too, but keeping media alongside your decks makes them easier to sync and move.
Opening media during a drill
While drilling:
- The footer shows “media file found” whenever the current card links to supported media.
- Press
O(uppercase or lowercase) before revealing the answer to open the first listed file. - The file launches via the OS default handler (
openon macOS,xdg-openon Linux,starton Windows), so whatever app normally opens that file type will appear.
If a file cannot be found you’ll see File does not exist: … in the terminal. Double-check the relative path from the deck file and ensure the media is synced locally.
Multiple attachments can be detected, and the first one will open today; broader selection support is on the roadmap.
Commands
repeater drill [PATH ...]
Start a terminal drilling session for one or more files/directories (default: current directory).
--card-limit <N>: cap the number of cards reviewed this session.--new-card-limit <N>: cap the number of unseen cards introduced.--rephrase: rephrase basic questions via the LLM helper before the session starts.
Example: drill all the physics decks and a single chemistry deck, stopping after 20 cards.
repeater drill flashcards/science/physics/ flashcards/science/chemistry.md --card-limit 20
Key bindings inside the drill UI:
Space/Enter: reveal the answer or cloze.F: mark asFail,Space/Enter: mark asPass.O: open the first media file detected in the current card (images/audio/video). The file opens in your OS default viewer before the answer is revealed.Esc/Ctrl+C: exit the session.
repeater create <path/to/deck.md>
Launch the capture editor for a specific Markdown file (it is created if missing).
Ctrl+B: start a basic (Q:/A:) template.Ctrl+K: start a cloze (C:) template.Ctrl+S: save the current card; you’ll be warned if another card already uses the same meaningful text.- Arrow keys/PageUp/PageDown: move the cursor;
Tab,Enter,Backspace, andDeletework as expected. EscorCtrl+C: exit the editor.
Example:
repeater create cards/neuro.md
repeater check [PATH ...]
Re-index the referenced decks and open the interactive dashboard with totals for new, due, overdue, and upcoming cards (press Esc/Ctrl+C to exit).
--plain: print a plain-text summary to stdout instead of launching the dashboard.
Example:
repeater check flashcards/math/
repeater import <anki.apkg> <output-dir>
Convert an Anki .apkg export into Markdown decks. Existing files in the export folder are overwritten, so rerunning is safe. FSRS history is not yet transferred.
Example:
repeater import ~/Downloads/my_collection.apkg cards/anki
repeater llm [--set|--clear|--test]
Manage the optional OpenAI helper that can auto-cloze missing brackets and rephrase questions before a drill.
--set <KEY>: write the key to the local keyring (com.repeater/openai:default).--test: verify the configured key by calling OpenAI.--clear: delete the stored key; use this when rotating credentials.
Instead of --set, you can export REPEATER_OPENAI_API_KEY for one-off runs. Skip configuring this command entirely to keep the feature disabled.
LLM Usage
LLM helper (opt-in)
Opt in
- LLM calls are off until you provide an OpenAI API key.
- Skip every prompt to keep running fully offline.
API keys
repeater llm --set <KEY>saves the key via the OS keyring (com.repeat/openai:default), so macOS Keychain/Windows Credential Manager/libsecret hold it securely.REPEATER_OPENAI_API_KEYoverrides the keyring for temporary runs.repeater llm --testconfirms the key with OpenAI,repeater llm --clearforgets it instantly.
Cloze generation
- Run
repeater drill <deck>; if anyC:cards lack[],repeatersends that text to OpenAI (gpt-5-nano) and patches the file before the drill continues. - Leave the API key prompt blank (or skip configuring a key) to keep the feature idle.
Question rephrasing
- Run
repeater drill <deck> --rephraseto rephrase basicQ:questions before the session starts. - The original answers are provided as context but are not revealed in the rewritten questions.
FSRS Scheduling
repeater schedules every review with the Free Spaced Repetition Scheduler (FSRS). The upstream model treats each card’s stability (how long it can be recalled) and difficulty (how hard the content feels) as latent variables and adjusts them after every answer. This page documents how repeater applies the model, along with the opinionated choices that make it feel lightweight in a terminal workflow.
Core Parameters
- Target recall — Intervals are solved for a 90 % recall probability (
TARGET_RECALL = 0.9), which is the default in FSRS research and keeps workloads manageable. - Weights — The 19 FSRS-4 weights (
WEIGHTS) are compiled into the binary instead of being trained per-user. Everyone starts from the same curve, so reviews are predictable even without a calibration phase. - State tracked per card — Each row in
cards.dbstoresstability,difficulty,interval_raw,interval_days,due_date, andreview_count. The timers you see during drills are derived from these values, while the Markdown deck stays untouched.
Simplified Feedback Model
Classic FSRS expects four answer buttons. repeater distills that into two hotkeys: Pass and Fail. Internally those map to quality scores of 3 and 1 respectively, so the formulas for initial_stability, initial_difficulty, delta_d, and calculate_stability still behave correctly. The benefit is a fast keyboard workflow; the trade-off is that you can’t express nuances like “Hard” or “Easy”, so the algorithm falls back to its conservative defaults when scheduling recoveries after a lapse.
Early Review Ramp
FSRS is designed for day-scale intervals, so the code layers a short-term trainer on top:
| Review count before the answer | Result | Max delay |
|---|---|---|
| 0 (brand new) | Pass/Fail | 1 minute |
| 1 | Pass | 10 minutes |
| 1 | Fail | 1 minute |
| 2 | Pass | 1 day |
| 2 | Fail | 10 minutes |
These caps override the usual interval just for the first few answers, which keeps new material in front of you until you can reliably recall it. Once the review count exceeds two, the pure FSRS interval is used.
Learn-Ahead Window & Queueing
- The spaced repetition queue treats anything due within the next 20 minutes as “due now”. This is the
LEARN_AHEAD_THRESHOLD_MINS, and it means that when you sit down for a session you see cards that are about to become due so you don’t have to reopen the app later in the day. - During a drill, the interval returned from FSRS is compared against the same threshold. If it’s shorter than 20 minutes (for example right after a lapse) the card is immediately re-queued in the current session instead of waiting for a later run.
- The daily queue pulls overdue cards first, then cards due later today, and only then does it sprinkle in new cards—subject to your optional daily limits. That ordering makes sure FSRS’s promises (“you’ll keep 90 % recall”) remain accurate even if you have a backlog.
What Happens After Each Answer
- The elapsed time since the last review is measured to compute the recall probability FSRS expected at the moment you answered.
- Depending on whether you pressed
PassorFail, the algorithm updates stability and difficulty with the upstream formulas. - A new interval is solved for 90 % recall, rounded, clamped, and—if applicable—shortened by the early-review caps above.
- Metadata in
cards.dbis updated atomically so stats, thecheckcommand, and future sessions all agree on the next due date.
Further Reading
- FSRS whitepaper & wiki — background on the equations
repeatercalls into. - FSRS weights repository — reference implementation and tuning scripts if you want to experiment with your own parameter set.
Roadmap
- Import from Anki
- Allow scrolling to other cards in a collection while creating a new card
- Edit an existing card while keeping the progress intact
- Allow for a fuzzy search of existing cards
- Use LLMs to import from various content sources
FAQ
How is scheduling different from Anki?
repeater schedules cards with the Free Spaced Repetition Scheduler (FSRS) targeting ~90 % recall, so you get dynamically computed intervals instead of SM-2’s fixed ease multipliers. Inside the drill UI there are only two buttons—Pass (2) and Fail (1)—which the code maps to FSRS quality scores of 3 and 1, respectively, while still applying the upstream stability/difficulty math plus the short “learning” ramp for your first few reviews. The end result feels faster to grade yet still reuses FSRS’s predictions.
Where does my progress live?
Your decks stay in plain Markdown wherever you save them, but progress metadata (stability, difficulty, due dates, etc.) is tracked in cards.db under the platform’s application data directory (for example ~/Library/Application Support/repeater/cards.db on macOS). Back up or sync that file if you want to keep review history when moving machines; deleting it resets scheduling without touching the Markdown decks.
What happens if I edit or move a card?
Each card gets a hash that only looks at the actual letters, numbers, and any +/- signs. We ignore punctuation, spacing, and capitalization, so cleaning up commas or case won’t touch your streak. Rewrite the wording itself and you’ll start fresh. Moving blocks between files is safe because the text stays the same.
Can I study ahead or repeat lapses immediately?
Yes. Anything due within the next 20 minutes is considered “due now”, so repeater check will show it and drills will surface it alongside overdue cards. During a session, cards that fail or return ultra-short intervals (under that 20‑minute window) are automatically added back into the current queue so you can clear them before quitting.
Does the Anki import carry over scheduling data?
repeater import converts .apkg exports into Markdown decks today, but it does not migrate Anki’s per-card FSRS/SM-2 history yet. Imported notes will be treated as new cards and scheduled fresh once they’re indexed.
I’m a developer—what’s the quickest way to run checks locally?
Use the just precommit recipe to run cargo fmt, cargo clippy --fix, cargo machete, and the full test suite behind SQLX_OFFLINE=true. The Justfile also includes helper recipes for launching repeater create, check, drill, and import against sample data plus the release workflow, so contributors can rely on those instead of memorizing individual cargo commands.
Still stuck or ran into a bug? Please open an issue at github.com/shaankhosla/repeater/issues with logs and repro steps so we can help.