Repository navigation
anki-cli 0.2.0
The largest release so far: about 40 merged pull requests since 0.1.5. The direct (SQLite) backend now follows Anki's own rules much more closely, several data-loss bugs are fixed, the CLI always answers with a JSON envelope, and the TUI got a layout and performance pass.
Upgrade note: if you use the direct backend, close Anki Desktop before the first command after upgrading. 0.2.0 rewrites hierarchical deck names in your collection once, to the format Anki itself uses (see "Deck names" below). While Anki is running the migration is skipped and retried next time.
Breaking changes
anki cardsno longer opens the TUI. It is now the full-detail JSON lister. The interactive browser isanki browse(bin the REPL;cstill meanscards).searchstays as a hidden alias ofcards. (#66)- Removed CLI surface. The
initcommand,--quiet/--verbose, and the never-constructiblestandalonebackend are gone.--backend standaloneis a usage error (exit 2);prefer = "standalone"orANKI_CLI_BACKEND=standalonewarns and runs asauto. (#52) - Config changes. The
backup.*,review.*anddisplay.day_boundary_hourkeys no longer exist; the nextconfig:setrewritesconfig.tomlwithout them.collection.anki_profileis now live, so multi-profile installs may resolve a different collection than before: pin one with--colorcollection.path. Stale defaults written by older builds are ignored at load. (#52) statuspayload. It now returnsok,backend,collection,reasonandprofile(orok: falsewitherror), always exits 0, and dropsmessage.config:pathdropsbackupsandstandalone_default. (#52)- Auto mode exits 3 when no backend is found. (#52)
- Search semantics now match Anki.
deck:andtag:(and--deckonreview,decks,deck) include child decks and child tags.is:dueno longer matches new cards.added:Nmeans "created since the last rollover".is:new/is:reviewgo by card type. Queries with an unknownprefix:now fail withINVALID_INPUT(exit 2) instead of returning wrong results; escape a literal colon as\:. (#63) - Duplicate and empty notes are refused on the direct backend, like AnkiConnect:
note:addof an empty first field, or of a duplicate without--allow-duplicate, fails withBACKEND_OPERATION_FAILED(exit 1); innote:bulksuch items come back asnullids. Duplicate lookup is scoped by notetype. (#61, #64) - Unopenable collections fail closed. A
--colthat is a directory or not a SQLite file now fails withBACKEND_UNAVAILABLE(exit 7) instead of proceeding. (#50) - AnkiConnect
fieldschanges shape: from a map to a list, withfield_namesas the replacement. Cards and notes also gain keys (notetype_name,id,due_info, ...); nothing else is removed. (#69) - AnkiConnect
bury/unburynow exit 7 (BACKEND_UNAVAILABLE, "Use --backend direct") with no request sent. They never worked over AnkiConnect; they used to exit 1. (#77) review:undoreportsrevlog_deletedinstead ofrevlog_id, andreview:answergainsrevlog_id(direct backend). (#49)- Detection errors now carry the real
meta.commandinstead of"bootstrap", anddetails = {"backend": "none", "requested_backend": ...}. (#76) cards:idsalso returnstotal.
Deck names
Hierarchical deck names are now stored the way Anki stores them (\x1f between components) and shown and typed with ::. Existing collections are migrated once, before the first connection. The migration skips while Anki is running, leaves rows with empty components or an existing native twin alone (logged), and marks changed rows for sync. Known limits: a new name typed with ::: is read as :: plus :, and deck: search cannot resolve names containing :::. (#91)
Data safety (direct backend)
- Deleting a filtered deck no longer hard-deletes the cards visiting it; they return to their home deck. Deleting a normal deck also removes cards on loan to a filtered deck. Deck 1 cannot be deleted. (#40)
- Removing a notetype field now rewrites every note of that notetype (field slots,
sfld,csum, sort field, requirements). Adding a field appends an empty slot to every note. (#40, #47) col.modis bumped on every write, andcol.scmon schema changes, so Anki Desktop actually sees and syncs CLI edits. Schema-changing results reportfull_sync_requiredand a warning. (#47)- The write guard fails closed. A
?,#or%in the collection path no longer disables the lock probe, and an inconclusive probe refuses to write. (#50) - Collections older than schema 18 are refused with an upgrade hint instead of a mid-command
no such tablecrash. (#54) - Deck subtree matching is literal and case-folded like Anki. Before,
_and%in a deck name acted as wildcards: deletingA_Bcould also deleteAXB::childand its cards. (#74) - Filtered-deck
odid/odueare honoured when answering, resetting, rescheduling and moving cards. (#53)
Scheduling fidelity (direct backend)
- The scheduling day is computed like Anki: rollover hour, timezone and creation offset, ported from rslib with its own test vectors. (#55)
- Day-learn cards (queue 3) are treated as day indexes, not epochs, so they no longer count as due immediately or come back from unsuspend due in 1970. Unsuspend and unbury restore the right queue. (#48)
- FSRS: the learning step is recovered correctly, 17- and 19-parameter sets from Anki 24.06 to 24.10 no longer crash, preview and answer use the same fuzz, review-Again is logged correctly, early reviews log as filtered, and the non-Anki
lrtkey is no longer written. (#56) - Empty learning or relearning step lists are respected, as in Anki's preset editor. (#58)
add_notegenerates cards from the templates, as Anki does, instead of one per template, andreqsare recomputed on every notetype change. (#59)- The checksum, emptiness test, duplicate confirmation and
sfldall use markup-stripped text like Anki, so<b>Q</b>andQare duplicates. Notes written by older anki-cli builds keep their old checksum until Tools > Check Database. (#50, #61, #64) - The next-card picker no longer mixes learning units, so a day-learn card cannot always beat an intraday one. (#69)
CLI and agents
- A JSON envelope for every exit. Unknown commands and options, locked collections (
COLLECTION_LOCKED), Ctrl-C (INTERRUPTED, exit 130) and internal bugs (INTERNAL_ERROR, withANKI_CLI_DEBUG=1for the traceback) all produce an envelope. Global options such as--formatand--yeswork after the subcommand. (#65) anki commands --format jsondescribes every command, option, global option, and the exit and error codes. (#66)note:bulkaccepts the documented flat shape and the nested{fields, tags}shape. Canonical flags are--deck,--notetype,--tagsand repeatable--field; the old spellings still work.deckshas one shape across formats.review:answerwarnings go tometa.warnings, so stderr is empty on success. (#66)- Option values containing
=are no longer rewritten askey=valuesugar. (#60) - Docs drift guard: every
anki ...example in README and SKILL.md is checked against the real command registry in CI. (#65, #66) - One card/note/deck/notetype shape on both backends, with an "Object Shapes" section in SKILL.md. (#69)
--offsetforcards/search, and--limit/--offsetforcards:ids. (#92)- Bare
ankiopens the REPL again, config lockouts are cured, andconfig:setrejects values that would break the next run. (#52)
AnkiConnect backend
- Detection is lazy and happens once per run, with a single
versionprobe and the lock probe beforepgrep. A detection failure keeps its exit code (3 or 7). (#76) - Connection reuse with a single retry on a dropped keep-alive socket, and split timeouts (2 s connect, 30 s read). (#77)
- Only documented request shapes are sent; the invented fallbacks are gone.
renameDeckis emulated correctly and no longer ends half-applied. - Far fewer round trips through
multi:get_notetype4 to 1,get_due_counts3 to 1,get_notetypes1 + 2n to 2, andset_card_flagis one request. (#77) - Batch reads:
get_cardsandget_notesare onecardsInfo/notesInforequest. (#92) - Remote AnkiConnect configuration is documented. (#12)
Performance
- The direct backend fetches many cards or notes in one
SELECT ... IN (...)over one connection, instead of opening a connection per card.cardsandsearchuse it. (#92) - The browse TUI loads cards in batches of 200 with progress and cancels a superseded load. The 0.12 s polling timer is replaced by
RowHighlighted, and suspend and delete run in worker threads. (#93) - The review TUI renders each card once. Flipping the answer no longer re-fetches the card, note, notetype, rating previews and due counts. (#93)
TUI
- Layout audit fixes: title bar, toolbar, key hints and error messages no longer overlap; the Due and Ivl columns stay on screen at any width; the review rate row fits at 80x24; keys work at startup and Esc leaves the search box; card text containing
[]is no longer eaten as markup; a crashed TUI no longer exits 0. (#89) - Hint bars are derived from the real key bindings, so no more phantom
edit,markoraddhints. Rating buttons show real newlines. A failed answer no longer leaves a stale undo entry. Undo deletes exactly the revlog row it wrote, andundo.jsonis written atomically. (#49)
Internals, tests and CI
anki_direct.pywas split intodb/modules, the db-to-backends import cycle was broken, and scheduler introspection is on the backend Protocol. One@anki_commanddecorator now owns the envelope, the session andBACKEND_UNAVAILABLE. (#31, #67, #68, #72)- Shared test fixtures build collections with Anki's real schema-18 DDL and protobuf-correct rows. The suite now has about 2,300 tests. (#70, #71)
- CI runs the TUI tests on every push, checks
ruff format, and pinstextual>=7,<8andprompt-toolkit>=3,<4. (#90) - Ruff
Wrules enabled. (#51) - README status badges, and
npx skills adddocumented as an install method. (#15, #94, #95)
Full list: v0.1.5...v0.2.0