add-card
Add a card to a deck, collection, or wanted list by name.
Uses the local card cache for fast autocomplete-based card selection. If the cache is empty or older than 7 days, you will be prompted to update it. Fully scriptable: every prompt can be preempted with a flag, and --output json emits a machine-readable result.
./ritual add-card <targetName> <cardName...> [options]The target list is resolved from <targetName> across all three list types (see List Resolution). Pass a --deck, --collection, or --wanted flag to pin the type — required when the name is ambiguous, and required to create a missing collection or wanted list. A deck:/collection:/wanted: prefix on the name (e.g. collection:Main Binder) supplies the type when no flag is given; a prefix that contradicts the flag is a usage error (exit 2) naming both, rather than one silently winning.
Arguments
Section titled “Arguments”| Argument | Description | Required |
|---|---|---|
<targetName> | Name of the deck, collection, or wanted list (case- and accent-insensitive, no extension) | Yes |
<cardName...> | Name of the card to search for | Yes |
Options
Section titled “Options”| Option | Description | Default | Applies To |
|---|---|---|---|
--deck | Resolve the name as a deck | ||
--collection | Resolve the name as a collection (created if missing) | ||
--wanted | Resolve the name as a wanted list (created if missing) | ||
-q, --quantity <num> | Number of copies to add (must be a positive integer); passing a value other than 1 to a collection or wanted target is a usage error | 1 | Deck only |
-f, --finish <finish> | Card finish: nonfoil, foil, etched | Deck, Collection, Wanted | |
-c, --condition <cond> | Card condition: NM, LP, MP, HP, DMG, or NONE to record no condition | Deck, Collection | |
--language <code> | Card language as a Scryfall code (ja, de, zhs, …; aliases like jp or Japanese normalize); omitted, the configured defaultLanguage is stamped | Deck, Collection, Wanted | |
--label <labels> | Label the new card: sale,trade (combinable), keep, or proxy on a collection; proxy alone on a deck; omit to inherit the list’s default | Deck, Collection | |
--section <name> | Deck section to add to, created at the end of the file if missing | Deck only | |
--commander | Add the card to the deck’s Commander section (created at the top if missing) | Deck only | |
-e, --exact | Use exact matching (skip selection if name matches) | false | |
--set <code> | Pin an exact printing by set code (requires --collector-number) | ||
--collector-number <num> | Pin an exact printing by collector number (requires --set) | ||
--name-only | Add the card by name without choosing a printing | Wanted only | |
--specific | Record a specific printing (via --set/--collector-number or interactive picker) | Wanted only | |
--refresh <mode> | Card cache refresh policy: ask (prompt; skip when prompts are unavailable), auto, no-bulk, or never | ask | |
-n, --dry-run | Report what would be added without writing anything | false | |
--output <format> | Output format: text, json, or ndjson | text | |
--quiet | Suppress non-essential output | false |
--deck, --collection, and --wanted are mutually exclusive, as are --name-only and --specific. Flags that don’t apply to the resolved target type (for example --condition on a wanted list, --section on a collection, --label on a wanted list — or --label sale on a deck, which carries only proxy — or --name-only on a deck) are rejected with a usage error rather than silently ignored. Invalid --finish, --condition, --label, and --quantity values are rejected at parse time.
Examples
Section titled “Examples”Add a single card to a deck (resolved by name across all types):
./ritual add-card "My Deck" Sol RingPin the type explicitly when a name could be ambiguous:
./ritual add-card --deck "My Deck" Lightning Bolt -q 4Pin an exact printing onto a deck line:
./ritual add-card --deck "My Deck" Sol Ring --exact --set C21 --collector-number 263Add a foil straight into a deck’s Sideboard, or a commander into its Commander section:
./ritual add-card --deck "My Deck" Lightning Bolt --exact \ --set STA --collector-number 42 --finish foil --section Sideboard./ritual add-card --deck "My Deck" Kenrith, the Returned King --exact --commanderPreview an add without touching a file:
./ritual add-card --deck "My Deck" Sol Ring --exact -q 4 --dry-runFully scripted collection add — no prompts, machine-readable result:
./ritual add-card --collection "Main" Lightning Bolt --exact \ --set STA --collector-number 42 --finish etched --condition LP --output jsonRecord no condition without being asked for one:
./ritual add-card --collection "Main" Sol Ring --exact \ --set LEA --collector-number 270 --condition NONEAdd a name-only wanted entry with a finish preference:
./ritual add-card --wanted "My Wants" Demonic Tutor --exact --name-only --finish foilAdd a wanted entry pinned to a printing (the pin implies --specific):
./ritual add-card --wanted "My Wants" Lightning Bolt --exact --set STA --collector-number 42Behavior
Section titled “Behavior”List Resolution
Section titled “List Resolution”<targetName> is matched case- and accent-insensitively against existing list files — an exact name wins, otherwise a unique substring match is accepted, and any ambiguity is an error. A --deck/--collection/--wanted flag restricts the search to that type. See List Resolution for the full rules.
A missing collection or wanted list is created automatically, but only when the type is pinned with a flag or a collection:/wanted: prefix (the command can’t know which kind of list to create otherwise). This includes the first-run case where the workspace holds no lists of that type at all. Decks are never auto-created — create them first with new deck.
The file is created at write time, after every validation has passed, so an add that fails (an empty card cache, an unknown printing pin, a cancelled prompt) — or a --dry-run — never leaves an empty list file behind.
Card Selection
Section titled “Card Selection”The card name you provide is used to filter the local card cache. An autocomplete prompt lets you type to narrow down the list and select the correct card.
What you type is split on whitespace and every term must appear in the card name, in any order — typing in tre finds “In the Trenches”, and bolt light finds “Lightning Bolt”. Case, accents, and punctuation don’t have to match (jaces archivist finds “Jace’s Archivist”). The same rule applies to the name passed on the command line and to whatever you type at the prompt afterwards.
Suggestions are ordered by EDHRec popularity, except that closer matches come first: a card whose whole name you have typed leads (typing The End puts the card named “The End” at the top rather than burying it below every popular card containing those letters, and typing the front face of a double-faced card counts as its whole name), then cards your query prefixes, then the cards whose words your terms begin — which is what keeps “In the Trenches” at the top of in tre instead of the 80 cards that merely contain those letters somewhere.
When --exact is used, the input name is normalized (case and accents folded, punctuation stripped) and compared against all cached card names. If exactly one card matches, it is selected automatically with a confirmation message. If no exact match is found, the command exits with an error indicating how many cards match the input by term (counted up to 100, reported as “100+” if the limit is reached).
When prompts are unavailable (stdin is not a terminal, or --no-input / RITUAL_NO_INPUT is in force), the autocomplete prompt cannot run — an input that exactly matches a cached card name is accepted as if --exact were passed, and anything else is a usage error rather than a silent first-suggestion pick.
Printing Pins
Section titled “Printing Pins”--set and --collector-number (always together) pin the add to one exact printing:
- Deck: the printing is written onto the deck line (
3 Sol Ring (C21:263) &7). - Collection: skips the interactive printing picker.
- Wanted: skips the printing picker and implies the specific-printing flow.
The pair is validated strictly against the card’s known printings — a set/collector-number combination that doesn’t exist fails with a usage error listing up to 10 available printings (also carried as details.available in JSON error output). There is no fuzzy or fallback matching.
When the local card cache holds no entry for the card at all, there is no printing list to validate against, so the pinned printing is verified directly with Scryfall instead (a single request). It is accepted when Scryfall confirms it belongs to that card, a usage error when it does not, and a runtime error (exit 1) when Scryfall cannot be reached.
Without a pin, a run where prompts are unavailable (stdin is not a terminal, or --no-input / RITUAL_NO_INPUT is in force) only succeeds when the card has a single paper printing; several candidates fail with an error instead of guessing.
Printing and Finish Prices
Section titled “Printing and Finish Prices”The interactive printing picker lists each printing’s price in your configured defaultCurrency in right-hand columns — one aligned column per finish any of the listed printings comes in, nonfoil first and foil/etched to its right, with non-nonfoil amounts named after the price ($14.93 foil). A printing that doesn’t come in a column’s finish leaves that cell blank, and N/A means the card cache carries no price for that printing and finish in that currency. The finish prompt then prices each finish in a single column beside its label. See edit for examples.
Finish and Condition
Section titled “Finish and Condition”--finish values are validated twice: the flag itself must be nonfoil, foil, or etched (rejected at parse time otherwise), and once a printing is resolved, a finish that printing isn’t offered in fails with a usage error listing the finishes that do exist (also details.availableFinishes in JSON).
--condition accepts the usual grades plus NONE, which explicitly records no condition and skips the condition prompt — the scripting equivalent of answering “Don’t Care”. It is not optional in a scripted collection run: with no terminal to prompt on, a collection add without --condition fails rather than guessing. Deck adds never prompt for either value; both are optional there.
Note that NM and NONE produce the same line: NM is the unrecorded default and is written without a [NM] annotation (see set-card).
Language
Section titled “Language”--language records the copy’s language as a lowercase Scryfall code (en es fr de it pt ja ko ru zhs zht he la grc ar sa ph — Scryfall’s codes, not ISO: Chinese is zhs/zht). Common aliases normalize (jp → ja, kr → ko, sp → es, cs → zhs, ct → zht, and full English names like Japanese), so the persisted value is always the canonical code.
Adding never prompts for a language. Without the flag, the configured defaultLanguage is stamped on the new card — edit it afterwards with set-card --language if a single copy differs.
On the line itself the language is a bracket token in canonical position (- Sol Ring (C21:263) [foil] [LP] [ja] &7), and the token is omitted for English: a bare line always means en whatever the configured default, so files stay self-describing. --language is recorded exactly as given — only the printing pin is verified, not the language. To change a copy’s language with verification against the printing’s real languages, use set-card --language.
Cache Freshness
Section titled “Cache Freshness”Before displaying the autocomplete prompt, the command checks the card cache; the shared --refresh <mode> option decides how it responds:
- If the cache is empty,
ask(the default) prompts to download the card database (default yes) andautodownloads it without prompting. If it isn’t downloaded — the prompt is declined or unanswerable, or the mode isno-bulk/never— the command fails with a hint to runritual cache preload-allor re-run with--refresh auto. - If the cache is older than 7 days,
askprompts to update it (default no) andautoupdates it without prompting;no-bulk/neveruse it as-is. - If the cache is fresh, the command proceeds immediately.
Under ask, prompts that can’t be answered (--no-input / RITUAL_NO_INPUT, or stdin is not a terminal) are declined, never resolved to their defaults.
Dry Runs
Section titled “Dry Runs”-n / --dry-run resolves the list and the card, runs every validation (printing pin, finish availability, flag applicability), and reports the line that would be written — then stops. No list file, changelog, or .sha256 sidecar is written, no missing list is created, and the card-ID backfill is skipped, so a dry run leaves the workspace byte-for-byte as it found it. Text output is prefixed [dry-run]; JSON output carries "dryRun": true.
Change Tracking
Section titled “Change Tracking”Every card added through this command creates a change event that is recorded in a .changes.md changelog file alongside the target file, including the &N card ID allocated for the new line. Deck adds record one event per copy, so -q 4 writes four Added entries. This changelog is displayed in the site’s change history view.
Deck Mode
Section titled “Deck Mode”- Card is selected via autocomplete from the cache (or
--exact). - The card goes through the same change engine the editors and the admin site use, so a CLI add and an editor add produce identical files:
- Copies merge onto an existing line for the same card, the same printing (set, collector number, finish, condition, language) and the same label override — adding 2 more copies of a card the deck already has yields one line with a larger quantity, never a second line. The merged line keeps everything else it carried, including its
&NID and any{note}. Labels are part of that identity because they are part of what the copies are:--label proxyon a card the deck already runs for real adds a second,[proxy]line rather than folding the proxies into the real copies (or handing them the label). - Merging wins over placement, which is what
--sectionand--commanderdo: they place a new line. When the deck already runs the printing, the copies join the existing line wherever it lives, and--commanderthen moves that whole line (every copy on it) into the Commander section. A--sectionthat names a different section than the merged line sits in does not move it, and the changelog records the section the copies actually landed in. - A new line is appended at the end of its section: the section named by
--section, the Commander section under--commander, otherwise the deck’s first non-commander, non-sideboard section. A deck organized as## Mainboardtherefore keeps one main section instead of gaining a## Main. A deck whose body is bare card lines with no headings at all is one implicit Main section: the card joins after its last card line rather than growing a## Mainheading that would split the deck in two on the next parse. Only a deck with no card lines to join gets a## Maincreated.
- Copies merge onto an existing line for the same card, the same printing (set, collector number, finish, condition, language) and the same label override — adding 2 more copies of a card the deck already has yields one line with a larger quantity, never a second line. The merged line keeps everything else it carried, including its
-q Nrecords one add change event per copy — the same per-copy accountingremove-carduses — so replaying a changelog reproduces the quantity actually added.
Deck entries record the card name and quantity, plus the set code and collector number when pinned, and [finish]/[condition]/[labels] annotations when --finish/--condition/--label are given (--label proxy is the only label a deck line takes — see Card Labels). A --finish on a deck add is validated only when --set/--collector-number pin a printing: an unpinned deck line records no printing, so there is nothing to validate the finish against.
Collection Mode
Section titled “Collection Mode”- Card is selected via autocomplete from the cache (or
--exact). - The printing comes from the
--set/--collector-numberpin, or you are prompted to select one. - Finish and condition come from
--finish/--condition, or you are prompted. - The entry is appended to the collection file in
collections/.
Collection entries always record the specific printing (set code and collector number), since collection cards have monetary value tied to the exact printing.
There is no implicit condition — pass --condition NONE to record none. When prompts are unavailable, a run missing --condition, or missing --finish on a printing that comes in more than one finish, is a usage error (exit 2) naming the flag rather than a silent no-op: an exit 0 always means a line was written.
Wanted List Mode
Section titled “Wanted List Mode”- Card is selected via autocomplete from the cache (or
--exact). - Specificity comes from
--name-only,--specific, or a printing pin; with none of them you are prompted: Name only (any copy) appends just the card name, while Choose specific printing enters the printing selection flow followed by a finish prompt. When prompts are unavailable (stdin is not a terminal, or--no-input/RITUAL_NO_INPUT), one of the flags is required — instead of prompting, the command exits with code2. - The entry is appended to the wanted list file in
wanted/.
As with collection adds, a specific-printing add whose printing comes in more than one finish requires --finish when prompts are unavailable (exit 2). Only the name-only flow is finish-optional.
In the specific flow, a printing that cannot be resolved (no pin and no way to ask) is an error — the command never silently degrades a specific request to a name-only entry. Wanted list entries require only the card name; the printing and finish are optional (see the card states). A default finish can be specified with -f.
Fenced Code Blocks
Section titled “Fenced Code Blocks”An add is line-preserving: a card-looking line inside a
fenced code block is prose, so it is never a merge target and
the block is left byte-for-byte as written. The one refusal is a file that ends inside an
unclosed fence — an unclosed fence runs to end of file, so the new card line would be appended
into it and read back as prose. The command exits 2 and writes nothing; close the fence and retry.
Output
Section titled “Output”With --output json (or ndjson), a successful add prints exactly one machine-readable record — informational chatter (cache counts, price lines) never appears on stdout:
{ "type": "collection", "list": "main", "cardName": "Lightning Bolt", "set": "sta", "collectorNumber": "42", "finish": "etched", "condition": "LP", "cardId": 7}Deck adds include quantity (the number of copies added, not the merged line’s new total) and section (where the card’s line ended up); an add made with --label includes labels (the override the new line carries); a non-English add includes language (the canonical lowercase code — English is omitted, like the line’s token); wanted adds omit the fields that weren’t recorded. A --dry-run payload carries "dryRun": true, and the text line is prefixed [dry-run]. Set codes are lowercase in JSON output (the internal representation). Errors raised after argument parsing (usage, not-found, and runtime errors) are emitted on stderr as { "error": { "code", "message", "details" } }. Invalid flag values and flag conflicts (e.g. --name-only with --set) are rejected by argument parsing itself and printed as plain text on stderr regardless of --output — the exit code is still 2. In text mode, --quiet suppresses all non-essential output.
Exit Codes
Section titled “Exit Codes”| Code | Meaning |
|---|---|
0 | Card added (or, under --dry-run, the add reported with nothing written) |
1 | Runtime error (card cache unavailable, printing unresolvable in the specific flow, file write failure) |
2 | Usage error (invalid or conflicting flags, a type prefix contradicting a type flag, unknown printing pin, unavailable finish, cancelled prompt, or a missing --finish/--condition/wanted-specificity flag when prompts are unavailable) |
3 | Not found (missing deck, no exact card-name match, no cards matching the search) |