blume 2.0.3 → 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.
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +48 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-1d7ve1dm.js → chunk-0gz36akr.js} +18 -2
- package/dist/cli/{chunk-1d7ve1dm.js.map → chunk-0gz36akr.js.map} +3 -3
- package/dist/cli/{chunk-2eytanqx.js → chunk-1n228xqb.js} +2 -2
- package/dist/cli/{chunk-yfyb25rh.js → chunk-2f3ndkpw.js} +24 -8
- package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
- package/dist/cli/{chunk-r20tn01b.js → chunk-2f7n4qzt.js} +8 -2
- package/dist/cli/{chunk-r20tn01b.js.map → chunk-2f7n4qzt.js.map} +3 -3
- package/dist/cli/{chunk-35d4wj9f.js → chunk-2n4ps1b3.js} +7 -7
- package/dist/cli/{chunk-j85vccga.js → chunk-43zcf4gz.js} +97 -57
- package/dist/cli/{chunk-j85vccga.js.map → chunk-43zcf4gz.js.map} +6 -6
- package/dist/cli/{chunk-e04dxsz1.js → chunk-4ejxbzee.js} +1 -1
- package/dist/cli/{chunk-5m5nmvyq.js → chunk-4h8z9vv5.js} +233 -39
- package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
- package/dist/cli/{chunk-5f86nr5m.js → chunk-5cg2rzgf.js} +18 -16
- package/dist/cli/{chunk-5f86nr5m.js.map → chunk-5cg2rzgf.js.map} +2 -2
- package/dist/cli/{chunk-tzmab476.js → chunk-5ffywsmb.js} +4 -4
- package/dist/cli/{chunk-2z928egk.js → chunk-5wj2qwn2.js} +5 -5
- package/dist/cli/chunk-6bm0xpgd.js +18 -0
- package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
- package/dist/cli/{chunk-vg9r4eb9.js → chunk-713mngdq.js} +12 -5
- package/dist/cli/{chunk-vg9r4eb9.js.map → chunk-713mngdq.js.map} +3 -3
- package/dist/cli/{chunk-5xvm6tfj.js → chunk-9geymqns.js} +17 -15
- package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-9geymqns.js.map} +2 -2
- package/dist/cli/{chunk-q58y5e6a.js → chunk-9xr4dgee.js} +26 -17
- package/dist/cli/chunk-9xr4dgee.js.map +10 -0
- package/dist/cli/{chunk-b07cmahc.js → chunk-amrb6hgh.js} +2 -2
- package/dist/cli/chunk-cm89scdv.js +443 -0
- package/dist/cli/chunk-cm89scdv.js.map +11 -0
- package/dist/cli/{chunk-akbpwfxc.js → chunk-cmtcnh9f.js} +2 -2
- package/dist/cli/chunk-cy5bm0j0.js +104 -0
- package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
- package/dist/cli/{chunk-crgn1q09.js → chunk-e7vvk5ya.js} +4 -4
- package/dist/cli/{chunk-pehfxfta.js → chunk-g2z9x01v.js} +6 -98
- package/dist/cli/chunk-g2z9x01v.js.map +10 -0
- package/dist/cli/{chunk-tkacnehg.js → chunk-gp84za0h.js} +2 -2
- package/dist/cli/{chunk-wrr3j9w9.js → chunk-h2a48yhd.js} +186 -63
- package/dist/cli/chunk-h2a48yhd.js.map +36 -0
- package/dist/cli/{chunk-hm3vjy5s.js → chunk-jnwj5kjj.js} +14 -9
- package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-jnwj5kjj.js.map} +4 -4
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-2hsdwb9n.js → chunk-nd1821sk.js} +18 -16
- package/dist/cli/{chunk-2hsdwb9n.js.map → chunk-nd1821sk.js.map} +2 -2
- package/dist/cli/{chunk-6dsbexzp.js → chunk-ntv4qrt7.js} +17 -15
- package/dist/cli/{chunk-6dsbexzp.js.map → chunk-ntv4qrt7.js.map} +2 -2
- package/dist/cli/{chunk-r9rcc4w7.js → chunk-p804f3pe.js} +7 -7
- package/dist/cli/{chunk-ey84smr6.js → chunk-py62fewh.js} +6 -6
- package/dist/cli/{chunk-ey84smr6.js.map → chunk-py62fewh.js.map} +1 -1
- package/dist/cli/chunk-q3wxxq39.js +126 -0
- package/dist/cli/chunk-q3wxxq39.js.map +11 -0
- package/dist/cli/{chunk-9t7a85s3.js → chunk-rekqz4qg.js} +23 -13
- package/dist/cli/chunk-rekqz4qg.js.map +10 -0
- package/dist/cli/{chunk-8ktnccpt.js → chunk-ta27feev.js} +1 -1
- package/dist/cli/{chunk-pv29h0wf.js → chunk-xkz5xbtv.js} +3476 -822
- package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
- package/dist/cli/{chunk-a58773jm.js → chunk-y4ykbark.js} +2 -2
- package/dist/cli/{chunk-p3v96n38.js → chunk-y5h4r2y0.js} +4 -4
- package/dist/cli/{chunk-acanzt5p.js → chunk-yj17vs39.js} +6 -6
- package/dist/cli/{chunk-p73c0m7w.js → chunk-yxmk4pec.js} +16 -14
- package/dist/cli/{chunk-p73c0m7w.js.map → chunk-yxmk4pec.js.map} +2 -2
- package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
- package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
- package/dist/cli/{chunk-c8chx29p.js → chunk-zf748avv.js} +384 -207
- package/dist/cli/chunk-zf748avv.js.map +37 -0
- package/dist/cli/{chunk-g4hq16wv.js → chunk-zhvd6adm.js} +16 -14
- package/dist/cli/{chunk-g4hq16wv.js.map → chunk-zhvd6adm.js.map} +2 -2
- package/dist/cli/index.js +26 -20
- package/dist/cli/index.js.map +5 -5
- package/dist/types/ai/agent-surface.d.ts +8 -7
- package/dist/types/ai/ai-catalog.d.ts +7 -0
- package/dist/types/ai/ask-context.d.ts +22 -0
- package/dist/types/ai/ask.d.ts +271 -58
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +16 -0
- package/dist/types/astro/integration.d.ts +8 -0
- package/dist/types/captcha/hcaptcha.d.ts +27 -0
- package/dist/types/captcha/index.d.ts +28 -0
- package/dist/types/captcha/schema.d.ts +29 -0
- package/dist/types/captcha/turnstile.d.ts +28 -0
- package/dist/types/components/content/api-field.d.ts +59 -0
- package/dist/types/components/content/api-page.d.ts +95 -0
- package/dist/types/components/layout/nav-utils.d.ts +26 -0
- package/dist/types/components/openapi/helpers.d.ts +137 -0
- package/dist/types/components/openapi/security.d.ts +85 -0
- package/dist/types/consent/ethyca.d.ts +48 -0
- package/dist/types/consent/index.d.ts +37 -0
- package/dist/types/consent/native.d.ts +28 -0
- package/dist/types/consent/osano.d.ts +42 -0
- package/dist/types/consent/schema.d.ts +110 -0
- package/dist/types/core/config-input.d.ts +187 -20
- package/dist/types/core/data.d.ts +49 -4
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/i18n-ui.d.ts +86 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/page-modes.d.ts +29 -0
- package/dist/types/core/redirect-patterns.d.ts +144 -0
- package/dist/types/core/related.d.ts +40 -0
- package/dist/types/core/schema.d.ts +551 -56
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/types.d.ts +1 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/node-headers.d.ts +13 -2
- package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
- package/dist/types/deploy/platforms/netlify.d.ts +7 -0
- package/dist/types/deploy/platforms/vercel.d.ts +6 -0
- package/dist/types/deploy/redirects.d.ts +39 -11
- package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/features.d.ts +3 -2
- package/dist/types/narration/build.d.ts +70 -0
- package/dist/types/narration/provider.d.ts +26 -0
- package/dist/types/narration/script.d.ts +154 -0
- package/dist/types/openapi/model.d.ts +12 -6
- package/dist/types/openapi/overlay.d.ts +54 -0
- package/dist/types/openapi/parse.d.ts +13 -0
- package/dist/types/openapi/references.d.ts +14 -4
- package/dist/types/ratelimit/cloudflare.d.ts +39 -0
- package/dist/types/ratelimit/index.d.ts +27 -0
- package/dist/types/ratelimit/memory.d.ts +36 -0
- package/dist/types/ratelimit/schema.d.ts +111 -0
- package/dist/types/ratelimit/upstash.d.ts +21 -0
- package/dist/types/ratelimit/wrangler.d.ts +23 -0
- package/dist/types/reference/asyncapi.d.ts +10 -10
- package/dist/types/reference/graphql.d.ts +13 -10
- package/dist/types/reference/openapi.d.ts +33 -12
- package/dist/types/reference/options.d.ts +45 -5
- package/dist/types/reference/scalar.d.ts +13 -0
- package/dist/types/reference/schema.d.ts +60 -36
- package/dist/types/search/documents.d.ts +12 -1
- package/dist/types/search/orama-index.d.ts +4 -0
- package/dist/types/seo/metatags.d.ts +20 -0
- package/docs/02-deployment.mdx +41 -5
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/advanced/changelog.mdx +2 -2
- package/docs/advanced/skills.mdx +14 -0
- package/docs/cli/audit.mdx +21 -1
- package/docs/cli/index.mdx +1 -0
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/assistant.mdx +188 -36
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +4 -4
- package/docs/configuration/index.mdx +63 -5
- package/docs/configuration/meta.ts +3 -0
- package/docs/configuration/narration.mdx +119 -0
- package/docs/configuration/rate-limiting.mdx +69 -0
- package/docs/configuration/search.mdx +29 -1
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +106 -0
- package/docs/content/frontmatter.mdx +76 -0
- package/docs/content/i18n.mdx +38 -3
- package/docs/content/includes.mdx +16 -0
- package/docs/content/meta.mdx +1 -0
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +28 -6
- package/docs/content/sources/contentful.mdx +32 -0
- package/docs/content/sources/custom.mdx +39 -0
- package/docs/content/sources/github-releases.mdx +31 -0
- package/docs/content/sources/index.mdx +91 -0
- package/docs/content/sources/meta.ts +16 -0
- package/docs/content/sources/notion.mdx +32 -0
- package/docs/content/sources/obsidian.mdx +37 -0
- package/docs/content/sources/payload.mdx +30 -0
- package/docs/content/sources/remote-mdx.mdx +40 -0
- package/docs/content/sources/sanity.mdx +29 -0
- package/docs/content/sources/strapi.mdx +31 -0
- package/docs/content/syntax.mdx +67 -2
- package/docs/content/variables.mdx +41 -0
- package/docs/discoverability/agent-discovery.mdx +29 -2
- package/docs/discoverability/index.mdx +1 -1
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +102 -3
- package/docs/references/scalar.mdx +3 -1
- package/package.json +36 -1
- package/skills/blume/SKILL.md +3 -0
- package/skills/blume-migrate/SKILL.md +5 -5
- package/skills/blume-migrate/references/docusaurus.md +3 -3
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +2 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +6 -3
- package/src/ai/ask-context.ts +73 -20
- package/src/ai/ask-data.ts +6 -0
- package/src/ai/ask-tools.ts +142 -0
- package/src/ai/ask.ts +354 -76
- package/src/ai/component-markdown.ts +75 -0
- package/src/ai/cors.ts +2 -0
- package/src/ai/index.ts +12 -0
- package/src/ai/llms.ts +6 -2
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/openapi-components.ts +9 -3
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +35 -2
- package/src/ai/write-skill.ts +97 -0
- package/src/astro/generate.ts +132 -18
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +35 -0
- package/src/astro/module-types.ts +6 -0
- package/src/astro/templates.ts +283 -30
- package/src/astro/variables.ts +31 -0
- package/src/audit/catalog.ts +19 -0
- package/src/audit/checks/contrast.ts +183 -0
- package/src/audit/checks/i18n.ts +2 -3
- package/src/audit/checks/indexability.ts +2 -3
- package/src/audit/checks/links.ts +5 -12
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +2 -0
- package/src/audit/types.ts +2 -1
- package/src/blume-modules.d.ts +7 -0
- package/src/captcha/client.ts +152 -0
- package/src/captcha/hcaptcha.ts +36 -0
- package/src/captcha/index.ts +28 -0
- package/src/captcha/schema.ts +32 -0
- package/src/captcha/turnstile.ts +38 -0
- package/src/captcha/verify.ts +71 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/doctor.ts +22 -16
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/required-secrets.ts +71 -29
- package/src/components/BlumePage.astro +2 -0
- package/src/components/content/ApiEndpoint.astro +18 -0
- package/src/components/content/ApiField.astro +79 -0
- package/src/components/content/ApiPlayground.astro +62 -0
- package/src/components/content/ApiRail.astro +19 -0
- package/src/components/content/Color.astro +1 -0
- package/src/components/content/FileTree.astro +1 -0
- package/src/components/content/GithubInfo.astro +1 -0
- package/src/components/content/Panel.astro +1 -0
- package/src/components/content/ParamField.astro +25 -0
- package/src/components/content/RequestExample.astro +17 -0
- package/src/components/content/ResponseExample.astro +17 -0
- package/src/components/content/ResponseField.astro +32 -0
- package/src/components/content/Step.astro +1 -0
- package/src/components/content/Tile.astro +1 -0
- package/src/components/content/Tooltip.astro +1 -0
- package/src/components/content/Tree.astro +1 -0
- package/src/components/content/TypeTable.astro +4 -1
- package/src/components/content/View.astro +21 -0
- package/src/components/content/ViewSwitcher.astro +168 -0
- package/src/components/content/api-field.ts +100 -0
- package/src/components/content/api-page.ts +224 -0
- package/src/components/content/view-switcher.ts +71 -0
- package/src/components/index.ts +2 -0
- package/src/components/islands/Assistant.astro +10 -1
- package/src/components/islands/assistant.tsx +104 -14
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +110 -21
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +6 -2
- package/src/components/layout/ConsentBanner.astro +62 -0
- package/src/components/layout/ConsentHead.astro +38 -0
- package/src/components/layout/Directory.astro +94 -0
- package/src/components/layout/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +122 -80
- package/src/components/layout/LanguageSwitcher.astro +48 -15
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +33 -7
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +71 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +308 -45
- package/src/components/layout/Search.astro +31 -17
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/consent/native.ts +79 -0
- package/src/components/layout/consent/types.ts +37 -0
- package/src/components/layout/footer-links.ts +53 -0
- package/src/components/layout/footer-socials.ts +87 -0
- package/src/components/layout/locale-redirect.ts +107 -0
- package/src/components/layout/nav-utils.ts +83 -0
- package/src/components/layout/page-feedback.ts +96 -0
- package/src/components/layout/search/analytics.ts +98 -0
- package/src/components/layout/search/flexsearch.ts +17 -5
- package/src/components/layout/search/orama-cloud.ts +26 -14
- package/src/components/layout/search/types.ts +20 -0
- package/src/components/layout/search/typesense.ts +5 -1
- package/src/components/openapi/Operation.astro +161 -13
- package/src/components/openapi/RequestBody.astro +6 -4
- package/src/components/openapi/RequestPanel.astro +59 -7
- package/src/components/openapi/Responses.astro +6 -4
- package/src/components/openapi/async-snippets.ts +4 -4
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/security.ts +6 -3
- package/src/components/openapi/snippets.ts +869 -8
- package/src/components/props.ts +6 -0
- package/src/consent/client.ts +95 -0
- package/src/consent/clients.ts +19 -0
- package/src/consent/ethyca.ts +71 -0
- package/src/consent/head.ts +24 -0
- package/src/consent/index.ts +37 -0
- package/src/consent/init.ts +29 -0
- package/src/consent/native.ts +36 -0
- package/src/consent/osano.ts +59 -0
- package/src/consent/schema.ts +30 -0
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/client-address.ts +19 -0
- package/src/core/config-input.ts +194 -20
- package/src/core/data.ts +44 -4
- package/src/core/diagnostics.ts +3 -2
- package/src/core/footer.ts +26 -0
- package/src/core/graph.ts +39 -36
- package/src/core/i18n-ui.ts +82 -2
- package/src/core/includes.ts +20 -3
- package/src/core/links.ts +16 -2
- package/src/core/localizable.ts +26 -0
- package/src/core/navigation.ts +43 -12
- package/src/core/page-modes.ts +56 -0
- package/src/core/project-graph.ts +111 -5
- package/src/core/redirect-patterns.ts +491 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +318 -64
- package/src/core/sources/normalize.ts +2 -1
- package/src/core/sources/read.ts +8 -3
- package/src/core/types.ts +1 -1
- package/src/core/ui-packs/ar.ts +43 -0
- package/src/core/ui-packs/bg.ts +45 -0
- package/src/core/ui-packs/bn.ts +43 -0
- package/src/core/ui-packs/ca.ts +46 -0
- package/src/core/ui-packs/cs.ts +44 -0
- package/src/core/ui-packs/da.ts +45 -0
- package/src/core/ui-packs/de.ts +46 -0
- package/src/core/ui-packs/el.ts +45 -0
- package/src/core/ui-packs/es.ts +46 -0
- package/src/core/ui-packs/fa.ts +45 -0
- package/src/core/ui-packs/fi.ts +45 -0
- package/src/core/ui-packs/fr.ts +46 -0
- package/src/core/ui-packs/he.ts +43 -0
- package/src/core/ui-packs/hi.ts +44 -0
- package/src/core/ui-packs/hr.ts +45 -0
- package/src/core/ui-packs/hu.ts +44 -0
- package/src/core/ui-packs/id.ts +45 -0
- package/src/core/ui-packs/it.ts +45 -0
- package/src/core/ui-packs/ja.ts +45 -0
- package/src/core/ui-packs/ko.ts +44 -0
- package/src/core/ui-packs/nl.ts +46 -0
- package/src/core/ui-packs/no.ts +44 -0
- package/src/core/ui-packs/pl.ts +45 -0
- package/src/core/ui-packs/pt-br.ts +46 -0
- package/src/core/ui-packs/pt.ts +45 -0
- package/src/core/ui-packs/ro.ts +45 -0
- package/src/core/ui-packs/ru.ts +45 -0
- package/src/core/ui-packs/sk.ts +44 -0
- package/src/core/ui-packs/sr.ts +45 -0
- package/src/core/ui-packs/sv.ts +44 -0
- package/src/core/ui-packs/th.ts +43 -0
- package/src/core/ui-packs/tr.ts +45 -0
- package/src/core/ui-packs/uk.ts +45 -0
- package/src/core/ui-packs/vi.ts +45 -0
- package/src/core/ui-packs/zh-tw.ts +43 -0
- package/src/core/ui-packs/zh.ts +43 -0
- package/src/core/variables.ts +103 -0
- package/src/deploy/artifacts.ts +77 -8
- package/src/deploy/cloudflare-negotiation.ts +35 -5
- package/src/deploy/headers.ts +2 -1
- package/src/deploy/node-headers.ts +63 -9
- package/src/deploy/platforms/cloudflare.ts +32 -0
- package/src/deploy/platforms/netlify.ts +33 -1
- package/src/deploy/platforms/vercel.ts +37 -2
- package/src/deploy/redirects.ts +91 -24
- package/src/deploy/vercel-negotiation.ts +36 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/features.ts +3 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +28 -5
- package/src/markdown/math.ts +4 -4
- package/src/markdown/variables.ts +56 -0
- package/src/markdown/views.ts +82 -0
- package/src/narration/build.ts +412 -0
- package/src/narration/player.ts +249 -0
- package/src/narration/provider.ts +29 -0
- package/src/narration/script.ts +592 -0
- package/src/og/card.ts +7 -6
- package/src/openapi/model.ts +57 -24
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +55 -2
- package/src/openapi/references.ts +21 -5
- package/src/openapi/render-mdx.ts +5 -1
- package/src/openapi/scalar.ts +40 -6
- package/src/openapi/source.ts +1 -1
- package/src/ratelimit/cloudflare.ts +54 -0
- package/src/ratelimit/index.ts +30 -0
- package/src/ratelimit/memory.ts +45 -0
- package/src/ratelimit/runtime.ts +229 -0
- package/src/ratelimit/schema.ts +28 -0
- package/src/ratelimit/upstash.ts +30 -0
- package/src/ratelimit/wrangler.ts +70 -0
- package/src/reference/asyncapi.ts +2 -2
- package/src/reference/graphql.ts +5 -2
- package/src/reference/openapi.ts +21 -7
- package/src/reference/options.ts +51 -7
- package/src/reference/scalar.ts +33 -8
- package/src/registry/eject.ts +20 -4
- package/src/registry/registry.ts +7 -0
- package/src/search/documents.ts +26 -2
- package/src/search/orama-index.ts +28 -3
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/sync/algolia.ts +15 -4
- package/src/search/sync/orama-cloud.ts +3 -0
- package/src/search/sync/typesense.ts +5 -0
- package/src/seo/metatags.ts +67 -0
- package/src/theme/chrome-icons.ts +1 -0
- package/src/theme/color.ts +500 -0
- package/src/theme/entry.ts +56 -3
- package/src/theme/palette.ts +121 -45
- package/src/translate/prompts.ts +1 -0
- package/src/translate/validate.ts +8 -2
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-364znk6q.js +0 -75
- package/dist/cli/chunk-364znk6q.js.map +0 -11
- package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
- package/dist/cli/chunk-9t7a85s3.js.map +0 -10
- package/dist/cli/chunk-c8chx29p.js.map +0 -36
- package/dist/cli/chunk-pehfxfta.js.map +0 -10
- package/dist/cli/chunk-pv29h0wf.js.map +0 -190
- package/dist/cli/chunk-q58y5e6a.js.map +0 -10
- package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
- package/dist/cli/chunk-yfyb25rh.js.map +0 -14
- package/docs/content/sources.mdx +0 -334
- /package/dist/cli/{chunk-2eytanqx.js.map → chunk-1n228xqb.js.map} +0 -0
- /package/dist/cli/{chunk-35d4wj9f.js.map → chunk-2n4ps1b3.js.map} +0 -0
- /package/dist/cli/{chunk-e04dxsz1.js.map → chunk-4ejxbzee.js.map} +0 -0
- /package/dist/cli/{chunk-tzmab476.js.map → chunk-5ffywsmb.js.map} +0 -0
- /package/dist/cli/{chunk-2z928egk.js.map → chunk-5wj2qwn2.js.map} +0 -0
- /package/dist/cli/{chunk-b07cmahc.js.map → chunk-amrb6hgh.js.map} +0 -0
- /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-cmtcnh9f.js.map} +0 -0
- /package/dist/cli/{chunk-crgn1q09.js.map → chunk-e7vvk5ya.js.map} +0 -0
- /package/dist/cli/{chunk-tkacnehg.js.map → chunk-gp84za0h.js.map} +0 -0
- /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-p804f3pe.js.map} +0 -0
- /package/dist/cli/{chunk-8ktnccpt.js.map → chunk-ta27feev.js.map} +0 -0
- /package/dist/cli/{chunk-a58773jm.js.map → chunk-y4ykbark.js.map} +0 -0
- /package/dist/cli/{chunk-p3v96n38.js.map → chunk-y5h4r2y0.js.map} +0 -0
- /package/dist/cli/{chunk-acanzt5p.js.map → chunk-yj17vs39.js.map} +0 -0
package/docs/02-deployment.mdx
CHANGED
|
@@ -136,6 +136,23 @@ On Vercel and Cloudflare, a server build also turns on [`Accept: text/markdown`
|
|
|
136
136
|
Server features have their own configuration — for example, the assistant needs a model API key. See the [assistant guide](/docs/configuration/assistant) for setup.
|
|
137
137
|
:::
|
|
138
138
|
|
|
139
|
+
## Private docs
|
|
140
|
+
|
|
141
|
+
Blume has no sign-in of its own. A static build is plain files, and anyone who can reach the host can fetch anything in `dist/`. To keep a site private, turn on your host's access protection, which checks every request before a file is served:
|
|
142
|
+
|
|
143
|
+
- **Vercel**: [Deployment Protection](https://vercel.com/docs/deployment-protection) with the **All Deployments** scope, so production is covered and not only previews. Vercel Authentication lets in members of your Vercel team; Password Protection lets in anyone with a shared password, on paid plans.
|
|
144
|
+
- **Netlify**: [password protection](https://docs.netlify.com/manage/security/secure-access-to-sites/password-protection/) on all deploys, with a shared password, or with team login on Enterprise plans.
|
|
145
|
+
- **Cloudflare**: [Cloudflare Access](https://developers.cloudflare.com/workers/configuration/cloudflare-access/) on the Worker that serves the site (**Workers & Pages**, then your Worker, then **Access**), which covers every domain it answers on. Choose who can sign in by email address, email domain, or Cloudflare account membership.
|
|
146
|
+
- **GitHub Pages**: publish the site privately from an organization on GitHub Enterprise Cloud, and only people with read access to the repository can view it.
|
|
147
|
+
- **Your own server**: put authentication in front of `dist/`, such as nginx's `auth_basic` or an identity-aware proxy.
|
|
148
|
+
|
|
149
|
+
Host protection works the same for [server builds](#server-rendering). It covers the whole site: to keep some pages public, publish them as a separate site.
|
|
150
|
+
|
|
151
|
+
Everything Blume writes to `dist/` sits behind the protection, including pages, the search index, the `.md` copies, `llms.txt`, and Open Graph images. A few features reach outside it:
|
|
152
|
+
|
|
153
|
+
- A hosted [search provider](/docs/configuration/search) (Algolia, Orama Cloud, Typesense, or Mixedbread) keeps its own copy of your content on that service. The built-in Orama, FlexSearch, and Pagefind indexes are files in `dist/`, so they stay private.
|
|
154
|
+
- Services outside the protection can't read your pages. **Open in chat** hands ChatGPT or Claude a link it can't open, link previews in Slack or X can't show your Open Graph cards, and agents can't read `llms.txt` or the MCP server. Set `ai.openInChat: false` to hide the chat actions.
|
|
155
|
+
|
|
139
156
|
## Redirects
|
|
140
157
|
|
|
141
158
|
Map old URLs to new ones in `blume.config.ts`:
|
|
@@ -151,14 +168,33 @@ Two hosts need more than the file:
|
|
|
151
168
|
- **Netlify** serves a file that exists ahead of a redirect rule unless the rule is forced, and a static build has a redirect page at every `from`. The `_redirects` for `netlify()` forces its rules (`/old /new 301!`). Cloudflare rejects that flag, so the file a build for no named host writes leaves it off, and on Netlify that build answers with the redirect page instead; name the host with `netlify({ output: "static" })` to get the HTTP redirect.
|
|
152
169
|
- **Vercel** reads `vercel.json` from the project's root directory, never from the output directory, so the copy in `dist/` applies only when you deploy that folder itself with the Vercel CLI (`vercel deploy dist`). A Git-connected project never reads it: it serves the redirect pages and none of the headers from [Content types](#content-types). Copy the `redirects` and `headers` from `dist/vercel.json` into the `vercel.json` in your project's root directory, or use `vercel()` for a server build, whose routing config carries the redirects and the discovery headers.
|
|
153
170
|
|
|
154
|
-
:::note
|
|
155
|
-
`from` and `to` are exact paths — a `:param` segment or `*` wildcard (e.g. `/blog/:slug` or `/old/*`) fails config validation, since hosts disagree on patterns. If you need pattern-based rules, handle them in an infrastructure file like `vercel.json` (which supports wildcard `source` patterns) or your host's redirect config instead. A `vercel.json` you ship in `public/` is preserved as-is.
|
|
156
|
-
:::
|
|
157
|
-
|
|
158
171
|
:::note
|
|
159
172
|
Write both `from` and `to` as if mounted at root, starting with `/` (`to` can also be a full `https://` URL) — under [`base`](#subpath-deploys) and [`basePath`](#mount-the-docs-under-a-path) alike, Blume rewrites both sides for you, so a redirect lands inside the base. A `to` that names a file in `public/` (`/files/guide.pdf`) gains `base` but not `basePath`, since that's where the file is served. A base you've already written into `to` by hand is preserved rather than doubled.
|
|
160
173
|
:::
|
|
161
174
|
|
|
175
|
+
### Pattern redirects
|
|
176
|
+
|
|
177
|
+
A `from` can cover many paths at once, written the way Mintlify and `vercel.json` write them:
|
|
178
|
+
|
|
179
|
+
```ts blume.config.ts
|
|
180
|
+
redirects: [
|
|
181
|
+
{ from: "/beta/:slug*", to: "/v2/:slug*" },
|
|
182
|
+
{ from: "/blog/:slug", to: "/articles/:slug" },
|
|
183
|
+
{ from: "/articles/concepts-*", to: "/concepts" },
|
|
184
|
+
{ from: "/old/article-*", to: "/new/article-*" },
|
|
185
|
+
],
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- `:name` matches one whole segment: `/blog/:slug` covers `/blog/hello`, not `/blog/a/b`.
|
|
189
|
+
- `:name*` as the last segment matches the rest of the path, any number of segments: `/beta/:slug*` sends `/beta/guides/setup` to `/v2/guides/setup`, and `/beta` itself to `/v2`. A last segment of `*` does the same.
|
|
190
|
+
- `*` at the end of a segment matches the rest of the path from there: `/articles/concepts-*` covers `/articles/concepts-overview`.
|
|
191
|
+
|
|
192
|
+
`to` reads a capture by name, as a whole segment (`/:slug*`, `/:slug`), or a `*` for what the `*` in `from` matched (`:splat`, the way `_redirects` spells it, reads it too). A `to` that reads no capture sends every path the pattern covers to one page. An exact redirect wins over a pattern that covers the same path, and patterns are tried in the order you list them.
|
|
193
|
+
|
|
194
|
+
Each host gets the patterns in its own syntax: splats in `_redirects`, `path-to-regexp` sources in `vercel.json`, and the routing config of a `vercel()` or `netlify()` server build. The `node()` and `cloudflare()` servers match them themselves, and so does `blume dev`. A static build writes no redirect page for a pattern, since it would need one for every path the pattern covers, so a host that reads none of these files (GitHub Pages, say) applies only the exact redirects; `blume-redirects.json` lists patterns as you wrote them.
|
|
195
|
+
|
|
196
|
+
A pattern can't also match a page: hosts disagree on which of the two answers, so the build stops with `BLUME_REDIRECT_MATCHES_PAGE`, naming the page. Narrow the pattern to the paths that moved.
|
|
197
|
+
|
|
162
198
|
## Content types
|
|
163
199
|
|
|
164
200
|
Where the host reads one, a build also emits a `_headers` file that pins `charset=utf-8` onto the raw AI-ready endpoints — `/<route>.md`, `/<route>.mdx`, and the `.txt` files (`llms.txt`, `llms-full.txt`). Those responses are valid UTF-8, but many static hosts serve them as `text/markdown` / `text/plain` with **no** charset, and browsers then fall back to Windows-1252 — so non-ASCII docs (Japanese, accented Latin, …) render as mojibake when the raw URL is opened directly. HTML pages are unaffected because they carry `<meta charset>`. Netlify reads `_headers` on a static deploy and Cloudflare (Pages, and Workers static assets) on both static and server builds, so those adapters and an unnamed static host get the file; Vercel (whose headers ride the routing config) and Node (whose server ignores the file) don't. A Netlify server build writes the same rules into the `headers` of its Frameworks API config (`.netlify/v1/config.json`) instead. A static Vercel build writes the same rules into `dist/vercel.json` instead, which a Git-connected project doesn't read (see [Redirects](#redirects)). A `_headers` you ship in `public/` is left untouched.
|
|
@@ -170,7 +206,7 @@ When a feature needs a runtime secret, Blume warns at `blume dev`/`build` if it'
|
|
|
170
206
|
| Feature | Variable |
|
|
171
207
|
| --- | --- |
|
|
172
208
|
| Assistant (AI Gateway) | `AI_GATEWAY_API_KEY` (or Vercel OIDC) |
|
|
173
|
-
| Assistant (other adapters) | the adapter's default key env var (`OPENROUTER_API_KEY`, `LLMGATEWAY_API_KEY`, `INKEEP_API_KEY`), or the `apiKeyEnv` you passed it |
|
|
209
|
+
| Assistant (other adapters) | the adapter's default key env var (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`, `OPENROUTER_API_KEY`, `LLMGATEWAY_API_KEY`, `INKEEP_API_KEY`), or the `apiKeyEnv` you passed it |
|
|
174
210
|
| Mixedbread search | `MIXEDBREAD_API_KEY` |
|
|
175
211
|
| [Content sources](/docs/content/sources) | `NOTION_TOKEN` (`notion()`), `SANITY_TOKEN` (`sanity()`), `CONTENTFUL_ACCESS_TOKEN` (`contentful()`), `PAYLOAD_API_KEY` (`payload()`), `STRAPI_API_TOKEN` (`strapi()`), `GITHUB_TOKEN` (`githubReleases()`, and `mdxRemote()` reading from GitHub) |
|
|
176
212
|
|
package/docs/03-upgrading.mdx
CHANGED
|
@@ -6,7 +6,7 @@ sidebar:
|
|
|
6
6
|
order: 2.5
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Blume 2 changes configuration, not content: your Markdown and MDX pages need no edits
|
|
9
|
+
Blume 2 changes configuration, not content: your Markdown and MDX pages need no edits (see [Frontmatter](#frontmatter) for one field that now does something). Settings that used to be a named string or a keyed block — the search provider, the deployment target, content sources, API references, analytics, and the assistant's model backend — are now **adapters** you import from a `blume/*` subpath and call. Ask AI is renamed the assistant, so `ai.ask` becomes `ai.assistant`. The machine-readable settings move from `ai` to a new `agents` key, and `components.ts` overrides are checked before the build. A zero-config site, or one that sets none of these, only needs the version bump.
|
|
10
10
|
|
|
11
11
|
## Upgrade with one command
|
|
12
12
|
|
|
@@ -87,7 +87,7 @@ export default defineConfig({
|
|
|
87
87
|
|
|
88
88
|
- `netlify()`, `cloudflare()`, and `node()` work the same way. Naming a host adapter switches to server output; pass `output: "static"` to keep a static build with that host's platform files.
|
|
89
89
|
- A config that only sets `site` or `base` stays as it is: `deployment: { site, base }` is still the static form.
|
|
90
|
-
- `redirects`
|
|
90
|
+
- `redirects` read `:name` segments and a trailing `*` as patterns, matched the same way on every host (see [Pattern redirects](/docs/deployment#pattern-redirects)). Blume 1 never supported patterns and handed them to each host as written, so check any `from` or `to` that holds one.
|
|
91
91
|
- The `--adapter`, `--output`, and `--base` flags on `blume build` are gone, and passing one stops the build with an error naming the `deployment` setting that replaces it. Set the adapter in `blume.config.ts`, and name it explicitly: server output is no longer inferred from the platform's environment.
|
|
92
92
|
|
|
93
93
|
See [Deployment](/docs/deployment) for each adapter's options.
|
|
@@ -239,7 +239,7 @@ export default defineConfig({
|
|
|
239
239
|
|
|
240
240
|
</CodeGroup>
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
A 1.x `provider` name maps onto `gateway()`, `openrouter()`, `llmgateway()`, or `inkeep()`, and `openai-compatible` onto `openai({ baseUrl, name, model, apiKeyEnv })`; the [adapter list](/docs/configuration/assistant#adapters) has the rest. `model`, `apiKeyEnv`, `baseUrl`, `headers`, and `reasoning` move into the adapter; `enabled`, `instructions`, `retrieval`, `suggestions`, `cors`, and `endpoint` move to `ai.assistant` unchanged. Leaving `provider` unset still uses the AI Gateway.
|
|
243
243
|
|
|
244
244
|
The rename reaches every name that said Ask AI:
|
|
245
245
|
|
|
@@ -290,7 +290,7 @@ export default defineConfig({
|
|
|
290
290
|
|
|
291
291
|
## Frontmatter
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
Pages keep their frontmatter. One field changes what it does: `search.boost`. Blume 1 accepted it, but search never read it, so a page ranked the same without it. It now multiplies the page's search relevance (see [Ranking](/docs/configuration/search#ranking)). Check any page that sets it: a boost you added and forgot now moves the page up.
|
|
294
294
|
|
|
295
295
|
## Component overrides
|
|
296
296
|
|
package/docs/04-migrating.mdx
CHANGED
|
@@ -43,7 +43,7 @@ The agent follows the playbook's workflow from the source's config to a passing
|
|
|
43
43
|
5. Points your `package.json` scripts at `blume dev` and `blume build`, and swaps the old framework's dependencies for `blume`.
|
|
44
44
|
6. Runs `blume build` and `blume validate` until both pass.
|
|
45
45
|
|
|
46
|
-
It finishes with a summary of what it migrated, dropped, or approximated, such as footer links or
|
|
46
|
+
It finishes with a summary of what it migrated, dropped, or approximated, such as footer links or navbar buttons with no Blume equivalent, so you can decide what to do with each.
|
|
47
47
|
|
|
48
48
|
## Other agents
|
|
49
49
|
|
|
@@ -68,7 +68,7 @@ A header [tab](/docs/content/navigation#tabs) pointing at `/changelog` opens thi
|
|
|
68
68
|
|
|
69
69
|
## From GitHub Releases
|
|
70
70
|
|
|
71
|
-
Rather than authoring entries by hand, point the built-in [`githubReleases()` source](/docs/content/sources
|
|
71
|
+
Rather than authoring entries by hand, point the built-in [`githubReleases()` source](/docs/content/sources/github-releases) at a repo and every release becomes a `type: changelog` entry — the same timeline and feed, fed straight from the releases you already publish. Blume's own [changelog](/changelog) is built this way:
|
|
72
72
|
|
|
73
73
|
```ts blume.config.ts
|
|
74
74
|
import { filesystem, githubReleases } from "blume/sources";
|
|
@@ -85,7 +85,7 @@ content: {
|
|
|
85
85
|
}
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
The release name becomes the title, its tag becomes `changelog.version`, and its published date sorts the timeline. Each release page also gets a unique meta description summarized from its notes — markdown stripped, section headings and changeset commit-hash prefixes dropped, trimmed to the search-snippet length [`blume audit`](/docs/cli/audit) checks for — instead of falling back to the site description. A private repo authenticates with the `GITHUB_TOKEN` environment variable. See [
|
|
88
|
+
The release name becomes the title, its tag becomes `changelog.version`, and its published date sorts the timeline. Each release page also gets a unique meta description summarized from its notes — markdown stripped, section headings and changeset commit-hash prefixes dropped, trimmed to the search-snippet length [`blume audit`](/docs/cli/audit) checks for — instead of falling back to the site description. A private repo authenticates with the `GITHUB_TOKEN` environment variable. See [GitHub Releases](/docs/content/sources/github-releases) for every option.
|
|
89
89
|
|
|
90
90
|
## The RSS feed
|
|
91
91
|
|
package/docs/advanced/skills.mdx
CHANGED
|
@@ -34,3 +34,17 @@ Blume doesn't host the automation — wire the skill into a scheduled task in Cl
|
|
|
34
34
|
```text
|
|
35
35
|
Use the blume-update-docs skill. Review the PRs merged within the last 7 days and compare them to the docs content. Ignore work behind feature flags. If docs need updates, make them, verify the docs build, and open a blume/* PR. If not, report what you checked and do not open a PR.
|
|
36
36
|
```
|
|
37
|
+
|
|
38
|
+
## Writing your site's skill
|
|
39
|
+
|
|
40
|
+
`blume-write-skill` writes the [agent skill for your own docs](/docs/discoverability/agent-discovery#your-sites-skill): one `SKILL.md`, grounded in the docs, that teaches a coding agent to use your product — setup, core concepts, common tasks, and gotchas — with each topic linked to its page. It takes the place of the page map Blume generates at `/skill.md`. The quickest way to run it is `blume skill`, which works out where the skill goes and opens Codex or Claude Code on the copy bundled in the package:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
blume skill --claude # or --codex
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
To use it from another agent, install it:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx skills add haydenbleasel/blume --skill blume-write-skill
|
|
50
|
+
```
|
package/docs/cli/audit.mdx
CHANGED
|
@@ -69,9 +69,10 @@ This writes the complete JSON report — every affected page, not the terminal's
|
|
|
69
69
|
|
|
70
70
|
The check set is deliberately narrower than a general-purpose SEO crawler's. Much of what such a crawler reports cannot happen to a Blume site — it never emits `rel=nofollow`, and Vite's content-hashed bundles are never missing or redirecting — and reporting those as permanent zeroes would just teach you to ignore the report.
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
Limits worth stating plainly:
|
|
73
73
|
|
|
74
74
|
- **Structured data** is validated for well-formedness (valid JSON, a `@context`, a `@type` on every node). Blume does not validate against the full schema.org vocabulary or Google's rich-results rules.
|
|
75
|
+
- **Contrast** is checked for the colors your theme config sets, not element by element. The audit doesn't render pages, so a color you write by hand in MDX or `theme.css` isn't measured.
|
|
75
76
|
- **Core Web Vitals** are not checked. They need a real browser, and a flag that quietly measured nothing would be worse than not having one — so `blume audit` reports the layout-shift causes it _can_ see offline (images with no `width`/`height`, oversized assets) and leaves the rest alone for now.
|
|
76
77
|
|
|
77
78
|
Anything the audit did not run is reported as skipped rather than silently passing:
|
|
@@ -87,6 +88,7 @@ Every check `blume audit` can report, grouped by category, with its default seve
|
|
|
87
88
|
|
|
88
89
|
| Category | Key |
|
|
89
90
|
| -------------------- | ----------------- |
|
|
91
|
+
| Accessibility | `accessibility` |
|
|
90
92
|
| Content | `content` |
|
|
91
93
|
| Duplicates | `duplicates` |
|
|
92
94
|
| Indexability | `indexability` |
|
|
@@ -445,6 +447,24 @@ Fix: Point the hreflang alternate at a page that exists and is canonical.
|
|
|
445
447
|
|
|
446
448
|
Fix: Each language in an hreflang group must name exactly one page.
|
|
447
449
|
|
|
450
|
+
### Accessibility
|
|
451
|
+
|
|
452
|
+
These run on your theme config, not on each page: a theme color is one choice for the whole site. The check resolves colors the way the build does (a preset's shade for each mode, and white or dark labels on fills) and measures every text color the theme draws against the WCAG AA ratio for body text, 4.5:1, in light and dark mode.
|
|
453
|
+
|
|
454
|
+
#### Theme colors miss WCAG AA contrast [#theme-contrast-low]
|
|
455
|
+
|
|
456
|
+
`BLUME_AUDIT_THEME_CONTRAST_LOW` · warning · from the theme config
|
|
457
|
+
|
|
458
|
+
Fix: Pick a darker shade for light mode or a lighter one for dark mode, per mode with `theme.accent: { light, dark }`.
|
|
459
|
+
|
|
460
|
+
It checks accent text on the page, labels on accent and `action` fills, and body and secondary text on a custom `background`. Each finding names the pair, the mode, and its ratio.
|
|
461
|
+
|
|
462
|
+
#### Theme color can't be checked for contrast [#theme-color-unchecked]
|
|
463
|
+
|
|
464
|
+
`BLUME_AUDIT_THEME_COLOR_UNCHECKED` · info · from the theme config
|
|
465
|
+
|
|
466
|
+
Fix: Write the color as hex, `rgb()`, `hsl()`, `hwb()`, `lab()`, `lch()`, `oklab()`, `oklch()`, or a named color so its contrast can be checked.
|
|
467
|
+
|
|
448
468
|
### Assets
|
|
449
469
|
|
|
450
470
|
#### Missing alt text [#image-alt-missing]
|
package/docs/cli/index.mdx
CHANGED
|
@@ -25,6 +25,7 @@ blume <command> [options]
|
|
|
25
25
|
| [`blume eval`](/docs/cli/evals) | Test the docs: an agent answers your questions using only the documentation. |
|
|
26
26
|
| [`blume translate`](/docs/cli/translate) | Translate docs into the configured locales with a local agent CLI. |
|
|
27
27
|
| [`blume version [id]`](/docs/cli/version) | Freeze the current docs as an archived version (no id lists configured versions). |
|
|
28
|
+
| [`blume skill`](/docs/discoverability/agent-discovery#your-sites-skill) | Write your docs' agent skill with Codex or Claude Code, in place of the generated one. |
|
|
28
29
|
| [`blume migrate [source]`](/docs/migrating) | Move a Mintlify, Fumadocs, Docusaurus, Starlight, or Nextra site to Blume with Codex or Claude Code. |
|
|
29
30
|
| [`blume upgrade`](/docs/upgrading) | Move to a new major: bump `blume`, then list the config changes left or hand them to Codex or Claude Code. |
|
|
30
31
|
|
|
@@ -27,6 +27,8 @@ Analytics loads in **production builds only**. The scripts are emitted by `blume
|
|
|
27
27
|
Analytics is opt-in. With no `analytics` list (or an empty one), Blume injects nothing.
|
|
28
28
|
:::
|
|
29
29
|
|
|
30
|
+
To ask readers first, set [`consent`](/docs/configuration/consent): every adapter here then waits until the reader allows analytics.
|
|
31
|
+
|
|
30
32
|
Every identifier below — a project key, a measurement ID, a site token — is the public, browser-side one from the provider's own install snippet. Each adapter renders that snippet, so it's safe to commit to your docs repository; Blume never asks for a secret.
|
|
31
33
|
|
|
32
34
|
## Adapters
|
|
@@ -429,4 +431,4 @@ analytics: [
|
|
|
429
431
|
|
|
430
432
|
## Custom events
|
|
431
433
|
|
|
432
|
-
Blume's page feedback
|
|
434
|
+
Blume's page feedback (`feedback`, and `feedback_comment` when [written feedback](/docs/configuration#written-feedback) is on), [search](/docs/configuration/search#search-analytics) (`search`, `search_select`), and [narration](/docs/configuration/narration#analytics) player (`narration_play`, `narration_complete`) send custom events through every configured adapter that has a client API — PostHog, Mixpanel, Heap, Segment, Hightouch, Amplitude, LogRocket, Adobe, Google Analytics, Google Tag Manager (as a `{ event }` push on `window.dataLayer`), Plausible, Databuddy, Fathom, OneDollarStats (with each property value as a string), Pirsch, Clarity, Hotjar, and Vercel. Cloudflare and Clearbit have no event API. Every event also fires as a `blume:track` CustomEvent on `window` with `{ event, props }` in `detail`, so a `script()` adapter can forward it anywhere else. For deploying your built site, see [Deployment](/docs/deployment).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Assistant
|
|
3
|
-
description: An in-page assistant grounded in your docs — suggested questions, custom instructions, retrieval sizing, provider adapters
|
|
3
|
+
description: An in-page assistant grounded in your docs — suggested questions, custom instructions, retrieval sizing, provider adapters for OpenAI, Anthropic, Gemini, Grok, the Vercel AI Gateway, and any OpenAI-compatible endpoint, and the server output it needs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Add an assistant that answers reader questions in an in-page chat panel, backed by a streaming server endpoint and the [AI SDK](https://ai-sdk.dev). It's opt-in, and static docs stay fully static until you turn it on:
|
|
@@ -34,6 +34,27 @@ ai: {
|
|
|
34
34
|
|
|
35
35
|
`label` is the question that gets asked; `icon` is optional. Leave `suggestions` unset (or empty) and the panel opens to a plain input.
|
|
36
36
|
|
|
37
|
+
## Asking about code
|
|
38
|
+
|
|
39
|
+
With the assistant on, every code block gets an **Ask** button beside its copy button. It opens the assistant with the block attached, shown as a chip above the input, so the reader can ask what it does, why it fails, or how to adapt it. Sent without a question, it asks the assistant to explain the code. The code goes to the model as a fenced block after the question, up to 6,000 characters.
|
|
40
|
+
|
|
41
|
+
A custom chat UI built on `useAssistant` can do the same: listen for `blume:open-assistant` on `window`, whose `detail.code` holds the block's `language`, `source`, and `title`.
|
|
42
|
+
|
|
43
|
+
## Contact support
|
|
44
|
+
|
|
45
|
+
When the docs can't answer, hand the reader to a person. Set `support`, and once there's a conversation the panel shows a **Contact support** link under it:
|
|
46
|
+
|
|
47
|
+
```ts blume.config.ts lineNumbers
|
|
48
|
+
ai: {
|
|
49
|
+
assistant: {
|
|
50
|
+
enabled: true,
|
|
51
|
+
support: "mailto:help@example.com", // or "https://example.com/support", or "/support"
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A `mailto:` address starts an email with the conversation as its body, so the reader doesn't have to explain again (a long conversation keeps its latest turns). A URL or a page on your site gets the conversation's id as a `thread` query parameter. The assistant's `ask`, `ask_answer`, and `ask_error` [analytics events](#analytics) carry the same `thread`, so your support team can find the conversation in your analytics.
|
|
57
|
+
|
|
37
58
|
## Custom instructions
|
|
38
59
|
|
|
39
60
|
Add your own system-prompt text with `instructions` — identity, language, tone, or anything else the assistant should keep in mind:
|
|
@@ -58,6 +79,24 @@ The page the reader is currently on is added to the context first and used to sc
|
|
|
58
79
|
|
|
59
80
|
Grounding is on for every adapter except **[Inkeep](#inkeep)**, which runs its own retrieval over the content you've indexed in its dashboard.
|
|
60
81
|
|
|
82
|
+
## Searching and reading pages
|
|
83
|
+
|
|
84
|
+
The retrieved pages are a head start. The assistant can also look things up itself: it has a `search_docs` tool that searches your docs, and a `read_page` tool that reads a whole page, for when an excerpt stops short of the answer. It can search, read a page or two, search again, and then answer, all within one question. Tool calls run on your server against the same snapshot as the grounding, keep to the reader's language and docs version, and never reach the reader; they only see the answer.
|
|
85
|
+
|
|
86
|
+
The tools are on by default for every adapter whose models support tool calling. They're off for [OpenAI-compatible endpoints](#openai-compatible-endpoints), since a self-hosted model may not. Set `tools` to change either default:
|
|
87
|
+
|
|
88
|
+
```ts blume.config.ts lineNumbers
|
|
89
|
+
ai: {
|
|
90
|
+
assistant: {
|
|
91
|
+
enabled: true,
|
|
92
|
+
provider: openai({ baseUrl, model, apiKeyEnv }),
|
|
93
|
+
tools: true, // this model supports tool calling
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Each question takes at most five model steps: up to four rounds of tool calls, then the answer. With `tools: false`, the assistant answers from the retrieved pages alone, as it did before tools existed.
|
|
99
|
+
|
|
61
100
|
## Retrieval size
|
|
62
101
|
|
|
63
102
|
How much documentation a question carries is the biggest lever on how long the reader waits for the first word: the model reads every injected character before it emits a token. On a hosted frontier model that's invisible, but on a self-hosted backend it dominates. `retrieval` sizes it:
|
|
@@ -160,13 +199,13 @@ A static build with the assistant enabled and no external `endpoint` fails fast
|
|
|
160
199
|
|
|
161
200
|
```ts blume.config.ts lineNumbers
|
|
162
201
|
import { defineConfig } from "blume";
|
|
163
|
-
import {
|
|
202
|
+
import { anthropic } from "blume/ai";
|
|
164
203
|
|
|
165
204
|
export default defineConfig({
|
|
166
205
|
ai: {
|
|
167
206
|
assistant: {
|
|
168
207
|
enabled: true,
|
|
169
|
-
provider:
|
|
208
|
+
provider: anthropic({ model: "claude-sonnet-5" }),
|
|
170
209
|
},
|
|
171
210
|
},
|
|
172
211
|
});
|
|
@@ -174,16 +213,120 @@ export default defineConfig({
|
|
|
174
213
|
|
|
175
214
|
| Adapter | Answers with | API key env var | SDK to install |
|
|
176
215
|
| --- | --- | --- | --- |
|
|
216
|
+
| [`openai()`](#openai) | an [OpenAI](https://platform.openai.com) model, or any [OpenAI-compatible endpoint](#openai-compatible-endpoints) | `OPENAI_API_KEY` | `@ai-sdk/openai` (`@ai-sdk/openai-compatible` with a `baseUrl`) |
|
|
217
|
+
| [`anthropic()`](#anthropic) | a [Claude](https://platform.claude.com) model | `ANTHROPIC_API_KEY` | `@ai-sdk/anthropic` |
|
|
218
|
+
| [`gemini()`](#gemini) | a [Gemini](https://ai.google.dev) model | `GEMINI_API_KEY` | `@ai-sdk/google` |
|
|
219
|
+
| [`grok()`](#grok) | an [xAI](https://x.ai/api) Grok model | `XAI_API_KEY` | `@ai-sdk/xai` |
|
|
177
220
|
| [`gateway()`](#vercel-ai-gateway) (default) | a `provider/model` string via the Vercel AI Gateway | `AI_GATEWAY_API_KEY` | none — ships with Blume |
|
|
178
221
|
| [`openrouter()`](#openrouter) | any [OpenRouter](https://openrouter.ai) model | `OPENROUTER_API_KEY` | `@openrouter/ai-sdk-provider` |
|
|
179
222
|
| [`llmgateway()`](#llmgateway) | any [LLMGateway](https://llmgateway.io) model | `LLMGATEWAY_API_KEY` | `@ai-sdk/openai-compatible` |
|
|
180
223
|
| [`inkeep()`](#inkeep) | an [Inkeep](https://inkeep.com) QA model | `INKEEP_API_KEY` | `@ai-sdk/openai-compatible` |
|
|
181
|
-
| [`openaiCompatible()`](#openai-compatible-endpoints) | whatever your endpoint serves | the `apiKeyEnv` you name | `@ai-sdk/openai-compatible` |
|
|
182
224
|
|
|
183
|
-
The SDKs are optional peer dependencies, so add the one your adapter needs to your project (`npm install @
|
|
225
|
+
`openai()`, `anthropic()`, `gemini()`, and `grok()` call the provider's own API with your key — nothing sits in between. The SDKs are optional peer dependencies, so add the one your adapter needs to your project (`npm install @ai-sdk/anthropic`, say). If it's missing, `blume build` stops before Vite runs, naming the package and the command that installs it, and [`blume doctor`](/docs/cli/doctor) reports it too.
|
|
184
226
|
|
|
185
227
|
The descriptor an adapter returns is plain data — its kind, its options, the env vars it reads, and the SDK it needs — so the generated route (and the [ejected](/docs/configuration/customization#eject) one) inlines it as literals and imports the provider SDK by name. Nothing reads `blume.config.ts` at request time, and no secret is ever written into a route: adapters take the **name** of the env var holding the key, and the route reads the value through Astro's [`getSecret()`](https://docs.astro.build/en/guides/environment-variables/#retrieving-secrets-programmatically), so each deployment adapter supplies it its own way — environment variables on Node, Vercel, and Netlify, and the Worker's [bindings](https://docs.astro.build/en/guides/integrations-guide/cloudflare/#environment-variables-and-secrets) on Cloudflare.
|
|
186
228
|
|
|
229
|
+
### OpenAI
|
|
230
|
+
|
|
231
|
+
Any [OpenAI](https://platform.openai.com) model, called directly through OpenAI's own AI SDK provider:
|
|
232
|
+
|
|
233
|
+
```ts blume.config.ts lineNumbers
|
|
234
|
+
import { defineConfig } from "blume";
|
|
235
|
+
import { openai } from "blume/ai";
|
|
236
|
+
|
|
237
|
+
export default defineConfig({
|
|
238
|
+
ai: {
|
|
239
|
+
assistant: {
|
|
240
|
+
enabled: true,
|
|
241
|
+
provider: openai({ model: "gpt-5.5" }),
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### OpenAI-compatible endpoints
|
|
248
|
+
|
|
249
|
+
Any endpoint that speaks the OpenAI API — a self-hosted model, an internal gateway — works through `openai()` too: add its `baseUrl`, and usually `apiKeyEnv` for the env var holding its key (it defaults to `OPENAI_API_KEY`). `name` is the provider name the AI SDK reports, and the key the endpoint reads [`providerOptions`](#options-every-adapter-takes) under; it defaults to `openai-compatible`:
|
|
250
|
+
|
|
251
|
+
```ts blume.config.ts lineNumbers
|
|
252
|
+
import { defineConfig } from "blume";
|
|
253
|
+
import { openai } from "blume/ai";
|
|
254
|
+
|
|
255
|
+
export default defineConfig({
|
|
256
|
+
ai: {
|
|
257
|
+
assistant: {
|
|
258
|
+
enabled: true,
|
|
259
|
+
provider: openai({
|
|
260
|
+
baseUrl: "https://my-gateway.example.com/v1",
|
|
261
|
+
apiKeyEnv: "MY_GATEWAY_API_KEY",
|
|
262
|
+
model: "gpt-4o",
|
|
263
|
+
name: "my-gateway",
|
|
264
|
+
}),
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
With a `baseUrl`, the route calls the endpoint's Chat Completions API through `@ai-sdk/openai-compatible`, so install that package instead of `@ai-sdk/openai`: most compatible endpoints don't serve OpenAI's Responses API. The [docs tools](#searching-and-reading-pages) are off by default, since a self-hosted model may not support tool calling.
|
|
271
|
+
|
|
272
|
+
:::note
|
|
273
|
+
`openaiCompatible()` from earlier Blume versions still works: it's the same adapter as `openai()` with a `baseUrl`.
|
|
274
|
+
:::
|
|
275
|
+
|
|
276
|
+
### Anthropic
|
|
277
|
+
|
|
278
|
+
Any [Claude](https://platform.claude.com) model, called directly through Anthropic's AI SDK provider:
|
|
279
|
+
|
|
280
|
+
```ts blume.config.ts lineNumbers
|
|
281
|
+
import { defineConfig } from "blume";
|
|
282
|
+
import { anthropic } from "blume/ai";
|
|
283
|
+
|
|
284
|
+
export default defineConfig({
|
|
285
|
+
ai: {
|
|
286
|
+
assistant: {
|
|
287
|
+
enabled: true,
|
|
288
|
+
provider: anthropic({ model: "claude-sonnet-5" }),
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Gemini
|
|
295
|
+
|
|
296
|
+
Any [Gemini](https://ai.google.dev) model, called directly through Google's AI SDK provider. The key is an API key from Google AI Studio:
|
|
297
|
+
|
|
298
|
+
```ts blume.config.ts lineNumbers
|
|
299
|
+
import { defineConfig } from "blume";
|
|
300
|
+
import { gemini } from "blume/ai";
|
|
301
|
+
|
|
302
|
+
export default defineConfig({
|
|
303
|
+
ai: {
|
|
304
|
+
assistant: {
|
|
305
|
+
enabled: true,
|
|
306
|
+
provider: gemini({ model: "gemini-3.5-flash" }),
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Grok
|
|
313
|
+
|
|
314
|
+
Any [xAI](https://x.ai/api) Grok model, called directly through xAI's AI SDK provider:
|
|
315
|
+
|
|
316
|
+
```ts blume.config.ts lineNumbers
|
|
317
|
+
import { defineConfig } from "blume";
|
|
318
|
+
import { grok } from "blume/ai";
|
|
319
|
+
|
|
320
|
+
export default defineConfig({
|
|
321
|
+
ai: {
|
|
322
|
+
assistant: {
|
|
323
|
+
enabled: true,
|
|
324
|
+
provider: grok({ model: "grok-4.7" }),
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
187
330
|
### Vercel AI Gateway
|
|
188
331
|
|
|
189
332
|
The default. `model` is a `provider/model` string, so you switch models by changing it (`openai/gpt-5.5`, `anthropic/claude-sonnet-4-5`, and so on) with no provider SDK to install. The gateway reads `AI_GATEWAY_API_KEY` from your environment and is wired up automatically when you deploy on Vercel, where it can also authenticate with the deployment's OIDC token:
|
|
@@ -265,29 +408,6 @@ export default defineConfig({
|
|
|
265
408
|
|
|
266
409
|
`baseUrl` overrides the preset endpoint (`https://api.inkeep.com/v1`).
|
|
267
410
|
|
|
268
|
-
### OpenAI-compatible endpoints
|
|
269
|
-
|
|
270
|
-
Any endpoint that speaks the OpenAI API works through `openaiCompatible()` — supply its `baseUrl`, the `model` it serves, and the env var holding its key. A generic endpoint has no preset for any of these, so all three are required; `name` is the provider name the AI SDK reports and defaults to `openai-compatible`:
|
|
271
|
-
|
|
272
|
-
```ts blume.config.ts lineNumbers
|
|
273
|
-
import { defineConfig } from "blume";
|
|
274
|
-
import { openaiCompatible } from "blume/ai";
|
|
275
|
-
|
|
276
|
-
export default defineConfig({
|
|
277
|
-
ai: {
|
|
278
|
-
assistant: {
|
|
279
|
-
enabled: true,
|
|
280
|
-
provider: openaiCompatible({
|
|
281
|
-
baseUrl: "https://my-gateway.example.com/v1",
|
|
282
|
-
apiKeyEnv: "MY_GATEWAY_API_KEY",
|
|
283
|
-
model: "gpt-4o",
|
|
284
|
-
name: "my-gateway",
|
|
285
|
-
}),
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
});
|
|
289
|
-
```
|
|
290
|
-
|
|
291
411
|
### Options every adapter takes
|
|
292
412
|
|
|
293
413
|
**`apiKeyEnv`** points an adapter at a different env var than its default — `gateway({ apiKeyEnv: "DOCS_GATEWAY_KEY" })` reads that variable instead of `AI_GATEWAY_API_KEY`, and the missing-secret warning at `blume dev`/`build` checks it too. Until the key is set, the deployed route answers `503` with a message naming the variable. The route reads a request body only up to 64 KB and answers anything larger with `413`.
|
|
@@ -295,7 +415,7 @@ export default defineConfig({
|
|
|
295
415
|
**`headers`** sends static request headers with every call — a caller-identifying header for a shared backend, say, so its own observability or rate limiting can tell your docs apart from other traffic:
|
|
296
416
|
|
|
297
417
|
```ts blume.config.ts lineNumbers
|
|
298
|
-
provider:
|
|
418
|
+
provider: openai({
|
|
299
419
|
baseUrl: "https://llm.internal.example.com/v1",
|
|
300
420
|
apiKeyEnv: "INTERNAL_LLM_API_KEY",
|
|
301
421
|
model: "gpt-4o",
|
|
@@ -314,7 +434,7 @@ provider: gateway({
|
|
|
314
434
|
}),
|
|
315
435
|
```
|
|
316
436
|
|
|
317
|
-
Blume maps only the options it names (`model`, `reasoning`, `apiKeyEnv`, `headers`) and forwards `providerOptions` verbatim, so it has to be JSON — it's inlined into the route — and it has to use the key the underlying provider expects (`openai` for an OpenAI model behind the gateway, `openrouter` on OpenRouter). Enabling the assistant also turns on React for the in-page island — see [Customization](/docs/configuration/customization#interactive-islands).
|
|
437
|
+
Blume maps only the options it names (`model`, `reasoning`, `apiKeyEnv`, `headers`) and forwards `providerOptions` verbatim, so it has to be JSON — it's inlined into the route — and it has to use the key the underlying provider expects (`openai` for `openai()` or an OpenAI model behind the gateway, `anthropic` for `anthropic()`, `google` for `gemini()`, `xai` for `grok()`, `openrouter` on OpenRouter, and the adapter's `name` on an OpenAI-compatible endpoint). Enabling the assistant also turns on React for the in-page island — see [Customization](/docs/configuration/customization#interactive-islands).
|
|
318
438
|
|
|
319
439
|
## Reasoning
|
|
320
440
|
|
|
@@ -328,10 +448,13 @@ Each adapter sends the level as its backend's own reasoning control, which is wh
|
|
|
328
448
|
|
|
329
449
|
| Adapter | What the level becomes |
|
|
330
450
|
| --- | --- |
|
|
451
|
+
| `openai()` | OpenAI's `reasoning_effort`. With a `baseUrl`, it's sent as `reasoning_effort` in the request, so the endpoint has to accept that parameter. |
|
|
452
|
+
| `anthropic()` | Claude's thinking: an effort level on models with adaptive thinking, a thinking budget on older ones, and thinking off for `"none"`. |
|
|
453
|
+
| `gemini()` | Gemini's thinking level, or a thinking budget on models that take one. |
|
|
454
|
+
| `grok()` | xAI's reasoning effort, on the models that offer one. |
|
|
331
455
|
| `gateway()` | The AI SDK's [`reasoning`](https://ai-sdk.dev/docs/ai-sdk-core/reasoning) call option, which the gateway maps to the model's own setting — OpenAI's `reasoning_effort`, for example. |
|
|
332
456
|
| `openrouter()` | OpenRouter's `reasoning.effort`, set on the model. Its provider ignores the AI SDK's call option, so the level is placed where OpenRouter reads it. |
|
|
333
457
|
| `llmgateway()` | `reasoning_effort` in the request, through the AI SDK's call option. |
|
|
334
|
-
| `openaiCompatible()` | `reasoning_effort` in the request, so the endpoint has to accept that parameter. |
|
|
335
458
|
| `inkeep()` | Not available. Inkeep runs its own QA pipeline with no reasoning control, so the adapter has no `reasoning` option and setting one is a config error. |
|
|
336
459
|
|
|
337
460
|
The model has to support the level you pick: OpenAI rejects a level a model doesn't offer (`"none"` and `"xhigh"` exist only on some), so check the model's documentation before setting one. Leave it unset to keep the model's default. Like [retrieval size](#retrieval-size), it trades thoroughness for time-to-first-token, and answers stay grounded either way.
|
|
@@ -342,16 +465,45 @@ With an [analytics provider](/docs/configuration/analytics) configured, the assi
|
|
|
342
465
|
|
|
343
466
|
| Event | When | Properties |
|
|
344
467
|
| --- | --- | --- |
|
|
345
|
-
| `ask` | A question is sent | `path`, `questionChars` |
|
|
346
|
-
| `ask_answer` | The answer finishes streaming | `path`, `questionChars`, `ms`, `chars` |
|
|
347
|
-
| `ask_error` | The request fails, breaks, or comes back empty | `path`, `questionChars`, `ms`, `status` |
|
|
468
|
+
| `ask` | A question is sent | `path`, `questionChars`, `thread` |
|
|
469
|
+
| `ask_answer` | The answer finishes streaming | `path`, `questionChars`, `thread`, `ms`, `chars` |
|
|
470
|
+
| `ask_error` | The request fails, breaks, or comes back empty | `path`, `questionChars`, `thread`, `ms`, `status` |
|
|
348
471
|
|
|
349
|
-
`path` is the page the reader asked from (the served pathname, so it matches the feedback widget and your pageviews under a `base`), `questionChars` the question's length, `ms` the time from sending the question to the last chunk, and `chars` the answer's length. `status` is the HTTP status: `0` when no response arrived at all (offline, DNS, CORS), and `200` when the response was fine but its stream broke mid-answer — how a provider or credential error surfaces, since the backend has already sent its headers — or delivered nothing. Clearing the conversation mid-answer reports neither outcome.
|
|
472
|
+
`path` is the page the reader asked from (the served pathname, so it matches the feedback widget and your pageviews under a `base`), `questionChars` the question's length, `thread` the conversation's id (new when the reader clears it), `ms` the time from sending the question to the last chunk, and `chars` the answer's length. `status` is the HTTP status: `0` when no response arrived at all (offline, DNS, CORS), and `200` when the response was fine but its stream broke mid-answer — how a provider or credential error surfaces, since the backend has already sent its headers — or delivered nothing. Clearing the conversation mid-answer reports neither outcome.
|
|
350
473
|
|
|
351
474
|
The question's text never reaches a provider: it is free-form reader input (pasted keys, error logs, names) that would breach most providers' terms and per-value size limits. It travels only on the `blume:track` DOM event, as `question` in `detail.props`, so a listener you write can forward it wherever you decide it belongs. A custom chat UI built on `useAssistant` from `blume/hooks` reports the same events. With no provider configured the built-in provider calls are no-ops, but the `blume:track` event still fires, so a custom integration listening for it receives them.
|
|
352
475
|
|
|
353
476
|
## Rate limiting
|
|
354
477
|
|
|
355
|
-
The `POST /api/ask` endpoint is **unauthenticated** — it has to be, so the in-page assistant can call it. Blume validates each request — rejecting malformed bodies, capping it to 1–40 messages, and accepting only `user`/`assistant` roles so a caller can't inject their own system prompt and repurpose the route as a general LLM proxy — to bound how much a single call can spend against your model
|
|
478
|
+
The `POST /api/ask` endpoint is **unauthenticated** — it has to be, so the in-page assistant can call it. Blume validates each request — rejecting malformed bodies, capping it to 1–40 messages, and accepting only `user`/`assistant` roles so a caller can't inject their own system prompt and repurpose the route as a general LLM proxy — to bound how much a single call can spend against your model. [Rate limiting](/docs/configuration/rate-limiting), on by default, then caps how many questions one reader can ask: past it, the panel tells them to try again in a few minutes.
|
|
479
|
+
|
|
480
|
+
## Bot protection
|
|
481
|
+
|
|
482
|
+
Rate limiting counts by IP address, so a script that spreads its questions across many addresses slips past it. To stop scripts, add a bot check from `blume/captcha`: before each question, the panel gets a token from the check, and the route verifies it with the provider before the model runs. Most readers never see a challenge; the check only asks for a click when it's unsure.
|
|
483
|
+
|
|
484
|
+
```ts blume.config.ts lineNumbers
|
|
485
|
+
import { defineConfig } from "blume";
|
|
486
|
+
import { turnstile } from "blume/captcha";
|
|
487
|
+
|
|
488
|
+
export default defineConfig({
|
|
489
|
+
ai: {
|
|
490
|
+
assistant: {
|
|
491
|
+
enabled: true,
|
|
492
|
+
captcha: turnstile({ siteKey: "0x4AAAAAAA…" }),
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
| Adapter | Provider | Secret key env var |
|
|
499
|
+
| --- | --- | --- |
|
|
500
|
+
| `turnstile()` | [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/) | `TURNSTILE_SECRET_KEY` |
|
|
501
|
+
| `hcaptcha()` | [hCaptcha](https://www.hcaptcha.com), run invisibly | `HCAPTCHA_SECRET_KEY` |
|
|
502
|
+
|
|
503
|
+
The site key goes to the browser and is safe to commit; the secret key stays on the server. For Turnstile, create a widget in the Cloudflare dashboard and choose **Managed** or **Invisible** mode. Until the secret is set, the assistant answers that it isn't configured, and `blume build` warns. The provider's script loads with a reader's first question, not with the page.
|
|
504
|
+
|
|
505
|
+
A failed check shows the reader a translated "we couldn't check that you're human" notice, and the route answers `403`. Both providers publish test keys that always pass, for trying the check locally: Turnstile's `1x00000000000000000000BB` with the secret `1x0000000000000000000000000000000AA`, and hCaptcha's `10000000-ffff-ffff-ffff-000000000001` with `0x0000000000000000000000000000000000000000`.
|
|
506
|
+
|
|
507
|
+
With an [external endpoint](#external-endpoint), the panel still sends the token, as `captcha` in the request body, for your backend to verify.
|
|
356
508
|
|
|
357
509
|
The endpoint is advertised in the [agent readability manifest](/docs/discoverability/agent-discovery#agent-readability) alongside the rest of the site's machine-readable surface.
|