# 5e.tools JSON Schema for Programmatic Use

Reference for scripts and automation that consume `query.py --json` output.

## Monster Schema

```json
{
  "name": "Ghost",
  "source": "MM",
  "page": 147,
  "cr": 4,
  "size": ["M"],
  "type": "undead",
  "alignment": ["L", "E"],
  "ac": 11,
  "hp": {"average": 45, "formula": "6d8+18"},
  "speed": {"walk": 0, "fly": 40},
  "str": 7, "dex": 13, "con": 10, "int": 10, "wis": 12, "cha": 17,
  "senses": "darkvision 60 ft.",
  "passive": 11,
  "languages": "any it knew in life",
  "immune": ["charmed", "frightened", "grappled", "paralyzed", "petrified", "prone", "restrained", "unconscious"],
  "resist": ["acid", "fire", ...],
  "conditionImmune": ["charmed", "frightened", ...],
  "trait": [{"name": "Ethereal Sight", "entries": [...]}],
  "action": [{"name": "Withering Touch", "entries": [...]}],
  "environment": ["dungeon", "ruins", "swamp", "urban"]
}
```

### Key field types

| Field | Type | Notes |
|-------|------|-------|
| `cr` | int or str | Use `str()` for display |
| `size` | list of str | One-letter codes: T, S, M, L, H, G |
| `type` | str or dict | If dict, use `.get("type", "")` |
| `ac` | int, list, or list of dicts | Complex; use first value for simple display |
| `hp` | dict | Always has `average` and `formula` |
| `speed` | dict or str | Common keys: `walk`, `fly`, `swim`, `burrow`, `climb` |
| `senses` | str or list | Join with `", "` if list |
| `languages` | str or list | Can be `"any"` or specific languages |
| `immune` / `resist` / `vulnerable` | list of str | Damage types or conditions |
| `trait` / `action` / `legendary` | list of dicts | Each has `name` and `entries` |

### Size code map

| Code | Size |
|------|------|
| T | Tiny |
| S | Small |
| M | Medium |
| L | Large |
| H | Huge |
| G | Gargantuan |

## Spell Schema

```json
{
  "name": "Fireball",
  "source": "PHB",
  "level": 3,
  "school": "V",
  "time": [{"number": 1, "unit": "action"}],
  "range": "150 feet",
  "components": {"v": true, "s": true, "m": "a tiny ball of bat guano and sulfur"},
  "duration": [{"type": "instant"}],
  "entries": ["A bright streak flashes..."],
  "classes": {"wizard": ["Wizard"], "sorcerer": ["Sorcerer"]}
}
```

### Key field types

| Field | Type | Notes |
|-------|------|-------|
| `level` | int | 0 = cantrip |
| `school` | str | One-letter code: A, C, D, E, I, N, T, V |
| `time` | list of dicts | `number` + `unit` |
| `components` | dict | Keys: `v`, `s`, `m` (material description) |
| `duration` | list of dicts | `type`, optional `concentration` |
| `entries` | list of str/dict | Description text; may contain nested dicts |
| `classes` | dict | Keys by class source, values are lists of class names |

## Item Schema

```json
{
  "name": "Cloak of Protection",
  "source": "DMG",
  "type": "W",
  "rarity": "uncommon",
  "value": 350000,
  "weight": 2,
  "entries": ["You gain a +1 bonus..."]
}
```

### Key field types

| Field | Type | Notes |
|-------|------|-------|
| `type` | str | Equipment type code |
| `rarity` | str | "common", "uncommon", "rare", "very rare", "legendary", "artifact" |
| `value` | int | In copper pieces |
| `weight` | int or float | In pounds |
| `entries` | list of str/dict | Description text |

## Batch Query Pattern

When running multiple queries in a loop, pipe through a Python one-liner instead of invoking the formatter:

```bash
for name in "Ghost" "Sahuagin" "Merrow"; do
    python3 query.py --type monster --json "$name" 2>/dev/null | \
        python3 -c "import sys,json; d=json.load(sys.stdin); print(d[0]['name'] if d else 'NOT FOUND')"
done
```

**Note:** This pattern may trigger security approval because it pipes downloaded content into a Python interpreter. Use it only when necessary, or write a standalone Python script that imports the query functions directly.

## Direct Import (Recommended for Automation)

Instead of shelling out to `query.py`, import its functions directly:

```python
import sys
sys.path.insert(0, "/home/thesage/.hermes/skills/dnd/5etools-query/scripts")
from query import load_all_bestiary, find_by_name

monsters = load_all_bestiary()
results = find_by_name(monsters, "Ghost")
if results:
    ghost = results[0]
    print(f"CR: {ghost['cr']}, Size: {ghost['size']}, Type: {ghost['type']}")
```
