blume 1.2.0 → 1.3.0

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 (77) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/cli/index.js +1715 -539
  3. package/dist/cli/index.js.map +40 -31
  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 +17 -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/ask-context.ts +7 -1
  28. package/src/ai/ask-data.ts +1 -0
  29. package/src/ai/link-headers.ts +52 -0
  30. package/src/ai/llms.ts +12 -1
  31. package/src/ai/markdown.ts +15 -2
  32. package/src/ai/mcp/data.ts +7 -0
  33. package/src/ai/mcp/discovery.ts +70 -15
  34. package/src/ai/mcp/server.ts +14 -8
  35. package/src/ai/mcp/stdio.ts +4 -1
  36. package/src/ai/skills.ts +193 -0
  37. package/src/ai/tar.ts +104 -0
  38. package/src/ai/web-bot-auth.ts +30 -0
  39. package/src/astro/generate.ts +116 -6
  40. package/src/astro/integration.ts +52 -14
  41. package/src/astro/templates.ts +191 -37
  42. package/src/audit/catalog.ts +20 -0
  43. package/src/audit/checks/dns-aid.ts +190 -0
  44. package/src/audit/report.ts +5 -0
  45. package/src/audit/run.ts +2 -0
  46. package/src/cli/commands/build.ts +178 -9
  47. package/src/cli/init/scaffold.ts +1 -1
  48. package/src/components/islands/ask-ai.tsx +4 -1
  49. package/src/components/islands/webmcp.ts +203 -0
  50. package/src/components/layout/NavTree.astro +4 -4
  51. package/src/components/layout/PageLayout.astro +2 -0
  52. package/src/components/layout/ReferenceLayout.astro +2 -0
  53. package/src/components/layout/RootLayout.astro +63 -11
  54. package/src/components/layout/Search.astro +2 -2
  55. package/src/components/layout/WebMcp.astro +49 -0
  56. package/src/components/layout/search/orama.ts +5 -2
  57. package/src/core/config-input.ts +143 -11
  58. package/src/core/config.ts +17 -1
  59. package/src/core/content-assets.ts +199 -0
  60. package/src/core/data.ts +21 -5
  61. package/src/core/diagnostics.ts +6 -5
  62. package/src/core/i18n-ui.ts +19 -28
  63. package/src/core/project-graph.ts +6 -0
  64. package/src/core/schema.ts +224 -71
  65. package/src/core/sources/normalize.ts +5 -5
  66. package/src/deploy/headers.ts +45 -3
  67. package/src/deploy/vercel-negotiation.ts +233 -0
  68. package/src/markdown/mermaid.ts +7 -1
  69. package/src/markdown/table-wrap.ts +33 -1
  70. package/src/og/card.ts +91 -22
  71. package/src/og/derive.ts +200 -0
  72. package/src/og/index.ts +6 -1
  73. package/src/search/orama-index.ts +151 -7
  74. package/src/theme/entry.ts +34 -13
  75. package/src/theme/fonts.ts +183 -30
  76. package/dist/types/og/card.d.ts +0 -63
  77. 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}
@@ -3903,10 +4161,10 @@ var searchClientImport = (module) => `import { createSearch as create } from "bl
3903
4161
  `;
3904
4162
  var SEARCH_BASE_IMPORT = `import { joinBase } from "blume/components/islands/base-path.ts";
3905
4163
  `;
3906
- var staticSearchClient = (module) => `${SEARCH_CLIENT_HEADER}${searchClientImport(module)}${SEARCH_BASE_IMPORT}
4164
+ var staticSearchClient = (module, locale) => `${SEARCH_CLIENT_HEADER}${searchClientImport(module)}${SEARCH_BASE_IMPORT}
3907
4165
  const indexUrl = joinBase(import.meta.env.BASE_URL, "blume-search.json");
3908
4166
 
3909
- export const createSearch = () => create({ indexUrl });
4167
+ export const createSearch = () => create({ indexUrl${locale ? `, locale: ${JSON.stringify(locale)}` : ""} });
3910
4168
  `;
3911
4169
  var hostedSearchClient = (module, options) => `${SEARCH_CLIENT_HEADER}${searchClientImport(module)}
3912
4170
  export const createSearch = () => create(${JSON.stringify(options)});
@@ -3936,7 +4194,7 @@ var hostedSearchOptions = (search) => {
3936
4194
  var searchClientTemplate = (config) => {
3937
4195
  const { search } = config;
3938
4196
  if (search.provider === "orama" || search.provider === "flexsearch") {
3939
- return staticSearchClient(search.provider);
4197
+ return staticSearchClient(search.provider, search.provider === "orama" ? config.i18n?.defaultLocale : undefined);
3940
4198
  }
3941
4199
  const hosted = hostedSearchOptions(search);
3942
4200
  if (hosted) {
@@ -4016,6 +4274,86 @@ export function GET({ props }: { props: { route: string } }) {
4016
4274
  });
4017
4275
  }
4018
4276
  `;
4277
+ var contentAssetsEndpointTemplate = (stagedAssetsDir) => `// Generated by Blume. Do not edit.
4278
+ import { existsSync } from "node:fs";
4279
+ import { readdir, readFile } from "node:fs/promises";
4280
+ import { join, relative, resolve } from "node:path";
4281
+ import type { APIRoute } from "astro";
4282
+ import assets from "../../generated/content-assets.json";
4283
+
4284
+ export const prerender = true;
4285
+
4286
+ const files = assets as Record<string, string>;
4287
+ const STAGED_DIR = ${JSON.stringify(stagedAssetsDir)};
4288
+
4289
+ const TYPES: Record<string, string> = {
4290
+ ".apng": "image/apng",
4291
+ ".avif": "image/avif",
4292
+ ".bmp": "image/bmp",
4293
+ ".gif": "image/gif",
4294
+ ".ico": "image/x-icon",
4295
+ ".jpeg": "image/jpeg",
4296
+ ".jpg": "image/jpeg",
4297
+ ".png": "image/png",
4298
+ ".svg": "image/svg+xml",
4299
+ ".tiff": "image/tiff",
4300
+ ".webp": "image/webp",
4301
+ };
4302
+
4303
+ const contentType = (path: string): string =>
4304
+ TYPES[path.slice(path.lastIndexOf(".")).toLowerCase()] ??
4305
+ "application/octet-stream";
4306
+
4307
+ const stagedParams = async (): Promise<string[]> => {
4308
+ if (!existsSync(STAGED_DIR)) {
4309
+ return [];
4310
+ }
4311
+ const entries = await readdir(STAGED_DIR, {
4312
+ recursive: true,
4313
+ withFileTypes: true,
4314
+ });
4315
+ return entries
4316
+ .filter((entry) => entry.isFile())
4317
+ .map((entry) =>
4318
+ relative(STAGED_DIR, join(entry.parentPath, entry.name)).replaceAll(
4319
+ "\\\\",
4320
+ "/"
4321
+ )
4322
+ );
4323
+ };
4324
+
4325
+ export async function getStaticPaths() {
4326
+ const params = [
4327
+ ...Object.keys(files).map((key) => "content/" + key),
4328
+ ...(await stagedParams()),
4329
+ ];
4330
+ return params.map((asset) => ({ params: { asset } }));
4331
+ }
4332
+
4333
+ const resolveAsset = (asset: string): string | null => {
4334
+ if (asset.startsWith("content/")) {
4335
+ return files[asset.slice("content/".length)] ?? null;
4336
+ }
4337
+ const abs = resolve(STAGED_DIR, asset);
4338
+ // Traversal guard: the dev server renders on demand, so the param is
4339
+ // attacker-controlled there — never step outside the staged directory.
4340
+ if (!abs.startsWith(STAGED_DIR + "/")) {
4341
+ return null;
4342
+ }
4343
+ return existsSync(abs) ? abs : null;
4344
+ };
4345
+
4346
+ export const GET: APIRoute = async ({ params }) => {
4347
+ const path = resolveAsset(String(params.asset ?? ""));
4348
+ if (!path) {
4349
+ return new Response(null, { status: 404 });
4350
+ }
4351
+ const body = await readFile(path);
4352
+ return new Response(new Uint8Array(body), {
4353
+ headers: { "Content-Type": contentType(path) },
4354
+ });
4355
+ };
4356
+ `;
4019
4357
  var mcpPageFile = (route) => `${trimChar(route, "/")}.ts`;
4020
4358
  var mcpEndpointTemplate = (route) => {
4021
4359
  const clean = trimChar(route, "/");
@@ -4062,8 +4400,9 @@ export function GET({ props }: { props: { section: string } }) {
4062
4400
  });
4063
4401
  }
4064
4402
  `;
4065
- var ogEndpointTemplate = (customRoutes = []) => `// Generated by Blume. Do not edit.
4403
+ var ogEndpointTemplate = (customRoutes = [], og = {}) => `// Generated by Blume. Do not edit.
4066
4404
  import { renderOgImage } from "blume/og";
4405
+ import type { OgFont, OgFontFamilies } from "blume/og";
4067
4406
  import data from "blume:data";
4068
4407
 
4069
4408
  export const prerender = true;
@@ -4073,6 +4412,13 @@ export const prerender = true;
4073
4412
  // (ts(7034)) under a strict tsconfig.
4074
4413
  const customRoutes: { slug: string; title: string }[] = ${JSON.stringify(customRoutes)};
4075
4414
 
4415
+ // Card fonts, resolved at generation time (explicit seo.og.fonts, or derived
4416
+ // from theme.fonts). Local entries carry absolute build-machine paths, which
4417
+ // is why they are baked into this build-only endpoint instead of the runtime
4418
+ // data that pages serialize into HTML.
4419
+ const fonts: OgFont[] = ${JSON.stringify(og.fonts ?? [])};
4420
+ const families: OgFontFamilies | undefined = ${og.families ? JSON.stringify(og.families) : "undefined"};
4421
+
4076
4422
  export function getStaticPaths() {
4077
4423
  const seen = new Set<string>();
4078
4424
  const paths: { params: { slug: string }; props: { title: string } }[] = [];
@@ -4093,32 +4439,23 @@ export function getStaticPaths() {
4093
4439
  return paths;
4094
4440
  }
4095
4441
 
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.
4442
+ // Footer branding shared by every card. The repo slug reuses the header link
4443
+ // URL; the site text (host plus deployment base) is resolved at generate time.
4098
4444
  const repoSlug = data.config.repoUrl
4099
4445
  ? data.config.repoUrl.split("github.com/")[1]
4100
4446
  : 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
4447
 
4112
4448
  export async function GET({ props }: { props: { title: string } }) {
4113
4449
  const png = await renderOgImage({
4114
4450
  accent: data.config.og.palette?.accent ?? data.config.theme.accent.light,
4115
4451
  brand: data.config.title,
4116
- description: data.config.description,
4117
- fonts: data.config.og.fonts,
4452
+ description: data.config.og.description,
4453
+ families,
4454
+ fonts,
4118
4455
  logo: data.config.og.logo,
4119
4456
  palette: data.config.og.palette,
4120
4457
  repo: repoSlug,
4121
- site: siteHost,
4458
+ site: data.config.og.site,
4122
4459
  title: props.title,
4123
4460
  });
4124
4461
  return new Response(new Uint8Array(png), {
@@ -5274,6 +5611,7 @@ var MODULES = [
5274
5611
  structuredDataChecks,
5275
5612
  urlChecks,
5276
5613
  networkChecks,
5614
+ dnsAidChecks,
5277
5615
  externalChecks
5278
5616
  ];
5279
5617
 
@@ -7436,7 +7774,7 @@ var uiStringsObject = z.object({
7436
7774
  generating: z.string().default("Generating…"),
7437
7775
  openInChat: z.string().default("Open in chat"),
7438
7776
  scrollToTop: z.string().default("Scroll to top")
7439
- }).default({}),
7777
+ }).prefault({}),
7440
7778
  ask: z.object({
7441
7779
  ai: z.string().default("AI"),
7442
7780
  clear: z.string().default("Clear conversation"),
@@ -7450,28 +7788,28 @@ var uiStringsObject = z.object({
7450
7788
  tip: z.string().default("Tip: You can open and close chat with"),
7451
7789
  title: z.string().default("Ask AI"),
7452
7790
  you: z.string().default("You")
7453
- }).default({}),
7791
+ }).prefault({}),
7454
7792
  banner: z.object({
7455
7793
  dismiss: z.string().default("Dismiss announcement")
7456
- }).default({}),
7794
+ }).prefault({}),
7457
7795
  changelog: z.object({
7458
7796
  description: z.string().default("Product updates, new features, and fixes from every release."),
7459
7797
  showReleases: z.string().default("Show {version} releases"),
7460
7798
  title: z.string().default("Changelog")
7461
- }).default({}),
7799
+ }).prefault({}),
7462
7800
  content: z.object({
7463
7801
  diagramError: z.string().default("Could not render this diagram.")
7464
- }).default({}),
7802
+ }).prefault({}),
7465
7803
  feedback: z.object({
7466
7804
  no: z.string().default("No"),
7467
7805
  question: z.string().default("Was this page helpful?"),
7468
7806
  thanks: z.string().default("Thanks for your feedback!"),
7469
7807
  yes: z.string().default("Yes")
7470
- }).default({}),
7808
+ }).prefault({}),
7471
7809
  languageSwitcher: z.object({
7472
7810
  label: z.string().default("Language"),
7473
7811
  untranslated: z.string().default("Not translated")
7474
- }).default({}),
7812
+ }).prefault({}),
7475
7813
  nav: z.object({
7476
7814
  back: z.string().default("Back"),
7477
7815
  breadcrumb: z.string().default("Breadcrumb"),
@@ -7484,19 +7822,19 @@ var uiStringsObject = z.object({
7484
7822
  sections: z.string().default("Sections"),
7485
7823
  toggleNavigation: z.string().default("Toggle navigation"),
7486
7824
  toggleTheme: z.string().default("Toggle color theme")
7487
- }).default({}),
7825
+ }).prefault({}),
7488
7826
  notFound: z.object({
7489
7827
  description: z.string().default("We couldn't find the page you're looking for."),
7490
7828
  home: z.string().default("Back to home"),
7491
7829
  title: z.string().default("Page not found")
7492
- }).default({}),
7830
+ }).prefault({}),
7493
7831
  page: z.object({
7494
7832
  lastUpdated: z.string().default("Last updated on"),
7495
7833
  next: z.string().default("Next"),
7496
7834
  pagination: z.string().default("Pagination"),
7497
7835
  previous: z.string().default("Previous"),
7498
7836
  skipToContent: z.string().default("Skip to content")
7499
- }).default({}),
7837
+ }).prefault({}),
7500
7838
  search: z.object({
7501
7839
  all: z.string().default("All"),
7502
7840
  allLanguages: z.string().default("All languages"),
@@ -7513,14 +7851,13 @@ var uiStringsObject = z.object({
7513
7851
  popular: z.string().default("Popular"),
7514
7852
  preview: z.string().default("preview"),
7515
7853
  results: z.string().default("Results")
7516
- }).default({}),
7854
+ }).prefault({}),
7517
7855
  toc: z.object({
7518
7856
  title: z.string().default("On this page")
7519
- }).default({})
7857
+ }).prefault({})
7520
7858
  });
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);
7859
+ var uiStringsSchema = uiStringsObject.prefault({});
7860
+ var EN_UI = uiStringsObject.parse({});
7524
7861
  var uiStringsOverrideSchema = z.record(z.string(), z.record(z.string(), z.string()));
7525
7862
  var uiLocaleOverridesSchema = z.record(z.string(), uiStringsOverrideSchema);
7526
7863
  var mergeUI = (base, override) => {
@@ -7570,7 +7907,7 @@ var sidebarMetaSchema = z2.strictObject({
7570
7907
  });
7571
7908
  var xHandleSchema = z2.string().transform(normalizeXHandle).optional();
7572
7909
  var seoMetaSchema = z2.strictObject({
7573
- canonical: z2.string().url().optional(),
7910
+ canonical: z2.url().optional(),
7574
7911
  description: z2.string().optional(),
7575
7912
  image: z2.string().optional(),
7576
7913
  noindex: z2.boolean().default(false),
@@ -7600,7 +7937,7 @@ var authorSchema = z2.union([
7600
7937
  }).catchall(z2.unknown())
7601
7938
  ]);
7602
7939
  var pageMetaBaseSchema = z2.strictObject({
7603
- ai: aiMetaSchema.default({}),
7940
+ ai: aiMetaSchema.prefault({}),
7604
7941
  authors: z2.union([authorSchema, z2.array(authorSchema)]).optional(),
7605
7942
  changelog: changelogMetaSchema.optional(),
7606
7943
  date: dateSchema.optional(),
@@ -7611,9 +7948,9 @@ var pageMetaBaseSchema = z2.strictObject({
7611
7948
  icon: iconName.optional(),
7612
7949
  lastModified: dateSchema.optional(),
7613
7950
  noindex: z2.boolean().default(false),
7614
- search: searchMetaSchema.default({}),
7615
- seo: seoMetaSchema.default({}),
7616
- sidebar: sidebarMetaSchema.default({}),
7951
+ search: searchMetaSchema.prefault({}),
7952
+ seo: seoMetaSchema.prefault({}),
7953
+ sidebar: sidebarMetaSchema.prefault({}),
7617
7954
  slug: z2.string().optional(),
7618
7955
  title: z2.string().optional(),
7619
7956
  type: z2.string().optional()
@@ -7773,11 +8110,31 @@ var sidebarItemSchema = z2.lazy(() => z2.union([
7773
8110
  root: z2.string().optional()
7774
8111
  })
7775
8112
  ]));
7776
- var fontSlug = z2.string().superRefine((value, ctx) => {
7777
- if (!isFontSlug(value)) {
8113
+ var fontFallbackSchema = z2.enum(["sans", "serif", "mono"]);
8114
+ var remoteFontSchema = z2.strictObject({
8115
+ fallback: fontFallbackSchema.optional(),
8116
+ name: z2.string().min(1),
8117
+ provider: z2.enum(["google", "fontsource", "bunny", "fontshare"]).default("google"),
8118
+ weights: z2.array(z2.union([z2.number().int().positive(), z2.string().regex(/^\d+\.\.\d+$/u)])).nonempty().optional()
8119
+ });
8120
+ var localFontVariantSchema = z2.strictObject({
8121
+ src: z2.string().min(1),
8122
+ style: z2.enum(["normal", "italic", "oblique"]).optional(),
8123
+ weight: z2.union([
8124
+ z2.number().int().positive(),
8125
+ z2.string().regex(/^\d+(?:\.\.\d+)?$/u)
8126
+ ]).optional()
8127
+ });
8128
+ var localFontSchema = z2.strictObject({
8129
+ fallback: fontFallbackSchema.optional(),
8130
+ name: z2.string().min(1),
8131
+ variants: z2.array(localFontVariantSchema).nonempty()
8132
+ });
8133
+ var fontValueSchema = z2.union([z2.string(), remoteFontSchema, localFontSchema]).superRefine((value, ctx) => {
8134
+ if (typeof value === "string" && !isFontSlug(value)) {
7778
8135
  ctx.addIssue({
7779
8136
  code: z2.ZodIssueCode.custom,
7780
- message: `Unknown font "${value}". Supported fonts: ${FONT_SLUGS.join(", ")}.`
8137
+ 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
8138
  });
7782
8139
  }
7783
8140
  });
@@ -7797,10 +8154,10 @@ var themeConfigSchema = z2.strictObject({
7797
8154
  background: perModeValueSchema,
7798
8155
  backgroundImage: perModeValueSchema,
7799
8156
  fonts: z2.strictObject({
7800
- body: fontSlug.default("inter"),
7801
- display: fontSlug.default("inter-tight"),
7802
- mono: fontSlug.default("ibm-plex-mono")
7803
- }).default({}),
8157
+ body: fontValueSchema.default("inter"),
8158
+ display: fontValueSchema.default("inter-tight"),
8159
+ mono: fontValueSchema.default("ibm-plex-mono")
8160
+ }).prefault({}),
7804
8161
  layout: z2.enum(["sidebar"]).default("sidebar"),
7805
8162
  mode: z2.enum(["system", "light", "dark"]).default("system"),
7806
8163
  radius: z2.enum(["none", "sm", "md", "lg"]).default("md")
@@ -7850,7 +8207,7 @@ var searchConfigSchema = z2.strictObject({
7850
8207
  algolia: algoliaSearchSchema.optional(),
7851
8208
  indexing: z2.strictObject({
7852
8209
  includeHiddenPages: z2.boolean().default(false)
7853
- }).default({}),
8210
+ }).prefault({}),
7854
8211
  mixedbread: mixedbreadSearchSchema.optional(),
7855
8212
  oramaCloud: oramaCloudSearchSchema.optional(),
7856
8213
  popular: z2.array(searchPopularLinkSchema).default([]),
@@ -7873,6 +8230,22 @@ var askAiProviders = [
7873
8230
  "inkeep",
7874
8231
  "openai-compatible"
7875
8232
  ];
8233
+ var PRIVATE_JWK_PARAMS = ["d", "p", "q", "dp", "dq", "qi", "oth", "k"];
8234
+ var publicJwkSchema = z2.record(z2.string(), z2.unknown()).superRefine((jwk, ctx) => {
8235
+ if (typeof jwk.kty !== "string" || jwk.kty.length === 0) {
8236
+ ctx.addIssue({
8237
+ code: z2.ZodIssueCode.custom,
8238
+ message: 'A JWK must declare its key type ("kty").'
8239
+ });
8240
+ }
8241
+ const leaked = PRIVATE_JWK_PARAMS.filter((param) => (param in jwk));
8242
+ if (leaked.length > 0) {
8243
+ ctx.addIssue({
8244
+ code: z2.ZodIssueCode.custom,
8245
+ 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.`
8246
+ });
8247
+ }
8248
+ });
7876
8249
  var mcpConfigSchema = z2.strictObject({
7877
8250
  enabled: z2.boolean().default(false),
7878
8251
  instructions: z2.string().optional(),
@@ -7895,7 +8268,7 @@ var askEndpointSchema = z2.string().trim().min(1).refine((value) => {
7895
8268
  var aiConfigSchema = z2.strictObject({
7896
8269
  ask: z2.strictObject({
7897
8270
  apiKeyEnv: z2.string().optional(),
7898
- baseUrl: z2.string().url().optional(),
8271
+ baseUrl: z2.url().optional(),
7899
8272
  enabled: z2.boolean().default(false),
7900
8273
  endpoint: askEndpointSchema.optional(),
7901
8274
  model: z2.string().default("openai/gpt-5.5"),
@@ -7923,7 +8296,12 @@ var aiConfigSchema = z2.strictObject({
7923
8296
  markdownComponents: z2.record(z2.string(), z2.custom((value) => typeof value === "function", {
7924
8297
  message: "Expected a serializer function."
7925
8298
  })).default({}),
7926
- mcp: mcpConfigSchema.default({})
8299
+ mcp: mcpConfigSchema.prefault({}),
8300
+ skills: z2.string().min(1).optional(),
8301
+ webBotAuth: z2.strictObject({
8302
+ keys: z2.array(publicJwkSchema).default([])
8303
+ }).prefault({}),
8304
+ webmcp: z2.boolean().default(true)
7927
8305
  });
7928
8306
  var featuredLinkSchema = z2.strictObject({
7929
8307
  href: z2.string(),
@@ -7940,7 +8318,7 @@ var navigationConfigSchema = z2.strictObject({
7940
8318
  display: sidebarDisplaySchema.default("flat"),
7941
8319
  items: z2.array(sidebarItemSchema).optional()
7942
8320
  })
7943
- ]).default({}).transform((value) => Array.isArray(value) ? { display: "flat", items: value } : value),
8321
+ ]).prefault({}).transform((value) => Array.isArray(value) ? { display: "flat", items: value } : value),
7944
8322
  tabs: z2.array(navTabSchema).default([])
7945
8323
  });
7946
8324
  var exportConfigSchema = z2.union([
@@ -7999,13 +8377,23 @@ var deploymentConfigSchema = z2.strictObject({
7999
8377
  adapter: z2.enum(["vercel", "node", "netlify", "cloudflare"]).nullable().default(null),
8000
8378
  base: z2.string().optional(),
8001
8379
  output: z2.enum(["static", "server"]).default("static"),
8002
- site: z2.string().url().optional()
8380
+ site: z2.url().optional()
8003
8381
  });
8004
8382
  var redirectSchema = z2.strictObject({
8005
8383
  from: z2.string(),
8006
8384
  status: z2.union([z2.literal(301), z2.literal(302), z2.literal(307), z2.literal(308)]).default(301),
8007
8385
  to: z2.string()
8008
8386
  });
8387
+ var imageRemotePatternSchema = z2.strictObject({
8388
+ hostname: z2.string().optional(),
8389
+ pathname: z2.string().optional(),
8390
+ port: z2.string().optional(),
8391
+ protocol: z2.string().optional()
8392
+ });
8393
+ var imageConfigSchema = z2.strictObject({
8394
+ domains: z2.array(z2.string()).default([]),
8395
+ remotePatterns: z2.array(imageRemotePatternSchema).default([])
8396
+ });
8009
8397
  var xConfigSchema = z2.strictObject({
8010
8398
  creator: xHandleSchema,
8011
8399
  handle: xHandleSchema
@@ -8030,13 +8418,21 @@ var ogFontSchema = z2.union([
8030
8418
  name: z2.string(),
8031
8419
  style: z2.union([ogFontStyleSchema, z2.array(ogFontStyleSchema)]).optional(),
8032
8420
  weight: ogFontWeightSchema.optional()
8421
+ }),
8422
+ z2.strictObject({
8423
+ name: z2.string(),
8424
+ src: z2.string().min(1),
8425
+ style: ogFontStyleSchema.optional(),
8426
+ weight: z2.number().int().positive().optional()
8033
8427
  })
8034
8428
  ]);
8035
8429
  var ogConfigSchema = z2.strictObject({
8430
+ description: z2.union([z2.string(), z2.literal(false)]).optional(),
8036
8431
  enabled: z2.boolean().optional(),
8037
8432
  fonts: z2.array(ogFontSchema).optional(),
8038
- logo: z2.string().optional(),
8433
+ logo: z2.union([z2.string(), z2.literal(false)]).optional(),
8039
8434
  palette: ogPaletteSchema.optional(),
8435
+ site: z2.union([z2.string(), z2.literal(false)]).optional(),
8040
8436
  titles: z2.record(z2.string(), z2.string()).optional()
8041
8437
  });
8042
8438
  var rssConfigSchema = z2.strictObject({
@@ -8060,10 +8456,10 @@ var contentSignalsSchema = z2.union([z2.boolean(), contentSignalsObjectSchema]).
8060
8456
  });
8061
8457
  var seoConfigSchema = z2.strictObject({
8062
8458
  agentReadability: z2.boolean().default(true),
8063
- contentSignals: contentSignalsSchema.default(true),
8459
+ contentSignals: contentSignalsSchema.prefault(true),
8064
8460
  og: ogConfigSchema.default({}),
8065
8461
  robots: z2.boolean().default(true),
8066
- rss: rssConfigSchema.default({}),
8462
+ rss: rssConfigSchema.prefault({}),
8067
8463
  sitemap: z2.boolean().default(true),
8068
8464
  structuredData: z2.boolean().default(true),
8069
8465
  x: xConfigSchema.default({})
@@ -8093,7 +8489,7 @@ var codeBlockThemeSchema = z2.strictObject({
8093
8489
  light: codeThemeSchema.default("github-light")
8094
8490
  });
8095
8491
  var codeBlocksConfigSchema = z2.strictObject({
8096
- theme: codeBlockThemeSchema.default({})
8492
+ theme: codeBlockThemeSchema.prefault({})
8097
8493
  });
8098
8494
  var examplesConfigSchema = z2.union([
8099
8495
  z2.string(),
@@ -8124,8 +8520,8 @@ var codeConfigSchema = z2.strictObject({
8124
8520
  wrap: z2.boolean().default(false)
8125
8521
  });
8126
8522
  var markdownConfigSchema = z2.strictObject({
8127
- code: codeConfigSchema.default({}),
8128
- codeBlocks: codeBlocksConfigSchema.default({}),
8523
+ code: codeConfigSchema.prefault({}),
8524
+ codeBlocks: codeBlocksConfigSchema.prefault({}),
8129
8525
  headingAnchors: z2.boolean().default(true),
8130
8526
  imageZoom: z2.boolean().default(true)
8131
8527
  });
@@ -8194,32 +8590,33 @@ var tocConfigSchema = z2.union([
8194
8590
  message: "toc.minHeadingLevel must be less than or equal to toc.maxHeadingLevel."
8195
8591
  });
8196
8592
  var blumeConfigSchema = z2.strictObject({
8197
- ai: aiConfigSchema.default({}),
8593
+ ai: aiConfigSchema.prefault({}),
8198
8594
  analytics: analyticsConfigSchema.optional(),
8199
- asyncapi: asyncapiConfigSchema.default({}),
8595
+ asyncapi: asyncapiConfigSchema.prefault({}),
8200
8596
  banner: bannerConfigSchema.optional(),
8201
8597
  basePath: z2.string().optional().transform((value) => normalizeBasePath(value)),
8202
- content: contentConfigSchema.default({}),
8598
+ content: contentConfigSchema.prefault({}),
8203
8599
  dateFormat: dateFormatConfigSchema.default({ dateStyle: "long" }),
8204
- deployment: deploymentConfigSchema.default({}),
8600
+ deployment: deploymentConfigSchema.prefault({}),
8205
8601
  description: z2.string().optional(),
8206
- examples: examplesConfigSchema.default("examples"),
8207
- export: exportConfigSchema.default(false),
8602
+ examples: examplesConfigSchema.prefault("examples"),
8603
+ export: exportConfigSchema.prefault(false),
8208
8604
  feedback: z2.boolean().default(true),
8209
- frontmatter: frontmatterConfigSchema.default({}),
8605
+ frontmatter: frontmatterConfigSchema.prefault({}),
8210
8606
  github: githubConfigSchema.optional(),
8211
8607
  i18n: i18nConfigSchema.optional(),
8608
+ image: imageConfigSchema.prefault({}),
8212
8609
  integrations: z2.array(z2.custom()).default([]),
8213
8610
  lastModified: lastModifiedConfigSchema.default(false),
8214
8611
  logo: logoConfigSchema.optional(),
8215
- markdown: markdownConfigSchema.default({}),
8216
- navigation: navigationConfigSchema.default({}),
8217
- openapi: openapiConfigSchema.default({}),
8218
- react: reactConfigSchema.default({}),
8612
+ markdown: markdownConfigSchema.prefault({}),
8613
+ navigation: navigationConfigSchema.prefault({}),
8614
+ openapi: openapiConfigSchema.prefault({}),
8615
+ react: reactConfigSchema.prefault({}),
8219
8616
  redirects: z2.array(redirectSchema).default([]),
8220
- search: searchConfigSchema.default({}),
8221
- seo: seoConfigSchema.default({}),
8222
- theme: themeConfigSchema.default({}),
8617
+ search: searchConfigSchema.prefault({}),
8618
+ seo: seoConfigSchema.prefault({}),
8619
+ theme: themeConfigSchema.prefault({}),
8223
8620
  title: z2.string().default("Documentation"),
8224
8621
  toc: tocConfigSchema
8225
8622
  });
@@ -8241,6 +8638,7 @@ var loadConfig = async (root, options = {}) => {
8241
8638
  });
8242
8639
  }
8243
8640
  }
8641
+ const themeFontsConfigured = Boolean(raw && typeof raw === "object" && raw.theme?.fonts !== undefined);
8244
8642
  const parsed = blumeConfigSchema.safeParse(raw ?? {});
8245
8643
  if (!parsed.success) {
8246
8644
  const source = configFile && existsSync6(configFile) ? readFileSync3(configFile, "utf-8") : undefined;
@@ -8275,7 +8673,8 @@ ${moreIssues}`
8275
8673
  seo: { ...config.seo, og: { ...config.seo.og, enabled: ogEnabled } }
8276
8674
  },
8277
8675
  configFile,
8278
- diagnostics: []
8676
+ diagnostics: [],
8677
+ themeFontsConfigured
8279
8678
  };
8280
8679
  };
8281
8680
 
@@ -11396,7 +11795,8 @@ var scanProject = async (root, options = {}) => {
11396
11795
  graph,
11397
11796
  manifest,
11398
11797
  mode,
11399
- sources
11798
+ sources,
11799
+ themeFontsConfigured: configResult.themeFontsConfigured
11400
11800
  };
11401
11801
  };
11402
11802
 
@@ -11580,11 +11980,11 @@ var auditCommand = defineCommand2({
11580
11980
  });
11581
11981
 
11582
11982
  // 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";
11983
+ import { existsSync as existsSync18 } from "node:fs";
11984
+ import { mkdir as mkdir7, readdir as readdir2, readFile as readFile17, stat as stat3, writeFile as writeFile8 } from "node:fs/promises";
11585
11985
  import { build } from "astro";
11586
11986
  import { defineCommand as defineCommand3 } from "citty";
11587
- import { join as join28 } from "pathe";
11987
+ import { dirname as dirname12, join as join30, resolve as resolve10 } from "pathe";
11588
11988
 
11589
11989
  // src/deploy/xml.ts
11590
11990
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
@@ -11688,6 +12088,19 @@ var askApiUrl = (endpoint, site, abs) => {
11688
12088
  }
11689
12089
  return site && endpoint.startsWith("/") ? `${site.replace(/\/+$/u, "")}${endpoint}` : endpoint;
11690
12090
  };
12091
+ var wellKnownArtifacts = (config, abs) => {
12092
+ const artifacts = {};
12093
+ if (config.ai.webBotAuth.keys.length > 0) {
12094
+ artifacts.httpMessageSignaturesDirectory = abs("/.well-known/http-message-signatures-directory");
12095
+ }
12096
+ if (hasApiCatalog(config)) {
12097
+ artifacts.apiCatalog = abs("/.well-known/api-catalog");
12098
+ }
12099
+ if (config.ai.skills) {
12100
+ artifacts.agentSkills = abs("/.well-known/agent-skills/index.json");
12101
+ }
12102
+ return artifacts;
12103
+ };
11691
12104
  var buildAgentReadability = (project) => {
11692
12105
  const { config } = project;
11693
12106
  if (!config.seo.agentReadability) {
@@ -11699,9 +12112,10 @@ var buildAgentReadability = (project) => {
11699
12112
  const based = withBasePath(deployBase, path);
11700
12113
  return site ? `${site.replace(/\/+$/u, "")}${based}` : based;
11701
12114
  };
12115
+ const negotiates = config.deployment.output === "server" && config.deployment.adapter === "vercel";
11702
12116
  const artifacts = {
11703
12117
  markdown: {
11704
- contentNegotiation: "text/markdown",
12118
+ ...negotiates ? { contentNegotiation: "text/markdown" } : {},
11705
12119
  pattern: abs("/{route}.md")
11706
12120
  }
11707
12121
  };
@@ -11718,6 +12132,7 @@ var buildAgentReadability = (project) => {
11718
12132
  if (config.ai.ask?.enabled) {
11719
12133
  artifacts.askApi = askApiUrl(config.ai.ask.endpoint, site, abs);
11720
12134
  }
12135
+ Object.assign(artifacts, wellKnownArtifacts(config, abs));
11721
12136
  if (site && config.seo.sitemap) {
11722
12137
  artifacts.sitemap = abs("/sitemap.xml");
11723
12138
  }
@@ -11743,8 +12158,113 @@ var buildAgentReadability = (project) => {
11743
12158
  return manifest;
11744
12159
  };
11745
12160
 
12161
+ // src/core/content-assets.ts
12162
+ import { existsSync as existsSync9 } from "node:fs";
12163
+ import { readFile as readFile8 } from "node:fs/promises";
12164
+ import { dirname as dirname7, extname as extname5, relative as relative11, resolve as resolve5 } from "pathe";
12165
+ var CONTENT_ASSETS_PREFIX = "/blume-assets/content";
12166
+ var IMAGE_EXTENSIONS = new Set([
12167
+ ".apng",
12168
+ ".avif",
12169
+ ".bmp",
12170
+ ".gif",
12171
+ ".ico",
12172
+ ".jpeg",
12173
+ ".jpg",
12174
+ ".png",
12175
+ ".svg",
12176
+ ".tiff",
12177
+ ".webp"
12178
+ ]);
12179
+ var isRelativeTarget = (target) => !(target.startsWith("/") || target.startsWith("#")) && !URL.canParse(target);
12180
+ var contentAssetParam = (projectRoot, absPath) => {
12181
+ const rel = relative11(projectRoot, absPath);
12182
+ if (rel.startsWith("..")) {
12183
+ return `_/${hashText(absPath)}${extname5(absPath)}`;
12184
+ }
12185
+ return rel;
12186
+ };
12187
+ var resolveRelativeImage = (sourceDir, target) => {
12188
+ if (!isRelativeTarget(target)) {
12189
+ return null;
12190
+ }
12191
+ let decoded = target;
12192
+ try {
12193
+ decoded = decodeURI(target);
12194
+ } catch {}
12195
+ if (!IMAGE_EXTENSIONS.has(extname5(decoded).toLowerCase())) {
12196
+ return null;
12197
+ }
12198
+ const abs = resolve5(sourceDir, decoded);
12199
+ return existsSync9(abs) ? abs : null;
12200
+ };
12201
+ var encodedParam = (param) => param.split("/").map(encodeURIComponent).join("/");
12202
+ var rewriteLine = (line, toUrl2) => {
12203
+ const masked = line.replaceAll(INLINE_CODE, (span) => " ".repeat(span.length));
12204
+ let out = "";
12205
+ let cursor = 0;
12206
+ for (const match of masked.matchAll(MD_IMAGE)) {
12207
+ const target = match.groups?.target ?? "";
12208
+ const url = toUrl2(target);
12209
+ if (url === null) {
12210
+ continue;
12211
+ }
12212
+ const offset = (match.index ?? 0) + targetOffsetIn(match[0], target, match.groups?.title);
12213
+ out += line.slice(cursor, offset) + url;
12214
+ cursor = offset + target.length;
12215
+ }
12216
+ return out + line.slice(cursor);
12217
+ };
12218
+ var rewriteRelativeImages = (options) => {
12219
+ const { source, sourcePath, projectRoot, deployBase, register } = options;
12220
+ const sourceDir = dirname7(sourcePath);
12221
+ const prefix = `${normalizeBasePath(deployBase)}${CONTENT_ASSETS_PREFIX}`;
12222
+ const toUrl2 = (target) => {
12223
+ const abs = resolveRelativeImage(sourceDir, target);
12224
+ if (abs === null) {
12225
+ return null;
12226
+ }
12227
+ const param = contentAssetParam(projectRoot, abs);
12228
+ register?.(param, abs);
12229
+ return `${prefix}/${encodedParam(param)}`;
12230
+ };
12231
+ let fence = null;
12232
+ const lines = source.split(`
12233
+ `).map((line) => {
12234
+ const next = nextFenceState(line, fence);
12235
+ const inFence = fence !== null || next !== null;
12236
+ fence = next;
12237
+ return inFence ? line : rewriteLine(line, toUrl2);
12238
+ });
12239
+ return lines.join(`
12240
+ `);
12241
+ };
12242
+ var collectContentAssets = async (project) => {
12243
+ const files = {};
12244
+ await Promise.all(project.manifest.routes.map(async (route) => {
12245
+ if (!route.sourcePath) {
12246
+ return;
12247
+ }
12248
+ let source;
12249
+ try {
12250
+ source = await readFile8(route.sourcePath, "utf-8");
12251
+ } catch {
12252
+ return;
12253
+ }
12254
+ rewriteRelativeImages({
12255
+ projectRoot: project.context.root,
12256
+ register: (param, abs) => {
12257
+ files[param] = abs;
12258
+ },
12259
+ source,
12260
+ sourcePath: route.sourcePath
12261
+ });
12262
+ }));
12263
+ return files;
12264
+ };
12265
+
11746
12266
  // src/core/sources/read.ts
11747
- import { readFile as readFile8 } from "node:fs/promises";
12267
+ import { readFile as readFile9 } from "node:fs/promises";
11748
12268
  var readEntryText = async (ctx, page) => {
11749
12269
  if (page.body) {
11750
12270
  return page.body.text;
@@ -11755,7 +12275,7 @@ var readEntryText = async (ctx, page) => {
11755
12275
  return await source.read(page.source.ref);
11756
12276
  }
11757
12277
  if (page.sourcePath) {
11758
- return await readFile8(page.sourcePath, "utf-8");
12278
+ return await readFile9(page.sourcePath, "utf-8");
11759
12279
  }
11760
12280
  return "";
11761
12281
  };
@@ -12154,7 +12674,15 @@ var buildFull = async (project) => {
12154
12674
  ...config.ai.markdownComponents
12155
12675
  };
12156
12676
  const sections = await Promise.all(pages.map(async (page) => {
12157
- const raw = await readEntryText(project, page);
12677
+ let raw = await readEntryText(project, page);
12678
+ if (page.sourcePath) {
12679
+ raw = rewriteRelativeImages({
12680
+ deployBase: config.deployment.base,
12681
+ projectRoot: project.context.root,
12682
+ source: raw,
12683
+ sourcePath: page.sourcePath
12684
+ });
12685
+ }
12158
12686
  const parsed = frontmatter_default(raw);
12159
12687
  const body = downlevelComponents(applyAgentVisibility(parsed.content), components, parsed.data).trim();
12160
12688
  const url = pageUrl(page.route, config.deployment.site, normalizeBasePath(config.deployment.base));
@@ -12179,14 +12707,200 @@ var buildLlmsFiles = async (project) => ({
12179
12707
  index: buildIndex(project)
12180
12708
  });
12181
12709
 
12182
- // src/core/gitignore.ts
12183
- import { existsSync as existsSync9 } from "node:fs";
12184
- import { readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
12710
+ // src/ai/skills.ts
12711
+ import { createHash as createHash2 } from "node:crypto";
12712
+ import { readdir, readFile as readFile10, stat as stat2 } from "node:fs/promises";
12185
12713
  import { join as join18 } from "pathe";
12714
+
12715
+ // src/ai/tar.ts
12716
+ import { gzipSync } from "node:zlib";
12717
+ var BLOCK = 512;
12718
+ var NAME_MAX = 100;
12719
+ var encoder = new TextEncoder;
12720
+ var octal = (header, offset, length, value) => {
12721
+ const text = value.toString(8).padStart(length - 1, "0");
12722
+ header.set(encoder.encode(text), offset);
12723
+ header[offset + length - 1] = 0;
12724
+ };
12725
+ var text = (header, offset, value) => {
12726
+ header.set(encoder.encode(value), offset);
12727
+ };
12728
+ var fileHeader = (entry) => {
12729
+ const header = new Uint8Array(BLOCK);
12730
+ text(header, 0, entry.path);
12731
+ octal(header, 100, 8, entry.executable ? 493 : 420);
12732
+ octal(header, 108, 8, 0);
12733
+ octal(header, 116, 8, 0);
12734
+ octal(header, 124, 12, entry.content.byteLength);
12735
+ octal(header, 136, 12, 0);
12736
+ text(header, 156, "0");
12737
+ text(header, 257, "ustar");
12738
+ header[262] = 0;
12739
+ text(header, 263, "00");
12740
+ octal(header, 329, 8, 0);
12741
+ octal(header, 337, 8, 0);
12742
+ header.fill(32, 148, 156);
12743
+ let sum = 0;
12744
+ for (const byte of header) {
12745
+ sum += byte;
12746
+ }
12747
+ text(header, 148, sum.toString(8).padStart(6, "0"));
12748
+ header[154] = 0;
12749
+ header[155] = 32;
12750
+ return header;
12751
+ };
12752
+ var buildTarGz = (entries) => {
12753
+ const blocks = [];
12754
+ for (const entry of entries) {
12755
+ if (encoder.encode(entry.path).byteLength > NAME_MAX) {
12756
+ throw new Error(`tar path exceeds ${NAME_MAX} bytes: ${entry.path}`);
12757
+ }
12758
+ if (entry.path.startsWith("/") || entry.path.split("/").includes("..")) {
12759
+ throw new Error(`tar path must be archive-relative: ${entry.path}`);
12760
+ }
12761
+ blocks.push(fileHeader(entry), entry.content);
12762
+ const overhang = entry.content.byteLength % BLOCK;
12763
+ if (overhang > 0) {
12764
+ blocks.push(new Uint8Array(BLOCK - overhang));
12765
+ }
12766
+ }
12767
+ blocks.push(new Uint8Array(BLOCK * 2));
12768
+ const total = blocks.reduce((sum, block) => sum + block.byteLength, 0);
12769
+ const tar = new Uint8Array(total);
12770
+ let offset = 0;
12771
+ for (const block of blocks) {
12772
+ tar.set(block, offset);
12773
+ offset += block.byteLength;
12774
+ }
12775
+ return new Uint8Array(gzipSync(tar, { level: 9 }));
12776
+ };
12777
+
12778
+ // src/ai/skills.ts
12779
+ var AGENT_SKILLS_DIR = "/.well-known/agent-skills";
12780
+ var AGENT_SKILLS_SCHEMA = "https://schemas.agentskills.io/discovery/0.2.0/schema.json";
12781
+ var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
12782
+ var SKILL_NAME_MAX = 64;
12783
+ var DESCRIPTION_MAX2 = 1024;
12784
+ var sha256 = (content) => `sha256:${createHash2("sha256").update(content).digest("hex")}`;
12785
+ var collectEntries = async (dir, prefix = "") => {
12786
+ const entries = [];
12787
+ const items = await readdir(dir, { withFileTypes: true });
12788
+ for (const item of items.toSorted((a, b) => a.name < b.name ? -1 : 1)) {
12789
+ if (item.name.startsWith(".")) {
12790
+ continue;
12791
+ }
12792
+ const path = join18(dir, item.name);
12793
+ const relative12 = prefix ? `${prefix}/${item.name}` : item.name;
12794
+ if (item.isDirectory()) {
12795
+ entries.push(...await collectEntries(path, relative12));
12796
+ } else if (item.isFile()) {
12797
+ const [content, info] = await Promise.all([readFile10(path), stat2(path)]);
12798
+ entries.push({
12799
+ content: new Uint8Array(content),
12800
+ executable: (info.mode & 64) !== 0,
12801
+ path: relative12
12802
+ });
12803
+ }
12804
+ }
12805
+ return entries;
12806
+ };
12807
+ var skillMeta = (raw, dirName) => {
12808
+ let data;
12809
+ try {
12810
+ ({ data } = frontmatter_default(raw));
12811
+ } catch {
12812
+ return {
12813
+ meta: null,
12814
+ warning: `Skill "${dirName}" has unparsable SKILL.md frontmatter; skipped.`
12815
+ };
12816
+ }
12817
+ const name = typeof data.name === "string" ? data.name : "";
12818
+ const description = typeof data.description === "string" ? data.description : "";
12819
+ if (!(name && description)) {
12820
+ return {
12821
+ meta: null,
12822
+ warning: `Skill "${dirName}" is missing the required "name"/"description" frontmatter; skipped.`
12823
+ };
12824
+ }
12825
+ if (!SKILL_NAME.test(name) || name.length > SKILL_NAME_MAX) {
12826
+ return {
12827
+ meta: null,
12828
+ warning: `Skill "${dirName}" has an invalid name "${name}" (lowercase alphanumerics and single hyphens, max 64 chars); skipped.`
12829
+ };
12830
+ }
12831
+ return {
12832
+ meta: { description: description.slice(0, DESCRIPTION_MAX2), name }
12833
+ };
12834
+ };
12835
+ var collectSkills = async (dir) => {
12836
+ const skills = [];
12837
+ const warnings = [];
12838
+ const items = await readdir(dir, { withFileTypes: true });
12839
+ for (const item of items.toSorted((a, b) => a.name < b.name ? -1 : 1)) {
12840
+ if (!item.isDirectory() || item.name.startsWith(".")) {
12841
+ continue;
12842
+ }
12843
+ const entries = await collectEntries(join18(dir, item.name));
12844
+ const skillMd = entries.find((entry) => entry.path === "SKILL.md");
12845
+ if (!skillMd) {
12846
+ continue;
12847
+ }
12848
+ const { meta, warning } = skillMeta(new TextDecoder().decode(skillMd.content), item.name);
12849
+ if (!meta) {
12850
+ if (warning) {
12851
+ warnings.push(warning);
12852
+ }
12853
+ continue;
12854
+ }
12855
+ const single = entries.length === 1;
12856
+ const content = single ? skillMd.content : buildTarGz(entries);
12857
+ skills.push({
12858
+ content,
12859
+ description: meta.description,
12860
+ digest: sha256(content),
12861
+ name: meta.name,
12862
+ path: single ? `${meta.name}/SKILL.md` : `${meta.name}.tar.gz`,
12863
+ type: single ? "skill-md" : "archive"
12864
+ });
12865
+ }
12866
+ return { skills, warnings };
12867
+ };
12868
+ var buildSkillsIndex = (skills, config) => {
12869
+ const deployBase = normalizeBasePath(config.deployment.base);
12870
+ const index = {
12871
+ $schema: AGENT_SKILLS_SCHEMA,
12872
+ skills: skills.map((skill) => ({
12873
+ description: skill.description,
12874
+ digest: skill.digest,
12875
+ name: skill.name,
12876
+ type: skill.type,
12877
+ url: `${deployBase}${AGENT_SKILLS_DIR}/${skill.path}`
12878
+ }))
12879
+ };
12880
+ return `${JSON.stringify(index, null, 2)}
12881
+ `;
12882
+ };
12883
+
12884
+ // src/ai/web-bot-auth.ts
12885
+ var SIGNATURES_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
12886
+ var SIGNATURES_DIRECTORY_TYPE = "application/http-message-signatures-directory+json";
12887
+ var buildSignaturesDirectory = (config) => {
12888
+ const { keys } = config.ai.webBotAuth;
12889
+ if (keys.length === 0) {
12890
+ return null;
12891
+ }
12892
+ return `${JSON.stringify({ keys }, null, 2)}
12893
+ `;
12894
+ };
12895
+
12896
+ // src/core/gitignore.ts
12897
+ import { existsSync as existsSync10 } from "node:fs";
12898
+ import { readFile as readFile11, writeFile as writeFile6 } from "node:fs/promises";
12899
+ import { join as join19 } from "pathe";
12186
12900
  var gitignoreKey = (line) => line.trim().replace(/\/+$/u, "");
12187
12901
  var ensureGitignore = async (root, entries) => {
12188
- const path = join18(root, ".gitignore");
12189
- const existing = existsSync9(path) ? await readFile9(path, "utf-8") : "";
12902
+ const path = join19(root, ".gitignore");
12903
+ const existing = existsSync10(path) ? await readFile11(path, "utf-8") : "";
12190
12904
  const present = new Set(existing.split(`
12191
12905
  `).flatMap((line) => {
12192
12906
  const key = gitignoreKey(line);
@@ -12238,13 +12952,31 @@ var HEADER_RULES = [
12238
12952
  underBasePath: false
12239
12953
  }
12240
12954
  ];
12241
- var buildNetlifyHeaders = (config) => {
12955
+ var buildNetlifyHeaders = (config, homeLinkHeader) => {
12242
12956
  const deployBase = normalizeBasePath(config.deployment.base);
12243
- return `${HEADER_RULES.map((rule) => {
12957
+ const rules = HEADER_RULES.map((rule) => {
12244
12958
  const prefix = rule.underBasePath ? `${deployBase}${config.basePath}` : deployBase;
12245
12959
  return `${prefix}/*.${rule.ext}
12246
12960
  Content-Type: ${rule.contentType}`;
12247
- }).join(`
12961
+ });
12962
+ if (homeLinkHeader) {
12963
+ rules.push(`${deployBase}/
12964
+ Link: ${homeLinkHeader}`);
12965
+ }
12966
+ if (hasApiCatalog(config)) {
12967
+ rules.push(`${deployBase}${API_CATALOG_PATH}
12968
+ Content-Type: ${API_CATALOG_TYPE}`);
12969
+ }
12970
+ if (config.ai.webBotAuth.keys.length > 0) {
12971
+ rules.push(`${deployBase}${SIGNATURES_DIRECTORY_PATH}
12972
+ Content-Type: ${SIGNATURES_DIRECTORY_TYPE}`);
12973
+ }
12974
+ if (config.ai.skills) {
12975
+ rules.push(`${deployBase}/.well-known/agent-skills/*.md
12976
+ Content-Type: text/markdown; charset=utf-8`, `${deployBase}/.well-known/agent-skills/*.tar.gz
12977
+ Content-Type: application/gzip`);
12978
+ }
12979
+ return `${rules.join(`
12248
12980
  `)}
12249
12981
  `;
12250
12982
  };
@@ -12284,7 +13016,7 @@ var buildRobots = (project) => {
12284
13016
  };
12285
13017
 
12286
13018
  // src/astro/pages.ts
12287
- import { extname as extname5, relative as relative11 } from "pathe";
13019
+ import { extname as extname6, relative as relative12 } from "pathe";
12288
13020
  import { glob as glob4, globSync } from "tinyglobby";
12289
13021
  var PAGE_GLOB = ["**/*.astro"];
12290
13022
  var isPrivatePage = (rel) => rel.split("/").some((segment) => segment.startsWith("_"));
@@ -12292,11 +13024,11 @@ var toPageRoutes = (pagesRoot, files) => {
12292
13024
  files.sort();
12293
13025
  const routes = [];
12294
13026
  for (const file of files) {
12295
- const rel = relative11(pagesRoot, file);
13027
+ const rel = relative12(pagesRoot, file);
12296
13028
  if (isPrivatePage(rel)) {
12297
13029
  continue;
12298
13030
  }
12299
- const withoutExt = rel.slice(0, rel.length - extname5(rel).length);
13031
+ const withoutExt = rel.slice(0, rel.length - extname6(rel).length);
12300
13032
  const parts = withoutExt.split("/");
12301
13033
  if (parts.at(-1) === "index") {
12302
13034
  parts.pop();
@@ -12408,8 +13140,102 @@ ${urls.join(`
12408
13140
  `;
12409
13141
  };
12410
13142
 
13143
+ // src/deploy/vercel-negotiation.ts
13144
+ var ACCEPT_MARKDOWN_HEADER_VALUE = "(.*,)?\\s*text/(x-)?markdown(\\s*[;,].*)?$";
13145
+ var ACCEPT_MARKDOWN_CONDITION = [
13146
+ { key: "accept", type: "header", value: ACCEPT_MARKDOWN_HEADER_VALUE }
13147
+ ];
13148
+ var VARY_ACCEPT = { vary: "Accept" };
13149
+ var MAX_ALTERNATION_LENGTH = 3900;
13150
+ var REGEX_SPECIALS = /[$()*+.?[\]^{|}\\]/gu;
13151
+ var routePattern = (route) => encodeURI(route).replace(REGEX_SPECIALS, "\\$&");
13152
+ var chunkPatterns = (patterns) => {
13153
+ const chunks = [];
13154
+ let current = [];
13155
+ let length = 0;
13156
+ for (const pattern of patterns) {
13157
+ if (current.length > 0 && length + pattern.length + 1 > MAX_ALTERNATION_LENGTH) {
13158
+ chunks.push(current);
13159
+ current = [];
13160
+ length = 0;
13161
+ }
13162
+ current.push(pattern);
13163
+ length += pattern.length + 1;
13164
+ }
13165
+ if (current.length > 0) {
13166
+ chunks.push(current);
13167
+ }
13168
+ return chunks;
13169
+ };
13170
+ var buildNegotiationRoutes = (routePaths) => {
13171
+ const home = routePaths.includes("/");
13172
+ const rest = routePaths.filter((path) => path !== "/").map((path) => routePattern(path));
13173
+ const chunks = chunkPatterns(rest);
13174
+ const rewriteRoutes = home ? [
13175
+ {
13176
+ dest: "/index.md",
13177
+ has: ACCEPT_MARKDOWN_CONDITION,
13178
+ headers: VARY_ACCEPT,
13179
+ src: "^/$"
13180
+ }
13181
+ ] : [];
13182
+ for (const chunk of chunks) {
13183
+ rewriteRoutes.push({
13184
+ dest: "$1.md",
13185
+ has: ACCEPT_MARKDOWN_CONDITION,
13186
+ headers: VARY_ACCEPT,
13187
+ src: `^(${chunk.join("|")})/?$`
13188
+ });
13189
+ }
13190
+ const headerChunks = chunkPatterns(home ? ["/", ...rest] : rest);
13191
+ const headerRoutes = headerChunks.map((chunk) => ({
13192
+ continue: true,
13193
+ headers: VARY_ACCEPT,
13194
+ src: `^(?:${chunk.join("|")})/?$`
13195
+ }));
13196
+ return { headerRoutes, rewriteRoutes };
13197
+ };
13198
+ var HOME_SRC = "^/$";
13199
+ 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;
13200
+ var injectNegotiationRoutes = (configText, routePaths, homeLinkHeader, contentTypeOverrides) => {
13201
+ const overrideEntries = Object.entries(contentTypeOverrides ?? {});
13202
+ if (routePaths.length === 0 && !homeLinkHeader && overrideEntries.length === 0) {
13203
+ return null;
13204
+ }
13205
+ let config;
13206
+ try {
13207
+ config = JSON.parse(configText);
13208
+ } catch {
13209
+ return null;
13210
+ }
13211
+ if (!Array.isArray(config.routes)) {
13212
+ return null;
13213
+ }
13214
+ for (const [path, contentType] of overrideEntries) {
13215
+ config.overrides = { ...config.overrides, [path]: { contentType } };
13216
+ }
13217
+ const routes = config.routes.filter((route) => !isNegotiationRoute(route));
13218
+ const filesystemIndex = routes.findIndex((route) => route.handle === "filesystem");
13219
+ if (filesystemIndex === -1) {
13220
+ return null;
13221
+ }
13222
+ const { headerRoutes, rewriteRoutes } = buildNegotiationRoutes(routePaths);
13223
+ if (homeLinkHeader) {
13224
+ headerRoutes.push({
13225
+ continue: true,
13226
+ headers: { link: homeLinkHeader },
13227
+ src: HOME_SRC
13228
+ });
13229
+ }
13230
+ routes.splice(filesystemIndex + 1, 0, ...headerRoutes);
13231
+ routes.splice(filesystemIndex, 0, ...rewriteRoutes);
13232
+ config.routes = routes;
13233
+ return `${JSON.stringify(config, null, "\t")}
13234
+ `;
13235
+ };
13236
+
12411
13237
  // src/search/build.ts
12412
- import { join as join19 } from "pathe";
13238
+ import { join as join20 } from "pathe";
12413
13239
  var buildSearchIndex = async (outDir) => {
12414
13240
  const pagefind = await import("pagefind");
12415
13241
  const { index } = await pagefind.createIndex({});
@@ -12417,7 +13243,7 @@ var buildSearchIndex = async (outDir) => {
12417
13243
  throw new Error("Failed to create Pagefind index.");
12418
13244
  }
12419
13245
  const result = await index.addDirectory({ path: outDir });
12420
- await index.writeFiles({ outputPath: join19(outDir, "pagefind") });
13246
+ await index.writeFiles({ outputPath: join20(outDir, "pagefind") });
12421
13247
  await pagefind.close();
12422
13248
  return result.page_count;
12423
13249
  };
@@ -12640,14 +13466,14 @@ var syncSearchProvider = async (project, reporter) => {
12640
13466
 
12641
13467
  // src/cli/dev-lock.ts
12642
13468
  import {
12643
- existsSync as existsSync10,
13469
+ existsSync as existsSync11,
12644
13470
  mkdirSync,
12645
13471
  readFileSync as readFileSync5,
12646
13472
  rmSync,
12647
13473
  writeFileSync
12648
13474
  } from "node:fs";
12649
- import { join as join20 } from "pathe";
12650
- var lockPath = (outDir) => join20(outDir, "dev.lock");
13475
+ import { join as join21 } from "pathe";
13476
+ var lockPath = (outDir) => join21(outDir, "dev.lock");
12651
13477
  var isValidPid = (pid) => typeof pid === "number" && Number.isInteger(pid) && pid > 0;
12652
13478
  var parseLock = (raw) => {
12653
13479
  let data;
@@ -12677,7 +13503,7 @@ var isProcessAlive = (pid) => {
12677
13503
  };
12678
13504
  var readDevLock = (outDir) => {
12679
13505
  const path = lockPath(outDir);
12680
- if (!existsSync10(path)) {
13506
+ if (!existsSync11(path)) {
12681
13507
  return null;
12682
13508
  }
12683
13509
  const lock = parseLock(readFileSync5(path, "utf-8"));
@@ -12701,7 +13527,7 @@ class DevLockHeldError extends Error {
12701
13527
  }
12702
13528
  var ownsLock = (outDir) => {
12703
13529
  const path = lockPath(outDir);
12704
- if (!existsSync10(path)) {
13530
+ if (!existsSync11(path)) {
12705
13531
  return false;
12706
13532
  }
12707
13533
  try {
@@ -12758,12 +13584,12 @@ var refuseIfDevRunning = (root, action, options = {}) => {
12758
13584
  };
12759
13585
 
12760
13586
  // src/astro/generate.ts
12761
- import { createHash as createHash2 } from "node:crypto";
12762
- import { existsSync as existsSync14, readFileSync as readFileSync9, realpathSync } from "node:fs";
13587
+ import { createHash as createHash3 } from "node:crypto";
13588
+ import { existsSync as existsSync16, readFileSync as readFileSync9, realpathSync } from "node:fs";
12763
13589
  import {
12764
13590
  lstat,
12765
13591
  mkdir as mkdir6,
12766
- readFile as readFile14,
13592
+ readFile as readFile16,
12767
13593
  readlink,
12768
13594
  realpath,
12769
13595
  rename,
@@ -12773,7 +13599,7 @@ import {
12773
13599
  } from "node:fs/promises";
12774
13600
  import { createRequire as createRequire5 } from "node:module";
12775
13601
  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";
13602
+ import { basename as basename3, dirname as dirname10, join as join28, normalize as normalize3, relative as relative14, resolve as resolve8 } from "pathe";
12777
13603
  import { glob as glob7 } from "tinyglobby";
12778
13604
 
12779
13605
  // src/ai/ask-data.ts
@@ -12784,6 +13610,7 @@ var buildAskData = async (project) => {
12784
13610
  includeWhenDisabled: true
12785
13611
  });
12786
13612
  return {
13613
+ defaultLocale: project.config.i18n?.defaultLocale,
12787
13614
  documents: documents.map((doc) => ({
12788
13615
  content: doc.content,
12789
13616
  description: doc.description,
@@ -12796,7 +13623,7 @@ var buildAskData = async (project) => {
12796
13623
  };
12797
13624
 
12798
13625
  // src/ai/markdown.ts
12799
- import { readFile as readFile10 } from "node:fs/promises";
13626
+ import { readFile as readFile12 } from "node:fs/promises";
12800
13627
  var agentMarkdown = (entry) => entry.md ?? entry.mdx;
12801
13628
  var buildRawMarkdown = async (project) => {
12802
13629
  const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));
@@ -12809,10 +13636,19 @@ var buildRawMarkdown = async (project) => {
12809
13636
  if (page) {
12810
13637
  return await readEntryText(project, page);
12811
13638
  }
12812
- return route.sourcePath ? await readFile10(route.sourcePath, "utf-8") : "";
13639
+ return route.sourcePath ? await readFile12(route.sourcePath, "utf-8") : "";
12813
13640
  };
12814
13641
  const entries = await Promise.all(project.manifest.routes.map(async (route) => {
12815
- const source = applyAgentVisibility(await readRoute(route));
13642
+ let text2 = await readRoute(route);
13643
+ if (route.sourcePath) {
13644
+ text2 = rewriteRelativeImages({
13645
+ deployBase: project.config.deployment.base,
13646
+ projectRoot: project.context.root,
13647
+ source: text2,
13648
+ sourcePath: route.sourcePath
13649
+ });
13650
+ }
13651
+ const source = applyAgentVisibility(text2);
12816
13652
  const md = downlevelComponents(source, components, frontmatter_default(source).data);
12817
13653
  const entry = md === source ? { mdx: source } : { md, mdx: source };
12818
13654
  return [route.path, entry];
@@ -12851,6 +13687,7 @@ var buildMcpData = async (project) => {
12851
13687
  }
12852
13688
  return {
12853
13689
  base: normalizeBasePath(config.deployment.base),
13690
+ defaultLocale: config.i18n?.defaultLocale,
12854
13691
  documents: documents.map((doc) => ({
12855
13692
  content: doc.content,
12856
13693
  description: doc.description,
@@ -12910,19 +13747,42 @@ var buildMcpDiscovery = (input) => ({
12910
13747
  }
12911
13748
  ]
12912
13749
  });
12913
- var buildMcpServerCard = (input) => ({
12914
- description: `Model Context Protocol server for the ${input.name} documentation.`,
12915
- name: input.name,
12916
- tools: MCP_TOOLS.map((tool) => ({
12917
- annotations: tool.annotations,
12918
- description: tool.description,
12919
- name: tool.name,
12920
- title: tool.title
12921
- })),
12922
- transport: "streamable-http",
12923
- url: serverUrl(input),
12924
- version: input.version
12925
- });
13750
+ var SERVER_CARD_SCHEMA = "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json";
13751
+ var CARD_TEXT_MAX = 100;
13752
+ var truncate = (text2) => text2.length > CARD_TEXT_MAX ? `${text2.slice(0, CARD_TEXT_MAX - 1)}…` : text2;
13753
+ var reverseDnsName = (input) => {
13754
+ let namespace = "localhost";
13755
+ if (input.site) {
13756
+ try {
13757
+ namespace = new URL(input.site).hostname.split(".").toReversed().join(".");
13758
+ } catch {}
13759
+ }
13760
+ return `${namespace}/${slugify(input.name) || "docs"}`;
13761
+ };
13762
+ var HTTP_URL2 = /^https?:\/\//u;
13763
+ var buildMcpServerCard = (input) => {
13764
+ const url = serverUrl(input);
13765
+ return {
13766
+ $schema: SERVER_CARD_SCHEMA,
13767
+ capabilities: { tools: { listChanged: false } },
13768
+ description: truncate(`Model Context Protocol server for the ${input.name} documentation.`),
13769
+ name: reverseDnsName(input),
13770
+ ...HTTP_URL2.test(url) ? { remotes: [{ type: "streamable-http", url }] } : {},
13771
+ serverInfo: { name: input.name, version: input.version },
13772
+ title: truncate(input.name),
13773
+ tools: MCP_TOOLS.map((tool) => ({
13774
+ annotations: tool.annotations,
13775
+ description: tool.description,
13776
+ name: tool.name,
13777
+ title: tool.title
13778
+ })),
13779
+ transport: "streamable-http",
13780
+ transports: [{ endpoint: url, type: "streamable-http" }],
13781
+ url,
13782
+ version: input.version,
13783
+ ...input.site ? { websiteUrl: trimEnd(input.site, "/") } : {}
13784
+ };
13785
+ };
12926
13786
 
12927
13787
  // src/core/builtin-tags.ts
12928
13788
  var BUILTIN_MDX_TAGS = new Set([
@@ -12989,8 +13849,8 @@ var validateUsedComponents = (pages, extraTags, registryNames) => {
12989
13849
  };
12990
13850
 
12991
13851
  // src/core/component-overrides.ts
12992
- import { existsSync as existsSync11 } from "node:fs";
12993
- import { dirname as dirname7, extname as extname6, isAbsolute as isAbsolute6, resolve as resolve5 } from "pathe";
13852
+ import { existsSync as existsSync12 } from "node:fs";
13853
+ import { dirname as dirname8, extname as extname7, isAbsolute as isAbsolute6, resolve as resolve6 } from "pathe";
12994
13854
  import ts from "typescript";
12995
13855
  var GROUPS = ["mdx", "layout", "islands"];
12996
13856
  var GROUP_SET = new Set(GROUPS);
@@ -13082,24 +13942,24 @@ var findDefaultExportObject = (sourceFile) => {
13082
13942
  var probeExtension = (base) => {
13083
13943
  for (const extension of COMPONENT_EXTS) {
13084
13944
  const candidate = `${base}.${extension}`;
13085
- if (existsSync11(candidate)) {
13945
+ if (existsSync12(candidate)) {
13086
13946
  return candidate;
13087
13947
  }
13088
13948
  }
13089
13949
  return null;
13090
13950
  };
13091
13951
  var toImport = (specifier, imported, dir) => {
13092
- const relative12 = specifier.startsWith(".") || isAbsolute6(specifier);
13952
+ const relative13 = specifier.startsWith(".") || isAbsolute6(specifier);
13093
13953
  let path = specifier;
13094
- let extension = extname6(specifier).slice(1).toLowerCase();
13095
- if (relative12) {
13096
- const absolute = isAbsolute6(specifier) ? specifier : resolve5(dir, specifier);
13954
+ let extension = extname7(specifier).slice(1).toLowerCase();
13955
+ if (relative13) {
13956
+ const absolute = isAbsolute6(specifier) ? specifier : resolve6(dir, specifier);
13097
13957
  if (extension) {
13098
13958
  path = absolute;
13099
13959
  } else {
13100
13960
  const probed = probeExtension(absolute);
13101
13961
  path = probed ?? absolute;
13102
- extension = probed ? extname6(probed).slice(1).toLowerCase() : "";
13962
+ extension = probed ? extname7(probed).slice(1).toLowerCase() : "";
13103
13963
  }
13104
13964
  }
13105
13965
  return {
@@ -13248,7 +14108,7 @@ var analyzeComponentOverrides = (source, filePath) => {
13248
14108
  return result;
13249
14109
  }
13250
14110
  const imports = collectImports(sourceFile);
13251
- const dir = dirname7(filePath);
14111
+ const dir = dirname8(filePath);
13252
14112
  for (const property of object.properties) {
13253
14113
  collectGroupOverrides(property, imports, dir, result);
13254
14114
  }
@@ -13256,16 +14116,16 @@ var analyzeComponentOverrides = (source, filePath) => {
13256
14116
  };
13257
14117
 
13258
14118
  // src/core/tsconfig-aliases.ts
13259
- import { existsSync as existsSync12, readFileSync as readFileSync6, statSync } from "node:fs";
14119
+ import { existsSync as existsSync13, readFileSync as readFileSync6, statSync } from "node:fs";
13260
14120
  import { createRequire as createRequire3 } from "node:module";
13261
14121
  import { pathToFileURL as pathToFileURL2 } from "node:url";
13262
- import { dirname as dirname8, isAbsolute as isAbsolute7, join as join21, resolve as resolve6 } from "pathe";
13263
- var scanJsonChar = (text, index, inString) => {
13264
- const char = text[index];
14122
+ import { dirname as dirname9, isAbsolute as isAbsolute7, join as join22, resolve as resolve7 } from "pathe";
14123
+ var scanJsonChar = (text2, index, inString) => {
14124
+ const char = text2[index];
13265
14125
  if (inString) {
13266
14126
  if (char === "\\") {
13267
14127
  return {
13268
- append: char + (text[index + 1] ?? ""),
14128
+ append: char + (text2[index + 1] ?? ""),
13269
14129
  inString: true,
13270
14130
  next: index + 2
13271
14131
  };
@@ -13275,35 +14135,35 @@ var scanJsonChar = (text, index, inString) => {
13275
14135
  if (char === '"') {
13276
14136
  return { append: char, inString: true, next: index + 1 };
13277
14137
  }
13278
- if (char === "/" && text[index + 1] === "/") {
13279
- const newline = text.indexOf(`
14138
+ if (char === "/" && text2[index + 1] === "/") {
14139
+ const newline = text2.indexOf(`
13280
14140
  `, index + 2);
13281
14141
  return {
13282
14142
  append: "",
13283
14143
  inString: false,
13284
- next: newline === -1 ? text.length : newline
14144
+ next: newline === -1 ? text2.length : newline
13285
14145
  };
13286
14146
  }
13287
- if (char === "/" && text[index + 1] === "*") {
13288
- const end = text.indexOf("*/", index + 2);
14147
+ if (char === "/" && text2[index + 1] === "*") {
14148
+ const end = text2.indexOf("*/", index + 2);
13289
14149
  return {
13290
14150
  append: "",
13291
14151
  inString: false,
13292
- next: end === -1 ? text.length : end + 2
14152
+ next: end === -1 ? text2.length : end + 2
13293
14153
  };
13294
14154
  }
13295
14155
  return { append: char ?? "", inString: false, next: index + 1 };
13296
14156
  };
13297
- var stripJsonComments = (text) => {
14157
+ var stripJsonComments = (text2) => {
13298
14158
  let out = "";
13299
14159
  let inString = false;
13300
14160
  let index = 0;
13301
- while (index < text.length) {
14161
+ while (index < text2.length) {
13302
14162
  const {
13303
14163
  append,
13304
14164
  inString: nextInString,
13305
14165
  next
13306
- } = scanJsonChar(text, index, inString);
14166
+ } = scanJsonChar(text2, index, inString);
13307
14167
  out += append;
13308
14168
  inString = nextInString;
13309
14169
  index = next;
@@ -13311,9 +14171,9 @@ var stripJsonComments = (text) => {
13311
14171
  return out;
13312
14172
  };
13313
14173
  var TRAILING_COMMA = /,(?<rest>\s*[}\]])/gu;
13314
- var parseJsonc = (text) => {
14174
+ var parseJsonc = (text2) => {
13315
14175
  try {
13316
- const cleaned = stripJsonComments(text).replaceAll(TRAILING_COMMA, "$<rest>");
14176
+ const cleaned = stripJsonComments(text2).replaceAll(TRAILING_COMMA, "$<rest>");
13317
14177
  const value = JSON.parse(cleaned);
13318
14178
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
13319
14179
  } catch {
@@ -13329,15 +14189,15 @@ var isFile = (path) => {
13329
14189
  };
13330
14190
  var resolveExtends = (spec, fromDir) => {
13331
14191
  if (spec.startsWith(".") || isAbsolute7(spec)) {
13332
- const candidates = spec.endsWith(".json") ? [resolve6(fromDir, spec)] : [
13333
- resolve6(fromDir, `${spec}.json`),
13334
- resolve6(fromDir, spec, "tsconfig.json"),
13335
- resolve6(fromDir, spec)
14192
+ const candidates = spec.endsWith(".json") ? [resolve7(fromDir, spec)] : [
14193
+ resolve7(fromDir, `${spec}.json`),
14194
+ resolve7(fromDir, spec, "tsconfig.json"),
14195
+ resolve7(fromDir, spec)
13336
14196
  ];
13337
14197
  return candidates.find(isFile) ?? null;
13338
14198
  }
13339
14199
  try {
13340
- const requireFromDir = createRequire3(pathToFileURL2(join21(fromDir, "_.js")).href);
14200
+ const requireFromDir = createRequire3(pathToFileURL2(join22(fromDir, "_.js")).href);
13341
14201
  for (const sub of [`${spec}/tsconfig.json`, spec]) {
13342
14202
  try {
13343
14203
  return requireFromDir.resolve(sub);
@@ -13347,7 +14207,7 @@ var resolveExtends = (spec, fromDir) => {
13347
14207
  return null;
13348
14208
  };
13349
14209
  var loadPaths = (file, seen) => {
13350
- if (seen.has(file) || !existsSync12(file)) {
14210
+ if (seen.has(file) || !existsSync13(file)) {
13351
14211
  return null;
13352
14212
  }
13353
14213
  seen.add(file);
@@ -13359,7 +14219,7 @@ var loadPaths = (file, seen) => {
13359
14219
  if (options.paths && typeof options.paths === "object") {
13360
14220
  const baseUrl = typeof options.baseUrl === "string" ? options.baseUrl : ".";
13361
14221
  return {
13362
- baseDir: resolve6(dirname8(file), baseUrl),
14222
+ baseDir: resolve7(dirname9(file), baseUrl),
13363
14223
  paths: options.paths
13364
14224
  };
13365
14225
  }
@@ -13368,7 +14228,7 @@ var loadPaths = (file, seen) => {
13368
14228
  if (typeof base !== "string") {
13369
14229
  continue;
13370
14230
  }
13371
- const resolved = resolveExtends(base, dirname8(file));
14231
+ const resolved = resolveExtends(base, dirname9(file));
13372
14232
  const found = resolved ? loadPaths(resolved, seen) : null;
13373
14233
  if (found) {
13374
14234
  return found;
@@ -13386,10 +14246,10 @@ var toAlias = (key, value, baseDir) => {
13386
14246
  if (find === "" || find === "*") {
13387
14247
  return null;
13388
14248
  }
13389
- return { find, replacement: resolve6(baseDir, target) };
14249
+ return { find, replacement: resolve7(baseDir, target) };
13390
14250
  };
13391
14251
  var resolveTsconfigAliases = (root) => {
13392
- const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join21(root, name)).find((file) => existsSync12(file));
14252
+ const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join22(root, name)).find((file) => existsSync13(file));
13393
14253
  if (!entry) {
13394
14254
  return {};
13395
14255
  }
@@ -13407,24 +14267,126 @@ var resolveTsconfigAliases = (root) => {
13407
14267
  return aliases;
13408
14268
  };
13409
14269
 
14270
+ // src/og/derive.ts
14271
+ import { existsSync as existsSync14 } from "node:fs";
14272
+ import { isAbsolute as isAbsolute8, join as join23 } from "pathe";
14273
+ var CARD_WEIGHTS = [400, 600];
14274
+ var absoluteSrc = (root, src) => isAbsolute8(src) ? src : join23(root, src);
14275
+ var googleWeights = (weights) => {
14276
+ const numbers = weights.filter((weight) => typeof weight === "number");
14277
+ const used = numbers.filter((weight) => CARD_WEIGHTS.includes(weight));
14278
+ if (used.length > 0) {
14279
+ return used;
14280
+ }
14281
+ if (numbers.length > 0) {
14282
+ return numbers;
14283
+ }
14284
+ const [first] = weights;
14285
+ return weights.length === 1 && typeof first === "string" ? first : undefined;
14286
+ };
14287
+ var googleOgFont = (name, weights) => {
14288
+ const weight = googleWeights(weights);
14289
+ return weight === undefined ? { name } : { name, weight };
14290
+ };
14291
+ var localOgFonts = (font, root) => font.variants.map((variant) => ({
14292
+ name: font.name,
14293
+ src: absoluteSrc(root, variant.src),
14294
+ ...typeof variant.weight === "number" ? { weight: variant.weight } : {},
14295
+ ...variant.style === "normal" || variant.style === "italic" ? { style: variant.style } : {}
14296
+ }));
14297
+ var roleFonts = (value, root) => {
14298
+ if (typeof value === "string") {
14299
+ if (!isFontSlug(value)) {
14300
+ return null;
14301
+ }
14302
+ const def = GOOGLE_FONTS[value];
14303
+ return [googleOgFont(def.family, def.weights)];
14304
+ }
14305
+ if ("variants" in value) {
14306
+ return localOgFonts(value, root);
14307
+ }
14308
+ const remote = value;
14309
+ if ((remote.provider ?? "google") !== "google") {
14310
+ return null;
14311
+ }
14312
+ return [googleOgFont(remote.name, remote.weights ?? CARD_WEIGHTS)];
14313
+ };
14314
+ var roleFamily = (value) => {
14315
+ if (typeof value === "string") {
14316
+ return isFontSlug(value) ? GOOGLE_FONTS[value].family : null;
14317
+ }
14318
+ return value.name;
14319
+ };
14320
+ var deriveOgFonts = (fonts, root) => {
14321
+ const derived = [];
14322
+ const seen = new Set;
14323
+ const families = {};
14324
+ const roles = [
14325
+ ["title", fonts?.display],
14326
+ ["body", fonts?.body]
14327
+ ];
14328
+ for (const [role, value] of roles) {
14329
+ if (value === undefined) {
14330
+ continue;
14331
+ }
14332
+ const roleEntries = roleFonts(value, root);
14333
+ if (!roleEntries) {
14334
+ continue;
14335
+ }
14336
+ families[role] = roleFamily(value) ?? undefined;
14337
+ for (const entry of roleEntries) {
14338
+ const key = JSON.stringify(entry);
14339
+ if (!seen.has(key)) {
14340
+ seen.add(key);
14341
+ derived.push(entry);
14342
+ }
14343
+ }
14344
+ }
14345
+ return {
14346
+ fonts: derived,
14347
+ ...families.title || families.body ? { families } : {}
14348
+ };
14349
+ };
14350
+ var resolveOgFontSources = (fonts, root) => fonts.map((font) => typeof font !== "string" && ("src" in font) ? { ...font, src: absoluteSrc(root, font.src) } : font);
14351
+ var resolveOgFonts = (options, root) => {
14352
+ if (options.ogFonts) {
14353
+ return { fonts: resolveOgFontSources(options.ogFonts, root) };
14354
+ }
14355
+ return options.themeFontsConfigured ? deriveOgFonts(options.themeFonts, root) : { fonts: [] };
14356
+ };
14357
+ var missingFontFiles = (options, root) => {
14358
+ const sources = [];
14359
+ for (const value of Object.values(options.themeFonts ?? {})) {
14360
+ if (typeof value !== "string" && "variants" in value) {
14361
+ sources.push(...value.variants.map((variant) => absoluteSrc(root, variant.src)));
14362
+ }
14363
+ }
14364
+ for (const font of options.ogFonts) {
14365
+ if (typeof font !== "string" && "src" in font) {
14366
+ sources.push(absoluteSrc(root, font.src));
14367
+ }
14368
+ }
14369
+ return [...new Set(sources)].filter((path) => !existsSync14(path));
14370
+ };
14371
+
13410
14372
  // src/og/logo.ts
13411
- import { existsSync as existsSync13, readFileSync as readFileSync7 } from "node:fs";
13412
- import { join as join22 } from "pathe";
14373
+ import { existsSync as existsSync15, readFileSync as readFileSync7 } from "node:fs";
14374
+ import { join as join24 } from "pathe";
13413
14375
  var resolveOgLogo = (project, source) => {
13414
14376
  if (!source?.toLowerCase().endsWith(".svg")) {
13415
14377
  return;
13416
14378
  }
13417
- const relative12 = source.replace(/^\//u, "");
14379
+ const relative13 = source.replace(/^\//u, "");
13418
14380
  const file = [
13419
- join22(project.context.root, "public", relative12),
13420
- join22(project.context.root, relative12)
13421
- ].find((path) => existsSync13(path));
14381
+ join24(project.context.root, "public", relative13),
14382
+ join24(project.context.root, relative13)
14383
+ ].find((path) => existsSync15(path));
13422
14384
  return file ? readFileSync7(file, "utf-8") : undefined;
13423
14385
  };
13424
14386
 
13425
14387
  // src/openapi/scalar.ts
13426
- import { readFile as readFile11 } from "node:fs/promises";
13427
- import { isAbsolute as isAbsolute8, join as join23 } from "pathe";
14388
+ import { readFile as readFile13 } from "node:fs/promises";
14389
+ import { isAbsolute as isAbsolute9, join as join25 } from "pathe";
13428
14390
 
13429
14391
  // src/theme/palette.ts
13430
14392
  var FALLBACK_ACCENT = "oklch(0.62 0.16 250)";
@@ -13530,9 +14492,9 @@ var specConfiguration = async (spec, root) => {
13530
14492
  if (URL_SPEC2.test(spec)) {
13531
14493
  return { config: { url: spec } };
13532
14494
  }
13533
- const absolute = isAbsolute8(spec) ? spec : join23(root, spec);
14495
+ const absolute = isAbsolute9(spec) ? spec : join25(root, spec);
13534
14496
  try {
13535
- return { config: { content: await readFile11(absolute, "utf-8") } };
14497
+ return { config: { content: await readFile13(absolute, "utf-8") } };
13536
14498
  } catch {
13537
14499
  return {
13538
14500
  config: { url: spec },
@@ -14019,19 +14981,29 @@ blume-diff {
14019
14981
  even though its chrome wrapper is not-prose. */
14020
14982
  .prose :where(pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code) {
14021
14983
  display: block;
14022
- overflow-x: auto;
14023
- padding: 0 1.25rem;
14024
- /* The scroller is only as tall as the code, so an overlay scrollbar would
14025
- draw on top of the last line; hide it (wheel/trackpad/keyboard scrolling
14026
- still works). */
14027
- scrollbar-width: none;
14984
+ /* Tall blocks scroll vertically in place instead of taking the page. Both
14985
+ axes live on the code element for the same reason: the pre stays static
14986
+ so the header bar and copy button never drift with the scroll. */
14987
+ max-height: 24rem;
14988
+ overflow: auto;
14989
+ /* The small bottom inset keeps the horizontal thumb off the last line's
14990
+ descenders now that scrollbars are visible. */
14991
+ padding: 0 1.25rem 0.375rem;
14992
+ /* Thin theme-colored scrollbars, matching the sidebar treatment, so a
14993
+ height-capped block reads as scrollable instead of simply ending.
14994
+ Safari before 18.2 supports neither property and falls back to the
14995
+ platform-default scrollbar — acceptable, since macOS overlays it. */
14996
+ scrollbar-color: var(--blume-border) transparent;
14997
+ scrollbar-width: thin;
14028
14998
  }
14029
14999
 
14030
- .prose
14031
- :where(
14032
- pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code
14033
- )::-webkit-scrollbar {
14034
- display: none;
15000
+ /* The dark border token is too close to the page background to read as a
15001
+ scrollbar thumb; derive a brighter one from the muted foreground instead. */
15002
+ :root[data-theme="dark"]
15003
+ .prose
15004
+ :where(pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code) {
15005
+ scrollbar-color: color-mix(in oklab, var(--blume-muted-foreground) 55%, transparent)
15006
+ transparent;
14035
15007
  }
14036
15008
 
14037
15009
  /* Word wrap (markdown.code.wrap): long lines wrap instead of scrolling. The
@@ -14099,6 +15071,10 @@ pre.blume-source {
14099
15071
 
14100
15072
  pre.blume-source > code {
14101
15073
  flex: 1;
15074
+ /* The pane's measured height is authoritative (it can exceed the prose
15075
+ 24rem cap), so undo the generic prose max-height: the code must fill
15076
+ whatever height the tab was given. */
15077
+ max-height: none;
14102
15078
  min-height: 0;
14103
15079
  overflow: auto;
14104
15080
  }
@@ -14280,6 +15256,13 @@ pre:has(.line.focused):hover .line:not(.focused) {
14280
15256
  display: none !important;
14281
15257
  }
14282
15258
 
15259
+ /* Paper can't scroll: uncap the code scroller so long blocks print in
15260
+ full, same as tab panels are force-expanded below. */
15261
+ .prose :where(pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code) {
15262
+ max-height: none;
15263
+ overflow: visible;
15264
+ }
15265
+
14283
15266
  #blume-content {
14284
15267
  padding: 0 !important;
14285
15268
  }
@@ -14523,13 +15506,13 @@ export const layoutOverrides = { ...(overrides.layout ?? {})${layoutEntries.leng
14523
15506
  };
14524
15507
 
14525
15508
  // src/astro/examples.ts
14526
- import { readFile as readFile13 } from "node:fs/promises";
14527
- import { join as join25, relative as relative12 } from "pathe";
15509
+ import { readFile as readFile15 } from "node:fs/promises";
15510
+ import { join as join27, relative as relative13 } from "pathe";
14528
15511
  import { glob as glob6 } from "tinyglobby";
14529
15512
 
14530
15513
  // src/astro/islands.ts
14531
- import { readFile as readFile12 } from "node:fs/promises";
14532
- import { basename as basename2, join as join24 } from "pathe";
15514
+ import { readFile as readFile14 } from "node:fs/promises";
15515
+ import { basename as basename2, join as join26 } from "pathe";
14533
15516
  import { glob as glob5 } from "tinyglobby";
14534
15517
  var DEFAULT_CLIENT = "visible";
14535
15518
  var VALID_MODES = new Set([
@@ -14558,14 +15541,14 @@ var readClientMode = (source, file, warnings) => {
14558
15541
  return mode;
14559
15542
  };
14560
15543
  var discoverIslands = async (root) => {
14561
- const dir = join24(root, "islands");
15544
+ const dir = join26(root, "islands");
14562
15545
  const matches2 = await glob5(["**/*.{jsx,svelte,tsx,vue}"], {
14563
15546
  absolute: true,
14564
15547
  cwd: dir,
14565
15548
  onlyFiles: true
14566
15549
  });
14567
15550
  const files = matches2.toSorted();
14568
- const sources = await Promise.all(files.map((file) => readFile12(file, "utf-8")));
15551
+ const sources = await Promise.all(files.map((file) => readFile14(file, "utf-8")));
14569
15552
  const islands = [];
14570
15553
  const warnings = [];
14571
15554
  const seen = new Map;
@@ -14621,14 +15604,14 @@ var splitGlobBase = (pattern) => {
14621
15604
  };
14622
15605
  var discoverExamples = async (root, pattern = "examples") => {
14623
15606
  const { base, rest } = GLOB_MAGIC.test(pattern) ? splitGlobBase(pattern) : { base: pattern, rest: DEFAULT_EXAMPLE_GLOB };
14624
- const dir = join25(root, base);
15607
+ const dir = join27(root, base);
14625
15608
  const matches2 = await glob6([rest], {
14626
15609
  absolute: true,
14627
15610
  cwd: dir,
14628
15611
  onlyFiles: true
14629
15612
  });
14630
15613
  const files = matches2.toSorted();
14631
- const sources = await Promise.all(files.map((file) => readFile13(file, "utf-8")));
15614
+ const sources = await Promise.all(files.map((file) => readFile15(file, "utf-8")));
14632
15615
  const examples = [];
14633
15616
  const warnings = [];
14634
15617
  const seen = new Map;
@@ -14638,7 +15621,7 @@ var discoverExamples = async (root, pattern = "examples") => {
14638
15621
  if (!(ext && framework)) {
14639
15622
  return;
14640
15623
  }
14641
- const path = relative12(dir, file).slice(0, -(ext.length + 1));
15624
+ const path = relative13(dir, file).slice(0, -(ext.length + 1));
14642
15625
  const existing = seen.get(path);
14643
15626
  if (existing) {
14644
15627
  warnings.push(`Two examples both resolve to "${path}" ("${existing}" and "${file}"); ignoring the second. Give them distinct paths.`);
@@ -14665,10 +15648,10 @@ var exampleMarkdownLookup = (examples) => Object.fromEntries(examples.map((examp
14665
15648
  ]));
14666
15649
 
14667
15650
  // src/astro/generate.ts
14668
- var BLUME_SRC = join26(packageRoot(), "src");
15651
+ var BLUME_SRC = join28(packageRoot(), "src");
14669
15652
  var canResolveFrom = (fromDir, spec) => {
14670
15653
  try {
14671
- createRequire5(pathToFileURL3(join26(fromDir, "_.js")).href).resolve(spec);
15654
+ createRequire5(pathToFileURL3(join28(fromDir, "_.js")).href).resolve(spec);
14672
15655
  return true;
14673
15656
  } catch {
14674
15657
  return false;
@@ -14679,7 +15662,7 @@ var resolveReactCompiler = (config, needsReact, pkgDir = packageRoot()) => {
14679
15662
  return null;
14680
15663
  }
14681
15664
  try {
14682
- return createRequire5(pathToFileURL3(join26(pkgDir, "_.js")).href).resolve("babel-plugin-react-compiler");
15665
+ return createRequire5(pathToFileURL3(join28(pkgDir, "_.js")).href).resolve("babel-plugin-react-compiler");
14683
15666
  } catch {
14684
15667
  return null;
14685
15668
  }
@@ -14689,7 +15672,7 @@ var reactCompilerWarnings = (config, needsReact, compilerPath) => needsReact &&
14689
15672
  ] : [];
14690
15673
  var resolveAstroPackageJson = (modulesDir) => {
14691
15674
  try {
14692
- return realpathSync(join26(modulesDir, "astro", "package.json"));
15675
+ return realpathSync(join28(modulesDir, "astro", "package.json"));
14693
15676
  } catch {
14694
15677
  return null;
14695
15678
  }
@@ -14697,12 +15680,12 @@ var resolveAstroPackageJson = (modulesDir) => {
14697
15680
  var resolvedAstroHit = (fromDir) => {
14698
15681
  let dir = normalize3(fromDir);
14699
15682
  while (true) {
14700
- const modulesDir = join26(dir, "node_modules");
15683
+ const modulesDir = join28(dir, "node_modules");
14701
15684
  const pkg = resolveAstroPackageJson(modulesDir);
14702
15685
  if (pkg) {
14703
15686
  return { modulesDir, pkg };
14704
15687
  }
14705
- const parent = dirname9(dir);
15688
+ const parent = dirname10(dir);
14706
15689
  if (parent === dir) {
14707
15690
  return null;
14708
15691
  }
@@ -14717,10 +15700,10 @@ var sameRealDir = (a, b) => {
14717
15700
  }
14718
15701
  };
14719
15702
  var depsCandidates = (pkgDir) => [
14720
- join26(pkgDir, "node_modules"),
14721
- dirname9(pkgDir)
15703
+ join28(pkgDir, "node_modules"),
15704
+ dirname10(pkgDir)
14722
15705
  ];
14723
- var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync14(join26(dir, ...segments))) ?? null;
15706
+ var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync16(join28(dir, ...segments))) ?? null;
14724
15707
  var linkDepsJunction = async (link, depsDir) => {
14725
15708
  let existing;
14726
15709
  try {
@@ -14733,13 +15716,13 @@ var linkDepsJunction = async (link, depsDir) => {
14733
15716
  return;
14734
15717
  }
14735
15718
  try {
14736
- if (resolve7(dirname9(link), await readlink(link)) === resolve7(depsDir)) {
15719
+ if (resolve8(dirname10(link), await readlink(link)) === resolve8(depsDir)) {
14737
15720
  return;
14738
15721
  }
14739
15722
  } catch {}
14740
15723
  await rm2(link, { force: true });
14741
15724
  }
14742
- await mkdir6(dirname9(link), { recursive: true });
15725
+ await mkdir6(dirname10(link), { recursive: true });
14743
15726
  await symlink(depsDir, link, "junction");
14744
15727
  };
14745
15728
  var readPkgVersion = (pkgJsonPath) => {
@@ -14772,7 +15755,7 @@ var dropStaleDepsLink = async (link, pkgDir) => {
14772
15755
  let linkedBlume;
14773
15756
  let runningBlume;
14774
15757
  try {
14775
- linkedBlume = await realpath(join26(link, "blume"));
15758
+ linkedBlume = await realpath(join28(link, "blume"));
14776
15759
  runningBlume = await realpath(pkgDir);
14777
15760
  } catch {
14778
15761
  return;
@@ -14788,7 +15771,7 @@ var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
14788
15771
  }
14789
15772
  const mdxDir = candidateHolding(pkgDir, "@astrojs", "mdx");
14790
15773
  const blumeAstro = resolveAstroPackageJson(astroDir);
14791
- await dropStaleDepsLink(join26(outDir, "node_modules"), pkgDir);
15774
+ await dropStaleDepsLink(join28(outDir, "node_modules"), pkgDir);
14792
15775
  const outDirHit = resolvedAstroHit(outDir);
14793
15776
  const astroCorrect = blumeAstro !== null && outDirHit?.pkg === blumeAstro;
14794
15777
  const walkLandsInDeps = astroCorrect && outDirHit !== null && sameRealDir(outDirHit.modulesDir, astroDir);
@@ -14796,7 +15779,7 @@ var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
14796
15779
  return null;
14797
15780
  }
14798
15781
  if (mdxDir && (mdxDir === astroDir || astroCorrect)) {
14799
- await linkDepsJunction(join26(outDir, "node_modules"), mdxDir);
15782
+ await linkDepsJunction(join28(outDir, "node_modules"), mdxDir);
14800
15783
  return null;
14801
15784
  }
14802
15785
  return astroConflictWarning(blumeAstro, outDirHit?.pkg ?? null);
@@ -14837,14 +15820,14 @@ var searchProviderWarnings = (provider, root, pkgDir = packageRoot()) => {
14837
15820
  }
14838
15821
  return warnings;
14839
15822
  };
14840
- var examplesCssFile = (root, config) => config.examples.css ? join26(root, config.examples.css) : null;
15823
+ var examplesCssFile = (root, config) => config.examples.css ? join28(root, config.examples.css) : null;
14841
15824
  var writeExamplesPreview = async (options) => {
14842
15825
  const { config, hasExamples, root, srcDir, write } = options;
14843
15826
  if (hasExamples) {
14844
- await write(join26(srcDir, "pages", ...config.basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro"), examplesPageTemplate());
15827
+ await write(join28(srcDir, "pages", ...config.basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro"), examplesPageTemplate());
14845
15828
  }
14846
15829
  const cssFile = examplesCssFile(root, config);
14847
- return cssFile && !existsSync14(cssFile) ? [
15830
+ return cssFile && !existsSync16(cssFile) ? [
14848
15831
  `examples.css points at "${config.examples.css}", which doesn't exist; previews render without it.`
14849
15832
  ] : [];
14850
15833
  };
@@ -14853,7 +15836,7 @@ var readOptional = async (path) => {
14853
15836
  return "";
14854
15837
  }
14855
15838
  try {
14856
- return await readFile14(path, "utf-8");
15839
+ return await readFile16(path, "utf-8");
14857
15840
  } catch {
14858
15841
  return "";
14859
15842
  }
@@ -14873,30 +15856,30 @@ var detectUsesMath = async (root, staged = []) => {
14873
15856
  ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
14874
15857
  onlyFiles: true
14875
15858
  });
14876
- const contents = await Promise.all(files.map((file) => readOptional(join26(root, file))));
15859
+ const contents = await Promise.all(files.map((file) => readOptional(join28(root, file))));
14877
15860
  return [...contents, ...staged].some(containsMath);
14878
15861
  };
14879
- var hashConfigSource = (source) => createHash2("sha256").update(source).digest("hex");
15862
+ var hashConfigSource = (source) => createHash3("sha256").update(source).digest("hex");
14880
15863
  var loadIntegrationBridge = async (config, context) => {
14881
15864
  if (config.integrations.length === 0 || !context.configFile) {
14882
15865
  return;
14883
15866
  }
14884
15867
  return {
14885
- configFile: relative13(context.outDir, context.configFile),
15868
+ configFile: relative14(context.outDir, context.configFile),
14886
15869
  sourceHash: hashConfigSource(await readOptional(context.configFile))
14887
15870
  };
14888
15871
  };
14889
15872
  var writeIfChanged = async (path, content) => {
14890
15873
  let existing = null;
14891
15874
  try {
14892
- existing = await readFile14(path, "utf-8");
15875
+ existing = await readFile16(path, "utf-8");
14893
15876
  } catch {
14894
15877
  existing = null;
14895
15878
  }
14896
15879
  if (existing === content) {
14897
15880
  return false;
14898
15881
  }
14899
- await mkdir6(dirname9(path), { recursive: true });
15882
+ await mkdir6(dirname10(path), { recursive: true });
14900
15883
  const tmp = `${path}.${process.pid}.tmp`;
14901
15884
  await writeFile7(tmp, content, "utf-8");
14902
15885
  try {
@@ -14934,12 +15917,12 @@ var collectStaged = (project) => {
14934
15917
  var writeStagedContent = async (out, staged) => {
14935
15918
  const contentDir = stagedContentDir(out);
14936
15919
  const written = new Set;
14937
- await Promise.all([...staged].map(async ([entryId, text]) => {
14938
- const path = join26(contentDir, entryId);
15920
+ await Promise.all([...staged].map(async ([entryId, text2]) => {
15921
+ const path = join28(contentDir, entryId);
14939
15922
  written.add(normalize3(path));
14940
- await writeIfChanged(path, text);
15923
+ await writeIfChanged(path, text2);
14941
15924
  }));
14942
- if (existsSync14(contentDir)) {
15925
+ if (existsSync16(contentDir)) {
14943
15926
  await pruneOrphans(contentDir, written);
14944
15927
  }
14945
15928
  };
@@ -14970,9 +15953,9 @@ var readLogoSvg = (project, source) => {
14970
15953
  }
14971
15954
  const rel = source.replace(/^\//u, "");
14972
15955
  const file = [
14973
- join26(project.context.root, "public", rel),
14974
- join26(project.context.root, rel)
14975
- ].find((path) => existsSync14(path));
15956
+ join28(project.context.root, "public", rel),
15957
+ join28(project.context.root, rel)
15958
+ ].find((path) => existsSync16(path));
14976
15959
  return file ? readFileSync9(file, "utf-8") : undefined;
14977
15960
  };
14978
15961
  var resolveLogo = (project) => {
@@ -14981,7 +15964,7 @@ var resolveLogo = (project) => {
14981
15964
  return null;
14982
15965
  }
14983
15966
  const config = typeof logo === "string" ? { image: logo } : logo;
14984
- const { href, image: source, text } = config;
15967
+ const { href, image: source, text: text2 } = config;
14985
15968
  const image = typeof source === "string" ? { light: source } : source;
14986
15969
  const light = image?.light ?? image?.dark;
14987
15970
  const dark = image?.dark ?? image?.light;
@@ -14990,12 +15973,12 @@ var resolveLogo = (project) => {
14990
15973
  const lightSvg = readLogoSvg(project, light);
14991
15974
  const darkSvg = dark === light ? lightSvg : readLogoSvg(project, dark);
14992
15975
  if (light && light === dark && lightSvg) {
14993
- return { alt, href: brandHref, svg: lightSvg, text };
15976
+ return { alt, href: brandHref, svg: lightSvg, text: text2 };
14994
15977
  }
14995
15978
  const lightDimensions = svgDimensions(lightSvg);
14996
15979
  const darkDimensions = svgDimensions(darkSvg);
14997
15980
  const dimensions = lightDimensions || darkDimensions ? { dark: darkDimensions, light: lightDimensions } : undefined;
14998
- return { alt, dark, dimensions, href: brandHref, light, text };
15981
+ return { alt, dark, dimensions, href: brandHref, light, text: text2 };
14999
15982
  };
15000
15983
  var FAVICON_CANDIDATES = [
15001
15984
  "icon.svg",
@@ -15018,7 +16001,7 @@ var faviconType = (name) => {
15018
16001
  };
15019
16002
  var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync9(file).toString("base64")}`;
15020
16003
  var defaultFavicon = () => ({
15021
- href: inlineDataUri(join26(BLUME_SRC, "assets", "icon.png"), "image/png"),
16004
+ href: inlineDataUri(join28(BLUME_SRC, "assets", "icon.png"), "image/png"),
15022
16005
  type: "image/png"
15023
16006
  });
15024
16007
  var APPLE_ICON_CANDIDATES = [
@@ -15030,13 +16013,13 @@ var APPLE_ICON_CANDIDATES = [
15030
16013
  var resolveIconFile = (project, candidates) => {
15031
16014
  const { root } = project.context;
15032
16015
  for (const name of candidates) {
15033
- if (existsSync14(join26(root, "public", name))) {
16016
+ if (existsSync16(join28(root, "public", name))) {
15034
16017
  return { href: `/${name}`, type: faviconType(name) };
15035
16018
  }
15036
16019
  }
15037
16020
  for (const name of candidates) {
15038
- const file = join26(root, name);
15039
- if (existsSync14(file)) {
16021
+ const file = join28(root, name);
16022
+ if (existsSync16(file)) {
15040
16023
  const type = faviconType(name);
15041
16024
  return { href: inlineDataUri(file, type ?? "image/x-icon"), type };
15042
16025
  }
@@ -15060,18 +16043,45 @@ var resolveBanner = (config) => {
15060
16043
  link: banner.link
15061
16044
  };
15062
16045
  };
16046
+ var resolveOgMark = (project, siteLogo) => {
16047
+ const configured = project.config.seo.og.logo;
16048
+ if (configured === false) {
16049
+ return false;
16050
+ }
16051
+ if (configured) {
16052
+ return resolveOgLogo(project, configured);
16053
+ }
16054
+ return siteLogo;
16055
+ };
16056
+ var resolveOgSite = (config) => {
16057
+ const configured = config.seo.og.site;
16058
+ if (configured === false) {
16059
+ return;
16060
+ }
16061
+ if (configured !== undefined) {
16062
+ return configured;
16063
+ }
16064
+ return config.deployment.site ? `${new URL(config.deployment.site).host}${normalizeBasePath(config.deployment.base)}` : undefined;
16065
+ };
16066
+ var resolveOgDescription = (config) => {
16067
+ const configured = config.seo.og.description;
16068
+ if (configured === false) {
16069
+ return;
16070
+ }
16071
+ return configured ?? config.description;
16072
+ };
15063
16073
  var buildRuntimeData = (project) => {
15064
16074
  const { config, context, graph, manifest } = project;
15065
16075
  const { github } = config;
15066
16076
  const repoUrl = github ? `https://github.com/${github.owner}/${github.repo}` : null;
15067
16077
  const editBase = github ? `${repoUrl}/edit/${github.branch}` : null;
15068
16078
  const logo = resolveLogo(project);
15069
- const ogLogo = config.seo.og.logo ? resolveOgLogo(project, config.seo.og.logo) : logo?.svg;
16079
+ const ogLogo = resolveOgMark(project, logo?.svg);
15070
16080
  const editUrlFor = (sourcePath) => {
15071
16081
  if (!(editBase && sourcePath)) {
15072
16082
  return null;
15073
16083
  }
15074
- const rel = relative13(context.root, sourcePath).split("\\").join("/");
16084
+ const rel = relative14(context.root, sourcePath).split("\\").join("/");
15075
16085
  const editPath = github?.dir ? `${github.dir}/${rel}` : rel;
15076
16086
  return `${editBase}/${editPath}`;
15077
16087
  };
@@ -15133,10 +16143,11 @@ var buildRuntimeData = (project) => {
15133
16143
  route: config.ai.mcp.route
15134
16144
  } : null,
15135
16145
  og: {
16146
+ description: resolveOgDescription(config),
15136
16147
  enabled: config.seo.og.enabled ?? false,
15137
- fonts: config.seo.og.fonts ?? [],
15138
16148
  logo: ogLogo,
15139
- palette: config.seo.og.palette
16149
+ palette: config.seo.og.palette,
16150
+ site: resolveOgSite(config)
15140
16151
  },
15141
16152
  repoUrl,
15142
16153
  search: {
@@ -15149,6 +16160,10 @@ var buildRuntimeData = (project) => {
15149
16160
  theme: config.theme,
15150
16161
  title: config.title,
15151
16162
  toc: config.toc,
16163
+ webmcp: {
16164
+ enabled: config.ai.webmcp,
16165
+ llms: config.ai.llmsTxt.enabled
16166
+ },
15152
16167
  x: config.seo.x
15153
16168
  },
15154
16169
  feeds: buildRssFeeds(project).map((feed) => ({
@@ -15182,7 +16197,7 @@ var buildRuntimeData = (project) => {
15182
16197
  var planMcp = (project, srcDir, userPages) => {
15183
16198
  const { config } = project;
15184
16199
  const { route } = config.ai.mcp;
15185
- const dir = join26(srcDir, "blume-mcp");
16200
+ const dir = join28(srcDir, "blume-mcp");
15186
16201
  const base = {
15187
16202
  dir,
15188
16203
  discoveryPages: [],
@@ -15206,11 +16221,11 @@ var planMcp = (project, srcDir, userPages) => {
15206
16221
  ...base,
15207
16222
  discoveryPages: [
15208
16223
  {
15209
- entrypoint: join26(dir, "discovery.ts"),
16224
+ entrypoint: join28(dir, "discovery.ts"),
15210
16225
  pattern: "/.well-known/mcp.json"
15211
16226
  },
15212
16227
  {
15213
- entrypoint: join26(dir, "server-card.ts"),
16228
+ entrypoint: join28(dir, "server-card.ts"),
15214
16229
  pattern: "/.well-known/mcp/server-card.json"
15215
16230
  }
15216
16231
  ],
@@ -15230,11 +16245,11 @@ var writeMcpFiles = async (project, plan, write) => {
15230
16245
  version: data.version
15231
16246
  };
15232
16247
  await Promise.all([
15233
- write(join26(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
16248
+ write(join28(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
15234
16249
  `),
15235
- write(join26(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate(plan.route)),
15236
- write(join26(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
15237
- write(join26(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
16250
+ write(join28(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate(plan.route)),
16251
+ write(join28(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
16252
+ write(join28(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
15238
16253
  ]);
15239
16254
  };
15240
16255
  var writeAskFiles = async (project, srcDir, write) => {
@@ -15244,20 +16259,20 @@ var writeAskFiles = async (project, srcDir, write) => {
15244
16259
  }
15245
16260
  const grounded = ask.provider !== "inkeep";
15246
16261
  if (grounded) {
15247
- await write(join26(srcDir, "generated", "ask-data.json"), `${JSON.stringify(await buildAskData(project))}
16262
+ await write(join28(srcDir, "generated", "ask-data.json"), `${JSON.stringify(await buildAskData(project))}
15248
16263
  `);
15249
16264
  }
15250
- await write(join26(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded));
16265
+ await write(join28(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded));
15251
16266
  };
15252
16267
  var writeNotFoundPage = async (write, srcDir, pages, contentPages) => {
15253
16268
  if (routeIsTaken(pages, contentPages, "/404")) {
15254
16269
  return;
15255
16270
  }
15256
- await write(join26(srcDir, "pages", "404.astro"), notFoundPageTemplate());
16271
+ await write(join28(srcDir, "pages", "404.astro"), notFoundPageTemplate());
15257
16272
  };
15258
16273
  var diagnosticWarning = (diagnostic) => diagnostic.suggestion ? `${diagnostic.message} ${diagnostic.suggestion}` : diagnostic.message;
15259
16274
  var buildComponentSlots = async (componentsFile) => {
15260
- const analysis = componentsFile ? analyzeComponentOverrides(await readFile14(componentsFile, "utf-8"), componentsFile) : null;
16275
+ const analysis = componentsFile ? analyzeComponentOverrides(await readFile16(componentsFile, "utf-8"), componentsFile) : null;
15261
16276
  return {
15262
16277
  plan: planComponentSlots(componentsFile, analysis),
15263
16278
  tags: analysis ? [...analysis.mdx, ...analysis.islands].map((entry) => entry.key) : [],
@@ -15265,17 +16280,34 @@ var buildComponentSlots = async (componentsFile) => {
15265
16280
  };
15266
16281
  };
15267
16282
  var contentWatchesRuntimeDir = (hasFilesystemSource, collectionBase, context) => hasFilesystemSource && runtimeDirWithin(collectionBase, context.outDir) !== null;
16283
+ var projectOgFonts = (project) => resolveOgFonts({
16284
+ ogFonts: project.config.seo.og.fonts,
16285
+ themeFonts: project.config.theme.fonts,
16286
+ themeFontsConfigured: project.themeFontsConfigured
16287
+ }, project.context.root);
16288
+ var assertFontFilesExist = (project) => {
16289
+ const { config, context } = project;
16290
+ const missing = missingFontFiles({ ogFonts: config.seo.og.fonts ?? [], themeFonts: config.theme.fonts }, context.root);
16291
+ if (missing.length > 0) {
16292
+ throw new BlumeError({
16293
+ code: "BLUME_FONT_FILE_MISSING",
16294
+ message: `Configured font file(s) not found: ${missing.join(", ")}. Font paths resolve relative to the project root.`,
16295
+ severity: "error"
16296
+ });
16297
+ }
16298
+ };
15268
16299
  var generateRuntime = async (project) => {
15269
16300
  const { context, config } = project;
16301
+ assertFontFilesExist(project);
15270
16302
  const out = context.outDir;
15271
- const srcDir = join26(out, "src");
15272
- const askPath = join26(srcDir, "generated", "Ask.astro");
15273
- const dataPath = join26(srcDir, "generated", "data.json");
15274
- const themePath = join26(srcDir, "generated", "app.css");
15275
- const searchClientPath = join26(srcDir, "generated", "search-client.ts");
15276
- const examplesPath = join26(srcDir, "generated", "examples.ts");
15277
- const examplesThemePath = join26(srcDir, "generated", "examples.css");
15278
- const openapiPath = join26(srcDir, "generated", "openapi.json");
16303
+ const srcDir = join28(out, "src");
16304
+ const askPath = join28(srcDir, "generated", "Ask.astro");
16305
+ const dataPath = join28(srcDir, "generated", "data.json");
16306
+ const themePath = join28(srcDir, "generated", "app.css");
16307
+ const searchClientPath = join28(srcDir, "generated", "search-client.ts");
16308
+ const examplesPath = join28(srcDir, "generated", "examples.ts");
16309
+ const examplesThemePath = join28(srcDir, "generated", "examples.css");
16310
+ const openapiPath = join28(srcDir, "generated", "openapi.json");
15279
16311
  const written = new Set;
15280
16312
  const write = (path, content) => {
15281
16313
  written.add(normalize3(path));
@@ -15330,7 +16362,7 @@ var generateRuntime = async (project) => {
15330
16362
  const docsCollection = resolveDocsCollection(config, context);
15331
16363
  const [structural] = await Promise.all([
15332
16364
  Promise.all([
15333
- write(join26(out, "astro.config.mjs"), astroConfigTemplate({
16365
+ write(join28(out, "astro.config.mjs"), astroConfigTemplate({
15334
16366
  aliases: resolveTsconfigAliases(context.root),
15335
16367
  askPath,
15336
16368
  config,
@@ -15350,26 +16382,26 @@ var generateRuntime = async (project) => {
15350
16382
  searchClientPath,
15351
16383
  themePath
15352
16384
  })),
15353
- write(join26(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
15354
- write(join26(out, "tsconfig.json"), runtimeTsconfigTemplate()),
15355
- write(join26(srcDir, "env.d.ts"), envTemplate()),
15356
- write(join26(srcDir, "content.config.ts"), contentConfigTemplate({
16385
+ write(join28(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
16386
+ write(join28(out, "tsconfig.json"), runtimeTsconfigTemplate()),
16387
+ write(join28(srcDir, "env.d.ts"), envTemplate()),
16388
+ write(join28(srcDir, "content.config.ts"), contentConfigTemplate({
15357
16389
  collection: docsCollection,
15358
16390
  config,
15359
16391
  context,
15360
16392
  filesystem: hasFilesystemSource,
15361
16393
  staged: hasStaged
15362
16394
  })),
15363
- write(join26(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
16395
+ write(join28(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
15364
16396
  exportEpub,
15365
16397
  exportPdf,
15366
16398
  mathEnabled: usesMath,
15367
16399
  needsReact
15368
16400
  })),
15369
16401
  write(askPath, askComponentTemplate(askEnabled)),
15370
- write(join26(srcDir, "generated", "components.ts"), slotPlan.module),
15371
- write(join26(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
15372
- write(join26(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples, config.basePath)),
16402
+ write(join28(srcDir, "generated", "components.ts"), slotPlan.module),
16403
+ write(join28(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
16404
+ write(join28(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples, config.basePath)),
15373
16405
  write(examplesThemePath, examplesEntryTemplate({
15374
16406
  configTokens: buildThemeCss(config.theme),
15375
16407
  sources: [`${context.root}/**/*.{astro,jsx,svelte,ts,tsx,vue}`],
@@ -15385,17 +16417,17 @@ var generateRuntime = async (project) => {
15385
16417
  userTheme
15386
16418
  }))
15387
16419
  ]),
15388
- Promise.all(islandDiscovery.islands.map((island) => write(join26(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island)))),
15389
- Promise.all(slotPlan.wrappers.map((wrapper) => write(join26(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content))),
15390
- Promise.all(exampleDiscovery.examples.map((example) => write(join26(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example)))),
16420
+ Promise.all(islandDiscovery.islands.map((island) => write(join28(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island)))),
16421
+ Promise.all(slotPlan.wrappers.map((wrapper) => write(join28(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content))),
16422
+ Promise.all(exampleDiscovery.examples.map((example) => write(join28(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example)))),
15391
16423
  writeAskFiles(project, srcDir, write),
15392
16424
  writeMcpFiles(project, mcp, write)
15393
16425
  ]);
15394
16426
  if (config.seo.og.enabled) {
15395
- await write(join26(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes));
16427
+ await write(join28(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes, projectOgFonts(project)));
15396
16428
  }
15397
16429
  if (hasGeneratedChangelog(project, pages)) {
15398
- await write(join26(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
16430
+ await write(join28(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
15399
16431
  exportEpub,
15400
16432
  exportPdf,
15401
16433
  needsReact,
@@ -15415,27 +16447,31 @@ var generateRuntime = async (project) => {
15415
16447
  ]);
15416
16448
  if (servesStaticIndex(config.search.provider)) {
15417
16449
  const documents = await buildSearchDocuments(project);
15418
- await write(join26(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
16450
+ await write(join28(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
15419
16451
  `);
15420
- await write(join26(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
16452
+ await write(join28(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
15421
16453
  }
15422
16454
  if (config.search.provider === "mixedbread") {
15423
- await write(join26(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
16455
+ await write(join28(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
15424
16456
  }
15425
16457
  const rawMarkdown = await buildRawMarkdown(project);
16458
+ const contentAssets = await collectContentAssets(project);
15426
16459
  await Promise.all([
15427
- write(join26(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
16460
+ write(join28(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
16461
+ `),
16462
+ write(join28(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate("md")),
16463
+ write(join28(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate("mdx")),
16464
+ write(join28(srcDir, "generated", "content-assets.json"), `${JSON.stringify(contentAssets)}
15428
16465
  `),
15429
- write(join26(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate("md")),
15430
- write(join26(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate("mdx"))
16466
+ write(join28(srcDir, "pages", "blume-assets", "[...asset].ts"), contentAssetsEndpointTemplate(join28(project.context.outDir, "public", "blume-assets")))
15431
16467
  ]);
15432
16468
  const feeds = buildRssFeeds(project);
15433
16469
  if (feeds.length > 0) {
15434
16470
  const feedXml = Object.fromEntries(feeds.map((feed) => [feed.type, renderRssFeed(feed)]));
15435
16471
  await Promise.all([
15436
- write(join26(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
16472
+ write(join28(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
15437
16473
  `),
15438
- write(join26(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
16474
+ write(join28(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
15439
16475
  ]);
15440
16476
  }
15441
16477
  const warnings = [
@@ -15471,14 +16507,14 @@ var generateRuntime = async (project) => {
15471
16507
  root: context.root
15472
16508
  });
15473
16509
  warnings.push(...references.warnings);
15474
- await Promise.all(references.files.map((file) => write(join26(srcDir, "pages", file.pagePath), file.content)));
16510
+ await Promise.all(references.files.map((file) => write(join28(srcDir, "pages", file.pagePath), file.content)));
15475
16511
  }
15476
16512
  const openApiSource2 = project.sources.find(isOpenApiSource);
15477
16513
  await Promise.all([
15478
- write(join26(srcDir, "generated", "data.json"), buildRuntimeData(project)),
16514
+ write(join28(srcDir, "generated", "data.json"), buildRuntimeData(project)),
15479
16515
  write(openapiPath, `${JSON.stringify(openApiSource2 ? openApiSource2.openApiData() : {})}
15480
16516
  `),
15481
- write(join26(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
16517
+ write(join28(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
15482
16518
  `),
15483
16519
  writeStagedContent(out, staged)
15484
16520
  ]);
@@ -15487,8 +16523,8 @@ var generateRuntime = async (project) => {
15487
16523
  };
15488
16524
 
15489
16525
  // src/cli/env.ts
15490
- import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
15491
- import { dirname as dirname10, join as join27, resolve as resolve8 } from "pathe";
16526
+ import { existsSync as existsSync17, readFileSync as readFileSync10 } from "node:fs";
16527
+ import { dirname as dirname11, join as join29, resolve as resolve9 } from "pathe";
15492
16528
  var ENV_LINE = /^\s*(?:export\s+)?(?<key>[A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?<value>.*?)\s*$/u;
15493
16529
  var DOUBLE_QUOTED2 = /^"(?<body>[\s\S]*)"$/u;
15494
16530
  var SINGLE_QUOTED = /^'(?<body>[\s\S]*)'$/u;
@@ -15534,19 +16570,19 @@ var applyEnv = (parsed) => {
15534
16570
  };
15535
16571
  var loadFile = (path) => {
15536
16572
  try {
15537
- if (existsSync15(path)) {
16573
+ if (existsSync17(path)) {
15538
16574
  applyEnv(parseEnv(readFileSync10(path, "utf-8")));
15539
16575
  }
15540
16576
  } catch {}
15541
16577
  };
15542
16578
  var loadEnvFiles = (startDir) => {
15543
- let dir = resolve8(startDir);
16579
+ let dir = resolve9(startDir);
15544
16580
  let done = false;
15545
16581
  while (!done) {
15546
- loadFile(join27(dir, ".env.local"));
15547
- loadFile(join27(dir, ".env"));
15548
- const parent = dirname10(dir);
15549
- done = existsSync15(join27(dir, ".git")) || parent === dir;
16582
+ loadFile(join29(dir, ".env.local"));
16583
+ loadFile(join29(dir, ".env"));
16584
+ const parent = dirname11(dir);
16585
+ done = existsSync17(join29(dir, ".git")) || parent === dir;
15550
16586
  dir = parent;
15551
16587
  }
15552
16588
  };
@@ -15651,20 +16687,91 @@ var emitRedirectFiles = async (config, distDir) => {
15651
16687
  if (redirects.length === 0 || config.deployment.output !== "static") {
15652
16688
  return;
15653
16689
  }
15654
- await writeFile8(join28(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
16690
+ await writeFile8(join30(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
15655
16691
  const platformFiles = [
15656
16692
  { content: buildNetlifyRedirects(redirects), name: "_redirects" },
15657
16693
  { content: buildVercelConfig(redirects), name: "vercel.json" }
15658
16694
  ];
15659
- await Promise.all(platformFiles.map((file) => existsSync16(join28(distDir, file.name)) ? Promise.resolve() : writeFile8(join28(distDir, file.name), file.content, "utf-8")));
16695
+ await Promise.all(platformFiles.map((file) => existsSync18(join30(distDir, file.name)) ? Promise.resolve() : writeFile8(join30(distDir, file.name), file.content, "utf-8")));
15660
16696
  logger.success(`Emitted redirect files for ${redirects.length} redirect(s)`);
15661
16697
  };
15662
- var emitHeaderFiles = async (config, distDir) => {
15663
- if (config.deployment.output !== "static" || existsSync16(join28(distDir, "_headers"))) {
16698
+ var emitHeaderFiles = async (project, distDir) => {
16699
+ const { config } = project;
16700
+ if (config.deployment.output !== "static" || existsSync18(join30(distDir, "_headers"))) {
16701
+ return;
16702
+ }
16703
+ await writeFile8(join30(distDir, "_headers"), buildNetlifyHeaders(config, buildHomeLinkHeader(config, project.manifest.routes.map((route) => route.path))), "utf-8");
16704
+ logger.success("Emitted _headers (UTF-8 Content-Type + homepage Link header)");
16705
+ };
16706
+ var emitAgentSkills = async (project, distDir) => {
16707
+ const configured = project.config.ai.skills;
16708
+ if (!configured) {
16709
+ return;
16710
+ }
16711
+ const dir = resolve10(project.context.root, configured);
16712
+ if (!existsSync18(dir)) {
16713
+ logger.warn(`ai.skills points at "${configured}" (${dir}), which does not exist; no skills published.`);
16714
+ return;
16715
+ }
16716
+ const outDir = join30(distDir, AGENT_SKILLS_DIR.slice(1));
16717
+ if (existsSync18(join30(outDir, "index.json"))) {
16718
+ return;
16719
+ }
16720
+ const { skills, warnings } = await collectSkills(dir);
16721
+ for (const warning of warnings) {
16722
+ logger.warn(warning);
16723
+ }
16724
+ if (skills.length === 0) {
16725
+ logger.warn(`ai.skills: no publishable skills found in "${configured}".`);
16726
+ return;
16727
+ }
16728
+ await Promise.all(skills.map(async (skill) => {
16729
+ const target = join30(outDir, skill.path);
16730
+ await mkdir7(dirname12(target), { recursive: true });
16731
+ await writeFile8(target, skill.content);
16732
+ }));
16733
+ await writeFile8(join30(outDir, "index.json"), buildSkillsIndex(skills, project.config), "utf-8");
16734
+ logger.success(`Published ${skills.length} agent skill(s) (.well-known/agent-skills/index.json)`);
16735
+ };
16736
+ var emitWellKnownFiles = async (config, distDir) => {
16737
+ const files = [
16738
+ {
16739
+ content: buildSignaturesDirectory(config),
16740
+ label: "Web Bot Auth",
16741
+ path: SIGNATURES_DIRECTORY_PATH
16742
+ },
16743
+ {
16744
+ content: buildApiCatalog(config),
16745
+ label: "RFC 9727",
16746
+ path: API_CATALOG_PATH
16747
+ }
16748
+ ];
16749
+ for (const file of files) {
16750
+ const target = join30(distDir, file.path.slice(1));
16751
+ if (!file.content || existsSync18(target)) {
16752
+ continue;
16753
+ }
16754
+ await mkdir7(join30(distDir, ".well-known"), { recursive: true });
16755
+ await writeFile8(target, file.content, "utf-8");
16756
+ logger.success(`Generated ${file.path.slice(1)} (${file.label})`);
16757
+ }
16758
+ };
16759
+ var emitVercelNegotiation = async (config, routePaths, root) => {
16760
+ const configPath = join30(root, ".vercel", "output", "config.json");
16761
+ if (!existsSync18(configPath)) {
16762
+ return;
16763
+ }
16764
+ const overrides = {
16765
+ ...hasApiCatalog(config) ? { [API_CATALOG_PATH.slice(1)]: API_CATALOG_TYPE } : {},
16766
+ ...config.ai.webBotAuth.keys.length > 0 ? { [SIGNATURES_DIRECTORY_PATH.slice(1)]: SIGNATURES_DIRECTORY_TYPE } : {}
16767
+ };
16768
+ const injected = injectNegotiationRoutes(await readFile17(configPath, "utf-8"), routePaths, buildHomeLinkHeader(config, routePaths), overrides);
16769
+ if (injected === null) {
16770
+ logger.warn("Could not wire Accept: text/markdown negotiation into .vercel/output/config.json — raw Markdown stays available at the .md URLs.");
15664
16771
  return;
15665
16772
  }
15666
- await writeFile8(join28(distDir, "_headers"), buildNetlifyHeaders(config), "utf-8");
15667
- logger.success("Emitted _headers (UTF-8 Content-Type for raw endpoints)");
16773
+ await writeFile8(configPath, injected, "utf-8");
16774
+ logger.success("Wired Accept: text/markdown negotiation into the Vercel routing config");
15668
16775
  };
15669
16776
  var formatBytes2 = (bytes) => {
15670
16777
  if (bytes < 1024) {
@@ -15674,14 +16781,14 @@ var formatBytes2 = (bytes) => {
15674
16781
  return `${(bytes / 1024).toFixed(digits)} kB`;
15675
16782
  };
15676
16783
  var astroAssets = async (distDir, ext) => {
15677
- const astroDir = join28(distDir, "_astro");
15678
- if (!existsSync16(astroDir)) {
16784
+ const astroDir = join30(distDir, "_astro");
16785
+ if (!existsSync18(astroDir)) {
15679
16786
  return [];
15680
16787
  }
15681
- const entries = await readdir(astroDir);
16788
+ const entries = await readdir2(astroDir);
15682
16789
  const files = entries.filter((name) => name.endsWith(`.${ext}`));
15683
16790
  const sized = await Promise.all(files.map(async (name) => {
15684
- const info = await stat2(join28(astroDir, name));
16791
+ const info = await stat3(join30(astroDir, name));
15685
16792
  return { name, size: info.size };
15686
16793
  }));
15687
16794
  return sized.toSorted((a, b) => b.size - a.size);
@@ -15734,26 +16841,26 @@ var runClientAssetChecks = async (staticDir, args) => {
15734
16841
  var isolatedOutputDir = (config, context) => {
15735
16842
  const { adapter, output } = config.deployment;
15736
16843
  if (output === "server" && adapter === "vercel") {
15737
- return join28(context.outDir, ".vercel", "output");
16844
+ return join30(context.outDir, ".vercel", "output");
15738
16845
  }
15739
- return context.distDir ?? join28(context.outDir, "dist");
16846
+ return context.distDir ?? join30(context.outDir, "dist");
15740
16847
  };
15741
16848
  var isolatedStaticDir = (config, context) => {
15742
16849
  const { adapter, output } = config.deployment;
15743
16850
  const outputDir = isolatedOutputDir(config, context);
15744
16851
  if (output === "server" && adapter === "vercel") {
15745
- return join28(outputDir, "static");
16852
+ return join30(outputDir, "static");
15746
16853
  }
15747
16854
  if (output === "server" && adapter === "node") {
15748
- return join28(outputDir, "client");
16855
+ return join30(outputDir, "client");
15749
16856
  }
15750
16857
  return outputDir;
15751
16858
  };
15752
16859
  var publishLlmsFiles = async (project, distDir) => {
15753
- const indexPath = join28(distDir, "llms.txt");
15754
- const fullPath = join28(distDir, "llms-full.txt");
15755
- const writeIndex = !existsSync16(indexPath);
15756
- const writeFull = !existsSync16(fullPath);
16860
+ const indexPath = join30(distDir, "llms.txt");
16861
+ const fullPath = join30(distDir, "llms-full.txt");
16862
+ const writeIndex = !existsSync18(indexPath);
16863
+ const writeFull = !existsSync18(fullPath);
15757
16864
  if (!(writeIndex || writeFull)) {
15758
16865
  return;
15759
16866
  }
@@ -15786,23 +16893,25 @@ var publishBuildArtifacts = async (project, distDir, args) => {
15786
16893
  await publishLlmsFiles(project, distDir);
15787
16894
  }
15788
16895
  const sitemap = buildSitemap(project);
15789
- if (sitemap && !existsSync16(join28(distDir, "sitemap.xml"))) {
15790
- await writeFile8(join28(distDir, "sitemap.xml"), sitemap, "utf-8");
16896
+ if (sitemap && !existsSync18(join30(distDir, "sitemap.xml"))) {
16897
+ await writeFile8(join30(distDir, "sitemap.xml"), sitemap, "utf-8");
15791
16898
  logger.success("Generated sitemap.xml");
15792
16899
  }
15793
16900
  const robots = buildRobots(project);
15794
- if (robots && !existsSync16(join28(distDir, "robots.txt"))) {
15795
- await writeFile8(join28(distDir, "robots.txt"), robots, "utf-8");
16901
+ if (robots && !existsSync18(join30(distDir, "robots.txt"))) {
16902
+ await writeFile8(join30(distDir, "robots.txt"), robots, "utf-8");
15796
16903
  logger.success("Generated robots.txt");
15797
16904
  }
15798
16905
  const agentReadability = buildAgentReadability(project);
15799
- if (agentReadability && !existsSync16(join28(distDir, "agent-readability.json"))) {
15800
- await writeFile8(join28(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
16906
+ if (agentReadability && !existsSync18(join30(distDir, "agent-readability.json"))) {
16907
+ await writeFile8(join30(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
15801
16908
  `, "utf-8");
15802
16909
  logger.success("Generated agent-readability.json");
15803
16910
  }
16911
+ await emitWellKnownFiles(project.config, distDir);
16912
+ await emitAgentSkills(project, distDir);
15804
16913
  await emitRedirectFiles(project.config, distDir);
15805
- await emitHeaderFiles(project.config, distDir);
16914
+ await emitHeaderFiles(project, distDir);
15806
16915
  const { config } = project;
15807
16916
  const features = serverFeatures(config);
15808
16917
  const sitemapNote = config.seo.sitemap ? "no (set deployment.site)" : "no (seo.sitemap is false)";
@@ -15916,16 +17025,19 @@ var buildCommand = defineCommand3({
15916
17025
  if (surfaced.moved) {
15917
17026
  logger.success(`Surfaced ${adapter} output to ${surfaced.to}`);
15918
17027
  }
17028
+ if (project.config.deployment.output === "server" && adapter === "vercel") {
17029
+ await emitVercelNegotiation(project.config, project.manifest.routes.map((route) => route.path), root);
17030
+ }
15919
17031
  await publishBuildArtifacts(project, deployStaticDir(project.config, project.context), args);
15920
17032
  }
15921
17033
  });
15922
17034
 
15923
17035
  // src/cli/commands/check.ts
15924
- import { existsSync as existsSync17 } from "node:fs";
17036
+ import { existsSync as existsSync19 } from "node:fs";
15925
17037
  import { check } from "@astrojs/check";
15926
17038
  import { sync } from "astro";
15927
17039
  import { defineCommand as defineCommand4 } from "citty";
15928
- import { join as join29 } from "pathe";
17040
+ import { join as join31 } from "pathe";
15929
17041
  var checkCommand = defineCommand4({
15930
17042
  args: {
15931
17043
  isolated: {
@@ -15961,13 +17073,13 @@ var checkCommand = defineCommand4({
15961
17073
  });
15962
17074
  const { outDir } = project.context;
15963
17075
  await sync({ logLevel: "warn", root: outDir });
15964
- const tsconfig = join29(root, "tsconfig.json");
17076
+ const tsconfig = join31(root, "tsconfig.json");
15965
17077
  logger.start(`Type-checking ${project.graph.pages.length} page(s)`);
15966
17078
  const failed = await check({
15967
17079
  minimumFailingSeverity: "error",
15968
17080
  minimumSeverity: "hint",
15969
17081
  root: outDir,
15970
- tsconfig: existsSync17(tsconfig) ? tsconfig : undefined,
17082
+ tsconfig: existsSync19(tsconfig) ? tsconfig : undefined,
15971
17083
  watch: false
15972
17084
  });
15973
17085
  if (failed) {
@@ -15982,7 +17094,7 @@ var checkCommand = defineCommand4({
15982
17094
  import { watch } from "node:fs";
15983
17095
  import { dev } from "astro";
15984
17096
  import { defineCommand as defineCommand5 } from "citty";
15985
- import { basename as basename4, dirname as dirname11 } from "pathe";
17097
+ import { basename as basename4, dirname as dirname13 } from "pathe";
15986
17098
 
15987
17099
  // src/astro/integration.ts
15988
17100
  var overlayServer = null;
@@ -16175,7 +17287,7 @@ var devCommand = defineCommand5({
16175
17287
  }),
16176
17288
  ...fileTargets.map((target) => {
16177
17289
  const name = basename4(target);
16178
- const watcher = watch(dirname11(target), (_event, filename) => {
17290
+ const watcher = watch(dirname13(target), (_event, filename) => {
16179
17291
  if (!filename || filename === name) {
16180
17292
  regenerate();
16181
17293
  }
@@ -16199,12 +17311,12 @@ var devCommand = defineCommand5({
16199
17311
  // src/cli/commands/doctor.ts
16200
17312
  import { readFileSync as readFileSync11 } from "node:fs";
16201
17313
  import { defineCommand as defineCommand6 } from "citty";
16202
- import { join as join30 } from "pathe";
17314
+ import { join as join32 } from "pathe";
16203
17315
  var FALLBACK_MIN_NODE = "22.12.0";
16204
17316
  var LEADING_RANGE = /^[^\d]*/u;
16205
17317
  var minSupportedNode = () => {
16206
17318
  try {
16207
- const pkg = JSON.parse(readFileSync11(join30(packageRoot(), "package.json"), "utf-8"));
17319
+ const pkg = JSON.parse(readFileSync11(join32(packageRoot(), "package.json"), "utf-8"));
16208
17320
  const range = pkg.engines?.node ?? "";
16209
17321
  return range.replace(LEADING_RANGE, "") || FALLBACK_MIN_NODE;
16210
17322
  } catch {
@@ -16297,21 +17409,21 @@ var doctorCommand = defineCommand6({
16297
17409
 
16298
17410
  // src/cli/commands/eject.ts
16299
17411
  import { defineCommand as defineCommand7 } from "citty";
16300
- import { relative as relative16 } from "pathe";
17412
+ import { relative as relative17 } from "pathe";
16301
17413
 
16302
17414
  // src/registry/eject.ts
16303
- import { existsSync as existsSync18 } from "node:fs";
16304
- import { cp as cp2, mkdir as mkdir7, readFile as readFile15, rm as rm3, writeFile as writeFile9 } from "node:fs/promises";
16305
- import { join as join31, relative as relative14 } from "pathe";
17415
+ import { existsSync as existsSync20 } from "node:fs";
17416
+ import { cp as cp2, mkdir as mkdir8, readFile as readFile18, rm as rm3, writeFile as writeFile9 } from "node:fs/promises";
17417
+ import { join as join33, relative as relative15 } from "pathe";
16306
17418
  var toPosix = (path) => path.split("\\").join("/");
16307
17419
  var LOCAL_BLUME_SOURCE = "../../node_modules/blume/src/**/*.{astro,ts,tsx}";
16308
17420
  var blumeSourceGlob = (root, genDir, resolveBlumeRoot = packageRoot) => {
16309
- if (existsSync18(join31(root, "node_modules", "blume"))) {
17421
+ if (existsSync20(join33(root, "node_modules", "blume"))) {
16310
17422
  return LOCAL_BLUME_SOURCE;
16311
17423
  }
16312
17424
  try {
16313
- const src = join31(resolveBlumeRoot(), "src");
16314
- return `${toPosix(relative14(genDir, src))}/**/*.{astro,ts,tsx}`;
17425
+ const src = join33(resolveBlumeRoot(), "src");
17426
+ return `${toPosix(relative15(genDir, src))}/**/*.{astro,ts,tsx}`;
16315
17427
  } catch {
16316
17428
  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.');
16317
17429
  return LOCAL_BLUME_SOURCE;
@@ -16324,28 +17436,28 @@ var ejectOpenApiData = (project) => {
16324
17436
  var askFiles = async (project, srcDir, genDir) => {
16325
17437
  const { ask } = project.config.ai;
16326
17438
  if (!(ask?.enabled && !ask.endpoint)) {
16327
- const endpointPath = join31(srcDir, "pages", "api", "ask.ts");
16328
- if (existsSync18(endpointPath)) {
16329
- const content = await readFile15(endpointPath, "utf-8");
17439
+ const endpointPath = join33(srcDir, "pages", "api", "ask.ts");
17440
+ if (existsSync20(endpointPath)) {
17441
+ const content = await readFile18(endpointPath, "utf-8");
16330
17442
  if (content.startsWith("// Generated by Blume. Do not edit.")) {
16331
17443
  await rm3(endpointPath, { force: true });
16332
17444
  }
16333
17445
  }
16334
- await rm3(join31(genDir, "ask-data.json"), { force: true });
17446
+ await rm3(join33(genDir, "ask-data.json"), { force: true });
16335
17447
  return [];
16336
17448
  }
16337
17449
  const grounded = ask.provider !== "inkeep";
16338
17450
  const files = [
16339
17451
  {
16340
17452
  content: askEndpointTemplate(resolveAskBackend(ask), grounded),
16341
- path: join31(srcDir, "pages", "api", "ask.ts")
17453
+ path: join33(srcDir, "pages", "api", "ask.ts")
16342
17454
  }
16343
17455
  ];
16344
17456
  if (grounded) {
16345
17457
  files.push({
16346
17458
  content: `${JSON.stringify(await buildAskData(project))}
16347
17459
  `,
16348
- path: join31(genDir, "ask-data.json")
17460
+ path: join33(genDir, "ask-data.json")
16349
17461
  });
16350
17462
  }
16351
17463
  return files;
@@ -16378,19 +17490,19 @@ var mcpFiles = async (project, userPages, srcDir, genDir) => {
16378
17490
  {
16379
17491
  content: `${JSON.stringify(data)}
16380
17492
  `,
16381
- path: join31(genDir, "mcp-data.json")
17493
+ path: join33(genDir, "mcp-data.json")
16382
17494
  },
16383
17495
  {
16384
17496
  content: mcpEndpointTemplate(route),
16385
- path: join31(srcDir, "pages", mcpPageFile(route))
17497
+ path: join33(srcDir, "pages", mcpPageFile(route))
16386
17498
  },
16387
17499
  {
16388
17500
  content: staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput)),
16389
- path: join31(srcDir, "blume-mcp", "discovery.ts")
17501
+ path: join33(srcDir, "blume-mcp", "discovery.ts")
16390
17502
  },
16391
17503
  {
16392
17504
  content: staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)),
16393
- path: join31(srcDir, "blume-mcp", "server-card.ts")
17505
+ path: join33(srcDir, "blume-mcp", "server-card.ts")
16394
17506
  }
16395
17507
  ];
16396
17508
  };
@@ -16403,23 +17515,23 @@ var changelogFiles = (project, userPages, srcDir, options) => {
16403
17515
  return [
16404
17516
  {
16405
17517
  content: changelogIndexTemplate(options),
16406
- path: join31(srcDir, "pages", "changelog.astro")
17518
+ path: join33(srcDir, "pages", "changelog.astro")
16407
17519
  }
16408
17520
  ];
16409
17521
  };
16410
- var readExamplesCss = (root, css) => css && existsSync18(join31(root, css)) ? readFile15(join31(root, css), "utf-8") : Promise.resolve("");
17522
+ var readExamplesCss = (root, css) => css && existsSync20(join33(root, css)) ? readFile18(join33(root, css), "utf-8") : Promise.resolve("");
16411
17523
  var examplesPreviewFiles = (srcDir, basePath, hasExamples) => hasExamples ? [
16412
17524
  {
16413
17525
  content: examplesPageTemplate(),
16414
- path: join31(srcDir, "pages", ...basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro")
17526
+ path: join33(srcDir, "pages", ...basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro")
16415
17527
  }
16416
17528
  ] : [];
16417
- var ejectIntegrationBridge = (config, root, configFile) => config.integrations.length > 0 && configFile ? { configFile: toPosix(relative14(root, configFile)) } : undefined;
17529
+ var ejectIntegrationBridge = (config, root, configFile) => config.integrations.length > 0 && configFile ? { configFile: toPosix(relative15(root, configFile)) } : undefined;
16418
17530
  var eject = async (root) => {
16419
17531
  const project = await scanProject(root, { mode: "build" });
16420
17532
  const { context, config } = project;
16421
- const srcDir = join31(root, "src");
16422
- const genDir = join31(srcDir, "generated");
17533
+ const srcDir = join33(root, "src");
17534
+ const genDir = join33(srcDir, "generated");
16423
17535
  const askEnabled = config.ai.ask?.enabled ?? false;
16424
17536
  const exportPdf = config.export.pdf;
16425
17537
  const exportEpub = config.export.epub;
@@ -16437,7 +17549,7 @@ var eject = async (root) => {
16437
17549
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
16438
17550
  detectNeedsReact(root),
16439
17551
  detectUsesMath(root),
16440
- context.themeFile ? readFile15(context.themeFile, "utf-8") : Promise.resolve(""),
17552
+ context.themeFile ? readFile18(context.themeFile, "utf-8") : Promise.resolve(""),
16441
17553
  readExamplesCss(root, config.examples.css),
16442
17554
  buildRawMarkdown(project),
16443
17555
  discoverIslands(root)
@@ -16451,14 +17563,14 @@ var eject = async (root) => {
16451
17563
  const needsSvelte = frameworks.has("svelte");
16452
17564
  const relContext = {
16453
17565
  ...context,
16454
- contentRoot: toPosix(relative14(root, context.contentRoot)),
17566
+ contentRoot: toPosix(relative15(root, context.contentRoot)),
16455
17567
  outDir: ".",
16456
17568
  root: "."
16457
17569
  };
16458
- const componentsImport = context.componentsFile ? `../../${toPosix(relative14(root, context.componentsFile))}` : null;
17570
+ const componentsImport = context.componentsFile ? `../../${toPosix(relative15(root, context.componentsFile))}` : null;
16459
17571
  const relPages = [
16460
17572
  ...pages.map((page) => ({
16461
- entrypoint: toPosix(relative14(root, page.entrypoint)),
17573
+ entrypoint: toPosix(relative15(root, page.entrypoint)),
16462
17574
  pattern: page.pattern
16463
17575
  })),
16464
17576
  ...mcpDiscoveryPages(project, pages)
@@ -16485,14 +17597,14 @@ var eject = async (root) => {
16485
17597
  searchClientPath: "./src/generated/search-client.ts",
16486
17598
  themePath: "./src/generated/app.css"
16487
17599
  }),
16488
- path: join31(root, "astro.config.mjs")
17600
+ path: join33(root, "astro.config.mjs")
16489
17601
  },
16490
17602
  {
16491
17603
  content: runtimeTsconfigTemplate(),
16492
- path: join31(root, "tsconfig.json"),
17604
+ path: join33(root, "tsconfig.json"),
16493
17605
  skipIfExists: true
16494
17606
  },
16495
- { content: envTemplate(), path: join31(srcDir, "env.d.ts") },
17607
+ { content: envTemplate(), path: join33(srcDir, "env.d.ts") },
16496
17608
  {
16497
17609
  content: contentConfigTemplate({
16498
17610
  config,
@@ -16500,7 +17612,7 @@ var eject = async (root) => {
16500
17612
  staged: hasStaged,
16501
17613
  stagedBase: stagedDir
16502
17614
  }),
16503
- path: join31(srcDir, "content.config.ts")
17615
+ path: join33(srcDir, "content.config.ts")
16504
17616
  },
16505
17617
  {
16506
17618
  content: catchAllPageTemplate({
@@ -16509,19 +17621,19 @@ var eject = async (root) => {
16509
17621
  mathEnabled: usesMath,
16510
17622
  needsReact
16511
17623
  }),
16512
- path: join31(srcDir, "pages", "[...slug].astro")
17624
+ path: join33(srcDir, "pages", "[...slug].astro")
16513
17625
  },
16514
17626
  {
16515
17627
  content: planComponentSlots(componentsImport, null).module,
16516
- path: join31(genDir, "components.ts")
17628
+ path: join33(genDir, "components.ts")
16517
17629
  },
16518
17630
  {
16519
17631
  content: islandMapTemplate(islands.islands),
16520
- path: join31(genDir, "islands.ts")
17632
+ path: join33(genDir, "islands.ts")
16521
17633
  },
16522
17634
  {
16523
17635
  content: exampleMapTemplate(examples.examples, config.basePath),
16524
- path: join31(genDir, "examples.ts")
17636
+ path: join33(genDir, "examples.ts")
16525
17637
  },
16526
17638
  {
16527
17639
  content: examplesEntryTemplate({
@@ -16529,7 +17641,7 @@ var eject = async (root) => {
16529
17641
  sources: ["../../**/*.{astro,jsx,svelte,ts,tsx,vue}"],
16530
17642
  userCss: userExamplesCss
16531
17643
  }),
16532
- path: join31(genDir, "examples.css")
17644
+ path: join33(genDir, "examples.css")
16533
17645
  },
16534
17646
  {
16535
17647
  content: tailwindEntryTemplate({
@@ -16541,30 +17653,30 @@ var eject = async (root) => {
16541
17653
  twoslashCss: twoslashCss(),
16542
17654
  userTheme
16543
17655
  }),
16544
- path: join31(genDir, "app.css")
17656
+ path: join33(genDir, "app.css")
16545
17657
  },
16546
- { content: buildRuntimeData(project), path: join31(genDir, "data.json") },
17658
+ { content: buildRuntimeData(project), path: join33(genDir, "data.json") },
16547
17659
  {
16548
17660
  content: askComponentTemplate(askEnabled),
16549
- path: join31(genDir, "Ask.astro")
17661
+ path: join33(genDir, "Ask.astro")
16550
17662
  },
16551
17663
  {
16552
17664
  content: `${JSON.stringify(ejectOpenApiData(project))}
16553
17665
  `,
16554
- path: join31(genDir, "openapi.json")
17666
+ path: join33(genDir, "openapi.json")
16555
17667
  },
16556
17668
  {
16557
17669
  content: `${JSON.stringify(rawMarkdown)}
16558
17670
  `,
16559
- path: join31(genDir, "raw-markdown.json")
17671
+ path: join33(genDir, "raw-markdown.json")
16560
17672
  },
16561
17673
  {
16562
17674
  content: rawMarkdownEndpointTemplate("md"),
16563
- path: join31(srcDir, "pages", "[...slug].md.ts")
17675
+ path: join33(srcDir, "pages", "[...slug].md.ts")
16564
17676
  },
16565
17677
  {
16566
17678
  content: rawMarkdownEndpointTemplate("mdx"),
16567
- path: join31(srcDir, "pages", "[...slug].mdx.ts")
17679
+ path: join33(srcDir, "pages", "[...slug].mdx.ts")
16568
17680
  }
16569
17681
  ];
16570
17682
  if (askEnabled) {
@@ -16573,7 +17685,7 @@ var eject = async (root) => {
16573
17685
  if (config.seo.og.enabled) {
16574
17686
  files.push({
16575
17687
  content: ogEndpointTemplate(customOgRoutes(pages, config.title, config.seo.og.titles)),
16576
- path: join31(srcDir, "pages", "og", "[...slug].png.ts")
17688
+ path: join33(srcDir, "pages", "og", "[...slug].png.ts")
16577
17689
  });
16578
17690
  }
16579
17691
  files.push(...await mcpFiles(project, pages, srcDir, genDir), ...changelogFiles(project, pages, srcDir, {
@@ -16585,28 +17697,28 @@ var eject = async (root) => {
16585
17697
  if (!routeIsTaken(pages, project.graph.pages, "/404")) {
16586
17698
  files.push({
16587
17699
  content: notFoundPageTemplate(),
16588
- path: join31(srcDir, "pages", "404.astro")
17700
+ path: join33(srcDir, "pages", "404.astro")
16589
17701
  });
16590
17702
  }
16591
17703
  files.push({
16592
17704
  content: searchClientTemplate(config),
16593
- path: join31(genDir, "search-client.ts")
17705
+ path: join33(genDir, "search-client.ts")
16594
17706
  });
16595
17707
  if (servesStaticIndex(config.search.provider)) {
16596
17708
  const documents = await buildSearchDocuments(project);
16597
17709
  files.push({
16598
17710
  content: `${JSON.stringify(documents)}
16599
17711
  `,
16600
- path: join31(genDir, "search.json")
17712
+ path: join33(genDir, "search.json")
16601
17713
  }, {
16602
17714
  content: searchEndpointTemplate(),
16603
- path: join31(srcDir, "pages", "blume-search.json.ts")
17715
+ path: join33(srcDir, "pages", "blume-search.json.ts")
16604
17716
  });
16605
17717
  }
16606
17718
  if (config.search.provider === "mixedbread") {
16607
17719
  files.push({
16608
17720
  content: mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""),
16609
- path: join31(srcDir, "pages", "api", "search.ts")
17721
+ path: join33(srcDir, "pages", "api", "search.ts")
16610
17722
  });
16611
17723
  }
16612
17724
  const feeds = buildRssFeeds(project);
@@ -16615,10 +17727,10 @@ var eject = async (root) => {
16615
17727
  files.push({
16616
17728
  content: `${JSON.stringify(feedXml)}
16617
17729
  `,
16618
- path: join31(genDir, "rss.json")
17730
+ path: join33(genDir, "rss.json")
16619
17731
  }, {
16620
17732
  content: rssEndpointTemplate(),
16621
- path: join31(srcDir, "pages", "[section]", "rss.xml.ts")
17733
+ path: join33(srcDir, "pages", "[section]", "rss.xml.ts")
16622
17734
  });
16623
17735
  }
16624
17736
  const warnings = [];
@@ -16632,28 +17744,28 @@ var eject = async (root) => {
16632
17744
  for (const file of references.files) {
16633
17745
  files.push({
16634
17746
  content: file.content,
16635
- path: join31(srcDir, "pages", file.pagePath)
17747
+ path: join33(srcDir, "pages", file.pagePath)
16636
17748
  });
16637
17749
  }
16638
17750
  }
16639
17751
  files.push(...islands.islands.map((island) => ({
16640
17752
  content: islandWrapperTemplate(island),
16641
- path: join31(genDir, "islands", `${island.name}.astro`)
17753
+ path: join33(genDir, "islands", `${island.name}.astro`)
16642
17754
  })), ...examples.examples.map((example) => ({
16643
17755
  content: exampleWrapperTemplate(example),
16644
- path: join31(genDir, "examples", `${exampleSlug(example.path)}.astro`)
17756
+ path: join33(genDir, "examples", `${exampleSlug(example.path)}.astro`)
16645
17757
  })), ...examplesPreviewFiles(srcDir, config.basePath, examples.examples.length > 0));
16646
17758
  for (const [entryId, content] of staged) {
16647
- files.push({ content, path: join31(root, stagedDir, entryId) });
17759
+ files.push({ content, path: join33(root, stagedDir, entryId) });
16648
17760
  }
16649
- const written = files.filter((file) => !(file.skipIfExists && existsSync18(file.path)));
17761
+ const written = files.filter((file) => !(file.skipIfExists && existsSync20(file.path)));
16650
17762
  await Promise.all(written.map(async (file) => {
16651
- await mkdir7(join31(file.path, ".."), { recursive: true });
17763
+ await mkdir8(join33(file.path, ".."), { recursive: true });
16652
17764
  await writeFile9(file.path, file.content, "utf-8");
16653
17765
  }));
16654
- const assetsSrc = join31(context.outDir, "public", "blume-assets");
16655
- if (existsSync18(assetsSrc)) {
16656
- await cp2(assetsSrc, join31(root, "public", "blume-assets"), {
17766
+ const assetsSrc = join33(context.outDir, "public", "blume-assets");
17767
+ if (existsSync20(assetsSrc)) {
17768
+ await cp2(assetsSrc, join33(root, "public", "blume-assets"), {
16657
17769
  recursive: true
16658
17770
  });
16659
17771
  }
@@ -16662,8 +17774,8 @@ var eject = async (root) => {
16662
17774
  };
16663
17775
 
16664
17776
  // src/cli/eject-scripts.ts
16665
- import { readFile as readFile16, writeFile as writeFile10 } from "node:fs/promises";
16666
- import { join as join32 } from "pathe";
17777
+ import { readFile as readFile19, writeFile as writeFile10 } from "node:fs/promises";
17778
+ import { join as join34 } from "pathe";
16667
17779
  var droppedArtifactNotices = (config) => {
16668
17780
  const notices = [];
16669
17781
  if (config.search.provider === "pagefind") {
@@ -16690,10 +17802,10 @@ var droppedArtifactNotices = (config) => {
16690
17802
  return notices;
16691
17803
  };
16692
17804
  var updatePackageScripts = async (root) => {
16693
- const pkgPath = join32(root, "package.json");
17805
+ const pkgPath = join34(root, "package.json");
16694
17806
  let pkg;
16695
17807
  try {
16696
- pkg = JSON.parse(await readFile16(pkgPath, "utf-8"));
17808
+ pkg = JSON.parse(await readFile19(pkgPath, "utf-8"));
16697
17809
  } catch {
16698
17810
  return;
16699
17811
  }
@@ -16709,9 +17821,9 @@ var updatePackageScripts = async (root) => {
16709
17821
  };
16710
17822
 
16711
17823
  // src/cli/init/scaffold.ts
16712
- import { existsSync as existsSync19 } from "node:fs";
16713
- import { mkdir as mkdir8, writeFile as writeFile11 } from "node:fs/promises";
16714
- import { basename as basename5, dirname as dirname12, isAbsolute as isAbsolute9, join as join33, relative as relative15 } from "pathe";
17824
+ import { existsSync as existsSync21 } from "node:fs";
17825
+ import { mkdir as mkdir9, writeFile as writeFile11 } from "node:fs/promises";
17826
+ import { basename as basename5, dirname as dirname14, isAbsolute as isAbsolute10, join as join35, relative as relative16 } from "pathe";
16715
17827
 
16716
17828
  // src/core/package-json.ts
16717
17829
  var toPackageName = (raw) => raw.toLowerCase().replaceAll(/[^a-z0-9._-]+/gu, "-").replaceAll(/^[-_.]+|[-_.]+$/gu, "") || "docs";
@@ -16770,7 +17882,7 @@ var STARTERS = {
16770
17882
  files: (dir) => [
16771
17883
  {
16772
17884
  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`."),
16773
- path: join33(dir, "index.mdx")
17885
+ path: join35(dir, "index.mdx")
16774
17886
  }
16775
17887
  ]
16776
17888
  },
@@ -16785,7 +17897,7 @@ var STARTERS = {
16785
17897
  files: (dir) => [
16786
17898
  {
16787
17899
  content: page("Introduction", "Welcome to your new Blume docs.", "# Introduction\n\nWrite your docs here, and log releases under `changelog/`."),
16788
- path: join33(dir, "index.mdx")
17900
+ path: join35(dir, "index.mdx")
16789
17901
  },
16790
17902
  {
16791
17903
  content: `---
@@ -16796,7 +17908,7 @@ date: 2026-01-01
16796
17908
 
16797
17909
  The first release. Edit \`${dir}/changelog/v1-0-0.mdx\` or add new entries beside it.
16798
17910
  `,
16799
- path: join33(dir, "changelog", "v1-0-0.mdx")
17911
+ path: join35(dir, "changelog", "v1-0-0.mdx")
16800
17912
  }
16801
17913
  ]
16802
17914
  },
@@ -16809,7 +17921,7 @@ The first release. Edit \`${dir}/changelog/v1-0-0.mdx\` or add new entries besid
16809
17921
  Welcome to **Blume** — markdown-first docs powered by Astro and Vite.
16810
17922
 
16811
17923
  Edit \`${dir}/index.mdx\` to get started, then run \`blume dev\`.`),
16812
- path: join33(dir, "index.mdx")
17924
+ path: join35(dir, "index.mdx")
16813
17925
  }
16814
17926
  ]
16815
17927
  },
@@ -16820,11 +17932,11 @@ Edit \`${dir}/index.mdx\` to get started, then run \`blume dev\`.`),
16820
17932
  content: page("Introduction", "Get started with the SDK.", `# Introduction
16821
17933
 
16822
17934
  Install the SDK and make your first call. See [Installation](/installation).`),
16823
- path: join33(dir, "index.mdx")
17935
+ path: join35(dir, "index.mdx")
16824
17936
  },
16825
17937
  {
16826
17938
  content: page("Installation", "Install the SDK.", "# Installation\n\n```package-install\nyour-sdk\n```"),
16827
- path: join33(dir, "installation.mdx")
17939
+ path: join35(dir, "installation.mdx")
16828
17940
  }
16829
17941
  ]
16830
17942
  }
@@ -16839,7 +17951,7 @@ var detectPackageManager = (userAgent) => {
16839
17951
  const name = userAgent?.split("/")[0];
16840
17952
  return name !== undefined && PACKAGE_MANAGERS.includes(name) ? name : "npm";
16841
17953
  };
16842
- var validateContentDir = (root, dir) => isAbsolute9(dir) || relative15(root, join33(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
17954
+ var validateContentDir = (root, dir) => isAbsolute10(dir) || relative16(root, join35(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
16843
17955
  var titleize = (raw) => {
16844
17956
  const words = raw.replaceAll(/[-_.]+/gu, " ").split(/\s+/u).filter(Boolean);
16845
17957
  return words.length === 0 ? "My Docs" : words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
@@ -16916,27 +18028,27 @@ export default defineConfig({
16916
18028
  `;
16917
18029
  var extraDepsFor = (sources) => ({
16918
18030
  ...sources.includes("notion") && { "@notionhq/client": "^2.2.15" },
16919
- ...sources.includes("sanity") && { "@sanity/client": "^6.21.0" }
18031
+ ...sources.includes("sanity") && { "@sanity/client": "^7.25.0" }
16920
18032
  });
16921
18033
  var buildPlan = (root, answers) => {
16922
18034
  const files = [
16923
18035
  {
16924
18036
  content: blumePackageJson(toPackageName(basename5(root)), extraDepsFor(answers.sources)),
16925
- path: join33(root, "package.json")
18037
+ path: join35(root, "package.json")
16926
18038
  },
16927
- { content: buildConfig(answers), path: join33(root, "blume.config.ts") }
18039
+ { content: buildConfig(answers), path: join35(root, "blume.config.ts") }
16928
18040
  ];
16929
18041
  if (answers.sources.length === 0 || answers.sources.includes("filesystem")) {
16930
- files.push(...STARTERS[answers.template].files(answers.contentDir).map((file) => ({ ...file, path: join33(root, file.path) })));
18042
+ files.push(...STARTERS[answers.template].files(answers.contentDir).map((file) => ({ ...file, path: join35(root, file.path) })));
16931
18043
  }
16932
18044
  return files;
16933
18045
  };
16934
18046
  var writeFileSafe = async (file, log) => {
16935
- if (existsSync19(file.path)) {
18047
+ if (existsSync21(file.path)) {
16936
18048
  log.info(`Skipped existing ${file.path}`);
16937
18049
  return false;
16938
18050
  }
16939
- await mkdir8(dirname12(file.path), { recursive: true });
18051
+ await mkdir9(dirname14(file.path), { recursive: true });
16940
18052
  await writeFile11(file.path, file.content, "utf-8");
16941
18053
  log.success(`Created ${file.path}`);
16942
18054
  return true;
@@ -17012,7 +18124,7 @@ var ejectCommand = defineCommand7({
17012
18124
  }
17013
18125
  logger.success(`Ejected ${files.length} file(s):`);
17014
18126
  for (const file of files) {
17015
- process.stdout.write(` ${relative16(root, file)}
18127
+ process.stdout.write(` ${relative17(root, file)}
17016
18128
  `);
17017
18129
  }
17018
18130
  reportDroppedArtifacts(notices);
@@ -17028,9 +18140,9 @@ The blume package remains importable.`);
17028
18140
  });
17029
18141
 
17030
18142
  // src/cli/commands/eval.ts
17031
- import { existsSync as existsSync20 } from "node:fs";
18143
+ import { existsSync as existsSync22 } from "node:fs";
17032
18144
  import { defineCommand as defineCommand8 } from "citty";
17033
- import { join as join37 } from "pathe";
18145
+ import { join as join39 } from "pathe";
17034
18146
 
17035
18147
  // src/eval/prompts.ts
17036
18148
  var readerPrompt = (question) => `You are evaluating whether a product's documentation can answer a user's question.
@@ -17099,7 +18211,7 @@ When you are done, print the file and suggest running \`blume eval\` to try it.`
17099
18211
  // src/eval/report.ts
17100
18212
  import { mkdtemp as mkdtemp2, writeFile as writeFile12 } from "node:fs/promises";
17101
18213
  import { tmpdir as tmpdir2 } from "node:os";
17102
- import { join as join34, relative as relative17 } from "pathe";
18214
+ import { join as join36, relative as relative18 } from "pathe";
17103
18215
  var ESC4 = String.fromCodePoint(27);
17104
18216
  var COLORS3 = {
17105
18217
  bold: `${ESC4}[1m`,
@@ -17180,15 +18292,15 @@ var summaryLine2 = (result) => {
17180
18292
  var headerLine = (total, agent) => `${COLORS3.bold}blume eval${COLORS3.reset} ${total} question(s) · ${AGENTS[agent].name}`;
17181
18293
  var startLine = (id2, index, total) => ` ${COLORS3.dim}▸ ${id2} (${index + 1}/${total})${COLORS3.reset}`;
17182
18294
  var fixLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code !== "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
17183
- const site = finding2.file ? `${relative17(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
18295
+ const site = finding2.file ? `${relative18(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
17184
18296
  return ` ${COLORS3.cyan}fix:${COLORS3.reset} ${site} ${COLORS3.dim}${finding2.message}${COLORS3.reset}`;
17185
18297
  });
17186
18298
  var warningLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code === "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
17187
- const site = finding2.file ? ` ${relative17(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
18299
+ const site = finding2.file ? ` ${relative18(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
17188
18300
  return ` ${COLORS3.yellow}⚠${COLORS3.reset}${site} ${COLORS3.dim}${finding2.message}${COLORS3.reset}`;
17189
18301
  });
17190
18302
  var evalReportJson = (result, root, threshold) => {
17191
- const diagnostics = result.diagnostics.map((diagnostic) => diagnostic.file ? { ...diagnostic, file: relative17(root, diagnostic.file) } : diagnostic);
18303
+ const diagnostics = result.diagnostics.map((diagnostic) => diagnostic.file ? { ...diagnostic, file: relative18(root, diagnostic.file) } : diagnostic);
17192
18304
  return `${JSON.stringify({
17193
18305
  diagnostics,
17194
18306
  eval: {
@@ -17204,21 +18316,21 @@ var evalReportJson = (result, root, threshold) => {
17204
18316
  `;
17205
18317
  };
17206
18318
  var writeEvalReport = async (result, root, threshold) => {
17207
- const dir = await mkdtemp2(join34(tmpdir2(), "blume-eval-"));
17208
- const path = join34(dir, "report.json");
18319
+ const dir = await mkdtemp2(join36(tmpdir2(), "blume-eval-"));
18320
+ const path = join36(dir, "report.json");
17209
18321
  await writeFile12(path, evalReportJson(result, root, threshold));
17210
18322
  return path;
17211
18323
  };
17212
18324
 
17213
18325
  // src/eval/run.ts
17214
- import { mkdir as mkdir9, mkdtemp as mkdtemp3, writeFile as writeFile14 } from "node:fs/promises";
18326
+ import { mkdir as mkdir10, mkdtemp as mkdtemp3, writeFile as writeFile14 } from "node:fs/promises";
17215
18327
  import { tmpdir as tmpdir3 } from "node:os";
17216
- import { join as join36 } from "pathe";
18328
+ import { join as join38 } from "pathe";
17217
18329
 
17218
18330
  // src/eval/agents.ts
17219
18331
  import { spawn as spawn2 } from "node:child_process";
17220
- import { readFile as readFile17, writeFile as writeFile13 } from "node:fs/promises";
17221
- import { join as join35 } from "pathe";
18332
+ import { readFile as readFile20, writeFile as writeFile13 } from "node:fs/promises";
18333
+ import { join as join37 } from "pathe";
17222
18334
  import { z as z3 } from "zod";
17223
18335
  var KILL_GRACE_MS = 5000;
17224
18336
  var MCP_TOOL_NAMES = [
@@ -17240,7 +18352,7 @@ var DISALLOWED_TOOLS = [
17240
18352
  "WebSearch",
17241
18353
  "Task"
17242
18354
  ];
17243
- var runAgentHeadless = (bin, args, options) => new Promise((resolve9, reject) => {
18355
+ var runAgentHeadless = (bin, args, options) => new Promise((resolve11, reject) => {
17244
18356
  const platform = options.platform ?? process.platform;
17245
18357
  const child = spawn2(bin, args, {
17246
18358
  cwd: options.cwd,
@@ -17269,12 +18381,12 @@ var runAgentHeadless = (bin, args, options) => new Promise((resolve9, reject) =>
17269
18381
  });
17270
18382
  child.once("close", (code) => {
17271
18383
  clearTimeout(deadline);
17272
- resolve9({ code: code ?? 1, stderr, stdout, timedOut });
18384
+ resolve11({ code: code ?? 1, stderr, stdout, timedOut });
17273
18385
  });
17274
18386
  child.once("exit", (code) => {
17275
18387
  if (timedOut) {
17276
18388
  clearTimeout(deadline);
17277
- resolve9({ code: code ?? 1, stderr, stdout, timedOut });
18389
+ resolve11({ code: code ?? 1, stderr, stdout, timedOut });
17278
18390
  }
17279
18391
  });
17280
18392
  child.stdin.end(options.prompt);
@@ -17284,7 +18396,7 @@ var writeMcpConfig = async (dir, snapshotPath, launcher) => {
17284
18396
  args: [process.argv[1] ?? "", "mcp-stdio", "--data", snapshotPath],
17285
18397
  command: process.execPath
17286
18398
  };
17287
- const configPath = join35(dir, "mcp-config.json");
18399
+ const configPath = join37(dir, "mcp-config.json");
17288
18400
  const config = {
17289
18401
  mcpServers: {
17290
18402
  [MCP_SERVER_NAME]: { args: resolved.args, command: resolved.command }
@@ -17392,17 +18504,17 @@ var readAgentOutput = async (kind, result, lastMessagePath) => {
17392
18504
  text: payload.data.result
17393
18505
  };
17394
18506
  }
17395
- let text;
18507
+ let text2;
17396
18508
  try {
17397
- const raw = await readFile17(lastMessagePath, "utf-8");
17398
- text = raw.trim();
18509
+ const raw = await readFile20(lastMessagePath, "utf-8");
18510
+ text2 = raw.trim();
17399
18511
  } catch {
17400
18512
  return { detail: "no last message written", isError: true, text: "" };
17401
18513
  }
17402
- if (text === "") {
18514
+ if (text2 === "") {
17403
18515
  return { detail: "empty last message", isError: true, text: "" };
17404
18516
  }
17405
- return { isError: false, text };
18517
+ return { isError: false, text: text2 };
17406
18518
  };
17407
18519
  var verdictSchema = z3.object({
17408
18520
  missing: z3.array(z3.string()).default([]),
@@ -17410,15 +18522,15 @@ var verdictSchema = z3.object({
17410
18522
  pass: z3.boolean(),
17411
18523
  score: z3.number().min(0).max(1).optional()
17412
18524
  });
17413
- var parseVerdict = (text) => {
17414
- const start = text.indexOf("{");
17415
- const end = text.lastIndexOf("}");
18525
+ var parseVerdict = (text2) => {
18526
+ const start = text2.indexOf("{");
18527
+ const end = text2.lastIndexOf("}");
17416
18528
  if (start === -1 || end <= start) {
17417
18529
  return;
17418
18530
  }
17419
18531
  let parsed;
17420
18532
  try {
17421
- parsed = JSON.parse(text.slice(start, end + 1));
18533
+ parsed = JSON.parse(text2.slice(start, end + 1));
17422
18534
  } catch {
17423
18535
  return;
17424
18536
  }
@@ -17427,7 +18539,7 @@ var parseVerdict = (text) => {
17427
18539
  };
17428
18540
 
17429
18541
  // src/eval/schema.ts
17430
- import { readFile as readFile18 } from "node:fs/promises";
18542
+ import { readFile as readFile21 } from "node:fs/promises";
17431
18543
  import { load as load2 } from "js-yaml";
17432
18544
  import { z as z4 } from "zod";
17433
18545
  var ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/u;
@@ -17472,7 +18584,7 @@ var describeIssues = (error) => error.issues.map((issue) => {
17472
18584
  var loadEvalsFile = async (path) => {
17473
18585
  let raw;
17474
18586
  try {
17475
- raw = await readFile18(path, "utf-8");
18587
+ raw = await readFile21(path, "utf-8");
17476
18588
  } catch {
17477
18589
  throw new EvalsFileError(path, `No evals file found at ${path}. Run \`blume eval init\` to draft one.`);
17478
18590
  }
@@ -17564,9 +18676,9 @@ var errored = (question, detail, durationMs) => ({
17564
18676
  var runQuestion = async (question, index, context) => {
17565
18677
  const started = performance.now();
17566
18678
  const elapsed = () => Math.round(performance.now() - started);
17567
- const workDir = join36(context.dir, `work-${index}`);
17568
- await mkdir9(workDir, { recursive: true });
17569
- const answerPath = join36(workDir, "answer.txt");
18679
+ const workDir = join38(context.dir, `work-${index}`);
18680
+ await mkdir10(workDir, { recursive: true });
18681
+ const answerPath = join38(workDir, "answer.txt");
17570
18682
  const reader = await context.run(context.bin, agentArgs(context.kind, { lastMessagePath: answerPath, mcp: context.mcp }), {
17571
18683
  cwd: workDir,
17572
18684
  prompt: readerPrompt(question),
@@ -17579,7 +18691,7 @@ var runQuestion = async (question, index, context) => {
17579
18691
  costUsd: answer.costUsd
17580
18692
  };
17581
18693
  }
17582
- const verdictPath = join36(workDir, "verdict.txt");
18694
+ const verdictPath = join38(workDir, "verdict.txt");
17583
18695
  const judge = await context.run(context.bin, agentArgs(context.kind, { lastMessagePath: verdictPath }), {
17584
18696
  cwd: workDir,
17585
18697
  prompt: judgePrompt(question, answer.text),
@@ -17621,8 +18733,8 @@ var runEval = async (options) => {
17621
18733
  const kind = options.agent;
17622
18734
  const run = options.run ?? runAgentHeadless;
17623
18735
  const anchor = { path: options.evalsPath, raw: options.rawEvals };
17624
- const dir = await mkdtemp3(join36(tmpdir3(), "blume-eval-"));
17625
- const snapshotPath = join36(dir, "mcp-data.json");
18736
+ const dir = await mkdtemp3(join38(tmpdir3(), "blume-eval-"));
18737
+ const snapshotPath = join38(dir, "mcp-data.json");
17626
18738
  await writeFile14(snapshotPath, JSON.stringify(await buildMcpData(options.project)));
17627
18739
  const mcp = await writeMcpConfig(dir, snapshotPath);
17628
18740
  const context = {
@@ -17760,8 +18872,8 @@ var runFixHandoff = async (agent, result, root, threshold) => {
17760
18872
  }
17761
18873
  };
17762
18874
  var runInit = async (agent, file) => {
17763
- const path = join37(process.cwd(), file);
17764
- if (existsSync20(path)) {
18875
+ const path = join39(process.cwd(), file);
18876
+ if (existsSync22(path)) {
17765
18877
  logger.error(`${file} already exists — edit it directly, or pass --file to draft elsewhere.`);
17766
18878
  process.exit(1);
17767
18879
  }
@@ -17825,7 +18937,7 @@ var evalCommand = defineCommand8({
17825
18937
  let result;
17826
18938
  try {
17827
18939
  const project = await scanProject(root, { mode: "build" });
17828
- const evalsPath = join37(root, args.file);
18940
+ const evalsPath = join39(root, args.file);
17829
18941
  const { evals, raw } = await loadEvalsFile(evalsPath);
17830
18942
  process.stderr.write(`${headerLine(evals.questions.length, agent)}
17831
18943
 
@@ -17899,10 +19011,10 @@ var evalCommand = defineCommand8({
17899
19011
  // src/cli/commands/init.ts
17900
19012
  import * as clack from "@clack/prompts";
17901
19013
  import { defineCommand as defineCommand9 } from "citty";
17902
- import { resolve as resolve10 } from "pathe";
19014
+ import { resolve as resolve12 } from "pathe";
17903
19015
 
17904
19016
  // src/cli/init/questions.ts
17905
- import { basename as basename6, resolve as resolve9 } from "pathe";
19017
+ import { basename as basename6, resolve as resolve11 } from "pathe";
17906
19018
  var cancelled = (value) => typeof value === "symbol";
17907
19019
  var collectAnswers = async (prompter, flags, defaults) => {
17908
19020
  const directory = flags.directory ?? await prompter.text({
@@ -17913,7 +19025,7 @@ var collectAnswers = async (prompter, flags, defaults) => {
17913
19025
  if (cancelled(directory)) {
17914
19026
  return null;
17915
19027
  }
17916
- const root = resolve9(defaults.cwd, directory);
19028
+ const root = resolve11(defaults.cwd, directory);
17917
19029
  const title = await prompter.text({
17918
19030
  initialValue: titleize(basename6(root)),
17919
19031
  message: "What's your docs site called?",
@@ -18082,7 +19194,7 @@ var initCommand = defineCommand9({
18082
19194
  title: "My Docs"
18083
19195
  };
18084
19196
  }
18085
- const root = resolve10(cwd, answers.directory);
19197
+ const root = resolve12(cwd, answers.directory);
18086
19198
  if (validateContentDir(root, answers.contentDir) !== undefined) {
18087
19199
  logger.error(`Invalid --content-dir "${answers.contentDir}" (must be a path inside the project).`);
18088
19200
  process.exit(1);
@@ -18112,7 +19224,7 @@ var initCommand = defineCommand9({
18112
19224
  });
18113
19225
 
18114
19226
  // src/cli/commands/mcp-stdio.ts
18115
- import { readFile as readFile19 } from "node:fs/promises";
19227
+ import { readFile as readFile22 } from "node:fs/promises";
18116
19228
  import { defineCommand as defineCommand10 } from "citty";
18117
19229
 
18118
19230
  // src/ai/mcp/stdio.ts
@@ -18137,19 +19249,83 @@ var SCHEMA = {
18137
19249
  title: "string"
18138
19250
  };
18139
19251
  var BOOST = { description: 2, title: 3 };
18140
- var buildOramaIndex = async (documents) => {
18141
- const db = create({ schema: SCHEMA });
19252
+ var SEGMENTED_LANGUAGES = new Set(["ja", "ko", "th", "zh"]);
19253
+ var BIGRAM_LANGUAGES = new Set(["ja", "zh"]);
19254
+ var BIGRAM_SCRIPTS = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}々〆〇ー゙゚]+$/u;
19255
+ var addBigrams = (run, tokens) => {
19256
+ const characters = [...run];
19257
+ if (characters.length === 1) {
19258
+ tokens.add(run);
19259
+ return;
19260
+ }
19261
+ let previous = "";
19262
+ for (const character of characters) {
19263
+ if (previous) {
19264
+ tokens.add(previous + character);
19265
+ }
19266
+ previous = character;
19267
+ }
19268
+ };
19269
+ var segmentingTokenizer = (locale) => {
19270
+ const language = locale?.toLowerCase().split(/[-_]/u)[0] ?? "";
19271
+ if (!SEGMENTED_LANGUAGES.has(language)) {
19272
+ return;
19273
+ }
19274
+ if (typeof Intl.Segmenter !== "function") {
19275
+ return;
19276
+ }
19277
+ const segmenter = new Intl.Segmenter(language, { granularity: "word" });
19278
+ const bigram = BIGRAM_LANGUAGES.has(language);
19279
+ return {
19280
+ language,
19281
+ normalizationCache: new Map,
19282
+ tokenize: (raw) => {
19283
+ const tokens = new Set;
19284
+ let run = "";
19285
+ const flush = () => {
19286
+ if (run) {
19287
+ addBigrams(run, tokens);
19288
+ run = "";
19289
+ }
19290
+ };
19291
+ for (const segment of segmenter.segment(raw.toLowerCase())) {
19292
+ if (!segment.isWordLike) {
19293
+ flush();
19294
+ continue;
19295
+ }
19296
+ if (bigram && BIGRAM_SCRIPTS.test(segment.segment)) {
19297
+ run += segment.segment;
19298
+ continue;
19299
+ }
19300
+ flush();
19301
+ tokens.add(segment.segment);
19302
+ }
19303
+ flush();
19304
+ return [...tokens];
19305
+ }
19306
+ };
19307
+ };
19308
+ var buildOramaIndex = async (documents, locale) => {
19309
+ const tokenizer = segmentingTokenizer(locale);
19310
+ const db = create({
19311
+ schema: SCHEMA,
19312
+ ...tokenizer ? { components: { tokenizer } } : {}
19313
+ });
18142
19314
  await insertMultiple(db, documents);
18143
19315
  return db;
18144
19316
  };
19317
+ var ALL_TOKENS = 0;
18145
19318
  var queryOramaIndex = async (db, term, limit, locale) => {
18146
- const found = await search(db, {
19319
+ const params = {
18147
19320
  boost: BOOST,
18148
19321
  limit,
18149
19322
  properties: ["title", "description", "content"],
18150
19323
  term,
18151
19324
  ...locale ? { where: { locale: { eq: locale } } } : {}
18152
- });
19325
+ };
19326
+ const bigrammed = BIGRAM_LANGUAGES.has(db.tokenizer?.language ?? "");
19327
+ const strict = bigrammed ? await search(db, { ...params, threshold: ALL_TOKENS }) : undefined;
19328
+ const found = strict && strict.hits.length > 0 ? strict : await search(db, params);
18153
19329
  return found.hits.map((hit) => hit.document);
18154
19330
  };
18155
19331
 
@@ -18226,14 +19402,14 @@ var excerptFor = (doc) => {
18226
19402
  const head = doc.content.slice(0, EXCERPT_LENGTH).trim();
18227
19403
  return doc.content.length > EXCERPT_LENGTH ? `${head}…` : head;
18228
19404
  };
18229
- var text = (value, isError = false) => ({
19405
+ var text2 = (value, isError = false) => ({
18230
19406
  content: [{ text: value, type: "text" }],
18231
19407
  ...isError ? { isError: true } : {}
18232
19408
  });
18233
- var createIndexProvider = (documents) => {
19409
+ var createIndexProvider = (documents, locale) => {
18234
19410
  let dbPromise = null;
18235
- return () => {
18236
- dbPromise ??= buildOramaIndex(documents);
19411
+ return function provideIndex() {
19412
+ dbPromise ??= buildOramaIndex(documents, locale);
18237
19413
  return dbPromise;
18238
19414
  };
18239
19415
  };
@@ -18256,18 +19432,18 @@ var buildServer = (data, index) => {
18256
19432
  title: doc.title,
18257
19433
  url: urlFor(doc.route, data)
18258
19434
  }));
18259
- return text(JSON.stringify(results, null, 2));
19435
+ return text2(JSON.stringify(results, null, 2));
18260
19436
  }
18261
19437
  if (name === "get_page") {
18262
19438
  const key = normalizeRoute2(asString2(args.route), data);
18263
19439
  const markdown = data.pages[key];
18264
19440
  if (markdown === undefined) {
18265
- return text(`No page found at "${key}". Use list_pages or search_docs to find valid routes.`, true);
19441
+ return text2(`No page found at "${key}". Use list_pages or search_docs to find valid routes.`, true);
18266
19442
  }
18267
- return text(markdown);
19443
+ return text2(markdown);
18268
19444
  }
18269
19445
  if (name === "list_pages") {
18270
- return text(JSON.stringify(data.routes.map((route) => ({
19446
+ return text2(JSON.stringify(data.routes.map((route) => ({
18271
19447
  contentType: route.contentType,
18272
19448
  description: route.description,
18273
19449
  lastModified: route.lastModified,
@@ -18277,9 +19453,9 @@ var buildServer = (data, index) => {
18277
19453
  })), null, 2));
18278
19454
  }
18279
19455
  if (name === "get_navigation") {
18280
- return text(JSON.stringify(data.navigation, null, 2));
19456
+ return text2(JSON.stringify(data.navigation, null, 2));
18281
19457
  }
18282
- return text(`Unknown tool: ${name}`, true);
19458
+ return text2(`Unknown tool: ${name}`, true);
18283
19459
  });
18284
19460
  return server;
18285
19461
  };
@@ -18289,7 +19465,7 @@ var serveMcpStdio = async (data, streams = {}) => {
18289
19465
  const stdin = streams.stdin ?? process.stdin;
18290
19466
  const stdout = streams.stdout ?? process.stdout;
18291
19467
  const transport = new StdioServerTransport(stdin, stdout);
18292
- const server = buildServer(data, createIndexProvider(data.documents));
19468
+ const server = buildServer(data, createIndexProvider(data.documents, data.defaultLocale));
18293
19469
  await server.connect(transport);
18294
19470
  await once(stdin, "end");
18295
19471
  await transport.close();
@@ -18311,7 +19487,7 @@ var mcpStdioCommand = defineCommand10({
18311
19487
  async run({ args }) {
18312
19488
  let data;
18313
19489
  try {
18314
- data = JSON.parse(await readFile19(args.data, "utf-8"));
19490
+ data = JSON.parse(await readFile22(args.data, "utf-8"));
18315
19491
  } catch (error) {
18316
19492
  const detail = error instanceof Error ? error.message : String(error);
18317
19493
  process.stderr.write(`blume mcp-stdio: cannot load the snapshot at ${args.data}: ${detail}
@@ -18323,10 +19499,10 @@ var mcpStdioCommand = defineCommand10({
18323
19499
  });
18324
19500
 
18325
19501
  // src/cli/commands/preview.ts
18326
- import { existsSync as existsSync21 } from "node:fs";
19502
+ import { existsSync as existsSync23 } from "node:fs";
18327
19503
  import { preview } from "astro";
18328
19504
  import { defineCommand as defineCommand11 } from "citty";
18329
- import { join as join38 } from "pathe";
19505
+ import { join as join40 } from "pathe";
18330
19506
  var previewCommand = defineCommand11({
18331
19507
  args: {
18332
19508
  host: { description: "Network host to bind.", type: "string" },
@@ -18340,7 +19516,7 @@ var previewCommand = defineCommand11({
18340
19516
  const root = process.cwd();
18341
19517
  const { config } = await loadConfig(root);
18342
19518
  const context = resolveProjectContext(root, config);
18343
- if (!existsSync21(join38(context.outDir, "astro.config.mjs"))) {
19519
+ if (!existsSync23(join40(context.outDir, "astro.config.mjs"))) {
18344
19520
  logger.error("No build found. Run `blume build` first.");
18345
19521
  process.exit(1);
18346
19522
  }
@@ -18358,7 +19534,7 @@ var previewCommand = defineCommand11({
18358
19534
  // src/cli/commands/sync.ts
18359
19535
  import { rm as rm4 } from "node:fs/promises";
18360
19536
  import { defineCommand as defineCommand12 } from "citty";
18361
- import { join as join39 } from "pathe";
19537
+ import { join as join41 } from "pathe";
18362
19538
  var syncCommand = defineCommand12({
18363
19539
  args: {
18364
19540
  force: {
@@ -18380,7 +19556,7 @@ var syncCommand = defineCommand12({
18380
19556
  if (args.force) {
18381
19557
  const { config } = await loadConfig(root);
18382
19558
  const context = resolveProjectContext(root, config);
18383
- await rm4(join39(context.outDir, "cache"), { force: true, recursive: true });
19559
+ await rm4(join41(context.outDir, "cache"), { force: true, recursive: true });
18384
19560
  logger.info("Cleared source cache.");
18385
19561
  }
18386
19562
  const lock = readDevLock(resolveRuntimeDir(root));
@@ -18398,13 +19574,13 @@ var syncCommand = defineCommand12({
18398
19574
  });
18399
19575
 
18400
19576
  // src/cli/commands/validate.ts
18401
- import { existsSync as existsSync23 } from "node:fs";
19577
+ import { existsSync as existsSync25 } from "node:fs";
18402
19578
  import { defineCommand as defineCommand13 } from "citty";
18403
- import { join as join41 } from "pathe";
19579
+ import { join as join43 } from "pathe";
18404
19580
 
18405
19581
  // src/core/links.ts
18406
- import { existsSync as existsSync22 } from "node:fs";
18407
- import { basename as basename7, join as join40 } from "pathe";
19582
+ import { existsSync as existsSync24 } from "node:fs";
19583
+ import { basename as basename7, join as join42 } from "pathe";
18408
19584
  var HTTP = /^https?:\/\//iu;
18409
19585
  var PROTOCOL_RELATIVE = /^\/\//u;
18410
19586
  var SCHEME = /^[a-z][a-z0-9+.-]*:/iu;
@@ -18417,7 +19593,7 @@ var decodePercent = (value) => {
18417
19593
  };
18418
19594
  var DOC_EXT = /\.(?:md|mdx)$/iu;
18419
19595
  var FILE_EXT = /\.[a-z0-9]+$/iu;
18420
- var assetIsPresent = (resolved, ctx) => ctx.publicDir !== null && existsSync22(join40(ctx.publicDir, resolved));
19596
+ var assetIsPresent = (resolved, ctx) => ctx.publicDir !== null && existsSync24(join42(ctx.publicDir, resolved));
18421
19597
  var NUMERIC_PREFIX3 = /^\d+[-_.]/u;
18422
19598
  var isIndexPage = (page2) => /^index\.(?:md|mdx)$/iu.test(basename7(page2.navPath).replace(NUMERIC_PREFIX3, ""));
18423
19599
  var applyRelativePart = (segments, part) => {
@@ -18628,12 +19804,12 @@ var validateCommand = defineCommand13({
18628
19804
  });
18629
19805
  extraRoutes.push(...manifest.routes.flatMap((route) => route.fallback ? [route.path] : []));
18630
19806
  }
18631
- const publicDir = join41(root, "public");
19807
+ const publicDir = join43(root, "public");
18632
19808
  diagnostics.push(...await validateLinks(project.graph, {
18633
19809
  basePath: project.config.basePath,
18634
19810
  checkExternal: Boolean(args.external),
18635
19811
  extraRoutes,
18636
- publicDir: existsSync23(publicDir) ? publicDir : null,
19812
+ publicDir: existsSync25(publicDir) ? publicDir : null,
18637
19813
  redirects: project.config.redirects
18638
19814
  }));
18639
19815
  } catch (error) {
@@ -18697,5 +19873,5 @@ process.on("unhandledRejection", (error) => {
18697
19873
  });
18698
19874
  runMain(main);
18699
19875
 
18700
- //# debugId=E4DFD2C6131D546564756E2164756E21
19876
+ //# debugId=CCE85D85BA5F467464756E2164756E21
18701
19877
  //# sourceMappingURL=index.js.map