blume 1.4.2 → 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 (90) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/cli/index.js +694 -579
  3. package/dist/cli/index.js.map +56 -55
  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/schema.d.ts +4 -0
  7. package/dist/types/core/sources/types.d.ts +9 -1
  8. package/dist/types/openapi/references.d.ts +8 -2
  9. package/docs/configuration/ai.mdx +26 -8
  10. package/docs/content/sources.mdx +1 -1
  11. package/package.json +11 -1
  12. package/src/ai/agent-readability.ts +3 -2
  13. package/src/ai/api-catalog.ts +2 -2
  14. package/src/ai/ask-context.ts +45 -12
  15. package/src/ai/llms.ts +2 -1
  16. package/src/ai/mcp/discovery.ts +25 -6
  17. package/src/ai/mcp/server.ts +108 -98
  18. package/src/ai/tar.ts +29 -70
  19. package/src/astro/examples.ts +7 -3
  20. package/src/astro/generate.ts +59 -34
  21. package/src/astro/islands.ts +7 -3
  22. package/src/astro/templates.ts +34 -9
  23. package/src/audit/agent.ts +14 -29
  24. package/src/audit/crawl.ts +41 -16
  25. package/src/audit/run.ts +10 -3
  26. package/src/audit/snapshot.ts +27 -2
  27. package/src/cli/commands/audit.ts +12 -17
  28. package/src/cli/commands/build.ts +15 -7
  29. package/src/cli/commands/dev.ts +13 -15
  30. package/src/cli/commands/eject.ts +4 -4
  31. package/src/cli/commands/eval.ts +17 -27
  32. package/src/cli/env.ts +13 -30
  33. package/src/cli/init/scaffold.ts +21 -0
  34. package/src/cli/report-format.ts +22 -0
  35. package/src/components/content/AccordionItem.astro +2 -9
  36. package/src/components/content/ColorItem.astro +5 -13
  37. package/src/components/content/Component.astro +12 -8
  38. package/src/components/content/Frame.astro +2 -12
  39. package/src/components/content/Prompt.astro +12 -31
  40. package/src/components/content/Tab.astro +2 -9
  41. package/src/components/content/Tooltip.astro +1 -9
  42. package/src/components/content/Update.astro +2 -9
  43. package/src/components/content/inline-markdown.ts +28 -0
  44. package/src/components/copy-feedback.ts +96 -0
  45. package/src/components/islands/ask-ai.tsx +78 -9
  46. package/src/components/layout/PageActions.astro +20 -32
  47. package/src/components/layout/PageLayout.astro +8 -28
  48. package/src/components/layout/RootLayout.astro +6 -48
  49. package/src/components/layout/Search.astro +56 -9
  50. package/src/components/layout/drawer-inert.ts +31 -0
  51. package/src/components/layout/search/pagefind.ts +6 -5
  52. package/src/components/layout/search/types.ts +32 -0
  53. package/src/components/openapi/panel.ts +11 -8
  54. package/src/components/raf-throttle.ts +21 -0
  55. package/src/components/slug.ts +14 -0
  56. package/src/core/base-path.ts +18 -1
  57. package/src/core/config-input.ts +8 -0
  58. package/src/core/frontmatter.ts +45 -1
  59. package/src/core/probe.ts +7 -19
  60. package/src/core/project-graph.ts +12 -1
  61. package/src/core/schema.ts +6 -0
  62. package/src/core/site-url.ts +27 -0
  63. package/src/core/sources/cache.ts +10 -8
  64. package/src/core/sources/github-releases.ts +21 -1
  65. package/src/core/sources/normalize.ts +26 -2
  66. package/src/core/sources/notion.ts +27 -5
  67. package/src/core/sources/portable-text.ts +16 -1
  68. package/src/core/sources/resolve.ts +1 -0
  69. package/src/core/sources/types.ts +13 -1
  70. package/src/deploy/cloudflare-negotiation.ts +15 -1
  71. package/src/deploy/robots.ts +2 -1
  72. package/src/deploy/rss.ts +2 -1
  73. package/src/deploy/sitemap.ts +56 -7
  74. package/src/eval/agents.ts +13 -10
  75. package/src/eval/report.ts +1 -14
  76. package/src/markdown/package-commands.ts +61 -54
  77. package/src/og/card.ts +24 -26
  78. package/src/openapi/model.ts +9 -9
  79. package/src/openapi/parse.ts +69 -28
  80. package/src/openapi/references.ts +35 -12
  81. package/src/openapi/render-mdx.ts +64 -25
  82. package/src/openapi/scalar.ts +2 -2
  83. package/src/openapi/source.ts +28 -1
  84. package/src/search/documents.ts +78 -34
  85. package/src/search/orama-index.ts +51 -12
  86. package/src/theme/palette.ts +6 -2
  87. package/src/translate/ledger.ts +4 -2
  88. package/src/translate/report.ts +1 -14
  89. package/src/translate/run.ts +20 -35
  90. 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.
@@ -5282,6 +5321,21 @@ var visibleText = (element) => {
5282
5321
 
5283
5322
  // src/audit/snapshot.ts
5284
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
+ };
5285
5339
  var CONTENT_ROOTS = ["main", "article", "body"];
5286
5340
  var contentRoot = (document) => {
5287
5341
  for (const selector of CONTENT_ROOTS) {
@@ -5341,6 +5395,7 @@ var buildSnapshot = (options) => {
5341
5395
  const root = contentRoot(document);
5342
5396
  const prose = root ? visibleText(root) : "";
5343
5397
  const robots = document.querySelector('meta[name="robots"]');
5398
+ const lang = attr(document.querySelector("html") ?? document, "lang");
5344
5399
  const { jsonld, jsonldErrors } = collectJsonLd(document);
5345
5400
  return {
5346
5401
  bytes: Buffer.byteLength(html, "utf-8"),
@@ -5361,7 +5416,7 @@ var buildSnapshot = (options) => {
5361
5416
  indexable: !robots?.getAttribute("content")?.includes("noindex"),
5362
5417
  jsonld,
5363
5418
  jsonldErrors,
5364
- lang: attr(document.querySelector("html") ?? document, "lang"),
5419
+ lang,
5365
5420
  links,
5366
5421
  metaRefresh: document.querySelector('meta[http-equiv="refresh" i]')?.getAttribute("content")?.trim() ?? null,
5367
5422
  og: prefixedMeta(document, "property", "og:"),
@@ -5374,12 +5429,13 @@ var buildSnapshot = (options) => {
5374
5429
  twitter: prefixedMeta(document, "name", "twitter:"),
5375
5430
  url,
5376
5431
  viewport: document.querySelector('meta[name="viewport"]')?.getAttribute("content")?.trim() ?? null,
5377
- wordCount: prose ? prose.split(/\s+/u).length : 0
5432
+ wordCount: countWords(prose, lang)
5378
5433
  };
5379
5434
  };
5380
5435
 
5381
5436
  // src/audit/crawl.ts
5382
5437
  var EXAMPLES_PREFIX = `${examplesRouteBase("")}/`;
5438
+ var CRAWL_CONCURRENCY = 16;
5383
5439
  var fileToUrl = (staticDir, file) => {
5384
5440
  const rel = relative6(staticDir, file).replaceAll("\\", "/");
5385
5441
  const path = rel.replace(/(?:^|\/)index\.html$/u, "").replace(/\.html$/u, "");
@@ -5387,11 +5443,11 @@ var fileToUrl = (staticDir, file) => {
5387
5443
  };
5388
5444
  var indexFiles = async (staticDir) => {
5389
5445
  const found = await glob("**/*", { cwd: staticDir, dot: true });
5390
- const sized = await Promise.all(found.map(async (file) => {
5446
+ const sized = await pMap2(found, async (file) => {
5391
5447
  const path = `/${file.replaceAll("\\", "/")}`;
5392
5448
  const info = await stat(join9(staticDir, file));
5393
5449
  return [path, info.size];
5394
- }));
5450
+ }, { concurrency: CRAWL_CONCURRENCY });
5395
5451
  return new Map(sized);
5396
5452
  };
5397
5453
  var routeIndex = (manifest, basePath) => {
@@ -5444,15 +5500,17 @@ var parseSitemap = (file, xml, bytes) => {
5444
5500
  };
5445
5501
  var parseLlms = (file, text) => {
5446
5502
  const entries = [];
5447
- const link = /\]\((?<url>[^)\s]+)\)/gu;
5448
- for (const [index, line] of text.split(/\r?\n/u).entries()) {
5449
- for (const match of line.matchAll(link)) {
5450
- const url = match.groups?.url;
5451
- if (url) {
5452
- 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);
5453
5510
  }
5454
5511
  }
5455
- }
5512
+ };
5513
+ collect(fromMarkdown(text));
5456
5514
  return { entries, file };
5457
5515
  };
5458
5516
  var ROBOTS_DIRECTIVE = /^(?<field>[a-z-]+)\s*:\s*(?<value>.*)$/iu;
@@ -5488,7 +5546,7 @@ var crawlStaticDir = async (options) => {
5488
5546
  const { staticDir, manifest, basePath } = options;
5489
5547
  const routes = routeIndex(manifest, basePath);
5490
5548
  const htmlFiles = await glob("**/*.html", { absolute: true, cwd: staticDir });
5491
- const snapshots = await Promise.all(htmlFiles.toSorted().map(async (file) => {
5549
+ const snapshots = await pMap2(htmlFiles.toSorted(), async (file) => {
5492
5550
  const url = fileToUrl(staticDir, file);
5493
5551
  if (stripBasePath(basePath, url).startsWith(EXAMPLES_PREFIX)) {
5494
5552
  return null;
@@ -5503,7 +5561,7 @@ var crawlStaticDir = async (options) => {
5503
5561
  route: routes.get(url) ?? routes.get(stripBasePath(basePath, url)),
5504
5562
  url
5505
5563
  });
5506
- }));
5564
+ }, { concurrency: CRAWL_CONCURRENCY });
5507
5565
  const pages = snapshots.filter((page) => page !== null);
5508
5566
  const sitemapFile = join9(staticDir, "sitemap.xml");
5509
5567
  const sitemapXml = await readIfPresent(sitemapFile);
@@ -5600,17 +5658,18 @@ class NoBuildError extends Error {
5600
5658
  this.staticDir = staticDir;
5601
5659
  }
5602
5660
  }
5661
+ var READ_CONCURRENCY = 16;
5603
5662
  var readSources = async (pages) => {
5604
5663
  const paths = [
5605
5664
  ...new Set(pages.flatMap((page) => page.source ? [page.source] : []))
5606
5665
  ];
5607
- const entries = await Promise.all(paths.map(async (path) => {
5666
+ const entries = await pMap3(paths, async (path) => {
5608
5667
  try {
5609
5668
  return [path, await readFile4(path, "utf-8")];
5610
5669
  } catch {
5611
5670
  return null;
5612
5671
  }
5613
- }));
5672
+ }, { concurrency: READ_CONCURRENCY });
5614
5673
  return new Map(entries.filter((entry) => entry !== null));
5615
5674
  };
5616
5675
  var matches = (id, terms) => {
@@ -9520,6 +9579,7 @@ var sanitySourceSchema = z2.object({
9520
9579
  type: z2.literal("sanity")
9521
9580
  });
9522
9581
  var notionSourceSchema = z2.object({
9582
+ concurrency: z2.number().positive().optional(),
9523
9583
  database: z2.string(),
9524
9584
  pollInterval: z2.number().positive().optional(),
9525
9585
  prefix: z2.string().optional(),
@@ -9775,6 +9835,7 @@ var aiConfigSchema = z2.strictObject({
9775
9835
  baseUrl: z2.url().optional(),
9776
9836
  enabled: z2.boolean().default(false),
9777
9837
  endpoint: askEndpointSchema.optional(),
9838
+ instructions: z2.string().trim().min(1).optional(),
9778
9839
  model: z2.string().default("openai/gpt-5.5"),
9779
9840
  provider: z2.enum(askAiProviders).default("gateway"),
9780
9841
  suggestions: z2.array(z2.strictObject({
@@ -11232,9 +11293,10 @@ var GROUP_FOLDER2 = /^\((?<label>.+)\)$/u;
11232
11293
  var WORD_SPLIT2 = /[-_]/u;
11233
11294
  var stripNumericPrefix = (segment) => segment.replace(NUMERIC_PREFIX2, "");
11234
11295
  var groupLabel = (segment) => segment.match(GROUP_FOLDER2)?.groups?.label ?? null;
11235
- 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, "");
11236
11297
  var slugifyPath = (text) => text.split("/").map(slugify2).filter(Boolean).join("/");
11237
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, "");
11238
11300
  var addRouteSegment = (part, segments, groups) => {
11239
11301
  if (part === "") {
11240
11302
  return;
@@ -11248,7 +11310,11 @@ var addRouteSegment = (part, segments, groups) => {
11248
11310
  if (clean === "index") {
11249
11311
  return;
11250
11312
  }
11251
- segments.push(clean);
11313
+ const safe = sanitizeSegment(clean);
11314
+ if (safe === "") {
11315
+ return;
11316
+ }
11317
+ segments.push(safe);
11252
11318
  };
11253
11319
  var mapRoute = (relativePath) => {
11254
11320
  const withoutExt = relativePath.slice(0, relativePath.length - extname2(relativePath).length);
@@ -11578,15 +11644,10 @@ var normalizeEntry = (entry, ctx) => {
11578
11644
  import { isAbsolute as isAbsolute5, join as join17, resolve as resolve4 } from "pathe";
11579
11645
 
11580
11646
  // src/core/sources/cache.ts
11647
+ import { createHash as createHash2 } from "node:crypto";
11581
11648
  import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
11582
11649
  import { join as join11 } from "pathe";
11583
- var hashText = (text) => {
11584
- let hash = 5381;
11585
- for (let i = 0;i < text.length; i += 1) {
11586
- hash = (hash * 33 + (text.codePointAt(i) ?? 0)) % 2147483647;
11587
- }
11588
- return hash.toString(36);
11589
- };
11650
+ var hashText = (text) => createHash2("sha256").update(text).digest("hex").slice(0, 16);
11590
11651
  var entriesDigest = (entries) => hashText(entries.map((entry) => `${entry.ref}:${entry.hash ?? hashText(entry.body.text)}`).join("|"));
11591
11652
  var pollingWatch = (load2, intervalSeconds, seed) => (onChange) => {
11592
11653
  let last = "";
@@ -11663,9 +11724,6 @@ var loadWithCache = async (name, cache, fetchEntries, refresh = true) => {
11663
11724
  };
11664
11725
 
11665
11726
  // src/openapi/model.ts
11666
- var NON_SLUG2 = /[^a-z0-9]+/gu;
11667
- var SLUG_EDGES = /^-+|-+$/gu;
11668
- var slugify3 = (text) => text.toLowerCase().replace(NON_SLUG2, "-").replace(SLUG_EDGES, "");
11669
11727
  var HTTP_METHODS = [
11670
11728
  "get",
11671
11729
  "put",
@@ -11678,8 +11736,8 @@ var HTTP_METHODS = [
11678
11736
  ];
11679
11737
  var UNTAGGED = "Operations";
11680
11738
  var operationKey = (method, path, operationId) => {
11681
- const fromId = operationId ? slugify3(operationId) : "";
11682
- return fromId || slugify3(`${method}-${path}`);
11739
+ const fromId = operationId ? slugify(operationId) : "";
11740
+ return fromId || slugify(`${method}-${path}`);
11683
11741
  };
11684
11742
  var isOperation = (value) => typeof value === "object" && value !== null;
11685
11743
  var tagSlugger = () => {
@@ -11690,7 +11748,7 @@ var tagSlugger = () => {
11690
11748
  if (existing) {
11691
11749
  return existing;
11692
11750
  }
11693
- const base = slugify3(name) || "operations";
11751
+ const base = slugify(name) || "operations";
11694
11752
  let slug = base;
11695
11753
  for (let suffix = 2;taken.has(slug); suffix += 1) {
11696
11754
  slug = `${base}-${suffix}`;
@@ -11759,6 +11817,7 @@ var extractOperations = (document, baseRoute) => {
11759
11817
  import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
11760
11818
  import { setTimeout as sleep } from "node:timers/promises";
11761
11819
  import { normalize as normalize2, upgrade } from "@scalar/openapi-parser";
11820
+ import pRetry, { AbortError } from "p-retry";
11762
11821
  import { isAbsolute as isAbsolute3, join as join12 } from "pathe";
11763
11822
  var URL_SPEC = /^https?:\/\//u;
11764
11823
  var FETCH_TIMEOUT_MS = 15000;
@@ -11796,8 +11855,15 @@ var ensureProxyDispatcher = async () => {
11796
11855
  };
11797
11856
  var retryAfterMs = (response) => {
11798
11857
  const header = response.headers.get("retry-after");
11799
- const seconds = header ? Number(header) : Number.NaN;
11800
- 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;
11801
11867
  };
11802
11868
  var attemptFetch = async (spec) => {
11803
11869
  try {
@@ -11824,25 +11890,37 @@ var attemptFetch = async (spec) => {
11824
11890
  };
11825
11891
  }
11826
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
+ };
11827
11901
  var fetchSpecText = async (spec) => {
11828
11902
  await ensureProxyDispatcher();
11829
- let last = {
11830
- error: new Error(`Could not fetch ${spec}`),
11831
- retryable: false
11832
- };
11833
- for (let attempt = 0;attempt < MAX_ATTEMPTS; attempt += 1) {
11834
- last = await attemptFetch(spec);
11835
- if ("text" in last) {
11836
- return last.text;
11837
- }
11838
- if (!last.retryable) {
11839
- break;
11840
- }
11841
- if (attempt < MAX_ATTEMPTS - 1) {
11842
- await sleep(Math.min(last.retryAfter ?? BASE_BACKOFF_MS * 2 ** attempt, MAX_RETRY_WAIT_MS));
11843
- }
11844
- }
11845
- 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
+ });
11846
11924
  };
11847
11925
  var cacheFileFor = (cacheDir, spec) => join12(cacheDir, `spec-${hashText(spec)}.cache`);
11848
11926
  var readCache = async (file) => {
@@ -11902,6 +11980,8 @@ var parseSpec = async (spec, root, options = {}) => {
11902
11980
  };
11903
11981
 
11904
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";
11905
11985
  var MDX_UNSAFE = /[<{}]/gu;
11906
11986
  var ENTITIES = {
11907
11987
  "<": "&lt;",
@@ -11909,26 +11989,46 @@ var ENTITIES = {
11909
11989
  "}": "&#125;"
11910
11990
  };
11911
11991
  var MDX_ESM_KEYWORD = /^(?<keyword>import|export)\b/gmu;
11912
- var BACKTICK_CODE = /(?<!`)(?<bt>`+)(?!`)[\s\S]*?(?<!`)\k<bt>(?!`)/gu;
11913
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
+ };
11914
12014
  var mdxSafe = (text) => {
11915
12015
  let out = "";
11916
12016
  let cursor = 0;
11917
- for (const match of text.matchAll(BACKTICK_CODE)) {
11918
- const start = match.index ?? 0;
12017
+ for (const [start, end] of codeSpans(text)) {
11919
12018
  out += escapeProse(text.slice(cursor, start));
11920
- out += match[0];
11921
- cursor = start + match[0].length;
12019
+ out += text.slice(start, end);
12020
+ cursor = end;
11922
12021
  }
11923
12022
  return out + escapeProse(text.slice(cursor));
11924
12023
  };
11925
12024
  var META_DESCRIPTION_MAX = 160;
11926
- var PARAGRAPH_BREAK = /\n\s*\n/u;
11927
- var MARKDOWN_LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
11928
- var MARKDOWN_MARKS = /[*_`#>]/gu;
11929
12025
  var WHITESPACE = /\s+/gu;
11930
12026
  var TRAILING_WORD = /\s+\S*$/u;
11931
- 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
+ };
11932
12032
  var clip = (text, max) => {
11933
12033
  if (max <= 0) {
11934
12034
  return "";
@@ -12047,6 +12147,13 @@ var specEntries = (spec, operations, reference) => {
12047
12147
  entries.push(toEntry(overviewMdx(spec, reference), base ? `${base}/index.mdx` : "index.mdx"));
12048
12148
  return entries;
12049
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
+ };
12050
12157
  var openApiSource = (references, ctx) => {
12051
12158
  let parsed = {};
12052
12159
  const loadReference = async (reference) => {
@@ -12099,6 +12206,7 @@ var openApiSource = (references, ctx) => {
12099
12206
  ] : []
12100
12207
  ],
12101
12208
  entries: specEntries(spec, operations, reference),
12209
+ folderMeta: tagFolderMeta(spec, tags),
12102
12210
  slug: reference.slug,
12103
12211
  spec
12104
12212
  };
@@ -12120,6 +12228,7 @@ var openApiSource = (references, ctx) => {
12120
12228
  severity: "warning"
12121
12229
  })));
12122
12230
  const data = {};
12231
+ const folderMeta = {};
12123
12232
  for (const result of results) {
12124
12233
  if ("severity" in result) {
12125
12234
  diagnostics.push(result);
@@ -12127,10 +12236,11 @@ var openApiSource = (references, ctx) => {
12127
12236
  }
12128
12237
  data[result.slug] = result.spec;
12129
12238
  entries.push(...result.entries);
12239
+ Object.assign(folderMeta, result.folderMeta);
12130
12240
  diagnostics.push(...result.diagnostics);
12131
12241
  }
12132
12242
  parsed = data;
12133
- return { diagnostics, entries };
12243
+ return { diagnostics, entries, folderMeta };
12134
12244
  };
12135
12245
  return {
12136
12246
  kind: "openapi-source",
@@ -12206,15 +12316,15 @@ var filesystemSource = (options) => {
12206
12316
  };
12207
12317
 
12208
12318
  // src/core/sources/github-releases.ts
12209
- import { fromMarkdown } from "mdast-util-from-markdown";
12319
+ import { fromMarkdown as fromMarkdown3 } from "mdast-util-from-markdown";
12210
12320
  import { gfmFromMarkdown } from "mdast-util-gfm";
12211
- import { toString as mdastToString } from "mdast-util-to-string";
12321
+ import { toString as mdastToString2 } from "mdast-util-to-string";
12212
12322
  import { gfm } from "micromark-extension-gfm";
12213
12323
  var DEFAULT_BASE_URL = "https://api.github.com";
12214
12324
  var DEFAULT_LIMIT = 100;
12215
12325
  var PER_PAGE = 100;
12216
12326
  var LEADING_V = /^v/iu;
12217
- var NON_SLUG3 = /[^a-z0-9]+/gu;
12327
+ var NON_SLUG2 = /[^a-z0-9]+/gu;
12218
12328
  var EDGE_DASHES = /^-+|-+$/gu;
12219
12329
  var DESCRIPTION_MAX = 160;
12220
12330
  var DESCRIPTION_MIN = 110;
@@ -12222,24 +12332,35 @@ var CHANGESET_HASH = /^(?<mark>\s*(?:[-*+]|\d+[.)])\s+)[0-9a-f]{7,40}:\s+/gmu;
12222
12332
  var WHITESPACE2 = /\s+/gu;
12223
12333
  var TRAILING_FRAGMENT = /[\s,;:.—–-]+$/u;
12224
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
+ };
12225
12346
  var releaseDescription = (body) => {
12226
- const tree = fromMarkdown(body.replaceAll(CHANGESET_HASH, "$<mark>"), {
12347
+ const tree = fromMarkdown3(body.replaceAll(CHANGESET_HASH, "$<mark>"), {
12227
12348
  extensions: [gfm()],
12228
12349
  mdastExtensions: [gfmFromMarkdown()]
12229
12350
  });
12230
- 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();
12231
12352
  if (!text) {
12232
12353
  return;
12233
12354
  }
12234
12355
  if (text.length <= DESCRIPTION_MAX) {
12235
12356
  return text;
12236
12357
  }
12237
- const slice = text.slice(0, DESCRIPTION_MAX - 1);
12358
+ const slice = graphemePrefix(text, DESCRIPTION_MAX - 1);
12238
12359
  const boundary = slice.lastIndexOf(" ");
12239
12360
  const head = (boundary >= DESCRIPTION_MIN ? slice.slice(0, boundary) : slice).replace(TRAILING_FRAGMENT, "");
12240
12361
  return `${head}…`;
12241
12362
  };
12242
- var slugifyTag = (tag) => tag.toLowerCase().replaceAll(NON_SLUG3, "-").replaceAll(EDGE_DASHES, "");
12363
+ var slugifyTag = (tag) => tag.toLowerCase().replaceAll(NON_SLUG2, "-").replaceAll(EDGE_DASHES, "");
12243
12364
  var githubHeaders = () => {
12244
12365
  const headers = new Headers({ Accept: "application/vnd.github+json" });
12245
12366
  const token = process.env.GITHUB_TOKEN;
@@ -12492,6 +12613,7 @@ var mdxRemoteSource = (options, ctx) => {
12492
12613
 
12493
12614
  // src/core/sources/notion.ts
12494
12615
  import { setTimeout as sleep2 } from "node:timers/promises";
12616
+ import pLimit from "p-limit";
12495
12617
  import { join as join15 } from "pathe";
12496
12618
 
12497
12619
  // src/core/sources/assets.ts
@@ -12571,6 +12693,7 @@ var RATE_LIMITED = 429;
12571
12693
  var MAX_RETRIES = 4;
12572
12694
  var BASE_DELAY_MS = 500;
12573
12695
  var SECOND_MS2 = 1000;
12696
+ var DEFAULT_CONCURRENCY = 3;
12574
12697
  var withNotionRetry = async (call, attempt = 0) => {
12575
12698
  try {
12576
12699
  return await call();
@@ -12580,7 +12703,7 @@ var withNotionRetry = async (call, attempt = 0) => {
12580
12703
  throw error;
12581
12704
  }
12582
12705
  const retryAfter = Number(error.headers?.["retry-after"]);
12583
- 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());
12584
12707
  await sleep2(wait);
12585
12708
  return withNotionRetry(call, attempt + 1);
12586
12709
  }
@@ -12644,6 +12767,8 @@ ${text}
12644
12767
  };
12645
12768
  var notionSource = (options, ctx) => {
12646
12769
  const props = options.properties ?? {};
12770
+ const limit = pLimit(Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY));
12771
+ const notionCall = (call) => withNotionRetry(() => limit(call));
12647
12772
  const cache = snapshotCache(ctx?.cacheDir ?? join15(".blume", "cache", options.name));
12648
12773
  const assetsDir = ctx?.assetsDir ?? join15(".blume", "public", "blume-assets", options.name);
12649
12774
  const assetsBaseUrl = ctx?.assetsBaseUrl ?? `/blume-assets/${options.name}`;
@@ -12664,7 +12789,7 @@ var notionSource = (options, ctx) => {
12664
12789
  }
12665
12790
  return new Client({ auth: options.token ?? process.env.NOTION_TOKEN });
12666
12791
  };
12667
- 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 })));
12668
12793
  const renderContainer = async (client, block, render) => {
12669
12794
  const children = async (target) => {
12670
12795
  if (!target.has_children) {
@@ -12797,7 +12922,7 @@ ${nested}`;
12797
12922
  }
12798
12923
  };
12799
12924
  };
12800
- const queryDatabase = (client, cursor) => withNotionRetry(() => client.databases.query({
12925
+ const queryDatabase = (client, cursor) => notionCall(() => client.databases.query({
12801
12926
  database_id: options.database,
12802
12927
  start_cursor: cursor
12803
12928
  }));
@@ -12848,8 +12973,11 @@ var HEADING_STYLES = {
12848
12973
  h5: "##### ",
12849
12974
  h6: "###### "
12850
12975
  };
12976
+ var MARKDOWN_SPECIALS = /[\\`*_[\]~<]/gu;
12977
+ var escapeText = (text) => text.replaceAll(MARKDOWN_SPECIALS, String.raw`\$&`);
12851
12978
  var renderSpan = (span, defs) => {
12852
- let text = span.text ?? "";
12979
+ const isCode = span.marks?.includes("code") ?? false;
12980
+ let text = isCode ? span.text ?? "" : escapeText(span.text ?? "");
12853
12981
  if (!span.marks || span.marks.length === 0) {
12854
12982
  return text;
12855
12983
  }
@@ -13086,6 +13214,7 @@ var buildSource = (def, name, context, runtime) => {
13086
13214
  }
13087
13215
  if (def.type === "notion") {
13088
13216
  return notionSource({
13217
+ concurrency: def.concurrency,
13089
13218
  database: def.database,
13090
13219
  name,
13091
13220
  pollInterval: def.pollInterval,
@@ -13260,6 +13389,10 @@ var scanProject = async (root, options = {}) => {
13260
13389
  Promise.all(sources.map(async (source) => ({ source, ...await source.load() }))),
13261
13390
  discoverFolderMeta(metaSources, { localeDirs })
13262
13391
  ]);
13392
+ const sharedFolderMeta = new Map([
13393
+ ...loaded.flatMap(({ folderMeta: sourceMeta }) => Object.entries(sourceMeta ?? {})),
13394
+ ...folderMeta.shared
13395
+ ]);
13263
13396
  const {
13264
13397
  diagnostics: contentDiagnostics,
13265
13398
  droppedPages,
@@ -13282,7 +13415,7 @@ var scanProject = async (root, options = {}) => {
13282
13415
  folderMeta: folderMeta.meta,
13283
13416
  i18n: config.i18n,
13284
13417
  navigation: config.navigation,
13285
- sharedFolderMeta: folderMeta.shared
13418
+ sharedFolderMeta
13286
13419
  });
13287
13420
  const manifest = buildManifest({ config, context, graph });
13288
13421
  const i18nWarnings = config.i18n ? i18nDiagnostics(pages, config.i18n) : [];
@@ -13345,14 +13478,16 @@ var shouldFail = (result, gate) => {
13345
13478
  const failing = failingSeverities(gate);
13346
13479
  return result.diagnostics.some((d) => failing.has(d.severity));
13347
13480
  };
13348
- var launchAgentCode = async (bin, prompt) => {
13481
+ var launchAgentCode = async (agent, prompt) => {
13482
+ const cli = AGENTS[agent];
13349
13483
  try {
13350
- return await launchAgent(bin, prompt);
13484
+ return await launchAgent(cli.bin, prompt);
13351
13485
  } catch (error) {
13352
13486
  if (error?.code !== "ENOENT") {
13353
13487
  throw error;
13354
13488
  }
13355
- 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);
13356
13491
  }
13357
13492
  };
13358
13493
  var auditCommand = defineCommand2({
@@ -13460,11 +13595,7 @@ var auditCommand = defineCommand2({
13460
13595
  process.stderr.write(` Handing ${count} finding${count === 1 ? "" : "s"} to ${cli.name}…
13461
13596
 
13462
13597
  `);
13463
- const code = await launchAgentCode(cli.bin, fixPrompt(report));
13464
- if (code === WINDOWS_COMMAND_NOT_FOUND) {
13465
- logger.error(`${cli.name} (\`${cli.bin}\`) was not found on PATH. Install it with \`${cli.install}\`.`);
13466
- process.exit(1);
13467
- }
13598
+ const code = await launchAgentCode(agent, fixPrompt(report));
13468
13599
  if (code !== 0) {
13469
13600
  process.exit(code);
13470
13601
  }
@@ -13510,7 +13641,7 @@ var buildRssFeeds = (project) => {
13510
13641
  if (!(rss.enabled && site)) {
13511
13642
  return [];
13512
13643
  }
13513
- const base = site.replace(/\/$/u, "");
13644
+ const base = siteRoot(site);
13514
13645
  const deployBase = normalizeBasePath(config.deployment.base);
13515
13646
  const rootLink = `${base}${deployBase}`;
13516
13647
  const feeds = [];
@@ -13590,7 +13721,7 @@ var askApiUrl = (endpoint, site, abs) => {
13590
13721
  if (!endpoint) {
13591
13722
  return abs("/api/ask");
13592
13723
  }
13593
- return site && endpoint.startsWith("/") ? `${site.replace(/\/+$/u, "")}${endpoint}` : endpoint;
13724
+ return site && endpoint.startsWith("/") ? absoluteUrl(site, endpoint) : endpoint;
13594
13725
  };
13595
13726
  var wellKnownArtifacts = (config, abs) => {
13596
13727
  const artifacts = {};
@@ -13614,7 +13745,7 @@ var buildAgentReadability = (project) => {
13614
13745
  const deployBase = normalizeBasePath(config.deployment.base);
13615
13746
  const abs = (path) => {
13616
13747
  const based = withBasePath(deployBase, path);
13617
- return site ? `${site.replace(/\/+$/u, "")}${based}` : based;
13748
+ return site ? absoluteUrl(site, based) : based;
13618
13749
  };
13619
13750
  const negotiates = config.deployment.output === "server" && (config.deployment.adapter === "vercel" || config.deployment.adapter === "cloudflare");
13620
13751
  const artifacts = {
@@ -14079,7 +14210,7 @@ var applyAgentVisibility = (markdown) => applyAudienceVisibility(markdown, "agen
14079
14210
  // src/ai/llms.ts
14080
14211
  var pageUrl = (route, site, base = "") => {
14081
14212
  const path = withBasePath(base, route);
14082
- return encodeURI(site ? `${site.replace(/\/$/u, "")}${path}` : path);
14213
+ return encodeURI(site ? absoluteUrl(site, path) : path);
14083
14214
  };
14084
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"));
14085
14216
  var indexedNavigations = (project) => {
@@ -14253,49 +14384,16 @@ var markdownRoutePaths = (project) => {
14253
14384
  };
14254
14385
 
14255
14386
  // src/ai/skills.ts
14256
- import { createHash as createHash2 } from "node:crypto";
14387
+ import { createHash as createHash3 } from "node:crypto";
14257
14388
  import { readdir, readFile as readFile11, stat as stat2 } from "node:fs/promises";
14258
14389
  import { join as join18 } from "pathe";
14259
14390
 
14260
14391
  // src/ai/tar.ts
14261
14392
  import { gzipSync } from "node:zlib";
14262
- var BLOCK = 512;
14393
+ import { createTar } from "nanotar";
14263
14394
  var NAME_MAX = 100;
14264
14395
  var encoder = new TextEncoder;
14265
- var octal = (header, offset, length, value) => {
14266
- const text = value.toString(8).padStart(length - 1, "0");
14267
- header.set(encoder.encode(text), offset);
14268
- header[offset + length - 1] = 0;
14269
- };
14270
- var text = (header, offset, value) => {
14271
- header.set(encoder.encode(value), offset);
14272
- };
14273
- var fileHeader = (entry) => {
14274
- const header = new Uint8Array(BLOCK);
14275
- text(header, 0, entry.path);
14276
- octal(header, 100, 8, entry.executable ? 493 : 420);
14277
- octal(header, 108, 8, 0);
14278
- octal(header, 116, 8, 0);
14279
- octal(header, 124, 12, entry.content.byteLength);
14280
- octal(header, 136, 12, 0);
14281
- text(header, 156, "0");
14282
- text(header, 257, "ustar");
14283
- header[262] = 0;
14284
- text(header, 263, "00");
14285
- octal(header, 329, 8, 0);
14286
- octal(header, 337, 8, 0);
14287
- header.fill(32, 148, 156);
14288
- let sum = 0;
14289
- for (const byte of header) {
14290
- sum += byte;
14291
- }
14292
- text(header, 148, sum.toString(8).padStart(6, "0"));
14293
- header[154] = 0;
14294
- header[155] = 32;
14295
- return header;
14296
- };
14297
14396
  var buildTarGz = (entries) => {
14298
- const blocks = [];
14299
14397
  for (const entry of entries) {
14300
14398
  if (encoder.encode(entry.path).byteLength > NAME_MAX) {
14301
14399
  throw new Error(`tar path exceeds ${NAME_MAX} bytes: ${entry.path}`);
@@ -14303,20 +14401,19 @@ var buildTarGz = (entries) => {
14303
14401
  if (entry.path.startsWith("/") || entry.path.split("/").includes("..")) {
14304
14402
  throw new Error(`tar path must be archive-relative: ${entry.path}`);
14305
14403
  }
14306
- blocks.push(fileHeader(entry), entry.content);
14307
- const overhang = entry.content.byteLength % BLOCK;
14308
- if (overhang > 0) {
14309
- blocks.push(new Uint8Array(BLOCK - overhang));
14310
- }
14311
- }
14312
- blocks.push(new Uint8Array(BLOCK * 2));
14313
- const total = blocks.reduce((sum, block) => sum + block.byteLength, 0);
14314
- const tar = new Uint8Array(total);
14315
- let offset = 0;
14316
- for (const block of blocks) {
14317
- tar.set(block, offset);
14318
- offset += block.byteLength;
14319
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
+ })));
14320
14417
  return new Uint8Array(gzipSync(tar, { level: 9 }));
14321
14418
  };
14322
14419
 
@@ -14326,7 +14423,7 @@ var AGENT_SKILLS_SCHEMA = "https://schemas.agentskills.io/discovery/0.2.0/schema
14326
14423
  var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
14327
14424
  var SKILL_NAME_MAX = 64;
14328
14425
  var DESCRIPTION_MAX2 = 1024;
14329
- var sha256 = (content) => `sha256:${createHash2("sha256").update(content).digest("hex")}`;
14426
+ var sha256 = (content) => `sha256:${createHash3("sha256").update(content).digest("hex")}`;
14330
14427
  var collectEntries = async (dir, prefix = "") => {
14331
14428
  const entries = [];
14332
14429
  const items = await readdir(dir, { withFileTypes: true });
@@ -14587,6 +14684,20 @@ const redirectFor = (pathname) => {
14587
14684
  return Object.hasOwn(REDIRECTS, path) ? REDIRECTS[path] : null;
14588
14685
  };
14589
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
+
14590
14701
  const parseAccept = (accept) =>
14591
14702
  accept.split(",").map((part) => {
14592
14703
  const segments = part.trim().split(";");
@@ -14666,7 +14777,7 @@ export default {
14666
14777
  const redirect = redirectFor(url.pathname);
14667
14778
  if (redirect !== null) {
14668
14779
  return new Response(null, {
14669
- headers: { location: redirect[0] },
14780
+ headers: { location: redirectLocation(redirect[0], url.search) },
14670
14781
  status: redirect[1],
14671
14782
  });
14672
14783
  }
@@ -14839,7 +14950,7 @@ var buildRobots = (project) => {
14839
14950
  const { site } = config.deployment;
14840
14951
  if (site && config.seo.sitemap) {
14841
14952
  const sitemapPath = withBasePath(normalizeBasePath(config.deployment.base), "/sitemap.xml");
14842
- lines.push("", `Sitemap: ${site.replace(/\/$/u, "")}${sitemapPath}`);
14953
+ lines.push("", `Sitemap: ${absoluteUrl(site, sitemapPath)}`);
14843
14954
  }
14844
14955
  return `${lines.join(`
14845
14956
  `)}
@@ -14934,12 +15045,19 @@ var lastmodTag = (value) => {
14934
15045
  const date = new Date(value);
14935
15046
  return Number.isNaN(date.getTime()) ? "" : `<lastmod>${date.toISOString().slice(0, 10)}</lastmod>`;
14936
15047
  };
14937
- 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) => {
14938
15056
  const { site } = project.config.deployment;
14939
15057
  if (!(site && project.config.seo.sitemap)) {
14940
15058
  return null;
14941
15059
  }
14942
- const base = site.replace(/\/$/u, "");
15060
+ const base = siteRoot(site);
14943
15061
  const deployBase = normalizeBasePath(project.config.deployment.base);
14944
15062
  const seen = new Set;
14945
15063
  const urls = [];
@@ -14966,12 +15084,27 @@ var buildSitemap = (project) => {
14966
15084
  pushUrl(withBasePath(deployBase, route));
14967
15085
  }
14968
15086
  urls.sort();
14969
- return `<?xml version="1.0" encoding="UTF-8"?>
14970
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
14971
- ${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(`
14972
15104
  `)}
14973
- </urlset>
15105
+ </sitemapindex>
14974
15106
  `;
15107
+ return [{ name: "sitemap.xml", xml: index }, ...chunks];
14975
15108
  };
14976
15109
 
14977
15110
  // src/deploy/vercel-negotiation.ts
@@ -15081,6 +15214,11 @@ var buildSearchIndex = async (outDir) => {
15081
15214
  return result.page_count;
15082
15215
  };
15083
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
+
15084
15222
  // src/search/facets.ts
15085
15223
  var pageFacets = (page, config) => {
15086
15224
  const declared = config.content.types[page.contentType]?.facets;
@@ -15098,25 +15236,60 @@ var pageFacets = (page, config) => {
15098
15236
  };
15099
15237
 
15100
15238
  // src/search/documents.ts
15101
- var CODE_FENCE2 = /```[\s\S]*?```/gu;
15102
- var INLINE_CODE2 = /`(?<code>[^`]+)`/gu;
15103
15239
  var HTML_OR_JSX = /<\/?[a-zA-Z][^\n<>]*>|<\/?>/gu;
15104
- var IMAGE = /!\[[^\]]*\]\([^)]*\)/gu;
15105
- var LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
15106
- var HEADING_MARK = /^#{1,6}\s+/gmu;
15107
- var MARKDOWN_PUNCT = /[*_~>]+/gu;
15108
15240
  var WHITESPACE3 = /\s+/gu;
15109
- var toPlainText = (markdown) => {
15110
- const withoutBlocks = markdown.replaceAll(CODE_FENCE2, " ").replaceAll(IMAGE, " ").replaceAll(LINK, "$<text>");
15111
- const pieces = [];
15112
- let cursor = 0;
15113
- for (const match of withoutBlocks.matchAll(INLINE_CODE2)) {
15114
- const start = match.index ?? 0;
15115
- pieces.push(withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX, " "), match.groups?.code ?? "");
15116
- 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
+ }
15117
15283
  }
15118
- pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX, " "));
15119
- 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();
15120
15293
  };
15121
15294
  var buildCrumbIndex = (sidebar) => {
15122
15295
  const index = new Map;
@@ -15436,7 +15609,7 @@ var refuseIfDevRunning = (root, action, options = {}) => {
15436
15609
  };
15437
15610
 
15438
15611
  // src/astro/generate.ts
15439
- import { createHash as createHash3 } from "node:crypto";
15612
+ import { createHash as createHash4 } from "node:crypto";
15440
15613
  import { existsSync as existsSync16, readFileSync as readFileSync8, realpathSync } from "node:fs";
15441
15614
  import {
15442
15615
  lstat,
@@ -15449,6 +15622,8 @@ import {
15449
15622
  } from "node:fs/promises";
15450
15623
  import { createRequire as createRequire4 } from "node:module";
15451
15624
  import { pathToFileURL as pathToFileURL2 } from "node:url";
15625
+ import { imageSize as imageSize2 } from "image-size";
15626
+ import pMap6 from "p-map";
15452
15627
  import { basename as basename3, dirname as dirname11, join as join28, normalize as normalize3, relative as relative14, resolve as resolve8 } from "pathe";
15453
15628
  import { glob as glob7 } from "tinyglobby";
15454
15629
 
@@ -15557,7 +15732,7 @@ var MCP_TOOLS = [
15557
15732
  // src/ai/mcp/discovery.ts
15558
15733
  var serverUrl = (input) => {
15559
15734
  const path = withBasePath(input.base, input.route);
15560
- return input.site ? `${trimEnd(input.site, "/")}${path}` : path;
15735
+ return input.site ? absoluteUrl(input.site, path) : path;
15561
15736
  };
15562
15737
  var buildMcpDiscovery = (input) => ({
15563
15738
  servers: [
@@ -15570,7 +15745,10 @@ var buildMcpDiscovery = (input) => ({
15570
15745
  });
15571
15746
  var SERVER_CARD_SCHEMA = "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json";
15572
15747
  var CARD_TEXT_MAX = 100;
15573
- 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, "-"), "-");
15574
15752
  var reverseDnsName = (input) => {
15575
15753
  let namespace = "localhost";
15576
15754
  if (input.site) {
@@ -15578,7 +15756,7 @@ var reverseDnsName = (input) => {
15578
15756
  namespace = new URL(input.site).hostname.split(".").toReversed().join(".");
15579
15757
  } catch {}
15580
15758
  }
15581
- return `${namespace}/${slugify(input.name) || "docs"}`;
15759
+ return `${namespace}/${asciiSlugify(input.name) || "docs"}`;
15582
15760
  };
15583
15761
  var HTTP_URL2 = /^https?:\/\//u;
15584
15762
  var buildMcpServerCard = (input) => {
@@ -15601,7 +15779,7 @@ var buildMcpServerCard = (input) => {
15601
15779
  transports: [{ endpoint: url, type: "streamable-http" }],
15602
15780
  url,
15603
15781
  version: input.version,
15604
- ...input.site ? { websiteUrl: trimEnd(input.site, "/") } : {}
15782
+ ...input.site ? { websiteUrl: siteRoot(input.site) } : {}
15605
15783
  };
15606
15784
  };
15607
15785
 
@@ -15936,9 +16114,9 @@ var analyzeComponentOverrides = (source, filePath) => {
15936
16114
  import { mkdir as mkdir6 } from "node:fs/promises";
15937
16115
  import { dirname as dirname9 } from "pathe";
15938
16116
  import writeFileAtomic from "write-file-atomic";
15939
- var writeTextAtomic = async (path, text2) => {
16117
+ var writeTextAtomic = async (path, text) => {
15940
16118
  await mkdir6(dirname9(path), { recursive: true });
15941
- await writeFileAtomic(path, text2, { encoding: "utf-8", fsync: false });
16119
+ await writeFileAtomic(path, text, { encoding: "utf-8", fsync: false });
15942
16120
  };
15943
16121
 
15944
16122
  // src/core/tsconfig-aliases.ts
@@ -16185,9 +16363,8 @@ ${dark}`;
16185
16363
 
16186
16364
  // src/openapi/scalar.ts
16187
16365
  var URL_SPEC2 = /^https?:\/\//u;
16188
- var ROUTE_EDGES = /^\/+|\/+$/gu;
16189
16366
  var referencePagePath = (route) => {
16190
- const segments = route.replace(ROUTE_EDGES, "");
16367
+ const segments = trimChar(route, "/");
16191
16368
  return `${segments === "" ? "index" : segments}.astro`;
16192
16369
  };
16193
16370
  var darkModeConfig = (mode) => {
@@ -17229,14 +17406,17 @@ export const layoutOverrides = { ...(overrides.layout ?? {})${layoutEntries.leng
17229
17406
 
17230
17407
  // src/astro/examples.ts
17231
17408
  import { readFile as readFile15 } from "node:fs/promises";
17409
+ import pMap5 from "p-map";
17232
17410
  import { join as join27, relative as relative13 } from "pathe";
17233
17411
  import { glob as glob6 } from "tinyglobby";
17234
17412
 
17235
17413
  // src/astro/islands.ts
17236
17414
  import { readFile as readFile14 } from "node:fs/promises";
17415
+ import pMap4 from "p-map";
17237
17416
  import { basename as basename2, join as join26 } from "pathe";
17238
17417
  import { glob as glob5 } from "tinyglobby";
17239
17418
  var DEFAULT_CLIENT = "visible";
17419
+ var READ_CONCURRENCY2 = 16;
17240
17420
  var VALID_MODES = new Set([
17241
17421
  "idle",
17242
17422
  "load",
@@ -17270,7 +17450,9 @@ var discoverIslands = async (root) => {
17270
17450
  onlyFiles: true
17271
17451
  });
17272
17452
  const files = matches2.toSorted();
17273
- 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
+ });
17274
17456
  const islands = [];
17275
17457
  const warnings = [];
17276
17458
  const seen = new Map;
@@ -17315,6 +17497,7 @@ var FRAMEWORK_BY_EXT3 = {
17315
17497
  };
17316
17498
  var EXAMPLE_FILE = /\.(?<ext>astro|jsx|svelte|tsx|vue)$/u;
17317
17499
  var DEFAULT_EXAMPLE_GLOB = "**/*.{astro,jsx,svelte,tsx,vue}";
17500
+ var READ_CONCURRENCY3 = 16;
17318
17501
  var GLOB_MAGIC = /[!*?[\]{}]/u;
17319
17502
  var splitGlobBase = (pattern) => {
17320
17503
  const segments = pattern.split("/");
@@ -17333,7 +17516,9 @@ var discoverExamples = async (root, pattern = "examples") => {
17333
17516
  onlyFiles: true
17334
17517
  });
17335
17518
  const files = matches2.toSorted();
17336
- 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
+ });
17337
17522
  const examples = [];
17338
17523
  const warnings = [];
17339
17524
  const seen = new Map;
@@ -17542,6 +17727,18 @@ var searchProviderWarnings = (provider, root, pkgDir = packageRoot()) => {
17542
17727
  }
17543
17728
  return warnings;
17544
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
+ };
17545
17742
  var examplesCssFile = (root, config) => config.examples.css ? join28(root, config.examples.css) : null;
17546
17743
  var writeExamplesPreview = async (options) => {
17547
17744
  const { config, hasExamples, root, srcDir, write } = options;
@@ -17572,16 +17769,19 @@ var detectNeedsReact = async (root) => {
17572
17769
  return matches2.length > 0;
17573
17770
  };
17574
17771
  var containsMath = (content) => content.includes("$$") || content.includes("<Math");
17772
+ var READ_CONCURRENCY4 = 16;
17575
17773
  var detectUsesMath = async (root, staged = []) => {
17576
17774
  const files = await glob7(["**/*.{md,mdx}"], {
17577
17775
  cwd: root,
17578
17776
  ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
17579
17777
  onlyFiles: true
17580
17778
  });
17581
- 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
+ });
17582
17782
  return [...contents, ...staged].some(containsMath);
17583
17783
  };
17584
- var hashConfigSource = (source) => createHash3("sha256").update(source).digest("hex");
17784
+ var hashConfigSource = (source) => createHash4("sha256").update(source).digest("hex");
17585
17785
  var loadIntegrationBridge = async (config, context) => {
17586
17786
  if (config.integrations.length === 0 || !context.configFile) {
17587
17787
  return;
@@ -17631,35 +17831,25 @@ var collectStaged = (project) => {
17631
17831
  var writeStagedContent = async (out, staged) => {
17632
17832
  const contentDir = stagedContentDir(out);
17633
17833
  const written = new Set;
17634
- await Promise.all([...staged].map(async ([entryId, text2]) => {
17834
+ await Promise.all([...staged].map(async ([entryId, text]) => {
17635
17835
  const path = join28(contentDir, entryId);
17636
17836
  written.add(normalize3(path));
17637
- await writeIfChanged(path, text2);
17837
+ await writeIfChanged(path, text);
17638
17838
  }));
17639
17839
  if (existsSync16(contentDir)) {
17640
17840
  await pruneOrphans(contentDir, written);
17641
17841
  }
17642
17842
  };
17643
- var SVG_ROOT = /<svg\b(?<attributes>[^>]*)>/u;
17644
- var SVG_WIDTH = /\bwidth\s*=\s*["'](?<value>[^"']+)["']/u;
17645
- var SVG_HEIGHT = /\bheight\s*=\s*["'](?<value>[^"']+)["']/u;
17646
- var SVG_LENGTH = /^\s*(?<value>[\d.]+)(?:px)?\s*$/u;
17647
- var SVG_VIEW_BOX = /\bviewBox\s*=\s*["'][\d.-]+[\s,]+[\d.-]+[\s,]+(?<width>[\d.]+)[\s,]+(?<height>[\d.]+)["']/u;
17648
- var parseSvgLength = (value) => {
17649
- const length = Number(value?.match(SVG_LENGTH)?.groups?.value);
17650
- return length > 0 ? length : undefined;
17651
- };
17652
17843
  var svgDimensions = (svg) => {
17653
- const attributes = svg?.match(SVG_ROOT)?.groups?.attributes;
17654
- const width = parseSvgLength(attributes?.match(SVG_WIDTH)?.groups?.value);
17655
- const height = parseSvgLength(attributes?.match(SVG_HEIGHT)?.groups?.value);
17656
- if (width && height) {
17657
- 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;
17658
17852
  }
17659
- const viewBox = attributes?.match(SVG_VIEW_BOX);
17660
- const viewBoxWidth = Number(viewBox?.groups?.width);
17661
- const viewBoxHeight = Number(viewBox?.groups?.height);
17662
- return viewBoxWidth > 0 && viewBoxHeight > 0 ? { height: viewBoxHeight, width: viewBoxWidth } : undefined;
17663
17853
  };
17664
17854
  var readLogoSvg = (project, source) => {
17665
17855
  if (!source?.toLowerCase().endsWith(".svg")) {
@@ -17678,7 +17868,7 @@ var resolveLogo = (project) => {
17678
17868
  return null;
17679
17869
  }
17680
17870
  const config = typeof logo === "string" ? { image: logo } : logo;
17681
- const { href, image: source, text: text2 } = config;
17871
+ const { href, image: source, text } = config;
17682
17872
  const image = typeof source === "string" ? { light: source } : source;
17683
17873
  const light = image?.light ?? image?.dark;
17684
17874
  const dark = image?.dark ?? image?.light;
@@ -17687,12 +17877,12 @@ var resolveLogo = (project) => {
17687
17877
  const lightSvg = readLogoSvg(project, light);
17688
17878
  const darkSvg = dark === light ? lightSvg : readLogoSvg(project, dark);
17689
17879
  if (light && light === dark && lightSvg) {
17690
- return { alt, href: brandHref, svg: lightSvg, text: text2 };
17880
+ return { alt, href: brandHref, svg: lightSvg, text };
17691
17881
  }
17692
17882
  const lightDimensions = svgDimensions(lightSvg);
17693
17883
  const darkDimensions = svgDimensions(darkSvg);
17694
17884
  const dimensions = lightDimensions || darkDimensions ? { dark: darkDimensions, light: lightDimensions } : undefined;
17695
- return { alt, dark, dimensions, href: brandHref, light, text: text2 };
17885
+ return { alt, dark, dimensions, href: brandHref, light, text };
17696
17886
  };
17697
17887
  var FAVICON_CANDIDATES = [
17698
17888
  "icon.svg",
@@ -17980,7 +18170,7 @@ var writeAskFiles = async (project, srcDir, write) => {
17980
18170
  await write(join28(srcDir, "generated", "ask-data.json"), `${JSON.stringify(await buildAskData(project))}
17981
18171
  `);
17982
18172
  }
17983
- 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));
17984
18174
  };
17985
18175
  var writeNotFoundPage = async (write, srcDir, pages, contentPages) => {
17986
18176
  if (routeIsTaken(pages, contentPages, "/404")) {
@@ -18217,7 +18407,7 @@ var generateRuntime = async (project) => {
18217
18407
  ...islandDiscovery.islands.map((island) => island.name),
18218
18408
  ...overrideTags
18219
18409
  ]);
18220
- 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));
18221
18411
  if (hasScalarReferences(config)) {
18222
18412
  const references = await buildReferenceFiles({
18223
18413
  config,
@@ -18241,37 +18431,24 @@ var generateRuntime = async (project) => {
18241
18431
  };
18242
18432
 
18243
18433
  // src/cli/env.ts
18244
- import { existsSync as existsSync17, readFileSync as readFileSync9 } from "node:fs";
18245
- import { parse as parse2 } from "dotenv";
18434
+ import { existsSync as existsSync17 } from "node:fs";
18435
+ import { config } from "dotenv";
18246
18436
  import { dirname as dirname12, join as join29, resolve as resolve9 } from "pathe";
18247
- var applyEnv = (parsed) => {
18248
- for (const [key, value] of Object.entries(parsed)) {
18249
- if (!(key in process.env)) {
18250
- process.env[key] = value;
18251
- }
18252
- }
18253
- };
18254
- var loadFile = (path) => {
18255
- try {
18256
- if (existsSync17(path)) {
18257
- applyEnv(parse2(readFileSync9(path, "utf-8")));
18258
- }
18259
- } catch {}
18260
- };
18261
18437
  var loadEnvFiles = (startDir) => {
18438
+ const paths = [];
18262
18439
  let dir = resolve9(startDir);
18263
18440
  let done = false;
18264
18441
  while (!done) {
18265
- loadFile(join29(dir, ".env.local"));
18266
- loadFile(join29(dir, ".env"));
18442
+ paths.push(join29(dir, ".env.local"), join29(dir, ".env"));
18267
18443
  const parent = dirname12(dir);
18268
18444
  done = existsSync17(join29(dir, ".git")) || parent === dir;
18269
18445
  dir = parent;
18270
18446
  }
18447
+ config({ path: paths, quiet: true });
18271
18448
  };
18272
18449
 
18273
18450
  // src/cli/required-secrets.ts
18274
- var checkRequiredSecrets = (config) => {
18451
+ var checkRequiredSecrets = (config2) => {
18275
18452
  const diagnostics = [];
18276
18453
  const requireSecret = (feature, env, note) => {
18277
18454
  if (process.env[env]) {
@@ -18285,15 +18462,15 @@ var checkRequiredSecrets = (config) => {
18285
18462
  suggestion: `Set ${env} in .env.local for local dev, or in your host's environment for production.`
18286
18463
  });
18287
18464
  };
18288
- if (config.ai.ask?.enabled && !config.ai.ask.endpoint) {
18289
- const backend = resolveAskBackend(config.ai.ask);
18465
+ if (config2.ai.ask?.enabled && !config2.ai.ask.endpoint) {
18466
+ const backend = resolveAskBackend(config2.ai.ask);
18290
18467
  if (backend.kind === "gateway") {
18291
18468
  requireSecret("Ask AI (AI Gateway)", "AI_GATEWAY_API_KEY", "on Vercel the gateway can also authenticate via OIDC");
18292
18469
  } else {
18293
18470
  requireSecret("Ask AI", backend.apiKeyEnv);
18294
18471
  }
18295
18472
  }
18296
- if (config.search.provider === "mixedbread") {
18473
+ if (config2.search.provider === "mixedbread") {
18297
18474
  requireSecret("Mixedbread search", "MIXEDBREAD_API_KEY");
18298
18475
  }
18299
18476
  return diagnostics;
@@ -18365,9 +18542,9 @@ var validateBudgetFlags = (args) => {
18365
18542
  }
18366
18543
  }
18367
18544
  };
18368
- var emitRedirectFiles = async (config, distDir) => {
18369
- const redirects = platformRedirects(config);
18370
- 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") {
18371
18548
  return;
18372
18549
  }
18373
18550
  await writeFile7(join30(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
@@ -18379,11 +18556,11 @@ var emitRedirectFiles = async (config, distDir) => {
18379
18556
  logger.success(`Emitted redirect files for ${redirects.length} redirect(s)`);
18380
18557
  };
18381
18558
  var emitHeaderFiles = async (project, distDir) => {
18382
- const { config } = project;
18383
- 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"))) {
18384
18561
  return;
18385
18562
  }
18386
- const ours = buildNetlifyHeaders(config, buildHomeLinkHeader(config, markdownRoutePaths(project)));
18563
+ const ours = buildNetlifyHeaders(config2, buildHomeLinkHeader(config2, markdownRoutePaths(project)));
18387
18564
  const target = join30(distDir, "_headers");
18388
18565
  const existing = existsSync18(target) ? await readFile17(target, "utf-8") : "";
18389
18566
  await writeFile7(target, existing ? `${existing.trimEnd()}
@@ -18420,15 +18597,15 @@ var emitAgentSkills = async (project, distDir) => {
18420
18597
  await writeFile7(join30(outDir, "index.json"), buildSkillsIndex(skills, project.config), "utf-8");
18421
18598
  logger.success(`Published ${skills.length} agent skill(s) (.well-known/agent-skills/index.json)`);
18422
18599
  };
18423
- var emitWellKnownFiles = async (config, distDir) => {
18600
+ var emitWellKnownFiles = async (config2, distDir) => {
18424
18601
  const files = [
18425
18602
  {
18426
- content: buildSignaturesDirectory(config),
18603
+ content: buildSignaturesDirectory(config2),
18427
18604
  label: "Web Bot Auth",
18428
18605
  path: SIGNATURES_DIRECTORY_PATH
18429
18606
  },
18430
18607
  {
18431
- content: buildApiCatalog(config),
18608
+ content: buildApiCatalog(config2),
18432
18609
  label: "RFC 9727",
18433
18610
  path: API_CATALOG_PATH
18434
18611
  }
@@ -18444,18 +18621,18 @@ var emitWellKnownFiles = async (config, distDir) => {
18444
18621
  }
18445
18622
  };
18446
18623
  var emitVercelNegotiation = async (project, routePaths, root) => {
18447
- const { config } = project;
18624
+ const { config: config2 } = project;
18448
18625
  const configPath = join30(root, ".vercel", "output", "config.json");
18449
18626
  if (!existsSync18(configPath)) {
18450
18627
  return;
18451
18628
  }
18452
18629
  const overrides = {
18453
- ...hasApiCatalog(config) ? { [API_CATALOG_PATH.slice(1)]: API_CATALOG_TYPE } : {},
18454
- ...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 } : {}
18455
18632
  };
18456
18633
  const rawMarkdown = await buildRawMarkdown(project);
18457
18634
  const home = rawMarkdown["/"];
18458
- 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);
18459
18636
  if (injected === null) {
18460
18637
  logger.warn("Could not wire Accept: text/markdown negotiation into .vercel/output/config.json — raw Markdown stays available at the .md URLs.");
18461
18638
  return;
@@ -18465,7 +18642,7 @@ var emitVercelNegotiation = async (project, routePaths, root) => {
18465
18642
  };
18466
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.");
18467
18644
  var emitCloudflareNegotiation = async (project, routePaths) => {
18468
- const { config, context } = project;
18645
+ const { config: config2, context } = project;
18469
18646
  const serverDir = join30(context.distDir ?? join30(context.root, "dist"), "server");
18470
18647
  const wranglerPath = join30(serverDir, "wrangler.json");
18471
18648
  if (!existsSync18(wranglerPath)) {
@@ -18475,11 +18652,11 @@ var emitCloudflareNegotiation = async (project, routePaths) => {
18475
18652
  const rawMarkdown = await buildRawMarkdown(project);
18476
18653
  const home = rawMarkdown["/"];
18477
18654
  const injected = injectWorkerNegotiation(await readFile17(wranglerPath, "utf-8"), {
18478
- base: config.deployment.base,
18655
+ base: config2.deployment.base,
18479
18656
  contentRoutePaths: project.manifest.routes.map((route) => route.path),
18480
- homeLinkHeader: buildHomeLinkHeader(config, routePaths),
18657
+ homeLinkHeader: buildHomeLinkHeader(config2, routePaths),
18481
18658
  homeTokens: home ? markdownTokenCount(agentMarkdown(home)) : undefined,
18482
- redirects: platformRedirects(config),
18659
+ redirects: platformRedirects(config2),
18483
18660
  routePaths
18484
18661
  });
18485
18662
  if (injected === null) {
@@ -18555,20 +18732,20 @@ var runClientAssetChecks = async (staticDir, args) => {
18555
18732
  process.exit(1);
18556
18733
  }
18557
18734
  };
18558
- var isolatedOutputDir = (config, context) => {
18559
- const { adapter, output } = config.deployment;
18735
+ var isolatedOutputDir = (config2, context) => {
18736
+ const { adapter, output } = config2.deployment;
18560
18737
  if (output === "server" && adapter === "vercel") {
18561
18738
  return join30(context.outDir, ".vercel", "output");
18562
18739
  }
18563
18740
  return context.distDir ?? join30(context.outDir, "dist");
18564
18741
  };
18565
- var isolatedStaticDir = (config, context) => {
18566
- const { adapter, output } = config.deployment;
18567
- const outputDir = isolatedOutputDir(config, context);
18742
+ var isolatedStaticDir = (config2, context) => {
18743
+ const { adapter, output } = config2.deployment;
18744
+ const outputDir = isolatedOutputDir(config2, context);
18568
18745
  if (output === "server" && adapter === "vercel") {
18569
18746
  return join30(outputDir, "static");
18570
18747
  }
18571
- if (servesClientSubdir(config.deployment)) {
18748
+ if (servesClientSubdir(config2.deployment)) {
18572
18749
  return join30(outputDir, "client");
18573
18750
  }
18574
18751
  return outputDir;
@@ -18609,10 +18786,10 @@ var publishBuildArtifacts = async (project, distDir, args) => {
18609
18786
  if (project.config.ai.llmsTxt.enabled) {
18610
18787
  await publishLlmsFiles(project, distDir);
18611
18788
  }
18612
- const sitemap = buildSitemap(project);
18613
- if (sitemap && !existsSync18(join30(distDir, "sitemap.xml"))) {
18614
- await writeFile7(join30(distDir, "sitemap.xml"), sitemap, "utf-8");
18615
- 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)`);
18616
18793
  }
18617
18794
  const robots = buildRobots(project);
18618
18795
  if (robots && !existsSync18(join30(distDir, "robots.txt"))) {
@@ -18629,19 +18806,19 @@ var publishBuildArtifacts = async (project, distDir, args) => {
18629
18806
  await emitAgentSkills(project, distDir);
18630
18807
  await emitRedirectFiles(project.config, distDir);
18631
18808
  await emitHeaderFiles(project, distDir);
18632
- const { config } = project;
18633
- const features = serverFeatures(config);
18634
- 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)";
18635
18812
  logger.box([
18636
- `Output ${config.deployment.output}`,
18637
- `Adapter ${config.deployment.adapter ?? "none"}`,
18638
- `Site ${config.deployment.site ?? "not set"}`,
18639
- `Search ${config.search.provider}`,
18640
- `Redirects ${config.redirects.length}`,
18641
- `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}`,
18642
18819
  `Robots ${robots ? "yes" : "no"}`,
18643
18820
  `Agent JSON ${agentReadability ? "yes" : "no"}`,
18644
- `LLM files ${config.ai.llmsTxt.enabled ? "yes" : "no"}`,
18821
+ `LLM files ${config2.ai.llmsTxt.enabled ? "yes" : "no"}`,
18645
18822
  `Server features ${features.length > 0 ? features.join(", ") : "none"}`
18646
18823
  ].join(`
18647
18824
  `));
@@ -18814,6 +18991,7 @@ var checkCommand = defineCommand4({
18814
18991
  import { dev } from "astro";
18815
18992
  import { watch } from "chokidar";
18816
18993
  import { defineCommand as defineCommand5 } from "citty";
18994
+ import { debounce } from "perfect-debounce";
18817
18995
 
18818
18996
  // src/astro/integration.ts
18819
18997
  var overlayServer = null;
@@ -18868,29 +19046,6 @@ var parsePort = (value) => {
18868
19046
  return port;
18869
19047
  };
18870
19048
 
18871
- // src/cli/coalesce.ts
18872
- var coalescedRunner = (task) => {
18873
- let inFlight = null;
18874
- let pending = false;
18875
- const cycle = async () => {
18876
- try {
18877
- do {
18878
- pending = false;
18879
- await task();
18880
- } while (pending);
18881
- } finally {
18882
- inFlight = null;
18883
- }
18884
- };
18885
- return () => {
18886
- if (inFlight) {
18887
- pending = true;
18888
- return;
18889
- }
18890
- inFlight = cycle();
18891
- };
18892
- };
18893
-
18894
19049
  // src/cli/commands/dev.ts
18895
19050
  var normalizeHost = (host) => host === "" ? true : host ?? false;
18896
19051
  var routeSignature = (routes) => routes.map((route) => `${route.path} ${route.entryId}`).toSorted().join(`
@@ -18958,7 +19113,7 @@ var devCommand = defineCommand5({
18958
19113
  }
18959
19114
  showBlumeErrorOverlay(project.diagnostics);
18960
19115
  let lastSignature = routeSignature(project.manifest.routes);
18961
- const runRegenerate = coalescedRunner(async () => {
19116
+ const regenerate = debounce(async () => {
18962
19117
  try {
18963
19118
  const next = await scanProject(root, {
18964
19119
  devServerUrl,
@@ -18981,16 +19136,9 @@ var devCommand = defineCommand5({
18981
19136
  } catch (error) {
18982
19137
  logger.error(`Regeneration failed: ${error.message}`);
18983
19138
  }
18984
- });
18985
- let timer = null;
18986
- const regenerate = () => {
18987
- if (timer) {
18988
- clearTimeout(timer);
18989
- }
18990
- timer = setTimeout(runRegenerate, 80);
18991
- };
19139
+ }, 80);
18992
19140
  if (boundPort !== port) {
18993
- runRegenerate();
19141
+ regenerate();
18994
19142
  }
18995
19143
  const dirTargets = [project.context.pagesRoot].filter((target) => target !== null);
18996
19144
  const fileTargets = [
@@ -19019,14 +19167,14 @@ var devCommand = defineCommand5({
19019
19167
  });
19020
19168
 
19021
19169
  // src/cli/commands/doctor.ts
19022
- import { readFileSync as readFileSync10 } from "node:fs";
19170
+ import { readFileSync as readFileSync9 } from "node:fs";
19023
19171
  import { defineCommand as defineCommand6 } from "citty";
19024
19172
  import { join as join32 } from "pathe";
19025
19173
  import { satisfies } from "semver";
19026
19174
  var FALLBACK_NODE_RANGE = ">=22.12.0";
19027
19175
  var supportedNodeRange = () => {
19028
19176
  try {
19029
- const pkg = JSON.parse(readFileSync10(join32(packageRoot(), "package.json"), "utf-8"));
19177
+ const pkg = JSON.parse(readFileSync9(join32(packageRoot(), "package.json"), "utf-8"));
19030
19178
  return pkg.engines?.node || FALLBACK_NODE_RANGE;
19031
19179
  } catch {
19032
19180
  return FALLBACK_NODE_RANGE;
@@ -19057,9 +19205,9 @@ var doctorCommand = defineCommand6({
19057
19205
  try {
19058
19206
  const project = await scanProject(root, { mode: "build" });
19059
19207
  diagnostics.push(...project.diagnostics);
19060
- const { config } = project;
19061
- const features = serverFeatures(config);
19062
- 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") {
19063
19211
  diagnostics.push({
19064
19212
  code: "BLUME_SERVER_FEATURE_REQUIRED",
19065
19213
  message: `${features.join(", ")} require server output.`,
@@ -19067,7 +19215,7 @@ var doctorCommand = defineCommand6({
19067
19215
  suggestion: 'Set deployment.output to "server".'
19068
19216
  });
19069
19217
  }
19070
- if (config.deployment.output === "server" && !config.deployment.adapter) {
19218
+ if (config2.deployment.output === "server" && !config2.deployment.adapter) {
19071
19219
  diagnostics.push({
19072
19220
  code: "BLUME_ADAPTER_REQUIRED",
19073
19221
  message: "Server output requires an adapter.",
@@ -19077,8 +19225,8 @@ var doctorCommand = defineCommand6({
19077
19225
  }
19078
19226
  if (!args.json) {
19079
19227
  logger.info(`Pages: ${project.graph.pages.length}`);
19080
- logger.info(`Output: ${config.deployment.output}`);
19081
- logger.info(`Search: ${config.search.provider}`);
19228
+ logger.info(`Output: ${config2.deployment.output}`);
19229
+ logger.info(`Search: ${config2.search.provider}`);
19082
19230
  }
19083
19231
  } catch (error) {
19084
19232
  if (error instanceof BlumeError) {
@@ -19224,16 +19372,16 @@ var examplesPreviewFiles = (srcDir, basePath, hasExamples) => hasExamples ? [
19224
19372
  path: join33(srcDir, "pages", ...basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro")
19225
19373
  }
19226
19374
  ] : [];
19227
- 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;
19228
19376
  var eject = async (root) => {
19229
19377
  const project = await scanProject(root, { mode: "build" });
19230
- const { context, config } = project;
19378
+ const { context, config: config2 } = project;
19231
19379
  const srcDir = join33(root, "src");
19232
19380
  const genDir = join33(srcDir, "generated");
19233
- const askEnabled = config.ai.ask?.enabled ?? false;
19234
- const exportPdf = config.export.pdf;
19235
- const exportEpub = config.export.epub;
19236
- 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);
19237
19385
  project.examples = exampleMarkdownLookup(examples.examples);
19238
19386
  const [
19239
19387
  pages,
@@ -19248,7 +19396,7 @@ var eject = async (root) => {
19248
19396
  detectNeedsReact(root),
19249
19397
  detectUsesMath(root),
19250
19398
  context.themeFile ? readFile18(context.themeFile, "utf-8") : Promise.resolve(""),
19251
- readExamplesCss(root, config.examples.css),
19399
+ readExamplesCss(root, config2.examples.css),
19252
19400
  buildRawMarkdown(project),
19253
19401
  discoverIslands(root)
19254
19402
  ]);
@@ -19280,13 +19428,13 @@ var eject = async (root) => {
19280
19428
  {
19281
19429
  content: astroConfigTemplate({
19282
19430
  askPath: "./src/generated/Ask.astro",
19283
- config,
19431
+ config: config2,
19284
19432
  contentRoutes: project.manifest.routes.map((route) => route.path),
19285
19433
  context: relContext,
19286
19434
  dataPath: "./src/generated/data.json",
19287
19435
  examplesPath: "./src/generated/examples.ts",
19288
19436
  examplesThemePath: "./src/generated/examples.css",
19289
- integrationBridge: ejectIntegrationBridge(config, root, context.configFile),
19437
+ integrationBridge: ejectIntegrationBridge(config2, root, context.configFile),
19290
19438
  needsReact,
19291
19439
  needsSvelte,
19292
19440
  needsVue,
@@ -19305,7 +19453,7 @@ var eject = async (root) => {
19305
19453
  { content: envTemplate(), path: join33(srcDir, "env.d.ts") },
19306
19454
  {
19307
19455
  content: contentConfigTemplate({
19308
- config,
19456
+ config: config2,
19309
19457
  context: relContext,
19310
19458
  staged: hasStaged,
19311
19459
  stagedBase: stagedDir
@@ -19330,12 +19478,12 @@ var eject = async (root) => {
19330
19478
  path: join33(genDir, "islands.ts")
19331
19479
  },
19332
19480
  {
19333
- content: exampleMapTemplate(examples.examples, config.basePath),
19481
+ content: exampleMapTemplate(examples.examples, config2.basePath),
19334
19482
  path: join33(genDir, "examples.ts")
19335
19483
  },
19336
19484
  {
19337
19485
  content: examplesEntryTemplate({
19338
- configTokens: buildThemeCss(config.theme),
19486
+ configTokens: buildThemeCss(config2.theme),
19339
19487
  sources: ["../../**/*.{astro,jsx,svelte,ts,tsx,vue}"],
19340
19488
  userCss: userExamplesCss
19341
19489
  }),
@@ -19343,7 +19491,7 @@ var eject = async (root) => {
19343
19491
  },
19344
19492
  {
19345
19493
  content: tailwindEntryTemplate({
19346
- configTokens: buildThemeCss(config.theme),
19494
+ configTokens: buildThemeCss(config2.theme),
19347
19495
  sources: [
19348
19496
  blumeSourceGlob(root, genDir),
19349
19497
  "../../**/*.{astro,mdx,ts,tsx}"
@@ -19380,9 +19528,9 @@ var eject = async (root) => {
19380
19528
  if (askEnabled) {
19381
19529
  files.push(...await askFiles(project, srcDir, genDir));
19382
19530
  }
19383
- if (config.seo.og.enabled) {
19531
+ if (config2.seo.og.enabled) {
19384
19532
  files.push({
19385
- content: ogEndpointTemplate(customOgRoutes(pages, config.title, config.seo.og.titles)),
19533
+ content: ogEndpointTemplate(customOgRoutes(pages, config2.title, config2.seo.og.titles)),
19386
19534
  path: join33(srcDir, "pages", "og", "[...slug].png.ts")
19387
19535
  });
19388
19536
  }
@@ -19399,10 +19547,10 @@ var eject = async (root) => {
19399
19547
  });
19400
19548
  }
19401
19549
  files.push({
19402
- content: searchClientTemplate(config),
19550
+ content: searchClientTemplate(config2),
19403
19551
  path: join33(genDir, "search-client.ts")
19404
19552
  });
19405
- if (servesStaticIndex(config.search.provider)) {
19553
+ if (servesStaticIndex(config2.search.provider)) {
19406
19554
  const documents = await buildSearchDocuments(project);
19407
19555
  files.push({
19408
19556
  content: `${JSON.stringify(documents)}
@@ -19413,9 +19561,9 @@ var eject = async (root) => {
19413
19561
  path: join33(srcDir, "pages", "blume-search.json.ts")
19414
19562
  });
19415
19563
  }
19416
- if (config.search.provider === "mixedbread") {
19564
+ if (config2.search.provider === "mixedbread") {
19417
19565
  files.push({
19418
- content: mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""),
19566
+ content: mixedbreadSearchEndpointTemplate(config2.search.mixedbread?.storeId ?? ""),
19419
19567
  path: join33(srcDir, "pages", "api", "search.ts")
19420
19568
  });
19421
19569
  }
@@ -19432,9 +19580,9 @@ var eject = async (root) => {
19432
19580
  });
19433
19581
  }
19434
19582
  const warnings = [];
19435
- if (hasScalarReferences(config)) {
19583
+ if (hasScalarReferences(config2)) {
19436
19584
  const references = await buildReferenceFiles({
19437
- config,
19585
+ config: config2,
19438
19586
  contentRoutes: new Set(project.graph.pages.map((page) => page.route)),
19439
19587
  root
19440
19588
  });
@@ -19452,7 +19600,7 @@ var eject = async (root) => {
19452
19600
  })), ...examples.examples.map((example) => ({
19453
19601
  content: exampleWrapperTemplate(example),
19454
19602
  path: join33(genDir, "examples", `${exampleSlug(example.path)}.astro`)
19455
- })), ...examplesPreviewFiles(srcDir, config.basePath, examples.examples.length > 0));
19603
+ })), ...examplesPreviewFiles(srcDir, config2.basePath, examples.examples.length > 0));
19456
19604
  for (const [entryId, content] of staged) {
19457
19605
  files.push({ content, path: join33(root, stagedDir, entryId) });
19458
19606
  }
@@ -19474,27 +19622,27 @@ var eject = async (root) => {
19474
19622
  // src/cli/eject-scripts.ts
19475
19623
  import { readFile as readFile19, writeFile as writeFile9 } from "node:fs/promises";
19476
19624
  import { join as join34 } from "pathe";
19477
- var droppedArtifactNotices = (config) => {
19625
+ var droppedArtifactNotices = (config2) => {
19478
19626
  const notices = [];
19479
- if (config.search.provider === "pagefind") {
19627
+ if (config2.search.provider === "pagefind") {
19480
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).');
19481
19629
  }
19482
- if (searchProviderMeta(config.search.provider).syncs) {
19483
- 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.`);
19484
19632
  }
19485
- if (config.ai.llmsTxt.enabled) {
19633
+ if (config2.ai.llmsTxt.enabled) {
19486
19634
  notices.push("llms.txt and llms-full.txt");
19487
19635
  }
19488
- if (config.deployment.site && config.seo.sitemap) {
19636
+ if (config2.deployment.site && config2.seo.sitemap) {
19489
19637
  notices.push("sitemap.xml — recreate it with the @astrojs/sitemap integration.");
19490
19638
  }
19491
- if (config.seo.robots) {
19639
+ if (config2.seo.robots) {
19492
19640
  notices.push("robots.txt — recreate it as a public/robots.txt file.");
19493
19641
  }
19494
- if (config.seo.agentReadability) {
19642
+ if (config2.seo.agentReadability) {
19495
19643
  notices.push("agent-readability.json");
19496
19644
  }
19497
- if (config.redirects.length > 0 && config.deployment.output === "static") {
19645
+ if (config2.redirects.length > 0 && config2.deployment.output === "static") {
19498
19646
  notices.push("the platform redirect files (_redirects, vercel.json) — your redirects still work as Astro-generated meta-refresh pages.");
19499
19647
  }
19500
19648
  return notices;
@@ -19521,6 +19669,7 @@ var updatePackageScripts = async (root) => {
19521
19669
  // src/cli/init/scaffold.ts
19522
19670
  import { existsSync as existsSync21 } from "node:fs";
19523
19671
  import { mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
19672
+ import { detect } from "package-manager-detector/detect";
19524
19673
  import { basename as basename4, dirname as dirname14, isAbsolute as isAbsolute9, join as join35, relative as relative16 } from "pathe";
19525
19674
 
19526
19675
  // src/core/package-json.ts
@@ -19649,6 +19798,11 @@ var detectPackageManager = (userAgent) => {
19649
19798
  const name = userAgent?.split("/")[0];
19650
19799
  return name !== undefined && PACKAGE_MANAGERS.includes(name) ? name : "npm";
19651
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
+ };
19652
19806
  var validateContentDir = (root, dir) => isAbsolute9(dir) || relative16(root, join35(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
19653
19807
  var titleize = (raw) => {
19654
19808
  const words = raw.replaceAll(/[-_.]+/gu, " ").split(/\s+/u).filter(Boolean);
@@ -19793,8 +19947,8 @@ var ejectCommand = defineCommand7({
19793
19947
  refuseIfDevRunning(root, "ejecting");
19794
19948
  let notices = [];
19795
19949
  try {
19796
- const { config } = await loadConfig(root);
19797
- notices = droppedArtifactNotices(config);
19950
+ const { config: config2 } = await loadConfig(root);
19951
+ notices = droppedArtifactNotices(config2);
19798
19952
  } catch {}
19799
19953
  if (!args.yes) {
19800
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.");
@@ -19813,7 +19967,7 @@ var ejectCommand = defineCommand7({
19813
19967
  `);
19814
19968
  }
19815
19969
  reportDroppedArtifacts(notices);
19816
- const pm = detectPackageManager(process.env.npm_config_user_agent);
19970
+ const pm = await detectProjectPackageManager(root);
19817
19971
  const { build: build2, dev: dev2 } = commandsFor(pm);
19818
19972
  logger.box(`Your project is now a standalone Astro app.
19819
19973
 
@@ -19898,6 +20052,20 @@ import { mkdtemp as mkdtemp2, writeFile as writeFile11 } from "node:fs/promises"
19898
20052
  import { tmpdir as tmpdir2 } from "node:os";
19899
20053
  import { colors as colors4 } from "consola/utils";
19900
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
19901
20069
  var GLYPH2 = {
19902
20070
  error: "!",
19903
20071
  fail: "✖",
@@ -19911,16 +20079,6 @@ var STATUS_COLOR = {
19911
20079
  skip: colors4.dim
19912
20080
  };
19913
20081
  var ID_PAD = 28;
19914
- var seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
19915
- var money = (cost) => cost === undefined ? "" : `$${cost.toFixed(2)}`;
19916
- var duration = (ms) => {
19917
- if (ms < 60000) {
19918
- return seconds(ms);
19919
- }
19920
- const minutes = Math.floor(ms / 60000);
19921
- const rest = Math.round(ms % 60000 / 1000);
19922
- return `${minutes}m ${rest}s`;
19923
- };
19924
20082
  var questionLine = (result) => {
19925
20083
  const color = STATUS_COLOR[result.status];
19926
20084
  const glyph = color(GLYPH2[result.status]);
@@ -20005,8 +20163,8 @@ import { tmpdir as tmpdir3 } from "node:os";
20005
20163
  import { join as join38 } from "pathe";
20006
20164
 
20007
20165
  // src/eval/agents.ts
20008
- import { spawn as spawn2 } from "node:child_process";
20009
20166
  import { readFile as readFile20, writeFile as writeFile12 } from "node:fs/promises";
20167
+ import spawn2 from "cross-spawn";
20010
20168
  import { join as join37 } from "pathe";
20011
20169
  import { z as z3 } from "zod";
20012
20170
  var KILL_GRACE_MS = 5000;
@@ -20030,19 +20188,17 @@ var DISALLOWED_TOOLS = [
20030
20188
  "Task"
20031
20189
  ];
20032
20190
  var runAgentHeadless = (bin, args, options) => new Promise((resolve11, reject) => {
20033
- const platform = options.platform ?? process.platform;
20034
20191
  const child = spawn2(bin, args, {
20035
20192
  cwd: options.cwd,
20036
- shell: platform === "win32",
20037
20193
  stdio: ["pipe", "pipe", "pipe"]
20038
20194
  });
20039
20195
  let stdout = "";
20040
20196
  let stderr = "";
20041
20197
  let timedOut = false;
20042
- child.stdout.on("data", (chunk) => {
20198
+ child.stdout?.on("data", (chunk) => {
20043
20199
  stdout += chunk.toString("utf-8");
20044
20200
  });
20045
- child.stderr.on("data", (chunk) => {
20201
+ child.stderr?.on("data", (chunk) => {
20046
20202
  stderr += chunk.toString("utf-8");
20047
20203
  });
20048
20204
  const deadline = setTimeout(() => {
@@ -20066,7 +20222,7 @@ var runAgentHeadless = (bin, args, options) => new Promise((resolve11, reject) =
20066
20222
  resolve11({ code: code ?? 1, stderr, stdout, timedOut });
20067
20223
  }
20068
20224
  });
20069
- child.stdin.end(options.prompt);
20225
+ child.stdin?.end(options.prompt);
20070
20226
  });
20071
20227
  var writeMcpConfig = async (dir, snapshotPath, launcher) => {
20072
20228
  const resolved = launcher ?? {
@@ -20074,12 +20230,12 @@ var writeMcpConfig = async (dir, snapshotPath, launcher) => {
20074
20230
  command: process.execPath
20075
20231
  };
20076
20232
  const configPath = join37(dir, "mcp-config.json");
20077
- const config = {
20233
+ const config2 = {
20078
20234
  mcpServers: {
20079
20235
  [MCP_SERVER_NAME]: { args: resolved.args, command: resolved.command }
20080
20236
  }
20081
20237
  };
20082
- await writeFile12(configPath, JSON.stringify(config, null, 2));
20238
+ await writeFile12(configPath, JSON.stringify(config2, null, 2));
20083
20239
  return {
20084
20240
  configPath,
20085
20241
  serverArgs: resolved.args,
@@ -20181,17 +20337,17 @@ var readAgentOutput = async (kind, result, lastMessagePath) => {
20181
20337
  text: payload.data.result
20182
20338
  };
20183
20339
  }
20184
- let text2;
20340
+ let text;
20185
20341
  try {
20186
20342
  const raw = await readFile20(lastMessagePath, "utf-8");
20187
- text2 = raw.trim();
20343
+ text = raw.trim();
20188
20344
  } catch {
20189
20345
  return { detail: "no last message written", isError: true, text: "" };
20190
20346
  }
20191
- if (text2 === "") {
20347
+ if (text === "") {
20192
20348
  return { detail: "empty last message", isError: true, text: "" };
20193
20349
  }
20194
- return { isError: false, text: text2 };
20350
+ return { isError: false, text };
20195
20351
  };
20196
20352
  var verdictSchema = z3.object({
20197
20353
  missing: z3.array(z3.string()).default([]),
@@ -20199,15 +20355,15 @@ var verdictSchema = z3.object({
20199
20355
  pass: z3.boolean(),
20200
20356
  score: z3.number().min(0).max(1).optional()
20201
20357
  });
20202
- var parseVerdict = (text2) => {
20203
- const start = text2.indexOf("{");
20204
- const end = text2.lastIndexOf("}");
20358
+ var parseVerdict = (text) => {
20359
+ const start = text.indexOf("{");
20360
+ const end = text.lastIndexOf("}");
20205
20361
  if (start === -1 || end <= start) {
20206
20362
  return;
20207
20363
  }
20208
20364
  let parsed;
20209
20365
  try {
20210
- parsed = JSON.parse(text2.slice(start, end + 1));
20366
+ parsed = JSON.parse(text.slice(start, end + 1));
20211
20367
  } catch {
20212
20368
  return;
20213
20369
  }
@@ -20486,21 +20642,21 @@ var runEval = async (options) => {
20486
20642
  var DEFAULT_FILE = "evals.yaml";
20487
20643
  var DEFAULT_TIMEOUT_S = 180;
20488
20644
  var isAgentKind = (value) => (value in AGENTS);
20489
- 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) => {
20490
20651
  try {
20491
- return await launchAgent(bin, prompt);
20652
+ return await launchAgent(AGENTS[agent].bin, prompt);
20492
20653
  } catch (error) {
20493
20654
  if (error?.code !== "ENOENT") {
20494
20655
  throw error;
20495
20656
  }
20496
- return WINDOWS_COMMAND_NOT_FOUND;
20657
+ return notInstalled(agent);
20497
20658
  }
20498
20659
  };
20499
- var notInstalled = (agent) => {
20500
- const cli = AGENTS[agent];
20501
- logger.error(`${cli.name} (\`${cli.bin}\`) was not found on PATH. Install it with \`${cli.install}\`.`);
20502
- return process.exit(1);
20503
- };
20504
20660
  var passFraction = (result) => {
20505
20661
  const ran = result.results.length - result.counts.skip;
20506
20662
  return ran === 0 ? 1 : result.counts.pass / ran;
@@ -20540,10 +20696,7 @@ var runFixHandoff = async (agent, result, root, threshold) => {
20540
20696
  process.stderr.write(` Handing ${count} failed question${count === 1 ? "" : "s"} to ${cli.name}…
20541
20697
 
20542
20698
  `);
20543
- const code = await launchAgentCode2(cli.bin, evalFixPrompt(report));
20544
- if (code === WINDOWS_COMMAND_NOT_FOUND) {
20545
- notInstalled(agent);
20546
- }
20699
+ const code = await launchAgentCode2(agent, evalFixPrompt(report));
20547
20700
  if (code !== 0) {
20548
20701
  process.exit(code);
20549
20702
  }
@@ -20554,10 +20707,7 @@ var runInit = async (agent, file) => {
20554
20707
  logger.error(`${file} already exists — edit it directly, or pass --file to draft elsewhere.`);
20555
20708
  process.exit(1);
20556
20709
  }
20557
- const code = await launchAgentCode2(AGENTS[agent].bin, initPrompt(file));
20558
- if (code === WINDOWS_COMMAND_NOT_FOUND) {
20559
- notInstalled(agent);
20560
- }
20710
+ const code = await launchAgentCode2(agent, initPrompt(file));
20561
20711
  if (code !== 0) {
20562
20712
  process.exit(code);
20563
20713
  }
@@ -20915,6 +21065,7 @@ import {
20915
21065
  CallToolRequestSchema,
20916
21066
  ListToolsRequestSchema
20917
21067
  } from "@modelcontextprotocol/sdk/types.js";
21068
+ import { z as z5 } from "zod";
20918
21069
 
20919
21070
  // src/search/orama-index.ts
20920
21071
  import { create, insertMultiple, search } from "@orama/orama";
@@ -20932,6 +21083,7 @@ var BOOST = { description: 2, title: 3 };
20932
21083
  var SEGMENTED_LANGUAGES = new Set(["ja", "ko", "th", "zh"]);
20933
21084
  var BIGRAM_LANGUAGES = new Set(["ja", "zh"]);
20934
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;
20935
21087
  var addBigrams = (run, tokens) => {
20936
21088
  const characters = [...run];
20937
21089
  if (characters.length === 1) {
@@ -20968,17 +21120,30 @@ var segmentingTokenizer = (locale) => {
20968
21120
  run = "";
20969
21121
  }
20970
21122
  };
20971
- 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())) {
20972
21132
  if (!segment.isWordLike) {
20973
21133
  flush();
20974
21134
  continue;
20975
21135
  }
20976
- if (bigram && BIGRAM_SCRIPTS.test(segment.segment)) {
20977
- run += segment.segment;
20978
- 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();
20979
21146
  }
20980
- flush();
20981
- tokens.add(segment.segment);
20982
21147
  }
20983
21148
  flush();
20984
21149
  return [...tokens];
@@ -21019,78 +21184,54 @@ var queryOramaIndex = async (db, term, limit, filters) => {
21019
21184
  var DEFAULT_SEARCH_LIMIT = 8;
21020
21185
  var MAX_SEARCH_LIMIT = 20;
21021
21186
  var EXCERPT_LENGTH = 200;
21022
- var CONTENT_TYPES_SCHEMA = {
21023
- 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.',
21024
- items: { type: "string" },
21025
- type: "array"
21026
- };
21027
- var FILTERS_SCHEMA = {
21028
- additionalProperties: { type: "string" },
21029
- 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.',
21030
- type: "object"
21031
- };
21032
- var INPUT_SCHEMAS = {
21033
- get_navigation: { properties: {}, type: "object" },
21034
- get_page: {
21035
- properties: {
21036
- route: {
21037
- description: "The page route, e.g. `/guides/install`.",
21038
- type: "string"
21039
- }
21040
- },
21041
- required: ["route"],
21042
- type: "object"
21043
- },
21044
- list_pages: {
21045
- properties: {
21046
- contentTypes: CONTENT_TYPES_SCHEMA,
21047
- filters: FILTERS_SCHEMA
21048
- },
21049
- type: "object"
21050
- },
21051
- search_docs: {
21052
- properties: {
21053
- contentTypes: CONTENT_TYPES_SCHEMA,
21054
- filters: FILTERS_SCHEMA,
21055
- limit: {
21056
- description: `Maximum hits to return (default ${DEFAULT_SEARCH_LIMIT}).`,
21057
- maximum: MAX_SEARCH_LIMIT,
21058
- minimum: 1,
21059
- type: "integer"
21060
- },
21061
- query: { description: "The search query.", type: "string" }
21062
- },
21063
- required: ["query"],
21064
- 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;
21065
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;
21066
21226
  };
21067
21227
  var TOOL_DEFINITIONS = MCP_TOOLS.map((tool) => ({
21068
21228
  annotations: tool.annotations,
21069
21229
  description: tool.description,
21070
- inputSchema: INPUT_SCHEMAS[tool.name],
21230
+ inputSchema: inputSchemaFor(TOOL_INPUTS[tool.name]),
21071
21231
  name: tool.name,
21072
21232
  title: tool.title
21073
21233
  }));
21074
- var asString2 = (value) => typeof value === "string" ? value : "";
21075
- var asContentTypes = (value) => {
21076
- const list2 = Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [value].filter((entry) => typeof entry === "string");
21077
- return list2.length > 0 ? list2 : undefined;
21078
- };
21079
- var asFacetFilters = (value) => {
21080
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
21081
- return;
21082
- }
21083
- const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
21084
- return entries.length > 0 ? Object.fromEntries(entries) : undefined;
21085
- };
21086
21234
  var matchesFacets = (facets, filters) => Object.entries(filters).every(([key, value]) => facets?.[key] === value);
21087
- var asLimit = (value) => {
21088
- const num = typeof value === "number" ? value : Number(value);
21089
- if (!Number.isFinite(num)) {
21090
- return DEFAULT_SEARCH_LIMIT;
21091
- }
21092
- return Math.min(Math.max(Math.trunc(num), 1), MAX_SEARCH_LIMIT);
21093
- };
21094
21235
  var normalizeRoute2 = (input, data) => {
21095
21236
  let value = input.trim();
21096
21237
  if (/^https?:\/\//iu.test(value)) {
@@ -21101,15 +21242,12 @@ var normalizeRoute2 = (input, data) => {
21101
21242
  try {
21102
21243
  value = decodeURI(value);
21103
21244
  } catch {}
21104
- const noTrailing = value.replace(/\/+$/u, "");
21105
- const noSuffix = noTrailing.replace(/\.mdx?$/u, "");
21106
- const withSlash = noSuffix.startsWith("/") ? noSuffix : `/${noSuffix}`;
21107
- const based = stripBasePath(data.base, withSlash);
21108
- return based === "" ? "/" : based;
21245
+ const noSuffix = trimEnd(value, "/").replace(/\.mdx?$/u, "");
21246
+ return stripBasePath(data.base, normalizeRoute(noSuffix));
21109
21247
  };
21110
21248
  var urlFor = (route, data) => {
21111
21249
  const path = withBasePath(data.base, route);
21112
- return data.site ? `${data.site.replace(/\/+$/u, "")}${path}` : path;
21250
+ return data.site ? absoluteUrl(data.site, path) : path;
21113
21251
  };
21114
21252
  var excerptFor = (doc) => {
21115
21253
  if (doc.description) {
@@ -21118,7 +21256,7 @@ var excerptFor = (doc) => {
21118
21256
  const head = doc.content.slice(0, EXCERPT_LENGTH).trim();
21119
21257
  return doc.content.length > EXCERPT_LENGTH ? `${head}…` : head;
21120
21258
  };
21121
- var text2 = (value, isError = false) => ({
21259
+ var text = (value, isError = false) => ({
21122
21260
  content: [{ text: value, type: "text" }],
21123
21261
  ...isError ? { isError: true } : {}
21124
21262
  });
@@ -21140,10 +21278,11 @@ var buildServer = (data, index) => {
21140
21278
  server.setRequestHandler(CallToolRequestSchema, async (request2) => {
21141
21279
  const { arguments: args = {}, name } = request2.params;
21142
21280
  if (name === "search_docs") {
21281
+ const input = TOOL_INPUTS.search_docs.parse(args);
21143
21282
  const db = await index();
21144
- const hits = await queryOramaIndex(db, asString2(args.query), asLimit(args.limit), {
21145
- contentTypes: asContentTypes(args.contentTypes),
21146
- 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
21147
21286
  });
21148
21287
  const results = hits.map((doc) => ({
21149
21288
  contentType: doc.contentType,
@@ -21153,21 +21292,21 @@ var buildServer = (data, index) => {
21153
21292
  title: doc.title,
21154
21293
  url: urlFor(doc.route, data)
21155
21294
  }));
21156
- return text2(JSON.stringify(results, null, 2));
21295
+ return text(JSON.stringify(results, null, 2));
21157
21296
  }
21158
21297
  if (name === "get_page") {
21159
- const key = normalizeRoute2(asString2(args.route), data);
21298
+ const input = TOOL_INPUTS.get_page.parse(args);
21299
+ const key = normalizeRoute2(input.route, data);
21160
21300
  const markdown = data.pages[key];
21161
21301
  if (markdown === undefined) {
21162
- 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);
21163
21303
  }
21164
- return text2(markdown);
21304
+ return text(markdown);
21165
21305
  }
21166
21306
  if (name === "list_pages") {
21167
- const contentTypes = asContentTypes(args.contentTypes);
21168
- const filters = asFacetFilters(args.filters);
21307
+ const { contentTypes, filters } = TOOL_INPUTS.list_pages.parse(args);
21169
21308
  const routes = data.routes.filter((route) => (!contentTypes || contentTypes.includes(route.contentType)) && (!filters || matchesFacets(route.facets, filters)));
21170
- return text2(JSON.stringify(routes.map((route) => ({
21309
+ return text(JSON.stringify(routes.map((route) => ({
21171
21310
  contentType: route.contentType,
21172
21311
  description: route.description,
21173
21312
  facets: route.facets,
@@ -21178,9 +21317,9 @@ var buildServer = (data, index) => {
21178
21317
  })), null, 2));
21179
21318
  }
21180
21319
  if (name === "get_navigation") {
21181
- return text2(JSON.stringify(data.navigation, null, 2));
21320
+ return text(JSON.stringify(data.navigation, null, 2));
21182
21321
  }
21183
- return text2(`Unknown tool: ${name}`, true);
21322
+ return text(`Unknown tool: ${name}`, true);
21184
21323
  });
21185
21324
  return server;
21186
21325
  };
@@ -21239,8 +21378,8 @@ var previewCommand = defineCommand11({
21239
21378
  },
21240
21379
  async run({ args }) {
21241
21380
  const root = process.cwd();
21242
- const { config } = await loadConfig(root);
21243
- const context = resolveProjectContext(root, config);
21381
+ const { config: config2 } = await loadConfig(root);
21382
+ const context = resolveProjectContext(root, config2);
21244
21383
  if (!existsSync23(join40(context.outDir, "astro.config.mjs"))) {
21245
21384
  logger.error("No build found. Run `blume build` first.");
21246
21385
  process.exit(1);
@@ -21279,8 +21418,8 @@ var syncCommand = defineCommand12({
21279
21418
  async run({ args }) {
21280
21419
  const root = process.cwd();
21281
21420
  if (args.force) {
21282
- const { config } = await loadConfig(root);
21283
- const context = resolveProjectContext(root, config);
21421
+ const { config: config2 } = await loadConfig(root);
21422
+ const context = resolveProjectContext(root, config2);
21284
21423
  await rm4(join41(context.outDir, "cache"), { force: true, recursive: true });
21285
21424
  logger.info("Cleared source cache.");
21286
21425
  }
@@ -21327,20 +21466,20 @@ var codexArgs2 = (lastMessagePath) => [
21327
21466
  var translateAgentArgs = (kind, lastMessagePath) => kind === "claude" ? claudeArgs2() : codexArgs2(lastMessagePath);
21328
21467
 
21329
21468
  // src/translate/ledger.ts
21330
- import { createHash as createHash4 } from "node:crypto";
21469
+ import { createHash as createHash5 } from "node:crypto";
21331
21470
  import { readFile as readFile23 } from "node:fs/promises";
21332
21471
  import { join as join42 } from "pathe";
21333
- import { z as z5 } from "zod";
21472
+ import { z as z6 } from "zod";
21334
21473
  var LEDGER_FILE = "blume.translations.json";
21335
- var ledgerSchema = z5.object({
21336
- files: z5.record(z5.string(), z5.record(z5.string(), z5.string())),
21337
- 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)
21338
21477
  });
21339
21478
  var emptyLedger = () => ({
21340
21479
  files: {},
21341
21480
  version: 1
21342
21481
  });
21343
- 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);
21344
21483
  var readLedger = async (root) => {
21345
21484
  let raw;
21346
21485
  try {
@@ -21427,16 +21566,6 @@ var SPINNER_FRAMES = [
21427
21566
  ];
21428
21567
  var SPINNER_INTERVAL_MS = 80;
21429
21568
  var REWRITE = `\r${ESC}[K`;
21430
- var seconds2 = (ms) => `${(ms / 1000).toFixed(1)}s`;
21431
- var money2 = (cost) => cost === undefined ? "" : `$${cost.toFixed(2)}`;
21432
- var duration2 = (ms) => {
21433
- if (ms < 60000) {
21434
- return seconds2(ms);
21435
- }
21436
- const minutes = Math.floor(ms / 60000);
21437
- const rest = Math.round(ms % 60000 / 1000);
21438
- return `${minutes}m ${rest}s`;
21439
- };
21440
21569
  var itemLabel = (item) => item.kind === "page" ? `${item.sourceRel} → ${item.locale}` : `meta title${item.entries.length === 1 ? "" : "s"} (${item.entries.length}) → ${item.locale}`;
21441
21570
  var spinnerLine = (active, done, total, frame) => {
21442
21571
  const first = active[0];
@@ -21448,7 +21577,7 @@ var itemEndLine = (result) => {
21448
21577
  const glyph = color(GLYPH3[result.status]);
21449
21578
  const label = itemLabel(result.item);
21450
21579
  if (result.status === "translated") {
21451
- 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(" ");
21452
21581
  return ` ${glyph} ${label} ${colors5.dim(cells)}`;
21453
21582
  }
21454
21583
  const word = result.status === "partial" ? "partial" : "failed";
@@ -21463,8 +21592,8 @@ var translateSummaryLine = (result, workList) => {
21463
21592
  counts.partial > 0 ? `${counts.partial} partial` : "",
21464
21593
  workList.untracked.length > 0 ? `${workList.untracked.length} adopted` : "",
21465
21594
  workList.upToDate > 0 ? `${workList.upToDate} already up to date` : "",
21466
- duration2(result.durationMs),
21467
- money2(result.costUsd)
21595
+ duration(result.durationMs),
21596
+ money(result.costUsd)
21468
21597
  ].filter((part) => part !== "");
21469
21598
  return parts.join(" · ");
21470
21599
  };
@@ -21605,6 +21734,8 @@ var createProgressRenderer = (options) => {
21605
21734
  import { existsSync as existsSync24 } from "node:fs";
21606
21735
  import { mkdtemp as mkdtemp4, readFile as readFile25 } from "node:fs/promises";
21607
21736
  import { tmpdir as tmpdir4 } from "node:os";
21737
+ import pLimit2 from "p-limit";
21738
+ import pMap7 from "p-map";
21608
21739
  import { join as join44 } from "pathe";
21609
21740
 
21610
21741
  // src/translate/meta.ts
@@ -21725,12 +21856,12 @@ ${JSON.stringify(titles, null, 2)}`;
21725
21856
  // src/translate/validate.ts
21726
21857
  var FRONTMATTER_OPEN = /^---\r?\n/u;
21727
21858
  var FENCE_LINE = /^\s*(?:```|~~~)/u;
21728
- var stripOuterFence = (text3) => {
21729
- const trimmed = text3.trim();
21859
+ var stripOuterFence = (text2) => {
21860
+ const trimmed = text2.trim();
21730
21861
  const match = trimmed.match(/^(?<fence>`{3,}|~{3,})[^\n]*\n(?<inner>[\s\S]*?)\n\k<fence>\s*$/u);
21731
21862
  return match?.groups?.inner ?? trimmed;
21732
21863
  };
21733
- var countFenceLines = (text3) => text3.split(`
21864
+ var countFenceLines = (text2) => text2.split(`
21734
21865
  `).filter((line) => FENCE_LINE.test(line)).length;
21735
21866
  var getPath2 = (data, path) => {
21736
21867
  let value = data;
@@ -21749,8 +21880,8 @@ var setPath = (data, path, value) => {
21749
21880
  }
21750
21881
  parent[path.at(-1)] = value;
21751
21882
  };
21752
- var ensureTrailingNewline = (text3) => text3.endsWith(`
21753
- `) ? text3 : `${text3}
21883
+ var ensureTrailingNewline = (text2) => text2.endsWith(`
21884
+ `) ? text2 : `${text2}
21754
21885
  `;
21755
21886
  var validateTranslation = (sourceText, agentText) => {
21756
21887
  const candidate = stripOuterFence(agentText);
@@ -21829,11 +21960,6 @@ var metaDirKey = (dir) => dir === "" ? "." : dir;
21829
21960
  var invokeAgent = async (context, prompt, index) => {
21830
21961
  const messagePath = join44(context.dir, `message-${index}.txt`);
21831
21962
  const result = await context.run(context.bin, translateAgentArgs(context.kind, messagePath), { cwd: context.dir, prompt, timeoutMs: context.timeoutMs });
21832
- if (!result.timedOut && result.code === WINDOWS_COMMAND_NOT_FOUND) {
21833
- const missing = new Error(`${context.bin} was not found on PATH`);
21834
- missing.code = "ENOENT";
21835
- throw missing;
21836
- }
21837
21963
  return await readAgentOutput(context.kind, result, messagePath);
21838
21964
  };
21839
21965
  var runPageItem = async (item, index, context, ledger) => {
@@ -21922,37 +22048,26 @@ var runTranslate = async (options) => {
21922
22048
  timeoutMs: options.timeoutMs ?? DEFAULT_TRANSLATE_TIMEOUT_MS
21923
22049
  };
21924
22050
  const { items } = options.workList;
21925
- const results = Array.from({ length: items.length });
21926
22051
  const concurrency = Math.max(1, Math.min(options.concurrency ?? 1, items.length || 1));
21927
- let persisting = Promise.resolve();
21928
- const persist = () => {
21929
- persisting = persisting.then(() => options.persistLedger?.());
21930
- return persisting;
21931
- };
21932
- let nextIndex = 0;
21933
- const worker = async () => {
21934
- while (nextIndex < items.length) {
21935
- const index = nextIndex;
21936
- nextIndex += 1;
21937
- const item = items[index];
21938
- options.onProgress?.({
21939
- index,
21940
- item,
21941
- kind: "item-start",
21942
- total: items.length
21943
- });
21944
- const result = await (item.kind === "page" ? runPageItem(item, index, context, options.ledger) : runMetaItem(item, index, context, options.ledger));
21945
- results[index] = result;
21946
- await persist();
21947
- options.onProgress?.({
21948
- index,
21949
- kind: "item-end",
21950
- result,
21951
- total: items.length
21952
- });
21953
- }
21954
- };
21955
- 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 });
21956
22071
  const diagnostics = [];
21957
22072
  for (const result of results) {
21958
22073
  if (result.status !== "translated") {
@@ -22106,7 +22221,7 @@ var computeWorkList = async (project, ledger, options = {}) => {
22106
22221
 
22107
22222
  // src/cli/commands/translate.ts
22108
22223
  var DEFAULT_TIMEOUT_S2 = DEFAULT_TRANSLATE_TIMEOUT_MS / 1000;
22109
- var DEFAULT_CONCURRENCY = 4;
22224
+ var DEFAULT_CONCURRENCY2 = 4;
22110
22225
  var MAX_CONCURRENCY = 16;
22111
22226
  var parseFlags2 = (args) => {
22112
22227
  const agents = Object.keys(AGENTS).filter((kind) => args[kind]);
@@ -22127,7 +22242,7 @@ var parseFlags2 = (args) => {
22127
22242
  logger.error(`Invalid --timeout "${args.timeout}" (whole seconds).`);
22128
22243
  process.exit(1);
22129
22244
  }
22130
- const concurrency = args.concurrency === undefined ? DEFAULT_CONCURRENCY : Number(args.concurrency);
22245
+ const concurrency = args.concurrency === undefined ? DEFAULT_CONCURRENCY2 : Number(args.concurrency);
22131
22246
  if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > MAX_CONCURRENCY) {
22132
22247
  logger.error(`Invalid --concurrency "${args.concurrency}" (use 1..${MAX_CONCURRENCY}).`);
22133
22248
  process.exit(1);
@@ -22174,7 +22289,7 @@ var translateCommand = defineCommand13({
22174
22289
  type: "boolean"
22175
22290
  },
22176
22291
  concurrency: {
22177
- description: `Parallel agent sessions. Defaults to ${DEFAULT_CONCURRENCY}, max ${MAX_CONCURRENCY}.`,
22292
+ description: `Parallel agent sessions. Defaults to ${DEFAULT_CONCURRENCY2}, max ${MAX_CONCURRENCY}.`,
22178
22293
  type: "string"
22179
22294
  },
22180
22295
  force: {
@@ -22593,5 +22708,5 @@ process.on("unhandledRejection", (error) => {
22593
22708
  });
22594
22709
  runMain(main);
22595
22710
 
22596
- //# debugId=84600BB45DA0E5EB64756E2164756E21
22711
+ //# debugId=07BF7082AA0A5EEC64756E2164756E21
22597
22712
  //# sourceMappingURL=index.js.map