@farming-labs/docs 0.2.115 → 0.2.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/{agent-5jyEosML.mjs → agent-CEZCaYto.mjs} +52 -7
  2. package/dist/{agent-Dlswu0Mm.mjs → agent-DGNTD-N6.mjs} +3 -3
  3. package/dist/{agent-evals-CnQC3W2a.mjs → agent-evals-CmeTjDMC.mjs} +3 -3
  4. package/dist/{agent-export-yilcRMg6.mjs → agent-export-DIKwVWYN.mjs} +11 -10
  5. package/dist/agent-skills-bundle.d.mts +1 -1
  6. package/dist/{agent-skills-progressive-disclosure-DL-q4JZo.mjs → agent-skills-progressive-disclosure-BDSjGGFd.mjs} +1 -1
  7. package/dist/{agent-skills-server-CF6iVTBR.d.mts → agent-skills-server-BWRJjIQG.d.mts} +2 -2
  8. package/dist/{agent-skills-server-Ddbf15pY.mjs → agent-skills-server-Ddm90WL6.mjs} +1 -1
  9. package/dist/agent-skills-vite.d.mts +3 -3
  10. package/dist/agent-skills-vite.mjs +1 -1
  11. package/dist/{agents-D5waM-jZ.mjs → agents-l8RtkFim.mjs} +10 -9
  12. package/dist/{analytics-CwXxM9oL.d.mts → analytics-R9c-Md4b.d.mts} +1 -1
  13. package/dist/api-reference-0XdEWya8.mjs +3033 -0
  14. package/dist/{authoring-CsR0sewh.mjs → authoring-CvsCmYuq.mjs} +4 -4
  15. package/dist/{authoring-mcp-BpwboE3k.mjs → authoring-mcp-C35lj3cI.mjs} +2 -2
  16. package/dist/browser.d.mts +3 -3
  17. package/dist/cli/index.mjs +75 -34
  18. package/dist/client/react.d.mts +1 -1
  19. package/dist/{cloud-Bvof6mET.mjs → cloud-DtmNQJg2.mjs} +3 -3
  20. package/dist/{cloud-ask-ai-C9dvEBpW.d.mts → cloud-ask-ai-DLyrydIR.d.mts} +1 -1
  21. package/dist/{codeblocks-C911CdyJ.mjs → codeblocks-DEcqNXve.mjs} +2 -2
  22. package/dist/{content-change-hydration-CDuohygf.d.mts → content-change-hydration-BWJNUIot.d.mts} +33 -5
  23. package/dist/{dev-Bx0JNglW.mjs → dev-Df77AKR1.mjs} +9 -24
  24. package/dist/docs-cloud-server.d.mts +2 -2
  25. package/dist/docs-cloud-server.mjs +1 -1
  26. package/dist/{doctor-DbsNvdb8.mjs → doctor-zNmt9s6j.mjs} +266 -17
  27. package/dist/{downgrade-BXmQMkMy.mjs → downgrade-C6DSsOO9.mjs} +2 -2
  28. package/dist/{feedback-DWVUMrxy.mjs → feedback-CNsQP2jY.mjs} +1 -1
  29. package/dist/{golden-evaluations-D49vhw5u.mjs → golden-evaluations-B1QDOzYj.mjs} +6 -2
  30. package/dist/index.d.mts +8 -8
  31. package/dist/index.mjs +5 -5
  32. package/dist/{init-CCV2eRNB.mjs → init-BXKzipSS.mjs} +3 -3
  33. package/dist/{mcp-D-1u1RBW.mjs → mcp-Bj3ZoEtt.mjs} +33 -19
  34. package/dist/mcp.d.mts +12 -4
  35. package/dist/mcp.mjs +50 -8
  36. package/dist/openapi-anOaQn9o.mjs +20 -0
  37. package/dist/openapi-diff-DKW3xkQU.mjs +461 -0
  38. package/dist/{openapi-mcp-D8NVpTW1.mjs → openapi-mcp-UF4Cx885.mjs} +43 -91
  39. package/dist/openapi-operations-DFpaqOqA.mjs +324 -0
  40. package/dist/openapi-sdk-DqRHPsCb.mjs +415 -0
  41. package/dist/openapi-source-CiKbUK71.mjs +37 -0
  42. package/dist/{package-version-Ci99L0yo.mjs → package-version-CTkii52k.mjs} +1 -1
  43. package/dist/{prompt-references-CV1zw72C.mjs → prompt-references-Cficetgk.mjs} +2 -2
  44. package/dist/{retrieval-digest-CBhsp9LK.d.mts → retrieval-digest-PD4fFy7P.d.mts} +2 -2
  45. package/dist/{review-BdM27yh7.mjs → review-CcplK9Hj.mjs} +9 -9
  46. package/dist/{robots-Bne9ODlC.mjs → robots-BPYWIGqD.mjs} +1 -1
  47. package/dist/{robots-D6VufwPn.mjs → robots-D4T3zZr_.mjs} +4 -4
  48. package/dist/{search-BCxmGlxh.mjs → search-BNmlDQJ2.mjs} +27 -11
  49. package/dist/server.d.mts +132 -9
  50. package/dist/server.mjs +12 -10
  51. package/dist/{sidebar-Bne72SU-.d.mts → sidebar-CGbvEPTX.d.mts} +1 -1
  52. package/dist/{sitemap-ChOEBnrk.mjs → sitemap-Bc4LkIcm.mjs} +10 -9
  53. package/dist/sitemap-server-C4bpuoLi.mjs +235 -0
  54. package/dist/{skills-qswruIKO.mjs → skills-B6wl1bUL.mjs} +10 -9
  55. package/dist/{standards-discovery-CjyUCxdy.d.mts → standards-discovery-C1RLKwV9.d.mts} +1 -1
  56. package/dist/{templates-DdB4BBGq.mjs → templates-BiORflC3.mjs} +1 -2
  57. package/dist/{types-BkAk6B2d.d.mts → types-Dkl2DUfp.d.mts} +39 -9
  58. package/dist/{upgrade-B92U83xo.mjs → upgrade-8zStRidw.mjs} +2 -2
  59. package/package.json +2 -1
  60. package/dist/sitemap-server-D3mFJkjE.mjs +0 -1166
  61. /package/dist/{agent-feedback-loop-vaC-M1sx.mjs → agent-feedback-loop-CcZL_sb1.mjs} +0 -0
  62. /package/dist/{audience-exposure-CkNZNeVX.d.mts → audience-exposure-BAjHjaTT.d.mts} +0 -0
  63. /package/dist/{cloud-ask-ai-CK2qlNns.mjs → cloud-ask-ai-BDtBbbZZ.mjs} +0 -0
  64. /package/dist/{code-blocks-D90CCJFs.mjs → code-blocks-GYdvS3BH.mjs} +0 -0
  65. /package/dist/{config-DrZ3fXgf.mjs → config-BnUOoj07.mjs} +0 -0
  66. /package/dist/{errors-BDmra6mz.mjs → errors-BkS29NUA.mjs} +0 -0
  67. /package/dist/{feedback-evals-k0sFDdms.mjs → feedback-evals-CYuS2wTu.mjs} +0 -0
  68. /package/dist/{openapi-mcp-node-vO3IXVGI.mjs → openapi-mcp-node-C4jUpZG6.mjs} +0 -0
  69. /package/dist/{propose-DJOiHu9Y.mjs → propose-ZH_mGMY_.mjs} +0 -0
  70. /package/dist/{utils-Cc1SrVza.mjs → utils-c9arebCi.mjs} +0 -0
@@ -0,0 +1,3033 @@
1
+ import { B as detectDocsMarkdownAgentRequest, G as hasDocsMarkdownSignatureAgent, R as createDocsMarkdownResponse, Tt as toDocsMarkdownUrl, k as acceptsDocsMarkdown, ot as renderDocsMarkdownDocument } from "./agent-CEZCaYto.mjs";
2
+ import { n as buildNormalizedOpenApiModel } from "./openapi-operations-DFpaqOqA.mjs";
3
+ import { parse } from "yaml";
4
+ import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
5
+ import { readFile } from "node:fs/promises";
6
+ import { basename, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
8
+ import { getHtmlDocument } from "@scalar/core/libs/html-rendering";
9
+ import { paths, query } from "jsonpath-rfc9535";
10
+ import { createHash } from "node:crypto";
11
+
12
+ //#region src/openapi-contract.ts
13
+ const OPENAPI_HTTP_METHODS = [
14
+ "get",
15
+ "put",
16
+ "post",
17
+ "delete",
18
+ "options",
19
+ "head",
20
+ "patch",
21
+ "trace"
22
+ ];
23
+ var OpenApiContractValidationError = class extends Error {
24
+ diagnostics;
25
+ constructor(result) {
26
+ const first = result.diagnostics[0];
27
+ const remaining = result.diagnostics.length - 1;
28
+ super(first ? `OpenAPI contract validation failed at ${first.path}: ${first.message}${remaining > 0 ? ` (${remaining} more ${remaining === 1 ? "error" : "errors"})` : ""}` : "OpenAPI contract validation failed.");
29
+ this.name = "OpenApiContractValidationError";
30
+ this.diagnostics = result.diagnostics;
31
+ }
32
+ };
33
+ function validateOpenApiContract(document) {
34
+ const state = {
35
+ diagnostics: [],
36
+ operationIds: /* @__PURE__ */ new Map(),
37
+ operationCount: 0,
38
+ activePathItems: /* @__PURE__ */ new WeakSet()
39
+ };
40
+ const hasOpenApi = Object.hasOwn(document, "openapi");
41
+ const hasSwagger = Object.hasOwn(document, "swagger");
42
+ let dialect;
43
+ let version;
44
+ if (hasOpenApi && hasSwagger) addDiagnostic(state, "conflicting-version-fields", "/", "Use either the `openapi` field or the `swagger` field, not both.");
45
+ else if (hasOpenApi) {
46
+ dialect = "openapi";
47
+ version = validateOpenApiVersion(document.openapi, state);
48
+ } else if (hasSwagger) {
49
+ dialect = "swagger";
50
+ version = validateSwaggerVersion(document.swagger, state);
51
+ } else addDiagnostic(state, "missing-version", "/", "The document must declare an `openapi` or `swagger` specification version.");
52
+ inspectPathItemMap(document.paths, "/paths", "paths", state);
53
+ if (document.webhooks !== void 0) inspectPathItemMap(document.webhooks, "/webhooks", "webhooks", state);
54
+ return {
55
+ valid: state.diagnostics.length === 0,
56
+ dialect,
57
+ version,
58
+ operationCount: state.operationCount,
59
+ diagnostics: state.diagnostics
60
+ };
61
+ }
62
+ function assertValidOpenApiContract(document) {
63
+ const result = validateOpenApiContract(document);
64
+ if (!result.valid) throw new OpenApiContractValidationError(result);
65
+ }
66
+ function validateOpenApiVersion(value, state) {
67
+ if (typeof value !== "string" || !/^\d+\.\d+\.\d+$/.test(value)) {
68
+ addDiagnostic(state, "invalid-version", "/openapi", "`openapi` must be a stable major.minor.patch version string.");
69
+ return;
70
+ }
71
+ if (!/^3\.(?:0|1|2)\.\d+$/.test(value)) addDiagnostic(state, "unsupported-version", "/openapi", `OpenAPI ${value} is not supported. Supported families are 3.0.x, 3.1.x, and 3.2.x.`);
72
+ return value;
73
+ }
74
+ function validateSwaggerVersion(value, state) {
75
+ if (typeof value !== "string") {
76
+ addDiagnostic(state, "invalid-version", "/swagger", "`swagger` must be the string `2.0`.");
77
+ return;
78
+ }
79
+ if (value !== "2.0") addDiagnostic(state, "unsupported-version", "/swagger", `Swagger ${value} is not supported. The supported Swagger version is 2.0.`);
80
+ return value;
81
+ }
82
+ function inspectPathItemMap(value, pointer, kind, state) {
83
+ if (value === void 0) return;
84
+ if (!isRecord$1(value)) {
85
+ addDiagnostic(state, kind === "paths" ? "invalid-paths" : kind === "webhooks" ? "invalid-webhooks" : "invalid-callbacks", pointer, `\`${kind}\` must be an object.`);
86
+ return;
87
+ }
88
+ for (const [key, pathItem] of Object.entries(value)) {
89
+ if (key.startsWith("x-")) continue;
90
+ const pathPointer = `${pointer}/${escapeJsonPointer$1(key)}`;
91
+ if (!isRecord$1(pathItem)) {
92
+ addDiagnostic(state, "invalid-path-item", pathPointer, "Path items must be objects or Reference Objects.");
93
+ continue;
94
+ }
95
+ inspectPathItem(pathItem, pathPointer, state);
96
+ }
97
+ }
98
+ function inspectPathItem(pathItem, pointer, state) {
99
+ if (state.activePathItems.has(pathItem)) return;
100
+ state.activePathItems.add(pathItem);
101
+ try {
102
+ for (const method of OPENAPI_HTTP_METHODS) {
103
+ const operation = pathItem[method];
104
+ if (operation === void 0) continue;
105
+ const operationPointer = `${pointer}/${method}`;
106
+ if (!isRecord$1(operation)) {
107
+ addDiagnostic(state, "invalid-operation", operationPointer, `The ${method.toUpperCase()} operation must be an object.`);
108
+ continue;
109
+ }
110
+ state.operationCount += 1;
111
+ inspectOperationId(operation.operationId, `${operationPointer}/operationId`, state);
112
+ inspectCallbacks(operation.callbacks, `${operationPointer}/callbacks`, state);
113
+ }
114
+ } finally {
115
+ state.activePathItems.delete(pathItem);
116
+ }
117
+ }
118
+ function inspectOperationId(value, pointer, state) {
119
+ if (value === void 0) return;
120
+ if (typeof value !== "string" || value.trim().length === 0) {
121
+ addDiagnostic(state, "invalid-operation-id", pointer, "`operationId` must be a non-empty string when provided.");
122
+ return;
123
+ }
124
+ const firstPath = state.operationIds.get(value);
125
+ if (firstPath) {
126
+ addDiagnostic(state, "duplicate-operation-id", pointer, `The operationId \`${value}\` is already used at ${firstPath}.`, firstPath);
127
+ return;
128
+ }
129
+ state.operationIds.set(value, pointer);
130
+ }
131
+ function inspectCallbacks(value, pointer, state) {
132
+ if (value === void 0) return;
133
+ if (!isRecord$1(value)) {
134
+ addDiagnostic(state, "invalid-callbacks", pointer, "`callbacks` must be an object.");
135
+ return;
136
+ }
137
+ for (const [callbackName, callback] of Object.entries(value)) {
138
+ if (callbackName.startsWith("x-")) continue;
139
+ const callbackPointer = `${pointer}/${escapeJsonPointer$1(callbackName)}`;
140
+ if (!isRecord$1(callback)) {
141
+ addDiagnostic(state, "invalid-callbacks", callbackPointer, "Callback entries must be Callback Objects or Reference Objects.");
142
+ continue;
143
+ }
144
+ if (typeof callback.$ref === "string") continue;
145
+ inspectPathItemMap(callback, callbackPointer, "callbacks", state);
146
+ }
147
+ }
148
+ function addDiagnostic(state, code, path, message, relatedPath) {
149
+ state.diagnostics.push({
150
+ severity: "error",
151
+ code,
152
+ path,
153
+ message,
154
+ relatedPath
155
+ });
156
+ }
157
+ function escapeJsonPointer$1(value) {
158
+ return value.replaceAll("~", "~0").replaceAll("/", "~1");
159
+ }
160
+ function isRecord$1(value) {
161
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
162
+ }
163
+
164
+ //#endregion
165
+ //#region src/openapi-overlays.ts
166
+ var OpenApiOverlayError = class extends Error {
167
+ constructor(message) {
168
+ super(message);
169
+ this.name = "OpenApiOverlayError";
170
+ }
171
+ };
172
+ function isJsonObject(value) {
173
+ return value !== null && typeof value === "object" && !Array.isArray(value);
174
+ }
175
+ function isJsonPrimitive(value) {
176
+ return value === null || typeof value !== "object";
177
+ }
178
+ function assertAllowedFields(value, allowed, location) {
179
+ const allowedSet = new Set(allowed);
180
+ const unexpected = Object.keys(value).find((key) => !allowedSet.has(key) && !key.startsWith("x-"));
181
+ if (unexpected) throw new OpenApiOverlayError(`${location} contains an unsupported \`${unexpected}\` field.`);
182
+ }
183
+ function requireNonEmptyString(value, location) {
184
+ if (typeof value !== "string" || !value.trim()) throw new OpenApiOverlayError(`${location} must be a non-empty string.`);
185
+ return value.trim();
186
+ }
187
+ function assertValidOpenApiOverlayDocument(value, source = "OpenAPI Overlay document") {
188
+ if (!isJsonObject(value)) throw new OpenApiOverlayError(`${source} must contain an object.`);
189
+ const overlayVersion = requireNonEmptyString(value.overlay, `${source} \`overlay\``);
190
+ const match = /^1\.(0|1)\.\d+$/.exec(overlayVersion);
191
+ if (!match) throw new OpenApiOverlayError(`${source} uses unsupported Overlay version \`${overlayVersion}\`. Supported versions are 1.0.x and 1.1.x.`);
192
+ const featureVersion = match[1] === "0" ? "1.0" : "1.1";
193
+ assertAllowedFields(value, [
194
+ "overlay",
195
+ "info",
196
+ "extends",
197
+ "actions"
198
+ ], source);
199
+ if (!isJsonObject(value.info)) throw new OpenApiOverlayError(`${source} \`info\` must be an object.`);
200
+ assertAllowedFields(value.info, featureVersion === "1.1" ? [
201
+ "title",
202
+ "version",
203
+ "description"
204
+ ] : ["title", "version"], `${source} \`info\``);
205
+ requireNonEmptyString(value.info.title, `${source} \`info.title\``);
206
+ requireNonEmptyString(value.info.version, `${source} \`info.version\``);
207
+ if (value.extends !== void 0 && typeof value.extends !== "string") throw new OpenApiOverlayError(`${source} \`extends\` must be a URI-reference string.`);
208
+ if (!Array.isArray(value.actions) || value.actions.length === 0) throw new OpenApiOverlayError(`${source} \`actions\` must contain at least one action.`);
209
+ const serializedActions = /* @__PURE__ */ new Set();
210
+ value.actions.forEach((action, index) => {
211
+ const location = `${source} action ${index + 1}`;
212
+ if (!isJsonObject(action)) throw new OpenApiOverlayError(`${location} must be an object.`);
213
+ assertAllowedFields(action, featureVersion === "1.1" ? [
214
+ "target",
215
+ "description",
216
+ "update",
217
+ "copy",
218
+ "remove"
219
+ ] : [
220
+ "target",
221
+ "description",
222
+ "update",
223
+ "remove"
224
+ ], location);
225
+ if (!requireNonEmptyString(action.target, `${location} \`target\``).startsWith("$")) throw new OpenApiOverlayError(`${location} \`target\` must start with \`$\`.`);
226
+ if (action.description !== void 0 && typeof action.description !== "string") throw new OpenApiOverlayError(`${location} \`description\` must be a string.`);
227
+ if (action.remove !== void 0 && typeof action.remove !== "boolean") throw new OpenApiOverlayError(`${location} \`remove\` must be a boolean.`);
228
+ if (action.copy !== void 0) {
229
+ if (!requireNonEmptyString(action.copy, `${location} \`copy\``).startsWith("$")) throw new OpenApiOverlayError(`${location} \`copy\` must start with \`$\`.`);
230
+ }
231
+ const serialized = JSON.stringify(action);
232
+ if (serializedActions.has(serialized)) throw new OpenApiOverlayError(`${location} duplicates an earlier action.`);
233
+ serializedActions.add(serialized);
234
+ });
235
+ }
236
+ function validateOverlay(value, source) {
237
+ assertValidOpenApiOverlayDocument(value, source);
238
+ return {
239
+ version: String(value.overlay).startsWith("1.0.") ? "1.0" : "1.1",
240
+ actions: value.actions
241
+ };
242
+ }
243
+ function decodeNormalizedPathKey(value) {
244
+ return value.replace(/\\(?:['\\bfnrt]|u[\da-fA-F]{4})/g, (match) => {
245
+ const escape = match.slice(1);
246
+ if (escape.startsWith("u")) return String.fromCharCode(Number.parseInt(escape.slice(1), 16));
247
+ return {
248
+ "'": "'",
249
+ "\\": "\\",
250
+ b: "\b",
251
+ f: "\f",
252
+ n: "\n",
253
+ r: "\r",
254
+ t: " "
255
+ }[escape];
256
+ });
257
+ }
258
+ function parseNormalizedJsonPath(path) {
259
+ if (path === "$") return [];
260
+ const segments = [];
261
+ const segmentPattern = /\[(?:(\d+)|'((?:\\.|[^'])*)')\]/gy;
262
+ segmentPattern.lastIndex = 1;
263
+ while (segmentPattern.lastIndex < path.length) {
264
+ const match = segmentPattern.exec(path);
265
+ if (!match) throw new OpenApiOverlayError(`JSONPath returned an unsupported normalized path: ${path}`);
266
+ segments.push(match[1] === void 0 ? decodeNormalizedPathKey(match[2] ?? "") : Number(match[1]));
267
+ }
268
+ return segments;
269
+ }
270
+ function getAtPath(root, path) {
271
+ let value = root;
272
+ for (const segment of path) {
273
+ if (Array.isArray(value) && typeof segment === "number") {
274
+ value = value[segment];
275
+ continue;
276
+ }
277
+ if (isJsonObject(value) && typeof segment === "string") {
278
+ value = value[segment];
279
+ continue;
280
+ }
281
+ throw new OpenApiOverlayError("An Overlay action selected a node that no longer exists.");
282
+ }
283
+ return value;
284
+ }
285
+ function setAtPath(root, path, value) {
286
+ if (path.length === 0) return value;
287
+ const parent = getAtPath(root, path.slice(0, -1));
288
+ const key = path[path.length - 1];
289
+ if (Array.isArray(parent) && typeof key === "number") parent[key] = value;
290
+ else if (isJsonObject(parent) && typeof key === "string") parent[key] = value;
291
+ else throw new OpenApiOverlayError("An Overlay action selected an invalid parent node.");
292
+ return root;
293
+ }
294
+ function removeAtPath(root, path) {
295
+ if (path.length === 0) throw new OpenApiOverlayError("An Overlay action cannot remove the document root.");
296
+ const parent = getAtPath(root, path.slice(0, -1));
297
+ const key = path[path.length - 1];
298
+ if (Array.isArray(parent) && typeof key === "number") parent.splice(key, 1);
299
+ else if (isJsonObject(parent) && typeof key === "string") delete parent[key];
300
+ else throw new OpenApiOverlayError("An Overlay remove action selected an invalid parent node.");
301
+ }
302
+ function mergeObjectValues(target, update, location) {
303
+ const result = structuredClone(target);
304
+ for (const [key, updateValue] of Object.entries(update)) {
305
+ if (!(key in result)) {
306
+ result[key] = structuredClone(updateValue);
307
+ continue;
308
+ }
309
+ const targetValue = result[key];
310
+ if (isJsonObject(targetValue) && isJsonObject(updateValue)) result[key] = mergeObjectValues(targetValue, updateValue, `${location}.${key}`);
311
+ else if (Array.isArray(targetValue) && Array.isArray(updateValue)) result[key] = [...targetValue, ...structuredClone(updateValue)];
312
+ else if (isJsonPrimitive(targetValue) && isJsonPrimitive(updateValue)) result[key] = updateValue;
313
+ else throw new OpenApiOverlayError(`${location} cannot merge incompatible values for \`${key}\`.`);
314
+ }
315
+ return result;
316
+ }
317
+ function applyUpdate(target, update, version, location) {
318
+ if (isJsonObject(target)) {
319
+ if (!isJsonObject(update)) throw new OpenApiOverlayError(`${location} must use an object update for an object target.`);
320
+ return mergeObjectValues(target, update, location);
321
+ }
322
+ if (Array.isArray(target)) return Array.isArray(update) ? [...target, ...structuredClone(update)] : [...target, structuredClone(update)];
323
+ if (version === "1.0") throw new OpenApiOverlayError(`${location} selects a primitive, which Overlay 1.0 cannot update.`);
324
+ if (!isJsonPrimitive(update)) throw new OpenApiOverlayError(`${location} must use a primitive update for a primitive target.`);
325
+ return update;
326
+ }
327
+ function queryPaths(document, expression, location) {
328
+ try {
329
+ return paths(document, expression);
330
+ } catch (error) {
331
+ throw new OpenApiOverlayError(`${location} contains invalid RFC 9535 JSONPath: ${error instanceof Error ? error.message : String(error)}`);
332
+ }
333
+ }
334
+ function queryCopyValue(document, expression, location) {
335
+ let values;
336
+ try {
337
+ values = query(document, expression);
338
+ } catch (error) {
339
+ throw new OpenApiOverlayError(`${location} contains invalid RFC 9535 JSONPath: ${error instanceof Error ? error.message : String(error)}`);
340
+ }
341
+ if (values.length !== 1) throw new OpenApiOverlayError(`${location} must select exactly one node, but selected ${values.length}.`);
342
+ return structuredClone(values[0]);
343
+ }
344
+ function compareRemovalPaths(a, b) {
345
+ if (a.length !== b.length) return b.length - a.length;
346
+ const aLast = a[a.length - 1];
347
+ const bLast = b[b.length - 1];
348
+ return typeof aLast === "number" && typeof bLast === "number" ? bLast - aLast : 0;
349
+ }
350
+ function getJsonValueKind(value) {
351
+ if (Array.isArray(value)) return "array";
352
+ return isJsonObject(value) ? "object" : "primitive";
353
+ }
354
+ function applyOpenApiOverlayDocuments(document, overlays) {
355
+ let result = structuredClone(document);
356
+ for (const overlaySource of overlays) {
357
+ const overlay = validateOverlay(overlaySource.document, overlaySource.source);
358
+ for (const [actionIndex, action] of overlay.actions.entries()) {
359
+ const location = `${overlaySource.source} action ${actionIndex + 1}`;
360
+ const normalizedPaths = queryPaths(result, action.target, `${location} \`target\``);
361
+ if (normalizedPaths.length === 0) throw new OpenApiOverlayError(`${location} target \`${action.target}\` did not match any nodes.`);
362
+ const selectedPaths = normalizedPaths.map(parseNormalizedJsonPath);
363
+ if (action.remove === true) {
364
+ for (const path of selectedPaths.sort(compareRemovalPaths)) removeAtPath(result, path);
365
+ continue;
366
+ }
367
+ const hasUpdate = Object.prototype.hasOwnProperty.call(action, "update");
368
+ const hasCopy = typeof action.copy === "string";
369
+ if (!hasUpdate && !hasCopy) continue;
370
+ if (new Set(selectedPaths.map((path) => getJsonValueKind(getAtPath(result, path)))).size > 1) throw new OpenApiOverlayError(`${location} must select only objects, only arrays, or only primitives for an update or copy action.`);
371
+ const updateValue = hasUpdate ? action.update : queryCopyValue(result, action.copy, `${location} \`copy\``);
372
+ for (const path of selectedPaths) {
373
+ const updated = applyUpdate(getAtPath(result, path), updateValue, overlay.version, location);
374
+ result = setAtPath(result, path, updated);
375
+ }
376
+ }
377
+ }
378
+ if (!isJsonObject(result)) throw new OpenApiOverlayError("OpenAPI Overlay actions must leave an object document root.");
379
+ return result;
380
+ }
381
+
382
+ //#endregion
383
+ //#region src/native-api-reference.ts
384
+ const METHOD_TONES = {
385
+ GET: "success",
386
+ POST: "info",
387
+ PUT: "warning",
388
+ PATCH: "warning",
389
+ DELETE: "danger",
390
+ OPTIONS: "neutral",
391
+ HEAD: "neutral",
392
+ TRACE: "neutral"
393
+ };
394
+ function renderNativeApiReferenceHtml(options) {
395
+ const { model } = options;
396
+ const tokens = resolveTokens(options.theme);
397
+ const description = options.description?.trim();
398
+ const versions = options.versions ?? [];
399
+ const groupedOperations = groupOperations(model.operations);
400
+ const operationMarkup = model.operations.length > 0 ? model.operations.map((operation) => renderOperation(operation, options)).join("\n") : renderEmptyContract();
401
+ return `<!doctype html>
402
+ <html lang="en">
403
+ <head>
404
+ <meta charset="utf-8" />
405
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
406
+ <meta name="generator" content="@farming-labs/docs" />
407
+ <meta name="color-scheme" content="light dark" />
408
+ <title>${escapeHtml(options.pageTitle)}</title>
409
+ <style>${buildStyles(tokens)}</style>
410
+ </head>
411
+ <body>
412
+ <a class="fl-skip-link" href="#fl-api-content">Skip to API operations</a>
413
+ <div class="fl-api-shell">
414
+ <header class="fl-api-topbar">
415
+ <a class="fl-api-brand" href="${escapeAttribute(options.basePath)}" aria-label="API reference home">
416
+ <span class="fl-api-brand-mark" aria-hidden="true">FL</span>
417
+ <span>API Reference</span>
418
+ </a>
419
+ <div class="fl-api-topbar-actions">
420
+ ${renderVersionSelect(versions, options.activeVersion)}
421
+ <a class="fl-api-action" href="${escapeAttribute(options.openApiUrl)}" download>OpenAPI</a>
422
+ <button class="fl-api-icon-button" type="button" data-theme-toggle aria-label="Switch color theme" title="Switch color theme">
423
+ ${themeIcon()}
424
+ </button>
425
+ </div>
426
+ </header>
427
+ <div class="fl-api-layout">
428
+ <aside class="fl-api-sidebar" aria-label="API operations">
429
+ <div class="fl-api-sidebar-heading">
430
+ <span class="fl-api-eyebrow">${escapeHtml(model.specificationVersion)}</span>
431
+ <span>${model.operationCount} ${model.operationCount === 1 ? "operation" : "operations"}</span>
432
+ </div>
433
+ <label class="fl-api-search-label" for="fl-api-search">Search operations</label>
434
+ <div class="fl-api-search-wrap">
435
+ ${searchIcon()}
436
+ <input id="fl-api-search" class="fl-api-search" type="search" placeholder="Method, path, tag…" autocomplete="off" />
437
+ <kbd>/</kbd>
438
+ </div>
439
+ <p class="fl-api-search-status" data-search-status aria-live="polite"></p>
440
+ <nav class="fl-api-operation-nav">
441
+ ${renderNavigation(groupedOperations)}
442
+ </nav>
443
+ </aside>
444
+ <main id="fl-api-content" class="fl-api-content">
445
+ <section class="fl-api-hero" aria-labelledby="fl-api-title">
446
+ <div class="fl-api-hero-copy">
447
+ <p class="fl-api-eyebrow">${escapeHtml(model.version)} · ${escapeHtml(model.specificationVersion)}</p>
448
+ <h1 id="fl-api-title">${escapeHtml(options.title ?? model.title)}</h1>
449
+ ${description ? `<div class="fl-api-description">${renderText(description)}</div>` : ""}
450
+ </div>
451
+ <dl class="fl-api-summary">
452
+ <div><dt>Operations</dt><dd>${model.operationCount}</dd></div>
453
+ <div><dt>Tags</dt><dd>${model.tags.length}</dd></div>
454
+ <div><dt>Servers</dt><dd>${model.servers.length}</dd></div>
455
+ </dl>
456
+ ${renderServers(model.servers)}
457
+ </section>
458
+ <section class="fl-api-operations" aria-label="Operations">
459
+ ${operationMarkup}
460
+ <div class="fl-api-no-results" data-no-results hidden>
461
+ <strong>No operations match this search.</strong>
462
+ <span>Try a method, route segment, operation ID, or tag.</span>
463
+ </div>
464
+ </section>
465
+ </main>
466
+ </div>
467
+ </div>
468
+ <div class="fl-api-live-region" aria-live="polite" aria-atomic="true" data-copy-status></div>
469
+ <script>${buildClientScript()}<\/script>
470
+ </body>
471
+ </html>`;
472
+ }
473
+ function renderVersionSelect(versions, activeVersion) {
474
+ if (versions.length === 0) return "";
475
+ return `<label class="fl-api-version-control">
476
+ <span>Version</span>
477
+ <select data-version-select aria-label="API version">
478
+ ${versions.map((version) => `<option value="${escapeAttribute(version.href)}"${version.active || version.id === activeVersion ? " selected" : ""}>${escapeHtml(version.label)}</option>`).join("")}
479
+ </select>
480
+ </label>`;
481
+ }
482
+ function renderNavigation(groups) {
483
+ if (groups.length === 0) return "<p class=\"fl-api-sidebar-empty\">No operations in this contract.</p>";
484
+ return groups.map((group) => `<section class="fl-api-nav-group" data-nav-group>
485
+ <h2>${escapeHtml(group.tag)}</h2>
486
+ <ul>
487
+ ${group.operations.map((operation) => `<li data-nav-operation data-search="${escapeAttribute(operationSearchText(operation))}">
488
+ <a href="#operation-${escapeAttribute(operation.slug)}">
489
+ <span class="fl-api-method fl-api-method--${methodTone(operation.method)}">${escapeHtml(operation.method)}</span>
490
+ <span>${escapeHtml(operation.title)}</span>
491
+ </a>
492
+ </li>`).join("")}
493
+ </ul>
494
+ </section>`).join("\n");
495
+ }
496
+ function renderOperation(operation, options) {
497
+ const markdownUrl = `${options.basePath}/operations/${encodeURIComponent(operation.slug)}.md`;
498
+ const curl = buildCurlExample(operation, options.model.servers);
499
+ const consoleMarkup = renderApiConsole(operation, options.model.servers);
500
+ const searchText = operationSearchText(operation);
501
+ const description = operation.description?.trim();
502
+ return `<article id="operation-${escapeAttribute(operation.slug)}" class="fl-api-operation" data-operation data-search="${escapeAttribute(searchText)}">
503
+ <header class="fl-api-operation-header">
504
+ <div class="fl-api-operation-meta">
505
+ <span class="fl-api-method fl-api-method--${methodTone(operation.method)}">${escapeHtml(operation.method)}</span>
506
+ <code>${escapeHtml(operation.path)}</code>
507
+ ${operation.deprecated ? "<span class=\"fl-api-chip fl-api-chip--danger\">Deprecated</span>" : ""}
508
+ </div>
509
+ <h2>${escapeHtml(operation.title)}</h2>
510
+ ${operation.summary && operation.summary !== operation.title ? `<p class="fl-api-summary-copy">${escapeHtml(operation.summary)}</p>` : ""}
511
+ ${description ? `<div class="fl-api-description">${renderText(description)}</div>` : ""}
512
+ <div class="fl-api-operation-links">
513
+ <a href="${escapeAttribute(markdownUrl)}">Markdown</a>
514
+ <a href="#operation-${escapeAttribute(operation.slug)}" aria-label="Link to ${escapeAttribute(operation.title)}">Permalink</a>
515
+ <code>${escapeHtml(operation.operationId)}</code>
516
+ </div>
517
+ </header>
518
+ <div class="fl-api-operation-grid">
519
+ <div class="fl-api-operation-details">
520
+ ${renderParameters(operation.parameters)}
521
+ ${renderRequestBody(operation.requestBody?.content ?? [], operation.requestBody?.required === true, operation.requestBody?.description)}
522
+ ${renderResponses(operation.responses)}
523
+ ${renderSecurity(operation)}
524
+ </div>
525
+ <aside class="fl-api-code-panel" aria-label="Request example and interactive console" data-console-root>
526
+ <div class="fl-api-code-header">
527
+ <div class="fl-api-code-tabs" role="tablist" aria-label="Request tools">
528
+ <button id="example-tab-${escapeAttribute(operation.slug)}" type="button" role="tab" aria-selected="true" aria-controls="example-panel-${escapeAttribute(operation.slug)}" tabindex="0" data-console-tab="example">cURL</button>
529
+ <button id="console-tab-${escapeAttribute(operation.slug)}" type="button" role="tab" aria-selected="false" aria-controls="console-panel-${escapeAttribute(operation.slug)}" tabindex="-1" data-console-tab="console">Try it</button>
530
+ </div>
531
+ <button type="button" class="fl-api-copy" data-copy="${escapeAttribute(curl)}" data-example-copy aria-label="Copy cURL request">Copy</button>
532
+ </div>
533
+ <div id="example-panel-${escapeAttribute(operation.slug)}" role="tabpanel" aria-labelledby="example-tab-${escapeAttribute(operation.slug)}" data-console-panel="example">
534
+ <pre><code>${escapeHtml(curl)}</code></pre>
535
+ </div>
536
+ <div id="console-panel-${escapeAttribute(operation.slug)}" role="tabpanel" aria-labelledby="console-tab-${escapeAttribute(operation.slug)}" data-console-panel="console" hidden>
537
+ ${consoleMarkup}
538
+ </div>
539
+ </aside>
540
+ </div>
541
+ </article>`;
542
+ }
543
+ function renderApiConsole(operation, modelServers) {
544
+ const server = operation.servers[0] ?? modelServers[0] ?? "";
545
+ const requestBody = operation.requestBody;
546
+ const requestMedia = requestBody?.content[0];
547
+ const bodyExample = requestMedia ? mediaTypeExample(requestMedia) : void 0;
548
+ const mutating = ![
549
+ "GET",
550
+ "HEAD",
551
+ "OPTIONS"
552
+ ].includes(operation.method);
553
+ return `<form class="fl-api-console" data-api-console data-method="${escapeAttribute(operation.method)}" data-path="${escapeAttribute(operation.path)}" novalidate>
554
+ <div class="fl-api-console-intro">
555
+ <div>
556
+ <span class="fl-api-console-kicker">Browser request</span>
557
+ <strong>${escapeHtml(operation.method)} ${escapeHtml(operation.path)}</strong>
558
+ </div>
559
+ <span class="fl-api-console-state" data-console-state data-state="idle">Ready</span>
560
+ </div>
561
+ <label class="fl-api-field">
562
+ <span>Server URL or path <em>required</em></span>
563
+ <input name="server" type="text" value="${escapeAttribute(server)}" placeholder="https://api.example.com" required spellcheck="false" autocomplete="url" />
564
+ </label>
565
+ ${renderConsoleParameters(operation.parameters)}
566
+ ${renderConsoleAuthentication(operation)}
567
+ ${requestBody ? renderConsoleRequestBody(requestBody.content, requestBody.required, bodyExample) : ""}
568
+ <label class="fl-api-field fl-api-field--inline">
569
+ <span>Browser credentials</span>
570
+ <select name="credentials" aria-label="Browser credentials mode">
571
+ <option value="same-origin">Same origin</option>
572
+ <option value="include">Include cookies</option>
573
+ <option value="omit">Omit cookies</option>
574
+ </select>
575
+ </label>
576
+ ${mutating ? `<label class="fl-api-console-confirm"><input type="checkbox" data-console-confirm /><span>I understand this request may change data.</span></label>` : ""}
577
+ <div class="fl-api-console-actions">
578
+ <button class="fl-api-console-send" type="submit" data-console-send${mutating ? " disabled" : ""}>Send request</button>
579
+ <button class="fl-api-console-cancel" type="button" data-console-cancel hidden>Cancel</button>
580
+ <span data-console-message role="status" aria-live="polite"></span>
581
+ </div>
582
+ <div class="fl-api-console-error" data-console-error role="alert" hidden></div>
583
+ <section class="fl-api-console-response" data-console-response aria-label="API response" tabindex="-1" hidden>
584
+ <div class="fl-api-console-response-header">
585
+ <div>
586
+ <span class="fl-api-console-status" data-response-status></span>
587
+ <span data-response-duration></span>
588
+ </div>
589
+ <button type="button" class="fl-api-copy" data-response-copy aria-label="Copy response body">Copy</button>
590
+ </div>
591
+ <div class="fl-api-console-response-meta">
592
+ <code data-response-url></code>
593
+ <span data-response-type></span>
594
+ </div>
595
+ <details class="fl-api-console-response-headers">
596
+ <summary>Response headers${chevronIcon()}</summary>
597
+ <pre><code data-response-headers></code></pre>
598
+ </details>
599
+ <pre class="fl-api-console-response-body"><code data-response-body></code></pre>
600
+ </section>
601
+ <p class="fl-api-console-note">Requests run in this browser. Cross-origin APIs must allow this docs origin through CORS. Credentials stay in memory and are never saved by Farming Labs.</p>
602
+ </form>`;
603
+ }
604
+ function renderConsoleParameters(parameters) {
605
+ const editable = parameters.filter((parameter) => parameter.in !== "cookie");
606
+ const cookies = parameters.filter((parameter) => parameter.in === "cookie");
607
+ if (editable.length === 0 && cookies.length === 0) return "";
608
+ return `<fieldset class="fl-api-console-group">
609
+ <legend>Parameters</legend>
610
+ ${editable.map(renderConsoleParameter).join("")}
611
+ ${cookies.length > 0 ? `<p class="fl-api-console-hint">Cookie parameters (${cookies.map((parameter) => `<code>${escapeHtml(parameter.name)}</code>`).join(", ")}) are browser-managed. Choose <strong>Include cookies</strong> below when the API uses them.</p>` : ""}
612
+ </fieldset>`;
613
+ }
614
+ function renderConsoleParameter(parameter) {
615
+ const type = schemaType(asRecord(parameter.schema));
616
+ const value = parameterExample(parameter);
617
+ const common = `name="parameter" data-param-name="${escapeAttribute(parameter.name)}" data-param-location="${escapeAttribute(parameter.in)}" data-param-array="${type === "array" ? "true" : "false"}"${parameter.required ? " required" : ""}`;
618
+ const control = type === "boolean" ? `<select ${common}><option value="">Select…</option><option value="true"${value === true ? " selected" : ""}>true</option><option value="false"${value === false ? " selected" : ""}>false</option></select>` : `<input ${common} type="${type === "number" || type === "integer" ? "number" : "text"}"${value !== void 0 ? ` value="${escapeAttribute(formatInputValue(value))}"` : ""} placeholder="${escapeAttribute(parameterPlaceholder(parameter, type))}" spellcheck="false" autocomplete="off" />`;
619
+ return `<label class="fl-api-field">
620
+ <span><code>${escapeHtml(parameter.name)}</code> <small>${escapeHtml(parameter.in)}</small>${parameter.required ? " <em>required</em>" : ""}</span>
621
+ ${control}
622
+ ${parameter.description ? `<small class="fl-api-field-help">${escapeHtml(parameter.description)}</small>` : ""}
623
+ </label>`;
624
+ }
625
+ function renderConsoleAuthentication(operation) {
626
+ const schemes = Object.entries(operation.securitySchemes);
627
+ if (operation.security.length === 0 || schemes.length === 0) return "";
628
+ return `<fieldset class="fl-api-console-group">
629
+ <legend>Authentication</legend>
630
+ ${schemes.map(([name, scheme]) => renderConsoleSecurityScheme(name, scheme)).join("")}
631
+ <p class="fl-api-console-hint">Authentication values are used only for this request and are not persisted.</p>
632
+ </fieldset>`;
633
+ }
634
+ function renderConsoleSecurityScheme(name, value) {
635
+ const scheme = asRecord(value) ?? {};
636
+ const type = typeof scheme.type === "string" ? scheme.type.toLocaleLowerCase() : "";
637
+ const httpScheme = typeof scheme.scheme === "string" ? scheme.scheme.toLocaleLowerCase() : "";
638
+ const location = typeof scheme.in === "string" ? scheme.in : "header";
639
+ const parameterName = typeof scheme.name === "string" ? scheme.name : name;
640
+ if (type === "http" && httpScheme === "basic") return `<div class="fl-api-auth-pair" data-auth-basic>
641
+ <label class="fl-api-field"><span>${escapeHtml(name)} username</span><input type="text" data-auth-username autocomplete="username" /></label>
642
+ <label class="fl-api-field"><span>${escapeHtml(name)} password</span><input type="password" data-auth-password autocomplete="current-password" /></label>
643
+ </div>`;
644
+ if (type === "apikey") {
645
+ if (location === "cookie") return `<p class="fl-api-console-hint"><strong>${escapeHtml(name)}</strong> uses the browser-managed <code>${escapeHtml(parameterName)}</code> cookie. Choose <strong>Include cookies</strong> below.</p>`;
646
+ return `<label class="fl-api-field">
647
+ <span>${escapeHtml(name)} <small>${escapeHtml(location)} · ${escapeHtml(parameterName)}</small></span>
648
+ <input type="password" data-auth-api-key data-auth-location="${escapeAttribute(location)}" data-auth-name="${escapeAttribute(parameterName)}" autocomplete="off" />
649
+ </label>`;
650
+ }
651
+ const tokenPrefix = type === "http" && httpScheme && httpScheme !== "bearer" ? httpScheme : "Bearer";
652
+ return `<label class="fl-api-field">
653
+ <span>${escapeHtml(name)} token</span>
654
+ <input type="password" data-auth-token data-auth-prefix="${escapeAttribute(tokenPrefix)}" autocomplete="off" placeholder="Paste access token" />
655
+ </label>`;
656
+ }
657
+ function renderConsoleRequestBody(content, required, bodyExample) {
658
+ const mediaTypes = content.length > 0 ? content : [{
659
+ mediaType: "application/json",
660
+ examples: []
661
+ }];
662
+ return `<fieldset class="fl-api-console-group">
663
+ <legend>Request body${required ? " · required" : ""}</legend>
664
+ <label class="fl-api-field fl-api-field--inline">
665
+ <span>Content type</span>
666
+ <select name="content-type">
667
+ ${mediaTypes.map((media) => `<option value="${escapeAttribute(media.mediaType)}">${escapeHtml(media.mediaType)}</option>`).join("")}
668
+ </select>
669
+ </label>
670
+ <label class="fl-api-field">
671
+ <span>Body${required ? " <em>required</em>" : ""}</span>
672
+ <textarea name="request-body" rows="8"${required ? " required" : ""} spellcheck="false" placeholder="Request body">${bodyExample === void 0 ? "" : escapeHtml(formatRequestBody(bodyExample, mediaTypes[0]?.mediaType))}</textarea>
673
+ </label>
674
+ </fieldset>`;
675
+ }
676
+ function renderParameters(parameters) {
677
+ if (parameters.length === 0) return "";
678
+ return `<section class="fl-api-section">
679
+ <h3>Parameters <span>${parameters.length}</span></h3>
680
+ <div class="fl-api-table-wrap">
681
+ <table>
682
+ <thead><tr><th scope="col">Name</th><th scope="col">Location</th><th scope="col">Type</th><th scope="col">Description</th></tr></thead>
683
+ <tbody>
684
+ ${parameters.map(renderParameter).join("")}
685
+ </tbody>
686
+ </table>
687
+ </div>
688
+ </section>`;
689
+ }
690
+ function renderParameter(parameter) {
691
+ return `<tr>
692
+ <td><code>${escapeHtml(parameter.name)}</code>${parameter.required ? "<span class=\"fl-api-required\">required</span>" : ""}</td>
693
+ <td><span class="fl-api-chip">${escapeHtml(parameter.in)}</span></td>
694
+ <td><code>${escapeHtml(schemaLabel(parameter.schema))}</code></td>
695
+ <td>${parameter.description ? renderText(parameter.description) : "<span class=\"fl-api-muted\">—</span>"}</td>
696
+ </tr>`;
697
+ }
698
+ function renderRequestBody(content, required, description) {
699
+ if (content.length === 0 && !description) return "";
700
+ return `<section class="fl-api-section">
701
+ <h3>Request body ${required ? "<span class=\"fl-api-required\">required</span>" : ""}</h3>
702
+ ${description ? `<div class="fl-api-description">${renderText(description)}</div>` : ""}
703
+ ${content.map((media) => renderMediaType(media, "Request schema")).join("")}
704
+ </section>`;
705
+ }
706
+ function renderResponses(responses) {
707
+ if (responses.length === 0) return `<section class="fl-api-section"><h3>Responses</h3><p class="fl-api-muted">No responses are documented.</p></section>`;
708
+ return `<section class="fl-api-section">
709
+ <h3>Responses <span>${responses.length}</span></h3>
710
+ <div class="fl-api-response-list">
711
+ ${responses.map(renderResponse).join("")}
712
+ </div>
713
+ </section>`;
714
+ }
715
+ function renderResponse(response) {
716
+ return `<details class="fl-api-response"${response.status.startsWith("2") ? " open" : ""}>
717
+ <summary>
718
+ <code>${escapeHtml(response.status)}</code>
719
+ <span>${escapeHtml(response.description ?? "Response")}</span>
720
+ ${chevronIcon()}
721
+ </summary>
722
+ <div class="fl-api-response-content">
723
+ ${response.content.length > 0 ? response.content.map((media) => renderMediaType(media, "Response schema")).join("") : "<p class=\"fl-api-muted\">No response body.</p>"}
724
+ </div>
725
+ </details>`;
726
+ }
727
+ function renderMediaType(media, label) {
728
+ const schema = media.schema === void 0 ? void 0 : safeJson(media.schema);
729
+ const example = media.examples.find((entry) => entry.value !== void 0);
730
+ return `<div class="fl-api-media">
731
+ <div class="fl-api-media-heading"><span class="fl-api-chip">${escapeHtml(media.mediaType)}</span></div>
732
+ ${schema ? renderCodeDisclosure(label, schema) : ""}
733
+ ${example?.value !== void 0 ? renderCodeDisclosure(example.summary ?? "Example", safeJson(example.value)) : ""}
734
+ </div>`;
735
+ }
736
+ function renderCodeDisclosure(label, value) {
737
+ return `<details class="fl-api-schema">
738
+ <summary>${escapeHtml(label)}${chevronIcon()}</summary>
739
+ <div class="fl-api-schema-code">
740
+ <button type="button" class="fl-api-copy" data-copy="${escapeAttribute(value)}" aria-label="Copy ${escapeAttribute(label)}">Copy</button>
741
+ <pre><code>${escapeHtml(value)}</code></pre>
742
+ </div>
743
+ </details>`;
744
+ }
745
+ function renderSecurity(operation) {
746
+ if (operation.security.length === 0) return "";
747
+ return `<section class="fl-api-section">
748
+ <h3>Authentication</h3>
749
+ <div class="fl-api-chip-row">${Array.from(new Set(operation.security.flatMap((requirement) => Object.keys(requirement)))).map((name) => `<span class="fl-api-chip">${lockIcon()}${escapeHtml(name)}</span>`).join("")}</div>
750
+ </section>`;
751
+ }
752
+ function renderServers(servers) {
753
+ if (servers.length === 0) return "";
754
+ return `<div class="fl-api-servers">
755
+ <span>Server</span>
756
+ <code>${escapeHtml(servers[0] ?? "")}</code>
757
+ <button type="button" class="fl-api-copy" data-copy="${escapeAttribute(servers[0] ?? "")}" aria-label="Copy server URL">Copy</button>
758
+ </div>`;
759
+ }
760
+ function renderEmptyContract() {
761
+ return `<div class="fl-api-empty">
762
+ <strong>No operations found.</strong>
763
+ <span>Add paths and operations to the OpenAPI contract, then refresh this reference.</span>
764
+ </div>`;
765
+ }
766
+ function groupOperations(operations) {
767
+ const groups = /* @__PURE__ */ new Map();
768
+ for (const operation of operations) {
769
+ const tag = operation.tags[0] ?? "General";
770
+ const group = groups.get(tag) ?? [];
771
+ group.push(operation);
772
+ groups.set(tag, group);
773
+ }
774
+ return Array.from(groups.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([tag, grouped]) => ({
775
+ tag,
776
+ operations: grouped.sort((left, right) => left.title.localeCompare(right.title))
777
+ }));
778
+ }
779
+ function buildCurlExample(operation, modelServers) {
780
+ const target = shellQuote(`${(operation.servers[0] ?? modelServers[0] ?? "https://api.example.com").replace(/\/$/, "")}${operation.path}`);
781
+ const parts = [`curl --request ${operation.method}`, ` --url ${target}`];
782
+ const contentType = operation.requestBody?.content[0]?.mediaType;
783
+ if (contentType) parts.push(` --header ${shellQuote(`Content-Type: ${contentType}`)}`);
784
+ const example = operation.requestBody?.content.flatMap((entry) => entry.examples).find((entry) => entry.value !== void 0)?.value;
785
+ if (operation.requestBody) parts.push(` --data ${shellQuote(example === void 0 ? "{}" : safeJson(example))}`);
786
+ return parts.join(" \\\n");
787
+ }
788
+ function mediaTypeExample(media) {
789
+ const example = media.examples.find((entry) => entry.value !== void 0)?.value;
790
+ return example === void 0 ? schemaExample(media.schema) : example;
791
+ }
792
+ function parameterExample(parameter) {
793
+ const example = parameter.examples.find((entry) => entry.value !== void 0)?.value;
794
+ if (example !== void 0) return example;
795
+ const schema = asRecord(parameter.schema);
796
+ if (!schema) return void 0;
797
+ if (Object.hasOwn(schema, "example")) return schema.example;
798
+ if (Object.hasOwn(schema, "default")) return schema.default;
799
+ if (Object.hasOwn(schema, "const")) return schema.const;
800
+ return Array.isArray(schema.enum) ? schema.enum[0] : void 0;
801
+ }
802
+ function schemaExample(schema, depth = 0) {
803
+ if (depth > 5) return void 0;
804
+ const record = asRecord(schema);
805
+ if (!record) return void 0;
806
+ if (Object.hasOwn(record, "example")) return record.example;
807
+ if (Object.hasOwn(record, "default")) return record.default;
808
+ if (Object.hasOwn(record, "const")) return record.const;
809
+ if (Array.isArray(record.enum) && record.enum.length > 0) return record.enum[0];
810
+ const composed = [
811
+ record.oneOf,
812
+ record.anyOf,
813
+ record.allOf
814
+ ].find((value) => Array.isArray(value) && value.length > 0);
815
+ if (Array.isArray(composed)) return schemaExample(composed[0], depth + 1);
816
+ const type = schemaType(record);
817
+ if (type === "array") {
818
+ const item = schemaExample(record.items, depth + 1);
819
+ return item === void 0 ? [] : [item];
820
+ }
821
+ if (type === "object" || asRecord(record.properties)) {
822
+ const properties = asRecord(record.properties) ?? {};
823
+ return Object.fromEntries(Object.entries(properties).map(([name, value]) => [name, schemaExample(value, depth + 1) ?? placeholderSchemaValue(value)]));
824
+ }
825
+ return placeholderSchemaValue(record);
826
+ }
827
+ function placeholderSchemaValue(schema) {
828
+ const type = schemaType(asRecord(schema));
829
+ if (type === "string") return "string";
830
+ if (type === "number" || type === "integer") return 0;
831
+ if (type === "boolean") return false;
832
+ if (type === "array") return [];
833
+ if (type === "object") return {};
834
+ }
835
+ function schemaType(schema) {
836
+ if (!schema) return "string";
837
+ if (typeof schema.type === "string") return schema.type;
838
+ if (Array.isArray(schema.type)) return schema.type.find((entry) => typeof entry === "string" && entry !== "null") ?? "string";
839
+ if (schema.items !== void 0) return "array";
840
+ if (schema.properties !== void 0 || schema.additionalProperties !== void 0) return "object";
841
+ return "string";
842
+ }
843
+ function parameterPlaceholder(parameter, type) {
844
+ if (type === "array") return "Comma-separated values";
845
+ if (parameter.in === "path") return parameter.name;
846
+ return `Optional ${type}`;
847
+ }
848
+ function formatInputValue(value) {
849
+ if (Array.isArray(value)) return value.map((entry) => String(entry)).join(", ");
850
+ if (value && typeof value === "object") return JSON.stringify(value);
851
+ return String(value);
852
+ }
853
+ function formatRequestBody(value, mediaType) {
854
+ if (typeof value === "string" && !isJsonMediaType(mediaType)) return value;
855
+ return safeJson(value);
856
+ }
857
+ function isJsonMediaType(mediaType) {
858
+ const normalized = mediaType?.toLocaleLowerCase() ?? "";
859
+ return normalized.includes("json") || normalized.includes("+json");
860
+ }
861
+ function asRecord(value) {
862
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
863
+ }
864
+ function operationSearchText(operation) {
865
+ return [
866
+ operation.method,
867
+ operation.path,
868
+ operation.title,
869
+ operation.summary,
870
+ operation.description,
871
+ operation.operationId,
872
+ ...operation.tags
873
+ ].filter(Boolean).join(" ").toLocaleLowerCase();
874
+ }
875
+ function schemaLabel(schema) {
876
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) return "any";
877
+ const record = schema;
878
+ if (typeof record.type === "string") {
879
+ if (record.type === "array") return `${schemaLabel(record.items)}[]`;
880
+ return record.type;
881
+ }
882
+ if (Array.isArray(record.type)) return record.type.join(" | ");
883
+ if (Array.isArray(record.enum)) return "enum";
884
+ return "object";
885
+ }
886
+ function safeJson(value) {
887
+ try {
888
+ return JSON.stringify(value, null, 2) ?? "null";
889
+ } catch {
890
+ return "[unserializable value]";
891
+ }
892
+ }
893
+ function shellQuote(value) {
894
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
895
+ }
896
+ function renderText(value) {
897
+ return value.split(/\n{2,}/).map((paragraph) => `<p>${escapeHtml(paragraph).replaceAll("\n", "<br />")}</p>`).join("");
898
+ }
899
+ function methodTone(method) {
900
+ return METHOD_TONES[method] ?? "neutral";
901
+ }
902
+ function escapeHtml(value) {
903
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
904
+ }
905
+ function escapeAttribute(value) {
906
+ return escapeHtml(value).replaceAll("`", "&#96;");
907
+ }
908
+ function resolveTokens(theme) {
909
+ const colors = theme?.ui?.colors;
910
+ const typography = theme?.ui?.typography?.font?.style;
911
+ const background = colors?.background ?? "#ffffff";
912
+ const foreground = colors?.foreground ?? (looksDark(background, theme?.name) ? "#f5f5f4" : "#18181b");
913
+ const primary = colors?.primary ?? "#4f46e5";
914
+ return {
915
+ primary,
916
+ primaryForeground: colors?.primaryForeground ?? (looksDark(primary) ? "#ffffff" : "#111113"),
917
+ background,
918
+ card: colors?.card ?? background,
919
+ foreground,
920
+ muted: colors?.muted ?? (looksDark(background, theme?.name) ? "#a1a1aa" : "#62626d"),
921
+ border: colors?.border ?? (looksDark(background, theme?.name) ? "#2d2d33" : "#dedee3"),
922
+ radius: theme?.ui?.radius ?? "8px",
923
+ sans: typography?.sans ?? "\"Geist\", \"Inter\", ui-sans-serif, system-ui, sans-serif",
924
+ mono: typography?.mono ?? "\"Geist Mono\", \"SFMono-Regular\", Menlo, monospace",
925
+ customPalette: Boolean(colors)
926
+ };
927
+ }
928
+ function looksDark(color, name) {
929
+ if (name?.toLocaleLowerCase().includes("dark")) return true;
930
+ const match = /^#([\da-f]{6})$/i.exec(color.trim());
931
+ if (!match?.[1]) return false;
932
+ const red = Number.parseInt(match[1].slice(0, 2), 16);
933
+ const green = Number.parseInt(match[1].slice(2, 4), 16);
934
+ const blue = Number.parseInt(match[1].slice(4, 6), 16);
935
+ return (red * 299 + green * 587 + blue * 114) / 1e3 < 128;
936
+ }
937
+ function buildStyles(tokens) {
938
+ const automaticDark = tokens.customPalette ? "" : `@media (prefers-color-scheme: dark) {
939
+ :root:not([data-theme]) { --fl-bg: #0b0b0d; --fl-card: #111114; --fl-fg: #f4f4f5; --fl-muted: #a1a1aa; --fl-border: #2b2b31; --fl-soft: #18181d; }
940
+ }`;
941
+ return `
942
+ :root { color-scheme: light; --fl-primary: ${tokens.primary}; --fl-primary-fg: ${tokens.primaryForeground}; --fl-bg: ${tokens.background}; --fl-card: ${tokens.card}; --fl-fg: ${tokens.foreground}; --fl-muted: ${tokens.muted}; --fl-border: ${tokens.border}; --fl-soft: color-mix(in srgb, var(--fl-card) 92%, var(--fl-fg) 8%); --fl-radius: ${tokens.radius}; --fl-sans: ${tokens.sans}; --fl-mono: ${tokens.mono}; }
943
+ :root[data-theme="light"] { color-scheme: light; }
944
+ :root[data-theme="dark"] { color-scheme: dark; --fl-bg: #0b0b0d; --fl-card: #111114; --fl-fg: #f4f4f5; --fl-muted: #a1a1aa; --fl-border: #2b2b31; --fl-soft: #18181d; }
945
+ ${automaticDark}
946
+ * { box-sizing: border-box; }
947
+ html { scroll-behavior: smooth; scroll-padding-top: 88px; }
948
+ body { margin: 0; background: var(--fl-bg); color: var(--fl-fg); font: 15px/1.6 var(--fl-sans); }
949
+ button, input, select { font: inherit; }
950
+ button, a, input, select { -webkit-tap-highlight-color: transparent; }
951
+ a { color: inherit; }
952
+ code, pre, kbd { font-family: var(--fl-mono); }
953
+ .fl-skip-link { position: fixed; z-index: 100; top: 8px; left: 8px; transform: translateY(-150%); background: var(--fl-fg); color: var(--fl-bg); padding: 8px 12px; }
954
+ .fl-skip-link:focus { transform: translateY(0); }
955
+ .fl-api-shell { min-height: 100vh; }
956
+ .fl-api-topbar { position: sticky; z-index: 20; top: 0; min-height: 58px; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 8px 20px; border-bottom: 1px solid var(--fl-border); background: color-mix(in srgb, var(--fl-bg) 94%, transparent); backdrop-filter: blur(12px); }
957
+ .fl-api-brand { display: inline-flex; align-items: center; gap: 10px; text-decoration: none; font-weight: 650; }
958
+ .fl-api-brand-mark { display: grid; place-items: center; width: 28px; height: 28px; border: 1px solid var(--fl-fg); font: 700 10px/1 var(--fl-mono); letter-spacing: -.04em; }
959
+ .fl-api-topbar-actions, .fl-api-version-control { display: flex; align-items: center; gap: 8px; }
960
+ .fl-api-version-control > span { color: var(--fl-muted); font: 11px/1 var(--fl-mono); text-transform: uppercase; letter-spacing: .08em; }
961
+ .fl-api-version-control select, .fl-api-action, .fl-api-icon-button { min-height: 34px; border: 1px solid var(--fl-border); border-radius: calc(var(--fl-radius) * .65); background: var(--fl-card); color: var(--fl-fg); }
962
+ .fl-api-version-control select { padding: 0 28px 0 10px; }
963
+ .fl-api-action { display: inline-flex; align-items: center; padding: 0 11px; text-decoration: none; font: 12px/1 var(--fl-mono); }
964
+ .fl-api-icon-button { width: 34px; display: grid; place-items: center; cursor: pointer; }
965
+ .fl-api-icon-button svg, .fl-api-search-wrap svg, .fl-api-chip svg { width: 15px; height: 15px; }
966
+ .fl-api-layout { display: grid; grid-template-columns: minmax(240px, 292px) minmax(0, 1fr); }
967
+ .fl-api-sidebar { position: sticky; top: 59px; height: calc(100vh - 59px); overflow: auto; padding: 20px 16px 32px; border-right: 1px solid var(--fl-border); }
968
+ .fl-api-sidebar-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 18px; color: var(--fl-muted); font-size: 12px; }
969
+ .fl-api-eyebrow { color: var(--fl-muted); font: 11px/1.3 var(--fl-mono); letter-spacing: .08em; text-transform: uppercase; }
970
+ .fl-api-search-label { display: block; margin-bottom: 7px; color: var(--fl-muted); font-size: 12px; }
971
+ .fl-api-search-wrap { display: grid; grid-template-columns: 18px 1fr auto; align-items: center; gap: 7px; height: 38px; padding: 0 10px; border: 1px solid var(--fl-border); border-radius: var(--fl-radius); background: var(--fl-card); }
972
+ .fl-api-search { width: 100%; border: 0; outline: 0; background: transparent; color: var(--fl-fg); }
973
+ .fl-api-search::placeholder { color: var(--fl-muted); }
974
+ .fl-api-search-wrap:focus-within { border-color: var(--fl-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--fl-primary) 18%, transparent); }
975
+ .fl-api-search-wrap kbd { padding: 2px 5px; border: 1px solid var(--fl-border); color: var(--fl-muted); font-size: 10px; }
976
+ .fl-api-search-status { min-height: 18px; margin: 7px 0 0; color: var(--fl-muted); font-size: 11px; }
977
+ .fl-api-nav-group { margin-top: 22px; }
978
+ .fl-api-nav-group[hidden] { display: none; }
979
+ .fl-api-nav-group h2 { margin: 0 8px 8px; color: var(--fl-muted); font: 600 11px/1.3 var(--fl-mono); letter-spacing: .08em; text-transform: uppercase; }
980
+ .fl-api-nav-group ul { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; }
981
+ .fl-api-nav-group a { display: grid; grid-template-columns: 46px minmax(0, 1fr); align-items: center; gap: 8px; min-height: 36px; padding: 6px 8px; border-left: 2px solid transparent; text-decoration: none; font-size: 13px; }
982
+ .fl-api-nav-group a:hover, .fl-api-nav-group a:focus-visible { border-left-color: var(--fl-primary); background: var(--fl-soft); outline: none; }
983
+ .fl-api-nav-group a span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
984
+ .fl-api-method { display: inline-flex; align-items: center; justify-content: center; min-width: 46px; height: 22px; padding: 0 6px; border: 1px solid currentColor; border-radius: calc(var(--fl-radius) * .5); font: 700 10px/1 var(--fl-mono); letter-spacing: .04em; }
985
+ .fl-api-method--success { color: #16803d; }.fl-api-method--info { color: #2563eb; }.fl-api-method--warning { color: #b45309; }.fl-api-method--danger { color: #dc2626; }.fl-api-method--neutral { color: var(--fl-muted); }
986
+ .fl-api-content { min-width: 0; }
987
+ .fl-api-hero { padding: clamp(32px, 6vw, 76px) clamp(22px, 6vw, 82px) 44px; border-bottom: 1px solid var(--fl-border); }
988
+ .fl-api-hero-copy { max-width: 780px; }
989
+ .fl-api-hero h1 { max-width: 18ch; margin: 10px 0 12px; font-size: clamp(34px, 5vw, 64px); line-height: 1.02; letter-spacing: -.045em; }
990
+ .fl-api-description { max-width: 76ch; color: var(--fl-muted); }
991
+ .fl-api-description p { margin: 0 0 8px; }
992
+ .fl-api-summary { display: flex; flex-wrap: wrap; gap: 1px; margin: 30px 0 0; }
993
+ .fl-api-summary div { min-width: 120px; padding: 12px 16px; border: 1px solid var(--fl-border); background: var(--fl-card); }
994
+ .fl-api-summary dt { color: var(--fl-muted); font: 10px/1.3 var(--fl-mono); text-transform: uppercase; letter-spacing: .08em; }
995
+ .fl-api-summary dd { margin: 4px 0 0; font: 650 20px/1.2 var(--fl-mono); }
996
+ .fl-api-servers { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; max-width: 780px; margin-top: 18px; border: 1px solid var(--fl-border); background: var(--fl-soft); }
997
+ .fl-api-servers > span { padding: 9px 12px; border-right: 1px solid var(--fl-border); color: var(--fl-muted); font: 10px/1 var(--fl-mono); text-transform: uppercase; }
998
+ .fl-api-servers code { overflow: auto; padding: 9px 12px; font-size: 12px; white-space: nowrap; }
999
+ .fl-api-copy { min-width: 54px; align-self: stretch; padding: 7px 10px; border: 0; border-left: 1px solid var(--fl-border); background: transparent; color: var(--fl-muted); cursor: pointer; font: 11px/1 var(--fl-mono); }
1000
+ .fl-api-copy:hover, .fl-api-copy:focus-visible { background: var(--fl-card); color: var(--fl-fg); outline: 2px solid var(--fl-primary); outline-offset: -2px; }
1001
+ .fl-api-operations { padding: 0 clamp(22px, 5vw, 72px) 80px; }
1002
+ .fl-api-operation { padding: clamp(36px, 6vw, 72px) 0; border-bottom: 1px solid var(--fl-border); scroll-margin-top: 76px; }
1003
+ .fl-api-operation[hidden] { display: none; }
1004
+ .fl-api-operation-header { max-width: 900px; }
1005
+ .fl-api-operation-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 9px; }
1006
+ .fl-api-operation-meta > code { overflow-wrap: anywhere; color: var(--fl-fg); font-size: 13px; }
1007
+ .fl-api-operation h2 { margin: 15px 0 7px; font-size: clamp(25px, 3vw, 38px); line-height: 1.15; letter-spacing: -.025em; }
1008
+ .fl-api-summary-copy { margin: 0 0 8px; font-weight: 520; }
1009
+ .fl-api-operation-links { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 18px; font: 11px/1.4 var(--fl-mono); }
1010
+ .fl-api-operation-links a { text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 3px; }
1011
+ .fl-api-operation-links code { color: var(--fl-muted); }
1012
+ .fl-api-operation-grid { display: grid; grid-template-columns: minmax(0, 1.12fr) minmax(300px, .88fr); gap: clamp(24px, 5vw, 60px); margin-top: 30px; align-items: start; }
1013
+ .fl-api-operation-details { min-width: 0; }
1014
+ .fl-api-section { padding: 24px 0; border-top: 1px solid var(--fl-border); }
1015
+ .fl-api-section h3 { display: flex; align-items: center; gap: 8px; margin: 0 0 14px; font-size: 15px; }
1016
+ .fl-api-section h3 > span { color: var(--fl-muted); font: 11px/1 var(--fl-mono); }
1017
+ .fl-api-table-wrap { overflow-x: auto; border: 1px solid var(--fl-border); }
1018
+ table { width: 100%; min-width: 620px; border-collapse: collapse; font-size: 13px; }
1019
+ th, td { padding: 11px 12px; border-bottom: 1px solid var(--fl-border); text-align: left; vertical-align: top; }
1020
+ th { background: var(--fl-soft); color: var(--fl-muted); font: 10px/1.3 var(--fl-mono); text-transform: uppercase; letter-spacing: .06em; }
1021
+ tbody tr:last-child td { border-bottom: 0; }
1022
+ td code { font-size: 12px; }
1023
+ .fl-api-required { display: inline-block; margin-left: 7px; color: #dc2626; font: 9px/1 var(--fl-mono); text-transform: uppercase; }
1024
+ .fl-api-chip-row { display: flex; flex-wrap: wrap; gap: 7px; }
1025
+ .fl-api-chip { display: inline-flex; align-items: center; gap: 5px; padding: 4px 7px; border: 1px solid var(--fl-border); border-radius: calc(var(--fl-radius) * .5); background: var(--fl-soft); color: var(--fl-muted); font: 10px/1 var(--fl-mono); }
1026
+ .fl-api-chip--danger { color: #dc2626; }
1027
+ .fl-api-code-panel { position: sticky; top: 84px; overflow: hidden; border: 1px solid var(--fl-border); border-radius: var(--fl-radius); background: #0c0c0f; color: #f4f4f5; }
1028
+ .fl-api-code-header { min-height: 38px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #29292f; color: #a1a1aa; font: 10px/1 var(--fl-mono); text-transform: uppercase; letter-spacing: .08em; }
1029
+ .fl-api-code-tabs { align-self: stretch; display: flex; align-items: stretch; }
1030
+ .fl-api-code-tabs button { position: relative; min-width: 70px; padding: 0 12px; border: 0; border-right: 1px solid #29292f; background: transparent; color: #a1a1aa; cursor: pointer; font: inherit; letter-spacing: inherit; text-transform: inherit; }
1031
+ .fl-api-code-tabs button[aria-selected="true"] { background: #151519; color: #f4f4f5; }
1032
+ .fl-api-code-tabs button[aria-selected="true"]::after { content: ""; position: absolute; right: 10px; bottom: -1px; left: 10px; height: 2px; background: var(--fl-primary); }
1033
+ .fl-api-code-tabs button:hover, .fl-api-code-tabs button:focus-visible { color: #f4f4f5; outline: 2px solid var(--fl-primary); outline-offset: -2px; }
1034
+ .fl-api-code-panel .fl-api-copy { color: #a1a1aa; border-left-color: #29292f; }
1035
+ .fl-api-code-panel pre, .fl-api-schema pre { margin: 0; overflow: auto; padding: 16px; font-size: 12px; line-height: 1.7; }
1036
+ .fl-api-console { display: grid; gap: 16px; max-height: min(76vh, 780px); overflow-y: auto; padding: 16px; background: #0c0c0f; color: #f4f4f5; }
1037
+ .fl-api-console-intro, .fl-api-console-response-header, .fl-api-console-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
1038
+ .fl-api-console-intro > div { min-width: 0; display: grid; gap: 5px; }
1039
+ .fl-api-console-intro strong { overflow-wrap: anywhere; font: 600 12px/1.45 var(--fl-mono); }
1040
+ .fl-api-console-kicker, .fl-api-console-group legend { color: #a1a1aa; font: 10px/1.3 var(--fl-mono); text-transform: uppercase; letter-spacing: .08em; }
1041
+ .fl-api-console-state { flex: 0 0 auto; padding: 4px 7px; border: 1px solid #3f3f46; color: #a1a1aa; font: 10px/1 var(--fl-mono); }
1042
+ .fl-api-console-state[data-state="sending"] { border-color: #6366f1; color: #a5b4fc; }
1043
+ .fl-api-console-state[data-state="success"] { border-color: #22c55e; color: #86efac; }
1044
+ .fl-api-console-state[data-state="error"] { border-color: #ef4444; color: #fca5a5; }
1045
+ .fl-api-console-group { display: grid; gap: 12px; min-width: 0; margin: 0; padding: 14px; border: 1px solid #29292f; }
1046
+ .fl-api-console-group legend { padding: 0 6px; }
1047
+ .fl-api-field { display: grid; gap: 6px; min-width: 0; color: #d4d4d8; font: 11px/1.35 var(--fl-mono); }
1048
+ .fl-api-field > span { display: flex; flex-wrap: wrap; align-items: baseline; gap: 5px; }
1049
+ .fl-api-field > span small { color: #71717a; font-size: 9px; text-transform: uppercase; }
1050
+ .fl-api-field em { color: #fca5a5; font-size: 9px; font-style: normal; text-transform: uppercase; }
1051
+ .fl-api-field input, .fl-api-field select, .fl-api-field textarea { width: 100%; min-width: 0; border: 1px solid #3f3f46; border-radius: calc(var(--fl-radius) * .55); background: #151519; color: #f4f4f5; font: 12px/1.45 var(--fl-mono); }
1052
+ .fl-api-field input, .fl-api-field select { height: 36px; padding: 0 10px; }
1053
+ .fl-api-field textarea { resize: vertical; min-height: 112px; padding: 10px; tab-size: 2; }
1054
+ .fl-api-field input::placeholder, .fl-api-field textarea::placeholder { color: #71717a; }
1055
+ .fl-api-field input:focus, .fl-api-field select:focus, .fl-api-field textarea:focus { border-color: #818cf8; outline: 2px solid color-mix(in srgb, #6366f1 45%, transparent); outline-offset: 0; }
1056
+ .fl-api-field--inline { grid-template-columns: minmax(120px, .7fr) minmax(0, 1fr); align-items: center; }
1057
+ .fl-api-field-help, .fl-api-console-hint, .fl-api-console-note { color: #a1a1aa; font-size: 10px; line-height: 1.5; }
1058
+ .fl-api-console-hint, .fl-api-console-note { margin: 0; }
1059
+ .fl-api-console-hint code { color: #d4d4d8; }
1060
+ .fl-api-auth-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
1061
+ .fl-api-console-confirm { display: flex; align-items: flex-start; gap: 8px; color: #fcd34d; font: 10px/1.45 var(--fl-mono); }
1062
+ .fl-api-console-confirm input { margin: 1px 0 0; accent-color: #6366f1; }
1063
+ .fl-api-console-actions { justify-content: flex-start; min-height: 36px; }
1064
+ .fl-api-console-actions button { min-height: 34px; padding: 0 12px; border: 1px solid #3f3f46; border-radius: calc(var(--fl-radius) * .55); cursor: pointer; font: 600 11px/1 var(--fl-mono); }
1065
+ .fl-api-console-send { background: var(--fl-primary); color: var(--fl-primary-fg); border-color: var(--fl-primary) !important; }
1066
+ .fl-api-console-send:hover:not(:disabled), .fl-api-console-send:focus-visible { filter: brightness(1.08); outline: 2px solid #a5b4fc; outline-offset: 2px; }
1067
+ .fl-api-console-send:disabled { cursor: not-allowed; opacity: .45; }
1068
+ .fl-api-console-cancel { background: transparent; color: #d4d4d8; }
1069
+ .fl-api-console-actions > span { color: #a1a1aa; font: 10px/1.4 var(--fl-mono); }
1070
+ .fl-api-console-error { padding: 10px 12px; border: 1px solid #7f1d1d; background: #2a1013; color: #fecaca; font: 11px/1.5 var(--fl-mono); }
1071
+ .fl-api-console-response { min-width: 0; border: 1px solid #3f3f46; outline: none; }
1072
+ .fl-api-console-response:focus-visible { border-color: #818cf8; box-shadow: 0 0 0 2px #6366f1; }
1073
+ .fl-api-console-response-header { min-height: 38px; border-bottom: 1px solid #29292f; }
1074
+ .fl-api-console-response-header > div { display: flex; align-items: center; gap: 8px; padding-left: 11px; color: #a1a1aa; font: 10px/1 var(--fl-mono); }
1075
+ .fl-api-console-status { color: #86efac; font-weight: 700; }
1076
+ .fl-api-console-status[data-ok="false"] { color: #fca5a5; }
1077
+ .fl-api-console-response-meta { display: grid; gap: 4px; padding: 9px 11px; border-bottom: 1px solid #29292f; color: #a1a1aa; font: 9px/1.4 var(--fl-mono); }
1078
+ .fl-api-console-response-meta code { overflow: hidden; color: #d4d4d8; text-overflow: ellipsis; white-space: nowrap; }
1079
+ .fl-api-console-response-headers { border-bottom: 1px solid #29292f; }
1080
+ .fl-api-console-response-headers summary { display: flex; align-items: center; justify-content: space-between; padding: 9px 11px; color: #a1a1aa; cursor: pointer; font: 10px/1.3 var(--fl-mono); list-style: none; }
1081
+ .fl-api-console-response-headers summary::-webkit-details-marker { display: none; }
1082
+ .fl-api-console-response-headers summary svg { width: 14px; transition: transform .15s ease; }
1083
+ .fl-api-console-response-headers[open] summary svg { transform: rotate(180deg); }
1084
+ .fl-api-console-response-headers pre { max-height: 180px; padding: 11px; border-top: 1px solid #29292f; color: #d4d4d8; }
1085
+ .fl-api-console-response-body { max-height: 340px; min-height: 72px; color: #f4f4f5; white-space: pre-wrap; overflow-wrap: anywhere; }
1086
+ .fl-api-console-note { padding-top: 2px; border-top: 1px solid #29292f; }
1087
+ .fl-api-response-list { display: grid; border: 1px solid var(--fl-border); }
1088
+ .fl-api-response + .fl-api-response { border-top: 1px solid var(--fl-border); }
1089
+ .fl-api-response > summary, .fl-api-schema > summary { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 11px 12px; cursor: pointer; list-style: none; }
1090
+ .fl-api-response > summary::-webkit-details-marker, .fl-api-schema > summary::-webkit-details-marker { display: none; }
1091
+ .fl-api-response > summary:hover, .fl-api-schema > summary:hover { background: var(--fl-soft); }
1092
+ .fl-api-response > summary svg, .fl-api-schema > summary svg { width: 14px; transition: transform .15s ease; }
1093
+ .fl-api-response[open] > summary svg, .fl-api-schema[open] > summary svg { transform: rotate(180deg); }
1094
+ .fl-api-response-content { padding: 0 12px 12px; }
1095
+ .fl-api-media + .fl-api-media { margin-top: 12px; }
1096
+ .fl-api-media-heading { margin: 10px 0 8px; }
1097
+ .fl-api-schema { overflow: hidden; margin-top: 8px; border: 1px solid var(--fl-border); border-radius: calc(var(--fl-radius) * .7); }
1098
+ .fl-api-schema > summary { grid-template-columns: minmax(0, 1fr) auto; font: 11px/1.4 var(--fl-mono); }
1099
+ .fl-api-schema-code { position: relative; border-top: 1px solid var(--fl-border); background: #0c0c0f; color: #f4f4f5; }
1100
+ .fl-api-schema-code .fl-api-copy { position: absolute; z-index: 1; top: 0; right: 0; height: 34px; color: #a1a1aa; border: 1px solid #29292f; border-top: 0; border-right: 0; background: #0c0c0f; }
1101
+ .fl-api-muted { color: var(--fl-muted); }
1102
+ .fl-api-empty, .fl-api-no-results { display: grid; gap: 6px; padding: 32px; border: 1px dashed var(--fl-border); color: var(--fl-muted); }
1103
+ .fl-api-empty strong, .fl-api-no-results strong { color: var(--fl-fg); }
1104
+ .fl-api-no-results { margin-top: 48px; }
1105
+ .fl-api-sidebar-empty { color: var(--fl-muted); font-size: 13px; }
1106
+ .fl-api-live-region { position: fixed; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
1107
+ :focus-visible { outline: 2px solid var(--fl-primary); outline-offset: 2px; }
1108
+ @media (max-width: 980px) { .fl-api-layout { grid-template-columns: 230px minmax(0, 1fr); } .fl-api-operation-grid { grid-template-columns: 1fr; } .fl-api-code-panel { position: relative; top: 0; } }
1109
+ @media (max-width: 720px) { .fl-api-topbar { padding-inline: 12px; } .fl-api-brand > span:last-child, .fl-api-version-control > span, .fl-api-action { display: none; } .fl-api-layout { display: block; } .fl-api-sidebar { position: relative; top: 0; width: 100%; height: auto; max-height: 52vh; border-right: 0; border-bottom: 1px solid var(--fl-border); } .fl-api-hero { padding: 32px 18px; } .fl-api-operations { padding-inline: 18px; } .fl-api-operation-grid { gap: 22px; } .fl-api-servers { grid-template-columns: auto minmax(0, 1fr); } .fl-api-servers .fl-api-copy { display: none; } .fl-api-field--inline, .fl-api-auth-pair { grid-template-columns: 1fr; } .fl-api-console { max-height: none; } }
1110
+ @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; } }
1111
+ `;
1112
+ }
1113
+ function buildClientScript() {
1114
+ return `(() => {
1115
+ const root = document.documentElement;
1116
+ const search = document.querySelector('[data-search-status]');
1117
+ const input = document.getElementById('fl-api-search');
1118
+ const operations = Array.from(document.querySelectorAll('[data-operation]'));
1119
+ const navItems = Array.from(document.querySelectorAll('[data-nav-operation]'));
1120
+ const groups = Array.from(document.querySelectorAll('[data-nav-group]'));
1121
+ const noResults = document.querySelector('[data-no-results]');
1122
+ const copyStatus = document.querySelector('[data-copy-status]');
1123
+ const applySearch = () => {
1124
+ const query = (input?.value || '').trim().toLocaleLowerCase();
1125
+ let visible = 0;
1126
+ for (const operation of operations) {
1127
+ const match = !query || (operation.dataset.search || '').includes(query);
1128
+ operation.hidden = !match;
1129
+ if (match) visible += 1;
1130
+ }
1131
+ for (const item of navItems) item.hidden = Boolean(query) && !(item.dataset.search || '').includes(query);
1132
+ for (const group of groups) group.hidden = !group.querySelector('[data-nav-operation]:not([hidden])');
1133
+ if (noResults) noResults.hidden = visible !== 0;
1134
+ if (search) search.textContent = query ? visible + ' of ' + operations.length + ' operations' : '';
1135
+ };
1136
+ input?.addEventListener('input', applySearch);
1137
+ document.addEventListener('keydown', (event) => {
1138
+ if (event.key === '/' && document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'SELECT') {
1139
+ event.preventDefault(); input?.focus();
1140
+ }
1141
+ });
1142
+ document.querySelector('[data-version-select]')?.addEventListener('change', (event) => {
1143
+ const target = event.currentTarget;
1144
+ if (target instanceof HTMLSelectElement && target.value) window.location.assign(target.value);
1145
+ });
1146
+ document.querySelector('[data-theme-toggle]')?.addEventListener('click', () => {
1147
+ const current = root.dataset.theme || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
1148
+ const next = current === 'dark' ? 'light' : 'dark';
1149
+ root.dataset.theme = next;
1150
+ try { localStorage.setItem('farming-labs-api-theme', next); } catch {}
1151
+ });
1152
+ try { const saved = localStorage.getItem('farming-labs-api-theme'); if (saved === 'dark' || saved === 'light') root.dataset.theme = saved; } catch {}
1153
+ const activateConsoleTab = (consoleRoot, tab) => {
1154
+ const tabs = Array.from(consoleRoot.querySelectorAll('[data-console-tab]'));
1155
+ const panels = Array.from(consoleRoot.querySelectorAll('[data-console-panel]'));
1156
+ for (const candidate of tabs) {
1157
+ const active = candidate.dataset.consoleTab === tab;
1158
+ candidate.setAttribute('aria-selected', String(active));
1159
+ candidate.tabIndex = active ? 0 : -1;
1160
+ }
1161
+ for (const panel of panels) panel.hidden = panel.dataset.consolePanel !== tab;
1162
+ const exampleCopy = consoleRoot.querySelector('[data-example-copy]');
1163
+ if (exampleCopy instanceof HTMLElement) exampleCopy.hidden = tab !== 'example';
1164
+ };
1165
+ for (const consoleRoot of document.querySelectorAll('[data-console-root]')) {
1166
+ const tabs = Array.from(consoleRoot.querySelectorAll('[data-console-tab]'));
1167
+ for (const tab of tabs) {
1168
+ tab.addEventListener('click', () => activateConsoleTab(consoleRoot, tab.dataset.consoleTab || 'example'));
1169
+ tab.addEventListener('keydown', (event) => {
1170
+ if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
1171
+ event.preventDefault();
1172
+ const current = tabs.indexOf(tab);
1173
+ const next = event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : (current + (event.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length;
1174
+ const nextTab = tabs[next];
1175
+ if (nextTab instanceof HTMLElement) {
1176
+ activateConsoleTab(consoleRoot, nextTab.dataset.consoleTab || 'example');
1177
+ nextTab.focus();
1178
+ }
1179
+ });
1180
+ }
1181
+ }
1182
+ const setConsoleState = (form, state, label) => {
1183
+ const badge = form.querySelector('[data-console-state]');
1184
+ if (badge instanceof HTMLElement) { badge.dataset.state = state; badge.textContent = label; }
1185
+ };
1186
+ const encodeBasicCredentials = (value) => {
1187
+ const bytes = new TextEncoder().encode(value);
1188
+ let binary = '';
1189
+ for (const byte of bytes) binary += String.fromCharCode(byte);
1190
+ return btoa(binary);
1191
+ };
1192
+ const appendQueryValue = (searchParams, name, value, array) => {
1193
+ const values = array ? value.split(',').map((entry) => entry.trim()).filter(Boolean) : [value];
1194
+ for (const entry of values) searchParams.append(name, entry);
1195
+ };
1196
+ const formatResponseBody = (value, contentType) => {
1197
+ if (!value) return '(empty response)';
1198
+ if (!contentType.includes('json') && !contentType.includes('+json')) return value;
1199
+ try { return JSON.stringify(JSON.parse(value), null, 2); } catch { return value; }
1200
+ };
1201
+ for (const form of document.querySelectorAll('[data-api-console]')) {
1202
+ if (!(form instanceof HTMLFormElement)) continue;
1203
+ let controller;
1204
+ const send = form.querySelector('[data-console-send]');
1205
+ const cancel = form.querySelector('[data-console-cancel]');
1206
+ const confirm = form.querySelector('[data-console-confirm]');
1207
+ const message = form.querySelector('[data-console-message]');
1208
+ const error = form.querySelector('[data-console-error]');
1209
+ const responsePanel = form.querySelector('[data-console-response]');
1210
+ if (confirm instanceof HTMLInputElement && send instanceof HTMLButtonElement) {
1211
+ confirm.addEventListener('change', () => { send.disabled = !confirm.checked; });
1212
+ }
1213
+ cancel?.addEventListener('click', () => controller?.abort());
1214
+ form.addEventListener('submit', async (event) => {
1215
+ event.preventDefault();
1216
+ if (!form.reportValidity() || !(send instanceof HTMLButtonElement)) return;
1217
+ if (error instanceof HTMLElement) { error.hidden = true; error.textContent = ''; }
1218
+ if (responsePanel instanceof HTMLElement) responsePanel.hidden = true;
1219
+ const originalLabel = send.textContent || 'Send request';
1220
+ send.disabled = true;
1221
+ send.textContent = 'Sending…';
1222
+ if (cancel instanceof HTMLElement) cancel.hidden = false;
1223
+ if (message instanceof HTMLElement) message.textContent = 'Request in progress';
1224
+ setConsoleState(form, 'sending', 'Sending');
1225
+ controller = new AbortController();
1226
+ const started = performance.now();
1227
+ try {
1228
+ const serverInput = form.elements.namedItem('server');
1229
+ if (!(serverInput instanceof HTMLInputElement)) throw new Error('Server URL is required.');
1230
+ const server = new URL(serverInput.value, window.location.origin);
1231
+ if (server.protocol !== 'http:' && server.protocol !== 'https:') throw new Error('Server URL must use HTTP or HTTPS.');
1232
+ let operationPath = form.dataset.path || '/';
1233
+ const headers = new Headers();
1234
+ const query = [];
1235
+ for (const input of form.querySelectorAll('[data-param-name]')) {
1236
+ if (!(input instanceof HTMLInputElement || input instanceof HTMLSelectElement)) continue;
1237
+ const value = input.value.trim();
1238
+ if (!value) continue;
1239
+ const name = input.dataset.paramName || '';
1240
+ const location = input.dataset.paramLocation;
1241
+ if (location === 'path') operationPath = operationPath.split('{' + name + '}').join(encodeURIComponent(value));
1242
+ else if (location === 'query') query.push([name, value, input.dataset.paramArray === 'true']);
1243
+ else if (location === 'header') headers.set(name, value);
1244
+ }
1245
+ server.hash = '';
1246
+ server.search = '';
1247
+ server.pathname = server.pathname.replace(/\\/+$/, '') + '/' + operationPath.replace(/^\\/+/, '');
1248
+ const url = server;
1249
+ for (const [name, value, array] of query) appendQueryValue(url.searchParams, name, value, array);
1250
+ for (const auth of form.querySelectorAll('[data-auth-token]')) {
1251
+ if (auth instanceof HTMLInputElement && auth.value) headers.set('authorization', (auth.dataset.authPrefix || 'Bearer') + ' ' + auth.value);
1252
+ }
1253
+ for (const auth of form.querySelectorAll('[data-auth-api-key]')) {
1254
+ if (!(auth instanceof HTMLInputElement) || !auth.value) continue;
1255
+ const name = auth.dataset.authName || '';
1256
+ if (auth.dataset.authLocation === 'query') url.searchParams.append(name, auth.value);
1257
+ else headers.set(name, auth.value);
1258
+ }
1259
+ const basic = form.querySelector('[data-auth-basic]');
1260
+ const username = basic?.querySelector('[data-auth-username]');
1261
+ const password = basic?.querySelector('[data-auth-password]');
1262
+ if (username instanceof HTMLInputElement && password instanceof HTMLInputElement && (username.value || password.value)) {
1263
+ headers.set('authorization', 'Basic ' + encodeBasicCredentials(username.value + ':' + password.value));
1264
+ }
1265
+ const bodyInput = form.elements.namedItem('request-body');
1266
+ const contentTypeInput = form.elements.namedItem('content-type');
1267
+ let body;
1268
+ if (bodyInput instanceof HTMLTextAreaElement && bodyInput.value.trim()) {
1269
+ body = bodyInput.value;
1270
+ const contentType = contentTypeInput instanceof HTMLSelectElement ? contentTypeInput.value : 'application/json';
1271
+ if (contentType.toLocaleLowerCase().includes('json') || contentType.toLocaleLowerCase().includes('+json')) body = JSON.stringify(JSON.parse(body));
1272
+ headers.set('content-type', contentType);
1273
+ }
1274
+ const credentialsInput = form.elements.namedItem('credentials');
1275
+ const credentials = credentialsInput instanceof HTMLSelectElement ? credentialsInput.value : 'same-origin';
1276
+ const response = await fetch(url, {
1277
+ method: form.dataset.method || 'GET',
1278
+ headers,
1279
+ body,
1280
+ credentials,
1281
+ signal: controller.signal,
1282
+ });
1283
+ const rawBody = await response.text();
1284
+ const contentType = response.headers.get('content-type') || 'unknown content type';
1285
+ const formattedBody = formatResponseBody(rawBody, contentType.toLocaleLowerCase());
1286
+ const duration = Math.round(performance.now() - started);
1287
+ const status = form.querySelector('[data-response-status]');
1288
+ const durationTarget = form.querySelector('[data-response-duration]');
1289
+ const urlTarget = form.querySelector('[data-response-url]');
1290
+ const typeTarget = form.querySelector('[data-response-type]');
1291
+ const headersTarget = form.querySelector('[data-response-headers]');
1292
+ const bodyTarget = form.querySelector('[data-response-body]');
1293
+ const copy = form.querySelector('[data-response-copy]');
1294
+ if (status instanceof HTMLElement) { status.textContent = response.status + ' ' + response.statusText; status.dataset.ok = String(response.ok); }
1295
+ if (durationTarget instanceof HTMLElement) durationTarget.textContent = duration + ' ms';
1296
+ if (urlTarget instanceof HTMLElement) { urlTarget.textContent = url.href; urlTarget.title = url.href; }
1297
+ if (typeTarget instanceof HTMLElement) typeTarget.textContent = contentType;
1298
+ if (headersTarget instanceof HTMLElement) headersTarget.textContent = Array.from(response.headers.entries()).map(([name, value]) => name + ': ' + value).join('\\n') || '(no exposed response headers)';
1299
+ if (bodyTarget instanceof HTMLElement) bodyTarget.textContent = formattedBody;
1300
+ if (copy instanceof HTMLButtonElement) copy.dataset.copy = formattedBody;
1301
+ if (responsePanel instanceof HTMLElement) { responsePanel.hidden = false; responsePanel.focus({ preventScroll: true }); }
1302
+ setConsoleState(form, response.ok ? 'success' : 'error', response.ok ? 'Complete' : 'HTTP error');
1303
+ if (message instanceof HTMLElement) message.textContent = response.ok ? 'Request completed' : 'Request completed with an error status';
1304
+ } catch (requestError) {
1305
+ const aborted = requestError instanceof DOMException && requestError.name === 'AbortError';
1306
+ const detail = aborted ? 'Request cancelled.' : requestError instanceof Error ? requestError.message : 'The request could not be sent.';
1307
+ setConsoleState(form, aborted ? 'idle' : 'error', aborted ? 'Cancelled' : 'Failed');
1308
+ if (message instanceof HTMLElement) message.textContent = aborted ? 'Request cancelled' : 'Request failed';
1309
+ if (error instanceof HTMLElement) { error.hidden = false; error.textContent = detail; }
1310
+ } finally {
1311
+ controller = undefined;
1312
+ send.textContent = originalLabel;
1313
+ send.disabled = confirm instanceof HTMLInputElement ? !confirm.checked : false;
1314
+ if (cancel instanceof HTMLElement) cancel.hidden = true;
1315
+ }
1316
+ });
1317
+ }
1318
+ document.addEventListener('click', async (event) => {
1319
+ const button = event.target instanceof Element ? event.target.closest('[data-copy]') : null;
1320
+ if (!(button instanceof HTMLButtonElement)) return;
1321
+ const value = button.dataset.copy || '';
1322
+ const original = button.textContent || 'Copy';
1323
+ try {
1324
+ await navigator.clipboard.writeText(value);
1325
+ button.textContent = 'Copied';
1326
+ if (copyStatus) copyStatus.textContent = 'Copied to clipboard';
1327
+ setTimeout(() => { button.textContent = original; if (copyStatus) copyStatus.textContent = ''; }, 1600);
1328
+ } catch {
1329
+ button.textContent = 'Failed';
1330
+ if (copyStatus) copyStatus.textContent = 'Could not copy to clipboard';
1331
+ setTimeout(() => { button.textContent = original; if (copyStatus) copyStatus.textContent = ''; }, 1600);
1332
+ }
1333
+ });
1334
+ })();`;
1335
+ }
1336
+ function searchIcon() {
1337
+ return "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.4-3.4\"/></svg>";
1338
+ }
1339
+ function themeIcon() {
1340
+ return "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"M12 3a9 9 0 1 0 9 9c0-.6-.1-1.2-.2-1.8A7 7 0 0 1 12 3Z\"/></svg>";
1341
+ }
1342
+ function lockIcon() {
1343
+ return "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><rect x=\"5\" y=\"10\" width=\"14\" height=\"10\" rx=\"2\"/><path d=\"M8 10V7a4 4 0 0 1 8 0v3\"/></svg>";
1344
+ }
1345
+ function chevronIcon() {
1346
+ return "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"/></svg>";
1347
+ }
1348
+
1349
+ //#endregion
1350
+ //#region src/openapi-references.ts
1351
+ const OPENAPI_BUNDLED_REFERENCES_EXTENSION = "x-farming-labs-references";
1352
+ const OPENAPI_REFERENCE_MAX_DOCUMENTS = 32;
1353
+ function resolveOpenApiReferencesSync(document, options) {
1354
+ const sourceUri = normalizeDocumentUri(options.sourceUri);
1355
+ const entry = createDocumentState({
1356
+ uri: sourceUri,
1357
+ document
1358
+ }, true);
1359
+ const documents = /* @__PURE__ */ new Map();
1360
+ registerDocument(documents, entry, sourceUri);
1361
+ const bundle = {};
1362
+ const maxDocuments = normalizeMaxDocuments(options.maxDocuments);
1363
+ const getDocument = (requestedUri, fromUri) => {
1364
+ const normalizedRequest = normalizeDocumentUri(requestedUri);
1365
+ const cached = documents.get(normalizedRequest);
1366
+ if (cached) return cached;
1367
+ if (!options.loadDocument) throw new Error(`OpenAPI reference requires an external document: ${normalizedRequest}`);
1368
+ if (countDocuments(documents) >= maxDocuments) throw new Error(`OpenAPI references exceed the ${maxDocuments}-document limit.`);
1369
+ const state = createDocumentState(options.loadDocument(new URL(normalizedRequest), new URL(fromUri)), false);
1370
+ registerDocument(documents, state, normalizedRequest);
1371
+ return state;
1372
+ };
1373
+ const processDocument = (state) => {
1374
+ if (state.processed || state.processing) return state.output;
1375
+ state.processing = true;
1376
+ state.output = structuredClone(state.document);
1377
+ if (!state.entry) {
1378
+ state.bundleKey = createBundleKey(state.retrievalUri);
1379
+ bundle[state.bundleKey] = state.output;
1380
+ }
1381
+ rewriteReferencesSync(state.output, state, getDocument, processDocument);
1382
+ state.processing = false;
1383
+ state.processed = true;
1384
+ return state.output;
1385
+ };
1386
+ const output = processDocument(entry);
1387
+ if (!isRecord(output)) throw new Error("The OpenAPI entry document must be an object.");
1388
+ if (Object.keys(bundle).length > 0) {
1389
+ if (Object.hasOwn(output, OPENAPI_BUNDLED_REFERENCES_EXTENSION)) throw new Error(`The OpenAPI document uses the reserved \`${OPENAPI_BUNDLED_REFERENCES_EXTENSION}\` extension.`);
1390
+ output[OPENAPI_BUNDLED_REFERENCES_EXTENSION] = bundle;
1391
+ }
1392
+ return output;
1393
+ }
1394
+ async function resolveOpenApiReferences(document, options) {
1395
+ const sourceUri = normalizeDocumentUri(options.sourceUri);
1396
+ const entry = createDocumentState({
1397
+ uri: sourceUri,
1398
+ document
1399
+ }, true);
1400
+ const documents = /* @__PURE__ */ new Map();
1401
+ registerDocument(documents, entry, sourceUri);
1402
+ const bundle = {};
1403
+ const maxDocuments = normalizeMaxDocuments(options.maxDocuments);
1404
+ const getDocument = async (requestedUri, fromUri) => {
1405
+ const normalizedRequest = normalizeDocumentUri(requestedUri);
1406
+ const cached = documents.get(normalizedRequest);
1407
+ if (cached) return cached;
1408
+ if (!options.loadDocument) throw new Error(`OpenAPI reference requires an external document: ${normalizedRequest}`);
1409
+ if (countDocuments(documents) >= maxDocuments) throw new Error(`OpenAPI references exceed the ${maxDocuments}-document limit.`);
1410
+ const state = createDocumentState(await options.loadDocument(new URL(normalizedRequest), new URL(fromUri)), false);
1411
+ registerDocument(documents, state, normalizedRequest);
1412
+ return state;
1413
+ };
1414
+ const processDocument = async (state) => {
1415
+ if (state.processed || state.processing) return state.output;
1416
+ state.processing = true;
1417
+ state.output = structuredClone(state.document);
1418
+ if (!state.entry) {
1419
+ state.bundleKey = createBundleKey(state.retrievalUri);
1420
+ bundle[state.bundleKey] = state.output;
1421
+ }
1422
+ await rewriteReferences(state.output, state, getDocument, processDocument);
1423
+ state.processing = false;
1424
+ state.processed = true;
1425
+ return state.output;
1426
+ };
1427
+ const output = await processDocument(entry);
1428
+ if (!isRecord(output)) throw new Error("The OpenAPI entry document must be an object.");
1429
+ if (Object.keys(bundle).length > 0) {
1430
+ if (Object.hasOwn(output, OPENAPI_BUNDLED_REFERENCES_EXTENSION)) throw new Error(`The OpenAPI document uses the reserved \`${OPENAPI_BUNDLED_REFERENCES_EXTENSION}\` extension.`);
1431
+ output[OPENAPI_BUNDLED_REFERENCES_EXTENSION] = bundle;
1432
+ }
1433
+ return output;
1434
+ }
1435
+ function rewriteReferencesSync(value, currentDocument, getDocument, processDocument, ancestors = /* @__PURE__ */ new WeakSet()) {
1436
+ if (!value || typeof value !== "object") return;
1437
+ if (ancestors.has(value)) throw new Error("OpenAPI source documents cannot contain cyclic YAML aliases.");
1438
+ ancestors.add(value);
1439
+ if (Array.isArray(value)) {
1440
+ for (const item of value) rewriteReferencesSync(item, currentDocument, getDocument, processDocument, ancestors);
1441
+ ancestors.delete(value);
1442
+ return;
1443
+ }
1444
+ const record = value;
1445
+ if (Object.hasOwn(record, "$ref")) record.$ref = rewriteReferenceSync(record.$ref, currentDocument, getDocument, processDocument);
1446
+ for (const [key, child] of Object.entries(record)) {
1447
+ if (key === "$ref") continue;
1448
+ rewriteReferencesSync(child, currentDocument, getDocument, processDocument, ancestors);
1449
+ }
1450
+ ancestors.delete(value);
1451
+ }
1452
+ async function rewriteReferences(value, currentDocument, getDocument, processDocument, ancestors = /* @__PURE__ */ new WeakSet()) {
1453
+ if (!value || typeof value !== "object") return;
1454
+ if (ancestors.has(value)) throw new Error("OpenAPI source documents cannot contain cyclic YAML aliases.");
1455
+ ancestors.add(value);
1456
+ if (Array.isArray(value)) {
1457
+ for (const item of value) await rewriteReferences(item, currentDocument, getDocument, processDocument, ancestors);
1458
+ ancestors.delete(value);
1459
+ return;
1460
+ }
1461
+ const record = value;
1462
+ if (Object.hasOwn(record, "$ref")) record.$ref = await rewriteReference(record.$ref, currentDocument, getDocument, processDocument);
1463
+ for (const [key, child] of Object.entries(record)) {
1464
+ if (key === "$ref") continue;
1465
+ await rewriteReferences(child, currentDocument, getDocument, processDocument, ancestors);
1466
+ }
1467
+ ancestors.delete(value);
1468
+ }
1469
+ function rewriteReferenceSync(reference, currentDocument, getDocument, processDocument) {
1470
+ const target = resolveReference(reference, currentDocument.baseUri);
1471
+ const targetDocument = getDocument(target.documentUri, currentDocument.baseUri);
1472
+ assertReferenceTarget(targetDocument.document, target.pointer, reference);
1473
+ processDocument(targetDocument);
1474
+ return buildBundledReference(targetDocument, target.pointer);
1475
+ }
1476
+ async function rewriteReference(reference, currentDocument, getDocument, processDocument) {
1477
+ const target = resolveReference(reference, currentDocument.baseUri);
1478
+ const targetDocument = await getDocument(target.documentUri, currentDocument.baseUri);
1479
+ assertReferenceTarget(targetDocument.document, target.pointer, reference);
1480
+ await processDocument(targetDocument);
1481
+ return buildBundledReference(targetDocument, target.pointer);
1482
+ }
1483
+ function resolveReference(reference, baseUri) {
1484
+ if (typeof reference !== "string" || !reference.trim()) throw new Error("OpenAPI `$ref` values must be non-empty URI strings.");
1485
+ let url;
1486
+ try {
1487
+ url = new URL(reference, baseUri);
1488
+ } catch {
1489
+ throw new Error(`OpenAPI reference is not a valid URI: ${reference}`);
1490
+ }
1491
+ const pointer = normalizePointerFragment(url.hash, reference);
1492
+ url.hash = "";
1493
+ return {
1494
+ documentUri: url.href,
1495
+ pointer
1496
+ };
1497
+ }
1498
+ function normalizePointerFragment(hash, reference) {
1499
+ if (!hash || hash === "#") return "";
1500
+ let decoded;
1501
+ try {
1502
+ decoded = decodeURIComponent(hash.slice(1));
1503
+ } catch {
1504
+ throw new Error(`OpenAPI reference has an invalid encoded fragment: ${String(reference)}`);
1505
+ }
1506
+ if (!decoded.startsWith("/")) throw new Error(`OpenAPI reference anchors are not supported yet; use a JSON Pointer fragment: ${String(reference)}`);
1507
+ return decoded;
1508
+ }
1509
+ function assertReferenceTarget(document, pointer, reference) {
1510
+ if (!pointer) return;
1511
+ let current = document;
1512
+ for (const token of pointer.slice(1).split("/")) {
1513
+ if (!current || typeof current !== "object") throw new Error(`OpenAPI reference target was not found: ${String(reference)}`);
1514
+ const key = decodeJsonPointerToken(token, reference);
1515
+ if (!Object.hasOwn(current, key)) throw new Error(`OpenAPI reference target was not found: ${String(reference)}`);
1516
+ current = current[key];
1517
+ }
1518
+ }
1519
+ function decodeJsonPointerToken(token, reference) {
1520
+ if (/~(?:[^01]|$)/.test(token)) throw new Error(`OpenAPI reference has an invalid JSON Pointer fragment: ${String(reference)}`);
1521
+ return token.replaceAll("~1", "/").replaceAll("~0", "~");
1522
+ }
1523
+ function buildBundledReference(document, pointer) {
1524
+ if (document.entry) return `#${pointer}`;
1525
+ if (!document.bundleKey) throw new Error(`OpenAPI reference document was not bundled: ${document.retrievalUri}`);
1526
+ return `#/${OPENAPI_BUNDLED_REFERENCES_EXTENSION}/${escapeJsonPointer(document.bundleKey)}${pointer}`;
1527
+ }
1528
+ function createDocumentState(loaded, entry) {
1529
+ const retrievalUri = normalizeDocumentUri(loaded.uri);
1530
+ const self = isRecord(loaded.document) ? loaded.document.$self : void 0;
1531
+ let baseUri = retrievalUri;
1532
+ if (typeof self === "string" && self.trim()) try {
1533
+ baseUri = normalizeDocumentUri(new URL(self, retrievalUri).href);
1534
+ } catch {
1535
+ throw new Error(`OpenAPI document has an invalid \`$self\` URI: ${self}`);
1536
+ }
1537
+ return {
1538
+ retrievalUri,
1539
+ baseUri,
1540
+ document: loaded.document,
1541
+ entry,
1542
+ processing: false,
1543
+ processed: false
1544
+ };
1545
+ }
1546
+ function registerDocument(documents, document, requestedUri) {
1547
+ for (const uri of [
1548
+ requestedUri,
1549
+ document.retrievalUri,
1550
+ document.baseUri
1551
+ ]) {
1552
+ const normalized = normalizeDocumentUri(uri);
1553
+ const existing = documents.get(normalized);
1554
+ if (existing && existing !== document) throw new Error(`Multiple OpenAPI documents claim the same base URI: ${normalized}`);
1555
+ documents.set(normalized, document);
1556
+ }
1557
+ }
1558
+ function countDocuments(documents) {
1559
+ return new Set(documents.values()).size;
1560
+ }
1561
+ function normalizeDocumentUri(value) {
1562
+ const url = new URL(value);
1563
+ url.hash = "";
1564
+ return url.href;
1565
+ }
1566
+ function normalizeMaxDocuments(value) {
1567
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : OPENAPI_REFERENCE_MAX_DOCUMENTS;
1568
+ }
1569
+ function createBundleKey(uri) {
1570
+ return `ref-${createHash("sha256").update(uri).digest("hex").slice(0, 16)}`;
1571
+ }
1572
+ function escapeJsonPointer(value) {
1573
+ return value.replaceAll("~", "~0").replaceAll("/", "~1");
1574
+ }
1575
+ function isRecord(value) {
1576
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1577
+ }
1578
+
1579
+ //#endregion
1580
+ //#region src/api-reference.ts
1581
+ const NEXT_ROUTE_FILE_RE = /^route\.(ts|tsx|js|jsx)$/;
1582
+ const SVELTE_ROUTE_FILE_RE = /^\+server\.(ts|js)$/;
1583
+ const ASTRO_ROUTE_FILE_RE = /^[^.].*\.(ts|js|mts|mjs)$/;
1584
+ const NUXT_ROUTE_FILE_RE = /^[^.].*\.(ts|js|mts|mjs)$/;
1585
+ const TANSTACK_ROUTE_FILE_RE = /\.(ts|tsx|js|jsx)$/;
1586
+ const METHOD_RE = /export\s+(?:async\s+function|function|const)\s+(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|ALL)\b/g;
1587
+ const METHOD_NAMES = [
1588
+ "GET",
1589
+ "POST",
1590
+ "PUT",
1591
+ "PATCH",
1592
+ "DELETE",
1593
+ "OPTIONS",
1594
+ "HEAD"
1595
+ ];
1596
+ const DEFAULT_API_REFERENCE_OPENAPI_ROUTE = "/api/docs?format=openapi";
1597
+ const OPENAPI_SPEC_FETCH_TIMEOUT_MS = 1e4;
1598
+ const OPENAPI_SPEC_MAX_BYTES = 5 * 1024 * 1024;
1599
+ function normalizePathSegment(value) {
1600
+ return value.replace(/^\/+|\/+$/g, "");
1601
+ }
1602
+ function resolveApiReferenceConfig(value) {
1603
+ if (value === true) return {
1604
+ enabled: true,
1605
+ path: "api-reference",
1606
+ specUrl: void 0,
1607
+ overlays: [],
1608
+ versions: [],
1609
+ defaultVersion: void 0,
1610
+ catalogTargets: void 0,
1611
+ renderer: void 0,
1612
+ mcp: void 0,
1613
+ routeRoot: "api",
1614
+ exclude: []
1615
+ };
1616
+ if (!value) return {
1617
+ enabled: false,
1618
+ path: "api-reference",
1619
+ specUrl: void 0,
1620
+ overlays: [],
1621
+ versions: [],
1622
+ defaultVersion: void 0,
1623
+ catalogTargets: void 0,
1624
+ renderer: void 0,
1625
+ mcp: void 0,
1626
+ routeRoot: "api",
1627
+ exclude: []
1628
+ };
1629
+ const specUrl = normalizeRemoteSpecUrl(value.specUrl);
1630
+ const overlays = normalizeOpenApiOverlaySources(value.overlays, "apiReference.overlays");
1631
+ const { versions, defaultVersion } = resolveApiReferenceVersions(value);
1632
+ if (versions.length > 0 && specUrl) throw new Error("`apiReference.versions` cannot be combined with `apiReference.specUrl`.");
1633
+ if (versions.length > 0 && overlays.length > 0) throw new Error("`apiReference.versions` cannot be combined with top-level `apiReference.overlays`; configure overlays on each version instead.");
1634
+ return {
1635
+ enabled: value.enabled !== false,
1636
+ path: normalizePathSegment(value.path ?? "api-reference"),
1637
+ specUrl,
1638
+ overlays,
1639
+ versions,
1640
+ defaultVersion,
1641
+ catalogTargets: normalizeApiReferenceCatalogTargets(value.catalogTargets),
1642
+ renderer: normalizeApiReferenceRenderer(value.renderer),
1643
+ mcp: resolveOpenApiMcpConfig(value.mcp),
1644
+ routeRoot: normalizePathSegment(value.routeRoot ?? "api") || "api",
1645
+ exclude: normalizeApiReferenceExcludes(value.exclude)
1646
+ };
1647
+ }
1648
+ function normalizeOpenApiOverlaySources(value, location) {
1649
+ if (value === void 0) return [];
1650
+ if (!Array.isArray(value)) throw new Error(`\`${location}\` must be an array of sources.`);
1651
+ return value.map((source, index) => {
1652
+ if (typeof source !== "string" || !source.trim()) throw new Error(`\`${location}[${index}]\` must be a non-empty string.`);
1653
+ return source.trim();
1654
+ });
1655
+ }
1656
+ function resolveApiReferenceVersions(value) {
1657
+ if (value.versions === void 0) {
1658
+ if (value.defaultVersion !== void 0) throw new Error("`apiReference.defaultVersion` requires `apiReference.versions`.");
1659
+ return { versions: [] };
1660
+ }
1661
+ if (!value.versions || typeof value.versions !== "object" || Array.isArray(value.versions)) throw new Error("`apiReference.versions` must be an object keyed by version identifier.");
1662
+ const entries = Object.entries(value.versions);
1663
+ if (entries.length === 0) throw new Error("`apiReference.versions` must not be empty.");
1664
+ const defaultVersion = value.defaultVersion?.trim();
1665
+ if (!defaultVersion) throw new Error("`apiReference.defaultVersion` is required when versions are configured.");
1666
+ if (!Object.prototype.hasOwnProperty.call(value.versions, defaultVersion)) throw new Error(`\`apiReference.defaultVersion\` references unknown version \`${defaultVersion}\`.`);
1667
+ return {
1668
+ versions: entries.map(([id, rawVersion]) => resolveApiReferenceVersionConfig(id, rawVersion, defaultVersion)),
1669
+ defaultVersion
1670
+ };
1671
+ }
1672
+ function resolveApiReferenceVersionConfig(rawId, value, defaultVersion) {
1673
+ const id = rawId.trim();
1674
+ if (!id || !/^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$/.test(id)) throw new Error(`OpenAPI version identifier \`${rawId}\` must be URL-safe and contain only letters, numbers, dots, underscores, or hyphens.`);
1675
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`\`apiReference.versions.${id}\` must be an object.`);
1676
+ const specUrl = normalizeRemoteSpecUrl(value.specUrl);
1677
+ if (!specUrl) throw new Error(`\`apiReference.versions.${id}.specUrl\` is required.`);
1678
+ return {
1679
+ id,
1680
+ label: value.label?.trim() || id,
1681
+ specUrl,
1682
+ overlays: normalizeOpenApiOverlaySources(value.overlays, `apiReference.versions.${id}.overlays`),
1683
+ default: id === defaultVersion
1684
+ };
1685
+ }
1686
+ function resolveOpenApiMcpConfig(value) {
1687
+ if (!value) return void 0;
1688
+ return value === true ? { enabled: true } : {
1689
+ ...value,
1690
+ enabled: value.enabled !== false
1691
+ };
1692
+ }
1693
+ function normalizeApiReferenceRenderer(value) {
1694
+ if (value === "farming-labs" || value === "fumadocs" || value === "scalar") return value;
1695
+ }
1696
+ function resolveApiReferenceRenderer(value, _framework) {
1697
+ const config = resolveApiReferenceConfig(value);
1698
+ if (config.renderer) return config.renderer;
1699
+ return "farming-labs";
1700
+ }
1701
+ function resolveApiReferenceOpenApiDiscovery(value, options = {}) {
1702
+ const config = resolveApiReferenceConfig(value);
1703
+ if (!config.enabled) return { enabled: false };
1704
+ const selectedSpecUrl = resolveApiReferenceVersion(config)?.specUrl ?? config.specUrl;
1705
+ const catalogTargets = config.catalogTargets ?? (!selectedSpecUrl || isRequestRelativeSpecUrl(selectedSpecUrl) ? ["/"] : void 0);
1706
+ const route = options.route ?? DEFAULT_API_REFERENCE_OPENAPI_ROUTE;
1707
+ const versions = config.versions.length > 0 ? config.versions.map((version) => ({
1708
+ id: version.id,
1709
+ label: version.label,
1710
+ default: version.default,
1711
+ url: appendUrlSearchParam(route, "version", version.id),
1712
+ apiReferencePath: `/${config.path}/${encodeURIComponent(version.id)}`,
1713
+ specUrl: isLocalSpecSource(version.specUrl) ? void 0 : version.specUrl
1714
+ })) : void 0;
1715
+ return {
1716
+ enabled: true,
1717
+ url: route,
1718
+ urlSource: options.route === void 0 ? "default" : "configured",
1719
+ source: selectedSpecUrl ? "configured" : "generated",
1720
+ specUrl: isLocalSpecSource(selectedSpecUrl) ? void 0 : selectedSpecUrl,
1721
+ apiReferencePath: `/${config.path}`,
1722
+ catalogTargets,
1723
+ versions
1724
+ };
1725
+ }
1726
+ function appendUrlSearchParam(url, name, value) {
1727
+ const [withoutHash, hash] = url.split("#", 2);
1728
+ return `${withoutHash}${withoutHash.includes("?") ? "&" : "?"}${encodeURIComponent(name)}=${encodeURIComponent(value)}${hash === void 0 ? "" : `#${hash}`}`;
1729
+ }
1730
+ function resolveApiReferenceVersion(config, requestedVersion) {
1731
+ if (config.versions.length === 0) return void 0;
1732
+ const versionId = requestedVersion?.trim() || config.defaultVersion;
1733
+ return config.versions.find((version) => version.id === versionId);
1734
+ }
1735
+ function resolveApiReferenceVersionFromPathname(value, pathname) {
1736
+ const config = resolveApiReferenceConfig(value);
1737
+ if (config.versions.length === 0) return void 0;
1738
+ const pathSegments = normalizePathSegment(config.path).split("/").filter(Boolean);
1739
+ const pathnameSegments = pathname.split("/").filter(Boolean);
1740
+ if (!pathSegments.every((segment, index) => pathnameSegments[index] === segment)) return;
1741
+ const rawVersion = pathnameSegments[pathSegments.length];
1742
+ if (!rawVersion) return void 0;
1743
+ try {
1744
+ return resolveApiReferenceVersion(config, decodeURIComponent(rawVersion));
1745
+ } catch {
1746
+ return;
1747
+ }
1748
+ }
1749
+ function isApiReferenceOpenApiRequest(url) {
1750
+ return url.searchParams.get("format")?.trim() === "openapi";
1751
+ }
1752
+ function normalizeRemoteSpecUrl(value) {
1753
+ const trimmed = value?.trim();
1754
+ if (!trimmed) return void 0;
1755
+ return trimmed;
1756
+ }
1757
+ function normalizeApiReferenceCatalogTargets(value) {
1758
+ if (value === void 0) return void 0;
1759
+ return Array.from(new Set(value.filter((target) => typeof target === "string").map((target) => target.trim()).filter(Boolean)));
1760
+ }
1761
+ function isRequestRelativeSpecUrl(value) {
1762
+ return typeof value === "string" && value.startsWith("/") && !value.startsWith("//");
1763
+ }
1764
+ function isRemoteSpecUrl(value) {
1765
+ return /^(?:https?:)?\/\//i.test(value);
1766
+ }
1767
+ function isLocalSpecSource(value) {
1768
+ if (!value) return false;
1769
+ if (value.startsWith("file:")) return true;
1770
+ return !isRemoteSpecUrl(value) && !isRequestRelativeSpecUrl(value) && !/^[a-z][a-z\d+.-]*:/i.test(value);
1771
+ }
1772
+ function buildApiReferencePageTitle(config, title = "API Reference") {
1773
+ const template = config.metadata?.titleTemplate;
1774
+ if (!template) return title;
1775
+ return template.replace("%s", title);
1776
+ }
1777
+ function buildApiReferenceScalarCss(config) {
1778
+ const theme = resolveTheme(config);
1779
+ const colors = theme?.ui?.colors;
1780
+ const typography = theme?.ui?.typography?.font?.style;
1781
+ const layout = theme?.ui?.layout;
1782
+ const radius = resolveApiReferenceRadius(theme);
1783
+ const primary = colors?.primary ?? "#6366f1";
1784
+ const border = colors?.border ?? "#2a2a2a";
1785
+ const muted = colors?.muted ?? "#64748b";
1786
+ const background = colors?.background ?? "#ffffff";
1787
+ const card = colors?.card ?? background;
1788
+ const foreground = colors?.foreground ?? inferApiReferenceForeground(theme, background);
1789
+ const primaryForeground = colors?.primaryForeground ?? inferContrastingForeground(primary, theme, background);
1790
+ const sidebarWidth = layout?.sidebarWidth ?? 280;
1791
+ const sans = typography?.sans ?? "\"Geist\", \"Inter\", \"Segoe UI\", sans-serif";
1792
+ const mono = typography?.mono ?? "\"Geist Mono\", \"SFMono-Regular\", \"Menlo\", monospace";
1793
+ const isPixelBorder = theme?.name?.includes("pixel-border");
1794
+ return `
1795
+ :root {
1796
+ --scalar-font: ${sans};
1797
+ --scalar-font-code: ${mono};
1798
+ --scalar-radius: ${radius};
1799
+ --scalar-radius-sm: calc(${radius} + 2px);
1800
+ --scalar-theme-primary: ${primary};
1801
+ --scalar-theme-border: ${border};
1802
+ --scalar-theme-muted: ${muted};
1803
+ --scalar-theme-background: ${background};
1804
+ --scalar-theme-card: ${card};
1805
+ --scalar-theme-foreground: ${foreground};
1806
+ }
1807
+
1808
+ .dark-mode {
1809
+ --scalar-background-1: color-mix(
1810
+ in srgb,
1811
+ var(--scalar-theme-background) 96%,
1812
+ var(--scalar-theme-primary) 4%
1813
+ );
1814
+ --scalar-background-2: color-mix(in srgb, var(--scalar-theme-card) 94%, var(--scalar-theme-primary) 6%);
1815
+ --scalar-background-3: color-mix(
1816
+ in srgb,
1817
+ var(--scalar-theme-card) 90%,
1818
+ var(--scalar-theme-foreground) 10%
1819
+ );
1820
+ --scalar-color-1: var(--scalar-theme-foreground);
1821
+ --scalar-color-2: color-mix(in srgb, var(--scalar-theme-foreground) 72%, transparent);
1822
+ --scalar-color-3: color-mix(in srgb, var(--scalar-theme-foreground) 52%, transparent);
1823
+ --scalar-color-accent: var(--scalar-theme-primary);
1824
+ --scalar-sidebar-background-1: var(--scalar-background-1);
1825
+ --scalar-sidebar-background-2: var(--scalar-background-2);
1826
+ --scalar-sidebar-color-1: var(--scalar-color-1);
1827
+ --scalar-sidebar-color-2: var(--scalar-color-2);
1828
+ --scalar-sidebar-search-background: var(--scalar-background-2);
1829
+ --scalar-sidebar-search-border-color: var(--scalar-border-color);
1830
+ --scalar-sidebar-search-color: var(--scalar-color-2);
1831
+ --scalar-sidebar-color-active: var(--scalar-theme-primary);
1832
+ --scalar-sidebar-item-active-background: color-mix(
1833
+ in srgb,
1834
+ var(--scalar-theme-primary) 7%,
1835
+ transparent
1836
+ );
1837
+ --scalar-border-color: ${isPixelBorder ? "color-mix(in srgb, var(--scalar-theme-foreground) 10%, transparent)" : "color-mix(in srgb, var(--scalar-theme-border) 14%, rgba(255, 255, 255, 0.02))"};
1838
+ --scalar-button-1: var(--scalar-theme-primary);
1839
+ --scalar-button-1-color: ${primaryForeground};
1840
+ --scalar-button-1-hover: color-mix(in srgb, var(--scalar-theme-primary) 88%, white 12%);
1841
+ }
1842
+
1843
+ .light-mode {
1844
+ --scalar-background-1: var(--scalar-theme-background);
1845
+ --scalar-background-2: color-mix(in srgb, var(--scalar-theme-card) 92%, white 8%);
1846
+ --scalar-background-3: color-mix(in srgb, var(--scalar-theme-card) 84%, black 4%);
1847
+ --scalar-color-1: var(--scalar-theme-foreground);
1848
+ --scalar-color-2: var(--scalar-theme-muted);
1849
+ --scalar-color-3: color-mix(in srgb, var(--scalar-theme-muted) 78%, white 22%);
1850
+ --scalar-color-accent: var(--scalar-theme-primary);
1851
+ --scalar-sidebar-background-1: var(--scalar-background-1);
1852
+ --scalar-sidebar-background-2: var(--scalar-background-2);
1853
+ --scalar-sidebar-color-1: var(--scalar-color-1);
1854
+ --scalar-sidebar-color-2: var(--scalar-color-2);
1855
+ --scalar-sidebar-search-background: var(--scalar-background-2);
1856
+ --scalar-sidebar-search-border-color: var(--scalar-border-color);
1857
+ --scalar-sidebar-search-color: var(--scalar-color-2);
1858
+ --scalar-sidebar-color-active: var(--scalar-theme-primary);
1859
+ --scalar-sidebar-item-active-background: color-mix(
1860
+ in srgb,
1861
+ var(--scalar-theme-primary) 5%,
1862
+ transparent
1863
+ );
1864
+ --scalar-border-color: ${isPixelBorder ? "color-mix(in srgb, var(--scalar-theme-foreground) 14%, transparent)" : "color-mix(in srgb, var(--scalar-theme-border) 30%, white 70%)"};
1865
+ --scalar-button-1: var(--scalar-theme-primary);
1866
+ --scalar-button-1-color: ${primaryForeground};
1867
+ --scalar-button-1-hover: color-mix(in srgb, var(--scalar-theme-primary) 88%, black 12%);
1868
+ }
1869
+
1870
+ body {
1871
+ background: var(--scalar-background-1);
1872
+ color: var(--scalar-color-1);
1873
+ }
1874
+
1875
+ .t-doc__sidebar {
1876
+ width: min(${sidebarWidth}px, 100vw);
1877
+ border-right: 1px solid var(--scalar-border-color);
1878
+ }
1879
+
1880
+ .scalar-card,
1881
+ .t-doc__sidebar,
1882
+ .references-layout .reference-layout__content .request-card,
1883
+ .references-layout .reference-layout__content .response-card,
1884
+ .references-layout .reference-layout__content .scalar-card-header,
1885
+ .references-layout .reference-layout__content .scalar-card-footer,
1886
+ .references-layout .reference-layout__content .section,
1887
+ .references-layout .reference-layout__content .section-container {
1888
+ border-color: var(--scalar-border-color) !important;
1889
+ }
1890
+
1891
+ .t-doc__sidebar,
1892
+ .t-doc__sidebar * {
1893
+ font-family: var(--scalar-font);
1894
+ }
1895
+
1896
+ .t-doc__sidebar .sidebar-search {
1897
+ margin: 0.5rem 0 1rem;
1898
+ }
1899
+
1900
+ .t-doc__sidebar .sidebar-search input {
1901
+ border-radius: var(--scalar-radius-sm);
1902
+ }
1903
+
1904
+ .t-doc__sidebar .sidebar-item,
1905
+ .t-doc__sidebar .sidebar-heading {
1906
+ border-radius: var(--scalar-radius-sm);
1907
+ }
1908
+
1909
+ .t-doc__sidebar .sidebar-group-label {
1910
+ font-size: 0.72rem;
1911
+ letter-spacing: 0.08em;
1912
+ text-transform: uppercase;
1913
+ color: var(--scalar-color-3);
1914
+ }
1915
+
1916
+ .t-doc__sidebar .sidebar-item--active {
1917
+ font-weight: 600;
1918
+ }
1919
+
1920
+ .scalar-card,
1921
+ .references-layout .reference-layout__content .request-card,
1922
+ .references-layout .reference-layout__content .response-card {
1923
+ border-radius: var(--scalar-radius);
1924
+ }
1925
+
1926
+ .references-layout .reference-layout__content {
1927
+ padding-top: 1.5rem;
1928
+ }
1929
+
1930
+ .references-layout .section-content,
1931
+ .references-layout .section-flare {
1932
+ background: transparent;
1933
+ }
1934
+
1935
+ .references-layout .reference-layout__content,
1936
+ .references-layout .reference-layout__content * {
1937
+ font-family: var(--scalar-font);
1938
+ }
1939
+
1940
+ .references-layout code,
1941
+ .references-layout pre,
1942
+ .references-layout .scalar-codeblock {
1943
+ font-family: var(--scalar-font-code);
1944
+ }
1945
+
1946
+ .references-layout,
1947
+ .references-layout * {
1948
+ color: inherit;
1949
+ }
1950
+
1951
+ .references-layout .reference-layout__content .introduction,
1952
+ .references-layout .reference-layout__content .section,
1953
+ .references-layout .reference-layout__content .section-container,
1954
+ .references-layout .reference-layout__content .operation-details,
1955
+ .references-layout .reference-layout__content .markdown,
1956
+ .references-layout .reference-layout__content .markdown *,
1957
+ .references-layout .reference-layout__content .property,
1958
+ .references-layout .reference-layout__content .property *,
1959
+ .references-layout .reference-layout__content .parameter-item,
1960
+ .references-layout .reference-layout__content .parameter-item *,
1961
+ .references-layout .reference-layout__content .response-card,
1962
+ .references-layout .reference-layout__content .response-card *,
1963
+ .references-layout .reference-layout__content .request-card,
1964
+ .references-layout .reference-layout__content .request-card * {
1965
+ color: var(--scalar-color-1);
1966
+ }
1967
+
1968
+ .references-layout a,
1969
+ .references-layout button {
1970
+ color: var(--scalar-color-1);
1971
+ }
1972
+
1973
+ ${isPixelBorder ? buildPixelBorderScalarCss() : ""}
1974
+ `;
1975
+ }
1976
+ function buildPixelBorderScalarCss() {
1977
+ return `
1978
+ .t-doc__sidebar,
1979
+ .references-layout .reference-layout__content {
1980
+ background-image:
1981
+ repeating-linear-gradient(
1982
+ -45deg,
1983
+ color-mix(in srgb, var(--scalar-theme-border) 12%, transparent),
1984
+ color-mix(in srgb, var(--scalar-theme-border) 12%, transparent) 1px,
1985
+ transparent 1px,
1986
+ transparent 8px
1987
+ );
1988
+ }
1989
+
1990
+ .t-doc__sidebar .sidebar-group-label,
1991
+ .t-doc__sidebar .sidebar-item,
1992
+ .references-layout .reference-layout__content .section-header {
1993
+ font-family: var(--scalar-font-code);
1994
+ text-transform: uppercase;
1995
+ letter-spacing: 0.06em;
1996
+ }
1997
+ `;
1998
+ }
1999
+ function inferApiReferenceForeground(theme, background) {
2000
+ if (looksDarkTheme(theme, background)) return "#f5f5f4";
2001
+ return "#1b1b1b";
2002
+ }
2003
+ function resolveApiReferenceRadius(theme) {
2004
+ if (theme?.ui?.radius) return theme.ui.radius;
2005
+ const name = theme?.name?.toLowerCase() ?? "";
2006
+ if (name.includes("pixel-border") || name.includes("darksharp")) return "0px";
2007
+ return "var(--radius, 0.75rem)";
2008
+ }
2009
+ function inferContrastingForeground(value, theme, background) {
2010
+ const rgb = parseCssColor(value);
2011
+ if (rgb) return relativeLuminance(rgb) > .58 ? "#0b0b0b" : "#ffffff";
2012
+ if (theme?.name?.includes("pixel-border")) return "#0b0b0b";
2013
+ if (looksDarkTheme(theme, background)) return "#0b0b0b";
2014
+ return "#ffffff";
2015
+ }
2016
+ function looksDarkTheme(theme, background) {
2017
+ const name = theme?.name?.toLowerCase() ?? "";
2018
+ if (name.includes("pixel-border") || name.includes("darksharp")) return true;
2019
+ const rgb = parseCssColor(background);
2020
+ if (!rgb) return false;
2021
+ return relativeLuminance(rgb) < .45;
2022
+ }
2023
+ function parseCssColor(value) {
2024
+ const normalized = value.trim().toLowerCase();
2025
+ if (normalized.startsWith("#")) return parseHexColor(normalized);
2026
+ const rgbMatch = normalized.match(/^rgba?\((.+)\)$/);
2027
+ if (rgbMatch) {
2028
+ const [r, g, b] = rgbMatch[1].split(",").slice(0, 3).map((part) => Number.parseFloat(part.trim()));
2029
+ if ([
2030
+ r,
2031
+ g,
2032
+ b
2033
+ ].every((channel) => Number.isFinite(channel))) return [
2034
+ r,
2035
+ g,
2036
+ b
2037
+ ];
2038
+ }
2039
+ const hslMatch = normalized.match(/^hsla?\((.+)\)$/);
2040
+ if (hslMatch) {
2041
+ const [h, s, l] = hslMatch[1].split(/[\s,/]+/).filter(Boolean).slice(0, 3);
2042
+ const hue = Number.parseFloat(h);
2043
+ const saturation = Number.parseFloat(s.replace("%", ""));
2044
+ const lightness = Number.parseFloat(l.replace("%", ""));
2045
+ if ([
2046
+ hue,
2047
+ saturation,
2048
+ lightness
2049
+ ].every((channel) => Number.isFinite(channel))) return hslToRgb(hue, saturation / 100, lightness / 100);
2050
+ }
2051
+ const oklchMatch = normalized.match(/^oklch\((.+)\)$/);
2052
+ if (oklchMatch) {
2053
+ const [l, c, h] = oklchMatch[1].split(/[\s/]+/).filter(Boolean);
2054
+ const lightness = Number.parseFloat(l);
2055
+ const chroma = Number.parseFloat(c);
2056
+ const hue = Number.parseFloat(h);
2057
+ if ([
2058
+ lightness,
2059
+ chroma,
2060
+ hue
2061
+ ].every((channel) => Number.isFinite(channel))) return oklchToRgb(lightness, chroma, hue);
2062
+ }
2063
+ }
2064
+ function oklchToRgb(l, c, h) {
2065
+ const hue = h * Math.PI / 180;
2066
+ const a = Math.cos(hue) * c;
2067
+ const b = Math.sin(hue) * c;
2068
+ const l_ = l + .3963377774 * a + .2158037573 * b;
2069
+ const m_ = l - .1055613458 * a - .0638541728 * b;
2070
+ const s_ = l - .0894841775 * a - 1.291485548 * b;
2071
+ const l3 = l_ ** 3;
2072
+ const m3 = m_ ** 3;
2073
+ const s3 = s_ ** 3;
2074
+ const linearR = 4.0767416621 * l3 - 3.3077115913 * m3 + .2309699292 * s3;
2075
+ const linearG = -1.2684380046 * l3 + 2.6097574011 * m3 - .3413193965 * s3;
2076
+ const linearB = -.0041960863 * l3 - .7034186147 * m3 + 1.707614701 * s3;
2077
+ return [
2078
+ srgbFromLinear(linearR),
2079
+ srgbFromLinear(linearG),
2080
+ srgbFromLinear(linearB)
2081
+ ];
2082
+ }
2083
+ function srgbFromLinear(value) {
2084
+ const normalized = value <= .0031308 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - .055;
2085
+ return Math.max(0, Math.min(255, Math.round(normalized * 255)));
2086
+ }
2087
+ function parseHexColor(value) {
2088
+ const raw = value.slice(1);
2089
+ if (raw.length === 3) return [
2090
+ Number.parseInt(raw[0] + raw[0], 16),
2091
+ Number.parseInt(raw[1] + raw[1], 16),
2092
+ Number.parseInt(raw[2] + raw[2], 16)
2093
+ ];
2094
+ if (raw.length === 6 || raw.length === 8) return [
2095
+ Number.parseInt(raw.slice(0, 2), 16),
2096
+ Number.parseInt(raw.slice(2, 4), 16),
2097
+ Number.parseInt(raw.slice(4, 6), 16)
2098
+ ];
2099
+ }
2100
+ function hslToRgb(h, s, l) {
2101
+ const hue = (h % 360 + 360) % 360 / 360;
2102
+ if (s === 0) {
2103
+ const gray = Math.round(l * 255);
2104
+ return [
2105
+ gray,
2106
+ gray,
2107
+ gray
2108
+ ];
2109
+ }
2110
+ const q = l < .5 ? l * (1 + s) : l + s - l * s;
2111
+ const p = 2 * l - q;
2112
+ const convert = (channel) => {
2113
+ let t = channel;
2114
+ if (t < 0) t += 1;
2115
+ if (t > 1) t -= 1;
2116
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
2117
+ if (t < 1 / 2) return q;
2118
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
2119
+ return p;
2120
+ };
2121
+ return [
2122
+ Math.round(convert(hue + 1 / 3) * 255),
2123
+ Math.round(convert(hue) * 255),
2124
+ Math.round(convert(hue - 1 / 3) * 255)
2125
+ ];
2126
+ }
2127
+ function relativeLuminance([r, g, b]) {
2128
+ const normalize = (channel) => {
2129
+ const value = channel / 255;
2130
+ return value <= .03928 ? value / 12.92 : ((value + .055) / 1.055) ** 2.4;
2131
+ };
2132
+ return .2126 * normalize(r) + .7152 * normalize(g) + .0722 * normalize(b);
2133
+ }
2134
+ function buildApiReferenceOpenApiDocument(config, options) {
2135
+ const apiReference = resolveApiReferenceConfig(config.apiReference);
2136
+ const selectedVersion = resolveApiReferenceVersion(apiReference, options.version);
2137
+ if (apiReference.versions.length > 0 && !selectedVersion) return buildUnavailableOpenApiDocument(config, `Unknown OpenAPI version \`${options.version}\`. Available versions: ${apiReference.versions.map((version) => version.id).join(", ")}.`);
2138
+ const specUrl = selectedVersion?.specUrl ?? apiReference.specUrl;
2139
+ const overlays = selectedVersion?.overlays ?? apiReference.overlays;
2140
+ if (specUrl) {
2141
+ if (isLocalSpecSource(specUrl)) try {
2142
+ if (overlays.some((overlay) => !isLocalSpecSource(overlay))) throw new Error("Remote OpenAPI overlays require the async API reference builder.");
2143
+ return normalizeConfiguredOpenApiDocument(readLocalOpenApiDocument(specUrl, options.rootDir, overlays), config, options.baseUrl, specUrl, selectedVersion);
2144
+ } catch (error) {
2145
+ return buildUnavailableOpenApiDocument(config, `Unable to load the configured OpenAPI document. ${error instanceof Error ? error.message : "Unknown error"}`);
2146
+ }
2147
+ return buildUnavailableOpenApiDocument(config, `Remote OpenAPI specs require the async API reference builder. Use the framework route helper or buildApiReferenceOpenApiDocumentAsync().`);
2148
+ }
2149
+ const routes = buildApiReferenceRoutes(config, options);
2150
+ return buildOpenApiDocumentFromRoutes(config, options.framework, routes, options.baseUrl);
2151
+ }
2152
+ async function buildApiReferenceOpenApiDocumentAsync(config, options) {
2153
+ try {
2154
+ return await loadApiReferenceOpenApiDocumentAsync(config, options);
2155
+ } catch (error) {
2156
+ return buildUnavailableOpenApiDocument(config, `Unable to load the configured OpenAPI document. ${error instanceof Error ? error.message : "Unknown error"}`);
2157
+ }
2158
+ }
2159
+ /**
2160
+ * Load the resolved OpenAPI document without replacing diagnostics with an unavailable schema.
2161
+ * This is used by CLI health checks that need the original parsing, reference, and overlay error.
2162
+ */
2163
+ async function loadApiReferenceOpenApiDocumentAsync(config, options) {
2164
+ const apiReference = resolveApiReferenceConfig(config.apiReference);
2165
+ const selectedVersion = resolveApiReferenceVersion(apiReference, options.version);
2166
+ if (apiReference.versions.length > 0 && !selectedVersion) throw new Error(`Unknown OpenAPI version \`${options.version}\`. Available versions: ${apiReference.versions.map((version) => version.id).join(", ")}.`);
2167
+ const specUrl = selectedVersion?.specUrl ?? apiReference.specUrl;
2168
+ const overlays = selectedVersion?.overlays ?? apiReference.overlays;
2169
+ if (!specUrl) return buildApiReferenceOpenApiDocument(config, options);
2170
+ return normalizeConfiguredOpenApiDocument(isLocalSpecSource(specUrl) ? await readLocalOpenApiDocumentAsync(specUrl, options.rootDir, overlays, options.baseUrl) : await fetchRemoteOpenApiDocument(specUrl, options.baseUrl, overlays, options.rootDir), config, options.baseUrl, specUrl, selectedVersion);
2171
+ }
2172
+ /**
2173
+ * Build deterministic, operation-level source pages from every configured OpenAPI version.
2174
+ *
2175
+ * These pages are framework-neutral projections used by documentation search and other
2176
+ * read-only knowledge surfaces. They never inherit MCP execution headers or credentials.
2177
+ */
2178
+ async function buildApiReferenceOperationPagesAsync(config, options) {
2179
+ const apiReference = resolveApiReferenceConfig(config.apiReference);
2180
+ if (!apiReference.enabled) return [];
2181
+ const versions = apiReference.versions.length > 0 ? apiReference.versions : [void 0];
2182
+ return (await Promise.all(versions.map(async (version) => {
2183
+ const model = buildNormalizedOpenApiModel(await buildApiReferenceOpenApiDocumentAsync(config, {
2184
+ ...options,
2185
+ version: version?.id
2186
+ }));
2187
+ const basePath = `/${apiReference.path}${version ? `/${encodeURIComponent(version.id)}` : ""}`;
2188
+ return model.operations.map((operation) => {
2189
+ const url = `${basePath}/operations/${encodeURIComponent(operation.slug)}`;
2190
+ const content = renderApiReferenceOperationMarkdown(operation, {
2191
+ apiTitle: model.title,
2192
+ apiVersion: version?.label ?? model.version,
2193
+ specificationVersion: model.specificationVersion
2194
+ });
2195
+ const description = operation.summary ?? operation.description ?? operation.selector;
2196
+ return {
2197
+ title: operation.title,
2198
+ url,
2199
+ canonicalUrl: url,
2200
+ content,
2201
+ description,
2202
+ rawContent: content,
2203
+ agentContent: content,
2204
+ agentRawContent: content,
2205
+ type: "api",
2206
+ ...options.locale ? { locale: options.locale } : {},
2207
+ ...version?.id || model.version ? { version: version?.id ?? model.version } : {},
2208
+ tags: Array.from(new Set([
2209
+ "openapi",
2210
+ operation.method.toLowerCase(),
2211
+ ...operation.tags
2212
+ ]))
2213
+ };
2214
+ });
2215
+ }))).flat();
2216
+ }
2217
+ function normalizeApiReferenceMarkdownPath(value) {
2218
+ const trimmed = value.trim().replace(/\.md$/i, "");
2219
+ if (!trimmed) return "/";
2220
+ return `/${trimmed.replace(/^\/+|\/+$/g, "")}`.replace(/\/{2,}/g, "/");
2221
+ }
2222
+ function isApiReferenceOperationPath(pathname, apiReferencePath) {
2223
+ const basePath = `/${normalizePathSegment(apiReferencePath)}`;
2224
+ if (!pathname.startsWith(`${basePath}/`)) return false;
2225
+ const segments = pathname.slice(basePath.length + 1).split("/");
2226
+ return segments.length === 2 && segments[0] === "operations" && Boolean(segments[1]) || segments.length === 3 && segments[1] === "operations" && Boolean(segments[0] && segments[2]);
2227
+ }
2228
+ function resolveApiReferenceOperationMarkdownPath(request, apiReferencePath, apiRoute = "/api/docs") {
2229
+ if (request.method !== "GET" && request.method !== "HEAD") return null;
2230
+ const url = new URL(request.url);
2231
+ const pathname = normalizeApiReferenceMarkdownPath(url.pathname);
2232
+ if (pathname === normalizeApiReferenceMarkdownPath(apiRoute) && url.searchParams.get("format")?.trim() === "markdown") {
2233
+ const requestedPath = normalizeApiReferenceMarkdownPath(url.searchParams.get("path")?.trim() ?? "");
2234
+ return isApiReferenceOperationPath(requestedPath, apiReferencePath) ? requestedPath : null;
2235
+ }
2236
+ if (url.pathname.toLowerCase().endsWith(".md")) return isApiReferenceOperationPath(pathname, apiReferencePath) ? pathname : null;
2237
+ return (acceptsDocsMarkdown(request) || hasDocsMarkdownSignatureAgent(request) || detectDocsMarkdownAgentRequest(request).detected) && isApiReferenceOperationPath(pathname, apiReferencePath) ? pathname : null;
2238
+ }
2239
+ /**
2240
+ * Resolve a public per-operation Markdown request using the same generated projection as search,
2241
+ * Ask AI, and MCP. Returns `null` when the request is not an API operation Markdown route.
2242
+ */
2243
+ async function createApiReferenceOperationMarkdownResponse(config, options) {
2244
+ const apiReference = resolveApiReferenceConfig(config.apiReference);
2245
+ if (!apiReference.enabled) return null;
2246
+ const requestedPath = resolveApiReferenceOperationMarkdownPath(options.request, apiReference.path, options.apiRoute);
2247
+ if (!requestedPath) return null;
2248
+ const requestUrl = new URL(options.request.url);
2249
+ const origin = options.origin ?? options.baseUrl ?? requestUrl.origin;
2250
+ const operationPages = options.operationPages ? await (typeof options.operationPages === "function" ? options.operationPages() : options.operationPages) : await buildApiReferenceOperationPagesAsync(config, {
2251
+ framework: options.framework,
2252
+ rootDir: options.rootDir,
2253
+ baseUrl: options.baseUrl ?? requestUrl.origin,
2254
+ locale: options.locale
2255
+ });
2256
+ const page = operationPages.find((candidate) => normalizeApiReferenceMarkdownPath(candidate.url) === requestedPath);
2257
+ const canonicalPath = page?.canonicalUrl ?? page?.url ?? requestedPath;
2258
+ const canonicalUrl = new URL(canonicalPath, origin).toString();
2259
+ const contentLocation = new URL(toDocsMarkdownUrl(page?.url ?? requestedPath), origin).toString();
2260
+ const relativeRequestedPath = requestedPath.slice(`/${apiReference.path}`.length).replace(/^\/+/, "");
2261
+ return createDocsMarkdownResponse({
2262
+ request: options.request,
2263
+ apiRoute: options.apiRoute,
2264
+ document: page ? renderDocsMarkdownDocument(page, {
2265
+ origin,
2266
+ sitemap: options.sitemap,
2267
+ okf: options.okf
2268
+ }) : null,
2269
+ entry: apiReference.path,
2270
+ requestedPath: relativeRequestedPath,
2271
+ origin,
2272
+ locale: options.locale,
2273
+ canonicalUrl,
2274
+ contentLocation,
2275
+ pages: [...options.pages ?? [], ...operationPages],
2276
+ sitemap: options.sitemap
2277
+ });
2278
+ }
2279
+ function renderApiReferenceOperationMarkdown(operation, metadata) {
2280
+ const lines = [
2281
+ `# ${operation.title}`,
2282
+ "",
2283
+ `\`${operation.method} ${operation.path}\``,
2284
+ "",
2285
+ `- Operation ID: \`${operation.operationId}\``,
2286
+ `- API: ${metadata.apiTitle}`,
2287
+ `- API version: ${metadata.apiVersion}`,
2288
+ `- OpenAPI: ${metadata.specificationVersion}`
2289
+ ];
2290
+ if (operation.tags.length > 0) lines.push(`- Tags: ${operation.tags.join(", ")}`);
2291
+ if (operation.deprecated) lines.push("- Deprecated: yes");
2292
+ if (operation.description && operation.description !== operation.title) lines.push("", operation.description);
2293
+ if (operation.servers.length > 0) lines.push("", "## Servers", "", ...operation.servers.map((server) => `- ${server}`));
2294
+ if (operation.parameters.length > 0) {
2295
+ lines.push("", "## Parameters", "");
2296
+ for (const parameter of operation.parameters) lines.push(renderApiReferenceParameter(parameter));
2297
+ }
2298
+ if (operation.requestBody) {
2299
+ lines.push("", "## Request body", "");
2300
+ if (operation.requestBody.description) lines.push(operation.requestBody.description, "");
2301
+ lines.push(`Required: ${operation.requestBody.required ? "yes" : "no"}`);
2302
+ appendApiReferenceMediaTypes(lines, operation.requestBody.content);
2303
+ }
2304
+ if (operation.responses.length > 0) {
2305
+ lines.push("", "## Responses", "");
2306
+ for (const response of operation.responses) {
2307
+ lines.push(`### ${response.status}`, "", response.description ?? "No response description.");
2308
+ appendApiReferenceMediaTypes(lines, response.content);
2309
+ lines.push("");
2310
+ }
2311
+ }
2312
+ if (operation.security.length > 0) {
2313
+ lines.push("", "## Security", "");
2314
+ for (const requirement of operation.security) {
2315
+ const schemes = Object.entries(requirement).map(([name, scopes]) => scopes.length > 0 ? `${name} (${scopes.join(", ")})` : name);
2316
+ lines.push(`- ${schemes.join(" and ")}`);
2317
+ }
2318
+ }
2319
+ return `${lines.join("\n").trim()}\n`;
2320
+ }
2321
+ function renderApiReferenceParameter(parameter) {
2322
+ const details = [parameter.in, parameter.required ? "required" : "optional"];
2323
+ const schema = renderApiReferenceSchema(parameter.schema);
2324
+ if (schema) details.push(schema);
2325
+ return `- \`${parameter.name}\` (${details.join(", ")})${parameter.description ? ` — ${parameter.description}` : ""}`;
2326
+ }
2327
+ function appendApiReferenceMediaTypes(lines, content) {
2328
+ for (const media of content) {
2329
+ lines.push("", `- Content type: \`${media.mediaType}\``);
2330
+ const schema = renderApiReferenceSchema(media.schema);
2331
+ if (schema) lines.push(` Schema: \`${schema}\``);
2332
+ for (const example of media.examples) {
2333
+ const value = renderApiReferenceSchema(example.value);
2334
+ lines.push(` Example ${example.name}: ${value ? `\`${value}\`` : "available"}`);
2335
+ }
2336
+ }
2337
+ }
2338
+ function renderApiReferenceSchema(value) {
2339
+ if (value === void 0) return void 0;
2340
+ if (typeof value === "string") return value;
2341
+ if (typeof value === "number" || typeof value === "boolean" || value === null) return String(value);
2342
+ try {
2343
+ const serialized = JSON.stringify(value);
2344
+ return serialized.length > 500 ? `${serialized.slice(0, 497)}...` : serialized;
2345
+ } catch {
2346
+ return;
2347
+ }
2348
+ }
2349
+ function buildOpenApiDocumentFromRoutes(config, framework, routes, baseUrl) {
2350
+ const tags = Array.from(new Set(routes.map((route) => route.tag))).map((name) => ({
2351
+ name,
2352
+ description: `${name} endpoints`
2353
+ }));
2354
+ return {
2355
+ openapi: "3.1.0",
2356
+ info: {
2357
+ title: "API Reference",
2358
+ description: config.metadata?.description ?? `Generated API reference for ${framework}.`,
2359
+ version: "0.0.0"
2360
+ },
2361
+ servers: [{ url: baseUrl ?? "/" }],
2362
+ tags,
2363
+ paths: buildOpenApiPaths(routes)
2364
+ };
2365
+ }
2366
+ function buildApiReferenceHtmlDocument(config, options) {
2367
+ return buildApiReferenceHtmlDocumentFromDocument(config, options, buildApiReferenceOpenApiDocument(config, options));
2368
+ }
2369
+ async function buildApiReferenceHtmlDocumentAsync(config, options) {
2370
+ return buildApiReferenceHtmlDocumentFromDocument(config, options, await buildApiReferenceOpenApiDocumentAsync(config, options));
2371
+ }
2372
+ function buildApiReferenceHtmlDocumentFromDocument(config, options, document) {
2373
+ const apiReference = resolveApiReferenceConfig(config.apiReference);
2374
+ const version = resolveApiReferenceVersion(apiReference, options.version);
2375
+ const basePath = `/${apiReference.path}${version ? `/${encodeURIComponent(version.id)}` : ""}`;
2376
+ if (resolveApiReferenceRenderer(config.apiReference, options.framework) === "farming-labs") {
2377
+ const model = buildNormalizedOpenApiModel(document);
2378
+ const info = document.info && typeof document.info === "object" && !Array.isArray(document.info) ? document.info : void 0;
2379
+ const title = options.title ?? model.title;
2380
+ const openApiUrl = version ? appendUrlSearchParam(DEFAULT_API_REFERENCE_OPENAPI_ROUTE, "version", version.id) : DEFAULT_API_REFERENCE_OPENAPI_ROUTE;
2381
+ return renderNativeApiReferenceHtml({
2382
+ model,
2383
+ pageTitle: buildApiReferencePageTitle(config, version ? `${title} – ${version.label}` : title),
2384
+ title: version ? `${title} – ${version.label}` : title,
2385
+ description: typeof info?.description === "string" && info.description.trim() ? info.description : config.metadata?.description,
2386
+ basePath,
2387
+ openApiUrl,
2388
+ activeVersion: version?.id,
2389
+ versions: apiReference.versions.map((entry) => ({
2390
+ id: entry.id,
2391
+ label: entry.label,
2392
+ href: `/${apiReference.path}/${encodeURIComponent(entry.id)}`,
2393
+ active: entry.id === version?.id
2394
+ })),
2395
+ theme: resolveTheme(config)
2396
+ });
2397
+ }
2398
+ const title = options.title ?? "API Reference";
2399
+ return getHtmlDocument({
2400
+ pageTitle: buildApiReferencePageTitle(config, title),
2401
+ title,
2402
+ content: () => document,
2403
+ theme: "deepSpace",
2404
+ layout: "modern",
2405
+ customCss: buildApiReferenceScalarCss(config),
2406
+ pathRouting: { basePath },
2407
+ showSidebar: true,
2408
+ defaultOpenFirstTag: true,
2409
+ tagsSorter: "alpha",
2410
+ operationsSorter: "alpha",
2411
+ operationTitleSource: "summary",
2412
+ defaultHttpClient: {
2413
+ targetKey: "shell",
2414
+ clientKey: "curl"
2415
+ },
2416
+ documentDownloadType: "json"
2417
+ });
2418
+ }
2419
+ async function fetchRemoteOpenApiDocument(specUrl, baseUrl, overlays = [], rootDir = process.cwd()) {
2420
+ let url;
2421
+ try {
2422
+ url = baseUrl ? new URL(specUrl, baseUrl) : new URL(specUrl);
2423
+ } catch {
2424
+ throw new Error(baseUrl ? "`apiReference.specUrl` must be an absolute URL or a request-relative path." : "`apiReference.specUrl` must be an absolute URL.");
2425
+ }
2426
+ const entry = await fetchOpenApiReferenceDocument(url, true);
2427
+ return resolveOpenApiReferences(await applyConfiguredOpenApiOverlaysAsync(entry.document, overlays, rootDir, baseUrl), {
2428
+ sourceUri: entry.uri,
2429
+ loadDocument: async (referenceUrl) => {
2430
+ if (referenceUrl.protocol !== "http:" && referenceUrl.protocol !== "https:") throw new Error(`Remote OpenAPI documents cannot load non-HTTP references: ${referenceUrl.href}`);
2431
+ return fetchOpenApiReferenceDocument(referenceUrl, false);
2432
+ }
2433
+ });
2434
+ }
2435
+ async function fetchOpenApiReferenceDocument(url, validateContract) {
2436
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("`apiReference.specUrl` must use HTTP or HTTPS for remote documents.");
2437
+ const abortController = new AbortController();
2438
+ const timeout = setTimeout(() => abortController.abort(), OPENAPI_SPEC_FETCH_TIMEOUT_MS);
2439
+ let response;
2440
+ let body;
2441
+ try {
2442
+ response = await fetch(url, {
2443
+ headers: { accept: "application/json, application/yaml, application/x-yaml, text/yaml, text/x-yaml" },
2444
+ redirect: "follow",
2445
+ signal: abortController.signal
2446
+ });
2447
+ if (!response.ok) throw new Error(`Received ${response.status} ${response.statusText}`.trim());
2448
+ if (response.url) {
2449
+ const responseUrl = new URL(response.url);
2450
+ if (responseUrl.protocol !== "http:" && responseUrl.protocol !== "https:") throw new Error("The remote OpenAPI endpoint redirected to a non-HTTP URL.");
2451
+ }
2452
+ body = await readOpenApiResponseBody(response);
2453
+ } catch (error) {
2454
+ if (abortController.signal.aborted) throw new Error(`The remote OpenAPI endpoint did not respond within ${OPENAPI_SPEC_FETCH_TIMEOUT_MS / 1e3} seconds.`);
2455
+ throw error;
2456
+ } finally {
2457
+ clearTimeout(timeout);
2458
+ }
2459
+ if (!body.trim()) throw new Error("The remote endpoint returned an empty response.");
2460
+ const sourceUri = response.url || url.href;
2461
+ return {
2462
+ uri: sourceUri,
2463
+ document: parseOpenApiDocument(body, sourceUri, response.headers.get("content-type"), "remote", validateContract)
2464
+ };
2465
+ }
2466
+ async function readOpenApiResponseBody(response) {
2467
+ const contentLength = response.headers.get("content-length");
2468
+ if (contentLength) {
2469
+ const declaredBytes = Number(contentLength);
2470
+ if (Number.isFinite(declaredBytes) && declaredBytes > OPENAPI_SPEC_MAX_BYTES) throw new Error(`The remote OpenAPI document exceeds the ${formatByteLimit(OPENAPI_SPEC_MAX_BYTES)} size limit.`);
2471
+ }
2472
+ if (!response.body) return "";
2473
+ const reader = response.body.getReader();
2474
+ const decoder = new TextDecoder();
2475
+ let receivedBytes = 0;
2476
+ let body = "";
2477
+ try {
2478
+ while (true) {
2479
+ const { done, value } = await reader.read();
2480
+ if (done) break;
2481
+ receivedBytes += value.byteLength;
2482
+ if (receivedBytes > OPENAPI_SPEC_MAX_BYTES) {
2483
+ await reader.cancel();
2484
+ throw new Error(`The remote OpenAPI document exceeds the ${formatByteLimit(OPENAPI_SPEC_MAX_BYTES)} size limit.`);
2485
+ }
2486
+ body += decoder.decode(value, { stream: true });
2487
+ }
2488
+ body += decoder.decode();
2489
+ return body;
2490
+ } finally {
2491
+ reader.releaseLock();
2492
+ }
2493
+ }
2494
+ function formatByteLimit(bytes) {
2495
+ return `${bytes / (1024 * 1024)} MiB`;
2496
+ }
2497
+ function readLocalOpenApiDocument(specUrl, rootDir = process.cwd(), overlays = []) {
2498
+ const filePath = resolveLocalOpenApiFilePath(specUrl, rootDir);
2499
+ const document = readOpenApiFileDocument(filePath, true);
2500
+ const trustedRoot = specUrl.startsWith("file:") ? void 0 : realpathSync(resolve(rootDir));
2501
+ return resolveOpenApiReferencesSync(applyConfiguredOpenApiOverlaysSync(document, overlays, rootDir), {
2502
+ sourceUri: pathToFileURL(filePath).href,
2503
+ loadDocument: (referenceUrl) => {
2504
+ if (referenceUrl.protocol === "http:" || referenceUrl.protocol === "https:") throw new Error("Remote OpenAPI references require the async API reference builder.");
2505
+ return readOpenApiFileReference(referenceUrl, trustedRoot);
2506
+ }
2507
+ });
2508
+ }
2509
+ async function readLocalOpenApiDocumentAsync(specUrl, rootDir = process.cwd(), overlays = [], baseUrl) {
2510
+ const filePath = resolveLocalOpenApiFilePath(specUrl, rootDir);
2511
+ const document = await readOpenApiFileDocumentAsync(filePath, true);
2512
+ const trustedRoot = specUrl.startsWith("file:") ? void 0 : realpathSync(resolve(rootDir));
2513
+ return resolveOpenApiReferences(await applyConfiguredOpenApiOverlaysAsync(document, overlays, rootDir, baseUrl), {
2514
+ sourceUri: pathToFileURL(filePath).href,
2515
+ loadDocument: async (referenceUrl, fromUrl) => {
2516
+ if (referenceUrl.protocol === "http:" || referenceUrl.protocol === "https:") return fetchOpenApiReferenceDocument(referenceUrl, false);
2517
+ if (fromUrl.protocol === "http:" || fromUrl.protocol === "https:") throw new Error(`Remote OpenAPI documents cannot load local file references: ${referenceUrl.href}`);
2518
+ return readOpenApiFileReferenceAsync(referenceUrl, trustedRoot);
2519
+ }
2520
+ });
2521
+ }
2522
+ function applyConfiguredOpenApiOverlaysSync(document, overlays, rootDir) {
2523
+ if (overlays.length === 0) return document;
2524
+ const result = applyOpenApiOverlayDocuments(document, overlays.map((source) => {
2525
+ if (!isLocalSpecSource(source)) throw new Error("Remote OpenAPI overlays require the async API reference builder.");
2526
+ return {
2527
+ source,
2528
+ document: readOpenApiFileDocument(resolveLocalOpenApiFilePath(source, rootDir), false)
2529
+ };
2530
+ }));
2531
+ assertValidOpenApiContract(result);
2532
+ return result;
2533
+ }
2534
+ async function applyConfiguredOpenApiOverlaysAsync(document, overlays, rootDir, baseUrl) {
2535
+ if (overlays.length === 0) return document;
2536
+ const result = applyOpenApiOverlayDocuments(document, await Promise.all(overlays.map(async (source) => {
2537
+ if (isLocalSpecSource(source)) return {
2538
+ source,
2539
+ document: await readOpenApiFileDocumentAsync(resolveLocalOpenApiFilePath(source, rootDir), false)
2540
+ };
2541
+ let url;
2542
+ try {
2543
+ url = baseUrl ? new URL(source, baseUrl) : new URL(source);
2544
+ } catch {
2545
+ throw new Error(baseUrl ? `OpenAPI overlay \`${source}\` must be an absolute URL or a request-relative path.` : `OpenAPI overlay \`${source}\` must be an absolute URL.`);
2546
+ }
2547
+ return {
2548
+ source,
2549
+ document: (await fetchOpenApiReferenceDocument(url, false)).document
2550
+ };
2551
+ })));
2552
+ assertValidOpenApiContract(result);
2553
+ return result;
2554
+ }
2555
+ function readOpenApiFileReference(url, trustedRoot) {
2556
+ if (url.protocol !== "file:") throw new Error(`Local OpenAPI documents cannot load this reference: ${url.href}`);
2557
+ const filePath = resolveReferencedOpenApiFilePath(url, trustedRoot);
2558
+ return {
2559
+ uri: pathToFileURL(filePath).href,
2560
+ document: readOpenApiFileDocument(filePath, false)
2561
+ };
2562
+ }
2563
+ async function readOpenApiFileReferenceAsync(url, trustedRoot) {
2564
+ if (url.protocol !== "file:") throw new Error(`Local OpenAPI documents cannot load this reference: ${url.href}`);
2565
+ const filePath = resolveReferencedOpenApiFilePath(url, trustedRoot);
2566
+ return {
2567
+ uri: pathToFileURL(filePath).href,
2568
+ document: await readOpenApiFileDocumentAsync(filePath, false)
2569
+ };
2570
+ }
2571
+ function readOpenApiFileDocument(filePath, validateContract) {
2572
+ assertOpenApiFileSize(filePath);
2573
+ const body = readFileSync(filePath, "utf-8");
2574
+ if (!body.trim()) throw new Error(`The local OpenAPI file is empty: ${filePath}`);
2575
+ return parseOpenApiDocument(body, filePath, void 0, "local", validateContract);
2576
+ }
2577
+ async function readOpenApiFileDocumentAsync(filePath, validateContract) {
2578
+ assertOpenApiFileSize(filePath);
2579
+ const body = await readFile(filePath, "utf-8");
2580
+ if (!body.trim()) throw new Error(`The local OpenAPI file is empty: ${filePath}`);
2581
+ return parseOpenApiDocument(body, filePath, void 0, "local", validateContract);
2582
+ }
2583
+ function assertOpenApiFileSize(filePath) {
2584
+ if (statSync(filePath).size > OPENAPI_SPEC_MAX_BYTES) throw new Error(`The local OpenAPI document exceeds the ${formatByteLimit(OPENAPI_SPEC_MAX_BYTES)} size limit: ${filePath}`);
2585
+ }
2586
+ function resolveLocalOpenApiFilePath(specUrl, rootDir) {
2587
+ if (specUrl.startsWith("file:")) return fileURLToPath(new URL(specUrl));
2588
+ const resolvedRoot = resolve(rootDir);
2589
+ return resolveContainedOpenApiFilePath(resolve(resolvedRoot, specUrl), realpathSync(resolvedRoot));
2590
+ }
2591
+ function resolveReferencedOpenApiFilePath(url, trustedRoot) {
2592
+ const filePath = fileURLToPath(url);
2593
+ return trustedRoot ? resolveContainedOpenApiFilePath(filePath, trustedRoot) : realpathSync(filePath);
2594
+ }
2595
+ function resolveContainedOpenApiFilePath(filePath, realRoot) {
2596
+ const realFilePath = realpathSync(filePath);
2597
+ const relativePath = relative(realRoot, realFilePath);
2598
+ if (relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) throw new Error("Project-relative OpenAPI files must stay inside the project root. Use an explicit `file:` URL for an external local file.");
2599
+ return realFilePath;
2600
+ }
2601
+ function parseOpenApiDocument(body, source, contentType, sourceKind, validateContract = true) {
2602
+ const extension = getOpenApiSourceExtension(source);
2603
+ const normalizedContentType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
2604
+ const format = normalizedContentType?.includes("yaml") || extension === ".yaml" || extension === ".yml" ? "yaml" : normalizedContentType?.includes("json") || extension === ".json" ? "json" : "auto";
2605
+ let parsed;
2606
+ try {
2607
+ if (format === "yaml") parsed = parse(body);
2608
+ else if (format === "json") parsed = JSON.parse(body);
2609
+ else try {
2610
+ parsed = JSON.parse(body);
2611
+ } catch {
2612
+ parsed = parse(body);
2613
+ }
2614
+ } catch {
2615
+ const location = sourceKind === "remote" ? "remote endpoint" : "local file";
2616
+ const verb = sourceKind === "remote" ? "return" : "contain";
2617
+ const expectedFormat = format === "auto" ? "JSON or YAML" : format.toUpperCase();
2618
+ throw new Error(`The ${location} did not ${verb} valid ${expectedFormat}.`);
2619
+ }
2620
+ if (validateContract && (!parsed || typeof parsed !== "object" || Array.isArray(parsed))) throw new Error("The configured source contains a value instead of an OpenAPI object.");
2621
+ if (validateContract) assertValidOpenApiContract(parsed);
2622
+ return parsed;
2623
+ }
2624
+ function getOpenApiSourceExtension(source) {
2625
+ try {
2626
+ return extname(new URL(source, "https://farming-labs.invalid").pathname).toLowerCase();
2627
+ } catch {
2628
+ return extname(source).toLowerCase();
2629
+ }
2630
+ }
2631
+ function normalizeConfiguredOpenApiDocument(document, config, baseUrl, specUrl, version) {
2632
+ const info = document.info && typeof document.info === "object" && !Array.isArray(document.info) ? document.info : {};
2633
+ const model = buildNormalizedOpenApiModel(document);
2634
+ const normalizedPaths = normalizeRemoteOpenApiPaths(document.paths);
2635
+ const normalizedTags = normalizeRemoteOpenApiTags(document.tags, normalizedPaths, model.tags);
2636
+ const normalizedServers = isRequestRelativeSpecUrl(specUrl) ? [{ url: baseUrl ?? "/" }] : document.servers;
2637
+ return {
2638
+ ...document,
2639
+ info: {
2640
+ title: "API Reference",
2641
+ version: "0.0.0",
2642
+ ...info,
2643
+ description: typeof info.description === "string" && info.description.trim() ? info.description : config.metadata?.description
2644
+ },
2645
+ servers: normalizedServers,
2646
+ paths: normalizedPaths,
2647
+ tags: normalizedTags,
2648
+ ...version ? { "x-farming-labs-api-version": {
2649
+ id: version.id,
2650
+ label: version.label,
2651
+ default: version.default
2652
+ } } : {}
2653
+ };
2654
+ }
2655
+ function normalizeRemoteOpenApiPaths(value) {
2656
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
2657
+ const paths = value;
2658
+ const normalized = {};
2659
+ for (const [routePath, pathItem] of Object.entries(paths)) {
2660
+ if (!pathItem || typeof pathItem !== "object" || Array.isArray(pathItem)) continue;
2661
+ const normalizedPathItem = { ...pathItem };
2662
+ for (const method of [
2663
+ "get",
2664
+ "post",
2665
+ "put",
2666
+ "patch",
2667
+ "delete",
2668
+ "options",
2669
+ "head",
2670
+ "trace"
2671
+ ]) {
2672
+ const operation = normalizedPathItem[method];
2673
+ if (!operation || typeof operation !== "object" || Array.isArray(operation)) continue;
2674
+ const operationRecord = operation;
2675
+ const tags = Array.isArray(operationRecord.tags) ? operationRecord.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : [];
2676
+ normalizedPathItem[method] = {
2677
+ ...operationRecord,
2678
+ tags: tags.length > 0 ? tags : [inferRemoteOpenApiTag(routePath)]
2679
+ };
2680
+ }
2681
+ normalized[routePath] = normalizedPathItem;
2682
+ }
2683
+ return normalized;
2684
+ }
2685
+ function inferRemoteOpenApiTag(routePath) {
2686
+ return humanizeSegment(routePath.split("/").filter(Boolean).find((value) => !value.startsWith("{")) ?? "general");
2687
+ }
2688
+ function normalizeRemoteOpenApiTags(value, paths, modelTags = []) {
2689
+ const existingTags = Array.isArray(value) ? value.filter((tag) => !!tag && typeof tag === "object") : [];
2690
+ const tagsByName = /* @__PURE__ */ new Map();
2691
+ for (const tag of existingTags) {
2692
+ const name = typeof tag.name === "string" && tag.name.trim() ? tag.name.trim() : void 0;
2693
+ if (!name) continue;
2694
+ tagsByName.set(name, tag);
2695
+ }
2696
+ for (const name of modelTags) if (!tagsByName.has(name)) tagsByName.set(name, {
2697
+ name,
2698
+ description: `${name} endpoints`
2699
+ });
2700
+ if (paths) for (const pathItem of Object.values(paths)) for (const method of [
2701
+ "get",
2702
+ "post",
2703
+ "put",
2704
+ "patch",
2705
+ "delete",
2706
+ "options",
2707
+ "head",
2708
+ "trace"
2709
+ ]) {
2710
+ const operation = pathItem[method];
2711
+ if (!operation || typeof operation !== "object" || Array.isArray(operation)) continue;
2712
+ const tags = Array.isArray(operation.tags) ? operation.tags : [];
2713
+ for (const tag of tags) {
2714
+ if (typeof tag !== "string" || !tag.trim()) continue;
2715
+ if (!tagsByName.has(tag)) tagsByName.set(tag, {
2716
+ name: tag,
2717
+ description: `${tag} endpoints`
2718
+ });
2719
+ }
2720
+ }
2721
+ if (tagsByName.size === 0) return [{
2722
+ name: "General",
2723
+ description: "General endpoints"
2724
+ }];
2725
+ return Array.from(tagsByName.values());
2726
+ }
2727
+ function buildUnavailableOpenApiDocument(config, description) {
2728
+ return {
2729
+ openapi: "3.1.0",
2730
+ info: {
2731
+ title: "API Reference",
2732
+ description,
2733
+ version: "0.0.0"
2734
+ },
2735
+ servers: [{ url: "/" }],
2736
+ tags: [{
2737
+ name: "Unavailable",
2738
+ description: config.metadata?.description ?? "OpenAPI spec could not be loaded."
2739
+ }],
2740
+ paths: {}
2741
+ };
2742
+ }
2743
+ function buildApiReferenceRoutes(config, options) {
2744
+ const apiReference = resolveApiReferenceConfig(config.apiReference);
2745
+ if (!apiReference.enabled) return [];
2746
+ const rootDir = options.rootDir ?? process.cwd();
2747
+ switch (options.framework) {
2748
+ case "next": return buildFileConventionRoutes({
2749
+ rootDir,
2750
+ sourceDir: resolveRootedDir(rootDir, apiReference.routeRoot, getNextAppDir(rootDir)),
2751
+ routePathBase: toRouteBase(apiReference.routeRoot, getNextAppDir(rootDir)),
2752
+ isRouteFile: (name) => NEXT_ROUTE_FILE_RE.test(name),
2753
+ toRouteSegments: (relativeFile) => relativeFile.split("/").slice(0, -1).filter(Boolean),
2754
+ exclude: apiReference.exclude
2755
+ });
2756
+ case "farmjs": return buildFileConventionRoutes({
2757
+ rootDir,
2758
+ sourceDir: resolveRootedDir(rootDir, apiReference.routeRoot, getFarmAppDir(rootDir)),
2759
+ routePathBase: toRouteBase(apiReference.routeRoot, getFarmAppDir(rootDir)),
2760
+ isRouteFile: (name) => NEXT_ROUTE_FILE_RE.test(name),
2761
+ toRouteSegments: (relativeFile) => relativeFile.split("/").slice(0, -1).filter(Boolean),
2762
+ exclude: apiReference.exclude
2763
+ });
2764
+ case "sveltekit": return buildFileConventionRoutes({
2765
+ rootDir,
2766
+ sourceDir: resolveRootedDir(rootDir, apiReference.routeRoot, "src/routes"),
2767
+ routePathBase: toRouteBase(apiReference.routeRoot, "src/routes"),
2768
+ isRouteFile: (name) => SVELTE_ROUTE_FILE_RE.test(name),
2769
+ toRouteSegments: (relativeFile) => relativeFile.split("/").slice(0, -1).filter(Boolean),
2770
+ exclude: apiReference.exclude
2771
+ });
2772
+ case "astro": return buildFileConventionRoutes({
2773
+ rootDir,
2774
+ sourceDir: resolveRootedDir(rootDir, apiReference.routeRoot, "src/pages"),
2775
+ routePathBase: toRouteBase(apiReference.routeRoot, "src/pages"),
2776
+ isRouteFile: (name) => ASTRO_ROUTE_FILE_RE.test(name),
2777
+ toRouteSegments: (relativeFile) => routeSegmentsFromEndpointFile(relativeFile),
2778
+ exclude: apiReference.exclude
2779
+ });
2780
+ case "nuxt": return buildFileConventionRoutes({
2781
+ rootDir,
2782
+ sourceDir: resolveRootedDir(rootDir, apiReference.routeRoot, "server"),
2783
+ routePathBase: toRouteBase(apiReference.routeRoot, "server"),
2784
+ isRouteFile: (name) => NUXT_ROUTE_FILE_RE.test(name),
2785
+ toRouteSegments: (relativeFile) => routeSegmentsFromEndpointFile(stripNuxtMethodSuffix(relativeFile)),
2786
+ exclude: apiReference.exclude,
2787
+ getMethods: (source, file) => extractNuxtMethods(source, file)
2788
+ });
2789
+ case "tanstack-start": return buildTanstackRoutes(rootDir, apiReference);
2790
+ }
2791
+ }
2792
+ function getFarmAppDir(rootDir) {
2793
+ return existsSync(join(rootDir, "src", "app")) ? "src/app" : "app";
2794
+ }
2795
+ function buildFileConventionRoutes({ rootDir, sourceDir, routePathBase, isRouteFile, toRouteSegments, exclude, getMethods = extractMethods }) {
2796
+ const files = scanRouteFiles(sourceDir, isRouteFile);
2797
+ const routes = [];
2798
+ for (const file of files) {
2799
+ const source = readFileSync(file, "utf-8");
2800
+ const methods = getMethods(source, file);
2801
+ if (methods.length === 0) continue;
2802
+ const relativeFile = relative(sourceDir, file).replace(/\\/g, "/");
2803
+ const routeSegments = toRouteSegments(relativeFile);
2804
+ const routePath = buildRoutePath(routePathBase, routeSegments);
2805
+ if (shouldExcludeRoute(exclude, routePath, relativeFile, routeSegments.join("/"))) continue;
2806
+ routes.push(createApiReferenceRoute({
2807
+ rootDir,
2808
+ file,
2809
+ source,
2810
+ methods,
2811
+ routePath
2812
+ }));
2813
+ }
2814
+ return routes.sort((a, b) => a.routePath.localeCompare(b.routePath));
2815
+ }
2816
+ function buildTanstackRoutes(rootDir, apiReference) {
2817
+ const routesDir = join(rootDir, "src", "routes");
2818
+ const files = scanRouteFiles(routesDir, (name) => TANSTACK_ROUTE_FILE_RE.test(name));
2819
+ const routeBase = `/${normalizePathSegment(apiReference.routeRoot)}`;
2820
+ const routes = [];
2821
+ for (const file of files) {
2822
+ const source = readFileSync(file, "utf-8");
2823
+ if (!source.includes("createFileRoute(") || !source.includes("handlers")) continue;
2824
+ const pathMatch = source.match(/createFileRoute\(\s*["'`]([^"'`]+)["'`]\s*\)/);
2825
+ if (!pathMatch) continue;
2826
+ const routePath = normalizeTanstackRoutePath(pathMatch[1]);
2827
+ if (!routePath.startsWith(routeBase)) continue;
2828
+ const methods = extractTanstackMethods(source);
2829
+ if (methods.length === 0) continue;
2830
+ const relativeFile = relative(routesDir, file).replace(/\\/g, "/");
2831
+ if (shouldExcludeRoute(apiReference.exclude, routePath, relativeFile, relativeFile)) continue;
2832
+ routes.push(createApiReferenceRoute({
2833
+ rootDir,
2834
+ file,
2835
+ source,
2836
+ methods,
2837
+ routePath
2838
+ }));
2839
+ }
2840
+ return routes.sort((a, b) => a.routePath.localeCompare(b.routePath));
2841
+ }
2842
+ function createApiReferenceRoute({ rootDir, file, source, methods, routePath }) {
2843
+ const docBlock = extractDocBlock(source);
2844
+ const pathSegments = routePath.split("/").filter(Boolean);
2845
+ const titleSegment = pathSegments[pathSegments.length - 1] ?? "overview";
2846
+ const tagSegment = pathSegments[0] ?? "general";
2847
+ const title = humanizeSegment(titleSegment);
2848
+ return {
2849
+ title,
2850
+ summary: docBlock.summary ?? `${title} endpoint`,
2851
+ description: docBlock.description,
2852
+ routePath,
2853
+ sourceFile: relative(rootDir, file).replace(/\\/g, "/"),
2854
+ methods,
2855
+ tag: humanizeSegment(tagSegment),
2856
+ parameters: buildPathParameters(routePath)
2857
+ };
2858
+ }
2859
+ function buildPathParameters(routePath) {
2860
+ const parameters = [];
2861
+ for (const segment of routePath.split("/")) {
2862
+ const match = segment.match(/^\{(.+)\}$/);
2863
+ if (!match) continue;
2864
+ parameters.push({
2865
+ name: match[1],
2866
+ in: "path",
2867
+ required: true,
2868
+ description: `${humanizeSegment(match[1])} path parameter.`,
2869
+ schema: { type: "string" }
2870
+ });
2871
+ }
2872
+ return parameters;
2873
+ }
2874
+ function buildOpenApiPaths(routes) {
2875
+ const paths = {};
2876
+ for (const route of routes) {
2877
+ const pathItem = {};
2878
+ for (const method of route.methods) pathItem[method.toLowerCase()] = {
2879
+ tags: [route.tag],
2880
+ summary: route.summary,
2881
+ description: route.description ?? route.summary,
2882
+ operationId: createOperationId(route, method),
2883
+ ...route.parameters.length > 0 ? { parameters: route.parameters } : {},
2884
+ ...buildRequestBody(method) ? { requestBody: buildRequestBody(method) } : {},
2885
+ responses: buildResponses(method),
2886
+ "x-farming-labs-source": route.sourceFile
2887
+ };
2888
+ paths[route.routePath] = pathItem;
2889
+ }
2890
+ return paths;
2891
+ }
2892
+ function buildRequestBody(method) {
2893
+ if (![
2894
+ "POST",
2895
+ "PUT",
2896
+ "PATCH"
2897
+ ].includes(method)) return void 0;
2898
+ return {
2899
+ required: method === "POST",
2900
+ content: { "application/json": {
2901
+ schema: {
2902
+ type: "object",
2903
+ additionalProperties: true
2904
+ },
2905
+ example: { example: true }
2906
+ } }
2907
+ };
2908
+ }
2909
+ function buildResponses(method) {
2910
+ return { "200": {
2911
+ description: method === "DELETE" ? "Resource removed successfully." : "Request completed successfully.",
2912
+ content: { "application/json": {
2913
+ schema: {
2914
+ type: "object",
2915
+ additionalProperties: true
2916
+ },
2917
+ example: { ok: true }
2918
+ } }
2919
+ } };
2920
+ }
2921
+ function createOperationId(route, method) {
2922
+ return `${method.toLowerCase()}_${route.routePath.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
2923
+ }
2924
+ function resolveTheme(config) {
2925
+ return config.theme;
2926
+ }
2927
+ function normalizeApiReferenceExcludes(values) {
2928
+ return (values ?? []).map(normalizeExcludeMatcher).filter(Boolean);
2929
+ }
2930
+ function normalizeExcludeMatcher(value) {
2931
+ return value.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "").replace(/\.(ts|tsx|js|jsx|mjs|mts)$/i, "").replace(/\/route$/i, "").replace(/\/\+server$/i, "").replace(/\/index$/i, "").replace(/\.(get|post|put|patch|delete|options|head)$/i, "");
2932
+ }
2933
+ function shouldExcludeRoute(excludes, routePath, relativeFile, relativeDir) {
2934
+ if (excludes.length === 0) return false;
2935
+ const candidates = new Set([
2936
+ normalizeExcludeMatcher(routePath),
2937
+ normalizeExcludeMatcher(routePath.replace(/^\/+/, "")),
2938
+ normalizeExcludeMatcher(relativeFile),
2939
+ normalizeExcludeMatcher(relativeDir)
2940
+ ]);
2941
+ return excludes.some((entry) => candidates.has(entry));
2942
+ }
2943
+ function scanRouteFiles(dir, isRouteFile) {
2944
+ if (!existsSync(dir)) return [];
2945
+ const results = [];
2946
+ for (const name of readdirSync(dir)) {
2947
+ const full = join(dir, name);
2948
+ if (statSync(full).isDirectory()) {
2949
+ results.push(...scanRouteFiles(full, isRouteFile));
2950
+ continue;
2951
+ }
2952
+ if (isRouteFile(name)) results.push(full);
2953
+ }
2954
+ return results;
2955
+ }
2956
+ function resolveRootedDir(rootDir, routeRoot, defaultRoot) {
2957
+ const normalized = normalizePathSegment(routeRoot) || "api";
2958
+ if (normalized === defaultRoot || normalized.startsWith(`${defaultRoot}/`)) return join(rootDir, ...normalized.split("/"));
2959
+ return join(rootDir, ...defaultRoot.split("/"), ...normalized.split("/"));
2960
+ }
2961
+ function toRouteBase(routeRoot, defaultRoot) {
2962
+ const normalized = normalizePathSegment(routeRoot) || "api";
2963
+ return `/${normalizePathSegment(normalized === defaultRoot || normalized.startsWith(`${defaultRoot}/`) ? normalized.slice(defaultRoot.length).replace(/^\/+/, "") : normalized)}`;
2964
+ }
2965
+ function getNextAppDir(rootDir) {
2966
+ if (existsSync(join(rootDir, "src", "app"))) return "src/app";
2967
+ return "app";
2968
+ }
2969
+ function routeSegmentsFromEndpointFile(relativeFile) {
2970
+ const segments = relativeFile.split("/");
2971
+ const name = (segments.pop() ?? "").replace(/\.(ts|js|mts|mjs)$/i, "");
2972
+ if (name !== "index") segments.push(name);
2973
+ return segments.filter(Boolean);
2974
+ }
2975
+ function stripNuxtMethodSuffix(relativeFile) {
2976
+ return relativeFile.replace(/\.(get|post|put|patch|delete|options|head)(?=\.(ts|js|mts|mjs)$)/i, "");
2977
+ }
2978
+ function buildRoutePath(basePath, rawSegments) {
2979
+ const segments = rawSegments.filter(Boolean).map((segment) => endpointSegmentFromConvention(segment)).join("/");
2980
+ const path = [normalizePathSegment(basePath), segments].filter(Boolean).join("/");
2981
+ return path ? `/${path}` : "/";
2982
+ }
2983
+ function endpointSegmentFromConvention(value) {
2984
+ if (value.startsWith("[[...") && value.endsWith("]]")) return `{${value.slice(5, -2)}}`;
2985
+ if (value.startsWith("[...") && value.endsWith("]")) return `{${value.slice(4, -1)}}`;
2986
+ if (value.startsWith("[") && value.endsWith("]")) return `{${value.slice(1, -1)}}`;
2987
+ return value;
2988
+ }
2989
+ function normalizeTanstackRoutePath(value) {
2990
+ return `/${value.replace(/^\/+|\/+$/g, "").split("/").map((segment) => segment.startsWith("$") ? `{${segment.slice(1)}}` : segment).filter(Boolean).join("/")}`;
2991
+ }
2992
+ function extractDocBlock(source) {
2993
+ const match = source.match(/\/\*\*([\s\S]*?)\*\//);
2994
+ if (!match) return {};
2995
+ const lines = match[1].split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim()).filter(Boolean).filter((line) => !line.startsWith("@"));
2996
+ if (lines.length === 0) return {};
2997
+ return {
2998
+ summary: lines[0],
2999
+ description: lines.slice(1).join(" ")
3000
+ };
3001
+ }
3002
+ function extractMethods(source) {
3003
+ const methods = /* @__PURE__ */ new Set();
3004
+ for (const match of source.matchAll(METHOD_RE)) {
3005
+ if (match[1] === "ALL") {
3006
+ METHOD_NAMES.forEach((method) => methods.add(method));
3007
+ continue;
3008
+ }
3009
+ methods.add(match[1]);
3010
+ }
3011
+ return Array.from(methods);
3012
+ }
3013
+ function extractNuxtMethods(source, file) {
3014
+ const methods = extractMethods(source);
3015
+ if (methods.length > 0) return methods;
3016
+ const suffix = basename(file).match(/\.(get|post|put|patch|delete|options|head)\.(ts|js|mts|mjs)$/i);
3017
+ if (suffix) return [suffix[1].toUpperCase()];
3018
+ if (/defineEventHandler|eventHandler/.test(source)) return ["GET"];
3019
+ return [];
3020
+ }
3021
+ function extractTanstackMethods(source) {
3022
+ const methods = /* @__PURE__ */ new Set();
3023
+ const handlersMatch = source.match(/handlers\s*:\s*\{([\s\S]*?)\}/m);
3024
+ if (!handlersMatch) return [];
3025
+ for (const match of handlersMatch[1].matchAll(/\b(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*:/g)) methods.add(match[1]);
3026
+ return Array.from(methods);
3027
+ }
3028
+ function humanizeSegment(value) {
3029
+ return value.replace(/^\{/, "").replace(/\}$/, "").replace(/^\[\[?\.{3}/, "").replace(/^\[/, "").replace(/\]\]?$/, "").replace(/^\$/, "").replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
3030
+ }
3031
+
3032
+ //#endregion
3033
+ export { validateOpenApiContract as _, buildApiReferenceOpenApiDocumentAsync as a, buildApiReferenceScalarCss as c, loadApiReferenceOpenApiDocumentAsync as d, resolveApiReferenceConfig as f, resolveApiReferenceVersionFromPathname as g, resolveApiReferenceVersion as h, buildApiReferenceOpenApiDocument as i, createApiReferenceOperationMarkdownResponse as l, resolveApiReferenceRenderer as m, buildApiReferenceHtmlDocument as n, buildApiReferenceOperationPagesAsync as o, resolveApiReferenceOpenApiDiscovery as p, buildApiReferenceHtmlDocumentAsync as r, buildApiReferencePageTitle as s, DEFAULT_API_REFERENCE_OPENAPI_ROUTE as t, isApiReferenceOpenApiRequest as u };