blume 1.7.3 → 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 (673) hide show
  1. package/AGENTS.md +19 -0
  2. package/CHANGELOG.md +217 -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-rqy0s5wh.js → chunk-1w8dp3qb.js} +17 -16
  7. package/dist/cli/{chunk-rqy0s5wh.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-5g0w1e2c.js → chunk-6k8vp3ta.js} +25 -13
  15. package/dist/cli/{chunk-5g0w1e2c.js.map → chunk-6k8vp3ta.js.map} +4 -4
  16. package/dist/cli/{chunk-5qk08vmp.js → chunk-79njf86q.js} +133 -54
  17. package/dist/cli/chunk-79njf86q.js.map +11 -0
  18. package/dist/cli/{chunk-e7f42gdj.js → chunk-7ez8ny0t.js} +2 -2
  19. package/dist/cli/{chunk-nn13znc2.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-b27xqwn9.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-0xjyb285.js → chunk-bw22s759.js} +15 -5
  33. package/dist/cli/{chunk-0xjyb285.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-ahnw3kxw.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-vacwm2hv.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-cjtn640a.js → chunk-fh5hj5jt.js} +43 -20
  48. package/dist/cli/chunk-fh5hj5jt.js.map +10 -0
  49. package/dist/cli/{chunk-yg63d42r.js → chunk-j8mw0za6.js} +86 -57
  50. package/dist/cli/chunk-j8mw0za6.js.map +35 -0
  51. package/dist/cli/{chunk-ct47dqpx.js → chunk-jts8mvcz.js} +67 -7
  52. package/dist/cli/{chunk-2mzebbbz.js.map → chunk-jts8mvcz.js.map} +6 -4
  53. package/dist/cli/{chunk-ps4m1xh4.js → chunk-mnqj32sj.js} +505 -542
  54. package/dist/cli/chunk-mnqj32sj.js.map +12 -0
  55. package/dist/cli/{chunk-8cjtbafj.js → chunk-mwt1k8n7.js} +100 -374
  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-3r45185y.js → chunk-pat2zzwc.js} +9 -11
  60. package/dist/cli/{chunk-3r45185y.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-4x36ddpw.js → chunk-sqn5t4q0.js} +81 -87
  64. package/dist/cli/chunk-sqn5t4q0.js.map +10 -0
  65. package/dist/cli/{chunk-bvwwhd84.js → chunk-tzne8qfq.js} +15 -15
  66. package/dist/cli/{chunk-bvwwhd84.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-k79xp7av.js → chunk-z1f5arsg.js} +219 -689
  74. package/dist/cli/chunk-z1f5arsg.js.map +36 -0
  75. package/dist/cli/{chunk-1jefwnfs.js → chunk-zg2gtj10.js} +1076 -2592
  76. package/dist/cli/chunk-zg2gtj10.js.map +35 -0
  77. package/dist/cli/{chunk-dwgcp5sm.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 +186 -515
  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 +27 -12
  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 +2282 -452
  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 +15 -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 +162 -61
  290. package/docs/configuration/customization.mdx +15 -9
  291. package/docs/configuration/index.mdx +50 -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 +35 -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 +9 -7
  303. package/docs/content/sources.mdx +145 -50
  304. package/docs/content/syntax.mdx +16 -12
  305. package/docs/content/versioning.mdx +3 -3
  306. package/docs/discoverability/agent-discovery.mdx +11 -11
  307. package/docs/discoverability/index.mdx +4 -4
  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 +21 -22
  329. package/src/ai/ai-catalog.ts +50 -32
  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 +7 -6
  338. package/src/ai/llms.ts +20 -15
  339. package/src/ai/markdown.ts +35 -5
  340. package/src/ai/mcp/data.ts +9 -5
  341. package/src/ai/openapi-components.ts +4 -1
  342. package/src/ai/relative-links.ts +170 -0
  343. package/src/ai/serializers.ts +2 -2
  344. package/src/ai/skills.ts +1 -1
  345. package/src/ai/web-bot-auth.ts +2 -2
  346. package/src/analytics/adobe.ts +46 -0
  347. package/src/analytics/amplitude.ts +79 -0
  348. package/src/analytics/clarity.ts +46 -0
  349. package/src/analytics/clearbit.ts +47 -0
  350. package/src/analytics/cloudflare.ts +67 -0
  351. package/src/analytics/fathom.ts +64 -0
  352. package/src/analytics/google-analytics.ts +84 -0
  353. package/src/analytics/google-tag-manager.ts +61 -0
  354. package/src/analytics/head.ts +130 -0
  355. package/src/analytics/heap.ts +65 -0
  356. package/src/analytics/hightouch.ts +77 -0
  357. package/src/analytics/hotjar.ts +47 -0
  358. package/src/analytics/index.ts +71 -0
  359. package/src/analytics/inline.ts +21 -0
  360. package/src/analytics/logrocket.ts +71 -0
  361. package/src/analytics/mixpanel.ts +92 -0
  362. package/src/analytics/pirsch.ts +66 -0
  363. package/src/analytics/plausible.ts +83 -0
  364. package/src/analytics/posthog.ts +78 -0
  365. package/src/analytics/schema.ts +60 -0
  366. package/src/analytics/script.ts +60 -0
  367. package/src/analytics/segment.ts +85 -0
  368. package/src/analytics/vercel.ts +50 -0
  369. package/src/astro/adapter-root.ts +7 -9
  370. package/src/astro/component-slots.ts +131 -91
  371. package/src/astro/generate.ts +112 -345
  372. package/src/astro/integration.ts +2 -6
  373. package/src/astro/pages.ts +13 -101
  374. package/src/astro/render-deps.ts +379 -0
  375. package/src/astro/runtime-deps.ts +201 -0
  376. package/src/astro/templates.ts +554 -541
  377. package/src/audit/agent.ts +24 -0
  378. package/src/audit/catalog.ts +2 -2
  379. package/src/audit/checks/assets.ts +2 -2
  380. package/src/audit/checks/dns-aid.ts +1 -1
  381. package/src/audit/checks/duplicates.ts +3 -1
  382. package/src/audit/checks/i18n.ts +1 -1
  383. package/src/audit/checks/indexability.ts +7 -7
  384. package/src/audit/checks/links.ts +2 -2
  385. package/src/audit/checks/llms.ts +6 -6
  386. package/src/audit/checks/network.ts +3 -3
  387. package/src/audit/checks/og-image.ts +2 -2
  388. package/src/audit/checks/robots.ts +1 -1
  389. package/src/audit/checks/sitemap.ts +2 -2
  390. package/src/audit/checks/social.ts +1 -1
  391. package/src/audit/run.ts +4 -3
  392. package/src/audit/terms.ts +31 -0
  393. package/src/audit/url.ts +13 -13
  394. package/src/cli/command-meta.ts +10 -0
  395. package/src/cli/commands/audit.ts +39 -20
  396. package/src/cli/commands/build.ts +71 -316
  397. package/src/cli/commands/check.ts +1 -0
  398. package/src/cli/commands/dev.ts +40 -1
  399. package/src/cli/commands/doctor.ts +90 -14
  400. package/src/cli/commands/eject.ts +44 -7
  401. package/src/cli/commands/eval.ts +2 -2
  402. package/src/cli/commands/init.ts +161 -41
  403. package/src/cli/commands/migrate.ts +121 -0
  404. package/src/cli/commands/preview.ts +7 -1
  405. package/src/cli/commands/translate.ts +2 -2
  406. package/src/cli/commands/upgrade.ts +141 -0
  407. package/src/cli/commands/version.ts +57 -44
  408. package/src/cli/eject-scripts.ts +121 -6
  409. package/src/cli/index.ts +11 -1
  410. package/src/cli/init/install.ts +70 -0
  411. package/src/cli/init/questions.ts +7 -0
  412. package/src/cli/init/scaffold.ts +459 -75
  413. package/src/cli/lazy-command.ts +37 -1
  414. package/src/cli/prepare.ts +40 -6
  415. package/src/cli/required-secrets.ts +38 -11
  416. package/src/cli/unknown-flags.ts +266 -0
  417. package/src/cli/yarn-pnp.ts +52 -0
  418. package/src/components/content/AccordionItem.astro +7 -1
  419. package/src/components/content/Card.astro +4 -3
  420. package/src/components/content/ColorItem.astro +22 -4
  421. package/src/components/content/GithubInfo.astro +2 -2
  422. package/src/components/content/Prompt.astro +25 -25
  423. package/src/components/content/Tabs.astro +3 -1
  424. package/src/components/content/Tile.astro +2 -3
  425. package/src/components/content/Tooltip.astro +57 -9
  426. package/src/components/content/content-strings.ts +34 -0
  427. package/src/components/content/diff.ts +1 -1
  428. package/src/components/content/mermaid-element.ts +13 -2
  429. package/src/components/content/prompt-markdown.ts +292 -0
  430. package/src/components/content/tooltip-id.ts +41 -0
  431. package/src/components/islands/hooks.ts +25 -2
  432. package/src/components/layout/Analytics.astro +21 -79
  433. package/src/components/layout/Banner.astro +3 -1
  434. package/src/components/layout/DiscoveryLinks.astro +69 -0
  435. package/src/components/layout/Header.astro +83 -20
  436. package/src/components/layout/LanguageSwitcher.astro +4 -1
  437. package/src/components/layout/Logo.astro +23 -2
  438. package/src/components/layout/NavSelector.astro +13 -2
  439. package/src/components/layout/NavTree.astro +12 -3
  440. package/src/components/layout/NavTreeScript.astro +17 -3
  441. package/src/components/layout/PageActions.astro +3 -3
  442. package/src/components/layout/PageFeedback.astro +11 -1
  443. package/src/components/layout/PageLayout.astro +52 -5
  444. package/src/components/layout/ReferenceLayout.astro +21 -12
  445. package/src/components/layout/RootLayout.astro +69 -66
  446. package/src/components/layout/Search.astro +30 -6
  447. package/src/components/layout/WebMcp.astro +1 -1
  448. package/src/components/layout/analytics-client.ts +72 -15
  449. package/src/components/layout/drawer-inert.ts +113 -15
  450. package/src/components/layout/dropdown-clamp.ts +105 -0
  451. package/src/components/layout/head-scripts.ts +15 -6
  452. package/src/components/layout/nav-utils.ts +17 -0
  453. package/src/components/layout/search/algolia.ts +8 -8
  454. package/src/components/layout/search/orama-cloud.ts +9 -7
  455. package/src/components/layout/search/typesense.ts +11 -17
  456. package/src/components/openapi/MessageComposer.astro +2 -2
  457. package/src/components/openapi/Playground.astro +2 -2
  458. package/src/components/openapi/playground-client.ts +79 -10
  459. package/src/core/changelog-index.ts +24 -0
  460. package/src/core/component-overrides.ts +399 -154
  461. package/src/core/config-input.ts +188 -553
  462. package/src/core/config.ts +130 -41
  463. package/src/core/custom-pages.ts +105 -0
  464. package/src/core/data.ts +31 -12
  465. package/src/core/define-components.ts +12 -9
  466. package/src/core/deployment-env.ts +18 -74
  467. package/src/core/diagnostics.ts +14 -7
  468. package/src/core/graph.ts +69 -25
  469. package/src/core/i18n-ui.ts +6 -2
  470. package/src/core/i18n.ts +17 -0
  471. package/src/core/includes.ts +156 -38
  472. package/src/core/last-modified.ts +6 -11
  473. package/src/core/links.ts +166 -2
  474. package/src/core/manifest.ts +10 -3
  475. package/src/core/navigation.ts +115 -16
  476. package/src/core/new-tab.ts +35 -0
  477. package/src/core/node-require.ts +21 -0
  478. package/src/core/project-graph.ts +42 -26
  479. package/src/core/project.ts +9 -4
  480. package/src/core/request-body.ts +61 -0
  481. package/src/core/safe-href.ts +28 -0
  482. package/src/core/safe-links.ts +68 -0
  483. package/src/core/schema.ts +517 -739
  484. package/src/core/server-features.ts +8 -9
  485. package/src/core/sources/assets.ts +47 -11
  486. package/src/core/sources/collection.ts +67 -0
  487. package/src/core/sources/contentful-rich-text.ts +285 -0
  488. package/src/core/sources/contentful.ts +173 -0
  489. package/src/core/sources/github-releases.ts +51 -1
  490. package/src/core/sources/json.ts +71 -0
  491. package/src/core/sources/lexical.ts +195 -0
  492. package/src/core/sources/lower.ts +226 -0
  493. package/src/core/sources/normalize.ts +52 -2
  494. package/src/core/sources/notion.ts +39 -28
  495. package/src/core/sources/payload.ts +135 -0
  496. package/src/core/sources/portable-text.ts +11 -16
  497. package/src/core/sources/remote.ts +226 -0
  498. package/src/core/sources/resolve.ts +104 -166
  499. package/src/core/sources/sanity.ts +16 -49
  500. package/src/core/sources/strapi-blocks.ts +124 -0
  501. package/src/core/sources/strapi.ts +191 -0
  502. package/src/core/sources/types.ts +12 -1
  503. package/src/core/types.ts +15 -1
  504. package/src/core/ui-packs/ar.ts +6 -2
  505. package/src/core/ui-packs/bg.ts +6 -2
  506. package/src/core/ui-packs/bn.ts +6 -2
  507. package/src/core/ui-packs/ca.ts +3 -1
  508. package/src/core/ui-packs/cs.ts +6 -2
  509. package/src/core/ui-packs/da.ts +6 -2
  510. package/src/core/ui-packs/de.ts +6 -2
  511. package/src/core/ui-packs/el.ts +3 -1
  512. package/src/core/ui-packs/es.ts +3 -1
  513. package/src/core/ui-packs/fa.ts +6 -2
  514. package/src/core/ui-packs/fi.ts +6 -2
  515. package/src/core/ui-packs/fr.ts +3 -1
  516. package/src/core/ui-packs/he.ts +6 -2
  517. package/src/core/ui-packs/hi.ts +6 -2
  518. package/src/core/ui-packs/hr.ts +6 -2
  519. package/src/core/ui-packs/hu.ts +6 -2
  520. package/src/core/ui-packs/id.ts +6 -2
  521. package/src/core/ui-packs/it.ts +3 -1
  522. package/src/core/ui-packs/ja.ts +3 -1
  523. package/src/core/ui-packs/ko.ts +3 -1
  524. package/src/core/ui-packs/nl.ts +6 -2
  525. package/src/core/ui-packs/no.ts +6 -2
  526. package/src/core/ui-packs/pl.ts +6 -2
  527. package/src/core/ui-packs/pt-br.ts +3 -1
  528. package/src/core/ui-packs/pt.ts +3 -1
  529. package/src/core/ui-packs/ro.ts +6 -2
  530. package/src/core/ui-packs/ru.ts +6 -2
  531. package/src/core/ui-packs/sk.ts +6 -2
  532. package/src/core/ui-packs/sr.ts +6 -2
  533. package/src/core/ui-packs/sv.ts +6 -2
  534. package/src/core/ui-packs/th.ts +3 -1
  535. package/src/core/ui-packs/tr.ts +6 -2
  536. package/src/core/ui-packs/uk.ts +6 -2
  537. package/src/core/ui-packs/vi.ts +3 -1
  538. package/src/core/ui-packs/zh-tw.ts +3 -1
  539. package/src/core/ui-packs/zh.ts +3 -1
  540. package/src/core/unrecognized-keys.ts +10 -0
  541. package/src/core/version-cut.ts +116 -8
  542. package/src/deploy/adapter-output.ts +57 -97
  543. package/src/deploy/adapters/cloudflare.ts +39 -0
  544. package/src/deploy/adapters/index.ts +41 -0
  545. package/src/deploy/adapters/netlify.ts +34 -0
  546. package/src/deploy/adapters/node.ts +34 -0
  547. package/src/deploy/adapters/registry.ts +127 -0
  548. package/src/deploy/adapters/types.ts +92 -0
  549. package/src/deploy/adapters/vercel.ts +35 -0
  550. package/src/deploy/artifacts.ts +55 -27
  551. package/src/deploy/cloudflare-negotiation.ts +179 -100
  552. package/src/deploy/function-bundle.ts +18 -3
  553. package/src/deploy/headers.ts +124 -23
  554. package/src/deploy/node-headers.ts +198 -0
  555. package/src/deploy/platforms/cloudflare.ts +312 -0
  556. package/src/deploy/platforms/index.ts +67 -0
  557. package/src/deploy/platforms/netlify.ts +52 -0
  558. package/src/deploy/platforms/node.ts +40 -0
  559. package/src/deploy/platforms/paths.ts +42 -0
  560. package/src/deploy/platforms/static.ts +29 -0
  561. package/src/deploy/platforms/types.ts +112 -0
  562. package/src/deploy/platforms/vercel.ts +193 -0
  563. package/src/deploy/redirects.ts +30 -18
  564. package/src/deploy/robots.ts +3 -3
  565. package/src/deploy/rss.ts +2 -2
  566. package/src/deploy/sitemap.ts +2 -2
  567. package/src/deploy/vercel-negotiation.ts +2 -2
  568. package/src/eval/agents.ts +32 -1
  569. package/src/eval/findings.ts +19 -11
  570. package/src/eval/report.ts +10 -2
  571. package/src/markdown/external-links.ts +65 -0
  572. package/src/markdown/include.ts +45 -27
  573. package/src/markdown/index.ts +34 -9
  574. package/src/markdown/inline-code.ts +12 -6
  575. package/src/markdown/relative-links.ts +324 -0
  576. package/src/markdown/themes.ts +3 -3
  577. package/src/migrate/migrate.ts +149 -0
  578. package/src/openapi/parse.ts +64 -16
  579. package/src/openapi/proxy.ts +62 -10
  580. package/src/openapi/references.ts +147 -147
  581. package/src/openapi/render-mdx.ts +32 -10
  582. package/src/openapi/scalar.ts +15 -13
  583. package/src/openapi/sentence.ts +14 -0
  584. package/src/openapi/source.ts +37 -21
  585. package/src/openapi/spec-dependency-error.ts +15 -0
  586. package/src/reference/asyncapi.ts +83 -0
  587. package/src/reference/graphql.ts +89 -0
  588. package/src/reference/index.ts +40 -0
  589. package/src/reference/openapi.ts +83 -0
  590. package/src/reference/options.ts +201 -0
  591. package/src/reference/scalar.ts +136 -0
  592. package/src/reference/schema.ts +88 -0
  593. package/src/registry/eject.ts +97 -42
  594. package/src/search/adapters/algolia.ts +46 -0
  595. package/src/search/adapters/flexsearch.ts +29 -0
  596. package/src/search/adapters/index.ts +39 -0
  597. package/src/search/adapters/mixedbread.ts +39 -0
  598. package/src/search/adapters/orama-cloud.ts +51 -0
  599. package/src/search/adapters/orama.ts +24 -0
  600. package/src/search/adapters/pagefind.ts +27 -0
  601. package/src/search/adapters/registry.ts +131 -0
  602. package/src/search/adapters/types.ts +46 -0
  603. package/src/search/adapters/typesense.ts +57 -0
  604. package/src/search/build.ts +8 -4
  605. package/src/search/documents.ts +6 -0
  606. package/src/search/sync/algolia.ts +12 -11
  607. package/src/search/sync/index.ts +35 -20
  608. package/src/search/sync/orama-cloud.ts +12 -9
  609. package/src/search/sync/typesense.ts +15 -13
  610. package/src/sources/contentful.ts +63 -0
  611. package/src/sources/custom.ts +38 -0
  612. package/src/sources/filesystem.ts +51 -0
  613. package/src/sources/github-releases.ts +52 -0
  614. package/src/sources/index.ts +60 -0
  615. package/src/sources/mdx-remote.ts +76 -0
  616. package/src/sources/notion.ts +63 -0
  617. package/src/sources/obsidian.ts +38 -0
  618. package/src/sources/payload.ts +60 -0
  619. package/src/sources/registry.ts +182 -0
  620. package/src/sources/sanity.ts +66 -0
  621. package/src/sources/shared.ts +52 -0
  622. package/src/sources/strapi.ts +58 -0
  623. package/src/theme/entry.ts +24 -2
  624. package/src/translate/report.ts +40 -7
  625. package/src/upgrade/upgrade.ts +499 -0
  626. package/dist/cli/chunk-0qymqwzz.js +0 -164
  627. package/dist/cli/chunk-0qymqwzz.js.map +0 -15
  628. package/dist/cli/chunk-1jefwnfs.js.map +0 -48
  629. package/dist/cli/chunk-2mzebbbz.js +0 -69
  630. package/dist/cli/chunk-2z47ypj8.js.map +0 -11
  631. package/dist/cli/chunk-4x36ddpw.js.map +0 -11
  632. package/dist/cli/chunk-5093q3n7.js +0 -68
  633. package/dist/cli/chunk-5093q3n7.js.map +0 -10
  634. package/dist/cli/chunk-5qk08vmp.js.map +0 -11
  635. package/dist/cli/chunk-7s8hm3b6.js +0 -5347
  636. package/dist/cli/chunk-7s8hm3b6.js.map +0 -58
  637. package/dist/cli/chunk-8cjtbafj.js.map +0 -13
  638. package/dist/cli/chunk-97r59kpr.js +0 -381
  639. package/dist/cli/chunk-97r59kpr.js.map +0 -12
  640. package/dist/cli/chunk-ahnw3kxw.js.map +0 -15
  641. package/dist/cli/chunk-b27xqwn9.js.map +0 -10
  642. package/dist/cli/chunk-bf6bt1xt.js +0 -185
  643. package/dist/cli/chunk-bf6bt1xt.js.map +0 -11
  644. package/dist/cli/chunk-cjtn640a.js.map +0 -10
  645. package/dist/cli/chunk-ct47dqpx.js.map +0 -11
  646. package/dist/cli/chunk-esphfr8p.js +0 -107
  647. package/dist/cli/chunk-esphfr8p.js.map +0 -11
  648. package/dist/cli/chunk-ex56aa81.js +0 -1016
  649. package/dist/cli/chunk-ex56aa81.js.map +0 -13
  650. package/dist/cli/chunk-garjf5z9.js +0 -30
  651. package/dist/cli/chunk-garjf5z9.js.map +0 -10
  652. package/dist/cli/chunk-js7saxwm.js +0 -1045
  653. package/dist/cli/chunk-js7saxwm.js.map +0 -22
  654. package/dist/cli/chunk-k79xp7av.js.map +0 -39
  655. package/dist/cli/chunk-ps4m1xh4.js.map +0 -15
  656. package/dist/cli/chunk-q4rae3bg.js +0 -60
  657. package/dist/cli/chunk-q4rae3bg.js.map +0 -10
  658. package/dist/cli/chunk-rz9jmfhz.js +0 -108
  659. package/dist/cli/chunk-rz9jmfhz.js.map +0 -10
  660. package/dist/cli/chunk-vh9w1sgp.js +0 -73
  661. package/dist/cli/chunk-vh9w1sgp.js.map +0 -10
  662. package/dist/cli/chunk-vrfp10qk.js +0 -81
  663. package/dist/cli/chunk-vrfp10qk.js.map +0 -10
  664. package/dist/cli/chunk-yg63d42r.js.map +0 -34
  665. package/docs/advanced/api-reference.mdx +0 -240
  666. package/docs/reference/cli.mdx +0 -197
  667. package/skills/blume-migrate/assets/oxfmt@0.55.0.patch +0 -20
  668. package/src/components/content/changelog-element.ts +0 -69
  669. package/src/search/providers.ts +0 -91
  670. /package/dist/cli/{chunk-e7f42gdj.js.map → chunk-7ez8ny0t.js.map} +0 -0
  671. /package/dist/cli/{chunk-nn13znc2.js.map → chunk-88cpgt6h.js.map} +0 -0
  672. /package/dist/cli/{chunk-vacwm2hv.js.map → chunk-f7t03s3g.js.map} +0 -0
  673. /package/dist/cli/{chunk-dwgcp5sm.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
- });
682
-
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;
609
+ };
722
610
 
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,7 +780,7 @@ const llmsTxtObjectSchema = z.strictObject({
869
780
 
870
781
  type LlmsTxtResolved = z.output<typeof llmsTxtObjectSchema>;
871
782
 
872
- /** The object form of `ai.catalog`; a bare boolean normalizes onto it. */
783
+ /** The object form of `agents.catalog`; a bare boolean normalizes onto it. */
873
784
  const aiCatalogObjectSchema = z.strictObject({
874
785
  enabled: z.boolean().default(true),
875
786
  /**
@@ -886,97 +797,92 @@ const aiCatalogObjectSchema = z.strictObject({
886
797
 
887
798
  type AiCatalogResolved = z.output<typeof aiCatalogObjectSchema>;
888
799
 
889
- const aiConfigSchema = z.strictObject({
890
- /**
891
- * The JSON docs API: the page index, per-page JSON, and navigation under
892
- * `/api/docs/` (prerendered, so a static site serves them from files), the
893
- * live search endpoint on server output, and the OpenAPI description of
894
- * the whole machine-readable surface at `/openapi.json`. On by default.
895
- */
896
- api: z.boolean().default(true),
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 = {
897
826
  ask: z
898
- .strictObject({
899
- // Name of the env var holding the provider's API key; each provider has
900
- // a sensible default, so this only needs setting to override it.
901
- apiKeyEnv: z.string().optional(),
902
- // Base URL of the backend. Required for `openai-compatible` only when no
903
- // external endpoint is supplied; for named providers it overrides the preset.
904
- baseUrl: z.url().optional(),
905
- // Origins allowed to call the generated `/api/ask` from another site (a
906
- // marketing page that embeds an ask box, say), or `"*"` for every
907
- // origin. Each URL is reduced to its origin so a trailing slash or path
908
- // can't defeat the exact match the route performs. Read by the
909
- // generated route only; an external `endpoint` owns its own CORS.
910
- cors: z
911
- .array(
912
- z.union([
913
- z.literal("*"),
914
- z
915
- .url({ protocol: /^https?$/u })
916
- .transform((value) => new URL(value).origin),
917
- ])
918
- )
919
- .optional(),
920
- enabled: z.boolean().default(false),
921
- // Optional external endpoint for projects that keep their docs static
922
- // and host Ask AI in an existing backend. Absolute URLs and root-relative
923
- // paths are both valid; the built-in request/stream contract is unchanged.
924
- endpoint: askEndpointSchema.optional(),
925
- // Static request headers the generated endpoint sends the provider on
926
- // every call (a caller-identifying header for a shared backend, say).
927
- // Values are inlined into the generated route as literals, so the API
928
- // key stays in `apiKeyEnv`; these are for non-secret metadata.
929
- headers: z.record(z.string(), z.string()).optional(),
930
- // Extra system-prompt text (identity, language, tone) appended to the
931
- // built-in instructions, so the grounding contract — answer from the
932
- // retrieved excerpts, cite pages as Markdown links — stays intact.
933
- instructions: z.string().trim().min(1).optional(),
934
- model: z.string().default("openai/gpt-5.5"),
935
- provider: z.enum(askAiProviders).default("gateway"),
936
- // How much the model reasons before answering, sent as the backend's
937
- // own reasoning-effort control (see `askEndpointTemplate`). Omitted
938
- // keeps the provider's default; `none` is the fastest and cheapest for
939
- // grounded docs Q&A, where the excerpts carry the answer. Not for
940
- // Inkeep, which has no such control (refined below).
941
- reasoning: z.enum(askReasoningLevels).optional(),
942
- // How much documentation each question carries. Injected characters are
943
- // the dominant term in time-to-first-token on a self-hosted backend, so
944
- // these trade recall for latency. No zod defaults here: only what the
945
- // user set reaches the generated (and ejected) endpoint, so omitted
946
- // fields keep tracking the installed package's built-in defaults in
947
- // `ai/ask-context.ts` instead of pinning today's numbers as literals.
948
- retrieval: z
949
- .strictObject({
950
- contextBudget: z.number().int().positive().optional(),
951
- excerptChars: z.number().int().positive().optional(),
952
- maxResults: z.number().int().positive().optional(),
953
- })
954
- .optional(),
955
- // Empty-state prompts shown before the first question. Each renders as a
956
- // clickable suggestion; `icon` is an optional Lucide name beside it.
957
- suggestions: z
958
- .array(
959
- z.strictObject({
960
- icon: iconName.optional(),
961
- 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(),
962
870
  })
963
- )
964
- .default([]),
965
- })
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
+ )
966
885
  .superRefine((value, ctx) => {
967
- // A generic OpenAI-compatible backend has no preset URL, so the user
968
- // must supply one; the named providers fall back to their preset.
969
- if (
970
- value.provider === "openai-compatible" &&
971
- !(value.baseUrl || value.endpoint)
972
- ) {
973
- ctx.addIssue({
974
- code: z.ZodIssueCode.custom,
975
- message:
976
- 'ai.ask.baseUrl is required when provider is "openai-compatible".',
977
- path: ["baseUrl"],
978
- });
979
- }
980
886
  // `cors` configures the generated route, which an external `endpoint`
981
887
  // replaces; accepting both would silently do nothing.
982
888
  if (value.cors && value.endpoint) {
@@ -987,67 +893,8 @@ const aiConfigSchema = z.strictObject({
987
893
  path: ["cors"],
988
894
  });
989
895
  }
990
- // Inkeep runs its own QA pipeline behind an OpenAI-compatible endpoint
991
- // with no reasoning control; a level would only reach it as an
992
- // unsupported `reasoning_effort`, so refuse it up front.
993
- if (value.provider === "inkeep" && value.reasoning) {
994
- ctx.addIssue({
995
- code: z.ZodIssueCode.custom,
996
- message:
997
- 'ai.ask.reasoning is not supported when provider is "inkeep".',
998
- path: ["reasoning"],
999
- });
1000
- }
1001
896
  })
1002
897
  .optional(),
1003
- /**
1004
- * The AI Catalog / ARD manifest at `/.well-known/ai-catalog.json` (mirrored
1005
- * at `/.well-known/ard.json`): one entry per agent-facing resource the site
1006
- * publishes — the MCP server card, each agent skill, the JSON docs API's
1007
- * OpenAPI document, each rendered API reference, and llms.txt — so agent
1008
- * registries can index the site from its domain alone. Needs a
1009
- * `deployment.site` (identifiers are domain-anchored URNs). On by default;
1010
- * the object form overrides the generated representative queries.
1011
- */
1012
- catalog: z
1013
- .union([z.boolean(), aiCatalogObjectSchema])
1014
- .default(true)
1015
- .transform((value): AiCatalogResolved =>
1016
- isBoolean(value) ? { enabled: value, queries: {} } : value
1017
- ),
1018
- /**
1019
- * `llms.txt`/`llms-full.txt` emission. A bare boolean toggles it; the object
1020
- * form adds `openapi: false` to keep generated API reference pages out of
1021
- * both files (e.g. when the configured spec is example content) and
1022
- * `details`, free-form Markdown placed after the summary — the llms.txt
1023
- * spec's details block, where a site tells agents when to reach for it.
1024
- */
1025
- llmsTxt: z
1026
- .union([z.boolean(), llmsTxtObjectSchema])
1027
- .default(true)
1028
- .transform((value): LlmsTxtResolved =>
1029
- isBoolean(value) ? { enabled: value, openapi: true } : value
1030
- ),
1031
- // Serializers for the agent-facing Markdown downlevel (the `.md` mirror,
1032
- // llms-full.txt, MCP get_page), keyed by JSX name. Functions live here —
1033
- // not in components.tsx — because the config file is executed at build
1034
- // time while the components file is only statically analyzed. A same-name
1035
- // entry replaces the built-in serializer.
1036
- // Two-argument `z.record` — the single-argument form throws at
1037
- // schema-construction time under Zod 4 (see uiStringsOverrideSchema).
1038
- markdownComponents: z
1039
- .record(
1040
- z.string(),
1041
- z.custom<ComponentMarkdown>(
1042
- (value): value is ComponentMarkdown => typeof value === "function",
1043
- {
1044
- message: "Expected a serializer function.",
1045
- }
1046
- )
1047
- )
1048
- .default({}),
1049
- /** Expose the docs as an MCP server for connecting agents. */
1050
- mcp: mcpConfigSchema.prefault({}),
1051
898
  /**
1052
899
  * The "Open in chat" page action. `true` (the default) lists every
1053
900
  * provider, `false` hides the action entirely, and an array of provider
@@ -1070,35 +917,12 @@ const aiConfigSchema = z.strictObject({
1070
917
  }
1071
918
  return value;
1072
919
  }),
1073
- /**
1074
- * Publish Agent Skills for discovery: a directory (resolved against the
1075
- * project root) whose subdirectories each hold a `SKILL.md`. The build
1076
- * copies each skill under `/.well-known/agent-skills/` — a lone `SKILL.md`
1077
- * verbatim, a skill with supporting files as a `.tar.gz` — and emits the
1078
- * discovery index (`index.json`) with SHA-256 digests per the Agent Skills
1079
- * Discovery RFC.
1080
- */
1081
- skills: z.string().min(1).optional(),
1082
- /**
1083
- * Web Bot Auth (IETF `webbotauth`): publish the org's HTTP Message
1084
- * Signature public keys at `/.well-known/http-message-signatures-directory`
1085
- * so sites receiving requests from the org's agents can verify them.
1086
- * Opt-in and public-keys-only — the private keys live wherever the signing
1087
- * agents run, never in the site.
1088
- */
1089
- webBotAuth: z
1090
- .strictObject({
1091
- keys: z.array(publicJwkSchema).default([]),
1092
- })
1093
- .prefault({}),
1094
- /**
1095
- * WebMCP: register in-page tools (search, page Markdown, the docs index)
1096
- * on the browser's model context so agentic browsers can drive the docs
1097
- * without a separate MCP connection. A tiny script that no-ops in browsers
1098
- * without the API; on by default.
1099
- */
1100
- webmcp: z.boolean().default(true),
1101
- });
920
+ };
921
+
922
+ const aiConfigSchema = z.strictObject(
923
+ aiConfigFields,
924
+ removedKeysHint(movedToAgentsHints("ai"))
925
+ );
1102
926
 
1103
927
  /**
1104
928
  * A pinned link rendered above the sidebar sections — a blog, changelog, or
@@ -1164,8 +988,8 @@ const navigationConfigSchema = z.strictObject({
1164
988
  tabs: z.array(navTabSchema).default([]),
1165
989
  });
1166
990
 
1167
- export type AskAiProvider = (typeof askAiProviders)[number];
1168
- export type AskReasoning = (typeof askReasoningLevels)[number];
991
+ export { askReasoningLevels } from "../ai/ask.ts";
992
+ export type { AskReasoning } from "../ai/ask.ts";
1169
993
  export type AskAiConfig = NonNullable<z.infer<typeof aiConfigSchema>["ask"]>;
1170
994
  export { openInChatProviders } from "./open-in-chat.ts";
1171
995
  export type { OpenInChatProvider } from "./open-in-chat.ts";
@@ -1316,57 +1140,28 @@ const versionsConfigSchema = z
1316
1140
  }
1317
1141
  });
1318
1142
 
1319
- const analyticsScriptSchema = z
1320
- .strictObject({
1321
- // Extra attributes (e.g. `data-domain`, `id`) spread onto the <script>.
1322
- attributes: z.record(z.string(), z.string()).optional(),
1323
- // Inline script body, mutually exclusive with `src`.
1324
- content: z.string().optional(),
1325
- // External script URL, mutually exclusive with `content`.
1326
- src: z.string().optional(),
1327
- // Load strategy for an external script.
1328
- strategy: z.enum(["async", "defer"]).optional(),
1329
- })
1330
- .refine((value) => Boolean(value.src) !== Boolean(value.content), {
1331
- message: "An analytics script must set exactly one of `src` or `content`.",
1332
- });
1333
-
1334
- const analyticsConfigSchema = z.strictObject({
1335
- // Cloudflare Web Analytics in manual (JS snippet) mode; the token comes from
1336
- // the site's snippet in the dashboard. A zone Cloudflare proxies with
1337
- // automatic RUM injection on needs no config at all.
1338
- cloudflare: z
1339
- .strictObject({
1340
- token: z.string().min(1),
1341
- })
1342
- .optional(),
1343
- posthog: z
1344
- .strictObject({
1345
- host: z.string().optional(),
1346
- key: z.string(),
1347
- })
1348
- .optional(),
1349
- // Escape hatch for any other provider (Plausible, Fathom, GA, Umami, …).
1350
- scripts: z.array(analyticsScriptSchema).optional(),
1351
- vercel: z.boolean().optional(),
1352
- });
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;
1353
1151
 
1354
- const deploymentConfigSchema = z.strictObject({
1355
- adapter: z
1356
- .enum(["vercel", "node", "netlify", "cloudflare"])
1357
- .nullable()
1358
- .default(null),
1359
- base: z.string().optional(),
1360
- output: z.enum(["static", "server"]).default("static"),
1361
- site: z.url().optional(),
1362
- });
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
+ });
1363
1158
 
1364
1159
  const redirectSchema = z.strictObject({
1365
- from: z.string(),
1160
+ from: exactRedirectPath("from"),
1366
1161
  status: z
1367
1162
  .union([z.literal(301), z.literal(302), z.literal(307), z.literal(308)])
1368
1163
  .default(301),
1369
- to: z.string(),
1164
+ to: exactRedirectPath("to"),
1370
1165
  });
1371
1166
 
1372
1167
  /**
@@ -1584,15 +1379,7 @@ const softwareConfigSchema = z.strictObject({
1584
1379
  type SoftwareResolved = z.output<typeof softwareConfigSchema>;
1585
1380
 
1586
1381
  /** Discoverability features: OG images, feeds, sitemap, structured data. */
1587
- const seoConfigSchema = z.strictObject({
1588
- /**
1589
- * Emit `agent-readability.json` at the site root: a manifest that indexes
1590
- * the agent-facing surface (llms.txt, Markdown mirrors, MCP server, feeds)
1591
- * so agents can discover it without scraping HTML.
1592
- */
1593
- agentReadability: z.boolean().default(true),
1594
- /** robots.txt `Content-Signal` usage declaration (on by default). */
1595
- contentSignals: contentSignalsSchema.prefault(true),
1382
+ const seoConfigFields = {
1596
1383
  og: ogConfigSchema.default({}),
1597
1384
  /** The organization behind the site, as an `Organization` JSON-LD node. */
1598
1385
  organization: organizationConfigSchema.optional(),
@@ -1615,6 +1402,114 @@ const seoConfigSchema = z.strictObject({
1615
1402
  structuredData: z.boolean().default(true),
1616
1403
  /** X (Twitter) account attribution for share cards. */
1617
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),
1618
1513
  });
1619
1514
 
1620
1515
  /**
@@ -1691,10 +1586,6 @@ const codeBlockThemeSchema = z.strictObject({
1691
1586
  light: codeThemeSchema.default("github-light"),
1692
1587
  });
1693
1588
 
1694
- const codeBlocksConfigSchema = z.strictObject({
1695
- theme: codeBlockThemeSchema.prefault({}),
1696
- });
1697
-
1698
1589
  /**
1699
1590
  * `<Component />` example previews. A string is shorthand for `{ source }`:
1700
1591
  * where examples live, relative to the project root (default `examples`).
@@ -1726,13 +1617,20 @@ const examplesConfigSchema = z
1726
1617
 
1727
1618
  /**
1728
1619
  * "Last updated" timestamps for content pages. `false` (default) disables the
1729
- * feature; `true` derives each page's date from git history; an object selects
1730
- * 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.
1731
1624
  */
1732
- const lastModifiedConfigSchema = z.union([
1733
- z.boolean(),
1734
- z.strictObject({ type: z.enum(["git", "frontmatter"]).default("git") }),
1735
- ]);
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
+ );
1736
1634
 
1737
1635
  /**
1738
1636
  * How the "last updated" stamp and the changelog timeline render their dates —
@@ -1777,13 +1675,18 @@ const dateFormatConfigSchema = z
1777
1675
  }
1778
1676
  );
1779
1677
 
1780
- /** Code-block rendering options (`markdown.code`). */
1678
+ /** Code rendering options (`markdown.code`). */
1781
1679
  const codeConfigSchema = z.strictObject({
1782
1680
  /**
1783
1681
  * Show a brand language icon in the code-block header (TypeScript, Python,
1784
1682
  * …). On by default; recognized languages only.
1785
1683
  */
1786
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({}),
1787
1690
  /**
1788
1691
  * Wrap long lines instead of scrolling horizontally. Off by default, so
1789
1692
  * code keeps its original line breaks and overflows into a scroll area.
@@ -1791,10 +1694,17 @@ const codeConfigSchema = z.strictObject({
1791
1694
  wrap: z.boolean().default(false),
1792
1695
  });
1793
1696
 
1794
- const markdownConfigSchema = z.strictObject({
1795
- /** Code-block rendering: language icons and line wrapping. */
1697
+ const markdownConfigFields = {
1698
+ /** Code rendering: language icons, syntax themes, and line wrapping. */
1796
1699
  code: codeConfigSchema.prefault({}),
1797
- 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),
1798
1708
  /**
1799
1709
  * Wrap each `##`–`######` heading in a link to its own anchor so readers can
1800
1710
  * click to copy, bookmark, or share a permalink to that section. On by
@@ -1806,7 +1716,15 @@ const markdownConfigSchema = z.strictObject({
1806
1716
  * opt a single image out with `data-no-zoom`.
1807
1717
  */
1808
1718
  imageZoom: z.boolean().default(true),
1809
- });
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
+ );
1810
1728
 
1811
1729
  /** React island behavior (`react`). */
1812
1730
  const reactConfigSchema = z.strictObject({
@@ -1819,163 +1737,6 @@ const reactConfigSchema = z.strictObject({
1819
1737
  compiler: z.boolean().default(true),
1820
1738
  });
1821
1739
 
1822
- /**
1823
- * A single spec rendered by the API reference. `spec` is a local path or an
1824
- * `http(s)` URL (an OpenAPI document under `openapi`, an AsyncAPI document
1825
- * under `asyncapi`).
1826
- */
1827
- const openapiSourceSchema = z.strictObject({
1828
- /** Include generated pages from this spec in llms.txt/llms-full.txt. */
1829
- includeInLlms: z.boolean().default(true),
1830
- /** Include generated pages from this spec in site search. */
1831
- includeInSearch: z.boolean().default(true),
1832
- /** Nav/section label for this source. */
1833
- label: z.string().optional(),
1834
- /** Emit noindex metadata and omit generated pages from the sitemap. */
1835
- noindex: z.boolean().default(false),
1836
- /** Per-source route; defaults to the block's `route` (or a derived path). */
1837
- route: z.string().optional(),
1838
- /**
1839
- * Append the English "Reference for the … endpoint in the … API." sentence
1840
- * to every generated operation page's meta description. On by default, so
1841
- * terse specs still ship distinct, snippet-length descriptions; set to
1842
- * `false` on a non-English site to describe pages with the spec's own prose
1843
- * alone (falling back to the page title when an operation has none).
1844
- */
1845
- seoDescriptionSuffix: z.boolean().default(true),
1846
- /** Local path or `http(s)` URL to the spec. */
1847
- spec: z.string(),
1848
- });
1849
-
1850
- export type OpenApiSource = z.input<typeof openapiSourceSchema>;
1851
-
1852
- /**
1853
- * Arbitrary Scalar API-reference options forwarded verbatim to the generated
1854
- * `<ScalarComponent>` (Scalar renderer only). A passthrough map — Blume doesn't
1855
- * mirror Scalar's full config surface — so keys like `localization`, `agent`,
1856
- * `hideTestRequestButton`, or `orderSchemaPropertiesBy` all flow through. These
1857
- * take precedence over Blume's own derived config (spec, theme), so this is a
1858
- * full escape hatch; the dedicated `theme` field is the ergonomic shorthand.
1859
- */
1860
- const scalarConfigSchema = z.record(z.string(), z.unknown()).optional();
1861
-
1862
- /**
1863
- * The interactive "Try it" panel on operation pages (Blume renderer). On by
1864
- * default; `false` hides it. The object form keeps it on and sets `proxy`,
1865
- * the CORS escape hatch the Send button routes requests through: a proxy URL,
1866
- * or `true` for the built-in `/_api-proxy` endpoint (which requires
1867
- * `deployment.output: "server"`). Booleans normalize to the object shape so
1868
- * consumers read `{ enabled, proxy }` directly. `proxy` applies to the
1869
- * HTTP-posting playgrounds (OpenAPI, GraphQL) — an event composer's WebSocket
1870
- * connect is direct. One schema for every reference block, so the
1871
- * normalization can never drift between them.
1872
- */
1873
- const playgroundConfigSchema = z
1874
- .union([
1875
- z.boolean(),
1876
- z.strictObject({
1877
- enabled: z.boolean().default(true),
1878
- proxy: z.union([z.boolean(), z.string()]).default(false),
1879
- }),
1880
- ])
1881
- .default(true)
1882
- .transform((value) =>
1883
- isBoolean(value) ? { enabled: value, proxy: false } : value
1884
- );
1885
-
1886
- /**
1887
- * The shared shape of the API-reference blocks — only the mount route and
1888
- * code-sample defaults differ per spec kind, so each block declares just
1889
- * those (the GraphQL block derives from this via omit/extend below).
1890
- */
1891
- const referenceConfigSchema = (defaults: {
1892
- codeSamples: string[];
1893
- route: string;
1894
- }) =>
1895
- z.strictObject({
1896
- /** Code-sample languages/tools shown per operation (Blume renderer). */
1897
- codeSamples: z.array(z.string()).default(defaults.codeSamples),
1898
- enabled: z.boolean().default(false),
1899
- /** Start nested schema rows expanded rather than collapsed (Blume renderer). */
1900
- expandSchemas: z.boolean().default(false),
1901
- /** The "Try it" panel; see {@link playgroundConfigSchema}. */
1902
- playground: playgroundConfigSchema,
1903
- /** Who renders the reference: Blume's own UI, or the embedded Scalar SPA. */
1904
- renderer: z.enum(["blume", "scalar"]).default("blume"),
1905
- /** Where the reference mounts. */
1906
- route: z.string().default(defaults.route),
1907
- /** Extra Scalar config forwarded to `<ScalarComponent>` (Scalar renderer only). */
1908
- scalar: scalarConfigSchema,
1909
- /** One or more specs; each renders on its own route by default. */
1910
- sources: z.array(openapiSourceSchema).default([]),
1911
- /** Shorthand for a single source: `sources: [{ spec }]`. */
1912
- spec: z.string().optional(),
1913
- /** Scalar theme name (Scalar renderer only). */
1914
- theme: z.string().optional(),
1915
- });
1916
-
1917
- /**
1918
- * OpenAPI reference. By default (`renderer: "blume"`) Blume parses the spec with
1919
- * Scalar's parser and renders its own UI: one real page per operation, grouped
1920
- * by tag in the sidebar and included in site search, llms.txt, and OG. Set
1921
- * `renderer: "scalar"` to fall back to the embedded Scalar SPA (a single
1922
- * self-contained route that doesn't weave into the sidebar or search).
1923
- */
1924
- const openapiConfigSchema = referenceConfigSchema({
1925
- codeSamples: ["curl", "js", "python"],
1926
- route: "/reference",
1927
- });
1928
-
1929
- /**
1930
- * AsyncAPI reference. Same shape as {@link openapiConfigSchema}: by default
1931
- * (`renderer: "blume"`) Blume normalizes the spec to AsyncAPI 3.x and renders
1932
- * its own UI — one real page per operation — with `renderer: "scalar"` as the
1933
- * embedded-SPA opt-out. Only the defaults differ: the reference mounts at
1934
- * `/events`, and empty `codeSamples` means every tool the operation's protocol
1935
- * binding suggests.
1936
- */
1937
- const asyncapiConfigSchema = referenceConfigSchema({
1938
- codeSamples: [],
1939
- route: "/events",
1940
- });
1941
-
1942
- /**
1943
- * A single GraphQL schema rendered by the reference. `spec` is a local path or
1944
- * an `http(s)` URL to SDL text or an introspection JSON result; `endpoint` is
1945
- * the live GraphQL API URL the playground and code samples target (a schema,
1946
- * unlike an OpenAPI document, names no server).
1947
- */
1948
- const graphqlSourceSchema = openapiSourceSchema.extend({
1949
- /** URL of the live GraphQL endpoint (playground + code samples). */
1950
- endpoint: z.string().optional(),
1951
- });
1952
-
1953
- export type GraphqlSource = z.input<typeof graphqlSourceSchema>;
1954
-
1955
- /**
1956
- * GraphQL reference. Blume lowers the schema (SDL or introspection JSON) to
1957
- * one real page per root field — grouped as Queries/Mutations/Subscriptions —
1958
- * plus one page per named type (Objects, Input Objects, Enums, Interfaces,
1959
- * Unions, Scalars), all included in the sidebar, search, llms.txt, and OG.
1960
- * Always Blume-rendered: the Scalar SPA reads OpenAPI documents only, so the
1961
- * block declares no `renderer`/`scalar`/`theme` escape hatches.
1962
- */
1963
- const graphqlConfigSchema = referenceConfigSchema({
1964
- codeSamples: ["curl", "js", "python"],
1965
- route: "/graphql",
1966
- })
1967
- // No `renderer`/`scalar`/`theme` escape hatches (the Scalar SPA reads
1968
- // OpenAPI documents only) and no `expandSchemas` (GraphQL field tables have
1969
- // no nesting) — everything else, the playground normalization included, is
1970
- // the shared reference shape.
1971
- .omit({ expandSchemas: true, renderer: true, scalar: true, theme: true })
1972
- .extend({
1973
- /** Default live endpoint URL for every source (per-source `endpoint` wins). */
1974
- endpoint: z.string().optional(),
1975
- /** One or more schemas; each renders on its own route by default. */
1976
- sources: z.array(graphqlSourceSchema).default([]),
1977
- });
1978
-
1979
1740
  /**
1980
1741
  * Opt-in custom frontmatter keys. `extend` maps each extra key a project's
1981
1742
  * pages may carry (e.g. `owner`, `reviewedAt`) to a validation schema; the
@@ -2025,60 +1786,77 @@ const tocConfigSchema = z
2025
1786
  });
2026
1787
 
2027
1788
  export const blumeConfigSchema = z
2028
- .strictObject({
2029
- ai: aiConfigSchema.prefault({}),
2030
- analytics: analyticsConfigSchema.optional(),
2031
- asyncapi: asyncapiConfigSchema.prefault({}),
2032
- banner: bannerConfigSchema.optional(),
2033
- /**
2034
- * Site-wide mount point prepended to every generated route (e.g. `/docs`),
2035
- * while staying invisible to the sidebar/nav tree. Distinct from a per-source
2036
- * `prefix` (which creates a group) and from `deployment.base` (Astro's
2037
- * host-subdirectory base); the two compose. Normalized to `""` or `/seg`.
2038
- */
2039
- basePath: z
2040
- .string()
2041
- .optional()
2042
- .transform((value) => normalizeBasePath(value)),
2043
- content: contentConfigSchema.prefault({}),
2044
- /**
2045
- * Date presentation for the "last updated" stamp and the changelog timeline.
2046
- * Pass-through `Intl.DateTimeFormat` options; defaults to `{ dateStyle: "long" }`.
2047
- */
2048
- dateFormat: dateFormatConfigSchema.default({ dateStyle: "long" }),
2049
- deployment: deploymentConfigSchema.prefault({}),
2050
- description: z.string().optional(),
2051
- /**
2052
- * Where `<Component path>` resolves live previews and their source from.
2053
- * A string is shorthand for `{ source }` — the directory (or glob, for
2054
- * colocated registry layouts) under the project root that holds example
2055
- * files. The object form adds `css`: a stylesheet injected into every
2056
- * preview frame (design tokens, shadcn variables, `@theme` mappings).
2057
- */
2058
- examples: examplesConfigSchema.prefault("examples"),
2059
- export: exportConfigSchema.prefault(false),
2060
- feedback: z.boolean().default(true),
2061
- /** Opt-in custom frontmatter keys, validated by user-supplied schemas. */
2062
- frontmatter: frontmatterConfigSchema.prefault({}),
2063
- github: githubConfigSchema.optional(),
2064
- graphql: graphqlConfigSchema.prefault({}),
2065
- i18n: i18nConfigSchema.optional(),
2066
- image: imageConfigSchema.prefault({}),
2067
- integrations: z.array(z.custom<AstroIntegration>()).default([]),
2068
- lastModified: lastModifiedConfigSchema.default(false),
2069
- logo: logoConfigSchema.optional(),
2070
- markdown: markdownConfigSchema.prefault({}),
2071
- navigation: navigationConfigSchema.prefault({}),
2072
- openapi: openapiConfigSchema.prefault({}),
2073
- react: reactConfigSchema.prefault({}),
2074
- redirects: z.array(redirectSchema).default([]),
2075
- search: searchConfigSchema.prefault({}),
2076
- seo: seoConfigSchema.prefault({}),
2077
- theme: themeConfigSchema.prefault({}),
2078
- title: z.string().default("Documentation"),
2079
- toc: tocConfigSchema,
2080
- versions: versionsConfigSchema.optional(),
2081
- })
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
+ )
2082
1860
  .superRefine((config, ctx) => {
2083
1861
  // A version id that is also a configured locale code would make a leading
2084
1862
  // `<id>/` directory ambiguous between the two axes — refuse it outright so
@@ -2150,8 +1928,8 @@ export type ArchivedVersionConfig = z.infer<typeof archivedVersionSchema>;
2150
1928
  * guard keeps structurally identical to this.
2151
1929
  */
2152
1930
  export type BlumeConfigInput = z.input<typeof blumeConfigSchema>;
2153
- /** A configured search backend. */
2154
- 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";
2155
1933
  /** Resolved robots.txt `Content-Signal` preferences (`null` when disabled). */
2156
1934
  export type ContentSignals = z.infer<typeof contentSignalsSchema>;
2157
1935
  /** The resolved per-signal policy object (present when signals are enabled). */