blume 1.2.1 → 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 (71) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/cli/index.js +1680 -534
  3. package/dist/cli/index.js.map +37 -28
  4. package/dist/types/core/config-input.d.ts +131 -11
  5. package/dist/types/core/config.d.ts +9 -1
  6. package/dist/types/core/data.d.ts +24 -5
  7. package/dist/types/core/i18n-ui.d.ts +58 -799
  8. package/dist/types/core/schema.d.ts +534 -3305
  9. package/dist/types/theme/fonts.d.ts +55 -11
  10. package/docs/02-deployment.mdx +2 -0
  11. package/docs/07-faq.mdx +14 -14
  12. package/docs/advanced/skills.mdx +2 -2
  13. package/docs/configuration/ai.mdx +126 -2
  14. package/docs/configuration/index.mdx +19 -1
  15. package/docs/configuration/search.mdx +2 -0
  16. package/docs/configuration/seo.mdx +26 -3
  17. package/docs/configuration/theming.mdx +44 -2
  18. package/docs/content/syntax.mdx +18 -2
  19. package/docs/reference/cli.mdx +3 -3
  20. package/package.json +9 -8
  21. package/skills/blume/SKILL.md +6 -4
  22. package/skills/blume-migrate/SKILL.md +5 -3
  23. package/skills/blume-migrate/references/mintlify.md +5 -5
  24. package/skills/blume-migrate/references/monorepo.md +2 -1
  25. package/src/ai/agent-readability.ts +31 -1
  26. package/src/ai/api-catalog.ts +81 -0
  27. package/src/ai/link-headers.ts +52 -0
  28. package/src/ai/llms.ts +12 -1
  29. package/src/ai/markdown.ts +15 -2
  30. package/src/ai/mcp/discovery.ts +70 -15
  31. package/src/ai/mcp/server.ts +5 -4
  32. package/src/ai/skills.ts +193 -0
  33. package/src/ai/tar.ts +104 -0
  34. package/src/ai/web-bot-auth.ts +30 -0
  35. package/src/astro/generate.ts +116 -6
  36. package/src/astro/integration.ts +52 -14
  37. package/src/astro/templates.ts +176 -33
  38. package/src/audit/catalog.ts +20 -0
  39. package/src/audit/checks/dns-aid.ts +190 -0
  40. package/src/audit/report.ts +5 -0
  41. package/src/audit/run.ts +2 -0
  42. package/src/cli/commands/build.ts +178 -9
  43. package/src/cli/init/scaffold.ts +1 -1
  44. package/src/components/islands/ask-ai.tsx +4 -1
  45. package/src/components/islands/webmcp.ts +203 -0
  46. package/src/components/layout/PageLayout.astro +2 -0
  47. package/src/components/layout/ReferenceLayout.astro +2 -0
  48. package/src/components/layout/RootLayout.astro +62 -10
  49. package/src/components/layout/Search.astro +2 -2
  50. package/src/components/layout/WebMcp.astro +49 -0
  51. package/src/core/config-input.ts +143 -11
  52. package/src/core/config.ts +17 -1
  53. package/src/core/content-assets.ts +199 -0
  54. package/src/core/data.ts +21 -5
  55. package/src/core/diagnostics.ts +6 -5
  56. package/src/core/i18n-ui.ts +19 -28
  57. package/src/core/project-graph.ts +6 -0
  58. package/src/core/schema.ts +224 -71
  59. package/src/core/sources/normalize.ts +5 -5
  60. package/src/deploy/headers.ts +45 -3
  61. package/src/deploy/vercel-negotiation.ts +233 -0
  62. package/src/markdown/mermaid.ts +7 -1
  63. package/src/markdown/table-wrap.ts +33 -1
  64. package/src/og/card.ts +91 -22
  65. package/src/og/derive.ts +200 -0
  66. package/src/og/index.ts +6 -1
  67. package/src/search/orama-index.ts +98 -4
  68. package/src/theme/entry.ts +34 -13
  69. package/src/theme/fonts.ts +183 -30
  70. package/dist/types/og/card.d.ts +0 -63
  71. package/dist/types/og/dimensions.d.ts +0 -12
package/dist/cli/index.js CHANGED
@@ -391,8 +391,8 @@ var diagnosticsFromIssues = (issues, options) => issues.map((issue) => {
391
391
  };
392
392
  });
393
393
  var diagnosticsFromZod = (error, options) => diagnosticsFromIssues(error.issues.map((issue) => ({
394
- message: "received" in issue ? `${issue.message} (received: ${JSON.stringify(issue.received)})` : issue.message,
395
- path: issue.path
394
+ message: issue.message,
395
+ path: issue.path.filter((segment) => typeof segment !== "symbol")
396
396
  })), options);
397
397
  var ESC = String.fromCodePoint(27);
398
398
  var COLORS = {
@@ -1180,6 +1180,22 @@ var CHECKS = [
1180
1180
  tier: "static",
1181
1181
  title: "Indexable page missing from llms.txt"
1182
1182
  },
1183
+ {
1184
+ category: "ai",
1185
+ fix: "Publish a ServiceMode SVCB or HTTPS record at `_index._agents.<host>` with your DNS provider so agents can discover the site via DNS (DNS-AID).",
1186
+ id: "BLUME_AUDIT_DNS_AID_MISSING",
1187
+ severity: "info",
1188
+ tier: "network",
1189
+ title: "No DNS-AID agent-discovery records"
1190
+ },
1191
+ {
1192
+ category: "ai",
1193
+ fix: "Enable DNSSEC for the zone so validating resolvers return authenticated DNS-AID answers; if your DNS provider doesn't support DNSSEC, the records still work unsigned.",
1194
+ id: "BLUME_AUDIT_DNS_AID_UNSIGNED",
1195
+ severity: "info",
1196
+ tier: "network",
1197
+ title: "DNS-AID records are not DNSSEC-authenticated"
1198
+ },
1183
1199
  {
1184
1200
  category: "structured-data",
1185
1201
  fix: "The JSON-LD block must be valid JSON.",
@@ -1376,6 +1392,9 @@ var formatReport = (result, root, options = {}) => {
1376
1392
  const shown = options.verbose ? group.findings : group.findings.slice(0, PREVIEW);
1377
1393
  for (const diagnostic of shown) {
1378
1394
  lines.push(findingLine(diagnostic, root));
1395
+ if (options.verbose) {
1396
+ lines.push(` ${COLORS2.dim}${diagnostic.message}${COLORS2.reset}`);
1397
+ }
1379
1398
  }
1380
1399
  const hidden = group.count - shown.length;
1381
1400
  if (hidden > 0) {
@@ -1831,6 +1850,104 @@ var contentChecks = {
1831
1850
  tier: "static"
1832
1851
  };
1833
1852
 
1853
+ // src/audit/checks/dns-aid.ts
1854
+ var RR_SVCB = 64;
1855
+ var RR_HTTPS = 65;
1856
+ var DOH_TIMEOUT_MS = 5000;
1857
+ var DOH_RESOLVERS = [
1858
+ "https://dns.google/resolve",
1859
+ "https://cloudflare-dns.com/dns-query"
1860
+ ];
1861
+ var resolvers = () => {
1862
+ const override = process.env.BLUME_DOH_URL;
1863
+ return override ? [override] : DOH_RESOLVERS;
1864
+ };
1865
+ var fetchDohJson = async (base, name, type, fetchFn) => {
1866
+ try {
1867
+ const response = await fetchFn(`${base}?name=${encodeURIComponent(name)}&type=${type}`, {
1868
+ headers: { accept: "application/dns-json" },
1869
+ signal: AbortSignal.timeout(DOH_TIMEOUT_MS)
1870
+ });
1871
+ if (!response.ok) {
1872
+ return null;
1873
+ }
1874
+ return await response.json();
1875
+ } catch {
1876
+ return null;
1877
+ }
1878
+ };
1879
+ var queryDoh = async (name, type, fetchFn) => {
1880
+ for (const base of resolvers()) {
1881
+ const response = await fetchDohJson(base, name, type, fetchFn);
1882
+ if (response) {
1883
+ return response;
1884
+ }
1885
+ }
1886
+ return null;
1887
+ };
1888
+ var lookupDnsAid = async (host, fetchFn = fetch) => {
1889
+ const name = `_index._agents.${host}`;
1890
+ let answered = false;
1891
+ let found = false;
1892
+ let authenticated = true;
1893
+ const responses = await Promise.all([RR_HTTPS, RR_SVCB].map((type) => queryDoh(name, type, fetchFn)));
1894
+ for (const response of responses) {
1895
+ if (!response) {
1896
+ continue;
1897
+ }
1898
+ answered = true;
1899
+ const records = (response.Answer ?? []).filter((answer) => answer.type === RR_SVCB || answer.type === RR_HTTPS);
1900
+ if (records.length > 0) {
1901
+ found = true;
1902
+ authenticated &&= response.AD === true;
1903
+ }
1904
+ }
1905
+ return answered ? { authenticated, found } : null;
1906
+ };
1907
+ var dnsAidFindings = (host, result) => {
1908
+ if (!result.found) {
1909
+ return [
1910
+ finding("BLUME_AUDIT_DNS_AID_MISSING", { url: "/" }, `No SVCB or HTTPS records at _index._agents.${host} — agents probing DNS for AI Discovery (DNS-AID) find nothing.`, `Publish a ServiceMode record with your DNS provider, e.g. \`_index._agents.${host}. 3600 IN HTTPS 1 ${host}. alpn=h2\`, and sign the zone with DNSSEC if the provider supports it.`)
1911
+ ];
1912
+ }
1913
+ if (!result.authenticated) {
1914
+ return [
1915
+ finding("BLUME_AUDIT_DNS_AID_UNSIGNED", { url: "/" }, `The DNS-AID records at _index._agents.${host} exist but are not DNSSEC-authenticated, so validating resolvers can't prove they're genuine.`)
1916
+ ];
1917
+ }
1918
+ return [];
1919
+ };
1920
+ var dnsAidHost = (site) => {
1921
+ if (!site) {
1922
+ return null;
1923
+ }
1924
+ let hostname;
1925
+ try {
1926
+ ({ hostname } = new URL(site));
1927
+ } catch {
1928
+ return null;
1929
+ }
1930
+ if (hostname === "localhost" || hostname.endsWith(".local") || !hostname.includes(".")) {
1931
+ return null;
1932
+ }
1933
+ return hostname;
1934
+ };
1935
+ var dnsAidChecks = {
1936
+ category: "ai",
1937
+ async run(context) {
1938
+ if (!context.origin) {
1939
+ return [];
1940
+ }
1941
+ const host = dnsAidHost(context.project.config.deployment.site);
1942
+ if (!host) {
1943
+ return [];
1944
+ }
1945
+ const result = await lookupDnsAid(host);
1946
+ return result ? dnsAidFindings(host, result) : [];
1947
+ },
1948
+ tier: "network"
1949
+ };
1950
+
1834
1951
  // src/audit/checks/duplicates.ts
1835
1952
  var isNonCanonical = (page, deployBase) => {
1836
1953
  if (!page.canonical) {
@@ -3000,31 +3117,6 @@ var askBackendRuntimeDep = (ask) => {
3000
3117
  return ASK_PRESETS[provider]?.runtimeDep ?? OPENAI_COMPATIBLE_DEP;
3001
3118
  };
3002
3119
 
3003
- // src/core/sources/watch.ts
3004
- var BLUME_IGNORE_DIRS = [
3005
- ".blume",
3006
- ".blume-verify",
3007
- ".cache",
3008
- ".git",
3009
- ".next",
3010
- ".turbo",
3011
- ".vercel",
3012
- "dist",
3013
- "node_modules"
3014
- ];
3015
- var baselineScanIgnore = () => BLUME_IGNORE_DIRS.map((dir) => `**/${dir}/**`);
3016
- var BLUME_WATCH_IGNORE_DIRS = BLUME_IGNORE_DIRS;
3017
- var excludeDirSegments = (patterns) => patterns.map((pattern) => /^(?<dir>[^*/]+)\/\*\*$/u.exec(pattern)?.groups?.dir).filter((dir) => dir !== undefined);
3018
- var ignoringWatchListener = (onChange, ignoreDirs = BLUME_WATCH_IGNORE_DIRS) => {
3019
- const ignore = new Set(ignoreDirs);
3020
- return (_event, filename) => {
3021
- if (typeof filename === "string" && filename.split(/[/\\]/u).some((segment) => ignore.has(segment))) {
3022
- return;
3023
- }
3024
- onChange();
3025
- };
3026
- };
3027
-
3028
3120
  // src/core/trim.ts
3029
3121
  var trimStart = (text, char) => {
3030
3122
  let start = 0;
@@ -3042,41 +3134,6 @@ var trimEnd = (text, char) => {
3042
3134
  };
3043
3135
  var trimChar = (text, char) => trimEnd(trimStart(text, char), char);
3044
3136
 
3045
- // src/deploy/redirects.ts
3046
- var applyBaseToAstroRedirects = (redirects, basePath, deployBase) => {
3047
- const base = normalizeBasePath(deployBase);
3048
- return basePath || base ? redirects.map((redirect) => ({
3049
- ...redirect,
3050
- from: withBasePath(basePath, redirect.from),
3051
- to: withComposedBasePath(base, basePath, redirect.to)
3052
- })) : redirects;
3053
- };
3054
- var applyBaseToPlatformRedirects = (redirects, basePath, deployBase) => {
3055
- const base = normalizeBasePath(deployBase);
3056
- return basePath || base ? redirects.map((redirect) => ({
3057
- ...redirect,
3058
- from: withComposedBasePath(base, basePath, redirect.from),
3059
- to: withComposedBasePath(base, basePath, redirect.to)
3060
- })) : redirects;
3061
- };
3062
- var buildNetlifyRedirects = (redirects) => `${redirects.map((redirect) => `${redirect.from} ${redirect.to} ${redirect.status}`).join(`
3063
- `)}
3064
- `;
3065
- var buildVercelConfig = (redirects) => `${JSON.stringify({
3066
- redirects: redirects.map((redirect) => ({
3067
- destination: redirect.to,
3068
- source: redirect.from,
3069
- statusCode: redirect.status
3070
- }))
3071
- }, null, 2)}
3072
- `;
3073
- var buildRedirectManifest = (redirects) => `${JSON.stringify(redirects.map((redirect) => ({
3074
- from: redirect.from,
3075
- status: redirect.status,
3076
- to: redirect.to
3077
- })), null, 2)}
3078
- `;
3079
-
3080
3137
  // src/openapi/references.ts
3081
3138
  var NON_SLUG = /[^a-z0-9]+/gu;
3082
3139
  var slugify = (text) => trimChar(text.toLowerCase().replace(NON_SLUG, "-"), "-");
@@ -3176,6 +3233,129 @@ var blumeReferences = (config) => {
3176
3233
  };
3177
3234
  var hasScalarReferences = (config) => resolveReferences(config).some((ref) => ref.renderer === "scalar");
3178
3235
 
3236
+ // src/ai/api-catalog.ts
3237
+ var API_CATALOG_PATH = "/.well-known/api-catalog";
3238
+ var API_CATALOG_TYPE = "application/linkset+json";
3239
+ var HTTP_URL = /^https?:\/\//u;
3240
+ var linksetEntries = (config) => {
3241
+ const site = config.deployment.site ?? null;
3242
+ const deployBase = normalizeBasePath(config.deployment.base);
3243
+ const abs = (path) => {
3244
+ const based = withBasePath(deployBase, path);
3245
+ return site ? `${trimEnd(site, "/")}${based}` : based;
3246
+ };
3247
+ const entries = [];
3248
+ for (const reference of resolveReferences(config)) {
3249
+ const docRoute = reference.renderer === "blume" ? withBasePath(reference.basePath, reference.route) : reference.route;
3250
+ const entry = {
3251
+ anchor: abs(docRoute),
3252
+ "service-doc": [{ href: abs(docRoute), type: "text/html" }]
3253
+ };
3254
+ if (HTTP_URL.test(reference.spec)) {
3255
+ entry["service-desc"] = [{ href: reference.spec }];
3256
+ }
3257
+ entries.push(entry);
3258
+ }
3259
+ if (config.ai.mcp.enabled) {
3260
+ entries.push({
3261
+ anchor: abs(config.ai.mcp.route),
3262
+ "service-desc": [
3263
+ { href: abs("/.well-known/mcp.json"), type: "application/json" }
3264
+ ],
3265
+ "service-doc": [{ href: abs("/"), type: "text/html" }]
3266
+ });
3267
+ }
3268
+ return entries;
3269
+ };
3270
+ var hasApiCatalog = (config) => linksetEntries(config).length > 0;
3271
+ var buildApiCatalog = (config) => {
3272
+ const linkset = linksetEntries(config);
3273
+ if (linkset.length === 0) {
3274
+ return null;
3275
+ }
3276
+ return `${JSON.stringify({ linkset }, null, 2)}
3277
+ `;
3278
+ };
3279
+
3280
+ // src/ai/link-headers.ts
3281
+ var buildHomeLinkHeader = (config, routePaths) => {
3282
+ const deployBase = normalizeBasePath(config.deployment.base);
3283
+ const links = [];
3284
+ if (hasApiCatalog(config)) {
3285
+ links.push(`<${deployBase}${API_CATALOG_PATH}>; rel="api-catalog"; type="application/linkset+json"`);
3286
+ }
3287
+ if (config.seo.agentReadability) {
3288
+ links.push(`<${deployBase}/agent-readability.json>; rel="describedby"; type="application/json"`);
3289
+ }
3290
+ if (config.ai.llmsTxt.enabled) {
3291
+ links.push(`<${deployBase}/llms.txt>; rel="describedby"; type="text/plain"`);
3292
+ }
3293
+ if (routePaths.includes("/")) {
3294
+ links.push(`<${deployBase}/index.md>; rel="alternate"; type="text/markdown"`);
3295
+ }
3296
+ return links.length > 0 ? links.join(", ") : null;
3297
+ };
3298
+
3299
+ // src/core/sources/watch.ts
3300
+ var BLUME_IGNORE_DIRS = [
3301
+ ".blume",
3302
+ ".blume-verify",
3303
+ ".cache",
3304
+ ".git",
3305
+ ".next",
3306
+ ".turbo",
3307
+ ".vercel",
3308
+ "dist",
3309
+ "node_modules"
3310
+ ];
3311
+ var baselineScanIgnore = () => BLUME_IGNORE_DIRS.map((dir) => `**/${dir}/**`);
3312
+ var BLUME_WATCH_IGNORE_DIRS = BLUME_IGNORE_DIRS;
3313
+ var excludeDirSegments = (patterns) => patterns.map((pattern) => /^(?<dir>[^*/]+)\/\*\*$/u.exec(pattern)?.groups?.dir).filter((dir) => dir !== undefined);
3314
+ var ignoringWatchListener = (onChange, ignoreDirs = BLUME_WATCH_IGNORE_DIRS) => {
3315
+ const ignore = new Set(ignoreDirs);
3316
+ return (_event, filename) => {
3317
+ if (typeof filename === "string" && filename.split(/[/\\]/u).some((segment) => ignore.has(segment))) {
3318
+ return;
3319
+ }
3320
+ onChange();
3321
+ };
3322
+ };
3323
+
3324
+ // src/deploy/redirects.ts
3325
+ var applyBaseToAstroRedirects = (redirects, basePath, deployBase) => {
3326
+ const base = normalizeBasePath(deployBase);
3327
+ return basePath || base ? redirects.map((redirect) => ({
3328
+ ...redirect,
3329
+ from: withBasePath(basePath, redirect.from),
3330
+ to: withComposedBasePath(base, basePath, redirect.to)
3331
+ })) : redirects;
3332
+ };
3333
+ var applyBaseToPlatformRedirects = (redirects, basePath, deployBase) => {
3334
+ const base = normalizeBasePath(deployBase);
3335
+ return basePath || base ? redirects.map((redirect) => ({
3336
+ ...redirect,
3337
+ from: withComposedBasePath(base, basePath, redirect.from),
3338
+ to: withComposedBasePath(base, basePath, redirect.to)
3339
+ })) : redirects;
3340
+ };
3341
+ var buildNetlifyRedirects = (redirects) => `${redirects.map((redirect) => `${redirect.from} ${redirect.to} ${redirect.status}`).join(`
3342
+ `)}
3343
+ `;
3344
+ var buildVercelConfig = (redirects) => `${JSON.stringify({
3345
+ redirects: redirects.map((redirect) => ({
3346
+ destination: redirect.to,
3347
+ source: redirect.from,
3348
+ statusCode: redirect.status
3349
+ }))
3350
+ }, null, 2)}
3351
+ `;
3352
+ var buildRedirectManifest = (redirects) => `${JSON.stringify(redirects.map((redirect) => ({
3353
+ from: redirect.from,
3354
+ status: redirect.status,
3355
+ to: redirect.to
3356
+ })), null, 2)}
3357
+ `;
3358
+
3179
3359
  // src/search/providers.ts
3180
3360
  var SEARCH_PROVIDERS = {
3181
3361
  algolia: {
@@ -3341,30 +3521,94 @@ var GOOGLE_FONTS = {
3341
3521
  };
3342
3522
  var FONT_SLUGS = Object.keys(GOOGLE_FONTS);
3343
3523
  var isFontSlug = (value) => Object.hasOwn(GOOGLE_FONTS, value);
3524
+ var DEFAULT_REMOTE_WEIGHTS = [400, 500, 600, 700];
3525
+ var slugifyFontName = (name) => name.toLowerCase().replaceAll(/[^a-z0-9]+/gu, "-").replaceAll(/^-+|-+$/gu, "");
3344
3526
  var fontVar = (slug) => `--blume-ff-${slug}`;
3345
3527
  var SLOTS = ["display", "body", "mono"];
3346
- var buildFontEntries = (fonts) => {
3347
- if (!fonts) {
3348
- return [];
3349
- }
3350
- const slugs = SLOTS.map((slot) => fonts[slot]).filter((slug) => typeof slug === "string" && isFontSlug(slug));
3351
- return [...new Set(slugs)].map((slug) => {
3352
- const def = GOOGLE_FONTS[slug];
3528
+ var slotCategory = (slot) => slot === "mono" ? "mono" : "sans";
3529
+ var resolveFontValue = (slot, value) => {
3530
+ if (typeof value === "string") {
3531
+ if (!isFontSlug(value)) {
3532
+ return null;
3533
+ }
3534
+ const def = GOOGLE_FONTS[value];
3353
3535
  return {
3354
- cssVariable: fontVar(slug),
3536
+ cssVariable: fontVar(value),
3355
3537
  fallbacks: FALLBACKS[def.category],
3538
+ kind: "remote",
3356
3539
  name: def.family,
3540
+ provider: "google",
3357
3541
  weights: def.weights
3358
3542
  };
3359
- });
3543
+ }
3544
+ const slug = slugifyFontName(value.name);
3545
+ const fallbacks = FALLBACKS[value.fallback ?? slotCategory(slot)];
3546
+ if ("variants" in value) {
3547
+ return {
3548
+ cssVariable: fontVar(slug),
3549
+ fallbacks,
3550
+ kind: "local",
3551
+ name: value.name,
3552
+ variants: value.variants
3553
+ };
3554
+ }
3555
+ return {
3556
+ cssVariable: fontVar(slug),
3557
+ fallbacks,
3558
+ kind: "remote",
3559
+ name: value.name,
3560
+ provider: value.provider ?? "google",
3561
+ weights: value.weights ?? DEFAULT_REMOTE_WEIGHTS
3562
+ };
3563
+ };
3564
+ var mergeFontEntries = (current, next) => {
3565
+ if (current.kind === "remote" && next.kind === "remote" && current.provider === next.provider && current.name === next.name) {
3566
+ return {
3567
+ ...current,
3568
+ weights: [...new Set([...current.weights, ...next.weights])]
3569
+ };
3570
+ }
3571
+ if (current.kind === "local" && next.kind === "local" && current.name === next.name && JSON.stringify(current.variants) === JSON.stringify(next.variants)) {
3572
+ return current;
3573
+ }
3574
+ throw new Error(`theme.fonts: "${next.name}" conflicts with "${current.name}" — both resolve to the CSS variable "${current.cssVariable}" with different definitions. Rename one family or align their definitions.`);
3575
+ };
3576
+ var buildFontEntries = (fonts) => {
3577
+ if (!fonts) {
3578
+ return [];
3579
+ }
3580
+ const entries = new Map;
3581
+ for (const slot of SLOTS) {
3582
+ const value = fonts[slot];
3583
+ if (value === undefined) {
3584
+ continue;
3585
+ }
3586
+ const entry = resolveFontValue(slot, value);
3587
+ if (!entry) {
3588
+ continue;
3589
+ }
3590
+ const current = entries.get(entry.cssVariable);
3591
+ entries.set(entry.cssVariable, current ? mergeFontEntries(current, entry) : entry);
3592
+ }
3593
+ return [...entries.values()];
3594
+ };
3595
+ var slotSlug = (value) => {
3596
+ if (typeof value === "string") {
3597
+ return isFontSlug(value) ? value : null;
3598
+ }
3599
+ return slugifyFontName(value.name);
3360
3600
  };
3361
3601
  var buildFontsCss = (fonts) => {
3362
3602
  if (!fonts) {
3363
3603
  return "";
3364
3604
  }
3365
3605
  const lines = SLOTS.flatMap((slot) => {
3366
- const slug = fonts[slot];
3367
- return slug && isFontSlug(slug) ? [` --blume-font-${slot}-src: var(${fontVar(slug)});`] : [];
3606
+ const value = fonts[slot];
3607
+ if (value === undefined) {
3608
+ return [];
3609
+ }
3610
+ const slug = slotSlug(value);
3611
+ return slug ? [` --blume-font-${slot}-src: var(${fontVar(slug)});`] : [];
3368
3612
  });
3369
3613
  return lines.length > 0 ? `/* Generated by Blume from theme.fonts. */
3370
3614
  :root {
@@ -3533,6 +3777,8 @@ const blumeConfig = await loadBlumeConfig(resolve(dirname(fileURLToPath(import.m
3533
3777
  userIntegrationSpread: ", ...(blumeConfig?.integrations ?? [])"
3534
3778
  };
3535
3779
  };
3780
+ var renderImageOption = (config) => config.image.domains.length > 0 || config.image.remotePatterns.length > 0 ? `
3781
+ image: ${JSON.stringify(config.image)},` : "";
3536
3782
  var astroConfigTemplate = (options) => {
3537
3783
  const { context, config, needsReact, pages, dataPath, themePath } = options;
3538
3784
  const {
@@ -3568,6 +3814,7 @@ var astroConfigTemplate = (options) => {
3568
3814
  site: ${JSON.stringify(deployment.site)},` : "";
3569
3815
  const baseOption = deployment.base ? `
3570
3816
  base: ${JSON.stringify(deployment.base)},` : "";
3817
+ const imageOption = renderImageOption(config);
3571
3818
  const i18nOption = config.i18n ? `
3572
3819
  i18n: ${JSON.stringify({
3573
3820
  defaultLocale: config.i18n.defaultLocale,
@@ -3584,7 +3831,13 @@ var astroConfigTemplate = (options) => {
3584
3831
  ])))},` : "";
3585
3832
  const fontEntries = buildFontEntries(config.theme.fonts);
3586
3833
  const fontsOption = fontEntries.length ? `
3587
- fonts: [${fontEntries.map((font) => `{ provider: fontProviders.google(), name: ${JSON.stringify(font.name)}, cssVariable: ${JSON.stringify(font.cssVariable)}, weights: ${JSON.stringify(font.weights)}, fallbacks: ${JSON.stringify(font.fallbacks)} }`).join(", ")}],` : "";
3834
+ fonts: [${fontEntries.map((font) => font.kind === "local" ? `{ provider: fontProviders.local(), name: ${JSON.stringify(font.name)}, cssVariable: ${JSON.stringify(font.cssVariable)}, fallbacks: ${JSON.stringify(font.fallbacks)}, options: { variants: ${JSON.stringify(font.variants.map((variant) => ({
3835
+ ...variant.weight === undefined ? {} : { weight: variant.weight },
3836
+ ...variant.style === undefined ? {} : { style: variant.style },
3837
+ src: [
3838
+ isAbsolute(variant.src) ? variant.src : join8(context.root, variant.src)
3839
+ ]
3840
+ })))} } }` : `{ provider: fontProviders.${font.provider}(), name: ${JSON.stringify(font.name)}, cssVariable: ${JSON.stringify(font.cssVariable)}, weights: ${JSON.stringify(font.weights)}, fallbacks: ${JSON.stringify(font.fallbacks)} }`).join(", ")}],` : "";
3588
3841
  const defineConfigImport = astroConfigImportLine({
3589
3842
  hasFonts: fontEntries.length > 0,
3590
3843
  hasSession: sessionOption.length > 0
@@ -3622,7 +3875,12 @@ var astroConfigTemplate = (options) => {
3622
3875
  if (needsSvelte) {
3623
3876
  integrations.push("svelte()");
3624
3877
  }
3625
- integrations.push(`blumeIntegration(${JSON.stringify({ base: deployment.base, contentRoutes, pages })})`);
3878
+ integrations.push(`blumeIntegration(${JSON.stringify({
3879
+ base: deployment.base,
3880
+ contentRoutes,
3881
+ homeLinkHeader: buildHomeLinkHeader(config, contentRoutes) ?? undefined,
3882
+ pages
3883
+ })})`);
3626
3884
  const watchOption = devWatchOption(context.outDir, options.contentWatchesRuntimeDir);
3627
3885
  const {
3628
3886
  configSourceMarker,
@@ -3641,7 +3899,7 @@ ${userConfigSetup}export default defineConfig({
3641
3899
  srcDir: ${JSON.stringify(`${context.outDir}/src`)},
3642
3900
  outDir: ${JSON.stringify(astroOutDir(context))},
3643
3901
  publicDir: ${JSON.stringify(`${context.root}/public`)},
3644
- output: ${JSON.stringify(deployment.output)},${adapterOption}${sessionOption}${siteOption}${baseOption}${redirectsOption}${i18nOption}${fontsOption}
3902
+ output: ${JSON.stringify(deployment.output)},${adapterOption}${sessionOption}${siteOption}${baseOption}${imageOption}${redirectsOption}${i18nOption}${fontsOption}
3645
3903
  integrations: [${integrations.join(", ")}${userIntegrationSpread}],
3646
3904
  markdown: {
3647
3905
  processor: blumeMarkdownProcessor(${JSON.stringify({
@@ -3832,17 +4090,17 @@ const ground = createAskContext(askData);
3832
4090
  const onError = ` onError({ error }) {
3833
4091
  console.error("Ask AI provider error:", error);
3834
4092
  },`;
3835
- const stream = grounded ? ` const system =
4093
+ const stream = grounded ? ` const instructions =
3836
4094
  (await ground(messages, body.page)) ??
3837
4095
  "You are a helpful documentation assistant. Answer using the project's documentation.";
3838
4096
  const result = streamText({
3839
4097
  model: ${modelExpr},
3840
- system,
4098
+ instructions,
3841
4099
  messages,
3842
4100
  ${onError}
3843
4101
  });` : ` const result = streamText({
3844
4102
  model: ${modelExpr},
3845
- system:
4103
+ instructions:
3846
4104
  "You are a helpful documentation assistant. Answer using the project's documentation.",
3847
4105
  messages,
3848
4106
  ${onError}
@@ -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
  }
@@ -11740,11 +12155,116 @@ var buildAgentReadability = (project) => {
11740
12155
  if (config.github) {
11741
12156
  manifest.repository = `https://github.com/${config.github.owner}/${config.github.repo}`;
11742
12157
  }
11743
- return manifest;
12158
+ return manifest;
12159
+ };
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;
11744
12264
  };
11745
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
@@ -12797,7 +13623,7 @@ var buildAskData = async (project) => {
12797
13623
  };
12798
13624
 
12799
13625
  // src/ai/markdown.ts
12800
- import { readFile as readFile10 } from "node:fs/promises";
13626
+ import { readFile as readFile12 } from "node:fs/promises";
12801
13627
  var agentMarkdown = (entry) => entry.md ?? entry.mdx;
12802
13628
  var buildRawMarkdown = async (project) => {
12803
13629
  const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));
@@ -12810,10 +13636,19 @@ var buildRawMarkdown = async (project) => {
12810
13636
  if (page) {
12811
13637
  return await readEntryText(project, page);
12812
13638
  }
12813
- return route.sourcePath ? await readFile10(route.sourcePath, "utf-8") : "";
13639
+ return route.sourcePath ? await readFile12(route.sourcePath, "utf-8") : "";
12814
13640
  };
12815
13641
  const entries = await Promise.all(project.manifest.routes.map(async (route) => {
12816
- 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);
12817
13652
  const md = downlevelComponents(source, components, frontmatter_default(source).data);
12818
13653
  const entry = md === source ? { mdx: source } : { md, mdx: source };
12819
13654
  return [route.path, entry];
@@ -12912,19 +13747,42 @@ var buildMcpDiscovery = (input) => ({
12912
13747
  }
12913
13748
  ]
12914
13749
  });
12915
- var buildMcpServerCard = (input) => ({
12916
- description: `Model Context Protocol server for the ${input.name} documentation.`,
12917
- name: input.name,
12918
- tools: MCP_TOOLS.map((tool) => ({
12919
- annotations: tool.annotations,
12920
- description: tool.description,
12921
- name: tool.name,
12922
- title: tool.title
12923
- })),
12924
- transport: "streamable-http",
12925
- url: serverUrl(input),
12926
- version: input.version
12927
- });
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
+ };
12928
13786
 
12929
13787
  // src/core/builtin-tags.ts
12930
13788
  var BUILTIN_MDX_TAGS = new Set([
@@ -12991,8 +13849,8 @@ var validateUsedComponents = (pages, extraTags, registryNames) => {
12991
13849
  };
12992
13850
 
12993
13851
  // src/core/component-overrides.ts
12994
- import { existsSync as existsSync11 } from "node:fs";
12995
- import { dirname as dirname7, extname as extname6, isAbsolute as isAbsolute6, resolve as resolve5 } from "pathe";
13852
+ import { existsSync as existsSync12 } from "node:fs";
13853
+ import { dirname as dirname8, extname as extname7, isAbsolute as isAbsolute6, resolve as resolve6 } from "pathe";
12996
13854
  import ts from "typescript";
12997
13855
  var GROUPS = ["mdx", "layout", "islands"];
12998
13856
  var GROUP_SET = new Set(GROUPS);
@@ -13084,24 +13942,24 @@ var findDefaultExportObject = (sourceFile) => {
13084
13942
  var probeExtension = (base) => {
13085
13943
  for (const extension of COMPONENT_EXTS) {
13086
13944
  const candidate = `${base}.${extension}`;
13087
- if (existsSync11(candidate)) {
13945
+ if (existsSync12(candidate)) {
13088
13946
  return candidate;
13089
13947
  }
13090
13948
  }
13091
13949
  return null;
13092
13950
  };
13093
13951
  var toImport = (specifier, imported, dir) => {
13094
- const relative12 = specifier.startsWith(".") || isAbsolute6(specifier);
13952
+ const relative13 = specifier.startsWith(".") || isAbsolute6(specifier);
13095
13953
  let path = specifier;
13096
- let extension = extname6(specifier).slice(1).toLowerCase();
13097
- if (relative12) {
13098
- 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);
13099
13957
  if (extension) {
13100
13958
  path = absolute;
13101
13959
  } else {
13102
13960
  const probed = probeExtension(absolute);
13103
13961
  path = probed ?? absolute;
13104
- extension = probed ? extname6(probed).slice(1).toLowerCase() : "";
13962
+ extension = probed ? extname7(probed).slice(1).toLowerCase() : "";
13105
13963
  }
13106
13964
  }
13107
13965
  return {
@@ -13250,7 +14108,7 @@ var analyzeComponentOverrides = (source, filePath) => {
13250
14108
  return result;
13251
14109
  }
13252
14110
  const imports = collectImports(sourceFile);
13253
- const dir = dirname7(filePath);
14111
+ const dir = dirname8(filePath);
13254
14112
  for (const property of object.properties) {
13255
14113
  collectGroupOverrides(property, imports, dir, result);
13256
14114
  }
@@ -13258,16 +14116,16 @@ var analyzeComponentOverrides = (source, filePath) => {
13258
14116
  };
13259
14117
 
13260
14118
  // src/core/tsconfig-aliases.ts
13261
- import { existsSync as existsSync12, readFileSync as readFileSync6, statSync } from "node:fs";
14119
+ import { existsSync as existsSync13, readFileSync as readFileSync6, statSync } from "node:fs";
13262
14120
  import { createRequire as createRequire3 } from "node:module";
13263
14121
  import { pathToFileURL as pathToFileURL2 } from "node:url";
13264
- import { dirname as dirname8, isAbsolute as isAbsolute7, join as join21, resolve as resolve6 } from "pathe";
13265
- var scanJsonChar = (text, index, inString) => {
13266
- const char = text[index];
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];
13267
14125
  if (inString) {
13268
14126
  if (char === "\\") {
13269
14127
  return {
13270
- append: char + (text[index + 1] ?? ""),
14128
+ append: char + (text2[index + 1] ?? ""),
13271
14129
  inString: true,
13272
14130
  next: index + 2
13273
14131
  };
@@ -13277,35 +14135,35 @@ var scanJsonChar = (text, index, inString) => {
13277
14135
  if (char === '"') {
13278
14136
  return { append: char, inString: true, next: index + 1 };
13279
14137
  }
13280
- if (char === "/" && text[index + 1] === "/") {
13281
- const newline = text.indexOf(`
14138
+ if (char === "/" && text2[index + 1] === "/") {
14139
+ const newline = text2.indexOf(`
13282
14140
  `, index + 2);
13283
14141
  return {
13284
14142
  append: "",
13285
14143
  inString: false,
13286
- next: newline === -1 ? text.length : newline
14144
+ next: newline === -1 ? text2.length : newline
13287
14145
  };
13288
14146
  }
13289
- if (char === "/" && text[index + 1] === "*") {
13290
- const end = text.indexOf("*/", index + 2);
14147
+ if (char === "/" && text2[index + 1] === "*") {
14148
+ const end = text2.indexOf("*/", index + 2);
13291
14149
  return {
13292
14150
  append: "",
13293
14151
  inString: false,
13294
- next: end === -1 ? text.length : end + 2
14152
+ next: end === -1 ? text2.length : end + 2
13295
14153
  };
13296
14154
  }
13297
14155
  return { append: char ?? "", inString: false, next: index + 1 };
13298
14156
  };
13299
- var stripJsonComments = (text) => {
14157
+ var stripJsonComments = (text2) => {
13300
14158
  let out = "";
13301
14159
  let inString = false;
13302
14160
  let index = 0;
13303
- while (index < text.length) {
14161
+ while (index < text2.length) {
13304
14162
  const {
13305
14163
  append,
13306
14164
  inString: nextInString,
13307
14165
  next
13308
- } = scanJsonChar(text, index, inString);
14166
+ } = scanJsonChar(text2, index, inString);
13309
14167
  out += append;
13310
14168
  inString = nextInString;
13311
14169
  index = next;
@@ -13313,9 +14171,9 @@ var stripJsonComments = (text) => {
13313
14171
  return out;
13314
14172
  };
13315
14173
  var TRAILING_COMMA = /,(?<rest>\s*[}\]])/gu;
13316
- var parseJsonc = (text) => {
14174
+ var parseJsonc = (text2) => {
13317
14175
  try {
13318
- const cleaned = stripJsonComments(text).replaceAll(TRAILING_COMMA, "$<rest>");
14176
+ const cleaned = stripJsonComments(text2).replaceAll(TRAILING_COMMA, "$<rest>");
13319
14177
  const value = JSON.parse(cleaned);
13320
14178
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
13321
14179
  } catch {
@@ -13331,15 +14189,15 @@ var isFile = (path) => {
13331
14189
  };
13332
14190
  var resolveExtends = (spec, fromDir) => {
13333
14191
  if (spec.startsWith(".") || isAbsolute7(spec)) {
13334
- const candidates = spec.endsWith(".json") ? [resolve6(fromDir, spec)] : [
13335
- resolve6(fromDir, `${spec}.json`),
13336
- resolve6(fromDir, spec, "tsconfig.json"),
13337
- resolve6(fromDir, spec)
14192
+ const candidates = spec.endsWith(".json") ? [resolve7(fromDir, spec)] : [
14193
+ resolve7(fromDir, `${spec}.json`),
14194
+ resolve7(fromDir, spec, "tsconfig.json"),
14195
+ resolve7(fromDir, spec)
13338
14196
  ];
13339
14197
  return candidates.find(isFile) ?? null;
13340
14198
  }
13341
14199
  try {
13342
- const requireFromDir = createRequire3(pathToFileURL2(join21(fromDir, "_.js")).href);
14200
+ const requireFromDir = createRequire3(pathToFileURL2(join22(fromDir, "_.js")).href);
13343
14201
  for (const sub of [`${spec}/tsconfig.json`, spec]) {
13344
14202
  try {
13345
14203
  return requireFromDir.resolve(sub);
@@ -13349,7 +14207,7 @@ var resolveExtends = (spec, fromDir) => {
13349
14207
  return null;
13350
14208
  };
13351
14209
  var loadPaths = (file, seen) => {
13352
- if (seen.has(file) || !existsSync12(file)) {
14210
+ if (seen.has(file) || !existsSync13(file)) {
13353
14211
  return null;
13354
14212
  }
13355
14213
  seen.add(file);
@@ -13361,7 +14219,7 @@ var loadPaths = (file, seen) => {
13361
14219
  if (options.paths && typeof options.paths === "object") {
13362
14220
  const baseUrl = typeof options.baseUrl === "string" ? options.baseUrl : ".";
13363
14221
  return {
13364
- baseDir: resolve6(dirname8(file), baseUrl),
14222
+ baseDir: resolve7(dirname9(file), baseUrl),
13365
14223
  paths: options.paths
13366
14224
  };
13367
14225
  }
@@ -13370,7 +14228,7 @@ var loadPaths = (file, seen) => {
13370
14228
  if (typeof base !== "string") {
13371
14229
  continue;
13372
14230
  }
13373
- const resolved = resolveExtends(base, dirname8(file));
14231
+ const resolved = resolveExtends(base, dirname9(file));
13374
14232
  const found = resolved ? loadPaths(resolved, seen) : null;
13375
14233
  if (found) {
13376
14234
  return found;
@@ -13388,10 +14246,10 @@ var toAlias = (key, value, baseDir) => {
13388
14246
  if (find === "" || find === "*") {
13389
14247
  return null;
13390
14248
  }
13391
- return { find, replacement: resolve6(baseDir, target) };
14249
+ return { find, replacement: resolve7(baseDir, target) };
13392
14250
  };
13393
14251
  var resolveTsconfigAliases = (root) => {
13394
- 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));
13395
14253
  if (!entry) {
13396
14254
  return {};
13397
14255
  }
@@ -13409,24 +14267,126 @@ var resolveTsconfigAliases = (root) => {
13409
14267
  return aliases;
13410
14268
  };
13411
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
+
13412
14372
  // src/og/logo.ts
13413
- import { existsSync as existsSync13, readFileSync as readFileSync7 } from "node:fs";
13414
- import { join as join22 } from "pathe";
14373
+ import { existsSync as existsSync15, readFileSync as readFileSync7 } from "node:fs";
14374
+ import { join as join24 } from "pathe";
13415
14375
  var resolveOgLogo = (project, source) => {
13416
14376
  if (!source?.toLowerCase().endsWith(".svg")) {
13417
14377
  return;
13418
14378
  }
13419
- const relative12 = source.replace(/^\//u, "");
14379
+ const relative13 = source.replace(/^\//u, "");
13420
14380
  const file = [
13421
- join22(project.context.root, "public", relative12),
13422
- join22(project.context.root, relative12)
13423
- ].find((path) => existsSync13(path));
14381
+ join24(project.context.root, "public", relative13),
14382
+ join24(project.context.root, relative13)
14383
+ ].find((path) => existsSync15(path));
13424
14384
  return file ? readFileSync7(file, "utf-8") : undefined;
13425
14385
  };
13426
14386
 
13427
14387
  // src/openapi/scalar.ts
13428
- import { readFile as readFile11 } from "node:fs/promises";
13429
- 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";
13430
14390
 
13431
14391
  // src/theme/palette.ts
13432
14392
  var FALLBACK_ACCENT = "oklch(0.62 0.16 250)";
@@ -13532,9 +14492,9 @@ var specConfiguration = async (spec, root) => {
13532
14492
  if (URL_SPEC2.test(spec)) {
13533
14493
  return { config: { url: spec } };
13534
14494
  }
13535
- const absolute = isAbsolute8(spec) ? spec : join23(root, spec);
14495
+ const absolute = isAbsolute9(spec) ? spec : join25(root, spec);
13536
14496
  try {
13537
- return { config: { content: await readFile11(absolute, "utf-8") } };
14497
+ return { config: { content: await readFile13(absolute, "utf-8") } };
13538
14498
  } catch {
13539
14499
  return {
13540
14500
  config: { url: spec },
@@ -14021,19 +14981,29 @@ blume-diff {
14021
14981
  even though its chrome wrapper is not-prose. */
14022
14982
  .prose :where(pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code) {
14023
14983
  display: block;
14024
- overflow-x: auto;
14025
- padding: 0 1.25rem;
14026
- /* The scroller is only as tall as the code, so an overlay scrollbar would
14027
- draw on top of the last line; hide it (wheel/trackpad/keyboard scrolling
14028
- still works). */
14029
- scrollbar-width: none;
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;
14030
14998
  }
14031
14999
 
14032
- .prose
14033
- :where(
14034
- pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code
14035
- )::-webkit-scrollbar {
14036
- 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;
14037
15007
  }
14038
15008
 
14039
15009
  /* Word wrap (markdown.code.wrap): long lines wrap instead of scrolling. The
@@ -14101,6 +15071,10 @@ pre.blume-source {
14101
15071
 
14102
15072
  pre.blume-source > code {
14103
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;
14104
15078
  min-height: 0;
14105
15079
  overflow: auto;
14106
15080
  }
@@ -14282,6 +15256,13 @@ pre:has(.line.focused):hover .line:not(.focused) {
14282
15256
  display: none !important;
14283
15257
  }
14284
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
+
14285
15266
  #blume-content {
14286
15267
  padding: 0 !important;
14287
15268
  }
@@ -14525,13 +15506,13 @@ export const layoutOverrides = { ...(overrides.layout ?? {})${layoutEntries.leng
14525
15506
  };
14526
15507
 
14527
15508
  // src/astro/examples.ts
14528
- import { readFile as readFile13 } from "node:fs/promises";
14529
- 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";
14530
15511
  import { glob as glob6 } from "tinyglobby";
14531
15512
 
14532
15513
  // src/astro/islands.ts
14533
- import { readFile as readFile12 } from "node:fs/promises";
14534
- 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";
14535
15516
  import { glob as glob5 } from "tinyglobby";
14536
15517
  var DEFAULT_CLIENT = "visible";
14537
15518
  var VALID_MODES = new Set([
@@ -14560,14 +15541,14 @@ var readClientMode = (source, file, warnings) => {
14560
15541
  return mode;
14561
15542
  };
14562
15543
  var discoverIslands = async (root) => {
14563
- const dir = join24(root, "islands");
15544
+ const dir = join26(root, "islands");
14564
15545
  const matches2 = await glob5(["**/*.{jsx,svelte,tsx,vue}"], {
14565
15546
  absolute: true,
14566
15547
  cwd: dir,
14567
15548
  onlyFiles: true
14568
15549
  });
14569
15550
  const files = matches2.toSorted();
14570
- 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")));
14571
15552
  const islands = [];
14572
15553
  const warnings = [];
14573
15554
  const seen = new Map;
@@ -14623,14 +15604,14 @@ var splitGlobBase = (pattern) => {
14623
15604
  };
14624
15605
  var discoverExamples = async (root, pattern = "examples") => {
14625
15606
  const { base, rest } = GLOB_MAGIC.test(pattern) ? splitGlobBase(pattern) : { base: pattern, rest: DEFAULT_EXAMPLE_GLOB };
14626
- const dir = join25(root, base);
15607
+ const dir = join27(root, base);
14627
15608
  const matches2 = await glob6([rest], {
14628
15609
  absolute: true,
14629
15610
  cwd: dir,
14630
15611
  onlyFiles: true
14631
15612
  });
14632
15613
  const files = matches2.toSorted();
14633
- 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")));
14634
15615
  const examples = [];
14635
15616
  const warnings = [];
14636
15617
  const seen = new Map;
@@ -14640,7 +15621,7 @@ var discoverExamples = async (root, pattern = "examples") => {
14640
15621
  if (!(ext && framework)) {
14641
15622
  return;
14642
15623
  }
14643
- const path = relative12(dir, file).slice(0, -(ext.length + 1));
15624
+ const path = relative13(dir, file).slice(0, -(ext.length + 1));
14644
15625
  const existing = seen.get(path);
14645
15626
  if (existing) {
14646
15627
  warnings.push(`Two examples both resolve to "${path}" ("${existing}" and "${file}"); ignoring the second. Give them distinct paths.`);
@@ -14667,10 +15648,10 @@ var exampleMarkdownLookup = (examples) => Object.fromEntries(examples.map((examp
14667
15648
  ]));
14668
15649
 
14669
15650
  // src/astro/generate.ts
14670
- var BLUME_SRC = join26(packageRoot(), "src");
15651
+ var BLUME_SRC = join28(packageRoot(), "src");
14671
15652
  var canResolveFrom = (fromDir, spec) => {
14672
15653
  try {
14673
- createRequire5(pathToFileURL3(join26(fromDir, "_.js")).href).resolve(spec);
15654
+ createRequire5(pathToFileURL3(join28(fromDir, "_.js")).href).resolve(spec);
14674
15655
  return true;
14675
15656
  } catch {
14676
15657
  return false;
@@ -14681,7 +15662,7 @@ var resolveReactCompiler = (config, needsReact, pkgDir = packageRoot()) => {
14681
15662
  return null;
14682
15663
  }
14683
15664
  try {
14684
- 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");
14685
15666
  } catch {
14686
15667
  return null;
14687
15668
  }
@@ -14691,7 +15672,7 @@ var reactCompilerWarnings = (config, needsReact, compilerPath) => needsReact &&
14691
15672
  ] : [];
14692
15673
  var resolveAstroPackageJson = (modulesDir) => {
14693
15674
  try {
14694
- return realpathSync(join26(modulesDir, "astro", "package.json"));
15675
+ return realpathSync(join28(modulesDir, "astro", "package.json"));
14695
15676
  } catch {
14696
15677
  return null;
14697
15678
  }
@@ -14699,12 +15680,12 @@ var resolveAstroPackageJson = (modulesDir) => {
14699
15680
  var resolvedAstroHit = (fromDir) => {
14700
15681
  let dir = normalize3(fromDir);
14701
15682
  while (true) {
14702
- const modulesDir = join26(dir, "node_modules");
15683
+ const modulesDir = join28(dir, "node_modules");
14703
15684
  const pkg = resolveAstroPackageJson(modulesDir);
14704
15685
  if (pkg) {
14705
15686
  return { modulesDir, pkg };
14706
15687
  }
14707
- const parent = dirname9(dir);
15688
+ const parent = dirname10(dir);
14708
15689
  if (parent === dir) {
14709
15690
  return null;
14710
15691
  }
@@ -14719,10 +15700,10 @@ var sameRealDir = (a, b) => {
14719
15700
  }
14720
15701
  };
14721
15702
  var depsCandidates = (pkgDir) => [
14722
- join26(pkgDir, "node_modules"),
14723
- dirname9(pkgDir)
15703
+ join28(pkgDir, "node_modules"),
15704
+ dirname10(pkgDir)
14724
15705
  ];
14725
- 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;
14726
15707
  var linkDepsJunction = async (link, depsDir) => {
14727
15708
  let existing;
14728
15709
  try {
@@ -14735,13 +15716,13 @@ var linkDepsJunction = async (link, depsDir) => {
14735
15716
  return;
14736
15717
  }
14737
15718
  try {
14738
- if (resolve7(dirname9(link), await readlink(link)) === resolve7(depsDir)) {
15719
+ if (resolve8(dirname10(link), await readlink(link)) === resolve8(depsDir)) {
14739
15720
  return;
14740
15721
  }
14741
15722
  } catch {}
14742
15723
  await rm2(link, { force: true });
14743
15724
  }
14744
- await mkdir6(dirname9(link), { recursive: true });
15725
+ await mkdir6(dirname10(link), { recursive: true });
14745
15726
  await symlink(depsDir, link, "junction");
14746
15727
  };
14747
15728
  var readPkgVersion = (pkgJsonPath) => {
@@ -14774,7 +15755,7 @@ var dropStaleDepsLink = async (link, pkgDir) => {
14774
15755
  let linkedBlume;
14775
15756
  let runningBlume;
14776
15757
  try {
14777
- linkedBlume = await realpath(join26(link, "blume"));
15758
+ linkedBlume = await realpath(join28(link, "blume"));
14778
15759
  runningBlume = await realpath(pkgDir);
14779
15760
  } catch {
14780
15761
  return;
@@ -14790,7 +15771,7 @@ var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
14790
15771
  }
14791
15772
  const mdxDir = candidateHolding(pkgDir, "@astrojs", "mdx");
14792
15773
  const blumeAstro = resolveAstroPackageJson(astroDir);
14793
- await dropStaleDepsLink(join26(outDir, "node_modules"), pkgDir);
15774
+ await dropStaleDepsLink(join28(outDir, "node_modules"), pkgDir);
14794
15775
  const outDirHit = resolvedAstroHit(outDir);
14795
15776
  const astroCorrect = blumeAstro !== null && outDirHit?.pkg === blumeAstro;
14796
15777
  const walkLandsInDeps = astroCorrect && outDirHit !== null && sameRealDir(outDirHit.modulesDir, astroDir);
@@ -14798,7 +15779,7 @@ var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
14798
15779
  return null;
14799
15780
  }
14800
15781
  if (mdxDir && (mdxDir === astroDir || astroCorrect)) {
14801
- await linkDepsJunction(join26(outDir, "node_modules"), mdxDir);
15782
+ await linkDepsJunction(join28(outDir, "node_modules"), mdxDir);
14802
15783
  return null;
14803
15784
  }
14804
15785
  return astroConflictWarning(blumeAstro, outDirHit?.pkg ?? null);
@@ -14839,14 +15820,14 @@ var searchProviderWarnings = (provider, root, pkgDir = packageRoot()) => {
14839
15820
  }
14840
15821
  return warnings;
14841
15822
  };
14842
- 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;
14843
15824
  var writeExamplesPreview = async (options) => {
14844
15825
  const { config, hasExamples, root, srcDir, write } = options;
14845
15826
  if (hasExamples) {
14846
- 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());
14847
15828
  }
14848
15829
  const cssFile = examplesCssFile(root, config);
14849
- return cssFile && !existsSync14(cssFile) ? [
15830
+ return cssFile && !existsSync16(cssFile) ? [
14850
15831
  `examples.css points at "${config.examples.css}", which doesn't exist; previews render without it.`
14851
15832
  ] : [];
14852
15833
  };
@@ -14855,7 +15836,7 @@ var readOptional = async (path) => {
14855
15836
  return "";
14856
15837
  }
14857
15838
  try {
14858
- return await readFile14(path, "utf-8");
15839
+ return await readFile16(path, "utf-8");
14859
15840
  } catch {
14860
15841
  return "";
14861
15842
  }
@@ -14875,30 +15856,30 @@ var detectUsesMath = async (root, staged = []) => {
14875
15856
  ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
14876
15857
  onlyFiles: true
14877
15858
  });
14878
- 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))));
14879
15860
  return [...contents, ...staged].some(containsMath);
14880
15861
  };
14881
- var hashConfigSource = (source) => createHash2("sha256").update(source).digest("hex");
15862
+ var hashConfigSource = (source) => createHash3("sha256").update(source).digest("hex");
14882
15863
  var loadIntegrationBridge = async (config, context) => {
14883
15864
  if (config.integrations.length === 0 || !context.configFile) {
14884
15865
  return;
14885
15866
  }
14886
15867
  return {
14887
- configFile: relative13(context.outDir, context.configFile),
15868
+ configFile: relative14(context.outDir, context.configFile),
14888
15869
  sourceHash: hashConfigSource(await readOptional(context.configFile))
14889
15870
  };
14890
15871
  };
14891
15872
  var writeIfChanged = async (path, content) => {
14892
15873
  let existing = null;
14893
15874
  try {
14894
- existing = await readFile14(path, "utf-8");
15875
+ existing = await readFile16(path, "utf-8");
14895
15876
  } catch {
14896
15877
  existing = null;
14897
15878
  }
14898
15879
  if (existing === content) {
14899
15880
  return false;
14900
15881
  }
14901
- await mkdir6(dirname9(path), { recursive: true });
15882
+ await mkdir6(dirname10(path), { recursive: true });
14902
15883
  const tmp = `${path}.${process.pid}.tmp`;
14903
15884
  await writeFile7(tmp, content, "utf-8");
14904
15885
  try {
@@ -14936,12 +15917,12 @@ var collectStaged = (project) => {
14936
15917
  var writeStagedContent = async (out, staged) => {
14937
15918
  const contentDir = stagedContentDir(out);
14938
15919
  const written = new Set;
14939
- await Promise.all([...staged].map(async ([entryId, text]) => {
14940
- const path = join26(contentDir, entryId);
15920
+ await Promise.all([...staged].map(async ([entryId, text2]) => {
15921
+ const path = join28(contentDir, entryId);
14941
15922
  written.add(normalize3(path));
14942
- await writeIfChanged(path, text);
15923
+ await writeIfChanged(path, text2);
14943
15924
  }));
14944
- if (existsSync14(contentDir)) {
15925
+ if (existsSync16(contentDir)) {
14945
15926
  await pruneOrphans(contentDir, written);
14946
15927
  }
14947
15928
  };
@@ -14972,9 +15953,9 @@ var readLogoSvg = (project, source) => {
14972
15953
  }
14973
15954
  const rel = source.replace(/^\//u, "");
14974
15955
  const file = [
14975
- join26(project.context.root, "public", rel),
14976
- join26(project.context.root, rel)
14977
- ].find((path) => existsSync14(path));
15956
+ join28(project.context.root, "public", rel),
15957
+ join28(project.context.root, rel)
15958
+ ].find((path) => existsSync16(path));
14978
15959
  return file ? readFileSync9(file, "utf-8") : undefined;
14979
15960
  };
14980
15961
  var resolveLogo = (project) => {
@@ -14983,7 +15964,7 @@ var resolveLogo = (project) => {
14983
15964
  return null;
14984
15965
  }
14985
15966
  const config = typeof logo === "string" ? { image: logo } : logo;
14986
- const { href, image: source, text } = config;
15967
+ const { href, image: source, text: text2 } = config;
14987
15968
  const image = typeof source === "string" ? { light: source } : source;
14988
15969
  const light = image?.light ?? image?.dark;
14989
15970
  const dark = image?.dark ?? image?.light;
@@ -14992,12 +15973,12 @@ var resolveLogo = (project) => {
14992
15973
  const lightSvg = readLogoSvg(project, light);
14993
15974
  const darkSvg = dark === light ? lightSvg : readLogoSvg(project, dark);
14994
15975
  if (light && light === dark && lightSvg) {
14995
- return { alt, href: brandHref, svg: lightSvg, text };
15976
+ return { alt, href: brandHref, svg: lightSvg, text: text2 };
14996
15977
  }
14997
15978
  const lightDimensions = svgDimensions(lightSvg);
14998
15979
  const darkDimensions = svgDimensions(darkSvg);
14999
15980
  const dimensions = lightDimensions || darkDimensions ? { dark: darkDimensions, light: lightDimensions } : undefined;
15000
- return { alt, dark, dimensions, href: brandHref, light, text };
15981
+ return { alt, dark, dimensions, href: brandHref, light, text: text2 };
15001
15982
  };
15002
15983
  var FAVICON_CANDIDATES = [
15003
15984
  "icon.svg",
@@ -15020,7 +16001,7 @@ var faviconType = (name) => {
15020
16001
  };
15021
16002
  var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync9(file).toString("base64")}`;
15022
16003
  var defaultFavicon = () => ({
15023
- href: inlineDataUri(join26(BLUME_SRC, "assets", "icon.png"), "image/png"),
16004
+ href: inlineDataUri(join28(BLUME_SRC, "assets", "icon.png"), "image/png"),
15024
16005
  type: "image/png"
15025
16006
  });
15026
16007
  var APPLE_ICON_CANDIDATES = [
@@ -15032,13 +16013,13 @@ var APPLE_ICON_CANDIDATES = [
15032
16013
  var resolveIconFile = (project, candidates) => {
15033
16014
  const { root } = project.context;
15034
16015
  for (const name of candidates) {
15035
- if (existsSync14(join26(root, "public", name))) {
16016
+ if (existsSync16(join28(root, "public", name))) {
15036
16017
  return { href: `/${name}`, type: faviconType(name) };
15037
16018
  }
15038
16019
  }
15039
16020
  for (const name of candidates) {
15040
- const file = join26(root, name);
15041
- if (existsSync14(file)) {
16021
+ const file = join28(root, name);
16022
+ if (existsSync16(file)) {
15042
16023
  const type = faviconType(name);
15043
16024
  return { href: inlineDataUri(file, type ?? "image/x-icon"), type };
15044
16025
  }
@@ -15062,18 +16043,45 @@ var resolveBanner = (config) => {
15062
16043
  link: banner.link
15063
16044
  };
15064
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
+ };
15065
16073
  var buildRuntimeData = (project) => {
15066
16074
  const { config, context, graph, manifest } = project;
15067
16075
  const { github } = config;
15068
16076
  const repoUrl = github ? `https://github.com/${github.owner}/${github.repo}` : null;
15069
16077
  const editBase = github ? `${repoUrl}/edit/${github.branch}` : null;
15070
16078
  const logo = resolveLogo(project);
15071
- const ogLogo = config.seo.og.logo ? resolveOgLogo(project, config.seo.og.logo) : logo?.svg;
16079
+ const ogLogo = resolveOgMark(project, logo?.svg);
15072
16080
  const editUrlFor = (sourcePath) => {
15073
16081
  if (!(editBase && sourcePath)) {
15074
16082
  return null;
15075
16083
  }
15076
- const rel = relative13(context.root, sourcePath).split("\\").join("/");
16084
+ const rel = relative14(context.root, sourcePath).split("\\").join("/");
15077
16085
  const editPath = github?.dir ? `${github.dir}/${rel}` : rel;
15078
16086
  return `${editBase}/${editPath}`;
15079
16087
  };
@@ -15135,10 +16143,11 @@ var buildRuntimeData = (project) => {
15135
16143
  route: config.ai.mcp.route
15136
16144
  } : null,
15137
16145
  og: {
16146
+ description: resolveOgDescription(config),
15138
16147
  enabled: config.seo.og.enabled ?? false,
15139
- fonts: config.seo.og.fonts ?? [],
15140
16148
  logo: ogLogo,
15141
- palette: config.seo.og.palette
16149
+ palette: config.seo.og.palette,
16150
+ site: resolveOgSite(config)
15142
16151
  },
15143
16152
  repoUrl,
15144
16153
  search: {
@@ -15151,6 +16160,10 @@ var buildRuntimeData = (project) => {
15151
16160
  theme: config.theme,
15152
16161
  title: config.title,
15153
16162
  toc: config.toc,
16163
+ webmcp: {
16164
+ enabled: config.ai.webmcp,
16165
+ llms: config.ai.llmsTxt.enabled
16166
+ },
15154
16167
  x: config.seo.x
15155
16168
  },
15156
16169
  feeds: buildRssFeeds(project).map((feed) => ({
@@ -15184,7 +16197,7 @@ var buildRuntimeData = (project) => {
15184
16197
  var planMcp = (project, srcDir, userPages) => {
15185
16198
  const { config } = project;
15186
16199
  const { route } = config.ai.mcp;
15187
- const dir = join26(srcDir, "blume-mcp");
16200
+ const dir = join28(srcDir, "blume-mcp");
15188
16201
  const base = {
15189
16202
  dir,
15190
16203
  discoveryPages: [],
@@ -15208,11 +16221,11 @@ var planMcp = (project, srcDir, userPages) => {
15208
16221
  ...base,
15209
16222
  discoveryPages: [
15210
16223
  {
15211
- entrypoint: join26(dir, "discovery.ts"),
16224
+ entrypoint: join28(dir, "discovery.ts"),
15212
16225
  pattern: "/.well-known/mcp.json"
15213
16226
  },
15214
16227
  {
15215
- entrypoint: join26(dir, "server-card.ts"),
16228
+ entrypoint: join28(dir, "server-card.ts"),
15216
16229
  pattern: "/.well-known/mcp/server-card.json"
15217
16230
  }
15218
16231
  ],
@@ -15232,11 +16245,11 @@ var writeMcpFiles = async (project, plan, write) => {
15232
16245
  version: data.version
15233
16246
  };
15234
16247
  await Promise.all([
15235
- write(join26(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
16248
+ write(join28(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
15236
16249
  `),
15237
- write(join26(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate(plan.route)),
15238
- write(join26(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
15239
- write(join26(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
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)))
15240
16253
  ]);
15241
16254
  };
15242
16255
  var writeAskFiles = async (project, srcDir, write) => {
@@ -15246,20 +16259,20 @@ var writeAskFiles = async (project, srcDir, write) => {
15246
16259
  }
15247
16260
  const grounded = ask.provider !== "inkeep";
15248
16261
  if (grounded) {
15249
- 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))}
15250
16263
  `);
15251
16264
  }
15252
- 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));
15253
16266
  };
15254
16267
  var writeNotFoundPage = async (write, srcDir, pages, contentPages) => {
15255
16268
  if (routeIsTaken(pages, contentPages, "/404")) {
15256
16269
  return;
15257
16270
  }
15258
- await write(join26(srcDir, "pages", "404.astro"), notFoundPageTemplate());
16271
+ await write(join28(srcDir, "pages", "404.astro"), notFoundPageTemplate());
15259
16272
  };
15260
16273
  var diagnosticWarning = (diagnostic) => diagnostic.suggestion ? `${diagnostic.message} ${diagnostic.suggestion}` : diagnostic.message;
15261
16274
  var buildComponentSlots = async (componentsFile) => {
15262
- const analysis = componentsFile ? analyzeComponentOverrides(await readFile14(componentsFile, "utf-8"), componentsFile) : null;
16275
+ const analysis = componentsFile ? analyzeComponentOverrides(await readFile16(componentsFile, "utf-8"), componentsFile) : null;
15263
16276
  return {
15264
16277
  plan: planComponentSlots(componentsFile, analysis),
15265
16278
  tags: analysis ? [...analysis.mdx, ...analysis.islands].map((entry) => entry.key) : [],
@@ -15267,17 +16280,34 @@ var buildComponentSlots = async (componentsFile) => {
15267
16280
  };
15268
16281
  };
15269
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
+ };
15270
16299
  var generateRuntime = async (project) => {
15271
16300
  const { context, config } = project;
16301
+ assertFontFilesExist(project);
15272
16302
  const out = context.outDir;
15273
- const srcDir = join26(out, "src");
15274
- const askPath = join26(srcDir, "generated", "Ask.astro");
15275
- const dataPath = join26(srcDir, "generated", "data.json");
15276
- const themePath = join26(srcDir, "generated", "app.css");
15277
- const searchClientPath = join26(srcDir, "generated", "search-client.ts");
15278
- const examplesPath = join26(srcDir, "generated", "examples.ts");
15279
- const examplesThemePath = join26(srcDir, "generated", "examples.css");
15280
- const openapiPath = join26(srcDir, "generated", "openapi.json");
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");
15281
16311
  const written = new Set;
15282
16312
  const write = (path, content) => {
15283
16313
  written.add(normalize3(path));
@@ -15332,7 +16362,7 @@ var generateRuntime = async (project) => {
15332
16362
  const docsCollection = resolveDocsCollection(config, context);
15333
16363
  const [structural] = await Promise.all([
15334
16364
  Promise.all([
15335
- write(join26(out, "astro.config.mjs"), astroConfigTemplate({
16365
+ write(join28(out, "astro.config.mjs"), astroConfigTemplate({
15336
16366
  aliases: resolveTsconfigAliases(context.root),
15337
16367
  askPath,
15338
16368
  config,
@@ -15352,26 +16382,26 @@ var generateRuntime = async (project) => {
15352
16382
  searchClientPath,
15353
16383
  themePath
15354
16384
  })),
15355
- write(join26(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
15356
- write(join26(out, "tsconfig.json"), runtimeTsconfigTemplate()),
15357
- write(join26(srcDir, "env.d.ts"), envTemplate()),
15358
- write(join26(srcDir, "content.config.ts"), contentConfigTemplate({
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({
15359
16389
  collection: docsCollection,
15360
16390
  config,
15361
16391
  context,
15362
16392
  filesystem: hasFilesystemSource,
15363
16393
  staged: hasStaged
15364
16394
  })),
15365
- write(join26(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
16395
+ write(join28(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
15366
16396
  exportEpub,
15367
16397
  exportPdf,
15368
16398
  mathEnabled: usesMath,
15369
16399
  needsReact
15370
16400
  })),
15371
16401
  write(askPath, askComponentTemplate(askEnabled)),
15372
- write(join26(srcDir, "generated", "components.ts"), slotPlan.module),
15373
- write(join26(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
15374
- write(join26(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples, config.basePath)),
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)),
15375
16405
  write(examplesThemePath, examplesEntryTemplate({
15376
16406
  configTokens: buildThemeCss(config.theme),
15377
16407
  sources: [`${context.root}/**/*.{astro,jsx,svelte,ts,tsx,vue}`],
@@ -15387,17 +16417,17 @@ var generateRuntime = async (project) => {
15387
16417
  userTheme
15388
16418
  }))
15389
16419
  ]),
15390
- Promise.all(islandDiscovery.islands.map((island) => write(join26(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island)))),
15391
- Promise.all(slotPlan.wrappers.map((wrapper) => write(join26(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content))),
15392
- Promise.all(exampleDiscovery.examples.map((example) => write(join26(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example)))),
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)))),
15393
16423
  writeAskFiles(project, srcDir, write),
15394
16424
  writeMcpFiles(project, mcp, write)
15395
16425
  ]);
15396
16426
  if (config.seo.og.enabled) {
15397
- 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)));
15398
16428
  }
15399
16429
  if (hasGeneratedChangelog(project, pages)) {
15400
- await write(join26(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
16430
+ await write(join28(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
15401
16431
  exportEpub,
15402
16432
  exportPdf,
15403
16433
  needsReact,
@@ -15417,27 +16447,31 @@ var generateRuntime = async (project) => {
15417
16447
  ]);
15418
16448
  if (servesStaticIndex(config.search.provider)) {
15419
16449
  const documents = await buildSearchDocuments(project);
15420
- await write(join26(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
16450
+ await write(join28(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
15421
16451
  `);
15422
- await write(join26(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
16452
+ await write(join28(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
15423
16453
  }
15424
16454
  if (config.search.provider === "mixedbread") {
15425
- 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 ?? ""));
15426
16456
  }
15427
16457
  const rawMarkdown = await buildRawMarkdown(project);
16458
+ const contentAssets = await collectContentAssets(project);
15428
16459
  await Promise.all([
15429
- write(join26(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
16460
+ write(join28(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
15430
16461
  `),
15431
- write(join26(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate("md")),
15432
- write(join26(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate("mdx"))
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)}
16465
+ `),
16466
+ write(join28(srcDir, "pages", "blume-assets", "[...asset].ts"), contentAssetsEndpointTemplate(join28(project.context.outDir, "public", "blume-assets")))
15433
16467
  ]);
15434
16468
  const feeds = buildRssFeeds(project);
15435
16469
  if (feeds.length > 0) {
15436
16470
  const feedXml = Object.fromEntries(feeds.map((feed) => [feed.type, renderRssFeed(feed)]));
15437
16471
  await Promise.all([
15438
- write(join26(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
16472
+ write(join28(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
15439
16473
  `),
15440
- write(join26(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
16474
+ write(join28(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
15441
16475
  ]);
15442
16476
  }
15443
16477
  const warnings = [
@@ -15473,14 +16507,14 @@ var generateRuntime = async (project) => {
15473
16507
  root: context.root
15474
16508
  });
15475
16509
  warnings.push(...references.warnings);
15476
- 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)));
15477
16511
  }
15478
16512
  const openApiSource2 = project.sources.find(isOpenApiSource);
15479
16513
  await Promise.all([
15480
- write(join26(srcDir, "generated", "data.json"), buildRuntimeData(project)),
16514
+ write(join28(srcDir, "generated", "data.json"), buildRuntimeData(project)),
15481
16515
  write(openapiPath, `${JSON.stringify(openApiSource2 ? openApiSource2.openApiData() : {})}
15482
16516
  `),
15483
- 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)}
15484
16518
  `),
15485
16519
  writeStagedContent(out, staged)
15486
16520
  ]);
@@ -15489,8 +16523,8 @@ var generateRuntime = async (project) => {
15489
16523
  };
15490
16524
 
15491
16525
  // src/cli/env.ts
15492
- import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
15493
- import { dirname as dirname10, join as join27, resolve as resolve8 } from "pathe";
16526
+ import { existsSync as existsSync17, readFileSync as readFileSync10 } from "node:fs";
16527
+ import { dirname as dirname11, join as join29, resolve as resolve9 } from "pathe";
15494
16528
  var ENV_LINE = /^\s*(?:export\s+)?(?<key>[A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?<value>.*?)\s*$/u;
15495
16529
  var DOUBLE_QUOTED2 = /^"(?<body>[\s\S]*)"$/u;
15496
16530
  var SINGLE_QUOTED = /^'(?<body>[\s\S]*)'$/u;
@@ -15536,19 +16570,19 @@ var applyEnv = (parsed) => {
15536
16570
  };
15537
16571
  var loadFile = (path) => {
15538
16572
  try {
15539
- if (existsSync15(path)) {
16573
+ if (existsSync17(path)) {
15540
16574
  applyEnv(parseEnv(readFileSync10(path, "utf-8")));
15541
16575
  }
15542
16576
  } catch {}
15543
16577
  };
15544
16578
  var loadEnvFiles = (startDir) => {
15545
- let dir = resolve8(startDir);
16579
+ let dir = resolve9(startDir);
15546
16580
  let done = false;
15547
16581
  while (!done) {
15548
- loadFile(join27(dir, ".env.local"));
15549
- loadFile(join27(dir, ".env"));
15550
- const parent = dirname10(dir);
15551
- done = existsSync15(join27(dir, ".git")) || parent === dir;
16582
+ loadFile(join29(dir, ".env.local"));
16583
+ loadFile(join29(dir, ".env"));
16584
+ const parent = dirname11(dir);
16585
+ done = existsSync17(join29(dir, ".git")) || parent === dir;
15552
16586
  dir = parent;
15553
16587
  }
15554
16588
  };
@@ -15653,20 +16687,91 @@ var emitRedirectFiles = async (config, distDir) => {
15653
16687
  if (redirects.length === 0 || config.deployment.output !== "static") {
15654
16688
  return;
15655
16689
  }
15656
- await writeFile8(join28(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
16690
+ await writeFile8(join30(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
15657
16691
  const platformFiles = [
15658
16692
  { content: buildNetlifyRedirects(redirects), name: "_redirects" },
15659
16693
  { content: buildVercelConfig(redirects), name: "vercel.json" }
15660
16694
  ];
15661
- 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")));
15662
16696
  logger.success(`Emitted redirect files for ${redirects.length} redirect(s)`);
15663
16697
  };
15664
- var emitHeaderFiles = async (config, distDir) => {
15665
- 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.");
15666
16771
  return;
15667
16772
  }
15668
- await writeFile8(join28(distDir, "_headers"), buildNetlifyHeaders(config), "utf-8");
15669
- 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");
15670
16775
  };
15671
16776
  var formatBytes2 = (bytes) => {
15672
16777
  if (bytes < 1024) {
@@ -15676,14 +16781,14 @@ var formatBytes2 = (bytes) => {
15676
16781
  return `${(bytes / 1024).toFixed(digits)} kB`;
15677
16782
  };
15678
16783
  var astroAssets = async (distDir, ext) => {
15679
- const astroDir = join28(distDir, "_astro");
15680
- if (!existsSync16(astroDir)) {
16784
+ const astroDir = join30(distDir, "_astro");
16785
+ if (!existsSync18(astroDir)) {
15681
16786
  return [];
15682
16787
  }
15683
- const entries = await readdir(astroDir);
16788
+ const entries = await readdir2(astroDir);
15684
16789
  const files = entries.filter((name) => name.endsWith(`.${ext}`));
15685
16790
  const sized = await Promise.all(files.map(async (name) => {
15686
- const info = await stat2(join28(astroDir, name));
16791
+ const info = await stat3(join30(astroDir, name));
15687
16792
  return { name, size: info.size };
15688
16793
  }));
15689
16794
  return sized.toSorted((a, b) => b.size - a.size);
@@ -15736,26 +16841,26 @@ var runClientAssetChecks = async (staticDir, args) => {
15736
16841
  var isolatedOutputDir = (config, context) => {
15737
16842
  const { adapter, output } = config.deployment;
15738
16843
  if (output === "server" && adapter === "vercel") {
15739
- return join28(context.outDir, ".vercel", "output");
16844
+ return join30(context.outDir, ".vercel", "output");
15740
16845
  }
15741
- return context.distDir ?? join28(context.outDir, "dist");
16846
+ return context.distDir ?? join30(context.outDir, "dist");
15742
16847
  };
15743
16848
  var isolatedStaticDir = (config, context) => {
15744
16849
  const { adapter, output } = config.deployment;
15745
16850
  const outputDir = isolatedOutputDir(config, context);
15746
16851
  if (output === "server" && adapter === "vercel") {
15747
- return join28(outputDir, "static");
16852
+ return join30(outputDir, "static");
15748
16853
  }
15749
16854
  if (output === "server" && adapter === "node") {
15750
- return join28(outputDir, "client");
16855
+ return join30(outputDir, "client");
15751
16856
  }
15752
16857
  return outputDir;
15753
16858
  };
15754
16859
  var publishLlmsFiles = async (project, distDir) => {
15755
- const indexPath = join28(distDir, "llms.txt");
15756
- const fullPath = join28(distDir, "llms-full.txt");
15757
- const writeIndex = !existsSync16(indexPath);
15758
- const writeFull = !existsSync16(fullPath);
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);
15759
16864
  if (!(writeIndex || writeFull)) {
15760
16865
  return;
15761
16866
  }
@@ -15788,23 +16893,25 @@ var publishBuildArtifacts = async (project, distDir, args) => {
15788
16893
  await publishLlmsFiles(project, distDir);
15789
16894
  }
15790
16895
  const sitemap = buildSitemap(project);
15791
- if (sitemap && !existsSync16(join28(distDir, "sitemap.xml"))) {
15792
- 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");
15793
16898
  logger.success("Generated sitemap.xml");
15794
16899
  }
15795
16900
  const robots = buildRobots(project);
15796
- if (robots && !existsSync16(join28(distDir, "robots.txt"))) {
15797
- 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");
15798
16903
  logger.success("Generated robots.txt");
15799
16904
  }
15800
16905
  const agentReadability = buildAgentReadability(project);
15801
- if (agentReadability && !existsSync16(join28(distDir, "agent-readability.json"))) {
15802
- await writeFile8(join28(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
16906
+ if (agentReadability && !existsSync18(join30(distDir, "agent-readability.json"))) {
16907
+ await writeFile8(join30(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
15803
16908
  `, "utf-8");
15804
16909
  logger.success("Generated agent-readability.json");
15805
16910
  }
16911
+ await emitWellKnownFiles(project.config, distDir);
16912
+ await emitAgentSkills(project, distDir);
15806
16913
  await emitRedirectFiles(project.config, distDir);
15807
- await emitHeaderFiles(project.config, distDir);
16914
+ await emitHeaderFiles(project, distDir);
15808
16915
  const { config } = project;
15809
16916
  const features = serverFeatures(config);
15810
16917
  const sitemapNote = config.seo.sitemap ? "no (set deployment.site)" : "no (seo.sitemap is false)";
@@ -15918,16 +17025,19 @@ var buildCommand = defineCommand3({
15918
17025
  if (surfaced.moved) {
15919
17026
  logger.success(`Surfaced ${adapter} output to ${surfaced.to}`);
15920
17027
  }
17028
+ if (project.config.deployment.output === "server" && adapter === "vercel") {
17029
+ await emitVercelNegotiation(project.config, project.manifest.routes.map((route) => route.path), root);
17030
+ }
15921
17031
  await publishBuildArtifacts(project, deployStaticDir(project.config, project.context), args);
15922
17032
  }
15923
17033
  });
15924
17034
 
15925
17035
  // src/cli/commands/check.ts
15926
- import { existsSync as existsSync17 } from "node:fs";
17036
+ import { existsSync as existsSync19 } from "node:fs";
15927
17037
  import { check } from "@astrojs/check";
15928
17038
  import { sync } from "astro";
15929
17039
  import { defineCommand as defineCommand4 } from "citty";
15930
- import { join as join29 } from "pathe";
17040
+ import { join as join31 } from "pathe";
15931
17041
  var checkCommand = defineCommand4({
15932
17042
  args: {
15933
17043
  isolated: {
@@ -15963,13 +17073,13 @@ var checkCommand = defineCommand4({
15963
17073
  });
15964
17074
  const { outDir } = project.context;
15965
17075
  await sync({ logLevel: "warn", root: outDir });
15966
- const tsconfig = join29(root, "tsconfig.json");
17076
+ const tsconfig = join31(root, "tsconfig.json");
15967
17077
  logger.start(`Type-checking ${project.graph.pages.length} page(s)`);
15968
17078
  const failed = await check({
15969
17079
  minimumFailingSeverity: "error",
15970
17080
  minimumSeverity: "hint",
15971
17081
  root: outDir,
15972
- tsconfig: existsSync17(tsconfig) ? tsconfig : undefined,
17082
+ tsconfig: existsSync19(tsconfig) ? tsconfig : undefined,
15973
17083
  watch: false
15974
17084
  });
15975
17085
  if (failed) {
@@ -15984,7 +17094,7 @@ var checkCommand = defineCommand4({
15984
17094
  import { watch } from "node:fs";
15985
17095
  import { dev } from "astro";
15986
17096
  import { defineCommand as defineCommand5 } from "citty";
15987
- import { basename as basename4, dirname as dirname11 } from "pathe";
17097
+ import { basename as basename4, dirname as dirname13 } from "pathe";
15988
17098
 
15989
17099
  // src/astro/integration.ts
15990
17100
  var overlayServer = null;
@@ -16177,7 +17287,7 @@ var devCommand = defineCommand5({
16177
17287
  }),
16178
17288
  ...fileTargets.map((target) => {
16179
17289
  const name = basename4(target);
16180
- const watcher = watch(dirname11(target), (_event, filename) => {
17290
+ const watcher = watch(dirname13(target), (_event, filename) => {
16181
17291
  if (!filename || filename === name) {
16182
17292
  regenerate();
16183
17293
  }
@@ -16201,12 +17311,12 @@ var devCommand = defineCommand5({
16201
17311
  // src/cli/commands/doctor.ts
16202
17312
  import { readFileSync as readFileSync11 } from "node:fs";
16203
17313
  import { defineCommand as defineCommand6 } from "citty";
16204
- import { join as join30 } from "pathe";
17314
+ import { join as join32 } from "pathe";
16205
17315
  var FALLBACK_MIN_NODE = "22.12.0";
16206
17316
  var LEADING_RANGE = /^[^\d]*/u;
16207
17317
  var minSupportedNode = () => {
16208
17318
  try {
16209
- const pkg = JSON.parse(readFileSync11(join30(packageRoot(), "package.json"), "utf-8"));
17319
+ const pkg = JSON.parse(readFileSync11(join32(packageRoot(), "package.json"), "utf-8"));
16210
17320
  const range = pkg.engines?.node ?? "";
16211
17321
  return range.replace(LEADING_RANGE, "") || FALLBACK_MIN_NODE;
16212
17322
  } catch {
@@ -16299,21 +17409,21 @@ var doctorCommand = defineCommand6({
16299
17409
 
16300
17410
  // src/cli/commands/eject.ts
16301
17411
  import { defineCommand as defineCommand7 } from "citty";
16302
- import { relative as relative16 } from "pathe";
17412
+ import { relative as relative17 } from "pathe";
16303
17413
 
16304
17414
  // src/registry/eject.ts
16305
- import { existsSync as existsSync18 } from "node:fs";
16306
- import { cp as cp2, mkdir as mkdir7, readFile as readFile15, rm as rm3, writeFile as writeFile9 } from "node:fs/promises";
16307
- import { join as join31, relative as relative14 } from "pathe";
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";
16308
17418
  var toPosix = (path) => path.split("\\").join("/");
16309
17419
  var LOCAL_BLUME_SOURCE = "../../node_modules/blume/src/**/*.{astro,ts,tsx}";
16310
17420
  var blumeSourceGlob = (root, genDir, resolveBlumeRoot = packageRoot) => {
16311
- if (existsSync18(join31(root, "node_modules", "blume"))) {
17421
+ if (existsSync20(join33(root, "node_modules", "blume"))) {
16312
17422
  return LOCAL_BLUME_SOURCE;
16313
17423
  }
16314
17424
  try {
16315
- const src = join31(resolveBlumeRoot(), "src");
16316
- return `${toPosix(relative14(genDir, src))}/**/*.{astro,ts,tsx}`;
17425
+ const src = join33(resolveBlumeRoot(), "src");
17426
+ return `${toPosix(relative15(genDir, src))}/**/*.{astro,ts,tsx}`;
16317
17427
  } catch {
16318
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.');
16319
17429
  return LOCAL_BLUME_SOURCE;
@@ -16326,28 +17436,28 @@ var ejectOpenApiData = (project) => {
16326
17436
  var askFiles = async (project, srcDir, genDir) => {
16327
17437
  const { ask } = project.config.ai;
16328
17438
  if (!(ask?.enabled && !ask.endpoint)) {
16329
- const endpointPath = join31(srcDir, "pages", "api", "ask.ts");
16330
- if (existsSync18(endpointPath)) {
16331
- const content = await readFile15(endpointPath, "utf-8");
17439
+ const endpointPath = join33(srcDir, "pages", "api", "ask.ts");
17440
+ if (existsSync20(endpointPath)) {
17441
+ const content = await readFile18(endpointPath, "utf-8");
16332
17442
  if (content.startsWith("// Generated by Blume. Do not edit.")) {
16333
17443
  await rm3(endpointPath, { force: true });
16334
17444
  }
16335
17445
  }
16336
- await rm3(join31(genDir, "ask-data.json"), { force: true });
17446
+ await rm3(join33(genDir, "ask-data.json"), { force: true });
16337
17447
  return [];
16338
17448
  }
16339
17449
  const grounded = ask.provider !== "inkeep";
16340
17450
  const files = [
16341
17451
  {
16342
17452
  content: askEndpointTemplate(resolveAskBackend(ask), grounded),
16343
- path: join31(srcDir, "pages", "api", "ask.ts")
17453
+ path: join33(srcDir, "pages", "api", "ask.ts")
16344
17454
  }
16345
17455
  ];
16346
17456
  if (grounded) {
16347
17457
  files.push({
16348
17458
  content: `${JSON.stringify(await buildAskData(project))}
16349
17459
  `,
16350
- path: join31(genDir, "ask-data.json")
17460
+ path: join33(genDir, "ask-data.json")
16351
17461
  });
16352
17462
  }
16353
17463
  return files;
@@ -16380,19 +17490,19 @@ var mcpFiles = async (project, userPages, srcDir, genDir) => {
16380
17490
  {
16381
17491
  content: `${JSON.stringify(data)}
16382
17492
  `,
16383
- path: join31(genDir, "mcp-data.json")
17493
+ path: join33(genDir, "mcp-data.json")
16384
17494
  },
16385
17495
  {
16386
17496
  content: mcpEndpointTemplate(route),
16387
- path: join31(srcDir, "pages", mcpPageFile(route))
17497
+ path: join33(srcDir, "pages", mcpPageFile(route))
16388
17498
  },
16389
17499
  {
16390
17500
  content: staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput)),
16391
- path: join31(srcDir, "blume-mcp", "discovery.ts")
17501
+ path: join33(srcDir, "blume-mcp", "discovery.ts")
16392
17502
  },
16393
17503
  {
16394
17504
  content: staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)),
16395
- path: join31(srcDir, "blume-mcp", "server-card.ts")
17505
+ path: join33(srcDir, "blume-mcp", "server-card.ts")
16396
17506
  }
16397
17507
  ];
16398
17508
  };
@@ -16405,23 +17515,23 @@ var changelogFiles = (project, userPages, srcDir, options) => {
16405
17515
  return [
16406
17516
  {
16407
17517
  content: changelogIndexTemplate(options),
16408
- path: join31(srcDir, "pages", "changelog.astro")
17518
+ path: join33(srcDir, "pages", "changelog.astro")
16409
17519
  }
16410
17520
  ];
16411
17521
  };
16412
- 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("");
16413
17523
  var examplesPreviewFiles = (srcDir, basePath, hasExamples) => hasExamples ? [
16414
17524
  {
16415
17525
  content: examplesPageTemplate(),
16416
- 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")
16417
17527
  }
16418
17528
  ] : [];
16419
- 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;
16420
17530
  var eject = async (root) => {
16421
17531
  const project = await scanProject(root, { mode: "build" });
16422
17532
  const { context, config } = project;
16423
- const srcDir = join31(root, "src");
16424
- const genDir = join31(srcDir, "generated");
17533
+ const srcDir = join33(root, "src");
17534
+ const genDir = join33(srcDir, "generated");
16425
17535
  const askEnabled = config.ai.ask?.enabled ?? false;
16426
17536
  const exportPdf = config.export.pdf;
16427
17537
  const exportEpub = config.export.epub;
@@ -16439,7 +17549,7 @@ var eject = async (root) => {
16439
17549
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
16440
17550
  detectNeedsReact(root),
16441
17551
  detectUsesMath(root),
16442
- context.themeFile ? readFile15(context.themeFile, "utf-8") : Promise.resolve(""),
17552
+ context.themeFile ? readFile18(context.themeFile, "utf-8") : Promise.resolve(""),
16443
17553
  readExamplesCss(root, config.examples.css),
16444
17554
  buildRawMarkdown(project),
16445
17555
  discoverIslands(root)
@@ -16453,14 +17563,14 @@ var eject = async (root) => {
16453
17563
  const needsSvelte = frameworks.has("svelte");
16454
17564
  const relContext = {
16455
17565
  ...context,
16456
- contentRoot: toPosix(relative14(root, context.contentRoot)),
17566
+ contentRoot: toPosix(relative15(root, context.contentRoot)),
16457
17567
  outDir: ".",
16458
17568
  root: "."
16459
17569
  };
16460
- const componentsImport = context.componentsFile ? `../../${toPosix(relative14(root, context.componentsFile))}` : null;
17570
+ const componentsImport = context.componentsFile ? `../../${toPosix(relative15(root, context.componentsFile))}` : null;
16461
17571
  const relPages = [
16462
17572
  ...pages.map((page) => ({
16463
- entrypoint: toPosix(relative14(root, page.entrypoint)),
17573
+ entrypoint: toPosix(relative15(root, page.entrypoint)),
16464
17574
  pattern: page.pattern
16465
17575
  })),
16466
17576
  ...mcpDiscoveryPages(project, pages)
@@ -16487,14 +17597,14 @@ var eject = async (root) => {
16487
17597
  searchClientPath: "./src/generated/search-client.ts",
16488
17598
  themePath: "./src/generated/app.css"
16489
17599
  }),
16490
- path: join31(root, "astro.config.mjs")
17600
+ path: join33(root, "astro.config.mjs")
16491
17601
  },
16492
17602
  {
16493
17603
  content: runtimeTsconfigTemplate(),
16494
- path: join31(root, "tsconfig.json"),
17604
+ path: join33(root, "tsconfig.json"),
16495
17605
  skipIfExists: true
16496
17606
  },
16497
- { content: envTemplate(), path: join31(srcDir, "env.d.ts") },
17607
+ { content: envTemplate(), path: join33(srcDir, "env.d.ts") },
16498
17608
  {
16499
17609
  content: contentConfigTemplate({
16500
17610
  config,
@@ -16502,7 +17612,7 @@ var eject = async (root) => {
16502
17612
  staged: hasStaged,
16503
17613
  stagedBase: stagedDir
16504
17614
  }),
16505
- path: join31(srcDir, "content.config.ts")
17615
+ path: join33(srcDir, "content.config.ts")
16506
17616
  },
16507
17617
  {
16508
17618
  content: catchAllPageTemplate({
@@ -16511,19 +17621,19 @@ var eject = async (root) => {
16511
17621
  mathEnabled: usesMath,
16512
17622
  needsReact
16513
17623
  }),
16514
- path: join31(srcDir, "pages", "[...slug].astro")
17624
+ path: join33(srcDir, "pages", "[...slug].astro")
16515
17625
  },
16516
17626
  {
16517
17627
  content: planComponentSlots(componentsImport, null).module,
16518
- path: join31(genDir, "components.ts")
17628
+ path: join33(genDir, "components.ts")
16519
17629
  },
16520
17630
  {
16521
17631
  content: islandMapTemplate(islands.islands),
16522
- path: join31(genDir, "islands.ts")
17632
+ path: join33(genDir, "islands.ts")
16523
17633
  },
16524
17634
  {
16525
17635
  content: exampleMapTemplate(examples.examples, config.basePath),
16526
- path: join31(genDir, "examples.ts")
17636
+ path: join33(genDir, "examples.ts")
16527
17637
  },
16528
17638
  {
16529
17639
  content: examplesEntryTemplate({
@@ -16531,7 +17641,7 @@ var eject = async (root) => {
16531
17641
  sources: ["../../**/*.{astro,jsx,svelte,ts,tsx,vue}"],
16532
17642
  userCss: userExamplesCss
16533
17643
  }),
16534
- path: join31(genDir, "examples.css")
17644
+ path: join33(genDir, "examples.css")
16535
17645
  },
16536
17646
  {
16537
17647
  content: tailwindEntryTemplate({
@@ -16543,30 +17653,30 @@ var eject = async (root) => {
16543
17653
  twoslashCss: twoslashCss(),
16544
17654
  userTheme
16545
17655
  }),
16546
- path: join31(genDir, "app.css")
17656
+ path: join33(genDir, "app.css")
16547
17657
  },
16548
- { content: buildRuntimeData(project), path: join31(genDir, "data.json") },
17658
+ { content: buildRuntimeData(project), path: join33(genDir, "data.json") },
16549
17659
  {
16550
17660
  content: askComponentTemplate(askEnabled),
16551
- path: join31(genDir, "Ask.astro")
17661
+ path: join33(genDir, "Ask.astro")
16552
17662
  },
16553
17663
  {
16554
17664
  content: `${JSON.stringify(ejectOpenApiData(project))}
16555
17665
  `,
16556
- path: join31(genDir, "openapi.json")
17666
+ path: join33(genDir, "openapi.json")
16557
17667
  },
16558
17668
  {
16559
17669
  content: `${JSON.stringify(rawMarkdown)}
16560
17670
  `,
16561
- path: join31(genDir, "raw-markdown.json")
17671
+ path: join33(genDir, "raw-markdown.json")
16562
17672
  },
16563
17673
  {
16564
17674
  content: rawMarkdownEndpointTemplate("md"),
16565
- path: join31(srcDir, "pages", "[...slug].md.ts")
17675
+ path: join33(srcDir, "pages", "[...slug].md.ts")
16566
17676
  },
16567
17677
  {
16568
17678
  content: rawMarkdownEndpointTemplate("mdx"),
16569
- path: join31(srcDir, "pages", "[...slug].mdx.ts")
17679
+ path: join33(srcDir, "pages", "[...slug].mdx.ts")
16570
17680
  }
16571
17681
  ];
16572
17682
  if (askEnabled) {
@@ -16575,7 +17685,7 @@ var eject = async (root) => {
16575
17685
  if (config.seo.og.enabled) {
16576
17686
  files.push({
16577
17687
  content: ogEndpointTemplate(customOgRoutes(pages, config.title, config.seo.og.titles)),
16578
- path: join31(srcDir, "pages", "og", "[...slug].png.ts")
17688
+ path: join33(srcDir, "pages", "og", "[...slug].png.ts")
16579
17689
  });
16580
17690
  }
16581
17691
  files.push(...await mcpFiles(project, pages, srcDir, genDir), ...changelogFiles(project, pages, srcDir, {
@@ -16587,28 +17697,28 @@ var eject = async (root) => {
16587
17697
  if (!routeIsTaken(pages, project.graph.pages, "/404")) {
16588
17698
  files.push({
16589
17699
  content: notFoundPageTemplate(),
16590
- path: join31(srcDir, "pages", "404.astro")
17700
+ path: join33(srcDir, "pages", "404.astro")
16591
17701
  });
16592
17702
  }
16593
17703
  files.push({
16594
17704
  content: searchClientTemplate(config),
16595
- path: join31(genDir, "search-client.ts")
17705
+ path: join33(genDir, "search-client.ts")
16596
17706
  });
16597
17707
  if (servesStaticIndex(config.search.provider)) {
16598
17708
  const documents = await buildSearchDocuments(project);
16599
17709
  files.push({
16600
17710
  content: `${JSON.stringify(documents)}
16601
17711
  `,
16602
- path: join31(genDir, "search.json")
17712
+ path: join33(genDir, "search.json")
16603
17713
  }, {
16604
17714
  content: searchEndpointTemplate(),
16605
- path: join31(srcDir, "pages", "blume-search.json.ts")
17715
+ path: join33(srcDir, "pages", "blume-search.json.ts")
16606
17716
  });
16607
17717
  }
16608
17718
  if (config.search.provider === "mixedbread") {
16609
17719
  files.push({
16610
17720
  content: mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""),
16611
- path: join31(srcDir, "pages", "api", "search.ts")
17721
+ path: join33(srcDir, "pages", "api", "search.ts")
16612
17722
  });
16613
17723
  }
16614
17724
  const feeds = buildRssFeeds(project);
@@ -16617,10 +17727,10 @@ var eject = async (root) => {
16617
17727
  files.push({
16618
17728
  content: `${JSON.stringify(feedXml)}
16619
17729
  `,
16620
- path: join31(genDir, "rss.json")
17730
+ path: join33(genDir, "rss.json")
16621
17731
  }, {
16622
17732
  content: rssEndpointTemplate(),
16623
- path: join31(srcDir, "pages", "[section]", "rss.xml.ts")
17733
+ path: join33(srcDir, "pages", "[section]", "rss.xml.ts")
16624
17734
  });
16625
17735
  }
16626
17736
  const warnings = [];
@@ -16634,28 +17744,28 @@ var eject = async (root) => {
16634
17744
  for (const file of references.files) {
16635
17745
  files.push({
16636
17746
  content: file.content,
16637
- path: join31(srcDir, "pages", file.pagePath)
17747
+ path: join33(srcDir, "pages", file.pagePath)
16638
17748
  });
16639
17749
  }
16640
17750
  }
16641
17751
  files.push(...islands.islands.map((island) => ({
16642
17752
  content: islandWrapperTemplate(island),
16643
- path: join31(genDir, "islands", `${island.name}.astro`)
17753
+ path: join33(genDir, "islands", `${island.name}.astro`)
16644
17754
  })), ...examples.examples.map((example) => ({
16645
17755
  content: exampleWrapperTemplate(example),
16646
- path: join31(genDir, "examples", `${exampleSlug(example.path)}.astro`)
17756
+ path: join33(genDir, "examples", `${exampleSlug(example.path)}.astro`)
16647
17757
  })), ...examplesPreviewFiles(srcDir, config.basePath, examples.examples.length > 0));
16648
17758
  for (const [entryId, content] of staged) {
16649
- files.push({ content, path: join31(root, stagedDir, entryId) });
17759
+ files.push({ content, path: join33(root, stagedDir, entryId) });
16650
17760
  }
16651
- const written = files.filter((file) => !(file.skipIfExists && existsSync18(file.path)));
17761
+ const written = files.filter((file) => !(file.skipIfExists && existsSync20(file.path)));
16652
17762
  await Promise.all(written.map(async (file) => {
16653
- await mkdir7(join31(file.path, ".."), { recursive: true });
17763
+ await mkdir8(join33(file.path, ".."), { recursive: true });
16654
17764
  await writeFile9(file.path, file.content, "utf-8");
16655
17765
  }));
16656
- const assetsSrc = join31(context.outDir, "public", "blume-assets");
16657
- if (existsSync18(assetsSrc)) {
16658
- 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"), {
16659
17769
  recursive: true
16660
17770
  });
16661
17771
  }
@@ -16664,8 +17774,8 @@ var eject = async (root) => {
16664
17774
  };
16665
17775
 
16666
17776
  // src/cli/eject-scripts.ts
16667
- import { readFile as readFile16, writeFile as writeFile10 } from "node:fs/promises";
16668
- 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";
16669
17779
  var droppedArtifactNotices = (config) => {
16670
17780
  const notices = [];
16671
17781
  if (config.search.provider === "pagefind") {
@@ -16692,10 +17802,10 @@ var droppedArtifactNotices = (config) => {
16692
17802
  return notices;
16693
17803
  };
16694
17804
  var updatePackageScripts = async (root) => {
16695
- const pkgPath = join32(root, "package.json");
17805
+ const pkgPath = join34(root, "package.json");
16696
17806
  let pkg;
16697
17807
  try {
16698
- pkg = JSON.parse(await readFile16(pkgPath, "utf-8"));
17808
+ pkg = JSON.parse(await readFile19(pkgPath, "utf-8"));
16699
17809
  } catch {
16700
17810
  return;
16701
17811
  }
@@ -16711,9 +17821,9 @@ var updatePackageScripts = async (root) => {
16711
17821
  };
16712
17822
 
16713
17823
  // src/cli/init/scaffold.ts
16714
- import { existsSync as existsSync19 } from "node:fs";
16715
- import { mkdir as mkdir8, writeFile as writeFile11 } from "node:fs/promises";
16716
- import { basename as basename5, dirname as dirname12, isAbsolute as isAbsolute9, join as join33, relative as relative15 } from "pathe";
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";
16717
17827
 
16718
17828
  // src/core/package-json.ts
16719
17829
  var toPackageName = (raw) => raw.toLowerCase().replaceAll(/[^a-z0-9._-]+/gu, "-").replaceAll(/^[-_.]+|[-_.]+$/gu, "") || "docs";
@@ -16772,7 +17882,7 @@ var STARTERS = {
16772
17882
  files: (dir) => [
16773
17883
  {
16774
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`."),
16775
- path: join33(dir, "index.mdx")
17885
+ path: join35(dir, "index.mdx")
16776
17886
  }
16777
17887
  ]
16778
17888
  },
@@ -16787,7 +17897,7 @@ var STARTERS = {
16787
17897
  files: (dir) => [
16788
17898
  {
16789
17899
  content: page("Introduction", "Welcome to your new Blume docs.", "# Introduction\n\nWrite your docs here, and log releases under `changelog/`."),
16790
- path: join33(dir, "index.mdx")
17900
+ path: join35(dir, "index.mdx")
16791
17901
  },
16792
17902
  {
16793
17903
  content: `---
@@ -16798,7 +17908,7 @@ date: 2026-01-01
16798
17908
 
16799
17909
  The first release. Edit \`${dir}/changelog/v1-0-0.mdx\` or add new entries beside it.
16800
17910
  `,
16801
- path: join33(dir, "changelog", "v1-0-0.mdx")
17911
+ path: join35(dir, "changelog", "v1-0-0.mdx")
16802
17912
  }
16803
17913
  ]
16804
17914
  },
@@ -16811,7 +17921,7 @@ The first release. Edit \`${dir}/changelog/v1-0-0.mdx\` or add new entries besid
16811
17921
  Welcome to **Blume** — markdown-first docs powered by Astro and Vite.
16812
17922
 
16813
17923
  Edit \`${dir}/index.mdx\` to get started, then run \`blume dev\`.`),
16814
- path: join33(dir, "index.mdx")
17924
+ path: join35(dir, "index.mdx")
16815
17925
  }
16816
17926
  ]
16817
17927
  },
@@ -16822,11 +17932,11 @@ Edit \`${dir}/index.mdx\` to get started, then run \`blume dev\`.`),
16822
17932
  content: page("Introduction", "Get started with the SDK.", `# Introduction
16823
17933
 
16824
17934
  Install the SDK and make your first call. See [Installation](/installation).`),
16825
- path: join33(dir, "index.mdx")
17935
+ path: join35(dir, "index.mdx")
16826
17936
  },
16827
17937
  {
16828
17938
  content: page("Installation", "Install the SDK.", "# Installation\n\n```package-install\nyour-sdk\n```"),
16829
- path: join33(dir, "installation.mdx")
17939
+ path: join35(dir, "installation.mdx")
16830
17940
  }
16831
17941
  ]
16832
17942
  }
@@ -16841,7 +17951,7 @@ var detectPackageManager = (userAgent) => {
16841
17951
  const name = userAgent?.split("/")[0];
16842
17952
  return name !== undefined && PACKAGE_MANAGERS.includes(name) ? name : "npm";
16843
17953
  };
16844
- 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;
16845
17955
  var titleize = (raw) => {
16846
17956
  const words = raw.replaceAll(/[-_.]+/gu, " ").split(/\s+/u).filter(Boolean);
16847
17957
  return words.length === 0 ? "My Docs" : words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
@@ -16918,27 +18028,27 @@ export default defineConfig({
16918
18028
  `;
16919
18029
  var extraDepsFor = (sources) => ({
16920
18030
  ...sources.includes("notion") && { "@notionhq/client": "^2.2.15" },
16921
- ...sources.includes("sanity") && { "@sanity/client": "^6.21.0" }
18031
+ ...sources.includes("sanity") && { "@sanity/client": "^7.25.0" }
16922
18032
  });
16923
18033
  var buildPlan = (root, answers) => {
16924
18034
  const files = [
16925
18035
  {
16926
18036
  content: blumePackageJson(toPackageName(basename5(root)), extraDepsFor(answers.sources)),
16927
- path: join33(root, "package.json")
18037
+ path: join35(root, "package.json")
16928
18038
  },
16929
- { content: buildConfig(answers), path: join33(root, "blume.config.ts") }
18039
+ { content: buildConfig(answers), path: join35(root, "blume.config.ts") }
16930
18040
  ];
16931
18041
  if (answers.sources.length === 0 || answers.sources.includes("filesystem")) {
16932
- files.push(...STARTERS[answers.template].files(answers.contentDir).map((file) => ({ ...file, path: join33(root, file.path) })));
18042
+ files.push(...STARTERS[answers.template].files(answers.contentDir).map((file) => ({ ...file, path: join35(root, file.path) })));
16933
18043
  }
16934
18044
  return files;
16935
18045
  };
16936
18046
  var writeFileSafe = async (file, log) => {
16937
- if (existsSync19(file.path)) {
18047
+ if (existsSync21(file.path)) {
16938
18048
  log.info(`Skipped existing ${file.path}`);
16939
18049
  return false;
16940
18050
  }
16941
- await mkdir8(dirname12(file.path), { recursive: true });
18051
+ await mkdir9(dirname14(file.path), { recursive: true });
16942
18052
  await writeFile11(file.path, file.content, "utf-8");
16943
18053
  log.success(`Created ${file.path}`);
16944
18054
  return true;
@@ -17014,7 +18124,7 @@ var ejectCommand = defineCommand7({
17014
18124
  }
17015
18125
  logger.success(`Ejected ${files.length} file(s):`);
17016
18126
  for (const file of files) {
17017
- process.stdout.write(` ${relative16(root, file)}
18127
+ process.stdout.write(` ${relative17(root, file)}
17018
18128
  `);
17019
18129
  }
17020
18130
  reportDroppedArtifacts(notices);
@@ -17030,9 +18140,9 @@ The blume package remains importable.`);
17030
18140
  });
17031
18141
 
17032
18142
  // src/cli/commands/eval.ts
17033
- import { existsSync as existsSync20 } from "node:fs";
18143
+ import { existsSync as existsSync22 } from "node:fs";
17034
18144
  import { defineCommand as defineCommand8 } from "citty";
17035
- import { join as join37 } from "pathe";
18145
+ import { join as join39 } from "pathe";
17036
18146
 
17037
18147
  // src/eval/prompts.ts
17038
18148
  var readerPrompt = (question) => `You are evaluating whether a product's documentation can answer a user's question.
@@ -17101,7 +18211,7 @@ When you are done, print the file and suggest running \`blume eval\` to try it.`
17101
18211
  // src/eval/report.ts
17102
18212
  import { mkdtemp as mkdtemp2, writeFile as writeFile12 } from "node:fs/promises";
17103
18213
  import { tmpdir as tmpdir2 } from "node:os";
17104
- import { join as join34, relative as relative17 } from "pathe";
18214
+ import { join as join36, relative as relative18 } from "pathe";
17105
18215
  var ESC4 = String.fromCodePoint(27);
17106
18216
  var COLORS3 = {
17107
18217
  bold: `${ESC4}[1m`,
@@ -17182,15 +18292,15 @@ var summaryLine2 = (result) => {
17182
18292
  var headerLine = (total, agent) => `${COLORS3.bold}blume eval${COLORS3.reset} ${total} question(s) · ${AGENTS[agent].name}`;
17183
18293
  var startLine = (id2, index, total) => ` ${COLORS3.dim}▸ ${id2} (${index + 1}/${total})${COLORS3.reset}`;
17184
18294
  var fixLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code !== "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
17185
- const site = finding2.file ? `${relative17(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
18295
+ const site = finding2.file ? `${relative18(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
17186
18296
  return ` ${COLORS3.cyan}fix:${COLORS3.reset} ${site} ${COLORS3.dim}${finding2.message}${COLORS3.reset}`;
17187
18297
  });
17188
18298
  var warningLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code === "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
17189
- const site = finding2.file ? ` ${relative17(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
18299
+ const site = finding2.file ? ` ${relative18(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
17190
18300
  return ` ${COLORS3.yellow}⚠${COLORS3.reset}${site} ${COLORS3.dim}${finding2.message}${COLORS3.reset}`;
17191
18301
  });
17192
18302
  var evalReportJson = (result, root, threshold) => {
17193
- 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);
17194
18304
  return `${JSON.stringify({
17195
18305
  diagnostics,
17196
18306
  eval: {
@@ -17206,21 +18316,21 @@ var evalReportJson = (result, root, threshold) => {
17206
18316
  `;
17207
18317
  };
17208
18318
  var writeEvalReport = async (result, root, threshold) => {
17209
- const dir = await mkdtemp2(join34(tmpdir2(), "blume-eval-"));
17210
- const path = join34(dir, "report.json");
18319
+ const dir = await mkdtemp2(join36(tmpdir2(), "blume-eval-"));
18320
+ const path = join36(dir, "report.json");
17211
18321
  await writeFile12(path, evalReportJson(result, root, threshold));
17212
18322
  return path;
17213
18323
  };
17214
18324
 
17215
18325
  // src/eval/run.ts
17216
- 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";
17217
18327
  import { tmpdir as tmpdir3 } from "node:os";
17218
- import { join as join36 } from "pathe";
18328
+ import { join as join38 } from "pathe";
17219
18329
 
17220
18330
  // src/eval/agents.ts
17221
18331
  import { spawn as spawn2 } from "node:child_process";
17222
- import { readFile as readFile17, writeFile as writeFile13 } from "node:fs/promises";
17223
- import { join as join35 } from "pathe";
18332
+ import { readFile as readFile20, writeFile as writeFile13 } from "node:fs/promises";
18333
+ import { join as join37 } from "pathe";
17224
18334
  import { z as z3 } from "zod";
17225
18335
  var KILL_GRACE_MS = 5000;
17226
18336
  var MCP_TOOL_NAMES = [
@@ -17242,7 +18352,7 @@ var DISALLOWED_TOOLS = [
17242
18352
  "WebSearch",
17243
18353
  "Task"
17244
18354
  ];
17245
- var runAgentHeadless = (bin, args, options) => new Promise((resolve9, reject) => {
18355
+ var runAgentHeadless = (bin, args, options) => new Promise((resolve11, reject) => {
17246
18356
  const platform = options.platform ?? process.platform;
17247
18357
  const child = spawn2(bin, args, {
17248
18358
  cwd: options.cwd,
@@ -17271,12 +18381,12 @@ var runAgentHeadless = (bin, args, options) => new Promise((resolve9, reject) =>
17271
18381
  });
17272
18382
  child.once("close", (code) => {
17273
18383
  clearTimeout(deadline);
17274
- resolve9({ code: code ?? 1, stderr, stdout, timedOut });
18384
+ resolve11({ code: code ?? 1, stderr, stdout, timedOut });
17275
18385
  });
17276
18386
  child.once("exit", (code) => {
17277
18387
  if (timedOut) {
17278
18388
  clearTimeout(deadline);
17279
- resolve9({ code: code ?? 1, stderr, stdout, timedOut });
18389
+ resolve11({ code: code ?? 1, stderr, stdout, timedOut });
17280
18390
  }
17281
18391
  });
17282
18392
  child.stdin.end(options.prompt);
@@ -17286,7 +18396,7 @@ var writeMcpConfig = async (dir, snapshotPath, launcher) => {
17286
18396
  args: [process.argv[1] ?? "", "mcp-stdio", "--data", snapshotPath],
17287
18397
  command: process.execPath
17288
18398
  };
17289
- const configPath = join35(dir, "mcp-config.json");
18399
+ const configPath = join37(dir, "mcp-config.json");
17290
18400
  const config = {
17291
18401
  mcpServers: {
17292
18402
  [MCP_SERVER_NAME]: { args: resolved.args, command: resolved.command }
@@ -17394,17 +18504,17 @@ var readAgentOutput = async (kind, result, lastMessagePath) => {
17394
18504
  text: payload.data.result
17395
18505
  };
17396
18506
  }
17397
- let text;
18507
+ let text2;
17398
18508
  try {
17399
- const raw = await readFile17(lastMessagePath, "utf-8");
17400
- text = raw.trim();
18509
+ const raw = await readFile20(lastMessagePath, "utf-8");
18510
+ text2 = raw.trim();
17401
18511
  } catch {
17402
18512
  return { detail: "no last message written", isError: true, text: "" };
17403
18513
  }
17404
- if (text === "") {
18514
+ if (text2 === "") {
17405
18515
  return { detail: "empty last message", isError: true, text: "" };
17406
18516
  }
17407
- return { isError: false, text };
18517
+ return { isError: false, text: text2 };
17408
18518
  };
17409
18519
  var verdictSchema = z3.object({
17410
18520
  missing: z3.array(z3.string()).default([]),
@@ -17412,15 +18522,15 @@ var verdictSchema = z3.object({
17412
18522
  pass: z3.boolean(),
17413
18523
  score: z3.number().min(0).max(1).optional()
17414
18524
  });
17415
- var parseVerdict = (text) => {
17416
- const start = text.indexOf("{");
17417
- const end = text.lastIndexOf("}");
18525
+ var parseVerdict = (text2) => {
18526
+ const start = text2.indexOf("{");
18527
+ const end = text2.lastIndexOf("}");
17418
18528
  if (start === -1 || end <= start) {
17419
18529
  return;
17420
18530
  }
17421
18531
  let parsed;
17422
18532
  try {
17423
- parsed = JSON.parse(text.slice(start, end + 1));
18533
+ parsed = JSON.parse(text2.slice(start, end + 1));
17424
18534
  } catch {
17425
18535
  return;
17426
18536
  }
@@ -17429,7 +18539,7 @@ var parseVerdict = (text) => {
17429
18539
  };
17430
18540
 
17431
18541
  // src/eval/schema.ts
17432
- import { readFile as readFile18 } from "node:fs/promises";
18542
+ import { readFile as readFile21 } from "node:fs/promises";
17433
18543
  import { load as load2 } from "js-yaml";
17434
18544
  import { z as z4 } from "zod";
17435
18545
  var ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/u;
@@ -17474,7 +18584,7 @@ var describeIssues = (error) => error.issues.map((issue) => {
17474
18584
  var loadEvalsFile = async (path) => {
17475
18585
  let raw;
17476
18586
  try {
17477
- raw = await readFile18(path, "utf-8");
18587
+ raw = await readFile21(path, "utf-8");
17478
18588
  } catch {
17479
18589
  throw new EvalsFileError(path, `No evals file found at ${path}. Run \`blume eval init\` to draft one.`);
17480
18590
  }
@@ -17566,9 +18676,9 @@ var errored = (question, detail, durationMs) => ({
17566
18676
  var runQuestion = async (question, index, context) => {
17567
18677
  const started = performance.now();
17568
18678
  const elapsed = () => Math.round(performance.now() - started);
17569
- const workDir = join36(context.dir, `work-${index}`);
17570
- await mkdir9(workDir, { recursive: true });
17571
- const answerPath = join36(workDir, "answer.txt");
18679
+ const workDir = join38(context.dir, `work-${index}`);
18680
+ await mkdir10(workDir, { recursive: true });
18681
+ const answerPath = join38(workDir, "answer.txt");
17572
18682
  const reader = await context.run(context.bin, agentArgs(context.kind, { lastMessagePath: answerPath, mcp: context.mcp }), {
17573
18683
  cwd: workDir,
17574
18684
  prompt: readerPrompt(question),
@@ -17581,7 +18691,7 @@ var runQuestion = async (question, index, context) => {
17581
18691
  costUsd: answer.costUsd
17582
18692
  };
17583
18693
  }
17584
- const verdictPath = join36(workDir, "verdict.txt");
18694
+ const verdictPath = join38(workDir, "verdict.txt");
17585
18695
  const judge = await context.run(context.bin, agentArgs(context.kind, { lastMessagePath: verdictPath }), {
17586
18696
  cwd: workDir,
17587
18697
  prompt: judgePrompt(question, answer.text),
@@ -17623,8 +18733,8 @@ var runEval = async (options) => {
17623
18733
  const kind = options.agent;
17624
18734
  const run = options.run ?? runAgentHeadless;
17625
18735
  const anchor = { path: options.evalsPath, raw: options.rawEvals };
17626
- const dir = await mkdtemp3(join36(tmpdir3(), "blume-eval-"));
17627
- const snapshotPath = join36(dir, "mcp-data.json");
18736
+ const dir = await mkdtemp3(join38(tmpdir3(), "blume-eval-"));
18737
+ const snapshotPath = join38(dir, "mcp-data.json");
17628
18738
  await writeFile14(snapshotPath, JSON.stringify(await buildMcpData(options.project)));
17629
18739
  const mcp = await writeMcpConfig(dir, snapshotPath);
17630
18740
  const context = {
@@ -17762,8 +18872,8 @@ var runFixHandoff = async (agent, result, root, threshold) => {
17762
18872
  }
17763
18873
  };
17764
18874
  var runInit = async (agent, file) => {
17765
- const path = join37(process.cwd(), file);
17766
- if (existsSync20(path)) {
18875
+ const path = join39(process.cwd(), file);
18876
+ if (existsSync22(path)) {
17767
18877
  logger.error(`${file} already exists — edit it directly, or pass --file to draft elsewhere.`);
17768
18878
  process.exit(1);
17769
18879
  }
@@ -17827,7 +18937,7 @@ var evalCommand = defineCommand8({
17827
18937
  let result;
17828
18938
  try {
17829
18939
  const project = await scanProject(root, { mode: "build" });
17830
- const evalsPath = join37(root, args.file);
18940
+ const evalsPath = join39(root, args.file);
17831
18941
  const { evals, raw } = await loadEvalsFile(evalsPath);
17832
18942
  process.stderr.write(`${headerLine(evals.questions.length, agent)}
17833
18943
 
@@ -17901,10 +19011,10 @@ var evalCommand = defineCommand8({
17901
19011
  // src/cli/commands/init.ts
17902
19012
  import * as clack from "@clack/prompts";
17903
19013
  import { defineCommand as defineCommand9 } from "citty";
17904
- import { resolve as resolve10 } from "pathe";
19014
+ import { resolve as resolve12 } from "pathe";
17905
19015
 
17906
19016
  // src/cli/init/questions.ts
17907
- import { basename as basename6, resolve as resolve9 } from "pathe";
19017
+ import { basename as basename6, resolve as resolve11 } from "pathe";
17908
19018
  var cancelled = (value) => typeof value === "symbol";
17909
19019
  var collectAnswers = async (prompter, flags, defaults) => {
17910
19020
  const directory = flags.directory ?? await prompter.text({
@@ -17915,7 +19025,7 @@ var collectAnswers = async (prompter, flags, defaults) => {
17915
19025
  if (cancelled(directory)) {
17916
19026
  return null;
17917
19027
  }
17918
- const root = resolve9(defaults.cwd, directory);
19028
+ const root = resolve11(defaults.cwd, directory);
17919
19029
  const title = await prompter.text({
17920
19030
  initialValue: titleize(basename6(root)),
17921
19031
  message: "What's your docs site called?",
@@ -18084,7 +19194,7 @@ var initCommand = defineCommand9({
18084
19194
  title: "My Docs"
18085
19195
  };
18086
19196
  }
18087
- const root = resolve10(cwd, answers.directory);
19197
+ const root = resolve12(cwd, answers.directory);
18088
19198
  if (validateContentDir(root, answers.contentDir) !== undefined) {
18089
19199
  logger.error(`Invalid --content-dir "${answers.contentDir}" (must be a path inside the project).`);
18090
19200
  process.exit(1);
@@ -18114,7 +19224,7 @@ var initCommand = defineCommand9({
18114
19224
  });
18115
19225
 
18116
19226
  // src/cli/commands/mcp-stdio.ts
18117
- import { readFile as readFile19 } from "node:fs/promises";
19227
+ import { readFile as readFile22 } from "node:fs/promises";
18118
19228
  import { defineCommand as defineCommand10 } from "citty";
18119
19229
 
18120
19230
  // src/ai/mcp/stdio.ts
@@ -18140,6 +19250,22 @@ var SCHEMA = {
18140
19250
  };
18141
19251
  var BOOST = { description: 2, title: 3 };
18142
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
+ };
18143
19269
  var segmentingTokenizer = (locale) => {
18144
19270
  const language = locale?.toLowerCase().split(/[-_]/u)[0] ?? "";
18145
19271
  if (!SEGMENTED_LANGUAGES.has(language)) {
@@ -18149,16 +19275,32 @@ var segmentingTokenizer = (locale) => {
18149
19275
  return;
18150
19276
  }
18151
19277
  const segmenter = new Intl.Segmenter(language, { granularity: "word" });
19278
+ const bigram = BIGRAM_LANGUAGES.has(language);
18152
19279
  return {
18153
19280
  language,
18154
19281
  normalizationCache: new Map,
18155
19282
  tokenize: (raw) => {
18156
19283
  const tokens = new Set;
19284
+ let run = "";
19285
+ const flush = () => {
19286
+ if (run) {
19287
+ addBigrams(run, tokens);
19288
+ run = "";
19289
+ }
19290
+ };
18157
19291
  for (const segment of segmenter.segment(raw.toLowerCase())) {
18158
- if (segment.isWordLike) {
18159
- tokens.add(segment.segment);
19292
+ if (!segment.isWordLike) {
19293
+ flush();
19294
+ continue;
19295
+ }
19296
+ if (bigram && BIGRAM_SCRIPTS.test(segment.segment)) {
19297
+ run += segment.segment;
19298
+ continue;
18160
19299
  }
19300
+ flush();
19301
+ tokens.add(segment.segment);
18161
19302
  }
19303
+ flush();
18162
19304
  return [...tokens];
18163
19305
  }
18164
19306
  };
@@ -18172,14 +19314,18 @@ var buildOramaIndex = async (documents, locale) => {
18172
19314
  await insertMultiple(db, documents);
18173
19315
  return db;
18174
19316
  };
19317
+ var ALL_TOKENS = 0;
18175
19318
  var queryOramaIndex = async (db, term, limit, locale) => {
18176
- const found = await search(db, {
19319
+ const params = {
18177
19320
  boost: BOOST,
18178
19321
  limit,
18179
19322
  properties: ["title", "description", "content"],
18180
19323
  term,
18181
19324
  ...locale ? { where: { locale: { eq: locale } } } : {}
18182
- });
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);
18183
19329
  return found.hits.map((hit) => hit.document);
18184
19330
  };
18185
19331
 
@@ -18256,13 +19402,13 @@ var excerptFor = (doc) => {
18256
19402
  const head = doc.content.slice(0, EXCERPT_LENGTH).trim();
18257
19403
  return doc.content.length > EXCERPT_LENGTH ? `${head}…` : head;
18258
19404
  };
18259
- var text = (value, isError = false) => ({
19405
+ var text2 = (value, isError = false) => ({
18260
19406
  content: [{ text: value, type: "text" }],
18261
19407
  ...isError ? { isError: true } : {}
18262
19408
  });
18263
19409
  var createIndexProvider = (documents, locale) => {
18264
19410
  let dbPromise = null;
18265
- return () => {
19411
+ return function provideIndex() {
18266
19412
  dbPromise ??= buildOramaIndex(documents, locale);
18267
19413
  return dbPromise;
18268
19414
  };
@@ -18286,18 +19432,18 @@ var buildServer = (data, index) => {
18286
19432
  title: doc.title,
18287
19433
  url: urlFor(doc.route, data)
18288
19434
  }));
18289
- return text(JSON.stringify(results, null, 2));
19435
+ return text2(JSON.stringify(results, null, 2));
18290
19436
  }
18291
19437
  if (name === "get_page") {
18292
19438
  const key = normalizeRoute2(asString2(args.route), data);
18293
19439
  const markdown = data.pages[key];
18294
19440
  if (markdown === undefined) {
18295
- 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);
18296
19442
  }
18297
- return text(markdown);
19443
+ return text2(markdown);
18298
19444
  }
18299
19445
  if (name === "list_pages") {
18300
- return text(JSON.stringify(data.routes.map((route) => ({
19446
+ return text2(JSON.stringify(data.routes.map((route) => ({
18301
19447
  contentType: route.contentType,
18302
19448
  description: route.description,
18303
19449
  lastModified: route.lastModified,
@@ -18307,9 +19453,9 @@ var buildServer = (data, index) => {
18307
19453
  })), null, 2));
18308
19454
  }
18309
19455
  if (name === "get_navigation") {
18310
- return text(JSON.stringify(data.navigation, null, 2));
19456
+ return text2(JSON.stringify(data.navigation, null, 2));
18311
19457
  }
18312
- return text(`Unknown tool: ${name}`, true);
19458
+ return text2(`Unknown tool: ${name}`, true);
18313
19459
  });
18314
19460
  return server;
18315
19461
  };
@@ -18341,7 +19487,7 @@ var mcpStdioCommand = defineCommand10({
18341
19487
  async run({ args }) {
18342
19488
  let data;
18343
19489
  try {
18344
- data = JSON.parse(await readFile19(args.data, "utf-8"));
19490
+ data = JSON.parse(await readFile22(args.data, "utf-8"));
18345
19491
  } catch (error) {
18346
19492
  const detail = error instanceof Error ? error.message : String(error);
18347
19493
  process.stderr.write(`blume mcp-stdio: cannot load the snapshot at ${args.data}: ${detail}
@@ -18353,10 +19499,10 @@ var mcpStdioCommand = defineCommand10({
18353
19499
  });
18354
19500
 
18355
19501
  // src/cli/commands/preview.ts
18356
- import { existsSync as existsSync21 } from "node:fs";
19502
+ import { existsSync as existsSync23 } from "node:fs";
18357
19503
  import { preview } from "astro";
18358
19504
  import { defineCommand as defineCommand11 } from "citty";
18359
- import { join as join38 } from "pathe";
19505
+ import { join as join40 } from "pathe";
18360
19506
  var previewCommand = defineCommand11({
18361
19507
  args: {
18362
19508
  host: { description: "Network host to bind.", type: "string" },
@@ -18370,7 +19516,7 @@ var previewCommand = defineCommand11({
18370
19516
  const root = process.cwd();
18371
19517
  const { config } = await loadConfig(root);
18372
19518
  const context = resolveProjectContext(root, config);
18373
- if (!existsSync21(join38(context.outDir, "astro.config.mjs"))) {
19519
+ if (!existsSync23(join40(context.outDir, "astro.config.mjs"))) {
18374
19520
  logger.error("No build found. Run `blume build` first.");
18375
19521
  process.exit(1);
18376
19522
  }
@@ -18388,7 +19534,7 @@ var previewCommand = defineCommand11({
18388
19534
  // src/cli/commands/sync.ts
18389
19535
  import { rm as rm4 } from "node:fs/promises";
18390
19536
  import { defineCommand as defineCommand12 } from "citty";
18391
- import { join as join39 } from "pathe";
19537
+ import { join as join41 } from "pathe";
18392
19538
  var syncCommand = defineCommand12({
18393
19539
  args: {
18394
19540
  force: {
@@ -18410,7 +19556,7 @@ var syncCommand = defineCommand12({
18410
19556
  if (args.force) {
18411
19557
  const { config } = await loadConfig(root);
18412
19558
  const context = resolveProjectContext(root, config);
18413
- await rm4(join39(context.outDir, "cache"), { force: true, recursive: true });
19559
+ await rm4(join41(context.outDir, "cache"), { force: true, recursive: true });
18414
19560
  logger.info("Cleared source cache.");
18415
19561
  }
18416
19562
  const lock = readDevLock(resolveRuntimeDir(root));
@@ -18428,13 +19574,13 @@ var syncCommand = defineCommand12({
18428
19574
  });
18429
19575
 
18430
19576
  // src/cli/commands/validate.ts
18431
- import { existsSync as existsSync23 } from "node:fs";
19577
+ import { existsSync as existsSync25 } from "node:fs";
18432
19578
  import { defineCommand as defineCommand13 } from "citty";
18433
- import { join as join41 } from "pathe";
19579
+ import { join as join43 } from "pathe";
18434
19580
 
18435
19581
  // src/core/links.ts
18436
- import { existsSync as existsSync22 } from "node:fs";
18437
- 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";
18438
19584
  var HTTP = /^https?:\/\//iu;
18439
19585
  var PROTOCOL_RELATIVE = /^\/\//u;
18440
19586
  var SCHEME = /^[a-z][a-z0-9+.-]*:/iu;
@@ -18447,7 +19593,7 @@ var decodePercent = (value) => {
18447
19593
  };
18448
19594
  var DOC_EXT = /\.(?:md|mdx)$/iu;
18449
19595
  var FILE_EXT = /\.[a-z0-9]+$/iu;
18450
- 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));
18451
19597
  var NUMERIC_PREFIX3 = /^\d+[-_.]/u;
18452
19598
  var isIndexPage = (page2) => /^index\.(?:md|mdx)$/iu.test(basename7(page2.navPath).replace(NUMERIC_PREFIX3, ""));
18453
19599
  var applyRelativePart = (segments, part) => {
@@ -18658,12 +19804,12 @@ var validateCommand = defineCommand13({
18658
19804
  });
18659
19805
  extraRoutes.push(...manifest.routes.flatMap((route) => route.fallback ? [route.path] : []));
18660
19806
  }
18661
- const publicDir = join41(root, "public");
19807
+ const publicDir = join43(root, "public");
18662
19808
  diagnostics.push(...await validateLinks(project.graph, {
18663
19809
  basePath: project.config.basePath,
18664
19810
  checkExternal: Boolean(args.external),
18665
19811
  extraRoutes,
18666
- publicDir: existsSync23(publicDir) ? publicDir : null,
19812
+ publicDir: existsSync25(publicDir) ? publicDir : null,
18667
19813
  redirects: project.config.redirects
18668
19814
  }));
18669
19815
  } catch (error) {
@@ -18727,5 +19873,5 @@ process.on("unhandledRejection", (error) => {
18727
19873
  });
18728
19874
  runMain(main);
18729
19875
 
18730
- //# debugId=0CA37F943036C96964756E2164756E21
19876
+ //# debugId=CCE85D85BA5F467464756E2164756E21
18731
19877
  //# sourceMappingURL=index.js.map