@ahtmljs/next 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/dist/extractors/data-attrs.d.ts +26 -0
- package/dist/extractors/data-attrs.d.ts.map +1 -0
- package/dist/extractors/data-attrs.js +163 -0
- package/dist/extractors/data-attrs.js.map +1 -0
- package/dist/extractors/index.d.ts +12 -0
- package/dist/extractors/index.d.ts.map +1 -0
- package/dist/extractors/index.js +12 -0
- package/dist/extractors/index.js.map +1 -0
- package/dist/extractors/merge.d.ts +13 -0
- package/dist/extractors/merge.d.ts.map +1 -0
- package/dist/extractors/merge.js +25 -0
- package/dist/extractors/merge.js.map +1 -0
- package/dist/extractors/opengraph.d.ts +7 -0
- package/dist/extractors/opengraph.d.ts.map +1 -0
- package/dist/extractors/opengraph.js +89 -0
- package/dist/extractors/opengraph.js.map +1 -0
- package/dist/extractors/schema-org.d.ts +9 -0
- package/dist/extractors/schema-org.d.ts.map +1 -0
- package/dist/extractors/schema-org.js +104 -0
- package/dist/extractors/schema-org.js.map +1 -0
- package/dist/handler.d.ts +43 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +139 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/llms-txt.d.ts +42 -0
- package/dist/llms-txt.d.ts.map +1 -0
- package/dist/llms-txt.js +88 -0
- package/dist/llms-txt.js.map +1 -0
- package/dist/mcp.d.ts +30 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +64 -0
- package/dist/mcp.js.map +1 -0
- package/dist/openapi.d.ts +13 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +74 -0
- package/dist/openapi.js.map +1 -0
- package/dist/policy.d.ts +24 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +79 -0
- package/dist/policy.js.map +1 -0
- package/dist/well-known.d.ts +40 -0
- package/dist/well-known.d.ts.map +1 -0
- package/dist/well-known.js +56 -0
- package/dist/well-known.js.map +1 -0
- package/package.json +33 -0
package/dist/handler.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-route snapshot handler factory.
|
|
3
|
+
*
|
|
4
|
+
* Usage in a Next.js App Router project:
|
|
5
|
+
*
|
|
6
|
+
* // app/ahtml/[...path]/route.ts
|
|
7
|
+
* import { createAHTMLRoute } from '@ahtmljs/next/handler';
|
|
8
|
+
* import { buildSnapshotForPath } from '../../lib/ahtml';
|
|
9
|
+
* export const { GET, HEAD } = createAHTMLRoute(buildSnapshotForPath);
|
|
10
|
+
*
|
|
11
|
+
* The handler supports:
|
|
12
|
+
* - Content negotiation:
|
|
13
|
+
* Accept: application/ahtml+json → canonical JSON
|
|
14
|
+
* Accept: application/ahtml+text → token-optimal compact text (default)
|
|
15
|
+
* - Conditional GET via If-None-Match → 304
|
|
16
|
+
* - Diff endpoint via ?since=<etag> → SnapshotDiff
|
|
17
|
+
* - ETag, Cache-Control, Last-Modified headers
|
|
18
|
+
* - Optional policy enforcement (rate limit / auth gate)
|
|
19
|
+
*/
|
|
20
|
+
import { toJson, toCompact, computeEtag, diff, } from '@ahtmljs/schema';
|
|
21
|
+
import { getConfig } from './index.js';
|
|
22
|
+
import { enforcePolicy } from './policy.js';
|
|
23
|
+
/** A simple in-memory store for previous snapshots (per process) so the
|
|
24
|
+
* diff endpoint can answer "what changed since <etag>?" without the
|
|
25
|
+
* caller re-uploading. Sites with multiple instances should plug in
|
|
26
|
+
* their own KV store via setSnapshotCache. */
|
|
27
|
+
const _cache = new Map();
|
|
28
|
+
export function setSnapshotCache(impl) {
|
|
29
|
+
// override
|
|
30
|
+
_cache._impl = impl;
|
|
31
|
+
}
|
|
32
|
+
function cacheGet(key) {
|
|
33
|
+
const impl = _cache._impl;
|
|
34
|
+
return impl ? impl.get(key) : _cache.get(key);
|
|
35
|
+
}
|
|
36
|
+
function cacheSet(key, s) {
|
|
37
|
+
const impl = _cache._impl;
|
|
38
|
+
if (impl)
|
|
39
|
+
impl.set(key, s);
|
|
40
|
+
else
|
|
41
|
+
_cache.set(key, s);
|
|
42
|
+
}
|
|
43
|
+
export function createAHTMLRoute(builder, configOverride) {
|
|
44
|
+
async function GET(req, ctx) {
|
|
45
|
+
const config = configOverride ?? getConfig();
|
|
46
|
+
const params = await ctx.params;
|
|
47
|
+
const segments = params.path ?? [];
|
|
48
|
+
const policyDecision = await enforcePolicy(req, config);
|
|
49
|
+
if (policyDecision.deny)
|
|
50
|
+
return policyDecision.response;
|
|
51
|
+
let snap;
|
|
52
|
+
try {
|
|
53
|
+
snap = await builder(segments, req);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
return error(500, 'snapshot_build_failed', err);
|
|
57
|
+
}
|
|
58
|
+
if (!snap) {
|
|
59
|
+
return error(404, 'no_snapshot', `no snapshot for /${segments.join('/')}`);
|
|
60
|
+
}
|
|
61
|
+
snap = ensureDefaults(snap, config);
|
|
62
|
+
const etag = snap.etag ?? computeEtag(snap);
|
|
63
|
+
snap.etag = etag;
|
|
64
|
+
const cacheKey = snap.url;
|
|
65
|
+
const url = new URL(req.url);
|
|
66
|
+
// Diff endpoint: GET /ahtml/...?since=W/"abc"
|
|
67
|
+
const sinceEtag = url.searchParams.get('since');
|
|
68
|
+
if (sinceEtag) {
|
|
69
|
+
const prev = cacheGet(cacheKey);
|
|
70
|
+
if (prev && (prev.etag === sinceEtag || computeEtag(prev) === sinceEtag)) {
|
|
71
|
+
const d = diff(prev, snap);
|
|
72
|
+
cacheSet(cacheKey, snap);
|
|
73
|
+
return new Response(JSON.stringify(d), {
|
|
74
|
+
status: 200,
|
|
75
|
+
headers: {
|
|
76
|
+
'content-type': 'application/ahtml-diff+json',
|
|
77
|
+
etag,
|
|
78
|
+
'cache-control': cacheControl(snap, config),
|
|
79
|
+
'x-ahtml-version': '0.1',
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Fall through to full snapshot if we don't have the prior.
|
|
84
|
+
}
|
|
85
|
+
// Conditional GET
|
|
86
|
+
const ifNoneMatch = req.headers.get('if-none-match');
|
|
87
|
+
if (ifNoneMatch && ifNoneMatch === etag) {
|
|
88
|
+
return new Response(null, {
|
|
89
|
+
status: 304,
|
|
90
|
+
headers: { etag, 'cache-control': cacheControl(snap, config) },
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
cacheSet(cacheKey, snap);
|
|
94
|
+
const fmt = pickFormat(req);
|
|
95
|
+
const body = fmt === 'json' ? toJson(snap) : toCompact(snap);
|
|
96
|
+
return new Response(body, {
|
|
97
|
+
status: 200,
|
|
98
|
+
headers: {
|
|
99
|
+
'content-type': fmt === 'json' ? 'application/ahtml+json' : 'application/ahtml+text; charset=utf-8',
|
|
100
|
+
etag,
|
|
101
|
+
'cache-control': cacheControl(snap, config),
|
|
102
|
+
'last-modified': new Date(snap.fetched_at).toUTCString(),
|
|
103
|
+
'x-ahtml-version': '0.1',
|
|
104
|
+
vary: 'Accept',
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async function HEAD(req, ctx) {
|
|
109
|
+
const res = await GET(req, ctx);
|
|
110
|
+
return new Response(null, { status: res.status, headers: res.headers });
|
|
111
|
+
}
|
|
112
|
+
return { GET, HEAD };
|
|
113
|
+
}
|
|
114
|
+
function ensureDefaults(snap, config) {
|
|
115
|
+
if (config.policy && !snap.policy)
|
|
116
|
+
snap.policy = config.policy;
|
|
117
|
+
if (config.default_ttl && snap.ttl == null)
|
|
118
|
+
snap.ttl = config.default_ttl;
|
|
119
|
+
return snap;
|
|
120
|
+
}
|
|
121
|
+
function cacheControl(snap, config) {
|
|
122
|
+
const ttl = snap.ttl ?? config.default_ttl ?? 60;
|
|
123
|
+
return `public, max-age=${ttl}, must-revalidate`;
|
|
124
|
+
}
|
|
125
|
+
function pickFormat(req) {
|
|
126
|
+
const accept = req.headers.get('accept') ?? '';
|
|
127
|
+
if (/application\/ahtml\+json/.test(accept))
|
|
128
|
+
return 'json';
|
|
129
|
+
if (/application\/ahtml\+text/.test(accept))
|
|
130
|
+
return 'compact';
|
|
131
|
+
if (/application\/json/.test(accept) && !/text/.test(accept))
|
|
132
|
+
return 'json';
|
|
133
|
+
// Default: compact text. Maximally token-efficient for LLM agents.
|
|
134
|
+
return 'compact';
|
|
135
|
+
}
|
|
136
|
+
function error(status, code, detail) {
|
|
137
|
+
return new Response(JSON.stringify({ error: code, detail: detail instanceof Error ? detail.message : String(detail) }), { status, headers: { 'content-type': 'application/json' } });
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ahtmljs/next — Next.js plugin.
|
|
3
|
+
*
|
|
4
|
+
* Quickstart:
|
|
5
|
+
*
|
|
6
|
+
* // next.config.js
|
|
7
|
+
* import { withAHTML } from '@ahtmljs/next';
|
|
8
|
+
* export default withAHTML({
|
|
9
|
+
* // your existing next config
|
|
10
|
+
* }, {
|
|
11
|
+
* site: 'https://shop.example.com',
|
|
12
|
+
* policy: {
|
|
13
|
+
* agents_welcome: true,
|
|
14
|
+
* license: 'CC-BY-4.0',
|
|
15
|
+
* rate_limit: '100/min',
|
|
16
|
+
* contact: 'agents@example.com',
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // app/ahtml/[...path]/route.ts
|
|
21
|
+
* import { createAHTMLRoute } from '@ahtmljs/next/handler';
|
|
22
|
+
* import { buildSnapshot } from '../../lib/ahtml';
|
|
23
|
+
* export const { GET, HEAD } = createAHTMLRoute(buildSnapshot);
|
|
24
|
+
*
|
|
25
|
+
* // app/.well-known/ahtml.json/route.ts
|
|
26
|
+
* import { createWellKnownRoute } from '@ahtmljs/next/well-known';
|
|
27
|
+
* export const { GET } = createWellKnownRoute();
|
|
28
|
+
*/
|
|
29
|
+
import type { Policy } from '@ahtmljs/schema';
|
|
30
|
+
export interface AHTMLConfig {
|
|
31
|
+
site: string;
|
|
32
|
+
policy?: Policy;
|
|
33
|
+
/** Default TTL in seconds applied to snapshots that don't set their own. */
|
|
34
|
+
default_ttl?: number;
|
|
35
|
+
/** Routes that should appear in /.well-known/ahtml.json. */
|
|
36
|
+
routes?: Array<{
|
|
37
|
+
path: string;
|
|
38
|
+
page_type: string;
|
|
39
|
+
}>;
|
|
40
|
+
/** Emit MCP tools at /ahtml/mcp.json — default true. */
|
|
41
|
+
emit_mcp?: boolean;
|
|
42
|
+
/** Emit OpenAPI at /ahtml/openapi.json — default true. */
|
|
43
|
+
emit_openapi?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export declare function withAHTML<T extends Record<string, unknown>>(nextConfig: T, ahtmlConfig: AHTMLConfig): T & {
|
|
46
|
+
__ahtml: AHTMLConfig;
|
|
47
|
+
};
|
|
48
|
+
export declare function getConfig(): AHTMLConfig;
|
|
49
|
+
export type { Policy };
|
|
50
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,wDAAwD;IACxD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAID,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzD,UAAU,EAAE,CAAC,EACb,WAAW,EAAE,WAAW,GACvB,CAAC,GAAG;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAM9B;AAED,wBAAgB,SAAS,IAAI,WAAW,CAQvC;AAED,YAAY,EAAE,MAAM,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ahtmljs/next — Next.js plugin.
|
|
3
|
+
*
|
|
4
|
+
* Quickstart:
|
|
5
|
+
*
|
|
6
|
+
* // next.config.js
|
|
7
|
+
* import { withAHTML } from '@ahtmljs/next';
|
|
8
|
+
* export default withAHTML({
|
|
9
|
+
* // your existing next config
|
|
10
|
+
* }, {
|
|
11
|
+
* site: 'https://shop.example.com',
|
|
12
|
+
* policy: {
|
|
13
|
+
* agents_welcome: true,
|
|
14
|
+
* license: 'CC-BY-4.0',
|
|
15
|
+
* rate_limit: '100/min',
|
|
16
|
+
* contact: 'agents@example.com',
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // app/ahtml/[...path]/route.ts
|
|
21
|
+
* import { createAHTMLRoute } from '@ahtmljs/next/handler';
|
|
22
|
+
* import { buildSnapshot } from '../../lib/ahtml';
|
|
23
|
+
* export const { GET, HEAD } = createAHTMLRoute(buildSnapshot);
|
|
24
|
+
*
|
|
25
|
+
* // app/.well-known/ahtml.json/route.ts
|
|
26
|
+
* import { createWellKnownRoute } from '@ahtmljs/next/well-known';
|
|
27
|
+
* export const { GET } = createWellKnownRoute();
|
|
28
|
+
*/
|
|
29
|
+
let _config;
|
|
30
|
+
export function withAHTML(nextConfig, ahtmlConfig) {
|
|
31
|
+
_config = ahtmlConfig;
|
|
32
|
+
if (typeof globalThis !== 'undefined') {
|
|
33
|
+
globalThis.__ahtml_config = ahtmlConfig;
|
|
34
|
+
}
|
|
35
|
+
return { ...nextConfig, __ahtml: ahtmlConfig };
|
|
36
|
+
}
|
|
37
|
+
export function getConfig() {
|
|
38
|
+
if (_config)
|
|
39
|
+
return _config;
|
|
40
|
+
const g = globalThis.__ahtml_config;
|
|
41
|
+
if (g)
|
|
42
|
+
return g;
|
|
43
|
+
return {
|
|
44
|
+
site: '',
|
|
45
|
+
policy: { agents_welcome: true },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAiBH,IAAI,OAAgC,CAAC;AAErC,MAAM,UAAU,SAAS,CACvB,UAAa,EACb,WAAwB;IAExB,OAAO,GAAG,WAAW,CAAC;IACtB,IAAI,OAAO,UAAU,KAAK,WAAW,EAAE,CAAC;QACrC,UAA+C,CAAC,cAAc,GAAG,WAAW,CAAC;IAChF,CAAC;IACD,OAAO,EAAE,GAAG,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,MAAM,CAAC,GAAI,UAA+C,CAAC,cAAc,CAAC;IAC1E,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAChB,OAAO;QACL,IAAI,EAAE,EAAE;QACR,MAAM,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE;KACjC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llms.txt emitter — compatibility shim with Jeremy Howard's convention
|
|
3
|
+
* (Sept 2024). ~10% of sites adopted as of May 2026; used by Cursor /
|
|
4
|
+
* Continue / Cline / Mintlify-style IDE agents.
|
|
5
|
+
*
|
|
6
|
+
* AHTML is a strict superset. We auto-emit /llms.txt from the same
|
|
7
|
+
* configured routes that feed /.well-known/ahtml.json — adopters get
|
|
8
|
+
* both lanes for free, with zero extra work.
|
|
9
|
+
*
|
|
10
|
+
* Format reference: https://llmstxt.org
|
|
11
|
+
*
|
|
12
|
+
* # Site Name
|
|
13
|
+
*
|
|
14
|
+
* > One-line description of the site
|
|
15
|
+
*
|
|
16
|
+
* ## Section
|
|
17
|
+
*
|
|
18
|
+
* - [Page title](url): one-line description
|
|
19
|
+
*/
|
|
20
|
+
import { type AHTMLConfig } from './index.js';
|
|
21
|
+
export interface LlmsTxtConfig {
|
|
22
|
+
/** The H1 of the file. Defaults to URL host. */
|
|
23
|
+
title?: string;
|
|
24
|
+
/** Blockquote one-liner under the title. */
|
|
25
|
+
description?: string;
|
|
26
|
+
/** Optional content groupings — defaults to one untitled section. */
|
|
27
|
+
sections?: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
items?: Array<{
|
|
30
|
+
title: string;
|
|
31
|
+
url: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
}>;
|
|
34
|
+
}>;
|
|
35
|
+
/** Optional pointer to fuller AHTML manifest. */
|
|
36
|
+
ahtml_manifest_url?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function buildLlmsTxt(cfg: LlmsTxtConfig): string;
|
|
39
|
+
export declare function createLlmsTxtRoute(cfgFn?: () => LlmsTxtConfig | Promise<LlmsTxtConfig>, configOverride?: AHTMLConfig): {
|
|
40
|
+
GET: (_req: Request) => Promise<Response>;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=llms-txt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-txt.d.ts","sourceRoot":"","sources":["../src/llms-txt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACrE,CAAC,CAAC;IACH,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA+BvD;AAED,wBAAgB,kBAAkB,CAChC,KAAK,CAAC,EAAE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,EACpD,cAAc,CAAC,EAAE,WAAW;gBAEH,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;EA6BrD"}
|
package/dist/llms-txt.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llms.txt emitter — compatibility shim with Jeremy Howard's convention
|
|
3
|
+
* (Sept 2024). ~10% of sites adopted as of May 2026; used by Cursor /
|
|
4
|
+
* Continue / Cline / Mintlify-style IDE agents.
|
|
5
|
+
*
|
|
6
|
+
* AHTML is a strict superset. We auto-emit /llms.txt from the same
|
|
7
|
+
* configured routes that feed /.well-known/ahtml.json — adopters get
|
|
8
|
+
* both lanes for free, with zero extra work.
|
|
9
|
+
*
|
|
10
|
+
* Format reference: https://llmstxt.org
|
|
11
|
+
*
|
|
12
|
+
* # Site Name
|
|
13
|
+
*
|
|
14
|
+
* > One-line description of the site
|
|
15
|
+
*
|
|
16
|
+
* ## Section
|
|
17
|
+
*
|
|
18
|
+
* - [Page title](url): one-line description
|
|
19
|
+
*/
|
|
20
|
+
import { getConfig } from './index.js';
|
|
21
|
+
export function buildLlmsTxt(cfg) {
|
|
22
|
+
const lines = [];
|
|
23
|
+
lines.push(`# ${cfg.title ?? 'Site'}`);
|
|
24
|
+
lines.push('');
|
|
25
|
+
if (cfg.description) {
|
|
26
|
+
lines.push(`> ${cfg.description}`);
|
|
27
|
+
lines.push('');
|
|
28
|
+
}
|
|
29
|
+
const sections = cfg.sections && cfg.sections.length
|
|
30
|
+
? cfg.sections
|
|
31
|
+
: [{ name: 'Pages', items: [] }];
|
|
32
|
+
for (const section of sections) {
|
|
33
|
+
lines.push(`## ${section.name}`);
|
|
34
|
+
lines.push('');
|
|
35
|
+
for (const item of section.items ?? []) {
|
|
36
|
+
const tail = item.description ? `: ${item.description}` : '';
|
|
37
|
+
lines.push(`- [${item.title}](${item.url})${tail}`);
|
|
38
|
+
}
|
|
39
|
+
lines.push('');
|
|
40
|
+
}
|
|
41
|
+
if (cfg.ahtml_manifest_url) {
|
|
42
|
+
lines.push('## Machine-readable');
|
|
43
|
+
lines.push('');
|
|
44
|
+
lines.push(`- [AHTML manifest](${cfg.ahtml_manifest_url}): Structured semantic snapshots, typed actions, MCP-compatible tools, OpenAPI`);
|
|
45
|
+
lines.push('');
|
|
46
|
+
}
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
}
|
|
49
|
+
export function createLlmsTxtRoute(cfgFn, configOverride) {
|
|
50
|
+
async function GET(_req) {
|
|
51
|
+
const ahtmlCfg = configOverride ?? getConfig();
|
|
52
|
+
const cfg = cfgFn
|
|
53
|
+
? await cfgFn()
|
|
54
|
+
: {
|
|
55
|
+
title: hostFromUrl(ahtmlCfg.site),
|
|
56
|
+
description: ahtmlCfg.policy?.contact ? `Agents welcome — contact: ${ahtmlCfg.policy.contact}` : undefined,
|
|
57
|
+
sections: [
|
|
58
|
+
{
|
|
59
|
+
name: 'Pages',
|
|
60
|
+
items: (ahtmlCfg.routes ?? []).map((r) => ({
|
|
61
|
+
title: r.path,
|
|
62
|
+
url: ahtmlCfg.site.replace(/\/$/, '') + r.path,
|
|
63
|
+
description: r.page_type,
|
|
64
|
+
})),
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
ahtml_manifest_url: ahtmlCfg.site.replace(/\/$/, '') + '/.well-known/ahtml.json',
|
|
68
|
+
};
|
|
69
|
+
const body = buildLlmsTxt(cfg);
|
|
70
|
+
return new Response(body, {
|
|
71
|
+
status: 200,
|
|
72
|
+
headers: {
|
|
73
|
+
'content-type': 'text/markdown; charset=utf-8',
|
|
74
|
+
'cache-control': 'public, max-age=300',
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return { GET };
|
|
79
|
+
}
|
|
80
|
+
function hostFromUrl(u) {
|
|
81
|
+
try {
|
|
82
|
+
return new URL(u).host;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return u;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=llms-txt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-txt.js","sourceRoot":"","sources":["../src/llms-txt.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emit MCP (Model Context Protocol) tool definitions from AHTML actions.
|
|
3
|
+
*
|
|
4
|
+
* Any AHTML snapshot already carries typed action contracts. By emitting
|
|
5
|
+
* those at /ahtml/mcp.json, the site automatically exposes itself as an
|
|
6
|
+
* MCP server-compatible tool surface — no separate MCP server required.
|
|
7
|
+
*/
|
|
8
|
+
import type { Snapshot } from '@ahtmljs/schema';
|
|
9
|
+
export interface McpToolDefinition {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
inputSchema: Record<string, unknown>;
|
|
13
|
+
annotations?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface McpManifest {
|
|
16
|
+
schema_version: '0.1';
|
|
17
|
+
server: {
|
|
18
|
+
name: string;
|
|
19
|
+
url: string;
|
|
20
|
+
};
|
|
21
|
+
tools: McpToolDefinition[];
|
|
22
|
+
}
|
|
23
|
+
export declare function snapshotsToMcp(server: {
|
|
24
|
+
name: string;
|
|
25
|
+
url: string;
|
|
26
|
+
}, snaps: Snapshot[]): McpManifest;
|
|
27
|
+
export declare function createMcpRoute(getAllSnapshots: () => Snapshot[] | Promise<Snapshot[]>): {
|
|
28
|
+
GET: (req: Request) => Promise<Response>;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,KAAK,CAAC;IACtB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,WAAW,CAYpG;AA0BD,wBAAgB,cAAc,CAAC,eAAe,EAAE,MAAM,QAAQ,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;eAC5D,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;EAapD"}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emit MCP (Model Context Protocol) tool definitions from AHTML actions.
|
|
3
|
+
*
|
|
4
|
+
* Any AHTML snapshot already carries typed action contracts. By emitting
|
|
5
|
+
* those at /ahtml/mcp.json, the site automatically exposes itself as an
|
|
6
|
+
* MCP server-compatible tool surface — no separate MCP server required.
|
|
7
|
+
*/
|
|
8
|
+
export function snapshotsToMcp(server, snaps) {
|
|
9
|
+
const tools = [];
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
for (const s of snaps) {
|
|
12
|
+
for (const a of s.actions) {
|
|
13
|
+
const name = `${s.page_type}.${a.id}`;
|
|
14
|
+
if (seen.has(name))
|
|
15
|
+
continue;
|
|
16
|
+
seen.add(name);
|
|
17
|
+
tools.push(actionToTool(name, a, s));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return { schema_version: '0.1', server, tools };
|
|
21
|
+
}
|
|
22
|
+
function actionToTool(name, a, s) {
|
|
23
|
+
const inputSchema = (a.input && '$ref' in a.input ? { type: 'object' } : a.input) ?? {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {},
|
|
26
|
+
};
|
|
27
|
+
const annotations = {};
|
|
28
|
+
if (a.auth)
|
|
29
|
+
annotations.auth = a.auth;
|
|
30
|
+
if (a.cost)
|
|
31
|
+
annotations.cost = a.cost;
|
|
32
|
+
if (a.reversible)
|
|
33
|
+
annotations.reversible = a.reversible;
|
|
34
|
+
if (a.side_effects)
|
|
35
|
+
annotations.side_effects = a.side_effects;
|
|
36
|
+
if (a.confirmation)
|
|
37
|
+
annotations.confirmation = a.confirmation;
|
|
38
|
+
if (a.rate_limit)
|
|
39
|
+
annotations.rate_limit = a.rate_limit;
|
|
40
|
+
if (a.execute_url)
|
|
41
|
+
annotations.execute_url = a.execute_url;
|
|
42
|
+
if (a.preview_url)
|
|
43
|
+
annotations.preview_url = a.preview_url;
|
|
44
|
+
annotations.snapshot_url = s.url;
|
|
45
|
+
return {
|
|
46
|
+
name,
|
|
47
|
+
description: a.label ?? `${a.category ?? 'action'} on ${s.page_type}`,
|
|
48
|
+
inputSchema: inputSchema,
|
|
49
|
+
annotations,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function createMcpRoute(getAllSnapshots) {
|
|
53
|
+
async function GET(req) {
|
|
54
|
+
const url = new URL(req.url);
|
|
55
|
+
const snaps = await getAllSnapshots();
|
|
56
|
+
const m = snapshotsToMcp({ name: 'ahtml', url: `${url.protocol}//${url.host}` }, snaps);
|
|
57
|
+
return new Response(JSON.stringify(m, null, 2), {
|
|
58
|
+
status: 200,
|
|
59
|
+
headers: { 'content-type': 'application/json' },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return { GET };
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emit an OpenAPI 3.1 document describing the snapshot endpoints and
|
|
3
|
+
* any action endpoints declared on snapshots' actions.
|
|
4
|
+
*/
|
|
5
|
+
import type { Snapshot } from '@ahtmljs/schema';
|
|
6
|
+
export declare function snapshotsToOpenApi(opts: {
|
|
7
|
+
title: string;
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
}, snaps: Snapshot[]): Record<string, unknown>;
|
|
10
|
+
export declare function createOpenApiRoute(getAllSnapshots: () => Snapshot[] | Promise<Snapshot[]>): {
|
|
11
|
+
GET: (req: Request) => Promise<Response>;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=openapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0DvH;AAED,wBAAgB,kBAAkB,CAAC,eAAe,EAAE,MAAM,QAAQ,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;eAChE,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;EAapD"}
|
package/dist/openapi.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emit an OpenAPI 3.1 document describing the snapshot endpoints and
|
|
3
|
+
* any action endpoints declared on snapshots' actions.
|
|
4
|
+
*/
|
|
5
|
+
export function snapshotsToOpenApi(opts, snaps) {
|
|
6
|
+
const paths = {};
|
|
7
|
+
for (const s of snaps) {
|
|
8
|
+
const path = s.url.replace(opts.baseUrl, '') || '/';
|
|
9
|
+
const ahtmlPath = '/ahtml' + path;
|
|
10
|
+
paths[ahtmlPath] = {
|
|
11
|
+
get: {
|
|
12
|
+
summary: `AHTML snapshot of ${path}`,
|
|
13
|
+
responses: {
|
|
14
|
+
'200': {
|
|
15
|
+
description: 'snapshot',
|
|
16
|
+
content: {
|
|
17
|
+
'application/ahtml+text': { schema: { type: 'string' } },
|
|
18
|
+
'application/ahtml+json': { schema: { $ref: 'https://ahtml.dev/schema/v0.1/snapshot.json' } },
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
'304': { description: 'not modified' },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
for (const s of snaps) {
|
|
27
|
+
for (const a of s.actions) {
|
|
28
|
+
if (!a.execute_url)
|
|
29
|
+
continue;
|
|
30
|
+
const verb = (a.method ?? 'post').toLowerCase();
|
|
31
|
+
const path = (paths[a.execute_url] ??= {});
|
|
32
|
+
path[verb] = {
|
|
33
|
+
summary: a.label ?? a.id,
|
|
34
|
+
operationId: a.id,
|
|
35
|
+
security: a.auth && a.auth !== 'none' ? [{ bearer: [] }] : undefined,
|
|
36
|
+
requestBody: a.input ? { content: { 'application/json': { schema: a.input } } } : undefined,
|
|
37
|
+
responses: {
|
|
38
|
+
'200': {
|
|
39
|
+
description: 'success',
|
|
40
|
+
...(a.output && { content: { 'application/json': { schema: a.output } } }),
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
'x-ahtml-cost': a.cost,
|
|
44
|
+
'x-ahtml-reversible': a.reversible,
|
|
45
|
+
'x-ahtml-side-effects': a.side_effects,
|
|
46
|
+
'x-ahtml-confirmation': a.confirmation,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
openapi: '3.1.0',
|
|
52
|
+
info: { title: opts.title, version: '0.1' },
|
|
53
|
+
servers: [{ url: opts.baseUrl }],
|
|
54
|
+
paths,
|
|
55
|
+
components: {
|
|
56
|
+
securitySchemes: {
|
|
57
|
+
bearer: { type: 'http', scheme: 'bearer' },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function createOpenApiRoute(getAllSnapshots) {
|
|
63
|
+
async function GET(req) {
|
|
64
|
+
const url = new URL(req.url);
|
|
65
|
+
const snaps = await getAllSnapshots();
|
|
66
|
+
const doc = snapshotsToOpenApi({ title: 'AHTML', baseUrl: `${url.protocol}//${url.host}` }, snaps);
|
|
67
|
+
return new Response(JSON.stringify(doc, null, 2), {
|
|
68
|
+
status: 200,
|
|
69
|
+
headers: { 'content-type': 'application/json' },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return { GET };
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=openapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
|
package/dist/policy.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy enforcement at the route handler edge.
|
|
3
|
+
*
|
|
4
|
+
* AHTML is opt-in. Sites that don't want agents do not install this plugin
|
|
5
|
+
* and continue to ship HTML behind their existing defenses (CAPTCHA,
|
|
6
|
+
* Cloudflare, etc). Sites that DO want agents install the plugin and use
|
|
7
|
+
* this layer to set the terms:
|
|
8
|
+
*
|
|
9
|
+
* - Identity: agents present a user-agent and (optionally) a signed
|
|
10
|
+
* identity token from the AI provider.
|
|
11
|
+
* - Rate limit: per-IP token bucket.
|
|
12
|
+
* - Auth: certain action endpoints require OAuth2 bearer tokens.
|
|
13
|
+
*
|
|
14
|
+
* This module ONLY enforces the read side (snapshot fetch). Action
|
|
15
|
+
* execution is enforced by the host application's own action handlers
|
|
16
|
+
* — AHTML just publishes the *contract* that says auth is required.
|
|
17
|
+
*/
|
|
18
|
+
import type { AHTMLConfig } from './index.js';
|
|
19
|
+
export interface PolicyDecision {
|
|
20
|
+
deny: boolean;
|
|
21
|
+
response: Response;
|
|
22
|
+
}
|
|
23
|
+
export declare function enforcePolicy(req: Request, config: AHTMLConfig): Promise<PolicyDecision>;
|
|
24
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS9C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAOD,wBAAsB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAe9F"}
|