@farming-labs/docs 0.2.62 → 0.2.63
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/agent-CQTH7NFu.mjs +624 -0
- package/dist/agent-DKKptIgy.mjs +4365 -0
- package/dist/agent-evals-B7MIxuEX.mjs +2144 -0
- package/dist/agent-export-CBgWgPvH.mjs +910 -0
- package/dist/agent-scope-C_U--OZ7.mjs +283 -0
- package/dist/agent-skills-bundle.d.mts +13 -0
- package/dist/agent-skills-bundle.mjs +12 -0
- package/dist/agent-skills-server-CPja6Syt.d.mts +14 -0
- package/dist/agent-skills-server-DraIb6FV.mjs +415 -0
- package/dist/agent-skills-vite.d.mts +31 -0
- package/dist/agent-skills-vite.mjs +70 -0
- package/dist/agents-XWZBub6f.mjs +221 -0
- package/dist/analytics-Bx44lg6d.mjs +177 -0
- package/dist/cli/index.d.mts +15 -0
- package/dist/cli/index.mjs +452 -0
- package/dist/client/react.d.mts +45 -0
- package/dist/client/react.mjs +223 -0
- package/dist/cloud-analytics-CSyFE6SS.mjs +132 -0
- package/dist/cloud-ask-ai-sbpjOR2K.mjs +382 -0
- package/dist/cloud-ask-ai-zpwkdwnF.d.mts +23 -0
- package/dist/cloud-pdNC-tyj.mjs +1615 -0
- package/dist/code-blocks-DnNVNK2M.mjs +871 -0
- package/dist/codeblocks-CFuurVIH.mjs +250 -0
- package/dist/config-Wcdj-D0a.mjs +369 -0
- package/dist/dev-Cmy6DtdF.mjs +1333 -0
- package/dist/docs-cloud-server.d.mts +70 -0
- package/dist/docs-cloud-server.mjs +310 -0
- package/dist/doctor-CJqtzKZT.mjs +2036 -0
- package/dist/downgrade-w7e6Se0L.mjs +184 -0
- package/dist/errors-DbOhkE1h.mjs +20 -0
- package/dist/golden-evaluations-Dsvh0etV.mjs +1503 -0
- package/dist/i18n-CCaFUnAN.mjs +40 -0
- package/dist/index.d.mts +1150 -0
- package/dist/index.mjs +10 -0
- package/dist/init-CQY0Woe3.mjs +1264 -0
- package/dist/mcp-B9dcsivk.mjs +156 -0
- package/dist/mcp.d.mts +298 -0
- package/dist/mcp.mjs +4430 -0
- package/dist/metadata-DWExHQnx.mjs +237 -0
- package/dist/package-version-n5AFur8a.mjs +128 -0
- package/dist/reading-time-CYZ5VvKU.mjs +742 -0
- package/dist/review-Fndl5brR.mjs +673 -0
- package/dist/robots-BIpC4j4P.mjs +201 -0
- package/dist/robots-CUTahhoY.mjs +179 -0
- package/dist/search-B6V6qtiI.mjs +1826 -0
- package/dist/search-CaSyi6H6.d.mts +279 -0
- package/dist/search-DSjCeOk7.mjs +104 -0
- package/dist/server.d.mts +343 -0
- package/dist/server.mjs +14 -0
- package/dist/sitemap-Cykpe3Tz.mjs +249 -0
- package/dist/sitemap-server-C_6Wes83.mjs +1137 -0
- package/dist/standards-discovery-C4HUqMd2.d.mts +227 -0
- package/dist/standards-discovery-jkykaXq1.mjs +519 -0
- package/dist/templates-Bq_P7ctv.mjs +2465 -0
- package/dist/types-lMBIdZg0.d.mts +3315 -0
- package/dist/upgrade-oz-GChgt.mjs +56 -0
- package/dist/utils-DpiIioYb.mjs +225 -0
- package/package.json +1 -1
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
import { $ as renderDocsAgentsDocument, At as buildDocsSitemapManifest, Ft as resolveDocsSitemapConfig, Nt as renderDocsSitemapMarkdown, Pt as renderDocsSitemapXml, S as DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE, a as DEFAULT_AGENT_MD_ROUTE, at as resolveDocsAgentFeedbackConfig, c as DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE, et as renderDocsLlmsTxt, g as DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE, h as DEFAULT_LLMS_FULL_TXT_ROUTE, k as buildDocsAgentDiscoverySpec, l as DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE, n as DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE, o as DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE, rt as renderDocsSkillDocument, t as DEFAULT_AGENTS_MD_ROUTE, tt as renderDocsMarkdownDocument, v as DEFAULT_LLMS_TXT_ROUTE, vt as toDocsMarkdownUrl, x as DEFAULT_SKILL_MD_ROUTE, y as DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE } from "./agent-DKKptIgy.mjs";
|
|
2
|
+
import { A as resolveDocsPublishedAgentSkill, S as buildDocsAgentSkillsIndex, a as DEFAULT_A2A_AGENT_CARD_ROUTE, c as DEFAULT_AGENT_SKILLS_INDEX_ROUTE, u as DEFAULT_AGENT_SKILLS_ROUTE_PREFIX, v as DEFAULT_LEGACY_SKILLS_INDEX_ROUTE, w as buildDocsLegacySkillsIndex, x as buildDocsA2AAgentCard, y as DEFAULT_LEGACY_SKILLS_ROUTE_PREFIX } from "./standards-discovery-jkykaXq1.mjs";
|
|
3
|
+
import "./search-B6V6qtiI.mjs";
|
|
4
|
+
import { t as resolveDocsI18n } from "./i18n-CCaFUnAN.mjs";
|
|
5
|
+
import { c as renderDocsRobotsGeneratedBlock, f as upsertDocsRobotsGeneratedBlock, i as DOCS_ROBOTS_GENERATED_BLOCK_START, r as DOCS_ROBOTS_GENERATED_BLOCK_END, u as resolveDocsRobotsConfig } from "./robots-BIpC4j4P.mjs";
|
|
6
|
+
import "./sitemap-server-C_6Wes83.mjs";
|
|
7
|
+
import "./agent-evals-B7MIxuEX.mjs";
|
|
8
|
+
import { createFilesystemDocsMcpSource, resolveDocsMcpConfig } from "./mcp.mjs";
|
|
9
|
+
import "./code-blocks-DnNVNK2M.mjs";
|
|
10
|
+
import { t as resolveConfiguredAgentSkills } from "./agent-skills-server-DraIb6FV.mjs";
|
|
11
|
+
import "./server.mjs";
|
|
12
|
+
import { _ as resolveDocsContentDir, a as loadDocsConfigModule, d as readNavTitle, g as resolveDocsConfigPath, h as readTopLevelStringProperty, l as readBooleanProperty, m as readTopLevelBooleanProperty, p as readStringProperty, r as extractTopLevelConfigObject, t as extractNestedObjectLiteral } from "./config-Wcdj-D0a.mjs";
|
|
13
|
+
import { t as detectFramework } from "./utils-DpiIioYb.mjs";
|
|
14
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { execFileSync } from "node:child_process";
|
|
17
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
18
|
+
import pc from "picocolors";
|
|
19
|
+
|
|
20
|
+
//#region src/cli/agent-export.ts
|
|
21
|
+
const AGENT_EXPORT_AGENTS_MARKER = "<!-- Generated by @farming-labs/docs agent export. -->";
|
|
22
|
+
const AGENTS_GENERATE_MARKER = "<!-- Generated by @farming-labs/docs agents generate. -->";
|
|
23
|
+
const AGENT_BUNDLE_MANIFEST_ROUTE = "/.well-known/agent-bundle.json";
|
|
24
|
+
const AGENT_BUNDLE_INTERNAL_MANIFEST = ".farming-labs/agent-bundle-manifest.json";
|
|
25
|
+
const SKILL_CANONICAL_ROUTE = "/skills/docs/SKILL.md";
|
|
26
|
+
function parseInlineFlag(arg) {
|
|
27
|
+
const [rawKey, value] = arg.slice(2).split("=", 2);
|
|
28
|
+
return {
|
|
29
|
+
key: rawKey.trim(),
|
|
30
|
+
value
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function parseAgentExportArgs(argv) {
|
|
34
|
+
const parsed = {};
|
|
35
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
36
|
+
const arg = argv[index];
|
|
37
|
+
if (arg === "--help" || arg === "-h") {
|
|
38
|
+
parsed.help = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (arg === "--public") {
|
|
42
|
+
parsed.public = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (arg === "--check") {
|
|
46
|
+
parsed.check = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (arg.startsWith("--config=")) {
|
|
50
|
+
const value = parseInlineFlag(arg).value;
|
|
51
|
+
if (!value) throw new Error("Missing value for --config.");
|
|
52
|
+
parsed.configPath = value;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg === "--config") {
|
|
56
|
+
const value = argv[index + 1];
|
|
57
|
+
if (!value || value.startsWith("--")) throw new Error("Missing value for --config.");
|
|
58
|
+
parsed.configPath = value;
|
|
59
|
+
index += 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`Unknown agent export flag: ${arg}.`);
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
function normalizePathSegment(value) {
|
|
67
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
68
|
+
}
|
|
69
|
+
function normalizeContent(content) {
|
|
70
|
+
return `${content.trimEnd()}\n`;
|
|
71
|
+
}
|
|
72
|
+
function encodeRoutePath(relativePath) {
|
|
73
|
+
return relativePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
74
|
+
}
|
|
75
|
+
function publicFilePath(publicDir, route) {
|
|
76
|
+
let relativeRoute;
|
|
77
|
+
try {
|
|
78
|
+
relativeRoute = route.replace(/^\/+/, "").split("/").map((segment) => decodeURIComponent(segment)).join("/");
|
|
79
|
+
} catch {
|
|
80
|
+
throw new Error(`Agent Bundle route contains invalid URL encoding: ${route}`);
|
|
81
|
+
}
|
|
82
|
+
const resolved = resolveContainedPath(publicDir, relativeRoute);
|
|
83
|
+
if (!resolved) throw new Error(`Agent Bundle route must stay inside the public directory: ${route}`);
|
|
84
|
+
return resolved;
|
|
85
|
+
}
|
|
86
|
+
function publicRelativePath(publicDir, filePath) {
|
|
87
|
+
return path.relative(publicDir, filePath).replace(/\\/g, "/");
|
|
88
|
+
}
|
|
89
|
+
function resolveContainedPath(baseDir, relativePath) {
|
|
90
|
+
const base = path.resolve(baseDir);
|
|
91
|
+
const resolved = path.resolve(base, relativePath);
|
|
92
|
+
if (!isPathWithin(base, resolved)) return void 0;
|
|
93
|
+
const realBase = resolveThroughNearestExistingAncestor(base);
|
|
94
|
+
const realResolved = resolveThroughNearestExistingAncestor(resolved);
|
|
95
|
+
if (!realBase || !realResolved || !isPathWithin(realBase, realResolved)) return void 0;
|
|
96
|
+
return resolved;
|
|
97
|
+
}
|
|
98
|
+
function isPathWithin(baseDir, candidatePath) {
|
|
99
|
+
const relative = path.relative(baseDir, candidatePath);
|
|
100
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolve the existing portion of a path so a symlinked ancestor cannot move a future write or
|
|
104
|
+
* delete outside its declared root. Missing suffixes are appended to the nearest real ancestor.
|
|
105
|
+
*/
|
|
106
|
+
function resolveThroughNearestExistingAncestor(absolutePath) {
|
|
107
|
+
let cursor = path.resolve(absolutePath);
|
|
108
|
+
const missingSegments = [];
|
|
109
|
+
while (true) {
|
|
110
|
+
let exists = false;
|
|
111
|
+
try {
|
|
112
|
+
lstatSync(cursor);
|
|
113
|
+
exists = true;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
const code = error.code;
|
|
116
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") return void 0;
|
|
117
|
+
}
|
|
118
|
+
if (exists) try {
|
|
119
|
+
return path.resolve(realpathSync(cursor), ...missingSegments.reverse());
|
|
120
|
+
} catch {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const parent = path.dirname(cursor);
|
|
124
|
+
if (parent === cursor) return void 0;
|
|
125
|
+
missingSegments.push(path.basename(cursor));
|
|
126
|
+
cursor = parent;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function sha256(content) {
|
|
130
|
+
return createHash("sha256").update(content).digest("hex");
|
|
131
|
+
}
|
|
132
|
+
function byteLength(content) {
|
|
133
|
+
return typeof content === "string" ? Buffer.byteLength(content, "utf-8") : content.byteLength;
|
|
134
|
+
}
|
|
135
|
+
function readExisting(filePath) {
|
|
136
|
+
return existsSync(filePath) ? readFileSync(filePath, "utf-8") : void 0;
|
|
137
|
+
}
|
|
138
|
+
function atomicWriteFile(filePath, content) {
|
|
139
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
140
|
+
const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
141
|
+
try {
|
|
142
|
+
if (typeof content === "string") writeFileSync(temporaryPath, content, "utf-8");
|
|
143
|
+
else writeFileSync(temporaryPath, content);
|
|
144
|
+
renameSync(temporaryPath, filePath);
|
|
145
|
+
} finally {
|
|
146
|
+
if (existsSync(temporaryPath)) rmSync(temporaryPath, { force: true });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function readStaticLlmsConfig(content) {
|
|
150
|
+
const enabled = readTopLevelBooleanProperty(content, "llmsTxt");
|
|
151
|
+
if (enabled !== void 0) return enabled;
|
|
152
|
+
const block = extractNestedObjectLiteral(content, ["llmsTxt"]);
|
|
153
|
+
if (!block) return void 0;
|
|
154
|
+
const maxCharsBlock = extractNestedObjectLiteral(content, ["llmsTxt", "maxChars"]);
|
|
155
|
+
return {
|
|
156
|
+
enabled: readBooleanProperty(block, "enabled") ?? true,
|
|
157
|
+
baseUrl: readStringProperty(block, "baseUrl"),
|
|
158
|
+
siteTitle: readStringProperty(block, "siteTitle"),
|
|
159
|
+
siteDescription: readStringProperty(block, "siteDescription"),
|
|
160
|
+
maxChars: maxCharsBlock ? {
|
|
161
|
+
mode: readStringProperty(maxCharsBlock, "mode"),
|
|
162
|
+
chars: readNumericProperty(maxCharsBlock, "chars")
|
|
163
|
+
} : void 0,
|
|
164
|
+
sections: readStaticLlmsSections(block)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function readNumericProperty(content, key) {
|
|
168
|
+
const match = content.match(new RegExp(`\\b${key}\\b\\s*:\\s*(-?\\d[\\d_]*(?:\\.\\d+)?)`));
|
|
169
|
+
if (!match) return void 0;
|
|
170
|
+
const value = Number(match[1].replace(/_/g, ""));
|
|
171
|
+
return Number.isFinite(value) ? value : void 0;
|
|
172
|
+
}
|
|
173
|
+
function findBalancedDelimiterEnd(content, start, open, close) {
|
|
174
|
+
let depth = 0;
|
|
175
|
+
let quote = null;
|
|
176
|
+
let escaped = false;
|
|
177
|
+
for (let index = start; index < content.length; index += 1) {
|
|
178
|
+
const char = content[index];
|
|
179
|
+
if (quote) {
|
|
180
|
+
if (escaped) escaped = false;
|
|
181
|
+
else if (char === "\\") escaped = true;
|
|
182
|
+
else if (char === quote) quote = null;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (char === "\"" || char === "'" || char === "`") {
|
|
186
|
+
quote = char;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (char === open) depth += 1;
|
|
190
|
+
if (char !== close) continue;
|
|
191
|
+
depth -= 1;
|
|
192
|
+
if (depth === 0) return index;
|
|
193
|
+
}
|
|
194
|
+
return -1;
|
|
195
|
+
}
|
|
196
|
+
function extractArrayProperty(content, key) {
|
|
197
|
+
const keyIndex = content.search(new RegExp(`\\b${key}\\b\\s*:\\s*\\[`));
|
|
198
|
+
if (keyIndex === -1) return void 0;
|
|
199
|
+
const start = content.indexOf("[", keyIndex);
|
|
200
|
+
if (start === -1) return void 0;
|
|
201
|
+
const end = findBalancedDelimiterEnd(content, start, "[", "]");
|
|
202
|
+
return end === -1 ? void 0 : content.slice(start + 1, end);
|
|
203
|
+
}
|
|
204
|
+
function readStaticLlmsSections(content) {
|
|
205
|
+
const sections = extractArrayProperty(content, "sections");
|
|
206
|
+
if (!sections) return void 0;
|
|
207
|
+
const result = [];
|
|
208
|
+
for (let index = 0; index < sections.length; index += 1) {
|
|
209
|
+
if (sections[index] !== "{") continue;
|
|
210
|
+
const end = findBalancedDelimiterEnd(sections, index, "{", "}");
|
|
211
|
+
if (end === -1) break;
|
|
212
|
+
const block = sections.slice(index + 1, end);
|
|
213
|
+
const title = readStringProperty(block, "title");
|
|
214
|
+
const match = readStringProperty(block, "match") ?? readStringArrayProperty(block, "match");
|
|
215
|
+
if (title && (typeof match === "string" || match.length > 0)) {
|
|
216
|
+
const maxCharsBlock = extractNestedObjectLiteral(block, ["maxChars"]);
|
|
217
|
+
result.push({
|
|
218
|
+
title,
|
|
219
|
+
description: readStringProperty(block, "description"),
|
|
220
|
+
match,
|
|
221
|
+
maxChars: maxCharsBlock ? {
|
|
222
|
+
mode: readStringProperty(maxCharsBlock, "mode"),
|
|
223
|
+
chars: readNumericProperty(maxCharsBlock, "chars")
|
|
224
|
+
} : void 0
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
index = end;
|
|
228
|
+
}
|
|
229
|
+
return result.length > 0 ? result : void 0;
|
|
230
|
+
}
|
|
231
|
+
function readStaticSitemapConfig(content) {
|
|
232
|
+
const enabled = readTopLevelBooleanProperty(content, "sitemap");
|
|
233
|
+
if (enabled !== void 0) return enabled;
|
|
234
|
+
const block = extractNestedObjectLiteral(content, ["sitemap"]);
|
|
235
|
+
if (!block) return void 0;
|
|
236
|
+
const xmlBoolean = readTopLevelBooleanProperty(block, "xml");
|
|
237
|
+
const xmlBlock = extractNestedObjectLiteral(content, ["sitemap", "xml"]);
|
|
238
|
+
const markdownBoolean = readTopLevelBooleanProperty(block, "markdown");
|
|
239
|
+
const markdownBlock = extractNestedObjectLiteral(content, ["sitemap", "markdown"]);
|
|
240
|
+
return {
|
|
241
|
+
enabled: readBooleanProperty(block, "enabled") ?? true,
|
|
242
|
+
routePrefix: readStringProperty(block, "routePrefix"),
|
|
243
|
+
baseUrl: readStringProperty(block, "baseUrl"),
|
|
244
|
+
manifestPath: readStringProperty(block, "manifestPath"),
|
|
245
|
+
xml: xmlBoolean ?? (xmlBlock ? {
|
|
246
|
+
enabled: readBooleanProperty(xmlBlock, "enabled"),
|
|
247
|
+
includeLastmod: readBooleanProperty(xmlBlock, "includeLastmod")
|
|
248
|
+
} : void 0),
|
|
249
|
+
markdown: markdownBoolean ?? (markdownBlock ? {
|
|
250
|
+
enabled: readBooleanProperty(markdownBlock, "enabled"),
|
|
251
|
+
includeDescriptions: readBooleanProperty(markdownBlock, "includeDescriptions"),
|
|
252
|
+
includeLastmod: readBooleanProperty(markdownBlock, "includeLastmod"),
|
|
253
|
+
linkTarget: readStringProperty(markdownBlock, "linkTarget")
|
|
254
|
+
} : void 0)
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function readStaticRobotsConfig(content) {
|
|
258
|
+
const enabled = readTopLevelBooleanProperty(content, "robots");
|
|
259
|
+
if (enabled !== void 0) return enabled;
|
|
260
|
+
const block = extractNestedObjectLiteral(content, ["robots"]);
|
|
261
|
+
if (!block) return void 0;
|
|
262
|
+
const ai = readStringProperty(block, "ai");
|
|
263
|
+
return {
|
|
264
|
+
enabled: readBooleanProperty(block, "enabled") ?? true,
|
|
265
|
+
baseUrl: readStringProperty(block, "baseUrl"),
|
|
266
|
+
ai: ai === "allow" || ai === "disallow" ? ai : readBooleanProperty(block, "ai")
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function readStringArrayProperty(content, key) {
|
|
270
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
271
|
+
const match = content.match(new RegExp(`\\b${escaped}\\b\\s*:\\s*\\[([\\s\\S]*?)\\]`));
|
|
272
|
+
if (!match) return [];
|
|
273
|
+
return [...match[1].matchAll(/["']([^"']+)["']/g)].map((value) => value[1]);
|
|
274
|
+
}
|
|
275
|
+
function readStaticI18nConfig(content) {
|
|
276
|
+
const block = extractNestedObjectLiteral(content, ["i18n"]);
|
|
277
|
+
if (!block) return void 0;
|
|
278
|
+
const locales = readStringArrayProperty(block, "locales");
|
|
279
|
+
if (locales.length === 0) return void 0;
|
|
280
|
+
return {
|
|
281
|
+
locales,
|
|
282
|
+
defaultLocale: readStringProperty(block, "defaultLocale")
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function readStaticMetadataDescription(content) {
|
|
286
|
+
const block = extractNestedObjectLiteral(content, ["metadata"]);
|
|
287
|
+
return block ? readStringProperty(block, "description") : void 0;
|
|
288
|
+
}
|
|
289
|
+
function readStaticDocsPath(content) {
|
|
290
|
+
const configured = readTopLevelStringProperty(content, "docsPath");
|
|
291
|
+
if (configured !== void 0) return configured;
|
|
292
|
+
const root = extractTopLevelConfigObject(content) ?? content;
|
|
293
|
+
return /\bdocsPath\b\s*:\s*(["'])\1/.test(root) ? "" : void 0;
|
|
294
|
+
}
|
|
295
|
+
function buildPageUrl(routeEntry, slug, locale) {
|
|
296
|
+
const parts = [
|
|
297
|
+
routeEntry,
|
|
298
|
+
locale,
|
|
299
|
+
slug
|
|
300
|
+
].flatMap((value) => value ? [normalizePathSegment(value)] : []);
|
|
301
|
+
return parts.length > 0 ? `/${parts.join("/")}` : "/";
|
|
302
|
+
}
|
|
303
|
+
function staticMarkdownRoute(page, sourceEntry, routeEntry) {
|
|
304
|
+
if (!routeEntry && !page.slug) return `/${sourceEntry || "docs"}.md`;
|
|
305
|
+
return toDocsMarkdownUrl(page.url);
|
|
306
|
+
}
|
|
307
|
+
async function loadPages(options) {
|
|
308
|
+
const localeInputs = options.i18n ? options.i18n.locales.map((locale) => ({
|
|
309
|
+
locale,
|
|
310
|
+
directory: path.join(options.contentDir, locale)
|
|
311
|
+
})) : [{
|
|
312
|
+
locale: void 0,
|
|
313
|
+
directory: options.contentDir
|
|
314
|
+
}];
|
|
315
|
+
const pages = [];
|
|
316
|
+
for (const input of localeInputs) {
|
|
317
|
+
const source = createFilesystemDocsMcpSource({
|
|
318
|
+
rootDir: options.rootDir,
|
|
319
|
+
entry: options.routeEntry || "docs",
|
|
320
|
+
contentDir: input.directory,
|
|
321
|
+
siteTitle: options.siteTitle,
|
|
322
|
+
ordering: options.ordering
|
|
323
|
+
});
|
|
324
|
+
const scanned = await Promise.resolve(source.getPages());
|
|
325
|
+
const localeRoute = input.locale && input.locale !== options.i18n?.defaultLocale ? input.locale : void 0;
|
|
326
|
+
for (const page of scanned) pages.push({
|
|
327
|
+
locale: input.locale,
|
|
328
|
+
page: {
|
|
329
|
+
...page,
|
|
330
|
+
url: buildPageUrl(options.routeEntry, page.slug, localeRoute)
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return pages;
|
|
335
|
+
}
|
|
336
|
+
function formatDateOnly(value) {
|
|
337
|
+
const parsed = new Date(value);
|
|
338
|
+
if (Number.isNaN(parsed.getTime())) return void 0;
|
|
339
|
+
return parsed.toISOString().slice(0, 10);
|
|
340
|
+
}
|
|
341
|
+
function pageGitLastmod(rootDir, page) {
|
|
342
|
+
if (page.sourcePath) try {
|
|
343
|
+
const lastmod = formatDateOnly(execFileSync("git", [
|
|
344
|
+
"log",
|
|
345
|
+
"-1",
|
|
346
|
+
"--format=%cI",
|
|
347
|
+
"--",
|
|
348
|
+
page.sourcePath
|
|
349
|
+
], {
|
|
350
|
+
cwd: rootDir,
|
|
351
|
+
encoding: "utf-8",
|
|
352
|
+
stdio: [
|
|
353
|
+
"ignore",
|
|
354
|
+
"pipe",
|
|
355
|
+
"ignore"
|
|
356
|
+
]
|
|
357
|
+
}).trim());
|
|
358
|
+
if (lastmod) return lastmod;
|
|
359
|
+
} catch {}
|
|
360
|
+
}
|
|
361
|
+
function deterministicSitemapManifest(options) {
|
|
362
|
+
const manifest = buildDocsSitemapManifest({
|
|
363
|
+
pages: options.pages,
|
|
364
|
+
entry: options.entry,
|
|
365
|
+
siteTitle: options.siteTitle,
|
|
366
|
+
baseUrl: options.baseUrl,
|
|
367
|
+
generatedAt: "1970-01-01T00:00:00.000Z",
|
|
368
|
+
resolveLastmod(page) {
|
|
369
|
+
const lastmod = formatDateOnly(page.lastModified ?? "");
|
|
370
|
+
return lastmod ? {
|
|
371
|
+
lastmod,
|
|
372
|
+
lastmodSource: "git"
|
|
373
|
+
} : void 0;
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
manifest.generatedAt = `${manifest.pages.reduce((current, page) => {
|
|
377
|
+
if (!page.lastmod) return current;
|
|
378
|
+
return !current || page.lastmod > current ? page.lastmod : current;
|
|
379
|
+
}, void 0) ?? "1970-01-01"}T00:00:00.000Z`;
|
|
380
|
+
return manifest;
|
|
381
|
+
}
|
|
382
|
+
function isManagedAgentsContent(content) {
|
|
383
|
+
return content.includes(AGENT_EXPORT_AGENTS_MARKER) || content.includes(AGENTS_GENERATE_MARKER);
|
|
384
|
+
}
|
|
385
|
+
function findPublicAgentsSource(publicDir) {
|
|
386
|
+
for (const route of [
|
|
387
|
+
DEFAULT_AGENTS_MD_ROUTE,
|
|
388
|
+
DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE,
|
|
389
|
+
DEFAULT_AGENT_MD_ROUTE,
|
|
390
|
+
DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE
|
|
391
|
+
]) {
|
|
392
|
+
const content = readExisting(publicFilePath(publicDir, route));
|
|
393
|
+
if (content !== void 0 && !isManagedAgentsContent(content)) return normalizeContent(content);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function addOutput(outputs, publicDir, route, kind, mediaType, content, managed = true, normalizeText = true) {
|
|
397
|
+
const absolutePath = publicFilePath(publicDir, route);
|
|
398
|
+
if (absolutePath === publicFilePath(publicDir, AGENT_BUNDLE_MANIFEST_ROUTE)) throw new Error(`Agent Bundle output collision at ${route} (reserved bundle manifest). Change the conflicting docs slug or configured route.`);
|
|
399
|
+
const collision = outputs.find((output) => output.absolutePath === absolutePath);
|
|
400
|
+
if (collision) throw new Error(`Agent Bundle output collision at ${route} (${collision.kind} and ${kind}). Change the conflicting docs slug or configured route.`);
|
|
401
|
+
outputs.push({
|
|
402
|
+
absolutePath,
|
|
403
|
+
publicPath: publicRelativePath(publicDir, absolutePath),
|
|
404
|
+
route,
|
|
405
|
+
kind,
|
|
406
|
+
mediaType,
|
|
407
|
+
content: typeof content === "string" && normalizeText ? normalizeContent(content) : content,
|
|
408
|
+
managed
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
function assertOutputPathsDoNotCollide(options) {
|
|
412
|
+
const claimed = new Map([[options.publicManifestPath, "public Agent Bundle manifest"], [options.internalManifestPath, "internal Agent Bundle manifest"]]);
|
|
413
|
+
for (const output of options.outputs) {
|
|
414
|
+
const owner = claimed.get(output.absolutePath);
|
|
415
|
+
if (owner) throw new Error(`Agent Bundle output collision at ${output.route} (${owner} and ${output.kind}). Change the conflicting docs slug or configured route.`);
|
|
416
|
+
claimed.set(output.absolutePath, `${output.kind} output`);
|
|
417
|
+
}
|
|
418
|
+
for (const output of options.internalOutputs) {
|
|
419
|
+
const owner = claimed.get(output.absolutePath);
|
|
420
|
+
if (owner) throw new Error(`Agent Bundle output collision at ${output.path} (${owner} and internal ${output.kind}). Change the conflicting manifest path.`);
|
|
421
|
+
claimed.set(output.absolutePath, `internal ${output.kind} output`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function resolveUserOwnedAgentsOutputs(outputs) {
|
|
425
|
+
return outputs.map((output) => {
|
|
426
|
+
if (output.kind !== "agents") return output;
|
|
427
|
+
const current = readExisting(output.absolutePath);
|
|
428
|
+
if (current === void 0 || isManagedAgentsContent(current)) return output;
|
|
429
|
+
return {
|
|
430
|
+
...output,
|
|
431
|
+
content: normalizeContent(current),
|
|
432
|
+
managed: false
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
function resolveUserOwnedOverrides(outputs, previous) {
|
|
437
|
+
const preserveKinds = new Set([
|
|
438
|
+
"page",
|
|
439
|
+
"llms",
|
|
440
|
+
"discovery",
|
|
441
|
+
"skill"
|
|
442
|
+
]);
|
|
443
|
+
const previousByPath = new Map(previous?.files.map((file) => [file.path, file]) ?? []);
|
|
444
|
+
return outputs.map((output) => {
|
|
445
|
+
if (!preserveKinds.has(output.kind)) return output;
|
|
446
|
+
if (output.route.startsWith("/.well-known/agent-skills/") || output.route.startsWith("/.well-known/skills/") || output.route === DEFAULT_A2A_AGENT_CARD_ROUTE) return output;
|
|
447
|
+
if (typeof output.content !== "string") return output;
|
|
448
|
+
const current = readExisting(output.absolutePath);
|
|
449
|
+
if (current === void 0 || current === output.content) return output;
|
|
450
|
+
const previousFile = previousByPath.get(output.publicPath);
|
|
451
|
+
if (previousFile?.managed && sha256(current) === previousFile.sha256) return output;
|
|
452
|
+
return {
|
|
453
|
+
...output,
|
|
454
|
+
content: normalizeContent(current),
|
|
455
|
+
managed: false
|
|
456
|
+
};
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function resolveRobotsOutput(publicDir, entry, sitemap, robots, agentCard = false) {
|
|
460
|
+
const route = "/robots.txt";
|
|
461
|
+
const absolutePath = publicFilePath(publicDir, route);
|
|
462
|
+
const existing = readExisting(absolutePath);
|
|
463
|
+
const generatedBlock = renderDocsRobotsGeneratedBlock({
|
|
464
|
+
entry,
|
|
465
|
+
apiCatalog: false,
|
|
466
|
+
sitemap,
|
|
467
|
+
baseUrl: robots.baseUrl,
|
|
468
|
+
robots,
|
|
469
|
+
agentCard
|
|
470
|
+
});
|
|
471
|
+
const hasManagedBlock = existing?.includes(DOCS_ROBOTS_GENERATED_BLOCK_START) === true && existing.includes(DOCS_ROBOTS_GENERATED_BLOCK_END);
|
|
472
|
+
if (existing !== void 0 && !hasManagedBlock) return {
|
|
473
|
+
absolutePath,
|
|
474
|
+
publicPath: publicRelativePath(publicDir, absolutePath),
|
|
475
|
+
route,
|
|
476
|
+
kind: "robots",
|
|
477
|
+
mediaType: "text/plain",
|
|
478
|
+
content: normalizeContent(existing),
|
|
479
|
+
managed: false
|
|
480
|
+
};
|
|
481
|
+
return {
|
|
482
|
+
absolutePath,
|
|
483
|
+
publicPath: publicRelativePath(publicDir, absolutePath),
|
|
484
|
+
route,
|
|
485
|
+
kind: "robots",
|
|
486
|
+
mediaType: "text/plain",
|
|
487
|
+
content: normalizeContent(existing && hasManagedBlock ? upsertDocsRobotsGeneratedBlock(existing, generatedBlock) : generatedBlock),
|
|
488
|
+
managed: true
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function toManifestFile(output) {
|
|
492
|
+
return {
|
|
493
|
+
path: output.publicPath,
|
|
494
|
+
route: output.route,
|
|
495
|
+
kind: output.kind,
|
|
496
|
+
mediaType: output.mediaType,
|
|
497
|
+
bytes: byteLength(output.content),
|
|
498
|
+
sha256: sha256(output.content),
|
|
499
|
+
managed: output.managed
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function buildAgentBundleManifest(options) {
|
|
503
|
+
const files = [...options.outputs.map(toManifestFile), ...options.orphanedFiles].sort((left, right) => left.path.localeCompare(right.path));
|
|
504
|
+
const internalFiles = [...options.internalOutputs.map((output) => ({
|
|
505
|
+
path: output.path,
|
|
506
|
+
kind: output.kind,
|
|
507
|
+
bytes: byteLength(output.content),
|
|
508
|
+
sha256: sha256(output.content)
|
|
509
|
+
})), ...options.orphanedInternalFiles].sort((left, right) => left.path.localeCompare(right.path));
|
|
510
|
+
const contentHash = sha256([...files.map((file) => `public:${file.path}\0${file.sha256}`), ...internalFiles.map((file) => `internal:${file.path}\0${file.sha256}`)].join("\n"));
|
|
511
|
+
return {
|
|
512
|
+
format: "farming-labs-agent-bundle.v1",
|
|
513
|
+
generator: "@farming-labs/docs",
|
|
514
|
+
entry: options.entry,
|
|
515
|
+
publicDirectory: options.publicDirectory,
|
|
516
|
+
baseUrl: options.baseUrl,
|
|
517
|
+
contentHash,
|
|
518
|
+
files,
|
|
519
|
+
internalFiles,
|
|
520
|
+
pages: options.pages.map(({ page, locale }) => ({
|
|
521
|
+
route: page.url,
|
|
522
|
+
markdownRoute: staticMarkdownRoute(page, options.sourceEntry, options.entry),
|
|
523
|
+
sourcePath: page.sourcePath,
|
|
524
|
+
locale
|
|
525
|
+
})).sort((left, right) => left.markdownRoute.localeCompare(right.markdownRoute))
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function readPreviousManifest(manifestPath) {
|
|
529
|
+
if (!existsSync(manifestPath)) return void 0;
|
|
530
|
+
try {
|
|
531
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
532
|
+
if (manifest.format !== "farming-labs-agent-bundle.v1" || !Array.isArray(manifest.files) || !Array.isArray(manifest.pages)) return;
|
|
533
|
+
return {
|
|
534
|
+
...manifest,
|
|
535
|
+
files: manifest.files.filter((file) => file && typeof file.path === "string" && typeof file.sha256 === "string" && typeof file.managed === "boolean"),
|
|
536
|
+
internalFiles: Array.isArray(manifest.internalFiles) ? manifest.internalFiles.filter((file) => file && typeof file.path === "string" && typeof file.sha256 === "string") : []
|
|
537
|
+
};
|
|
538
|
+
} catch {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function staleManagedPaths(publicDir, previous, outputs) {
|
|
543
|
+
if (!previous) return [];
|
|
544
|
+
const expected = new Set(outputs.map((output) => output.publicPath));
|
|
545
|
+
return previous.files.filter((file) => (file.managed || file.orphaned === true) && !expected.has(file.path)).map((file) => resolvePreviousOutputPath(publicDir, file.path, "public")).filter((filePath) => existsSync(filePath));
|
|
546
|
+
}
|
|
547
|
+
function staleInternalPaths(rootDir, previous, outputs) {
|
|
548
|
+
if (!previous?.internalFiles) return [];
|
|
549
|
+
const expected = new Set(outputs.map((output) => output.path));
|
|
550
|
+
return previous.internalFiles.filter((file) => !expected.has(file.path)).map((file) => resolvePreviousOutputPath(rootDir, file.path, "internal")).filter((filePath) => existsSync(filePath));
|
|
551
|
+
}
|
|
552
|
+
function resolvePreviousOutputPath(baseDir, relativePath, kind) {
|
|
553
|
+
const resolved = resolveContainedPath(baseDir, relativePath);
|
|
554
|
+
if (!resolved) throw new Error(`Previous Agent Bundle manifest contains an unsafe ${kind} output path: ${relativePath}`);
|
|
555
|
+
return resolved;
|
|
556
|
+
}
|
|
557
|
+
function preservedStaleManifestFiles(publicDir, previous, outputs) {
|
|
558
|
+
if (!previous) return [];
|
|
559
|
+
const expected = new Set(outputs.map((output) => output.publicPath));
|
|
560
|
+
return previous.files.flatMap((file) => {
|
|
561
|
+
if (!file.managed && file.orphaned !== true || expected.has(file.path)) return [];
|
|
562
|
+
const filePath = resolvePreviousOutputPath(publicDir, file.path, "public");
|
|
563
|
+
if (!existsSync(filePath)) return [];
|
|
564
|
+
const current = readFileSync(filePath);
|
|
565
|
+
if (file.orphaned !== true && sha256(current) === file.sha256) return [];
|
|
566
|
+
return [{
|
|
567
|
+
...file,
|
|
568
|
+
bytes: byteLength(current),
|
|
569
|
+
sha256: sha256(current),
|
|
570
|
+
managed: false,
|
|
571
|
+
orphaned: true
|
|
572
|
+
}];
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
function preservedStaleInternalManifestFiles(rootDir, previous, outputs) {
|
|
576
|
+
if (!previous?.internalFiles) return [];
|
|
577
|
+
const expected = new Set(outputs.map((output) => output.path));
|
|
578
|
+
return previous.internalFiles.flatMap((file) => {
|
|
579
|
+
if (expected.has(file.path)) return [];
|
|
580
|
+
const filePath = resolvePreviousOutputPath(rootDir, file.path, "internal");
|
|
581
|
+
const current = existsSync(filePath) ? readFileSync(filePath) : void 0;
|
|
582
|
+
if (current === void 0) return [];
|
|
583
|
+
if (file.orphaned !== true && sha256(current) === file.sha256) return [];
|
|
584
|
+
return [{
|
|
585
|
+
...file,
|
|
586
|
+
bytes: byteLength(current),
|
|
587
|
+
sha256: sha256(current),
|
|
588
|
+
orphaned: true
|
|
589
|
+
}];
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
function removeStaleManagedFiles(publicDir, previous, stalePaths) {
|
|
593
|
+
if (!previous) return [];
|
|
594
|
+
const removed = [];
|
|
595
|
+
const previousByPath = new Map(previous.files.map((file) => [resolvePreviousOutputPath(publicDir, file.path, "public"), file]));
|
|
596
|
+
for (const filePath of stalePaths) {
|
|
597
|
+
const previousFile = previousByPath.get(filePath);
|
|
598
|
+
const current = existsSync(filePath) ? readFileSync(filePath) : void 0;
|
|
599
|
+
if (!previousFile?.managed || previousFile.orphaned === true || current === void 0 || sha256(current) !== previousFile.sha256) continue;
|
|
600
|
+
rmSync(filePath);
|
|
601
|
+
removed.push(filePath);
|
|
602
|
+
}
|
|
603
|
+
return removed;
|
|
604
|
+
}
|
|
605
|
+
function removeStaleInternalFiles(rootDir, previous, stalePaths) {
|
|
606
|
+
if (!previous?.internalFiles) return [];
|
|
607
|
+
const previousByPath = new Map(previous.internalFiles.map((file) => [resolvePreviousOutputPath(rootDir, file.path, "internal"), file]));
|
|
608
|
+
const removed = [];
|
|
609
|
+
for (const filePath of stalePaths) {
|
|
610
|
+
const previousFile = previousByPath.get(filePath);
|
|
611
|
+
const current = readExisting(filePath);
|
|
612
|
+
if (!previousFile || previousFile.orphaned === true || current === void 0 || sha256(current) !== previousFile.sha256) continue;
|
|
613
|
+
rmSync(filePath);
|
|
614
|
+
removed.push(filePath);
|
|
615
|
+
}
|
|
616
|
+
return removed;
|
|
617
|
+
}
|
|
618
|
+
function changedOutputs(outputs) {
|
|
619
|
+
return outputs.filter((output) => !outputMatchesExisting(output));
|
|
620
|
+
}
|
|
621
|
+
function outputMatchesExisting(output) {
|
|
622
|
+
if (!existsSync(output.absolutePath)) return false;
|
|
623
|
+
if (typeof output.content === "string") return readExisting(output.absolutePath) === output.content;
|
|
624
|
+
const current = readFileSync(output.absolutePath);
|
|
625
|
+
return current.byteLength === output.content.byteLength && current.equals(Buffer.from(output.content));
|
|
626
|
+
}
|
|
627
|
+
async function exportAgentBundle(options = {}) {
|
|
628
|
+
if (!options.public && !options.check) throw new Error("Pass --public to write the static Agent Bundle into the framework public directory.");
|
|
629
|
+
const rootDir = process.cwd();
|
|
630
|
+
const loadedConfigModule = await loadDocsConfigModule(rootDir, options.configPath);
|
|
631
|
+
const configContent = readFileSync(loadedConfigModule?.path ?? resolveDocsConfigPath(rootDir, options.configPath), "utf-8");
|
|
632
|
+
const config = loadedConfigModule?.config;
|
|
633
|
+
const sourceEntry = normalizePathSegment(config?.entry ?? readTopLevelStringProperty(configContent, "entry") ?? "docs");
|
|
634
|
+
const routeEntry = normalizePathSegment(config?.docsPath ?? readStaticDocsPath(configContent) ?? sourceEntry);
|
|
635
|
+
const contentDir = config?.contentDir ?? resolveDocsContentDir(rootDir, configContent, sourceEntry || "docs");
|
|
636
|
+
const siteTitle = typeof config?.nav?.title === "string" ? config.nav.title : readNavTitle(configContent) ?? "Documentation";
|
|
637
|
+
const metadataDescription = config?.metadata?.description ?? readStaticMetadataDescription(configContent);
|
|
638
|
+
const llmsInput = config?.llmsTxt ?? readStaticLlmsConfig(configContent) ?? true;
|
|
639
|
+
const llmsConfig = typeof llmsInput === "object" ? llmsInput : void 0;
|
|
640
|
+
const llmsEnabled = llmsInput !== false && !(typeof llmsInput === "object" && llmsInput.enabled === false);
|
|
641
|
+
const siteDescription = llmsConfig?.siteDescription ?? metadataDescription;
|
|
642
|
+
const resolvedSiteTitle = llmsConfig?.siteTitle ?? siteTitle;
|
|
643
|
+
const sitemapInput = config?.sitemap ?? readStaticSitemapConfig(configContent) ?? true;
|
|
644
|
+
const robotsInput = config?.robots ?? readStaticRobotsConfig(configContent) ?? true;
|
|
645
|
+
const baseUrl = llmsConfig?.baseUrl ?? (typeof sitemapInput === "object" ? sitemapInput.baseUrl : void 0) ?? (typeof robotsInput === "object" ? robotsInput.baseUrl : void 0);
|
|
646
|
+
const i18n = resolveDocsI18n(config?.i18n ?? readStaticI18nConfig(configContent));
|
|
647
|
+
const publicDirectory = detectFramework(rootDir) === "sveltekit" ? "static" : "public";
|
|
648
|
+
const publicDir = resolveContainedPath(rootDir, publicDirectory);
|
|
649
|
+
if (!publicDir) throw new Error(`Agent Bundle public directory must resolve inside the project root: ${publicDirectory}`);
|
|
650
|
+
const internalManifestPath = resolveContainedPath(rootDir, AGENT_BUNDLE_INTERNAL_MANIFEST);
|
|
651
|
+
if (!internalManifestPath) throw new Error(`Agent Bundle internal manifest must resolve inside the project root: ${AGENT_BUNDLE_INTERNAL_MANIFEST}`);
|
|
652
|
+
const publicManifestPath = publicFilePath(publicDir, AGENT_BUNDLE_MANIFEST_ROUTE);
|
|
653
|
+
const previous = readPreviousManifest(internalManifestPath);
|
|
654
|
+
const localizedPages = (await loadPages({
|
|
655
|
+
rootDir,
|
|
656
|
+
contentDir,
|
|
657
|
+
routeEntry,
|
|
658
|
+
siteTitle: resolvedSiteTitle,
|
|
659
|
+
ordering: config?.ordering,
|
|
660
|
+
i18n
|
|
661
|
+
})).map(({ page, locale }) => {
|
|
662
|
+
const gitLastmod = pageGitLastmod(rootDir, page);
|
|
663
|
+
return {
|
|
664
|
+
locale,
|
|
665
|
+
page: {
|
|
666
|
+
...page,
|
|
667
|
+
lastModified: gitLastmod
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
});
|
|
671
|
+
if (localizedPages.length === 0) throw new Error(`No docs content was found under ${contentDir}.`);
|
|
672
|
+
const pages = localizedPages.map(({ page }) => page);
|
|
673
|
+
const sitemap = resolveDocsSitemapConfig(sitemapInput, { baseUrl });
|
|
674
|
+
const robots = resolveDocsRobotsConfig(robotsInput, { baseUrl: typeof robotsInput === "object" ? robotsInput.baseUrl ?? sitemap.baseUrl : sitemap.baseUrl });
|
|
675
|
+
const mcp = resolveDocsMcpConfig(false, { defaultName: resolvedSiteTitle });
|
|
676
|
+
const feedbackConfig = resolveDocsAgentFeedbackConfig(false);
|
|
677
|
+
const feedback = {
|
|
678
|
+
enabled: feedbackConfig.enabled,
|
|
679
|
+
route: feedbackConfig.route,
|
|
680
|
+
schemaRoute: feedbackConfig.schemaRoute
|
|
681
|
+
};
|
|
682
|
+
const openapi = { enabled: false };
|
|
683
|
+
const llms = {
|
|
684
|
+
enabled: llmsEnabled,
|
|
685
|
+
apiCatalog: false,
|
|
686
|
+
baseUrl,
|
|
687
|
+
siteTitle: resolvedSiteTitle,
|
|
688
|
+
siteDescription,
|
|
689
|
+
maxChars: llmsConfig?.maxChars,
|
|
690
|
+
sections: llmsConfig?.sections,
|
|
691
|
+
openapi
|
|
692
|
+
};
|
|
693
|
+
const documentOptions = {
|
|
694
|
+
origin: baseUrl ?? "/",
|
|
695
|
+
entry: routeEntry || "docs",
|
|
696
|
+
apiRoute: config?.cloud?.apiRoute,
|
|
697
|
+
apiCatalog: false,
|
|
698
|
+
search: false,
|
|
699
|
+
mcp,
|
|
700
|
+
feedback,
|
|
701
|
+
llms,
|
|
702
|
+
sitemap: sitemapInput,
|
|
703
|
+
robots: robotsInput,
|
|
704
|
+
openapi,
|
|
705
|
+
markdown: { acceptHeader: false }
|
|
706
|
+
};
|
|
707
|
+
const configuredAgentSkills = await resolveConfiguredAgentSkills(config?.agent?.skills, { rootDir });
|
|
708
|
+
let outputs = [];
|
|
709
|
+
const internalOutputs = [];
|
|
710
|
+
for (const { page } of localizedPages) addOutput(outputs, publicDir, staticMarkdownRoute(page, sourceEntry, routeEntry), "page", "text/markdown", renderDocsMarkdownDocument({
|
|
711
|
+
...page,
|
|
712
|
+
markdownUrl: staticMarkdownRoute(page, sourceEntry, routeEntry)
|
|
713
|
+
}, {
|
|
714
|
+
llms,
|
|
715
|
+
origin: baseUrl,
|
|
716
|
+
sitemap: sitemapInput
|
|
717
|
+
}));
|
|
718
|
+
if (llmsEnabled) {
|
|
719
|
+
const llmsContent = renderDocsLlmsTxt(pages.map((page) => ({
|
|
720
|
+
url: page.url,
|
|
721
|
+
markdownUrl: staticMarkdownRoute(page, sourceEntry, routeEntry),
|
|
722
|
+
title: page.title,
|
|
723
|
+
description: page.description,
|
|
724
|
+
content: page.content,
|
|
725
|
+
rawContent: page.rawContent,
|
|
726
|
+
agentContent: page.agentContent,
|
|
727
|
+
agentRawContent: page.agentRawContent,
|
|
728
|
+
agentFallbackContent: page.agentFallbackContent,
|
|
729
|
+
agentFallbackRawContent: page.agentFallbackRawContent
|
|
730
|
+
})), llms);
|
|
731
|
+
for (const route of [DEFAULT_LLMS_TXT_ROUTE, DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE]) addOutput(outputs, publicDir, route, "llms", "text/plain", llmsContent.llmsTxt);
|
|
732
|
+
for (const route of [DEFAULT_LLMS_FULL_TXT_ROUTE, DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE]) addOutput(outputs, publicDir, route, "llms", "text/plain", llmsContent.llmsFullTxt);
|
|
733
|
+
const sectionRoutes = new Set(llmsContent.sections.flatMap((section) => [section.route, section.fullRoute]));
|
|
734
|
+
const llmsBaseRoutes = new Set([sourceEntry, routeEntry].filter(Boolean));
|
|
735
|
+
for (const llmsBaseRoute of llmsBaseRoutes) {
|
|
736
|
+
const entryLlmsRoute = `/${llmsBaseRoute}/llms.txt`;
|
|
737
|
+
const entryLlmsFullRoute = `/${llmsBaseRoute}/llms-full.txt`;
|
|
738
|
+
if (!sectionRoutes.has(entryLlmsRoute)) addOutput(outputs, publicDir, entryLlmsRoute, "llms", "text/plain", llmsContent.llmsTxt);
|
|
739
|
+
if (!sectionRoutes.has(entryLlmsFullRoute)) addOutput(outputs, publicDir, entryLlmsFullRoute, "llms", "text/plain", llmsContent.llmsFullTxt);
|
|
740
|
+
}
|
|
741
|
+
for (const section of llmsContent.sections) {
|
|
742
|
+
addOutput(outputs, publicDir, section.route, "llms", "text/plain", section.llmsTxt);
|
|
743
|
+
addOutput(outputs, publicDir, section.fullRoute, "llms", "text/plain", section.llmsFullTxt);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const generatedSkill = renderDocsSkillDocument(documentOptions);
|
|
747
|
+
const rootSkillPath = path.join(rootDir, "skill.md");
|
|
748
|
+
const publicSkill = existsSync(rootSkillPath) ? readFileSync(rootSkillPath, "utf-8") : generatedSkill;
|
|
749
|
+
for (const route of [DEFAULT_SKILL_MD_ROUTE, DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE]) addOutput(outputs, publicDir, route, "skill", "text/markdown", publicSkill);
|
|
750
|
+
addOutput(outputs, publicDir, SKILL_CANONICAL_ROUTE, "skill", "text/markdown", generatedSkill);
|
|
751
|
+
const generatedAgents = `${AGENT_EXPORT_AGENTS_MARKER}\n${renderDocsAgentsDocument(documentOptions)}`;
|
|
752
|
+
const publicAgentsSource = findPublicAgentsSource(publicDir) ?? generatedAgents;
|
|
753
|
+
for (const route of [
|
|
754
|
+
DEFAULT_AGENTS_MD_ROUTE,
|
|
755
|
+
DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE,
|
|
756
|
+
DEFAULT_AGENT_MD_ROUTE,
|
|
757
|
+
DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE
|
|
758
|
+
]) addOutput(outputs, publicDir, route, "agents", "text/markdown", publicAgentsSource);
|
|
759
|
+
outputs = resolveUserOwnedAgentsOutputs(outputs);
|
|
760
|
+
if (sitemap.enabled) {
|
|
761
|
+
const sitemapManifest = deterministicSitemapManifest({
|
|
762
|
+
pages: pages.map((page) => ({
|
|
763
|
+
...page,
|
|
764
|
+
markdownUrl: staticMarkdownRoute(page, sourceEntry, routeEntry)
|
|
765
|
+
})),
|
|
766
|
+
entry: routeEntry || "docs",
|
|
767
|
+
siteTitle: resolvedSiteTitle,
|
|
768
|
+
baseUrl: sitemap.baseUrl
|
|
769
|
+
});
|
|
770
|
+
const sitemapManifestPath = resolveContainedPath(rootDir, sitemap.manifestPath);
|
|
771
|
+
if (!sitemapManifestPath) throw new Error("sitemap.manifestPath must stay inside the project root for agent export.");
|
|
772
|
+
if (sitemapManifestPath === internalManifestPath) throw new Error(`sitemap.manifestPath conflicts with ${AGENT_BUNDLE_INTERNAL_MANIFEST}. Choose a different sitemap manifest path.`);
|
|
773
|
+
internalOutputs.push({
|
|
774
|
+
absolutePath: sitemapManifestPath,
|
|
775
|
+
path: path.relative(rootDir, sitemapManifestPath).replace(/\\/g, "/"),
|
|
776
|
+
kind: "sitemap",
|
|
777
|
+
content: `${JSON.stringify(sitemapManifest, null, 2)}\n`
|
|
778
|
+
});
|
|
779
|
+
if (sitemap.xml.enabled) addOutput(outputs, publicDir, sitemap.xml.route, "sitemap", "application/xml", renderDocsSitemapXml(sitemapManifest, {
|
|
780
|
+
baseUrl: sitemap.baseUrl,
|
|
781
|
+
includeLastmod: sitemap.xml.includeLastmod
|
|
782
|
+
}));
|
|
783
|
+
if (sitemap.markdown.enabled) {
|
|
784
|
+
const sitemapMarkdown = renderDocsSitemapMarkdown(sitemapManifest, {
|
|
785
|
+
baseUrl: sitemap.baseUrl,
|
|
786
|
+
includeDescriptions: sitemap.markdown.includeDescriptions,
|
|
787
|
+
includeLastmod: sitemap.markdown.includeLastmod,
|
|
788
|
+
linkTarget: sitemap.markdown.linkTarget
|
|
789
|
+
});
|
|
790
|
+
for (const route of [
|
|
791
|
+
sitemap.markdown.route,
|
|
792
|
+
sitemap.markdown.docsRoute,
|
|
793
|
+
sitemap.markdown.wellKnownRoute
|
|
794
|
+
]) if (route) addOutput(outputs, publicDir, route, "sitemap", "text/markdown", sitemapMarkdown);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (robots.enabled) outputs.push(resolveRobotsOutput(publicDir, routeEntry || "docs", sitemapInput, robots, Boolean(config?.agent?.a2a)));
|
|
798
|
+
outputs = resolveUserOwnedOverrides(outputs, previous);
|
|
799
|
+
const effectiveSkillOutput = outputs.find((output) => output.route === DEFAULT_SKILL_MD_ROUTE)?.content;
|
|
800
|
+
const allPublishedSkills = [await resolveDocsPublishedAgentSkill({
|
|
801
|
+
preferredDocument: typeof effectiveSkillOutput === "string" ? effectiveSkillOutput : normalizeContent(publicSkill),
|
|
802
|
+
fallbackDocument: normalizeContent(generatedSkill)
|
|
803
|
+
}), ...configuredAgentSkills];
|
|
804
|
+
const publishedNames = /* @__PURE__ */ new Set();
|
|
805
|
+
for (const skill of allPublishedSkills) {
|
|
806
|
+
if (publishedNames.has(skill.name)) throw new Error(`Duplicate published Agent Skill name: ${skill.name}`);
|
|
807
|
+
publishedNames.add(skill.name);
|
|
808
|
+
addOutput(outputs, publicDir, skill.url, "skill", skill.type === "archive" ? "application/gzip" : "text/markdown", skill.content, true, false);
|
|
809
|
+
for (const file of skill.files) {
|
|
810
|
+
if (file.url !== skill.url) addOutput(outputs, publicDir, file.url, "skill", file.mediaType, file.content, true, false);
|
|
811
|
+
if (file.path === "SKILL.md") addOutput(outputs, publicDir, `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/${encodeURIComponent(skill.name)}/skill.md`, "skill", file.mediaType, file.content, true, false);
|
|
812
|
+
const legacyPath = file.path === "SKILL.md" ? "skill.md" : encodeRoutePath(file.path);
|
|
813
|
+
addOutput(outputs, publicDir, `${DEFAULT_LEGACY_SKILLS_ROUTE_PREFIX}/${encodeURIComponent(skill.name)}/${legacyPath}`, "skill", file.mediaType, file.content, true, false);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
addOutput(outputs, publicDir, DEFAULT_AGENT_SKILLS_INDEX_ROUTE, "discovery", "application/json", JSON.stringify(buildDocsAgentSkillsIndex(allPublishedSkills), null, 2));
|
|
817
|
+
addOutput(outputs, publicDir, DEFAULT_LEGACY_SKILLS_INDEX_ROUTE, "discovery", "application/json", JSON.stringify(buildDocsLegacySkillsIndex(allPublishedSkills), null, 2));
|
|
818
|
+
if (config?.agent?.a2a) addOutput(outputs, publicDir, DEFAULT_A2A_AGENT_CARD_ROUTE, "discovery", "application/json", JSON.stringify(buildDocsA2AAgentCard(config.agent.a2a, allPublishedSkills), null, 2));
|
|
819
|
+
const discoveryWithBundle = {
|
|
820
|
+
...buildDocsAgentDiscoverySpec({
|
|
821
|
+
...documentOptions,
|
|
822
|
+
i18n,
|
|
823
|
+
publishedSkills: allPublishedSkills,
|
|
824
|
+
agentCard: config?.agent?.a2a
|
|
825
|
+
}),
|
|
826
|
+
staticBundle: {
|
|
827
|
+
format: "farming-labs-agent-bundle.v1",
|
|
828
|
+
manifest: AGENT_BUNDLE_MANIFEST_ROUTE,
|
|
829
|
+
check: "docs agent export --check",
|
|
830
|
+
locales: i18n ? {
|
|
831
|
+
default: i18n.defaultLocale,
|
|
832
|
+
pathPattern: `${routeEntry ? `/${routeEntry}` : ""}/{locale}/{slug}.md`
|
|
833
|
+
} : void 0
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
const discoveryJson = JSON.stringify(discoveryWithBundle, null, 2);
|
|
837
|
+
for (const route of [DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE, DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE]) addOutput(outputs, publicDir, route, "discovery", "application/json", discoveryJson);
|
|
838
|
+
outputs = resolveUserOwnedOverrides(outputs, previous);
|
|
839
|
+
assertOutputPathsDoNotCollide({
|
|
840
|
+
outputs,
|
|
841
|
+
internalOutputs,
|
|
842
|
+
publicManifestPath,
|
|
843
|
+
internalManifestPath
|
|
844
|
+
});
|
|
845
|
+
outputs.sort((left, right) => left.publicPath.localeCompare(right.publicPath));
|
|
846
|
+
const stalePaths = staleManagedPaths(publicDir, previous, outputs);
|
|
847
|
+
const staleInternal = staleInternalPaths(rootDir, previous, internalOutputs);
|
|
848
|
+
const orphanedFiles = preservedStaleManifestFiles(publicDir, previous, outputs);
|
|
849
|
+
const orphanedInternalFiles = preservedStaleInternalManifestFiles(rootDir, previous, internalOutputs);
|
|
850
|
+
const manifest = buildAgentBundleManifest({
|
|
851
|
+
sourceEntry,
|
|
852
|
+
entry: routeEntry,
|
|
853
|
+
publicDirectory,
|
|
854
|
+
baseUrl,
|
|
855
|
+
outputs,
|
|
856
|
+
internalOutputs,
|
|
857
|
+
orphanedFiles,
|
|
858
|
+
orphanedInternalFiles,
|
|
859
|
+
pages: localizedPages
|
|
860
|
+
});
|
|
861
|
+
const manifestJson = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
862
|
+
const changed = changedOutputs(outputs);
|
|
863
|
+
const changedInternalOutputs = internalOutputs.filter((output) => readExisting(output.absolutePath) !== output.content);
|
|
864
|
+
if (readExisting(publicManifestPath) !== manifestJson) changed.push({
|
|
865
|
+
absolutePath: publicManifestPath,
|
|
866
|
+
publicPath: publicRelativePath(publicDir, publicManifestPath),
|
|
867
|
+
route: AGENT_BUNDLE_MANIFEST_ROUTE,
|
|
868
|
+
kind: "discovery",
|
|
869
|
+
mediaType: "application/json",
|
|
870
|
+
content: manifestJson,
|
|
871
|
+
managed: true
|
|
872
|
+
});
|
|
873
|
+
const internalChanged = readExisting(internalManifestPath) !== manifestJson;
|
|
874
|
+
if (options.check && (changed.length > 0 || changedInternalOutputs.length > 0 || internalChanged || stalePaths.length > 0 || staleInternal.length > 0)) throw new Error(`Static Agent Bundle is stale (${changed.length + changedInternalOutputs.length + (internalChanged ? 1 : 0) + stalePaths.length + staleInternal.length} output(s)). Run \`docs agent export --public\` to update it.`);
|
|
875
|
+
if (!options.check) {
|
|
876
|
+
for (const output of outputs) if (!outputMatchesExisting(output)) atomicWriteFile(output.absolutePath, output.content);
|
|
877
|
+
for (const output of internalOutputs) if (readExisting(output.absolutePath) !== output.content) atomicWriteFile(output.absolutePath, output.content);
|
|
878
|
+
atomicWriteFile(publicManifestPath, manifestJson);
|
|
879
|
+
atomicWriteFile(internalManifestPath, manifestJson);
|
|
880
|
+
const removed = removeStaleManagedFiles(publicDir, previous, stalePaths);
|
|
881
|
+
removed.push(...removeStaleInternalFiles(rootDir, previous, staleInternal));
|
|
882
|
+
for (const filePath of removed) console.log(pc.dim(`Removed ${path.relative(rootDir, filePath).replace(/\\/g, "/")}`));
|
|
883
|
+
}
|
|
884
|
+
const managedCount = manifest.files.filter((file) => file.managed).length;
|
|
885
|
+
const customCount = manifest.files.length - managedCount;
|
|
886
|
+
const orphanedCount = manifest.files.filter((file) => file.orphaned === true).length + manifest.internalFiles.filter((file) => file.orphaned === true).length;
|
|
887
|
+
console.log(pc.green(options.check ? `Static Agent Bundle is current (${manifest.files.length} files, ${manifest.pages.length} pages).` : `Exported Static Agent Bundle (${manifest.files.length} files, ${manifest.pages.length} pages).`));
|
|
888
|
+
console.log(pc.dim(`${publicDirectory}/.well-known/agent-bundle.json`));
|
|
889
|
+
console.log(pc.dim(AGENT_BUNDLE_INTERNAL_MANIFEST));
|
|
890
|
+
if (customCount > 0) console.log(pc.yellow(`Preserved ${customCount} user-owned public file(s).`));
|
|
891
|
+
if (orphanedCount > 0) console.log(pc.yellow(`Preserved ${orphanedCount} modified obsolete output(s); remove them manually, then rerun the export.`));
|
|
892
|
+
}
|
|
893
|
+
function printAgentExportHelp() {
|
|
894
|
+
console.log(`
|
|
895
|
+
${pc.bold("docs agent export")} — Export a complete static Agent Bundle.
|
|
896
|
+
|
|
897
|
+
${pc.dim("Usage:")}
|
|
898
|
+
pnpm exec docs ${pc.cyan("agent export --public")}
|
|
899
|
+
pnpm exec docs ${pc.cyan("agent export --check")}
|
|
900
|
+
|
|
901
|
+
${pc.dim("Options:")}
|
|
902
|
+
${pc.cyan("--public")} Write static agent files to ${pc.dim("public/")} or SvelteKit ${pc.dim("static/")}
|
|
903
|
+
${pc.cyan("--check")} Fail when exported files or their SHA-256 manifest are stale
|
|
904
|
+
${pc.cyan("--config <path>")} Use a custom docs config path instead of ${pc.dim("docs.config.ts[x]")}
|
|
905
|
+
${pc.cyan("-h, --help")} Show this help message
|
|
906
|
+
`);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
//#endregion
|
|
910
|
+
export { exportAgentBundle, parseAgentExportArgs, printAgentExportHelp };
|