@farming-labs/docs 0.2.57 → 0.2.59

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 (46) hide show
  1. package/dist/{agent-CxDzPqtl.mjs → agent-BFqyqEnC.mjs} +557 -71
  2. package/dist/{agent-v6R0V3lm.mjs → agent-DlxriaTs.mjs} +3 -3
  3. package/dist/agent-evals-kJs2Y9xR.mjs +2144 -0
  4. package/dist/{agent-export-CZR20g0e.mjs → agent-export-BgUaiW8f.mjs} +20 -10
  5. package/dist/{agents-BPonjolo.mjs → agents-Djh-HXih.mjs} +15 -7
  6. package/dist/cli/index.mjs +29 -29
  7. package/dist/client/react.d.mts +1 -1
  8. package/dist/{cloud-C45jpBtp.mjs → cloud-BH_sHX64.mjs} +3 -3
  9. package/dist/{cloud-ask-ai-CvRVnwdK.d.mts → cloud-ask-ai-B2WnG4fF.d.mts} +1 -1
  10. package/dist/{code-blocks-C9awWzEQ.mjs → code-blocks-qe0T8-xe.mjs} +1 -1
  11. package/dist/{codeblocks-CSqgqbg-.mjs → codeblocks-Bq67u32v.mjs} +2 -2
  12. package/dist/{dev-BA2kRuCn.mjs → dev-CAQlbguk.mjs} +2 -2
  13. package/dist/docs-cloud-server.d.mts +2 -2
  14. package/dist/docs-cloud-server.mjs +1 -1
  15. package/dist/{doctor-svA9Flsx.mjs → doctor-CO1VMcF_.mjs} +208 -41
  16. package/dist/{downgrade-BStNhyCc.mjs → downgrade-BZs86NVr.mjs} +2 -2
  17. package/dist/{agent-surface-drift-rGFS38Ak.mjs → golden-evaluations-BN9u2wxw.mjs} +60 -4
  18. package/dist/index.d.mts +98 -20
  19. package/dist/index.mjs +6 -5
  20. package/dist/{init-Dm0yDz6v.mjs → init-B_9ENq8Z.mjs} +3 -3
  21. package/dist/{mcp-BYIgKsPn.mjs → mcp-BAJr3wC2.mjs} +6 -6
  22. package/dist/mcp.d.mts +1 -1
  23. package/dist/mcp.mjs +149 -7
  24. package/dist/metadata-BDuewuzq.mjs +237 -0
  25. package/dist/{package-version-DRIc08EU.mjs → package-version-qik_4J6C.mjs} +1 -1
  26. package/dist/{reading-time-W_Id2ayC.mjs → reading-time-BkEft6SD.mjs} +307 -250
  27. package/dist/{review-DZ81wm7h.mjs → review-NC-sOdXn.mjs} +44 -32
  28. package/dist/{robots-mSc98PMp.mjs → robots-DskPvGPw.mjs} +4 -4
  29. package/dist/{robots-DRpoiYAV.mjs → robots-ltltiLJF.mjs} +4 -1
  30. package/dist/{search-9uEaC23e.d.mts → search-C1JitPwi.d.mts} +153 -6
  31. package/dist/{search-OHp36znm.mjs → search-D57JXQLj.mjs} +150 -39
  32. package/dist/{search-AMEoabYU.mjs → search-o4Ud6OXv.mjs} +6 -6
  33. package/dist/server.d.mts +108 -6
  34. package/dist/server.mjs +6 -6
  35. package/dist/{sitemap-Cyc0Bbbn.mjs → sitemap-Cq-Yj_iA.mjs} +7 -7
  36. package/dist/{sitemap-server-zFYctvCa.mjs → sitemap-server-C1ibVKOy.mjs} +2 -1
  37. package/dist/{types-R-DN23yw.d.mts → types-XHABMh_f.d.mts} +96 -6
  38. package/dist/{upgrade-CXYRNV0C.mjs → upgrade-BCJTCW3O.mjs} +2 -2
  39. package/package.json +1 -1
  40. package/dist/agent-evals-BD17jOqL.mjs +0 -1166
  41. /package/dist/{agent-scope-B8lptqCd.mjs → agent-scope-CCaIY1aK.mjs} +0 -0
  42. /package/dist/{cloud-ask-ai-1k0q6OAH.mjs → cloud-ask-ai-hnJfj8-X.mjs} +0 -0
  43. /package/dist/{config-DZbZiYEa.mjs → config-DASewQ0x.mjs} +0 -0
  44. /package/dist/{errors-BFUtdZfC.mjs → errors-CVqZ3kOO.mjs} +0 -0
  45. /package/dist/{templates-1Cod8KrJ.mjs → templates-CfQjpDWW.mjs} +0 -0
  46. /package/dist/{utils-DBCCkkJS.mjs → utils-6UCLxv4B.mjs} +0 -0
@@ -1,5 +1,5 @@
1
- import { S as DEFAULT_MCP_WELL_KNOWN_ROUTE, Xt as normalizePageAgentFrontmatter, Yt as hasStructuredPageAgentContract, c as DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE, f as DEFAULT_DOCS_CONFIG_ROUTE, h as DEFAULT_LLMS_FULL_TXT_ROUTE, i as DEFAULT_AGENT_FEEDBACK_ROUTE, jt as DEFAULT_SITEMAP_XML_ROUTE, kt as DEFAULT_SITEMAP_MD_ROUTE, m as DEFAULT_DOCS_DIAGNOSTICS_ROUTE, t as DEFAULT_AGENTS_MD_ROUTE, v as DEFAULT_LLMS_TXT_ROUTE, w as DEFAULT_SKILL_MD_ROUTE } from "./agent-CxDzPqtl.mjs";
2
- import { n as DEFAULT_ROBOTS_TXT_ROUTE } from "./robots-DRpoiYAV.mjs";
1
+ import { Gt as API_CATALOG_PROFILE_URI, Nt as DEFAULT_SITEMAP_XML_ROUTE, Qt as DEFAULT_API_CATALOG_ROUTE, S as DEFAULT_MCP_WELL_KNOWN_ROUTE, Ut as AGENT_SKILLS_DISCOVERY_SCHEMA_URI, Wt as API_CATALOG_MEDIA_TYPE, Yt as DEFAULT_AGENT_SKILLS_ROUTE_PREFIX, c as DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE, f as DEFAULT_DOCS_CONFIG_ROUTE, h as DEFAULT_LLMS_FULL_TXT_ROUTE, i as DEFAULT_AGENT_FEEDBACK_ROUTE, jt as DEFAULT_SITEMAP_MD_ROUTE, m as DEFAULT_DOCS_DIAGNOSTICS_ROUTE, qt as DEFAULT_AGENT_SKILLS_INDEX_ROUTE, t as DEFAULT_AGENTS_MD_ROUTE, v as DEFAULT_LLMS_TXT_ROUTE, w as DEFAULT_SKILL_MD_ROUTE } from "./agent-BFqyqEnC.mjs";
2
+ import { n as DEFAULT_ROBOTS_TXT_ROUTE } from "./robots-ltltiLJF.mjs";
3
3
  import matter from "gray-matter";
4
4
 
5
5
  //#region src/define-docs.ts
@@ -159,9 +159,89 @@ function extendTheme(baseTheme, extensions) {
159
159
  return deepMerge(baseTheme, extensions);
160
160
  }
161
161
 
162
+ //#endregion
163
+ //#region src/http-link.ts
164
+ /** Split an HTTP list without treating delimiters inside quotes or URI references as separators. */
165
+ function splitHttpList(value, delimiter) {
166
+ const parts = [];
167
+ let start = 0;
168
+ let quoted = false;
169
+ let escaped = false;
170
+ let angleDepth = 0;
171
+ for (let index = 0; index < value.length; index += 1) {
172
+ const character = value[index];
173
+ if (escaped) {
174
+ escaped = false;
175
+ continue;
176
+ }
177
+ if (quoted && character === "\\") {
178
+ escaped = true;
179
+ continue;
180
+ }
181
+ if (character === "\"") {
182
+ quoted = !quoted;
183
+ continue;
184
+ }
185
+ if (!quoted && character === "<") angleDepth += 1;
186
+ if (!quoted && character === ">" && angleDepth > 0) angleDepth -= 1;
187
+ if (!quoted && angleDepth === 0 && character === delimiter) {
188
+ parts.push(value.slice(start, index).trim());
189
+ start = index + 1;
190
+ }
191
+ }
192
+ parts.push(value.slice(start).trim());
193
+ return parts.filter(Boolean);
194
+ }
195
+ function unquoteHttpValue(value) {
196
+ const trimmed = value.trim();
197
+ if (!trimmed.startsWith("\"") || !trimmed.endsWith("\"")) return trimmed;
198
+ return trimmed.slice(1, -1).replace(/\\(.)/g, "$1");
199
+ }
200
+ /** Parse Link header values while preserving the target/relation association. */
201
+ function parseHttpLinkHeader(header) {
202
+ if (!header) return [];
203
+ const links = [];
204
+ for (const value of splitHttpList(header, ",")) {
205
+ const target = value.match(/^\s*<([^>]*)>/);
206
+ if (!target) continue;
207
+ const relations = [];
208
+ for (const rawParameter of splitHttpList(value.slice(target[0].length), ";")) {
209
+ const separator = rawParameter.indexOf("=");
210
+ if (separator < 0 || rawParameter.slice(0, separator).trim().toLowerCase() !== "rel") continue;
211
+ relations.push(...unquoteHttpValue(rawParameter.slice(separator + 1)).toLowerCase().split(/\s+/));
212
+ }
213
+ links.push({
214
+ href: target[1],
215
+ relations: relations.filter(Boolean)
216
+ });
217
+ }
218
+ return links;
219
+ }
220
+ function httpLinkMatchesExpectation(links, expectation, responseUrl) {
221
+ let expectedUrl;
222
+ try {
223
+ expectedUrl = new URL(expectation.href, responseUrl).toString();
224
+ } catch {
225
+ return false;
226
+ }
227
+ return links.some((link) => {
228
+ try {
229
+ return new URL(link.href, responseUrl).toString() === expectedUrl && link.relations.includes(expectation.rel.toLowerCase());
230
+ } catch {
231
+ return false;
232
+ }
233
+ });
234
+ }
235
+ function httpLinkHeaderHasTargetRelation(header, href, rel, responseUrl) {
236
+ return httpLinkMatchesExpectation(parseHttpLinkHeader(header), {
237
+ href,
238
+ rel
239
+ }, responseUrl);
240
+ }
241
+
162
242
  //#endregion
163
243
  //#region src/agent-conformance.ts
164
- const DOCS_AGENT_CONTRACT_VERSION = "1.0";
244
+ const DOCS_AGENT_CONTRACT_VERSION = "1.1";
165
245
  function normalizeOrigin(value) {
166
246
  return value.replace(/\/+$/, "");
167
247
  }
@@ -185,7 +265,118 @@ function createDocsAgentContractCases(options = {}) {
185
265
  request: { url: url(DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE) },
186
266
  expect: {
187
267
  statuses: [200],
188
- contentTypes: json
268
+ contentTypes: json,
269
+ linkRelations: [{
270
+ href: DEFAULT_API_CATALOG_ROUTE,
271
+ rel: "api-catalog"
272
+ }, {
273
+ href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
274
+ rel: "service-meta"
275
+ }]
276
+ }
277
+ },
278
+ {
279
+ surface: "api-catalog",
280
+ request: { url: url(DEFAULT_API_CATALOG_ROUTE) },
281
+ expect: {
282
+ statuses: [200],
283
+ contentTypes: [`${API_CATALOG_MEDIA_TYPE}; profile="${API_CATALOG_PROFILE_URI}"`],
284
+ bodyIncludes: [
285
+ "linkset",
286
+ DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE,
287
+ DEFAULT_AGENT_SKILLS_INDEX_ROUTE
288
+ ],
289
+ linkRelations: [{
290
+ href: DEFAULT_API_CATALOG_ROUTE,
291
+ rel: "api-catalog"
292
+ }]
293
+ }
294
+ },
295
+ {
296
+ surface: "api-catalog-head",
297
+ request: {
298
+ url: url(DEFAULT_API_CATALOG_ROUTE),
299
+ init: { method: "HEAD" }
300
+ },
301
+ expect: {
302
+ statuses: [200],
303
+ contentTypes: [`${API_CATALOG_MEDIA_TYPE}; profile="${API_CATALOG_PROFILE_URI}"`],
304
+ bodyEmpty: true,
305
+ linkRelations: [{
306
+ href: DEFAULT_API_CATALOG_ROUTE,
307
+ rel: "api-catalog"
308
+ }]
309
+ }
310
+ },
311
+ {
312
+ surface: "agent-skills-index",
313
+ request: { url: url(DEFAULT_AGENT_SKILLS_INDEX_ROUTE) },
314
+ expect: {
315
+ statuses: [200],
316
+ contentTypes: json,
317
+ bodyIncludes: [
318
+ "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
319
+ `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`,
320
+ "sha256:"
321
+ ],
322
+ linkRelations: [{
323
+ href: `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`,
324
+ rel: "item"
325
+ }, {
326
+ href: DEFAULT_API_CATALOG_ROUTE,
327
+ rel: "api-catalog"
328
+ }]
329
+ }
330
+ },
331
+ {
332
+ surface: "agent-skills-index-head",
333
+ request: {
334
+ url: url(DEFAULT_AGENT_SKILLS_INDEX_ROUTE),
335
+ init: { method: "HEAD" }
336
+ },
337
+ expect: {
338
+ statuses: [200],
339
+ contentTypes: json,
340
+ bodyEmpty: true,
341
+ linkRelations: [{
342
+ href: DEFAULT_API_CATALOG_ROUTE,
343
+ rel: "api-catalog"
344
+ }]
345
+ }
346
+ },
347
+ {
348
+ surface: "agent-skill",
349
+ request: { url: url(`${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`) },
350
+ expect: {
351
+ statuses: [200],
352
+ contentTypes: markdown,
353
+ bodyIncludes: ["name: docs"],
354
+ linkRelations: [{
355
+ href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
356
+ rel: "collection"
357
+ }, {
358
+ href: DEFAULT_API_CATALOG_ROUTE,
359
+ rel: "api-catalog"
360
+ }]
361
+ }
362
+ },
363
+ {
364
+ surface: "agent-skill-head",
365
+ request: {
366
+ url: url(`${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/docs/SKILL.md`),
367
+ init: { method: "HEAD" }
368
+ },
369
+ expect: {
370
+ statuses: [200],
371
+ contentTypes: markdown,
372
+ bodyEmpty: true,
373
+ linkRelations: [{
374
+ href: DEFAULT_AGENT_SKILLS_INDEX_ROUTE,
375
+ rel: "collection"
376
+ }, {
377
+ href: DEFAULT_API_CATALOG_ROUTE,
378
+ rel: "api-catalog"
379
+ }]
189
380
  }
190
381
  },
191
382
  {
@@ -340,22 +531,112 @@ function createDocsAgentContractCases(options = {}) {
340
531
  }
341
532
  }
342
533
  ];
343
- if (locale) cases.splice(6, 0, {
344
- surface: "markdown-locale",
345
- request: { url: url(`/${entry}.md?lang=${encodeURIComponent(locale)}`) },
346
- expect: {
347
- statuses: [200],
348
- contentTypes: markdown,
349
- bodyIncludes: ["Bonjour"]
350
- }
351
- });
534
+ if (locale) {
535
+ const markdownMissingIndex = cases.findIndex((contractCase) => contractCase.surface === "markdown-missing");
536
+ cases.splice(markdownMissingIndex, 0, {
537
+ surface: "markdown-locale",
538
+ request: { url: url(`/${entry}.md?lang=${encodeURIComponent(locale)}`) },
539
+ expect: {
540
+ statuses: [200],
541
+ contentTypes: markdown,
542
+ bodyIncludes: ["Bonjour"]
543
+ }
544
+ });
545
+ }
352
546
  return cases;
353
547
  }
548
+ function parseParameterizedValue(value) {
549
+ const [rawValue, ...rawParameters] = splitHttpList(value, ";");
550
+ const normalizedValue = rawValue?.trim().toLowerCase();
551
+ if (!normalizedValue) return null;
552
+ const parameters = /* @__PURE__ */ new Map();
553
+ for (const rawParameter of rawParameters) {
554
+ const separator = rawParameter.indexOf("=");
555
+ if (separator < 0) continue;
556
+ const name = rawParameter.slice(0, separator).trim().toLowerCase();
557
+ if (!name) continue;
558
+ parameters.set(name, unquoteHttpValue(rawParameter.slice(separator + 1)));
559
+ }
560
+ return {
561
+ value: normalizedValue,
562
+ parameters
563
+ };
564
+ }
354
565
  function matchesContentType(actual, expected) {
355
- const [mediaType] = actual.split(";", 1);
356
- const normalized = mediaType?.trim().toLowerCase();
357
- if (!normalized) return false;
358
- return expected.some((value) => normalized === value.trim().toLowerCase());
566
+ const parsedActual = parseParameterizedValue(actual);
567
+ if (!parsedActual) return false;
568
+ return expected.some((value) => {
569
+ const parsedExpected = parseParameterizedValue(value);
570
+ if (!parsedExpected || parsedExpected.value !== parsedActual.value) return false;
571
+ return [...parsedExpected.parameters].every(([name, expectedValue]) => parsedActual.parameters.get(name) === expectedValue);
572
+ });
573
+ }
574
+ function asRecord(value) {
575
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
576
+ }
577
+ function isNonEmptyString(value) {
578
+ return typeof value === "string" && value.trim().length > 0;
579
+ }
580
+ async function sha256Utf8(content) {
581
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(content));
582
+ return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
583
+ }
584
+ async function validateAgentSkillsIndex(content, indexUrl, handle) {
585
+ const issues = [];
586
+ let parsed;
587
+ try {
588
+ parsed = JSON.parse(content);
589
+ } catch {
590
+ return ["Agent Skills index did not contain valid JSON"];
591
+ }
592
+ const root = asRecord(parsed);
593
+ if (root?.$schema !== AGENT_SKILLS_DISCOVERY_SCHEMA_URI) issues.push(`Agent Skills index did not declare ${AGENT_SKILLS_DISCOVERY_SCHEMA_URI}`);
594
+ if (!Array.isArray(root?.skills) || root.skills.length === 0) {
595
+ issues.push("Agent Skills index did not declare any skills");
596
+ return issues;
597
+ }
598
+ const seenNames = /* @__PURE__ */ new Set();
599
+ const origin = new URL(indexUrl).origin;
600
+ for (const rawSkill of root.skills) {
601
+ const skill = asRecord(rawSkill);
602
+ const name = skill?.name;
603
+ const description = skill?.description;
604
+ const artifactRoute = skill?.url;
605
+ const digest = skill?.digest;
606
+ if (!isNonEmptyString(name) || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name) || name.length > 64 || skill?.type !== "skill-md" || !isNonEmptyString(description) || description.length > 1024 || !isNonEmptyString(artifactRoute) || typeof digest !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(digest)) {
607
+ issues.push("Agent Skills index contained an invalid skill entry");
608
+ continue;
609
+ }
610
+ if (seenNames.has(name)) {
611
+ issues.push(`Agent Skills index declared duplicate skill ${JSON.stringify(name)}`);
612
+ continue;
613
+ }
614
+ seenNames.add(name);
615
+ let artifactUrl;
616
+ try {
617
+ artifactUrl = new URL(artifactRoute, indexUrl);
618
+ } catch {
619
+ issues.push(`Agent Skills artifact URL for ${JSON.stringify(name)} was invalid`);
620
+ continue;
621
+ }
622
+ const expectedPath = `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/${name}/SKILL.md`;
623
+ if (artifactUrl.origin !== origin || !artifactUrl.pathname.endsWith(expectedPath)) {
624
+ issues.push(`Agent Skills artifact URL for ${JSON.stringify(name)} did not resolve to same-origin ${expectedPath}`);
625
+ continue;
626
+ }
627
+ try {
628
+ const response = await handle(new Request(artifactUrl), "agent-skill");
629
+ const artifact = await response.text();
630
+ if (response.status !== 200) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} returned status ${response.status}`);
631
+ const contentType = response.headers.get("content-type") ?? "";
632
+ if (!matchesContentType(contentType, ["text/markdown"])) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} returned content-type ${contentType || "<missing>"}`);
633
+ const actualDigest = `sha256:${await sha256Utf8(artifact)}`;
634
+ if (actualDigest !== digest) issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} digest ${actualDigest} did not match ${digest}`);
635
+ } catch (error) {
636
+ issues.push(`Agent Skills artifact ${JSON.stringify(artifactRoute)} failed: ${error instanceof Error ? error.message : String(error)}`);
637
+ }
638
+ }
639
+ return issues;
359
640
  }
360
641
  async function runDocsAgentConformance(options) {
361
642
  const cases = createDocsAgentContractCases(options);
@@ -369,6 +650,14 @@ async function runDocsAgentConformance(options) {
369
650
  if (!contractCase.expect.statuses.includes(response.status)) issues.push(`expected status ${contractCase.expect.statuses.join(" or ")}, received ${response.status}`);
370
651
  if (!matchesContentType(contentType, contractCase.expect.contentTypes)) issues.push(`expected content-type ${contractCase.expect.contentTypes.join(" or ")}, received ${contentType || "<missing>"}`);
371
652
  for (const requiredText of contractCase.expect.bodyIncludes ?? []) if (!body.toLowerCase().includes(requiredText.toLowerCase())) issues.push(`response body did not include ${JSON.stringify(requiredText)}`);
653
+ if (contractCase.expect.bodyEmpty && body !== "") issues.push(`expected an empty response body, received ${body.length} characters`);
654
+ if (contractCase.surface === "agent-skills-index") issues.push(...await validateAgentSkillsIndex(body, contractCase.request.url, options.handle));
655
+ for (const [header, requiredValues] of Object.entries(contractCase.expect.headerIncludes ?? {})) {
656
+ const actual = response.headers.get(header) ?? "";
657
+ for (const requiredValue of requiredValues) if (!actual.toLowerCase().includes(requiredValue.toLowerCase())) issues.push(`response header ${JSON.stringify(header)} did not include ${JSON.stringify(requiredValue)}`);
658
+ }
659
+ const parsedLinks = parseHttpLinkHeader(response.headers.get("link"));
660
+ for (const expectation of contractCase.expect.linkRelations ?? []) if (!httpLinkMatchesExpectation(parsedLinks, expectation, contractCase.request.url)) issues.push(`response Link header did not include ${JSON.stringify(expectation.href)} with rel=${JSON.stringify(expectation.rel)} in the same link-value`);
372
661
  results.push({
373
662
  surface: contractCase.surface,
374
663
  passed: issues.length === 0,
@@ -393,238 +682,6 @@ async function runDocsAgentConformance(options) {
393
682
  };
394
683
  }
395
684
 
396
- //#endregion
397
- //#region src/metadata.ts
398
- /**
399
- * Resolve page title using metadata titleTemplate.
400
- * %s is replaced with page title.
401
- */
402
- function resolveTitle(pageTitle, metadata) {
403
- return (metadata?.titleTemplate ?? "%s").replace("%s", pageTitle);
404
- }
405
- /**
406
- * Resolve OG image URL for a page.
407
- * Prefers page.openGraph.images[0], then page.ogImage, then config endpoint/default.
408
- */
409
- function resolveOGImage(page, ogConfig, baseUrl) {
410
- if (page.openGraph?.images?.length) return resolveImageUrl(page.openGraph.images[0].url, baseUrl);
411
- if (!ogConfig?.enabled) return void 0;
412
- if (page.ogImage) return resolveImageUrl(page.ogImage, baseUrl);
413
- if (ogConfig.type === "dynamic" && ogConfig.endpoint) return `${baseUrl ?? ""}${ogConfig.endpoint}`;
414
- return ogConfig.defaultImage;
415
- }
416
- function resolveImageUrl(url, baseUrl) {
417
- if (url.startsWith("/") || url.startsWith("http")) return url;
418
- const base = baseUrl ?? "";
419
- return `${base}${base.length > 0 && !base.endsWith("/") ? "/" : ""}${url}`;
420
- }
421
- /**
422
- * Build the Open Graph metadata object for a page.
423
- * When the page has openGraph in frontmatter, uses it (with title/description filled from page if omitted).
424
- * Otherwise uses ogImage or config (dynamic endpoint / defaultImage).
425
- */
426
- function buildPageOpenGraph(page, ogConfig, baseUrl) {
427
- if (page.openGraph) {
428
- const images = page.openGraph.images?.length ? page.openGraph.images.map((img) => ({
429
- url: resolveImageUrl(img.url, baseUrl),
430
- width: img.width ?? 1200,
431
- height: img.height ?? 630
432
- })) : void 0;
433
- return {
434
- title: page.openGraph.title ?? page.title,
435
- description: page.openGraph.description ?? page.description,
436
- ...images && { images }
437
- };
438
- }
439
- const url = resolveOGImage(page, ogConfig, baseUrl);
440
- if (!url) return void 0;
441
- return {
442
- title: page.title,
443
- ...page.description && { description: page.description },
444
- images: [{
445
- url,
446
- width: 1200,
447
- height: 630
448
- }]
449
- };
450
- }
451
- /**
452
- * Build the Twitter card metadata object for a page.
453
- * When the page has twitter in frontmatter, uses it.
454
- * Otherwise builds from ogImage or config (dynamic endpoint).
455
- */
456
- function buildPageTwitter(page, ogConfig, baseUrl) {
457
- if (page.twitter) {
458
- const images = page.twitter.images?.length ? page.twitter.images.map((url) => resolveImageUrl(url, baseUrl)) : void 0;
459
- return {
460
- ...page.twitter.card && { card: page.twitter.card },
461
- ...page.twitter.title !== void 0 && { title: page.twitter.title },
462
- ...page.twitter.description !== void 0 && { description: page.twitter.description },
463
- ...images && { images }
464
- };
465
- }
466
- const url = resolveOGImage(page, ogConfig, baseUrl);
467
- if (!url) return void 0;
468
- return {
469
- card: "summary_large_image",
470
- title: page.title,
471
- ...page.description && { description: page.description },
472
- images: [url]
473
- };
474
- }
475
- function normalizeBaseUrl(value) {
476
- if (!value) return void 0;
477
- try {
478
- return new URL(value).toString().replace(/\/+$/, "");
479
- } catch {
480
- return;
481
- }
482
- }
483
- function absolutizeUrl(url, baseUrl) {
484
- const base = normalizeBaseUrl(baseUrl);
485
- if (!base) return url;
486
- try {
487
- return new URL(url, `${base}/`).toString();
488
- } catch {
489
- return url;
490
- }
491
- }
492
- function titleFromSegment(segment) {
493
- return segment.replace(/\.[a-z0-9]+$/i, "").replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
494
- }
495
- function normalizeRoutePath(url) {
496
- try {
497
- return new URL(url, "https://farming-labs.local").pathname.replace(/\/+$/, "") || "/";
498
- } catch {
499
- return `/${url.replace(/^\/+/, "")}`.replace(/\/+$/, "") || "/";
500
- }
501
- }
502
- function buildDefaultBreadcrumbs(input) {
503
- const routePath = normalizeRoutePath(input.url);
504
- const entry = (input.entry ?? "docs").replace(/^\/+|\/+$/g, "") || "docs";
505
- const segments = routePath.split("/").filter(Boolean);
506
- const entryParts = entry.split("/").filter(Boolean);
507
- const contentSegments = segments.slice(0, entryParts.length).join("/") === entryParts.join("/") ? segments.slice(entryParts.length) : segments;
508
- const breadcrumbs = [{
509
- name: titleFromSegment(entryParts[entryParts.length - 1] ?? "Docs"),
510
- url: `/${entry}`
511
- }];
512
- let current = `/${entry}`;
513
- contentSegments.forEach((segment, index) => {
514
- current = `${current}/${segment}`.replace(/\/+/g, "/");
515
- breadcrumbs.push({
516
- name: index === contentSegments.length - 1 ? input.title : titleFromSegment(segment),
517
- url: current
518
- });
519
- });
520
- return breadcrumbs;
521
- }
522
- function normalizeDateModified(value) {
523
- if (!value) return void 0;
524
- const date = new Date(value);
525
- return Number.isNaN(date.getTime()) ? value : date.toISOString();
526
- }
527
- function buildAgentContractHowTo(value, fallback) {
528
- const agent = normalizePageAgentFrontmatter(value);
529
- if (!agent || !hasStructuredPageAgentContract(agent)) return void 0;
530
- const steps = [];
531
- for (const command of agent.commands ?? []) {
532
- const run = typeof command === "string" ? command : command.run;
533
- const description = typeof command === "string" ? void 0 : command.description;
534
- const cwd = typeof command === "string" ? void 0 : command.cwd;
535
- steps.push({
536
- "@type": "HowToStep",
537
- position: steps.length + 1,
538
- name: description ?? "Run command",
539
- text: cwd ? `${run} (from ${cwd})` : run
540
- });
541
- }
542
- for (const verification of agent.verification ?? []) {
543
- const description = typeof verification === "string" ? verification : verification.description ?? "Verify the result";
544
- const details = typeof verification === "string" ? verification : [verification.run, verification.expect ? `Expected: ${verification.expect}` : void 0].filter(Boolean).join(". ");
545
- steps.push({
546
- "@type": "HowToStep",
547
- position: steps.length + 1,
548
- name: description,
549
- text: details || description
550
- });
551
- }
552
- const supplies = [...(agent.prerequisites ?? []).map((name) => ({
553
- "@type": "HowToSupply",
554
- name
555
- })), ...(agent.files ?? []).map((name) => ({
556
- "@type": "HowToSupply",
557
- name
558
- }))];
559
- const about = Object.entries(agent.appliesTo ?? {}).flatMap(([kind, values]) => (typeof values === "string" ? [values] : values ?? []).map((name) => ({
560
- "@type": "Thing",
561
- name: `${kind}: ${name}`
562
- })));
563
- return {
564
- "@type": "HowTo",
565
- name: agent.task ?? fallback.title,
566
- ...agent.outcome ?? fallback.description ? { description: agent.outcome ?? fallback.description } : {},
567
- ...about.length > 0 ? { about } : {},
568
- ...supplies.length > 0 ? { supply: supplies } : {},
569
- ...steps.length > 0 ? { step: steps } : {}
570
- };
571
- }
572
- /**
573
- * Resolve the public docs site URL from existing agent-facing config.
574
- *
575
- * The framework intentionally reuses `sitemap.baseUrl`, `llmsTxt.baseUrl`,
576
- * `robots.baseUrl`, or `ai.docsUrl` instead of requiring another setting.
577
- */
578
- function resolveDocsMetadataBaseUrl(config) {
579
- return [
580
- typeof config.sitemap === "object" ? config.sitemap.baseUrl : void 0,
581
- typeof config.llmsTxt === "object" ? config.llmsTxt.baseUrl : void 0,
582
- typeof config.robots === "object" ? config.robots.baseUrl : void 0,
583
- config.ai?.docsUrl
584
- ].map((value) => normalizeBaseUrl(value)).find((value) => Boolean(value));
585
- }
586
- /**
587
- * Build Schema.org JSON-LD for a docs page.
588
- *
589
- * The shape follows Vercel's agent readability guidance: a `TechArticle`
590
- * with title, description, canonical URL, freshness, and breadcrumbs.
591
- */
592
- function buildDocsPageStructuredData(input) {
593
- const breadcrumbs = input.breadcrumbs?.length ? input.breadcrumbs : buildDefaultBreadcrumbs(input);
594
- const result = {
595
- "@context": "https://schema.org",
596
- "@type": "TechArticle",
597
- headline: input.title,
598
- name: input.title,
599
- url: absolutizeUrl(input.url, input.baseUrl),
600
- mainEntityOfPage: {
601
- "@type": "WebPage",
602
- "@id": absolutizeUrl(input.url, input.baseUrl)
603
- },
604
- breadcrumb: {
605
- "@type": "BreadcrumbList",
606
- itemListElement: breadcrumbs.map((item, index) => ({
607
- "@type": "ListItem",
608
- position: index + 1,
609
- name: item.name,
610
- item: absolutizeUrl(item.url, input.baseUrl)
611
- }))
612
- }
613
- };
614
- if (input.description) result.description = input.description;
615
- const agentContract = buildAgentContractHowTo(input.agent, input);
616
- if (agentContract) result.mainEntity = agentContract;
617
- const dateModified = normalizeDateModified(input.dateModified);
618
- if (dateModified) result.dateModified = dateModified;
619
- return result;
620
- }
621
- /**
622
- * Serialize Schema.org JSON-LD for safe insertion into a `<script>` tag.
623
- */
624
- function renderDocsPageStructuredDataJson(input) {
625
- return JSON.stringify(buildDocsPageStructuredData(input)).replace(/</g, "\\u003c");
626
- }
627
-
628
685
  //#endregion
629
686
  //#region src/reading-time.ts
630
687
  function hasExplicitReadingTime(frontmatter) {
@@ -681,4 +738,4 @@ function resolveReadingTimeFromSource(source, wordsPerMinute, options) {
681
738
  }
682
739
 
683
740
  //#endregion
684
- export { extendTheme as _, resolveReadingTimeOptions as a, defineDocs as b, buildPageTwitter as c, resolveOGImage as d, resolveTitle as f, createTheme as g, runDocsAgentConformance as h, resolveReadingTimeFromSource as i, renderDocsPageStructuredDataJson as l, createDocsAgentContractCases as m, resolvePageReadingTime as n, buildDocsPageStructuredData as o, DOCS_AGENT_CONTRACT_VERSION as p, resolveReadingTimeFromContent as r, buildPageOpenGraph as s, estimateReadingTimeMinutes as t, resolveDocsMetadataBaseUrl as u, deepMerge as v, resolveChangelogConfig as y };
741
+ export { resolveReadingTimeOptions as a, runDocsAgentConformance as c, extendTheme as d, deepMerge as f, resolveReadingTimeFromSource as i, httpLinkHeaderHasTargetRelation as l, defineDocs as m, resolvePageReadingTime as n, DOCS_AGENT_CONTRACT_VERSION as o, resolveChangelogConfig as p, resolveReadingTimeFromContent as r, createDocsAgentContractCases as s, estimateReadingTimeMinutes as t, createTheme as u };