open-museum-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/cite.d.ts +17 -0
- package/dist/cite.js +61 -0
- package/dist/cite.js.map +1 -0
- package/dist/data/dynasties.json +93 -0
- package/dist/data/regions.json +21 -0
- package/dist/dateParser.d.ts +17 -0
- package/dist/dateParser.js +265 -0
- package/dist/dateParser.js.map +1 -0
- package/dist/db.d.ts +35 -0
- package/dist/db.js +182 -0
- package/dist/db.js.map +1 -0
- package/dist/fetchers/met.d.ts +2 -0
- package/dist/fetchers/met.js +132 -0
- package/dist/fetchers/met.js.map +1 -0
- package/dist/fetchers/types.d.ts +11 -0
- package/dist/fetchers/types.js +2 -0
- package/dist/fetchers/types.js.map +1 -0
- package/dist/licenseGate.d.ts +13 -0
- package/dist/licenseGate.js +112 -0
- package/dist/licenseGate.js.map +1 -0
- package/dist/mappings.d.ts +3 -0
- package/dist/mappings.js +36 -0
- package/dist/mappings.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +243 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pramod Prasanth
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# open-museum-mcp
|
|
2
|
+
|
|
3
|
+
> Open-access museum search for MCP clients, with rights verification per museum.
|
|
4
|
+
|
|
5
|
+
## Why I built this
|
|
6
|
+
|
|
7
|
+
I kept wanting reuse-safe artwork for my writing, and every museum's rights model is different. So I built one MCP interface that only returns records that pass per-museum verification rules, with strict deny on ambiguity. It lets me search by artist, period, region, and other fields, and pulls the image and description back in one normalized shape.
|
|
8
|
+
|
|
9
|
+
If anyone else is exploring open-access art, I hope this helps. The plan is to keep adding museums from around the world.
|
|
10
|
+
|
|
11
|
+
## What you get
|
|
12
|
+
|
|
13
|
+
- **One interface, registered museums.** The Met is live. Cleveland and the Art Institute of Chicago are next.
|
|
14
|
+
- **Strict deny on ambiguity.** Records are validated against per-museum rights rules in code. Missing or unclear indicators drop the record; nothing is defaulted to "open".
|
|
15
|
+
- **Catalog-grade metadata.** A dynasty-aware date parser handles Tang, Edo, Safavid, Mughal and the rest. Regions normalize across museums. Attribution separates named artists from anonymous, workshop, "after", and attributed works.
|
|
16
|
+
- **Listable resources and deterministic citations.** `museum://{code}/{id}` resources, three citation styles, structured JSON search results.
|
|
17
|
+
|
|
18
|
+
## Quick example
|
|
19
|
+
|
|
20
|
+
A search call returns license-verified results in one normalized shape:
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
// Tool call: search_artworks({ query: "van gogh wheat", museum: "met", limit: 1 })
|
|
24
|
+
{
|
|
25
|
+
"count": 1,
|
|
26
|
+
"results": [
|
|
27
|
+
{
|
|
28
|
+
"id": "met:436535",
|
|
29
|
+
"museum": {
|
|
30
|
+
"code": "met",
|
|
31
|
+
"name": "The Metropolitan Museum of Art",
|
|
32
|
+
"url": "https://www.metmuseum.org"
|
|
33
|
+
},
|
|
34
|
+
"title": "Wheat Field with Cypresses",
|
|
35
|
+
"artist": {
|
|
36
|
+
"name": "Vincent van Gogh",
|
|
37
|
+
"nationality": "Dutch",
|
|
38
|
+
"lifespan": "1853–1890",
|
|
39
|
+
"attributionType": "named"
|
|
40
|
+
},
|
|
41
|
+
"displayDate": "1889",
|
|
42
|
+
"yearStart": 1889,
|
|
43
|
+
"yearEnd": 1889,
|
|
44
|
+
"medium": "Oil on canvas",
|
|
45
|
+
"region": "netherlands",
|
|
46
|
+
"period": null,
|
|
47
|
+
"imageUrls": { "full": "https://images.metmuseum.org/..." },
|
|
48
|
+
"imageOpenAccess": true,
|
|
49
|
+
"metadataOpenAccess": true,
|
|
50
|
+
"license": {
|
|
51
|
+
"type": "CC0",
|
|
52
|
+
"rawValue": "true",
|
|
53
|
+
"verificationSource": "met.isPublicDomain",
|
|
54
|
+
"verifiedAt": "2026-04-25T12:00:00.000Z",
|
|
55
|
+
"confidence": "high"
|
|
56
|
+
},
|
|
57
|
+
"source": {
|
|
58
|
+
"apiUrl": "https://collectionapi.metmuseum.org/public/collection/v1/objects/436535",
|
|
59
|
+
"pageUrl": "https://www.metmuseum.org/art/collection/search/436535"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install
|
|
70
|
+
npm run build
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Wire into Claude Code
|
|
74
|
+
|
|
75
|
+
Add to your MCP config (`.mcp.json` in a project, or your user-level config):
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"open-museum": {
|
|
81
|
+
"command": "node",
|
|
82
|
+
"args": ["/absolute/path/to/open-museum-mcp/dist/server.js"]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
After restarting your MCP client, the tools below become available.
|
|
89
|
+
|
|
90
|
+
## Tools
|
|
91
|
+
|
|
92
|
+
| Tool | Description |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `search_artworks(query, museum?, has_image?, limit?)` | Search across registered museums. Returns only records that pass the rights gate. |
|
|
95
|
+
| `get_artwork(id)` | Fetch a single artwork by its normalized ID (e.g. `met:436535`). |
|
|
96
|
+
| `cite(id, style?)` | Render a citation. `style`: `full` (artist, title, date, museum, license, URL), `caption` (image attribution), `short` (inline). |
|
|
97
|
+
|
|
98
|
+
### `cite` example outputs
|
|
99
|
+
|
|
100
|
+
For Van Gogh's *Wheat Field with Cypresses* (`met:436535`):
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
caption: "Vincent van Gogh, Wheat Field with Cypresses, 1889. Oil on canvas.
|
|
104
|
+
The Metropolitan Museum of Art, CC0.
|
|
105
|
+
https://www.metmuseum.org/art/collection/search/436535"
|
|
106
|
+
|
|
107
|
+
full: "Vincent van Gogh, Wheat Field with Cypresses. 1889. The Metropolitan
|
|
108
|
+
Museum of Art. CC0. https://www.metmuseum.org/art/collection/search/436535."
|
|
109
|
+
|
|
110
|
+
short: "Wheat Field with Cypresses (Vincent van Gogh, 1889)"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The `caption` style follows museum-publication convention: comma-separated head, medium called out, terse end. The `full` style is suitable for footnotes and bibliographies. The `short` style is for inline references where you've already established context.
|
|
114
|
+
|
|
115
|
+
For anonymous works (e.g. a Tang dynasty funerary vessel), the artist field becomes `"Unknown artist"` in caption form.
|
|
116
|
+
|
|
117
|
+
## Resources
|
|
118
|
+
|
|
119
|
+
- `museum://{museum_code}/{id}`: read or list any indexed artwork by URI. Listable resources let you build a per-session shortlist without re-invoking tools.
|
|
120
|
+
|
|
121
|
+
## Performance notes
|
|
122
|
+
|
|
123
|
+
- The Met API has no batch endpoint for object retrieval. A `search_artworks` call with `limit: 10` makes one search request plus up to ten parallel object fetches (eleven HTTP round trips total on a cold cache). On warm cache the search is one round trip and most objects are local.
|
|
124
|
+
- Where possible, search-side filters are pushed to the museum (`isPublicDomain=true` is sent with every Met search) so the rights gate has fewer rejections to handle.
|
|
125
|
+
- Object records are cached for 90 days (artworks don't change). Search result IDs are cached for 14 days (museums add new open-access objects regularly).
|
|
126
|
+
|
|
127
|
+
## Verification model
|
|
128
|
+
|
|
129
|
+
This is the heart of the project. Each museum exposes rights information in its own way; the server's job is to decide acceptance per museum and never default to "open" on ambiguity.
|
|
130
|
+
|
|
131
|
+
**Default policy: strict deny.** If a record's rights signal is missing, malformed, or non-affirmative, the record is dropped and the rejection reason is logged.
|
|
132
|
+
|
|
133
|
+
| Museum | Verification source | Accept condition |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| The Met | `isPublicDomain` (boolean) | `=== true` |
|
|
136
|
+
| Cleveland Museum of Art | `share_license_status` (string) | `=== "CC0"` (case-insensitive) |
|
|
137
|
+
| Art Institute of Chicago | `is_public_domain` (boolean) | `=== true` |
|
|
138
|
+
|
|
139
|
+
Each accepted record carries:
|
|
140
|
+
|
|
141
|
+
- `imageOpenAccess`: the artwork's image may be reused under the recorded license.
|
|
142
|
+
- `metadataOpenAccess`: the artwork's catalog metadata may be reused (often broader than image rights).
|
|
143
|
+
- `license.type`: normalized license tier (`CC0`, `PD`, `CC-BY`, …; v0.1 only emits `CC0`).
|
|
144
|
+
- `license.rawValue`: the museum's own field value, preserved.
|
|
145
|
+
- `license.verificationSource`: the exact museum field that was checked (e.g. `met.isPublicDomain`).
|
|
146
|
+
- `license.confidence`: `high` for unambiguous accepts (the only level v0.1 emits).
|
|
147
|
+
- `license.verifiedAt`: ISO timestamp of when this verification ran.
|
|
148
|
+
|
|
149
|
+
This is what "rights-verified" means here: validated against published museum metadata using source-specific rules implemented in this repo, with strict deny on ambiguity. It is **not** a guarantee of third-party rights beyond what each museum's API publicly represents. See [Disclaimer](#disclaimer).
|
|
150
|
+
|
|
151
|
+
## Supported museums
|
|
152
|
+
|
|
153
|
+
| Museum | Code | Auth | Status |
|
|
154
|
+
|---|---|---|---|
|
|
155
|
+
| The Metropolitan Museum of Art | `met` | none | ✅ v0.1 |
|
|
156
|
+
| Cleveland Museum of Art | `cleveland` | none | 🚧 next |
|
|
157
|
+
| Art Institute of Chicago | `aic` | none | 🚧 next |
|
|
158
|
+
| Smithsonian Open Access | `si` | API key (free) | 📋 v2 |
|
|
159
|
+
| Rijksmuseum | `rijks` | API key (free) | 📋 v2 |
|
|
160
|
+
|
|
161
|
+
## Schema
|
|
162
|
+
|
|
163
|
+
Full TypeScript definitions in [`src/types.ts`](src/types.ts). The `Artwork` shape is stable; additional fields may be added but existing fields will not be repurposed.
|
|
164
|
+
|
|
165
|
+
Highlights:
|
|
166
|
+
|
|
167
|
+
- `displayDate` (string, museum-provided) preserved alongside parsed `yearStart` / `yearEnd` (signed integers, BCE encoded as negatives).
|
|
168
|
+
- `region` and `period` normalized across museums (`china`, `japan`, `tang dynasty`, etc.).
|
|
169
|
+
- `artist.attributionType` distinguishes `named` / `anonymous` / `workshop` / `after` / `attributed` / `circle` / `follower`.
|
|
170
|
+
- `imageOpenAccess` is held distinct from `metadataOpenAccess` because museums frequently publish open metadata for objects whose images are not openly licensed.
|
|
171
|
+
|
|
172
|
+
## Non-goals
|
|
173
|
+
|
|
174
|
+
- **Not a full art-history ontology.** The dynasty and region tables cover the most-encountered cases; they are not exhaustive iconographic taxonomies.
|
|
175
|
+
- **Not a generic museum API client.** The server only returns records that pass rights verification. If you need raw, unfiltered API access, talk to the museum APIs directly.
|
|
176
|
+
- **Not a rights advisor.** The verification model establishes machine-checkable acceptance rules; final rights decisions in commercial or sensitive contexts remain the user's responsibility.
|
|
177
|
+
- **Not a content host.** Image URLs point at each museum's CDN; this server does not rehost media.
|
|
178
|
+
|
|
179
|
+
## Roadmap
|
|
180
|
+
|
|
181
|
+
- v0.1: Met adapter, dynasty-aware date parser, license gate, `cite` tool, MCP resources. **(here)**
|
|
182
|
+
- v0.2: Cleveland and AIC adapters, `discover_random` with constraints (`region`, `period`, `not_artist`), `list_traditions`.
|
|
183
|
+
- v0.5: Dominant-color extraction across museums (`color: "#3a5f7d"` discovery via `sharp`).
|
|
184
|
+
- v1.0: Artist-obscurity scoring (`object_count_total`, `museum_count`) for deliberate exploration of less-canonical work.
|
|
185
|
+
- v2.0: Smithsonian, Rijksmuseum, Wikimedia Commons (long-tail).
|
|
186
|
+
|
|
187
|
+
## Contributing a museum adapter
|
|
188
|
+
|
|
189
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). The short version:
|
|
190
|
+
|
|
191
|
+
1. Implement the `Fetcher` interface in `src/fetchers/{code}.ts`.
|
|
192
|
+
2. Add a `validate{Code}License` function in `src/licenseGate.ts` with explicit accept rules and strict default deny.
|
|
193
|
+
3. Add region/period mappings to `src/data/regions.json` and `src/data/dynasties.json` if the new collection introduces unmapped traditions.
|
|
194
|
+
4. Add fixture-based tests in `tests/{code}.test.ts` covering: one accepted record, one rejected (non-open) record, one rejected (missing field) record.
|
|
195
|
+
5. Register the adapter in `src/server.ts`.
|
|
196
|
+
|
|
197
|
+
The license gate is the most opinionated part of the codebase. Additions should err strict.
|
|
198
|
+
|
|
199
|
+
## Security
|
|
200
|
+
|
|
201
|
+
- **`npm audit` clean at launch.** Zero vulnerabilities at any severity level across runtime and dev dependencies as of v0.1.
|
|
202
|
+
- **stdio-only transport.** No HTTP listener, no auth surface to bypass. The server only speaks to the MCP client over standard streams.
|
|
203
|
+
- **Strict input validation.** All tool arguments pass through Zod schemas; artwork IDs are constrained to `/^[a-z]+:[1-9]\d*$/`. The resource URI handler re-validates the constructed ID against the same regex, so URI-form requests can't bypass the constraint.
|
|
204
|
+
- **Defense-in-depth on rights.** The Met search filter `isPublicDomain=true` is sometimes inconsistent with the per-object boolean. The license gate runs again on every fetched record and rejects any disagreement.
|
|
205
|
+
- **Parameterized SQL.** All `better-sqlite3` calls use named/positional parameters; zero string-concatenated SQL paths.
|
|
206
|
+
- **No file writes from user input.** The cache directory is created at `~/.open-museum-mcp/cache.db` (or wherever `OMM_CACHE_PATH` points) with mode `0o700` and the cache file at `0o600`; no fetcher rehosts media bytes locally.
|
|
207
|
+
|
|
208
|
+
If you find a record the gate accepts that shouldn't pass, please open an issue with the artwork ID and the museum's raw API response. Rights correctness is the project's most important property, and the part where outside review most helps.
|
|
209
|
+
|
|
210
|
+
## Disclaimer
|
|
211
|
+
|
|
212
|
+
This software validates open-access status against the rights metadata each museum publishes and the rules each museum requests. It cannot independently verify third-party rights, derived works, model release issues, or sensitive cultural-heritage considerations beyond what the source museum represents. Several museums (e.g. the Art Institute of Chicago) explicitly note that even CC0-marked images may carry obligations around third-party permissions or culturally sensitive material. **Always confirm against the source museum's terms before commercial or sensitive use.**
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT. See [`LICENSE`](LICENSE).
|
package/dist/cite.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Artwork } from './types.js';
|
|
2
|
+
export type CiteStyle = 'short' | 'full' | 'caption';
|
|
3
|
+
/**
|
|
4
|
+
* Render an attribution string for an artwork.
|
|
5
|
+
*
|
|
6
|
+
* Style semantics:
|
|
7
|
+
* - `full`: footnote/bibliography form. Period-separated.
|
|
8
|
+
* - `caption`: museum-publication caption convention. Comma-separated head
|
|
9
|
+
* (artist, title, date), medium called out, terse end with museum + license.
|
|
10
|
+
* - `short`: inline reference like "Title (Artist, Date)".
|
|
11
|
+
*
|
|
12
|
+
* Per-museum overrides live in the PER_MUSEUM_FULL / PER_MUSEUM_CAPTION
|
|
13
|
+
* tables. Museums without an entry fall back to the Met formatters — keep
|
|
14
|
+
* them general-purpose enough to read sensibly across collections until a
|
|
15
|
+
* museum-specific style is contributed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function cite(artwork: Artwork, style?: CiteStyle): string;
|
package/dist/cite.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
function joinNonEmpty(parts, sep) {
|
|
2
|
+
return parts.filter((p) => Boolean(p && p.trim())).join(sep);
|
|
3
|
+
}
|
|
4
|
+
function metFull(a) {
|
|
5
|
+
const artist = a.artist.attributionType === 'anonymous' ? '' : a.artist.name;
|
|
6
|
+
const head = joinNonEmpty([artist, a.title], ', ');
|
|
7
|
+
const date = a.displayDate;
|
|
8
|
+
const tail = `${a.museum.name}. ${a.license.type}. ${a.source.pageUrl}`;
|
|
9
|
+
const joined = joinNonEmpty([head, date, tail], '. ');
|
|
10
|
+
return joined.endsWith('.') ? joined : `${joined}.`;
|
|
11
|
+
}
|
|
12
|
+
function metCaption(a) {
|
|
13
|
+
const artist = a.artist.attributionType === 'anonymous' ? 'Unknown artist' : a.artist.name;
|
|
14
|
+
const parts = [`${artist}, ${a.title}, ${a.displayDate}`];
|
|
15
|
+
if (a.medium && a.medium.trim())
|
|
16
|
+
parts.push(a.medium.trim());
|
|
17
|
+
parts.push(`${a.museum.name}, ${a.license.type}`);
|
|
18
|
+
const body = parts.join('. ');
|
|
19
|
+
const terminated = body.endsWith('.') ? body : `${body}.`;
|
|
20
|
+
return `${terminated} ${a.source.pageUrl}`;
|
|
21
|
+
}
|
|
22
|
+
// Short uses bare "Unknown" rather than caption's "Unknown artist" because
|
|
23
|
+
// the parenthetical inline form reads better with a single word: prefer
|
|
24
|
+
// "Title (Unknown, 1500)" over "Title (Unknown artist, 1500)". Captions are
|
|
25
|
+
// standalone sentences where the longer form is conventional.
|
|
26
|
+
function shortStyle(a) {
|
|
27
|
+
const artist = a.artist.attributionType === 'anonymous' ? 'Unknown' : a.artist.name;
|
|
28
|
+
const date = a.displayDate || (a.yearStart != null ? String(a.yearStart) : '');
|
|
29
|
+
return `${a.title} (${artist}, ${date})`;
|
|
30
|
+
}
|
|
31
|
+
const PER_MUSEUM_FULL = {
|
|
32
|
+
met: metFull,
|
|
33
|
+
};
|
|
34
|
+
const PER_MUSEUM_CAPTION = {
|
|
35
|
+
met: metCaption,
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Render an attribution string for an artwork.
|
|
39
|
+
*
|
|
40
|
+
* Style semantics:
|
|
41
|
+
* - `full`: footnote/bibliography form. Period-separated.
|
|
42
|
+
* - `caption`: museum-publication caption convention. Comma-separated head
|
|
43
|
+
* (artist, title, date), medium called out, terse end with museum + license.
|
|
44
|
+
* - `short`: inline reference like "Title (Artist, Date)".
|
|
45
|
+
*
|
|
46
|
+
* Per-museum overrides live in the PER_MUSEUM_FULL / PER_MUSEUM_CAPTION
|
|
47
|
+
* tables. Museums without an entry fall back to the Met formatters — keep
|
|
48
|
+
* them general-purpose enough to read sensibly across collections until a
|
|
49
|
+
* museum-specific style is contributed.
|
|
50
|
+
*/
|
|
51
|
+
export function cite(artwork, style = 'full') {
|
|
52
|
+
if (style === 'short')
|
|
53
|
+
return shortStyle(artwork);
|
|
54
|
+
if (style === 'caption') {
|
|
55
|
+
const fn = PER_MUSEUM_CAPTION[artwork.museum.code] ?? metCaption;
|
|
56
|
+
return fn(artwork);
|
|
57
|
+
}
|
|
58
|
+
const fn = PER_MUSEUM_FULL[artwork.museum.code] ?? metFull;
|
|
59
|
+
return fn(artwork);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=cite.js.map
|
package/dist/cite.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cite.js","sourceRoot":"","sources":["../src/cite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"japan": {
|
|
3
|
+
"jomon": [-14000, -300],
|
|
4
|
+
"yayoi": [-300, 250],
|
|
5
|
+
"kofun": [250, 538],
|
|
6
|
+
"asuka": [538, 710],
|
|
7
|
+
"nara": [710, 794],
|
|
8
|
+
"heian": [794, 1185],
|
|
9
|
+
"kamakura": [1185, 1333],
|
|
10
|
+
"muromachi": [1336, 1573],
|
|
11
|
+
"azuchi-momoyama": [1573, 1603],
|
|
12
|
+
"momoyama": [1573, 1603],
|
|
13
|
+
"edo": [1603, 1868],
|
|
14
|
+
"meiji": [1868, 1912],
|
|
15
|
+
"taisho": [1912, 1926],
|
|
16
|
+
"showa": [1926, 1989]
|
|
17
|
+
},
|
|
18
|
+
"china": {
|
|
19
|
+
"shang": [-1600, -1046],
|
|
20
|
+
"zhou": [-1046, -256],
|
|
21
|
+
"qin": [-221, -206],
|
|
22
|
+
"han": [-206, 220],
|
|
23
|
+
"three kingdoms": [220, 280],
|
|
24
|
+
"jin": [266, 420],
|
|
25
|
+
"northern and southern": [420, 589],
|
|
26
|
+
"sui": [581, 618],
|
|
27
|
+
"tang": [618, 907],
|
|
28
|
+
"five dynasties": [907, 960],
|
|
29
|
+
"song": [960, 1279],
|
|
30
|
+
"northern song": [960, 1127],
|
|
31
|
+
"southern song": [1127, 1279],
|
|
32
|
+
"yuan": [1271, 1368],
|
|
33
|
+
"ming": [1368, 1644],
|
|
34
|
+
"qing": [1644, 1912]
|
|
35
|
+
},
|
|
36
|
+
"korea": {
|
|
37
|
+
"three kingdoms korea": [-57, 668],
|
|
38
|
+
"silla": [-57, 935],
|
|
39
|
+
"unified silla": [668, 935],
|
|
40
|
+
"goryeo": [918, 1392],
|
|
41
|
+
"joseon": [1392, 1897]
|
|
42
|
+
},
|
|
43
|
+
"iran": {
|
|
44
|
+
"achaemenid": [-550, -330],
|
|
45
|
+
"parthian": [-247, 224],
|
|
46
|
+
"sasanian": [224, 651],
|
|
47
|
+
"umayyad caliphate": [661, 750],
|
|
48
|
+
"abbasid caliphate": [750, 1258],
|
|
49
|
+
"ilkhanid": [1256, 1335],
|
|
50
|
+
"timurid": [1370, 1507],
|
|
51
|
+
"safavid": [1501, 1736],
|
|
52
|
+
"qajar": [1789, 1925],
|
|
53
|
+
"pahlavi": [1925, 1979]
|
|
54
|
+
},
|
|
55
|
+
"india": {
|
|
56
|
+
"maurya": [-322, -185],
|
|
57
|
+
"gupta": [320, 550],
|
|
58
|
+
"chola": [848, 1279],
|
|
59
|
+
"delhi sultanate": [1206, 1526],
|
|
60
|
+
"mughal": [1526, 1857],
|
|
61
|
+
"british raj": [1858, 1947]
|
|
62
|
+
},
|
|
63
|
+
"islamic": {
|
|
64
|
+
"umayyad": [661, 750],
|
|
65
|
+
"abbasid": [750, 1258],
|
|
66
|
+
"fatimid": [909, 1171],
|
|
67
|
+
"ayyubid": [1171, 1260],
|
|
68
|
+
"mamluk": [1250, 1517],
|
|
69
|
+
"ottoman": [1299, 1922]
|
|
70
|
+
},
|
|
71
|
+
"egypt": {
|
|
72
|
+
"old kingdom": [-2686, -2181],
|
|
73
|
+
"middle kingdom": [-2055, -1650],
|
|
74
|
+
"new kingdom": [-1550, -1069],
|
|
75
|
+
"ptolemaic": [-305, -30],
|
|
76
|
+
"late period": [-664, -332]
|
|
77
|
+
},
|
|
78
|
+
"europe": {
|
|
79
|
+
"merovingian": [481, 751],
|
|
80
|
+
"carolingian": [800, 888],
|
|
81
|
+
"ottonian": [919, 1024],
|
|
82
|
+
"romanesque": [1000, 1200],
|
|
83
|
+
"gothic": [1200, 1500],
|
|
84
|
+
"early renaissance": [1400, 1495],
|
|
85
|
+
"high renaissance": [1495, 1527],
|
|
86
|
+
"mannerism": [1527, 1600],
|
|
87
|
+
"baroque": [1600, 1750],
|
|
88
|
+
"rococo": [1730, 1770],
|
|
89
|
+
"neoclassical": [1750, 1830],
|
|
90
|
+
"romanticism": [1800, 1850],
|
|
91
|
+
"victorian": [1837, 1901]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"japan": ["japan", "japanese", "edo", "kyoto", "tokyo", "osaka"],
|
|
3
|
+
"china": ["china", "chinese", "tang", "song", "ming", "qing", "tibet"],
|
|
4
|
+
"korea": ["korea", "korean", "joseon", "goryeo"],
|
|
5
|
+
"iran": ["iran", "iranian", "persian", "persia", "safavid", "qajar"],
|
|
6
|
+
"india": ["india", "indian", "mughal", "rajasthani", "punjabi"],
|
|
7
|
+
"islamic": ["islamic art", "ottoman", "mamluk", "fatimid"],
|
|
8
|
+
"egypt": ["egypt", "egyptian", "ptolemaic"],
|
|
9
|
+
"greece": ["greece", "greek", "hellenistic", "attic"],
|
|
10
|
+
"rome": ["rome", "roman", "roman empire"],
|
|
11
|
+
"byzantine": ["byzantine", "byzantium"],
|
|
12
|
+
"italy": ["italy", "italian", "florentine", "venetian", "roman renaissance"],
|
|
13
|
+
"france": ["france", "french", "parisian"],
|
|
14
|
+
"netherlands": ["netherlands", "dutch", "flemish", "holland"],
|
|
15
|
+
"germany": ["germany", "german", "bavarian", "prussian"],
|
|
16
|
+
"spain": ["spain", "spanish", "andalusian", "catalan"],
|
|
17
|
+
"england": ["england", "english", "british", "great britain"],
|
|
18
|
+
"americas": ["united states", "american", "mexican", "peruvian"],
|
|
19
|
+
"africa": ["africa", "african", "yoruba", "kongo", "asante"],
|
|
20
|
+
"oceania": ["oceania", "polynesian", "maori", "papua"]
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DateRange } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a museum-supplied display date into a {yearStart, yearEnd} range.
|
|
4
|
+
*
|
|
5
|
+
* Strategies are tried in this exact order — earlier strategies win:
|
|
6
|
+
* 1. cross-era range ("500 BCE – 50 CE")
|
|
7
|
+
* 2. numeric range ("1820–1830", "1820-5", "1899–05")
|
|
8
|
+
* 3. ordinal-century range ("14th-15th century")
|
|
9
|
+
* 4. ordinal century with optional early/mid/late qualifier
|
|
10
|
+
* 5. decade ("1820s")
|
|
11
|
+
* 6. single year ("1888", "ca. 1820", "500 BCE")
|
|
12
|
+
* 7. dynasty/period lookup (longest key first to avoid prefix shadowing)
|
|
13
|
+
*
|
|
14
|
+
* Returns {null, null} when nothing matches — never guesses. BCE is encoded
|
|
15
|
+
* as negative integers so range arithmetic Just Works.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseDisplayDate(input: string | null | undefined): DateRange;
|