Skip to content

move

Move cards between decks, collections, and wanted lists — interactively by default, or as a single scripted command with --from and --to.

Terminal window
# Interactive session across all lists
./ritual move
# Interactive session, pre-filtered to one source list
./ritual move --from <list>
# Scripted (headless) move — no prompts
./ritual move [cardName...] --from <list> --to <list> [options]

<list> accepts an optional deck:, collection:, or wanted: prefix (e.g. wanted:needs). The prefix pins the list type; without it, the name is resolved across all three types and an ambiguous name is an error (see List Resolution).

ArgumentDescriptionRequired
[cardName...]Card to move, fuzzy-matched against the source list’s entriesIn scripted mode, unless --card-id is given
OptionDescriptionDefault
--from <list>Source list. Alone, launches the interactive session filtered to this source; with --to, moves without prompts
--to <list>Destination list. Requires --from
-q, --quantity <n>Number of copies to move1
--card-id <id>Select the source card by ID (the &N suffix in list files)
--set <code>Narrow the match to this set code — or assign the printing when the card has none
--collector-number <cn>Narrow the match to this collector number — or assign the printing when the card has none
--finish <finish>Narrow the match to this finish: nonfoil, foil, etched
--to-section <name>Deck destinations only: add the card to this section (exact name, created if missing)
--output <format>Output format: text, json, or ndjsontext
--quietSuppress non-essential outputfalse

When both --from and --to are given, the move runs headlessly — no prompts, ever — making it safe for scripts and agents. A card selector is required: a card name argument or --card-id. Passing any scripting flag (a card name, --quantity, --card-id, --set, --collector-number, --finish, --to-section) without both --from and --to is a usage error (exit code 2) — it never silently falls back to the interactive session.

The scripted path uses the exact same engine as the interactive session, so all its behaviors apply: deck sources decrement quantity, notes travel with the card, both lists get changelog entries, and destination lists assign fresh &N IDs.

When the destination is a deck, --to-section <name> places the card in that section instead of the default (the first non-Commander, non-Sideboard section). The section is matched by exact name and created when missing; using it with a collection or wanted-list destination is a usage error.

When a move quantity-merges onto an existing deck line that already carries a different note, the incoming card’s note cannot travel (one line has one note slot) — the existing note wins. Each dropped note is warned on stderr, and JSON output reports them in a droppedNotes array ({ cardName, cardId?, note }).

Record a purchase — a wanted card arrived and goes into the collection with its printing assigned in the same command:

Terminal window
./ritual move "Demonic Tutor" --from wanted:needs --to collection:binder \
--set sta --collector-number 90

Move a card between decks:

Terminal window
./ritual move "Lightning Bolt" --from deck:burn --to deck:storm

Move two copies at once:

Terminal window
./ritual move "Lightning Bolt" --from deck:burn --to collection:binder -q 2

Disambiguate between printings with --set (or --card-id):

Terminal window
./ritual move "Lightning Bolt" --from deck:burn --to deck:storm --set lea

Move a card into a specific deck section:

Terminal window
./ritual move "Duress" --from collection:binder --to deck:storm --to-section Sideboard

Select by card ID and emit a JSON record for scripting:

Terminal window
./ritual move --card-id 7 --from wanted:needs --to deck:storm --output json
{
"moved": 1,
"card": { "name": "Demonic Tutor", "cardId": 7 },
"from": { "type": "wanted", "name": "needs" },
"to": { "type": "deck", "name": "Storm" },
"droppedNotes": []
}
  • By name: punctuation-, case-, and accent-insensitive; an exact name match wins, otherwise substring matches are used.
  • By card ID: --card-id <N> targets an entry by its persistent &N suffix in the source list. A card name given alongside it must match the ID’s entry, or the move is refused with a usage error naming both (--card-id 2 is 'Brainstorm', which does not match 'Sol Ring'.) — a stale ID would otherwise move the wrong card. ID-only and name-only selection are unaffected.
  • Narrowing: when the name matches several distinct variants (set / collector number / finish / language), the command refuses to pick one arbitrarily — it exits with a usage error listing them. Narrow with --set, --collector-number, --finish, or --card-id; two copies differing only by language (a bare English line beside a [ja] one) are distinct variants, and --card-id is what pins one of them.
  • Quantity: -q moves that many copies of the same printing. Requesting more copies than the source list holds is an error, and nothing is moved.

Collections require a concrete printing. When the selected card already has one, nothing changes. When it does not (a name-only wanted entry), the printing is resolved in this order:

  1. --set + --collector-number, when given (both are required together). The pair is validated against the card’s known printings in the local Scryfall cache — a set/collector-number the card was never printed as is a usage error listing the printings that do exist.
  2. The card’s single known printing in the local Scryfall cache, auto-accepted.
  3. Otherwise the command exits with a usage error listing the cached printings to pick from.

Both steps read the local card cache only: a name the cache holds no entry for has no known printing list, so step 2 cannot apply and the command asks for --set/--collector-number rather than guessing. When a pin is given for such a card, Ritual verifies that one printing directly with Scryfall (a single request) instead of validating it against an incomplete list — so the purchase flow above works on a workspace whose cache has never been bulk-downloaded, as long as it is online. Offline, that verification fails with exit code 1. Running ritual cache preload-all once removes both round trips.

The resolution happens before anything is written — a failure here leaves both lists untouched.

Run without --to to launch the interactive session. With --from <list>, the session starts with only that list enabled as a source — the same setting the Session Filters screen edits, so it can be widened mid-session.

The session requires a terminal with prompts enabled. When prompts are unavailable (stdin is not a terminal, or --no-input / RITUAL_NO_INPUT is in force), the command refuses to open the session and exits with code 2 (Input required: pass --from and --to …) — the headless path is the only one available to scripts.

Key behaviors:

  • Deck moves: Moving a card from a deck decrements its quantity by 1. The line is removed when quantity reaches 0.
  • Note preservation: Notes ({note}) on deck, collection, and wanted list entries are carried over to the destination list. The one exception is a quantity-merge onto an existing deck line that already carries a different note — the existing note wins and the dropped note is reported after saving.
  • Label preservation: A card’s [labels] override travels with the move, filtered on arrival to what the destination type carries: another collection keeps all of it, a deck keeps proxy and drops the rest, and a wanted list keeps none. A move never invents a label, and never writes one the destination grammar cannot express. (The list default never travels — the destination’s own front matter applies.)
  • Language preservation: A card’s language token ([ja]) travels with the move to any list type — a bare line stays bare, since a bare line always means English. When a printing is resolved for a collection destination, its availability in the card’s language is checked like the printing itself, and the JSON record’s card includes language for non-English copies.
  • Name-only wanted entries: If a card has no set/collector number (i.e., it is a name-only wanted list entry) and the destination requires a printing (e.g., a collection), you will be prompted to resolve a printing before the move is queued. The picker lists each printing’s price in your configured defaultCurrency — see Printing and Finish Prices.
  • Single destination: In the single-card flow, if only one valid destination is configured, the destination prompt is skipped and the card is queued immediately. Batch Mode always asks.
  • Change tracking: Source files receive a Moved … to … changelog entry. Destination files receive a Moved … from … changelog entry.
  • Deck files are rewritten in canonical form: a move touching a deck re-serializes that deck file, so a move is refused when the parser cannot read some of that file (prose, comments, a fenced code block, an empty ## Main/## Sideboard header) — the write would delete it. An empty extras section (## Maybeboard, ## Tokens) is the exception: it holds nothing, so it is dropped rather than refused. Collection and wanted-list files are edited as text and keep such lines.

When launched, the tool shows an autocomplete search field. You can type a card name to search, or select from the menu:

OptionDescription
📋 View Pending Changes (N)Preview queued moves before committing
🧺 Batch Mode — select many cards at onceSelect many cards at once and send them all to one destination
⚙️ Configure Session FiltersRestrict which lists are eligible as sources or destinations
🚪 ExitLeave the session (asks to save, discard, or cancel when pending)

After searching, select a card and choose a destination (or confirm the single available one). The destination prompt is also an autocomplete field — type to filter the list of destinations instead of scrolling with the arrow keys. The move is queued as a pending change. You can queue multiple moves before committing.

When the destination is a deck, the session then asks which section the card lands in, listing the deck’s sections plus ➕ New section… for one that does not exist yet. The deck’s default section — the first that is neither the commander nor the sideboard — is preselected, so pressing Return keeps the behavior a move had before the prompt existed. (On a deck whose only sections are a commander and a sideboard, that default is a Main section the add would create, and it is offered as a row.) A deck with no sections at all is not asked about, and neither is a collection or wanted-list destination.

Escaping the section prompt cancels the move: in the single-card flow nothing is queued, and in Batch Mode you return to the checklist with the selection intact. ➕ New section… asks for a name; an empty name cancels the same way, a name matching an existing section (in any casing) is folded onto that section rather than creating a second one, and a name starting with # or containing a line break is refused — it could not survive being written as a ## heading. A destination deck whose file the parser cannot read is refused here too, rather than at save time.

Foil and etched cards are flagged in the search results, the pending-changes view, and the queued-move confirmation (e.g. Lightning Bolt (LEA:161) [Foil]). Normal non-foil printings are shown without a finish tag.

Nothing is written until you exit and choose to save. 🚪 Exit (or pressing Escape) leaves the session immediately when nothing is pending; with pending moves it opens a menu to Save and exit (commit all pending moves), Exit without saving, or Cancel (keep editing).

Selecting 🧺 Batch Mode switches the session from “one card at a time” to “many cards, one destination”. It is a toggle: the batch screens replace the card search until you leave them, and every card you queue lands in the same pending-move state the single-card flow uses — View Pending Changes, chained moves, and the save-on-exit menu all behave identically.

A batch is three screens:

  1. Which lists to view. The same two-level toggle screen the Session Filters use, seeded from the current Move FROM filter. Changes here are local to the batch — they never rewrite the session’s filters. Emptying the list is how you leave Batch Mode from this screen.
  2. Which cards to take. The cards from every viewed list are combined into one searchable, autocomplete-filtered checklist. Each row carries the card’s usual details — printing, finish, language, &N, note — plus the list it comes from. Selecting a row toggles its [X]; the list never reorders as you tick cards. Cards already queued for a move this session are not shown.
  3. Where they go. An autocomplete of every enabled destination — including lists some selected cards already sit in, which are skipped and counted — then the deck-section question when the destination is a deck. The whole selection is queued to that one destination.

The rows above the cards are:

RowDescription
✅ Done selecting (N)Continue to the destination screen; refuses while nothing is selected
☐ Select allTick every card on screen — flips to ☑ Deselect all once everything is ticked
☑ Select all from…Shown instead when more than one list is being viewed (see below)
⬅ Exit batch modeReturn to the single-card search

☑ Select all from… opens a picker of the lists currently being viewed. Tick at least one and choose → Continue to select every card in them, or take ★ All selected lists to select every card from every viewed list at once whatever the boxes say. Both add to the current selection rather than replacing it; ← Back (or Escape) returns to the checklist unchanged.

Escape (or backing out) at the destination or section question returns to the checklist with the selection intact, and so does a batch in which nothing could be queued — picking the cards is never thrown away.

After a batch is queued the session stays in Batch Mode and reopens at the list picker, with the same lists still ticked, ready for the next batch. The one exception is a view with nothing left to take: when every card of the viewed lists is already queued (or those lists are empty), Batch Mode reports No cards left to move in the selected lists. and hands the session back to the card search. So Batch Mode ends in four ways — ⬅ Exit batch mode, Escape on the checklist, emptying the list picker, or running the viewed lists dry.

Cards can drop out of a queued batch, each reported with a count:

  • It already sits in the chosen destination.
  • It is a printing-less card headed for a collection with no known printings. Each such card is prompted for individually after the destination is chosen, exactly as the single-card flow prompts.
  • Escaping one of those printing prompts ends the batch there: the cards already resolved are queued, and the ones after it are reported as left unqueued rather than asked about one by one.

Select Configure Session Filters to open the filter dialog. You can independently configure:

  • Move FROM — which lists are valid sources
  • Move TO — which lists are valid destinations

Inside each filter view, you can toggle lists by category (Decks, Collections, Wanted Lists) or individually:

[X] Decks (3/3)
[~] Collections (1/2)
[ ] Wanted Lists (0/1)
── Toggle All ON ──
── Toggle All OFF ──
← Done

The bracket indicator shows:

  • [X] — all lists in this category are enabled
  • [~] — some lists in this category are enabled
  • [ ] — no lists in this category are enabled

At least one destination must remain enabled at all times.

If you move a card and then try to move the same card again (e.g., from B to C after already queuing A → B), the tool updates the pending move to reflect the final destination. Only the original source and the final destination are written to; intermediate lists are never touched.

For example, if you queue:

  1. Sol Ring: Deck A → Collection B
  2. Sol Ring: Collection B → Wanted C

The committed result is: Sol Ring removed from Deck A and added to Wanted C. Collection B is unchanged. Changelogs reflect Deck A → Wanted C.

Card names are written quoted so the name is unambiguously separated from trailing annotations.

Source list changelog (.changes.md):

- Moved "Lightning Bolt" (LEA:161) [foil] &5 to Collection 'Red Binder'

Destination list changelog:

- Moved "Lightning Bolt" (LEA:161) [foil] &5 from Deck 'Ghyrson Starn Spellslinger'

A moved card takes its custom art with it: the entry leaves the source list’s .art.json and is re-filed under the &N the destination’s new line was given. Two exceptions leave the destination’s own art alone — a copy that merges onto a line the destination already had, and a deck line in the source that still has copies left (which keeps its id, and its art). Art sidecars carry no changelog entry, but they are included in the files an auto-commit stages.

Collection and wanted-list sides of a move are line-preserving: a card-looking bullet inside a fenced code block is prose, never the line a move removes, and the block survives byte-for-byte. A deck on either side is different — a deck move re-serializes the whole deck file, which cannot reproduce a fenced block — so a move touching a deck whose file holds one (or holds a line the parser cannot read) is refused before anything is written.

A destination whose file ends inside an unclosed fence is also refused: the moved card’s line would be appended past the fence opener and read back as prose.

Scripted (--from + --to) invocations follow the standard exit-code contract:

CodeMeaning
0The requested copies were moved
1Runtime error, or fewer copies were moved than requested
2Usage error: --to without --from, missing card selector, a --card-id that disagrees with the card name, ambiguous list or printing, unresolvable collection printing, invalid flag values, or an interactive session requested while prompts are unavailable
3Not found: unknown source or destination list, no matching card, or fewer copies available than requested