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
@@ -29,12 +29,12 @@ Resolve `$ref` includes first (Mintlify splits config across files). Map only wh
29
29
  | `background.decoration` | **drop** | no Blume equivalent |
30
30
  | `fonts.family` / `fonts.{heading,body}.family` | `theme.fonts.{display,body}` | a curated slug (kebab-case, e.g. `space-grotesk`) maps directly; any other Google family maps to the object form `{ name: "Family Name" }`; a self-hosted font (`fonts.*.src` URLs) maps to `{ name, variants: [{ src, weight }] }` after downloading the files into the project |
31
31
  | `banner` | `banner` (`{ content, dismissible, id, link: { href, text } }`) | **only** those keys — drop `banner.color`/`banner.type` |
32
- | `styling.latex: true` | **drop the field** — block math `$$…$$` renders in `.mdx` with no config | there is **no** `markdown.math` field; inline `$…$` is **not** supported — convert inline math to `$$…$$` or drop it (report) |
32
+ | `styling.latex: true` | **drop the field** — block math `$$…$$` renders in `.mdx` with no config | there is **no** `markdown.math` field; Blume writes inline math with two dollar signs too — convert each inline `$…$` to `$$…$$` inside the sentence, checking every pair is math and not currency or a shell variable |
33
33
  | `styling.codeblocks.theme` | `markdown.code.theme` (`{ light, dark }`) | |
34
34
  | `search.prompt` | **drop** | no equivalent |
35
- | `seo.metatags` | **drop** | no equivalent; use per-page `seo` frontmatter |
35
+ | `seo.metatags` | `seo.metatags`, minus the tags Blume writes itself | keep verification tokens, `theme-color`, and the like as written; Blume refuses `description`, `robots`, `og:title`/`og:description`/`og:image`/`og:url`/`og:type`/`og:site_name`, `twitter:*` card tags, and `article:*` dates, so move those to their settings (`og:image` → `seo.og` or a page's `seo.image`, `twitter:site` → `seo.x.handle`) and drop the rest. `seo.paths` (folder tags) has no equivalent: set per-page `seo` frontmatter |
36
36
  | `seo.indexing: "all"` | `search.indexing.includeHiddenPages: true` | |
37
- | `variables` (`{{name}}`) | **inline into content** | Blume has no runtime `{{var}}` substitution — replace each `{{name}}` with its value in the pages |
37
+ | `variables` (`{{name}}`) | `variables` | same syntax and names (letters, digits, `_`, `-`); pages keep their `{{name}}` references unchanged |
38
38
  | `integrations.posthog` (`{ apiKey, apiHost, sessionRecording }`) | `posthog({ key, host })` in the `analytics` list | preserve the host verbatim (e.g. `us.posthog.com` — Blume's default is `us.i.posthog.com`); `sessionRecording: false` → `disable_session_recording: true`; every adapter is imported from `blume/analytics` |
39
39
  | `integrations.ga4` (`{ measurementId }`) | `googleAnalytics({ id })` | |
40
40
  | `integrations.gtm` (`{ tagId }`) | `googleTagManager({ id })` | |
@@ -52,8 +52,15 @@ Resolve `$ref` includes first (Mintlify splits config across files). Map only wh
52
52
  | `integrations.clearbit` (`{ publicApiKey }`) | `clearbit({ key })` | |
53
53
  | `integrations.adobe` (`{ launchUrl }`) | `adobe({ url })` | |
54
54
  | any other `integrations` script | `script({ src, strategy, attributes })` / `vercel()` in the `analytics` list | one `script()` adapter per provider without a factory |
55
+ | `telemetry` (`{ enabled: false }`) | `feedback: false` | Mintlify's switch also turned off its feedback widgets; Blume sends no telemetry of its own, so only the rating needs turning off |
56
+ | Feedback toggles in the Mintlify dashboard (not in `docs.json`) | `feedback: { comments: true }` for contextual (written) feedback | ask whether the site collected written feedback; Blume sends comments through the `analytics` adapters as `feedback_comment` events. Code-snippet feedback, edit suggestions, and raise-issue have no equivalent: report them |
57
+ | `integrations.cookies` (`{ key, value }`), or a content-folder `.js` file that injects Osano or Ethyca/Fides | `consent`, imported from `blume/consent`: `osano({ customerId, configId })` (both IDs from the `cmp.osano.com/<customerId>/<configId>/osano.js` URL), `ethyca({ privacyCenter })` (the origin that serves `fides.js`), or `native()` for Blume's own banner | Mintlify's `cookies` pair only switched off its own telemetry for readers whose localStorage lacked the value; under `consent`, every `analytics` adapter waits for the reader instead. Delete the injector script. Another consent manager (Transcend, OneTrust, …) has no adapter: report it |
58
+ | A root `skill.md`, or `.mintlify/skills/<name>/SKILL.md` | move each into a `skills/<name>/` folder and set `agents.skills: "./skills"`; drop Mintlify-only frontmatter (`groups`) | Blume already generates a skill for the site at `/skill.md` (`agents.skillMd`, on when `deployment.site` is set); a root `skill.md` replaces it only if its `name` matches the site's title slug (`Acme Docs` → `acme-docs`), so set that name |
55
59
  | `contextual` (`["copy","chatgpt","claude",…]`) | **mostly free** | Copy-as-Markdown and Open-in-chat are default page actions; `mcp` needs `agents.mcp.enabled` + server output (report as a follow-up) |
56
- | `redirects` | `redirects: [{ from, to }]` | static only — see below |
60
+ | `footer` (`{ socials, links }`) | `footer: { socials, links }` | `socials` keeps its keys, except `twitter`/`x-twitter` → `x` and `earth-americas` → `website`; a key outside Blume's list drops (report); `links` is one row of `{ label, href }`: every column's `items` flatten into it in order, and each column's `header` drops (report) |
61
+ | `api.mdx` (`{ server, auth: { method, name } }`) | `api: { server, auth: { method, name } }` | the defaults for `api`-frontmatter pages |
62
+ | `api.playground` (`{ display, proxy }`) | `api: { playground }` | `display: "none"` → `playground: false`; `"simple"` has no site-wide form, so set `playground: simple` in each page's frontmatter; `proxy` (on by default in Mintlify) → `playground: { proxy: true }`, which needs a server-output adapter, or leave it off to send directly |
63
+ | `redirects` (`{ source, destination, permanent }`) | `redirects: [{ from, to, status }]` | `source` → `from`, `destination` → `to` as written, patterns included (`/beta/:slug*`, `/old/article-*`); `permanent: false` → `status: 307`, otherwise `status: 308` |
57
64
  | `navigation.languages` | `i18n` | see i18n below |
58
65
 
59
66
  A minimal result is often just `defineConfig({ title, logo, theme: { accent } })`.
@@ -92,13 +99,13 @@ The codemod touches **only frontmatter**. Icons in MDX **body** (`<Icon icon="
92
99
  | `life-ring` | `life-buoy` | | `shield-halved` | `shield` |
93
100
  | `rocket`/`book`/`book-open`/`code`/`terminal`/`key`/`lock`/`user`/`users`/`database`/`server`/`cloud`/`bell`/`calendar`/`star`/`heart`/`tag`/`folder`/`globe`/`link`/`download`/`upload`/`check`/`copy`/`play`/`filter` | _(same name — verify)_ |
94
101
 
95
- **Rules:** verify each Lucide name exists at [lucide.dev/icons](https://lucide.dev/icons) before writing it. **Brand icons** (`fa6-brands:*` — github, discord, x, slack, linkedin…) mostly have **no** Lucide equivalent: for GitHub use the `github` config (renders the header repo link); for other socials, drop the icon and report it (or add via a Footer override after `blume eject`). Where no Lucide counterpart exists, **drop the icon and report it**. The build won't catch a wrong name for you: an unknown icon renders nothing, and it's only a warning (`BLUME_UNKNOWN_ICON`) for navigation icons — an icon prop on a component like `<Card>` fails silently — so check every name.
102
+ **Rules:** verify each Lucide name exists at [lucide.dev/icons](https://lucide.dev/icons) before writing it. **Brand icons** (`fa6-brands:*` — github, discord, x, slack, linkedin…) mostly have **no** Lucide equivalent: for GitHub use the `github` config (renders the footer's repo link); for other socials, use `footer.socials`. Where no Lucide counterpart exists, **drop the icon and report it**. The build won't catch a wrong name for you: an unknown icon renders nothing, and it's only a warning (`BLUME_UNKNOWN_ICON`) for navigation icons — an icon prop on a component like `<Card>` fails silently — so check every name.
96
103
 
97
104
  ## Navigation: `docs.json` `navigation` → filesystem + tabs
98
105
 
99
106
  Mintlify's `navigation` object (`tabs`/`anchors`/`dropdowns`/`products`/`versions`/`languages`/`groups`/`pages`) is fully config-declared. **Prefer restructuring content into folders**, not porting the config verbatim:
100
107
 
101
- - **`groups`** (`{ group, pages: [...] }`) → a folder per group. The `group` name → the folder's humanized name or a `meta.ts` `title`. Nested groups → nested folders. `expanded: false` → `meta.ts` `collapsed: true` (inverted). `tag` → the folder/page `sidebar.badge`. **Folder metadata is a `meta.ts` module** (`export default defineMeta({ title, icon, order, collapsed, pages, display })`), a TypeScript file — **not** JSON. Blume does not read a `meta.json`, so any folder-level config you carry over from the source (or hand-write to preserve a group's label/order/collapse) must be authored as `meta.ts`. (Render mode — flat/group/page — defaults to the global `navigation.sidebar.display` in `blume.config.ts`; a folder overrides its own group with `meta.ts` `display`, or `sidebar.display` in its `index` page's frontmatter.)
108
+ - **`groups`** (`{ group, pages: [...] }`) → a folder per group. The `group` name → the folder's humanized name or a `meta.ts` `title`. Nested groups → nested folders. `expanded: false` → `meta.ts` `collapsed: true` (inverted). `tag` → the folder/page `sidebar.badge`. `directory` (`card`/`accordion`/`none`, on a group, tab, or the navigation itself) → `meta.ts` `directory` on that folder, or on the content root's `meta.ts` for the whole navigation; it inherits down the tree the same way, and lists the pages on the group's `root`/`index` page. **Folder metadata is a `meta.ts` module** (`export default defineMeta({ title, icon, order, collapsed, pages, display, directory })`), a TypeScript file — **not** JSON. Blume does not read a `meta.json`, so any folder-level config you carry over from the source (or hand-write to preserve a group's label/order/collapse) must be authored as `meta.ts`. (Render mode — flat/group/page — defaults to the global `navigation.sidebar.display` in `blume.config.ts`; a folder overrides its own group with `meta.ts` `display`, or `sidebar.display` in its `index` page's frontmatter.)
102
109
  - **Config-only nested groups don't exist on disk — you must materialize them, or they flatten silently.** A nested `{ group, pages }` almost never has a matching subfolder: its pages sit **flat in the parent directory** (e.g. `platform/analytics/getting-started.mdx`, `…/quick-reference.mdx`) and the grouping lives **only** in the `docs.json` `pages` array. If you leave the files where they are, filesystem-derived nav sees one flat folder and the inner group vanishes — Mintlify's `Analytics → Reference → {…}` becomes a flat `Analytics → {…}`. To preserve it you must **either** move those pages into a real subfolder (`platform/analytics/reference/`, with a `meta.ts` for the label/`collapsed`), **or** declare the shape in an explicit `navigation.sidebar`. Walk **every** `pages` array recursively and treat any nested `group` object as a folder-move to plan, not files already in place. When inventorying the nav (workflow step 2), record the config nesting depth separately from the on-disk depth — they diverge exactly here.
103
110
  - **This is the canonical case for `navigation.sidebar` over folders.** Materializing a purely-presentational nested group as a subfolder changes URLs (`/platform/analytics/getting-started` → `/platform/analytics/reference/getting-started`) for no reason other than a visual grouping, forcing a `redirects` entry per page. When you want to keep the nesting **and** the URLs, an explicit `navigation.sidebar` group (`{ label, items }`) is the better trade — it nests the existing routes without moving any file. Pick per group; don't reflexively flatten.
104
111
  - **`pages`** entries are page refs (paths without extension) → files at the corresponding path. An entry that's `"GET /path"` is an OpenAPI endpoint stub → **delete it** (Blume generates these; see OpenAPI).
@@ -110,7 +117,7 @@ Mintlify's `navigation` object (`tabs`/`anchors`/`dropdowns`/`products`/`version
110
117
 
111
118
  - **`dropdowns`/`products`/`versions`** → `navigation.selectors` (`{ kind, label, items: [{ label, path, icon?, description?, tag? }] }`). Use `kind` `dropdown`/`product`/`version` accordingly.
112
119
  - **`anchors` / `navigation.global.anchors`** (persistent sidebar-top links — Blog, Changelog, Community, Contact/Support) → **`navigation.featured`** (`{ label, href, icon? }`). These pin to the **top of the sidebar, above every section, on every route** (not tab-scoped) — the right home for Mintlify's always-visible utility links. An external `href` opens in a new tab; an internal one (`/contact`) is build-time validated against your pages. Convert the FontAwesome `icon` to Lucide as usual. (This replaces the old "drop and report" for anchors.)
113
- - **`navbar.links`** (plain header links — Log in, Status, Support) → **`navigation.actions`** (`[{ label, href }]`), rendered in the header left of the icon buttons. A GitHub link → the `github` config (or `navigation.repo` as a URL when the docs repo is private) instead. Header links hide on phones; a link that must survive there belongs in `featured`.
120
+ - **`navbar.links`** (plain header links — Log in, Status, Support) → **`navigation.actions`** (`[{ label, href }]`), rendered in the header left of the icon buttons. A GitHub link → the `github` config (or `navigation.repo` as a URL when the docs repo is private) instead; it renders in the footer, the only place Blume links a repository. Header links hide on phones; a link that must survive there belongs in `featured`.
114
121
  - **`navbar.primary`** (the header **CTA button** — "Get Started"/"Sign Up", `type: "button"`) → **`navigation.cta`** (`{ label, href }`, singular: Blume renders exactly one filled button). A `type: "github"` primary → the `github` config instead. A route the docs don't serve (the product's own `/signup`) must be an absolute URL, or it raises `BLUME_NAV_MISSING_PAGE`.
115
122
  - **`languages`** → `i18n`, not a selector (see below).
116
123
  - Only fall back to an explicit `navigation.sidebar` for a shape the filesystem genuinely can't express.
@@ -123,16 +130,18 @@ Rewrite each page's MDX:
123
130
 
124
131
  - **Callouts → directives** (directives are **MDX-only** — Mintlify content is already `.mdx`, so this just works; never rename a page to `.md`): `<Note>`→`:::note`, `<Tip>`→`:::tip`, `<Warning>`→`:::warning`, `<Info>`→`:::info`, `<Check>`→`:::success`, `<Danger>`/`<Error>`→`:::danger`. `<Callout type="x">` maps by type (`caution`→warning, `check`→success). A `title` attr → `:::type[Title]`. Drop `icon`/color props.
125
132
  - **Accordions — container/item inversion!** Mintlify nests `<Accordion title="…">` inside `<AccordionGroup>`. Blume inverts: `<AccordionGroup>`→`<Accordion>` (container), and each Mintlify `<Accordion title="…">`→`<AccordionItem title="…">` (item).
126
- - **`<RequestExample>`/`<ResponseExample>`** → `<CodeGroup>` (titled-fence tabs).
127
133
  - **`<Tabs>` → `<Tabs inline>`.** Mintlify renders tabs **borderless** — a strip on a full-width rule with the content flowing beneath as prose — while Blume's `<Tabs>` defaults to a bordered box. Add `inline` to each `<Tabs>` to preserve Mintlify's appearance; every child `<Tab title="…">` is unchanged. Don't add `param` — Mintlify tabs switch in place and don't deep-link to the URL, so plain `inline` is the faithful mapping.
128
- - **These pass through — Blume ships them natively:** `<Columns>`/`<Column>`, `<Expandable>`, `<Tooltip>`, `<Frame>`, `<Panel>`, `<Card>`/`<CardGroup>`, `<Tab>`, `<Steps>`/`<Step>`. Keep them as-is.
129
- - **API fields → `TypeTable`.** Blume does **not** ship `<ParamField>`/`<ResponseField>`/`<RequestField>`. Convert a cluster of fields into one `<TypeTable>` (rows keyed by field name, each `{ type, required?, default?, description }`). For a fully spec'd API, prefer deleting the hand-written fields and using the [OpenAPI reference](#openapi) instead.
134
+ - **These pass through — Blume ships them natively:** `<Columns>`/`<Column>`, `<Expandable>`, `<Tooltip>`, `<Frame>`, `<Panel>`, `<Card>`/`<CardGroup>`, `<Tab>`, `<Steps>`/`<Step>`, `<View>` (its `title` becomes an option in the page's view picker, as on Mintlify; remap its `icon` with the table below and drop `iconType`). Keep them as-is.
135
+ - **Multi-word code-block titles need quotes.** Mintlify reads everything after the language as the title (` ```ts Acme SDK `); Blume reads one bare word, so write ` ```ts title="Acme SDK" `. One-word titles (` ```bash cURL `) stay as they are.
136
+ - **`<RequestExample>`/`<ResponseExample>` pass through.** Top-level ones pin to a column beside the page on wide screens, request above response, as on Mintlify; `dropdown` works the same.
137
+ - **Hand-written endpoint pages pass through.** `api: "POST /v1/users"` frontmatter, with `authMethod` (`bearer`/`basic`/`key`/`none`) and `playground` (`interactive`/`simple`/`none`), works as on Mintlify: the page's `<ParamField>`s build its Try it panel and request samples. Keep them as written.
138
+ - **API fields pass through.** `<ParamField>` (`path`/`query`/`header`/`body`, `type`, `required`, `deprecated`, `default`, `placeholder`) and `<ResponseField>` (`name`, `type`, `required`, `deprecated`, `default`, `pre`, `post`) render natively, nested `<Expandable>` fields included. Keep them as written. An older `<RequestField>` isn't supported: rewrite it as a `<ParamField>`.
130
139
  - **`<Update>`** (a Mintlify changelog entry) has no component form → convert to a `type: changelog` page, or use the `githubReleases()` source.
131
- - **Snippets are inlined, not imported.** Blume has no `/snippets` import mechanism. For each `import X from "/snippets/x.mdx"` + `<X prop="v" />`, inline the snippet's body (substituting `{prop}` placeholders), then delete the import and the `/snippets` file. Named string imports (`import { foo } from "/snippets/vars.mdx"`) → inline the value at each `{foo}`.
140
+ - **Snippets become includes.** Blume has no `/snippets` import mechanism; its reuse is `<include>`. Move each snippet into the content folder as a `_`-prefixed partial (`_snippets/x.mdx`, which never renders as a page), and replace `import X from "/snippets/x.mdx"` + `<X prop="v" />` with `<include prop="v">./_snippets/x.mdx</include>` on its own line. In the partial, rewrite each `{prop}` placeholder to `{{prop}}`: include attributes are props read with the variables syntax. Named string imports (`import { foo } from "/snippets/vars.mdx"`) → a site-wide `variables` entry read as `{{foo}}`, or inline the value.
132
141
 
133
142
  ## Frontmatter
134
143
 
135
- Mintlify page frontmatter → Blume's strict schema. **`scripts/mintlify-codemod.mjs --write` does the mechanical rows automatically** (rename, drop, icon-remap) and flags the judgment rows (`openapi`/`asyncapi`/`api`) for you — see [Icons](#icons-fontawesome--lucide-required) for the invocation. The table is both the mapping reference and a description of exactly what the codemod does:
144
+ Mintlify page frontmatter → Blume's strict schema. **`scripts/mintlify-codemod.mjs --write` does the mechanical rows automatically** (rename, drop, icon-remap) and flags the judgment rows (`openapi`/`asyncapi`) for you — see [Icons](#icons-fontawesome--lucide-required) for the invocation. The table is both the mapping reference and a description of exactly what the codemod does:
136
145
 
137
146
  | Mintlify | Blume | Codemod |
138
147
  | --- | --- | --- |
@@ -143,17 +152,25 @@ Mintlify page frontmatter → Blume's strict schema. **`scripts/mintlify-codemod
143
152
  | `canonical` | `seo.canonical` | renames |
144
153
  | `og:image` | `seo.image` | renames |
145
154
  | `hidden: true` | valid top-level in Blume — **kept as-is**; add `noindex: true` yourself if the page must also leave the search index | left (do by hand) |
146
- | `openapi`/`asyncapi`/`api` | usually an endpoint stub → **delete the page** (Blume generates operation pages); else drop the key and keep it as a normal page (there's no built-in `api` page type) | **flags** for review — never auto-deletes a page |
147
- | `mode`, `public`, `rss`, `groups`, `keywords`, `hideApiMarker`, `hideFooterPagination`, `iconType` | **drop** (report) | drops |
155
+ | `openapi`/`asyncapi` | usually an endpoint stub → **delete the page** (Blume generates operation pages); else drop the key and keep it as a normal page | **flags** for review — never auto-deletes a page |
156
+ | `api`, `authMethod`, `playground` | kept as written (a hand-written endpoint page, see above) | left |
157
+ | `related` (a list) | kept as written: root-relative paths, URLs, and `{ Title: link }` entries render as Related pages cards | left; `related: true` has no equivalent (Blume has no automatic related pages), so drop it (report) |
158
+ | `mode` | `mode`, as written | `default`, `wide`, `center`, `custom`, and `frame` carry over; `assistant` has no equivalent → drop (report) |
159
+ | `hideFooterPagination: true` | `pagination: false` | renames (`false` drops, since the links show by default) |
160
+ | `keywords`, `boost` | `search.keywords`, `search.boost` | renames |
161
+ | `searchable: false` | `search.exclude: true` | renames (`searchable: true` drops, since pages are searchable by default) |
162
+ | `public`, `rss`, `groups`, `hideApiMarker`, `iconType` | **drop** (report) | drops |
148
163
 
149
164
  The codemod leaves the source key in place and reports a conflict rather than clobbering data when a rename target already exists (e.g. a page already has `sidebar.label`) or the value is too structured to move safely — resolve those by hand. Remove any duplicate H1 in the body — `title` renders the H1. (The codemod only edits frontmatter; it never touches the body.)
150
165
 
151
166
  ## OpenAPI
152
167
 
153
- Top-level `openapi`, `api.openapi`, or a per-group/per-tab `openapi` → `reference: [openapi({ sources: [{ spec, label?, route? }] })]`, with `openapi` imported from `blume/reference` (`spec` alone is the single-source shorthand; several per-tab specs become several sources, or several `openapi()` entries when they need different routes; a spec the source embedded with Scalar becomes a `scalar({ spec })` entry). A Mintlify `{ source, directory }` object: `directory` → the source's `route`. An `asyncapi` field maps the same way to `asyncapi({ … })` in the list. **Delete every per-endpoint stub page** (frontmatter `openapi: "GET /path"` or a `"GET /path"` nav entry) — Blume's native renderer generates one real page per operation. **Add a `navigation.tabs` entry pointing at the reference `route` yourself** (Mintlify's API tab maps to it); the reference does not create a header tab automatically.
168
+ Top-level `openapi`, `api.openapi`, or a per-group/per-tab `openapi` → `reference: [openapi({ sources: [{ spec, label?, route? }] })]`, with `openapi` imported from `blume/reference` (`spec` alone is the single-source shorthand; several per-tab specs become several sources, or several `openapi()` entries when they need different routes; a spec the source embedded with Scalar becomes a `scalar({ spec })` entry). A Mintlify `{ source, directory }` object: `directory` → the source's `route`. An `asyncapi` field maps the same way to `asyncapi({ … })` in the list. **Delete every per-endpoint stub page** (frontmatter `openapi: "GET /path"`, `openapi: "spec.json webhook name"`, or a `"GET /path"` nav entry) — Blume's native renderer generates one real page per operation and per webhook, and shows callbacks on their operation's page. **Add a `navigation.tabs` entry pointing at the reference `route` yourself** (Mintlify's API tab maps to it); the reference does not create a header tab automatically.
154
169
 
155
170
  - **Vendor the spec.** Mintlify usually points at a spec **URL**. Copying that straight into `spec:` makes every build fetch it at build time — a single point of failure in CI/offline/behind a proxy, and a failed fetch silently drops the reference (leaving the tab pointing at a route that 404s). Prefer downloading it into the repo (`curl … -o openapi/<name>.json`) and pointing `spec` at that local path. If you keep the URL, report the dependency and consider a `prebuild` refresh-with-fallback.
156
171
  - **Fix endpoint links.** Blume operation routes are `<route>/<slugified-tag>/<slugified-operationId>` (tag `Models` + id `listModels` → `/api-reference/models/list-models`: a camelCase id is split into kebab-case, not just lowercased — see SKILL.md "OpenAPI") — this differs from Mintlify's endpoint URLs, so **rewrite every inbound link to an operation**. `blume validate` resolves operation pages like any other route and flags the ones you miss.
172
+ - **Overlays carry over.** An `openapi: { source, overlays: [...] }` object → that source's `overlays` (`openapi({ spec, overlays })`, or `overlays` on each entry in `sources`), in the same order; Blume applies Overlay 1.0 and 1.1 the same way. An overlay Mintlify found only through its `extends` field (auto-discovered, not listed in `docs.json`) must be listed explicitly: Blume doesn't scan for overlays.
173
+ - **Code samples carry over.** `api.examples.languages` → `openapi({ codeSamples: [...] })`; Blume generates the same 18 languages and accepts Mintlify's keys and aliases (`bash`, `javascript`, `node`, `csharp`, `dotnet`, `c++`, …) as written. `api.examples.autogenerate: false` → `codeSamples: false`. `x-codeSamples` in the spec needs nothing: Blume renders them ahead of the generated samples.
157
174
  - **Keep the "Introduction" page.** Mintlify commonly has a written intro/auth page in an "Introduction" group beside the "Endpoints" (openapi) group in the same tab. Keep it: a normal content page placed under the `openapi()` adapter's `route` (e.g. `<root>/api-reference/introduction.mdx`) merges into the reference tab's sidebar alongside the generated operations. Delete only the per-endpoint stubs, not the conceptual pages.
158
175
 
159
176
  ## Assets
@@ -164,9 +181,10 @@ Mintlify serves every top-level dir (e.g. `/images`) at the site root. Blume ser
164
181
 
165
182
  `navigation.languages` (≥2) → `i18n: { defaultLocale, locales: [{ code, label }] }`. The `default: true` language → `defaultLocale`. Translated content already lives in ISO-code directories, which match Blume's `dir` parser — no file moves. Remove any language selector; language switching is handled by i18n.
166
183
 
184
+ Per-language `banner`, `navbar`, and `footer` (on `navigation.languages[]`) merge into the one site-wide config, with each label as a map of language code to that language's text: `banner: { content: { en: "…", fr: "…" } }` (and `link.text`), `navigation.actions`/`cta` labels, and `footer` link labels. Hrefs stay single — Blume moves an internal link into the reader's language itself. A language without an entry shows the default language's text. Report what doesn't merge: a banner or link set for only some languages, or one whose href differs per language.
185
+
167
186
  ## Dropped — report these
168
187
 
169
- - **`footer.socials`** → suggest the `github` config, or a Footer override.
170
- - **Per-language banners** (`navigation.languages[].banner`) → no equivalent.
171
- - **Dynamic redirects** (`:slug*`/`:id` params) → can't be static path-to-path; move to host rules (`_redirects`, `vercel.json`).
172
- - **`<Update>`** changelog components, `iconType`, `background.decoration`, `search.prompt`, `seo.metatags`.
188
+ - **A group's `boost`** (on a `navigation` group, inherited by its pages) → no inherited boost; set `search.boost` on the pages that need it.
189
+ - **Authentication** (password or SSO, set in the Mintlify dashboard) → Blume has none of its own; point the user at host-level protection (Vercel Deployment Protection, Netlify password protection, Cloudflare Access), per the deployment docs' **Private docs** section. It covers the whole site, so a mix of public and private pages needs two sites.
190
+ - **`<Update>`** changelog components, `iconType`, `background.decoration`, `search.prompt`.
@@ -15,9 +15,9 @@ Nextra (on Next.js) declares navigation and per-page labels in `_meta` files —
15
15
 
16
16
  None of it maps automatically — read the config surface for the generation by hand and reconstruct in `blume.config.ts`:
17
17
 
18
- - **v2/v3 — `theme.config.*`:** `logo` (JSX — extract text/image) → `logo`; `project.link` → `github` (renders the header repo link); `docsRepositoryBase` → `github` (owner/repo/branch — and any trailing sub-path → `github.dir`; if its origin is not `https://github.com`, set `github.host` to that origin — with `host` omitted, Blume builds the repository and edit links against public GitHub); `banner.text`/`banner.key` → `banner.content`/`banner.id` (`dismissible` maps); `primaryHue`/`primarySaturation` → pick an equivalent `theme.accent` color; `footer` → drop (report); `faviconGlyph` → drop (Blume's favicon is a file convention); `useNextSeoProps`/head → per-page `seo` frontmatter or drop.
18
+ - **v2/v3 — `theme.config.*`:** `logo` (JSX — extract text/image) → `logo`; `project.link` → `github` (renders the footer's repo link); `docsRepositoryBase` → `github` (owner/repo/branch — and any trailing sub-path → `github.dir`; if its origin is not `https://github.com`, set `github.host` to that origin — with `host` omitted, Blume builds the repository and edit links against public GitHub); `banner.text`/`banner.key` → `banner.content`/`banner.id` (`dismissible` maps); `primaryHue`/`primarySaturation` → pick an equivalent `theme.accent` color; `footer` → drop (report); `faviconGlyph` → drop (Blume's favicon is a file convention); `useNextSeoProps`/head → per-page `seo` frontmatter or drop.
19
19
  - **v4 — `app/layout.*` props:** the same facts moved: `<Navbar logo projectLink>`, `<Layout docsRepositoryBase editLink sidebar={{…}} toc={{…}}>`, `<Banner>`, `<Footer>`. Map them the same way; sidebar/TOC tuning mostly drops (Blume's `toc` config covers min/max heading levels).
20
- - **`next.config.*`:** harvest **`redirects()`** — static entries become Blume `redirects: [{ from, to }]`; wildcard/dynamic ones move to host config (report). A `latex: true` flag means math is in play (see Math below). Then delete the file.
20
+ - **`next.config.*`:** harvest **`redirects()`** — static entries and `:param`/`:path*` patterns become Blume `redirects: [{ from, to }]` as written (`permanent: true` → `status: 308`, `false` → `307`); ones with regex params (`:slug(\d+)`) or `has`/`missing` conditions move to host config (report). A `latex: true` flag means math is in play (see Math below). Then delete the file.
21
21
  - Root `_meta` entries with **`type: "page"`** → `navigation.tabs` (`{ label, path }`, where `path` is `/` for `index`, else `/<slug>`). `type: "page"` only maps at the **root**.
22
22
 
23
23
  ## Navigation: `_meta` → `meta.ts` + frontmatter
@@ -64,7 +64,7 @@ Nextra enables math via `nextra({ latex: true })` (KaTeX or MathJax). In Blume,
64
64
 
65
65
  ## i18n
66
66
 
67
- - **v2/v3:** locale **file suffixes** (`index.en.mdx`, `index.zh.mdx`) + `i18n` in `next.config` → restructure into locale **folders** (default locale at the content root, others under `<code>/`), then `i18n: { defaultLocale, locales: [{ code, label }] }`.
67
+ - **v2/v3:** locale **file suffixes** (`index.en.mdx`, `index.zh.mdx`) + `i18n` in `next.config` → match Blume's `dot` parser as-is, default-locale suffix included: `i18n: { defaultLocale, locales: [{ code, label }], parser: "dot" }`, no file moves. Page titles land in each file's own frontmatter, so they stay per-locale, but under `dot` a folder's `meta.ts` serves every locale — build it from the default locale's `_meta` and report translated folder titles from the other locales' `_meta` files.
68
68
  - **v4:** `content/<lang>/` dirs already match Blume's `dir` parser — map the locale list, no file moves.
69
69
 
70
70
  ## Package.json & teardown
@@ -57,7 +57,8 @@ Starlight's `sidebar` array → prefer letting Blume generate from the filesyste
57
57
  | `pagefind: false` | `search.exclude: true` |
58
58
  | `lastUpdated` (date/string) | `lastModified` |
59
59
  | `lastUpdated: false` (boolean) | **remove** (Blume's `lastModified` takes a date; a boolean is a build error) |
60
- | `prev` / `next` (booleans or labels) | **drop** (report — no per-page pagination toggle; `hideFooterPagination` does not exist) |
60
+ | `prev: false` **and** `next: false` | `pagination: false` (hides both links) |
61
+ | `prev` / `next` otherwise (one side, or a label/link) | **drop** (report — the links can only be hidden together, and their labels come from the linked pages) |
61
62
  | `template: splash` / `hero` | **no equivalent** — rebuild as a custom `.astro` page under `content.pages`; a `hero` on a normal page drops (report) |
62
63
  | `banner`, `tableOfContents`, `editUrl`, `head` | drop (report) |
63
64
 
@@ -156,22 +156,31 @@ const ICONS = {
156
156
 
157
157
  // --- Frontmatter field policy ----------------------------------------------
158
158
  // Top-level keys Blume's strict schema rejects: delete the whole block, report.
159
- const DROP = new Set([
160
- "groups",
161
- "hideApiMarker",
162
- "hideFooterPagination",
163
- "iconType",
164
- "keywords",
165
- "mode",
166
- "public",
167
- "rss",
168
- ]);
159
+ const DROP = new Set(["groups", "hideApiMarker", "iconType", "public", "rss"]);
169
160
 
170
- // Mintlify-only keys → Blume nested target. `[parent, child]`.
161
+ // Keys whose fate depends on their value: the source value (and its line) →
162
+ // the line that replaces it, the line itself to keep it, or `null` to drop it
163
+ // (a value Blume has no counterpart for, or one that restates its default).
164
+ const REPLACE = {
165
+ hideFooterPagination: (value) =>
166
+ value === "true" ? "pagination: false" : null,
167
+ mode: (value, line) => (value === "assistant" ? null : line),
168
+ };
169
+
170
+ // Mintlify-only keys → Blume nested target. `[parent, child]`, plus a value
171
+ // mapper where the meaning flips: it returns the new value, or `null` to drop a
172
+ // value that restates Blume's default.
171
173
  const RENAME = {
174
+ boost: ["search", "boost"],
172
175
  canonical: ["seo", "canonical"],
176
+ keywords: ["search", "keywords"],
173
177
  "og:image": ["seo", "image"],
174
178
  ogImage: ["seo", "image"],
179
+ searchable: [
180
+ "search",
181
+ "exclude",
182
+ (value) => (value === "false" ? "true" : null),
183
+ ],
175
184
  sidebarTitle: ["sidebar", "label"],
176
185
  tag: ["sidebar", "badge"],
177
186
  };
@@ -179,7 +188,8 @@ const RENAME = {
179
188
  // Keys we deliberately do NOT auto-transform — they usually mean the page is an
180
189
  // OpenAPI endpoint stub that should be deleted (Blume generates operation pages),
181
190
  // or else a normal page that just loses the key. Flag for the human; never guess.
182
- const FLAG = new Set(["api", "asyncapi", "openapi"]);
191
+ // (`api` is not one: a hand-written endpoint page keeps it as written.)
192
+ const FLAG = new Set(["asyncapi", "openapi"]);
183
193
 
184
194
  // Which change kinds actually edit the file. Report-only kinds (flags,
185
195
  // unknowns, conflicts, manual-rename notices) leave the bytes untouched.
@@ -219,10 +229,15 @@ const topKey = (line) => {
219
229
  return { key: m.groups.key, value: (m.groups.rest ?? "").trim() };
220
230
  };
221
231
 
222
- // The line index range [start, endExclusive) of a top-level key's block.
232
+ // The line index range [start, endExclusive) of a top-level key's block: its
233
+ // indented lines plus any `- ` sequence items written flush at column 0,
234
+ // which YAML also reads as the key's value (`keywords:` then `- one`).
223
235
  const blockRange = (fm, start) => {
224
236
  let end = start + 1;
225
- while (end < fm.length && (fm[end] === "" || /^\s/u.test(fm[end]))) {
237
+ while (
238
+ end < fm.length &&
239
+ (fm[end] === "" || /^\s/u.test(fm[end]) || /^-(?:\s|$)/u.test(fm[end]))
240
+ ) {
226
241
  end += 1;
227
242
  }
228
243
  // Trim trailing blank lines back out so the gap before the next key survives.
@@ -329,11 +344,41 @@ const rewriteFields = (fm) => {
329
344
  changes.push({ detail: tk.key, kind: "flag" });
330
345
  continue;
331
346
  }
347
+ if (Object.hasOwn(REPLACE, tk.key)) {
348
+ const [start, end] = blockRange(fm, i);
349
+ const value = tk.value.replace(
350
+ /^(?<q>["'])(?<inner>.*)\k<q>$/u,
351
+ "$<inner>"
352
+ );
353
+ const line =
354
+ end - start === 1 ? REPLACE[tk.key](value, fm[start]) : undefined;
355
+ if (line === undefined) {
356
+ changes.push({ detail: tk.key, kind: "rename-manual" });
357
+ } else if (line === null) {
358
+ fm.splice(start, 1);
359
+ changes.push({ detail: `${tk.key}: ${value}`, kind: "drop" });
360
+ } else if (line !== fm[start]) {
361
+ const [key] = line.split(":");
362
+ if (fm.some((other, j) => j !== start && topKey(other)?.key === key)) {
363
+ changes.push({
364
+ detail: `${tk.key} → ${key} (child-exists)`,
365
+ kind: "rename-conflict",
366
+ });
367
+ } else {
368
+ fm[start] = line;
369
+ changes.push({
370
+ detail: `${tk.key}: ${value} → ${line}`,
371
+ kind: "rename",
372
+ });
373
+ }
374
+ }
375
+ continue;
376
+ }
332
377
  const target = RENAME[tk.key];
333
378
  if (!target) {
334
379
  continue;
335
380
  }
336
- const [parent, child] = target;
381
+ const [parent, child, map] = target;
337
382
  const [start, end] = blockRange(fm, i);
338
383
  if (end - start !== 1 || tk.value === "") {
339
384
  // Multi-line or valueless source — too structured to move safely.
@@ -343,12 +388,18 @@ const rewriteFields = (fm) => {
343
388
  });
344
389
  continue;
345
390
  }
391
+ const value = map ? map(tk.value) : tk.value;
392
+ if (value === null) {
393
+ fm.splice(start, 1);
394
+ changes.push({ detail: `${tk.key}: ${tk.value}`, kind: "drop" });
395
+ continue;
396
+ }
346
397
  // Remove the source line before inserting: setNested splices into the
347
398
  // parent block, and when that block sits above the source key the insert
348
399
  // would otherwise shift `start` onto the wrong line. Failure paths don't
349
400
  // mutate `fm`, so the line can be restored as-is on conflict.
350
401
  const [removed] = fm.splice(start, 1);
351
- const placed = setNested(fm, parent, child, tk.value);
402
+ const placed = setNested(fm, parent, child, value);
352
403
  if (placed.ok) {
353
404
  if (placed.index < start) {
354
405
  // The insert above the cursor pushed the unvisited lines down one
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: blume-write-skill
3
+ description: Write the agent skill for a product's Blume docs site — one SKILL.md, grounded in the docs, that teaches a coding agent to use the product (setup, core concepts, common tasks, gotchas) and links each topic's page. Blume publishes it at the site's /skill.md in place of the page map it generates. Use when asked to write, improve, or refresh a docs site's agent skill, or when `blume skill` hands you this task.
4
+ ---
5
+
6
+ # Write a docs site's agent skill
7
+
8
+ Every Blume site already publishes a generated skill at `/skill.md`: a map of its pages with their one-line descriptions, built without a model. It tells an agent where things are, not how the product works. Your job is to write the skill that does: what the product is, how to set it up, the concepts and tasks that matter, and where it bites, with every topic linked to the page that covers it.
9
+
10
+ The reader is a coding agent working in someone else's project. It loaded your skill because a task touches this product, and it needs to act correctly without reading the whole site first.
11
+
12
+ ## Ground rules
13
+
14
+ - **Only what the docs say.** Every claim, command, config key, flag, environment variable, and code sample must come from a page in the content root, copied rather than paraphrased. Never fill a gap from memory or from what similar products do. Where the docs don't answer something the skill needs, leave it out and list it in your report as a docs gap.
15
+ - **One file.** Keep it a lone `SKILL.md`: no `references/`, `scripts/`, or `assets/`. A single file is what the site serves at `/skill.md` and what an agent can fetch in one request. Depth lives in the docs, so link to them instead of copying them in.
16
+ - **Absolute links to Markdown.** Link pages as `<site><route>.md` (the home page is `<site>/index.md`), using the site URL your prompt gives. Link only pages that exist: a page's route is its path under the content root without the extension, and a folder's `index` page takes the folder's route.
17
+ - **Keep the given name.** The frontmatter `name` must be exactly the one in your prompt. That match is what makes your skill replace the generated one.
18
+ - **Short beats complete.** Aim for well under 300 lines. Skip anything a capable agent already knows (general programming, Markdown, how HTTP works) and anything it can look up from a link when it needs it.
19
+
20
+ ## Workflow
21
+
22
+ 1. **Read the docs.** Start with `blume.config.ts` (the site's title, description, `content.root`, navigation, API references) and the `meta.ts` files, which give the sidebar order. Then read the home page, the quickstart or installation page, each core-concept page, the configuration reference, the API reference overview, and any troubleshooting or FAQ pages. If the site has been built, its `llms.txt` lists every page with its description.
23
+
24
+ 2. **Decide what matters.** Before writing, note:
25
+ - the product in two or three sentences: what it is, who uses it, and how it's installed or reached
26
+ - the five to ten tasks people most often come to the docs to do
27
+ - the concepts an agent must understand to do those tasks right
28
+ - the sharp edges: limits, required setup, defaults that surprise people, common errors, and anything deprecated along with what replaced it
29
+
30
+ 3. **Write the skill.** Use this shape, dropping any section the docs give you nothing for:
31
+
32
+ ```markdown
33
+ ---
34
+ name: <the name from your prompt>
35
+ description: <What the product is and when to use this skill, in the words a user would type: the product name, its package and CLI names, its key concepts. At most 1024 characters.>
36
+ ---
37
+
38
+ # <Product>
39
+
40
+ <Two or three sentences: what it is, who it's for, how to install or reach it.>
41
+
42
+ ## Setup
43
+
44
+ <Install, configure, and authenticate, verbatim from the docs, with a link to the page.>
45
+
46
+ ## Core concepts
47
+
48
+ - **<Concept>**: <one line>. [<Page title>](<site><route>.md)
49
+
50
+ ## Common tasks
51
+
52
+ ### <Task>
53
+
54
+ 1. <Step, with the exact command or code from the docs.>
55
+
56
+ See [<Page title>](<site><route>.md).
57
+
58
+ ## Gotchas
59
+
60
+ - <The limit, default, or error, and what to do about it.>
61
+
62
+ ## Where to look
63
+
64
+ - [<Section>](<site><route>.md): <when to read it>
65
+ - [llms.txt](<site>/llms.txt): every page with a one-line summary.
66
+ ```
67
+
68
+ The `description` decides whether an agent loads the skill at all, so spend time on it. Name the product the way users do, and cover the situations it's for ("setting up X", "calling the X API", "configuring X"). Write the body in the imperative, for an agent that will act on it.
69
+
70
+ If the site serves an MCP server or other agent surfaces, the generated skill names them. Carry those lines over under "Where to look".
71
+
72
+ 4. **Check it.**
73
+ - Every link points at a page that exists in the content root, as `<site><route>.md`.
74
+ - Every command, code sample, config key, and default appears in the docs as written.
75
+ - The frontmatter parses, `name` is exactly as given, and `description` is 1024 characters or fewer.
76
+ - Then run the docs build (`blume build`). The build output's `skill.md` should now be yours, and `.well-known/agent-skills/index.json` should list the name once.
77
+
78
+ 5. **Report.** Say where you wrote the skill and any config you changed, summarize what it covers, and list the docs gaps you found: questions the skill needed answered that no page does. Those are worth fixing in the docs.
79
+
80
+ ## Keeping it current
81
+
82
+ A hand-written skill doesn't update itself the way the generated one does. After a meaningful docs change (new features, renamed APIs, a changed setup flow), run `blume skill` again to refresh it. Update the existing file rather than starting over, and keep what's still true.
@@ -1,4 +1,4 @@
1
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
1
+ import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
2
2
  import { repoUrl } from "../core/github.ts";
3
3
  import type { BlumeProject } from "../core/project-graph.ts";
4
4
  import type { ContentSignalPolicy, ContentSignals } from "../core/schema.ts";
@@ -8,6 +8,7 @@ import { buildRssFeeds } from "../deploy/rss.ts";
8
8
  import { AI_CATALOG_PATH, hasAiCatalog } from "./ai-catalog.ts";
9
9
  import { hasApiCatalog } from "./api-catalog.ts";
10
10
  import { API_PAGES_PATH, API_SEARCH_PATH, OPENAPI_PATH } from "./api/paths.ts";
11
+ import { servesSkillsIndex } from "./skills.ts";
11
12
 
12
13
  /** Token map for the machine-readable content-usage echo. */
13
14
  const USAGE_TOKENS: [keyof ContentSignalPolicy, string][] = [
@@ -145,7 +146,7 @@ const wellKnownArtifacts = (
145
146
  if (hasApiCatalog(config)) {
146
147
  artifacts.apiCatalog = abs("/.well-known/api-catalog");
147
148
  }
148
- if (config.agents.skills) {
149
+ if (servesSkillsIndex(config)) {
149
150
  artifacts.agentSkills = abs("/.well-known/agent-skills/index.json");
150
151
  }
151
152
  return artifacts;
@@ -173,7 +174,7 @@ export const buildAgentReadability = (
173
174
  // `site`; concatenate rather than `new URL()` so the subpath is preserved.
174
175
  const deployBase = normalizeBasePath(config.deployment.options.base);
175
176
  const abs = (path: string): string => {
176
- const based = withBasePath(deployBase, path);
177
+ const based = mountBasePath(deployBase, path);
177
178
  return site ? absoluteUrl(site, based) : based;
178
179
  };
179
180
 
@@ -11,7 +11,8 @@ export interface EmittedAgentSurface {
11
11
  mcp: boolean;
12
12
  /**
13
13
  * A skills discovery index is served: generated from `agents.skills` (at
14
- * least one valid skill), or shipped by the user in `public/`.
14
+ * least one valid skill) or the site skill (`agents.skillMd`), or shipped
15
+ * by the user in `public/`.
15
16
  */
16
17
  skills: boolean;
17
18
  }
@@ -33,12 +34,12 @@ export const servesMcp = (
33
34
  );
34
35
 
35
36
  /**
36
- * The config the discovery documents are built from: `agents.mcp` and
37
- * `agents.skills` switched off when the build didn't emit them, so llms.txt,
38
- * agent-readability.json, the catalogs, and the header rules only point at
39
- * what is there. A config flag says what was asked for; the MCP server is
40
- * skipped when a page owns its route, and skills publish nothing when their
41
- * directory is missing or holds no valid skill.
37
+ * The config the discovery documents are built from: `agents.mcp`,
38
+ * `agents.skills`, and `agents.skillMd` switched off when the build didn't
39
+ * emit them, so llms.txt, agent-readability.json, the catalogs, and the
40
+ * header rules only point at what is there. A config flag says what was asked
41
+ * for; the MCP server is skipped when a page owns its route, and skills
42
+ * publish nothing when their directory is missing or holds no valid skill.
42
43
  */
43
44
  export const advertisedConfig = (
44
45
  config: ResolvedConfig,
@@ -51,6 +52,7 @@ export const advertisedConfig = (
51
52
  ...config.agents.mcp,
52
53
  enabled: config.agents.mcp.enabled && emitted.mcp,
53
54
  },
55
+ skillMd: config.agents.skillMd && emitted.skills,
54
56
  skills: emitted.skills ? config.agents.skills : undefined,
55
57
  },
56
58
  });
@@ -1,12 +1,16 @@
1
1
  import { apiNamePhrase } from "../core/api-name.ts";
2
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
2
+ import {
3
+ mountBasePath,
4
+ normalizeBasePath,
5
+ withBasePath,
6
+ } from "../core/base-path.ts";
3
7
  import type { ResolvedConfig } from "../core/schema.ts";
4
8
  import { absoluteUrl } from "../core/site-url.ts";
5
9
  import { resolveReferences } from "../openapi/references.ts";
6
10
  import { API_CATALOG_PATH, hasApiCatalog } from "./api-catalog.ts";
7
11
  import { OPENAPI_PATH } from "./api/paths.ts";
8
12
  import { asciiSlugify } from "./mcp/discovery.ts";
9
- import { AGENT_SKILLS_DIR } from "./skills.ts";
13
+ import { AGENT_SKILLS_DIR, servesSkillsIndex } from "./skills.ts";
10
14
  import type { SkillArtifact } from "./skills.ts";
11
15
 
12
16
  /**
@@ -82,7 +86,7 @@ export const hasAiCatalog = (config: ResolvedConfig): boolean =>
82
86
  (config.agents.mcp.enabled ||
83
87
  config.agents.api ||
84
88
  config.agents.llmsTxt.enabled ||
85
- Boolean(config.agents.skills) ||
89
+ servesSkillsIndex(config) ||
86
90
  resolveReferences(config).length > 0);
87
91
 
88
92
  /**
@@ -114,7 +118,10 @@ const NAMES_DOCS = /\b(?:docs|documentation)$/iu;
114
118
  * reads "the Acme documentation" and "Acme Docs" reads "the Acme Docs", not
115
119
  * "the Acme Docs docs".
116
120
  */
117
- const docsPhrase = (title: string, noun: "docs" | "documentation"): string =>
121
+ export const docsPhrase = (
122
+ title: string,
123
+ noun: "docs" | "documentation"
124
+ ): string =>
118
125
  NAMES_DOCS.test(title.trim()) ? `the ${title}` : `the ${title} ${noun}`;
119
126
 
120
127
  const entrySeeds = (
@@ -227,7 +234,7 @@ export const buildAiCatalog = (
227
234
  }
228
235
  const deployBase = normalizeBasePath(config.deployment.options.base);
229
236
  const abs = (path: string): string =>
230
- absoluteUrl(site, withBasePath(deployBase, path));
237
+ absoluteUrl(site, mountBasePath(deployBase, path));
231
238
  const host = publisherHost(site);
232
239
  const entries = entrySeeds(config, skills, abs).map((seed): CatalogEntry => {
233
240
  const entry: CatalogEntry = {
@@ -1,4 +1,4 @@
1
- import { withBasePath } from "../../core/base-path.ts";
1
+ import { mountBasePath } from "../../core/base-path.ts";
2
2
  import { absoluteUrl } from "../../core/site-url.ts";
3
3
  import type { Navigation } from "../../core/types.ts";
4
4
  import type { McpData, McpRoute } from "../mcp/data.ts";
@@ -88,7 +88,7 @@ export const jsonResponse = (payload: ApiPayload, status = 200): Response =>
88
88
 
89
89
  /** The absolute (or root-relative) URL for a base-less path. */
90
90
  const siteUrl = (path: string, context: ApiSiteContext): string => {
91
- const based = withBasePath(context.base, path);
91
+ const based = mountBasePath(context.base, path);
92
92
  return context.site ? absoluteUrl(context.site, based) : based;
93
93
  };
94
94
 
@@ -1,5 +1,5 @@
1
1
  import { apiNamePhrase } from "../../core/api-name.ts";
2
- import { withBasePath } from "../../core/base-path.ts";
2
+ import { mountBasePath } from "../../core/base-path.ts";
3
3
  import { absoluteUrl, siteRoot } from "../../core/site-url.ts";
4
4
  import {
5
5
  API_NAVIGATION_PATH,
@@ -688,7 +688,7 @@ export const buildApiSpec = (input: ApiSpecInput): ApiSpecDocument => {
688
688
  if (input.site) {
689
689
  document.externalDocs = {
690
690
  description: `${input.name} documentation`,
691
- url: absoluteUrl(input.site, withBasePath(input.base, "/")),
691
+ url: absoluteUrl(input.site, mountBasePath(input.base, "/")),
692
692
  };
693
693
  }
694
694
  return document;
@@ -1,4 +1,8 @@
1
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
1
+ import {
2
+ mountBasePath,
3
+ normalizeBasePath,
4
+ withBasePath,
5
+ } from "../core/base-path.ts";
2
6
  import type { ResolvedConfig } from "../core/schema.ts";
3
7
  import { absoluteUrl } from "../core/site-url.ts";
4
8
  import { resolveReferences } from "../openapi/references.ts";
@@ -39,7 +43,7 @@ const linksetEntries = (config: ResolvedConfig): LinksetEntry[] => {
39
43
  const site = config.deployment.options.site ?? null;
40
44
  const deployBase = normalizeBasePath(config.deployment.options.base);
41
45
  const abs = (path: string): string => {
42
- const based = withBasePath(deployBase, path);
46
+ const based = mountBasePath(deployBase, path);
43
47
  return site ? absoluteUrl(site, based) : based;
44
48
  };
45
49