blume 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CHANGELOG.md +618 -0
  2. package/LICENSE +21 -0
  3. package/README.md +107 -0
  4. package/dist/cli/index.js +1487 -360
  5. package/dist/cli/index.js.map +91 -85
  6. package/dist/types/ai/component-markdown.d.ts +34 -0
  7. package/dist/types/components/content/youtube.d.ts +18 -0
  8. package/dist/types/core/base-path.d.ts +9 -0
  9. package/dist/types/core/config-input.d.ts +36 -2
  10. package/dist/types/core/config.d.ts +3 -2
  11. package/dist/types/core/data.d.ts +2 -0
  12. package/dist/types/core/i18n-ui.d.ts +476 -132
  13. package/dist/types/core/schema.d.ts +216 -145
  14. package/dist/types/index.d.ts +1 -0
  15. package/dist/types/openapi/references.d.ts +60 -0
  16. package/docs/01-quickstart.mdx +5 -2
  17. package/docs/02-deployment.mdx +8 -8
  18. package/docs/03-faq.mdx +46 -16
  19. package/docs/advanced/custom-pages.mdx +1 -1
  20. package/docs/advanced/skills.mdx +1 -1
  21. package/docs/configuration/ai.mdx +49 -10
  22. package/docs/configuration/customization.mdx +11 -0
  23. package/docs/configuration/index.mdx +27 -3
  24. package/docs/configuration/seo.mdx +2 -2
  25. package/docs/content/components.mdx +1 -1
  26. package/docs/content/i18n.mdx +1 -1
  27. package/docs/content/navigation.mdx +3 -3
  28. package/docs/content/sources.mdx +1 -1
  29. package/docs/content/syntax.mdx +4 -2
  30. package/docs/index.mdx +2 -2
  31. package/docs/reference/cli.mdx +8 -6
  32. package/package.json +14 -4
  33. package/skills/blume/SKILL.md +5 -3
  34. package/skills/blume-update-docs/SKILL.md +3 -2
  35. package/src/ai/agent-readability.ts +9 -8
  36. package/src/ai/ask-context.ts +7 -2
  37. package/src/ai/ask-data.ts +3 -0
  38. package/src/ai/component-markdown.ts +461 -0
  39. package/src/ai/llms.ts +135 -26
  40. package/src/ai/markdown.ts +35 -6
  41. package/src/ai/mcp/data.ts +25 -4
  42. package/src/ai/mcp/discovery.ts +10 -3
  43. package/src/ai/mcp/server.ts +21 -7
  44. package/src/ai/visibility.ts +74 -0
  45. package/src/astro/component-slots.ts +11 -1
  46. package/src/astro/generate.ts +76 -45
  47. package/src/astro/integration.ts +1 -1
  48. package/src/astro/markdown-negotiation.ts +1 -1
  49. package/src/astro/pages.ts +81 -19
  50. package/src/astro/templates.ts +99 -12
  51. package/src/blume-modules.d.ts +8 -0
  52. package/src/cli/commands/build.ts +99 -19
  53. package/src/cli/commands/check.ts +1 -1
  54. package/src/cli/commands/dev.ts +26 -5
  55. package/src/cli/commands/eject.ts +47 -19
  56. package/src/cli/commands/init.ts +120 -180
  57. package/src/cli/commands/preview.ts +4 -1
  58. package/src/cli/commands/validate.ts +43 -2
  59. package/src/cli/dev-lock.ts +8 -4
  60. package/src/cli/eject-scripts.ts +72 -0
  61. package/src/cli/env.ts +15 -5
  62. package/src/cli/init/questions.ts +158 -0
  63. package/src/cli/init/scaffold.ts +380 -0
  64. package/src/components/content/AccordionItem.astro +23 -4
  65. package/src/components/content/Badge.astro +3 -1
  66. package/src/components/content/Card.astro +4 -2
  67. package/src/components/content/Step.astro +10 -1
  68. package/src/components/content/Tabs.astro +15 -3
  69. package/src/components/content/Tile.astro +2 -1
  70. package/src/components/content/Tooltip.astro +3 -1
  71. package/src/components/content/Update.astro +9 -2
  72. package/src/components/content/auto-type-table.ts +7 -1
  73. package/src/components/content/base-href.ts +33 -0
  74. package/src/components/content/changelog-element.ts +9 -2
  75. package/src/components/content/mermaid-element.ts +7 -2
  76. package/src/components/islands/AskAI.astro +5 -2
  77. package/src/components/islands/ask-ai.tsx +56 -6
  78. package/src/components/islands/hooks.ts +28 -8
  79. package/src/components/layout/Banner.astro +10 -2
  80. package/src/components/layout/Header.astro +13 -4
  81. package/src/components/layout/Logo.astro +11 -3
  82. package/src/components/layout/NavTree.astro +17 -3
  83. package/src/components/layout/PageActions.astro +25 -10
  84. package/src/components/layout/PageLayout.astro +45 -8
  85. package/src/components/layout/ReferenceLayout.astro +8 -1
  86. package/src/components/layout/RootLayout.astro +67 -9
  87. package/src/components/layout/Search.astro +94 -22
  88. package/src/components/layout/search/algolia.ts +11 -2
  89. package/src/components/layout/search/endpoint.ts +11 -5
  90. package/src/components/layout/search/orama-cloud.ts +8 -2
  91. package/src/components/layout/search/types.ts +5 -1
  92. package/src/components/layout/search/typesense.ts +4 -1
  93. package/src/components/layout/toc-element.ts +1 -1
  94. package/src/components/openapi/ApiTagOperations.astro +2 -1
  95. package/src/components/openapi/Operation.astro +47 -40
  96. package/src/components/openapi/RequestPanel.astro +1 -1
  97. package/src/components/openapi/helpers.ts +71 -3
  98. package/src/components/openapi/panel.ts +1 -1
  99. package/src/core/base-path.ts +24 -0
  100. package/src/core/builtin-tags.ts +2 -0
  101. package/src/core/config-input.ts +37 -2
  102. package/src/core/config.ts +3 -2
  103. package/src/core/data.ts +2 -0
  104. package/src/core/graph.ts +15 -5
  105. package/src/core/i18n-ui.ts +45 -0
  106. package/src/core/last-modified.ts +13 -6
  107. package/src/core/links.ts +32 -8
  108. package/src/core/navigation.ts +29 -4
  109. package/src/core/package-json.ts +17 -2
  110. package/src/core/project-graph.ts +15 -6
  111. package/src/core/schema.ts +36 -2
  112. package/src/core/sources/assets.ts +6 -1
  113. package/src/core/sources/filesystem.ts +4 -0
  114. package/src/core/sources/mdx-remote.ts +23 -14
  115. package/src/core/sources/normalize.ts +152 -50
  116. package/src/core/sources/notion.ts +8 -8
  117. package/src/core/ui-packs/ar.ts +1 -0
  118. package/src/core/ui-packs/bg.ts +1 -0
  119. package/src/core/ui-packs/bn.ts +1 -0
  120. package/src/core/ui-packs/ca.ts +1 -0
  121. package/src/core/ui-packs/cs.ts +1 -0
  122. package/src/core/ui-packs/da.ts +1 -0
  123. package/src/core/ui-packs/de.ts +1 -0
  124. package/src/core/ui-packs/el.ts +1 -0
  125. package/src/core/ui-packs/es.ts +1 -0
  126. package/src/core/ui-packs/fa.ts +1 -0
  127. package/src/core/ui-packs/fi.ts +1 -0
  128. package/src/core/ui-packs/fr.ts +2 -1
  129. package/src/core/ui-packs/he.ts +1 -0
  130. package/src/core/ui-packs/hi.ts +1 -0
  131. package/src/core/ui-packs/hr.ts +1 -0
  132. package/src/core/ui-packs/hu.ts +1 -0
  133. package/src/core/ui-packs/id.ts +1 -0
  134. package/src/core/ui-packs/it.ts +1 -0
  135. package/src/core/ui-packs/ja.ts +1 -0
  136. package/src/core/ui-packs/ko.ts +1 -0
  137. package/src/core/ui-packs/nl.ts +1 -0
  138. package/src/core/ui-packs/no.ts +1 -0
  139. package/src/core/ui-packs/pl.ts +1 -0
  140. package/src/core/ui-packs/pt-br.ts +1 -0
  141. package/src/core/ui-packs/pt.ts +1 -0
  142. package/src/core/ui-packs/ro.ts +1 -0
  143. package/src/core/ui-packs/ru.ts +1 -0
  144. package/src/core/ui-packs/sk.ts +1 -0
  145. package/src/core/ui-packs/sr.ts +1 -0
  146. package/src/core/ui-packs/sv.ts +1 -0
  147. package/src/core/ui-packs/th.ts +1 -0
  148. package/src/core/ui-packs/tr.ts +1 -0
  149. package/src/core/ui-packs/uk.ts +1 -0
  150. package/src/core/ui-packs/vi.ts +1 -0
  151. package/src/core/ui-packs/zh-tw.ts +1 -0
  152. package/src/core/ui-packs/zh.ts +1 -0
  153. package/src/deploy/adapter-output.ts +18 -8
  154. package/src/deploy/redirects.ts +7 -2
  155. package/src/deploy/sitemap.ts +53 -11
  156. package/src/index.ts +5 -0
  157. package/src/markdown/base-links.ts +10 -8
  158. package/src/markdown/index.ts +15 -3
  159. package/src/markdown/inline-code.ts +7 -2
  160. package/src/markdown/package-commands.ts +10 -4
  161. package/src/openapi/model.ts +12 -4
  162. package/src/openapi/parse.ts +21 -0
  163. package/src/openapi/references.ts +38 -8
  164. package/src/openapi/source.ts +59 -10
  165. package/src/registry/eject.ts +184 -12
  166. package/src/registry/registry.ts +0 -3
  167. package/src/search/documents.ts +34 -2
  168. package/src/seo/jsonld.ts +13 -12
@@ -1,3 +1,4 @@
1
+ export type { ComponentMarkdown, ComponentMarkdownChild, ComponentMarkdownContext, } from "./ai/component-markdown.ts";
1
2
  export { defineConfig } from "./core/config.ts";
2
3
  export type { BlumeBanner, BlumeData, BlumeDataConfig, BlumeDataI18n, BlumeDataLocale, BlumeFavicon, BlumeFeed, BlumeLogo, BlumeRoute, } from "./core/data.ts";
3
4
  export { defineComponents } from "./core/define-components.ts";
@@ -0,0 +1,60 @@
1
+ import type { ResolvedConfig } from "../core/schema.ts";
2
+ import type { NavTab } from "../core/types.ts";
3
+ /**
4
+ * Pure resolution of the configured API reference blocks into concrete routes,
5
+ * labels, and a renderer choice — no file IO, so the content source, the nav
6
+ * tabs, the Scalar page generator, and the `blume:openapi` data module all share
7
+ * one source of truth. Kept free of any Astro/template imports so `core` can
8
+ * depend on it without a cycle.
9
+ */
10
+ export type ReferenceKind = "openapi" | "asyncapi";
11
+ /** Who renders a reference: Blume's own UI, or the embedded Scalar SPA. */
12
+ export type ReferenceRenderer = "blume" | "scalar";
13
+ /** Per-block display options for the Blume renderer. */
14
+ export interface ReferenceDisplay {
15
+ /** Code-sample languages shown per operation. */
16
+ codeSamples: string[];
17
+ /** Whether nested schema rows start expanded. */
18
+ expandSchemas: boolean;
19
+ }
20
+ /** A spec source resolved to a concrete route, label, and renderer. */
21
+ export interface ReferenceSource {
22
+ kind: ReferenceKind;
23
+ renderer: ReferenceRenderer;
24
+ /** Unique token derived from the route; the `<Operation source>` / data key. */
25
+ slug: string;
26
+ /** Normalized route the reference mounts at, e.g. `/reference`. */
27
+ route: string;
28
+ /**
29
+ * Site-wide `basePath` the rendered pages are mounted under (`""` when
30
+ * none). Kept separate from `route` — the content pipeline applies it to
31
+ * staged entries itself — so consumers prefix only the URLs they emit.
32
+ */
33
+ basePath: string;
34
+ label: string;
35
+ /** Local path or `http(s)` URL, verbatim from config. */
36
+ spec: string;
37
+ /** Per-block Scalar theme name override, if any (Scalar renderer only). */
38
+ theme?: string;
39
+ /** Display options carried through to the Blume renderer. */
40
+ display: ReferenceDisplay;
41
+ /**
42
+ * Warnings recorded while deduping — another source's route collided with
43
+ * this one and was dropped. Surfaced as diagnostics when the source loads.
44
+ */
45
+ collisions?: string[];
46
+ }
47
+ export declare const slugify: (text: string) => string;
48
+ /** Normalize a configured route to a single leading slash, no trailing slash. */
49
+ export declare const normalizeRoute: (route: string) => string;
50
+ /**
51
+ * Resolve every enabled reference. OpenAPI honors its `renderer` (Blume's own UI
52
+ * by default); AsyncAPI is always rendered by Scalar for now.
53
+ */
54
+ export declare const resolveReferences: (config: ResolvedConfig) => ReferenceSource[];
55
+ /** Nav tabs (header links) for every reference, regardless of renderer. */
56
+ export declare const referenceTabs: (config: ResolvedConfig) => NavTab[];
57
+ /** Blume-rendered OpenAPI references, deduped by route (first wins). */
58
+ export declare const blumeReferences: (config: ResolvedConfig) => ReferenceSource[];
59
+ /** Whether any reference is Scalar-rendered (gates the `@scalar/astro` dep + pages). */
60
+ export declare const hasScalarReferences: (config: ResolvedConfig) => boolean;
@@ -6,7 +6,7 @@ sidebar:
6
6
  order: 1
7
7
  ---
8
8
 
9
- Go from an empty folder to a running docs site in a few commands. Blume needs **Node.js 22 or newer** and a `docs/` folder with at least one `.md` or `.mdx` file — there's nothing else to set up.
9
+ Go from an empty folder to a running docs site in a few commands. Blume needs **Node.js 22.12 or newer** and a `docs/` folder with at least one `.md` or `.mdx` file — there's nothing else to set up.
10
10
 
11
11
  ## Install and run
12
12
 
@@ -20,7 +20,10 @@ Go from an empty folder to a running docs site in a few commands. Blume needs **
20
20
 
21
21
  </Step>
22
22
  <Step title="Scaffold a project">
23
- `blume init` creates the minimum surface: a content folder and a config file.
23
+ `blume init` asks a few questions where to create the project, what your
24
+ docs site is called, which template, and where your content lives — then
25
+ scaffolds the minimum surface: a content folder and a config file. Pass
26
+ `--yes` to skip the prompts and take the defaults.
24
27
 
25
28
  ```bash
26
29
  blume init
@@ -10,11 +10,11 @@ sidebar:
10
10
 
11
11
  `blume build` compiles your docs to plain HTML, CSS, and a local search index in `dist/`. There's no server to run — point any static host at the folder.
12
12
 
13
- | Setting | Value |
14
- | ---------------- | ------------- |
15
- | Build command | `blume build` |
16
- | Output directory | `dist` |
17
- | Node version | 22 or newer |
13
+ | Setting | Value |
14
+ | ---------------- | -------------- |
15
+ | Build command | `blume build` |
16
+ | Output directory | `dist` |
17
+ | Node version | 22.12 or newer |
18
18
 
19
19
  These settings work on Vercel, Netlify, Cloudflare Pages, GitHub Pages, Amazon S3 + CloudFront, or any bucket or CDN. Make sure `blume` is a dependency so the host can run the build.
20
20
 
@@ -88,7 +88,7 @@ deployment: {
88
88
  }
89
89
  ```
90
90
 
91
- Choosing an adapter pulls in the matching Astro adapter automatically:
91
+ The `vercel` and `node` adapters ship with Blume — choosing one just works. The `netlify` and `cloudflare` adapters must be installed in your project (e.g. `bun add -d @astrojs/netlify`); the CLI warns you if the package is missing:
92
92
 
93
93
  | Adapter | Package | Use for |
94
94
  | ------------ | --------------------- | ------------------------------------ |
@@ -97,7 +97,7 @@ Choosing an adapter pulls in the matching Astro adapter automatically:
97
97
  | `node` | `@astrojs/node` | Self-hosted Node servers, containers |
98
98
  | `cloudflare` | `@astrojs/cloudflare` | Cloudflare Workers and Pages |
99
99
 
100
- On **Vercel**, **Netlify**, and **Cloudflare Pages**, Blume picks the matching adapter automatically for server output — set `output: "server"` and deploy. Set `adapter` explicitly to override the detected value, or when self-hosting with `node`.
100
+ On **Vercel**, **Netlify**, and **Cloudflare Pages**, Blume picks the matching adapter automatically for server output — set `output: "server"` and deploy (on Netlify and Cloudflare, install the adapter package too). Set `adapter` explicitly to override the detected value, or when self-hosting with `node`.
101
101
 
102
102
  A server build includes everything a static build does, plus any Astro endpoints or middleware you add. The `node` adapter produces a standalone server you can run directly.
103
103
 
@@ -126,7 +126,7 @@ When a feature needs a runtime secret, Blume warns at `blume dev`/`build` if it'
126
126
  | Feature | Variable |
127
127
  | --- | --- |
128
128
  | Ask AI (AI Gateway) | `AI_GATEWAY_API_KEY` (or Vercel OIDC) |
129
- | Ask AI (OpenRouter / OpenAI-compatible) | the provider's `apiKeyEnv` |
129
+ | Ask AI (other providers) | the provider's default key env var (`OPENROUTER_API_KEY`, `LLMGATEWAY_API_KEY`, `INKEEP_API_KEY`), or your configured `apiKeyEnv` |
130
130
  | Mixedbread search | `MIXEDBREAD_API_KEY` |
131
131
 
132
132
  Set them in `.env.local` for local dev and in your host's environment for production. Build-time secrets for search-index sync (Algolia, Orama Cloud, Typesense) are warned about separately during the sync step.
package/docs/03-faq.mdx CHANGED
@@ -79,26 +79,26 @@ Once the opening `:::note` fence is joined to the prose, it's no longer a direct
79
79
 
80
80
  ### Why it happens
81
81
 
82
- This is a bug in oxfmt's Markdown formatter (inherited from Prettier's Markdown printer — see [prettier/prettier#19040](https://github.com/prettier/prettier/pull/19040)). When it wraps prose, it treats the `:::` fence lines as ordinary text and joins them with the adjacent line, breaking the directive. It affects every container directive — `:::note`, `:::tip`, `:::warning`, `:::danger`, `:::success`.
82
+ This is a bug in oxfmt's Markdown formatter (inherited from Prettier's Markdown printer — see [prettier/prettier#19040](https://github.com/prettier/prettier/pull/19040)). When it wraps prose, it treats the `:::` fence lines as ordinary text and joins them with the adjacent line, breaking the directive. It affects every container directive type — `:::note`, `:::tip`, `:::info`, `:::warning`, `:::danger`, `:::success`.
83
83
 
84
84
  We reported it upstream in [oxc-project/oxc#24096](https://github.com/oxc-project/oxc/issues/24096); until it's fixed there, the patch below is the workaround.
85
85
 
86
86
  ### The fix
87
87
 
88
- Patch oxfmt so it preserves the line break that sits directly against a `:::` fence. Blume ships exactly this patch in its own repo, and you can apply the same one in any project.
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.57.0.patch`:
90
+ 1. Save the patch as `patches/oxfmt@0.58.0.patch`:
91
91
 
92
- ```diff patches/oxfmt@0.57.0.patch
93
- diff --git a/dist/markdown-B5hFVJKQ.js b/dist/markdown-B5hFVJKQ.js
94
- index 58322b247b263f87975c2a97eecfe0b97d7143c4..0f39f1901603026a391c6d343cb838dfaccbfd03 100644
95
- --- a/dist/markdown-B5hFVJKQ.js
96
- +++ b/dist/markdown-B5hFVJKQ.js
97
- @@ -1141,7 +1141,14 @@ function Yf(e, r, t) {
98
- case "sentence": return Yi(e, t);
99
- case "word": return Li(e);
92
+ ```diff patches/oxfmt@0.58.0.patch
93
+ diff --git a/dist/markdown-BjyDFyaO.js b/dist/markdown-BjyDFyaO.js
94
+ index 1905aa7563e612808426e26f1ed28bebbef3456f..42bb9f66829068676c5697fc9fc39279bf937f3e 100644
95
+ --- a/dist/markdown-BjyDFyaO.js
96
+ +++ b/dist/markdown-BjyDFyaO.js
97
+ @@ -4830,7 +4830,43 @@ function lu(e, t, r) {
98
+ case "sentence": return Mh(e, r);
99
+ case "word": return t.parser !== "mdx" ? Vh(e, t) : Gh(e);
100
100
  case "whitespace": {
101
- - let { next: i } = e, u = i && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/u.test(i.value) ? "never" : r.proseWrap;
101
+ - let { next: i } = e, u = i && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/.test(i.value) && !ME(e) && !(t.proseWrap === "preserve" && zE(e)) ? "never" : t.proseWrap;
102
102
  + let { next: i, previous: oxfmtFencePrev } = e;
103
103
  + // Preserve line breaks that sit directly against a `:::` container
104
104
  + // directive fence, so `proseWrap: "never"` keeps the opening/closing
@@ -106,8 +106,37 @@ Patch oxfmt so it preserves the line break that sits directly against a `:::` fe
106
106
  + // breaks the directive). Ordinary prose still wraps per proseWrap.
107
107
  + // See prettier/prettier#19040.
108
108
  + let oxfmtIsFence = (w) => w != null && typeof w.value === "string" && w.value.startsWith(":::");
109
- + let u = oxfmtIsFence(oxfmtFencePrev) || oxfmtIsFence(i) ? "preserve" : i && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/u.test(i.value) ? "never" : r.proseWrap;
110
- return qt(e, n.value, u);
109
+ + // A titled directive (`:::warning[Heads up]`) parses its `[title]` as a
110
+ + // linkReference between two sentence nodes at the paragraph level: the
111
+ + // fence word ends the sentence before the reference, and the body's
112
+ + // leading newline opens the sentence after it. So when this whitespace
113
+ + // starts its sentence, climb to the paragraph and check whether the two
114
+ + // preceding siblings are a (link) reference and a sentence ending in a
115
+ + // `:::` fence word.
116
+ + let oxfmtPrevIsTitledFence = !1;
117
+ + if (oxfmtFencePrev == null && e.index === 0 && e.grandparent != null && Array.isArray(e.grandparent.children)) {
118
+ + let oxfmtSibs = e.grandparent.children, oxfmtSentIdx = oxfmtSibs.indexOf(e.parent);
119
+ + if (oxfmtSentIdx >= 2) {
120
+ + let oxfmtLink = oxfmtSibs[oxfmtSentIdx - 1], oxfmtBefore = oxfmtSibs[oxfmtSentIdx - 2];
121
+ + let oxfmtLastWord = oxfmtBefore && oxfmtBefore.type === "sentence" && Array.isArray(oxfmtBefore.children) ? oxfmtBefore.children[oxfmtBefore.children.length - 1] : null;
122
+ + oxfmtPrevIsTitledFence = oxfmtLink != null && (oxfmtLink.type === "linkReference" || oxfmtLink.type === "link") && oxfmtIsFence(oxfmtLastWord);
123
+ + }
124
+ + }
125
+ + // The plain-markdown parser keeps a titled fence's `[title]` as literal
126
+ + // words, so the whole directive is one sentence. For a newline
127
+ + // whitespace, walk back to the start of its visual line within the
128
+ + // sentence; a line led by a `:::` word is a fence whose break must stay.
129
+ + if (!oxfmtPrevIsTitledFence && e.node.value.includes("\n") && e.parent != null && Array.isArray(e.parent.children)) {
130
+ + let oxfmtLineFirst = null;
131
+ + for (let oxfmtJ = e.index - 1; oxfmtJ >= 0; oxfmtJ--) {
132
+ + let oxfmtSib = e.parent.children[oxfmtJ];
133
+ + if (oxfmtSib.type === "whitespace" && typeof oxfmtSib.value === "string" && oxfmtSib.value.includes("\n")) break;
134
+ + oxfmtLineFirst = oxfmtSib;
135
+ + }
136
+ + oxfmtPrevIsTitledFence = oxfmtIsFence(oxfmtLineFirst);
137
+ + }
138
+ + let u = oxfmtIsFence(oxfmtFencePrev) || oxfmtPrevIsTitledFence || oxfmtIsFence(i) ? "preserve" : i && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/.test(i.value) && !ME(e) && !(t.proseWrap === "preserve" && zE(e)) ? "never" : t.proseWrap;
139
+ return ou(e, n.value, u, !1, t);
111
140
  }
112
141
  case "emphasis": {
113
142
  ```
@@ -117,7 +146,7 @@ Patch oxfmt so it preserves the line break that sits directly against a `:::` fe
117
146
  ```json package.json
118
147
  {
119
148
  "patchedDependencies": {
120
- "oxfmt@0.57.0": "patches/oxfmt@0.57.0.patch"
149
+ "oxfmt@0.58.0": "patches/oxfmt@0.58.0.patch"
121
150
  }
122
151
  }
123
152
  ```
@@ -128,5 +157,6 @@ Patch oxfmt so it preserves the line break that sits directly against a `:::` fe
128
157
  bun install
129
158
  ```
130
159
 
131
- :::warning[Version-pinned] The patch targets oxfmt `0.57.0` — its diff references a file hashed to that build (`dist/markdown-B5hFVJKQ.js`). When you bump oxfmt, regenerate the patch (e.g. `bun patch oxfmt`) or check whether the upstream fix has landed and the patch is no longer needed.
160
+ :::warning[Version-pinned]
161
+ The patch targets a specific oxfmt build — its diff references a file whose name is hashed per release (`dist/markdown-*.js`). When you bump oxfmt, regenerate the patch (e.g. `bun patch oxfmt`) or check whether the upstream fix has landed and the patch is no longer needed.
132
162
  :::
@@ -74,7 +74,7 @@ The module exposes:
74
74
  type: "BlumeDataConfig",
75
75
  required: true,
76
76
  description:
77
- "Resolved site settings: title, description, logo, favicon, appleIcon, banner, theme, site, repoUrl, search, i18n, mcp, og, analytics, feedback, structuredData, codeWrap, and imageZoom.",
77
+ "Resolved site settings: title, description, logo, favicon, appleIcon, banner, theme, site, repoUrl, search, i18n, mcp, ask, og, analytics, feedback, structuredData, toc, codeThemes, codeWrap, and imageZoom.",
78
78
  },
79
79
  navigation: {
80
80
  type: "Navigation",
@@ -18,7 +18,7 @@ npx skills add haydenbleasel/blume
18
18
  `blume-update-docs` keeps your docs in sync with the product they document. On each run — typically from a schedule you configure in your agent runner — it audits recently merged PRs, changelogs, config schemas, and CLI help against the docs content, updates only pages that are factually stale (feature-flagged work is ignored), verifies with `blume build`, and opens or updates a `blume/*` pull request. If nothing drifted, it reports a clean no-op instead of opening a noisy PR.
19
19
 
20
20
  ```bash
21
- npx skills use haydenbleasel/blume@blume-update-docs
21
+ npx skills add haydenbleasel/blume --skill blume-update-docs
22
22
  ```
23
23
 
24
24
  Blume doesn't host the automation — wire the skill into a scheduled task in Claude Code, a Codex or Cursor automation, or plain cron, with permission to read repo history and open PRs. A typical weekly prompt:
@@ -17,23 +17,62 @@ ai: {
17
17
 
18
18
  While enabled, `blume build` writes two files to the root of your site:
19
19
 
20
- - **`/llms.txt`** — a compact index: your site title and description, then a linked list of every page with its summary.
20
+ - **`/llms.txt`** — a compact index: your site title and description, then a linked list of every page with its summary, organized into sections that mirror your sidebar — folders and groups become headings, so an agent sees the docs' structure, not one flat blob.
21
21
  - **`/llms-full.txt`** — the entire corpus: each page's full Markdown body, with its source URL, in one file.
22
22
 
23
23
  Draft pages are excluded. Set [`deployment.site`](/docs/deployment) so the links and source URLs resolve to absolute addresses.
24
24
 
25
+ `llmsTxt` also takes an object form with knobs for what the files include. If your [API reference](/docs/advanced/api-reference) documents a placeholder or example spec, set `openapi: false` to keep its generated pages out of both files:
26
+
27
+ ```ts blume.config.ts lineNumbers
28
+ ai: {
29
+ llmsTxt: {
30
+ enabled: true, // default
31
+ openapi: false, // exclude generated API reference pages
32
+ },
33
+ }
34
+ ```
35
+
36
+ To take full control of either file, add your own `llms.txt` or `llms-full.txt` to your `public/` folder. Like a custom favicon, it's picked up automatically and ships in place of the generated file — override one and Blume still generates the other.
37
+
25
38
  ## Raw Markdown
26
39
 
27
40
  Append `.md` or `.mdx` to any page's URL to fetch its raw Markdown source — perfect for LLMs, coding agents, and "copy as Markdown" workflows. It's available for every page, in dev and production, with no configuration.
28
41
 
29
- | URL | Returns |
30
- | ----------------- | ----------------------- |
31
- | `/quickstart` | The rendered page |
32
- | `/quickstart.md` | The raw Markdown source |
33
- | `/quickstart.mdx` | The raw Markdown source |
42
+ | URL | Returns |
43
+ | ----------------- | ----------------------------------------- |
44
+ | `/quickstart` | The rendered page |
45
+ | `/quickstart.md` | Plain Markdown, with components converted |
46
+ | `/quickstart.mdx` | The raw MDX source, exactly as written |
34
47
 
35
48
  Nested routes work the same way (`/content/syntax.md`), and the home page is served at `/index.md`.
36
49
 
50
+ The `.md` variant _downlevels_ components to plain Markdown for consumers that can't interpret JSX: `<TypeTable>` becomes a Markdown table, `<Callout>` a labeled blockquote, `<Steps>` an ordered list, `<Tabs>` bold-labeled sections, and `<YouTube>` a link. Anything that can't be converted faithfully — a custom component, or a prop computed from an import — is left as-is, and component markup inside fenced code blocks is never touched. The same conversion applies to `llms-full.txt` and the MCP server's `get_page` tool, so every agent-facing surface reads clean Markdown. When you want the untransformed source, use the `.mdx` variant.
51
+
52
+ ### Custom component serializers
53
+
54
+ 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) and its `children` (already downleveled to Markdown), and returns the replacement — or `null` to leave the JSX as-is:
55
+
56
+ ```ts blume.config.ts lineNumbers
57
+ import { defineConfig } from "blume";
58
+ import type { ComponentMarkdown } from "blume";
59
+
60
+ const chart: ComponentMarkdown = ({ props }) =>
61
+ `![${props.title}](/charts/${props.slug}.png)`;
62
+
63
+ export default defineConfig({
64
+ ai: {
65
+ markdownComponents: {
66
+ Chart: chart,
67
+ },
68
+ },
69
+ });
70
+ ```
71
+
72
+ For container components, `childComponents("Name")` extracts direct children by tag — the same way the built-in `<Steps>` serializer collects its `<Step>` items. A same-name entry replaces a built-in serializer, so you can restyle how `<Callout>` downlevels — or return `null` to opt one out entirely.
73
+
74
+ Serializers live in `blume.config.ts`, not `components.tsx`: the config file is executed at build time, while the components file is only statically analyzed (it may import `.astro` files, which can't run outside the site build). Your components themselves stay registered in `components.tsx` exactly as before — `markdownComponents` only adds their agent-facing Markdown form.
75
+
37
76
  ## Copy as Markdown
38
77
 
39
78
  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.
@@ -46,7 +85,7 @@ The **Open in chat** action opens the current page in an AI assistant — v0, Ch
46
85
 
47
86
  Like Copy as Markdown, it needs no setup. The assistant fetches the page over its public URL, so it works as soon as the page is deployed.
48
87
 
49
- To embed a ready-to-copy prompt inline in your content — rather than a whole-page action — use the [Prompt component](/docs/content/components#prompt), which renders a labelled row with a **Copy prompt** button and an optional open-in-Cursor link.
88
+ To embed a ready-to-copy prompt inline in your content — rather than a whole-page action — use the [Prompt component](/docs/content/components#prompt), which renders a labeled row with a **Copy prompt** button and an optional open-in-Cursor link.
50
89
 
51
90
  ## Ask AI
52
91
 
@@ -172,7 +211,7 @@ mcp: {
172
211
  | `name` | title | Server name shown to clients (defaults to title). |
173
212
  | `instructions` | — | Optional system hint passed to connecting agents. |
174
213
 
175
- The server exposes four read-only tools — `search_docs`, `get_page`, `list_pages`, and `get_navigation` — and publishes discovery documents at `/.well-known/mcp.json` and `/.well-known/mcp/server-card.json`. Each page's **Connect to MCP** menu offers copy-and-go install for Claude Code, Cursor, and VS Code (shown once [`deployment.site`](/docs/deployment) is set).
214
+ The server exposes read-only tools — `search_docs`, `get_page`, `list_pages`, and `get_navigation` — and publishes discovery documents at `/.well-known/mcp.json` and `/.well-known/mcp/server-card.json`. Each page's **Connect to MCP** menu offers copy-and-go install for Claude Code, Cursor, VS Code, and Codex (shown once [`deployment.site`](/docs/deployment) is set).
176
215
 
177
216
  `search_docs` runs its own full-text index, so it works regardless of your [search](/docs/configuration/search) provider — and even when search is set to `none`. The MCP server is a separate feature from on-page search.
178
217
 
@@ -196,7 +235,7 @@ claude mcp add --transport http my-docs https://docs.example.com/mcp
196
235
 
197
236
  ## Agent readability
198
237
 
199
- 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. Unlike the features above, it's on by default:
238
+ 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:
200
239
 
201
240
  ```ts blume.config.ts lineNumbers
202
241
  seo: {
@@ -241,4 +280,4 @@ npx skills add haydenbleasel/blume
241
280
 
242
281
  The skill teaches the agent what Blume is and how to scaffold, write, and configure a site, and points it at the full docs bundled in the installed package (`node_modules/blume/docs`).
243
282
 
244
- It's one of the [agent skills Blume ships](/docs/advanced/skills), alongside skills for migrating an existing docs site to Blume and keeping docs in sync with your product from a scheduled agent run.
283
+ It's one of the [agent skills Blume ships](/docs/advanced/skills), alongside a skill for keeping docs in sync with your product from a scheduled agent run.
@@ -154,3 +154,14 @@ blume eject --yes
154
154
  ```
155
155
 
156
156
  Eject is a one-way step: the hidden `.blume/` runtime becomes a normal Astro app you own and can modify directly. The `blume` package stays importable, so you keep its components, theme, and Markdown processors.
157
+
158
+ ### What eject leaves behind
159
+
160
+ After ejecting, your `build` script runs plain `astro build` — the site itself builds the same, but the artifacts `blume build` layered on top are no longer produced. The eject command warns about the ones your config actually uses. To keep them:
161
+
162
+ - **Pagefind search index** — with `search.provider: "pagefind"`, the search UI loads the index from the built site, so search breaks in production until you index it yourself. Install `pagefind` as a devDependency and index after each build: `"build": "astro build && pagefind --site dist"`.
163
+ - **Hosted search sync** — a hosted provider's index is no longer pushed on build; re-upload your search records after each build with the provider's API or CLI.
164
+ - **sitemap.xml** — recreate it with the standard [@astrojs/sitemap](https://docs.astro.build/en/guides/integrations-guide/sitemap/) integration.
165
+ - **robots.txt** — ship your own as `public/robots.txt`.
166
+ - **llms.txt / llms-full.txt and agent-readability.json** — write them by hand (or generate them in a build step of your own) and serve them from `public/`.
167
+ - **Platform redirect files** — `_redirects` and `vercel.json` are no longer emitted for static builds. Your redirects still work as Astro-generated meta-refresh pages, or you can move them into your host's own config.
@@ -189,6 +189,24 @@ content: {
189
189
 
190
190
  Static assets live in `public/` — a file at `public/logo.png` is served at `/logo.png`, so a reference like `![](/images/create.png)` resolves against `public/images/create.png`.
191
191
 
192
+ ## GitHub
193
+
194
+ Point Blume at your repository with `github`. It powers the header [repository link](/docs/content/navigation#repository-link) and the **Edit on GitHub** and **Give feedback** [page actions](/docs/content/navigation#page-actions):
195
+
196
+ ```ts blume.config.ts lineNumbers
197
+ github: {
198
+ owner: "acme",
199
+ repo: "docs",
200
+ }
201
+ ```
202
+
203
+ | Option | Default | Description |
204
+ | --- | --- | --- |
205
+ | `owner` | — | GitHub account or organization that owns the repository. |
206
+ | `repo` | — | Repository name. |
207
+ | `branch` | `"main"` | Branch that edit links point at. |
208
+ | `dir` | — | Path from the repo root to the project root (for monorepos). |
209
+
192
210
  ## Last modified
193
211
 
194
212
  Show a "Last updated on …" line at the bottom of each page. Off by default; set `lastModified` to `true` to derive each page's date from its git history:
@@ -248,7 +266,13 @@ The on-this-page outline is on by default and lists `H2`–`H3` headings. Turn i
248
266
  ```ts blume.config.ts
249
267
  export default defineConfig({
250
268
  toc: false, // hide it everywhere
251
- // …or narrow the range:
269
+ });
270
+ ```
271
+
272
+ Or narrow the heading range instead:
273
+
274
+ ```ts blume.config.ts
275
+ export default defineConfig({
252
276
  toc: { minHeadingLevel: 2, maxHeadingLevel: 4 },
253
277
  });
254
278
  ```
@@ -261,8 +285,8 @@ Each of these has its own guide. The config field is the entry point:
261
285
  | --- | --- | --- |
262
286
  | `theme` | Accent color, corner radius, fonts, light/dark mode | [Theming](/docs/configuration/theming) |
263
287
  | `navigation` | Explicit sidebar and header tabs | [Navigation](/docs/content/navigation) |
264
- | `search` | Provider (Orama or Pagefind) and indexing | [Search](/docs/configuration/search) |
265
- | `markdown` | Opt-in Markdown features like math | [Syntax](/docs/content/syntax) |
288
+ | `search` | Provider (Orama, Pagefind, Algolia, and more) and indexing | [Search](/docs/configuration/search) |
289
+ | `markdown` | Markdown rendering options code blocks, heading anchors, image zoom | [Syntax](/docs/content/syntax) |
266
290
  | `ai` | `llms.txt`, Ask AI, and the MCP server | [AI](/docs/configuration/ai) |
267
291
  | `mcp` | Hosted MCP server for coding agents | [AI](/docs/configuration/ai#mcp-server) |
268
292
  | `analytics` | Vercel, PostHog, and custom scripts | [Analytics](/docs/configuration/analytics) |
@@ -5,7 +5,7 @@ sidebar:
5
5
  label: SEO
6
6
  ---
7
7
 
8
- Blume handles the discoverability layer for you: page metadata, social share images, feeds, and structured data. The three configurable features live under the `seo` key in `blume.config.ts`; metadata is driven by your content.
8
+ Blume handles the discoverability layer for you: page metadata, social share images, feeds, and structured data. The configurable features live under the `seo` key in `blume.config.ts`; metadata is driven by your content.
9
9
 
10
10
  ```ts blume.config.ts lineNumbers
11
11
  seo: {
@@ -225,6 +225,6 @@ seo: {
225
225
  }}
226
226
  />
227
227
 
228
- Content signals express a preference, not access control: they tell well-behaved crawlers how you'd like your content used, and it's on the crawler to honour them.
228
+ Content signals express a preference, not access control: they tell well-behaved crawlers how you'd like your content used, and it's on the crawler to honor them.
229
229
 
230
230
  Ship your own `public/robots.txt` to take over.
@@ -428,7 +428,7 @@ A single row with a label and a copy button. The `description` (Markdown) is the
428
428
 
429
429
  ## Visibility
430
430
 
431
- Show or hide content by audience. `for="web"` renders only on the site; `for="agents"` targets the generated Markdown (`llms.txt`) that AI agents read.
431
+ Show or hide content by audience. `for="web"` renders only on the site; `for="agents"` targets the agent-facing Markdown that AI agents read (`llms-full.txt` and each page's `.md` mirror).
432
432
 
433
433
  <Visibility for="web">
434
434
  This note appears on the website but is omitted from the agent-facing
@@ -108,7 +108,7 @@ When i18n is on, a language switcher appears in the header automatically, genera
108
108
 
109
109
  Blume ships built-in translations for its own interface chrome — “On this page”, “Search”, “Edit on GitHub”, and the rest — so a locale with a built-in pack gets translated UI out of the box. **You only translate your content.**
110
110
 
111
- Packs ship for 36 languages — Arabic, Bengali, Bulgarian, Catalan, Chinese (Simplified and Traditional), Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Norwegian, Persian, Polish, Portuguese (and Brazilian Portuguese), Romanian, Russian, Serbian, Slovak, Spanish, Swedish, Thai, Turkish, Ukrainian, and Vietnamese. They're community-maintained — open a PR to add a locale or sharpen a translation.
111
+ Packs ship for over 30 languages — Arabic, Bengali, Bulgarian, Catalan, Chinese (Simplified and Traditional), Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Norwegian, Persian, Polish, Portuguese (and Brazilian Portuguese), Romanian, Russian, Serbian, Slovak, Spanish, Swedish, Thai, Turkish, Ukrainian, and Vietnamese. They're community-maintained — open a PR to add a locale or sharpen a translation.
112
112
 
113
113
  Missing or unshipped strings fall back to the default locale, then to English. To override a string or supply your own language, set `i18n.ui`, keyed by locale:
114
114
 
@@ -147,7 +147,7 @@ navigation: {
147
147
  }
148
148
  ```
149
149
 
150
- Each link takes a `label`, an `href`, and an optional `icon` (a [built-in icon](/docs/configuration/customization) name, image path/URL, or inline SVG — the same as anywhere else). An `href` may point anywhere: an external URL opens in a new tab, while an internal route (`/contact`) is validated against your pages at build time, warning you if nothing matches.
150
+ Each link takes a `label`, an `href`, and an optional `icon` (a [built-in icon](/docs/content/components#icon) name, image path/URL, or inline SVG — the same as anywhere else). An `href` may point anywhere: an external URL opens in a new tab, while an internal route (`/contact`) is validated against your pages at build time, warning you if nothing matches.
151
151
 
152
152
  ## Explicit sidebar
153
153
 
@@ -196,10 +196,10 @@ A right-rail outline is generated automatically from each page's `##` and `###`
196
196
 
197
197
  Below the table of contents, every page shows a set of quick actions:
198
198
 
199
- - **Edit this page on GitHub** — links straight to the source file. Appears once you set [`github`](/docs/configuration) in your config.
199
+ - **Edit on GitHub** — links straight to the source file. Appears once you set [`github`](/docs/configuration) in your config.
200
200
  - **Scroll to top** — smoothly returns to the top of long pages.
201
201
  - **Give feedback** — opens a prefilled GitHub issue with an optional reaction and note (also requires `github`).
202
202
 
203
- Three more hand the page to AI tools — **Copy as Markdown**, **Open in chat**, and **Ask AI about this page** — covered in [AI](/docs/configuration/ai#copy-as-markdown).
203
+ Others hand the page to AI tools — **Copy as Markdown** and **Open in chat** — covered in [AI](/docs/configuration/ai#copy-as-markdown).
204
204
 
205
205
  With [`export`](/docs/configuration/export) on, an **Export** action also lets readers download the page as a PDF or EPUB.
@@ -159,7 +159,7 @@ Two flags control how remote content is fetched and what's included:
159
159
  blume dev --preview # author workflow: see drafts live
160
160
  blume build --preview # render a full preview build
161
161
  blume sync # refresh remote content now
162
- blume sync --force # ...ignoring any cached snapshot
162
+ blume sync --force # ...ignoring any cached snapshot
163
163
  ```
164
164
 
165
165
  ## Custom sources
@@ -345,11 +345,13 @@ Your docs built successfully and are ready to deploy.
345
345
 
346
346
  Flag something that needs care to avoid a mistake or surprising behavior.
347
347
 
348
- :::warning[Heads up] Switching to `output: "server"` requires an adapter before you can deploy.
348
+ :::warning[Heads up]
349
+ Switching to `output: "server"` requires an adapter before you can deploy.
349
350
  :::
350
351
 
351
352
  ```md
352
- :::warning[Heads up] Switching to `output: "server"` requires an adapter before you can deploy.
353
+ :::warning[Heads up]
354
+ Switching to `output: "server"` requires an adapter before you can deploy.
353
355
  :::
354
356
  ```
355
357
 
package/docs/index.mdx CHANGED
@@ -19,9 +19,9 @@ Drop Markdown or MDX into a folder, run `blume dev`, and get a production-grade
19
19
 
20
20
  ## Why Blume exists
21
21
 
22
- Docs should be fast, AI-ready and require zero configuration — down to not needing a starter template at all. Some docs tools hand you an entire codebase to maintain before you've written a word. Others build the template around your content, but lock you in to their managed service.
22
+ Docs should be fast, AI-ready, and zero-config — down to not needing a starter template at all. Some docs tools hand you an entire codebase to maintain before you've written a word. Others build the template around your content, but lock you in to their managed service.
23
23
 
24
- Blume takes the best of both worlds. The framework is the template, so the only thing you ever touch is your content. When you want to customize, you can start replacing the built-in components, modifying the single configuration file or even ejecting if you want the Astro site directly.
24
+ Blume takes the best of both worlds. The framework is the template, so the only thing you ever touch is your content. When you want to customize, you can start by replacing the built-in components, modifying the single configuration file, or even ejecting if you want the Astro site directly.
25
25
 
26
26
  ## What makes Blume different
27
27
 
@@ -11,7 +11,7 @@ blume <command> [options]
11
11
 
12
12
  | Command | Description |
13
13
  | ------------------ | ------------------------------------------------ |
14
- | `blume init` | Scaffold a minimal project. |
14
+ | `blume init [dir]` | Scaffold a project (interactive by default). |
15
15
  | `blume dev` | Start the dev server with hot reload. |
16
16
  | `blume build` | Build the static (or server) site. |
17
17
  | `blume preview` | Preview the last build. |
@@ -24,7 +24,9 @@ blume <command> [options]
24
24
 
25
25
  ## Common flags
26
26
 
27
- - `blume init --content-dir <dir> --yes` set the content folder (default `docs`) and skip prompts.
27
+ - `blume init` in a terminal, walks you through a few questions (where to create the project, site name, template, content sources); each flag below pre-answers its question.
28
+ - `blume init --yes` — skip the prompts and scaffold with defaults (also the behavior in CI or when stdin isn't a terminal).
29
+ - `blume init --content-dir <dir>` — set the content folder (default `docs`).
28
30
  - `blume init --template docs|api|sdk|changelog` — scaffold from a starter (API reference, SDK, or changelog instead of the plain docs seed).
29
31
  - `blume init --package-manager npm|pnpm|yarn|bun` — tailor the printed next-steps to your package manager.
30
32
  - `blume init --eject` — scaffold, then eject to a standalone Astro project (falls back to guiding you through `blume eject` when dependencies aren't installed yet).
@@ -53,9 +55,9 @@ blume <command> [options]
53
55
  `blume dev` serves a live Astro server rooted at the generated `.blume/` runtime and regenerates it on every change. `blume build` and `blume check` regenerate the _same_ `.blume/`, so running either while the dev server is live would corrupt it — both refuse with an error and exit non-zero:
54
56
 
55
57
  ```
56
- A `blume dev` server is running against .blume; building would corrupt it.
57
- Stop the dev server, or re-run with --isolated to build/verify against
58
- .blume-verify without touching it.
58
+ A `blume dev` server is running at http://localhost:3000; building would
59
+ corrupt its .blume runtime. Reuse that server, stop it first, or re-run with
60
+ --isolated to build/verify against .blume-verify without touching it.
59
61
  ```
60
62
 
61
63
  The `--isolated` flag is the escape hatch. It relocates the entire generated runtime (and, for `build`, its output `dist/`) to a sibling `.blume-verify/` directory, so the verification never writes anything the dev server — or your real `dist/` — depends on:
@@ -66,7 +68,7 @@ blume check --isolated # fast: type-check the .astro/config changes
66
68
  blume build --isolated # thorough: full production render into .blume-verify/dist
67
69
  ```
68
70
 
69
- `check --isolated` is the quick path (Astro type + template diagnostics, no `dist/`); `build --isolated` is the heavier one that also catches runtime render errors. Isolated builds skip the deploy post-steps (search index, hosted-provider sync, `llms.txt`, sitemap/robots, redirects) — a verify only needs to confirm the site compiles and renders, not publish it. Blume adds `.blume-verify/` to your `.gitignore` automatically.
71
+ `check --isolated` is the quick path (Astro type + template diagnostics, no `dist/`); `build --isolated` is the heavier one that also catches runtime render errors. Isolated builds skip the deploy post-steps (search index, hosted-provider sync, `llms.txt`, sitemap/robots, redirects) — a verify only needs to confirm the site compiles and renders, not publish it. `--analyze` and the `--budget-js`/`--budget-css` gates still run, measured against the isolated output. Blume adds `.blume-verify/` to your `.gitignore` automatically.
70
72
 
71
73
  This is especially useful when a coding agent needs to verify changes while you keep the dev server open. To make plain `blume build`/`blume check` isolate without the flag — for example in an agent's shell — set `BLUME_RUNTIME_DIR` to the runtime directory to use:
72
74