blume 2.0.3 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (607) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +142 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
  5. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
  6. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  7. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  8. package/dist/cli/chunk-150jk7km.js +52 -0
  9. package/dist/cli/chunk-150jk7km.js.map +10 -0
  10. package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
  11. package/dist/cli/chunk-313vnn40.js.map +10 -0
  12. package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
  13. package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
  14. package/dist/cli/chunk-3x6x3pph.js.map +10 -0
  15. package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
  16. package/dist/cli/chunk-42t4jxdx.js.map +12 -0
  17. package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
  18. package/dist/cli/chunk-54ztw1nj.js.map +10 -0
  19. package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
  20. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
  21. package/dist/cli/chunk-66w9j7p6.js +322 -0
  22. package/dist/cli/chunk-66w9j7p6.js.map +14 -0
  23. package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
  24. package/dist/cli/chunk-7ppf1rva.js.map +10 -0
  25. package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
  26. package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
  27. package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
  28. package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
  29. package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
  30. package/dist/cli/chunk-cfyg47zg.js.map +10 -0
  31. package/dist/cli/chunk-cm89scdv.js +443 -0
  32. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  33. package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
  34. package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
  35. package/dist/cli/chunk-e6atmn4h.js.map +36 -0
  36. package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
  37. package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
  38. package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
  39. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
  40. package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
  41. package/dist/cli/chunk-ehweg4fr.js.map +218 -0
  42. package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
  43. package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
  44. package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
  45. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
  46. package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
  47. package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
  48. package/dist/cli/chunk-gb420vvz.js.map +17 -0
  49. package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
  50. package/dist/cli/chunk-hebn0tpx.js.map +14 -0
  51. package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
  52. package/dist/cli/chunk-htrwgabr.js.map +15 -0
  53. package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
  54. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
  55. package/dist/cli/chunk-j134vrp2.js +65 -0
  56. package/dist/cli/chunk-j134vrp2.js.map +10 -0
  57. package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
  58. package/dist/cli/chunk-kwrg63tm.js +31 -0
  59. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  60. package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
  61. package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
  62. package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
  63. package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
  64. package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
  65. package/dist/cli/chunk-ncj7w596.js.map +35 -0
  66. package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
  67. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
  68. package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
  69. package/dist/cli/chunk-nrns10mj.js.map +10 -0
  70. package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
  71. package/dist/cli/chunk-t2m5j4pr.js +104 -0
  72. package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
  73. package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
  74. package/dist/cli/chunk-tphac74f.js +126 -0
  75. package/dist/cli/chunk-tphac74f.js.map +11 -0
  76. package/dist/cli/chunk-w1r4vase.js +18 -0
  77. package/dist/cli/chunk-w1r4vase.js.map +10 -0
  78. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  79. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  80. package/dist/cli/index.js +31 -20
  81. package/dist/cli/index.js.map +5 -5
  82. package/dist/types/ai/agent-surface.d.ts +8 -7
  83. package/dist/types/ai/ai-catalog.d.ts +7 -0
  84. package/dist/types/ai/ask-context.d.ts +26 -0
  85. package/dist/types/ai/ask.d.ts +271 -58
  86. package/dist/types/ai/component-markdown.d.ts +2 -0
  87. package/dist/types/ai/index.d.ts +2 -2
  88. package/dist/types/ai/openapi-components.d.ts +4 -1
  89. package/dist/types/ai/site-skill.d.ts +26 -0
  90. package/dist/types/ai/skills.d.ts +16 -0
  91. package/dist/types/astro/integration.d.ts +9 -0
  92. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  93. package/dist/types/astro/pages.d.ts +10 -0
  94. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  95. package/dist/types/captcha/index.d.ts +28 -0
  96. package/dist/types/captcha/schema.d.ts +29 -0
  97. package/dist/types/captcha/turnstile.d.ts +28 -0
  98. package/dist/types/cli/init/scaffold.d.ts +1 -1
  99. package/dist/types/components/content/api-field.d.ts +62 -0
  100. package/dist/types/components/content/api-page.d.ts +100 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  103. package/dist/types/components/openapi/helpers.d.ts +158 -0
  104. package/dist/types/components/openapi/security.d.ts +95 -0
  105. package/dist/types/components/openapi/snippets.d.ts +60 -0
  106. package/dist/types/consent/ethyca.d.ts +48 -0
  107. package/dist/types/consent/index.d.ts +37 -0
  108. package/dist/types/consent/native.d.ts +28 -0
  109. package/dist/types/consent/osano.d.ts +42 -0
  110. package/dist/types/consent/schema.d.ts +110 -0
  111. package/dist/types/core/changelog-index.d.ts +8 -0
  112. package/dist/types/core/config-input.d.ts +217 -23
  113. package/dist/types/core/data.d.ts +53 -4
  114. package/dist/types/core/footer.d.ts +8 -0
  115. package/dist/types/core/i18n-ui.d.ts +88 -0
  116. package/dist/types/core/includes.d.ts +8 -2
  117. package/dist/types/core/links.d.ts +8 -0
  118. package/dist/types/core/localizable.d.ts +7 -0
  119. package/dist/types/core/page-modes.d.ts +29 -0
  120. package/dist/types/core/probe.d.ts +2 -0
  121. package/dist/types/core/project-graph.d.ts +6 -0
  122. package/dist/types/core/redirect-patterns.d.ts +152 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +670 -56
  125. package/dist/types/core/sources/cache.d.ts +3 -0
  126. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  127. package/dist/types/core/sources/contentful.d.ts +17 -1
  128. package/dist/types/core/sources/lexical.d.ts +5 -0
  129. package/dist/types/core/sources/normalize.d.ts +20 -1
  130. package/dist/types/core/sources/read.d.ts +5 -0
  131. package/dist/types/core/sources/remote.d.ts +37 -10
  132. package/dist/types/core/sources/sanity.d.ts +1 -1
  133. package/dist/types/core/types.d.ts +18 -1
  134. package/dist/types/core/variables.d.ts +42 -0
  135. package/dist/types/deploy/adapter-output.d.ts +2 -1
  136. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  137. package/dist/types/deploy/adapters/node.d.ts +58 -23
  138. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  139. package/dist/types/deploy/adapters/types.d.ts +3 -2
  140. package/dist/types/deploy/node-headers.d.ts +13 -2
  141. package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
  142. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  143. package/dist/types/deploy/platforms/node.d.ts +3 -1
  144. package/dist/types/deploy/platforms/types.d.ts +18 -7
  145. package/dist/types/deploy/platforms/vercel.d.ts +26 -1
  146. package/dist/types/deploy/redirects.d.ts +55 -11
  147. package/dist/types/deploy/sitemap.d.ts +2 -1
  148. package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
  149. package/dist/types/markdown/code-title.d.ts +17 -0
  150. package/dist/types/markdown/features.d.ts +3 -2
  151. package/dist/types/narration/build.d.ts +70 -0
  152. package/dist/types/narration/provider.d.ts +26 -0
  153. package/dist/types/narration/script.d.ts +154 -0
  154. package/dist/types/openapi/checks.d.ts +27 -0
  155. package/dist/types/openapi/graphql.d.ts +6 -0
  156. package/dist/types/openapi/model.d.ts +27 -10
  157. package/dist/types/openapi/overlay.d.ts +54 -0
  158. package/dist/types/openapi/parse.d.ts +16 -0
  159. package/dist/types/openapi/references.d.ts +30 -4
  160. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  161. package/dist/types/ratelimit/index.d.ts +27 -0
  162. package/dist/types/ratelimit/memory.d.ts +36 -0
  163. package/dist/types/ratelimit/schema.d.ts +119 -0
  164. package/dist/types/ratelimit/upstash.d.ts +46 -0
  165. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  166. package/dist/types/reference/asyncapi.d.ts +10 -10
  167. package/dist/types/reference/graphql.d.ts +89 -11
  168. package/dist/types/reference/index.d.ts +1 -1
  169. package/dist/types/reference/openapi.d.ts +33 -12
  170. package/dist/types/reference/options.d.ts +82 -6
  171. package/dist/types/reference/scalar.d.ts +13 -0
  172. package/dist/types/reference/schema.d.ts +144 -36
  173. package/dist/types/search/adapters/registry.d.ts +3 -0
  174. package/dist/types/search/adapters/typesense.d.ts +14 -4
  175. package/dist/types/search/build.d.ts +3 -1
  176. package/dist/types/search/documents.d.ts +12 -1
  177. package/dist/types/search/orama-index.d.ts +13 -9
  178. package/dist/types/search/sync/algolia.d.ts +5 -2
  179. package/dist/types/search/sync/index.d.ts +3 -1
  180. package/dist/types/search/sync/typesense.d.ts +11 -7
  181. package/dist/types/seo/metatags.d.ts +20 -0
  182. package/dist/types/sources/contentful.d.ts +16 -0
  183. package/dist/types/sources/github-releases.d.ts +8 -1
  184. package/dist/types/sources/payload.d.ts +2 -0
  185. package/dist/types/sources/registry.d.ts +54 -0
  186. package/dist/types/sources/shared.d.ts +3 -0
  187. package/dist/types/sources/strapi.d.ts +2 -0
  188. package/docs/01-quickstart.mdx +4 -2
  189. package/docs/02-deployment.mdx +71 -17
  190. package/docs/03-upgrading.mdx +4 -4
  191. package/docs/04-migrating.mdx +1 -1
  192. package/docs/advanced/changelog.mdx +13 -2
  193. package/docs/advanced/skills.mdx +15 -1
  194. package/docs/cli/audit.mdx +51 -8
  195. package/docs/cli/doctor.mdx +5 -1
  196. package/docs/cli/evals.mdx +4 -4
  197. package/docs/cli/index.mdx +1 -0
  198. package/docs/cli/translate.mdx +1 -0
  199. package/docs/cli/validate.mdx +4 -3
  200. package/docs/configuration/analytics.mdx +3 -1
  201. package/docs/configuration/assistant.mdx +194 -38
  202. package/docs/configuration/consent.mdx +100 -0
  203. package/docs/configuration/customization.mdx +8 -6
  204. package/docs/configuration/export.mdx +5 -3
  205. package/docs/configuration/index.mdx +64 -5
  206. package/docs/configuration/meta.ts +3 -0
  207. package/docs/configuration/narration.mdx +119 -0
  208. package/docs/configuration/rate-limiting.mdx +90 -0
  209. package/docs/configuration/search.mdx +63 -12
  210. package/docs/configuration/theming.mdx +3 -1
  211. package/docs/content/components.mdx +108 -2
  212. package/docs/content/frontmatter.mdx +80 -2
  213. package/docs/content/i18n.mdx +39 -4
  214. package/docs/content/includes.mdx +16 -0
  215. package/docs/content/meta.mdx +1 -0
  216. package/docs/content/meta.ts +1 -0
  217. package/docs/content/navigation.mdx +32 -10
  218. package/docs/content/sources/contentful.mdx +35 -0
  219. package/docs/content/sources/custom.mdx +43 -0
  220. package/docs/content/sources/github-releases.mdx +39 -0
  221. package/docs/content/sources/index.mdx +91 -0
  222. package/docs/content/sources/meta.ts +16 -0
  223. package/docs/content/sources/notion.mdx +32 -0
  224. package/docs/content/sources/obsidian.mdx +37 -0
  225. package/docs/content/sources/payload.mdx +31 -0
  226. package/docs/content/sources/remote-mdx.mdx +44 -0
  227. package/docs/content/sources/sanity.mdx +29 -0
  228. package/docs/content/sources/strapi.mdx +32 -0
  229. package/docs/content/syntax.mdx +69 -4
  230. package/docs/content/variables.mdx +41 -0
  231. package/docs/discoverability/agent-discovery.mdx +29 -2
  232. package/docs/discoverability/index.mdx +2 -2
  233. package/docs/discoverability/json-api.mdx +2 -0
  234. package/docs/discoverability/llms-txt.mdx +2 -2
  235. package/docs/discoverability/markdown.mdx +3 -1
  236. package/docs/discoverability/mcp.mdx +4 -0
  237. package/docs/discoverability/metadata.mdx +17 -0
  238. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  239. package/docs/references/api-pages.mdx +74 -0
  240. package/docs/references/asyncapi.mdx +4 -2
  241. package/docs/references/graphql.mdx +14 -2
  242. package/docs/references/meta.ts +1 -1
  243. package/docs/references/openapi.mdx +148 -7
  244. package/docs/references/scalar.mdx +3 -1
  245. package/package.json +43 -8
  246. package/skills/blume/SKILL.md +3 -0
  247. package/skills/blume-migrate/SKILL.md +6 -6
  248. package/skills/blume-migrate/references/docusaurus.md +7 -7
  249. package/skills/blume-migrate/references/fumadocs.md +17 -13
  250. package/skills/blume-migrate/references/mintlify.md +37 -19
  251. package/skills/blume-migrate/references/nextra.md +10 -7
  252. package/skills/blume-migrate/references/starlight.md +11 -8
  253. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  254. package/skills/blume-write-skill/SKILL.md +82 -0
  255. package/src/ai/agent-readability.ts +2 -1
  256. package/src/ai/agent-surface.ts +9 -7
  257. package/src/ai/ai-catalog.ts +6 -3
  258. package/src/ai/ask-context.ts +103 -20
  259. package/src/ai/ask-data.ts +6 -0
  260. package/src/ai/ask-limits.ts +13 -0
  261. package/src/ai/ask-tools.ts +142 -0
  262. package/src/ai/ask.ts +354 -76
  263. package/src/ai/changelog-markdown.ts +11 -6
  264. package/src/ai/component-markdown.ts +76 -1
  265. package/src/ai/cors.ts +14 -3
  266. package/src/ai/index.ts +12 -0
  267. package/src/ai/llms.ts +6 -2
  268. package/src/ai/markdown.ts +2 -1
  269. package/src/ai/mcp/data.ts +6 -0
  270. package/src/ai/mcp/query.ts +14 -6
  271. package/src/ai/openapi-components.ts +36 -6
  272. package/src/ai/relative-links.ts +4 -38
  273. package/src/ai/site-skill.ts +274 -0
  274. package/src/ai/skills.ts +35 -2
  275. package/src/ai/write-skill.ts +97 -0
  276. package/src/astro/generate.ts +210 -66
  277. package/src/astro/index.ts +1 -0
  278. package/src/astro/integration.ts +42 -4
  279. package/src/astro/markdown-negotiation.ts +3 -0
  280. package/src/astro/module-types.ts +9 -1
  281. package/src/astro/pages.ts +23 -0
  282. package/src/astro/templates.ts +406 -108
  283. package/src/astro/variables.ts +31 -0
  284. package/src/audit/catalog.ts +38 -3
  285. package/src/audit/checks/contrast.ts +183 -0
  286. package/src/audit/checks/i18n.ts +2 -3
  287. package/src/audit/checks/indexability.ts +2 -3
  288. package/src/audit/checks/links.ts +19 -13
  289. package/src/audit/checks/network.ts +110 -6
  290. package/src/audit/checks/redirects.ts +6 -4
  291. package/src/audit/checks/robots.ts +58 -2
  292. package/src/audit/checks/sitemap.ts +2 -3
  293. package/src/audit/redirects.ts +46 -15
  294. package/src/audit/run.ts +5 -0
  295. package/src/audit/types.ts +4 -1
  296. package/src/audit/url.ts +14 -5
  297. package/src/blume-modules.d.ts +12 -1
  298. package/src/captcha/client.ts +152 -0
  299. package/src/captcha/hcaptcha.ts +36 -0
  300. package/src/captcha/index.ts +28 -0
  301. package/src/captcha/schema.ts +32 -0
  302. package/src/captcha/turnstile.ts +38 -0
  303. package/src/captcha/verify.ts +71 -0
  304. package/src/cli/args.ts +51 -0
  305. package/src/cli/build-failure.ts +23 -0
  306. package/src/cli/command-meta.ts +5 -0
  307. package/src/cli/commands/audit.ts +10 -1
  308. package/src/cli/commands/dev.ts +18 -2
  309. package/src/cli/commands/doctor.ts +67 -28
  310. package/src/cli/commands/eval.ts +36 -4
  311. package/src/cli/commands/init.ts +6 -4
  312. package/src/cli/commands/skill.ts +96 -0
  313. package/src/cli/commands/validate.ts +10 -1
  314. package/src/cli/index.ts +5 -0
  315. package/src/cli/init/scaffold.ts +3 -3
  316. package/src/cli/prepare.ts +6 -3
  317. package/src/cli/required-secrets.ts +72 -30
  318. package/src/components/BlumePage.astro +2 -0
  319. package/src/components/content/ApiEndpoint.astro +18 -0
  320. package/src/components/content/ApiField.astro +79 -0
  321. package/src/components/content/ApiPlayground.astro +66 -0
  322. package/src/components/content/ApiRail.astro +19 -0
  323. package/src/components/content/Color.astro +1 -0
  324. package/src/components/content/FileTree.astro +1 -0
  325. package/src/components/content/GithubInfo.astro +1 -0
  326. package/src/components/content/Panel.astro +1 -0
  327. package/src/components/content/ParamField.astro +25 -0
  328. package/src/components/content/RequestExample.astro +17 -0
  329. package/src/components/content/ResponseExample.astro +17 -0
  330. package/src/components/content/ResponseField.astro +32 -0
  331. package/src/components/content/Step.astro +1 -0
  332. package/src/components/content/Tile.astro +1 -0
  333. package/src/components/content/Tooltip.astro +1 -0
  334. package/src/components/content/Tree.astro +1 -0
  335. package/src/components/content/TypeTable.astro +4 -1
  336. package/src/components/content/View.astro +21 -0
  337. package/src/components/content/ViewSwitcher.astro +168 -0
  338. package/src/components/content/api-field.ts +103 -0
  339. package/src/components/content/api-page.ts +248 -0
  340. package/src/components/content/mermaid-element.ts +15 -1
  341. package/src/components/content/view-switcher.ts +71 -0
  342. package/src/components/index.ts +2 -0
  343. package/src/components/islands/Assistant.astro +10 -1
  344. package/src/components/islands/assistant.tsx +104 -14
  345. package/src/components/islands/code-question.ts +62 -0
  346. package/src/components/islands/hooks.ts +136 -21
  347. package/src/components/islands/support-link.ts +68 -0
  348. package/src/components/layout/Analytics.astro +11 -2
  349. package/src/components/layout/Banner.astro +14 -10
  350. package/src/components/layout/Breadcrumbs.astro +6 -2
  351. package/src/components/layout/ConsentBanner.astro +62 -0
  352. package/src/components/layout/ConsentHead.astro +38 -0
  353. package/src/components/layout/Directory.astro +94 -0
  354. package/src/components/layout/DrawerSwitchers.astro +51 -0
  355. package/src/components/layout/Empty.astro +2 -2
  356. package/src/components/layout/Header.astro +122 -80
  357. package/src/components/layout/LanguageSwitcher.astro +48 -15
  358. package/src/components/layout/NarrationPlayer.astro +1085 -0
  359. package/src/components/layout/NavSelector.astro +33 -7
  360. package/src/components/layout/PageActions.astro +139 -24
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +71 -3
  363. package/src/components/layout/ReferenceLayout.astro +17 -6
  364. package/src/components/layout/RelatedPages.astro +42 -0
  365. package/src/components/layout/RootLayout.astro +318 -47
  366. package/src/components/layout/Search.astro +47 -19
  367. package/src/components/layout/SiteFooter.astro +124 -0
  368. package/src/components/layout/SiteMetatags.astro +16 -0
  369. package/src/components/layout/TableOfContents.astro +24 -17
  370. package/src/components/layout/consent/native.ts +79 -0
  371. package/src/components/layout/consent/types.ts +37 -0
  372. package/src/components/layout/footer-links.ts +53 -0
  373. package/src/components/layout/footer-socials.ts +87 -0
  374. package/src/components/layout/head-scripts.ts +6 -1
  375. package/src/components/layout/locale-redirect.ts +107 -0
  376. package/src/components/layout/nav-utils.ts +83 -0
  377. package/src/components/layout/page-feedback.ts +96 -0
  378. package/src/components/layout/search/algolia.ts +10 -1
  379. package/src/components/layout/search/analytics.ts +126 -0
  380. package/src/components/layout/search/endpoint.ts +48 -5
  381. package/src/components/layout/search/flexsearch.ts +17 -5
  382. package/src/components/layout/search/orama-cloud.ts +26 -14
  383. package/src/components/layout/search/pagefind.ts +114 -5
  384. package/src/components/layout/search/types.ts +32 -0
  385. package/src/components/layout/search/typesense.ts +19 -3
  386. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  387. package/src/components/openapi/Authorization.astro +10 -0
  388. package/src/components/openapi/GraphqlOperation.astro +5 -0
  389. package/src/components/openapi/Operation.astro +184 -13
  390. package/src/components/openapi/ParametersTable.astro +8 -4
  391. package/src/components/openapi/Playground.astro +7 -1
  392. package/src/components/openapi/RequestBody.astro +24 -5
  393. package/src/components/openapi/RequestPanel.astro +59 -7
  394. package/src/components/openapi/Responses.astro +7 -4
  395. package/src/components/openapi/SchemaTable.astro +15 -3
  396. package/src/components/openapi/async-snippets.ts +65 -9
  397. package/src/components/openapi/async.ts +123 -29
  398. package/src/components/openapi/code-samples.ts +97 -0
  399. package/src/components/openapi/graphql-helpers.ts +35 -12
  400. package/src/components/openapi/helpers.ts +36 -2
  401. package/src/components/openapi/message-model.ts +54 -11
  402. package/src/components/openapi/message.ts +14 -4
  403. package/src/components/openapi/operation-model.ts +16 -7
  404. package/src/components/openapi/playground-client.ts +24 -5
  405. package/src/components/openapi/playground-schema.ts +1 -1
  406. package/src/components/openapi/schema-tree.ts +44 -14
  407. package/src/components/openapi/security.ts +16 -3
  408. package/src/components/openapi/snippets.ts +873 -10
  409. package/src/components/openapi/validate-json.ts +4 -3
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/builtin-tags.ts +5 -0
  421. package/src/core/changelog-index.ts +31 -1
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/component-diagnostics.ts +53 -0
  424. package/src/core/component-overrides.ts +32 -0
  425. package/src/core/config-input.ts +225 -23
  426. package/src/core/data.ts +46 -4
  427. package/src/core/diagnostics.ts +8 -2
  428. package/src/core/footer.ts +26 -0
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/i18n-ui.ts +84 -2
  431. package/src/core/includes.ts +21 -4
  432. package/src/core/links.ts +63 -19
  433. package/src/core/localizable.ts +26 -0
  434. package/src/core/navigation.ts +43 -12
  435. package/src/core/page-modes.ts +56 -0
  436. package/src/core/probe.ts +26 -6
  437. package/src/core/project-graph.ts +163 -16
  438. package/src/core/redirect-patterns.ts +520 -0
  439. package/src/core/related.ts +131 -0
  440. package/src/core/schema.ts +339 -66
  441. package/src/core/sources/cache.ts +6 -0
  442. package/src/core/sources/contentful-rich-text.ts +29 -6
  443. package/src/core/sources/contentful.ts +135 -25
  444. package/src/core/sources/filesystem.ts +26 -4
  445. package/src/core/sources/github-releases.ts +12 -1
  446. package/src/core/sources/lexical.ts +15 -10
  447. package/src/core/sources/mdx-remote.ts +62 -10
  448. package/src/core/sources/normalize.ts +228 -41
  449. package/src/core/sources/notion.ts +7 -1
  450. package/src/core/sources/obsidian.ts +16 -3
  451. package/src/core/sources/payload.ts +38 -15
  452. package/src/core/sources/read.ts +8 -3
  453. package/src/core/sources/remote.ts +72 -15
  454. package/src/core/sources/resolve.ts +11 -1
  455. package/src/core/sources/sanity.ts +43 -25
  456. package/src/core/sources/strapi.ts +2 -2
  457. package/src/core/types.ts +19 -1
  458. package/src/core/ui-packs/ar.ts +44 -0
  459. package/src/core/ui-packs/bg.ts +46 -0
  460. package/src/core/ui-packs/bn.ts +44 -0
  461. package/src/core/ui-packs/ca.ts +47 -0
  462. package/src/core/ui-packs/cs.ts +45 -0
  463. package/src/core/ui-packs/da.ts +46 -0
  464. package/src/core/ui-packs/de.ts +47 -0
  465. package/src/core/ui-packs/el.ts +46 -0
  466. package/src/core/ui-packs/es.ts +47 -0
  467. package/src/core/ui-packs/fa.ts +46 -0
  468. package/src/core/ui-packs/fi.ts +46 -0
  469. package/src/core/ui-packs/fr.ts +47 -0
  470. package/src/core/ui-packs/he.ts +44 -0
  471. package/src/core/ui-packs/hi.ts +45 -0
  472. package/src/core/ui-packs/hr.ts +46 -0
  473. package/src/core/ui-packs/hu.ts +45 -0
  474. package/src/core/ui-packs/id.ts +46 -0
  475. package/src/core/ui-packs/it.ts +46 -0
  476. package/src/core/ui-packs/ja.ts +46 -0
  477. package/src/core/ui-packs/ko.ts +45 -0
  478. package/src/core/ui-packs/nl.ts +47 -0
  479. package/src/core/ui-packs/no.ts +45 -0
  480. package/src/core/ui-packs/pl.ts +46 -0
  481. package/src/core/ui-packs/pt-br.ts +47 -0
  482. package/src/core/ui-packs/pt.ts +46 -0
  483. package/src/core/ui-packs/ro.ts +46 -0
  484. package/src/core/ui-packs/ru.ts +46 -0
  485. package/src/core/ui-packs/sk.ts +45 -0
  486. package/src/core/ui-packs/sr.ts +46 -0
  487. package/src/core/ui-packs/sv.ts +45 -0
  488. package/src/core/ui-packs/th.ts +44 -0
  489. package/src/core/ui-packs/tr.ts +46 -0
  490. package/src/core/ui-packs/uk.ts +46 -0
  491. package/src/core/ui-packs/vi.ts +46 -0
  492. package/src/core/ui-packs/zh-tw.ts +44 -0
  493. package/src/core/ui-packs/zh.ts +44 -0
  494. package/src/core/variables.ts +103 -0
  495. package/src/deploy/adapter-output.ts +7 -2
  496. package/src/deploy/adapters/cloudflare.ts +7 -5
  497. package/src/deploy/adapters/node.ts +47 -9
  498. package/src/deploy/adapters/types.ts +7 -3
  499. package/src/deploy/artifacts.ts +79 -9
  500. package/src/deploy/cloudflare-negotiation.ts +35 -5
  501. package/src/deploy/headers.ts +2 -1
  502. package/src/deploy/node-headers.ts +63 -9
  503. package/src/deploy/platforms/cloudflare.ts +45 -9
  504. package/src/deploy/platforms/netlify.ts +35 -2
  505. package/src/deploy/platforms/node.ts +12 -2
  506. package/src/deploy/platforms/types.ts +21 -7
  507. package/src/deploy/platforms/vercel.ts +149 -17
  508. package/src/deploy/redirects.ts +151 -24
  509. package/src/deploy/rss.ts +3 -1
  510. package/src/deploy/sitemap.ts +39 -5
  511. package/src/deploy/vercel-negotiation.ts +326 -102
  512. package/src/eval/agents.ts +18 -1
  513. package/src/eval/prompts.ts +6 -3
  514. package/src/eval/report.ts +34 -13
  515. package/src/eval/run.ts +5 -0
  516. package/src/markdown/api-rail.ts +259 -0
  517. package/src/markdown/code-title.ts +35 -9
  518. package/src/markdown/features.ts +3 -2
  519. package/src/markdown/fence-meta.ts +2 -0
  520. package/src/markdown/include.ts +11 -1
  521. package/src/markdown/index.ts +28 -5
  522. package/src/markdown/math.ts +4 -4
  523. package/src/markdown/relative-links.ts +3 -2
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +7 -6
  531. package/src/openapi/checks.ts +273 -0
  532. package/src/openapi/graphql-build.ts +21 -0
  533. package/src/openapi/graphql.ts +11 -0
  534. package/src/openapi/model.ts +72 -27
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +182 -4
  537. package/src/openapi/references.ts +64 -8
  538. package/src/openapi/render-mdx.ts +5 -1
  539. package/src/openapi/scalar.ts +40 -6
  540. package/src/openapi/source.ts +40 -15
  541. package/src/ratelimit/cloudflare.ts +54 -0
  542. package/src/ratelimit/index.ts +30 -0
  543. package/src/ratelimit/memory.ts +45 -0
  544. package/src/ratelimit/runtime.ts +247 -0
  545. package/src/ratelimit/schema.ts +28 -0
  546. package/src/ratelimit/upstash.ts +60 -0
  547. package/src/ratelimit/wrangler.ts +70 -0
  548. package/src/reference/asyncapi.ts +2 -2
  549. package/src/reference/graphql.ts +20 -3
  550. package/src/reference/index.ts +1 -0
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +80 -8
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +23 -5
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/locale-scope.ts +30 -0
  557. package/src/search/adapters/typesense.ts +14 -4
  558. package/src/search/build.ts +21 -3
  559. package/src/search/documents.ts +26 -2
  560. package/src/search/orama-index.ts +191 -34
  561. package/src/search/pagefind-ranking.ts +39 -0
  562. package/src/search/source-pages.ts +29 -0
  563. package/src/search/sync/algolia.ts +111 -10
  564. package/src/search/sync/index.ts +52 -13
  565. package/src/search/sync/orama-cloud.ts +3 -0
  566. package/src/search/sync/typesense.ts +76 -26
  567. package/src/seo/metatags.ts +67 -0
  568. package/src/sources/contentful.ts +12 -0
  569. package/src/sources/github-releases.ts +7 -1
  570. package/src/sources/shared.ts +3 -0
  571. package/src/theme/chrome-icons.ts +1 -0
  572. package/src/theme/color.ts +500 -0
  573. package/src/theme/entry.ts +85 -6
  574. package/src/theme/palette.ts +121 -45
  575. package/src/translate/prompts.ts +1 -0
  576. package/src/translate/validate.ts +31 -3
  577. package/src/upgrade/upgrade.ts +4 -171
  578. package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
  579. package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
  580. package/dist/cli/chunk-35d4wj9f.js.map +0 -15
  581. package/dist/cli/chunk-364znk6q.js +0 -75
  582. package/dist/cli/chunk-364znk6q.js.map +0 -11
  583. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  584. package/dist/cli/chunk-8ktnccpt.js.map +0 -10
  585. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  586. package/dist/cli/chunk-b07cmahc.js +0 -145
  587. package/dist/cli/chunk-b07cmahc.js.map +0 -11
  588. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  589. package/dist/cli/chunk-e04dxsz1.js +0 -39
  590. package/dist/cli/chunk-e04dxsz1.js.map +0 -10
  591. package/dist/cli/chunk-j85vccga.js.map +0 -36
  592. package/dist/cli/chunk-p3v96n38.js.map +0 -10
  593. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  594. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  595. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  596. package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
  597. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  598. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  599. package/docs/content/sources.mdx +0 -334
  600. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
  601. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
  602. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
  603. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
  604. /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
  605. /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
  606. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
  607. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
@@ -156,22 +156,31 @@ const ICONS = {
156
156
 
157
157
  // --- Frontmatter field policy ----------------------------------------------
158
158
  // Top-level keys Blume's strict schema rejects: delete the whole block, report.
159
- const DROP = new Set([
160
- "groups",
161
- "hideApiMarker",
162
- "hideFooterPagination",
163
- "iconType",
164
- "keywords",
165
- "mode",
166
- "public",
167
- "rss",
168
- ]);
159
+ const DROP = new Set(["groups", "hideApiMarker", "iconType", "public", "rss"]);
169
160
 
170
- // Mintlify-only keys → Blume nested target. `[parent, child]`.
161
+ // Keys whose fate depends on their value: the source value (and its line) →
162
+ // the line that replaces it, the line itself to keep it, or `null` to drop it
163
+ // (a value Blume has no counterpart for, or one that restates its default).
164
+ const REPLACE = {
165
+ hideFooterPagination: (value) =>
166
+ value === "true" ? "pagination: false" : null,
167
+ mode: (value, line) => (value === "assistant" ? null : line),
168
+ };
169
+
170
+ // Mintlify-only keys → Blume nested target. `[parent, child]`, plus a value
171
+ // mapper where the meaning flips: it returns the new value, or `null` to drop a
172
+ // value that restates Blume's default.
171
173
  const RENAME = {
174
+ boost: ["search", "boost"],
172
175
  canonical: ["seo", "canonical"],
176
+ keywords: ["search", "keywords"],
173
177
  "og:image": ["seo", "image"],
174
178
  ogImage: ["seo", "image"],
179
+ searchable: [
180
+ "search",
181
+ "exclude",
182
+ (value) => (value === "false" ? "true" : null),
183
+ ],
175
184
  sidebarTitle: ["sidebar", "label"],
176
185
  tag: ["sidebar", "badge"],
177
186
  };
@@ -179,7 +188,8 @@ const RENAME = {
179
188
  // Keys we deliberately do NOT auto-transform — they usually mean the page is an
180
189
  // OpenAPI endpoint stub that should be deleted (Blume generates operation pages),
181
190
  // or else a normal page that just loses the key. Flag for the human; never guess.
182
- const FLAG = new Set(["api", "asyncapi", "openapi"]);
191
+ // (`api` is not one: a hand-written endpoint page keeps it as written.)
192
+ const FLAG = new Set(["asyncapi", "openapi"]);
183
193
 
184
194
  // Which change kinds actually edit the file. Report-only kinds (flags,
185
195
  // unknowns, conflicts, manual-rename notices) leave the bytes untouched.
@@ -334,11 +344,41 @@ const rewriteFields = (fm) => {
334
344
  changes.push({ detail: tk.key, kind: "flag" });
335
345
  continue;
336
346
  }
347
+ if (Object.hasOwn(REPLACE, tk.key)) {
348
+ const [start, end] = blockRange(fm, i);
349
+ const value = tk.value.replace(
350
+ /^(?<q>["'])(?<inner>.*)\k<q>$/u,
351
+ "$<inner>"
352
+ );
353
+ const line =
354
+ end - start === 1 ? REPLACE[tk.key](value, fm[start]) : undefined;
355
+ if (line === undefined) {
356
+ changes.push({ detail: tk.key, kind: "rename-manual" });
357
+ } else if (line === null) {
358
+ fm.splice(start, 1);
359
+ changes.push({ detail: `${tk.key}: ${value}`, kind: "drop" });
360
+ } else if (line !== fm[start]) {
361
+ const [key] = line.split(":");
362
+ if (fm.some((other, j) => j !== start && topKey(other)?.key === key)) {
363
+ changes.push({
364
+ detail: `${tk.key} → ${key} (child-exists)`,
365
+ kind: "rename-conflict",
366
+ });
367
+ } else {
368
+ fm[start] = line;
369
+ changes.push({
370
+ detail: `${tk.key}: ${value} → ${line}`,
371
+ kind: "rename",
372
+ });
373
+ }
374
+ }
375
+ continue;
376
+ }
337
377
  const target = RENAME[tk.key];
338
378
  if (!target) {
339
379
  continue;
340
380
  }
341
- const [parent, child] = target;
381
+ const [parent, child, map] = target;
342
382
  const [start, end] = blockRange(fm, i);
343
383
  if (end - start !== 1 || tk.value === "") {
344
384
  // Multi-line or valueless source — too structured to move safely.
@@ -348,12 +388,18 @@ const rewriteFields = (fm) => {
348
388
  });
349
389
  continue;
350
390
  }
391
+ const value = map ? map(tk.value) : tk.value;
392
+ if (value === null) {
393
+ fm.splice(start, 1);
394
+ changes.push({ detail: `${tk.key}: ${tk.value}`, kind: "drop" });
395
+ continue;
396
+ }
351
397
  // Remove the source line before inserting: setNested splices into the
352
398
  // parent block, and when that block sits above the source key the insert
353
399
  // would otherwise shift `start` onto the wrong line. Failure paths don't
354
400
  // mutate `fm`, so the line can be restored as-is on conflict.
355
401
  const [removed] = fm.splice(start, 1);
356
- const placed = setNested(fm, parent, child, tk.value);
402
+ const placed = setNested(fm, parent, child, value);
357
403
  if (placed.ok) {
358
404
  if (placed.index < start) {
359
405
  // The insert above the cursor pushed the unvisited lines down one
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: blume-write-skill
3
+ description: Write the agent skill for a product's Blume docs site — one SKILL.md, grounded in the docs, that teaches a coding agent to use the product (setup, core concepts, common tasks, gotchas) and links each topic's page. Blume publishes it at the site's /skill.md in place of the page map it generates. Use when asked to write, improve, or refresh a docs site's agent skill, or when `blume skill` hands you this task.
4
+ ---
5
+
6
+ # Write a docs site's agent skill
7
+
8
+ Every Blume site already publishes a generated skill at `/skill.md`: a map of its pages with their one-line descriptions, built without a model. It tells an agent where things are, not how the product works. Your job is to write the skill that does: what the product is, how to set it up, the concepts and tasks that matter, and where it bites, with every topic linked to the page that covers it.
9
+
10
+ The reader is a coding agent working in someone else's project. It loaded your skill because a task touches this product, and it needs to act correctly without reading the whole site first.
11
+
12
+ ## Ground rules
13
+
14
+ - **Only what the docs say.** Every claim, command, config key, flag, environment variable, and code sample must come from a page in the content root, copied rather than paraphrased. Never fill a gap from memory or from what similar products do. Where the docs don't answer something the skill needs, leave it out and list it in your report as a docs gap.
15
+ - **One file.** Keep it a lone `SKILL.md`: no `references/`, `scripts/`, or `assets/`. A single file is what the site serves at `/skill.md` and what an agent can fetch in one request. Depth lives in the docs, so link to them instead of copying them in.
16
+ - **Absolute links to Markdown.** Link pages as `<site><route>.md` (the home page is `<site>/index.md`), using the site URL your prompt gives. Link only pages that exist: a page's route is its path under the content root without the extension, and a folder's `index` page takes the folder's route.
17
+ - **Keep the given name.** The frontmatter `name` must be exactly the one in your prompt. That match is what makes your skill replace the generated one.
18
+ - **Short beats complete.** Aim for well under 300 lines. Skip anything a capable agent already knows (general programming, Markdown, how HTTP works) and anything it can look up from a link when it needs it.
19
+
20
+ ## Workflow
21
+
22
+ 1. **Read the docs.** Start with `blume.config.ts` (the site's title, description, `content.root`, navigation, API references) and the `meta.ts` files, which give the sidebar order. Then read the home page, the quickstart or installation page, each core-concept page, the configuration reference, the API reference overview, and any troubleshooting or FAQ pages. If the site has been built, its `llms.txt` lists every page with its description.
23
+
24
+ 2. **Decide what matters.** Before writing, note:
25
+ - the product in two or three sentences: what it is, who uses it, and how it's installed or reached
26
+ - the five to ten tasks people most often come to the docs to do
27
+ - the concepts an agent must understand to do those tasks right
28
+ - the sharp edges: limits, required setup, defaults that surprise people, common errors, and anything deprecated along with what replaced it
29
+
30
+ 3. **Write the skill.** Use this shape, dropping any section the docs give you nothing for:
31
+
32
+ ```markdown
33
+ ---
34
+ name: <the name from your prompt>
35
+ description: <What the product is and when to use this skill, in the words a user would type: the product name, its package and CLI names, its key concepts. At most 1024 characters.>
36
+ ---
37
+
38
+ # <Product>
39
+
40
+ <Two or three sentences: what it is, who it's for, how to install or reach it.>
41
+
42
+ ## Setup
43
+
44
+ <Install, configure, and authenticate, verbatim from the docs, with a link to the page.>
45
+
46
+ ## Core concepts
47
+
48
+ - **<Concept>**: <one line>. [<Page title>](<site><route>.md)
49
+
50
+ ## Common tasks
51
+
52
+ ### <Task>
53
+
54
+ 1. <Step, with the exact command or code from the docs.>
55
+
56
+ See [<Page title>](<site><route>.md).
57
+
58
+ ## Gotchas
59
+
60
+ - <The limit, default, or error, and what to do about it.>
61
+
62
+ ## Where to look
63
+
64
+ - [<Section>](<site><route>.md): <when to read it>
65
+ - [llms.txt](<site>/llms.txt): every page with a one-line summary.
66
+ ```
67
+
68
+ The `description` decides whether an agent loads the skill at all, so spend time on it. Name the product the way users do, and cover the situations it's for ("setting up X", "calling the X API", "configuring X"). Write the body in the imperative, for an agent that will act on it.
69
+
70
+ If the site serves an MCP server or other agent surfaces, the generated skill names them. Carry those lines over under "Where to look".
71
+
72
+ 4. **Check it.**
73
+ - Every link points at a page that exists in the content root, as `<site><route>.md`.
74
+ - Every command, code sample, config key, and default appears in the docs as written.
75
+ - The frontmatter parses, `name` is exactly as given, and `description` is 1024 characters or fewer.
76
+ - Then run the docs build (`blume build`). The build output's `skill.md` should now be yours, and `.well-known/agent-skills/index.json` should list the name once.
77
+
78
+ 5. **Report.** Say where you wrote the skill and any config you changed, summarize what it covers, and list the docs gaps you found: questions the skill needed answered that no page does. Those are worth fixing in the docs.
79
+
80
+ ## Keeping it current
81
+
82
+ A hand-written skill doesn't update itself the way the generated one does. After a meaningful docs change (new features, renamed APIs, a changed setup flow), run `blume skill` again to refresh it. Update the existing file rather than starting over, and keep what's still true.
@@ -8,6 +8,7 @@ import { buildRssFeeds } from "../deploy/rss.ts";
8
8
  import { AI_CATALOG_PATH, hasAiCatalog } from "./ai-catalog.ts";
9
9
  import { hasApiCatalog } from "./api-catalog.ts";
10
10
  import { API_PAGES_PATH, API_SEARCH_PATH, OPENAPI_PATH } from "./api/paths.ts";
11
+ import { servesSkillsIndex } from "./skills.ts";
11
12
 
12
13
  /** Token map for the machine-readable content-usage echo. */
13
14
  const USAGE_TOKENS: [keyof ContentSignalPolicy, string][] = [
@@ -145,7 +146,7 @@ const wellKnownArtifacts = (
145
146
  if (hasApiCatalog(config)) {
146
147
  artifacts.apiCatalog = abs("/.well-known/api-catalog");
147
148
  }
148
- if (config.agents.skills) {
149
+ if (servesSkillsIndex(config)) {
149
150
  artifacts.agentSkills = abs("/.well-known/agent-skills/index.json");
150
151
  }
151
152
  return artifacts;
@@ -11,7 +11,8 @@ export interface EmittedAgentSurface {
11
11
  mcp: boolean;
12
12
  /**
13
13
  * A skills discovery index is served: generated from `agents.skills` (at
14
- * least one valid skill), or shipped by the user in `public/`.
14
+ * least one valid skill) or the site skill (`agents.skillMd`), or shipped
15
+ * by the user in `public/`.
15
16
  */
16
17
  skills: boolean;
17
18
  }
@@ -33,12 +34,12 @@ export const servesMcp = (
33
34
  );
34
35
 
35
36
  /**
36
- * The config the discovery documents are built from: `agents.mcp` and
37
- * `agents.skills` switched off when the build didn't emit them, so llms.txt,
38
- * agent-readability.json, the catalogs, and the header rules only point at
39
- * what is there. A config flag says what was asked for; the MCP server is
40
- * skipped when a page owns its route, and skills publish nothing when their
41
- * directory is missing or holds no valid skill.
37
+ * The config the discovery documents are built from: `agents.mcp`,
38
+ * `agents.skills`, and `agents.skillMd` switched off when the build didn't
39
+ * emit them, so llms.txt, agent-readability.json, the catalogs, and the
40
+ * header rules only point at what is there. A config flag says what was asked
41
+ * for; the MCP server is skipped when a page owns its route, and skills
42
+ * publish nothing when their directory is missing or holds no valid skill.
42
43
  */
43
44
  export const advertisedConfig = (
44
45
  config: ResolvedConfig,
@@ -51,6 +52,7 @@ export const advertisedConfig = (
51
52
  ...config.agents.mcp,
52
53
  enabled: config.agents.mcp.enabled && emitted.mcp,
53
54
  },
55
+ skillMd: config.agents.skillMd && emitted.skills,
54
56
  skills: emitted.skills ? config.agents.skills : undefined,
55
57
  },
56
58
  });
@@ -10,7 +10,7 @@ import { resolveReferences } from "../openapi/references.ts";
10
10
  import { API_CATALOG_PATH, hasApiCatalog } from "./api-catalog.ts";
11
11
  import { OPENAPI_PATH } from "./api/paths.ts";
12
12
  import { asciiSlugify } from "./mcp/discovery.ts";
13
- import { AGENT_SKILLS_DIR } from "./skills.ts";
13
+ import { AGENT_SKILLS_DIR, servesSkillsIndex } from "./skills.ts";
14
14
  import type { SkillArtifact } from "./skills.ts";
15
15
 
16
16
  /**
@@ -86,7 +86,7 @@ export const hasAiCatalog = (config: ResolvedConfig): boolean =>
86
86
  (config.agents.mcp.enabled ||
87
87
  config.agents.api ||
88
88
  config.agents.llmsTxt.enabled ||
89
- Boolean(config.agents.skills) ||
89
+ servesSkillsIndex(config) ||
90
90
  resolveReferences(config).length > 0);
91
91
 
92
92
  /**
@@ -118,7 +118,10 @@ const NAMES_DOCS = /\b(?:docs|documentation)$/iu;
118
118
  * reads "the Acme documentation" and "Acme Docs" reads "the Acme Docs", not
119
119
  * "the Acme Docs docs".
120
120
  */
121
- const docsPhrase = (title: string, noun: "docs" | "documentation"): string =>
121
+ export const docsPhrase = (
122
+ title: string,
123
+ noun: "docs" | "documentation"
124
+ ): string =>
122
125
  NAMES_DOCS.test(title.trim()) ? `the ${title}` : `the ${title} ${noun}`;
123
126
 
124
127
  const entrySeeds = (
@@ -228,6 +228,17 @@ const queryTerms = (query: string): string[] =>
228
228
  const BASE_INSTRUCTION =
229
229
  "You are a helpful documentation assistant for this project. Answer the user's question using ONLY the documentation excerpts below. Each excerpt is headed by its page as `## Page Title (/route)`. If the answer is not covered by the excerpts, say you don't know and suggest where in the docs to look — do not invent details. Always cite the pages you drew from, and write every citation as a Markdown link to that page using its route, e.g. [Page Title](/route).";
230
230
 
231
+ /**
232
+ * Appended when the endpoint gives the model the docs tools (see
233
+ * `ask-tools.ts`): the excerpts are a head start, not the whole of the docs.
234
+ */
235
+ const TOOL_INSTRUCTION =
236
+ "You can also search the documentation yourself. When the excerpts don't fully answer the question, call search_docs with a focused query to find other pages, and read_page to read a page in full (one an excerpt cuts off, say) before you answer. Treat tool results exactly like the excerpts: answer only from them, and cite the pages you drew from the same way.";
237
+
238
+ /** Said instead of the excerpts when retrieval found none but tools can look. */
239
+ const NO_EXCERPTS_WITH_TOOLS =
240
+ "No documentation excerpts matched this question up front. Use search_docs to look before you answer.";
241
+
231
242
  /** The non-empty user turns, oldest first. Assistant turns never seed retrieval. */
232
243
  const userTurns = (messages: AskMessage[]): string[] =>
233
244
  messages
@@ -591,6 +602,78 @@ export const sectionExcerpt = (
591
602
  max: number
592
603
  ): string => excerptPage(parsePage(content), query, max);
593
604
 
605
+ // One Orama index per snapshot, shared by the grounding function and the
606
+ // docs tools so a cold endpoint builds it once.
607
+ const indexes = new WeakMap<AskData, ReturnType<typeof buildOramaIndex>>();
608
+
609
+ /** The search index over an assistant snapshot, built on first use. */
610
+ export const askIndex = (data: AskData): ReturnType<typeof buildOramaIndex> => {
611
+ let index = indexes.get(data);
612
+ if (!index) {
613
+ index = buildOramaIndex(data.documents, data.defaultLocale);
614
+ indexes.set(data, index);
615
+ }
616
+ return index;
617
+ };
618
+
619
+ /** The page the reader is on, and the search filters it implies. */
620
+ export interface ReaderScope {
621
+ current: OramaDoc | undefined;
622
+ filters: { locale: string | undefined; version: string | undefined };
623
+ }
624
+
625
+ // The snapshot's locale codes, keyed lowercase, built once per snapshot.
626
+ const localeCodes = new WeakMap<AskData, Map<string, string>>();
627
+
628
+ /**
629
+ * The locale a path's first segment names (`/ja` or `/ja/pricing` → `ja`),
630
+ * when it's one the snapshot's documents carry: a page outside the docs,
631
+ * like a localized landing page, still says which language it's in.
632
+ */
633
+ const pathLocale = (data: AskData, path: string): string | undefined => {
634
+ let codes = localeCodes.get(data);
635
+ if (!codes) {
636
+ codes = new Map<string, string>();
637
+ for (const { locale } of data.documents) {
638
+ if (locale) {
639
+ codes.set(locale.toLowerCase(), locale);
640
+ }
641
+ }
642
+ localeCodes.set(data, codes);
643
+ }
644
+ const [, first = ""] = normalizeRoute(path).split("/");
645
+ return first ? codes.get(first.toLowerCase()) : undefined;
646
+ };
647
+
648
+ /**
649
+ * Where the reader is: their page, when it's in the snapshot, and the locale
650
+ * and docs version retrieval keeps to. A page the snapshot doesn't have (the
651
+ * homepage, a custom page, or none at all from a cross-origin caller) keeps
652
+ * to the locale its path's first segment names, or the default locale, so
653
+ * translations of every page don't crowd each other out. Without a page, a
654
+ * versioned site grounds in the current docs rather than every archived copy
655
+ * of each page.
656
+ */
657
+ export const readerScope = (
658
+ data: AskData,
659
+ byRoute: ReadonlyMap<string, OramaDoc>,
660
+ page?: AskPage
661
+ ): ReaderScope => {
662
+ const current = page?.path
663
+ ? byRoute.get(normalizeRoute(page.path))
664
+ : undefined;
665
+ const locale = current
666
+ ? current.locale
667
+ : (page?.path && pathLocale(data, page.path)) || data.defaultLocale;
668
+ return {
669
+ current,
670
+ filters: {
671
+ locale: locale || undefined,
672
+ version: data.versioned ? (current?.version ?? "") : undefined,
673
+ },
674
+ };
675
+ };
676
+
594
677
  /**
595
678
  * Build the request-time grounding function for the assistant endpoint.
596
679
  *
@@ -607,20 +690,22 @@ export const sectionExcerpt = (
607
690
  *
608
691
  * `options.retrieval` (the `ai.assistant.retrieval` config) sizes how much
609
692
  * documentation each question carries; omitted fields keep today's defaults.
693
+ *
694
+ * `options.tools` says the endpoint also hands the model the docs tools: the
695
+ * prompt then says so, and a question retrieval found nothing for still gets
696
+ * a grounded prompt, since the model can go and search.
610
697
  */
611
698
  export const createAskContext = (
612
699
  data: AskData,
613
- options?: { instructions?: string; retrieval?: AskRetrievalOptions }
700
+ options?: {
701
+ instructions?: string;
702
+ retrieval?: AskRetrievalOptions;
703
+ tools?: boolean;
704
+ }
614
705
  ): ((
615
706
  messages: AskMessage[],
616
707
  page?: AskPage
617
708
  ) => Promise<string | undefined>) => {
618
- let dbPromise: Promise<Awaited<ReturnType<typeof buildOramaIndex>>> | null =
619
- null;
620
- const index = () => {
621
- dbPromise ??= buildOramaIndex(data.documents, data.defaultLocale);
622
- return dbPromise;
623
- };
624
709
  const byRoute = new Map(data.documents.map((doc) => [doc.route, doc]));
625
710
  // Section splitting and tokenizing are per page, not per question, so each
626
711
  // page is parsed on first use and reused for the life of the endpoint.
@@ -633,9 +718,12 @@ export const createAskContext = (
633
718
  }
634
719
  return page;
635
720
  };
636
- const instruction = options?.instructions
637
- ? `${BASE_INSTRUCTION}\n\n${options.instructions}`
721
+ const base = options?.tools
722
+ ? `${BASE_INSTRUCTION} ${TOOL_INSTRUCTION}`
638
723
  : BASE_INSTRUCTION;
724
+ const instruction = options?.instructions
725
+ ? `${base}\n\n${options.instructions}`
726
+ : base;
639
727
  const maxResults = options?.retrieval?.maxResults ?? MAX_RESULTS;
640
728
  const excerptChars = options?.retrieval?.excerptChars ?? EXCERPT_CHARS;
641
729
  const contextBudget = options?.retrieval?.contextBudget ?? CONTEXT_BUDGET;
@@ -652,16 +740,9 @@ export const createAskContext = (
652
740
  const [query = ""] = queries;
653
741
 
654
742
  // The current page anchors retrieval to its locale and docs version, and
655
- // is injected first. Without one, a versioned site grounds in the
656
- // current docs rather than every archived copy of each page.
657
- const current = page?.path
658
- ? byRoute.get(normalizeRoute(page.path))
659
- : undefined;
660
- const db = await index();
661
- const filters = {
662
- locale: current?.locale || undefined,
663
- version: data.versioned ? (current?.version ?? "") : undefined,
664
- };
743
+ // is injected first.
744
+ const { current, filters } = readerScope(data, byRoute, page);
745
+ const db = await askIndex(data);
665
746
  const hits = interleave(
666
747
  await Promise.all(
667
748
  queries.map((text) => queryOramaIndex(db, text, maxResults, filters))
@@ -700,7 +781,9 @@ export const createAskContext = (
700
781
  }
701
782
 
702
783
  if (sections.length === 0) {
703
- return;
784
+ return options?.tools
785
+ ? `${instruction}\n\n${NO_EXCERPTS_WITH_TOOLS}`
786
+ : undefined;
704
787
  }
705
788
  return `${instruction}\n\n<docs>\n${sections.join("\n\n")}\n</docs>`;
706
789
  };
@@ -36,6 +36,12 @@ export const buildAskData = async (project: BlumeProject): Promise<AskData> => {
36
36
  if (versioned) {
37
37
  document.version = doc.version;
38
38
  }
39
+ if (doc.boost !== undefined) {
40
+ document.boost = doc.boost;
41
+ }
42
+ if (doc.keywords) {
43
+ document.keywords = doc.keywords;
44
+ }
39
45
  return document;
40
46
  }),
41
47
  site: project.config.deployment.options.site ?? null,
@@ -0,0 +1,13 @@
1
+ /**
2
+ * What one request to the assistant's generated route (`/api/ask`) may carry.
3
+ * The route answers a longer conversation with a `400` (see
4
+ * `askEndpointTemplate`), so the panel's client (`useAssistant`) sends only
5
+ * the latest turns that fit. The endpoint is unauthenticated, so the limits
6
+ * bound what one request can spend against the model.
7
+ */
8
+
9
+ /** The most messages, questions and answers together, one request sends. */
10
+ export const ASK_MAX_MESSAGES = 40;
11
+
12
+ /** The most characters the messages take as JSON (`JSON.stringify`). */
13
+ export const ASK_MAX_MESSAGES_CHARS = 24_000;
@@ -0,0 +1,142 @@
1
+ import { jsonSchema, tool } from "ai";
2
+
3
+ import { normalizeRoute } from "../core/base-path.ts";
4
+ import { queryOramaIndex } from "../search/orama-index.ts";
5
+ import type { OramaDoc } from "../search/orama-index.ts";
6
+ import { askIndex, readerScope, sectionExcerpt } from "./ask-context.ts";
7
+ import type { AskData, AskPage, ReaderScope } from "./ask-context.ts";
8
+
9
+ /**
10
+ * The assistant's docs tools: what lets it look beyond the excerpts the
11
+ * endpoint injects up front. `search_docs` runs the same Orama search the
12
+ * grounding does, scoped to the reader's language and docs version;
13
+ * `read_page` returns a whole page, for when an excerpt stops short of the
14
+ * answer. Both read the assistant snapshot the endpoint already imports, so
15
+ * they add no build output and no network call.
16
+ *
17
+ * The generated `/api/ask` route passes them to `streamText` with a step cap
18
+ * ({@link ASK_MAX_STEPS}), so the model can search, read, and then answer
19
+ * within one request.
20
+ */
21
+
22
+ /**
23
+ * The most model steps one answer may take: up to four rounds of tool calls,
24
+ * then the answer. Enough to search, read a page or two, and search again,
25
+ * while bounding what one question can spend.
26
+ */
27
+ export const ASK_MAX_STEPS = 5;
28
+
29
+ /** Pages one search returns. */
30
+ const SEARCH_RESULTS = 5;
31
+
32
+ /** Characters of each search result's most relevant excerpt. */
33
+ const SEARCH_EXCERPT_CHARS = 700;
34
+
35
+ /** Characters of a page `read_page` returns; a longer page is cut here. */
36
+ const PAGE_CHARS = 20_000;
37
+
38
+ /** A result block, headed the way the injected excerpts are, for citing. */
39
+ const block = (doc: OramaDoc, body: string): string =>
40
+ `## ${doc.title} (${doc.route})\n${body}`;
41
+
42
+ /**
43
+ * Search the docs for `query` within the reader's scope: the most relevant
44
+ * excerpt of each of the top pages, or a note that nothing matched.
45
+ */
46
+ export const searchDocs = async (
47
+ data: AskData,
48
+ scope: ReaderScope,
49
+ query: string
50
+ ): Promise<string> => {
51
+ const hits = await queryOramaIndex(
52
+ await askIndex(data),
53
+ query,
54
+ SEARCH_RESULTS,
55
+ scope.filters
56
+ );
57
+ if (hits.length === 0) {
58
+ return `No pages matched "${query}". Try other words, or a broader query.`;
59
+ }
60
+ return hits
61
+ .map((doc) =>
62
+ block(doc, sectionExcerpt(doc.content, query, SEARCH_EXCERPT_CHARS))
63
+ )
64
+ .join("\n\n");
65
+ };
66
+
67
+ /**
68
+ * The route a model asked for, as the snapshot keys it: a full URL on the
69
+ * site becomes its path, and a query string or fragment is dropped.
70
+ */
71
+ const routeOf = (data: AskData, requested: string): string => {
72
+ let path = requested.trim();
73
+ if (data.site && path.startsWith(data.site)) {
74
+ path = path.slice(data.site.replace(/\/$/u, "").length) || "/";
75
+ }
76
+ path = path.replace(/[?#].*$/u, "");
77
+ return normalizeRoute(path.startsWith("/") ? path : `/${path}`);
78
+ };
79
+
80
+ /** A whole page by route, cut at {@link PAGE_CHARS}, or a note that it doesn't exist. */
81
+ export const readPage = (
82
+ data: AskData,
83
+ byRoute: ReadonlyMap<string, OramaDoc>,
84
+ requested: string
85
+ ): string => {
86
+ const route = routeOf(data, requested);
87
+ const doc = byRoute.get(route);
88
+ if (!doc) {
89
+ return `There is no page at ${route}. Use search_docs to find the right one.`;
90
+ }
91
+ const text =
92
+ doc.content.length > PAGE_CHARS
93
+ ? `${doc.content.slice(0, PAGE_CHARS)}\n\n[The page continues; this is its first ${PAGE_CHARS.toLocaleString("en-US")} characters.]`
94
+ : doc.content;
95
+ return block(doc, text);
96
+ };
97
+
98
+ /**
99
+ * The tools for one request, given the page the reader is on. Built per
100
+ * request because the search scope follows the reader's locale and version.
101
+ */
102
+ export const createAskTools = (data: AskData) => {
103
+ const byRoute = new Map(data.documents.map((doc) => [doc.route, doc]));
104
+ return (page?: AskPage) => {
105
+ const scope = readerScope(data, byRoute, page);
106
+ return {
107
+ read_page: tool({
108
+ description:
109
+ "Read one documentation page in full, by its route (the path in parentheses after a page title, such as /docs/getting-started).",
110
+ execute: ({ route }) => Promise.resolve(readPage(data, byRoute, route)),
111
+ inputSchema: jsonSchema<{ route: string }>({
112
+ additionalProperties: false,
113
+ properties: {
114
+ route: {
115
+ description: "The page's route, such as /docs/getting-started.",
116
+ type: "string",
117
+ },
118
+ },
119
+ required: ["route"],
120
+ type: "object",
121
+ }),
122
+ }),
123
+ search_docs: tool({
124
+ description:
125
+ "Search this project's documentation. Returns the most relevant excerpt of each matching page, headed by its title and route.",
126
+ execute: ({ query }) => searchDocs(data, scope, query),
127
+ inputSchema: jsonSchema<{ query: string }>({
128
+ additionalProperties: false,
129
+ properties: {
130
+ query: {
131
+ description:
132
+ "A few focused keywords, such as the feature or option the question is about.",
133
+ type: "string",
134
+ },
135
+ },
136
+ required: ["query"],
137
+ type: "object",
138
+ }),
139
+ }),
140
+ };
141
+ };
142
+ };