blume 1.4.1 → 1.4.3

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 (93) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/cli/index.js +700 -579
  3. package/dist/cli/index.js.map +57 -56
  4. package/dist/types/core/base-path.d.ts +8 -0
  5. package/dist/types/core/config-input.d.ts +8 -0
  6. package/dist/types/core/data.d.ts +10 -0
  7. package/dist/types/core/schema.d.ts +4 -0
  8. package/dist/types/core/sources/types.d.ts +9 -1
  9. package/dist/types/openapi/references.d.ts +8 -2
  10. package/docs/configuration/ai.mdx +41 -9
  11. package/docs/content/sources.mdx +1 -1
  12. package/package.json +17 -7
  13. package/src/ai/agent-readability.ts +3 -2
  14. package/src/ai/api-catalog.ts +2 -2
  15. package/src/ai/ask-context.ts +45 -12
  16. package/src/ai/link-headers.ts +7 -2
  17. package/src/ai/llms.ts +2 -1
  18. package/src/ai/mcp/discovery.ts +25 -6
  19. package/src/ai/mcp/server.ts +108 -98
  20. package/src/ai/tar.ts +29 -70
  21. package/src/astro/examples.ts +7 -3
  22. package/src/astro/generate.ts +63 -34
  23. package/src/astro/islands.ts +7 -3
  24. package/src/astro/templates.ts +36 -9
  25. package/src/audit/agent.ts +14 -29
  26. package/src/audit/crawl.ts +41 -16
  27. package/src/audit/run.ts +10 -3
  28. package/src/audit/snapshot.ts +27 -2
  29. package/src/cli/commands/audit.ts +12 -17
  30. package/src/cli/commands/build.ts +15 -7
  31. package/src/cli/commands/dev.ts +13 -15
  32. package/src/cli/commands/eject.ts +4 -4
  33. package/src/cli/commands/eval.ts +17 -27
  34. package/src/cli/env.ts +13 -30
  35. package/src/cli/init/scaffold.ts +21 -0
  36. package/src/cli/report-format.ts +22 -0
  37. package/src/components/content/AccordionItem.astro +2 -9
  38. package/src/components/content/ColorItem.astro +5 -13
  39. package/src/components/content/Component.astro +12 -8
  40. package/src/components/content/Frame.astro +2 -12
  41. package/src/components/content/Prompt.astro +12 -31
  42. package/src/components/content/Tab.astro +2 -9
  43. package/src/components/content/Tooltip.astro +1 -9
  44. package/src/components/content/Update.astro +2 -9
  45. package/src/components/content/inline-markdown.ts +28 -0
  46. package/src/components/copy-feedback.ts +96 -0
  47. package/src/components/islands/ask-ai.tsx +78 -9
  48. package/src/components/layout/PageActions.astro +20 -32
  49. package/src/components/layout/PageLayout.astro +8 -28
  50. package/src/components/layout/RootLayout.astro +47 -48
  51. package/src/components/layout/Search.astro +56 -9
  52. package/src/components/layout/drawer-inert.ts +31 -0
  53. package/src/components/layout/search/pagefind.ts +6 -5
  54. package/src/components/layout/search/types.ts +32 -0
  55. package/src/components/openapi/panel.ts +11 -8
  56. package/src/components/raf-throttle.ts +21 -0
  57. package/src/components/slug.ts +14 -0
  58. package/src/core/base-path.ts +18 -1
  59. package/src/core/config-input.ts +8 -0
  60. package/src/core/data.ts +7 -0
  61. package/src/core/frontmatter.ts +45 -1
  62. package/src/core/probe.ts +7 -19
  63. package/src/core/project-graph.ts +12 -1
  64. package/src/core/schema.ts +6 -0
  65. package/src/core/site-url.ts +27 -0
  66. package/src/core/sources/cache.ts +10 -8
  67. package/src/core/sources/github-releases.ts +21 -1
  68. package/src/core/sources/normalize.ts +26 -2
  69. package/src/core/sources/notion.ts +27 -5
  70. package/src/core/sources/portable-text.ts +16 -1
  71. package/src/core/sources/resolve.ts +1 -0
  72. package/src/core/sources/types.ts +13 -1
  73. package/src/deploy/cloudflare-negotiation.ts +15 -1
  74. package/src/deploy/robots.ts +2 -1
  75. package/src/deploy/rss.ts +2 -1
  76. package/src/deploy/sitemap.ts +56 -7
  77. package/src/eval/agents.ts +13 -10
  78. package/src/eval/report.ts +1 -14
  79. package/src/markdown/package-commands.ts +61 -54
  80. package/src/og/card.ts +24 -26
  81. package/src/openapi/model.ts +9 -9
  82. package/src/openapi/parse.ts +69 -28
  83. package/src/openapi/references.ts +35 -12
  84. package/src/openapi/render-mdx.ts +64 -25
  85. package/src/openapi/scalar.ts +2 -2
  86. package/src/openapi/source.ts +28 -1
  87. package/src/search/documents.ts +78 -34
  88. package/src/search/orama-index.ts +51 -12
  89. package/src/theme/palette.ts +6 -2
  90. package/src/translate/ledger.ts +4 -2
  91. package/src/translate/report.ts +1 -14
  92. package/src/translate/run.ts +20 -35
  93. package/src/cli/coalesce.ts +0 -43
package/dist/cli/index.js CHANGED
@@ -537,9 +537,9 @@ Next steps:
537
537
  import { defineCommand as defineCommand2 } from "citty";
538
538
 
539
539
  // src/audit/agent.ts
540
- import { spawn } from "node:child_process";
541
540
  import { mkdtemp, writeFile as writeFile2 } from "node:fs/promises";
542
541
  import { tmpdir } from "node:os";
542
+ import spawn from "cross-spawn";
543
543
  import { join as join5 } from "pathe";
544
544
 
545
545
  // src/audit/report.ts
@@ -1463,24 +1463,43 @@ Work through every finding:
1463
1463
  3. Never fix a finding by deleting a page, removing content, or hiding it from the audit; if a finding genuinely needs a human decision, leave it and say so in your summary.
1464
1464
 
1465
1465
  When you are done, run \`blume build\` and then \`blume audit\` to verify, and repeat until the audit reports no issues.`;
1466
- var spawnAgent = (command, args, shell) => new Promise((resolve2, reject) => {
1467
- const child = spawn(command, args, { shell, stdio: "inherit" });
1466
+ var spawnAgent = (bin, args) => new Promise((resolve2, reject) => {
1467
+ const child = spawn(bin, args, { stdio: "inherit" });
1468
1468
  child.once("error", reject);
1469
1469
  child.once("close", (code) => resolve2(code ?? 1));
1470
1470
  });
1471
- var WINDOWS_COMMAND_NOT_FOUND = 9009;
1472
1471
  var launchAgent = async (bin, prompt, platform = process.platform) => {
1473
1472
  if (platform !== "win32") {
1474
- return await spawnAgent(bin, [prompt], false);
1473
+ return await spawnAgent(bin, [prompt]);
1475
1474
  }
1476
1475
  const dir = await mkdtemp(join5(tmpdir(), "blume-audit-"));
1477
1476
  const promptPath = join5(dir, "prompt.md");
1478
1477
  await writeFile2(promptPath, prompt);
1479
- return await spawnAgent(`"${bin}" "Read ${promptPath} and follow its instructions exactly."`, [], true);
1478
+ return await spawnAgent(bin, [
1479
+ `Read ${promptPath} and follow its instructions exactly.`
1480
+ ]);
1480
1481
  };
1481
1482
 
1482
1483
  // src/audit/run.ts
1483
1484
  import { readFile as readFile4 } from "node:fs/promises";
1485
+ import pMap3 from "p-map";
1486
+
1487
+ // src/core/trim.ts
1488
+ var trimStart = (text, char) => {
1489
+ let start = 0;
1490
+ while (start < text.length && text[start] === char) {
1491
+ start += 1;
1492
+ }
1493
+ return text.slice(start);
1494
+ };
1495
+ var trimEnd = (text, char) => {
1496
+ let end = text.length;
1497
+ while (end > 0 && text[end - 1] === char) {
1498
+ end -= 1;
1499
+ }
1500
+ return text.slice(0, end);
1501
+ };
1502
+ var trimChar = (text, char) => trimEnd(trimStart(text, char), char);
1484
1503
 
1485
1504
  // src/core/base-path.ts
1486
1505
  var normalizeBasePath = (input) => {
@@ -1491,9 +1510,16 @@ var normalizeBasePath = (input) => {
1491
1510
  return trimmed === "" ? "" : `/${trimmed}`;
1492
1511
  };
1493
1512
  var normalizePath = (path) => {
1494
- const trimmed = path.replace(/\/+$/u, "");
1513
+ const trimmed = trimEnd(path, "/");
1495
1514
  return trimmed === "" ? "/" : trimmed;
1496
1515
  };
1516
+ var normalizeRoute = (input) => {
1517
+ const trimmed = trimEnd(input.trim(), "/");
1518
+ if (trimmed === "") {
1519
+ return "/";
1520
+ }
1521
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
1522
+ };
1497
1523
  var isInternalPath = (target) => target.startsWith("/") && !target.startsWith("//");
1498
1524
  var withBasePath = (basePath, route) => {
1499
1525
  if (!basePath || !isInternalPath(route)) {
@@ -1709,7 +1735,28 @@ var withYamlEngine = (options) => ({
1709
1735
  ...options?.engines
1710
1736
  }
1711
1737
  });
1712
- var matter = Object.assign((input, options) => baseMatter(input, withYamlEngine(options)), baseMatter, {
1738
+ var opensWithThematicBreak = (input) => {
1739
+ const [first = "", second] = input.split(/\r?\n/u, 2);
1740
+ if (!/^-{3}\s*$/u.test(first)) {
1741
+ return false;
1742
+ }
1743
+ if (second === undefined || second.trim() === "") {
1744
+ return true;
1745
+ }
1746
+ return !input.includes(`
1747
+ ---`, 1);
1748
+ };
1749
+ var passthrough = (input) => ({
1750
+ content: input,
1751
+ data: {},
1752
+ excerpt: "",
1753
+ isEmpty: false,
1754
+ language: "",
1755
+ matter: "",
1756
+ orig: input,
1757
+ stringify: () => input
1758
+ });
1759
+ var matter = Object.assign((input, options) => typeof input === "string" && opensWithThematicBreak(input) ? passthrough(input) : baseMatter(input, withYamlEngine(options)), baseMatter, {
1713
1760
  read: (filepath, options) => baseMatter.read(filepath, withYamlEngine(options)),
1714
1761
  stringify: (file, data, options) => baseMatter.stringify(file, data, withYamlEngine(options))
1715
1762
  });
@@ -2428,6 +2475,7 @@ var llmsChecks = {
2428
2475
  };
2429
2476
 
2430
2477
  // src/core/probe.ts
2478
+ import pMap from "p-map";
2431
2479
  var PROBE_CONCURRENCY = 8;
2432
2480
  var PROBE_TIMEOUT_MS = 1e4;
2433
2481
  var STATUS_NOT_FOUND = 404;
@@ -2489,20 +2537,8 @@ var gradeExternal = (result) => {
2489
2537
  };
2490
2538
  var probeAll = async (urls, options = {}) => {
2491
2539
  const unique = [...new Set(urls)];
2492
- const results = new Map;
2493
- const limit = Math.min(options.concurrency ?? PROBE_CONCURRENCY, unique.length);
2494
- let cursor = 0;
2495
- const worker = async () => {
2496
- while (cursor < unique.length) {
2497
- const url = unique[cursor];
2498
- cursor += 1;
2499
- if (url !== undefined) {
2500
- results.set(url, await probe(url, options));
2501
- }
2502
- }
2503
- };
2504
- await Promise.all(Array.from({ length: limit }, worker));
2505
- return results;
2540
+ const entries = await pMap(unique, async (url) => [url, await probe(url, options)], { concurrency: Math.max(1, options.concurrency ?? PROBE_CONCURRENCY) });
2541
+ return new Map(entries);
2506
2542
  };
2507
2543
 
2508
2544
  // src/audit/checks/network.ts
@@ -2996,6 +3032,8 @@ var urlChecks = {
2996
3032
  // src/audit/crawl.ts
2997
3033
  import { readFile as readFile3, stat } from "node:fs/promises";
2998
3034
  import { XMLParser } from "fast-xml-parser";
3035
+ import { fromMarkdown } from "mdast-util-from-markdown";
3036
+ import pMap2 from "p-map";
2999
3037
  import { join as join9, relative as relative6 } from "pathe";
3000
3038
  import { glob } from "tinyglobby";
3001
3039
 
@@ -3057,32 +3095,19 @@ var askBackendRuntimeDep = (ask) => {
3057
3095
  return ASK_PRESETS[provider]?.runtimeDep ?? OPENAI_COMPATIBLE_DEP;
3058
3096
  };
3059
3097
 
3060
- // src/core/trim.ts
3061
- var trimStart = (text, char) => {
3062
- let start = 0;
3063
- while (start < text.length && text[start] === char) {
3064
- start += 1;
3065
- }
3066
- return text.slice(start);
3098
+ // src/core/site-url.ts
3099
+ import { joinURL } from "ufo";
3100
+ var siteRoot = (site) => trimEnd(site, "/");
3101
+ var absoluteUrl = (site, path) => {
3102
+ const root = siteRoot(site);
3103
+ return path === "/" ? `${root}/` : joinURL(root, path);
3067
3104
  };
3068
- var trimEnd = (text, char) => {
3069
- let end = text.length;
3070
- while (end > 0 && text[end - 1] === char) {
3071
- end -= 1;
3072
- }
3073
- return text.slice(0, end);
3074
- };
3075
- var trimChar = (text, char) => trimEnd(trimStart(text, char), char);
3076
3105
 
3077
3106
  // src/openapi/references.ts
3078
- var NON_SLUG = /[^a-z0-9]+/gu;
3079
- var slugify = (text) => trimChar(text.toLowerCase().replace(NON_SLUG, "-"), "-");
3080
- var normalizeRoute = (route) => {
3081
- const trimmed = route.trim();
3082
- const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
3083
- const noTrailing = trimEnd(withSlash, "/");
3084
- return noTrailing === "" ? "/" : noTrailing;
3085
- };
3107
+ var NON_SLUG = /[^\p{L}\p{M}\p{N}]+/gu;
3108
+ var FORMAT_CHARS = /\p{Cf}/gu;
3109
+ var LEADING_MARKS = /^\p{M}+/u;
3110
+ var slugify = (text) => trimChar(text.normalize("NFC").toLowerCase().replace(FORMAT_CHARS, "").replace(NON_SLUG, "-"), "-").replace(LEADING_MARKS, "");
3086
3111
  var routeSlug = (route) => slugify(trimChar(route, "/")) || "reference";
3087
3112
  var sourcesOf = (block) => {
3088
3113
  const sources = [...block.sources];
@@ -3182,7 +3207,7 @@ var linksetEntries = (config) => {
3182
3207
  const deployBase = normalizeBasePath(config.deployment.base);
3183
3208
  const abs = (path) => {
3184
3209
  const based = withBasePath(deployBase, path);
3185
- return site ? `${trimEnd(site, "/")}${based}` : based;
3210
+ return site ? absoluteUrl(site, based) : based;
3186
3211
  };
3187
3212
  const entries = [];
3188
3213
  for (const reference of resolveReferences(config)) {
@@ -3989,7 +4014,11 @@ ${stagedBlock}
3989
4014
  export const collections = { docs${options.staged ? ", staged" : ""} };
3990
4015
  `;
3991
4016
  };
3992
- var askEndpointTemplate = (backend, grounded) => {
4017
+ var ASK_FALLBACK_PROMPT = "You are a helpful documentation assistant. Answer using the project's documentation.";
4018
+ var askEndpointTemplate = (backend, grounded, instructions) => {
4019
+ const fallbackPrompt = instructions ? `${ASK_FALLBACK_PROMPT}
4020
+
4021
+ ${instructions}` : ASK_FALLBACK_PROMPT;
3993
4022
  const imports = [
3994
4023
  'import type { APIRoute } from "astro";',
3995
4024
  'import { streamText } from "ai";'
@@ -4015,8 +4044,9 @@ const provider = createOpenAICompatible({
4015
4044
  }
4016
4045
  if (grounded) {
4017
4046
  imports.push('import { createAskContext } from "blume/ai/ask-context.ts";', 'import askData from "../../generated/ask-data.json";');
4047
+ const groundOptions = instructions ? `, { instructions: ${JSON.stringify(instructions)} }` : "";
4018
4048
  setup += `
4019
- const ground = createAskContext(askData);
4049
+ const ground = createAskContext(askData${groundOptions});
4020
4050
  `;
4021
4051
  }
4022
4052
  const validate = ` const body = await request.json().catch(() => null);
@@ -4061,7 +4091,7 @@ const ground = createAskContext(askData);
4061
4091
  },`;
4062
4092
  const stream = grounded ? ` const instructions =
4063
4093
  (await ground(messages, body.page)) ??
4064
- "You are a helpful documentation assistant. Answer using the project's documentation.";
4094
+ ${JSON.stringify(fallbackPrompt)};
4065
4095
  const result = streamText({
4066
4096
  model: ${modelExpr},
4067
4097
  instructions,
@@ -4070,7 +4100,7 @@ ${onError}
4070
4100
  });` : ` const result = streamText({
4071
4101
  model: ${modelExpr},
4072
4102
  instructions:
4073
- "You are a helpful documentation assistant. Answer using the project's documentation.",
4103
+ ${JSON.stringify(fallbackPrompt)},
4074
4104
  messages,
4075
4105
  ${onError}
4076
4106
  });`;
@@ -4252,7 +4282,7 @@ export function GET({ props }: { props: { route: string } }) {
4252
4282
  var contentAssetsEndpointTemplate = (stagedAssetsDir) => `// Generated by Blume. Do not edit.
4253
4283
  import { existsSync } from "node:fs";
4254
4284
  import { readdir, readFile } from "node:fs/promises";
4255
- import { join, relative, resolve } from "node:path";
4285
+ import { isAbsolute, join, relative, resolve } from "node:path";
4256
4286
  import type { APIRoute } from "astro";
4257
4287
  import assets from "../../generated/content-assets.json";
4258
4288
 
@@ -4312,7 +4342,13 @@ const resolveAsset = (asset: string): string | null => {
4312
4342
  const abs = resolve(STAGED_DIR, asset);
4313
4343
  // Traversal guard: the dev server renders on demand, so the param is
4314
4344
  // attacker-controlled there — never step outside the staged directory.
4315
- if (!abs.startsWith(STAGED_DIR + "/")) {
4345
+ // path.relative rather than a string-prefix test: STAGED_DIR is baked in
4346
+ // with forward slashes while resolve() answers in the platform's
4347
+ // separators, so on Windows the prefix test 404'd every legitimate staged
4348
+ // asset — and a bare prefix also admits a sibling directory that merely
4349
+ // shares the name.
4350
+ const rel = relative(STAGED_DIR, abs);
4351
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
4316
4352
  return null;
4317
4353
  }
4318
4354
  return existsSync(abs) ? abs : null;
@@ -4617,8 +4653,11 @@ const frontmatter = entry.data ?? {};
4617
4653
  const seo = frontmatter.seo ?? {};
4618
4654
  const base = data.config.site ? data.config.site.replace(/\\/$/, "") : null;
4619
4655
 
4656
+ // Percent-encode the route-derived path (the sitemap convention): a Unicode
4657
+ // slug (\`/api/größe\`) is not legal in a raw URI, and crawlers compare
4658
+ // canonical against the sitemap's encoded <loc> byte-for-byte.
4620
4659
  const ogPath = data.config.og.enabled
4621
- ? \`/og/\${route === "/" ? "index" : route.slice(1)}.png\`
4660
+ ? encodeURI(\`/og/\${route === "/" ? "index" : route.slice(1)}.png\`)
4622
4661
  : null;
4623
4662
  const ogRel = seo.image ?? ogPath;
4624
4663
  // Absolute URLs also carry the deployment base (the page is served under it):
@@ -4637,7 +4676,7 @@ const x = { ...data.config.x, ...(seo.x?.creator ? { creator: seo.x.creator } :
4637
4676
  const basedRoute = withBase(route);
4638
4677
  const canonical =
4639
4678
  seo.canonical ??
4640
- (base ? \`\${base}\${basedRoute === "/" ? "" : basedRoute}\` : null);
4679
+ (base ? \`\${base}\${basedRoute === "/" ? "" : encodeURI(basedRoute)}\` : null);
4641
4680
 
4642
4681
  // Locale resolution. With i18n on, pick the active locale's nav + dictionary,
4643
4682
  // build hreflang alternates, and derive the language-switcher targets.
@@ -4738,6 +4777,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
4738
4777
  exportPdf={${options.exportPdf}}
4739
4778
  exportEpub={${options.exportEpub}}
4740
4779
  feeds={data.feeds}
4780
+ discovery={data.config.discovery}
4741
4781
  siteUrl={data.config.site}
4742
4782
  pageType={frontmatter.type}
4743
4783
  published={frontmatter.date ?? frontmatter.changelog?.date ?? null}
@@ -4940,6 +4980,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
4940
4980
  exportPdf={${options.exportPdf}}
4941
4981
  exportEpub={${options.exportEpub}}
4942
4982
  feeds={data.feeds}
4983
+ discovery={data.config.discovery}
4943
4984
  siteUrl={data.config.site}
4944
4985
  noindex={false}
4945
4986
  structuredDataEnabled={data.config.structuredData}
@@ -5280,6 +5321,21 @@ var visibleText = (element) => {
5280
5321
 
5281
5322
  // src/audit/snapshot.ts
5282
5323
  var CHROME = "nav, aside, header, footer";
5324
+ var countWords = (prose, lang) => {
5325
+ let segmenter;
5326
+ try {
5327
+ segmenter = new Intl.Segmenter(lang ?? undefined, { granularity: "word" });
5328
+ } catch {
5329
+ segmenter = new Intl.Segmenter(undefined, { granularity: "word" });
5330
+ }
5331
+ let count = 0;
5332
+ for (const segment of segmenter.segment(prose)) {
5333
+ if (segment.isWordLike) {
5334
+ count += 1;
5335
+ }
5336
+ }
5337
+ return count;
5338
+ };
5283
5339
  var CONTENT_ROOTS = ["main", "article", "body"];
5284
5340
  var contentRoot = (document) => {
5285
5341
  for (const selector of CONTENT_ROOTS) {
@@ -5339,6 +5395,7 @@ var buildSnapshot = (options) => {
5339
5395
  const root = contentRoot(document);
5340
5396
  const prose = root ? visibleText(root) : "";
5341
5397
  const robots = document.querySelector('meta[name="robots"]');
5398
+ const lang = attr(document.querySelector("html") ?? document, "lang");
5342
5399
  const { jsonld, jsonldErrors } = collectJsonLd(document);
5343
5400
  return {
5344
5401
  bytes: Buffer.byteLength(html, "utf-8"),
@@ -5359,7 +5416,7 @@ var buildSnapshot = (options) => {
5359
5416
  indexable: !robots?.getAttribute("content")?.includes("noindex"),
5360
5417
  jsonld,
5361
5418
  jsonldErrors,
5362
- lang: attr(document.querySelector("html") ?? document, "lang"),
5419
+ lang,
5363
5420
  links,
5364
5421
  metaRefresh: document.querySelector('meta[http-equiv="refresh" i]')?.getAttribute("content")?.trim() ?? null,
5365
5422
  og: prefixedMeta(document, "property", "og:"),
@@ -5372,12 +5429,13 @@ var buildSnapshot = (options) => {
5372
5429
  twitter: prefixedMeta(document, "name", "twitter:"),
5373
5430
  url,
5374
5431
  viewport: document.querySelector('meta[name="viewport"]')?.getAttribute("content")?.trim() ?? null,
5375
- wordCount: prose ? prose.split(/\s+/u).length : 0
5432
+ wordCount: countWords(prose, lang)
5376
5433
  };
5377
5434
  };
5378
5435
 
5379
5436
  // src/audit/crawl.ts
5380
5437
  var EXAMPLES_PREFIX = `${examplesRouteBase("")}/`;
5438
+ var CRAWL_CONCURRENCY = 16;
5381
5439
  var fileToUrl = (staticDir, file) => {
5382
5440
  const rel = relative6(staticDir, file).replaceAll("\\", "/");
5383
5441
  const path = rel.replace(/(?:^|\/)index\.html$/u, "").replace(/\.html$/u, "");
@@ -5385,11 +5443,11 @@ var fileToUrl = (staticDir, file) => {
5385
5443
  };
5386
5444
  var indexFiles = async (staticDir) => {
5387
5445
  const found = await glob("**/*", { cwd: staticDir, dot: true });
5388
- const sized = await Promise.all(found.map(async (file) => {
5446
+ const sized = await pMap2(found, async (file) => {
5389
5447
  const path = `/${file.replaceAll("\\", "/")}`;
5390
5448
  const info = await stat(join9(staticDir, file));
5391
5449
  return [path, info.size];
5392
- }));
5450
+ }, { concurrency: CRAWL_CONCURRENCY });
5393
5451
  return new Map(sized);
5394
5452
  };
5395
5453
  var routeIndex = (manifest, basePath) => {
@@ -5442,15 +5500,17 @@ var parseSitemap = (file, xml, bytes) => {
5442
5500
  };
5443
5501
  var parseLlms = (file, text) => {
5444
5502
  const entries = [];
5445
- const link = /\]\((?<url>[^)\s]+)\)/gu;
5446
- for (const [index, line] of text.split(/\r?\n/u).entries()) {
5447
- for (const match of line.matchAll(link)) {
5448
- const url = match.groups?.url;
5449
- if (url) {
5450
- entries.push({ line: index + 1, url });
5503
+ const collect = (node) => {
5504
+ if ((node.type === "link" || node.type === "image" || node.type === "definition") && node.url) {
5505
+ entries.push({ line: node.position?.start.line ?? 1, url: node.url });
5506
+ }
5507
+ if ("children" in node) {
5508
+ for (const child of node.children) {
5509
+ collect(child);
5451
5510
  }
5452
5511
  }
5453
- }
5512
+ };
5513
+ collect(fromMarkdown(text));
5454
5514
  return { entries, file };
5455
5515
  };
5456
5516
  var ROBOTS_DIRECTIVE = /^(?<field>[a-z-]+)\s*:\s*(?<value>.*)$/iu;
@@ -5486,7 +5546,7 @@ var crawlStaticDir = async (options) => {
5486
5546
  const { staticDir, manifest, basePath } = options;
5487
5547
  const routes = routeIndex(manifest, basePath);
5488
5548
  const htmlFiles = await glob("**/*.html", { absolute: true, cwd: staticDir });
5489
- const snapshots = await Promise.all(htmlFiles.toSorted().map(async (file) => {
5549
+ const snapshots = await pMap2(htmlFiles.toSorted(), async (file) => {
5490
5550
  const url = fileToUrl(staticDir, file);
5491
5551
  if (stripBasePath(basePath, url).startsWith(EXAMPLES_PREFIX)) {
5492
5552
  return null;
@@ -5501,7 +5561,7 @@ var crawlStaticDir = async (options) => {
5501
5561
  route: routes.get(url) ?? routes.get(stripBasePath(basePath, url)),
5502
5562
  url
5503
5563
  });
5504
- }));
5564
+ }, { concurrency: CRAWL_CONCURRENCY });
5505
5565
  const pages = snapshots.filter((page) => page !== null);
5506
5566
  const sitemapFile = join9(staticDir, "sitemap.xml");
5507
5567
  const sitemapXml = await readIfPresent(sitemapFile);
@@ -5598,17 +5658,18 @@ class NoBuildError extends Error {
5598
5658
  this.staticDir = staticDir;
5599
5659
  }
5600
5660
  }
5661
+ var READ_CONCURRENCY = 16;
5601
5662
  var readSources = async (pages) => {
5602
5663
  const paths = [
5603
5664
  ...new Set(pages.flatMap((page) => page.source ? [page.source] : []))
5604
5665
  ];
5605
- const entries = await Promise.all(paths.map(async (path) => {
5666
+ const entries = await pMap3(paths, async (path) => {
5606
5667
  try {
5607
5668
  return [path, await readFile4(path, "utf-8")];
5608
5669
  } catch {
5609
5670
  return null;
5610
5671
  }
5611
- }));
5672
+ }, { concurrency: READ_CONCURRENCY });
5612
5673
  return new Map(entries.filter((entry) => entry !== null));
5613
5674
  };
5614
5675
  var matches = (id, terms) => {
@@ -9518,6 +9579,7 @@ var sanitySourceSchema = z2.object({
9518
9579
  type: z2.literal("sanity")
9519
9580
  });
9520
9581
  var notionSourceSchema = z2.object({
9582
+ concurrency: z2.number().positive().optional(),
9521
9583
  database: z2.string(),
9522
9584
  pollInterval: z2.number().positive().optional(),
9523
9585
  prefix: z2.string().optional(),
@@ -9773,6 +9835,7 @@ var aiConfigSchema = z2.strictObject({
9773
9835
  baseUrl: z2.url().optional(),
9774
9836
  enabled: z2.boolean().default(false),
9775
9837
  endpoint: askEndpointSchema.optional(),
9838
+ instructions: z2.string().trim().min(1).optional(),
9776
9839
  model: z2.string().default("openai/gpt-5.5"),
9777
9840
  provider: z2.enum(askAiProviders).default("gateway"),
9778
9841
  suggestions: z2.array(z2.strictObject({
@@ -11230,9 +11293,10 @@ var GROUP_FOLDER2 = /^\((?<label>.+)\)$/u;
11230
11293
  var WORD_SPLIT2 = /[-_]/u;
11231
11294
  var stripNumericPrefix = (segment) => segment.replace(NUMERIC_PREFIX2, "");
11232
11295
  var groupLabel = (segment) => segment.match(GROUP_FOLDER2)?.groups?.label ?? null;
11233
- var slugify2 = (text) => text.toLowerCase().trim().replaceAll(/[^\w\s-]/gu, "").replaceAll(/[\s_]+/gu, "-").replaceAll(/-+/gu, "-").replaceAll(/^-|-$/gu, "");
11296
+ var slugify2 = (text) => text.normalize("NFC").toLowerCase().trim().replaceAll(/[^\p{L}\p{M}\p{N}\s_-]/gu, "").replaceAll(/[\s_]+/gu, "-").replaceAll(/-+/gu, "-").replaceAll(/^-|-$/gu, "");
11234
11297
  var slugifyPath = (text) => text.split("/").map(slugify2).filter(Boolean).join("/");
11235
11298
  var titleCase = (value) => value.split(WORD_SPLIT2).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
11299
+ var sanitizeSegment = (segment) => segment.replaceAll(/[:\p{Cc}]/gu, "");
11236
11300
  var addRouteSegment = (part, segments, groups) => {
11237
11301
  if (part === "") {
11238
11302
  return;
@@ -11246,7 +11310,11 @@ var addRouteSegment = (part, segments, groups) => {
11246
11310
  if (clean === "index") {
11247
11311
  return;
11248
11312
  }
11249
- segments.push(clean);
11313
+ const safe = sanitizeSegment(clean);
11314
+ if (safe === "") {
11315
+ return;
11316
+ }
11317
+ segments.push(safe);
11250
11318
  };
11251
11319
  var mapRoute = (relativePath) => {
11252
11320
  const withoutExt = relativePath.slice(0, relativePath.length - extname2(relativePath).length);
@@ -11576,15 +11644,10 @@ var normalizeEntry = (entry, ctx) => {
11576
11644
  import { isAbsolute as isAbsolute5, join as join17, resolve as resolve4 } from "pathe";
11577
11645
 
11578
11646
  // src/core/sources/cache.ts
11647
+ import { createHash as createHash2 } from "node:crypto";
11579
11648
  import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
11580
11649
  import { join as join11 } from "pathe";
11581
- var hashText = (text) => {
11582
- let hash = 5381;
11583
- for (let i = 0;i < text.length; i += 1) {
11584
- hash = (hash * 33 + (text.codePointAt(i) ?? 0)) % 2147483647;
11585
- }
11586
- return hash.toString(36);
11587
- };
11650
+ var hashText = (text) => createHash2("sha256").update(text).digest("hex").slice(0, 16);
11588
11651
  var entriesDigest = (entries) => hashText(entries.map((entry) => `${entry.ref}:${entry.hash ?? hashText(entry.body.text)}`).join("|"));
11589
11652
  var pollingWatch = (load2, intervalSeconds, seed) => (onChange) => {
11590
11653
  let last = "";
@@ -11661,9 +11724,6 @@ var loadWithCache = async (name, cache, fetchEntries, refresh = true) => {
11661
11724
  };
11662
11725
 
11663
11726
  // src/openapi/model.ts
11664
- var NON_SLUG2 = /[^a-z0-9]+/gu;
11665
- var SLUG_EDGES = /^-+|-+$/gu;
11666
- var slugify3 = (text) => text.toLowerCase().replace(NON_SLUG2, "-").replace(SLUG_EDGES, "");
11667
11727
  var HTTP_METHODS = [
11668
11728
  "get",
11669
11729
  "put",
@@ -11676,8 +11736,8 @@ var HTTP_METHODS = [
11676
11736
  ];
11677
11737
  var UNTAGGED = "Operations";
11678
11738
  var operationKey = (method, path, operationId) => {
11679
- const fromId = operationId ? slugify3(operationId) : "";
11680
- return fromId || slugify3(`${method}-${path}`);
11739
+ const fromId = operationId ? slugify(operationId) : "";
11740
+ return fromId || slugify(`${method}-${path}`);
11681
11741
  };
11682
11742
  var isOperation = (value) => typeof value === "object" && value !== null;
11683
11743
  var tagSlugger = () => {
@@ -11688,7 +11748,7 @@ var tagSlugger = () => {
11688
11748
  if (existing) {
11689
11749
  return existing;
11690
11750
  }
11691
- const base = slugify3(name) || "operations";
11751
+ const base = slugify(name) || "operations";
11692
11752
  let slug = base;
11693
11753
  for (let suffix = 2;taken.has(slug); suffix += 1) {
11694
11754
  slug = `${base}-${suffix}`;
@@ -11757,6 +11817,7 @@ var extractOperations = (document, baseRoute) => {
11757
11817
  import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
11758
11818
  import { setTimeout as sleep } from "node:timers/promises";
11759
11819
  import { normalize as normalize2, upgrade } from "@scalar/openapi-parser";
11820
+ import pRetry, { AbortError } from "p-retry";
11760
11821
  import { isAbsolute as isAbsolute3, join as join12 } from "pathe";
11761
11822
  var URL_SPEC = /^https?:\/\//u;
11762
11823
  var FETCH_TIMEOUT_MS = 15000;
@@ -11794,8 +11855,15 @@ var ensureProxyDispatcher = async () => {
11794
11855
  };
11795
11856
  var retryAfterMs = (response) => {
11796
11857
  const header = response.headers.get("retry-after");
11797
- const seconds = header ? Number(header) : Number.NaN;
11798
- return Number.isFinite(seconds) && seconds > 0 ? seconds * SECOND_MS : undefined;
11858
+ if (!header) {
11859
+ return;
11860
+ }
11861
+ const seconds = Number(header);
11862
+ if (Number.isFinite(seconds)) {
11863
+ return seconds > 0 ? seconds * SECOND_MS : undefined;
11864
+ }
11865
+ const delta = Date.parse(header) - Date.now();
11866
+ return Number.isFinite(delta) && delta > 0 ? delta : undefined;
11799
11867
  };
11800
11868
  var attemptFetch = async (spec) => {
11801
11869
  try {
@@ -11822,25 +11890,37 @@ var attemptFetch = async (spec) => {
11822
11890
  };
11823
11891
  }
11824
11892
  };
11893
+ var retryableFetchError = (error, retryAfter) => {
11894
+ const wrapper = new Error(error.message, {
11895
+ cause: error
11896
+ });
11897
+ wrapper.name = "RetryableFetchError";
11898
+ wrapper.retryAfter = retryAfter;
11899
+ return wrapper;
11900
+ };
11825
11901
  var fetchSpecText = async (spec) => {
11826
11902
  await ensureProxyDispatcher();
11827
- let last = {
11828
- error: new Error(`Could not fetch ${spec}`),
11829
- retryable: false
11830
- };
11831
- for (let attempt = 0;attempt < MAX_ATTEMPTS; attempt += 1) {
11832
- last = await attemptFetch(spec);
11833
- if ("text" in last) {
11834
- return last.text;
11835
- }
11836
- if (!last.retryable) {
11837
- break;
11838
- }
11839
- if (attempt < MAX_ATTEMPTS - 1) {
11840
- await sleep(Math.min(last.retryAfter ?? BASE_BACKOFF_MS * 2 ** attempt, MAX_RETRY_WAIT_MS));
11841
- }
11842
- }
11843
- throw last.error;
11903
+ return await pRetry(async () => {
11904
+ const attempt = await attemptFetch(spec);
11905
+ if ("text" in attempt) {
11906
+ return attempt.text;
11907
+ }
11908
+ if (!attempt.retryable) {
11909
+ throw new AbortError(attempt.error);
11910
+ }
11911
+ throw retryableFetchError(attempt.error, attempt.retryAfter);
11912
+ }, {
11913
+ factor: 2,
11914
+ maxTimeout: MAX_RETRY_WAIT_MS,
11915
+ minTimeout: BASE_BACKOFF_MS,
11916
+ onFailedAttempt: async (context) => {
11917
+ const { retryAfter } = context.error;
11918
+ if (retryAfter !== undefined && context.retriesLeft > 0) {
11919
+ await sleep(Math.max(0, Math.min(retryAfter, MAX_RETRY_WAIT_MS) - context.retryDelay));
11920
+ }
11921
+ },
11922
+ retries: MAX_ATTEMPTS - 1
11923
+ });
11844
11924
  };
11845
11925
  var cacheFileFor = (cacheDir, spec) => join12(cacheDir, `spec-${hashText(spec)}.cache`);
11846
11926
  var readCache = async (file) => {
@@ -11900,6 +11980,8 @@ var parseSpec = async (spec, root, options = {}) => {
11900
11980
  };
11901
11981
 
11902
11982
  // src/openapi/render-mdx.ts
11983
+ import { fromMarkdown as fromMarkdown2 } from "mdast-util-from-markdown";
11984
+ import { toString as mdastToString } from "mdast-util-to-string";
11903
11985
  var MDX_UNSAFE = /[<{}]/gu;
11904
11986
  var ENTITIES = {
11905
11987
  "<": "&lt;",
@@ -11907,26 +11989,46 @@ var ENTITIES = {
11907
11989
  "}": "&#125;"
11908
11990
  };
11909
11991
  var MDX_ESM_KEYWORD = /^(?<keyword>import|export)\b/gmu;
11910
- var BACKTICK_CODE = /(?<!`)(?<bt>`+)(?!`)[\s\S]*?(?<!`)\k<bt>(?!`)/gu;
11911
11992
  var escapeProse = (text) => text.replace(MDX_UNSAFE, (char) => ENTITIES[char] ?? char).replace(MDX_ESM_KEYWORD, (keyword) => `&#${keyword.codePointAt(0)};${keyword.slice(1)}`);
11993
+ var codeSpans = (text) => {
11994
+ const spans = [];
11995
+ const collect = (node) => {
11996
+ if (node.type === "inlineCode" || node.type === "code") {
11997
+ const start = node.position?.start.offset ?? -1;
11998
+ const end = node.position?.end.offset ?? -1;
11999
+ const head = text.slice(Math.max(start, 0), Math.max(end, 0)).trimStart();
12000
+ if (start >= 0 && (node.type === "inlineCode" || head.startsWith("`") || head.startsWith("~"))) {
12001
+ spans.push([start, end]);
12002
+ }
12003
+ return;
12004
+ }
12005
+ if ("children" in node) {
12006
+ for (const child of node.children) {
12007
+ collect(child);
12008
+ }
12009
+ }
12010
+ };
12011
+ collect(fromMarkdown2(text));
12012
+ return spans;
12013
+ };
11912
12014
  var mdxSafe = (text) => {
11913
12015
  let out = "";
11914
12016
  let cursor = 0;
11915
- for (const match of text.matchAll(BACKTICK_CODE)) {
11916
- const start = match.index ?? 0;
12017
+ for (const [start, end] of codeSpans(text)) {
11917
12018
  out += escapeProse(text.slice(cursor, start));
11918
- out += match[0];
11919
- cursor = start + match[0].length;
12019
+ out += text.slice(start, end);
12020
+ cursor = end;
11920
12021
  }
11921
12022
  return out + escapeProse(text.slice(cursor));
11922
12023
  };
11923
12024
  var META_DESCRIPTION_MAX = 160;
11924
- var PARAGRAPH_BREAK = /\n\s*\n/u;
11925
- var MARKDOWN_LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
11926
- var MARKDOWN_MARKS = /[*_`#>]/gu;
11927
12025
  var WHITESPACE = /\s+/gu;
11928
12026
  var TRAILING_WORD = /\s+\S*$/u;
11929
- var plainProse = (markdown) => (markdown.trim().split(PARAGRAPH_BREAK).at(0) ?? "").replace(MARKDOWN_LINK, "$<text>").replace(MARKDOWN_MARKS, "").replace(WHITESPACE, " ").trim();
12027
+ var plainProse = (markdown) => {
12028
+ const tree = fromMarkdown2(markdown);
12029
+ const first = tree.children.find((node) => node.type === "paragraph") ?? tree.children[0];
12030
+ return first ? mdastToString(first).replace(WHITESPACE, " ").trim() : "";
12031
+ };
11930
12032
  var clip = (text, max) => {
11931
12033
  if (max <= 0) {
11932
12034
  return "";
@@ -12045,6 +12147,13 @@ var specEntries = (spec, operations, reference) => {
12045
12147
  entries.push(toEntry(overviewMdx(spec, reference), base ? `${base}/index.mdx` : "index.mdx"));
12046
12148
  return entries;
12047
12149
  };
12150
+ var tagFolderMeta = (spec, tags) => {
12151
+ const base = routeToRef(spec.route);
12152
+ return Object.fromEntries(tags.map((tag) => [
12153
+ base ? `${base}/${tag.slug}` : tag.slug,
12154
+ { title: tag.name }
12155
+ ]));
12156
+ };
12048
12157
  var openApiSource = (references, ctx) => {
12049
12158
  let parsed = {};
12050
12159
  const loadReference = async (reference) => {
@@ -12097,6 +12206,7 @@ var openApiSource = (references, ctx) => {
12097
12206
  ] : []
12098
12207
  ],
12099
12208
  entries: specEntries(spec, operations, reference),
12209
+ folderMeta: tagFolderMeta(spec, tags),
12100
12210
  slug: reference.slug,
12101
12211
  spec
12102
12212
  };
@@ -12118,6 +12228,7 @@ var openApiSource = (references, ctx) => {
12118
12228
  severity: "warning"
12119
12229
  })));
12120
12230
  const data = {};
12231
+ const folderMeta = {};
12121
12232
  for (const result of results) {
12122
12233
  if ("severity" in result) {
12123
12234
  diagnostics.push(result);
@@ -12125,10 +12236,11 @@ var openApiSource = (references, ctx) => {
12125
12236
  }
12126
12237
  data[result.slug] = result.spec;
12127
12238
  entries.push(...result.entries);
12239
+ Object.assign(folderMeta, result.folderMeta);
12128
12240
  diagnostics.push(...result.diagnostics);
12129
12241
  }
12130
12242
  parsed = data;
12131
- return { diagnostics, entries };
12243
+ return { diagnostics, entries, folderMeta };
12132
12244
  };
12133
12245
  return {
12134
12246
  kind: "openapi-source",
@@ -12204,15 +12316,15 @@ var filesystemSource = (options) => {
12204
12316
  };
12205
12317
 
12206
12318
  // src/core/sources/github-releases.ts
12207
- import { fromMarkdown } from "mdast-util-from-markdown";
12319
+ import { fromMarkdown as fromMarkdown3 } from "mdast-util-from-markdown";
12208
12320
  import { gfmFromMarkdown } from "mdast-util-gfm";
12209
- import { toString as mdastToString } from "mdast-util-to-string";
12321
+ import { toString as mdastToString2 } from "mdast-util-to-string";
12210
12322
  import { gfm } from "micromark-extension-gfm";
12211
12323
  var DEFAULT_BASE_URL = "https://api.github.com";
12212
12324
  var DEFAULT_LIMIT = 100;
12213
12325
  var PER_PAGE = 100;
12214
12326
  var LEADING_V = /^v/iu;
12215
- var NON_SLUG3 = /[^a-z0-9]+/gu;
12327
+ var NON_SLUG2 = /[^a-z0-9]+/gu;
12216
12328
  var EDGE_DASHES = /^-+|-+$/gu;
12217
12329
  var DESCRIPTION_MAX = 160;
12218
12330
  var DESCRIPTION_MIN = 110;
@@ -12220,24 +12332,35 @@ var CHANGESET_HASH = /^(?<mark>\s*(?:[-*+]|\d+[.)])\s+)[0-9a-f]{7,40}:\s+/gmu;
12220
12332
  var WHITESPACE2 = /\s+/gu;
12221
12333
  var TRAILING_FRAGMENT = /[\s,;:.—–-]+$/u;
12222
12334
  var NON_PROSE = new Set(["code", "heading", "html", "thematicBreak"]);
12335
+ var graphemePrefix = (text, max) => {
12336
+ let end = 0;
12337
+ const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
12338
+ for (const { index, segment } of graphemes.segment(text)) {
12339
+ if (index + segment.length > max) {
12340
+ break;
12341
+ }
12342
+ end = index + segment.length;
12343
+ }
12344
+ return text.slice(0, end);
12345
+ };
12223
12346
  var releaseDescription = (body) => {
12224
- const tree = fromMarkdown(body.replaceAll(CHANGESET_HASH, "$<mark>"), {
12347
+ const tree = fromMarkdown3(body.replaceAll(CHANGESET_HASH, "$<mark>"), {
12225
12348
  extensions: [gfm()],
12226
12349
  mdastExtensions: [gfmFromMarkdown()]
12227
12350
  });
12228
- const text = tree.children.filter((node) => !NON_PROSE.has(node.type)).map((node) => mdastToString(node, { includeHtml: false, includeImageAlt: false })).join(" ").replaceAll(WHITESPACE2, " ").trim();
12351
+ const text = tree.children.filter((node) => !NON_PROSE.has(node.type)).map((node) => mdastToString2(node, { includeHtml: false, includeImageAlt: false })).join(" ").replaceAll(WHITESPACE2, " ").trim();
12229
12352
  if (!text) {
12230
12353
  return;
12231
12354
  }
12232
12355
  if (text.length <= DESCRIPTION_MAX) {
12233
12356
  return text;
12234
12357
  }
12235
- const slice = text.slice(0, DESCRIPTION_MAX - 1);
12358
+ const slice = graphemePrefix(text, DESCRIPTION_MAX - 1);
12236
12359
  const boundary = slice.lastIndexOf(" ");
12237
12360
  const head = (boundary >= DESCRIPTION_MIN ? slice.slice(0, boundary) : slice).replace(TRAILING_FRAGMENT, "");
12238
12361
  return `${head}…`;
12239
12362
  };
12240
- var slugifyTag = (tag) => tag.toLowerCase().replaceAll(NON_SLUG3, "-").replaceAll(EDGE_DASHES, "");
12363
+ var slugifyTag = (tag) => tag.toLowerCase().replaceAll(NON_SLUG2, "-").replaceAll(EDGE_DASHES, "");
12241
12364
  var githubHeaders = () => {
12242
12365
  const headers = new Headers({ Accept: "application/vnd.github+json" });
12243
12366
  const token = process.env.GITHUB_TOKEN;
@@ -12490,6 +12613,7 @@ var mdxRemoteSource = (options, ctx) => {
12490
12613
 
12491
12614
  // src/core/sources/notion.ts
12492
12615
  import { setTimeout as sleep2 } from "node:timers/promises";
12616
+ import pLimit from "p-limit";
12493
12617
  import { join as join15 } from "pathe";
12494
12618
 
12495
12619
  // src/core/sources/assets.ts
@@ -12569,6 +12693,7 @@ var RATE_LIMITED = 429;
12569
12693
  var MAX_RETRIES = 4;
12570
12694
  var BASE_DELAY_MS = 500;
12571
12695
  var SECOND_MS2 = 1000;
12696
+ var DEFAULT_CONCURRENCY = 3;
12572
12697
  var withNotionRetry = async (call, attempt = 0) => {
12573
12698
  try {
12574
12699
  return await call();
@@ -12578,7 +12703,7 @@ var withNotionRetry = async (call, attempt = 0) => {
12578
12703
  throw error;
12579
12704
  }
12580
12705
  const retryAfter = Number(error.headers?.["retry-after"]);
12581
- const wait = retryAfter > 0 ? retryAfter * SECOND_MS2 : BASE_DELAY_MS * 2 ** attempt;
12706
+ const wait = retryAfter > 0 ? retryAfter * SECOND_MS2 : BASE_DELAY_MS * 2 ** attempt * (1 + Math.random());
12582
12707
  await sleep2(wait);
12583
12708
  return withNotionRetry(call, attempt + 1);
12584
12709
  }
@@ -12642,6 +12767,8 @@ ${text}
12642
12767
  };
12643
12768
  var notionSource = (options, ctx) => {
12644
12769
  const props = options.properties ?? {};
12770
+ const limit = pLimit(Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY));
12771
+ const notionCall = (call) => withNotionRetry(() => limit(call));
12645
12772
  const cache = snapshotCache(ctx?.cacheDir ?? join15(".blume", "cache", options.name));
12646
12773
  const assetsDir = ctx?.assetsDir ?? join15(".blume", "public", "blume-assets", options.name);
12647
12774
  const assetsBaseUrl = ctx?.assetsBaseUrl ?? `/blume-assets/${options.name}`;
@@ -12662,7 +12789,7 @@ var notionSource = (options, ctx) => {
12662
12789
  }
12663
12790
  return new Client({ auth: options.token ?? process.env.NOTION_TOKEN });
12664
12791
  };
12665
- const childrenOf = (client, blockId) => collectAll((cursor) => withNotionRetry(() => client.blocks.children.list({ block_id: blockId, start_cursor: cursor })));
12792
+ const childrenOf = (client, blockId) => collectAll((cursor) => notionCall(() => client.blocks.children.list({ block_id: blockId, start_cursor: cursor })));
12666
12793
  const renderContainer = async (client, block, render) => {
12667
12794
  const children = async (target) => {
12668
12795
  if (!target.has_children) {
@@ -12795,7 +12922,7 @@ ${nested}`;
12795
12922
  }
12796
12923
  };
12797
12924
  };
12798
- const queryDatabase = (client, cursor) => withNotionRetry(() => client.databases.query({
12925
+ const queryDatabase = (client, cursor) => notionCall(() => client.databases.query({
12799
12926
  database_id: options.database,
12800
12927
  start_cursor: cursor
12801
12928
  }));
@@ -12846,8 +12973,11 @@ var HEADING_STYLES = {
12846
12973
  h5: "##### ",
12847
12974
  h6: "###### "
12848
12975
  };
12976
+ var MARKDOWN_SPECIALS = /[\\`*_[\]~<]/gu;
12977
+ var escapeText = (text) => text.replaceAll(MARKDOWN_SPECIALS, String.raw`\$&`);
12849
12978
  var renderSpan = (span, defs) => {
12850
- let text = span.text ?? "";
12979
+ const isCode = span.marks?.includes("code") ?? false;
12980
+ let text = isCode ? span.text ?? "" : escapeText(span.text ?? "");
12851
12981
  if (!span.marks || span.marks.length === 0) {
12852
12982
  return text;
12853
12983
  }
@@ -13084,6 +13214,7 @@ var buildSource = (def, name, context, runtime) => {
13084
13214
  }
13085
13215
  if (def.type === "notion") {
13086
13216
  return notionSource({
13217
+ concurrency: def.concurrency,
13087
13218
  database: def.database,
13088
13219
  name,
13089
13220
  pollInterval: def.pollInterval,
@@ -13258,6 +13389,10 @@ var scanProject = async (root, options = {}) => {
13258
13389
  Promise.all(sources.map(async (source) => ({ source, ...await source.load() }))),
13259
13390
  discoverFolderMeta(metaSources, { localeDirs })
13260
13391
  ]);
13392
+ const sharedFolderMeta = new Map([
13393
+ ...loaded.flatMap(({ folderMeta: sourceMeta }) => Object.entries(sourceMeta ?? {})),
13394
+ ...folderMeta.shared
13395
+ ]);
13261
13396
  const {
13262
13397
  diagnostics: contentDiagnostics,
13263
13398
  droppedPages,
@@ -13280,7 +13415,7 @@ var scanProject = async (root, options = {}) => {
13280
13415
  folderMeta: folderMeta.meta,
13281
13416
  i18n: config.i18n,
13282
13417
  navigation: config.navigation,
13283
- sharedFolderMeta: folderMeta.shared
13418
+ sharedFolderMeta
13284
13419
  });
13285
13420
  const manifest = buildManifest({ config, context, graph });
13286
13421
  const i18nWarnings = config.i18n ? i18nDiagnostics(pages, config.i18n) : [];
@@ -13343,14 +13478,16 @@ var shouldFail = (result, gate) => {
13343
13478
  const failing = failingSeverities(gate);
13344
13479
  return result.diagnostics.some((d) => failing.has(d.severity));
13345
13480
  };
13346
- var launchAgentCode = async (bin, prompt) => {
13481
+ var launchAgentCode = async (agent, prompt) => {
13482
+ const cli = AGENTS[agent];
13347
13483
  try {
13348
- return await launchAgent(bin, prompt);
13484
+ return await launchAgent(cli.bin, prompt);
13349
13485
  } catch (error) {
13350
13486
  if (error?.code !== "ENOENT") {
13351
13487
  throw error;
13352
13488
  }
13353
- return WINDOWS_COMMAND_NOT_FOUND;
13489
+ logger.error(`${cli.name} (\`${cli.bin}\`) was not found on PATH. Install it with \`${cli.install}\`.`);
13490
+ return process.exit(1);
13354
13491
  }
13355
13492
  };
13356
13493
  var auditCommand = defineCommand2({
@@ -13458,11 +13595,7 @@ var auditCommand = defineCommand2({
13458
13595
  process.stderr.write(` Handing ${count} finding${count === 1 ? "" : "s"} to ${cli.name}…
13459
13596
 
13460
13597
  `);
13461
- const code = await launchAgentCode(cli.bin, fixPrompt(report));
13462
- if (code === WINDOWS_COMMAND_NOT_FOUND) {
13463
- logger.error(`${cli.name} (\`${cli.bin}\`) was not found on PATH. Install it with \`${cli.install}\`.`);
13464
- process.exit(1);
13465
- }
13598
+ const code = await launchAgentCode(agent, fixPrompt(report));
13466
13599
  if (code !== 0) {
13467
13600
  process.exit(code);
13468
13601
  }
@@ -13508,7 +13641,7 @@ var buildRssFeeds = (project) => {
13508
13641
  if (!(rss.enabled && site)) {
13509
13642
  return [];
13510
13643
  }
13511
- const base = site.replace(/\/$/u, "");
13644
+ const base = siteRoot(site);
13512
13645
  const deployBase = normalizeBasePath(config.deployment.base);
13513
13646
  const rootLink = `${base}${deployBase}`;
13514
13647
  const feeds = [];
@@ -13588,7 +13721,7 @@ var askApiUrl = (endpoint, site, abs) => {
13588
13721
  if (!endpoint) {
13589
13722
  return abs("/api/ask");
13590
13723
  }
13591
- return site && endpoint.startsWith("/") ? `${site.replace(/\/+$/u, "")}${endpoint}` : endpoint;
13724
+ return site && endpoint.startsWith("/") ? absoluteUrl(site, endpoint) : endpoint;
13592
13725
  };
13593
13726
  var wellKnownArtifacts = (config, abs) => {
13594
13727
  const artifacts = {};
@@ -13612,7 +13745,7 @@ var buildAgentReadability = (project) => {
13612
13745
  const deployBase = normalizeBasePath(config.deployment.base);
13613
13746
  const abs = (path) => {
13614
13747
  const based = withBasePath(deployBase, path);
13615
- return site ? `${site.replace(/\/+$/u, "")}${based}` : based;
13748
+ return site ? absoluteUrl(site, based) : based;
13616
13749
  };
13617
13750
  const negotiates = config.deployment.output === "server" && (config.deployment.adapter === "vercel" || config.deployment.adapter === "cloudflare");
13618
13751
  const artifacts = {
@@ -14077,7 +14210,7 @@ var applyAgentVisibility = (markdown) => applyAudienceVisibility(markdown, "agen
14077
14210
  // src/ai/llms.ts
14078
14211
  var pageUrl = (route, site, base = "") => {
14079
14212
  const path = withBasePath(base, route);
14080
- return encodeURI(site ? `${site.replace(/\/$/u, "")}${path}` : path);
14213
+ return encodeURI(site ? absoluteUrl(site, path) : path);
14081
14214
  };
14082
14215
  var eligiblePages = (project) => project.graph.pages.filter((page) => !(page.meta.ai.exclude || page.meta.draft || page.meta.sidebar.hidden || page.meta.seo.noindex && page.source.name !== "openapi") && (project.config.ai.llmsTxt.openapi || page.source.name !== "openapi"));
14083
14216
  var indexedNavigations = (project) => {
@@ -14251,49 +14384,16 @@ var markdownRoutePaths = (project) => {
14251
14384
  };
14252
14385
 
14253
14386
  // src/ai/skills.ts
14254
- import { createHash as createHash2 } from "node:crypto";
14387
+ import { createHash as createHash3 } from "node:crypto";
14255
14388
  import { readdir, readFile as readFile11, stat as stat2 } from "node:fs/promises";
14256
14389
  import { join as join18 } from "pathe";
14257
14390
 
14258
14391
  // src/ai/tar.ts
14259
14392
  import { gzipSync } from "node:zlib";
14260
- var BLOCK = 512;
14393
+ import { createTar } from "nanotar";
14261
14394
  var NAME_MAX = 100;
14262
14395
  var encoder = new TextEncoder;
14263
- var octal = (header, offset, length, value) => {
14264
- const text = value.toString(8).padStart(length - 1, "0");
14265
- header.set(encoder.encode(text), offset);
14266
- header[offset + length - 1] = 0;
14267
- };
14268
- var text = (header, offset, value) => {
14269
- header.set(encoder.encode(value), offset);
14270
- };
14271
- var fileHeader = (entry) => {
14272
- const header = new Uint8Array(BLOCK);
14273
- text(header, 0, entry.path);
14274
- octal(header, 100, 8, entry.executable ? 493 : 420);
14275
- octal(header, 108, 8, 0);
14276
- octal(header, 116, 8, 0);
14277
- octal(header, 124, 12, entry.content.byteLength);
14278
- octal(header, 136, 12, 0);
14279
- text(header, 156, "0");
14280
- text(header, 257, "ustar");
14281
- header[262] = 0;
14282
- text(header, 263, "00");
14283
- octal(header, 329, 8, 0);
14284
- octal(header, 337, 8, 0);
14285
- header.fill(32, 148, 156);
14286
- let sum = 0;
14287
- for (const byte of header) {
14288
- sum += byte;
14289
- }
14290
- text(header, 148, sum.toString(8).padStart(6, "0"));
14291
- header[154] = 0;
14292
- header[155] = 32;
14293
- return header;
14294
- };
14295
14396
  var buildTarGz = (entries) => {
14296
- const blocks = [];
14297
14397
  for (const entry of entries) {
14298
14398
  if (encoder.encode(entry.path).byteLength > NAME_MAX) {
14299
14399
  throw new Error(`tar path exceeds ${NAME_MAX} bytes: ${entry.path}`);
@@ -14301,20 +14401,19 @@ var buildTarGz = (entries) => {
14301
14401
  if (entry.path.startsWith("/") || entry.path.split("/").includes("..")) {
14302
14402
  throw new Error(`tar path must be archive-relative: ${entry.path}`);
14303
14403
  }
14304
- blocks.push(fileHeader(entry), entry.content);
14305
- const overhang = entry.content.byteLength % BLOCK;
14306
- if (overhang > 0) {
14307
- blocks.push(new Uint8Array(BLOCK - overhang));
14308
- }
14309
- }
14310
- blocks.push(new Uint8Array(BLOCK * 2));
14311
- const total = blocks.reduce((sum, block) => sum + block.byteLength, 0);
14312
- const tar = new Uint8Array(total);
14313
- let offset = 0;
14314
- for (const block of blocks) {
14315
- tar.set(block, offset);
14316
- offset += block.byteLength;
14317
14404
  }
14405
+ const tar = createTar(entries.map((entry) => ({
14406
+ attrs: {
14407
+ gid: 0,
14408
+ group: "",
14409
+ mode: entry.executable ? "755" : "644",
14410
+ mtime: 0,
14411
+ uid: 0,
14412
+ user: ""
14413
+ },
14414
+ data: entry.content,
14415
+ name: entry.path
14416
+ })));
14318
14417
  return new Uint8Array(gzipSync(tar, { level: 9 }));
14319
14418
  };
14320
14419
 
@@ -14324,7 +14423,7 @@ var AGENT_SKILLS_SCHEMA = "https://schemas.agentskills.io/discovery/0.2.0/schema
14324
14423
  var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
14325
14424
  var SKILL_NAME_MAX = 64;
14326
14425
  var DESCRIPTION_MAX2 = 1024;
14327
- var sha256 = (content) => `sha256:${createHash2("sha256").update(content).digest("hex")}`;
14426
+ var sha256 = (content) => `sha256:${createHash3("sha256").update(content).digest("hex")}`;
14328
14427
  var collectEntries = async (dir, prefix = "") => {
14329
14428
  const entries = [];
14330
14429
  const items = await readdir(dir, { withFileTypes: true });
@@ -14585,6 +14684,20 @@ const redirectFor = (pathname) => {
14585
14684
  return Object.hasOwn(REDIRECTS, path) ? REDIRECTS[path] : null;
14586
14685
  };
14587
14686
 
14687
+ // \`_redirects\` semantics, which the static layer applies to these same
14688
+ // paths: the request's query string is forwarded unless the destination
14689
+ // carries its own, and a destination fragment stays after the query.
14690
+ const redirectLocation = (destination, search) => {
14691
+ const hashIndex = destination.indexOf("#");
14692
+ const bare = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
14693
+ if (!search || bare.includes("?")) {
14694
+ return destination;
14695
+ }
14696
+ return hashIndex === -1
14697
+ ? bare + search
14698
+ : bare + search + destination.slice(hashIndex);
14699
+ };
14700
+
14588
14701
  const parseAccept = (accept) =>
14589
14702
  accept.split(",").map((part) => {
14590
14703
  const segments = part.trim().split(";");
@@ -14664,7 +14777,7 @@ export default {
14664
14777
  const redirect = redirectFor(url.pathname);
14665
14778
  if (redirect !== null) {
14666
14779
  return new Response(null, {
14667
- headers: { location: redirect[0] },
14780
+ headers: { location: redirectLocation(redirect[0], url.search) },
14668
14781
  status: redirect[1],
14669
14782
  });
14670
14783
  }
@@ -14837,7 +14950,7 @@ var buildRobots = (project) => {
14837
14950
  const { site } = config.deployment;
14838
14951
  if (site && config.seo.sitemap) {
14839
14952
  const sitemapPath = withBasePath(normalizeBasePath(config.deployment.base), "/sitemap.xml");
14840
- lines.push("", `Sitemap: ${site.replace(/\/$/u, "")}${sitemapPath}`);
14953
+ lines.push("", `Sitemap: ${absoluteUrl(site, sitemapPath)}`);
14841
14954
  }
14842
14955
  return `${lines.join(`
14843
14956
  `)}
@@ -14932,12 +15045,19 @@ var lastmodTag = (value) => {
14932
15045
  const date = new Date(value);
14933
15046
  return Number.isNaN(date.getTime()) ? "" : `<lastmod>${date.toISOString().slice(0, 10)}</lastmod>`;
14934
15047
  };
14935
- var buildSitemap = (project) => {
15048
+ var URLS_PER_FILE = 50000;
15049
+ var renderUrlset = (urls) => `<?xml version="1.0" encoding="UTF-8"?>
15050
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
15051
+ ${urls.join(`
15052
+ `)}
15053
+ </urlset>
15054
+ `;
15055
+ var buildSitemapFiles = (project) => {
14936
15056
  const { site } = project.config.deployment;
14937
15057
  if (!(site && project.config.seo.sitemap)) {
14938
15058
  return null;
14939
15059
  }
14940
- const base = site.replace(/\/$/u, "");
15060
+ const base = siteRoot(site);
14941
15061
  const deployBase = normalizeBasePath(project.config.deployment.base);
14942
15062
  const seen = new Set;
14943
15063
  const urls = [];
@@ -14964,12 +15084,27 @@ var buildSitemap = (project) => {
14964
15084
  pushUrl(withBasePath(deployBase, route));
14965
15085
  }
14966
15086
  urls.sort();
14967
- return `<?xml version="1.0" encoding="UTF-8"?>
14968
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
14969
- ${urls.join(`
15087
+ if (urls.length <= URLS_PER_FILE) {
15088
+ return [{ name: "sitemap.xml", xml: renderUrlset(urls) }];
15089
+ }
15090
+ const chunks = [];
15091
+ const references = [];
15092
+ for (let start = 0;start < urls.length; start += URLS_PER_FILE) {
15093
+ const name = `sitemap-${chunks.length + 1}.xml`;
15094
+ chunks.push({
15095
+ name,
15096
+ xml: renderUrlset(urls.slice(start, start + URLS_PER_FILE))
15097
+ });
15098
+ const loc = escapeXml2(encodeURI(`${base}${withBasePath(deployBase, `/${name}`)}`));
15099
+ references.push(` <sitemap><loc>${loc}</loc></sitemap>`);
15100
+ }
15101
+ const index = `<?xml version="1.0" encoding="UTF-8"?>
15102
+ <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
15103
+ ${references.join(`
14970
15104
  `)}
14971
- </urlset>
15105
+ </sitemapindex>
14972
15106
  `;
15107
+ return [{ name: "sitemap.xml", xml: index }, ...chunks];
14973
15108
  };
14974
15109
 
14975
15110
  // src/deploy/vercel-negotiation.ts
@@ -15079,6 +15214,11 @@ var buildSearchIndex = async (outDir) => {
15079
15214
  return result.page_count;
15080
15215
  };
15081
15216
 
15217
+ // src/search/documents.ts
15218
+ import { fromMarkdown as fromMarkdown4 } from "mdast-util-from-markdown";
15219
+ import { gfmFromMarkdown as gfmFromMarkdown2 } from "mdast-util-gfm";
15220
+ import { gfm as gfm2 } from "micromark-extension-gfm";
15221
+
15082
15222
  // src/search/facets.ts
15083
15223
  var pageFacets = (page, config) => {
15084
15224
  const declared = config.content.types[page.contentType]?.facets;
@@ -15096,25 +15236,60 @@ var pageFacets = (page, config) => {
15096
15236
  };
15097
15237
 
15098
15238
  // src/search/documents.ts
15099
- var CODE_FENCE2 = /```[\s\S]*?```/gu;
15100
- var INLINE_CODE2 = /`(?<code>[^`]+)`/gu;
15101
15239
  var HTML_OR_JSX = /<\/?[a-zA-Z][^\n<>]*>|<\/?>/gu;
15102
- var IMAGE = /!\[[^\]]*\]\([^)]*\)/gu;
15103
- var LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
15104
- var HEADING_MARK = /^#{1,6}\s+/gmu;
15105
- var MARKDOWN_PUNCT = /[*_~>]+/gu;
15106
15240
  var WHITESPACE3 = /\s+/gu;
15107
- var toPlainText = (markdown) => {
15108
- const withoutBlocks = markdown.replaceAll(CODE_FENCE2, " ").replaceAll(IMAGE, " ").replaceAll(LINK, "$<text>");
15109
- const pieces = [];
15110
- let cursor = 0;
15111
- for (const match of withoutBlocks.matchAll(INLINE_CODE2)) {
15112
- const start = match.index ?? 0;
15113
- pieces.push(withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX, " "), match.groups?.code ?? "");
15114
- cursor = start + match[0].length;
15241
+ var INLINE_PARENTS = new Set([
15242
+ "delete",
15243
+ "emphasis",
15244
+ "footnoteReference",
15245
+ "link",
15246
+ "linkReference",
15247
+ "strong"
15248
+ ]);
15249
+ var collectText = (node, out) => {
15250
+ switch (node.type) {
15251
+ case "code":
15252
+ case "image":
15253
+ case "imageReference": {
15254
+ return;
15255
+ }
15256
+ case "inlineCode": {
15257
+ out.push(node.value);
15258
+ return;
15259
+ }
15260
+ case "html": {
15261
+ out.push(node.value.replaceAll(HTML_OR_JSX, " "));
15262
+ return;
15263
+ }
15264
+ case "break": {
15265
+ out.push(" ");
15266
+ return;
15267
+ }
15268
+ default: {
15269
+ break;
15270
+ }
15271
+ }
15272
+ if ("value" in node) {
15273
+ out.push(node.value);
15274
+ return;
15275
+ }
15276
+ if ("children" in node) {
15277
+ for (const child of node.children) {
15278
+ collectText(child, out);
15279
+ }
15280
+ if (!INLINE_PARENTS.has(node.type)) {
15281
+ out.push(" ");
15282
+ }
15115
15283
  }
15116
- pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX, " "));
15117
- return pieces.join("").replaceAll(HEADING_MARK, "").replaceAll(MARKDOWN_PUNCT, " ").replaceAll(WHITESPACE3, " ").trim();
15284
+ };
15285
+ var toPlainText = (markdown) => {
15286
+ const tree = fromMarkdown4(markdown, {
15287
+ extensions: [gfm2()],
15288
+ mdastExtensions: [gfmFromMarkdown2()]
15289
+ });
15290
+ const out = [];
15291
+ collectText(tree, out);
15292
+ return out.join("").replaceAll(WHITESPACE3, " ").trim();
15118
15293
  };
15119
15294
  var buildCrumbIndex = (sidebar) => {
15120
15295
  const index = new Map;
@@ -15434,7 +15609,7 @@ var refuseIfDevRunning = (root, action, options = {}) => {
15434
15609
  };
15435
15610
 
15436
15611
  // src/astro/generate.ts
15437
- import { createHash as createHash3 } from "node:crypto";
15612
+ import { createHash as createHash4 } from "node:crypto";
15438
15613
  import { existsSync as existsSync16, readFileSync as readFileSync8, realpathSync } from "node:fs";
15439
15614
  import {
15440
15615
  lstat,
@@ -15447,6 +15622,8 @@ import {
15447
15622
  } from "node:fs/promises";
15448
15623
  import { createRequire as createRequire4 } from "node:module";
15449
15624
  import { pathToFileURL as pathToFileURL2 } from "node:url";
15625
+ import { imageSize as imageSize2 } from "image-size";
15626
+ import pMap6 from "p-map";
15450
15627
  import { basename as basename3, dirname as dirname11, join as join28, normalize as normalize3, relative as relative14, resolve as resolve8 } from "pathe";
15451
15628
  import { glob as glob7 } from "tinyglobby";
15452
15629
 
@@ -15555,7 +15732,7 @@ var MCP_TOOLS = [
15555
15732
  // src/ai/mcp/discovery.ts
15556
15733
  var serverUrl = (input) => {
15557
15734
  const path = withBasePath(input.base, input.route);
15558
- return input.site ? `${trimEnd(input.site, "/")}${path}` : path;
15735
+ return input.site ? absoluteUrl(input.site, path) : path;
15559
15736
  };
15560
15737
  var buildMcpDiscovery = (input) => ({
15561
15738
  servers: [
@@ -15568,7 +15745,10 @@ var buildMcpDiscovery = (input) => ({
15568
15745
  });
15569
15746
  var SERVER_CARD_SCHEMA = "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json";
15570
15747
  var CARD_TEXT_MAX = 100;
15571
- var truncate = (text2) => text2.length > CARD_TEXT_MAX ? `${text2.slice(0, CARD_TEXT_MAX - 1)}…` : text2;
15748
+ var truncate = (text) => text.length > CARD_TEXT_MAX ? `${text.slice(0, CARD_TEXT_MAX - 1)}…` : text;
15749
+ var NON_ASCII_SLUG = /[^a-z0-9]+/gu;
15750
+ var COMBINING_MARKS = /\p{M}+/gu;
15751
+ var asciiSlugify = (text) => trimChar(text.normalize("NFKD").replace(COMBINING_MARKS, "").toLowerCase().replace(NON_ASCII_SLUG, "-"), "-");
15572
15752
  var reverseDnsName = (input) => {
15573
15753
  let namespace = "localhost";
15574
15754
  if (input.site) {
@@ -15576,7 +15756,7 @@ var reverseDnsName = (input) => {
15576
15756
  namespace = new URL(input.site).hostname.split(".").toReversed().join(".");
15577
15757
  } catch {}
15578
15758
  }
15579
- return `${namespace}/${slugify(input.name) || "docs"}`;
15759
+ return `${namespace}/${asciiSlugify(input.name) || "docs"}`;
15580
15760
  };
15581
15761
  var HTTP_URL2 = /^https?:\/\//u;
15582
15762
  var buildMcpServerCard = (input) => {
@@ -15599,7 +15779,7 @@ var buildMcpServerCard = (input) => {
15599
15779
  transports: [{ endpoint: url, type: "streamable-http" }],
15600
15780
  url,
15601
15781
  version: input.version,
15602
- ...input.site ? { websiteUrl: trimEnd(input.site, "/") } : {}
15782
+ ...input.site ? { websiteUrl: siteRoot(input.site) } : {}
15603
15783
  };
15604
15784
  };
15605
15785
 
@@ -15934,9 +16114,9 @@ var analyzeComponentOverrides = (source, filePath) => {
15934
16114
  import { mkdir as mkdir6 } from "node:fs/promises";
15935
16115
  import { dirname as dirname9 } from "pathe";
15936
16116
  import writeFileAtomic from "write-file-atomic";
15937
- var writeTextAtomic = async (path, text2) => {
16117
+ var writeTextAtomic = async (path, text) => {
15938
16118
  await mkdir6(dirname9(path), { recursive: true });
15939
- await writeFileAtomic(path, text2, { encoding: "utf-8", fsync: false });
16119
+ await writeFileAtomic(path, text, { encoding: "utf-8", fsync: false });
15940
16120
  };
15941
16121
 
15942
16122
  // src/core/tsconfig-aliases.ts
@@ -16183,9 +16363,8 @@ ${dark}`;
16183
16363
 
16184
16364
  // src/openapi/scalar.ts
16185
16365
  var URL_SPEC2 = /^https?:\/\//u;
16186
- var ROUTE_EDGES = /^\/+|\/+$/gu;
16187
16366
  var referencePagePath = (route) => {
16188
- const segments = route.replace(ROUTE_EDGES, "");
16367
+ const segments = trimChar(route, "/");
16189
16368
  return `${segments === "" ? "index" : segments}.astro`;
16190
16369
  };
16191
16370
  var darkModeConfig = (mode) => {
@@ -17227,14 +17406,17 @@ export const layoutOverrides = { ...(overrides.layout ?? {})${layoutEntries.leng
17227
17406
 
17228
17407
  // src/astro/examples.ts
17229
17408
  import { readFile as readFile15 } from "node:fs/promises";
17409
+ import pMap5 from "p-map";
17230
17410
  import { join as join27, relative as relative13 } from "pathe";
17231
17411
  import { glob as glob6 } from "tinyglobby";
17232
17412
 
17233
17413
  // src/astro/islands.ts
17234
17414
  import { readFile as readFile14 } from "node:fs/promises";
17415
+ import pMap4 from "p-map";
17235
17416
  import { basename as basename2, join as join26 } from "pathe";
17236
17417
  import { glob as glob5 } from "tinyglobby";
17237
17418
  var DEFAULT_CLIENT = "visible";
17419
+ var READ_CONCURRENCY2 = 16;
17238
17420
  var VALID_MODES = new Set([
17239
17421
  "idle",
17240
17422
  "load",
@@ -17268,7 +17450,9 @@ var discoverIslands = async (root) => {
17268
17450
  onlyFiles: true
17269
17451
  });
17270
17452
  const files = matches2.toSorted();
17271
- const sources = await Promise.all(files.map((file) => readFile14(file, "utf-8")));
17453
+ const sources = await pMap4(files, (file) => readFile14(file, "utf-8"), {
17454
+ concurrency: READ_CONCURRENCY2
17455
+ });
17272
17456
  const islands = [];
17273
17457
  const warnings = [];
17274
17458
  const seen = new Map;
@@ -17313,6 +17497,7 @@ var FRAMEWORK_BY_EXT3 = {
17313
17497
  };
17314
17498
  var EXAMPLE_FILE = /\.(?<ext>astro|jsx|svelte|tsx|vue)$/u;
17315
17499
  var DEFAULT_EXAMPLE_GLOB = "**/*.{astro,jsx,svelte,tsx,vue}";
17500
+ var READ_CONCURRENCY3 = 16;
17316
17501
  var GLOB_MAGIC = /[!*?[\]{}]/u;
17317
17502
  var splitGlobBase = (pattern) => {
17318
17503
  const segments = pattern.split("/");
@@ -17331,7 +17516,9 @@ var discoverExamples = async (root, pattern = "examples") => {
17331
17516
  onlyFiles: true
17332
17517
  });
17333
17518
  const files = matches2.toSorted();
17334
- const sources = await Promise.all(files.map((file) => readFile15(file, "utf-8")));
17519
+ const sources = await pMap5(files, (file) => readFile15(file, "utf-8"), {
17520
+ concurrency: READ_CONCURRENCY3
17521
+ });
17335
17522
  const examples = [];
17336
17523
  const warnings = [];
17337
17524
  const seen = new Map;
@@ -17540,6 +17727,18 @@ var searchProviderWarnings = (provider, root, pkgDir = packageRoot()) => {
17540
17727
  }
17541
17728
  return warnings;
17542
17729
  };
17730
+ var askProviderWarnings = (ask, root, pkgDir = packageRoot()) => {
17731
+ if (!ask?.enabled || ask.endpoint) {
17732
+ return [];
17733
+ }
17734
+ const dep = askBackendRuntimeDep(ask);
17735
+ if (!dep || canResolveFrom(root, dep) || canResolveFrom(pkgDir, dep)) {
17736
+ return [];
17737
+ }
17738
+ return [
17739
+ `Ask AI provider "${ask.provider}" needs "${dep}", which isn't installed. Run \`npm install ${dep}\` (or your package manager's equivalent).`
17740
+ ];
17741
+ };
17543
17742
  var examplesCssFile = (root, config) => config.examples.css ? join28(root, config.examples.css) : null;
17544
17743
  var writeExamplesPreview = async (options) => {
17545
17744
  const { config, hasExamples, root, srcDir, write } = options;
@@ -17570,16 +17769,19 @@ var detectNeedsReact = async (root) => {
17570
17769
  return matches2.length > 0;
17571
17770
  };
17572
17771
  var containsMath = (content) => content.includes("$$") || content.includes("<Math");
17772
+ var READ_CONCURRENCY4 = 16;
17573
17773
  var detectUsesMath = async (root, staged = []) => {
17574
17774
  const files = await glob7(["**/*.{md,mdx}"], {
17575
17775
  cwd: root,
17576
17776
  ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
17577
17777
  onlyFiles: true
17578
17778
  });
17579
- const contents = await Promise.all(files.map((file) => readOptional(join28(root, file))));
17779
+ const contents = await pMap6(files, (file) => readOptional(join28(root, file)), {
17780
+ concurrency: READ_CONCURRENCY4
17781
+ });
17580
17782
  return [...contents, ...staged].some(containsMath);
17581
17783
  };
17582
- var hashConfigSource = (source) => createHash3("sha256").update(source).digest("hex");
17784
+ var hashConfigSource = (source) => createHash4("sha256").update(source).digest("hex");
17583
17785
  var loadIntegrationBridge = async (config, context) => {
17584
17786
  if (config.integrations.length === 0 || !context.configFile) {
17585
17787
  return;
@@ -17629,35 +17831,25 @@ var collectStaged = (project) => {
17629
17831
  var writeStagedContent = async (out, staged) => {
17630
17832
  const contentDir = stagedContentDir(out);
17631
17833
  const written = new Set;
17632
- await Promise.all([...staged].map(async ([entryId, text2]) => {
17834
+ await Promise.all([...staged].map(async ([entryId, text]) => {
17633
17835
  const path = join28(contentDir, entryId);
17634
17836
  written.add(normalize3(path));
17635
- await writeIfChanged(path, text2);
17837
+ await writeIfChanged(path, text);
17636
17838
  }));
17637
17839
  if (existsSync16(contentDir)) {
17638
17840
  await pruneOrphans(contentDir, written);
17639
17841
  }
17640
17842
  };
17641
- var SVG_ROOT = /<svg\b(?<attributes>[^>]*)>/u;
17642
- var SVG_WIDTH = /\bwidth\s*=\s*["'](?<value>[^"']+)["']/u;
17643
- var SVG_HEIGHT = /\bheight\s*=\s*["'](?<value>[^"']+)["']/u;
17644
- var SVG_LENGTH = /^\s*(?<value>[\d.]+)(?:px)?\s*$/u;
17645
- var SVG_VIEW_BOX = /\bviewBox\s*=\s*["'][\d.-]+[\s,]+[\d.-]+[\s,]+(?<width>[\d.]+)[\s,]+(?<height>[\d.]+)["']/u;
17646
- var parseSvgLength = (value) => {
17647
- const length = Number(value?.match(SVG_LENGTH)?.groups?.value);
17648
- return length > 0 ? length : undefined;
17649
- };
17650
17843
  var svgDimensions = (svg) => {
17651
- const attributes = svg?.match(SVG_ROOT)?.groups?.attributes;
17652
- const width = parseSvgLength(attributes?.match(SVG_WIDTH)?.groups?.value);
17653
- const height = parseSvgLength(attributes?.match(SVG_HEIGHT)?.groups?.value);
17654
- if (width && height) {
17655
- return { height, width };
17844
+ if (!svg) {
17845
+ return;
17846
+ }
17847
+ try {
17848
+ const { height, width } = imageSize2(Buffer.from(svg));
17849
+ return height && width ? { height, width } : undefined;
17850
+ } catch {
17851
+ return;
17656
17852
  }
17657
- const viewBox = attributes?.match(SVG_VIEW_BOX);
17658
- const viewBoxWidth = Number(viewBox?.groups?.width);
17659
- const viewBoxHeight = Number(viewBox?.groups?.height);
17660
- return viewBoxWidth > 0 && viewBoxHeight > 0 ? { height: viewBoxHeight, width: viewBoxWidth } : undefined;
17661
17853
  };
17662
17854
  var readLogoSvg = (project, source) => {
17663
17855
  if (!source?.toLowerCase().endsWith(".svg")) {
@@ -17676,7 +17868,7 @@ var resolveLogo = (project) => {
17676
17868
  return null;
17677
17869
  }
17678
17870
  const config = typeof logo === "string" ? { image: logo } : logo;
17679
- const { href, image: source, text: text2 } = config;
17871
+ const { href, image: source, text } = config;
17680
17872
  const image = typeof source === "string" ? { light: source } : source;
17681
17873
  const light = image?.light ?? image?.dark;
17682
17874
  const dark = image?.dark ?? image?.light;
@@ -17685,12 +17877,12 @@ var resolveLogo = (project) => {
17685
17877
  const lightSvg = readLogoSvg(project, light);
17686
17878
  const darkSvg = dark === light ? lightSvg : readLogoSvg(project, dark);
17687
17879
  if (light && light === dark && lightSvg) {
17688
- return { alt, href: brandHref, svg: lightSvg, text: text2 };
17880
+ return { alt, href: brandHref, svg: lightSvg, text };
17689
17881
  }
17690
17882
  const lightDimensions = svgDimensions(lightSvg);
17691
17883
  const darkDimensions = svgDimensions(darkSvg);
17692
17884
  const dimensions = lightDimensions || darkDimensions ? { dark: darkDimensions, light: lightDimensions } : undefined;
17693
- return { alt, dark, dimensions, href: brandHref, light, text: text2 };
17885
+ return { alt, dark, dimensions, href: brandHref, light, text };
17694
17886
  };
17695
17887
  var FAVICON_CANDIDATES = [
17696
17888
  "icon.svg",
@@ -17836,6 +18028,10 @@ var buildRuntimeData = (project) => {
17836
18028
  codeWrap: config.markdown.code.wrap,
17837
18029
  dateFormat: config.dateFormat,
17838
18030
  description: config.description,
18031
+ discovery: {
18032
+ agentReadability: config.seo.agentReadability,
18033
+ llmsTxt: config.ai.llmsTxt.enabled
18034
+ },
17839
18035
  favicon: resolveFavicon(project),
17840
18036
  feedback: config.feedback,
17841
18037
  i18n: i18n ? {
@@ -17974,7 +18170,7 @@ var writeAskFiles = async (project, srcDir, write) => {
17974
18170
  await write(join28(srcDir, "generated", "ask-data.json"), `${JSON.stringify(await buildAskData(project))}
17975
18171
  `);
17976
18172
  }
17977
- await write(join28(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded));
18173
+ await write(join28(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded, ask.instructions));
17978
18174
  };
17979
18175
  var writeNotFoundPage = async (write, srcDir, pages, contentPages) => {
17980
18176
  if (routeIsTaken(pages, contentPages, "/404")) {
@@ -18211,7 +18407,7 @@ var generateRuntime = async (project) => {
18211
18407
  ...islandDiscovery.islands.map((island) => island.name),
18212
18408
  ...overrideTags
18213
18409
  ]);
18214
- warnings.push(...validateUsedComponents(project.graph.pages, knownComponentTags, new Set(registry.map((item) => item.name))).map(diagnosticWarning), ...searchProviderWarnings(config.search.provider, context.root), ...deploymentAdapterWarnings(config.deployment, context.root), ...islandFrameworkWarnings(frameworks, context.root));
18410
+ warnings.push(...validateUsedComponents(project.graph.pages, knownComponentTags, new Set(registry.map((item) => item.name))).map(diagnosticWarning), ...searchProviderWarnings(config.search.provider, context.root), ...askProviderWarnings(config.ai.ask, context.root), ...deploymentAdapterWarnings(config.deployment, context.root), ...islandFrameworkWarnings(frameworks, context.root));
18215
18411
  if (hasScalarReferences(config)) {
18216
18412
  const references = await buildReferenceFiles({
18217
18413
  config,
@@ -18235,37 +18431,24 @@ var generateRuntime = async (project) => {
18235
18431
  };
18236
18432
 
18237
18433
  // src/cli/env.ts
18238
- import { existsSync as existsSync17, readFileSync as readFileSync9 } from "node:fs";
18239
- import { parse as parse2 } from "dotenv";
18434
+ import { existsSync as existsSync17 } from "node:fs";
18435
+ import { config } from "dotenv";
18240
18436
  import { dirname as dirname12, join as join29, resolve as resolve9 } from "pathe";
18241
- var applyEnv = (parsed) => {
18242
- for (const [key, value] of Object.entries(parsed)) {
18243
- if (!(key in process.env)) {
18244
- process.env[key] = value;
18245
- }
18246
- }
18247
- };
18248
- var loadFile = (path) => {
18249
- try {
18250
- if (existsSync17(path)) {
18251
- applyEnv(parse2(readFileSync9(path, "utf-8")));
18252
- }
18253
- } catch {}
18254
- };
18255
18437
  var loadEnvFiles = (startDir) => {
18438
+ const paths = [];
18256
18439
  let dir = resolve9(startDir);
18257
18440
  let done = false;
18258
18441
  while (!done) {
18259
- loadFile(join29(dir, ".env.local"));
18260
- loadFile(join29(dir, ".env"));
18442
+ paths.push(join29(dir, ".env.local"), join29(dir, ".env"));
18261
18443
  const parent = dirname12(dir);
18262
18444
  done = existsSync17(join29(dir, ".git")) || parent === dir;
18263
18445
  dir = parent;
18264
18446
  }
18447
+ config({ path: paths, quiet: true });
18265
18448
  };
18266
18449
 
18267
18450
  // src/cli/required-secrets.ts
18268
- var checkRequiredSecrets = (config) => {
18451
+ var checkRequiredSecrets = (config2) => {
18269
18452
  const diagnostics = [];
18270
18453
  const requireSecret = (feature, env, note) => {
18271
18454
  if (process.env[env]) {
@@ -18279,15 +18462,15 @@ var checkRequiredSecrets = (config) => {
18279
18462
  suggestion: `Set ${env} in .env.local for local dev, or in your host's environment for production.`
18280
18463
  });
18281
18464
  };
18282
- if (config.ai.ask?.enabled && !config.ai.ask.endpoint) {
18283
- const backend = resolveAskBackend(config.ai.ask);
18465
+ if (config2.ai.ask?.enabled && !config2.ai.ask.endpoint) {
18466
+ const backend = resolveAskBackend(config2.ai.ask);
18284
18467
  if (backend.kind === "gateway") {
18285
18468
  requireSecret("Ask AI (AI Gateway)", "AI_GATEWAY_API_KEY", "on Vercel the gateway can also authenticate via OIDC");
18286
18469
  } else {
18287
18470
  requireSecret("Ask AI", backend.apiKeyEnv);
18288
18471
  }
18289
18472
  }
18290
- if (config.search.provider === "mixedbread") {
18473
+ if (config2.search.provider === "mixedbread") {
18291
18474
  requireSecret("Mixedbread search", "MIXEDBREAD_API_KEY");
18292
18475
  }
18293
18476
  return diagnostics;
@@ -18359,9 +18542,9 @@ var validateBudgetFlags = (args) => {
18359
18542
  }
18360
18543
  }
18361
18544
  };
18362
- var emitRedirectFiles = async (config, distDir) => {
18363
- const redirects = platformRedirects(config);
18364
- if (redirects.length === 0 || config.deployment.output !== "static") {
18545
+ var emitRedirectFiles = async (config2, distDir) => {
18546
+ const redirects = platformRedirects(config2);
18547
+ if (redirects.length === 0 || config2.deployment.output !== "static") {
18365
18548
  return;
18366
18549
  }
18367
18550
  await writeFile7(join30(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
@@ -18373,11 +18556,11 @@ var emitRedirectFiles = async (config, distDir) => {
18373
18556
  logger.success(`Emitted redirect files for ${redirects.length} redirect(s)`);
18374
18557
  };
18375
18558
  var emitHeaderFiles = async (project, distDir) => {
18376
- const { config } = project;
18377
- if (!readsHeaderFiles(config.deployment) || existsSync18(join30(project.context.root, "public", "_headers"))) {
18559
+ const { config: config2 } = project;
18560
+ if (!readsHeaderFiles(config2.deployment) || existsSync18(join30(project.context.root, "public", "_headers"))) {
18378
18561
  return;
18379
18562
  }
18380
- const ours = buildNetlifyHeaders(config, buildHomeLinkHeader(config, markdownRoutePaths(project)));
18563
+ const ours = buildNetlifyHeaders(config2, buildHomeLinkHeader(config2, markdownRoutePaths(project)));
18381
18564
  const target = join30(distDir, "_headers");
18382
18565
  const existing = existsSync18(target) ? await readFile17(target, "utf-8") : "";
18383
18566
  await writeFile7(target, existing ? `${existing.trimEnd()}
@@ -18414,15 +18597,15 @@ var emitAgentSkills = async (project, distDir) => {
18414
18597
  await writeFile7(join30(outDir, "index.json"), buildSkillsIndex(skills, project.config), "utf-8");
18415
18598
  logger.success(`Published ${skills.length} agent skill(s) (.well-known/agent-skills/index.json)`);
18416
18599
  };
18417
- var emitWellKnownFiles = async (config, distDir) => {
18600
+ var emitWellKnownFiles = async (config2, distDir) => {
18418
18601
  const files = [
18419
18602
  {
18420
- content: buildSignaturesDirectory(config),
18603
+ content: buildSignaturesDirectory(config2),
18421
18604
  label: "Web Bot Auth",
18422
18605
  path: SIGNATURES_DIRECTORY_PATH
18423
18606
  },
18424
18607
  {
18425
- content: buildApiCatalog(config),
18608
+ content: buildApiCatalog(config2),
18426
18609
  label: "RFC 9727",
18427
18610
  path: API_CATALOG_PATH
18428
18611
  }
@@ -18438,18 +18621,18 @@ var emitWellKnownFiles = async (config, distDir) => {
18438
18621
  }
18439
18622
  };
18440
18623
  var emitVercelNegotiation = async (project, routePaths, root) => {
18441
- const { config } = project;
18624
+ const { config: config2 } = project;
18442
18625
  const configPath = join30(root, ".vercel", "output", "config.json");
18443
18626
  if (!existsSync18(configPath)) {
18444
18627
  return;
18445
18628
  }
18446
18629
  const overrides = {
18447
- ...hasApiCatalog(config) ? { [API_CATALOG_PATH.slice(1)]: API_CATALOG_TYPE } : {},
18448
- ...config.ai.webBotAuth.keys.length > 0 ? { [SIGNATURES_DIRECTORY_PATH.slice(1)]: SIGNATURES_DIRECTORY_TYPE } : {}
18630
+ ...hasApiCatalog(config2) ? { [API_CATALOG_PATH.slice(1)]: API_CATALOG_TYPE } : {},
18631
+ ...config2.ai.webBotAuth.keys.length > 0 ? { [SIGNATURES_DIRECTORY_PATH.slice(1)]: SIGNATURES_DIRECTORY_TYPE } : {}
18449
18632
  };
18450
18633
  const rawMarkdown = await buildRawMarkdown(project);
18451
18634
  const home = rawMarkdown["/"];
18452
- const injected = injectNegotiationRoutes(await readFile17(configPath, "utf-8"), routePaths, buildHomeLinkHeader(config, routePaths), overrides, home ? markdownTokenCount(agentMarkdown(home)) : undefined);
18635
+ const injected = injectNegotiationRoutes(await readFile17(configPath, "utf-8"), routePaths, buildHomeLinkHeader(config2, routePaths), overrides, home ? markdownTokenCount(agentMarkdown(home)) : undefined);
18453
18636
  if (injected === null) {
18454
18637
  logger.warn("Could not wire Accept: text/markdown negotiation into .vercel/output/config.json — raw Markdown stays available at the .md URLs.");
18455
18638
  return;
@@ -18459,7 +18642,7 @@ var emitVercelNegotiation = async (project, routePaths, root) => {
18459
18642
  };
18460
18643
  var warnCloudflareNegotiationSkipped = () => logger.warn("Could not wire Accept: text/markdown negotiation into dist/server/wrangler.json — raw Markdown stays available at the .md URLs.");
18461
18644
  var emitCloudflareNegotiation = async (project, routePaths) => {
18462
- const { config, context } = project;
18645
+ const { config: config2, context } = project;
18463
18646
  const serverDir = join30(context.distDir ?? join30(context.root, "dist"), "server");
18464
18647
  const wranglerPath = join30(serverDir, "wrangler.json");
18465
18648
  if (!existsSync18(wranglerPath)) {
@@ -18469,11 +18652,11 @@ var emitCloudflareNegotiation = async (project, routePaths) => {
18469
18652
  const rawMarkdown = await buildRawMarkdown(project);
18470
18653
  const home = rawMarkdown["/"];
18471
18654
  const injected = injectWorkerNegotiation(await readFile17(wranglerPath, "utf-8"), {
18472
- base: config.deployment.base,
18655
+ base: config2.deployment.base,
18473
18656
  contentRoutePaths: project.manifest.routes.map((route) => route.path),
18474
- homeLinkHeader: buildHomeLinkHeader(config, routePaths),
18657
+ homeLinkHeader: buildHomeLinkHeader(config2, routePaths),
18475
18658
  homeTokens: home ? markdownTokenCount(agentMarkdown(home)) : undefined,
18476
- redirects: platformRedirects(config),
18659
+ redirects: platformRedirects(config2),
18477
18660
  routePaths
18478
18661
  });
18479
18662
  if (injected === null) {
@@ -18549,20 +18732,20 @@ var runClientAssetChecks = async (staticDir, args) => {
18549
18732
  process.exit(1);
18550
18733
  }
18551
18734
  };
18552
- var isolatedOutputDir = (config, context) => {
18553
- const { adapter, output } = config.deployment;
18735
+ var isolatedOutputDir = (config2, context) => {
18736
+ const { adapter, output } = config2.deployment;
18554
18737
  if (output === "server" && adapter === "vercel") {
18555
18738
  return join30(context.outDir, ".vercel", "output");
18556
18739
  }
18557
18740
  return context.distDir ?? join30(context.outDir, "dist");
18558
18741
  };
18559
- var isolatedStaticDir = (config, context) => {
18560
- const { adapter, output } = config.deployment;
18561
- const outputDir = isolatedOutputDir(config, context);
18742
+ var isolatedStaticDir = (config2, context) => {
18743
+ const { adapter, output } = config2.deployment;
18744
+ const outputDir = isolatedOutputDir(config2, context);
18562
18745
  if (output === "server" && adapter === "vercel") {
18563
18746
  return join30(outputDir, "static");
18564
18747
  }
18565
- if (servesClientSubdir(config.deployment)) {
18748
+ if (servesClientSubdir(config2.deployment)) {
18566
18749
  return join30(outputDir, "client");
18567
18750
  }
18568
18751
  return outputDir;
@@ -18603,10 +18786,10 @@ var publishBuildArtifacts = async (project, distDir, args) => {
18603
18786
  if (project.config.ai.llmsTxt.enabled) {
18604
18787
  await publishLlmsFiles(project, distDir);
18605
18788
  }
18606
- const sitemap = buildSitemap(project);
18607
- if (sitemap && !existsSync18(join30(distDir, "sitemap.xml"))) {
18608
- await writeFile7(join30(distDir, "sitemap.xml"), sitemap, "utf-8");
18609
- logger.success("Generated sitemap.xml");
18789
+ const sitemapFiles = buildSitemapFiles(project);
18790
+ if (sitemapFiles && !existsSync18(join30(distDir, "sitemap.xml"))) {
18791
+ await Promise.all(sitemapFiles.map((file) => writeFile7(join30(distDir, file.name), file.xml, "utf-8")));
18792
+ logger.success(sitemapFiles.length === 1 ? "Generated sitemap.xml" : `Generated sitemap.xml (index of ${sitemapFiles.length - 1} sitemap files)`);
18610
18793
  }
18611
18794
  const robots = buildRobots(project);
18612
18795
  if (robots && !existsSync18(join30(distDir, "robots.txt"))) {
@@ -18623,19 +18806,19 @@ var publishBuildArtifacts = async (project, distDir, args) => {
18623
18806
  await emitAgentSkills(project, distDir);
18624
18807
  await emitRedirectFiles(project.config, distDir);
18625
18808
  await emitHeaderFiles(project, distDir);
18626
- const { config } = project;
18627
- const features = serverFeatures(config);
18628
- const sitemapNote = config.seo.sitemap ? "no (set deployment.site)" : "no (seo.sitemap is false)";
18809
+ const { config: config2 } = project;
18810
+ const features = serverFeatures(config2);
18811
+ const sitemapNote = config2.seo.sitemap ? "no (set deployment.site)" : "no (seo.sitemap is false)";
18629
18812
  logger.box([
18630
- `Output ${config.deployment.output}`,
18631
- `Adapter ${config.deployment.adapter ?? "none"}`,
18632
- `Site ${config.deployment.site ?? "not set"}`,
18633
- `Search ${config.search.provider}`,
18634
- `Redirects ${config.redirects.length}`,
18635
- `Sitemap ${sitemap ? "yes" : sitemapNote}`,
18813
+ `Output ${config2.deployment.output}`,
18814
+ `Adapter ${config2.deployment.adapter ?? "none"}`,
18815
+ `Site ${config2.deployment.site ?? "not set"}`,
18816
+ `Search ${config2.search.provider}`,
18817
+ `Redirects ${config2.redirects.length}`,
18818
+ `Sitemap ${sitemapFiles ? "yes" : sitemapNote}`,
18636
18819
  `Robots ${robots ? "yes" : "no"}`,
18637
18820
  `Agent JSON ${agentReadability ? "yes" : "no"}`,
18638
- `LLM files ${config.ai.llmsTxt.enabled ? "yes" : "no"}`,
18821
+ `LLM files ${config2.ai.llmsTxt.enabled ? "yes" : "no"}`,
18639
18822
  `Server features ${features.length > 0 ? features.join(", ") : "none"}`
18640
18823
  ].join(`
18641
18824
  `));
@@ -18808,6 +18991,7 @@ var checkCommand = defineCommand4({
18808
18991
  import { dev } from "astro";
18809
18992
  import { watch } from "chokidar";
18810
18993
  import { defineCommand as defineCommand5 } from "citty";
18994
+ import { debounce } from "perfect-debounce";
18811
18995
 
18812
18996
  // src/astro/integration.ts
18813
18997
  var overlayServer = null;
@@ -18862,29 +19046,6 @@ var parsePort = (value) => {
18862
19046
  return port;
18863
19047
  };
18864
19048
 
18865
- // src/cli/coalesce.ts
18866
- var coalescedRunner = (task) => {
18867
- let inFlight = null;
18868
- let pending = false;
18869
- const cycle = async () => {
18870
- try {
18871
- do {
18872
- pending = false;
18873
- await task();
18874
- } while (pending);
18875
- } finally {
18876
- inFlight = null;
18877
- }
18878
- };
18879
- return () => {
18880
- if (inFlight) {
18881
- pending = true;
18882
- return;
18883
- }
18884
- inFlight = cycle();
18885
- };
18886
- };
18887
-
18888
19049
  // src/cli/commands/dev.ts
18889
19050
  var normalizeHost = (host) => host === "" ? true : host ?? false;
18890
19051
  var routeSignature = (routes) => routes.map((route) => `${route.path} ${route.entryId}`).toSorted().join(`
@@ -18952,7 +19113,7 @@ var devCommand = defineCommand5({
18952
19113
  }
18953
19114
  showBlumeErrorOverlay(project.diagnostics);
18954
19115
  let lastSignature = routeSignature(project.manifest.routes);
18955
- const runRegenerate = coalescedRunner(async () => {
19116
+ const regenerate = debounce(async () => {
18956
19117
  try {
18957
19118
  const next = await scanProject(root, {
18958
19119
  devServerUrl,
@@ -18975,16 +19136,9 @@ var devCommand = defineCommand5({
18975
19136
  } catch (error) {
18976
19137
  logger.error(`Regeneration failed: ${error.message}`);
18977
19138
  }
18978
- });
18979
- let timer = null;
18980
- const regenerate = () => {
18981
- if (timer) {
18982
- clearTimeout(timer);
18983
- }
18984
- timer = setTimeout(runRegenerate, 80);
18985
- };
19139
+ }, 80);
18986
19140
  if (boundPort !== port) {
18987
- runRegenerate();
19141
+ regenerate();
18988
19142
  }
18989
19143
  const dirTargets = [project.context.pagesRoot].filter((target) => target !== null);
18990
19144
  const fileTargets = [
@@ -19013,14 +19167,14 @@ var devCommand = defineCommand5({
19013
19167
  });
19014
19168
 
19015
19169
  // src/cli/commands/doctor.ts
19016
- import { readFileSync as readFileSync10 } from "node:fs";
19170
+ import { readFileSync as readFileSync9 } from "node:fs";
19017
19171
  import { defineCommand as defineCommand6 } from "citty";
19018
19172
  import { join as join32 } from "pathe";
19019
19173
  import { satisfies } from "semver";
19020
19174
  var FALLBACK_NODE_RANGE = ">=22.12.0";
19021
19175
  var supportedNodeRange = () => {
19022
19176
  try {
19023
- const pkg = JSON.parse(readFileSync10(join32(packageRoot(), "package.json"), "utf-8"));
19177
+ const pkg = JSON.parse(readFileSync9(join32(packageRoot(), "package.json"), "utf-8"));
19024
19178
  return pkg.engines?.node || FALLBACK_NODE_RANGE;
19025
19179
  } catch {
19026
19180
  return FALLBACK_NODE_RANGE;
@@ -19051,9 +19205,9 @@ var doctorCommand = defineCommand6({
19051
19205
  try {
19052
19206
  const project = await scanProject(root, { mode: "build" });
19053
19207
  diagnostics.push(...project.diagnostics);
19054
- const { config } = project;
19055
- const features = serverFeatures(config);
19056
- if (features.length > 0 && config.deployment.output === "static") {
19208
+ const { config: config2 } = project;
19209
+ const features = serverFeatures(config2);
19210
+ if (features.length > 0 && config2.deployment.output === "static") {
19057
19211
  diagnostics.push({
19058
19212
  code: "BLUME_SERVER_FEATURE_REQUIRED",
19059
19213
  message: `${features.join(", ")} require server output.`,
@@ -19061,7 +19215,7 @@ var doctorCommand = defineCommand6({
19061
19215
  suggestion: 'Set deployment.output to "server".'
19062
19216
  });
19063
19217
  }
19064
- if (config.deployment.output === "server" && !config.deployment.adapter) {
19218
+ if (config2.deployment.output === "server" && !config2.deployment.adapter) {
19065
19219
  diagnostics.push({
19066
19220
  code: "BLUME_ADAPTER_REQUIRED",
19067
19221
  message: "Server output requires an adapter.",
@@ -19071,8 +19225,8 @@ var doctorCommand = defineCommand6({
19071
19225
  }
19072
19226
  if (!args.json) {
19073
19227
  logger.info(`Pages: ${project.graph.pages.length}`);
19074
- logger.info(`Output: ${config.deployment.output}`);
19075
- logger.info(`Search: ${config.search.provider}`);
19228
+ logger.info(`Output: ${config2.deployment.output}`);
19229
+ logger.info(`Search: ${config2.search.provider}`);
19076
19230
  }
19077
19231
  } catch (error) {
19078
19232
  if (error instanceof BlumeError) {
@@ -19218,16 +19372,16 @@ var examplesPreviewFiles = (srcDir, basePath, hasExamples) => hasExamples ? [
19218
19372
  path: join33(srcDir, "pages", ...basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro")
19219
19373
  }
19220
19374
  ] : [];
19221
- var ejectIntegrationBridge = (config, root, configFile) => config.integrations.length > 0 && configFile ? { configFile: toPosix(relative15(root, configFile)) } : undefined;
19375
+ var ejectIntegrationBridge = (config2, root, configFile) => config2.integrations.length > 0 && configFile ? { configFile: toPosix(relative15(root, configFile)) } : undefined;
19222
19376
  var eject = async (root) => {
19223
19377
  const project = await scanProject(root, { mode: "build" });
19224
- const { context, config } = project;
19378
+ const { context, config: config2 } = project;
19225
19379
  const srcDir = join33(root, "src");
19226
19380
  const genDir = join33(srcDir, "generated");
19227
- const askEnabled = config.ai.ask?.enabled ?? false;
19228
- const exportPdf = config.export.pdf;
19229
- const exportEpub = config.export.epub;
19230
- const examples = await discoverExamples(root, config.examples.source);
19381
+ const askEnabled = config2.ai.ask?.enabled ?? false;
19382
+ const exportPdf = config2.export.pdf;
19383
+ const exportEpub = config2.export.epub;
19384
+ const examples = await discoverExamples(root, config2.examples.source);
19231
19385
  project.examples = exampleMarkdownLookup(examples.examples);
19232
19386
  const [
19233
19387
  pages,
@@ -19242,7 +19396,7 @@ var eject = async (root) => {
19242
19396
  detectNeedsReact(root),
19243
19397
  detectUsesMath(root),
19244
19398
  context.themeFile ? readFile18(context.themeFile, "utf-8") : Promise.resolve(""),
19245
- readExamplesCss(root, config.examples.css),
19399
+ readExamplesCss(root, config2.examples.css),
19246
19400
  buildRawMarkdown(project),
19247
19401
  discoverIslands(root)
19248
19402
  ]);
@@ -19274,13 +19428,13 @@ var eject = async (root) => {
19274
19428
  {
19275
19429
  content: astroConfigTemplate({
19276
19430
  askPath: "./src/generated/Ask.astro",
19277
- config,
19431
+ config: config2,
19278
19432
  contentRoutes: project.manifest.routes.map((route) => route.path),
19279
19433
  context: relContext,
19280
19434
  dataPath: "./src/generated/data.json",
19281
19435
  examplesPath: "./src/generated/examples.ts",
19282
19436
  examplesThemePath: "./src/generated/examples.css",
19283
- integrationBridge: ejectIntegrationBridge(config, root, context.configFile),
19437
+ integrationBridge: ejectIntegrationBridge(config2, root, context.configFile),
19284
19438
  needsReact,
19285
19439
  needsSvelte,
19286
19440
  needsVue,
@@ -19299,7 +19453,7 @@ var eject = async (root) => {
19299
19453
  { content: envTemplate(), path: join33(srcDir, "env.d.ts") },
19300
19454
  {
19301
19455
  content: contentConfigTemplate({
19302
- config,
19456
+ config: config2,
19303
19457
  context: relContext,
19304
19458
  staged: hasStaged,
19305
19459
  stagedBase: stagedDir
@@ -19324,12 +19478,12 @@ var eject = async (root) => {
19324
19478
  path: join33(genDir, "islands.ts")
19325
19479
  },
19326
19480
  {
19327
- content: exampleMapTemplate(examples.examples, config.basePath),
19481
+ content: exampleMapTemplate(examples.examples, config2.basePath),
19328
19482
  path: join33(genDir, "examples.ts")
19329
19483
  },
19330
19484
  {
19331
19485
  content: examplesEntryTemplate({
19332
- configTokens: buildThemeCss(config.theme),
19486
+ configTokens: buildThemeCss(config2.theme),
19333
19487
  sources: ["../../**/*.{astro,jsx,svelte,ts,tsx,vue}"],
19334
19488
  userCss: userExamplesCss
19335
19489
  }),
@@ -19337,7 +19491,7 @@ var eject = async (root) => {
19337
19491
  },
19338
19492
  {
19339
19493
  content: tailwindEntryTemplate({
19340
- configTokens: buildThemeCss(config.theme),
19494
+ configTokens: buildThemeCss(config2.theme),
19341
19495
  sources: [
19342
19496
  blumeSourceGlob(root, genDir),
19343
19497
  "../../**/*.{astro,mdx,ts,tsx}"
@@ -19374,9 +19528,9 @@ var eject = async (root) => {
19374
19528
  if (askEnabled) {
19375
19529
  files.push(...await askFiles(project, srcDir, genDir));
19376
19530
  }
19377
- if (config.seo.og.enabled) {
19531
+ if (config2.seo.og.enabled) {
19378
19532
  files.push({
19379
- content: ogEndpointTemplate(customOgRoutes(pages, config.title, config.seo.og.titles)),
19533
+ content: ogEndpointTemplate(customOgRoutes(pages, config2.title, config2.seo.og.titles)),
19380
19534
  path: join33(srcDir, "pages", "og", "[...slug].png.ts")
19381
19535
  });
19382
19536
  }
@@ -19393,10 +19547,10 @@ var eject = async (root) => {
19393
19547
  });
19394
19548
  }
19395
19549
  files.push({
19396
- content: searchClientTemplate(config),
19550
+ content: searchClientTemplate(config2),
19397
19551
  path: join33(genDir, "search-client.ts")
19398
19552
  });
19399
- if (servesStaticIndex(config.search.provider)) {
19553
+ if (servesStaticIndex(config2.search.provider)) {
19400
19554
  const documents = await buildSearchDocuments(project);
19401
19555
  files.push({
19402
19556
  content: `${JSON.stringify(documents)}
@@ -19407,9 +19561,9 @@ var eject = async (root) => {
19407
19561
  path: join33(srcDir, "pages", "blume-search.json.ts")
19408
19562
  });
19409
19563
  }
19410
- if (config.search.provider === "mixedbread") {
19564
+ if (config2.search.provider === "mixedbread") {
19411
19565
  files.push({
19412
- content: mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""),
19566
+ content: mixedbreadSearchEndpointTemplate(config2.search.mixedbread?.storeId ?? ""),
19413
19567
  path: join33(srcDir, "pages", "api", "search.ts")
19414
19568
  });
19415
19569
  }
@@ -19426,9 +19580,9 @@ var eject = async (root) => {
19426
19580
  });
19427
19581
  }
19428
19582
  const warnings = [];
19429
- if (hasScalarReferences(config)) {
19583
+ if (hasScalarReferences(config2)) {
19430
19584
  const references = await buildReferenceFiles({
19431
- config,
19585
+ config: config2,
19432
19586
  contentRoutes: new Set(project.graph.pages.map((page) => page.route)),
19433
19587
  root
19434
19588
  });
@@ -19446,7 +19600,7 @@ var eject = async (root) => {
19446
19600
  })), ...examples.examples.map((example) => ({
19447
19601
  content: exampleWrapperTemplate(example),
19448
19602
  path: join33(genDir, "examples", `${exampleSlug(example.path)}.astro`)
19449
- })), ...examplesPreviewFiles(srcDir, config.basePath, examples.examples.length > 0));
19603
+ })), ...examplesPreviewFiles(srcDir, config2.basePath, examples.examples.length > 0));
19450
19604
  for (const [entryId, content] of staged) {
19451
19605
  files.push({ content, path: join33(root, stagedDir, entryId) });
19452
19606
  }
@@ -19468,27 +19622,27 @@ var eject = async (root) => {
19468
19622
  // src/cli/eject-scripts.ts
19469
19623
  import { readFile as readFile19, writeFile as writeFile9 } from "node:fs/promises";
19470
19624
  import { join as join34 } from "pathe";
19471
- var droppedArtifactNotices = (config) => {
19625
+ var droppedArtifactNotices = (config2) => {
19472
19626
  const notices = [];
19473
- if (config.search.provider === "pagefind") {
19627
+ if (config2.search.provider === "pagefind") {
19474
19628
  notices.push('the Pagefind search index — the search UI loads it from the built site, so search will break in production. Add a post-build step: `"build": "astro build && pagefind --site dist"` (with `pagefind` installed as a devDependency).');
19475
19629
  }
19476
- if (searchProviderMeta(config.search.provider).syncs) {
19477
- notices.push(`the hosted ${config.search.provider} index sync — new and updated pages stop being pushed; re-upload your search records after each build with the provider's API or CLI.`);
19630
+ if (searchProviderMeta(config2.search.provider).syncs) {
19631
+ notices.push(`the hosted ${config2.search.provider} index sync — new and updated pages stop being pushed; re-upload your search records after each build with the provider's API or CLI.`);
19478
19632
  }
19479
- if (config.ai.llmsTxt.enabled) {
19633
+ if (config2.ai.llmsTxt.enabled) {
19480
19634
  notices.push("llms.txt and llms-full.txt");
19481
19635
  }
19482
- if (config.deployment.site && config.seo.sitemap) {
19636
+ if (config2.deployment.site && config2.seo.sitemap) {
19483
19637
  notices.push("sitemap.xml — recreate it with the @astrojs/sitemap integration.");
19484
19638
  }
19485
- if (config.seo.robots) {
19639
+ if (config2.seo.robots) {
19486
19640
  notices.push("robots.txt — recreate it as a public/robots.txt file.");
19487
19641
  }
19488
- if (config.seo.agentReadability) {
19642
+ if (config2.seo.agentReadability) {
19489
19643
  notices.push("agent-readability.json");
19490
19644
  }
19491
- if (config.redirects.length > 0 && config.deployment.output === "static") {
19645
+ if (config2.redirects.length > 0 && config2.deployment.output === "static") {
19492
19646
  notices.push("the platform redirect files (_redirects, vercel.json) — your redirects still work as Astro-generated meta-refresh pages.");
19493
19647
  }
19494
19648
  return notices;
@@ -19515,6 +19669,7 @@ var updatePackageScripts = async (root) => {
19515
19669
  // src/cli/init/scaffold.ts
19516
19670
  import { existsSync as existsSync21 } from "node:fs";
19517
19671
  import { mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
19672
+ import { detect } from "package-manager-detector/detect";
19518
19673
  import { basename as basename4, dirname as dirname14, isAbsolute as isAbsolute9, join as join35, relative as relative16 } from "pathe";
19519
19674
 
19520
19675
  // src/core/package-json.ts
@@ -19643,6 +19798,11 @@ var detectPackageManager = (userAgent) => {
19643
19798
  const name = userAgent?.split("/")[0];
19644
19799
  return name !== undefined && PACKAGE_MANAGERS.includes(name) ? name : "npm";
19645
19800
  };
19801
+ var detectProjectPackageManager = async (root) => {
19802
+ const detected = await detect({ cwd: root });
19803
+ const name = detected?.name;
19804
+ return name !== undefined && PACKAGE_MANAGERS.includes(name) ? name : detectPackageManager(process.env.npm_config_user_agent);
19805
+ };
19646
19806
  var validateContentDir = (root, dir) => isAbsolute9(dir) || relative16(root, join35(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
19647
19807
  var titleize = (raw) => {
19648
19808
  const words = raw.replaceAll(/[-_.]+/gu, " ").split(/\s+/u).filter(Boolean);
@@ -19787,8 +19947,8 @@ var ejectCommand = defineCommand7({
19787
19947
  refuseIfDevRunning(root, "ejecting");
19788
19948
  let notices = [];
19789
19949
  try {
19790
- const { config } = await loadConfig(root);
19791
- notices = droppedArtifactNotices(config);
19950
+ const { config: config2 } = await loadConfig(root);
19951
+ notices = droppedArtifactNotices(config2);
19792
19952
  } catch {}
19793
19953
  if (!args.yes) {
19794
19954
  logger.warn("Eject is one-way: it writes astro.config.mjs, src/, and (if absent) tsconfig.json, rewrites your package.json scripts, and removes .blume. An existing tsconfig.json is left untouched.");
@@ -19807,7 +19967,7 @@ var ejectCommand = defineCommand7({
19807
19967
  `);
19808
19968
  }
19809
19969
  reportDroppedArtifacts(notices);
19810
- const pm = detectPackageManager(process.env.npm_config_user_agent);
19970
+ const pm = await detectProjectPackageManager(root);
19811
19971
  const { build: build2, dev: dev2 } = commandsFor(pm);
19812
19972
  logger.box(`Your project is now a standalone Astro app.
19813
19973
 
@@ -19892,6 +20052,20 @@ import { mkdtemp as mkdtemp2, writeFile as writeFile11 } from "node:fs/promises"
19892
20052
  import { tmpdir as tmpdir2 } from "node:os";
19893
20053
  import { colors as colors4 } from "consola/utils";
19894
20054
  import { join as join36, relative as relative18 } from "pathe";
20055
+
20056
+ // src/cli/report-format.ts
20057
+ var seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
20058
+ var money = (cost) => cost === undefined ? "" : `$${cost.toFixed(2)}`;
20059
+ var duration = (ms) => {
20060
+ if (ms < 60000) {
20061
+ return seconds(ms);
20062
+ }
20063
+ const minutes = Math.floor(ms / 60000);
20064
+ const rest = Math.round(ms % 60000 / 1000);
20065
+ return `${minutes}m ${rest}s`;
20066
+ };
20067
+
20068
+ // src/eval/report.ts
19895
20069
  var GLYPH2 = {
19896
20070
  error: "!",
19897
20071
  fail: "✖",
@@ -19905,16 +20079,6 @@ var STATUS_COLOR = {
19905
20079
  skip: colors4.dim
19906
20080
  };
19907
20081
  var ID_PAD = 28;
19908
- var seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
19909
- var money = (cost) => cost === undefined ? "" : `$${cost.toFixed(2)}`;
19910
- var duration = (ms) => {
19911
- if (ms < 60000) {
19912
- return seconds(ms);
19913
- }
19914
- const minutes = Math.floor(ms / 60000);
19915
- const rest = Math.round(ms % 60000 / 1000);
19916
- return `${minutes}m ${rest}s`;
19917
- };
19918
20082
  var questionLine = (result) => {
19919
20083
  const color = STATUS_COLOR[result.status];
19920
20084
  const glyph = color(GLYPH2[result.status]);
@@ -19999,8 +20163,8 @@ import { tmpdir as tmpdir3 } from "node:os";
19999
20163
  import { join as join38 } from "pathe";
20000
20164
 
20001
20165
  // src/eval/agents.ts
20002
- import { spawn as spawn2 } from "node:child_process";
20003
20166
  import { readFile as readFile20, writeFile as writeFile12 } from "node:fs/promises";
20167
+ import spawn2 from "cross-spawn";
20004
20168
  import { join as join37 } from "pathe";
20005
20169
  import { z as z3 } from "zod";
20006
20170
  var KILL_GRACE_MS = 5000;
@@ -20024,19 +20188,17 @@ var DISALLOWED_TOOLS = [
20024
20188
  "Task"
20025
20189
  ];
20026
20190
  var runAgentHeadless = (bin, args, options) => new Promise((resolve11, reject) => {
20027
- const platform = options.platform ?? process.platform;
20028
20191
  const child = spawn2(bin, args, {
20029
20192
  cwd: options.cwd,
20030
- shell: platform === "win32",
20031
20193
  stdio: ["pipe", "pipe", "pipe"]
20032
20194
  });
20033
20195
  let stdout = "";
20034
20196
  let stderr = "";
20035
20197
  let timedOut = false;
20036
- child.stdout.on("data", (chunk) => {
20198
+ child.stdout?.on("data", (chunk) => {
20037
20199
  stdout += chunk.toString("utf-8");
20038
20200
  });
20039
- child.stderr.on("data", (chunk) => {
20201
+ child.stderr?.on("data", (chunk) => {
20040
20202
  stderr += chunk.toString("utf-8");
20041
20203
  });
20042
20204
  const deadline = setTimeout(() => {
@@ -20060,7 +20222,7 @@ var runAgentHeadless = (bin, args, options) => new Promise((resolve11, reject) =
20060
20222
  resolve11({ code: code ?? 1, stderr, stdout, timedOut });
20061
20223
  }
20062
20224
  });
20063
- child.stdin.end(options.prompt);
20225
+ child.stdin?.end(options.prompt);
20064
20226
  });
20065
20227
  var writeMcpConfig = async (dir, snapshotPath, launcher) => {
20066
20228
  const resolved = launcher ?? {
@@ -20068,12 +20230,12 @@ var writeMcpConfig = async (dir, snapshotPath, launcher) => {
20068
20230
  command: process.execPath
20069
20231
  };
20070
20232
  const configPath = join37(dir, "mcp-config.json");
20071
- const config = {
20233
+ const config2 = {
20072
20234
  mcpServers: {
20073
20235
  [MCP_SERVER_NAME]: { args: resolved.args, command: resolved.command }
20074
20236
  }
20075
20237
  };
20076
- await writeFile12(configPath, JSON.stringify(config, null, 2));
20238
+ await writeFile12(configPath, JSON.stringify(config2, null, 2));
20077
20239
  return {
20078
20240
  configPath,
20079
20241
  serverArgs: resolved.args,
@@ -20175,17 +20337,17 @@ var readAgentOutput = async (kind, result, lastMessagePath) => {
20175
20337
  text: payload.data.result
20176
20338
  };
20177
20339
  }
20178
- let text2;
20340
+ let text;
20179
20341
  try {
20180
20342
  const raw = await readFile20(lastMessagePath, "utf-8");
20181
- text2 = raw.trim();
20343
+ text = raw.trim();
20182
20344
  } catch {
20183
20345
  return { detail: "no last message written", isError: true, text: "" };
20184
20346
  }
20185
- if (text2 === "") {
20347
+ if (text === "") {
20186
20348
  return { detail: "empty last message", isError: true, text: "" };
20187
20349
  }
20188
- return { isError: false, text: text2 };
20350
+ return { isError: false, text };
20189
20351
  };
20190
20352
  var verdictSchema = z3.object({
20191
20353
  missing: z3.array(z3.string()).default([]),
@@ -20193,15 +20355,15 @@ var verdictSchema = z3.object({
20193
20355
  pass: z3.boolean(),
20194
20356
  score: z3.number().min(0).max(1).optional()
20195
20357
  });
20196
- var parseVerdict = (text2) => {
20197
- const start = text2.indexOf("{");
20198
- const end = text2.lastIndexOf("}");
20358
+ var parseVerdict = (text) => {
20359
+ const start = text.indexOf("{");
20360
+ const end = text.lastIndexOf("}");
20199
20361
  if (start === -1 || end <= start) {
20200
20362
  return;
20201
20363
  }
20202
20364
  let parsed;
20203
20365
  try {
20204
- parsed = JSON.parse(text2.slice(start, end + 1));
20366
+ parsed = JSON.parse(text.slice(start, end + 1));
20205
20367
  } catch {
20206
20368
  return;
20207
20369
  }
@@ -20480,21 +20642,21 @@ var runEval = async (options) => {
20480
20642
  var DEFAULT_FILE = "evals.yaml";
20481
20643
  var DEFAULT_TIMEOUT_S = 180;
20482
20644
  var isAgentKind = (value) => (value in AGENTS);
20483
- var launchAgentCode2 = async (bin, prompt) => {
20645
+ var notInstalled = (agent) => {
20646
+ const cli = AGENTS[agent];
20647
+ logger.error(`${cli.name} (\`${cli.bin}\`) was not found on PATH. Install it with \`${cli.install}\`.`);
20648
+ return process.exit(1);
20649
+ };
20650
+ var launchAgentCode2 = async (agent, prompt) => {
20484
20651
  try {
20485
- return await launchAgent(bin, prompt);
20652
+ return await launchAgent(AGENTS[agent].bin, prompt);
20486
20653
  } catch (error) {
20487
20654
  if (error?.code !== "ENOENT") {
20488
20655
  throw error;
20489
20656
  }
20490
- return WINDOWS_COMMAND_NOT_FOUND;
20657
+ return notInstalled(agent);
20491
20658
  }
20492
20659
  };
20493
- var notInstalled = (agent) => {
20494
- const cli = AGENTS[agent];
20495
- logger.error(`${cli.name} (\`${cli.bin}\`) was not found on PATH. Install it with \`${cli.install}\`.`);
20496
- return process.exit(1);
20497
- };
20498
20660
  var passFraction = (result) => {
20499
20661
  const ran = result.results.length - result.counts.skip;
20500
20662
  return ran === 0 ? 1 : result.counts.pass / ran;
@@ -20534,10 +20696,7 @@ var runFixHandoff = async (agent, result, root, threshold) => {
20534
20696
  process.stderr.write(` Handing ${count} failed question${count === 1 ? "" : "s"} to ${cli.name}…
20535
20697
 
20536
20698
  `);
20537
- const code = await launchAgentCode2(cli.bin, evalFixPrompt(report));
20538
- if (code === WINDOWS_COMMAND_NOT_FOUND) {
20539
- notInstalled(agent);
20540
- }
20699
+ const code = await launchAgentCode2(agent, evalFixPrompt(report));
20541
20700
  if (code !== 0) {
20542
20701
  process.exit(code);
20543
20702
  }
@@ -20548,10 +20707,7 @@ var runInit = async (agent, file) => {
20548
20707
  logger.error(`${file} already exists — edit it directly, or pass --file to draft elsewhere.`);
20549
20708
  process.exit(1);
20550
20709
  }
20551
- const code = await launchAgentCode2(AGENTS[agent].bin, initPrompt(file));
20552
- if (code === WINDOWS_COMMAND_NOT_FOUND) {
20553
- notInstalled(agent);
20554
- }
20710
+ const code = await launchAgentCode2(agent, initPrompt(file));
20555
20711
  if (code !== 0) {
20556
20712
  process.exit(code);
20557
20713
  }
@@ -20909,6 +21065,7 @@ import {
20909
21065
  CallToolRequestSchema,
20910
21066
  ListToolsRequestSchema
20911
21067
  } from "@modelcontextprotocol/sdk/types.js";
21068
+ import { z as z5 } from "zod";
20912
21069
 
20913
21070
  // src/search/orama-index.ts
20914
21071
  import { create, insertMultiple, search } from "@orama/orama";
@@ -20926,6 +21083,7 @@ var BOOST = { description: 2, title: 3 };
20926
21083
  var SEGMENTED_LANGUAGES = new Set(["ja", "ko", "th", "zh"]);
20927
21084
  var BIGRAM_LANGUAGES = new Set(["ja", "zh"]);
20928
21085
  var BIGRAM_SCRIPTS = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}々〆〇ー゙゚]+$/u;
21086
+ var TERM = /[\p{L}\p{M}\p{N}]+(?:(?:['’](?=\p{L})|(?<=\p{N})[.,](?=\p{N}))[\p{L}\p{M}\p{N}]+)*/gu;
20929
21087
  var addBigrams = (run, tokens) => {
20930
21088
  const characters = [...run];
20931
21089
  if (characters.length === 1) {
@@ -20962,17 +21120,30 @@ var segmentingTokenizer = (locale) => {
20962
21120
  run = "";
20963
21121
  }
20964
21122
  };
20965
- for (const segment of segmenter.segment(raw.toLowerCase())) {
21123
+ const take = (term) => {
21124
+ if (bigram && BIGRAM_SCRIPTS.test(term)) {
21125
+ run += term;
21126
+ return;
21127
+ }
21128
+ flush();
21129
+ tokens.add(term);
21130
+ };
21131
+ for (const segment of segmenter.segment(raw.normalize("NFC").toLowerCase())) {
20966
21132
  if (!segment.isWordLike) {
20967
21133
  flush();
20968
21134
  continue;
20969
21135
  }
20970
- if (bigram && BIGRAM_SCRIPTS.test(segment.segment)) {
20971
- run += segment.segment;
20972
- continue;
21136
+ let end = 0;
21137
+ for (const match of segment.segment.matchAll(TERM)) {
21138
+ if (match.index > end) {
21139
+ flush();
21140
+ }
21141
+ take(match[0]);
21142
+ end = match.index + match[0].length;
21143
+ }
21144
+ if (end < segment.segment.length) {
21145
+ flush();
20973
21146
  }
20974
- flush();
20975
- tokens.add(segment.segment);
20976
21147
  }
20977
21148
  flush();
20978
21149
  return [...tokens];
@@ -21013,78 +21184,54 @@ var queryOramaIndex = async (db, term, limit, filters) => {
21013
21184
  var DEFAULT_SEARCH_LIMIT = 8;
21014
21185
  var MAX_SEARCH_LIMIT = 20;
21015
21186
  var EXCERPT_LENGTH = 200;
21016
- var CONTENT_TYPES_SCHEMA = {
21017
- description: 'Only include pages of these content types (frontmatter `type`, e.g. `["doc", "rfc"]`). `list_pages` shows each page\'s type. Omit to include every type.',
21018
- items: { type: "string" },
21019
- type: "array"
21020
- };
21021
- var FILTERS_SCHEMA = {
21022
- additionalProperties: { type: "string" },
21023
- description: 'Only include pages matching every facet, key → required value (e.g. `{"status": "enforced"}`). Facets are metadata the site declares per content type; `list_pages` shows each page\'s facet values. Omit for no facet filtering.',
21024
- type: "object"
21025
- };
21026
- var INPUT_SCHEMAS = {
21027
- get_navigation: { properties: {}, type: "object" },
21028
- get_page: {
21029
- properties: {
21030
- route: {
21031
- description: "The page route, e.g. `/guides/install`.",
21032
- type: "string"
21033
- }
21034
- },
21035
- required: ["route"],
21036
- type: "object"
21037
- },
21038
- list_pages: {
21039
- properties: {
21040
- contentTypes: CONTENT_TYPES_SCHEMA,
21041
- filters: FILTERS_SCHEMA
21042
- },
21043
- type: "object"
21044
- },
21045
- search_docs: {
21046
- properties: {
21047
- contentTypes: CONTENT_TYPES_SCHEMA,
21048
- filters: FILTERS_SCHEMA,
21049
- limit: {
21050
- description: `Maximum hits to return (default ${DEFAULT_SEARCH_LIMIT}).`,
21051
- maximum: MAX_SEARCH_LIMIT,
21052
- minimum: 1,
21053
- type: "integer"
21054
- },
21055
- query: { description: "The search query.", type: "string" }
21056
- },
21057
- required: ["query"],
21058
- type: "object"
21187
+ var contentTypesField = z5.preprocess((value) => {
21188
+ const list2 = (Array.isArray(value) ? value : [value]).filter((entry) => typeof entry === "string");
21189
+ return list2.length > 0 ? list2 : undefined;
21190
+ }, z5.array(z5.string()).optional().describe('Only include pages of these content types (frontmatter `type`, e.g. `["doc", "rfc"]`). `list_pages` shows each page\'s type. Omit to include every type.'));
21191
+ var filtersField = z5.preprocess((value) => {
21192
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
21193
+ return;
21059
21194
  }
21195
+ const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
21196
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
21197
+ }, z5.record(z5.string(), z5.string()).optional().describe('Only include pages matching every facet, key → required value (e.g. `{"status": "enforced"}`). Facets are metadata the site declares per content type; `list_pages` shows each page\'s facet values. Omit for no facet filtering.'));
21198
+ var limitField = z5.preprocess((value) => {
21199
+ const num = typeof value === "number" ? value : Number(value);
21200
+ return Number.isFinite(num) ? Math.min(Math.max(Math.trunc(num), 1), MAX_SEARCH_LIMIT) : undefined;
21201
+ }, z5.int().min(1).max(MAX_SEARCH_LIMIT).optional().describe(`Maximum hits to return (default ${DEFAULT_SEARCH_LIMIT}).`));
21202
+ var textField = (description) => z5.preprocess((value) => typeof value === "string" ? value : "", z5.string().describe(description));
21203
+ var TOOL_INPUTS = {
21204
+ get_navigation: z5.object({}),
21205
+ get_page: z5.object({
21206
+ route: textField("The page route, e.g. `/guides/install`.")
21207
+ }),
21208
+ list_pages: z5.object({
21209
+ contentTypes: contentTypesField,
21210
+ filters: filtersField
21211
+ }),
21212
+ search_docs: z5.object({
21213
+ contentTypes: contentTypesField,
21214
+ filters: filtersField,
21215
+ limit: limitField,
21216
+ query: textField("The search query.")
21217
+ })
21218
+ };
21219
+ var inputSchemaFor = (schema) => {
21220
+ const {
21221
+ $schema: _dialect,
21222
+ additionalProperties: _closed,
21223
+ ...rest
21224
+ } = z5.toJSONSchema(schema);
21225
+ return rest;
21060
21226
  };
21061
21227
  var TOOL_DEFINITIONS = MCP_TOOLS.map((tool) => ({
21062
21228
  annotations: tool.annotations,
21063
21229
  description: tool.description,
21064
- inputSchema: INPUT_SCHEMAS[tool.name],
21230
+ inputSchema: inputSchemaFor(TOOL_INPUTS[tool.name]),
21065
21231
  name: tool.name,
21066
21232
  title: tool.title
21067
21233
  }));
21068
- var asString2 = (value) => typeof value === "string" ? value : "";
21069
- var asContentTypes = (value) => {
21070
- const list2 = Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [value].filter((entry) => typeof entry === "string");
21071
- return list2.length > 0 ? list2 : undefined;
21072
- };
21073
- var asFacetFilters = (value) => {
21074
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
21075
- return;
21076
- }
21077
- const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
21078
- return entries.length > 0 ? Object.fromEntries(entries) : undefined;
21079
- };
21080
21234
  var matchesFacets = (facets, filters) => Object.entries(filters).every(([key, value]) => facets?.[key] === value);
21081
- var asLimit = (value) => {
21082
- const num = typeof value === "number" ? value : Number(value);
21083
- if (!Number.isFinite(num)) {
21084
- return DEFAULT_SEARCH_LIMIT;
21085
- }
21086
- return Math.min(Math.max(Math.trunc(num), 1), MAX_SEARCH_LIMIT);
21087
- };
21088
21235
  var normalizeRoute2 = (input, data) => {
21089
21236
  let value = input.trim();
21090
21237
  if (/^https?:\/\//iu.test(value)) {
@@ -21095,15 +21242,12 @@ var normalizeRoute2 = (input, data) => {
21095
21242
  try {
21096
21243
  value = decodeURI(value);
21097
21244
  } catch {}
21098
- const noTrailing = value.replace(/\/+$/u, "");
21099
- const noSuffix = noTrailing.replace(/\.mdx?$/u, "");
21100
- const withSlash = noSuffix.startsWith("/") ? noSuffix : `/${noSuffix}`;
21101
- const based = stripBasePath(data.base, withSlash);
21102
- return based === "" ? "/" : based;
21245
+ const noSuffix = trimEnd(value, "/").replace(/\.mdx?$/u, "");
21246
+ return stripBasePath(data.base, normalizeRoute(noSuffix));
21103
21247
  };
21104
21248
  var urlFor = (route, data) => {
21105
21249
  const path = withBasePath(data.base, route);
21106
- return data.site ? `${data.site.replace(/\/+$/u, "")}${path}` : path;
21250
+ return data.site ? absoluteUrl(data.site, path) : path;
21107
21251
  };
21108
21252
  var excerptFor = (doc) => {
21109
21253
  if (doc.description) {
@@ -21112,7 +21256,7 @@ var excerptFor = (doc) => {
21112
21256
  const head = doc.content.slice(0, EXCERPT_LENGTH).trim();
21113
21257
  return doc.content.length > EXCERPT_LENGTH ? `${head}…` : head;
21114
21258
  };
21115
- var text2 = (value, isError = false) => ({
21259
+ var text = (value, isError = false) => ({
21116
21260
  content: [{ text: value, type: "text" }],
21117
21261
  ...isError ? { isError: true } : {}
21118
21262
  });
@@ -21134,10 +21278,11 @@ var buildServer = (data, index) => {
21134
21278
  server.setRequestHandler(CallToolRequestSchema, async (request2) => {
21135
21279
  const { arguments: args = {}, name } = request2.params;
21136
21280
  if (name === "search_docs") {
21281
+ const input = TOOL_INPUTS.search_docs.parse(args);
21137
21282
  const db = await index();
21138
- const hits = await queryOramaIndex(db, asString2(args.query), asLimit(args.limit), {
21139
- contentTypes: asContentTypes(args.contentTypes),
21140
- facets: asFacetFilters(args.filters)
21283
+ const hits = await queryOramaIndex(db, input.query, input.limit ?? DEFAULT_SEARCH_LIMIT, {
21284
+ contentTypes: input.contentTypes,
21285
+ facets: input.filters
21141
21286
  });
21142
21287
  const results = hits.map((doc) => ({
21143
21288
  contentType: doc.contentType,
@@ -21147,21 +21292,21 @@ var buildServer = (data, index) => {
21147
21292
  title: doc.title,
21148
21293
  url: urlFor(doc.route, data)
21149
21294
  }));
21150
- return text2(JSON.stringify(results, null, 2));
21295
+ return text(JSON.stringify(results, null, 2));
21151
21296
  }
21152
21297
  if (name === "get_page") {
21153
- const key = normalizeRoute2(asString2(args.route), data);
21298
+ const input = TOOL_INPUTS.get_page.parse(args);
21299
+ const key = normalizeRoute2(input.route, data);
21154
21300
  const markdown = data.pages[key];
21155
21301
  if (markdown === undefined) {
21156
- return text2(`No page found at "${key}". Use list_pages or search_docs to find valid routes.`, true);
21302
+ return text(`No page found at "${key}". Use list_pages or search_docs to find valid routes.`, true);
21157
21303
  }
21158
- return text2(markdown);
21304
+ return text(markdown);
21159
21305
  }
21160
21306
  if (name === "list_pages") {
21161
- const contentTypes = asContentTypes(args.contentTypes);
21162
- const filters = asFacetFilters(args.filters);
21307
+ const { contentTypes, filters } = TOOL_INPUTS.list_pages.parse(args);
21163
21308
  const routes = data.routes.filter((route) => (!contentTypes || contentTypes.includes(route.contentType)) && (!filters || matchesFacets(route.facets, filters)));
21164
- return text2(JSON.stringify(routes.map((route) => ({
21309
+ return text(JSON.stringify(routes.map((route) => ({
21165
21310
  contentType: route.contentType,
21166
21311
  description: route.description,
21167
21312
  facets: route.facets,
@@ -21172,9 +21317,9 @@ var buildServer = (data, index) => {
21172
21317
  })), null, 2));
21173
21318
  }
21174
21319
  if (name === "get_navigation") {
21175
- return text2(JSON.stringify(data.navigation, null, 2));
21320
+ return text(JSON.stringify(data.navigation, null, 2));
21176
21321
  }
21177
- return text2(`Unknown tool: ${name}`, true);
21322
+ return text(`Unknown tool: ${name}`, true);
21178
21323
  });
21179
21324
  return server;
21180
21325
  };
@@ -21233,8 +21378,8 @@ var previewCommand = defineCommand11({
21233
21378
  },
21234
21379
  async run({ args }) {
21235
21380
  const root = process.cwd();
21236
- const { config } = await loadConfig(root);
21237
- const context = resolveProjectContext(root, config);
21381
+ const { config: config2 } = await loadConfig(root);
21382
+ const context = resolveProjectContext(root, config2);
21238
21383
  if (!existsSync23(join40(context.outDir, "astro.config.mjs"))) {
21239
21384
  logger.error("No build found. Run `blume build` first.");
21240
21385
  process.exit(1);
@@ -21273,8 +21418,8 @@ var syncCommand = defineCommand12({
21273
21418
  async run({ args }) {
21274
21419
  const root = process.cwd();
21275
21420
  if (args.force) {
21276
- const { config } = await loadConfig(root);
21277
- const context = resolveProjectContext(root, config);
21421
+ const { config: config2 } = await loadConfig(root);
21422
+ const context = resolveProjectContext(root, config2);
21278
21423
  await rm4(join41(context.outDir, "cache"), { force: true, recursive: true });
21279
21424
  logger.info("Cleared source cache.");
21280
21425
  }
@@ -21321,20 +21466,20 @@ var codexArgs2 = (lastMessagePath) => [
21321
21466
  var translateAgentArgs = (kind, lastMessagePath) => kind === "claude" ? claudeArgs2() : codexArgs2(lastMessagePath);
21322
21467
 
21323
21468
  // src/translate/ledger.ts
21324
- import { createHash as createHash4 } from "node:crypto";
21469
+ import { createHash as createHash5 } from "node:crypto";
21325
21470
  import { readFile as readFile23 } from "node:fs/promises";
21326
21471
  import { join as join42 } from "pathe";
21327
- import { z as z5 } from "zod";
21472
+ import { z as z6 } from "zod";
21328
21473
  var LEDGER_FILE = "blume.translations.json";
21329
- var ledgerSchema = z5.object({
21330
- files: z5.record(z5.string(), z5.record(z5.string(), z5.string())),
21331
- version: z5.literal(1)
21474
+ var ledgerSchema = z6.object({
21475
+ files: z6.record(z6.string(), z6.record(z6.string(), z6.string())),
21476
+ version: z6.literal(1)
21332
21477
  });
21333
21478
  var emptyLedger = () => ({
21334
21479
  files: {},
21335
21480
  version: 1
21336
21481
  });
21337
- var hashSource = (text3) => createHash4("sha256").update(text3).digest("hex").slice(0, 16);
21482
+ var hashSource = (text2) => createHash5("sha256").update(text2).digest("hex").slice(0, 16);
21338
21483
  var readLedger = async (root) => {
21339
21484
  let raw;
21340
21485
  try {
@@ -21421,16 +21566,6 @@ var SPINNER_FRAMES = [
21421
21566
  ];
21422
21567
  var SPINNER_INTERVAL_MS = 80;
21423
21568
  var REWRITE = `\r${ESC}[K`;
21424
- var seconds2 = (ms) => `${(ms / 1000).toFixed(1)}s`;
21425
- var money2 = (cost) => cost === undefined ? "" : `$${cost.toFixed(2)}`;
21426
- var duration2 = (ms) => {
21427
- if (ms < 60000) {
21428
- return seconds2(ms);
21429
- }
21430
- const minutes = Math.floor(ms / 60000);
21431
- const rest = Math.round(ms % 60000 / 1000);
21432
- return `${minutes}m ${rest}s`;
21433
- };
21434
21569
  var itemLabel = (item) => item.kind === "page" ? `${item.sourceRel} → ${item.locale}` : `meta title${item.entries.length === 1 ? "" : "s"} (${item.entries.length}) → ${item.locale}`;
21435
21570
  var spinnerLine = (active, done, total, frame) => {
21436
21571
  const first = active[0];
@@ -21442,7 +21577,7 @@ var itemEndLine = (result) => {
21442
21577
  const glyph = color(GLYPH3[result.status]);
21443
21578
  const label = itemLabel(result.item);
21444
21579
  if (result.status === "translated") {
21445
- const cells = [seconds2(result.durationMs), money2(result.costUsd)].filter((cell) => cell !== "").join(" ");
21580
+ const cells = [seconds(result.durationMs), money(result.costUsd)].filter((cell) => cell !== "").join(" ");
21446
21581
  return ` ${glyph} ${label} ${colors5.dim(cells)}`;
21447
21582
  }
21448
21583
  const word = result.status === "partial" ? "partial" : "failed";
@@ -21457,8 +21592,8 @@ var translateSummaryLine = (result, workList) => {
21457
21592
  counts.partial > 0 ? `${counts.partial} partial` : "",
21458
21593
  workList.untracked.length > 0 ? `${workList.untracked.length} adopted` : "",
21459
21594
  workList.upToDate > 0 ? `${workList.upToDate} already up to date` : "",
21460
- duration2(result.durationMs),
21461
- money2(result.costUsd)
21595
+ duration(result.durationMs),
21596
+ money(result.costUsd)
21462
21597
  ].filter((part) => part !== "");
21463
21598
  return parts.join(" · ");
21464
21599
  };
@@ -21599,6 +21734,8 @@ var createProgressRenderer = (options) => {
21599
21734
  import { existsSync as existsSync24 } from "node:fs";
21600
21735
  import { mkdtemp as mkdtemp4, readFile as readFile25 } from "node:fs/promises";
21601
21736
  import { tmpdir as tmpdir4 } from "node:os";
21737
+ import pLimit2 from "p-limit";
21738
+ import pMap7 from "p-map";
21602
21739
  import { join as join44 } from "pathe";
21603
21740
 
21604
21741
  // src/translate/meta.ts
@@ -21719,12 +21856,12 @@ ${JSON.stringify(titles, null, 2)}`;
21719
21856
  // src/translate/validate.ts
21720
21857
  var FRONTMATTER_OPEN = /^---\r?\n/u;
21721
21858
  var FENCE_LINE = /^\s*(?:```|~~~)/u;
21722
- var stripOuterFence = (text3) => {
21723
- const trimmed = text3.trim();
21859
+ var stripOuterFence = (text2) => {
21860
+ const trimmed = text2.trim();
21724
21861
  const match = trimmed.match(/^(?<fence>`{3,}|~{3,})[^\n]*\n(?<inner>[\s\S]*?)\n\k<fence>\s*$/u);
21725
21862
  return match?.groups?.inner ?? trimmed;
21726
21863
  };
21727
- var countFenceLines = (text3) => text3.split(`
21864
+ var countFenceLines = (text2) => text2.split(`
21728
21865
  `).filter((line) => FENCE_LINE.test(line)).length;
21729
21866
  var getPath2 = (data, path) => {
21730
21867
  let value = data;
@@ -21743,8 +21880,8 @@ var setPath = (data, path, value) => {
21743
21880
  }
21744
21881
  parent[path.at(-1)] = value;
21745
21882
  };
21746
- var ensureTrailingNewline = (text3) => text3.endsWith(`
21747
- `) ? text3 : `${text3}
21883
+ var ensureTrailingNewline = (text2) => text2.endsWith(`
21884
+ `) ? text2 : `${text2}
21748
21885
  `;
21749
21886
  var validateTranslation = (sourceText, agentText) => {
21750
21887
  const candidate = stripOuterFence(agentText);
@@ -21823,11 +21960,6 @@ var metaDirKey = (dir) => dir === "" ? "." : dir;
21823
21960
  var invokeAgent = async (context, prompt, index) => {
21824
21961
  const messagePath = join44(context.dir, `message-${index}.txt`);
21825
21962
  const result = await context.run(context.bin, translateAgentArgs(context.kind, messagePath), { cwd: context.dir, prompt, timeoutMs: context.timeoutMs });
21826
- if (!result.timedOut && result.code === WINDOWS_COMMAND_NOT_FOUND) {
21827
- const missing = new Error(`${context.bin} was not found on PATH`);
21828
- missing.code = "ENOENT";
21829
- throw missing;
21830
- }
21831
21963
  return await readAgentOutput(context.kind, result, messagePath);
21832
21964
  };
21833
21965
  var runPageItem = async (item, index, context, ledger) => {
@@ -21916,37 +22048,26 @@ var runTranslate = async (options) => {
21916
22048
  timeoutMs: options.timeoutMs ?? DEFAULT_TRANSLATE_TIMEOUT_MS
21917
22049
  };
21918
22050
  const { items } = options.workList;
21919
- const results = Array.from({ length: items.length });
21920
22051
  const concurrency = Math.max(1, Math.min(options.concurrency ?? 1, items.length || 1));
21921
- let persisting = Promise.resolve();
21922
- const persist = () => {
21923
- persisting = persisting.then(() => options.persistLedger?.());
21924
- return persisting;
21925
- };
21926
- let nextIndex = 0;
21927
- const worker = async () => {
21928
- while (nextIndex < items.length) {
21929
- const index = nextIndex;
21930
- nextIndex += 1;
21931
- const item = items[index];
21932
- options.onProgress?.({
21933
- index,
21934
- item,
21935
- kind: "item-start",
21936
- total: items.length
21937
- });
21938
- const result = await (item.kind === "page" ? runPageItem(item, index, context, options.ledger) : runMetaItem(item, index, context, options.ledger));
21939
- results[index] = result;
21940
- await persist();
21941
- options.onProgress?.({
21942
- index,
21943
- kind: "item-end",
21944
- result,
21945
- total: items.length
21946
- });
21947
- }
21948
- };
21949
- await Promise.all(Array.from({ length: concurrency }, () => worker()));
22052
+ const persistLimit = pLimit2(1);
22053
+ const persist = () => persistLimit(() => options.persistLedger?.());
22054
+ const results = await pMap7(items, async (item, index) => {
22055
+ options.onProgress?.({
22056
+ index,
22057
+ item,
22058
+ kind: "item-start",
22059
+ total: items.length
22060
+ });
22061
+ const result = await (item.kind === "page" ? runPageItem(item, index, context, options.ledger) : runMetaItem(item, index, context, options.ledger));
22062
+ await persist();
22063
+ options.onProgress?.({
22064
+ index,
22065
+ kind: "item-end",
22066
+ result,
22067
+ total: items.length
22068
+ });
22069
+ return result;
22070
+ }, { concurrency });
21950
22071
  const diagnostics = [];
21951
22072
  for (const result of results) {
21952
22073
  if (result.status !== "translated") {
@@ -22100,7 +22221,7 @@ var computeWorkList = async (project, ledger, options = {}) => {
22100
22221
 
22101
22222
  // src/cli/commands/translate.ts
22102
22223
  var DEFAULT_TIMEOUT_S2 = DEFAULT_TRANSLATE_TIMEOUT_MS / 1000;
22103
- var DEFAULT_CONCURRENCY = 4;
22224
+ var DEFAULT_CONCURRENCY2 = 4;
22104
22225
  var MAX_CONCURRENCY = 16;
22105
22226
  var parseFlags2 = (args) => {
22106
22227
  const agents = Object.keys(AGENTS).filter((kind) => args[kind]);
@@ -22121,7 +22242,7 @@ var parseFlags2 = (args) => {
22121
22242
  logger.error(`Invalid --timeout "${args.timeout}" (whole seconds).`);
22122
22243
  process.exit(1);
22123
22244
  }
22124
- const concurrency = args.concurrency === undefined ? DEFAULT_CONCURRENCY : Number(args.concurrency);
22245
+ const concurrency = args.concurrency === undefined ? DEFAULT_CONCURRENCY2 : Number(args.concurrency);
22125
22246
  if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > MAX_CONCURRENCY) {
22126
22247
  logger.error(`Invalid --concurrency "${args.concurrency}" (use 1..${MAX_CONCURRENCY}).`);
22127
22248
  process.exit(1);
@@ -22168,7 +22289,7 @@ var translateCommand = defineCommand13({
22168
22289
  type: "boolean"
22169
22290
  },
22170
22291
  concurrency: {
22171
- description: `Parallel agent sessions. Defaults to ${DEFAULT_CONCURRENCY}, max ${MAX_CONCURRENCY}.`,
22292
+ description: `Parallel agent sessions. Defaults to ${DEFAULT_CONCURRENCY2}, max ${MAX_CONCURRENCY}.`,
22172
22293
  type: "string"
22173
22294
  },
22174
22295
  force: {
@@ -22587,5 +22708,5 @@ process.on("unhandledRejection", (error) => {
22587
22708
  });
22588
22709
  runMain(main);
22589
22710
 
22590
- //# debugId=C86B4C53CC85579764756E2164756E21
22711
+ //# debugId=07BF7082AA0A5EEC64756E2164756E21
22591
22712
  //# sourceMappingURL=index.js.map