- Migrated data files to 'data/collection/' and 'data/decks/'. - Moved 'card_cache.json' to 'cache/'. - Reorganized 'collection_hydrated/' and 'deck_analysis.json' into 'output/'. - Updated 'hydrate.py' and script defaults to match the new paths. - Updated 'README.template.md' and 'AGENTS.template.md' templates. - Regenerated 'README.md' and 'AGENTS.md'.
368 lines
10 KiB
Markdown
368 lines
10 KiB
Markdown
# MTG EDH Deck Manager
|
|
|
|
Command-line toolkit for managing Magic: The Gathering Commander (EDH) decks using the Scryfall API.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
git clone <repo-url>
|
|
cd Decks
|
|
|
|
# Install git hook for auto-updating docs
|
|
cp scripts/pre-commit .git/hooks/pre-commit
|
|
chmod +x .git/hooks/pre-commit
|
|
```
|
|
|
|
No dependencies required - pure Python 3 standard library.
|
|
|
|
## Usage
|
|
|
|
### Hydrate a Collection
|
|
|
|
Fetch card data from Scryfall for a decklist:
|
|
|
|
```bash
|
|
python hydrate.py hydrate data/collection/decklist.txt -o output/hydrated/ -c cache/card_cache.json
|
|
```
|
|
|
|
### Create a New Deck
|
|
|
|
```bash
|
|
python hydrate.py new my_deck
|
|
```
|
|
|
|
### Analyze Decks
|
|
|
|
Find upgrade options from your collection:
|
|
|
|
```bash
|
|
python scripts/analyze_decks.py --collection output/hydrated/deck.json --deck-dir data/decks/
|
|
```
|
|
|
|
### Find Synergies
|
|
|
|
Search for cards by keywords, colors, and type:
|
|
|
|
```bash
|
|
# Find landfall cards in Simic colors
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --colors U G --keywords landfall
|
|
|
|
# Find Bird creatures in Bant colors
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --colors U G W --creature-type Bird
|
|
|
|
# Find instants with CMC 2 or less
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --type instant --cmc-max 2
|
|
```
|
|
|
|
### Generate Reports
|
|
|
|
```bash
|
|
python scripts/deck_report.py --collection output/hydrated/deck.json --decks-dir data/decks/ --output output/report.md
|
|
```
|
|
|
|
## Decks
|
|
|
|
| Deck | Colors | Commander | Archetype |
|
|
|------|--------|-----------|-----------|
|
|
| Choco | UGW | Choco, Seeker of Paradise | Bird Tribal Landfall |
|
|
| Hazel | BG | Hazel of the Rootbloom | Golgari Aristocrats |
|
|
| Palamecia | UR | The Emperor of Palamecia // The Lord Master of Hell | Izzet Self-Mill Storm |
|
|
| Yshtola | UBW | Y'shtola, Night's Blessed | Esper Stax Drain |
|
|
|
|
## Workflow
|
|
|
|
### Quick Reference
|
|
|
|
1. **Import** - Place text decklists in `data/collection/`
|
|
2. **Hydrate** - Run `hydrate.py` to fetch Scryfall data → `output/hydrated/`
|
|
3. **Explore** - Find synergies and commanders in your collection
|
|
4. **Define** - Create deck JSON files in `data/decks/`
|
|
5. **Analyze** - Run `analyze_decks.py` to find upgrade options
|
|
6. **Report** - Use `deck_report.py` for markdown summaries
|
|
|
|
---
|
|
|
|
## Runbook: Build a Deck From Your Collection
|
|
|
|
### Step 1: Import Your Collection
|
|
|
|
Create a text file in `data/collection/` with your cards (one per line):
|
|
|
|
```
|
|
1 Sol Ring (CMA) 1
|
|
3 Birds of Paradise (M12) 165
|
|
1 Eternal Witness (EMA) 183
|
|
```
|
|
|
|
Format: `<count> <Card Name> (<Set Code>) <Collector Number>`
|
|
|
|
The set code and collector number are optional but help with accuracy.
|
|
|
|
### Step 2: Hydrate Your Collection
|
|
|
|
Fetch card data from Scryfall:
|
|
|
|
```bash
|
|
python hydrate.py hydrate data/collection/my_cards.txt -o output/hydrated/ -c cache/card_cache.json
|
|
```
|
|
|
|
This creates:
|
|
- `output/hydrated/deck.json` - All cards with full data
|
|
- `output/hydrated/creatures.json` - Creatures only
|
|
- `output/hydrated/instants.json`, `sorceries.json`, etc.
|
|
|
|
### Step 3: Explore Your Collection
|
|
|
|
**View color distribution:**
|
|
```bash
|
|
python -c "
|
|
import json
|
|
from collections import Counter
|
|
cards = json.load(open('output/hydrated/deck.json'))
|
|
colors = Counter(c for card in cards for c in card.get('color_identity', []))
|
|
for c, n in colors.most_common(): print(f'{c}: {n}')
|
|
"
|
|
```
|
|
|
|
**Find creature types you own:**
|
|
```bash
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --creature-type Elf
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --creature-type Elemental
|
|
```
|
|
|
|
**Find cards by keyword/mechanic:**
|
|
```bash
|
|
# Find all landfall cards
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --keywords landfall
|
|
|
|
# Find lifegain synergies
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --keywords "gain life"
|
|
|
|
# Find -1/-1 counter cards
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --keywords "-1/-1 counter"
|
|
```
|
|
|
|
**Find cards by color identity:**
|
|
```bash
|
|
# Simic (UG) cards
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --colors U G
|
|
|
|
# Esper (WUB) instants
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --colors W U B --type instant
|
|
```
|
|
|
|
### Step 4: Choose a Commander
|
|
|
|
Based on your collection strengths, pick a commander that matches:
|
|
- Your most abundant colors
|
|
- Creature types you have many of
|
|
- Mechanics with good support in your collection
|
|
|
|
### Step 5: Create the Deck File
|
|
|
|
Create `data/decks/<deck_name>.json`:
|
|
|
|
```json
|
|
{
|
|
"name": "My Deck Name",
|
|
"commander": "Commander Name",
|
|
"colors": ["U", "G"],
|
|
"archetype": "Simic Value",
|
|
"cards": {
|
|
"Sol Ring": 1,
|
|
"Birds of Paradise": 1,
|
|
"Eternal Witness": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 6: Analyze for Upgrades
|
|
|
|
Find cards in your collection that fit your deck:
|
|
|
|
```bash
|
|
python scripts/analyze_decks.py --collection output/hydrated/deck.json --deck-dir data/decks/
|
|
```
|
|
|
|
This compares your deck against your collection and suggests:
|
|
- Cards matching your commander's color identity
|
|
- Cards with synergistic mechanics
|
|
- Cards not yet in the deck
|
|
|
|
### Step 7: Generate a Report
|
|
|
|
```bash
|
|
python scripts/deck_report.py --collection output/hydrated/deck.json --decks-dir data/decks/ --output output/report.md
|
|
```
|
|
|
|
---
|
|
|
|
## Example: Building a Deck From Scratch
|
|
|
|
```bash
|
|
# 1. Add your collection
|
|
echo "1 Ashling, Rekindled // Ashling, Rimebound (ECL) 124" >> data/collection/my_cards.txt
|
|
|
|
# 2. Hydrate
|
|
python hydrate.py hydrate data/collection/my_cards.txt -o output/hydrated/ -c cache/card_cache.json
|
|
|
|
# 3. Find red instants/sorceries (spellslinger support)
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --colors R --type instant
|
|
python scripts/find_synergies.py --collection output/hydrated/deck.json --colors R --type sorcery
|
|
|
|
# 4. Create deck file
|
|
cat > data/decks/ashling.json << 'EOF'
|
|
{
|
|
"name": "Ashling Spellslinger",
|
|
"commander": "Ashling, Rekindled // Ashling, Rimebound",
|
|
"colors": ["R"],
|
|
"archetype": "Mono-Red Spellslinger",
|
|
"cards": {}
|
|
}
|
|
EOF
|
|
|
|
# 5. Analyze and find synergies
|
|
python scripts/analyze_decks.py --collection output/hydrated/deck.json --deck-dir data/decks/
|
|
```
|
|
|
|
---
|
|
|
|
## Runbook: Update Your Collection
|
|
|
|
### Scenario 1: Add New Cards (Single File)
|
|
|
|
If you opened new packs and want to add cards:
|
|
|
|
```bash
|
|
# 1. Create a new file with the new cards
|
|
cat > data/collection/new_cards_2026-02-20.txt << 'EOF'
|
|
1 Bre of Clan Stoutarm (ECL) 8
|
|
3 Mulldrifter (ECC) 67
|
|
1 Sol Ring (ECC) 57
|
|
EOF
|
|
|
|
# 2. Hydrate the new file (merges into existing cache)
|
|
python hydrate.py hydrate data/collection/new_cards_2026-02-20.txt -o output/hydrated/ -c cache/card_cache.json
|
|
```
|
|
|
|
### Scenario 2: Add Cards to Existing File
|
|
|
|
```bash
|
|
# Append to existing collection file
|
|
echo "1 The Reaper, King No More (ECC) 4" >> "data/collection/Box1 2026-01-30.txt"
|
|
|
|
# Re-hydrate that file
|
|
python hydrate.py hydrate "data/collection/Box1 2026-01-30.txt" -o output/hydrated/ -c cache/card_cache.json
|
|
```
|
|
|
|
### Scenario 3: Merge Multiple Collection Files
|
|
|
|
If you have multiple files to combine:
|
|
|
|
```bash
|
|
# Hydrate each file separately (cache persists)
|
|
python hydrate.py hydrate data/collection/Box1.txt -o output/hydrated/ -c cache/card_cache.json
|
|
python hydrate.py hydrate data/collection/Box2.txt -o output/hydrated/ -c cache/card_cache.json
|
|
|
|
# Note: Each run overwrites deck.json. To merge, combine files first:
|
|
cat data/collection/Box1.txt data/collection/Box2.txt > data/collection/combined.txt
|
|
python hydrate.py hydrate data/collection/combined.txt -o output/hydrated/ -c cache/card_cache.json
|
|
```
|
|
|
|
### Scenario 4: Re-hydrate Entire Collection
|
|
|
|
If cache is corrupted or you want fresh data:
|
|
|
|
```bash
|
|
# Option A: Keep cache, just re-run
|
|
python hydrate.py hydrate data/collection/combined.txt -o output/hydrated/ -c cache/card_cache.json
|
|
|
|
# Option B: Clear cache and fetch fresh (slow, ~100ms per unique card)
|
|
rm cache/card_cache.json
|
|
python hydrate.py hydrate data/collection/combined.txt -o output/hydrated/ -c cache/card_cache.json
|
|
```
|
|
|
|
### Scenario 5: Update After Set Release
|
|
|
|
When a new set releases and you want to add those cards:
|
|
|
|
```bash
|
|
# 1. Add new set cards to collection file
|
|
echo "# Lorwyn Eclipsed - 2026-01-23" >> data/collection/my_collection.txt
|
|
cat data/collection/new_ecl_cards.txt >> data/collection/my_collection.txt
|
|
|
|
# 2. Hydrate (cache speeds up existing cards, fetches new ones)
|
|
python hydrate.py hydrate data/collection/my_collection.txt -o output/hydrated/ -c cache/card_cache.json
|
|
|
|
# 3. Verify new cards are present
|
|
python -c "
|
|
import json
|
|
cards = json.load(open('output/hydrated/deck.json'))
|
|
print(f'Total unique cards: {len(cards)}')
|
|
"
|
|
```
|
|
|
|
### Collection File Format
|
|
|
|
```
|
|
# Comments start with #
|
|
# Format: <count> <Card Name> (<Set Code>) <Collector Number>
|
|
|
|
1 Sol Ring (CMA) 1
|
|
3 Birds of Paradise
|
|
1 Eternal Witness (EMA) 183
|
|
|
|
# Set code and collector number are optional but recommended
|
|
# They help Scryfall find the exact printing
|
|
```
|
|
|
|
### Troubleshooting
|
|
|
|
**Card not found:**
|
|
```
|
|
Error fetching 'Some Card Name': HTTP 404
|
|
```
|
|
→ Check spelling, or try without set code. Use the exact name from Scryfall.
|
|
|
|
**Duplicate cards:**
|
|
If the same card appears multiple times in your file, they're stored separately. Dedupe first:
|
|
```bash
|
|
# Combine duplicates
|
|
python -c "
|
|
from collections import Counter
|
|
cards = Counter()
|
|
with open('data/collection/my_cards.txt') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line and not line.startswith('#'):
|
|
parts = line.split(None, 1)
|
|
if parts:
|
|
count = int(parts[0]) if parts[0].isdigit() else 1
|
|
name = parts[1] if len(parts) > 1 else parts[0]
|
|
cards[name.split('(')[0].strip()] += count
|
|
for name, count in sorted(cards.items()):
|
|
print(f'{count} {name}')
|
|
" > data/collection/deduped.txt
|
|
```
|
|
|
|
**Slow hydration:**
|
|
- First run fetches all cards (~100ms each)
|
|
- Subsequent runs use cache (instant)
|
|
- 866 unique cards ≈ 87 seconds first run
|
|
|
|
## Collection Stats
|
|
|
|
- **Total cards:** 1209
|
|
- **Unique cards:** 866
|
|
- **By type:**
|
|
- Artifacts: 84
|
|
- Creatures: 390
|
|
- Enchantments: 63
|
|
- Instants: 124
|
|
- Lands: 97
|
|
- Planeswalkers: 2
|
|
- Sorceries: 105
|
|
|
|
---
|
|
|
|
*This file is auto-generated. Edit `docs/templates/README.template.md` instead.* |