blume 1.7.2 → 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.
Files changed (101) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/cli/chunk-0qymqwzz.js +164 -0
  3. package/dist/cli/chunk-0qymqwzz.js.map +15 -0
  4. package/dist/cli/{chunk-9bkjd11x.js → chunk-0xjyb285.js} +1 -1
  5. package/dist/cli/{chunk-hdm2dkd2.js → chunk-1jefwnfs.js} +13 -7
  6. package/dist/cli/{chunk-hdm2dkd2.js.map → chunk-1jefwnfs.js.map} +3 -3
  7. package/dist/cli/{chunk-n9sra6sy.js → chunk-3r45185y.js} +5 -7
  8. package/dist/cli/{chunk-n9sra6sy.js.map → chunk-3r45185y.js.map} +2 -2
  9. package/dist/cli/{chunk-cvky9gb2.js → chunk-4x36ddpw.js} +3 -3
  10. package/dist/cli/{chunk-mqb2ka8m.js → chunk-5093q3n7.js} +12 -12
  11. package/dist/cli/{chunk-9he6crym.js → chunk-5g0w1e2c.js} +4 -4
  12. package/dist/cli/{chunk-mt76t7dj.js → chunk-5qk08vmp.js} +11 -11
  13. package/dist/cli/{chunk-61j18dwk.js → chunk-7s8hm3b6.js} +7 -2
  14. package/dist/cli/{chunk-61j18dwk.js.map → chunk-7s8hm3b6.js.map} +3 -3
  15. package/dist/cli/{chunk-196vjxp9.js → chunk-8cjtbafj.js} +11 -9
  16. package/dist/cli/{chunk-196vjxp9.js.map → chunk-8cjtbafj.js.map} +2 -2
  17. package/dist/cli/{chunk-aztttvb3.js → chunk-97r59kpr.js} +4 -4
  18. package/dist/cli/{chunk-t3tj0dgr.js → chunk-ahnw3kxw.js} +8 -8
  19. package/dist/cli/{chunk-hs3gbh8p.js → chunk-b27xqwn9.js} +3 -3
  20. package/dist/cli/{chunk-450a7rcr.js → chunk-bf6bt1xt.js} +2 -2
  21. package/dist/cli/{chunk-eevwt1sc.js → chunk-bvwwhd84.js} +13 -13
  22. package/dist/cli/{chunk-12dxjqk7.js → chunk-cjtn640a.js} +17 -17
  23. package/dist/cli/{chunk-12dxjqk7.js.map → chunk-cjtn640a.js.map} +1 -1
  24. package/dist/cli/{chunk-ra1v2nc2.js → chunk-ct47dqpx.js} +14 -3
  25. package/dist/cli/{chunk-ra1v2nc2.js.map → chunk-ct47dqpx.js.map} +4 -3
  26. package/dist/cli/{chunk-ppfvdcd4.js → chunk-dwgcp5sm.js} +1 -1
  27. package/dist/cli/{chunk-3w7b2vcx.js → chunk-e7f42gdj.js} +2 -2
  28. package/dist/cli/{chunk-vkrsvbr5.js → chunk-esphfr8p.js} +8 -8
  29. package/dist/cli/{chunk-vkrsvbr5.js.map → chunk-esphfr8p.js.map} +1 -1
  30. package/dist/cli/{chunk-fmceyezb.js → chunk-ex56aa81.js} +27 -18
  31. package/dist/cli/chunk-ex56aa81.js.map +13 -0
  32. package/dist/cli/{chunk-jbj4qhfw.js → chunk-garjf5z9.js} +2 -2
  33. package/dist/cli/{chunk-wjt80jps.js → chunk-js7saxwm.js} +14 -18
  34. package/dist/cli/{chunk-wjt80jps.js.map → chunk-js7saxwm.js.map} +4 -6
  35. package/dist/cli/{chunk-5n7t497w.js → chunk-k79xp7av.js} +53 -91
  36. package/dist/cli/chunk-k79xp7av.js.map +39 -0
  37. package/dist/cli/{chunk-688e0dde.js → chunk-nn13znc2.js} +1 -1
  38. package/dist/cli/{chunk-ejjx8znq.js → chunk-ps4m1xh4.js} +15 -9
  39. package/dist/cli/{chunk-ejjx8znq.js.map → chunk-ps4m1xh4.js.map} +3 -3
  40. package/dist/cli/{chunk-xhtpx3ff.js → chunk-rqy0s5wh.js} +13 -13
  41. package/dist/cli/{chunk-30e87n55.js → chunk-rz9jmfhz.js} +4 -4
  42. package/dist/cli/{chunk-exeeb35e.js → chunk-vacwm2hv.js} +2 -2
  43. package/dist/cli/{chunk-8cd8tj54.js → chunk-yg63d42r.js} +7 -7
  44. package/dist/cli/index.js +15 -15
  45. package/dist/types/core/config-input.d.ts +33 -0
  46. package/dist/types/core/data.d.ts +2 -0
  47. package/dist/types/core/schema.d.ts +20 -0
  48. package/dist/types/core/types.d.ts +5 -0
  49. package/docs/configuration/ask-ai.mdx +14 -0
  50. package/docs/configuration/index.mdx +3 -1
  51. package/docs/content/navigation.mdx +3 -0
  52. package/docs/content/syntax.mdx +10 -0
  53. package/docs/discoverability/agent-discovery.mdx +82 -1
  54. package/docs/discoverability/index.mdx +1 -1
  55. package/docs/discoverability/llms-txt.mdx +1 -1
  56. package/docs/reference/frontmatter.mdx +2 -0
  57. package/package.json +1 -1
  58. package/src/ai/agent-readability.ts +5 -0
  59. package/src/ai/ai-catalog.ts +241 -0
  60. package/src/ai/link-headers.ts +12 -0
  61. package/src/ai/llms.ts +6 -0
  62. package/src/ai/mcp/discovery.ts +1 -1
  63. package/src/astro/generate.ts +2 -0
  64. package/src/cli/commands/build.ts +3 -1
  65. package/src/components/islands/hooks.ts +50 -1
  66. package/src/components/layout/RootLayout.astro +24 -1
  67. package/src/components/layout/analytics-client.ts +36 -7
  68. package/src/core/config-input.ts +34 -0
  69. package/src/core/data.ts +2 -0
  70. package/src/core/navigation.ts +22 -3
  71. package/src/core/schema.ts +32 -0
  72. package/src/core/types.ts +5 -0
  73. package/src/deploy/artifacts.ts +12 -1
  74. package/src/deploy/headers.ts +6 -0
  75. package/src/deploy/vercel-negotiation.ts +25 -2
  76. package/src/search/build.ts +25 -3
  77. package/src/theme/entry.ts +23 -0
  78. package/dist/cli/chunk-5n7t497w.js.map +0 -40
  79. package/dist/cli/chunk-88by27n5.js +0 -17
  80. package/dist/cli/chunk-88by27n5.js.map +0 -10
  81. package/dist/cli/chunk-fmceyezb.js.map +0 -13
  82. package/dist/cli/chunk-tqa1s0k8.js +0 -69
  83. package/dist/cli/chunk-tqa1s0k8.js.map +0 -11
  84. /package/dist/cli/{chunk-9bkjd11x.js.map → chunk-0xjyb285.js.map} +0 -0
  85. /package/dist/cli/{chunk-cvky9gb2.js.map → chunk-4x36ddpw.js.map} +0 -0
  86. /package/dist/cli/{chunk-mqb2ka8m.js.map → chunk-5093q3n7.js.map} +0 -0
  87. /package/dist/cli/{chunk-9he6crym.js.map → chunk-5g0w1e2c.js.map} +0 -0
  88. /package/dist/cli/{chunk-mt76t7dj.js.map → chunk-5qk08vmp.js.map} +0 -0
  89. /package/dist/cli/{chunk-aztttvb3.js.map → chunk-97r59kpr.js.map} +0 -0
  90. /package/dist/cli/{chunk-t3tj0dgr.js.map → chunk-ahnw3kxw.js.map} +0 -0
  91. /package/dist/cli/{chunk-hs3gbh8p.js.map → chunk-b27xqwn9.js.map} +0 -0
  92. /package/dist/cli/{chunk-450a7rcr.js.map → chunk-bf6bt1xt.js.map} +0 -0
  93. /package/dist/cli/{chunk-eevwt1sc.js.map → chunk-bvwwhd84.js.map} +0 -0
  94. /package/dist/cli/{chunk-ppfvdcd4.js.map → chunk-dwgcp5sm.js.map} +0 -0
  95. /package/dist/cli/{chunk-3w7b2vcx.js.map → chunk-e7f42gdj.js.map} +0 -0
  96. /package/dist/cli/{chunk-jbj4qhfw.js.map → chunk-garjf5z9.js.map} +0 -0
  97. /package/dist/cli/{chunk-688e0dde.js.map → chunk-nn13znc2.js.map} +0 -0
  98. /package/dist/cli/{chunk-xhtpx3ff.js.map → chunk-rqy0s5wh.js.map} +0 -0
  99. /package/dist/cli/{chunk-30e87n55.js.map → chunk-rz9jmfhz.js.map} +0 -0
  100. /package/dist/cli/{chunk-exeeb35e.js.map → chunk-vacwm2hv.js.map} +0 -0
  101. /package/dist/cli/{chunk-8cd8tj54.js.map → chunk-yg63d42r.js.map} +0 -0
@@ -2,28 +2,28 @@
2
2
  import {
3
3
  ensureGitignore
4
4
  } from "./chunk-yzhm0j9q.js";
5
- import"./chunk-61j18dwk.js";
5
+ import"./chunk-7s8hm3b6.js";
6
6
  import {
7
7
  commandMeta,
8
8
  logger
9
9
  } from "./index.js";
10
10
  import {
11
11
  refuseIfDevRunning
12
- } from "./chunk-exeeb35e.js";
13
- import"./chunk-tqa1s0k8.js";
14
- import"./chunk-88by27n5.js";
15
- import"./chunk-5n7t497w.js";
16
- import"./chunk-wjt80jps.js";
17
- import"./chunk-hdm2dkd2.js";
18
- import"./chunk-450a7rcr.js";
12
+ } from "./chunk-vacwm2hv.js";
13
+ import"./chunk-ct47dqpx.js";
14
+ import"./chunk-0qymqwzz.js";
15
+ import"./chunk-1jefwnfs.js";
16
+ import"./chunk-k79xp7av.js";
17
+ import"./chunk-js7saxwm.js";
18
+ import"./chunk-bf6bt1xt.js";
19
19
  import"./chunk-jq5n4avg.js";
20
- import"./chunk-ejjx8znq.js";
21
- import"./chunk-688e0dde.js";
20
+ import"./chunk-ps4m1xh4.js";
21
+ import"./chunk-nn13znc2.js";
22
22
  import"./chunk-vrfp10qk.js";
23
23
  import {
24
24
  prepareProject
25
- } from "./chunk-vkrsvbr5.js";
26
- import"./chunk-jbj4qhfw.js";
25
+ } from "./chunk-esphfr8p.js";
26
+ import"./chunk-garjf5z9.js";
27
27
 
28
28
  // src/cli/commands/check.ts
29
29
  import { existsSync } from "node:fs";
@@ -84,4 +84,4 @@ export {
84
84
  };
85
85
 
86
86
  //# debugId=785FB9FFBD7A7B1164756E2164756E21
87
- //# sourceMappingURL=chunk-xhtpx3ff.js.map
87
+ //# sourceMappingURL=chunk-rqy0s5wh.js.map
@@ -9,13 +9,13 @@ import {
9
9
  reportDiagnosticsJson,
10
10
  reportDiagnostics
11
11
  } from "./index.js";
12
- import"./chunk-61j18dwk.js";
12
+ import"./chunk-7s8hm3b6.js";
13
13
  import {
14
14
  scanProject
15
- } from "./chunk-hdm2dkd2.js";
15
+ } from "./chunk-1jefwnfs.js";
16
16
  import {
17
17
  serverFeatures
18
- } from "./chunk-jbj4qhfw.js";
18
+ } from "./chunk-garjf5z9.js";
19
19
 
20
20
  // src/cli/commands/doctor.ts
21
21
  import { readFileSync } from "node:fs";
@@ -105,4 +105,4 @@ export {
105
105
  };
106
106
 
107
107
  //# debugId=8F001C9B0DBCC9F964756E2164756E21
108
- //# sourceMappingURL=chunk-30e87n55.js.map
108
+ //# sourceMappingURL=chunk-rz9jmfhz.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  resolveRuntimeDir
4
- } from "./chunk-61j18dwk.js";
4
+ } from "./chunk-7s8hm3b6.js";
5
5
  import {
6
6
  logger
7
7
  } from "./index.js";
@@ -133,4 +133,4 @@ var refuseIfDevRunning = (root, action, options = {}) => {
133
133
  export { readDevLock, DevLockHeldError, acquireDevLock, updateDevLockPort, describeDevLock, refuseIfDevRunning };
134
134
 
135
135
  //# debugId=1007843429DAE30664756E2164756E21
136
- //# sourceMappingURL=chunk-exeeb35e.js.map
136
+ //# sourceMappingURL=chunk-vacwm2hv.js.map
@@ -17,24 +17,24 @@ import {
17
17
  writeAgentReport,
18
18
  fixPrompt,
19
19
  launchAgent
20
- } from "./chunk-9bkjd11x.js";
20
+ } from "./chunk-0xjyb285.js";
21
21
  import {
22
22
  deployStaticDir,
23
23
  examplesRouteBase
24
- } from "./chunk-ejjx8znq.js";
24
+ } from "./chunk-ps4m1xh4.js";
25
25
  import {
26
26
  frontmatter_default,
27
27
  scanProject
28
- } from "./chunk-hdm2dkd2.js";
28
+ } from "./chunk-1jefwnfs.js";
29
29
  import {
30
30
  SITE_INFERRING_ADAPTERS
31
- } from "./chunk-61j18dwk.js";
31
+ } from "./chunk-7s8hm3b6.js";
32
32
  import {
33
33
  gradeExternal,
34
34
  probeAll
35
35
  } from "./chunk-vh9w1sgp.js";
36
- import"./chunk-88by27n5.js";
37
- import"./chunk-tqa1s0k8.js";
36
+ import"./chunk-ct47dqpx.js";
37
+ import"./chunk-0qymqwzz.js";
38
38
  import {
39
39
  normalizeBasePath,
40
40
  normalizePath,
@@ -1957,4 +1957,4 @@ export {
1957
1957
  };
1958
1958
 
1959
1959
  //# debugId=59B0974018DD3C3464756E2164756E21
1960
- //# sourceMappingURL=chunk-8cd8tj54.js.map
1960
+ //# sourceMappingURL=chunk-yg63d42r.js.map
package/dist/cli/index.js CHANGED
@@ -400,21 +400,21 @@ var main = defineCommand({
400
400
  version: getBlumeVersion()
401
401
  },
402
402
  subCommands: {
403
- add: lazyCommand(commandMeta.add, () => import("./chunk-3w7b2vcx.js"), "addCommand"),
404
- audit: lazyCommand(commandMeta.audit, () => import("./chunk-8cd8tj54.js"), "auditCommand"),
405
- build: lazyCommand(commandMeta.build, () => import("./chunk-fmceyezb.js"), "buildCommand"),
406
- check: lazyCommand(commandMeta.check, () => import("./chunk-xhtpx3ff.js"), "checkCommand"),
407
- dev: lazyCommand(commandMeta.dev, () => import("./chunk-12dxjqk7.js"), "devCommand"),
408
- doctor: lazyCommand(commandMeta.doctor, () => import("./chunk-30e87n55.js"), "doctorCommand"),
409
- eject: lazyCommand(commandMeta.eject, () => import("./chunk-mqb2ka8m.js"), "ejectCommand"),
410
- eval: lazyCommand(commandMeta.eval, () => import("./chunk-t3tj0dgr.js"), "evalCommand"),
411
- init: lazyCommand(commandMeta.init, () => import("./chunk-mt76t7dj.js"), "initCommand"),
412
- "mcp-stdio": lazyCommand(commandMeta["mcp-stdio"], () => import("./chunk-n9sra6sy.js"), "mcpStdioCommand"),
413
- preview: lazyCommand(commandMeta.preview, () => import("./chunk-hs3gbh8p.js"), "previewCommand"),
414
- sync: lazyCommand(commandMeta.sync, () => import("./chunk-eevwt1sc.js"), "syncCommand"),
415
- translate: lazyCommand(commandMeta.translate, () => import("./chunk-9he6crym.js"), "translateCommand"),
416
- validate: lazyCommand(commandMeta.validate, () => import("./chunk-aztttvb3.js"), "validateCommand"),
417
- version: lazyCommand(commandMeta.version, () => import("./chunk-cvky9gb2.js"), "versionCommand")
403
+ add: lazyCommand(commandMeta.add, () => import("./chunk-e7f42gdj.js"), "addCommand"),
404
+ audit: lazyCommand(commandMeta.audit, () => import("./chunk-yg63d42r.js"), "auditCommand"),
405
+ build: lazyCommand(commandMeta.build, () => import("./chunk-ex56aa81.js"), "buildCommand"),
406
+ check: lazyCommand(commandMeta.check, () => import("./chunk-rqy0s5wh.js"), "checkCommand"),
407
+ dev: lazyCommand(commandMeta.dev, () => import("./chunk-cjtn640a.js"), "devCommand"),
408
+ doctor: lazyCommand(commandMeta.doctor, () => import("./chunk-rz9jmfhz.js"), "doctorCommand"),
409
+ eject: lazyCommand(commandMeta.eject, () => import("./chunk-5093q3n7.js"), "ejectCommand"),
410
+ eval: lazyCommand(commandMeta.eval, () => import("./chunk-ahnw3kxw.js"), "evalCommand"),
411
+ init: lazyCommand(commandMeta.init, () => import("./chunk-5qk08vmp.js"), "initCommand"),
412
+ "mcp-stdio": lazyCommand(commandMeta["mcp-stdio"], () => import("./chunk-3r45185y.js"), "mcpStdioCommand"),
413
+ preview: lazyCommand(commandMeta.preview, () => import("./chunk-b27xqwn9.js"), "previewCommand"),
414
+ sync: lazyCommand(commandMeta.sync, () => import("./chunk-bvwwhd84.js"), "syncCommand"),
415
+ translate: lazyCommand(commandMeta.translate, () => import("./chunk-5g0w1e2c.js"), "translateCommand"),
416
+ validate: lazyCommand(commandMeta.validate, () => import("./chunk-97r59kpr.js"), "validateCommand"),
417
+ version: lazyCommand(commandMeta.version, () => import("./chunk-4x36ddpw.js"), "versionCommand")
418
418
  }
419
419
  });
420
420
  loadEnvFiles(process.cwd());
@@ -680,6 +680,30 @@ export interface AskConfig {
680
680
  /** Starter prompts shown before the first question. */
681
681
  suggestions?: AskSuggestion[];
682
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
+ }
683
707
  /** What the `llms.txt`/`llms-full.txt` files include. */
684
708
  export interface LlmsTxtConfig {
685
709
  /**
@@ -734,6 +758,15 @@ export interface AiConfig {
734
758
  api?: boolean;
735
759
  /** The Ask AI chat assistant. */
736
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;
737
770
  /**
738
771
  * Emit `llms.txt` (an index of the docs for LLMs). Defaults to `true`.
739
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;
@@ -370,6 +370,16 @@ declare const aiConfigSchema: z.ZodObject<{
370
370
  label: z.ZodString;
371
371
  }, z.core.$strict>>>;
372
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
+ }>>;
373
383
  llmsTxt: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
374
384
  details: z.ZodOptional<z.ZodString>;
375
385
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -622,6 +632,16 @@ export declare const blumeConfigSchema: z.ZodObject<{
622
632
  label: z.ZodString;
623
633
  }, z.core.$strict>>>;
624
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
+ }>>;
625
645
  llmsTxt: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
626
646
  details: z.ZodOptional<z.ZodString>;
627
647
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -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
@@ -235,6 +235,20 @@ ai: {
235
235
 
236
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
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
+
238
252
  ## Rate limiting
239
253
 
240
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`:
@@ -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, and `api-catalog` at the [generated API catalog](#api-catalog). The header rides on every surface Blume controls: the dev server (check it with `curl -I localhost:4321`), static builds via the emitted `_headers` file (Netlify and Cloudflare), and Vercel server builds via the deploy's routing rules.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blume",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "description": "Documentation that's fast, AI-ready, and zero-config.",
5
5
  "keywords": [
6
6
  "astro",
@@ -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
  }