@relvehq/agent-ready 0.0.0-stage → 0.3.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 +364 -3
- package/SETUP.md +354 -0
- package/dist/bin/agent-ready.d.ts +23 -0
- package/dist/bin/agent-ready.js +444 -0
- package/dist/cli/init.d.ts +70 -0
- package/dist/cli/init.js +366 -0
- package/dist/core/accept.d.ts +8 -0
- package/dist/core/accept.js +76 -0
- package/dist/core/config.d.ts +145 -0
- package/dist/core/config.js +103 -0
- package/dist/core/convert.d.ts +22 -0
- package/dist/core/convert.js +334 -0
- package/dist/core/detect.d.ts +30 -0
- package/dist/core/detect.js +82 -0
- package/dist/core/easy.d.ts +19 -0
- package/dist/core/easy.js +117 -0
- package/dist/core/enrich.d.ts +30 -0
- package/dist/core/enrich.js +155 -0
- package/dist/core/llms.d.ts +11 -0
- package/dist/core/llms.js +71 -0
- package/dist/core/report.d.ts +15 -0
- package/dist/core/report.js +46 -0
- package/dist/core/resolve.d.ts +43 -0
- package/dist/core/resolve.js +204 -0
- package/dist/core/respond.d.ts +18 -0
- package/dist/core/respond.js +59 -0
- package/dist/core/sitemap.d.ts +31 -0
- package/dist/core/sitemap.js +140 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +76 -0
- package/dist/next/dashboard.d.ts +14 -0
- package/dist/next/dashboard.js +56 -0
- package/dist/next/index.d.ts +42 -0
- package/dist/next/index.js +100 -0
- package/dist/next/route.d.ts +26 -0
- package/dist/next/route.js +167 -0
- package/dist/node/index.d.ts +8 -0
- package/dist/node/index.js +191 -0
- package/dist/proof/audit.d.ts +47 -0
- package/dist/proof/audit.js +253 -0
- package/dist/proof/bots.d.ts +24 -0
- package/dist/proof/bots.js +60 -0
- package/dist/proof/dashboard.d.ts +8 -0
- package/dist/proof/dashboard.js +77 -0
- package/dist/proof/hits.d.ts +18 -0
- package/dist/proof/hits.js +51 -0
- package/dist/proof/jsonl.d.ts +5 -0
- package/dist/proof/jsonl.js +80 -0
- package/dist/proof/stats.d.ts +39 -0
- package/dist/proof/stats.js +127 -0
- package/dist/refine/check.d.ts +17 -0
- package/dist/refine/check.js +134 -0
- package/dist/refine/extract.d.ts +7 -0
- package/dist/refine/extract.js +120 -0
- package/dist/refine/file-store.d.ts +6 -0
- package/dist/refine/file-store.js +87 -0
- package/dist/refine/model.d.ts +30 -0
- package/dist/refine/model.js +105 -0
- package/dist/refine/prompts.d.ts +49 -0
- package/dist/refine/prompts.js +178 -0
- package/dist/refine/refine.d.ts +38 -0
- package/dist/refine/refine.js +75 -0
- package/dist/refine/remote.d.ts +57 -0
- package/dist/refine/remote.js +238 -0
- package/dist/refine/run.d.ts +52 -0
- package/dist/refine/run.js +66 -0
- package/dist/refine/types.d.ts +17 -0
- package/dist/refine/types.js +8 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-admin.php +550 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-converter.php +762 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-llms.php +192 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-markdown-builder.php +455 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-negotiation.php +334 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-refiner.php +894 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-response.php +226 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-router.php +421 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-settings.php +201 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-store.php +583 -0
- package/dist/wp/relve-agent-ready/includes/index.php +2 -0
- package/dist/wp/relve-agent-ready/index.php +2 -0
- package/dist/wp/relve-agent-ready/readme.txt +108 -0
- package/dist/wp/relve-agent-ready/relve-agent-ready.php +81 -0
- package/dist/wp/relve-agent-ready/uninstall.php +35 -0
- package/package.json +64 -6
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The easy front door (changes/agent-ready-engine, "easy setup"): a full
|
|
3
|
+
// config from almost nothing.
|
|
4
|
+
//
|
|
5
|
+
// export default createAgentReady(); // all defaults
|
|
6
|
+
// export default createAgentReady({ exclude: ["/members"] });
|
|
7
|
+
//
|
|
8
|
+
// What it works out on its own:
|
|
9
|
+
// site NEXT_PUBLIC_SITE_URL, SITE_URL, NEXT_PUBLIC_APP_URL, URL
|
|
10
|
+
// (Netlify), VERCEL_PROJECT_PRODUCTION_URL, VERCEL_URL, in that order
|
|
11
|
+
// enabled AGENT_READY_ENABLED = "1" | "true" (off otherwise)
|
|
12
|
+
// exclude the always-excluded list + common private pages (account,
|
|
13
|
+
// checkout, cart, dashboard, settings, billing...) + yours
|
|
14
|
+
// llms /llms.txt built from the site's sitemap.xml, titled from the
|
|
15
|
+
// site's host (pass `llms: false` to serve none, or your own)
|
|
16
|
+
// onHit dashboard reporting when AGENT_READY_KEY + AGENT_READY_ENDPOINT
|
|
17
|
+
// are set; nothing otherwise. `hits: store` records every visit
|
|
18
|
+
// in a HitStore as well (proof/hits), for the built-in dashboard
|
|
19
|
+
// Anything passed explicitly wins. The result is a plain resolved config, so
|
|
20
|
+
// it goes everywhere `defineAgentReady()`'s does.
|
|
21
|
+
//
|
|
22
|
+
// EDGE-SAFE. Reads `process.env` only through the injectable `env`.
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.NO_SITE_WARNING = exports.COMMON_PRIVATE_PATHS = void 0;
|
|
25
|
+
exports.siteFromEnv = siteFromEnv;
|
|
26
|
+
exports.enabledFromEnv = enabledFromEnv;
|
|
27
|
+
exports.createAgentReady = createAgentReady;
|
|
28
|
+
const config_1 = require("./config");
|
|
29
|
+
const report_1 = require("./report");
|
|
30
|
+
const hits_1 = require("../proof/hits");
|
|
31
|
+
const sitemap_1 = require("./sitemap");
|
|
32
|
+
/** Private pages most sites have, never served as Markdown by the easy config. */
|
|
33
|
+
exports.COMMON_PRIVATE_PATHS = [
|
|
34
|
+
"/account",
|
|
35
|
+
"/accounts",
|
|
36
|
+
"/my-account",
|
|
37
|
+
"/profile",
|
|
38
|
+
"/settings",
|
|
39
|
+
"/dashboard",
|
|
40
|
+
"/billing",
|
|
41
|
+
"/checkout",
|
|
42
|
+
"/cart",
|
|
43
|
+
"/orders",
|
|
44
|
+
"/admin",
|
|
45
|
+
"/register",
|
|
46
|
+
"/reset-password",
|
|
47
|
+
"/forgot-password",
|
|
48
|
+
"/verify",
|
|
49
|
+
"/oauth",
|
|
50
|
+
"/callback",
|
|
51
|
+
"/preview",
|
|
52
|
+
"/draft",
|
|
53
|
+
];
|
|
54
|
+
const SITE_VARS = ["NEXT_PUBLIC_SITE_URL", "SITE_URL", "NEXT_PUBLIC_APP_URL", "URL", "VERCEL_PROJECT_PRODUCTION_URL", "VERCEL_URL"];
|
|
55
|
+
/** The site's public origin from the usual env vars, or null. */
|
|
56
|
+
function siteFromEnv(env) {
|
|
57
|
+
for (const name of SITE_VARS) {
|
|
58
|
+
const raw = env[name]?.trim();
|
|
59
|
+
if (!raw)
|
|
60
|
+
continue;
|
|
61
|
+
// Vercel's vars are bare hosts ("acme.vercel.app").
|
|
62
|
+
const withScheme = /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
|
|
63
|
+
try {
|
|
64
|
+
return new URL(withScheme).origin;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/** AGENT_READY_ENABLED is "1" or "true". */
|
|
73
|
+
function enabledFromEnv(env) {
|
|
74
|
+
const v = env.AGENT_READY_ENABLED?.trim().toLowerCase();
|
|
75
|
+
return v === "1" || v === "true";
|
|
76
|
+
}
|
|
77
|
+
function createAgentReady(options = {}, env = readEnv()) {
|
|
78
|
+
const { excludeCommon = true, llms, hits, ...rest } = options;
|
|
79
|
+
// No site URL: stay OFF and say so, never throw. A throw here would fail the
|
|
80
|
+
// venture's whole `next build` over one missing env var (seen in the
|
|
81
|
+
// fresh-app install test, 2026-10-21).
|
|
82
|
+
const found = rest.site ?? siteFromEnv(env);
|
|
83
|
+
if (!found)
|
|
84
|
+
warnOnce(exports.NO_SITE_WARNING);
|
|
85
|
+
const site = found ?? "http://localhost:3000";
|
|
86
|
+
const userExclude = rest.exclude ?? [];
|
|
87
|
+
const exclude = excludeCommon ? [...exports.COMMON_PRIVATE_PATHS, ...userExclude] : userExclude;
|
|
88
|
+
const host = new URL(site).hostname.replace(/^www\./, "");
|
|
89
|
+
const resolvedLlms = llms === false || llms === null
|
|
90
|
+
? null
|
|
91
|
+
: llms ?? {
|
|
92
|
+
path: "/llms.txt",
|
|
93
|
+
title: host,
|
|
94
|
+
details: "Every page below is also available as Markdown: add `.md` to its URL or send `Accept: text/markdown`.",
|
|
95
|
+
entries: (0, sitemap_1.sitemapEntries)({ exclude }),
|
|
96
|
+
};
|
|
97
|
+
return (0, config_1.defineAgentReady)({
|
|
98
|
+
...rest,
|
|
99
|
+
site,
|
|
100
|
+
enabled: found ? rest.enabled ?? enabledFromEnv(env) : false,
|
|
101
|
+
exclude,
|
|
102
|
+
llms: resolvedLlms,
|
|
103
|
+
onHit: hits ? (0, hits_1.teeHits)((e) => hits.add(e), rest.onHit ?? (0, report_1.reporterFromEnv)(env)) : rest.onHit ?? (0, report_1.reporterFromEnv)(env) ?? undefined,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
exports.NO_SITE_WARNING = "agent-ready: no site URL, so it stays off. Set NEXT_PUBLIC_SITE_URL (e.g. https://example.com) or pass createAgentReady({ site }).";
|
|
107
|
+
const warned = new Set();
|
|
108
|
+
function warnOnce(message) {
|
|
109
|
+
if (warned.has(message))
|
|
110
|
+
return;
|
|
111
|
+
warned.add(message);
|
|
112
|
+
console.warn(message);
|
|
113
|
+
}
|
|
114
|
+
function readEnv() {
|
|
115
|
+
// `process` is absent in some runtimes; Next.js inlines what it can at build.
|
|
116
|
+
return typeof process !== "undefined" && process.env ? process.env : {};
|
|
117
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MarkdownDoc, Source } from "./config";
|
|
2
|
+
export interface EnrichMeta {
|
|
3
|
+
/** Absolute canonical URL of the HTML page. */
|
|
4
|
+
canonical: string;
|
|
5
|
+
/** Absolute URL the Markdown was produced from (the HTML page). */
|
|
6
|
+
sourceUrl: string;
|
|
7
|
+
source: Source;
|
|
8
|
+
generatedAt?: Date;
|
|
9
|
+
}
|
|
10
|
+
export interface Enriched {
|
|
11
|
+
body: string;
|
|
12
|
+
tokens: number;
|
|
13
|
+
}
|
|
14
|
+
/** Rough token count: ~4 characters per token for English prose. */
|
|
15
|
+
export declare function estimateTokens(text: string): number;
|
|
16
|
+
export declare function hasFrontMatter(markdown: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Builds the final Markdown body. A doc that already carries front matter
|
|
19
|
+
* (hand-written or stored docs usually do) is left exactly as it is, so the
|
|
20
|
+
* package never stacks a second block on top of an author's.
|
|
21
|
+
*/
|
|
22
|
+
export declare function enrich(doc: MarkdownDoc, meta: EnrichMeta): Enriched;
|
|
23
|
+
/** A YAML front-matter block. Empty values are dropped; strings are JSON-quoted (valid YAML). */
|
|
24
|
+
export declare function frontMatter(fields: Record<string, string | number | null | undefined>): string;
|
|
25
|
+
/**
|
|
26
|
+
* A short "## Structured data" section from the page's JSON-LD, or null when
|
|
27
|
+
* nothing useful is there. Only well-known, flat facts are listed; the raw
|
|
28
|
+
* JSON is never dumped.
|
|
29
|
+
*/
|
|
30
|
+
export declare function jsonLdFacts(jsonLd: unknown[]): string | null;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Enrich: YAML front matter, a JSON-LD facts summary and a token estimate.
|
|
3
|
+
//
|
|
4
|
+
// EDGE-SAFE. No Node APIs.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.estimateTokens = estimateTokens;
|
|
7
|
+
exports.hasFrontMatter = hasFrontMatter;
|
|
8
|
+
exports.enrich = enrich;
|
|
9
|
+
exports.frontMatter = frontMatter;
|
|
10
|
+
exports.jsonLdFacts = jsonLdFacts;
|
|
11
|
+
/** Rough token count: ~4 characters per token for English prose. */
|
|
12
|
+
function estimateTokens(text) {
|
|
13
|
+
return Math.ceil(text.length / 4);
|
|
14
|
+
}
|
|
15
|
+
function hasFrontMatter(markdown) {
|
|
16
|
+
return /^---\r?\n[\s\S]*?\r?\n---\r?\n/.test(markdown);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Builds the final Markdown body. A doc that already carries front matter
|
|
20
|
+
* (hand-written or stored docs usually do) is left exactly as it is, so the
|
|
21
|
+
* package never stacks a second block on top of an author's.
|
|
22
|
+
*/
|
|
23
|
+
function enrich(doc, meta) {
|
|
24
|
+
const markdown = doc.markdown.trim();
|
|
25
|
+
if (hasFrontMatter(markdown)) {
|
|
26
|
+
const body = markdown + "\n";
|
|
27
|
+
return { body, tokens: estimateTokens(body) };
|
|
28
|
+
}
|
|
29
|
+
const fm = frontMatter({
|
|
30
|
+
title: doc.title,
|
|
31
|
+
description: doc.description,
|
|
32
|
+
canonical: doc.canonical || meta.canonical,
|
|
33
|
+
source: meta.sourceUrl,
|
|
34
|
+
generated_at: (meta.generatedAt ?? new Date()).toISOString(),
|
|
35
|
+
generator: `agent-ready/${meta.source}`,
|
|
36
|
+
lang: doc.lang,
|
|
37
|
+
});
|
|
38
|
+
const facts = doc.jsonLd && doc.jsonLd.length ? jsonLdFacts(doc.jsonLd) : null;
|
|
39
|
+
const body = [fm, markdown, facts].filter(Boolean).join("\n\n") + "\n";
|
|
40
|
+
return { body, tokens: estimateTokens(body) };
|
|
41
|
+
}
|
|
42
|
+
/** A YAML front-matter block. Empty values are dropped; strings are JSON-quoted (valid YAML). */
|
|
43
|
+
function frontMatter(fields) {
|
|
44
|
+
const lines = ["---"];
|
|
45
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
46
|
+
if (value === null || value === undefined || value === "")
|
|
47
|
+
continue;
|
|
48
|
+
lines.push(`${key}: ${yamlScalar(value)}`);
|
|
49
|
+
}
|
|
50
|
+
lines.push("---");
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
function yamlScalar(value) {
|
|
54
|
+
if (typeof value === "number")
|
|
55
|
+
return String(value);
|
|
56
|
+
const oneLine = value.replace(/\s+/g, " ").trim();
|
|
57
|
+
// Plain scalars are fine for URLs and ISO dates; quote everything else.
|
|
58
|
+
if (/^(https?:\/\/[^\s#]+|\d{4}-\d{2}-\d{2}T[\d:.]+Z|[a-z]{2,3}(-[A-Za-z]{2,4})?|agent-ready\/[a-z]+)$/.test(oneLine)) {
|
|
59
|
+
return oneLine;
|
|
60
|
+
}
|
|
61
|
+
return JSON.stringify(oneLine);
|
|
62
|
+
}
|
|
63
|
+
// JSON-LD types that describe the site chrome rather than the page's subject.
|
|
64
|
+
const SKIP_TYPES = new Set([
|
|
65
|
+
"BreadcrumbList",
|
|
66
|
+
"WebSite",
|
|
67
|
+
"WebPage",
|
|
68
|
+
"SearchAction",
|
|
69
|
+
"SiteNavigationElement",
|
|
70
|
+
"ImageObject",
|
|
71
|
+
"ListItem",
|
|
72
|
+
]);
|
|
73
|
+
const MAX_FACT_OBJECTS = 5;
|
|
74
|
+
/**
|
|
75
|
+
* A short "## Structured data" section from the page's JSON-LD, or null when
|
|
76
|
+
* nothing useful is there. Only well-known, flat facts are listed; the raw
|
|
77
|
+
* JSON is never dumped.
|
|
78
|
+
*/
|
|
79
|
+
function jsonLdFacts(jsonLd) {
|
|
80
|
+
const objects = [];
|
|
81
|
+
const visit = (node) => {
|
|
82
|
+
if (Array.isArray(node))
|
|
83
|
+
return node.forEach(visit);
|
|
84
|
+
if (!node || typeof node !== "object")
|
|
85
|
+
return;
|
|
86
|
+
const o = node;
|
|
87
|
+
if (Array.isArray(o["@graph"]))
|
|
88
|
+
o["@graph"].forEach(visit);
|
|
89
|
+
if (o["@type"])
|
|
90
|
+
objects.push(o);
|
|
91
|
+
};
|
|
92
|
+
jsonLd.forEach(visit);
|
|
93
|
+
const blocks = [];
|
|
94
|
+
for (const o of objects) {
|
|
95
|
+
const types = [].concat(o["@type"]).map(String);
|
|
96
|
+
if (types.every((t) => SKIP_TYPES.has(t)))
|
|
97
|
+
continue;
|
|
98
|
+
const facts = [];
|
|
99
|
+
const add = (label, v) => {
|
|
100
|
+
const s = text(v);
|
|
101
|
+
if (s)
|
|
102
|
+
facts.push([label, s]);
|
|
103
|
+
};
|
|
104
|
+
add("Name", o.name ?? o.headline);
|
|
105
|
+
add("Description", o.description);
|
|
106
|
+
add("URL", o.url);
|
|
107
|
+
add("Category", o.applicationCategory ?? o.category);
|
|
108
|
+
add("Operating system", o.operatingSystem);
|
|
109
|
+
add("Start", o.startDate);
|
|
110
|
+
add("End", o.endDate);
|
|
111
|
+
add("Location", pick(o.location, "name") ?? pick(pick(o.location, "address"), "addressLocality"));
|
|
112
|
+
add("Organizer", pick(o.organizer, "name"));
|
|
113
|
+
add("Author", pick(o.author, "name"));
|
|
114
|
+
add("Published", o.datePublished);
|
|
115
|
+
add("Modified", o.dateModified);
|
|
116
|
+
const offer = first(o.offers);
|
|
117
|
+
if (offer) {
|
|
118
|
+
const price = text(pick(offer, "price") ?? pick(offer, "lowPrice"));
|
|
119
|
+
const currency = text(pick(offer, "priceCurrency"));
|
|
120
|
+
if (price)
|
|
121
|
+
facts.push(["Price", currency ? `${price} ${currency}` : price]);
|
|
122
|
+
}
|
|
123
|
+
const rating = o.aggregateRating;
|
|
124
|
+
if (rating && typeof rating === "object") {
|
|
125
|
+
const value = text(rating.ratingValue);
|
|
126
|
+
const count = text(rating.reviewCount ?? rating.ratingCount);
|
|
127
|
+
if (value)
|
|
128
|
+
facts.push(["Rating", count ? `${value} (${count} reviews)` : value]);
|
|
129
|
+
}
|
|
130
|
+
if (!facts.length)
|
|
131
|
+
continue;
|
|
132
|
+
blocks.push([`### ${types.join(", ")}`, "", ...facts.map(([k, v]) => `- **${k}:** ${v}`)].join("\n"));
|
|
133
|
+
if (blocks.length >= MAX_FACT_OBJECTS)
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
return blocks.length ? ["## Structured data", "", blocks.join("\n\n")].join("\n") : null;
|
|
137
|
+
}
|
|
138
|
+
function first(v) {
|
|
139
|
+
return Array.isArray(v) ? v[0] : v;
|
|
140
|
+
}
|
|
141
|
+
function pick(v, key) {
|
|
142
|
+
const o = first(v);
|
|
143
|
+
return o && typeof o === "object" ? o[key] : undefined;
|
|
144
|
+
}
|
|
145
|
+
function text(v) {
|
|
146
|
+
const x = first(v);
|
|
147
|
+
if (typeof x === "number")
|
|
148
|
+
return String(x);
|
|
149
|
+
if (typeof x !== "string")
|
|
150
|
+
return null;
|
|
151
|
+
const s = x.replace(/\s+/g, " ").trim();
|
|
152
|
+
if (!s)
|
|
153
|
+
return null;
|
|
154
|
+
return s.length > 300 ? s.slice(0, 297) + "..." : s;
|
|
155
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LlmsEntry } from "./config";
|
|
2
|
+
export interface BuildLlmsOptions {
|
|
3
|
+
site: string;
|
|
4
|
+
title: string;
|
|
5
|
+
summary?: string;
|
|
6
|
+
details?: string;
|
|
7
|
+
entries: LlmsEntry[];
|
|
8
|
+
/** Link to the `.md` variant of same-site pages. Default true. */
|
|
9
|
+
markdownLinks?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function buildLlmsTxt(opts: BuildLlmsOptions): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Discover: build an llms.txt (https://llmstxt.org) from config entries.
|
|
3
|
+
//
|
|
4
|
+
// # Title
|
|
5
|
+
//
|
|
6
|
+
// > Summary
|
|
7
|
+
//
|
|
8
|
+
// Details paragraphs
|
|
9
|
+
//
|
|
10
|
+
// ## Section
|
|
11
|
+
//
|
|
12
|
+
// - [Title](https://site/page.md): Description
|
|
13
|
+
//
|
|
14
|
+
// ## Optional
|
|
15
|
+
//
|
|
16
|
+
// - ...
|
|
17
|
+
//
|
|
18
|
+
// Links point at each page's `.md` variant when `markdownLinks` is on (the
|
|
19
|
+
// default), so an agent reading llms.txt lands straight on Markdown.
|
|
20
|
+
//
|
|
21
|
+
// EDGE-SAFE. No Node APIs.
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.buildLlmsTxt = buildLlmsTxt;
|
|
24
|
+
const detect_1 = require("./detect");
|
|
25
|
+
function buildLlmsTxt(opts) {
|
|
26
|
+
const site = new URL(opts.site).origin;
|
|
27
|
+
const out = [`# ${oneLine(opts.title)}`];
|
|
28
|
+
if (opts.summary)
|
|
29
|
+
out.push(`> ${oneLine(opts.summary)}`);
|
|
30
|
+
if (opts.details)
|
|
31
|
+
out.push(opts.details.trim());
|
|
32
|
+
const sections = new Map();
|
|
33
|
+
const optional = [];
|
|
34
|
+
for (const e of opts.entries) {
|
|
35
|
+
const line = entryLine(e, site, opts.markdownLinks !== false);
|
|
36
|
+
if (!line)
|
|
37
|
+
continue;
|
|
38
|
+
if (e.optional) {
|
|
39
|
+
optional.push(line);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const name = oneLine(e.section || "Pages");
|
|
43
|
+
if (!sections.has(name))
|
|
44
|
+
sections.set(name, []);
|
|
45
|
+
sections.get(name).push(line);
|
|
46
|
+
}
|
|
47
|
+
sections.forEach((lines, name) => out.push(`## ${name}`, lines.join("\n")));
|
|
48
|
+
if (optional.length)
|
|
49
|
+
out.push("## Optional", optional.join("\n"));
|
|
50
|
+
return out.join("\n\n") + "\n";
|
|
51
|
+
}
|
|
52
|
+
function entryLine(e, site, markdownLinks) {
|
|
53
|
+
if (!e || !e.title || !e.url)
|
|
54
|
+
return null;
|
|
55
|
+
let url;
|
|
56
|
+
try {
|
|
57
|
+
url = new URL(e.url, site);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (markdownLinks && e.markdown !== false && url.origin === site && !/\.[a-z0-9]+$/i.test(url.pathname)) {
|
|
63
|
+
url.pathname = (0, detect_1.mdUrlPath)(url.pathname.replace(/\/+$/, "") || "/");
|
|
64
|
+
}
|
|
65
|
+
const title = oneLine(e.title).replace(/[[\]]/g, "");
|
|
66
|
+
const desc = e.description ? `: ${oneLine(e.description)}` : "";
|
|
67
|
+
return `- [${title}](${url.href})${desc}`;
|
|
68
|
+
}
|
|
69
|
+
function oneLine(s) {
|
|
70
|
+
return s.replace(/\s+/g, " ").trim();
|
|
71
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HitEvent } from "./config";
|
|
2
|
+
export interface ReporterOptions {
|
|
3
|
+
/** The venture's dashboard key. */
|
|
4
|
+
key: string;
|
|
5
|
+
/** Ingest URL that receives { key, event }. */
|
|
6
|
+
endpoint: string;
|
|
7
|
+
/** Give up after this long. Default 2000 ms. */
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
/** Injected for tests. */
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
}
|
|
12
|
+
/** An `onHit` that sends each event to the dashboard. */
|
|
13
|
+
export declare function reporter(opts: ReporterOptions): (event: HitEvent) => Promise<void>;
|
|
14
|
+
/** A reporter from env (AGENT_READY_KEY + AGENT_READY_ENDPOINT), or null when either is missing. */
|
|
15
|
+
export declare function reporterFromEnv(env: Record<string, string | undefined>): ((event: HitEvent) => Promise<void>) | null;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Measure, the easy way: send every agent visit to a dashboard with one key
|
|
3
|
+
// (changes/agent-ready-engine, "easy setup"). Replaces writing an `onHit`.
|
|
4
|
+
//
|
|
5
|
+
// AGENT_READY_KEY=ar_live_xxx (the venture's key)
|
|
6
|
+
// AGENT_READY_ENDPOINT=https://.../hits (the dashboard's ingest URL)
|
|
7
|
+
//
|
|
8
|
+
// `reporter()` returns an `onHit` that POSTs { key, event } as JSON, with a
|
|
9
|
+
// short timeout and keepalive. Fire-and-forget like every `onHit`: it never
|
|
10
|
+
// throws and the response never waits for it.
|
|
11
|
+
//
|
|
12
|
+
// EDGE-SAFE. No Node APIs.
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.reporter = reporter;
|
|
15
|
+
exports.reporterFromEnv = reporterFromEnv;
|
|
16
|
+
/** An `onHit` that sends each event to the dashboard. */
|
|
17
|
+
function reporter(opts) {
|
|
18
|
+
const f = opts.fetch ?? fetch;
|
|
19
|
+
return async (event) => {
|
|
20
|
+
const ctl = new AbortController();
|
|
21
|
+
const timer = setTimeout(() => ctl.abort(), opts.timeoutMs ?? 2000);
|
|
22
|
+
try {
|
|
23
|
+
await f(opts.endpoint, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${opts.key}` },
|
|
26
|
+
body: JSON.stringify({ key: opts.key, event }),
|
|
27
|
+
keepalive: true,
|
|
28
|
+
signal: ctl.signal,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// A dashboard outage must never touch the agent's response.
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** A reporter from env (AGENT_READY_KEY + AGENT_READY_ENDPOINT), or null when either is missing. */
|
|
40
|
+
function reporterFromEnv(env) {
|
|
41
|
+
const key = env.AGENT_READY_KEY?.trim();
|
|
42
|
+
const endpoint = env.AGENT_READY_ENDPOINT?.trim();
|
|
43
|
+
if (!key || !endpoint || !/^https?:\/\//.test(endpoint))
|
|
44
|
+
return null;
|
|
45
|
+
return reporter({ key, endpoint });
|
|
46
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type MarkdownDoc, type ResolvedAgentReadyConfig } from "./config";
|
|
2
|
+
import type { ConvertResult } from "./convert";
|
|
3
|
+
export interface ResolveInput {
|
|
4
|
+
/** Normalised page path, e.g. "/tool/notion". */
|
|
5
|
+
path: string;
|
|
6
|
+
/** Query string to keep, "" or "?a=b". */
|
|
7
|
+
search: string;
|
|
8
|
+
request: Request;
|
|
9
|
+
}
|
|
10
|
+
export interface Resolved {
|
|
11
|
+
/** Which step produced the doc; null = nothing usable (doc is null). */
|
|
12
|
+
source: "native" | "stored" | "convert" | null;
|
|
13
|
+
doc: MarkdownDoc | null;
|
|
14
|
+
/** Absolute URL of the HTML page the doc stands for. */
|
|
15
|
+
sourceUrl: string;
|
|
16
|
+
/** The origin's HTML response when convert fetched one but couldn't use it. */
|
|
17
|
+
html: {
|
|
18
|
+
status: number;
|
|
19
|
+
contentType: string;
|
|
20
|
+
body: string;
|
|
21
|
+
} | null;
|
|
22
|
+
/** Origin status when the convert step reached the origin. */
|
|
23
|
+
originStatus: number | null;
|
|
24
|
+
}
|
|
25
|
+
export interface ResolveDeps {
|
|
26
|
+
fetch?: typeof fetch;
|
|
27
|
+
convert?: (html: string, opts: {
|
|
28
|
+
url: string;
|
|
29
|
+
minChars?: number;
|
|
30
|
+
}) => ConvertResult | null;
|
|
31
|
+
}
|
|
32
|
+
export declare function resolveMarkdown(cfg: ResolvedAgentReadyConfig, input: ResolveInput, deps?: ResolveDeps): Promise<Resolved>;
|
|
33
|
+
/**
|
|
34
|
+
* Where the converter fetches HTML from: the host the request came in on, so
|
|
35
|
+
* staging converts staging and a test host converts itself. `*.localhost`
|
|
36
|
+
* names become `localhost` (Node's resolver doesn't map them everywhere).
|
|
37
|
+
*/
|
|
38
|
+
export declare function defaultFetchOrigin(request: Request, site: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* "/tool/:slug" -> { slug }, "/docs/*" -> { "*": rest }. Exact otherwise.
|
|
41
|
+
* Returns null when the path doesn't match.
|
|
42
|
+
*/
|
|
43
|
+
export declare function matchPattern(pattern: string, path: string): Record<string, string> | null;
|