@farming-labs/docs 0.2.52 → 0.2.54

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