blume 2.1.3 → 2.2.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 (377) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +126 -0
  3. package/README.md +4 -4
  4. package/dist/cli/{chunk-74wfhv79.js → chunk-05f21r8h.js} +1 -1
  5. package/dist/cli/{chunk-9qzgg6mj.js → chunk-1cm0h819.js} +3 -3
  6. package/dist/cli/{chunk-nbyvgnnm.js → chunk-1rkxzg3n.js} +104 -47
  7. package/dist/cli/chunk-1rkxzg3n.js.map +36 -0
  8. package/dist/cli/{chunk-desvm72z.js → chunk-2nr29y95.js} +22 -26
  9. package/dist/cli/chunk-2nr29y95.js.map +13 -0
  10. package/dist/cli/{chunk-32khwcb6.js → chunk-49r370mb.js} +42 -69
  11. package/dist/cli/{chunk-32khwcb6.js.map → chunk-49r370mb.js.map} +9 -10
  12. package/dist/cli/{chunk-ms9z66nz.js → chunk-4rftmttv.js} +2 -2
  13. package/dist/cli/{chunk-s0bdnc7v.js → chunk-79bzzkx4.js} +5 -4
  14. package/dist/cli/chunk-79bzzkx4.js.map +10 -0
  15. package/dist/cli/{chunk-hvcxng5f.js → chunk-7tekknev.js} +6 -6
  16. package/dist/cli/{chunk-dv2z0rzf.js → chunk-8b489v1b.js} +38 -13
  17. package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-8b489v1b.js.map} +5 -4
  18. package/dist/cli/{chunk-0pmb4tr4.js → chunk-8f8xw4x2.js} +1195 -222
  19. package/dist/cli/chunk-8f8xw4x2.js.map +44 -0
  20. package/dist/cli/chunk-96snx8yy.js +329 -0
  21. package/dist/cli/chunk-96snx8yy.js.map +13 -0
  22. package/dist/cli/{chunk-ckrcm08h.js → chunk-a3ydrqny.js} +48 -62
  23. package/dist/cli/{chunk-ckrcm08h.js.map → chunk-a3ydrqny.js.map} +4 -5
  24. package/dist/cli/{chunk-bxwhtvw8.js → chunk-ahc5gc9h.js} +5 -5
  25. package/dist/cli/{chunk-dpy8qdjf.js → chunk-as09pafp.js} +73 -182
  26. package/dist/cli/chunk-as09pafp.js.map +18 -0
  27. package/dist/cli/{chunk-tfycddbt.js → chunk-bjk964fp.js} +2 -2
  28. package/dist/cli/{chunk-gfc5fqvf.js → chunk-br0n8n3e.js} +18 -18
  29. package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-br0n8n3e.js.map} +1 -1
  30. package/dist/cli/{chunk-297ajdcv.js → chunk-c69tw29j.js} +6 -6
  31. package/dist/cli/{chunk-297ajdcv.js.map → chunk-c69tw29j.js.map} +3 -3
  32. package/dist/cli/chunk-cn3dy3hz.js +146 -0
  33. package/dist/cli/chunk-cn3dy3hz.js.map +11 -0
  34. package/dist/cli/{chunk-124fvmzq.js → chunk-dm4mg9bj.js} +11064 -8742
  35. package/dist/cli/{chunk-124fvmzq.js.map → chunk-dm4mg9bj.js.map} +78 -64
  36. package/dist/cli/{chunk-dqhg54mr.js → chunk-dmx6cezx.js} +1 -1
  37. package/dist/cli/{chunk-60hz265r.js → chunk-dpcahcrb.js} +24 -11
  38. package/dist/cli/chunk-dpcahcrb.js.map +10 -0
  39. package/dist/cli/{chunk-a57yh0h9.js → chunk-dyk3r7jw.js} +7 -7
  40. package/dist/cli/{chunk-a57yh0h9.js.map → chunk-dyk3r7jw.js.map} +1 -1
  41. package/dist/cli/{chunk-twy7psdz.js → chunk-enk76qd5.js} +14 -10
  42. package/dist/cli/chunk-enk76qd5.js.map +10 -0
  43. package/dist/cli/{chunk-k3gr1nm9.js → chunk-fqy4n3q2.js} +13 -16
  44. package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-fqy4n3q2.js.map} +3 -3
  45. package/dist/cli/{chunk-sc93apfs.js → chunk-frk2wdq1.js} +17 -17
  46. package/dist/cli/{chunk-sc93apfs.js.map → chunk-frk2wdq1.js.map} +2 -2
  47. package/dist/cli/{chunk-kdpzw38a.js → chunk-gprwej7h.js} +95 -7
  48. package/dist/cli/chunk-gprwej7h.js.map +11 -0
  49. package/dist/cli/chunk-kkrteb15.js +157 -0
  50. package/dist/cli/chunk-kkrteb15.js.map +10 -0
  51. package/dist/cli/{chunk-ave1wcbr.js → chunk-m9bp2hc1.js} +1 -1
  52. package/dist/cli/{chunk-58bt3hag.js → chunk-mrvh4c88.js} +4 -4
  53. package/dist/cli/{chunk-5cmts6dv.js → chunk-p8zg3a2h.js} +7 -7
  54. package/dist/cli/{chunk-5cmts6dv.js.map → chunk-p8zg3a2h.js.map} +1 -1
  55. package/dist/cli/{chunk-0w6hs479.js → chunk-rkqae7kh.js} +30 -18
  56. package/dist/cli/chunk-rkqae7kh.js.map +10 -0
  57. package/dist/cli/{chunk-bqxaqn5p.js → chunk-t5xx74rd.js} +8 -8
  58. package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-t5xx74rd.js.map} +1 -1
  59. package/dist/cli/{chunk-1f882900.js → chunk-th0pgaj5.js} +2 -2
  60. package/dist/cli/{chunk-dhwtqnnd.js → chunk-vnbvhgmd.js} +2 -2
  61. package/dist/cli/{chunk-gxtg6ed2.js → chunk-vzy9rh6y.js} +21 -21
  62. package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-vzy9rh6y.js.map} +3 -3
  63. package/dist/cli/{chunk-j1x92mx9.js → chunk-w6e455pz.js} +1 -1
  64. package/dist/cli/chunk-wehv26ry.js +155 -0
  65. package/dist/cli/chunk-wehv26ry.js.map +11 -0
  66. package/dist/cli/{chunk-y4msrwm9.js → chunk-xgq4eytj.js} +15 -15
  67. package/dist/cli/{chunk-36bxs9c7.js → chunk-xt0qvz1t.js} +27 -23
  68. package/dist/cli/chunk-xt0qvz1t.js.map +10 -0
  69. package/dist/cli/index.js +78 -42
  70. package/dist/cli/index.js.map +7 -6
  71. package/dist/types/ai/changelog-components.d.ts +18 -0
  72. package/dist/types/ai/changelog-markdown.d.ts +12 -0
  73. package/dist/types/ai/openapi-components.d.ts +7 -7
  74. package/dist/types/ai/operation-details.d.ts +39 -0
  75. package/dist/types/ai/relative-links.d.ts +3 -1
  76. package/dist/types/ai/serializers.d.ts +6 -4
  77. package/dist/types/analytics/head.d.ts +4 -2
  78. package/dist/types/analytics/script.d.ts +8 -2
  79. package/dist/types/components/content/youtube.d.ts +20 -0
  80. package/dist/types/components/layout/nav-utils.d.ts +8 -0
  81. package/dist/types/components/openapi/example-panels.d.ts +33 -0
  82. package/dist/types/components/openapi/helpers.d.ts +53 -11
  83. package/dist/types/components/openapi/snippets.d.ts +7 -1
  84. package/dist/types/core/alert-diagnostics.d.ts +10 -0
  85. package/dist/types/core/builtin-tags.d.ts +16 -0
  86. package/dist/types/core/changelog-list.d.ts +48 -0
  87. package/dist/types/core/closest-match.d.ts +6 -0
  88. package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
  89. package/dist/types/core/code-fences.d.ts +8 -1
  90. package/dist/types/core/config-input.d.ts +1 -1
  91. package/dist/types/core/content-assets.d.ts +16 -18
  92. package/dist/types/core/diagnostics.d.ts +14 -0
  93. package/dist/types/core/directive-diagnostics.d.ts +22 -8
  94. package/dist/types/core/former-routes.d.ts +14 -0
  95. package/dist/types/core/github.d.ts +5 -4
  96. package/dist/types/core/heading-markers.d.ts +65 -2
  97. package/dist/types/core/hidden-pages.d.ts +3 -0
  98. package/dist/types/core/includes.d.ts +23 -0
  99. package/dist/types/core/last-modified.d.ts +18 -6
  100. package/dist/types/core/links.d.ts +42 -2
  101. package/dist/types/core/load-module.d.ts +19 -4
  102. package/dist/types/core/manifest.d.ts +14 -1
  103. package/dist/types/core/markdown-mirrors.d.ts +22 -0
  104. package/dist/types/core/mdx-elements.d.ts +32 -0
  105. package/dist/types/core/mdx-syntax.d.ts +100 -0
  106. package/dist/types/core/meta-diagnostics.d.ts +7 -0
  107. package/dist/types/core/meta.d.ts +32 -5
  108. package/dist/types/core/nav-diagnostics.d.ts +9 -2
  109. package/dist/types/core/navigation.d.ts +13 -0
  110. package/dist/types/core/ordering-prefix.d.ts +6 -0
  111. package/dist/types/core/pinned-ids.d.ts +23 -0
  112. package/dist/types/core/project-graph.d.ts +9 -1
  113. package/dist/types/core/redirect-patterns.d.ts +18 -0
  114. package/dist/types/core/relative-files.d.ts +31 -0
  115. package/dist/types/core/schema.d.ts +267 -7
  116. package/dist/types/core/sources/normalize.d.ts +101 -9
  117. package/dist/types/core/sources/types.d.ts +16 -0
  118. package/dist/types/core/static-files.d.ts +10 -0
  119. package/dist/types/core/static-host.d.ts +42 -0
  120. package/dist/types/core/svg-images.d.ts +26 -0
  121. package/dist/types/core/syntax-diagnostics.d.ts +14 -0
  122. package/dist/types/core/types.d.ts +42 -1
  123. package/dist/types/deploy/generated-files.d.ts +41 -0
  124. package/dist/types/deploy/redirects.d.ts +21 -7
  125. package/dist/types/markdown/code-title.d.ts +9 -5
  126. package/dist/types/markdown/directives.d.ts +8 -0
  127. package/dist/types/markdown/fence-language.d.ts +51 -0
  128. package/dist/types/markdown/fence-meta.d.ts +5 -0
  129. package/dist/types/markdown/github-alerts.d.ts +35 -0
  130. package/dist/types/openapi/asyncapi.d.ts +6 -5
  131. package/dist/types/openapi/checks.d.ts +10 -5
  132. package/dist/types/openapi/model.d.ts +55 -8
  133. package/dist/types/openapi/parse.d.ts +4 -1
  134. package/dist/types/sources/filesystem.d.ts +14 -4
  135. package/dist/types/sources/mdx-remote.d.ts +6 -3
  136. package/dist/types/sources/registry.d.ts +3 -3
  137. package/dist/types/theme/color.d.ts +33 -0
  138. package/dist/types/theme/palette.d.ts +122 -0
  139. package/docs/01-quickstart.mdx +1 -1
  140. package/docs/02-deployment.mdx +23 -8
  141. package/docs/04-migrating.mdx +16 -4
  142. package/docs/advanced/changelog.mdx +27 -1
  143. package/docs/advanced/skills.mdx +1 -1
  144. package/docs/cli/audit.mdx +1 -1
  145. package/docs/cli/doctor.mdx +2 -2
  146. package/docs/cli/evals.mdx +1 -1
  147. package/docs/cli/index.mdx +4 -2
  148. package/docs/cli/validate.mdx +10 -7
  149. package/docs/configuration/analytics.mdx +2 -0
  150. package/docs/configuration/customization.mdx +3 -1
  151. package/docs/configuration/index.mdx +2 -2
  152. package/docs/configuration/theming.mdx +5 -1
  153. package/docs/content/components.mdx +26 -6
  154. package/docs/content/frontmatter.mdx +1 -1
  155. package/docs/content/includes.mdx +6 -2
  156. package/docs/content/index.mdx +11 -1
  157. package/docs/content/islands.mdx +2 -0
  158. package/docs/content/meta.mdx +9 -3
  159. package/docs/content/navigation.mdx +14 -6
  160. package/docs/content/sources/obsidian.mdx +1 -1
  161. package/docs/content/sources/remote-mdx.mdx +3 -1
  162. package/docs/content/syntax.mdx +76 -8
  163. package/docs/content/variables.mdx +3 -1
  164. package/docs/discoverability/agent-discovery.mdx +1 -1
  165. package/docs/discoverability/json-api.mdx +1 -1
  166. package/docs/discoverability/llms-txt.mdx +1 -1
  167. package/docs/discoverability/markdown.mdx +2 -2
  168. package/docs/discoverability/open-graph.mdx +1 -1
  169. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  170. package/docs/references/graphql.mdx +2 -0
  171. package/docs/references/openapi.mdx +45 -7
  172. package/package.json +4 -4
  173. package/skills/blume/SKILL.md +2 -2
  174. package/skills/blume-migrate/SKILL.md +49 -27
  175. package/skills/blume-migrate/references/docsify.md +262 -0
  176. package/skills/blume-migrate/references/docus.md +298 -0
  177. package/skills/blume-migrate/references/docusaurus.md +3 -3
  178. package/skills/blume-migrate/references/fern.md +327 -0
  179. package/skills/blume-migrate/references/fumadocs.md +1 -1
  180. package/skills/blume-migrate/references/gitbook.md +267 -0
  181. package/skills/blume-migrate/references/github-wiki.md +259 -0
  182. package/skills/blume-migrate/references/jekyll.md +253 -0
  183. package/skills/blume-migrate/references/mdbook.md +256 -0
  184. package/skills/blume-migrate/references/mintlify.md +5 -5
  185. package/skills/blume-migrate/references/mkdocs.md +236 -0
  186. package/skills/blume-migrate/references/monorepo.md +20 -8
  187. package/skills/blume-migrate/references/nextra.md +2 -2
  188. package/skills/blume-migrate/references/readme.md +257 -0
  189. package/skills/blume-migrate/references/redocly.md +198 -0
  190. package/skills/blume-migrate/references/starlight.md +3 -3
  191. package/skills/blume-migrate/references/vitepress.md +204 -0
  192. package/skills/blume-migrate/references/vuepress.md +271 -0
  193. package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
  194. package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
  195. package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
  196. package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
  197. package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
  198. package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
  199. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
  200. package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
  201. package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
  202. package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
  203. package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
  204. package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
  205. package/src/ai/changelog-components.ts +24 -0
  206. package/src/ai/changelog-markdown.ts +45 -41
  207. package/src/ai/component-markdown.ts +22 -9
  208. package/src/ai/llms.ts +36 -5
  209. package/src/ai/markdown.ts +2 -2
  210. package/src/ai/openapi-components.ts +25 -9
  211. package/src/ai/operation-details.ts +208 -0
  212. package/src/ai/relative-links.ts +48 -8
  213. package/src/ai/serializers.ts +7 -6
  214. package/src/ai/site-skill.ts +6 -2
  215. package/src/analytics/head.ts +11 -5
  216. package/src/analytics/script.ts +13 -3
  217. package/src/astro/generate.ts +32 -6
  218. package/src/astro/index.ts +1 -0
  219. package/src/astro/integration.ts +27 -1
  220. package/src/astro/mdx-source-errors.ts +46 -0
  221. package/src/astro/templates.ts +89 -185
  222. package/src/audit/checks/redirects.ts +5 -1
  223. package/src/audit/redirects.ts +36 -19
  224. package/src/audit/report.ts +22 -9
  225. package/src/audit/run.ts +12 -28
  226. package/src/audit/terms.ts +23 -0
  227. package/src/audit/url.ts +2 -2
  228. package/src/cli/build-failure.ts +256 -7
  229. package/src/cli/command-meta.ts +1 -1
  230. package/src/cli/commands/audit.ts +1 -1
  231. package/src/cli/commands/build.ts +12 -3
  232. package/src/cli/commands/check.ts +13 -2
  233. package/src/cli/commands/dev.ts +1 -1
  234. package/src/cli/commands/doctor.ts +4 -2
  235. package/src/cli/commands/init.ts +3 -3
  236. package/src/cli/commands/preview.ts +17 -1
  237. package/src/cli/commands/validate.ts +22 -10
  238. package/src/cli/prepare.ts +1 -1
  239. package/src/cli/preview-pages.ts +161 -0
  240. package/src/cli/unknown-flags.ts +1 -42
  241. package/src/components/code-copy.ts +51 -0
  242. package/src/components/code-language.ts +75 -0
  243. package/src/components/content/AccordionItem.astro +16 -1
  244. package/src/components/content/Callout.astro +11 -1
  245. package/src/components/content/Changelog.astro +189 -0
  246. package/src/components/content/CodeBlock.astro +8 -11
  247. package/src/components/content/Tabs.astro +16 -2
  248. package/src/components/content/YouTube.astro +6 -4
  249. package/src/components/content/code-block.ts +33 -0
  250. package/src/components/content/content-strings.ts +5 -1
  251. package/src/components/content/inline-markdown.ts +35 -8
  252. package/src/components/content/youtube.ts +49 -7
  253. package/src/components/layout/Analytics.astro +5 -1
  254. package/src/components/layout/NavTree.astro +8 -3
  255. package/src/components/layout/RootLayout.astro +18 -31
  256. package/src/components/layout/nav-utils.ts +14 -0
  257. package/src/components/layout/search/pagefind.ts +2 -1
  258. package/src/components/openapi/Operation.astro +4 -0
  259. package/src/components/openapi/PanelTabs.astro +9 -3
  260. package/src/components/openapi/RequestBody.astro +42 -8
  261. package/src/components/openapi/RequestPanel.astro +34 -53
  262. package/src/components/openapi/example-panels.ts +110 -0
  263. package/src/components/openapi/helpers.ts +195 -20
  264. package/src/components/openapi/operation-model.ts +63 -26
  265. package/src/components/openapi/request.ts +13 -0
  266. package/src/components/openapi/snippets.ts +14 -2
  267. package/src/core/alert-diagnostics.ts +89 -0
  268. package/src/core/builtin-tags.ts +76 -0
  269. package/src/core/changelog-list.ts +87 -0
  270. package/src/core/closest-match.ts +41 -0
  271. package/src/core/code-fence-diagnostics.ts +267 -0
  272. package/src/core/code-fences.ts +43 -7
  273. package/src/core/component-diagnostics.ts +65 -12
  274. package/src/core/config-input.ts +1 -1
  275. package/src/core/config.ts +46 -1
  276. package/src/core/content-assets.ts +64 -144
  277. package/src/core/diagnostics.ts +36 -3
  278. package/src/core/directive-diagnostics.ts +265 -47
  279. package/src/core/former-routes.ts +46 -0
  280. package/src/core/git-ignored.ts +53 -0
  281. package/src/core/github.ts +5 -4
  282. package/src/core/heading-markers.ts +156 -2
  283. package/src/core/hidden-pages.ts +64 -0
  284. package/src/core/includes.ts +181 -41
  285. package/src/core/last-modified.ts +47 -10
  286. package/src/core/links.ts +438 -83
  287. package/src/core/load-module.ts +98 -9
  288. package/src/core/manifest.ts +12 -9
  289. package/src/core/markdown-mirrors.ts +52 -0
  290. package/src/core/mdx-elements.ts +153 -0
  291. package/src/core/mdx-syntax.ts +377 -0
  292. package/src/core/meta-diagnostics.ts +134 -0
  293. package/src/core/meta.ts +154 -55
  294. package/src/core/nav-diagnostics.ts +16 -4
  295. package/src/core/navigation.ts +70 -10
  296. package/src/core/ordering-prefix.ts +22 -2
  297. package/src/core/pinned-ids.ts +141 -0
  298. package/src/core/project-graph.ts +221 -29
  299. package/src/core/redirect-patterns.ts +41 -0
  300. package/src/core/relative-files.ts +145 -0
  301. package/src/core/schema.ts +17 -3
  302. package/src/core/sources/mdx-remote.ts +75 -14
  303. package/src/core/sources/normalize.ts +885 -179
  304. package/src/core/sources/notion.ts +5 -4
  305. package/src/core/sources/obsidian.ts +2 -5
  306. package/src/core/sources/types.ts +17 -0
  307. package/src/core/static-files.ts +40 -0
  308. package/src/core/static-host.ts +125 -0
  309. package/src/core/svg-images.ts +98 -0
  310. package/src/core/syntax-diagnostics.ts +491 -0
  311. package/src/core/types.ts +43 -1
  312. package/src/deploy/artifacts.ts +2 -28
  313. package/src/deploy/generated-files.ts +128 -0
  314. package/src/deploy/platforms/vercel.ts +25 -1
  315. package/src/deploy/redirects.ts +43 -16
  316. package/src/deploy/sitemap.ts +2 -1
  317. package/src/markdown/base-links.ts +87 -6
  318. package/src/markdown/code-title.ts +29 -13
  319. package/src/markdown/content-assets.ts +210 -0
  320. package/src/markdown/directives.ts +69 -5
  321. package/src/markdown/fence-language.ts +106 -0
  322. package/src/markdown/fence-meta.ts +42 -0
  323. package/src/markdown/github-alerts.ts +172 -0
  324. package/src/markdown/heading-anchors.ts +265 -56
  325. package/src/markdown/include.ts +30 -3
  326. package/src/markdown/index.ts +145 -40
  327. package/src/markdown/prompt-text.ts +95 -0
  328. package/src/markdown/relative-links.ts +37 -2
  329. package/src/markdown/route-snapshot.ts +22 -12
  330. package/src/markdown/svg-fallback.ts +51 -0
  331. package/src/markdown/title-heading.ts +134 -0
  332. package/src/migrate/migrate.ts +127 -2
  333. package/src/og/card.ts +14 -4
  334. package/src/openapi/asyncapi.ts +14 -6
  335. package/src/openapi/checks.ts +160 -15
  336. package/src/openapi/model.ts +172 -33
  337. package/src/openapi/parse.ts +13 -4
  338. package/src/openapi/render-mdx.ts +55 -9
  339. package/src/openapi/source.ts +87 -6
  340. package/src/registry/eject.ts +0 -1
  341. package/src/search/documents.ts +10 -2
  342. package/src/sources/filesystem.ts +28 -3
  343. package/src/sources/mdx-remote.ts +7 -15
  344. package/src/theme/color.ts +14 -0
  345. package/src/theme/entry.ts +22 -1
  346. package/src/theme/palette.ts +74 -1
  347. package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
  348. package/dist/cli/chunk-0w6hs479.js.map +0 -10
  349. package/dist/cli/chunk-150jk7km.js +0 -52
  350. package/dist/cli/chunk-150jk7km.js.map +0 -10
  351. package/dist/cli/chunk-36bxs9c7.js.map +0 -10
  352. package/dist/cli/chunk-53jqw8z0.js +0 -89
  353. package/dist/cli/chunk-53jqw8z0.js.map +0 -10
  354. package/dist/cli/chunk-60hz265r.js.map +0 -10
  355. package/dist/cli/chunk-desvm72z.js.map +0 -14
  356. package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
  357. package/dist/cli/chunk-es8h0v3j.js +0 -441
  358. package/dist/cli/chunk-es8h0v3j.js.map +0 -11
  359. package/dist/cli/chunk-kdpzw38a.js.map +0 -11
  360. package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
  361. package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
  362. package/dist/cli/chunk-twy7psdz.js.map +0 -10
  363. package/dist/cli/chunk-ydp3065p.js +0 -64
  364. package/dist/cli/chunk-ydp3065p.js.map +0 -10
  365. /package/dist/cli/{chunk-74wfhv79.js.map → chunk-05f21r8h.js.map} +0 -0
  366. /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-1cm0h819.js.map} +0 -0
  367. /package/dist/cli/{chunk-ms9z66nz.js.map → chunk-4rftmttv.js.map} +0 -0
  368. /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-7tekknev.js.map} +0 -0
  369. /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-ahc5gc9h.js.map} +0 -0
  370. /package/dist/cli/{chunk-tfycddbt.js.map → chunk-bjk964fp.js.map} +0 -0
  371. /package/dist/cli/{chunk-dqhg54mr.js.map → chunk-dmx6cezx.js.map} +0 -0
  372. /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-m9bp2hc1.js.map} +0 -0
  373. /package/dist/cli/{chunk-58bt3hag.js.map → chunk-mrvh4c88.js.map} +0 -0
  374. /package/dist/cli/{chunk-1f882900.js.map → chunk-th0pgaj5.js.map} +0 -0
  375. /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-vnbvhgmd.js.map} +0 -0
  376. /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-w6e455pz.js.map} +0 -0
  377. /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-xgq4eytj.js.map} +0 -0
@@ -0,0 +1,327 @@
1
+ # Fern Docs → Blume
2
+
3
+ Fern Docs is a hosted platform driven by a `fern/` folder: `docs.yml` declares the **entire** navigation (tabs, sections, pages, the API Reference, changelogs, versions, products), pages are MDX files anywhere under `fern/`, and the API Reference is generated from an OpenAPI/AsyncAPI spec or a **Fern Definition**. Two things set it apart from the other sources:
4
+
5
+ - **The same `fern/` folder usually drives SDK generation too** (`generators.yml`, `definition/` or `apis/<name>/`). Only the docs move. Never delete or rewrite the API definition, a `generators.yml`, or `fern.config.json` (see [Teardown](#teardown-keep-sdk-generation)).
6
+ - **URLs come from `docs.yml`, not from file paths.** A page's URL joins slugs from its tab, sections, and page name, and a frontmatter `slug` replaces most of that. Files sit wherever the team put them (`pages/get-started/welcome.mdx` can serve `/welcome`).
7
+
8
+ Scope: checked against Fern CLI 5.44.8 and an end-to-end migration of agentmail-to/agentmail-docs (115 pages, a Fern Definition with 149 endpoints and 8 webhooks, 270 old URLs). Versions, products, tab variants, translations, `folder:` entries, and OpenAPI (rather than Fern Definition) inputs weren't in that run: the guidance for them below is untested.
9
+
10
+ ## Detect
11
+
12
+ - **`fern/docs.yml` beside `fern/fern.config.json`** (run from the repo root), or `docs.yml` + `fern.config.json` in the working directory (run from inside `fern/`). Monorepos nest it (`docs/fern/docs.yml`).
13
+ - `fern.config.json` **without** a `docs.yml` is an SDK-only Fern project: the docs live elsewhere (often a Mintlify `docs.json`), so use that source's reference and leave `fern/` alone.
14
+ - A stale `fern/docs.yml` can outlive a move off Fern. If the repo also carries another docs config, check which one the live site runs: Fern pages answer with an `x-fern-renderer` header (`curl -sI https://<docs-domain>/ | grep -i x-fern`).
15
+
16
+ ## Run the codemod first
17
+
18
+ The bundled codemod does the mechanical half: URL computation and file placement, `meta.ts`, frontmatter, the common components, assets, and every redirect. Steps, in order:
19
+
20
+ 1. **Save the old URL list.** Fetch `https://<docs-domain>/sitemap.xml` and keep only the paths, one per line, in `old-urls.txt` (a Fern sitemap can name a different host than the one you fetched). Hidden pages aren't in it; the codemod computes those from `docs.yml`. Run the CORS probe under [Try it](#layout-summary-snippets-try-it) while you're there.
21
+ 2. **Dry run**, from the folder that holds `fern/` (where `blume.config.ts` goes):
22
+
23
+ ```bash
24
+ node <skill>/scripts/fern-codemod.mjs --sitemap old-urls.txt
25
+ ```
26
+
27
+ It prints a sitemap diff (`0 computed URL(s) not in it, 0 of its URLs not computed` is the goal; otherwise fix those pages by hand with [Fern's URL rules](#ferns-url-rules) and report them), the `navigation.tabs` entries for the config, which APIs are Fern Definitions, a TODO list of judgment calls, and the icons it couldn't map. `--out` sets the content root (default `docs`), `--public` the public folder, `--fern` the Fern folder. It **stops** on `versions` or `products`: migrate those by hand.
28
+
29
+ 3. **Apply** with `--write`. It moves every published page to a path that reproduces its old URL, writes frontmatter and `meta.ts` files, converts components, moves the assets pages use (and the logo and favicon) into `public/`, and saves `fern-migration.json`. A YAML or JSON file a page links to (a spec offered for download) is copied, not moved, since SDK generation may read it. It's idempotent: a rerun skips moved pages.
30
+ 4. **Write `blume.config.ts`** from [Config](#config-docsyml--blumeconfigts), with the printed tabs and `logo.href` set to the page `/` now redirects to.
31
+ 5. **Set up the API reference** ([API reference](#api-reference)): export a Fern Definition, write the overlay (tag names, webhooks, idempotency headers), add `openapi()`.
32
+ 6. **Build, then list the operation routes**:
33
+
34
+ ```bash
35
+ npx blume build
36
+ node <skill>/scripts/operation-routes.mjs dist > routes.json
37
+ npx fern-api@<version> export --api <api-name> spec.json # JSON because the path ends in .json
38
+ ```
39
+
40
+ The join needs a JSON copy of the spec the build rendered: for a Fern Definition, that export (a scratch file, not the committed spec); for a YAML OpenAPI input, `npx @redocly/cli bundle <spec>.yaml --output spec.json`.
41
+
42
+ 7. **Join endpoints and write the redirects**:
43
+
44
+ ```bash
45
+ node <skill>/scripts/fern-codemod.mjs endpoints --routes routes.json --spec /api-reference=spec.json --sitemap old-urls.txt # dry run
46
+ node <skill>/scripts/fern-codemod.mjs endpoints --routes routes.json --spec /api-reference=spec.json --sitemap old-urls.txt --write
47
+ ```
48
+
49
+ One `--spec <route>=<file>` per API. It maps every old endpoint, webhook, and group URL to its Blume route, translates `docs.yml` redirects, adds the content-move, changelog, feed, and `/` redirects, rewrites content links that point at old URLs, and checks every old URL is served or redirected (it must print `every old URL is served or redirected`). `--write` saves `fern-redirects.json`; import it:
50
+
51
+ ```ts
52
+ import fernRedirects from "./fern-redirects.json" with { type: "json" };
53
+ // in defineConfig:
54
+ redirects: [...fernRedirects],
55
+ ```
56
+
57
+ Fix a redirect it flagged by filtering that entry out and adding your own after the spread.
58
+
59
+ 8. **Work the TODO list**, then `blume build`, `blume validate --strict`, `blume audit --only redirects`, SKILL.md's heading-anchor check (`pin-heading-ids.mjs`), and `fern check` after the [teardown](#teardown-keep-sdk-generation).
60
+
61
+ What it leaves to you: the config, the overlay, the TODO list, the components it names, links already broken on Fern, CI, and contributor docs. It never touches `generators.yml`, the definition, or a spec.
62
+
63
+ ## Inventory
64
+
65
+ Read `docs.yml` first, then every file it points at: `redirects` (a list, or YAML files that each hold a `redirects:` list), `tabs.<key>.changelog`, every `path:` in `navigation`, `css`, `js`. **Every path in a Fern YAML file is relative to that YAML file.**
66
+
67
+ - **Unpublished drafts.** Fern builds only the files `docs.yml` references plus each changelog folder; any other `.md`/`.mdx` under `fern/` is a draft nobody sees. The codemod leaves them in place and lists them. Blume would publish them: delete them or move them to a `_`-prefixed folder (`_drafts/`), and report the list.
68
+ - **SDK generation.** Every `generators.yml` and its `groups` stay exactly as they are.
69
+ - **Second instances.** Each `instances[]` entry is a deployment of the same docs (a second domain, a subpath). One Blume build serves one; report the rest (see [Deploy](#urls-redirects-and-deploy)).
70
+
71
+ **Where the Blume project goes.** If the repo root has a `package.json`, put `blume.config.ts` there with content in `docs/` (the default root). If it has no JavaScript toolchain (a Python or Go repo whose docs were only `fern/`), make a self-contained docs package (`docs/` holding `package.json`, `blume.config.ts`, and `content.root: "content"`), run the codemod from there with `--fern ../fern --out content`, and say which you chose. Don't make `fern/` itself the content root: it keeps holding the SDK config. See `references/monorepo.md` for workspaces.
72
+
73
+ ## Config: `docs.yml` → `blume.config.ts`
74
+
75
+ Map only what's set.
76
+
77
+ | Fern `docs.yml` | Blume | Notes |
78
+ | --- | --- | --- |
79
+ | `title` | `title` | Fern shows it only in the browser tab; Blume also shows it beside the logo (see `logo`) |
80
+ | `logo` (`{ light, dark, href, height, right-text }`) | `logo: { image: { light, dark, alt }, href, text }` | the codemod moves the files into `public/`. Fern shows the image alone unless `right-text` is set, so set **`text: right-text`, or `text: ""`**. A local monochrome SVG can collapse to `logo: "/logo.svg"` with `currentColor` fills (SKILL.md). Set `href` to the first page when nothing serves `/`. `height` drops |
81
+ | `favicon` | **drop the field** | the codemod moves the file to `public/favicon.<ext>`; Blume detects it by filename |
82
+ | `colors.accent-primary` (or `accentPrimary`), a string or `{ light, dark }` | `theme.accent` | same shape |
83
+ | `colors.background` | `theme.background` | same shape |
84
+ | `colors.*`, `layout.*`, `settings.*` (others), `theme.*`, `global-theme`, `page-actions`, `experimental`, `check`, `ai-examples`, `libraries`, `roles` | **drop** (report) | `global-theme` merged a registry theme at publish: read its values off the live site |
85
+ | `typography.bodyFont` / `headingsFont` / `codeFont` | `theme.fonts.body` / `display` / `mono` | Fern fonts are files in the repo (`path`, or `paths` with `weight`/`style`): each role takes `{ name, variants: [{ src, weight, style }] }` |
86
+ | `navbar-links` `type: filled` / `primary` | `navigation.cta` (`{ label: text, href }`) | one only; extra filled links go to `actions`. `url` is Fern's old name for `href` |
87
+ | `navbar-links` `type: minimal` / `outlined` / `secondary` | `navigation.actions` (`[{ label: text, href }]`) | text only: link icons drop. A `mailto:` link works |
88
+ | `navbar-links` `type: github` / `dropdown` | `github: { owner, repo }` / **no equivalent** | flatten a dropdown into `actions` (report) |
89
+ | `footer-links` | `footer.socials` | keys carry over except `twitter` → `x`, `hackernews` → `hacker-news` |
90
+ | `footer` / `header` (React files) | **drop** (report) | or a layout slot in `components.ts` |
91
+ | `instances[].custom-domain` | `deployment.site` (`https://…`) | only when the target host isn't Vercel or Netlify (SKILL.md). **A domain with a path** (`acme.com/docs`) is a reverse-proxied subpath → `deployment: { base: "/docs" }`. **Several instances** → one Blume build; keep the canonical one (`metadata.canonical-host` names it when set) and report the rest |
92
+ | `instances[].edit-this-page.github` (`{ owner, repo, branch }`) | `github: { owner, repo, branch, dir }` | `dir` = path from the repo root to the folder holding `blume.config.ts` |
93
+ | `metadata.canonical-host` | `deployment.site` | same host rule as `custom-domain`; a path in it is the `base` above |
94
+ | `metadata.og:description` / `twitter:site` | `description` / `seo.x.handle` | other `og:*`/`twitter:*` drop: Blume writes them |
95
+ | `announcement.message` | `banner` | a string, or `{ content, link: { text, href } }` |
96
+ | `analytics.*` | the matching factory from `blume/analytics` (`posthog`, `googleAnalytics`, `googleTagManager`, `segment`), or `script()` | a `${VAR}` value → `process.env.VAR` |
97
+ | `js` (a path, `{ path, strategy }`, or `{ url, strategy }`) | `script({ src \| content, strategy })` per tracking script | a third-party loader ports; code that reads or restyles Fern's DOM (`.fern-*`) doesn't (report it). Fern's strategies (`beforeInteractive`, `afterInteractive`, `lazyOnload`) aren't Blume's (`async`, `defer`): pick one or leave it out. A `<link rel="help" href="/llms.txt">` injection drops: Blume publishes `llms.txt` |
98
+ | `css` | a root `theme.css` | `.fern-*` selectors match nothing in Blume; report what you drop |
99
+ | `default-language`, `settings.http-snippets` | `codeSamples` on `openapi()` | that language first; a list → that list; `false` → `false` |
100
+ | `settings.disable-search` | `search: false` | |
101
+ | `layout.hide-feedback` | `feedback: false` | |
102
+ | `ai-search` / `ai-chat` (Ask Fern) | **drop** (report) | Blume's assistant is opt-in and self-hosted (`ai.assistant`, a provider key, server output); offer it, don't enable it silently |
103
+ | `agents.*` | **drop**, or `agents.llmsTxt.details` for directive text | Blume generates `llms.txt` and `robots.txt` |
104
+
105
+ ## Navigation
106
+
107
+ ### Fern's URL rules
108
+
109
+ The codemod applies these; you need them for its sitemap diff and anything it doesn't handle. Each level contributes a slug, joined outermost first: **product → version → tab → sections → page**.
110
+
111
+ - **Each slug is the item's `slug:` if set, else lodash `kebabCase` of its display name**: tab `display-name` (not the tab's key), `section:` title, `page:` name. `kebabCase` lowercases, drops punctuation, and splits camelCase and letter/digit boundaries: `API Reference` → `api-reference`, `AgentMail` → `agent-mail`, `SOC 2` → `soc-2`, `v3 (Latest)` → `v-3-latest`.
112
+ - **`skip-slug: true`** on a tab, section, or api entry drops that level from the URL but keeps it in the sidebar. A changelog tab ignores it.
113
+ - **A frontmatter `slug` is the page's whole URL below any product/version prefix**: it replaces the tab and section slugs too. A `docs.yml` `slug:` replaces only that item's own segment.
114
+ - **An `api` entry's base** is its `summary` page's frontmatter `slug` when set; otherwise its `slug` (or `kebabCase(title)`) under the tab.
115
+ - **A changelog** serves its index at its slug and **each entry at `<changelog>/<YYYY>/<M>/<D>`** (no zero padding), titled with the date (`September 5, 2026`). Two entry files dated the same day share one URL: Fern merged them into one page.
116
+ - **`/`** answers with a 307 to the first page when no landing page is set. **API group paths** (`/api-reference/inboxes/threads`) answer with a 307 to the group's first endpoint and aren't in the sitemap, but content links to them.
117
+ - **The first version is the default and has no version segment**; every other version is prefixed with its slug. Products always carry their slug.
118
+
119
+ ### What the codemod builds
120
+
121
+ - **One folder per tab** at the tab's URL prefix. The tab whose pages share no single prefix (a `skip-slug` tab, or one whose pages all carry root-level frontmatter slugs) becomes the content root, with a tab at `path: "/"`. Only one tab can be the root; the codemod reports a second one, which must move under a prefix (route change, redirects).
122
+ - **A section** whose pages share a URL segment → a real folder; one that adds no segment → a `(group)` folder. A section's `path` (its overview page) → that folder's `index.mdx`.
123
+ - **A page whose URL departs from its folder** (a frontmatter `slug`) stays in its sidebar folder with `slug` set, since Blume places a page in the sidebar by file path, not route.
124
+ - **A file `docs.yml` lists twice** is published once, at its first listing; the other URL redirects there (reported).
125
+ - **`meta.ts`** per folder: `pages` in `docs.yml` order, the section title and Lucide icon, and a collapsible section (`collapsed`, `collapsible`, `collapsed-by-default`) → `display: "group"`. Blume lists loose pages above subgroups, so a section that interleaves pages and subsections renders its pages first: report any such reorder.
126
+ - **Labels.** Fern labels a page in the sidebar with its `page:` name (or `sidebar-title`) and titles it with frontmatter `title`. So: no `title` → `title: <page name>` (or an H1 that opens the body, which it removes); a different `title` → `sidebar.label: <page name>`. The api **summary** page keeps its `title` with no label (its page name usually repeats the tab label).
127
+ - **`hidden: true`** → `hidden: true`, `seo: { noindex: true }`, and `search: { exclude: true }`: Fern keeps hidden pages reachable but out of search and indexing.
128
+ - **The root URL.** Nothing serves `/` under a root tab, so it redirects `/` to the first page (307), as Fern did; set `logo.href` to that page. The tab at `/` links to the sidebar's first page on its own. A root `index.mdx` would move the landing page to `/`; do that only if the user wants `/` as the landing URL.
129
+
130
+ By hand: `availability` (`beta`, `deprecated`, …) → `sidebar.badge` (`Beta`) or `deprecated: true`; `- link:` items → `navigation.featured` (`{ label, href, icon? }`) or an explicit `navigation.sidebar` link (report which); a hidden **tab** (its printed line ends `(hidden on Fern)`) → leave it out of `navigation.tabs`; the codemod already marked its pages hidden.
131
+
132
+ ### Tabs
133
+
134
+ The codemod prints one `navigation.tabs` entry per tab (`{ label: display-name, path, icon }`). Keep them as header tabs.
135
+
136
+ - An **`href` tab** (Blog, GitHub, a dashboard) has no content → `navigation.actions` or `navigation.featured`; a GitHub tab → `github`.
137
+ - **Tab `variants`** (several sidebars under one tab): the codemod reports them. One subfolder per variant and a tab with `items` (`{ label, path }` per variant); variant pages move, so add redirects and report the approximation.
138
+
139
+ ### Changelog
140
+
141
+ The codemod moves each entry to `changelog/<YYYY-MM-DD>[-suffix].mdx` (`MM-DD-YYYY` and `MM-DD-YY` names are renamed, so entries sort by date and a two-digit year's month isn't read as an ordering prefix. Fern parsed those in the publishing machine's time zone, so one can sit a day off: the sitemap diff shows it) with `type: changelog`, `date`, and a `title`: the frontmatter one, the date as Fern showed it, or the file suffix sentence-cased (`agentid-sign-in-keys` → "Agentid sign in keys"). **Review every suffix title** (`AgentID sign-in keys`); the TODO list names them. `tags` → `changelog.category` (the first) and `search.tags` (all).
142
+
143
+ - **Same-day entries.** The day's Fern URL redirects to the unsuffixed entry, or else the first by file name. Fern showed both on one page: consider linking the second from the first, and report it.
144
+ - `overview.mdx` (or `index.mdx`, `summary.mdx`) → its text goes into `changelog: { title, description }` in `blume.config.ts`, since an `index.mdx` would replace Blume's generated `/changelog` index (a list, where Fern's index rendered whole entries). Its `layout` and `authors` drop.
145
+ - A non-date file (`TEMPLATE.mdx`) was never published: move it out or `_`-prefix it, and update it to Blume's format if contributors copy it.
146
+ - The feeds `/<changelog>.rss` (`.atom`, `.json`) redirect to `/changelog/rss.xml`, which needs a site URL.
147
+ - For a public GitHub repo, offer `githubReleases()` as SKILL.md "Changelogs" says; keep the files when the user says the entries are curated rather than release notes.
148
+
149
+ ### Versions and products (by hand, untested)
150
+
151
+ - **`versions`** → Blume's native versioning (`docs/content/versioning.mdx` in the installed package). The first Fern version is the live tree at the content root; every other version → a folder named by its slug, listed in `versions.archived`. Place each version file's `navigation` with the same rules; a `ref:` version lives in git, so check it out. Per-version API references → one `openapi()` each, with its own `route`.
152
+ - **`products`** → one folder per product plus `navigation.selectors: [{ kind: "product", label, items }]`.
153
+ - `<If versions products>` → keep each folder's matching branch.
154
+
155
+ ## Content and components
156
+
157
+ The codemod writes every page as `.mdx` and converts:
158
+
159
+ - **Callouts.** `<Note>`, `<Tip>`, `<Info>`, `<Warning>`, `<Success>`/`<Check>`, `<Error>`, `<Launch>`, and `<Callout intent>` (no intent → `info`, as Fern rendered it). A top-level one becomes a `:::type[Title]` directive (dedented). One whose icon maps to Lucide (a directive can't set one), or one nested in a component, becomes `<Callout type title icon>`; an icon with no Lucide equivalent drops (reported).
160
+ - **Code groups.** `<CodeBlocks>` → `<CodeGroup>`; every untitled fence inside a group gets Fern's language title (`title="Python"`, `title="cURL"`). Blume labels an untitled grouped fence by its language anyway, so the title only keeps Fern's spelling. `<CodeBlock title>` wrappers are unwrapped onto the fence: Blume's `<CodeBlock>` renders a wrapped fence, but not the wrapper's `title`.
161
+ - **Fence metas.** Multi-word bare titles are quoted (Fern and Blume both read every word of ` ```js Snippet with title` as the title), `filename=` → `title=` and `wordWrap` → `wrap` (Blume warns `BLUME_CODE_FENCE_OPTION` on the Fern spellings), `env` → `dotenv` (Shiki has no `env`: `BLUME_UNKNOWN_CODE_LANGUAGE`).
162
+ - **Cards and accordions.** `<Cards>` → `<CardGroup>`, card icons → Lucide, Fern-only card props drop. `<AccordionGroup>`/`<Accordion>` → `<Accordion>`/`<AccordionItem>`; a lone `<Accordion>` is wrapped in one.
163
+ - **`<Icon>`**: icon → Lucide, `size` × 4 (Fern counts in 4px units). **`<Frame>`**: `background` drops. **`<Tab language>`**: `language` drops (Blume syncs same-titled tabs).
164
+ - **`<ParamField path>`** → `<ParamField name>`: Fern's `path` is the field's name, and Blume's `path=` would mark it a path parameter. `toc` drops. Nested fields in an `<Indent>` (reported) → an `<Expandable title="properties">` in the parent field's body.
165
+ - **Snippets.** `<Markdown src="/snippets/x.mdx" planName="x" />` → `<include planName="x">/_snippets/x.mdx</include>` on a line of its own, with the snippet moved to `<content-root>/_snippets/`. Its `{{planName}}` placeholders carry over as written; Blume reads an include prop only when its name starts with a lowercase letter, so rename any that don't.
166
+ - **Embeds and comments.** A YouTube iframe → `<YouTube id>` (a playlist embed → `<YouTube url>`); `allowfullscreen`/`frameborder` → `allowFullScreen`/`frameBorder` (MDX drops the lowercase forms silently); `<!-- -->` → `{/* */}`.
167
+ - **Assets.** `/assets/x.png` and `../x.png` → moved into `public/` at their path from `fern/` and linked root-absolute. A YAML or JSON file is copied instead (SDK generation may read it; the copy goes stale when it changes). An asset no page or config references stays put: move or delete it.
168
+ - **Links.** Absolute links to the docs' own domain → root-relative; links to moved pages, endpoints, and Fern's `api:METHOD/path` syntax → the new routes (the `endpoints` step).
169
+
170
+ **Reported for you to convert** (a leftover Fern tag gets a `BLUME_UNKNOWN_COMPONENT` warning, or crashes the page):
171
+
172
+ | Fern | Blume |
173
+ | --- | --- |
174
+ | `<EndpointRequestSnippet endpoint="POST /x">`, `<EndpointResponseSnippet>` | a hand-written fenced sample plus a link to the operation page: Blume can't embed an operation's sample in prose |
175
+ | `<EndpointSchemaSnippet>`, `<Schema>`, `<SchemaSnippet>`, `<WebhookPayloadSnippet>`, `<RunnableEndpoint>` | a link to the operation page; a small schema can become a `TypeTable` |
176
+ | `<Badge intent outlined>`, `<Availability type>` | `<Badge variant stroke>` (`success`, `warning`, `error` → `danger`, `tip` → `accent`); an availability that labels the page → `sidebar.badge` |
177
+ | `<Code src title>` | `<include meta='title="…"' lang="…">./path</include>` with the file inside the content root |
178
+ | `<Files>`/`<Folder>`/`<File>` | `<Tree>`/`<Tree.Folder>`/`<Tree.File>` |
179
+ | `<Versions>`/`<Version title>` | `<Tabs>`/`<Tab title>` |
180
+ | `<If>` (roles), `<Feature flag>` | the public or default content; ask before dropping role-gated text |
181
+ | `<Button href>`, `<Download src>`, `<Copy>` | a link (or `<Card href>`), a link to the file in `public/`, inline code |
182
+ | `<Aside>`, `<Indent>`, `<StickyTable>`/`<SearchableTable>`/`<PaginatedTable>` | the content inside |
183
+ | `<Prompt title>`, `<ScrollWalkthrough>` | `<Prompt description>`; `<Steps>` with a code block per step |
184
+ | `<ChangelogTags>`, `<Template>` | delete (report) |
185
+
186
+ Not reported, but check: headings inside `<Steps>` (Fern makes each a step) → one `<Step title>` each; `<Anchor id>` → `<a id="x"></a>`; `<llms-only>`/`<llms-ignore>` → `<Visibility for="agents">`/`<Visibility for="web">`; `maxLines` → `expandable`; inline `$…$` math → `$$…$$`; custom React components → an island or a `components.ts` override. **Unclosed fences** (Fern rendered an empty block): the codemod closes them at the end of the file and reports them; delete the stray fence.
187
+
188
+ ### Frontmatter
189
+
190
+ Fern ignores unknown keys; Blume's schema is strict. The codemod maps `title`, `sidebar-title` → `sidebar.label`, `subtitle` → `description` (Blume renders `description` under the title), `description` → `seo.description` when `subtitle` took `description`, `headline` → `seo.title`, `slug`, `noindex` → `seo.noindex`, and the `docs.yml` `icon`. It reports every other key; map these by hand:
191
+
192
+ | Fern | Blume |
193
+ | --- | --- |
194
+ | `canonical-url` | `seo.canonical` (absolute) |
195
+ | `image`, `og:image` | `seo.image` |
196
+ | `keywords` | `search.keywords` (a list) |
197
+ | `last-updated` | drop it everywhere and set `lastModified: "git"` site-wide; retire any workflow that stamps it, since the key now fails the build |
198
+ | `layout` | `mode`: `overview`/`reference` → `wide`, `page` → `center`, `custom` → `custom`, `guide` → drop |
199
+ | `hide-nav-links: true` | `pagination: false` |
200
+ | `availability` | `sidebar.badge`, or `deprecated: true` |
201
+ | `position` (orders pages in a `- folder:` entry) | `sidebar.order` |
202
+ | `hide-toc`, `force-toc`, `max-toc-depth`, `hide-feedback`, `hide-page-actions`, `no-image-zoom`, `breadcrumb`, `search-metadata`, `edit-this-page-url`, `logo`, `nofollow`, `jsonld:breadcrumb`, `og:*`, `twitter:*`, `excerpt`, `tags` | drop (report) |
203
+ | keys Fern never read (`sidebar_position`, `lastUpdated`) | drop |
204
+
205
+ ### Icons
206
+
207
+ Fern uses Font Awesome; Blume is Lucide-only. The codemod normalizes Fern's spellings (`fa-solid fa-bolt`, `solid bolt`, bare `bolt`), maps them to Lucide names Blume bundles, and reports the rest. Most brand icons (`discord`, `google`, `aws`, `python`, `node-js`, `x-twitter`) have no Lucide equivalent: they drop. An image path on a component (`icon="./icons/x.svg"`) → `public/` and a root-absolute path; one in `docs.yml` is reported for you to move. For icons you write yourself (config, hand conversions), check each name from the folder holding `blume.config.ts`; an unknown icon renders nothing:
208
+
209
+ ```bash
210
+ node -e "const p=require.resolve('@iconify-json/lucide/icons.json',{paths:[require('path').dirname(require.resolve('blume/package.json'))]});const j=require(p);for(const n of process.argv.slice(1))console.log(n,n in j.icons||n in (j.aliases||{}))" zap mail-check discord
211
+ ```
212
+
213
+ ## API reference
214
+
215
+ Each `- api:` entry → one `openapi()` (or `asyncapi()`/`graphql()`) source from `blume/reference`, with **`route` set to the api entry's old base URL** so the overview keeps its address, and a tab at that route. Find the spec each entry renders:
216
+
217
+ - `specs:` on the api entry → those files (`type: openapi | asyncapi | graphql`).
218
+ - Otherwise `api-name: <name>` → `fern/apis/<name>/`, or `fern/` with one API. There, `generators.yml` `api.specs` lists OpenAPI/AsyncAPI files (older projects use top-level `openapi:`/`async-api:`); **a `definition/` folder is a Fern Definition**, which the codemod's dry run names.
219
+
220
+ ### OpenAPI and AsyncAPI inputs
221
+
222
+ - **Point `spec` at the file where it lives** (SDK generation reads it too).
223
+ - **Fern `overrides`** are Fern's deep-merge format, not OpenAPI Overlays. Most hold only `x-fern-*` SDK extensions, which Blume ignores; content an override changes for readers → an Overlay in `overlays`. Fern's own `overlays:` files carry over.
224
+ - **`x-fern-ignore: true` operations would appear** in Blume: remove them with an overlay action (`target: $.paths.*[?@['x-fern-ignore'] == true]`, `remove: true`), likewise operations an api entry's `audiences` filtered out.
225
+ - gRPC and OpenRPC references have no Blume renderer: report them.
226
+
227
+ ### Fern Definition inputs → export to OpenAPI
228
+
229
+ Blume can't read a Fern Definition. Export it with the Fern CLI, which runs offline with no login:
230
+
231
+ ```bash
232
+ npx fern-api@<version from fern/fern.config.json> export --api <api-name> openapi/<api-name>.yml
233
+ ```
234
+
235
+ - The output is **OpenAPI 3.0.1** (Blume upgrades it). Operation ids are `<package path>_<endpoint>` (`inboxes_threads_list`); each Fern package becomes one PascalCase tag (`InboxesThreads`); `info.title` is the API's name with an empty `version`.
236
+ - **It drops webhooks, WebSocket channels, and `idempotency-headers`.** Restore the first and last with the overlay below; a WebSocket channel has no Blume page, so redirect its old URL to the guide that covers it and report it.
237
+ - **A committed export.** If the repo already commits an OpenAPI generated from the definition (a `fernapi/fern-openapi` generator group writing locally, or a CI step running `fern export`), point `spec` at it, then check it isn't stale: export to a scratch path and diff. If they differ, report it and refresh it with the repo's own command (`fern generate --group <group> --local`), not a second copy. Look for workflows that trigger on that file (a release pipeline) before committing a refresh. Only when nothing produces a committed file, commit your export and add a `"docs:spec"` script that reruns it.
238
+
239
+ Docs-only changes go in an overlay so the spec stays regenerable. The agentmail overlay, abridged:
240
+
241
+ ```yaml openapi/docs.overlay.yaml
242
+ overlay: 1.1.0
243
+ info: { title: Docs fixes, version: 1.0.0 }
244
+ actions:
245
+ - target: $.info
246
+ update: { title: AgentMail API }
247
+ # Tags are flat in Blume: name the concatenated package tags.
248
+ - target: $.paths.*.*.tags[?@ == 'InboxesThreads']
249
+ update: Inbox Threads
250
+ # Fern's sidebar order.
251
+ - target: $
252
+ update:
253
+ tags: [{ name: Inboxes }, { name: Inbox Threads }]
254
+ # Webhooks the export dropped: their payload schemas survive in components.
255
+ - target: $
256
+ update:
257
+ x-webhooks:
258
+ messageReceived:
259
+ post:
260
+ summary: Message Received
261
+ tags: [Webhook Events]
262
+ requestBody:
263
+ content:
264
+ application/json:
265
+ schema: { $ref: "#/components/schemas/MessageReceivedEvent" }
266
+ responses:
267
+ "200":
268
+ description: Return a 2xx status to acknowledge the event.
269
+ # One action per `idempotent: true` endpoint in the definition.
270
+ - target: $.paths['/v0/inboxes/{inbox_id}/messages/send'].post.parameters
271
+ update:
272
+ - name: Idempotency-Key
273
+ in: header
274
+ schema: { type: string }
275
+ ```
276
+
277
+ **Restore webhooks this way** rather than redirecting their pages: one `x-webhooks` entry per webhook in the definition, keyed by its name, with a `$ref` to its payload schema. Blume upgrades `x-webhooks` to `webhooks` and gives each a page at `<route>/<tag-slug>/<kebab-name>`, which the `endpoints` step matches to the old URL. Nested packages (`Inboxes › Threads`) become sibling tags: report the lost nesting.
278
+
279
+ ### Layout, summary, snippets, Try it
280
+
281
+ - **`summary`** (a page shown at the API's root) → the codemod places it at `<route>/introduction.mdx`, a normal page in the reference sidebar. The old base URL serves Blume's overview, which renders `info.description`.
282
+ - **`layout`**, `alphabetized`, `flattened`, `paginated` → Blume orders tag groups by the top-level `tags` list (set it in the overlay) and operations within a tag in the spec's order (paths as listed, each path's methods as written). An endpoint order that `layout` or `alphabetized` set and the spec doesn't share → a `pages` list of operation slugs in `<route>/<tag-slug>/meta.ts`, or report it. An endpoint's `title` → its `summary` via overlay; `hidden: true` endpoints → remove them or let them show (ask).
283
+ - **`snippets`** (SDK package names) → Fern rendered SDK calls; Blume generates HTTP-client samples. Add `x-codeSamples` through the overlay to keep SDK calls, or report the loss.
284
+ - **Try it.** Fern's API Explorer proxies requests; Blume's playground sends them from the browser, so the API must answer CORS for the docs origin. Probe it:
285
+
286
+ ```bash
287
+ curl -si -X OPTIONS https://<api-host>/<path> -H 'Origin: https://<docs-domain>' -H 'Access-Control-Request-Method: GET'
288
+ ```
289
+
290
+ No `Access-Control-Allow-Origin` in the answer → `playground: false`, and report it; the alternative is `playground: { proxy: true }`, which needs server output and a host adapter (SKILL.md). `environments: []` → `playground: false`; `oauth: true` → Blume takes a pasted token and doesn't run the flow (report).
291
+
292
+ ### API URLs
293
+
294
+ The `endpoints` step computes these; check its output against them.
295
+
296
+ - **Fern Definition**: the operation id split on `_`, each part kebab-cased, under the base: `inboxes_threads_list` → `/api-reference/inboxes/threads/list`.
297
+ - **OpenAPI**: with `x-fern-sdk-method-name`, `<base>/<x-fern-sdk-group-name>/<x-fern-sdk-method-name>`; otherwise `<base>/<first tag>/<the operation id minus the tag's leading words>`, or the summary when there's no operation id. Each part kebab-cased (`v1` → `v-1`). Read from Fern's parser, not from a migrated site: the `endpoints` step's coverage check catches any URL it gets wrong.
298
+ - **Group paths** → the overview's anchor for that tag: `/api-reference/inboxes/threads` → `/api-reference#inbox-threads` (307, as Fern's was). Content linking to a group gets the same anchor; `blume validate` checks it.
299
+
300
+ Blume routes rarely match Fern's (the `endpoints` step skips any that do), and a pattern redirect over the old prefix also covers the new pages (`BLUME_REDIRECT_MATCHES_PAGE`), hence one exact redirect per endpoint. Don't shorten operation ids with an overlay: OpenAPI needs them unique across the whole spec, and shortened ones repeat (`list` in every tag), which warns `BLUME_OPENAPI_DUPLICATE_OPERATION_ID`.
301
+
302
+ ## URLs, redirects, and deploy
303
+
304
+ - **`docs.yml` redirects** → `{ from, to, status }`: Fern's `permanent` defaults to true (308), `permanent: false` → 307; Blume's own default is 301, so the codemod writes the status. `:name` and `:name*` carry over; `:name+` and regex groups are reported (expand them into exact entries). A destination that moved is followed to its final route, and a `:slug*` pattern into the reference expands into one entry per operation. Trailing-slash duplicates merge.
305
+ - **A destination that's neither a page nor another redirect** was already broken on Fern (the TODO list names it): point it at the intended page and report it.
306
+ - **`blume validate --strict` finds links that already 404 on Fern.** Fix the link, don't add a redirect for it, and report the list.
307
+ - **A subpath instance** (`acme.com/docs`): sources, destinations, and links carry the subpath; strip it, since Blume adds `base` itself. Serving both a domain and a subpath means two builds.
308
+
309
+ ## Teardown (keep SDK generation)
310
+
311
+ **Keep**, even with no docs left in it: `fern/fern.config.json`, every `generators.yml`, the Fern Definition, every spec, override, and overlay a `generators.yml` references, and CI steps that run `fern check`, `fern generate --group <sdk>`, or `fern export` (with the `FERN_TOKEN` secret if they use it).
312
+
313
+ **Remove**, once migrated: `fern/docs.yml`, redirect YAML files, the docs folders that moved (pages, `changelog/`, `snippets/`, assets, docs `components/`, `css`/`js`), CI steps and wrapper scripts that publish or preview the docs (`fern generate --docs`), and the `fern-api` dependency only if nothing else uses the CLI.
314
+
315
+ Then **grep the repo for the old paths and syntax**: `fern/pages`, `fern/changelog`, `fern/snippets`, `docs.yml`, `<CodeBlocks>`, `<Note>`, `last-updated`. Contributor docs (`AGENTS.md`, `CONTRIBUTING.md`, templates) and generators that write into `fern/snippets/` need the new paths, frontmatter, and components. Run `fern check` afterwards.
316
+
317
+ Repoint `package.json` scripts to `blume dev`/`build`/`preview`, add `blume`, and regenerate the lockfile (SKILL.md step 6). Tell the user what can't be committed: moving the custom domain's DNS (or the proxy behind a subpath instance) off Fern, then unpublishing the site in the Fern dashboard.
318
+
319
+ ## Dropped: report these
320
+
321
+ - **Hosted features**: the API Explorer's proxy, login-injected credentials, and OAuth flow; Ask Fern search and chat; AI-generated examples; **Fern's docs MCP server (`/_mcp/server`)**, which has no static equivalent (Blume's MCP server is at `/mcp` and needs server output; `.md` page mirrors and `llms.txt` carry over), so update content that advertises it; the dashboard, analytics, preview links, RBAC and authenticated docs (host-level protection instead, per the deployment docs' **Private docs** section), feature flags.
322
+ - **API reference**: SDK snippets (unless rebuilt as `x-codeSamples`), WebSocket channel pages, a `layout` or `alphabetized` endpoint order you didn't rebuild with `meta.ts`, nested sections, hidden endpoints, gRPC/OpenRPC references.
323
+ - **Navigation and theme**: dropdown and link icons, approximated variants, sidebar reorders, moved `link` items, extra `colors`, `layout`, custom `header`/`footer`, `css` aimed at `.fern-*`, and `js` that isn't a tracking script.
324
+ - **Content**: API embeds replaced by samples or links, code tooltips, `<ChangelogTags>`, role gating, and each dropped frontmatter key.
325
+ - **Icons** with no Lucide equivalent, by name and where they were used.
326
+ - **Unpublished drafts** and what was done with them.
327
+ - **Second instances** and what replaces them.
@@ -66,7 +66,7 @@ Fumadocs icons are strings resolved by the repo's own `icon` handler in `loader(
66
66
 
67
67
  ## Components
68
68
 
69
- - **Callouts:** `<Callout type="x">` → `:::` directive. Fumadocs types are `info` (the default), `warn`, `warning`, `error`, `success`, `idea`: `warn`/`warning`→`:::warning`, `error`→`:::danger`, `success`→`:::success`, `idea`→`:::tip`, `info` and **bare `<Callout>`** → `:::info`. `title` → `:::type[Title]`; drop `icon`.
69
+ - **Callouts:** `<Callout type="x">` → `:::` directive. Fumadocs types are `info` (the default), `warn`, `warning`, `error`, `success`, `idea`: `warn`/`warning`→`:::warning`, `error`→`:::danger`, `success`→`:::success`, `idea`→`:::tip`, `info` and **bare `<Callout>`** → `:::info`. `title` → `:::type[Title]`. An `icon` (a JSX element there) needs a `<Callout type="…" title="…" icon="cpu">` with its kebab-case Lucide name, since a directive can't set one; drop it if it only restated the type's icon.
70
70
  - **Cards:** `<Cards>` → `<CardGroup>`. `<Card>` needs **prop surgery**, not pass-through: Fumadocs `icon` is a JSX element (`icon={<Cpu />}` imported from `lucide-react`) → Blume takes a string name (`icon="cpu"`); Fumadocs `description="…"` → Blume has no `description` prop — move the text into the card body.
71
71
  - **Accordions (container/item inversion):** `<Accordions>` → `<Accordion>` (container); `<Accordion>` → `<AccordionItem>` (item).
72
72
  - **File trees:** `<Files>` → **`<Tree>`** (the JSX container for `Tree.*` children — _not_ `<FileTree>`, which wraps a Markdown list); `<Folder>` → `<Tree.Folder>` (`defaultOpen` carries over); `<File>` → `<Tree.File>`. Or convert the whole block to a list-driven `<FileTree>`.