blume 2.0.2 → 2.1.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 (601) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +113 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-6k4ftwze.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-6k4ftwze.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-zp79m0ts.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-ddndchfr.js → chunk-2f3ndkpw.js} +57 -29
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-fsmrqk8a.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-ppzjqwx2.js → chunk-2n4ps1b3.js} +17 -18
  12. package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-2n4ps1b3.js.map} +3 -3
  13. package/dist/cli/{chunk-5r8g91qn.js → chunk-43zcf4gz.js} +398 -159
  14. package/dist/cli/chunk-43zcf4gz.js.map +36 -0
  15. package/dist/cli/chunk-4ejxbzee.js +39 -0
  16. package/dist/cli/chunk-4ejxbzee.js.map +10 -0
  17. package/dist/cli/{chunk-hr8ne106.js → chunk-4h8z9vv5.js} +296 -77
  18. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  19. package/dist/cli/{chunk-k7pj68a8.js → chunk-5cg2rzgf.js} +18 -16
  20. package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5cg2rzgf.js.map} +2 -2
  21. package/dist/cli/{chunk-h2ez8dzb.js → chunk-5ffywsmb.js} +4 -4
  22. package/dist/cli/{chunk-g698a744.js → chunk-5wj2qwn2.js} +5 -5
  23. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  24. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  25. package/dist/cli/{chunk-3yce002v.js → chunk-713mngdq.js} +15 -8
  26. package/dist/cli/chunk-713mngdq.js.map +14 -0
  27. package/dist/cli/{chunk-91ws1n6j.js → chunk-9geymqns.js} +17 -15
  28. package/dist/cli/{chunk-91ws1n6j.js.map → chunk-9geymqns.js.map} +2 -2
  29. package/dist/cli/{chunk-6dtt0zfn.js → chunk-9xr4dgee.js} +27 -18
  30. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  31. package/dist/cli/{chunk-sqw4ekg1.js → chunk-amrb6hgh.js} +2 -2
  32. package/dist/cli/chunk-cm89scdv.js +443 -0
  33. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  34. package/dist/cli/{chunk-wdrt2k2v.js → chunk-cmtcnh9f.js} +90 -26
  35. package/dist/cli/chunk-cmtcnh9f.js.map +10 -0
  36. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  37. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  38. package/dist/cli/{chunk-7mbqtmgb.js → chunk-e7vvk5ya.js} +21 -12
  39. package/dist/cli/chunk-e7vvk5ya.js.map +10 -0
  40. package/dist/cli/{chunk-qkb5a8sa.js → chunk-g2z9x01v.js} +6 -98
  41. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  42. package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
  43. package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
  44. package/dist/cli/{chunk-h7k3nq3v.js → chunk-gp84za0h.js} +2 -2
  45. package/dist/cli/{chunk-hqp2ajnh.js → chunk-h2a48yhd.js} +195 -73
  46. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  47. package/dist/cli/{chunk-pbg5a4s3.js → chunk-jnwj5kjj.js} +71 -47
  48. package/dist/cli/chunk-jnwj5kjj.js.map +19 -0
  49. package/dist/cli/chunk-kwrg63tm.js +31 -0
  50. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  51. package/dist/cli/{chunk-8g8ytmgx.js → chunk-nd1821sk.js} +21 -19
  52. package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-nd1821sk.js.map} +2 -2
  53. package/dist/cli/{chunk-w4bxdvsa.js → chunk-ntv4qrt7.js} +17 -15
  54. package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-ntv4qrt7.js.map} +2 -2
  55. package/dist/cli/{chunk-bfwp9vp6.js → chunk-p804f3pe.js} +7 -7
  56. package/dist/cli/{chunk-4e9b9ra6.js → chunk-py62fewh.js} +6 -6
  57. package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-py62fewh.js.map} +1 -1
  58. package/dist/cli/chunk-q3wxxq39.js +126 -0
  59. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  60. package/dist/cli/{chunk-j85scx15.js → chunk-rekqz4qg.js} +142 -46
  61. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  62. package/dist/cli/{chunk-00gs3wqs.js → chunk-ta27feev.js} +1 -1
  63. package/dist/cli/{chunk-v6ya5kcb.js → chunk-xkz5xbtv.js} +4031 -1132
  64. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  65. package/dist/cli/{chunk-bbnwccaz.js → chunk-y4ykbark.js} +2 -2
  66. package/dist/cli/{chunk-n1yg3tj3.js → chunk-y5h4r2y0.js} +6 -6
  67. package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-y5h4r2y0.js.map} +3 -3
  68. package/dist/cli/{chunk-nfcyttvj.js → chunk-yj17vs39.js} +9 -9
  69. package/dist/cli/{chunk-nfcyttvj.js.map → chunk-yj17vs39.js.map} +1 -1
  70. package/dist/cli/{chunk-7vtckvaw.js → chunk-yxmk4pec.js} +16 -14
  71. package/dist/cli/{chunk-7vtckvaw.js.map → chunk-yxmk4pec.js.map} +2 -2
  72. package/dist/cli/{chunk-gs7r695n.js → chunk-zccbjcj0.js} +22 -8
  73. package/dist/cli/{chunk-gs7r695n.js.map → chunk-zccbjcj0.js.map} +3 -3
  74. package/dist/cli/{chunk-xh43dwgw.js → chunk-zf748avv.js} +415 -225
  75. package/dist/cli/chunk-zf748avv.js.map +37 -0
  76. package/dist/cli/{chunk-d1v5rhy0.js → chunk-zhvd6adm.js} +16 -14
  77. package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-zhvd6adm.js.map} +2 -2
  78. package/dist/cli/index.js +28 -20
  79. package/dist/cli/index.js.map +5 -5
  80. package/dist/types/ai/agent-surface.d.ts +8 -7
  81. package/dist/types/ai/ai-catalog.d.ts +7 -0
  82. package/dist/types/ai/ask-context.d.ts +22 -0
  83. package/dist/types/ai/ask.d.ts +271 -58
  84. package/dist/types/ai/index.d.ts +2 -2
  85. package/dist/types/ai/link-headers.d.ts +8 -1
  86. package/dist/types/ai/openapi-components.d.ts +5 -2
  87. package/dist/types/ai/relative-links.d.ts +9 -7
  88. package/dist/types/ai/site-skill.d.ts +26 -0
  89. package/dist/types/ai/skills.d.ts +20 -1
  90. package/dist/types/ai/tar.d.ts +1 -3
  91. package/dist/types/analytics/index.d.ts +2 -0
  92. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  93. package/dist/types/analytics/schema.d.ts +14 -0
  94. package/dist/types/astro/integration.d.ts +11 -2
  95. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  96. package/dist/types/captcha/index.d.ts +28 -0
  97. package/dist/types/captcha/schema.d.ts +29 -0
  98. package/dist/types/captcha/turnstile.d.ts +28 -0
  99. package/dist/types/components/content/api-field.d.ts +59 -0
  100. package/dist/types/components/content/api-page.d.ts +95 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/helpers.d.ts +137 -0
  103. package/dist/types/components/openapi/security.d.ts +85 -0
  104. package/dist/types/consent/ethyca.d.ts +48 -0
  105. package/dist/types/consent/index.d.ts +37 -0
  106. package/dist/types/consent/native.d.ts +28 -0
  107. package/dist/types/consent/osano.d.ts +42 -0
  108. package/dist/types/consent/schema.d.ts +110 -0
  109. package/dist/types/core/base-path.d.ts +21 -7
  110. package/dist/types/core/config-input.d.ts +196 -27
  111. package/dist/types/core/data.d.ts +49 -4
  112. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  113. package/dist/types/core/footer.d.ts +8 -0
  114. package/dist/types/core/heading-markers.d.ts +5 -7
  115. package/dist/types/core/i18n-ui.d.ts +86 -0
  116. package/dist/types/core/i18n.d.ts +2 -0
  117. package/dist/types/core/includes.d.ts +8 -2
  118. package/dist/types/core/last-modified.d.ts +10 -0
  119. package/dist/types/core/localizable.d.ts +7 -0
  120. package/dist/types/core/meta.d.ts +8 -0
  121. package/dist/types/core/page-modes.d.ts +29 -0
  122. package/dist/types/core/redirect-patterns.d.ts +144 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +558 -56
  125. package/dist/types/core/sources/lower.d.ts +29 -16
  126. package/dist/types/core/sources/normalize.d.ts +13 -1
  127. package/dist/types/core/sources/read.d.ts +5 -0
  128. package/dist/types/core/sources/watch.d.ts +12 -5
  129. package/dist/types/core/standard-schema.d.ts +5 -0
  130. package/dist/types/core/types.d.ts +1 -1
  131. package/dist/types/core/variables.d.ts +42 -0
  132. package/dist/types/deploy/artifacts.d.ts +6 -4
  133. package/dist/types/deploy/headers.d.ts +5 -0
  134. package/dist/types/deploy/node-headers.d.ts +13 -2
  135. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  136. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  137. package/dist/types/deploy/platforms/types.d.ts +8 -0
  138. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  139. package/dist/types/deploy/redirects.d.ts +61 -22
  140. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  141. package/dist/types/markdown/code-title.d.ts +17 -0
  142. package/dist/types/markdown/directives.d.ts +62 -0
  143. package/dist/types/markdown/features.d.ts +24 -2
  144. package/dist/types/markdown/mdast.d.ts +63 -0
  145. package/dist/types/narration/build.d.ts +70 -0
  146. package/dist/types/narration/provider.d.ts +26 -0
  147. package/dist/types/narration/script.d.ts +154 -0
  148. package/dist/types/openapi/asyncapi.d.ts +4 -2
  149. package/dist/types/openapi/model.d.ts +12 -6
  150. package/dist/types/openapi/overlay.d.ts +54 -0
  151. package/dist/types/openapi/parse.d.ts +13 -0
  152. package/dist/types/openapi/references.d.ts +14 -4
  153. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  154. package/dist/types/ratelimit/index.d.ts +27 -0
  155. package/dist/types/ratelimit/memory.d.ts +36 -0
  156. package/dist/types/ratelimit/schema.d.ts +111 -0
  157. package/dist/types/ratelimit/upstash.d.ts +21 -0
  158. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  159. package/dist/types/reference/asyncapi.d.ts +10 -10
  160. package/dist/types/reference/graphql.d.ts +13 -10
  161. package/dist/types/reference/openapi.d.ts +33 -12
  162. package/dist/types/reference/options.d.ts +45 -5
  163. package/dist/types/reference/scalar.d.ts +13 -0
  164. package/dist/types/reference/schema.d.ts +60 -36
  165. package/dist/types/search/documents.d.ts +12 -1
  166. package/dist/types/search/orama-index.d.ts +4 -0
  167. package/dist/types/seo/metatags.d.ts +20 -0
  168. package/docs/02-deployment.mdx +44 -6
  169. package/docs/03-upgrading.mdx +4 -4
  170. package/docs/04-migrating.mdx +1 -1
  171. package/docs/08-faq.mdx +1 -1
  172. package/docs/advanced/changelog.mdx +2 -2
  173. package/docs/advanced/custom-pages.mdx +3 -3
  174. package/docs/advanced/skills.mdx +14 -0
  175. package/docs/cli/audit.mdx +38 -2
  176. package/docs/cli/evals.mdx +3 -3
  177. package/docs/cli/index.mdx +1 -0
  178. package/docs/cli/translate.mdx +3 -3
  179. package/docs/cli/version.mdx +1 -1
  180. package/docs/configuration/analytics.mdx +22 -1
  181. package/docs/configuration/assistant.mdx +188 -36
  182. package/docs/configuration/consent.mdx +100 -0
  183. package/docs/configuration/customization.mdx +6 -4
  184. package/docs/configuration/index.mdx +63 -5
  185. package/docs/configuration/meta.ts +3 -0
  186. package/docs/configuration/narration.mdx +119 -0
  187. package/docs/configuration/rate-limiting.mdx +69 -0
  188. package/docs/configuration/search.mdx +30 -2
  189. package/docs/configuration/theming.mdx +3 -1
  190. package/docs/content/components.mdx +107 -1
  191. package/docs/content/frontmatter.mdx +77 -1
  192. package/docs/content/i18n.mdx +40 -3
  193. package/docs/content/includes.mdx +16 -0
  194. package/docs/content/index.mdx +4 -2
  195. package/docs/content/islands.mdx +1 -1
  196. package/docs/content/meta.mdx +4 -1
  197. package/docs/content/meta.ts +1 -0
  198. package/docs/content/navigation.mdx +28 -6
  199. package/docs/content/sources/contentful.mdx +32 -0
  200. package/docs/content/sources/custom.mdx +39 -0
  201. package/docs/content/sources/github-releases.mdx +31 -0
  202. package/docs/content/sources/index.mdx +91 -0
  203. package/docs/content/sources/meta.ts +16 -0
  204. package/docs/content/sources/notion.mdx +32 -0
  205. package/docs/content/sources/obsidian.mdx +37 -0
  206. package/docs/content/sources/payload.mdx +30 -0
  207. package/docs/content/sources/remote-mdx.mdx +40 -0
  208. package/docs/content/sources/sanity.mdx +29 -0
  209. package/docs/content/sources/strapi.mdx +31 -0
  210. package/docs/content/syntax.mdx +81 -2
  211. package/docs/content/variables.mdx +41 -0
  212. package/docs/content/versioning.mdx +1 -1
  213. package/docs/discoverability/agent-discovery.mdx +30 -3
  214. package/docs/discoverability/index.mdx +3 -2
  215. package/docs/discoverability/llms-txt.mdx +1 -1
  216. package/docs/discoverability/markdown.mdx +2 -2
  217. package/docs/discoverability/metadata.mdx +17 -0
  218. package/docs/discoverability/open-graph.mdx +5 -3
  219. package/docs/discoverability/rss.mdx +1 -1
  220. package/docs/references/api-pages.mdx +74 -0
  221. package/docs/references/asyncapi.mdx +2 -2
  222. package/docs/references/graphql.mdx +3 -3
  223. package/docs/references/meta.ts +1 -1
  224. package/docs/references/openapi.mdx +106 -5
  225. package/docs/references/scalar.mdx +3 -1
  226. package/package.json +36 -1
  227. package/skills/blume/SKILL.md +3 -0
  228. package/skills/blume-migrate/SKILL.md +5 -5
  229. package/skills/blume-migrate/references/docusaurus.md +3 -3
  230. package/skills/blume-migrate/references/fumadocs.md +1 -1
  231. package/skills/blume-migrate/references/mintlify.md +37 -19
  232. package/skills/blume-migrate/references/nextra.md +3 -3
  233. package/skills/blume-migrate/references/starlight.md +2 -1
  234. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +67 -16
  235. package/skills/blume-write-skill/SKILL.md +82 -0
  236. package/src/ai/agent-readability.ts +4 -3
  237. package/src/ai/agent-surface.ts +9 -7
  238. package/src/ai/ai-catalog.ts +12 -5
  239. package/src/ai/api/handlers.ts +2 -2
  240. package/src/ai/api/spec.ts +2 -2
  241. package/src/ai/api-catalog.ts +6 -2
  242. package/src/ai/ask-context.ts +73 -20
  243. package/src/ai/ask-data.ts +6 -0
  244. package/src/ai/ask-tools.ts +142 -0
  245. package/src/ai/ask.ts +354 -76
  246. package/src/ai/changelog-markdown.ts +2 -2
  247. package/src/ai/component-markdown.ts +75 -0
  248. package/src/ai/cors.ts +2 -0
  249. package/src/ai/index.ts +12 -0
  250. package/src/ai/link-headers.ts +14 -5
  251. package/src/ai/llms.ts +24 -9
  252. package/src/ai/markdown.ts +2 -1
  253. package/src/ai/mcp/data.ts +6 -0
  254. package/src/ai/mcp/discovery.ts +2 -2
  255. package/src/ai/mcp/query.ts +2 -2
  256. package/src/ai/mcp/server.ts +2 -2
  257. package/src/ai/openapi-components.ts +31 -9
  258. package/src/ai/relative-links.ts +42 -26
  259. package/src/ai/serializers.ts +2 -1
  260. package/src/ai/site-skill.ts +274 -0
  261. package/src/ai/skills.ts +49 -3
  262. package/src/ai/tar.ts +139 -12
  263. package/src/ai/write-skill.ts +97 -0
  264. package/src/analytics/head.ts +4 -0
  265. package/src/analytics/index.ts +5 -0
  266. package/src/analytics/one-dollar-stats.ts +86 -0
  267. package/src/analytics/schema.ts +2 -0
  268. package/src/astro/generate.ts +133 -19
  269. package/src/astro/include-hmr.ts +31 -12
  270. package/src/astro/include-refresh.ts +19 -8
  271. package/src/astro/index.ts +1 -0
  272. package/src/astro/integration.ts +143 -14
  273. package/src/astro/module-types.ts +6 -0
  274. package/src/astro/runtime-modules.ts +28 -13
  275. package/src/astro/templates.ts +364 -67
  276. package/src/astro/variables.ts +31 -0
  277. package/src/audit/catalog.ts +19 -0
  278. package/src/audit/checks/contrast.ts +183 -0
  279. package/src/audit/checks/i18n.ts +2 -3
  280. package/src/audit/checks/indexability.ts +2 -3
  281. package/src/audit/checks/links.ts +13 -13
  282. package/src/audit/checks/sitemap.ts +2 -3
  283. package/src/audit/graph.ts +3 -1
  284. package/src/audit/redirects.ts +46 -15
  285. package/src/audit/run.ts +14 -9
  286. package/src/audit/snapshot.ts +5 -0
  287. package/src/audit/types.ts +2 -1
  288. package/src/blume-modules.d.ts +7 -0
  289. package/src/captcha/client.ts +152 -0
  290. package/src/captcha/hcaptcha.ts +36 -0
  291. package/src/captcha/index.ts +28 -0
  292. package/src/captcha/schema.ts +32 -0
  293. package/src/captcha/turnstile.ts +38 -0
  294. package/src/captcha/verify.ts +71 -0
  295. package/src/cli/args.ts +32 -0
  296. package/src/cli/command-meta.ts +5 -0
  297. package/src/cli/commands/audit.ts +4 -1
  298. package/src/cli/commands/doctor.ts +25 -17
  299. package/src/cli/commands/eval.ts +19 -11
  300. package/src/cli/commands/skill.ts +96 -0
  301. package/src/cli/commands/translate.ts +9 -12
  302. package/src/cli/commands/validate.ts +3 -1
  303. package/src/cli/dev-lock.ts +157 -37
  304. package/src/cli/index.ts +5 -0
  305. package/src/cli/report-format.ts +11 -6
  306. package/src/cli/required-secrets.ts +71 -29
  307. package/src/components/BlumePage.astro +2 -0
  308. package/src/components/colors.ts +19 -0
  309. package/src/components/content/ApiEndpoint.astro +18 -0
  310. package/src/components/content/ApiField.astro +79 -0
  311. package/src/components/content/ApiPlayground.astro +62 -0
  312. package/src/components/content/ApiRail.astro +19 -0
  313. package/src/components/content/Badge.astro +5 -3
  314. package/src/components/content/Color.astro +1 -0
  315. package/src/components/content/Component.astro +2 -2
  316. package/src/components/content/FileTree.astro +1 -0
  317. package/src/components/content/GithubInfo.astro +1 -0
  318. package/src/components/content/Panel.astro +1 -0
  319. package/src/components/content/ParamField.astro +25 -0
  320. package/src/components/content/RequestExample.astro +17 -0
  321. package/src/components/content/ResponseExample.astro +17 -0
  322. package/src/components/content/ResponseField.astro +32 -0
  323. package/src/components/content/Step.astro +1 -0
  324. package/src/components/content/Tab.astro +0 -1
  325. package/src/components/content/Tabs.astro +4 -0
  326. package/src/components/content/Tile.astro +1 -0
  327. package/src/components/content/Tooltip.astro +1 -0
  328. package/src/components/content/Tree.astro +1 -0
  329. package/src/components/content/TypeTable.astro +4 -1
  330. package/src/components/content/View.astro +21 -0
  331. package/src/components/content/ViewSwitcher.astro +168 -0
  332. package/src/components/content/api-field.ts +100 -0
  333. package/src/components/content/api-page.ts +224 -0
  334. package/src/components/content/badge-color.ts +5 -2
  335. package/src/components/content/base-href.ts +13 -36
  336. package/src/components/content/view-switcher.ts +71 -0
  337. package/src/components/index.ts +2 -0
  338. package/src/components/islands/Assistant.astro +10 -1
  339. package/src/components/islands/assistant.tsx +125 -18
  340. package/src/components/islands/base-path.ts +47 -10
  341. package/src/components/islands/code-question.ts +62 -0
  342. package/src/components/islands/hooks.ts +145 -40
  343. package/src/components/islands/support-link.ts +68 -0
  344. package/src/components/islands/webmcp.ts +4 -2
  345. package/src/components/layout/Analytics.astro +11 -2
  346. package/src/components/layout/Banner.astro +14 -10
  347. package/src/components/layout/Breadcrumbs.astro +11 -4
  348. package/src/components/layout/ConsentBanner.astro +62 -0
  349. package/src/components/layout/ConsentHead.astro +38 -0
  350. package/src/components/layout/Directory.astro +94 -0
  351. package/src/components/layout/DiscoveryLinks.astro +9 -5
  352. package/src/components/layout/DrawerSwitchers.astro +51 -0
  353. package/src/components/layout/Empty.astro +2 -2
  354. package/src/components/layout/Header.astro +124 -82
  355. package/src/components/layout/LanguageSwitcher.astro +50 -17
  356. package/src/components/layout/NarrationPlayer.astro +1085 -0
  357. package/src/components/layout/NavSelector.astro +35 -9
  358. package/src/components/layout/NavTabMenu.astro +3 -3
  359. package/src/components/layout/NavTree.astro +16 -6
  360. package/src/components/layout/PageActions.astro +4 -3
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +78 -9
  363. package/src/components/layout/Pagination.astro +3 -3
  364. package/src/components/layout/ReferenceLayout.astro +17 -6
  365. package/src/components/layout/RelatedPages.astro +42 -0
  366. package/src/components/layout/RootLayout.astro +313 -50
  367. package/src/components/layout/Search.astro +64 -30
  368. package/src/components/layout/SiteFooter.astro +124 -0
  369. package/src/components/layout/SiteMetatags.astro +16 -0
  370. package/src/components/layout/VersionBanner.astro +2 -2
  371. package/src/components/layout/analytics-client.ts +12 -0
  372. package/src/components/layout/consent/native.ts +79 -0
  373. package/src/components/layout/consent/types.ts +37 -0
  374. package/src/components/layout/footer-links.ts +53 -0
  375. package/src/components/layout/footer-socials.ts +87 -0
  376. package/src/components/layout/locale-redirect.ts +107 -0
  377. package/src/components/layout/nav-utils.ts +83 -0
  378. package/src/components/layout/page-feedback.ts +96 -0
  379. package/src/components/layout/search/analytics.ts +98 -0
  380. package/src/components/layout/search/flexsearch.ts +17 -5
  381. package/src/components/layout/search/orama-cloud.ts +26 -14
  382. package/src/components/layout/search/types.ts +20 -0
  383. package/src/components/layout/search/typesense.ts +5 -1
  384. package/src/components/layout/toc-active.ts +41 -0
  385. package/src/components/layout/toc-element.ts +8 -14
  386. package/src/components/openapi/ApiTagOperations.astro +2 -2
  387. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  388. package/src/components/openapi/GraphqlChip.astro +2 -2
  389. package/src/components/openapi/Operation.astro +163 -13
  390. package/src/components/openapi/Playground.astro +4 -4
  391. package/src/components/openapi/RequestBody.astro +6 -4
  392. package/src/components/openapi/RequestPanel.astro +63 -8
  393. package/src/components/openapi/Responses.astro +6 -4
  394. package/src/components/openapi/SchemaTable.astro +7 -0
  395. package/src/components/openapi/async-snippets.ts +4 -4
  396. package/src/components/openapi/async.ts +38 -6
  397. package/src/components/openapi/code-samples.ts +97 -0
  398. package/src/components/openapi/helpers.ts +19 -9
  399. package/src/components/openapi/message-composer.ts +6 -1
  400. package/src/components/openapi/message.ts +17 -2
  401. package/src/components/openapi/operation-model.ts +81 -11
  402. package/src/components/openapi/panel.ts +4 -2
  403. package/src/components/openapi/param-style.ts +181 -0
  404. package/src/components/openapi/playground-client.ts +73 -16
  405. package/src/components/openapi/request.ts +190 -26
  406. package/src/components/openapi/schema-tree.ts +30 -24
  407. package/src/components/openapi/security.ts +6 -3
  408. package/src/components/openapi/snippets.ts +953 -13
  409. package/src/components/openapi/ws-client.ts +18 -2
  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/base-path.ts +62 -16
  421. package/src/core/builtin-tags.ts +5 -0
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/config-input.ts +203 -27
  424. package/src/core/data.ts +44 -4
  425. package/src/core/diagnostics.ts +5 -2
  426. package/src/core/directive-diagnostics.ts +99 -0
  427. package/src/core/footer.ts +26 -0
  428. package/src/core/frontmatter.ts +21 -18
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/heading-markers.ts +5 -18
  431. package/src/core/i18n-ui.ts +82 -2
  432. package/src/core/i18n.ts +31 -3
  433. package/src/core/includes.ts +20 -3
  434. package/src/core/last-modified.ts +25 -3
  435. package/src/core/links.ts +16 -2
  436. package/src/core/locale-links.ts +5 -1
  437. package/src/core/localizable.ts +26 -0
  438. package/src/core/meta.ts +33 -19
  439. package/src/core/navigation.ts +71 -20
  440. package/src/core/page-modes.ts +56 -0
  441. package/src/core/project-graph.ts +138 -10
  442. package/src/core/redirect-patterns.ts +491 -0
  443. package/src/core/related.ts +131 -0
  444. package/src/core/schema.ts +342 -68
  445. package/src/core/sources/contentful-rich-text.ts +27 -20
  446. package/src/core/sources/filesystem.ts +20 -2
  447. package/src/core/sources/github-releases.ts +25 -6
  448. package/src/core/sources/lexical.ts +23 -18
  449. package/src/core/sources/lower.ts +201 -34
  450. package/src/core/sources/mdx-remote.ts +51 -16
  451. package/src/core/sources/normalize.ts +327 -91
  452. package/src/core/sources/notion.ts +52 -25
  453. package/src/core/sources/obsidian.ts +23 -6
  454. package/src/core/sources/portable-text.ts +38 -21
  455. package/src/core/sources/read.ts +8 -3
  456. package/src/core/sources/strapi-blocks.ts +20 -16
  457. package/src/core/sources/watch.ts +20 -7
  458. package/src/core/standard-schema.ts +10 -6
  459. package/src/core/types.ts +1 -1
  460. package/src/core/ui-packs/ar.ts +43 -0
  461. package/src/core/ui-packs/bg.ts +45 -0
  462. package/src/core/ui-packs/bn.ts +43 -0
  463. package/src/core/ui-packs/ca.ts +46 -0
  464. package/src/core/ui-packs/cs.ts +44 -0
  465. package/src/core/ui-packs/da.ts +45 -0
  466. package/src/core/ui-packs/de.ts +46 -0
  467. package/src/core/ui-packs/el.ts +45 -0
  468. package/src/core/ui-packs/es.ts +46 -0
  469. package/src/core/ui-packs/fa.ts +45 -0
  470. package/src/core/ui-packs/fi.ts +45 -0
  471. package/src/core/ui-packs/fr.ts +46 -0
  472. package/src/core/ui-packs/he.ts +43 -0
  473. package/src/core/ui-packs/hi.ts +44 -0
  474. package/src/core/ui-packs/hr.ts +45 -0
  475. package/src/core/ui-packs/hu.ts +44 -0
  476. package/src/core/ui-packs/id.ts +45 -0
  477. package/src/core/ui-packs/it.ts +45 -0
  478. package/src/core/ui-packs/ja.ts +45 -0
  479. package/src/core/ui-packs/ko.ts +44 -0
  480. package/src/core/ui-packs/nl.ts +46 -0
  481. package/src/core/ui-packs/no.ts +44 -0
  482. package/src/core/ui-packs/pl.ts +45 -0
  483. package/src/core/ui-packs/pt-br.ts +46 -0
  484. package/src/core/ui-packs/pt.ts +45 -0
  485. package/src/core/ui-packs/ro.ts +45 -0
  486. package/src/core/ui-packs/ru.ts +45 -0
  487. package/src/core/ui-packs/sk.ts +44 -0
  488. package/src/core/ui-packs/sr.ts +45 -0
  489. package/src/core/ui-packs/sv.ts +44 -0
  490. package/src/core/ui-packs/th.ts +43 -0
  491. package/src/core/ui-packs/tr.ts +45 -0
  492. package/src/core/ui-packs/uk.ts +45 -0
  493. package/src/core/ui-packs/vi.ts +45 -0
  494. package/src/core/ui-packs/zh-tw.ts +43 -0
  495. package/src/core/ui-packs/zh.ts +43 -0
  496. package/src/core/variables.ts +103 -0
  497. package/src/core/version-cut.ts +52 -15
  498. package/src/deploy/artifacts.ts +104 -14
  499. package/src/deploy/cloudflare-negotiation.ts +39 -17
  500. package/src/deploy/headers.ts +10 -5
  501. package/src/deploy/node-headers.ts +64 -10
  502. package/src/deploy/platforms/cloudflare.ts +44 -3
  503. package/src/deploy/platforms/netlify.ts +34 -1
  504. package/src/deploy/platforms/node.ts +1 -0
  505. package/src/deploy/platforms/static.ts +1 -0
  506. package/src/deploy/platforms/types.ts +8 -0
  507. package/src/deploy/platforms/vercel.ts +38 -2
  508. package/src/deploy/redirects.ts +139 -36
  509. package/src/deploy/robots.ts +2 -2
  510. package/src/deploy/rss.ts +4 -3
  511. package/src/deploy/sitemap.ts +7 -5
  512. package/src/deploy/vercel-negotiation.ts +36 -0
  513. package/src/markdown/api-rail.ts +259 -0
  514. package/src/markdown/base-links.ts +34 -36
  515. package/src/markdown/code-title.ts +35 -9
  516. package/src/markdown/directives.ts +242 -36
  517. package/src/markdown/features.ts +20 -2
  518. package/src/markdown/fence-meta.ts +2 -0
  519. package/src/markdown/include.ts +11 -1
  520. package/src/markdown/index.ts +35 -11
  521. package/src/markdown/math.ts +4 -4
  522. package/src/markdown/mdast.ts +5 -2
  523. package/src/markdown/relative-links.ts +12 -4
  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 +136 -11
  531. package/src/og/derive.ts +41 -31
  532. package/src/og/index.ts +1 -0
  533. package/src/openapi/asyncapi.ts +4 -2
  534. package/src/openapi/model.ts +77 -36
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +55 -2
  537. package/src/openapi/proxy.ts +63 -10
  538. package/src/openapi/references.ts +21 -5
  539. package/src/openapi/render-mdx.ts +46 -3
  540. package/src/openapi/scalar.ts +40 -6
  541. package/src/openapi/source.ts +1 -1
  542. package/src/ratelimit/cloudflare.ts +54 -0
  543. package/src/ratelimit/index.ts +30 -0
  544. package/src/ratelimit/memory.ts +45 -0
  545. package/src/ratelimit/runtime.ts +229 -0
  546. package/src/ratelimit/schema.ts +28 -0
  547. package/src/ratelimit/upstash.ts +30 -0
  548. package/src/ratelimit/wrangler.ts +70 -0
  549. package/src/reference/asyncapi.ts +2 -2
  550. package/src/reference/graphql.ts +5 -2
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +51 -7
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +201 -28
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/version-scope.ts +30 -0
  557. package/src/search/documents.ts +93 -28
  558. package/src/search/orama-index.ts +28 -3
  559. package/src/search/pagefind-ranking.ts +39 -0
  560. package/src/search/popular.ts +2 -1
  561. package/src/search/sync/algolia.ts +15 -4
  562. package/src/search/sync/orama-cloud.ts +3 -0
  563. package/src/search/sync/typesense.ts +5 -0
  564. package/src/seo/jsonld.ts +7 -3
  565. package/src/seo/metatags.ts +67 -0
  566. package/src/theme/chrome-icons.ts +1 -0
  567. package/src/theme/color.ts +500 -0
  568. package/src/theme/entry.ts +56 -3
  569. package/src/theme/palette.ts +121 -45
  570. package/src/translate/meta.ts +68 -24
  571. package/src/translate/prompts.ts +1 -0
  572. package/src/translate/run.ts +3 -3
  573. package/src/translate/validate.ts +12 -3
  574. package/src/translate/work-list.ts +56 -17
  575. package/src/upgrade/upgrade.ts +4 -171
  576. package/dist/cli/chunk-3yce002v.js.map +0 -14
  577. package/dist/cli/chunk-5r8g91qn.js.map +0 -34
  578. package/dist/cli/chunk-6dtt0zfn.js.map +0 -10
  579. package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
  580. package/dist/cli/chunk-ddndchfr.js.map +0 -14
  581. package/dist/cli/chunk-esh98wmb.js +0 -23
  582. package/dist/cli/chunk-esh98wmb.js.map +0 -10
  583. package/dist/cli/chunk-hqp2ajnh.js.map +0 -35
  584. package/dist/cli/chunk-hr8ne106.js.map +0 -13
  585. package/dist/cli/chunk-j85scx15.js.map +0 -10
  586. package/dist/cli/chunk-mqc662a6.js +0 -75
  587. package/dist/cli/chunk-mqc662a6.js.map +0 -11
  588. package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
  589. package/dist/cli/chunk-qkb5a8sa.js.map +0 -10
  590. package/dist/cli/chunk-v6ya5kcb.js.map +0 -189
  591. package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
  592. package/dist/cli/chunk-xh43dwgw.js.map +0 -36
  593. package/docs/content/sources.mdx +0 -338
  594. /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-1n228xqb.js.map} +0 -0
  595. /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-5ffywsmb.js.map} +0 -0
  596. /package/dist/cli/{chunk-g698a744.js.map → chunk-5wj2qwn2.js.map} +0 -0
  597. /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-amrb6hgh.js.map} +0 -0
  598. /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-gp84za0h.js.map} +0 -0
  599. /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-p804f3pe.js.map} +0 -0
  600. /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-ta27feev.js.map} +0 -0
  601. /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-y4ykbark.js.map} +0 -0
@@ -0,0 +1,152 @@
1
+ /**
2
+ * The browser half of the bot check: a fresh token for each question the
3
+ * assistant sends (both providers' tokens are single-use). The provider's
4
+ * script loads with the first question rather than with the page, and one
5
+ * widget serves every question after it: an invisible hCaptcha, or a
6
+ * Turnstile that only shows itself when it needs the reader to interact,
7
+ * in a small container at the foot of the viewport.
8
+ *
9
+ * The client router swaps `<body>` on every navigation, taking the container
10
+ * and its widget with it, so a question after a swap mounts a new one.
11
+ */
12
+ import type { CaptchaSettings } from "./schema.ts";
13
+
14
+ /** Each provider's script, rendering only when asked to. */
15
+ export const CAPTCHA_SCRIPTS = {
16
+ hcaptcha: "https://js.hcaptcha.com/1/api.js?render=explicit",
17
+ turnstile:
18
+ "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit",
19
+ } as const;
20
+
21
+ /** What Blume asks a Turnstile widget for. */
22
+ interface TurnstileRenderOptions {
23
+ appearance: "interaction-only";
24
+ callback: (token: string) => void;
25
+ "error-callback": () => void;
26
+ execution: "execute";
27
+ sitekey: string;
28
+ }
29
+
30
+ /** The slice of `window.turnstile` the check uses. */
31
+ export interface TurnstileApi {
32
+ execute: (widget: string) => void;
33
+ render: (container: HTMLElement, options: TurnstileRenderOptions) => string;
34
+ reset: (widget: string) => void;
35
+ }
36
+
37
+ /** What Blume asks an hCaptcha widget for. */
38
+ interface HcaptchaRenderOptions {
39
+ sitekey: string;
40
+ size: "invisible";
41
+ }
42
+
43
+ /** The slice of `window.hcaptcha` the check uses. */
44
+ export interface HcaptchaApi {
45
+ execute: (
46
+ widget: string,
47
+ options: { async: true }
48
+ ) => Promise<{ response: string }>;
49
+ render: (container: HTMLElement, options: HcaptchaRenderOptions) => string;
50
+ reset: (widget: string) => void;
51
+ }
52
+
53
+ /** The window, with the providers' globals once their scripts ran. */
54
+ type CaptchaWindow = Window & {
55
+ hcaptcha?: HcaptchaApi;
56
+ turnstile?: TurnstileApi;
57
+ };
58
+
59
+ /** A Turnstile token on its way. */
60
+ interface PendingToken {
61
+ reject: (error: Error) => void;
62
+ resolve: (token: string) => void;
63
+ }
64
+
65
+ /** Get a token for the next question. */
66
+ export type CaptchaTokenGetter = (settings: CaptchaSettings) => Promise<string>;
67
+
68
+ /** A token getter with its own script, container, and widget. */
69
+ export const createCaptchaClient = (): CaptchaTokenGetter => {
70
+ let loaded: Promise<void> | undefined;
71
+ let host: HTMLElement | undefined;
72
+ let widget: string | undefined;
73
+ let pending: PendingToken | undefined;
74
+
75
+ const load = (kind: CaptchaSettings["kind"]): Promise<void> => {
76
+ // oxlint-disable-next-line promise/avoid-new -- adapt the script tag's load events
77
+ loaded ??= new Promise<void>((resolve, reject) => {
78
+ const tag = document.createElement("script");
79
+ tag.async = true;
80
+ tag.src = CAPTCHA_SCRIPTS[kind];
81
+ tag.addEventListener("load", () => resolve());
82
+ tag.addEventListener("error", () => {
83
+ // Let the next question try again.
84
+ loaded = undefined;
85
+ reject(new Error(`The ${kind} script didn't load.`));
86
+ });
87
+ document.head.append(tag);
88
+ });
89
+ return loaded;
90
+ };
91
+
92
+ // The widget's container, mounted again when a swap took the last one.
93
+ const mount = (): HTMLElement => {
94
+ if (host?.isConnected) {
95
+ return host;
96
+ }
97
+ host = document.createElement("div");
98
+ host.className = "fixed bottom-4 left-1/2 z-[60] -translate-x-1/2";
99
+ host.dataset.blumeCaptcha = "";
100
+ document.body.append(host);
101
+ widget = undefined;
102
+ return host;
103
+ };
104
+
105
+ const turnstileToken = (api: TurnstileApi, siteKey: string) =>
106
+ // oxlint-disable-next-line promise/avoid-new -- adapt Turnstile's token callbacks
107
+ new Promise<string>((resolve, reject) => {
108
+ pending = { reject, resolve };
109
+ const target = mount();
110
+ if (widget === undefined) {
111
+ widget = api.render(target, {
112
+ appearance: "interaction-only",
113
+ callback: (token) => pending?.resolve(token),
114
+ "error-callback": () =>
115
+ pending?.reject(new Error("The Turnstile check failed.")),
116
+ execution: "execute",
117
+ sitekey: siteKey,
118
+ });
119
+ } else {
120
+ api.reset(widget);
121
+ }
122
+ api.execute(widget);
123
+ });
124
+
125
+ const hcaptchaToken = async (api: HcaptchaApi, siteKey: string) => {
126
+ const target = mount();
127
+ if (widget === undefined) {
128
+ widget = api.render(target, { sitekey: siteKey, size: "invisible" });
129
+ } else {
130
+ api.reset(widget);
131
+ }
132
+ const { response } = await api.execute(widget, { async: true });
133
+ return response;
134
+ };
135
+
136
+ return async (settings) => {
137
+ await load(settings.kind);
138
+ // SAFETY: CaptchaWindow only adds the providers' optional globals, each
139
+ // checked before use.
140
+ const { hcaptcha, turnstile } = window as CaptchaWindow;
141
+ if (settings.kind === "hcaptcha" && hcaptcha) {
142
+ return hcaptchaToken(hcaptcha, settings.siteKey);
143
+ }
144
+ if (settings.kind === "turnstile" && turnstile) {
145
+ return turnstileToken(turnstile, settings.siteKey);
146
+ }
147
+ throw new Error(`The ${settings.kind} script loaded without its API.`);
148
+ };
149
+ };
150
+
151
+ /** The page's token getter: one script and one widget per page load. */
152
+ export const captchaToken = createCaptchaClient();
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+
3
+ import type { AdapterDescriptor } from "../core/adapter.ts";
4
+ import { adapterDescriptorSchema } from "../core/adapter.ts";
5
+
6
+ /** Options for {@link hcaptcha}. */
7
+ export interface HcaptchaOptions {
8
+ /** The site key, from the hCaptcha dashboard. */
9
+ siteKey: string;
10
+ }
11
+
12
+ export const hcaptchaOptionsSchema = z.strictObject({
13
+ siteKey: z.string().min(1),
14
+ });
15
+
16
+ export type HcaptchaAdapter = AdapterDescriptor<"hcaptcha", HcaptchaOptions>;
17
+
18
+ export const hcaptchaAdapterSchema = adapterDescriptorSchema(
19
+ "hcaptcha",
20
+ hcaptchaOptionsSchema
21
+ );
22
+
23
+ /** Where the assistant's route checks an hCaptcha token. */
24
+ export const HCAPTCHA_VERIFY_URL = "https://api.hcaptcha.com/siteverify";
25
+
26
+ /**
27
+ * hCaptcha, as an invisible check: most readers never see a challenge. The
28
+ * site key is public and goes to the browser; the secret the route checks
29
+ * tokens with comes from `HCAPTCHA_SECRET_KEY`.
30
+ */
31
+ export const hcaptcha = (options: HcaptchaOptions): HcaptchaAdapter => ({
32
+ kind: "hcaptcha",
33
+ options,
34
+ requiredSecrets: ["HCAPTCHA_SECRET_KEY"],
35
+ runtimeDeps: [],
36
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Bot checks for the assistant (`ai.assistant.captcha` in `blume.config.ts`).
3
+ * The assistant gets a token from the check before each question and the
4
+ * generated route verifies it before calling the model:
5
+ *
6
+ * ```ts
7
+ * import { defineConfig } from "blume";
8
+ * import { turnstile } from "blume/captcha";
9
+ *
10
+ * export default defineConfig({
11
+ * ai: {
12
+ * assistant: {
13
+ * enabled: true,
14
+ * captcha: turnstile({ siteKey: "0x4AAAAAAA…" }),
15
+ * },
16
+ * },
17
+ * });
18
+ * ```
19
+ *
20
+ * Each factory returns a plain descriptor (see `core/adapter.ts`); the browser
21
+ * half is `client.ts`, the server half `verify.ts`.
22
+ */
23
+ export type { AdapterDescriptor, JsonValue } from "../core/adapter.ts";
24
+ export { hcaptcha } from "./hcaptcha.ts";
25
+ export type { HcaptchaAdapter, HcaptchaOptions } from "./hcaptcha.ts";
26
+ export type { CaptchaAdapter, CaptchaSettings } from "./schema.ts";
27
+ export { turnstile } from "./turnstile.ts";
28
+ export type { TurnstileAdapter, TurnstileOptions } from "./turnstile.ts";
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+
3
+ import { hcaptchaAdapterSchema } from "./hcaptcha.ts";
4
+ import { turnstileAdapterSchema } from "./turnstile.ts";
5
+
6
+ /**
7
+ * The configured bot check, as its factory returned it. Anything that isn't
8
+ * one fails with the adapter hint; an adapter's own option errors keep their
9
+ * messages.
10
+ */
11
+ export const captchaAdapterSchema = z.discriminatedUnion(
12
+ "kind",
13
+ [hcaptchaAdapterSchema, turnstileAdapterSchema],
14
+ {
15
+ error:
16
+ 'captcha takes one adapter — e.g. `captcha: turnstile({ siteKey })` or `captcha: hcaptcha({ siteKey })`, imported from "blume/captcha".',
17
+ }
18
+ );
19
+
20
+ export type CaptchaAdapter = z.output<typeof captchaAdapterSchema>;
21
+
22
+ /** What the browser needs to run the check: never the secret. */
23
+ export interface CaptchaSettings {
24
+ kind: CaptchaAdapter["kind"];
25
+ siteKey: string;
26
+ }
27
+
28
+ /** The public half of an adapter, for the data snapshot. */
29
+ export const captchaSettings = (adapter: CaptchaAdapter): CaptchaSettings => ({
30
+ kind: adapter.kind,
31
+ siteKey: adapter.options.siteKey,
32
+ });
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+
3
+ import type { AdapterDescriptor } from "../core/adapter.ts";
4
+ import { adapterDescriptorSchema } from "../core/adapter.ts";
5
+
6
+ /** Options for {@link turnstile}. */
7
+ export interface TurnstileOptions {
8
+ /** The widget's site key, from the Cloudflare dashboard. */
9
+ siteKey: string;
10
+ }
11
+
12
+ export const turnstileOptionsSchema = z.strictObject({
13
+ siteKey: z.string().min(1),
14
+ });
15
+
16
+ export type TurnstileAdapter = AdapterDescriptor<"turnstile", TurnstileOptions>;
17
+
18
+ export const turnstileAdapterSchema = adapterDescriptorSchema(
19
+ "turnstile",
20
+ turnstileOptionsSchema
21
+ );
22
+
23
+ /** Where the assistant's route checks a Turnstile token. */
24
+ export const TURNSTILE_VERIFY_URL =
25
+ "https://challenges.cloudflare.com/turnstile/v0/siteverify";
26
+
27
+ /**
28
+ * Cloudflare Turnstile. The site key is public and goes to the browser; the
29
+ * secret key the route checks tokens with comes from `TURNSTILE_SECRET_KEY`.
30
+ * Set the widget to Invisible or Managed in the Cloudflare dashboard: most
31
+ * readers never see a challenge.
32
+ */
33
+ export const turnstile = (options: TurnstileOptions): TurnstileAdapter => ({
34
+ kind: "turnstile",
35
+ options,
36
+ requiredSecrets: ["TURNSTILE_SECRET_KEY"],
37
+ runtimeDeps: [],
38
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The server half of the bot check: the generated assistant route verifies
3
+ * the token the browser sent with the question against the provider before
4
+ * it calls the model. A failed or unreachable check refuses the question
5
+ * (and logs why), so a server that can't reach the provider shows up as a
6
+ * refused question rather than a silently unprotected route.
7
+ */
8
+ import { clientAddressOf } from "../core/client-address.ts";
9
+ import type { ClientContext } from "../core/client-address.ts";
10
+ import { HCAPTCHA_VERIFY_URL } from "./hcaptcha.ts";
11
+ import type { CaptchaAdapter } from "./schema.ts";
12
+ import { TURNSTILE_VERIFY_URL } from "./turnstile.ts";
13
+
14
+ /** The longest token either provider issues, with room to spare. */
15
+ const MAX_TOKEN_LENGTH = 4096;
16
+
17
+ /** What the provider answers about a token. */
18
+ interface VerifyReply {
19
+ "error-codes"?: string[];
20
+ success?: boolean;
21
+ }
22
+
23
+ /** What the route hands the check from its runtime. */
24
+ export interface VerifyOptions {
25
+ /** For tests; defaults to the global `fetch`. */
26
+ fetch?: typeof fetch;
27
+ /** The provider's secret key. */
28
+ secret: string;
29
+ }
30
+
31
+ /**
32
+ * Whether the provider confirms `token` came from a person on this site. The
33
+ * route passes the request body's `captcha` when it's a string.
34
+ */
35
+ export const verifyCaptcha = async (
36
+ adapter: CaptchaAdapter,
37
+ token: string | undefined,
38
+ context: ClientContext,
39
+ options: VerifyOptions
40
+ ): Promise<boolean> => {
41
+ if (!token || token.length > MAX_TOKEN_LENGTH) {
42
+ return false;
43
+ }
44
+ const form = new URLSearchParams({ response: token, secret: options.secret });
45
+ const address = clientAddressOf(context);
46
+ if (address) {
47
+ form.set("remoteip", address);
48
+ }
49
+ if (adapter.kind === "hcaptcha") {
50
+ form.set("sitekey", adapter.options.siteKey);
51
+ }
52
+ try {
53
+ const response = await (options.fetch ?? fetch)(
54
+ adapter.kind === "hcaptcha" ? HCAPTCHA_VERIFY_URL : TURNSTILE_VERIFY_URL,
55
+ { body: form, method: "POST" }
56
+ );
57
+ // SAFETY: both providers answer JSON with a `success` flag; anything
58
+ // else reads as a failure below.
59
+ const reply = (await response.json()) as VerifyReply;
60
+ if (reply.success === true) {
61
+ return true;
62
+ }
63
+ console.warn(
64
+ `The ${adapter.kind} check refused a question:`,
65
+ reply["error-codes"] ?? response.status
66
+ );
67
+ } catch (error) {
68
+ console.error(`The ${adapter.kind} check could not be reached:`, error);
69
+ }
70
+ return false;
71
+ };
package/src/cli/args.ts CHANGED
@@ -21,3 +21,35 @@ export const parsePort = (value?: string): number | undefined => {
21
21
  }
22
22
  return port;
23
23
  };
24
+
25
+ /**
26
+ * The longest `--timeout` a timer can hold, in seconds. Node clamps a
27
+ * `setTimeout` delay above 2^31−1 ms (about 24.8 days) to 1 ms, so a larger
28
+ * value would time every agent out the moment it starts.
29
+ */
30
+ export const MAX_TIMEOUT_S = Math.floor(2_147_483_647 / 1000);
31
+
32
+ /**
33
+ * Parse a `--timeout` value in whole seconds, or return `fallback` when unset.
34
+ * A non-integer, non-positive, or too-long value exits with an error.
35
+ */
36
+ export const parseTimeoutSeconds = (
37
+ value: string | undefined,
38
+ fallback: number
39
+ ): number => {
40
+ if (value === undefined) {
41
+ return fallback;
42
+ }
43
+ const timeout = Number(value);
44
+ if (!Number.isInteger(timeout) || timeout <= 0) {
45
+ logger.error(`Invalid --timeout "${value}" (whole seconds).`);
46
+ process.exit(1);
47
+ }
48
+ if (timeout > MAX_TIMEOUT_S) {
49
+ logger.error(
50
+ `Invalid --timeout "${value}" (at most ${MAX_TIMEOUT_S} seconds, about 24 days).`
51
+ );
52
+ process.exit(1);
53
+ }
54
+ return timeout;
55
+ };
@@ -62,6 +62,11 @@ export const commandMeta = {
62
62
  description: "Preview the last production build.",
63
63
  name: "preview",
64
64
  },
65
+ skill: {
66
+ description:
67
+ "Write the docs site's agent skill with a coding agent, in place of the generated one.",
68
+ name: "skill",
69
+ },
65
70
  sync: {
66
71
  description: "Re-fetch remote content sources and regenerate the runtime.",
67
72
  name: "sync",
@@ -238,7 +238,10 @@ export const auditCommand = defineCommand({
238
238
  process.stderr.write(formatReport(result, root, { verbose: args.verbose }));
239
239
 
240
240
  if (shouldFail(result, gate)) {
241
- process.exit(1);
241
+ // Set the code and return rather than `process.exit`, which doesn't wait
242
+ // for a piped stderr: a long report was cut off after the first 64 KiB,
243
+ // in exactly the CI logs that need to show every finding.
244
+ process.exitCode = 1;
242
245
  }
243
246
  },
244
247
  });
@@ -14,6 +14,7 @@ import {
14
14
  import { BlumeError } from "../../core/diagnostics.ts";
15
15
  import { packageRoot } from "../../core/package-root.ts";
16
16
  import { scanProject } from "../../core/project-graph.ts";
17
+ import type { ResolvedConfig } from "../../core/schema.ts";
17
18
  import { serverFeatures } from "../../core/server-features.ts";
18
19
  import type { Diagnostic } from "../../core/types.ts";
19
20
  import { unregisteredSnapshotDiagnostics } from "../../core/version-cut.ts";
@@ -71,6 +72,25 @@ const supportedNodeRange = (): string => {
71
72
  }
72
73
  };
73
74
 
75
+ /** The adapters a project runs, one line per kind, for the doctor summary. */
76
+ const integrationSummary = (config: ResolvedConfig): string[] => {
77
+ const { assistant } = config.ai;
78
+ let assistantSummary = "off";
79
+ if (assistant?.enabled) {
80
+ assistantSummary = assistant.endpoint
81
+ ? "external endpoint"
82
+ : assistant.provider.kind;
83
+ }
84
+ return [
85
+ `References: ${config.reference.map((adapter) => adapter.kind).join(", ") || "none"}`,
86
+ `Analytics: ${config.analytics.map((adapter) => adapter.kind).join(", ") || "none"}`,
87
+ `Consent: ${config.consent?.kind ?? "none"}`,
88
+ `Rate limiting: ${config.rateLimit?.kind ?? "off"}`,
89
+ `Sources: ${config.content.sources.map((source) => source.kind).join(", ")}`,
90
+ `Assistant: ${assistantSummary}`,
91
+ ];
92
+ };
93
+
74
94
  export const doctorCommand = defineCommand({
75
95
  args: {
76
96
  json: {
@@ -144,23 +164,9 @@ export const doctorCommand = defineCommand({
144
164
  logger.info(`Output: ${config.deployment.options.output}`);
145
165
  logger.info(`Adapter: ${config.deployment.kind}`);
146
166
  logger.info(`Search: ${config.search.provider.kind}`);
147
- logger.info(
148
- `References: ${config.reference.map((adapter) => adapter.kind).join(", ") || "none"}`
149
- );
150
- logger.info(
151
- `Analytics: ${config.analytics.map((adapter) => adapter.kind).join(", ") || "none"}`
152
- );
153
- logger.info(
154
- `Sources: ${config.content.sources.map((source) => source.kind).join(", ")}`
155
- );
156
- const { assistant } = config.ai;
157
- let assistantSummary = "off";
158
- if (assistant?.enabled) {
159
- assistantSummary = assistant.endpoint
160
- ? "external endpoint"
161
- : assistant.provider.kind;
167
+ for (const line of integrationSummary(config)) {
168
+ logger.info(line);
162
169
  }
163
- logger.info(`Assistant: ${assistantSummary}`);
164
170
  }
165
171
  } catch (error) {
166
172
  if (error instanceof BlumeError) {
@@ -187,7 +193,9 @@ export const doctorCommand = defineCommand({
187
193
  logger.success("No problems found.");
188
194
  }
189
195
  if (hadErrors) {
190
- process.exit(1);
196
+ // Set the code and return rather than `process.exit`, which doesn't wait
197
+ // for a piped stderr: a long diagnostic list would be cut off.
198
+ process.exitCode = 1;
191
199
  }
192
200
  },
193
201
  });
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
 
3
3
  import { defineCommand } from "citty";
4
- import { join } from "pathe";
4
+ import { resolve } from "pathe";
5
5
 
6
6
  import { AGENTS, launchAgent } from "../../audit/agent.ts";
7
7
  import type { AgentKind } from "../../audit/agent.ts";
@@ -22,6 +22,7 @@ import {
22
22
  import { passFraction, runEval } from "../../eval/run.ts";
23
23
  import type { EvalResult } from "../../eval/run.ts";
24
24
  import { EvalsFileError, loadEvalsFile } from "../../eval/schema.ts";
25
+ import { parseTimeoutSeconds } from "../args.ts";
25
26
  import { commandMeta } from "../command-meta.ts";
26
27
  import { reportInternalError } from "../internal-error.ts";
27
28
  import { flushStdout, logger, reportDiagnostics } from "../log.ts";
@@ -88,16 +89,18 @@ const parseFlags = (args: EvalFlags) => {
88
89
  process.exit(1);
89
90
  }
90
91
  const threshold = args.threshold === undefined ? 1 : Number(args.threshold);
91
- if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
92
+ // A bare `--threshold` (or `--threshold $UNSET_VAR`) arrives as "", which
93
+ // `Number` reads as 0 — a gate that passes every run — so it's rejected.
94
+ if (
95
+ args.threshold?.trim() === "" ||
96
+ !Number.isFinite(threshold) ||
97
+ threshold < 0 ||
98
+ threshold > 1
99
+ ) {
92
100
  logger.error(`Invalid --threshold "${args.threshold}" (use 0..1).`);
93
101
  process.exit(1);
94
102
  }
95
- const timeoutS =
96
- args.timeout === undefined ? DEFAULT_TIMEOUT_S : Number(args.timeout);
97
- if (!Number.isInteger(timeoutS) || timeoutS <= 0) {
98
- logger.error(`Invalid --timeout "${args.timeout}" (whole seconds).`);
99
- process.exit(1);
100
- }
103
+ const timeoutS = parseTimeoutSeconds(args.timeout, DEFAULT_TIMEOUT_S);
101
104
  return { agent: args.agent, threshold, timeoutS };
102
105
  };
103
106
 
@@ -125,7 +128,7 @@ const runFixHandoff = async (
125
128
 
126
129
  /** `blume eval init`: draft a starter evals file via the interactive agent. */
127
130
  const runInit = async (agent: AgentKind, file: string): Promise<void> => {
128
- const path = join(process.cwd(), file);
131
+ const path = resolve(process.cwd(), file);
129
132
  if (existsSync(path)) {
130
133
  logger.error(
131
134
  `${file} already exists — edit it directly, or pass --file to draft elsewhere.`
@@ -193,7 +196,9 @@ export const evalCommand = defineCommand({
193
196
  // and never regenerates the runtime, so it doesn't contend with a
194
197
  // running dev server. Same reasoning as `blume audit`.
195
198
  const project = await scanProject(root, { mode: "build" });
196
- const evalsPath = join(root, args.file);
199
+ // `resolve`, not `join`: an absolute `--file` (`/ci/evals.yaml`) is
200
+ // used as given instead of being nested under the project root.
201
+ const evalsPath = resolve(root, args.file);
197
202
  const { evals, raw } = await loadEvalsFile(evalsPath);
198
203
 
199
204
  process.stderr.write(`${headerLine(evals.questions.length, agent)}\n\n`);
@@ -270,7 +275,10 @@ export const evalCommand = defineCommand({
270
275
  }
271
276
 
272
277
  if (failed) {
273
- process.exit(1);
278
+ // Set the code and return rather than `process.exit`, which doesn't wait
279
+ // for a piped stderr: a long `--verbose` report was cut off mid-write, in
280
+ // exactly the CI logs that need to show every failure.
281
+ process.exitCode = 1;
274
282
  }
275
283
  },
276
284
  });
@@ -0,0 +1,96 @@
1
+ import { defineCommand } from "citty";
2
+ import { join } from "pathe";
3
+
4
+ import {
5
+ planSkill,
6
+ WRITE_SKILL_DIR,
7
+ writeSkillPrompt,
8
+ } from "../../ai/write-skill.ts";
9
+ import { AGENTS, launchInstalledAgent } from "../../audit/agent.ts";
10
+ import type { AgentKind } from "../../audit/agent.ts";
11
+ import { BlumeError } from "../../core/diagnostics.ts";
12
+ import { packageRoot } from "../../core/package-root.ts";
13
+ import { scanProject } from "../../core/project-graph.ts";
14
+ import { getBlumeVersion } from "../../core/version.ts";
15
+ import { commandMeta } from "../command-meta.ts";
16
+ import { reportInternalError } from "../internal-error.ts";
17
+ import { logger, reportDiagnostics } from "../log.ts";
18
+
19
+ /**
20
+ * `blume skill` — write the docs site's agent skill with a coding agent. Every
21
+ * build publishes a generated skill at `/skill.md` (a map of the docs); this
22
+ * opens Claude Code or Codex on the `blume-write-skill` skill bundled in this
23
+ * package to write the richer one, grounded in the docs, under the same name
24
+ * so it takes the generated one's place. The judgment stays in the skill; the
25
+ * command is the front door, like `blume migrate`. Status lines go straight to
26
+ * stderr: consola drops info-level lines in test and CI environments.
27
+ */
28
+ export const skillCommand = defineCommand({
29
+ args: {
30
+ claude: {
31
+ description: "Write the skill with Claude Code.",
32
+ type: "boolean",
33
+ },
34
+ codex: {
35
+ description: "Write the skill with Codex.",
36
+ type: "boolean",
37
+ },
38
+ },
39
+ meta: commandMeta.skill,
40
+ async run({ args }) {
41
+ const root = process.cwd();
42
+ // SAFETY: AGENTS is a closed record keyed by AgentKind, so its keys are
43
+ // exactly the agent kinds.
44
+ const agents = (Object.keys(AGENTS) as AgentKind[]).filter(
45
+ (kind) => args[kind]
46
+ );
47
+ if (agents.length > 1) {
48
+ logger.error("Pass at most one of --codex or --claude.");
49
+ process.exit(1);
50
+ }
51
+ const [agent] = agents;
52
+
53
+ try {
54
+ // A scan, not `prepareProject`: the agent reads the content tree and
55
+ // the runtime isn't touched, so a running dev server is left alone.
56
+ const plan = planSkill(await scanProject(root, { mode: "build" }));
57
+ const skillDir = join(packageRoot(), WRITE_SKILL_DIR);
58
+ process.stderr.write(
59
+ ` ${plan.exists ? "Updating" : "Writing"} the "${plan.name}" skill at ${plan.file}.\n`
60
+ );
61
+
62
+ // Without an agent flag the command is the pointer for any other agent:
63
+ // the bundled skill's path, and the install line that puts the skill
64
+ // where agents look for it.
65
+ if (!agent) {
66
+ process.stderr.write(
67
+ ` blume skill writes it with a coding agent: rerun with --codex or --claude.\n Using another agent? Point it at ${join(skillDir, "SKILL.md")},\n or install the skill where your agent looks for skills: npx skills add haydenbleasel/blume --skill blume-write-skill\n`
68
+ );
69
+ return;
70
+ }
71
+
72
+ const cli = AGENTS[agent];
73
+ process.stderr.write(` Handing it to ${cli.name}…\n\n`);
74
+ const code = await launchInstalledAgent(
75
+ cli.bin,
76
+ writeSkillPrompt(plan, { skillDir, version: getBlumeVersion() })
77
+ );
78
+ if (code === null) {
79
+ logger.error(
80
+ `${cli.name} (\`${cli.bin}\`) was not found on PATH. Install it with \`${cli.install}\`.`
81
+ );
82
+ process.exit(1);
83
+ }
84
+ if (code !== 0) {
85
+ process.exit(code);
86
+ }
87
+ } catch (error) {
88
+ if (error instanceof BlumeError) {
89
+ reportDiagnostics([error.diagnostic], root);
90
+ process.exit(1);
91
+ }
92
+ reportInternalError(error);
93
+ process.exit(1);
94
+ }
95
+ },
96
+ });