blume 2.0.0 → 2.0.1

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 (203) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +2 -2
  4. package/dist/cli/{chunk-f7t03s3g.js → chunk-27g6wdth.js} +2 -2
  5. package/dist/cli/{chunk-mnqj32sj.js → chunk-2hn4b8z7.js} +13 -13
  6. package/dist/cli/chunk-2hn4b8z7.js.map +12 -0
  7. package/dist/cli/{chunk-by2290sx.js → chunk-5shv93fd.js} +2 -2
  8. package/dist/cli/{chunk-a9kptbw5.js → chunk-6crbhc3x.js} +3 -3
  9. package/dist/cli/{chunk-a9kptbw5.js.map → chunk-6crbhc3x.js.map} +1 -1
  10. package/dist/cli/{chunk-mwt1k8n7.js → chunk-6hsn950k.js} +20 -20
  11. package/dist/cli/chunk-6hsn950k.js.map +10 -0
  12. package/dist/cli/{chunk-11j0384y.js → chunk-6vm74dry.js} +13 -13
  13. package/dist/cli/{chunk-11j0384y.js.map → chunk-6vm74dry.js.map} +3 -3
  14. package/dist/cli/{chunk-j8mw0za6.js → chunk-79jhk4py.js} +8 -8
  15. package/dist/cli/{chunk-j8mw0za6.js.map → chunk-79jhk4py.js.map} +2 -2
  16. package/dist/cli/{chunk-nk3ts2xk.js → chunk-82bbrxdn.js} +2 -2
  17. package/dist/cli/{chunk-2q1dwty4.js → chunk-ah61y8py.js} +8 -8
  18. package/dist/cli/{chunk-2q1dwty4.js.map → chunk-ah61y8py.js.map} +4 -4
  19. package/dist/cli/{chunk-zxccj738.js → chunk-ce574jw2.js} +1 -1
  20. package/dist/cli/{chunk-y3e45rc8.js → chunk-ch6g3ar0.js} +3 -3
  21. package/dist/cli/{chunk-beat36xx.js → chunk-dh8cwk36.js} +5 -5
  22. package/dist/cli/{chunk-beat36xx.js.map → chunk-dh8cwk36.js.map} +2 -2
  23. package/dist/cli/{chunk-1w8dp3qb.js → chunk-epjnccmv.js} +13 -13
  24. package/dist/cli/{chunk-ernrthtr.js → chunk-f2z5v128.js} +13 -13
  25. package/dist/cli/{chunk-zg2gtj10.js → chunk-fs23ddbb.js} +2 -2
  26. package/dist/cli/{chunk-7ez8ny0t.js → chunk-fxypxtvm.js} +2 -2
  27. package/dist/cli/{chunk-tzne8qfq.js → chunk-fz5wtpmh.js} +13 -13
  28. package/dist/cli/{chunk-b5aj94ah.js → chunk-hdpx1tax.js} +4 -4
  29. package/dist/cli/{chunk-d80hr03s.js → chunk-jwyddg7y.js} +9 -9
  30. package/dist/cli/{chunk-d80hr03s.js.map → chunk-jwyddg7y.js.map} +2 -2
  31. package/dist/cli/{chunk-fh5hj5jt.js → chunk-kdp5q7ke.js} +15 -15
  32. package/dist/cli/{chunk-6k8vp3ta.js → chunk-kpf8rrjc.js} +9 -9
  33. package/dist/cli/{chunk-6k8vp3ta.js.map → chunk-kpf8rrjc.js.map} +3 -3
  34. package/dist/cli/{chunk-5a2z0198.js → chunk-m3vmjgmq.js} +9 -9
  35. package/dist/cli/{chunk-5a2z0198.js.map → chunk-m3vmjgmq.js.map} +2 -2
  36. package/dist/cli/{chunk-bctazmbk.js → chunk-mb2919y2.js} +4 -4
  37. package/dist/cli/{chunk-79njf86q.js → chunk-q5163e60.js} +13 -13
  38. package/dist/cli/{chunk-xaz13gwg.js → chunk-qkqwkpte.js} +196 -208
  39. package/dist/cli/{chunk-xaz13gwg.js.map → chunk-qkqwkpte.js.map} +48 -48
  40. package/dist/cli/{chunk-sqn5t4q0.js → chunk-qs4q5p4e.js} +3 -3
  41. package/dist/cli/{chunk-bw22s759.js → chunk-qwsrynx5.js} +1 -1
  42. package/dist/cli/{chunk-z01ze5c1.js → chunk-s1p84fyh.js} +15 -15
  43. package/dist/cli/{chunk-pnnvybbk.js → chunk-s6jhgk0q.js} +5 -5
  44. package/dist/cli/{chunk-pnnvybbk.js.map → chunk-s6jhgk0q.js.map} +2 -2
  45. package/dist/cli/{chunk-f2972sbt.js → chunk-vtk4a6dg.js} +1 -1
  46. package/dist/cli/{chunk-z1f5arsg.js → chunk-wgm7m9qk.js} +21 -21
  47. package/dist/cli/{chunk-z1f5arsg.js.map → chunk-wgm7m9qk.js.map} +4 -4
  48. package/dist/cli/{chunk-bnbmcwfb.js → chunk-wm7js3j9.js} +5 -5
  49. package/dist/cli/{chunk-bnbmcwfb.js.map → chunk-wm7js3j9.js.map} +3 -3
  50. package/dist/cli/{chunk-d1tadaw7.js → chunk-yt5n7ppj.js} +3 -3
  51. package/dist/cli/{chunk-pat2zzwc.js → chunk-yw7dm696.js} +1 -1
  52. package/dist/cli/{chunk-pat2zzwc.js.map → chunk-yw7dm696.js.map} +1 -1
  53. package/dist/cli/{chunk-88cpgt6h.js → chunk-zxcczpyx.js} +1 -1
  54. package/dist/cli/{chunk-41za066z.js → chunk-zxh4d9vy.js} +4 -4
  55. package/dist/cli/index.js +17 -17
  56. package/dist/types/ai/agent-readability.d.ts +1 -1
  57. package/dist/types/ai/api/paths.d.ts +1 -1
  58. package/dist/types/ai/ask-context.d.ts +7 -7
  59. package/dist/types/ai/ask.d.ts +43 -43
  60. package/dist/types/ai/index.d.ts +3 -3
  61. package/dist/types/ai/openapi-components.d.ts +1 -1
  62. package/dist/types/ai/serializers.d.ts +1 -1
  63. package/dist/types/ai/visibility.d.ts +1 -1
  64. package/dist/types/core/config-input.d.ts +17 -17
  65. package/dist/types/core/config.d.ts +3 -3
  66. package/dist/types/core/data.d.ts +3 -3
  67. package/dist/types/core/i18n-ui.d.ts +6 -8
  68. package/dist/types/core/schema.d.ts +5 -5
  69. package/dist/types/core/unrecognized-keys.d.ts +1 -1
  70. package/dist/types/search/documents.d.ts +1 -1
  71. package/dist/types/search/orama-index.d.ts +1 -1
  72. package/docs/02-deployment.mdx +4 -4
  73. package/docs/03-upgrading.mdx +22 -9
  74. package/docs/04-migrating.mdx +4 -4
  75. package/docs/08-faq.mdx +3 -3
  76. package/docs/advanced/custom-pages.mdx +2 -2
  77. package/docs/advanced/skills.mdx +1 -1
  78. package/docs/cli/audit.mdx +3 -3
  79. package/docs/cli/doctor.mdx +3 -3
  80. package/docs/cli/evals.mdx +7 -7
  81. package/docs/cli/index.mdx +2 -2
  82. package/docs/cli/translate.mdx +8 -8
  83. package/docs/configuration/{ask-ai.mdx → assistant.mdx} +19 -19
  84. package/docs/configuration/customization.mdx +2 -2
  85. package/docs/configuration/index.mdx +2 -2
  86. package/docs/configuration/meta.ts +1 -1
  87. package/docs/configuration/search.mdx +1 -1
  88. package/docs/content/i18n.mdx +2 -2
  89. package/docs/content/islands.mdx +1 -1
  90. package/docs/discoverability/agent-discovery.mdx +1 -1
  91. package/docs/discoverability/index.mdx +1 -1
  92. package/docs/index.mdx +2 -2
  93. package/package.json +1 -1
  94. package/skills/blume/SKILL.md +5 -5
  95. package/skills/blume-migrate/SKILL.md +2 -2
  96. package/src/ai/agent-readability.ts +4 -4
  97. package/src/ai/api/paths.ts +1 -1
  98. package/src/ai/ask-context.ts +7 -7
  99. package/src/ai/ask-data.ts +2 -2
  100. package/src/ai/ask.ts +84 -71
  101. package/src/ai/cors.ts +3 -3
  102. package/src/ai/index.ts +16 -16
  103. package/src/ai/openapi-components.ts +1 -1
  104. package/src/ai/serializers.ts +1 -1
  105. package/src/ai/visibility.ts +1 -1
  106. package/src/astro/generate.ts +19 -18
  107. package/src/astro/module-types.ts +1 -1
  108. package/src/astro/runtime-deps.ts +6 -6
  109. package/src/astro/templates.ts +26 -26
  110. package/src/blume-modules.d.ts +2 -2
  111. package/src/cli/commands/audit.ts +1 -1
  112. package/src/cli/commands/doctor.ts +7 -5
  113. package/src/cli/commands/eval.ts +3 -3
  114. package/src/cli/commands/migrate.ts +2 -2
  115. package/src/cli/commands/translate.ts +3 -3
  116. package/src/cli/commands/upgrade.ts +2 -2
  117. package/src/cli/required-secrets.ts +3 -3
  118. package/src/components/copy-feedback.ts +1 -1
  119. package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
  120. package/src/components/islands/{ask-ai.tsx → assistant.tsx} +23 -23
  121. package/src/components/islands/hooks.ts +14 -12
  122. package/src/components/layout/Header.astro +10 -10
  123. package/src/components/layout/PageLayout.astro +6 -6
  124. package/src/components/layout/Pagination.astro +7 -7
  125. package/src/components/layout/ReferenceLayout.astro +1 -1
  126. package/src/components/layout/RootLayout.astro +6 -6
  127. package/src/components/layout/Search.astro +13 -13
  128. package/src/components/layout/analytics-client.ts +1 -1
  129. package/src/components/layout/drawer-inert.ts +1 -1
  130. package/src/components/openapi/description.ts +2 -2
  131. package/src/core/code-fences.ts +1 -1
  132. package/src/core/config-input.ts +19 -19
  133. package/src/core/config.ts +3 -3
  134. package/src/core/data.ts +3 -3
  135. package/src/core/i18n-ui.ts +35 -9
  136. package/src/core/request-body.ts +1 -1
  137. package/src/core/schema.ts +25 -20
  138. package/src/core/server-features.ts +2 -2
  139. package/src/core/ui-packs/ar.ts +4 -5
  140. package/src/core/ui-packs/bg.ts +4 -5
  141. package/src/core/ui-packs/bn.ts +4 -5
  142. package/src/core/ui-packs/ca.ts +4 -5
  143. package/src/core/ui-packs/cs.ts +4 -5
  144. package/src/core/ui-packs/da.ts +4 -5
  145. package/src/core/ui-packs/de.ts +4 -5
  146. package/src/core/ui-packs/el.ts +4 -5
  147. package/src/core/ui-packs/es.ts +4 -5
  148. package/src/core/ui-packs/fa.ts +4 -5
  149. package/src/core/ui-packs/fi.ts +4 -5
  150. package/src/core/ui-packs/fr.ts +4 -5
  151. package/src/core/ui-packs/he.ts +4 -5
  152. package/src/core/ui-packs/hi.ts +4 -5
  153. package/src/core/ui-packs/hr.ts +4 -5
  154. package/src/core/ui-packs/hu.ts +4 -5
  155. package/src/core/ui-packs/id.ts +4 -5
  156. package/src/core/ui-packs/it.ts +4 -5
  157. package/src/core/ui-packs/ja.ts +4 -5
  158. package/src/core/ui-packs/ko.ts +4 -5
  159. package/src/core/ui-packs/nl.ts +4 -5
  160. package/src/core/ui-packs/no.ts +4 -5
  161. package/src/core/ui-packs/pl.ts +4 -5
  162. package/src/core/ui-packs/pt-br.ts +4 -5
  163. package/src/core/ui-packs/pt.ts +4 -5
  164. package/src/core/ui-packs/ro.ts +4 -5
  165. package/src/core/ui-packs/ru.ts +4 -5
  166. package/src/core/ui-packs/sk.ts +4 -5
  167. package/src/core/ui-packs/sr.ts +4 -5
  168. package/src/core/ui-packs/sv.ts +4 -5
  169. package/src/core/ui-packs/th.ts +4 -5
  170. package/src/core/ui-packs/tr.ts +4 -5
  171. package/src/core/ui-packs/uk.ts +4 -5
  172. package/src/core/ui-packs/vi.ts +4 -5
  173. package/src/core/ui-packs/zh-tw.ts +4 -5
  174. package/src/core/ui-packs/zh.ts +4 -5
  175. package/src/core/unrecognized-keys.ts +1 -1
  176. package/src/registry/eject.ts +19 -18
  177. package/src/search/documents.ts +2 -2
  178. package/src/search/orama-index.ts +1 -1
  179. package/src/translate/report.ts +1 -1
  180. package/src/upgrade/upgrade.ts +1 -1
  181. package/dist/cli/chunk-mnqj32sj.js.map +0 -12
  182. package/dist/cli/chunk-mwt1k8n7.js.map +0 -10
  183. /package/dist/cli/{chunk-f7t03s3g.js.map → chunk-27g6wdth.js.map} +0 -0
  184. /package/dist/cli/{chunk-by2290sx.js.map → chunk-5shv93fd.js.map} +0 -0
  185. /package/dist/cli/{chunk-nk3ts2xk.js.map → chunk-82bbrxdn.js.map} +0 -0
  186. /package/dist/cli/{chunk-zxccj738.js.map → chunk-ce574jw2.js.map} +0 -0
  187. /package/dist/cli/{chunk-y3e45rc8.js.map → chunk-ch6g3ar0.js.map} +0 -0
  188. /package/dist/cli/{chunk-1w8dp3qb.js.map → chunk-epjnccmv.js.map} +0 -0
  189. /package/dist/cli/{chunk-ernrthtr.js.map → chunk-f2z5v128.js.map} +0 -0
  190. /package/dist/cli/{chunk-zg2gtj10.js.map → chunk-fs23ddbb.js.map} +0 -0
  191. /package/dist/cli/{chunk-7ez8ny0t.js.map → chunk-fxypxtvm.js.map} +0 -0
  192. /package/dist/cli/{chunk-tzne8qfq.js.map → chunk-fz5wtpmh.js.map} +0 -0
  193. /package/dist/cli/{chunk-b5aj94ah.js.map → chunk-hdpx1tax.js.map} +0 -0
  194. /package/dist/cli/{chunk-fh5hj5jt.js.map → chunk-kdp5q7ke.js.map} +0 -0
  195. /package/dist/cli/{chunk-bctazmbk.js.map → chunk-mb2919y2.js.map} +0 -0
  196. /package/dist/cli/{chunk-79njf86q.js.map → chunk-q5163e60.js.map} +0 -0
  197. /package/dist/cli/{chunk-sqn5t4q0.js.map → chunk-qs4q5p4e.js.map} +0 -0
  198. /package/dist/cli/{chunk-bw22s759.js.map → chunk-qwsrynx5.js.map} +0 -0
  199. /package/dist/cli/{chunk-z01ze5c1.js.map → chunk-s1p84fyh.js.map} +0 -0
  200. /package/dist/cli/{chunk-f2972sbt.js.map → chunk-vtk4a6dg.js.map} +0 -0
  201. /package/dist/cli/{chunk-d1tadaw7.js.map → chunk-yt5n7ppj.js.map} +0 -0
  202. /package/dist/cli/{chunk-88cpgt6h.js.map → chunk-zxcczpyx.js.map} +0 -0
  203. /package/dist/cli/{chunk-41za066z.js.map → chunk-zxh4d9vy.js.map} +0 -0
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  commandsFor,
7
7
  detectProjectPackageManager
8
- } from "./chunk-xaz13gwg.js";
8
+ } from "./chunk-qkqwkpte.js";
9
9
 
10
10
  // src/astro/islands.ts
11
11
  import { readFile } from "node:fs/promises";
@@ -100,7 +100,7 @@ var canResolveFrom = (fromDir, spec) => {
100
100
  var unresolved = (owner, deps, root, pkgDir) => deps.filter((dep) => !(canResolveFrom(root, dep) || canResolveFrom(pkgDir, dep))).map((dep) => ({ dep, install: [dep], owner }));
101
101
  var searchProviderDependencies = (provider, root, pkgDir = packageRoot()) => unresolved(`Search adapter "${provider.kind}"`, provider.runtimeDeps, root, pkgDir);
102
102
  var sourceAdapterDependencies = (sources, root, pkgDir = packageRoot()) => sources.flatMap((source) => unresolved(`Content source "${source.kind}"`, source.runtimeDeps, root, pkgDir));
103
- var askProviderDependencies = (ask, root, pkgDir = packageRoot()) => ask?.enabled && !ask.endpoint ? unresolved(`Ask AI provider "${ask.provider.kind}"`, ask.provider.runtimeDeps, root, pkgDir) : [];
103
+ var assistantProviderDependencies = (ask, root, pkgDir = packageRoot()) => ask?.enabled && !ask.endpoint ? unresolved(`Assistant provider "${ask.provider.kind}"`, ask.provider.runtimeDeps, root, pkgDir) : [];
104
104
  var deploymentAdapterDependencies = (deployment, root, pkgDir = packageRoot()) => unresolved(`Deployment adapter "${deployment.kind}"`, deployment.runtimeDeps, root, pkgDir);
105
105
  var ISLAND_FRAMEWORK_DEPS = new Map([
106
106
  ["svelte", "@astrojs/svelte"],
@@ -119,7 +119,7 @@ var islandFrameworkDependencies = (frameworks, root) => [...frameworks].flatMap(
119
119
  var missingRuntimeDependencies = (config, root, frameworks = [], pkgDir = packageRoot()) => [
120
120
  ...searchProviderDependencies(config.search.provider, root, pkgDir),
121
121
  ...sourceAdapterDependencies(config.content.sources, root, pkgDir),
122
- ...askProviderDependencies(config.ai.ask, root, pkgDir),
122
+ ...assistantProviderDependencies(config.ai.assistant, root, pkgDir),
123
123
  ...deploymentAdapterDependencies(config.deployment, root, pkgDir),
124
124
  ...islandFrameworkDependencies(frameworks, root)
125
125
  ];
@@ -141,5 +141,5 @@ var missingDependencyDiagnostic = async (config, root, severity, frameworks = []
141
141
 
142
142
  export { readClientMode, discoverIslands, missingDependencyDiagnostic };
143
143
 
144
- //# debugId=340EEA216AAB3A0164756E2164756E21
145
- //# sourceMappingURL=chunk-bnbmcwfb.js.map
144
+ //# debugId=137E521D7DE6F81064756E2164756E21
145
+ //# sourceMappingURL=chunk-wm7js3j9.js.map
@@ -3,9 +3,9 @@
3
3
  "sources": ["../src/astro/islands.ts", "../src/astro/runtime-deps.ts"],
4
4
  "sourcesContent": [
5
5
  "import { readFile } from \"node:fs/promises\";\n\nimport pMap from \"p-map\";\nimport { basename, join } from \"pathe\";\nimport { glob } from \"tinyglobby\";\n\n/** Astro hydration directives Blume applies to a convention island. */\nexport type IslandClientMode = \"idle\" | \"load\" | \"only\" | \"visible\";\n\n/** Client framework an island is authored in, inferred from its extension. */\nexport type IslandFramework = \"react\" | \"svelte\" | \"vue\";\n\n/** A discovered `islands/` component, ready to wrap and expose to MDX. */\nexport interface IslandSpec {\n /** Hydration directive, from `export const client` or the default. */\n client: IslandClientMode;\n /** Absolute path to the island source file. */\n file: string;\n /** Astro client framework, used to enable the renderer and for `client:only`. */\n framework: IslandFramework;\n /** Component name used in MDX — the file's basename without extension. */\n name: string;\n}\n\nexport interface IslandDiscovery {\n islands: IslandSpec[];\n warnings: string[];\n}\n\n/** Hydration mode used when an island doesn't declare one. */\nconst DEFAULT_CLIENT: IslandClientMode = \"visible\";\n\n/** Ceiling on concurrent island-file reads; unbounded fan-out risks EMFILE. */\nconst READ_CONCURRENCY = 16;\n\nconst VALID_MODES = new Set<IslandClientMode>([\n \"idle\",\n \"load\",\n \"only\",\n \"visible\",\n]);\n\n/** Whether a declared client mode is one Astro's directives support. */\nconst isClientMode = (mode: string): mode is IslandClientMode =>\n // SAFETY: `Set.has` only compares identity at runtime; the assertion widens\n // the lookup key so the narrower-typed set accepts it, and the `has` result\n // is exactly the predicate being claimed.\n VALID_MODES.has(mode as IslandClientMode);\n\n/** Island extensions mapped to the Astro renderer that handles them. */\nconst FRAMEWORK_BY_EXT = new Map<string, IslandFramework>([\n [\"jsx\", \"react\"],\n [\"svelte\", \"svelte\"],\n [\"tsx\", \"react\"],\n [\"vue\", \"vue\"],\n]);\n\n// Captures the extension so we can both strip it from the name and pick the\n// framework. Kept in sync with the glob below.\nconst ISLAND_FILE = /\\.(?<ext>jsx|svelte|tsx|vue)$/u;\n\n// Matches `export const client = \"load\"` (with optional type annotation and\n// either quote style). Read statically so we never execute island code in Node.\nconst CLIENT_EXPORT =\n /export\\s+const\\s+client\\s*(?::[^=]+)?=\\s*[\"'](?<mode>\\w+)[\"']/u;\n\nexport const readClientMode = (\n source: string,\n file: string,\n warnings: string[]\n): IslandClientMode => {\n const mode = source.match(CLIENT_EXPORT)?.groups?.mode;\n if (!mode) {\n return DEFAULT_CLIENT;\n }\n if (!isClientMode(mode)) {\n warnings.push(\n `Island \"${file}\" declares an unknown client mode \"${mode}\"; defaulting to \"${DEFAULT_CLIENT}\". Use \"load\", \"idle\", \"visible\", or \"only\".`\n );\n return DEFAULT_CLIENT;\n }\n return mode;\n};\n\n/**\n * Discover convention islands under `<root>/islands`. Every `.tsx`/`.jsx`/\n * `.vue`/`.svelte` file becomes a globally-available, hydrated MDX component\n * named after the file (which must be PascalCase to be usable as a JSX tag).\n * Hydration defaults to `client:visible`; a file opts out with `export const\n * client = \"load\" | \"idle\" | \"only\"`. Discovery is path-based (a glob), so no\n * user code is executed.\n */\nexport const discoverIslands = async (\n root: string\n): Promise<IslandDiscovery> => {\n const dir = join(root, \"islands\");\n // Enumerate everything and let ISLAND_FILE do the filtering below, so\n // unknown extensions take the skip guard instead of relying on a glob\n // pattern staying in sync with the regex and framework map.\n const matches = await glob([\"**/*\"], {\n absolute: true,\n cwd: dir,\n onlyFiles: true,\n });\n const files = matches.toSorted();\n const sources = await pMap(files, (file) => readFile(file, \"utf-8\"), {\n concurrency: READ_CONCURRENCY,\n });\n\n const islands: IslandSpec[] = [];\n const warnings: string[] = [];\n const seen = new Map<string, string>();\n\n // Extracted so the skip paths become early `return`s (one `continue` budget\n // per loop under the lint rule) instead of `continue` statements.\n const collectIsland = (file: string, source: string): void => {\n const base = basename(file);\n const ext = base.match(ISLAND_FILE)?.groups?.ext;\n const framework = ext ? FRAMEWORK_BY_EXT.get(ext) : undefined;\n if (!framework) {\n return;\n }\n const name = base.replace(ISLAND_FILE, \"\");\n // The name is used verbatim as both an MDX tag and an unquoted object key\n // in the generated island map, so it must be a plain PascalCase identifier\n // — a `-`, `.`, or space (e.g. `Time-Picker.tsx`) would otherwise emit a\n // syntax-error module and fail the whole build with no pointer to the file.\n if (!/^[A-Z][A-Za-z0-9_]*$/u.test(name)) {\n warnings.push(\n `Island \"${file}\" must have a PascalCase identifier filename to be used in MDX (letters, digits, and underscores only, e.g. Counter.tsx → <Counter />); skipping it.`\n );\n return;\n }\n const existing = seen.get(name);\n if (existing) {\n warnings.push(\n `Two islands both resolve to <${name}> (\"${existing}\" and \"${file}\"); ignoring the second. Give them distinct filenames.`\n );\n return;\n }\n seen.set(name, file);\n islands.push({\n client: readClientMode(source, file, warnings),\n file,\n framework,\n name,\n });\n };\n\n for (const [index, file] of files.entries()) {\n collectIsland(file, sources[index] ?? \"\");\n }\n\n return { islands, warnings };\n};\n",
6
- "import { createRequire } from \"node:module\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { join } from \"pathe\";\n\nimport {\n commandsFor,\n detectProjectPackageManager,\n} from \"../cli/init/scaffold.ts\";\nimport { packageRoot } from \"../core/package-root.ts\";\nimport type { ResolvedConfig } from \"../core/schema.ts\";\nimport type { Diagnostic } from \"../core/types.ts\";\n\n/** Whether a module specifier resolves from a directory via node resolution. */\nexport const canResolveFrom = (fromDir: string, spec: string): boolean => {\n try {\n createRequire(pathToFileURL(join(fromDir, \"_.js\")).href).resolve(spec);\n return true;\n } catch {\n return false;\n }\n};\n\n/** A package a configured feature imports that the project can't resolve. */\nexport interface MissingDependency {\n /** The package whose absence was detected. */\n dep: string;\n /** Every package to install for the feature (a framework and its integration). */\n install: string[];\n /** What needs it, as the message names it: `Search adapter \"algolia\"`. */\n owner: string;\n}\n\n/**\n * The declared `deps` of one feature that resolve neither from the project\n * root nor from the Blume package itself. A dep is available when the project\n * installed it OR Blume ships it (the same set the `.blume` deps link exposes\n * to the build). Resolving from the project root alone falsely flagged a\n * shipped SDK like Orama (the default provider) as missing whenever it wasn't\n * hoisted into the project, e.g. under isolated linkers. Each package is\n * resolved from its real location rather than through the `.blume` junction,\n * which can't be traversed reliably for store-symlinked deps.\n */\nconst unresolved = (\n owner: string,\n deps: readonly string[],\n root: string,\n pkgDir: string\n): MissingDependency[] =>\n deps\n .filter(\n (dep) => !(canResolveFrom(root, dep) || canResolveFrom(pkgDir, dep))\n )\n .map((dep) => ({ dep, install: [dep], owner }));\n\n/**\n * The search adapter's SDK, when missing. Provider SDKs are optional peers,\n * so a hosted adapter's client import fails in Vite without one.\n */\nexport const searchProviderDependencies = (\n provider: ResolvedConfig[\"search\"][\"provider\"],\n root: string,\n pkgDir: string = packageRoot()\n): MissingDependency[] =>\n unresolved(\n `Search adapter \"${provider.kind}\"`,\n provider.runtimeDeps,\n root,\n pkgDir\n );\n\n/** Content source SDKs (`@notionhq/client`, `@sanity/client`), when missing. */\nexport const sourceAdapterDependencies = (\n sources: ResolvedConfig[\"content\"][\"sources\"],\n root: string,\n pkgDir: string = packageRoot()\n): MissingDependency[] =>\n sources.flatMap((source) =>\n unresolved(\n `Content source \"${source.kind}\"`,\n source.runtimeDeps,\n root,\n pkgDir\n )\n );\n\n/**\n * The Ask AI adapter's provider SDK, when missing — only `gateway` needs\n * nothing beyond the core `ai` package Blume ships. An external `endpoint`\n * means no generated route, so no SDK is imported.\n */\nexport const askProviderDependencies = (\n ask: ResolvedConfig[\"ai\"][\"ask\"],\n root: string,\n pkgDir: string = packageRoot()\n): MissingDependency[] =>\n ask?.enabled && !ask.endpoint\n ? unresolved(\n `Ask AI provider \"${ask.provider.kind}\"`,\n ask.provider.runtimeDeps,\n root,\n pkgDir\n )\n : [];\n\n/**\n * The deployment adapter's package, when missing. Node and Vercel ship with\n * Blume, so theirs always resolve; Netlify and Cloudflare are optional peers.\n * The generated astro.config.mjs imports the package directly, so without it\n * the build dies with an opaque ERR_MODULE_NOT_FOUND from the hidden config.\n */\nexport const deploymentAdapterDependencies = (\n deployment: ResolvedConfig[\"deployment\"],\n root: string,\n pkgDir: string = packageRoot()\n): MissingDependency[] =>\n unresolved(\n `Deployment adapter \"${deployment.kind}\"`,\n deployment.runtimeDeps,\n root,\n pkgDir\n );\n\n/** Astro integration package each non-React island framework needs installed. */\nconst ISLAND_FRAMEWORK_DEPS = new Map([\n [\"svelte\", \"@astrojs/svelte\"],\n [\"vue\", \"@astrojs/vue\"],\n]);\n\n/**\n * A Vue/Svelte island's Astro integration, when missing — the generated config\n * imports it. React ships with Blume, so it never needs this. The framework\n * itself is installed alongside its integration.\n */\nexport const islandFrameworkDependencies = (\n frameworks: Iterable<string>,\n root: string\n): MissingDependency[] =>\n [...frameworks].flatMap((framework) => {\n const dep = ISLAND_FRAMEWORK_DEPS.get(framework);\n return dep && !canResolveFrom(root, dep)\n ? [\n {\n dep,\n install: [dep, framework],\n owner: `Island framework \"${framework}\"`,\n },\n ]\n : [];\n });\n\n/**\n * Every package the resolved config's adapters (and the given island\n * frameworks) import that isn't installed: the search SDK, content source\n * SDKs, the Ask AI provider SDK, the deployment adapter, and island\n * integrations. The adapters' `runtimeDeps` are the one place that knows.\n */\nexport const missingRuntimeDependencies = (\n config: ResolvedConfig,\n root: string,\n frameworks: Iterable<string> = [],\n pkgDir: string = packageRoot()\n): MissingDependency[] => [\n ...searchProviderDependencies(config.search.provider, root, pkgDir),\n ...sourceAdapterDependencies(config.content.sources, root, pkgDir),\n ...askProviderDependencies(config.ai.ask, root, pkgDir),\n ...deploymentAdapterDependencies(config.deployment, root, pkgDir),\n ...islandFrameworkDependencies(frameworks, root),\n];\n\n/**\n * One diagnostic for every package the config imports that isn't installed,\n * with the command that installs them all through the project's package\n * manager (`npm install`, `pnpm add`, …), or undefined when nothing is\n * missing. `blume build` raises it as an error and `blume dev` as a warning;\n * `blume doctor` reports it before a build ever runs.\n */\nexport const missingDependencyDiagnostic = async (\n config: ResolvedConfig,\n root: string,\n severity: Diagnostic[\"severity\"],\n frameworks: Iterable<string> = [],\n pkgDir: string = packageRoot()\n): Promise<Diagnostic | undefined> => {\n const missing = missingRuntimeDependencies(config, root, frameworks, pkgDir);\n if (missing.length === 0) {\n return undefined;\n }\n const { add } = commandsFor(await detectProjectPackageManager(root));\n const needs = missing.map(({ dep, owner }) => `${owner} needs \"${dep}\"`);\n const packages = [...new Set(missing.flatMap(({ install }) => install))];\n return {\n code: \"BLUME_DEPENDENCY_MISSING\",\n message:\n missing.length === 1\n ? `${needs[0]}, which isn't installed.`\n : `These packages aren't installed: ${needs.join(\"; \")}.`,\n severity,\n suggestion: `Install ${packages.length === 1 ? \"it\" : \"them\"}: \\`${add} ${packages.join(\" \")}\\`.`,\n };\n};\n"
6
+ "import { createRequire } from \"node:module\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { join } from \"pathe\";\n\nimport {\n commandsFor,\n detectProjectPackageManager,\n} from \"../cli/init/scaffold.ts\";\nimport { packageRoot } from \"../core/package-root.ts\";\nimport type { ResolvedConfig } from \"../core/schema.ts\";\nimport type { Diagnostic } from \"../core/types.ts\";\n\n/** Whether a module specifier resolves from a directory via node resolution. */\nexport const canResolveFrom = (fromDir: string, spec: string): boolean => {\n try {\n createRequire(pathToFileURL(join(fromDir, \"_.js\")).href).resolve(spec);\n return true;\n } catch {\n return false;\n }\n};\n\n/** A package a configured feature imports that the project can't resolve. */\nexport interface MissingDependency {\n /** The package whose absence was detected. */\n dep: string;\n /** Every package to install for the feature (a framework and its integration). */\n install: string[];\n /** What needs it, as the message names it: `Search adapter \"algolia\"`. */\n owner: string;\n}\n\n/**\n * The declared `deps` of one feature that resolve neither from the project\n * root nor from the Blume package itself. A dep is available when the project\n * installed it OR Blume ships it (the same set the `.blume` deps link exposes\n * to the build). Resolving from the project root alone falsely flagged a\n * shipped SDK like Orama (the default provider) as missing whenever it wasn't\n * hoisted into the project, e.g. under isolated linkers. Each package is\n * resolved from its real location rather than through the `.blume` junction,\n * which can't be traversed reliably for store-symlinked deps.\n */\nconst unresolved = (\n owner: string,\n deps: readonly string[],\n root: string,\n pkgDir: string\n): MissingDependency[] =>\n deps\n .filter(\n (dep) => !(canResolveFrom(root, dep) || canResolveFrom(pkgDir, dep))\n )\n .map((dep) => ({ dep, install: [dep], owner }));\n\n/**\n * The search adapter's SDK, when missing. Provider SDKs are optional peers,\n * so a hosted adapter's client import fails in Vite without one.\n */\nexport const searchProviderDependencies = (\n provider: ResolvedConfig[\"search\"][\"provider\"],\n root: string,\n pkgDir: string = packageRoot()\n): MissingDependency[] =>\n unresolved(\n `Search adapter \"${provider.kind}\"`,\n provider.runtimeDeps,\n root,\n pkgDir\n );\n\n/** Content source SDKs (`@notionhq/client`, `@sanity/client`), when missing. */\nexport const sourceAdapterDependencies = (\n sources: ResolvedConfig[\"content\"][\"sources\"],\n root: string,\n pkgDir: string = packageRoot()\n): MissingDependency[] =>\n sources.flatMap((source) =>\n unresolved(\n `Content source \"${source.kind}\"`,\n source.runtimeDeps,\n root,\n pkgDir\n )\n );\n\n/**\n * The assistant adapter's provider SDK, when missing — only `gateway` needs\n * nothing beyond the core `ai` package Blume ships. An external `endpoint`\n * means no generated route, so no SDK is imported.\n */\nexport const assistantProviderDependencies = (\n ask: ResolvedConfig[\"ai\"][\"assistant\"],\n root: string,\n pkgDir: string = packageRoot()\n): MissingDependency[] =>\n ask?.enabled && !ask.endpoint\n ? unresolved(\n `Assistant provider \"${ask.provider.kind}\"`,\n ask.provider.runtimeDeps,\n root,\n pkgDir\n )\n : [];\n\n/**\n * The deployment adapter's package, when missing. Node and Vercel ship with\n * Blume, so theirs always resolve; Netlify and Cloudflare are optional peers.\n * The generated astro.config.mjs imports the package directly, so without it\n * the build dies with an opaque ERR_MODULE_NOT_FOUND from the hidden config.\n */\nexport const deploymentAdapterDependencies = (\n deployment: ResolvedConfig[\"deployment\"],\n root: string,\n pkgDir: string = packageRoot()\n): MissingDependency[] =>\n unresolved(\n `Deployment adapter \"${deployment.kind}\"`,\n deployment.runtimeDeps,\n root,\n pkgDir\n );\n\n/** Astro integration package each non-React island framework needs installed. */\nconst ISLAND_FRAMEWORK_DEPS = new Map([\n [\"svelte\", \"@astrojs/svelte\"],\n [\"vue\", \"@astrojs/vue\"],\n]);\n\n/**\n * A Vue/Svelte island's Astro integration, when missing — the generated config\n * imports it. React ships with Blume, so it never needs this. The framework\n * itself is installed alongside its integration.\n */\nexport const islandFrameworkDependencies = (\n frameworks: Iterable<string>,\n root: string\n): MissingDependency[] =>\n [...frameworks].flatMap((framework) => {\n const dep = ISLAND_FRAMEWORK_DEPS.get(framework);\n return dep && !canResolveFrom(root, dep)\n ? [\n {\n dep,\n install: [dep, framework],\n owner: `Island framework \"${framework}\"`,\n },\n ]\n : [];\n });\n\n/**\n * Every package the resolved config's adapters (and the given island\n * frameworks) import that isn't installed: the search SDK, content source\n * SDKs, the assistant provider SDK, the deployment adapter, and island\n * integrations. The adapters' `runtimeDeps` are the one place that knows.\n */\nexport const missingRuntimeDependencies = (\n config: ResolvedConfig,\n root: string,\n frameworks: Iterable<string> = [],\n pkgDir: string = packageRoot()\n): MissingDependency[] => [\n ...searchProviderDependencies(config.search.provider, root, pkgDir),\n ...sourceAdapterDependencies(config.content.sources, root, pkgDir),\n ...assistantProviderDependencies(config.ai.assistant, root, pkgDir),\n ...deploymentAdapterDependencies(config.deployment, root, pkgDir),\n ...islandFrameworkDependencies(frameworks, root),\n];\n\n/**\n * One diagnostic for every package the config imports that isn't installed,\n * with the command that installs them all through the project's package\n * manager (`npm install`, `pnpm add`, …), or undefined when nothing is\n * missing. `blume build` raises it as an error and `blume dev` as a warning;\n * `blume doctor` reports it before a build ever runs.\n */\nexport const missingDependencyDiagnostic = async (\n config: ResolvedConfig,\n root: string,\n severity: Diagnostic[\"severity\"],\n frameworks: Iterable<string> = [],\n pkgDir: string = packageRoot()\n): Promise<Diagnostic | undefined> => {\n const missing = missingRuntimeDependencies(config, root, frameworks, pkgDir);\n if (missing.length === 0) {\n return undefined;\n }\n const { add } = commandsFor(await detectProjectPackageManager(root));\n const needs = missing.map(({ dep, owner }) => `${owner} needs \"${dep}\"`);\n const packages = [...new Set(missing.flatMap(({ install }) => install))];\n return {\n code: \"BLUME_DEPENDENCY_MISSING\",\n message:\n missing.length === 1\n ? `${needs[0]}, which isn't installed.`\n : `These packages aren't installed: ${needs.join(\"; \")}.`,\n severity,\n suggestion: `Install ${packages.length === 1 ? \"it\" : \"them\"}: \\`${add} ${packages.join(\" \")}\\`.`,\n };\n};\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "340EEA216AAB3A0164756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "137E521D7DE6F81064756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  commandsFor,
4
4
  detectProjectPackageManager
5
- } from "./chunk-xaz13gwg.js";
5
+ } from "./chunk-qkqwkpte.js";
6
6
  import {
7
7
  packageRoot,
8
8
  logger
9
9
  } from "./index.js";
10
10
  import {
11
11
  EJECTED_CONFIG_HEADER
12
- } from "./chunk-mnqj32sj.js";
12
+ } from "./chunk-2hn4b8z7.js";
13
13
 
14
14
  // src/cli/eject-scripts.ts
15
15
  import { existsSync, readFileSync } from "node:fs";
@@ -76,4 +76,4 @@ var refuseIfEjected = async (root, script) => {
76
76
  export { updatePackageScripts, isEjectedProject, refuseIfEjected };
77
77
 
78
78
  //# debugId=5D0834602D1CB35B64756E2164756E21
79
- //# sourceMappingURL=chunk-d1tadaw7.js.map
79
+ //# sourceMappingURL=chunk-yt5n7ppj.js.map
@@ -460,4 +460,4 @@ export {
460
460
  };
461
461
 
462
462
  //# debugId=F74FD7C10F87037364756E2164756E21
463
- //# sourceMappingURL=chunk-pat2zzwc.js.map
463
+ //# sourceMappingURL=chunk-yw7dm696.js.map
@@ -6,7 +6,7 @@
6
6
  "import { once } from \"node:events\";\nimport type { Readable, Writable } from \"node:stream\";\n\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\n\nimport type { McpData } from \"./data.ts\";\nimport { buildServer, createIndexProvider } from \"./server.ts\";\n\n/** Streams for `serveMcpStdio`, injectable so tests can use in-memory pipes. */\nexport interface McpStdioStreams {\n stdin?: Readable;\n stdout?: Writable;\n}\n\n/**\n * Serve a precomputed MCP data snapshot over a stdio transport until the\n * client hangs up.\n *\n * The SDK transport reads its input stream but never watches for its end, so\n * a client that simply closes the pipe — which is how every MCP host shuts a\n * stdio server down — would leave the process running forever. EOF on the\n * input stream therefore ends the serve loop explicitly.\n */\nexport const serveMcpStdio = async (\n data: McpData,\n streams: McpStdioStreams = {}\n): Promise<void> => {\n const stdin = streams.stdin ?? process.stdin;\n const stdout = streams.stdout ?? process.stdout;\n const transport = new StdioServerTransport(stdin, stdout);\n const server = buildServer(\n data,\n createIndexProvider(data.documents, data.defaultLocale)\n );\n await server.connect(transport);\n await once(stdin, \"end\");\n await transport.close();\n};\n",
7
7
  "import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport type { ServerOptions } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { WebStandardStreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js\";\nimport {\n CallToolRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ReadResourceRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\n\nimport { withBasePath } from \"../../core/base-path.ts\";\nimport type { McpData } from \"./data.ts\";\nimport {\n createIndexProvider,\n getNavigation,\n getPageMarkdown,\n listPages,\n normalizeRoute,\n searchDocs,\n TOOL_INPUTS,\n urlFor,\n} from \"./query.ts\";\nimport type { OramaIndexProvider } from \"./query.ts\";\nimport { MCP_TOOLS } from \"./tools.ts\";\n\nexport { createIndexProvider } from \"./query.ts\";\nexport type { OramaIndexProvider } from \"./query.ts\";\n\n/**\n * The low-level SDK `Server` is used (rather than the high-level `McpServer`)\n * because the latter's `registerTool` is generic over the caller's Zod instance;\n * Blume's zod and the SDK's may resolve to different copies, whose types don't\n * unify. The operations themselves live in `query.ts`, shared with the JSON\n * docs API; this module is the MCP transport over them.\n */\n\n/** Every page resource is the page's agent Markdown. */\nconst RESOURCE_MIME_TYPE = \"text/markdown\";\n/** The MCP spec's JSON-RPC code for an unknown resource URI. */\nconst RESOURCE_NOT_FOUND = -32_002;\n/** URI scheme for page resources when no `deployment.site` is configured. */\nconst LOCAL_RESOURCE_SCHEME = \"blume:\";\n\nconst CORS_HEADERS = {\n \"Access-Control-Allow-Headers\":\n \"Content-Type, Mcp-Session-Id, Mcp-Protocol-Version\",\n \"Access-Control-Allow-Methods\": \"GET, POST, OPTIONS\",\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Expose-Headers\": \"Mcp-Session-Id\",\n};\n\n/**\n * A tool's advertised JSON Schema. The dialect key is dropped (noise in a\n * tools/list payload), as is the root `additionalProperties: false` — the\n * runtime strips unknown keys rather than rejecting them, and the advertised\n * schema shouldn't promise stricter validation than the server performs.\n */\nconst inputSchemaFor = (schema: z.ZodType) => {\n const {\n $schema: _dialect,\n additionalProperties: _closed,\n ...rest\n } = z.toJSONSchema(schema);\n return rest;\n};\n\n/** The `tools/list` payload, derived from shared metadata + input schemas. */\nconst TOOL_DEFINITIONS = MCP_TOOLS.map((tool) => ({\n annotations: tool.annotations,\n description: tool.description,\n inputSchema: inputSchemaFor(\n // SAFETY: TOOL_INPUTS declares a schema for every MCP_TOOLS name; the two\n // lists are maintained together so names and descriptions never drift.\n TOOL_INPUTS[tool.name as keyof typeof TOOL_INPUTS]\n ),\n name: tool.name,\n title: tool.title,\n}));\n\n/** One `resources/list` entry: a page served as `text/markdown`. */\ninterface PageResource {\n description?: string;\n mimeType: string;\n name: string;\n title: string;\n uri: string;\n}\n\n/**\n * A page's resource URI. Resource URIs must be absolute, so this is the page's\n * served URL when a site is configured (the same URL `search_docs` and\n * `list_pages` emit, so an agent can hand either back to `resources/read`),\n * and a `blume:` URI carrying the based route otherwise.\n */\nconst resourceUri = (route: string, data: McpData): string =>\n data.site\n ? urlFor(route, data)\n : `${LOCAL_RESOURCE_SCHEME}${withBasePath(data.base, route)}`;\n\n/** The `pages` key a resource URI (either form, or a bare route) names. */\nconst resourceRoute = (uri: string, data: McpData): string =>\n normalizeRoute(\n uri.startsWith(LOCAL_RESOURCE_SCHEME)\n ? uri.slice(LOCAL_RESOURCE_SCHEME.length)\n : uri,\n data\n );\n\n/** A tool call's text result, marked as an error when `isError` is set. */\nconst text = (value: string, isError = false) => {\n const content = [{ text: value, type: \"text\" as const }];\n return isError ? { content, isError: true } : { content };\n};\n\n/** Construct a fresh MCP server with Blume's read-only docs tools registered. */\nexport const buildServer = (\n data: McpData,\n index: OramaIndexProvider\n): Server => {\n const capabilities = { resources: {}, tools: {} };\n const serverOptions: ServerOptions = data.instructions\n ? { capabilities, instructions: data.instructions }\n : { capabilities };\n const server = new Server(\n { name: data.name, version: data.version },\n serverOptions\n );\n\n server.setRequestHandler(ListToolsRequestSchema, () => ({\n tools: TOOL_DEFINITIONS,\n }));\n\n // Every page doubles as a resource, so a client that attaches context by\n // URI (rather than calling tools) can browse and read the docs too. The\n // list is the same route set `list_pages` returns; reading one serves the\n // same agent Markdown `get_page` does.\n server.setRequestHandler(ListResourcesRequestSchema, () => ({\n resources: data.routes.map((route) => {\n const resource: PageResource = {\n mimeType: RESOURCE_MIME_TYPE,\n name: route.title,\n title: route.title,\n uri: resourceUri(route.route, data),\n };\n if (route.description) {\n resource.description = route.description;\n }\n return resource;\n }),\n }));\n\n server.setRequestHandler(ReadResourceRequestSchema, (request) => {\n const { uri } = request.params;\n const markdown = getPageMarkdown(data, resourceRoute(uri, data));\n if (markdown === undefined) {\n throw new McpError(\n RESOURCE_NOT_FOUND,\n `No page found at \"${uri}\". Use resources/list or list_pages to find valid URIs.`\n );\n }\n return {\n contents: [{ mimeType: RESOURCE_MIME_TYPE, text: markdown, uri }],\n };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { arguments: args = {}, name } = request.params;\n\n if (name === \"search_docs\") {\n const results = await searchDocs(\n data,\n index,\n TOOL_INPUTS.search_docs.parse(args)\n );\n return text(JSON.stringify(results, null, 2));\n }\n\n if (name === \"get_page\") {\n const input = TOOL_INPUTS.get_page.parse(args);\n const key = normalizeRoute(input.route, data);\n const markdown = getPageMarkdown(data, key);\n if (markdown === undefined) {\n return text(\n `No page found at \"${key}\". Use list_pages or search_docs to find valid routes.`,\n true\n );\n }\n return text(markdown);\n }\n\n if (name === \"list_pages\") {\n const listing = listPages(data, TOOL_INPUTS.list_pages.parse(args));\n return text(JSON.stringify(listing, null, 2));\n }\n\n if (name === \"get_navigation\") {\n const result = getNavigation(\n data,\n TOOL_INPUTS.get_navigation.parse(args)\n );\n if (\"error\" in result) {\n return text(result.error, true);\n }\n return text(JSON.stringify(result.navigation, null, 2));\n }\n\n return text(`Unknown tool: ${name}`, true);\n });\n\n return server;\n};\n\n/**\n * Build a stateless Streamable-HTTP MCP request handler from a data snapshot.\n *\n * The Orama index is built once and reused; a fresh `Server` and transport are\n * created per request (required by the SDK's stateless mode, which skips session\n * tracking). `enableJsonResponse` makes each call a plain request/response — no\n * SSE — which suits read-only docs tools and runs on any adapter (Node, Vercel,\n * Netlify, Cloudflare). CORS is added so browser-based connectors (e.g.\n * claude.ai) can reach the endpoint.\n */\nexport const createMcpFetchHandler = (\n data: McpData\n): ((request: Request) => Promise<Response>) => {\n const index = createIndexProvider(data.documents, data.defaultLocale);\n\n return async (request: Request): Promise<Response> => {\n if (request.method === \"OPTIONS\") {\n return new Response(null, { headers: CORS_HEADERS, status: 204 });\n }\n if (request.method === \"GET\") {\n // No server-initiated streams are needed for read-only tools.\n return new Response(\"Method Not Allowed\", {\n headers: { ...CORS_HEADERS, Allow: \"POST, OPTIONS\" },\n status: 405,\n });\n }\n\n const server = buildServer(data, index);\n const transport = new WebStandardStreamableHTTPServerTransport({\n enableJsonResponse: true,\n // The SDK enables stateless mode only when this is `undefined`; `null` is\n // not an accepted value for the `(() => string) | undefined` option.\n // oxlint-disable-next-line sonarjs/no-undefined-assignment\n sessionIdGenerator: undefined,\n });\n await server.connect(transport);\n const response = await transport.handleRequest(request);\n\n const headers = new Headers(response.headers);\n for (const [key, value] of Object.entries(CORS_HEADERS)) {\n headers.set(key, value);\n }\n return new Response(response.body, {\n headers,\n status: response.status,\n statusText: response.statusText,\n });\n };\n};\n",
8
8
  "import { z } from \"zod\";\n\nimport {\n normalizeRoute as normalizePageRoute,\n stripBasePath,\n withBasePath,\n} from \"../../core/base-path.ts\";\nimport { absoluteUrl } from \"../../core/site-url.ts\";\nimport { trimEnd } from \"../../core/trim.ts\";\nimport type { Navigation } from \"../../core/types.ts\";\nimport { buildOramaIndex, queryOramaIndex } from \"../../search/orama-index.ts\";\nimport type { OramaDoc } from \"../../search/orama-index.ts\";\nimport type { McpData } from \"./data.ts\";\n\n/**\n * The read-only docs operations behind the agent-facing surfaces — search,\n * page lookup, the page listing, and the navigation tree — over an\n * {@link McpData} snapshot. The MCP server (`server.ts`) and the JSON docs API\n * (`ai/api/handlers.ts`) are both thin transports over these, so a tool call\n * and its REST twin can never answer differently. SDK-free on purpose: the\n * prerendered API endpoints import this module without pulling the MCP SDK\n * into their bundle.\n *\n * Each input is defined once in Zod: the runtime parse and the JSON Schema\n * advertised by `tools/list` (via `z.toJSONSchema`) derive from the same\n * definition, so they cannot drift.\n */\n\n/** Default and maximum number of hits returned by a search. */\nexport const DEFAULT_SEARCH_LIMIT = 8;\nexport const MAX_SEARCH_LIMIT = 20;\n/** Excerpt length when a page has no description. */\nconst EXCERPT_LENGTH = 200;\n\n// Each field is a preprocess pipe: the input side accepts the sloppy shapes\n// LLM callers actually send (a bare string for an array field, `[]`/`{}`\n// meaning \"no filter\", out-of-range limits clamped rather than rejected), and\n// the pipe's *output* side is the clean shape — which is exactly what\n// `z.toJSONSchema` emits for `tools/list`. No coercion can ever fail, so a\n// tool call is never rejected on argument shape, matching the previous\n// hand-rolled coercions.\n\n/**\n * The optional content-type filter `search_docs` and `list_pages` share.\n * `[]` or no usable strings mean \"no filter\", not \"match nothing\"; a bare\n * string is accepted as a one-element list.\n */\nconst contentTypesField = z.preprocess((value) => {\n const list = (Array.isArray(value) ? value : [value]).filter(\n (entry): entry is string => typeof entry === \"string\"\n );\n return list.length > 0 ? list : undefined;\n}, z.array(z.string()).optional().describe('Only include pages of these content types (frontmatter `type`, e.g. `[\"doc\", \"rfc\"]`). `list_pages` shows each page\\'s type. Omit to include every type.'));\n\n/**\n * The optional facet filter `search_docs` and `list_pages` share. Only\n * string-valued entries survive; an empty `{}` means \"no filter\".\n */\n/** Accepts any plain object, so the string-valued entries can be sifted out. */\nconst looseFacetObject = z.record(z.string(), z.unknown());\n\nconst filtersField = z.preprocess((value) => {\n const candidate = looseFacetObject.safeParse(value);\n if (!candidate.success) {\n return;\n }\n const entries = Object.entries(candidate.data).filter(\n (entry): entry is [string, string] => typeof entry[1] === \"string\"\n );\n return entries.length > 0 ? Object.fromEntries(entries) : undefined;\n}, z.record(z.string(), z.string()).optional().describe('Only include pages matching every facet, key → required value (e.g. `{\"status\": \"enforced\"}`). Facets are metadata the site declares per content type; `list_pages` shows each page\\'s facet values. Omit for no facet filtering.'));\n\n/** Clamped into range rather than rejected; non-numeric means the default. */\nconst limitField = z.preprocess(\n (value) => {\n // `Number` is the identity on numbers, so one conversion covers both the\n // well-typed call and a numeric string.\n const num = Number(value);\n return Number.isFinite(num)\n ? Math.min(Math.max(Math.trunc(num), 1), MAX_SEARCH_LIMIT)\n : undefined;\n },\n z\n .int()\n .min(1)\n .max(MAX_SEARCH_LIMIT)\n .optional()\n .describe(`Maximum hits to return (default ${DEFAULT_SEARCH_LIMIT}).`)\n);\n\n/** A required text field; a missing or non-string value coerces to \"\". */\nconst textField = (description: string) =>\n z.preprocess((value) => {\n const parsed = z.string().safeParse(value);\n return parsed.success ? parsed.data : \"\";\n }, z.string().describe(description));\n\n/** An optional trimmed text field; blank or non-string means \"absent\". */\nconst optionalTextField = (description: string) =>\n z.preprocess((value) => {\n const parsed = z.string().safeParse(value);\n const trimmed = parsed.success ? parsed.data.trim() : \"\";\n return trimmed || undefined;\n }, z.string().optional().describe(description));\n\n/** The optional locale filter `search_docs` and `list_pages` share. */\nconst localeField = optionalTextField(\n \"Only include pages in this locale (e.g. `fr`). Omit for every language.\"\n);\n\n/** The optional docs-version scope `search_docs` and `list_pages` share. */\nconst versionField = optionalTextField(\n 'Docs version to scope to on a versioned site: `\"latest\"` (the default — current docs only), `\"all\"` (every version), or an archived version id (e.g. `\"v1.0\"`). Ignored when the site is unversioned.'\n);\n\n/** Every tool's input schema — the runtime parse and tools/list source. */\nexport const TOOL_INPUTS = {\n get_navigation: z.object({\n locale: optionalTextField(\n \"Locale whose navigation tree to return (defaults to the default locale).\"\n ),\n version: optionalTextField(\n \"Archived version id whose tree to return (defaults to the current docs).\"\n ),\n }),\n get_page: z.object({\n route: textField(\"The page route, e.g. `/guides/install`.\"),\n }),\n list_pages: z.object({\n contentTypes: contentTypesField,\n filters: filtersField,\n locale: localeField,\n version: versionField,\n }),\n search_docs: z.object({\n contentTypes: contentTypesField,\n filters: filtersField,\n limit: limitField,\n locale: localeField,\n query: textField(\"The search query.\"),\n version: versionField,\n }),\n};\n\n/** One `search_docs` result entry; `version` only appears on versioned sites. */\nexport interface SearchHitPayload {\n contentType: string | undefined;\n excerpt: string;\n facets: Record<string, string> | undefined;\n route: string;\n title: string;\n url: string;\n version?: string;\n}\n\n/** One `list_pages` entry; `version` only appears on versioned sites. */\nexport interface PageListingPayload {\n contentType: string;\n description: string | undefined;\n facets: Record<string, string> | undefined;\n lastModified: string | null;\n route: string;\n title: string;\n url: string;\n version?: string;\n}\n\n/** Whether a page's facet values satisfy every requested filter entry. */\nconst matchesFacets = (\n facets: Record<string, string> | undefined,\n filters: Record<string, string>\n): boolean =>\n Object.entries(filters).every(([key, value]) => facets?.[key] === value);\n\n/**\n * Resolve the `version` scope on a versioned site: `undefined` disables the\n * filter (`\"all\"`), `\"\"` is the current docs (the default — agents almost\n * always want the live documentation), and anything else is an archived id\n * (an unknown id simply matches nothing). On an unversioned site the input is\n * ignored entirely. The input arrives pre-trimmed (blank coerced to absent)\n * from the tool's input schema.\n */\nconst asVersionScope = (\n value: string | undefined,\n data: McpData\n): string | undefined => {\n if (!data.archivedVersions) {\n return;\n }\n if (value === \"all\") {\n return;\n }\n if (value === undefined || value === \"latest\" || value === \"current\") {\n return \"\";\n }\n return value;\n};\n\n/**\n * Error message for a `get_navigation` version id that isn't a configured\n * archived version, or `null` when the id is valid (or the site is\n * unversioned, where the id is ignored like the other tools' scopes). Unlike\n * `asVersionScope`'s match-nothing filters, a bad id here would otherwise\n * silently return the *current* tree posing as the requested snapshot.\n */\nconst unknownVersionError = (\n versionId: string | undefined,\n data: McpData\n): string | null =>\n versionId &&\n data.archivedVersions &&\n !data.archivedVersions.includes(versionId)\n ? `Unknown version \"${versionId}\". Archived versions: ${data.archivedVersions.join(\", \")}.`\n : null;\n\n/**\n * Normalize a user-supplied route to a `pages` key (`/`, `/a/b`, no suffix).\n * Accepts a full URL too — `search_docs` hits and llms.txt entries carry\n * `site` + `deployment.base`, and an agent following \"pass a route from\n * `search_docs`\" will hand one straight back — reducing it to its base-less,\n * percent-decoded path.\n */\nexport const normalizeRoute = (input: string, data: McpData): string => {\n let value = input.trim();\n if (/^https?:\\/\\//iu.test(value)) {\n try {\n value = new URL(value).pathname;\n } catch {\n // Not parseable as a URL after all; treat it as a path.\n }\n }\n try {\n value = decodeURI(value);\n } catch {\n // Malformed percent sequence — compare it as written.\n }\n // Trailing slashes come off before the suffix so `/a/b.md/` still loses its\n // `.md`; normalizePageRoute then settles the leading slash.\n const noSuffix = trimEnd(value, \"/\").replace(/\\.mdx?$/u, \"\");\n return stripBasePath(data.base, normalizePageRoute(noSuffix));\n};\n\n/** Build the absolute (or root-relative) URL for a route. */\nexport const urlFor = (route: string, data: McpData): string => {\n // Routes are base-less manifest paths; layer `deployment.base` on top so the\n // URL matches where the page is served (the sitemap/llms.txt convention).\n const path = withBasePath(data.base, route);\n // Concatenate rather than `new URL(path, site)` — a root-absolute path\n // would drop the base path of a subpath deployment (`acme.com/docs`).\n return data.site ? absoluteUrl(data.site, path) : path;\n};\n\n/** A hit's excerpt: its description, else the head of its content with an\n * ellipsis only when something was actually cut off. */\nconst excerptFor = (doc: OramaDoc): string => {\n if (doc.description) {\n return doc.description;\n }\n const head = doc.content.slice(0, EXCERPT_LENGTH).trim();\n return doc.content.length > EXCERPT_LENGTH ? `${head}…` : head;\n};\n\n/** Lazily builds the Orama index over a snapshot's documents, once. */\nexport type OramaIndexProvider = () => Promise<\n Awaited<ReturnType<typeof buildOramaIndex>>\n>;\n\n/**\n * Memoize the search index so every server built from a snapshot shares it.\n * `locale` is the snapshot's `defaultLocale`, forwarded so non-Latin scripts\n * (Japanese and Chinese, but equally Cyrillic, Greek, Hebrew, Devanagari…)\n * get a word-segmenting tokenizer.\n */\nexport const createIndexProvider = (\n documents: OramaDoc[],\n locale?: string\n): OramaIndexProvider => {\n let dbPromise: ReturnType<OramaIndexProvider> | null = null;\n return function provideIndex() {\n dbPromise ??= buildOramaIndex(documents, locale);\n return dbPromise;\n };\n};\n\n/** Full-text search over the snapshot; the `search_docs` operation. */\nexport const searchDocs = async (\n data: McpData,\n index: OramaIndexProvider,\n input: z.output<typeof TOOL_INPUTS.search_docs>\n): Promise<SearchHitPayload[]> => {\n const db = await index();\n const hits = await queryOramaIndex(\n db,\n input.query,\n input.limit ?? DEFAULT_SEARCH_LIMIT,\n {\n contentTypes: input.contentTypes,\n facets: input.filters,\n locale: input.locale,\n version: asVersionScope(input.version, data),\n }\n );\n // `route` is the key `get_page` takes (the tool descriptions promise\n // it); `url` is where the page is served.\n return hits.map((doc: OramaDoc) => {\n const hit: SearchHitPayload = {\n contentType: doc.contentType,\n excerpt: excerptFor(doc),\n facets: doc.facets,\n route: doc.route,\n title: doc.title,\n url: urlFor(doc.route, data),\n };\n if (data.archivedVersions) {\n hit.version = doc.version ?? \"\";\n }\n return hit;\n });\n};\n\n/** A page's agent Markdown, or `undefined` when no page has the route. */\nexport const getPageMarkdown = (\n data: McpData,\n route: string\n): string | undefined => data.pages[route];\n\n/** Every non-hidden route matching the filters; the `list_pages` operation. */\nexport const listPages = (\n data: McpData,\n input: z.output<typeof TOOL_INPUTS.list_pages>\n): PageListingPayload[] => {\n const { contentTypes, filters, locale } = input;\n const versionScope = asVersionScope(input.version, data);\n return data.routes\n .filter(\n (route) =>\n (!contentTypes || contentTypes.includes(route.contentType)) &&\n (!filters || matchesFacets(route.facets, filters)) &&\n (!locale || route.locale === locale) &&\n (versionScope === undefined || route.version === versionScope)\n )\n .map((route) => {\n const listing: PageListingPayload = {\n contentType: route.contentType,\n description: route.description,\n facets: route.facets,\n lastModified: route.lastModified,\n route: route.route,\n title: route.title,\n url: urlFor(route.route, data),\n };\n if (data.archivedVersions) {\n listing.version = route.version;\n }\n return listing;\n });\n};\n\n/**\n * The navigation tree for a locale and version; the `get_navigation`\n * operation. A version id selects the snapshot's tree; a locale selects its\n * language (falling back through the default locale to any tree the snapshot\n * has). Without a version, a locale selects the current docs' localized\n * tree. An unknown id on a versioned site is an error string — the current\n * tree would silently masquerade as the requested snapshot.\n */\nexport const getNavigation = (\n data: McpData,\n input: z.output<typeof TOOL_INPUTS.get_navigation>\n): { error: string } | { navigation: Navigation } => {\n const { locale, version: versionId } = input;\n const unknownVersion = unknownVersionError(versionId, data);\n if (unknownVersion) {\n return { error: unknownVersion };\n }\n let { navigation } = data;\n const byLocale = versionId\n ? data.navigationByVersion?.[versionId]\n : undefined;\n if (byLocale) {\n navigation =\n (locale ? byLocale[locale] : undefined) ??\n byLocale[data.defaultLocale ?? \"\"] ??\n Object.values(byLocale)[0] ??\n navigation;\n } else if (locale && data.navigationByLocale?.[locale]) {\n navigation = data.navigationByLocale[locale];\n }\n return { navigation };\n};\n",
9
- "import { create, insertMultiple, search } from \"@orama/orama\";\nimport type {\n AnyOrama,\n EnumArrComparisonOperator,\n EnumComparisonOperator,\n Tokenizer,\n} from \"@orama/orama\";\n\n/**\n * The minimal document shape both the client-side search dialog and the\n * server-side MCP `search_docs` tool index. Mirrors the `blume-search.json`\n * entries built by `buildSearchDocuments`.\n */\nexport interface OramaDoc {\n content: string;\n description: string;\n route: string;\n title: string;\n /** Locale code; indexed as an enum so queries can filter to one language. */\n locale?: string;\n /**\n * Docs version; indexed as an enum so queries can filter to one version.\n * The current docs carry `\"\"`, which the enum stores and matches exactly.\n */\n version?: string;\n /** Resolved page `type`; indexed as an enum so queries can filter by type. */\n contentType?: string;\n /** Declared facet values (`content.types.<type>.facets`), key → value. */\n facets?: Record<string, string>;\n /** Carried through for the search dialog's breadcrumb + filter pills. Stored\n * but not indexed, so they ride along on the returned document untouched. */\n breadcrumb?: string[];\n section?: string;\n}\n\nconst SCHEMA = {\n content: \"string\",\n // Enums (not full-text \"string\") so `where` does an exact-match filter.\n contentType: \"enum\",\n description: \"string\",\n // Facets, flattened to `key:value` terms — enum[] so one static schema\n // serves every project's facet keys, with `containsAll` matching a filter\n // set. Derived from `facets` at insert time.\n facetTerms: \"enum[]\",\n locale: \"enum\",\n route: \"string\",\n title: \"string\",\n version: \"enum\",\n} as const;\n\n/** Flatten a facet map to the `key:value` terms the `facetTerms` enum holds. */\nconst toFacetTerms = (facets: Record<string, string>): string[] =>\n Object.entries(facets).map(([key, value]) => `${key}:${value}`);\n\n/** Title and description outrank body text, matching the search dialog. */\nconst BOOST = { description: 2, title: 3 };\n\n/**\n * The script whose text Orama's default tokenizer keeps mostly intact. Its\n * delimiter class is `/[^A-Za-zàèéìòóù0-9_'-]+/`, so every character outside\n * that set counts as a separator: text in any other script collapses to zero\n * tokens and every query silently returns no hits. Unspaced scripts are the\n * best-known casualty, but the failure is not about spacing — Russian, Greek,\n * Hebrew and Hindi lose their tokens the same way Japanese does.\n */\nconst LATIN_SCRIPT = \"Latn\";\n\n/**\n * Parse a locale tag, tolerating the legacy forms that reach here from\n * hand-written config: underscores (`ru_RU`), POSIX suffixes (`ja_JP.UTF-8`,\n * `zh_TW@Big5`), and extlang tags ICU rejects (`zh-cmn-Hans`). The POSIX\n * codeset/modifier suffix is dropped first — it carries no BCP 47 meaning,\n * and dropping it keeps an explicit script subtag in play: `az_Cyrl.UTF-8`\n * must resolve to `Cyrl`, not fall back to `az` and maximize to Latin.\n * `Intl.Locale` still throws on extlang forms, so a failed parse retries with\n * the primary language subtag alone — enough to resolve the script, which is\n * all that gates the tokenizer. Returns `undefined` when even that subtag is\n * unparseable.\n */\nconst parseLocale = (tag: string): Intl.Locale | undefined => {\n const hyphenated = tag.replace(/[.@].*$/u, \"\").replaceAll(\"_\", \"-\");\n try {\n return new Intl.Locale(hyphenated);\n } catch {\n // Retry below with the primary subtag.\n }\n const [primary = \"\"] = hyphenated.split(\"-\");\n try {\n return new Intl.Locale(primary);\n } catch {\n return undefined;\n }\n};\n\n/**\n * The maximized locale — script and language filled in from CLDR likely-subtags\n * and alias data, so `ru` resolves to `Cyrl`, `cmn` to the canonical `zh`, and\n * an explicit script subtag is honored: `sr-Latn` reports `Latn` while\n * `az-Cyrl` reports `Cyrl`. `undefined` for tags {@link parseLocale} cannot\n * salvage; a well-formed tag ICU knows nothing about keeps an `undefined`\n * script instead.\n */\nconst resolveLocale = (tag: string): Intl.Locale | undefined =>\n parseLocale(tag)?.maximize();\n\n/**\n * Indexes whose language maximizes to one of these scripts are built as\n * character bigrams rather than whole segments, and queried to match\n * accordingly. Japanese and Chinese write compounds in {@link BIGRAM_SCRIPTS}\n * without delimiters, and the writing system — not the language subtag — is\n * what carries that convention: `yue` (Cantonese) maximizes to `Hant` and\n * needs bigrams the same way `zh` does. Korean maximizes to `Kore` and Thai\n * to `Thai`; Korean separates words with spaces and Thai has no comparable\n * bigram convention, so both keep the plain segmented tokens even where a\n * page mixes in Han or kana.\n *\n * Dictionary segmentation alone drops the adjacency that makes a compound term\n * distinctive: 資金決済法 becomes 資金 / 決済 / 法, and because Orama scores a\n * bag of words, a page that merely mentions each fragment somewhere outranks\n * the page about the law itself. Bigrams put that adjacency back as index\n * terms, and dropping the whole-segment tokens keeps a fragment as common as\n * 法 from matching on its own.\n */\nconst BIGRAM_INDEX_SCRIPTS = new Set([\"Hans\", \"Hant\", \"Jpan\"]);\n\n/**\n * Whether the index keyed to this tokenizer language is built from bigrams.\n * {@link segmentingTokenizer} stores the canonical maximized language, so the\n * builder and the strict pass in {@link queryOramaIndex} share this predicate\n * — an index is never built from bigrams that the query side then matches\n * loosely, or vice versa.\n */\nconst isBigramLanguage = (language: string): boolean => {\n const script = resolveLocale(language)?.script;\n return script !== undefined && BIGRAM_INDEX_SCRIPTS.has(script);\n};\n\n/**\n * Segments written entirely in these scripts are the ones re-cut into bigrams,\n * matching the scripts Lucene's CJK analyzer bigrams. Property escapes rather\n * than ranges, so ideographs outside the basic plane are covered as well —\n * 𠮟, the 常用漢字表 form of しかる, is one. The literals that follow belong to\n * no script of their own but appear only inside such words: the iteration\n * marks, the prolonged sound mark, and the halfwidth voiced sound marks.\n */\nconst BIGRAM_SCRIPTS =\n /^[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}々〆〇ー゙゚]+$/u;\n\n/**\n * One index term inside a word-like segment. Being word-like does not make a\n * segment all letters: UAX #29 keeps connector punctuation, format characters\n * and mid-number punctuation *within* a word, so `Intl.Segmenter` reports\n * スネーク_ケース and robots.txt as one segment each. A term is a run of\n * letters, combining marks and digits. Marks are spelling, not punctuation —\n * Thai writes vowels and tones as combining marks, so dropping them leaves\n * consonant skeletons that collapse distinct words (เสื้อ, shirt, and เสือ,\n * tiger, differ by one mark). Two separators stay where the surrounding text\n * makes them part of the word: an apostrophe followed by a letter (don't),\n * and a decimal point or thousands separator flanked by digits (1.0.3,\n * 1,000) — split, either would leave one-letter and one-digit fragments that\n * co-occur on unrelated pages.\n */\nconst TERM =\n /[\\p{L}\\p{M}\\p{N}]+(?:(?:['’](?=\\p{L})|(?<=\\p{N})[.,](?=\\p{N}))[\\p{L}\\p{M}\\p{N}]+)*/gu;\n\n/**\n * A term written entirely in Latin script (plus digits and the separators\n * {@link TERM} keeps within a word). Orama's default tokenizer folds the\n * accented vowels it recognizes (café → cafe), so a segmented index folds\n * Latin terms too — otherwise switching a Cyrillic- or Greek-default site to\n * the segmenting tokenizer would silently drop the unaccented-query matches\n * the default tokenizer provided. Only all-Latin terms fold: marks are\n * spelling elsewhere (Thai vowels and tones; the breve that separates\n * Cyrillic й from и), so a term carrying any other script keeps its marks.\n */\nconst LATIN_TERM = /^[\\p{Script=Latin}\\p{N}'’.,]+$/u;\n\nconst MARKS = /\\p{M}+/gu;\n\nconst foldDiacritics = (term: string): string =>\n LATIN_TERM.test(term) ? term.normalize(\"NFD\").replace(MARKS, \"\") : term;\n\n/**\n * Emit every overlapping 2-character window of `run`, or the lone character.\n * Windows are cut by code point: an ideograph outside the basic plane is a\n * surrogate pair, and slicing by code unit would split it into halves that\n * match nothing.\n *\n * Dropping the whole-segment tokens means a single-character query reaches\n * only pages where the character opens a bigram: a run-final 法 sits in 示法,\n * which the query 法 does not prefix-match. Lucene's CJK analyzer shares this\n * property; indexing lone characters alongside the bigrams would reinvite the\n * fragment noise this file exists to remove.\n */\nconst addBigrams = (run: string, tokens: Set<string>): void => {\n const characters = [...run];\n if (characters.length === 1) {\n tokens.add(run);\n return;\n }\n let previous = \"\";\n for (const character of characters) {\n if (previous) {\n tokens.add(previous + character);\n }\n previous = character;\n }\n};\n\n/**\n * `Intl.Segmenter` is missing on some runtimes even though the lib type\n * declares it, so the constructor's presence is probed before use.\n */\nconst hasSegmenter = (\n segmenter: typeof Intl.Segmenter | undefined\n): segmenter is typeof Intl.Segmenter => typeof segmenter === \"function\";\n\n/**\n * A word-segmenting tokenizer for languages the default splitter can't handle,\n * built on `Intl.Segmenter` (the same engine `@orama/tokenizers` wraps).\n * Input is NFC-normalized and lowercased before segmenting — unlike the\n * upstream tokenizers — so decomposed text (macOS filenames, some CMS\n * pipelines) indexes the same terms a composed query produces, and Latin\n * terms (\"GDPR\", English pages on a mixed-locale site) still match\n * case-insensitively, with their diacritics folded by\n * {@link foldDiacritics}. Returns `undefined` for scripts the default\n * tokenizer already serves ({@link resolveLocale} decides, so `sr-Latn` keeps\n * the default while `az-Cyrl` is segmented), and on runtimes without\n * `Intl.Segmenter`, where the caller falls back to Orama's default.\n *\n * On a {@link BIGRAM_INDEX_SCRIPTS} index, runs of adjacent\n * {@link BIGRAM_SCRIPTS} segments are joined and re-cut into character\n * bigrams; everything else (Latin, digits, and every segment on a Korean or\n * Thai index) is emitted one {@link TERM} at a time. Separators end a run\n * either way — whether they stand between segments, as 「クーリング・オフ」\n * does, or inside one.\n */\nconst segmentingTokenizer = (locale?: string): Tokenizer | undefined => {\n const resolved = resolveLocale(locale ?? \"\");\n if (!resolved?.script || resolved.script === LATIN_SCRIPT) {\n return;\n }\n if (!hasSegmenter(Intl.Segmenter)) {\n return;\n }\n // The canonical maximized language (`cmn` → `zh`), so the bigram predicate\n // here and the strict query pass read the same name.\n const { language } = resolved;\n const segmenter = new Intl.Segmenter(language, { granularity: \"word\" });\n const bigram = isBigramLanguage(language);\n return {\n language,\n normalizationCache: new Map(),\n tokenize: (raw: string): string[] => {\n const tokens = new Set<string>();\n let run = \"\";\n const flush = (): void => {\n if (run) {\n addBigrams(run, tokens);\n run = \"\";\n }\n };\n const take = (term: string): void => {\n if (bigram && BIGRAM_SCRIPTS.test(term)) {\n run += term;\n return;\n }\n flush();\n tokens.add(foldDiacritics(term));\n };\n for (const segment of segmenter.segment(\n raw.normalize(\"NFC\").toLowerCase()\n )) {\n if (!segment.isWordLike) {\n flush();\n continue;\n }\n let end = 0;\n for (const match of segment.segment.matchAll(TERM)) {\n // A gap means punctuation stood there, which ends the run as surely\n // as a non-word-like segment would: スネーク_ケース pairs either side\n // of the connector, never across it.\n if (match.index > end) {\n flush();\n }\n take(match[0]);\n end = match.index + match[0].length;\n }\n if (end < segment.segment.length) {\n flush();\n }\n }\n flush();\n return [...tokens];\n },\n };\n};\n\n/**\n * Build an in-memory Orama full-text index from search documents. Shared by the\n * Orama client loader (browser), the MCP server, and Ask AI grounding (Node),\n * so ranking is identical wherever docs are queried. `locale` — the site's\n * `i18n.defaultLocale` — swaps in a word-segmenting tokenizer for every\n * non-Latin script, all of which Orama's default tokenizer reduces to zero\n * tokens; the tokenizer belongs to the database, so on a mixed-locale site it\n * applies to every document. That is safe in one direction only: Latin words\n * survive segmentation intact, so English pages on a segmented index stay\n * searchable, but non-Latin translations on a Latin-default index still\n * collapse to zero tokens.\n */\nexport const buildOramaIndex = async (\n documents: OramaDoc[],\n locale?: string\n): Promise<AnyOrama> => {\n const tokenizer = segmentingTokenizer(locale);\n const db = tokenizer\n ? create({ components: { tokenizer }, schema: SCHEMA })\n : create({ schema: SCHEMA });\n await insertMultiple(\n db,\n documents.map((doc) =>\n doc.facets ? { ...doc, facetTerms: toFacetTerms(doc.facets) } : doc\n )\n );\n return db;\n};\n\n/** Orama keeps only documents matching every token at a threshold of 0. */\nconst ALL_TOKENS = 0;\n\n/** Optional exact-match filters applied to a query via Orama's `where`. */\nexport interface OramaQueryFilters {\n /** Keep only documents whose `contentType` is in this list. */\n contentTypes?: string[];\n /**\n * Keep only documents matching every facet, key → required value. Facet\n * keys and values come from the `facets` field on the indexed documents.\n */\n facets?: Record<string, string>;\n /** Keep only documents in this locale. */\n locale?: string;\n /**\n * Keep only documents of this docs version (`\"\"` is the current docs — a\n * meaningful filter value, so absence alone disables version filtering).\n */\n version?: string;\n}\n\n/**\n * The exact-match `where` clause the filters compile to. Orama types `where`\n * openly (any schema property to an operator), mirrored here by the index\n * signature; this module only ever emits the two enum operators.\n */\ninterface OramaWhereClause {\n [property: string]:\n | EnumArrComparisonOperator\n | EnumComparisonOperator\n | undefined;\n}\n\n/**\n * Query the index, returning the matching documents (highest-ranked first).\n * `filters` narrows results by exact `where` matches on the enum fields:\n * `locale` to one language, `contentTypes` to a set of page types, `facets`\n * to documents carrying every requested `key:value` term.\n *\n * On a bigrammed index the strict pass runs first: a term is only meant to\n * match where its bigrams sit together, and scoring them independently lets a\n * page sharing a couple of windows outrank the page the term is about. Terms\n * spanning several words rarely appear in full on one page, so an empty strict\n * result falls back to the default pass rather than reporting no matches.\n */\nexport const queryOramaIndex = async (\n db: AnyOrama,\n term: string,\n limit: number,\n filters?: OramaQueryFilters\n): Promise<OramaDoc[]> => {\n const facetTerms = filters?.facets ? toFacetTerms(filters.facets) : [];\n const where: OramaWhereClause = {};\n if (filters?.locale) {\n where.locale = { eq: filters.locale };\n }\n // `\"\"` (the current docs) is a real filter value, so test for presence.\n if (filters?.version !== undefined) {\n where.version = { eq: filters.version };\n }\n if (filters?.contentTypes && filters.contentTypes.length > 0) {\n where.contentType = { in: filters.contentTypes };\n }\n if (facetTerms.length > 0) {\n where.facetTerms = { containsAll: facetTerms };\n }\n const unfiltered = {\n boost: BOOST,\n limit,\n properties: [\"title\", \"description\", \"content\"],\n term,\n };\n const params =\n Object.keys(where).length > 0 ? { ...unfiltered, where } : unfiltered;\n const bigrammed = isBigramLanguage(db.tokenizer?.language ?? \"\");\n // The result-document generic is OramaDoc because `buildOramaIndex` is the\n // only writer to this database and inserts OramaDoc records (plus the\n // derived `facetTerms`).\n const strict = bigrammed\n ? await search<AnyOrama, OramaDoc>(db, { ...params, threshold: ALL_TOKENS })\n : undefined;\n const found =\n strict && strict.hits.length > 0\n ? strict\n : await search<AnyOrama, OramaDoc>(db, params);\n return found.hits.map((hit) => hit.document);\n};\n"
9
+ "import { create, insertMultiple, search } from \"@orama/orama\";\nimport type {\n AnyOrama,\n EnumArrComparisonOperator,\n EnumComparisonOperator,\n Tokenizer,\n} from \"@orama/orama\";\n\n/**\n * The minimal document shape both the client-side search dialog and the\n * server-side MCP `search_docs` tool index. Mirrors the `blume-search.json`\n * entries built by `buildSearchDocuments`.\n */\nexport interface OramaDoc {\n content: string;\n description: string;\n route: string;\n title: string;\n /** Locale code; indexed as an enum so queries can filter to one language. */\n locale?: string;\n /**\n * Docs version; indexed as an enum so queries can filter to one version.\n * The current docs carry `\"\"`, which the enum stores and matches exactly.\n */\n version?: string;\n /** Resolved page `type`; indexed as an enum so queries can filter by type. */\n contentType?: string;\n /** Declared facet values (`content.types.<type>.facets`), key → value. */\n facets?: Record<string, string>;\n /** Carried through for the search dialog's breadcrumb + filter pills. Stored\n * but not indexed, so they ride along on the returned document untouched. */\n breadcrumb?: string[];\n section?: string;\n}\n\nconst SCHEMA = {\n content: \"string\",\n // Enums (not full-text \"string\") so `where` does an exact-match filter.\n contentType: \"enum\",\n description: \"string\",\n // Facets, flattened to `key:value` terms — enum[] so one static schema\n // serves every project's facet keys, with `containsAll` matching a filter\n // set. Derived from `facets` at insert time.\n facetTerms: \"enum[]\",\n locale: \"enum\",\n route: \"string\",\n title: \"string\",\n version: \"enum\",\n} as const;\n\n/** Flatten a facet map to the `key:value` terms the `facetTerms` enum holds. */\nconst toFacetTerms = (facets: Record<string, string>): string[] =>\n Object.entries(facets).map(([key, value]) => `${key}:${value}`);\n\n/** Title and description outrank body text, matching the search dialog. */\nconst BOOST = { description: 2, title: 3 };\n\n/**\n * The script whose text Orama's default tokenizer keeps mostly intact. Its\n * delimiter class is `/[^A-Za-zàèéìòóù0-9_'-]+/`, so every character outside\n * that set counts as a separator: text in any other script collapses to zero\n * tokens and every query silently returns no hits. Unspaced scripts are the\n * best-known casualty, but the failure is not about spacing — Russian, Greek,\n * Hebrew and Hindi lose their tokens the same way Japanese does.\n */\nconst LATIN_SCRIPT = \"Latn\";\n\n/**\n * Parse a locale tag, tolerating the legacy forms that reach here from\n * hand-written config: underscores (`ru_RU`), POSIX suffixes (`ja_JP.UTF-8`,\n * `zh_TW@Big5`), and extlang tags ICU rejects (`zh-cmn-Hans`). The POSIX\n * codeset/modifier suffix is dropped first — it carries no BCP 47 meaning,\n * and dropping it keeps an explicit script subtag in play: `az_Cyrl.UTF-8`\n * must resolve to `Cyrl`, not fall back to `az` and maximize to Latin.\n * `Intl.Locale` still throws on extlang forms, so a failed parse retries with\n * the primary language subtag alone — enough to resolve the script, which is\n * all that gates the tokenizer. Returns `undefined` when even that subtag is\n * unparseable.\n */\nconst parseLocale = (tag: string): Intl.Locale | undefined => {\n const hyphenated = tag.replace(/[.@].*$/u, \"\").replaceAll(\"_\", \"-\");\n try {\n return new Intl.Locale(hyphenated);\n } catch {\n // Retry below with the primary subtag.\n }\n const [primary = \"\"] = hyphenated.split(\"-\");\n try {\n return new Intl.Locale(primary);\n } catch {\n return undefined;\n }\n};\n\n/**\n * The maximized locale — script and language filled in from CLDR likely-subtags\n * and alias data, so `ru` resolves to `Cyrl`, `cmn` to the canonical `zh`, and\n * an explicit script subtag is honored: `sr-Latn` reports `Latn` while\n * `az-Cyrl` reports `Cyrl`. `undefined` for tags {@link parseLocale} cannot\n * salvage; a well-formed tag ICU knows nothing about keeps an `undefined`\n * script instead.\n */\nconst resolveLocale = (tag: string): Intl.Locale | undefined =>\n parseLocale(tag)?.maximize();\n\n/**\n * Indexes whose language maximizes to one of these scripts are built as\n * character bigrams rather than whole segments, and queried to match\n * accordingly. Japanese and Chinese write compounds in {@link BIGRAM_SCRIPTS}\n * without delimiters, and the writing system — not the language subtag — is\n * what carries that convention: `yue` (Cantonese) maximizes to `Hant` and\n * needs bigrams the same way `zh` does. Korean maximizes to `Kore` and Thai\n * to `Thai`; Korean separates words with spaces and Thai has no comparable\n * bigram convention, so both keep the plain segmented tokens even where a\n * page mixes in Han or kana.\n *\n * Dictionary segmentation alone drops the adjacency that makes a compound term\n * distinctive: 資金決済法 becomes 資金 / 決済 / 法, and because Orama scores a\n * bag of words, a page that merely mentions each fragment somewhere outranks\n * the page about the law itself. Bigrams put that adjacency back as index\n * terms, and dropping the whole-segment tokens keeps a fragment as common as\n * 法 from matching on its own.\n */\nconst BIGRAM_INDEX_SCRIPTS = new Set([\"Hans\", \"Hant\", \"Jpan\"]);\n\n/**\n * Whether the index keyed to this tokenizer language is built from bigrams.\n * {@link segmentingTokenizer} stores the canonical maximized language, so the\n * builder and the strict pass in {@link queryOramaIndex} share this predicate\n * — an index is never built from bigrams that the query side then matches\n * loosely, or vice versa.\n */\nconst isBigramLanguage = (language: string): boolean => {\n const script = resolveLocale(language)?.script;\n return script !== undefined && BIGRAM_INDEX_SCRIPTS.has(script);\n};\n\n/**\n * Segments written entirely in these scripts are the ones re-cut into bigrams,\n * matching the scripts Lucene's CJK analyzer bigrams. Property escapes rather\n * than ranges, so ideographs outside the basic plane are covered as well —\n * 𠮟, the 常用漢字表 form of しかる, is one. The literals that follow belong to\n * no script of their own but appear only inside such words: the iteration\n * marks, the prolonged sound mark, and the halfwidth voiced sound marks.\n */\nconst BIGRAM_SCRIPTS =\n /^[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}々〆〇ー゙゚]+$/u;\n\n/**\n * One index term inside a word-like segment. Being word-like does not make a\n * segment all letters: UAX #29 keeps connector punctuation, format characters\n * and mid-number punctuation *within* a word, so `Intl.Segmenter` reports\n * スネーク_ケース and robots.txt as one segment each. A term is a run of\n * letters, combining marks and digits. Marks are spelling, not punctuation —\n * Thai writes vowels and tones as combining marks, so dropping them leaves\n * consonant skeletons that collapse distinct words (เสื้อ, shirt, and เสือ,\n * tiger, differ by one mark). Two separators stay where the surrounding text\n * makes them part of the word: an apostrophe followed by a letter (don't),\n * and a decimal point or thousands separator flanked by digits (1.0.3,\n * 1,000) — split, either would leave one-letter and one-digit fragments that\n * co-occur on unrelated pages.\n */\nconst TERM =\n /[\\p{L}\\p{M}\\p{N}]+(?:(?:['’](?=\\p{L})|(?<=\\p{N})[.,](?=\\p{N}))[\\p{L}\\p{M}\\p{N}]+)*/gu;\n\n/**\n * A term written entirely in Latin script (plus digits and the separators\n * {@link TERM} keeps within a word). Orama's default tokenizer folds the\n * accented vowels it recognizes (café → cafe), so a segmented index folds\n * Latin terms too — otherwise switching a Cyrillic- or Greek-default site to\n * the segmenting tokenizer would silently drop the unaccented-query matches\n * the default tokenizer provided. Only all-Latin terms fold: marks are\n * spelling elsewhere (Thai vowels and tones; the breve that separates\n * Cyrillic й from и), so a term carrying any other script keeps its marks.\n */\nconst LATIN_TERM = /^[\\p{Script=Latin}\\p{N}'’.,]+$/u;\n\nconst MARKS = /\\p{M}+/gu;\n\nconst foldDiacritics = (term: string): string =>\n LATIN_TERM.test(term) ? term.normalize(\"NFD\").replace(MARKS, \"\") : term;\n\n/**\n * Emit every overlapping 2-character window of `run`, or the lone character.\n * Windows are cut by code point: an ideograph outside the basic plane is a\n * surrogate pair, and slicing by code unit would split it into halves that\n * match nothing.\n *\n * Dropping the whole-segment tokens means a single-character query reaches\n * only pages where the character opens a bigram: a run-final 法 sits in 示法,\n * which the query 法 does not prefix-match. Lucene's CJK analyzer shares this\n * property; indexing lone characters alongside the bigrams would reinvite the\n * fragment noise this file exists to remove.\n */\nconst addBigrams = (run: string, tokens: Set<string>): void => {\n const characters = [...run];\n if (characters.length === 1) {\n tokens.add(run);\n return;\n }\n let previous = \"\";\n for (const character of characters) {\n if (previous) {\n tokens.add(previous + character);\n }\n previous = character;\n }\n};\n\n/**\n * `Intl.Segmenter` is missing on some runtimes even though the lib type\n * declares it, so the constructor's presence is probed before use.\n */\nconst hasSegmenter = (\n segmenter: typeof Intl.Segmenter | undefined\n): segmenter is typeof Intl.Segmenter => typeof segmenter === \"function\";\n\n/**\n * A word-segmenting tokenizer for languages the default splitter can't handle,\n * built on `Intl.Segmenter` (the same engine `@orama/tokenizers` wraps).\n * Input is NFC-normalized and lowercased before segmenting — unlike the\n * upstream tokenizers — so decomposed text (macOS filenames, some CMS\n * pipelines) indexes the same terms a composed query produces, and Latin\n * terms (\"GDPR\", English pages on a mixed-locale site) still match\n * case-insensitively, with their diacritics folded by\n * {@link foldDiacritics}. Returns `undefined` for scripts the default\n * tokenizer already serves ({@link resolveLocale} decides, so `sr-Latn` keeps\n * the default while `az-Cyrl` is segmented), and on runtimes without\n * `Intl.Segmenter`, where the caller falls back to Orama's default.\n *\n * On a {@link BIGRAM_INDEX_SCRIPTS} index, runs of adjacent\n * {@link BIGRAM_SCRIPTS} segments are joined and re-cut into character\n * bigrams; everything else (Latin, digits, and every segment on a Korean or\n * Thai index) is emitted one {@link TERM} at a time. Separators end a run\n * either way — whether they stand between segments, as 「クーリング・オフ」\n * does, or inside one.\n */\nconst segmentingTokenizer = (locale?: string): Tokenizer | undefined => {\n const resolved = resolveLocale(locale ?? \"\");\n if (!resolved?.script || resolved.script === LATIN_SCRIPT) {\n return;\n }\n if (!hasSegmenter(Intl.Segmenter)) {\n return;\n }\n // The canonical maximized language (`cmn` → `zh`), so the bigram predicate\n // here and the strict query pass read the same name.\n const { language } = resolved;\n const segmenter = new Intl.Segmenter(language, { granularity: \"word\" });\n const bigram = isBigramLanguage(language);\n return {\n language,\n normalizationCache: new Map(),\n tokenize: (raw: string): string[] => {\n const tokens = new Set<string>();\n let run = \"\";\n const flush = (): void => {\n if (run) {\n addBigrams(run, tokens);\n run = \"\";\n }\n };\n const take = (term: string): void => {\n if (bigram && BIGRAM_SCRIPTS.test(term)) {\n run += term;\n return;\n }\n flush();\n tokens.add(foldDiacritics(term));\n };\n for (const segment of segmenter.segment(\n raw.normalize(\"NFC\").toLowerCase()\n )) {\n if (!segment.isWordLike) {\n flush();\n continue;\n }\n let end = 0;\n for (const match of segment.segment.matchAll(TERM)) {\n // A gap means punctuation stood there, which ends the run as surely\n // as a non-word-like segment would: スネーク_ケース pairs either side\n // of the connector, never across it.\n if (match.index > end) {\n flush();\n }\n take(match[0]);\n end = match.index + match[0].length;\n }\n if (end < segment.segment.length) {\n flush();\n }\n }\n flush();\n return [...tokens];\n },\n };\n};\n\n/**\n * Build an in-memory Orama full-text index from search documents. Shared by the\n * Orama client loader (browser), the MCP server, and assistant grounding (Node),\n * so ranking is identical wherever docs are queried. `locale` — the site's\n * `i18n.defaultLocale` — swaps in a word-segmenting tokenizer for every\n * non-Latin script, all of which Orama's default tokenizer reduces to zero\n * tokens; the tokenizer belongs to the database, so on a mixed-locale site it\n * applies to every document. That is safe in one direction only: Latin words\n * survive segmentation intact, so English pages on a segmented index stay\n * searchable, but non-Latin translations on a Latin-default index still\n * collapse to zero tokens.\n */\nexport const buildOramaIndex = async (\n documents: OramaDoc[],\n locale?: string\n): Promise<AnyOrama> => {\n const tokenizer = segmentingTokenizer(locale);\n const db = tokenizer\n ? create({ components: { tokenizer }, schema: SCHEMA })\n : create({ schema: SCHEMA });\n await insertMultiple(\n db,\n documents.map((doc) =>\n doc.facets ? { ...doc, facetTerms: toFacetTerms(doc.facets) } : doc\n )\n );\n return db;\n};\n\n/** Orama keeps only documents matching every token at a threshold of 0. */\nconst ALL_TOKENS = 0;\n\n/** Optional exact-match filters applied to a query via Orama's `where`. */\nexport interface OramaQueryFilters {\n /** Keep only documents whose `contentType` is in this list. */\n contentTypes?: string[];\n /**\n * Keep only documents matching every facet, key → required value. Facet\n * keys and values come from the `facets` field on the indexed documents.\n */\n facets?: Record<string, string>;\n /** Keep only documents in this locale. */\n locale?: string;\n /**\n * Keep only documents of this docs version (`\"\"` is the current docs — a\n * meaningful filter value, so absence alone disables version filtering).\n */\n version?: string;\n}\n\n/**\n * The exact-match `where` clause the filters compile to. Orama types `where`\n * openly (any schema property to an operator), mirrored here by the index\n * signature; this module only ever emits the two enum operators.\n */\ninterface OramaWhereClause {\n [property: string]:\n | EnumArrComparisonOperator\n | EnumComparisonOperator\n | undefined;\n}\n\n/**\n * Query the index, returning the matching documents (highest-ranked first).\n * `filters` narrows results by exact `where` matches on the enum fields:\n * `locale` to one language, `contentTypes` to a set of page types, `facets`\n * to documents carrying every requested `key:value` term.\n *\n * On a bigrammed index the strict pass runs first: a term is only meant to\n * match where its bigrams sit together, and scoring them independently lets a\n * page sharing a couple of windows outrank the page the term is about. Terms\n * spanning several words rarely appear in full on one page, so an empty strict\n * result falls back to the default pass rather than reporting no matches.\n */\nexport const queryOramaIndex = async (\n db: AnyOrama,\n term: string,\n limit: number,\n filters?: OramaQueryFilters\n): Promise<OramaDoc[]> => {\n const facetTerms = filters?.facets ? toFacetTerms(filters.facets) : [];\n const where: OramaWhereClause = {};\n if (filters?.locale) {\n where.locale = { eq: filters.locale };\n }\n // `\"\"` (the current docs) is a real filter value, so test for presence.\n if (filters?.version !== undefined) {\n where.version = { eq: filters.version };\n }\n if (filters?.contentTypes && filters.contentTypes.length > 0) {\n where.contentType = { in: filters.contentTypes };\n }\n if (facetTerms.length > 0) {\n where.facetTerms = { containsAll: facetTerms };\n }\n const unfiltered = {\n boost: BOOST,\n limit,\n properties: [\"title\", \"description\", \"content\"],\n term,\n };\n const params =\n Object.keys(where).length > 0 ? { ...unfiltered, where } : unfiltered;\n const bigrammed = isBigramLanguage(db.tokenizer?.language ?? \"\");\n // The result-document generic is OramaDoc because `buildOramaIndex` is the\n // only writer to this database and inserts OramaDoc records (plus the\n // derived `facetTerms`).\n const strict = bigrammed\n ? await search<AnyOrama, OramaDoc>(db, { ...params, threshold: ALL_TOKENS })\n : undefined;\n const found =\n strict && strict.hits.length > 0\n ? strict\n : await search<AnyOrama, OramaDoc>(db, params);\n return found.hits.map((hit) => hit.document);\n};\n"
10
10
  ],
11
11
  "mappings": "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",
12
12
  "debugId": "F74FD7C10F87037364756E2164756E21",
@@ -218,4 +218,4 @@ var findItem = (name) => registry.find((item) => item.name === name);
218
218
  export { packageSrc, registry, findItem };
219
219
 
220
220
  //# debugId=2BDCE672B82B453C64756E2164756E21
221
- //# sourceMappingURL=chunk-88cpgt6h.js.map
221
+ //# sourceMappingURL=chunk-zxcczpyx.js.map
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  packageDirFrom,
4
4
  generateRuntime
5
- } from "./chunk-z1f5arsg.js";
5
+ } from "./chunk-wgm7m9qk.js";
6
6
  import {
7
7
  loadEnvFiles,
8
8
  reportInternalError,
@@ -13,11 +13,11 @@ import {
13
13
  } from "./index.js";
14
14
  import {
15
15
  scanProject
16
- } from "./chunk-zg2gtj10.js";
16
+ } from "./chunk-fs23ddbb.js";
17
17
  import {
18
18
  serverFeatures,
19
19
  checkRequiredSecrets
20
- } from "./chunk-2q1dwty4.js";
20
+ } from "./chunk-ah61y8py.js";
21
21
 
22
22
  // src/cli/yarn-pnp.ts
23
23
  import { existsSync } from "node:fs";
@@ -119,4 +119,4 @@ var prepareProject = async (options) => {
119
119
  export { prepareProject };
120
120
 
121
121
  //# debugId=28CF8258A34C9DAE64756E2164756E21
122
- //# sourceMappingURL=chunk-41za066z.js.map
122
+ //# sourceMappingURL=chunk-zxh4d9vy.js.map
package/dist/cli/index.js CHANGED
@@ -553,23 +553,23 @@ var main = defineCommand({
553
553
  version: getBlumeVersion()
554
554
  },
555
555
  subCommands: {
556
- add: lazyCommand(commandMeta.add, () => import("./chunk-7ez8ny0t.js"), "addCommand"),
557
- audit: lazyCommand(commandMeta.audit, () => import("./chunk-j8mw0za6.js"), "auditCommand"),
558
- build: lazyCommand(commandMeta.build, () => import("./chunk-z01ze5c1.js"), "buildCommand"),
559
- check: lazyCommand(commandMeta.check, () => import("./chunk-1w8dp3qb.js"), "checkCommand"),
560
- dev: lazyCommand(commandMeta.dev, () => import("./chunk-fh5hj5jt.js"), "devCommand"),
561
- doctor: lazyCommand(commandMeta.doctor, () => import("./chunk-11j0384y.js"), "doctorCommand"),
562
- eject: lazyCommand(commandMeta.eject, () => import("./chunk-ernrthtr.js"), "ejectCommand"),
563
- eval: lazyCommand(commandMeta.eval, () => import("./chunk-d80hr03s.js"), "evalCommand"),
564
- init: lazyCommand(commandMeta.init, () => import("./chunk-79njf86q.js"), "initCommand"),
565
- "mcp-stdio": lazyCommand(commandMeta["mcp-stdio"], () => import("./chunk-pat2zzwc.js"), "mcpStdioCommand"),
566
- migrate: lazyCommand(commandMeta.migrate, () => import("./chunk-pnnvybbk.js"), "migrateCommand"),
567
- preview: lazyCommand(commandMeta.preview, () => import("./chunk-bctazmbk.js"), "previewCommand"),
568
- sync: lazyCommand(commandMeta.sync, () => import("./chunk-tzne8qfq.js"), "syncCommand"),
569
- translate: lazyCommand(commandMeta.translate, () => import("./chunk-6k8vp3ta.js"), "translateCommand"),
570
- upgrade: lazyCommand(commandMeta.upgrade, () => import("./chunk-5a2z0198.js"), "upgradeCommand"),
571
- validate: lazyCommand(commandMeta.validate, () => import("./chunk-y3e45rc8.js"), "validateCommand"),
572
- version: lazyCommand(commandMeta.version, () => import("./chunk-b5aj94ah.js"), "versionCommand")
556
+ add: lazyCommand(commandMeta.add, () => import("./chunk-fxypxtvm.js"), "addCommand"),
557
+ audit: lazyCommand(commandMeta.audit, () => import("./chunk-79jhk4py.js"), "auditCommand"),
558
+ build: lazyCommand(commandMeta.build, () => import("./chunk-s1p84fyh.js"), "buildCommand"),
559
+ check: lazyCommand(commandMeta.check, () => import("./chunk-epjnccmv.js"), "checkCommand"),
560
+ dev: lazyCommand(commandMeta.dev, () => import("./chunk-kdp5q7ke.js"), "devCommand"),
561
+ doctor: lazyCommand(commandMeta.doctor, () => import("./chunk-6vm74dry.js"), "doctorCommand"),
562
+ eject: lazyCommand(commandMeta.eject, () => import("./chunk-f2z5v128.js"), "ejectCommand"),
563
+ eval: lazyCommand(commandMeta.eval, () => import("./chunk-jwyddg7y.js"), "evalCommand"),
564
+ init: lazyCommand(commandMeta.init, () => import("./chunk-q5163e60.js"), "initCommand"),
565
+ "mcp-stdio": lazyCommand(commandMeta["mcp-stdio"], () => import("./chunk-yw7dm696.js"), "mcpStdioCommand"),
566
+ migrate: lazyCommand(commandMeta.migrate, () => import("./chunk-s6jhgk0q.js"), "migrateCommand"),
567
+ preview: lazyCommand(commandMeta.preview, () => import("./chunk-mb2919y2.js"), "previewCommand"),
568
+ sync: lazyCommand(commandMeta.sync, () => import("./chunk-fz5wtpmh.js"), "syncCommand"),
569
+ translate: lazyCommand(commandMeta.translate, () => import("./chunk-kpf8rrjc.js"), "translateCommand"),
570
+ upgrade: lazyCommand(commandMeta.upgrade, () => import("./chunk-m3vmjgmq.js"), "upgradeCommand"),
571
+ validate: lazyCommand(commandMeta.validate, () => import("./chunk-ch6g3ar0.js"), "validateCommand"),
572
+ version: lazyCommand(commandMeta.version, () => import("./chunk-hdpx1tax.js"), "versionCommand")
573
573
  }
574
574
  });
575
575
  loadEnvFiles(process.cwd());
@@ -42,7 +42,7 @@ export interface AgentReadabilityManifest {
42
42
  /**
43
43
  * Build `agent-readability.json`: a root manifest that indexes the project's
44
44
  * agent-facing surface — llms.txt, the raw-Markdown mirrors, the JSON docs
45
- * API and its OpenAPI description, the MCP server, Ask AI, sitemap, and feeds
45
+ * API and its OpenAPI description, the MCP server, the assistant, sitemap, and feeds
46
46
  * — so agents can discover and cite the docs without
47
47
  * scraping HTML. URLs are absolute when a `site` is configured and root-relative
48
48
  * (still under `deployment.base`) otherwise. Returns null when the manifest is
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Where the JSON docs API and its OpenAPI description are served. Base-less
3
3
  * (like every route Blume emits); callers layer `deployment.base` on top.
4
- * Under `/api/` alongside the Ask AI endpoint (`/api/ask`) so the namespace a
4
+ * Under `/api/` alongside the assistant endpoint (`/api/ask`) so the namespace a
5
5
  * Blume site reserves for live endpoints stays one prefix, and under its own
6
6
  * `docs` segment so a search provider's proxy at `/api/search` never collides.
7
7
  */
@@ -1,5 +1,5 @@
1
1
  import type { OramaDoc } from "../search/orama-index.ts";
2
- /** A chat message as posted by the Ask AI island (`{ role, content }`). */
2
+ /** A chat message as posted by the assistant island (`{ role, content }`). */
3
3
  export interface AskMessage {
4
4
  content: string;
5
5
  role: string;
@@ -9,7 +9,7 @@ export interface AskPage {
9
9
  path?: string;
10
10
  }
11
11
  /**
12
- * The self-contained snapshot the grounded Ask AI endpoint imports. Bundles the
12
+ * The self-contained snapshot the assistant's grounded endpoint imports. Bundles the
13
13
  * search documents so retrieval works regardless of the configured search
14
14
  * provider and needs no filesystem access at request time. Serialized to
15
15
  * `generated/ask-data.json` and built by {@link buildAskData}.
@@ -25,7 +25,7 @@ export interface AskData {
25
25
  site: string | null;
26
26
  }
27
27
  /**
28
- * How much retrieved documentation a question carries (the `ai.ask.retrieval`
28
+ * How much retrieved documentation a question carries (the `ai.assistant.retrieval`
29
29
  * config). Every field falls back to the built-in default, so a partial object
30
30
  * only changes what it names. Injected characters dominate time-to-first-token
31
31
  * on a self-hosted backend, and the three knobs aren't interchangeable: the
@@ -49,7 +49,7 @@ export interface AskRetrievalOptions {
49
49
  *
50
50
  * Pages are indexed whole (one document each), so a naive head slice of a long
51
51
  * page returns its intro and misses sections below the fold — the exact failure
52
- * where "How does Ask AI work?" retrieves the right page but only sees its
52
+ * where "How does the assistant work?" retrieves the right page but only sees its
53
53
  * opening paragraph. This centers the window on the densest cluster of query
54
54
  * terms so the injected text is the part that actually answers the question.
55
55
  * Exported for testing; {@link createAskContext} is the runtime entry point.
@@ -81,7 +81,7 @@ export declare const parsePage: (content: string) => ParsedPage;
81
81
  /** {@link excerptPage} over a page parsed on the spot. Exported for testing. */
82
82
  export declare const sectionExcerpt: (content: string, query: string, max: number) => string;
83
83
  /**
84
- * Build the request-time grounding function for the Ask AI endpoint.
84
+ * Build the request-time grounding function for the assistant endpoint.
85
85
  *
86
86
  * Lexical retrieval over Orama (the same index/ranking the search dialog and MCP
87
87
  * server use). The index is built once and memoized across requests. Returns a
@@ -89,12 +89,12 @@ export declare const sectionExcerpt: (content: string, query: string, max: numbe
89
89
  * viewing — or `undefined` when there is nothing to ground on, so the endpoint
90
90
  * can fall back to its plain prompt.
91
91
  *
92
- * `options.instructions` (the `ai.ask.instructions` config) is appended after
92
+ * `options.instructions` (the `ai.assistant.instructions` config) is appended after
93
93
  * the base instruction rather than replacing it: the base carries the
94
94
  * functional contract (answer only from the excerpts, cite pages as Markdown
95
95
  * links) that the panel's citation rendering depends on.
96
96
  *
97
- * `options.retrieval` (the `ai.ask.retrieval` config) sizes how much
97
+ * `options.retrieval` (the `ai.assistant.retrieval` config) sizes how much
98
98
  * documentation each question carries; omitted fields keep today's defaults.
99
99
  */
100
100
  export declare const createAskContext: (data: AskData, options?: {