deckops 2.1.1
Anki decks ↔ Markdown files, in perfect sync
License: MIT
Requires Python: >=3.10

# DeckOps

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![PyPI version](https://badge.fury.io/py/deckops.svg)](https://badge.fury.io/py/deckops) 

**Anki decks ↔ Markdown files, in perfect sync**

Editing flashcards in Anki's UI is tedious when you could be using your favorite text editor, AI tools, and Git. Currently available Markdown → Anki tools only go one way, where edits in Anki don't sync back. 

**DeckOps** is a bidirectional Anki ↔ Markdown bridge. Each deck is a Markdown file. Work in either Anki or your text editor, and let changes flow both ways. This brings AI assistance, batch editing, and version control to your flashcards.

## Features

- Fully round-trip, bidirectional sync that handles note creations, deletions, movements, and conflicts
- High-performance processing: handles thousands of cards across hundreds of decks in seconds
- Markdown rendering with nearly all features (including syntax-highlighted code blocks, supported on desktop and mobile)
- Thoroughly tested, bidirectional conversion between Markdown and Anki-compatible HTML
- Support for Basic (Q&A), Cloze, Single & Multiple Choice note types
- Embed images via VS Code where they are directly copied into your Anki media folder (automatically set up)
- Built-in Git integration with autocommit for tracking all changes
- Package/unpackage entire collections to JSON format for backup, sharing, or automated AI processing
- Simple CLI interface: after initialization, only two commands are needed for daily use

> [!NOTE]
> DeckOps only syncs the `DeckOpsQA`, `DeckOpsCloze`, and `DeckOpsChoice` note types.


## Getting Started


1. **Install DeckOps via [pipx](https://github.com/pypa/pipx)**: Pipx will make DeckOps globally available in your terminal.
```bash
pipx install deckops
```
2. **Initialize DeckOps**: Make sure that Anki is running, with the [AnkiConnect add-on](https://ankiweb.net/shared/info/2055492159) enabled. Initialize DeckOps in any empty directory of your choosing. This is where your text-based decks will live. The additional tutorial flag creates a sample Markdown deck.
```bash
deckops init --tutorial
```
3. **Execute DeckOps**: Import the tutorial deck into Anki using:
```bash
deckops ma # markdown to anki (import)
```
4. **Keep everything in sync**: When editing your Markdown files, sync Markdown → Anki (and vice versa), as each sync makes one side match the other. After reviewing and editing your cards in Anki, you can sync Anki → Markdown using the following command:
```bash
deckops am # anki to markdown (export)
```

## FAQ

### How is this different from other Markdown or Obsidian tools?

Available tools are one-way importers: you write in Markdown or Obsidian and push to Anki, but edits in Anki don't sync back. DeckOps is bidirectional: you can edit in either Anki or Markdown and sync in both directions. Additionally, DeckOps uses a one-file-per-deck structure, making your collection easier to navigate and manage than approaches that use one file per card.

### Is it safe to use?

Yes, DeckOps will never modify notes with non-DeckOps note types. Your existing collection won't be affected and you can safely mix managed and unmanaged notes in the same deck. Further, DeckOps only syncs if the activated profiles matches the one it was initialized with. When orphaned DeckOps notes are detected, you will be prompted to confirm their deletion. Concerning your Markdown files, DeckOps 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 DeckOps folder. For the first import, the file name will act as the deck name. Subdecks are supported via two underscores `__` (Anki's `::` is not supported in the file system). Start by writing your notes in Markdown. For each note, you can decide whether to use the QA or cloze format. Notes must be separated by a new line, three dashes `---`, and another new line. You can add new notes anywhere in an existing file.

```markdown
Q: Question text here
A: Answer text here
E: Extra information (optional)
M: Content behind a "more" button (optional)

---

T: Text with {{c1::multiple}} {{c2::cloze deletions}}.
E: ![image with set width](im.png){width=700}

---

Q: What is this?
C1: A multiple choice note
C2: with
C3: automatically randomized answers.
A: 1,3

---

And so on…
```

### Which characters or symbols cannot be used?

Since notes are separated by horizontal lines (`---`), they cannot be used within the content fields of your notes. This includes all special Markdown characters that render these lines (`***`, `___`), and `<hr>`.

### How does it work?

On first import, DeckOps assigns IDs from Anki to each deck and note for tracking. They are represented by a single-line HTML tag (e.g., `<!-- note_id: 1770487991522 -->`) above a note in the Markdown. With the IDs in place, we can track what is new, changed, moved between decks, or deleted, and DeckOps will sync accordingly. Content is automatically converted between Anki's HTML format and Markdown during sync operations. Note that one DeckOps folder represents an entire Anki profile.

### What is the recommended workflow?

We recommend using VS Code. It has excellent AI integration, a great [add-on](https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced) for Markdown previews, and supports image pasting (which will be saved in your Anki media folder by default).

### How can I share my DeckOps collection?

Use `deckops package --no-ids` to export your local DeckOps collection to a clean JSON package file without profile-specific IDs. Add `--include-media` to bundle media files from the Anki media folder into a ZIP archive. Recipients can import either format with `deckops unpackage <package-file>`, which creates a new local DeckOps directory on their machine and imports media to their Anki folder with smart conflict resolution. Alternatively, you could share your collection using the native Anki export (`.apkg`), or by sharing your plain Markdown files along with the `media/DeckOpsMedia` folder. Make sure to remove all ID tags from your Markdown files first, as they are profile-specific.

### How can I migrate my existing notes into DeckOps?

While migration is doable, it can be tricky. The process requires:

1. **Converting note types**: Your existing notes must be converted to DeckOps note types (`DeckOpsQA` or `DeckOpsCloze`). This must be done manually in Anki or by adapting the DeckOps code.
2. **Exporting to Markdown**: Once converted, use `deckops am` to export your notes from Anki to Markdown.
3. **Formatting adjustments**: In the first re-import, some formatting may change because the original HTML from Anki may not follow the CommonMark standard.

If your existing note format doesn't map cleanly to the DeckOps format (e.g., notes with additional or custom fields), you'll need to adapt the code to your specific needs.

### How can I develop DeckOps locally?

Fork this repository and initialize the tutorial in your root folder (make sure Anki is running). This will create a folder called `collection` with the sample Markdown in it. Paths will adapt automatically to the development environment. You can run DeckOps locally using the main script.

```bash
git clone https://github.com/visserle/deckops.git
cd deckops
uv sync
uv run python -m main init --tutorial
uv run python -m main ma
```

### What commands and flags are available in the CLI?

**Global:**
- `--debug` - Enable debug logging
- `--help` - Show help message

**`init`:**
- `--no-auto-commit` - Disable automatic git commits
- `--tutorial` - Create tutorial markdown file

**`anki-to-markdown` / `am`:**
- `--deck`, `-d` - Export single deck by name
- `--keep-orphans` - Keep deck files/notes that no longer exist in Anki
- `--no-auto-commit`, `-n` - Skip automatic git commit

**`markdown-to-anki` / `ma`:**
- `--file`, `-f` - Import single file
- `--only-add-new` - Only add new notes, skip existing
- `--no-auto-commit`, `-n` - Skip automatic git commit

**`package`:**
- `--output`, `-o` - Output package file path (default: `<collection-name>.json`)
- `--no-ids` - Exclude note_id and deck_id from package (useful for templates/sharing)
- `--include-media` - Bundle media files into a ZIP archive (creates .zip instead of .json)

**`unpackage`:**
- `PACKAGE` - Package file to import: .json or .zip (required)
- `--directory`, `-d` - Local collection directory to create/update (default: use package filename)
- `--overwrite` - Overwrite existing markdown files (media uses smart conflict resolution)

---

[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/visserle)