blume 1.7.2 → 2.0.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 (677) hide show
  1. package/AGENTS.md +19 -0
  2. package/CHANGELOG.md +227 -0
  3. package/README.md +34 -21
  4. package/dist/cli/chunk-11j0384y.js +148 -0
  5. package/dist/cli/chunk-11j0384y.js.map +10 -0
  6. package/dist/cli/{chunk-xhtpx3ff.js → chunk-1w8dp3qb.js} +17 -16
  7. package/dist/cli/{chunk-xhtpx3ff.js.map → chunk-1w8dp3qb.js.map} +3 -3
  8. package/dist/cli/chunk-2q1dwty4.js +75 -0
  9. package/dist/cli/chunk-2q1dwty4.js.map +11 -0
  10. package/dist/cli/chunk-41za066z.js +122 -0
  11. package/dist/cli/chunk-41za066z.js.map +11 -0
  12. package/dist/cli/chunk-5a2z0198.js +133 -0
  13. package/dist/cli/chunk-5a2z0198.js.map +10 -0
  14. package/dist/cli/{chunk-9he6crym.js → chunk-6k8vp3ta.js} +25 -13
  15. package/dist/cli/{chunk-9he6crym.js.map → chunk-6k8vp3ta.js.map} +4 -4
  16. package/dist/cli/{chunk-mt76t7dj.js → chunk-79njf86q.js} +133 -54
  17. package/dist/cli/chunk-79njf86q.js.map +11 -0
  18. package/dist/cli/{chunk-3w7b2vcx.js → chunk-7ez8ny0t.js} +2 -2
  19. package/dist/cli/{chunk-688e0dde.js → chunk-88cpgt6h.js} +1 -1
  20. package/dist/cli/chunk-a9kptbw5.js +361 -0
  21. package/dist/cli/chunk-a9kptbw5.js.map +14 -0
  22. package/dist/cli/chunk-abh8yjkn.js +31 -0
  23. package/dist/cli/chunk-abh8yjkn.js.map +10 -0
  24. package/dist/cli/chunk-b5aj94ah.js +91 -0
  25. package/dist/cli/chunk-b5aj94ah.js.map +10 -0
  26. package/dist/cli/{chunk-hs3gbh8p.js → chunk-bctazmbk.js} +9 -5
  27. package/dist/cli/chunk-bctazmbk.js.map +10 -0
  28. package/dist/cli/chunk-beat36xx.js +279 -0
  29. package/dist/cli/chunk-beat36xx.js.map +10 -0
  30. package/dist/cli/chunk-bnbmcwfb.js +145 -0
  31. package/dist/cli/chunk-bnbmcwfb.js.map +11 -0
  32. package/dist/cli/{chunk-9bkjd11x.js → chunk-bw22s759.js} +15 -5
  33. package/dist/cli/{chunk-9bkjd11x.js.map → chunk-bw22s759.js.map} +4 -4
  34. package/dist/cli/chunk-by2290sx.js +39 -0
  35. package/dist/cli/chunk-by2290sx.js.map +10 -0
  36. package/dist/cli/chunk-d1tadaw7.js +79 -0
  37. package/dist/cli/chunk-d1tadaw7.js.map +10 -0
  38. package/dist/cli/{chunk-t3tj0dgr.js → chunk-d80hr03s.js} +24 -19
  39. package/dist/cli/chunk-d80hr03s.js.map +15 -0
  40. package/dist/cli/chunk-ernrthtr.js +97 -0
  41. package/dist/cli/chunk-ernrthtr.js.map +10 -0
  42. package/dist/cli/chunk-f2972sbt.js +374 -0
  43. package/dist/cli/chunk-f2972sbt.js.map +10 -0
  44. package/dist/cli/{chunk-exeeb35e.js → chunk-f7t03s3g.js} +2 -2
  45. package/dist/cli/{chunk-2z47ypj8.js → chunk-fa25z98p.js} +16 -3
  46. package/dist/cli/chunk-fa25z98p.js.map +11 -0
  47. package/dist/cli/{chunk-12dxjqk7.js → chunk-fh5hj5jt.js} +44 -21
  48. package/dist/cli/chunk-fh5hj5jt.js.map +10 -0
  49. package/dist/cli/{chunk-8cd8tj54.js → chunk-j8mw0za6.js} +86 -57
  50. package/dist/cli/chunk-j8mw0za6.js.map +35 -0
  51. package/dist/cli/chunk-jts8mvcz.js +106 -0
  52. package/dist/cli/{chunk-2mzebbbz.js.map → chunk-jts8mvcz.js.map} +6 -4
  53. package/dist/cli/{chunk-ejjx8znq.js → chunk-mnqj32sj.js} +505 -536
  54. package/dist/cli/chunk-mnqj32sj.js.map +12 -0
  55. package/dist/cli/{chunk-196vjxp9.js → chunk-mwt1k8n7.js} +100 -372
  56. package/dist/cli/chunk-mwt1k8n7.js.map +10 -0
  57. package/dist/cli/chunk-nk3ts2xk.js +51 -0
  58. package/dist/cli/chunk-nk3ts2xk.js.map +10 -0
  59. package/dist/cli/{chunk-n9sra6sy.js → chunk-pat2zzwc.js} +10 -14
  60. package/dist/cli/{chunk-n9sra6sy.js.map → chunk-pat2zzwc.js.map} +2 -2
  61. package/dist/cli/chunk-pnnvybbk.js +176 -0
  62. package/dist/cli/chunk-pnnvybbk.js.map +11 -0
  63. package/dist/cli/{chunk-cvky9gb2.js → chunk-sqn5t4q0.js} +81 -87
  64. package/dist/cli/chunk-sqn5t4q0.js.map +10 -0
  65. package/dist/cli/{chunk-eevwt1sc.js → chunk-tzne8qfq.js} +15 -15
  66. package/dist/cli/{chunk-eevwt1sc.js.map → chunk-tzne8qfq.js.map} +1 -1
  67. package/dist/cli/chunk-xaz13gwg.js +12449 -0
  68. package/dist/cli/chunk-xaz13gwg.js.map +182 -0
  69. package/dist/cli/chunk-y3e45rc8.js +102 -0
  70. package/dist/cli/chunk-y3e45rc8.js.map +10 -0
  71. package/dist/cli/chunk-z01ze5c1.js +261 -0
  72. package/dist/cli/chunk-z01ze5c1.js.map +10 -0
  73. package/dist/cli/{chunk-5n7t497w.js → chunk-z1f5arsg.js} +247 -755
  74. package/dist/cli/chunk-z1f5arsg.js.map +36 -0
  75. package/dist/cli/{chunk-hdm2dkd2.js → chunk-zg2gtj10.js} +1086 -2596
  76. package/dist/cli/chunk-zg2gtj10.js.map +35 -0
  77. package/dist/cli/{chunk-ppfvdcd4.js → chunk-zxccj738.js} +1 -1
  78. package/dist/cli/index.js +214 -57
  79. package/dist/cli/index.js.map +8 -7
  80. package/dist/types/ai/agent-readability.d.ts +52 -0
  81. package/dist/types/ai/ai-catalog.d.ts +42 -0
  82. package/dist/types/ai/api/paths.d.ts +17 -0
  83. package/dist/types/ai/api-catalog.d.ts +18 -0
  84. package/dist/types/ai/ask.d.ts +368 -0
  85. package/dist/types/ai/changelog-markdown.d.ts +2 -0
  86. package/dist/types/ai/component-markdown.d.ts +2 -2
  87. package/dist/types/ai/index.d.ts +24 -0
  88. package/dist/types/ai/link-headers.d.ts +24 -0
  89. package/dist/types/ai/llms.d.ts +25 -0
  90. package/dist/types/ai/markdown.d.ts +45 -0
  91. package/dist/types/ai/mcp/discovery.d.ts +68 -0
  92. package/dist/types/ai/mcp/tools.d.ts +16 -0
  93. package/dist/types/ai/openapi-components.d.ts +43 -0
  94. package/dist/types/ai/relative-links.d.ts +26 -0
  95. package/dist/types/ai/serializers.d.ts +15 -0
  96. package/dist/types/ai/skills.d.ts +42 -0
  97. package/dist/types/ai/tar.d.ts +25 -0
  98. package/dist/types/ai/visibility.d.ts +17 -0
  99. package/dist/types/ai/web-bot-auth.d.ts +16 -0
  100. package/dist/types/analytics/adobe.d.ts +35 -0
  101. package/dist/types/analytics/amplitude.d.ts +50 -0
  102. package/dist/types/analytics/clarity.d.ts +31 -0
  103. package/dist/types/analytics/clearbit.d.ts +29 -0
  104. package/dist/types/analytics/cloudflare.d.ts +41 -0
  105. package/dist/types/analytics/fathom.d.ts +41 -0
  106. package/dist/types/analytics/google-analytics.d.ts +44 -0
  107. package/dist/types/analytics/google-tag-manager.d.ts +40 -0
  108. package/dist/types/analytics/head.d.ts +30 -0
  109. package/dist/types/analytics/heap.d.ts +40 -0
  110. package/dist/types/analytics/hightouch.d.ts +44 -0
  111. package/dist/types/analytics/hotjar.d.ts +33 -0
  112. package/dist/types/analytics/index.d.ts +61 -0
  113. package/dist/types/analytics/inline.d.ts +17 -0
  114. package/dist/types/analytics/logrocket.d.ts +44 -0
  115. package/dist/types/analytics/mixpanel.d.ts +67 -0
  116. package/dist/types/analytics/pirsch.d.ts +42 -0
  117. package/dist/types/analytics/plausible.d.ts +56 -0
  118. package/dist/types/analytics/posthog.d.ts +45 -0
  119. package/dist/types/analytics/schema.d.ts +320 -0
  120. package/dist/types/analytics/script.d.ts +46 -0
  121. package/dist/types/analytics/segment.d.ts +50 -0
  122. package/dist/types/analytics/vercel.d.ts +48 -0
  123. package/dist/types/astro/integration.d.ts +76 -0
  124. package/dist/types/astro/markdown-negotiation.d.ts +23 -0
  125. package/dist/types/astro/module-types.d.ts +14 -0
  126. package/dist/types/astro/pages.d.ts +44 -0
  127. package/dist/types/cli/env.d.ts +12 -0
  128. package/dist/types/cli/init/scaffold.d.ts +154 -0
  129. package/dist/types/components/layout/nav-utils.d.ts +11 -0
  130. package/dist/types/core/adapter.d.ts +47 -0
  131. package/dist/types/core/api-name.d.ts +7 -0
  132. package/dist/types/core/changelog-index.d.ts +13 -0
  133. package/dist/types/core/config-input.d.ts +209 -505
  134. package/dist/types/core/config.d.ts +60 -34
  135. package/dist/types/core/content-assets.d.ts +76 -0
  136. package/dist/types/core/custom-pages.d.ts +33 -0
  137. package/dist/types/core/data.d.ts +28 -11
  138. package/dist/types/core/define-components.d.ts +12 -9
  139. package/dist/types/core/deployment-env.d.ts +6 -11
  140. package/dist/types/core/frontmatter.d.ts +10 -0
  141. package/dist/types/core/graph.d.ts +18 -0
  142. package/dist/types/core/heading-markers.d.ts +54 -0
  143. package/dist/types/core/i18n-ui.d.ts +6 -2
  144. package/dist/types/core/i18n.d.ts +87 -0
  145. package/dist/types/core/includes.d.ts +138 -0
  146. package/dist/types/core/last-modified.d.ts +47 -0
  147. package/dist/types/core/links.d.ts +95 -0
  148. package/dist/types/core/locale-links.d.ts +60 -0
  149. package/dist/types/core/manifest.d.ts +17 -0
  150. package/dist/types/core/meta.d.ts +38 -0
  151. package/dist/types/core/nav-diagnostics.d.ts +26 -0
  152. package/dist/types/core/navigation.d.ts +6 -0
  153. package/dist/types/core/node-require.d.ts +19 -0
  154. package/dist/types/core/package-json.d.ts +13 -0
  155. package/dist/types/core/probe.d.ts +42 -0
  156. package/dist/types/core/project-graph.d.ts +51 -0
  157. package/dist/types/core/project.d.ts +2 -0
  158. package/dist/types/core/safe-href.d.ts +2 -0
  159. package/dist/types/core/safe-links.d.ts +26 -0
  160. package/dist/types/core/schema.d.ts +2291 -441
  161. package/dist/types/core/site-url.d.ts +16 -0
  162. package/dist/types/core/sources/assets.d.ts +36 -0
  163. package/dist/types/core/sources/cache.d.ts +37 -0
  164. package/dist/types/core/sources/collection.d.ts +28 -0
  165. package/dist/types/core/sources/contentful-rich-text.d.ts +31 -0
  166. package/dist/types/core/sources/contentful.d.ts +40 -0
  167. package/dist/types/core/sources/filesystem.d.ts +24 -0
  168. package/dist/types/core/sources/github-releases.d.ts +31 -0
  169. package/dist/types/core/sources/json.d.ts +30 -0
  170. package/dist/types/core/sources/lexical.d.ts +19 -0
  171. package/dist/types/core/sources/lower.d.ts +75 -0
  172. package/dist/types/core/sources/mdx-remote.d.ts +28 -0
  173. package/dist/types/core/sources/normalize.d.ts +147 -0
  174. package/dist/types/core/sources/notion.d.ts +131 -0
  175. package/dist/types/core/sources/obsidian.d.ts +46 -0
  176. package/dist/types/core/sources/payload.d.ts +39 -0
  177. package/dist/types/core/sources/portable-text.d.ts +42 -0
  178. package/dist/types/core/sources/read.d.ts +23 -0
  179. package/dist/types/core/sources/remote.d.ts +74 -0
  180. package/dist/types/core/sources/resolve.d.ts +19 -0
  181. package/dist/types/core/sources/sanity.d.ts +43 -0
  182. package/dist/types/core/sources/strapi-blocks.d.ts +12 -0
  183. package/dist/types/core/sources/strapi.d.ts +33 -0
  184. package/dist/types/core/sources/types.d.ts +7 -0
  185. package/dist/types/core/sources/watch.d.ts +45 -0
  186. package/dist/types/core/text-width.d.ts +11 -0
  187. package/dist/types/core/types.d.ts +20 -1
  188. package/dist/types/core/unrecognized-keys.d.ts +7 -0
  189. package/dist/types/core/versions.d.ts +72 -0
  190. package/dist/types/core/yaml.d.ts +9 -0
  191. package/dist/types/deploy/adapter-output.d.ts +45 -0
  192. package/dist/types/deploy/adapters/cloudflare.d.ts +40 -0
  193. package/dist/types/deploy/adapters/index.d.ts +29 -0
  194. package/dist/types/deploy/adapters/netlify.d.ts +37 -0
  195. package/dist/types/deploy/adapters/node.d.ts +37 -0
  196. package/dist/types/deploy/adapters/registry.d.ts +133 -0
  197. package/dist/types/deploy/adapters/types.d.ts +71 -0
  198. package/dist/types/deploy/adapters/vercel.d.ts +38 -0
  199. package/dist/types/deploy/artifacts.d.ts +65 -0
  200. package/dist/types/deploy/cloudflare-negotiation.d.ts +196 -0
  201. package/dist/types/deploy/function-bundle.d.ts +80 -0
  202. package/dist/types/deploy/headers.d.ts +50 -0
  203. package/dist/types/deploy/node-headers.d.ts +42 -0
  204. package/dist/types/deploy/platforms/cloudflare.d.ts +40 -0
  205. package/dist/types/deploy/platforms/index.d.ts +18 -0
  206. package/dist/types/deploy/platforms/netlify.d.ts +13 -0
  207. package/dist/types/deploy/platforms/node.d.ts +11 -0
  208. package/dist/types/deploy/platforms/paths.d.ts +27 -0
  209. package/dist/types/deploy/platforms/static.d.ts +10 -0
  210. package/dist/types/deploy/platforms/types.d.ts +104 -0
  211. package/dist/types/deploy/platforms/vercel.d.ts +32 -0
  212. package/dist/types/deploy/redirects.d.ts +53 -0
  213. package/dist/types/deploy/robots.d.ts +8 -0
  214. package/dist/types/deploy/rss.d.ts +31 -0
  215. package/dist/types/deploy/sitemap.d.ts +21 -0
  216. package/dist/types/deploy/vercel-negotiation.d.ts +109 -0
  217. package/dist/types/markdown/code-title.d.ts +32 -0
  218. package/dist/types/markdown/fence-meta.d.ts +23 -0
  219. package/dist/types/markdown/themes.d.ts +3 -3
  220. package/dist/types/openapi/asyncapi.d.ts +129 -0
  221. package/dist/types/openapi/graphql-build.d.ts +8 -0
  222. package/dist/types/openapi/graphql.d.ts +122 -0
  223. package/dist/types/openapi/model.d.ts +158 -0
  224. package/dist/types/openapi/parse.d.ts +57 -0
  225. package/dist/types/openapi/references.d.ts +37 -25
  226. package/dist/types/openapi/render-mdx.d.ts +33 -0
  227. package/dist/types/openapi/sentence.d.ts +7 -0
  228. package/dist/types/openapi/signature.d.ts +10 -0
  229. package/dist/types/openapi/source.d.ts +22 -0
  230. package/dist/types/openapi/spec-dependency-error.d.ts +10 -0
  231. package/dist/types/reference/asyncapi.d.ts +166 -0
  232. package/dist/types/reference/graphql.d.ts +181 -0
  233. package/dist/types/reference/index.d.ts +32 -0
  234. package/dist/types/reference/openapi.d.ts +165 -0
  235. package/dist/types/reference/options.d.ts +157 -0
  236. package/dist/types/reference/scalar.d.ts +136 -0
  237. package/dist/types/reference/schema.d.ts +630 -0
  238. package/dist/types/search/adapters/algolia.d.ts +32 -0
  239. package/dist/types/search/adapters/flexsearch.d.ts +12 -0
  240. package/dist/types/search/adapters/index.d.ts +31 -0
  241. package/dist/types/search/adapters/mixedbread.d.ts +22 -0
  242. package/dist/types/search/adapters/orama-cloud.d.ts +33 -0
  243. package/dist/types/search/adapters/orama.d.ts +13 -0
  244. package/dist/types/search/adapters/pagefind.d.ts +12 -0
  245. package/dist/types/search/adapters/registry.d.ts +228 -0
  246. package/dist/types/search/adapters/types.d.ts +34 -0
  247. package/dist/types/search/adapters/typesense.d.ts +42 -0
  248. package/dist/types/search/build.d.ts +23 -0
  249. package/dist/types/search/documents.d.ts +90 -0
  250. package/dist/types/search/facets.d.ts +3 -0
  251. package/dist/types/search/sync/algolia.d.ts +14 -0
  252. package/dist/types/search/sync/index.d.ts +14 -0
  253. package/dist/types/search/sync/orama-cloud.d.ts +10 -0
  254. package/dist/types/search/sync/typesense.d.ts +14 -0
  255. package/dist/types/sources/contentful.d.ts +68 -0
  256. package/dist/types/sources/custom.d.ts +20 -0
  257. package/dist/types/sources/filesystem.d.ts +42 -0
  258. package/dist/types/sources/github-releases.d.ts +46 -0
  259. package/dist/types/sources/index.d.ts +48 -0
  260. package/dist/types/sources/mdx-remote.d.ts +63 -0
  261. package/dist/types/sources/notion.d.ts +65 -0
  262. package/dist/types/sources/obsidian.d.ts +34 -0
  263. package/dist/types/sources/payload.d.ts +68 -0
  264. package/dist/types/sources/registry.d.ts +1414 -0
  265. package/dist/types/sources/sanity.d.ts +69 -0
  266. package/dist/types/sources/shared.d.ts +46 -0
  267. package/dist/types/sources/strapi.d.ts +66 -0
  268. package/dist/types/theme/icon-kind.d.ts +11 -0
  269. package/dist/types/theme/icons.d.ts +20 -0
  270. package/docs/01-quickstart.mdx +18 -18
  271. package/docs/02-deployment.mdx +50 -26
  272. package/docs/03-upgrading.mdx +351 -0
  273. package/docs/04-migrating.mdx +58 -0
  274. package/docs/08-faq.mdx +3 -3
  275. package/docs/advanced/blog.mdx +13 -6
  276. package/docs/advanced/changelog.mdx +23 -35
  277. package/docs/advanced/custom-pages.mdx +20 -8
  278. package/docs/advanced/meta.ts +1 -8
  279. package/docs/advanced/skills.mdx +8 -0
  280. package/docs/cli/audit.mdx +646 -0
  281. package/docs/cli/doctor.mdx +29 -0
  282. package/docs/{reference/eval.mdx → cli/evals.mdx} +9 -8
  283. package/docs/cli/index.mdx +105 -0
  284. package/docs/cli/meta.ts +7 -0
  285. package/docs/{reference → cli}/translate.mdx +1 -1
  286. package/docs/cli/validate.mdx +41 -0
  287. package/docs/cli/version.mdx +40 -0
  288. package/docs/configuration/analytics.mdx +350 -59
  289. package/docs/configuration/ask-ai.mdx +176 -61
  290. package/docs/configuration/customization.mdx +15 -9
  291. package/docs/configuration/index.mdx +52 -31
  292. package/docs/configuration/search.mdx +75 -54
  293. package/docs/configuration/theming.mdx +24 -15
  294. package/docs/content/components.mdx +21 -5
  295. package/docs/{reference → content}/frontmatter.mdx +37 -1
  296. package/docs/content/i18n.mdx +8 -6
  297. package/docs/content/includes.mdx +2 -4
  298. package/docs/content/index.mdx +1 -1
  299. package/docs/content/islands.mdx +10 -5
  300. package/docs/content/meta.mdx +1 -1
  301. package/docs/content/meta.ts +1 -0
  302. package/docs/content/navigation.mdx +12 -7
  303. package/docs/content/sources.mdx +145 -50
  304. package/docs/content/syntax.mdx +26 -12
  305. package/docs/content/versioning.mdx +3 -3
  306. package/docs/discoverability/agent-discovery.mdx +91 -10
  307. package/docs/discoverability/index.mdx +5 -5
  308. package/docs/discoverability/json-api.mdx +4 -4
  309. package/docs/discoverability/llms-txt.mdx +6 -6
  310. package/docs/discoverability/markdown.mdx +4 -4
  311. package/docs/discoverability/mcp.mdx +11 -11
  312. package/docs/discoverability/sitemap-and-robots.mdx +3 -3
  313. package/docs/index.mdx +2 -2
  314. package/docs/references/asyncapi.mdx +59 -0
  315. package/docs/{advanced → references}/graphql.mdx +45 -32
  316. package/docs/{reference → references}/meta.ts +2 -2
  317. package/docs/references/openapi.mdx +171 -0
  318. package/docs/references/scalar.mdx +64 -0
  319. package/package.json +42 -8
  320. package/skills/blume/SKILL.md +21 -8
  321. package/skills/blume-migrate/SKILL.md +22 -21
  322. package/skills/blume-migrate/assets/oxfmt@0.67.0.patch +49 -0
  323. package/skills/blume-migrate/references/docusaurus.md +5 -4
  324. package/skills/blume-migrate/references/fumadocs.md +4 -4
  325. package/skills/blume-migrate/references/mintlify.md +23 -8
  326. package/skills/blume-migrate/references/monorepo.md +6 -6
  327. package/skills/blume-migrate/references/starlight.md +4 -4
  328. package/src/ai/agent-readability.ts +26 -22
  329. package/src/ai/ai-catalog.ts +259 -0
  330. package/src/ai/api/handlers.ts +46 -11
  331. package/src/ai/api-catalog.ts +8 -8
  332. package/src/ai/ask-data.ts +1 -1
  333. package/src/ai/ask.ts +624 -100
  334. package/src/ai/changelog-markdown.ts +91 -0
  335. package/src/ai/component-markdown.ts +328 -12
  336. package/src/ai/index.ts +45 -0
  337. package/src/ai/link-headers.ts +19 -6
  338. package/src/ai/llms.ts +26 -15
  339. package/src/ai/markdown.ts +35 -5
  340. package/src/ai/mcp/data.ts +9 -5
  341. package/src/ai/mcp/discovery.ts +1 -1
  342. package/src/ai/openapi-components.ts +4 -1
  343. package/src/ai/relative-links.ts +170 -0
  344. package/src/ai/serializers.ts +2 -2
  345. package/src/ai/skills.ts +1 -1
  346. package/src/ai/web-bot-auth.ts +2 -2
  347. package/src/analytics/adobe.ts +46 -0
  348. package/src/analytics/amplitude.ts +79 -0
  349. package/src/analytics/clarity.ts +46 -0
  350. package/src/analytics/clearbit.ts +47 -0
  351. package/src/analytics/cloudflare.ts +67 -0
  352. package/src/analytics/fathom.ts +64 -0
  353. package/src/analytics/google-analytics.ts +84 -0
  354. package/src/analytics/google-tag-manager.ts +61 -0
  355. package/src/analytics/head.ts +130 -0
  356. package/src/analytics/heap.ts +65 -0
  357. package/src/analytics/hightouch.ts +77 -0
  358. package/src/analytics/hotjar.ts +47 -0
  359. package/src/analytics/index.ts +71 -0
  360. package/src/analytics/inline.ts +21 -0
  361. package/src/analytics/logrocket.ts +71 -0
  362. package/src/analytics/mixpanel.ts +92 -0
  363. package/src/analytics/pirsch.ts +66 -0
  364. package/src/analytics/plausible.ts +83 -0
  365. package/src/analytics/posthog.ts +78 -0
  366. package/src/analytics/schema.ts +60 -0
  367. package/src/analytics/script.ts +60 -0
  368. package/src/analytics/segment.ts +85 -0
  369. package/src/analytics/vercel.ts +50 -0
  370. package/src/astro/adapter-root.ts +7 -9
  371. package/src/astro/component-slots.ts +131 -91
  372. package/src/astro/generate.ts +114 -345
  373. package/src/astro/integration.ts +2 -6
  374. package/src/astro/pages.ts +13 -101
  375. package/src/astro/render-deps.ts +379 -0
  376. package/src/astro/runtime-deps.ts +201 -0
  377. package/src/astro/templates.ts +554 -541
  378. package/src/audit/agent.ts +24 -0
  379. package/src/audit/catalog.ts +2 -2
  380. package/src/audit/checks/assets.ts +2 -2
  381. package/src/audit/checks/dns-aid.ts +1 -1
  382. package/src/audit/checks/duplicates.ts +3 -1
  383. package/src/audit/checks/i18n.ts +1 -1
  384. package/src/audit/checks/indexability.ts +7 -7
  385. package/src/audit/checks/links.ts +2 -2
  386. package/src/audit/checks/llms.ts +6 -6
  387. package/src/audit/checks/network.ts +3 -3
  388. package/src/audit/checks/og-image.ts +2 -2
  389. package/src/audit/checks/robots.ts +1 -1
  390. package/src/audit/checks/sitemap.ts +2 -2
  391. package/src/audit/checks/social.ts +1 -1
  392. package/src/audit/run.ts +4 -3
  393. package/src/audit/terms.ts +31 -0
  394. package/src/audit/url.ts +13 -13
  395. package/src/cli/command-meta.ts +10 -0
  396. package/src/cli/commands/audit.ts +39 -20
  397. package/src/cli/commands/build.ts +71 -314
  398. package/src/cli/commands/check.ts +1 -0
  399. package/src/cli/commands/dev.ts +40 -1
  400. package/src/cli/commands/doctor.ts +90 -14
  401. package/src/cli/commands/eject.ts +44 -7
  402. package/src/cli/commands/eval.ts +2 -2
  403. package/src/cli/commands/init.ts +161 -41
  404. package/src/cli/commands/migrate.ts +121 -0
  405. package/src/cli/commands/preview.ts +7 -1
  406. package/src/cli/commands/translate.ts +2 -2
  407. package/src/cli/commands/upgrade.ts +141 -0
  408. package/src/cli/commands/version.ts +57 -44
  409. package/src/cli/eject-scripts.ts +121 -6
  410. package/src/cli/index.ts +11 -1
  411. package/src/cli/init/install.ts +70 -0
  412. package/src/cli/init/questions.ts +7 -0
  413. package/src/cli/init/scaffold.ts +459 -75
  414. package/src/cli/lazy-command.ts +37 -1
  415. package/src/cli/prepare.ts +40 -6
  416. package/src/cli/required-secrets.ts +38 -11
  417. package/src/cli/unknown-flags.ts +266 -0
  418. package/src/cli/yarn-pnp.ts +52 -0
  419. package/src/components/content/AccordionItem.astro +7 -1
  420. package/src/components/content/Card.astro +4 -3
  421. package/src/components/content/ColorItem.astro +22 -4
  422. package/src/components/content/GithubInfo.astro +2 -2
  423. package/src/components/content/Prompt.astro +25 -25
  424. package/src/components/content/Tabs.astro +3 -1
  425. package/src/components/content/Tile.astro +2 -3
  426. package/src/components/content/Tooltip.astro +57 -9
  427. package/src/components/content/content-strings.ts +34 -0
  428. package/src/components/content/diff.ts +1 -1
  429. package/src/components/content/mermaid-element.ts +13 -2
  430. package/src/components/content/prompt-markdown.ts +292 -0
  431. package/src/components/content/tooltip-id.ts +41 -0
  432. package/src/components/islands/hooks.ts +75 -3
  433. package/src/components/layout/Analytics.astro +21 -79
  434. package/src/components/layout/Banner.astro +3 -1
  435. package/src/components/layout/DiscoveryLinks.astro +69 -0
  436. package/src/components/layout/Header.astro +83 -20
  437. package/src/components/layout/LanguageSwitcher.astro +4 -1
  438. package/src/components/layout/Logo.astro +23 -2
  439. package/src/components/layout/NavSelector.astro +13 -2
  440. package/src/components/layout/NavTree.astro +12 -3
  441. package/src/components/layout/NavTreeScript.astro +17 -3
  442. package/src/components/layout/PageActions.astro +3 -3
  443. package/src/components/layout/PageFeedback.astro +11 -1
  444. package/src/components/layout/PageLayout.astro +52 -5
  445. package/src/components/layout/ReferenceLayout.astro +21 -12
  446. package/src/components/layout/RootLayout.astro +69 -43
  447. package/src/components/layout/Search.astro +30 -6
  448. package/src/components/layout/WebMcp.astro +1 -1
  449. package/src/components/layout/analytics-client.ts +101 -15
  450. package/src/components/layout/drawer-inert.ts +113 -15
  451. package/src/components/layout/dropdown-clamp.ts +105 -0
  452. package/src/components/layout/head-scripts.ts +15 -6
  453. package/src/components/layout/nav-utils.ts +17 -0
  454. package/src/components/layout/search/algolia.ts +8 -8
  455. package/src/components/layout/search/orama-cloud.ts +9 -7
  456. package/src/components/layout/search/typesense.ts +11 -17
  457. package/src/components/openapi/MessageComposer.astro +2 -2
  458. package/src/components/openapi/Playground.astro +2 -2
  459. package/src/components/openapi/playground-client.ts +79 -10
  460. package/src/core/changelog-index.ts +24 -0
  461. package/src/core/component-overrides.ts +399 -154
  462. package/src/core/config-input.ts +212 -543
  463. package/src/core/config.ts +130 -41
  464. package/src/core/custom-pages.ts +105 -0
  465. package/src/core/data.ts +32 -11
  466. package/src/core/define-components.ts +12 -9
  467. package/src/core/deployment-env.ts +18 -74
  468. package/src/core/diagnostics.ts +14 -7
  469. package/src/core/graph.ts +69 -25
  470. package/src/core/i18n-ui.ts +6 -2
  471. package/src/core/i18n.ts +17 -0
  472. package/src/core/includes.ts +156 -38
  473. package/src/core/last-modified.ts +6 -11
  474. package/src/core/links.ts +166 -2
  475. package/src/core/manifest.ts +10 -3
  476. package/src/core/navigation.ts +137 -19
  477. package/src/core/new-tab.ts +35 -0
  478. package/src/core/node-require.ts +21 -0
  479. package/src/core/project-graph.ts +42 -26
  480. package/src/core/project.ts +9 -4
  481. package/src/core/request-body.ts +61 -0
  482. package/src/core/safe-href.ts +28 -0
  483. package/src/core/safe-links.ts +68 -0
  484. package/src/core/schema.ts +531 -721
  485. package/src/core/server-features.ts +8 -9
  486. package/src/core/sources/assets.ts +47 -11
  487. package/src/core/sources/collection.ts +67 -0
  488. package/src/core/sources/contentful-rich-text.ts +285 -0
  489. package/src/core/sources/contentful.ts +173 -0
  490. package/src/core/sources/github-releases.ts +51 -1
  491. package/src/core/sources/json.ts +71 -0
  492. package/src/core/sources/lexical.ts +195 -0
  493. package/src/core/sources/lower.ts +226 -0
  494. package/src/core/sources/normalize.ts +52 -2
  495. package/src/core/sources/notion.ts +39 -28
  496. package/src/core/sources/payload.ts +135 -0
  497. package/src/core/sources/portable-text.ts +11 -16
  498. package/src/core/sources/remote.ts +226 -0
  499. package/src/core/sources/resolve.ts +104 -166
  500. package/src/core/sources/sanity.ts +16 -49
  501. package/src/core/sources/strapi-blocks.ts +124 -0
  502. package/src/core/sources/strapi.ts +191 -0
  503. package/src/core/sources/types.ts +12 -1
  504. package/src/core/types.ts +20 -1
  505. package/src/core/ui-packs/ar.ts +6 -2
  506. package/src/core/ui-packs/bg.ts +6 -2
  507. package/src/core/ui-packs/bn.ts +6 -2
  508. package/src/core/ui-packs/ca.ts +3 -1
  509. package/src/core/ui-packs/cs.ts +6 -2
  510. package/src/core/ui-packs/da.ts +6 -2
  511. package/src/core/ui-packs/de.ts +6 -2
  512. package/src/core/ui-packs/el.ts +3 -1
  513. package/src/core/ui-packs/es.ts +3 -1
  514. package/src/core/ui-packs/fa.ts +6 -2
  515. package/src/core/ui-packs/fi.ts +6 -2
  516. package/src/core/ui-packs/fr.ts +3 -1
  517. package/src/core/ui-packs/he.ts +6 -2
  518. package/src/core/ui-packs/hi.ts +6 -2
  519. package/src/core/ui-packs/hr.ts +6 -2
  520. package/src/core/ui-packs/hu.ts +6 -2
  521. package/src/core/ui-packs/id.ts +6 -2
  522. package/src/core/ui-packs/it.ts +3 -1
  523. package/src/core/ui-packs/ja.ts +3 -1
  524. package/src/core/ui-packs/ko.ts +3 -1
  525. package/src/core/ui-packs/nl.ts +6 -2
  526. package/src/core/ui-packs/no.ts +6 -2
  527. package/src/core/ui-packs/pl.ts +6 -2
  528. package/src/core/ui-packs/pt-br.ts +3 -1
  529. package/src/core/ui-packs/pt.ts +3 -1
  530. package/src/core/ui-packs/ro.ts +6 -2
  531. package/src/core/ui-packs/ru.ts +6 -2
  532. package/src/core/ui-packs/sk.ts +6 -2
  533. package/src/core/ui-packs/sr.ts +6 -2
  534. package/src/core/ui-packs/sv.ts +6 -2
  535. package/src/core/ui-packs/th.ts +3 -1
  536. package/src/core/ui-packs/tr.ts +6 -2
  537. package/src/core/ui-packs/uk.ts +6 -2
  538. package/src/core/ui-packs/vi.ts +3 -1
  539. package/src/core/ui-packs/zh-tw.ts +3 -1
  540. package/src/core/ui-packs/zh.ts +3 -1
  541. package/src/core/unrecognized-keys.ts +10 -0
  542. package/src/core/version-cut.ts +116 -8
  543. package/src/deploy/adapter-output.ts +57 -97
  544. package/src/deploy/adapters/cloudflare.ts +39 -0
  545. package/src/deploy/adapters/index.ts +41 -0
  546. package/src/deploy/adapters/netlify.ts +34 -0
  547. package/src/deploy/adapters/node.ts +34 -0
  548. package/src/deploy/adapters/registry.ts +127 -0
  549. package/src/deploy/adapters/types.ts +92 -0
  550. package/src/deploy/adapters/vercel.ts +35 -0
  551. package/src/deploy/artifacts.ts +67 -28
  552. package/src/deploy/cloudflare-negotiation.ts +179 -100
  553. package/src/deploy/function-bundle.ts +18 -3
  554. package/src/deploy/headers.ts +129 -22
  555. package/src/deploy/node-headers.ts +198 -0
  556. package/src/deploy/platforms/cloudflare.ts +312 -0
  557. package/src/deploy/platforms/index.ts +67 -0
  558. package/src/deploy/platforms/netlify.ts +52 -0
  559. package/src/deploy/platforms/node.ts +40 -0
  560. package/src/deploy/platforms/paths.ts +42 -0
  561. package/src/deploy/platforms/static.ts +29 -0
  562. package/src/deploy/platforms/types.ts +112 -0
  563. package/src/deploy/platforms/vercel.ts +193 -0
  564. package/src/deploy/redirects.ts +30 -18
  565. package/src/deploy/robots.ts +3 -3
  566. package/src/deploy/rss.ts +2 -2
  567. package/src/deploy/sitemap.ts +2 -2
  568. package/src/deploy/vercel-negotiation.ts +27 -4
  569. package/src/eval/agents.ts +32 -1
  570. package/src/eval/findings.ts +19 -11
  571. package/src/eval/report.ts +10 -2
  572. package/src/markdown/external-links.ts +65 -0
  573. package/src/markdown/include.ts +45 -27
  574. package/src/markdown/index.ts +34 -9
  575. package/src/markdown/inline-code.ts +12 -6
  576. package/src/markdown/relative-links.ts +324 -0
  577. package/src/markdown/themes.ts +3 -3
  578. package/src/migrate/migrate.ts +149 -0
  579. package/src/openapi/parse.ts +64 -16
  580. package/src/openapi/proxy.ts +62 -10
  581. package/src/openapi/references.ts +147 -147
  582. package/src/openapi/render-mdx.ts +32 -10
  583. package/src/openapi/scalar.ts +15 -13
  584. package/src/openapi/sentence.ts +14 -0
  585. package/src/openapi/source.ts +37 -21
  586. package/src/openapi/spec-dependency-error.ts +15 -0
  587. package/src/reference/asyncapi.ts +83 -0
  588. package/src/reference/graphql.ts +89 -0
  589. package/src/reference/index.ts +40 -0
  590. package/src/reference/openapi.ts +83 -0
  591. package/src/reference/options.ts +201 -0
  592. package/src/reference/scalar.ts +136 -0
  593. package/src/reference/schema.ts +88 -0
  594. package/src/registry/eject.ts +97 -42
  595. package/src/search/adapters/algolia.ts +46 -0
  596. package/src/search/adapters/flexsearch.ts +29 -0
  597. package/src/search/adapters/index.ts +39 -0
  598. package/src/search/adapters/mixedbread.ts +39 -0
  599. package/src/search/adapters/orama-cloud.ts +51 -0
  600. package/src/search/adapters/orama.ts +24 -0
  601. package/src/search/adapters/pagefind.ts +27 -0
  602. package/src/search/adapters/registry.ts +131 -0
  603. package/src/search/adapters/types.ts +46 -0
  604. package/src/search/adapters/typesense.ts +57 -0
  605. package/src/search/build.ts +33 -7
  606. package/src/search/documents.ts +6 -0
  607. package/src/search/sync/algolia.ts +12 -11
  608. package/src/search/sync/index.ts +35 -20
  609. package/src/search/sync/orama-cloud.ts +12 -9
  610. package/src/search/sync/typesense.ts +15 -13
  611. package/src/sources/contentful.ts +63 -0
  612. package/src/sources/custom.ts +38 -0
  613. package/src/sources/filesystem.ts +51 -0
  614. package/src/sources/github-releases.ts +52 -0
  615. package/src/sources/index.ts +60 -0
  616. package/src/sources/mdx-remote.ts +76 -0
  617. package/src/sources/notion.ts +63 -0
  618. package/src/sources/obsidian.ts +38 -0
  619. package/src/sources/payload.ts +60 -0
  620. package/src/sources/registry.ts +182 -0
  621. package/src/sources/sanity.ts +66 -0
  622. package/src/sources/shared.ts +52 -0
  623. package/src/sources/strapi.ts +58 -0
  624. package/src/theme/entry.ts +47 -2
  625. package/src/translate/report.ts +40 -7
  626. package/src/upgrade/upgrade.ts +499 -0
  627. package/dist/cli/chunk-12dxjqk7.js.map +0 -10
  628. package/dist/cli/chunk-196vjxp9.js.map +0 -13
  629. package/dist/cli/chunk-2mzebbbz.js +0 -69
  630. package/dist/cli/chunk-2z47ypj8.js.map +0 -11
  631. package/dist/cli/chunk-30e87n55.js +0 -108
  632. package/dist/cli/chunk-30e87n55.js.map +0 -10
  633. package/dist/cli/chunk-450a7rcr.js +0 -185
  634. package/dist/cli/chunk-450a7rcr.js.map +0 -11
  635. package/dist/cli/chunk-5n7t497w.js.map +0 -40
  636. package/dist/cli/chunk-61j18dwk.js +0 -5342
  637. package/dist/cli/chunk-61j18dwk.js.map +0 -58
  638. package/dist/cli/chunk-88by27n5.js +0 -17
  639. package/dist/cli/chunk-88by27n5.js.map +0 -10
  640. package/dist/cli/chunk-8cd8tj54.js.map +0 -34
  641. package/dist/cli/chunk-aztttvb3.js +0 -381
  642. package/dist/cli/chunk-aztttvb3.js.map +0 -12
  643. package/dist/cli/chunk-cvky9gb2.js.map +0 -11
  644. package/dist/cli/chunk-ejjx8znq.js.map +0 -15
  645. package/dist/cli/chunk-fmceyezb.js +0 -1007
  646. package/dist/cli/chunk-fmceyezb.js.map +0 -13
  647. package/dist/cli/chunk-hdm2dkd2.js.map +0 -48
  648. package/dist/cli/chunk-hs3gbh8p.js.map +0 -10
  649. package/dist/cli/chunk-jbj4qhfw.js +0 -30
  650. package/dist/cli/chunk-jbj4qhfw.js.map +0 -10
  651. package/dist/cli/chunk-mqb2ka8m.js +0 -68
  652. package/dist/cli/chunk-mqb2ka8m.js.map +0 -10
  653. package/dist/cli/chunk-mt76t7dj.js.map +0 -11
  654. package/dist/cli/chunk-q4rae3bg.js +0 -60
  655. package/dist/cli/chunk-q4rae3bg.js.map +0 -10
  656. package/dist/cli/chunk-ra1v2nc2.js +0 -35
  657. package/dist/cli/chunk-ra1v2nc2.js.map +0 -10
  658. package/dist/cli/chunk-t3tj0dgr.js.map +0 -15
  659. package/dist/cli/chunk-tqa1s0k8.js +0 -69
  660. package/dist/cli/chunk-tqa1s0k8.js.map +0 -11
  661. package/dist/cli/chunk-vh9w1sgp.js +0 -73
  662. package/dist/cli/chunk-vh9w1sgp.js.map +0 -10
  663. package/dist/cli/chunk-vkrsvbr5.js +0 -107
  664. package/dist/cli/chunk-vkrsvbr5.js.map +0 -11
  665. package/dist/cli/chunk-vrfp10qk.js +0 -81
  666. package/dist/cli/chunk-vrfp10qk.js.map +0 -10
  667. package/dist/cli/chunk-wjt80jps.js +0 -1049
  668. package/dist/cli/chunk-wjt80jps.js.map +0 -24
  669. package/docs/advanced/api-reference.mdx +0 -240
  670. package/docs/reference/cli.mdx +0 -197
  671. package/skills/blume-migrate/assets/oxfmt@0.55.0.patch +0 -20
  672. package/src/components/content/changelog-element.ts +0 -69
  673. package/src/search/providers.ts +0 -91
  674. /package/dist/cli/{chunk-3w7b2vcx.js.map → chunk-7ez8ny0t.js.map} +0 -0
  675. /package/dist/cli/{chunk-688e0dde.js.map → chunk-88cpgt6h.js.map} +0 -0
  676. /package/dist/cli/{chunk-exeeb35e.js.map → chunk-f7t03s3g.js.map} +0 -0
  677. /package/dist/cli/{chunk-ppfvdcd4.js.map → chunk-zxccj738.js.map} +0 -0
@@ -1,19 +1,41 @@
1
1
  import type { AstroIntegration } from "astro";
2
2
  import { z } from "zod";
3
3
 
4
+ import {
5
+ askAdapterSchema,
6
+ askMovedFieldsHint,
7
+ DEFAULT_ASK_PROVIDER,
8
+ } from "../ai/ask.ts";
4
9
  import type { ComponentMarkdown } from "../ai/component-markdown.ts";
10
+ import { analyticsConfigSchema } from "../analytics/schema.ts";
11
+ import { resolvedDeploymentSchema } from "../deploy/adapters/registry.ts";
5
12
  import type { CodeTheme } from "../markdown/themes.ts";
6
13
  import { normalizeRoute } from "../openapi/references.ts";
14
+ import {
15
+ referenceConfigSchema,
16
+ removedReferenceKeysHint,
17
+ } from "../reference/schema.ts";
18
+ import { orama } from "../search/adapters/orama.ts";
19
+ import {
20
+ NONE_SEARCH_ADAPTER,
21
+ resolvedSearchAdapterSchema,
22
+ } from "../search/adapters/registry.ts";
23
+ import type { SearchAdapterInput } from "../search/adapters/registry.ts";
7
24
  import { normalizeXHandle } from "../seo/x-handle.ts";
25
+ import { filesystem } from "../sources/filesystem.ts";
26
+ import {
27
+ contentSourcesSchema,
28
+ resolvedSourceAdapterSchema,
29
+ } from "../sources/registry.ts";
8
30
  import { FONT_SLUGS, isFontSlug } from "../theme/fonts.ts";
9
31
  import { normalizeBasePath } from "./base-path.ts";
10
32
  import { PUBLIC_HOST_URL } from "./github.ts";
11
33
  import { uiLocaleOverridesSchema } from "./i18n-ui.ts";
12
34
  import { openInChatProviders } from "./open-in-chat.ts";
13
- import type { ContentSource } from "./sources/types.ts";
14
35
  import { isStandardSchema } from "./standard-schema.ts";
15
36
  import type { StandardSchema } from "./standard-schema.ts";
16
37
  import { trimEnd } from "./trim.ts";
38
+ import { unrecognizedKeysMessage } from "./unrecognized-keys.ts";
17
39
 
18
40
  /**
19
41
  * An absolute HTTP(S) URL, for any field that lands verbatim in an `href` —
@@ -42,12 +64,40 @@ const isString = <Value>(value: Value): value is Value & string =>
42
64
  const isBoolean = <Value>(value: Value): value is Value & boolean =>
43
65
  typeof value === "boolean";
44
66
 
67
+ const isObjectLike = <Value>(value: Value): value is Value & object =>
68
+ typeof value === "object" && value !== null;
69
+
45
70
  /** Icon inputs in serializable contexts (frontmatter, meta files). */
46
71
  const iconName = z.string().min(1);
47
72
 
48
- /** Default include glob for filesystem-backed content sources. */
49
- const DEFAULT_CONTENT_GLOB = "**/*.{md,mdx}";
73
+ /**
74
+ * Error params for a strict object whose keys were removed or moved in a
75
+ * major release. A config still carrying one of `hints`' keys fails with
76
+ * the message that names its replacement, instead of Zod's bare
77
+ * "Unrecognized key"; any other unknown key keeps the default message.
78
+ */
79
+ const removedKeysHint = (hints: Record<string, string>) => ({
80
+ error: (issue: z.core.$ZodRawIssue): string | undefined => {
81
+ if (issue.code !== "unrecognized_keys") {
82
+ return;
83
+ }
84
+ const messages = issue.keys.flatMap((key) =>
85
+ Object.hasOwn(hints, key) ? [hints[key]] : []
86
+ );
87
+ if (messages.length === 0) {
88
+ return;
89
+ }
90
+ // A hinted key beside a plain unknown one: keep Zod's wording for the
91
+ // latter so it isn't silently dropped from the diagnostic.
92
+ const others = issue.keys.filter((key) => !Object.hasOwn(hints, key));
93
+ if (others.length > 0) {
94
+ messages.push(unrecognizedKeysMessage(others));
95
+ }
96
+ return messages.join(" ");
97
+ },
98
+ });
50
99
 
100
+ /** Default include glob for filesystem-backed content sources. */
51
101
  const hydrationMode = z.enum(["load", "idle", "visible", "media", "only"]);
52
102
  export type HydrationMode = z.infer<typeof hydrationMode>;
53
103
 
@@ -105,11 +155,16 @@ const seoMetaSchema = z.strictObject({
105
155
  x: z.strictObject({ creator: xHandleSchema }).optional(),
106
156
  });
107
157
 
108
- const searchMetaSchema = z.strictObject({
109
- boost: z.number().optional(),
110
- exclude: z.boolean().default(false),
111
- tags: z.array(z.string()).optional(),
112
- });
158
+ const searchMetaSchema = z.strictObject(
159
+ {
160
+ exclude: z.boolean().default(false),
161
+ tags: z.array(z.string()).optional(),
162
+ },
163
+ removedKeysHint({
164
+ boost:
165
+ "search.boost was removed: search never read it, so the page ranked the same without it. Delete the field.",
166
+ })
167
+ );
113
168
 
114
169
  const aiMetaSchema = z.strictObject({
115
170
  /** Exclude this page from llms.txt and llms-full.txt. */
@@ -291,161 +346,8 @@ const bannerConfigSchema = z.union([
291
346
  }),
292
347
  ]);
293
348
 
294
- /** A local filesystem content source. */
295
- const filesystemSourceSchema = z.strictObject({
296
- exclude: z.array(z.string()).default(["**/_*", "**/.*"]),
297
- include: z.array(z.string()).default([DEFAULT_CONTENT_GLOB]),
298
- /** Namespaces the source's routes under `/<prefix>/`. */
299
- prefix: z.string().optional(),
300
- root: z.string().default("docs"),
301
- type: z.literal("filesystem"),
302
- });
303
-
304
- /**
305
- * Remote Markdown/MDX fetched over HTTP. Enumerate files either explicitly
306
- * (`files` against a raw `url` base) or from a GitHub repo subtree (`github`).
307
- * The token, when needed, comes from `GITHUB_TOKEN` — never inlined here.
308
- */
309
- const mdxRemoteSourceSchema = z.strictObject({
310
- /** Explicit list of source-relative file paths to fetch from `url`. */
311
- files: z.array(z.string()).optional(),
312
- /** Enumerate a GitHub repo subtree via the git-trees API. */
313
- github: z
314
- .strictObject({
315
- owner: z.string(),
316
- path: z.string().default(""),
317
- ref: z.string().default("main"),
318
- repo: z.string(),
319
- })
320
- .optional(),
321
- /** Glob patterns applied to enumerated refs. */
322
- include: z.array(z.string()).default([DEFAULT_CONTENT_GLOB]),
323
- /** Opt-in dev polling interval (seconds); omit to freeze for the session. */
324
- pollInterval: z.number().positive().optional(),
325
- /** Namespaces the source's routes under `/<prefix>/`. */
326
- prefix: z.string().optional(),
327
- type: z.literal("mdx-remote"),
328
- /** Raw base URL, e.g. `https://raw.githubusercontent.com/acme/sdk/main/docs`. */
329
- url: z.string().optional(),
330
- });
331
-
332
- /** A Sanity dataset queried with GROQ; Portable Text bodies become Markdown. */
333
- const sanitySourceSchema = z.object({
334
- /** Sanity API version (a date); default `2024-01-01`. */
335
- apiVersion: z.string().optional(),
336
- dataset: z.string(),
337
- /** Field paths mapping a document onto Blume meta + body. */
338
- fields: z
339
- .strictObject({
340
- body: z.string().optional(),
341
- description: z.string().optional(),
342
- lastModified: z.string().optional(),
343
- slug: z.string().optional(),
344
- title: z.string().optional(),
345
- })
346
- .optional(),
347
- /** Opt-in dev polling interval (seconds); omit to freeze for the session. */
348
- pollInterval: z.number().positive().optional(),
349
- prefix: z.string().optional(),
350
- projectId: z.string(),
351
- /** GROQ query selecting the documents to import. */
352
- query: z.string(),
353
- type: z.literal("sanity"),
354
- });
355
-
356
- /** A Notion database; pages become entries, blocks become MDX. */
357
- const notionSourceSchema = z.object({
358
- /** Max concurrent Notion API requests; default 3 (Notion's per-integration pace). */
359
- concurrency: z.number().positive().optional(),
360
- database: z.string(),
361
- /** Opt-in dev polling interval (seconds); omit to freeze for the session. */
362
- pollInterval: z.number().positive().optional(),
363
- prefix: z.string().optional(),
364
- /** Notion property names mapped onto Blume meta. */
365
- properties: z
366
- .strictObject({
367
- description: z.string().optional(),
368
- order: z.string().optional(),
369
- slug: z.string().optional(),
370
- status: z.string().optional(),
371
- title: z.string().optional(),
372
- })
373
- .optional(),
374
- /** Status value treated as published; others map to `draft`. Default `Published`. */
375
- publishedValue: z.string().optional(),
376
- type: z.literal("notion"),
377
- });
378
-
379
- /**
380
- * An Obsidian vault, read in place. Wikilinks become route links and
381
- * `%%comments%%` are stripped at load time, so the vault stays the source of
382
- * truth — no export step and no generated notes in the repo.
383
- */
384
- const obsidianSourceSchema = z.strictObject({
385
- /** Vault folder names to skip at any depth, in addition to dot-folders. */
386
- exclude: z.array(z.string()).optional(),
387
- /** Namespaces the source's routes under `/<prefix>/`; e.g. `vault`. */
388
- prefix: z.string().optional(),
389
- type: z.literal("obsidian"),
390
- /** Vault directory, absolute or relative to the project root. */
391
- vault: z.string().min(1),
392
- });
393
-
394
- /**
395
- * A repo's GitHub Releases, materialized as `type: changelog` entries — release
396
- * notes become the changelog with no files to maintain. A private repo reads a
397
- * token from `GITHUB_TOKEN`; it is never inlined here.
398
- */
399
- const githubReleasesSourceSchema = z.strictObject({
400
- /** Include draft releases (needs a token with repo write access). */
401
- drafts: z.boolean().optional(),
402
- /** Cap the number of releases materialized, newest-first. Default 100. */
403
- limit: z.number().positive().optional(),
404
- /** Repository owner (user or org). */
405
- owner: z.string(),
406
- /** Opt-in dev polling interval (seconds); omit to freeze for the session. */
407
- pollInterval: z.number().positive().optional(),
408
- /** Namespaces the source's routes under `/<prefix>/`; e.g. `changelog`. */
409
- prefix: z.string().optional(),
410
- /** Include prereleases. */
411
- prereleases: z.boolean().optional(),
412
- /** Repository name. */
413
- repo: z.string(),
414
- type: z.literal("github-releases"),
415
- });
416
-
417
- /**
418
- * A user-provided `ContentSource` instance, passed straight through from
419
- * `blume.config.ts`. This is the extension point that lets adapters with custom
420
- * serializers (or any backend) ship without their SDKs touching core.
421
- */
422
- const customSourceSchema = z.object({
423
- source: z.custom<ContentSource>(
424
- (val): val is ContentSource =>
425
- typeof val === "object" &&
426
- val !== null &&
427
- "load" in val &&
428
- typeof val.load === "function" &&
429
- "name" in val &&
430
- typeof val.name === "string",
431
- { message: "custom source must be a ContentSource (with name + load)" }
432
- ),
433
- type: z.literal("custom"),
434
- });
435
-
436
- /** A single configured content source. */
437
- const contentSourceSchema = z.discriminatedUnion("type", [
438
- filesystemSourceSchema,
439
- mdxRemoteSourceSchema,
440
- githubReleasesSourceSchema,
441
- sanitySourceSchema,
442
- notionSourceSchema,
443
- obsidianSourceSchema,
444
- customSourceSchema,
445
- ]);
446
-
447
- /** A resolved content-source config entry (post-defaults). */
448
- export type ContentSourceConfig = z.infer<typeof contentSourceSchema>;
349
+ /** A validated `content.sources` entry: an adapter descriptor from `blume/sources`. */
350
+ export type { ContentSourceAdapter } from "../sources/registry.ts";
449
351
 
450
352
  /**
451
353
  * Per-type content definition. An object (rather than a bare frontmatter map)
@@ -471,24 +373,51 @@ const contentTypeConfigSchema = z.strictObject({
471
373
  frontmatter: customKeySchemaRecord("content.types"),
472
374
  });
473
375
 
474
- const contentConfigSchema = z.strictObject({
475
- defaultType: z.string().default("doc"),
476
- exclude: z.array(z.string()).default(["**/_*", "**/.*"]),
477
- include: z.array(z.string()).default([DEFAULT_CONTENT_GLOB]),
478
- pages: z.string().default("pages"),
479
- root: z.string().default("docs"),
480
- /**
481
- * Pluggable content sources. When omitted, the top-level
482
- * `root`/`include`/`exclude` desugar to one implicit filesystem source, so
483
- * existing projects are unchanged.
484
- */
485
- sources: z.array(contentSourceSchema).optional(),
486
- /**
487
- * Per-type content definitions, keyed by the frontmatter `type` they apply
488
- * to (including `defaultType`, for pages that set none).
489
- */
490
- types: z.record(z.string(), contentTypeConfigSchema).default({}),
491
- });
376
+ /** The top-level fields that are shorthand for a single `filesystem()` source. */
377
+ const FILESYSTEM_SHORTHAND_KEYS = ["exclude", "include", "root"] as const;
378
+
379
+ /**
380
+ * `content`: where pages come from. `sources` lists adapters from
381
+ * `blume/sources`; the top-level `root`/`include`/`exclude` are zero-config
382
+ * shorthand that desugars to exactly one `filesystem()` entry when `sources`
383
+ * is absent, and are rejected beside it — so after parse, `sources` is the
384
+ * one source of truth and nothing downstream picks between the two.
385
+ */
386
+ const contentConfigSchema = z
387
+ .strictObject({
388
+ defaultType: z.string().default("doc"),
389
+ exclude: z.array(z.string()).optional(),
390
+ include: z.array(z.string()).optional(),
391
+ pages: z.string().default("pages"),
392
+ root: z.string().optional(),
393
+ sources: contentSourcesSchema.optional(),
394
+ /**
395
+ * Per-type content definitions, keyed by the frontmatter `type` they apply
396
+ * to (including `defaultType`, for pages that set none).
397
+ */
398
+ types: z.record(z.string(), contentTypeConfigSchema).default({}),
399
+ })
400
+ .superRefine((value, ctx) => {
401
+ if (!value.sources) {
402
+ return;
403
+ }
404
+ for (const key of FILESYSTEM_SHORTHAND_KEYS) {
405
+ if (value[key] !== undefined) {
406
+ ctx.addIssue({
407
+ code: z.ZodIssueCode.custom,
408
+ message: `content.${key} is shorthand for a single filesystem() source and can't be combined with content.sources — move it into a filesystem({ ${key} }) entry in content.sources.`,
409
+ path: [key],
410
+ });
411
+ }
412
+ }
413
+ })
414
+ .transform(({ exclude, include, root, sources, ...rest }) => ({
415
+ ...rest,
416
+ sources: sources ?? [
417
+ // The shorthand's defaults are the adapter's own, applied by its schema.
418
+ resolvedSourceAdapterSchema.parse(filesystem({ exclude, include, root })),
419
+ ],
420
+ }));
492
421
 
493
422
  /**
494
423
  * A header label that may localize: a plain string, or a map of locale code to
@@ -655,7 +584,7 @@ const perModeValueSchema = z
655
584
  isString(value) ? { dark: value, light: value } : value
656
585
  );
657
586
 
658
- const themeConfigSchema = z.strictObject({
587
+ const themeConfigFields = {
659
588
  accent: z
660
589
  .union([
661
590
  z.string(),
@@ -675,58 +604,17 @@ const themeConfigSchema = z.strictObject({
675
604
  mono: fontValueSchema.default("ibm-plex-mono"),
676
605
  })
677
606
  .prefault({}),
678
- layout: z.enum(["sidebar"]).default("sidebar"),
679
607
  mode: z.enum(["system", "light", "dark"]).default("system"),
680
608
  radius: z.enum(["none", "sm", "md", "lg"]).default("md"),
681
- });
609
+ };
682
610
 
683
- /** Public credentials for the Algolia search backend (sync key is an env var). */
684
- const algoliaSearchSchema = z.strictObject({
685
- appId: z.string(),
686
- indexName: z.string(),
687
- searchApiKey: z.string(),
688
- });
689
-
690
- /** Public credentials for the Orama Cloud search backend. */
691
- const oramaCloudSearchSchema = z.strictObject({
692
- apiKey: z.string(),
693
- endpoint: z.string(),
694
- /** Index id used by the build-time sync (with `ORAMA_PRIVATE_API_KEY`). */
695
- indexId: z.string().optional(),
696
- });
697
-
698
- /** Public credentials for a (self-hosted or cloud) Typesense backend. */
699
- const typesenseSearchSchema = z.strictObject({
700
- collection: z.string(),
701
- host: z.string(),
702
- port: z.number().int().positive().optional(),
703
- protocol: z.enum(["http", "https"]).optional(),
704
- searchApiKey: z.string(),
705
- });
706
-
707
- /** Mixedbread semantic search: the store the server endpoint queries. */
708
- const mixedbreadSearchSchema = z.strictObject({
709
- storeId: z.string(),
710
- });
711
-
712
- export const searchProviders = [
713
- "orama",
714
- "pagefind",
715
- "flexsearch",
716
- "algolia",
717
- "orama-cloud",
718
- "typesense",
719
- "mixedbread",
720
- "none",
721
- ] as const;
722
-
723
- /** Providers that need a config block, mapped to its `search.*` key. */
724
- const PROVIDER_CONFIG_KEY = {
725
- algolia: "algolia",
726
- mixedbread: "mixedbread",
727
- "orama-cloud": "oramaCloud",
728
- typesense: "typesense",
729
- } as const;
611
+ const themeConfigSchema = z.strictObject(
612
+ themeConfigFields,
613
+ removedKeysHint({
614
+ layout:
615
+ "theme.layout was removed: the sidebar layout is the only one, so delete the field.",
616
+ })
617
+ );
730
618
 
731
619
  /** Curated link for the search dialog empty state (internal route or external URL). */
732
620
  const searchPopularLinkSchema = z.strictObject({
@@ -735,59 +623,82 @@ const searchPopularLinkSchema = z.strictObject({
735
623
  label: z.string(),
736
624
  });
737
625
 
738
- const searchConfigSchema = z
626
+ /**
627
+ * The search backend: an adapter descriptor from `blume/search` (`algolia({…})`,
628
+ * `orama()`, …) or `false` to disable search. Resolves to a descriptor either
629
+ * way — `false` becomes the `none` adapter — so consumers read `kind`,
630
+ * `runtimeDeps`, and `requiredSecrets` without a special case.
631
+ */
632
+ const searchProviderSchema = z
633
+ .custom<false | SearchAdapterInput>(
634
+ // A 1.x provider name (`"algolia"`, `"none"`) fails here with the adapter
635
+ // that replaces it, rather than the pipe's bare "expected object".
636
+ (value) => value === false || isObjectLike(value),
637
+ {
638
+ message:
639
+ 'search.provider takes an adapter from "blume/search" — algolia({…}), pagefind(), … — not a provider name. The 1.x provider string was removed, and "none" is now `search: false`.',
640
+ }
641
+ )
642
+ .transform((value) => (value === false ? NONE_SEARCH_ADAPTER : value))
643
+ .pipe(resolvedSearchAdapterSchema);
644
+
645
+ /** Indexing behavior shared by every source-built index. */
646
+ const searchIndexingSchema = z
739
647
  .strictObject({
740
- algolia: algoliaSearchSchema.optional(),
741
- indexing: z
742
- .strictObject({
743
- includeCodeBlocks: z.boolean().default(false),
744
- includeHiddenPages: z.boolean().default(false),
745
- })
746
- .prefault({}),
747
- mixedbread: mixedbreadSearchSchema.optional(),
748
- oramaCloud: oramaCloudSearchSchema.optional(),
648
+ includeCodeBlocks: z.boolean().default(false),
649
+ includeHiddenPages: z.boolean().default(false),
650
+ })
651
+ .prefault({});
652
+
653
+ /** The object form of `search`: the adapter plus its adapter-independent settings. */
654
+ const searchOptionsSchema = z.strictObject(
655
+ {
656
+ indexing: searchIndexingSchema,
749
657
  /** Curated links for the Cmd+K empty state; defaults to the first sidebar pages. */
750
658
  popular: z.array(searchPopularLinkSchema).default([]),
751
- provider: z.enum(searchProviders).default("orama"),
752
- typesense: typesenseSearchSchema.optional(),
659
+ provider: searchProviderSchema.default(() => orama()),
660
+ },
661
+ // The 1.x credential blocks, each now its adapter's options.
662
+ removedKeysHint({
663
+ algolia:
664
+ 'search.algolia moved into its adapter: `search: algolia({ appId, indexName, apiKey })` from "blume/search", where the 1.x `searchApiKey` is now `apiKey`.',
665
+ mixedbread:
666
+ 'search.mixedbread moved into its adapter: `search: mixedbread({ storeId })` from "blume/search".',
667
+ oramaCloud:
668
+ 'search.oramaCloud moved into its adapter: `search: oramaCloud({ endpoint, apiKey, indexId })` from "blume/search".',
669
+ typesense:
670
+ 'search.typesense moved into its adapter: `search: typesense({ host, collection, apiKey })` from "blume/search", where the 1.x `searchApiKey` is now `apiKey`.',
753
671
  })
754
- .superRefine((value, ctx) => {
755
- // Hosted providers can't work without their credentials; flag a missing
756
- // block with a path so the diagnostic points at `search.<provider>`.
757
- // SAFETY: providers without a config block (orama, pagefind, …) miss the
758
- // map and read undefined, which the `field &&` guard below absorbs.
759
- const field =
760
- PROVIDER_CONFIG_KEY[value.provider as keyof typeof PROVIDER_CONFIG_KEY];
761
- if (field && !value[field]) {
762
- ctx.addIssue({
763
- code: z.ZodIssueCode.custom,
764
- message: `search.${field} is required when provider is "${value.provider}".`,
765
- path: [field],
766
- });
767
- }
768
- });
672
+ );
673
+
674
+ type SearchOptionsInput = z.input<typeof searchOptionsSchema>;
675
+
676
+ /** What `search` accepts: an adapter (or `false`) directly, or the object form. */
677
+ type SearchConfigInput = false | SearchAdapterInput | SearchOptionsInput;
769
678
 
770
679
  /**
771
- * The `ai.ask.reasoning` levels: the AI SDK's top-level `reasoning` values
772
- * minus `provider-default`, which is what omitting the field means.
680
+ * `search` takes an adapter directly (`search: algolia({…})`, or `false`) as
681
+ * shorthand for the object form (`search: { provider: algolia({…}), popular,
682
+ * indexing }`). The shorthand is lifted into `provider` before the object
683
+ * schema validates, rather than through a union: a union reports whichever
684
+ * branch fails "softest", which for a descriptor missing an option is the
685
+ * object form's "unrecognized keys" — pointing at the wrong problem. A
686
+ * descriptor is recognized by its `kind`; the object form never has one.
773
687
  */
774
- export const askReasoningLevels = [
775
- "none",
776
- "minimal",
777
- "low",
778
- "medium",
779
- "high",
780
- "xhigh",
781
- ] as const;
782
-
783
- /** Ask AI backends. `gateway` (default) routes through the Vercel AI Gateway. */
784
- export const askAiProviders = [
785
- "gateway",
786
- "openrouter",
787
- "llmgateway",
788
- "inkeep",
789
- "openai-compatible",
790
- ] as const;
688
+ const searchConfigSchema = z
689
+ .custom<SearchConfigInput>(
690
+ // Anything else (a 1.x provider string, null, true) must become a
691
+ // diagnostic here: the `in` check below would throw a TypeError on it.
692
+ (value) => value === false || isObjectLike(value),
693
+ {
694
+ message:
695
+ 'search must be an adapter from "blume/search" (orama(), algolia({…}), …), false, or { provider, popular, indexing }. The 1.x provider string was removed.',
696
+ }
697
+ )
698
+ .transform((value): SearchOptionsInput =>
699
+ value === false || "kind" in value ? { provider: value } : value
700
+ )
701
+ .pipe(searchOptionsSchema);
791
702
 
792
703
  /**
793
704
  * JWK parameters that carry private or secret key material (RFC 7518): the
@@ -855,7 +766,7 @@ const askEndpointSchema = z
855
766
  }
856
767
  );
857
768
 
858
- /** The object form of `ai.llmsTxt`; a bare boolean normalizes onto it. */
769
+ /** The object form of `agents.llmsTxt`; a bare boolean normalizes onto it. */
859
770
  const llmsTxtObjectSchema = z.strictObject({
860
771
  /**
861
772
  * Markdown inserted after the title and summary, before the page sections:
@@ -869,97 +780,109 @@ const llmsTxtObjectSchema = z.strictObject({
869
780
 
870
781
  type LlmsTxtResolved = z.output<typeof llmsTxtObjectSchema>;
871
782
 
872
- const aiConfigSchema = z.strictObject({
783
+ /** The object form of `agents.catalog`; a bare boolean normalizes onto it. */
784
+ const aiCatalogObjectSchema = z.strictObject({
785
+ enabled: z.boolean().default(true),
873
786
  /**
874
- * The JSON docs API: the page index, per-page JSON, and navigation under
875
- * `/api/docs/` (prerendered, so a static site serves them from files), the
876
- * live search endpoint on server output, and the OpenAPI description of
877
- * the whole machine-readable surface at `/openapi.json`. On by default.
787
+ * Representative queries per catalog entry, keyed by the entry's
788
+ * `<namespace>:<name>` (the identifier minus its `urn:air:<host>:` prefix,
789
+ * e.g. `mcp:docs`, `skill:blume`, `api:docs`). Replaces the generated
790
+ * defaults for that entry; 2–5 short natural-language questions the
791
+ * resource can answer, which registries embed for semantic search.
878
792
  */
879
- api: z.boolean().default(true),
793
+ queries: z
794
+ .record(z.string().min(1), z.array(z.string().trim().min(1)).min(1))
795
+ .default({}),
796
+ });
797
+
798
+ type AiCatalogResolved = z.output<typeof aiCatalogObjectSchema>;
799
+
800
+ /**
801
+ * The keys that moved from `ai` to `agents`: `ai` now holds only what faces a
802
+ * model at read time (Ask AI, Open in chat), and the machine-readable surface
803
+ * agents consume lives under `agents`.
804
+ */
805
+ const MOVED_TO_AGENTS = [
806
+ "api",
807
+ "catalog",
808
+ "llmsTxt",
809
+ "markdownComponents",
810
+ "mcp",
811
+ "skills",
812
+ "webBotAuth",
813
+ "webmcp",
814
+ ] as const;
815
+
816
+ const movedToAgentsHints = (from: string): Record<string, string> =>
817
+ Object.fromEntries(
818
+ MOVED_TO_AGENTS.map((key) => [
819
+ key,
820
+ `${from}.${key} moved to agents.${key}.`,
821
+ ])
822
+ );
823
+
824
+ /** Model-facing config: the Ask AI assistant and the "Open in chat" action. */
825
+ const aiConfigFields = {
880
826
  ask: z
881
- .strictObject({
882
- // Name of the env var holding the provider's API key; each provider has
883
- // a sensible default, so this only needs setting to override it.
884
- apiKeyEnv: z.string().optional(),
885
- // Base URL of the backend. Required for `openai-compatible` only when no
886
- // external endpoint is supplied; for named providers it overrides the preset.
887
- baseUrl: z.url().optional(),
888
- // Origins allowed to call the generated `/api/ask` from another site (a
889
- // marketing page that embeds an ask box, say), or `"*"` for every
890
- // origin. Each URL is reduced to its origin so a trailing slash or path
891
- // can't defeat the exact match the route performs. Read by the
892
- // generated route only; an external `endpoint` owns its own CORS.
893
- cors: z
894
- .array(
895
- z.union([
896
- z.literal("*"),
897
- z
898
- .url({ protocol: /^https?$/u })
899
- .transform((value) => new URL(value).origin),
900
- ])
901
- )
902
- .optional(),
903
- enabled: z.boolean().default(false),
904
- // Optional external endpoint for projects that keep their docs static
905
- // and host Ask AI in an existing backend. Absolute URLs and root-relative
906
- // paths are both valid; the built-in request/stream contract is unchanged.
907
- endpoint: askEndpointSchema.optional(),
908
- // Static request headers the generated endpoint sends the provider on
909
- // every call (a caller-identifying header for a shared backend, say).
910
- // Values are inlined into the generated route as literals, so the API
911
- // key stays in `apiKeyEnv`; these are for non-secret metadata.
912
- headers: z.record(z.string(), z.string()).optional(),
913
- // Extra system-prompt text (identity, language, tone) appended to the
914
- // built-in instructions, so the grounding contract — answer from the
915
- // retrieved excerpts, cite pages as Markdown links — stays intact.
916
- instructions: z.string().trim().min(1).optional(),
917
- model: z.string().default("openai/gpt-5.5"),
918
- provider: z.enum(askAiProviders).default("gateway"),
919
- // How much the model reasons before answering, sent as the backend's
920
- // own reasoning-effort control (see `askEndpointTemplate`). Omitted
921
- // keeps the provider's default; `none` is the fastest and cheapest for
922
- // grounded docs Q&A, where the excerpts carry the answer. Not for
923
- // Inkeep, which has no such control (refined below).
924
- reasoning: z.enum(askReasoningLevels).optional(),
925
- // How much documentation each question carries. Injected characters are
926
- // the dominant term in time-to-first-token on a self-hosted backend, so
927
- // these trade recall for latency. No zod defaults here: only what the
928
- // user set reaches the generated (and ejected) endpoint, so omitted
929
- // fields keep tracking the installed package's built-in defaults in
930
- // `ai/ask-context.ts` instead of pinning today's numbers as literals.
931
- retrieval: z
932
- .strictObject({
933
- contextBudget: z.number().int().positive().optional(),
934
- excerptChars: z.number().int().positive().optional(),
935
- maxResults: z.number().int().positive().optional(),
936
- })
937
- .optional(),
938
- // Empty-state prompts shown before the first question. Each renders as a
939
- // clickable suggestion; `icon` is an optional Lucide name beside it.
940
- suggestions: z
941
- .array(
942
- z.strictObject({
943
- icon: iconName.optional(),
944
- label: z.string().min(1),
827
+ .strictObject(
828
+ {
829
+ // Origins allowed to call the generated `/api/ask` from another site (a
830
+ // marketing page that embeds an ask box, say), or `"*"` for every
831
+ // origin. Each URL is reduced to its origin so a trailing slash or path
832
+ // can't defeat the exact match the route performs. Read by the
833
+ // generated route only; an external `endpoint` owns its own CORS.
834
+ cors: z
835
+ .array(
836
+ z.union([
837
+ z.literal("*"),
838
+ z
839
+ .url({ protocol: /^https?$/u })
840
+ .transform((value) => new URL(value).origin),
841
+ ])
842
+ )
843
+ .optional(),
844
+ enabled: z.boolean().default(false),
845
+ // Optional external endpoint for projects that keep their docs static
846
+ // and host Ask AI in an existing backend. Absolute URLs and root-relative
847
+ // paths are both valid; the built-in request/stream contract is unchanged.
848
+ endpoint: askEndpointSchema.optional(),
849
+ // Extra system-prompt text (identity, language, tone) appended to the
850
+ // built-in instructions, so the grounding contract — answer from the
851
+ // retrieved excerpts, cite pages as Markdown links — stays intact.
852
+ instructions: z.string().trim().min(1).optional(),
853
+ // The adapter descriptor a `gateway()`/`openrouter()`/... factory
854
+ // returns; each adapter validates its own options (model, key env var,
855
+ // reasoning mapping, `providerOptions` passthrough) in `ai/ask.ts`.
856
+ // Unset means the gateway with its default model, so zero-config Ask AI
857
+ // is unchanged.
858
+ provider: askAdapterSchema.prefault(DEFAULT_ASK_PROVIDER),
859
+ // How much documentation each question carries. Injected characters are
860
+ // the dominant term in time-to-first-token on a self-hosted backend, so
861
+ // these trade recall for latency. No zod defaults here: only what the
862
+ // user set reaches the generated (and ejected) endpoint, so omitted
863
+ // fields keep tracking the installed package's built-in defaults in
864
+ // `ai/ask-context.ts` instead of pinning today's numbers as literals.
865
+ retrieval: z
866
+ .strictObject({
867
+ contextBudget: z.number().int().positive().optional(),
868
+ excerptChars: z.number().int().positive().optional(),
869
+ maxResults: z.number().int().positive().optional(),
945
870
  })
946
- )
947
- .default([]),
948
- })
871
+ .optional(),
872
+ // Empty-state prompts shown before the first question. Each renders as a
873
+ // clickable suggestion; `icon` is an optional Lucide name beside it.
874
+ suggestions: z
875
+ .array(
876
+ z.strictObject({
877
+ icon: iconName.optional(),
878
+ label: z.string().min(1),
879
+ })
880
+ )
881
+ .default([]),
882
+ },
883
+ askMovedFieldsHint
884
+ )
949
885
  .superRefine((value, ctx) => {
950
- // A generic OpenAI-compatible backend has no preset URL, so the user
951
- // must supply one; the named providers fall back to their preset.
952
- if (
953
- value.provider === "openai-compatible" &&
954
- !(value.baseUrl || value.endpoint)
955
- ) {
956
- ctx.addIssue({
957
- code: z.ZodIssueCode.custom,
958
- message:
959
- 'ai.ask.baseUrl is required when provider is "openai-compatible".',
960
- path: ["baseUrl"],
961
- });
962
- }
963
886
  // `cors` configures the generated route, which an external `endpoint`
964
887
  // replaces; accepting both would silently do nothing.
965
888
  if (value.cors && value.endpoint) {
@@ -970,52 +893,8 @@ const aiConfigSchema = z.strictObject({
970
893
  path: ["cors"],
971
894
  });
972
895
  }
973
- // Inkeep runs its own QA pipeline behind an OpenAI-compatible endpoint
974
- // with no reasoning control; a level would only reach it as an
975
- // unsupported `reasoning_effort`, so refuse it up front.
976
- if (value.provider === "inkeep" && value.reasoning) {
977
- ctx.addIssue({
978
- code: z.ZodIssueCode.custom,
979
- message:
980
- 'ai.ask.reasoning is not supported when provider is "inkeep".',
981
- path: ["reasoning"],
982
- });
983
- }
984
896
  })
985
897
  .optional(),
986
- /**
987
- * `llms.txt`/`llms-full.txt` emission. A bare boolean toggles it; the object
988
- * form adds `openapi: false` to keep generated API reference pages out of
989
- * both files (e.g. when the configured spec is example content) and
990
- * `details`, free-form Markdown placed after the summary — the llms.txt
991
- * spec's details block, where a site tells agents when to reach for it.
992
- */
993
- llmsTxt: z
994
- .union([z.boolean(), llmsTxtObjectSchema])
995
- .default(true)
996
- .transform((value): LlmsTxtResolved =>
997
- isBoolean(value) ? { enabled: value, openapi: true } : value
998
- ),
999
- // Serializers for the agent-facing Markdown downlevel (the `.md` mirror,
1000
- // llms-full.txt, MCP get_page), keyed by JSX name. Functions live here —
1001
- // not in components.tsx — because the config file is executed at build
1002
- // time while the components file is only statically analyzed. A same-name
1003
- // entry replaces the built-in serializer.
1004
- // Two-argument `z.record` — the single-argument form throws at
1005
- // schema-construction time under Zod 4 (see uiStringsOverrideSchema).
1006
- markdownComponents: z
1007
- .record(
1008
- z.string(),
1009
- z.custom<ComponentMarkdown>(
1010
- (value): value is ComponentMarkdown => typeof value === "function",
1011
- {
1012
- message: "Expected a serializer function.",
1013
- }
1014
- )
1015
- )
1016
- .default({}),
1017
- /** Expose the docs as an MCP server for connecting agents. */
1018
- mcp: mcpConfigSchema.prefault({}),
1019
898
  /**
1020
899
  * The "Open in chat" page action. `true` (the default) lists every
1021
900
  * provider, `false` hides the action entirely, and an array of provider
@@ -1038,35 +917,12 @@ const aiConfigSchema = z.strictObject({
1038
917
  }
1039
918
  return value;
1040
919
  }),
1041
- /**
1042
- * Publish Agent Skills for discovery: a directory (resolved against the
1043
- * project root) whose subdirectories each hold a `SKILL.md`. The build
1044
- * copies each skill under `/.well-known/agent-skills/` — a lone `SKILL.md`
1045
- * verbatim, a skill with supporting files as a `.tar.gz` — and emits the
1046
- * discovery index (`index.json`) with SHA-256 digests per the Agent Skills
1047
- * Discovery RFC.
1048
- */
1049
- skills: z.string().min(1).optional(),
1050
- /**
1051
- * Web Bot Auth (IETF `webbotauth`): publish the org's HTTP Message
1052
- * Signature public keys at `/.well-known/http-message-signatures-directory`
1053
- * so sites receiving requests from the org's agents can verify them.
1054
- * Opt-in and public-keys-only — the private keys live wherever the signing
1055
- * agents run, never in the site.
1056
- */
1057
- webBotAuth: z
1058
- .strictObject({
1059
- keys: z.array(publicJwkSchema).default([]),
1060
- })
1061
- .prefault({}),
1062
- /**
1063
- * WebMCP: register in-page tools (search, page Markdown, the docs index)
1064
- * on the browser's model context so agentic browsers can drive the docs
1065
- * without a separate MCP connection. A tiny script that no-ops in browsers
1066
- * without the API; on by default.
1067
- */
1068
- webmcp: z.boolean().default(true),
1069
- });
920
+ };
921
+
922
+ const aiConfigSchema = z.strictObject(
923
+ aiConfigFields,
924
+ removedKeysHint(movedToAgentsHints("ai"))
925
+ );
1070
926
 
1071
927
  /**
1072
928
  * A pinned link rendered above the sidebar sections — a blog, changelog, or
@@ -1132,8 +988,8 @@ const navigationConfigSchema = z.strictObject({
1132
988
  tabs: z.array(navTabSchema).default([]),
1133
989
  });
1134
990
 
1135
- export type AskAiProvider = (typeof askAiProviders)[number];
1136
- export type AskReasoning = (typeof askReasoningLevels)[number];
991
+ export { askReasoningLevels } from "../ai/ask.ts";
992
+ export type { AskReasoning } from "../ai/ask.ts";
1137
993
  export type AskAiConfig = NonNullable<z.infer<typeof aiConfigSchema>["ask"]>;
1138
994
  export { openInChatProviders } from "./open-in-chat.ts";
1139
995
  export type { OpenInChatProvider } from "./open-in-chat.ts";
@@ -1284,57 +1140,28 @@ const versionsConfigSchema = z
1284
1140
  }
1285
1141
  });
1286
1142
 
1287
- const analyticsScriptSchema = z
1288
- .strictObject({
1289
- // Extra attributes (e.g. `data-domain`, `id`) spread onto the <script>.
1290
- attributes: z.record(z.string(), z.string()).optional(),
1291
- // Inline script body, mutually exclusive with `src`.
1292
- content: z.string().optional(),
1293
- // External script URL, mutually exclusive with `content`.
1294
- src: z.string().optional(),
1295
- // Load strategy for an external script.
1296
- strategy: z.enum(["async", "defer"]).optional(),
1297
- })
1298
- .refine((value) => Boolean(value.src) !== Boolean(value.content), {
1299
- message: "An analytics script must set exactly one of `src` or `content`.",
1300
- });
1301
-
1302
- const analyticsConfigSchema = z.strictObject({
1303
- // Cloudflare Web Analytics in manual (JS snippet) mode; the token comes from
1304
- // the site's snippet in the dashboard. A zone Cloudflare proxies with
1305
- // automatic RUM injection on needs no config at all.
1306
- cloudflare: z
1307
- .strictObject({
1308
- token: z.string().min(1),
1309
- })
1310
- .optional(),
1311
- posthog: z
1312
- .strictObject({
1313
- host: z.string().optional(),
1314
- key: z.string(),
1315
- })
1316
- .optional(),
1317
- // Escape hatch for any other provider (Plausible, Fathom, GA, Umami, …).
1318
- scripts: z.array(analyticsScriptSchema).optional(),
1319
- vercel: z.boolean().optional(),
1320
- });
1143
+ /**
1144
+ * A pattern segment in a redirect path: a named `:param` segment or a `*`
1145
+ * splat. `from` is matched as an exact path, and hosts disagree on patterns —
1146
+ * a static build would even write a literal `:slug` folder — so both ends are
1147
+ * checked. An absolute `to` URL's own scheme and host are skipped.
1148
+ */
1149
+ const REDIRECT_PATTERN = /(?:^|\/):[A-Za-z_]|\*/u;
1150
+ const URL_ORIGIN = /^[a-z][\d+.a-z-]*:\/\/[^/]*/iu;
1321
1151
 
1322
- const deploymentConfigSchema = z.strictObject({
1323
- adapter: z
1324
- .enum(["vercel", "node", "netlify", "cloudflare"])
1325
- .nullable()
1326
- .default(null),
1327
- base: z.string().optional(),
1328
- output: z.enum(["static", "server"]).default("static"),
1329
- site: z.url().optional(),
1330
- });
1152
+ const exactRedirectPath = (end: "from" | "to") =>
1153
+ z
1154
+ .string()
1155
+ .refine((path) => !REDIRECT_PATTERN.test(path.replace(URL_ORIGIN, "")), {
1156
+ message: `redirects take exact paths: \`${end}\` can't hold a \`:param\` segment or a \`*\` wildcard. Add one redirect per path, or put pattern rules in your host's redirect config (vercel.json, _redirects).`,
1157
+ });
1331
1158
 
1332
1159
  const redirectSchema = z.strictObject({
1333
- from: z.string(),
1160
+ from: exactRedirectPath("from"),
1334
1161
  status: z
1335
1162
  .union([z.literal(301), z.literal(302), z.literal(307), z.literal(308)])
1336
1163
  .default(301),
1337
- to: z.string(),
1164
+ to: exactRedirectPath("to"),
1338
1165
  });
1339
1166
 
1340
1167
  /**
@@ -1552,15 +1379,7 @@ const softwareConfigSchema = z.strictObject({
1552
1379
  type SoftwareResolved = z.output<typeof softwareConfigSchema>;
1553
1380
 
1554
1381
  /** Discoverability features: OG images, feeds, sitemap, structured data. */
1555
- const seoConfigSchema = z.strictObject({
1556
- /**
1557
- * Emit `agent-readability.json` at the site root: a manifest that indexes
1558
- * the agent-facing surface (llms.txt, Markdown mirrors, MCP server, feeds)
1559
- * so agents can discover it without scraping HTML.
1560
- */
1561
- agentReadability: z.boolean().default(true),
1562
- /** robots.txt `Content-Signal` usage declaration (on by default). */
1563
- contentSignals: contentSignalsSchema.prefault(true),
1382
+ const seoConfigFields = {
1564
1383
  og: ogConfigSchema.default({}),
1565
1384
  /** The organization behind the site, as an `Organization` JSON-LD node. */
1566
1385
  organization: organizationConfigSchema.optional(),
@@ -1583,6 +1402,114 @@ const seoConfigSchema = z.strictObject({
1583
1402
  structuredData: z.boolean().default(true),
1584
1403
  /** X (Twitter) account attribution for share cards. */
1585
1404
  x: xConfigSchema.default({}),
1405
+ };
1406
+
1407
+ const seoConfigSchema = z.strictObject(
1408
+ seoConfigFields,
1409
+ removedKeysHint({
1410
+ agentReadability: "seo.agentReadability moved to agents.agentReadability.",
1411
+ contentSignals: "seo.contentSignals moved to agents.contentSignals.",
1412
+ })
1413
+ );
1414
+
1415
+ /**
1416
+ * The machine-readable surface agents consume: the JSON API, `llms.txt`, the
1417
+ * MCP server, published skills, discovery manifests, and the robots.txt
1418
+ * usage policy. Everything reader-facing that talks to a model (Ask AI, Open
1419
+ * in chat) stays under `ai`.
1420
+ */
1421
+ const agentsConfigSchema = z.strictObject({
1422
+ /**
1423
+ * Emit `agent-readability.json` at the site root: a manifest that indexes
1424
+ * the agent-facing surface (llms.txt, Markdown mirrors, MCP server, feeds)
1425
+ * so agents can discover it without scraping HTML.
1426
+ */
1427
+ agentReadability: z.boolean().default(true),
1428
+ /**
1429
+ * The JSON docs API: the page index, per-page JSON, and navigation under
1430
+ * `/api/docs/` (prerendered, so a static site serves them from files), the
1431
+ * live search endpoint on server output, and the OpenAPI description of
1432
+ * the whole machine-readable surface at `/openapi.json`. On by default.
1433
+ */
1434
+ api: z.boolean().default(true),
1435
+ /**
1436
+ * The AI Catalog / ARD manifest at `/.well-known/ai-catalog.json` (mirrored
1437
+ * at `/.well-known/ard.json`): one entry per agent-facing resource the site
1438
+ * publishes — the MCP server card, each agent skill, the JSON docs API's
1439
+ * OpenAPI document, each rendered API reference, and llms.txt — so agent
1440
+ * registries can index the site from its domain alone. Needs a
1441
+ * `deployment.site` (identifiers are domain-anchored URNs). On by default;
1442
+ * the object form overrides the generated representative queries.
1443
+ */
1444
+ catalog: z
1445
+ .union([z.boolean(), aiCatalogObjectSchema])
1446
+ .default(true)
1447
+ .transform((value): AiCatalogResolved =>
1448
+ isBoolean(value) ? { enabled: value, queries: {} } : value
1449
+ ),
1450
+ /** robots.txt `Content-Signal` usage declaration (on by default). */
1451
+ contentSignals: contentSignalsSchema.prefault(true),
1452
+ /**
1453
+ * `llms.txt`/`llms-full.txt` emission. A bare boolean toggles it; the object
1454
+ * form adds `openapi: false` to keep generated API reference pages out of
1455
+ * both files (e.g. when the configured spec is example content) and
1456
+ * `details`, free-form Markdown placed after the summary — the llms.txt
1457
+ * spec's details block, where a site tells agents when to reach for it.
1458
+ */
1459
+ llmsTxt: z
1460
+ .union([z.boolean(), llmsTxtObjectSchema])
1461
+ .default(true)
1462
+ .transform((value): LlmsTxtResolved =>
1463
+ isBoolean(value) ? { enabled: value, openapi: true } : value
1464
+ ),
1465
+ // Serializers for the agent-facing Markdown downlevel (the `.md` mirror,
1466
+ // llms-full.txt, MCP get_page), keyed by JSX name. Functions live here —
1467
+ // not in components.tsx — because the config file is executed at build
1468
+ // time while the components file is only statically analyzed. A same-name
1469
+ // entry replaces the built-in serializer.
1470
+ // Two-argument `z.record` — the single-argument form throws at
1471
+ // schema-construction time under Zod 4 (see uiStringsOverrideSchema).
1472
+ markdownComponents: z
1473
+ .record(
1474
+ z.string(),
1475
+ z.custom<ComponentMarkdown>(
1476
+ (value): value is ComponentMarkdown => typeof value === "function",
1477
+ {
1478
+ message: "Expected a serializer function.",
1479
+ }
1480
+ )
1481
+ )
1482
+ .default({}),
1483
+ /** Expose the docs as an MCP server for connecting agents. */
1484
+ mcp: mcpConfigSchema.prefault({}),
1485
+ /**
1486
+ * Publish Agent Skills for discovery: a directory (resolved against the
1487
+ * project root) whose subdirectories each hold a `SKILL.md`. The build
1488
+ * copies each skill under `/.well-known/agent-skills/` — a lone `SKILL.md`
1489
+ * verbatim, a skill with supporting files as a `.tar.gz` — and emits the
1490
+ * discovery index (`index.json`) with SHA-256 digests per the Agent Skills
1491
+ * Discovery RFC.
1492
+ */
1493
+ skills: z.string().min(1).optional(),
1494
+ /**
1495
+ * Web Bot Auth (IETF `webbotauth`): publish the org's HTTP Message
1496
+ * Signature public keys at `/.well-known/http-message-signatures-directory`
1497
+ * so sites receiving requests from the org's agents can verify them.
1498
+ * Opt-in and public-keys-only — the private keys live wherever the signing
1499
+ * agents run, never in the site.
1500
+ */
1501
+ webBotAuth: z
1502
+ .strictObject({
1503
+ keys: z.array(publicJwkSchema).default([]),
1504
+ })
1505
+ .prefault({}),
1506
+ /**
1507
+ * WebMCP: register in-page tools (search, page Markdown, the docs index)
1508
+ * on the browser's model context so agentic browsers can drive the docs
1509
+ * without a separate MCP connection. A tiny script that no-ops in browsers
1510
+ * without the API; on by default.
1511
+ */
1512
+ webmcp: z.boolean().default(true),
1586
1513
  });
1587
1514
 
1588
1515
  /**
@@ -1659,10 +1586,6 @@ const codeBlockThemeSchema = z.strictObject({
1659
1586
  light: codeThemeSchema.default("github-light"),
1660
1587
  });
1661
1588
 
1662
- const codeBlocksConfigSchema = z.strictObject({
1663
- theme: codeBlockThemeSchema.prefault({}),
1664
- });
1665
-
1666
1589
  /**
1667
1590
  * `<Component />` example previews. A string is shorthand for `{ source }`:
1668
1591
  * where examples live, relative to the project root (default `examples`).
@@ -1694,13 +1617,20 @@ const examplesConfigSchema = z
1694
1617
 
1695
1618
  /**
1696
1619
  * "Last updated" timestamps for content pages. `false` (default) disables the
1697
- * feature; `true` derives each page's date from git history; an object selects
1698
- * the source explicitly. A page's `lastModified` frontmatter always wins.
1620
+ * feature; `"git"` derives each page's date from the repository history;
1621
+ * `"frontmatter"` never runs git and reads only the page's own field. A page's
1622
+ * `lastModified` frontmatter always wins. The 1.x `true` and `{ type }` forms
1623
+ * fail with the hint below.
1699
1624
  */
1700
- const lastModifiedConfigSchema = z.union([
1701
- z.boolean(),
1702
- z.strictObject({ type: z.enum(["git", "frontmatter"]).default("git") }),
1703
- ]);
1625
+ const lastModifiedConfigSchema = z.union(
1626
+ [z.literal(false), z.enum(["git", "frontmatter"])],
1627
+ {
1628
+ error: (issue) =>
1629
+ issue.code === "invalid_union"
1630
+ ? 'lastModified takes false, "git", or "frontmatter": `true` became "git" and `{ type: "…" }` became the bare string.'
1631
+ : undefined,
1632
+ }
1633
+ );
1704
1634
 
1705
1635
  /**
1706
1636
  * How the "last updated" stamp and the changelog timeline render their dates —
@@ -1745,13 +1675,18 @@ const dateFormatConfigSchema = z
1745
1675
  }
1746
1676
  );
1747
1677
 
1748
- /** Code-block rendering options (`markdown.code`). */
1678
+ /** Code rendering options (`markdown.code`). */
1749
1679
  const codeConfigSchema = z.strictObject({
1750
1680
  /**
1751
1681
  * Show a brand language icon in the code-block header (TypeScript, Python,
1752
1682
  * …). On by default; recognized languages only.
1753
1683
  */
1754
1684
  icons: z.boolean().default(true),
1685
+ /**
1686
+ * Light/dark Shiki themes for every code surface: fenced blocks, inline
1687
+ * `` `code`{:lang} ``, `<CodeBlock>`, and `<Diff>`.
1688
+ */
1689
+ theme: codeBlockThemeSchema.prefault({}),
1755
1690
  /**
1756
1691
  * Wrap long lines instead of scrolling horizontally. Off by default, so
1757
1692
  * code keeps its original line breaks and overflows into a scroll area.
@@ -1759,10 +1694,17 @@ const codeConfigSchema = z.strictObject({
1759
1694
  wrap: z.boolean().default(false),
1760
1695
  });
1761
1696
 
1762
- const markdownConfigSchema = z.strictObject({
1763
- /** Code-block rendering: language icons and line wrapping. */
1697
+ const markdownConfigFields = {
1698
+ /** Code rendering: language icons, syntax themes, and line wrapping. */
1764
1699
  code: codeConfigSchema.prefault({}),
1765
- codeBlocks: codeBlocksConfigSchema.prefault({}),
1700
+ /**
1701
+ * Open external Markdown links (absolute `http(s)://` and `//host` URLs) in
1702
+ * a new tab, like Blume's own header and sidebar links: `target="_blank"`,
1703
+ * `rel="noreferrer"`, an arrow icon, and a screen-reader "Opens in a new
1704
+ * tab" hint. Off by default; site routes, fragments, and `mailto:`/`tel:`
1705
+ * links are never affected.
1706
+ */
1707
+ externalLinks: z.boolean().default(false),
1766
1708
  /**
1767
1709
  * Wrap each `##`–`######` heading in a link to its own anchor so readers can
1768
1710
  * click to copy, bookmark, or share a permalink to that section. On by
@@ -1774,7 +1716,15 @@ const markdownConfigSchema = z.strictObject({
1774
1716
  * opt a single image out with `data-no-zoom`.
1775
1717
  */
1776
1718
  imageZoom: z.boolean().default(true),
1777
- });
1719
+ };
1720
+
1721
+ const markdownConfigSchema = z.strictObject(
1722
+ markdownConfigFields,
1723
+ removedKeysHint({
1724
+ codeBlocks:
1725
+ "markdown.codeBlocks was merged into markdown.code: move theme: { light, dark } under markdown.code.",
1726
+ })
1727
+ );
1778
1728
 
1779
1729
  /** React island behavior (`react`). */
1780
1730
  const reactConfigSchema = z.strictObject({
@@ -1787,163 +1737,6 @@ const reactConfigSchema = z.strictObject({
1787
1737
  compiler: z.boolean().default(true),
1788
1738
  });
1789
1739
 
1790
- /**
1791
- * A single spec rendered by the API reference. `spec` is a local path or an
1792
- * `http(s)` URL (an OpenAPI document under `openapi`, an AsyncAPI document
1793
- * under `asyncapi`).
1794
- */
1795
- const openapiSourceSchema = z.strictObject({
1796
- /** Include generated pages from this spec in llms.txt/llms-full.txt. */
1797
- includeInLlms: z.boolean().default(true),
1798
- /** Include generated pages from this spec in site search. */
1799
- includeInSearch: z.boolean().default(true),
1800
- /** Nav/section label for this source. */
1801
- label: z.string().optional(),
1802
- /** Emit noindex metadata and omit generated pages from the sitemap. */
1803
- noindex: z.boolean().default(false),
1804
- /** Per-source route; defaults to the block's `route` (or a derived path). */
1805
- route: z.string().optional(),
1806
- /**
1807
- * Append the English "Reference for the … endpoint in the … API." sentence
1808
- * to every generated operation page's meta description. On by default, so
1809
- * terse specs still ship distinct, snippet-length descriptions; set to
1810
- * `false` on a non-English site to describe pages with the spec's own prose
1811
- * alone (falling back to the page title when an operation has none).
1812
- */
1813
- seoDescriptionSuffix: z.boolean().default(true),
1814
- /** Local path or `http(s)` URL to the spec. */
1815
- spec: z.string(),
1816
- });
1817
-
1818
- export type OpenApiSource = z.input<typeof openapiSourceSchema>;
1819
-
1820
- /**
1821
- * Arbitrary Scalar API-reference options forwarded verbatim to the generated
1822
- * `<ScalarComponent>` (Scalar renderer only). A passthrough map — Blume doesn't
1823
- * mirror Scalar's full config surface — so keys like `localization`, `agent`,
1824
- * `hideTestRequestButton`, or `orderSchemaPropertiesBy` all flow through. These
1825
- * take precedence over Blume's own derived config (spec, theme), so this is a
1826
- * full escape hatch; the dedicated `theme` field is the ergonomic shorthand.
1827
- */
1828
- const scalarConfigSchema = z.record(z.string(), z.unknown()).optional();
1829
-
1830
- /**
1831
- * The interactive "Try it" panel on operation pages (Blume renderer). On by
1832
- * default; `false` hides it. The object form keeps it on and sets `proxy`,
1833
- * the CORS escape hatch the Send button routes requests through: a proxy URL,
1834
- * or `true` for the built-in `/_api-proxy` endpoint (which requires
1835
- * `deployment.output: "server"`). Booleans normalize to the object shape so
1836
- * consumers read `{ enabled, proxy }` directly. `proxy` applies to the
1837
- * HTTP-posting playgrounds (OpenAPI, GraphQL) — an event composer's WebSocket
1838
- * connect is direct. One schema for every reference block, so the
1839
- * normalization can never drift between them.
1840
- */
1841
- const playgroundConfigSchema = z
1842
- .union([
1843
- z.boolean(),
1844
- z.strictObject({
1845
- enabled: z.boolean().default(true),
1846
- proxy: z.union([z.boolean(), z.string()]).default(false),
1847
- }),
1848
- ])
1849
- .default(true)
1850
- .transform((value) =>
1851
- isBoolean(value) ? { enabled: value, proxy: false } : value
1852
- );
1853
-
1854
- /**
1855
- * The shared shape of the API-reference blocks — only the mount route and
1856
- * code-sample defaults differ per spec kind, so each block declares just
1857
- * those (the GraphQL block derives from this via omit/extend below).
1858
- */
1859
- const referenceConfigSchema = (defaults: {
1860
- codeSamples: string[];
1861
- route: string;
1862
- }) =>
1863
- z.strictObject({
1864
- /** Code-sample languages/tools shown per operation (Blume renderer). */
1865
- codeSamples: z.array(z.string()).default(defaults.codeSamples),
1866
- enabled: z.boolean().default(false),
1867
- /** Start nested schema rows expanded rather than collapsed (Blume renderer). */
1868
- expandSchemas: z.boolean().default(false),
1869
- /** The "Try it" panel; see {@link playgroundConfigSchema}. */
1870
- playground: playgroundConfigSchema,
1871
- /** Who renders the reference: Blume's own UI, or the embedded Scalar SPA. */
1872
- renderer: z.enum(["blume", "scalar"]).default("blume"),
1873
- /** Where the reference mounts. */
1874
- route: z.string().default(defaults.route),
1875
- /** Extra Scalar config forwarded to `<ScalarComponent>` (Scalar renderer only). */
1876
- scalar: scalarConfigSchema,
1877
- /** One or more specs; each renders on its own route by default. */
1878
- sources: z.array(openapiSourceSchema).default([]),
1879
- /** Shorthand for a single source: `sources: [{ spec }]`. */
1880
- spec: z.string().optional(),
1881
- /** Scalar theme name (Scalar renderer only). */
1882
- theme: z.string().optional(),
1883
- });
1884
-
1885
- /**
1886
- * OpenAPI reference. By default (`renderer: "blume"`) Blume parses the spec with
1887
- * Scalar's parser and renders its own UI: one real page per operation, grouped
1888
- * by tag in the sidebar and included in site search, llms.txt, and OG. Set
1889
- * `renderer: "scalar"` to fall back to the embedded Scalar SPA (a single
1890
- * self-contained route that doesn't weave into the sidebar or search).
1891
- */
1892
- const openapiConfigSchema = referenceConfigSchema({
1893
- codeSamples: ["curl", "js", "python"],
1894
- route: "/reference",
1895
- });
1896
-
1897
- /**
1898
- * AsyncAPI reference. Same shape as {@link openapiConfigSchema}: by default
1899
- * (`renderer: "blume"`) Blume normalizes the spec to AsyncAPI 3.x and renders
1900
- * its own UI — one real page per operation — with `renderer: "scalar"` as the
1901
- * embedded-SPA opt-out. Only the defaults differ: the reference mounts at
1902
- * `/events`, and empty `codeSamples` means every tool the operation's protocol
1903
- * binding suggests.
1904
- */
1905
- const asyncapiConfigSchema = referenceConfigSchema({
1906
- codeSamples: [],
1907
- route: "/events",
1908
- });
1909
-
1910
- /**
1911
- * A single GraphQL schema rendered by the reference. `spec` is a local path or
1912
- * an `http(s)` URL to SDL text or an introspection JSON result; `endpoint` is
1913
- * the live GraphQL API URL the playground and code samples target (a schema,
1914
- * unlike an OpenAPI document, names no server).
1915
- */
1916
- const graphqlSourceSchema = openapiSourceSchema.extend({
1917
- /** URL of the live GraphQL endpoint (playground + code samples). */
1918
- endpoint: z.string().optional(),
1919
- });
1920
-
1921
- export type GraphqlSource = z.input<typeof graphqlSourceSchema>;
1922
-
1923
- /**
1924
- * GraphQL reference. Blume lowers the schema (SDL or introspection JSON) to
1925
- * one real page per root field — grouped as Queries/Mutations/Subscriptions —
1926
- * plus one page per named type (Objects, Input Objects, Enums, Interfaces,
1927
- * Unions, Scalars), all included in the sidebar, search, llms.txt, and OG.
1928
- * Always Blume-rendered: the Scalar SPA reads OpenAPI documents only, so the
1929
- * block declares no `renderer`/`scalar`/`theme` escape hatches.
1930
- */
1931
- const graphqlConfigSchema = referenceConfigSchema({
1932
- codeSamples: ["curl", "js", "python"],
1933
- route: "/graphql",
1934
- })
1935
- // No `renderer`/`scalar`/`theme` escape hatches (the Scalar SPA reads
1936
- // OpenAPI documents only) and no `expandSchemas` (GraphQL field tables have
1937
- // no nesting) — everything else, the playground normalization included, is
1938
- // the shared reference shape.
1939
- .omit({ expandSchemas: true, renderer: true, scalar: true, theme: true })
1940
- .extend({
1941
- /** Default live endpoint URL for every source (per-source `endpoint` wins). */
1942
- endpoint: z.string().optional(),
1943
- /** One or more schemas; each renders on its own route by default. */
1944
- sources: z.array(graphqlSourceSchema).default([]),
1945
- });
1946
-
1947
1740
  /**
1948
1741
  * Opt-in custom frontmatter keys. `extend` maps each extra key a project's
1949
1742
  * pages may carry (e.g. `owner`, `reviewedAt`) to a validation schema; the
@@ -1993,60 +1786,77 @@ const tocConfigSchema = z
1993
1786
  });
1994
1787
 
1995
1788
  export const blumeConfigSchema = z
1996
- .strictObject({
1997
- ai: aiConfigSchema.prefault({}),
1998
- analytics: analyticsConfigSchema.optional(),
1999
- asyncapi: asyncapiConfigSchema.prefault({}),
2000
- banner: bannerConfigSchema.optional(),
2001
- /**
2002
- * Site-wide mount point prepended to every generated route (e.g. `/docs`),
2003
- * while staying invisible to the sidebar/nav tree. Distinct from a per-source
2004
- * `prefix` (which creates a group) and from `deployment.base` (Astro's
2005
- * host-subdirectory base); the two compose. Normalized to `""` or `/seg`.
2006
- */
2007
- basePath: z
2008
- .string()
2009
- .optional()
2010
- .transform((value) => normalizeBasePath(value)),
2011
- content: contentConfigSchema.prefault({}),
2012
- /**
2013
- * Date presentation for the "last updated" stamp and the changelog timeline.
2014
- * Pass-through `Intl.DateTimeFormat` options; defaults to `{ dateStyle: "long" }`.
2015
- */
2016
- dateFormat: dateFormatConfigSchema.default({ dateStyle: "long" }),
2017
- deployment: deploymentConfigSchema.prefault({}),
2018
- description: z.string().optional(),
2019
- /**
2020
- * Where `<Component path>` resolves live previews and their source from.
2021
- * A string is shorthand for `{ source }` — the directory (or glob, for
2022
- * colocated registry layouts) under the project root that holds example
2023
- * files. The object form adds `css`: a stylesheet injected into every
2024
- * preview frame (design tokens, shadcn variables, `@theme` mappings).
2025
- */
2026
- examples: examplesConfigSchema.prefault("examples"),
2027
- export: exportConfigSchema.prefault(false),
2028
- feedback: z.boolean().default(true),
2029
- /** Opt-in custom frontmatter keys, validated by user-supplied schemas. */
2030
- frontmatter: frontmatterConfigSchema.prefault({}),
2031
- github: githubConfigSchema.optional(),
2032
- graphql: graphqlConfigSchema.prefault({}),
2033
- i18n: i18nConfigSchema.optional(),
2034
- image: imageConfigSchema.prefault({}),
2035
- integrations: z.array(z.custom<AstroIntegration>()).default([]),
2036
- lastModified: lastModifiedConfigSchema.default(false),
2037
- logo: logoConfigSchema.optional(),
2038
- markdown: markdownConfigSchema.prefault({}),
2039
- navigation: navigationConfigSchema.prefault({}),
2040
- openapi: openapiConfigSchema.prefault({}),
2041
- react: reactConfigSchema.prefault({}),
2042
- redirects: z.array(redirectSchema).default([]),
2043
- search: searchConfigSchema.prefault({}),
2044
- seo: seoConfigSchema.prefault({}),
2045
- theme: themeConfigSchema.prefault({}),
2046
- title: z.string().default("Documentation"),
2047
- toc: tocConfigSchema,
2048
- versions: versionsConfigSchema.optional(),
2049
- })
1789
+ .strictObject(
1790
+ {
1791
+ agents: agentsConfigSchema.prefault({}),
1792
+ ai: aiConfigSchema.prefault({}),
1793
+ // Adapters from `blume/analytics`, each a serializable descriptor.
1794
+ analytics: analyticsConfigSchema,
1795
+ banner: bannerConfigSchema.optional(),
1796
+ /**
1797
+ * Site-wide mount point prepended to every generated route (e.g. `/docs`),
1798
+ * while staying invisible to the sidebar/nav tree. Distinct from a per-source
1799
+ * `prefix` (which creates a group) and from `deployment.base` (Astro's
1800
+ * host-subdirectory base); the two compose. Normalized to `""` or `/seg`.
1801
+ */
1802
+ basePath: z
1803
+ .string()
1804
+ .optional()
1805
+ .transform((value) => normalizeBasePath(value)),
1806
+ content: contentConfigSchema.prefault({}),
1807
+ /**
1808
+ * Date presentation for the "last updated" stamp and the changelog timeline.
1809
+ * Pass-through `Intl.DateTimeFormat` options; defaults to `{ dateStyle: "long" }`.
1810
+ */
1811
+ dateFormat: dateFormatConfigSchema.default({ dateStyle: "long" }),
1812
+ /**
1813
+ * A host adapter from `blume/deploy` (`vercel()`, `netlify()`,
1814
+ * `cloudflare()`, `node()`) for a server build on that host, or the plain
1815
+ * `{ site, base }` form for a static build anywhere. Resolves to the
1816
+ * adapter's descriptor with `output` filled in; `static` when unset.
1817
+ */
1818
+ deployment: resolvedDeploymentSchema.prefault({}),
1819
+ description: z.string().optional(),
1820
+ /**
1821
+ * Where `<Component path>` resolves live previews and their source from.
1822
+ * A string is shorthand for `{ source }` — the directory (or glob, for
1823
+ * colocated registry layouts) under the project root that holds example
1824
+ * files. The object form adds `css`: a stylesheet injected into every
1825
+ * preview frame (design tokens, shadcn variables, `@theme` mappings).
1826
+ */
1827
+ examples: examplesConfigSchema.prefault("examples"),
1828
+ export: exportConfigSchema.prefault(false),
1829
+ feedback: z.boolean().default(true),
1830
+ /** Opt-in custom frontmatter keys, validated by user-supplied schemas. */
1831
+ frontmatter: frontmatterConfigSchema.prefault({}),
1832
+ github: githubConfigSchema.optional(),
1833
+ i18n: i18nConfigSchema.optional(),
1834
+ image: imageConfigSchema.prefault({}),
1835
+ integrations: z.array(z.custom<AstroIntegration>()).default([]),
1836
+ lastModified: lastModifiedConfigSchema.default(false),
1837
+ logo: logoConfigSchema.optional(),
1838
+ markdown: markdownConfigSchema.prefault({}),
1839
+ navigation: navigationConfigSchema.prefault({}),
1840
+ react: reactConfigSchema.prefault({}),
1841
+ redirects: z.array(redirectSchema).default([]),
1842
+ /** API references: adapters from `blume/reference`, each a serializable descriptor. */
1843
+ reference: referenceConfigSchema,
1844
+ search: searchConfigSchema.prefault({}),
1845
+ seo: seoConfigSchema.prefault({}),
1846
+ theme: themeConfigSchema.prefault({}),
1847
+ title: z.string().default("Documentation"),
1848
+ toc: tocConfigSchema,
1849
+ versions: versionsConfigSchema.optional(),
1850
+ },
1851
+ {
1852
+ // The 1.x `openapi`/`asyncapi`/`graphql` blocks name the `reference` list
1853
+ // that replaced them, alongside every other issue in the config.
1854
+ error: (issue) =>
1855
+ issue.code === "unrecognized_keys"
1856
+ ? removedReferenceKeysHint(issue.keys)
1857
+ : undefined,
1858
+ }
1859
+ )
2050
1860
  .superRefine((config, ctx) => {
2051
1861
  // A version id that is also a configured locale code would make a leading
2052
1862
  // `<id>/` directory ambiguous between the two axes — refuse it outright so
@@ -2118,8 +1928,8 @@ export type ArchivedVersionConfig = z.infer<typeof archivedVersionSchema>;
2118
1928
  * guard keeps structurally identical to this.
2119
1929
  */
2120
1930
  export type BlumeConfigInput = z.input<typeof blumeConfigSchema>;
2121
- /** A configured search backend. */
2122
- export type SearchProvider = (typeof searchProviders)[number];
1931
+ /** The resolved search backend: an adapter descriptor, or `none`. */
1932
+ export type { ResolvedSearchAdapter } from "../search/adapters/registry.ts";
2123
1933
  /** Resolved robots.txt `Content-Signal` preferences (`null` when disabled). */
2124
1934
  export type ContentSignals = z.infer<typeof contentSignalsSchema>;
2125
1935
  /** The resolved per-signal policy object (present when signals are enabled). */