wiki-formant 0.3.0 → 0.4.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/dist/blocks.d.ts +6 -0
- package/dist/blocks.d.ts.map +1 -1
- package/dist/blocks.js +3 -3
- package/dist/blocks.js.map +1 -1
- package/dist/conformance.d.ts +99 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +158 -0
- package/dist/conformance.js.map +1 -0
- package/dist/freshness.d.ts +20 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/freshness.js +42 -0
- package/dist/freshness.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/links.d.ts +31 -0
- package/dist/links.d.ts.map +1 -0
- package/dist/links.js +81 -0
- package/dist/links.js.map +1 -0
- package/dist/maps.d.ts +21 -0
- package/dist/maps.d.ts.map +1 -0
- package/dist/maps.js +77 -0
- package/dist/maps.js.map +1 -0
- package/dist/text.d.ts +41 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +112 -0
- package/dist/text.js.map +1 -0
- package/dist/validation.d.ts +47 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +124 -0
- package/dist/validation.js.map +1 -0
- package/dist/well-known.d.ts +83 -0
- package/dist/well-known.d.ts.map +1 -0
- package/dist/well-known.js +84 -0
- package/dist/well-known.js.map +1 -0
- package/package.json +33 -3
package/dist/maps.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// maps.ts — turn a map URL a human pasted into one an <iframe> will accept.
|
|
2
|
+
//
|
|
3
|
+
// Both wikis carried this byte-for-byte apart from one `export` keyword. It is
|
|
4
|
+
// pure string work over Google and Apple Maps URL shapes, with no framework or
|
|
5
|
+
// database in it, which is why neither copy had a reason to diverge.
|
|
6
|
+
/** A plain embed URL for a coordinate pair. */
|
|
7
|
+
export function mapsEmbedUrl(lat, lon, zoom = 15) {
|
|
8
|
+
return `https://maps.google.com/maps?q=${lat},${lon}&z=${zoom}&output=embed`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Dig a coordinate pair out of a maps URL. Four shapes in descending
|
|
12
|
+
* specificity: the `@lat,lon,zoom` path segment, a `/search/lat,lon`, the
|
|
13
|
+
* `!3d…!4d…` data blob, and finally an `ll`/`sll` query parameter.
|
|
14
|
+
*/
|
|
15
|
+
export function extractCoordsFromUrl(url) {
|
|
16
|
+
const coords = url.match(/@(-?\d+\.?\d*),(-?\d+\.?\d*),?(\d+\.?\d*)?z?/);
|
|
17
|
+
if (coords)
|
|
18
|
+
return { lat: +coords[1], lon: +coords[2], zoom: coords[3] ? +coords[3] : undefined };
|
|
19
|
+
const search = url.match(/\/search\/(-?\d+\.?\d*),[\s+]*(-?\d+\.?\d*)/);
|
|
20
|
+
if (search)
|
|
21
|
+
return { lat: +search[1], lon: +search[2] };
|
|
22
|
+
const data = url.match(/!3d(-?\d+\.?\d*)!4d(-?\d+\.?\d*)/);
|
|
23
|
+
if (data)
|
|
24
|
+
return { lat: +data[1], lon: +data[2] };
|
|
25
|
+
try {
|
|
26
|
+
const ll = new URL(url).searchParams.get('ll') ?? new URL(url).searchParams.get('sll');
|
|
27
|
+
if (ll) {
|
|
28
|
+
const [lat, lon] = ll.split(',').map(Number);
|
|
29
|
+
if (lat !== undefined && lon !== undefined && !isNaN(lat) && !isNaN(lon))
|
|
30
|
+
return { lat, lon };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
/* not a valid URL */
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* An embeddable URL for `url`, or null when it is not a map link this
|
|
40
|
+
* understands. Already-embeddable URLs pass through untouched.
|
|
41
|
+
*/
|
|
42
|
+
export function toMapEmbedUrl(url) {
|
|
43
|
+
if (/google\.[a-z.]+\/maps\/embed/.test(url))
|
|
44
|
+
return url;
|
|
45
|
+
if (/embed\.apple\.com\/maps/.test(url))
|
|
46
|
+
return url;
|
|
47
|
+
const c = extractCoordsFromUrl(url);
|
|
48
|
+
if (c)
|
|
49
|
+
return mapsEmbedUrl(c.lat, c.lon, c.zoom);
|
|
50
|
+
if (/google\.[a-z.]+\/maps/.test(url)) {
|
|
51
|
+
const place = url.match(/\/place\/([^/@]+)/);
|
|
52
|
+
if (place) {
|
|
53
|
+
const q = encodeURIComponent(decodeURIComponent(place[1]).replace(/\+/g, ' '));
|
|
54
|
+
return `https://maps.google.com/maps?q=${q}&output=embed`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (/maps\.apple\.com/.test(url)) {
|
|
58
|
+
try {
|
|
59
|
+
const u = new URL(url);
|
|
60
|
+
const ll = u.searchParams.get('ll') ?? u.searchParams.get('sll');
|
|
61
|
+
const q = u.searchParams.get('q') ?? u.searchParams.get('address');
|
|
62
|
+
const params = new URLSearchParams();
|
|
63
|
+
if (ll)
|
|
64
|
+
params.set('ll', ll);
|
|
65
|
+
if (q)
|
|
66
|
+
params.set('q', q);
|
|
67
|
+
return `https://embed.apple.com/maps?${params.toString()}`;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
/** True for the shortener forms that only a redirect can resolve. */
|
|
76
|
+
export const isShortMapUrl = (url) => /maps\.app\.goo\.gl|goo\.gl\/maps/.test(url);
|
|
77
|
+
//# sourceMappingURL=maps.js.map
|
package/dist/maps.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maps.js","sourceRoot":"","sources":["../src/maps.ts"],"names":[],"mappings":"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"}
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CodeTab, ReferenceItem } from './blocks.js';
|
|
2
|
+
/**
|
|
3
|
+
* HTML to readable text. Links keep their href in parentheses so a model can
|
|
4
|
+
* still follow a citation; block-level tags become newlines; list items get a
|
|
5
|
+
* bullet. Entities are decoded last, after the tags are gone, so a `<` in
|
|
6
|
+
* prose cannot become a tag the strip has already run past.
|
|
7
|
+
*/
|
|
8
|
+
export declare function stripHtml(html: string): string;
|
|
9
|
+
/** The six maintenance-banner variants every wiki here renders. */
|
|
10
|
+
export type BannerVariant = 'stub' | 'unsourced' | 'outdated' | 'promotional' | 'cleanup' | 'coi';
|
|
11
|
+
/**
|
|
12
|
+
* Display labels for the maintenance banners. Canonical: the prose extractor,
|
|
13
|
+
* the markdown twin and the MDX export all render the same six strings.
|
|
14
|
+
*/
|
|
15
|
+
export declare const BANNER_LABELS: Record<BannerVariant, string>;
|
|
16
|
+
/** A maintenance notice, inline: `[Notice: Needs citations] …`. */
|
|
17
|
+
export declare function bannerToText(label: string, text?: string | null): string;
|
|
18
|
+
/** Each tab under its label, tags stripped — highlighted markup is noise here. */
|
|
19
|
+
export declare function codeTabsToText(tabs: readonly CodeTab[]): string;
|
|
20
|
+
/** A numbered reference list, or `''` when there are none. */
|
|
21
|
+
export declare function referencesToText(items: readonly ReferenceItem[]): string;
|
|
22
|
+
/**
|
|
23
|
+
* Every `text` value at any depth of a block tree, in document order.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately NOT the typed extractor above: that walks a switch and formats
|
|
26
|
+
* for reading (labels, bullets, reference numbering), so it can surface text a
|
|
27
|
+
* search index never matched and miss text it did. A snippet claiming to show
|
|
28
|
+
* why a row matched has to read the same bytes the match was made against.
|
|
29
|
+
*/
|
|
30
|
+
export declare function collectText(node: unknown, out?: string[]): string[];
|
|
31
|
+
/**
|
|
32
|
+
* The passage that matched `query`, not the opening of the page.
|
|
33
|
+
*
|
|
34
|
+
* Wiki pages open with an infobox far more often than not — 243 of 269 on caper
|
|
35
|
+
* when this was measured — so an opening-line snippet hands nine search rows in
|
|
36
|
+
* ten a flattened metadata table, whatever the query was. Falls back to the
|
|
37
|
+
* opening when the term appears only in the title, which is a real case rather
|
|
38
|
+
* than a failure: title-only hits are how the top tiers match.
|
|
39
|
+
*/
|
|
40
|
+
export declare function matchSnippet(blocks: unknown, query: string, opening: () => string, maxLen?: number): string;
|
|
41
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW9C;AAED,mEAAmE;AACnE,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,GAAG,KAAK,CAAC;AAElG;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAOvD,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAExE;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAE/D;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CAIxE;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAYvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,MAAM,EAAE,MAAM,SAAM,GAAG,MAAM,CAwBxG"}
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// text.ts — a block tree as plain prose, for LLM and MCP exports.
|
|
2
|
+
//
|
|
3
|
+
// The markdown twin in `blocks.ts` is for a reader; this is for a model reading
|
|
4
|
+
// the page as evidence. Both wikis had the same `stripHtml`, the same six
|
|
5
|
+
// banner labels and the same four leaf bodies, character for character.
|
|
6
|
+
//
|
|
7
|
+
// As in `blocks.ts`, the dispatch stays with the caller — a `switch` over its
|
|
8
|
+
// own block union, where a new type is a compile error until it is handled —
|
|
9
|
+
// and only the bodies live here.
|
|
10
|
+
import { decodeEntities } from './entities.js';
|
|
11
|
+
/**
|
|
12
|
+
* HTML to readable text. Links keep their href in parentheses so a model can
|
|
13
|
+
* still follow a citation; block-level tags become newlines; list items get a
|
|
14
|
+
* bullet. Entities are decoded last, after the tags are gone, so a `<` in
|
|
15
|
+
* prose cannot become a tag the strip has already run past.
|
|
16
|
+
*/
|
|
17
|
+
export function stripHtml(html) {
|
|
18
|
+
return decodeEntities(html
|
|
19
|
+
.replace(/<a[^>]+href="([^"]*)"[^>]*>(.*?)<\/a>/gi, ' $2 ($1) ')
|
|
20
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
21
|
+
.replace(/<\/(?:p|h[1-6]|li|tr|th|td|div)>/gi, '\n')
|
|
22
|
+
.replace(/<(?:li)>/gi, '- ')
|
|
23
|
+
.replace(/<[^>]+>/g, ''))
|
|
24
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
25
|
+
.trim();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Display labels for the maintenance banners. Canonical: the prose extractor,
|
|
29
|
+
* the markdown twin and the MDX export all render the same six strings.
|
|
30
|
+
*/
|
|
31
|
+
export const BANNER_LABELS = {
|
|
32
|
+
stub: 'Stub',
|
|
33
|
+
unsourced: 'Needs citations',
|
|
34
|
+
outdated: 'May be outdated',
|
|
35
|
+
promotional: 'Written like an advertisement',
|
|
36
|
+
cleanup: 'Needs cleanup',
|
|
37
|
+
coi: 'Conflict of interest',
|
|
38
|
+
};
|
|
39
|
+
/** A maintenance notice, inline: `[Notice: Needs citations] …`. */
|
|
40
|
+
export function bannerToText(label, text) {
|
|
41
|
+
return `[Notice: ${label}]${text ? ' ' + stripHtml(text) : ''}`;
|
|
42
|
+
}
|
|
43
|
+
/** Each tab under its label, tags stripped — highlighted markup is noise here. */
|
|
44
|
+
export function codeTabsToText(tabs) {
|
|
45
|
+
return tabs.map(t => `[${t.label}]\n${t.code}`).join('\n');
|
|
46
|
+
}
|
|
47
|
+
/** A numbered reference list, or `''` when there are none. */
|
|
48
|
+
export function referencesToText(items) {
|
|
49
|
+
if (!items.length)
|
|
50
|
+
return '';
|
|
51
|
+
const lines = items.map((it, i) => `${i + 1}. ${stripHtml(it.text)}${it.url ? ` (${it.url})` : ''}`);
|
|
52
|
+
return `References:\n${lines.join('\n')}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Every `text` value at any depth of a block tree, in document order.
|
|
56
|
+
*
|
|
57
|
+
* Deliberately NOT the typed extractor above: that walks a switch and formats
|
|
58
|
+
* for reading (labels, bullets, reference numbering), so it can surface text a
|
|
59
|
+
* search index never matched and miss text it did. A snippet claiming to show
|
|
60
|
+
* why a row matched has to read the same bytes the match was made against.
|
|
61
|
+
*/
|
|
62
|
+
export function collectText(node, out = []) {
|
|
63
|
+
if (Array.isArray(node)) {
|
|
64
|
+
for (const item of node)
|
|
65
|
+
collectText(item, out);
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
if (node && typeof node === 'object') {
|
|
69
|
+
for (const [key, value] of Object.entries(node)) {
|
|
70
|
+
if (key === 'text' && typeof value === 'string')
|
|
71
|
+
out.push(value);
|
|
72
|
+
else
|
|
73
|
+
collectText(value, out);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The passage that matched `query`, not the opening of the page.
|
|
80
|
+
*
|
|
81
|
+
* Wiki pages open with an infobox far more often than not — 243 of 269 on caper
|
|
82
|
+
* when this was measured — so an opening-line snippet hands nine search rows in
|
|
83
|
+
* ten a flattened metadata table, whatever the query was. Falls back to the
|
|
84
|
+
* opening when the term appears only in the title, which is a real case rather
|
|
85
|
+
* than a failure: title-only hits are how the top tiers match.
|
|
86
|
+
*/
|
|
87
|
+
export function matchSnippet(blocks, query, opening, maxLen = 200) {
|
|
88
|
+
const term = query.trim();
|
|
89
|
+
if (!term)
|
|
90
|
+
return opening();
|
|
91
|
+
// Collapse tags and non-breaking spaces the way SQL does, so a phrase broken
|
|
92
|
+
// by markup is still one searchable string, then decode what is left: editors
|
|
93
|
+
// store typographic punctuation named, and without this a snippet reads back
|
|
94
|
+
// "docs · Related".
|
|
95
|
+
const text = decodeEntities(collectText(blocks).join(' ').replace(/<[^>]*>| /g, ' '))
|
|
96
|
+
.replace(/\s+/g, ' ') // JS \s covers U+00A0, which SQL has to translate by hand
|
|
97
|
+
.trim();
|
|
98
|
+
const at = text.toLowerCase().indexOf(term.toLowerCase());
|
|
99
|
+
if (at === -1)
|
|
100
|
+
return opening();
|
|
101
|
+
// Keep about a line of lead-in, cut to a word boundary so it does not open
|
|
102
|
+
// mid-word.
|
|
103
|
+
let start = Math.max(0, at - 60);
|
|
104
|
+
if (start > 0) {
|
|
105
|
+
const boundary = text.indexOf(' ', start);
|
|
106
|
+
if (boundary > -1 && boundary < at)
|
|
107
|
+
start = boundary + 1;
|
|
108
|
+
}
|
|
109
|
+
const end = Math.min(text.length, start + maxLen);
|
|
110
|
+
return `${start > 0 ? '…' : ''}${text.slice(start, end).trimEnd()}${end < text.length ? '…' : ''}`;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=text.js.map
|
package/dist/text.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An href acceptable to persist: http, https, mailto, or a relative path or
|
|
3
|
+
* fragment. Everything else — `javascript:`, `data:`, `vbscript:`, a
|
|
4
|
+
* protocol-relative `//host` — comes back null.
|
|
5
|
+
*
|
|
6
|
+
* React 19 already neutralises `javascript:` in an href at render time, so this
|
|
7
|
+
* is defence in depth rather than the only thing between an editor and a
|
|
8
|
+
* reader. Its value is at the write path: a URL that can never render safely is
|
|
9
|
+
* better rejected than stored.
|
|
10
|
+
*/
|
|
11
|
+
export declare function safeLinkHref(raw: string | null | undefined): string | null;
|
|
12
|
+
/** An author-supplied URL is fine iff it is empty (unset) or resolves safely. */
|
|
13
|
+
export declare const okUrl: (u: unknown) => boolean;
|
|
14
|
+
/** `[{ id, text, url? }]` — the shape a references block stores. */
|
|
15
|
+
export declare function validateReferenceItems(items: unknown, urlCheck?: (u: unknown) => boolean): boolean;
|
|
16
|
+
/** `[{ id, heading, links: [{ label, href }] }]` — a link-grid block's groups. */
|
|
17
|
+
export declare function validateLinkGroups(groups: unknown, urlCheck?: (u: unknown) => boolean): boolean;
|
|
18
|
+
export interface BlockValidatorOptions {
|
|
19
|
+
/** True for a type this wiki knows at all. */
|
|
20
|
+
isKnownType: (type: string) => boolean;
|
|
21
|
+
/** True for a type that may nest inside a container. */
|
|
22
|
+
isAtomicType: (type: string) => boolean;
|
|
23
|
+
/** This wiki's switch over its leaf types. Runs only after id/type pass. */
|
|
24
|
+
validateAtomic: (block: Record<string, unknown>) => boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The block-tree walk, with one wiki's leaf switch plugged into it. The caller
|
|
28
|
+
* keeps its own type parameter, so this stays free of any repo's types while
|
|
29
|
+
* the call site still gets a real type guard.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createBlockValidator(opts: BlockValidatorOptions): {
|
|
32
|
+
/** One leaf block; container types are rejected. */
|
|
33
|
+
validateAtomicBlock: (block: unknown) => boolean;
|
|
34
|
+
/** One block of any kind, containers included. */
|
|
35
|
+
validateBlock: (block: unknown) => boolean;
|
|
36
|
+
/** A whole page's content array. */
|
|
37
|
+
validateBlocks: (content: unknown) => boolean;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A copy of a block with a fresh id at every level, so a duplicated container
|
|
41
|
+
* does not share child ids with its original.
|
|
42
|
+
*/
|
|
43
|
+
export declare function duplicateBlockIds<B extends {
|
|
44
|
+
type: string;
|
|
45
|
+
id: string;
|
|
46
|
+
}>(block: B, newId: () => string): B;
|
|
47
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAWA;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAa1E;AAED,iFAAiF;AACjF,eAAO,MAAM,KAAK,GAAI,GAAG,OAAO,KAAG,OAC8B,CAAC;AAKlE,oEAAoE;AACpE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,GAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAe,GAAG,OAAO,CAWzG;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAe,GAAG,OAAO,CAYtG;AAED,MAAM,WAAW,qBAAqB;IACpC,8CAA8C;IAC9C,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,wDAAwD;IACxD,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,4EAA4E;IAC5E,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;CAC7D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB;IAkC5D,oDAAoD;iCA/B/B,OAAO,KAAG,OAAO;IAiCtC,kDAAkD;2BA1BhC,OAAO,KAAG,OAAO;IA4BnC,oCAAoC;8BACV,OAAO,KAAG,OAAO;EAE9C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,MAAM,GAAG,CAAC,CAgB1G"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// validation.ts — the block-tree checks that are the same wiki to wiki.
|
|
2
|
+
//
|
|
3
|
+
// Both wikis validate a block tree before it reaches the database, and both had
|
|
4
|
+
// written the same walk: an id-and-type gate, a container branch for
|
|
5
|
+
// columns/infobox, and a switch over leaf types. Only the switch is a project's
|
|
6
|
+
// own — its type set is. So the walk and the two fiddly nested validators live
|
|
7
|
+
// here, and the switch is passed in.
|
|
8
|
+
//
|
|
9
|
+
// As elsewhere in this package, the dispatch stays with the caller so a new
|
|
10
|
+
// block type is a compile error there until it is handled.
|
|
11
|
+
/**
|
|
12
|
+
* An href acceptable to persist: http, https, mailto, or a relative path or
|
|
13
|
+
* fragment. Everything else — `javascript:`, `data:`, `vbscript:`, a
|
|
14
|
+
* protocol-relative `//host` — comes back null.
|
|
15
|
+
*
|
|
16
|
+
* React 19 already neutralises `javascript:` in an href at render time, so this
|
|
17
|
+
* is defence in depth rather than the only thing between an editor and a
|
|
18
|
+
* reader. Its value is at the write path: a URL that can never render safely is
|
|
19
|
+
* better rejected than stored.
|
|
20
|
+
*/
|
|
21
|
+
export function safeLinkHref(raw) {
|
|
22
|
+
if (!raw)
|
|
23
|
+
return null;
|
|
24
|
+
// Strip C0 controls (including tab, newline and CR) and DEL before reading
|
|
25
|
+
// the scheme, so a newline cannot smuggle one past the match below.
|
|
26
|
+
const href = raw.replace(/[\u0000-\u0020\u007f]/g, '').trim();
|
|
27
|
+
if (!href)
|
|
28
|
+
return null;
|
|
29
|
+
if (href.startsWith('//'))
|
|
30
|
+
return null; // protocol-relative -> external host
|
|
31
|
+
const scheme = href.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
|
|
32
|
+
if (scheme) {
|
|
33
|
+
const s = scheme[1].toLowerCase();
|
|
34
|
+
if (s !== 'http' && s !== 'https' && s !== 'mailto')
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return href;
|
|
38
|
+
}
|
|
39
|
+
/** An author-supplied URL is fine iff it is empty (unset) or resolves safely. */
|
|
40
|
+
export const okUrl = (u) => typeof u === 'string' && (u === '' || safeLinkHref(u) !== null);
|
|
41
|
+
const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
42
|
+
/** `[{ id, text, url? }]` — the shape a references block stores. */
|
|
43
|
+
export function validateReferenceItems(items, urlCheck = okUrl) {
|
|
44
|
+
return (Array.isArray(items) &&
|
|
45
|
+
items.every(it => isRecord(it) &&
|
|
46
|
+
typeof it.id === 'string' &&
|
|
47
|
+
typeof it.text === 'string' &&
|
|
48
|
+
(it.url === undefined || urlCheck(it.url))));
|
|
49
|
+
}
|
|
50
|
+
/** `[{ id, heading, links: [{ label, href }] }]` — a link-grid block's groups. */
|
|
51
|
+
export function validateLinkGroups(groups, urlCheck = okUrl) {
|
|
52
|
+
return (Array.isArray(groups) &&
|
|
53
|
+
groups.every(g => isRecord(g) &&
|
|
54
|
+
typeof g.id === 'string' &&
|
|
55
|
+
typeof g.heading === 'string' &&
|
|
56
|
+
Array.isArray(g.links) &&
|
|
57
|
+
g.links.every(l => isRecord(l) && typeof l.label === 'string' && urlCheck(l.href))));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The block-tree walk, with one wiki's leaf switch plugged into it. The caller
|
|
61
|
+
* keeps its own type parameter, so this stays free of any repo's types while
|
|
62
|
+
* the call site still gets a real type guard.
|
|
63
|
+
*/
|
|
64
|
+
export function createBlockValidator(opts) {
|
|
65
|
+
const { isKnownType, isAtomicType, validateAtomic } = opts;
|
|
66
|
+
const atomic = (block) => {
|
|
67
|
+
if (!isRecord(block))
|
|
68
|
+
return false;
|
|
69
|
+
if (typeof block.id !== 'string')
|
|
70
|
+
return false;
|
|
71
|
+
if (typeof block.type !== 'string' || !isKnownType(block.type) || !isAtomicType(block.type))
|
|
72
|
+
return false;
|
|
73
|
+
return validateAtomic(block);
|
|
74
|
+
};
|
|
75
|
+
const one = (block) => {
|
|
76
|
+
if (!isRecord(block))
|
|
77
|
+
return false;
|
|
78
|
+
if (typeof block.id !== 'string')
|
|
79
|
+
return false;
|
|
80
|
+
if (typeof block.type !== 'string' || !isKnownType(block.type))
|
|
81
|
+
return false;
|
|
82
|
+
if (block.type === 'columns') {
|
|
83
|
+
return (Array.isArray(block.columns) &&
|
|
84
|
+
block.columns.every(col => isRecord(col) &&
|
|
85
|
+
typeof col.id === 'string' &&
|
|
86
|
+
Array.isArray(col.blocks) &&
|
|
87
|
+
col.blocks.every(atomic)));
|
|
88
|
+
}
|
|
89
|
+
if (block.type === 'infobox') {
|
|
90
|
+
return Array.isArray(block.blocks) && block.blocks.every(atomic);
|
|
91
|
+
}
|
|
92
|
+
return atomic(block);
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
/** One leaf block; container types are rejected. */
|
|
96
|
+
validateAtomicBlock: atomic,
|
|
97
|
+
/** One block of any kind, containers included. */
|
|
98
|
+
validateBlock: one,
|
|
99
|
+
/** A whole page's content array. */
|
|
100
|
+
validateBlocks: (content) => Array.isArray(content) && content.every(one),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A copy of a block with a fresh id at every level, so a duplicated container
|
|
105
|
+
* does not share child ids with its original.
|
|
106
|
+
*/
|
|
107
|
+
export function duplicateBlockIds(block, newId) {
|
|
108
|
+
const b = block;
|
|
109
|
+
if (block.type === 'columns' && Array.isArray(b.columns)) {
|
|
110
|
+
return {
|
|
111
|
+
...block,
|
|
112
|
+
id: newId(),
|
|
113
|
+
columns: b.columns.map(col => {
|
|
114
|
+
const c = col;
|
|
115
|
+
return { ...c, id: newId(), blocks: (c.blocks ?? []).map(x => ({ ...x, id: newId() })) };
|
|
116
|
+
}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (block.type === 'infobox' && Array.isArray(b.blocks)) {
|
|
120
|
+
return { ...block, id: newId(), blocks: b.blocks.map(x => ({ ...x, id: newId() })) };
|
|
121
|
+
}
|
|
122
|
+
return { ...block, id: newId() };
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,EAAE;AACF,gFAAgF;AAChF,qEAAqE;AACrE,gFAAgF;AAChF,+EAA+E;AAC/E,qCAAqC;AACrC,EAAE;AACF,4EAA4E;AAC5E,2DAA2D;AAE3D;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,GAA8B;IACzD,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9D,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,qCAAqC;IAC7E,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACzD,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC;QACnC,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;IACnE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAAU,EAAW,EAAE,CAC3C,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,EAAE,IAAI,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAElE,MAAM,QAAQ,GAAG,CAAC,CAAU,EAAgC,EAAE,CAC5D,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAEpD,oEAAoE;AACpE,MAAM,UAAU,sBAAsB,CAAC,KAAc,EAAE,WAAoC,KAAK;IAC9F,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACpB,KAAK,CAAC,KAAK,CACT,EAAE,CAAC,EAAE,CACH,QAAQ,CAAC,EAAE,CAAC;YACZ,OAAO,EAAE,CAAC,EAAE,KAAK,QAAQ;YACzB,OAAO,EAAE,CAAC,IAAI,KAAK,QAAQ;YAC3B,CAAC,EAAE,CAAC,GAAG,KAAK,SAAS,IAAI,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAC7C,CACF,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,kBAAkB,CAAC,MAAe,EAAE,WAAoC,KAAK;IAC3F,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QACrB,MAAM,CAAC,KAAK,CACV,CAAC,CAAC,EAAE,CACF,QAAQ,CAAC,CAAC,CAAC;YACX,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ;YACxB,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ;YAC7B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;YACtB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CACrF,CACF,CAAC;AACJ,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAA2B;IAC9D,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAE3D,MAAM,MAAM,GAAG,CAAC,KAAc,EAAW,EAAE;QACzC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACnC,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC/C,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAC1G,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC,CAAC;IAEF,MAAM,GAAG,GAAG,CAAC,KAAc,EAAW,EAAE;QACtC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACnC,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC/C,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAE7E,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;gBAC5B,KAAK,CAAC,OAAO,CAAC,KAAK,CACjB,GAAG,CAAC,EAAE,CACJ,QAAQ,CAAC,GAAG,CAAC;oBACb,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;oBAC1B,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;oBACzB,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAC3B,CACF,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC,CAAC;IAEF,OAAO;QACL,oDAAoD;QACpD,mBAAmB,EAAE,MAAM;QAC3B,kDAAkD;QAClD,aAAa,EAAE,GAAG;QAClB,oCAAoC;QACpC,cAAc,EAAE,CAAC,OAAgB,EAAW,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;KAC5F,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAyC,KAAQ,EAAE,KAAmB;IACrG,MAAM,CAAC,GAAG,KAA2C,CAAC;IACtD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QACzD,OAAO;YACL,GAAG,KAAK;YACR,EAAE,EAAE,KAAK,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;gBAC3B,MAAM,CAAC,GAAG,GAA8B,CAAC;gBACzC,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAE,CAAC,CAAC,MAAoB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAI,CAAY,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;YACtH,CAAC,CAAC;SACa,CAAC;IACpB,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAI,CAAY,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAkB,CAAC;IACnH,CAAC;IACD,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export interface RegistryAuthRecord {
|
|
2
|
+
body: string;
|
|
3
|
+
contentType: string;
|
|
4
|
+
cacheControl: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The `v=MCPv1` record the official MCP registry fetches from
|
|
8
|
+
* `/.well-known/mcp-registry-auth` to prove domain ownership. `mcp-publisher
|
|
9
|
+
* login http --domain=<host>` checks the key here against the private key
|
|
10
|
+
* signing the login, which is what grants publish rights over the reversed-
|
|
11
|
+
* domain namespace the server's name sits in.
|
|
12
|
+
*
|
|
13
|
+
* Returns null when no key is configured, so the caller can 404 rather than
|
|
14
|
+
* serve a malformed record — a missing key should read as "not configured",
|
|
15
|
+
* not as a verification failure nobody can explain.
|
|
16
|
+
*
|
|
17
|
+
* The key material belongs in an env var rather than the repo: the public half
|
|
18
|
+
* is harmless to serve but pointless to commit, and keeping it out gives the
|
|
19
|
+
* private half an obvious home too (a keychain, never git).
|
|
20
|
+
*
|
|
21
|
+
* openssl genpkey -algorithm Ed25519 -out key.pem
|
|
22
|
+
* openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | base64
|
|
23
|
+
*/
|
|
24
|
+
export declare function registryAuthRecord(publicKey?: string, keyType?: string): RegistryAuthRecord | null;
|
|
25
|
+
/** A day at the edge, a week stale-while-revalidate. A card changes rarely. */
|
|
26
|
+
export declare const AGENT_CARD_CACHE_CONTROL = "public, s-maxage=86400, stale-while-revalidate=604800";
|
|
27
|
+
export interface AgentSkill {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
tags: string[];
|
|
32
|
+
examples?: string[];
|
|
33
|
+
authentication?: unknown;
|
|
34
|
+
}
|
|
35
|
+
/** The shape a tool needs to carry to become a skill. */
|
|
36
|
+
export interface SkillSource {
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
skill?: {
|
|
40
|
+
id: string;
|
|
41
|
+
tags: string[];
|
|
42
|
+
examples?: string[];
|
|
43
|
+
} | undefined;
|
|
44
|
+
auth?: unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Skills derived from the MCP tool manifest, so a card can never advertise a
|
|
48
|
+
* capability the server does not have. `search_pages` becomes "Search Pages".
|
|
49
|
+
*/
|
|
50
|
+
export declare function skillsFromTools(tools: readonly SkillSource[]): AgentSkill[];
|
|
51
|
+
export interface AgentCardLicense {
|
|
52
|
+
name: string;
|
|
53
|
+
url: string;
|
|
54
|
+
spdx?: string;
|
|
55
|
+
/** What the licence covers, e.g. `'content'`. */
|
|
56
|
+
scope?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface AgentCardConfig {
|
|
59
|
+
name: string;
|
|
60
|
+
description: string;
|
|
61
|
+
/** Origin, no trailing slash. Every derived URL below hangs off it. */
|
|
62
|
+
url: string;
|
|
63
|
+
version: string;
|
|
64
|
+
skills: AgentSkill[];
|
|
65
|
+
license?: AgentCardLicense;
|
|
66
|
+
/** Defaults to `name`. */
|
|
67
|
+
organization?: string;
|
|
68
|
+
/** Defaults to `${url}/llms.txt`. */
|
|
69
|
+
documentationUrl?: string;
|
|
70
|
+
/** Defaults to `${url}/api/mcp`. Pass null for an origin with no MCP server. */
|
|
71
|
+
mcpEndpoint?: string | null;
|
|
72
|
+
/** Anything this origin advertises that the others do not. */
|
|
73
|
+
extra?: Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* An A2A Agent Card. Serve the same object at both `/.well-known/agent.json`
|
|
77
|
+
* and `/.well-known/agent-card.json`: v0.3 renamed the path and defined no
|
|
78
|
+
* fallback in either direction, so a spec-current client probes only the new
|
|
79
|
+
* one and a client on an older SDK probes only the old one. Serving one path
|
|
80
|
+
* means half the callers conclude the origin has no agent at all.
|
|
81
|
+
*/
|
|
82
|
+
export declare function agentCard(config: AgentCardConfig): Record<string, unknown>;
|
|
83
|
+
//# sourceMappingURL=well-known.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"well-known.d.ts","sourceRoot":"","sources":["../src/well-known.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAQlG;AAID,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB,0DAA0D,CAAC;AAEhG,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,yDAAyD;AACzD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,SAAS,CAAC;IACxE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,UAAU,EAAE,CAW3E;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAiB1E"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// well-known.ts — the descriptors an agent finds before it knows anything else
|
|
2
|
+
// about an origin: the A2A Agent Card and the MCP registry's domain proof.
|
|
3
|
+
//
|
|
4
|
+
// All three apps here serve both, and all three had written them out. The
|
|
5
|
+
// registry-auth handler was identical bar a domain named in a comment; the
|
|
6
|
+
// agent cards shared an envelope, a skills mapping and a cache header, and
|
|
7
|
+
// differed only in the fields a card is *supposed* to differ in.
|
|
8
|
+
//
|
|
9
|
+
// Nothing here touches a framework: these return plain values, and the caller
|
|
10
|
+
// wraps them in whatever its router wants. That keeps this package free of a
|
|
11
|
+
// Next dependency and keeps each app's route a two-liner.
|
|
12
|
+
/**
|
|
13
|
+
* The `v=MCPv1` record the official MCP registry fetches from
|
|
14
|
+
* `/.well-known/mcp-registry-auth` to prove domain ownership. `mcp-publisher
|
|
15
|
+
* login http --domain=<host>` checks the key here against the private key
|
|
16
|
+
* signing the login, which is what grants publish rights over the reversed-
|
|
17
|
+
* domain namespace the server's name sits in.
|
|
18
|
+
*
|
|
19
|
+
* Returns null when no key is configured, so the caller can 404 rather than
|
|
20
|
+
* serve a malformed record — a missing key should read as "not configured",
|
|
21
|
+
* not as a verification failure nobody can explain.
|
|
22
|
+
*
|
|
23
|
+
* The key material belongs in an env var rather than the repo: the public half
|
|
24
|
+
* is harmless to serve but pointless to commit, and keeping it out gives the
|
|
25
|
+
* private half an obvious home too (a keychain, never git).
|
|
26
|
+
*
|
|
27
|
+
* openssl genpkey -algorithm Ed25519 -out key.pem
|
|
28
|
+
* openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | base64
|
|
29
|
+
*/
|
|
30
|
+
export function registryAuthRecord(publicKey, keyType) {
|
|
31
|
+
if (!publicKey)
|
|
32
|
+
return null;
|
|
33
|
+
return {
|
|
34
|
+
// ed25519 unless a P-384 key was used instead (the LibreSSL-friendly path).
|
|
35
|
+
body: `v=MCPv1; k=${keyType || 'ed25519'}; p=${publicKey}\n`,
|
|
36
|
+
contentType: 'text/plain; charset=utf-8',
|
|
37
|
+
cacheControl: 'no-store',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// ---- A2A Agent Card ---------------------------------------------------------
|
|
41
|
+
/** A day at the edge, a week stale-while-revalidate. A card changes rarely. */
|
|
42
|
+
export const AGENT_CARD_CACHE_CONTROL = 'public, s-maxage=86400, stale-while-revalidate=604800';
|
|
43
|
+
/**
|
|
44
|
+
* Skills derived from the MCP tool manifest, so a card can never advertise a
|
|
45
|
+
* capability the server does not have. `search_pages` becomes "Search Pages".
|
|
46
|
+
*/
|
|
47
|
+
export function skillsFromTools(tools) {
|
|
48
|
+
return tools
|
|
49
|
+
.filter(t => t.skill)
|
|
50
|
+
.map(t => ({
|
|
51
|
+
id: t.skill.id,
|
|
52
|
+
name: t.name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
|
|
53
|
+
description: t.description,
|
|
54
|
+
tags: t.skill.tags,
|
|
55
|
+
...(t.skill.examples ? { examples: t.skill.examples } : {}),
|
|
56
|
+
...(t.auth ? { authentication: t.auth } : {}),
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* An A2A Agent Card. Serve the same object at both `/.well-known/agent.json`
|
|
61
|
+
* and `/.well-known/agent-card.json`: v0.3 renamed the path and defined no
|
|
62
|
+
* fallback in either direction, so a spec-current client probes only the new
|
|
63
|
+
* one and a client on an older SDK probes only the old one. Serving one path
|
|
64
|
+
* means half the callers conclude the origin has no agent at all.
|
|
65
|
+
*/
|
|
66
|
+
export function agentCard(config) {
|
|
67
|
+
const { name, description, url, version, skills, license, organization, documentationUrl, mcpEndpoint, extra } = config;
|
|
68
|
+
return {
|
|
69
|
+
name,
|
|
70
|
+
description,
|
|
71
|
+
url,
|
|
72
|
+
version,
|
|
73
|
+
capabilities: { streaming: false, pushNotifications: false },
|
|
74
|
+
skills,
|
|
75
|
+
provider: { organization: organization ?? name, url },
|
|
76
|
+
documentationUrl: documentationUrl ?? `${url}/llms.txt`,
|
|
77
|
+
...(mcpEndpoint === null ? {} : { mcpEndpoint: mcpEndpoint ?? `${url}/api/mcp` }),
|
|
78
|
+
...(license ? { license } : {}),
|
|
79
|
+
...extra,
|
|
80
|
+
defaultInputModes: ['text/plain', 'application/json'],
|
|
81
|
+
defaultOutputModes: ['text/plain', 'application/json', 'text/markdown'],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=well-known.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"well-known.js","sourceRoot":"","sources":["../src/well-known.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,2EAA2E;AAC3E,EAAE;AACF,0EAA0E;AAC1E,2EAA2E;AAC3E,2EAA2E;AAC3E,iEAAiE;AACjE,EAAE;AACF,8EAA8E;AAC9E,6EAA6E;AAC7E,0DAA0D;AAU1D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAkB,EAAE,OAAgB;IACrE,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAC5B,OAAO;QACL,4EAA4E;QAC5E,IAAI,EAAE,cAAc,OAAO,IAAI,SAAS,OAAO,SAAS,IAAI;QAC5D,WAAW,EAAE,2BAA2B;QACxC,YAAY,EAAE,UAAU;KACzB,CAAC;AACJ,CAAC;AAED,gFAAgF;AAEhF,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAAG,uDAAuD,CAAC;AAmBhG;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAA6B;IAC3D,OAAO,KAAK;SACT,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;SACpB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACT,EAAE,EAAE,CAAC,CAAC,KAAM,CAAC,EAAE;QACf,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACtE,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,IAAI,EAAE,CAAC,CAAC,KAAM,CAAC,IAAI;QACnB,GAAG,CAAC,CAAC,CAAC,KAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9C,CAAC,CAAC,CAAC;AACR,CAAC;AA4BD;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,MAAuB;IAC/C,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IACxH,OAAO;QACL,IAAI;QACJ,WAAW;QACX,GAAG;QACH,OAAO;QACP,YAAY,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE;QAC5D,MAAM;QACN,QAAQ,EAAE,EAAE,YAAY,EAAE,YAAY,IAAI,IAAI,EAAE,GAAG,EAAE;QACrD,gBAAgB,EAAE,gBAAgB,IAAI,GAAG,GAAG,WAAW;QACvD,GAAG,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,WAAW,IAAI,GAAG,GAAG,UAAU,EAAE,CAAC;QACjF,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/B,GAAG,KAAK;QACR,iBAAiB,EAAE,CAAC,YAAY,EAAE,kBAAkB,CAAC;QACrD,kBAAkB,EAAE,CAAC,YAAY,EAAE,kBAAkB,EAAE,eAAe,CAAC;KACxE,CAAC;AACJ,CAAC"}
|