IA
Torne sua documentação legível por máquinas com llms.txt, adicione um assistente Ask AI opcional na página e exponha um servidor MCP hospedado para agentes de programação.
O Blume tem alguns recursos de IA: documentação legível por máquinas para ferramentas externas (llms.txt, ativado por padrão), um assistente Ask AI na página e um servidor MCP hospedado para agentes de programação. O Ask AI e o MCP são opcionais, e a documentação estática continua totalmente estática até você ativar um recurso.
llms.txt
O Blume emite versões legíveis por máquinas da sua documentação que agentes de programação e assistentes de chat podem consumir. Isso vem ativado por padrão; defina llmsTxt: false para desativar:
ai: {
llmsTxt: false,
}
Enquanto estiver ativado, blume build escreve dois arquivos na raiz do seu site:
/llms.txt— um índice compacto: o título e a descrição do seu site, depois uma lista com links de todas as páginas com seus resumos, organizada em seções que espelham a sua barra lateral — pastas e grupos viram cabeçalhos, para que um agente veja a estrutura da documentação, e não um bloco único e plano./llms-full.txt— o corpus completo: o corpo Markdown inteiro de cada página, com sua URL de origem, em um único arquivo.
Páginas em rascunho são excluídas. Defina deployment.site para que os links e as URLs de origem resolvam para endereços absolutos.
llmsTxt também aceita uma forma de objeto com opções para o que os arquivos incluem. Se a sua referência de API documenta uma especificação de exemplo ou provisória, defina openapi: false para manter as páginas geradas dela fora dos dois arquivos:
ai: {
llmsTxt: {
enabled: true, // default
openapi: false, // exclude generated API reference pages
},
}
Para manter uma página individual fora dos dois arquivos, defina ai.exclude no frontmatter dela:
---
title: Internal notes
ai:
exclude: true
---
A página continua sendo renderizada, permanece na busca e mantém seu lugar no sitemap — só os arquivos llms.txt a pulam.
Para assumir o controle total de qualquer um dos arquivos, adicione seu próprio llms.txt ou llms-full.txt à sua pasta public/. Como um favicon personalizado, ele é detectado automaticamente e entra no lugar do arquivo gerado — substitua um e o Blume continua gerando o outro.
Markdown bruto
Adicione .md ou .mdx à URL de qualquer página para buscar seu código-fonte Markdown bruto — perfeito para LLMs, agentes de programação e fluxos de trabalho de “copiar como Markdown”. Está disponível para todas as páginas, em desenvolvimento e em produção, sem nenhuma configuração.
| URL | Retorna |
|---|---|
/quickstart |
A página renderizada |
/quickstart.md |
Markdown simples, com os componentes convertidos |
/quickstart.mdx |
O código-fonte MDX bruto, exatamente como foi escrito |
As rotas aninhadas funcionam do mesmo jeito (/content/syntax.md), e a página inicial é servida em /index.md.
A variante .md reduz os componentes a Markdown simples para consumidores que não conseguem interpretar JSX: <TypeTable> vira uma tabela Markdown, <Callout> uma citação rotulada, <Steps> uma lista ordenada, <Tabs> seções com rótulos em negrito e <YouTube> um link. As props são avaliadas com o frontmatter da página no escopo, então uma prop como title={frontmatter.status} resolve para o mesmo valor que a página renderizada mostra. Tudo que não puder ser convertido fielmente — um componente personalizado ou uma prop calculada a partir de um import — é deixado como está, e a marcação de componentes dentro de blocos de código cercados nunca é tocada. A mesma conversão se aplica ao llms-full.txt e à ferramenta get_page do servidor MCP, para que toda superfície voltada a agentes leia Markdown limpo. Quando quiser o código-fonte não transformado, use a variante .mdx.
Negociação de conteúdo
Os agentes não precisam conhecer a convenção .md: pedir a própria URL de uma página com um cabeçalho Accept: text/markdown serve a variante Markdown no mesmo endereço, com Vary: Accept para que os caches mantenham as duas separadas. O servidor de desenvolvimento respeita o cabeçalho de fábrica, e um build de servidor na Vercel ou na Cloudflare integra a mesma negociação ao deploy automaticamente — regras de roteamento na Vercel, um Worker gerado na Cloudflare — sem precisar de configuração. A página inicial sempre negocia, mesmo quando é uma landing page personalizada em vez de uma página de conteúdo: seu espelho Markdown recorre ao índice llms.txt, então um agente que pede Markdown à raiz do site recebe o mapa legível por máquinas do site. As respostas Markdown também carregam um cabeçalho x-markdown-tokens — uma contagem estimada de tokens (~4 caracteres por token), seguindo a convenção do Markdown for Agents da Cloudflare — em toda superfície onde o Blume controla os cabeçalhos de resposta: o servidor de desenvolvimento, as respostas renderizadas no servidor e a página inicial negociada na Vercel e na Cloudflare. Outros destinos de deploy servem páginas pré-renderizadas a partir de uma camada estática sem nenhum hook em tempo de requisição, então os agentes ali buscam a URL .md diretamente; o manifesto de legibilidade para agentes anuncia contentNegotiation só em deploys que respeitam o cabeçalho.
Serializadores de componentes personalizados
Dê aos seus próprios componentes uma forma Markdown com ai.markdownComponents — um mapa de nome JSX para serializador. Cada serializador recebe as props do componente (avaliadas estaticamente a partir dos atributos MDX, com o frontmatter da página no escopo), seus children (já reduzidos a Markdown) e os dados de frontmatter da página, e retorna a substituição — ou null para deixar o JSX como está:
import { defineConfig } from "blume";
import type { ComponentMarkdown } from "blume";
const chart: ComponentMarkdown = ({ props }) =>
``;
export default defineConfig({
ai: {
markdownComponents: {
Chart: chart,
},
},
});
Para componentes de contêiner, childComponents("Name") extrai os filhos diretos por tag — do mesmo jeito que o serializador integrado de <Steps> coleta seus itens <Step>. Uma entrada com o mesmo nome substitui um serializador integrado, então você pode reformular como <Callout> é reduzido — ou retornar null para deixá-lo de fora por completo.
Os serializadores moram no blume.config.ts, não no components.tsx: o arquivo de configuração é executado em tempo de build, enquanto o arquivo de componentes é apenas analisado estaticamente (ele pode importar arquivos .astro, que não podem rodar fora do build do site). Seus componentes em si continuam registrados no components.tsx exatamente como antes — markdownComponents só adiciona a forma Markdown deles voltada a agentes.
Copiar como Markdown
Toda página traz uma ação Copiar como Markdown — nas ações da página abaixo do sumário — que copia o Markdown bruto da página para a área de transferência. É o mesmo código-fonte servido na URL .md acima, pronto para colar em um LLM, uma issue ou nas suas anotações. Está disponível em todas as páginas, em desenvolvimento e em produção, sem nenhuma configuração.
Abrir no chat
A ação Abrir no chat abre a página atual em um assistente de IA — v0, ChatGPT, Claude, T3 Chat, Scira ou Cursor — pré-preenchida com um prompt que o aponta para o Markdown bruto da página, para que ele possa responder perguntas sobre o que você está lendo:
Leia
https://your-site/this-page.mdpara que eu possa te fazer perguntas sobre esta página.
Assim como Copiar como Markdown, não precisa de configuração. O assistente busca a página pela URL pública dela, então funciona assim que a página estiver publicada.
Para incorporar um prompt pronto para copiar direto no seu conteúdo — em vez de uma ação de página inteira — use o componente Prompt, que renderiza uma linha rotulada com um botão Copiar prompt e um link opcional para abrir no Cursor.
Ask AI
Adicione um assistente que responde às perguntas dos leitores em um painel de chat na página, sustentado por um endpoint de servidor com streaming e pelo AI SDK:
ai: {
ask: {
enabled: true,
provider: "gateway", // default
model: "openai/gpt-5.5",
},
}
Perguntas sugeridas
Preencha o estado vazio com alguns prompts iniciais. Cada um é renderizado como uma sugestão clicável — clique em uma para enviá-la — com um ícone Lucide opcional ao lado do rótulo:
ai: {
ask: {
enabled: true,
suggestions: [
{ label: "What is Blume?", icon: "rocket" },
{ label: "How do I write a docs page?", icon: "file-text" },
{ label: "How do I configure the theme?", icon: "settings" },
],
},
}
label é a pergunta que é feita; icon é opcional. Deixe suggestions sem definir (ou vazio) e o painel abre com um campo de entrada simples.
Fundamentação
O Ask AI é fundamentado na sua documentação. Para cada pergunta, ele recupera as páginas mais relevantes — usando o mesmo índice lexical do Orama que alimenta a busca na página — e as injeta no prompt de sistema do modelo, para que as respostas venham do seu conteúdo, e não do conhecimento próprio do modelo. O assistente é instruído a responder só a partir das páginas recuperadas, a dizer quando algo não está coberto e a citar as páginas de onde tirou a informação.
A página em que o leitor está é adicionada primeiro ao contexto e usada para restringir a recuperação ao idioma dessa página, para que as respostas continuem relevantes para o ponto em que ele está na documentação. A recuperação roda em tempo de requisição a partir de um snapshot embutido no build, então funciona independentemente do seu provedor de busca — mesmo quando a busca está definida como none — e não precisa de configuração.
A fundamentação fica ativa em todos os backends, exceto o Inkeep, que executa a própria recuperação sobre o conteúdo que você indexou no painel dele.
Endpoint externo
Já tem um backend de API para IA? Aponte o painel para ele e mantenha o build da documentação estático:
ai: {
ask: {
enabled: true,
endpoint: "https://api.example.com/v1/docs/ask",
},
}
O Blume envia o mesmo corpo POST que a rota integrada:
{
"messages": [{ "role": "user", "content": "How do I deploy?" }],
"page": { "path": "/deployment" }
}
Retorne uma resposta bem-sucedida cujo corpo seja um stream de texto UTF-8 simples. Se o endpoint estiver em outra origem, libere a origem da documentação com CORS: aceite OPTIONS e POST, permita o cabeçalho de requisição content-type e retorne os cabeçalhos CORS tanto no preflight quanto na resposta em streaming. Com endpoint definido, o Blume gera a interface de chat, mas nenhuma rota de servidor, snapshot de fundamentação, dependência de provedor ou aviso de segredo de provedor; o seu backend cuida de recuperação, autenticação, limitação de taxa, acesso ao modelo e citações.
Saída de servidor obrigatória
O backend integrado do Ask AI no Blume é uma rota de servidor (POST /api/ask), então ele não pode rodar em um build estático. Mude para saída de servidor e escolha um adaptador:
deployment: {
output: "server",
adapter: "vercel",
}
Um build estático com o Ask AI ativado e sem endpoint externo falha logo de cara, com uma mensagem dizendo para definir deployment.output como server. Veja Deploy para conhecer os adaptadores.
Backends
Por padrão, o Ask AI é roteado pelo Vercel AI Gateway: model é uma string provider/model, então você troca de modelo alterando essa string (openai/gpt-5.5, anthropic/claude-sonnet-4-5, e assim por diante) sem nenhum SDK de provedor para instalar. O gateway lê AI_GATEWAY_API_KEY do seu ambiente e é configurado automaticamente quando você faz o deploy na Vercel.
Defina provider para apontar o Ask AI para outro lugar. Cada backend lê sua chave de API de uma variável de ambiente, e o SDK de provedor correspondente é adicionado automaticamente ao runtime do seu projeto no build — só o que você usar:
provider |
model |
Variável de ambiente da chave de API |
|---|---|---|
gateway (padrão) |
uma string provider/model via AI Gateway |
AI_GATEWAY_API_KEY |
openrouter |
qualquer modelo do OpenRouter | OPENROUTER_API_KEY |
llmgateway |
qualquer modelo do LLMGateway | LLMGATEWAY_API_KEY |
inkeep |
um modelo QA do Inkeep | INKEEP_API_KEY |
openai-compatible |
o que quer que o seu endpoint sirva | definida com apiKeyEnv |
Por exemplo, para usar o OpenRouter:
ai: {
ask: {
enabled: true,
provider: "openrouter",
model: "anthropic/claude-sonnet-4-5",
},
}
Qualquer endpoint compatível com OpenAI funciona via openai-compatible — informe o baseUrl e a variável de ambiente que guarda a chave:
ai: {
ask: {
enabled: true,
provider: "openai-compatible",
baseUrl: "https://my-gateway.example.com/v1",
apiKeyEnv: "MY_GATEWAY_API_KEY",
model: "gpt-4o",
},
}
Defina apiKeyEnv (e, para os provedores nomeados, baseUrl) em qualquer backend para apontar para outra variável de ambiente ou proxy.
As chaves são lidas com process.env, o que cobre os adaptadores Node, Vercel e Netlify. Na Cloudflare, exponha a chave pelo binding de runtime da plataforma. Ativar o Ask AI também liga o React para a ilha na página — veja Personalização.
Limitação de taxa
O endpoint POST /api/ask é não autenticado — precisa ser, para que o assistente na página consiga chamá-lo. O Blume valida cada requisição — rejeitando corpos malformados, limitando a 1–40 mensagens e aceitando só os papéis user/assistant, para que quem chama não consiga injetar o próprio prompt de sistema e transformar a rota em um proxy genérico de LLM — para limitar quanto uma única chamada pode gastar do seu modelo, mas não consegue impedir alguém de chamar o endpoint repetidamente. Se abuso de custos for uma preocupação, coloque a rota atrás de um rate limiter — a limitação de taxa na edge do seu host (por exemplo, a da Vercel), um middleware ou os limites de gasto por chave do seu provedor de modelos.
Servidor MCP
Hospede um servidor Model Context Protocol para que agentes de programação (Claude Code, Cursor, VS Code, conectores do claude.ai) possam buscar e ler sua documentação diretamente — sem scraping:
ai: {
mcp: {
enabled: true,
route: "/mcp", // where the server is mounted
},
}
| Opção | Padrão | Descrição |
|---|---|---|
enabled |
false |
Gerar e hospedar o servidor MCP. |
route |
/mcp |
Caminho onde o endpoint Streamable-HTTP é montado. |
name |
título | Nome do servidor mostrado aos clientes (por padrão, o título). |
instructions |
— | Dica de sistema opcional passada aos agentes que se conectam. |
O servidor expõe ferramentas somente leitura — search_docs, get_page, list_pages e get_navigation — e publica documentos de descoberta em /.well-known/mcp.json e /.well-known/mcp/server-card.json. O cartão do servidor segue o esquema da extensão Server Card SEP-2127 (name em DNS reverso, endpoints de transporte remotes), com campos de compatibilidade no formato do initialize (serverInfo, capabilities, transports) para scanners construídos com base na revisão anterior da proposta. O menu Conectar ao MCP de cada página oferece instalação de copiar e usar para Claude Code, Cursor, VS Code e Codex (mostrado assim que deployment.site estiver definido).
search_docs roda seu próprio índice de texto completo, então funciona independentemente do seu provedor de busca — e mesmo quando a busca está definida como none. O servidor MCP é um recurso separado da busca na página.
Tanto search_docs quanto list_pages aceitam um filtro opcional contentTypes, que restringe os resultados a páginas dos types de frontmatter indicados — ["rfc"], ["blog", "changelog"] — para que um agente trabalhando com um site que mistura documentação com RFCs, runbooks ou políticas possa restringir a recuperação ao tipo de página de que precisa. Todo resultado informa seu tipo de conteúdo, e a saída do list_pages mostra os tipos em uso.
As duas ferramentas também aceitam um objeto filters que casa com as facetas que um site declara por tipo de conteúdo (content.types.<type>.facets) — chaves de frontmatter personalizadas cujos valores viram metadados filtráveis:
{
"query": "OpenAPI request schemas",
"contentTypes": ["rfc"],
"filters": { "domain": "architecture", "status": "enforced" }
}
Toda entrada de filters precisa casar (os resultados carregam seus valores de faceta, e o list_pages mostra os de cada página), então uma base de conhecimento pode conduzir fluxos de trabalho de agentes com divulgação progressiva — enumerar os padrões em vigor, buscar só dentro deles — sem nenhum servidor próprio.
Saída de servidor obrigatória
O servidor MCP é um endpoint ativo (/mcp), então ele não pode rodar em um build estático. Mude para saída de servidor e escolha um adaptador:
deployment: {
output: "server",
adapter: "node", // or "vercel" | "netlify" | "cloudflare"
site: "https://docs.example.com",
}
Um build estático com ai.mcp.enabled falha logo de cara, com uma mensagem dizendo para definir deployment.output como server. Veja Deploy para conhecer os adaptadores. Depois do deploy, conecte a partir do Claude Code com:
claude mcp add --transport http my-docs https://docs.example.com/mcp
Legibilidade para agentes
O Blume escreve um manifesto /agent-readability.json na raiz do seu site que indexa a superfície voltada a agentes descrita nesta página — para que um agente possa descobri-la em uma única requisição, em vez de adivinhar convenções ou raspar o HTML. Assim como o llms.txt, vem ativado por padrão:
seo: {
agentReadability: true,
}
O manifesto lista só o que você ativou — o padrão de espelho de Markdown bruto, o llms.txt e o llms-full.txt, o servidor MCP e seu documento de descoberta, o endpoint do Ask AI, o sitemap e os feeds RSS — junto com o nome do seu site, a descrição, o repositório de origem e a política de uso de content-signal. As URLs são absolutas quando deployment.site está definido e relativas à raiz caso contrário:
{
"artifacts": {
"markdown": {
"contentNegotiation": "text/markdown",
"pattern": "https://docs.example.com/{route}.md"
},
"llmsFullTxt": "https://docs.example.com/llms-full.txt",
"llmsTxt": "https://docs.example.com/llms.txt",
"mcp": {
"discovery": "https://docs.example.com/.well-known/mcp.json",
"url": "https://docs.example.com/mcp"
}
},
"description": "Docs for the Acme API.",
"generator": "blume@1.0.0",
"name": "Acme Docs",
"site": "https://docs.example.com",
"contentUsage": { "search": true, "ai-input": true, "ai-train": true },
"repository": "https://github.com/acme/docs"
}
O campo contentNegotiation aparece só quando o site em produção realmente respeita o cabeçalho Accept: text/markdown — veja negociação de conteúdo; em todos os outros deploys, o manifesto anuncia apenas o padrão de espelho .md.
Defina seo.agentReadability como false para pulá-lo, ou publique seu próprio public/agent-readability.json para assumir o controle — o Blume nunca sobrescreve um arquivo que você coloca em public/.
Cabeçalho Link de descoberta
Agentes que sondam um site não sabem que devem procurar o manifesto — por isso o Blume também o anuncia em um cabeçalho de resposta Link conforme a RFC 8288 na página inicial, usando tipos de relação registrados na IANA:
Link: </agent-readability.json>; rel="describedby"; type="application/json",
</llms.txt>; rel="describedby"; type="text/plain",
</index.md>; rel="alternate"; type="text/markdown"
Cada entrada aparece só quando o respectivo recurso está ativo. O link alternate aponta para o espelho Markdown da página inicial — o Markdown bruto da própria página quando a rota inicial é uma página de conteúdo, ou o fallback do llms.txt sintetizado quando é uma landing page. Sites que também publicam APIs ganham uma entrada rel="api-catalog" apontando para o catálogo de API gerado. O cabeçalho vai em todas as superfícies que o Blume controla: o servidor de desenvolvimento (confira com curl -I localhost:4321), os builds estáticos via arquivo _headers emitido (Netlify e Cloudflare) e os builds de servidor na Vercel via regras de roteamento do deploy. Hosts que ignoram _headers na saída estática (GitHub Pages, S3) não conseguem enviar cabeçalhos de resposta personalizados de jeito nenhum — nesses casos, os agentes ainda encontram tudo pelo llms.txt e pelo agent-readability.json na raiz do site.
Catálogo de API
Quando o site publica APIs, o Blume gera um catálogo de API conforme a RFC 9727 em /.well-known/api-catalog — um linkset que permite aos agentes enumerar suas APIs só a partir do domínio, servido com seu media type registrado application/linkset+json em todas as superfícies de build. Não há nada para configurar: o catálogo é derivado do que já está no blume.config.ts. Cada referência OpenAPI ou AsyncAPI vira uma entrada ancorada na sua rota de documentação renderizada, com service-doc apontando para essa documentação e service-desc para a especificação quando ela mora em uma URL acessível; o servidor MCP vira uma entrada com seu documento de descoberta como descrição do serviço:
{
"linkset": [
{
"anchor": "https://docs.example.com/reference",
"service-doc": [
{ "href": "https://docs.example.com/reference", "type": "text/html" }
],
"service-desc": [{ "href": "https://api.example.com/openapi.json" }]
},
{
"anchor": "https://docs.example.com/mcp",
"service-desc": [
{
"href": "https://docs.example.com/.well-known/mcp.json",
"type": "application/json"
}
],
"service-doc": [
{ "href": "https://docs.example.com/", "type": "text/html" }
]
}
]
}
Um site sem referências de API e sem servidor MCP não emite catálogo nenhum — não haveria nada nele. Como em todo lugar, um arquivo public/.well-known/api-catalog publicado por você vence o gerado.
WebMCP
O WebMCP é uma API de navegador emergente que permite a uma página registrar ferramentas diretamente em um navegador agêntico — sem precisar de uma conexão com um servidor separado. Toda página do Blume registra a superfície somente leitura da documentação no model context da página: search_docs (busca do site), get_page (o Markdown bruto de uma página) e list_pages (o índice llms.txt). O script é minúsculo, não carrega nenhuma maquinaria de busca até uma ferramenta ser de fato chamada e vira um no-op silencioso em todo navegador sem a API — o que hoje são todos, fora a prévia inicial do Chrome. Ele se registra na superfície que a spec em evolução expuser (navigator.modelContext ou document.modelContext), via provideContext ou registerTool por ferramenta.
Vem ativado por padrão; defina webmcp: false para desativar:
ai: {
webmcp: false,
}
Descoberta de skills
Se o seu projeto inclui agent skills — o próprio repositório do Blume inclui — aponte ai.skills para o diretório que as contém, e o build as publica para descoberta conforme a RFC de Agent Skills Discovery:
ai: {
skills: "./skills",
}
O caminho é resolvido em relação à raiz do seu projeto, e cada subdiretório com um SKILL.md vira uma skill publicada. Uma skill que é só um SKILL.md sozinho é copiada como está para /.well-known/agent-skills/<name>/SKILL.md (type: "skill-md"); uma skill com recursos de apoio (scripts/, references/, assets/) é empacotada em um .tar.gz determinístico (type: "archive") para que suas referências relativas resolvam depois da extração, com os bits de execução dos scripts preservados. O índice de descoberta em /.well-known/agent-skills/index.json carrega o $schema v0.2.0 e, por skill, o nome, o tipo, a descrição (do frontmatter do SKILL.md), a URL do artefato e o digest SHA-256 que os clientes usam para verificar os downloads.
Skills com name/description ausente ou inválido perante a spec são puladas com um aviso de build, em vez de publicadas quebradas, e um public/.well-known/agent-skills/index.json publicado por você assume o controle de toda a superfície.
Descoberta baseada em DNS (DNS-AID)
O DNS for AI Discovery é um rascunho emergente do IETF que permite aos agentes descobrir a superfície de IA de um site antes de fazer uma única requisição HTTP, consultando registros SVCB/HTTPS em ServiceMode em um ponto de entrada DNS bem conhecido. Os registros DNS moram na sua zona, não no build, então essa é a única superfície de descoberta que o Blume não pode publicar por você — em vez disso, adicione um registro no seu provedor de DNS:
_index._agents.docs.example.com. 3600 IN HTTPS 1 docs.example.com. alpn=h2
Use o tipo de registro HTTPS se o seu provedor oferecer (o Vercel DNS oferece; ele não suporta o tipo SVCB puro), ou, caso contrário, um registro SVCB em ServiceMode com os parâmetros alpn e port. O rascunho também recomenda assinar a zona com DNSSEC para que resolvedores validadores retornem respostas autenticadas — provedores como a Cloudflare ativam isso com um clique, enquanto alguns (incluindo o Vercel DNS) não suportam de jeito nenhum.
blume audit --url <origin> verifica isso por você: quando deployment.site está definido, a camada de rede consulta o ponto de entrada por DNS-over-HTTPS e informa o registro exato a publicar se não existir nenhum, além de dizer se as respostas são autenticadas por DNSSEC. Defina BLUME_DOH_URL para apontar a consulta para o seu próprio resolvedor se a sua rede bloquear os públicos (Google, Cloudflare).
Web Bot Auth
O Web Bot Auth funciona na direção contrária: não se trata de agentes lendo sua documentação, mas de os agentes da sua organização se identificarem quando fazem requisições em outros lugares. Seus agentes assinam as requisições com HTTP Message Signatures, e os sites que as recebem as verificam contra um diretório de chaves públicas publicado no seu domínio. Se a sua organização roda agentes e o seu site Blume mora no domínio com que eles se identificam, publique as chaves públicas deles:
ai: {
webBotAuth: {
keys: [{ kty: "OKP", crv: "Ed25519", x: "JrQLj5P_89iXES9-vFgrIy29c…" }],
},
}
O Blume passa então a servir o JWKS em /.well-known/http-message-signatures-directory com seu media type registrado em todas as superfícies de build. O diretório é público por definição, então a configuração só admite chaves públicas — um JWK que contenha material privado (d, p, q, …) falha na validação com um erro, em vez de publicar uma credencial vazada. Gere um par Ed25519 com:
node -e 'const { generateKeyPairSync } = require("node:crypto"); const { publicKey, privateKey } = generateKeyPairSync("ed25519"); console.log("public: ", JSON.stringify(publicKey.export({ format: "jwk" }))); console.log("private:", JSON.stringify(privateKey.export({ format: "jwk" })))'
O JWK público vai na configuração acima; o privado vai para onde quer que o seu agente de assinatura rode (um gerenciador de segredos, nunca o repositório). Se a sua organização não opera agentes, pule isso — um diretório vazio não anuncia nada que valha a pena verificar.
Como o blume.config.ts é executado em tempo de build, a chave não precisa estar no código — carregue-a de uma variável de ambiente em tempo de build para manter a configuração livre de blobs de chave e rotacioná-la sem commit:
const webBotAuthKey = process.env.WEB_BOT_AUTH_PUBLIC_JWK;
export default defineConfig({
ai: {
webBotAuth: {
keys: webBotAuthKey ? [JSON.parse(webBotAuthKey)] : [],
},
},
});
Ambientes sem a variável não publicam diretório nenhum, e uma chave carregada assim é validada exatamente como uma inline — incluindo a checagem de material privado. (A chave pública não é um segredo, então commitá-la inline é igualmente válido; a variável de ambiente é uma escolha ergonômica, não de segurança.)
Agent skill
Está construindo um site Blume com a ajuda de um agente de programação? Instale a agent skill do Blume para que ele saiba como o Blume funciona sem você precisar explicar:
npx skills add haydenbleasel/blume
A skill ensina ao agente o que é o Blume e como criar, escrever e configurar um site, e o aponta para a documentação completa incluída no pacote instalado (o diretório docs/ dentro de blume, onde quer que o seu gerenciador de pacotes o instale).
É uma das agent skills que o Blume traz, junto com uma skill para manter a documentação em sincronia com o seu produto a partir de uma execução agendada de um agente.