yafette
v0.1.0
Published
Open-access art in your terminal — find one exact work (never a guess), search 11,500 public-domain works from 13 museums, and download full-resolution files with their rights attached. Built for coding agents.
Maintainers
Readme
yafette
Open-access art in your terminal — 11,500 public-domain works from 13 museums (the Met, the Rijksmuseum, the National Gallery of Art, the Getty, the V&A, Wikimedia Commons and more), one catalogue, no key. Built so a coding agent can pull one exact work and never a guess.
npx yafette get "The Starry Night" --artist "van Gogh" # one work, exit 0
npx yafette get "Night Watch" # finds the Rijksmuseum's Nachtwacht
npx yafette get Sunflowers --artist "van Gogh" # several versions → a numbered list, exit 2
npx yafette search cypress --artist "van gogh" --pd-only # ranked, forgiving
npx yafette pull wikidata:Q45585 --out ./art # the museum's file + a .json rights sidecarThe catalogue is published by yafette.com and downloaded once (about 2 MB) into your cache directory; every command then runs locally — no server, no rate limit, works offline.
Install
npm install -g yafette # installs the `yafette` command
# or run one-off with npx yafette — nothing to installRequires Node 18.17 or newer. No runtime dependencies.
For coding agents
get is the command to reach for when a person names a work. It answers in exactly one of
three ways, and the exit code says which:
| exit | meaning | what to do |
|---:|---|---|
| 0 | one work — work in the JSON | use it |
| 2 | several fit — candidates in the JSON | show the list, or narrow with --museum, --year, --qid, or yafette get <id> |
| 4 | nothing fits — didYouMean in the JSON | the suggestions are search results, not answers; ask, or use search |
get never picks from an ambiguous list for you. Titles match exactly (case, accents,
punctuation and a leading article aside) in every language the museums use, and studies,
copies and fragments are set aside unless you pass --role any. Prints are always a list
until a museum or an id names the impression. search is the forgiving one: ranked,
prefix and typo tolerant, alias-aware (nachtwacht finds The Night Watch).
yafette get "The Oath of the Horatii" --json --fields id,title,image,licence,web
yafette get wikidata:Q45585 --json # ids and Q-numbers resolve too
yafette search "still life" --category post-impressionism --json --limit 10
yafette pull --from-search "self-portrait" --artist rembrandt --limit 5 --out ./rembrandtAdd --json to any command for a machine-readable body ({ "ok": true, … } or
{ "ok": false, "error": { "code", "message", "hint" } }), --format md for a markdown
table, --fields a,b.c to trim a record, --quiet for ids only. A ready-made skill file
for Claude Code and similar tools is in skills/yafette/SKILL.md.
Commands
| Command | What it does |
|---|---|
| get <title \| id \| Qnnn> | Exactly one work, or a list to choose from. Filters: --artist --year 1889\|1885..1890 --museum --medium --kind --qid --role any |
| search <words…> | Ranked search over titles (every language), artists, tags, museums. Same filters, plus --category <slug> --pd-only --limit --offset |
| categories [--family <slug>] | The 16 families and 974 categories, with approximate counts |
| category <slug> | The works in one category (--limit --offset) |
| artists / artist <key \| Qnnn> | Who is in the catalogue |
| random | One work by chance (--artist --category --open) |
| pull <id…> | The museum's own file and a rights sidecar. --out <dir> --size full\|display\|thumb --from-search "<q>" --stdin --no-sidecar --overwrite --allow-non-pd --allow-host <h> |
| open <id> [--site] | The museum's page, or the yafette page |
| cache status\|refresh\|clear\|path | The local catalogue |
| doctor | Node version, cache, data URL reachability |
Categories are approximate. A category holds the works that name it and every work by
a related artist — the same rule the website uses. Every category response says so
("approximate": true). Use get when you need one particular work.
What a record holds
{
"id": "rijksmuseum:200107928", "qid": "Q219831",
"title": "The Night Watch",
"titles": ["The Night Watch", "Het korporaalschap van kapitein Frans Banninck Cocq … 'Nachtwacht'"],
"artist": "Rembrandt", "artistQid": "Q5598", "artistDates": "1606–1669",
"role": "original", "kind": "painting", "attribution": "firm",
"date": "1642", "year": [1642, 1642], "medium": "canvas",
"museum": "Rijksmuseum", "museums": ["Rijksmuseum"],
"pd": true, "licence": { "label": "Public Domain Mark 1.0", "url": "…" },
"image": { "full": "https://iiif.micr.io/PJEZO/full/max/0/default.jpg", "thumb": "…", "iiif": "…", "blocked": false, "via": "rijksmuseum" },
"web": "https://www.rijksmuseum.nl/…",
"members": [ { "id": "rijksmuseum:200107928", … }, { "id": "wikidata:Q219831", … } ]
}One record is one work. When the same painting reaches the index from its museum and
again from Wikidata, the copies are merged; members lists every record, titles every
title, and any member id resolves to the work. role says what the record is to the work
it names: original, version, study, copy, fragment, or impression (one
impression of a print). --json gives the compact record; --json --full the whole thing.
Rights
Only works their museum publishes as public domain or CC0 are offered (pd: true); a
Wikimedia Commons photograph is offered only when the file's own Commons page says public
domain or CC0. pull refuses anything else unless you pass --allow-non-pd, and writes a
.json sidecar beside every file naming the museum, the record, the rights statement,
the licence and the source URLs, so provenance travels with the file. Images stream from
the museums' own servers; the Art Institute of Chicago serves its files only to a browser
on its own site, so those records give you the page instead. Verify before commercial
reuse — institutions revise their records.
Security
- Zero runtime dependencies — the tool is Node built-ins and its own source.
- HTTPS only. The catalogue comes from one fixed origin and every file is checked against the hash the manifest publishes.
- Images only from the museums' hosts (an allowlist). Europeana points at thousands of
provider servers, so those need
--allow-host <host>. - Filenames are sanitised and kept inside
--out; existing files are never overwritten without--overwrite. - No telemetry. Nothing is written outside the cache and output directories.
Environment: YAFETTE_CACHE_DIR (default ~/.cache/yafette, %LOCALAPPDATA%\yafette on
Windows), YAFETTE_DATA_URL (the catalogue origin), YAFETTE_OFFLINE=1 (never fetch),
NO_COLOR.
Development
The search and resolve engine lives in tools/catalog-core and is copied into
src/core/ by bin/sync-core; the catalogue is built by tools/art-api
(npx tsx src/cli.ts catalog). npm test builds and runs the golden queries against
the fixture in test/fixtures/catalog/ with no network; regenerate the fixture with
npm run fixture after the catalogue's shape changes.
Licence
MIT. Museum metadata belongs to the institutions that publish it; every download names its source.
