blume 1.6.0 → 1.6.2
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/CHANGELOG.md +21 -0
- package/dist/cli/index.js +1318 -270
- package/dist/cli/index.js.map +68 -61
- package/dist/types/core/config-input.d.ts +9 -0
- package/dist/types/core/data.d.ts +12 -1
- package/dist/types/core/i18n-ui.d.ts +4 -0
- package/dist/types/core/schema.d.ts +7 -0
- package/dist/types/core/types.d.ts +6 -0
- package/dist/types/openapi/references.d.ts +5 -0
- package/docs/07-faq.mdx +9 -9
- package/docs/advanced/api-reference.mdx +10 -1
- package/docs/advanced/custom-pages.mdx +3 -1
- package/docs/advanced/graphql.mdx +1 -1
- package/docs/configuration/ai.mdx +76 -7
- package/docs/configuration/seo.mdx +3 -3
- package/docs/configuration/theming.mdx +6 -0
- package/docs/content/components.mdx +8 -1
- package/docs/index.mdx +2 -2
- package/package.json +53 -53
- package/skills/blume/SKILL.md +2 -2
- package/src/ai/agent-readability.ts +60 -17
- package/src/ai/api/handlers.ts +273 -0
- package/src/ai/api/paths.ts +14 -0
- package/src/ai/api/problem.ts +63 -0
- package/src/ai/api/spec.ts +681 -0
- package/src/ai/api-catalog.ts +11 -1
- package/src/ai/link-headers.ts +12 -3
- package/src/ai/llms.ts +9 -2
- package/src/ai/mcp/query.ts +390 -0
- package/src/ai/mcp/server.ts +32 -352
- package/src/astro/examples.ts +29 -2
- package/src/astro/generate.ts +256 -64
- package/src/astro/index.ts +7 -0
- package/src/astro/markdown-negotiation.ts +1 -1
- package/src/astro/runtime-modules.ts +196 -0
- package/src/astro/templates.ts +398 -38
- package/src/cli/commands/build.ts +9 -1
- package/src/cli/commands/dev.ts +6 -3
- package/src/cli/host-args.ts +18 -0
- package/src/cli/index.ts +2 -1
- package/src/components/copy-feedback.ts +93 -9
- package/src/components/islands/ask-ai.tsx +4 -1
- package/src/components/islands/hooks.ts +3 -1
- package/src/components/layout/PageActions.astro +25 -14
- package/src/core/config-input.ts +9 -0
- package/src/core/data.ts +17 -2
- package/src/core/define-components.ts +2 -0
- package/src/core/i18n-ui.ts +3 -0
- package/src/core/includes.ts +2 -1
- package/src/core/manifest.ts +10 -0
- package/src/core/schema.ts +20 -5
- package/src/core/types.ts +6 -0
- package/src/core/ui-packs/ar.ts +1 -0
- package/src/core/ui-packs/bg.ts +1 -0
- package/src/core/ui-packs/bn.ts +1 -0
- package/src/core/ui-packs/ca.ts +1 -0
- package/src/core/ui-packs/cs.ts +1 -0
- package/src/core/ui-packs/da.ts +1 -0
- package/src/core/ui-packs/de.ts +1 -0
- package/src/core/ui-packs/el.ts +1 -0
- package/src/core/ui-packs/es.ts +1 -0
- package/src/core/ui-packs/fa.ts +1 -0
- package/src/core/ui-packs/fi.ts +1 -0
- package/src/core/ui-packs/fr.ts +1 -0
- package/src/core/ui-packs/he.ts +1 -0
- package/src/core/ui-packs/hi.ts +1 -0
- package/src/core/ui-packs/hr.ts +1 -0
- package/src/core/ui-packs/hu.ts +1 -0
- package/src/core/ui-packs/id.ts +1 -0
- package/src/core/ui-packs/it.ts +1 -0
- package/src/core/ui-packs/ja.ts +1 -0
- package/src/core/ui-packs/ko.ts +1 -0
- package/src/core/ui-packs/nl.ts +1 -0
- package/src/core/ui-packs/no.ts +1 -0
- package/src/core/ui-packs/pl.ts +1 -0
- package/src/core/ui-packs/pt-br.ts +1 -0
- package/src/core/ui-packs/pt.ts +1 -0
- package/src/core/ui-packs/ro.ts +1 -0
- package/src/core/ui-packs/ru.ts +1 -0
- package/src/core/ui-packs/sk.ts +1 -0
- package/src/core/ui-packs/sr.ts +1 -0
- package/src/core/ui-packs/sv.ts +1 -0
- package/src/core/ui-packs/th.ts +1 -0
- package/src/core/ui-packs/tr.ts +1 -0
- package/src/core/ui-packs/uk.ts +1 -0
- package/src/core/ui-packs/vi.ts +1 -0
- package/src/core/ui-packs/zh-tw.ts +1 -0
- package/src/core/ui-packs/zh.ts +1 -0
- package/src/core/version-cut.ts +5 -3
- package/src/deploy/vercel-negotiation.ts +97 -6
- package/src/og/card.ts +1 -1
- package/src/openapi/references.ts +8 -0
- package/src/openapi/render-mdx.ts +18 -4
- package/src/openapi/scalar.ts +0 -4
- package/src/registry/eject.ts +36 -17
- package/src/theme/entry.ts +2 -2
- package/src/theme/sources.ts +49 -0
|
@@ -690,6 +690,15 @@ export interface McpConfig {
|
|
|
690
690
|
* hosted MCP server.
|
|
691
691
|
*/
|
|
692
692
|
export interface AiConfig {
|
|
693
|
+
/**
|
|
694
|
+
* The JSON docs API — the REST twin of the MCP tools. Serves the page index
|
|
695
|
+
* (`/api/docs/pages.json`), each page as JSON (`/api/docs/pages/{route}.json`),
|
|
696
|
+
* and the navigation tree (`/api/docs/navigation.json`) as prerendered
|
|
697
|
+
* files, plus full-text search (`/api/docs/search?q=`) on server output,
|
|
698
|
+
* all described by an OpenAPI document at `/openapi.json`. Errors are RFC
|
|
699
|
+
* 9457 problem details. Defaults to `true`; set `false` to publish none of it.
|
|
700
|
+
*/
|
|
701
|
+
api?: boolean;
|
|
693
702
|
/** The Ask AI chat assistant. */
|
|
694
703
|
ask?: AskConfig;
|
|
695
704
|
/**
|
|
@@ -82,6 +82,11 @@ export interface BlumeRoute {
|
|
|
82
82
|
alternates: RouteAlternate[];
|
|
83
83
|
/** Astro collection the entry renders through (`"docs"` | `"staged"`). */
|
|
84
84
|
collection: string;
|
|
85
|
+
/**
|
|
86
|
+
* Meta description as the page head renders it (`seo.description` over the
|
|
87
|
+
* front matter `description`), or `null` when the page declares neither.
|
|
88
|
+
*/
|
|
89
|
+
description: string | null;
|
|
85
90
|
draft: boolean;
|
|
86
91
|
/** "Edit this page" URL, or `null` when no repo/source provides one. */
|
|
87
92
|
editUrl: string | null;
|
|
@@ -138,6 +143,8 @@ export interface BlumeDataConfig {
|
|
|
138
143
|
*/
|
|
139
144
|
discovery: {
|
|
140
145
|
agentReadability: boolean;
|
|
146
|
+
/** Whether the JSON docs API and its `/openapi.json` are published. */
|
|
147
|
+
api: boolean;
|
|
141
148
|
llmsTxt: boolean;
|
|
142
149
|
sitemap: boolean;
|
|
143
150
|
};
|
|
@@ -176,7 +183,11 @@ export interface BlumeDataConfig {
|
|
|
176
183
|
* kept out of this snapshot, which pages serialize into HTML.
|
|
177
184
|
*/
|
|
178
185
|
og: {
|
|
179
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* Site-wide card subtitle: `seo.og.description` (`false` omits it) over
|
|
188
|
+
* the site description. A page with its own description shows that
|
|
189
|
+
* instead; this is the fallback for pages without one.
|
|
190
|
+
*/
|
|
180
191
|
description?: string;
|
|
181
192
|
enabled: boolean;
|
|
182
193
|
/** Inlined SVG brand mark; `false` renders the card without any mark. */
|
|
@@ -17,6 +17,7 @@ declare const uiStringsObject: z.ZodObject<{
|
|
|
17
17
|
copyClaudeCode: z.ZodDefault<z.ZodString>;
|
|
18
18
|
copyCode: z.ZodDefault<z.ZodString>;
|
|
19
19
|
copyCodex: z.ZodDefault<z.ZodString>;
|
|
20
|
+
copyFailed: z.ZodDefault<z.ZodString>;
|
|
20
21
|
copyMarkdown: z.ZodDefault<z.ZodString>;
|
|
21
22
|
copyServerUrl: z.ZodDefault<z.ZodString>;
|
|
22
23
|
edit: z.ZodDefault<z.ZodString>;
|
|
@@ -78,6 +79,7 @@ declare const uiStringsObject: z.ZodObject<{
|
|
|
78
79
|
toggleTheme: z.ZodDefault<z.ZodString>;
|
|
79
80
|
}, z.core.$strip>>;
|
|
80
81
|
notFound: z.ZodPrefault<z.ZodObject<{
|
|
82
|
+
api: z.ZodDefault<z.ZodString>;
|
|
81
83
|
description: z.ZodDefault<z.ZodString>;
|
|
82
84
|
home: z.ZodDefault<z.ZodString>;
|
|
83
85
|
llms: z.ZodDefault<z.ZodString>;
|
|
@@ -129,6 +131,7 @@ export declare const uiStringsSchema: z.ZodPrefault<z.ZodObject<{
|
|
|
129
131
|
copyClaudeCode: z.ZodDefault<z.ZodString>;
|
|
130
132
|
copyCode: z.ZodDefault<z.ZodString>;
|
|
131
133
|
copyCodex: z.ZodDefault<z.ZodString>;
|
|
134
|
+
copyFailed: z.ZodDefault<z.ZodString>;
|
|
132
135
|
copyMarkdown: z.ZodDefault<z.ZodString>;
|
|
133
136
|
copyServerUrl: z.ZodDefault<z.ZodString>;
|
|
134
137
|
edit: z.ZodDefault<z.ZodString>;
|
|
@@ -190,6 +193,7 @@ export declare const uiStringsSchema: z.ZodPrefault<z.ZodObject<{
|
|
|
190
193
|
toggleTheme: z.ZodDefault<z.ZodString>;
|
|
191
194
|
}, z.core.$strip>>;
|
|
192
195
|
notFound: z.ZodPrefault<z.ZodObject<{
|
|
196
|
+
api: z.ZodDefault<z.ZodString>;
|
|
193
197
|
description: z.ZodDefault<z.ZodString>;
|
|
194
198
|
home: z.ZodDefault<z.ZodString>;
|
|
195
199
|
llms: z.ZodDefault<z.ZodString>;
|
|
@@ -260,6 +260,7 @@ export declare const searchProviders: readonly ["orama", "pagefind", "flexsearch
|
|
|
260
260
|
/** Ask AI backends. `gateway` (default) routes through the Vercel AI Gateway. */
|
|
261
261
|
export declare const askAiProviders: readonly ["gateway", "openrouter", "llmgateway", "inkeep", "openai-compatible"];
|
|
262
262
|
declare const aiConfigSchema: z.ZodObject<{
|
|
263
|
+
api: z.ZodDefault<z.ZodBoolean>;
|
|
263
264
|
ask: z.ZodOptional<z.ZodObject<{
|
|
264
265
|
apiKeyEnv: z.ZodOptional<z.ZodString>;
|
|
265
266
|
baseUrl: z.ZodOptional<z.ZodURL>;
|
|
@@ -477,6 +478,7 @@ declare const openapiSourceSchema: z.ZodObject<{
|
|
|
477
478
|
label: z.ZodOptional<z.ZodString>;
|
|
478
479
|
noindex: z.ZodDefault<z.ZodBoolean>;
|
|
479
480
|
route: z.ZodOptional<z.ZodString>;
|
|
481
|
+
seoDescriptionSuffix: z.ZodDefault<z.ZodBoolean>;
|
|
480
482
|
spec: z.ZodString;
|
|
481
483
|
}, z.core.$strict>;
|
|
482
484
|
export type OpenApiSource = z.input<typeof openapiSourceSchema>;
|
|
@@ -492,12 +494,14 @@ declare const graphqlSourceSchema: z.ZodObject<{
|
|
|
492
494
|
label: z.ZodOptional<z.ZodString>;
|
|
493
495
|
noindex: z.ZodDefault<z.ZodBoolean>;
|
|
494
496
|
route: z.ZodOptional<z.ZodString>;
|
|
497
|
+
seoDescriptionSuffix: z.ZodDefault<z.ZodBoolean>;
|
|
495
498
|
spec: z.ZodString;
|
|
496
499
|
endpoint: z.ZodOptional<z.ZodString>;
|
|
497
500
|
}, z.core.$strict>;
|
|
498
501
|
export type GraphqlSource = z.input<typeof graphqlSourceSchema>;
|
|
499
502
|
export declare const blumeConfigSchema: z.ZodObject<{
|
|
500
503
|
ai: z.ZodPrefault<z.ZodObject<{
|
|
504
|
+
api: z.ZodDefault<z.ZodBoolean>;
|
|
501
505
|
ask: z.ZodOptional<z.ZodObject<{
|
|
502
506
|
apiKeyEnv: z.ZodOptional<z.ZodString>;
|
|
503
507
|
baseUrl: z.ZodOptional<z.ZodURL>;
|
|
@@ -598,6 +602,7 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
598
602
|
label: z.ZodOptional<z.ZodString>;
|
|
599
603
|
noindex: z.ZodDefault<z.ZodBoolean>;
|
|
600
604
|
route: z.ZodOptional<z.ZodString>;
|
|
605
|
+
seoDescriptionSuffix: z.ZodDefault<z.ZodBoolean>;
|
|
601
606
|
spec: z.ZodString;
|
|
602
607
|
}, z.core.$strict>>>;
|
|
603
608
|
spec: z.ZodOptional<z.ZodString>;
|
|
@@ -795,6 +800,7 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
795
800
|
label: z.ZodOptional<z.ZodString>;
|
|
796
801
|
noindex: z.ZodDefault<z.ZodBoolean>;
|
|
797
802
|
route: z.ZodOptional<z.ZodString>;
|
|
803
|
+
seoDescriptionSuffix: z.ZodDefault<z.ZodBoolean>;
|
|
798
804
|
spec: z.ZodString;
|
|
799
805
|
endpoint: z.ZodOptional<z.ZodString>;
|
|
800
806
|
}, z.core.$strict>>>;
|
|
@@ -942,6 +948,7 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
942
948
|
label: z.ZodOptional<z.ZodString>;
|
|
943
949
|
noindex: z.ZodDefault<z.ZodBoolean>;
|
|
944
950
|
route: z.ZodOptional<z.ZodString>;
|
|
951
|
+
seoDescriptionSuffix: z.ZodDefault<z.ZodBoolean>;
|
|
945
952
|
spec: z.ZodString;
|
|
946
953
|
}, z.core.$strict>>>;
|
|
947
954
|
spec: z.ZodOptional<z.ZodString>;
|
|
@@ -347,6 +347,12 @@ export interface RouteManifestEntry {
|
|
|
347
347
|
/** Adapter-supplied "edit this page" URL (non-filesystem sources). */
|
|
348
348
|
editUrl?: string;
|
|
349
349
|
title: string;
|
|
350
|
+
/**
|
|
351
|
+
* The page's meta description as the head renders it: `seo.description`
|
|
352
|
+
* over the front matter `description`. Feeds the generated OG card's
|
|
353
|
+
* subtitle, so a shared link's image and its `og:description` agree.
|
|
354
|
+
*/
|
|
355
|
+
description?: string;
|
|
350
356
|
contentType: string;
|
|
351
357
|
hidden: boolean;
|
|
352
358
|
draft: boolean;
|
|
@@ -47,6 +47,11 @@ export interface ReferenceSource {
|
|
|
47
47
|
includeInSearch: boolean;
|
|
48
48
|
/** Whether generated pages emit noindex metadata and stay out of the sitemap. */
|
|
49
49
|
noindex: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Whether operation meta descriptions end with the generated English
|
|
52
|
+
* "Reference for …" sentence, or carry the spec's own prose alone.
|
|
53
|
+
*/
|
|
54
|
+
seoDescriptionSuffix: boolean;
|
|
50
55
|
/** Local path or `http(s)` URL, verbatim from config. */
|
|
51
56
|
spec: string;
|
|
52
57
|
/**
|
package/docs/07-faq.mdx
CHANGED
|
@@ -87,14 +87,14 @@ We reported it upstream in [oxc-project/oxc#24096](https://github.com/oxc-projec
|
|
|
87
87
|
|
|
88
88
|
Patch oxfmt so it preserves the line break that sits directly against a `:::` fence. Blume ships the same fix in its own repo, and you can apply it in any project.
|
|
89
89
|
|
|
90
|
-
1. Save the patch as `patches/oxfmt@0.
|
|
91
|
-
|
|
92
|
-
```diff patches/oxfmt@0.
|
|
93
|
-
diff --git a/dist/markdown-
|
|
94
|
-
index
|
|
95
|
-
--- a/dist/markdown-
|
|
96
|
-
+++ b/dist/markdown-
|
|
97
|
-
@@ -
|
|
90
|
+
1. Save the patch as `patches/oxfmt@0.66.0.patch`:
|
|
91
|
+
|
|
92
|
+
```diff patches/oxfmt@0.66.0.patch
|
|
93
|
+
diff --git a/dist/markdown-BgZGxhM2.js b/dist/markdown-BgZGxhM2.js
|
|
94
|
+
index 859231f9387a50df16419bc22b5268c4e446ddbc..dcc0ffda6f71adb27a03e3918a3db6fc7a58ffe9 100644
|
|
95
|
+
--- a/dist/markdown-BgZGxhM2.js
|
|
96
|
+
+++ b/dist/markdown-BgZGxhM2.js
|
|
97
|
+
@@ -4872,7 +4872,43 @@ function lu(e, t, r) {
|
|
98
98
|
case "sentence": return Oh(e, r);
|
|
99
99
|
case "word": return t.parser !== "mdx" ? zh(e, t) : Uh(e);
|
|
100
100
|
case "whitespace": {
|
|
@@ -146,7 +146,7 @@ Patch oxfmt so it preserves the line break that sits directly against a `:::` fe
|
|
|
146
146
|
```json package.json
|
|
147
147
|
{
|
|
148
148
|
"patchedDependencies": {
|
|
149
|
-
"oxfmt@0.
|
|
149
|
+
"oxfmt@0.66.0": "patches/oxfmt@0.66.0.patch"
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
```
|
|
@@ -139,6 +139,15 @@ openapi: {
|
|
|
139
139
|
- `includeInLlms: false` keeps them out of both `llms.txt` files.
|
|
140
140
|
- `noindex: true` adds crawler noindex metadata and removes the pages from the sitemap.
|
|
141
141
|
|
|
142
|
+
Each operation page's meta description is the operation's own `description` (or `summary`), followed by a generated sentence naming the endpoint — "Reference for the `GET /pets` endpoint in the Petstore API." — so a spec of terse one-line summaries still ships a distinct, snippet-length description per page. That sentence is English. On a site whose spec prose is written in another language, set `seoDescriptionSuffix: false` on the source to drop it and describe each page with the authored prose alone; an operation with neither a `description` nor a `summary` falls back to its title (`GET /pets`), so no page ships an empty description:
|
|
143
|
+
|
|
144
|
+
```ts blume.config.ts lineNumbers
|
|
145
|
+
openapi: {
|
|
146
|
+
enabled: true,
|
|
147
|
+
sources: [{ spec: "./openapi.de.json", seoDescriptionSuffix: false }],
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
142
151
|
With the [Scalar renderer](#the-scalar-renderer), only `noindex` applies — a Scalar-rendered reference already sits outside Blume's search and `llms.txt`, so the two `include*` settings have nothing to act on there.
|
|
143
152
|
|
|
144
153
|
## Authorization
|
|
@@ -202,7 +211,7 @@ AsyncAPI **2.x specs are normalized to 3.x automatically** with the official Asy
|
|
|
202
211
|
|
|
203
212
|
Code samples are **protocol-aware**, keyed off the operation's binding (or its servers' protocol): `wscat` and a browser `WebSocket` snippet for WebSockets, `kcat` for Kafka, `mosquitto_pub`/`mosquitto_sub` for MQTT. `codeSamples` filters that set, the same way it picks languages on the `openapi` block; a protocol without a supported tool renders the message payload example alone rather than a fabricated client.
|
|
204
213
|
|
|
205
|
-
Everything documented above carries over, [`playground`](#try-it-for-events) included: `route`, `sources` with `label`/`route`, `expandSchemas`, the [per-source indexing](#per-source-indexing) flags, and search indexing by operation summary and tag.
|
|
214
|
+
Everything documented above carries over, [`playground`](#try-it-for-events) included: `route`, `sources` with `label`/`route`, `expandSchemas`, the [per-source indexing](#per-source-indexing) flags (`seoDescriptionSuffix` too — the generated sentence names the channel and action instead of an endpoint), and search indexing by operation summary and tag.
|
|
206
215
|
|
|
207
216
|
Setting `renderer: "scalar"` opts back into the embedded Scalar SPA, where — as with OpenAPI — only `noindex` applies. Scalar has no AsyncAPI playground of its own; its embed auto-detects the document type and renders channels, operations, messages, and a Models section, so that swap trades the composer away.
|
|
208
217
|
|
|
@@ -272,7 +272,9 @@ import data from "blume:data";
|
|
|
272
272
|
|
|
273
273
|
## 404 page
|
|
274
274
|
|
|
275
|
-
Blume ships a default **not found** page out of the box: a centered "404" message wrapped in the site chrome (header, search, theme), served for any unmatched URL. `blume build` writes it to `404.html`, which static hosts serve automatically, and `blume dev` shows it for unknown routes.
|
|
275
|
+
Blume ships a default **not found** page out of the box: a centered "404" message wrapped in the site chrome (header, search, theme), served for any unmatched URL. `blume build` writes it to `404.html`, which static hosts serve automatically, and `blume dev` shows it for unknown routes. Under the message, a **Where to look next** list links every top-level section plus the `sitemap.xml` and [`llms.txt`](/docs/configuration/ai#llmstxt) indexes when they exist, so a reader — or an agent that followed a stale URL — has a way back.
|
|
276
|
+
|
|
277
|
+
The page also has a Markdown twin at `/404.md` and a JSON twin at `/404.json` ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem details) with the same recovery links (absolute URLs once [`deployment.site`](/docs/deployment) is set), plus the [`openapi.json`](/docs/configuration/ai#json-api) description when the JSON API is on. On a [Vercel server build](/docs/deployment#server-rendering), a request for a missing page that sends [`Accept: text/markdown`](/docs/configuration/ai#content-negotiation), or asks for a `.md` URL no page backs, gets the Markdown body with the `404` status instead of the HTML shell; one that sends `Accept: application/json`, or asks for a `.json` URL no file backs, gets the problem document — so an agent never has to parse a page of chrome to learn where to go next.
|
|
276
278
|
|
|
277
279
|
To replace it with your own, add a `pages/404.astro`. It owns the `/404` route the same way `pages/changelog.astro` takes over the changelog — your page wins and the default is dropped. Build it like any other custom page, in `PageLayout` or `RootLayout`:
|
|
278
280
|
|
|
@@ -66,7 +66,7 @@ graphql: {
|
|
|
66
66
|
}
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
Each source takes the same per-source controls
|
|
69
|
+
Each source takes the same [per-source controls](/docs/advanced/api-reference#per-source-indexing) as the OpenAPI block: `includeInSearch`, `includeInLlms`, `noindex`, and `seoDescriptionSuffix` (here the generated sentence names the query, mutation, or type — "Reference for the `pets` query in the GraphQL API.").
|
|
70
70
|
|
|
71
71
|
## Try it playground
|
|
72
72
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: AI
|
|
3
|
-
description: Make your docs machine-readable with llms.txt, add an optional in-page Ask AI assistant, and expose a hosted MCP server for coding agents.
|
|
3
|
+
description: Make your docs machine-readable with llms.txt and a JSON API described by OpenAPI, add an optional in-page Ask AI assistant, and expose a hosted MCP server for coding agents.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Blume has a few AI features: machine-readable docs for external tools (`llms.txt
|
|
6
|
+
Blume has a few AI features: machine-readable docs for external tools (`llms.txt` and a [JSON API](#json-api) with an OpenAPI description, both on by default), an in-page **Ask AI** assistant, and a hosted **MCP server** for coding agents. Ask AI and MCP are opt-in, and static docs stay fully static until you turn a feature on.
|
|
7
7
|
|
|
8
8
|
## llms.txt
|
|
9
9
|
|
|
@@ -81,6 +81,8 @@ The `.md` variant _downlevels_ components to plain Markdown for consumers that c
|
|
|
81
81
|
|
|
82
82
|
Agents don't need to know the `.md` convention: requesting a page's own URL with an [`Accept: text/markdown`](https://acceptmarkdown.com) header serves the Markdown variant at the same address, with `Vary: Accept` so caches keep the two apart. The dev server honors the header out of the box, and a [Vercel or Cloudflare server build](/docs/deployment#server-rendering) wires the same negotiation into the deploy automatically — routing rules on Vercel, a generated Worker on Cloudflare — no configuration needed. The homepage always negotiates, even when it's a custom landing page rather than a content page: its Markdown mirror falls back to the [`llms.txt`](#llmstxt) index, so an agent asking the site root for Markdown gets the machine-readable map of the site. Markdown responses also carry an `x-markdown-tokens` header — an estimated token count (~4 characters per token), following the convention of [Cloudflare's Markdown for Agents](https://developers.cloudflare.com/fundamentals/reference/markdown-for-agents/) — on every surface where Blume controls response headers: the dev server, server-rendered responses, and the negotiated homepage on Vercel and Cloudflare. Other deploy targets serve prerendered pages from a static layer with no request-time hook, so agents there fetch the `.md` URL directly; the [agent readability manifest](#agent-readability) advertises `contentNegotiation` only on deployments that honor the header.
|
|
83
83
|
|
|
84
|
+
Missing pages negotiate too. Every build emits a Markdown [404 page](/docs/advanced/custom-pages#404-page) at `/404.md` — the not-found message followed by recovery links to every top-level section, the sitemap, and `llms.txt` — and on Vercel a request for a nonexistent URL that prefers Markdown, or any `.md` URL with no page behind it, gets that body with a real `404` status rather than the HTML shell.
|
|
85
|
+
|
|
84
86
|
### Custom component serializers
|
|
85
87
|
|
|
86
88
|
Give your own components a Markdown form with `ai.markdownComponents` — a map of JSX name to serializer. Each serializer receives the component's `props` (statically evaluated from the MDX attributes, with the page's `frontmatter` in scope), its `children` (already downleveled to Markdown), and the page's `frontmatter` data, and returns the replacement — or `null` to leave the JSX as-is:
|
|
@@ -109,6 +111,8 @@ Serializers live in `blume.config.ts`, not `components.tsx`: the config file is
|
|
|
109
111
|
|
|
110
112
|
Every page carries a **Copy as Markdown** action — in the [page actions](/docs/content/navigation#page-actions) beneath the table of contents — that copies the page's raw Markdown to the clipboard. It's the same source served at the [`.md` URL](#raw-markdown) above, ready to paste into an LLM, an issue, or your notes. It's available on every page, in dev and production, with no configuration.
|
|
111
113
|
|
|
114
|
+
Where the Clipboard API is unavailable or the browser denies it — in-app browsers, WebViews, insecure origins — the action falls back to the legacy copy command, and if nothing lands on the clipboard the button reports **Copy failed** (localized via `actions.copyFailed`) rather than staying silent. The same fallback backs every copy button Blume renders.
|
|
115
|
+
|
|
112
116
|
## Open in chat
|
|
113
117
|
|
|
114
118
|
The **Open in chat** action opens the current page in an AI assistant — v0, ChatGPT, Claude, T3 Chat, Scira, or Cursor — pre-filled with a prompt that points it at the page's raw Markdown so it can answer questions about what you're reading:
|
|
@@ -360,6 +364,52 @@ A static build with `ai.mcp.enabled` fails fast with a message telling you to se
|
|
|
360
364
|
claude mcp add --transport http my-docs https://docs.example.com/mcp
|
|
361
365
|
```
|
|
362
366
|
|
|
367
|
+
## JSON API
|
|
368
|
+
|
|
369
|
+
Every Blume site also serves its docs as a small read-only **JSON API** — the REST twin of the [MCP server](#mcp-server)'s tools, over the same page snapshot, for agents and function-calling frameworks that speak plain HTTP rather than MCP. It's on by default and needs no configuration:
|
|
370
|
+
|
|
371
|
+
| Endpoint | Returns |
|
|
372
|
+
| --- | --- |
|
|
373
|
+
| `/api/docs/pages.json` | Every page with its route, title, description, content type, locale, facets, and the URLs of its rendered, Markdown, and JSON forms. |
|
|
374
|
+
| `/api/docs/pages/{route}.json` | One page: its index entry plus the agent Markdown (the same body `get_page` returns). `{route}` is the page route without the leading slash, `index` for home. |
|
|
375
|
+
| `/api/docs/navigation.json` | The navigation tree — header tabs and the sidebar hierarchy. |
|
|
376
|
+
| `/api/docs/search?q=` | Full-text search, with the same `limit`, `contentTypes`, `locale`, `version`, and `filters[key]` scoping as `search_docs`. Server output only. |
|
|
377
|
+
| `/openapi.json` | The OpenAPI 3.1 description of the whole machine-readable surface. |
|
|
378
|
+
|
|
379
|
+
The page index, per-page documents, and navigation are prerendered, so a static site serves them as files from any host. Search is a live endpoint and exists only under [server output](/docs/deployment#server-rendering), where it runs the same index `search_docs` does. Errors are [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem details (`application/problem+json`) with a stable `code`, a `detail`, and a `resolution` hint telling the agent where to go next — a missing page, a blank search query, or on server output any `/api/…` URL no endpoint answers:
|
|
380
|
+
|
|
381
|
+
```json
|
|
382
|
+
{
|
|
383
|
+
"code": "API_ROUTE_NOT_FOUND",
|
|
384
|
+
"detail": "No API route exists at /api/nope.",
|
|
385
|
+
"instance": "/api/nope",
|
|
386
|
+
"links": [
|
|
387
|
+
{
|
|
388
|
+
"href": "https://docs.example.com/openapi.json",
|
|
389
|
+
"label": "OpenAPI description"
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
"href": "https://docs.example.com/api/docs/pages.json",
|
|
393
|
+
"label": "Page index"
|
|
394
|
+
}
|
|
395
|
+
],
|
|
396
|
+
"resolution": "Discover the available operations through the OpenAPI description at https://docs.example.com/openapi.json, or list every page at https://docs.example.com/api/docs/pages.json.",
|
|
397
|
+
"status": 404,
|
|
398
|
+
"title": "API route not found",
|
|
399
|
+
"type": "about:blank"
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The **OpenAPI document** at `/openapi.json` is generated per build from your config, so it describes only what the deployed site serves: every JSON endpoint with a unique `operationId`, typed parameters, and response schemas, plus the text surfaces alongside — the [`.md` mirrors](#raw-markdown), [`llms.txt`](#llmstxt) and `llms-full.txt`, [`agent-readability.json`](#agent-readability) — and the [MCP endpoint](#mcp-server) when it's enabled. Frameworks that build tools from an OpenAPI description get the same reach an MCP client has. The document is linked from the [API catalog](#api-catalog), the [readability manifest](#agent-readability), the homepage `Link` header as `rel="service-desc"`, and `llms.txt`.
|
|
404
|
+
|
|
405
|
+
None of this touches your own [API reference](/docs/advanced/api-reference): a documented spec is rendered into pages, never served at `/openapi.json`, and the catalog lists both. A `public/openapi.json` you ship yourself takes over that route (the JSON endpoints stay). The `/api/…` catch-all steps aside when a docs section is served from the `/api` namespace (`content/api/overview.md`) or a custom page owns a rest route under `/api/`, so those pages keep winning. Set `ai.api` to `false` to publish none of it:
|
|
406
|
+
|
|
407
|
+
```ts blume.config.ts lineNumbers
|
|
408
|
+
ai: {
|
|
409
|
+
api: false,
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
363
413
|
## Agent readability
|
|
364
414
|
|
|
365
415
|
Blume writes an **`/agent-readability.json`** manifest at your site root that indexes the agent-facing surface described on this page — so an agent can discover it in a single fetch instead of guessing at conventions or scraping HTML. Like `llms.txt`, it's on by default:
|
|
@@ -370,7 +420,7 @@ seo: {
|
|
|
370
420
|
}
|
|
371
421
|
```
|
|
372
422
|
|
|
373
|
-
The manifest lists only what you've enabled — the [raw Markdown](#raw-markdown) mirror pattern, [`llms.txt`](#llmstxt) and `llms-full.txt`, the [MCP server](#mcp-server) and its discovery document, the [Ask AI](#ask-ai) endpoint, the [sitemap](/docs/configuration/seo#sitemap), and [RSS feeds](/docs/configuration/seo#rss-feeds) — alongside your site name, description, source repository, and the [content-signal](/docs/configuration/seo#content-signals) usage policy. URLs are absolute when [`deployment.site`](/docs/deployment) is set and root-relative otherwise:
|
|
423
|
+
The manifest lists only what you've enabled — the [raw Markdown](#raw-markdown) mirror pattern, the [JSON API](#json-api) and its OpenAPI description, [`llms.txt`](#llmstxt) and `llms-full.txt`, the [MCP server](#mcp-server) and its discovery document, the [Ask AI](#ask-ai) endpoint, the [sitemap](/docs/configuration/seo#sitemap), and [RSS feeds](/docs/configuration/seo#rss-feeds) — alongside your site name, description, source repository, and the [content-signal](/docs/configuration/seo#content-signals) usage policy. URLs are absolute when [`deployment.site`](/docs/deployment) is set and root-relative otherwise:
|
|
374
424
|
|
|
375
425
|
```json agent-readability.json
|
|
376
426
|
{
|
|
@@ -379,6 +429,11 @@ The manifest lists only what you've enabled — the [raw Markdown](#raw-markdown
|
|
|
379
429
|
"contentNegotiation": "text/markdown",
|
|
380
430
|
"pattern": "https://docs.example.com/{route}.md"
|
|
381
431
|
},
|
|
432
|
+
"api": {
|
|
433
|
+
"openapi": "https://docs.example.com/openapi.json",
|
|
434
|
+
"pages": "https://docs.example.com/api/docs/pages.json",
|
|
435
|
+
"search": "https://docs.example.com/api/docs/search"
|
|
436
|
+
},
|
|
382
437
|
"llmsFullTxt": "https://docs.example.com/llms-full.txt",
|
|
383
438
|
"llmsTxt": "https://docs.example.com/llms.txt",
|
|
384
439
|
"mcp": {
|
|
@@ -404,12 +459,14 @@ Set `seo.agentReadability` to `false` to skip it, or ship your own `public/agent
|
|
|
404
459
|
Agents that probe a site don't know to look for the manifest — so Blume also advertises it in an [RFC 8288](https://www.rfc-editor.org/rfc/rfc8288) `Link` response header on the homepage, using IANA-registered relation types:
|
|
405
460
|
|
|
406
461
|
```http
|
|
407
|
-
Link:
|
|
462
|
+
Link: </.well-known/api-catalog>; rel="api-catalog"; type="application/linkset+json",
|
|
463
|
+
</openapi.json>; rel="service-desc"; type="application/json",
|
|
464
|
+
</agent-readability.json>; rel="describedby"; type="application/json",
|
|
408
465
|
</llms.txt>; rel="describedby"; type="text/plain",
|
|
409
466
|
</index.md>; rel="alternate"; type="text/markdown"
|
|
410
467
|
```
|
|
411
468
|
|
|
412
|
-
Each entry appears only when its feature is on. The `alternate` link points at the homepage's Markdown mirror — the page's own [raw Markdown](#raw-markdown) when the home route is a content page, or the synthesized `llms.txt` fallback when it's a landing page.
|
|
469
|
+
Each entry appears only when its feature is on. The `alternate` link points at the homepage's Markdown mirror — the page's own [raw Markdown](#raw-markdown) when the home route is a content page, or the synthesized `llms.txt` fallback when it's a landing page. The `service-desc` link ([RFC 8631](https://www.rfc-editor.org/rfc/rfc8631)) points at the [JSON API](#json-api)'s OpenAPI description, and `api-catalog` at the [generated API catalog](#api-catalog). The header rides on every surface Blume controls: the dev server (check it with `curl -I localhost:4321`), static builds via the emitted `_headers` file (Netlify and Cloudflare), and Vercel server builds via the deploy's routing rules.
|
|
413
470
|
|
|
414
471
|
Not every agent enters through the root, though — one following a search result or a shared link lands on a deep page and never sees the homepage header. So every rendered page also carries the same discovery links in its HTML `<head>`, using the same IANA-registered relations:
|
|
415
472
|
|
|
@@ -427,7 +484,7 @@ Here the `alternate` link points at _that page's own_ [raw-Markdown mirror](#raw
|
|
|
427
484
|
|
|
428
485
|
### API catalog
|
|
429
486
|
|
|
430
|
-
When the site publishes APIs, Blume generates an [RFC 9727](https://www.rfc-editor.org/rfc/rfc9727) API catalog at `/.well-known/api-catalog` — a [linkset](https://www.rfc-editor.org/rfc/rfc9264) that lets agents enumerate your APIs from the domain alone, served with its registered `application/linkset+json` media type on every build surface. There's nothing to configure: the catalog is derived from what's already in `blume.config.ts`. Each [OpenAPI or AsyncAPI reference](/docs/advanced/api-reference) becomes an entry anchored at its rendered docs route, with `service-doc` pointing at those docs and `service-desc` at the spec when it lives at a fetchable URL; the [MCP server](#mcp-server) becomes an entry with its discovery document as the service description:
|
|
487
|
+
When the site publishes APIs, Blume generates an [RFC 9727](https://www.rfc-editor.org/rfc/rfc9727) API catalog at `/.well-known/api-catalog` — a [linkset](https://www.rfc-editor.org/rfc/rfc9264) that lets agents enumerate your APIs from the domain alone, served with its registered `application/linkset+json` media type on every build surface. There's nothing to configure: the catalog is derived from what's already in `blume.config.ts`. Each [OpenAPI or AsyncAPI reference](/docs/advanced/api-reference) becomes an entry anchored at its rendered docs route, with `service-doc` pointing at those docs and `service-desc` at the spec when it lives at a fetchable URL; the site's own [JSON API](#json-api) becomes an entry described by its `/openapi.json`; and the [MCP server](#mcp-server) becomes an entry with its discovery document as the service description:
|
|
431
488
|
|
|
432
489
|
```json .well-known/api-catalog
|
|
433
490
|
{
|
|
@@ -439,6 +496,18 @@ When the site publishes APIs, Blume generates an [RFC 9727](https://www.rfc-edit
|
|
|
439
496
|
],
|
|
440
497
|
"service-desc": [{ "href": "https://api.example.com/openapi.json" }]
|
|
441
498
|
},
|
|
499
|
+
{
|
|
500
|
+
"anchor": "https://docs.example.com/api/docs",
|
|
501
|
+
"service-desc": [
|
|
502
|
+
{
|
|
503
|
+
"href": "https://docs.example.com/openapi.json",
|
|
504
|
+
"type": "application/json"
|
|
505
|
+
}
|
|
506
|
+
],
|
|
507
|
+
"service-doc": [
|
|
508
|
+
{ "href": "https://docs.example.com/", "type": "text/html" }
|
|
509
|
+
]
|
|
510
|
+
},
|
|
442
511
|
{
|
|
443
512
|
"anchor": "https://docs.example.com/mcp",
|
|
444
513
|
"service-desc": [
|
|
@@ -455,7 +524,7 @@ When the site publishes APIs, Blume generates an [RFC 9727](https://www.rfc-edit
|
|
|
455
524
|
}
|
|
456
525
|
```
|
|
457
526
|
|
|
458
|
-
A site with no API references
|
|
527
|
+
A site with no API references, no MCP server, and the [JSON API](#json-api) turned off emits no catalog — there'd be nothing in it. As everywhere, a `public/.well-known/api-catalog` file you ship yourself wins over the generated one.
|
|
459
528
|
|
|
460
529
|
### WebMCP
|
|
461
530
|
|
|
@@ -125,7 +125,7 @@ seo: {
|
|
|
125
125
|
}
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
By default, each card is derived from your content and theme — the **page title** as the headline, your **site title** as the eyebrow, and your theme **accent** for the mark. Images are served at `/og/<slug>.png`, mirroring each route, and are prerendered as static files even in server mode:
|
|
128
|
+
By default, each card is derived from your content and theme — the **page title** as the headline, the **page description** as the subtitle (the same text as its `og:description`, so `seo.description` wins over `description`), your **site title** as the eyebrow, and your theme **accent** for the mark. Images are served at `/og/<slug>.png`, mirroring each route, and are prerendered as static files even in server mode:
|
|
129
129
|
|
|
130
130
|
| Page route | Image URL |
|
|
131
131
|
| ------------------- | -------------------------- |
|
|
@@ -151,13 +151,13 @@ Emoji in a page title or site title render as [Twemoji](https://github.com/jdeck
|
|
|
151
151
|
|
|
152
152
|
### Show, hide, or override card layers
|
|
153
153
|
|
|
154
|
-
Beyond the headline, the card carries three optional layers: the **brand mark** in the top-left (your logo, or an accent tile with the site title's initial), the **subtitle** under the headline (your site `description`), and a **footer** with your repo slug (from `github`) and the site's URL — the deployment site's host plus [`deployment.base`](/docs/deployment#subpath-deploys), so a GitHub Pages project site reads `user.github.io/repo`. Override any of them with a string of your own, or hide one with `false`:
|
|
154
|
+
Beyond the headline, the card carries three optional layers: the **brand mark** in the top-left (your logo, or an accent tile with the site title's initial), the **subtitle** under the headline (the page `description`, or your site `description` for pages without one), and a **footer** with your repo slug (from `github`) and the site's URL — the deployment site's host plus [`deployment.base`](/docs/deployment#subpath-deploys), so a GitHub Pages project site reads `user.github.io/repo`. Override any of them with a string of your own, or hide one with `false`:
|
|
155
155
|
|
|
156
156
|
```ts blume.config.ts lineNumbers
|
|
157
157
|
seo: {
|
|
158
158
|
og: {
|
|
159
159
|
site: "docs.acme.com", // footer URL text, or false to hide it
|
|
160
|
-
description: false, // hide the subtitle; a string
|
|
160
|
+
description: false, // hide the subtitle on every card; a string replaces the site fallback
|
|
161
161
|
logo: false, // no brand mark at all — not even the initial tile
|
|
162
162
|
},
|
|
163
163
|
}
|
|
@@ -206,6 +206,12 @@ Drop a `theme.css` in your project root to override any design token. It's the l
|
|
|
206
206
|
|
|
207
207
|
Set a token under `:root` for light mode and under `:root[data-theme="dark"]` for dark mode. Color tokens have distinct built-in dark values declared at the dark selector's higher specificity, so a `:root`-only override of `--blume-accent`, `--blume-background`, and friends applies to light mode only — declare the dark block too when both modes should change.
|
|
208
208
|
|
|
209
|
+
`theme.css` is inlined into the site's Tailwind entry, so Tailwind directives work in it too. The one to know for a monorepo is `@source`: Blume scans your project for utility classes, and a page that imports components from a sibling workspace package needs that package scanned as well. Point at it relative to `theme.css` — the standard Tailwind rule — and Blume carries the path into the generated sheet:
|
|
210
|
+
|
|
211
|
+
```css theme.css lineNumbers
|
|
212
|
+
@source "../../packages/ui/src";
|
|
213
|
+
```
|
|
214
|
+
|
|
209
215
|
### Design tokens
|
|
210
216
|
|
|
211
217
|
| Token | Controls |
|
|
@@ -567,7 +567,7 @@ The card reads the instance from [`github.host`](/docs/configuration#github-ente
|
|
|
567
567
|
|
|
568
568
|
`Component` renders an example file from your project's `examples/` directory as a live preview alongside its highlighted source, in tabs. Point it at a file with `path` — its location under `examples/`, without the extension (so `examples/counter.tsx` is `path="counter"`). React, Vue, Svelte, and Astro examples are all supported; framework examples hydrate, Astro ones render statically. It keeps the preview and the code in sync from a single file.
|
|
569
569
|
|
|
570
|
-
The preview renders in an isolated frame that the docs styles never reach — no prose margins, typography, or theme chrome bleed into your component. The frame gets Tailwind (preflight + utilities scanned from your
|
|
570
|
+
The preview renders in an isolated frame that the docs styles never reach — no prose margins, typography, or theme chrome bleed into your component. The frame gets Tailwind (preflight + utilities scanned from your project and your examples directory), Blume's design tokens so classes like `bg-background` follow the site palette by default, and it follows the site's light/dark toggle live. The pane sizes itself to the rendered example — and keeps tracking it if the example grows or shrinks after load — with the Preview and Code tabs sharing one height so toggling them never shifts the page.
|
|
571
571
|
|
|
572
572
|
To style previews with your own design system — say, shadcn variables — point `examples.css` at a stylesheet. It's injected into every preview frame after Blume's defaults, so your tokens win. Don't `@import "tailwindcss"` in it; the frame already provides Tailwind. Both `.dark` and `[data-theme="dark"]` work for dark-mode overrides:
|
|
573
573
|
|
|
@@ -623,6 +623,13 @@ export default defineConfig({
|
|
|
623
623
|
<Component path="file-list/examples/basic" />
|
|
624
624
|
```
|
|
625
625
|
|
|
626
|
+
In a monorepo, the components your examples import usually live in a sibling workspace package. Tailwind's `@source` scans files, not imports: Blume scans your project and the `examples` directory (wherever `source` points, even outside the project), so a class used only inside that sibling package isn't generated until you add the package to the scan. Do that with an `@source` directive in `examples.css` (for the preview frames) or `theme.css` (for the site), written relative to the file it sits in — the standard Tailwind rule — and Blume carries it into the generated sheet:
|
|
627
|
+
|
|
628
|
+
```css
|
|
629
|
+
/* examples/theme.css */
|
|
630
|
+
@source "../../../packages/ui/src";
|
|
631
|
+
```
|
|
632
|
+
|
|
626
633
|
<Component path="counter" />
|
|
627
634
|
|
|
628
635
|
```astro
|
package/docs/index.mdx
CHANGED
|
@@ -31,7 +31,7 @@ Blume builds on Astro and Vite and renders static HTML by default — fast, cach
|
|
|
31
31
|
|
|
32
32
|
### AI-ready out of the box
|
|
33
33
|
|
|
34
|
-
Every Blume site speaks fluent machine. It emits [`llms.txt` and `llms-full.txt`](/docs/configuration/ai), serves any page's raw Markdown by appending `.md` to its URL, and gives readers **Copy as Markdown** and **Open in chat** actions on every page. Add an optional in-page **Ask AI** assistant, or host an [**MCP server**](/docs/configuration/ai#mcp-server) so coding agents like Claude Code and Cursor can search and read your docs directly — no scraping, no hosted service. Your Markdown is the source of truth for both humans and models.
|
|
34
|
+
Every Blume site speaks fluent machine. It emits [`llms.txt` and `llms-full.txt`](/docs/configuration/ai), serves any page's raw Markdown by appending `.md` to its URL, exposes a [JSON API described by OpenAPI](/docs/configuration/ai#json-api), and gives readers **Copy as Markdown** and **Open in chat** actions on every page. Add an optional in-page **Ask AI** assistant, or host an [**MCP server**](/docs/configuration/ai#mcp-server) so coding agents like Claude Code and Cursor can search and read your docs directly — no scraping, no hosted service. Your Markdown is the source of truth for both humans and models.
|
|
35
35
|
|
|
36
36
|
### Zero configuration — even the template
|
|
37
37
|
|
|
@@ -45,7 +45,7 @@ Your [`blume.config.ts`](/docs/configuration) and every [`meta.ts`](/docs/conten
|
|
|
45
45
|
|
|
46
46
|
- **Components** — callouts, cards, steps, tabs, accordions, badges, file trees, and parameter tables, usable in MDX with [no imports](/docs/content/components).
|
|
47
47
|
- **Local search** — Orama works in dev and production; Pagefind is one flag away for large sites. No hosted index.
|
|
48
|
-
- **AI** — [`llms.txt`, raw Markdown URLs, Copy as Markdown, Open in chat, an Ask AI assistant, and a hosted MCP server](/docs/configuration/ai).
|
|
48
|
+
- **AI** — [`llms.txt`, raw Markdown URLs, a JSON API with an OpenAPI description, Copy as Markdown, Open in chat, an Ask AI assistant, and a hosted MCP server](/docs/configuration/ai).
|
|
49
49
|
- **Navigation** — inferred from files, refined with `meta.ts` or config.
|
|
50
50
|
- **SEO** — metadata, Open Graph images, RSS feeds, and JSON-LD, [built in](/docs/configuration/seo).
|
|
51
51
|
- **Customization** — component overrides, React islands, custom pages, theme tokens, and a source-component registry via `blume add`.
|