@farming-labs/docs 0.1.43 → 0.1.44

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.
@@ -1,272 +1,5 @@
1
1
  import { u as renderDocsRelatedMarkdownLines } from "./search-8oEskRtz.mjs";
2
2
 
3
- //#region src/define-docs.ts
4
- /**
5
- * Define docs configuration. Validates and returns the config.
6
- */
7
- function defineDocs(config) {
8
- return {
9
- entry: config.entry ?? "docs",
10
- contentDir: config.contentDir,
11
- i18n: config.i18n,
12
- theme: config.theme,
13
- nav: config.nav,
14
- github: config.github,
15
- themeToggle: config.themeToggle,
16
- breadcrumb: config.breadcrumb,
17
- sidebar: config.sidebar,
18
- components: config.components,
19
- onCopyClick: config.onCopyClick,
20
- feedback: config.feedback,
21
- search: config.search,
22
- mcp: config.mcp,
23
- icons: config.icons,
24
- pageActions: config.pageActions,
25
- lastUpdated: config.lastUpdated,
26
- llmsTxt: config.llmsTxt,
27
- ai: config.ai,
28
- ordering: config.ordering,
29
- metadata: config.metadata,
30
- og: config.og,
31
- changelog: config.changelog,
32
- apiReference: config.apiReference,
33
- agent: config.agent
34
- };
35
- }
36
-
37
- //#endregion
38
- //#region src/changelog.ts
39
- function normalizePathSegment(value, fallback) {
40
- return (value ?? fallback).trim().replace(/^\/+|\/+$/g, "") || fallback;
41
- }
42
- function normalizeContentDir(value) {
43
- const trimmed = value?.trim();
44
- if (!trimmed) return "changelog";
45
- return trimmed.replace(/\/+$/, "") || "changelog";
46
- }
47
- function resolveChangelogConfig(value) {
48
- if (value === false || value === void 0) return {
49
- enabled: false,
50
- path: "changelog",
51
- contentDir: "changelog",
52
- title: "Changelog",
53
- description: void 0,
54
- search: true
55
- };
56
- if (value === true) return {
57
- enabled: true,
58
- path: "changelog",
59
- contentDir: "changelog",
60
- title: "Changelog",
61
- description: void 0,
62
- search: true
63
- };
64
- return {
65
- enabled: value.enabled !== false,
66
- path: normalizePathSegment(value.path, "changelog"),
67
- contentDir: normalizeContentDir(value.contentDir),
68
- title: value.title?.trim() || "Changelog",
69
- description: value.description?.trim() || void 0,
70
- search: value.search !== false,
71
- actionsComponent: value.actionsComponent
72
- };
73
- }
74
-
75
- //#endregion
76
- //#region src/utils.ts
77
- /**
78
- * Deep merge utility for theme overrides.
79
- * Merges objects recursively; later values override earlier ones.
80
- */
81
- function deepMerge(target, ...sources) {
82
- if (!sources.length) return target;
83
- const source = sources.shift();
84
- if (!source) return target;
85
- const result = { ...target };
86
- for (const key of Object.keys(source)) {
87
- const sourceVal = source[key];
88
- const targetVal = result[key];
89
- if (sourceVal && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal && typeof targetVal === "object" && !Array.isArray(targetVal)) result[key] = deepMerge(targetVal, sourceVal);
90
- else if (sourceVal !== void 0) result[key] = sourceVal;
91
- }
92
- if (sources.length) return deepMerge(result, ...sources);
93
- return result;
94
- }
95
-
96
- //#endregion
97
- //#region src/create-theme.ts
98
- /**
99
- * Create a theme preset factory.
100
- *
101
- * Returns a function that accepts optional overrides and deep-merges them
102
- * with the base theme defaults. This is the same pattern used by the
103
- * built-in `fumadocs()`, `darksharp()`, and `pixelBorder()` presets.
104
- *
105
- * @param baseTheme - The default theme configuration
106
- * @returns A factory function `(overrides?) => DocsTheme`
107
- *
108
- * @example
109
- * ```ts
110
- * import { createTheme } from "@farming-labs/docs";
111
- *
112
- * export const myTheme = createTheme({
113
- * name: "my-theme",
114
- * ui: {
115
- * colors: { primary: "#6366f1" },
116
- * layout: { contentWidth: 800 },
117
- * },
118
- * });
119
- * ```
120
- */
121
- function createTheme(baseTheme) {
122
- return function themeFactory(overrides = {}) {
123
- const merged = deepMerge(baseTheme, overrides);
124
- if (overrides.ui?.colors) merged._userColorOverrides = { ...overrides.ui.colors };
125
- return merged;
126
- };
127
- }
128
- /**
129
- * Extend an existing theme preset with additional defaults.
130
- *
131
- * Useful when you want to build on top of an existing theme (e.g. fumadocs)
132
- * rather than starting from scratch.
133
- *
134
- * @example
135
- * ```ts
136
- * import { extendTheme } from "@farming-labs/docs";
137
- * import { fumadocs } from "@farming-labs/theme/default";
138
- *
139
- * // Start with fumadocs defaults, override some values
140
- * export const myTheme = extendTheme(fumadocs(), {
141
- * name: "my-custom-fumadocs",
142
- * ui: { colors: { primary: "#22c55e" } },
143
- * });
144
- * ```
145
- */
146
- function extendTheme(baseTheme, extensions) {
147
- return deepMerge(baseTheme, extensions);
148
- }
149
-
150
- //#endregion
151
- //#region src/i18n.ts
152
- function normalizeSegment(value) {
153
- return value.replace(/^\/+|\/+$/g, "");
154
- }
155
- function splitSegments(value) {
156
- const cleaned = normalizeSegment(value);
157
- return cleaned ? cleaned.split("/").filter(Boolean) : [];
158
- }
159
- function resolveDocsI18n(config) {
160
- if (!config || !Array.isArray(config.locales)) return null;
161
- const locales = Array.from(new Set(config.locales.map((l) => l.trim()).filter(Boolean)));
162
- if (locales.length === 0) return null;
163
- return {
164
- locales,
165
- defaultLocale: config.defaultLocale && locales.includes(config.defaultLocale) ? config.defaultLocale : locales[0]
166
- };
167
- }
168
- function resolveDocsLocale(searchParams, i18n) {
169
- if (!i18n) return void 0;
170
- const raw = searchParams.get("lang") ?? searchParams.get("locale");
171
- if (!raw) return void 0;
172
- if (i18n.locales.includes(raw)) return raw;
173
- return i18n.defaultLocale;
174
- }
175
- function resolveDocsPath(pathname, entry) {
176
- const entryBase = normalizeSegment(entry || "docs") || "docs";
177
- const entryParts = splitSegments(entryBase);
178
- const pathParts = splitSegments(pathname);
179
- let rest = pathParts;
180
- if (entryParts.length > 0) {
181
- if (pathParts.slice(0, entryParts.length).join("/") === entryParts.join("/")) rest = pathParts.slice(entryParts.length);
182
- }
183
- return {
184
- slug: rest.join("/"),
185
- entryPath: entryBase
186
- };
187
- }
188
-
189
- //#endregion
190
- //#region src/metadata.ts
191
- /**
192
- * Resolve page title using metadata titleTemplate.
193
- * %s is replaced with page title.
194
- */
195
- function resolveTitle(pageTitle, metadata) {
196
- return (metadata?.titleTemplate ?? "%s").replace("%s", pageTitle);
197
- }
198
- /**
199
- * Resolve OG image URL for a page.
200
- * Prefers page.openGraph.images[0], then page.ogImage, then config endpoint/default.
201
- */
202
- function resolveOGImage(page, ogConfig, baseUrl) {
203
- if (page.openGraph?.images?.length) return resolveImageUrl(page.openGraph.images[0].url, baseUrl);
204
- if (!ogConfig?.enabled) return void 0;
205
- if (page.ogImage) return resolveImageUrl(page.ogImage, baseUrl);
206
- if (ogConfig.type === "dynamic" && ogConfig.endpoint) return `${baseUrl ?? ""}${ogConfig.endpoint}`;
207
- return ogConfig.defaultImage;
208
- }
209
- function resolveImageUrl(url, baseUrl) {
210
- if (url.startsWith("/") || url.startsWith("http")) return url;
211
- const base = baseUrl ?? "";
212
- return `${base}${base.length > 0 && !base.endsWith("/") ? "/" : ""}${url}`;
213
- }
214
- /**
215
- * Build the Open Graph metadata object for a page.
216
- * When the page has openGraph in frontmatter, uses it (with title/description filled from page if omitted).
217
- * Otherwise uses ogImage or config (dynamic endpoint / defaultImage).
218
- */
219
- function buildPageOpenGraph(page, ogConfig, baseUrl) {
220
- if (page.openGraph) {
221
- const images = page.openGraph.images?.length ? page.openGraph.images.map((img) => ({
222
- url: resolveImageUrl(img.url, baseUrl),
223
- width: img.width ?? 1200,
224
- height: img.height ?? 630
225
- })) : void 0;
226
- return {
227
- title: page.openGraph.title ?? page.title,
228
- description: page.openGraph.description ?? page.description,
229
- ...images && { images }
230
- };
231
- }
232
- const url = resolveOGImage(page, ogConfig, baseUrl);
233
- if (!url) return void 0;
234
- return {
235
- title: page.title,
236
- ...page.description && { description: page.description },
237
- images: [{
238
- url,
239
- width: 1200,
240
- height: 630
241
- }]
242
- };
243
- }
244
- /**
245
- * Build the Twitter card metadata object for a page.
246
- * When the page has twitter in frontmatter, uses it.
247
- * Otherwise builds from ogImage or config (dynamic endpoint).
248
- */
249
- function buildPageTwitter(page, ogConfig, baseUrl) {
250
- if (page.twitter) {
251
- const images = page.twitter.images?.length ? page.twitter.images.map((url) => resolveImageUrl(url, baseUrl)) : void 0;
252
- return {
253
- ...page.twitter.card && { card: page.twitter.card },
254
- ...page.twitter.title !== void 0 && { title: page.twitter.title },
255
- ...page.twitter.description !== void 0 && { description: page.twitter.description },
256
- ...images && { images }
257
- };
258
- }
259
- const url = resolveOGImage(page, ogConfig, baseUrl);
260
- if (!url) return void 0;
261
- return {
262
- card: "summary_large_image",
263
- title: page.title,
264
- ...page.description && { description: page.description },
265
- images: [url]
266
- };
267
- }
268
-
269
- //#endregion
270
3
  //#region src/agent.ts
271
4
  const DEFAULT_DOCS_API_ROUTE = "/api/docs";
272
5
  const DEFAULT_AGENT_SPEC_ROUTE = "/api/docs/agent/spec";
@@ -585,4 +318,4 @@ function toYamlString(value) {
585
318
  }
586
319
 
587
320
  //#endregion
588
- export { buildPageTwitter as A, defineDocs as B, renderDocsMarkdownDocument as C, resolveDocsMarkdownRequest as D, resolveDocsLlmsTxtFormat as E, resolveDocsPath as F, createTheme as I, extendTheme as L, resolveTitle as M, resolveDocsI18n as N, resolveDocsSkillFormat as O, resolveDocsLocale as P, deepMerge as R, normalizeDocsUrlPath as S, resolveDocsAgentMdxContent as T, isDocsAgentDiscoveryRequest as _, DEFAULT_DOCS_API_ROUTE as a, isDocsSkillRequest as b, DEFAULT_LLMS_TXT_ROUTE as c, DEFAULT_MCP_ROUTE as d, DEFAULT_MCP_WELL_KNOWN_ROUTE as f, findDocsMarkdownPage as g, buildDocsAgentDiscoverySpec as h, DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE as i, resolveOGImage as j, buildPageOpenGraph as k, DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE as l, DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE as m, DEFAULT_AGENT_SPEC_ROUTE as n, DEFAULT_LLMS_FULL_TXT_ROUTE as o, DEFAULT_SKILL_MD_ROUTE as p, DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE as r, DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE as s, DEFAULT_AGENT_FEEDBACK_ROUTE as t, DEFAULT_MCP_PUBLIC_ROUTE as u, isDocsMcpRequest as v, renderDocsSkillDocument as w, normalizeDocsPathSegment as x, isDocsPublicGetRequest as y, resolveChangelogConfig as z };
321
+ export { renderDocsMarkdownDocument as C, resolveDocsMarkdownRequest as D, resolveDocsLlmsTxtFormat as E, resolveDocsSkillFormat as O, normalizeDocsUrlPath as S, resolveDocsAgentMdxContent as T, isDocsAgentDiscoveryRequest as _, DEFAULT_DOCS_API_ROUTE as a, isDocsSkillRequest as b, DEFAULT_LLMS_TXT_ROUTE as c, DEFAULT_MCP_ROUTE as d, DEFAULT_MCP_WELL_KNOWN_ROUTE as f, findDocsMarkdownPage as g, buildDocsAgentDiscoverySpec as h, DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE as i, DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE as l, DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE as m, DEFAULT_AGENT_SPEC_ROUTE as n, DEFAULT_LLMS_FULL_TXT_ROUTE as o, DEFAULT_SKILL_MD_ROUTE as p, DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE as r, DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE as s, DEFAULT_AGENT_FEEDBACK_ROUTE as t, DEFAULT_MCP_PUBLIC_ROUTE as u, isDocsMcpRequest as v, renderDocsSkillDocument as w, normalizeDocsPathSegment as x, isDocsPublicGetRequest as y };
@@ -1,8 +1,8 @@
1
- import { C as renderDocsMarkdownDocument, g as findDocsMarkdownPage } from "./agent-qcweNgy-.mjs";
2
- import "./api-reference-GDAEzQn1.mjs";
1
+ import { C as renderDocsMarkdownDocument, g as findDocsMarkdownPage } from "./agent-CbAtuZAc.mjs";
2
+ import "./api-reference-y7cqtq4w.mjs";
3
3
  import { createFilesystemDocsMcpSource } from "./mcp.mjs";
4
4
  import "./server.mjs";
5
- import { a as readBooleanProperty, c as readNumberProperty, d as resolveDocsConfigPath, f as resolveDocsContentDir, i as loadProjectEnv, l as readStringProperty, o as readEnvReferenceProperty, r as loadDocsConfigModule, s as readNavTitle, t as extractNestedObjectLiteral, u as readTopLevelStringProperty } from "./config-CyqDp8tD.mjs";
5
+ import { a as loadProjectEnv, c as readNavTitle, d as readTopLevelStringProperty, f as resolveDocsConfigPath, i as loadDocsConfigModule, l as readNumberProperty, o as readBooleanProperty, p as resolveDocsContentDir, s as readEnvReferenceProperty, t as extractNestedObjectLiteral, u as readStringProperty } from "./config-C7sUsMkm.mjs";
6
6
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
7
7
  import path from "node:path";
8
8
  import pc from "picocolors";
@@ -69,13 +69,13 @@ async function main() {
69
69
  searchApiKey: typeof flags["search-api-key"] === "string" ? flags["search-api-key"] : void 0
70
70
  };
71
71
  if (!parsedCommand.command || parsedCommand.command === "init") {
72
- const { init } = await import("../init-9U8AijRu.mjs");
72
+ const { init } = await import("../init-CShiL8Ch.mjs");
73
73
  await init(initOptions);
74
74
  } else if (parsedCommand.command === "mcp") {
75
- const { runMcp } = await import("../mcp-1j3-_z66.mjs");
75
+ const { runMcp } = await import("../mcp-CYpMeMfi.mjs");
76
76
  await runMcp(mcpOptions);
77
77
  } else if (parsedCommand.command === "agent" && subcommand === "compact") {
78
- const { compactAgentDocs, parseAgentCompactArgs, printAgentCompactHelp } = await import("../agent-CoO9z8aW.mjs");
78
+ const { compactAgentDocs, parseAgentCompactArgs, printAgentCompactHelp } = await import("../agent-Dget5s8h.mjs");
79
79
  const agentCompactOptions = parseAgentCompactArgs(args.slice(2));
80
80
  if (agentCompactOptions.help) {
81
81
  printAgentCompactHelp();
@@ -85,11 +85,19 @@ async function main() {
85
85
  } else if (parsedCommand.command === "agent") {
86
86
  console.error(pc.red(`Unknown agent subcommand: ${subcommand ?? "(missing)"}`));
87
87
  console.error();
88
- const { printAgentCompactHelp } = await import("../agent-CoO9z8aW.mjs");
88
+ const { printAgentCompactHelp } = await import("../agent-Dget5s8h.mjs");
89
89
  printAgentCompactHelp();
90
90
  process.exit(1);
91
+ } else if (parsedCommand.command === "doctor") {
92
+ const { parseDoctorArgs, printDoctorHelp, runDoctor } = await import("../doctor-Jm7T0qYS.mjs");
93
+ const doctorOptions = parseDoctorArgs(args.slice(1));
94
+ if (doctorOptions.help) {
95
+ printDoctorHelp();
96
+ return;
97
+ }
98
+ await runDoctor(doctorOptions);
91
99
  } else if (parsedCommand.command === "search" && subcommand === "sync") {
92
- const { syncSearch } = await import("../search-B2HvbbeE.mjs");
100
+ const { syncSearch } = await import("../search-D7DS0MLc.mjs");
93
101
  await syncSearch(searchSyncOptions);
94
102
  } else if (parsedCommand.command === "search") {
95
103
  console.error(pc.red(`Unknown search subcommand: ${subcommand ?? "(missing)"}`));
@@ -97,7 +105,7 @@ async function main() {
97
105
  printHelp();
98
106
  process.exit(1);
99
107
  } else if (parsedCommand.command === "upgrade") {
100
- const { upgrade } = await import("../upgrade-BGWZ_NLh.mjs");
108
+ const { upgrade } = await import("../upgrade-2xcgMsj6.mjs");
101
109
  await upgrade({
102
110
  framework: (typeof flags.framework === "string" ? flags.framework : void 0) ?? (args[1] && !args[1].startsWith("--") ? args[1] : void 0),
103
111
  tag: args.includes("--beta") ? "beta" : args.includes("--latest") ? "latest" : parsedCommand.tag ?? "latest"
@@ -121,6 +129,7 @@ ${pc.dim("Usage:")}
121
129
  ${pc.dim("Commands:")}
122
130
  ${pc.cyan("init")} Scaffold docs in your project (default)
123
131
  ${pc.cyan("agent")} Agent utilities (${pc.dim("compact")} to generate sibling agent.md files)
132
+ ${pc.cyan("doctor")} Inspect and score agent-readiness for the current docs app
124
133
  ${pc.cyan("mcp")} Run the built-in docs MCP server over stdio
125
134
  ${pc.cyan("search")} Search utilities (${pc.dim("sync")} for external indexes)
126
135
  ${pc.cyan("upgrade")} Upgrade @farming-labs/* packages to latest (auto-detect or use --framework)
@@ -150,6 +159,12 @@ ${pc.dim("Options for agent compact:")}
150
159
  ${pc.cyan("--aggressiveness <0-1>")} Compression intensity for compacted output
151
160
  ${pc.cyan("--dry-run")} Resolve and compress pages without writing files
152
161
 
162
+ ${pc.dim("Options for doctor:")}
163
+ ${pc.cyan("doctor")} Score the current docs app for agent-readiness
164
+ ${pc.cyan("doctor --agent")} Same as ${pc.cyan("doctor")}; explicit agent scoring mode
165
+ ${pc.cyan("doctor agent")} Subcommand alias for agent scoring
166
+ ${pc.cyan("--config <path>")} Use a custom docs config path instead of ${pc.dim("docs.config.ts[x]")}
167
+
153
168
  ${pc.dim("Options for search sync:")}
154
169
  ${pc.cyan("search sync --typesense")} Sync docs content to Typesense using env/flags
155
170
  ${pc.cyan("search sync --algolia")} Sync docs content to Algolia using env/flags
@@ -267,4 +267,4 @@ async function loadDocsConfigModule(rootDir, explicitPath) {
267
267
  }
268
268
 
269
269
  //#endregion
270
- export { readBooleanProperty as a, readNumberProperty as c, resolveDocsConfigPath as d, resolveDocsContentDir as f, loadProjectEnv as i, readStringProperty as l, extractObjectLiteral as n, readEnvReferenceProperty as o, loadDocsConfigModule as r, readNavTitle as s, extractNestedObjectLiteral as t, readTopLevelStringProperty as u };
270
+ export { loadProjectEnv as a, readNavTitle as c, readTopLevelStringProperty as d, resolveDocsConfigPath as f, loadDocsConfigModule as i, readNumberProperty as l, extractObjectLiteral as n, readBooleanProperty as o, resolveDocsContentDir as p, extractTopLevelConfigObject as r, readEnvReferenceProperty as s, extractNestedObjectLiteral as t, readStringProperty as u };
@@ -0,0 +1,507 @@
1
+ import { c as DEFAULT_LLMS_TXT_ROUTE, f as DEFAULT_MCP_WELL_KNOWN_ROUTE, i as DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE, m as DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE, o as DEFAULT_LLMS_FULL_TXT_ROUTE, p as DEFAULT_SKILL_MD_ROUTE, r as DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE, t as DEFAULT_AGENT_FEEDBACK_ROUTE, u as DEFAULT_MCP_PUBLIC_ROUTE } from "./agent-CbAtuZAc.mjs";
2
+ import "./api-reference-y7cqtq4w.mjs";
3
+ import { createFilesystemDocsMcpSource, resolveDocsMcpConfig } from "./mcp.mjs";
4
+ import "./server.mjs";
5
+ import { a as loadProjectEnv, c as readNavTitle, d as readTopLevelStringProperty, f as resolveDocsConfigPath, i as loadDocsConfigModule, o as readBooleanProperty, p as resolveDocsContentDir, r as extractTopLevelConfigObject, t as extractNestedObjectLiteral } from "./config-C7sUsMkm.mjs";
6
+ import { t as detectFramework } from "./utils-DSMXVnEu.mjs";
7
+ import { existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
8
+ import path from "node:path";
9
+ import pc from "picocolors";
10
+
11
+ //#region src/cli/doctor.ts
12
+ const NEXT_CONFIG_PATTERN = /^next\.config\.(?:[cm]?js|[cm]?ts)$/;
13
+ const ASTRO_CONFIG_PATTERN = /^astro\.config\.(?:[cm]?js|[cm]?ts)$/;
14
+ const CODE_FILE_PATTERN = /\.(?:[cm]?js|[cm]?ts|jsx|tsx)$/;
15
+ const IGNORED_DIRS = new Set([
16
+ ".git",
17
+ ".next",
18
+ ".nuxt",
19
+ ".output",
20
+ ".svelte-kit",
21
+ ".turbo",
22
+ "build",
23
+ "coverage",
24
+ "dist",
25
+ "node_modules",
26
+ "out"
27
+ ]);
28
+ function parseInlineFlag(arg) {
29
+ const [rawKey, value] = arg.slice(2).split("=", 2);
30
+ return {
31
+ key: rawKey.trim(),
32
+ value
33
+ };
34
+ }
35
+ function parseDoctorArgs(argv) {
36
+ const parsed = {};
37
+ for (let index = 0; index < argv.length; index += 1) {
38
+ const arg = argv[index];
39
+ if (arg === "--help" || arg === "-h") {
40
+ parsed.help = true;
41
+ continue;
42
+ }
43
+ if (arg === "--agent" || arg === "agent") {
44
+ parsed.agent = 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 doctor flag or subcommand: ${arg}.`);
61
+ }
62
+ if (!parsed.help && parsed.agent !== true) parsed.agent = true;
63
+ return parsed;
64
+ }
65
+ function printDoctorHelp() {
66
+ console.log(`
67
+ ${pc.bold("@farming-labs/docs doctor")}
68
+
69
+ ${pc.dim("Usage:")}
70
+ pnpm exec docs doctor
71
+ pnpm exec docs doctor --agent
72
+ pnpm exec docs doctor agent
73
+
74
+ ${pc.dim("Options:")}
75
+ ${pc.cyan("--agent")} Score agent-readiness for the current docs app (default)
76
+ ${pc.cyan("--config <path>")} Use a custom docs config path instead of ${pc.dim("docs.config.ts[x]")}
77
+ ${pc.cyan("-h, --help")} Show this help message
78
+ `);
79
+ }
80
+ function escapeRegExp(value) {
81
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
82
+ }
83
+ function splitTopLevelProperties(content) {
84
+ const properties = [];
85
+ let start = 0;
86
+ let stringQuote = null;
87
+ let escaped = false;
88
+ let braceDepth = 0;
89
+ let bracketDepth = 0;
90
+ let parenDepth = 0;
91
+ for (let index = 0; index < content.length; index += 1) {
92
+ const char = content[index];
93
+ if (stringQuote) {
94
+ if (escaped) {
95
+ escaped = false;
96
+ continue;
97
+ }
98
+ if (char === "\\") {
99
+ escaped = true;
100
+ continue;
101
+ }
102
+ if (char === stringQuote) stringQuote = null;
103
+ continue;
104
+ }
105
+ if (char === "\"" || char === "'" || char === "`") {
106
+ stringQuote = char;
107
+ continue;
108
+ }
109
+ if (char === "{") {
110
+ braceDepth += 1;
111
+ continue;
112
+ }
113
+ if (char === "}") {
114
+ braceDepth = Math.max(0, braceDepth - 1);
115
+ continue;
116
+ }
117
+ if (char === "[") {
118
+ bracketDepth += 1;
119
+ continue;
120
+ }
121
+ if (char === "]") {
122
+ bracketDepth = Math.max(0, bracketDepth - 1);
123
+ continue;
124
+ }
125
+ if (char === "(") {
126
+ parenDepth += 1;
127
+ continue;
128
+ }
129
+ if (char === ")") {
130
+ parenDepth = Math.max(0, parenDepth - 1);
131
+ continue;
132
+ }
133
+ if (char === "," && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
134
+ properties.push(content.slice(start, index));
135
+ start = index + 1;
136
+ }
137
+ }
138
+ const trailing = content.slice(start);
139
+ if (trailing.trim().length > 0) properties.push(trailing);
140
+ return properties;
141
+ }
142
+ function readTopLevelBooleanProperty(content, key) {
143
+ const rootObject = extractTopLevelConfigObject(content) ?? content;
144
+ const propertyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*:\\s*(true|false)(?:\\s|$)`);
145
+ for (const property of splitTopLevelProperties(rootObject)) {
146
+ const match = property.trim().match(propertyPattern);
147
+ if (match) return match[1] === "true";
148
+ }
149
+ }
150
+ function resolveFeatureEnabled(config, content, key) {
151
+ const current = config?.[key];
152
+ if (typeof current === "boolean") return current;
153
+ if (current && typeof current === "object") return current.enabled ?? true;
154
+ const topLevelBoolean = readTopLevelBooleanProperty(content, key);
155
+ if (typeof topLevelBoolean === "boolean") return topLevelBoolean;
156
+ const block = extractNestedObjectLiteral(content, [key]);
157
+ if (!block) return true;
158
+ return readBooleanProperty(block, "enabled") ?? true;
159
+ }
160
+ function resolveStaticExport(config, content) {
161
+ if (typeof config?.staticExport === "boolean") return config.staticExport;
162
+ return readTopLevelBooleanProperty(content, "staticExport") ?? false;
163
+ }
164
+ function resolveAgentFeedbackEnabled(config, content) {
165
+ const feedback = config?.feedback;
166
+ if (feedback && typeof feedback === "object") {
167
+ const agent = feedback.agent;
168
+ if (typeof agent === "boolean") return agent;
169
+ if (agent && typeof agent === "object") return agent.enabled ?? true;
170
+ }
171
+ const feedbackBlock = extractNestedObjectLiteral(content, ["feedback"]);
172
+ if (!feedbackBlock) return false;
173
+ const nestedAgentBlock = extractNestedObjectLiteral(content, ["feedback", "agent"]);
174
+ if (nestedAgentBlock) return readBooleanProperty(nestedAgentBlock, "enabled") ?? true;
175
+ return readBooleanProperty(feedbackBlock, "agent") ?? false;
176
+ }
177
+ function hasAgentCompactDefaults(config, content) {
178
+ if (config?.agent?.compact) return true;
179
+ return extractNestedObjectLiteral(content, ["agent", "compact"]) !== void 0;
180
+ }
181
+ function listProjectFiles(rootDir) {
182
+ const files = [];
183
+ const visit = (dir) => {
184
+ if (!existsSync(dir)) return;
185
+ for (const entry of readdirSync(dir).sort()) {
186
+ const fullPath = path.join(dir, entry);
187
+ const stat = lstatSync(fullPath);
188
+ if (stat.isSymbolicLink()) continue;
189
+ if (stat.isDirectory()) {
190
+ if (IGNORED_DIRS.has(entry)) continue;
191
+ visit(fullPath);
192
+ continue;
193
+ }
194
+ files.push(path.relative(rootDir, fullPath).replace(/\\/g, "/"));
195
+ }
196
+ };
197
+ visit(rootDir);
198
+ return files;
199
+ }
200
+ function buildFileReader(rootDir) {
201
+ const cache = /* @__PURE__ */ new Map();
202
+ return (relativePath) => {
203
+ const cached = cache.get(relativePath);
204
+ if (cached !== void 0) return cached;
205
+ const content = readFileSync(path.join(rootDir, relativePath), "utf-8");
206
+ cache.set(relativePath, content);
207
+ return content;
208
+ };
209
+ }
210
+ function formatPathList(paths) {
211
+ if (paths.length === 0) return "";
212
+ if (paths.length === 1) return paths[0];
213
+ return `${paths[0]} (+${paths.length - 1} more)`;
214
+ }
215
+ function findCodeFiles(files, predicate) {
216
+ return files.filter((relativePath) => CODE_FILE_PATTERN.test(relativePath) && predicate(relativePath));
217
+ }
218
+ function detectFrameworkFromFiles(files) {
219
+ if (files.some((file) => NEXT_CONFIG_PATTERN.test(path.basename(file)))) return "nextjs";
220
+ if (files.some((file) => file.startsWith("src/routes/") && file.includes("api.docs"))) return "tanstack-start";
221
+ if (files.some((file) => file === "src/hooks.server.js" || file === "src/hooks.server.ts")) return "sveltekit";
222
+ if (files.some((file) => ASTRO_CONFIG_PATTERN.test(path.basename(file)))) return "astro";
223
+ if (files.some((file) => file.startsWith("server/middleware/"))) return "nuxt";
224
+ return null;
225
+ }
226
+ function detectRouteSurface(rootDir, framework, staticExport, files) {
227
+ const read = buildFileReader(rootDir);
228
+ if (framework === "nextjs") {
229
+ const withDocsConfigs = files.filter((file) => NEXT_CONFIG_PATTERN.test(path.basename(file))).filter((file) => read(file).includes("withDocs("));
230
+ const apiRoutes = findCodeFiles(files, (file) => /(?:^|\/)route\.(?:[cm]?js|[cm]?ts|jsx|tsx)$/.test(file) && read(file).includes("createDocsAPI("));
231
+ if (staticExport) return {
232
+ apiMounted: false,
233
+ apiDetail: "Next static export disables /api/docs and the shared agent endpoints.",
234
+ publicMounted: false,
235
+ publicDetail: "Public .md, llms.txt, skill.md, and agent discovery routes depend on /api/docs."
236
+ };
237
+ return {
238
+ apiMounted: apiRoutes.length > 0,
239
+ apiDetail: apiRoutes.length > 0 ? `Found docs API route at ${formatPathList(apiRoutes)}.` : "Could not find a Next docs API route that uses createDocsAPI().",
240
+ publicMounted: withDocsConfigs.length > 0,
241
+ publicDetail: withDocsConfigs.length > 0 ? `Found withDocs() in ${formatPathList(withDocsConfigs)}.` : "Could not find withDocs() in next.config.*, so public docs rewrites are not verified."
242
+ };
243
+ }
244
+ if (framework === "tanstack-start") {
245
+ const apiRoutes = findCodeFiles(files, (file) => file.startsWith("src/routes/") && read(file).includes("docsServer.GET") && /createFileRoute\((["'])\/api/.test(read(file)));
246
+ const publicHandlers = findCodeFiles(files, (file) => file.startsWith("src/routes/") && read(file).includes("isDocsPublicGetRequest(") && read(file).includes("isDocsMcpRequest("));
247
+ return {
248
+ apiMounted: apiRoutes.length > 0,
249
+ apiDetail: apiRoutes.length > 0 ? `Found TanStack docs API route at ${formatPathList(apiRoutes)}.` : "Could not find a TanStack route that forwards /api/docs into docsServer.GET.",
250
+ publicMounted: publicHandlers.length > 0,
251
+ publicDetail: publicHandlers.length > 0 ? `Found public docs forwarder at ${formatPathList(publicHandlers)}.` : "Could not find a TanStack public docs forwarder using isDocsPublicGetRequest()."
252
+ };
253
+ }
254
+ if (framework === "sveltekit") {
255
+ const apiRoutes = findCodeFiles(files, (file) => file.startsWith("src/routes/") && path.basename(file).startsWith("+server.") && read(file).includes("docs.server"));
256
+ const publicHandlers = findCodeFiles(files, (file) => /^src\/hooks\.server\.(?:[cm]?js|[cm]?ts)$/.test(file) && read(file).includes("isDocsPublicGetRequest("));
257
+ return {
258
+ apiMounted: apiRoutes.length > 0,
259
+ apiDetail: apiRoutes.length > 0 ? `Found SvelteKit docs API route at ${formatPathList(apiRoutes)}.` : "Could not find a SvelteKit +server route that re-exports docs.server.",
260
+ publicMounted: publicHandlers.length > 0,
261
+ publicDetail: publicHandlers.length > 0 ? `Found SvelteKit public docs hook at ${formatPathList(publicHandlers)}.` : "Could not find hooks.server with isDocsPublicGetRequest()."
262
+ };
263
+ }
264
+ if (framework === "astro") {
265
+ const apiRoutes = findCodeFiles(files, (file) => file.startsWith("src/pages/") && read(file).includes("docsGET") && read(file).includes("docsPOST"));
266
+ const publicHandlers = findCodeFiles(files, (file) => /^src\/middleware\.(?:[cm]?js|[cm]?ts)$/.test(file) && read(file).includes("isDocsPublicGetRequest("));
267
+ return {
268
+ apiMounted: apiRoutes.length > 0,
269
+ apiDetail: apiRoutes.length > 0 ? `Found Astro docs API route at ${formatPathList(apiRoutes)}.` : "Could not find an Astro docs API route that forwards to docs.server.",
270
+ publicMounted: publicHandlers.length > 0,
271
+ publicDetail: publicHandlers.length > 0 ? `Found Astro middleware forwarder at ${formatPathList(publicHandlers)}.` : "Could not find Astro middleware using isDocsPublicGetRequest()."
272
+ };
273
+ }
274
+ if (framework === "nuxt") {
275
+ const apiRoutes = findCodeFiles(files, (file) => file.startsWith("server/api/") && read(file).includes("defineDocsHandler("));
276
+ const publicHandlers = findCodeFiles(files, (file) => file.startsWith("server/middleware/") && read(file).includes("defineDocsPublicHandler("));
277
+ return {
278
+ apiMounted: apiRoutes.length > 0,
279
+ apiDetail: apiRoutes.length > 0 ? `Found Nuxt docs API handler at ${formatPathList(apiRoutes)}.` : "Could not find a Nuxt docs API handler using defineDocsHandler().",
280
+ publicMounted: publicHandlers.length > 0,
281
+ publicDetail: publicHandlers.length > 0 ? `Found Nuxt public docs middleware at ${formatPathList(publicHandlers)}.` : "Could not find Nuxt middleware using defineDocsPublicHandler()."
282
+ };
283
+ }
284
+ return {
285
+ apiMounted: false,
286
+ apiDetail: "Could not detect a supported framework, so API route inspection was skipped.",
287
+ publicMounted: false,
288
+ publicDetail: "Could not detect a supported framework, so public route inspection was skipped."
289
+ };
290
+ }
291
+ function coverageScore(explicitCoverage) {
292
+ if (explicitCoverage >= 80) return {
293
+ status: "pass",
294
+ score: 10
295
+ };
296
+ if (explicitCoverage >= 50) return {
297
+ status: "pass",
298
+ score: 8
299
+ };
300
+ if (explicitCoverage >= 20) return {
301
+ status: "warn",
302
+ score: 5
303
+ };
304
+ if (explicitCoverage > 0) return {
305
+ status: "warn",
306
+ score: 3
307
+ };
308
+ return {
309
+ status: "warn",
310
+ score: 0
311
+ };
312
+ }
313
+ function metadataScore(descriptionCoverage, relatedCoverage) {
314
+ if (descriptionCoverage >= 90 && relatedCoverage >= 20) return {
315
+ status: "pass",
316
+ score: 5
317
+ };
318
+ if (descriptionCoverage >= 75) return {
319
+ status: "pass",
320
+ score: 4
321
+ };
322
+ if (descriptionCoverage >= 50) return {
323
+ status: "warn",
324
+ score: 2
325
+ };
326
+ if (descriptionCoverage > 0) return {
327
+ status: "warn",
328
+ score: 1
329
+ };
330
+ return {
331
+ status: "warn",
332
+ score: 0
333
+ };
334
+ }
335
+ function gradeForScore(score) {
336
+ if (score >= 90) return "Agent-optimized";
337
+ if (score >= 75) return "Agent-ready";
338
+ if (score >= 60) return "Promising";
339
+ return "Needs work";
340
+ }
341
+ function formatStatus(status) {
342
+ if (status === "pass") return pc.green("PASS");
343
+ if (status === "warn") return pc.yellow("WARN");
344
+ return pc.red("FAIL");
345
+ }
346
+ function buildCoverage(pages) {
347
+ const totalPages = pages.length;
348
+ const pagesWithAgentFiles = pages.filter((page) => page.agentRawContent !== void 0).length;
349
+ const pagesWithAgentBlocks = pages.filter((page) => page.agentFallbackRawContent !== void 0).length;
350
+ const explicitPages = pages.filter((page) => page.agentRawContent !== void 0 || page.agentFallbackRawContent !== void 0).length;
351
+ return {
352
+ totalPages,
353
+ pagesWithAgentFiles,
354
+ pagesWithAgentBlocks,
355
+ explicitPages,
356
+ explicitCoverage: totalPages === 0 ? 0 : Math.round(explicitPages / Math.max(totalPages, 1) * 100)
357
+ };
358
+ }
359
+ function buildMetadataCoverage(pages) {
360
+ const totalPages = pages.length;
361
+ const describedPages = pages.filter((page) => typeof page.description === "string" && page.description.trim().length > 0).length;
362
+ const relatedPages = pages.filter((page) => Array.isArray(page.related) && page.related.length > 0).length;
363
+ return {
364
+ describedPages,
365
+ relatedPages,
366
+ descriptionCoverage: totalPages === 0 ? 0 : Math.round(describedPages / totalPages * 100),
367
+ relatedCoverage: totalPages === 0 ? 0 : Math.round(relatedPages / totalPages * 100)
368
+ };
369
+ }
370
+ async function loadDocsConfigModuleWithProjectEnv(rootDir, explicitPath) {
371
+ const env = loadProjectEnv(rootDir);
372
+ const injectedKeys = Object.entries(env).filter(([key]) => process.env[key] === void 0).map(([key, value]) => {
373
+ process.env[key] = value;
374
+ return key;
375
+ });
376
+ try {
377
+ return await loadDocsConfigModule(rootDir, explicitPath);
378
+ } finally {
379
+ for (const key of injectedKeys) delete process.env[key];
380
+ }
381
+ }
382
+ function makeCheck(id, title, status, score, maxScore, detail, recommendation) {
383
+ return {
384
+ id,
385
+ title,
386
+ status,
387
+ score,
388
+ maxScore,
389
+ detail,
390
+ recommendation
391
+ };
392
+ }
393
+ async function inspectAgentReadiness(options = {}) {
394
+ const rootDir = process.cwd();
395
+ const files = listProjectFiles(rootDir);
396
+ const framework = detectFramework(rootDir) ?? detectFrameworkFromFiles(files) ?? "unknown";
397
+ const configCheckMax = 10;
398
+ let configPath;
399
+ try {
400
+ configPath = resolveDocsConfigPath(rootDir, options.configPath);
401
+ } catch (error) {
402
+ const checks = [makeCheck("config", "Docs config", "fail", 0, configCheckMax, error instanceof Error ? error.message : String(error), "Add docs.config.ts[x] or pass --config so the doctor can inspect the docs app.")];
403
+ return {
404
+ mode: "agent",
405
+ framework,
406
+ score: 0,
407
+ maxScore: 100,
408
+ grade: gradeForScore(0),
409
+ checks,
410
+ coverage: {
411
+ totalPages: 0,
412
+ pagesWithAgentFiles: 0,
413
+ pagesWithAgentBlocks: 0,
414
+ explicitPages: 0,
415
+ explicitCoverage: 0
416
+ },
417
+ recommendations: checks.map((check) => check.recommendation).filter(Boolean)
418
+ };
419
+ }
420
+ const configContent = readFileSync(configPath, "utf-8");
421
+ const loadedConfig = await loadDocsConfigModuleWithProjectEnv(rootDir, options.configPath);
422
+ const config = loadedConfig?.config;
423
+ const entry = config?.entry ?? readTopLevelStringProperty(configContent, "entry") ?? "docs";
424
+ const contentDir = config?.contentDir ?? resolveDocsContentDir(rootDir, configContent, entry);
425
+ const ordering = config?.ordering === "alphabetical" || config?.ordering === "numeric" || Array.isArray(config?.ordering) ? config.ordering : void 0;
426
+ const siteTitle = typeof config?.nav?.title === "string" ? config.nav.title : readNavTitle(configContent) ?? "Documentation";
427
+ const staticExport = resolveStaticExport(config, configContent);
428
+ const llmsEnabled = resolveFeatureEnabled(config, configContent, "llmsTxt");
429
+ const searchEnabled = resolveFeatureEnabled(config, configContent, "search");
430
+ const mcpEnabled = resolveFeatureEnabled(config, configContent, "mcp");
431
+ const agentFeedbackEnabled = resolveAgentFeedbackEnabled(config, configContent);
432
+ const compactConfigured = hasAgentCompactDefaults(config, configContent);
433
+ const skillFileExists = existsSync(path.join(rootDir, "skill.md"));
434
+ const source = createFilesystemDocsMcpSource({
435
+ rootDir,
436
+ entry,
437
+ contentDir,
438
+ siteTitle,
439
+ ordering
440
+ });
441
+ const pages = await Promise.resolve(source.getPages());
442
+ const coverage = buildCoverage(pages);
443
+ const metadataCoverage = buildMetadataCoverage(pages);
444
+ const metadataResult = metadataScore(metadataCoverage.descriptionCoverage, metadataCoverage.relatedCoverage);
445
+ const routeSurface = detectRouteSurface(rootDir, framework, staticExport, files);
446
+ const mcpConfig = resolveDocsMcpConfig(config?.mcp ?? void 0, { defaultName: siteTitle });
447
+ const feedbackRoute = DEFAULT_AGENT_FEEDBACK_ROUTE;
448
+ const feedbackSchemaRoute = `${feedbackRoute}/schema`;
449
+ const checks = [];
450
+ checks.push(makeCheck("config", "Docs config", "pass", 10, configCheckMax, loadedConfig ? `Resolved ${path.relative(rootDir, loadedConfig.path).replace(/\\/g, "/")} and evaluated the config module.` : `Resolved ${path.relative(rootDir, configPath).replace(/\\/g, "/")} using static parsing fallback.`));
451
+ const contentDirAbs = path.resolve(rootDir, contentDir);
452
+ checks.push(coverage.totalPages > 0 ? makeCheck("content", "Docs content", "pass", 10, 10, `Found ${coverage.totalPages} docs page${coverage.totalPages === 1 ? "" : "s"} in ${path.relative(rootDir, contentDirAbs).replace(/\\/g, "/")}.`) : makeCheck("content", "Docs content", "fail", 0, 10, `No folder-based docs pages were found in ${path.relative(rootDir, contentDirAbs).replace(/\\/g, "/")}.`, "Add index/page MDX files under the configured contentDir so the machine-readable surfaces have pages to serve."));
453
+ checks.push(makeCheck("api-route", "Docs API route", routeSurface.apiMounted ? "pass" : "fail", routeSurface.apiMounted ? 10 : 0, 10, routeSurface.apiDetail, routeSurface.apiMounted ? void 0 : "Wire the framework docs API route so /api/docs can serve markdown, llms.txt, skill.md, and discovery responses."));
454
+ checks.push(makeCheck("public-routes", "Public agent routes", routeSurface.publicMounted ? "pass" : "fail", routeSurface.publicMounted ? 10 : 0, 10, routeSurface.publicDetail, routeSurface.publicMounted ? void 0 : "Add the framework public forwarder so /.well-known/*, /llms.txt, /skill.md, /mcp, and .md routes resolve from the shared docs API."));
455
+ checks.push(makeCheck("agent-discovery", "Agent discovery spec", routeSurface.apiMounted && routeSurface.publicMounted ? "pass" : "fail", routeSurface.apiMounted && routeSurface.publicMounted ? 5 : 0, 5, routeSurface.apiMounted && routeSurface.publicMounted ? `Expected discovery endpoints are available through ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}, ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE}, and /api/docs?agent=spec.` : "Could not verify the shared agent discovery spec endpoints because docs API/public route wiring is incomplete.", routeSurface.apiMounted && routeSurface.publicMounted ? void 0 : "Make sure both the docs API handler and the public docs forwarder are mounted so agents can discover the site through the well-known agent spec."));
456
+ checks.push(llmsEnabled ? makeCheck("llms", "llms.txt discovery", "pass", 10, 10, `Enabled via ${DEFAULT_LLMS_TXT_ROUTE} and ${DEFAULT_LLMS_FULL_TXT_ROUTE}.`) : makeCheck("llms", "llms.txt discovery", "warn", 0, 10, `${DEFAULT_LLMS_TXT_ROUTE} and ${DEFAULT_LLMS_FULL_TXT_ROUTE} are disabled in docs config.`, "Enable llmsTxt so agents and GEO crawlers can discover the docs index and full context surfaces."));
457
+ checks.push(skillFileExists ? makeCheck("skill", "Skill document", "pass", 5, 5, `Found root skill.md for ${DEFAULT_SKILL_MD_ROUTE} and ${DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE}.`) : makeCheck("skill", "Skill document", "warn", 3, 5, `No root skill.md found; the framework will serve the generated fallback at ${DEFAULT_SKILL_MD_ROUTE}.`, "Add a root skill.md if you want a custom site-specific bootstrap document instead of the generated fallback."));
458
+ checks.push(mcpEnabled ? makeCheck("mcp", "MCP access", "pass", 10, 10, `Enabled with public aliases ${DEFAULT_MCP_PUBLIC_ROUTE} and ${DEFAULT_MCP_WELL_KNOWN_ROUTE} (canonical route ${mcpConfig.route}).`) : makeCheck("mcp", "MCP access", "warn", 0, 10, "MCP is disabled in docs config.", "Enable mcp so agents can use list/search/read tools directly instead of only scraping markdown routes."));
459
+ checks.push(searchEnabled ? makeCheck("search", "Search surface", "pass", 5, 5, "Search is enabled for the shared docs API and agent flows.") : makeCheck("search", "Search surface", "warn", 0, 5, "Search is disabled in docs config.", "Enable search so agents can narrow retrieval before reading whole markdown pages."));
460
+ checks.push(agentFeedbackEnabled ? makeCheck("feedback", "Agent feedback", "pass", 5, 5, `Structured agent feedback is enabled at ${feedbackRoute} with schema ${feedbackSchemaRoute}.`) : makeCheck("feedback", "Agent feedback", "warn", 0, 5, "Structured agent feedback is not enabled.", "Enable feedback.agent if you want agents to discover and post feedback through the shared docs API."));
461
+ checks.push(makeCheck("metadata", "Page metadata", metadataResult.status, metadataResult.score, 5, coverage.totalPages > 0 ? `${metadataCoverage.describedPages}/${coverage.totalPages} pages include descriptions and ${metadataCoverage.relatedPages}/${coverage.totalPages} pages include related links (${metadataCoverage.descriptionCoverage}% described, ${metadataCoverage.relatedCoverage}% related).` : "No docs pages were available to score page metadata.", metadataCoverage.descriptionCoverage >= 75 ? void 0 : "Add page descriptions and related links to more docs pages so agent markdown output carries better context and navigation hints."));
462
+ const coverageResult = coverageScore(coverage.explicitCoverage);
463
+ checks.push(makeCheck("coverage", "Explicit page optimization", coverageResult.status, coverageResult.score, 10, coverage.totalPages > 0 ? `${coverage.explicitPages}/${coverage.totalPages} pages define explicit machine-only context (${coverage.pagesWithAgentFiles} agent.md, ${coverage.pagesWithAgentBlocks} Agent blocks, ${coverage.explicitCoverage}% of pages).` : "No docs pages were available to score explicit page optimization.", coverage.explicitCoverage >= 50 ? void 0 : "Add agent.md files or <Agent> blocks to more pages, or run docs agent compact to create page-level machine docs."));
464
+ checks.push(compactConfigured ? makeCheck("compact", "Compaction defaults", "pass", 5, 5, "agent.compact defaults are configured in docs.config for repeatable page compaction.") : makeCheck("compact", "Compaction defaults", "warn", 0, 5, "No agent.compact defaults were found in docs config.", "Add agent.compact defaults if you want docs agent compact to run without repeating model and key settings."));
465
+ const score = checks.reduce((total, check) => total + check.score, 0);
466
+ const maxScore = checks.reduce((total, check) => total + check.maxScore, 0);
467
+ return {
468
+ mode: "agent",
469
+ framework,
470
+ configPath: path.relative(rootDir, configPath).replace(/\\/g, "/"),
471
+ entry,
472
+ contentDir,
473
+ score,
474
+ maxScore,
475
+ grade: gradeForScore(score),
476
+ checks,
477
+ coverage,
478
+ recommendations: checks.map((check) => check.recommendation).filter((recommendation) => Boolean(recommendation)).slice(0, 3)
479
+ };
480
+ }
481
+ function printAgentDoctorReport(report) {
482
+ console.log(`${pc.bold("@farming-labs/docs doctor")} ${pc.dim("—")} ${pc.bold("agent")}`);
483
+ console.log();
484
+ console.log(`${pc.bold("Score:")} ${pc.cyan(`${report.score}/${report.maxScore}`)} ${pc.dim(`(${report.grade})`)}`);
485
+ console.log(`${pc.bold("Framework:")} ${report.framework} ${pc.dim("•")} ${pc.bold("Entry:")} ${report.entry ?? "docs"} ${pc.dim("•")} ${pc.bold("Content:")} ${report.contentDir ?? "-"}`);
486
+ console.log(`${pc.bold("Explicit agent-friendly pages:")} ${report.coverage.explicitPages}/${report.coverage.totalPages} pages ${pc.dim(`(${report.coverage.explicitCoverage}%)`)}`);
487
+ console.log();
488
+ for (const check of report.checks) {
489
+ console.log(`${formatStatus(check.status)} ${check.title} ${pc.dim(`(${check.score}/${check.maxScore})`)}`);
490
+ console.log(` ${check.detail}`);
491
+ }
492
+ if (report.recommendations.length > 0) {
493
+ console.log();
494
+ console.log(pc.bold("Next steps"));
495
+ for (const recommendation of report.recommendations) console.log(`- ${recommendation}`);
496
+ }
497
+ console.log();
498
+ console.log(pc.dim(`Expected public surfaces: ${DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE}, ${DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE}, ${DEFAULT_LLMS_TXT_ROUTE}, ${DEFAULT_LLMS_FULL_TXT_ROUTE}, ${DEFAULT_SKILL_MD_ROUTE}, ${DEFAULT_MCP_PUBLIC_ROUTE}`));
499
+ }
500
+ async function runDoctor(options = {}) {
501
+ const report = await inspectAgentReadiness(options);
502
+ printAgentDoctorReport(report);
503
+ return report;
504
+ }
505
+
506
+ //#endregion
507
+ export { parseDoctorArgs, printDoctorHelp, runDoctor };
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { $ as SidebarFolderNode, A as DocsSearchQuery, B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, E as DocsSearchChunkingConfig, F as FeedbackConfig, G as OrderingItem, H as OpenDocsConfig, I as FontStyle, J as PageOpenGraph, K as PageActionsConfig, L as GithubConfig, M as DocsSearchResultType, N as DocsSearchSourcePage, O as DocsSearchDocument, P as DocsTheme, Q as SidebarConfig, R as LastUpdatedConfig, S as DocsRelatedItem, T as DocsSearchAdapterFactory, U as OpenDocsProvider, V as OGConfig, W as OpenGraphImage, X as ResolvedDocsRelatedLink, Y as PageTwitter, Z as SidebarComponentProps, _ as DocsI18nConfig, a as ApiReferenceRenderer, at as TypesenseDocsSearchConfig, b as DocsMetadata, c as ChangelogFrontmatter, d as CustomDocsSearchConfig, et as SidebarNode, f as DocsAgentFeedbackContext, g as DocsFeedbackValue, h as DocsFeedbackData, i as ApiReferenceConfig, it as ThemeToggleConfig, j as DocsSearchResult, k as DocsSearchEmbeddingsConfig, l as CodeBlockCopyData, m as DocsConfig, n as AgentFeedbackConfig, nt as SidebarTree, o as BreadcrumbConfig, ot as TypographyConfig, p as DocsAgentFeedbackData, q as PageFrontmatter, r as AlgoliaDocsSearchConfig, rt as SimpleDocsSearchConfig, s as ChangelogConfig, st as UIConfig, t as AIConfig, tt as SidebarPageNode, u as CopyMarkdownConfig, v as DocsMcpConfig, w as DocsSearchAdapterContext, x as DocsNav, y as DocsMcpToolsConfig, z as LlmsTxtConfig } from "./types-CKdSg7n8.mjs";
1
+ import { $ as SidebarFolderNode, A as DocsSearchQuery, B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, E as DocsSearchChunkingConfig, F as FeedbackConfig, G as OrderingItem, H as OpenDocsConfig, I as FontStyle, J as PageOpenGraph, K as PageActionsConfig, L as GithubConfig, M as DocsSearchResultType, N as DocsSearchSourcePage, O as DocsSearchDocument, P as DocsTheme, Q as SidebarConfig, R as LastUpdatedConfig, S as DocsRelatedItem, T as DocsSearchAdapterFactory, U as OpenDocsProvider, V as OGConfig, W as OpenGraphImage, X as ResolvedDocsRelatedLink, Y as PageTwitter, Z as SidebarComponentProps, _ as DocsI18nConfig, a as ApiReferenceRenderer, at as TypesenseDocsSearchConfig, b as DocsMetadata, c as ChangelogFrontmatter, d as CustomDocsSearchConfig, et as SidebarNode, f as DocsAgentFeedbackContext, g as DocsFeedbackValue, h as DocsFeedbackData, i as ApiReferenceConfig, it as ThemeToggleConfig, j as DocsSearchResult, k as DocsSearchEmbeddingsConfig, l as CodeBlockCopyData, m as DocsConfig, n as AgentFeedbackConfig, nt as SidebarTree, o as BreadcrumbConfig, ot as TypographyConfig, p as DocsAgentFeedbackData, q as PageFrontmatter, r as AlgoliaDocsSearchConfig, rt as SimpleDocsSearchConfig, s as ChangelogConfig, st as UIConfig, t as AIConfig, tt as SidebarPageNode, u as CopyMarkdownConfig, v as DocsMcpConfig, w as DocsSearchAdapterContext, x as DocsNav, y as DocsMcpToolsConfig, z as LlmsTxtConfig } from "./types-nlEL5lLq.mjs";
2
2
  import { DocsMcpPage, DocsMcpResolvedConfig } from "./mcp.mjs";
3
- import { a as createSimpleSearchAdapter, c as resolveSearchRequestConfig, i as createMcpSearchAdapter, n as createAlgoliaSearchAdapter, o as createTypesenseSearchAdapter, r as createCustomSearchAdapter, s as performDocsSearch, t as buildDocsSearchDocuments } from "./search-C15yRNVE.mjs";
3
+ import { a as createSimpleSearchAdapter, c as resolveSearchRequestConfig, i as createMcpSearchAdapter, n as createAlgoliaSearchAdapter, o as createTypesenseSearchAdapter, r as createCustomSearchAdapter, s as performDocsSearch, t as buildDocsSearchDocuments } from "./search-cPHa_3jJ.mjs";
4
4
 
5
5
  //#region src/define-docs.d.ts
6
6
  /**
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { A as buildPageTwitter, B as defineDocs, C as renderDocsMarkdownDocument, D as resolveDocsMarkdownRequest, E as resolveDocsLlmsTxtFormat, F as resolveDocsPath, I as createTheme, L as extendTheme, M as resolveTitle, N as resolveDocsI18n, O as resolveDocsSkillFormat, P as resolveDocsLocale, R as deepMerge, S as normalizeDocsUrlPath, T as resolveDocsAgentMdxContent, _ as isDocsAgentDiscoveryRequest, a as DEFAULT_DOCS_API_ROUTE, b as isDocsSkillRequest, c as DEFAULT_LLMS_TXT_ROUTE, d as DEFAULT_MCP_ROUTE, f as DEFAULT_MCP_WELL_KNOWN_ROUTE, g as findDocsMarkdownPage, h as buildDocsAgentDiscoverySpec, i as DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE, j as resolveOGImage, k as buildPageOpenGraph, l as DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE, m as DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE, n as DEFAULT_AGENT_SPEC_ROUTE, o as DEFAULT_LLMS_FULL_TXT_ROUTE, p as DEFAULT_SKILL_MD_ROUTE, r as DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE, s as DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE, t as DEFAULT_AGENT_FEEDBACK_ROUTE, u as DEFAULT_MCP_PUBLIC_ROUTE, v as isDocsMcpRequest, w as renderDocsSkillDocument, x as normalizeDocsPathSegment, y as isDocsPublicGetRequest, z as resolveChangelogConfig } from "./agent-qcweNgy-.mjs";
1
+ import { a as resolveDocsI18n, c as createTheme, d as resolveChangelogConfig, f as defineDocs, i as resolveTitle, l as extendTheme, n as buildPageTwitter, o as resolveDocsLocale, r as resolveOGImage, s as resolveDocsPath, t as buildPageOpenGraph, u as deepMerge } from "./metadata-CdrtTSz-.mjs";
2
2
  import { a as createSimpleSearchAdapter, c as resolveSearchRequestConfig, i as createMcpSearchAdapter, l as normalizeDocsRelated, n as createAlgoliaSearchAdapter, o as createTypesenseSearchAdapter, r as createCustomSearchAdapter, s as performDocsSearch, t as buildDocsSearchDocuments, u as renderDocsRelatedMarkdownLines } from "./search-8oEskRtz.mjs";
3
+ import { C as renderDocsMarkdownDocument, D as resolveDocsMarkdownRequest, E as resolveDocsLlmsTxtFormat, O as resolveDocsSkillFormat, S as normalizeDocsUrlPath, T as resolveDocsAgentMdxContent, _ as isDocsAgentDiscoveryRequest, a as DEFAULT_DOCS_API_ROUTE, b as isDocsSkillRequest, c as DEFAULT_LLMS_TXT_ROUTE, d as DEFAULT_MCP_ROUTE, f as DEFAULT_MCP_WELL_KNOWN_ROUTE, g as findDocsMarkdownPage, h as buildDocsAgentDiscoverySpec, i as DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE, l as DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE, m as DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE, n as DEFAULT_AGENT_SPEC_ROUTE, o as DEFAULT_LLMS_FULL_TXT_ROUTE, p as DEFAULT_SKILL_MD_ROUTE, r as DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE, s as DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE, t as DEFAULT_AGENT_FEEDBACK_ROUTE, u as DEFAULT_MCP_PUBLIC_ROUTE, v as isDocsMcpRequest, w as renderDocsSkillDocument, x as normalizeDocsPathSegment, y as isDocsPublicGetRequest } from "./agent-CbAtuZAc.mjs";
3
4
 
4
5
  export { DEFAULT_AGENT_FEEDBACK_ROUTE, DEFAULT_AGENT_SPEC_ROUTE, DEFAULT_AGENT_SPEC_WELL_KNOWN_JSON_ROUTE, DEFAULT_AGENT_SPEC_WELL_KNOWN_ROUTE, DEFAULT_DOCS_API_ROUTE, DEFAULT_LLMS_FULL_TXT_ROUTE, DEFAULT_LLMS_FULL_TXT_WELL_KNOWN_ROUTE, DEFAULT_LLMS_TXT_ROUTE, DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE, DEFAULT_MCP_PUBLIC_ROUTE, DEFAULT_MCP_ROUTE, DEFAULT_MCP_WELL_KNOWN_ROUTE, DEFAULT_SKILL_MD_ROUTE, DEFAULT_SKILL_MD_WELL_KNOWN_ROUTE, buildDocsAgentDiscoverySpec, buildDocsSearchDocuments, buildPageOpenGraph, buildPageTwitter, createAlgoliaSearchAdapter, createCustomSearchAdapter, createMcpSearchAdapter, createSimpleSearchAdapter, createTheme, createTypesenseSearchAdapter, deepMerge, defineDocs, extendTheme, findDocsMarkdownPage, isDocsAgentDiscoveryRequest, isDocsMcpRequest, isDocsPublicGetRequest, isDocsSkillRequest, normalizeDocsPathSegment, normalizeDocsRelated, normalizeDocsUrlPath, performDocsSearch, renderDocsMarkdownDocument, renderDocsRelatedMarkdownLines, renderDocsSkillDocument, resolveChangelogConfig, resolveDocsAgentMdxContent, resolveDocsI18n, resolveDocsLlmsTxtFormat, resolveDocsLocale, resolveDocsMarkdownRequest, resolveDocsPath, resolveDocsSkillFormat, resolveOGImage, resolveSearchRequestConfig, resolveTitle };
@@ -1,4 +1,4 @@
1
- import { a as devInstallCommand, c as installCommand, d as writeFileSafe, i as detectPackageManagerFromLockfile, l as readFileSafe, n as detectGlobalCssFiles, o as exec, r as detectNextAppDir, s as fileExists, t as detectFramework, u as spawnAndWaitFor } from "./utils-CpTFbAiS.mjs";
1
+ import { a as devInstallCommand, c as installCommand, d as writeFileSafe, i as detectPackageManagerFromLockfile, l as readFileSafe, n as detectGlobalCssFiles, o as exec, r as detectNextAppDir, s as fileExists, t as detectFramework, u as spawnAndWaitFor } from "./utils-DSMXVnEu.mjs";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import pc from "picocolors";
@@ -1,7 +1,7 @@
1
- import "./api-reference-GDAEzQn1.mjs";
1
+ import "./api-reference-y7cqtq4w.mjs";
2
2
  import { createFilesystemDocsMcpSource, resolveDocsMcpConfig, runDocsMcpStdio } from "./mcp.mjs";
3
3
  import "./server.mjs";
4
- import { a as readBooleanProperty, d as resolveDocsConfigPath, f as resolveDocsContentDir, l as readStringProperty, n as extractObjectLiteral, s as readNavTitle } from "./config-CyqDp8tD.mjs";
4
+ import { c as readNavTitle, f as resolveDocsConfigPath, n as extractObjectLiteral, o as readBooleanProperty, p as resolveDocsContentDir, u as readStringProperty } from "./config-C7sUsMkm.mjs";
5
5
  import { readFileSync } from "node:fs";
6
6
 
7
7
  //#region src/cli/mcp.ts
package/dist/mcp.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { D as DocsSearchConfig, G as OrderingItem, N as DocsSearchSourcePage, v as DocsMcpConfig } from "./types-CKdSg7n8.mjs";
1
+ import { D as DocsSearchConfig, G as OrderingItem, N as DocsSearchSourcePage, v as DocsMcpConfig } from "./types-nlEL5lLq.mjs";
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
 
4
4
  //#region src/mcp.d.ts
@@ -0,0 +1,268 @@
1
+ //#region src/define-docs.ts
2
+ /**
3
+ * Define docs configuration. Validates and returns the config.
4
+ */
5
+ function defineDocs(config) {
6
+ return {
7
+ entry: config.entry ?? "docs",
8
+ contentDir: config.contentDir,
9
+ i18n: config.i18n,
10
+ theme: config.theme,
11
+ nav: config.nav,
12
+ github: config.github,
13
+ themeToggle: config.themeToggle,
14
+ breadcrumb: config.breadcrumb,
15
+ sidebar: config.sidebar,
16
+ components: config.components,
17
+ onCopyClick: config.onCopyClick,
18
+ feedback: config.feedback,
19
+ search: config.search,
20
+ mcp: config.mcp,
21
+ icons: config.icons,
22
+ pageActions: config.pageActions,
23
+ lastUpdated: config.lastUpdated,
24
+ llmsTxt: config.llmsTxt,
25
+ ai: config.ai,
26
+ ordering: config.ordering,
27
+ metadata: config.metadata,
28
+ og: config.og,
29
+ changelog: config.changelog,
30
+ apiReference: config.apiReference,
31
+ agent: config.agent
32
+ };
33
+ }
34
+
35
+ //#endregion
36
+ //#region src/changelog.ts
37
+ function normalizePathSegment(value, fallback) {
38
+ return (value ?? fallback).trim().replace(/^\/+|\/+$/g, "") || fallback;
39
+ }
40
+ function normalizeContentDir(value) {
41
+ const trimmed = value?.trim();
42
+ if (!trimmed) return "changelog";
43
+ return trimmed.replace(/\/+$/, "") || "changelog";
44
+ }
45
+ function resolveChangelogConfig(value) {
46
+ if (value === false || value === void 0) return {
47
+ enabled: false,
48
+ path: "changelog",
49
+ contentDir: "changelog",
50
+ title: "Changelog",
51
+ description: void 0,
52
+ search: true
53
+ };
54
+ if (value === true) return {
55
+ enabled: true,
56
+ path: "changelog",
57
+ contentDir: "changelog",
58
+ title: "Changelog",
59
+ description: void 0,
60
+ search: true
61
+ };
62
+ return {
63
+ enabled: value.enabled !== false,
64
+ path: normalizePathSegment(value.path, "changelog"),
65
+ contentDir: normalizeContentDir(value.contentDir),
66
+ title: value.title?.trim() || "Changelog",
67
+ description: value.description?.trim() || void 0,
68
+ search: value.search !== false,
69
+ actionsComponent: value.actionsComponent
70
+ };
71
+ }
72
+
73
+ //#endregion
74
+ //#region src/utils.ts
75
+ /**
76
+ * Deep merge utility for theme overrides.
77
+ * Merges objects recursively; later values override earlier ones.
78
+ */
79
+ function deepMerge(target, ...sources) {
80
+ if (!sources.length) return target;
81
+ const source = sources.shift();
82
+ if (!source) return target;
83
+ const result = { ...target };
84
+ for (const key of Object.keys(source)) {
85
+ const sourceVal = source[key];
86
+ const targetVal = result[key];
87
+ if (sourceVal && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal && typeof targetVal === "object" && !Array.isArray(targetVal)) result[key] = deepMerge(targetVal, sourceVal);
88
+ else if (sourceVal !== void 0) result[key] = sourceVal;
89
+ }
90
+ if (sources.length) return deepMerge(result, ...sources);
91
+ return result;
92
+ }
93
+
94
+ //#endregion
95
+ //#region src/create-theme.ts
96
+ /**
97
+ * Create a theme preset factory.
98
+ *
99
+ * Returns a function that accepts optional overrides and deep-merges them
100
+ * with the base theme defaults. This is the same pattern used by the
101
+ * built-in `fumadocs()`, `darksharp()`, and `pixelBorder()` presets.
102
+ *
103
+ * @param baseTheme - The default theme configuration
104
+ * @returns A factory function `(overrides?) => DocsTheme`
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * import { createTheme } from "@farming-labs/docs";
109
+ *
110
+ * export const myTheme = createTheme({
111
+ * name: "my-theme",
112
+ * ui: {
113
+ * colors: { primary: "#6366f1" },
114
+ * layout: { contentWidth: 800 },
115
+ * },
116
+ * });
117
+ * ```
118
+ */
119
+ function createTheme(baseTheme) {
120
+ return function themeFactory(overrides = {}) {
121
+ const merged = deepMerge(baseTheme, overrides);
122
+ if (overrides.ui?.colors) merged._userColorOverrides = { ...overrides.ui.colors };
123
+ return merged;
124
+ };
125
+ }
126
+ /**
127
+ * Extend an existing theme preset with additional defaults.
128
+ *
129
+ * Useful when you want to build on top of an existing theme (e.g. fumadocs)
130
+ * rather than starting from scratch.
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * import { extendTheme } from "@farming-labs/docs";
135
+ * import { fumadocs } from "@farming-labs/theme/default";
136
+ *
137
+ * // Start with fumadocs defaults, override some values
138
+ * export const myTheme = extendTheme(fumadocs(), {
139
+ * name: "my-custom-fumadocs",
140
+ * ui: { colors: { primary: "#22c55e" } },
141
+ * });
142
+ * ```
143
+ */
144
+ function extendTheme(baseTheme, extensions) {
145
+ return deepMerge(baseTheme, extensions);
146
+ }
147
+
148
+ //#endregion
149
+ //#region src/i18n.ts
150
+ function normalizeSegment(value) {
151
+ return value.replace(/^\/+|\/+$/g, "");
152
+ }
153
+ function splitSegments(value) {
154
+ const cleaned = normalizeSegment(value);
155
+ return cleaned ? cleaned.split("/").filter(Boolean) : [];
156
+ }
157
+ function resolveDocsI18n(config) {
158
+ if (!config || !Array.isArray(config.locales)) return null;
159
+ const locales = Array.from(new Set(config.locales.map((l) => l.trim()).filter(Boolean)));
160
+ if (locales.length === 0) return null;
161
+ return {
162
+ locales,
163
+ defaultLocale: config.defaultLocale && locales.includes(config.defaultLocale) ? config.defaultLocale : locales[0]
164
+ };
165
+ }
166
+ function resolveDocsLocale(searchParams, i18n) {
167
+ if (!i18n) return void 0;
168
+ const raw = searchParams.get("lang") ?? searchParams.get("locale");
169
+ if (!raw) return void 0;
170
+ if (i18n.locales.includes(raw)) return raw;
171
+ return i18n.defaultLocale;
172
+ }
173
+ function resolveDocsPath(pathname, entry) {
174
+ const entryBase = normalizeSegment(entry || "docs") || "docs";
175
+ const entryParts = splitSegments(entryBase);
176
+ const pathParts = splitSegments(pathname);
177
+ let rest = pathParts;
178
+ if (entryParts.length > 0) {
179
+ if (pathParts.slice(0, entryParts.length).join("/") === entryParts.join("/")) rest = pathParts.slice(entryParts.length);
180
+ }
181
+ return {
182
+ slug: rest.join("/"),
183
+ entryPath: entryBase
184
+ };
185
+ }
186
+
187
+ //#endregion
188
+ //#region src/metadata.ts
189
+ /**
190
+ * Resolve page title using metadata titleTemplate.
191
+ * %s is replaced with page title.
192
+ */
193
+ function resolveTitle(pageTitle, metadata) {
194
+ return (metadata?.titleTemplate ?? "%s").replace("%s", pageTitle);
195
+ }
196
+ /**
197
+ * Resolve OG image URL for a page.
198
+ * Prefers page.openGraph.images[0], then page.ogImage, then config endpoint/default.
199
+ */
200
+ function resolveOGImage(page, ogConfig, baseUrl) {
201
+ if (page.openGraph?.images?.length) return resolveImageUrl(page.openGraph.images[0].url, baseUrl);
202
+ if (!ogConfig?.enabled) return void 0;
203
+ if (page.ogImage) return resolveImageUrl(page.ogImage, baseUrl);
204
+ if (ogConfig.type === "dynamic" && ogConfig.endpoint) return `${baseUrl ?? ""}${ogConfig.endpoint}`;
205
+ return ogConfig.defaultImage;
206
+ }
207
+ function resolveImageUrl(url, baseUrl) {
208
+ if (url.startsWith("/") || url.startsWith("http")) return url;
209
+ const base = baseUrl ?? "";
210
+ return `${base}${base.length > 0 && !base.endsWith("/") ? "/" : ""}${url}`;
211
+ }
212
+ /**
213
+ * Build the Open Graph metadata object for a page.
214
+ * When the page has openGraph in frontmatter, uses it (with title/description filled from page if omitted).
215
+ * Otherwise uses ogImage or config (dynamic endpoint / defaultImage).
216
+ */
217
+ function buildPageOpenGraph(page, ogConfig, baseUrl) {
218
+ if (page.openGraph) {
219
+ const images = page.openGraph.images?.length ? page.openGraph.images.map((img) => ({
220
+ url: resolveImageUrl(img.url, baseUrl),
221
+ width: img.width ?? 1200,
222
+ height: img.height ?? 630
223
+ })) : void 0;
224
+ return {
225
+ title: page.openGraph.title ?? page.title,
226
+ description: page.openGraph.description ?? page.description,
227
+ ...images && { images }
228
+ };
229
+ }
230
+ const url = resolveOGImage(page, ogConfig, baseUrl);
231
+ if (!url) return void 0;
232
+ return {
233
+ title: page.title,
234
+ ...page.description && { description: page.description },
235
+ images: [{
236
+ url,
237
+ width: 1200,
238
+ height: 630
239
+ }]
240
+ };
241
+ }
242
+ /**
243
+ * Build the Twitter card metadata object for a page.
244
+ * When the page has twitter in frontmatter, uses it.
245
+ * Otherwise builds from ogImage or config (dynamic endpoint).
246
+ */
247
+ function buildPageTwitter(page, ogConfig, baseUrl) {
248
+ if (page.twitter) {
249
+ const images = page.twitter.images?.length ? page.twitter.images.map((url) => resolveImageUrl(url, baseUrl)) : void 0;
250
+ return {
251
+ ...page.twitter.card && { card: page.twitter.card },
252
+ ...page.twitter.title !== void 0 && { title: page.twitter.title },
253
+ ...page.twitter.description !== void 0 && { description: page.twitter.description },
254
+ ...images && { images }
255
+ };
256
+ }
257
+ const url = resolveOGImage(page, ogConfig, baseUrl);
258
+ if (!url) return void 0;
259
+ return {
260
+ card: "summary_large_image",
261
+ title: page.title,
262
+ ...page.description && { description: page.description },
263
+ images: [url]
264
+ };
265
+ }
266
+
267
+ //#endregion
268
+ export { resolveDocsI18n as a, createTheme as c, resolveChangelogConfig as d, defineDocs as f, resolveTitle as i, extendTheme as l, buildPageTwitter as n, resolveDocsLocale as o, resolveOGImage as r, resolveDocsPath as s, buildPageOpenGraph as t, deepMerge as u };
@@ -1,8 +1,8 @@
1
1
  import { n as createAlgoliaSearchAdapter, o as createTypesenseSearchAdapter, t as buildDocsSearchDocuments } from "./search-8oEskRtz.mjs";
2
- import "./api-reference-GDAEzQn1.mjs";
2
+ import "./api-reference-y7cqtq4w.mjs";
3
3
  import { createFilesystemDocsMcpSource } from "./mcp.mjs";
4
4
  import "./server.mjs";
5
- import { d as resolveDocsConfigPath, f as resolveDocsContentDir, i as loadProjectEnv, u as readTopLevelStringProperty } from "./config-CyqDp8tD.mjs";
5
+ import { a as loadProjectEnv, d as readTopLevelStringProperty, f as resolveDocsConfigPath, p as resolveDocsContentDir } from "./config-C7sUsMkm.mjs";
6
6
  import { readFileSync } from "node:fs";
7
7
  import pc from "picocolors";
8
8
 
@@ -1,4 +1,4 @@
1
- import { B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, E as DocsSearchChunkingConfig, N as DocsSearchSourcePage, O as DocsSearchDocument, T as DocsSearchAdapterFactory, at as TypesenseDocsSearchConfig, d as CustomDocsSearchConfig, j as DocsSearchResult, r as AlgoliaDocsSearchConfig } from "./types-CKdSg7n8.mjs";
1
+ import { B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, E as DocsSearchChunkingConfig, N as DocsSearchSourcePage, O as DocsSearchDocument, T as DocsSearchAdapterFactory, at as TypesenseDocsSearchConfig, d as CustomDocsSearchConfig, j as DocsSearchResult, r as AlgoliaDocsSearchConfig } from "./types-nlEL5lLq.mjs";
2
2
 
3
3
  //#region src/search.d.ts
4
4
  declare function buildDocsSearchDocuments(pages: DocsSearchSourcePage[], chunking?: DocsSearchChunkingConfig): DocsSearchDocument[];
package/dist/server.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { A as DocsSearchQuery, B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, N as DocsSearchSourcePage, O as DocsSearchDocument, T as DocsSearchAdapterFactory, a as ApiReferenceRenderer, j as DocsSearchResult, m as DocsConfig, w as DocsSearchAdapterContext } from "./types-CKdSg7n8.mjs";
1
+ import { A as DocsSearchQuery, B as McpDocsSearchConfig, C as DocsSearchAdapter, D as DocsSearchConfig, N as DocsSearchSourcePage, O as DocsSearchDocument, T as DocsSearchAdapterFactory, a as ApiReferenceRenderer, j as DocsSearchResult, m as DocsConfig, w as DocsSearchAdapterContext } from "./types-nlEL5lLq.mjs";
2
2
  import { DocsMcpHttpHandlers, DocsMcpNavigationNode, DocsMcpNavigationTree, DocsMcpPage, DocsMcpResolvedConfig, DocsMcpSource, createDocsMcpHttpHandler, createDocsMcpServer, createFilesystemDocsMcpSource, normalizeDocsMcpRoute, resolveDocsMcpConfig, runDocsMcpStdio } from "./mcp.mjs";
3
- import { a as createSimpleSearchAdapter, c as resolveSearchRequestConfig, i as createMcpSearchAdapter, n as createAlgoliaSearchAdapter, o as createTypesenseSearchAdapter, r as createCustomSearchAdapter, s as performDocsSearch, t as buildDocsSearchDocuments } from "./search-C15yRNVE.mjs";
3
+ import { a as createSimpleSearchAdapter, c as resolveSearchRequestConfig, i as createMcpSearchAdapter, n as createAlgoliaSearchAdapter, o as createTypesenseSearchAdapter, r as createCustomSearchAdapter, s as performDocsSearch, t as buildDocsSearchDocuments } from "./search-cPHa_3jJ.mjs";
4
4
 
5
5
  //#region src/api-reference.d.ts
6
6
  type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
package/dist/server.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as createSimpleSearchAdapter, c as resolveSearchRequestConfig, i as createMcpSearchAdapter, n as createAlgoliaSearchAdapter, o as createTypesenseSearchAdapter, r as createCustomSearchAdapter, s as performDocsSearch, t as buildDocsSearchDocuments } from "./search-8oEskRtz.mjs";
2
- import { a as buildApiReferencePageTitle, c as resolveApiReferenceRenderer, i as buildApiReferenceOpenApiDocumentAsync, n as buildApiReferenceHtmlDocumentAsync, o as buildApiReferenceScalarCss, r as buildApiReferenceOpenApiDocument, s as resolveApiReferenceConfig, t as buildApiReferenceHtmlDocument } from "./api-reference-GDAEzQn1.mjs";
2
+ import { a as buildApiReferencePageTitle, c as resolveApiReferenceRenderer, i as buildApiReferenceOpenApiDocumentAsync, n as buildApiReferenceHtmlDocumentAsync, o as buildApiReferenceScalarCss, r as buildApiReferenceOpenApiDocument, s as resolveApiReferenceConfig, t as buildApiReferenceHtmlDocument } from "./api-reference-y7cqtq4w.mjs";
3
3
  import { createDocsMcpHttpHandler, createDocsMcpServer, createFilesystemDocsMcpSource, normalizeDocsMcpRoute, resolveDocsMcpConfig, runDocsMcpStdio } from "./mcp.mjs";
4
4
 
5
5
  export { buildApiReferenceHtmlDocument, buildApiReferenceHtmlDocumentAsync, buildApiReferenceOpenApiDocument, buildApiReferenceOpenApiDocumentAsync, buildApiReferencePageTitle, buildApiReferenceScalarCss, buildDocsSearchDocuments, createAlgoliaSearchAdapter, createCustomSearchAdapter, createDocsMcpHttpHandler, createDocsMcpServer, createFilesystemDocsMcpSource, createMcpSearchAdapter, createSimpleSearchAdapter, createTypesenseSearchAdapter, normalizeDocsMcpRoute, performDocsSearch, resolveApiReferenceConfig, resolveApiReferenceRenderer, resolveDocsMcpConfig, resolveSearchRequestConfig, runDocsMcpStdio };
@@ -1,4 +1,4 @@
1
- import { c as installCommand, i as detectPackageManagerFromLockfile, o as exec, s as fileExists, t as detectFramework } from "./utils-CpTFbAiS.mjs";
1
+ import { c as installCommand, i as detectPackageManagerFromLockfile, o as exec, s as fileExists, t as detectFramework } from "./utils-DSMXVnEu.mjs";
2
2
  import path from "node:path";
3
3
  import pc from "picocolors";
4
4
  import * as p from "@clack/prompts";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/docs",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "description": "Modern, flexible MDX-based docs framework — core types, config, and CLI",
5
5
  "keywords": [
6
6
  "docs",
File without changes