Anki ↔ Markdown, with bidirectional sync, custom note types, and LLM integration
Project description
AnkiOps
AnkiOps is a bidirectional Anki ↔ Markdown bridge where each deck becomes a Markdown file. Edit in plain text, version with Git, enhance with LLMs, and sync changes both ways.
Features
- Full Anki support: Safe, performant bidirectional syncing of notes with custom note types, (sub-)decks, and media files
- Markdown-first: Edit in your favourite editor and render Markdown features in Anki (including syntax highlighting)
- Simple CLI interface for importing and exporting between Anki and the filesystem
- LLM-integration: Run programmable tasks such as content review, grammar fixes, or translations on your collection
- GitHub collaboration: Publish deck trees to GitHub, subscribe to shared decks, pull updates, and contribute changes through PRs (experimental)
[!NOTE] AnkiOps only acts on note types defined within the
note_types/folder. You can add note types from Anki using various methods described below.
Installation
- Install AnkiOps via pipx: Pipx makes AnkiOps available in your terminal.
pipx install ankiops
- Initialize AnkiOps: Make sure that Anki is running, with AnkiOpsConnect enabled. (For collab operations, the AnkiOps addon must be manually installed which is currently experimental.) Run
ankiops initin an empty collection directory. AnkiOps creates.ankiops.db,llm/, andnote_types/. The tutorial flag also creates a sample Markdown deck.
ankiops init --tutorial
- Import Markdown into Anki: Import the current collection of Markdown decks into Anki.
ankiops ma # alias for markdown-to-anki (import)
- Sync Anki back to Markdown: After you review or edit cards in Anki, export those changes. Each sync makes one side match the other.
ankiops am # alias for anki-to-markdown (export)
FAQ
How is this different from other Markdown tools?
Most Markdown-to-Anki tools import one way: you write Markdown and push it to Anki. AnkiOps lets you edit in either place and sync back. It stores each deck as one Markdown file, so you browse decks instead of hundreds of per-card files. It also keeps custom note type definitions beside your decks, which lets you edit both card content and card structure from your editor.
Is it safe to use?
Yes, AnkiOps will never modify notes that are not defined within the note_types/ folder. Your existing collection won't be affected and you can safely mix managed and unmanaged notes within one deck. Further, AnkiOps only syncs if the activated profiles matches the one it was initialized with. Concerning the Markdown files, AnkiOps automatically creates a Git commit of your collection folder before every sync, so you can always roll your files back if needed.
How do I create new notes?
Create a new Markdown file in your initialized AnkiOps folder. On the first import, AnkiOps uses the file name as the deck name. Subdecks use __ (for example, Anatomy::Heart maps to Anatomy__Heart.md). Separate notes with a blank line, three dashes ---, and another blank line. You can add new notes anywhere in an existing file.
<!-- note_key: 123487556abc -->
<!-- note_type: AnkiOpsQA -->
<!-- tags: AnkiOps -->
Q: Question text here
A: Answer text here
E: Extra information (optional)
M: Content behind a "more" button (optional)
---
<!-- note_key: 123474567def -->
<!-- note_type: AnkiOpsCloze -->
T: Text with the {{c1::first}} cloze.
Text with the {{c1::second}} cloze.
E: Some *formatted* extra info.
{width=700}
---
Q: What is this?
C1: A multiple choice note
C2: with
C3: randomized answers.
A: 1, 3
On the next import, AnkiOps assigns a note_key and note_type comment to the last note.
How are different note types handled?
AnkiOps reads note types exclusively from your local note_types/ directory. ankiops init ejects default note types as bootstrap files; those local files are then the only source of truth and can be modified as needed. Each note type is identified by a unique set of field labels. These labels are defined innote_types/name/note_type.yaml and can be customized as needed. The set operations of each unique note type are defined by the identifying fields in the yamls. For an overview of the current configuration, use ankiops note-types.
How can I migrate my existing notes into AnkiOps?
You can migrate existing notes in three ways: write matching note type files in note_types/, copy note types from Anki with ankiops note-types --add <name>, or convert notes to the default AnkiOps note types with Change Note Type… in the Anki browser.
For Anki browser conversion, use this workflow:
- Convert your existing notes to the matching AnkiOps note types via
Change Note Type…in the Anki browser. - Export your notes from Anki to Markdown using
ankiops am. - Review the Git diff after the first re-import. Original Anki HTML may not match CommonMark, so the first Markdown-to-Anki sync can change formatting. Formatting issues can be fixed by hand or via LLM tasks.
How does it work?
AnkiOps assigns a stable note_key to each managed note. In Markdown, it writes the key as a single-line HTML comment above the note, such as <!-- note_key: a1b2c3d4e5f6 -->. AnkiOps also writes a derived note_type comment, such as <!-- note_type: AnkiOpsQA -->, so you can see the resolved type in the file. Note types are inferred by sets of identifying fields (e.g. Q:, A:), defined in the note type folder. AnkiOps note keys do not depend on Anki's note IDs. The .ankiops.db database maps Anki note IDs to AnkiOps note keys and stores sync metadata. During sync, AnkiOps uses note keys to decide which notes to create, update, or delete. It stores media in media/ with hashed file names to avoid name conflicts.
What is the recommended workflow?
We recommend VS Code because it previews Markdown and can paste clipboard images into the media/ folder that AnkiOps creates during init.
How can I share my AnkiOps collection? (experimental)
Use ankiops collab from a Git-backed collection. AnkiOps stores each collab source at collab/<owner>/<repo> and pulls from https://github.com/<owner>/<repo>.git on the main branch. AnkiOps scopes note types from that source as collab/<owner>/<repo>/<note_type>, so two shared decks can use the same local note type names without colliding.
To publish one of your decks:
ankiops collab publish "Deck Name" owner/repo
publish requires a clean Git index and note_key metadata on every selected note. It includes the selected deck and its subdecks, copies referenced media and used note types into the collab source, commits the move, and pushes the subtree to GitHub. If the GitHub repository does not exist and the gh CLI is available, AnkiOps creates a private repo by default. Pass --public to create a public repo.
To use a shared deck:
ankiops collab subscribe owner/repo
ankiops collab pull owner/repo --to-anki
subscribe adds the GitHub repository as a subtree. pull updates one source, or all sources when you omit owner/repo. --to-anki runs Markdown-to-Anki after the pull.
To contribute local edits back:
ankiops collab contribute owner/repo --from-anki
--from-anki exports Anki edits to Markdown first. contribute then commits the collab source, creates a subtree branch, and opens a pull request with gh when possible. Without gh, AnkiOps leaves you with the branch name and GitHub remote so you can push and open the PR yourself.
How do I upgrade AnkiOps to the latest version?
AnkiOps is in early development, so breaking changes are expected. Use pipx upgrade ankiops to upgrade AnkiOps. Delete local AnkiOps support files except your Markdown decks, re-initialize the same folder with ankiops init, and export from Anki with ankiops am. If your collection is in Git, inspect the diff before you continue syncing.
What is the Add-on for?
The Add-on has two main purposes. First, it adds am and ma buttons to the Anki toolbar for quick sync. Second, it implements AnkiOpsConnect, an extension of AnkiConnect that enables operations for the collaboration features (mainly the conversion of note types without losing schedule information). The Add-on is still experimental and not available on AnkiWeb yet. To install it, download the folder and put it in your Anki add-ons directory.
Advanced: the CLI sends AnkiOpsConnect requests to http://127.0.0.1:8766 by default. For connection to a different host, set ANKIOPS_CONNECT_URL :
ANKIOPS_CONNECT_URL=http://host.docker.internal:8766 ankiops ma
When this variable is set, AnkiOps does not fall back to AnkiConnect; connection problems fail fast. This changes only where the CLI connects, not where the Anki add-on listens.
How can I develop AnkiOps locally?
Fork this repository and initialize the tutorial from the repository root while Anki is running. The commands below create collection/ with the sample Markdown deck and run the CLI from source.
git clone https://github.com/visserle/ankiops.git
cd ankiops
uv sync
uv run python -m main init --tutorial
uv run python -m main ma
Are Pull Requests welcome?
Yes. Bug fixes, feature work, and documentation PRs are welcome. Open an issue first for behavior changes.
What commands and flags are available in the CLI?
Global:
--debug- Enable debug logging--version- Show installed AnkiOps version--help- Show help message
init:
--tutorial- Create tutorial markdown file
anki-to-markdown / am:
--no-auto-commit,-n- Skip automatic git commit
markdown-to-anki / ma:
--no-auto-commit,-n- Skip automatic git commit
note-types:
ankiops note-types- Show note types, identifying labels, and the label registryankiops note-types --add <name>- Copy a note type from Anki into localnote_types/with interactive label/identifying prompts
serialize:
--output,-o- Output file path (default:AnkiCollection.json,<deck-stem>.jsonwhen--deckis set)--deck- Serialize only one deck (includes subdecks by default)--no-subdecks- With--deck, exclude subdecks (exact deck only)
deserialize:
--input,-i- Input file path (default:AnkiCollection.json)--overwrite- Overwrite existing markdown files
fix-image-widths:
--deck- Fix only one deck (includes subdecks by default)--no-subdecks- With--deck, exclude subdecks (exact deck only)--tolerance- Pixel tolerance for near-equal width fixes (default:5)--width- Force all Markdown images in scope to this width--no-auto-commit,-n- Skip automatic git commit
llm:
ankiops llm- Show LLM status dashboard (tasks + recent jobs)ankiops llm <task_name> [--model <model>] [--deck <deck_name>]- Plan one configured taskankiops llm <task_name> --run [--model <model>] [--deck <deck_name>] [--no-auto-commit]- Run one configured task jobankiops llm --job <job_id|latest>- Show one LLM job in detail
collab:
ankiops collab publish <deck> <owner>/<repo> [--public|--private]- Publish a local deck tree as a GitHub collab sourceankiops collab subscribe <owner>/<repo>- Add a GitHub collab source to the collectionankiops collab pull [owner/repo] [--to-anki]- Pull one collab source, or all sources when omittedankiops collab contribute <owner>/<repo> [--from-anki]- Prepare a contribution branch and PR for local collab editsankiops collab status- Show known collab sources
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ankiops-0.6.0.tar.gz.
File metadata
- Download URL: ankiops-0.6.0.tar.gz
- Upload date:
- Size: 125.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
322814665be4133159a85b097748e6797894086a5eb224d60b6c0003ae3f6331
|
|
| MD5 |
3f234df6df2bf355df2a8ea3cb2df0a3
|
|
| BLAKE2b-256 |
0304fc72b920061e4a6f6a3bc34471ce035c845d7568a5696043b3c8e5a02ea6
|
Provenance
The following attestation bundles were made for ankiops-0.6.0.tar.gz:
Publisher:
publish.yml on visserle/AnkiOps
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ankiops-0.6.0.tar.gz -
Subject digest:
322814665be4133159a85b097748e6797894086a5eb224d60b6c0003ae3f6331 - Sigstore transparency entry: 1739278082
- Sigstore integration time:
-
Permalink:
visserle/AnkiOps@cb7d1fa2736579dcb270db8422fa539aeb76952c -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/visserle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cb7d1fa2736579dcb270db8422fa539aeb76952c -
Trigger Event:
release
-
Statement type:
File details
Details for the file ankiops-0.6.0-py3-none-any.whl.
File metadata
- Download URL: ankiops-0.6.0-py3-none-any.whl
- Upload date:
- Size: 148.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c143655150d6b656e9874a3e2dd9af8fd9e3463800a25b9b9a3087b082901e21
|
|
| MD5 |
1349ec3cbd85288c77f6200e0e48b8bf
|
|
| BLAKE2b-256 |
0b741e5a098fc2dcdbc504ac353474be37e2ad97b03f092b9eac500f544ca95d
|
Provenance
The following attestation bundles were made for ankiops-0.6.0-py3-none-any.whl:
Publisher:
publish.yml on visserle/AnkiOps
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ankiops-0.6.0-py3-none-any.whl -
Subject digest:
c143655150d6b656e9874a3e2dd9af8fd9e3463800a25b9b9a3087b082901e21 - Sigstore transparency entry: 1739278102
- Sigstore integration time:
-
Permalink:
visserle/AnkiOps@cb7d1fa2736579dcb270db8422fa539aeb76952c -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/visserle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cb7d1fa2736579dcb270db8422fa539aeb76952c -
Trigger Event:
release
-
Statement type: