blume 2.0.1 → 2.0.2

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 (410) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/cli/{chunk-vtk4a6dg.js → chunk-00gs3wqs.js} +1 -1
  3. package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
  4. package/dist/cli/chunk-273ygyr4.js.map +11 -0
  5. package/dist/cli/{chunk-yw7dm696.js → chunk-3yce002v.js} +21 -8
  6. package/dist/cli/{chunk-yw7dm696.js.map → chunk-3yce002v.js.map} +3 -3
  7. package/dist/cli/{chunk-yt5n7ppj.js → chunk-4e9b9ra6.js} +17 -7
  8. package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
  9. package/dist/cli/{chunk-fs23ddbb.js → chunk-5r8g91qn.js} +352 -676
  10. package/dist/cli/chunk-5r8g91qn.js.map +34 -0
  11. package/dist/cli/{chunk-6vm74dry.js → chunk-6dtt0zfn.js} +8 -8
  12. package/dist/cli/{chunk-qwsrynx5.js → chunk-6k4ftwze.js} +38 -19
  13. package/dist/cli/{chunk-qwsrynx5.js.map → chunk-6k4ftwze.js.map} +4 -4
  14. package/dist/cli/{chunk-qs4q5p4e.js → chunk-7mbqtmgb.js} +16 -7
  15. package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
  16. package/dist/cli/{chunk-q5163e60.js → chunk-7vtckvaw.js} +21 -19
  17. package/dist/cli/chunk-7vtckvaw.js.map +11 -0
  18. package/dist/cli/{chunk-kdp5q7ke.js → chunk-8g8ytmgx.js} +17 -18
  19. package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-8g8ytmgx.js.map} +2 -2
  20. package/dist/cli/{chunk-epjnccmv.js → chunk-91ws1n6j.js} +18 -15
  21. package/dist/cli/chunk-91ws1n6j.js.map +10 -0
  22. package/dist/cli/{chunk-fxypxtvm.js → chunk-bbnwccaz.js} +2 -2
  23. package/dist/cli/{chunk-m3vmjgmq.js → chunk-bfwp9vp6.js} +16 -8
  24. package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
  25. package/dist/cli/{chunk-f2z5v128.js → chunk-d1v5rhy0.js} +14 -15
  26. package/dist/cli/{chunk-f2z5v128.js.map → chunk-d1v5rhy0.js.map} +2 -2
  27. package/dist/cli/{chunk-6crbhc3x.js → chunk-ddndchfr.js} +21 -6
  28. package/dist/cli/chunk-ddndchfr.js.map +14 -0
  29. package/dist/cli/{chunk-ce574jw2.js → chunk-esh98wmb.js} +1 -1
  30. package/dist/cli/{chunk-zxcczpyx.js → chunk-fsmrqk8a.js} +1 -1
  31. package/dist/cli/{chunk-hdpx1tax.js → chunk-g698a744.js} +5 -5
  32. package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
  33. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
  34. package/dist/cli/{chunk-zxh4d9vy.js → chunk-h2ez8dzb.js} +4 -4
  35. package/dist/cli/{chunk-5shv93fd.js → chunk-h7k3nq3v.js} +2 -2
  36. package/dist/cli/{chunk-79jhk4py.js → chunk-hqp2ajnh.js} +251 -122
  37. package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
  38. package/dist/cli/{chunk-2hn4b8z7.js → chunk-hr8ne106.js} +109 -40
  39. package/dist/cli/chunk-hr8ne106.js.map +13 -0
  40. package/dist/cli/{chunk-6hsn950k.js → chunk-j85scx15.js} +62 -18
  41. package/dist/cli/chunk-j85scx15.js.map +10 -0
  42. package/dist/cli/{chunk-s1p84fyh.js → chunk-k7pj68a8.js} +63 -22
  43. package/dist/cli/chunk-k7pj68a8.js.map +11 -0
  44. package/dist/cli/{chunk-ah61y8py.js → chunk-mqc662a6.js} +2 -2
  45. package/dist/cli/{chunk-ch6g3ar0.js → chunk-n1yg3tj3.js} +4 -4
  46. package/dist/cli/{chunk-mb2919y2.js → chunk-nfcyttvj.js} +17 -6
  47. package/dist/cli/chunk-nfcyttvj.js.map +10 -0
  48. package/dist/cli/{chunk-kpf8rrjc.js → chunk-pbg5a4s3.js} +56 -24
  49. package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
  50. package/dist/cli/{chunk-jwyddg7y.js → chunk-ppzjqwx2.js} +21 -14
  51. package/dist/cli/{chunk-jwyddg7y.js.map → chunk-ppzjqwx2.js.map} +4 -4
  52. package/dist/cli/{chunk-dh8cwk36.js → chunk-qkb5a8sa.js} +24 -9
  53. package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
  54. package/dist/cli/{chunk-wm7js3j9.js → chunk-sqw4ekg1.js} +2 -2
  55. package/dist/cli/{chunk-qkqwkpte.js → chunk-v6ya5kcb.js} +3083 -953
  56. package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
  57. package/dist/cli/{chunk-fz5wtpmh.js → chunk-w4bxdvsa.js} +18 -15
  58. package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
  59. package/dist/cli/{chunk-27g6wdth.js → chunk-wdrt2k2v.js} +2 -2
  60. package/dist/cli/{chunk-wgm7m9qk.js → chunk-xh43dwgw.js} +158 -43
  61. package/dist/cli/chunk-xh43dwgw.js.map +36 -0
  62. package/dist/cli/{chunk-s6jhgk0q.js → chunk-zp79m0ts.js} +2 -2
  63. package/dist/cli/index.js +160 -35
  64. package/dist/cli/index.js.map +4 -4
  65. package/dist/types/ai/agent-surface.d.ts +32 -0
  66. package/dist/types/ai/api-catalog.d.ts +7 -1
  67. package/dist/types/ai/ask-context.d.ts +7 -0
  68. package/dist/types/ai/component-markdown.d.ts +4 -4
  69. package/dist/types/ai/relative-links.d.ts +9 -4
  70. package/dist/types/ai/static-expression.d.ts +28 -0
  71. package/dist/types/analytics/databuddy.d.ts +43 -0
  72. package/dist/types/analytics/index.d.ts +2 -0
  73. package/dist/types/analytics/schema.d.ts +14 -0
  74. package/dist/types/cli/env.d.ts +5 -0
  75. package/dist/types/cli/init/scaffold.d.ts +19 -3
  76. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  77. package/dist/types/core/base-path.d.ts +9 -0
  78. package/dist/types/core/config-input.d.ts +4 -4
  79. package/dist/types/core/config.d.ts +2 -2
  80. package/dist/types/core/graph.d.ts +2 -0
  81. package/dist/types/core/i18n-ui.d.ts +31 -0
  82. package/dist/types/core/i18n.d.ts +7 -1
  83. package/dist/types/core/links.d.ts +3 -1
  84. package/dist/types/core/load-module.d.ts +10 -0
  85. package/dist/types/core/locale-links.d.ts +12 -2
  86. package/dist/types/core/meta.d.ts +5 -1
  87. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  88. package/dist/types/core/navigation.d.ts +38 -0
  89. package/dist/types/core/ordering-prefix.d.ts +4 -0
  90. package/dist/types/core/safe-links.d.ts +3 -1
  91. package/dist/types/core/schema.d.ts +52 -13
  92. package/dist/types/core/sources/github-releases.d.ts +5 -0
  93. package/dist/types/core/sources/lower.d.ts +14 -2
  94. package/dist/types/core/sources/normalize.d.ts +10 -1
  95. package/dist/types/core/sources/remote.d.ts +11 -1
  96. package/dist/types/core/sources/resolve.d.ts +12 -0
  97. package/dist/types/core/sources/types.d.ts +31 -0
  98. package/dist/types/core/types.d.ts +9 -0
  99. package/dist/types/deploy/adapters/node.d.ts +5 -2
  100. package/dist/types/deploy/adapters/types.d.ts +7 -0
  101. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  102. package/dist/types/deploy/headers.d.ts +31 -7
  103. package/dist/types/deploy/node-headers.d.ts +43 -8
  104. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  105. package/dist/types/deploy/platforms/node.d.ts +6 -5
  106. package/dist/types/deploy/platforms/types.d.ts +7 -0
  107. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  108. package/dist/types/deploy/redirects.d.ts +7 -2
  109. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  110. package/dist/types/openapi/model.d.ts +20 -6
  111. package/dist/types/search/sync/algolia.d.ts +3 -1
  112. package/docs/01-quickstart.mdx +3 -2
  113. package/docs/02-deployment.mdx +17 -8
  114. package/docs/08-faq.mdx +9 -2
  115. package/docs/advanced/changelog.mdx +1 -1
  116. package/docs/advanced/custom-pages.mdx +4 -2
  117. package/docs/cli/audit.mdx +2 -2
  118. package/docs/cli/doctor.mdx +2 -2
  119. package/docs/cli/evals.mdx +2 -2
  120. package/docs/cli/index.mdx +4 -1
  121. package/docs/cli/translate.mdx +1 -1
  122. package/docs/configuration/analytics.mdx +23 -2
  123. package/docs/configuration/assistant.mdx +1 -1
  124. package/docs/configuration/customization.mdx +4 -3
  125. package/docs/configuration/index.mdx +5 -3
  126. package/docs/configuration/search.mdx +1 -1
  127. package/docs/content/components.mdx +1 -1
  128. package/docs/content/frontmatter.mdx +5 -1
  129. package/docs/content/i18n.mdx +1 -1
  130. package/docs/content/index.mdx +4 -2
  131. package/docs/content/meta.mdx +5 -3
  132. package/docs/content/navigation.mdx +29 -4
  133. package/docs/content/sources.mdx +18 -12
  134. package/docs/content/versioning.mdx +1 -0
  135. package/docs/discoverability/agent-discovery.mdx +21 -7
  136. package/docs/discoverability/index.mdx +2 -2
  137. package/docs/discoverability/llms-txt.mdx +2 -5
  138. package/docs/discoverability/markdown.mdx +5 -3
  139. package/docs/discoverability/mcp.mdx +4 -0
  140. package/docs/discoverability/metadata.mdx +3 -2
  141. package/docs/discoverability/open-graph.mdx +6 -4
  142. package/docs/discoverability/rss.mdx +3 -1
  143. package/docs/references/asyncapi.mdx +1 -1
  144. package/docs/references/graphql.mdx +2 -2
  145. package/docs/references/openapi.mdx +3 -3
  146. package/package.json +1 -1
  147. package/skills/blume-migrate/SKILL.md +6 -6
  148. package/skills/blume-migrate/references/docusaurus.md +6 -6
  149. package/skills/blume-migrate/references/mintlify.md +3 -3
  150. package/skills/blume-migrate/references/monorepo.md +1 -1
  151. package/skills/blume-migrate/references/nextra.md +1 -1
  152. package/skills/blume-migrate/references/starlight.md +5 -5
  153. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
  154. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  155. package/src/ai/agent-surface.ts +56 -0
  156. package/src/ai/api/spec.ts +15 -2
  157. package/src/ai/api-catalog.ts +7 -1
  158. package/src/ai/ask-context.ts +14 -2
  159. package/src/ai/ask-data.ts +26 -12
  160. package/src/ai/component-markdown.ts +43 -37
  161. package/src/ai/link-headers.ts +8 -3
  162. package/src/ai/llms.ts +4 -3
  163. package/src/ai/markdown.ts +23 -11
  164. package/src/ai/mcp/server.ts +78 -7
  165. package/src/ai/relative-links.ts +78 -10
  166. package/src/ai/static-expression.ts +416 -0
  167. package/src/ai/visibility.ts +45 -14
  168. package/src/analytics/databuddy.ts +67 -0
  169. package/src/analytics/head.ts +4 -0
  170. package/src/analytics/index.ts +2 -0
  171. package/src/analytics/posthog.ts +21 -3
  172. package/src/analytics/schema.ts +2 -0
  173. package/src/astro/generate.ts +71 -27
  174. package/src/astro/templates.ts +61 -23
  175. package/src/audit/catalog.ts +2 -2
  176. package/src/audit/checks/assets.ts +25 -5
  177. package/src/audit/checks/content.ts +20 -2
  178. package/src/audit/checks/i18n.ts +46 -8
  179. package/src/audit/checks/indexability.ts +39 -19
  180. package/src/audit/checks/links.ts +14 -0
  181. package/src/audit/checks/llms.ts +6 -3
  182. package/src/audit/checks/network.ts +3 -1
  183. package/src/audit/checks/og-image.ts +10 -0
  184. package/src/audit/checks/robots.ts +6 -1
  185. package/src/audit/checks/sitemap.ts +56 -31
  186. package/src/audit/checks/social.ts +21 -2
  187. package/src/audit/crawl.ts +88 -15
  188. package/src/audit/report.ts +54 -17
  189. package/src/audit/run.ts +1 -0
  190. package/src/audit/types.ts +18 -0
  191. package/src/audit/url.ts +37 -6
  192. package/src/cli/build-failure.ts +50 -0
  193. package/src/cli/commands/audit.ts +7 -0
  194. package/src/cli/commands/build.ts +19 -5
  195. package/src/cli/commands/check.ts +2 -0
  196. package/src/cli/commands/eval.ts +1 -7
  197. package/src/cli/commands/init.ts +15 -5
  198. package/src/cli/commands/preview.ts +15 -0
  199. package/src/cli/commands/sync.ts +2 -0
  200. package/src/cli/commands/translate.ts +9 -6
  201. package/src/cli/commands/upgrade.ts +11 -0
  202. package/src/cli/eject-scripts.ts +32 -7
  203. package/src/cli/env.ts +12 -1
  204. package/src/cli/init/scaffold.ts +117 -11
  205. package/src/cli/init/starter-spec.ts +235 -0
  206. package/src/components/content/AccordionItem.astro +26 -22
  207. package/src/components/content/Badge.astro +2 -9
  208. package/src/components/content/Card.astro +2 -2
  209. package/src/components/content/Component.astro +9 -3
  210. package/src/components/content/Expandable.astro +5 -1
  211. package/src/components/content/Frame.astro +2 -7
  212. package/src/components/content/GithubInfo.astro +12 -2
  213. package/src/components/content/Prompt.astro +2 -7
  214. package/src/components/content/Tabs.astro +54 -5
  215. package/src/components/content/Tile.astro +1 -1
  216. package/src/components/content/Tooltip.astro +69 -7
  217. package/src/components/content/Tree.astro +7 -2
  218. package/src/components/content/TypeTable.astro +10 -5
  219. package/src/components/content/Update.astro +8 -2
  220. package/src/components/content/auto-type-table.ts +4 -1
  221. package/src/components/content/badge-color.ts +17 -0
  222. package/src/components/content/base-href.ts +18 -3
  223. package/src/components/content/inline-markdown.ts +27 -7
  224. package/src/components/copy-feedback.ts +35 -8
  225. package/src/components/islands/assistant.tsx +16 -2
  226. package/src/components/islands/hooks.ts +7 -2
  227. package/src/components/islands/webmcp.ts +12 -8
  228. package/src/components/layout/Banner.astro +23 -4
  229. package/src/components/layout/Header.astro +22 -9
  230. package/src/components/layout/Logo.astro +5 -0
  231. package/src/components/layout/NavSelector.astro +6 -1
  232. package/src/components/layout/NavTabMenu.astro +133 -0
  233. package/src/components/layout/NavTree.astro +15 -6
  234. package/src/components/layout/NavTreeCache.astro +5 -2
  235. package/src/components/layout/NavTreeScript.astro +45 -5
  236. package/src/components/layout/PageLayout.astro +22 -8
  237. package/src/components/layout/ReferenceLayout.astro +4 -1
  238. package/src/components/layout/RootLayout.astro +21 -8
  239. package/src/components/layout/Search.astro +5 -1
  240. package/src/components/layout/analytics-client.ts +2 -0
  241. package/src/components/openapi/GraphqlType.astro +11 -3
  242. package/src/components/openapi/MessageComposer.astro +1 -1
  243. package/src/components/openapi/Operation.astro +21 -5
  244. package/src/components/openapi/PanelTabs.astro +4 -1
  245. package/src/components/openapi/Playground.astro +8 -2
  246. package/src/components/openapi/RequestPanel.astro +9 -5
  247. package/src/components/openapi/SchemaProperty.astro +11 -33
  248. package/src/components/openapi/SchemaTable.astro +27 -52
  249. package/src/components/openapi/helpers.ts +90 -12
  250. package/src/components/openapi/message-composer.ts +8 -0
  251. package/src/components/openapi/message-model.ts +12 -2
  252. package/src/components/openapi/message.ts +4 -1
  253. package/src/components/openapi/operation-model.ts +40 -8
  254. package/src/components/openapi/panel.ts +29 -5
  255. package/src/components/openapi/playground-client.ts +37 -7
  256. package/src/components/openapi/playground-schema.ts +25 -6
  257. package/src/components/openapi/request.ts +2 -0
  258. package/src/components/openapi/schema-tree.ts +203 -0
  259. package/src/components/openapi/snippets.ts +30 -15
  260. package/src/components/openapi/validate-json.ts +1 -1
  261. package/src/core/base-path.ts +15 -0
  262. package/src/core/config-input.ts +4 -4
  263. package/src/core/config.ts +18 -4
  264. package/src/core/diagnostics.ts +217 -30
  265. package/src/core/graph.ts +120 -11
  266. package/src/core/i18n-ui.ts +39 -2
  267. package/src/core/i18n.ts +13 -2
  268. package/src/core/links.ts +7 -4
  269. package/src/core/load-module.ts +20 -0
  270. package/src/core/locale-links.ts +17 -17
  271. package/src/core/manifest.ts +3 -2
  272. package/src/core/meta.ts +69 -6
  273. package/src/core/nav-diagnostics.ts +56 -1
  274. package/src/core/navigation.ts +245 -76
  275. package/src/core/ordering-prefix.ts +27 -0
  276. package/src/core/project-graph.ts +23 -1
  277. package/src/core/safe-href.ts +53 -1
  278. package/src/core/safe-links.ts +11 -2
  279. package/src/core/schema.ts +70 -14
  280. package/src/core/sources/assets.ts +83 -41
  281. package/src/core/sources/contentful-rich-text.ts +3 -2
  282. package/src/core/sources/contentful.ts +25 -12
  283. package/src/core/sources/filesystem.ts +5 -1
  284. package/src/core/sources/github-releases.ts +106 -5
  285. package/src/core/sources/lexical.ts +9 -4
  286. package/src/core/sources/lower.ts +79 -26
  287. package/src/core/sources/mdx-remote.ts +1 -0
  288. package/src/core/sources/normalize.ts +132 -30
  289. package/src/core/sources/notion.ts +26 -10
  290. package/src/core/sources/obsidian.ts +17 -3
  291. package/src/core/sources/payload.ts +1 -0
  292. package/src/core/sources/portable-text.ts +63 -44
  293. package/src/core/sources/remote.ts +18 -2
  294. package/src/core/sources/resolve.ts +52 -33
  295. package/src/core/sources/sanity.ts +1 -0
  296. package/src/core/sources/strapi-blocks.ts +9 -4
  297. package/src/core/sources/strapi.ts +1 -0
  298. package/src/core/sources/types.ts +31 -0
  299. package/src/core/types.ts +9 -0
  300. package/src/core/ui-packs/ar.ts +13 -0
  301. package/src/core/ui-packs/bg.ts +13 -0
  302. package/src/core/ui-packs/bn.ts +13 -0
  303. package/src/core/ui-packs/ca.ts +13 -0
  304. package/src/core/ui-packs/cs.ts +13 -0
  305. package/src/core/ui-packs/da.ts +13 -0
  306. package/src/core/ui-packs/de.ts +13 -0
  307. package/src/core/ui-packs/el.ts +13 -0
  308. package/src/core/ui-packs/es.ts +13 -0
  309. package/src/core/ui-packs/fa.ts +13 -0
  310. package/src/core/ui-packs/fi.ts +13 -0
  311. package/src/core/ui-packs/fr.ts +13 -0
  312. package/src/core/ui-packs/he.ts +13 -0
  313. package/src/core/ui-packs/hi.ts +13 -0
  314. package/src/core/ui-packs/hr.ts +13 -0
  315. package/src/core/ui-packs/hu.ts +13 -0
  316. package/src/core/ui-packs/id.ts +13 -0
  317. package/src/core/ui-packs/it.ts +13 -0
  318. package/src/core/ui-packs/ja.ts +13 -0
  319. package/src/core/ui-packs/ko.ts +13 -0
  320. package/src/core/ui-packs/nl.ts +13 -0
  321. package/src/core/ui-packs/no.ts +13 -0
  322. package/src/core/ui-packs/pl.ts +13 -0
  323. package/src/core/ui-packs/pt-br.ts +13 -0
  324. package/src/core/ui-packs/pt.ts +13 -0
  325. package/src/core/ui-packs/ro.ts +13 -0
  326. package/src/core/ui-packs/ru.ts +13 -0
  327. package/src/core/ui-packs/sk.ts +13 -0
  328. package/src/core/ui-packs/sr.ts +13 -0
  329. package/src/core/ui-packs/sv.ts +13 -0
  330. package/src/core/ui-packs/th.ts +13 -0
  331. package/src/core/ui-packs/tr.ts +13 -0
  332. package/src/core/ui-packs/uk.ts +13 -0
  333. package/src/core/ui-packs/vi.ts +13 -0
  334. package/src/core/ui-packs/zh-tw.ts +13 -0
  335. package/src/core/ui-packs/zh.ts +13 -0
  336. package/src/core/version-cut.ts +17 -2
  337. package/src/core/versions.ts +4 -1
  338. package/src/deploy/adapters/node.ts +5 -2
  339. package/src/deploy/adapters/registry.ts +2 -1
  340. package/src/deploy/adapters/types.ts +13 -1
  341. package/src/deploy/artifacts.ts +25 -5
  342. package/src/deploy/cloudflare-negotiation.ts +23 -4
  343. package/src/deploy/headers.ts +67 -51
  344. package/src/deploy/node-headers.ts +148 -27
  345. package/src/deploy/platforms/cloudflare.ts +1 -0
  346. package/src/deploy/platforms/netlify.ts +82 -5
  347. package/src/deploy/platforms/node.ts +7 -5
  348. package/src/deploy/platforms/static.ts +1 -0
  349. package/src/deploy/platforms/types.ts +7 -0
  350. package/src/deploy/platforms/vercel.ts +9 -3
  351. package/src/deploy/redirects.ts +14 -3
  352. package/src/deploy/vercel-negotiation.ts +35 -3
  353. package/src/eval/agents.ts +10 -2
  354. package/src/eval/run.ts +25 -0
  355. package/src/markdown/base-links.ts +55 -8
  356. package/src/markdown/index.ts +6 -3
  357. package/src/markdown/relative-links.ts +3 -23
  358. package/src/markdown/route-snapshot.ts +37 -0
  359. package/src/og/card.ts +20 -4
  360. package/src/og/derive.ts +145 -4
  361. package/src/openapi/graphql-build.ts +28 -2
  362. package/src/openapi/model.ts +77 -13
  363. package/src/openapi/render-mdx.ts +10 -1
  364. package/src/registry/eject.ts +119 -22
  365. package/src/search/documents.ts +22 -1
  366. package/src/search/sync/algolia.ts +36 -2
  367. package/src/sources/registry.ts +5 -0
  368. package/src/theme/entry.ts +11 -4
  369. package/src/translate/agents.ts +6 -1
  370. package/src/translate/ledger.ts +26 -3
  371. package/src/translate/meta.ts +11 -3
  372. package/src/translate/run.ts +11 -5
  373. package/src/translate/validate.ts +10 -1
  374. package/src/translate/work-list.ts +36 -3
  375. package/src/upgrade/upgrade.ts +36 -4
  376. package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
  377. package/dist/cli/chunk-6crbhc3x.js.map +0 -14
  378. package/dist/cli/chunk-6hsn950k.js.map +0 -10
  379. package/dist/cli/chunk-79jhk4py.js.map +0 -35
  380. package/dist/cli/chunk-82bbrxdn.js +0 -51
  381. package/dist/cli/chunk-82bbrxdn.js.map +0 -10
  382. package/dist/cli/chunk-abh8yjkn.js +0 -31
  383. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  384. package/dist/cli/chunk-dh8cwk36.js.map +0 -10
  385. package/dist/cli/chunk-epjnccmv.js.map +0 -10
  386. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  387. package/dist/cli/chunk-fs23ddbb.js.map +0 -35
  388. package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
  389. package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
  390. package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
  391. package/dist/cli/chunk-mb2919y2.js.map +0 -10
  392. package/dist/cli/chunk-q5163e60.js.map +0 -11
  393. package/dist/cli/chunk-qkqwkpte.js.map +0 -182
  394. package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
  395. package/dist/cli/chunk-s1p84fyh.js.map +0 -10
  396. package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
  397. package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
  398. /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-00gs3wqs.js.map} +0 -0
  399. /package/dist/cli/{chunk-6vm74dry.js.map → chunk-6dtt0zfn.js.map} +0 -0
  400. /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-bbnwccaz.js.map} +0 -0
  401. /package/dist/cli/{chunk-ce574jw2.js.map → chunk-esh98wmb.js.map} +0 -0
  402. /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-fsmrqk8a.js.map} +0 -0
  403. /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-g698a744.js.map} +0 -0
  404. /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-h2ez8dzb.js.map} +0 -0
  405. /package/dist/cli/{chunk-5shv93fd.js.map → chunk-h7k3nq3v.js.map} +0 -0
  406. /package/dist/cli/{chunk-ah61y8py.js.map → chunk-mqc662a6.js.map} +0 -0
  407. /package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-n1yg3tj3.js.map} +0 -0
  408. /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-sqw4ekg1.js.map} +0 -0
  409. /package/dist/cli/{chunk-27g6wdth.js.map → chunk-wdrt2k2v.js.map} +0 -0
  410. /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-zp79m0ts.js.map} +0 -0
@@ -23,7 +23,7 @@ Read `themeConfig`, `presets`, and `plugins`:
23
23
  | `themeConfig.colorMode.defaultMode` | `theme.mode` (`respectPrefersColorScheme: true` → `"system"`) |
24
24
  | `themeConfig.prism.theme` / `.darkTheme` | `markdown.code.theme: { light, dark }` (map Prism theme names to Shiki themes, e.g. `github`/`github-dark`) |
25
25
  | `themeConfig.metadata` / `themeConfig.image` | per-page `seo` frontmatter / `seo.og`; report what doesn't fit |
26
- | `url` + `baseUrl` | **`url` → drop** (the deployment's `site` is auto-detected); `baseUrl` (when not `/`) → `deployment: { base: "/…" }`, or the `base` option of a host adapter (`vercel({ base })`) when the site also needs one |
26
+ | `url` + `baseUrl` | **`url` → `deployment.site`**, unless the target host is Vercel, Netlify, or Cloudflare Pages, which Blume auto-detects (see SKILL.md — GitHub Pages, a common Docusaurus host, is not one of them); `baseUrl` (when not `/`) → `deployment: { base: "/…" }`, or the `base` option of a host adapter (`vercel({ base })`) when the site also needs one |
27
27
  | preset `docs.routeBasePath` — **including the default!** | Docusaurus serves docs at **`/docs/…` by default**; the "map only declared fields" rule does **not** apply here because the _URLs_ are load-bearing. Either keep them with top-level **`basePath: "/docs"`** (invisible to the sidebar), or intentionally move to root and emit a `redirects` entry per page. Decide explicitly and say which. (`routeBasePath: '/'` = docs-only mode — nothing to do.) |
28
28
  | preset `docs.editUrl` | `github` (owner/repo/branch; a path after the branch → `github.dir`; **an origin other than `https://github.com` → `github.host`** — a GitHub Enterprise repo's edit links and header mark point at the public site without it) |
29
29
  | `themeConfig.footer` | drop → Footer override (`defineComponents` layout slot) |
@@ -50,18 +50,18 @@ Every Docusaurus repo serves **`static/`** at the site root (`static/img/foo.png
50
50
 
51
51
  ## Versioned docs
52
52
 
53
- Docusaurus `versioned_docs/version-X/` + `versions.json` (+ `versioned_sidebars/`) → **recommend migrating the latest released version only**. Mind the URL scheme: by default the **latest release** serves at `/docs/` and the work-in-progress `docs/` folder serves at `/docs/next` (`lastVersion: 'current'` flips this) — pick the folder that matches what users see at `/docs/`. If older versions must stay, put each under its own folder and wire a `navigation.selectors` entry of `kind: "version"`.
53
+ Docusaurus `versioned_docs/version-X/` + `versions.json` (+ `versioned_sidebars/`) → **recommend migrating the latest released version only**. Mind the URL scheme: by default the **latest release** serves at `/docs/` and the work-in-progress `docs/` folder serves at `/docs/next` (`lastVersion: 'current'` flips this) — pick the folder that matches what users see at `/docs/`. If older versions must stay, use Blume's native versioning rather than a hand-built `navigation.selectors` dropdown: move each `versioned_docs/version-X/` into a top-level folder under `content.root` named for its id, and list that id in `versions.archived` (newest first), with `versions.current` labeling the live tree. Ids must start with a letter, so `version-1.0/` becomes `v1.0/`. Blume then adds the version switcher, the old-version notice, version-scoped search, and canonicals to the latest itself. A version-shaped folder left out of `versions.archived` only warns (`BLUME_VERSIONS_UNCONFIGURED_VERSION`) and publishes as ordinary current content. Snapshot routes become `/<id>/…`, so rewrite root-absolute links inside each snapshot to stay in it (`/guides/x` → `/v1.0/guides/x`) and add `redirects` from the old version URLs. Full reference: `docs/content/versioning.mdx` in the installed package.
54
54
 
55
55
  ## Blog
56
56
 
57
- A Docusaurus `blog/` → Blume `type: blog` pages. **Dates come from filenames/folders** (`2024-01-31-foo.md`) — extract each into `date` frontmatter and strip the date from the filename (the old dated URLs `/blog/2024/01/31/foo` need `redirects`). Strip `<!-- truncate -->` / `{/* truncate */}` markers. `authors.yml` refs → inline author objects in each post's `authors` frontmatter. RSS stays at `/blog/rss.xml` on both sides.
57
+ A Docusaurus `blog/` → Blume `type: blog` pages. **Dates come from filenames/folders** (`2024-01-31-foo.md`) — extract each into `date` frontmatter and strip the date from the filename (the old dated URLs `/blog/2024/01/31/foo` need `redirects`). Strip `<!-- truncate -->` / `{/* truncate */}` markers. `authors.yml` refs → inline author objects in each post's `authors` frontmatter. RSS stays at `/blog/rss.xml` on both sides. Blume generates **no** blog index, tag, author, or archive pages: write a `blog/index.mdx` whose `CardGroup` links each post (see `docs/advanced/blog.mdx` in the installed package), and report the tag, author, and archive pages as dropped.
58
58
 
59
59
  ## Content & components
60
60
 
61
61
  - **`.md` vs `.mdx` — both majors need renames, for opposite reasons.** Blume parses `.md` as plain Markdown: no directives, no JSX, no `$$` math, no mermaid/package-install fences. **v3** treats `.md` as MDX (so a `.md` with imports/JSX/`{}` renders them as literal text in Blume); **v2** content is looser MDX v1. Rule: **rename any `.md` that contains admonitions, JSX, imports, or math to `.mdx`** — for typical Docusaurus repos that is most files.
62
62
  - **Admonitions are directives — but check the version.** v3: `:::note`, `:::tip`, `:::info`, `:::warning`, `:::danger` pass through; `:::caution` → `:::warning` (or rely on Blume's alias); titles `:::note[Title]` work. **v2:** titles are space-separated (`:::note Your Title`) — rewrite to brackets or the title is silently lost; and v2's `:::warning` rendered **red/danger** — audit whether it should become `:::danger`.
63
63
  - **Tabs:** `<Tabs>`/`<TabItem label="…" value="…">` → `<Tabs>`/`<Tab title="…">`. Drop `groupId`/`queryString`/`value`; strip the `@theme/Tabs` imports.
64
- - **Theme JSX in content:** `<DocCardList/>` (standard on category index pages) → hand-write `Card`/`CardGroup` links or delete (a Blume group page lists its children); `<TOCInline/>` → drop (report); `<CodeBlock>` JSX → a fenced code block; `<Admonition>` → the matching directive; `<details>`/`<summary>` → `<Accordion>`/`<AccordionItem>` or leave as raw HTML.
64
+ - **Theme JSX in content:** `<DocCardList/>` (standard on category index pages) → a `CardGroup` of `Card` links to the folder's pages, one per child (nothing in Blume lists a folder's children on its index page, so deleting it leaves the page empty); `<TOCInline/>` → drop (report); `<CodeBlock>` JSX → a fenced code block; `<Admonition>` → the matching directive; `<details>`/`<summary>` → `<Accordion>`/`<AccordionItem>` or leave as raw HTML.
65
65
  - **`@theme/*` / `@site/*` imports** — strip `@theme/*` (Blume injects components globally); rewrite `@site/` asset/module paths to `/public` URLs or inline. **MDX partials** (`_partial.mdx` imports) → inline the partial's body (Blume's default `**/_*` exclude already hides the partial files themselves).
66
66
  - **Code blocks:** `title="file.js"` → works as-is; `showLineNumbers` → `lineNumbers`; **magic comments** (`// highlight-next-line`, `highlight-start`/`end`) → `{ranges}` or `// [!code highlight]` — unconverted they ship as literal comments in every sample; ` ```bash npm2yarn ` → ` ```package-install `.
67
67
  - **MDX v1 (v2 sources) pitfalls:** unescaped `<`/`{` in prose, HTML comments `<!-- -->` (→ `{/* */}`), string `style="…"` attributes (→ objects). Fix as build errors surface.
@@ -72,7 +72,7 @@ A Docusaurus `blog/` → Blume `type: blog` pages. **Dates come from filenames/f
72
72
  | --- | --- |
73
73
  | `title` / `description` | pass through |
74
74
  | `id` | usually drop (routing is filesystem-based); use `slug` to pin a route |
75
- | `slug` | `slug` |
75
+ | `slug` | `slug` as a **full path from the content root**: Blume's `slug` replaces the page's whole route, while Docusaurus resolves a relative slug (no leading `/`) against the doc's folder. So `guides/intro.md` with `slug: start` → `slug: guides/start`; an absolute slug (`/start`) is already a full path |
76
76
  | `sidebar_label` | `sidebar.label` |
77
77
  | `sidebar_position` | `sidebar.order` |
78
78
  | `unlisted` | `hidden: true` + `noindex: true` |
@@ -95,4 +95,4 @@ Remove `@docusaurus/*` and Algolia deps; delete `docusaurus.config.*`, `sidebars
95
95
 
96
96
  ## Dropped — report these
97
97
 
98
- Custom/swizzled theme components (layout slots or `blume eject`), footer columns, Algolia config, `sidebar_custom_props` and the other dropped frontmatter keys, `createRedirects` functions (→ host rules), React pages under `src/pages/`, `<TOCInline>`, per-category `className`/`customProps`, and any `@theme/*` component with no Blume equivalent.
98
+ Custom/swizzled theme components (layout slots or `blume eject`), footer columns, the blog's generated tag, author, and archive pages, Algolia config, `sidebar_custom_props` and the other dropped frontmatter keys, `createRedirects` functions (→ host rules), React pages under `src/pages/`, `<TOCInline>`, per-category `className`/`customProps`, and any `@theme/*` component with no Blume equivalent.
@@ -92,7 +92,7 @@ The codemod touches **only frontmatter**. Icons in MDX **body** (`<Icon icon="
92
92
  | `life-ring` | `life-buoy` | | `shield-halved` | `shield` |
93
93
  | `rocket`/`book`/`book-open`/`code`/`terminal`/`key`/`lock`/`user`/`users`/`database`/`server`/`cloud`/`bell`/`calendar`/`star`/`heart`/`tag`/`folder`/`globe`/`link`/`download`/`upload`/`check`/`copy`/`play`/`filter` | _(same name — verify)_ |
94
94
 
95
- **Rules:** verify each Lucide name exists at [lucide.dev/icons](https://lucide.dev/icons) before writing it. **Brand icons** (`fa6-brands:*` — github, discord, x, slack, linkedin…) mostly have **no** Lucide equivalent: for GitHub use the `github` config (renders the header repo link); for other socials, drop the icon and report it (or add via a Footer override after `blume eject`). Where no Lucide counterpart exists, **drop the icon and report it** — an unknown icon name is a build error.
95
+ **Rules:** verify each Lucide name exists at [lucide.dev/icons](https://lucide.dev/icons) before writing it. **Brand icons** (`fa6-brands:*` — github, discord, x, slack, linkedin…) mostly have **no** Lucide equivalent: for GitHub use the `github` config (renders the header repo link); for other socials, drop the icon and report it (or add via a Footer override after `blume eject`). Where no Lucide counterpart exists, **drop the icon and report it**. The build won't catch a wrong name for you: an unknown icon renders nothing, and it's only a warning (`BLUME_UNKNOWN_ICON`) for navigation icons — an icon prop on a component like `<Card>` fails silently — so check every name.
96
96
 
97
97
  ## Navigation: `docs.json` `navigation` → filesystem + tabs
98
98
 
@@ -143,7 +143,7 @@ Mintlify page frontmatter → Blume's strict schema. **`scripts/mintlify-codemod
143
143
  | `canonical` | `seo.canonical` | renames |
144
144
  | `og:image` | `seo.image` | renames |
145
145
  | `hidden: true` | valid top-level in Blume — **kept as-is**; add `noindex: true` yourself if the page must also leave the search index | left (do by hand) |
146
- | `openapi`/`asyncapi`/`api` | usually an endpoint stub → **delete the page** (Blume generates operation pages); else `type: api` | **flags** for review — never auto-deletes a page |
146
+ | `openapi`/`asyncapi`/`api` | usually an endpoint stub → **delete the page** (Blume generates operation pages); else drop the key and keep it as a normal page (there's no built-in `api` page type) | **flags** for review — never auto-deletes a page |
147
147
  | `mode`, `public`, `rss`, `groups`, `keywords`, `hideApiMarker`, `hideFooterPagination`, `iconType` | **drop** (report) | drops |
148
148
 
149
149
  The codemod leaves the source key in place and reports a conflict rather than clobbering data when a rename target already exists (e.g. a page already has `sidebar.label`) or the value is too structured to move safely — resolve those by hand. Remove any duplicate H1 in the body — `title` renders the H1. (The codemod only edits frontmatter; it never touches the body.)
@@ -153,7 +153,7 @@ The codemod leaves the source key in place and reports a conflict rather than cl
153
153
  Top-level `openapi`, `api.openapi`, or a per-group/per-tab `openapi` → `reference: [openapi({ sources: [{ spec, label?, route? }] })]`, with `openapi` imported from `blume/reference` (`spec` alone is the single-source shorthand; several per-tab specs become several sources, or several `openapi()` entries when they need different routes; a spec the source embedded with Scalar becomes a `scalar({ spec })` entry). A Mintlify `{ source, directory }` object: `directory` → the source's `route`. An `asyncapi` field maps the same way to `asyncapi({ … })` in the list. **Delete every per-endpoint stub page** (frontmatter `openapi: "GET /path"` or a `"GET /path"` nav entry) — Blume's native renderer generates one real page per operation. **Add a `navigation.tabs` entry pointing at the reference `route` yourself** (Mintlify's API tab maps to it); the reference does not create a header tab automatically.
154
154
 
155
155
  - **Vendor the spec.** Mintlify usually points at a spec **URL**. Copying that straight into `spec:` makes every build fetch it at build time — a single point of failure in CI/offline/behind a proxy, and a failed fetch silently drops the reference (leaving the tab pointing at a route that 404s). Prefer downloading it into the repo (`curl … -o openapi/<name>.json`) and pointing `spec` at that local path. If you keep the URL, report the dependency and consider a `prebuild` refresh-with-fallback.
156
- - **Fix endpoint links.** Blume operation routes are `<route>/<slugified-tag>/<slugified-operationId>` (tag `Models` + id `listModels` → `/api-reference/models/listmodels`) — this differs from Mintlify's endpoint URLs, so **rewrite every inbound link to an operation**. `blume validate` resolves operation pages like any other route and flags the ones you miss.
156
+ - **Fix endpoint links.** Blume operation routes are `<route>/<slugified-tag>/<slugified-operationId>` (tag `Models` + id `listModels` → `/api-reference/models/list-models`: a camelCase id is split into kebab-case, not just lowercased — see SKILL.md "OpenAPI") — this differs from Mintlify's endpoint URLs, so **rewrite every inbound link to an operation**. `blume validate` resolves operation pages like any other route and flags the ones you miss.
157
157
  - **Keep the "Introduction" page.** Mintlify commonly has a written intro/auth page in an "Introduction" group beside the "Endpoints" (openapi) group in the same tab. Keep it: a normal content page placed under the `openapi()` adapter's `route` (e.g. `<root>/api-reference/introduction.mdx`) merges into the reference tab's sidebar alongside the generated operations. Delete only the per-endpoint stubs, not the conceptual pages.
158
158
 
159
159
  ## Assets
@@ -112,7 +112,7 @@ This is a **copyable template**, not prose. Three pieces:
112
112
  "preview": "blume preview"
113
113
  },
114
114
  "dependencies": {
115
- "blume": "^1"
115
+ "blume": "^2"
116
116
  }
117
117
  }
118
118
  ```
@@ -56,7 +56,7 @@ Most Nextra pages have **no frontmatter**; the title falls back `_meta` title
56
56
 
57
57
  ## Code fences
58
58
 
59
- Nextra's fence meta differs from Blume's — rewrite it: `filename="app.js"` → a space-separated title (` ```js app.js `); `showLineNumbers` → `lineNumbers`; line highlighting `{1,4-5}` carries over unchanged; **drop** word-highlight `/word/`, `copy`/`copy=false`, and inline-code `{:lang}` suffixes. ` ```sh npm2yarn ` fences → ` ```package-install `.
59
+ Nextra's fence meta differs from Blume's — rewrite it: `filename="app.js"` → a space-separated title (` ```js app.js `); `showLineNumbers` → `lineNumbers`; line highlighting `{1,4-5}` carries over unchanged; **drop** word-highlight `/word/` and `copy`/`copy=false`. **Keep** inline-code `{:lang}` suffixes (`` `useState(){:js}` ``) — Blume highlights them natively. ` ```sh npm2yarn ` fences → ` ```package-install `.
60
60
 
61
61
  ## Math
62
62
 
@@ -12,7 +12,7 @@ Keep content where it is — set `content.root: "src/content/docs"`.
12
12
 
13
13
  ## Config: `starlight({…})` → `blume.config.ts`
14
14
 
15
- **Harvest the surrounding `astro.config.*` too, not just the `starlight()` call:** top-level Astro `redirects` → Blume `redirects`; `site` → leave unset (Blume auto-detects); other integrations → report.
15
+ **Harvest the surrounding `astro.config.*` too, not just the `starlight()` call:** top-level Astro `redirects` → Blume `redirects`; `site` → `deployment.site`, unless the target host is Vercel, Netlify, or Cloudflare Pages, which Blume auto-detects (see SKILL.md); other integrations → report.
16
16
 
17
17
  | Starlight option | Blume |
18
18
  | --- | --- |
@@ -70,7 +70,7 @@ Starlight's primary callout syntax is the `:::note`/`:::tip`/`:::caution`/`:::da
70
70
 
71
71
  ## Components
72
72
 
73
- - **Renames:** `<CardGrid>` → `<CardGroup>`; `<LinkCard>` → `<Card>` (its `description` prop drops — fold into the body); `<TabItem label="…">` → `<Tab title="…">`. `<Tabs>` and `<Card>` stay; a `<Tabs syncKey="…">` → strip the prop (Blume tabs sync by default).
73
+ - **Renames:** `<CardGrid>` → `<CardGroup>`; `<LinkCard>` → `<Card>` (its `description` prop drops — fold into the body); `<TabItem label="…">` → `<Tab title="…">`. `<Tabs>` and `<Card>` stay; **keep** a `<Tabs syncKey="…">` prop as is — Blume's `Tabs` takes `syncKey` with the same scoping (only groups sharing the key switch together). One difference: Blume groups without a key also sync, page-wide, by tab title, where Starlight leaves them independent — add `sync={false}` to a keyless group whose same-titled tabs must stay unlinked.
74
74
  - **`<Badge>` needs conversion, not pass-through:** Starlight puts content in a `text` prop and uses variants `note`/`tip`/`caution`/`danger`/`success`/`default` with sizes `small`/`medium`/`large`. Blume's `<Badge>` renders **children** with variants `default`/`accent`/`success`/`warning`/`danger` and sizes `xs`/`sm`/`md`/`lg`. Move `text` into the children; remap variant (`note`→`default`, `tip`→`accent`, `caution`→`warning`, `danger`→`danger`, `success`→`success`) and size (`small`→`sm`, `medium`→`md`, `large`→`lg`).
75
75
  - **Convert yourself:** `<Steps>` → Blume `<Steps>`/`<Step>`; `<FileTree>` → Blume `<FileTree>`; `<Code code={…}>` → a fenced code block; `<LinkButton>` → a Markdown link or `<Card>`.
76
76
  - Strip `import … from "@astrojs/starlight/*"` and `astro:assets` lines.
@@ -88,8 +88,8 @@ Starlight content is full of Expressive Code fence meta; Blume understands some
88
88
  ## Plugins — map, don't drop
89
89
 
90
90
  - `starlight-openapi` → an `openapi({ sources })` entry in Blume's `reference` list, imported from `blume/reference` (delete any generated pages; add the `navigation.tabs` entry).
91
- - `starlight-blog` → `type: blog` pages.
92
- - `starlight-versions` → `navigation.selectors` with `kind: "version"`.
91
+ - `starlight-blog` → `type: blog` pages (RSS at `/blog/rss.xml`). Blume generates **no** blog index, tag, or author pages: write a `blog/index.mdx` whose `CardGroup` links each post (see `docs/advanced/blog.mdx` in the installed package), and report the tag and author pages as dropped.
92
+ - `starlight-versions` → Blume's native versioning, not a `navigation.selectors` dropdown: each archived version's content goes in a top-level folder under `content.root` named for its id, listed in `versions.archived` (newest first), and Blume adds the switcher, the old-version notice, and version-scoped search. Ids must start with a letter (`1.0/` → `v1.0/`, with `redirects` from the old URLs), and a version-shaped folder left out of `versions.archived` only warns (`BLUME_VERSIONS_UNCONFIGURED_VERSION`) and publishes as current content. Full reference: `docs/content/versioning.mdx` in the installed package.
93
93
  - `starlight-image-zoom` → delete (Blume zooms content images by default).
94
94
  - `starlight-links-validator` → delete (`blume validate` covers it).
95
95
  - Anything else → report.
@@ -113,4 +113,4 @@ Remove `@astrojs/starlight` (and plugin deps) from deps, delete the Starlight bi
113
113
 
114
114
  ## Dropped — report these
115
115
 
116
- Non-GitHub socials, badge variants, sidebar/item `attrs` + `translations`, `customCss` beyond `theme.css`, `head` entries, `routeMiddleware`, splash/hero pages (rebuild as custom pages), aside custom icons, EC frames/collapse/text markers, prev/next toggles, unmapped plugins, any `<Icon>` name with no Lucide equivalent.
116
+ Non-GitHub socials, badge variants, sidebar/item `attrs` + `translations`, `customCss` beyond `theme.css`, `head` entries, `routeMiddleware`, splash/hero pages (rebuild as custom pages), starlight-blog's tag and author pages, aside custom icons, EC frames/collapse/text markers, prev/next toggles, unmapped plugins, any `<Icon>` name with no Lucide equivalent.
@@ -177,8 +177,8 @@ const RENAME = {
177
177
  };
178
178
 
179
179
  // Keys we deliberately do NOT auto-transform — they usually mean the page is an
180
- // OpenAPI endpoint stub that should be deleted (Blume generates operation pages)
181
- // or converted to `type: api`. Flag for the human; never guess.
180
+ // OpenAPI endpoint stub that should be deleted (Blume generates operation pages),
181
+ // or else a normal page that just loses the key. Flag for the human; never guess.
182
182
  const FLAG = new Set(["api", "asyncapi", "openapi"]);
183
183
 
184
184
  // Which change kinds actually edit the file. Report-only kinds (flags,
@@ -32,7 +32,7 @@ Skip the edit when the only available change is subjective polish, wording prefe
32
32
  - Preserve existing page order and `defineMeta` style; update `pages` arrays when adding, renaming, or removing pages.
33
33
  - Use the Blume components already present in the docs (callout directives, steps, cards) instead of inventing new markup patterns.
34
34
  - Match nearby code fences: filenames, language tags, and line numbers where the surrounding docs use them.
35
- - Keep internal links root-relative (`/docs/...`).
35
+ - Keep internal links root-relative (`/guides/setup`), in the form nearby pages already use.
36
36
  - Do not edit generated `.blume/` or `dist/` output.
37
37
 
38
38
  ## PR notes
@@ -0,0 +1,56 @@
1
+ import { routeIsTaken } from "../astro/pages.ts";
2
+ import type { BlumeProject } from "../core/project-graph.ts";
3
+ import type { ResolvedConfig } from "../core/schema.ts";
4
+
5
+ /**
6
+ * What a build actually serves of the agent surfaces whose config flag alone
7
+ * doesn't guarantee them.
8
+ */
9
+ export interface EmittedAgentSurface {
10
+ /** The MCP server was generated (see {@link servesMcp}). */
11
+ mcp: boolean;
12
+ /**
13
+ * A skills discovery index is served: generated from `agents.skills` (at
14
+ * least one valid skill), or shipped by the user in `public/`.
15
+ */
16
+ skills: boolean;
17
+ }
18
+
19
+ /**
20
+ * Whether the build generates the MCP server: it is enabled and no content
21
+ * or custom page owns its route. When one does, the generator skips the
22
+ * server with a warning rather than collide with the page (`planMcp`).
23
+ */
24
+ export const servesMcp = (
25
+ project: BlumeProject,
26
+ userPages: { pattern: string }[]
27
+ ): boolean =>
28
+ project.config.agents.mcp.enabled &&
29
+ !routeIsTaken(
30
+ userPages,
31
+ project.graph.pages,
32
+ project.config.agents.mcp.route
33
+ );
34
+
35
+ /**
36
+ * The config the discovery documents are built from: `agents.mcp` and
37
+ * `agents.skills` switched off when the build didn't emit them, so llms.txt,
38
+ * agent-readability.json, the catalogs, and the header rules only point at
39
+ * what is there. A config flag says what was asked for; the MCP server is
40
+ * skipped when a page owns its route, and skills publish nothing when their
41
+ * directory is missing or holds no valid skill.
42
+ */
43
+ export const advertisedConfig = (
44
+ config: ResolvedConfig,
45
+ emitted: EmittedAgentSurface
46
+ ): ResolvedConfig => ({
47
+ ...config,
48
+ agents: {
49
+ ...config.agents,
50
+ mcp: {
51
+ ...config.agents.mcp,
52
+ enabled: config.agents.mcp.enabled && emitted.mcp,
53
+ },
54
+ skills: emitted.skills ? config.agents.skills : undefined,
55
+ },
56
+ });
@@ -113,6 +113,7 @@ export interface ApiSpecDocument {
113
113
  }
114
114
 
115
115
  const JSON_TYPE = "application/json";
116
+ const EVENT_STREAM_TYPE = "text/event-stream";
116
117
  const MARKDOWN_TYPE = "text/markdown";
117
118
  const TEXT_TYPE = "text/plain";
118
119
 
@@ -623,14 +624,26 @@ export const buildApiSpec = (input: ApiSpecInput): ApiSpecDocument => {
623
624
  {
624
625
  post: {
625
626
  description:
626
- "The Model Context Protocol server (Streamable HTTP, stateless, JSON responses). Tools: `search_docs`, `get_page`, `list_pages`, `get_navigation` — the same operations this API exposes — plus every page as a `text/markdown` resource. Discovery document at `/.well-known/mcp.json`.",
627
+ "The Model Context Protocol server (Streamable HTTP, stateless, JSON responses). Send `Accept: application/json, text/event-stream`: the Streamable HTTP transport requires a client to accept both and answers `406` otherwise, though this server always replies with JSON. Tools: `search_docs`, `get_page`, `list_pages`, `get_navigation` — the same operations this API exposes — plus every page as a `text/markdown` resource. Discovery document at `/.well-known/mcp.json`.",
627
628
  operationId: "mcp",
628
629
  requestBody: {
629
630
  content: { [JSON_TYPE]: { schema: ref("JsonRpcRequest") } },
630
631
  required: true,
631
632
  },
632
633
  responses: {
633
- "200": jsonResponse("The JSON-RPC response.", "JsonRpcResponse"),
634
+ // Both media types, so a generated client sends the Accept header
635
+ // the transport requires; OpenAPI ignores an `Accept` parameter.
636
+ "200": {
637
+ content: {
638
+ [JSON_TYPE]: { schema: ref("JsonRpcResponse") },
639
+ [EVENT_STREAM_TYPE]: { schema: { type: "string" } },
640
+ },
641
+ description: "The JSON-RPC response.",
642
+ },
643
+ "406": jsonResponse(
644
+ "The request's `Accept` header doesn't list both `application/json` and `text/event-stream`.",
645
+ "JsonRpcResponse"
646
+ ),
634
647
  },
635
648
  summary: "Call the MCP server",
636
649
  tags: ["MCP"],
@@ -17,7 +17,13 @@ import { API_BASE, OPENAPI_PATH } from "./api/paths.ts";
17
17
  */
18
18
 
19
19
  export const API_CATALOG_PATH = "/.well-known/api-catalog";
20
- export const API_CATALOG_TYPE = "application/linkset+json";
20
+ /** The profile URI RFC 9727 registers for an API catalog linkset. */
21
+ export const API_CATALOG_PROFILE = "https://www.rfc-editor.org/info/rfc9727";
22
+ /**
23
+ * The catalog's media type: a linkset carrying the RFC 9727 profile
24
+ * parameter, which the RFC says an API catalog SHOULD be served with.
25
+ */
26
+ export const API_CATALOG_TYPE = `application/linkset+json; profile="${API_CATALOG_PROFILE}"`;
21
27
 
22
28
  /** An RFC 9264 linkset entry, restricted to the relations Blume emits. */
23
29
  interface LinksetEntry {
@@ -30,6 +30,13 @@ export interface AskData {
30
30
  defaultLocale?: string;
31
31
  documents: OramaDoc[];
32
32
  site: string | null;
33
+ /**
34
+ * Present on a versioned site, whose documents then carry their `version`
35
+ * (`""` for the current docs): retrieval keeps to the version the reader
36
+ * is viewing — the current docs unless they're on an archived page — as
37
+ * the search dialog does.
38
+ */
39
+ versioned?: boolean;
33
40
  }
34
41
 
35
42
  /** Documents retrieved per question and injected into the system prompt. */
@@ -644,12 +651,17 @@ export const createAskContext = (
644
651
  // which part of each page is quoted.
645
652
  const [query = ""] = queries;
646
653
 
647
- // The current page anchors retrieval to its locale and is injected first.
654
+ // The current page anchors retrieval to its locale and docs version, and
655
+ // is injected first. Without one, a versioned site grounds in the
656
+ // current docs rather than every archived copy of each page.
648
657
  const current = page?.path
649
658
  ? byRoute.get(normalizeRoute(page.path))
650
659
  : undefined;
651
660
  const db = await index();
652
- const filters = { locale: current?.locale || undefined };
661
+ const filters = {
662
+ locale: current?.locale || undefined,
663
+ version: data.versioned ? (current?.version ?? "") : undefined,
664
+ };
653
665
  const hits = interleave(
654
666
  await Promise.all(
655
667
  queries.map((text) => queryOramaIndex(db, text, maxResults, filters))
@@ -1,14 +1,17 @@
1
1
  import type { BlumeProject } from "../core/project-graph.ts";
2
2
  import { buildSearchDocuments } from "../search/documents.ts";
3
+ import type { OramaDoc } from "../search/orama-index.ts";
3
4
  import type { AskData } from "./ask-context.ts";
4
5
 
5
6
  /**
6
7
  * Build the grounding snapshot the assistant endpoint serves. Like the MCP server,
7
8
  * the assistant is independent of on-page search, so documents are indexed even when the
8
- * search provider is `none` (`includeWhenDisabled`). `locale` is kept (unlike the
9
- * MCP snapshot) so retrieval can be filtered to the current page's language, and
10
- * content is kept as Markdown so grounding sees fenced code examples — the model
11
- * answers "what does the config look like?" from the docs instead of declining.
9
+ * search provider is `none` (`includeWhenDisabled`). `locale` is kept so
10
+ * retrieval can be filtered to the current page's language, and on a
11
+ * versioned site `version` too, so archived snapshots don't crowd the docs
12
+ * being read out of the answer. Content is kept as Markdown so grounding sees
13
+ * fenced code examples — the model answers "what does the config look like?"
14
+ * from the docs instead of declining.
12
15
  * The reader is an AI agent, so `<Visibility>` resolves for the agents audience
13
16
  * (web-only content removed, agents-only unwrapped) and components downlevel to
14
17
  * Markdown, both matching llms-full.txt.
@@ -19,15 +22,26 @@ export const buildAskData = async (project: BlumeProject): Promise<AskData> => {
19
22
  content: "markdown",
20
23
  includeWhenDisabled: true,
21
24
  });
22
- return {
25
+ const versioned = Boolean(project.config.versions);
26
+ const data: AskData = {
23
27
  defaultLocale: project.config.i18n?.defaultLocale,
24
- documents: documents.map((doc) => ({
25
- content: doc.content,
26
- description: doc.description,
27
- locale: doc.locale,
28
- route: doc.route,
29
- title: doc.title,
30
- })),
28
+ documents: documents.map((doc) => {
29
+ const document: OramaDoc = {
30
+ content: doc.content,
31
+ description: doc.description,
32
+ locale: doc.locale,
33
+ route: doc.route,
34
+ title: doc.title,
35
+ };
36
+ if (versioned) {
37
+ document.version = doc.version;
38
+ }
39
+ return document;
40
+ }),
31
41
  site: project.config.deployment.options.site ?? null,
32
42
  };
43
+ if (versioned) {
44
+ data.versioned = true;
45
+ }
46
+ return data;
33
47
  };
@@ -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
  /**
@@ -37,10 +41,11 @@ export const buildHomeLinkHeader = (
37
41
  const deployBase = normalizeBasePath(config.deployment.options.base);
38
42
  const links: string[] = [];
39
43
  // RFC 9727 §3: the api-catalog relation is how a homepage advertises the
40
- // well-known catalog.
44
+ // well-known catalog. Its type carries the RFC 9727 profile, whose quotes
45
+ // are escaped inside the quoted `type` value (RFC 8288 quoted-string).
41
46
  if (hasApiCatalog(config)) {
42
47
  links.push(
43
- `<${deployBase}${API_CATALOG_PATH}>; rel="api-catalog"; type="application/linkset+json"`
48
+ `<${deployBase}${API_CATALOG_PATH}>; rel="api-catalog"; type="${API_CATALOG_TYPE.replaceAll('"', String.raw`\"`)}"`
44
49
  );
45
50
  }
46
51
  // The ai-catalog spec's own relation for its well-known document, the
package/src/ai/llms.ts CHANGED
@@ -312,10 +312,11 @@ const buildFull = async (project: BlumeProject): Promise<string> => {
312
312
  source: raw,
313
313
  sourcePath: page.sourcePath,
314
314
  });
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
315
  }
316
+ // Page links too: a reader of one flat file has no page URL to resolve
317
+ // `./install` against, and `/install` needs the base the site is
318
+ // served under.
319
+ raw = rewriteLinks(raw, page);
319
320
  // Resolve `<Visibility>` audiences (web-only content omitted from the
320
321
  // agent-facing output, agents-only unwrapped), then downlevel supported
321
322
  // 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
  }