blume 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/cli/index.js +1765 -603
  3. package/dist/cli/index.js.map +37 -28
  4. package/dist/types/core/config-input.d.ts +131 -11
  5. package/dist/types/core/config.d.ts +9 -1
  6. package/dist/types/core/data.d.ts +24 -5
  7. package/dist/types/core/i18n-ui.d.ts +58 -799
  8. package/dist/types/core/schema.d.ts +534 -3305
  9. package/dist/types/theme/fonts.d.ts +55 -11
  10. package/docs/02-deployment.mdx +2 -0
  11. package/docs/07-faq.mdx +14 -14
  12. package/docs/advanced/skills.mdx +2 -2
  13. package/docs/configuration/ai.mdx +126 -2
  14. package/docs/configuration/index.mdx +19 -1
  15. package/docs/configuration/search.mdx +2 -0
  16. package/docs/configuration/seo.mdx +26 -3
  17. package/docs/configuration/theming.mdx +44 -2
  18. package/docs/content/syntax.mdx +18 -2
  19. package/docs/reference/cli.mdx +3 -3
  20. package/package.json +9 -8
  21. package/skills/blume/SKILL.md +6 -4
  22. package/skills/blume-migrate/SKILL.md +5 -3
  23. package/skills/blume-migrate/references/mintlify.md +5 -5
  24. package/skills/blume-migrate/references/monorepo.md +2 -1
  25. package/src/ai/agent-readability.ts +31 -1
  26. package/src/ai/api-catalog.ts +81 -0
  27. package/src/ai/link-headers.ts +53 -0
  28. package/src/ai/llms.ts +16 -3
  29. package/src/ai/markdown.ts +49 -3
  30. package/src/ai/mcp/discovery.ts +70 -15
  31. package/src/ai/mcp/server.ts +5 -4
  32. package/src/ai/skills.ts +193 -0
  33. package/src/ai/tar.ts +104 -0
  34. package/src/ai/web-bot-auth.ts +30 -0
  35. package/src/astro/generate.ts +118 -8
  36. package/src/astro/integration.ts +54 -14
  37. package/src/astro/templates.ts +184 -36
  38. package/src/audit/catalog.ts +20 -0
  39. package/src/audit/checks/dns-aid.ts +190 -0
  40. package/src/audit/report.ts +5 -0
  41. package/src/audit/run.ts +2 -0
  42. package/src/cli/commands/build.ts +184 -9
  43. package/src/cli/init/scaffold.ts +1 -1
  44. package/src/components/islands/ask-ai.tsx +4 -1
  45. package/src/components/islands/webmcp.ts +203 -0
  46. package/src/components/layout/PageLayout.astro +2 -0
  47. package/src/components/layout/ReferenceLayout.astro +2 -0
  48. package/src/components/layout/RootLayout.astro +62 -10
  49. package/src/components/layout/Search.astro +2 -2
  50. package/src/components/layout/WebMcp.astro +49 -0
  51. package/src/core/config-input.ts +143 -11
  52. package/src/core/config.ts +17 -1
  53. package/src/core/content-assets.ts +199 -0
  54. package/src/core/data.ts +21 -5
  55. package/src/core/diagnostics.ts +6 -5
  56. package/src/core/i18n-ui.ts +19 -28
  57. package/src/core/project-graph.ts +6 -0
  58. package/src/core/schema.ts +224 -71
  59. package/src/core/sources/normalize.ts +5 -5
  60. package/src/deploy/headers.ts +45 -3
  61. package/src/deploy/vercel-negotiation.ts +250 -0
  62. package/src/markdown/mermaid.ts +7 -1
  63. package/src/markdown/table-wrap.ts +33 -1
  64. package/src/og/card.ts +91 -22
  65. package/src/og/derive.ts +200 -0
  66. package/src/og/index.ts +6 -1
  67. package/src/search/orama-index.ts +98 -4
  68. package/src/theme/entry.ts +34 -13
  69. package/src/theme/fonts.ts +185 -30
  70. package/dist/types/og/card.d.ts +0 -63
  71. package/dist/types/og/dimensions.d.ts +0 -12
package/dist/cli/index.js CHANGED
@@ -391,8 +391,8 @@ var diagnosticsFromIssues = (issues, options) => issues.map((issue) => {
391
391
  };
392
392
  });
393
393
  var diagnosticsFromZod = (error, options) => diagnosticsFromIssues(error.issues.map((issue) => ({
394
- message: "received" in issue ? `${issue.message} (received: ${JSON.stringify(issue.received)})` : issue.message,
395
- path: issue.path
394
+ message: issue.message,
395
+ path: issue.path.filter((segment) => typeof segment !== "symbol")
396
396
  })), options);
397
397
  var ESC = String.fromCodePoint(27);
398
398
  var COLORS = {
@@ -1180,6 +1180,22 @@ var CHECKS = [
1180
1180
  tier: "static",
1181
1181
  title: "Indexable page missing from llms.txt"
1182
1182
  },
1183
+ {
1184
+ category: "ai",
1185
+ fix: "Publish a ServiceMode SVCB or HTTPS record at `_index._agents.<host>` with your DNS provider so agents can discover the site via DNS (DNS-AID).",
1186
+ id: "BLUME_AUDIT_DNS_AID_MISSING",
1187
+ severity: "info",
1188
+ tier: "network",
1189
+ title: "No DNS-AID agent-discovery records"
1190
+ },
1191
+ {
1192
+ category: "ai",
1193
+ fix: "Enable DNSSEC for the zone so validating resolvers return authenticated DNS-AID answers; if your DNS provider doesn't support DNSSEC, the records still work unsigned.",
1194
+ id: "BLUME_AUDIT_DNS_AID_UNSIGNED",
1195
+ severity: "info",
1196
+ tier: "network",
1197
+ title: "DNS-AID records are not DNSSEC-authenticated"
1198
+ },
1183
1199
  {
1184
1200
  category: "structured-data",
1185
1201
  fix: "The JSON-LD block must be valid JSON.",
@@ -1376,6 +1392,9 @@ var formatReport = (result, root, options = {}) => {
1376
1392
  const shown = options.verbose ? group.findings : group.findings.slice(0, PREVIEW);
1377
1393
  for (const diagnostic of shown) {
1378
1394
  lines.push(findingLine(diagnostic, root));
1395
+ if (options.verbose) {
1396
+ lines.push(` ${COLORS2.dim}${diagnostic.message}${COLORS2.reset}`);
1397
+ }
1379
1398
  }
1380
1399
  const hidden = group.count - shown.length;
1381
1400
  if (hidden > 0) {
@@ -1831,6 +1850,104 @@ var contentChecks = {
1831
1850
  tier: "static"
1832
1851
  };
1833
1852
 
1853
+ // src/audit/checks/dns-aid.ts
1854
+ var RR_SVCB = 64;
1855
+ var RR_HTTPS = 65;
1856
+ var DOH_TIMEOUT_MS = 5000;
1857
+ var DOH_RESOLVERS = [
1858
+ "https://dns.google/resolve",
1859
+ "https://cloudflare-dns.com/dns-query"
1860
+ ];
1861
+ var resolvers = () => {
1862
+ const override = process.env.BLUME_DOH_URL;
1863
+ return override ? [override] : DOH_RESOLVERS;
1864
+ };
1865
+ var fetchDohJson = async (base, name, type, fetchFn) => {
1866
+ try {
1867
+ const response = await fetchFn(`${base}?name=${encodeURIComponent(name)}&type=${type}`, {
1868
+ headers: { accept: "application/dns-json" },
1869
+ signal: AbortSignal.timeout(DOH_TIMEOUT_MS)
1870
+ });
1871
+ if (!response.ok) {
1872
+ return null;
1873
+ }
1874
+ return await response.json();
1875
+ } catch {
1876
+ return null;
1877
+ }
1878
+ };
1879
+ var queryDoh = async (name, type, fetchFn) => {
1880
+ for (const base of resolvers()) {
1881
+ const response = await fetchDohJson(base, name, type, fetchFn);
1882
+ if (response) {
1883
+ return response;
1884
+ }
1885
+ }
1886
+ return null;
1887
+ };
1888
+ var lookupDnsAid = async (host, fetchFn = fetch) => {
1889
+ const name = `_index._agents.${host}`;
1890
+ let answered = false;
1891
+ let found = false;
1892
+ let authenticated = true;
1893
+ const responses = await Promise.all([RR_HTTPS, RR_SVCB].map((type) => queryDoh(name, type, fetchFn)));
1894
+ for (const response of responses) {
1895
+ if (!response) {
1896
+ continue;
1897
+ }
1898
+ answered = true;
1899
+ const records = (response.Answer ?? []).filter((answer) => answer.type === RR_SVCB || answer.type === RR_HTTPS);
1900
+ if (records.length > 0) {
1901
+ found = true;
1902
+ authenticated &&= response.AD === true;
1903
+ }
1904
+ }
1905
+ return answered ? { authenticated, found } : null;
1906
+ };
1907
+ var dnsAidFindings = (host, result) => {
1908
+ if (!result.found) {
1909
+ return [
1910
+ finding("BLUME_AUDIT_DNS_AID_MISSING", { url: "/" }, `No SVCB or HTTPS records at _index._agents.${host} — agents probing DNS for AI Discovery (DNS-AID) find nothing.`, `Publish a ServiceMode record with your DNS provider, e.g. \`_index._agents.${host}. 3600 IN HTTPS 1 ${host}. alpn=h2\`, and sign the zone with DNSSEC if the provider supports it.`)
1911
+ ];
1912
+ }
1913
+ if (!result.authenticated) {
1914
+ return [
1915
+ finding("BLUME_AUDIT_DNS_AID_UNSIGNED", { url: "/" }, `The DNS-AID records at _index._agents.${host} exist but are not DNSSEC-authenticated, so validating resolvers can't prove they're genuine.`)
1916
+ ];
1917
+ }
1918
+ return [];
1919
+ };
1920
+ var dnsAidHost = (site) => {
1921
+ if (!site) {
1922
+ return null;
1923
+ }
1924
+ let hostname;
1925
+ try {
1926
+ ({ hostname } = new URL(site));
1927
+ } catch {
1928
+ return null;
1929
+ }
1930
+ if (hostname === "localhost" || hostname.endsWith(".local") || !hostname.includes(".")) {
1931
+ return null;
1932
+ }
1933
+ return hostname;
1934
+ };
1935
+ var dnsAidChecks = {
1936
+ category: "ai",
1937
+ async run(context) {
1938
+ if (!context.origin) {
1939
+ return [];
1940
+ }
1941
+ const host = dnsAidHost(context.project.config.deployment.site);
1942
+ if (!host) {
1943
+ return [];
1944
+ }
1945
+ const result = await lookupDnsAid(host);
1946
+ return result ? dnsAidFindings(host, result) : [];
1947
+ },
1948
+ tier: "network"
1949
+ };
1950
+
1834
1951
  // src/audit/checks/duplicates.ts
1835
1952
  var isNonCanonical = (page, deployBase) => {
1836
1953
  if (!page.canonical) {
@@ -3000,31 +3117,6 @@ var askBackendRuntimeDep = (ask) => {
3000
3117
  return ASK_PRESETS[provider]?.runtimeDep ?? OPENAI_COMPATIBLE_DEP;
3001
3118
  };
3002
3119
 
3003
- // src/core/sources/watch.ts
3004
- var BLUME_IGNORE_DIRS = [
3005
- ".blume",
3006
- ".blume-verify",
3007
- ".cache",
3008
- ".git",
3009
- ".next",
3010
- ".turbo",
3011
- ".vercel",
3012
- "dist",
3013
- "node_modules"
3014
- ];
3015
- var baselineScanIgnore = () => BLUME_IGNORE_DIRS.map((dir) => `**/${dir}/**`);
3016
- var BLUME_WATCH_IGNORE_DIRS = BLUME_IGNORE_DIRS;
3017
- var excludeDirSegments = (patterns) => patterns.map((pattern) => /^(?<dir>[^*/]+)\/\*\*$/u.exec(pattern)?.groups?.dir).filter((dir) => dir !== undefined);
3018
- var ignoringWatchListener = (onChange, ignoreDirs = BLUME_WATCH_IGNORE_DIRS) => {
3019
- const ignore = new Set(ignoreDirs);
3020
- return (_event, filename) => {
3021
- if (typeof filename === "string" && filename.split(/[/\\]/u).some((segment) => ignore.has(segment))) {
3022
- return;
3023
- }
3024
- onChange();
3025
- };
3026
- };
3027
-
3028
3120
  // src/core/trim.ts
3029
3121
  var trimStart = (text, char) => {
3030
3122
  let start = 0;
@@ -3042,41 +3134,6 @@ var trimEnd = (text, char) => {
3042
3134
  };
3043
3135
  var trimChar = (text, char) => trimEnd(trimStart(text, char), char);
3044
3136
 
3045
- // src/deploy/redirects.ts
3046
- var applyBaseToAstroRedirects = (redirects, basePath, deployBase) => {
3047
- const base = normalizeBasePath(deployBase);
3048
- return basePath || base ? redirects.map((redirect) => ({
3049
- ...redirect,
3050
- from: withBasePath(basePath, redirect.from),
3051
- to: withComposedBasePath(base, basePath, redirect.to)
3052
- })) : redirects;
3053
- };
3054
- var applyBaseToPlatformRedirects = (redirects, basePath, deployBase) => {
3055
- const base = normalizeBasePath(deployBase);
3056
- return basePath || base ? redirects.map((redirect) => ({
3057
- ...redirect,
3058
- from: withComposedBasePath(base, basePath, redirect.from),
3059
- to: withComposedBasePath(base, basePath, redirect.to)
3060
- })) : redirects;
3061
- };
3062
- var buildNetlifyRedirects = (redirects) => `${redirects.map((redirect) => `${redirect.from} ${redirect.to} ${redirect.status}`).join(`
3063
- `)}
3064
- `;
3065
- var buildVercelConfig = (redirects) => `${JSON.stringify({
3066
- redirects: redirects.map((redirect) => ({
3067
- destination: redirect.to,
3068
- source: redirect.from,
3069
- statusCode: redirect.status
3070
- }))
3071
- }, null, 2)}
3072
- `;
3073
- var buildRedirectManifest = (redirects) => `${JSON.stringify(redirects.map((redirect) => ({
3074
- from: redirect.from,
3075
- status: redirect.status,
3076
- to: redirect.to
3077
- })), null, 2)}
3078
- `;
3079
-
3080
3137
  // src/openapi/references.ts
3081
3138
  var NON_SLUG = /[^a-z0-9]+/gu;
3082
3139
  var slugify = (text) => trimChar(text.toLowerCase().replace(NON_SLUG, "-"), "-");
@@ -3176,6 +3233,129 @@ var blumeReferences = (config) => {
3176
3233
  };
3177
3234
  var hasScalarReferences = (config) => resolveReferences(config).some((ref) => ref.renderer === "scalar");
3178
3235
 
3236
+ // src/ai/api-catalog.ts
3237
+ var API_CATALOG_PATH = "/.well-known/api-catalog";
3238
+ var API_CATALOG_TYPE = "application/linkset+json";
3239
+ var HTTP_URL = /^https?:\/\//u;
3240
+ var linksetEntries = (config) => {
3241
+ const site = config.deployment.site ?? null;
3242
+ const deployBase = normalizeBasePath(config.deployment.base);
3243
+ const abs = (path) => {
3244
+ const based = withBasePath(deployBase, path);
3245
+ return site ? `${trimEnd(site, "/")}${based}` : based;
3246
+ };
3247
+ const entries = [];
3248
+ for (const reference of resolveReferences(config)) {
3249
+ const docRoute = reference.renderer === "blume" ? withBasePath(reference.basePath, reference.route) : reference.route;
3250
+ const entry = {
3251
+ anchor: abs(docRoute),
3252
+ "service-doc": [{ href: abs(docRoute), type: "text/html" }]
3253
+ };
3254
+ if (HTTP_URL.test(reference.spec)) {
3255
+ entry["service-desc"] = [{ href: reference.spec }];
3256
+ }
3257
+ entries.push(entry);
3258
+ }
3259
+ if (config.ai.mcp.enabled) {
3260
+ entries.push({
3261
+ anchor: abs(config.ai.mcp.route),
3262
+ "service-desc": [
3263
+ { href: abs("/.well-known/mcp.json"), type: "application/json" }
3264
+ ],
3265
+ "service-doc": [{ href: abs("/"), type: "text/html" }]
3266
+ });
3267
+ }
3268
+ return entries;
3269
+ };
3270
+ var hasApiCatalog = (config) => linksetEntries(config).length > 0;
3271
+ var buildApiCatalog = (config) => {
3272
+ const linkset = linksetEntries(config);
3273
+ if (linkset.length === 0) {
3274
+ return null;
3275
+ }
3276
+ return `${JSON.stringify({ linkset }, null, 2)}
3277
+ `;
3278
+ };
3279
+
3280
+ // src/ai/link-headers.ts
3281
+ var buildHomeLinkHeader = (config, routePaths) => {
3282
+ const deployBase = normalizeBasePath(config.deployment.base);
3283
+ const links = [];
3284
+ if (hasApiCatalog(config)) {
3285
+ links.push(`<${deployBase}${API_CATALOG_PATH}>; rel="api-catalog"; type="application/linkset+json"`);
3286
+ }
3287
+ if (config.seo.agentReadability) {
3288
+ links.push(`<${deployBase}/agent-readability.json>; rel="describedby"; type="application/json"`);
3289
+ }
3290
+ if (config.ai.llmsTxt.enabled) {
3291
+ links.push(`<${deployBase}/llms.txt>; rel="describedby"; type="text/plain"`);
3292
+ }
3293
+ if (routePaths.includes("/")) {
3294
+ links.push(`<${deployBase}/index.md>; rel="alternate"; type="text/markdown"`);
3295
+ }
3296
+ return links.length > 0 ? links.join(", ") : null;
3297
+ };
3298
+
3299
+ // src/core/sources/watch.ts
3300
+ var BLUME_IGNORE_DIRS = [
3301
+ ".blume",
3302
+ ".blume-verify",
3303
+ ".cache",
3304
+ ".git",
3305
+ ".next",
3306
+ ".turbo",
3307
+ ".vercel",
3308
+ "dist",
3309
+ "node_modules"
3310
+ ];
3311
+ var baselineScanIgnore = () => BLUME_IGNORE_DIRS.map((dir) => `**/${dir}/**`);
3312
+ var BLUME_WATCH_IGNORE_DIRS = BLUME_IGNORE_DIRS;
3313
+ var excludeDirSegments = (patterns) => patterns.map((pattern) => /^(?<dir>[^*/]+)\/\*\*$/u.exec(pattern)?.groups?.dir).filter((dir) => dir !== undefined);
3314
+ var ignoringWatchListener = (onChange, ignoreDirs = BLUME_WATCH_IGNORE_DIRS) => {
3315
+ const ignore = new Set(ignoreDirs);
3316
+ return (_event, filename) => {
3317
+ if (typeof filename === "string" && filename.split(/[/\\]/u).some((segment) => ignore.has(segment))) {
3318
+ return;
3319
+ }
3320
+ onChange();
3321
+ };
3322
+ };
3323
+
3324
+ // src/deploy/redirects.ts
3325
+ var applyBaseToAstroRedirects = (redirects, basePath, deployBase) => {
3326
+ const base = normalizeBasePath(deployBase);
3327
+ return basePath || base ? redirects.map((redirect) => ({
3328
+ ...redirect,
3329
+ from: withBasePath(basePath, redirect.from),
3330
+ to: withComposedBasePath(base, basePath, redirect.to)
3331
+ })) : redirects;
3332
+ };
3333
+ var applyBaseToPlatformRedirects = (redirects, basePath, deployBase) => {
3334
+ const base = normalizeBasePath(deployBase);
3335
+ return basePath || base ? redirects.map((redirect) => ({
3336
+ ...redirect,
3337
+ from: withComposedBasePath(base, basePath, redirect.from),
3338
+ to: withComposedBasePath(base, basePath, redirect.to)
3339
+ })) : redirects;
3340
+ };
3341
+ var buildNetlifyRedirects = (redirects) => `${redirects.map((redirect) => `${redirect.from} ${redirect.to} ${redirect.status}`).join(`
3342
+ `)}
3343
+ `;
3344
+ var buildVercelConfig = (redirects) => `${JSON.stringify({
3345
+ redirects: redirects.map((redirect) => ({
3346
+ destination: redirect.to,
3347
+ source: redirect.from,
3348
+ statusCode: redirect.status
3349
+ }))
3350
+ }, null, 2)}
3351
+ `;
3352
+ var buildRedirectManifest = (redirects) => `${JSON.stringify(redirects.map((redirect) => ({
3353
+ from: redirect.from,
3354
+ status: redirect.status,
3355
+ to: redirect.to
3356
+ })), null, 2)}
3357
+ `;
3358
+
3179
3359
  // src/search/providers.ts
3180
3360
  var SEARCH_PROVIDERS = {
3181
3361
  algolia: {
@@ -3341,30 +3521,94 @@ var GOOGLE_FONTS = {
3341
3521
  };
3342
3522
  var FONT_SLUGS = Object.keys(GOOGLE_FONTS);
3343
3523
  var isFontSlug = (value) => Object.hasOwn(GOOGLE_FONTS, value);
3524
+ var DEFAULT_REMOTE_WEIGHTS = [400, 500, 600, 700];
3525
+ var slugifyFontName = (name) => name.toLowerCase().replaceAll(/[^a-z0-9]+/gu, "-").replaceAll(/^-|-$/gu, "");
3344
3526
  var fontVar = (slug) => `--blume-ff-${slug}`;
3345
3527
  var SLOTS = ["display", "body", "mono"];
3346
- var buildFontEntries = (fonts) => {
3347
- if (!fonts) {
3348
- return [];
3349
- }
3350
- const slugs = SLOTS.map((slot) => fonts[slot]).filter((slug) => typeof slug === "string" && isFontSlug(slug));
3351
- return [...new Set(slugs)].map((slug) => {
3352
- const def = GOOGLE_FONTS[slug];
3528
+ var slotCategory = (slot) => slot === "mono" ? "mono" : "sans";
3529
+ var resolveFontValue = (slot, value) => {
3530
+ if (typeof value === "string") {
3531
+ if (!isFontSlug(value)) {
3532
+ return null;
3533
+ }
3534
+ const def = GOOGLE_FONTS[value];
3353
3535
  return {
3354
- cssVariable: fontVar(slug),
3536
+ cssVariable: fontVar(value),
3355
3537
  fallbacks: FALLBACKS[def.category],
3538
+ kind: "remote",
3356
3539
  name: def.family,
3540
+ provider: "google",
3357
3541
  weights: def.weights
3358
3542
  };
3359
- });
3543
+ }
3544
+ const slug = slugifyFontName(value.name);
3545
+ const fallbacks = FALLBACKS[value.fallback ?? slotCategory(slot)];
3546
+ if ("variants" in value) {
3547
+ return {
3548
+ cssVariable: fontVar(slug),
3549
+ fallbacks,
3550
+ kind: "local",
3551
+ name: value.name,
3552
+ variants: value.variants
3553
+ };
3554
+ }
3555
+ return {
3556
+ cssVariable: fontVar(slug),
3557
+ fallbacks,
3558
+ kind: "remote",
3559
+ name: value.name,
3560
+ provider: value.provider ?? "google",
3561
+ weights: value.weights ?? DEFAULT_REMOTE_WEIGHTS
3562
+ };
3563
+ };
3564
+ var mergeFontEntries = (current, next) => {
3565
+ if (current.kind === "remote" && next.kind === "remote" && current.provider === next.provider && current.name === next.name) {
3566
+ return {
3567
+ ...current,
3568
+ weights: [...new Set([...current.weights, ...next.weights])]
3569
+ };
3570
+ }
3571
+ if (current.kind === "local" && next.kind === "local" && current.name === next.name && JSON.stringify(current.variants) === JSON.stringify(next.variants)) {
3572
+ return current;
3573
+ }
3574
+ throw new Error(`theme.fonts: "${next.name}" conflicts with "${current.name}" — both resolve to the CSS variable "${current.cssVariable}" with different definitions. Rename one family or align their definitions.`);
3575
+ };
3576
+ var buildFontEntries = (fonts) => {
3577
+ if (!fonts) {
3578
+ return [];
3579
+ }
3580
+ const entries = new Map;
3581
+ for (const slot of SLOTS) {
3582
+ const value = fonts[slot];
3583
+ if (value === undefined) {
3584
+ continue;
3585
+ }
3586
+ const entry = resolveFontValue(slot, value);
3587
+ if (!entry) {
3588
+ continue;
3589
+ }
3590
+ const current = entries.get(entry.cssVariable);
3591
+ entries.set(entry.cssVariable, current ? mergeFontEntries(current, entry) : entry);
3592
+ }
3593
+ return [...entries.values()];
3594
+ };
3595
+ var slotSlug = (value) => {
3596
+ if (typeof value === "string") {
3597
+ return isFontSlug(value) ? value : null;
3598
+ }
3599
+ return slugifyFontName(value.name);
3360
3600
  };
3361
3601
  var buildFontsCss = (fonts) => {
3362
3602
  if (!fonts) {
3363
3603
  return "";
3364
3604
  }
3365
3605
  const lines = SLOTS.flatMap((slot) => {
3366
- const slug = fonts[slot];
3367
- return slug && isFontSlug(slug) ? [` --blume-font-${slot}-src: var(${fontVar(slug)});`] : [];
3606
+ const value = fonts[slot];
3607
+ if (value === undefined) {
3608
+ return [];
3609
+ }
3610
+ const slug = slotSlug(value);
3611
+ return slug ? [` --blume-font-${slot}-src: var(${fontVar(slug)});`] : [];
3368
3612
  });
3369
3613
  return lines.length > 0 ? `/* Generated by Blume from theme.fonts. */
3370
3614
  :root {
@@ -3533,6 +3777,8 @@ const blumeConfig = await loadBlumeConfig(resolve(dirname(fileURLToPath(import.m
3533
3777
  userIntegrationSpread: ", ...(blumeConfig?.integrations ?? [])"
3534
3778
  };
3535
3779
  };
3780
+ var renderImageOption = (config) => config.image.domains.length > 0 || config.image.remotePatterns.length > 0 ? `
3781
+ image: ${JSON.stringify(config.image)},` : "";
3536
3782
  var astroConfigTemplate = (options) => {
3537
3783
  const { context, config, needsReact, pages, dataPath, themePath } = options;
3538
3784
  const {
@@ -3568,6 +3814,7 @@ var astroConfigTemplate = (options) => {
3568
3814
  site: ${JSON.stringify(deployment.site)},` : "";
3569
3815
  const baseOption = deployment.base ? `
3570
3816
  base: ${JSON.stringify(deployment.base)},` : "";
3817
+ const imageOption = renderImageOption(config);
3571
3818
  const i18nOption = config.i18n ? `
3572
3819
  i18n: ${JSON.stringify({
3573
3820
  defaultLocale: config.i18n.defaultLocale,
@@ -3584,7 +3831,13 @@ var astroConfigTemplate = (options) => {
3584
3831
  ])))},` : "";
3585
3832
  const fontEntries = buildFontEntries(config.theme.fonts);
3586
3833
  const fontsOption = fontEntries.length ? `
3587
- fonts: [${fontEntries.map((font) => `{ provider: fontProviders.google(), name: ${JSON.stringify(font.name)}, cssVariable: ${JSON.stringify(font.cssVariable)}, weights: ${JSON.stringify(font.weights)}, fallbacks: ${JSON.stringify(font.fallbacks)} }`).join(", ")}],` : "";
3834
+ fonts: [${fontEntries.map((font) => font.kind === "local" ? `{ provider: fontProviders.local(), name: ${JSON.stringify(font.name)}, cssVariable: ${JSON.stringify(font.cssVariable)}, fallbacks: ${JSON.stringify(font.fallbacks)}, options: { variants: ${JSON.stringify(font.variants.map((variant) => ({
3835
+ ...variant.weight === undefined ? {} : { weight: variant.weight },
3836
+ ...variant.style === undefined ? {} : { style: variant.style },
3837
+ src: [
3838
+ isAbsolute(variant.src) ? variant.src : join8(context.root, variant.src)
3839
+ ]
3840
+ })))} } }` : `{ provider: fontProviders.${font.provider}(), name: ${JSON.stringify(font.name)}, cssVariable: ${JSON.stringify(font.cssVariable)}, weights: ${JSON.stringify(font.weights)}, fallbacks: ${JSON.stringify(font.fallbacks)} }`).join(", ")}],` : "";
3588
3841
  const defineConfigImport = astroConfigImportLine({
3589
3842
  hasFonts: fontEntries.length > 0,
3590
3843
  hasSession: sessionOption.length > 0
@@ -3622,7 +3875,12 @@ var astroConfigTemplate = (options) => {
3622
3875
  if (needsSvelte) {
3623
3876
  integrations.push("svelte()");
3624
3877
  }
3625
- integrations.push(`blumeIntegration(${JSON.stringify({ base: deployment.base, contentRoutes, pages })})`);
3878
+ integrations.push(`blumeIntegration(${JSON.stringify({
3879
+ base: deployment.base,
3880
+ contentRoutes,
3881
+ homeLinkHeader: buildHomeLinkHeader(config, contentRoutes) ?? undefined,
3882
+ pages
3883
+ })})`);
3626
3884
  const watchOption = devWatchOption(context.outDir, options.contentWatchesRuntimeDir);
3627
3885
  const {
3628
3886
  configSourceMarker,
@@ -3641,7 +3899,7 @@ ${userConfigSetup}export default defineConfig({
3641
3899
  srcDir: ${JSON.stringify(`${context.outDir}/src`)},
3642
3900
  outDir: ${JSON.stringify(astroOutDir(context))},
3643
3901
  publicDir: ${JSON.stringify(`${context.root}/public`)},
3644
- output: ${JSON.stringify(deployment.output)},${adapterOption}${sessionOption}${siteOption}${baseOption}${redirectsOption}${i18nOption}${fontsOption}
3902
+ output: ${JSON.stringify(deployment.output)},${adapterOption}${sessionOption}${siteOption}${baseOption}${imageOption}${redirectsOption}${i18nOption}${fontsOption}
3645
3903
  integrations: [${integrations.join(", ")}${userIntegrationSpread}],
3646
3904
  markdown: {
3647
3905
  processor: blumeMarkdownProcessor(${JSON.stringify({
@@ -3832,17 +4090,17 @@ const ground = createAskContext(askData);
3832
4090
  const onError = ` onError({ error }) {
3833
4091
  console.error("Ask AI provider error:", error);
3834
4092
  },`;
3835
- const stream = grounded ? ` const system =
4093
+ const stream = grounded ? ` const instructions =
3836
4094
  (await ground(messages, body.page)) ??
3837
4095
  "You are a helpful documentation assistant. Answer using the project's documentation.";
3838
4096
  const result = streamText({
3839
4097
  model: ${modelExpr},
3840
- system,
4098
+ instructions,
3841
4099
  messages,
3842
4100
  ${onError}
3843
4101
  });` : ` const result = streamText({
3844
4102
  model: ${modelExpr},
3845
- system:
4103
+ instructions:
3846
4104
  "You are a helpful documentation assistant. Answer using the project's documentation.",
3847
4105
  messages,
3848
4106
  ${onError}
@@ -4011,11 +4269,96 @@ export function getStaticPaths() {
4011
4269
  export function GET({ props }: { props: { route: string } }) {
4012
4270
  const entries = raw as Record<string, { md?: string; mdx?: string }>;
4013
4271
  const entry = entries[props.route];
4014
- return new Response(entry ? ${kind === "md" ? '(entry.md ?? entry.mdx ?? "")' : '(entry.mdx ?? "")'} : "", {
4015
- headers: { "Content-Type": "text/markdown; charset=utf-8" },
4272
+ const body = entry ? ${kind === "md" ? '(entry.md ?? entry.mdx ?? "")' : '(entry.mdx ?? "")'} : "";
4273
+ return new Response(body, {
4274
+ headers: {
4275
+ "Content-Type": "text/markdown; charset=utf-8",
4276
+ // ~4 characters per token; keep in sync with markdownTokenCount.
4277
+ "x-markdown-tokens": String(Math.ceil(body.length / 4)),
4278
+ },
4016
4279
  });
4017
4280
  }
4018
4281
  `;
4282
+ var contentAssetsEndpointTemplate = (stagedAssetsDir) => `// Generated by Blume. Do not edit.
4283
+ import { existsSync } from "node:fs";
4284
+ import { readdir, readFile } from "node:fs/promises";
4285
+ import { join, relative, resolve } from "node:path";
4286
+ import type { APIRoute } from "astro";
4287
+ import assets from "../../generated/content-assets.json";
4288
+
4289
+ export const prerender = true;
4290
+
4291
+ const files = assets as Record<string, string>;
4292
+ const STAGED_DIR = ${JSON.stringify(stagedAssetsDir)};
4293
+
4294
+ const TYPES: Record<string, string> = {
4295
+ ".apng": "image/apng",
4296
+ ".avif": "image/avif",
4297
+ ".bmp": "image/bmp",
4298
+ ".gif": "image/gif",
4299
+ ".ico": "image/x-icon",
4300
+ ".jpeg": "image/jpeg",
4301
+ ".jpg": "image/jpeg",
4302
+ ".png": "image/png",
4303
+ ".svg": "image/svg+xml",
4304
+ ".tiff": "image/tiff",
4305
+ ".webp": "image/webp",
4306
+ };
4307
+
4308
+ const contentType = (path: string): string =>
4309
+ TYPES[path.slice(path.lastIndexOf(".")).toLowerCase()] ??
4310
+ "application/octet-stream";
4311
+
4312
+ const stagedParams = async (): Promise<string[]> => {
4313
+ if (!existsSync(STAGED_DIR)) {
4314
+ return [];
4315
+ }
4316
+ const entries = await readdir(STAGED_DIR, {
4317
+ recursive: true,
4318
+ withFileTypes: true,
4319
+ });
4320
+ return entries
4321
+ .filter((entry) => entry.isFile())
4322
+ .map((entry) =>
4323
+ relative(STAGED_DIR, join(entry.parentPath, entry.name)).replaceAll(
4324
+ "\\\\",
4325
+ "/"
4326
+ )
4327
+ );
4328
+ };
4329
+
4330
+ export async function getStaticPaths() {
4331
+ const params = [
4332
+ ...Object.keys(files).map((key) => "content/" + key),
4333
+ ...(await stagedParams()),
4334
+ ];
4335
+ return params.map((asset) => ({ params: { asset } }));
4336
+ }
4337
+
4338
+ const resolveAsset = (asset: string): string | null => {
4339
+ if (asset.startsWith("content/")) {
4340
+ return files[asset.slice("content/".length)] ?? null;
4341
+ }
4342
+ const abs = resolve(STAGED_DIR, asset);
4343
+ // Traversal guard: the dev server renders on demand, so the param is
4344
+ // attacker-controlled there — never step outside the staged directory.
4345
+ if (!abs.startsWith(STAGED_DIR + "/")) {
4346
+ return null;
4347
+ }
4348
+ return existsSync(abs) ? abs : null;
4349
+ };
4350
+
4351
+ export const GET: APIRoute = async ({ params }) => {
4352
+ const path = resolveAsset(String(params.asset ?? ""));
4353
+ if (!path) {
4354
+ return new Response(null, { status: 404 });
4355
+ }
4356
+ const body = await readFile(path);
4357
+ return new Response(new Uint8Array(body), {
4358
+ headers: { "Content-Type": contentType(path) },
4359
+ });
4360
+ };
4361
+ `;
4019
4362
  var mcpPageFile = (route) => `${trimChar(route, "/")}.ts`;
4020
4363
  var mcpEndpointTemplate = (route) => {
4021
4364
  const clean = trimChar(route, "/");
@@ -4062,8 +4405,9 @@ export function GET({ props }: { props: { section: string } }) {
4062
4405
  });
4063
4406
  }
4064
4407
  `;
4065
- var ogEndpointTemplate = (customRoutes = []) => `// Generated by Blume. Do not edit.
4408
+ var ogEndpointTemplate = (customRoutes = [], og = {}) => `// Generated by Blume. Do not edit.
4066
4409
  import { renderOgImage } from "blume/og";
4410
+ import type { OgFont, OgFontFamilies } from "blume/og";
4067
4411
  import data from "blume:data";
4068
4412
 
4069
4413
  export const prerender = true;
@@ -4073,6 +4417,13 @@ export const prerender = true;
4073
4417
  // (ts(7034)) under a strict tsconfig.
4074
4418
  const customRoutes: { slug: string; title: string }[] = ${JSON.stringify(customRoutes)};
4075
4419
 
4420
+ // Card fonts, resolved at generation time (explicit seo.og.fonts, or derived
4421
+ // from theme.fonts). Local entries carry absolute build-machine paths, which
4422
+ // is why they are baked into this build-only endpoint instead of the runtime
4423
+ // data that pages serialize into HTML.
4424
+ const fonts: OgFont[] = ${JSON.stringify(og.fonts ?? [])};
4425
+ const families: OgFontFamilies | undefined = ${og.families ? JSON.stringify(og.families) : "undefined"};
4426
+
4076
4427
  export function getStaticPaths() {
4077
4428
  const seen = new Set<string>();
4078
4429
  const paths: { params: { slug: string }; props: { title: string } }[] = [];
@@ -4093,32 +4444,23 @@ export function getStaticPaths() {
4093
4444
  return paths;
4094
4445
  }
4095
4446
 
4096
- // Footer branding shared by every card, derived once from the resolved config.
4097
- // The repo slug reuses the header link URL; the host comes from the site URL.
4447
+ // Footer branding shared by every card. The repo slug reuses the header link
4448
+ // URL; the site text (host plus deployment base) is resolved at generate time.
4098
4449
  const repoSlug = data.config.repoUrl
4099
4450
  ? data.config.repoUrl.split("github.com/")[1]
4100
4451
  : undefined;
4101
- const siteHost = (() => {
4102
- if (!data.config.site) {
4103
- return undefined;
4104
- }
4105
- try {
4106
- return new URL(data.config.site).host;
4107
- } catch {
4108
- return undefined;
4109
- }
4110
- })();
4111
4452
 
4112
4453
  export async function GET({ props }: { props: { title: string } }) {
4113
4454
  const png = await renderOgImage({
4114
4455
  accent: data.config.og.palette?.accent ?? data.config.theme.accent.light,
4115
4456
  brand: data.config.title,
4116
- description: data.config.description,
4117
- fonts: data.config.og.fonts,
4457
+ description: data.config.og.description,
4458
+ families,
4459
+ fonts,
4118
4460
  logo: data.config.og.logo,
4119
4461
  palette: data.config.og.palette,
4120
4462
  repo: repoSlug,
4121
- site: siteHost,
4463
+ site: data.config.og.site,
4122
4464
  title: props.title,
4123
4465
  });
4124
4466
  return new Response(new Uint8Array(png), {
@@ -5274,6 +5616,7 @@ var MODULES = [
5274
5616
  structuredDataChecks,
5275
5617
  urlChecks,
5276
5618
  networkChecks,
5619
+ dnsAidChecks,
5277
5620
  externalChecks
5278
5621
  ];
5279
5622
 
@@ -7436,7 +7779,7 @@ var uiStringsObject = z.object({
7436
7779
  generating: z.string().default("Generating…"),
7437
7780
  openInChat: z.string().default("Open in chat"),
7438
7781
  scrollToTop: z.string().default("Scroll to top")
7439
- }).default({}),
7782
+ }).prefault({}),
7440
7783
  ask: z.object({
7441
7784
  ai: z.string().default("AI"),
7442
7785
  clear: z.string().default("Clear conversation"),
@@ -7450,28 +7793,28 @@ var uiStringsObject = z.object({
7450
7793
  tip: z.string().default("Tip: You can open and close chat with"),
7451
7794
  title: z.string().default("Ask AI"),
7452
7795
  you: z.string().default("You")
7453
- }).default({}),
7796
+ }).prefault({}),
7454
7797
  banner: z.object({
7455
7798
  dismiss: z.string().default("Dismiss announcement")
7456
- }).default({}),
7799
+ }).prefault({}),
7457
7800
  changelog: z.object({
7458
7801
  description: z.string().default("Product updates, new features, and fixes from every release."),
7459
7802
  showReleases: z.string().default("Show {version} releases"),
7460
7803
  title: z.string().default("Changelog")
7461
- }).default({}),
7804
+ }).prefault({}),
7462
7805
  content: z.object({
7463
7806
  diagramError: z.string().default("Could not render this diagram.")
7464
- }).default({}),
7807
+ }).prefault({}),
7465
7808
  feedback: z.object({
7466
7809
  no: z.string().default("No"),
7467
7810
  question: z.string().default("Was this page helpful?"),
7468
7811
  thanks: z.string().default("Thanks for your feedback!"),
7469
7812
  yes: z.string().default("Yes")
7470
- }).default({}),
7813
+ }).prefault({}),
7471
7814
  languageSwitcher: z.object({
7472
7815
  label: z.string().default("Language"),
7473
7816
  untranslated: z.string().default("Not translated")
7474
- }).default({}),
7817
+ }).prefault({}),
7475
7818
  nav: z.object({
7476
7819
  back: z.string().default("Back"),
7477
7820
  breadcrumb: z.string().default("Breadcrumb"),
@@ -7484,19 +7827,19 @@ var uiStringsObject = z.object({
7484
7827
  sections: z.string().default("Sections"),
7485
7828
  toggleNavigation: z.string().default("Toggle navigation"),
7486
7829
  toggleTheme: z.string().default("Toggle color theme")
7487
- }).default({}),
7830
+ }).prefault({}),
7488
7831
  notFound: z.object({
7489
7832
  description: z.string().default("We couldn't find the page you're looking for."),
7490
7833
  home: z.string().default("Back to home"),
7491
7834
  title: z.string().default("Page not found")
7492
- }).default({}),
7835
+ }).prefault({}),
7493
7836
  page: z.object({
7494
7837
  lastUpdated: z.string().default("Last updated on"),
7495
7838
  next: z.string().default("Next"),
7496
7839
  pagination: z.string().default("Pagination"),
7497
7840
  previous: z.string().default("Previous"),
7498
7841
  skipToContent: z.string().default("Skip to content")
7499
- }).default({}),
7842
+ }).prefault({}),
7500
7843
  search: z.object({
7501
7844
  all: z.string().default("All"),
7502
7845
  allLanguages: z.string().default("All languages"),
@@ -7513,14 +7856,13 @@ var uiStringsObject = z.object({
7513
7856
  popular: z.string().default("Popular"),
7514
7857
  preview: z.string().default("preview"),
7515
7858
  results: z.string().default("Results")
7516
- }).default({}),
7859
+ }).prefault({}),
7517
7860
  toc: z.object({
7518
7861
  title: z.string().default("On this page")
7519
- }).default({})
7862
+ }).prefault({})
7520
7863
  });
7521
- var uiStringsSchema = uiStringsObject.default({});
7522
- var EN_UI_INPUT = Object.fromEntries(Object.keys(uiStringsObject.shape).map((group) => [group, {}]));
7523
- var EN_UI = uiStringsObject.parse(EN_UI_INPUT);
7864
+ var uiStringsSchema = uiStringsObject.prefault({});
7865
+ var EN_UI = uiStringsObject.parse({});
7524
7866
  var uiStringsOverrideSchema = z.record(z.string(), z.record(z.string(), z.string()));
7525
7867
  var uiLocaleOverridesSchema = z.record(z.string(), uiStringsOverrideSchema);
7526
7868
  var mergeUI = (base, override) => {
@@ -7570,7 +7912,7 @@ var sidebarMetaSchema = z2.strictObject({
7570
7912
  });
7571
7913
  var xHandleSchema = z2.string().transform(normalizeXHandle).optional();
7572
7914
  var seoMetaSchema = z2.strictObject({
7573
- canonical: z2.string().url().optional(),
7915
+ canonical: z2.url().optional(),
7574
7916
  description: z2.string().optional(),
7575
7917
  image: z2.string().optional(),
7576
7918
  noindex: z2.boolean().default(false),
@@ -7600,7 +7942,7 @@ var authorSchema = z2.union([
7600
7942
  }).catchall(z2.unknown())
7601
7943
  ]);
7602
7944
  var pageMetaBaseSchema = z2.strictObject({
7603
- ai: aiMetaSchema.default({}),
7945
+ ai: aiMetaSchema.prefault({}),
7604
7946
  authors: z2.union([authorSchema, z2.array(authorSchema)]).optional(),
7605
7947
  changelog: changelogMetaSchema.optional(),
7606
7948
  date: dateSchema.optional(),
@@ -7611,9 +7953,9 @@ var pageMetaBaseSchema = z2.strictObject({
7611
7953
  icon: iconName.optional(),
7612
7954
  lastModified: dateSchema.optional(),
7613
7955
  noindex: z2.boolean().default(false),
7614
- search: searchMetaSchema.default({}),
7615
- seo: seoMetaSchema.default({}),
7616
- sidebar: sidebarMetaSchema.default({}),
7956
+ search: searchMetaSchema.prefault({}),
7957
+ seo: seoMetaSchema.prefault({}),
7958
+ sidebar: sidebarMetaSchema.prefault({}),
7617
7959
  slug: z2.string().optional(),
7618
7960
  title: z2.string().optional(),
7619
7961
  type: z2.string().optional()
@@ -7773,11 +8115,31 @@ var sidebarItemSchema = z2.lazy(() => z2.union([
7773
8115
  root: z2.string().optional()
7774
8116
  })
7775
8117
  ]));
7776
- var fontSlug = z2.string().superRefine((value, ctx) => {
7777
- if (!isFontSlug(value)) {
8118
+ var fontFallbackSchema = z2.enum(["sans", "serif", "mono"]);
8119
+ var remoteFontSchema = z2.strictObject({
8120
+ fallback: fontFallbackSchema.optional(),
8121
+ name: z2.string().min(1),
8122
+ provider: z2.enum(["google", "fontsource", "bunny", "fontshare"]).default("google"),
8123
+ weights: z2.array(z2.union([z2.number().int().positive(), z2.string().regex(/^\d+\.\.\d+$/u)])).nonempty().optional()
8124
+ });
8125
+ var localFontVariantSchema = z2.strictObject({
8126
+ src: z2.string().min(1),
8127
+ style: z2.enum(["normal", "italic", "oblique"]).optional(),
8128
+ weight: z2.union([
8129
+ z2.number().int().positive(),
8130
+ z2.string().regex(/^\d+(?:\.\.\d+)?$/u)
8131
+ ]).optional()
8132
+ });
8133
+ var localFontSchema = z2.strictObject({
8134
+ fallback: fontFallbackSchema.optional(),
8135
+ name: z2.string().min(1),
8136
+ variants: z2.array(localFontVariantSchema).nonempty()
8137
+ });
8138
+ var fontValueSchema = z2.union([z2.string(), remoteFontSchema, localFontSchema]).superRefine((value, ctx) => {
8139
+ if (typeof value === "string" && !isFontSlug(value)) {
7778
8140
  ctx.addIssue({
7779
8141
  code: z2.ZodIssueCode.custom,
7780
- message: `Unknown font "${value}". Supported fonts: ${FONT_SLUGS.join(", ")}.`
8142
+ message: `Unknown font "${value}". Supported fonts: ${FONT_SLUGS.join(", ")}. For any other family, use the object form: { name: "..." } (remote provider) or { name: "...", variants: [...] } (local files).`
7781
8143
  });
7782
8144
  }
7783
8145
  });
@@ -7797,10 +8159,10 @@ var themeConfigSchema = z2.strictObject({
7797
8159
  background: perModeValueSchema,
7798
8160
  backgroundImage: perModeValueSchema,
7799
8161
  fonts: z2.strictObject({
7800
- body: fontSlug.default("inter"),
7801
- display: fontSlug.default("inter-tight"),
7802
- mono: fontSlug.default("ibm-plex-mono")
7803
- }).default({}),
8162
+ body: fontValueSchema.default("inter"),
8163
+ display: fontValueSchema.default("inter-tight"),
8164
+ mono: fontValueSchema.default("ibm-plex-mono")
8165
+ }).prefault({}),
7804
8166
  layout: z2.enum(["sidebar"]).default("sidebar"),
7805
8167
  mode: z2.enum(["system", "light", "dark"]).default("system"),
7806
8168
  radius: z2.enum(["none", "sm", "md", "lg"]).default("md")
@@ -7850,7 +8212,7 @@ var searchConfigSchema = z2.strictObject({
7850
8212
  algolia: algoliaSearchSchema.optional(),
7851
8213
  indexing: z2.strictObject({
7852
8214
  includeHiddenPages: z2.boolean().default(false)
7853
- }).default({}),
8215
+ }).prefault({}),
7854
8216
  mixedbread: mixedbreadSearchSchema.optional(),
7855
8217
  oramaCloud: oramaCloudSearchSchema.optional(),
7856
8218
  popular: z2.array(searchPopularLinkSchema).default([]),
@@ -7873,6 +8235,22 @@ var askAiProviders = [
7873
8235
  "inkeep",
7874
8236
  "openai-compatible"
7875
8237
  ];
8238
+ var PRIVATE_JWK_PARAMS = ["d", "p", "q", "dp", "dq", "qi", "oth", "k"];
8239
+ var publicJwkSchema = z2.record(z2.string(), z2.unknown()).superRefine((jwk, ctx) => {
8240
+ if (typeof jwk.kty !== "string" || jwk.kty.length === 0) {
8241
+ ctx.addIssue({
8242
+ code: z2.ZodIssueCode.custom,
8243
+ message: 'A JWK must declare its key type ("kty").'
8244
+ });
8245
+ }
8246
+ const leaked = PRIVATE_JWK_PARAMS.filter((param) => (param in jwk));
8247
+ if (leaked.length > 0) {
8248
+ ctx.addIssue({
8249
+ code: z2.ZodIssueCode.custom,
8250
+ message: `The JWK contains private key material ("${leaked.join('", "')}") — the signatures directory is public, so list only public keys and keep the private key where the signing agent runs.`
8251
+ });
8252
+ }
8253
+ });
7876
8254
  var mcpConfigSchema = z2.strictObject({
7877
8255
  enabled: z2.boolean().default(false),
7878
8256
  instructions: z2.string().optional(),
@@ -7895,7 +8273,7 @@ var askEndpointSchema = z2.string().trim().min(1).refine((value) => {
7895
8273
  var aiConfigSchema = z2.strictObject({
7896
8274
  ask: z2.strictObject({
7897
8275
  apiKeyEnv: z2.string().optional(),
7898
- baseUrl: z2.string().url().optional(),
8276
+ baseUrl: z2.url().optional(),
7899
8277
  enabled: z2.boolean().default(false),
7900
8278
  endpoint: askEndpointSchema.optional(),
7901
8279
  model: z2.string().default("openai/gpt-5.5"),
@@ -7923,7 +8301,12 @@ var aiConfigSchema = z2.strictObject({
7923
8301
  markdownComponents: z2.record(z2.string(), z2.custom((value) => typeof value === "function", {
7924
8302
  message: "Expected a serializer function."
7925
8303
  })).default({}),
7926
- mcp: mcpConfigSchema.default({})
8304
+ mcp: mcpConfigSchema.prefault({}),
8305
+ skills: z2.string().min(1).optional(),
8306
+ webBotAuth: z2.strictObject({
8307
+ keys: z2.array(publicJwkSchema).default([])
8308
+ }).prefault({}),
8309
+ webmcp: z2.boolean().default(true)
7927
8310
  });
7928
8311
  var featuredLinkSchema = z2.strictObject({
7929
8312
  href: z2.string(),
@@ -7940,7 +8323,7 @@ var navigationConfigSchema = z2.strictObject({
7940
8323
  display: sidebarDisplaySchema.default("flat"),
7941
8324
  items: z2.array(sidebarItemSchema).optional()
7942
8325
  })
7943
- ]).default({}).transform((value) => Array.isArray(value) ? { display: "flat", items: value } : value),
8326
+ ]).prefault({}).transform((value) => Array.isArray(value) ? { display: "flat", items: value } : value),
7944
8327
  tabs: z2.array(navTabSchema).default([])
7945
8328
  });
7946
8329
  var exportConfigSchema = z2.union([
@@ -7999,13 +8382,23 @@ var deploymentConfigSchema = z2.strictObject({
7999
8382
  adapter: z2.enum(["vercel", "node", "netlify", "cloudflare"]).nullable().default(null),
8000
8383
  base: z2.string().optional(),
8001
8384
  output: z2.enum(["static", "server"]).default("static"),
8002
- site: z2.string().url().optional()
8385
+ site: z2.url().optional()
8003
8386
  });
8004
8387
  var redirectSchema = z2.strictObject({
8005
8388
  from: z2.string(),
8006
8389
  status: z2.union([z2.literal(301), z2.literal(302), z2.literal(307), z2.literal(308)]).default(301),
8007
8390
  to: z2.string()
8008
8391
  });
8392
+ var imageRemotePatternSchema = z2.strictObject({
8393
+ hostname: z2.string().optional(),
8394
+ pathname: z2.string().optional(),
8395
+ port: z2.string().optional(),
8396
+ protocol: z2.string().optional()
8397
+ });
8398
+ var imageConfigSchema = z2.strictObject({
8399
+ domains: z2.array(z2.string()).default([]),
8400
+ remotePatterns: z2.array(imageRemotePatternSchema).default([])
8401
+ });
8009
8402
  var xConfigSchema = z2.strictObject({
8010
8403
  creator: xHandleSchema,
8011
8404
  handle: xHandleSchema
@@ -8030,13 +8423,21 @@ var ogFontSchema = z2.union([
8030
8423
  name: z2.string(),
8031
8424
  style: z2.union([ogFontStyleSchema, z2.array(ogFontStyleSchema)]).optional(),
8032
8425
  weight: ogFontWeightSchema.optional()
8426
+ }),
8427
+ z2.strictObject({
8428
+ name: z2.string(),
8429
+ src: z2.string().min(1),
8430
+ style: ogFontStyleSchema.optional(),
8431
+ weight: z2.number().int().positive().optional()
8033
8432
  })
8034
8433
  ]);
8035
8434
  var ogConfigSchema = z2.strictObject({
8435
+ description: z2.union([z2.string(), z2.literal(false)]).optional(),
8036
8436
  enabled: z2.boolean().optional(),
8037
8437
  fonts: z2.array(ogFontSchema).optional(),
8038
- logo: z2.string().optional(),
8438
+ logo: z2.union([z2.string(), z2.literal(false)]).optional(),
8039
8439
  palette: ogPaletteSchema.optional(),
8440
+ site: z2.union([z2.string(), z2.literal(false)]).optional(),
8040
8441
  titles: z2.record(z2.string(), z2.string()).optional()
8041
8442
  });
8042
8443
  var rssConfigSchema = z2.strictObject({
@@ -8060,10 +8461,10 @@ var contentSignalsSchema = z2.union([z2.boolean(), contentSignalsObjectSchema]).
8060
8461
  });
8061
8462
  var seoConfigSchema = z2.strictObject({
8062
8463
  agentReadability: z2.boolean().default(true),
8063
- contentSignals: contentSignalsSchema.default(true),
8464
+ contentSignals: contentSignalsSchema.prefault(true),
8064
8465
  og: ogConfigSchema.default({}),
8065
8466
  robots: z2.boolean().default(true),
8066
- rss: rssConfigSchema.default({}),
8467
+ rss: rssConfigSchema.prefault({}),
8067
8468
  sitemap: z2.boolean().default(true),
8068
8469
  structuredData: z2.boolean().default(true),
8069
8470
  x: xConfigSchema.default({})
@@ -8093,7 +8494,7 @@ var codeBlockThemeSchema = z2.strictObject({
8093
8494
  light: codeThemeSchema.default("github-light")
8094
8495
  });
8095
8496
  var codeBlocksConfigSchema = z2.strictObject({
8096
- theme: codeBlockThemeSchema.default({})
8497
+ theme: codeBlockThemeSchema.prefault({})
8097
8498
  });
8098
8499
  var examplesConfigSchema = z2.union([
8099
8500
  z2.string(),
@@ -8124,8 +8525,8 @@ var codeConfigSchema = z2.strictObject({
8124
8525
  wrap: z2.boolean().default(false)
8125
8526
  });
8126
8527
  var markdownConfigSchema = z2.strictObject({
8127
- code: codeConfigSchema.default({}),
8128
- codeBlocks: codeBlocksConfigSchema.default({}),
8528
+ code: codeConfigSchema.prefault({}),
8529
+ codeBlocks: codeBlocksConfigSchema.prefault({}),
8129
8530
  headingAnchors: z2.boolean().default(true),
8130
8531
  imageZoom: z2.boolean().default(true)
8131
8532
  });
@@ -8194,32 +8595,33 @@ var tocConfigSchema = z2.union([
8194
8595
  message: "toc.minHeadingLevel must be less than or equal to toc.maxHeadingLevel."
8195
8596
  });
8196
8597
  var blumeConfigSchema = z2.strictObject({
8197
- ai: aiConfigSchema.default({}),
8598
+ ai: aiConfigSchema.prefault({}),
8198
8599
  analytics: analyticsConfigSchema.optional(),
8199
- asyncapi: asyncapiConfigSchema.default({}),
8600
+ asyncapi: asyncapiConfigSchema.prefault({}),
8200
8601
  banner: bannerConfigSchema.optional(),
8201
8602
  basePath: z2.string().optional().transform((value) => normalizeBasePath(value)),
8202
- content: contentConfigSchema.default({}),
8603
+ content: contentConfigSchema.prefault({}),
8203
8604
  dateFormat: dateFormatConfigSchema.default({ dateStyle: "long" }),
8204
- deployment: deploymentConfigSchema.default({}),
8605
+ deployment: deploymentConfigSchema.prefault({}),
8205
8606
  description: z2.string().optional(),
8206
- examples: examplesConfigSchema.default("examples"),
8207
- export: exportConfigSchema.default(false),
8607
+ examples: examplesConfigSchema.prefault("examples"),
8608
+ export: exportConfigSchema.prefault(false),
8208
8609
  feedback: z2.boolean().default(true),
8209
- frontmatter: frontmatterConfigSchema.default({}),
8610
+ frontmatter: frontmatterConfigSchema.prefault({}),
8210
8611
  github: githubConfigSchema.optional(),
8211
8612
  i18n: i18nConfigSchema.optional(),
8613
+ image: imageConfigSchema.prefault({}),
8212
8614
  integrations: z2.array(z2.custom()).default([]),
8213
8615
  lastModified: lastModifiedConfigSchema.default(false),
8214
8616
  logo: logoConfigSchema.optional(),
8215
- markdown: markdownConfigSchema.default({}),
8216
- navigation: navigationConfigSchema.default({}),
8217
- openapi: openapiConfigSchema.default({}),
8218
- react: reactConfigSchema.default({}),
8617
+ markdown: markdownConfigSchema.prefault({}),
8618
+ navigation: navigationConfigSchema.prefault({}),
8619
+ openapi: openapiConfigSchema.prefault({}),
8620
+ react: reactConfigSchema.prefault({}),
8219
8621
  redirects: z2.array(redirectSchema).default([]),
8220
- search: searchConfigSchema.default({}),
8221
- seo: seoConfigSchema.default({}),
8222
- theme: themeConfigSchema.default({}),
8622
+ search: searchConfigSchema.prefault({}),
8623
+ seo: seoConfigSchema.prefault({}),
8624
+ theme: themeConfigSchema.prefault({}),
8223
8625
  title: z2.string().default("Documentation"),
8224
8626
  toc: tocConfigSchema
8225
8627
  });
@@ -8241,6 +8643,7 @@ var loadConfig = async (root, options = {}) => {
8241
8643
  });
8242
8644
  }
8243
8645
  }
8646
+ const themeFontsConfigured = Boolean(raw && typeof raw === "object" && raw.theme?.fonts !== undefined);
8244
8647
  const parsed = blumeConfigSchema.safeParse(raw ?? {});
8245
8648
  if (!parsed.success) {
8246
8649
  const source = configFile && existsSync6(configFile) ? readFileSync3(configFile, "utf-8") : undefined;
@@ -8275,7 +8678,8 @@ ${moreIssues}`
8275
8678
  seo: { ...config.seo, og: { ...config.seo.og, enabled: ogEnabled } }
8276
8679
  },
8277
8680
  configFile,
8278
- diagnostics: []
8681
+ diagnostics: [],
8682
+ themeFontsConfigured
8279
8683
  };
8280
8684
  };
8281
8685
 
@@ -11396,7 +11800,8 @@ var scanProject = async (root, options = {}) => {
11396
11800
  graph,
11397
11801
  manifest,
11398
11802
  mode,
11399
- sources
11803
+ sources,
11804
+ themeFontsConfigured: configResult.themeFontsConfigured
11400
11805
  };
11401
11806
  };
11402
11807
 
@@ -11580,11 +11985,11 @@ var auditCommand = defineCommand2({
11580
11985
  });
11581
11986
 
11582
11987
  // src/cli/commands/build.ts
11583
- import { existsSync as existsSync16 } from "node:fs";
11584
- import { readdir, stat as stat2, writeFile as writeFile8 } from "node:fs/promises";
11988
+ import { existsSync as existsSync18 } from "node:fs";
11989
+ import { mkdir as mkdir7, readdir as readdir2, readFile as readFile17, stat as stat3, writeFile as writeFile8 } from "node:fs/promises";
11585
11990
  import { build } from "astro";
11586
11991
  import { defineCommand as defineCommand3 } from "citty";
11587
- import { join as join28 } from "pathe";
11992
+ import { dirname as dirname12, join as join30, resolve as resolve10 } from "pathe";
11588
11993
 
11589
11994
  // src/deploy/xml.ts
11590
11995
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
@@ -11688,6 +12093,19 @@ var askApiUrl = (endpoint, site, abs) => {
11688
12093
  }
11689
12094
  return site && endpoint.startsWith("/") ? `${site.replace(/\/+$/u, "")}${endpoint}` : endpoint;
11690
12095
  };
12096
+ var wellKnownArtifacts = (config, abs) => {
12097
+ const artifacts = {};
12098
+ if (config.ai.webBotAuth.keys.length > 0) {
12099
+ artifacts.httpMessageSignaturesDirectory = abs("/.well-known/http-message-signatures-directory");
12100
+ }
12101
+ if (hasApiCatalog(config)) {
12102
+ artifacts.apiCatalog = abs("/.well-known/api-catalog");
12103
+ }
12104
+ if (config.ai.skills) {
12105
+ artifacts.agentSkills = abs("/.well-known/agent-skills/index.json");
12106
+ }
12107
+ return artifacts;
12108
+ };
11691
12109
  var buildAgentReadability = (project) => {
11692
12110
  const { config } = project;
11693
12111
  if (!config.seo.agentReadability) {
@@ -11699,9 +12117,10 @@ var buildAgentReadability = (project) => {
11699
12117
  const based = withBasePath(deployBase, path);
11700
12118
  return site ? `${site.replace(/\/+$/u, "")}${based}` : based;
11701
12119
  };
12120
+ const negotiates = config.deployment.output === "server" && config.deployment.adapter === "vercel";
11702
12121
  const artifacts = {
11703
12122
  markdown: {
11704
- contentNegotiation: "text/markdown",
12123
+ ...negotiates ? { contentNegotiation: "text/markdown" } : {},
11705
12124
  pattern: abs("/{route}.md")
11706
12125
  }
11707
12126
  };
@@ -11718,6 +12137,7 @@ var buildAgentReadability = (project) => {
11718
12137
  if (config.ai.ask?.enabled) {
11719
12138
  artifacts.askApi = askApiUrl(config.ai.ask.endpoint, site, abs);
11720
12139
  }
12140
+ Object.assign(artifacts, wellKnownArtifacts(config, abs));
11721
12141
  if (site && config.seo.sitemap) {
11722
12142
  artifacts.sitemap = abs("/sitemap.xml");
11723
12143
  }
@@ -11743,8 +12163,113 @@ var buildAgentReadability = (project) => {
11743
12163
  return manifest;
11744
12164
  };
11745
12165
 
11746
- // src/core/sources/read.ts
12166
+ // src/core/content-assets.ts
12167
+ import { existsSync as existsSync9 } from "node:fs";
11747
12168
  import { readFile as readFile8 } from "node:fs/promises";
12169
+ import { dirname as dirname7, extname as extname5, relative as relative11, resolve as resolve5 } from "pathe";
12170
+ var CONTENT_ASSETS_PREFIX = "/blume-assets/content";
12171
+ var IMAGE_EXTENSIONS = new Set([
12172
+ ".apng",
12173
+ ".avif",
12174
+ ".bmp",
12175
+ ".gif",
12176
+ ".ico",
12177
+ ".jpeg",
12178
+ ".jpg",
12179
+ ".png",
12180
+ ".svg",
12181
+ ".tiff",
12182
+ ".webp"
12183
+ ]);
12184
+ var isRelativeTarget = (target) => !(target.startsWith("/") || target.startsWith("#")) && !URL.canParse(target);
12185
+ var contentAssetParam = (projectRoot, absPath) => {
12186
+ const rel = relative11(projectRoot, absPath);
12187
+ if (rel.startsWith("..")) {
12188
+ return `_/${hashText(absPath)}${extname5(absPath)}`;
12189
+ }
12190
+ return rel;
12191
+ };
12192
+ var resolveRelativeImage = (sourceDir, target) => {
12193
+ if (!isRelativeTarget(target)) {
12194
+ return null;
12195
+ }
12196
+ let decoded = target;
12197
+ try {
12198
+ decoded = decodeURI(target);
12199
+ } catch {}
12200
+ if (!IMAGE_EXTENSIONS.has(extname5(decoded).toLowerCase())) {
12201
+ return null;
12202
+ }
12203
+ const abs = resolve5(sourceDir, decoded);
12204
+ return existsSync9(abs) ? abs : null;
12205
+ };
12206
+ var encodedParam = (param) => param.split("/").map(encodeURIComponent).join("/");
12207
+ var rewriteLine = (line, toUrl2) => {
12208
+ const masked = line.replaceAll(INLINE_CODE, (span) => " ".repeat(span.length));
12209
+ let out = "";
12210
+ let cursor = 0;
12211
+ for (const match of masked.matchAll(MD_IMAGE)) {
12212
+ const target = match.groups?.target ?? "";
12213
+ const url = toUrl2(target);
12214
+ if (url === null) {
12215
+ continue;
12216
+ }
12217
+ const offset = (match.index ?? 0) + targetOffsetIn(match[0], target, match.groups?.title);
12218
+ out += line.slice(cursor, offset) + url;
12219
+ cursor = offset + target.length;
12220
+ }
12221
+ return out + line.slice(cursor);
12222
+ };
12223
+ var rewriteRelativeImages = (options) => {
12224
+ const { source, sourcePath, projectRoot, deployBase, register } = options;
12225
+ const sourceDir = dirname7(sourcePath);
12226
+ const prefix = `${normalizeBasePath(deployBase)}${CONTENT_ASSETS_PREFIX}`;
12227
+ const toUrl2 = (target) => {
12228
+ const abs = resolveRelativeImage(sourceDir, target);
12229
+ if (abs === null) {
12230
+ return null;
12231
+ }
12232
+ const param = contentAssetParam(projectRoot, abs);
12233
+ register?.(param, abs);
12234
+ return `${prefix}/${encodedParam(param)}`;
12235
+ };
12236
+ let fence = null;
12237
+ const lines = source.split(`
12238
+ `).map((line) => {
12239
+ const next = nextFenceState(line, fence);
12240
+ const inFence = fence !== null || next !== null;
12241
+ fence = next;
12242
+ return inFence ? line : rewriteLine(line, toUrl2);
12243
+ });
12244
+ return lines.join(`
12245
+ `);
12246
+ };
12247
+ var collectContentAssets = async (project) => {
12248
+ const files = {};
12249
+ await Promise.all(project.manifest.routes.map(async (route) => {
12250
+ if (!route.sourcePath) {
12251
+ return;
12252
+ }
12253
+ let source;
12254
+ try {
12255
+ source = await readFile8(route.sourcePath, "utf-8");
12256
+ } catch {
12257
+ return;
12258
+ }
12259
+ rewriteRelativeImages({
12260
+ projectRoot: project.context.root,
12261
+ register: (param, abs) => {
12262
+ files[param] = abs;
12263
+ },
12264
+ source,
12265
+ sourcePath: route.sourcePath
12266
+ });
12267
+ }));
12268
+ return files;
12269
+ };
12270
+
12271
+ // src/core/sources/read.ts
12272
+ import { readFile as readFile9 } from "node:fs/promises";
11748
12273
  var readEntryText = async (ctx, page) => {
11749
12274
  if (page.body) {
11750
12275
  return page.body.text;
@@ -11755,7 +12280,7 @@ var readEntryText = async (ctx, page) => {
11755
12280
  return await source.read(page.source.ref);
11756
12281
  }
11757
12282
  if (page.sourcePath) {
11758
- return await readFile8(page.sourcePath, "utf-8");
12283
+ return await readFile9(page.sourcePath, "utf-8");
11759
12284
  }
11760
12285
  return "";
11761
12286
  };
@@ -12073,7 +12598,7 @@ var indexedNavigations = (project) => {
12073
12598
  }
12074
12599
  return [{ nav: project.graph.navigation }];
12075
12600
  };
12076
- var buildIndex = (project) => {
12601
+ var buildLlmsIndex = (project) => {
12077
12602
  const { config } = project;
12078
12603
  const { site } = config.deployment;
12079
12604
  const base = normalizeBasePath(config.deployment.base);
@@ -12126,67 +12651,304 @@ var buildIndex = (project) => {
12126
12651
  }
12127
12652
  continue;
12128
12653
  }
12129
- blocks.push(...renderNav(nav, 2));
12130
- }
12131
- const leftover = eligible.filter((page) => !seen.has(page.route)).toSorted((a, b) => a.route.localeCompare(b.route));
12132
- if (leftover.length > 0) {
12133
- blocks.push(blocks.length > 0 ? "## Other" : "## Docs", leftover.map(line).join(`
12134
- `));
12135
- }
12136
- const feeds = buildRssFeeds(project);
12137
- if (feeds.length > 0) {
12138
- blocks.push("## RSS Feeds", feeds.map((feed) => `- [${feed.title}](${pageUrl(feed.path, site, base)})`).join(`
12139
- `));
12654
+ blocks.push(...renderNav(nav, 2));
12655
+ }
12656
+ const leftover = eligible.filter((page) => !seen.has(page.route)).toSorted((a, b) => a.route.localeCompare(b.route));
12657
+ if (leftover.length > 0) {
12658
+ blocks.push(blocks.length > 0 ? "## Other" : "## Docs", leftover.map(line).join(`
12659
+ `));
12660
+ }
12661
+ const feeds = buildRssFeeds(project);
12662
+ if (feeds.length > 0) {
12663
+ blocks.push("## RSS Feeds", feeds.map((feed) => `- [${feed.title}](${pageUrl(feed.path, site, base)})`).join(`
12664
+ `));
12665
+ }
12666
+ const header = config.description ? `# ${config.title}
12667
+
12668
+ > ${config.description}` : `# ${config.title}`;
12669
+ return `${[header, ...blocks].join(`
12670
+
12671
+ `)}
12672
+ `;
12673
+ };
12674
+ var buildFull = async (project) => {
12675
+ const { config } = project;
12676
+ const pages = eligiblePages(project).toSorted((a, b) => a.route.localeCompare(b.route));
12677
+ const components = {
12678
+ ...exampleComponentSerializers(project.examples ?? {}),
12679
+ ...config.ai.markdownComponents
12680
+ };
12681
+ const sections = await Promise.all(pages.map(async (page) => {
12682
+ let raw = await readEntryText(project, page);
12683
+ if (page.sourcePath) {
12684
+ raw = rewriteRelativeImages({
12685
+ deployBase: config.deployment.base,
12686
+ projectRoot: project.context.root,
12687
+ source: raw,
12688
+ sourcePath: page.sourcePath
12689
+ });
12690
+ }
12691
+ const parsed = frontmatter_default(raw);
12692
+ const body = downlevelComponents(applyAgentVisibility(parsed.content), components, parsed.data).trim();
12693
+ const url = pageUrl(page.route, config.deployment.site, normalizeBasePath(config.deployment.base));
12694
+ return [`# ${page.title}`, `Source: ${url}`, "", body].join(`
12695
+ `);
12696
+ }));
12697
+ const header = config.description ? `# ${config.title}
12698
+
12699
+ > ${config.description}
12700
+ ` : `# ${config.title}
12701
+ `;
12702
+ return `${header}
12703
+ ${sections.join(`
12704
+
12705
+ ---
12706
+
12707
+ `)}
12708
+ `;
12709
+ };
12710
+ var buildLlmsFiles = async (project) => ({
12711
+ full: await buildFull(project),
12712
+ index: buildLlmsIndex(project)
12713
+ });
12714
+
12715
+ // src/ai/markdown.ts
12716
+ import { readFile as readFile10 } from "node:fs/promises";
12717
+ var agentMarkdown = (entry) => entry.md ?? entry.mdx;
12718
+ var markdownTokenCount = (text) => Math.ceil(text.length / 4);
12719
+ var buildRawMarkdown = async (project) => {
12720
+ const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));
12721
+ const components = {
12722
+ ...exampleComponentSerializers(project.examples ?? {}),
12723
+ ...project.config.ai.markdownComponents
12724
+ };
12725
+ const readRoute = async (route) => {
12726
+ const page = pageById.get(route.id);
12727
+ if (page) {
12728
+ return await readEntryText(project, page);
12729
+ }
12730
+ return route.sourcePath ? await readFile10(route.sourcePath, "utf-8") : "";
12731
+ };
12732
+ const entries = await Promise.all(project.manifest.routes.map(async (route) => {
12733
+ let text = await readRoute(route);
12734
+ if (route.sourcePath) {
12735
+ text = rewriteRelativeImages({
12736
+ deployBase: project.config.deployment.base,
12737
+ projectRoot: project.context.root,
12738
+ source: text,
12739
+ sourcePath: route.sourcePath
12740
+ });
12741
+ }
12742
+ const source = applyAgentVisibility(text);
12743
+ const md = downlevelComponents(source, components, frontmatter_default(source).data);
12744
+ const entry = md === source ? { mdx: source } : { md, mdx: source };
12745
+ return [route.path, entry];
12746
+ }));
12747
+ const map = Object.fromEntries(entries);
12748
+ if (!map["/"]) {
12749
+ map["/"] = { mdx: buildLlmsIndex(project) };
12750
+ }
12751
+ return map;
12752
+ };
12753
+ var markdownRoutePaths = (project) => {
12754
+ const paths = project.manifest.routes.map((route) => route.path);
12755
+ return paths.includes("/") ? paths : [...paths, "/"];
12756
+ };
12757
+
12758
+ // src/ai/skills.ts
12759
+ import { createHash as createHash2 } from "node:crypto";
12760
+ import { readdir, readFile as readFile11, stat as stat2 } from "node:fs/promises";
12761
+ import { join as join18 } from "pathe";
12762
+
12763
+ // src/ai/tar.ts
12764
+ import { gzipSync } from "node:zlib";
12765
+ var BLOCK = 512;
12766
+ var NAME_MAX = 100;
12767
+ var encoder = new TextEncoder;
12768
+ var octal = (header, offset, length, value) => {
12769
+ const text = value.toString(8).padStart(length - 1, "0");
12770
+ header.set(encoder.encode(text), offset);
12771
+ header[offset + length - 1] = 0;
12772
+ };
12773
+ var text = (header, offset, value) => {
12774
+ header.set(encoder.encode(value), offset);
12775
+ };
12776
+ var fileHeader = (entry) => {
12777
+ const header = new Uint8Array(BLOCK);
12778
+ text(header, 0, entry.path);
12779
+ octal(header, 100, 8, entry.executable ? 493 : 420);
12780
+ octal(header, 108, 8, 0);
12781
+ octal(header, 116, 8, 0);
12782
+ octal(header, 124, 12, entry.content.byteLength);
12783
+ octal(header, 136, 12, 0);
12784
+ text(header, 156, "0");
12785
+ text(header, 257, "ustar");
12786
+ header[262] = 0;
12787
+ text(header, 263, "00");
12788
+ octal(header, 329, 8, 0);
12789
+ octal(header, 337, 8, 0);
12790
+ header.fill(32, 148, 156);
12791
+ let sum = 0;
12792
+ for (const byte of header) {
12793
+ sum += byte;
12794
+ }
12795
+ text(header, 148, sum.toString(8).padStart(6, "0"));
12796
+ header[154] = 0;
12797
+ header[155] = 32;
12798
+ return header;
12799
+ };
12800
+ var buildTarGz = (entries) => {
12801
+ const blocks = [];
12802
+ for (const entry of entries) {
12803
+ if (encoder.encode(entry.path).byteLength > NAME_MAX) {
12804
+ throw new Error(`tar path exceeds ${NAME_MAX} bytes: ${entry.path}`);
12805
+ }
12806
+ if (entry.path.startsWith("/") || entry.path.split("/").includes("..")) {
12807
+ throw new Error(`tar path must be archive-relative: ${entry.path}`);
12808
+ }
12809
+ blocks.push(fileHeader(entry), entry.content);
12810
+ const overhang = entry.content.byteLength % BLOCK;
12811
+ if (overhang > 0) {
12812
+ blocks.push(new Uint8Array(BLOCK - overhang));
12813
+ }
12814
+ }
12815
+ blocks.push(new Uint8Array(BLOCK * 2));
12816
+ const total = blocks.reduce((sum, block) => sum + block.byteLength, 0);
12817
+ const tar = new Uint8Array(total);
12818
+ let offset = 0;
12819
+ for (const block of blocks) {
12820
+ tar.set(block, offset);
12821
+ offset += block.byteLength;
12822
+ }
12823
+ return new Uint8Array(gzipSync(tar, { level: 9 }));
12824
+ };
12825
+
12826
+ // src/ai/skills.ts
12827
+ var AGENT_SKILLS_DIR = "/.well-known/agent-skills";
12828
+ var AGENT_SKILLS_SCHEMA = "https://schemas.agentskills.io/discovery/0.2.0/schema.json";
12829
+ var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
12830
+ var SKILL_NAME_MAX = 64;
12831
+ var DESCRIPTION_MAX2 = 1024;
12832
+ var sha256 = (content) => `sha256:${createHash2("sha256").update(content).digest("hex")}`;
12833
+ var collectEntries = async (dir, prefix = "") => {
12834
+ const entries = [];
12835
+ const items = await readdir(dir, { withFileTypes: true });
12836
+ for (const item of items.toSorted((a, b) => a.name < b.name ? -1 : 1)) {
12837
+ if (item.name.startsWith(".")) {
12838
+ continue;
12839
+ }
12840
+ const path = join18(dir, item.name);
12841
+ const relative12 = prefix ? `${prefix}/${item.name}` : item.name;
12842
+ if (item.isDirectory()) {
12843
+ entries.push(...await collectEntries(path, relative12));
12844
+ } else if (item.isFile()) {
12845
+ const [content, info] = await Promise.all([readFile11(path), stat2(path)]);
12846
+ entries.push({
12847
+ content: new Uint8Array(content),
12848
+ executable: (info.mode & 64) !== 0,
12849
+ path: relative12
12850
+ });
12851
+ }
12852
+ }
12853
+ return entries;
12854
+ };
12855
+ var skillMeta = (raw, dirName) => {
12856
+ let data;
12857
+ try {
12858
+ ({ data } = frontmatter_default(raw));
12859
+ } catch {
12860
+ return {
12861
+ meta: null,
12862
+ warning: `Skill "${dirName}" has unparsable SKILL.md frontmatter; skipped.`
12863
+ };
12864
+ }
12865
+ const name = typeof data.name === "string" ? data.name : "";
12866
+ const description = typeof data.description === "string" ? data.description : "";
12867
+ if (!(name && description)) {
12868
+ return {
12869
+ meta: null,
12870
+ warning: `Skill "${dirName}" is missing the required "name"/"description" frontmatter; skipped.`
12871
+ };
12872
+ }
12873
+ if (!SKILL_NAME.test(name) || name.length > SKILL_NAME_MAX) {
12874
+ return {
12875
+ meta: null,
12876
+ warning: `Skill "${dirName}" has an invalid name "${name}" (lowercase alphanumerics and single hyphens, max 64 chars); skipped.`
12877
+ };
12878
+ }
12879
+ return {
12880
+ meta: { description: description.slice(0, DESCRIPTION_MAX2), name }
12881
+ };
12882
+ };
12883
+ var collectSkills = async (dir) => {
12884
+ const skills = [];
12885
+ const warnings = [];
12886
+ const items = await readdir(dir, { withFileTypes: true });
12887
+ for (const item of items.toSorted((a, b) => a.name < b.name ? -1 : 1)) {
12888
+ if (!item.isDirectory() || item.name.startsWith(".")) {
12889
+ continue;
12890
+ }
12891
+ const entries = await collectEntries(join18(dir, item.name));
12892
+ const skillMd = entries.find((entry) => entry.path === "SKILL.md");
12893
+ if (!skillMd) {
12894
+ continue;
12895
+ }
12896
+ const { meta, warning } = skillMeta(new TextDecoder().decode(skillMd.content), item.name);
12897
+ if (!meta) {
12898
+ if (warning) {
12899
+ warnings.push(warning);
12900
+ }
12901
+ continue;
12902
+ }
12903
+ const single = entries.length === 1;
12904
+ const content = single ? skillMd.content : buildTarGz(entries);
12905
+ skills.push({
12906
+ content,
12907
+ description: meta.description,
12908
+ digest: sha256(content),
12909
+ name: meta.name,
12910
+ path: single ? `${meta.name}/SKILL.md` : `${meta.name}.tar.gz`,
12911
+ type: single ? "skill-md" : "archive"
12912
+ });
12140
12913
  }
12141
- const header = config.description ? `# ${config.title}
12142
-
12143
- > ${config.description}` : `# ${config.title}`;
12144
- return `${[header, ...blocks].join(`
12145
-
12146
- `)}
12147
- `;
12914
+ return { skills, warnings };
12148
12915
  };
12149
- var buildFull = async (project) => {
12150
- const { config } = project;
12151
- const pages = eligiblePages(project).toSorted((a, b) => a.route.localeCompare(b.route));
12152
- const components = {
12153
- ...exampleComponentSerializers(project.examples ?? {}),
12154
- ...config.ai.markdownComponents
12916
+ var buildSkillsIndex = (skills, config) => {
12917
+ const deployBase = normalizeBasePath(config.deployment.base);
12918
+ const index = {
12919
+ $schema: AGENT_SKILLS_SCHEMA,
12920
+ skills: skills.map((skill) => ({
12921
+ description: skill.description,
12922
+ digest: skill.digest,
12923
+ name: skill.name,
12924
+ type: skill.type,
12925
+ url: `${deployBase}${AGENT_SKILLS_DIR}/${skill.path}`
12926
+ }))
12155
12927
  };
12156
- const sections = await Promise.all(pages.map(async (page) => {
12157
- const raw = await readEntryText(project, page);
12158
- const parsed = frontmatter_default(raw);
12159
- const body = downlevelComponents(applyAgentVisibility(parsed.content), components, parsed.data).trim();
12160
- const url = pageUrl(page.route, config.deployment.site, normalizeBasePath(config.deployment.base));
12161
- return [`# ${page.title}`, `Source: ${url}`, "", body].join(`
12162
- `);
12163
- }));
12164
- const header = config.description ? `# ${config.title}
12165
-
12166
- > ${config.description}
12167
- ` : `# ${config.title}
12928
+ return `${JSON.stringify(index, null, 2)}
12168
12929
  `;
12169
- return `${header}
12170
- ${sections.join(`
12171
-
12172
- ---
12930
+ };
12173
12931
 
12174
- `)}
12932
+ // src/ai/web-bot-auth.ts
12933
+ var SIGNATURES_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
12934
+ var SIGNATURES_DIRECTORY_TYPE = "application/http-message-signatures-directory+json";
12935
+ var buildSignaturesDirectory = (config) => {
12936
+ const { keys } = config.ai.webBotAuth;
12937
+ if (keys.length === 0) {
12938
+ return null;
12939
+ }
12940
+ return `${JSON.stringify({ keys }, null, 2)}
12175
12941
  `;
12176
12942
  };
12177
- var buildLlmsFiles = async (project) => ({
12178
- full: await buildFull(project),
12179
- index: buildIndex(project)
12180
- });
12181
12943
 
12182
12944
  // src/core/gitignore.ts
12183
- import { existsSync as existsSync9 } from "node:fs";
12184
- import { readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
12185
- import { join as join18 } from "pathe";
12945
+ import { existsSync as existsSync10 } from "node:fs";
12946
+ import { readFile as readFile12, writeFile as writeFile6 } from "node:fs/promises";
12947
+ import { join as join19 } from "pathe";
12186
12948
  var gitignoreKey = (line) => line.trim().replace(/\/+$/u, "");
12187
12949
  var ensureGitignore = async (root, entries) => {
12188
- const path = join18(root, ".gitignore");
12189
- const existing = existsSync9(path) ? await readFile9(path, "utf-8") : "";
12950
+ const path = join19(root, ".gitignore");
12951
+ const existing = existsSync10(path) ? await readFile12(path, "utf-8") : "";
12190
12952
  const present = new Set(existing.split(`
12191
12953
  `).flatMap((line) => {
12192
12954
  const key = gitignoreKey(line);
@@ -12238,13 +13000,31 @@ var HEADER_RULES = [
12238
13000
  underBasePath: false
12239
13001
  }
12240
13002
  ];
12241
- var buildNetlifyHeaders = (config) => {
13003
+ var buildNetlifyHeaders = (config, homeLinkHeader) => {
12242
13004
  const deployBase = normalizeBasePath(config.deployment.base);
12243
- return `${HEADER_RULES.map((rule) => {
13005
+ const rules = HEADER_RULES.map((rule) => {
12244
13006
  const prefix = rule.underBasePath ? `${deployBase}${config.basePath}` : deployBase;
12245
13007
  return `${prefix}/*.${rule.ext}
12246
13008
  Content-Type: ${rule.contentType}`;
12247
- }).join(`
13009
+ });
13010
+ if (homeLinkHeader) {
13011
+ rules.push(`${deployBase}/
13012
+ Link: ${homeLinkHeader}`);
13013
+ }
13014
+ if (hasApiCatalog(config)) {
13015
+ rules.push(`${deployBase}${API_CATALOG_PATH}
13016
+ Content-Type: ${API_CATALOG_TYPE}`);
13017
+ }
13018
+ if (config.ai.webBotAuth.keys.length > 0) {
13019
+ rules.push(`${deployBase}${SIGNATURES_DIRECTORY_PATH}
13020
+ Content-Type: ${SIGNATURES_DIRECTORY_TYPE}`);
13021
+ }
13022
+ if (config.ai.skills) {
13023
+ rules.push(`${deployBase}/.well-known/agent-skills/*.md
13024
+ Content-Type: text/markdown; charset=utf-8`, `${deployBase}/.well-known/agent-skills/*.tar.gz
13025
+ Content-Type: application/gzip`);
13026
+ }
13027
+ return `${rules.join(`
12248
13028
  `)}
12249
13029
  `;
12250
13030
  };
@@ -12284,7 +13064,7 @@ var buildRobots = (project) => {
12284
13064
  };
12285
13065
 
12286
13066
  // src/astro/pages.ts
12287
- import { extname as extname5, relative as relative11 } from "pathe";
13067
+ import { extname as extname6, relative as relative12 } from "pathe";
12288
13068
  import { glob as glob4, globSync } from "tinyglobby";
12289
13069
  var PAGE_GLOB = ["**/*.astro"];
12290
13070
  var isPrivatePage = (rel) => rel.split("/").some((segment) => segment.startsWith("_"));
@@ -12292,11 +13072,11 @@ var toPageRoutes = (pagesRoot, files) => {
12292
13072
  files.sort();
12293
13073
  const routes = [];
12294
13074
  for (const file of files) {
12295
- const rel = relative11(pagesRoot, file);
13075
+ const rel = relative12(pagesRoot, file);
12296
13076
  if (isPrivatePage(rel)) {
12297
13077
  continue;
12298
13078
  }
12299
- const withoutExt = rel.slice(0, rel.length - extname5(rel).length);
13079
+ const withoutExt = rel.slice(0, rel.length - extname6(rel).length);
12300
13080
  const parts = withoutExt.split("/");
12301
13081
  if (parts.at(-1) === "index") {
12302
13082
  parts.pop();
@@ -12408,8 +13188,101 @@ ${urls.join(`
12408
13188
  `;
12409
13189
  };
12410
13190
 
13191
+ // src/deploy/vercel-negotiation.ts
13192
+ var ACCEPT_MARKDOWN_HEADER_VALUE = "(.*,)?\\s*text/(x-)?markdown(\\s*[;,].*)?$";
13193
+ var ACCEPT_MARKDOWN_CONDITION = [
13194
+ { key: "accept", type: "header", value: ACCEPT_MARKDOWN_HEADER_VALUE }
13195
+ ];
13196
+ var VARY_ACCEPT = { vary: "Accept" };
13197
+ var MAX_ALTERNATION_LENGTH = 3900;
13198
+ var REGEX_SPECIALS = /[$()*+.?[\]^{|}\\]/gu;
13199
+ var routePattern = (route) => encodeURI(route).replace(REGEX_SPECIALS, "\\$&");
13200
+ var chunkPatterns = (patterns) => {
13201
+ const chunks = [];
13202
+ let current = [];
13203
+ let length = 0;
13204
+ for (const pattern of patterns) {
13205
+ if (current.length > 0 && length + pattern.length + 1 > MAX_ALTERNATION_LENGTH) {
13206
+ chunks.push(current);
13207
+ current = [];
13208
+ length = 0;
13209
+ }
13210
+ current.push(pattern);
13211
+ length += pattern.length + 1;
13212
+ }
13213
+ if (current.length > 0) {
13214
+ chunks.push(current);
13215
+ }
13216
+ return chunks;
13217
+ };
13218
+ var buildNegotiationRoutes = (routePaths, homeTokens) => {
13219
+ const home = routePaths.includes("/");
13220
+ const rest = routePaths.filter((path) => path !== "/").map((path) => routePattern(path));
13221
+ const chunks = chunkPatterns(rest);
13222
+ const rewriteRoutes = home ? [
13223
+ {
13224
+ dest: "/index.md",
13225
+ has: ACCEPT_MARKDOWN_CONDITION,
13226
+ headers: homeTokens === undefined ? VARY_ACCEPT : { ...VARY_ACCEPT, "x-markdown-tokens": String(homeTokens) },
13227
+ src: "^/$"
13228
+ }
13229
+ ] : [];
13230
+ for (const chunk of chunks) {
13231
+ rewriteRoutes.push({
13232
+ dest: "$1.md",
13233
+ has: ACCEPT_MARKDOWN_CONDITION,
13234
+ headers: VARY_ACCEPT,
13235
+ src: `^(${chunk.join("|")})/?$`
13236
+ });
13237
+ }
13238
+ const headerChunks = chunkPatterns(home ? ["/", ...rest] : rest);
13239
+ const headerRoutes = headerChunks.map((chunk) => ({
13240
+ continue: true,
13241
+ headers: VARY_ACCEPT,
13242
+ src: `^(?:${chunk.join("|")})/?$`
13243
+ }));
13244
+ return { headerRoutes, rewriteRoutes };
13245
+ };
13246
+ var HOME_SRC = "^/$";
13247
+ var isNegotiationRoute = (route) => route.has?.some((condition) => condition.value === ACCEPT_MARKDOWN_HEADER_VALUE) === true || route.continue === true && route.headers?.vary === "Accept" && typeof route.src === "string" && Object.keys(route).length === 3 || route.continue === true && typeof route.headers?.link === "string" && route.src === HOME_SRC && Object.keys(route).length === 3;
13248
+ var injectNegotiationRoutes = (configText, routePaths, homeLinkHeader, contentTypeOverrides, homeTokens) => {
13249
+ const overrideEntries = Object.entries(contentTypeOverrides ?? {});
13250
+ if (routePaths.length === 0 && !homeLinkHeader && overrideEntries.length === 0) {
13251
+ return null;
13252
+ }
13253
+ let config;
13254
+ try {
13255
+ config = JSON.parse(configText);
13256
+ } catch {
13257
+ return null;
13258
+ }
13259
+ if (!Array.isArray(config.routes)) {
13260
+ return null;
13261
+ }
13262
+ for (const [path, contentType] of overrideEntries) {
13263
+ config.overrides = { ...config.overrides, [path]: { contentType } };
13264
+ }
13265
+ const routes = config.routes.filter((route) => !isNegotiationRoute(route));
13266
+ const filesystemIndex = routes.findIndex((route) => route.handle === "filesystem");
13267
+ if (filesystemIndex === -1) {
13268
+ return null;
13269
+ }
13270
+ const { headerRoutes, rewriteRoutes } = buildNegotiationRoutes(routePaths, homeTokens);
13271
+ if (homeLinkHeader) {
13272
+ headerRoutes.push({
13273
+ continue: true,
13274
+ headers: { link: homeLinkHeader },
13275
+ src: HOME_SRC
13276
+ });
13277
+ }
13278
+ routes.splice(filesystemIndex, 0, ...headerRoutes, ...rewriteRoutes);
13279
+ config.routes = routes;
13280
+ return `${JSON.stringify(config, null, "\t")}
13281
+ `;
13282
+ };
13283
+
12411
13284
  // src/search/build.ts
12412
- import { join as join19 } from "pathe";
13285
+ import { join as join20 } from "pathe";
12413
13286
  var buildSearchIndex = async (outDir) => {
12414
13287
  const pagefind = await import("pagefind");
12415
13288
  const { index } = await pagefind.createIndex({});
@@ -12417,7 +13290,7 @@ var buildSearchIndex = async (outDir) => {
12417
13290
  throw new Error("Failed to create Pagefind index.");
12418
13291
  }
12419
13292
  const result = await index.addDirectory({ path: outDir });
12420
- await index.writeFiles({ outputPath: join19(outDir, "pagefind") });
13293
+ await index.writeFiles({ outputPath: join20(outDir, "pagefind") });
12421
13294
  await pagefind.close();
12422
13295
  return result.page_count;
12423
13296
  };
@@ -12640,14 +13513,14 @@ var syncSearchProvider = async (project, reporter) => {
12640
13513
 
12641
13514
  // src/cli/dev-lock.ts
12642
13515
  import {
12643
- existsSync as existsSync10,
13516
+ existsSync as existsSync11,
12644
13517
  mkdirSync,
12645
13518
  readFileSync as readFileSync5,
12646
13519
  rmSync,
12647
13520
  writeFileSync
12648
13521
  } from "node:fs";
12649
- import { join as join20 } from "pathe";
12650
- var lockPath = (outDir) => join20(outDir, "dev.lock");
13522
+ import { join as join21 } from "pathe";
13523
+ var lockPath = (outDir) => join21(outDir, "dev.lock");
12651
13524
  var isValidPid = (pid) => typeof pid === "number" && Number.isInteger(pid) && pid > 0;
12652
13525
  var parseLock = (raw) => {
12653
13526
  let data;
@@ -12677,7 +13550,7 @@ var isProcessAlive = (pid) => {
12677
13550
  };
12678
13551
  var readDevLock = (outDir) => {
12679
13552
  const path = lockPath(outDir);
12680
- if (!existsSync10(path)) {
13553
+ if (!existsSync11(path)) {
12681
13554
  return null;
12682
13555
  }
12683
13556
  const lock = parseLock(readFileSync5(path, "utf-8"));
@@ -12701,7 +13574,7 @@ class DevLockHeldError extends Error {
12701
13574
  }
12702
13575
  var ownsLock = (outDir) => {
12703
13576
  const path = lockPath(outDir);
12704
- if (!existsSync10(path)) {
13577
+ if (!existsSync11(path)) {
12705
13578
  return false;
12706
13579
  }
12707
13580
  try {
@@ -12758,12 +13631,12 @@ var refuseIfDevRunning = (root, action, options = {}) => {
12758
13631
  };
12759
13632
 
12760
13633
  // src/astro/generate.ts
12761
- import { createHash as createHash2 } from "node:crypto";
12762
- import { existsSync as existsSync14, readFileSync as readFileSync9, realpathSync } from "node:fs";
13634
+ import { createHash as createHash3 } from "node:crypto";
13635
+ import { existsSync as existsSync16, readFileSync as readFileSync9, realpathSync } from "node:fs";
12763
13636
  import {
12764
13637
  lstat,
12765
13638
  mkdir as mkdir6,
12766
- readFile as readFile14,
13639
+ readFile as readFile16,
12767
13640
  readlink,
12768
13641
  realpath,
12769
13642
  rename,
@@ -12773,7 +13646,7 @@ import {
12773
13646
  } from "node:fs/promises";
12774
13647
  import { createRequire as createRequire5 } from "node:module";
12775
13648
  import { pathToFileURL as pathToFileURL3 } from "node:url";
12776
- import { basename as basename3, dirname as dirname9, join as join26, normalize as normalize3, relative as relative13, resolve as resolve7 } from "pathe";
13649
+ import { basename as basename3, dirname as dirname10, join as join28, normalize as normalize3, relative as relative14, resolve as resolve8 } from "pathe";
12777
13650
  import { glob as glob7 } from "tinyglobby";
12778
13651
 
12779
13652
  // src/ai/ask-data.ts
@@ -12796,31 +13669,6 @@ var buildAskData = async (project) => {
12796
13669
  };
12797
13670
  };
12798
13671
 
12799
- // src/ai/markdown.ts
12800
- import { readFile as readFile10 } from "node:fs/promises";
12801
- var agentMarkdown = (entry) => entry.md ?? entry.mdx;
12802
- var buildRawMarkdown = async (project) => {
12803
- const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));
12804
- const components = {
12805
- ...exampleComponentSerializers(project.examples ?? {}),
12806
- ...project.config.ai.markdownComponents
12807
- };
12808
- const readRoute = async (route) => {
12809
- const page = pageById.get(route.id);
12810
- if (page) {
12811
- return await readEntryText(project, page);
12812
- }
12813
- return route.sourcePath ? await readFile10(route.sourcePath, "utf-8") : "";
12814
- };
12815
- const entries = await Promise.all(project.manifest.routes.map(async (route) => {
12816
- const source = applyAgentVisibility(await readRoute(route));
12817
- const md = downlevelComponents(source, components, frontmatter_default(source).data);
12818
- const entry = md === source ? { mdx: source } : { md, mdx: source };
12819
- return [route.path, entry];
12820
- }));
12821
- return Object.fromEntries(entries);
12822
- };
12823
-
12824
13672
  // src/ai/mcp/data.ts
12825
13673
  var buildMcpData = async (project) => {
12826
13674
  const { config, graph, manifest } = project;
@@ -12912,19 +13760,42 @@ var buildMcpDiscovery = (input) => ({
12912
13760
  }
12913
13761
  ]
12914
13762
  });
12915
- var buildMcpServerCard = (input) => ({
12916
- description: `Model Context Protocol server for the ${input.name} documentation.`,
12917
- name: input.name,
12918
- tools: MCP_TOOLS.map((tool) => ({
12919
- annotations: tool.annotations,
12920
- description: tool.description,
12921
- name: tool.name,
12922
- title: tool.title
12923
- })),
12924
- transport: "streamable-http",
12925
- url: serverUrl(input),
12926
- version: input.version
12927
- });
13763
+ var SERVER_CARD_SCHEMA = "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json";
13764
+ var CARD_TEXT_MAX = 100;
13765
+ var truncate = (text2) => text2.length > CARD_TEXT_MAX ? `${text2.slice(0, CARD_TEXT_MAX - 1)}…` : text2;
13766
+ var reverseDnsName = (input) => {
13767
+ let namespace = "localhost";
13768
+ if (input.site) {
13769
+ try {
13770
+ namespace = new URL(input.site).hostname.split(".").toReversed().join(".");
13771
+ } catch {}
13772
+ }
13773
+ return `${namespace}/${slugify(input.name) || "docs"}`;
13774
+ };
13775
+ var HTTP_URL2 = /^https?:\/\//u;
13776
+ var buildMcpServerCard = (input) => {
13777
+ const url = serverUrl(input);
13778
+ return {
13779
+ $schema: SERVER_CARD_SCHEMA,
13780
+ capabilities: { tools: { listChanged: false } },
13781
+ description: truncate(`Model Context Protocol server for the ${input.name} documentation.`),
13782
+ name: reverseDnsName(input),
13783
+ ...HTTP_URL2.test(url) ? { remotes: [{ type: "streamable-http", url }] } : {},
13784
+ serverInfo: { name: input.name, version: input.version },
13785
+ title: truncate(input.name),
13786
+ tools: MCP_TOOLS.map((tool) => ({
13787
+ annotations: tool.annotations,
13788
+ description: tool.description,
13789
+ name: tool.name,
13790
+ title: tool.title
13791
+ })),
13792
+ transport: "streamable-http",
13793
+ transports: [{ endpoint: url, type: "streamable-http" }],
13794
+ url,
13795
+ version: input.version,
13796
+ ...input.site ? { websiteUrl: trimEnd(input.site, "/") } : {}
13797
+ };
13798
+ };
12928
13799
 
12929
13800
  // src/core/builtin-tags.ts
12930
13801
  var BUILTIN_MDX_TAGS = new Set([
@@ -12991,8 +13862,8 @@ var validateUsedComponents = (pages, extraTags, registryNames) => {
12991
13862
  };
12992
13863
 
12993
13864
  // src/core/component-overrides.ts
12994
- import { existsSync as existsSync11 } from "node:fs";
12995
- import { dirname as dirname7, extname as extname6, isAbsolute as isAbsolute6, resolve as resolve5 } from "pathe";
13865
+ import { existsSync as existsSync12 } from "node:fs";
13866
+ import { dirname as dirname8, extname as extname7, isAbsolute as isAbsolute6, resolve as resolve6 } from "pathe";
12996
13867
  import ts from "typescript";
12997
13868
  var GROUPS = ["mdx", "layout", "islands"];
12998
13869
  var GROUP_SET = new Set(GROUPS);
@@ -13084,24 +13955,24 @@ var findDefaultExportObject = (sourceFile) => {
13084
13955
  var probeExtension = (base) => {
13085
13956
  for (const extension of COMPONENT_EXTS) {
13086
13957
  const candidate = `${base}.${extension}`;
13087
- if (existsSync11(candidate)) {
13958
+ if (existsSync12(candidate)) {
13088
13959
  return candidate;
13089
13960
  }
13090
13961
  }
13091
13962
  return null;
13092
13963
  };
13093
13964
  var toImport = (specifier, imported, dir) => {
13094
- const relative12 = specifier.startsWith(".") || isAbsolute6(specifier);
13965
+ const relative13 = specifier.startsWith(".") || isAbsolute6(specifier);
13095
13966
  let path = specifier;
13096
- let extension = extname6(specifier).slice(1).toLowerCase();
13097
- if (relative12) {
13098
- const absolute = isAbsolute6(specifier) ? specifier : resolve5(dir, specifier);
13967
+ let extension = extname7(specifier).slice(1).toLowerCase();
13968
+ if (relative13) {
13969
+ const absolute = isAbsolute6(specifier) ? specifier : resolve6(dir, specifier);
13099
13970
  if (extension) {
13100
13971
  path = absolute;
13101
13972
  } else {
13102
13973
  const probed = probeExtension(absolute);
13103
13974
  path = probed ?? absolute;
13104
- extension = probed ? extname6(probed).slice(1).toLowerCase() : "";
13975
+ extension = probed ? extname7(probed).slice(1).toLowerCase() : "";
13105
13976
  }
13106
13977
  }
13107
13978
  return {
@@ -13250,7 +14121,7 @@ var analyzeComponentOverrides = (source, filePath) => {
13250
14121
  return result;
13251
14122
  }
13252
14123
  const imports = collectImports(sourceFile);
13253
- const dir = dirname7(filePath);
14124
+ const dir = dirname8(filePath);
13254
14125
  for (const property of object.properties) {
13255
14126
  collectGroupOverrides(property, imports, dir, result);
13256
14127
  }
@@ -13258,16 +14129,16 @@ var analyzeComponentOverrides = (source, filePath) => {
13258
14129
  };
13259
14130
 
13260
14131
  // src/core/tsconfig-aliases.ts
13261
- import { existsSync as existsSync12, readFileSync as readFileSync6, statSync } from "node:fs";
14132
+ import { existsSync as existsSync13, readFileSync as readFileSync6, statSync } from "node:fs";
13262
14133
  import { createRequire as createRequire3 } from "node:module";
13263
14134
  import { pathToFileURL as pathToFileURL2 } from "node:url";
13264
- import { dirname as dirname8, isAbsolute as isAbsolute7, join as join21, resolve as resolve6 } from "pathe";
13265
- var scanJsonChar = (text, index, inString) => {
13266
- const char = text[index];
14135
+ import { dirname as dirname9, isAbsolute as isAbsolute7, join as join22, resolve as resolve7 } from "pathe";
14136
+ var scanJsonChar = (text2, index, inString) => {
14137
+ const char = text2[index];
13267
14138
  if (inString) {
13268
14139
  if (char === "\\") {
13269
14140
  return {
13270
- append: char + (text[index + 1] ?? ""),
14141
+ append: char + (text2[index + 1] ?? ""),
13271
14142
  inString: true,
13272
14143
  next: index + 2
13273
14144
  };
@@ -13277,35 +14148,35 @@ var scanJsonChar = (text, index, inString) => {
13277
14148
  if (char === '"') {
13278
14149
  return { append: char, inString: true, next: index + 1 };
13279
14150
  }
13280
- if (char === "/" && text[index + 1] === "/") {
13281
- const newline = text.indexOf(`
14151
+ if (char === "/" && text2[index + 1] === "/") {
14152
+ const newline = text2.indexOf(`
13282
14153
  `, index + 2);
13283
14154
  return {
13284
14155
  append: "",
13285
14156
  inString: false,
13286
- next: newline === -1 ? text.length : newline
14157
+ next: newline === -1 ? text2.length : newline
13287
14158
  };
13288
14159
  }
13289
- if (char === "/" && text[index + 1] === "*") {
13290
- const end = text.indexOf("*/", index + 2);
14160
+ if (char === "/" && text2[index + 1] === "*") {
14161
+ const end = text2.indexOf("*/", index + 2);
13291
14162
  return {
13292
14163
  append: "",
13293
14164
  inString: false,
13294
- next: end === -1 ? text.length : end + 2
14165
+ next: end === -1 ? text2.length : end + 2
13295
14166
  };
13296
14167
  }
13297
14168
  return { append: char ?? "", inString: false, next: index + 1 };
13298
14169
  };
13299
- var stripJsonComments = (text) => {
14170
+ var stripJsonComments = (text2) => {
13300
14171
  let out = "";
13301
14172
  let inString = false;
13302
14173
  let index = 0;
13303
- while (index < text.length) {
14174
+ while (index < text2.length) {
13304
14175
  const {
13305
14176
  append,
13306
14177
  inString: nextInString,
13307
14178
  next
13308
- } = scanJsonChar(text, index, inString);
14179
+ } = scanJsonChar(text2, index, inString);
13309
14180
  out += append;
13310
14181
  inString = nextInString;
13311
14182
  index = next;
@@ -13313,9 +14184,9 @@ var stripJsonComments = (text) => {
13313
14184
  return out;
13314
14185
  };
13315
14186
  var TRAILING_COMMA = /,(?<rest>\s*[}\]])/gu;
13316
- var parseJsonc = (text) => {
14187
+ var parseJsonc = (text2) => {
13317
14188
  try {
13318
- const cleaned = stripJsonComments(text).replaceAll(TRAILING_COMMA, "$<rest>");
14189
+ const cleaned = stripJsonComments(text2).replaceAll(TRAILING_COMMA, "$<rest>");
13319
14190
  const value = JSON.parse(cleaned);
13320
14191
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
13321
14192
  } catch {
@@ -13331,15 +14202,15 @@ var isFile = (path) => {
13331
14202
  };
13332
14203
  var resolveExtends = (spec, fromDir) => {
13333
14204
  if (spec.startsWith(".") || isAbsolute7(spec)) {
13334
- const candidates = spec.endsWith(".json") ? [resolve6(fromDir, spec)] : [
13335
- resolve6(fromDir, `${spec}.json`),
13336
- resolve6(fromDir, spec, "tsconfig.json"),
13337
- resolve6(fromDir, spec)
14205
+ const candidates = spec.endsWith(".json") ? [resolve7(fromDir, spec)] : [
14206
+ resolve7(fromDir, `${spec}.json`),
14207
+ resolve7(fromDir, spec, "tsconfig.json"),
14208
+ resolve7(fromDir, spec)
13338
14209
  ];
13339
14210
  return candidates.find(isFile) ?? null;
13340
14211
  }
13341
14212
  try {
13342
- const requireFromDir = createRequire3(pathToFileURL2(join21(fromDir, "_.js")).href);
14213
+ const requireFromDir = createRequire3(pathToFileURL2(join22(fromDir, "_.js")).href);
13343
14214
  for (const sub of [`${spec}/tsconfig.json`, spec]) {
13344
14215
  try {
13345
14216
  return requireFromDir.resolve(sub);
@@ -13349,7 +14220,7 @@ var resolveExtends = (spec, fromDir) => {
13349
14220
  return null;
13350
14221
  };
13351
14222
  var loadPaths = (file, seen) => {
13352
- if (seen.has(file) || !existsSync12(file)) {
14223
+ if (seen.has(file) || !existsSync13(file)) {
13353
14224
  return null;
13354
14225
  }
13355
14226
  seen.add(file);
@@ -13361,7 +14232,7 @@ var loadPaths = (file, seen) => {
13361
14232
  if (options.paths && typeof options.paths === "object") {
13362
14233
  const baseUrl = typeof options.baseUrl === "string" ? options.baseUrl : ".";
13363
14234
  return {
13364
- baseDir: resolve6(dirname8(file), baseUrl),
14235
+ baseDir: resolve7(dirname9(file), baseUrl),
13365
14236
  paths: options.paths
13366
14237
  };
13367
14238
  }
@@ -13370,7 +14241,7 @@ var loadPaths = (file, seen) => {
13370
14241
  if (typeof base !== "string") {
13371
14242
  continue;
13372
14243
  }
13373
- const resolved = resolveExtends(base, dirname8(file));
14244
+ const resolved = resolveExtends(base, dirname9(file));
13374
14245
  const found = resolved ? loadPaths(resolved, seen) : null;
13375
14246
  if (found) {
13376
14247
  return found;
@@ -13388,10 +14259,10 @@ var toAlias = (key, value, baseDir) => {
13388
14259
  if (find === "" || find === "*") {
13389
14260
  return null;
13390
14261
  }
13391
- return { find, replacement: resolve6(baseDir, target) };
14262
+ return { find, replacement: resolve7(baseDir, target) };
13392
14263
  };
13393
14264
  var resolveTsconfigAliases = (root) => {
13394
- const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join21(root, name)).find((file) => existsSync12(file));
14265
+ const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join22(root, name)).find((file) => existsSync13(file));
13395
14266
  if (!entry) {
13396
14267
  return {};
13397
14268
  }
@@ -13409,24 +14280,126 @@ var resolveTsconfigAliases = (root) => {
13409
14280
  return aliases;
13410
14281
  };
13411
14282
 
14283
+ // src/og/derive.ts
14284
+ import { existsSync as existsSync14 } from "node:fs";
14285
+ import { isAbsolute as isAbsolute8, join as join23 } from "pathe";
14286
+ var CARD_WEIGHTS = [400, 600];
14287
+ var absoluteSrc = (root, src) => isAbsolute8(src) ? src : join23(root, src);
14288
+ var googleWeights = (weights) => {
14289
+ const numbers = weights.filter((weight) => typeof weight === "number");
14290
+ const used = numbers.filter((weight) => CARD_WEIGHTS.includes(weight));
14291
+ if (used.length > 0) {
14292
+ return used;
14293
+ }
14294
+ if (numbers.length > 0) {
14295
+ return numbers;
14296
+ }
14297
+ const [first] = weights;
14298
+ return weights.length === 1 && typeof first === "string" ? first : undefined;
14299
+ };
14300
+ var googleOgFont = (name, weights) => {
14301
+ const weight = googleWeights(weights);
14302
+ return weight === undefined ? { name } : { name, weight };
14303
+ };
14304
+ var localOgFonts = (font, root) => font.variants.map((variant) => ({
14305
+ name: font.name,
14306
+ src: absoluteSrc(root, variant.src),
14307
+ ...typeof variant.weight === "number" ? { weight: variant.weight } : {},
14308
+ ...variant.style === "normal" || variant.style === "italic" ? { style: variant.style } : {}
14309
+ }));
14310
+ var roleFonts = (value, root) => {
14311
+ if (typeof value === "string") {
14312
+ if (!isFontSlug(value)) {
14313
+ return null;
14314
+ }
14315
+ const def = GOOGLE_FONTS[value];
14316
+ return [googleOgFont(def.family, def.weights)];
14317
+ }
14318
+ if ("variants" in value) {
14319
+ return localOgFonts(value, root);
14320
+ }
14321
+ const remote = value;
14322
+ if ((remote.provider ?? "google") !== "google") {
14323
+ return null;
14324
+ }
14325
+ return [googleOgFont(remote.name, remote.weights ?? CARD_WEIGHTS)];
14326
+ };
14327
+ var roleFamily = (value) => {
14328
+ if (typeof value === "string") {
14329
+ return isFontSlug(value) ? GOOGLE_FONTS[value].family : null;
14330
+ }
14331
+ return value.name;
14332
+ };
14333
+ var deriveOgFonts = (fonts, root) => {
14334
+ const derived = [];
14335
+ const seen = new Set;
14336
+ const families = {};
14337
+ const roles = [
14338
+ ["title", fonts?.display],
14339
+ ["body", fonts?.body]
14340
+ ];
14341
+ for (const [role, value] of roles) {
14342
+ if (value === undefined) {
14343
+ continue;
14344
+ }
14345
+ const roleEntries = roleFonts(value, root);
14346
+ if (!roleEntries) {
14347
+ continue;
14348
+ }
14349
+ families[role] = roleFamily(value) ?? undefined;
14350
+ for (const entry of roleEntries) {
14351
+ const key = JSON.stringify(entry);
14352
+ if (!seen.has(key)) {
14353
+ seen.add(key);
14354
+ derived.push(entry);
14355
+ }
14356
+ }
14357
+ }
14358
+ return {
14359
+ fonts: derived,
14360
+ ...families.title || families.body ? { families } : {}
14361
+ };
14362
+ };
14363
+ var resolveOgFontSources = (fonts, root) => fonts.map((font) => typeof font !== "string" && ("src" in font) ? { ...font, src: absoluteSrc(root, font.src) } : font);
14364
+ var resolveOgFonts = (options, root) => {
14365
+ if (options.ogFonts) {
14366
+ return { fonts: resolveOgFontSources(options.ogFonts, root) };
14367
+ }
14368
+ return options.themeFontsConfigured ? deriveOgFonts(options.themeFonts, root) : { fonts: [] };
14369
+ };
14370
+ var missingFontFiles = (options, root) => {
14371
+ const sources = [];
14372
+ for (const value of Object.values(options.themeFonts ?? {})) {
14373
+ if (typeof value !== "string" && "variants" in value) {
14374
+ sources.push(...value.variants.map((variant) => absoluteSrc(root, variant.src)));
14375
+ }
14376
+ }
14377
+ for (const font of options.ogFonts) {
14378
+ if (typeof font !== "string" && "src" in font) {
14379
+ sources.push(absoluteSrc(root, font.src));
14380
+ }
14381
+ }
14382
+ return [...new Set(sources)].filter((path) => !existsSync14(path));
14383
+ };
14384
+
13412
14385
  // src/og/logo.ts
13413
- import { existsSync as existsSync13, readFileSync as readFileSync7 } from "node:fs";
13414
- import { join as join22 } from "pathe";
14386
+ import { existsSync as existsSync15, readFileSync as readFileSync7 } from "node:fs";
14387
+ import { join as join24 } from "pathe";
13415
14388
  var resolveOgLogo = (project, source) => {
13416
14389
  if (!source?.toLowerCase().endsWith(".svg")) {
13417
14390
  return;
13418
14391
  }
13419
- const relative12 = source.replace(/^\//u, "");
14392
+ const relative13 = source.replace(/^\//u, "");
13420
14393
  const file = [
13421
- join22(project.context.root, "public", relative12),
13422
- join22(project.context.root, relative12)
13423
- ].find((path) => existsSync13(path));
14394
+ join24(project.context.root, "public", relative13),
14395
+ join24(project.context.root, relative13)
14396
+ ].find((path) => existsSync15(path));
13424
14397
  return file ? readFileSync7(file, "utf-8") : undefined;
13425
14398
  };
13426
14399
 
13427
14400
  // src/openapi/scalar.ts
13428
- import { readFile as readFile11 } from "node:fs/promises";
13429
- import { isAbsolute as isAbsolute8, join as join23 } from "pathe";
14401
+ import { readFile as readFile13 } from "node:fs/promises";
14402
+ import { isAbsolute as isAbsolute9, join as join25 } from "pathe";
13430
14403
 
13431
14404
  // src/theme/palette.ts
13432
14405
  var FALLBACK_ACCENT = "oklch(0.62 0.16 250)";
@@ -13532,9 +14505,9 @@ var specConfiguration = async (spec, root) => {
13532
14505
  if (URL_SPEC2.test(spec)) {
13533
14506
  return { config: { url: spec } };
13534
14507
  }
13535
- const absolute = isAbsolute8(spec) ? spec : join23(root, spec);
14508
+ const absolute = isAbsolute9(spec) ? spec : join25(root, spec);
13536
14509
  try {
13537
- return { config: { content: await readFile11(absolute, "utf-8") } };
14510
+ return { config: { content: await readFile13(absolute, "utf-8") } };
13538
14511
  } catch {
13539
14512
  return {
13540
14513
  config: { url: spec },
@@ -14021,19 +14994,29 @@ blume-diff {
14021
14994
  even though its chrome wrapper is not-prose. */
14022
14995
  .prose :where(pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code) {
14023
14996
  display: block;
14024
- overflow-x: auto;
14025
- padding: 0 1.25rem;
14026
- /* The scroller is only as tall as the code, so an overlay scrollbar would
14027
- draw on top of the last line; hide it (wheel/trackpad/keyboard scrolling
14028
- still works). */
14029
- scrollbar-width: none;
14997
+ /* Tall blocks scroll vertically in place instead of taking the page. Both
14998
+ axes live on the code element for the same reason: the pre stays static
14999
+ so the header bar and copy button never drift with the scroll. */
15000
+ max-height: 24rem;
15001
+ overflow: auto;
15002
+ /* The small bottom inset keeps the horizontal thumb off the last line's
15003
+ descenders now that scrollbars are visible. */
15004
+ padding: 0 1.25rem 0.375rem;
15005
+ /* Thin theme-colored scrollbars, matching the sidebar treatment, so a
15006
+ height-capped block reads as scrollable instead of simply ending.
15007
+ Safari before 18.2 supports neither property and falls back to the
15008
+ platform-default scrollbar — acceptable, since macOS overlays it. */
15009
+ scrollbar-color: var(--blume-border) transparent;
15010
+ scrollbar-width: thin;
14030
15011
  }
14031
15012
 
14032
- .prose
14033
- :where(
14034
- pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code
14035
- )::-webkit-scrollbar {
14036
- display: none;
15013
+ /* The dark border token is too close to the page background to read as a
15014
+ scrollbar thumb; derive a brighter one from the muted foreground instead. */
15015
+ :root[data-theme="dark"]
15016
+ .prose
15017
+ :where(pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code) {
15018
+ scrollbar-color: color-mix(in oklab, var(--blume-muted-foreground) 55%, transparent)
15019
+ transparent;
14037
15020
  }
14038
15021
 
14039
15022
  /* Word wrap (markdown.code.wrap): long lines wrap instead of scrolling. The
@@ -14101,6 +15084,10 @@ pre.blume-source {
14101
15084
 
14102
15085
  pre.blume-source > code {
14103
15086
  flex: 1;
15087
+ /* The pane's measured height is authoritative (it can exceed the prose
15088
+ 24rem cap), so undo the generic prose max-height: the code must fill
15089
+ whatever height the tab was given. */
15090
+ max-height: none;
14104
15091
  min-height: 0;
14105
15092
  overflow: auto;
14106
15093
  }
@@ -14282,6 +15269,13 @@ pre:has(.line.focused):hover .line:not(.focused) {
14282
15269
  display: none !important;
14283
15270
  }
14284
15271
 
15272
+ /* Paper can't scroll: uncap the code scroller so long blocks print in
15273
+ full, same as tab panels are force-expanded below. */
15274
+ .prose :where(pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code) {
15275
+ max-height: none;
15276
+ overflow: visible;
15277
+ }
15278
+
14285
15279
  #blume-content {
14286
15280
  padding: 0 !important;
14287
15281
  }
@@ -14525,13 +15519,13 @@ export const layoutOverrides = { ...(overrides.layout ?? {})${layoutEntries.leng
14525
15519
  };
14526
15520
 
14527
15521
  // src/astro/examples.ts
14528
- import { readFile as readFile13 } from "node:fs/promises";
14529
- import { join as join25, relative as relative12 } from "pathe";
15522
+ import { readFile as readFile15 } from "node:fs/promises";
15523
+ import { join as join27, relative as relative13 } from "pathe";
14530
15524
  import { glob as glob6 } from "tinyglobby";
14531
15525
 
14532
15526
  // src/astro/islands.ts
14533
- import { readFile as readFile12 } from "node:fs/promises";
14534
- import { basename as basename2, join as join24 } from "pathe";
15527
+ import { readFile as readFile14 } from "node:fs/promises";
15528
+ import { basename as basename2, join as join26 } from "pathe";
14535
15529
  import { glob as glob5 } from "tinyglobby";
14536
15530
  var DEFAULT_CLIENT = "visible";
14537
15531
  var VALID_MODES = new Set([
@@ -14560,14 +15554,14 @@ var readClientMode = (source, file, warnings) => {
14560
15554
  return mode;
14561
15555
  };
14562
15556
  var discoverIslands = async (root) => {
14563
- const dir = join24(root, "islands");
15557
+ const dir = join26(root, "islands");
14564
15558
  const matches2 = await glob5(["**/*.{jsx,svelte,tsx,vue}"], {
14565
15559
  absolute: true,
14566
15560
  cwd: dir,
14567
15561
  onlyFiles: true
14568
15562
  });
14569
15563
  const files = matches2.toSorted();
14570
- const sources = await Promise.all(files.map((file) => readFile12(file, "utf-8")));
15564
+ const sources = await Promise.all(files.map((file) => readFile14(file, "utf-8")));
14571
15565
  const islands = [];
14572
15566
  const warnings = [];
14573
15567
  const seen = new Map;
@@ -14623,14 +15617,14 @@ var splitGlobBase = (pattern) => {
14623
15617
  };
14624
15618
  var discoverExamples = async (root, pattern = "examples") => {
14625
15619
  const { base, rest } = GLOB_MAGIC.test(pattern) ? splitGlobBase(pattern) : { base: pattern, rest: DEFAULT_EXAMPLE_GLOB };
14626
- const dir = join25(root, base);
15620
+ const dir = join27(root, base);
14627
15621
  const matches2 = await glob6([rest], {
14628
15622
  absolute: true,
14629
15623
  cwd: dir,
14630
15624
  onlyFiles: true
14631
15625
  });
14632
15626
  const files = matches2.toSorted();
14633
- const sources = await Promise.all(files.map((file) => readFile13(file, "utf-8")));
15627
+ const sources = await Promise.all(files.map((file) => readFile15(file, "utf-8")));
14634
15628
  const examples = [];
14635
15629
  const warnings = [];
14636
15630
  const seen = new Map;
@@ -14640,7 +15634,7 @@ var discoverExamples = async (root, pattern = "examples") => {
14640
15634
  if (!(ext && framework)) {
14641
15635
  return;
14642
15636
  }
14643
- const path = relative12(dir, file).slice(0, -(ext.length + 1));
15637
+ const path = relative13(dir, file).slice(0, -(ext.length + 1));
14644
15638
  const existing = seen.get(path);
14645
15639
  if (existing) {
14646
15640
  warnings.push(`Two examples both resolve to "${path}" ("${existing}" and "${file}"); ignoring the second. Give them distinct paths.`);
@@ -14667,10 +15661,10 @@ var exampleMarkdownLookup = (examples) => Object.fromEntries(examples.map((examp
14667
15661
  ]));
14668
15662
 
14669
15663
  // src/astro/generate.ts
14670
- var BLUME_SRC = join26(packageRoot(), "src");
15664
+ var BLUME_SRC = join28(packageRoot(), "src");
14671
15665
  var canResolveFrom = (fromDir, spec) => {
14672
15666
  try {
14673
- createRequire5(pathToFileURL3(join26(fromDir, "_.js")).href).resolve(spec);
15667
+ createRequire5(pathToFileURL3(join28(fromDir, "_.js")).href).resolve(spec);
14674
15668
  return true;
14675
15669
  } catch {
14676
15670
  return false;
@@ -14681,7 +15675,7 @@ var resolveReactCompiler = (config, needsReact, pkgDir = packageRoot()) => {
14681
15675
  return null;
14682
15676
  }
14683
15677
  try {
14684
- return createRequire5(pathToFileURL3(join26(pkgDir, "_.js")).href).resolve("babel-plugin-react-compiler");
15678
+ return createRequire5(pathToFileURL3(join28(pkgDir, "_.js")).href).resolve("babel-plugin-react-compiler");
14685
15679
  } catch {
14686
15680
  return null;
14687
15681
  }
@@ -14691,7 +15685,7 @@ var reactCompilerWarnings = (config, needsReact, compilerPath) => needsReact &&
14691
15685
  ] : [];
14692
15686
  var resolveAstroPackageJson = (modulesDir) => {
14693
15687
  try {
14694
- return realpathSync(join26(modulesDir, "astro", "package.json"));
15688
+ return realpathSync(join28(modulesDir, "astro", "package.json"));
14695
15689
  } catch {
14696
15690
  return null;
14697
15691
  }
@@ -14699,12 +15693,12 @@ var resolveAstroPackageJson = (modulesDir) => {
14699
15693
  var resolvedAstroHit = (fromDir) => {
14700
15694
  let dir = normalize3(fromDir);
14701
15695
  while (true) {
14702
- const modulesDir = join26(dir, "node_modules");
15696
+ const modulesDir = join28(dir, "node_modules");
14703
15697
  const pkg = resolveAstroPackageJson(modulesDir);
14704
15698
  if (pkg) {
14705
15699
  return { modulesDir, pkg };
14706
15700
  }
14707
- const parent = dirname9(dir);
15701
+ const parent = dirname10(dir);
14708
15702
  if (parent === dir) {
14709
15703
  return null;
14710
15704
  }
@@ -14719,10 +15713,10 @@ var sameRealDir = (a, b) => {
14719
15713
  }
14720
15714
  };
14721
15715
  var depsCandidates = (pkgDir) => [
14722
- join26(pkgDir, "node_modules"),
14723
- dirname9(pkgDir)
15716
+ join28(pkgDir, "node_modules"),
15717
+ dirname10(pkgDir)
14724
15718
  ];
14725
- var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync14(join26(dir, ...segments))) ?? null;
15719
+ var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync16(join28(dir, ...segments))) ?? null;
14726
15720
  var linkDepsJunction = async (link, depsDir) => {
14727
15721
  let existing;
14728
15722
  try {
@@ -14735,13 +15729,13 @@ var linkDepsJunction = async (link, depsDir) => {
14735
15729
  return;
14736
15730
  }
14737
15731
  try {
14738
- if (resolve7(dirname9(link), await readlink(link)) === resolve7(depsDir)) {
15732
+ if (resolve8(dirname10(link), await readlink(link)) === resolve8(depsDir)) {
14739
15733
  return;
14740
15734
  }
14741
15735
  } catch {}
14742
15736
  await rm2(link, { force: true });
14743
15737
  }
14744
- await mkdir6(dirname9(link), { recursive: true });
15738
+ await mkdir6(dirname10(link), { recursive: true });
14745
15739
  await symlink(depsDir, link, "junction");
14746
15740
  };
14747
15741
  var readPkgVersion = (pkgJsonPath) => {
@@ -14774,7 +15768,7 @@ var dropStaleDepsLink = async (link, pkgDir) => {
14774
15768
  let linkedBlume;
14775
15769
  let runningBlume;
14776
15770
  try {
14777
- linkedBlume = await realpath(join26(link, "blume"));
15771
+ linkedBlume = await realpath(join28(link, "blume"));
14778
15772
  runningBlume = await realpath(pkgDir);
14779
15773
  } catch {
14780
15774
  return;
@@ -14790,7 +15784,7 @@ var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
14790
15784
  }
14791
15785
  const mdxDir = candidateHolding(pkgDir, "@astrojs", "mdx");
14792
15786
  const blumeAstro = resolveAstroPackageJson(astroDir);
14793
- await dropStaleDepsLink(join26(outDir, "node_modules"), pkgDir);
15787
+ await dropStaleDepsLink(join28(outDir, "node_modules"), pkgDir);
14794
15788
  const outDirHit = resolvedAstroHit(outDir);
14795
15789
  const astroCorrect = blumeAstro !== null && outDirHit?.pkg === blumeAstro;
14796
15790
  const walkLandsInDeps = astroCorrect && outDirHit !== null && sameRealDir(outDirHit.modulesDir, astroDir);
@@ -14798,7 +15792,7 @@ var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
14798
15792
  return null;
14799
15793
  }
14800
15794
  if (mdxDir && (mdxDir === astroDir || astroCorrect)) {
14801
- await linkDepsJunction(join26(outDir, "node_modules"), mdxDir);
15795
+ await linkDepsJunction(join28(outDir, "node_modules"), mdxDir);
14802
15796
  return null;
14803
15797
  }
14804
15798
  return astroConflictWarning(blumeAstro, outDirHit?.pkg ?? null);
@@ -14839,14 +15833,14 @@ var searchProviderWarnings = (provider, root, pkgDir = packageRoot()) => {
14839
15833
  }
14840
15834
  return warnings;
14841
15835
  };
14842
- var examplesCssFile = (root, config) => config.examples.css ? join26(root, config.examples.css) : null;
15836
+ var examplesCssFile = (root, config) => config.examples.css ? join28(root, config.examples.css) : null;
14843
15837
  var writeExamplesPreview = async (options) => {
14844
15838
  const { config, hasExamples, root, srcDir, write } = options;
14845
15839
  if (hasExamples) {
14846
- await write(join26(srcDir, "pages", ...config.basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro"), examplesPageTemplate());
15840
+ await write(join28(srcDir, "pages", ...config.basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro"), examplesPageTemplate());
14847
15841
  }
14848
15842
  const cssFile = examplesCssFile(root, config);
14849
- return cssFile && !existsSync14(cssFile) ? [
15843
+ return cssFile && !existsSync16(cssFile) ? [
14850
15844
  `examples.css points at "${config.examples.css}", which doesn't exist; previews render without it.`
14851
15845
  ] : [];
14852
15846
  };
@@ -14855,7 +15849,7 @@ var readOptional = async (path) => {
14855
15849
  return "";
14856
15850
  }
14857
15851
  try {
14858
- return await readFile14(path, "utf-8");
15852
+ return await readFile16(path, "utf-8");
14859
15853
  } catch {
14860
15854
  return "";
14861
15855
  }
@@ -14875,30 +15869,30 @@ var detectUsesMath = async (root, staged = []) => {
14875
15869
  ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
14876
15870
  onlyFiles: true
14877
15871
  });
14878
- const contents = await Promise.all(files.map((file) => readOptional(join26(root, file))));
15872
+ const contents = await Promise.all(files.map((file) => readOptional(join28(root, file))));
14879
15873
  return [...contents, ...staged].some(containsMath);
14880
15874
  };
14881
- var hashConfigSource = (source) => createHash2("sha256").update(source).digest("hex");
15875
+ var hashConfigSource = (source) => createHash3("sha256").update(source).digest("hex");
14882
15876
  var loadIntegrationBridge = async (config, context) => {
14883
15877
  if (config.integrations.length === 0 || !context.configFile) {
14884
15878
  return;
14885
15879
  }
14886
15880
  return {
14887
- configFile: relative13(context.outDir, context.configFile),
15881
+ configFile: relative14(context.outDir, context.configFile),
14888
15882
  sourceHash: hashConfigSource(await readOptional(context.configFile))
14889
15883
  };
14890
15884
  };
14891
15885
  var writeIfChanged = async (path, content) => {
14892
15886
  let existing = null;
14893
15887
  try {
14894
- existing = await readFile14(path, "utf-8");
15888
+ existing = await readFile16(path, "utf-8");
14895
15889
  } catch {
14896
15890
  existing = null;
14897
15891
  }
14898
15892
  if (existing === content) {
14899
15893
  return false;
14900
15894
  }
14901
- await mkdir6(dirname9(path), { recursive: true });
15895
+ await mkdir6(dirname10(path), { recursive: true });
14902
15896
  const tmp = `${path}.${process.pid}.tmp`;
14903
15897
  await writeFile7(tmp, content, "utf-8");
14904
15898
  try {
@@ -14936,12 +15930,12 @@ var collectStaged = (project) => {
14936
15930
  var writeStagedContent = async (out, staged) => {
14937
15931
  const contentDir = stagedContentDir(out);
14938
15932
  const written = new Set;
14939
- await Promise.all([...staged].map(async ([entryId, text]) => {
14940
- const path = join26(contentDir, entryId);
15933
+ await Promise.all([...staged].map(async ([entryId, text2]) => {
15934
+ const path = join28(contentDir, entryId);
14941
15935
  written.add(normalize3(path));
14942
- await writeIfChanged(path, text);
15936
+ await writeIfChanged(path, text2);
14943
15937
  }));
14944
- if (existsSync14(contentDir)) {
15938
+ if (existsSync16(contentDir)) {
14945
15939
  await pruneOrphans(contentDir, written);
14946
15940
  }
14947
15941
  };
@@ -14972,9 +15966,9 @@ var readLogoSvg = (project, source) => {
14972
15966
  }
14973
15967
  const rel = source.replace(/^\//u, "");
14974
15968
  const file = [
14975
- join26(project.context.root, "public", rel),
14976
- join26(project.context.root, rel)
14977
- ].find((path) => existsSync14(path));
15969
+ join28(project.context.root, "public", rel),
15970
+ join28(project.context.root, rel)
15971
+ ].find((path) => existsSync16(path));
14978
15972
  return file ? readFileSync9(file, "utf-8") : undefined;
14979
15973
  };
14980
15974
  var resolveLogo = (project) => {
@@ -14983,7 +15977,7 @@ var resolveLogo = (project) => {
14983
15977
  return null;
14984
15978
  }
14985
15979
  const config = typeof logo === "string" ? { image: logo } : logo;
14986
- const { href, image: source, text } = config;
15980
+ const { href, image: source, text: text2 } = config;
14987
15981
  const image = typeof source === "string" ? { light: source } : source;
14988
15982
  const light = image?.light ?? image?.dark;
14989
15983
  const dark = image?.dark ?? image?.light;
@@ -14992,12 +15986,12 @@ var resolveLogo = (project) => {
14992
15986
  const lightSvg = readLogoSvg(project, light);
14993
15987
  const darkSvg = dark === light ? lightSvg : readLogoSvg(project, dark);
14994
15988
  if (light && light === dark && lightSvg) {
14995
- return { alt, href: brandHref, svg: lightSvg, text };
15989
+ return { alt, href: brandHref, svg: lightSvg, text: text2 };
14996
15990
  }
14997
15991
  const lightDimensions = svgDimensions(lightSvg);
14998
15992
  const darkDimensions = svgDimensions(darkSvg);
14999
15993
  const dimensions = lightDimensions || darkDimensions ? { dark: darkDimensions, light: lightDimensions } : undefined;
15000
- return { alt, dark, dimensions, href: brandHref, light, text };
15994
+ return { alt, dark, dimensions, href: brandHref, light, text: text2 };
15001
15995
  };
15002
15996
  var FAVICON_CANDIDATES = [
15003
15997
  "icon.svg",
@@ -15020,7 +16014,7 @@ var faviconType = (name) => {
15020
16014
  };
15021
16015
  var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync9(file).toString("base64")}`;
15022
16016
  var defaultFavicon = () => ({
15023
- href: inlineDataUri(join26(BLUME_SRC, "assets", "icon.png"), "image/png"),
16017
+ href: inlineDataUri(join28(BLUME_SRC, "assets", "icon.png"), "image/png"),
15024
16018
  type: "image/png"
15025
16019
  });
15026
16020
  var APPLE_ICON_CANDIDATES = [
@@ -15032,13 +16026,13 @@ var APPLE_ICON_CANDIDATES = [
15032
16026
  var resolveIconFile = (project, candidates) => {
15033
16027
  const { root } = project.context;
15034
16028
  for (const name of candidates) {
15035
- if (existsSync14(join26(root, "public", name))) {
16029
+ if (existsSync16(join28(root, "public", name))) {
15036
16030
  return { href: `/${name}`, type: faviconType(name) };
15037
16031
  }
15038
16032
  }
15039
16033
  for (const name of candidates) {
15040
- const file = join26(root, name);
15041
- if (existsSync14(file)) {
16034
+ const file = join28(root, name);
16035
+ if (existsSync16(file)) {
15042
16036
  const type = faviconType(name);
15043
16037
  return { href: inlineDataUri(file, type ?? "image/x-icon"), type };
15044
16038
  }
@@ -15062,18 +16056,45 @@ var resolveBanner = (config) => {
15062
16056
  link: banner.link
15063
16057
  };
15064
16058
  };
16059
+ var resolveOgMark = (project, siteLogo) => {
16060
+ const configured = project.config.seo.og.logo;
16061
+ if (configured === false) {
16062
+ return false;
16063
+ }
16064
+ if (configured) {
16065
+ return resolveOgLogo(project, configured);
16066
+ }
16067
+ return siteLogo;
16068
+ };
16069
+ var resolveOgSite = (config) => {
16070
+ const configured = config.seo.og.site;
16071
+ if (configured === false) {
16072
+ return;
16073
+ }
16074
+ if (configured !== undefined) {
16075
+ return configured;
16076
+ }
16077
+ return config.deployment.site ? `${new URL(config.deployment.site).host}${normalizeBasePath(config.deployment.base)}` : undefined;
16078
+ };
16079
+ var resolveOgDescription = (config) => {
16080
+ const configured = config.seo.og.description;
16081
+ if (configured === false) {
16082
+ return;
16083
+ }
16084
+ return configured ?? config.description;
16085
+ };
15065
16086
  var buildRuntimeData = (project) => {
15066
16087
  const { config, context, graph, manifest } = project;
15067
16088
  const { github } = config;
15068
16089
  const repoUrl = github ? `https://github.com/${github.owner}/${github.repo}` : null;
15069
16090
  const editBase = github ? `${repoUrl}/edit/${github.branch}` : null;
15070
16091
  const logo = resolveLogo(project);
15071
- const ogLogo = config.seo.og.logo ? resolveOgLogo(project, config.seo.og.logo) : logo?.svg;
16092
+ const ogLogo = resolveOgMark(project, logo?.svg);
15072
16093
  const editUrlFor = (sourcePath) => {
15073
16094
  if (!(editBase && sourcePath)) {
15074
16095
  return null;
15075
16096
  }
15076
- const rel = relative13(context.root, sourcePath).split("\\").join("/");
16097
+ const rel = relative14(context.root, sourcePath).split("\\").join("/");
15077
16098
  const editPath = github?.dir ? `${github.dir}/${rel}` : rel;
15078
16099
  return `${editBase}/${editPath}`;
15079
16100
  };
@@ -15135,10 +16156,11 @@ var buildRuntimeData = (project) => {
15135
16156
  route: config.ai.mcp.route
15136
16157
  } : null,
15137
16158
  og: {
16159
+ description: resolveOgDescription(config),
15138
16160
  enabled: config.seo.og.enabled ?? false,
15139
- fonts: config.seo.og.fonts ?? [],
15140
16161
  logo: ogLogo,
15141
- palette: config.seo.og.palette
16162
+ palette: config.seo.og.palette,
16163
+ site: resolveOgSite(config)
15142
16164
  },
15143
16165
  repoUrl,
15144
16166
  search: {
@@ -15151,6 +16173,10 @@ var buildRuntimeData = (project) => {
15151
16173
  theme: config.theme,
15152
16174
  title: config.title,
15153
16175
  toc: config.toc,
16176
+ webmcp: {
16177
+ enabled: config.ai.webmcp,
16178
+ llms: config.ai.llmsTxt.enabled
16179
+ },
15154
16180
  x: config.seo.x
15155
16181
  },
15156
16182
  feeds: buildRssFeeds(project).map((feed) => ({
@@ -15184,7 +16210,7 @@ var buildRuntimeData = (project) => {
15184
16210
  var planMcp = (project, srcDir, userPages) => {
15185
16211
  const { config } = project;
15186
16212
  const { route } = config.ai.mcp;
15187
- const dir = join26(srcDir, "blume-mcp");
16213
+ const dir = join28(srcDir, "blume-mcp");
15188
16214
  const base = {
15189
16215
  dir,
15190
16216
  discoveryPages: [],
@@ -15208,11 +16234,11 @@ var planMcp = (project, srcDir, userPages) => {
15208
16234
  ...base,
15209
16235
  discoveryPages: [
15210
16236
  {
15211
- entrypoint: join26(dir, "discovery.ts"),
16237
+ entrypoint: join28(dir, "discovery.ts"),
15212
16238
  pattern: "/.well-known/mcp.json"
15213
16239
  },
15214
16240
  {
15215
- entrypoint: join26(dir, "server-card.ts"),
16241
+ entrypoint: join28(dir, "server-card.ts"),
15216
16242
  pattern: "/.well-known/mcp/server-card.json"
15217
16243
  }
15218
16244
  ],
@@ -15232,11 +16258,11 @@ var writeMcpFiles = async (project, plan, write) => {
15232
16258
  version: data.version
15233
16259
  };
15234
16260
  await Promise.all([
15235
- write(join26(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
16261
+ write(join28(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
15236
16262
  `),
15237
- write(join26(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate(plan.route)),
15238
- write(join26(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
15239
- write(join26(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
16263
+ write(join28(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate(plan.route)),
16264
+ write(join28(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
16265
+ write(join28(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
15240
16266
  ]);
15241
16267
  };
15242
16268
  var writeAskFiles = async (project, srcDir, write) => {
@@ -15246,20 +16272,20 @@ var writeAskFiles = async (project, srcDir, write) => {
15246
16272
  }
15247
16273
  const grounded = ask.provider !== "inkeep";
15248
16274
  if (grounded) {
15249
- await write(join26(srcDir, "generated", "ask-data.json"), `${JSON.stringify(await buildAskData(project))}
16275
+ await write(join28(srcDir, "generated", "ask-data.json"), `${JSON.stringify(await buildAskData(project))}
15250
16276
  `);
15251
16277
  }
15252
- await write(join26(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded));
16278
+ await write(join28(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded));
15253
16279
  };
15254
16280
  var writeNotFoundPage = async (write, srcDir, pages, contentPages) => {
15255
16281
  if (routeIsTaken(pages, contentPages, "/404")) {
15256
16282
  return;
15257
16283
  }
15258
- await write(join26(srcDir, "pages", "404.astro"), notFoundPageTemplate());
16284
+ await write(join28(srcDir, "pages", "404.astro"), notFoundPageTemplate());
15259
16285
  };
15260
16286
  var diagnosticWarning = (diagnostic) => diagnostic.suggestion ? `${diagnostic.message} ${diagnostic.suggestion}` : diagnostic.message;
15261
16287
  var buildComponentSlots = async (componentsFile) => {
15262
- const analysis = componentsFile ? analyzeComponentOverrides(await readFile14(componentsFile, "utf-8"), componentsFile) : null;
16288
+ const analysis = componentsFile ? analyzeComponentOverrides(await readFile16(componentsFile, "utf-8"), componentsFile) : null;
15263
16289
  return {
15264
16290
  plan: planComponentSlots(componentsFile, analysis),
15265
16291
  tags: analysis ? [...analysis.mdx, ...analysis.islands].map((entry) => entry.key) : [],
@@ -15267,17 +16293,34 @@ var buildComponentSlots = async (componentsFile) => {
15267
16293
  };
15268
16294
  };
15269
16295
  var contentWatchesRuntimeDir = (hasFilesystemSource, collectionBase, context) => hasFilesystemSource && runtimeDirWithin(collectionBase, context.outDir) !== null;
16296
+ var projectOgFonts = (project) => resolveOgFonts({
16297
+ ogFonts: project.config.seo.og.fonts,
16298
+ themeFonts: project.config.theme.fonts,
16299
+ themeFontsConfigured: project.themeFontsConfigured
16300
+ }, project.context.root);
16301
+ var assertFontFilesExist = (project) => {
16302
+ const { config, context } = project;
16303
+ const missing = missingFontFiles({ ogFonts: config.seo.og.fonts ?? [], themeFonts: config.theme.fonts }, context.root);
16304
+ if (missing.length > 0) {
16305
+ throw new BlumeError({
16306
+ code: "BLUME_FONT_FILE_MISSING",
16307
+ message: `Configured font file(s) not found: ${missing.join(", ")}. Font paths resolve relative to the project root.`,
16308
+ severity: "error"
16309
+ });
16310
+ }
16311
+ };
15270
16312
  var generateRuntime = async (project) => {
15271
16313
  const { context, config } = project;
16314
+ assertFontFilesExist(project);
15272
16315
  const out = context.outDir;
15273
- const srcDir = join26(out, "src");
15274
- const askPath = join26(srcDir, "generated", "Ask.astro");
15275
- const dataPath = join26(srcDir, "generated", "data.json");
15276
- const themePath = join26(srcDir, "generated", "app.css");
15277
- const searchClientPath = join26(srcDir, "generated", "search-client.ts");
15278
- const examplesPath = join26(srcDir, "generated", "examples.ts");
15279
- const examplesThemePath = join26(srcDir, "generated", "examples.css");
15280
- const openapiPath = join26(srcDir, "generated", "openapi.json");
16316
+ const srcDir = join28(out, "src");
16317
+ const askPath = join28(srcDir, "generated", "Ask.astro");
16318
+ const dataPath = join28(srcDir, "generated", "data.json");
16319
+ const themePath = join28(srcDir, "generated", "app.css");
16320
+ const searchClientPath = join28(srcDir, "generated", "search-client.ts");
16321
+ const examplesPath = join28(srcDir, "generated", "examples.ts");
16322
+ const examplesThemePath = join28(srcDir, "generated", "examples.css");
16323
+ const openapiPath = join28(srcDir, "generated", "openapi.json");
15281
16324
  const written = new Set;
15282
16325
  const write = (path, content) => {
15283
16326
  written.add(normalize3(path));
@@ -15332,11 +16375,11 @@ var generateRuntime = async (project) => {
15332
16375
  const docsCollection = resolveDocsCollection(config, context);
15333
16376
  const [structural] = await Promise.all([
15334
16377
  Promise.all([
15335
- write(join26(out, "astro.config.mjs"), astroConfigTemplate({
16378
+ write(join28(out, "astro.config.mjs"), astroConfigTemplate({
15336
16379
  aliases: resolveTsconfigAliases(context.root),
15337
16380
  askPath,
15338
16381
  config,
15339
- contentRoutes: project.manifest.routes.map((route) => route.path),
16382
+ contentRoutes: markdownRoutePaths(project),
15340
16383
  contentWatchesRuntimeDir: contentWatchesRuntimeDir(hasFilesystemSource, docsCollection.base, context),
15341
16384
  context,
15342
16385
  dataPath,
@@ -15352,26 +16395,26 @@ var generateRuntime = async (project) => {
15352
16395
  searchClientPath,
15353
16396
  themePath
15354
16397
  })),
15355
- write(join26(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
15356
- write(join26(out, "tsconfig.json"), runtimeTsconfigTemplate()),
15357
- write(join26(srcDir, "env.d.ts"), envTemplate()),
15358
- write(join26(srcDir, "content.config.ts"), contentConfigTemplate({
16398
+ write(join28(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
16399
+ write(join28(out, "tsconfig.json"), runtimeTsconfigTemplate()),
16400
+ write(join28(srcDir, "env.d.ts"), envTemplate()),
16401
+ write(join28(srcDir, "content.config.ts"), contentConfigTemplate({
15359
16402
  collection: docsCollection,
15360
16403
  config,
15361
16404
  context,
15362
16405
  filesystem: hasFilesystemSource,
15363
16406
  staged: hasStaged
15364
16407
  })),
15365
- write(join26(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
16408
+ write(join28(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
15366
16409
  exportEpub,
15367
16410
  exportPdf,
15368
16411
  mathEnabled: usesMath,
15369
16412
  needsReact
15370
16413
  })),
15371
16414
  write(askPath, askComponentTemplate(askEnabled)),
15372
- write(join26(srcDir, "generated", "components.ts"), slotPlan.module),
15373
- write(join26(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
15374
- write(join26(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples, config.basePath)),
16415
+ write(join28(srcDir, "generated", "components.ts"), slotPlan.module),
16416
+ write(join28(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
16417
+ write(join28(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples, config.basePath)),
15375
16418
  write(examplesThemePath, examplesEntryTemplate({
15376
16419
  configTokens: buildThemeCss(config.theme),
15377
16420
  sources: [`${context.root}/**/*.{astro,jsx,svelte,ts,tsx,vue}`],
@@ -15387,17 +16430,17 @@ var generateRuntime = async (project) => {
15387
16430
  userTheme
15388
16431
  }))
15389
16432
  ]),
15390
- Promise.all(islandDiscovery.islands.map((island) => write(join26(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island)))),
15391
- Promise.all(slotPlan.wrappers.map((wrapper) => write(join26(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content))),
15392
- Promise.all(exampleDiscovery.examples.map((example) => write(join26(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example)))),
16433
+ Promise.all(islandDiscovery.islands.map((island) => write(join28(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island)))),
16434
+ Promise.all(slotPlan.wrappers.map((wrapper) => write(join28(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content))),
16435
+ Promise.all(exampleDiscovery.examples.map((example) => write(join28(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example)))),
15393
16436
  writeAskFiles(project, srcDir, write),
15394
16437
  writeMcpFiles(project, mcp, write)
15395
16438
  ]);
15396
16439
  if (config.seo.og.enabled) {
15397
- await write(join26(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes));
16440
+ await write(join28(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes, projectOgFonts(project)));
15398
16441
  }
15399
16442
  if (hasGeneratedChangelog(project, pages)) {
15400
- await write(join26(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
16443
+ await write(join28(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
15401
16444
  exportEpub,
15402
16445
  exportPdf,
15403
16446
  needsReact,
@@ -15417,27 +16460,31 @@ var generateRuntime = async (project) => {
15417
16460
  ]);
15418
16461
  if (servesStaticIndex(config.search.provider)) {
15419
16462
  const documents = await buildSearchDocuments(project);
15420
- await write(join26(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
16463
+ await write(join28(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
15421
16464
  `);
15422
- await write(join26(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
16465
+ await write(join28(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
15423
16466
  }
15424
16467
  if (config.search.provider === "mixedbread") {
15425
- await write(join26(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
16468
+ await write(join28(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
15426
16469
  }
15427
16470
  const rawMarkdown = await buildRawMarkdown(project);
16471
+ const contentAssets = await collectContentAssets(project);
15428
16472
  await Promise.all([
15429
- write(join26(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
16473
+ write(join28(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
15430
16474
  `),
15431
- write(join26(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate("md")),
15432
- write(join26(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate("mdx"))
16475
+ write(join28(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate("md")),
16476
+ write(join28(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate("mdx")),
16477
+ write(join28(srcDir, "generated", "content-assets.json"), `${JSON.stringify(contentAssets)}
16478
+ `),
16479
+ write(join28(srcDir, "pages", "blume-assets", "[...asset].ts"), contentAssetsEndpointTemplate(join28(project.context.outDir, "public", "blume-assets")))
15433
16480
  ]);
15434
16481
  const feeds = buildRssFeeds(project);
15435
16482
  if (feeds.length > 0) {
15436
16483
  const feedXml = Object.fromEntries(feeds.map((feed) => [feed.type, renderRssFeed(feed)]));
15437
16484
  await Promise.all([
15438
- write(join26(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
16485
+ write(join28(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
15439
16486
  `),
15440
- write(join26(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
16487
+ write(join28(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
15441
16488
  ]);
15442
16489
  }
15443
16490
  const warnings = [
@@ -15473,14 +16520,14 @@ var generateRuntime = async (project) => {
15473
16520
  root: context.root
15474
16521
  });
15475
16522
  warnings.push(...references.warnings);
15476
- await Promise.all(references.files.map((file) => write(join26(srcDir, "pages", file.pagePath), file.content)));
16523
+ await Promise.all(references.files.map((file) => write(join28(srcDir, "pages", file.pagePath), file.content)));
15477
16524
  }
15478
16525
  const openApiSource2 = project.sources.find(isOpenApiSource);
15479
16526
  await Promise.all([
15480
- write(join26(srcDir, "generated", "data.json"), buildRuntimeData(project)),
16527
+ write(join28(srcDir, "generated", "data.json"), buildRuntimeData(project)),
15481
16528
  write(openapiPath, `${JSON.stringify(openApiSource2 ? openApiSource2.openApiData() : {})}
15482
16529
  `),
15483
- write(join26(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
16530
+ write(join28(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
15484
16531
  `),
15485
16532
  writeStagedContent(out, staged)
15486
16533
  ]);
@@ -15489,8 +16536,8 @@ var generateRuntime = async (project) => {
15489
16536
  };
15490
16537
 
15491
16538
  // src/cli/env.ts
15492
- import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
15493
- import { dirname as dirname10, join as join27, resolve as resolve8 } from "pathe";
16539
+ import { existsSync as existsSync17, readFileSync as readFileSync10 } from "node:fs";
16540
+ import { dirname as dirname11, join as join29, resolve as resolve9 } from "pathe";
15494
16541
  var ENV_LINE = /^\s*(?:export\s+)?(?<key>[A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?<value>.*?)\s*$/u;
15495
16542
  var DOUBLE_QUOTED2 = /^"(?<body>[\s\S]*)"$/u;
15496
16543
  var SINGLE_QUOTED = /^'(?<body>[\s\S]*)'$/u;
@@ -15536,19 +16583,19 @@ var applyEnv = (parsed) => {
15536
16583
  };
15537
16584
  var loadFile = (path) => {
15538
16585
  try {
15539
- if (existsSync15(path)) {
16586
+ if (existsSync17(path)) {
15540
16587
  applyEnv(parseEnv(readFileSync10(path, "utf-8")));
15541
16588
  }
15542
16589
  } catch {}
15543
16590
  };
15544
16591
  var loadEnvFiles = (startDir) => {
15545
- let dir = resolve8(startDir);
16592
+ let dir = resolve9(startDir);
15546
16593
  let done = false;
15547
16594
  while (!done) {
15548
- loadFile(join27(dir, ".env.local"));
15549
- loadFile(join27(dir, ".env"));
15550
- const parent = dirname10(dir);
15551
- done = existsSync15(join27(dir, ".git")) || parent === dir;
16595
+ loadFile(join29(dir, ".env.local"));
16596
+ loadFile(join29(dir, ".env"));
16597
+ const parent = dirname11(dir);
16598
+ done = existsSync17(join29(dir, ".git")) || parent === dir;
15552
16599
  dir = parent;
15553
16600
  }
15554
16601
  };
@@ -15653,20 +16700,94 @@ var emitRedirectFiles = async (config, distDir) => {
15653
16700
  if (redirects.length === 0 || config.deployment.output !== "static") {
15654
16701
  return;
15655
16702
  }
15656
- await writeFile8(join28(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
16703
+ await writeFile8(join30(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
15657
16704
  const platformFiles = [
15658
16705
  { content: buildNetlifyRedirects(redirects), name: "_redirects" },
15659
16706
  { content: buildVercelConfig(redirects), name: "vercel.json" }
15660
16707
  ];
15661
- await Promise.all(platformFiles.map((file) => existsSync16(join28(distDir, file.name)) ? Promise.resolve() : writeFile8(join28(distDir, file.name), file.content, "utf-8")));
16708
+ await Promise.all(platformFiles.map((file) => existsSync18(join30(distDir, file.name)) ? Promise.resolve() : writeFile8(join30(distDir, file.name), file.content, "utf-8")));
15662
16709
  logger.success(`Emitted redirect files for ${redirects.length} redirect(s)`);
15663
16710
  };
15664
- var emitHeaderFiles = async (config, distDir) => {
15665
- if (config.deployment.output !== "static" || existsSync16(join28(distDir, "_headers"))) {
16711
+ var emitHeaderFiles = async (project, distDir) => {
16712
+ const { config } = project;
16713
+ if (config.deployment.output !== "static" || existsSync18(join30(distDir, "_headers"))) {
16714
+ return;
16715
+ }
16716
+ await writeFile8(join30(distDir, "_headers"), buildNetlifyHeaders(config, buildHomeLinkHeader(config, markdownRoutePaths(project))), "utf-8");
16717
+ logger.success("Emitted _headers (UTF-8 Content-Type + homepage Link header)");
16718
+ };
16719
+ var emitAgentSkills = async (project, distDir) => {
16720
+ const configured = project.config.ai.skills;
16721
+ if (!configured) {
15666
16722
  return;
15667
16723
  }
15668
- await writeFile8(join28(distDir, "_headers"), buildNetlifyHeaders(config), "utf-8");
15669
- logger.success("Emitted _headers (UTF-8 Content-Type for raw endpoints)");
16724
+ const dir = resolve10(project.context.root, configured);
16725
+ if (!existsSync18(dir)) {
16726
+ logger.warn(`ai.skills points at "${configured}" (${dir}), which does not exist; no skills published.`);
16727
+ return;
16728
+ }
16729
+ const outDir = join30(distDir, AGENT_SKILLS_DIR.slice(1));
16730
+ if (existsSync18(join30(outDir, "index.json"))) {
16731
+ return;
16732
+ }
16733
+ const { skills, warnings } = await collectSkills(dir);
16734
+ for (const warning of warnings) {
16735
+ logger.warn(warning);
16736
+ }
16737
+ if (skills.length === 0) {
16738
+ logger.warn(`ai.skills: no publishable skills found in "${configured}".`);
16739
+ return;
16740
+ }
16741
+ await Promise.all(skills.map(async (skill) => {
16742
+ const target = join30(outDir, skill.path);
16743
+ await mkdir7(dirname12(target), { recursive: true });
16744
+ await writeFile8(target, skill.content);
16745
+ }));
16746
+ await writeFile8(join30(outDir, "index.json"), buildSkillsIndex(skills, project.config), "utf-8");
16747
+ logger.success(`Published ${skills.length} agent skill(s) (.well-known/agent-skills/index.json)`);
16748
+ };
16749
+ var emitWellKnownFiles = async (config, distDir) => {
16750
+ const files = [
16751
+ {
16752
+ content: buildSignaturesDirectory(config),
16753
+ label: "Web Bot Auth",
16754
+ path: SIGNATURES_DIRECTORY_PATH
16755
+ },
16756
+ {
16757
+ content: buildApiCatalog(config),
16758
+ label: "RFC 9727",
16759
+ path: API_CATALOG_PATH
16760
+ }
16761
+ ];
16762
+ for (const file of files) {
16763
+ const target = join30(distDir, file.path.slice(1));
16764
+ if (!file.content || existsSync18(target)) {
16765
+ continue;
16766
+ }
16767
+ await mkdir7(join30(distDir, ".well-known"), { recursive: true });
16768
+ await writeFile8(target, file.content, "utf-8");
16769
+ logger.success(`Generated ${file.path.slice(1)} (${file.label})`);
16770
+ }
16771
+ };
16772
+ var emitVercelNegotiation = async (project, routePaths, root) => {
16773
+ const { config } = project;
16774
+ const configPath = join30(root, ".vercel", "output", "config.json");
16775
+ if (!existsSync18(configPath)) {
16776
+ return;
16777
+ }
16778
+ const overrides = {
16779
+ ...hasApiCatalog(config) ? { [API_CATALOG_PATH.slice(1)]: API_CATALOG_TYPE } : {},
16780
+ ...config.ai.webBotAuth.keys.length > 0 ? { [SIGNATURES_DIRECTORY_PATH.slice(1)]: SIGNATURES_DIRECTORY_TYPE } : {}
16781
+ };
16782
+ const rawMarkdown = await buildRawMarkdown(project);
16783
+ const home = rawMarkdown["/"];
16784
+ const injected = injectNegotiationRoutes(await readFile17(configPath, "utf-8"), routePaths, buildHomeLinkHeader(config, routePaths), overrides, home ? markdownTokenCount(agentMarkdown(home)) : undefined);
16785
+ if (injected === null) {
16786
+ logger.warn("Could not wire Accept: text/markdown negotiation into .vercel/output/config.json — raw Markdown stays available at the .md URLs.");
16787
+ return;
16788
+ }
16789
+ await writeFile8(configPath, injected, "utf-8");
16790
+ logger.success("Wired Accept: text/markdown negotiation into the Vercel routing config");
15670
16791
  };
15671
16792
  var formatBytes2 = (bytes) => {
15672
16793
  if (bytes < 1024) {
@@ -15676,14 +16797,14 @@ var formatBytes2 = (bytes) => {
15676
16797
  return `${(bytes / 1024).toFixed(digits)} kB`;
15677
16798
  };
15678
16799
  var astroAssets = async (distDir, ext) => {
15679
- const astroDir = join28(distDir, "_astro");
15680
- if (!existsSync16(astroDir)) {
16800
+ const astroDir = join30(distDir, "_astro");
16801
+ if (!existsSync18(astroDir)) {
15681
16802
  return [];
15682
16803
  }
15683
- const entries = await readdir(astroDir);
16804
+ const entries = await readdir2(astroDir);
15684
16805
  const files = entries.filter((name) => name.endsWith(`.${ext}`));
15685
16806
  const sized = await Promise.all(files.map(async (name) => {
15686
- const info = await stat2(join28(astroDir, name));
16807
+ const info = await stat3(join30(astroDir, name));
15687
16808
  return { name, size: info.size };
15688
16809
  }));
15689
16810
  return sized.toSorted((a, b) => b.size - a.size);
@@ -15736,26 +16857,26 @@ var runClientAssetChecks = async (staticDir, args) => {
15736
16857
  var isolatedOutputDir = (config, context) => {
15737
16858
  const { adapter, output } = config.deployment;
15738
16859
  if (output === "server" && adapter === "vercel") {
15739
- return join28(context.outDir, ".vercel", "output");
16860
+ return join30(context.outDir, ".vercel", "output");
15740
16861
  }
15741
- return context.distDir ?? join28(context.outDir, "dist");
16862
+ return context.distDir ?? join30(context.outDir, "dist");
15742
16863
  };
15743
16864
  var isolatedStaticDir = (config, context) => {
15744
16865
  const { adapter, output } = config.deployment;
15745
16866
  const outputDir = isolatedOutputDir(config, context);
15746
16867
  if (output === "server" && adapter === "vercel") {
15747
- return join28(outputDir, "static");
16868
+ return join30(outputDir, "static");
15748
16869
  }
15749
16870
  if (output === "server" && adapter === "node") {
15750
- return join28(outputDir, "client");
16871
+ return join30(outputDir, "client");
15751
16872
  }
15752
16873
  return outputDir;
15753
16874
  };
15754
16875
  var publishLlmsFiles = async (project, distDir) => {
15755
- const indexPath = join28(distDir, "llms.txt");
15756
- const fullPath = join28(distDir, "llms-full.txt");
15757
- const writeIndex = !existsSync16(indexPath);
15758
- const writeFull = !existsSync16(fullPath);
16876
+ const indexPath = join30(distDir, "llms.txt");
16877
+ const fullPath = join30(distDir, "llms-full.txt");
16878
+ const writeIndex = !existsSync18(indexPath);
16879
+ const writeFull = !existsSync18(fullPath);
15759
16880
  if (!(writeIndex || writeFull)) {
15760
16881
  return;
15761
16882
  }
@@ -15788,23 +16909,25 @@ var publishBuildArtifacts = async (project, distDir, args) => {
15788
16909
  await publishLlmsFiles(project, distDir);
15789
16910
  }
15790
16911
  const sitemap = buildSitemap(project);
15791
- if (sitemap && !existsSync16(join28(distDir, "sitemap.xml"))) {
15792
- await writeFile8(join28(distDir, "sitemap.xml"), sitemap, "utf-8");
16912
+ if (sitemap && !existsSync18(join30(distDir, "sitemap.xml"))) {
16913
+ await writeFile8(join30(distDir, "sitemap.xml"), sitemap, "utf-8");
15793
16914
  logger.success("Generated sitemap.xml");
15794
16915
  }
15795
16916
  const robots = buildRobots(project);
15796
- if (robots && !existsSync16(join28(distDir, "robots.txt"))) {
15797
- await writeFile8(join28(distDir, "robots.txt"), robots, "utf-8");
16917
+ if (robots && !existsSync18(join30(distDir, "robots.txt"))) {
16918
+ await writeFile8(join30(distDir, "robots.txt"), robots, "utf-8");
15798
16919
  logger.success("Generated robots.txt");
15799
16920
  }
15800
16921
  const agentReadability = buildAgentReadability(project);
15801
- if (agentReadability && !existsSync16(join28(distDir, "agent-readability.json"))) {
15802
- await writeFile8(join28(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
16922
+ if (agentReadability && !existsSync18(join30(distDir, "agent-readability.json"))) {
16923
+ await writeFile8(join30(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
15803
16924
  `, "utf-8");
15804
16925
  logger.success("Generated agent-readability.json");
15805
16926
  }
16927
+ await emitWellKnownFiles(project.config, distDir);
16928
+ await emitAgentSkills(project, distDir);
15806
16929
  await emitRedirectFiles(project.config, distDir);
15807
- await emitHeaderFiles(project.config, distDir);
16930
+ await emitHeaderFiles(project, distDir);
15808
16931
  const { config } = project;
15809
16932
  const features = serverFeatures(config);
15810
16933
  const sitemapNote = config.seo.sitemap ? "no (set deployment.site)" : "no (seo.sitemap is false)";
@@ -15918,16 +17041,19 @@ var buildCommand = defineCommand3({
15918
17041
  if (surfaced.moved) {
15919
17042
  logger.success(`Surfaced ${adapter} output to ${surfaced.to}`);
15920
17043
  }
17044
+ if (project.config.deployment.output === "server" && adapter === "vercel") {
17045
+ await emitVercelNegotiation(project, markdownRoutePaths(project), root);
17046
+ }
15921
17047
  await publishBuildArtifacts(project, deployStaticDir(project.config, project.context), args);
15922
17048
  }
15923
17049
  });
15924
17050
 
15925
17051
  // src/cli/commands/check.ts
15926
- import { existsSync as existsSync17 } from "node:fs";
17052
+ import { existsSync as existsSync19 } from "node:fs";
15927
17053
  import { check } from "@astrojs/check";
15928
17054
  import { sync } from "astro";
15929
17055
  import { defineCommand as defineCommand4 } from "citty";
15930
- import { join as join29 } from "pathe";
17056
+ import { join as join31 } from "pathe";
15931
17057
  var checkCommand = defineCommand4({
15932
17058
  args: {
15933
17059
  isolated: {
@@ -15963,13 +17089,13 @@ var checkCommand = defineCommand4({
15963
17089
  });
15964
17090
  const { outDir } = project.context;
15965
17091
  await sync({ logLevel: "warn", root: outDir });
15966
- const tsconfig = join29(root, "tsconfig.json");
17092
+ const tsconfig = join31(root, "tsconfig.json");
15967
17093
  logger.start(`Type-checking ${project.graph.pages.length} page(s)`);
15968
17094
  const failed = await check({
15969
17095
  minimumFailingSeverity: "error",
15970
17096
  minimumSeverity: "hint",
15971
17097
  root: outDir,
15972
- tsconfig: existsSync17(tsconfig) ? tsconfig : undefined,
17098
+ tsconfig: existsSync19(tsconfig) ? tsconfig : undefined,
15973
17099
  watch: false
15974
17100
  });
15975
17101
  if (failed) {
@@ -15984,7 +17110,7 @@ var checkCommand = defineCommand4({
15984
17110
  import { watch } from "node:fs";
15985
17111
  import { dev } from "astro";
15986
17112
  import { defineCommand as defineCommand5 } from "citty";
15987
- import { basename as basename4, dirname as dirname11 } from "pathe";
17113
+ import { basename as basename4, dirname as dirname13 } from "pathe";
15988
17114
 
15989
17115
  // src/astro/integration.ts
15990
17116
  var overlayServer = null;
@@ -16177,7 +17303,7 @@ var devCommand = defineCommand5({
16177
17303
  }),
16178
17304
  ...fileTargets.map((target) => {
16179
17305
  const name = basename4(target);
16180
- const watcher = watch(dirname11(target), (_event, filename) => {
17306
+ const watcher = watch(dirname13(target), (_event, filename) => {
16181
17307
  if (!filename || filename === name) {
16182
17308
  regenerate();
16183
17309
  }
@@ -16201,12 +17327,12 @@ var devCommand = defineCommand5({
16201
17327
  // src/cli/commands/doctor.ts
16202
17328
  import { readFileSync as readFileSync11 } from "node:fs";
16203
17329
  import { defineCommand as defineCommand6 } from "citty";
16204
- import { join as join30 } from "pathe";
17330
+ import { join as join32 } from "pathe";
16205
17331
  var FALLBACK_MIN_NODE = "22.12.0";
16206
17332
  var LEADING_RANGE = /^[^\d]*/u;
16207
17333
  var minSupportedNode = () => {
16208
17334
  try {
16209
- const pkg = JSON.parse(readFileSync11(join30(packageRoot(), "package.json"), "utf-8"));
17335
+ const pkg = JSON.parse(readFileSync11(join32(packageRoot(), "package.json"), "utf-8"));
16210
17336
  const range = pkg.engines?.node ?? "";
16211
17337
  return range.replace(LEADING_RANGE, "") || FALLBACK_MIN_NODE;
16212
17338
  } catch {
@@ -16299,21 +17425,21 @@ var doctorCommand = defineCommand6({
16299
17425
 
16300
17426
  // src/cli/commands/eject.ts
16301
17427
  import { defineCommand as defineCommand7 } from "citty";
16302
- import { relative as relative16 } from "pathe";
17428
+ import { relative as relative17 } from "pathe";
16303
17429
 
16304
17430
  // src/registry/eject.ts
16305
- import { existsSync as existsSync18 } from "node:fs";
16306
- import { cp as cp2, mkdir as mkdir7, readFile as readFile15, rm as rm3, writeFile as writeFile9 } from "node:fs/promises";
16307
- import { join as join31, relative as relative14 } from "pathe";
17431
+ import { existsSync as existsSync20 } from "node:fs";
17432
+ import { cp as cp2, mkdir as mkdir8, readFile as readFile18, rm as rm3, writeFile as writeFile9 } from "node:fs/promises";
17433
+ import { join as join33, relative as relative15 } from "pathe";
16308
17434
  var toPosix = (path) => path.split("\\").join("/");
16309
17435
  var LOCAL_BLUME_SOURCE = "../../node_modules/blume/src/**/*.{astro,ts,tsx}";
16310
17436
  var blumeSourceGlob = (root, genDir, resolveBlumeRoot = packageRoot) => {
16311
- if (existsSync18(join31(root, "node_modules", "blume"))) {
17437
+ if (existsSync20(join33(root, "node_modules", "blume"))) {
16312
17438
  return LOCAL_BLUME_SOURCE;
16313
17439
  }
16314
17440
  try {
16315
- const src = join31(resolveBlumeRoot(), "src");
16316
- return `${toPosix(relative14(genDir, src))}/**/*.{astro,ts,tsx}`;
17441
+ const src = join33(resolveBlumeRoot(), "src");
17442
+ return `${toPosix(relative15(genDir, src))}/**/*.{astro,ts,tsx}`;
16317
17443
  } catch {
16318
17444
  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.');
16319
17445
  return LOCAL_BLUME_SOURCE;
@@ -16326,28 +17452,28 @@ var ejectOpenApiData = (project) => {
16326
17452
  var askFiles = async (project, srcDir, genDir) => {
16327
17453
  const { ask } = project.config.ai;
16328
17454
  if (!(ask?.enabled && !ask.endpoint)) {
16329
- const endpointPath = join31(srcDir, "pages", "api", "ask.ts");
16330
- if (existsSync18(endpointPath)) {
16331
- const content = await readFile15(endpointPath, "utf-8");
17455
+ const endpointPath = join33(srcDir, "pages", "api", "ask.ts");
17456
+ if (existsSync20(endpointPath)) {
17457
+ const content = await readFile18(endpointPath, "utf-8");
16332
17458
  if (content.startsWith("// Generated by Blume. Do not edit.")) {
16333
17459
  await rm3(endpointPath, { force: true });
16334
17460
  }
16335
17461
  }
16336
- await rm3(join31(genDir, "ask-data.json"), { force: true });
17462
+ await rm3(join33(genDir, "ask-data.json"), { force: true });
16337
17463
  return [];
16338
17464
  }
16339
17465
  const grounded = ask.provider !== "inkeep";
16340
17466
  const files = [
16341
17467
  {
16342
17468
  content: askEndpointTemplate(resolveAskBackend(ask), grounded),
16343
- path: join31(srcDir, "pages", "api", "ask.ts")
17469
+ path: join33(srcDir, "pages", "api", "ask.ts")
16344
17470
  }
16345
17471
  ];
16346
17472
  if (grounded) {
16347
17473
  files.push({
16348
17474
  content: `${JSON.stringify(await buildAskData(project))}
16349
17475
  `,
16350
- path: join31(genDir, "ask-data.json")
17476
+ path: join33(genDir, "ask-data.json")
16351
17477
  });
16352
17478
  }
16353
17479
  return files;
@@ -16380,19 +17506,19 @@ var mcpFiles = async (project, userPages, srcDir, genDir) => {
16380
17506
  {
16381
17507
  content: `${JSON.stringify(data)}
16382
17508
  `,
16383
- path: join31(genDir, "mcp-data.json")
17509
+ path: join33(genDir, "mcp-data.json")
16384
17510
  },
16385
17511
  {
16386
17512
  content: mcpEndpointTemplate(route),
16387
- path: join31(srcDir, "pages", mcpPageFile(route))
17513
+ path: join33(srcDir, "pages", mcpPageFile(route))
16388
17514
  },
16389
17515
  {
16390
17516
  content: staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput)),
16391
- path: join31(srcDir, "blume-mcp", "discovery.ts")
17517
+ path: join33(srcDir, "blume-mcp", "discovery.ts")
16392
17518
  },
16393
17519
  {
16394
17520
  content: staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)),
16395
- path: join31(srcDir, "blume-mcp", "server-card.ts")
17521
+ path: join33(srcDir, "blume-mcp", "server-card.ts")
16396
17522
  }
16397
17523
  ];
16398
17524
  };
@@ -16405,23 +17531,23 @@ var changelogFiles = (project, userPages, srcDir, options) => {
16405
17531
  return [
16406
17532
  {
16407
17533
  content: changelogIndexTemplate(options),
16408
- path: join31(srcDir, "pages", "changelog.astro")
17534
+ path: join33(srcDir, "pages", "changelog.astro")
16409
17535
  }
16410
17536
  ];
16411
17537
  };
16412
- var readExamplesCss = (root, css) => css && existsSync18(join31(root, css)) ? readFile15(join31(root, css), "utf-8") : Promise.resolve("");
17538
+ var readExamplesCss = (root, css) => css && existsSync20(join33(root, css)) ? readFile18(join33(root, css), "utf-8") : Promise.resolve("");
16413
17539
  var examplesPreviewFiles = (srcDir, basePath, hasExamples) => hasExamples ? [
16414
17540
  {
16415
17541
  content: examplesPageTemplate(),
16416
- path: join31(srcDir, "pages", ...basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro")
17542
+ path: join33(srcDir, "pages", ...basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro")
16417
17543
  }
16418
17544
  ] : [];
16419
- var ejectIntegrationBridge = (config, root, configFile) => config.integrations.length > 0 && configFile ? { configFile: toPosix(relative14(root, configFile)) } : undefined;
17545
+ var ejectIntegrationBridge = (config, root, configFile) => config.integrations.length > 0 && configFile ? { configFile: toPosix(relative15(root, configFile)) } : undefined;
16420
17546
  var eject = async (root) => {
16421
17547
  const project = await scanProject(root, { mode: "build" });
16422
17548
  const { context, config } = project;
16423
- const srcDir = join31(root, "src");
16424
- const genDir = join31(srcDir, "generated");
17549
+ const srcDir = join33(root, "src");
17550
+ const genDir = join33(srcDir, "generated");
16425
17551
  const askEnabled = config.ai.ask?.enabled ?? false;
16426
17552
  const exportPdf = config.export.pdf;
16427
17553
  const exportEpub = config.export.epub;
@@ -16439,7 +17565,7 @@ var eject = async (root) => {
16439
17565
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
16440
17566
  detectNeedsReact(root),
16441
17567
  detectUsesMath(root),
16442
- context.themeFile ? readFile15(context.themeFile, "utf-8") : Promise.resolve(""),
17568
+ context.themeFile ? readFile18(context.themeFile, "utf-8") : Promise.resolve(""),
16443
17569
  readExamplesCss(root, config.examples.css),
16444
17570
  buildRawMarkdown(project),
16445
17571
  discoverIslands(root)
@@ -16453,14 +17579,14 @@ var eject = async (root) => {
16453
17579
  const needsSvelte = frameworks.has("svelte");
16454
17580
  const relContext = {
16455
17581
  ...context,
16456
- contentRoot: toPosix(relative14(root, context.contentRoot)),
17582
+ contentRoot: toPosix(relative15(root, context.contentRoot)),
16457
17583
  outDir: ".",
16458
17584
  root: "."
16459
17585
  };
16460
- const componentsImport = context.componentsFile ? `../../${toPosix(relative14(root, context.componentsFile))}` : null;
17586
+ const componentsImport = context.componentsFile ? `../../${toPosix(relative15(root, context.componentsFile))}` : null;
16461
17587
  const relPages = [
16462
17588
  ...pages.map((page) => ({
16463
- entrypoint: toPosix(relative14(root, page.entrypoint)),
17589
+ entrypoint: toPosix(relative15(root, page.entrypoint)),
16464
17590
  pattern: page.pattern
16465
17591
  })),
16466
17592
  ...mcpDiscoveryPages(project, pages)
@@ -16487,14 +17613,14 @@ var eject = async (root) => {
16487
17613
  searchClientPath: "./src/generated/search-client.ts",
16488
17614
  themePath: "./src/generated/app.css"
16489
17615
  }),
16490
- path: join31(root, "astro.config.mjs")
17616
+ path: join33(root, "astro.config.mjs")
16491
17617
  },
16492
17618
  {
16493
17619
  content: runtimeTsconfigTemplate(),
16494
- path: join31(root, "tsconfig.json"),
17620
+ path: join33(root, "tsconfig.json"),
16495
17621
  skipIfExists: true
16496
17622
  },
16497
- { content: envTemplate(), path: join31(srcDir, "env.d.ts") },
17623
+ { content: envTemplate(), path: join33(srcDir, "env.d.ts") },
16498
17624
  {
16499
17625
  content: contentConfigTemplate({
16500
17626
  config,
@@ -16502,7 +17628,7 @@ var eject = async (root) => {
16502
17628
  staged: hasStaged,
16503
17629
  stagedBase: stagedDir
16504
17630
  }),
16505
- path: join31(srcDir, "content.config.ts")
17631
+ path: join33(srcDir, "content.config.ts")
16506
17632
  },
16507
17633
  {
16508
17634
  content: catchAllPageTemplate({
@@ -16511,19 +17637,19 @@ var eject = async (root) => {
16511
17637
  mathEnabled: usesMath,
16512
17638
  needsReact
16513
17639
  }),
16514
- path: join31(srcDir, "pages", "[...slug].astro")
17640
+ path: join33(srcDir, "pages", "[...slug].astro")
16515
17641
  },
16516
17642
  {
16517
17643
  content: planComponentSlots(componentsImport, null).module,
16518
- path: join31(genDir, "components.ts")
17644
+ path: join33(genDir, "components.ts")
16519
17645
  },
16520
17646
  {
16521
17647
  content: islandMapTemplate(islands.islands),
16522
- path: join31(genDir, "islands.ts")
17648
+ path: join33(genDir, "islands.ts")
16523
17649
  },
16524
17650
  {
16525
17651
  content: exampleMapTemplate(examples.examples, config.basePath),
16526
- path: join31(genDir, "examples.ts")
17652
+ path: join33(genDir, "examples.ts")
16527
17653
  },
16528
17654
  {
16529
17655
  content: examplesEntryTemplate({
@@ -16531,7 +17657,7 @@ var eject = async (root) => {
16531
17657
  sources: ["../../**/*.{astro,jsx,svelte,ts,tsx,vue}"],
16532
17658
  userCss: userExamplesCss
16533
17659
  }),
16534
- path: join31(genDir, "examples.css")
17660
+ path: join33(genDir, "examples.css")
16535
17661
  },
16536
17662
  {
16537
17663
  content: tailwindEntryTemplate({
@@ -16543,30 +17669,30 @@ var eject = async (root) => {
16543
17669
  twoslashCss: twoslashCss(),
16544
17670
  userTheme
16545
17671
  }),
16546
- path: join31(genDir, "app.css")
17672
+ path: join33(genDir, "app.css")
16547
17673
  },
16548
- { content: buildRuntimeData(project), path: join31(genDir, "data.json") },
17674
+ { content: buildRuntimeData(project), path: join33(genDir, "data.json") },
16549
17675
  {
16550
17676
  content: askComponentTemplate(askEnabled),
16551
- path: join31(genDir, "Ask.astro")
17677
+ path: join33(genDir, "Ask.astro")
16552
17678
  },
16553
17679
  {
16554
17680
  content: `${JSON.stringify(ejectOpenApiData(project))}
16555
17681
  `,
16556
- path: join31(genDir, "openapi.json")
17682
+ path: join33(genDir, "openapi.json")
16557
17683
  },
16558
17684
  {
16559
17685
  content: `${JSON.stringify(rawMarkdown)}
16560
17686
  `,
16561
- path: join31(genDir, "raw-markdown.json")
17687
+ path: join33(genDir, "raw-markdown.json")
16562
17688
  },
16563
17689
  {
16564
17690
  content: rawMarkdownEndpointTemplate("md"),
16565
- path: join31(srcDir, "pages", "[...slug].md.ts")
17691
+ path: join33(srcDir, "pages", "[...slug].md.ts")
16566
17692
  },
16567
17693
  {
16568
17694
  content: rawMarkdownEndpointTemplate("mdx"),
16569
- path: join31(srcDir, "pages", "[...slug].mdx.ts")
17695
+ path: join33(srcDir, "pages", "[...slug].mdx.ts")
16570
17696
  }
16571
17697
  ];
16572
17698
  if (askEnabled) {
@@ -16575,7 +17701,7 @@ var eject = async (root) => {
16575
17701
  if (config.seo.og.enabled) {
16576
17702
  files.push({
16577
17703
  content: ogEndpointTemplate(customOgRoutes(pages, config.title, config.seo.og.titles)),
16578
- path: join31(srcDir, "pages", "og", "[...slug].png.ts")
17704
+ path: join33(srcDir, "pages", "og", "[...slug].png.ts")
16579
17705
  });
16580
17706
  }
16581
17707
  files.push(...await mcpFiles(project, pages, srcDir, genDir), ...changelogFiles(project, pages, srcDir, {
@@ -16587,28 +17713,28 @@ var eject = async (root) => {
16587
17713
  if (!routeIsTaken(pages, project.graph.pages, "/404")) {
16588
17714
  files.push({
16589
17715
  content: notFoundPageTemplate(),
16590
- path: join31(srcDir, "pages", "404.astro")
17716
+ path: join33(srcDir, "pages", "404.astro")
16591
17717
  });
16592
17718
  }
16593
17719
  files.push({
16594
17720
  content: searchClientTemplate(config),
16595
- path: join31(genDir, "search-client.ts")
17721
+ path: join33(genDir, "search-client.ts")
16596
17722
  });
16597
17723
  if (servesStaticIndex(config.search.provider)) {
16598
17724
  const documents = await buildSearchDocuments(project);
16599
17725
  files.push({
16600
17726
  content: `${JSON.stringify(documents)}
16601
17727
  `,
16602
- path: join31(genDir, "search.json")
17728
+ path: join33(genDir, "search.json")
16603
17729
  }, {
16604
17730
  content: searchEndpointTemplate(),
16605
- path: join31(srcDir, "pages", "blume-search.json.ts")
17731
+ path: join33(srcDir, "pages", "blume-search.json.ts")
16606
17732
  });
16607
17733
  }
16608
17734
  if (config.search.provider === "mixedbread") {
16609
17735
  files.push({
16610
17736
  content: mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""),
16611
- path: join31(srcDir, "pages", "api", "search.ts")
17737
+ path: join33(srcDir, "pages", "api", "search.ts")
16612
17738
  });
16613
17739
  }
16614
17740
  const feeds = buildRssFeeds(project);
@@ -16617,10 +17743,10 @@ var eject = async (root) => {
16617
17743
  files.push({
16618
17744
  content: `${JSON.stringify(feedXml)}
16619
17745
  `,
16620
- path: join31(genDir, "rss.json")
17746
+ path: join33(genDir, "rss.json")
16621
17747
  }, {
16622
17748
  content: rssEndpointTemplate(),
16623
- path: join31(srcDir, "pages", "[section]", "rss.xml.ts")
17749
+ path: join33(srcDir, "pages", "[section]", "rss.xml.ts")
16624
17750
  });
16625
17751
  }
16626
17752
  const warnings = [];
@@ -16634,28 +17760,28 @@ var eject = async (root) => {
16634
17760
  for (const file of references.files) {
16635
17761
  files.push({
16636
17762
  content: file.content,
16637
- path: join31(srcDir, "pages", file.pagePath)
17763
+ path: join33(srcDir, "pages", file.pagePath)
16638
17764
  });
16639
17765
  }
16640
17766
  }
16641
17767
  files.push(...islands.islands.map((island) => ({
16642
17768
  content: islandWrapperTemplate(island),
16643
- path: join31(genDir, "islands", `${island.name}.astro`)
17769
+ path: join33(genDir, "islands", `${island.name}.astro`)
16644
17770
  })), ...examples.examples.map((example) => ({
16645
17771
  content: exampleWrapperTemplate(example),
16646
- path: join31(genDir, "examples", `${exampleSlug(example.path)}.astro`)
17772
+ path: join33(genDir, "examples", `${exampleSlug(example.path)}.astro`)
16647
17773
  })), ...examplesPreviewFiles(srcDir, config.basePath, examples.examples.length > 0));
16648
17774
  for (const [entryId, content] of staged) {
16649
- files.push({ content, path: join31(root, stagedDir, entryId) });
17775
+ files.push({ content, path: join33(root, stagedDir, entryId) });
16650
17776
  }
16651
- const written = files.filter((file) => !(file.skipIfExists && existsSync18(file.path)));
17777
+ const written = files.filter((file) => !(file.skipIfExists && existsSync20(file.path)));
16652
17778
  await Promise.all(written.map(async (file) => {
16653
- await mkdir7(join31(file.path, ".."), { recursive: true });
17779
+ await mkdir8(join33(file.path, ".."), { recursive: true });
16654
17780
  await writeFile9(file.path, file.content, "utf-8");
16655
17781
  }));
16656
- const assetsSrc = join31(context.outDir, "public", "blume-assets");
16657
- if (existsSync18(assetsSrc)) {
16658
- await cp2(assetsSrc, join31(root, "public", "blume-assets"), {
17782
+ const assetsSrc = join33(context.outDir, "public", "blume-assets");
17783
+ if (existsSync20(assetsSrc)) {
17784
+ await cp2(assetsSrc, join33(root, "public", "blume-assets"), {
16659
17785
  recursive: true
16660
17786
  });
16661
17787
  }
@@ -16664,8 +17790,8 @@ var eject = async (root) => {
16664
17790
  };
16665
17791
 
16666
17792
  // src/cli/eject-scripts.ts
16667
- import { readFile as readFile16, writeFile as writeFile10 } from "node:fs/promises";
16668
- import { join as join32 } from "pathe";
17793
+ import { readFile as readFile19, writeFile as writeFile10 } from "node:fs/promises";
17794
+ import { join as join34 } from "pathe";
16669
17795
  var droppedArtifactNotices = (config) => {
16670
17796
  const notices = [];
16671
17797
  if (config.search.provider === "pagefind") {
@@ -16692,10 +17818,10 @@ var droppedArtifactNotices = (config) => {
16692
17818
  return notices;
16693
17819
  };
16694
17820
  var updatePackageScripts = async (root) => {
16695
- const pkgPath = join32(root, "package.json");
17821
+ const pkgPath = join34(root, "package.json");
16696
17822
  let pkg;
16697
17823
  try {
16698
- pkg = JSON.parse(await readFile16(pkgPath, "utf-8"));
17824
+ pkg = JSON.parse(await readFile19(pkgPath, "utf-8"));
16699
17825
  } catch {
16700
17826
  return;
16701
17827
  }
@@ -16711,9 +17837,9 @@ var updatePackageScripts = async (root) => {
16711
17837
  };
16712
17838
 
16713
17839
  // src/cli/init/scaffold.ts
16714
- import { existsSync as existsSync19 } from "node:fs";
16715
- import { mkdir as mkdir8, writeFile as writeFile11 } from "node:fs/promises";
16716
- import { basename as basename5, dirname as dirname12, isAbsolute as isAbsolute9, join as join33, relative as relative15 } from "pathe";
17840
+ import { existsSync as existsSync21 } from "node:fs";
17841
+ import { mkdir as mkdir9, writeFile as writeFile11 } from "node:fs/promises";
17842
+ import { basename as basename5, dirname as dirname14, isAbsolute as isAbsolute10, join as join35, relative as relative16 } from "pathe";
16717
17843
 
16718
17844
  // src/core/package-json.ts
16719
17845
  var toPackageName = (raw) => raw.toLowerCase().replaceAll(/[^a-z0-9._-]+/gu, "-").replaceAll(/^[-_.]+|[-_.]+$/gu, "") || "docs";
@@ -16772,7 +17898,7 @@ var STARTERS = {
16772
17898
  files: (dir) => [
16773
17899
  {
16774
17900
  content: page("API Reference", "Explore the API.", "# API Reference\n\nYour OpenAPI spec renders at [`/api`](/api). Point `openapi.sources` at your own spec in `blume.config.ts`."),
16775
- path: join33(dir, "index.mdx")
17901
+ path: join35(dir, "index.mdx")
16776
17902
  }
16777
17903
  ]
16778
17904
  },
@@ -16787,7 +17913,7 @@ var STARTERS = {
16787
17913
  files: (dir) => [
16788
17914
  {
16789
17915
  content: page("Introduction", "Welcome to your new Blume docs.", "# Introduction\n\nWrite your docs here, and log releases under `changelog/`."),
16790
- path: join33(dir, "index.mdx")
17916
+ path: join35(dir, "index.mdx")
16791
17917
  },
16792
17918
  {
16793
17919
  content: `---
@@ -16798,7 +17924,7 @@ date: 2026-01-01
16798
17924
 
16799
17925
  The first release. Edit \`${dir}/changelog/v1-0-0.mdx\` or add new entries beside it.
16800
17926
  `,
16801
- path: join33(dir, "changelog", "v1-0-0.mdx")
17927
+ path: join35(dir, "changelog", "v1-0-0.mdx")
16802
17928
  }
16803
17929
  ]
16804
17930
  },
@@ -16811,7 +17937,7 @@ The first release. Edit \`${dir}/changelog/v1-0-0.mdx\` or add new entries besid
16811
17937
  Welcome to **Blume** — markdown-first docs powered by Astro and Vite.
16812
17938
 
16813
17939
  Edit \`${dir}/index.mdx\` to get started, then run \`blume dev\`.`),
16814
- path: join33(dir, "index.mdx")
17940
+ path: join35(dir, "index.mdx")
16815
17941
  }
16816
17942
  ]
16817
17943
  },
@@ -16822,11 +17948,11 @@ Edit \`${dir}/index.mdx\` to get started, then run \`blume dev\`.`),
16822
17948
  content: page("Introduction", "Get started with the SDK.", `# Introduction
16823
17949
 
16824
17950
  Install the SDK and make your first call. See [Installation](/installation).`),
16825
- path: join33(dir, "index.mdx")
17951
+ path: join35(dir, "index.mdx")
16826
17952
  },
16827
17953
  {
16828
17954
  content: page("Installation", "Install the SDK.", "# Installation\n\n```package-install\nyour-sdk\n```"),
16829
- path: join33(dir, "installation.mdx")
17955
+ path: join35(dir, "installation.mdx")
16830
17956
  }
16831
17957
  ]
16832
17958
  }
@@ -16841,7 +17967,7 @@ var detectPackageManager = (userAgent) => {
16841
17967
  const name = userAgent?.split("/")[0];
16842
17968
  return name !== undefined && PACKAGE_MANAGERS.includes(name) ? name : "npm";
16843
17969
  };
16844
- var validateContentDir = (root, dir) => isAbsolute9(dir) || relative15(root, join33(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
17970
+ var validateContentDir = (root, dir) => isAbsolute10(dir) || relative16(root, join35(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
16845
17971
  var titleize = (raw) => {
16846
17972
  const words = raw.replaceAll(/[-_.]+/gu, " ").split(/\s+/u).filter(Boolean);
16847
17973
  return words.length === 0 ? "My Docs" : words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
@@ -16918,27 +18044,27 @@ export default defineConfig({
16918
18044
  `;
16919
18045
  var extraDepsFor = (sources) => ({
16920
18046
  ...sources.includes("notion") && { "@notionhq/client": "^2.2.15" },
16921
- ...sources.includes("sanity") && { "@sanity/client": "^6.21.0" }
18047
+ ...sources.includes("sanity") && { "@sanity/client": "^7.25.0" }
16922
18048
  });
16923
18049
  var buildPlan = (root, answers) => {
16924
18050
  const files = [
16925
18051
  {
16926
18052
  content: blumePackageJson(toPackageName(basename5(root)), extraDepsFor(answers.sources)),
16927
- path: join33(root, "package.json")
18053
+ path: join35(root, "package.json")
16928
18054
  },
16929
- { content: buildConfig(answers), path: join33(root, "blume.config.ts") }
18055
+ { content: buildConfig(answers), path: join35(root, "blume.config.ts") }
16930
18056
  ];
16931
18057
  if (answers.sources.length === 0 || answers.sources.includes("filesystem")) {
16932
- files.push(...STARTERS[answers.template].files(answers.contentDir).map((file) => ({ ...file, path: join33(root, file.path) })));
18058
+ files.push(...STARTERS[answers.template].files(answers.contentDir).map((file) => ({ ...file, path: join35(root, file.path) })));
16933
18059
  }
16934
18060
  return files;
16935
18061
  };
16936
18062
  var writeFileSafe = async (file, log) => {
16937
- if (existsSync19(file.path)) {
18063
+ if (existsSync21(file.path)) {
16938
18064
  log.info(`Skipped existing ${file.path}`);
16939
18065
  return false;
16940
18066
  }
16941
- await mkdir8(dirname12(file.path), { recursive: true });
18067
+ await mkdir9(dirname14(file.path), { recursive: true });
16942
18068
  await writeFile11(file.path, file.content, "utf-8");
16943
18069
  log.success(`Created ${file.path}`);
16944
18070
  return true;
@@ -17014,7 +18140,7 @@ var ejectCommand = defineCommand7({
17014
18140
  }
17015
18141
  logger.success(`Ejected ${files.length} file(s):`);
17016
18142
  for (const file of files) {
17017
- process.stdout.write(` ${relative16(root, file)}
18143
+ process.stdout.write(` ${relative17(root, file)}
17018
18144
  `);
17019
18145
  }
17020
18146
  reportDroppedArtifacts(notices);
@@ -17030,9 +18156,9 @@ The blume package remains importable.`);
17030
18156
  });
17031
18157
 
17032
18158
  // src/cli/commands/eval.ts
17033
- import { existsSync as existsSync20 } from "node:fs";
18159
+ import { existsSync as existsSync22 } from "node:fs";
17034
18160
  import { defineCommand as defineCommand8 } from "citty";
17035
- import { join as join37 } from "pathe";
18161
+ import { join as join39 } from "pathe";
17036
18162
 
17037
18163
  // src/eval/prompts.ts
17038
18164
  var readerPrompt = (question) => `You are evaluating whether a product's documentation can answer a user's question.
@@ -17101,7 +18227,7 @@ When you are done, print the file and suggest running \`blume eval\` to try it.`
17101
18227
  // src/eval/report.ts
17102
18228
  import { mkdtemp as mkdtemp2, writeFile as writeFile12 } from "node:fs/promises";
17103
18229
  import { tmpdir as tmpdir2 } from "node:os";
17104
- import { join as join34, relative as relative17 } from "pathe";
18230
+ import { join as join36, relative as relative18 } from "pathe";
17105
18231
  var ESC4 = String.fromCodePoint(27);
17106
18232
  var COLORS3 = {
17107
18233
  bold: `${ESC4}[1m`,
@@ -17182,15 +18308,15 @@ var summaryLine2 = (result) => {
17182
18308
  var headerLine = (total, agent) => `${COLORS3.bold}blume eval${COLORS3.reset} ${total} question(s) · ${AGENTS[agent].name}`;
17183
18309
  var startLine = (id2, index, total) => ` ${COLORS3.dim}▸ ${id2} (${index + 1}/${total})${COLORS3.reset}`;
17184
18310
  var fixLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code !== "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
17185
- const site = finding2.file ? `${relative17(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
18311
+ const site = finding2.file ? `${relative18(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
17186
18312
  return ` ${COLORS3.cyan}fix:${COLORS3.reset} ${site} ${COLORS3.dim}${finding2.message}${COLORS3.reset}`;
17187
18313
  });
17188
18314
  var warningLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code === "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
17189
- const site = finding2.file ? ` ${relative17(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
18315
+ const site = finding2.file ? ` ${relative18(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
17190
18316
  return ` ${COLORS3.yellow}⚠${COLORS3.reset}${site} ${COLORS3.dim}${finding2.message}${COLORS3.reset}`;
17191
18317
  });
17192
18318
  var evalReportJson = (result, root, threshold) => {
17193
- const diagnostics = result.diagnostics.map((diagnostic) => diagnostic.file ? { ...diagnostic, file: relative17(root, diagnostic.file) } : diagnostic);
18319
+ const diagnostics = result.diagnostics.map((diagnostic) => diagnostic.file ? { ...diagnostic, file: relative18(root, diagnostic.file) } : diagnostic);
17194
18320
  return `${JSON.stringify({
17195
18321
  diagnostics,
17196
18322
  eval: {
@@ -17206,21 +18332,21 @@ var evalReportJson = (result, root, threshold) => {
17206
18332
  `;
17207
18333
  };
17208
18334
  var writeEvalReport = async (result, root, threshold) => {
17209
- const dir = await mkdtemp2(join34(tmpdir2(), "blume-eval-"));
17210
- const path = join34(dir, "report.json");
18335
+ const dir = await mkdtemp2(join36(tmpdir2(), "blume-eval-"));
18336
+ const path = join36(dir, "report.json");
17211
18337
  await writeFile12(path, evalReportJson(result, root, threshold));
17212
18338
  return path;
17213
18339
  };
17214
18340
 
17215
18341
  // src/eval/run.ts
17216
- import { mkdir as mkdir9, mkdtemp as mkdtemp3, writeFile as writeFile14 } from "node:fs/promises";
18342
+ import { mkdir as mkdir10, mkdtemp as mkdtemp3, writeFile as writeFile14 } from "node:fs/promises";
17217
18343
  import { tmpdir as tmpdir3 } from "node:os";
17218
- import { join as join36 } from "pathe";
18344
+ import { join as join38 } from "pathe";
17219
18345
 
17220
18346
  // src/eval/agents.ts
17221
18347
  import { spawn as spawn2 } from "node:child_process";
17222
- import { readFile as readFile17, writeFile as writeFile13 } from "node:fs/promises";
17223
- import { join as join35 } from "pathe";
18348
+ import { readFile as readFile20, writeFile as writeFile13 } from "node:fs/promises";
18349
+ import { join as join37 } from "pathe";
17224
18350
  import { z as z3 } from "zod";
17225
18351
  var KILL_GRACE_MS = 5000;
17226
18352
  var MCP_TOOL_NAMES = [
@@ -17242,7 +18368,7 @@ var DISALLOWED_TOOLS = [
17242
18368
  "WebSearch",
17243
18369
  "Task"
17244
18370
  ];
17245
- var runAgentHeadless = (bin, args, options) => new Promise((resolve9, reject) => {
18371
+ var runAgentHeadless = (bin, args, options) => new Promise((resolve11, reject) => {
17246
18372
  const platform = options.platform ?? process.platform;
17247
18373
  const child = spawn2(bin, args, {
17248
18374
  cwd: options.cwd,
@@ -17271,12 +18397,12 @@ var runAgentHeadless = (bin, args, options) => new Promise((resolve9, reject) =>
17271
18397
  });
17272
18398
  child.once("close", (code) => {
17273
18399
  clearTimeout(deadline);
17274
- resolve9({ code: code ?? 1, stderr, stdout, timedOut });
18400
+ resolve11({ code: code ?? 1, stderr, stdout, timedOut });
17275
18401
  });
17276
18402
  child.once("exit", (code) => {
17277
18403
  if (timedOut) {
17278
18404
  clearTimeout(deadline);
17279
- resolve9({ code: code ?? 1, stderr, stdout, timedOut });
18405
+ resolve11({ code: code ?? 1, stderr, stdout, timedOut });
17280
18406
  }
17281
18407
  });
17282
18408
  child.stdin.end(options.prompt);
@@ -17286,7 +18412,7 @@ var writeMcpConfig = async (dir, snapshotPath, launcher) => {
17286
18412
  args: [process.argv[1] ?? "", "mcp-stdio", "--data", snapshotPath],
17287
18413
  command: process.execPath
17288
18414
  };
17289
- const configPath = join35(dir, "mcp-config.json");
18415
+ const configPath = join37(dir, "mcp-config.json");
17290
18416
  const config = {
17291
18417
  mcpServers: {
17292
18418
  [MCP_SERVER_NAME]: { args: resolved.args, command: resolved.command }
@@ -17394,17 +18520,17 @@ var readAgentOutput = async (kind, result, lastMessagePath) => {
17394
18520
  text: payload.data.result
17395
18521
  };
17396
18522
  }
17397
- let text;
18523
+ let text2;
17398
18524
  try {
17399
- const raw = await readFile17(lastMessagePath, "utf-8");
17400
- text = raw.trim();
18525
+ const raw = await readFile20(lastMessagePath, "utf-8");
18526
+ text2 = raw.trim();
17401
18527
  } catch {
17402
18528
  return { detail: "no last message written", isError: true, text: "" };
17403
18529
  }
17404
- if (text === "") {
18530
+ if (text2 === "") {
17405
18531
  return { detail: "empty last message", isError: true, text: "" };
17406
18532
  }
17407
- return { isError: false, text };
18533
+ return { isError: false, text: text2 };
17408
18534
  };
17409
18535
  var verdictSchema = z3.object({
17410
18536
  missing: z3.array(z3.string()).default([]),
@@ -17412,15 +18538,15 @@ var verdictSchema = z3.object({
17412
18538
  pass: z3.boolean(),
17413
18539
  score: z3.number().min(0).max(1).optional()
17414
18540
  });
17415
- var parseVerdict = (text) => {
17416
- const start = text.indexOf("{");
17417
- const end = text.lastIndexOf("}");
18541
+ var parseVerdict = (text2) => {
18542
+ const start = text2.indexOf("{");
18543
+ const end = text2.lastIndexOf("}");
17418
18544
  if (start === -1 || end <= start) {
17419
18545
  return;
17420
18546
  }
17421
18547
  let parsed;
17422
18548
  try {
17423
- parsed = JSON.parse(text.slice(start, end + 1));
18549
+ parsed = JSON.parse(text2.slice(start, end + 1));
17424
18550
  } catch {
17425
18551
  return;
17426
18552
  }
@@ -17429,7 +18555,7 @@ var parseVerdict = (text) => {
17429
18555
  };
17430
18556
 
17431
18557
  // src/eval/schema.ts
17432
- import { readFile as readFile18 } from "node:fs/promises";
18558
+ import { readFile as readFile21 } from "node:fs/promises";
17433
18559
  import { load as load2 } from "js-yaml";
17434
18560
  import { z as z4 } from "zod";
17435
18561
  var ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/u;
@@ -17474,7 +18600,7 @@ var describeIssues = (error) => error.issues.map((issue) => {
17474
18600
  var loadEvalsFile = async (path) => {
17475
18601
  let raw;
17476
18602
  try {
17477
- raw = await readFile18(path, "utf-8");
18603
+ raw = await readFile21(path, "utf-8");
17478
18604
  } catch {
17479
18605
  throw new EvalsFileError(path, `No evals file found at ${path}. Run \`blume eval init\` to draft one.`);
17480
18606
  }
@@ -17566,9 +18692,9 @@ var errored = (question, detail, durationMs) => ({
17566
18692
  var runQuestion = async (question, index, context) => {
17567
18693
  const started = performance.now();
17568
18694
  const elapsed = () => Math.round(performance.now() - started);
17569
- const workDir = join36(context.dir, `work-${index}`);
17570
- await mkdir9(workDir, { recursive: true });
17571
- const answerPath = join36(workDir, "answer.txt");
18695
+ const workDir = join38(context.dir, `work-${index}`);
18696
+ await mkdir10(workDir, { recursive: true });
18697
+ const answerPath = join38(workDir, "answer.txt");
17572
18698
  const reader = await context.run(context.bin, agentArgs(context.kind, { lastMessagePath: answerPath, mcp: context.mcp }), {
17573
18699
  cwd: workDir,
17574
18700
  prompt: readerPrompt(question),
@@ -17581,7 +18707,7 @@ var runQuestion = async (question, index, context) => {
17581
18707
  costUsd: answer.costUsd
17582
18708
  };
17583
18709
  }
17584
- const verdictPath = join36(workDir, "verdict.txt");
18710
+ const verdictPath = join38(workDir, "verdict.txt");
17585
18711
  const judge = await context.run(context.bin, agentArgs(context.kind, { lastMessagePath: verdictPath }), {
17586
18712
  cwd: workDir,
17587
18713
  prompt: judgePrompt(question, answer.text),
@@ -17623,8 +18749,8 @@ var runEval = async (options) => {
17623
18749
  const kind = options.agent;
17624
18750
  const run = options.run ?? runAgentHeadless;
17625
18751
  const anchor = { path: options.evalsPath, raw: options.rawEvals };
17626
- const dir = await mkdtemp3(join36(tmpdir3(), "blume-eval-"));
17627
- const snapshotPath = join36(dir, "mcp-data.json");
18752
+ const dir = await mkdtemp3(join38(tmpdir3(), "blume-eval-"));
18753
+ const snapshotPath = join38(dir, "mcp-data.json");
17628
18754
  await writeFile14(snapshotPath, JSON.stringify(await buildMcpData(options.project)));
17629
18755
  const mcp = await writeMcpConfig(dir, snapshotPath);
17630
18756
  const context = {
@@ -17762,8 +18888,8 @@ var runFixHandoff = async (agent, result, root, threshold) => {
17762
18888
  }
17763
18889
  };
17764
18890
  var runInit = async (agent, file) => {
17765
- const path = join37(process.cwd(), file);
17766
- if (existsSync20(path)) {
18891
+ const path = join39(process.cwd(), file);
18892
+ if (existsSync22(path)) {
17767
18893
  logger.error(`${file} already exists — edit it directly, or pass --file to draft elsewhere.`);
17768
18894
  process.exit(1);
17769
18895
  }
@@ -17827,7 +18953,7 @@ var evalCommand = defineCommand8({
17827
18953
  let result;
17828
18954
  try {
17829
18955
  const project = await scanProject(root, { mode: "build" });
17830
- const evalsPath = join37(root, args.file);
18956
+ const evalsPath = join39(root, args.file);
17831
18957
  const { evals, raw } = await loadEvalsFile(evalsPath);
17832
18958
  process.stderr.write(`${headerLine(evals.questions.length, agent)}
17833
18959
 
@@ -17901,10 +19027,10 @@ var evalCommand = defineCommand8({
17901
19027
  // src/cli/commands/init.ts
17902
19028
  import * as clack from "@clack/prompts";
17903
19029
  import { defineCommand as defineCommand9 } from "citty";
17904
- import { resolve as resolve10 } from "pathe";
19030
+ import { resolve as resolve12 } from "pathe";
17905
19031
 
17906
19032
  // src/cli/init/questions.ts
17907
- import { basename as basename6, resolve as resolve9 } from "pathe";
19033
+ import { basename as basename6, resolve as resolve11 } from "pathe";
17908
19034
  var cancelled = (value) => typeof value === "symbol";
17909
19035
  var collectAnswers = async (prompter, flags, defaults) => {
17910
19036
  const directory = flags.directory ?? await prompter.text({
@@ -17915,7 +19041,7 @@ var collectAnswers = async (prompter, flags, defaults) => {
17915
19041
  if (cancelled(directory)) {
17916
19042
  return null;
17917
19043
  }
17918
- const root = resolve9(defaults.cwd, directory);
19044
+ const root = resolve11(defaults.cwd, directory);
17919
19045
  const title = await prompter.text({
17920
19046
  initialValue: titleize(basename6(root)),
17921
19047
  message: "What's your docs site called?",
@@ -18084,7 +19210,7 @@ var initCommand = defineCommand9({
18084
19210
  title: "My Docs"
18085
19211
  };
18086
19212
  }
18087
- const root = resolve10(cwd, answers.directory);
19213
+ const root = resolve12(cwd, answers.directory);
18088
19214
  if (validateContentDir(root, answers.contentDir) !== undefined) {
18089
19215
  logger.error(`Invalid --content-dir "${answers.contentDir}" (must be a path inside the project).`);
18090
19216
  process.exit(1);
@@ -18114,7 +19240,7 @@ var initCommand = defineCommand9({
18114
19240
  });
18115
19241
 
18116
19242
  // src/cli/commands/mcp-stdio.ts
18117
- import { readFile as readFile19 } from "node:fs/promises";
19243
+ import { readFile as readFile22 } from "node:fs/promises";
18118
19244
  import { defineCommand as defineCommand10 } from "citty";
18119
19245
 
18120
19246
  // src/ai/mcp/stdio.ts
@@ -18140,6 +19266,22 @@ var SCHEMA = {
18140
19266
  };
18141
19267
  var BOOST = { description: 2, title: 3 };
18142
19268
  var SEGMENTED_LANGUAGES = new Set(["ja", "ko", "th", "zh"]);
19269
+ var BIGRAM_LANGUAGES = new Set(["ja", "zh"]);
19270
+ var BIGRAM_SCRIPTS = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}々〆〇ー゙゚]+$/u;
19271
+ var addBigrams = (run, tokens) => {
19272
+ const characters = [...run];
19273
+ if (characters.length === 1) {
19274
+ tokens.add(run);
19275
+ return;
19276
+ }
19277
+ let previous = "";
19278
+ for (const character of characters) {
19279
+ if (previous) {
19280
+ tokens.add(previous + character);
19281
+ }
19282
+ previous = character;
19283
+ }
19284
+ };
18143
19285
  var segmentingTokenizer = (locale) => {
18144
19286
  const language = locale?.toLowerCase().split(/[-_]/u)[0] ?? "";
18145
19287
  if (!SEGMENTED_LANGUAGES.has(language)) {
@@ -18149,16 +19291,32 @@ var segmentingTokenizer = (locale) => {
18149
19291
  return;
18150
19292
  }
18151
19293
  const segmenter = new Intl.Segmenter(language, { granularity: "word" });
19294
+ const bigram = BIGRAM_LANGUAGES.has(language);
18152
19295
  return {
18153
19296
  language,
18154
19297
  normalizationCache: new Map,
18155
19298
  tokenize: (raw) => {
18156
19299
  const tokens = new Set;
19300
+ let run = "";
19301
+ const flush = () => {
19302
+ if (run) {
19303
+ addBigrams(run, tokens);
19304
+ run = "";
19305
+ }
19306
+ };
18157
19307
  for (const segment of segmenter.segment(raw.toLowerCase())) {
18158
- if (segment.isWordLike) {
18159
- tokens.add(segment.segment);
19308
+ if (!segment.isWordLike) {
19309
+ flush();
19310
+ continue;
19311
+ }
19312
+ if (bigram && BIGRAM_SCRIPTS.test(segment.segment)) {
19313
+ run += segment.segment;
19314
+ continue;
18160
19315
  }
19316
+ flush();
19317
+ tokens.add(segment.segment);
18161
19318
  }
19319
+ flush();
18162
19320
  return [...tokens];
18163
19321
  }
18164
19322
  };
@@ -18172,14 +19330,18 @@ var buildOramaIndex = async (documents, locale) => {
18172
19330
  await insertMultiple(db, documents);
18173
19331
  return db;
18174
19332
  };
19333
+ var ALL_TOKENS = 0;
18175
19334
  var queryOramaIndex = async (db, term, limit, locale) => {
18176
- const found = await search(db, {
19335
+ const params = {
18177
19336
  boost: BOOST,
18178
19337
  limit,
18179
19338
  properties: ["title", "description", "content"],
18180
19339
  term,
18181
19340
  ...locale ? { where: { locale: { eq: locale } } } : {}
18182
- });
19341
+ };
19342
+ const bigrammed = BIGRAM_LANGUAGES.has(db.tokenizer?.language ?? "");
19343
+ const strict = bigrammed ? await search(db, { ...params, threshold: ALL_TOKENS }) : undefined;
19344
+ const found = strict && strict.hits.length > 0 ? strict : await search(db, params);
18183
19345
  return found.hits.map((hit) => hit.document);
18184
19346
  };
18185
19347
 
@@ -18256,13 +19418,13 @@ var excerptFor = (doc) => {
18256
19418
  const head = doc.content.slice(0, EXCERPT_LENGTH).trim();
18257
19419
  return doc.content.length > EXCERPT_LENGTH ? `${head}…` : head;
18258
19420
  };
18259
- var text = (value, isError = false) => ({
19421
+ var text2 = (value, isError = false) => ({
18260
19422
  content: [{ text: value, type: "text" }],
18261
19423
  ...isError ? { isError: true } : {}
18262
19424
  });
18263
19425
  var createIndexProvider = (documents, locale) => {
18264
19426
  let dbPromise = null;
18265
- return () => {
19427
+ return function provideIndex() {
18266
19428
  dbPromise ??= buildOramaIndex(documents, locale);
18267
19429
  return dbPromise;
18268
19430
  };
@@ -18286,18 +19448,18 @@ var buildServer = (data, index) => {
18286
19448
  title: doc.title,
18287
19449
  url: urlFor(doc.route, data)
18288
19450
  }));
18289
- return text(JSON.stringify(results, null, 2));
19451
+ return text2(JSON.stringify(results, null, 2));
18290
19452
  }
18291
19453
  if (name === "get_page") {
18292
19454
  const key = normalizeRoute2(asString2(args.route), data);
18293
19455
  const markdown = data.pages[key];
18294
19456
  if (markdown === undefined) {
18295
- return text(`No page found at "${key}". Use list_pages or search_docs to find valid routes.`, true);
19457
+ return text2(`No page found at "${key}". Use list_pages or search_docs to find valid routes.`, true);
18296
19458
  }
18297
- return text(markdown);
19459
+ return text2(markdown);
18298
19460
  }
18299
19461
  if (name === "list_pages") {
18300
- return text(JSON.stringify(data.routes.map((route) => ({
19462
+ return text2(JSON.stringify(data.routes.map((route) => ({
18301
19463
  contentType: route.contentType,
18302
19464
  description: route.description,
18303
19465
  lastModified: route.lastModified,
@@ -18307,9 +19469,9 @@ var buildServer = (data, index) => {
18307
19469
  })), null, 2));
18308
19470
  }
18309
19471
  if (name === "get_navigation") {
18310
- return text(JSON.stringify(data.navigation, null, 2));
19472
+ return text2(JSON.stringify(data.navigation, null, 2));
18311
19473
  }
18312
- return text(`Unknown tool: ${name}`, true);
19474
+ return text2(`Unknown tool: ${name}`, true);
18313
19475
  });
18314
19476
  return server;
18315
19477
  };
@@ -18341,7 +19503,7 @@ var mcpStdioCommand = defineCommand10({
18341
19503
  async run({ args }) {
18342
19504
  let data;
18343
19505
  try {
18344
- data = JSON.parse(await readFile19(args.data, "utf-8"));
19506
+ data = JSON.parse(await readFile22(args.data, "utf-8"));
18345
19507
  } catch (error) {
18346
19508
  const detail = error instanceof Error ? error.message : String(error);
18347
19509
  process.stderr.write(`blume mcp-stdio: cannot load the snapshot at ${args.data}: ${detail}
@@ -18353,10 +19515,10 @@ var mcpStdioCommand = defineCommand10({
18353
19515
  });
18354
19516
 
18355
19517
  // src/cli/commands/preview.ts
18356
- import { existsSync as existsSync21 } from "node:fs";
19518
+ import { existsSync as existsSync23 } from "node:fs";
18357
19519
  import { preview } from "astro";
18358
19520
  import { defineCommand as defineCommand11 } from "citty";
18359
- import { join as join38 } from "pathe";
19521
+ import { join as join40 } from "pathe";
18360
19522
  var previewCommand = defineCommand11({
18361
19523
  args: {
18362
19524
  host: { description: "Network host to bind.", type: "string" },
@@ -18370,7 +19532,7 @@ var previewCommand = defineCommand11({
18370
19532
  const root = process.cwd();
18371
19533
  const { config } = await loadConfig(root);
18372
19534
  const context = resolveProjectContext(root, config);
18373
- if (!existsSync21(join38(context.outDir, "astro.config.mjs"))) {
19535
+ if (!existsSync23(join40(context.outDir, "astro.config.mjs"))) {
18374
19536
  logger.error("No build found. Run `blume build` first.");
18375
19537
  process.exit(1);
18376
19538
  }
@@ -18388,7 +19550,7 @@ var previewCommand = defineCommand11({
18388
19550
  // src/cli/commands/sync.ts
18389
19551
  import { rm as rm4 } from "node:fs/promises";
18390
19552
  import { defineCommand as defineCommand12 } from "citty";
18391
- import { join as join39 } from "pathe";
19553
+ import { join as join41 } from "pathe";
18392
19554
  var syncCommand = defineCommand12({
18393
19555
  args: {
18394
19556
  force: {
@@ -18410,7 +19572,7 @@ var syncCommand = defineCommand12({
18410
19572
  if (args.force) {
18411
19573
  const { config } = await loadConfig(root);
18412
19574
  const context = resolveProjectContext(root, config);
18413
- await rm4(join39(context.outDir, "cache"), { force: true, recursive: true });
19575
+ await rm4(join41(context.outDir, "cache"), { force: true, recursive: true });
18414
19576
  logger.info("Cleared source cache.");
18415
19577
  }
18416
19578
  const lock = readDevLock(resolveRuntimeDir(root));
@@ -18428,13 +19590,13 @@ var syncCommand = defineCommand12({
18428
19590
  });
18429
19591
 
18430
19592
  // src/cli/commands/validate.ts
18431
- import { existsSync as existsSync23 } from "node:fs";
19593
+ import { existsSync as existsSync25 } from "node:fs";
18432
19594
  import { defineCommand as defineCommand13 } from "citty";
18433
- import { join as join41 } from "pathe";
19595
+ import { join as join43 } from "pathe";
18434
19596
 
18435
19597
  // src/core/links.ts
18436
- import { existsSync as existsSync22 } from "node:fs";
18437
- import { basename as basename7, join as join40 } from "pathe";
19598
+ import { existsSync as existsSync24 } from "node:fs";
19599
+ import { basename as basename7, join as join42 } from "pathe";
18438
19600
  var HTTP = /^https?:\/\//iu;
18439
19601
  var PROTOCOL_RELATIVE = /^\/\//u;
18440
19602
  var SCHEME = /^[a-z][a-z0-9+.-]*:/iu;
@@ -18447,7 +19609,7 @@ var decodePercent = (value) => {
18447
19609
  };
18448
19610
  var DOC_EXT = /\.(?:md|mdx)$/iu;
18449
19611
  var FILE_EXT = /\.[a-z0-9]+$/iu;
18450
- var assetIsPresent = (resolved, ctx) => ctx.publicDir !== null && existsSync22(join40(ctx.publicDir, resolved));
19612
+ var assetIsPresent = (resolved, ctx) => ctx.publicDir !== null && existsSync24(join42(ctx.publicDir, resolved));
18451
19613
  var NUMERIC_PREFIX3 = /^\d+[-_.]/u;
18452
19614
  var isIndexPage = (page2) => /^index\.(?:md|mdx)$/iu.test(basename7(page2.navPath).replace(NUMERIC_PREFIX3, ""));
18453
19615
  var applyRelativePart = (segments, part) => {
@@ -18658,12 +19820,12 @@ var validateCommand = defineCommand13({
18658
19820
  });
18659
19821
  extraRoutes.push(...manifest.routes.flatMap((route) => route.fallback ? [route.path] : []));
18660
19822
  }
18661
- const publicDir = join41(root, "public");
19823
+ const publicDir = join43(root, "public");
18662
19824
  diagnostics.push(...await validateLinks(project.graph, {
18663
19825
  basePath: project.config.basePath,
18664
19826
  checkExternal: Boolean(args.external),
18665
19827
  extraRoutes,
18666
- publicDir: existsSync23(publicDir) ? publicDir : null,
19828
+ publicDir: existsSync25(publicDir) ? publicDir : null,
18667
19829
  redirects: project.config.redirects
18668
19830
  }));
18669
19831
  } catch (error) {
@@ -18727,5 +19889,5 @@ process.on("unhandledRejection", (error) => {
18727
19889
  });
18728
19890
  runMain(main);
18729
19891
 
18730
- //# debugId=0CA37F943036C96964756E2164756E21
19892
+ //# debugId=DE086E5CDF1627AA64756E2164756E21
18731
19893
  //# sourceMappingURL=index.js.map