How this skill is triggered — by the user, by Claude, or both
Slash command
/myfootmarks:find-place-imageThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Given an entity's name and category (and optionally a region, coordinates, or a pre-resolved Wikidata ID), find a single representative image from a category-aware multi-source stack and return a structured JSON envelope. Used by:
Given an entity's name and category (and optionally a region, coordinates, or a pre-resolved Wikidata ID), find a single representative image from a category-aware multi-source stack and return a structured JSON envelope. Used by:
places: (or dishes:) frontmatter.build-trip-book Stage 2 (fetch-assets) — invoked in validate-mode for pre-resolved Wikidata IDs and in fresh-resolve-mode when no ID is present./myfootmarks:find-place-image — diagnostic, read-only, prints a trace + envelope.name: <string> # required — entity name
category: <string> # required — free-form (e.g., "garden", "restaurant-fine-dining", "concert", "dish")
region: <string> # optional — e.g., "Victoria, BC"; used for region verification + Openverse query
coordinates: [<lat>, <lon>] # optional — tie-breaker only
wikidata_id: <Qnnnnn> # optional — when present, runs validate-mode instead of fresh-resolve
When invoked as a slash command, the user passes these as a JSON or YAML block in the prompt body.
{
"status": "found" | "no-image",
"archetype": "destination" | "landmark" | "restaurant" | "event" | "dish" | "seasonal" | "neighborhood",
"source": "rest-summary" | "wikidata-verified" | "wikivoyage-banner" | "openverse" | "commons-file-search" | "commons-category" | "subpage-og-image" | "homepage-og-image" | null,
"wikidata_id": "Qnnnnn" | null,
"commons_filename": "..." | null,
"image_url": "https://...",
"author": "...",
"license": "...",
"source_page_url": "...",
"confidence": "high" | "medium" | "low" | null,
"match_reason": "rest-title-match" | "wikidata-name+category+region" | "wikivoyage-banner-extract" | "openverse-cc-match" | "commons-file-image" | "commons-category-first" | "subpage-og-link-text-match" | "homepage-og-filtered" | "category-mismatch" | "region-mismatch" | "no-p18-on-verified-entity" | "no-match",
"rejected_candidates": [ { "source": "...", "reason": "..." } ]
}
For status: "no-image", source and image_url are null; match_reason carries the exhaustion reason; rejected_candidates summarizes what was tried.
references/category-to-archetype.md — free-form category → one of seven archetypes.references/category-to-p31.md — archetype → canonical Wikidata P31 Q-IDs for verification.references/og-image-heuristics.md — filename reject list + sub-page link-text matching rules.references/openverse-query.md — query construction + result filtering.references/attribution-extract.md — Commons imageinfo.extmetadata, Openverse field mapping, og:image domain credit fallback.references/user-agent.md — Wikimedia UA policy + exact UA string.| Archetype | Source stack (in priority order) |
|---|---|
destination | wikivoyage-banner → rest-summary → wikidata-verified → openverse → commons-file-search |
landmark | rest-summary → wikidata-verified → openverse → commons-file-search → homepage-og-image |
restaurant | openverse → subpage-og-image → commons-file-search → homepage-og-image |
event | subpage-og-image → commons-file-search → openverse → homepage-og-image |
dish | openverse → commons-category |
seasonal | openverse → commons-category |
neighborhood | (treated as destination) |
destination deliberately omits homepage-og-image (Wikivoyage banners cover the surface). Unfamiliar categories fall back to the landmark stack with confidence: "low".
When the input category resolves to the neighborhood archetype, the resolver runs the destination source stack but returns archetype: "neighborhood" in the envelope (input-faithful, not execution-faithful). Stage 2 consumers can filter on the input archetype without losing neighborhood entities to a destination alias.
If the caller supplied wikidata_id: run validate-mode (Step V below).
Otherwise: run fresh-resolve mode (Steps 1–N below).
Map category → archetype per references/category-to-archetype.md.
Fetch P18, P31, P131, P17 for the supplied Q-ID:
curl -sS -A "myfootmarks/0.0.1 ([email protected])" \
"https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${wikidata_id}&props=claims|labels&format=json"
Check the candidate's P31 against the archetype's canonical P31 set (references/category-to-p31.md). If no overlap → return:
{ "status": "no-image", "archetype": "<X>", "wikidata_id": "<Q...>", "match_reason": "category-mismatch", "rejected_candidates": [{"source": "wikidata-verified", "reason": "P31 ${actual} ∉ archetype canonical set"}] }
Check P131/P17 against the trip's region (if provided). If region was provided AND verification fails → return status: "no-image", match_reason: "region-mismatch". If region was NOT provided → skip region check, downgrade confidence to "medium".
If P18 is absent on the verified entity → return status: "no-image", match_reason: "no-p18-on-verified-entity".
Otherwise: extract P18 filename → construct Commons thumbnail URL → fetch attribution per references/attribution-extract.md §1 → return:
{ "status": "found", "archetype": "<X>", "source": "wikidata-verified", "wikidata_id": "<Q...>", "commons_filename": "<file>", "image_url": "<thumb-url>", "author": "...", "license": "...", "source_page_url": "https://commons.wikimedia.org/wiki/File:<file>", "confidence": "high", "match_reason": "wikidata-name+category+region", "rejected_candidates": [] }
Validate-mode never falls through to fresh-resolve. If the caller's Q-ID is wrong, returning no-image is the right outcome — the caller (Stage 2 of build-trip-book) will, on a subsequent run with the bad ID cleared from research output, fall through to fresh-resolve naturally.
Map category → archetype per references/category-to-archetype.md. For each source in the archetype's stack (in order), run the source's resolution. Stop at the first found result. If the entire stack exhausts → return status: "no-image", match_reason: "no-match", rejected_candidates populated.
wikivoyage-banner (destination archetype)curl -sS -A "myfootmarks/0.0.1 ([email protected])" \
"https://en.wikivoyage.org/wiki/${name_url_encoded}?action=raw"
filename=$(echo "$wv_page" | grep -oE -i '\{\{\s*pagebanner\s*\|\s*[^|}]+' | head -1 | sed -E 's/.*\|\s*//')
{source: "wikivoyage-banner", reason: "no-pagebanner-template"}, continue.skills/build-trip-book/SKILL.md Stage 2's "Compute the Commons thumbnail URL" step for the canonical formula.references/attribution-extract.md §1.source: "wikivoyage-banner", confidence: "high", match_reason: "wikivoyage-banner-extract".rest-summary (destination + landmark archetypes)region was provided:
# Try "Butchart Gardens, British Columbia" first, then bare "Butchart Gardens"
for title_candidate in "${name}, ${region}" "${name}"; do
enc=$(jq -rn --arg t "$title_candidate" '$t | @uri')
resp=$(curl -sS -A "myfootmarks/0.0.1 ([email protected])" \
"https://en.wikipedia.org/api/rest_v1/page/summary/${enc}")
...
done
originalimage.source over thumbnail.source. If neither exists → continue to next title or record rejected.image/*.https://upload.wikimedia.org/.../File.jpg → File.jpg).references/attribution-extract.md §1.source: "rest-summary", confidence: "high", match_reason: "rest-title-match".wikidata-verified (destination + landmark archetypes)curl -sS -A "myfootmarks/0.0.1 ([email protected])" \
"https://www.wikidata.org/w/api.php?action=wbsearchentities&search=${name_url_encoded}&language=en&limit=5&format=json"
curl -sS -A "myfootmarks/0.0.1 ([email protected])" \
"https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${candidate_qid}&props=claims|labels&format=json"
source: "wikidata-verified", confidence: "high", match_reason: "wikidata-name+category+region".rejected_candidates listing each Q-ID + rejection reason; continue.openverse (most archetypes)references/openverse-query.md.source: "openverse", confidence from references/openverse-query.md (medium for most; high for dish/seasonal where Openverse is dominant), match_reason: "openverse-cc-match".commons-file-search (most archetypes, MIME-filtered)curl -sS -A "myfootmarks/0.0.1 ([email protected])" \
"https://commons.wikimedia.org/w/api.php?action=query&list=search&srnamespace=6&srsearch=${name_url_encoded}&srlimit=5&format=json"
imageinfo.mime. Reject any non-image/* (Commons returns PDFs of historical newspapers for common-noun queries — must filter).references/attribution-extract.md §1 → return source: "commons-file-search", confidence: "medium", match_reason: "commons-file-image".commons-category (dish + seasonal archetypes)curl -sS -A "myfootmarks/0.0.1 ([email protected])" \
"https://commons.wikimedia.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:${term_url_encoded}&cmtype=file&cmlimit=5&format=json"
commons-file-search.source: "commons-category", confidence: "medium", match_reason: "commons-category-first".subpage-og-image (event + restaurant archetypes)Apply the link-text matching algorithm in references/og-image-heuristics.md §2. On hit → fetch the sub-page → extract its <meta property="og:image"> → apply filename reject filter → return source: "subpage-og-image", confidence: "medium", match_reason: "subpage-og-link-text-match". Attribution per references/attribution-extract.md §3.
homepage-og-image (last-resort for landmark + restaurant + event)Fetch the venue homepage with browser-like UA + Referer. Extract <meta property="og:image">. Apply filename reject filter (references/og-image-heuristics.md §1). Return source: "homepage-og-image", confidence: "low" ALWAYS, match_reason: "homepage-og-filtered". Attribution per references/attribution-extract.md §3.
When invoked as /myfootmarks:find-place-image (slash command), print a human-readable trace BEFORE the JSON envelope:
[find-place-image] entity: <name>
[find-place-image] category: <category>
[find-place-image] resolved archetype: <X> (matched | fallback)
[find-place-image] running stack: <source1> → <source2> → ...
[find-place-image] <source1>: <hit | miss | rejected — reason>
[find-place-image] <source2>: <hit | miss | rejected — reason>
[find-place-image] result: <found | no-image>
[find-place-image] envelope:
{ "status": ..., "source": ..., ... }
The trace is the diagnostic surface — it's how the trip planner sees why a specific place resolved (or didn't) the way it did. The trace MUST cite the reference file when a decision was driven by it (e.g., [find-place-image] homepage-og-image: rejected — filename matches reject filter (og-image-heuristics.md §1)).
The slash command is read-only: it does not write book-data.json, asset-manifest.json, or any other file. The JSON envelope is printed to stdout for the user to inspect.
references/user-agent.md.references/user-agent.md.| Failure | Behavior |
|---|---|
| HTTP 429 | sleep 30s, retry once |
| HTTP 5xx / timeout | retry once after 10s; on second failure, record rejected, continue |
| HTTP 403 / 404 | record rejected, continue |
| Stack exhausted | return status: "no-image", match_reason: "no-match", rejected_candidates populated |
The resolver NEVER throws an exception or returns an undefined envelope — every code path produces a valid JSON envelope with status of either found or no-image.
Cache the entire envelope keyed on (name, category, region, wikidata_id?) tuple under .myfootmarks/trips/<slug>/cache/find-place-image/<hash>.json when invoked from Stage 2. The cache key is the SHA-1 of the canonicalized JSON {name, category, region, wikidata_id?} — including the optional Q-ID matters because validate-mode and fresh-resolve produce different envelopes for the same (name, category, region) triple. Slash-command invocations are NOT cached (diagnostic, by design).
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub escapevelocitylabs/myfootmarks --plugin myfootmarks