@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.
Files changed (49) hide show
  1. package/dist/extractors/data-attrs.d.ts +26 -0
  2. package/dist/extractors/data-attrs.d.ts.map +1 -0
  3. package/dist/extractors/data-attrs.js +163 -0
  4. package/dist/extractors/data-attrs.js.map +1 -0
  5. package/dist/extractors/index.d.ts +12 -0
  6. package/dist/extractors/index.d.ts.map +1 -0
  7. package/dist/extractors/index.js +12 -0
  8. package/dist/extractors/index.js.map +1 -0
  9. package/dist/extractors/merge.d.ts +13 -0
  10. package/dist/extractors/merge.d.ts.map +1 -0
  11. package/dist/extractors/merge.js +25 -0
  12. package/dist/extractors/merge.js.map +1 -0
  13. package/dist/extractors/opengraph.d.ts +7 -0
  14. package/dist/extractors/opengraph.d.ts.map +1 -0
  15. package/dist/extractors/opengraph.js +89 -0
  16. package/dist/extractors/opengraph.js.map +1 -0
  17. package/dist/extractors/schema-org.d.ts +9 -0
  18. package/dist/extractors/schema-org.d.ts.map +1 -0
  19. package/dist/extractors/schema-org.js +104 -0
  20. package/dist/extractors/schema-org.js.map +1 -0
  21. package/dist/handler.d.ts +43 -0
  22. package/dist/handler.d.ts.map +1 -0
  23. package/dist/handler.js +139 -0
  24. package/dist/handler.js.map +1 -0
  25. package/dist/index.d.ts +50 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +48 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/llms-txt.d.ts +42 -0
  30. package/dist/llms-txt.d.ts.map +1 -0
  31. package/dist/llms-txt.js +88 -0
  32. package/dist/llms-txt.js.map +1 -0
  33. package/dist/mcp.d.ts +30 -0
  34. package/dist/mcp.d.ts.map +1 -0
  35. package/dist/mcp.js +64 -0
  36. package/dist/mcp.js.map +1 -0
  37. package/dist/openapi.d.ts +13 -0
  38. package/dist/openapi.d.ts.map +1 -0
  39. package/dist/openapi.js +74 -0
  40. package/dist/openapi.js.map +1 -0
  41. package/dist/policy.d.ts +24 -0
  42. package/dist/policy.d.ts.map +1 -0
  43. package/dist/policy.js +79 -0
  44. package/dist/policy.js.map +1 -0
  45. package/dist/well-known.d.ts +40 -0
  46. package/dist/well-known.d.ts.map +1 -0
  47. package/dist/well-known.js +56 -0
  48. package/dist/well-known.js.map +1 -0
  49. package/package.json +33 -0
package/dist/policy.js ADDED
@@ -0,0 +1,79 @@
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
+ const buckets = new Map();
19
+ const ALLOW = {
20
+ deny: false,
21
+ response: new Response(null),
22
+ };
23
+ export async function enforcePolicy(req, config) {
24
+ if (config.policy?.agents_welcome === false) {
25
+ return deny(403, 'agents_not_welcome', 'this site has not opted into agent traffic');
26
+ }
27
+ const limit = parseRateLimit(config.policy?.rate_limit);
28
+ if (limit) {
29
+ const key = clientKey(req);
30
+ const ok = consume(key, limit);
31
+ if (!ok) {
32
+ return deny(429, 'rate_limited', `rate limit ${config.policy?.rate_limit} exceeded`);
33
+ }
34
+ }
35
+ return ALLOW;
36
+ }
37
+ function deny(status, code, message) {
38
+ return {
39
+ deny: true,
40
+ response: new Response(JSON.stringify({ error: code, message }), {
41
+ status,
42
+ headers: { 'content-type': 'application/json', 'x-ahtml-policy': code },
43
+ }),
44
+ };
45
+ }
46
+ function clientKey(req) {
47
+ const fwd = req.headers.get('x-forwarded-for');
48
+ const real = req.headers.get('x-real-ip');
49
+ return (fwd?.split(',')[0]?.trim() || real || 'anon').toLowerCase();
50
+ }
51
+ /** Token bucket. Refills `tokens` per `windowMs`. */
52
+ function consume(key, limit) {
53
+ const now = Date.now();
54
+ let b = buckets.get(key);
55
+ if (!b) {
56
+ b = { tokens: limit.tokens, last: now };
57
+ buckets.set(key, b);
58
+ }
59
+ const elapsed = now - b.last;
60
+ const refill = (elapsed / limit.windowMs) * limit.tokens;
61
+ b.tokens = Math.min(limit.tokens, b.tokens + refill);
62
+ b.last = now;
63
+ if (b.tokens < 1)
64
+ return false;
65
+ b.tokens -= 1;
66
+ return true;
67
+ }
68
+ function parseRateLimit(s) {
69
+ if (!s)
70
+ return null;
71
+ const m = s.match(/^(\d+)\/(s|sec|min|hr|hour)$/i);
72
+ if (!m)
73
+ return null;
74
+ const tokens = parseInt(m[1], 10);
75
+ const unit = m[2].toLowerCase();
76
+ const windowMs = unit.startsWith('s') ? 1_000 : unit.startsWith('m') ? 60_000 : 3_600_000;
77
+ return { tokens, windowMs };
78
+ }
79
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * /.well-known/ahtml.json — the site-wide AHTML manifest.
3
+ *
4
+ * Tells agents:
5
+ * - this site speaks AHTML
6
+ * - the policy (terms, rate limit, auth scheme, contact)
7
+ * - the route map (what kinds of pages exist + where to fetch their snapshots)
8
+ * - where to find the MCP and OpenAPI emissions
9
+ *
10
+ * Single source of truth. Agents fetch this ONCE, then resolve any URL
11
+ * on the site to its snapshot endpoint deterministically.
12
+ */
13
+ import { type AHTMLConfig } from './index.js';
14
+ export interface WellKnownManifest {
15
+ ahtml: '0.1';
16
+ site: string;
17
+ policy: Record<string, unknown>;
18
+ snapshot_url_template: string;
19
+ routes?: Array<{
20
+ path: string;
21
+ page_type: string;
22
+ snapshot_url: string;
23
+ }>;
24
+ endpoints: {
25
+ snapshot: string;
26
+ diff_param: string;
27
+ mcp?: string;
28
+ openapi?: string;
29
+ };
30
+ formats: Array<{
31
+ media_type: string;
32
+ description: string;
33
+ }>;
34
+ generated_at: string;
35
+ }
36
+ export declare function buildManifest(configOverride?: AHTMLConfig): WellKnownManifest;
37
+ export declare function createWellKnownRoute(configOverride?: AHTMLConfig): {
38
+ GET: (_req: Request) => Response;
39
+ };
40
+ //# 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":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1E,SAAS,EAAE;QACT,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,OAAO,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,aAAa,CAAC,cAAc,CAAC,EAAE,WAAW,GAAG,iBAAiB,CA2B7E;AAED,wBAAgB,oBAAoB,CAAC,cAAc,CAAC,EAAE,WAAW;gBAC5C,OAAO,KAAG,QAAQ;EAYtC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * /.well-known/ahtml.json — the site-wide AHTML manifest.
3
+ *
4
+ * Tells agents:
5
+ * - this site speaks AHTML
6
+ * - the policy (terms, rate limit, auth scheme, contact)
7
+ * - the route map (what kinds of pages exist + where to fetch their snapshots)
8
+ * - where to find the MCP and OpenAPI emissions
9
+ *
10
+ * Single source of truth. Agents fetch this ONCE, then resolve any URL
11
+ * on the site to its snapshot endpoint deterministically.
12
+ */
13
+ import { getConfig } from './index.js';
14
+ export function buildManifest(configOverride) {
15
+ const cfg = configOverride ?? getConfig();
16
+ const base = cfg.site.replace(/\/$/, '');
17
+ const snapshotTemplate = `${base}/ahtml/{path}`;
18
+ return {
19
+ ahtml: '0.1',
20
+ site: base,
21
+ policy: cfg.policy ?? { agents_welcome: true },
22
+ snapshot_url_template: snapshotTemplate,
23
+ routes: cfg.routes?.map((r) => ({
24
+ path: r.path,
25
+ page_type: r.page_type,
26
+ snapshot_url: `${base}/ahtml${r.path.startsWith('/') ? r.path : '/' + r.path}`,
27
+ })),
28
+ endpoints: {
29
+ snapshot: snapshotTemplate,
30
+ diff_param: 'since',
31
+ mcp: cfg.emit_mcp !== false ? `${base}/ahtml/mcp.json` : undefined,
32
+ openapi: cfg.emit_openapi !== false ? `${base}/ahtml/openapi.json` : undefined,
33
+ },
34
+ formats: [
35
+ { media_type: 'application/ahtml+text', description: 'Token-optimal compact text. Default for LLM agents.' },
36
+ { media_type: 'application/ahtml+json', description: 'Canonical JSON. Use for programmatic consumers and signature verification.' },
37
+ { media_type: 'application/ahtml-diff+json', description: 'Returned for ?since=<etag> requests; minimal change list.' },
38
+ ],
39
+ generated_at: new Date().toISOString(),
40
+ };
41
+ }
42
+ export function createWellKnownRoute(configOverride) {
43
+ function GET(_req) {
44
+ const m = buildManifest(configOverride);
45
+ return new Response(JSON.stringify(m, null, 2), {
46
+ status: 200,
47
+ headers: {
48
+ 'content-type': 'application/json',
49
+ 'cache-control': 'public, max-age=300, must-revalidate',
50
+ 'x-ahtml-version': '0.1',
51
+ },
52
+ });
53
+ }
54
+ return { GET };
55
+ }
56
+ //# sourceMappingURL=well-known.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"well-known.js","sourceRoot":"","sources":["../src/well-known.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,SAAS,EAAoB,MAAM,YAAY,CAAC;AAkBzD,MAAM,UAAU,aAAa,CAAC,cAA4B;IACxD,MAAM,GAAG,GAAG,cAAc,IAAI,SAAS,EAAE,CAAC;IAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,gBAAgB,GAAG,GAAG,IAAI,eAAe,CAAC;IAChD,OAAO;QACL,KAAK,EAAE,KAAK;QACZ,IAAI,EAAE,IAAI;QACV,MAAM,EAAG,GAAG,CAAC,MAA6C,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;QACtF,qBAAqB,EAAE,gBAAgB;QACvC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9B,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,YAAY,EAAE,GAAG,IAAI,SAAS,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE;SAC/E,CAAC,CAAC;QACH,SAAS,EAAE;YACT,QAAQ,EAAE,gBAAgB;YAC1B,UAAU,EAAE,OAAO;YACnB,GAAG,EAAE,GAAG,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,iBAAiB,CAAC,CAAC,CAAC,SAAS;YAClE,OAAO,EAAE,GAAG,CAAC,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,qBAAqB,CAAC,CAAC,CAAC,SAAS;SAC/E;QACD,OAAO,EAAE;YACP,EAAE,UAAU,EAAE,wBAAwB,EAAE,WAAW,EAAE,qDAAqD,EAAE;YAC5G,EAAE,UAAU,EAAE,wBAAwB,EAAE,WAAW,EAAE,4EAA4E,EAAE;YACnI,EAAE,UAAU,EAAE,6BAA6B,EAAE,WAAW,EAAE,2DAA2D,EAAE;SACxH;QACD,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACvC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,cAA4B;IAC/D,SAAS,GAAG,CAAC,IAAa;QACxB,MAAM,CAAC,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC;QACxC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;YAC9C,MAAM,EAAE,GAAG;YACX,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,eAAe,EAAE,sCAAsC;gBACvD,iBAAiB,EAAE,KAAK;aACzB;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,CAAC;AACjB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@ahtmljs/next",
3
+ "version": "0.1.0",
4
+ "description": "Next.js plugin for AHTML. Adds /.well-known/ahtml.json, /ahtml/[...path] route handlers, content negotiation, ETag-based incremental fetch, MCP + OpenAPI emitters, and a per-route snapshot builder.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
10
+ "./handler": { "types": "./dist/handler.d.ts", "import": "./dist/handler.js" },
11
+ "./well-known": { "types": "./dist/well-known.d.ts", "import": "./dist/well-known.js" },
12
+ "./llms-txt": { "types": "./dist/llms-txt.d.ts", "import": "./dist/llms-txt.js" },
13
+ "./extractors": { "types": "./dist/extractors/index.d.ts", "import": "./dist/extractors/index.js" },
14
+ "./mcp": { "types": "./dist/mcp.d.ts", "import": "./dist/mcp.js" },
15
+ "./openapi": { "types": "./dist/openapi.d.ts", "import": "./dist/openapi.js" }
16
+ },
17
+ "files": ["dist"],
18
+ "publishConfig": { "access": "public" },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "dev": "tsc -p tsconfig.json --watch"
22
+ },
23
+ "dependencies": {
24
+ "@ahtmljs/schema": "0.1.0"
25
+ },
26
+ "peerDependencies": {
27
+ "next": ">=14"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "next": { "optional": true }
31
+ },
32
+ "license": "MIT"
33
+ }