@farming-labs/theme 0.1.102 → 0.1.104

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.
@@ -0,0 +1,49 @@
1
+ import * as React$1 from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+
4
+ //#region src/code-block-spacing.tsx
5
+ const leadingWhitespacePattern = /^[\t ]+/;
6
+ function whitespaceColumnCount(value) {
7
+ let columns = 0;
8
+ for (const char of value) columns += char === " " ? 4 : 1;
9
+ return columns;
10
+ }
11
+ function splitLeadingWhitespace(value, key) {
12
+ const match = value.match(leadingWhitespacePattern);
13
+ if (!match) return value;
14
+ const leadingWhitespace = match[0] ?? "";
15
+ const rest = value.slice(leadingWhitespace.length);
16
+ if (!rest.startsWith("--")) return value;
17
+ const isSingleWordGap = leadingWhitespace === " ";
18
+ return [/* @__PURE__ */ jsx("span", {
19
+ "data-fd-code-space": isSingleWordGap ? "gap" : "indent",
20
+ style: { "--fd-code-space-count": whitespaceColumnCount(leadingWhitespace) },
21
+ children: leadingWhitespace
22
+ }, `${key}-space`), rest];
23
+ }
24
+ function normalizeNode(node, key) {
25
+ if (typeof node === "string") return splitLeadingWhitespace(node, key);
26
+ if (!React$1.isValidElement(node)) return node;
27
+ const props = node.props;
28
+ if (props.children === void 0) return node;
29
+ return React$1.cloneElement(node, void 0, React$1.Children.map(props.children, (child, index) => normalizeNode(child, `${key}-${index}`)));
30
+ }
31
+ function normalizeCodeSpacing(children) {
32
+ return React$1.Children.map(children, (child, index) => normalizeNode(child, `${index}`));
33
+ }
34
+ function createPreWithCodeSpacing(DefaultPre) {
35
+ return function PreWithCodeSpacing(props) {
36
+ const normalizedChildren = normalizeCodeSpacing(props.children);
37
+ if (typeof DefaultPre === "string") return /* @__PURE__ */ jsx("pre", {
38
+ ...props,
39
+ children: normalizedChildren
40
+ });
41
+ return /* @__PURE__ */ jsx(DefaultPre, {
42
+ ...props,
43
+ children: normalizedChildren
44
+ });
45
+ };
46
+ }
47
+
48
+ //#endregion
49
+ export { createPreWithCodeSpacing };
package/dist/docs-api.mjs CHANGED
@@ -13,6 +13,7 @@ import { buildApiReferenceOpenApiDocumentAsync, createDocsMcpHttpHandler, create
13
13
  * A single route handler that serves **both** search and AI chat:
14
14
  *
15
15
  * - `GET /api/docs?query=…` → full-text search over indexed MDX pages
16
+ * - `GET /api/docs?format=agents` → AGENTS.md for coding-agent instructions
16
17
  * - `GET /api/docs?format=skill` → skill.md for agent discovery
17
18
  * - `POST /api/docs` → RAG-powered "Ask AI" (searches relevant docs,
18
19
  * then streams an LLM response using the docs as context)
@@ -46,6 +47,10 @@ const DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE = "/.well-known/llms.txt";
46
47
  const DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE = "/.well-known/llms-full.txt";
47
48
  const DEFAULT_SKILL_MD_ROUTE = "/skill.md";
48
49
  const DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE = "/.well-known/skill.md";
50
+ const DEFAULT_AGENTS_MD_ROUTE = "/AGENTS.md";
51
+ const DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE = "/.well-known/AGENTS.md";
52
+ const DEFAULT_AGENT_MD_ROUTE = "/AGENT.md";
53
+ const DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE = "/.well-known/AGENT.md";
49
54
  const DEFAULT_ROBOTS_TXT_ROUTE = "/robots.txt";
50
55
  const DEFAULT_AGENT_FEEDBACK_ROUTE = "/api/docs/agent/feedback";
51
56
  const DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA = {
@@ -171,6 +176,11 @@ function resolveSkillRequest(url) {
171
176
  if (pathname === DEFAULT_SKILL_MD_ROUTE || pathname === DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE) return true;
172
177
  return url.searchParams.get("format")?.trim() === "skill";
173
178
  }
179
+ function resolveAgentsRequest(url) {
180
+ const pathname = normalizeUrlPath(url.pathname);
181
+ if (pathname === DEFAULT_AGENTS_MD_ROUTE || pathname === DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE || pathname === DEFAULT_AGENT_MD_ROUTE || pathname === DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE) return true;
182
+ return url.searchParams.get("format")?.trim() === "agents";
183
+ }
174
184
  function isSearchEnabled(search) {
175
185
  if (search === false) return false;
176
186
  if (search && typeof search === "object" && search.enabled === false) return false;
@@ -223,6 +233,7 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, site
223
233
  markdownRoutes: true,
224
234
  agentMdOverrides: true,
225
235
  agentBlocks: true,
236
+ agents: true,
226
237
  llms: llms.enabled,
227
238
  skills: true,
228
239
  mcp: mcp.enabled,
@@ -243,6 +254,7 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, site
243
254
  agentSpecWellKnown: DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE,
244
255
  agentSpecWellKnownJson: DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE,
245
256
  agentSpecQuery: `${DEFAULT_DOCS_API_ROUTE}?agent=spec`,
257
+ agents: `${DEFAULT_DOCS_API_ROUTE}?format=agents`,
246
258
  openapi: `${DEFAULT_DOCS_API_ROUTE}?format=openapi`
247
259
  },
248
260
  markdown: {
@@ -323,6 +335,15 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms, site
323
335
  queryParam: "query",
324
336
  localeParam: "lang"
325
337
  },
338
+ agents: {
339
+ enabled: true,
340
+ file: "AGENTS.md",
341
+ route: DEFAULT_AGENTS_MD_ROUTE,
342
+ wellKnown: DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE,
343
+ api: `${DEFAULT_DOCS_API_ROUTE}?format=agents`,
344
+ generatedFallback: true,
345
+ aliases: [DEFAULT_AGENT_MD_ROUTE, DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE]
346
+ },
326
347
  skills: {
327
348
  enabled: true,
328
349
  file: "skill.md",
@@ -1007,7 +1028,7 @@ function renderSkillDocument({ origin, entry, search, mcp, feedback, llms, sitem
1007
1028
  if (robotsEnabled) lines.push(`- Check ${DEFAULT_ROBOTS_TXT_ROUTE} for crawler and AI-agent access policy.`);
1008
1029
  if (mcp.enabled) lines.push(`- Use ${DEFAULT_MCP_WELL_KNOWN_ROUTE} or ${DEFAULT_MCP_PUBLIC_ROUTE} for MCP tools when your environment supports MCP.`);
1009
1030
  if (feedback.enabled) lines.push(`- Read ${feedback.schemaRoute} before posting agent feedback to ${feedback.route}.`);
1010
- lines.push("", "## Routes", `- Skill document: ${DEFAULT_SKILL_MD_ROUTE}`, `- Skill well-known alias: ${DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE}`, `- Skill API format: ${DEFAULT_DOCS_API_ROUTE}?format=skill`, `- Agent discovery: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}`, `- Agent discovery fallback: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE}`, `- Markdown root: /${normalizedEntry}.md`, `- Markdown pages: /${normalizedEntry}/{slug}.md`);
1031
+ lines.push("", "## Routes", `- Agent instructions: ${DEFAULT_AGENTS_MD_ROUTE}`, `- Agent instructions well-known alias: ${DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE}`, `- Agent instructions API format: ${DEFAULT_DOCS_API_ROUTE}?format=agents`, `- Skill document: ${DEFAULT_SKILL_MD_ROUTE}`, `- Skill well-known alias: ${DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE}`, `- Skill API format: ${DEFAULT_DOCS_API_ROUTE}?format=skill`, `- Agent discovery: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}`, `- Agent discovery fallback: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE}`, `- Markdown root: /${normalizedEntry}.md`, `- Markdown pages: /${normalizedEntry}/{slug}.md`);
1011
1032
  if (robotsEnabled) lines.push(`- Robots policy: ${DEFAULT_ROBOTS_TXT_ROUTE}`);
1012
1033
  if (llms.enabled) {
1013
1034
  lines.push(`- llms.txt: ${DEFAULT_LLMS_TXT_ROUTE}`, `- llms-full.txt: ${DEFAULT_LLMS_FULL_TXT_ROUTE}`, `- llms well-known aliases: ${DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE}, ${DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE}`);
@@ -1028,6 +1049,56 @@ function renderSkillDocument({ origin, entry, search, mcp, feedback, llms, sitem
1028
1049
  lines.push("", "## Reusable Framework Skills", "For framework setup, CLI, page actions, Ask AI, or configuration work, install the reusable Farming Labs skills:", "", "```sh", "npx skills add farming-labs/docs", "```");
1029
1050
  return lines.join("\n");
1030
1051
  }
1052
+ function renderAgentsDocument({ origin, entry, search, mcp, feedback, llms, sitemap, robots, openapi }) {
1053
+ const normalizedEntry = normalizePathSegment(entry) || "docs";
1054
+ const siteTitle = compactSkillText(llms.siteTitle ?? "Documentation");
1055
+ const siteDescription = llms.siteDescription ? compactSkillText(llms.siteDescription) : void 0;
1056
+ const searchEnabled = isSearchEnabled(search);
1057
+ const sitemapConfig = resolveDocsSitemapConfig(sitemap, { baseUrl: llms.baseUrl });
1058
+ const robotsEnabled = isRobotsDiscoveryEnabled(robots);
1059
+ const llmsSections = resolveDocsLlmsTxtSections(llms);
1060
+ const lines = [
1061
+ "# Agent Instructions",
1062
+ "",
1063
+ `Site: ${siteTitle}`,
1064
+ `Base URL: ${origin}`
1065
+ ];
1066
+ if (siteDescription) lines.push(`Description: ${siteDescription}`);
1067
+ lines.push("", "## Start Here", `- Read ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE} first; fall back to ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE} or ${DEFAULT_AGENT_SPEC_ROUTE}.`, `- Read /${normalizedEntry}.md for the root docs page.`, `- Read /${normalizedEntry}/{slug}.md for page-specific context.`);
1068
+ if (llms.enabled) {
1069
+ lines.push(`- Use ${DEFAULT_LLMS_TXT_ROUTE} as the compact docs map.`, `- Use ${DEFAULT_LLMS_FULL_TXT_ROUTE} when you need the full markdown bundle.`);
1070
+ for (const section of llmsSections) lines.push(`- Use ${section.route} for the ${section.title} section map.`);
1071
+ }
1072
+ if (sitemapConfig.enabled) {
1073
+ if (sitemapConfig.markdown.enabled) lines.push(`- Use ${sitemapConfig.markdown.route} for a semantic sitemap with sections.`);
1074
+ if (sitemapConfig.xml.enabled) lines.push(`- Use ${sitemapConfig.xml.route} for canonical URLs and freshness metadata.`);
1075
+ }
1076
+ if (robotsEnabled) lines.push(`- Check ${DEFAULT_ROBOTS_TXT_ROUTE} before crawling broadly.`);
1077
+ if (searchEnabled) lines.push(`- Search with ${DEFAULT_DOCS_API_ROUTE}?query={query} when the route is unknown.`);
1078
+ if (openapi.enabled && openapi.url) lines.push(`- Fetch ${openapi.url} before scraping API reference pages; prefer schemas over prose.`);
1079
+ if (mcp.enabled) lines.push(`- Use MCP at ${DEFAULT_MCP_PUBLIC_ROUTE} or ${DEFAULT_MCP_WELL_KNOWN_ROUTE} when your environment supports MCP tools.`);
1080
+ if (feedback.enabled) lines.push(`- Read ${feedback.schemaRoute} before posting feedback to ${feedback.route}.`);
1081
+ lines.push("", "## Working Rules", "- Prefer markdown routes, llms.txt, sitemap.md, OpenAPI schemas, and MCP tools over scraping rendered HTML.", "- Treat generated context files as discovery aids, then fetch the smallest page or section that answers the task.", "- Preserve canonical docs URLs when citing pages back to humans.", "- If a route returns a markdown 404, use the sitemap and discovery spec before guessing a slug.", "", "## Public Routes", `- Agent instructions: ${DEFAULT_AGENTS_MD_ROUTE}`, `- Agent instructions well-known alias: ${DEFAULT_AGENTS_MD_WELL_KNOWN_ROUTE}`, `- Agent instructions API format: ${DEFAULT_DOCS_API_ROUTE}?format=agents`, `- Agent instructions aliases: ${DEFAULT_AGENT_MD_ROUTE}, ${DEFAULT_AGENT_MD_WELL_KNOWN_ROUTE}`, `- Site skill: ${DEFAULT_SKILL_MD_ROUTE}`, `- Site skill well-known alias: ${DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE}`, `- Site skill API format: ${DEFAULT_DOCS_API_ROUTE}?format=skill`, `- Markdown root: /${normalizedEntry}.md`, `- Markdown pages: /${normalizedEntry}/{slug}.md`, `- Agent discovery: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}`, `- Agent discovery fallback: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE}`);
1082
+ if (llms.enabled) {
1083
+ lines.push(`- llms.txt: ${DEFAULT_LLMS_TXT_ROUTE}`, `- llms-full.txt: ${DEFAULT_LLMS_FULL_TXT_ROUTE}`, `- llms well-known aliases: ${DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE}, ${DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE}`);
1084
+ for (const section of llmsSections) {
1085
+ lines.push(`- ${section.title} llms.txt: ${section.route}`);
1086
+ lines.push(`- ${section.title} llms-full.txt: ${section.fullRoute}`);
1087
+ }
1088
+ }
1089
+ if (robotsEnabled) lines.push(`- Robots policy: ${DEFAULT_ROBOTS_TXT_ROUTE}`);
1090
+ if (sitemapConfig.enabled) {
1091
+ if (sitemapConfig.xml.enabled) lines.push(`- Sitemap XML: ${sitemapConfig.xml.route}`);
1092
+ if (sitemapConfig.markdown.enabled) lines.push(`- Sitemap Markdown: ${sitemapConfig.markdown.route}`, `- Sitemap well-known alias: ${sitemapConfig.markdown.wellKnownRoute}`);
1093
+ }
1094
+ if (openapi.enabled && openapi.url) {
1095
+ lines.push(`- OpenAPI schema: ${openapi.url}`);
1096
+ if (openapi.apiReferencePath) lines.push(`- API reference: ${openapi.apiReferencePath}`);
1097
+ }
1098
+ if (mcp.enabled) lines.push(`- MCP: ${DEFAULT_MCP_PUBLIC_ROUTE}, ${DEFAULT_MCP_WELL_KNOWN_ROUTE}`);
1099
+ lines.push("", "## Framework Maintenance", "- For @farming-labs/docs projects, keep the framework package current before debugging missing agent surfaces.", "", "```sh", "npx @farming-labs/docs@latest upgrade --latest", "```", "", "- For framework setup, configuration, CLI, Ask AI, page actions, or theme work, install the reusable Skills pack:", "", "```sh", "npx skills add farming-labs/docs", "```");
1100
+ return lines.join("\n");
1101
+ }
1031
1102
  function readRootSkillDocument(rootDir) {
1032
1103
  const candidate = path.join(rootDir, "skill.md");
1033
1104
  try {
@@ -1037,6 +1108,17 @@ function readRootSkillDocument(rootDir) {
1037
1108
  }
1038
1109
  return null;
1039
1110
  }
1111
+ function readRootAgentsDocument(rootDir) {
1112
+ for (const fileName of ["AGENTS.md", "AGENT.md"]) {
1113
+ const candidate = path.join(rootDir, fileName);
1114
+ try {
1115
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return fs.readFileSync(candidate, "utf-8");
1116
+ } catch {
1117
+ continue;
1118
+ }
1119
+ }
1120
+ return null;
1121
+ }
1040
1122
  function compactSkillText(value) {
1041
1123
  return value.replace(/\s+/g, " ").trim();
1042
1124
  }
@@ -1898,6 +1980,31 @@ function createDocsAPI(options) {
1898
1980
  "X-Robots-Tag": "noindex"
1899
1981
  } });
1900
1982
  }
1983
+ if (resolveAgentsRequest(url)) {
1984
+ await emitDocsAnalyticsEvent(analytics, {
1985
+ type: "agents_request",
1986
+ source: "server",
1987
+ url: request.url,
1988
+ path: url.pathname,
1989
+ locale: ctx.locale,
1990
+ properties: { method: "GET" }
1991
+ });
1992
+ return new Response(readRootAgentsDocument(root) ?? renderAgentsDocument({
1993
+ origin: url.origin,
1994
+ entry,
1995
+ search: searchConfig,
1996
+ mcp: mcpConfig,
1997
+ feedback: agentFeedbackConfig,
1998
+ llms: llmsConfig,
1999
+ sitemap: sitemapConfig,
2000
+ robots: robotsConfig,
2001
+ openapi: openapiDiscovery
2002
+ }), { headers: {
2003
+ "Content-Type": "text/markdown; charset=utf-8",
2004
+ "Cache-Control": "public, max-age=0, s-maxage=3600",
2005
+ "X-Robots-Tag": "noindex"
2006
+ } });
2007
+ }
1901
2008
  if (resolveSkillRequest(url)) {
1902
2009
  await emitDocsAnalyticsEvent(analytics, {
1903
2010
  type: "skill_request",
package/dist/mdx.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { MDXImg } from "./mdx-img.mjs";
2
+ import { createPreWithCodeSpacing } from "./code-block-spacing.mjs";
2
3
  import { createPreWithCopyCallback } from "./code-block-copy-wrapper.mjs";
3
4
  import { HoverLink } from "./hover-link.mjs";
4
5
  import { extractPromptText } from "./prompt-text.mjs";
@@ -94,6 +95,8 @@ function getMDXComponents(overrides, options) {
94
95
  ...applyBuiltInComponentDefaults(options),
95
96
  ...overrides
96
97
  };
98
+ const DefaultPre = base.pre;
99
+ if (DefaultPre) base.pre = createPreWithCodeSpacing(DefaultPre);
97
100
  if (base.Prompt) {
98
101
  const DefaultPrompt = base.Prompt;
99
102
  base.Prompt = function PromptWithDocsContext(props) {
@@ -108,8 +111,8 @@ function getMDXComponents(overrides, options) {
108
111
  };
109
112
  }
110
113
  if (options?.onCopyClick) {
111
- const DefaultPre = base.pre;
112
- if (DefaultPre) base.pre = createPreWithCopyCallback(DefaultPre, options.onCopyClick);
114
+ const CopyPre = base.pre;
115
+ if (CopyPre) base.pre = createPreWithCopyCallback(CopyPre, options.onCopyClick);
113
116
  }
114
117
  return base;
115
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/theme",
3
- "version": "0.1.102",
3
+ "version": "0.1.104",
4
4
  "description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
5
5
  "keywords": [
6
6
  "docs",
@@ -139,7 +139,7 @@
139
139
  "tsdown": "^0.20.3",
140
140
  "typescript": "^5.9.3",
141
141
  "vitest": "^3.2.4",
142
- "@farming-labs/docs": "0.1.102"
142
+ "@farming-labs/docs": "0.1.104"
143
143
  },
144
144
  "peerDependencies": {
145
145
  "@farming-labs/docs": ">=0.0.1",
package/styles/base.css CHANGED
@@ -139,12 +139,38 @@ article small,
139
139
 
140
140
  figure.shiki pre {
141
141
  padding: 0.15rem 0.5rem;
142
+ white-space: pre;
142
143
  }
143
144
 
144
145
  figure.shiki pre > code {
146
+ display: block;
147
+ white-space: inherit;
148
+ }
149
+
150
+ /* Wrapped Shiki lines can use grid rows; unwrapped token spans need inline flow for spaces. */
151
+ figure.shiki pre > code:has(> .line),
152
+ figure.shiki pre > code:has(> [data-line]) {
145
153
  display: grid;
146
154
  }
147
155
 
156
+ figure.shiki pre > code > .line,
157
+ figure.shiki pre > code > [data-line] {
158
+ white-space: inherit;
159
+ }
160
+
161
+ figure.shiki [data-fd-code-space] {
162
+ display: inline-block;
163
+ white-space: pre;
164
+ }
165
+
166
+ figure.shiki [data-fd-code-space="gap"] {
167
+ width: var(--fd-code-token-gap, 1.75ch);
168
+ }
169
+
170
+ figure.shiki [data-fd-code-space="indent"] {
171
+ width: calc(var(--fd-code-space-count, 1) * 1ch);
172
+ }
173
+
148
174
  figure.shiki pre > code > [data-line] {
149
175
  padding: 0 0.25rem;
150
176
  }