Skip to main content

anki-cli

Hybrid Anki CLI for humans and agents.

CI PyPI version Python versions Total PyPI downloads License: MIT Ruff

anki-cli interactive REPL showing due card counts, keyboard shortcuts, and command autocomplete

anki-cli supports both:

  • AnkiConnect backend (use a running Anki Desktop instance)
  • Direct SQLite backend (operate directly on collection files)

Alpha software: see the PyPI badge above for the current release.

Install

From PyPI (recommended):

uv tool install anki-cli
anki --version

With optional TUI extras:

uv tool install "anki-cli[tui]"

Quick Start

Inspect environment and backend:

anki status
anki version

status always exits 0 — it reports probe results in data.ok/data.error rather than failing, so it works as a health check on hosts with no Anki.

List core entities:

anki decks
anki notetypes
anki tags

Query cards and notes:

anki cards --query "deck:Default is:due"       # full card details (first 1000; --limit 0 for all)
anki cards:ids --query "deck:Default is:due"   # ids only
anki notes --query "tag:verb"                  # note ids
anki browse --query "tag:verb"                 # interactive TUI

Inspect individual objects:

anki card --id 1234567890
anki note --id 1234567890
anki deck --deck "Default"
anki notetype --notetype "Basic"

Backend Modes

Global backend selection:

anki --backend auto ...
anki --backend ankiconnect ...
anki --backend direct ...

Collection override (direct backend):

anki --backend direct --col "/path/to/collection.anki2" status

Anki profile selection (for multi-profile installs):

# ~/.config/anki-cli/config.toml
[collection]
anki_profile = "Work"

When set, collection discovery picks <Anki2>/<anki_profile>/collection.anki2 instead of the first profile found. A name matching no profile exits with code 3 and lists the available profiles — but only where a local collection is actually required: on the AnkiConnect path the lookup is informational and a miss never fails. --col takes precedence over it entirely.

Backend behavior:

  • auto: detects and chooses best available backend
  • ankiconnect: forwards search queries to findCards and findNotes
  • direct: compiles queries to SQL and executes directly on the collection DB

The old standalone backend was removed: prefer = "standalone" in config.toml and ANKI_CLI_BACKEND=standalone warn and run as auto; --backend standalone is rejected at the CLI with a usage error (exit 2).

Remote AnkiConnect

To connect to AnkiConnect running on a different machine (e.g. via Tailscale or LAN):

# ~/.config/anki-cli/config.toml
[backend]
prefer = "ankiconnect"
ankiconnect_url = "http://192.168.1.100:8765"
allow_non_localhost = true

The remote Anki Desktop must have AnkiConnect configured to accept non-localhost connections. Set "webBindAddress" to a specific interface address (e.g. your Tailscale IP) or "0.0.0.0" for all interfaces. If binding to all interfaces, consider restricting access with firewall rules.

Without allow_non_localhost = true the remote URL is never contacted — the detection probe and all data operations refuse non-localhost hosts.

Search Query Language

Filters follow Anki's own semantics:

  • deck:NAME — the deck and its subdecks (deck:Lang covers Lang::Spanish), including cards visiting a filtered deck; deck:* (all), deck:filtered (cards in filtered decks); supports * glob. Name matching is case-insensitive for ASCII.
  • notetype:NAME (or Anki's spelling note:NAME)
  • tag:NAME — the tag and its children (tag:verb covers verb::irregular); tag:none for untagged notes, tag:* for every note; supports * glob
  • is:new / is:review (by card type, so a suspended new card is still is:new), is:learn, is:due (learning/review cards whose due time has passed; never new cards), is:suspended, is:buried
  • added:N — cards created in the last N scheduling days (added:1 = since the last rollover)
  • flag:N
  • prop:ivl>N, prop:due>N, prop:reps>N, prop:lapses>N (<, <=, =, >=, >)
  • nid:ID, cid:ID
  • bare text and quoted text ("specific text"); escape a literal colon as \:

Any other prefix: (for example card:, rated:, mid:, deck:current, field searches like front:dog) is rejected with INVALID_INPUT rather than silently searched as text.

The --deck option on review, decks and deck follows the same rule: it covers the named deck and its subdecks, so a parent row in decks includes its children's counts.

Logical syntax:

  • implicit AND from whitespace
  • explicit OR
  • unary - and NOT
  • parentheses for grouping

Examples:

anki cards --query "deck:Japanese is:due"
anki cards --query "tag:verb -is:suspended"
anki notes --query "\"specific text\""
anki cards --query "(tag:a OR tag:b) is:new"

Common Commands

Cards

anki cards --query "deck:Default"          # details for every match
anki cards:ids --query "deck:Default"      # ids only
anki card --id 123
anki card:suspend --query "is:due"
anki card:unsuspend --id 123
anki card:move --query "tag:to-move" --deck "Archive"
anki card:flag --query "is:review" --flag 3
anki card:bury --query "deck:Default"        # direct backend only
anki card:unbury --deck "Default"           # direct backend only
anki card:reschedule --query "tag:reset-me" --days 3
anki card:reset --query "tag:relearn"
anki card:revlog --id 123 --limit 20

Notes and tags

anki notes --query "deck:Default"
anki note --id 123
anki note:add --deck "Default" --notetype "Basic" --Front "Q" --Back "A"
anki note:add --deck "Default" --notetype "Basic" --Front "Q" --Back "A2" --allow-duplicate
anki note:edit --id 123 --Front "Updated Q" --Back "Updated A"
anki note:fields --id 123
anki note:delete --id 123 --yes
anki tag --tag "verb"
anki tag:add --query "deck:Default" --tag "important"
anki tag:remove --id 123 --tag "important"
anki tag:rename --from "old" --to "new"

Like Anki's own Add dialog, note:add refuses a note whose first field already exists in the same notetype (exit 1) or is empty. --allow-duplicate lifts the duplicate check; it also works on note:bulk, where refused items otherwise come back as null ids.

Decks and notetypes

anki deck --deck "Default"
anki deck:create --deck "Japanese::Vocab"
anki deck:rename --from "Old" --to "New"
anki deck:delete --deck "Temporary" --yes
anki deck:config --deck "Default"
anki deck:config:set --deck "Default" --new-per-day 20 --reviews-per-day 200

anki notetypes
anki notetype --notetype "Basic"
anki notetype:create --notetype "MyType" --field "Front" --field "Back"
anki notetype:field:add --notetype "Basic" --field "Extra"
anki --yes notetype:field:remove --notetype "Basic" --field "Extra"   # deletes the field from every note
anki notetype:css --notetype "Basic" --set ".card { font-size: 18px; }"

Review

anki review
anki review:next
anki review:show
anki review:preview --id 123
anki review:answer --id 123 --rating good
anki review:undo

Interactive TUI review (requires TUI extras, direct backend):

anki review:start --deck "Japanese"

Output and Exit Codes

Global output formats:

anki --format json ...
anki --format table ...
anki --format md ...
anki --format csv ...
anki --format plain ...

Every command names its target the same way: --deck, --notetype, --tag, --id, --query; --field is repeatable. Older spellings (deck:create --name, notetype:create --name, tag:add --tags, note:fields --fields a,b) still work as aliases. anki commands --format json lists every command, option, exit and error code.

Global options (--format, --backend, --col, --yes, --copy, --no-color) may be written before or after the subcommand: anki --yes note:delete --id 1 and anki note:delete --id 1 --yes are the same command.

Every failure — including a mistyped option or command name — is reported through the same envelope, so --format json always yields parseable stderr. Set ANKI_CLI_DEBUG=1 to append a Python traceback after an INTERNAL_ERROR envelope.

Exit codes:

  • 0: Success
  • 1: Backend operation failed (including unexpected errors)
  • 2: Invalid input, usage error, confirmation required, or unsupported operation
  • 3: No Anki backend found (auto mode could not reach AnkiConnect or find a collection)
  • 4: Entity not found
  • 7: Backend unavailable or collection locked
  • 130: Interrupted (Ctrl-C)

status is the exception: it reports detection failures as {"ok": true, "data": {"ok": false, "error": "..."}} and still exits 0.

AI Agent Integration

This repo ships a SKILL.md at the project root — an agent skill that AI coding agents can consume to operate anki-cli without human guidance. It covers the full command reference, JSON output structure, search query syntax, error codes, and common workflows.

npx skills add ubermenchh/anki-cli

Automatically detects your installed agents and places the skill in the correct directory. Works with Claude Code, Codex, Cursor, Copilot, OpenCode, and others.

Manual

Copy or symlink SKILL.md into your agent's skills directory:

Agent Skill directory
Claude Code ~/.claude/skills/anki-cli/SKILL.md
Codex ~/.codex/skills/anki-cli/SKILL.md
Cursor ~/.cursor/skills/anki-cli/SKILL.md
OpenCode ~/.agents/skills/anki-cli/SKILL.md
Project-level (any) .agents/skills/anki-cli/SKILL.md
mkdir -p ~/.claude/skills/anki-cli
cp SKILL.md ~/.claude/skills/anki-cli/SKILL.md

For other AI coding agents, point them at SKILL.md in the repo root or include it in your agent's context/system prompt.

Safety Notes

  • Use --yes for destructive operations (note:delete, deck:delete, notetype:field:remove).
  • In direct mode, avoid write operations while Anki Desktop is open.
  • If Anki Desktop is running, prefer --backend ankiconnect.
  • review:undo (direct mode only) restores the card's previous state and deletes the revlog row written by the undone answer, matching Anki's own undo.
  • In direct mode, csum for notes with HTML in the first field now matches Anki. Rows written by older anki-cli versions keep their old csum until Tools ▸ Check Database recomputes them; duplicate detection may miss them until then.

Development

uv sync --group dev
uv run ruff check .
uv run ty check
uv run pytest

With optional TUI dependencies:

uv sync --group dev --extra tui
uv run pytest -m tui

Adding a command

Commands live in anki_cli/cli/commands/ and are declared with @anki_command (anki_cli/cli/command.py), which owns the output envelope, the backend session, and BACKEND_UNAVAILABLE. The body returns the success data and declares which other exceptions become which error code:

@anki_command("deck", errors={LookupError: ("ENTITY_NOT_FOUND", 4)})
@click.option("--deck", "deck_name", required=True)
def deck_cmd(cmd: CommandContext, deck_name: str) -> JSONValue:
    name = deck_name.strip()
    if not name:
        raise cmd.invalid("Deck name cannot be empty.")
    with cmd.errors(details={"deck": name}):
        return cmd.backend.get_deck(name)

Anything not mapped falls through to the entry-point mapper, which still renders an envelope. anki commands --format json lists the result; the docs-sync tests check that README and SKILL.md only mention commands and options that exist.

License

MIT. See LICENSE.

Metadata

Release files for anki-cli 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for anki-cli 0.2.0
File Size Uploaded
anki_cli-0.2.0.tar.gz 594.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for anki-cli 0.2.0
File Interpreter ABI Platform
anki_cli-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 780.6 kB

Release files / anki_cli-0.2.0.tar.gz

Download URL anki_cli-0.2.0.tar.gz
Size 594.2 kB
Tags Source
SHA-256 checksum
How to use checksums
ffe9cdd6062e964183460d283728d4b44c449352741b7d03dc99560d122aea13
BLAKE2b-256 checksum
How to use checksums
096762b040b530365b1123c660b40df35806a3d8c7e9fd3d0218010cbe0044a8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / anki_cli-0.2.0-py3-none-any.whl

Download URL anki_cli-0.2.0-py3-none-any.whl
Size 186.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
11cd8481be05f3df39805e30dea21f8aed72d4a396803435f2ae6f700b0b992a
BLAKE2b-256 checksum
How to use checksums
348924d7c5b512cd7987c55d36443949248a739aca2ce14105b2deb88f879603
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page