blume 1.7.1 → 1.7.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 +18 -0
- package/dist/cli/chunk-0qymqwzz.js +164 -0
- package/dist/cli/chunk-0qymqwzz.js.map +15 -0
- package/dist/cli/{chunk-8gnpdsn1.js → chunk-0xjyb285.js} +2 -2
- package/dist/cli/{chunk-12dzsn9b.js → chunk-1jefwnfs.js} +82 -81
- package/dist/cli/{chunk-12dzsn9b.js.map → chunk-1jefwnfs.js.map} +3 -3
- package/dist/cli/{chunk-27gtm2ym.js → chunk-2mzebbbz.js} +1 -1
- package/dist/cli/{chunk-s5e5jt53.js → chunk-2z47ypj8.js} +1 -1
- package/dist/cli/{chunk-jtb45atp.js → chunk-3r45185y.js} +10 -12
- package/dist/cli/{chunk-jtb45atp.js.map → chunk-3r45185y.js.map} +2 -2
- package/dist/cli/{chunk-6mq7qkve.js → chunk-4x36ddpw.js} +18 -24
- package/dist/cli/{chunk-6mq7qkve.js.map → chunk-4x36ddpw.js.map} +2 -2
- package/dist/cli/{chunk-he2zfgah.js → chunk-5093q3n7.js} +22 -30
- package/dist/cli/{chunk-he2zfgah.js.map → chunk-5093q3n7.js.map} +2 -2
- package/dist/cli/{chunk-k0v1f8bb.js → chunk-5g0w1e2c.js} +21 -28
- package/dist/cli/{chunk-k0v1f8bb.js.map → chunk-5g0w1e2c.js.map} +2 -2
- package/dist/cli/{chunk-5gfw0q4j.js → chunk-5qk08vmp.js} +26 -34
- package/dist/cli/{chunk-5gfw0q4j.js.map → chunk-5qk08vmp.js.map} +2 -2
- package/dist/cli/{chunk-r99hynxh.js → chunk-7s8hm3b6.js} +41 -9
- package/dist/cli/{chunk-r99hynxh.js.map → chunk-7s8hm3b6.js.map} +3 -3
- package/dist/cli/{chunk-vyqj481z.js → chunk-8cjtbafj.js} +68 -66
- package/dist/cli/chunk-8cjtbafj.js.map +13 -0
- package/dist/cli/{chunk-aqjvpd03.js → chunk-97r59kpr.js} +27 -33
- package/dist/cli/{chunk-aqjvpd03.js.map → chunk-97r59kpr.js.map} +2 -2
- package/dist/cli/{chunk-np8dmfb0.js → chunk-ahnw3kxw.js} +26 -33
- package/dist/cli/{chunk-np8dmfb0.js.map → chunk-ahnw3kxw.js.map} +2 -2
- package/dist/cli/{chunk-j5f2wrj5.js → chunk-b27xqwn9.js} +10 -15
- package/dist/cli/{chunk-j5f2wrj5.js.map → chunk-b27xqwn9.js.map} +2 -2
- package/dist/cli/{chunk-kmx2mydj.js → chunk-bf6bt1xt.js} +8 -8
- package/dist/cli/{chunk-kmx2mydj.js.map → chunk-bf6bt1xt.js.map} +1 -1
- package/dist/cli/{chunk-90pdhkpm.js → chunk-bvwwhd84.js} +23 -32
- package/dist/cli/{chunk-90pdhkpm.js.map → chunk-bvwwhd84.js.map} +2 -2
- package/dist/cli/{chunk-mfm4sjwx.js → chunk-cjtn640a.js} +32 -43
- package/dist/cli/{chunk-mfm4sjwx.js.map → chunk-cjtn640a.js.map} +2 -2
- package/dist/cli/{chunk-pxj10x8y.js → chunk-ct47dqpx.js} +14 -3
- package/dist/cli/{chunk-pxj10x8y.js.map → chunk-ct47dqpx.js.map} +4 -3
- package/dist/cli/{chunk-x66c5yjn.js → chunk-dwgcp5sm.js} +2 -2
- package/dist/cli/{chunk-4trphnvy.js → chunk-e7f42gdj.js} +10 -13
- package/dist/cli/{chunk-4trphnvy.js.map → chunk-e7f42gdj.js.map} +2 -2
- package/dist/cli/{chunk-82atea4k.js → chunk-esphfr8p.js} +14 -18
- package/dist/cli/{chunk-82atea4k.js.map → chunk-esphfr8p.js.map} +2 -2
- package/dist/cli/{chunk-q56730e0.js → chunk-ex56aa81.js} +53 -53
- package/dist/cli/chunk-ex56aa81.js.map +13 -0
- package/dist/cli/{chunk-ywn7t0pb.js → chunk-garjf5z9.js} +3 -3
- package/dist/cli/{chunk-ev67ycx0.js → chunk-jq5n4avg.js} +1 -1
- package/dist/cli/{chunk-ka5k7cz9.js → chunk-js7saxwm.js} +35 -39
- package/dist/cli/{chunk-ka5k7cz9.js.map → chunk-js7saxwm.js.map} +4 -6
- package/dist/cli/{chunk-x1wvw7a8.js → chunk-k79xp7av.js} +168 -208
- package/dist/cli/chunk-k79xp7av.js.map +39 -0
- package/dist/cli/{chunk-3r94j3tc.js → chunk-nn13znc2.js} +2 -2
- package/dist/cli/{chunk-4ae4f395.js → chunk-ps4m1xh4.js} +60 -35
- package/dist/cli/chunk-ps4m1xh4.js.map +15 -0
- package/dist/cli/{chunk-wd27zjcz.js → chunk-q4rae3bg.js} +1 -1
- package/dist/cli/{chunk-pdwg3q9g.js → chunk-rqy0s5wh.js} +21 -30
- package/dist/cli/{chunk-pdwg3q9g.js.map → chunk-rqy0s5wh.js.map} +2 -2
- package/dist/cli/{chunk-52cwcqvp.js → chunk-rz9jmfhz.js} +15 -24
- package/dist/cli/{chunk-52cwcqvp.js.map → chunk-rz9jmfhz.js.map} +2 -2
- package/dist/cli/{chunk-8p3xe5jv.js → chunk-vacwm2hv.js} +3 -3
- package/dist/cli/{chunk-cbjnx4s8.js → chunk-vh9w1sgp.js} +1 -1
- package/dist/cli/{chunk-sbdqrjbb.js → chunk-vrfp10qk.js} +1 -1
- package/dist/cli/{chunk-h9ekmtz7.js → chunk-yg63d42r.js} +28 -35
- package/dist/cli/{chunk-h9ekmtz7.js.map → chunk-yg63d42r.js.map} +2 -2
- package/dist/cli/{chunk-5hs6gb7n.js → chunk-yzhm0j9q.js} +1 -1
- package/dist/cli/index.js +397 -34
- package/dist/cli/index.js.map +12 -4
- package/dist/types/core/config-input.d.ts +59 -0
- package/dist/types/core/data.d.ts +2 -0
- package/dist/types/core/schema.d.ts +47 -3
- package/dist/types/core/types.d.ts +5 -0
- package/docs/configuration/ask-ai.mdx +61 -0
- package/docs/configuration/index.mdx +3 -1
- package/docs/content/navigation.mdx +3 -0
- package/docs/content/syntax.mdx +10 -0
- package/docs/discoverability/agent-discovery.mdx +82 -1
- package/docs/discoverability/index.mdx +1 -1
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/reference/frontmatter.mdx +2 -0
- package/package.json +1 -1
- package/src/ai/agent-readability.ts +5 -0
- package/src/ai/ai-catalog.ts +241 -0
- package/src/ai/cors.ts +87 -0
- package/src/ai/link-headers.ts +12 -0
- package/src/ai/llms.ts +6 -0
- package/src/ai/mcp/discovery.ts +1 -1
- package/src/astro/generate.ts +4 -0
- package/src/astro/templates.ts +88 -23
- package/src/cli/commands/build.ts +3 -1
- package/src/components/islands/hooks.ts +50 -1
- package/src/components/layout/NavTree.astro +75 -66
- package/src/components/layout/RootLayout.astro +28 -2
- package/src/components/layout/analytics-client.ts +36 -7
- package/src/components/layout/nav-utils.ts +17 -3
- package/src/core/adapter.ts +61 -0
- package/src/core/config-input.ts +60 -0
- package/src/core/data.ts +2 -0
- package/src/core/navigation.ts +22 -3
- package/src/core/schema.ts +88 -0
- package/src/core/types.ts +5 -0
- package/src/deploy/artifacts.ts +12 -1
- package/src/deploy/headers.ts +6 -0
- package/src/deploy/vercel-negotiation.ts +25 -2
- package/src/registry/eject.ts +2 -0
- package/src/search/build.ts +25 -3
- package/src/theme/entry.ts +23 -0
- package/dist/cli/chunk-2aj8ddew.js +0 -72
- package/dist/cli/chunk-2aj8ddew.js.map +0 -10
- package/dist/cli/chunk-4ae4f395.js.map +0 -15
- package/dist/cli/chunk-4xyggvgf.js +0 -21
- package/dist/cli/chunk-4xyggvgf.js.map +0 -10
- package/dist/cli/chunk-6kzzpsx8.js +0 -26
- package/dist/cli/chunk-6kzzpsx8.js.map +0 -10
- package/dist/cli/chunk-bcy492zc.js +0 -16
- package/dist/cli/chunk-bcy492zc.js.map +0 -10
- package/dist/cli/chunk-btfr9yvw.js +0 -41
- package/dist/cli/chunk-btfr9yvw.js.map +0 -10
- package/dist/cli/chunk-ey89bjj1.js +0 -209
- package/dist/cli/chunk-ey89bjj1.js.map +0 -11
- package/dist/cli/chunk-q56730e0.js.map +0 -13
- package/dist/cli/chunk-qvvpnwaz.js +0 -69
- package/dist/cli/chunk-qvvpnwaz.js.map +0 -11
- package/dist/cli/chunk-vt8fgygt.js +0 -23
- package/dist/cli/chunk-vt8fgygt.js.map +0 -10
- package/dist/cli/chunk-vxv4x1n8.js +0 -17
- package/dist/cli/chunk-vxv4x1n8.js.map +0 -10
- package/dist/cli/chunk-vyqj481z.js.map +0 -13
- package/dist/cli/chunk-x1wvw7a8.js.map +0 -40
- /package/dist/cli/{chunk-8gnpdsn1.js.map → chunk-0xjyb285.js.map} +0 -0
- /package/dist/cli/{chunk-27gtm2ym.js.map → chunk-2mzebbbz.js.map} +0 -0
- /package/dist/cli/{chunk-s5e5jt53.js.map → chunk-2z47ypj8.js.map} +0 -0
- /package/dist/cli/{chunk-x66c5yjn.js.map → chunk-dwgcp5sm.js.map} +0 -0
- /package/dist/cli/{chunk-ywn7t0pb.js.map → chunk-garjf5z9.js.map} +0 -0
- /package/dist/cli/{chunk-ev67ycx0.js.map → chunk-jq5n4avg.js.map} +0 -0
- /package/dist/cli/{chunk-3r94j3tc.js.map → chunk-nn13znc2.js.map} +0 -0
- /package/dist/cli/{chunk-wd27zjcz.js.map → chunk-q4rae3bg.js.map} +0 -0
- /package/dist/cli/{chunk-8p3xe5jv.js.map → chunk-vacwm2hv.js.map} +0 -0
- /package/dist/cli/{chunk-cbjnx4s8.js.map → chunk-vh9w1sgp.js.map} +0 -0
- /package/dist/cli/{chunk-sbdqrjbb.js.map → chunk-vrfp10qk.js.map} +0 -0
- /package/dist/cli/{chunk-5hs6gb7n.js.map → chunk-yzhm0j9q.js.map} +0 -0
|
@@ -588,6 +588,8 @@ export interface AskSuggestion {
|
|
|
588
588
|
/** Backends that can route an Ask AI request. */
|
|
589
589
|
type AskProviderGateway = "gateway" | "openrouter" | "llmgateway";
|
|
590
590
|
type AskProvider = AskProviderGateway | "inkeep" | "openai-compatible";
|
|
591
|
+
/** How much the model reasons before answering (`ai.ask.reasoning`). */
|
|
592
|
+
type AskReasoning = "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
591
593
|
/** How much retrieved documentation each Ask AI question carries. */
|
|
592
594
|
export interface AskRetrievalConfig {
|
|
593
595
|
/**
|
|
@@ -620,6 +622,18 @@ export interface AskConfig {
|
|
|
620
622
|
* overrides the built-in preset.
|
|
621
623
|
*/
|
|
622
624
|
baseUrl?: string;
|
|
625
|
+
/**
|
|
626
|
+
* Origins allowed to call the generated endpoint from another site — a
|
|
627
|
+
* marketing page that embeds an ask box, for example — or `"*"` to allow
|
|
628
|
+
* every origin. The route answers preflight requests and names a listed
|
|
629
|
+
* origin on every response, errors included; every other origin stays
|
|
630
|
+
* subject to the browser's same-origin rule. Callers must send the body as
|
|
631
|
+
* JSON with a `content-type: application/json` header, or Astro's cross-site
|
|
632
|
+
* request check rejects the `POST` before the route runs. Only the generated
|
|
633
|
+
* route reads this; an external `endpoint` handles its own CORS and can't be
|
|
634
|
+
* combined with it.
|
|
635
|
+
*/
|
|
636
|
+
cors?: string[];
|
|
623
637
|
/** Turn Ask AI on. Defaults to `false`. */
|
|
624
638
|
enabled?: boolean;
|
|
625
639
|
/**
|
|
@@ -645,6 +659,18 @@ export interface AskConfig {
|
|
|
645
659
|
model?: string;
|
|
646
660
|
/** Which backend routes the request. Defaults to `gateway`. */
|
|
647
661
|
provider?: AskProvider;
|
|
662
|
+
/**
|
|
663
|
+
* How much the model reasons before answering, from `"none"` to `"xhigh"`.
|
|
664
|
+
* Sent as the backend's own reasoning-effort control: the AI SDK's
|
|
665
|
+
* `reasoning` option on the gateway, `reasoning.effort` on OpenRouter, and
|
|
666
|
+
* `reasoning_effort` on OpenAI-compatible endpoints. The model has to
|
|
667
|
+
* support the level — OpenAI rejects one a model doesn't offer — and the
|
|
668
|
+
* endpoint has to accept the parameter; Inkeep has no reasoning control,
|
|
669
|
+
* so the field is rejected there. Omitted keeps the model's default.
|
|
670
|
+
* `"none"` is the fastest and cheapest for grounded docs Q&A, where the
|
|
671
|
+
* retrieved excerpts carry the answer.
|
|
672
|
+
*/
|
|
673
|
+
reasoning?: AskReasoning;
|
|
648
674
|
/**
|
|
649
675
|
* How much documentation each question carries into the model's prompt.
|
|
650
676
|
* Lower values cut time-to-first-token — which dominates on a self-hosted
|
|
@@ -654,6 +680,30 @@ export interface AskConfig {
|
|
|
654
680
|
/** Starter prompts shown before the first question. */
|
|
655
681
|
suggestions?: AskSuggestion[];
|
|
656
682
|
}
|
|
683
|
+
/** What the AI Catalog (ARD) manifest carries. */
|
|
684
|
+
export interface AiCatalogConfig {
|
|
685
|
+
/** Emit `/.well-known/ai-catalog.json` and `/.well-known/ard.json`. Defaults to `true`. */
|
|
686
|
+
enabled?: boolean;
|
|
687
|
+
/**
|
|
688
|
+
* Representative queries per entry, keyed by the entry's `<namespace>:<name>`
|
|
689
|
+
* — its identifier minus the `urn:air:<host>:` prefix (`mcp:docs`,
|
|
690
|
+
* `skill:blume`, `api:docs`, `reference:<slug>`, `docs:llms-txt`). Each
|
|
691
|
+
* list replaces the generated defaults for that entry: 2–5 short
|
|
692
|
+
* natural-language questions the resource can answer, which agent
|
|
693
|
+
* registries embed for semantic search.
|
|
694
|
+
*
|
|
695
|
+
* ```ts
|
|
696
|
+
* ai: {
|
|
697
|
+
* catalog: {
|
|
698
|
+
* queries: {
|
|
699
|
+
* "mcp:acme": ["how do I install Acme", "search the Acme docs"],
|
|
700
|
+
* },
|
|
701
|
+
* },
|
|
702
|
+
* }
|
|
703
|
+
* ```
|
|
704
|
+
*/
|
|
705
|
+
queries?: Record<string, string[]>;
|
|
706
|
+
}
|
|
657
707
|
/** What the `llms.txt`/`llms-full.txt` files include. */
|
|
658
708
|
export interface LlmsTxtConfig {
|
|
659
709
|
/**
|
|
@@ -708,6 +758,15 @@ export interface AiConfig {
|
|
|
708
758
|
api?: boolean;
|
|
709
759
|
/** The Ask AI chat assistant. */
|
|
710
760
|
ask?: AskConfig;
|
|
761
|
+
/**
|
|
762
|
+
* The AI Catalog / ARD manifest (`/.well-known/ai-catalog.json`, mirrored
|
|
763
|
+
* at `/.well-known/ard.json`): a domain-level index of the agent-facing
|
|
764
|
+
* resources the site publishes — MCP server, agent skills, the JSON docs
|
|
765
|
+
* API, API references, llms.txt — for agent registries. Needs a
|
|
766
|
+
* `deployment.site`. Defaults to `true`; the object form overrides the
|
|
767
|
+
* generated representative queries per entry.
|
|
768
|
+
*/
|
|
769
|
+
catalog?: boolean | AiCatalogConfig;
|
|
711
770
|
/**
|
|
712
771
|
* Emit `llms.txt` (an index of the docs for LLMs). Defaults to `true`.
|
|
713
772
|
* The object form adds knobs for what the files include.
|
|
@@ -143,6 +143,8 @@ export interface BlumeDataConfig {
|
|
|
143
143
|
*/
|
|
144
144
|
discovery: {
|
|
145
145
|
agentReadability: boolean;
|
|
146
|
+
/** Whether the AI Catalog / ARD manifest is published (`ai.catalog`). */
|
|
147
|
+
aiCatalog: boolean;
|
|
146
148
|
/** Whether the JSON docs API and its `/openapi.json` are published. */
|
|
147
149
|
api: boolean;
|
|
148
150
|
llmsTxt: boolean;
|
|
@@ -327,6 +327,11 @@ export type SidebarItemConfig = string | {
|
|
|
327
327
|
root?: string;
|
|
328
328
|
};
|
|
329
329
|
export declare const searchProviders: readonly ["orama", "pagefind", "flexsearch", "algolia", "orama-cloud", "typesense", "mixedbread", "none"];
|
|
330
|
+
/**
|
|
331
|
+
* The `ai.ask.reasoning` levels: the AI SDK's top-level `reasoning` values
|
|
332
|
+
* minus `provider-default`, which is what omitting the field means.
|
|
333
|
+
*/
|
|
334
|
+
export declare const askReasoningLevels: readonly ["none", "minimal", "low", "medium", "high", "xhigh"];
|
|
330
335
|
/** Ask AI backends. `gateway` (default) routes through the Vercel AI Gateway. */
|
|
331
336
|
export declare const askAiProviders: readonly ["gateway", "openrouter", "llmgateway", "inkeep", "openai-compatible"];
|
|
332
337
|
declare const aiConfigSchema: z.ZodObject<{
|
|
@@ -334,6 +339,7 @@ declare const aiConfigSchema: z.ZodObject<{
|
|
|
334
339
|
ask: z.ZodOptional<z.ZodObject<{
|
|
335
340
|
apiKeyEnv: z.ZodOptional<z.ZodString>;
|
|
336
341
|
baseUrl: z.ZodOptional<z.ZodURL>;
|
|
342
|
+
cors: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodLiteral<"*">, z.ZodPipe<z.ZodURL, z.ZodTransform<string, string>>]>>>;
|
|
337
343
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
338
344
|
endpoint: z.ZodOptional<z.ZodString>;
|
|
339
345
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
@@ -346,6 +352,14 @@ declare const aiConfigSchema: z.ZodObject<{
|
|
|
346
352
|
inkeep: "inkeep";
|
|
347
353
|
"openai-compatible": "openai-compatible";
|
|
348
354
|
}>>;
|
|
355
|
+
reasoning: z.ZodOptional<z.ZodEnum<{
|
|
356
|
+
none: "none";
|
|
357
|
+
minimal: "minimal";
|
|
358
|
+
low: "low";
|
|
359
|
+
medium: "medium";
|
|
360
|
+
high: "high";
|
|
361
|
+
xhigh: "xhigh";
|
|
362
|
+
}>>;
|
|
349
363
|
retrieval: z.ZodOptional<z.ZodObject<{
|
|
350
364
|
contextBudget: z.ZodOptional<z.ZodNumber>;
|
|
351
365
|
excerptChars: z.ZodOptional<z.ZodNumber>;
|
|
@@ -356,6 +370,16 @@ declare const aiConfigSchema: z.ZodObject<{
|
|
|
356
370
|
label: z.ZodString;
|
|
357
371
|
}, z.core.$strict>>>;
|
|
358
372
|
}, z.core.$strict>>;
|
|
373
|
+
catalog: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
374
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
375
|
+
queries: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
376
|
+
}, z.core.$strict>]>>, z.ZodTransform<{
|
|
377
|
+
enabled: boolean;
|
|
378
|
+
queries: Record<string, string[]>;
|
|
379
|
+
}, boolean | {
|
|
380
|
+
enabled: boolean;
|
|
381
|
+
queries: Record<string, string[]>;
|
|
382
|
+
}>>;
|
|
359
383
|
llmsTxt: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
360
384
|
details: z.ZodOptional<z.ZodString>;
|
|
361
385
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -391,6 +415,7 @@ declare const aiConfigSchema: z.ZodObject<{
|
|
|
391
415
|
webmcp: z.ZodDefault<z.ZodBoolean>;
|
|
392
416
|
}, z.core.$strict>;
|
|
393
417
|
export type AskAiProvider = (typeof askAiProviders)[number];
|
|
418
|
+
export type AskReasoning = (typeof askReasoningLevels)[number];
|
|
394
419
|
export type AskAiConfig = NonNullable<z.infer<typeof aiConfigSchema>["ask"]>;
|
|
395
420
|
export { openInChatProviders } from "./open-in-chat.ts";
|
|
396
421
|
export type { OpenInChatProvider } from "./open-in-chat.ts";
|
|
@@ -505,9 +530,9 @@ declare const contentSignalsSchema: z.ZodPipe<z.ZodUnion<readonly [z.ZodBoolean,
|
|
|
505
530
|
declare const dateFormatConfigSchema: z.ZodObject<{
|
|
506
531
|
calendar: z.ZodOptional<z.ZodString>;
|
|
507
532
|
dateStyle: z.ZodOptional<z.ZodEnum<{
|
|
533
|
+
medium: "medium";
|
|
508
534
|
full: "full";
|
|
509
535
|
long: "long";
|
|
510
|
-
medium: "medium";
|
|
511
536
|
short: "short";
|
|
512
537
|
}>>;
|
|
513
538
|
day: z.ZodOptional<z.ZodEnum<{
|
|
@@ -576,6 +601,7 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
576
601
|
ask: z.ZodOptional<z.ZodObject<{
|
|
577
602
|
apiKeyEnv: z.ZodOptional<z.ZodString>;
|
|
578
603
|
baseUrl: z.ZodOptional<z.ZodURL>;
|
|
604
|
+
cors: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodLiteral<"*">, z.ZodPipe<z.ZodURL, z.ZodTransform<string, string>>]>>>;
|
|
579
605
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
580
606
|
endpoint: z.ZodOptional<z.ZodString>;
|
|
581
607
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
@@ -588,6 +614,14 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
588
614
|
inkeep: "inkeep";
|
|
589
615
|
"openai-compatible": "openai-compatible";
|
|
590
616
|
}>>;
|
|
617
|
+
reasoning: z.ZodOptional<z.ZodEnum<{
|
|
618
|
+
none: "none";
|
|
619
|
+
minimal: "minimal";
|
|
620
|
+
low: "low";
|
|
621
|
+
medium: "medium";
|
|
622
|
+
high: "high";
|
|
623
|
+
xhigh: "xhigh";
|
|
624
|
+
}>>;
|
|
591
625
|
retrieval: z.ZodOptional<z.ZodObject<{
|
|
592
626
|
contextBudget: z.ZodOptional<z.ZodNumber>;
|
|
593
627
|
excerptChars: z.ZodOptional<z.ZodNumber>;
|
|
@@ -598,6 +632,16 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
598
632
|
label: z.ZodString;
|
|
599
633
|
}, z.core.$strict>>>;
|
|
600
634
|
}, z.core.$strict>>;
|
|
635
|
+
catalog: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
636
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
637
|
+
queries: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
638
|
+
}, z.core.$strict>]>>, z.ZodTransform<{
|
|
639
|
+
enabled: boolean;
|
|
640
|
+
queries: Record<string, string[]>;
|
|
641
|
+
}, boolean | {
|
|
642
|
+
enabled: boolean;
|
|
643
|
+
queries: Record<string, string[]>;
|
|
644
|
+
}>>;
|
|
601
645
|
llmsTxt: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
602
646
|
details: z.ZodOptional<z.ZodString>;
|
|
603
647
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -773,9 +817,9 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
773
817
|
dateFormat: z.ZodDefault<z.ZodObject<{
|
|
774
818
|
calendar: z.ZodOptional<z.ZodString>;
|
|
775
819
|
dateStyle: z.ZodOptional<z.ZodEnum<{
|
|
820
|
+
medium: "medium";
|
|
776
821
|
full: "full";
|
|
777
822
|
long: "long";
|
|
778
|
-
medium: "medium";
|
|
779
823
|
short: "short";
|
|
780
824
|
}>>;
|
|
781
825
|
day: z.ZodOptional<z.ZodEnum<{
|
|
@@ -1061,6 +1105,7 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
1061
1105
|
label: z.ZodString;
|
|
1062
1106
|
}, z.core.$strict>>>;
|
|
1063
1107
|
provider: z.ZodDefault<z.ZodEnum<{
|
|
1108
|
+
none: "none";
|
|
1064
1109
|
algolia: "algolia";
|
|
1065
1110
|
mixedbread: "mixedbread";
|
|
1066
1111
|
orama: "orama";
|
|
@@ -1068,7 +1113,6 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
1068
1113
|
flexsearch: "flexsearch";
|
|
1069
1114
|
"orama-cloud": "orama-cloud";
|
|
1070
1115
|
typesense: "typesense";
|
|
1071
|
-
none: "none";
|
|
1072
1116
|
}>>;
|
|
1073
1117
|
typesense: z.ZodOptional<z.ZodObject<{
|
|
1074
1118
|
collection: z.ZodString;
|
|
@@ -208,6 +208,11 @@ export type NavNode = {
|
|
|
208
208
|
*/
|
|
209
209
|
display: SidebarDisplay;
|
|
210
210
|
icon?: string;
|
|
211
|
+
/**
|
|
212
|
+
* The group row's link: an explicit-config group's `root`, or the
|
|
213
|
+
* generated folder's index page route. Absent when there is no page at
|
|
214
|
+
* the group's own path, so the row never links to a 404.
|
|
215
|
+
*/
|
|
211
216
|
route?: string;
|
|
212
217
|
/**
|
|
213
218
|
* The group's URL path (its folder route prefix), even when the folder
|
|
@@ -109,6 +109,37 @@ Blume sends the same `POST` body as its built-in route:
|
|
|
109
109
|
|
|
110
110
|
Return a successful response whose body is a plain UTF-8 text stream. If the endpoint is on another origin, allow the docs origin with CORS: accept `OPTIONS` and `POST`, permit the `content-type` request header, and return the CORS headers on both the preflight and streamed response. With `endpoint` set, Blume generates the chat UI but no server route, grounding snapshot, provider dependency, or provider-secret warning; your backend owns retrieval, authentication, rate limiting, model access, and citations.
|
|
111
111
|
|
|
112
|
+
## Cross-origin callers
|
|
113
|
+
|
|
114
|
+
The generated endpoint answers the in-page assistant on its own origin. To call it from another site as well — a marketing page with an ask box, say — list that site's origin in `cors`:
|
|
115
|
+
|
|
116
|
+
```ts blume.config.ts lineNumbers
|
|
117
|
+
ai: {
|
|
118
|
+
ask: {
|
|
119
|
+
enabled: true,
|
|
120
|
+
cors: ["https://www.example.com"],
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The route then answers the browser's `OPTIONS` preflight and names a listed origin on every response — the streamed answer and the error statuses alike, so the caller can tell a rejected body from a provider failure. Origins that aren't listed get no header and stay subject to the browser's same-origin rule. Each entry is reduced to its origin, so `https://www.example.com/docs/` and `https://www.example.com` mean the same thing. To let any page call the route, list `"*"` instead of origins.
|
|
126
|
+
|
|
127
|
+
The caller sends the same `POST` body the [external endpoint](#external-endpoint) contract describes and reads back the same text stream. Send it as JSON with a `content-type: application/json` header:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const response = await fetch("https://docs.example.com/api/ask", {
|
|
131
|
+
body: JSON.stringify({
|
|
132
|
+
messages: [{ role: "user", content: "How do I deploy?" }],
|
|
133
|
+
}),
|
|
134
|
+
headers: { "content-type": "application/json" },
|
|
135
|
+
method: "POST",
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The content type matters: Astro's cross-site request check rejects a cross-origin `POST` that has no content type, or a form-like one such as `text/plain`, with a 403 before the route runs, and that response carries no CORS headers, so the browser reports it as a network error rather than a status. The preflight allows whatever request headers the caller asks for, so a fetch wrapper that adds its own headers needs no extra configuration.
|
|
140
|
+
|
|
141
|
+
`cors` only affects the generated route; with an external `endpoint`, CORS is that backend's job, and setting both is a config error. The endpoint stays unauthenticated either way, so the [rate limiting](#rate-limiting) advice applies to cross-origin traffic too.
|
|
142
|
+
|
|
112
143
|
## Server output required
|
|
113
144
|
|
|
114
145
|
Blume's built-in Ask AI backend is a server route (`POST /api/ask`), so it can't run on a static build. Switch to server output and pick an adapter:
|
|
@@ -188,6 +219,36 @@ The values are written into the generated route as-is, so keep secrets in `apiKe
|
|
|
188
219
|
|
|
189
220
|
Keys are read through Astro's [`getSecret()`](https://docs.astro.build/en/guides/environment-variables/#retrieving-secrets-programmatically), so each adapter supplies them its own way: environment variables on Node, Vercel, and Netlify, and the Worker's [bindings](https://docs.astro.build/en/guides/integrations-guide/cloudflare/#environment-variables-and-secrets) on Cloudflare. Enabling Ask AI also turns on React for the in-page island — see [Customization](/docs/configuration/customization#interactive-islands).
|
|
190
221
|
|
|
222
|
+
## Reasoning
|
|
223
|
+
|
|
224
|
+
Reasoning models think before they answer, and how much they do so by default varies by model. For grounded docs Q&A the retrieved excerpts carry the answer, so most of that thinking is latency the reader waits through. `reasoning` sets how much the model reasons: `"none"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, or `"xhigh"`:
|
|
225
|
+
|
|
226
|
+
```ts blume.config.ts lineNumbers
|
|
227
|
+
ai: {
|
|
228
|
+
ask: {
|
|
229
|
+
enabled: true,
|
|
230
|
+
model: "openai/gpt-5.5",
|
|
231
|
+
reasoning: "none",
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The value is sent as the backend's own reasoning-effort control. Through the gateway it travels as the [AI SDK's `reasoning` option](https://ai-sdk.dev/docs/ai-sdk-core/reasoning), which the gateway maps to the model's setting — OpenAI's `reasoning_effort`, for example. On OpenRouter it is sent as `reasoning.effort`, and on LLMGateway or a custom `openai-compatible` endpoint as `reasoning_effort` in the request, so the endpoint has to accept that parameter. The model has to support the level you pick: OpenAI rejects a level a model doesn't offer (`"none"` and `"xhigh"` exist only on some), so check the model's documentation before setting one. Inkeep runs its own QA pipeline and has no reasoning control, so setting `reasoning` with that backend is a config error. Leave it unset to keep the model's default. Like [retrieval size](#retrieval-size), it trades thoroughness for time-to-first-token, and answers stay grounded either way.
|
|
237
|
+
|
|
238
|
+
## Analytics
|
|
239
|
+
|
|
240
|
+
With an [analytics provider](/docs/configuration/analytics) configured, the assistant reports its usage through the same `track()` the page feedback widget uses, so questions land next to your pageviews:
|
|
241
|
+
|
|
242
|
+
| Event | When | Properties |
|
|
243
|
+
| --- | --- | --- |
|
|
244
|
+
| `ask` | A question is sent | `path`, `questionChars` |
|
|
245
|
+
| `ask_answer` | The answer finishes streaming | `path`, `questionChars`, `ms`, `chars` |
|
|
246
|
+
| `ask_error` | The request fails, breaks, or comes back empty | `path`, `questionChars`, `ms`, `status` |
|
|
247
|
+
|
|
248
|
+
`path` is the page the reader asked from (the served pathname, so it matches the feedback widget and your pageviews under a `base`), `questionChars` the question's length, `ms` the time from sending the question to the last chunk, and `chars` the answer's length. `status` is the HTTP status: `0` when no response arrived at all (offline, DNS, CORS), and `200` when the response was fine but its stream broke mid-answer — how a provider or credential error surfaces, since the backend has already sent its headers — or delivered nothing. Clearing the conversation mid-answer reports neither outcome.
|
|
249
|
+
|
|
250
|
+
The question's text never reaches a provider: it is free-form reader input (pasted keys, error logs, names) that would breach most providers' terms and per-value size limits. It travels only on the `blume:track` DOM event, as `question` in `detail.props`, so a listener you write can forward it wherever you decide it belongs. A custom chat UI built on `useAskAI` from `blume/hooks` reports the same events. With no provider configured the built-in provider calls are no-ops, but the `blume:track` event still fires, so a custom integration listening for it receives them.
|
|
251
|
+
|
|
191
252
|
## Rate limiting
|
|
192
253
|
|
|
193
254
|
The `POST /api/ask` endpoint is **unauthenticated** — it has to be, so the in-page assistant can call it. Blume validates each request — rejecting malformed bodies, capping it to 1–40 messages, and accepting only `user`/`assistant` roles so a caller can't inject their own system prompt and repurpose the route as a general LLM proxy — to bound how much a single call can spend against your model, but it can't stop someone from calling the endpoint repeatedly. If cost abuse is a concern, put the route behind a rate limiter — your host's (e.g. Vercel's) edge rate limiting, a middleware, or your model provider's per-key spend limits.
|
|
@@ -65,9 +65,11 @@ export default defineConfig({
|
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
67
|
|
|
68
|
-
// AI — llms.txt, MCP; see the Discoverability section
|
|
68
|
+
// AI — llms.txt, MCP, the AI catalog; see the Discoverability section
|
|
69
69
|
ai: {
|
|
70
70
|
llmsTxt: true,
|
|
71
|
+
// AI Catalog / ARD manifest at /.well-known/ai-catalog.json (needs deployment.site)
|
|
72
|
+
catalog: true,
|
|
71
73
|
// MCP server (needs server output)
|
|
72
74
|
mcp: {
|
|
73
75
|
enabled: false,
|
|
@@ -12,6 +12,7 @@ By default the sidebar mirrors your content tree:
|
|
|
12
12
|
- folders become **groups**, files become **pages**
|
|
13
13
|
- a page's label is its frontmatter `title`; a group's label is the humanized folder name
|
|
14
14
|
- items sort by [numeric prefix](/docs/content), then alphabetically, and a folder's `index` page comes first
|
|
15
|
+
- a folder with an `index` page links its group row to that page, so clicking the section name opens the section's landing page
|
|
15
16
|
|
|
16
17
|
That's enough for many sites — everything below is opt-in.
|
|
17
18
|
|
|
@@ -133,6 +134,8 @@ sidebar:
|
|
|
133
134
|
hidden: true
|
|
134
135
|
```
|
|
135
136
|
|
|
137
|
+
A folder's `index` page appears both as the group row's link and as the first row inside the group. Hide the index page to keep only the linked header: the group row still opens the landing page, and previous/next links still pass through it.
|
|
138
|
+
|
|
136
139
|
## Tabs
|
|
137
140
|
|
|
138
141
|
Render top-level sections as tabs in the header, useful for splitting a large site into distinct areas — say adapters, an API, and AI guides. A tab is highlighted when the current route falls under its `path`:
|
package/docs/content/syntax.mdx
CHANGED
|
@@ -59,6 +59,16 @@ Inline formatting for stressing words, marking deletions, and showing code or ke
|
|
|
59
59
|
**Bold**, _italic_, ~~strikethrough~~, and `inline code`.
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
## Keyboard keys
|
|
63
|
+
|
|
64
|
+
For shortcuts and keystrokes. A `<kbd>` element renders as the same bordered key badge the search dialog uses, in Markdown, MDX, and inside components like `<Steps>` and `<Callout>`.
|
|
65
|
+
|
|
66
|
+
Press <kbd>⌘</kbd> <kbd>K</kbd> to open search, or <kbd>Esc</kbd> to close it.
|
|
67
|
+
|
|
68
|
+
```md
|
|
69
|
+
Press <kbd>⌘</kbd> <kbd>K</kbd> to open search, or <kbd>Esc</kbd> to close it.
|
|
70
|
+
```
|
|
71
|
+
|
|
62
72
|
## Superscript and subscript
|
|
63
73
|
|
|
64
74
|
For footnote markers, ordinals, and scientific or chemical notation inline.
|
|
@@ -55,13 +55,14 @@ Agents that probe a site don't know to look for the manifest — so Blume also a
|
|
|
55
55
|
|
|
56
56
|
```http
|
|
57
57
|
Link: </.well-known/api-catalog>; rel="api-catalog"; type="application/linkset+json",
|
|
58
|
+
</.well-known/ai-catalog.json>; rel="ai-catalog"; type="application/ai-catalog+json",
|
|
58
59
|
</openapi.json>; rel="service-desc"; type="application/json",
|
|
59
60
|
</agent-readability.json>; rel="describedby"; type="application/json",
|
|
60
61
|
</llms.txt>; rel="describedby"; type="text/plain",
|
|
61
62
|
</index.md>; rel="alternate"; type="text/markdown"
|
|
62
63
|
```
|
|
63
64
|
|
|
64
|
-
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](/docs/discoverability/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](/docs/discoverability/json-api)'s OpenAPI description,
|
|
65
|
+
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](/docs/discoverability/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](/docs/discoverability/json-api)'s OpenAPI description, `api-catalog` at the [generated API catalog](#api-catalog), and `ai-catalog` at the [AI catalog](#ai-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.
|
|
65
66
|
|
|
66
67
|
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:
|
|
67
68
|
|
|
@@ -72,6 +73,12 @@ Not every agent enters through the root, though — one following a search resul
|
|
|
72
73
|
type="application/json"
|
|
73
74
|
/>
|
|
74
75
|
<link rel="describedby" href="/llms.txt" type="text/plain" />
|
|
76
|
+
<link
|
|
77
|
+
rel="ai-catalog"
|
|
78
|
+
href="/.well-known/ai-catalog.json"
|
|
79
|
+
type="application/ai-catalog+json"
|
|
80
|
+
/>
|
|
81
|
+
<link rel="ard" href="/.well-known/ard.json" type="application/json" />
|
|
75
82
|
<link rel="alternate" href="/docs/example.md" type="text/markdown" />
|
|
76
83
|
```
|
|
77
84
|
|
|
@@ -121,6 +128,80 @@ When the site publishes APIs, Blume generates an [RFC 9727](https://www.rfc-edit
|
|
|
121
128
|
|
|
122
129
|
A site with no API references, no MCP server, and the [JSON API](/docs/discoverability/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.
|
|
123
130
|
|
|
131
|
+
## AI catalog
|
|
132
|
+
|
|
133
|
+
The API catalog lists APIs. The **AI catalog** lists everything an agent could pick up from the site — the MCP server, each published skill, the JSON API, each rendered API reference, and `llms.txt` — in the format agent registries index: an [AI Catalog](https://github.com/Agent-Card/ai-catalog) document at `/.well-known/ai-catalog.json`, which is also the manifest [Agentic Resource Discovery (ARD)](https://agenticresourcediscovery.org/) consumers resolve. Each entry carries a domain-anchored `urn:air:<host>:<namespace>:<name>` identifier, a display name, the artifact's media type, its URL, and a handful of `representativeQueries` — sample questions the resource can answer, which registries embed for semantic search:
|
|
134
|
+
|
|
135
|
+
```json .well-known/ai-catalog.json
|
|
136
|
+
{
|
|
137
|
+
"specVersion": "1.0",
|
|
138
|
+
"host": {
|
|
139
|
+
"displayName": "Acme",
|
|
140
|
+
"identifier": "did:web:docs.example.com",
|
|
141
|
+
"documentationUrl": "https://docs.example.com/"
|
|
142
|
+
},
|
|
143
|
+
"entries": [
|
|
144
|
+
{
|
|
145
|
+
"identifier": "urn:air:docs.example.com:mcp:acme",
|
|
146
|
+
"displayName": "Acme",
|
|
147
|
+
"type": "application/mcp-server-card+json",
|
|
148
|
+
"url": "https://docs.example.com/.well-known/mcp/server-card.json",
|
|
149
|
+
"capabilities": [
|
|
150
|
+
"search_docs",
|
|
151
|
+
"get_page",
|
|
152
|
+
"list_pages",
|
|
153
|
+
"get_navigation"
|
|
154
|
+
],
|
|
155
|
+
"representativeQueries": [
|
|
156
|
+
"search the Acme documentation",
|
|
157
|
+
"get a Acme docs page as Markdown",
|
|
158
|
+
"list every page in the Acme docs"
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"identifier": "urn:air:docs.example.com:skill:acme",
|
|
163
|
+
"displayName": "acme",
|
|
164
|
+
"type": "application/agent-skills+md",
|
|
165
|
+
"url": "https://docs.example.com/.well-known/agent-skills/acme/SKILL.md",
|
|
166
|
+
"representativeQueries": [
|
|
167
|
+
"load the acme agent skill",
|
|
168
|
+
"how do I use acme"
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"identifier": "urn:air:docs.example.com:api:docs",
|
|
173
|
+
"displayName": "Acme docs API",
|
|
174
|
+
"type": "application/vnd.oai.openapi+json",
|
|
175
|
+
"url": "https://docs.example.com/openapi.json",
|
|
176
|
+
"representativeQueries": [
|
|
177
|
+
"fetch a Acme docs page as JSON",
|
|
178
|
+
"list the pages in the Acme docs",
|
|
179
|
+
"get the Acme docs navigation tree"
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
ARD's current revision reads the manifest from `/.well-known/ard.json` and calls `ai-catalog.json` the predecessor path, so Blume writes the same document to both, advertises it under both link relations (`ai-catalog` and `ard`) in every page's head, and lists it in `llms.txt` and `agent-readability.json`. The catalog and its `.well-known` neighbors (the API catalog, the MCP discovery files) are served with `Access-Control-Allow-Origin: *` on every build surface, so a registry reading them from another origin isn't blocked.
|
|
187
|
+
|
|
188
|
+
Entry identifiers are anchored on your domain, so the catalog needs a [`deployment.site`](/docs/deployment) — without one nothing is emitted. It's on by default; `ai.catalog: false` turns it off. The generated queries are derived from the site title and each entry's own description. To write your own for an entry, key them by the identifier's tail (`<namespace>:<name>`):
|
|
189
|
+
|
|
190
|
+
```ts blume.config.ts
|
|
191
|
+
export default defineConfig({
|
|
192
|
+
ai: {
|
|
193
|
+
catalog: {
|
|
194
|
+
queries: {
|
|
195
|
+
"mcp:acme": ["how do I install Acme", "search the Acme docs"],
|
|
196
|
+
"skill:acme": ["set up an Acme project", "write an Acme plugin"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
A list replaces the generated queries for that entry; entries you don't name keep theirs. As everywhere, a `public/.well-known/ai-catalog.json` (or `ard.json`) you ship yourself wins over the generated one.
|
|
204
|
+
|
|
124
205
|
## WebMCP
|
|
125
206
|
|
|
126
207
|
[WebMCP](https://webmachinelearning.github.io/webmcp/) is an emerging browser API that lets a page register tools directly with an agentic browser — no separate server connection needed. Every Blume page registers the docs' read-only surface on the page's model context: `search_docs` (site search), `get_page` (a page's [raw Markdown](/docs/discoverability/markdown)), and `list_pages` (the [`llms.txt`](/docs/discoverability/llms-txt) index). The script is tiny, loads no search machinery until a tool is actually called, and silently no-ops in every browser without the API — which today is all of them outside [Chrome's early preview](https://developer.chrome.com/blog/webmcp-epp). It registers on whichever surface the in-flux spec exposes (`navigator.modelContext` or `document.modelContext`), via `provideContext` or per-tool `registerTool`.
|
|
@@ -37,7 +37,7 @@ Most of this is sharper with an absolute site URL — set [`deployment.site`](/d
|
|
|
37
37
|
| Raw Markdown mirrors, content negotiation, Copy as Markdown, Open in chat | `/<route>.md` | on | [Markdown for agents](/docs/discoverability/markdown) |
|
|
38
38
|
| JSON API and its OpenAPI description | `/api/docs/…`, `/openapi.json` | on | [JSON API](/docs/discoverability/json-api) |
|
|
39
39
|
| MCP server | `/mcp` | opt-in, server output | [MCP server](/docs/discoverability/mcp) |
|
|
40
|
-
| `agent-readability.json`, `Link` headers, API catalog, WebMCP, skills, Web Bot Auth | site root, `/.well-known/…` | on | [Agent discovery](/docs/discoverability/agent-discovery) |
|
|
40
|
+
| `agent-readability.json`, `Link` headers, API catalog, AI catalog (ARD), WebMCP, skills, Web Bot Auth | site root, `/.well-known/…` | on | [Agent discovery](/docs/discoverability/agent-discovery) |
|
|
41
41
|
|
|
42
42
|
Every generated file yields to one you ship yourself: drop a `robots.txt`, `sitemap.xml`, `llms.txt`, `openapi.json`, or `agent-readability.json` in `public/` and Blume serves yours in its place.
|
|
43
43
|
|
|
@@ -47,7 +47,7 @@ ai: {
|
|
|
47
47
|
|
|
48
48
|
## Generated sections
|
|
49
49
|
|
|
50
|
-
`llms.txt` closes with two generated sections that need no configuration. **Agent skills** lists each skill published through [`ai.skills`](/docs/discoverability/agent-discovery#skills-discovery) with its description (where a skill says when to use it). **Agent resources** links every machine-readable artifact the build emits — `llms-full.txt`, the per-page [raw Markdown](/docs/discoverability/markdown) mirror, the [MCP server](/docs/discoverability/mcp) and its discovery document, the skills index, the [API catalog](/docs/discoverability/agent-discovery#api-catalog), [`agent-readability.json`](/docs/discoverability/agent-discovery#agent-readability), and the [sitemap](/docs/discoverability/sitemap-and-robots#sitemap) — each only when it exists, so an agent that reads nothing but `llms.txt` still finds the whole surface.
|
|
50
|
+
`llms.txt` closes with two generated sections that need no configuration. **Agent skills** lists each skill published through [`ai.skills`](/docs/discoverability/agent-discovery#skills-discovery) with its description (where a skill says when to use it). **Agent resources** links every machine-readable artifact the build emits — `llms-full.txt`, the per-page [raw Markdown](/docs/discoverability/markdown) mirror, the [MCP server](/docs/discoverability/mcp) and its discovery document, the skills index, the [API catalog](/docs/discoverability/agent-discovery#api-catalog), the [AI catalog](/docs/discoverability/agent-discovery#ai-catalog), [`agent-readability.json`](/docs/discoverability/agent-discovery#agent-readability), and the [sitemap](/docs/discoverability/sitemap-and-robots#sitemap) — each only when it exists, so an agent that reads nothing but `llms.txt` still finds the whole surface.
|
|
51
51
|
|
|
52
52
|
## Excluding a page
|
|
53
53
|
|
|
@@ -49,6 +49,8 @@ sidebar:
|
|
|
49
49
|
display: page
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
`hidden` removes the page from the sidebar and from previous/next pagination. On a folder's `index` page it removes only the page's own row: the group row keeps linking to the page, and previous/next links still pass through it.
|
|
53
|
+
|
|
52
54
|
`display` sets the render mode of the page's folder group ([per-group overrides](/docs/content/navigation#per-group-overrides)) and is only meaningful on a folder's `index` page under the generated sidebar — anywhere else (a non-index page, the content root's own `index` page, or any page under an explicit `navigation.sidebar`) it has no group to configure, and Blume warns with `BLUME_SIDEBAR_DISPLAY_IGNORED`.
|
|
53
55
|
|
|
54
56
|
## SEO
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import type { BlumeProject } from "../core/project-graph.ts";
|
|
|
4
4
|
import type { ContentSignalPolicy, ContentSignals } from "../core/schema.ts";
|
|
5
5
|
import { absoluteUrl } from "../core/site-url.ts";
|
|
6
6
|
import { buildRssFeeds } from "../deploy/rss.ts";
|
|
7
|
+
import { AI_CATALOG_PATH, hasAiCatalog } from "./ai-catalog.ts";
|
|
7
8
|
import { hasApiCatalog } from "./api-catalog.ts";
|
|
8
9
|
import { API_PAGES_PATH, API_SEARCH_PATH, OPENAPI_PATH } from "./api/paths.ts";
|
|
9
10
|
|
|
@@ -52,6 +53,7 @@ const askApiUrl = (
|
|
|
52
53
|
/** The `.well-known` discovery URLs a site can publish. */
|
|
53
54
|
interface WellKnownArtifacts {
|
|
54
55
|
httpMessageSignaturesDirectory?: string;
|
|
56
|
+
aiCatalog?: string;
|
|
55
57
|
apiCatalog?: string;
|
|
56
58
|
agentSkills?: string;
|
|
57
59
|
}
|
|
@@ -138,6 +140,9 @@ const wellKnownArtifacts = (
|
|
|
138
140
|
"/.well-known/http-message-signatures-directory"
|
|
139
141
|
);
|
|
140
142
|
}
|
|
143
|
+
if (hasAiCatalog(config)) {
|
|
144
|
+
artifacts.aiCatalog = abs(AI_CATALOG_PATH);
|
|
145
|
+
}
|
|
141
146
|
if (hasApiCatalog(config)) {
|
|
142
147
|
artifacts.apiCatalog = abs("/.well-known/api-catalog");
|
|
143
148
|
}
|