blume 1.6.0 → 1.6.2

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 (97) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/cli/index.js +1318 -270
  3. package/dist/cli/index.js.map +68 -61
  4. package/dist/types/core/config-input.d.ts +9 -0
  5. package/dist/types/core/data.d.ts +12 -1
  6. package/dist/types/core/i18n-ui.d.ts +4 -0
  7. package/dist/types/core/schema.d.ts +7 -0
  8. package/dist/types/core/types.d.ts +6 -0
  9. package/dist/types/openapi/references.d.ts +5 -0
  10. package/docs/07-faq.mdx +9 -9
  11. package/docs/advanced/api-reference.mdx +10 -1
  12. package/docs/advanced/custom-pages.mdx +3 -1
  13. package/docs/advanced/graphql.mdx +1 -1
  14. package/docs/configuration/ai.mdx +76 -7
  15. package/docs/configuration/seo.mdx +3 -3
  16. package/docs/configuration/theming.mdx +6 -0
  17. package/docs/content/components.mdx +8 -1
  18. package/docs/index.mdx +2 -2
  19. package/package.json +53 -53
  20. package/skills/blume/SKILL.md +2 -2
  21. package/src/ai/agent-readability.ts +60 -17
  22. package/src/ai/api/handlers.ts +273 -0
  23. package/src/ai/api/paths.ts +14 -0
  24. package/src/ai/api/problem.ts +63 -0
  25. package/src/ai/api/spec.ts +681 -0
  26. package/src/ai/api-catalog.ts +11 -1
  27. package/src/ai/link-headers.ts +12 -3
  28. package/src/ai/llms.ts +9 -2
  29. package/src/ai/mcp/query.ts +390 -0
  30. package/src/ai/mcp/server.ts +32 -352
  31. package/src/astro/examples.ts +29 -2
  32. package/src/astro/generate.ts +256 -64
  33. package/src/astro/index.ts +7 -0
  34. package/src/astro/markdown-negotiation.ts +1 -1
  35. package/src/astro/runtime-modules.ts +196 -0
  36. package/src/astro/templates.ts +398 -38
  37. package/src/cli/commands/build.ts +9 -1
  38. package/src/cli/commands/dev.ts +6 -3
  39. package/src/cli/host-args.ts +18 -0
  40. package/src/cli/index.ts +2 -1
  41. package/src/components/copy-feedback.ts +93 -9
  42. package/src/components/islands/ask-ai.tsx +4 -1
  43. package/src/components/islands/hooks.ts +3 -1
  44. package/src/components/layout/PageActions.astro +25 -14
  45. package/src/core/config-input.ts +9 -0
  46. package/src/core/data.ts +17 -2
  47. package/src/core/define-components.ts +2 -0
  48. package/src/core/i18n-ui.ts +3 -0
  49. package/src/core/includes.ts +2 -1
  50. package/src/core/manifest.ts +10 -0
  51. package/src/core/schema.ts +20 -5
  52. package/src/core/types.ts +6 -0
  53. package/src/core/ui-packs/ar.ts +1 -0
  54. package/src/core/ui-packs/bg.ts +1 -0
  55. package/src/core/ui-packs/bn.ts +1 -0
  56. package/src/core/ui-packs/ca.ts +1 -0
  57. package/src/core/ui-packs/cs.ts +1 -0
  58. package/src/core/ui-packs/da.ts +1 -0
  59. package/src/core/ui-packs/de.ts +1 -0
  60. package/src/core/ui-packs/el.ts +1 -0
  61. package/src/core/ui-packs/es.ts +1 -0
  62. package/src/core/ui-packs/fa.ts +1 -0
  63. package/src/core/ui-packs/fi.ts +1 -0
  64. package/src/core/ui-packs/fr.ts +1 -0
  65. package/src/core/ui-packs/he.ts +1 -0
  66. package/src/core/ui-packs/hi.ts +1 -0
  67. package/src/core/ui-packs/hr.ts +1 -0
  68. package/src/core/ui-packs/hu.ts +1 -0
  69. package/src/core/ui-packs/id.ts +1 -0
  70. package/src/core/ui-packs/it.ts +1 -0
  71. package/src/core/ui-packs/ja.ts +1 -0
  72. package/src/core/ui-packs/ko.ts +1 -0
  73. package/src/core/ui-packs/nl.ts +1 -0
  74. package/src/core/ui-packs/no.ts +1 -0
  75. package/src/core/ui-packs/pl.ts +1 -0
  76. package/src/core/ui-packs/pt-br.ts +1 -0
  77. package/src/core/ui-packs/pt.ts +1 -0
  78. package/src/core/ui-packs/ro.ts +1 -0
  79. package/src/core/ui-packs/ru.ts +1 -0
  80. package/src/core/ui-packs/sk.ts +1 -0
  81. package/src/core/ui-packs/sr.ts +1 -0
  82. package/src/core/ui-packs/sv.ts +1 -0
  83. package/src/core/ui-packs/th.ts +1 -0
  84. package/src/core/ui-packs/tr.ts +1 -0
  85. package/src/core/ui-packs/uk.ts +1 -0
  86. package/src/core/ui-packs/vi.ts +1 -0
  87. package/src/core/ui-packs/zh-tw.ts +1 -0
  88. package/src/core/ui-packs/zh.ts +1 -0
  89. package/src/core/version-cut.ts +5 -3
  90. package/src/deploy/vercel-negotiation.ts +97 -6
  91. package/src/og/card.ts +1 -1
  92. package/src/openapi/references.ts +8 -0
  93. package/src/openapi/render-mdx.ts +18 -4
  94. package/src/openapi/scalar.ts +0 -4
  95. package/src/registry/eject.ts +36 -17
  96. package/src/theme/entry.ts +2 -2
  97. package/src/theme/sources.ts +49 -0
package/dist/cli/index.js CHANGED
@@ -3158,6 +3158,7 @@ var sourcesOf = (block) => {
3158
3158
  includeInLlms: true,
3159
3159
  includeInSearch: true,
3160
3160
  noindex: false,
3161
+ seoDescriptionSuffix: true,
3161
3162
  spec: block.spec
3162
3163
  });
3163
3164
  }
@@ -3191,6 +3192,7 @@ var referencesFor = (kind, block, defaultLabel, renderer, display, basePath) =>
3191
3192
  renderer,
3192
3193
  route,
3193
3194
  scalar: block.scalar,
3195
+ seoDescriptionSuffix: source.seoDescriptionSuffix,
3194
3196
  slug: routeSlug(route),
3195
3197
  spec: source.spec,
3196
3198
  theme: block.theme
@@ -3265,6 +3267,14 @@ var builtinProxyKinds = (config) => {
3265
3267
  };
3266
3268
  var needsPlaygroundProxy = (config) => builtinProxyKinds(config).length > 0;
3267
3269
 
3270
+ // src/ai/api/paths.ts
3271
+ var OPENAPI_PATH = "/openapi.json";
3272
+ var API_BASE = "/api/docs";
3273
+ var API_PAGES_PATH = `${API_BASE}/pages.json`;
3274
+ var API_PAGE_PATH = `${API_BASE}/pages/{route}.json`;
3275
+ var API_NAVIGATION_PATH = `${API_BASE}/navigation.json`;
3276
+ var API_SEARCH_PATH = `${API_BASE}/search`;
3277
+
3268
3278
  // src/ai/api-catalog.ts
3269
3279
  var API_CATALOG_PATH = "/.well-known/api-catalog";
3270
3280
  var API_CATALOG_TYPE = "application/linkset+json";
@@ -3288,6 +3298,13 @@ var linksetEntries = (config) => {
3288
3298
  }
3289
3299
  entries.push(entry);
3290
3300
  }
3301
+ if (config.ai.api) {
3302
+ entries.push({
3303
+ anchor: abs(API_BASE),
3304
+ "service-desc": [{ href: abs(OPENAPI_PATH), type: "application/json" }],
3305
+ "service-doc": [{ href: abs("/"), type: "text/html" }]
3306
+ });
3307
+ }
3291
3308
  if (config.ai.mcp.enabled) {
3292
3309
  entries.push({
3293
3310
  anchor: abs(config.ai.mcp.route),
@@ -3316,6 +3333,9 @@ var buildHomeLinkHeader = (config, routePaths) => {
3316
3333
  if (hasApiCatalog(config)) {
3317
3334
  links.push(`<${deployBase}${API_CATALOG_PATH}>; rel="api-catalog"; type="application/linkset+json"`);
3318
3335
  }
3336
+ if (config.ai.api) {
3337
+ links.push(`<${deployBase}${OPENAPI_PATH}>; rel="service-desc"; type="application/json"`);
3338
+ }
3319
3339
  if (config.seo.agentReadability) {
3320
3340
  links.push(`<${deployBase}/agent-readability.json>; rel="describedby"; type="application/json"`);
3321
3341
  }
@@ -3818,6 +3838,55 @@ var configuredFonts = (fonts, locales = []) => {
3818
3838
  });
3819
3839
  };
3820
3840
 
3841
+ // src/astro/runtime-modules.ts
3842
+ var RUNTIME_MODULE_FILES = new Map([
3843
+ ["blume:ask-data", "ask-data.json"],
3844
+ ["blume:content-assets", "content-assets.json"],
3845
+ ["blume:data", "data.json"],
3846
+ ["blume:mcp-data", "mcp-data.json"],
3847
+ ["blume:openapi", "openapi.json"],
3848
+ ["blume:raw-markdown", "raw-markdown.json"],
3849
+ ["blume:rss", "rss.json"],
3850
+ ["blume:search-index", "search.json"]
3851
+ ]);
3852
+ var RUNTIME_MODULE_IDS = new Set(RUNTIME_MODULE_FILES.keys());
3853
+ var RESOLVED_PREFIX = "\x00";
3854
+ var REGISTRY_KEY = Symbol.for("blume.runtime-modules");
3855
+ var registry2 = () => {
3856
+ const host = globalThis;
3857
+ host[REGISTRY_KEY] ??= { modules: new Map, servers: new Set };
3858
+ return host[REGISTRY_KEY];
3859
+ };
3860
+ var publishRuntimeModules = (modules) => {
3861
+ const { modules: current, servers } = registry2();
3862
+ const changed = [];
3863
+ for (const id of RUNTIME_MODULE_FILES.keys()) {
3864
+ const next = modules.get(id);
3865
+ if (current.get(id) === next) {
3866
+ continue;
3867
+ }
3868
+ if (next === undefined) {
3869
+ current.delete(id);
3870
+ } else {
3871
+ current.set(id, next);
3872
+ }
3873
+ changed.push(id);
3874
+ }
3875
+ if (changed.length === 0) {
3876
+ return changed;
3877
+ }
3878
+ for (const server of servers) {
3879
+ for (const id of changed) {
3880
+ const mod = server.moduleGraph.getModuleById(`${RESOLVED_PREFIX}${id}`);
3881
+ if (mod) {
3882
+ server.moduleGraph.invalidateModule(mod);
3883
+ }
3884
+ }
3885
+ server.ws.send({ type: "full-reload" });
3886
+ }
3887
+ return changed;
3888
+ };
3889
+
3821
3890
  // src/astro/templates.ts
3822
3891
  var WORKSPACE_MARKERS = [
3823
3892
  ".git",
@@ -3944,7 +4013,18 @@ var renderUserAliases = (aliases) => Object.entries(aliases ?? {}).toSorted(([a]
3944
4013
  ${JSON.stringify(find)}: ${JSON.stringify(replacement)},`).join("");
3945
4014
  var astroOutDir = (context) => context.distDir ?? `${context.root}/dist`;
3946
4015
  var adapterRoot = (context) => dirname5(astroOutDir(context));
3947
- var REACT_EXCLUDE = String.raw`exclude: [/\/node_modules\/\.vite\//]`;
4016
+ var REACT_EXCLUDE = String.raw`exclude: [/\/node_modules\/\.vite\//, /\/\.cache\/vite\//]`;
4017
+ var runtimeCacheOptions = (context, generatedModulesDir) => {
4018
+ if (generatedModulesDir !== undefined) {
4019
+ return { astro: "", vite: "" };
4020
+ }
4021
+ return {
4022
+ astro: `
4023
+ cacheDir: ${JSON.stringify(`${context.outDir}/.cache/astro`)},`,
4024
+ vite: `
4025
+ cacheDir: ${JSON.stringify(`${context.outDir}/.cache/vite`)},`
4026
+ };
4027
+ };
3948
4028
  var reactIntegration = (compilerPath) => compilerPath ? `react({ babel: { plugins: [[${JSON.stringify(compilerPath)}, { target: "19" }]] }, ${REACT_EXCLUDE} })` : `react({ ${REACT_EXCLUDE} })`;
3949
4029
  var renderIntegrationBridge = (bridge) => {
3950
4030
  if (!bridge) {
@@ -3985,18 +4065,36 @@ var resolveOptimizeDeps = (options) => {
3985
4065
  ];
3986
4066
  return { optimizeDepsEntries, optimizeDepsInclude };
3987
4067
  };
4068
+ var renderRuntimeModuleWiring = (generatedModulesDir) => {
4069
+ if (generatedModulesDir === undefined) {
4070
+ return {
4071
+ aliasLines: "",
4072
+ imports: ["runtimeModulesPlugin"],
4073
+ pluginEntry: "runtimeModulesPlugin(), "
4074
+ };
4075
+ }
4076
+ const aliasLines = [...RUNTIME_MODULE_FILES].map(([id, file]) => `
4077
+ ${JSON.stringify(id)}: ${JSON.stringify(`${generatedModulesDir}/${file}`)},`).join("");
4078
+ return { aliasLines, imports: [], pluginEntry: "" };
4079
+ };
3988
4080
  var astroConfigTemplate = (options) => {
3989
- const { context, config, needsReact, pages, dataPath, themePath } = options;
4081
+ const { context, config, needsReact, pages, themePath } = options;
4082
+ const { astro: cacheOptions, vite: viteCacheOption } = runtimeCacheOptions(context, options.generatedModulesDir);
3990
4083
  const {
3991
4084
  askPath,
3992
4085
  contentRoutes,
3993
4086
  examplesPath,
3994
4087
  examplesThemePath,
4088
+ generatedModulesDir,
3995
4089
  needsSvelte,
3996
4090
  needsVue,
3997
- openapiPath,
3998
4091
  searchClientPath
3999
4092
  } = options;
4093
+ const {
4094
+ aliasLines: runtimeModuleAliasLines,
4095
+ imports: runtimeModuleImports,
4096
+ pluginEntry: runtimeModulesPluginEntry
4097
+ } = renderRuntimeModuleWiring(generatedModulesDir);
4000
4098
  const { deployment } = config;
4001
4099
  const userAliasLines = renderUserAliases(options.aliases);
4002
4100
  const server = deployment.output === "server";
@@ -4072,6 +4170,7 @@ var astroConfigTemplate = (options) => {
4072
4170
  "blumeIntegration",
4073
4171
  "includeHmrPlugin",
4074
4172
  "prerenderDepsPlugin",
4173
+ ...runtimeModuleImports,
4075
4174
  ...adapterOption.includes("withAdapterRoot") ? ["withAdapterRoot"] : []
4076
4175
  ];
4077
4176
  const blumeImport = `import { ${blumeImports.join(", ")} } from "blume/astro";
@@ -4118,7 +4217,7 @@ ${userConfigSetup}export default defineConfig({
4118
4217
  root: ${JSON.stringify(context.outDir)},
4119
4218
  srcDir: ${JSON.stringify(`${context.outDir}/src`)},
4120
4219
  outDir: ${JSON.stringify(astroOutDir(context))},
4121
- publicDir: ${JSON.stringify(`${context.root}/public`)},
4220
+ publicDir: ${JSON.stringify(`${context.root}/public`)},${cacheOptions}
4122
4221
  output: ${JSON.stringify(deployment.output)},${adapterOption}${sessionOption}${siteOption}${baseOption}${imageOption}${redirectsOption}${i18nOption}${fontsOption}
4123
4222
  integrations: [${integrations.join(", ")}${userIntegrationSpread}],
4124
4223
  markdown: {
@@ -4144,8 +4243,8 @@ ${userConfigSetup}export default defineConfig({
4144
4243
  // request latency behind the user's intent, so most navigations swap
4145
4244
  // instantly.
4146
4245
  prefetch: { prefetchAll: true },
4147
- vite: {
4148
- plugins: [tailwindcss(), includeHmrPlugin(${JSON.stringify(`${context.outDir}/src/generated/includes.json`)}), prerenderDepsPlugin()],
4246
+ vite: {${viteCacheOption}
4247
+ plugins: [${runtimeModulesPluginEntry}tailwindcss(), includeHmrPlugin(${JSON.stringify(`${context.outDir}/src/generated/includes.json`)}), prerenderDepsPlugin()],
4149
4248
  // Everything hydration can reach must be part of the dev dep optimizer's
4150
4249
  // FIRST run. The Vite root is the generated runtime, so user pages,
4151
4250
  // islands, and aliased components live outside it and are only crawled
@@ -4192,12 +4291,10 @@ ${userConfigSetup}export default defineConfig({
4192
4291
  resolve: {
4193
4292
  alias: {
4194
4293
  "blume:ask": ${JSON.stringify(askPath)},
4195
- "blume:data": ${JSON.stringify(dataPath)},
4196
4294
  "blume:examples": ${JSON.stringify(examplesPath)},
4197
4295
  "blume:examples-theme": ${JSON.stringify(examplesThemePath)},
4198
- "blume:openapi": ${JSON.stringify(openapiPath)},
4199
4296
  "blume:search-client": ${JSON.stringify(searchClientPath)},
4200
- "blume:theme": ${JSON.stringify(themePath)},${userAliasLines}
4297
+ "blume:theme": ${JSON.stringify(themePath)},${runtimeModuleAliasLines}${userAliasLines}
4201
4298
  },
4202
4299
  },
4203
4300
  server: {
@@ -4288,7 +4385,7 @@ const provider = createOpenAICompatible({
4288
4385
  modelExpr = `provider(${JSON.stringify(backend.model)})`;
4289
4386
  }
4290
4387
  if (grounded) {
4291
- imports.push('import { createAskContext } from "blume/ai/ask-context.ts";', 'import askData from "../../generated/ask-data.json";');
4388
+ imports.push('import { createAskContext } from "blume/ai/ask-context.ts";', 'import askData from "blume:ask-data";');
4292
4389
  const groundFields = [];
4293
4390
  if (instructions) {
4294
4391
  groundFields.push(`instructions: ${JSON.stringify(instructions)}`);
@@ -4396,7 +4493,7 @@ const { strings } = Astro.props;
4396
4493
  ---
4397
4494
  `;
4398
4495
  var searchEndpointTemplate = () => `// Generated by Blume. Do not edit.
4399
- import documents from "../generated/search.json";
4496
+ import documents from "blume:search-index";
4400
4497
 
4401
4498
  export const prerender = true;
4402
4499
 
@@ -4507,7 +4604,7 @@ export const POST: APIRoute = async ({ request }) => {
4507
4604
  };
4508
4605
  `;
4509
4606
  var rawMarkdownEndpointTemplate = (kind) => `// Generated by Blume. Do not edit.
4510
- import raw from "../generated/raw-markdown.json";
4607
+ import raw from "blume:raw-markdown";
4511
4608
 
4512
4609
  export const prerender = true;
4513
4610
 
@@ -4536,7 +4633,7 @@ import { existsSync } from "node:fs";
4536
4633
  import { readdir, readFile } from "node:fs/promises";
4537
4634
  import { isAbsolute, join, relative, resolve } from "node:path";
4538
4635
  import type { APIRoute } from "astro";
4539
- import assets from "../../generated/content-assets.json";
4636
+ import assets from "blume:content-assets";
4540
4637
 
4541
4638
  export const prerender = true;
4542
4639
 
@@ -4618,22 +4715,17 @@ export const GET: APIRoute = async ({ params }) => {
4618
4715
  };
4619
4716
  `;
4620
4717
  var mcpPageFile = (route) => `${trimChar(route, "/")}.ts`;
4621
- var mcpEndpointTemplate = (route) => {
4622
- const clean = trimChar(route, "/");
4623
- const up = "../".repeat(clean.split("/").length);
4624
- return `// Generated by Blume. Do not edit.
4718
+ var mcpEndpointTemplate = () => `// Generated by Blume. Do not edit.
4625
4719
  import type { APIRoute } from "astro";
4626
4720
  import { createMcpFetchHandler } from "blume/ai/mcp/server.ts";
4627
- import type { McpData } from "blume/ai/mcp/data.ts";
4628
- import data from "${up}generated/mcp-data.json";
4721
+ import data from "blume:mcp-data";
4629
4722
 
4630
4723
  export const prerender = false;
4631
4724
 
4632
- const handler = createMcpFetchHandler(data as McpData);
4725
+ const handler = createMcpFetchHandler(data);
4633
4726
 
4634
4727
  export const ALL: APIRoute = ({ request }) => handler(request);
4635
4728
  `;
4636
- };
4637
4729
  var playgroundProxyTemplate = (origins) => `// Generated by Blume. Do not edit.
4638
4730
  import type { APIRoute } from "astro";
4639
4731
  import { createPlaygroundProxyHandler } from "blume/openapi/proxy.ts";
@@ -4656,7 +4748,7 @@ export function GET() {
4656
4748
  }
4657
4749
  `;
4658
4750
  var rssEndpointTemplate = () => `// Generated by Blume. Do not edit.
4659
- import feeds from "../../generated/rss.json";
4751
+ import feeds from "blume:rss";
4660
4752
 
4661
4753
  export const prerender = true;
4662
4754
 
@@ -4693,26 +4785,50 @@ const customRoutes: { slug: string; title: string }[] = ${JSON.stringify(customR
4693
4785
  const fonts: OgFont[] = ${JSON.stringify(og.fonts ?? [])};
4694
4786
  const families: OgFontFamilies | undefined = ${og.families ? JSON.stringify(og.families) : "undefined"};
4695
4787
 
4788
+ // A page's own description (its \`seo.description\`, else \`description\`) is
4789
+ // the card subtitle, so the image says what the page's og:description says.
4790
+ // Pages without one fall back to the site-wide subtitle at render time.
4791
+ // \`seo.og.description: false\` hides the subtitle on every card, page text
4792
+ // included, which is what switches this off.
4793
+ const pageDescriptions = ${og.pageDescriptions !== false};
4794
+
4795
+ interface CardProps {
4796
+ title: string;
4797
+ description: string | null;
4798
+ }
4799
+
4696
4800
  export function getStaticPaths() {
4697
4801
  const seen = new Set<string>();
4698
- const paths: { params: { slug: string }; props: { title: string } }[] = [];
4699
- const add = (slug: string, title: string) => {
4802
+ const paths: { params: { slug: string }; props: CardProps }[] = [];
4803
+ const add = (slug: string, title: string, description: string | null) => {
4700
4804
  if (seen.has(slug)) {
4701
4805
  return;
4702
4806
  }
4703
4807
  seen.add(slug);
4704
- paths.push({ params: { slug }, props: { title } });
4808
+ paths.push({
4809
+ params: { slug },
4810
+ props: { title, description: pageDescriptions ? description : null },
4811
+ });
4705
4812
  };
4706
4813
  // A custom page wins over a content route sharing its path, so add it first.
4814
+ // Its description is unknown at generate time, so it takes the site subtitle.
4707
4815
  for (const route of customRoutes) {
4708
- add(route.slug, route.title);
4816
+ add(route.slug, route.title, null);
4709
4817
  }
4710
4818
  for (const route of data.routes) {
4711
- add(route.path === "/" ? "index" : route.path.slice(1), route.title);
4819
+ add(
4820
+ route.path === "/" ? "index" : route.path.slice(1),
4821
+ route.title,
4822
+ route.description
4823
+ );
4712
4824
  }${includeChangelog ? `
4713
4825
  // The generated changelog index is not a content route, so it needs its own
4714
4826
  // card. Added last: a custom page or content route owning /changelog wins.
4715
- add("changelog", data.ui.changelog?.title ?? "Changelog");` : ""}
4827
+ add(
4828
+ "changelog",
4829
+ data.ui.changelog?.title ?? "Changelog",
4830
+ data.ui.changelog?.description ?? null
4831
+ );` : ""}
4716
4832
  return paths;
4717
4833
  }
4718
4834
 
@@ -4723,11 +4839,11 @@ const repoSlug = data.config.github
4723
4839
  ? \`\${data.config.github.owner}/\${data.config.github.repo}\`
4724
4840
  : undefined;
4725
4841
 
4726
- export async function GET({ props }: { props: { title: string } }) {
4842
+ export async function GET({ props }: { props: CardProps }) {
4727
4843
  const png = await renderOgImage({
4728
4844
  accent: data.config.og.palette?.accent ?? data.config.theme.accent.light,
4729
4845
  brand: data.config.title,
4730
- description: data.config.og.description,
4846
+ description: props.description ?? data.config.og.description,
4731
4847
  families,
4732
4848
  fonts,
4733
4849
  logo: data.config.og.logo,
@@ -4748,7 +4864,7 @@ var scalarReferenceTemplate = (options) => `---
4748
4864
  // Generated by Blume. Do not edit.
4749
4865
  import { ScalarComponent } from "@scalar/astro";
4750
4866
  import ReferenceLayout from "blume/components/layout/ReferenceLayout.astro";
4751
- import data from ${JSON.stringify(options.dataImport)};
4867
+ import data from "blume:data";
4752
4868
 
4753
4869
  export const prerender = true;
4754
4870
 
@@ -5532,6 +5648,172 @@ const suggestions = [
5532
5648
  </div>
5533
5649
  </PageLayout>
5534
5650
  `;
5651
+ var notFoundMarkdownTemplate = () => `// Generated by Blume. Do not edit. Override by adding \`pages/404.astro\`.
5652
+ import { withBase } from "blume/components/islands/base-path.ts";
5653
+ import { absoluteUrl } from "blume/core/site-url.ts";
5654
+ import data from "blume:data";
5655
+
5656
+ export const prerender = true;
5657
+
5658
+ const nf = data.ui.notFound;
5659
+
5660
+ // Absolute for internal routes when the site is known; an external tab href
5661
+ // passes through untouched.
5662
+ const href = (path: string): string => {
5663
+ const based = withBase(path);
5664
+ return data.config.site && based.startsWith("/") && !based.startsWith("//")
5665
+ ? absoluteUrl(data.config.site, based)
5666
+ : based;
5667
+ };
5668
+
5669
+ // The recovery set of 404.astro: home, every top-level section (a tab links to
5670
+ // its resolved target), then the machine-readable indexes that exist.
5671
+ const links = [
5672
+ { href: href("/"), label: nf.home },
5673
+ ...data.navigation.tabs.map((tab) => ({
5674
+ href: href(tab.href ?? tab.path),
5675
+ label: tab.label,
5676
+ })),
5677
+ ...(data.config.discovery.sitemap
5678
+ ? [{ href: href("/sitemap.xml"), label: nf.sitemap }]
5679
+ : []),
5680
+ ...(data.config.discovery.llmsTxt
5681
+ ? [{ href: href("/llms.txt"), label: nf.llms }]
5682
+ : []),
5683
+ ...(data.config.discovery.api
5684
+ ? [{ href: href("/openapi.json"), label: nf.api }]
5685
+ : []),
5686
+ ];
5687
+
5688
+ const body = [
5689
+ "# " + nf.title,
5690
+ "",
5691
+ nf.description,
5692
+ "",
5693
+ "## " + nf.suggestions,
5694
+ "",
5695
+ ...links.map((link) => "- [" + link.label + "](" + link.href + ")"),
5696
+ "",
5697
+ ].join("\\n");
5698
+
5699
+ export function GET() {
5700
+ return new Response(body, {
5701
+ headers: {
5702
+ "Content-Type": "text/markdown; charset=utf-8",
5703
+ // ~4 characters per token; keep in sync with markdownTokenCount.
5704
+ "x-markdown-tokens": String(Math.ceil(body.length / 4)),
5705
+ },
5706
+ });
5707
+ }
5708
+ `;
5709
+ var notFoundJsonTemplate = () => `// Generated by Blume. Do not edit. Override by adding \`pages/404.astro\`.
5710
+ import { problem } from "blume/ai/api/problem.ts";
5711
+ import { withBase } from "blume/components/islands/base-path.ts";
5712
+ import { absoluteUrl } from "blume/core/site-url.ts";
5713
+ import data from "blume:data";
5714
+
5715
+ export const prerender = true;
5716
+
5717
+ const nf = data.ui.notFound;
5718
+
5719
+ // Absolute for internal routes when the site is known; an external tab href
5720
+ // passes through untouched.
5721
+ const href = (path: string): string => {
5722
+ const based = withBase(path);
5723
+ return data.config.site && based.startsWith("/") && !based.startsWith("//")
5724
+ ? absoluteUrl(data.config.site, based)
5725
+ : based;
5726
+ };
5727
+
5728
+ // The recovery set of 404.astro: home, every top-level section (a tab links to
5729
+ // its resolved target), then the machine-readable indexes that exist.
5730
+ const links = [
5731
+ { href: href("/"), label: nf.home },
5732
+ ...data.navigation.tabs.map((tab) => ({
5733
+ href: href(tab.href ?? tab.path),
5734
+ label: tab.label,
5735
+ })),
5736
+ ...(data.config.discovery.sitemap
5737
+ ? [{ href: href("/sitemap.xml"), label: nf.sitemap }]
5738
+ : []),
5739
+ ...(data.config.discovery.llmsTxt
5740
+ ? [{ href: href("/llms.txt"), label: nf.llms }]
5741
+ : []),
5742
+ ...(data.config.discovery.api
5743
+ ? [{ href: href("/openapi.json"), label: nf.api }]
5744
+ : []),
5745
+ ];
5746
+
5747
+ const body = problem({
5748
+ code: "PAGE_NOT_FOUND",
5749
+ detail: nf.description,
5750
+ links,
5751
+ resolution: nf.suggestions + ": " + links.map((link) => link.href).join(", "),
5752
+ status: 404,
5753
+ title: nf.title,
5754
+ });
5755
+
5756
+ export function GET() {
5757
+ return new Response(JSON.stringify(body, null, 2) + "\\n", {
5758
+ headers: { "Content-Type": "application/problem+json; charset=utf-8" },
5759
+ });
5760
+ }
5761
+ `;
5762
+ var apiPagesIndexTemplate = () => `// Generated by Blume. Do not edit.
5763
+ import { pagesIndexResponse } from "blume/ai/api/handlers.ts";
5764
+ import data from "blume:mcp-data";
5765
+
5766
+ export const prerender = true;
5767
+
5768
+ export function GET() {
5769
+ return pagesIndexResponse(data);
5770
+ }
5771
+ `;
5772
+ var apiPageTemplate = () => `// Generated by Blume. Do not edit.
5773
+ import { pageParams, pageResponse } from "blume/ai/api/handlers.ts";
5774
+ import data from "blume:mcp-data";
5775
+
5776
+ export const prerender = true;
5777
+
5778
+ export function getStaticPaths() {
5779
+ return pageParams(data);
5780
+ }
5781
+
5782
+ export function GET({ props }: { props: { route: string } }) {
5783
+ return pageResponse(data, props.route);
5784
+ }
5785
+ `;
5786
+ var apiNavigationTemplate = () => `// Generated by Blume. Do not edit.
5787
+ import { navigationResponse } from "blume/ai/api/handlers.ts";
5788
+ import data from "blume:mcp-data";
5789
+
5790
+ export const prerender = true;
5791
+
5792
+ export function GET() {
5793
+ return navigationResponse(data);
5794
+ }
5795
+ `;
5796
+ var apiSearchTemplate = () => `// Generated by Blume. Do not edit.
5797
+ import type { APIRoute } from "astro";
5798
+ import { createSearchHandler } from "blume/ai/api/handlers.ts";
5799
+ import data from "blume:mcp-data";
5800
+
5801
+ export const prerender = false;
5802
+
5803
+ const handler = createSearchHandler(data);
5804
+
5805
+ export const GET: APIRoute = ({ request }) => handler(request);
5806
+ `;
5807
+ var apiNotFoundTemplate = (context) => `// Generated by Blume. Do not edit.
5808
+ import type { APIRoute } from "astro";
5809
+ import { apiNotFoundResponse } from "blume/ai/api/handlers.ts";
5810
+
5811
+ export const prerender = false;
5812
+
5813
+ const context = ${JSON.stringify(context)};
5814
+
5815
+ export const ALL: APIRoute = ({ request }) => apiNotFoundResponse(request, context);
5816
+ `;
5535
5817
  var islandDirective = (spec) => spec.client === "only" ? `client:only="${spec.framework}"` : `client:${spec.client}`;
5536
5818
  var wrapperPropsType = (name) => `type Props = typeof ${name} extends (
5537
5819
  props: infer P extends object,
@@ -5702,6 +5984,36 @@ declare module "blume:data" {
5702
5984
  export default data;
5703
5985
  }
5704
5986
 
5987
+ declare module "blume:ask-data" {
5988
+ const askData: import("blume/ai/ask-context.ts").AskData;
5989
+ export default askData;
5990
+ }
5991
+
5992
+ declare module "blume:content-assets" {
5993
+ const assets: Record<string, string>;
5994
+ export default assets;
5995
+ }
5996
+
5997
+ declare module "blume:mcp-data" {
5998
+ const data: import("blume/ai/mcp/data.ts").McpData;
5999
+ export default data;
6000
+ }
6001
+
6002
+ declare module "blume:raw-markdown" {
6003
+ const raw: Record<string, import("blume/ai/markdown.ts").RawMarkdownEntry>;
6004
+ export default raw;
6005
+ }
6006
+
6007
+ declare module "blume:rss" {
6008
+ const feeds: Record<string, string>;
6009
+ export default feeds;
6010
+ }
6011
+
6012
+ declare module "blume:search-index" {
6013
+ const documents: import("blume/search/documents.ts").SearchDocument[];
6014
+ export default documents;
6015
+ }
6016
+
5705
6017
  declare module "blume:examples" {
5706
6018
  type Examples = typeof import("./generated/examples.ts").examples;
5707
6019
  export const examples: Record<string, Examples[keyof Examples]>;
@@ -6288,6 +6600,7 @@ var ar = {
6288
6600
  copyClaudeCode: "نسخ أمر Claude Code",
6289
6601
  copyCode: "نسخ الكود",
6290
6602
  copyCodex: "نسخ أمر Codex",
6603
+ copyFailed: "فشل النسخ",
6291
6604
  copyMarkdown: "نسخ بصيغة Markdown",
6292
6605
  copyServerUrl: "نسخ عنوان URL للخادم",
6293
6606
  edit: "التعديل على GitHub",
@@ -6384,6 +6697,7 @@ var bg = {
6384
6697
  copyClaudeCode: "Копирай командата на Claude Code",
6385
6698
  copyCode: "Копирай кода",
6386
6699
  copyCodex: "Копирай командата на Codex",
6700
+ copyFailed: "Копирането не бе успешно",
6387
6701
  copyMarkdown: "Копирай като Markdown",
6388
6702
  copyServerUrl: "Копирай URL на сървъра",
6389
6703
  edit: "Редактирай в GitHub",
@@ -6480,6 +6794,7 @@ var bn = {
6480
6794
  copyClaudeCode: "Claude Code কমান্ড অনুলিপি করুন",
6481
6795
  copyCode: "কোড অনুলিপি করুন",
6482
6796
  copyCodex: "Codex কমান্ড অনুলিপি করুন",
6797
+ copyFailed: "অনুলিপি ব্যর্থ হয়েছে",
6483
6798
  copyMarkdown: "Markdown হিসেবে অনুলিপি করুন",
6484
6799
  copyServerUrl: "সার্ভার URL অনুলিপি করুন",
6485
6800
  edit: "GitHub-এ সম্পাদনা করুন",
@@ -6576,6 +6891,7 @@ var ca = {
6576
6891
  copyClaudeCode: "Copia l'ordre de Claude Code",
6577
6892
  copyCode: "Copia el codi",
6578
6893
  copyCodex: "Copia l'ordre de Codex",
6894
+ copyFailed: "No s'ha pogut copiar",
6579
6895
  copyMarkdown: "Copia com a Markdown",
6580
6896
  copyServerUrl: "Copia l'URL del servidor",
6581
6897
  edit: "Edita a GitHub",
@@ -6674,6 +6990,7 @@ var cs = {
6674
6990
  copyClaudeCode: "Kopírovat příkaz Claude Code",
6675
6991
  copyCode: "Kopírovat kód",
6676
6992
  copyCodex: "Kopírovat příkaz Codex",
6993
+ copyFailed: "Kopírování se nezdařilo",
6677
6994
  copyMarkdown: "Kopírovat jako Markdown",
6678
6995
  copyServerUrl: "Kopírovat URL serveru",
6679
6996
  edit: "Upravit na GitHubu",
@@ -6770,6 +7087,7 @@ var da = {
6770
7087
  copyClaudeCode: "Kopiér Claude Code-kommando",
6771
7088
  copyCode: "Kopiér kode",
6772
7089
  copyCodex: "Kopiér Codex-kommando",
7090
+ copyFailed: "Kopiering mislykkedes",
6773
7091
  copyMarkdown: "Kopiér som Markdown",
6774
7092
  copyServerUrl: "Kopiér server-URL",
6775
7093
  edit: "Rediger på GitHub",
@@ -6866,6 +7184,7 @@ var de = {
6866
7184
  copyClaudeCode: "Claude-Code-Befehl kopieren",
6867
7185
  copyCode: "Code kopieren",
6868
7186
  copyCodex: "Codex-Befehl kopieren",
7187
+ copyFailed: "Kopieren fehlgeschlagen",
6869
7188
  copyMarkdown: "Als Markdown kopieren",
6870
7189
  copyServerUrl: "Server-URL kopieren",
6871
7190
  edit: "Auf GitHub bearbeiten",
@@ -6962,6 +7281,7 @@ var el = {
6962
7281
  copyClaudeCode: "Αντιγραφή εντολής Claude Code",
6963
7282
  copyCode: "Αντιγραφή κώδικα",
6964
7283
  copyCodex: "Αντιγραφή εντολής Codex",
7284
+ copyFailed: "Η αντιγραφή απέτυχε",
6965
7285
  copyMarkdown: "Αντιγραφή ως Markdown",
6966
7286
  copyServerUrl: "Αντιγραφή URL διακομιστή",
6967
7287
  edit: "Επεξεργασία στο GitHub",
@@ -7060,6 +7380,7 @@ var es = {
7060
7380
  copyClaudeCode: "Copiar comando de Claude Code",
7061
7381
  copyCode: "Copiar código",
7062
7382
  copyCodex: "Copiar comando de Codex",
7383
+ copyFailed: "Error al copiar",
7063
7384
  copyMarkdown: "Copiar como Markdown",
7064
7385
  copyServerUrl: "Copiar URL del servidor",
7065
7386
  edit: "Editar en GitHub",
@@ -7158,6 +7479,7 @@ var fa = {
7158
7479
  copyClaudeCode: "کپی دستور Claude Code",
7159
7480
  copyCode: "کپی کد",
7160
7481
  copyCodex: "کپی دستور Codex",
7482
+ copyFailed: "کپی ناموفق بود",
7161
7483
  copyMarkdown: "کپی به‌صورت Markdown",
7162
7484
  copyServerUrl: "کپی نشانی سرور",
7163
7485
  edit: "ویرایش در GitHub",
@@ -7254,6 +7576,7 @@ var fi = {
7254
7576
  copyClaudeCode: "Kopioi Claude Code -komento",
7255
7577
  copyCode: "Kopioi koodi",
7256
7578
  copyCodex: "Kopioi Codex-komento",
7579
+ copyFailed: "Kopiointi epäonnistui",
7257
7580
  copyMarkdown: "Kopioi Markdownina",
7258
7581
  copyServerUrl: "Kopioi palvelimen URL",
7259
7582
  edit: "Muokkaa GitHubissa",
@@ -7350,6 +7673,7 @@ var fr = {
7350
7673
  copyClaudeCode: "Copier la commande Claude Code",
7351
7674
  copyCode: "Copier le code",
7352
7675
  copyCodex: "Copier la commande Codex",
7676
+ copyFailed: "Échec de la copie",
7353
7677
  copyMarkdown: "Copier en Markdown",
7354
7678
  copyServerUrl: "Copier l'URL du serveur",
7355
7679
  edit: "Modifier sur GitHub",
@@ -7448,6 +7772,7 @@ var he = {
7448
7772
  copyClaudeCode: "העתק פקודת Claude Code",
7449
7773
  copyCode: "העתק קוד",
7450
7774
  copyCodex: "העתק פקודת Codex",
7775
+ copyFailed: "ההעתקה נכשלה",
7451
7776
  copyMarkdown: "העתק כ-Markdown",
7452
7777
  copyServerUrl: "העתק כתובת URL של השרת",
7453
7778
  edit: "ערוך ב-GitHub",
@@ -7544,6 +7869,7 @@ var hi = {
7544
7869
  copyClaudeCode: "Claude Code कमांड कॉपी करें",
7545
7870
  copyCode: "कोड कॉपी करें",
7546
7871
  copyCodex: "Codex कमांड कॉपी करें",
7872
+ copyFailed: "कॉपी विफल रहा",
7547
7873
  copyMarkdown: "Markdown के रूप में कॉपी करें",
7548
7874
  copyServerUrl: "सर्वर URL कॉपी करें",
7549
7875
  edit: "GitHub पर संपादित करें",
@@ -7640,6 +7966,7 @@ var hr = {
7640
7966
  copyClaudeCode: "Kopiraj naredbu Claude Code",
7641
7967
  copyCode: "Kopiraj kôd",
7642
7968
  copyCodex: "Kopiraj naredbu Codex",
7969
+ copyFailed: "Kopiranje nije uspjelo",
7643
7970
  copyMarkdown: "Kopiraj kao Markdown",
7644
7971
  copyServerUrl: "Kopiraj URL poslužitelja",
7645
7972
  edit: "Uredi na GitHubu",
@@ -7736,6 +8063,7 @@ var hu = {
7736
8063
  copyClaudeCode: "Claude Code parancs másolása",
7737
8064
  copyCode: "Kód másolása",
7738
8065
  copyCodex: "Codex parancs másolása",
8066
+ copyFailed: "A másolás nem sikerült",
7739
8067
  copyMarkdown: "Másolás Markdownként",
7740
8068
  copyServerUrl: "Szerver URL másolása",
7741
8069
  edit: "Szerkesztés a GitHubon",
@@ -7832,6 +8160,7 @@ var id = {
7832
8160
  copyClaudeCode: "Salin perintah Claude Code",
7833
8161
  copyCode: "Salin kode",
7834
8162
  copyCodex: "Salin perintah Codex",
8163
+ copyFailed: "Gagal menyalin",
7835
8164
  copyMarkdown: "Salin sebagai Markdown",
7836
8165
  copyServerUrl: "Salin URL server",
7837
8166
  edit: "Edit di GitHub",
@@ -7928,6 +8257,7 @@ var it = {
7928
8257
  copyClaudeCode: "Copia comando di Claude Code",
7929
8258
  copyCode: "Copia codice",
7930
8259
  copyCodex: "Copia comando di Codex",
8260
+ copyFailed: "Copia non riuscita",
7931
8261
  copyMarkdown: "Copia come Markdown",
7932
8262
  copyServerUrl: "Copia URL del server",
7933
8263
  edit: "Modifica su GitHub",
@@ -8026,6 +8356,7 @@ var ja = {
8026
8356
  copyClaudeCode: "Claude Code コマンドをコピー",
8027
8357
  copyCode: "コードをコピー",
8028
8358
  copyCodex: "Codex コマンドをコピー",
8359
+ copyFailed: "コピーに失敗しました",
8029
8360
  copyMarkdown: "Markdown としてコピー",
8030
8361
  copyServerUrl: "サーバー URL をコピー",
8031
8362
  edit: "GitHub で編集",
@@ -8124,6 +8455,7 @@ var ko = {
8124
8455
  copyClaudeCode: "Claude Code 명령 복사",
8125
8456
  copyCode: "코드 복사",
8126
8457
  copyCodex: "Codex 명령 복사",
8458
+ copyFailed: "복사 실패",
8127
8459
  copyMarkdown: "Markdown으로 복사",
8128
8460
  copyServerUrl: "서버 URL 복사",
8129
8461
  edit: "GitHub에서 편집",
@@ -8222,6 +8554,7 @@ var nl = {
8222
8554
  copyClaudeCode: "Claude Code-opdracht kopiëren",
8223
8555
  copyCode: "Code kopiëren",
8224
8556
  copyCodex: "Codex-opdracht kopiëren",
8557
+ copyFailed: "Kopiëren mislukt",
8225
8558
  copyMarkdown: "Kopiëren als Markdown",
8226
8559
  copyServerUrl: "Server-URL kopiëren",
8227
8560
  edit: "Bewerken op GitHub",
@@ -8318,6 +8651,7 @@ var no = {
8318
8651
  copyClaudeCode: "Kopier Claude Code-kommando",
8319
8652
  copyCode: "Kopier kode",
8320
8653
  copyCodex: "Kopier Codex-kommando",
8654
+ copyFailed: "Kopiering mislyktes",
8321
8655
  copyMarkdown: "Kopier som Markdown",
8322
8656
  copyServerUrl: "Kopier server-URL",
8323
8657
  edit: "Rediger på GitHub",
@@ -8414,6 +8748,7 @@ var pl = {
8414
8748
  copyClaudeCode: "Kopiuj polecenie Claude Code",
8415
8749
  copyCode: "Kopiuj kod",
8416
8750
  copyCodex: "Kopiuj polecenie Codex",
8751
+ copyFailed: "Kopiowanie nie powiodło się",
8417
8752
  copyMarkdown: "Kopiuj jako Markdown",
8418
8753
  copyServerUrl: "Kopiuj adres URL serwera",
8419
8754
  edit: "Edytuj na GitHubie",
@@ -8510,6 +8845,7 @@ var ptBR = {
8510
8845
  copyClaudeCode: "Copiar comando do Claude Code",
8511
8846
  copyCode: "Copiar código",
8512
8847
  copyCodex: "Copiar comando do Codex",
8848
+ copyFailed: "Falha ao copiar",
8513
8849
  copyMarkdown: "Copiar como Markdown",
8514
8850
  copyServerUrl: "Copiar URL do servidor",
8515
8851
  edit: "Editar no GitHub",
@@ -8608,6 +8944,7 @@ var pt = {
8608
8944
  copyClaudeCode: "Copiar comando do Claude Code",
8609
8945
  copyCode: "Copiar código",
8610
8946
  copyCodex: "Copiar comando do Codex",
8947
+ copyFailed: "Falha ao copiar",
8611
8948
  copyMarkdown: "Copiar como Markdown",
8612
8949
  copyServerUrl: "Copiar URL do servidor",
8613
8950
  edit: "Editar no GitHub",
@@ -8706,6 +9043,7 @@ var ro = {
8706
9043
  copyClaudeCode: "Copiază comanda Claude Code",
8707
9044
  copyCode: "Copiază codul",
8708
9045
  copyCodex: "Copiază comanda Codex",
9046
+ copyFailed: "Copierea a eșuat",
8709
9047
  copyMarkdown: "Copiază ca Markdown",
8710
9048
  copyServerUrl: "Copiază URL-ul serverului",
8711
9049
  edit: "Editează pe GitHub",
@@ -8802,6 +9140,7 @@ var ru = {
8802
9140
  copyClaudeCode: "Скопировать команду Claude Code",
8803
9141
  copyCode: "Скопировать код",
8804
9142
  copyCodex: "Скопировать команду Codex",
9143
+ copyFailed: "Не удалось скопировать",
8805
9144
  copyMarkdown: "Скопировать как Markdown",
8806
9145
  copyServerUrl: "Скопировать URL сервера",
8807
9146
  edit: "Редактировать на GitHub",
@@ -8898,6 +9237,7 @@ var sk = {
8898
9237
  copyClaudeCode: "Kopírovať príkaz Claude Code",
8899
9238
  copyCode: "Kopírovať kód",
8900
9239
  copyCodex: "Kopírovať príkaz Codex",
9240
+ copyFailed: "Kopírovanie zlyhalo",
8901
9241
  copyMarkdown: "Kopírovať ako Markdown",
8902
9242
  copyServerUrl: "Kopírovať URL servera",
8903
9243
  edit: "Upraviť na GitHube",
@@ -8994,6 +9334,7 @@ var sr = {
8994
9334
  copyClaudeCode: "Копирај Claude Code команду",
8995
9335
  copyCode: "Копирај код",
8996
9336
  copyCodex: "Копирај Codex команду",
9337
+ copyFailed: "Копирање није успело",
8997
9338
  copyMarkdown: "Копирај као Markdown",
8998
9339
  copyServerUrl: "Копирај URL сервера",
8999
9340
  edit: "Уреди на GitHub-у",
@@ -9090,6 +9431,7 @@ var sv = {
9090
9431
  copyClaudeCode: "Kopiera Claude Code-kommando",
9091
9432
  copyCode: "Kopiera kod",
9092
9433
  copyCodex: "Kopiera Codex-kommando",
9434
+ copyFailed: "Kopiering misslyckades",
9093
9435
  copyMarkdown: "Kopiera som Markdown",
9094
9436
  copyServerUrl: "Kopiera server-URL",
9095
9437
  edit: "Redigera på GitHub",
@@ -9186,6 +9528,7 @@ var th = {
9186
9528
  copyClaudeCode: "คัดลอกคำสั่ง Claude Code",
9187
9529
  copyCode: "คัดลอกโค้ด",
9188
9530
  copyCodex: "คัดลอกคำสั่ง Codex",
9531
+ copyFailed: "คัดลอกไม่สำเร็จ",
9189
9532
  copyMarkdown: "คัดลอกเป็น Markdown",
9190
9533
  copyServerUrl: "คัดลอก URL ของเซิร์ฟเวอร์",
9191
9534
  edit: "แก้ไขบน GitHub",
@@ -9284,6 +9627,7 @@ var tr = {
9284
9627
  copyClaudeCode: "Claude Code komutunu kopyala",
9285
9628
  copyCode: "Kodu kopyala",
9286
9629
  copyCodex: "Codex komutunu kopyala",
9630
+ copyFailed: "Kopyalama başarısız oldu",
9287
9631
  copyMarkdown: "Markdown olarak kopyala",
9288
9632
  copyServerUrl: "Sunucu URL'sini kopyala",
9289
9633
  edit: "GitHub'da düzenle",
@@ -9380,6 +9724,7 @@ var uk = {
9380
9724
  copyClaudeCode: "Скопіювати команду Claude Code",
9381
9725
  copyCode: "Скопіювати код",
9382
9726
  copyCodex: "Скопіювати команду Codex",
9727
+ copyFailed: "Не вдалося скопіювати",
9383
9728
  copyMarkdown: "Скопіювати як Markdown",
9384
9729
  copyServerUrl: "Скопіювати URL сервера",
9385
9730
  edit: "Редагувати на GitHub",
@@ -9476,6 +9821,7 @@ var vi = {
9476
9821
  copyClaudeCode: "Sao chép lệnh Claude Code",
9477
9822
  copyCode: "Sao chép mã",
9478
9823
  copyCodex: "Sao chép lệnh Codex",
9824
+ copyFailed: "Sao chép thất bại",
9479
9825
  copyMarkdown: "Sao chép dưới dạng Markdown",
9480
9826
  copyServerUrl: "Sao chép URL máy chủ",
9481
9827
  edit: "Chỉnh sửa trên GitHub",
@@ -9574,6 +9920,7 @@ var zhTW = {
9574
9920
  copyClaudeCode: "複製 Claude Code 指令",
9575
9921
  copyCode: "複製程式碼",
9576
9922
  copyCodex: "複製 Codex 指令",
9923
+ copyFailed: "複製失敗",
9577
9924
  copyMarkdown: "複製為 Markdown",
9578
9925
  copyServerUrl: "複製伺服器 URL",
9579
9926
  edit: "在 GitHub 上編輯",
@@ -9672,6 +10019,7 @@ var zh = {
9672
10019
  copyClaudeCode: "复制 Claude Code 命令",
9673
10020
  copyCode: "复制代码",
9674
10021
  copyCodex: "复制 Codex 命令",
10022
+ copyFailed: "复制失败",
9675
10023
  copyMarkdown: "复制为 Markdown",
9676
10024
  copyServerUrl: "复制服务器 URL",
9677
10025
  edit: "在 GitHub 上编辑",
@@ -9810,6 +10158,7 @@ var uiStringsObject = z.object({
9810
10158
  copyClaudeCode: z.string().default("Copy Claude Code command"),
9811
10159
  copyCode: z.string().default("Copy code"),
9812
10160
  copyCodex: z.string().default("Copy Codex command"),
10161
+ copyFailed: z.string().default("Copy failed"),
9813
10162
  copyMarkdown: z.string().default("Copy as Markdown"),
9814
10163
  copyServerUrl: z.string().default("Copy server URL"),
9815
10164
  edit: z.string().default("Edit on GitHub"),
@@ -9871,6 +10220,7 @@ var uiStringsObject = z.object({
9871
10220
  toggleTheme: z.string().default("Toggle color theme")
9872
10221
  }).prefault({}),
9873
10222
  notFound: z.object({
10223
+ api: z.string().default("JSON API description (openapi.json)"),
9874
10224
  description: z.string().default("We couldn't find the page you're looking for."),
9875
10225
  home: z.string().default("Back to home"),
9876
10226
  llms: z.string().default("Docs index for AI agents (llms.txt)"),
@@ -10377,6 +10727,7 @@ var llmsTxtObjectSchema = z2.strictObject({
10377
10727
  openapi: z2.boolean().default(true)
10378
10728
  });
10379
10729
  var aiConfigSchema = z2.strictObject({
10730
+ api: z2.boolean().default(true),
10380
10731
  ask: z2.strictObject({
10381
10732
  apiKeyEnv: z2.string().optional(),
10382
10733
  baseUrl: z2.url().optional(),
@@ -10734,6 +11085,7 @@ var openapiSourceSchema = z2.strictObject({
10734
11085
  label: z2.string().optional(),
10735
11086
  noindex: z2.boolean().default(false),
10736
11087
  route: z2.string().optional(),
11088
+ seoDescriptionSuffix: z2.boolean().default(true),
10737
11089
  spec: z2.string()
10738
11090
  });
10739
11091
  var scalarConfigSchema = z2.record(z2.string(), z2.unknown()).optional();
@@ -12651,7 +13003,8 @@ var buildIncludeGraph = (pages) => {
12651
13003
  continue;
12652
13004
  }
12653
13005
  for (const partial of page.includes ?? []) {
12654
- const includers = graph[partial] ??= [];
13006
+ const includers = graph[partial] ?? [];
13007
+ graph[partial] = includers;
12655
13008
  if (!includers.includes(sourcePath)) {
12656
13009
  includers.push(sourcePath);
12657
13010
  }
@@ -12773,6 +13126,7 @@ var versionAlternatesFor = (byKey, versionKey, locale) => {
12773
13126
  };
12774
13127
  var MANIFEST_VERSION = 1;
12775
13128
  var contentIndexable = (page, config) => !page.meta.search.exclude && (!page.meta.sidebar.hidden || config.search.indexing.includeHiddenPages);
13129
+ var routeDescription = (page) => page.meta.seo.description ?? page.description;
12776
13130
  var buildFallbackRoutes = (graph, i18n, alternatesByKey, basePath, versionAlternatesByKey) => {
12777
13131
  const fallback = resolveFallbackLocale(i18n);
12778
13132
  if (!fallback) {
@@ -12798,6 +13152,7 @@ var buildFallbackRoutes = (graph, i18n, alternatesByKey, basePath, versionAltern
12798
13152
  alternates: alternatesByKey.get(key) ?? [],
12799
13153
  collection: source.collection ?? "docs",
12800
13154
  contentType: source.contentType,
13155
+ description: routeDescription(source),
12801
13156
  draft: source.meta.draft,
12802
13157
  editUrl: source.editUrl,
12803
13158
  entryId: source.entryId ?? source.source.ref,
@@ -12840,6 +13195,7 @@ var buildManifest = (options) => {
12840
13195
  alternates: alternatesByKey.get(page.translationKey) ?? [],
12841
13196
  collection: page.collection ?? "docs",
12842
13197
  contentType: page.contentType,
13198
+ description: routeDescription(page),
12843
13199
  draft: page.meta.draft,
12844
13200
  editUrl: page.editUrl,
12845
13201
  entryId: page.entryId ?? page.source.ref,
@@ -13830,7 +14186,10 @@ var GRAPHQL_MEMBER_PHRASES = {
13830
14186
  subscription: "subscription",
13831
14187
  union: "union type"
13832
14188
  };
13833
- var operationDescription = (spec, operation) => {
14189
+ var operationDescription = (spec, operation, options) => {
14190
+ if (!options.suffix) {
14191
+ return clip(plainProse(operation.description || operation.summary) || options.title, META_DESCRIPTION_MAX);
14192
+ }
13834
14193
  let suffix;
13835
14194
  if (spec.kind === "asyncapi") {
13836
14195
  suffix = `Reference for the ${operation.method} operation on ${operation.path} in the ${apiName(spec)} API.`;
@@ -13863,7 +14222,10 @@ var operationMdx = (spec, operation, reference) => {
13863
14222
  searchFlags.exclude = true;
13864
14223
  }
13865
14224
  const seo = {
13866
- description: operationDescription(spec, operation)
14225
+ description: operationDescription(spec, operation, {
14226
+ suffix: reference?.seoDescriptionSuffix !== false,
14227
+ title
14228
+ })
13867
14229
  };
13868
14230
  if (reference?.noindex) {
13869
14231
  seo.noindex = true;
@@ -16048,7 +16410,7 @@ import { existsSync as existsSync19 } from "node:fs";
16048
16410
  import { mkdir as mkdir8, readdir as readdir4, readFile as readFile20, stat as stat4, writeFile as writeFile7 } from "node:fs/promises";
16049
16411
  import { build } from "astro";
16050
16412
  import { defineCommand as defineCommand3 } from "citty";
16051
- import { dirname as dirname15, join as join33, resolve as resolve12 } from "pathe";
16413
+ import { dirname as dirname16, join as join33, resolve as resolve13 } from "pathe";
16052
16414
 
16053
16415
  // src/deploy/rss.ts
16054
16416
  import { escape as escapeXml } from "html-escaper";
@@ -16150,6 +16512,23 @@ var askApiUrl = (endpoint, site, abs) => {
16150
16512
  }
16151
16513
  return site && endpoint.startsWith("/") ? absoluteUrl(site, endpoint) : endpoint;
16152
16514
  };
16515
+ var markdownArtifact = (config, abs) => {
16516
+ const negotiates = config.deployment.output === "server" && (config.deployment.adapter === "vercel" || config.deployment.adapter === "cloudflare");
16517
+ return negotiates ? { contentNegotiation: "text/markdown", pattern: abs("/{route}.md") } : { pattern: abs("/{route}.md") };
16518
+ };
16519
+ var apiArtifact = (config, abs) => {
16520
+ if (!config.ai.api) {
16521
+ return null;
16522
+ }
16523
+ const api = {
16524
+ openapi: abs(OPENAPI_PATH),
16525
+ pages: abs(API_PAGES_PATH)
16526
+ };
16527
+ if (config.deployment.output === "server") {
16528
+ api.search = abs(API_SEARCH_PATH);
16529
+ }
16530
+ return api;
16531
+ };
16153
16532
  var wellKnownArtifacts = (config, abs) => {
16154
16533
  const artifacts = {};
16155
16534
  if (config.ai.webBotAuth.keys.length > 0) {
@@ -16174,9 +16553,13 @@ var buildAgentReadability = (project) => {
16174
16553
  const based = withBasePath(deployBase, path);
16175
16554
  return site ? absoluteUrl(site, based) : based;
16176
16555
  };
16177
- const negotiates = config.deployment.output === "server" && (config.deployment.adapter === "vercel" || config.deployment.adapter === "cloudflare");
16178
- const markdown = negotiates ? { contentNegotiation: "text/markdown", pattern: abs("/{route}.md") } : { pattern: abs("/{route}.md") };
16179
- const artifacts = { markdown };
16556
+ const artifacts = {
16557
+ markdown: markdownArtifact(config, abs)
16558
+ };
16559
+ const api = apiArtifact(config, abs);
16560
+ if (api) {
16561
+ artifacts.api = api;
16562
+ }
16180
16563
  if (config.ai.llmsTxt.enabled) {
16181
16564
  artifacts.llmsFullTxt = abs("/llms-full.txt");
16182
16565
  artifacts.llmsTxt = abs("/llms.txt");
@@ -16632,7 +17015,7 @@ var SERIALIZERS = {
16632
17015
  YouTube: youtube
16633
17016
  };
16634
17017
  var escapeRegExp2 = (value) => value.replaceAll(/[$()*+.?[\\\]^{|}]/gu, String.raw`\$&`);
16635
- var componentHint = (registry2) => new RegExp(`<(?:${Object.keys(registry2).map(escapeRegExp2).join("|")})[\\s/>]`, "u");
17018
+ var componentHint = (registry3) => new RegExp(`<(?:${Object.keys(registry3).map(escapeRegExp2).join("|")})[\\s/>]`, "u");
16636
17019
  var BUILT_IN_HINT = componentHint(SERIALIZERS);
16637
17020
  var componentRegistry = (components) => components && Object.keys(components).length > 0 ? { ...SERIALIZERS, ...components } : SERIALIZERS;
16638
17021
  var renderSlice = (walk, start, end, nodes) => {
@@ -16691,8 +17074,8 @@ var collectSplices = (walk, nodes, out) => {
16691
17074
  }
16692
17075
  };
16693
17076
  var downlevelComponents = (source, components, frontmatter) => {
16694
- const registry2 = componentRegistry(components);
16695
- const hint = registry2 === SERIALIZERS ? BUILT_IN_HINT : componentHint(registry2);
17077
+ const registry3 = componentRegistry(components);
17078
+ const hint = registry3 === SERIALIZERS ? BUILT_IN_HINT : componentHint(registry3);
16696
17079
  if (!hint.test(source)) {
16697
17080
  return source;
16698
17081
  }
@@ -16703,7 +17086,7 @@ var downlevelComponents = (source, components, frontmatter) => {
16703
17086
  return source;
16704
17087
  }
16705
17088
  const splices = [];
16706
- collectSplices({ frontmatter, registry: registry2, source }, tree.children ?? [], splices);
17089
+ collectSplices({ frontmatter, registry: registry3, source }, tree.children ?? [], splices);
16707
17090
  return splices.length > 0 ? applySplices(source, splices) : source;
16708
17091
  };
16709
17092
 
@@ -16892,6 +17275,9 @@ var agentResourceLines = (project) => {
16892
17275
  `- [llms-full.txt](${url("/llms-full.txt")}): The full Markdown of every page in one file.`,
16893
17276
  `- [Page Markdown](${url("/index.md")}): Append \`.md\` to any page URL to fetch that page as raw Markdown.`
16894
17277
  ];
17278
+ if (config.ai.api) {
17279
+ lines.push(`- [JSON API](${url(API_PAGES_PATH)}): Page index of the JSON docs API; each entry links the page's JSON and Markdown forms. Described by the OpenAPI document at ${url(OPENAPI_PATH)}.`);
17280
+ }
16895
17281
  if (config.ai.mcp.enabled) {
16896
17282
  lines.push(`- [MCP server](${url(config.ai.mcp.route)}): Streamable HTTP Model Context Protocol server with search_docs, get_page, list_pages, and get_navigation tools, plus every page as a resource. Discovery document: ${url("/.well-known/mcp.json")}`);
16897
17283
  }
@@ -17831,11 +18217,38 @@ ${references.join(`
17831
18217
 
17832
18218
  // src/deploy/vercel-negotiation.ts
17833
18219
  var ACCEPT_MARKDOWN_HEADER_VALUE = "(.*,)?\\s*text/(x-)?markdown(\\s*[;,].*)?$";
18220
+ var ACCEPT_JSON_HEADER_VALUE = "(.*,)?\\s*application/(problem\\+)?json(\\s*[;,].*)?$";
17834
18221
  var isString6 = (value) => typeof value === "string";
17835
18222
  var ACCEPT_MARKDOWN_CONDITION = [
17836
18223
  { key: "accept", type: "header", value: ACCEPT_MARKDOWN_HEADER_VALUE }
17837
18224
  ];
18225
+ var ACCEPT_JSON_CONDITION = [
18226
+ { key: "accept", type: "header", value: ACCEPT_JSON_HEADER_VALUE }
18227
+ ];
17838
18228
  var VARY_ACCEPT = { vary: "Accept" };
18229
+ var NOT_FOUND_MARKDOWN_DEST = "/404.md";
18230
+ var NOT_FOUND_JSON_DEST = "/404.json";
18231
+ var NOT_FOUND_HTML_DEST = "/404.html";
18232
+ var NOT_FOUND_MARKDOWN_ROUTES = [
18233
+ {
18234
+ dest: NOT_FOUND_MARKDOWN_DEST,
18235
+ has: ACCEPT_MARKDOWN_CONDITION,
18236
+ headers: VARY_ACCEPT,
18237
+ src: "^/.*$",
18238
+ status: 404
18239
+ },
18240
+ { dest: NOT_FOUND_MARKDOWN_DEST, src: "^/.*\\.mdx?$", status: 404 }
18241
+ ];
18242
+ var NOT_FOUND_JSON_ROUTES = [
18243
+ {
18244
+ dest: NOT_FOUND_JSON_DEST,
18245
+ has: ACCEPT_JSON_CONDITION,
18246
+ headers: VARY_ACCEPT,
18247
+ src: "^/.*$",
18248
+ status: 404
18249
+ },
18250
+ { dest: NOT_FOUND_JSON_DEST, src: "^/.*\\.json$", status: 404 }
18251
+ ];
17839
18252
  var MAX_ALTERNATION_LENGTH = 3900;
17840
18253
  var REGEX_SPECIALS = /[$()*+.?[\]^{|}\\]/gu;
17841
18254
  var routePattern = (route) => encodeURI(route).replace(REGEX_SPECIALS, "\\$&");
@@ -17891,8 +18304,8 @@ var TRAILING_SLASH_REDIRECT = {
17891
18304
  src: "^/(.+)/$",
17892
18305
  status: 308
17893
18306
  };
17894
- var isNegotiationRoute = (route) => route.has?.some((condition) => condition.value === ACCEPT_MARKDOWN_HEADER_VALUE) === true || route.continue === true && route.headers?.vary === "Accept" && isString6(route.src) && Object.keys(route).length === 3 || route.continue === true && isString6(route.headers?.link) && route.src === HOME_SRC && Object.keys(route).length === 3 || route.status === TRAILING_SLASH_REDIRECT.status && route.src === TRAILING_SLASH_REDIRECT.src;
17895
- var injectNegotiationRoutes = (configText, routePaths, homeLinkHeader, contentTypeOverrides, homeTokens) => {
18307
+ var isNegotiationRoute = (route) => route.has?.some((condition) => condition.value === ACCEPT_MARKDOWN_HEADER_VALUE) === true || route.dest === NOT_FOUND_MARKDOWN_DEST && route.status === 404 || route.dest === NOT_FOUND_JSON_DEST && route.status === 404 || route.continue === true && route.headers?.vary === "Accept" && isString6(route.src) && Object.keys(route).length === 3 || route.continue === true && isString6(route.headers?.link) && route.src === HOME_SRC && Object.keys(route).length === 3 || route.status === TRAILING_SLASH_REDIRECT.status && route.src === TRAILING_SLASH_REDIRECT.src;
18308
+ var injectNegotiationRoutes = (configText, routePaths, homeLinkHeader, contentTypeOverrides, homeTokens, notFound = {}) => {
17896
18309
  const overrideEntries = Object.entries(contentTypeOverrides ?? {});
17897
18310
  let config;
17898
18311
  try {
@@ -17920,6 +18333,16 @@ var injectNegotiationRoutes = (configText, routePaths, homeLinkHeader, contentTy
17920
18333
  });
17921
18334
  }
17922
18335
  routes.splice(filesystemIndex, 0, ...headerRoutes, ...rewriteRoutes, TRAILING_SLASH_REDIRECT);
18336
+ const notFoundRoutes = [
18337
+ ...notFound.markdown ? NOT_FOUND_MARKDOWN_ROUTES : [],
18338
+ ...notFound.json ? NOT_FOUND_JSON_ROUTES : []
18339
+ ];
18340
+ if (notFoundRoutes.length > 0) {
18341
+ const fallbackIndex = routes.findIndex((route) => route.status === 404 && route.dest === NOT_FOUND_HTML_DEST);
18342
+ if (fallbackIndex !== -1) {
18343
+ routes.splice(fallbackIndex, 0, ...notFoundRoutes);
18344
+ }
18345
+ }
17923
18346
  config.routes = routes;
17924
18347
  return `${JSON.stringify(config, null, "\t")}
17925
18348
  `;
@@ -18473,15 +18896,528 @@ import { imageSize as imageSize2 } from "image-size";
18473
18896
  import pMap6 from "p-map";
18474
18897
  import {
18475
18898
  basename as basename5,
18476
- dirname as dirname13,
18477
- isAbsolute as isAbsolute11,
18899
+ dirname as dirname14,
18900
+ isAbsolute as isAbsolute13,
18478
18901
  join as join31,
18479
18902
  normalize as normalize4,
18480
- relative as relative17,
18481
- resolve as resolve10
18903
+ relative as relative18,
18904
+ resolve as resolve11
18482
18905
  } from "pathe";
18483
18906
  import { glob as glob7 } from "tinyglobby";
18484
18907
 
18908
+ // src/ai/api/problem.ts
18909
+ var PROBLEM_TYPE = "application/problem+json";
18910
+
18911
+ // src/ai/api/spec.ts
18912
+ var JSON_TYPE = "application/json";
18913
+ var MARKDOWN_TYPE = "text/markdown";
18914
+ var TEXT_TYPE = "text/plain";
18915
+ var ref = (name) => ({
18916
+ $ref: `#/components/schemas/${name}`
18917
+ });
18918
+ var jsonResponse = (description, schema) => ({
18919
+ content: { [JSON_TYPE]: { schema: ref(schema) } },
18920
+ description
18921
+ });
18922
+ var problemResponse = (description) => ({
18923
+ content: { [PROBLEM_TYPE]: { schema: ref("Problem") } },
18924
+ description
18925
+ });
18926
+ var textResponse = (description, type) => ({
18927
+ content: { [type]: { schema: { type: "string" } } },
18928
+ description
18929
+ });
18930
+ var string = (description) => ({ description, type: "string" });
18931
+ var facetsSchema = {
18932
+ additionalProperties: { type: "string" },
18933
+ description: "Facet values the site declares for the page's content type (`content.types.<type>.facets`), key → value.",
18934
+ type: "object"
18935
+ };
18936
+ var versionProperty = {
18937
+ description: 'Docs version the page belongs to on a versioned site: `""` for the current docs, else an archived version id. Absent on unversioned sites.',
18938
+ type: "string"
18939
+ };
18940
+ var searchParameters = [
18941
+ {
18942
+ description: "The search query.",
18943
+ in: "query",
18944
+ name: "q",
18945
+ required: true,
18946
+ schema: { minLength: 1, type: "string" }
18947
+ },
18948
+ {
18949
+ description: "Maximum hits to return (default 8, at most 20).",
18950
+ in: "query",
18951
+ name: "limit",
18952
+ required: false,
18953
+ schema: { default: 8, maximum: 20, minimum: 1, type: "integer" }
18954
+ },
18955
+ {
18956
+ description: "Only include pages of these content types (frontmatter `type`, e.g. `doc`, `rfc`). Comma-separated or repeated. Omit for every type.",
18957
+ explode: false,
18958
+ in: "query",
18959
+ name: "contentTypes",
18960
+ required: false,
18961
+ schema: { items: { type: "string" }, type: "array" },
18962
+ style: "form"
18963
+ },
18964
+ {
18965
+ description: "Only include pages in this locale (e.g. `fr`). Omit for every language.",
18966
+ in: "query",
18967
+ name: "locale",
18968
+ required: false,
18969
+ schema: { type: "string" }
18970
+ },
18971
+ {
18972
+ description: "Docs version to scope to on a versioned site: `latest` (the default — current docs only), `all`, or an archived version id. Ignored when the site is unversioned.",
18973
+ in: "query",
18974
+ name: "version",
18975
+ required: false,
18976
+ schema: { type: "string" }
18977
+ },
18978
+ {
18979
+ description: "Only include pages matching every facet, as `filters[key]=value` pairs (e.g. `filters[status]=enforced`). Facets are metadata the site declares per content type; the page index shows each page's values.",
18980
+ explode: true,
18981
+ in: "query",
18982
+ name: "filters",
18983
+ required: false,
18984
+ schema: { additionalProperties: { type: "string" }, type: "object" },
18985
+ style: "deepObject"
18986
+ }
18987
+ ];
18988
+ var schemas = {
18989
+ JsonRpcRequest: {
18990
+ description: "A JSON-RPC 2.0 request, as the Model Context Protocol sends.",
18991
+ properties: {
18992
+ id: { description: "Request id (absent on notifications)." },
18993
+ jsonrpc: { const: "2.0", type: "string" },
18994
+ method: string("The MCP method, e.g. `initialize` or `tools/call`."),
18995
+ params: { description: "Method parameters.", type: "object" }
18996
+ },
18997
+ required: ["jsonrpc", "method"],
18998
+ type: "object"
18999
+ },
19000
+ JsonRpcResponse: {
19001
+ description: "A JSON-RPC 2.0 response: a `result` or an `error`.",
19002
+ properties: {
19003
+ error: {
19004
+ properties: {
19005
+ code: { type: "integer" },
19006
+ data: {},
19007
+ message: { type: "string" }
19008
+ },
19009
+ required: ["code", "message"],
19010
+ type: "object"
19011
+ },
19012
+ id: {},
19013
+ jsonrpc: { const: "2.0", type: "string" },
19014
+ result: { type: "object" }
19015
+ },
19016
+ required: ["jsonrpc"],
19017
+ type: "object"
19018
+ },
19019
+ NavLink: {
19020
+ properties: {
19021
+ href: string("Link target — an internal route or an external URL."),
19022
+ icon: string("Optional icon name."),
19023
+ label: { type: "string" }
19024
+ },
19025
+ required: ["href", "label"],
19026
+ type: "object"
19027
+ },
19028
+ NavNode: {
19029
+ description: "A sidebar entry: a page (linking to its route) or a group holding further nodes.",
19030
+ oneOf: [
19031
+ {
19032
+ properties: {
19033
+ badge: { type: "string" },
19034
+ deprecated: { type: "boolean" },
19035
+ description: { type: "string" },
19036
+ icon: { type: "string" },
19037
+ kind: { const: "page", type: "string" },
19038
+ label: { type: "string" },
19039
+ pageId: string("The page's stable content id."),
19040
+ route: string("The page's route.")
19041
+ },
19042
+ required: ["kind", "label", "pageId", "route"],
19043
+ type: "object"
19044
+ },
19045
+ {
19046
+ properties: {
19047
+ badge: { type: "string" },
19048
+ children: { items: ref("NavNode"), type: "array" },
19049
+ collapsed: { type: "boolean" },
19050
+ icon: { type: "string" },
19051
+ kind: { const: "group", type: "string" },
19052
+ label: { type: "string" },
19053
+ path: string("The group's route prefix (not necessarily a page)."),
19054
+ route: string("The group's index page route, when it has one.")
19055
+ },
19056
+ required: ["children", "kind", "label"],
19057
+ type: "object"
19058
+ }
19059
+ ]
19060
+ },
19061
+ NavSelector: {
19062
+ description: "A top-level partition selector (products, versions, languages).",
19063
+ properties: {
19064
+ items: {
19065
+ items: {
19066
+ properties: {
19067
+ description: { type: "string" },
19068
+ label: { type: "string" },
19069
+ path: { type: "string" },
19070
+ tag: { type: "string" }
19071
+ },
19072
+ required: ["label", "path"],
19073
+ type: "object"
19074
+ },
19075
+ type: "array"
19076
+ },
19077
+ kind: { type: "string" },
19078
+ label: { type: "string" }
19079
+ },
19080
+ required: ["items", "kind", "label"],
19081
+ type: "object"
19082
+ },
19083
+ NavTab: {
19084
+ properties: {
19085
+ href: string("The clickable target when it differs from `path` (the section's first page)."),
19086
+ icon: { type: "string" },
19087
+ label: { type: "string" },
19088
+ path: string("The tab's section prefix.")
19089
+ },
19090
+ required: ["label", "path"],
19091
+ type: "object"
19092
+ },
19093
+ Navigation: {
19094
+ description: "The docs navigation model: header tabs, the sidebar tree, partition selectors, and pinned links.",
19095
+ properties: {
19096
+ actions: { items: ref("NavLink"), type: "array" },
19097
+ cta: { oneOf: [ref("NavLink"), { type: "null" }] },
19098
+ featured: { items: ref("NavLink"), type: "array" },
19099
+ repoUrl: { type: ["string", "null"] },
19100
+ root: string("The tree root's route (`/`, or a locale/version prefix)."),
19101
+ selectors: { items: ref("NavSelector"), type: "array" },
19102
+ sidebar: { items: ref("NavNode"), type: "array" },
19103
+ tabs: { items: ref("NavTab"), type: "array" }
19104
+ },
19105
+ required: ["featured", "selectors", "sidebar", "tabs"],
19106
+ type: "object"
19107
+ },
19108
+ Page: {
19109
+ allOf: [
19110
+ ref("PageSummary"),
19111
+ {
19112
+ properties: {
19113
+ markdown: string("The page as agent Markdown: frontmatter included, components downleveled to plain Markdown.")
19114
+ },
19115
+ required: ["markdown"],
19116
+ type: "object"
19117
+ }
19118
+ ],
19119
+ description: "A page's index entry plus its full Markdown body."
19120
+ },
19121
+ PageSummary: {
19122
+ properties: {
19123
+ contentType: string("The page's content type (frontmatter `type`)."),
19124
+ description: { type: "string" },
19125
+ facets: facetsSchema,
19126
+ json: {
19127
+ description: "This page's JSON representation (the `getPage` operation).",
19128
+ format: "uri-reference",
19129
+ type: "string"
19130
+ },
19131
+ lastModified: {
19132
+ description: "ISO 8601 last-modified date, when known.",
19133
+ type: ["string", "null"]
19134
+ },
19135
+ locale: string("The page's locale code."),
19136
+ markdownUrl: {
19137
+ description: "The page's raw-Markdown mirror (the `getPageMarkdown` operation).",
19138
+ format: "uri-reference",
19139
+ type: "string"
19140
+ },
19141
+ route: string("The page's route (`/guides/install`); the key every other operation takes."),
19142
+ title: { type: "string" },
19143
+ url: {
19144
+ description: "Where the rendered page is served.",
19145
+ format: "uri-reference",
19146
+ type: "string"
19147
+ },
19148
+ version: versionProperty
19149
+ },
19150
+ required: [
19151
+ "contentType",
19152
+ "json",
19153
+ "lastModified",
19154
+ "locale",
19155
+ "markdownUrl",
19156
+ "route",
19157
+ "title",
19158
+ "url"
19159
+ ],
19160
+ type: "object"
19161
+ },
19162
+ PagesIndex: {
19163
+ properties: {
19164
+ count: { type: "integer" },
19165
+ generator: string("The Blume version that built the site."),
19166
+ pages: { items: ref("PageSummary"), type: "array" },
19167
+ site: { type: ["string", "null"] }
19168
+ },
19169
+ required: ["count", "generator", "pages", "site"],
19170
+ type: "object"
19171
+ },
19172
+ Problem: {
19173
+ description: "RFC 9457 problem details, with a stable code and a resolution hint.",
19174
+ properties: {
19175
+ code: string("Stable error code (e.g. `PAGE_NOT_FOUND`)."),
19176
+ detail: string("Human-readable explanation of this occurrence."),
19177
+ instance: string("The request path the problem occurred on."),
19178
+ links: {
19179
+ description: "Recovery links, when there is somewhere useful to go.",
19180
+ items: {
19181
+ properties: { href: { type: "string" }, label: { type: "string" } },
19182
+ required: ["href", "label"],
19183
+ type: "object"
19184
+ },
19185
+ type: "array"
19186
+ },
19187
+ resolution: string("What to do next."),
19188
+ status: { type: "integer" },
19189
+ title: { type: "string" },
19190
+ type: string("Problem type URI; `about:blank` by default.")
19191
+ },
19192
+ required: ["code", "detail", "resolution", "status", "title", "type"],
19193
+ type: "object"
19194
+ },
19195
+ SearchHit: {
19196
+ properties: {
19197
+ contentType: { type: "string" },
19198
+ excerpt: string("The page description, else the start of its content."),
19199
+ facets: facetsSchema,
19200
+ route: string("The page's route; pass it to `getPage`."),
19201
+ title: { type: "string" },
19202
+ url: { format: "uri-reference", type: "string" },
19203
+ version: versionProperty
19204
+ },
19205
+ required: ["excerpt", "route", "title", "url"],
19206
+ type: "object"
19207
+ },
19208
+ SearchResponse: {
19209
+ properties: {
19210
+ count: { type: "integer" },
19211
+ query: { type: "string" },
19212
+ results: { items: ref("SearchHit"), type: "array" }
19213
+ },
19214
+ required: ["count", "query", "results"],
19215
+ type: "object"
19216
+ }
19217
+ };
19218
+ var ROUTE_PARAM = {
19219
+ description: "The page route without its leading slash (`guides/install`), or `index` for the home page. May contain slashes.",
19220
+ in: "path",
19221
+ name: "route",
19222
+ required: true,
19223
+ schema: { type: "string" }
19224
+ };
19225
+ var serverUrl = (input) => {
19226
+ if (input.site) {
19227
+ return input.base ? absoluteUrl(input.site, input.base) : siteRoot(input.site);
19228
+ }
19229
+ return input.base || "/";
19230
+ };
19231
+ var buildApiSpec = (input) => {
19232
+ const pathEntries = [
19233
+ [
19234
+ API_PAGES_PATH,
19235
+ {
19236
+ get: {
19237
+ description: "Every documentation page with its route, title, description, content type, locale, facets, and the URLs of its rendered, Markdown, and JSON forms. Unfiltered; on a versioned site every version is listed with its `version`. Use it to enumerate the docs or to find a page when search is too narrow.",
19238
+ operationId: "listPages",
19239
+ responses: {
19240
+ "200": jsonResponse("The page index.", "PagesIndex"),
19241
+ default: problemResponse("An error, as problem details.")
19242
+ },
19243
+ summary: "List every page",
19244
+ tags: ["Pages"]
19245
+ }
19246
+ }
19247
+ ],
19248
+ [
19249
+ API_PAGE_PATH,
19250
+ {
19251
+ get: {
19252
+ description: "A single page as JSON: its index entry plus the page's agent Markdown (frontmatter included, components downleveled to plain Markdown). Take `route` from `listPages` or `searchDocs`.",
19253
+ operationId: "getPage",
19254
+ parameters: [ROUTE_PARAM],
19255
+ responses: {
19256
+ "200": jsonResponse("The page.", "Page"),
19257
+ "404": problemResponse("No page has that route."),
19258
+ default: problemResponse("An error, as problem details.")
19259
+ },
19260
+ summary: "Get a page as JSON",
19261
+ tags: ["Pages"]
19262
+ }
19263
+ }
19264
+ ],
19265
+ [
19266
+ API_NAVIGATION_PATH,
19267
+ {
19268
+ get: {
19269
+ description: "The navigation tree (header tabs and the sidebar hierarchy) as readers see it, for the default locale and the current docs.",
19270
+ operationId: "getNavigation",
19271
+ responses: {
19272
+ "200": jsonResponse("The navigation tree.", "Navigation"),
19273
+ default: problemResponse("An error, as problem details.")
19274
+ },
19275
+ summary: "Get the navigation tree",
19276
+ tags: ["Navigation"]
19277
+ }
19278
+ }
19279
+ ]
19280
+ ];
19281
+ if (input.search) {
19282
+ pathEntries.push([
19283
+ API_SEARCH_PATH,
19284
+ {
19285
+ get: {
19286
+ description: "Full-text search across the documentation. Returns matching pages with their title, route, content type, and a short excerpt; narrow by content type, locale, version, or facet. Use it first to discover relevant pages, then `getPage` to read one in full.",
19287
+ operationId: "searchDocs",
19288
+ parameters: searchParameters,
19289
+ responses: {
19290
+ "200": jsonResponse("The matching pages, best first.", "SearchResponse"),
19291
+ "400": problemResponse("The query was missing or blank."),
19292
+ default: problemResponse("An error, as problem details.")
19293
+ },
19294
+ summary: "Search the docs",
19295
+ tags: ["Search"]
19296
+ }
19297
+ }
19298
+ ]);
19299
+ }
19300
+ pathEntries.push([
19301
+ "/{route}.md",
19302
+ {
19303
+ get: {
19304
+ description: "A page's raw-Markdown mirror: append `.md` to any page URL. Components are downleveled to plain Markdown; `.mdx` serves the source as written. The same body the `getPage` operation carries in its `markdown` field.",
19305
+ operationId: "getPageMarkdown",
19306
+ parameters: [ROUTE_PARAM],
19307
+ responses: {
19308
+ "200": textResponse("The page as Markdown.", MARKDOWN_TYPE),
19309
+ "404": textResponse("No page has that route; the body lists where to look next.", MARKDOWN_TYPE)
19310
+ },
19311
+ summary: "Get a page as Markdown",
19312
+ tags: ["Markdown"]
19313
+ }
19314
+ }
19315
+ ]);
19316
+ if (input.llmsTxt) {
19317
+ pathEntries.push([
19318
+ "/llms.txt",
19319
+ {
19320
+ get: {
19321
+ description: "The llms.txt index: the site's summary, when to use it, and every page with a one-line description, grouped by section.",
19322
+ operationId: "getLlmsTxt",
19323
+ responses: {
19324
+ "200": textResponse("The index.", TEXT_TYPE)
19325
+ },
19326
+ summary: "Get llms.txt",
19327
+ tags: ["Markdown"]
19328
+ }
19329
+ }
19330
+ ], [
19331
+ "/llms-full.txt",
19332
+ {
19333
+ get: {
19334
+ description: "The full Markdown of every current-docs page in one file.",
19335
+ operationId: "getLlmsFullTxt",
19336
+ responses: {
19337
+ "200": textResponse("Every page's Markdown.", TEXT_TYPE)
19338
+ },
19339
+ summary: "Get llms-full.txt",
19340
+ tags: ["Markdown"]
19341
+ }
19342
+ }
19343
+ ]);
19344
+ }
19345
+ if (input.agentReadability) {
19346
+ pathEntries.push([
19347
+ "/agent-readability.json",
19348
+ {
19349
+ get: {
19350
+ description: "A manifest indexing every agent-facing artifact the site publishes — this API, the Markdown mirrors, llms.txt, the MCP server, feeds, and the sitemap.",
19351
+ operationId: "getAgentReadability",
19352
+ responses: {
19353
+ "200": {
19354
+ content: { [JSON_TYPE]: { schema: { type: "object" } } },
19355
+ description: "The manifest."
19356
+ }
19357
+ },
19358
+ summary: "Get the agent-readability manifest",
19359
+ tags: ["Discovery"]
19360
+ }
19361
+ }
19362
+ ]);
19363
+ }
19364
+ if (input.mcpRoute) {
19365
+ pathEntries.push([
19366
+ input.mcpRoute,
19367
+ {
19368
+ post: {
19369
+ description: "The Model Context Protocol server (Streamable HTTP, stateless, JSON responses). Tools: `search_docs`, `get_page`, `list_pages`, `get_navigation` — the same operations this API exposes — plus every page as a `text/markdown` resource. Discovery document at `/.well-known/mcp.json`.",
19370
+ operationId: "mcp",
19371
+ requestBody: {
19372
+ content: { [JSON_TYPE]: { schema: ref("JsonRpcRequest") } },
19373
+ required: true
19374
+ },
19375
+ responses: {
19376
+ "200": jsonResponse("The JSON-RPC response.", "JsonRpcResponse")
19377
+ },
19378
+ summary: "Call the MCP server",
19379
+ tags: ["MCP"]
19380
+ }
19381
+ }
19382
+ ]);
19383
+ }
19384
+ const tags = [
19385
+ { description: "Enumerate and read documentation pages.", name: "Pages" },
19386
+ ...input.search ? [{ description: "Full-text search over the docs.", name: "Search" }] : [],
19387
+ { description: "How the docs are organized.", name: "Navigation" },
19388
+ { description: "Plain-text and Markdown surfaces.", name: "Markdown" },
19389
+ ...input.agentReadability ? [{ description: "Agent discovery documents.", name: "Discovery" }] : [],
19390
+ ...input.mcpRoute ? [{ description: "The Model Context Protocol endpoint.", name: "MCP" }] : []
19391
+ ];
19392
+ const info = {
19393
+ description: [
19394
+ `Read-only JSON API over the ${input.name} documentation${input.description ? `: ${input.description}` : "."}`,
19395
+ "Every operation is public and needs no authentication. Errors are RFC 9457 problem details (`application/problem+json`) with a stable `code`, a `detail`, and a `resolution` hint."
19396
+ ].join(`
19397
+
19398
+ `),
19399
+ title: `${input.name} API`,
19400
+ version: input.version,
19401
+ "x-generator": `blume@${input.version}`
19402
+ };
19403
+ const document = {
19404
+ components: { schemas },
19405
+ info,
19406
+ openapi: "3.1.0",
19407
+ paths: Object.fromEntries(pathEntries),
19408
+ security: [],
19409
+ servers: [{ description: input.name, url: serverUrl(input) }],
19410
+ tags
19411
+ };
19412
+ if (input.site) {
19413
+ document.externalDocs = {
19414
+ description: `${input.name} documentation`,
19415
+ url: absoluteUrl(input.site, withBasePath(input.base, "/"))
19416
+ };
19417
+ }
19418
+ return document;
19419
+ };
19420
+
18485
19421
  // src/ai/ask-data.ts
18486
19422
  var buildAskData = async (project) => {
18487
19423
  const documents = await buildSearchDocuments(project, {
@@ -18605,7 +19541,7 @@ var MCP_TOOLS = [
18605
19541
  ];
18606
19542
 
18607
19543
  // src/ai/mcp/discovery.ts
18608
- var serverUrl = (input) => {
19544
+ var serverUrl2 = (input) => {
18609
19545
  const path = withBasePath(input.base, input.route);
18610
19546
  return input.site ? absoluteUrl(input.site, path) : path;
18611
19547
  };
@@ -18614,7 +19550,7 @@ var buildMcpDiscovery = (input) => ({
18614
19550
  {
18615
19551
  name: input.name,
18616
19552
  transport: "streamable-http",
18617
- url: serverUrl(input)
19553
+ url: serverUrl2(input)
18618
19554
  }
18619
19555
  ]
18620
19556
  });
@@ -18635,7 +19571,7 @@ var reverseDnsName = (input) => {
18635
19571
  };
18636
19572
  var HTTP_URL2 = /^https?:\/\//u;
18637
19573
  var buildMcpServerCard = (input) => {
18638
- const url = serverUrl(input);
19574
+ const url = serverUrl2(input);
18639
19575
  const card2 = {
18640
19576
  $schema: SERVER_CARD_SCHEMA,
18641
19577
  capabilities: {
@@ -19285,54 +20221,52 @@ var specConfiguration = async (spec, root) => {
19285
20221
  };
19286
20222
  }
19287
20223
  };
19288
- var acceptScalarReference = (ref, seen, contentRoutes, warnings) => {
19289
- if (ref.renderer !== "scalar") {
20224
+ var acceptScalarReference = (ref2, seen, contentRoutes, warnings) => {
20225
+ if (ref2.renderer !== "scalar") {
19290
20226
  return null;
19291
20227
  }
19292
- if (seen.has(ref.route)) {
19293
- warnings.push(`Two API reference sources resolve to ${ref.route}; keeping the first.`);
20228
+ if (seen.has(ref2.route)) {
20229
+ warnings.push(`Two API reference sources resolve to ${ref2.route}; keeping the first.`);
19294
20230
  return null;
19295
20231
  }
19296
- if (contentRoutes.has(ref.route)) {
19297
- warnings.push(`API reference route ${ref.route} collides with a content page; skipping the reference there.`);
20232
+ if (contentRoutes.has(ref2.route)) {
20233
+ warnings.push(`API reference route ${ref2.route} collides with a content page; skipping the reference there.`);
19298
20234
  return null;
19299
20235
  }
19300
- seen.add(ref.route);
19301
- return ref;
20236
+ seen.add(ref2.route);
20237
+ return ref2;
19302
20238
  };
19303
20239
  var buildReferenceFiles = async (options) => {
19304
20240
  const { config, root, contentRoutes } = options;
19305
20241
  const warnings = [];
19306
20242
  const seen = new Set;
19307
20243
  const accepted = [];
19308
- for (const ref of resolveReferences(config)) {
19309
- const next = acceptScalarReference(ref, seen, contentRoutes, warnings);
20244
+ for (const ref2 of resolveReferences(config)) {
20245
+ const next = acceptScalarReference(ref2, seen, contentRoutes, warnings);
19310
20246
  if (next) {
19311
20247
  accepted.push(next);
19312
20248
  }
19313
20249
  }
19314
- const built = await Promise.all(accepted.map(async (ref) => ({
19315
- ref,
19316
- spec: await specConfiguration(ref.spec, root)
20250
+ const built = await Promise.all(accepted.map(async (ref2) => ({
20251
+ ref: ref2,
20252
+ spec: await specConfiguration(ref2.spec, root)
19317
20253
  })));
19318
20254
  const files = [];
19319
- for (const { ref, spec } of built) {
20255
+ for (const { ref: ref2, spec } of built) {
19320
20256
  if (spec.warning) {
19321
20257
  warnings.push(spec.warning);
19322
20258
  }
19323
- const pagePath = referencePagePath(ref.route);
19324
- const depth = pagePath.split("/").length - 1;
20259
+ const pagePath = referencePagePath(ref2.route);
19325
20260
  files.push({
19326
20261
  content: scalarReferenceTemplate({
19327
20262
  configuration: {
19328
20263
  ...spec.config,
19329
- ...themeConfiguration(config, ref.theme),
19330
- ...ref.scalar
20264
+ ...themeConfiguration(config, ref2.theme),
20265
+ ...ref2.scalar
19331
20266
  },
19332
- dataImport: `${"../".repeat(depth + 1)}generated/data.json`,
19333
- noindex: ref.noindex,
19334
- route: ref.route,
19335
- title: ref.label
20267
+ noindex: ref2.noindex,
20268
+ route: ref2.route,
20269
+ title: ref2.label
19336
20270
  }),
19337
20271
  pagePath
19338
20272
  });
@@ -20150,7 +21084,7 @@ ${options.userTheme}
20150
21084
  var examplesEntryTemplate = (options) => `/* Generated by Blume. Do not edit. */
20151
21085
  @import "tailwindcss";
20152
21086
 
20153
- /* Scan the example files and the project sources they import. */
21087
+ /* Scan the project (and an out-of-root examples directory) for utility classes. */
20154
21088
  ${options.sources.map((source) => `@source "${source}";`).join(`
20155
21089
  `)}
20156
21090
 
@@ -20177,6 +21111,20 @@ ${options.configTokens}
20177
21111
  ${options.userCss}
20178
21112
  `;
20179
21113
 
21114
+ // src/theme/sources.ts
21115
+ import { dirname as dirname13, isAbsolute as isAbsolute11, relative as relative16, resolve as resolve10 } from "pathe";
21116
+ var SOURCE_DIRECTIVE = /@source(?<not>\s+not)?\s+(?<quote>["'])(?<path>[^"']+)\k<quote>/gu;
21117
+ var rebaseSourceDirectives = (css, options) => {
21118
+ const base = dirname13(options.from);
21119
+ return css.replace(SOURCE_DIRECTIVE, (directive, not, quote, path) => {
21120
+ if (isAbsolute11(path)) {
21121
+ return directive;
21122
+ }
21123
+ const rebased = relative16(options.to, resolve10(base, path));
21124
+ return `@source${not ?? ""} ${quote}${rebased}${quote}`;
21125
+ });
21126
+ };
21127
+
20180
21128
  // src/theme/twoslash.ts
20181
21129
  import { readFileSync as readFileSync8 } from "node:fs";
20182
21130
  import { createRequire as createRequire3 } from "node:module";
@@ -20362,7 +21310,7 @@ export const layoutOverrides = { ...(overrides.layout ?? {})${layoutEntries.leng
20362
21310
  // src/astro/examples.ts
20363
21311
  import { readFile as readFile18 } from "node:fs/promises";
20364
21312
  import pMap5 from "p-map";
20365
- import { join as join30, relative as relative16 } from "pathe";
21313
+ import { isAbsolute as isAbsolute12, join as join30, relative as relative17 } from "pathe";
20366
21314
  import { glob as glob6 } from "tinyglobby";
20367
21315
 
20368
21316
  // src/astro/islands.ts
@@ -20454,6 +21402,12 @@ var FRAMEWORK_BY_EXT3 = {
20454
21402
  var EXAMPLE_FILE = /\.(?<ext>astro|jsx|svelte|tsx|vue)$/u;
20455
21403
  var DEFAULT_EXAMPLE_GLOB = "**/*.{astro,jsx,svelte,tsx,vue}";
20456
21404
  var READ_CONCURRENCY3 = 16;
21405
+ var EXAMPLE_SCAN_GLOB = "**/*.{astro,jsx,svelte,ts,tsx,vue}";
21406
+ var exampleScanRoots = (root, examplesDir) => {
21407
+ const path = relative17(root, examplesDir);
21408
+ const outside = path.startsWith("..") || isAbsolute12(path);
21409
+ return outside ? [root, examplesDir] : [root];
21410
+ };
20457
21411
  var GLOB_MAGIC = /[!*?[\]{}]/u;
20458
21412
  var splitGlobBase = (pattern) => {
20459
21413
  const segments = pattern.split("/");
@@ -20484,7 +21438,7 @@ var discoverExamples = async (root, pattern = "examples") => {
20484
21438
  if (!(ext && framework)) {
20485
21439
  return;
20486
21440
  }
20487
- const path = relative16(dir, file).slice(0, -(ext.length + 1));
21441
+ const path = relative17(dir, file).slice(0, -(ext.length + 1));
20488
21442
  const existing = seen.get(path);
20489
21443
  if (existing) {
20490
21444
  warnings.push(`Two examples both resolve to "${path}" ("${existing}" and "${file}"); ignoring the second. Give them distinct paths.`);
@@ -20503,7 +21457,7 @@ var discoverExamples = async (root, pattern = "examples") => {
20503
21457
  for (const [index, file] of files.entries()) {
20504
21458
  collectExample(file, sources[index] ?? "");
20505
21459
  }
20506
- return { examples, warnings };
21460
+ return { dir, examples, warnings };
20507
21461
  };
20508
21462
  var exampleMarkdownLookup = (examples) => Object.fromEntries(examples.map((example) => [
20509
21463
  example.path,
@@ -20548,7 +21502,7 @@ var resolvedAstroHit = (fromDir) => {
20548
21502
  if (pkg) {
20549
21503
  return { modulesDir, pkg };
20550
21504
  }
20551
- const parent = dirname13(dir);
21505
+ const parent = dirname14(dir);
20552
21506
  if (parent === dir) {
20553
21507
  return null;
20554
21508
  }
@@ -20564,7 +21518,7 @@ var sameRealDir = (a, b) => {
20564
21518
  };
20565
21519
  var depsCandidates = (pkgDir) => [
20566
21520
  join31(pkgDir, "node_modules"),
20567
- dirname13(pkgDir)
21521
+ dirname14(pkgDir)
20568
21522
  ];
20569
21523
  var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync17(join31(dir, ...segments))) ?? null;
20570
21524
  var linkDepsJunction = async (link, depsDir) => {
@@ -20579,13 +21533,13 @@ var linkDepsJunction = async (link, depsDir) => {
20579
21533
  return;
20580
21534
  }
20581
21535
  try {
20582
- if (resolve10(dirname13(link), await readlink(link)) === resolve10(depsDir)) {
21536
+ if (resolve11(dirname14(link), await readlink(link)) === resolve11(depsDir)) {
20583
21537
  return;
20584
21538
  }
20585
21539
  } catch {}
20586
21540
  await rm2(link, { force: true });
20587
21541
  }
20588
- await mkdir7(dirname13(link), { recursive: true });
21542
+ await mkdir7(dirname14(link), { recursive: true });
20589
21543
  await symlink(depsDir, link, "junction");
20590
21544
  };
20591
21545
  var readPkgVersion = (pkgJsonPath) => {
@@ -20716,6 +21670,10 @@ var readOptional = async (path) => {
20716
21670
  return "";
20717
21671
  }
20718
21672
  };
21673
+ var readUserCss = async (file, outputDir) => {
21674
+ const css = await readOptional(file);
21675
+ return file ? rebaseSourceDirectives(css, { from: file, to: outputDir }) : css;
21676
+ };
20719
21677
  var detectNeedsReact = async (root) => {
20720
21678
  const matches2 = await glob7(["**/*.{tsx,jsx}"], {
20721
21679
  cwd: root,
@@ -20743,7 +21701,7 @@ var loadIntegrationBridge = async (config, context) => {
20743
21701
  return;
20744
21702
  }
20745
21703
  return {
20746
- configFile: relative17(context.outDir, context.configFile),
21704
+ configFile: relative18(context.outDir, context.configFile),
20747
21705
  sourceHash: hashConfigSource(await readOptional(context.configFile))
20748
21706
  };
20749
21707
  };
@@ -20988,10 +21946,10 @@ var buildRuntimeData = (project) => {
20988
21946
  if (!(editBase && sourcePath)) {
20989
21947
  return null;
20990
21948
  }
20991
- const rel = relative17(context.root, sourcePath).split("\\").join("/");
21949
+ const rel = relative18(context.root, sourcePath).split("\\").join("/");
20992
21950
  const editDir = trimChar(github?.dir ?? "", "/");
20993
21951
  const editPath = normalize4(editDir ? join31(editDir, rel) : rel);
20994
- if (editPath.startsWith("..") || isAbsolute11(editPath)) {
21952
+ if (editPath.startsWith("..") || isAbsolute13(editPath)) {
20995
21953
  return null;
20996
21954
  }
20997
21955
  return `${editBase}/${editPath}`;
@@ -21040,6 +21998,7 @@ var buildRuntimeData = (project) => {
21040
21998
  description: config.description,
21041
21999
  discovery: {
21042
22000
  agentReadability: config.seo.agentReadability,
22001
+ api: config.ai.api,
21043
22002
  llmsTxt: config.ai.llmsTxt.enabled,
21044
22003
  sitemap: config.seo.sitemap && Boolean(config.deployment.site)
21045
22004
  },
@@ -21109,6 +22068,7 @@ var buildRuntimeData = (project) => {
21109
22068
  routes: manifest.routes.map((route) => ({
21110
22069
  alternates: route.alternates,
21111
22070
  collection: route.collection,
22071
+ description: route.description ?? null,
21112
22072
  draft: route.draft,
21113
22073
  editUrl: route.editUrl ?? editUrlFor(route.sourcePath),
21114
22074
  entryId: route.entryId,
@@ -21167,11 +22127,18 @@ var planMcp = (project, srcDir, userPages) => {
21167
22127
  enabled: true
21168
22128
  };
21169
22129
  };
21170
- var writeMcpFiles = async (project, plan, write) => {
21171
- if (!plan.enabled) {
21172
- return;
22130
+ var publishAgentData = async (project, plans, modules) => {
22131
+ if (!(plans.mcp.enabled || plans.api.enabled)) {
22132
+ return null;
21173
22133
  }
21174
22134
  const data = await buildMcpData(project);
22135
+ modules.set("blume:mcp-data", JSON.stringify(data));
22136
+ return data;
22137
+ };
22138
+ var writeMcpFiles = async (plan, write, data) => {
22139
+ if (!(plan.enabled && data)) {
22140
+ return;
22141
+ }
21175
22142
  const discoveryInput = {
21176
22143
  base: data.base,
21177
22144
  name: data.name,
@@ -21180,13 +22147,57 @@ var writeMcpFiles = async (project, plan, write) => {
21180
22147
  version: data.version
21181
22148
  };
21182
22149
  await Promise.all([
21183
- write(join31(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
21184
- `),
21185
- write(join31(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate(plan.route)),
22150
+ write(join31(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate()),
21186
22151
  write(join31(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
21187
22152
  write(join31(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
21188
22153
  ]);
21189
22154
  };
22155
+ var ownsApiRest = (page) => page.pattern.startsWith("/api/[");
22156
+ var contentUnderApi = (page) => page.route === "/api" || page.route.startsWith("/api/");
22157
+ var planApi = (project, srcDir, userPages) => {
22158
+ const { config, context } = project;
22159
+ const server = config.deployment.output === "server";
22160
+ return {
22161
+ catchAll: server && !userPages.some(ownsApiRest) && !project.graph.pages.some(contentUnderApi),
22162
+ enabled: config.ai.api,
22163
+ server,
22164
+ spec: !routeIsTaken(userPages, project.graph.pages, OPENAPI_PATH) && !existsSync17(join31(context.root, "public", "openapi.json")),
22165
+ srcDir
22166
+ };
22167
+ };
22168
+ var writeApiFiles = async (project, plan, write, data, mcp) => {
22169
+ if (!(plan.enabled && data)) {
22170
+ return;
22171
+ }
22172
+ const { config } = project;
22173
+ const mcpRoute = mcp.enabled ? mcp.route : null;
22174
+ const apiDir = join31(plan.srcDir, "pages", "api");
22175
+ const writes = [
22176
+ write(join31(apiDir, "docs", "pages.json.ts"), apiPagesIndexTemplate()),
22177
+ write(join31(apiDir, "docs", "pages", "[...route].json.ts"), apiPageTemplate()),
22178
+ write(join31(apiDir, "docs", "navigation.json.ts"), apiNavigationTemplate())
22179
+ ];
22180
+ if (plan.server) {
22181
+ writes.push(write(join31(apiDir, "docs", "search.ts"), apiSearchTemplate()));
22182
+ }
22183
+ if (plan.catchAll) {
22184
+ writes.push(write(join31(apiDir, "[...path].ts"), apiNotFoundTemplate({ base: data.base, site: data.site })));
22185
+ }
22186
+ if (plan.spec) {
22187
+ writes.push(write(join31(plan.srcDir, "pages", "openapi.json.ts"), staticJsonEndpointTemplate(buildApiSpec({
22188
+ agentReadability: config.seo.agentReadability,
22189
+ base: data.base,
22190
+ description: config.description,
22191
+ llmsTxt: config.ai.llmsTxt.enabled,
22192
+ mcpRoute,
22193
+ name: config.title,
22194
+ search: plan.server,
22195
+ site: data.site,
22196
+ version: data.version
22197
+ }))));
22198
+ }
22199
+ await Promise.all(writes);
22200
+ };
21190
22201
  var planPlaygroundProxy = (config, srcDir) => ({
21191
22202
  enabled: needsPlaygroundProxy(config),
21192
22203
  entrypoint: join31(srcDir, "blume-openapi", "api-proxy.ts"),
@@ -21223,15 +22234,14 @@ var proxyAllowlistWarnings = (config, data) => {
21223
22234
  }
21224
22235
  return warnings;
21225
22236
  };
21226
- var writeAskFiles = async (project, srcDir, write) => {
22237
+ var writeAskFiles = async (project, srcDir, write, modules) => {
21227
22238
  const { ask } = project.config.ai;
21228
22239
  if (!(ask?.enabled && !ask.endpoint)) {
21229
22240
  return;
21230
22241
  }
21231
22242
  const grounded = ask.provider !== "inkeep";
21232
22243
  if (grounded) {
21233
- await write(join31(srcDir, "generated", "ask-data.json"), `${JSON.stringify(await buildAskData(project))}
21234
- `);
22244
+ modules.set("blume:ask-data", JSON.stringify(await buildAskData(project)));
21235
22245
  }
21236
22246
  await write(join31(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded, {
21237
22247
  instructions: ask.instructions,
@@ -21242,7 +22252,11 @@ var writeNotFoundPage = async (write, srcDir, pages, contentPages) => {
21242
22252
  if (routeIsTaken(pages, contentPages, "/404")) {
21243
22253
  return;
21244
22254
  }
21245
- await write(join31(srcDir, "pages", "404.astro"), notFoundPageTemplate());
22255
+ await Promise.all([
22256
+ write(join31(srcDir, "pages", "404.astro"), notFoundPageTemplate()),
22257
+ write(join31(srcDir, "pages", "404.md.ts"), notFoundMarkdownTemplate()),
22258
+ write(join31(srcDir, "pages", "404.json.ts"), notFoundJsonTemplate())
22259
+ ]);
21246
22260
  };
21247
22261
  var diagnosticWarning = (diagnostic) => diagnostic.suggestion ? `${diagnostic.message} ${diagnostic.suggestion}` : diagnostic.message;
21248
22262
  var buildComponentSlots = async (componentsFile) => {
@@ -21274,18 +22288,18 @@ var generateRuntime = async (project) => {
21274
22288
  assertFontFilesExist(project);
21275
22289
  const out = context.outDir;
21276
22290
  const srcDir = join31(out, "src");
22291
+ const generatedDir = join31(srcDir, "generated");
21277
22292
  const askPath = join31(srcDir, "generated", "Ask.astro");
21278
- const dataPath = join31(srcDir, "generated", "data.json");
21279
22293
  const themePath = join31(srcDir, "generated", "app.css");
21280
22294
  const searchClientPath = join31(srcDir, "generated", "search-client.ts");
21281
22295
  const examplesPath = join31(srcDir, "generated", "examples.ts");
21282
22296
  const examplesThemePath = join31(srcDir, "generated", "examples.css");
21283
- const openapiPath = join31(srcDir, "generated", "openapi.json");
21284
22297
  const written = new Set;
21285
22298
  const write = (path, content) => {
21286
22299
  written.add(normalize4(path));
21287
22300
  return writeIfChanged(path, content);
21288
22301
  };
22302
+ const modules = new Map;
21289
22303
  const depsLinkWarning = await ensureDepsLink(out);
21290
22304
  const askEnabled = config.ai.ask?.enabled ?? false;
21291
22305
  const exportPdf = config.export.pdf;
@@ -21305,8 +22319,8 @@ var generateRuntime = async (project) => {
21305
22319
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
21306
22320
  detectNeedsReact(context.root),
21307
22321
  detectUsesMath(context.root, staged.values()),
21308
- readOptional(context.themeFile),
21309
- readOptional(examplesCssFile(context.root, config)),
22322
+ readUserCss(context.themeFile, generatedDir),
22323
+ readUserCss(examplesCssFile(context.root, config), generatedDir),
21310
22324
  loadIntegrationBridge(config, context),
21311
22325
  discoverIslands(context.root),
21312
22326
  discoverExamples(context.root, config.examples.source),
@@ -21331,6 +22345,8 @@ var generateRuntime = async (project) => {
21331
22345
  const changelogIndex = hasGeneratedChangelog(project, pages);
21332
22346
  const mcp = planMcp(project, srcDir, pages);
21333
22347
  pages.push(...mcp.discoveryPages);
22348
+ const api = planApi(project, srcDir, pages);
22349
+ const agentData = await publishAgentData(project, { api, mcp }, modules);
21334
22350
  const openApiSource2 = project.sources.find(isOpenApiSource);
21335
22351
  const openApiData = openApiSource2 ? openApiSource2.openApiData() : {};
21336
22352
  const playgroundProxy = planPlaygroundProxy(config, srcDir);
@@ -21353,14 +22369,12 @@ var generateRuntime = async (project) => {
21353
22369
  contentRoot: docsCollection.base,
21354
22370
  contentRoutes: markdownRoutePaths(project),
21355
22371
  context,
21356
- dataPath,
21357
22372
  examplesPath,
21358
22373
  examplesThemePath,
21359
22374
  integrationBridge,
21360
22375
  needsReact,
21361
22376
  needsSvelte,
21362
22377
  needsVue,
21363
- openapiPath,
21364
22378
  pages,
21365
22379
  reactCompilerPath,
21366
22380
  searchClientPath,
@@ -21388,7 +22402,7 @@ var generateRuntime = async (project) => {
21388
22402
  write(join31(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples, config.basePath)),
21389
22403
  write(examplesThemePath, examplesEntryTemplate({
21390
22404
  configTokens: buildThemeCss(config.theme),
21391
- sources: [`${context.root}/**/*.{astro,jsx,svelte,ts,tsx,vue}`],
22405
+ sources: exampleScanRoots(context.root, exampleDiscovery.dir).map((dir) => `${dir}/${EXAMPLE_SCAN_GLOB}`),
21392
22406
  userCss: userExamplesCss
21393
22407
  })),
21394
22408
  write(themePath, tailwindEntryTemplate({
@@ -21404,12 +22418,16 @@ var generateRuntime = async (project) => {
21404
22418
  Promise.all(islandDiscovery.islands.map((island) => write(join31(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island)))),
21405
22419
  Promise.all(slotPlan.wrappers.map((wrapper) => write(join31(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content))),
21406
22420
  Promise.all(exampleDiscovery.examples.map((example) => write(join31(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example)))),
21407
- writeAskFiles(project, srcDir, write),
21408
- writeMcpFiles(project, mcp, write),
22421
+ writeAskFiles(project, srcDir, write, modules),
22422
+ writeMcpFiles(mcp, write, agentData),
22423
+ writeApiFiles(project, api, write, agentData, mcp),
21409
22424
  playgroundProxy.enabled ? write(playgroundProxy.entrypoint, playgroundProxyTemplate(proxyOrigins)) : Promise.resolve(false)
21410
22425
  ]);
21411
22426
  if (config.seo.og.enabled) {
21412
- await write(join31(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes, projectOgFonts(project), changelogIndex));
22427
+ await write(join31(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes, {
22428
+ ...projectOgFonts(project),
22429
+ pageDescriptions: config.seo.og.description !== false
22430
+ }, changelogIndex));
21413
22431
  }
21414
22432
  if (changelogIndex) {
21415
22433
  await write(join31(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
@@ -21432,8 +22450,7 @@ var generateRuntime = async (project) => {
21432
22450
  ]);
21433
22451
  if (servesStaticIndex(config.search.provider)) {
21434
22452
  const documents = await buildSearchDocuments(project);
21435
- await write(join31(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
21436
- `);
22453
+ modules.set("blume:search-index", JSON.stringify(documents));
21437
22454
  await write(join31(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
21438
22455
  }
21439
22456
  if (config.search.provider === "mixedbread") {
@@ -21442,24 +22459,19 @@ var generateRuntime = async (project) => {
21442
22459
  await write(join31(srcDir, "generated", "includes.json"), `${JSON.stringify(buildIncludeGraph(project.graph.pages))}
21443
22460
  `);
21444
22461
  const rawMarkdown = await buildRawMarkdown(project);
22462
+ modules.set("blume:raw-markdown", JSON.stringify(rawMarkdown));
21445
22463
  const contentAssets = await collectContentAssets(project);
22464
+ modules.set("blume:content-assets", JSON.stringify(contentAssets));
21446
22465
  await Promise.all([
21447
- write(join31(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
21448
- `),
21449
22466
  write(join31(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate("md")),
21450
22467
  write(join31(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate("mdx")),
21451
- write(join31(srcDir, "generated", "content-assets.json"), `${JSON.stringify(contentAssets)}
21452
- `),
21453
22468
  write(join31(srcDir, "pages", "blume-assets", "[...asset].ts"), contentAssetsEndpointTemplate(join31(project.context.outDir, "public", "blume-assets")))
21454
22469
  ]);
21455
22470
  const feeds = buildRssFeeds(project);
21456
22471
  if (feeds.length > 0) {
21457
22472
  const feedXml = Object.fromEntries(feeds.map((feed) => [feed.type, renderRssFeed(feed)]));
21458
- await Promise.all([
21459
- write(join31(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
21460
- `),
21461
- write(join31(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
21462
- ]);
22473
+ modules.set("blume:rss", JSON.stringify(feedXml));
22474
+ await write(join31(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate());
21463
22475
  }
21464
22476
  const warnings = [
21465
22477
  ...depsLinkWarning ? [depsLinkWarning] : [],
@@ -21497,29 +22509,29 @@ var generateRuntime = async (project) => {
21497
22509
  warnings.push(...references.warnings);
21498
22510
  await Promise.all(references.files.map((file) => write(join31(srcDir, "pages", file.pagePath), file.content)));
21499
22511
  }
22512
+ modules.set("blume:data", buildRuntimeData(project));
22513
+ modules.set("blume:openapi", JSON.stringify(openApiData));
21500
22514
  await Promise.all([
21501
- write(join31(srcDir, "generated", "data.json"), buildRuntimeData(project)),
21502
- write(openapiPath, `${JSON.stringify(openApiData)}
21503
- `),
21504
22515
  write(join31(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
21505
22516
  `),
21506
22517
  writeStagedContent(out, staged)
21507
22518
  ]);
21508
22519
  await pruneOrphans(srcDir, written);
22520
+ publishRuntimeModules(modules);
21509
22521
  return { structuralChange: structural.some(Boolean), warnings };
21510
22522
  };
21511
22523
 
21512
22524
  // src/cli/env.ts
21513
22525
  import { existsSync as existsSync18 } from "node:fs";
21514
22526
  import { config } from "dotenv";
21515
- import { dirname as dirname14, join as join32, resolve as resolve11 } from "pathe";
22527
+ import { dirname as dirname15, join as join32, resolve as resolve12 } from "pathe";
21516
22528
  var loadEnvFiles = (startDir) => {
21517
22529
  const paths = [];
21518
- let dir = resolve11(startDir);
22530
+ let dir = resolve12(startDir);
21519
22531
  let done = false;
21520
22532
  while (!done) {
21521
22533
  paths.push(join32(dir, ".env.local"), join32(dir, ".env"));
21522
- const parent = dirname14(dir);
22534
+ const parent = dirname15(dir);
21523
22535
  done = existsSync18(join32(dir, ".git")) || parent === dir;
21524
22536
  dir = parent;
21525
22537
  }
@@ -21652,7 +22664,7 @@ var collectConfiguredSkills = async (project, distDir) => {
21652
22664
  if (!configured) {
21653
22665
  return [];
21654
22666
  }
21655
- const dir = resolve12(project.context.root, configured);
22667
+ const dir = resolve13(project.context.root, configured);
21656
22668
  if (!existsSync19(dir)) {
21657
22669
  logger.warn(`ai.skills points at "${configured}" (${dir}), which does not exist; no skills published.`);
21658
22670
  return [];
@@ -21676,7 +22688,7 @@ var emitAgentSkills = async (project, distDir, skills) => {
21676
22688
  const outDir = join33(distDir, AGENT_SKILLS_DIR.slice(1));
21677
22689
  await Promise.all(skills.map(async (skill) => {
21678
22690
  const target = join33(outDir, skill.path);
21679
- await mkdir8(dirname15(target), { recursive: true });
22691
+ await mkdir8(dirname16(target), { recursive: true });
21680
22692
  await writeFile7(target, skill.content);
21681
22693
  }));
21682
22694
  await writeFile7(join33(outDir, "index.json"), buildSkillsIndex(skills, project.config), "utf-8");
@@ -21720,7 +22732,11 @@ var emitVercelNegotiation = async (project, routePaths, root) => {
21720
22732
  }
21721
22733
  const rawMarkdown = await buildRawMarkdown(project);
21722
22734
  const home = rawMarkdown["/"];
21723
- const injected = injectNegotiationRoutes(await readFile20(configPath, "utf-8"), routePaths, buildHomeLinkHeader(config2, routePaths), overrides, home ? markdownTokenCount(agentMarkdown(home)) : undefined);
22735
+ const staticDir = join33(root, ".vercel", "output", "static");
22736
+ const injected = injectNegotiationRoutes(await readFile20(configPath, "utf-8"), routePaths, buildHomeLinkHeader(config2, routePaths), overrides, home ? markdownTokenCount(agentMarkdown(home)) : undefined, {
22737
+ json: existsSync19(join33(staticDir, "404.json")),
22738
+ markdown: existsSync19(join33(staticDir, "404.md"))
22739
+ });
21724
22740
  if (injected === null) {
21725
22741
  logger.warn("Could not wire Accept: text/markdown negotiation into .vercel/output/config.json — raw Markdown stays available at the .md URLs.");
21726
22742
  return;
@@ -22264,7 +23280,9 @@ var devCommand = defineCommand5({
22264
23280
  }).on("all", regenerate);
22265
23281
  const disposers = [
22266
23282
  ...project.sources.map((source) => source.watch?.(regenerate)),
22267
- () => void projectWatcher.close()
23283
+ () => {
23284
+ projectWatcher.close();
23285
+ }
22268
23286
  ].filter((dispose) => dispose !== undefined);
22269
23287
  const shutdown = async () => {
22270
23288
  for (const dispose of disposers) {
@@ -22368,12 +23386,12 @@ var doctorCommand = defineCommand6({
22368
23386
 
22369
23387
  // src/cli/commands/eject.ts
22370
23388
  import { defineCommand as defineCommand7 } from "citty";
22371
- import { relative as relative20 } from "pathe";
23389
+ import { relative as relative21 } from "pathe";
22372
23390
 
22373
23391
  // src/registry/eject.ts
22374
23392
  import { existsSync as existsSync21 } from "node:fs";
22375
23393
  import { cp as cp2, mkdir as mkdir9, readFile as readFile21, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
22376
- import { join as join36, relative as relative18 } from "pathe";
23394
+ import { join as join36, relative as relative19 } from "pathe";
22377
23395
  var toPosix = (path) => path.split("\\").join("/");
22378
23396
  var LOCAL_BLUME_SOURCE = "../../node_modules/blume/src/**/*.{astro,ts,tsx}";
22379
23397
  var blumeSourceGlob = (root, genDir, resolveBlumeRoot = packageRoot) => {
@@ -22382,7 +23400,7 @@ var blumeSourceGlob = (root, genDir, resolveBlumeRoot = packageRoot) => {
22382
23400
  }
22383
23401
  try {
22384
23402
  const src = join36(resolveBlumeRoot(), "src");
22385
- return `${toPosix(relative18(genDir, src))}/**/*.{astro,ts,tsx}`;
23403
+ return `${toPosix(relative19(genDir, src))}/**/*.{astro,ts,tsx}`;
22386
23404
  } catch {
22387
23405
  console.warn('blume: could not locate the installed blume package; src/generated/app.css keeps its default `@source "../../node_modules/blume/..."` glob. If blume is hoisted elsewhere, point that glob at its install location or Blume\'s utility classes will be missing.');
22388
23406
  return LOCAL_BLUME_SOURCE;
@@ -22455,7 +23473,7 @@ var mcpFiles = async (project, userPages, srcDir, genDir) => {
22455
23473
  path: join36(genDir, "mcp-data.json")
22456
23474
  },
22457
23475
  {
22458
- content: mcpEndpointTemplate(route),
23476
+ content: mcpEndpointTemplate(),
22459
23477
  path: join36(srcDir, "pages", mcpPageFile(route))
22460
23478
  },
22461
23479
  {
@@ -22481,14 +23499,20 @@ var changelogFiles = (project, userPages, srcDir, options) => {
22481
23499
  }
22482
23500
  ];
22483
23501
  };
22484
- var readExamplesCss = (root, css) => css && existsSync21(join36(root, css)) ? readFile21(join36(root, css), "utf-8") : Promise.resolve("");
23502
+ var readUserCss2 = async (file, genDir) => {
23503
+ if (!(file && existsSync21(file))) {
23504
+ return "";
23505
+ }
23506
+ const css = await readFile21(file, "utf-8");
23507
+ return rebaseSourceDirectives(css, { from: file, to: genDir });
23508
+ };
22485
23509
  var examplesPreviewFiles = (srcDir, basePath, hasExamples) => hasExamples ? [
22486
23510
  {
22487
23511
  content: examplesPageTemplate(),
22488
23512
  path: join36(srcDir, "pages", ...basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro")
22489
23513
  }
22490
23514
  ] : [];
22491
- var ejectIntegrationBridge = (config2, root, configFile) => config2.integrations.length > 0 && configFile ? { configFile: toPosix(relative18(root, configFile)) } : undefined;
23515
+ var ejectIntegrationBridge = (config2, root, configFile) => config2.integrations.length > 0 && configFile ? { configFile: toPosix(relative19(root, configFile)) } : undefined;
22492
23516
  var eject = async (root) => {
22493
23517
  const project = await scanProject(root, { mode: "build" });
22494
23518
  const { context, config: config2 } = project;
@@ -22511,8 +23535,8 @@ var eject = async (root) => {
22511
23535
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
22512
23536
  detectNeedsReact(root),
22513
23537
  detectUsesMath(root),
22514
- context.themeFile ? readFile21(context.themeFile, "utf-8") : Promise.resolve(""),
22515
- readExamplesCss(root, config2.examples.css),
23538
+ readUserCss2(context.themeFile, genDir),
23539
+ readUserCss2(config2.examples.css ? join36(root, config2.examples.css) : null, genDir),
22516
23540
  buildRawMarkdown(project),
22517
23541
  discoverIslands(root)
22518
23542
  ]);
@@ -22525,14 +23549,14 @@ var eject = async (root) => {
22525
23549
  const needsSvelte = frameworks.has("svelte");
22526
23550
  const relContext = {
22527
23551
  ...context,
22528
- contentRoot: toPosix(relative18(root, context.contentRoot)),
23552
+ contentRoot: toPosix(relative19(root, context.contentRoot)),
22529
23553
  outDir: ".",
22530
23554
  root: "."
22531
23555
  };
22532
- const componentsImport = context.componentsFile ? `../../${toPosix(relative18(root, context.componentsFile))}` : null;
23556
+ const componentsImport = context.componentsFile ? `../../${toPosix(relative19(root, context.componentsFile))}` : null;
22533
23557
  const relPages = [
22534
23558
  ...pages.map((page) => ({
22535
- entrypoint: toPosix(relative18(root, page.entrypoint)),
23559
+ entrypoint: toPosix(relative19(root, page.entrypoint)),
22536
23560
  pattern: page.pattern
22537
23561
  })),
22538
23562
  ...mcpDiscoveryPages(project, pages)
@@ -22548,14 +23572,13 @@ var eject = async (root) => {
22548
23572
  contentRoot: relContext.contentRoot,
22549
23573
  contentRoutes: project.manifest.routes.map((route) => route.path),
22550
23574
  context: relContext,
22551
- dataPath: "./src/generated/data.json",
22552
23575
  examplesPath: "./src/generated/examples.ts",
22553
23576
  examplesThemePath: "./src/generated/examples.css",
23577
+ generatedModulesDir: "./src/generated",
22554
23578
  integrationBridge: ejectIntegrationBridge(config2, root, context.configFile),
22555
23579
  needsReact,
22556
23580
  needsSvelte,
22557
23581
  needsVue,
22558
- openapiPath: "./src/generated/openapi.json",
22559
23582
  pages: relPages,
22560
23583
  searchClientPath: "./src/generated/search-client.ts",
22561
23584
  themePath: "./src/generated/app.css"
@@ -22601,7 +23624,7 @@ var eject = async (root) => {
22601
23624
  {
22602
23625
  content: examplesEntryTemplate({
22603
23626
  configTokens: buildThemeCss(config2.theme),
22604
- sources: ["../../**/*.{astro,jsx,svelte,ts,tsx,vue}"],
23627
+ sources: exampleScanRoots(root, examples.dir).map((dir) => `${relative19(genDir, dir)}/${EXAMPLE_SCAN_GLOB}`),
22605
23628
  userCss: userExamplesCss
22606
23629
  }),
22607
23630
  path: join36(genDir, "examples.css")
@@ -22652,7 +23675,7 @@ var eject = async (root) => {
22652
23675
  }
22653
23676
  if (config2.seo.og.enabled) {
22654
23677
  files.push({
22655
- content: ogEndpointTemplate(customOgRoutes(pages, config2.title, config2.seo.og.titles)),
23678
+ content: ogEndpointTemplate(customOgRoutes(pages, config2.title, config2.seo.og.titles), { pageDescriptions: config2.seo.og.description !== false }),
22656
23679
  path: join36(srcDir, "pages", "og", "[...slug].png.ts")
22657
23680
  });
22658
23681
  }
@@ -22792,7 +23815,7 @@ var updatePackageScripts = async (root) => {
22792
23815
  import { existsSync as existsSync22 } from "node:fs";
22793
23816
  import { mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
22794
23817
  import { detect } from "package-manager-detector/detect";
22795
- import { basename as basename6, dirname as dirname16, isAbsolute as isAbsolute12, join as join38, relative as relative19 } from "pathe";
23818
+ import { basename as basename6, dirname as dirname17, isAbsolute as isAbsolute14, join as join38, relative as relative20 } from "pathe";
22796
23819
 
22797
23820
  // src/core/package-json.ts
22798
23821
  var toPackageName = (raw) => raw.toLowerCase().replaceAll(/[^a-z0-9._-]+/gu, "-").replaceAll(/^[-_.]+|[-_.]+$/gu, "") || "docs";
@@ -22927,7 +23950,7 @@ var detectProjectPackageManager = async (root) => {
22927
23950
  const name = detected?.name;
22928
23951
  return name !== undefined && isPackageManager(name) ? name : detectPackageManager(process.env.npm_config_user_agent);
22929
23952
  };
22930
- var validateContentDir = (root, dir) => isAbsolute12(dir) || relative19(root, join38(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
23953
+ var validateContentDir = (root, dir) => isAbsolute14(dir) || relative20(root, join38(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
22931
23954
  var titleize = (raw) => {
22932
23955
  const words = raw.replaceAll(/[-_.]+/gu, " ").split(/\s+/u).filter(Boolean);
22933
23956
  return words.length === 0 ? "My Docs" : words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
@@ -23031,7 +24054,7 @@ var writeFileSafe = async (file, log) => {
23031
24054
  log.info(`Skipped existing ${file.path}`);
23032
24055
  return false;
23033
24056
  }
23034
- await mkdir10(dirname16(file.path), { recursive: true });
24057
+ await mkdir10(dirname17(file.path), { recursive: true });
23035
24058
  await writeFile10(file.path, file.content, "utf-8");
23036
24059
  log.success(`Created ${file.path}`);
23037
24060
  return true;
@@ -23107,7 +24130,7 @@ var ejectCommand = defineCommand7({
23107
24130
  }
23108
24131
  logger.success(`Ejected ${files.length} file(s):`);
23109
24132
  for (const file of files) {
23110
- process.stdout.write(` ${relative20(root, file)}
24133
+ process.stdout.write(` ${relative21(root, file)}
23111
24134
  `);
23112
24135
  }
23113
24136
  reportDroppedArtifacts(notices);
@@ -23195,7 +24218,7 @@ When you are done, print the file and suggest running \`blume eval\` to try it.`
23195
24218
  import { mkdtemp as mkdtemp2, writeFile as writeFile11 } from "node:fs/promises";
23196
24219
  import { tmpdir as tmpdir2 } from "node:os";
23197
24220
  import { colors as colors4 } from "consola/utils";
23198
- import { join as join39, relative as relative21 } from "pathe";
24221
+ import { join as join39, relative as relative22 } from "pathe";
23199
24222
 
23200
24223
  // src/cli/report-format.ts
23201
24224
  var seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
@@ -23271,15 +24294,15 @@ var summaryLine2 = (result) => {
23271
24294
  var headerLine = (total, agent) => `${colors4.bold("blume eval")} ${total} question(s) · ${AGENTS[agent].name}`;
23272
24295
  var startLine = (id2, index, total) => ` ${colors4.dim(`▸ ${id2} (${index + 1}/${total})`)}`;
23273
24296
  var fixLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code !== "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
23274
- const site = finding2.file ? `${relative21(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
24297
+ const site = finding2.file ? `${relative22(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
23275
24298
  return ` ${colors4.cyan("fix:")} ${site} ${colors4.dim(finding2.message)}`;
23276
24299
  });
23277
24300
  var warningLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code === "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
23278
- const site = finding2.file ? ` ${relative21(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
24301
+ const site = finding2.file ? ` ${relative22(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
23279
24302
  return ` ${colors4.yellow("⚠")}${site} ${colors4.dim(finding2.message)}`;
23280
24303
  });
23281
24304
  var evalReportJson = (result, root, threshold) => {
23282
- const diagnostics = result.diagnostics.map((diagnostic) => diagnostic.file ? { ...diagnostic, file: relative21(root, diagnostic.file) } : diagnostic);
24305
+ const diagnostics = result.diagnostics.map((diagnostic) => diagnostic.file ? { ...diagnostic, file: relative22(root, diagnostic.file) } : diagnostic);
23283
24306
  return `${JSON.stringify({
23284
24307
  diagnostics,
23285
24308
  eval: {
@@ -23331,7 +24354,7 @@ var DISALLOWED_TOOLS = [
23331
24354
  "WebSearch",
23332
24355
  "Task"
23333
24356
  ];
23334
- var runAgentHeadless = (bin, args, options) => new Promise((resolve13, reject) => {
24357
+ var runAgentHeadless = (bin, args, options) => new Promise((resolve14, reject) => {
23335
24358
  const child = spawn2(bin, args, {
23336
24359
  cwd: options.cwd,
23337
24360
  stdio: ["pipe", "pipe", "pipe"]
@@ -23358,12 +24381,12 @@ var runAgentHeadless = (bin, args, options) => new Promise((resolve13, reject) =
23358
24381
  });
23359
24382
  child.once("close", (code) => {
23360
24383
  clearTimeout(deadline);
23361
- resolve13({ code: code ?? 1, stderr, stdout, timedOut });
24384
+ resolve14({ code: code ?? 1, stderr, stdout, timedOut });
23362
24385
  });
23363
24386
  child.once("exit", (code) => {
23364
24387
  if (timedOut) {
23365
24388
  clearTimeout(deadline);
23366
- resolve13({ code: code ?? 1, stderr, stdout, timedOut });
24389
+ resolve14({ code: code ?? 1, stderr, stdout, timedOut });
23367
24390
  }
23368
24391
  });
23369
24392
  child.stdin?.end(options.prompt);
@@ -23982,10 +25005,10 @@ var evalCommand = defineCommand8({
23982
25005
  // src/cli/commands/init.ts
23983
25006
  import * as clack from "@clack/prompts";
23984
25007
  import { defineCommand as defineCommand9 } from "citty";
23985
- import { resolve as resolve14 } from "pathe";
25008
+ import { resolve as resolve15 } from "pathe";
23986
25009
 
23987
25010
  // src/cli/init/questions.ts
23988
- import { basename as basename7, resolve as resolve13 } from "pathe";
25011
+ import { basename as basename7, resolve as resolve14 } from "pathe";
23989
25012
  var cancelled = (value) => typeof value === "symbol";
23990
25013
  var collectAnswers = async (prompter, flags, defaults) => {
23991
25014
  const directory = flags.directory ?? await prompter.text({
@@ -23996,7 +25019,7 @@ var collectAnswers = async (prompter, flags, defaults) => {
23996
25019
  if (cancelled(directory)) {
23997
25020
  return null;
23998
25021
  }
23999
- const root = resolve13(defaults.cwd, directory);
25022
+ const root = resolve14(defaults.cwd, directory);
24000
25023
  const title = await prompter.text({
24001
25024
  initialValue: titleize(basename7(root)),
24002
25025
  message: "What's your docs site called?",
@@ -24166,7 +25189,7 @@ var initCommand = defineCommand9({
24166
25189
  title: "My Docs"
24167
25190
  };
24168
25191
  }
24169
- const root = resolve14(cwd, answers.directory);
25192
+ const root = resolve15(cwd, answers.directory);
24170
25193
  if (validateContentDir(root, answers.contentDir) !== undefined) {
24171
25194
  logger.error(`Invalid --content-dir "${answers.contentDir}" (must be a path inside the project).`);
24172
25195
  process.exit(1);
@@ -24213,6 +25236,9 @@ import {
24213
25236
  McpError,
24214
25237
  ReadResourceRequestSchema
24215
25238
  } from "@modelcontextprotocol/sdk/types.js";
25239
+ import { z as z8 } from "zod";
25240
+
25241
+ // src/ai/mcp/query.ts
24216
25242
  import { z as z7 } from "zod";
24217
25243
 
24218
25244
  // src/search/orama-index.ts
@@ -24356,13 +25382,10 @@ var queryOramaIndex = async (db, term, limit, filters) => {
24356
25382
  return found.hits.map((hit) => hit.document);
24357
25383
  };
24358
25384
 
24359
- // src/ai/mcp/server.ts
25385
+ // src/ai/mcp/query.ts
24360
25386
  var DEFAULT_SEARCH_LIMIT = 8;
24361
25387
  var MAX_SEARCH_LIMIT = 20;
24362
25388
  var EXCERPT_LENGTH = 200;
24363
- var RESOURCE_MIME_TYPE = "text/markdown";
24364
- var RESOURCE_NOT_FOUND = -32002;
24365
- var LOCAL_RESOURCE_SCHEME = "blume:";
24366
25389
  var contentTypesField = z7.preprocess((value) => {
24367
25390
  const list2 = (Array.isArray(value) ? value : [value]).filter((entry) => typeof entry === "string");
24368
25391
  return list2.length > 0 ? list2 : undefined;
@@ -24414,21 +25437,6 @@ var TOOL_INPUTS = {
24414
25437
  version: versionField
24415
25438
  })
24416
25439
  };
24417
- var inputSchemaFor = (schema) => {
24418
- const {
24419
- $schema: _dialect,
24420
- additionalProperties: _closed,
24421
- ...rest
24422
- } = z7.toJSONSchema(schema);
24423
- return rest;
24424
- };
24425
- var TOOL_DEFINITIONS = MCP_TOOLS.map((tool) => ({
24426
- annotations: tool.annotations,
24427
- description: tool.description,
24428
- inputSchema: inputSchemaFor(TOOL_INPUTS[tool.name]),
24429
- name: tool.name,
24430
- title: tool.title
24431
- }));
24432
25440
  var matchesFacets = (facets, filters) => Object.entries(filters).every(([key, value]) => facets?.[key] === value);
24433
25441
  var asVersionScope = (value, data) => {
24434
25442
  if (!data.archivedVersions) {
@@ -24460,8 +25468,6 @@ var urlFor = (route, data) => {
24460
25468
  const path = withBasePath(data.base, route);
24461
25469
  return data.site ? absoluteUrl(data.site, path) : path;
24462
25470
  };
24463
- var resourceUri = (route, data) => data.site ? urlFor(route, data) : `${LOCAL_RESOURCE_SCHEME}${withBasePath(data.base, route)}`;
24464
- var resourceRoute = (uri, data) => normalizeRoute2(uri.startsWith(LOCAL_RESOURCE_SCHEME) ? uri.slice(LOCAL_RESOURCE_SCHEME.length) : uri, data);
24465
25471
  var excerptFor = (doc) => {
24466
25472
  if (doc.description) {
24467
25473
  return doc.description;
@@ -24469,10 +25475,6 @@ var excerptFor = (doc) => {
24469
25475
  const head = doc.content.slice(0, EXCERPT_LENGTH).trim();
24470
25476
  return doc.content.length > EXCERPT_LENGTH ? `${head}…` : head;
24471
25477
  };
24472
- var text = (value, isError = false) => {
24473
- const content = [{ text: value, type: "text" }];
24474
- return isError ? { content, isError: true } : { content };
24475
- };
24476
25478
  var createIndexProvider = (documents, locale) => {
24477
25479
  let dbPromise = null;
24478
25480
  return function provideIndex() {
@@ -24480,6 +25482,90 @@ var createIndexProvider = (documents, locale) => {
24480
25482
  return dbPromise;
24481
25483
  };
24482
25484
  };
25485
+ var searchDocs = async (data, index, input) => {
25486
+ const db = await index();
25487
+ const hits = await queryOramaIndex(db, input.query, input.limit ?? DEFAULT_SEARCH_LIMIT, {
25488
+ contentTypes: input.contentTypes,
25489
+ facets: input.filters,
25490
+ locale: input.locale,
25491
+ version: asVersionScope(input.version, data)
25492
+ });
25493
+ return hits.map((doc) => {
25494
+ const hit = {
25495
+ contentType: doc.contentType,
25496
+ excerpt: excerptFor(doc),
25497
+ facets: doc.facets,
25498
+ route: doc.route,
25499
+ title: doc.title,
25500
+ url: urlFor(doc.route, data)
25501
+ };
25502
+ if (data.archivedVersions) {
25503
+ hit.version = doc.version ?? "";
25504
+ }
25505
+ return hit;
25506
+ });
25507
+ };
25508
+ var getPageMarkdown = (data, route) => data.pages[route];
25509
+ var listPages = (data, input) => {
25510
+ const { contentTypes, filters, locale } = input;
25511
+ const versionScope = asVersionScope(input.version, data);
25512
+ return data.routes.filter((route) => (!contentTypes || contentTypes.includes(route.contentType)) && (!filters || matchesFacets(route.facets, filters)) && (!locale || route.locale === locale) && (versionScope === undefined || route.version === versionScope)).map((route) => {
25513
+ const listing = {
25514
+ contentType: route.contentType,
25515
+ description: route.description,
25516
+ facets: route.facets,
25517
+ lastModified: route.lastModified,
25518
+ route: route.route,
25519
+ title: route.title,
25520
+ url: urlFor(route.route, data)
25521
+ };
25522
+ if (data.archivedVersions) {
25523
+ listing.version = route.version;
25524
+ }
25525
+ return listing;
25526
+ });
25527
+ };
25528
+ var getNavigation = (data, input) => {
25529
+ const { locale, version: versionId } = input;
25530
+ const unknownVersion = unknownVersionError(versionId, data);
25531
+ if (unknownVersion) {
25532
+ return { error: unknownVersion };
25533
+ }
25534
+ let { navigation } = data;
25535
+ const byLocale = versionId ? data.navigationByVersion?.[versionId] : undefined;
25536
+ if (byLocale) {
25537
+ navigation = (locale ? byLocale[locale] : undefined) ?? byLocale[data.defaultLocale ?? ""] ?? Object.values(byLocale)[0] ?? navigation;
25538
+ } else if (locale && data.navigationByLocale?.[locale]) {
25539
+ navigation = data.navigationByLocale[locale];
25540
+ }
25541
+ return { navigation };
25542
+ };
25543
+
25544
+ // src/ai/mcp/server.ts
25545
+ var RESOURCE_MIME_TYPE = "text/markdown";
25546
+ var RESOURCE_NOT_FOUND = -32002;
25547
+ var LOCAL_RESOURCE_SCHEME = "blume:";
25548
+ var inputSchemaFor = (schema) => {
25549
+ const {
25550
+ $schema: _dialect,
25551
+ additionalProperties: _closed,
25552
+ ...rest
25553
+ } = z8.toJSONSchema(schema);
25554
+ return rest;
25555
+ };
25556
+ var TOOL_DEFINITIONS = MCP_TOOLS.map((tool) => ({
25557
+ annotations: tool.annotations,
25558
+ description: tool.description,
25559
+ inputSchema: inputSchemaFor(TOOL_INPUTS[tool.name]),
25560
+ name: tool.name,
25561
+ title: tool.title
25562
+ }));
25563
+ var resourceUri = (route, data) => data.site ? urlFor(route, data) : `${LOCAL_RESOURCE_SCHEME}${withBasePath(data.base, route)}`;
25564
+ var resourceRoute = (uri, data) => normalizeRoute2(uri.startsWith(LOCAL_RESOURCE_SCHEME) ? uri.slice(LOCAL_RESOURCE_SCHEME.length) : uri, data);
25565
+ var text = (value, isError = false) => {
25566
+ const content = [{ text: value, type: "text" }];
25567
+ return isError ? { content, isError: true } : { content };
25568
+ };
24483
25569
  var buildServer = (data, index) => {
24484
25570
  const capabilities = { resources: {}, tools: {} };
24485
25571
  const serverOptions = data.instructions ? { capabilities, instructions: data.instructions } : { capabilities };
@@ -24503,7 +25589,7 @@ var buildServer = (data, index) => {
24503
25589
  }));
24504
25590
  server.setRequestHandler(ReadResourceRequestSchema, (request2) => {
24505
25591
  const { uri } = request2.params;
24506
- const markdown = data.pages[resourceRoute(uri, data)];
25592
+ const markdown = getPageMarkdown(data, resourceRoute(uri, data));
24507
25593
  if (markdown === undefined) {
24508
25594
  throw new McpError(RESOURCE_NOT_FOUND, `No page found at "${uri}". Use resources/list or list_pages to find valid URIs.`);
24509
25595
  }
@@ -24514,74 +25600,28 @@ var buildServer = (data, index) => {
24514
25600
  server.setRequestHandler(CallToolRequestSchema, async (request2) => {
24515
25601
  const { arguments: args = {}, name } = request2.params;
24516
25602
  if (name === "search_docs") {
24517
- const input = TOOL_INPUTS.search_docs.parse(args);
24518
- const db = await index();
24519
- const hits = await queryOramaIndex(db, input.query, input.limit ?? DEFAULT_SEARCH_LIMIT, {
24520
- contentTypes: input.contentTypes,
24521
- facets: input.filters,
24522
- locale: input.locale,
24523
- version: asVersionScope(input.version, data)
24524
- });
24525
- const results = hits.map((doc) => {
24526
- const hit = {
24527
- contentType: doc.contentType,
24528
- excerpt: excerptFor(doc),
24529
- facets: doc.facets,
24530
- route: doc.route,
24531
- title: doc.title,
24532
- url: urlFor(doc.route, data)
24533
- };
24534
- if (data.archivedVersions) {
24535
- hit.version = doc.version ?? "";
24536
- }
24537
- return hit;
24538
- });
25603
+ const results = await searchDocs(data, index, TOOL_INPUTS.search_docs.parse(args));
24539
25604
  return text(JSON.stringify(results, null, 2));
24540
25605
  }
24541
25606
  if (name === "get_page") {
24542
25607
  const input = TOOL_INPUTS.get_page.parse(args);
24543
25608
  const key = normalizeRoute2(input.route, data);
24544
- const markdown = data.pages[key];
25609
+ const markdown = getPageMarkdown(data, key);
24545
25610
  if (markdown === undefined) {
24546
25611
  return text(`No page found at "${key}". Use list_pages or search_docs to find valid routes.`, true);
24547
25612
  }
24548
25613
  return text(markdown);
24549
25614
  }
24550
25615
  if (name === "list_pages") {
24551
- const input = TOOL_INPUTS.list_pages.parse(args);
24552
- const { contentTypes, filters, locale } = input;
24553
- const versionScope = asVersionScope(input.version, data);
24554
- const routes = data.routes.filter((route) => (!contentTypes || contentTypes.includes(route.contentType)) && (!filters || matchesFacets(route.facets, filters)) && (!locale || route.locale === locale) && (versionScope === undefined || route.version === versionScope));
24555
- return text(JSON.stringify(routes.map((route) => {
24556
- const listing = {
24557
- contentType: route.contentType,
24558
- description: route.description,
24559
- facets: route.facets,
24560
- lastModified: route.lastModified,
24561
- route: route.route,
24562
- title: route.title,
24563
- url: urlFor(route.route, data)
24564
- };
24565
- if (data.archivedVersions) {
24566
- listing.version = route.version;
24567
- }
24568
- return listing;
24569
- }), null, 2));
25616
+ const listing = listPages(data, TOOL_INPUTS.list_pages.parse(args));
25617
+ return text(JSON.stringify(listing, null, 2));
24570
25618
  }
24571
25619
  if (name === "get_navigation") {
24572
- const { locale, version: versionId } = TOOL_INPUTS.get_navigation.parse(args);
24573
- const unknownVersion = unknownVersionError(versionId, data);
24574
- if (unknownVersion) {
24575
- return text(unknownVersion, true);
25620
+ const result = getNavigation(data, TOOL_INPUTS.get_navigation.parse(args));
25621
+ if ("error" in result) {
25622
+ return text(result.error, true);
24576
25623
  }
24577
- let { navigation } = data;
24578
- const byLocale = versionId ? data.navigationByVersion?.[versionId] : undefined;
24579
- if (byLocale) {
24580
- navigation = (locale ? byLocale[locale] : undefined) ?? byLocale[data.defaultLocale ?? ""] ?? Object.values(byLocale)[0] ?? navigation;
24581
- } else if (locale && data.navigationByLocale?.[locale]) {
24582
- navigation = data.navigationByLocale[locale];
24583
- }
24584
- return text(JSON.stringify(navigation, null, 2));
25624
+ return text(JSON.stringify(result.navigation, null, 2));
24585
25625
  }
24586
25626
  return text(`Unknown tool: ${name}`, true);
24587
25627
  });
@@ -24733,11 +25773,11 @@ var translateAgentArgs = (kind, lastMessagePath) => kind === "claude" ? claudeAr
24733
25773
  import { createHash as createHash5 } from "node:crypto";
24734
25774
  import { readFile as readFile26 } from "node:fs/promises";
24735
25775
  import { join as join45 } from "pathe";
24736
- import { z as z8 } from "zod";
25776
+ import { z as z9 } from "zod";
24737
25777
  var LEDGER_FILE = "blume.translations.json";
24738
- var ledgerSchema = z8.object({
24739
- files: z8.record(z8.string(), z8.record(z8.string(), z8.string())),
24740
- version: z8.literal(1)
25778
+ var ledgerSchema = z9.object({
25779
+ files: z9.record(z9.string(), z9.record(z9.string(), z9.string())),
25780
+ version: z9.literal(1)
24741
25781
  });
24742
25782
  var emptyLedger = () => ({
24743
25783
  files: {},
@@ -25004,7 +26044,7 @@ import { join as join47 } from "pathe";
25004
26044
 
25005
26045
  // src/translate/meta.ts
25006
26046
  import { readFile as readFile27 } from "node:fs/promises";
25007
- import { dirname as dirname17, join as join46, relative as relative22 } from "pathe";
26047
+ import { dirname as dirname18, join as join46, relative as relative23 } from "pathe";
25008
26048
  import { glob as glob8 } from "tinyglobby";
25009
26049
  var META_FILES2 = ["**/meta.ts", "**/meta.js", "**/meta.mjs"];
25010
26050
  var isFactoryModule = (value) => typeof value === "function";
@@ -25027,7 +26067,7 @@ var discoverTranslatableMeta = async (project) => {
25027
26067
  onlyFiles: true
25028
26068
  });
25029
26069
  for (const file of files.toSorted()) {
25030
- const dir = relative22(contentRoot2, dirname17(file));
26070
+ const dir = relative23(contentRoot2, dirname18(file));
25031
26071
  const first = dir.split("/")[0]?.toLowerCase();
25032
26072
  if (first && localeDirs.has(first)) {
25033
26073
  continue;
@@ -25056,7 +26096,7 @@ var discoverTranslatableMeta = async (project) => {
25056
26096
  dir: dir === "." ? "" : dir,
25057
26097
  file,
25058
26098
  raw,
25059
- sourceRel: relative22(project.context.root, file),
26099
+ sourceRel: relative23(project.context.root, file),
25060
26100
  title: parsed.data.title
25061
26101
  });
25062
26102
  }
@@ -25370,7 +26410,7 @@ var runTranslate = async (options) => {
25370
26410
  // src/translate/work-list.ts
25371
26411
  import { existsSync as existsSync26 } from "node:fs";
25372
26412
  import { readFile as readFile29 } from "node:fs/promises";
25373
- import { dirname as dirname18, extname as extname9, join as join48, relative as relative23 } from "pathe";
26413
+ import { dirname as dirname19, extname as extname9, join as join48, relative as relative24 } from "pathe";
25374
26414
  var PAGE_EXTENSIONS = new Set([".md", ".mdx"]);
25375
26415
  var translatablePages = (project, i18n) => {
25376
26416
  const rootsByName = new Map(project.sources.flatMap((source) => source.staged || !source.contentRoot ? [] : [[source.name, source.contentRoot]]));
@@ -25421,12 +26461,12 @@ var partialWorkItems = async (universe, project, i18n, ledger, targetLocales, fo
25421
26461
  }
25422
26462
  }
25423
26463
  for (const [partialPath, contentRoot2] of partials) {
25424
- const sourceRel = relative23(root, partialPath);
26464
+ const sourceRel = relative24(root, partialPath);
25425
26465
  work.sources.push(sourceRel);
25426
26466
  const hash = hashSource(await readFile29(partialPath, "utf-8"));
25427
26467
  const ext = extname9(partialPath);
25428
26468
  const verbatim = !PAGE_EXTENSIONS.has(ext.toLowerCase());
25429
- const contentRel = relative23(contentRoot2, partialPath);
26469
+ const contentRel = relative24(contentRoot2, partialPath);
25430
26470
  for (const locale of targetLocales) {
25431
26471
  const targetPath = join48(contentRoot2, localeTargetPath(contentRel, ext, locale, i18n));
25432
26472
  const item = (status) => {
@@ -25437,7 +26477,7 @@ var partialWorkItems = async (universe, project, i18n, ledger, targetLocales, fo
25437
26477
  sourceRel,
25438
26478
  status,
25439
26479
  targetPath,
25440
- targetRel: relative23(root, targetPath)
26480
+ targetRel: relative24(root, targetPath)
25441
26481
  };
25442
26482
  if (verbatim) {
25443
26483
  partialItem.verbatim = true;
@@ -25478,10 +26518,10 @@ var computeWorkList = async (project, ledger, options = {}) => {
25478
26518
  let upToDate = 0;
25479
26519
  const universe = translatablePages(project, i18n);
25480
26520
  for (const { page: page2, contentRoot: contentRoot2, ext, sourcePath } of universe) {
25481
- const sourceRel = relative23(root, sourcePath);
26521
+ const sourceRel = relative24(root, sourcePath);
25482
26522
  knownSources.add(sourceRel);
25483
26523
  const hash = hashSource(await readFile29(sourcePath, "utf-8"));
25484
- const contentRel = relative23(contentRoot2, sourcePath);
26524
+ const contentRel = relative24(contentRoot2, sourcePath);
25485
26525
  for (const locale of targetLocales) {
25486
26526
  const targetPath = join48(contentRoot2, localeTargetPath(contentRel, ext, locale, i18n));
25487
26527
  const item = (status) => ({
@@ -25491,7 +26531,7 @@ var computeWorkList = async (project, ledger, options = {}) => {
25491
26531
  sourceRel,
25492
26532
  status,
25493
26533
  targetPath,
25494
- targetRel: relative23(root, targetPath)
26534
+ targetRel: relative24(root, targetPath)
25495
26535
  });
25496
26536
  const exists = translated.has(`${page2.translationKey}\x00${locale}`);
25497
26537
  const stamp = ledger.files[sourceRel]?.[locale];
@@ -25518,7 +26558,7 @@ var computeWorkList = async (project, ledger, options = {}) => {
25518
26558
  const entries = [];
25519
26559
  for (const source of meta.metas) {
25520
26560
  knownSources.add(source.sourceRel);
25521
- const targetDir = dirname18(metaTargetPath(source, locale));
26561
+ const targetDir = dirname19(metaTargetPath(source, locale));
25522
26562
  const exists = ["meta.ts", "meta.js", "meta.mjs"].some((name) => existsSync26(join48(targetDir, name)));
25523
26563
  const hash = hashSource(source.raw);
25524
26564
  const stamp = ledger.files[source.sourceRel]?.[locale];
@@ -25752,7 +26792,7 @@ import { join as join50 } from "pathe";
25752
26792
 
25753
26793
  // src/core/links.ts
25754
26794
  import { existsSync as existsSync27 } from "node:fs";
25755
- import { basename as basename8, dirname as dirname19, join as join49, relative as relative24, resolve as resolve15 } from "pathe";
26795
+ import { basename as basename8, dirname as dirname20, join as join49, relative as relative25, resolve as resolve16 } from "pathe";
25756
26796
  var HTTP = /^https?:\/\//iu;
25757
26797
  var PROTOCOL_RELATIVE = /^\/\//u;
25758
26798
  var SCHEME = /^[a-z][a-z0-9+.-]*:/iu;
@@ -25767,7 +26807,7 @@ var DOC_EXT = /\.(?:md|mdx)$/iu;
25767
26807
  var FILE_EXT = /\.[a-z0-9]+$/iu;
25768
26808
  var includedBySuffix = (link, page2) => link.file ? ` (included by ${basename8(page2.sourcePath ?? page2.id)})` : "";
25769
26809
  var authoredImageTarget = (target, pagePath, partialPath) => {
25770
- const authored = relative24(dirname19(partialPath), resolve15(dirname19(pagePath), target));
26810
+ const authored = relative25(dirname20(partialPath), resolve16(dirname20(pagePath), target));
25771
26811
  return authored.startsWith(".") ? authored : `./${authored}`;
25772
26812
  };
25773
26813
  var assetIsPresent = (resolved, ctx) => ctx.publicDir !== null && existsSync27(join49(ctx.publicDir, resolved));
@@ -25842,7 +26882,7 @@ var checkPathLink = (resolved, fragment, page2, link, site, ctx, via = "") => {
25842
26882
  return null;
25843
26883
  }
25844
26884
  if (link.image && page2.sourcePath && isRelativeImageTarget(link.target)) {
25845
- if (resolveRelativeImage(dirname19(page2.sourcePath), link.target)) {
26885
+ if (resolveRelativeImage(dirname20(page2.sourcePath), link.target)) {
25846
26886
  return null;
25847
26887
  }
25848
26888
  const authored = link.file ? authoredImageTarget(link.target, page2.sourcePath, link.file) : link.target;
@@ -25874,18 +26914,18 @@ var checkPathLink = (resolved, fragment, page2, link, site, ctx, via = "") => {
25874
26914
  };
25875
26915
  };
25876
26916
  var checkExternalLinks = async (refs) => {
25877
- const results = await probeAll(refs.map((ref) => ref.url));
26917
+ const results = await probeAll(refs.map((ref2) => ref2.url));
25878
26918
  const diagnostics = [];
25879
- for (const ref of refs) {
25880
- const result = results.get(ref.url);
26919
+ for (const ref2 of refs) {
26920
+ const result = results.get(ref2.url);
25881
26921
  const grade = result ? gradeExternal(result) : null;
25882
26922
  if (grade) {
25883
26923
  diagnostics.push({
25884
26924
  code: "BLUME_DEAD_LINK",
25885
- column: ref.column,
25886
- file: ref.file,
25887
- line: ref.line,
25888
- message: `External link ${ref.url} is unreachable (${grade.detail}).`,
26925
+ column: ref2.column,
26926
+ file: ref2.file,
26927
+ line: ref2.line,
26928
+ message: `External link ${ref2.url} is unreachable (${grade.detail}).`,
25889
26929
  severity: grade.severity
25890
26930
  });
25891
26931
  }
@@ -25939,7 +26979,7 @@ var validateLinks = async (graph, options) => {
25939
26979
  let uncheckedAssets = 0;
25940
26980
  for (const page2 of graph.pages) {
25941
26981
  for (const link of page2.links) {
25942
- const result = classifyLink(page2, link, ctx, (ref) => external.push(ref));
26982
+ const result = classifyLink(page2, link, ctx, (ref2) => external.push(ref2));
25943
26983
  if (result === "asset-unchecked") {
25944
26984
  uncheckedAssets += 1;
25945
26985
  } else if (result) {
@@ -26055,7 +27095,7 @@ import { defineCommand as defineCommand15 } from "citty";
26055
27095
  // src/core/version-cut.ts
26056
27096
  import { existsSync as existsSync29 } from "node:fs";
26057
27097
  import { cp as cp3, readdir as readdir5, readFile as readFile30, rm as rm5 } from "node:fs/promises";
26058
- import { join as join51, relative as relative25 } from "pathe";
27098
+ import { join as join51, relative as relative26 } from "pathe";
26059
27099
  class CutError extends Error {
26060
27100
  constructor(message) {
26061
27101
  super(message);
@@ -26070,7 +27110,7 @@ var buildRouteRewrites = (project, id2) => {
26070
27110
  const rewrites = new Map;
26071
27111
  for (const page2 of project.graph.pages) {
26072
27112
  const { sourcePath } = page2;
26073
- if (page2.version !== "" || page2.collection === "staged" || !sourcePath || relative25(contentRoot2, sourcePath).startsWith("..")) {
27113
+ if (page2.version !== "" || page2.collection === "staged" || !sourcePath || relative26(contentRoot2, sourcePath).startsWith("..")) {
26074
27114
  continue;
26075
27115
  }
26076
27116
  const logical = versionizeRoute(page2.versionKey, id2);
@@ -26146,13 +27186,12 @@ var insertArchivedVersion = async (configPath, id2) => {
26146
27186
  `, match.index) + 1;
26147
27187
  const indent = text2.slice(lineStart).match(/^\s*/u)?.[0] ?? "";
26148
27188
  const rest = text2.slice(insertAt);
27189
+ const eol = /^\r?\n/u.exec(rest)?.[0];
26149
27190
  let entry;
26150
27191
  if (rest.trimStart().startsWith("]")) {
26151
27192
  entry = `{ id: "${id2}" }`;
26152
- } else if (rest.startsWith(`
26153
- `)) {
26154
- entry = `
26155
- ${indent} { id: "${id2}" },`;
27193
+ } else if (eol) {
27194
+ entry = `${eol}${indent} { id: "${id2}" },`;
26156
27195
  } else {
26157
27196
  entry = `{ id: "${id2}" }, `;
26158
27197
  }
@@ -26215,7 +27254,7 @@ var cutVersion = async (root, id2, options = {}) => {
26215
27254
  const { text: text2, count } = rewriteSnapshotLinks(source, rewrites);
26216
27255
  if (count > 0) {
26217
27256
  await writeTextAtomic(abs, text2);
26218
- rewritten.push({ count, file: relative25(dir, abs) });
27257
+ rewritten.push({ count, file: relative26(dir, abs) });
26219
27258
  }
26220
27259
  }));
26221
27260
  };
@@ -26293,6 +27332,15 @@ var versionCommand = defineCommand15({
26293
27332
  }
26294
27333
  });
26295
27334
 
27335
+ // src/cli/host-args.ts
27336
+ var normalizeHostArgs = (rawArgs) => rawArgs.map((arg, index) => {
27337
+ if (arg !== "--host") {
27338
+ return arg;
27339
+ }
27340
+ const next = rawArgs[index + 1];
27341
+ return next === undefined || next.startsWith("-") ? "--host=" : arg;
27342
+ });
27343
+
26296
27344
  // src/cli/index.ts
26297
27345
  var main = defineCommand16({
26298
27346
  meta: {
@@ -26327,7 +27375,7 @@ process.on("unhandledRejection", (error) => {
26327
27375
  reportInternalError(error);
26328
27376
  process.exit(1);
26329
27377
  });
26330
- runMain(main);
27378
+ runMain(main, { rawArgs: normalizeHostArgs(process.argv.slice(2)) });
26331
27379
 
26332
- //# debugId=6AEF08BD7421838D64756E2164756E21
27380
+ //# debugId=9AB8E5F1D71CCEF764756E2164756E21
26333
27381
  //# sourceMappingURL=index.js.map