Skip to content

Releases: mcgrizzz/Tsunagi

Tsunagi 0.7.1

Choose a tag to compare

@github-actions github-actions released this 11 Oct 21:19

Tsunagi 0.7.1 adds to the Tsunagi API without breaking anything. Websites and programs can ask for access and get their own key, with the role you choose. Statistics are there for dashboards, heatmaps and streaks. You can change notes' note type and delete empty cards, with a preview first, and event streams say when Anki's day changes. Two problems after switching profiles are fixed. AnkiConnect tools need no changes. It needs Anki 26.08 or newer.

Changes

  • Event streams say when Anki's day changes. day.changed comes when a new day starts, and when changing "Next day starts at" (in Anki's preferences or by sync) moves the day, even back. Searches like is:due then match other cards though none was changed. It comes with every change stream (types=change, or types=day.changed) and needs the events:changes permission. Tsunagi checks for the new day itself, so it also comes while you're reviewing, when Anki's own day_did_change hook doesn't fire.
  • Change notes' note type with Anki's own conversion. POST /v1/notes:change-note-type takes notes of one note type and the new note type, by name or id. Each new field takes an old field's content, and each new card template an old template's cards with their reviews, as in Anki's Change Note Type window. fields and templates name the maps; Anki's defaults fill the rest. With dry_run: true it answers the maps, the fields whose content would be lost and the cards that would be removed, and changes nothing. One undo step; like editing a note type's fields, it needs a full sync next.
  • Statistics, as Anki's Statistics screen counts them. GET /v1/statistics/today answers today's cards answered, time spent, correct answers, answers by kind (learn, review, relearn, filtered), mature cards, and the current streak of days with reviews. GET /v1/review-days is a list with one row per day with reviews: enough for a heatmap. Like every list it takes where (date>="2026-01-01"), order, limit, cursor, select and include=total (the days studied). On both, search counts only the reviews of the cards an Anki search finds (a deck, a tag…). Both need read:reviews, which Default and Read-only grant.
  • Websites and programs can ask for access, and get their own key. POST /v1/permissions:request needs no key. Anki shows who is asking: a website by its address, or a program by the name it gives, marked as unchecked, with its address when it's another device. It also shows a role to give it, preselected from what the request suggests or Default. Allow adds it to Apps & keys with that role and answers its key, once; the app sends it as X-Api-Key from then on. A website's app keeps its address, and the site gets in only with that key, while the app is on (without it, it's refused, so it can't skip the role you chose). Asking again asks you again, and allowing gives the app a new key. Don't allow is the default, and Don't ask again ignores a website or program from then on. The TypeScript client has requestAccess(). AnkiConnect's requestPermission still gives no key; its prompt now names the role an allowed site gets ("No key, this computer").
  • Delete empty cards, as Anki's Tools → Empty Cards does. POST /v1/cards:delete-empty deletes the cards whose template shows nothing, as one undo step. A note whose cards are all empty keeps its first card, as with Anki's "Keep notes with no valid cards"; keep_notes: false deletes such notes instead (and needs write:notes). dry_run: true answers each note and the cards that would go, and deletes nothing.
  • TypeScript client 0.7.1: requestAccess(), typed raw that reaches every route in Tsunagi's API description, and watches that read again when Anki's day changes. Its changelog has the details.

Deprecated

  • Changing a note's type with PATCH /v1/notes/{id} (note_type_id or note_type_name, with every field resent) still works, and will be removed in 0.8. Use POST /v1/notes:change-note-type, which maps fields and cards for you. The AnkiConnect Shim's updateNoteModel stays.

Fixes

  • A retry after switching profiles writes to the new profile. An Idempotency-Key retried within ten minutes, after a switch to another profile, got the first profile's answer back, and nothing was written: an app was told its note was added when it wasn't. Keys now belong to the profile too. A retry on the same profile, even after reopening it, is still answered from the first attempt.
  • A page cursor from another profile is refused. After a switch to another profile, an old next_cursor continued in the new profile's collection, so a listing could mix two profiles' rows without saying so. It now answers 400 ("This cursor is from another profile's collection"); start over without it. On the same profile, even after reopening it, cursors work as before. Cursors from before 0.7.1 are refused once, as invalid: start the listing over.

Full Changelog: v0.7.0...v0.7.1

Tsunagi 0.7.0

Choose a tag to compare

@github-actions github-actions released this 05 Oct 19:32

Tsunagi 0.7.0 settles how the Tsunagi API names things and reports outcomes, ahead of 1.0: note types and deck presets by the names Anki shows, one name for each field, and a mistake always an HTTP error. Apps using the Tsunagi API need the changes below; AnkiConnect tools need none. Add Cards can be prefilled with files, and the TypeScript client can open windows in Anki and find or create a note in one request. It needs Anki 26.08 or newer.

Breaking changes

  • The Tsunagi API uses Anki's own words: note types and deck presets. What AnkiConnect calls models and deck configs, the Tsunagi API now calls by the names Anki shows. AnkiConnect tools need no changes: the AnkiConnect API keeps AnkiConnect's names.

    Before Now
    /v1/models (and /query, /{id}, /{model_id}/fields…, /{model_id}/templates…) /v1/note-types (/{note_type_id}/fields…, /{note_type_id}/templates…)
    POST /v1/models:find-replace POST /v1/note-types:find-replace
    /v1/deck-configs /v1/deck-presets
    model_id, model_name (note and card rows, note bodies, Add Cards, find-replace, the current card) note_type_id, note_type_name
    modelId, modelName in Tsunagi API bodies note_type_id, note_type_name (or noteTypeId, noteTypeName)
    check_all_models (checkAllModels) in duplicate options check_all_note_types (checkAllNoteTypes)
    A deck's config_id preset_id
    Import options with_deck_configs, merge_notetypes, update_notetypes with_deck_presets, merge_note_types, update_note_types
    Permissions read:models, write:models, read:deck_configs, write:deck_configs read:note_types, write:note_types, read:deck_presets, write:deck_presets
    Event resource models (models.stale) note_types (note_types.stale)

    Roles saved in Tsunagi's settings are updated when the add-on starts, so an app keeps what it was allowed.

  • One name for each thing, in the API and its clients. Fields that used Anki's short column names now say what they are, the names the TypeScript client already showed:

    Where Before Now
    Cards ord, mod, factor, left template_index, modified, ease_factor, steps_left
    Notes, decks, note types mod modified
    A note's fields, a note type's fields and templates ord index
    A note type's template did deck_override_id
    Media mtime modified
    Reviews (rows and POST /v1/reviews) ease, factor, time_ms rating, ease_factor, duration_ms
    POST /v1/cards:answer, POST /v1/gui:answer-card ease rating
    POST /v1/cards:set-ease factor ease_factor
    Creating a field or template ord index
    Event messages origin, client by, app
    cards.answered ease rating
    decks.counts each deck's id deck_id
    order on cards and notes mod modified

    Anki's own names for row fields (ord, mod, factor, left, ease, time, mid) are still accepted in select and where and in rows sent back (POST /v1/reviews), as ivl and nid are; answers always use the names above. POST /v1/cards:set-values still takes Anki's raw columns.

  • A mistake is an HTTP error; ok: false only means "not right now". Each Tsunagi API operation now reports its outcome the same way: something the request names that doesn't exist is a 404, a missing permission a 403, and ok: false is kept for what an app may expect and retry, such as no review in progress.

    • gui:deck-overview and gui:deck-review answer 404 for an unknown deck, not ok: false.
    • gui:add-cards and gui:set-add-note-data answer 404 for an unknown deck or note type, not 400. gui:set-add-note-data answers {ok: false} when Add Cards isn't open, without AnkiConnect's error and code.
    • gui:select-card answers 404 for a card that doesn't exist, and ok: false when the Browser isn't open or doesn't show the card. It answered ok: true without selecting anything.
    • cards:change-deck answers 404 for an unknown deck name, as it already did for an unknown deck id. Changing a note's note type to one that doesn't exist (PATCH /v1/notes/{id}) answers 404, not 400.
    • profiles:load answers 404 for an unknown profile, not loaded: false.
    • POST /v1/reviews answers 404 naming the cards that don't exist, and inserts nothing. It inserted rows for them.
    • A file sent by its path without the local_files permission refuses the whole request with 403 (POST /v1/media, POST /v1/notes, PATCH /v1/notes/{id}). It failed only that file or note.
  • gui:add-cards fills the note as POST /v1/notes takes it. A field the note type doesn't have is a 400; it was dropped without a word.

  • gui:undo says what it undid. It answers {undone: "Add Note"} once the undo is done, or {undone: null} when there was nothing to undo, instead of ok: true before Anki had undone anything. An error now reaches the app instead of a dialog in Anki.

  • A deck or note type is its name or its id, in every request. The GUI actions, export and find-replace took only a name; like note writes, they now take deck_id or deck_name, note_type_id or note_type_name (the id when both are sent):

    • gui:deck-overview, gui:deck-review: {deck_name} or {deck_id}, instead of {name}.
    • collection:export: deck_name or deck_id, instead of deck.
    • gui:add-cards, gui:set-add-note-data, note-types:find-replace: also take the ids. A note that prefills Add Cards names both its deck and its note type; one without a deck answered "Deck None not found" and is now a 422.
  • notes:check takes one note or an array, as POST /v1/notes does, instead of {notes: [...]}. It still answers results, one per note.

  • POST /v1/collection:reload is gone. It did nothing; there is nothing to reload. AnkiConnect's reloadCollection is unchanged.

  • notes:upsert no longer lists audio, picture and video. It refused them anyway; sending them is now the usual 422 for an unknown key. Upload files with POST /v1/media and put their names in the fields.

Changes

  • gui:edit-note answers once the Browser shows the note, with typing going into its first field (Anki's Go → Note). It answered before the note showed, and left the cursor in the search bar.
  • gui:browse answers once the Browser shows the search. It answered before the search had run, so selecting a card straight after could miss it. Anki keeps working while it waits; if the Browser isn't ready in time, the answer is a 503 and the search still runs.
  • Add Cards can be prefilled with files. gui:add-cards takes audio, video and picture as POST /v1/notes does: each is stored when the dialog opens and referenced in its fields, and the answer's files says how each was stored. This is asbplayer's "Open in Anki" with a screenshot.
  • The settings page says "Deck presets" where it said "Deck options", matching the permission's new name.
  • TypeScript client 0.7.0: find-or-create (create(note, { ifDuplicate: "skip" })), openBrowser, openEditor, openAdd, openOverview and openReview, and misuses that say why. It has breaking changes of its own; its changelog says what to change.

Tsunagi 0.6.0

Choose a tag to compare

@github-actions github-actions released this 04 Oct 22:56

Tsunagi 0.6.0 changes the Tsunagi API in ways an app may need to follow: request bodies refuse keys they don't take, health names the caller like the capabilities report, and row fields that misled are renamed or gone. AnkiConnect tools need no changes. The API reference now describes every row field, answer and event message, and there's a TypeScript client built from it, on npm as tsunagi-client. It needs Anki 26.08 or newer.

Breaking changes

  • A key a request body doesn't take is refused. Every Tsunagi API request body answers 422 for a key it doesn't have, naming it in errors ("loc": ["body", "allowDuplicates"]), instead of quietly dropping it. A misspelled option no longer looks like it worked. Fields read from GET can still be sent back as they are, ord included. The AnkiConnect API is unchanged.
  • Creating a deck, note type, field or template takes a documented body. POST /v1/decks, /v1/models, /v1/models/{id}/fields and /v1/models/{id}/templates took an untyped object: a deck ignored a key it didn't take, and a bad note type answered 400 with a raw validation message. Each now has a schema in the API reference, and a bad or unknown key is the usual 422 naming it. A field's size must be 0 to 65535 (Anki's limit); a larger one answered 500. Deck presets and add-on action inputs stay free-form, since their keys are Anki's and the add-on's own.
  • Row fields that misled are fixed or gone. Each was Anki's stored value, whose name or number said something untrue:
    • Decks: new_today, review_today, time_today and learn_today were [day, count] pairs that only count on the day they name, and learn_today is always 0 in current Anki. They are now new_limit_used, review_limit_used and study_ms_today: plain numbers for today, 0 when nothing was studied today. The first two are how much of today's limit is used, so Custom Study's "increase today's limit" makes them smaller, even negative. New: new_limit_today and review_limit_today, the limits set for today only, null otherwise.
    • Cards: left is the learning steps left (Anki's left % 1000); cards last studied with Anki's old schedulers carried a second count above the last three digits. flags is gone: flag is the flag colour, and Anki gives the column's other bits no meaning. set-values still writes the raw column.
    • Note types: did, tags, vers and each field's media are gone. Current Anki no longer writes them; did was a deck from an older Anki or from the collection the note type came from, often one that doesn't exist here. A template's did is unchanged.
    • Add-ons: installed_at is version_time: when the installed version was published, not when it was installed.
  • Health names the caller like the capabilities report. GET /v1/health's caller.app is now caller.name, and both endpoints answer the same caller: name, role, enabled, key, this_computer and host. The capabilities report now also says whether your key was recognized (key: valid, unknown or none).

Changes

  • The capabilities report covers file paths on note writes. POST /v1/notes and PATCH /v1/notes/{id} report the path option, as POST /v1/media already did, so an app can see whether it may send a file by its path on this computer.
  • access.changed tells an app to fetch its capabilities again. It arrives on every event stream when the app's key, role or permissions change (just before the stream closes with auth), and on open streams when add-on actions are enabled or disabled or FSRS is turned on or off. Any app can watch for it alone with GET /v1/events?types=access.changed, which needs no permission.
  • The capabilities report says what the event stream would send. operations["GET /v1/events"].options lists access.changed, sync, cards.answered and each resource, so an app can see that it won't get card answers before it asks for them.
  • Row fields are described in the API reference. Every field a list returns has a description, coded numbers list what they mean (x-values: a card's queue -1 is suspended, a review's ease 0 is none, a reschedule rather than an answer), and fields that can be null say so (x-nullable). Each list's order names its sorts (x-sorts).
  • Every listed sort works on decks, note types and deck presets. Ordering by a field that was null on every row, such as a deck's desired_retention or a note type's original_id, answered 400; it now sorts, with nulls last.
  • The API reference says more about answers. Item failures list their codes (invalid_note, ambiguous, storage_error...), each for the request it answers; notes:check's state, health's collection state and a job's status list their values; fields that can be null say so. search is documented only on the lists that take it (cards, notes, reviews), and Idempotency-Key on every write.
  • The event stream's messages are in the API reference. Each message GET /v1/events can send has a schema, named per type in the operation's x-events (with the resource or option that carries it). Every operation documents its error body (detail, and on a 503 a reason: busy, closed or syncing). Row fields built from other resources, and each list's search, say which resources' changes affect them (x-from).
  • A TypeScript client: npm install tsunagi-client. Typed queries, writes and live updates, generated from the API reference and tested against the server on every change, at the add-on's version. The API reference is now public too, and Build your first integration walks through a first set of requests.

Tsunagi 0.5.1

Choose a tag to compare

@github-actions github-actions released this 03 Oct 18:21

Tsunagi 0.5.1 fixes a security bug in two AnkiConnect actions, and deleting notes through the API now names their deleted cards on the event stream. Apps need no changes. The server now comes back reliably after a restart on Linux and macOS, and restarting or stopping it no longer pauses Anki. It needs Anki 26.08 or newer.

Security

  • Two AnkiConnect actions no longer run SQL from their card IDs. relearnCards and cardsToNotes pasted the card IDs they were given into a database query, as AnkiConnect itself does. An app could put every card into relearning, or read every note's text while allowed only to read cards. Both now refuse anything that isn't a card ID ("cards must be card IDs"). Requests with real card IDs, as numbers or numeric strings, work exactly as before.

Changes

  • Deleting notes through the API names their cards. Event streams get cards.deleted with the deleted notes' card IDs, as they already did for notes deleted in Anki, instead of cards.stale. An app that shows cards no longer has to reload them all.

Fixes

  • Saving the operation timeout no longer restarts the server. Since 0.5.0 the timeout applies as soon as it's saved, but the settings page still restarted the server for it, closing open event streams.
  • On Linux and macOS, the server comes back after a restart. Saving server settings, switching profiles or reloading the add-on shortly after the API was used could leave Tsunagi off with "Port 7777 is busy": the connections the old server had just closed made the port look taken. Windows wasn't affected.
  • Restarting or stopping the server no longer pauses Anki. Saving server settings, or Stop now after turning Tsunagi off in Tools → Add-ons, held Anki for up to 3 seconds when an app had a request in progress, and that request failed with a 500. The request now finishes first. When a profile closes, a request still waiting gets a 503 saying the collection is closing, instead of a 500.
  • Turning Tsunagi back on says so. After stopping the server by turning Tsunagi off in Tools → Add-ons, turning it back on starts the server again as before, and now shows "Tsunagi's server is running again."

Tsunagi 0.5.0

Choose a tag to compare

@github-actions github-actions released this 02 Oct 15:14

Tsunagi 0.5.0 makes the Tsunagi API refuse what it used to accept and quietly drop: a misspelled field, a card column Anki doesn't save, a tag Anki would split. Every write now takes an Idempotency-Key, sorted review pages stay fast at any depth, and no client mistake answers 500 any more. AnkiConnect clients need nothing: the AnkiConnect side is unchanged. It needs Anki 26.08 or newer.

Changes that need a client update

If your app calls the Tsunagi API (/v1/…), check these. Each says what to send instead.

  • A misspelled field is a 400. select or where naming a field the rows don't have (GET /v1/cards?select=ivl) answered {"ivl": null} or matched nothing; it now answers 400 listing the fields. Use the names it lists (interval). Deck presets, which carry Anki's own setting names, aren't checked.
  • select no longer renames fields. The undocumented name:label form (also fields[].name:labels and fields[].(name:n)) is a 400. Rows always use the field names; rename keys in your app if you need to.
  • cards:set-values writes only what Anki saves. The columns are did, due, factor, flags, ivl, lapses, left, nid, odid, odue, ord, queue, reps and type, each an integer. Anything else is a 400: a name that isn't a column, mod, usn and data (which answered 200 and changed nothing), id (which sent the write to another card), and null.
  • A tag with a space is a 422. Anki splits tags on spaces when it saves a note, so "tags": ["a b"] quietly became two tags. Creating, upserting and editing notes, and prefilling Anki's Add window, now refuse it; send ["a", "b"] for two tags, or "a_b" for one.
  • A bad step in cards:batch is a 422, with detail and errors located in operations, like any other bad body; it was a 400 with the validator's raw text. An unknown op and an empty list are 422s too.
  • 422 errors name fields as the API reference does. A location said addTags, cardIds or cid where the reference says add_tags, card_ids and card_id, even when the request had used those names. Match on the field names.
  • Generated clients: the API description now names every field as the answers do (card_ids, model_name, description, not cardIds, modelName, desc), and each list documents its row fields. Regenerate to get the documented names; the camelCase and Anki names are still accepted on input.
  • A next_cursor for sorted reviews from 0.4.0 is a 400. Start the query again.

New

  • Idempotency-Key on every write. Jobs, exports, switching profile, checking the database and actions on Anki's windows (/v1/gui:…) now take the key too. A retried import, sync, FSRS computation or add-on action gets the same job (its result, or the same job_id to poll) instead of a 409 and then a second run; a retried gui:answer-card or gui:undo that finished after a 503 isn't done twice. A job that failed starts again on retry.
  • Deep pages of sorted reviews cost what the first does. GET /v1/reviews?order=… resumed each page by sorting every review again; it now starts after the last row sent. On 283,038 reviews: about 150 ms a page at any depth before; now 30 ms for 50 rows or 80 ms for 1,000. Walking them all took 44 s before and 20.5 s now.
  • Heartbeat interval in ready. The event stream's ready message carries heartbeat_ms (15000), how often the server sends a : ping comment, so a client can tell a quiet stream from a dead one without hard-coding the interval. The events guide now spells out the stream's framing for clients that parse it without a library.

Fixes

  • No more 500s for client mistakes. Each of these now gets the usual answer:
    • An id sent twice to a card action (cards:suspend, bury, set-flag, change-deck and the rest) or to tags:bulk-add/bulk-remove failed; it now counts once.
    • Card id 0 in cards:answer, cards:set-ease or cards:set-memory-state is a missing card, skipped like any other.
    • An unusable path on POST /v1/collection:import or :export (empty, or in a folder that doesn't exist) is a 400 with Anki's message.
    • A value of the wrong type in cards:set-values ("due": "x") is a 400.
  • Review rows refuse numbers beyond 64 bits. POST /v1/reviews stored a too-large card_id, interval or other value as a decimal number and failed on a too-large id; any of them is now a 422 and nothing is inserted.
  • The operation timeout applies when it's saved. op_timeout_seconds is read as it is set, not copied once when the server starts.

Docs

  • version is not an access check. The AnkiConnect page says which actions need a key (all but requestPermission, as in AnkiConnect) and that version answers any app's key; GET /v1/health says which app a key belongs to and its role.

Tsunagi 0.4.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 21:20

Tsunagi 0.4.0

This release changes several Tsunagi API (/v1/…) request and response formats. Apps using that API may need updates. AnkiConnect clients can keep using their existing requests. Tsunagi now also preserves Anki's undo history after several AnkiConnect writes.

Requires Anki 26.08 or newer.

Update your Tsunagi API client

  • A one-field select now returns objects. GET /v1/notes?select=id returns "items": [{"id": 123}, …] instead of "items": [123, …]. Add shape=scalar to keep the old response. shape=auto now returns 422.
  • 422 errors now have a string detail. The individual validation problems are in errors. Route and validation errors across the Tsunagi API now use a string detail.
  • Encode text in where as a JSON string. Escapes are now decoded: name=="Ki\"ku" matches Ki"ku. Previously it compared against the backslash too. Use JSON.stringify or json.dumps when building a value.
  • Invalid PATCH bodies now return 422. PATCH requests for notes, decks, note types, fields and templates validate their bodies before running. For example, "tags": "x" now returns detail and errors instead of a 400 with Python validation text. The API reference also shows each PATCH body's fields.
  • A name already in use now returns 409. This applies when creating or renaming decks, note types, fields, templates and deck presets, ignoring case. Deck and note type clashes previously returned 400; Anki could silently alter other names (Front+, Card 1+, Default+, Basic-93af0) or allow duplicate preset names. You can still change only the case of an item's own name.
  • Tags now return rows. GET /v1/tags returns [{"name": "verb"}, …] instead of ["verb", …]. Use select=name&shape=scalar for the old shape. Replace prefix=verb with where=name^="verb".
  • Media list filters changed. Replace prefix=dog with where=filename^="dog" and suffix=.mp3 with where=filename$=".mp3".
  • Duplicate IDs are requested through include. Replace include_duplicate_ids=true with include=duplicate_ids. POST /v1/notes:check now leaves duplicate_note_ids as null unless you request them. To request both card IDs and duplicate IDs on note creation, use include=cards,duplicate_ids. Unknown include values return 422.
  • Generated clients: the result type for card actions such as POST /v1/cards:suspend is now VerbResult, formerly SchedulingResult. POST /v1/notes:delete uses it too. The JSON has not changed.

New

  • Sort every list with order=. Cards and notes use Anki's Browser sorts, for example order=due or order=note_modified:desc. Other resources sort by a field, for example GET /v1/reviews?order=id:desc. Row field names also work where they match a Browser sort, including order=reps and order=mod.
  • Check how Tsunagi identifies your request with GET /v1/health, even without an open profile. Its caller reports the app or no-key row, role, whether the app is on, and whether the supplied key was valid, unknown or absent.
  • Get matching note IDs when checking duplicates with include=duplicate_ids on POST /v1/notes.
  • Count matches with include=total on any list. It adds total across all pages. Add limit=0 for the count alone: GET /v1/cards?search=is:suspended&include=total&limit=0.
  • Query tags and media like other lists. Both now take select, where, order, distinct_on, include=total, limit (including 0) and cursor. They also have POST /v1/tags/query and POST /v1/media/query.
  • Filter text by its beginning or end with ^= and $= in where, for example GET /v1/notes?where=first_field^="食".
  • Keep one row per value with distinct_on=<field>. It keeps the first row in the requested order. For example, GET /v1/reviews?distinct_on=card_id&order=id:desc&select=card_id,interval returns each card's latest review and interval in one request.
  • Delete several notes in one undo step with POST /v1/notes:delete and note_ids. Missing notes are skipped.
  • See which files note creation stored. A created note with audio, video or picture now reports its files in the same form as POST /v1/media. If Anki renamed a file (renamed: true), update any reference you put in the fields yourself.
  • Identify a failed attachment. An invalid_attachment failure on POST /v1/notes now includes attachment with its kind, position within that kind's list, and filename.
  • Attach files to an existing note with PATCH /v1/notes/{id}. It accepts audio, video and picture as creation does, adding the file and updating the note in one request and one undo step. It won't append a reference the field already contains. For retries after a timeout, send an Idempotency-Key.
  • Retry collection writes with a key. Idempotency-Key now covers writes made in one collection operation, including card answers, PATCH, DELETE and card actions. Reusing the key after a 503 returns the completed write's result rather than running that write again. Without a key, retrying cards:answer can answer the card twice. Jobs (import, export, sync, FSRS computations and add-on actions), profile switching, Check Database and gui: actions are not covered yet. Poll a returned job ID instead of retrying its request.

Fixes

  • AnkiConnect writes preserve undo history. updateNoteFields, updateNote, updateNoteModel, setEaseFactors, setSpecificValueOfCard, replaceTags, replaceTagsInAllNotes and relearnCards now create undo steps instead of clearing Anki's undo history. Inserting review rows still clears it because Anki cannot undo those.
  • Idle connections stay open longer: 75 seconds instead of 5. This avoids dropped connections for clients that poll every few seconds.
  • A duplicate review ID returns 409. POST /v1/reviews previously returned 500 when an ID already existed or appeared twice in the request. It now names the conflicting IDs and inserts nothing.
  • Every app can read capabilities. GET /v1/capabilities no longer requires read:collection, so an app can see which permissions it lacks. Collection and installed add-on features show as disabled when the app cannot read them.
  • The API reference has one status-code table. Under Errors, 400 means the request is wrong, 409 means it conflicts with existing data, and 5xx covers failures from Tsunagi, Anki or AnkiWeb.

Tsunagi 0.3.1

Choose a tag to compare

@github-actions github-actions released this 29 Sep 21:27

Tsunagi 0.3.1 makes moving from AnkiConnect a single step, and tidies up the settings page. It needs Anki 26.08 or newer.

Moving from AnkiConnect

  • Take over from AnkiConnect when Tsunagi first starts. With AnkiConnect installed, the settings window opens with one offer that lists what will change. Take over copies AnkiConnect's port, key and allowed websites, turns AnkiConnect off and runs Tsunagi on its port, so your tools keep working without changes.
  • Later, the same takeover is on the Server page (Take over from AnkiConnect…). It replaces the AnkiConnect page.

Settings

  • One Port field on the Server page instead of separate preferred and fixed ports. If the port is busy, Tsunagi says so and doesn't start.
  • Turning Tsunagi off in Tools → Add-ons now offers to stop its server right away, instead of leaving it running until Anki restarts. Turning it back on starts the server again.

Tsunagi 0.3.0

Choose a tag to compare

@github-actions github-actions released this 29 Sep 21:02

Tsunagi 0.3.0 is a large update: a new settings page, a key and a set of permissions for each app, change events with exact IDs, and several new ways to work with notes. It needs Anki 26.08 or newer.

Your settings start fresh. 0.3.0 doesn't carry over 0.2.0's API key or its two switches (local file uploads, memory-state writes). If you set a key, open Tools → Tsunagi Settings → Apps & keys and add it for your tools again; if you came from AnkiConnect, the AnkiConnect page can import it once more. Until then, tools on this computer keep working without a key, and tools on other devices can't connect.

Settings

  • A settings page inside Anki (Tools → Tsunagi Settings) replaces the old dialog: one page each for the server, apps and keys, requests without a key, websites, roles and AnkiConnect, with Save and per-page Revert.
  • A key for each app, and a role for what it may do: read only, add notes, open Anki's windows, sync, and more. Built-in roles cover the usual cases, and you can make your own. Turn an app off without deleting it.
  • Other devices need a key. Requests from other computers without a key are refused by default.
  • Recent requests shows which app called what, and what failed.
  • The server status shows as a small flowing river that calms when the server stops.

For AnkiConnect tools

  • Existing tools keep working as before; importing AnkiConnect's settings copies its port, key and allowed websites.
  • Answering, forgetting or rescheduling cards now reports the new review rows to apps listening for changes, right away.

For the Tsunagi API

  • Change events with exact IDs, for changes made through the API and in Anki alike, including answers (cards.answered, with the card's new state), new review rows and due-count changes. A category that changed without exact IDs is sent as <resource>.stale (was .changed, without an ids field).
  • Notes:
    • Upsert (POST /v1/notes:upsert): add to the note you already have instead of creating a second one.
    • Files with the note: audio, video and picture on POST /v1/notes, as in AnkiConnect's addNote, stored only if the note is added.
    • Every note with a word, in any note type: where=first_field in [...], using Anki's duplicate index.
    • Duplicate checks take AnkiConnect's scope options (deck, child decks, all note types).
  • Just the fields you need: select=fields[name in ["Expression","Reading"]].
  • Safe retries: send an Idempotency-Key and a retried request is never saved twice.
  • Sync like Anki's Sync button (POST /v1/collection:sync).
  • Health reports the collection's state, and every 503 says why.
  • Removed: the add-on config endpoints (the add-on list stays).

Also

  • Supports Anki 26.08 and 26.09, the current release and the one before it.
  • Keeps macOS from putting Anki to sleep while the server runs.
  • Stops with a clear message when another add-on loads a clashing library.

Benchmarks against AnkiConnect, on a real mining profile: https://github.com/mcgrizzz/Tsunagi/blob/main/docs/benchmarks.md

Full Changelog: v0.2.0...v0.3.0

Tsunagi 0.2.0

Choose a tag to compare

@github-actions github-actions released this 12 Sep 20:26

Full Changelog: v0.1.0...v0.2.0

Tsunagi 0.1.0

Choose a tag to compare

@github-actions github-actions released this 12 Sep 03:18