blume 1.0.2 → 1.0.3
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 +8 -0
- package/dist/cli/index.js +59 -40
- package/dist/cli/index.js.map +11 -11
- package/dist/types/core/config-input.d.ts +17 -14
- package/dist/types/core/config.d.ts +3 -3
- package/dist/types/core/schema.d.ts +82 -34
- package/docs/advanced/custom-pages.mdx +2 -0
- package/docs/configuration/ai.mdx +6 -4
- package/docs/configuration/index.mdx +6 -8
- package/docs/content/components.mdx +25 -3
- package/package.json +1 -1
- package/src/ai/agent-readability.ts +3 -3
- package/src/ai/mcp/data.ts +2 -2
- package/src/astro/generate.ts +15 -7
- package/src/astro/templates.ts +48 -20
- package/src/blume-modules.d.ts +6 -0
- package/src/components/layout/Header.astro +15 -2
- package/src/components/layout/PageLayout.astro +7 -3
- package/src/components/layout/ReferenceLayout.astro +1 -0
- package/src/components/layout/RootLayout.astro +6 -3
- package/src/core/config-input.ts +18 -19
- package/src/core/config.ts +3 -3
- package/src/core/schema.ts +15 -14
- package/src/core/server-features.ts +1 -1
- package/src/deploy/adapter-output.ts +11 -1
- package/src/registry/eject.ts +11 -5
package/src/blume-modules.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ declare module "blume:search-client" {
|
|
|
15
15
|
export const createSearch: () => Fn | Promise<Fn>;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
declare module "blume:ask" {
|
|
19
|
+
/** The generated Ask trigger (see `askComponentTemplate`); empty when Ask is off. */
|
|
20
|
+
const Ask: (props: Record<string, unknown>) => unknown;
|
|
21
|
+
export default Ask;
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
declare module "blume:data" {
|
|
19
25
|
/** The generated per-project data snapshot (see `core/data.ts`). */
|
|
20
26
|
// biome-ignore lint/style/useImportType: ambient module must stay a global script
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import Ask from "blume:ask";
|
|
3
|
+
import data from "blume:data";
|
|
2
4
|
import { withBase } from "../islands/base-path.ts";
|
|
3
5
|
import type { ComponentOverride } from "../../core/define-components.ts";
|
|
4
6
|
import { EN_UI } from "../../core/i18n-ui.ts";
|
|
@@ -26,7 +28,14 @@ interface Props {
|
|
|
26
28
|
navigation: Navigation;
|
|
27
29
|
route: string;
|
|
28
30
|
searchEnabled: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether the search modal offers an "Ask AI" hand-off. Defaults to whether
|
|
33
|
+
* Ask AI is configured, so no page has to pass it; a layout can still opt a
|
|
34
|
+
* shell out explicitly.
|
|
35
|
+
*/
|
|
29
36
|
askEnabled?: boolean;
|
|
37
|
+
/** Localized Ask AI strings for the active locale. */
|
|
38
|
+
askStrings?: UIStrings["ask"];
|
|
30
39
|
// The mobile menu button toggles the docs sidebar drawer; custom pages
|
|
31
40
|
// without a sidebar (e.g. a landing page) pass `false` to hide it.
|
|
32
41
|
hasSidebar?: boolean;
|
|
@@ -57,7 +66,11 @@ const {
|
|
|
57
66
|
navigation,
|
|
58
67
|
route,
|
|
59
68
|
searchEnabled,
|
|
60
|
-
|
|
69
|
+
// `config.ask` is null whenever Ask AI is off, so the header is the one place
|
|
70
|
+
// that has to know — the Ask trigger below and the search modal's hand-off to
|
|
71
|
+
// it both switch on this, and every page gets both for free.
|
|
72
|
+
askEnabled = Boolean(data.config.ask),
|
|
73
|
+
askStrings,
|
|
61
74
|
hasSidebar = true,
|
|
62
75
|
hasDrawer = true,
|
|
63
76
|
searchStrings,
|
|
@@ -208,7 +221,7 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
|
|
|
208
221
|
<span class="inline-flex dark:hidden"><Icon name="sun" size={18} /></span>
|
|
209
222
|
<span class="hidden dark:inline-flex"><Icon name="moon" size={18} /></span>
|
|
210
223
|
</button>
|
|
211
|
-
<
|
|
224
|
+
{askEnabled && <Ask strings={askStrings} />}
|
|
212
225
|
</div>
|
|
213
226
|
</header>
|
|
214
227
|
<script is:inline set:html={clickScript} />
|
|
@@ -58,6 +58,11 @@ interface Props {
|
|
|
58
58
|
themeMode: "system" | "light" | "dark";
|
|
59
59
|
fontCssVars?: string[];
|
|
60
60
|
searchEnabled: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Opt this page out of the header's Ask AI trigger. Defaults to whether Ask
|
|
63
|
+
* AI is configured, so a custom page gets the same trigger the docs pages have
|
|
64
|
+
* without wiring anything up.
|
|
65
|
+
*/
|
|
61
66
|
askEnabled?: boolean;
|
|
62
67
|
/**
|
|
63
68
|
* Absolute site URL (`data.config.site`). When set, `canonical` and the
|
|
@@ -229,6 +234,7 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
229
234
|
<Banner banner={banner} strings={strings.banner} />
|
|
230
235
|
<Header
|
|
231
236
|
askEnabled={askEnabled}
|
|
237
|
+
askStrings={strings.ask}
|
|
232
238
|
hasSidebar={false}
|
|
233
239
|
localeSwitch={localeSwitch}
|
|
234
240
|
logo={logo}
|
|
@@ -240,9 +246,7 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
240
246
|
searchStrings={strings.search}
|
|
241
247
|
site={site}
|
|
242
248
|
switcherStrings={strings.languageSwitcher}
|
|
243
|
-
|
|
244
|
-
<slot name="ask" slot="ask" />
|
|
245
|
-
</Header>
|
|
249
|
+
/>
|
|
246
250
|
<main id="blume-content"><slot /></main>
|
|
247
251
|
<slot name="footer" />
|
|
248
252
|
{
|
|
@@ -100,6 +100,10 @@ interface Props {
|
|
|
100
100
|
x?: { creator?: string; handle?: string };
|
|
101
101
|
canonical?: string | null;
|
|
102
102
|
editUrl?: string | null;
|
|
103
|
+
/**
|
|
104
|
+
* Opt this page out of the header's Ask AI trigger. Defaults to whether Ask
|
|
105
|
+
* AI is configured, so pages never wire the trigger up themselves.
|
|
106
|
+
*/
|
|
103
107
|
askEnabled?: boolean;
|
|
104
108
|
/** Show the "Was this page helpful?" rating below the content. */
|
|
105
109
|
feedback?: boolean;
|
|
@@ -430,6 +434,7 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
430
434
|
<Banner banner={banner} strings={strings.banner} />
|
|
431
435
|
<HeaderSlot
|
|
432
436
|
askEnabled={askEnabled}
|
|
437
|
+
askStrings={strings.ask}
|
|
433
438
|
layout={layout}
|
|
434
439
|
localeSwitch={localeSwitch}
|
|
435
440
|
logo={logo}
|
|
@@ -441,9 +446,7 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
441
446
|
searchStrings={strings.search}
|
|
442
447
|
site={site}
|
|
443
448
|
switcherStrings={strings.languageSwitcher}
|
|
444
|
-
|
|
445
|
-
<slot name="ask" slot="ask" />
|
|
446
|
-
</HeaderSlot>
|
|
449
|
+
/>
|
|
447
450
|
<div
|
|
448
451
|
class:list={["mx-auto grid grid-cols-1 items-start", gridClass]}
|
|
449
452
|
data-blume-doc-grid
|
package/src/core/config-input.ts
CHANGED
|
@@ -525,7 +525,22 @@ export interface LlmsTxtConfig {
|
|
|
525
525
|
openapi?: boolean;
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
-
/**
|
|
528
|
+
/** Expose the docs as an MCP server for connecting agents. */
|
|
529
|
+
export interface McpConfig {
|
|
530
|
+
/** Turn the MCP server on. Defaults to `false`. */
|
|
531
|
+
enabled?: boolean;
|
|
532
|
+
/** Optional system hint passed to connecting agents. */
|
|
533
|
+
instructions?: string;
|
|
534
|
+
/** Server name shown to clients; defaults to the site title. */
|
|
535
|
+
name?: string;
|
|
536
|
+
/** Route the server mounts at. Defaults to `/mcp`. */
|
|
537
|
+
route?: string;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* AI-facing features: the Ask AI assistant, an `llms.txt` manifest, and the
|
|
542
|
+
* hosted MCP server.
|
|
543
|
+
*/
|
|
529
544
|
export interface AiConfig {
|
|
530
545
|
/** The Ask AI chat assistant. */
|
|
531
546
|
ask?: AskConfig;
|
|
@@ -553,6 +568,8 @@ export interface AiConfig {
|
|
|
553
568
|
* ```
|
|
554
569
|
*/
|
|
555
570
|
markdownComponents?: Record<string, ComponentMarkdown>;
|
|
571
|
+
/** Expose the docs as an MCP server for agents. */
|
|
572
|
+
mcp?: McpConfig;
|
|
556
573
|
}
|
|
557
574
|
|
|
558
575
|
// ---------------------------------------------------------------------------
|
|
@@ -586,22 +603,6 @@ export interface AnalyticsConfig {
|
|
|
586
603
|
vercel?: boolean;
|
|
587
604
|
}
|
|
588
605
|
|
|
589
|
-
// ---------------------------------------------------------------------------
|
|
590
|
-
// MCP
|
|
591
|
-
// ---------------------------------------------------------------------------
|
|
592
|
-
|
|
593
|
-
/** Expose the docs as an MCP server for connecting agents. */
|
|
594
|
-
export interface McpConfig {
|
|
595
|
-
/** Turn the MCP server on. Defaults to `false`. */
|
|
596
|
-
enabled?: boolean;
|
|
597
|
-
/** Optional system hint passed to connecting agents. */
|
|
598
|
-
instructions?: string;
|
|
599
|
-
/** Server name shown to clients; defaults to the site title. */
|
|
600
|
-
name?: string;
|
|
601
|
-
/** Route the server mounts at. Defaults to `/mcp`. */
|
|
602
|
-
route?: string;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
606
|
// ---------------------------------------------------------------------------
|
|
606
607
|
// i18n
|
|
607
608
|
// ---------------------------------------------------------------------------
|
|
@@ -979,8 +980,6 @@ export interface BlumeConfig {
|
|
|
979
980
|
logo?: LogoConfig;
|
|
980
981
|
/** Markdown / MDX rendering behavior. */
|
|
981
982
|
markdown?: MarkdownConfig;
|
|
982
|
-
/** Expose the docs as an MCP server for agents. */
|
|
983
|
-
mcp?: McpConfig;
|
|
984
983
|
/** Header, sidebar, tabs, and switchers. */
|
|
985
984
|
navigation?: NavigationConfig;
|
|
986
985
|
/** Native OpenAPI reference. */
|
package/src/core/config.ts
CHANGED
|
@@ -74,9 +74,9 @@ import type { Diagnostic } from "./types.ts";
|
|
|
74
74
|
* `algolia`, `typesense`, `orama-cloud`, `mixedbread`, or `none`) plus its
|
|
75
75
|
* credential block.
|
|
76
76
|
* - `ai` — `ask` (the Ask AI chat endpoint and its provider/model), `llmsTxt`
|
|
77
|
-
* (emit `llms.txt`),
|
|
78
|
-
*
|
|
79
|
-
* -
|
|
77
|
+
* (emit `llms.txt`), `mcp` (expose the docs as an MCP server for connecting
|
|
78
|
+
* agents), and `markdownComponents` (Markdown serializers for custom
|
|
79
|
+
* components in agent-facing output).
|
|
80
80
|
*
|
|
81
81
|
* **SEO, feeds & analytics**
|
|
82
82
|
* - `seo` — `og` images, `sitemap`, `robots`, `rss` feeds, `structuredData`
|
package/src/core/schema.ts
CHANGED
|
@@ -550,6 +550,19 @@ export const askAiProviders = [
|
|
|
550
550
|
"openai-compatible",
|
|
551
551
|
] as const;
|
|
552
552
|
|
|
553
|
+
const mcpConfigSchema = z.strictObject({
|
|
554
|
+
enabled: z.boolean().default(false),
|
|
555
|
+
/** Optional system hint passed to connecting agents. */
|
|
556
|
+
instructions: z.string().optional(),
|
|
557
|
+
/** Server name shown to clients; defaults to the site title. */
|
|
558
|
+
name: z.string().optional(),
|
|
559
|
+
/**
|
|
560
|
+
* Normalized like `openapi.route`: a slash-less value would otherwise be
|
|
561
|
+
* string-concatenated onto the site origin (`https://acme.comdocs-mcp`).
|
|
562
|
+
*/
|
|
563
|
+
route: z.string().default("/mcp").transform(normalizeRoute),
|
|
564
|
+
});
|
|
565
|
+
|
|
553
566
|
const aiConfigSchema = z.strictObject({
|
|
554
567
|
ask: z
|
|
555
568
|
.strictObject({
|
|
@@ -620,6 +633,8 @@ const aiConfigSchema = z.strictObject({
|
|
|
620
633
|
})
|
|
621
634
|
)
|
|
622
635
|
.default({}),
|
|
636
|
+
/** Expose the docs as an MCP server for connecting agents. */
|
|
637
|
+
mcp: mcpConfigSchema.default({}),
|
|
623
638
|
});
|
|
624
639
|
|
|
625
640
|
/**
|
|
@@ -679,19 +694,6 @@ const exportConfigSchema = z
|
|
|
679
694
|
typeof value === "boolean" ? { epub: value, pdf: value } : value
|
|
680
695
|
);
|
|
681
696
|
|
|
682
|
-
const mcpConfigSchema = z.strictObject({
|
|
683
|
-
enabled: z.boolean().default(false),
|
|
684
|
-
/** Optional system hint passed to connecting agents. */
|
|
685
|
-
instructions: z.string().optional(),
|
|
686
|
-
/** Server name shown to clients; defaults to the site title. */
|
|
687
|
-
name: z.string().optional(),
|
|
688
|
-
/**
|
|
689
|
-
* Normalized like `openapi.route`: a slash-less value would otherwise be
|
|
690
|
-
* string-concatenated onto the site origin (`https://acme.comdocs-mcp`).
|
|
691
|
-
*/
|
|
692
|
-
route: z.string().default("/mcp").transform(normalizeRoute),
|
|
693
|
-
});
|
|
694
|
-
|
|
695
697
|
/** A configured locale: ISO-ish code plus display metadata for the switcher. */
|
|
696
698
|
const localeSchema = z.strictObject({
|
|
697
699
|
code: z.string().min(1),
|
|
@@ -1088,7 +1090,6 @@ export const blumeConfigSchema = z.strictObject({
|
|
|
1088
1090
|
lastModified: lastModifiedConfigSchema.default(false),
|
|
1089
1091
|
logo: logoConfigSchema.optional(),
|
|
1090
1092
|
markdown: markdownConfigSchema.default({}),
|
|
1091
|
-
mcp: mcpConfigSchema.default({}),
|
|
1092
1093
|
navigation: navigationConfigSchema.default({}),
|
|
1093
1094
|
openapi: openapiConfigSchema.default({}),
|
|
1094
1095
|
react: reactConfigSchema.default({}),
|
|
@@ -11,7 +11,7 @@ export const serverFeatures = (config: ResolvedConfig): string[] => {
|
|
|
11
11
|
features.push("Ask AI");
|
|
12
12
|
}
|
|
13
13
|
// The hosted MCP server is a live JSON-RPC endpoint, so it needs a runtime.
|
|
14
|
-
if (config.mcp.enabled) {
|
|
14
|
+
if (config.ai.mcp.enabled) {
|
|
15
15
|
features.push("MCP server");
|
|
16
16
|
}
|
|
17
17
|
// Mixedbread (and any future provider) that proxies queries through a secret
|
|
@@ -83,7 +83,17 @@ export const surfaceAdapterOutput = async (
|
|
|
83
83
|
}
|
|
84
84
|
await mkdir(dirname(to), { recursive: true });
|
|
85
85
|
await rm(to, { force: true, recursive: true });
|
|
86
|
-
|
|
86
|
+
// `verbatimSymlinks` keeps each symlink's target text as written. Without it,
|
|
87
|
+
// `cp` resolves every relative target against the *source*, rewriting it to an
|
|
88
|
+
// absolute path under `.blume` — which this function then deletes. Adapters
|
|
89
|
+
// that trace dependencies into their function bundle link one package to
|
|
90
|
+
// another that way (under an isolated linker — Bun's `isolated` mode, pnpm —
|
|
91
|
+
// that is every external dependency the function imports), so the resolved
|
|
92
|
+
// links leave the deployed function dying on its first external import with
|
|
93
|
+
// ERR_MODULE_NOT_FOUND. Verbatim, the links stay relative and internal to the
|
|
94
|
+
// bundle, surviving both this move and the platform's own (Vercel mounts the
|
|
95
|
+
// bundle at `/var/task`).
|
|
96
|
+
await cp(from, to, { recursive: true, verbatimSymlinks: true });
|
|
87
97
|
await rm(from, { force: true, recursive: true });
|
|
88
98
|
// The `.gitignore` entry is the surfaced top-level dir (`.vercel`/`.netlify`),
|
|
89
99
|
// never the moved sub-path — the platform's own state (`.vercel/project.json`,
|
package/src/registry/eject.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { discoverIslands } from "../astro/islands.ts";
|
|
|
20
20
|
import { customOgRoutes, discoverPages, routeIsTaken } from "../astro/pages.ts";
|
|
21
21
|
import {
|
|
22
22
|
askEndpointTemplate,
|
|
23
|
+
askComponentTemplate,
|
|
23
24
|
astroConfigTemplate,
|
|
24
25
|
catchAllPageTemplate,
|
|
25
26
|
changelogIndexTemplate,
|
|
@@ -135,8 +136,8 @@ const hostsMcp = (
|
|
|
135
136
|
project: BlumeProject,
|
|
136
137
|
userPages: { pattern: string }[]
|
|
137
138
|
): boolean =>
|
|
138
|
-
project.config.mcp.enabled &&
|
|
139
|
-
!routeIsTaken(userPages, project.graph.pages, project.config.mcp.route);
|
|
139
|
+
project.config.ai.mcp.enabled &&
|
|
140
|
+
!routeIsTaken(userPages, project.graph.pages, project.config.ai.mcp.route);
|
|
140
141
|
|
|
141
142
|
/**
|
|
142
143
|
* The `.well-known` MCP discovery routes, injected as prerendered pages
|
|
@@ -174,7 +175,7 @@ const mcpFiles = async (
|
|
|
174
175
|
if (!hostsMcp(project, userPages)) {
|
|
175
176
|
return [];
|
|
176
177
|
}
|
|
177
|
-
const { route } = project.config.mcp;
|
|
178
|
+
const { route } = project.config.ai.mcp;
|
|
178
179
|
const data = await buildMcpData(project);
|
|
179
180
|
const discoveryInput = {
|
|
180
181
|
base: data.base,
|
|
@@ -356,6 +357,7 @@ export const eject = async (
|
|
|
356
357
|
}[] = [
|
|
357
358
|
{
|
|
358
359
|
content: astroConfigTemplate({
|
|
360
|
+
askPath: "./src/generated/Ask.astro",
|
|
359
361
|
config,
|
|
360
362
|
contentRoutes: project.manifest.routes.map((route) => route.path),
|
|
361
363
|
context: relContext,
|
|
@@ -390,7 +392,6 @@ export const eject = async (
|
|
|
390
392
|
},
|
|
391
393
|
{
|
|
392
394
|
content: catchAllPageTemplate({
|
|
393
|
-
askEnabled,
|
|
394
395
|
exportEpub,
|
|
395
396
|
exportPdf,
|
|
396
397
|
mathEnabled: usesMath,
|
|
@@ -441,6 +442,12 @@ export const eject = async (
|
|
|
441
442
|
path: join(genDir, "app.css"),
|
|
442
443
|
},
|
|
443
444
|
{ content: buildRuntimeData(project), path: join(genDir, "data.json") },
|
|
445
|
+
// The header's Ask trigger behind the `blume:ask` alias. Always written — it
|
|
446
|
+
// renders nothing when Ask is off — so the alias always resolves.
|
|
447
|
+
{
|
|
448
|
+
content: askComponentTemplate(askEnabled),
|
|
449
|
+
path: join(genDir, "Ask.astro"),
|
|
450
|
+
},
|
|
444
451
|
{
|
|
445
452
|
content: `${JSON.stringify(ejectOpenApiData(project))}\n`,
|
|
446
453
|
path: join(genDir, "openapi.json"),
|
|
@@ -475,7 +482,6 @@ export const eject = async (
|
|
|
475
482
|
files.push(
|
|
476
483
|
...(await mcpFiles(project, pages, srcDir, genDir)),
|
|
477
484
|
...changelogFiles(project, pages, srcDir, {
|
|
478
|
-
askEnabled,
|
|
479
485
|
exportEpub,
|
|
480
486
|
exportPdf,
|
|
481
487
|
needsReact,
|