radiant-docs 0.1.66 → 0.1.67
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.
- package/package.json +1 -1
- package/template/astro.config.mjs +21 -1
- package/template/package-lock.json +4 -4
- package/template/package.json +1 -1
- package/template/src/components/MdxPage.astro +12 -2
- package/template/src/components/OpenApiPage.astro +13 -3
- package/template/src/components/PageAiActions.astro +715 -0
- package/template/src/layouts/Layout.astro +6 -1
- package/template/src/lib/ai-artifacts.ts +295 -13
- package/template/src/pages/{[...slug]/index.md.ts → [...slug].md.ts} +1 -1
package/package.json
CHANGED
|
@@ -311,6 +311,24 @@ function resolveDocsSiteConfig() {
|
|
|
311
311
|
const docsSiteConfig = resolveDocsSiteConfig();
|
|
312
312
|
globalThis.__RADIANT_DOCS_BASE_PATH__ = docsSiteConfig.base;
|
|
313
313
|
|
|
314
|
+
function isInternalDocsRoutePathname(pathname) {
|
|
315
|
+
const basePath = docsSiteConfig.base === "/" ? "" : docsSiteConfig.base;
|
|
316
|
+
const relativePath =
|
|
317
|
+
basePath && pathname.startsWith(`${basePath}/`)
|
|
318
|
+
? pathname.slice(basePath.length)
|
|
319
|
+
: pathname;
|
|
320
|
+
|
|
321
|
+
return relativePath === "/-" || relativePath.startsWith("/-/");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function shouldIncludeSitemapPage(page) {
|
|
325
|
+
try {
|
|
326
|
+
return !isInternalDocsRoutePathname(new URL(page).pathname);
|
|
327
|
+
} catch {
|
|
328
|
+
return !page.startsWith("/-/");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
314
332
|
const markdownProcessor = unified({
|
|
315
333
|
remarkPlugins: [
|
|
316
334
|
remarkGfm,
|
|
@@ -387,7 +405,9 @@ export default defineConfig({
|
|
|
387
405
|
assetsPrefix: configuredAssetsPrefix,
|
|
388
406
|
},
|
|
389
407
|
integrations: [
|
|
390
|
-
sitemap(
|
|
408
|
+
sitemap({
|
|
409
|
+
filter: shouldIncludeSitemapPage,
|
|
410
|
+
}),
|
|
391
411
|
preact({
|
|
392
412
|
compat: true,
|
|
393
413
|
}),
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@fontsource-variable/source-serif-4": "^5.2.9",
|
|
29
29
|
"@iconify-json/fluent": "^1.2.47",
|
|
30
30
|
"@iconify-json/lucide": "^1.2.79",
|
|
31
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
31
|
+
"@iconify-json/simple-icons": "^1.2.87",
|
|
32
32
|
"@iconify/react": "^6.0.2",
|
|
33
33
|
"@paper-design/shaders": "^0.0.76",
|
|
34
34
|
"@preact/preset-vite": "^2.10.3",
|
|
@@ -2343,9 +2343,9 @@
|
|
|
2343
2343
|
}
|
|
2344
2344
|
},
|
|
2345
2345
|
"node_modules/@iconify-json/simple-icons": {
|
|
2346
|
-
"version": "1.2.
|
|
2347
|
-
"resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.
|
|
2348
|
-
"integrity": "sha512-
|
|
2346
|
+
"version": "1.2.87",
|
|
2347
|
+
"resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.87.tgz",
|
|
2348
|
+
"integrity": "sha512-8YciStObhSji3OZFmWAWK6kBujyqO5bLCxeDwLxf3CR3F4PVelq7keC2LBvgTqviWzSTysj5/g4PCFLiAMVGsw==",
|
|
2349
2349
|
"license": "CC0-1.0",
|
|
2350
2350
|
"dependencies": {
|
|
2351
2351
|
"@iconify/types": "*"
|
package/template/package.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@fontsource-variable/source-serif-4": "^5.2.9",
|
|
28
28
|
"@iconify-json/fluent": "^1.2.47",
|
|
29
29
|
"@iconify-json/lucide": "^1.2.79",
|
|
30
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
30
|
+
"@iconify-json/simple-icons": "^1.2.87",
|
|
31
31
|
"@iconify/react": "^6.0.2",
|
|
32
32
|
"@paper-design/shaders": "^0.0.76",
|
|
33
33
|
"@preact/preset-vite": "^2.10.3",
|
|
@@ -19,6 +19,7 @@ import ComponentPreview from "./user/ComponentPreview.astro";
|
|
|
19
19
|
import ComponentPreviewBlock from "./user/ComponentPreviewBlock.astro";
|
|
20
20
|
import type { MdxRoute, Route } from "../lib/routes";
|
|
21
21
|
import PagePagination from "./PagePagination.astro";
|
|
22
|
+
import PageAiActions from "./PageAiActions.astro";
|
|
22
23
|
import { PREVIEW_HEADING_ID_PREFIX } from "../lib/mdx/rehype-prefix-preview-heading-ids";
|
|
23
24
|
|
|
24
25
|
interface Props {
|
|
@@ -67,10 +68,19 @@ const tocHeadings = headings.filter(
|
|
|
67
68
|
<Layout pageTitle={title} pageDescription={description}>
|
|
68
69
|
<div class="flex w-full min-w-0 justify-between gap-x-10">
|
|
69
70
|
<div class="mx-auto max-w-3xl w-full">
|
|
70
|
-
<header
|
|
71
|
-
|
|
71
|
+
<header
|
|
72
|
+
class="mb-8 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between"
|
|
73
|
+
>
|
|
74
|
+
<h1
|
|
75
|
+
class="rd-document-heading min-w-0 text-4xl font-semibold tracking-tight"
|
|
76
|
+
>
|
|
72
77
|
{title}
|
|
73
78
|
</h1>
|
|
79
|
+
<PageAiActions
|
|
80
|
+
title={title}
|
|
81
|
+
routeSlug={route.slug}
|
|
82
|
+
description={description}
|
|
83
|
+
/>
|
|
74
84
|
</header>
|
|
75
85
|
<article class="prose-rules">
|
|
76
86
|
<Content components={components} />
|
|
@@ -10,6 +10,7 @@ import ResponseFieldTree from "./endpoint/ResponseFieldTree.astro";
|
|
|
10
10
|
import PlaygroundBar from "./endpoint/PlaygroundBar.astro";
|
|
11
11
|
import PlaygroundForm from "./endpoint/PlaygroundForm.astro";
|
|
12
12
|
import PlaygroundButton from "./endpoint/PlaygroundButton.astro";
|
|
13
|
+
import PageAiActions from "./PageAiActions.astro";
|
|
13
14
|
import { getConfig } from "../lib/validation";
|
|
14
15
|
import {
|
|
15
16
|
getOpenApiOperationDoc,
|
|
@@ -59,11 +60,20 @@ const snippetStickyClass = hasTopbarNavigationTabs
|
|
|
59
60
|
---
|
|
60
61
|
|
|
61
62
|
<Layout pageTitle={title}>
|
|
62
|
-
<article>
|
|
63
|
-
<header
|
|
64
|
-
|
|
63
|
+
<article class="mx-auto w-full max-w-7xl">
|
|
64
|
+
<header
|
|
65
|
+
class="mb-8 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between"
|
|
66
|
+
>
|
|
67
|
+
<h1
|
|
68
|
+
class="rd-document-heading min-w-0 text-4xl font-semibold tracking-tight"
|
|
69
|
+
>
|
|
65
70
|
{title}
|
|
66
71
|
</h1>
|
|
72
|
+
<PageAiActions
|
|
73
|
+
title={title}
|
|
74
|
+
routeSlug={route.slug}
|
|
75
|
+
description={description}
|
|
76
|
+
/>
|
|
67
77
|
</header>
|
|
68
78
|
<div class="flex flex-row-reverse justify-between gap-6 w-full">
|
|
69
79
|
<aside class="flex-1 min-w-0 hidden xl:block">
|
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import Icon from "./ui/Icon.astro";
|
|
4
|
+
import { prependBasePath } from "../lib/base-path";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
title: string;
|
|
8
|
+
routeSlug: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type MenuItem = {
|
|
13
|
+
type: "button" | "link";
|
|
14
|
+
icon: string;
|
|
15
|
+
title: string;
|
|
16
|
+
description: string;
|
|
17
|
+
href?: string;
|
|
18
|
+
target?: string;
|
|
19
|
+
rel?: string;
|
|
20
|
+
external?: boolean;
|
|
21
|
+
copyMarkdown?: boolean;
|
|
22
|
+
copyMcp?: string;
|
|
23
|
+
hasActionLabel?: boolean;
|
|
24
|
+
openApp?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function getMarkdownPathForSlug(routeSlug: string): string {
|
|
28
|
+
const normalizedSlug = routeSlug.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
29
|
+
return normalizedSlug ? `/${normalizedSlug}.md` : "/index.md";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getPagePathForSlug(routeSlug: string): string {
|
|
33
|
+
const normalizedSlug = routeSlug.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
34
|
+
return normalizedSlug ? `/${normalizedSlug}` : "/";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getInstallNameFromUrl(url: string): string {
|
|
38
|
+
try {
|
|
39
|
+
const hostname = new URL(url).hostname;
|
|
40
|
+
const normalized = hostname
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
43
|
+
.replace(/^-+|-+$/g, "")
|
|
44
|
+
.slice(0, 48);
|
|
45
|
+
|
|
46
|
+
return normalized || "docs";
|
|
47
|
+
} catch {
|
|
48
|
+
return "docs";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getCursorInstallNameFromUrl(url: string): string {
|
|
53
|
+
try {
|
|
54
|
+
return new URL(url).hostname.toLowerCase() || "docs";
|
|
55
|
+
} catch {
|
|
56
|
+
return "docs";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function toBase64(value: string): string {
|
|
61
|
+
return Buffer.from(value, "utf8").toString("base64");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildAssistantPrompt(pageUrl: string): string {
|
|
65
|
+
return `Read from ${pageUrl} so I can ask questions about it.`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { routeSlug } = Astro.props;
|
|
69
|
+
const parsedOrgTier = Number.parseInt(
|
|
70
|
+
(import.meta.env.ORG_TIER ?? "1").toString(),
|
|
71
|
+
10,
|
|
72
|
+
);
|
|
73
|
+
const orgTier =
|
|
74
|
+
Number.isFinite(parsedOrgTier) && parsedOrgTier > 0 ? parsedOrgTier : 1;
|
|
75
|
+
const isDev = import.meta.env.DEV;
|
|
76
|
+
const mcpEnabled = isDev || orgTier >= 3;
|
|
77
|
+
const markdownPath = prependBasePath(getMarkdownPathForSlug(routeSlug));
|
|
78
|
+
const pagePath = prependBasePath(getPagePathForSlug(routeSlug));
|
|
79
|
+
const pageUrl = new URL(pagePath, Astro.site ?? Astro.url).toString();
|
|
80
|
+
const mcpPath = prependBasePath("/_mcp/server");
|
|
81
|
+
const mcpUrl = new URL(mcpPath, Astro.site ?? Astro.url).toString();
|
|
82
|
+
const installName = getInstallNameFromUrl(mcpUrl);
|
|
83
|
+
const cursorInstallName = getCursorInstallNameFromUrl(mcpUrl);
|
|
84
|
+
const cursorServerConfig = {
|
|
85
|
+
name: cursorInstallName,
|
|
86
|
+
url: mcpUrl,
|
|
87
|
+
};
|
|
88
|
+
const cursorInstallUrl = `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(cursorInstallName)}&config=${encodeURIComponent(toBase64(JSON.stringify(cursorServerConfig)))}`;
|
|
89
|
+
const prompt = buildAssistantPrompt(pageUrl);
|
|
90
|
+
const chatGptUrl = `https://chatgpt.com/?q=${encodeURIComponent(prompt)}`;
|
|
91
|
+
const claudeUrl = `https://claude.ai/new?q=${encodeURIComponent(prompt)}`;
|
|
92
|
+
|
|
93
|
+
const menuItemClass =
|
|
94
|
+
"group cursor-pointer flex w-full items-center gap-2.5 rounded-lg p-1.5 text-left transition-colors hover:bg-neutral-100/80 focus-visible:outline focus-visible:outline-2 focus-visible:outline-neutral-900 dark:hover:bg-neutral-900/80 dark:focus-visible:outline-white";
|
|
95
|
+
const menuIconClass =
|
|
96
|
+
"inline-flex size-[32px] shrink-0 items-center justify-center rounded-sm border border-neutral-900/10 text-neutral-500 dark:border-white/6 dark:text-neutral-400";
|
|
97
|
+
const menuTitleClass =
|
|
98
|
+
"block text-sm font-medium leading-5 text-neutral-900 dark:text-white";
|
|
99
|
+
const menuDescriptionClass =
|
|
100
|
+
"block text-xs leading-4 text-neutral-500 dark:text-neutral-400";
|
|
101
|
+
|
|
102
|
+
const menuItems: MenuItem[] = [
|
|
103
|
+
{
|
|
104
|
+
type: "button",
|
|
105
|
+
icon: "lucide:copy",
|
|
106
|
+
title: "Copy page",
|
|
107
|
+
description: "Copy page as Markdown for LLMs",
|
|
108
|
+
copyMarkdown: true,
|
|
109
|
+
hasActionLabel: true,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
type: "link",
|
|
113
|
+
icon: "simple-icons:markdown",
|
|
114
|
+
title: "View as Markdown",
|
|
115
|
+
description: "Open the Markdown version of this page",
|
|
116
|
+
href: markdownPath,
|
|
117
|
+
target: "_blank",
|
|
118
|
+
rel: "noopener noreferrer",
|
|
119
|
+
external: true,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: "link",
|
|
123
|
+
icon: "simple-icons:openai",
|
|
124
|
+
title: "Open in ChatGPT",
|
|
125
|
+
description: "Ask questions about this page",
|
|
126
|
+
href: chatGptUrl,
|
|
127
|
+
target: "_blank",
|
|
128
|
+
rel: "noopener noreferrer",
|
|
129
|
+
external: true,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: "link",
|
|
133
|
+
icon: "simple-icons:claude",
|
|
134
|
+
title: "Open in Claude",
|
|
135
|
+
description: "Ask questions about this page",
|
|
136
|
+
href: claudeUrl,
|
|
137
|
+
target: "_blank",
|
|
138
|
+
rel: "noopener noreferrer",
|
|
139
|
+
external: true,
|
|
140
|
+
},
|
|
141
|
+
...(mcpEnabled
|
|
142
|
+
? [
|
|
143
|
+
{
|
|
144
|
+
type: "button",
|
|
145
|
+
icon: "lucide:terminal",
|
|
146
|
+
title: "Copy MCP install command",
|
|
147
|
+
description: "Copy command to install MCP server",
|
|
148
|
+
copyMcp: "install",
|
|
149
|
+
hasActionLabel: true,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: "link",
|
|
153
|
+
icon: "simple-icons:cursor",
|
|
154
|
+
title: "Connect to Cursor",
|
|
155
|
+
description: "Install MCP Server on Cursor",
|
|
156
|
+
href: cursorInstallUrl,
|
|
157
|
+
openApp: "cursor",
|
|
158
|
+
external: true,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
type: "button",
|
|
162
|
+
icon: "simple-icons:claudecode",
|
|
163
|
+
title: "Connect to Claude Code",
|
|
164
|
+
description: "Copy command for Claude Code",
|
|
165
|
+
copyMcp: "claudeCode",
|
|
166
|
+
hasActionLabel: true,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
type: "button",
|
|
170
|
+
icon: "custom:codex",
|
|
171
|
+
title: "Connect to Codex",
|
|
172
|
+
description: "Copy command for Codex",
|
|
173
|
+
copyMcp: "codexCli",
|
|
174
|
+
hasActionLabel: true,
|
|
175
|
+
},
|
|
176
|
+
]
|
|
177
|
+
: []),
|
|
178
|
+
];
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
<nav
|
|
182
|
+
class="relative flex w-max shrink-0 justify-end"
|
|
183
|
+
data-rd-page-actions
|
|
184
|
+
data-markdown-url={markdownPath}
|
|
185
|
+
data-mcp-url={mcpUrl}
|
|
186
|
+
data-install-name={installName}
|
|
187
|
+
aria-label="Page AI actions"
|
|
188
|
+
>
|
|
189
|
+
<div class="relative inline-flex w-max shrink-0 flex-col items-end">
|
|
190
|
+
<div
|
|
191
|
+
class="inline-flex w-max shrink-0 overflow-hidden rounded-lg border-[0.5px] border-neutral-900/12 bg-neutral-50/50 text-sm text-neutral-700 shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-6px_rgba(0,0,0,0.08)] dark:border-white/6 dark:bg-(--rd-code-surface) dark:text-neutral-200 dark:shadow-[0_-.5px_1px_rgba(255,255,255,0.15),0_5px_12px_-6px_rgba(0,0,0,0.2)]"
|
|
192
|
+
>
|
|
193
|
+
<button
|
|
194
|
+
type="button"
|
|
195
|
+
class="cursor-pointer inline-flex h-8 shrink-0 items-center gap-1.5 whitespace-nowrap pl-2.5 pr-2 transition-colors hover:bg-white/80 hover:text-neutral-950 focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-neutral-900 dark:hover:bg-white/6 dark:hover:text-white dark:focus-visible:outline-white"
|
|
196
|
+
data-rd-copy-markdown
|
|
197
|
+
>
|
|
198
|
+
<span
|
|
199
|
+
class="relative inline-flex size-3.5 shrink-0 items-center justify-center"
|
|
200
|
+
>
|
|
201
|
+
<Icon
|
|
202
|
+
name="lucide:copy"
|
|
203
|
+
class="size-3.5 origin-center scale-100 rotate-0 opacity-100 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
204
|
+
data-rd-copy-icon
|
|
205
|
+
aria-hidden="true"
|
|
206
|
+
/>
|
|
207
|
+
<Icon
|
|
208
|
+
name="lucide:check"
|
|
209
|
+
class="absolute size-3.5 stroke-3 origin-center scale-50 rotate-6 opacity-0 text-green-700/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none dark:text-green-400/90"
|
|
210
|
+
data-rd-copy-check
|
|
211
|
+
aria-hidden="true"
|
|
212
|
+
/>
|
|
213
|
+
<Icon
|
|
214
|
+
name="lucide:x"
|
|
215
|
+
class="absolute size-3.5 stroke-3 origin-center -rotate-6 scale-50 opacity-0 text-red-600/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none dark:text-red-400/90"
|
|
216
|
+
data-rd-copy-error
|
|
217
|
+
aria-hidden="true"
|
|
218
|
+
/>
|
|
219
|
+
</span>
|
|
220
|
+
<span data-rd-action-label>Copy page</span>
|
|
221
|
+
</button>
|
|
222
|
+
|
|
223
|
+
<button
|
|
224
|
+
type="button"
|
|
225
|
+
class="cursor-pointer inline-flex h-8 w-8 pr-0.5 items-center justify-center border-l-[0.5px] border-neutral-900/12 transition-colors hover:bg-white/80 hover:text-neutral-950 focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-neutral-900 dark:border-white/6 dark:hover:bg-white/6 dark:hover:text-white dark:focus-visible:outline-white"
|
|
226
|
+
data-rd-page-actions-trigger
|
|
227
|
+
aria-expanded="false"
|
|
228
|
+
aria-haspopup="true"
|
|
229
|
+
aria-label="More page actions"
|
|
230
|
+
>
|
|
231
|
+
<Icon
|
|
232
|
+
name="lucide:chevron-down"
|
|
233
|
+
class="size-3.5 transition-transform"
|
|
234
|
+
aria-hidden="true"
|
|
235
|
+
data-rd-page-actions-chevron
|
|
236
|
+
/>
|
|
237
|
+
</button>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div
|
|
241
|
+
class="rd-page-actions-dropdown absolute left-0 right-auto top-full z-30 mt-1 w-max max-w-[calc(100vw-2rem)] rounded-xl border-[0.5px] border-neutral-200 bg-white p-1 text-neutral-700 shadow-2xl shadow-neutral-900/12 sm:left-auto sm:right-0 dark:border-neutral-800 dark:bg-(--rd-code-surface) dark:text-neutral-200 dark:shadow-black/40"
|
|
242
|
+
data-origin="responsive"
|
|
243
|
+
data-rd-page-actions-menu
|
|
244
|
+
hidden
|
|
245
|
+
>
|
|
246
|
+
{
|
|
247
|
+
menuItems.map((item) =>
|
|
248
|
+
item.type === "button" ? (
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
class={menuItemClass}
|
|
252
|
+
data-rd-copy-markdown={item.copyMarkdown ? true : undefined}
|
|
253
|
+
data-rd-copy-mcp={item.copyMcp}
|
|
254
|
+
>
|
|
255
|
+
<span class={menuIconClass}>
|
|
256
|
+
{item.copyMarkdown || item.copyMcp ? (
|
|
257
|
+
<span class="relative inline-flex size-4 items-center justify-center">
|
|
258
|
+
{item.icon === "custom:codex" ? (
|
|
259
|
+
<svg
|
|
260
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
261
|
+
viewBox="0 0 24 24"
|
|
262
|
+
fill="currentColor"
|
|
263
|
+
fill-rule="evenodd"
|
|
264
|
+
class="size-4 origin-center scale-100 rotate-0 opacity-100 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
265
|
+
data-rd-copy-icon
|
|
266
|
+
aria-hidden="true"
|
|
267
|
+
>
|
|
268
|
+
<path
|
|
269
|
+
clip-rule="evenodd"
|
|
270
|
+
d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"
|
|
271
|
+
/>
|
|
272
|
+
</svg>
|
|
273
|
+
) : (
|
|
274
|
+
<Icon
|
|
275
|
+
name={item.copyMarkdown ? "lucide:copy" : item.icon}
|
|
276
|
+
class="size-4 origin-center scale-100 rotate-0 opacity-100 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
277
|
+
data-rd-copy-icon
|
|
278
|
+
aria-hidden="true"
|
|
279
|
+
/>
|
|
280
|
+
)}
|
|
281
|
+
<Icon
|
|
282
|
+
name="lucide:check"
|
|
283
|
+
class="absolute size-4 stroke-3 origin-center scale-50 rotate-6 opacity-0 text-green-700/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none dark:text-green-400/90"
|
|
284
|
+
data-rd-copy-check
|
|
285
|
+
aria-hidden="true"
|
|
286
|
+
/>
|
|
287
|
+
<Icon
|
|
288
|
+
name="lucide:x"
|
|
289
|
+
class="absolute size-4 stroke-3 origin-center -rotate-6 scale-50 opacity-0 text-red-600/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none dark:text-red-400/90"
|
|
290
|
+
data-rd-copy-error
|
|
291
|
+
aria-hidden="true"
|
|
292
|
+
/>
|
|
293
|
+
</span>
|
|
294
|
+
) : item.icon === "custom:codex" ? (
|
|
295
|
+
<svg
|
|
296
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
297
|
+
viewBox="0 0 24 24"
|
|
298
|
+
fill="currentColor"
|
|
299
|
+
fill-rule="evenodd"
|
|
300
|
+
class="size-4"
|
|
301
|
+
aria-hidden="true"
|
|
302
|
+
>
|
|
303
|
+
<path
|
|
304
|
+
clip-rule="evenodd"
|
|
305
|
+
d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"
|
|
306
|
+
/>
|
|
307
|
+
</svg>
|
|
308
|
+
) : (
|
|
309
|
+
<Icon name={item.icon} class="size-4" aria-hidden="true" />
|
|
310
|
+
)}
|
|
311
|
+
</span>
|
|
312
|
+
<span class="min-w-0 pr-3">
|
|
313
|
+
<span
|
|
314
|
+
class={menuTitleClass}
|
|
315
|
+
data-rd-action-label={item.hasActionLabel ? true : undefined}
|
|
316
|
+
>
|
|
317
|
+
{item.title}
|
|
318
|
+
</span>
|
|
319
|
+
<span class={menuDescriptionClass}>{item.description}</span>
|
|
320
|
+
</span>
|
|
321
|
+
</button>
|
|
322
|
+
) : (
|
|
323
|
+
<a
|
|
324
|
+
href={item.href}
|
|
325
|
+
target={item.target}
|
|
326
|
+
rel={item.rel}
|
|
327
|
+
class={menuItemClass}
|
|
328
|
+
data-rd-open-app={item.openApp}
|
|
329
|
+
>
|
|
330
|
+
<span class={menuIconClass}>
|
|
331
|
+
{item.icon === "custom:codex" ? (
|
|
332
|
+
<svg
|
|
333
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
334
|
+
viewBox="0 0 24 24"
|
|
335
|
+
fill="currentColor"
|
|
336
|
+
fill-rule="evenodd"
|
|
337
|
+
class="size-4"
|
|
338
|
+
aria-hidden="true"
|
|
339
|
+
>
|
|
340
|
+
<path
|
|
341
|
+
clip-rule="evenodd"
|
|
342
|
+
d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"
|
|
343
|
+
/>
|
|
344
|
+
</svg>
|
|
345
|
+
) : (
|
|
346
|
+
<Icon name={item.icon} class="size-4" aria-hidden="true" />
|
|
347
|
+
)}
|
|
348
|
+
</span>
|
|
349
|
+
<span class="min-w-0 pr-3">
|
|
350
|
+
<span class={menuTitleClass}>
|
|
351
|
+
{item.title}
|
|
352
|
+
{item.external && (
|
|
353
|
+
<Icon
|
|
354
|
+
name="lucide:arrow-up-right"
|
|
355
|
+
class="ml-[3px] mb-[3px] inline size-3 self-start"
|
|
356
|
+
aria-hidden="true"
|
|
357
|
+
/>
|
|
358
|
+
)}
|
|
359
|
+
</span>
|
|
360
|
+
<span class={menuDescriptionClass}>{item.description}</span>
|
|
361
|
+
</span>
|
|
362
|
+
</a>
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
</nav>
|
|
369
|
+
|
|
370
|
+
<style>
|
|
371
|
+
[data-rd-page-actions] {
|
|
372
|
+
--dropdown-open-dur: 250ms;
|
|
373
|
+
--dropdown-close-dur: 150ms;
|
|
374
|
+
--dropdown-pre-scale: 0.97;
|
|
375
|
+
--dropdown-closing-scale: 0.99;
|
|
376
|
+
--dropdown-ease: cubic-bezier(0.22, 1, 0.36, 1);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.rd-page-actions-dropdown {
|
|
380
|
+
transform-origin: top right;
|
|
381
|
+
transform: scale(var(--dropdown-pre-scale));
|
|
382
|
+
opacity: 0;
|
|
383
|
+
pointer-events: none;
|
|
384
|
+
transition:
|
|
385
|
+
transform var(--dropdown-open-dur) var(--dropdown-ease),
|
|
386
|
+
opacity var(--dropdown-open-dur) var(--dropdown-ease);
|
|
387
|
+
will-change: transform, opacity;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.rd-page-actions-dropdown[data-origin="top-left"] {
|
|
391
|
+
transform-origin: top left;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.rd-page-actions-dropdown[data-origin="top-center"] {
|
|
395
|
+
transform-origin: top center;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.rd-page-actions-dropdown[data-origin="top-right"] {
|
|
399
|
+
transform-origin: top right;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.rd-page-actions-dropdown[data-origin="responsive"] {
|
|
403
|
+
transform-origin: top left;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@media (min-width: 640px) {
|
|
407
|
+
.rd-page-actions-dropdown[data-origin="responsive"] {
|
|
408
|
+
transform-origin: top right;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.rd-page-actions-dropdown.is-open {
|
|
413
|
+
transform: scale(1);
|
|
414
|
+
opacity: 1;
|
|
415
|
+
pointer-events: auto;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.rd-page-actions-dropdown.is-closing {
|
|
419
|
+
transform: scale(var(--dropdown-closing-scale));
|
|
420
|
+
opacity: 0;
|
|
421
|
+
pointer-events: none;
|
|
422
|
+
transition:
|
|
423
|
+
transform var(--dropdown-close-dur) var(--dropdown-ease),
|
|
424
|
+
opacity var(--dropdown-close-dur) var(--dropdown-ease);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
@media (prefers-reduced-motion: reduce) {
|
|
428
|
+
.rd-page-actions-dropdown {
|
|
429
|
+
transition: none !important;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
</style>
|
|
433
|
+
|
|
434
|
+
<script is:inline data-astro-rerun>
|
|
435
|
+
(() => {
|
|
436
|
+
const script = document.currentScript;
|
|
437
|
+
let root = script?.previousElementSibling;
|
|
438
|
+
while (
|
|
439
|
+
root &&
|
|
440
|
+
(!(root instanceof HTMLElement) ||
|
|
441
|
+
!root.hasAttribute("data-rd-page-actions"))
|
|
442
|
+
) {
|
|
443
|
+
root = root.previousElementSibling;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (
|
|
447
|
+
!(root instanceof HTMLElement) ||
|
|
448
|
+
root.dataset.pageActionsBound === "true"
|
|
449
|
+
) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
root.dataset.pageActionsBound = "true";
|
|
453
|
+
|
|
454
|
+
const trigger = root.querySelector("[data-rd-page-actions-trigger]");
|
|
455
|
+
const menu = root.querySelector("[data-rd-page-actions-menu]");
|
|
456
|
+
const chevron = root.querySelector("[data-rd-page-actions-chevron]");
|
|
457
|
+
const markdownUrl = root.dataset.markdownUrl || "/index.md";
|
|
458
|
+
const mcpUrl = root.dataset.mcpUrl || "/_mcp/server";
|
|
459
|
+
const installName = root.dataset.installName || "docs";
|
|
460
|
+
const closeDurationMs = 150;
|
|
461
|
+
const copyFeedbackDurationMs = 1400;
|
|
462
|
+
let closeTimeout;
|
|
463
|
+
let mcpMetadataPromise;
|
|
464
|
+
const copyFeedbackTimeouts = new WeakMap();
|
|
465
|
+
|
|
466
|
+
function fallbackCopy(text) {
|
|
467
|
+
const textarea = document.createElement("textarea");
|
|
468
|
+
textarea.value = text;
|
|
469
|
+
textarea.setAttribute("readonly", "");
|
|
470
|
+
textarea.style.position = "fixed";
|
|
471
|
+
textarea.style.opacity = "0";
|
|
472
|
+
document.body.appendChild(textarea);
|
|
473
|
+
textarea.select();
|
|
474
|
+
const copied = document.execCommand("copy");
|
|
475
|
+
document.body.removeChild(textarea);
|
|
476
|
+
return copied;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function copyToClipboard(text) {
|
|
480
|
+
try {
|
|
481
|
+
if (navigator.clipboard?.writeText) {
|
|
482
|
+
await navigator.clipboard.writeText(text);
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
} catch {
|
|
486
|
+
// Fallback below when clipboard API is unavailable or blocked.
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return fallbackCopy(text);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function setExpanded(nextExpanded) {
|
|
493
|
+
if (!(trigger instanceof HTMLElement)) return;
|
|
494
|
+
trigger.setAttribute("aria-expanded", String(nextExpanded));
|
|
495
|
+
if (chevron instanceof HTMLElement) {
|
|
496
|
+
chevron.classList.toggle("rotate-180", nextExpanded);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function openMenu() {
|
|
501
|
+
if (!(menu instanceof HTMLElement)) return;
|
|
502
|
+
window.clearTimeout(closeTimeout);
|
|
503
|
+
menu.hidden = false;
|
|
504
|
+
menu.classList.remove("is-closing");
|
|
505
|
+
menu.classList.add("is-open");
|
|
506
|
+
setExpanded(true);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function closeMenu() {
|
|
510
|
+
if (!(menu instanceof HTMLElement) || menu.hidden) return;
|
|
511
|
+
window.clearTimeout(closeTimeout);
|
|
512
|
+
menu.classList.remove("is-open");
|
|
513
|
+
menu.classList.add("is-closing");
|
|
514
|
+
setExpanded(false);
|
|
515
|
+
closeTimeout = window.setTimeout(() => {
|
|
516
|
+
menu.classList.remove("is-closing");
|
|
517
|
+
menu.hidden = true;
|
|
518
|
+
}, closeDurationMs);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function toggleMenu() {
|
|
522
|
+
if (!(menu instanceof HTMLElement)) return;
|
|
523
|
+
if (menu.hidden || !menu.classList.contains("is-open")) {
|
|
524
|
+
openMenu();
|
|
525
|
+
} else {
|
|
526
|
+
closeMenu();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function setLabelFeedback(button, copiedText = "Copied") {
|
|
531
|
+
const label = button.querySelector("[data-rd-action-label]");
|
|
532
|
+
if (!(label instanceof HTMLElement)) return;
|
|
533
|
+
|
|
534
|
+
const defaultText = label.dataset.defaultLabel || label.textContent || "";
|
|
535
|
+
label.dataset.defaultLabel = defaultText;
|
|
536
|
+
label.textContent = copiedText;
|
|
537
|
+
window.setTimeout(() => {
|
|
538
|
+
label.textContent = label.dataset.defaultLabel || defaultText;
|
|
539
|
+
}, copyFeedbackDurationMs);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function setCopyIconState(button, state) {
|
|
543
|
+
const copyIcon = button.querySelector("[data-rd-copy-icon]");
|
|
544
|
+
const checkIcon = button.querySelector("[data-rd-copy-check]");
|
|
545
|
+
const errorIcon = button.querySelector("[data-rd-copy-error]");
|
|
546
|
+
|
|
547
|
+
if (!copyIcon || !checkIcon || !errorIcon) return false;
|
|
548
|
+
|
|
549
|
+
const showCopy = () => {
|
|
550
|
+
copyIcon.classList.remove("scale-50", "opacity-0", "-rotate-6");
|
|
551
|
+
copyIcon.classList.add("scale-100", "opacity-100", "rotate-0");
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const hideCopy = () => {
|
|
555
|
+
copyIcon.classList.add("scale-50", "opacity-0", "-rotate-6");
|
|
556
|
+
copyIcon.classList.remove("scale-100", "opacity-100", "rotate-0");
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
const showStatusIcon = (icon) => {
|
|
560
|
+
icon.classList.remove("scale-50", "opacity-0", "rotate-6", "-rotate-6");
|
|
561
|
+
icon.classList.add("scale-110", "opacity-100", "rotate-0");
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const hideStatusIcon = (icon, rotationClass) => {
|
|
565
|
+
icon.classList.remove("scale-110", "opacity-100", "rotate-0");
|
|
566
|
+
icon.classList.add("scale-50", "opacity-0", rotationClass);
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
if (state === "success") {
|
|
570
|
+
hideCopy();
|
|
571
|
+
showStatusIcon(checkIcon);
|
|
572
|
+
hideStatusIcon(errorIcon, "-rotate-6");
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (state === "error") {
|
|
577
|
+
hideCopy();
|
|
578
|
+
hideStatusIcon(checkIcon, "rotate-6");
|
|
579
|
+
showStatusIcon(errorIcon);
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
showCopy();
|
|
584
|
+
hideStatusIcon(checkIcon, "rotate-6");
|
|
585
|
+
hideStatusIcon(errorIcon, "-rotate-6");
|
|
586
|
+
return true;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function setCopyFeedback(button, state, fallbackText) {
|
|
590
|
+
if (setCopyIconState(button, state)) {
|
|
591
|
+
const existingTimeout = copyFeedbackTimeouts.get(button);
|
|
592
|
+
if (existingTimeout) window.clearTimeout(existingTimeout);
|
|
593
|
+
|
|
594
|
+
const timeout = window.setTimeout(() => {
|
|
595
|
+
setCopyIconState(button, "idle");
|
|
596
|
+
copyFeedbackTimeouts.delete(button);
|
|
597
|
+
}, copyFeedbackDurationMs);
|
|
598
|
+
copyFeedbackTimeouts.set(button, timeout);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
setLabelFeedback(
|
|
603
|
+
button,
|
|
604
|
+
fallbackText || (state === "error" ? "Failed" : "Copied"),
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function getTomlServerKey() {
|
|
609
|
+
return (
|
|
610
|
+
installName.replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") ||
|
|
611
|
+
"docs"
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function buildFallbackMcpMetadata() {
|
|
616
|
+
return {
|
|
617
|
+
usage: {
|
|
618
|
+
install: `npx add-mcp ${mcpUrl}`,
|
|
619
|
+
claudeCode: `claude mcp add --transport http ${installName} ${mcpUrl}`,
|
|
620
|
+
codexCli: `codex mcp add ${installName} --url ${mcpUrl}`,
|
|
621
|
+
cursor: `Add "${mcpUrl}" to your MCP server configuration`,
|
|
622
|
+
codex: `[mcp_servers.${getTomlServerKey()}]\nurl = "${mcpUrl}"`,
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async function getMcpMetadata() {
|
|
628
|
+
if (!mcpMetadataPromise) {
|
|
629
|
+
mcpMetadataPromise = fetch(mcpUrl, {
|
|
630
|
+
headers: { Accept: "application/json" },
|
|
631
|
+
})
|
|
632
|
+
.then((response) => {
|
|
633
|
+
if (!response.ok) throw new Error("MCP metadata request failed");
|
|
634
|
+
return response.json();
|
|
635
|
+
})
|
|
636
|
+
.catch(() => buildFallbackMcpMetadata());
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return mcpMetadataPromise;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async function copyMarkdown(button) {
|
|
643
|
+
const response = await fetch(markdownUrl, {
|
|
644
|
+
headers: { Accept: "text/markdown,text/plain;q=0.9,*/*;q=0.1" },
|
|
645
|
+
});
|
|
646
|
+
if (!response.ok) {
|
|
647
|
+
throw new Error("Markdown request failed");
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const text = await response.text();
|
|
651
|
+
const copied = await copyToClipboard(text);
|
|
652
|
+
if (!copied) throw new Error("Clipboard write failed");
|
|
653
|
+
setCopyFeedback(button, "success");
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function copyMcpUsage(button) {
|
|
657
|
+
const key = button.getAttribute("data-rd-copy-mcp");
|
|
658
|
+
const metadata = await getMcpMetadata();
|
|
659
|
+
const usage = metadata?.usage || buildFallbackMcpMetadata().usage;
|
|
660
|
+
const fallbackUsage = buildFallbackMcpMetadata().usage;
|
|
661
|
+
const usageKey = key && key in fallbackUsage ? key : "cursor";
|
|
662
|
+
const value = usage[usageKey] || fallbackUsage[usageKey] || mcpUrl;
|
|
663
|
+
|
|
664
|
+
const copied = await copyToClipboard(value || mcpUrl);
|
|
665
|
+
if (!copied) throw new Error("Clipboard write failed");
|
|
666
|
+
setCopyFeedback(button, "success");
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
trigger?.addEventListener("click", (event) => {
|
|
670
|
+
event.stopPropagation();
|
|
671
|
+
toggleMenu();
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
menu?.addEventListener("click", (event) => {
|
|
675
|
+
event.stopPropagation();
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
document.addEventListener("click", (event) => {
|
|
679
|
+
if (!root.contains(event.target)) closeMenu();
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
document.addEventListener("keydown", (event) => {
|
|
683
|
+
if (event.key === "Escape") closeMenu();
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
root.querySelectorAll("[data-rd-copy-markdown]").forEach((button) => {
|
|
687
|
+
if (!(button instanceof HTMLButtonElement)) return;
|
|
688
|
+
button.addEventListener("click", async () => {
|
|
689
|
+
try {
|
|
690
|
+
await copyMarkdown(button);
|
|
691
|
+
} catch {
|
|
692
|
+
setCopyFeedback(button, "error", "Failed");
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
root.querySelectorAll("[data-rd-copy-mcp]").forEach((button) => {
|
|
698
|
+
if (!(button instanceof HTMLButtonElement)) return;
|
|
699
|
+
button.addEventListener("click", async () => {
|
|
700
|
+
try {
|
|
701
|
+
await copyMcpUsage(button);
|
|
702
|
+
} catch {
|
|
703
|
+
setCopyFeedback(button, "error", "Failed");
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
root.querySelectorAll("[data-rd-open-app]").forEach((link) => {
|
|
709
|
+
if (!(link instanceof HTMLAnchorElement)) return;
|
|
710
|
+
link.addEventListener("click", () => {
|
|
711
|
+
closeMenu();
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
})();
|
|
715
|
+
</script>
|
|
@@ -75,6 +75,8 @@ const navigationTabs = config.navigation.tabs as
|
|
|
75
75
|
const hasNavigationTabs =
|
|
76
76
|
Array.isArray(navigationTabs?.items) && navigationTabs.items.length > 0;
|
|
77
77
|
const tabsPresentation = navigationTabs?.presentation ?? "topbar";
|
|
78
|
+
const hasTopbarNavigationTabs =
|
|
79
|
+
hasNavigationTabs && tabsPresentation === "topbar";
|
|
78
80
|
---
|
|
79
81
|
|
|
80
82
|
<!doctype html>
|
|
@@ -356,7 +358,10 @@ const tabsPresentation = navigationTabs?.presentation ?? "topbar";
|
|
|
356
358
|
|
|
357
359
|
<!-- Main Content -->
|
|
358
360
|
<div
|
|
359
|
-
class=
|
|
361
|
+
class:list={[
|
|
362
|
+
"mx-1 mt-1 px-4 sm:px-6 lg:pl-[calc(288px+32px)] pt-16 lg:pr-8 bg-background",
|
|
363
|
+
hasTopbarNavigationTabs && "lg:pt-[108px]",
|
|
364
|
+
]}
|
|
360
365
|
>
|
|
361
366
|
<main class="mx-auto pt-16 pb-20 min-h-[calc(100vh-64px)]">
|
|
362
367
|
<slot />
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type MdxRoute,
|
|
10
10
|
type OpenApiRoute,
|
|
11
11
|
} from "./routes";
|
|
12
|
+
import { parseOpenApiEndpoint } from "./utils";
|
|
12
13
|
import {
|
|
13
14
|
getOpenApiOperationDoc,
|
|
14
15
|
OPENAPI_REQUEST_SECTION_LABELS,
|
|
@@ -18,10 +19,19 @@ import {
|
|
|
18
19
|
type OpenApiRequestSectionVariantData,
|
|
19
20
|
} from "./openapi/operation-doc";
|
|
20
21
|
import { resolvePageDescription } from "./page-description";
|
|
21
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
getConfig,
|
|
24
|
+
type DocsConfig,
|
|
25
|
+
type NavGroup,
|
|
26
|
+
type NavMenu,
|
|
27
|
+
type NavOpenApi,
|
|
28
|
+
type NavOpenApiPage,
|
|
29
|
+
type NavPage,
|
|
30
|
+
} from "./validation";
|
|
22
31
|
|
|
23
32
|
const DOCS_ROOT = path.join(process.cwd(), "src/content/docs");
|
|
24
33
|
const MAX_DESCRIPTION_LENGTH = 300;
|
|
34
|
+
const PRO_TIER = 3;
|
|
25
35
|
|
|
26
36
|
const OPENAPI_SPEC_EXTENSIONS = new Set([".json", ".yaml", ".yml"]);
|
|
27
37
|
|
|
@@ -29,6 +39,7 @@ export const MARKDOWN_CONTENT_TYPE = "text/markdown; charset=utf-8";
|
|
|
29
39
|
export const PLAIN_TEXT_CONTENT_TYPE = "text/plain; charset=utf-8";
|
|
30
40
|
|
|
31
41
|
export type AiMarkdownPage = {
|
|
42
|
+
routeIdentity?: string;
|
|
32
43
|
filePath: string;
|
|
33
44
|
routeSlug?: string;
|
|
34
45
|
title: string;
|
|
@@ -90,6 +101,24 @@ function buildPublicUrl(pathname: string): string {
|
|
|
90
101
|
return new URL(publicPath, siteOrigin).toString();
|
|
91
102
|
}
|
|
92
103
|
|
|
104
|
+
function getOrgTier(): number {
|
|
105
|
+
const parsed = Number.parseInt((process.env.ORG_TIER ?? "1").trim(), 10);
|
|
106
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function formatMcpSection(): string[] {
|
|
110
|
+
if (getOrgTier() < PRO_TIER) return [];
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
"## MCP",
|
|
114
|
+
"",
|
|
115
|
+
"This documentation exposes a read-only MCP server for AI agents:",
|
|
116
|
+
"",
|
|
117
|
+
`- MCP server: ${buildPublicUrl("/_mcp/server")}`,
|
|
118
|
+
"- Tool: searchDocs(query)",
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
|
|
93
122
|
function getMarkdownPathForSlug(routeSlug: string): string {
|
|
94
123
|
const normalizedSlug = routeSlug.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
95
124
|
return normalizedSlug ? `/${normalizedSlug}.md` : "/index.md";
|
|
@@ -142,19 +171,23 @@ async function getMdxEntryByFilePath(filePath: string) {
|
|
|
142
171
|
async function createMarkdownPage(args: {
|
|
143
172
|
filePath: string;
|
|
144
173
|
routeSlug?: string;
|
|
174
|
+
routeIdentity?: string;
|
|
145
175
|
title?: string;
|
|
146
176
|
markdownPath?: string;
|
|
147
177
|
canonicalPath?: string;
|
|
148
178
|
}): Promise<AiMarkdownPage> {
|
|
149
179
|
const entry = await getMdxEntryByFilePath(args.filePath);
|
|
150
180
|
if (!entry) {
|
|
151
|
-
throw new Error(
|
|
181
|
+
throw new Error(
|
|
182
|
+
`Could not find content collection entry for "${args.filePath}".`,
|
|
183
|
+
);
|
|
152
184
|
}
|
|
153
185
|
|
|
154
186
|
const { sourcePath, source } = await readMdxSource(args.filePath);
|
|
155
187
|
const routeSlug = args.routeSlug ?? "";
|
|
156
188
|
const markdownPath = args.markdownPath ?? getMarkdownPathForSlug(routeSlug);
|
|
157
|
-
const canonicalPath =
|
|
189
|
+
const canonicalPath =
|
|
190
|
+
args.canonicalPath ?? getCanonicalPathForSlug(routeSlug);
|
|
158
191
|
const title =
|
|
159
192
|
args.title ??
|
|
160
193
|
resolveMdxPageTitle({
|
|
@@ -164,6 +197,7 @@ async function createMarkdownPage(args: {
|
|
|
164
197
|
const config = await getConfig();
|
|
165
198
|
|
|
166
199
|
return {
|
|
200
|
+
routeIdentity: args.routeIdentity,
|
|
167
201
|
filePath: normalizeDocsRootRelativePath(args.filePath),
|
|
168
202
|
routeSlug,
|
|
169
203
|
title,
|
|
@@ -205,6 +239,7 @@ export async function getLlmsListedMarkdownPages(): Promise<AiMarkdownPage[]> {
|
|
|
205
239
|
await createMarkdownPage({
|
|
206
240
|
filePath: config.home,
|
|
207
241
|
routeSlug: "",
|
|
242
|
+
routeIdentity: homeRoute?.routeIdentity,
|
|
208
243
|
title: homeRoute?.title,
|
|
209
244
|
markdownPath: "/index.md",
|
|
210
245
|
canonicalPath: "/",
|
|
@@ -277,6 +312,234 @@ function formatDocsListItem(page: AiMarkdownPage): string {
|
|
|
277
312
|
return `- [${page.title}](${page.markdownUrl})${description}`;
|
|
278
313
|
}
|
|
279
314
|
|
|
315
|
+
type LlmsTxtSection = {
|
|
316
|
+
title: string;
|
|
317
|
+
pages: AiMarkdownPage[];
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
type LlmsNavPageItem = string | NavPage | NavGroup | NavOpenApiPage;
|
|
321
|
+
|
|
322
|
+
type LlmsNavigationContainer = {
|
|
323
|
+
pages?: LlmsNavPageItem[];
|
|
324
|
+
menu?: NavMenu;
|
|
325
|
+
openapi?: string | NavOpenApi;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
function getAiMarkdownPageKey(page: AiMarkdownPage): string {
|
|
329
|
+
return page.routeIdentity ?? `${page.filePath}:${page.markdownUrl}`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function buildLlmsSectionTitle(parts: string[]): string {
|
|
333
|
+
const title = parts
|
|
334
|
+
.map((part) => part.trim())
|
|
335
|
+
.filter(Boolean)
|
|
336
|
+
.join(": ");
|
|
337
|
+
|
|
338
|
+
return title || "Docs";
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function isNavPageItem(item: LlmsNavPageItem): item is string | NavPage {
|
|
342
|
+
return typeof item === "string" || "page" in item;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function isNavGroupItem(item: LlmsNavPageItem): item is NavGroup {
|
|
346
|
+
return typeof item !== "string" && "group" in item;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function isNavOpenApiPageItem(
|
|
350
|
+
item: LlmsNavPageItem,
|
|
351
|
+
): item is NavOpenApiPage {
|
|
352
|
+
return typeof item !== "string" && "openapi" in item;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function getNavPageRouteIdentity(item: string | NavPage): string {
|
|
356
|
+
return `mdx:${typeof item === "string" ? item : item.page}`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function getNavOpenApiPageRouteIdentity(item: NavOpenApiPage): string | null {
|
|
360
|
+
const parsedEndpoint = parseOpenApiEndpoint(item.openapi.endpoint);
|
|
361
|
+
if (!parsedEndpoint) return null;
|
|
362
|
+
|
|
363
|
+
return `openapi:${item.openapi.source}:${parsedEndpoint.method} ${parsedEndpoint.path}`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function getNavOpenApiSource(openapi: string | NavOpenApi): string {
|
|
367
|
+
return typeof openapi === "string" ? openapi : openapi.source;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function getOpenApiEndpointKey(method: string, pathStr: string): string {
|
|
371
|
+
return `${method.toUpperCase()} ${pathStr.toLowerCase()}`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function matchesOpenApiEndpointFilter(
|
|
375
|
+
route: OpenApiRoute,
|
|
376
|
+
endpoints: string[],
|
|
377
|
+
): boolean {
|
|
378
|
+
const routeEndpointKey = getOpenApiEndpointKey(
|
|
379
|
+
route.openApiMethod,
|
|
380
|
+
route.openApiPath,
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
return endpoints.some((endpoint) => {
|
|
384
|
+
const parsedEndpoint = parseOpenApiEndpoint(endpoint);
|
|
385
|
+
if (!parsedEndpoint) return false;
|
|
386
|
+
|
|
387
|
+
return (
|
|
388
|
+
getOpenApiEndpointKey(parsedEndpoint.method, parsedEndpoint.path) ===
|
|
389
|
+
routeEndpointKey
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function getOpenApiFileRouteIdentities(
|
|
395
|
+
openapi: string | NavOpenApi,
|
|
396
|
+
routes: Route[],
|
|
397
|
+
): string[] {
|
|
398
|
+
const source = getNavOpenApiSource(openapi);
|
|
399
|
+
const include = typeof openapi === "string" ? undefined : openapi.include;
|
|
400
|
+
const exclude = typeof openapi === "string" ? undefined : openapi.exclude;
|
|
401
|
+
|
|
402
|
+
return routes
|
|
403
|
+
.filter((route): route is OpenApiRoute => {
|
|
404
|
+
if (route.type !== "openapi" || route.filePath !== source) return false;
|
|
405
|
+
if (include && !matchesOpenApiEndpointFilter(route, include)) {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
if (exclude && matchesOpenApiEndpointFilter(route, exclude)) {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return true;
|
|
413
|
+
})
|
|
414
|
+
.map((route) => route.routeIdentity);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function buildLlmsTxtSections(args: {
|
|
418
|
+
config: DocsConfig;
|
|
419
|
+
pages: AiMarkdownPage[];
|
|
420
|
+
routes: Route[];
|
|
421
|
+
}): LlmsTxtSection[] {
|
|
422
|
+
const pageByRouteIdentity = new Map(
|
|
423
|
+
args.pages
|
|
424
|
+
.filter((page) => page.routeIdentity)
|
|
425
|
+
.map((page) => [page.routeIdentity as string, page]),
|
|
426
|
+
);
|
|
427
|
+
const emittedPageKeys = new Set<string>();
|
|
428
|
+
const sections: LlmsTxtSection[] = [];
|
|
429
|
+
|
|
430
|
+
const appendSection = (titleParts: string[], routeIdentities: string[]) => {
|
|
431
|
+
const sectionPages: AiMarkdownPage[] = [];
|
|
432
|
+
|
|
433
|
+
for (const routeIdentity of routeIdentities) {
|
|
434
|
+
const page = pageByRouteIdentity.get(routeIdentity);
|
|
435
|
+
if (!page) continue;
|
|
436
|
+
|
|
437
|
+
const pageKey = getAiMarkdownPageKey(page);
|
|
438
|
+
if (emittedPageKeys.has(pageKey)) continue;
|
|
439
|
+
|
|
440
|
+
emittedPageKeys.add(pageKey);
|
|
441
|
+
sectionPages.push(page);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (sectionPages.length === 0) return;
|
|
445
|
+
|
|
446
|
+
const title = buildLlmsSectionTitle(titleParts);
|
|
447
|
+
const existingSection = sections.find((section) => section.title === title);
|
|
448
|
+
|
|
449
|
+
if (existingSection) {
|
|
450
|
+
existingSection.pages.push(...sectionPages);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
sections.push({
|
|
455
|
+
title,
|
|
456
|
+
pages: sectionPages,
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const collectPages = (items: LlmsNavPageItem[], titleParts: string[]) => {
|
|
461
|
+
let directRouteIdentities: string[] = [];
|
|
462
|
+
const flushDirectPages = () => {
|
|
463
|
+
appendSection(titleParts, directRouteIdentities);
|
|
464
|
+
directRouteIdentities = [];
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
for (const item of items) {
|
|
468
|
+
if (isNavPageItem(item)) {
|
|
469
|
+
directRouteIdentities.push(getNavPageRouteIdentity(item));
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (isNavOpenApiPageItem(item)) {
|
|
474
|
+
const routeIdentity = getNavOpenApiPageRouteIdentity(item);
|
|
475
|
+
if (routeIdentity) directRouteIdentities.push(routeIdentity);
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (isNavGroupItem(item)) {
|
|
480
|
+
flushDirectPages();
|
|
481
|
+
collectNavigationContainer(
|
|
482
|
+
{ pages: item.pages },
|
|
483
|
+
[...titleParts, item.group],
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
flushDirectPages();
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const collectNavigationContainer = (
|
|
492
|
+
container: LlmsNavigationContainer,
|
|
493
|
+
titleParts: string[],
|
|
494
|
+
) => {
|
|
495
|
+
if (container.pages) {
|
|
496
|
+
collectPages(container.pages, titleParts);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (container.openapi) {
|
|
500
|
+
appendSection(
|
|
501
|
+
titleParts.length ? titleParts : ["API Reference"],
|
|
502
|
+
getOpenApiFileRouteIdentities(container.openapi, args.routes),
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (container.menu) {
|
|
507
|
+
for (const menuItem of container.menu.items) {
|
|
508
|
+
collectNavigationContainer(menuItem, [...titleParts, menuItem.label]);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const navigation = args.config.navigation;
|
|
514
|
+
|
|
515
|
+
if (navigation.pages) {
|
|
516
|
+
collectPages(navigation.pages, []);
|
|
517
|
+
} else if (navigation.menu) {
|
|
518
|
+
collectNavigationContainer({ menu: navigation.menu }, []);
|
|
519
|
+
} else if (navigation.openapi) {
|
|
520
|
+
collectNavigationContainer({ openapi: navigation.openapi }, [
|
|
521
|
+
"API Reference",
|
|
522
|
+
]);
|
|
523
|
+
} else if (navigation.tabs) {
|
|
524
|
+
for (const tabItem of navigation.tabs.items) {
|
|
525
|
+
collectNavigationContainer(tabItem, [tabItem.label]);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const remainingPages = args.pages.filter(
|
|
530
|
+
(page) => !emittedPageKeys.has(getAiMarkdownPageKey(page)),
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
if (remainingPages.length > 0) {
|
|
534
|
+
sections.push({
|
|
535
|
+
title: sections.length ? "Additional Docs" : "Docs",
|
|
536
|
+
pages: remainingPages,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return sections;
|
|
541
|
+
}
|
|
542
|
+
|
|
280
543
|
function normalizeLocalOpenApiSource(source: string): string {
|
|
281
544
|
return normalizeDocsRootRelativePath(source);
|
|
282
545
|
}
|
|
@@ -297,7 +560,9 @@ function getOpenApiSpecLabel(source: string): string {
|
|
|
297
560
|
if (isRemoteUrl(source)) {
|
|
298
561
|
try {
|
|
299
562
|
const url = new URL(source);
|
|
300
|
-
return
|
|
563
|
+
return (
|
|
564
|
+
path.posix.basename(url.pathname).replace(/\.[^.]+$/, "") || source
|
|
565
|
+
);
|
|
301
566
|
} catch {
|
|
302
567
|
return source;
|
|
303
568
|
}
|
|
@@ -344,7 +609,9 @@ function createOpenApiSpecArtifact(source: string): OpenApiSpecArtifact | null {
|
|
|
344
609
|
};
|
|
345
610
|
}
|
|
346
611
|
|
|
347
|
-
export async function getOpenApiSpecArtifacts(): Promise<
|
|
612
|
+
export async function getOpenApiSpecArtifacts(): Promise<
|
|
613
|
+
OpenApiSpecArtifact[]
|
|
614
|
+
> {
|
|
348
615
|
const routes = await getAllRoutes();
|
|
349
616
|
const sources = new Set(
|
|
350
617
|
routes
|
|
@@ -566,10 +833,7 @@ function getResponseContentTypes(response: any): string[] {
|
|
|
566
833
|
return Object.keys(content);
|
|
567
834
|
}
|
|
568
835
|
|
|
569
|
-
function appendOpenApiResponsesMarkdown(
|
|
570
|
-
lines: string[],
|
|
571
|
-
responses: unknown,
|
|
572
|
-
) {
|
|
836
|
+
function appendOpenApiResponsesMarkdown(lines: string[], responses: unknown) {
|
|
573
837
|
if (!responses || typeof responses !== "object") return;
|
|
574
838
|
|
|
575
839
|
const responseEntries = Object.entries(responses).filter(
|
|
@@ -662,6 +926,7 @@ async function createOpenApiMarkdownPage(args: {
|
|
|
662
926
|
args.canonicalPath ?? getCanonicalPathForSlug(routeSlug);
|
|
663
927
|
|
|
664
928
|
return {
|
|
929
|
+
routeIdentity: args.route.routeIdentity,
|
|
665
930
|
filePath: normalizeDocsRootRelativePath(args.route.filePath),
|
|
666
931
|
routeSlug,
|
|
667
932
|
title: args.route.title,
|
|
@@ -690,6 +955,7 @@ async function createRouteMarkdownPage(
|
|
|
690
955
|
return createMarkdownPage({
|
|
691
956
|
filePath: route.filePath,
|
|
692
957
|
routeSlug: route.slug,
|
|
958
|
+
routeIdentity: route.routeIdentity,
|
|
693
959
|
title: route.title,
|
|
694
960
|
...overrides,
|
|
695
961
|
});
|
|
@@ -729,16 +995,30 @@ export async function getLlmsTxt(): Promise<string> {
|
|
|
729
995
|
|
|
730
996
|
const config = await getConfig();
|
|
731
997
|
const pages = await getLlmsListedMarkdownPages();
|
|
998
|
+
const routes = await getAllRoutes();
|
|
732
999
|
const specs = await getOpenApiSpecArtifacts();
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
1000
|
+
const sections = buildLlmsTxtSections({ config, pages, routes });
|
|
1001
|
+
const lines = [`# ${config.title}`];
|
|
1002
|
+
|
|
1003
|
+
for (const section of sections) {
|
|
1004
|
+
lines.push(
|
|
1005
|
+
"",
|
|
1006
|
+
`## ${section.title}`,
|
|
1007
|
+
"",
|
|
1008
|
+
...section.pages.map(formatDocsListItem),
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
736
1011
|
|
|
737
1012
|
const specsSection = formatOpenApiSpecsSection(specs);
|
|
738
1013
|
if (specsSection.length > 0) {
|
|
739
1014
|
lines.push("", ...specsSection);
|
|
740
1015
|
}
|
|
741
1016
|
|
|
1017
|
+
const mcpSection = formatMcpSection();
|
|
1018
|
+
if (mcpSection.length > 0) {
|
|
1019
|
+
lines.push("", ...mcpSection);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
742
1022
|
return `${lines.join("\n").trimEnd()}\n`;
|
|
743
1023
|
}
|
|
744
1024
|
|
|
@@ -785,7 +1065,9 @@ export async function getLlmsFullTxt(): Promise<string> {
|
|
|
785
1065
|
continue;
|
|
786
1066
|
}
|
|
787
1067
|
|
|
788
|
-
sections.push(
|
|
1068
|
+
sections.push(
|
|
1069
|
+
formatLlmsFullSpec(spec, await readOpenApiSpecArtifact(spec)),
|
|
1070
|
+
);
|
|
789
1071
|
}
|
|
790
1072
|
|
|
791
1073
|
return `${sections.join("\n\n---\n\n").trimEnd()}\n`;
|