blume 2.1.3 → 2.2.1

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 (397) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +134 -0
  3. package/README.md +4 -4
  4. package/dist/cli/{chunk-y4msrwm9.js → chunk-080mfch6.js} +15 -15
  5. package/dist/cli/{chunk-gfc5fqvf.js → chunk-1ch3ct3r.js} +18 -18
  6. package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-1ch3ct3r.js.map} +1 -1
  7. package/dist/cli/{chunk-32khwcb6.js → chunk-2ssh60qh.js} +42 -69
  8. package/dist/cli/{chunk-32khwcb6.js.map → chunk-2ssh60qh.js.map} +9 -10
  9. package/dist/cli/{chunk-9qzgg6mj.js → chunk-34wap3ed.js} +3 -3
  10. package/dist/cli/{chunk-k3gr1nm9.js → chunk-40vk8kgf.js} +13 -16
  11. package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-40vk8kgf.js.map} +3 -3
  12. package/dist/cli/chunk-487b95qy.js +329 -0
  13. package/dist/cli/chunk-487b95qy.js.map +13 -0
  14. package/dist/cli/chunk-4zxh25hc.js +157 -0
  15. package/dist/cli/chunk-4zxh25hc.js.map +10 -0
  16. package/dist/cli/chunk-69rjf786.js +146 -0
  17. package/dist/cli/chunk-69rjf786.js.map +11 -0
  18. package/dist/cli/{chunk-dqhg54mr.js → chunk-9h8mh2tk.js} +3 -3
  19. package/dist/cli/{chunk-dqhg54mr.js.map → chunk-9h8mh2tk.js.map} +2 -2
  20. package/dist/cli/{chunk-bqxaqn5p.js → chunk-9jqmq5hv.js} +8 -8
  21. package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-9jqmq5hv.js.map} +1 -1
  22. package/dist/cli/{chunk-dv2z0rzf.js → chunk-aafhwx74.js} +38 -13
  23. package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-aafhwx74.js.map} +5 -4
  24. package/dist/cli/{chunk-desvm72z.js → chunk-b680sv30.js} +22 -26
  25. package/dist/cli/chunk-b680sv30.js.map +13 -0
  26. package/dist/cli/{chunk-0w6hs479.js → chunk-beb0z983.js} +30 -18
  27. package/dist/cli/chunk-beb0z983.js.map +10 -0
  28. package/dist/cli/{chunk-1f882900.js → chunk-c5bjxq8v.js} +2 -2
  29. package/dist/cli/chunk-e1n41w9m.js +155 -0
  30. package/dist/cli/chunk-e1n41w9m.js.map +11 -0
  31. package/dist/cli/{chunk-sc93apfs.js → chunk-etckvbrh.js} +17 -17
  32. package/dist/cli/{chunk-sc93apfs.js.map → chunk-etckvbrh.js.map} +2 -2
  33. package/dist/cli/{chunk-bxwhtvw8.js → chunk-f260jdrn.js} +5 -5
  34. package/dist/cli/{chunk-74wfhv79.js → chunk-fnxgtjeq.js} +1 -1
  35. package/dist/cli/{chunk-ms9z66nz.js → chunk-gwy55cx3.js} +2 -2
  36. package/dist/cli/{chunk-ms9z66nz.js.map → chunk-gwy55cx3.js.map} +2 -2
  37. package/dist/cli/{chunk-dhwtqnnd.js → chunk-gxhcf2hs.js} +2 -2
  38. package/dist/cli/{chunk-tfycddbt.js → chunk-h0qwdbem.js} +2 -2
  39. package/dist/cli/{chunk-nbyvgnnm.js → chunk-hktq4j5r.js} +109 -49
  40. package/dist/cli/chunk-hktq4j5r.js.map +36 -0
  41. package/dist/cli/{chunk-twy7psdz.js → chunk-hn3g2b89.js} +14 -10
  42. package/dist/cli/chunk-hn3g2b89.js.map +10 -0
  43. package/dist/cli/{chunk-j1x92mx9.js → chunk-j4nfgn2j.js} +1 -1
  44. package/dist/cli/{chunk-dpy8qdjf.js → chunk-k45sq16q.js} +74 -183
  45. package/dist/cli/chunk-k45sq16q.js.map +18 -0
  46. package/dist/cli/{chunk-5cmts6dv.js → chunk-kmswjxj9.js} +7 -7
  47. package/dist/cli/{chunk-5cmts6dv.js.map → chunk-kmswjxj9.js.map} +1 -1
  48. package/dist/cli/{chunk-36bxs9c7.js → chunk-nqna8dzp.js} +27 -23
  49. package/dist/cli/chunk-nqna8dzp.js.map +10 -0
  50. package/dist/cli/{chunk-a57yh0h9.js → chunk-p834rkcn.js} +7 -7
  51. package/dist/cli/{chunk-a57yh0h9.js.map → chunk-p834rkcn.js.map} +1 -1
  52. package/dist/cli/{chunk-60hz265r.js → chunk-ppa6mg4d.js} +24 -11
  53. package/dist/cli/chunk-ppa6mg4d.js.map +10 -0
  54. package/dist/cli/{chunk-s0bdnc7v.js → chunk-q1wwrxzw.js} +5 -4
  55. package/dist/cli/chunk-q1wwrxzw.js.map +10 -0
  56. package/dist/cli/{chunk-ave1wcbr.js → chunk-qcyxkj44.js} +1 -1
  57. package/dist/cli/{chunk-124fvmzq.js → chunk-r5nm9w0c.js} +11183 -8806
  58. package/dist/cli/{chunk-124fvmzq.js.map → chunk-r5nm9w0c.js.map} +82 -66
  59. package/dist/cli/{chunk-ckrcm08h.js → chunk-rtvgmg2j.js} +48 -62
  60. package/dist/cli/{chunk-ckrcm08h.js.map → chunk-rtvgmg2j.js.map} +4 -5
  61. package/dist/cli/{chunk-297ajdcv.js → chunk-t2h8pwy1.js} +6 -6
  62. package/dist/cli/{chunk-297ajdcv.js.map → chunk-t2h8pwy1.js.map} +3 -3
  63. package/dist/cli/{chunk-gxtg6ed2.js → chunk-x60e15st.js} +21 -21
  64. package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-x60e15st.js.map} +3 -3
  65. package/dist/cli/{chunk-58bt3hag.js → chunk-x7bh6d9j.js} +4 -4
  66. package/dist/cli/{chunk-hvcxng5f.js → chunk-ydqzkhjn.js} +6 -6
  67. package/dist/cli/{chunk-kdpzw38a.js → chunk-yg8ckc3g.js} +95 -7
  68. package/dist/cli/chunk-yg8ckc3g.js.map +11 -0
  69. package/dist/cli/{chunk-0pmb4tr4.js → chunk-zxd5jrgx.js} +1205 -222
  70. package/dist/cli/chunk-zxd5jrgx.js.map +44 -0
  71. package/dist/cli/index.js +78 -42
  72. package/dist/cli/index.js.map +7 -6
  73. package/dist/types/ai/changelog-components.d.ts +18 -0
  74. package/dist/types/ai/changelog-markdown.d.ts +12 -0
  75. package/dist/types/ai/openapi-components.d.ts +7 -7
  76. package/dist/types/ai/operation-details.d.ts +39 -0
  77. package/dist/types/ai/relative-links.d.ts +3 -1
  78. package/dist/types/ai/serializers.d.ts +6 -4
  79. package/dist/types/analytics/head.d.ts +4 -2
  80. package/dist/types/analytics/script.d.ts +8 -2
  81. package/dist/types/components/content/youtube.d.ts +20 -0
  82. package/dist/types/components/layout/nav-utils.d.ts +8 -0
  83. package/dist/types/components/openapi/example-panels.d.ts +33 -0
  84. package/dist/types/components/openapi/helpers.d.ts +53 -11
  85. package/dist/types/components/openapi/snippets.d.ts +7 -1
  86. package/dist/types/core/alert-diagnostics.d.ts +10 -0
  87. package/dist/types/core/builtin-tags.d.ts +16 -0
  88. package/dist/types/core/changelog-list.d.ts +48 -0
  89. package/dist/types/core/closest-match.d.ts +6 -0
  90. package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
  91. package/dist/types/core/code-fences.d.ts +8 -1
  92. package/dist/types/core/config-input.d.ts +38 -6
  93. package/dist/types/core/content-assets.d.ts +16 -18
  94. package/dist/types/core/data.d.ts +6 -2
  95. package/dist/types/core/diagnostics.d.ts +14 -0
  96. package/dist/types/core/directive-diagnostics.d.ts +22 -8
  97. package/dist/types/core/former-routes.d.ts +14 -0
  98. package/dist/types/core/github.d.ts +5 -4
  99. package/dist/types/core/heading-markers.d.ts +65 -2
  100. package/dist/types/core/hidden-pages.d.ts +3 -0
  101. package/dist/types/core/includes.d.ts +23 -0
  102. package/dist/types/core/last-modified.d.ts +18 -6
  103. package/dist/types/core/links.d.ts +42 -2
  104. package/dist/types/core/load-module.d.ts +19 -4
  105. package/dist/types/core/manifest.d.ts +14 -1
  106. package/dist/types/core/markdown-mirrors.d.ts +22 -0
  107. package/dist/types/core/mcp-clients.d.ts +47 -0
  108. package/dist/types/core/mdx-elements.d.ts +32 -0
  109. package/dist/types/core/mdx-syntax.d.ts +100 -0
  110. package/dist/types/core/meta-diagnostics.d.ts +7 -0
  111. package/dist/types/core/meta.d.ts +32 -5
  112. package/dist/types/core/nav-diagnostics.d.ts +16 -2
  113. package/dist/types/core/navigation.d.ts +13 -0
  114. package/dist/types/core/ordering-prefix.d.ts +6 -0
  115. package/dist/types/core/pinned-ids.d.ts +23 -0
  116. package/dist/types/core/project-graph.d.ts +9 -1
  117. package/dist/types/core/redirect-patterns.d.ts +18 -0
  118. package/dist/types/core/relative-files.d.ts +31 -0
  119. package/dist/types/core/schema.d.ts +322 -11
  120. package/dist/types/core/sources/normalize.d.ts +101 -9
  121. package/dist/types/core/sources/types.d.ts +16 -0
  122. package/dist/types/core/static-files.d.ts +10 -0
  123. package/dist/types/core/static-host.d.ts +42 -0
  124. package/dist/types/core/svg-images.d.ts +26 -0
  125. package/dist/types/core/syntax-diagnostics.d.ts +14 -0
  126. package/dist/types/core/types.d.ts +42 -1
  127. package/dist/types/deploy/generated-files.d.ts +41 -0
  128. package/dist/types/deploy/redirects.d.ts +21 -7
  129. package/dist/types/markdown/code-title.d.ts +9 -5
  130. package/dist/types/markdown/directives.d.ts +8 -0
  131. package/dist/types/markdown/fence-language.d.ts +51 -0
  132. package/dist/types/markdown/fence-meta.d.ts +5 -0
  133. package/dist/types/markdown/github-alerts.d.ts +35 -0
  134. package/dist/types/openapi/asyncapi.d.ts +6 -5
  135. package/dist/types/openapi/checks.d.ts +10 -5
  136. package/dist/types/openapi/model.d.ts +55 -8
  137. package/dist/types/openapi/parse.d.ts +4 -1
  138. package/dist/types/ratelimit/index.d.ts +2 -0
  139. package/dist/types/ratelimit/memory.d.ts +1 -1
  140. package/dist/types/ratelimit/schema.d.ts +40 -0
  141. package/dist/types/ratelimit/unkey.d.ts +48 -0
  142. package/dist/types/sources/filesystem.d.ts +14 -4
  143. package/dist/types/sources/mdx-remote.d.ts +6 -3
  144. package/dist/types/sources/registry.d.ts +3 -3
  145. package/dist/types/theme/color.d.ts +33 -0
  146. package/dist/types/theme/palette.d.ts +122 -0
  147. package/docs/01-quickstart.mdx +1 -1
  148. package/docs/02-deployment.mdx +24 -8
  149. package/docs/04-migrating.mdx +16 -4
  150. package/docs/advanced/changelog.mdx +27 -1
  151. package/docs/advanced/skills.mdx +1 -1
  152. package/docs/cli/audit.mdx +1 -1
  153. package/docs/cli/doctor.mdx +2 -2
  154. package/docs/cli/evals.mdx +1 -1
  155. package/docs/cli/index.mdx +4 -2
  156. package/docs/cli/validate.mdx +10 -7
  157. package/docs/configuration/analytics.mdx +2 -0
  158. package/docs/configuration/customization.mdx +3 -1
  159. package/docs/configuration/index.mdx +7 -6
  160. package/docs/configuration/rate-limiting.mdx +20 -1
  161. package/docs/configuration/theming.mdx +5 -1
  162. package/docs/content/components.mdx +26 -6
  163. package/docs/content/frontmatter.mdx +1 -1
  164. package/docs/content/i18n.mdx +1 -1
  165. package/docs/content/includes.mdx +6 -2
  166. package/docs/content/index.mdx +11 -1
  167. package/docs/content/islands.mdx +2 -0
  168. package/docs/content/meta.mdx +9 -3
  169. package/docs/content/navigation.mdx +14 -6
  170. package/docs/content/sources/obsidian.mdx +1 -1
  171. package/docs/content/sources/remote-mdx.mdx +3 -1
  172. package/docs/content/syntax.mdx +76 -8
  173. package/docs/content/variables.mdx +3 -1
  174. package/docs/discoverability/agent-discovery.mdx +1 -1
  175. package/docs/discoverability/json-api.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/markdown.mdx +2 -2
  178. package/docs/discoverability/mcp.mdx +40 -8
  179. package/docs/discoverability/open-graph.mdx +1 -1
  180. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  181. package/docs/references/graphql.mdx +2 -0
  182. package/docs/references/openapi.mdx +45 -7
  183. package/package.json +4 -4
  184. package/skills/blume/SKILL.md +2 -2
  185. package/skills/blume-migrate/SKILL.md +51 -29
  186. package/skills/blume-migrate/references/docsify.md +262 -0
  187. package/skills/blume-migrate/references/docus.md +298 -0
  188. package/skills/blume-migrate/references/docusaurus.md +5 -5
  189. package/skills/blume-migrate/references/fern.md +327 -0
  190. package/skills/blume-migrate/references/fumadocs.md +1 -1
  191. package/skills/blume-migrate/references/gitbook.md +267 -0
  192. package/skills/blume-migrate/references/github-wiki.md +259 -0
  193. package/skills/blume-migrate/references/jekyll.md +253 -0
  194. package/skills/blume-migrate/references/mdbook.md +256 -0
  195. package/skills/blume-migrate/references/mintlify.md +5 -5
  196. package/skills/blume-migrate/references/mkdocs.md +236 -0
  197. package/skills/blume-migrate/references/monorepo.md +20 -8
  198. package/skills/blume-migrate/references/nextra.md +5 -5
  199. package/skills/blume-migrate/references/readme.md +257 -0
  200. package/skills/blume-migrate/references/redocly.md +198 -0
  201. package/skills/blume-migrate/references/starlight.md +3 -3
  202. package/skills/blume-migrate/references/vitepress.md +204 -0
  203. package/skills/blume-migrate/references/vuepress.md +271 -0
  204. package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
  205. package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
  206. package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
  207. package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
  208. package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
  209. package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
  210. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
  211. package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
  212. package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
  213. package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
  214. package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
  215. package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
  216. package/src/ai/changelog-components.ts +24 -0
  217. package/src/ai/changelog-markdown.ts +45 -41
  218. package/src/ai/component-markdown.ts +22 -9
  219. package/src/ai/llms.ts +36 -5
  220. package/src/ai/markdown.ts +2 -2
  221. package/src/ai/openapi-components.ts +25 -9
  222. package/src/ai/operation-details.ts +208 -0
  223. package/src/ai/relative-links.ts +48 -8
  224. package/src/ai/serializers.ts +7 -6
  225. package/src/ai/site-skill.ts +6 -2
  226. package/src/analytics/head.ts +11 -5
  227. package/src/analytics/script.ts +13 -3
  228. package/src/astro/generate.ts +47 -11
  229. package/src/astro/index.ts +1 -0
  230. package/src/astro/integration.ts +27 -1
  231. package/src/astro/mdx-source-errors.ts +46 -0
  232. package/src/astro/templates.ts +93 -189
  233. package/src/audit/checks/redirects.ts +5 -1
  234. package/src/audit/redirects.ts +36 -19
  235. package/src/audit/report.ts +22 -9
  236. package/src/audit/run.ts +12 -28
  237. package/src/audit/terms.ts +23 -0
  238. package/src/audit/url.ts +2 -2
  239. package/src/cli/build-failure.ts +256 -7
  240. package/src/cli/command-meta.ts +1 -1
  241. package/src/cli/commands/audit.ts +1 -1
  242. package/src/cli/commands/build.ts +12 -3
  243. package/src/cli/commands/check.ts +13 -2
  244. package/src/cli/commands/dev.ts +1 -1
  245. package/src/cli/commands/doctor.ts +4 -2
  246. package/src/cli/commands/init.ts +3 -3
  247. package/src/cli/commands/preview.ts +17 -1
  248. package/src/cli/commands/validate.ts +22 -10
  249. package/src/cli/prepare.ts +1 -1
  250. package/src/cli/preview-pages.ts +161 -0
  251. package/src/cli/required-secrets.ts +0 -1
  252. package/src/cli/unknown-flags.ts +1 -42
  253. package/src/components/code-copy.ts +51 -0
  254. package/src/components/code-language.ts +75 -0
  255. package/src/components/content/AccordionItem.astro +16 -1
  256. package/src/components/content/Callout.astro +11 -1
  257. package/src/components/content/Changelog.astro +189 -0
  258. package/src/components/content/CodeBlock.astro +8 -11
  259. package/src/components/content/Tabs.astro +16 -2
  260. package/src/components/content/YouTube.astro +6 -4
  261. package/src/components/content/code-block.ts +33 -0
  262. package/src/components/content/content-strings.ts +5 -1
  263. package/src/components/content/inline-markdown.ts +35 -8
  264. package/src/components/content/youtube.ts +49 -7
  265. package/src/components/layout/Analytics.astro +5 -1
  266. package/src/components/layout/NavTree.astro +8 -3
  267. package/src/components/layout/PageActions.astro +81 -23
  268. package/src/components/layout/RootLayout.astro +35 -32
  269. package/src/components/layout/SiteFooter.astro +31 -8
  270. package/src/components/layout/nav-utils.ts +14 -0
  271. package/src/components/layout/search/pagefind.ts +2 -1
  272. package/src/components/openapi/Operation.astro +4 -0
  273. package/src/components/openapi/PanelTabs.astro +9 -3
  274. package/src/components/openapi/RequestBody.astro +42 -8
  275. package/src/components/openapi/RequestPanel.astro +34 -53
  276. package/src/components/openapi/example-panels.ts +110 -0
  277. package/src/components/openapi/helpers.ts +195 -20
  278. package/src/components/openapi/operation-model.ts +63 -26
  279. package/src/components/openapi/request.ts +13 -0
  280. package/src/components/openapi/snippets.ts +14 -2
  281. package/src/core/alert-diagnostics.ts +89 -0
  282. package/src/core/builtin-tags.ts +76 -0
  283. package/src/core/changelog-list.ts +87 -0
  284. package/src/core/closest-match.ts +41 -0
  285. package/src/core/code-fence-diagnostics.ts +267 -0
  286. package/src/core/code-fences.ts +43 -7
  287. package/src/core/component-diagnostics.ts +65 -12
  288. package/src/core/config-input.ts +39 -5
  289. package/src/core/config.ts +46 -1
  290. package/src/core/content-assets.ts +64 -144
  291. package/src/core/data.ts +10 -3
  292. package/src/core/diagnostics.ts +36 -3
  293. package/src/core/directive-diagnostics.ts +265 -47
  294. package/src/core/former-routes.ts +46 -0
  295. package/src/core/git-ignored.ts +53 -0
  296. package/src/core/github.ts +5 -4
  297. package/src/core/heading-markers.ts +156 -2
  298. package/src/core/hidden-pages.ts +64 -0
  299. package/src/core/includes.ts +181 -41
  300. package/src/core/last-modified.ts +47 -10
  301. package/src/core/links.ts +438 -83
  302. package/src/core/load-module.ts +98 -9
  303. package/src/core/manifest.ts +12 -9
  304. package/src/core/markdown-mirrors.ts +52 -0
  305. package/src/core/mcp-clients.ts +87 -0
  306. package/src/core/mdx-elements.ts +153 -0
  307. package/src/core/mdx-syntax.ts +377 -0
  308. package/src/core/meta-diagnostics.ts +134 -0
  309. package/src/core/meta.ts +154 -55
  310. package/src/core/nav-diagnostics.ts +41 -4
  311. package/src/core/navigation.ts +70 -10
  312. package/src/core/ordering-prefix.ts +22 -2
  313. package/src/core/pinned-ids.ts +141 -0
  314. package/src/core/project-graph.ts +221 -29
  315. package/src/core/redirect-patterns.ts +41 -0
  316. package/src/core/relative-files.ts +145 -0
  317. package/src/core/schema.ts +110 -6
  318. package/src/core/sources/mdx-remote.ts +75 -14
  319. package/src/core/sources/normalize.ts +885 -179
  320. package/src/core/sources/notion.ts +5 -4
  321. package/src/core/sources/obsidian.ts +2 -5
  322. package/src/core/sources/types.ts +17 -0
  323. package/src/core/static-files.ts +40 -0
  324. package/src/core/static-host.ts +125 -0
  325. package/src/core/svg-images.ts +98 -0
  326. package/src/core/syntax-diagnostics.ts +491 -0
  327. package/src/core/types.ts +43 -1
  328. package/src/deploy/artifacts.ts +2 -28
  329. package/src/deploy/generated-files.ts +128 -0
  330. package/src/deploy/platforms/vercel.ts +25 -1
  331. package/src/deploy/redirects.ts +43 -16
  332. package/src/deploy/sitemap.ts +2 -1
  333. package/src/markdown/base-links.ts +87 -6
  334. package/src/markdown/code-title.ts +29 -13
  335. package/src/markdown/content-assets.ts +210 -0
  336. package/src/markdown/directives.ts +69 -5
  337. package/src/markdown/fence-language.ts +106 -0
  338. package/src/markdown/fence-meta.ts +42 -0
  339. package/src/markdown/github-alerts.ts +172 -0
  340. package/src/markdown/heading-anchors.ts +265 -56
  341. package/src/markdown/include.ts +30 -3
  342. package/src/markdown/index.ts +145 -40
  343. package/src/markdown/prompt-text.ts +95 -0
  344. package/src/markdown/relative-links.ts +37 -2
  345. package/src/markdown/route-snapshot.ts +22 -12
  346. package/src/markdown/svg-fallback.ts +51 -0
  347. package/src/markdown/title-heading.ts +134 -0
  348. package/src/migrate/migrate.ts +127 -2
  349. package/src/og/card.ts +14 -4
  350. package/src/openapi/asyncapi.ts +14 -6
  351. package/src/openapi/checks.ts +160 -15
  352. package/src/openapi/model.ts +172 -33
  353. package/src/openapi/parse.ts +13 -4
  354. package/src/openapi/render-mdx.ts +55 -9
  355. package/src/openapi/source.ts +87 -6
  356. package/src/ratelimit/index.ts +2 -0
  357. package/src/ratelimit/memory.ts +1 -1
  358. package/src/ratelimit/runtime.ts +81 -0
  359. package/src/ratelimit/schema.ts +2 -0
  360. package/src/ratelimit/unkey.ts +61 -0
  361. package/src/registry/eject.ts +0 -1
  362. package/src/registry/registry.ts +1 -1
  363. package/src/search/documents.ts +10 -2
  364. package/src/sources/filesystem.ts +28 -3
  365. package/src/sources/mdx-remote.ts +7 -15
  366. package/src/theme/color.ts +14 -0
  367. package/src/theme/entry.ts +22 -1
  368. package/src/theme/palette.ts +74 -1
  369. package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
  370. package/dist/cli/chunk-0w6hs479.js.map +0 -10
  371. package/dist/cli/chunk-150jk7km.js +0 -52
  372. package/dist/cli/chunk-150jk7km.js.map +0 -10
  373. package/dist/cli/chunk-36bxs9c7.js.map +0 -10
  374. package/dist/cli/chunk-53jqw8z0.js +0 -89
  375. package/dist/cli/chunk-53jqw8z0.js.map +0 -10
  376. package/dist/cli/chunk-60hz265r.js.map +0 -10
  377. package/dist/cli/chunk-desvm72z.js.map +0 -14
  378. package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
  379. package/dist/cli/chunk-es8h0v3j.js +0 -441
  380. package/dist/cli/chunk-es8h0v3j.js.map +0 -11
  381. package/dist/cli/chunk-kdpzw38a.js.map +0 -11
  382. package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
  383. package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
  384. package/dist/cli/chunk-twy7psdz.js.map +0 -10
  385. package/dist/cli/chunk-ydp3065p.js +0 -64
  386. package/dist/cli/chunk-ydp3065p.js.map +0 -10
  387. /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-080mfch6.js.map} +0 -0
  388. /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-34wap3ed.js.map} +0 -0
  389. /package/dist/cli/{chunk-1f882900.js.map → chunk-c5bjxq8v.js.map} +0 -0
  390. /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-f260jdrn.js.map} +0 -0
  391. /package/dist/cli/{chunk-74wfhv79.js.map → chunk-fnxgtjeq.js.map} +0 -0
  392. /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-gxhcf2hs.js.map} +0 -0
  393. /package/dist/cli/{chunk-tfycddbt.js.map → chunk-h0qwdbem.js.map} +0 -0
  394. /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-j4nfgn2j.js.map} +0 -0
  395. /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-qcyxkj44.js.map} +0 -0
  396. /package/dist/cli/{chunk-58bt3hag.js.map → chunk-x7bh6d9j.js.map} +0 -0
  397. /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-ydqzkhjn.js.map} +0 -0
@@ -1,6 +1,7 @@
1
1
  import type { Document, OperationObject } from "@scalar/openapi-types/3.2";
2
2
  import type { GraphqlAuthOptions } from "../reference/options.ts";
3
3
  import type { AsyncApiAction, AsyncApiDocument, AsyncApiSpecValue } from "./asyncapi.ts";
4
+ import type { SpecIssue } from "./checks.ts";
4
5
  import type { GraphqlDocument, GraphqlMember } from "./graphql.ts";
5
6
  import type { ReferenceKind } from "./references.ts";
6
7
  export { slugify } from "./references.ts";
@@ -150,19 +151,48 @@ export declare const withServerDefaults: (template: string, variables: AsyncApiS
150
151
  * silently merges the tags' routes, sidebar groups, and overview sections.
151
152
  * Collisions gain `-2`, `-3`, … in first-seen order. Shared with the AsyncAPI
152
153
  * extractor (`asyncapi.ts`), whose untagged fallback groups are channel
153
- * addresses.
154
+ * addresses. `slug` is the rule before collisions: the collector also runs
155
+ * the one earlier releases used (`slugify` alone, so `InboxesThreads` ->
156
+ * `inboxesthreads`) to redirect the routes they gave.
154
157
  */
155
- export declare const tagSlugger: () => ((name: string) => string);
158
+ export declare const tagSlugger: (slug?: (name: string) => string) => ((name: string) => string);
159
+ /**
160
+ * How a warning names an operation: `GET /pets`, or a webhook by its name
161
+ * (`Webhook "newPet" (POST)`), since a webhook has no path.
162
+ */
163
+ export declare const operationLabel: (name: string, method: string, webhook: boolean) => string;
156
164
  /** An operation before the collector assigns its unique key and route. */
157
165
  type CollectedOperation = Omit<ApiOperationRef, "route" | "tagSlug">;
166
+ /** What a spec's `tags` list says about one tag. */
167
+ export interface ApiTagMeta {
168
+ description: string;
169
+ /**
170
+ * The tag's label, in place of its name, from Redocly's `x-displayName`
171
+ * extension. Only the label changes: the tag's slug, and so its routes,
172
+ * still come from its name.
173
+ */
174
+ displayName?: string;
175
+ }
176
+ /**
177
+ * A route an earlier release gave an operation's page, and the route the
178
+ * page has now: the source turns each into a redirect, so links to the old
179
+ * URL keep working.
180
+ */
181
+ export interface MovedRoute {
182
+ from: string;
183
+ to: string;
184
+ }
158
185
  /** The flattened output both extractors produce. */
159
186
  export interface CollectedOperations {
160
187
  operations: ApiOperationRef[];
161
188
  tags: ApiTagRef[];
189
+ /** Operation routes that moved from what earlier slug rules gave them. */
190
+ moved: MovedRoute[];
162
191
  }
163
192
  /** The collector handle: feed operations in, read the flattened output out. */
164
193
  export interface OperationCollector {
165
- add: (entry: CollectedOperation) => void;
194
+ /** Add an operation, returning it with its key and route assigned. */
195
+ add: (entry: CollectedOperation) => ApiOperationRef;
166
196
  finish: () => CollectedOperations;
167
197
  }
168
198
  /** {@link CollectedOperations} plus anything the extractor had to skip. */
@@ -175,15 +205,32 @@ export interface ExtractedOperations extends CollectedOperations {
175
205
  * (a repeated key gains its method/action as a suffix), and the shared route
176
206
  * template — so URL shape and slug rules can never drift between the two spec
177
207
  * kinds.
208
+ *
209
+ * A key is unique across the spec, since it names the operation in
210
+ * `<Operation id>`. Its page's URL only has to be unique within its tag's
211
+ * folder, so the last route segment de-duplicates per tag: `list` in two
212
+ * tags is `/pets/list` and `/stores/list`, not `/stores/list-get`. Earlier
213
+ * releases de-duplicated the route like the key, and slugged tags without
214
+ * splitting their words; each route those rules gave differently is
215
+ * reported in `moved`.
178
216
  */
179
- export declare const operationCollector: (baseRoute: string, tagMeta: ReadonlyMap<string, string>) => OperationCollector;
217
+ export declare const operationCollector: (baseRoute: string, tagMeta: ReadonlyMap<string, ApiTagMeta>) => OperationCollector;
218
+ /** {@link ExtractedOperations} plus the spec mistakes extraction noticed. */
219
+ export interface ExtractedApiOperations extends ExtractedOperations {
220
+ issues: SpecIssue[];
221
+ }
180
222
  /**
181
223
  * Flatten a 3.1 document into a route-mapped operation list and its ordered
182
224
  * tags, webhooks included. Operations inherit the first tag they declare; keys
183
- * are de-duplicated so a repeated `operationId` still yields distinct routes.
184
- * `warnings` reports anything skipped (a `$ref` path item), so missing
185
- * operations aren't silent.
225
+ * are de-duplicated so a repeated `operationId` still yields distinct routes,
226
+ * with an issue naming both operations. `warnings` reports anything skipped (a
227
+ * `$ref` path item), so missing operations aren't silent.
228
+ *
229
+ * Operations come out in the spec's order: paths as listed, and each path's
230
+ * methods as written. They're keyed in the method order earlier releases
231
+ * walked (`HTTP_METHODS`), so a repeated id resolves to the same key, and
232
+ * the same route, it always did.
186
233
  */
187
- export declare const extractOperations: (document: ApiDocument, baseRoute: string) => ExtractedOperations;
234
+ export declare const extractOperations: (document: ApiDocument, baseRoute: string) => ExtractedApiOperations;
188
235
  /** Resolve the operation object for a ref out of its (OpenAPI) document. */
189
236
  export declare const operationObject: (spec: ApiSpecData, ref: ApiOperationRef) => OperationObject | undefined;
@@ -4,7 +4,10 @@ import type { GraphqlDocument } from "./graphql.ts";
4
4
  import type { ApiDocument } from "./model.ts";
5
5
  export interface ParsedSpec {
6
6
  document: ApiDocument;
7
- /** `x-codeSamples` entries whose `$ref` couldn't be inlined. */
7
+ /**
8
+ * Issues only the spec as written shows: `nullable` in a 3.1 spec, and
9
+ * `x-codeSamples` entries whose `$ref` couldn't be inlined.
10
+ */
8
11
  issues: SpecIssue[];
9
12
  warnings: string[];
10
13
  }
@@ -25,3 +25,5 @@ export type { MemoryAdapter, RateLimitOptions } from "./memory.ts";
25
25
  export type { RateLimitAdapter } from "./schema.ts";
26
26
  export { upstash } from "./upstash.ts";
27
27
  export type { UpstashAdapter, UpstashOptions } from "./upstash.ts";
28
+ export { unkey } from "./unkey.ts";
29
+ export type { UnkeyAdapter, UnkeyOptions } from "./unkey.ts";
@@ -31,6 +31,6 @@ export declare const memoryAdapterSchema: z.ZodObject<{
31
31
  * platform that runs many short-lived instances (Vercel, Netlify,
32
32
  * Cloudflare) each instance keeps its own, so it stops a burst from one
33
33
  * reader rather than enforcing the limit exactly. For that, share the count
34
- * with `upstash()` or `cloudflare()`.
34
+ * with `upstash()`, `cloudflare()`, or `unkey()`.
35
35
  */
36
36
  export declare const memory: (options?: RateLimitOptions) => MemoryAdapter;
@@ -27,6 +27,16 @@ export declare const rateLimitAdapterSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
27
27
  }, z.core.$strict>;
28
28
  requiredSecrets: z.ZodArray<z.ZodString>;
29
29
  runtimeDeps: z.ZodArray<z.ZodString>;
30
+ }, z.core.$strict>, z.ZodObject<{
31
+ kind: z.ZodLiteral<"unkey">;
32
+ options: z.ZodObject<{
33
+ requests: z.ZodOptional<z.ZodNumber>;
34
+ namespace: z.ZodOptional<z.ZodString>;
35
+ rootKeyEnv: z.ZodOptional<z.ZodString>;
36
+ window: z.ZodOptional<z.ZodNumber>;
37
+ }, z.core.$strict>;
38
+ requiredSecrets: z.ZodArray<z.ZodString>;
39
+ runtimeDeps: z.ZodArray<z.ZodString>;
30
40
  }, z.core.$strict>], "kind">;
31
41
  export type RateLimitAdapter = z.output<typeof rateLimitAdapterSchema>;
32
42
  /**
@@ -62,6 +72,16 @@ export declare const rateLimitConfigSchema: z.ZodPipe<z.ZodOptional<z.ZodUnion<r
62
72
  }, z.core.$strict>;
63
73
  requiredSecrets: z.ZodArray<z.ZodString>;
64
74
  runtimeDeps: z.ZodArray<z.ZodString>;
75
+ }, z.core.$strict>, z.ZodObject<{
76
+ kind: z.ZodLiteral<"unkey">;
77
+ options: z.ZodObject<{
78
+ requests: z.ZodOptional<z.ZodNumber>;
79
+ namespace: z.ZodOptional<z.ZodString>;
80
+ rootKeyEnv: z.ZodOptional<z.ZodString>;
81
+ window: z.ZodOptional<z.ZodNumber>;
82
+ }, z.core.$strict>;
83
+ requiredSecrets: z.ZodArray<z.ZodString>;
84
+ runtimeDeps: z.ZodArray<z.ZodString>;
65
85
  }, z.core.$strict>], "kind">]>>, z.ZodTransform<{
66
86
  kind: "cloudflare";
67
87
  options: {
@@ -89,6 +109,16 @@ export declare const rateLimitConfigSchema: z.ZodPipe<z.ZodOptional<z.ZodUnion<r
89
109
  };
90
110
  requiredSecrets: string[];
91
111
  runtimeDeps: string[];
112
+ } | {
113
+ kind: "unkey";
114
+ options: {
115
+ requests?: number | undefined;
116
+ namespace?: string | undefined;
117
+ rootKeyEnv?: string | undefined;
118
+ window?: number | undefined;
119
+ };
120
+ requiredSecrets: string[];
121
+ runtimeDeps: string[];
92
122
  } | null, false | {
93
123
  kind: "cloudflare";
94
124
  options: {
@@ -116,4 +146,14 @@ export declare const rateLimitConfigSchema: z.ZodPipe<z.ZodOptional<z.ZodUnion<r
116
146
  };
117
147
  requiredSecrets: string[];
118
148
  runtimeDeps: string[];
149
+ } | {
150
+ kind: "unkey";
151
+ options: {
152
+ requests?: number | undefined;
153
+ namespace?: string | undefined;
154
+ rootKeyEnv?: string | undefined;
155
+ window?: number | undefined;
156
+ };
157
+ requiredSecrets: string[];
158
+ runtimeDeps: string[];
119
159
  } | undefined>>;
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+ import type { AdapterDescriptor } from "../core/adapter.ts";
3
+ import type { RateLimitOptions } from "./memory.ts";
4
+ /** The Unkey rate limit namespace the count is kept in, unless set. */
5
+ export declare const UNKEY_NAMESPACE = "docs";
6
+ /** The longest window Unkey takes, in seconds (30 days). */
7
+ export declare const UNKEY_MAX_WINDOW = 2592000;
8
+ /** Options for {@link unkey}. */
9
+ export interface UnkeyOptions extends RateLimitOptions {
10
+ /**
11
+ * The Unkey rate limit namespace, up to 512 characters. Defaults to `docs`;
12
+ * Unkey creates it on the first request.
13
+ */
14
+ namespace?: string;
15
+ /** Name of the env var holding the root key. Defaults to `UNKEY_ROOT_KEY`. */
16
+ rootKeyEnv?: string;
17
+ /** The window, in seconds: Unkey takes up to 30 days. */
18
+ window?: number;
19
+ }
20
+ export declare const unkeyOptionsSchema: z.ZodObject<{
21
+ requests: z.ZodOptional<z.ZodNumber>;
22
+ namespace: z.ZodOptional<z.ZodString>;
23
+ rootKeyEnv: z.ZodOptional<z.ZodString>;
24
+ window: z.ZodOptional<z.ZodNumber>;
25
+ }, z.core.$strict>;
26
+ export type UnkeyAdapter = AdapterDescriptor<"unkey", UnkeyOptions>;
27
+ export declare const unkeyAdapterSchema: z.ZodObject<{
28
+ kind: z.ZodLiteral<"unkey">;
29
+ options: z.ZodObject<{
30
+ requests: z.ZodOptional<z.ZodNumber>;
31
+ namespace: z.ZodOptional<z.ZodString>;
32
+ rootKeyEnv: z.ZodOptional<z.ZodString>;
33
+ window: z.ZodOptional<z.ZodNumber>;
34
+ }, z.core.$strict>;
35
+ requiredSecrets: z.ZodArray<z.ZodString>;
36
+ runtimeDeps: z.ZodArray<z.ZodString>;
37
+ }, z.core.$strict>;
38
+ /** The env var the root key is read from. */
39
+ export declare const unkeySecrets: (options: UnkeyOptions) => [string];
40
+ /**
41
+ * Counts with Unkey's rate limiting, which every instance on every host
42
+ * shares. Unkey counts in each region and shares the counts between them
43
+ * within moments, so a short burst split across regions can pass more than
44
+ * the limit. It talks to Unkey's REST API, so there is nothing to install;
45
+ * the root key comes from `UNKEY_ROOT_KEY`, or the env var `rootKeyEnv`
46
+ * names.
47
+ */
48
+ export declare const unkey: (options?: UnkeyOptions) => UnkeyAdapter;
@@ -3,11 +3,21 @@ import type { AdapterDescriptor } from "../core/adapter.ts";
3
3
  import type { SharedSourceOptions } from "./shared.ts";
4
4
  /** What `include` defaults to: every Markdown/MDX file under the root. */
5
5
  export declare const DEFAULT_CONTENT_INCLUDE: string[];
6
- /** What `exclude` defaults to: underscore- and dot-prefixed paths. */
6
+ /** What `exclude` always holds: underscore- and dot-prefixed paths. */
7
7
  export declare const DEFAULT_CONTENT_EXCLUDE: string[];
8
+ /**
9
+ * A source's `exclude` as the scan applies it: the defaults, plus the
10
+ * author's patterns. A `!pattern` entry takes that exact pattern back out, a
11
+ * default included, so `["!**\/_*"]` publishes `_`-prefixed files. The glob
12
+ * library can't negate an ignore pattern itself.
13
+ */
14
+ export declare const resolveContentExclude: (patterns: readonly string[]) => string[];
8
15
  /** Options for {@link filesystem}. */
9
16
  export interface FilesystemOptions extends SharedSourceOptions {
10
- /** Glob patterns to ignore. Defaults to `["**\/_*", "**\/.*"]`. */
17
+ /**
18
+ * Glob patterns to ignore, on top of `["**\/_*", "**\/.*"]`. A `!pattern`
19
+ * entry stops ignoring that pattern, a default included.
20
+ */
11
21
  exclude?: string[];
12
22
  /** Glob patterns to include. Defaults to `["**\/*.{md,mdx}"]`. */
13
23
  include?: string[];
@@ -17,7 +27,7 @@ export interface FilesystemOptions extends SharedSourceOptions {
17
27
  export declare const filesystemOptionsSchema: z.ZodObject<{
18
28
  pollInterval: z.ZodOptional<z.ZodNumber>;
19
29
  prefix: z.ZodOptional<z.ZodString>;
20
- exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
30
+ exclude: z.ZodPipe<z.ZodDefault<z.ZodArray<z.ZodString>>, z.ZodTransform<string[], string[]>>;
21
31
  include: z.ZodDefault<z.ZodArray<z.ZodString>>;
22
32
  root: z.ZodDefault<z.ZodString>;
23
33
  }, z.core.$strict>;
@@ -27,7 +37,7 @@ export declare const filesystemAdapterSchema: z.ZodObject<{
27
37
  options: z.ZodObject<{
28
38
  pollInterval: z.ZodOptional<z.ZodNumber>;
29
39
  prefix: z.ZodOptional<z.ZodString>;
30
- exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
40
+ exclude: z.ZodPipe<z.ZodDefault<z.ZodArray<z.ZodString>>, z.ZodTransform<string[], string[]>>;
31
41
  include: z.ZodDefault<z.ZodArray<z.ZodString>>;
32
42
  root: z.ZodDefault<z.ZodString>;
33
43
  }, z.core.$strict>;
@@ -56,8 +56,11 @@ export declare const mdxRemoteAdapterSchema: z.ZodObject<{
56
56
  /**
57
57
  * Remote Markdown/MDX fetched over HTTP. Enumerate files explicitly against a
58
58
  * raw `url` base, or from a GitHub repo subtree via `github`. A private repo's
59
- * token comes from `GITHUB_TOKEN` — never inline it here. The adapter declares
60
- * the variable only when it reads from GitHub, since a `url` on any other host
61
- * is never sent the token.
59
+ * token comes from `GITHUB_TOKEN` — never inline it here. Only `github`
60
+ * declares the variable: it lists files through GitHub's API, whose
61
+ * unauthenticated rate limit a build can run into even for a public
62
+ * repository. A raw `url` needs no token for a public repository, so a fetch
63
+ * that fails for want of one names the variable instead (see
64
+ * core/sources/mdx-remote.ts).
62
65
  */
63
66
  export declare const mdxRemote: (options: MdxRemoteOptions) => MdxRemoteAdapter;
@@ -18,7 +18,7 @@ declare const sourceAdapterUnion: z.ZodDiscriminatedUnion<readonly [z.ZodObject<
18
18
  options: z.ZodObject<{
19
19
  pollInterval: z.ZodOptional<z.ZodNumber>;
20
20
  prefix: z.ZodOptional<z.ZodString>;
21
- exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
21
+ exclude: z.ZodPipe<z.ZodDefault<z.ZodArray<z.ZodString>>, z.ZodTransform<string[], string[]>>;
22
22
  include: z.ZodDefault<z.ZodArray<z.ZodString>>;
23
23
  root: z.ZodDefault<z.ZodString>;
24
24
  }, z.core.$strict>;
@@ -193,7 +193,7 @@ export declare const resolvedSourceAdapterSchema: z.ZodPipe<z.ZodDiscriminatedUn
193
193
  options: z.ZodObject<{
194
194
  pollInterval: z.ZodOptional<z.ZodNumber>;
195
195
  prefix: z.ZodOptional<z.ZodString>;
196
- exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
196
+ exclude: z.ZodPipe<z.ZodDefault<z.ZodArray<z.ZodString>>, z.ZodTransform<string[], string[]>>;
197
197
  include: z.ZodDefault<z.ZodArray<z.ZodString>>;
198
198
  root: z.ZodDefault<z.ZodString>;
199
199
  }, z.core.$strict>;
@@ -998,7 +998,7 @@ export declare const contentSourcesSchema: z.ZodArray<z.ZodPipe<z.ZodCustom<{
998
998
  options: z.ZodObject<{
999
999
  pollInterval: z.ZodOptional<z.ZodNumber>;
1000
1000
  prefix: z.ZodOptional<z.ZodString>;
1001
- exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
1001
+ exclude: z.ZodPipe<z.ZodDefault<z.ZodArray<z.ZodString>>, z.ZodTransform<string[], string[]>>;
1002
1002
  include: z.ZodDefault<z.ZodArray<z.ZodString>>;
1003
1003
  root: z.ZodDefault<z.ZodString>;
1004
1004
  }, z.core.$strict>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Just enough CSS color math to check contrast at build time: parse the color
3
+ * syntaxes a theme config takes (hex, `rgb()`, `hsl()`, `hwb()`, `lab()`,
4
+ * `lch()`, `oklab()`, `oklch()`, and named colors) to sRGB, then measure WCAG
5
+ * 2 relative luminance and contrast. A color outside sRGB is clipped to it,
6
+ * which is what a browser shows on an sRGB display. Anything else — `var()`,
7
+ * `color-mix()`, `color()` — parses to `null`, and the caller says it couldn't
8
+ * check it rather than guessing.
9
+ */
10
+ /** An sRGB color: gamma-encoded channels and alpha, each 0–1. */
11
+ export interface Rgba {
12
+ alpha: number;
13
+ blue: number;
14
+ green: number;
15
+ red: number;
16
+ }
17
+ /** Parse a CSS color to sRGB, or `null` for syntax this module doesn't read. */
18
+ export declare const parseColor: (value: string) => Rgba | null;
19
+ /**
20
+ * Whether `value` is a CSS color: one `parseColor` reads, or a color function
21
+ * or keyword it leaves unread. Words that name no color (`deep purple`), and
22
+ * hex values with a wrong digit count or no `#`, are not.
23
+ */
24
+ export declare const isCssColor: (value: string) => boolean;
25
+ /** `color` painted over an opaque `backdrop`. */
26
+ export declare const composite: (color: Rgba, backdrop: Rgba) => Rgba;
27
+ /** WCAG 2 relative luminance of an opaque color. */
28
+ export declare const luminance: (color: Rgba) => number;
29
+ /**
30
+ * WCAG 2 contrast ratio of `foreground` on an opaque `background`, from 1 to
31
+ * 21. A translucent foreground is painted over the background first.
32
+ */
33
+ export declare const contrastRatio: (foreground: Rgba, background: Rgba) => number;
@@ -0,0 +1,122 @@
1
+ import type { ResolvedConfig } from "../core/schema.ts";
2
+ import type { Rgba } from "./color.ts";
3
+ /** Per-mode CSS colors. */
4
+ export interface AccentColors {
5
+ dark: string;
6
+ light: string;
7
+ }
8
+ /** A color mode. */
9
+ export type ColorMode = keyof AccentColors;
10
+ /**
11
+ * Named accent presets mapped to OKLCH values, per mode. The single source of
12
+ * truth for preset colors: the theme CSS and the OG card (og/card.ts) both
13
+ * resolve from this table, so a site and its social cards can't disagree
14
+ * about "blue". Each clears WCAG AA (4.5:1) as text on its mode's page and
15
+ * under its button label ({@link readableOn}), with room to spare on the
16
+ * tinted surfaces accent text also sits on: the light shades are dark enough
17
+ * for white labels, the dark shades light enough to read on the dark page.
18
+ */
19
+ export declare const ACCENTS: {
20
+ blue: {
21
+ dark: string;
22
+ light: string;
23
+ };
24
+ green: {
25
+ dark: string;
26
+ light: string;
27
+ };
28
+ orange: {
29
+ dark: string;
30
+ light: string;
31
+ };
32
+ pink: {
33
+ dark: string;
34
+ light: string;
35
+ };
36
+ purple: {
37
+ dark: string;
38
+ light: string;
39
+ };
40
+ red: {
41
+ dark: string;
42
+ light: string;
43
+ };
44
+ teal: {
45
+ dark: string;
46
+ light: string;
47
+ };
48
+ };
49
+ /** The contrast WCAG AA asks of body-size text. */
50
+ export declare const MIN_TEXT_CONTRAST = 4.5;
51
+ /**
52
+ * The theme's own colors a configured accent is drawn against: each mode's
53
+ * page background and text, and the dark ink a filled accent can carry.
54
+ * Mirrors the defaults in `entry.ts`.
55
+ */
56
+ export declare const THEME_INKS: {
57
+ dark: {
58
+ background: string;
59
+ foreground: string;
60
+ ink: string;
61
+ mutedForeground: string;
62
+ };
63
+ light: {
64
+ background: string;
65
+ foreground: string;
66
+ ink: string;
67
+ mutedForeground: string;
68
+ };
69
+ };
70
+ /** {@link THEME_INKS}, parsed, for contrast math. */
71
+ export declare const themeInk: (mode: ColorMode, name: keyof (typeof THEME_INKS)[ColorMode]) => Rgba;
72
+ /**
73
+ * The label color for text on a filled `fill` (a button, a step number): white
74
+ * while it reaches AA, else the mode's dark ink when that reads better, so a
75
+ * light accent (every dark-mode preset) gets dark text instead of white text
76
+ * nobody can read.
77
+ */
78
+ export declare const labelOn: (fill: Rgba, mode: ColorMode) => string;
79
+ /** {@link labelOn} for a CSS color; one this module can't parse keeps white. */
80
+ export declare const readableOn: (fill: string, mode: ColorMode) => string;
81
+ /**
82
+ * Whether a raw config value names an accent preset. `hasOwn` keeps a value
83
+ * like "constructor" from resolving an Object.prototype member — which would
84
+ * stringify a function into the generated CSS, breaking the rule (the exact
85
+ * breakout {@link safeColor} exists to prevent).
86
+ */
87
+ export declare const isAccentPreset: (value: string) => value is keyof typeof ACCENTS;
88
+ /**
89
+ * Resolve the configured accent to per-mode CSS colors. A named accent
90
+ * resolves to its preset's shade for each mode; any other value is treated as
91
+ * a raw CSS color so users can pass arbitrary colors without a config change.
92
+ * A string accent has already been normalized by the config schema to the
93
+ * same value for both modes.
94
+ */
95
+ export declare const resolveAccent: (theme: ResolvedConfig["theme"]) => AccentColors;
96
+ /** Resolve the configured `action` fill per mode, or `null` when unset. */
97
+ export declare const resolveAction: (theme: ResolvedConfig["theme"]) => AccentColors | null;
98
+ /** Resolve the configured radius preset to a CSS length. */
99
+ export declare const resolveRadius: (theme: ResolvedConfig["theme"]) => string;
100
+ /**
101
+ * Compile theme config into CSS custom properties. A named accent resolves to
102
+ * its preset; any other value is treated as a raw CSS color so users can pass
103
+ * arbitrary colors without a config change. Labels on accent and action fills
104
+ * take whichever of white or dark ink reads ({@link readableOn}).
105
+ */
106
+ export declare const buildThemeCss: (theme: ResolvedConfig["theme"]) => string;
107
+ /** A configured color: where the config sets it, and its value. */
108
+ export interface ColorSetting {
109
+ /** The config path, e.g. `["theme", "accent", "dark"]`. */
110
+ path: string[];
111
+ /** Whether the field also takes an accent preset name (`teal`). */
112
+ presets: boolean;
113
+ value: string;
114
+ }
115
+ /**
116
+ * The configured colors that aren't CSS colors (`"deep purple"`, a hex value
117
+ * missing its `#`): `theme.accent`, `theme.action`, and `theme.background`,
118
+ * and the OG card's `seo.og.palette`. The schema takes any string, so a
119
+ * mistake would otherwise reach the stylesheet, where browsers drop it, or
120
+ * the card renderer, which rejects it. Preset names are CSS color names too.
121
+ */
122
+ export declare const invalidColorSettings: (config: ResolvedConfig) => ColorSetting[];
@@ -6,7 +6,7 @@ sidebar:
6
6
  order: 1
7
7
  ---
8
8
 
9
- Go from an empty folder to a running docs site in a few commands. Blume needs **Node.js 22.12 or newer** and a `docs/` folder with at least one `.md` or `.mdx` file — there's nothing else to set up.
9
+ Go from an empty folder to a running docs site in a few commands. Blume needs **Node.js 22.19 or newer** and a `docs/` folder with at least one `.md` or `.mdx` file — there's nothing else to set up.
10
10
 
11
11
  ## Install and run
12
12
 
@@ -14,7 +14,7 @@ sidebar:
14
14
  | ---------------- | -------------- |
15
15
  | Build command | `blume build` |
16
16
  | Output directory | `dist` |
17
- | Node version | 22.12 or newer |
17
+ | Node version | 22.19 or newer |
18
18
 
19
19
  These settings work on Vercel, Netlify, Cloudflare Pages, GitHub Pages, or any bucket or CDN. Make sure `blume` is a dependency so the host can run the build.
20
20
 
@@ -68,9 +68,16 @@ blume preview
68
68
 
69
69
  `blume preview` serves static builds and `node()` and `cloudflare()` server builds. The Vercel and Netlify adapters have no local preview server, so after a `vercel()` or `netlify()` server build it stops with an error instead. Try the site with `blume dev`, or deploy a preview with `vercel deploy` or `netlify deploy`.
70
70
 
71
+ On a static build, `blume preview` also answers what the host would, beyond the files in `dist/`. It applies the [redirects](#redirects), patterns included, with their configured status, as the host files the build writes do. It also handles trailing slashes the way static hosts do:
72
+
73
+ - A page's URL with a trailing slash (`/guide/`) redirects to the page (`/guide`), keeping the query string.
74
+ - A folder of HTML you ship in `public/` (`public/demo/index.html`) is served at `/demo/`, and `/demo` redirects there, so the relative links inside it resolve.
75
+
76
+ `blume dev` handles trailing slashes the same way.
77
+
71
78
  ## Subpath deploys
72
79
 
73
- Serving docs under a path like `example.com/docs`? Set `base` — common for GitHub Pages project sites. The whole site, root included, moves under the base, and internal links and assets are rewritten to include it.
80
+ Serving docs under a path like `example.com/docs`? Set `base` — common for GitHub Pages project sites. The whole site, root included, moves under the base, and internal links and assets are rewritten to include it, along with a root-relative `href` or `src` in raw HTML (`<a href="/guide">`, `<img src="/logo.png">`).
74
81
 
75
82
  ```ts blume.config.ts lineNumbers
76
83
  deployment: {
@@ -153,6 +160,8 @@ deployment: netlify({ output: "static" }),
153
160
 
154
161
  A server build includes everything a static build does, plus any Astro endpoints or middleware you add. The `node()` adapter produces a standalone server you can run directly with `node dist/server/entry.mjs`. It listens on `localhost:4321` unless you set the `HOST` and `PORT` environment variables when you start it (`HOST=0.0.0.0 PORT=8080 node dist/server/entry.mjs`); `host` and `port` passed to `node()` have no effect, because `@astrojs/node` replaces them with Astro's own server settings. Behind a reverse proxy, set `allowedDomains` so the [rate limit](/docs/configuration/rate-limiting#behind-a-reverse-proxy) counts each reader apart. The server resolves its packages through links into your project's `node_modules`, so deploy the project with its installed dependencies, not `dist/` alone. When the site publishes discovery files, serves images a content source downloaded, or configures redirects, Blume puts a small wrapper in front of Astro's entry (moved to `astro-entry.mjs` beside it). It sends the `.well-known` discovery files with their media types and CORS headers and downloaded SVGs sandboxed, which the standalone server's static handler can't do on its own, and answers each redirect with its configured status.
155
162
 
163
+ A `vercel()` server build checks the function bundle the adapter traced for packages it imports but didn't copy, which would crash the function on Vercel. A missing import of your own is a warning. A missing Blume dependency stops the build, and the build removes `.vercel/output/config.json`, so `vercel deploy --prebuilt` can't ship the output without the routes Blume adds to it.
164
+
156
165
  A `cloudflare()` server build runs on Cloudflare Workers only, since `@astrojs/cloudflare` dropped Cloudflare Pages in version 13; a static build doesn't load the package, so its `dist/` still deploys to Pages. The server build deploys with `npx wrangler deploy` from the project root after `blume build`: Blume writes the redirected Wrangler config to `.wrangler/deploy/` (and adds `.wrangler/` to `.gitignore`), and names the Worker after your project — the `package.json` `name`, else the site's hostname, else the folder — unless a `wrangler.jsonc` of your own at the project root sets `name`.
157
166
 
158
167
  On Vercel and Cloudflare, a server build also turns on [`Accept: text/markdown` content negotiation](/docs/discoverability/markdown#content-negotiation), so an agent requesting any content page with that header receives its raw-Markdown mirror at the same URL. On Vercel, Blume splices header-conditional rewrites into the deploy's routing config; on Cloudflare, it generates a small Worker in front of the Astro one and points `assets.run_worker_first` at every path except the fingerprinted build assets and the raw `.md`, `.txt`, `.json`, and `.well-known` files, since the platform would otherwise serve the prerendered pages — and its `404.html` for a URL no page backs — before any server code runs; those exempted files keep their zero-Worker fast path. That Worker also answers the prerendered per-page JSON documents (`/api/docs/pages/{route}.json`) from the asset binding when a request for one reaches it, because Astro would otherwise route it to the `/api/` catch-all.
@@ -190,9 +199,13 @@ Map old URLs to new ones in `blume.config.ts`:
190
199
  redirects: [{ from: "/old", to: "/new", status: 301 }],
191
200
  ```
192
201
 
193
- `status` accepts `301`, `302`, `307`, or `308` (default `301`). Server builds answer redirects at request time with the configured status, and so does `blume dev`. Static builds emit redirect pages **and** the platform files your host reads, so it issues a real HTTP redirect: `_redirects` for `netlify()` and `cloudflare()`, `vercel.json` for `vercel()`, and — when no host is named — both of those plus `blume-redirects.json`, a structured manifest for anything else (nginx/Apache rules, an edge worker). A `_redirects` or `vercel.json` you ship in `public/` is left untouched.
202
+ `status` accepts `301`, `302`, `307`, or `308` (default `301`). Server builds answer redirects at request time with the configured status, and so do `blume dev` and `blume preview`. Static builds emit redirect pages **and** the platform files your host reads, so it issues a real HTTP redirect: `_redirects` for `netlify()` and `cloudflare()`, `vercel.json` for `vercel()`, and — when no host is named — both of those plus `blume-redirects.json`, a structured manifest for anything else (nginx/Apache rules, an edge worker). A `_redirects` or `vercel.json` you ship in `public/` is left untouched.
194
203
 
195
- A redirect that moves one page to another moves the page's [raw Markdown](/docs/discoverability/markdown#raw-markdown) with it: `/old.md` and `/old.mdx` redirect to `/new.md` and `/new.mdx` with the same status, in `blume dev` and every build.
204
+ A redirect that moves one page to another moves the page's [raw Markdown](/docs/discoverability/markdown#raw-markdown) with it: `/old.md` and `/old.mdx` redirect to `/new.md` and `/new.mdx` with the same status, in `blume dev` and every build. A redirect from an `.html` URL, like `/old.html` from a site you migrated, doesn't, since that URL never had a Markdown copy. Nor does one whose Markdown URL already serves a copy: `/index.md` is the home page's, so a redirect from `/index` to `/` leaves it alone.
205
+
206
+ A redirect from a page's own `index.html` URL, like `/guide/index.html` to `/guide`, gets no redirect page in a static build, because the page itself is written to that file. Hosts that read the redirect files answer it with the redirect. Any other host serves the page at that URL.
207
+
208
+ Blume adds one kind of redirect itself: a date-named page that an earlier version published under a shorter URL, like `changelog/12-05-2022.mdx` at `/changelog/05-2022`, redirects from that URL to its own (see [ordering with numeric prefixes](/docs/content#ordering-with-numeric-prefixes)). It adds none where another page lives at the old URL or a redirect you configured already starts there.
196
209
 
197
210
  Two hosts need more than the file:
198
211
 
@@ -217,14 +230,16 @@ redirects: [
217
230
  ```
218
231
 
219
232
  - `:name` matches one whole segment: `/blog/:slug` covers `/blog/hello`, not `/blog/a/b`.
220
- - `:name*` as the last segment matches the rest of the path, any number of segments: `/beta/:slug*` sends `/beta/guides/setup` to `/v2/guides/setup`, and `/beta` itself to `/v2`. A last segment of `*` does the same.
233
+ - `:name*` as the last segment matches the rest of the path, any number of segments: `/beta/:slug*` sends `/beta/guides/setup` to `/v2/guides/setup`, and `/beta` itself to `/v2`. A last segment of `*` does the same. Every host gets that bare path as a rule of its own (`/beta /v2` in `_redirects`), so all of them send it to the same place. `blume audit` checks where it lands, and reports `/beta` going to a `/v2` the build doesn't serve as a broken redirect.
221
234
  - `*` at the end of a segment matches the rest of the path from there: `/articles/concepts-*` covers `/articles/concepts-overview`.
222
235
 
223
236
  `to` reads a capture by name, as a whole segment (`/:slug*`, `/:slug`), or a `*` for what the `*` in `from` matched (`:splat`, the way `_redirects` spells it, reads it too). A `to` that reads no capture sends every path the pattern covers to one page. An exact redirect wins over a pattern that covers the same path, and patterns are tried in the order you list them.
224
237
 
225
- Each host gets the patterns in its own syntax: splats in `_redirects`, `path-to-regexp` sources in `vercel.json`, and the routing config of a `vercel()` or `netlify()` server build. The `node()` and `cloudflare()` servers match them themselves, and so does `blume dev`. A static build writes no redirect page for a pattern, since it would need one for every path the pattern covers, so a host that reads none of these files (GitHub Pages, say) applies only the exact redirects; `blume-redirects.json` lists patterns as you wrote them.
238
+ Each host gets the patterns in its own syntax: splats in `_redirects`, `path-to-regexp` sources in `vercel.json`, and the routing config of a `vercel()` or `netlify()` server build. The `node()` and `cloudflare()` servers match them themselves, and so do `blume dev` and `blume preview`. A static build writes no redirect page for a pattern, since it would need one for every path the pattern covers, so a host that reads none of these files (GitHub Pages, say) applies only the exact redirects; `blume-redirects.json` lists patterns as you wrote them.
239
+
240
+ [`blume validate`](/docs/cli/validate) checks where each redirect leads. An exact `to` must be a page, a file in `public/` or one Blume generates, or another redirect's `from`; for a pattern, something must be served under the literal part of `to` before its first capture (`/v2/` in `/v2/:slug*`). One that leads nowhere warns `BLUME_BROKEN_REDIRECT` at its `to`. [`blume audit`](/docs/cli/audit) follows every redirect through the built site.
226
241
 
227
- A pattern can't also match a page: hosts disagree on which of the two answers, so the build stops with `BLUME_REDIRECT_MATCHES_PAGE`, naming the page. Narrow the pattern to the paths that moved. An exact redirect from a page's own URL answers the same way on every host, taking the URL over so the page never publishes, so the same code is a warning there: delete the page if it moved, or drop the redirect.
242
+ A pattern can't also match a page: hosts disagree on which of the two answers, so the build stops with `BLUME_REDIRECT_MATCHES_PAGE`, naming the page. Narrow the pattern to the paths that moved. An exact redirect from a page's own URL answers the same way on every host, taking the URL over so the page never publishes, so the same code is a warning there: delete the page if it moved, or drop the redirect. So is one from a page's Markdown copy, like `/guide.md` or the home page's `/index.md`, which takes that copy away from agents: drop the redirect.
228
243
 
229
244
  ## Content types
230
245
 
@@ -252,9 +267,10 @@ When a feature needs a secret, Blume warns at `blume dev`/`build` if it's missin
252
267
  | Assistant (other adapters) | the adapter's default key env var (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`, `OPENROUTER_API_KEY`, `LLMGATEWAY_API_KEY`, `INKEEP_API_KEY`), or the `apiKeyEnv` you passed it |
253
268
  | [Assistant bot protection](/docs/configuration/assistant#bot-protection) | `TURNSTILE_SECRET_KEY` (`turnstile()`), `HCAPTCHA_SECRET_KEY` (`hcaptcha()`) |
254
269
  | [Rate limiting](/docs/configuration/rate-limiting) (`upstash()`) | `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` |
270
+ | [Rate limiting](/docs/configuration/rate-limiting) (`unkey()`) | `UNKEY_ROOT_KEY`, or the `rootKeyEnv` you passed it |
255
271
  | Mixedbread search | `MIXEDBREAD_API_KEY` |
256
272
  | [Narration](/docs/configuration/narration) (generated voices) | `AI_GATEWAY_API_KEY` (or Vercel OIDC) for `gateway()`, `OPENAI_API_KEY` for `openai()` (none with a `baseUrl`, unless you name one), or the `apiKeyEnv` you passed to either |
257
- | [Content sources](/docs/content/sources) | `NOTION_TOKEN` (`notion()`), `SANITY_TOKEN` (`sanity()`), `CONTENTFUL_ACCESS_TOKEN` (`contentful()`, plus `CONTENTFUL_PREVIEW_TOKEN` for `--preview`, which fails without it), `PAYLOAD_API_KEY` (`payload()`), `STRAPI_API_TOKEN` (`strapi()`), `GITHUB_TOKEN` (`githubReleases()`, and `mdxRemote()` reading from GitHub) |
273
+ | [Content sources](/docs/content/sources) | `NOTION_TOKEN` (`notion()`), `SANITY_TOKEN` (`sanity()`), `CONTENTFUL_ACCESS_TOKEN` (`contentful()`, plus `CONTENTFUL_PREVIEW_TOKEN` for `--preview`, which fails without it), `PAYLOAD_API_KEY` (`payload()`), `STRAPI_API_TOKEN` (`strapi()`), `GITHUB_TOKEN` (`githubReleases()`, and `mdxRemote()` with `github`) |
258
274
 
259
275
  Set them in `.env.local` for local dev and in your host's environment for production. A content source reads its token when it fetches, during `blume dev` and `blume build`, and narration reads its key during `blume build`, so set those where your site builds. Build-time secrets for search-index sync (`ALGOLIA_ADMIN_API_KEY`, `ORAMA_PRIVATE_API_KEY`, `TYPESENSE_ADMIN_API_KEY`) are checked by the sync step itself: when one is unset, the build warns and skips the sync, and when it's set, a failed sync [fails the build](/docs/configuration/search#adapters). The [GitHub info](/docs/content/components#github-info) card also sends `GITHUB_TOKEN`, when it's set, to lift GitHub's API rate limit.
260
276
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Migrate to Blume
3
- description: Move a Mintlify, Fumadocs, Docusaurus, Starlight, or Nextra site to Blume with one command that hands the migration to Codex or Claude Code.
3
+ description: Move a Mintlify, Docusaurus, GitBook, MkDocs, ReadMe, or other docs site to Blume with one command that hands the migration to Codex or Claude Code.
4
4
  sidebar:
5
5
  label: Migrate to Blume
6
6
  order: 2.4
@@ -29,8 +29,20 @@ Name the framework you're migrating from, or leave it out and Blume detects it f
29
29
  | `docusaurus` | `docusaurus.config.*` | [Migrate from Docusaurus](/guides/migrate-from-docusaurus) |
30
30
  | `starlight` | `@astrojs/starlight` in `package.json` | [Migrate from Starlight](/guides/migrate-from-starlight) |
31
31
  | `nextra` | `nextra` in `package.json` | [Migrate from Nextra](/guides/migrate-from-nextra) |
32
-
33
- Each source has its own mapping reference in the playbook. A site built with anything else still migrates: run the command without a source, and the agent inventories the repo first, then works from the playbook's general rules. The [MkDocs Material guide](/guides/migrate-from-mkdocs-material) walks through a migration that way.
32
+ | `vitepress` | `.vitepress/config.ts` (or `.mts`, `.js`, `.mjs`) at the root or in `docs/`, or `vitepress` in `package.json` | [Migrate from VitePress](/guides/migrate-from-vitepress) |
33
+ | `vuepress` | `.vuepress/config.ts` (or `.js`, `.mjs`, `.yml`, `.toml`) at the root or in `docs/`, `vuepress.config.*`, or `vuepress` (or `vuepress-vite`, `vuepress-webpack`) in `package.json` | [Migrate from VuePress](/guides/migrate-from-vuepress) |
34
+ | `docus` | `docus` (Docus 3 and later) or `@nuxt-themes/docus` (Docus 1) in `package.json`; run it from the docs package in a workspace | [Migrate from Docus](/guides/migrate-from-docus) |
35
+ | `mkdocs` | `mkdocs.yml`, `mkdocs.yaml`, `zensical.toml`, `properdocs.yml`, or `properdocs.yaml` | [Migrate from MkDocs Material](/guides/migrate-from-mkdocs-material) |
36
+ | `mdbook` | `book.toml` at the root or in `docs/` or `book/` | [Migrate from mdBook](/guides/migrate-from-mdbook) |
37
+ | `fern` | `fern/docs.yml` | [Migrate from Fern](/guides/migrate-from-fern) |
38
+ | `gitbook` | `.gitbook.yaml`, `.gitbook.yml`, or `gitbook-docs.yaml`, or `honkit` or `gitbook-cli` in `package.json`; never `SUMMARY.md` alone | [Migrate from GitBook](/guides/migrate-from-gitbook) |
39
+ | `redocly` | `sidebars.yaml`, or `@redocly/realm` or another Realm-family package in `package.json`; never `redocly.yaml` alone | [Migrate from Redocly](/guides/migrate-from-redocly) |
40
+ | `readme` | `_order.yaml` in `docs/`, `reference/`, `recipes/`, or `custom_pages/`; a repository that uploads with `rdme` needs the source named | [Migrate from ReadMe](/guides/migrate-from-readme) |
41
+ | `docsify` | `docsify-cli` or `docsify` in `package.json`; a site without a `package.json` needs the source named | [Migrate from Docsify](/guides/migrate-from-docsify) |
42
+ | `jekyll` | `_config.yml` at the root or in `docs/`, checked after every other source Blume detects | [Migrate from Just the Docs](/guides/migrate-from-jekyll) |
43
+ | `github-wiki` | Never detected: a wiki has no config file, so name the source | [Migrate from a GitHub wiki](/guides/migrate-from-github-wiki) |
44
+
45
+ Detection checks the folder you run the command in. Each source has its own mapping reference in the playbook, and several also have a codemod the agent runs first for the mechanical rewrites, so its own effort goes to the judgment calls. A site built with anything else still migrates: run the command without a source, and the agent inventories the repo first, then works from the playbook's general rules.
34
46
 
35
47
  ## What the agent does
36
48
 
@@ -39,7 +51,7 @@ The agent follows the playbook's workflow from the source's config to a passing
39
51
  1. Writes `blume.config.ts`, mapping only what your source declares and leaving Blume's defaults to cover the rest.
40
52
  2. Restructures content into [filesystem navigation](/docs/content/navigation), converting per-folder ordering files (Fumadocs `meta.json`, Nextra `_meta`) to `meta.ts`.
41
53
  3. Rewrites pages: frontmatter to Blume's schema, callout components to [directives](/docs/content/syntax), icons to Lucide, and snippets inlined.
42
- 4. Adds a [redirect](/docs/deployment#redirects) for every URL that moves, so no link breaks.
54
+ 4. Adds a [redirect](/docs/deployment#redirects) for every URL that moves, so no link breaks. Docsify's `#/` URLs never reach the server, so for those it adds a small script that sends each one to its new page. A GitHub wiki's URLs stay on github.com, which can't redirect, so the agent maps each page to its new route and prepares link stubs you can push to the old wiki.
43
55
  5. Points your `package.json` scripts at `blume dev` and `blume build`, and swaps the old framework's dependencies for `blume`.
44
56
  6. Runs `blume build` and `blume validate` until both pass.
45
57