deck-sync
Sync deck card lists between local files and Archidekt.
The same sync runs from the admin site’s Sync Decks page and from the MCP sync_decks tool. All three share one engine, so the rules below apply everywhere.
ritual deck-sync pull [decks...] [--sync-printings] [--sync-categories] [-v]ritual deck-sync push [decks...] [--force] [--sync-printings] [--sync-categories] [-v]ritual deck-sync link <deck> <url>ritual deck-sync statusSubcommands
Section titled “Subcommands”| Subcommand | Description |
|---|---|
pull |
Apply Archidekt deck changes to the local deck files |
push |
Send local deck changes to the decks you own on Archidekt |
link |
Link a local deck to a deck that already exists on Archidekt |
status |
Show which decks are linked to Archidekt, and when each last synced |
Anything else is a usage error (exit code 2).
Arguments
Section titled “Arguments”| Argument | Description | Required |
|---|---|---|
[decks...] |
Deck names to sync (no .md). If omitted, syncs all Archidekt-sourced decks |
No |
Names resolve within decks only, following the usual list name rules. An ambiguous or unknown name is reported as a failed deck (not skipped) and the run exits 1.
Options
Section titled “Options”| Option | Description | Default |
|---|---|---|
-n, --dry-run |
Report what would sync without writing files or pushing changes | false |
-y, --yes |
Sync decks with unreadable lines without asking (those lines are removed) | false |
--only <changes> |
Apply only additions or removals (relative to the sync destination) |
all changes |
--force |
push only — overwrite a remote deck that changed since its last sync | false |
--sync-printings |
Also sync each card’s exact printing (set, collector number, foil/etched finish) | false |
--sync-categories |
Also sync each card’s categories (its role) | false |
-v, --verbose |
List every card being synced under its deck (see Card Listing) | false |
--output <format> |
Output format: text, json, or ndjson |
text |
--quiet |
Suppress non-essential output | false |
--force exists on push only. A pull never writes to Archidekt, so passing --force to it is a usage error.
Under --dry-run, both directions still fetch the remote deck state, since the diff needs it. A pull writes no files and records no changelog entries. A push sends nothing to Archidekt and does not update lastSynced.
Change Filter
Section titled “Change Filter”--only narrows a run to one side of each deck’s diff. Its values are relative to the destination: your deck files on a pull, Archidekt on a push.
| Value | Applies | Skips |
|---|---|---|
additions |
Cards missing from the destination, and quantity increases | Removals and decreases |
removals |
Cards gone from the source, and quantity decreases | Additions and increases |
Any other value exits with code 2. Skipped changes are still counted and reported, once per deck:
Syncing "Winota Stax" (pull)... Skipped 3 removals (applying additions only). Changes: +2 added, -0 removed, ~1 quantity changedThe filter applies to cards only. A pull still adopts the deck’s Archidekt format, and a deck whose only change is a format change still saves.
--only never drops a board move. A move changes no copy count, so both values keep it, and only the additions or removals left over around it are filtered. A move still takes its copies off the losing board under --only additions.
The admin Sync Decks page offers the same choice as an All changes / Additions only / Removals only control, and the MCP sync_decks tool takes it as an only field.
Scripted Output
Section titled “Scripted Output”With --output json (or ndjson), progress logging is suppressed and a single report is emitted on stdout:
{ "direction": "push", "decks": [ { "name": "Winota Stax", "status": "synced", "outcome": "changed" }, { "name": "Oops All Soldiers", "status": "synced", "outcome": "unchanged", "reason": "no changes", "reasonKey": "domain.sync.reasonNoChanges" }, { "name": "Borrowed Deck", "status": "skipped", "reason": "you do not own Archidekt deck 12345", "reasonKey": "domain.sync.notDeckOwner", "reasonParams": { "deckId": "12345" } }, { "name": "Gone", "status": "failed", "reason": "Failed to fetch Archidekt deck 999: 404" } ], "failedCount": 1, "unreadable": [], "cancelled": false}- Each deck’s
statusissynced,failed, orskipped;reasonexplains anything other than a clean sync. outcomeis the code to branch on:changed(written or sent),unchanged(already matched, or a push that once planned had nothing to send),dry-run(a preview, whichreasonsummarizes) orcancelled(never reached). It is absent on a failed deck and on one skipped for another reason, such as a deck the account does not own. (not-runexists for collection lists a refused run never reached.) Scripts should readoutcome, not the text ofreason.reasonis English prose whatever the UI language.reasonKeyandreasonParamsare the message it was rendered from, for a client that renders it in its own language; a param may itself be a message (messageKey,messageParams), a list (list,join), a duration (elapsedMs, ordurationMsfor an age or a time-out) or a card language (language). A reason raised outside the message catalog, such as a network error, has no key.- Decks that could not be resolved, or that are not sourced from Archidekt, appear as
failed. A deck with an ArchidektsourceUrlbut nosourceIdisskippedin an all-decks run andfailedwhen you name it explicitly. - Under
--sync-printingseach deck also carriesprintingsChanged(printing differences applied, or previewed on a dry run). Without the flag, a deck whose printings disagree between the two sides carriesprintingsUnaligned(those card names). A card one side holds with no printing at all never counts as disagreeing. - A deck whose diff moves cards between boards carries
boardMoves: the number of moves applied, or previewed on a dry run. It is absent when there are none. - Under
--sync-categorieseach deck also carriescategoriesChanged: the number of cards whose categories the run changed, or would change on a dry run. - A deck with card changes carries
changes, the card listing as data, whether or not--verbosewas given. It lists what went through: a push leaves out what its plan could not send, or did not need to. When the push itself failed (nothing was sent), it lists what the push attempted, andreasonsays so. Entries are discriminated bykind:added/removed(name,quantity,board),quantity(name,board,from,to),move(name,quantity,from,to),printing(name,board,from,to) andcategories(name,categories). On a run syncing printings, the first four also carryprinting(setlowercase,collectorNumber,finish). unreadablelists any deck whose file holds lines the parser could not read, with those lines. See Unreadable Lines.cancelledis alwaysfalseon the CLI. The admin API and the MCPsync_deckstool set it when a client cancels between decks.
Top-level failures (for example, not being signed in) are emitted as a structured error on stderr.
Run Summary
Section titled “Run Summary”A text-mode run closes with a one-line tally:
Synced 4 decks (2 with changes), 1 skipped, 1 failed.“with changes” counts decks that actually moved cards, as opposed to decks already in sync. A dry run says [dry-run] Would sync … instead. A run that covered no decks prints only No Archidekt decks found to sync.
Card Listing
Section titled “Card Listing”Each deck with changes prints a one-line Changes: count. With -v, --verbose the run also lists every card behind those counts, one line each, under the count:
Syncing "Winota Stax" (pull)... Changes: +1 added, -1 removed, ~1 quantity changed, 1 board move, 1 printing changed + 1 Lightning Bolt (2XM:117) [foil] to Main - 1 Brainstorm from Sideboard ~ Counterspell in Main: 2 → 3 > 2 Island: Main → Sideboard * Sol Ring in Main: (C21:263) → (LEA:270)| Marker | Change |
|---|---|
+ |
Copies added to a board |
- |
Copies removed from a board |
~ |
A card’s quantity on a board changes (old → new) |
> |
Copies move between boards |
* |
Copies change printing (old → new) |
= |
A card’s categories are set (primary first) |
Cards carry their printing on +, -, ~ and > lines, and * lines appear at all, only under --sync-printings. Without it the diff compares names and totals only. Likewise = lines appear only under --sync-categories.
The listing follows --only: it names only what the run applies, or would apply on a dry run. The count line and the cards come from the deck’s reported changes. A pull prints them once the deck is saved (or, on a dry run, at once), so a pull that cannot write its categories file counts and lists only the card changes. A push prints them before sending, and prints no count when none of its changes could be planned. Without --verbose, a run that had cards to list ends with Run with --verbose to list the cards being synced. The listing is progress output, so --quiet and --output json/ndjson print neither it nor that hint. Under --output json/ndjson the same cards arrive as data in each deck’s changes, and the MCP sync_decks tool reports them the same way. The admin Sync Decks page always shows the listing under each deck.
Divergence Guard (push)
Section titled “Divergence Guard (push)”A push makes Archidekt match your local file, so a card added on archidekt.com since your last sync would be set to quantity 0. To catch that, a push compares the remote deck’s updatedAt against the sourceUpdatedAt in the deck’s front matter (the remote updatedAt your last sync saw). If the remote changed since then, that deck fails and nothing is pushed for it:
Syncing "Winota Stax" (push)... Remote deck changed since last sync (remote: 2026-08-02T12:00:00.000Z, last synced against: 2026-08-01T00:00:00.000Z) — pull first, or pass --force to overwrite remote changes.Two ways forward:
deck-sync pullthe deck first. This adopts the remote edits, after which the push has nothing to revert.deck-sync push --force <deck>to overwrite them deliberately.
Both sides of the comparison are Archidekt’s clock, which is why the guard uses sourceUpdatedAt rather than lastSynced (your machine’s clock).
A pull always records the baseline, even when it finds no card changes. Remote edits that touch no card (a rename, a category shuffle, another machine’s push) still move Archidekt’s updatedAt, so “pull first” clears the refusal in that case too. Such a pull rewrites only the deck’s front matter; card lines and prose are untouched.
--dry-run reports the divergence the same way and does not need --force to preview it. A dry-run pull records nothing.
Three cases skip the check and are pushed normally. The run log names the reason, followed by — pushing without the divergence check.:
- A deck with no
sourceUpdatedAt. It has never synced through Ritual, so there is nothing to compare against. - A
sourceUpdatedAtthat is not a readable timestamp (for example, hand-edited). - A remote response with no usable
updatedAt(Archidekt reported no update timestamp for this deck — pushing without the divergence check.).
The admin Sync Decks page and the MCP sync_decks tool enforce the same guard. The tool takes force: true for the override; the admin page has no override, so pull first there.
Linking a Deck (deck-sync link)
Section titled “Linking a Deck (deck-sync link)”push only operates on decks whose front matter carries sourceUrl + sourceId, which import/import-account write. link writes those two fields for a deck that already exists on Archidekt:
ritual deck-sync link "Winota Stax" https://archidekt.com/decks/123456# Linked "Winota Stax" to https://archidekt.com/decks/123456 (deck 123456).- The URL must be an Archidekt deck URL. A scheme-less
archidekt.com/decks/123456is accepted. A trailing deck slug and any query string are dropped, so the storedsourceUrlis alwayshttps://archidekt.com/decks/<id>, the same spellingimportwrites. Anything else is a usage error (exit code2). - Only the front matter is written. Card lines (
&Nids included), prose, and fenced blocks are preserved byte for byte. - Re-linking a deck reports what it was linked to before.
-n, --dry-runreports the link without writing.--output jsonemits the result, and--quietdrops the text confirmation.
This is the same write the admin API’s PUT /api/metadata/deck/:slug, the MCP set_list_metadata tool, and ritual metadata perform.
Sync Status (deck-sync status)
Section titled “Sync Status (deck-sync status)”A read-only, offline view: which decks are linked, when each last synced, and when the account’s collection last synced. It needs no Archidekt session and makes no requests.
ritual deck-sync status# 2 decks linked to Archidekt:# Winota Stax — https://archidekt.com/decks/123456# last synced: 2026-08-01T00:00:00.000Z# Oops All Soldiers — https://archidekt.com/decks/222# last synced: never# Collection: last synced 2026-07-30T00:00:00.000Z (Archidekt user myuser).--output json emits { "decks": [...], "collection": {...} | null, "collectionStateError": string | null }. --output ndjson emits one tagged row per deck ({"kind":"deck",...}) plus one for the collection when it has synced.
A recorded collection state that exists but cannot be read is reported as such, not as never synced:
Collection: sync state unreadable (the file is not valid JSON).The reason lands in collectionStateError under --output json, and as a {"kind":"collection-state-error","reason":...} row under --output ndjson. The listing is the whole payload, so status has no --quiet (shared convention).
The same data backs the admin Sync Decks page and the MCP get_sync_status tool.
Unreadable Lines
Section titled “Unreadable Lines”Both directions rewrite the deck file, so a line the parser cannot read (a stray comment, a malformed card line) would be deleted by the save. A fenced code block counts too, since the canonical writer cannot re-emit it. A sync lists every affected deck and the exact lines at stake, then asks:
1 deck contains lines Ritual cannot read.Syncing rewrites the deck file, so these lines would be removed: winota-stax.md ("Winota Stax"): Skipped malformed line: buy this one later? Sync 1 deck anyway, removing the lines above? › (y/N)Answering no (the default) fails those decks; the rest of the run continues. Pass -y, --yes to answer yes up front.
Without a terminal to ask (--no-input, a piped stdin, or --output json/ndjson), the affected decks fail with N unreadable lines would be dropped by a sync, and the command exits 1. Pass --yes to sync them anyway, or fix the lines first. The listing is written to stderr in every mode, and every report carries an unreadable array with the same decks and lines.
--dry-run is exempt: a preview writes nothing, so there is nothing to confirm. The lines are listed and the deck is previewed like any other.
Failure Behavior
Section titled “Failure Behavior”Per-deck failures (a failed Archidekt fetch or push, cards that could not be turned into upload entries, a deck name that did not resolve) are reported as they happen, and the sync continues with the remaining decks. If any deck failed, a summary such as 2 of 5 decks failed is printed to stderr and the command exits 1. It exits 0 only when every deck synced cleanly.
Rate Limiting
Section titled “Rate Limiting”Archidekt allows about 80 requests per minute per IP. Ritual spaces requests at least 1.5 s apart (40 per minute). The budget is shared process-wide, so two syncs in the same server pace against each other.
When Archidekt answers 429 Too Many Requests, the request is retried up to 5 times. Each retry waits out the server’s Retry-After (capped at 60s) when given, otherwise backs off exponentially (2s, 4s, 8s, 16s, 32s). Each wait is reported as a warning. A 429 that outlives the retries fails that deck’s operation like any other HTTP error.
Tune the spacing with the RITUAL_ARCHIDEKT_MIN_INTERVAL_MS environment variable (0 disables it). The 429 handling is always on.
How It Works
Section titled “How It Works”Prerequisites
Section titled “Prerequisites”Sign in to Archidekt first:
ritual login archidektDecks must be linked to Archidekt: their YAML front matter carries sourceUrl and sourceId. import/import-account write those when they fetch a deck. For a deck you built locally, create it on archidekt.com and then deck-sync link it.
Pull (deck-sync pull)
Section titled “Pull (deck-sync pull)”- Fetches the current deck state from Archidekt.
- Compares cards and quantities against the local deck file, per board (Main, Commander, Sideboard, Maybeboard) and by card name.
- Applies the differences to the local file, respecting each card’s board:
- New cards are added to the section matching their remote board, creating that section if needed. A new section is inserted in canonical board order (Commander, Main, Sideboard, Maybeboard) without reordering existing sections.
- Removed cards are deleted from the board they were removed from.
- Quantity changes are applied in place within the matching board.
- A card that moved between boards on Archidekt is moved to the matching local section: the line itself moves, keeping its
&NID, tags, labels, note, condition, and language. When only some of a line’s copies moved, the line keeps the rest (and its ID and note), and the moved copies become a new line in the target section with the same tags, labels, condition, and language and a new ID. - A section the pull leaves with no cards is removed with its last card, such as an emptied
## Commander,## Maybeboard, or custom section. Empty## Mainand## Sideboardheaders are kept.
- Records all changes in the deck’s
.changes.mdchangelog. Card names are quoted, and changes to a non-main board name the board, such asAdded "Cavern-Hoard Dragon" to MaybeboardorRemoved "Lightning Bolt" from Sideboard. A line that moved between boards is recorded as a section move (Moved "Sol Ring" to section "Sideboard"); a line split by a partial move is recorded as the copies removed from it and added to the new line. - Adopts the deck’s Archidekt format, mapped onto Ritual’s format keys (“Commander / EDH” becomes
commander, “Dual Commander” becomesduel-commander, and so on). A format Ritual does not model (Custom, Frontier, Future Standard) leaves the local format untouched. A format change alone is enough to make the deck sync, but it is not recorded in the changelog, which tracks cards only. - Sets
lastSyncedandsourceUpdatedAtin front matter, including on a pull that found no changes (which rewrites the front matter only).
Push (deck-sync push)
Section titled “Push (deck-sync push)”- Verifies you own the Archidekt deck (skips non-owned decks with a warning).
- Fetches the current Archidekt deck state.
- Refuses the deck when the remote changed since its
sourceUpdatedAt, unless--forcewas given. See Divergence Guard. A refused deck is never diffed. - Compares local cards and quantities against the remote state, per board and by card name, the same way a pull does.
- Pushes differences to Archidekt via their batch API:
- New cards are resolved by name (with the local line’s set as a search hint) and added to their local board (see Board moves).
- Removed cards are set to quantity 0, on the board they were removed from only.
- Quantity changes are set to the new absolute value.
- A card the local file holds on a different board is moved there on Archidekt.
- Sets
lastSyncedandsourceUpdatedAtin front matter, only for decks that pushed cleanly. A deck whose cards could not all be turned into upload entries is reportedfailedand keeps its old stamps.sourceUpdatedAtis re-read from Archidekt after the push, since the push itself moved it.
What Is Compared
Section titled “What Is Compared”Sync compares card names and quantities, per board (Main, Commander, Sideboard, Maybeboard), in both directions. A card that sits on a different board on each side is moved, see Board moves. Archidekt files boards as categories; see Boards and categories for which ones count as boards.
Pulls adopt the deck’s format from Archidekt. Pushes do not send the local format back.
Not written by default:
- Specific printings (set code, collector number): synced with
--sync-printings - Card finish (foil, etched): synced with
--sync-printings - Categories (beyond mapping to a board): synced with
--sync-categories - Labels and tags
- Card condition and language
Printings are still read without the flag: enough to land a quantity change on the line that holds that printing, and to report a difference the run will not act on. See Without the flag.
Board moves
Section titled “Board moves”A card on one board on one side and another board on the other side is a board move, and both directions apply it. There is no flag for it. The summary line gets a clause, such as …, 2 board moves.
-
What pairs. Copies one board loses and another board gains pair up by card name. With
--sync-printingsthey also have to be the same printing, except that a gaining line naming no printing (or only a finish the copies already have) takes the copies at their own printing. A card that changes board and printing in one sync is removed from one board and added to the other. Copies that do not pair are ordinary additions, removals, or quantity changes. For example, a local1 Mainagainst a remote1 Main + 1 Maybeboardis a removal from the Maybeboard, not a move. -
Never onto a board that already holds the card. When the gaining board already has the card (at the same printing, under
--sync-printings), moving copies there would make a second line or entry beside the first. The copies are a quantity change instead: removed from one board, added to the existing line or entry on the other. -
What a board is. Sync works at four boards: Commander, Main, Sideboard, Maybeboard.
## Tokenscounts as the Maybeboard, and## Companion/## Oathbreakercount as Main, so a card in those sections is never moved just because the other side files it under a different section of the same board. -
Pull moves the local line itself, so it keeps its ID and every per-copy detail. The target section is the first one on that board, or a new one inserted in canonical board order.
-
Push moves the existing Archidekt entry by replacing its board category, keeping its label, printing, finish, and role categories. When only some of an entry’s copies move, the entry keeps the rest and a new entry with the same details takes the moved copies. The board categories a push uses:
- Main: no board category at all.
- Commander: the deck’s command-zone (premier) category, else
Commander. - Sideboard:
Sideboard. - Maybeboard: the deck’s
Maybeboardcategory, else another category the deck excludes from its count (such as Tokens & Extras, but never one namedTokens), elseMaybeboard.
A card new to Archidekt gets its board’s category too, plus the role categories its other entries use. Archidekt’s default category is used only for a new Main card with no other category.
-
--onlynever drops a move (see Change Filter). -
Reporting. Each deck’s structured result carries
boardMoves. A move the other side cannot fully cover (which the diff should never produce) is a warning on a pull and fails the deck on a push.
Front Matter
Section titled “Front Matter”After a successful sync, two fields are added or updated in the deck’s YAML front matter:
---format: commandersourceId: '12345'sourceUrl: 'https://archidekt.com/decks/12345'lastSynced: '2026-04-02T12:00:00.000Z'sourceUpdatedAt: '2026-04-02T11:59:58.000Z'---lastSynced is your machine’s clock at the moment of the sync; deck-sync status shows it. sourceUpdatedAt is Archidekt’s own updatedAt for that deck as of the sync, and is the only value the divergence guard compares. Neither is hand-authored.
format is written on every save, whether it came from Archidekt or was inferred from the deck’s sections. See new.
Printing Sync (--sync-printings)
Section titled “Printing Sync (--sync-printings)”By default the diff does not sync which printing a card line names. --sync-printings (valid on pull and push) also syncs each card’s set code, collector number, and finish:
- Pull: a local card whose printing differs from Archidekt’s is rewritten to the remote printing (
1 Sol Ring (C21:263) &5becomes1 Sol Ring (LTC:284) [foil] &5), keeping its&Nid, condition, language, and note. Each rewrite is recorded in the changelog as aSet "<card>" printing to SET:CN …entry. Newly added cards also carry their remote printing instead of arriving as bare names. - Push: a remote entry whose printing differs from the local line is moved to the local printing. The target edition is resolved through Archidekt’s printing search by set and collector number, and the entry’s finish is set from the local
[foil]/[etched]token. Newly added cards are placed at their exact printing rather than resolved by name and set alone.
Printing changes get their own clause on each deck’s summary line: Changes: +0 added, -0 removed, ~0 quantity changed, 3 printings changed on a pull, …, 3 printings to change on a push. A deck whose only difference is a printing still syncs.
Cards held at several printings
Section titled “Cards held at several printings”A card name can be held at more than one printing at once: 2 Lightning Bolt (LEA:161) beside 1 Lightning Bolt (2XM:157) locally, or several Archidekt entries of the same card, one per edition and finish. With --sync-printings these are reconciled printing by printing rather than by name, so both sides end up holding the same printings in the same quantities:
- Copies at a printing both sides hold have their quantity adjusted.
- A printing only the source holds is added as a new line (or a new Archidekt entry).
- A printing only the destination holds is removed.
- When a printing on each side is left over, the destination’s copies are re-pointed to the source’s printing in place. The local line keeps its
&N, and the Archidekt entry keeps its categories and relation.
So a local deck holding 2 Lightning Bolt (LEA:161) and 1 Lightning Bolt (2XM:157) pushed against a remote holding 3 Lightning Bolt (LEA:161) sets the existing entry to 2 and adds a new 2XM entry for the third copy.
The rules:
- A line that names no printing pushes nothing. A bare
1 Sol Ringstates no preference, so a push leaves the remote edition alone. A pull stamps the remote printing onto it, since Archidekt entries always name an edition. - A stated finish must exist. Pushing
[etched]for a printing that has no etched finish on Archidekt fails that deck instead of silently substituting another finish. The rest of the deck’s changes still push, but itslastSyncedis withheld. An unstated finish falls back to the printing’s default, so a bare line on a foil-only printing does not fail. --onlydoes not filter printing updates. Changing a printing neither adds nor removes cards.- Condition and language are never synced. Archidekt deck entries carry neither.
Without the flag
Section titled “Without the flag”A sync that is not syncing printings never adds or removes a card to reconcile a printing. A card’s new total is spread over the lines (or Archidekt entries) it already occupies: a surplus lands on the first, a shortfall drains from the last backwards, so a card split across printings keeps its split. When the two sides hold genuinely different printings of a card, the run says so and moves on:
Printings not synced for "Lightning Bolt": the local file and Archidekt holddifferent printings of it. Re-run with --sync-printings to reconcile them.Those card names are reported as printingsUnaligned in the structured report (omitted when there are none). Only a printing with no counterpart at all on the other side counts, since squaring that up would mean adding or removing copies. A card the two sides merely hold at different printings is not reported, and neither is a line that names no printing.
The admin Sync Decks page offers the same behavior as a checkbox (“Also sync each card’s exact printing…”), and the MCP sync_decks tool takes it as a syncPrintings field.
Category Sync (--sync-categories)
Section titled “Category Sync (--sync-categories)”A category is a card’s role in one deck (Ramp, Removal, Board Wipes). Archidekt calls it a category too; Ritual keeps it in the deck’s <deck>.categories.json. By default a sync leaves categories alone. --sync-categories (valid on pull and push) syncs them too:
- Pull: every card the deck holds after the pull gets exactly the categories Archidekt files it under, in Archidekt’s order, the first being primary. A card with no role category on Archidekt loses its local ones. The categories of cards the pull removed are pruned. Each card whose categories changed is recorded in the changelog (
Set categories of "Sol Ring" to Ramp, Artifact). A pull that changes only categories leaves every card line exactly as written and updates only the front matter’s sync stamps. - Push: every Archidekt card the local deck also holds gets the local categories, alongside the board category of the board the card ends up on (
Commander,Sideboard, …; see Board moves). Role categories never decide a board. A local card with no categories has its Archidekt roles cleared. A card new to Archidekt goes into its board’s category and its local categories, or Archidekt’s default category when it goes to Main and the local file gives it none.
Each deck’s summary line gets a clause: …, 3 cards' categories changed on a pull, …, 3 cards' categories to change on a push. A deck whose only difference is its categories still syncs.
The rules:
- A first push cannot wipe Archidekt. A push from a deck with no local categories at all leaves Archidekt’s categories as they are and says so. Pull with
--sync-categoriesfirst to bring them down. - An unreadable categories file fails the deck before anything is written or sent.
--onlydoes not filter category changes. Changing a card’s categories neither adds nor removes it.- Card names are the key. Archidekt holds a card once per printing; when those entries disagree, a pull takes the union in entry order. A push gives every entry of the card the same categories.
- Names Ritual cannot store are skipped with a warning. Anything goes except a comma or a control character, so in practice this rarely happens. A push leaves such a category on Archidekt as it is, since the local file never held it.
- A local category named like a board is not pushed. Sending
Sideboardas a category would move the card on Archidekt, so a push leaves it out. - A pull speaks only for cards Archidekt holds. A local card the remote deck lacks (one an
--only additionspull kept) keeps its categories.
Boards and categories
Section titled “Boards and categories”Archidekt uses categories for boards too. With or without the flag, a category counts as a board, and decides the card’s section instead of becoming a Ritual category, when:
- its name is a section name Ritual reserves (
Commander,Sideboard,Maybeboard,Companion,Oathbreaker,Main/Mainboard; see sections), matched exactly, not as a substring; - the deck marks it as the command zone (a premier category) →
Commander; - the deck excludes it from the card count (Archidekt’s Tokens & Extras, or any category not “included in deck”) →
Maybeboard.
Tokens is the exception: Archidekt decks commonly use it for token makers, so it is a category unless the deck excludes it from the count, in which case it is the Tokens board.
The admin Sync Decks page offers the same behavior as a checkbox (“Also sync each card’s categories…”), and the MCP sync_decks tool takes it as a syncCategories field.
Exit Codes
Section titled “Exit Codes”| Code | Meaning |
|---|---|
0 |
Every deck synced cleanly (or there was nothing to sync) |
1 |
At least one deck failed — including a deck refused for unreadable lines — or you are not signed into Archidekt |
2 |
Unknown subcommand, an invalid --only value, a link URL that is not an Archidekt deck URL, or a link deck name that matches more than one deck |
3 |
link named a deck that does not exist |
Examples
Section titled “Examples”Pull changes for a specific deck:
ritual deck-sync pull black-pantherPush changes for multiple decks:
ritual deck-sync push black-panther oops-all-soldiersPull for all Archidekt decks:
ritual deck-sync pullPreview a push without sending anything:
ritual deck-sync push --dry-runPull new cards without letting a pull delete anything locally:
ritual deck-sync pull --only additionsSync in a script, accepting the loss of any lines Ritual cannot read:
ritual deck-sync pull --yes --no-inputBring Archidekt’s categories down, then push local category edits back:
ritual deck-sync pull "Winota Stax" --sync-categoriesritual deck-sync push "Winota Stax" --sync-categoriesPreview a pull and see exactly which cards it would change:
ritual deck-sync pull "Winota Stax" --dry-run --verboseScript a pull and inspect per-deck results:
ritual deck-sync pull --output jsonLink a locally built deck to an empty deck you created on Archidekt, then push it:
ritual deck-sync link "Alpha Deck" https://archidekt.com/decks/123456ritual deck-sync push "Alpha Deck"See what is linked and when it last synced:
ritual deck-sync status --output jsonOverwrite remote edits made since your last sync:
ritual deck-sync push "Winota Stax" --forcePush the exact printings and finishes you picked locally to Archidekt:
ritual deck-sync push "Winota Stax" --sync-printingsRitual is unofficial Fan Content permitted under the Fan Content Policy. Not approved or endorsed by Wizards of the Coast. Portions of the materials used are property of Wizards of the Coast. ©Wizards of the Coast LLC.
Card data, images and symbols come from Scryfall, which is not affiliated with this project. Ritual is free software under the AGPL-3.0. License and attribution.