Skip to content

import-changes

Apply a change bundle exported from the public site’s in-browser editor (or the admin editor’s Export panel) to your list files. The full change list is previewed grouped by target list, and nothing is written until you confirm.

The same JSON can also be applied in the admin site (Import Changes page) and via the MCP import_change_bundle tool, all backed by the same engine.

Terminal window
./ritual import-changes <file>
ArgumentDescriptionRequired
<file>Path to the exported JSON fileYes
OptionDescriptionDefault
-y, --yesApply without asking for confirmationfalse
--output <format>Output format: text, json, or ndjsontext
--quietSuppress the preview and applied countsfalse

The file is a ritual-change-bundle JSON covering one or more lists — the Export panel’s This list scope produces a one-list bundle, and its All lists scope (available when edit mode accumulated changes across several lists) covers every edited list in the same envelope.

Before anything is applied, the command prints every pending change grouped by its target list:

🎴 Winota Stax (deck 'Winota Stax') — 2 changes
• Add Counterspell
• Remove Lightning Bolt (LEA:161) &2
📦 Main Binder (collection 'main-binder') — 1 change
• Add Sol Ring (C19:221)
? Apply 3 changes to 2 lists? › (y/N)

Pass --yes to skip the prompt (for scripts and agents). When stdin is not a terminal, prompts are disabled globally (--no-input / RITUAL_NO_INPUT), or --output json/ndjson owns stdout, --yes is required — instead of prompting, the command exits with code 2.

--quiet suppresses the preview and the per-list applied counts. It never hides skipped conflicts: a quiet run that skipped changes prints a one-line ⚠ <list>: N changes skipped (card not found: 2, not applicable to this list: 1) summary to stderr, because nothing else reports them and they do not affect the exit code. List failures are always reported on stderr too. Even without --quiet, the per-change ⚠ Skipped (card not found): … / ⚠ Skipped (not applicable to this list): … lines go to stderr, keeping stdout the applied-counts report.

Lists are applied in file order, each loaded fresh immediately before saving (so a cross-list move applied by an earlier list never conflicts with a later one):

  • Changes are re-targeted to each list’s current &N card IDs — added cards draw fresh IDs, and other changes match by ID when it still exists, otherwise by card name.
  • Changes whose target card can no longer be found — or whose action cannot apply to that list, such as a commander change aimed at a collection — are skipped and reported as conflicts; the rest still apply. A conflict’s reason is "target-not-found" or "not-applicable".
  • move-from changes also write the destination list (the card is added there, with a move-to changelog entry), exactly like an admin editor save.
  • Every list that received changes gets an entry in its .changes.md changelog — the same save path the admin editors use.

A list that fails entirely (for example, one that no longer exists) is reported without stopping the remaining lists.

The CLI command never creates git commits — applied changes are left in the working tree for you to review. The admin Import Changes page and the MCP import_change_bundle tool apply the same engine to the same bundle, and those surfaces auto-commit each saved list when admin.gitEnabled and admin.gitAutoCommit are set (see Git integration).

With --output json (or ndjson), the preview and glyph lines are replaced by a single payload on stdout after the apply — byte-for-byte the response body of the admin POST /api/import-changes route (the MCP import_change_bundle tool returns the same fields without the constant success key, which the MCP layer strips from every result), so a script can consume any of the three surfaces identically. --yes is required (there is no prompt outside text mode); without it a structured usage error is written to stderr and the command exits with code 2.

{
"success": true,
"failedCount": 0,
"lists": [
{
"kind": "deck",
"slug": "test-deck",
"name": "Test Deck",
"applied": 2,
"conflicts": [
{
"change": {
"id": "r2",
"timestamp": 3,
"action": "remove",
"cardName": "Not In Deck",
"cardId": 99
},
"reason": "target-not-found"
}
]
}
],
"message": "Applied 2 changes across 1 list"
}

A list that failed to load or save carries an error string instead of applying anything and is counted in failedCount. success stays true — it is the envelope flag, and the run was processed; failedCount (and each list’s own error) is what reports the failures. The exit code is still 1, the same as text mode.

CodeMeaning
0All lists applied (skipped conflicts do not fail the run)
1At least one list failed to apply, or the file could not be read
2Invalid change bundle, confirmation declined/cancelled, or missing --yes when prompts are unavailable (stdin is not a terminal, --no-input, or --output json/ndjson)
3Bundle file not found, or the bundle contains no changes to apply