blume 2.0.1 → 2.0.3

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 (492) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
  3. package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
  4. package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
  5. package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
  6. package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
  7. package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
  8. package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
  9. package/dist/cli/chunk-35d4wj9f.js.map +15 -0
  10. package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
  11. package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
  12. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
  13. package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
  14. package/dist/cli/chunk-5f86nr5m.js.map +11 -0
  15. package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
  16. package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
  17. package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
  18. package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
  19. package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
  20. package/dist/cli/chunk-6dsbexzp.js.map +10 -0
  21. package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
  22. package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
  23. package/dist/cli/chunk-9t7a85s3.js.map +10 -0
  24. package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
  25. package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
  26. package/dist/cli/chunk-acanzt5p.js.map +10 -0
  27. package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
  28. package/dist/cli/chunk-akbpwfxc.js.map +10 -0
  29. package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
  30. package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
  31. package/dist/cli/chunk-c8chx29p.js.map +36 -0
  32. package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
  33. package/dist/cli/chunk-crgn1q09.js.map +10 -0
  34. package/dist/cli/chunk-e04dxsz1.js +39 -0
  35. package/dist/cli/chunk-e04dxsz1.js.map +10 -0
  36. package/dist/cli/{chunk-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
  37. package/dist/cli/chunk-ey84smr6.js.map +10 -0
  38. package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
  39. package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
  40. package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
  41. package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
  42. package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
  43. package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
  44. package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
  45. package/dist/cli/chunk-j85vccga.js.map +36 -0
  46. package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
  47. package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
  48. package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
  49. package/dist/cli/chunk-p73c0m7w.js.map +11 -0
  50. package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
  51. package/dist/cli/chunk-pehfxfta.js.map +10 -0
  52. package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
  53. package/dist/cli/chunk-pv29h0wf.js.map +190 -0
  54. package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
  55. package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
  56. package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
  57. package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
  58. package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
  59. package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
  60. package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
  61. package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
  62. package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
  63. package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
  64. package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
  65. package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
  66. package/dist/cli/chunk-yfyb25rh.js.map +14 -0
  67. package/dist/cli/index.js +162 -35
  68. package/dist/cli/index.js.map +4 -4
  69. package/dist/types/ai/agent-surface.d.ts +32 -0
  70. package/dist/types/ai/api-catalog.d.ts +7 -1
  71. package/dist/types/ai/ask-context.d.ts +7 -0
  72. package/dist/types/ai/component-markdown.d.ts +4 -4
  73. package/dist/types/ai/link-headers.d.ts +8 -1
  74. package/dist/types/ai/openapi-components.d.ts +5 -2
  75. package/dist/types/ai/relative-links.d.ts +11 -4
  76. package/dist/types/ai/skills.d.ts +4 -1
  77. package/dist/types/ai/static-expression.d.ts +28 -0
  78. package/dist/types/ai/tar.d.ts +1 -3
  79. package/dist/types/analytics/databuddy.d.ts +43 -0
  80. package/dist/types/analytics/index.d.ts +4 -0
  81. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  82. package/dist/types/analytics/schema.d.ts +28 -0
  83. package/dist/types/astro/integration.d.ts +3 -2
  84. package/dist/types/cli/env.d.ts +5 -0
  85. package/dist/types/cli/init/scaffold.d.ts +19 -3
  86. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  87. package/dist/types/core/base-path.d.ts +24 -1
  88. package/dist/types/core/config-input.d.ts +13 -11
  89. package/dist/types/core/config.d.ts +2 -2
  90. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  91. package/dist/types/core/graph.d.ts +2 -0
  92. package/dist/types/core/heading-markers.d.ts +5 -7
  93. package/dist/types/core/i18n-ui.d.ts +31 -0
  94. package/dist/types/core/i18n.d.ts +9 -1
  95. package/dist/types/core/last-modified.d.ts +10 -0
  96. package/dist/types/core/links.d.ts +3 -1
  97. package/dist/types/core/load-module.d.ts +10 -0
  98. package/dist/types/core/locale-links.d.ts +12 -2
  99. package/dist/types/core/meta.d.ts +13 -1
  100. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  101. package/dist/types/core/navigation.d.ts +38 -0
  102. package/dist/types/core/ordering-prefix.d.ts +4 -0
  103. package/dist/types/core/safe-links.d.ts +3 -1
  104. package/dist/types/core/schema.d.ts +59 -13
  105. package/dist/types/core/sources/github-releases.d.ts +5 -0
  106. package/dist/types/core/sources/lower.d.ts +38 -13
  107. package/dist/types/core/sources/normalize.d.ts +23 -2
  108. package/dist/types/core/sources/remote.d.ts +11 -1
  109. package/dist/types/core/sources/resolve.d.ts +12 -0
  110. package/dist/types/core/sources/types.d.ts +31 -0
  111. package/dist/types/core/sources/watch.d.ts +12 -5
  112. package/dist/types/core/standard-schema.d.ts +5 -0
  113. package/dist/types/core/types.d.ts +9 -0
  114. package/dist/types/deploy/adapters/node.d.ts +5 -2
  115. package/dist/types/deploy/adapters/types.d.ts +7 -0
  116. package/dist/types/deploy/artifacts.d.ts +6 -4
  117. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  118. package/dist/types/deploy/headers.d.ts +36 -7
  119. package/dist/types/deploy/node-headers.d.ts +43 -8
  120. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  121. package/dist/types/deploy/platforms/node.d.ts +6 -5
  122. package/dist/types/deploy/platforms/types.d.ts +15 -0
  123. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  124. package/dist/types/deploy/redirects.d.ts +31 -15
  125. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  126. package/dist/types/markdown/directives.d.ts +62 -0
  127. package/dist/types/markdown/features.d.ts +21 -0
  128. package/dist/types/markdown/mdast.d.ts +63 -0
  129. package/dist/types/openapi/asyncapi.d.ts +4 -2
  130. package/dist/types/openapi/model.d.ts +20 -6
  131. package/dist/types/search/sync/algolia.d.ts +3 -1
  132. package/docs/01-quickstart.mdx +3 -2
  133. package/docs/02-deployment.mdx +20 -9
  134. package/docs/08-faq.mdx +10 -3
  135. package/docs/advanced/changelog.mdx +1 -1
  136. package/docs/advanced/custom-pages.mdx +7 -5
  137. package/docs/cli/audit.mdx +19 -3
  138. package/docs/cli/doctor.mdx +2 -2
  139. package/docs/cli/evals.mdx +4 -4
  140. package/docs/cli/index.mdx +4 -1
  141. package/docs/cli/translate.mdx +4 -4
  142. package/docs/cli/version.mdx +1 -1
  143. package/docs/configuration/analytics.mdx +42 -2
  144. package/docs/configuration/assistant.mdx +1 -1
  145. package/docs/configuration/customization.mdx +6 -3
  146. package/docs/configuration/index.mdx +5 -3
  147. package/docs/configuration/search.mdx +2 -2
  148. package/docs/content/components.mdx +2 -2
  149. package/docs/content/frontmatter.mdx +5 -1
  150. package/docs/content/i18n.mdx +3 -1
  151. package/docs/content/index.mdx +8 -4
  152. package/docs/content/islands.mdx +1 -1
  153. package/docs/content/meta.mdx +7 -3
  154. package/docs/content/navigation.mdx +29 -4
  155. package/docs/content/sources.mdx +18 -16
  156. package/docs/content/syntax.mdx +14 -0
  157. package/docs/content/versioning.mdx +2 -1
  158. package/docs/discoverability/agent-discovery.mdx +22 -8
  159. package/docs/discoverability/index.mdx +4 -3
  160. package/docs/discoverability/llms-txt.mdx +2 -5
  161. package/docs/discoverability/markdown.mdx +6 -4
  162. package/docs/discoverability/mcp.mdx +4 -0
  163. package/docs/discoverability/metadata.mdx +3 -2
  164. package/docs/discoverability/open-graph.mdx +8 -4
  165. package/docs/discoverability/rss.mdx +4 -2
  166. package/docs/references/asyncapi.mdx +2 -2
  167. package/docs/references/graphql.mdx +2 -2
  168. package/docs/references/openapi.mdx +6 -4
  169. package/package.json +1 -1
  170. package/skills/blume-migrate/SKILL.md +6 -6
  171. package/skills/blume-migrate/references/docusaurus.md +6 -6
  172. package/skills/blume-migrate/references/fumadocs.md +1 -1
  173. package/skills/blume-migrate/references/mintlify.md +3 -3
  174. package/skills/blume-migrate/references/monorepo.md +1 -1
  175. package/skills/blume-migrate/references/nextra.md +2 -2
  176. package/skills/blume-migrate/references/starlight.md +5 -5
  177. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
  178. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  179. package/src/ai/agent-readability.ts +2 -2
  180. package/src/ai/agent-surface.ts +56 -0
  181. package/src/ai/ai-catalog.ts +6 -2
  182. package/src/ai/api/handlers.ts +2 -2
  183. package/src/ai/api/spec.ts +17 -4
  184. package/src/ai/api-catalog.ts +13 -3
  185. package/src/ai/ask-context.ts +14 -2
  186. package/src/ai/ask-data.ts +26 -12
  187. package/src/ai/changelog-markdown.ts +2 -2
  188. package/src/ai/component-markdown.ts +43 -37
  189. package/src/ai/link-headers.ts +22 -8
  190. package/src/ai/llms.ts +22 -10
  191. package/src/ai/markdown.ts +23 -11
  192. package/src/ai/mcp/discovery.ts +2 -2
  193. package/src/ai/mcp/query.ts +2 -2
  194. package/src/ai/mcp/server.ts +80 -9
  195. package/src/ai/openapi-components.ts +22 -6
  196. package/src/ai/relative-links.ts +99 -15
  197. package/src/ai/serializers.ts +2 -1
  198. package/src/ai/skills.ts +14 -1
  199. package/src/ai/static-expression.ts +416 -0
  200. package/src/ai/tar.ts +139 -12
  201. package/src/ai/visibility.ts +45 -14
  202. package/src/analytics/databuddy.ts +67 -0
  203. package/src/analytics/head.ts +8 -0
  204. package/src/analytics/index.ts +7 -0
  205. package/src/analytics/one-dollar-stats.ts +86 -0
  206. package/src/analytics/posthog.ts +21 -3
  207. package/src/analytics/schema.ts +4 -0
  208. package/src/astro/generate.ts +72 -28
  209. package/src/astro/include-hmr.ts +31 -12
  210. package/src/astro/include-refresh.ts +19 -8
  211. package/src/astro/integration.ts +108 -14
  212. package/src/astro/runtime-modules.ts +28 -13
  213. package/src/astro/templates.ts +139 -57
  214. package/src/audit/catalog.ts +2 -2
  215. package/src/audit/checks/assets.ts +25 -5
  216. package/src/audit/checks/content.ts +20 -2
  217. package/src/audit/checks/i18n.ts +46 -8
  218. package/src/audit/checks/indexability.ts +39 -19
  219. package/src/audit/checks/links.ts +22 -1
  220. package/src/audit/checks/llms.ts +6 -3
  221. package/src/audit/checks/network.ts +3 -1
  222. package/src/audit/checks/og-image.ts +10 -0
  223. package/src/audit/checks/robots.ts +6 -1
  224. package/src/audit/checks/sitemap.ts +56 -31
  225. package/src/audit/checks/social.ts +21 -2
  226. package/src/audit/crawl.ts +88 -15
  227. package/src/audit/graph.ts +3 -1
  228. package/src/audit/report.ts +54 -17
  229. package/src/audit/run.ts +13 -9
  230. package/src/audit/snapshot.ts +5 -0
  231. package/src/audit/types.ts +18 -0
  232. package/src/audit/url.ts +37 -6
  233. package/src/cli/args.ts +32 -0
  234. package/src/cli/build-failure.ts +50 -0
  235. package/src/cli/commands/audit.ts +11 -1
  236. package/src/cli/commands/build.ts +19 -5
  237. package/src/cli/commands/check.ts +2 -0
  238. package/src/cli/commands/doctor.ts +3 -1
  239. package/src/cli/commands/eval.ts +20 -18
  240. package/src/cli/commands/init.ts +15 -5
  241. package/src/cli/commands/preview.ts +15 -0
  242. package/src/cli/commands/sync.ts +2 -0
  243. package/src/cli/commands/translate.ts +18 -18
  244. package/src/cli/commands/upgrade.ts +11 -0
  245. package/src/cli/commands/validate.ts +3 -1
  246. package/src/cli/dev-lock.ts +157 -37
  247. package/src/cli/eject-scripts.ts +32 -7
  248. package/src/cli/env.ts +12 -1
  249. package/src/cli/init/scaffold.ts +117 -11
  250. package/src/cli/init/starter-spec.ts +235 -0
  251. package/src/cli/report-format.ts +11 -6
  252. package/src/components/colors.ts +19 -0
  253. package/src/components/content/AccordionItem.astro +26 -22
  254. package/src/components/content/Badge.astro +7 -12
  255. package/src/components/content/Card.astro +2 -2
  256. package/src/components/content/Component.astro +11 -5
  257. package/src/components/content/Expandable.astro +5 -1
  258. package/src/components/content/Frame.astro +2 -7
  259. package/src/components/content/GithubInfo.astro +12 -2
  260. package/src/components/content/Prompt.astro +2 -7
  261. package/src/components/content/Tab.astro +0 -1
  262. package/src/components/content/Tabs.astro +58 -5
  263. package/src/components/content/Tile.astro +1 -1
  264. package/src/components/content/Tooltip.astro +69 -7
  265. package/src/components/content/Tree.astro +7 -2
  266. package/src/components/content/TypeTable.astro +10 -5
  267. package/src/components/content/Update.astro +8 -2
  268. package/src/components/content/auto-type-table.ts +4 -1
  269. package/src/components/content/badge-color.ts +20 -0
  270. package/src/components/content/base-href.ts +14 -22
  271. package/src/components/content/inline-markdown.ts +27 -7
  272. package/src/components/copy-feedback.ts +35 -8
  273. package/src/components/islands/assistant.tsx +37 -6
  274. package/src/components/islands/base-path.ts +47 -10
  275. package/src/components/islands/hooks.ts +42 -21
  276. package/src/components/islands/webmcp.ts +16 -10
  277. package/src/components/layout/Banner.astro +23 -4
  278. package/src/components/layout/Breadcrumbs.astro +5 -2
  279. package/src/components/layout/DiscoveryLinks.astro +9 -5
  280. package/src/components/layout/Header.astro +23 -10
  281. package/src/components/layout/LanguageSwitcher.astro +2 -2
  282. package/src/components/layout/Logo.astro +5 -0
  283. package/src/components/layout/NavSelector.astro +8 -3
  284. package/src/components/layout/NavTabMenu.astro +133 -0
  285. package/src/components/layout/NavTree.astro +31 -12
  286. package/src/components/layout/NavTreeCache.astro +5 -2
  287. package/src/components/layout/NavTreeScript.astro +45 -5
  288. package/src/components/layout/PageActions.astro +4 -3
  289. package/src/components/layout/PageLayout.astro +28 -13
  290. package/src/components/layout/Pagination.astro +3 -3
  291. package/src/components/layout/ReferenceLayout.astro +4 -1
  292. package/src/components/layout/RootLayout.astro +25 -12
  293. package/src/components/layout/Search.astro +38 -14
  294. package/src/components/layout/VersionBanner.astro +2 -2
  295. package/src/components/layout/analytics-client.ts +14 -0
  296. package/src/components/layout/toc-active.ts +41 -0
  297. package/src/components/layout/toc-element.ts +8 -14
  298. package/src/components/openapi/ApiTagOperations.astro +2 -2
  299. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  300. package/src/components/openapi/GraphqlChip.astro +2 -2
  301. package/src/components/openapi/GraphqlType.astro +11 -3
  302. package/src/components/openapi/MessageComposer.astro +1 -1
  303. package/src/components/openapi/Operation.astro +23 -5
  304. package/src/components/openapi/PanelTabs.astro +4 -1
  305. package/src/components/openapi/Playground.astro +8 -2
  306. package/src/components/openapi/RequestPanel.astro +13 -6
  307. package/src/components/openapi/SchemaProperty.astro +11 -33
  308. package/src/components/openapi/SchemaTable.astro +34 -52
  309. package/src/components/openapi/async.ts +38 -6
  310. package/src/components/openapi/helpers.ts +100 -12
  311. package/src/components/openapi/message-composer.ts +14 -1
  312. package/src/components/openapi/message-model.ts +12 -2
  313. package/src/components/openapi/message.ts +21 -3
  314. package/src/components/openapi/operation-model.ts +119 -17
  315. package/src/components/openapi/panel.ts +31 -5
  316. package/src/components/openapi/param-style.ts +181 -0
  317. package/src/components/openapi/playground-client.ts +110 -23
  318. package/src/components/openapi/playground-schema.ts +25 -6
  319. package/src/components/openapi/request.ts +192 -26
  320. package/src/components/openapi/schema-tree.ts +209 -0
  321. package/src/components/openapi/snippets.ts +113 -19
  322. package/src/components/openapi/validate-json.ts +1 -1
  323. package/src/components/openapi/ws-client.ts +18 -2
  324. package/src/core/base-path.ts +69 -8
  325. package/src/core/config-input.ts +13 -11
  326. package/src/core/config.ts +18 -4
  327. package/src/core/diagnostics.ts +219 -30
  328. package/src/core/directive-diagnostics.ts +99 -0
  329. package/src/core/frontmatter.ts +21 -18
  330. package/src/core/graph.ts +120 -11
  331. package/src/core/heading-markers.ts +5 -18
  332. package/src/core/i18n-ui.ts +39 -2
  333. package/src/core/i18n.ts +44 -5
  334. package/src/core/last-modified.ts +25 -3
  335. package/src/core/links.ts +7 -4
  336. package/src/core/load-module.ts +20 -0
  337. package/src/core/locale-links.ts +22 -18
  338. package/src/core/manifest.ts +3 -2
  339. package/src/core/meta.ts +89 -12
  340. package/src/core/nav-diagnostics.ts +56 -1
  341. package/src/core/navigation.ts +272 -83
  342. package/src/core/ordering-prefix.ts +27 -0
  343. package/src/core/project-graph.ts +50 -6
  344. package/src/core/safe-href.ts +53 -1
  345. package/src/core/safe-links.ts +11 -2
  346. package/src/core/schema.ts +94 -18
  347. package/src/core/sources/assets.ts +83 -41
  348. package/src/core/sources/contentful-rich-text.ts +28 -20
  349. package/src/core/sources/contentful.ts +25 -12
  350. package/src/core/sources/filesystem.ts +25 -3
  351. package/src/core/sources/github-releases.ts +131 -11
  352. package/src/core/sources/lexical.ts +30 -20
  353. package/src/core/sources/lower.ts +276 -56
  354. package/src/core/sources/mdx-remote.ts +52 -16
  355. package/src/core/sources/normalize.ts +456 -119
  356. package/src/core/sources/notion.ts +77 -34
  357. package/src/core/sources/obsidian.ts +40 -9
  358. package/src/core/sources/payload.ts +1 -0
  359. package/src/core/sources/portable-text.ts +85 -49
  360. package/src/core/sources/remote.ts +18 -2
  361. package/src/core/sources/resolve.ts +52 -33
  362. package/src/core/sources/sanity.ts +1 -0
  363. package/src/core/sources/strapi-blocks.ts +28 -19
  364. package/src/core/sources/strapi.ts +1 -0
  365. package/src/core/sources/types.ts +31 -0
  366. package/src/core/sources/watch.ts +20 -7
  367. package/src/core/standard-schema.ts +10 -6
  368. package/src/core/types.ts +9 -0
  369. package/src/core/ui-packs/ar.ts +13 -0
  370. package/src/core/ui-packs/bg.ts +13 -0
  371. package/src/core/ui-packs/bn.ts +13 -0
  372. package/src/core/ui-packs/ca.ts +13 -0
  373. package/src/core/ui-packs/cs.ts +13 -0
  374. package/src/core/ui-packs/da.ts +13 -0
  375. package/src/core/ui-packs/de.ts +13 -0
  376. package/src/core/ui-packs/el.ts +13 -0
  377. package/src/core/ui-packs/es.ts +13 -0
  378. package/src/core/ui-packs/fa.ts +13 -0
  379. package/src/core/ui-packs/fi.ts +13 -0
  380. package/src/core/ui-packs/fr.ts +13 -0
  381. package/src/core/ui-packs/he.ts +13 -0
  382. package/src/core/ui-packs/hi.ts +13 -0
  383. package/src/core/ui-packs/hr.ts +13 -0
  384. package/src/core/ui-packs/hu.ts +13 -0
  385. package/src/core/ui-packs/id.ts +13 -0
  386. package/src/core/ui-packs/it.ts +13 -0
  387. package/src/core/ui-packs/ja.ts +13 -0
  388. package/src/core/ui-packs/ko.ts +13 -0
  389. package/src/core/ui-packs/nl.ts +13 -0
  390. package/src/core/ui-packs/no.ts +13 -0
  391. package/src/core/ui-packs/pl.ts +13 -0
  392. package/src/core/ui-packs/pt-br.ts +13 -0
  393. package/src/core/ui-packs/pt.ts +13 -0
  394. package/src/core/ui-packs/ro.ts +13 -0
  395. package/src/core/ui-packs/ru.ts +13 -0
  396. package/src/core/ui-packs/sk.ts +13 -0
  397. package/src/core/ui-packs/sr.ts +13 -0
  398. package/src/core/ui-packs/sv.ts +13 -0
  399. package/src/core/ui-packs/th.ts +13 -0
  400. package/src/core/ui-packs/tr.ts +13 -0
  401. package/src/core/ui-packs/uk.ts +13 -0
  402. package/src/core/ui-packs/vi.ts +13 -0
  403. package/src/core/ui-packs/zh-tw.ts +13 -0
  404. package/src/core/ui-packs/zh.ts +13 -0
  405. package/src/core/version-cut.ts +69 -17
  406. package/src/core/versions.ts +4 -1
  407. package/src/deploy/adapters/node.ts +5 -2
  408. package/src/deploy/adapters/registry.ts +2 -1
  409. package/src/deploy/adapters/types.ts +13 -1
  410. package/src/deploy/artifacts.ts +52 -11
  411. package/src/deploy/cloudflare-negotiation.ts +27 -16
  412. package/src/deploy/headers.ts +75 -55
  413. package/src/deploy/node-headers.ts +148 -27
  414. package/src/deploy/platforms/cloudflare.ts +13 -3
  415. package/src/deploy/platforms/netlify.ts +83 -5
  416. package/src/deploy/platforms/node.ts +8 -5
  417. package/src/deploy/platforms/static.ts +2 -0
  418. package/src/deploy/platforms/types.ts +15 -0
  419. package/src/deploy/platforms/vercel.ts +10 -3
  420. package/src/deploy/redirects.ts +68 -21
  421. package/src/deploy/robots.ts +2 -2
  422. package/src/deploy/rss.ts +4 -3
  423. package/src/deploy/sitemap.ts +7 -5
  424. package/src/deploy/vercel-negotiation.ts +35 -3
  425. package/src/eval/agents.ts +10 -2
  426. package/src/eval/run.ts +25 -0
  427. package/src/markdown/base-links.ts +74 -29
  428. package/src/markdown/directives.ts +242 -36
  429. package/src/markdown/features.ts +17 -0
  430. package/src/markdown/index.ts +13 -9
  431. package/src/markdown/mdast.ts +5 -2
  432. package/src/markdown/relative-links.ts +15 -27
  433. package/src/markdown/route-snapshot.ts +37 -0
  434. package/src/og/card.ts +149 -9
  435. package/src/og/derive.ts +156 -5
  436. package/src/og/index.ts +1 -0
  437. package/src/openapi/asyncapi.ts +4 -2
  438. package/src/openapi/graphql-build.ts +28 -2
  439. package/src/openapi/model.ts +99 -27
  440. package/src/openapi/proxy.ts +63 -10
  441. package/src/openapi/render-mdx.ts +51 -3
  442. package/src/registry/eject.ts +297 -43
  443. package/src/search/adapters/version-scope.ts +30 -0
  444. package/src/search/documents.ts +89 -27
  445. package/src/search/popular.ts +2 -1
  446. package/src/search/sync/algolia.ts +36 -2
  447. package/src/seo/jsonld.ts +7 -3
  448. package/src/sources/registry.ts +5 -0
  449. package/src/theme/entry.ts +11 -4
  450. package/src/translate/agents.ts +6 -1
  451. package/src/translate/ledger.ts +26 -3
  452. package/src/translate/meta.ts +76 -24
  453. package/src/translate/run.ts +14 -8
  454. package/src/translate/validate.ts +14 -2
  455. package/src/translate/work-list.ts +91 -19
  456. package/src/upgrade/upgrade.ts +36 -4
  457. package/dist/cli/chunk-27g6wdth.js.map +0 -10
  458. package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
  459. package/dist/cli/chunk-6crbhc3x.js.map +0 -14
  460. package/dist/cli/chunk-6hsn950k.js.map +0 -10
  461. package/dist/cli/chunk-79jhk4py.js.map +0 -35
  462. package/dist/cli/chunk-82bbrxdn.js +0 -51
  463. package/dist/cli/chunk-82bbrxdn.js.map +0 -10
  464. package/dist/cli/chunk-abh8yjkn.js +0 -31
  465. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  466. package/dist/cli/chunk-ce574jw2.js +0 -23
  467. package/dist/cli/chunk-ce574jw2.js.map +0 -10
  468. package/dist/cli/chunk-dh8cwk36.js.map +0 -10
  469. package/dist/cli/chunk-epjnccmv.js.map +0 -10
  470. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  471. package/dist/cli/chunk-fs23ddbb.js.map +0 -35
  472. package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
  473. package/dist/cli/chunk-jwyddg7y.js.map +0 -15
  474. package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
  475. package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
  476. package/dist/cli/chunk-mb2919y2.js.map +0 -10
  477. package/dist/cli/chunk-q5163e60.js.map +0 -11
  478. package/dist/cli/chunk-qkqwkpte.js.map +0 -182
  479. package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
  480. package/dist/cli/chunk-s1p84fyh.js.map +0 -10
  481. package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
  482. package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
  483. package/dist/cli/chunk-yw7dm696.js.map +0 -14
  484. /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
  485. /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
  486. /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
  487. /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
  488. /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
  489. /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
  490. /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
  491. /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
  492. /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
@@ -4,6 +4,7 @@ import { mdxToMdast } from "satteri";
4
4
  import { parseYouTubeId } from "../components/content/youtube.ts";
5
5
  import type { ExampleLookup } from "../core/types.ts";
6
6
  import { MDX_FEATURES } from "../markdown/features.ts";
7
+ import { readStaticExpression } from "./static-expression.ts";
7
8
 
8
9
  /**
9
10
  * Downlevel Blume's MDX components to plain Markdown for agent-facing output
@@ -44,11 +45,19 @@ interface MdxAttribute {
44
45
 
45
46
  /** A single source replacement: `[start, end)` byte range → `text`. */
46
47
  interface Splice {
48
+ /**
49
+ * The range is a flow (block-level) element, so the line after it must
50
+ * start a block of its own.
51
+ */
52
+ block: boolean;
47
53
  end: number;
48
54
  start: number;
49
55
  text: string;
50
56
  }
51
57
 
58
+ /** Text that continues on the very next line, with no blank line between. */
59
+ const NEXT_LINE_TEXT = /^[\t ]*\n[\t ]*\S/u;
60
+
52
61
  /**
53
62
  * A statically-recovered data value. Parsed front matter and evaluated
54
63
  * attribute literals are both plain data — scalars, dates, arrays, and
@@ -135,33 +144,13 @@ export type ComponentMarkdown = (
135
144
  ) => string | null;
136
145
 
137
146
  /**
138
- * Statically evaluate an MDX attribute expression (`prop={...}`). Component
139
- * data props are object/array/number literals in practice; evaluation runs at
140
- * build time over the author's own content — the same trust level as the MDX
141
- * itself, which Astro compiles and executes. The page's `frontmatter` is in
142
- * scope, mirroring what Astro provides an MDX body at render time, so
143
- * `prop={frontmatter.status}` resolves; expressions that reference imports or
144
- * other scope throw and report as not evaluable.
147
+ * Read an element's attributes into a plain props object. An expression
148
+ * attribute (`prop={...}`) is read as literal data and never executed (see
149
+ * `static-expression.ts`): the downleveler also runs over plain `.md` pages
150
+ * and remote content, which nothing else executes. `prop={frontmatter.status}`
151
+ * resolves against the page's front matter, as it does when Astro renders
152
+ * the page; anything else — a call, an import — marks the props lossy.
145
153
  */
146
- const evaluateExpression = (
147
- raw: string,
148
- frontmatter: Record<string, EvaluatedValue> | undefined
149
- ) => {
150
- try {
151
- // Build-time eval of the author's own attribute literals; a throw falls
152
- // back to leaving the JSX verbatim.
153
- // oxlint-disable-next-line no-new-func
154
- const value: EvaluatedValue = new Function(
155
- "frontmatter",
156
- `"use strict"; return (${raw});`
157
- )(frontmatter);
158
- return { ok: true, value };
159
- } catch {
160
- return { ok: false, value: undefined };
161
- }
162
- };
163
-
164
- /** Evaluate an element's attributes into a plain props object. */
165
154
  const readProps = (
166
155
  node: MdastNode,
167
156
  frontmatter: Record<string, EvaluatedValue> | undefined
@@ -180,7 +169,7 @@ const readProps = (
180
169
  } else if (isString(attribute.value)) {
181
170
  props[attribute.name] = attribute.value;
182
171
  } else {
183
- const result = evaluateExpression(attribute.value.value, frontmatter);
172
+ const result = readStaticExpression(attribute.value.value, frontmatter);
184
173
  if (result.ok) {
185
174
  props[attribute.name] = result.value;
186
175
  } else {
@@ -208,14 +197,24 @@ const applySplices = (text: string, splices: Splice[]): string => {
208
197
  const lineStart = result.lastIndexOf("\n", splice.start - 1) + 1;
209
198
  const prefix = result.slice(lineStart, splice.start);
210
199
  const indent = /^[\t ]+$/u.test(prefix) ? prefix : "";
200
+ // The JSX's closing tag ended its block; the Markdown standing in for it
201
+ // doesn't. Text on the very next line would read as a lazy continuation
202
+ // of a blockquote or list item (`> Body` then `Next.` joins the quote),
203
+ // so a blank line keeps it out.
204
+ const spliced =
205
+ splice.block &&
206
+ splice.text !== "" &&
207
+ NEXT_LINE_TEXT.test(result.slice(splice.end))
208
+ ? `${splice.text}\n`
209
+ : splice.text;
211
210
  const replacement = indent
212
- ? splice.text
211
+ ? spliced
213
212
  .split("\n")
214
213
  .map((line, index) =>
215
214
  index === 0 || line === "" ? line : `${indent}${line}`
216
215
  )
217
216
  .join("\n")
218
- : splice.text;
217
+ : spliced;
219
218
  result =
220
219
  result.slice(0, splice.start) + replacement + result.slice(splice.end);
221
220
  }
@@ -888,15 +887,21 @@ const renderSlice = (
888
887
  const splices: Splice[] = [];
889
888
  // oxlint-disable-next-line no-use-before-define
890
889
  collectSplices(walk, nodes, splices);
890
+ // Start the slice at its line's indent, so a component that opens the
891
+ // slice is indented like the lines after it once replaced (see
892
+ // `applySplices`) and dedents with them; the final trim drops that indent
893
+ // from the first line again.
894
+ const indent = indentAt(walk.source, start);
895
+ const from = start - (indent ?? 0);
891
896
  const spliced = applySplices(
892
- walk.source.slice(start, end),
897
+ walk.source.slice(from, end),
893
898
  splices.map((splice) => ({
894
899
  ...splice,
895
- end: splice.end - start,
896
- start: splice.start - start,
900
+ end: splice.end - from,
901
+ start: splice.start - from,
897
902
  }))
898
903
  );
899
- return dedent(spliced, indentAt(walk.source, start)).trim();
904
+ return dedent(spliced, indent).trim();
900
905
  };
901
906
 
902
907
  /** The element's body as Markdown: the slice covering all of its children. */
@@ -996,6 +1001,7 @@ const collectSplices = (
996
1001
  const text = hasOffsets(node) ? downlevelComponentNode(node, walk) : null;
997
1002
  if (text !== null && hasOffsets(node)) {
998
1003
  out.push({
1004
+ block: node.type === "mdxJsxFlowElement",
999
1005
  end: node.position.end.offset,
1000
1006
  start: node.position.start.offset,
1001
1007
  text,
@@ -1015,10 +1021,10 @@ const collectSplices = (
1015
1021
  * over the built-ins: a same-name entry replaces the built-in serializer, and
1016
1022
  * one that always returns `null` effectively opts that component out.
1017
1023
  *
1018
- * `frontmatter` is the page's parsed front-matter data. It is put in scope
1019
- * when evaluating attribute expressions — so `prop={frontmatter.status}`
1020
- * resolves the way it does when Astro renders the page — and handed to
1021
- * serializers on their context.
1024
+ * `frontmatter` is the page's parsed front-matter data. Attribute
1025
+ * expressions that reference it — `prop={frontmatter.status}` — resolve the
1026
+ * way they do when Astro renders the page, and it is handed to serializers on
1027
+ * their context. Expressions are read as literal data, never executed.
1022
1028
  */
1023
1029
  export const downlevelComponents = (
1024
1030
  source: string,
@@ -5,7 +5,11 @@ import {
5
5
  AI_CATALOG_TYPE,
6
6
  hasAiCatalog,
7
7
  } from "./ai-catalog.ts";
8
- import { API_CATALOG_PATH, hasApiCatalog } from "./api-catalog.ts";
8
+ import {
9
+ API_CATALOG_PATH,
10
+ API_CATALOG_TYPE,
11
+ hasApiCatalog,
12
+ } from "./api-catalog.ts";
9
13
  import { OPENAPI_PATH } from "./api/paths.ts";
10
14
 
11
15
  /**
@@ -29,23 +33,33 @@ import { OPENAPI_PATH } from "./api/paths.ts";
29
33
  * headers. Targets are
30
34
  * root-relative under `deployment.base` — RFC 8288 resolves them against the
31
35
  * request URL. Returns null when nothing is advertisable.
36
+ *
37
+ * `surface` is where the header is sent. A deployed site (`"build"`) serves
38
+ * every target. The dev server (`"dev"`) serves the routes — the JSON API's
39
+ * OpenAPI document and the Markdown mirror — but not the files `blume build`
40
+ * writes after the build (the API and AI catalogs, `agent-readability.json`,
41
+ * `llms.txt`), so its header leaves those out rather than point agents at
42
+ * 404s.
32
43
  */
33
44
  export const buildHomeLinkHeader = (
34
45
  config: ResolvedConfig,
35
- routePaths: readonly string[]
46
+ routePaths: readonly string[],
47
+ surface: "build" | "dev" = "build"
36
48
  ): string | null => {
37
49
  const deployBase = normalizeBasePath(config.deployment.options.base);
50
+ const artifacts = surface === "build";
38
51
  const links: string[] = [];
39
52
  // RFC 9727 §3: the api-catalog relation is how a homepage advertises the
40
- // well-known catalog.
41
- if (hasApiCatalog(config)) {
53
+ // well-known catalog. Its type carries the RFC 9727 profile, whose quotes
54
+ // are escaped inside the quoted `type` value (RFC 8288 quoted-string).
55
+ if (artifacts && hasApiCatalog(config)) {
42
56
  links.push(
43
- `<${deployBase}${API_CATALOG_PATH}>; rel="api-catalog"; type="application/linkset+json"`
57
+ `<${deployBase}${API_CATALOG_PATH}>; rel="api-catalog"; type="${API_CATALOG_TYPE.replaceAll('"', String.raw`\"`)}"`
44
58
  );
45
59
  }
46
60
  // The ai-catalog spec's own relation for its well-known document, the
47
61
  // header form of the `<link rel="ai-catalog">` every page carries.
48
- if (hasAiCatalog(config)) {
62
+ if (artifacts && hasAiCatalog(config)) {
49
63
  links.push(
50
64
  `<${deployBase}${AI_CATALOG_PATH}>; rel="ai-catalog"; type="${AI_CATALOG_TYPE}"`
51
65
  );
@@ -57,12 +71,12 @@ export const buildHomeLinkHeader = (
57
71
  `<${deployBase}${OPENAPI_PATH}>; rel="service-desc"; type="application/json"`
58
72
  );
59
73
  }
60
- if (config.agents.agentReadability) {
74
+ if (artifacts && config.agents.agentReadability) {
61
75
  links.push(
62
76
  `<${deployBase}/agent-readability.json>; rel="describedby"; type="application/json"`
63
77
  );
64
78
  }
65
- if (config.agents.llmsTxt.enabled) {
79
+ if (artifacts && config.agents.llmsTxt.enabled) {
66
80
  links.push(
67
81
  `<${deployBase}/llms.txt>; rel="describedby"; type="text/plain"`
68
82
  );
package/src/ai/llms.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
1
+ import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
2
2
  import { rewriteRelativeImages } from "../core/content-assets.ts";
3
3
  import matter from "../core/frontmatter.ts";
4
4
  import type { BlumeProject } from "../core/project-graph.ts";
@@ -17,14 +17,22 @@ import type { SkillArtifact } from "./skills.ts";
17
17
  import { applyAgentVisibility } from "./visibility.ts";
18
18
 
19
19
  // Routes carry `basePath`; a `deployment.base` subdirectory is layered on top —
20
- // with or without a `site` (the mcp.json convention) — so the emitted URL
21
- // matches where the page is served. Encoded like the sitemap: a route with
22
- // spaces or non-ASCII must still yield a valid Markdown link.
20
+ // with or without a `site` (the mcp.json convention), and even over a route
21
+ // that starts with the base's own name — so the emitted URL matches where the
22
+ // page is served. Encoded like the sitemap: a route with spaces or non-ASCII
23
+ // must still yield a valid Markdown link.
23
24
  const pageUrl = (route: string, site?: string, base = ""): string => {
24
- const path = withBasePath(base, route);
25
+ const path = mountBasePath(base, route);
25
26
  return encodeURI(site ? absoluteUrl(site, path) : path);
26
27
  };
27
28
 
29
+ /**
30
+ * A title as Markdown link text. An unescaped bracket in it — `[Beta] Webhooks`
31
+ * — closes or nests the text early, and the line stops being a link.
32
+ */
33
+ const linkText = (title: string): string =>
34
+ title.replaceAll(/[\\[\]]/gu, String.raw`\$&`);
35
+
28
36
  /** Inputs only the build has: the published skills, collected once per build. */
29
37
  export interface LlmsIndexOptions {
30
38
  /**
@@ -176,7 +184,7 @@ export const buildLlmsIndex = (
176
184
  const line = (page: PageRecord): string => {
177
185
  seen.add(page.route);
178
186
  const summary = page.description ? `: ${page.description}` : "";
179
- return `- [${page.title}](${pageUrl(page.route, site, base)})${summary}`;
187
+ return `- [${linkText(page.title)}](${pageUrl(page.route, site, base)})${summary}`;
180
188
  };
181
189
 
182
190
  // One nav level -> Markdown blocks: the level's loose pages as a link list,
@@ -259,7 +267,10 @@ export const buildLlmsIndex = (
259
267
  blocks.push(
260
268
  "## RSS Feeds",
261
269
  feeds
262
- .map((feed) => `- [${feed.title}](${pageUrl(feed.path, site, base)})`)
270
+ .map(
271
+ (feed) =>
272
+ `- [${linkText(feed.title)}](${pageUrl(feed.path, site, base)})`
273
+ )
263
274
  .join("\n")
264
275
  );
265
276
  }
@@ -312,10 +323,11 @@ const buildFull = async (project: BlumeProject): Promise<string> => {
312
323
  source: raw,
313
324
  sourcePath: page.sourcePath,
314
325
  });
315
- // Relative page links too: a reader of one flat file has no page URL
316
- // to resolve `./install` against.
317
- raw = rewriteLinks(raw, page);
318
326
  }
327
+ // Page links too: a reader of one flat file has no page URL to resolve
328
+ // `./install` against, and `/install` needs the base the site is
329
+ // served under.
330
+ raw = rewriteLinks(raw, page);
319
331
  // Resolve `<Visibility>` audiences (web-only content omitted from the
320
332
  // agent-facing output, agents-only unwrapped), then downlevel supported
321
333
  // components to plain Markdown.
@@ -8,6 +8,7 @@ import matter from "../core/frontmatter.ts";
8
8
  import type { BlumeProject } from "../core/project-graph.ts";
9
9
  import { readExpandedEntryText } from "../core/sources/read.ts";
10
10
  import type { RouteManifestEntry } from "../core/types.ts";
11
+ import { advertisedConfig, servesMcp } from "./agent-surface.ts";
11
12
  import { buildChangelogIndexMarkdown } from "./changelog-markdown.ts";
12
13
  import { downlevelComponents } from "./component-markdown.ts";
13
14
  import { buildLlmsIndex } from "./llms.ts";
@@ -80,13 +81,14 @@ export const buildRawMarkdown = async (
80
81
  source: text,
81
82
  sourcePath: route.sourcePath,
82
83
  });
83
- // `./install` means the page's sibling, not whatever the `.md` URL
84
- // an agent fetched resolves it to.
85
- text = rewriteLinks(text, {
86
- route: route.path,
87
- sourcePath: route.sourcePath,
88
- });
89
84
  }
85
+ // `./install` means the page's sibling, not whatever the `.md` URL an
86
+ // agent fetched resolves it to, and `/install` gains the base and the
87
+ // page's locale the rendered link has — remote content included.
88
+ text = rewriteLinks(text, {
89
+ route: route.path,
90
+ sourcePath: route.sourcePath,
91
+ });
90
92
  const source = applyAgentVisibility(text);
91
93
  // The `.md` variant keeps the front-matter block in the output, but its
92
94
  // data must also be in scope for `prop={frontmatter.*}` expressions.
@@ -97,19 +99,29 @@ export const buildRawMarkdown = async (
97
99
  })
98
100
  );
99
101
  const map = Object.fromEntries(entries);
102
+ const userPages = project.context.pagesRoot
103
+ ? await discoverPages(project.context.pagesRoot)
104
+ : [];
100
105
  // A landing-page homepage (user `.astro` page, or no home route at all) has
101
106
  // no Markdown source, but agents negotiating `Accept: text/markdown` on `/`
102
107
  // still expect a Markdown answer. The llms.txt index — the machine-readable
103
108
  // representation of the site a landing page fronts — becomes its mirror, so
104
- // `/index.md` always exists (see `markdownRoutePaths`).
109
+ // `/index.md` always exists (see `markdownRoutePaths`). Like llms.txt, it
110
+ // leaves out an MCP server a page's route kept from being generated.
105
111
  if (!map["/"]) {
106
- map["/"] = { mdx: buildLlmsIndex(project) };
112
+ map["/"] = {
113
+ mdx: buildLlmsIndex({
114
+ ...project,
115
+ config: advertisedConfig(project.config, {
116
+ mcp: servesMcp(project, userPages),
117
+ // Skills are collected at the end of the build, after this runs.
118
+ skills: Boolean(project.config.agents.skills),
119
+ }),
120
+ }),
121
+ };
107
122
  }
108
123
  // The generated changelog index has no source either; its release list is
109
124
  // its mirror, so `/changelog.md` and `get_page` answer as the page does.
110
- const userPages = project.context.pagesRoot
111
- ? await discoverPages(project.context.pagesRoot)
112
- : [];
113
125
  if (hasGeneratedChangelog(project, userPages)) {
114
126
  map[CHANGELOG_INDEX_ROUTE] = { mdx: buildChangelogIndexMarkdown(project) };
115
127
  }
@@ -1,4 +1,4 @@
1
- import { withBasePath } from "../../core/base-path.ts";
1
+ import { mountBasePath } from "../../core/base-path.ts";
2
2
  import { absoluteUrl, siteRoot } from "../../core/site-url.ts";
3
3
  import { trimChar } from "../../core/trim.ts";
4
4
  import { MCP_TOOLS } from "./tools.ts";
@@ -18,7 +18,7 @@ export interface McpDiscoveryInput {
18
18
  const serverUrl = (input: McpDiscoveryInput): string => {
19
19
  // The endpoint is a generated Astro page, so it's served under
20
20
  // `deployment.base` like every other route (the sitemap/llms.txt convention).
21
- const path = withBasePath(input.base, input.route);
21
+ const path = mountBasePath(input.base, input.route);
22
22
  // Concatenate rather than `new URL(path, site)` — a root-absolute path
23
23
  // would drop the base path of a subpath deployment (`acme.com/docs`).
24
24
  return input.site ? absoluteUrl(input.site, path) : path;
@@ -1,9 +1,9 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import {
4
+ mountBasePath,
4
5
  normalizeRoute as normalizePageRoute,
5
6
  stripBasePath,
6
- withBasePath,
7
7
  } from "../../core/base-path.ts";
8
8
  import { absoluteUrl } from "../../core/site-url.ts";
9
9
  import { trimEnd } from "../../core/trim.ts";
@@ -244,7 +244,7 @@ export const normalizeRoute = (input: string, data: McpData): string => {
244
244
  export const urlFor = (route: string, data: McpData): string => {
245
245
  // Routes are base-less manifest paths; layer `deployment.base` on top so the
246
246
  // URL matches where the page is served (the sitemap/llms.txt convention).
247
- const path = withBasePath(data.base, route);
247
+ const path = mountBasePath(data.base, route);
248
248
  // Concatenate rather than `new URL(path, site)` — a root-absolute path
249
249
  // would drop the base path of a subpath deployment (`acme.com/docs`).
250
250
  return data.site ? absoluteUrl(data.site, path) : path;
@@ -2,7 +2,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import type { ServerOptions } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
4
4
  import {
5
- CallToolRequestSchema,
5
+ ErrorCode,
6
6
  ListResourcesRequestSchema,
7
7
  ListToolsRequestSchema,
8
8
  McpError,
@@ -10,7 +10,8 @@ import {
10
10
  } from "@modelcontextprotocol/sdk/types.js";
11
11
  import { z } from "zod";
12
12
 
13
- import { withBasePath } from "../../core/base-path.ts";
13
+ import { mountBasePath } from "../../core/base-path.ts";
14
+ import { readCappedText } from "../../core/request-body.ts";
14
15
  import type { McpData } from "./data.ts";
15
16
  import {
16
17
  createIndexProvider,
@@ -42,6 +43,12 @@ const RESOURCE_MIME_TYPE = "text/markdown";
42
43
  const RESOURCE_NOT_FOUND = -32_002;
43
44
  /** URI scheme for page resources when no `deployment.site` is configured. */
44
45
  const LOCAL_RESOURCE_SCHEME = "blume:";
46
+ /**
47
+ * The largest request body the HTTP endpoint reads. A JSON-RPC call to these
48
+ * tools is a few hundred bytes; the cap keeps one oversized POST from being
49
+ * buffered whole, as the assistant route's does.
50
+ */
51
+ const MCP_BODY_LIMIT_BYTES = 65_536;
45
52
 
46
53
  const CORS_HEADERS = {
47
54
  "Access-Control-Allow-Headers":
@@ -97,7 +104,7 @@ interface PageResource {
97
104
  const resourceUri = (route: string, data: McpData): string =>
98
105
  data.site
99
106
  ? urlFor(route, data)
100
- : `${LOCAL_RESOURCE_SCHEME}${withBasePath(data.base, route)}`;
107
+ : `${LOCAL_RESOURCE_SCHEME}${mountBasePath(data.base, route)}`;
101
108
 
102
109
  /** The `pages` key a resource URI (either form, or a bare route) names. */
103
110
  const resourceRoute = (uri: string, data: McpData): string =>
@@ -108,6 +115,20 @@ const resourceRoute = (uri: string, data: McpData): string =>
108
115
  data
109
116
  );
110
117
 
118
+ /**
119
+ * The `tools/call` params, as far as the server reads them before dispatch:
120
+ * each tool validates its own `arguments` against `TOOL_INPUTS`.
121
+ */
122
+ const TOOL_CALL_PARAMS = z.object({
123
+ arguments: z.record(z.string(), z.unknown()).optional(),
124
+ name: z.string(),
125
+ });
126
+
127
+ /** A tool call's `arguments` object, before the tool's own validation. */
128
+ type ToolArguments = NonNullable<
129
+ z.output<typeof TOOL_CALL_PARAMS>["arguments"]
130
+ >;
131
+
111
132
  /** A tool call's text result, marked as an error when `isError` is set. */
112
133
  const text = (value: string, isError = false) => {
113
134
  const content = [{ text: value, type: "text" as const }];
@@ -165,9 +186,7 @@ export const buildServer = (
165
186
  };
166
187
  });
167
188
 
168
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
169
- const { arguments: args = {}, name } = request.params;
170
-
189
+ const callTool = async (name: string, args: ToolArguments) => {
171
190
  if (name === "search_docs") {
172
191
  const results = await searchDocs(
173
192
  data,
@@ -206,12 +225,44 @@ export const buildServer = (
206
225
  return text(JSON.stringify(result.navigation, null, 2));
207
226
  }
208
227
 
209
- return text(`Unknown tool: ${name}`, true);
210
- });
228
+ // The MCP spec answers an unknown tool with a protocol error, not a
229
+ // failed tool result.
230
+ throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${name}`);
231
+ };
232
+
233
+ // `tools/call` goes through the fallback handler rather than a registered
234
+ // one: the SDK parses a registered handler's request against its own
235
+ // schema first and reports a malformed call (`arguments` that aren't an
236
+ // object) as -32603 Internal error carrying a raw Zod dump. Reading the
237
+ // params here answers it as -32602 Invalid params with a short message.
238
+ server.fallbackRequestHandler = async (request) => {
239
+ if (request.method !== "tools/call") {
240
+ throw new McpError(ErrorCode.MethodNotFound, "Method not found");
241
+ }
242
+ const params = TOOL_CALL_PARAMS.safeParse(request.params);
243
+ if (!params.success) {
244
+ throw new McpError(
245
+ ErrorCode.InvalidParams,
246
+ "Invalid tools/call params: `name` must be a string and `arguments`, when given, an object."
247
+ );
248
+ }
249
+ return await callTool(params.data.name, params.data.arguments ?? {});
250
+ };
211
251
 
212
252
  return server;
213
253
  };
214
254
 
255
+ /** A JSON-RPC error response the transport would send, for errors raised before it runs. */
256
+ const jsonRpcError = (
257
+ status: number,
258
+ code: number,
259
+ message: string
260
+ ): Response =>
261
+ Response.json(
262
+ { error: { code, message }, id: null, jsonrpc: "2.0" },
263
+ { headers: CORS_HEADERS, status }
264
+ );
265
+
215
266
  /**
216
267
  * Build a stateless Streamable-HTTP MCP request handler from a data snapshot.
217
268
  *
@@ -239,6 +290,26 @@ export const createMcpFetchHandler = (
239
290
  });
240
291
  }
241
292
 
293
+ // The transport would read the body with `request.json()`, buffering
294
+ // whatever a client sends; read it under the cap first and hand the
295
+ // transport a copy.
296
+ let forwarded = request;
297
+ if (request.method === "POST") {
298
+ const body = await readCappedText(request, MCP_BODY_LIMIT_BYTES);
299
+ if (body === undefined) {
300
+ return jsonRpcError(
301
+ 413,
302
+ ErrorCode.InvalidRequest,
303
+ "Request too large: the body must be at most 64 KB."
304
+ );
305
+ }
306
+ forwarded = new Request(request.url, {
307
+ body,
308
+ headers: request.headers,
309
+ method: "POST",
310
+ });
311
+ }
312
+
242
313
  const server = buildServer(data, index);
243
314
  const transport = new WebStandardStreamableHTTPServerTransport({
244
315
  enableJsonResponse: true,
@@ -248,7 +319,7 @@ export const createMcpFetchHandler = (
248
319
  sessionIdGenerator: undefined,
249
320
  });
250
321
  await server.connect(transport);
251
- const response = await transport.handleRequest(request);
322
+ const response = await transport.handleRequest(forwarded);
252
323
 
253
324
  const headers = new Headers(response.headers);
254
325
  for (const [key, value] of Object.entries(CORS_HEADERS)) {
@@ -1,3 +1,4 @@
1
+ import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
1
2
  import type { ApiOperationRef, OpenApiData } from "../openapi/model.ts";
2
3
  import { operationOf, specAddresses, specOf } from "../openapi/model.ts";
3
4
  import { asSentence } from "../openapi/sentence.ts";
@@ -20,10 +21,14 @@ const inlineText = (text: string): string =>
20
21
  .replaceAll(/\s+/gu, " ")
21
22
  .replaceAll(/[\\`*_[\]<>~]/gu, String.raw`\$&`);
22
23
 
23
- /** One operation as a link, for the tag listings. */
24
+ /**
25
+ * One operation as a link, for the tag listings. `href` is the operation's
26
+ * served URL: its route under the deployment base.
27
+ */
24
28
  const listItem = (
25
29
  signature: string,
26
- operation: Pick<ApiOperationRef, "deprecated" | "route" | "summary">
30
+ href: string,
31
+ operation: Pick<ApiOperationRef, "deprecated" | "summary">
27
32
  ): string => {
28
33
  const tail = [
29
34
  // Title-like summaries ("Get a flag") need their own period before the
@@ -33,7 +38,7 @@ const listItem = (
33
38
  ]
34
39
  .filter(Boolean)
35
40
  .join(" ");
36
- return `- [${inlineCode(signature)}](${linkDestination(operation.route)})${tail ? ` — ${tail}` : ""}`;
41
+ return `- [${inlineCode(signature)}](${linkDestination(href)})${tail ? ` — ${tail}` : ""}`;
37
42
  };
38
43
 
39
44
  /**
@@ -60,9 +65,16 @@ const listItem = (
60
65
  * endpoint and what it does is the part that was missing altogether.
61
66
  *
62
67
  * `specs` is the parsed `blume:openapi` data — empty when the project has no
63
- * API reference, in which case every serializer declines.
68
+ * API reference, in which case every serializer declines. `deployBase` is the
69
+ * configured `deployment.base`: operation routes carry `basePath` but not it,
70
+ * and the links written here are made after the agent surfaces rebase the
71
+ * page's own links, so they take it here, as the rendered page's do.
64
72
  */
65
- export const openapiComponentSerializers = (specs: OpenApiData) => {
73
+ export const openapiComponentSerializers = (
74
+ specs: OpenApiData,
75
+ deployBase?: string
76
+ ) => {
77
+ const base = normalizeBasePath(deployBase);
66
78
  const spec = (source: EvaluatedValue) =>
67
79
  isString(source) ? specOf(specs, source) : undefined;
68
80
 
@@ -97,7 +109,11 @@ export const openapiComponentSerializers = (specs: OpenApiData) => {
97
109
  const items = Object.values(data.operations)
98
110
  .filter((operation) => operation.tagSlug === tag)
99
111
  .map((operation) =>
100
- listItem(operationSignature(data, operation), operation)
112
+ listItem(
113
+ operationSignature(data, operation),
114
+ mountBasePath(base, operation.route),
115
+ operation
116
+ )
101
117
  );
102
118
  return items.length > 0 ? items.join("\n") : null;
103
119
  },