@farming-labs/theme 0.1.38 → 0.1.41

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.
@@ -64,6 +64,7 @@ interface DocsMCPAPIOptions {
64
64
  * - **GET ?query=…** → full-text search
65
65
  * - **GET ?format=llms** → llms.txt (concise page listing)
66
66
  * - **GET ?format=llms-full** → llms-full.txt (full page content)
67
+ * - **GET ?format=skill** → skill.md
67
68
  * - **POST** → AI-powered chat with RAG
68
69
  *
69
70
  * @example
@@ -82,7 +83,7 @@ interface DocsMCPAPIOptions {
82
83
  */
83
84
  declare function createDocsAPI(options?: DocsAPIOptions): {
84
85
  /**
85
- * GET handler — search, llms.txt, or llms-full.txt depending on query params.
86
+ * GET handler — search, markdown, llms.txt, llms-full.txt, or skill.md.
86
87
  */
87
88
  GET(request: Request): Promise<Response>;
88
89
  /**
package/dist/docs-api.mjs CHANGED
@@ -13,6 +13,7 @@ import { createDocsMcpHttpHandler, createFilesystemDocsMcpSource, resolveDocsMcp
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=skill` → skill.md for agent discovery
16
17
  * - `POST /api/docs` → RAG-powered "Ask AI" (searches relevant docs,
17
18
  * then streams an LLM response using the docs as context)
18
19
  *
@@ -43,6 +44,8 @@ const DEFAULT_LLMS_TXT_ROUTE = "/llms.txt";
43
44
  const DEFAULT_LLMS_FULL_TXT_ROUTE = "/llms-full.txt";
44
45
  const DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE = "/.well-known/llms.txt";
45
46
  const DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE = "/.well-known/llms-full.txt";
47
+ const DEFAULT_SKILL_MD_ROUTE = "/skill.md";
48
+ const DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE = "/.well-known/skill.md";
46
49
  const DEFAULT_AGENT_FEEDBACK_ROUTE = "/api/docs/agent/feedback";
47
50
  const DEFAULT_AGENT_FEEDBACK_PAYLOAD_SCHEMA = {
48
51
  type: "object",
@@ -159,6 +162,11 @@ function resolveAgentSpecRequest(url) {
159
162
  const pathname = normalizeUrlPath(url.pathname);
160
163
  return pathname === DEFAULT_AGENT_SPEC_ROUTE || pathname === DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE || pathname === DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE;
161
164
  }
165
+ function resolveSkillRequest(url) {
166
+ const pathname = normalizeUrlPath(url.pathname);
167
+ if (pathname === DEFAULT_SKILL_MD_ROUTE || pathname === DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE) return true;
168
+ return url.searchParams.get("format")?.trim() === "skill";
169
+ }
162
170
  function isSearchEnabled(search) {
163
171
  if (search === false) return false;
164
172
  if (search && typeof search === "object" && search.enabled === false) return false;
@@ -237,6 +245,11 @@ function buildAgentSpec({ origin, entry, i18n, search, mcp, feedback, llms }) {
237
245
  },
238
246
  skills: {
239
247
  enabled: true,
248
+ file: "skill.md",
249
+ route: DEFAULT_SKILL_MD_ROUTE,
250
+ wellKnown: DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE,
251
+ api: `${DEFAULT_DOCS_API_ROUTE}?format=skill`,
252
+ generatedFallback: true,
240
253
  registry: "skills.sh",
241
254
  install: "npx skills add farming-labs/docs",
242
255
  recommended: [{
@@ -820,6 +833,53 @@ function renderMarkdownDocument(page) {
820
833
  lines.push("", page.agentFallbackRawContent ?? page.rawContent ?? page.content);
821
834
  return lines.join("\n");
822
835
  }
836
+ function renderSkillDocument({ origin, entry, search, mcp, feedback, llms }) {
837
+ const normalizedEntry = normalizePathSegment(entry) || "docs";
838
+ const siteTitle = compactSkillText(llms.siteTitle ?? "Documentation");
839
+ const siteDescription = llms.siteDescription ? compactSkillText(llms.siteDescription) : void 0;
840
+ const searchEnabled = isSearchEnabled(search);
841
+ const lines = [
842
+ "---",
843
+ "name: docs",
844
+ `description: ${toYamlString(truncateSkillDescription(`Use ${siteTitle} through markdown routes, llms.txt, agent discovery, search, and MCP when available.`))}`,
845
+ "---",
846
+ "",
847
+ `# ${siteTitle} Skill`,
848
+ "",
849
+ `Base URL: ${origin}`
850
+ ];
851
+ if (siteDescription) lines.push(`Description: ${siteDescription}`);
852
+ lines.push("", "## When To Use", "Use this skill when you need to read or implement against this documentation site.", "", "## Start Here", `- Fetch ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}; fall back to ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE} or ${DEFAULT_AGENT_SPEC_ROUTE}.`, `- Fetch /${normalizedEntry}.md for the root docs page.`, `- Fetch /${normalizedEntry}/{slug}.md for page-specific context.`, "- You can also request text/markdown from normal page URLs.");
853
+ if (searchEnabled) lines.push(`- Search with ${DEFAULT_DOCS_API_ROUTE}?query={query} when you do not know the page.`);
854
+ if (llms.enabled) lines.push(`- Use ${DEFAULT_LLMS_TXT_ROUTE} for a compact docs index.`, `- Use ${DEFAULT_LLMS_FULL_TXT_ROUTE} for full markdown context.`);
855
+ if (mcp.enabled) lines.push(`- Use ${DEFAULT_MCP_WELL_KNOWN_ROUTE} or ${DEFAULT_MCP_PUBLIC_ROUTE} for MCP tools when your environment supports MCP.`);
856
+ if (feedback.enabled) lines.push(`- Read ${feedback.schemaRoute} before posting agent feedback to ${feedback.route}.`);
857
+ 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`);
858
+ if (llms.enabled) 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}`);
859
+ if (mcp.enabled) lines.push(`- MCP: ${DEFAULT_MCP_PUBLIC_ROUTE}, ${DEFAULT_MCP_WELL_KNOWN_ROUTE}`);
860
+ 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", "```");
861
+ return lines.join("\n");
862
+ }
863
+ function readRootSkillDocument(rootDir) {
864
+ const candidate = path.join(rootDir, "skill.md");
865
+ try {
866
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return fs.readFileSync(candidate, "utf-8");
867
+ } catch {
868
+ return null;
869
+ }
870
+ return null;
871
+ }
872
+ function compactSkillText(value) {
873
+ return value.replace(/\s+/g, " ").trim();
874
+ }
875
+ function truncateSkillDescription(value) {
876
+ const normalized = compactSkillText(value);
877
+ if (normalized.length <= 1024) return normalized;
878
+ return `${normalized.slice(0, 1021).trimEnd()}...`;
879
+ }
880
+ function toYamlString(value) {
881
+ return JSON.stringify(value);
882
+ }
823
883
  const DEFAULT_SYSTEM_PROMPT = `You are a helpful documentation assistant. Answer questions based on the provided documentation context. Be concise and accurate. If the answer is not in the context, say so honestly. Use markdown formatting for code examples and links.`;
824
884
  function resolveModelAndProvider(aiConfig, requestedModelId) {
825
885
  const raw = aiConfig.model;
@@ -946,6 +1006,7 @@ function generateLlmsTxt(indexes, options) {
946
1006
  * - **GET ?query=…** → full-text search
947
1007
  * - **GET ?format=llms** → llms.txt (concise page listing)
948
1008
  * - **GET ?format=llms-full** → llms-full.txt (full page content)
1009
+ * - **GET ?format=skill** → skill.md
949
1010
  * - **POST** → AI-powered chat with RAG
950
1011
  *
951
1012
  * @example
@@ -1117,6 +1178,19 @@ function createDocsAPI(options) {
1117
1178
  "X-Robots-Tag": "noindex"
1118
1179
  } });
1119
1180
  }
1181
+ if (resolveSkillRequest(url)) return new Response(readRootSkillDocument(root) ?? renderSkillDocument({
1182
+ origin: url.origin,
1183
+ entry,
1184
+ i18n,
1185
+ search: searchConfig,
1186
+ mcp: mcpConfig,
1187
+ feedback: agentFeedbackConfig,
1188
+ llms: llmsConfig
1189
+ }), { headers: {
1190
+ "Content-Type": "text/markdown; charset=utf-8",
1191
+ "Cache-Control": "public, max-age=0, s-maxage=3600",
1192
+ "X-Robots-Tag": "noindex"
1193
+ } });
1120
1194
  const markdownRequest = resolveMarkdownRequest(entry, url, request);
1121
1195
  if (markdownRequest) {
1122
1196
  const document = await getMarkdownDocument(ctx, markdownRequest.requestedPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/theme",
3
- "version": "0.1.38",
3
+ "version": "0.1.41",
4
4
  "description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
5
5
  "keywords": [
6
6
  "docs",
@@ -133,7 +133,7 @@
133
133
  "tsdown": "^0.20.3",
134
134
  "typescript": "^5.9.3",
135
135
  "vitest": "^3.2.4",
136
- "@farming-labs/docs": "0.1.38"
136
+ "@farming-labs/docs": "0.1.41"
137
137
  },
138
138
  "peerDependencies": {
139
139
  "@farming-labs/docs": ">=0.0.1",