Muninn
Muninn (雾尼) - An Extensible Reciting CLI.
What is Muninn?
Muninn is a highly extensible CLI application designed to help you memorize anything. Instead of hardcoding questions, Muninn relies on a Plugin Architecture. You can install "Reciting Packs" created by others (like chemistry elements, GRE vocabulary, or historical events) or develop your own packs using Python.
Muninn acts as a "host" that provides:
- A Smart Scheduling Algorithm (focuses on your weak-points).
- Persistent State Management (remembers your progress across sessions).
- A clean, distraction-free Terminal UI.
Installation & Usage
Install Muninn globally using uv (recommended) or pip:
# Using uv (Recommended)
uv tool install muninn-cli
# Or using pip
pip install muninn-cli
Available commands:
# List all installed packs
muninn list
# Install a pack (supports directories or .zip files)
muninn install path/to/pack_or_zip
# Uninstall a previously installed pack
muninn uninstall <pack_id>
# Run a specific pack by ID
muninn run <pack_id>
# Generate a new plugin template for development
muninn new <your_new_pack_id>
Plugin Development Guide
Muninn provides a layered API. Choose the level that fits your needs.
Quickstart: FlashcardPlugin (zero boilerplate)
For simple front/back flashcards (e.g. GRE words), create a CSV or JSON file with front and back columns, then write a 3-line plugin:
from core.helpers import FlashcardPlugin
class Plugin(FlashcardPlugin):
DATA_FILE = "words.csv"
That's it. FlashcardPlugin handles rendering, answer-checking, and ID generation automatically.
| File | Description |
|---|---|
manifest.json |
Pack metadata (name, author, version). |
words.csv |
Data with front and back columns. |
plugin.py |
The 3-line plugin above. |
For structured data: DataPlugin + QuestionType
When each data record can be quizzed from multiple angles, use DataPlugin. Declare your question types and let Muninn generate all problem variants automatically.
Example — Chemistry elements quizzed from 4 directions (symbol → name, name → number, etc.):
import os, json
from core.helpers import DataPlugin, QuestionType, Matchers
class Plugin(DataPlugin):
QUESTION_TYPES = [
QuestionType(
label="看序号背元素",
statement=lambda el: f"原子序数: {el['num']}",
answer=lambda el: f"{el['name']} {el['sym']}",
matcher=Matchers.chinese_symbol_pair("name", "sym"),
),
QuestionType(
label="看元素背序号",
statement=lambda el: f"元素: {el['name']} ({el['sym']})",
answer=lambda el: str(el["num"]),
matcher=Matchers.exact_integer("num"),
),
]
def load_records(self) -> list:
with open(
os.path.join(self.workspace_dir, "elements.json"), encoding="utf-8"
) as f:
return json.load(f)
def filter(self, record, q_type):
# Optional: skip certain question types for specific records
return True
DataPlugin auto-generates problem IDs ({record_index}__{question_label}) and routes all five interface methods. You only supply data + question types.
Built-in Matchers
Instead of writing custom regex for every question type, use the built-in Matchers factories:
| Matcher | Behavior |
|---|---|
Matchers.exact(key) |
Exact match after trimming whitespace. |
Matchers.exact_integer(key) |
Extract digits, compare numerically. |
Matchers.case_insensitive(key) |
Case-insensitive match. |
Matchers.chinese_symbol_pair(key1, key2) |
Match "中文+符号" or "符号+中文" in any order. |
Matchers.any_order(*keys) |
Match all field values appearing anywhere in the input. |
Matchers.custom(fn) |
Pass your own (record, user_input) -> bool function. |
Low-level: BaseRecitePlugin
For full control, implement the base interface directly:
from core.base_plugin import BaseRecitePlugin
class Plugin(BaseRecitePlugin):
def load_data(self):
# Load static data from self.workspace_dir
pass
def get_all_problem_ids(self) -> list[str]:
"""Return all unique problem IDs."""
pass
def render_statement(self, problem_id: str) -> str:
"""Return the question text to display."""
pass
def check_answer(self, problem_id: str, user_input: str) -> bool:
"""Return True if correct."""
pass
def get_expected_display(self, problem_id: str) -> str:
"""Return the correct answer to show on failure."""
pass
def get_expand_info(self, problem_id: str) -> str:
"""Optional: Return extra info to show on success."""
return ""
Developing a pack from scratch
-
Generate a template:
muninn new my_cool_pack
This creates a
my_cool_pack/directory withmanifest.jsonand a skeletonplugin.py. -
Write your logic using one of the approaches above.
-
Install and test:
muninn install ./my_cool_pack muninn run my_cool_pack
Release files for muninn-cli 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| muninn_cli-0.1.1.tar.gz | 32.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| muninn_cli-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 47.1 kB
Release files / muninn_cli-0.1.1.tar.gz
| Download URL | muninn_cli-0.1.1.tar.gz |
|---|---|
| Size | 32.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5a9c4d8acaf089fed638cecd0a8a09d1248235e63f1c43b5562ae624c492698c
|
|
BLAKE2b-256 checksum How to use checksums |
b8e233f9418523bf3cafbdeb8cbd6cd4e6727118f1c25dd666eb9c9ee4a5186a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / muninn_cli-0.1.1-py3-none-any.whl
| Download URL | muninn_cli-0.1.1-py3-none-any.whl |
|---|---|
| Size | 14.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0aafecf7d454784fd40fcf763be4efb8767d7d79a02e95aa14771c112bff792f
|
|
BLAKE2b-256 checksum How to use checksums |
cc8bcd5421dfedc1c32bd826a5addc180e38bb57fcd6a3aaac256ae096212ab7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|