blume 2.0.2 → 2.1.0

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 (601) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +113 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-6k4ftwze.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-6k4ftwze.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-zp79m0ts.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-ddndchfr.js → chunk-2f3ndkpw.js} +57 -29
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-fsmrqk8a.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-ppzjqwx2.js → chunk-2n4ps1b3.js} +17 -18
  12. package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-2n4ps1b3.js.map} +3 -3
  13. package/dist/cli/{chunk-5r8g91qn.js → chunk-43zcf4gz.js} +398 -159
  14. package/dist/cli/chunk-43zcf4gz.js.map +36 -0
  15. package/dist/cli/chunk-4ejxbzee.js +39 -0
  16. package/dist/cli/chunk-4ejxbzee.js.map +10 -0
  17. package/dist/cli/{chunk-hr8ne106.js → chunk-4h8z9vv5.js} +296 -77
  18. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  19. package/dist/cli/{chunk-k7pj68a8.js → chunk-5cg2rzgf.js} +18 -16
  20. package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5cg2rzgf.js.map} +2 -2
  21. package/dist/cli/{chunk-h2ez8dzb.js → chunk-5ffywsmb.js} +4 -4
  22. package/dist/cli/{chunk-g698a744.js → chunk-5wj2qwn2.js} +5 -5
  23. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  24. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  25. package/dist/cli/{chunk-3yce002v.js → chunk-713mngdq.js} +15 -8
  26. package/dist/cli/chunk-713mngdq.js.map +14 -0
  27. package/dist/cli/{chunk-91ws1n6j.js → chunk-9geymqns.js} +17 -15
  28. package/dist/cli/{chunk-91ws1n6j.js.map → chunk-9geymqns.js.map} +2 -2
  29. package/dist/cli/{chunk-6dtt0zfn.js → chunk-9xr4dgee.js} +27 -18
  30. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  31. package/dist/cli/{chunk-sqw4ekg1.js → chunk-amrb6hgh.js} +2 -2
  32. package/dist/cli/chunk-cm89scdv.js +443 -0
  33. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  34. package/dist/cli/{chunk-wdrt2k2v.js → chunk-cmtcnh9f.js} +90 -26
  35. package/dist/cli/chunk-cmtcnh9f.js.map +10 -0
  36. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  37. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  38. package/dist/cli/{chunk-7mbqtmgb.js → chunk-e7vvk5ya.js} +21 -12
  39. package/dist/cli/chunk-e7vvk5ya.js.map +10 -0
  40. package/dist/cli/{chunk-qkb5a8sa.js → chunk-g2z9x01v.js} +6 -98
  41. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  42. package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
  43. package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
  44. package/dist/cli/{chunk-h7k3nq3v.js → chunk-gp84za0h.js} +2 -2
  45. package/dist/cli/{chunk-hqp2ajnh.js → chunk-h2a48yhd.js} +195 -73
  46. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  47. package/dist/cli/{chunk-pbg5a4s3.js → chunk-jnwj5kjj.js} +71 -47
  48. package/dist/cli/chunk-jnwj5kjj.js.map +19 -0
  49. package/dist/cli/chunk-kwrg63tm.js +31 -0
  50. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  51. package/dist/cli/{chunk-8g8ytmgx.js → chunk-nd1821sk.js} +21 -19
  52. package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-nd1821sk.js.map} +2 -2
  53. package/dist/cli/{chunk-w4bxdvsa.js → chunk-ntv4qrt7.js} +17 -15
  54. package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-ntv4qrt7.js.map} +2 -2
  55. package/dist/cli/{chunk-bfwp9vp6.js → chunk-p804f3pe.js} +7 -7
  56. package/dist/cli/{chunk-4e9b9ra6.js → chunk-py62fewh.js} +6 -6
  57. package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-py62fewh.js.map} +1 -1
  58. package/dist/cli/chunk-q3wxxq39.js +126 -0
  59. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  60. package/dist/cli/{chunk-j85scx15.js → chunk-rekqz4qg.js} +142 -46
  61. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  62. package/dist/cli/{chunk-00gs3wqs.js → chunk-ta27feev.js} +1 -1
  63. package/dist/cli/{chunk-v6ya5kcb.js → chunk-xkz5xbtv.js} +4031 -1132
  64. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  65. package/dist/cli/{chunk-bbnwccaz.js → chunk-y4ykbark.js} +2 -2
  66. package/dist/cli/{chunk-n1yg3tj3.js → chunk-y5h4r2y0.js} +6 -6
  67. package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-y5h4r2y0.js.map} +3 -3
  68. package/dist/cli/{chunk-nfcyttvj.js → chunk-yj17vs39.js} +9 -9
  69. package/dist/cli/{chunk-nfcyttvj.js.map → chunk-yj17vs39.js.map} +1 -1
  70. package/dist/cli/{chunk-7vtckvaw.js → chunk-yxmk4pec.js} +16 -14
  71. package/dist/cli/{chunk-7vtckvaw.js.map → chunk-yxmk4pec.js.map} +2 -2
  72. package/dist/cli/{chunk-gs7r695n.js → chunk-zccbjcj0.js} +22 -8
  73. package/dist/cli/{chunk-gs7r695n.js.map → chunk-zccbjcj0.js.map} +3 -3
  74. package/dist/cli/{chunk-xh43dwgw.js → chunk-zf748avv.js} +415 -225
  75. package/dist/cli/chunk-zf748avv.js.map +37 -0
  76. package/dist/cli/{chunk-d1v5rhy0.js → chunk-zhvd6adm.js} +16 -14
  77. package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-zhvd6adm.js.map} +2 -2
  78. package/dist/cli/index.js +28 -20
  79. package/dist/cli/index.js.map +5 -5
  80. package/dist/types/ai/agent-surface.d.ts +8 -7
  81. package/dist/types/ai/ai-catalog.d.ts +7 -0
  82. package/dist/types/ai/ask-context.d.ts +22 -0
  83. package/dist/types/ai/ask.d.ts +271 -58
  84. package/dist/types/ai/index.d.ts +2 -2
  85. package/dist/types/ai/link-headers.d.ts +8 -1
  86. package/dist/types/ai/openapi-components.d.ts +5 -2
  87. package/dist/types/ai/relative-links.d.ts +9 -7
  88. package/dist/types/ai/site-skill.d.ts +26 -0
  89. package/dist/types/ai/skills.d.ts +20 -1
  90. package/dist/types/ai/tar.d.ts +1 -3
  91. package/dist/types/analytics/index.d.ts +2 -0
  92. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  93. package/dist/types/analytics/schema.d.ts +14 -0
  94. package/dist/types/astro/integration.d.ts +11 -2
  95. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  96. package/dist/types/captcha/index.d.ts +28 -0
  97. package/dist/types/captcha/schema.d.ts +29 -0
  98. package/dist/types/captcha/turnstile.d.ts +28 -0
  99. package/dist/types/components/content/api-field.d.ts +59 -0
  100. package/dist/types/components/content/api-page.d.ts +95 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/helpers.d.ts +137 -0
  103. package/dist/types/components/openapi/security.d.ts +85 -0
  104. package/dist/types/consent/ethyca.d.ts +48 -0
  105. package/dist/types/consent/index.d.ts +37 -0
  106. package/dist/types/consent/native.d.ts +28 -0
  107. package/dist/types/consent/osano.d.ts +42 -0
  108. package/dist/types/consent/schema.d.ts +110 -0
  109. package/dist/types/core/base-path.d.ts +21 -7
  110. package/dist/types/core/config-input.d.ts +196 -27
  111. package/dist/types/core/data.d.ts +49 -4
  112. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  113. package/dist/types/core/footer.d.ts +8 -0
  114. package/dist/types/core/heading-markers.d.ts +5 -7
  115. package/dist/types/core/i18n-ui.d.ts +86 -0
  116. package/dist/types/core/i18n.d.ts +2 -0
  117. package/dist/types/core/includes.d.ts +8 -2
  118. package/dist/types/core/last-modified.d.ts +10 -0
  119. package/dist/types/core/localizable.d.ts +7 -0
  120. package/dist/types/core/meta.d.ts +8 -0
  121. package/dist/types/core/page-modes.d.ts +29 -0
  122. package/dist/types/core/redirect-patterns.d.ts +144 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +558 -56
  125. package/dist/types/core/sources/lower.d.ts +29 -16
  126. package/dist/types/core/sources/normalize.d.ts +13 -1
  127. package/dist/types/core/sources/read.d.ts +5 -0
  128. package/dist/types/core/sources/watch.d.ts +12 -5
  129. package/dist/types/core/standard-schema.d.ts +5 -0
  130. package/dist/types/core/types.d.ts +1 -1
  131. package/dist/types/core/variables.d.ts +42 -0
  132. package/dist/types/deploy/artifacts.d.ts +6 -4
  133. package/dist/types/deploy/headers.d.ts +5 -0
  134. package/dist/types/deploy/node-headers.d.ts +13 -2
  135. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  136. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  137. package/dist/types/deploy/platforms/types.d.ts +8 -0
  138. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  139. package/dist/types/deploy/redirects.d.ts +61 -22
  140. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  141. package/dist/types/markdown/code-title.d.ts +17 -0
  142. package/dist/types/markdown/directives.d.ts +62 -0
  143. package/dist/types/markdown/features.d.ts +24 -2
  144. package/dist/types/markdown/mdast.d.ts +63 -0
  145. package/dist/types/narration/build.d.ts +70 -0
  146. package/dist/types/narration/provider.d.ts +26 -0
  147. package/dist/types/narration/script.d.ts +154 -0
  148. package/dist/types/openapi/asyncapi.d.ts +4 -2
  149. package/dist/types/openapi/model.d.ts +12 -6
  150. package/dist/types/openapi/overlay.d.ts +54 -0
  151. package/dist/types/openapi/parse.d.ts +13 -0
  152. package/dist/types/openapi/references.d.ts +14 -4
  153. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  154. package/dist/types/ratelimit/index.d.ts +27 -0
  155. package/dist/types/ratelimit/memory.d.ts +36 -0
  156. package/dist/types/ratelimit/schema.d.ts +111 -0
  157. package/dist/types/ratelimit/upstash.d.ts +21 -0
  158. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  159. package/dist/types/reference/asyncapi.d.ts +10 -10
  160. package/dist/types/reference/graphql.d.ts +13 -10
  161. package/dist/types/reference/openapi.d.ts +33 -12
  162. package/dist/types/reference/options.d.ts +45 -5
  163. package/dist/types/reference/scalar.d.ts +13 -0
  164. package/dist/types/reference/schema.d.ts +60 -36
  165. package/dist/types/search/documents.d.ts +12 -1
  166. package/dist/types/search/orama-index.d.ts +4 -0
  167. package/dist/types/seo/metatags.d.ts +20 -0
  168. package/docs/02-deployment.mdx +44 -6
  169. package/docs/03-upgrading.mdx +4 -4
  170. package/docs/04-migrating.mdx +1 -1
  171. package/docs/08-faq.mdx +1 -1
  172. package/docs/advanced/changelog.mdx +2 -2
  173. package/docs/advanced/custom-pages.mdx +3 -3
  174. package/docs/advanced/skills.mdx +14 -0
  175. package/docs/cli/audit.mdx +38 -2
  176. package/docs/cli/evals.mdx +3 -3
  177. package/docs/cli/index.mdx +1 -0
  178. package/docs/cli/translate.mdx +3 -3
  179. package/docs/cli/version.mdx +1 -1
  180. package/docs/configuration/analytics.mdx +22 -1
  181. package/docs/configuration/assistant.mdx +188 -36
  182. package/docs/configuration/consent.mdx +100 -0
  183. package/docs/configuration/customization.mdx +6 -4
  184. package/docs/configuration/index.mdx +63 -5
  185. package/docs/configuration/meta.ts +3 -0
  186. package/docs/configuration/narration.mdx +119 -0
  187. package/docs/configuration/rate-limiting.mdx +69 -0
  188. package/docs/configuration/search.mdx +30 -2
  189. package/docs/configuration/theming.mdx +3 -1
  190. package/docs/content/components.mdx +107 -1
  191. package/docs/content/frontmatter.mdx +77 -1
  192. package/docs/content/i18n.mdx +40 -3
  193. package/docs/content/includes.mdx +16 -0
  194. package/docs/content/index.mdx +4 -2
  195. package/docs/content/islands.mdx +1 -1
  196. package/docs/content/meta.mdx +4 -1
  197. package/docs/content/meta.ts +1 -0
  198. package/docs/content/navigation.mdx +28 -6
  199. package/docs/content/sources/contentful.mdx +32 -0
  200. package/docs/content/sources/custom.mdx +39 -0
  201. package/docs/content/sources/github-releases.mdx +31 -0
  202. package/docs/content/sources/index.mdx +91 -0
  203. package/docs/content/sources/meta.ts +16 -0
  204. package/docs/content/sources/notion.mdx +32 -0
  205. package/docs/content/sources/obsidian.mdx +37 -0
  206. package/docs/content/sources/payload.mdx +30 -0
  207. package/docs/content/sources/remote-mdx.mdx +40 -0
  208. package/docs/content/sources/sanity.mdx +29 -0
  209. package/docs/content/sources/strapi.mdx +31 -0
  210. package/docs/content/syntax.mdx +81 -2
  211. package/docs/content/variables.mdx +41 -0
  212. package/docs/content/versioning.mdx +1 -1
  213. package/docs/discoverability/agent-discovery.mdx +30 -3
  214. package/docs/discoverability/index.mdx +3 -2
  215. package/docs/discoverability/llms-txt.mdx +1 -1
  216. package/docs/discoverability/markdown.mdx +2 -2
  217. package/docs/discoverability/metadata.mdx +17 -0
  218. package/docs/discoverability/open-graph.mdx +5 -3
  219. package/docs/discoverability/rss.mdx +1 -1
  220. package/docs/references/api-pages.mdx +74 -0
  221. package/docs/references/asyncapi.mdx +2 -2
  222. package/docs/references/graphql.mdx +3 -3
  223. package/docs/references/meta.ts +1 -1
  224. package/docs/references/openapi.mdx +106 -5
  225. package/docs/references/scalar.mdx +3 -1
  226. package/package.json +36 -1
  227. package/skills/blume/SKILL.md +3 -0
  228. package/skills/blume-migrate/SKILL.md +5 -5
  229. package/skills/blume-migrate/references/docusaurus.md +3 -3
  230. package/skills/blume-migrate/references/fumadocs.md +1 -1
  231. package/skills/blume-migrate/references/mintlify.md +37 -19
  232. package/skills/blume-migrate/references/nextra.md +3 -3
  233. package/skills/blume-migrate/references/starlight.md +2 -1
  234. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +67 -16
  235. package/skills/blume-write-skill/SKILL.md +82 -0
  236. package/src/ai/agent-readability.ts +4 -3
  237. package/src/ai/agent-surface.ts +9 -7
  238. package/src/ai/ai-catalog.ts +12 -5
  239. package/src/ai/api/handlers.ts +2 -2
  240. package/src/ai/api/spec.ts +2 -2
  241. package/src/ai/api-catalog.ts +6 -2
  242. package/src/ai/ask-context.ts +73 -20
  243. package/src/ai/ask-data.ts +6 -0
  244. package/src/ai/ask-tools.ts +142 -0
  245. package/src/ai/ask.ts +354 -76
  246. package/src/ai/changelog-markdown.ts +2 -2
  247. package/src/ai/component-markdown.ts +75 -0
  248. package/src/ai/cors.ts +2 -0
  249. package/src/ai/index.ts +12 -0
  250. package/src/ai/link-headers.ts +14 -5
  251. package/src/ai/llms.ts +24 -9
  252. package/src/ai/markdown.ts +2 -1
  253. package/src/ai/mcp/data.ts +6 -0
  254. package/src/ai/mcp/discovery.ts +2 -2
  255. package/src/ai/mcp/query.ts +2 -2
  256. package/src/ai/mcp/server.ts +2 -2
  257. package/src/ai/openapi-components.ts +31 -9
  258. package/src/ai/relative-links.ts +42 -26
  259. package/src/ai/serializers.ts +2 -1
  260. package/src/ai/site-skill.ts +274 -0
  261. package/src/ai/skills.ts +49 -3
  262. package/src/ai/tar.ts +139 -12
  263. package/src/ai/write-skill.ts +97 -0
  264. package/src/analytics/head.ts +4 -0
  265. package/src/analytics/index.ts +5 -0
  266. package/src/analytics/one-dollar-stats.ts +86 -0
  267. package/src/analytics/schema.ts +2 -0
  268. package/src/astro/generate.ts +133 -19
  269. package/src/astro/include-hmr.ts +31 -12
  270. package/src/astro/include-refresh.ts +19 -8
  271. package/src/astro/index.ts +1 -0
  272. package/src/astro/integration.ts +143 -14
  273. package/src/astro/module-types.ts +6 -0
  274. package/src/astro/runtime-modules.ts +28 -13
  275. package/src/astro/templates.ts +364 -67
  276. package/src/astro/variables.ts +31 -0
  277. package/src/audit/catalog.ts +19 -0
  278. package/src/audit/checks/contrast.ts +183 -0
  279. package/src/audit/checks/i18n.ts +2 -3
  280. package/src/audit/checks/indexability.ts +2 -3
  281. package/src/audit/checks/links.ts +13 -13
  282. package/src/audit/checks/sitemap.ts +2 -3
  283. package/src/audit/graph.ts +3 -1
  284. package/src/audit/redirects.ts +46 -15
  285. package/src/audit/run.ts +14 -9
  286. package/src/audit/snapshot.ts +5 -0
  287. package/src/audit/types.ts +2 -1
  288. package/src/blume-modules.d.ts +7 -0
  289. package/src/captcha/client.ts +152 -0
  290. package/src/captcha/hcaptcha.ts +36 -0
  291. package/src/captcha/index.ts +28 -0
  292. package/src/captcha/schema.ts +32 -0
  293. package/src/captcha/turnstile.ts +38 -0
  294. package/src/captcha/verify.ts +71 -0
  295. package/src/cli/args.ts +32 -0
  296. package/src/cli/command-meta.ts +5 -0
  297. package/src/cli/commands/audit.ts +4 -1
  298. package/src/cli/commands/doctor.ts +25 -17
  299. package/src/cli/commands/eval.ts +19 -11
  300. package/src/cli/commands/skill.ts +96 -0
  301. package/src/cli/commands/translate.ts +9 -12
  302. package/src/cli/commands/validate.ts +3 -1
  303. package/src/cli/dev-lock.ts +157 -37
  304. package/src/cli/index.ts +5 -0
  305. package/src/cli/report-format.ts +11 -6
  306. package/src/cli/required-secrets.ts +71 -29
  307. package/src/components/BlumePage.astro +2 -0
  308. package/src/components/colors.ts +19 -0
  309. package/src/components/content/ApiEndpoint.astro +18 -0
  310. package/src/components/content/ApiField.astro +79 -0
  311. package/src/components/content/ApiPlayground.astro +62 -0
  312. package/src/components/content/ApiRail.astro +19 -0
  313. package/src/components/content/Badge.astro +5 -3
  314. package/src/components/content/Color.astro +1 -0
  315. package/src/components/content/Component.astro +2 -2
  316. package/src/components/content/FileTree.astro +1 -0
  317. package/src/components/content/GithubInfo.astro +1 -0
  318. package/src/components/content/Panel.astro +1 -0
  319. package/src/components/content/ParamField.astro +25 -0
  320. package/src/components/content/RequestExample.astro +17 -0
  321. package/src/components/content/ResponseExample.astro +17 -0
  322. package/src/components/content/ResponseField.astro +32 -0
  323. package/src/components/content/Step.astro +1 -0
  324. package/src/components/content/Tab.astro +0 -1
  325. package/src/components/content/Tabs.astro +4 -0
  326. package/src/components/content/Tile.astro +1 -0
  327. package/src/components/content/Tooltip.astro +1 -0
  328. package/src/components/content/Tree.astro +1 -0
  329. package/src/components/content/TypeTable.astro +4 -1
  330. package/src/components/content/View.astro +21 -0
  331. package/src/components/content/ViewSwitcher.astro +168 -0
  332. package/src/components/content/api-field.ts +100 -0
  333. package/src/components/content/api-page.ts +224 -0
  334. package/src/components/content/badge-color.ts +5 -2
  335. package/src/components/content/base-href.ts +13 -36
  336. package/src/components/content/view-switcher.ts +71 -0
  337. package/src/components/index.ts +2 -0
  338. package/src/components/islands/Assistant.astro +10 -1
  339. package/src/components/islands/assistant.tsx +125 -18
  340. package/src/components/islands/base-path.ts +47 -10
  341. package/src/components/islands/code-question.ts +62 -0
  342. package/src/components/islands/hooks.ts +145 -40
  343. package/src/components/islands/support-link.ts +68 -0
  344. package/src/components/islands/webmcp.ts +4 -2
  345. package/src/components/layout/Analytics.astro +11 -2
  346. package/src/components/layout/Banner.astro +14 -10
  347. package/src/components/layout/Breadcrumbs.astro +11 -4
  348. package/src/components/layout/ConsentBanner.astro +62 -0
  349. package/src/components/layout/ConsentHead.astro +38 -0
  350. package/src/components/layout/Directory.astro +94 -0
  351. package/src/components/layout/DiscoveryLinks.astro +9 -5
  352. package/src/components/layout/DrawerSwitchers.astro +51 -0
  353. package/src/components/layout/Empty.astro +2 -2
  354. package/src/components/layout/Header.astro +124 -82
  355. package/src/components/layout/LanguageSwitcher.astro +50 -17
  356. package/src/components/layout/NarrationPlayer.astro +1085 -0
  357. package/src/components/layout/NavSelector.astro +35 -9
  358. package/src/components/layout/NavTabMenu.astro +3 -3
  359. package/src/components/layout/NavTree.astro +16 -6
  360. package/src/components/layout/PageActions.astro +4 -3
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +78 -9
  363. package/src/components/layout/Pagination.astro +3 -3
  364. package/src/components/layout/ReferenceLayout.astro +17 -6
  365. package/src/components/layout/RelatedPages.astro +42 -0
  366. package/src/components/layout/RootLayout.astro +313 -50
  367. package/src/components/layout/Search.astro +64 -30
  368. package/src/components/layout/SiteFooter.astro +124 -0
  369. package/src/components/layout/SiteMetatags.astro +16 -0
  370. package/src/components/layout/VersionBanner.astro +2 -2
  371. package/src/components/layout/analytics-client.ts +12 -0
  372. package/src/components/layout/consent/native.ts +79 -0
  373. package/src/components/layout/consent/types.ts +37 -0
  374. package/src/components/layout/footer-links.ts +53 -0
  375. package/src/components/layout/footer-socials.ts +87 -0
  376. package/src/components/layout/locale-redirect.ts +107 -0
  377. package/src/components/layout/nav-utils.ts +83 -0
  378. package/src/components/layout/page-feedback.ts +96 -0
  379. package/src/components/layout/search/analytics.ts +98 -0
  380. package/src/components/layout/search/flexsearch.ts +17 -5
  381. package/src/components/layout/search/orama-cloud.ts +26 -14
  382. package/src/components/layout/search/types.ts +20 -0
  383. package/src/components/layout/search/typesense.ts +5 -1
  384. package/src/components/layout/toc-active.ts +41 -0
  385. package/src/components/layout/toc-element.ts +8 -14
  386. package/src/components/openapi/ApiTagOperations.astro +2 -2
  387. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  388. package/src/components/openapi/GraphqlChip.astro +2 -2
  389. package/src/components/openapi/Operation.astro +163 -13
  390. package/src/components/openapi/Playground.astro +4 -4
  391. package/src/components/openapi/RequestBody.astro +6 -4
  392. package/src/components/openapi/RequestPanel.astro +63 -8
  393. package/src/components/openapi/Responses.astro +6 -4
  394. package/src/components/openapi/SchemaTable.astro +7 -0
  395. package/src/components/openapi/async-snippets.ts +4 -4
  396. package/src/components/openapi/async.ts +38 -6
  397. package/src/components/openapi/code-samples.ts +97 -0
  398. package/src/components/openapi/helpers.ts +19 -9
  399. package/src/components/openapi/message-composer.ts +6 -1
  400. package/src/components/openapi/message.ts +17 -2
  401. package/src/components/openapi/operation-model.ts +81 -11
  402. package/src/components/openapi/panel.ts +4 -2
  403. package/src/components/openapi/param-style.ts +181 -0
  404. package/src/components/openapi/playground-client.ts +73 -16
  405. package/src/components/openapi/request.ts +190 -26
  406. package/src/components/openapi/schema-tree.ts +30 -24
  407. package/src/components/openapi/security.ts +6 -3
  408. package/src/components/openapi/snippets.ts +953 -13
  409. package/src/components/openapi/ws-client.ts +18 -2
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/base-path.ts +62 -16
  421. package/src/core/builtin-tags.ts +5 -0
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/config-input.ts +203 -27
  424. package/src/core/data.ts +44 -4
  425. package/src/core/diagnostics.ts +5 -2
  426. package/src/core/directive-diagnostics.ts +99 -0
  427. package/src/core/footer.ts +26 -0
  428. package/src/core/frontmatter.ts +21 -18
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/heading-markers.ts +5 -18
  431. package/src/core/i18n-ui.ts +82 -2
  432. package/src/core/i18n.ts +31 -3
  433. package/src/core/includes.ts +20 -3
  434. package/src/core/last-modified.ts +25 -3
  435. package/src/core/links.ts +16 -2
  436. package/src/core/locale-links.ts +5 -1
  437. package/src/core/localizable.ts +26 -0
  438. package/src/core/meta.ts +33 -19
  439. package/src/core/navigation.ts +71 -20
  440. package/src/core/page-modes.ts +56 -0
  441. package/src/core/project-graph.ts +138 -10
  442. package/src/core/redirect-patterns.ts +491 -0
  443. package/src/core/related.ts +131 -0
  444. package/src/core/schema.ts +342 -68
  445. package/src/core/sources/contentful-rich-text.ts +27 -20
  446. package/src/core/sources/filesystem.ts +20 -2
  447. package/src/core/sources/github-releases.ts +25 -6
  448. package/src/core/sources/lexical.ts +23 -18
  449. package/src/core/sources/lower.ts +201 -34
  450. package/src/core/sources/mdx-remote.ts +51 -16
  451. package/src/core/sources/normalize.ts +327 -91
  452. package/src/core/sources/notion.ts +52 -25
  453. package/src/core/sources/obsidian.ts +23 -6
  454. package/src/core/sources/portable-text.ts +38 -21
  455. package/src/core/sources/read.ts +8 -3
  456. package/src/core/sources/strapi-blocks.ts +20 -16
  457. package/src/core/sources/watch.ts +20 -7
  458. package/src/core/standard-schema.ts +10 -6
  459. package/src/core/types.ts +1 -1
  460. package/src/core/ui-packs/ar.ts +43 -0
  461. package/src/core/ui-packs/bg.ts +45 -0
  462. package/src/core/ui-packs/bn.ts +43 -0
  463. package/src/core/ui-packs/ca.ts +46 -0
  464. package/src/core/ui-packs/cs.ts +44 -0
  465. package/src/core/ui-packs/da.ts +45 -0
  466. package/src/core/ui-packs/de.ts +46 -0
  467. package/src/core/ui-packs/el.ts +45 -0
  468. package/src/core/ui-packs/es.ts +46 -0
  469. package/src/core/ui-packs/fa.ts +45 -0
  470. package/src/core/ui-packs/fi.ts +45 -0
  471. package/src/core/ui-packs/fr.ts +46 -0
  472. package/src/core/ui-packs/he.ts +43 -0
  473. package/src/core/ui-packs/hi.ts +44 -0
  474. package/src/core/ui-packs/hr.ts +45 -0
  475. package/src/core/ui-packs/hu.ts +44 -0
  476. package/src/core/ui-packs/id.ts +45 -0
  477. package/src/core/ui-packs/it.ts +45 -0
  478. package/src/core/ui-packs/ja.ts +45 -0
  479. package/src/core/ui-packs/ko.ts +44 -0
  480. package/src/core/ui-packs/nl.ts +46 -0
  481. package/src/core/ui-packs/no.ts +44 -0
  482. package/src/core/ui-packs/pl.ts +45 -0
  483. package/src/core/ui-packs/pt-br.ts +46 -0
  484. package/src/core/ui-packs/pt.ts +45 -0
  485. package/src/core/ui-packs/ro.ts +45 -0
  486. package/src/core/ui-packs/ru.ts +45 -0
  487. package/src/core/ui-packs/sk.ts +44 -0
  488. package/src/core/ui-packs/sr.ts +45 -0
  489. package/src/core/ui-packs/sv.ts +44 -0
  490. package/src/core/ui-packs/th.ts +43 -0
  491. package/src/core/ui-packs/tr.ts +45 -0
  492. package/src/core/ui-packs/uk.ts +45 -0
  493. package/src/core/ui-packs/vi.ts +45 -0
  494. package/src/core/ui-packs/zh-tw.ts +43 -0
  495. package/src/core/ui-packs/zh.ts +43 -0
  496. package/src/core/variables.ts +103 -0
  497. package/src/core/version-cut.ts +52 -15
  498. package/src/deploy/artifacts.ts +104 -14
  499. package/src/deploy/cloudflare-negotiation.ts +39 -17
  500. package/src/deploy/headers.ts +10 -5
  501. package/src/deploy/node-headers.ts +64 -10
  502. package/src/deploy/platforms/cloudflare.ts +44 -3
  503. package/src/deploy/platforms/netlify.ts +34 -1
  504. package/src/deploy/platforms/node.ts +1 -0
  505. package/src/deploy/platforms/static.ts +1 -0
  506. package/src/deploy/platforms/types.ts +8 -0
  507. package/src/deploy/platforms/vercel.ts +38 -2
  508. package/src/deploy/redirects.ts +139 -36
  509. package/src/deploy/robots.ts +2 -2
  510. package/src/deploy/rss.ts +4 -3
  511. package/src/deploy/sitemap.ts +7 -5
  512. package/src/deploy/vercel-negotiation.ts +36 -0
  513. package/src/markdown/api-rail.ts +259 -0
  514. package/src/markdown/base-links.ts +34 -36
  515. package/src/markdown/code-title.ts +35 -9
  516. package/src/markdown/directives.ts +242 -36
  517. package/src/markdown/features.ts +20 -2
  518. package/src/markdown/fence-meta.ts +2 -0
  519. package/src/markdown/include.ts +11 -1
  520. package/src/markdown/index.ts +35 -11
  521. package/src/markdown/math.ts +4 -4
  522. package/src/markdown/mdast.ts +5 -2
  523. package/src/markdown/relative-links.ts +12 -4
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +136 -11
  531. package/src/og/derive.ts +41 -31
  532. package/src/og/index.ts +1 -0
  533. package/src/openapi/asyncapi.ts +4 -2
  534. package/src/openapi/model.ts +77 -36
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +55 -2
  537. package/src/openapi/proxy.ts +63 -10
  538. package/src/openapi/references.ts +21 -5
  539. package/src/openapi/render-mdx.ts +46 -3
  540. package/src/openapi/scalar.ts +40 -6
  541. package/src/openapi/source.ts +1 -1
  542. package/src/ratelimit/cloudflare.ts +54 -0
  543. package/src/ratelimit/index.ts +30 -0
  544. package/src/ratelimit/memory.ts +45 -0
  545. package/src/ratelimit/runtime.ts +229 -0
  546. package/src/ratelimit/schema.ts +28 -0
  547. package/src/ratelimit/upstash.ts +30 -0
  548. package/src/ratelimit/wrangler.ts +70 -0
  549. package/src/reference/asyncapi.ts +2 -2
  550. package/src/reference/graphql.ts +5 -2
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +51 -7
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +201 -28
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/version-scope.ts +30 -0
  557. package/src/search/documents.ts +93 -28
  558. package/src/search/orama-index.ts +28 -3
  559. package/src/search/pagefind-ranking.ts +39 -0
  560. package/src/search/popular.ts +2 -1
  561. package/src/search/sync/algolia.ts +15 -4
  562. package/src/search/sync/orama-cloud.ts +3 -0
  563. package/src/search/sync/typesense.ts +5 -0
  564. package/src/seo/jsonld.ts +7 -3
  565. package/src/seo/metatags.ts +67 -0
  566. package/src/theme/chrome-icons.ts +1 -0
  567. package/src/theme/color.ts +500 -0
  568. package/src/theme/entry.ts +56 -3
  569. package/src/theme/palette.ts +121 -45
  570. package/src/translate/meta.ts +68 -24
  571. package/src/translate/prompts.ts +1 -0
  572. package/src/translate/run.ts +3 -3
  573. package/src/translate/validate.ts +12 -3
  574. package/src/translate/work-list.ts +56 -17
  575. package/src/upgrade/upgrade.ts +4 -171
  576. package/dist/cli/chunk-3yce002v.js.map +0 -14
  577. package/dist/cli/chunk-5r8g91qn.js.map +0 -34
  578. package/dist/cli/chunk-6dtt0zfn.js.map +0 -10
  579. package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
  580. package/dist/cli/chunk-ddndchfr.js.map +0 -14
  581. package/dist/cli/chunk-esh98wmb.js +0 -23
  582. package/dist/cli/chunk-esh98wmb.js.map +0 -10
  583. package/dist/cli/chunk-hqp2ajnh.js.map +0 -35
  584. package/dist/cli/chunk-hr8ne106.js.map +0 -13
  585. package/dist/cli/chunk-j85scx15.js.map +0 -10
  586. package/dist/cli/chunk-mqc662a6.js +0 -75
  587. package/dist/cli/chunk-mqc662a6.js.map +0 -11
  588. package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
  589. package/dist/cli/chunk-qkb5a8sa.js.map +0 -10
  590. package/dist/cli/chunk-v6ya5kcb.js.map +0 -189
  591. package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
  592. package/dist/cli/chunk-xh43dwgw.js.map +0 -36
  593. package/docs/content/sources.mdx +0 -338
  594. /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-1n228xqb.js.map} +0 -0
  595. /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-5ffywsmb.js.map} +0 -0
  596. /package/dist/cli/{chunk-g698a744.js.map → chunk-5wj2qwn2.js.map} +0 -0
  597. /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-amrb6hgh.js.map} +0 -0
  598. /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-gp84za0h.js.map} +0 -0
  599. /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-p804f3pe.js.map} +0 -0
  600. /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-ta27feev.js.map} +0 -0
  601. /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-y4ykbark.js.map} +0 -0
@@ -34,6 +34,23 @@ seo:
34
34
  ---
35
35
  ```
36
36
 
37
+ ## Your own tags
38
+
39
+ For anything Blume has no setting for, add tags under `seo.metatags` and every page carries them, custom pages and API references included. It suits site-verification tokens from Google, Bing, or Facebook, a `theme-color`, or an app banner:
40
+
41
+ ```ts blume.config.ts lineNumbers
42
+ seo: {
43
+ metatags: {
44
+ "google-site-verification": "abc123",
45
+ "msvalidate.01": "def456",
46
+ "theme-color": "#0b5fff",
47
+ "apple-itunes-app": "app-id=123456789",
48
+ },
49
+ }
50
+ ```
51
+
52
+ Each key is the tag's name and each value its content. Open Graph families (`og:`, `fb:`, `article:`, and the rest) render with `property`, as the protocol names them, and everything else with `name`. The tags Blume writes itself, listed at the top of this page, can't be set here, so a page never carries two values for one tag. The build names the setting that controls each: `og:image` comes from [`seo.og`](/docs/discoverability/open-graph) or a page's `seo.image`, and `robots` from a page's `seo.noindex`.
53
+
37
54
  ## Per-page overrides
38
55
 
39
56
  Override any of the other tags per page with `seo` frontmatter:
@@ -72,11 +72,13 @@ seo: {
72
72
 
73
73
  By default the card renders in Takumi's built-in font, which covers only basic Latin glyphs — on its own, a title in another script (Japanese, Chinese, Korean, Arabic, Hindi, Russian, …) would render as tofu, empty boxes.
74
74
 
75
- **Your [locales](/docs/content/i18n) cover their own scripts.** For each configured locale whose script the built-in font can't draw, the card adds a Google Noto family as a fallback: `Noto Sans JP` for `ja`, `Noto Sans Devanagari` for `hi`, `Noto Sans SC` or `Noto Sans TC` for Chinese, `Noto Sans` for Cyrillic, Greek, Vietnamese, and accented Latin, and so on. Fallback is per glyph, so Latin text keeps the built-in font and an English card looks the same as on a single-language site. The fallbacks come from Google Fonts at build time, which needs network access, and a card fetches only the glyph subsets its text uses. They apply unless `og.fonts` is set, which takes over the whole list.
75
+ **Every script is covered by default.** Behind the built-in font, cards carry a fallback stack of Google Noto families, one per script: `Noto Sans` for Cyrillic, Greek, Vietnamese, and accented Latin, then `Noto Sans JP`, `Noto Sans Arabic`, `Noto Sans Devanagari`, and so on. A Japanese or Hindi title renders with nothing to configure, even on a site with no [locales](/docs/content/i18n). Fallback is per glyph, so Latin text keeps the built-in font. A card fetches from Google Fonts only when its text has a glyph the built-in font can't draw, and then only the subsets those glyphs need: an English card fetches nothing, so a Latin-only site still builds offline. The stack applies unless `og.fonts` is set, which takes over the whole list.
76
+
77
+ Chinese, Japanese, and Korean share most Han characters but draw some of them differently, and cards use Japanese forms by default. Each configured locale moves its own family to the front, so a site with a `zh` locale draws Han in Simplified Chinese forms, and `zh-Hant` or `zh-TW` in Traditional ones.
76
78
 
77
79
  **Set [`theme.fonts`](/docs/configuration/theming#fonts) and the card follows it.** When your config picks its own fonts, the generated cards automatically render the headline in your display font and the description and footer in your body font, so shared links match the site — including non-Latin coverage, with nothing to configure here. (Families from non-Google providers are skipped — the card renderer can only fetch from Google Fonts — but local font files work.)
78
80
 
79
- To use different fonts on cards than on the site, or to add script coverage without touching the theme, set `og.fonts` explicitly — it always wins over the theme-derived fonts and the locale fallbacks:
81
+ To use different fonts on cards than on the site, set `og.fonts` explicitly. It always wins over the theme-derived fonts and replaces the script fallbacks, so list every family your cards need:
80
82
 
81
83
  ```ts blume.config.ts lineNumbers
82
84
  seo: {
@@ -94,7 +96,7 @@ Each entry is a Google Fonts family name, an object pinning its `weight` (a numb
94
96
 
95
97
  Google families are fetched at build — so a build that uses them needs network access — and the renderer only pulls the glyph subsets each title actually uses. Fallback is per-glyph, so adding a family only affects glyphs the other fonts can't draw.
96
98
 
97
- An explicit `og.fonts: []` opts out entirely: cards keep the built-in font even when `theme.fonts` is set or a locale's script needs a fallback.
99
+ An explicit `og.fonts: []` opts out entirely: cards keep the built-in font even when `theme.fonts` is set or a title needs a script fallback.
98
100
 
99
101
  ## Card cache
100
102
 
@@ -3,7 +3,7 @@ title: RSS feeds
3
3
  description: A feed per dated content type — blog and changelog by default — served at /<type>/rss.xml and advertised to feed readers automatically.
4
4
  ---
5
5
 
6
- Blume builds an RSS feed for each content type in `rss.types` — `blog` and `changelog` by default — that has pages, served at `/<type>/rss.xml`. See [Feeds](/docs/content#feeds) for authoring blog and changelog entries with dates.
6
+ Blume builds an RSS feed for each content type in `seo.rss.types` — `blog` and `changelog` by default — that has pages, served at `/<type>/rss.xml`. See [Feeds](/docs/content#feeds) for authoring blog and changelog entries with dates.
7
7
 
8
8
  A feed's links must be absolute, so feeds need a site URL: set [`deployment.site`](/docs/deployment#set-your-site-url), or deploy to a host Blume detects it from (Vercel, Netlify, Cloudflare). Without one, a build emits no feeds at all. During `blume dev` the local server's URL stands in, so feeds show up there either way.
9
9
 
@@ -0,0 +1,74 @@
1
+ ---
2
+ title: Hand-written API pages
3
+ description: Document an endpoint in MDX, without a spec, and still get the method and path, a Try it panel, request samples, and pinned examples.
4
+ ---
5
+
6
+ Not every API has an OpenAPI spec, and some endpoints read better written by hand. A page with `api` frontmatter documents one endpoint: its [fields](/docs/content/components#api-fields) describe the request and response, and Blume builds the rest of an [OpenAPI reference](/docs/references/openapi) page from them: the method and path at the top, a Try it panel and request samples in a column beside the content, and any [request and response examples](/docs/content/components#request-and-response-examples) pinned under them.
7
+
8
+ ````mdx docs/users/create.mdx lineNumbers
9
+ ---
10
+ title: Create a user
11
+ api: POST /workspaces/{workspaceId}/users
12
+ ---
13
+
14
+ Creates a user and sends them an invite.
15
+
16
+ <ParamField path="workspaceId" type="string" required>
17
+ The workspace to add the user to.
18
+ </ParamField>
19
+
20
+ <ParamField body="email" type="string" required placeholder="ada@example.com">
21
+ The user's email address.
22
+ </ParamField>
23
+
24
+ <ParamField body="role" type="string" default="member">
25
+ One of `owner`, `admin`, or `member`.
26
+ </ParamField>
27
+
28
+ <ResponseField name="id" type="string" required>
29
+ The new user's ID.
30
+ </ResponseField>
31
+
32
+ <ResponseExample>
33
+
34
+ ```json 201
35
+ { "id": "usr_8f2k", "status": "invited" }
36
+ ```
37
+
38
+ </ResponseExample>
39
+ ````
40
+
41
+ `api` takes an HTTP method and a path or a full URL. Path parameters are written in braces, `{workspaceId}`, and filled from the matching `path` field. A full URL, like `GET https://api.acme.com/v1/users`, is sent to as written; a path joins the site's [`api.server`](#site-defaults).
42
+
43
+ ## The playground and samples
44
+
45
+ The page's `ParamField`s become the Try it panel's inputs, the way an operation's parameters do in a spec: `path`, `query`, and `header` fields are parameters, and `body` fields make up a JSON body, with a field's nested fields (inside its [`Expandable`](/docs/content/components#expandable)) as the properties of an object. A `type` of `string[]` is an array, and one that isn't a JSON type, such as `enum<string>`, is sent as a string. A field's `default` fills the body, and its `placeholder` is the example value the samples show.
46
+
47
+ Beside the panel, the page gets request samples in cURL, JavaScript, and Python. A page with its own `RequestExample` shows that in their place.
48
+
49
+ Two more frontmatter keys tune the page:
50
+
51
+ - `authMethod` sets how the endpoint authenticates: `bearer`, `basic`, `key` (an API key in a header), or `none`. It overrides the site's [`api.auth`](#site-defaults).
52
+ - `playground` sets what the page shows: `interactive` (the default) for the Try it panel and samples, `simple` for the samples only, or `none` for neither. The method, path, and any examples stay.
53
+
54
+ ## Site defaults
55
+
56
+ `api` in `blume.config.ts` sets what every endpoint page shares:
57
+
58
+ ```ts blume.config.ts lineNumbers
59
+ export default defineConfig({
60
+ api: {
61
+ server: "https://api.acme.com/v1",
62
+ auth: { method: "key", name: "x-api-key" },
63
+ playground: { proxy: true },
64
+ },
65
+ });
66
+ ```
67
+
68
+ - `server` is the base URL an `api` path joins.
69
+ - `auth` is how requests authenticate unless a page sets `authMethod`: `method` is `bearer`, `basic`, `key`, or `none`, and `name` is the header an API key goes in (`x-api-key` by default). Without `auth`, pages send no credentials.
70
+ - `playground` takes the same values as an OpenAPI reference's [playground option](/docs/references/openapi#try-it-playground): `false` hides the Try it panel on every page, and `proxy` sends its requests through a CORS proxy, a URL of your own or `true` for Blume's built-in one. The built-in proxy needs [server output](/docs/deployment#server-rendering), and only forwards to `server`'s origin and the origins of any full URLs in `api` frontmatter.
71
+
72
+ The page's layout, fields, and examples are the same components as elsewhere, so the [Markdown copy](/docs/discoverability/llms-txt) of the page lists each field, and search indexes the page like any other.
73
+
74
+ The frontmatter keys and component props match Mintlify's, so pages written for it keep working as they are.
@@ -28,11 +28,11 @@ AsyncAPI **2.x specs are normalized to 3.x automatically** with the official Asy
28
28
 
29
29
  ## Code samples
30
30
 
31
- Code samples are **protocol-aware**, keyed off the operation's binding (or its servers' protocol): `wscat` and a browser `WebSocket` snippet for WebSockets, `kcat` for Kafka, `mosquitto_pub`/`mosquitto_sub` for MQTT. `codeSamples` filters that set, the same way it picks languages on `openapi()`; a protocol without a supported tool renders the message payload example alone rather than a fabricated client.
31
+ Code samples are **protocol-aware**, keyed off the operation's binding (or its servers' protocol): `wscat` and a browser `WebSocket` snippet for WebSockets, `kcat` for Kafka, `mosquitto_pub`/`mosquitto_sub` for MQTT. `codeSamples` filters that set, the same way it picks languages on `openapi()`, and `codeSamples: false` shows none. A protocol without a supported tool renders the message payload example alone rather than a fabricated client.
32
32
 
33
33
  ## Shared options
34
34
 
35
- Everything documented for [OpenAPI](/docs/references/openapi) carries over, [`playground`](#try-it-for-events) included: [`route`](/docs/references/openapi#route), [`sources`](/docs/references/openapi#multiple-specs) with `label`/`route`, `expandSchemas`, the [per-source indexing](/docs/references/openapi#per-source-indexing) flags (`seoDescriptionSuffix` too — the generated sentence names the channel and action instead of an endpoint), and search indexing by operation summary and tag.
35
+ Everything documented for [OpenAPI](/docs/references/openapi) carries over, [`playground`](#try-it-for-events) included: [`route`](/docs/references/openapi#route), [`sources`](/docs/references/openapi#multiple-specs) with `label`/`route`, `expandSchemas`, the [per-source indexing](/docs/references/openapi#per-source-indexing) flags (`seoDescriptionSuffix` too — the generated sentence names the channel and action instead of an endpoint), and search indexing by operation summary, description, tag, and endpoint (`SEND user/signup`).
36
36
 
37
37
  ## Embedding Scalar instead
38
38
 
@@ -40,13 +40,13 @@ navigation: {
40
40
 
41
41
  ## Generated examples
42
42
 
43
- Every operation page carries a complete, valid example operation — one variable per argument, typed off the schema, with a bounded-depth selection set over the return type — plus matching example variables and an example response that mirrors the same selection. Code samples show the exact HTTP request (a JSON `POST` of `{ query, variables }`) in each configured language:
43
+ Every operation page carries a complete, valid example operation — one variable per argument, typed off the schema, with a bounded-depth selection set over the return type — plus matching example variables and an example response that mirrors the same selection. Code samples show the exact HTTP request (a JSON `POST` of `{ query, variables }`) in each configured language. The same [languages](/docs/references/openapi#code-samples-and-schemas) as `openapi()` are available, and `codeSamples: false` shows none:
44
44
 
45
45
  ```ts blume.config.ts lineNumbers
46
46
  reference: [
47
47
  graphql({
48
48
  spec: "./schema.graphql",
49
- codeSamples: ["curl", "js"], // built in: curl, js, python
49
+ codeSamples: ["curl", "js"], // any language openapi() offers
50
50
  }),
51
51
  ],
52
52
  ```
@@ -82,7 +82,7 @@ reference: [
82
82
 
83
83
  Query and mutation pages render an interactive panel: edit the request body (the query and variables), point it at your endpoint or a custom URL, and send — the code samples update live so what you copy is byte-for-byte what was sent. Disable it with `playground: false`. Subscription pages show the generated operation and an example event instead: subscriptions run over a stateful transport (WebSocket or SSE) that the playground's single HTTP `POST` can't speak.
84
84
 
85
- If your GraphQL API doesn't allow cross-origin requests from the docs site, route sends through a CORS proxy — a URL of your own, or `true` for the built-in `/_api-proxy` endpoint (under your `basePath`, if you set one; it needs [server output](/docs/deployment#server-rendering): a host adapter such as `deployment: vercel()` from `blume/deploy`). The built-in proxy only forwards to origins your documented specs declare — each configured GraphQL `endpoint`, plus any absolute `servers[].url` from a documented [OpenAPI spec](/docs/references/openapi) — so a public docs deployment can't be aimed at other hosts. That makes `endpoint` required for a working proxy: without one, the proxy has no origin to allow for this reference and refuses every send (the build warns about this). The same body limit and response headers apply as for the [OpenAPI proxy](/docs/references/openapi#try-it-playground).
85
+ If your GraphQL API doesn't allow cross-origin requests from the docs site, route sends through a CORS proxy — a URL of your own, or `true` for the built-in `/_api-proxy` endpoint (under your `basePath`, if you set one; it needs [server output](/docs/deployment#server-rendering): a host adapter such as `deployment: vercel()` from `blume/deploy`). The built-in proxy only forwards to origins your documented specs declare — each configured GraphQL `endpoint`, plus any absolute `servers[].url` from a documented [OpenAPI spec](/docs/references/openapi) — so a public docs deployment can't be aimed at other hosts. That makes `endpoint` required for a working proxy: without one, the proxy has no origin to allow for this reference and refuses every send (the build warns about this). The same body limit, header forwarding, and response headers apply as for the [OpenAPI proxy](/docs/references/openapi#try-it-playground).
86
86
 
87
87
  ```ts blume.config.ts lineNumbers
88
88
  reference: [
@@ -2,6 +2,6 @@ import { defineMeta } from "blume";
2
2
 
3
3
  export default defineMeta({
4
4
  order: 7,
5
- pages: ["openapi", "asyncapi", "graphql", "scalar"],
5
+ pages: ["openapi", "asyncapi", "graphql", "scalar", "api-pages"],
6
6
  title: "References",
7
7
  });
@@ -29,7 +29,7 @@ navigation: {
29
29
  ```
30
30
 
31
31
  :::note
32
- Operations are indexed for search by their **summary and tag**. The rendered schema tables and code samples aren't full-text indexed; search matches an operation's title and section, then links to its own page.
32
+ Operations are indexed for search by their **summary, description, tag, and endpoint** (`GET /pets/{id}`). The rendered schema tables and code samples aren't full-text indexed; search matches an operation's title, prose, section, and path, then links to its own page.
33
33
  :::
34
34
 
35
35
  ## A local spec
@@ -55,24 +55,72 @@ reference: [
55
55
 
56
56
  ## Code samples and schemas
57
57
 
58
- `codeSamples` picks which languages render per operation (built in: `curl`, `js`, `python`); `expandSchemas` starts nested schema rows expanded rather than collapsed:
58
+ `codeSamples` picks which languages render per operation, in the order you list them. It defaults to `["curl", "js", "python"]`. `expandSchemas` starts nested schema rows expanded rather than collapsed:
59
59
 
60
60
  ```ts blume.config.ts lineNumbers
61
61
  reference: [
62
62
  openapi({
63
63
  spec: "./openapi.yaml",
64
- codeSamples: ["curl", "js"],
64
+ codeSamples: ["curl", "python", "go", "java", "csharp"],
65
65
  expandSchemas: true,
66
66
  }),
67
67
  ],
68
68
  ```
69
69
 
70
+ Every sample is plain, copy-pasteable code for the request, written with the language's standard or most common HTTP client:
71
+
72
+ | Id | Language | Client | Also accepts |
73
+ | ------------ | ---------- | ------------------- | --------------------- |
74
+ | `curl` | cURL | `curl` | `bash`, `sh`, `shell` |
75
+ | `python` | Python | `requests` | `py` |
76
+ | `js` | JavaScript | `fetch` | `javascript` |
77
+ | `node` | Node.js | `axios` | `nodejs`, `node.js` |
78
+ | `typescript` | TypeScript | `fetch` | `ts` |
79
+ | `php` | PHP | the curl extension | |
80
+ | `go` | Go | `net/http` | `golang` |
81
+ | `java` | Java | `java.net.http` | |
82
+ | `ruby` | Ruby | `net/http` | `rb` |
83
+ | `powershell` | PowerShell | `Invoke-RestMethod` | `ps1` |
84
+ | `swift` | Swift | `URLSession` | |
85
+ | `csharp` | C# | `HttpClient` | `c#`, `cs` |
86
+ | `dotnet` | .NET | RestSharp | `.net`, `dot-net` |
87
+ | `c` | C | libcurl | |
88
+ | `cpp` | C++ | cpr | `c++` |
89
+ | `kotlin` | Kotlin | OkHttp | `kt` |
90
+ | `rust` | Rust | reqwest | `rs` |
91
+ | `dart` | Dart | `package:http` | `flutter` |
92
+
93
+ Each value is quoted by its own language's rules, so a header like `If-Match: "33a64df5"` or a body with a `$` in it reaches the API as written.
94
+
95
+ ### Your own samples
96
+
97
+ To show your SDK instead of a raw HTTP call, add `x-codeSamples` to an operation in the spec. It's the extension Redocly defined, and Speakeasy and Stainless can write it for you. Each entry takes a `lang`, the `source`, and an optional `label`:
98
+
99
+ ```yaml openapi.yaml lineNumbers
100
+ paths:
101
+ /plants:
102
+ get:
103
+ x-codeSamples:
104
+ - lang: typescript
105
+ label: SDK
106
+ source: |
107
+ const plants = await planter.plants.list();
108
+ - lang: bash
109
+ label: CLI
110
+ source: |
111
+ planter list
112
+ ```
113
+
114
+ These samples render as their own tabs, ahead of the generated ones, and stay as written when readers change the [Try it](#try-it-playground) form. The older `x-code-samples` spelling works too. To show only your own samples, set `codeSamples: false`.
115
+
70
116
  ## Try it playground
71
117
 
72
- Operation pages rendered natively ship an interactive **Try it** panel by default. Blume generates the form from the operation itself: an input per path, query, and header parameter, a body editor built from the request-body schema, everything prefilled from the spec's examples. A server picker lists the operation's servers (its own `servers`, else its path's, else the spec's, with each `{variable}` at its `default`), with a free-text field for any other base URL, and auth inputs match the operation's [resolved security](#authorization) — bearer token, API key, and basic credentials, with OAuth2 as a token paste field (bring an access token; Blume doesn't run the flow).
118
+ Operation pages rendered natively ship an interactive **Try it** panel by default. Blume generates the form from the operation itself: an input per path, query, and header parameter, a body editor built from the request-body schema, everything prefilled from the spec's examples. Values go on the wire the way the spec describes them: array and object parameters follow their `style` and `explode` (`tags=dog&tags=cat` in a query, `1,2` in a path, `filter[color]=red` for `deepObject`), and an `application/x-www-form-urlencoded` or `multipart/form-data` body is sent as form fields rather than JSON. A server picker lists the operation's servers (its own `servers`, else its path's, else the spec's, with each `{variable}` at its `default`), with a free-text field for any other base URL, and auth inputs match the operation's [resolved security](#authorization) — bearer token, API key, and basic credentials, with OAuth2 as a token paste field (bring an access token; Blume doesn't run the flow).
73
119
 
74
120
  The panel and the code samples stay in lockstep: values typed into the form update the generated samples live, so a copied curl command always matches exactly what **Send** would do. And it stays out of the way — the panel is server-rendered collapsed, and its JavaScript loads only when a reader first opens it. Readers who never touch it download none of it.
75
121
 
122
+ The one method a browser can't send is `TRACE`: `fetch` refuses it, so on a `trace` operation **Send** says so instead of sending, and the JavaScript sample (built on `fetch`) is a note rather than code. The cURL and Python samples send it fine.
123
+
76
124
  `playground: false` is the entire off switch:
77
125
 
78
126
  ```ts blume.config.ts lineNumbers
@@ -98,7 +146,7 @@ reference: [
98
146
  ],
99
147
  ```
100
148
 
101
- The built-in proxy only forwards requests to the origins your specs declare in `servers` (at the document, path, or operation level, with variables at their defaults) — including across redirects — so a public docs deployment can't be aimed at other hosts on its network. A **Custom base URL** typed into the panel isn't a documented server: with the proxy enabled, requests to it are refused with a 403. It reads a request body only up to 4 MB (anything larger gets a `413`), and every response it relays carries `Content-Security-Policy: sandbox`, `X-Content-Type-Options: nosniff`, and `Cross-Origin-Resource-Policy: same-origin` — plus `Content-Disposition: attachment` for HTML or SVG — so an API error page that echoes its input can't run script on the docs origin.
149
+ The built-in proxy only forwards requests to the origins your specs declare in `servers` (at the document, path, or operation level, with variables at their defaults) — including across redirects — so a public docs deployment can't be aimed at other hosts on its network. A **Custom base URL** typed into the panel isn't a documented server: with the proxy enabled, requests to it are refused with a 403. It forwards only the headers the panel sets itself — the credentials and header parameters filled in, and the body's `Content-Type` — so cookies, credentials the browser attaches for the docs site (HTTP Basic auth on a password-protected preview, say), and headers your host adds (`X-Forwarded-For`, `CF-*`, `X-Vercel-*`) never reach the API. It reads a request body only up to 4 MB (anything larger gets a `413`), and every response it relays carries `Content-Security-Policy: sandbox`, `X-Content-Type-Options: nosniff`, and `Cross-Origin-Resource-Policy: same-origin` — plus `Content-Disposition: attachment` for HTML or SVG — so an API error page that echoes its input can't run script on the docs origin. The proxy is also [rate limited](/docs/configuration/rate-limiting) per reader, like every server route a reader can call.
102
150
 
103
151
  ## Multiple specs
104
152
 
@@ -155,6 +203,36 @@ reference: [
155
203
 
156
204
  A [`scalar()`](/docs/references/scalar) embed takes only `noindex` of these — it already sits outside Blume's search and `llms.txt`, so the two `include*` settings have nothing to act on there.
157
205
 
206
+ ## Overlays
207
+
208
+ When the spec is generated by another tool or owned by another team, keep your docs changes out of it with an [OpenAPI Overlay](https://spec.openapis.org/overlay/latest.html): a separate file that lists changes to make to the spec, such as clearer descriptions, a public server URL, or internal operations removed. List overlays beside `spec`. Each is a local path or an `http(s)` URL, and they apply in order:
209
+
210
+ ```ts blume.config.ts lineNumbers
211
+ reference: [
212
+ openapi({
213
+ spec: "./openapi.json",
214
+ overlays: ["./overlays/public.yaml"],
215
+ }),
216
+ ],
217
+ ```
218
+
219
+ ```yaml overlays/public.yaml lineNumbers
220
+ overlay: 1.1.0
221
+ info:
222
+ title: Public docs
223
+ version: 1.0.0
224
+ actions:
225
+ - target: $.paths.*[?@['x-internal'] == true]
226
+ remove: true
227
+ - target: $.info
228
+ update:
229
+ description: The public Acme API.
230
+ ```
231
+
232
+ Each action's `target` is a [JSONPath](https://www.rfc-editor.org/rfc/rfc9535) expression. `update` merges into every node it selects: objects merge key by key, arrays gain the new items, and other values are replaced. `copy` merges in a node from elsewhere in the spec (Overlay 1.1), and `remove: true` deletes the targets. Overlay Specification 1.0 and 1.1 are both supported.
233
+
234
+ Overlays apply to the spec as written, before Blume upgrades it to OpenAPI 3.1, so an overlay targets the version you wrote the spec in. Everything built from the spec sees the result: the operation pages, the sidebar, search, `llms.txt`, and the MCP server. With `sources`, give each source its own `overlays`. An overlay that doesn't apply fails the reference the way an unreadable spec does, so a broken overlay can't publish what it was meant to hide.
235
+
158
236
  ## Authorization
159
237
 
160
238
  Operations that declare [security requirements](https://spec.openapis.org/oas/v3.1.0#security-requirement-object) render an **Authorization** section above their parameters, and the generated code samples send a placeholder credential (`Authorization: Bearer YOUR_TOKEN`, an API-key header, or a query key — whatever the scheme calls for). There's nothing to configure: Blume reads `security` from the spec, so the reference always matches what the API actually enforces.
@@ -166,6 +244,29 @@ The OpenAPI semantics carry over as written:
166
244
  - An empty `{}` entry means auth is **optional** for that operation, and the section says so.
167
245
  - OAuth2 scopes are listed per scheme; scheme `description`s from `components.securitySchemes` render inline.
168
246
 
247
+ ## Webhooks and callbacks
248
+
249
+ Your API's outgoing requests get documented too. Each [webhook](https://spec.openapis.org/oas/v3.1.0#oas-webhooks) in the spec's top-level `webhooks` gets its own page, like an operation: filed under its first tag, or a **Webhooks** group when it has none. The page shows the payload's schema and the responses your endpoint should send back. In place of the Try it panel and code samples, the rail shows an example payload, since your API sends this request rather than serving it. A webhook page lists only the `security` the webhook declares itself: the spec's root `security` guards calls to your API, so it doesn't apply to requests the API sends.
250
+
251
+ ```yaml openapi.yaml lineNumbers
252
+ webhooks:
253
+ newPet:
254
+ post:
255
+ summary: New pet added
256
+ requestBody:
257
+ content:
258
+ application/json:
259
+ schema:
260
+ $ref: "#/components/schemas/Pet"
261
+ responses:
262
+ "200":
263
+ description: Return a 200 to acknowledge the event.
264
+ ```
265
+
266
+ An operation's [`callbacks`](https://spec.openapis.org/oas/v3.1.0#callback-object), the requests your API makes back to a URL the caller supplied, render in a **Callbacks** section on that operation's page. Each shows the URL expression (`{$request.body#/callbackUrl}`), its method, its request body, and its expected responses. Callbacks declared under `components.callbacks` and referenced with `$ref` work the same way.
267
+
268
+ Webhook pages are real pages: they're in search, `llms.txt`, and the MCP server, and their Markdown tells agents it's a request the API sends, so an agent won't try to call it.
269
+
169
270
  ## Embedding Scalar instead
170
271
 
171
272
  `openapi()` always renders Blume's own pages. To embed [Scalar](https://scalar.com)'s self-contained API reference UI on a single route instead — its own sidebar, search, theme, and request client — list a `scalar()` adapter from `blume/reference` in place of (or beside) this one. The [Scalar](/docs/references/scalar) page covers what the embed does and doesn't do, and how to pass Scalar's own options through.
@@ -33,6 +33,8 @@ reference: [
33
33
  ],
34
34
  ```
35
35
 
36
+ [Overlays](/docs/references/openapi#overlays) work here too: list `overlays` beside `spec`, or on each source. The embed then inlines the spec with the overlays applied, so a remote spec with overlays is fetched at build time rather than from the browser. If an overlay doesn't apply, the build warns and leaves that page out rather than embed the spec without it.
37
+
36
38
  A Scalar embed and native pages can sit side by side in the list — an `openapi()` adapter for the current API and a `scalar()` adapter for a legacy one, say — as long as their routes differ. Like every reference, the embed doesn't add a header tab on its own; point a [navigation tab](/docs/content/navigation#tabs) at its route to surface it.
37
39
 
38
40
  ## What the embed doesn't do
@@ -43,7 +45,7 @@ The embed does follow Blume's light/dark toggle: it's pinned to the page's theme
43
45
 
44
46
  ## Passing Scalar options
45
47
 
46
- `theme` is the one option most people reach for, but Scalar supports many more. Every key you pass to `scalar()` beyond `spec`, `sources`, `route`, and `theme` is forwarded verbatim as [Scalar configuration](https://github.com/scalar/scalar/blob/main/documentation/configuration.md) to the embedded reference — Blume doesn't gate the keys, so anything Scalar accepts flows through (JSON values only, since the configuration is inlined into the generated page):
48
+ `theme` is the one option most people reach for, but Scalar supports many more. Every key you pass to `scalar()` beyond `spec`, `overlays`, `sources`, `route`, and `theme` is forwarded verbatim as [Scalar configuration](https://github.com/scalar/scalar/blob/main/documentation/configuration.md) to the embedded reference — Blume doesn't gate the keys, so anything Scalar accepts flows through (JSON values only, since the configuration is inlined into the generated page):
47
49
 
48
50
  ```ts blume.config.ts lineNumbers
49
51
  reference: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blume",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "The open-source docs framework for humans and agents.",
5
5
  "keywords": [
6
6
  "agents",
@@ -54,10 +54,24 @@
54
54
  "types": "./dist/types/analytics/index.d.ts",
55
55
  "default": "./src/analytics/index.ts"
56
56
  },
57
+ "./captcha": {
58
+ "types": "./dist/types/captcha/index.d.ts",
59
+ "default": "./src/captcha/index.ts"
60
+ },
61
+ "./captcha/*": "./src/captcha/*",
62
+ "./consent": {
63
+ "types": "./dist/types/consent/index.d.ts",
64
+ "default": "./src/consent/index.ts"
65
+ },
57
66
  "./deploy": {
58
67
  "types": "./dist/types/deploy/adapters/index.d.ts",
59
68
  "default": "./src/deploy/adapters/index.ts"
60
69
  },
70
+ "./ratelimit": {
71
+ "types": "./dist/types/ratelimit/index.d.ts",
72
+ "default": "./src/ratelimit/index.ts"
73
+ },
74
+ "./ratelimit/*": "./src/ratelimit/*",
61
75
  "./reference": {
62
76
  "types": "./dist/types/reference/index.d.ts",
63
77
  "default": "./src/reference/index.ts"
@@ -138,6 +152,7 @@
138
152
  "html-escaper": "^3.0.3",
139
153
  "jiti": "^2.7.0",
140
154
  "js-yaml": "^5.4.1",
155
+ "jsonpath-rfc9535": "^1.3.0",
141
156
  "katex": "^0.18.7",
142
157
  "markdown-table": "^3.0.4",
143
158
  "marked": "^18.0.12",
@@ -179,7 +194,11 @@
179
194
  "zod": "^4.6.2"
180
195
  },
181
196
  "devDependencies": {
197
+ "@ai-sdk/anthropic": "^4.0.65",
198
+ "@ai-sdk/google": "^4.0.82",
199
+ "@ai-sdk/openai": "^4.0.78",
182
200
  "@ai-sdk/openai-compatible": "^3.0.48",
201
+ "@ai-sdk/xai": "^5.0.10",
183
202
  "@asyncapi/converter": "^2.0.2",
184
203
  "@mixedbread/sdk": "^0.77.0",
185
204
  "@notionhq/client": "^5.26.0",
@@ -201,7 +220,11 @@
201
220
  "typesense": "^3.0.6"
202
221
  },
203
222
  "peerDependencies": {
223
+ "@ai-sdk/anthropic": "^4.0.0",
224
+ "@ai-sdk/google": "^4.0.0",
225
+ "@ai-sdk/openai": "^4.0.0",
204
226
  "@ai-sdk/openai-compatible": "^3.0.0",
227
+ "@ai-sdk/xai": "^5.0.0",
205
228
  "@astrojs/cloudflare": "^14.0.0",
206
229
  "@astrojs/netlify": "^8.0.0",
207
230
  "@astrojs/svelte": "^9.0.0",
@@ -217,9 +240,21 @@
217
240
  "typesense": "^3.0.0"
218
241
  },
219
242
  "peerDependenciesMeta": {
243
+ "@ai-sdk/anthropic": {
244
+ "optional": true
245
+ },
246
+ "@ai-sdk/google": {
247
+ "optional": true
248
+ },
249
+ "@ai-sdk/openai": {
250
+ "optional": true
251
+ },
220
252
  "@ai-sdk/openai-compatible": {
221
253
  "optional": true
222
254
  },
255
+ "@ai-sdk/xai": {
256
+ "optional": true
257
+ },
223
258
  "@astrojs/cloudflare": {
224
259
  "optional": true
225
260
  },
@@ -61,11 +61,14 @@ It bumps `blume` in `package.json`, installs, and lists every config change stil
61
61
 
62
62
  To move a Mintlify, Fumadocs, Docusaurus, Starlight, or Nextra site to Blume, the user runs `npx blume migrate [source] --codex` (or `--claude`) from that project, which opens an agent on the `blume-migrate` skill. When you are that agent, or the user asks you to migrate directly, follow `skills/blume-migrate/SKILL.md` in the installed package instead of this file.
63
63
 
64
+ Every build publishes a generated agent skill for the site at `/skill.md` (`agents.skillMd`). To write a richer one from the docs, the user runs `blume skill --codex` (or `--claude`), which opens an agent on the `blume-write-skill` skill. When you are that agent, or the user asks you to write the site's skill directly, follow `skills/blume-write-skill/SKILL.md` in the installed package.
65
+
64
66
  ## What's included
65
67
 
66
68
  - **Components** — callouts, cards, steps, tabs, accordions, badges, file trees, and parameter tables, usable in MDX with no imports.
67
69
  - **Local search** — Orama in dev and production, with no hosted index; Pagefind, Algolia, and other backends are one adapter away (`search: pagefind()` from `blume/search`).
68
70
  - **AI** — `llms.txt`, raw Markdown URLs, a JSON docs API with an OpenAPI description, Copy as Markdown, Open in chat, an in-page assistant, and an MCP server endpoint served by the docs site itself.
71
+ - **Narration** — a "Listen to this page" player (`narration: true` for browser voices, or `narration: { provider: gateway({ … }) }` from `blume/ai` for voices generated at build).
69
72
  - **Navigation** — inferred from files, refined with `meta.ts` or config.
70
73
  - **SEO** — metadata, Open Graph images, RSS feeds, and JSON-LD.
71
74
  - **Customization** — component overrides, React islands, custom pages, theme tokens, and a source-component registry via `blume add`.
@@ -15,7 +15,7 @@ Throughout this skill (including the `references/` files), **`<skill>` means the
15
15
 
16
16
  - **Target idiomatic Blume, not a mechanical port.** Prefer filesystem-derived navigation over an exhaustive explicit `navigation.sidebar`. Prefer `:::` directives over JSX callouts. Prefer Blume defaults over restating them in config.
17
17
  - **Every field has a default; `{}` is a valid config.** Map only what the source _declares_. If the source uses a framework default, don't write it.
18
- - **Drop chrome that has no Blume equivalent — and say so.** Navbar CTAs, footer columns, custom theming, dynamic redirects, and unmappable icons get reported to the user, not silently discarded or faked.
18
+ - **Drop chrome that has no Blume equivalent — and say so.** Navbar CTAs, footer copyright text, custom theming, conditional redirects, and unmappable icons get reported to the user, not silently discarded or faked.
19
19
  - **Convert, don't preserve.** Blume's page frontmatter schema is **strict** — unknown keys are build errors. A source-only frontmatter key must be mapped to a Blume key or removed (and reported), never left to "maybe validate."
20
20
 
21
21
  ## Migration workflow
@@ -44,7 +44,7 @@ The single biggest shift for most sources — especially Mintlify — is that **
44
44
 
45
45
  - **Folders become groups, files become pages.** A page's sidebar label is its frontmatter `title`; a group's label is the humanized folder name.
46
46
  - **Ordering resolves highest-priority-first:** an explicit `navigation.sidebar` (replaces the whole tree) → a folder's `meta.ts` `pages` array → a page's frontmatter `sidebar.order` → the filesystem (`index` first, then numeric filename prefix like `01-`, then alphabetical).
47
- - **`meta.ts` refines one folder** (`defineMeta({ title, icon, order, collapsed, pages, display })`). The `pages` array lists children by slug (numeric prefix and parentheses stripped); children you omit fall back to their own `sidebar.order`, then filesystem order (`index` still sorts first) — so a partial `pages` list is safe, but list every child when the source declared a complete order.
47
+ - **`meta.ts` refines one folder** (`defineMeta({ title, icon, order, collapsed, pages, display, directory })`). The `pages` array lists children by slug (numeric prefix and parentheses stripped); children you omit fall back to their own `sidebar.order`, then filesystem order (`index` still sorts first) — so a partial `pages` list is safe, but list every child when the source declared a complete order.
48
48
  - **Sidebar render mode: a global default with per-folder overrides.** `navigation.sidebar.display` in `blume.config.ts` is `"flat"` (default), `"group"` (collapsible), or `"page"` (drill-in sub-panel) and sets the mode for every group at once. A folder can override its **own** group: `display` in its `meta.ts`, or — sugar when the folder has an `index` page — `sidebar.display` in that index page's frontmatter. Precedence: index frontmatter → `meta.ts` → global config → `flat`; an override applies to that one group only (nested subgroups resolve their own chain). So a source's per-category collapse/drill-in modes migrate per folder — only reach for the global mode when the whole sidebar changes. Under an explicit `navigation.sidebar`, the config item's own `display` field is the only per-group control (frontmatter/meta `display` is ignored there, with a `BLUME_SIDEBAR_DISPLAY_IGNORED` warning).
49
49
  - **An explicit `navigation.sidebar` replaces filesystem generation entirely.** Use it only for a nav shape files can't express. Its items are a page route string, a group (`{ label, items }`), or a link (`{ label, href }`).
50
50
  - **Config-declared nesting has no on-disk counterpart — materialize it or it flattens silently.** When a source (Mintlify `groups`, Nextra `_meta`, a Docusaurus sidebar…) declares a nested group, its pages usually sit **flat in one folder** and the grouping lives only in config. Filesystem-derived nav sees the flat folder and drops the inner group. To keep the nesting you must **either** move those pages into a real subfolder (`meta.ts` for label/`collapsed`) — which changes their URLs, so add `redirects` — **or** declare the group in an explicit `navigation.sidebar`, which nests the existing routes without moving a file. Walk config `pages`/nav arrays **recursively** during inventory and record where config nesting depth exceeds on-disk depth; that gap is exactly what gets lost.
@@ -69,7 +69,7 @@ The single biggest shift for most sources — especially Mintlify — is that **
69
69
  - **`content`:** `root` (default `"docs"`, relative to the project dir where `blume` runs), `include`/`exclude` (arrays of globs **relative to `content.root`**; defaults `["**/*.{md,mdx}"]` / `["**/_*", "**/.*"]`), `sources` (an array of **adapters imported from `blume/sources`**: `filesystem({ root, include, exclude })`, `obsidian({ vault })`, `githubReleases({ owner, repo })`, `notion({ database })`, `sanity({ projectId, dataset, query })`, `contentful({ space, contentType })`, `payload({ url, collection })`, `strapi({ url, contentType })`, `mdxRemote({ github })`, `custom(source)`; every factory with an options object also takes `prefix` and `pollInterval`, while `custom(source)` takes a `ContentSource` instance that sets its own `prefix`. The 1.x `{ type: "…" }` objects were removed — rename `type` to the factory call and pass the other fields as its options — except `{ type: "custom", source }`, which becomes `custom(source)` with the instance as the only argument. `root`/`include`/`exclude` are shorthand for a single `filesystem()` and are **rejected beside `sources`** — move them into the `filesystem()` entry. OpenAPI/AsyncAPI/GraphQL are **not** among these; they're adapters in the top-level `reference` list), `pages` (custom `.astro` dir), `defaultType`. When docs sit directly under the project dir (no `docs/` subfolder), set `root` there and **scope `include` to the real content folders** instead of scanning everything — `references/monorepo.md` §1.
70
70
  - **`basePath`** (top-level): a site-wide mount point (e.g. `"/docs"`) prepended to **every** route while staying invisible to the sidebar (no wrapper group). This is the right target for a source that served all docs under a prefix (Docusaurus `routeBasePath`, a Fumadocs `baseUrl` of `/docs`) — distinct from a per-source `prefix` (which adds a nav group) and from `deployment.base` (host subdirectory).
71
71
  - **`navigation`:** `tabs`, `selectors`, `actions` and `cta` (header links and the one filled button), `featured` (links pinned above the sidebar on every route), `sidebar` (`{ display, items }` — `display` is the global render mode above; `items` is an explicit tree), `repo` (`true`/`false`, or an absolute GitHub URL for the header mark when the docs repo is private and `github` must stay unset). **Avoid an explicit `navigation.sidebar` unless you have to** — lean on the filesystem-derived sidebar. It only works when the file tree roughly matches the intended sidebar layout, so reshape folders to match first; reach for `sidebar.items` only for a shape files genuinely can't express (see "Config-declared nesting" above).
72
- - **`search`** — an adapter imported from `blume/search`: `orama()` (default, so omit `search` entirely for it), `flexsearch()`, `pagefind()`, `algolia({ appId, apiKey, indexName })`, `oramaCloud({ endpoint, apiKey, indexId? })`, `typesense({ host, collection, apiKey })`, `mixedbread({ storeId })`, or `false` to disable. Pass the adapter directly (`search: algolia({…})`) or as `search: { provider, popular, indexing }` when you also set curated links or indexing options. **Blume 1.x's `search.provider` string and its `search.algolia`/`oramaCloud`/`typesense`/`mixedbread` credential blocks are gone** — when a source config (or an older `blume.config.ts`) has `provider: "algolia", algolia: { appId, indexName, searchApiKey }`, rewrite it as `search: algolia({ appId, indexName, apiKey: searchApiKey })` (the search-only key is now `apiKey` in every adapter that takes one; admin keys stay in env vars). **`ai`** (the assistant, Open in chat — `ai.assistant.provider` takes an **adapter descriptor** imported from `blume/ai`: `gateway({ model })` (the default, `openai/gpt-5.5`), `openrouter({ model, reasoning })`, `llmgateway({ model })`, `inkeep({ model })`, or `openaiCompatible({ baseUrl, name, model, apiKeyEnv })`. Every adapter takes `model`, `apiKeyEnv`, `headers`, and a verbatim `providerOptions` passthrough (`headers` values are written into the generated route source as-is, so they are for non-secret static headers only — a bearer token or any other credential belongs in the env var `apiKeyEnv` names, never in `headers`). Every adapter except `inkeep()` also takes `reasoning`: Inkeep runs its own answer pipeline, so `inkeep({ reasoning })` fails validation with an unrecognized key — drop a source's reasoning setting there and report it. There are **no** flat `provider`/`model`/`apiKeyEnv`/`baseUrl`/`headers`/`reasoning` fields on `ai.assistant` — a source that configured an AI assistant that way (Blume < 2.0 included) maps onto one adapter call. `enabled`, `instructions`, `retrieval`, `suggestions`, `cors`, and `endpoint` sit on `ai.assistant` itself; there is **no** `ai.ask` — Blume 2.0.0 and earlier used that name, so an older `blume.config.ts` moves the whole block to `ai.assistant`), **`agents`** (llms.txt, the JSON API, the MCP server, published skills, discovery manifests, robots content signals), **`reference`** (a list of adapters imported from `blume/reference` — `openapi({ spec | sources, route, … })`, `asyncapi({ … })`, `graphql({ spec, endpoint, … })` — never the 1.x `openapi`/`asyncapi`/`graphql` blocks), **`redirects`**, **`seo`**, **`markdown`**, **`analytics`** (a list of adapters imported from `blume/analytics` — one factory per provider (`posthog({ key, host })`, `googleAnalytics({ id })`, `plausible({ domain, host })`, `mixpanel({ token, region })`, `segment({ key })`, …; the docs page lists them all), `vercel()`, and `script({ src | content, strategy, attributes })` for anything without one — never an object keyed by provider), **`deployment`**, **`i18n`**, **`toc`**, **`lastModified`**, **`github`** (`{ owner, repo, branch?, dir?, host?, api? }` — set `host` whenever the source's edit URL is on a GitHub Enterprise origin rather than `github.com`).
72
+ - **`search`** — an adapter imported from `blume/search`: `orama()` (default, so omit `search` entirely for it), `flexsearch()`, `pagefind()`, `algolia({ appId, apiKey, indexName })`, `oramaCloud({ endpoint, apiKey, indexId? })`, `typesense({ host, collection, apiKey })`, `mixedbread({ storeId })`, or `false` to disable. Pass the adapter directly (`search: algolia({…})`) or as `search: { provider, popular, indexing }` when you also set curated links or indexing options. **Blume 1.x's `search.provider` string and its `search.algolia`/`oramaCloud`/`typesense`/`mixedbread` credential blocks are gone** — when a source config (or an older `blume.config.ts`) has `provider: "algolia", algolia: { appId, indexName, searchApiKey }`, rewrite it as `search: algolia({ appId, indexName, apiKey: searchApiKey })` (the search-only key is now `apiKey` in every adapter that takes one; admin keys stay in env vars). **`ai`** (the assistant, Open in chat — `ai.assistant.provider` takes an **adapter descriptor** imported from `blume/ai`: `gateway({ model })` (the default, `openai/gpt-5.5`), `openai({ model })` (or `openai({ baseUrl, name, model, apiKeyEnv })` for any OpenAI-compatible endpoint), `anthropic({ model })`, `gemini({ model })`, `grok({ model })`, `openrouter({ model, reasoning })`, `llmgateway({ model })`, or `inkeep({ model })`. Every adapter takes `model`, `apiKeyEnv`, `headers`, and a verbatim `providerOptions` passthrough (`headers` values are written into the generated route source as-is, so they are for non-secret static headers only — a bearer token or any other credential belongs in the env var `apiKeyEnv` names, never in `headers`). Every adapter except `inkeep()` also takes `reasoning`: Inkeep runs its own answer pipeline, so `inkeep({ reasoning })` fails validation with an unrecognized key — drop a source's reasoning setting there and report it. There are **no** flat `provider`/`model`/`apiKeyEnv`/`baseUrl`/`headers`/`reasoning` fields on `ai.assistant` — a source that configured an AI assistant that way (Blume < 2.0 included) maps onto one adapter call. `enabled`, `instructions`, `retrieval`, `suggestions`, `cors`, and `endpoint` sit on `ai.assistant` itself; there is **no** `ai.ask` — Blume 2.0.0 and earlier used that name, so an older `blume.config.ts` moves the whole block to `ai.assistant`), **`agents`** (llms.txt, the JSON API, the MCP server, published skills, discovery manifests, robots content signals), **`reference`** (a list of adapters imported from `blume/reference` — `openapi({ spec | sources, route, … })`, `asyncapi({ … })`, `graphql({ spec, endpoint, … })` — never the 1.x `openapi`/`asyncapi`/`graphql` blocks), **`redirects`**, **`seo`**, **`markdown`**, **`analytics`** (a list of adapters imported from `blume/analytics` — one factory per provider (`posthog({ key, host })`, `googleAnalytics({ id })`, `plausible({ domain, host })`, `mixpanel({ token, region })`, `segment({ key })`, …; the docs page lists them all), `vercel()`, and `script({ src | content, strategy, attributes })` for anything without one — never an object keyed by provider), **`deployment`**, **`i18n`**, **`toc`**, **`lastModified`**, **`github`** (`{ owner, repo, branch?, dir?, host?, api? }` — set `host` whenever the source's edit URL is on a GitHub Enterprise origin rather than `github.com`).
73
73
  - **`deployment`** is a host adapter from `blume/deploy` — `vercel()`, `netlify()`, `cloudflare()`, or `node()` — or a plain `{ site, base }` for a static build on any host (the default is a static build). Naming a host adapter switches the build to **server output** on that host (pass `output: "static"` to stay static there); `site`, `base`, and `output` are the named options, and anything else is forwarded verbatim to the underlying `@astrojs/*` adapter. There is **no** `deployment.adapter` string and **no** `deployment.output` field: a source that needs server rendering (the assistant, the MCP server, Mixedbread search, the API playground proxy) gets `import { vercel } from "blume/deploy"` and `deployment: vercel()`; a static source gets nothing — unless it served its docs under a subpath, which becomes `deployment: { base: "/docs" }` (see below for when to add `site`). Note that `cloudflare` and `vercel` are also exported from `blume/analytics` — alias one (`import { cloudflare as cloudflareDeploy } from "blume/deploy"`) when a config uses both.
74
74
  - **Keep the source's site URL as `deployment.site` unless the target host is one Blume auto-detects.** Blume fills `site` in from the platform's build environment only on **Vercel, Netlify, and Cloudflare Pages**, and uses the dev server's `localhost` URL during `blume dev`; on those three hosts leave it unset and let detection pick the deployed URL. Everywhere else — GitHub Pages, S3 or another static host, a custom CDN, Cloudflare Workers, a `node()` server — nothing detects it, and an unset `site` silently drops the sitemap, OG images, RSS feeds, the AI catalog, and absolute canonical URLs. So when the source config had a `url`/`site` field and the target isn't one of those three hosts, carry it over: `deployment: { site: "https://…" }` for a static build, or the `site` option of a host adapter (`node({ site })`). If you can't tell where the site will deploy, keep it and say so in the report.
75
75
  - **Favicon is a filename convention, not config.** Drop `icon.{svg,png,ico}` or `favicon.{svg,png,ico}` (and `apple-icon.png`) in the project root or `public/` — Blume auto-detects it. There is **no** `favicon` config field. A source favicon given as `{ light, dark }` maps to a filename pair: copy the light file to a conventional name (e.g. `public/icon.png`) and the dark file to its `-dark` sibling — same directory and extension, `-dark` before the extension (`public/icon-dark.png`). If the two files have different formats, convert one so the extensions match; only an exact sibling of the resolved icon is picked up.
@@ -162,13 +162,13 @@ content: {
162
162
 
163
163
  ### Redirects are static
164
164
 
165
- A `redirects: [{ from, to, status? }]` array **in `blume.config.ts`** maps old URLs when you restructure routes — Blume serves these itself, so any reorganization that moves a page (folder-per-tab, materialized nested groups, renamed slugs, index promotion) is fixed by adding an entry there; no host config needed. **Restructuring is the main source of these:** every page you moved in step 4 (folder-per-tab, renamed slugs, index promotion) needs an entry, or old URLs 404. `status` defaults to **301 (permanent — browsers cache it indefinitely)**; that's correct for genuine moves, but never use 301/308 for redirects you might reverse. Dynamic/wildcard patterns (`:slug*`) can't be modeled as static path-to-path; move those to host-level config (`_redirects`, `vercel.json`) and report them.
165
+ A `redirects: [{ from, to, status? }]` array **in `blume.config.ts`** maps old URLs when you restructure routes — Blume serves these itself, so any reorganization that moves a page (folder-per-tab, materialized nested groups, renamed slugs, index promotion) is fixed by adding an entry there; no host config needed. **Restructuring is the main source of these:** every page you moved in step 4 (folder-per-tab, renamed slugs, index promotion) needs an entry, or old URLs 404. `status` defaults to **301 (permanent — browsers cache it indefinitely)**; that's correct for genuine moves, but never use 301/308 for redirects you might reverse. Patterns carry over as written: `:name` for one segment, `:name*` or a trailing `*` for the rest of the path (`{ from: "/beta/:slug*", to: "/v2/:slug*" }`). A pattern can't also match a page, so the build fails (`BLUME_REDIRECT_MATCHES_PAGE`) if one covers a page you kept; narrow it. Rules a pattern can't express (regex params, header or cookie conditions) move to host-level config (`_redirects`, `vercel.json`); report them.
166
166
 
167
167
  ## Verification & reporting
168
168
 
169
169
  1. Run **`blume build`** — it validates the frontmatter schema, duplicate routes, and config, and fails on any error diagnostic by default (**don't pass `--no-strict`**: that builds anyway and silently drops invalid pages). Then run **`blume validate --strict`** — links, heading anchors, and assets live here, not in `build` (add `--external` to also check outbound HTTP links). OpenAPI operation pages are real routes to `validate`, so dead links to them are caught too. Iterate until both are clean.
170
170
  2. Run `blume dev` and review the site visually — nav structure, tabs, theme, rendered components.
171
- 3. **Write a migration summary** covering: what was migrated (config, N pages, nav, API references), what was **dropped** (navbar CTAs, footers, custom theming, dynamic redirects, unmappable icons, unsupported components), and suggested follow-ups (`blume eject` for full control, `blume add` to vendor a component for customization).
171
+ 3. **Write a migration summary** covering: what was migrated (config, N pages, nav, API references), what was **dropped** (navbar CTAs, footer copyright text, custom theming, conditional redirects, unmappable icons, unsupported components), and suggested follow-ups (`blume eject` for full control, `blume add` to vendor a component for customization).
172
172
 
173
173
  ## Full documentation
174
174
 
@@ -18,7 +18,7 @@ Read `themeConfig`, `presets`, and `plugins`:
18
18
  | `tagline` | `description` |
19
19
  | `themeConfig.navbar.title` / `.logo` | `title` / `logo` (move the image into `public/`) |
20
20
  | `themeConfig.navbar.items` (doc items) | `navigation.tabs` (for section links) |
21
- | `themeConfig.navbar.items` (external/utility links — Blog, Discord…) | **`navigation.actions`** (`[{ label, href }]`, plain header links) to keep them in the header, or **`navigation.featured`** (`{ label, href, icon? }`, pinned above the sidebar on every route) if they should survive on phones; the GitHub link → the `github` config instead |
21
+ | `themeConfig.navbar.items` (external/utility links — Blog, Discord…) | **`navigation.actions`** (`[{ label, href }]`, plain header links) to keep them in the header, or **`navigation.featured`** (`{ label, href, icon? }`, pinned above the sidebar on every route) if they should survive on phones; the GitHub link → the `github` config instead (it renders in the footer) |
22
22
  | `themeConfig.navbar.items` (a `className`-styled button — "Get started", "Sign up") | **`navigation.cta`** (`{ label, href }`, the header's one filled button; a route the docs don't serve must be an absolute URL) |
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`) |
@@ -26,7 +26,7 @@ Read `themeConfig`, `presets`, and `plugins`:
26
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
- | `themeConfig.footer` | drop → Footer override (`defineComponents` layout slot) |
29
+ | `themeConfig.footer` | `footer.links`, one row of `{ label, href }`: every column's items flatten into it in order (an item's `to`, a route, is its `href`); column `title`s, `copyright`, `logo`, and `style` drop (report), or keep the whole footer as a Footer override (`defineComponents` layout slot) |
30
30
  | `themeConfig.announcementBar` | `banner` (`{ content, dismissible, id }` — `isCloseable` → `dismissible`; colors drop) |
31
31
  | `i18n.locales` / `defaultLocale` | `i18n` — translated files live at `i18n/<locale>/docusaurus-plugin-content-docs/current/…`; move them to `<locale>/…` under `content.root` |
32
32
  | `themeConfig.algolia` | drop — Blume ships built-in search (Orama); remove the Algolia dep. To keep the index, `search: algolia({ appId, apiKey, indexName })` from `blume/search` (the DocSearch `apiKey` is the search-only key) |
@@ -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, 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.
98
+ Custom/swizzled theme components (layout slots or `blume eject`), the footer's copyright, logo, and HTML items, 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 @@ Trailing heading markers — `[#custom-id]` (pinned anchor), `[!toc]` (hide from
92
92
 
93
93
  ## i18n
94
94
 
95
- A `loader({ i18n })` setup (locale-suffixed files or locale dirs) → Blume `i18n: { defaultLocale, locales: [{ code, label }] }`. Locale **directories** match Blume's `dir` parser as-is; locale **file suffixes** (`page.cn.mdx`) need restructuring into locale folders. Report whichever transform you apply.
95
+ A `loader({ i18n })` setup (locale-suffixed files or locale dirs) → Blume `i18n: { defaultLocale, locales: [{ code, label }] }`. Locale **directories** match Blume's default `dir` parser as-is; locale **file suffixes** (`page.cn.mdx` beside an unsuffixed default-locale `page.mdx`) match the `dot` parser as-is — add `parser: "dot"`, no file moves. Under `dot` a folder's `meta.ts` serves every locale, so build it from the default locale's `meta.json` and report any translated folder titles in `meta.<lang>.json`. Report whichever transform you apply.
96
96
 
97
97
  ## Package.json & teardown
98
98