Getting Started
An all-in-one toolkit for Magic: The Gathering decks, collections, and self-hosting.
Installation
Section titled “Installation”curl -fsSL https://raw.githubusercontent.com/sloshy/ritual/main/scripts/install.sh | bashirm https://raw.githubusercontent.com/sloshy/ritual/main/scripts/install.ps1 | iexPrerelease Builds
Section titled “Prerelease Builds”Ritual is pre-release, so there is no stable release yet. Install the newest build — prereleases included — with:
curl -fsSL https://raw.githubusercontent.com/sloshy/ritual/main/scripts/install.sh | bash -s -- --prerelease& ([scriptblock]::Create((irm https://raw.githubusercontent.com/sloshy/ritual/main/scripts/install.ps1))) -PrereleaseBoth installers accept these options:
| Option | Environment variable | Description |
|---|---|---|
--prerelease/-Prerelease | RITUAL_PRERELEASE=1 | Install the newest release, including prereleases. |
--version <tag>/-Version <tag> | RITUAL_VERSION=<tag> | Install a specific release tag. Use prerelease for the newest prerelease. |
| — | RITUAL_INSTALL_DIR=<dir> | Where to install the binary. |
Other Methods
Section titled “Other Methods”You can also download a platform-specific binary from the releases page and run it directly:
./ritual --helpBuilding from Source
Section titled “Building from Source”This project uses Bun as its runtime. First, install dependencies:
bun installThen build the binary:
bun run buildQuick Start
Section titled “Quick Start”Create Your First Deck
Section titled “Create Your First Deck”./ritual new deck "My Commander Deck" --format commanderImport a Deck from a Website
Section titled “Import a Deck from a Website”Import decks from Archidekt, Moxfield, or MTGGoldfish:
./ritual import https://archidekt.com/decks/12345Get Pricing Information
Section titled “Get Pricing Information”./ritual price "My Commander Deck"Generate a Static Website (Experimental)
Section titled “Generate a Static Website (Experimental)”Ritual supports building static websites to showcase your decks (and soon, your collection). This is an experimental feature, so expect some rough edges. To build and serve the site locally:
./ritual serve --buildThen open http://localhost:3000 to view your deck collection.
Global Options
Section titled “Global Options”These options are available on every command:
| Option | Environment variable | Description |
|---|---|---|
--base-dir <path> | RITUAL_BASE_DIR | Use this directory instead of the current working directory |
--cache-server <host:port> | RITUAL_CACHE_SERVER | Use a remote cache server instead of local cache files |
--no-input | RITUAL_NO_INPUT | Never prompt; fail or use documented defaults where input would be required |
--locale <tag> | RITUAL_LOCALE | Language for Ritual’s own interface text (BCP-47, e.g. de-AT) |
In every case the flag wins when both are given, and an empty or whitespace-only environment variable counts as not set.
--base-dir
Section titled “--base-dir”By default, Ritual reads and writes files relative to the directory you run it from (the current working directory). Use --base-dir to point Ritual at a different directory without needing to cd there first:
# Run from anywhere, but operate on files in ~/my-collectionritual --base-dir ~/my-collection build-site
# Price all decks in a specific project directoryritual --base-dir /projects/mtg price "My Deck"This affects all file paths: decks, collections, wanted lists, cache, config files, and the output dist/ directory. The directory locations themselves can be customized via ritual.config.json — paths in that file are interpreted relative to --base-dir.
The RITUAL_BASE_DIR environment variable does the same thing and is used whenever --base-dir itself is not passed, so a shell session or a service unit can pin a workspace once:
export RITUAL_BASE_DIR=~/my-collectionritual lists # operates on ~/my-collectionritual --base-dir /projects/mtg lists # the flag wins for this one runThe directory must already exist. Ritual never creates a base directory: a path that is missing, or that is not a directory, is a usage error (exit code 2) and nothing is read or written:
base directory does not exist: /home/you/my-collectoinbase directory is not a directory: /home/you/notes.txtbase directory is not readable: /srv/private — EACCES: permission denied, stat '/srv/private'The third form covers permission problems and any other filesystem failure while checking the path.
This is deliberate — a typo’d --base-dir used to read as a perfectly empty workspace ((no lists)) and a write would have forked your data into a stray tree.
--no-input
Section titled “--no-input”Disables every interactive prompt across the CLI — the headless switch for scripts, CI, and agents. A command that would normally prompt either fails with a usage error or falls back to a documented default instead (the specific behavior is called out in each command’s own docs, e.g. price, export, cleanup).
The RITUAL_NO_INPUT environment variable does the same thing and is checked whenever --no-input itself is not passed — set it once in a CI environment instead of adding the flag to every invocation:
RITUAL_NO_INPUT=1 ritual cleanup --skip-formatsritual import ./decklist.txt --type deck --no-inputSetting it to a falsy value (0, false, no, off, any casing) counts as not set, so an inherited RITUAL_NO_INPUT=1 can be turned back off for one invocation:
RITUAL_NO_INPUT=0 ritual edit "My Deck" # prompts again, despite the CI defaultWhen prompts are unavailable
Section titled “When prompts are unavailable”A prompt cannot run when --no-input/RITUAL_NO_INPUT disabled prompting or when stdin is not a terminal (piped input, </dev/null, most CI runners). The two causes are treated identically everywhere in the CLI: no command opens a prompt, spawns a pager, or exits 0 having quietly skipped the work it was asked to do. Where an answer is genuinely required you get a usage error (exit code 2) naming the flag that would have supplied it, e.g.:
Input required: pass --finish <nonfoil|foil|etched> (no terminal available for prompts).Input required: pass --finish <nonfoil|foil|etched> (prompts are disabled by --no-input / RITUAL_NO_INPUT).The parenthetical names which of the two causes applied, since the remedies differ — supply the missing flags, or reconsider --no-input. Interactive-only surfaces (edit, history’s editor, move’s session, dep-license’s picker) refuse to open at all and point at their headless equivalent; license and dep-license <package> print their text straight to stdout instead of paging it through less.
Where a question has a documented default, that default is used instead of failing — each command’s page says which: --refresh ask is declined rather than answered wherever it is a prompt — but three commands document their own reading, and two of them download without asking: build-site bulk-downloads an empty or stale card cache automatically, since it cannot build without card data; sell redownloads an already-downloaded Card Kingdom buylist once it is a day old (as do admin, serve --api, and a sell-mode build-site), since a day-old feed quotes yesterday’s offers — only the first buylist download prompts; and collection-sync fails the run instead of skipping, and a text import without --type falls back to a deck under an explicit --no-input (a merely piped run still errors, since nothing said which type was meant). A URL import (and import-account) without a --sync-printings/--no-sync-printings answer behaves the same way: an explicit --no-input keeps the source’s exact printings and says so, while a merely piped run errors.
--locale
Section titled “--locale”Sets the language Ritual’s own interface text is written in — output, prompts, menus, help, and errors. It takes a BCP-47 tag (en, de, de-AT, pt-BR), which is canonicalized, so --locale de-at and --locale de-AT are the same thing.
ritual --locale de lists # this one runexport RITUAL_LOCALE=de # this shellritual config set uiLocale de # this workspace, persistentlyThe full precedence is --locale → RITUAL_LOCALE → the uiLocale config key → your OS locale → en. Only the flag is strict: a tag naming no known language is a usage error (exit code 2), while a bad RITUAL_LOCALE or uiLocale warns and falls through to the next tier, since an unusable interface language is cosmetic and refusing to run over one would be worse than the misconfiguration.
Run ritual locale to see which tier won — or ritual locale --detect to run the deeper OS probes (Windows/macOS) and optionally save the result as uiLocale.
The card-ID backfill
Section titled “The card-ID backfill”Every card line in a deck, collection, or wanted list file carries a stable internal ID — the &N suffix (e.g. 1 Sol Ring &5). The tools manage these IDs; never hand-author or renumber them. When a file is missing IDs (typically after a hand edit), Ritual backfills them automatically before running any command that rewrites list card lines or needs every line to already carry an ID: the editors (edit, history’s editor mode, admin — the server itself, not its setup/reset-password/disable-totp subcommands — mcp, and serve --build/--api), the one-shot card commands (add-card, remove-card, set-card, note, move), the importers and syncs (import, import-account, import-changes, deck-sync pull/deck-sync push — but not the read-only deck-sync status or the front-matter-only deck-sync link — and collection-sync), and the whole-workspace passes (cleanup, build-site).
No other command triggers the backfill, so none of them ever rewrites the card lines in your list files. That covers the read-only commands (lists, diff, price, sell, export, get-primer, history --show, list-all-cards, scry, card, …), the lifecycle commands (new, rename, delete), the front-matter-only metadata command (it writes list files, but never their card lines), and utilities like detect-changes — deliberately, since a backfill would rewrite the very files it is inspecting (that holds for --hash-only and --verify too). (Some of these still write files of their own, of course: detect-changes --hash-only writes .sha256 sidecars, and new/rename/delete create, move, or remove list files as their whole purpose.) -n/--dry-run suppresses the backfill too: a dry run writes nothing.
The backfill also never disturbs change tracking for hand-edited files: it refreshes a file’s .sha256 sidecar only when the sidecar already matched the file, so detect-changes still sees your hand edits and records their changelog entries.
Features
Section titled “Features”- List Management: Create and organize your decks, collections, and wanted lists
- Multi-source Import: Import from Archidekt, Moxfield, MTGGoldfish, or local files
- Scryfall Integration: Full card search powered by Scryfall
- Pricing: Get min, max, and latest prices for your decks
- Static Site Generation: Create a self-hosted website to showcase your decks or collection
- Caching: Smart caching for fast card lookups
Scripting quickstart
Section titled “Scripting quickstart”Use structured output for automation:
./ritual price "My Commander Deck" --output json | jq '.totals'Stream multiple card lookups as NDJSON:
./ritual card --from-file cards.txt --output ndjson --fields name,set,prices.usdAvoid interactive prompts in CI:
./ritual import-account johndoe --all --no-input --dry-runScripting tips
Section titled “Scripting tips”- Prefer
--output jsonor--output ndjsonfor script parsing. - Use
--fieldsto project stable subsets of data. - Add the global
--no-inputflag (or setRITUAL_NO_INPUT) in headless environments so no command ever prompts; pass--yeswhere a confirmation is required.
Scripting conventions
Section titled “Scripting conventions”Three flags and one exit-code vocabulary behave the same way on every command that has them.
--output always means the scripting format
Section titled “--output always means the scripting format”--output text|json|ndjson selects the envelope the command writes to stdout — never a file format, never a destination. json emits exactly one document per run (a batch of card lookups or a multi-page search is still one array); ndjson streams one JSON object per line; text is the human rendering. Three commands widen or drop the value list rather than redefining the flag: scry adds --output csv (Scryfall renders the CSV itself), sell adds --output csv too (a different payload — Card Kingdom’s sell-cart upload rows, not the report re-rendered), and export has no --output at all — its stdout payload is the export, chosen with --format csv|json|text|md and redirected with --out <file>.
Errors always go to stderr, structured to match: {"error": {"code", "messageKey", "messageParams", "message", "details"}} under json/ndjson, a plain line under text. code and messageKey are locale-invariant — match on those; message is prose that follows the UI locale (messageKey/messageParams are omitted when the failure has no message-catalog entry behind it, such as an error quoted verbatim from Scryfall or the filesystem). stdout stays parseable in every case.
--quiet suppresses chatter, never data
Section titled “--quiet suppresses chatter, never data”--quiet removes progress and status messages — “Reading cards from file…”, “Successfully imported…”, applied counts, confirmations. It never removes:
- the structured payload:
--output json/ndjsonstdout is always emitted, so--quiet --output jsonis a JSON document with no chatter around it. Scripts that want total silence redirect stdout. - errors, and warnings that mean content was lost — a card line the parser could not read, a change skipped as a conflict, a result set truncated by the page cap. These always print to stderr,
--quietor not, because nothing else would tell you.
A command with no non-essential output does not register the flag at all rather than advertising an inert one. card, diff, scry, cache status, dep-license, history, login status, deck-sync status, and skills list therefore accept --output but no --quiet: everything they print is either the payload or a warning that has to survive anyway.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 | Success |
1 | Runtime error — the work was attempted and failed |
2 | Usage error — a bad flag value or an impossible combination, including a malformed --base-dir or --cache-server |
3 | Not found — a named list, file, card, or other resource does not exist |
A closed stdout pipe is not a failure: ritual … --output ndjson | head stops quietly and exits 0, like any standard Unix tool.