## What people say
> What's next get AI to answer your flashcards for you?
> — grei_earl (Reddit)
> I love this. The README is extremely detailed and clear, and using
> AnkiConnect to edit decks in-place avoids the usual apkg headaches.
> — rahimnathwani (Hacker News)
> This is cool!
> — Hsaeedx (Reddit)
## Example workflows
- **Bulk-verify translations** – End-to-end pipeline for cleaning large decks.
[Read more](#example-use-case-fixing-1000-japanese-translations)
- **Add a Key Vocabulary field** – Create a per-note field highlighting 1–3 key
words with readings, meanings, and HTML context.
[Read more](#example-use-case-adding-a-key-vocabulary-field)
- **Generate new cards** – Interactively create multiple contextual flashcards
for a vocabulary word or concept from a single command.
[Read more](#example-use-case-generating-new-vocabulary-cards)
- **Add TTS audio** – Bulk-fill audio fields for existing notes or synthesize
audio for newly generated cards. [Read more](#anki-llm-tts)
- **AI-assisted card template editing** – Pull note type HTML + CSS to local
files so coding agents (Claude Code, Cursor, etc.) can redesign card
layouts, then push changes back to Anki.
[Read more](#anki-llm-note-type)
- **Scriptable collection access** – Query AnkiConnect directly from the CLI or
AI agents. [Command reference](#anki-llm-query-action-params)
## Why?
Hand-editing a large Anki collection is slow and error-prone. Verifying
translations, adding grammar notes, generating contextual examples: doing it
card by card is impractical at any real size.
`anki-llm` provides a bridge between your Anki collection and modern AI models.
**Batch processing**
- **File-based**: _Export_ deck to file, _process_ with LLM, _import_ results
back to Anki.
- **Direct**: Process and update notes in-place.
**Card generation**
Generate multiple contextual flashcard examples for a term, review
interactively, and add selected cards to your deck.
## Features
- **Batch processing workflows**: File-based (with resume) or direct-to-Anki
(one command).
- **Export** Anki decks to clean CSV or YAML files.
- **Batch process** note fields using any OpenAI-compatible LLM (OpenAI, Gemini,
OpenRouter, Ollama, and more).
- **Custom prompts**: Use flexible template files to define exactly how the LLM
should process your cards.
- **Concurrent processing**: Make multiple parallel API requests to speed up
large jobs.
- **Resilient**: Automatically retries failed requests and saves progress
incrementally (file mode).
- **Automatic resume**: Pick up where you left off if processing is interrupted
(file mode).
- **Copy mode**: Alternatively, generate cards without API keys by pasting LLM
responses from browser interfaces (ChatGPT, Claude, etc.).
- **TTS audio**: Generate text-to-speech audio for notes with `anki-llm tts`
(bulk-fill existing decks) or `anki-llm generate` (auto-finalize audio for
newly generated cards at import time, with an in-TUI preview hotkey).
## Installation
### Quick install
```sh
curl -fsSL https://raw.githubusercontent.com/raine/anki-llm/main/scripts/install.sh | bash
```
### Homebrew (macOS/Linux)
```sh
brew install raine/anki-llm/anki-llm
```
### Cargo
```sh
cargo install anki-llm
```
## Requirements
- Anki Desktop with the
[AnkiConnect](https://ankiweb.net/shared/info/2055492159) add-on installed
([Why?](#why-ankiconnect)). Must be running for any command that talks to your
collection; `process-file` works while Anki is closed.
## LLM Configuration
`anki-llm` works with any LLM that exposes an OpenAI-compatible chat completions
API. This includes OpenAI, Google Gemini, xAI, OpenRouter, Ollama, and many other
providers.
### Quick start: OpenAI, Gemini, DeepSeek, or Grok
Set the appropriate environment variable and you're ready to go:
```bash
# OpenAI
export OPENAI_API_KEY="your-api-key-here"
# Google Gemini
export GEMINI_API_KEY="your-api-key-here"
# DeepSeek
export DEEPSEEK_API_KEY="your-api-key-here"
# xAI / Grok
export XAI_API_KEY="your-api-key-here"
```
Get your API key from [OpenAI](https://platform.openai.com/api-keys),
[Google AI Studio](https://aistudio.google.com/api-keys),
[DeepSeek](https://platform.deepseek.com/api_keys), or
[xAI](https://console.x.ai/team/default/api-keys).
OpenAI, Gemini, DeepSeek, and Grok models are auto-detected from the model name
prefix and work with zero additional configuration.
### Using OpenRouter
[OpenRouter](https://openrouter.ai) provides access to hundreds of models
through a single API key:
```bash
export ANKI_LLM_API_KEY="your-openrouter-key"
anki-llm generate "今日" \
--api-base-url https://openrouter.ai/api/v1 \
--model anthropic/claude-sonnet-4
```
Or configure it persistently:
```bash
anki-llm config set api_base_url https://openrouter.ai/api/v1
anki-llm config set model anthropic/claude-sonnet-4
export ANKI_LLM_API_KEY="your-openrouter-key"
```
### Using Ollama or local servers
For local inference servers (Ollama, llama.cpp, vLLM, etc.), point to your
server's URL. No API key is needed:
```bash
anki-llm generate "今日" \
--api-base-url http://localhost:11434/v1 \
--model llama3
```
### Any OpenAI-compatible API
Any service that exposes the OpenAI `/v1/chat/completions` endpoint works
(Together, Fireworks, Groq, etc.):
```bash
anki-llm process-file input.yaml -o output.yaml -p prompt.md \
--api-base-url https://api.together.xyz/v1 \
--api-key your-key \
--model meta-llama/Llama-3-70b-chat-hf
```
### Provider configuration options
| Setting | CLI flag | Environment variable | Config key |
| ----------------------- | ---------------- | ----------------------- | ------------------------- |
| API base URL | `--api-base-url` | `ANKI_LLM_API_BASE_URL` | `api_base_url` |
| API key | `--api-key` | `ANKI_LLM_API_KEY` | - |
| Model | `--model` / `-m` | - | `model` |
| Gemini thinking | - | - | `gemini_thinking_enabled` |
**Precedence:** CLI flag > environment variable > config file > auto-detect.
For built-in providers (OpenAI, Gemini, DeepSeek, xAI), the provider-specific
environment variables (`OPENAI_API_KEY`, `GEMINI_API_KEY`, `DEEPSEEK_API_KEY`,
`XAI_API_KEY`) are used as a fallback when `ANKI_LLM_API_KEY` is not set.
### Known models with pricing
Cost estimates are displayed for known models. Any model name is accepted; cost
display is simply skipped for models without pricing data.
Pricing table
| Model | Input | Output | |
| ------------------------------- | ------- | -------- | -------------------------------------------------------------------------------- |
| **OpenAI models** |
| `gpt-4.1` | $2.00/M | $8.00/M | [🔗](https://platform.openai.com/docs/models/gpt-4.1) |
| `gpt-4.1-mini` | $0.40/M | $1.60/M | [🔗](https://platform.openai.com/docs/models/gpt-4.1-mini) |
| `gpt-4.1-nano` | $0.10/M | $0.40/M | [🔗](https://platform.openai.com/docs/models/gpt-4.1-nano) |
| `gpt-4o` | $2.50/M | $10.00/M | [🔗](https://platform.openai.com/docs/models/gpt-4o) |
| `gpt-4o-mini` | $0.15/M | $0.60/M | [🔗](https://platform.openai.com/docs/models/gpt-4o-mini) |
| `gpt-5` | $1.25/M | $10.00/M | [🔗](https://platform.openai.com/docs/models/gpt-5) |
| `gpt-5-mini` | $0.25/M | $2.00/M | [🔗](https://platform.openai.com/docs/models/gpt-5-mini) |
| `gpt-5-nano` | $0.05/M | $0.40/M | [🔗](https://platform.openai.com/docs/models/gpt-5-nano) |
| `gpt-5.1` | $1.25/M | $10.00/M | [🔗](https://platform.openai.com/docs/models/gpt-5.1) |
| `gpt-5.2` | $1.75/M | $14.00/M | [🔗](https://platform.openai.com/docs/models/gpt-5.2) |
| `gpt-5.3` | $1.75/M | $14.00/M | [🔗](https://platform.openai.com/docs/models/gpt-5.3) |
| `gpt-5.4` | $2.50/M | $15.00/M | [🔗](https://platform.openai.com/docs/models/gpt-5.4) |
| `gpt-5.4-mini` | $0.75/M | $4.50/M | [🔗](https://platform.openai.com/docs/models/gpt-5.4-mini) |
| `gpt-5.4-nano` | $0.20/M | $1.25/M | [🔗](https://platform.openai.com/docs/models/gpt-5.4-nano) |
| **Google Gemini models** |
| `gemini-2.0-flash` | $0.10/M | $0.40/M | [🔗](https://ai.google.dev/gemini-api/docs/models#gemini-2.0-flash) |
| `gemini-2.5-flash` | $0.30/M | $2.50/M | [🔗](https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash) |
| `gemini-2.5-flash-lite` | $0.10/M | $0.40/M | [🔗](https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-lite) |
| `gemini-2.5-pro` | $1.25/M | $10.00/M | [🔗](https://ai.google.dev/gemini-api/docs/models#gemini-2.5-pro) |
| `gemini-3-flash-preview` | $0.50/M | $3.00/M | [🔗](https://ai.google.dev/gemini-api/docs/models#gemini-3-flash-preview) |
| `gemini-3.1-flash-lite-preview` | $0.25/M | $1.50/M | [🔗](https://ai.google.dev/gemini-api/docs/models#gemini-3.1-flash-lite-preview) |
| `gemini-3.1-pro-preview` | $2.00/M | $12.00/M | [🔗](https://ai.google.dev/gemini-api/docs/models#gemini-3.1-pro-preview) |
| **DeepSeek models** |
| `deepseek-v4-flash` | $0.14/M | $0.28/M | [🔗](https://api-docs.deepseek.com/quick_start/pricing) |
| `deepseek-v4-pro` | $1.74/M | $3.48/M | [🔗](https://api-docs.deepseek.com/quick_start/pricing) |
| **xAI models** |
| `grok-4.3` | $1.25/M | $2.50/M | [🔗](https://docs.x.ai/docs/models) |
Pricing is per million tokens (M). Check the latest prices on the provider's
website to be sure.
## Configuration
Use `anki-llm config` to store defaults (for example, the model and API base
URL) so you don't have to repeat flags on every command.
```bash
# Set or override defaults
anki-llm config set model gpt-4o-mini
anki-llm config set api_base_url https://openrouter.ai/api/v1
# WSL or remote Anki: point at a non-default AnkiConnect host
anki-llm config set anki_connect_url http://192.168.1.100:8765
# Disable Gemini thinking in the generate TUI
anki-llm config set gemini_thinking_enabled false
```
Config file lives at `~/.config/anki-llm/config.json`.
### Prompts directory
Prompt files live in a **workspace**: any directory with a `prompts/` folder.
When you run `anki-llm` from a workspace, its prompts are used automatically,
so commands like `anki-llm generate` work without the `-p` flag.
```bash
# Create a workspace and a starter prompt
anki-llm workspace init
anki-llm generate-init # saves to ./prompts/
# Generate cards (no -p needed)
anki-llm generate "今日"
```
If you have **one prompt**, it's used automatically. If you have **multiple
prompts**, an interactive picker is shown where you can select which one to use.
The last-used prompt is remembered and pre-selected next time.
To use a workspace from outside it (so `anki-llm generate`, `note-type`, etc.
work from any directory), set it as the default workspace:
```bash
anki-llm config set default_workspace ~/anki
```
This single setting provides the workspace's `prompts/`, `note-types/`, and
`anki-llm.yaml` (default model) as fallbacks whenever you run anki-llm outside
a workspace.
### Workspaces (recommended for version control)
A workspace is just a directory that contains a `prompts/` folder (and
optionally an `anki-llm.yaml` settings file). When you run anki-llm from a
workspace, its `prompts/` directory is used automatically.
```bash
# Create a workspace in the current directory
anki-llm workspace init
# Or just create the folder yourself
mkdir prompts
# Check if the current directory is a workspace
anki-llm workspace info
```
`anki-llm.yaml` is optional; use it for per-directory settings like a default
model:
```yaml
default_model: gemini-2.5-flash
```
This takes precedence over the config file model but yields to `--model` on the
CLI.
Workspaces are especially useful if you want to keep prompts in git alongside
your deck data.
Prompt files can include optional `title` and `description` fields in their
frontmatter for a better picker experience:
```yaml
---
title: Japanese Vocabulary
description: Contextual sentence cards with readings
deck: Japanese::Vocabulary
note_type: Japanese (recognition)
field_map:
en: English
jp: Japanese
---
```
---
## Commands reference
- [`export`](#anki-llm-export) - Export deck to file
- [`import`](#anki-llm-import-input) - Import data to deck
- [`process-file`](#anki-llm-process-file-input) - Process notes from file with
AI
- [`process-deck`](#anki-llm-process-deck) - Process notes from deck with AI
- [`history`](#anki-llm-history) - List past process-deck runs
- [`rollback`](#anki-llm-rollback-run-id) - Undo a process-deck run
- [`generate-init`](#anki-llm-generate-init-output) - Create prompt template for
generate
- [`generate`](#anki-llm-generate-term) - Generate new cards for a term
- [`tts`](#anki-llm-tts) - Generate TTS audio for notes and upload to Anki
- [`tts-voices`](#anki-llm-tts-voices) - Browse and audition TTS voices
- [`query`](#anki-llm-query-action-params) - Query AnkiConnect API
### `anki-llm export`
Exports notes from Anki. Select notes by deck name or by an Anki search query.
- ``: The name of the Anki deck to export.
- `-q, --query`: Anki search query to select notes (alternative to deck name).
One of `` or `--query` is required (mutually exclusive).
**Options:**
- `-o, --output`: Output file path. When using a deck name, this is optional; a
filename is auto-generated from the deck name (e.g., `"My Deck"` →
`my-deck.yaml`). When using `--query`, an output path is required.
- `-n, --note-type`: Filter by note type (required if results contain multiple
note types).
**Examples:**
```bash
# Export a deck (auto-generate filename)
anki-llm export "Japanese Core 1k"
# → japanese-core-1k.yaml
# Export a deck to CSV
anki-llm export "Japanese Core 1k" -o japanese.csv
# Export only cards missing an audio field
anki-llm export --query "deck:Japanese -field:Audio" -o missing-audio.yaml
# Export leeches across all decks
anki-llm export --query "tag:leech" -o leeches.yaml
# Export cards failed in the last 7 days
anki-llm export --query "rated:7:1" -o recent-failures.yaml
```
### `anki-llm import `
Imports data from a file into an Anki deck. Existing notes (matched by key
field) are updated, while new entries create new notes.
- ``: Path to the data file to import (CSV or YAML).
**Required options:**
- `-d, --deck`: The name of the target Anki deck.
**Common options:**
- `-n, --note-type`: The Anki note type to use when creating new notes. If not
specified, it will be inferred from existing notes in the deck.
- `-k, --key-field`: Field to use for identifying existing notes. If not
specified, auto-detects using this priority: (1) `noteId` column if present,
(2) first field of the note type, (3) error if neither found.
---
### `anki-llm process-file `
Batch-process notes from a CSV/YAML file using an LLM and user-defined prompts.
This command saves the transformed results to an output file and features
automatic resume, allowing it to safely skip completed notes if interrupted or
re-run. Runs as an interactive TUI in a terminal, or prints a progress bar when
output is piped.
- ``: Input file path (CSV or YAML).
**Required options:**
- `-o, --output`: Output file path (CSV or YAML).
- `-p, --prompt`: Path to the prompt file. The prompt file **must** begin with a
YAML frontmatter block that declares the output field; see
[Prompt file format](#prompt-file-format).
**Common options:**
- `-m, --model`: AI model to use (required unless set via `config set model`).
- `-b, --batch-size`: Number of concurrent API requests (default: `5`).
- `-r, --retries`: Number of retries for failed requests (default: `3`).
- `-d, --dry-run`: Preview the operation without making API calls (recommended
for testing).
- `-P, --preview`: Process a small sample of cards with the LLM and show a
diff-like summary of what would change. Prompts for confirmation before
proceeding with the full run.
- `--preview-count`: Number of cards to process in preview mode (default: `3`).
- `-f, --force`: Re-process all rows, ignoring existing output.
- `--limit`: Limit the number of new rows to process (useful for testing prompts
on a small sample before processing large datasets).
- `--log `: Append raw LLM prompts and responses to a log file at ``
for debugging.
- `--very-verbose`: Also print raw LLM prompts and responses to stderr. Useful
for debugging prompts and understanding model outputs.
**Prompt file format:**
`process-file` and `process-deck` share a single prompt file format. Each prompt
is a text file that begins with a YAML frontmatter block:
```
---
output:
field: Translation # required: Anki field to write
require_result_tag: true # optional, default false
---
You are an expert Japanese-to-English translator.
Translate this sentence: {Japanese}
Existing translation for reference: {English}
Wrap your final answer in tags.
```
- `output.field`: the Anki field name that receives the LLM's response.
- `output.require_result_tag`: when `true`, only the content inside the last
`...` pair in the response is written; without tags, the row
fails. Lets the model "think out loud" before committing to an answer.
The body uses `{field_name}` placeholders referring to raw Anki field names
(case-insensitive). Unknown placeholders cause a per-row error.
See [`examples/`](examples/) for complete prompts and the
[translation walkthrough](#example-use-case-fixing-1000-japanese-translations)
for an end-to-end tutorial.
**Workflow:**
1. Export deck to file: `anki-llm export "My Deck" -o notes.yaml`
2. Process file:
`anki-llm process-file notes.yaml -o output.yaml -p prompt.md -m gpt-4o-mini`
3. Import results: `anki-llm import output.yaml -d "My Deck"`
**Examples:**
```bash
# Process a file
anki-llm process-file notes.yaml -o output.yaml -p prompt.md -m gpt-4o-mini
# Preview the first 10 notes without calling the API
anki-llm process-file notes.yaml -o output.yaml -p prompt.md --limit 10 --dry-run -m gpt-4o-mini
# Preview 3 cards with the LLM, then proceed if satisfied
anki-llm process-file notes.yaml -o output.yaml -p prompt.md --preview -m gpt-4o-mini
# Resume processing after interruption (automatic - just re-run the same command)
anki-llm process-file notes.yaml -o output.yaml -p prompt.md -m gpt-4o-mini
# Force re-process all notes (ignore existing output)
anki-llm process-file notes.yaml -o output.yaml -p prompt.md --force -m gpt-4o-mini
```
Use `process-file` when you want a reviewable staging file, resume support for
large runs, or when Anki isn't running. Use `process-deck` when you want to
update notes directly in-place.
---
### `anki-llm process-deck`
Batch-process notes directly in Anki using an LLM and user-defined prompts,
updating them in-place. No intermediate files needed. Select notes by deck name
or by an Anki search query. Runs as an interactive TUI in a terminal, or prints
a progress bar when output is piped.
- ``: Name of the Anki deck to process.
- `-q, --query`: Anki search query to select notes (alternative to deck name).
One of `` or `--query` is required (mutually exclusive).
**Required options:**
- `-p, --prompt`: Path to the prompt file. Must begin with a YAML frontmatter
block declaring the output field; see
[Prompt file format](#prompt-file-format).
**Common options:**
- `-m, --model`: AI model to use (required unless set via `config set model`).
- `-b, --batch-size`: Number of concurrent API requests (default: `5`).
- `-r, --retries`: Number of retries for failed requests (default: `3`).
- `-d, --dry-run`: Preview the operation without making API calls (recommended
for testing).
- `-P, --preview`: Process a small sample of cards with the LLM and show a
diff-like summary of what would change. Prompts for confirmation before
proceeding with the full run.
- `--preview-count`: Number of cards to process in preview mode (default: `3`).
- `--limit`: Limit the number of notes to process (useful for testing prompts on
a small sample before processing entire deck).
- `-f, --force`: Re-process notes even if the target field already has content.
By default, `process-deck` skips notes where the output field is populated to
avoid overwriting existing data.
- `--log `: Append raw LLM prompts and responses to a log file at ``
for debugging.
- `--very-verbose`: Also print raw LLM prompts and responses to stderr. Useful
for debugging prompts and understanding model outputs.
**Prerequisites:**
- Anki Desktop must be running
- AnkiConnect add-on must be installed
**Examples:**
```bash
# Process a deck directly
anki-llm process-deck "Japanese Core 1k" -p prompt.md
# Preview the first 10 notes without calling the API
anki-llm process-deck "My Deck" -p prompt.md --limit 10 --dry-run
# Preview 3 cards with the LLM, then proceed if satisfied
anki-llm process-deck "My Deck" -p prompt.md --preview
# Rewrite explanations only for cards you keep failing
anki-llm process-deck --query "deck:Japanese prop:lapses>5" -p prompt.md
# Add mnemonics to leeches
anki-llm process-deck --query "tag:leech" -p prompt.md
# Fix cards you got wrong in the last 7 days
anki-llm process-deck --query "rated:7:1" -p prompt.md
# Re-process everything, overwriting existing data
anki-llm process-deck "My Deck" -p prompt.md --force
```
**Undoing a run:**
Every `process-deck` run is automatically snapshotted. The run ID is printed at
the end; pass it to `anki-llm rollback ` to revert all changes. Use
[`anki-llm history`](#anki-llm-history) to list past runs.
`process-deck` does not support resume; use `process-file` for large runs where
interruptions are likely. Failed notes are logged to `-errors.jsonl`
in the working directory.
---
### `anki-llm history`
Lists past `process-deck` runs that have snapshot data available.
```
$ anki-llm history
Run ID Source Model Notes Status
──────────────────────────────────────────────────────────────────────────────────────
20260411T153000_123Z Japanese Core gpt-5-mini 142 ok
20260410T091500_456Z query: tag:leech gpt-5-mini 50 rolled back
```
Snapshots are stored in `~/.local/state/anki-llm/snapshots/`.
---
### `anki-llm rollback `
Restores notes to their state before a `process-deck` run. The run ID is shown
after each `process-deck` completes and can be found via `anki-llm history`.
```bash
anki-llm rollback 20260411T153000_123Z
```
Before restoring, the command checks each note for conflicts: if a field was
manually edited in Anki after the run, that note is skipped. Use `--force` to
override conflict detection.
**Options:**
- `--force`: Rollback even if notes were modified after the run.
- `-d, --dry-run`: Preview what would be restored without making changes.
**Examples:**
```bash
# Preview what would be rolled back
anki-llm rollback 20260411T153000_123Z --dry-run
# Force rollback despite conflicts
anki-llm rollback 20260411T153000_123Z --force
```
---
### `anki-llm generate-init [output]`
Interactively creates a prompt template file for the `generate` command. The
wizard guides you through selecting a deck and note type, then uses an LLM to
analyze your existing cards and generate a tailored prompt that matches your
deck's style and formatting. This is the recommended way to get started with
card generation.
- `[output]`: Optional output file path. If omitted, saves to your workspace's
`prompts/-prompt.md` (or the default workspace's `prompts/`).
**Common options:**
- `-m, --model`: The LLM model to use for the smart prompt generation step.
- `-t, --temperature`: Temperature for LLM generation (0.0-2.0, default varies
by model). Lower values produce more consistent output.
- `--copy`: Copy the LLM prompt to clipboard and wait for manual response
pasting. Useful when you don't have API access and want to use a browser LLM
interface like ChatGPT.
> [!TIP]
> Using a more capable reasoning model like `gemini-3.1-pro-preview` for the
> `generate-init` step can produce higher-quality prompt templates that better
> capture the nuances and style of your existing cards.
**Workflow:**
1. Run the wizard: `anki-llm generate-init`
2. Follow the interactive steps to select a deck and note type.
3. A prompt file is saved to your workspace's prompts directory (e.g.,
`./prompts/vocabulary-prompt.md`).
4. Review and customize the generated prompt file.
5. Use it with the `generate` command: `anki-llm generate "term"` (the prompt is
found automatically).
---
### `anki-llm generate `
Generates multiple new Anki card examples for a given term, lets you review and
select which ones to keep, and adds them directly to your deck.
The command launches an interactive terminal UI. You can also omit `` to
enter it in the TUI directly, which lets you generate cards for multiple terms
in a single session.
- ``: The word or phrase to generate cards for (must be in quotes if it
contains spaces). Optional; can be entered in the TUI.
**Common options:**
- `-p, --prompt`: Path to the prompt template file. If omitted, auto-resolved
from your [prompts directory](#prompts-directory) (single prompt is used
directly; multiple prompts show a picker).
- `-c, --count`: Number of card examples to generate (default: `3`).
- `-m, --model`: AI model to use (defaults to `gpt-5-mini` or `gemini-2.5-flash`
depending on your API key; can also be set via `config set model`).
- `-d, --dry-run`: Display generated cards without starting the interactive
selection or import process.
- `-r, --retries`: Number of retries for failed requests (default: `3`).
- `-t, --temperature`: LLM temperature, a value between 0 and 2 that controls
creativity (default: `1.0`).
- `--max-tokens`: Set a maximum number of tokens for the LLM response.
- `-o, --output`: Export cards to a file instead of importing to Anki (e.g.,
`cards.yaml`, `cards.csv`).
- `--log `: Append raw LLM prompts and responses to a log file at ``
for debugging.
- `--copy`: Copy the LLM prompt to clipboard and wait for manual response
pasting. Useful when you don't have API access and want to use a browser LLM
interface like ChatGPT.
#### Interactive TUI
The generate command runs in a full-screen terminal UI. Enter a term, review the
generated cards, and confirm which ones to import. Duplicates are flagged
against your existing deck with a field-by-field diff. You can regenerate a card
with feedback, edit any card in your `$EDITOR`, switch models mid-session, or
queue multiple terms for batch processing.
If the prompt declares a `tts:` block and a system audio player is available,
press `p` to preview the focused card's audio in selection and replay imported
audio from the summary; audio for selected cards is finalized automatically at
import time.
When a supported thinking model from Gemini, DeepSeek, or Grok emits raw
reasoning during the primary generation request, the running view shows it live in
a temporary Thinking block above the log. This stream is for display only: it is
cleared when generation finishes and is not written to prompt/response logs.
Gemini thinking can be disabled with
`anki-llm config set gemini_thinking_enabled false`, which uses the normal
non-thinking Gemini request path instead.
Press `?` at any time to see keyboard shortcuts for the current mode. Token
usage and estimated cost are tracked in the sidebar across the session.
#### **Understanding the Prompt File**
The `--prompt` file is a text or markdown file that contains two parts: YAML
frontmatter for configuration and a prompt body with instructions for the LLM.
**Frontmatter (Required)**
The frontmatter is a YAML block at the top of the file enclosed by `---`.
- `deck`: The target Anki deck name.
- `note_type`: The name of the Anki note type (model) to use.
- `field_map`: Maps the keys from the LLM's JSON output to your actual Anki
field names. The LLM will be instructed to generate JSON with the keys on the
left, and `anki-llm` will use them to populate the Anki fields on the right.
- `processing` (optional): Runs LLM processing steps before and/or after card
selection. Supports two step types: `transform` (rewrite fields) and `check`
(quality verification with pass/flag/reject verdicts).
##### Optional: Processing Steps
Asking a single LLM call to generate content, format fields correctly, add
furigana, and verify quality all at once tends to degrade each individual
aspect. Processing steps let you split this work into a pipeline where each step
handles one concern with a focused prompt. The generation prompt can concentrate
on producing natural, diverse content, while separate steps handle mechanical
tasks like furigana annotation or quality checks, optionally using cheaper,
faster models for those steps.
The `processing` config lets you run LLM steps in two phases:
- **`pre_select`**: Runs after generation, before you choose cards. Useful for
fixing field formatting or filtering out bad cards early.
- **`post_select`**: Runs after selection. Useful for quality checks or final
polishing before import.
Each step is either a **transform** (rewrites card fields) or a **check**
(evaluates card quality).
**Transform: single field:**
Use `target` to rewrite one field:
```yaml
processing:
pre_select:
- type: transform
target: read
model: gpt-4o-mini # Optional: use a different model
prompt: |
Segment this sentence with correct bunsetsu spacing and Kanji[reading] annotations.
Sentence: {kanji}
English meaning: {front}
```
**Transform: multiple fields:**
Use `writes` to update several fields in one LLM call:
```yaml
processing:
pre_select:
- type: transform
writes: [read, context]
prompt: |
Given this Japanese sentence: {kanji}
Provide the reading with furigana and a brief context note.
```
**Check: quality verification:**
Check steps evaluate cards and return `pass`, `flag`, or `reject`:
- **pass**: card continues normally
- **flag**: card is kept but shown with a warning (pre-select flags are
informational in the selection UI; post-select flags trigger a review screen)
- **reject**: card is discarded
```yaml
processing:
post_select:
- type: check
prompt: |
Evaluate if the following text sounds natural in Japanese.
Text: {kanji}
```
You don't need to specify the response format; the system automatically
instructs the LLM to return structured JSON with `result` and `reason` fields.
**Key details:**
- All card fields are available as `{placeholders}` in the prompt.
- Steps within a phase run in order. Later steps see results from earlier ones.
- Cards within each step are processed concurrently.
- Transform steps must declare which fields they write (`target` or `writes`).
Check steps must not have `target`/`writes`.
- Each step can specify its own `model`.
- Not supported in `--copy` mode.
**Prompt Body**
The body contains your instructions for the LLM. It must:
1. Include the `{term}` placeholder, which will be replaced by the `` you
provide on the command line.
2. Include the `{count}` placeholder, which will be replaced by the number of
cards requested.
3. Instruct the LLM to return a JSON array of objects, where each object
represents one card and uses the keys defined in `field_map`.
4. Include a "one-shot" example showing the exact JSON array structure and
desired formatting (e.g., HTML for bolding or lists).
5. Encourage the LLM to generate diverse cards that highlight different nuances,
contexts, or usage examples of the term.
**Example Prompt File (`japanese-vocab-prompt.md`)**
````markdown
---
deck: Japanese::Vocabulary
note_type: Japanese (recognition)
field_map:
en: English
jp: Japanese
context: Context
---
You are an expert assistant who creates {count} distinct Anki flashcards for a
Japanese vocabulary word. The term to create cards for is: **{term}**
IMPORTANT: Your output must be a single, valid JSON array of objects and nothing
else. Each object in the array should represent a unique flashcard. Field values
can be strings or JSON arrays, and arrays are automatically converted into
`
` HTML lists before cards are imported.
Follow the structure shown in this example precisely:
```json
[
{
"en": "How was your day?",
"jp": "今日はどうでしたか?",
"context": "A natural and common way to ask about someone's day politely. You can say 「今日どうだった?」 in casual speech."
}
]
```
Return only a valid JSON array matching this structure. Ensure you generate
{count} varied and high-quality cards that highlight different nuances,
contexts, or usage examples of the term.
````
#### Using `--copy` Mode (Manual LLM Workflow)
The `--copy` flag allows you to generate cards without API keys by manually
copying prompts to a browser-based LLM interface (like ChatGPT, Claude, Gemini,
etc.) and pasting responses back.
**Workflow:**
1. Run the command with `--copy`:
```bash
anki-llm generate "今日" -p prompt.md --copy
```
2. The program automatically copies the LLM prompt to your clipboard.
3. Paste the prompt into your preferred LLM interface (ChatGPT, Claude, etc.).
4. Copy the complete JSON response from the LLM.
5. Paste it into the terminal.
6. Type `END` on a new line and press Enter to submit.
7. The program validates and processes your cards normally.
**Benefits:**
- No API key required
- Use any LLM interface you prefer
- Works with free-tier LLM services
- Full control over the LLM interaction
**Examples:**
```bash
# Generate 3 cards for a term using a prompt file
anki-llm generate "新しい" -p japanese-vocab-prompt.md
# Generate 5 cards and preview them without importing
anki-llm generate "ambiguous" -p english-vocab-prompt.md --count 5 --dry-run
# Use a different model for a specific run
anki-llm generate "maison" -p french-prompt.md -m gemini-2.5-pro
# Generate cards and export to YAML for later review/import
anki-llm generate "今日" -p japanese-vocab-prompt.md -o cards.yaml
# Import the exported cards when ready
anki-llm import cards.yaml --deck "Japanese::Vocabulary"
# Enable logging for debugging
anki-llm generate "新しい" -p prompt.md --log run.log
# Use manual copy-paste mode (no API key required)
anki-llm generate "今日" -p japanese-vocab-prompt.md --copy
# Launch TUI mode (interactive full-screen terminal UI)
anki-llm generate
```
---
### `anki-llm tts`
Generate text-to-speech audio for notes in an Anki deck and upload it to Anki's
media store as `[sound:...]` tags in a target field. Streams notes directly from
AnkiConnect, so there's no intermediate file to manage.
Audio is generated by a pluggable TTS provider (OpenAI, Azure Neural TTS, Google
Cloud Text-to-Speech, Amazon Polly, and Microsoft Edge TTS are supported), cached
on disk, and written to the target field as a `[sound:...]` tag.
For Japanese decks, neural TTS voices routinely mis-read kanji that have
multiple readings (e.g. `日本語` vs `ひのもとのことば`). The fix is to put the
intended reading in the source field next to each kanji cluster using the
convention `漢字[かんじ]`, and `anki-llm tts` routes that reading into the
provider's native pronunciation mechanism:
Each provider gets the furigana routed into its native pronunciation mechanism:
SSML `` tags for Azure, plain-kana substitution for OpenAI, Google, Polly,
and Edge. If you'd rather have the provider read the raw kanji directly, leave
the `[reading]` annotations out; plain text without annotations passes through
unchanged.
Each `[...]` annotation is bound to the immediately preceding run of CJK
characters, so mid-word splits like `転がり込[こ]んだ` and `お父[とう]さん`
parse correctly. How the annotations get into the source field is up to you:
write them by hand, generate them with `anki-llm generate` from an LLM prompt
that emits the format, or paste them from any other tool.
**Quick start:**
```bash
# For every note in the "Japanese" deck with an empty "Audio" field, synthesize
# audio from "Front" and write the [sound:...] reference into "Audio".
# (Notes that already have audio are skipped unless you pass --force.)
anki-llm tts Japanese \
--field Audio \
--text-field Front \
--voice alloy
```
**Using a template instead of a raw field:**
```bash
cat > speak.txt <<'EOF'
{Word}. {ExampleSentence}
EOF
anki-llm tts Japanese \
--field Audio \
--template speak.txt \
--voice nova
```
Templates use the same `{field}` placeholder syntax as `process-deck`.
**Two ways to use it**
`anki-llm tts` has two first-class modes:
1. **Flag mode** (shown in the quick start above): pass voice / target field /
source text / provider on the CLI. Best for one-shot fills, trying TTS for
the first time, or processing decks you don't maintain.
2. **Prompt mode** (`--prompt `): read the deck's TTS settings from a
YAML frontmatter alongside its LLM prompt. Best for decks you maintain in
version control, where the voice and source-text strategy are inherent to the
deck's design.
**Using a prompt YAML**
The TTS settings for a deck (voice, model, target field, source text) are
usually fixed and belong with the rest of the deck's design. They can be
declared in the same YAML frontmatter `anki-llm generate` uses, under a
top-level `tts:` block. **Both** `anki-llm tts --prompt` (for bulk-filling
existing notes) and `anki-llm generate` (for new cards) read the same block;
generate synthesizes + uploads audio for the cards you confirm at import time,
and offers an in-TUI `p` preview hotkey while you're reviewing them. TTS
credentials are read from environment variables and
`~/.config/anki-llm/config.json` (see Provider configuration below);
`anki-llm generate`'s `--api-key` / `--api-base-url` flags are LLM-only and are
never forwarded to the TTS provider, so you can point generate at OpenRouter /
Ollama / a local proxy while still synthesizing audio against OpenAI or Azure.
Example:
```yaml
---
deck: Japanese::Vocab
note_type: VocabCard
field_map:
expression: Expression
reading: Reading
meaning: Meaning
tts:
target: Audio
source:
template: '{expression}'
# or:
# field: expression
voice: alloy
# provider: openai # default
# model: gpt-4o-mini-tts
# format: mp3 # default
# speed: 1.0
---
prompt body for `generate` goes here...
```
**Azure Neural TTS example (Japanese):**
```yaml
---
deck: Japanese::Vocab
note_type: VocabCard
field_map:
expression: Expression
reading: Reading
meaning: Meaning
tts:
target: Audio
source:
field: reading # contains inline furigana like `日本語[にほんご]`
voice: ja-JP-MasaruMultilingualNeural
provider: azure
region: eastus
---
prompt body...
```
When `provider: azure`, `region` is required; `model` and `speed` aren't used.
Credentials never live in the YAML; set `AZURE_TTS_KEY` in the environment
instead (see Provider configuration below).
**Google Cloud TTS example (Japanese):**
```yaml
---
deck: Japanese::Vocab
note_type: VocabCard
field_map:
expression: Expression
reading: Reading
meaning: Meaning
tts:
target: Audio
source:
field: reading
voice: ja-JP-Neural2-B
provider: google
# speed: 1.0 # sent as audioConfig.speakingRate
---
prompt body...
```
Google voice names always follow `--