skills/boardgamegeek/SKILL.md
Interact with the BoardGameGeek XML API2. Search games, fetch details with ratings and weight, pull a user's collection or wishlist, download box art from the CDN. Covers the 2026 auth reality (registered application token, Bearer header, 401 without one, approval takes a week or more) plus a browser fallback via the GEEK.geekitemPreload JSON while approval is pending. Reads BGG_XML_API_TOKEN from ~/.claude/.env. Triggers on "boardgamegeek", "bgg", "fetch my BGG collection", "look up a board game", "bgg search", "download board game images".
npx skillsauth add RonanCodes/ronan-skills boardgamegeekInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
BoardGameGeek (BGG) is the canonical database for board games: every game has a numeric id, community ratings, complexity weight, player-count polls, and box art. BGG exposes this over the XML API2 at https://boardgamegeek.com/xmlapi2/. Responses are XML; this skill ships a stdlib-only Python client (scripts/bgg.py) that converts them to clean JSON.
The one thing to know before anything else: since 2026 every XML API endpoint returns 401 Unauthorized without a registered application token. That includes /thing, /search, /collection, /hot, and the legacy /xmlapi. There is no anonymous API access anymore. Facts below verified 2026-07-03.
/applications/create). Describe the application and whether it is commercial.Authorization: Bearer <token> (the word "Bearer", one space, the token; no colon). Tokens currently do not expire and there is no refresh flow.https://boardgamegeek.com WITHOUT a leading www, or auth fails.Current state for Ronan: an application named "Oostenburg Board Game Club Tools" (non-commercial) was submitted on 2026-07-03 and is pending approval. Until it lands, API calls return 401 and the browser fallback below is the working path.
Documented exceptions to registration: downloading YOUR OWN collection while logged in on the site needs no registration; fetching other users' collections while logged in works but is heavily rate limited.
Usage monitoring once live: https://boardgamegeek.com/applications, "Usage" under the application.
Store the token in the shared env file ~/.claude/.env (section headers per plugin, one file):
# --- BoardGameGeek ---
BGG_XML_API_TOKEN=<token from boardgamegeek.com/applications>
scripts/bgg.py reads it from there (tolerates quotes, comments, and export prefixes). If the key is unset or the API answers 401, the script prints the pending-registration explanation and exits 2. That is expected behaviour until approval.
flowchart TD
A["Need BGG data"] --> B{"BGG_XML_API_TOKEN<br/>in ~/.claude/.env?"}
B -- yes --> C["scripts/bgg.py<br/>XML API2 + Bearer header"]
C --> D{"HTTP status"}
D -- 200 --> E["JSON out"]
D -- 202 (collection queued) --> F["retry 2s, 5s, 10s"] --> C
D -- 401 --> G["token invalid or app<br/>still pending approval"]
B -- no --> G
G --> H["Browser fallback:<br/>GEEK.geekitemPreload JSON<br/>(own/public data only)"]
A -.box art only.-> I["cf.geekdo-images.com<br/>plain curl + browser UA,<br/>no auth needed"]
Base: https://boardgamegeek.com/xmlapi2/. All return XML.
| Endpoint | What it gives | Notes |
|---|---|---|
| /thing?id=<comma-separated-ids>&stats=1 | Game details: name, yearpublished, minplayers/maxplayers, playingtime, image + thumbnail URLs; with stats=1 also ratings average, averageweight (complexity), ranks | Batch ids in one call; keep batches modest (~20) |
| /search?query=<q>&type=boardgame | Name search returning ids | Add &exact=1 for exact-name match |
| /collection?username=<u>&own=1&stats=1 | A user's collection | See the 202 contract below. Other flags: wishlist=1, want=1, played=1, subtype=boardgame, excludesubtype=boardgameexpansion |
| /hot?type=boardgame | Current hotness list | |
| /user?name=<u> | User profile | |
| /plays?username=<u> | Logged plays | Paged |
The 202-retry contract on /collection: the first request for a collection usually returns HTTP 202 with an empty-ish body, meaning "queued, come back". Retry the same URL with backoff (2s, 5s, 10s) until it returns 200 with the real payload. scripts/bgg.py does this automatically.
/thing calls./thing response is fine for almost everything.Run from the skill directory (or use the full path to scripts/bgg.py):
python3 scripts/bgg.py search "brass birmingham"
python3 scripts/bgg.py thing 224517 167791 --stats
python3 scripts/bgg.py collection <username> --own --stats --exclude-expansions
python3 scripts/bgg.py hot
python3 scripts/bgg.py image 224517 --out ./boxart
Output is pretty JSON. thing items carry: id, name, year, players.min/max, playingtime, rating.average, rating.weight, rating.rank, image, thumbnail. Exit codes: 0 ok, 1 API or parse error, 2 auth (401, registration pending).
BGG game pages and public collection/wishlist pages render without login and embed the full item JSON in the HTML as GEEK.geekitemPreload. Plain curl gets a Cloudflare 403; a real browser (Playwright MCP or Claude in Chrome) passes. Verified working 2026-07-03.
Recipe:
fetch() calls to the game page URLs with 400 to 500ms delays between them.GEEK.geekitemPreload out of each response body. Useful fields: item.name, yearpublished, minplayers, maxplayers, minplaytime, maxplaytime, stats.average, stats.avgweight, rankinfo[0].rank, polls.userplayers.best / recommended, imageurl, images.original.For collection pages, the HTML itself is the data: rows match #collectionitems tr[id^="row_"], the game link is td.collection_objectname a (its href contains /boardgame/<id>/), the user rating is .collection_rating .ratingtext, and ownership status is td.collection_status.
This is a stopgap for your own or public data, not a substitute for registering.
The image CDN cf.geekdo-images.com is NOT auth-gated. Plain curl with a browser User-Agent downloads box art fine, token or no token. Convention: python3 scripts/bgg.py image <id> --out <dir> resolves the image URL via /thing, then saves to <dir>/<id>-<slug>.jpg (slug from the game name). If the API is still 401, take imageurl / images.original from the browser fallback and curl it directly with -A "Mozilla/5.0 ...".
Fetch a collection into JSON:
python3 scripts/bgg.py collection <username> --own --stats > collection.json
The script handles the 202 queue-retry. Add --wishlist for wishlist items, --exclude-expansions to drop expansions.
Resolve a list of game names to ids + stats:
python3 scripts/bgg.py search "ark nova" # take the id from the results
python3 scripts/bgg.py thing 342942 --stats # full details, batched ids fine
For many names, search each (2s apart), collect ids, then one batched thing --stats call.
Cache box art locally:
for id in 224517 342942 167791; do
python3 scripts/bgg.py image "$id" --out ./boxart
sleep 2
done
testing
--- name: linear-pipeline description: The Fable orchestrator for a single dispatched Linear ticket. Holds almost no context itself; it receives `--issue <ID> --detached`, decides the stage sequence, and fans out a sub-agent per stage, passing forward only each stage's artifact (never re-derived, never inlined into its own context). Step zero, before any planning or stage routing, is a boundary triage against `canon/security-boundary.md` (#199): a match tags Ronan Connolly and stops the run, no
development
--- name: in-your-face description: Capture a chat-only answer into a durable artifact (markdown + HTML, PDF when cheap) and launch it automatically so the user cannot miss it. Use when user says "in your face", "don't let me lose this", "save that answer", "make that durable", or right after answering a substantive side question (a recipe, comparison, how-to, or generated prompt) that would otherwise die with the context. category: workflow argument-hint: [--no-open] [--vault <short>] [hint of
tools
One-shot headless OpenAI Codex CLI calls for background/admin AI tasks — summaries, classification, extraction, admin glue. The default engine for anything that runs AI constantly in the background (daemon-driven, per-event), because it bills the flat ChatGPT subscription instead of Claude usage or per-token API spend, and it keeps working while Claude is rate-limited. NEVER for coding — coding stays Claude. Use when a skill or daemon needs a cheap always-on AI call, when the user says "use codex", "ask codex", "codex as backup", or when building a background summarizer/classifier into a listener or loop. Reads auth from ~/.codex/auth.json (ChatGPT account, no API key).
research
Turn a warranty rejection, repair quote, or RMA email into a cited decision brief — legal read (NL/EU consumer law), is the part user-serviceable, live part and new-unit prices, repair-vs-DIY-vs-new economics, before-you-send-it checklist, deadlines. Use when the user pastes or screenshots a repair quote, warranty rejection, "not covered" email, onderzoekskosten fee, or asks "should I repair or replace this".