Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lexicon

Local, personal spaced repetition over your Markdown notes. Point it at an Obsidian vault and it turns your notes into flashcards, schedules them with FSRS-5, and quizzes you in the terminal. Everything runs on your machine — no accounts, no sync service, no data leaving the box unless you point it at a hosted model yourself.

It also exposes your vault over MCP, so an LLM client like Claude Desktop can search and read your notes.

How it works

notes/*.md  ──sync──>  cards (FSRS)  ──review──>  ratings  ──>  next due date
     │                                                │
     └──────────> ChromaDB ──> search / MCP           └──> missed cards explained by the LLM

sync walks the vault, stamps each note with a stable uid, asks the model for cards, and indexes the note for semantic search. Re-running it is cheap: a note is only re-processed when its body actually changed, so nothing is regenerated and no duplicate cards appear.

Requirements

  • Python 3.14+
  • uv
  • A model. The default is llama3.2:3b on a local Ollama, but anything litellm supports works

Setup

uv sync

# Local model via Docker (skip if you're using a hosted provider)
docker compose up -d ollama
docker exec ollama ollama pull llama3.2:3b

cp .env.sample .env      # then edit if you want anything other than the defaults
mkdir -p notes           # or point LEXICON_NOTES_DIR at an existing vault

uv run lexicon sync
uv run lexicon review

Commands

Command What it does
sync Parse notes, generate cards, index for search. Safe to re-run.
review [--topic T] [--limit N] [--all] Review what's due, one card per screen. --all ignores the daily limit.
stats [--topic T] Card counts by state, predicted recall, true retention, streak.
search QUERY [--topic T] Semantic search across your notes.
cards list [--topic T] [--limit N] [--suspended] Cards in due order.
cards suspend ID / cards unsuspend ID Take a card out of / back into rotation.
prune [--yes] Clean up notes you deleted from the vault. Dry-run unless --yes.
serve Run the MCP server over stdio.

--topic matches a topic and everything nested under it, so --topic aws also covers aws/saa.

During a review: space reveals the answer, 14 rate it (Again / Hard / Good / Easy, each showing how far out it pushes the card), s suspends, u undoes the last rating, q quits. Cards you got wrong are explained by the model at the end of the session.

Writing notes

A bare .md file dropped anywhere in notes/ is already valid — no frontmatter required. Everything below is optional:

---
title: B-tree Indexes        # optional; falls back to the first "# H1", then the filename
tags: [postgres, indexing]   # optional
cards: generate              # generate (default) | extract | none
---

Your note body.

<!-- lexicon-card question="What does a B-tree index cost on write?" answer="Extra write amplification — every INSERT and UPDATE maintains the index pages." -->

The topic comes from the folder path, so notes/aws/saa/vpc.md has topic aws/saa.

Card modes:

  • generate (the default) — the model writes question/answer pairs from the note.
  • extract — the note already contains questions (an exam dump, a practice test); pull them out verbatim rather than inventing new ones.
  • none — index the note for search, but make no cards from it.

Manual <!-- lexicon-card --> annotations are always picked up regardless of mode, and are stripped from the text before indexing.

uid is stamped automatically on first sync. Don't edit it — it's what keeps your review history attached to a note when you rename or move the file. Two notes with the same uid is an error; delete the line from one and re-sync to get a fresh one.

Configuration

Everything is environment variables with a LEXICON_ prefix, read from .env:

Variable Default
LEXICON_AI_MODEL ollama/llama3.2:3b Any litellm model string
LEXICON_AI_BASE_URL (unset) Override the provider endpoint
LEXICON_NOTES_DIR notes Your vault
LEXICON_DATA_DIR data Where the DB and index live
LEXICON_DATABASE_URL sqlite:///data/lexicon.db
LEXICON_CHROMA_PERSIST_DIR data/chroma
LEXICON_DESIRED_RETENTION 0.9 FSRS target — higher means more frequent reviews
LEXICON_DAILY_REVIEW_LIMIT 20 Cards per review session
LEXICON_NEW_CARDS_PER_DAY 10 Caps how fast new material enters rotation
LEXICON_CARDS_PER_NOTE 5 Target when generating
LEXICON_MAX_NOTE_CHARS 24000 Sync fails past this rather than chunking blindly

MCP

lexicon serve exposes four read-only tools — search_knowledge, read_note, get_stats, list_topics — so a client can answer questions from your notes and tell you what you're weakest on.

Because every path defaults to a relative one, the client must launch the server with the project as its working directory:

{
  "mcpServers": {
    "lexicon": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/Lexicon", "lexicon", "serve"]
    }
  }
}

Layout

src/lexicon/
├── cli.py          typer commands
├── config.py       pydantic-settings
├── exceptions.py   LexiconError base
├── db/             SQLAlchemy models + engine
├── core/           parser, scheduler (FSRS), review session, sync pipeline
├── ai/             card generation, explanations for missed cards
├── search/         ChromaDB indexing and query
├── ui/             all Rich rendering, formatting, key input
└── mcp/            FastMCP server

Notes live in notes/, state in data/ — both gitignored.

About

AI-powered flash card CLI app

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages