@anubhav-choubey/web-for-agents 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vite.js CHANGED
@@ -1,19 +1,27 @@
1
1
  import {
2
- AI_BOTS
3
- } from "./chunk-CKXHTGGT.js";
2
+ EMPTY_OBJECT_SCHEMA,
3
+ schemaFromForm
4
+ } from "./chunk-LCVPXUFN.js";
4
5
  import {
5
- AGENT_MAP_VERSION
6
- } from "./chunk-LCVI2PRP.js";
6
+ AI_BOTS,
7
+ scanHtml
8
+ } from "./chunk-CGIIB6AF.js";
9
+ import {
10
+ AGENT_MAP_VERSION,
11
+ isValidActionName,
12
+ metaLengthWarnings
13
+ } from "./chunk-PHTV5NAY.js";
7
14
  import {
8
15
  DEFAULT_CONTENT_SELECTOR,
9
16
  DEFAULT_SHORTCUT,
10
17
  buildPageMarkdown,
11
18
  escapeLinkTarget,
12
19
  escapeMarkdownInline,
20
+ markupActions,
13
21
  parseShortcut,
14
22
  readPageMeta,
15
23
  selectContentRegion
16
- } from "./chunk-ZYMNO5AO.js";
24
+ } from "./chunk-PSNLS4GL.js";
17
25
  import {
18
26
  compareStrings
19
27
  } from "./chunk-AAIYCJD3.js";
@@ -25,13 +33,15 @@ import {
25
33
  TEMP_BASE_ATTR,
26
34
  prepareShell,
27
35
  readyCheck
28
- } from "./chunk-HONWT6GK.js";
36
+ } from "./chunk-SMFRAZAA.js";
29
37
  import {
30
38
  AGENT_GLOBAL,
31
39
  AGENT_MAP_PATH,
32
40
  CONTENT_ATTR,
41
+ HEAD_OWNED_ATTR,
33
42
  LLMS_FULL_TXT_PATH,
34
43
  LLMS_TXT_PATH,
44
+ MARKDOWN_LINK_KEY,
35
45
  MARKDOWN_MIME,
36
46
  PRERENDERED_ATTR,
37
47
  ROBOTS_PATH,
@@ -42,19 +52,23 @@ import {
42
52
  normalizeRoute,
43
53
  publicUrl,
44
54
  trimTrailingSlash
45
- } from "./chunk-AZG5MLNG.js";
55
+ } from "./chunk-F7PN5R25.js";
46
56
  import {
47
57
  POLL_MS,
48
58
  RenderError,
49
59
  SERIALIZE_EXPR,
60
+ SNAPSHOT_GRACE_MS,
61
+ TimeoutError,
62
+ assertNoPageErrors,
50
63
  blockedHint,
51
64
  createDeadline,
52
65
  createOriginPolicy,
53
66
  readyTimeoutMessage,
54
67
  runInPage,
68
+ snapshotWarning,
55
69
  waitForReady,
56
70
  withoutBlockedLoads
57
- } from "./chunk-5TNG4QMP.js";
71
+ } from "./chunk-PGZH33B5.js";
58
72
  import {
59
73
  LOG_PREFIX,
60
74
  errorMessage,
@@ -77,9 +91,18 @@ function segments(route) {
77
91
  function decodeSegments(route) {
78
92
  return segments(normalizeRoute(route)).map((s) => decodeURIComponent(s));
79
93
  }
80
- function routeHtmlFile(route) {
94
+ function encodePathSegment(segment) {
95
+ return encodeURIComponent(segment).replace(/%(24|26|2B|2C|3B|3D|3A|40)/g, (m) => decodeURIComponent(m));
96
+ }
97
+ function routeOfHtmlFile(file) {
98
+ const segs = file.slice(0, -".html".length).split("/");
99
+ if (segs[segs.length - 1] === "index") segs.pop();
100
+ return "/" + segs.map(encodePathSegment).join("/");
101
+ }
102
+ function routeHtmlFile(route, layout = "directory") {
81
103
  const segs = decodeSegments(route);
82
- return segs.length === 0 ? "index.html" : segs.join("/") + "/index.html";
104
+ if (segs.length === 0) return "index.html";
105
+ return segs.join("/") + (layout === "flat" ? ".html" : "/index.html");
83
106
  }
84
107
  function routeMdFile(route) {
85
108
  return markdownPathFor(route).slice(1).split("/").map((s) => decodeURIComponent(s)).join("/");
@@ -108,7 +131,7 @@ function isSafeRoute(route) {
108
131
  var MAX_NAME_BYTES = 255;
109
132
  var MAX_SEGMENT_BYTES = MAX_NAME_BYTES - ".md".length;
110
133
  var MAX_ROUTE_BYTES = 1024;
111
- function isRouteTooLong(route) {
134
+ function isRouteTooLong(route, layout = "directory") {
112
135
  let segs;
113
136
  try {
114
137
  segs = decodeSegments(route);
@@ -116,9 +139,10 @@ function isRouteTooLong(route) {
116
139
  return false;
117
140
  }
118
141
  let total = 0;
119
- for (const s of segs) {
142
+ const leafMax = layout === "flat" ? MAX_NAME_BYTES - ".html".length : MAX_SEGMENT_BYTES;
143
+ for (const [i, s] of segs.entries()) {
120
144
  const n = Buffer.byteLength(s, "utf8");
121
- if (n > MAX_SEGMENT_BYTES) return true;
145
+ if (n > (i === segs.length - 1 ? leafMax : MAX_SEGMENT_BYTES)) return true;
122
146
  total += n + 1;
123
147
  }
124
148
  return total > MAX_ROUTE_BYTES;
@@ -136,16 +160,16 @@ function linkToRoute(href, origin, base) {
136
160
  if (u.protocol !== "http:" && u.protocol !== "https:") return null;
137
161
  if (u.origin !== new URL(origin).origin) return null;
138
162
  const b = trimTrailingSlash(base);
139
- let path5 = u.pathname;
163
+ let path6 = u.pathname;
140
164
  if (b !== "") {
141
- if (path5 !== b && !path5.startsWith(b + "/")) return null;
142
- path5 = path5.slice(b.length);
165
+ if (path6 !== b && !path6.startsWith(b + "/")) return null;
166
+ path6 = path6.slice(b.length);
143
167
  }
144
- if (path5 === "") path5 = "/";
145
- path5 = path5.replace(/\/index\.html$/, "") || "/";
146
- const last = path5.split("/").pop();
168
+ if (path6 === "") path6 = "/";
169
+ path6 = path6.replace(/\/index\.html$/, "") || "/";
170
+ const last = path6.split("/").pop();
147
171
  if (/\.[a-z0-9]+$/i.test(last)) return null;
148
- const route = normalizeRoute(path5);
172
+ const route = normalizeRoute(path6);
149
173
  return isSafeRoute(route) ? route : null;
150
174
  }
151
175
 
@@ -231,6 +255,21 @@ function outputFileName(fileName) {
231
255
  }
232
256
  return rel;
233
257
  }
258
+ function symlinkOnPath(outDir, rel) {
259
+ let p = outDir;
260
+ const parts = rel.split("/");
261
+ for (let i = 0; i < parts.length; i++) {
262
+ p = path.join(p, parts[i]);
263
+ let st;
264
+ try {
265
+ st = fs.lstatSync(p);
266
+ } catch {
267
+ return null;
268
+ }
269
+ if (st.isSymbolicLink()) return parts.slice(0, i + 1).join("/");
270
+ }
271
+ return null;
272
+ }
234
273
  var short = (s, max) => s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
235
274
  function createFileStager(opts) {
236
275
  const files = /* @__PURE__ */ new Map();
@@ -247,6 +286,14 @@ function createFileStager(opts) {
247
286
  stage(fileName, source) {
248
287
  const file = outputFileName(fileName);
249
288
  if (inPublicDir(file)) return { file, status: "skipped" };
289
+ const link = symlinkOnPath(opts.outDir, file);
290
+ if (link !== null) {
291
+ if (!warned.has("link:" + file)) {
292
+ warned.add("link:" + file);
293
+ opts.warn(`${LOG_PREFIX} ${link} is a symbolic link; not writing ${file} through it`);
294
+ }
295
+ return { file, status: "skipped" };
296
+ }
250
297
  const status = files.has(file) ? "replaced" : "staged";
251
298
  files.set(file, source);
252
299
  return { file, status };
@@ -299,34 +346,6 @@ function createFileStager(opts) {
299
346
  };
300
347
  }
301
348
 
302
- // src/vite/llms.ts
303
- function header(site) {
304
- const blocks = [`# ${escapeMarkdownInline(site.title)}`];
305
- const desc = escapeMarkdownInline(site.description);
306
- if (desc) blocks.push(`> ${desc}`);
307
- return blocks;
308
- }
309
- function buildLlmsTxt(site, pages, sections = []) {
310
- const blocks = header(site);
311
- const lines = [...pages].sort(byRoute).map((p) => {
312
- const title = escapeMarkdownInline(p.title);
313
- const desc = escapeMarkdownInline(p.description);
314
- return `- [${title}](${escapeLinkTarget(p.mdUrl ?? p.url)})${desc ? `: ${desc}` : ""}`;
315
- });
316
- blocks.push(["## Pages", ...lines.length ? ["", ...lines] : []].join("\n"));
317
- for (const s of sections) {
318
- blocks.push([`## ${s.heading}`, ...s.lines.length ? ["", ...s.lines] : []].join("\n"));
319
- }
320
- return blocks.join("\n\n") + "\n";
321
- }
322
- function buildLlmsFullTxt(site, pages) {
323
- const head = header(site).join("\n\n");
324
- const blocks = [...pages].sort(byRoute).filter((p) => p.markdown !== void 0).map((p) => `Source: ${p.url}
325
-
326
- ${p.markdown.trim()}`);
327
- return [head, blocks.join("\n\n---\n\n")].filter(Boolean).join("\n\n") + "\n";
328
- }
329
-
330
349
  // src/vite/prerender/happyDomProcess.ts
331
350
  import { fork } from "child_process";
332
351
  import { createHash } from "crypto";
@@ -352,7 +371,7 @@ function nodeModulesRoot(file) {
352
371
  }
353
372
  var sourceEntry;
354
373
  async function childEntryFile() {
355
- const { CHILD_ENTRY } = await import("./renderChild-Y2RFSYY4.js");
374
+ const { CHILD_ENTRY } = await import("./renderChild-H6TR4SGV.js");
356
375
  const file = fileURLToPath(CHILD_ENTRY);
357
376
  if (!file.endsWith(".ts")) return file;
358
377
  return sourceEntry ??= bundleFromSource(file);
@@ -732,8 +751,18 @@ function resolvePrerender(input) {
732
751
  concurrency: posInt("concurrency", input.concurrency, usesBrowser ? 4 : 8),
733
752
  maxRoutes: posInt("maxRoutes", input.maxRoutes, 500),
734
753
  allowOrigins,
735
- isolation: resolveIsolation(input.isolation, bad)
754
+ isolation: resolveIsolation(input.isolation, bad),
755
+ fileLayout: input.fileLayout ?? "directory",
756
+ onTimeout: input.onTimeout ?? "fail"
736
757
  };
758
+ if (out.fileLayout !== "directory" && out.fileLayout !== "flat") bad("fileLayout", '"directory" or "flat"');
759
+ if (out.onTimeout !== "fail" && out.onTimeout !== "snapshot") bad("onTimeout", '"fail" or "snapshot"');
760
+ if (input.readySelector !== void 0) {
761
+ if (typeof input.readySelector !== "string" || input.readySelector.trim() === "") {
762
+ bad("readySelector", "a non-empty string");
763
+ }
764
+ out.readySelector = input.readySelector;
765
+ }
737
766
  if (input.executablePath !== void 0) out.executablePath = input.executablePath;
738
767
  if (input.idleMs !== void 0) {
739
768
  if (typeof input.idleMs !== "number" || !Number.isInteger(input.idleMs) || input.idleMs < 0) {
@@ -751,6 +780,21 @@ function agentOutputs(r) {
751
780
  if (r.agentMap) out.push(r.agentMap.fileName);
752
781
  return out;
753
782
  }
783
+ function resolveMarkdown(input) {
784
+ if (input !== void 0 && (typeof input !== "object" || input === null)) {
785
+ throw new Error(withPrefix("markdown must be false or { contentSelector, transform }"));
786
+ }
787
+ const sel = input?.contentSelector;
788
+ if (sel !== void 0 && (typeof sel !== "string" || sel === "")) {
789
+ throw new Error(`${LOG_PREFIX} markdown.contentSelector must be a non-empty string`);
790
+ }
791
+ const out = { contentSelector: sel ?? DEFAULT_CONTENT_SELECTOR };
792
+ if (input?.transform !== void 0) {
793
+ if (typeof input.transform !== "function") throw new Error(withPrefix("markdown.transform must be a function"));
794
+ out.transform = input.transform;
795
+ }
796
+ return out;
797
+ }
754
798
  var AGENT_MAP_FILE = /^[A-Za-z0-9_.-]+\.json$/;
755
799
  function resolveAgentMap(input) {
756
800
  if (input === false) return false;
@@ -814,8 +858,8 @@ function resolveOptions(input) {
814
858
  throw new Error(`${LOG_PREFIX} robots.ai.overrides.${bot} must be "allow" or "block"`);
815
859
  }
816
860
  }
817
- for (const path5 of input.robots.disallow ?? []) {
818
- if (/[\r\n]/.test(path5)) {
861
+ for (const path6 of input.robots.disallow ?? []) {
862
+ if (/[\r\n]/.test(path6)) {
819
863
  throw new Error(`${LOG_PREFIX} robots.disallow paths must not contain line breaks`);
820
864
  }
821
865
  }
@@ -836,13 +880,7 @@ function resolveOptions(input) {
836
880
  warnings.push(withPrefix(`${names} ${verb} disabled because prerender is false`));
837
881
  }
838
882
  } else {
839
- if (input.markdown !== false) {
840
- const sel = input.markdown?.contentSelector;
841
- if (sel !== void 0 && (typeof sel !== "string" || sel === "")) {
842
- throw new Error(`${LOG_PREFIX} markdown.contentSelector must be a non-empty string`);
843
- }
844
- markdown = { contentSelector: sel ?? DEFAULT_CONTENT_SELECTOR };
845
- }
883
+ if (input.markdown !== false) markdown = resolveMarkdown(input.markdown);
846
884
  if (input.llms !== false) llms = { ...input.llms };
847
885
  agentMap = requestedAgentMap;
848
886
  }
@@ -984,9 +1022,9 @@ function finalizeDocument(document, opts) {
984
1022
  if (!u.startsWith(origin)) return u;
985
1023
  const rest = u.slice(origin.length);
986
1024
  if (rest !== "" && !/^[/?#]/.test(rest)) return u;
987
- const path5 = rest.startsWith("/") ? rest : "/" + rest;
988
- if (opts.fullBase) return trimTrailingSlash(opts.fullBase) + "/" + path5.slice(1);
989
- return opts.relativeBase ? "." + path5 : path5;
1025
+ const path6 = rest.startsWith("/") ? rest : "/" + rest;
1026
+ if (opts.fullBase) return trimTrailingSlash(opts.fullBase) + "/" + path6.slice(1);
1027
+ return opts.relativeBase ? "." + path6 : path6;
990
1028
  }, attrs);
991
1029
  }
992
1030
  if (opts.relativeBase) {
@@ -1050,7 +1088,7 @@ async function crawlRoutes(opts, render) {
1050
1088
  const seeds = [];
1051
1089
  for (const s of [...new Set(opts.seeds.map(normalizeRoute))].sort()) {
1052
1090
  if (!isSafeRoute(s)) continue;
1053
- if (isRouteTooLong(s)) seedTooLong.push(s);
1091
+ if (isRouteTooLong(s, opts.fileLayout)) seedTooLong.push(s);
1054
1092
  else if (!skip(s)) seeds.push(s);
1055
1093
  }
1056
1094
  const attempt = async (route, renderer) => {
@@ -1104,7 +1142,7 @@ async function crawlRoutes(opts, render) {
1104
1142
  const tooLong2 = [];
1105
1143
  const candidate = (r) => {
1106
1144
  if (r === null || isExcluded(r, opts.exclude)) return false;
1107
- if (isRouteTooLong(r)) {
1145
+ if (isRouteTooLong(r, opts.fileLayout)) {
1108
1146
  tooLong2.push(r);
1109
1147
  return false;
1110
1148
  }
@@ -1286,7 +1324,7 @@ async function createChromeRenderer(channel, opts = {}, load = () => import("pla
1286
1324
  }
1287
1325
  const allowed = createOriginPolicy(opts.allowOrigins);
1288
1326
  const idleMs = opts.idleMs ?? IDLE_MS;
1289
- const check = readyCheck(idleMs);
1327
+ const check = readyCheck(idleMs, opts.readySelector);
1290
1328
  let gone = false;
1291
1329
  browser.on?.("disconnected", () => {
1292
1330
  gone = true;
@@ -1303,8 +1341,9 @@ async function createChromeRenderer(channel, opts = {}, load = () => import("pla
1303
1341
  return e;
1304
1342
  };
1305
1343
  const renderPage = async (url, timeoutMs, inPage) => {
1306
- const deadline = createDeadline(timeoutMs);
1307
- const timeoutText = readyTimeoutMessage(timeoutMs);
1344
+ let deadline = createDeadline(timeoutMs);
1345
+ let timeoutText = readyTimeoutMessage(timeoutMs);
1346
+ let budgetMs = timeoutMs;
1308
1347
  const origin = new URL(url).origin;
1309
1348
  const blocked = [];
1310
1349
  const opening = browser.newPage({ serviceWorkers: "block" }).then(async (p) => {
@@ -1345,22 +1384,35 @@ async function createChromeRenderer(channel, opts = {}, load = () => import("pla
1345
1384
  page.on("pageerror", (e) => pageErrors.push(e.message));
1346
1385
  let html;
1347
1386
  let finalPath;
1387
+ let timedOut = false;
1348
1388
  try {
1349
1389
  await deadline.race(
1350
1390
  page.goto(url, { waitUntil: "load", timeout: deadline.remaining() + 1e3 }),
1351
1391
  timeoutText
1352
1392
  );
1353
- await deadline.race(
1354
- waitForReady(
1355
- async () => {
1356
- const r = await page.evaluate(check);
1357
- return { ready: r.ready, errors: withoutBlockedLoads([...pageErrors, ...r.errors], blocked) };
1358
- },
1359
- timeoutMs,
1360
- Math.min(POLL_MS, idleMs)
1361
- ),
1362
- timeoutText
1363
- );
1393
+ try {
1394
+ await deadline.race(
1395
+ waitForReady(
1396
+ async () => {
1397
+ const r = await page.evaluate(check);
1398
+ return { ready: r.ready, errors: withoutBlockedLoads([...pageErrors, ...r.errors], blocked) };
1399
+ },
1400
+ timeoutMs,
1401
+ Math.min(POLL_MS, idleMs)
1402
+ ),
1403
+ timeoutText
1404
+ );
1405
+ } catch (e) {
1406
+ if (!(opts.onTimeout === "snapshot" && e instanceof TimeoutError)) throw e;
1407
+ timedOut = true;
1408
+ deadline = createDeadline(SNAPSHOT_GRACE_MS);
1409
+ budgetMs = SNAPSHOT_GRACE_MS;
1410
+ timeoutText = readyTimeoutMessage(SNAPSHOT_GRACE_MS);
1411
+ await assertNoPageErrors(async () => {
1412
+ const r = await deadline.race(page.evaluate(check), timeoutText);
1413
+ return withoutBlockedLoads([...pageErrors, ...r.errors], blocked);
1414
+ });
1415
+ }
1364
1416
  html = await deadline.race(page.evaluate(SERIALIZE_EXPR), timeoutText);
1365
1417
  finalPath = new URL(page.url()).pathname;
1366
1418
  } catch (e) {
@@ -1371,9 +1423,9 @@ async function createChromeRenderer(channel, opts = {}, load = () => import("pla
1371
1423
  inPage,
1372
1424
  (expr) => page.evaluate(expr),
1373
1425
  deadline,
1374
- timeoutMs
1426
+ budgetMs
1375
1427
  );
1376
- return { html, finalPath, inPage: results };
1428
+ return { html, finalPath, inPage: results, ...timedOut ? { warnings: [snapshotWarning(timeoutMs)] } : {} };
1377
1429
  } finally {
1378
1430
  await settleWithin(page.close(), opts.pageCloseMs ?? PAGE_CLOSE_MS);
1379
1431
  }
@@ -1397,6 +1449,10 @@ async function createChromeRenderer(channel, opts = {}, load = () => import("pla
1397
1449
  // src/vite/prerender/renderers.ts
1398
1450
  function createRendererPool(opts) {
1399
1451
  const made = /* @__PURE__ */ new Map();
1452
+ const readiness = {
1453
+ ...opts.readySelector !== void 0 ? { readySelector: opts.readySelector } : {},
1454
+ ...opts.onTimeout !== void 0 ? { onTimeout: opts.onTimeout } : {}
1455
+ };
1400
1456
  return {
1401
1457
  get(name) {
1402
1458
  let r = made.get(name);
@@ -1406,6 +1462,7 @@ function createRendererPool(opts) {
1406
1462
  {
1407
1463
  allowOrigins: opts.allowOrigins ?? [],
1408
1464
  ...opts.idleMs !== void 0 ? { idleMs: opts.idleMs } : {},
1465
+ ...readiness,
1409
1466
  ...opts.syncResources ? { syncResources: opts.syncResources } : {}
1410
1467
  },
1411
1468
  { codeGeneration: opts.codeGeneration === true }
@@ -1413,7 +1470,8 @@ function createRendererPool(opts) {
1413
1470
  ) : createChromeRenderer(name, {
1414
1471
  ...opts.executablePath !== void 0 ? { executablePath: opts.executablePath } : {},
1415
1472
  allowOrigins: opts.allowOrigins ?? [],
1416
- ...opts.idleMs !== void 0 ? { idleMs: opts.idleMs } : {}
1473
+ ...opts.idleMs !== void 0 ? { idleMs: opts.idleMs } : {},
1474
+ ...readiness
1417
1475
  });
1418
1476
  made.set(name, r);
1419
1477
  }
@@ -1433,7 +1491,25 @@ import { createServer } from "http";
1433
1491
  import { realpathSync as realpathSync2 } from "fs";
1434
1492
  import { readFile, realpath, stat } from "fs/promises";
1435
1493
  import { extname, resolve, sep } from "path";
1436
- import { parseAst } from "vite";
1494
+ var viteParseAst;
1495
+ var VITE_MISSING = "prerendering needs the vite package, which is not installed: npm i -D vite. To add agent files to HTML you prerendered yourself, use web-for-agents generate, which does not need vite.";
1496
+ function isPackageNotFound(e, pkg) {
1497
+ const code = e?.code;
1498
+ if (code !== "ERR_MODULE_NOT_FOUND" && code !== "MODULE_NOT_FOUND") return false;
1499
+ return new RegExp(`(Cannot find (package|module) ['"]${pkg}['"])`).test(errorMessage(e));
1500
+ }
1501
+ async function loadViteParser(importVite = () => import("vite")) {
1502
+ if (viteParseAst) return viteParseAst;
1503
+ let mod;
1504
+ try {
1505
+ mod = await importVite();
1506
+ } catch (e) {
1507
+ if (isPackageNotFound(e, "vite")) throw new Error(withPrefix(VITE_MISSING), { cause: e });
1508
+ throw e;
1509
+ }
1510
+ viteParseAst = mod.parseAst;
1511
+ return viteParseAst;
1512
+ }
1437
1513
  var TYPES = {
1438
1514
  ".html": "text/html; charset=utf-8",
1439
1515
  ".js": "text/javascript; charset=utf-8",
@@ -1458,6 +1534,8 @@ function fixImportMeta(code) {
1458
1534
  }
1459
1535
  function parenthesizeRegExps(code) {
1460
1536
  if (!code.includes("/")) return code;
1537
+ const parseAst = viteParseAst;
1538
+ if (!parseAst) throw new Error(withPrefix("internal: loadViteParser() must run before parenthesizeRegExps"));
1461
1539
  let ast;
1462
1540
  try {
1463
1541
  ast = parseAst(code, { sourceType: "module" });
@@ -1576,7 +1654,8 @@ function startStaticServer(opts) {
1576
1654
  send(500, "text/plain; charset=utf-8", "Internal error");
1577
1655
  }
1578
1656
  });
1579
- return new Promise((resolveP, reject) => {
1657
+ const ready = compat ? loadViteParser() : Promise.resolve();
1658
+ return ready.then(() => new Promise((resolveP, reject) => {
1580
1659
  server.once("error", reject);
1581
1660
  server.listen(0, "127.0.0.1", () => {
1582
1661
  const { port } = server.address();
@@ -1590,7 +1669,7 @@ function startStaticServer(opts) {
1590
1669
  })
1591
1670
  });
1592
1671
  });
1593
- });
1672
+ }));
1594
1673
  }
1595
1674
 
1596
1675
  // src/vite/prerender/index.ts
@@ -1626,6 +1705,8 @@ async function prerenderSite(input) {
1626
1705
  ...pr.executablePath !== void 0 ? { executablePath: pr.executablePath } : {},
1627
1706
  allowOrigins: pr.allowOrigins,
1628
1707
  ...pr.idleMs !== void 0 ? { idleMs: pr.idleMs } : {},
1708
+ ...pr.readySelector !== void 0 ? { readySelector: pr.readySelector } : {},
1709
+ onTimeout: pr.onTimeout,
1629
1710
  codeGeneration: pr.isolation.codeGeneration,
1630
1711
  syncResources
1631
1712
  });
@@ -1647,7 +1728,8 @@ async function prerenderSite(input) {
1647
1728
  contentSelector: markdown ? markdown.contentSelector : DEFAULT_CONTENT_SELECTOR,
1648
1729
  ...markdown ? { markdownUrl: (route) => publicMdUrl(options.siteUrl, route) } : {},
1649
1730
  inPage,
1650
- skipRoute: input.skipRoute
1731
+ skipRoute: input.skipRoute,
1732
+ fileLayout: pr.fileLayout
1651
1733
  },
1652
1734
  async (url, renderer, timeoutMs, scripts) => (await pool.get(renderer)).render(url, timeoutMs, scripts)
1653
1735
  ).catch((e) => {
@@ -1777,7 +1859,35 @@ function buildSitemap(siteUrl, routes) {
1777
1859
  return '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' + urls + "\n</urlset>\n";
1778
1860
  }
1779
1861
 
1780
- // src/vite/buildPlugin.ts
1862
+ // src/vite/llms.ts
1863
+ function header(site) {
1864
+ const blocks = [`# ${escapeMarkdownInline(site.title)}`];
1865
+ const desc = escapeMarkdownInline(site.description);
1866
+ if (desc) blocks.push(`> ${desc}`);
1867
+ return blocks;
1868
+ }
1869
+ function buildLlmsTxt(site, pages, sections = []) {
1870
+ const blocks = header(site);
1871
+ const lines = [...pages].sort(byRoute).map((p) => {
1872
+ const title = escapeMarkdownInline(p.title);
1873
+ const desc = escapeMarkdownInline(p.description);
1874
+ return `- [${title}](${escapeLinkTarget(p.mdUrl ?? p.url)})${desc ? `: ${desc}` : ""}`;
1875
+ });
1876
+ blocks.push(["## Pages", ...lines.length ? ["", ...lines] : []].join("\n"));
1877
+ for (const s of sections) {
1878
+ blocks.push([`## ${s.heading}`, ...s.lines.length ? ["", ...s.lines] : []].join("\n"));
1879
+ }
1880
+ return blocks.join("\n\n") + "\n";
1881
+ }
1882
+ function buildLlmsFullTxt(site, pages) {
1883
+ const head = header(site).join("\n\n");
1884
+ const blocks = [...pages].sort(byRoute).filter((p) => p.markdown !== void 0).map((p) => `Source: ${p.url}
1885
+
1886
+ ${p.markdown.trim()}`);
1887
+ return [head, blocks.join("\n\n---\n\n")].filter(Boolean).join("\n\n") + "\n";
1888
+ }
1889
+
1890
+ // src/vite/siteFiles.ts
1781
1891
  var SPA_FALLBACK_FILE = "200.html";
1782
1892
  var fileOf = (urlPath) => urlPath.slice(1);
1783
1893
  function prefixed(e, context) {
@@ -1792,6 +1902,105 @@ async function callHook(ext, hook, fn) {
1792
1902
  throw prefixed(e, `extension "${ext.name}" ${hook} failed`);
1793
1903
  }
1794
1904
  }
1905
+ function dropFileCollisions(pages, filesOf, taken, warn) {
1906
+ const kept = [];
1907
+ for (const page of pages) {
1908
+ const files = filesOf(page);
1909
+ const clash = files.find((f) => taken.has(f));
1910
+ if (clash !== void 0) {
1911
+ warn(
1912
+ `${LOG_PREFIX} skipped ${shortRoute(page.route)}: its file ${clash} is already the file of ${taken.get(clash)}`
1913
+ );
1914
+ continue;
1915
+ }
1916
+ for (const f of files) taken.set(f, shortRoute(page.route));
1917
+ kept.push(page);
1918
+ }
1919
+ return kept;
1920
+ }
1921
+ async function pageMarkdown(markdown, page) {
1922
+ const md = buildPageMarkdown(page.info.title, page.info.contentHtml, page.url);
1923
+ if (!markdown.transform) return md;
1924
+ let out;
1925
+ try {
1926
+ out = await markdown.transform({
1927
+ route: page.route,
1928
+ url: page.url,
1929
+ title: page.info.title,
1930
+ ...page.info.description !== void 0 ? { description: page.info.description } : {},
1931
+ markdown: md,
1932
+ html: page.html
1933
+ });
1934
+ } catch (e) {
1935
+ throw prefixed(e, `markdown.transform failed for ${shortRoute(page.route)}`);
1936
+ }
1937
+ if (out === void 0) return md;
1938
+ if (typeof out !== "string") {
1939
+ throw new Error(withPrefix(`markdown.transform must return a string or undefined (got ${typeof out} for ${shortRoute(page.route)})`));
1940
+ }
1941
+ return out;
1942
+ }
1943
+ function pageLengthWarnings(listed) {
1944
+ return metaLengthWarnings(
1945
+ listed.map((p) => {
1946
+ const out = { route: p.route };
1947
+ if (p.info.titleFromDocument) out.title = p.info.title;
1948
+ if (p.info.description !== void 0) out.description = p.info.description;
1949
+ return out;
1950
+ })
1951
+ );
1952
+ }
1953
+ async function stageSiteFiles(opts, stager, { pages: allPages, listed, mdByRoute }) {
1954
+ const { siteUrl, markdown, llms, extensions, logger } = opts;
1955
+ for (const ext of extensions) {
1956
+ if (!ext.onRoute) continue;
1957
+ for (const page of listed) {
1958
+ await callHook(ext, `onRoute(${page.route})`, () => ext.onRoute(page.route, page.inPage[ext.name]));
1959
+ }
1960
+ }
1961
+ const rootPage = allPages.find((p) => p.route === "/");
1962
+ const rootTitle = rootPage?.info.titleFromDocument ? rootPage.info.title : void 0;
1963
+ const site = {
1964
+ url: siteUrl,
1965
+ title: (llms ? llms.title : void 0) ?? rootTitle ?? new URL(siteUrl).hostname
1966
+ };
1967
+ const description = (llms ? llms.description : void 0) ?? rootPage?.info.description;
1968
+ if (description !== void 0) site.description = description;
1969
+ const pages = listed.map((p) => {
1970
+ const out = { route: p.route, title: p.info.title, url: publicUrl(siteUrl, p.route) };
1971
+ if (p.info.description !== void 0) out.description = p.info.description;
1972
+ if (markdown) out.mdUrl = publicMdUrl(siteUrl, p.route);
1973
+ return out;
1974
+ });
1975
+ if (llms) {
1976
+ const sections = [];
1977
+ for (const ext of extensions) {
1978
+ if (ext.llmsSections) sections.push(...await callHook(ext, "llmsSections", () => ext.llmsSections()));
1979
+ }
1980
+ const llmsPages = pages.map((p) => {
1981
+ const md = mdByRoute.get(p.route);
1982
+ return md !== void 0 ? { ...p, markdown: md } : { ...p };
1983
+ });
1984
+ stager.stage(fileOf(LLMS_TXT_PATH), buildLlmsTxt(site, llmsPages, sections));
1985
+ if (markdown) stager.stage(fileOf(LLMS_FULL_TXT_PATH), buildLlmsFullTxt(site, llmsPages));
1986
+ }
1987
+ for (const ext of extensions) {
1988
+ if (!ext.finalize) continue;
1989
+ const ctx = {
1990
+ site,
1991
+ pages,
1992
+ emitFile: (fileName, source) => {
1993
+ const { file, status } = stager.stage(fileName, source);
1994
+ if (status === "replaced") logger.warn(`${LOG_PREFIX} extension "${ext.name}" overwrote ${file}`);
1995
+ },
1996
+ warn: (msg) => logger.warn(withPrefix(msg))
1997
+ };
1998
+ if (llms) ctx.llmsUrl = publicUrl(siteUrl, LLMS_TXT_PATH);
1999
+ await callHook(ext, "finalize", () => ext.finalize(ctx));
2000
+ }
2001
+ }
2002
+
2003
+ // src/vite/buildPlugin.ts
1795
2004
  function isClientEnv(env) {
1796
2005
  if (!env) return true;
1797
2006
  if (env.config?.consumer) return env.config.consumer === "client";
@@ -1814,6 +2023,7 @@ function createBuildPlugin(resolved, extensions) {
1814
2023
  const shell = fs2.readFileSync(shellFile, "utf8");
1815
2024
  stager.stage(SPA_FALLBACK_FILE, shell);
1816
2025
  const pr = resolved.prerender;
2026
+ const layout = pr === false ? "directory" : pr.fileLayout;
1817
2027
  if (pr !== false && mayUseHappyDom(pr) && !hasPermissionModel() && !warnedPermission) {
1818
2028
  warnedPermission = true;
1819
2029
  logger.warn(NO_PERMISSION_MODEL_WARNING);
@@ -1825,71 +2035,35 @@ function createBuildPlugin(resolved, extensions) {
1825
2035
  base,
1826
2036
  shell,
1827
2037
  options: resolved,
1828
- skipRoute: (route) => stager.inPublicDir(routeHtmlFile(route)),
2038
+ skipRoute: (route) => stager.inPublicDir(routeHtmlFile(route, layout)),
1829
2039
  extensions
1830
2040
  });
1831
2041
  } catch (e) {
1832
2042
  throw prefixed(e, "prerender failed");
1833
2043
  }
2044
+ const filesOf = (route) => [routeHtmlFile(route, layout), ...markdown ? [routeMdFile(route)] : []];
2045
+ const pages = dropFileCollisions(
2046
+ result.pages,
2047
+ (p) => filesOf(p.route),
2048
+ /* @__PURE__ */ new Map([[SPA_FALLBACK_FILE, `the SPA fallback (${SPA_FALLBACK_FILE})`]]),
2049
+ (m) => logger.warn(m)
2050
+ );
2051
+ result = { ...result, pages };
1834
2052
  const mdByRoute = /* @__PURE__ */ new Map();
1835
- for (const page of result.pages) {
1836
- stager.stage(routeHtmlFile(page.route), page.html);
1837
- if (markdown) {
1838
- const md = buildPageMarkdown(page.info.title, page.info.contentHtml, publicUrl(siteUrl, page.route));
1839
- mdByRoute.set(page.route, md);
1840
- stager.stage(routeMdFile(page.route), md);
2053
+ const stageNew = (file, source) => {
2054
+ if (stager.stage(file, source).status === "replaced") {
2055
+ throw new Error(`${LOG_PREFIX} internal error: ${file} was generated twice; nothing was written`);
1841
2056
  }
1842
- }
1843
- return { result, listed: result.pages.filter((p) => p.listed), mdByRoute };
1844
- };
1845
- const runExtensions = async (stager, { result, listed, mdByRoute }) => {
1846
- const { siteUrl, markdown, llms } = resolved;
1847
- for (const ext of extensions) {
1848
- if (!ext.onRoute) continue;
1849
- for (const page of listed) {
1850
- await callHook(ext, `onRoute(${page.route})`, () => ext.onRoute(page.route, page.inPage[ext.name]));
1851
- }
1852
- }
1853
- const rootPage = result.pages.find((p) => p.route === "/");
1854
- const rootTitle = rootPage?.info.titleFromDocument ? rootPage.info.title : void 0;
1855
- const site = {
1856
- url: siteUrl,
1857
- title: (llms ? llms.title : void 0) ?? rootTitle ?? new URL(siteUrl).hostname
1858
2057
  };
1859
- const description = (llms ? llms.description : void 0) ?? rootPage?.info.description;
1860
- if (description !== void 0) site.description = description;
1861
- const pages = listed.map((p) => {
1862
- const out = { route: p.route, title: p.info.title, url: publicUrl(siteUrl, p.route) };
1863
- if (p.info.description !== void 0) out.description = p.info.description;
1864
- if (markdown) out.mdUrl = publicMdUrl(siteUrl, p.route);
1865
- return out;
1866
- });
1867
- if (llms) {
1868
- const sections = [];
1869
- for (const ext of extensions) {
1870
- if (ext.llmsSections) sections.push(...await callHook(ext, "llmsSections", () => ext.llmsSections()));
2058
+ for (const page of pages) {
2059
+ stageNew(routeHtmlFile(page.route, layout), page.html);
2060
+ if (markdown) {
2061
+ const md = await pageMarkdown(markdown, { ...page, url: publicUrl(siteUrl, page.route) });
2062
+ mdByRoute.set(page.route, md);
2063
+ stageNew(routeMdFile(page.route), md);
1871
2064
  }
1872
- const llmsPages = pages.map((p) => {
1873
- const md = mdByRoute.get(p.route);
1874
- return md !== void 0 ? { ...p, markdown: md } : { ...p };
1875
- });
1876
- stager.stage(fileOf(LLMS_TXT_PATH), buildLlmsTxt(site, llmsPages, sections));
1877
- if (markdown) stager.stage(fileOf(LLMS_FULL_TXT_PATH), buildLlmsFullTxt(site, llmsPages));
1878
- }
1879
- for (const ext of extensions) {
1880
- if (!ext.finalize) continue;
1881
- const ctx = {
1882
- site,
1883
- pages,
1884
- emitFile: (fileName, source) => {
1885
- const { file, status } = stager.stage(fileName, source);
1886
- if (status === "replaced") logger.warn(`${LOG_PREFIX} extension "${ext.name}" overwrote ${file}`);
1887
- },
1888
- warn: (msg) => logger.warn(withPrefix(msg))
1889
- };
1890
- if (llms) ctx.llmsUrl = publicUrl(siteUrl, LLMS_TXT_PATH);
1891
- await callHook(ext, "finalize", () => ext.finalize(ctx));
1892
2065
  }
2066
+ return { result, listed: pages.filter((p) => p.listed), mdByRoute };
1893
2067
  };
1894
2068
  const runPipeline = async () => {
1895
2069
  const { siteUrl, markdown } = resolved;
@@ -1899,12 +2073,21 @@ function createBuildPlugin(resolved, extensions) {
1899
2073
  const sitemapRoutes = rendered ? [
1900
2074
  .../* @__PURE__ */ new Set([
1901
2075
  ...rendered.listed.map((p) => p.route),
1902
- ...resolved.routes.filter((r) => stager.inPublicDir(routeHtmlFile(r)))
2076
+ ...resolved.routes.filter(
2077
+ (r) => stager.inPublicDir(routeHtmlFile(r, pr === false ? "directory" : pr.fileLayout))
2078
+ )
1903
2079
  ])
1904
2080
  ].sort() : resolved.routes;
1905
2081
  if (resolved.sitemap) stager.stage(fileOf(SITEMAP_PATH), buildSitemap(siteUrl, sitemapRoutes));
1906
2082
  stager.stage(fileOf(ROBOTS_PATH), buildRobots(resolved));
1907
- if (rendered) await runExtensions(stager, rendered);
2083
+ if (rendered) {
2084
+ const { siteUrl: url, markdown: md, llms } = resolved;
2085
+ await stageSiteFiles({ siteUrl: url, markdown: md, llms, extensions, logger }, stager, {
2086
+ pages: rendered.result.pages,
2087
+ listed: rendered.listed,
2088
+ mdByRoute: rendered.mdByRoute
2089
+ });
2090
+ }
1908
2091
  stager.flush();
1909
2092
  if (pr === false || !rendered) return;
1910
2093
  const { result, listed } = rendered;
@@ -1934,6 +2117,7 @@ function createBuildPlugin(resolved, extensions) {
1934
2117
  `${LOG_PREFIX} no [${CONTENT_ATTR}] or "${markdown.contentSelector}" element on ${result.bodyFallbackRoutes.join(", ")}; content was taken from <body> minus header, nav, footer, aside`
1935
2118
  );
1936
2119
  }
2120
+ for (const w of pageLengthWarnings(listed)) logger.warn(w);
1937
2121
  logger.info(
1938
2122
  `${LOG_PREFIX} prerendered ${result.pages.length} routes (${listed.length} listed in llms.txt and the sitemap; the sitemap also keeps configured routes served from publicDir)`
1939
2123
  );
@@ -1979,17 +2163,314 @@ function createBuildPlugin(resolved, extensions) {
1979
2163
  };
1980
2164
  }
1981
2165
 
2166
+ // src/vite/generate.ts
2167
+ import fs3 from "fs";
2168
+ import path4 from "path";
2169
+
2170
+ // src/vite/generateActions.ts
2171
+ function staticActions(document) {
2172
+ const out = /* @__PURE__ */ new Map();
2173
+ for (const a of markupActions(document)) {
2174
+ if (!isValidActionName(a.name) || out.has(a.name)) continue;
2175
+ out.set(a.name, {
2176
+ name: a.name,
2177
+ description: a.description?.trim() || a.name,
2178
+ inputSchema: a.kind === "form" ? schemaFromForm(a.element) : { ...EMPTY_OBJECT_SCHEMA, properties: {} },
2179
+ destructive: a.destructive,
2180
+ readOnly: false,
2181
+ kind: a.kind
2182
+ });
2183
+ }
2184
+ return [...out.values()].sort((a, b) => compareStrings(a.name, b.name));
2185
+ }
2186
+
2187
+ // src/vite/generateHtml.ts
2188
+ function detectCharset(bytes) {
2189
+ if (bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) return "utf-8";
2190
+ if (bytes[0] === 254 && bytes[1] === 255) return "utf-16be";
2191
+ if (bytes[0] === 255 && bytes[1] === 254) return "utf-16le";
2192
+ const head = Buffer.from(bytes.subarray(0, 1024)).toString("latin1");
2193
+ const m = /<meta\b[^>]*?charset\s*=\s*["']?\s*([A-Za-z0-9_:.-]+)/i.exec(head);
2194
+ if (m) {
2195
+ try {
2196
+ const enc = new TextDecoder(m[1]).encoding;
2197
+ return enc.startsWith("utf-16") ? "utf-8" : enc;
2198
+ } catch {
2199
+ }
2200
+ }
2201
+ return "utf-8";
2202
+ }
2203
+ function decodeHtml(bytes) {
2204
+ const charset = detectCharset(bytes);
2205
+ return { charset, text: new TextDecoder(charset).decode(bytes) };
2206
+ }
2207
+ var HEAD_RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "template", "textarea", "title", "noscript"]);
2208
+ var escapeAttr = (href) => href.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/[^\x20-\x7e]/gu, (c) => `&#x${c.codePointAt(0).toString(16)};`);
2209
+ function isMarkdownAlternate(attrs) {
2210
+ const get = (n) => attrs.find((a) => a.name === n)?.value ?? "";
2211
+ const rel = get("rel").toLowerCase().split(/\s+/);
2212
+ return rel.includes("alternate") && get("type").split(";")[0].trim().toLowerCase() === MARKDOWN_MIME;
2213
+ }
2214
+ var ourLink = (attr) => `<link rel="alternate" type="${MARKDOWN_MIME}" href="${attr}" ${HEAD_OWNED_ATTR}="${MARKDOWN_LINK_KEY}">`;
2215
+ function setMarkdownLink(bytes, charset, href) {
2216
+ if (charset.startsWith("utf-16")) return null;
2217
+ const buf = Buffer.from(bytes);
2218
+ const view = buf.toString("latin1");
2219
+ const attr = escapeAttr(href);
2220
+ let headOpenEnd = -1;
2221
+ let headClose = -1;
2222
+ let bodyOpen = -1;
2223
+ let preamble = 0;
2224
+ let marked;
2225
+ for (const t of scanHtml(view, HEAD_RAW_TEXT)) {
2226
+ if (t.type === "comment") {
2227
+ if (headOpenEnd === -1 && /^<!doctype/i.test(view.slice(t.start, t.start + 9))) preamble = t.end;
2228
+ continue;
2229
+ }
2230
+ if (t.type !== "tag") continue;
2231
+ if (t.closing) {
2232
+ if (t.name === "head") {
2233
+ headClose = t.start;
2234
+ break;
2235
+ }
2236
+ continue;
2237
+ }
2238
+ if (t.name === "html" && headOpenEnd === -1) preamble = Math.max(preamble, t.end);
2239
+ if (t.name === "head" && headOpenEnd === -1) headOpenEnd = t.end;
2240
+ else if (t.name === "body") {
2241
+ bodyOpen = t.start;
2242
+ break;
2243
+ } else if (t.name === "link" && isMarkdownAlternate(t.attrs)) {
2244
+ const owner = t.attrs.find((a) => a.name === HEAD_OWNED_ATTR)?.value;
2245
+ if (owner !== MARKDOWN_LINK_KEY) return { bytes: buf, action: "kept" };
2246
+ marked ??= t;
2247
+ }
2248
+ }
2249
+ const splice = (start, end, text, action) => ({
2250
+ bytes: Buffer.concat([buf.subarray(0, start), Buffer.from(text, "latin1"), buf.subarray(end)]),
2251
+ action
2252
+ });
2253
+ if (marked) {
2254
+ const h = marked.attrs.find((a) => a.name === "href");
2255
+ if (h && h.valueStart !== -1) {
2256
+ if (h.value === attr) return { bytes: buf, action: "unchanged" };
2257
+ return splice(h.valueStart, h.valueEnd, h.quote ? attr : `"${attr}"`, "updated");
2258
+ }
2259
+ return splice(marked.start, marked.end, ourLink(attr), "updated");
2260
+ }
2261
+ const at = headClose !== -1 ? headClose : headOpenEnd !== -1 ? headOpenEnd : bodyOpen !== -1 ? bodyOpen : preamble;
2262
+ return splice(at, at, ourLink(attr), "inserted");
2263
+ }
2264
+
2265
+ // src/vite/generate.ts
2266
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["assets", "node_modules"]);
2267
+ var SKIP_FILES = /* @__PURE__ */ new Set([SPA_FALLBACK_FILE, "404.html"]);
2268
+ function htmlFiles(outDir, warn) {
2269
+ const out = [];
2270
+ const walk = (dir, rel) => {
2271
+ for (const e of fs3.readdirSync(dir, { withFileTypes: true })) {
2272
+ if (e.name.startsWith(".")) continue;
2273
+ const r = rel ? `${rel}/${e.name}` : e.name;
2274
+ if (e.isSymbolicLink()) {
2275
+ if (e.name.endsWith(".html") || fs3.statSync(path4.join(dir, e.name), { throwIfNoEntry: false })?.isDirectory()) {
2276
+ warn(`${LOG_PREFIX} ${r} is a symbolic link; skipped`);
2277
+ }
2278
+ } else if (e.isDirectory()) {
2279
+ if (!(rel === "" && SKIP_DIRS.has(e.name))) walk(path4.join(dir, e.name), r);
2280
+ } else if (e.isFile() && e.name.endsWith(".html") && !SKIP_FILES.has(e.name)) {
2281
+ out.push(r);
2282
+ }
2283
+ }
2284
+ };
2285
+ walk(outDir, "");
2286
+ return out.sort(compareStrings);
2287
+ }
2288
+ var GENERATE_OPTION_KEYS = [
2289
+ "outDir",
2290
+ "siteUrl",
2291
+ "robots",
2292
+ "exclude",
2293
+ "sitemap",
2294
+ "markdown",
2295
+ "llms",
2296
+ "agentMap",
2297
+ "linkMarkdown",
2298
+ "overwrite",
2299
+ "logger"
2300
+ ];
2301
+ async function generateAgentFiles(options) {
2302
+ if (!options || typeof options.outDir !== "string" || options.outDir === "") {
2303
+ throw new Error(withPrefix("outDir is required"));
2304
+ }
2305
+ for (const key of ["linkMarkdown", "overwrite"]) {
2306
+ if (options[key] !== void 0 && typeof options[key] !== "boolean") throw new Error(withPrefix(`${key} must be a boolean`));
2307
+ }
2308
+ const resolved = resolveOptions({
2309
+ siteUrl: options.siteUrl,
2310
+ robots: options.robots,
2311
+ ...options.exclude !== void 0 ? { exclude: options.exclude } : {},
2312
+ ...options.sitemap !== void 0 ? { sitemap: options.sitemap } : {},
2313
+ ...options.markdown !== void 0 ? { markdown: options.markdown } : {},
2314
+ ...options.llms !== void 0 ? { llms: options.llms } : {},
2315
+ ...options.agentMap !== void 0 ? { agentMap: options.agentMap } : {},
2316
+ overlay: false
2317
+ });
2318
+ const { siteUrl, markdown } = resolved;
2319
+ const outDir = path4.resolve(options.outDir);
2320
+ if (!fs3.existsSync(outDir) || !fs3.statSync(outDir).isDirectory()) {
2321
+ throw new Error(`${LOG_PREFIX} ${outDir} is not a directory`);
2322
+ }
2323
+ const linkMarkdown = options.linkMarkdown ?? true;
2324
+ const base = options.logger ?? console;
2325
+ const warnings = [];
2326
+ const logger = {
2327
+ warn(msg) {
2328
+ warnings.push(msg);
2329
+ base.warn(msg);
2330
+ }
2331
+ };
2332
+ for (const w of resolved.warnings) logger.warn(w);
2333
+ const unknown = Object.keys(options).filter((k) => !GENERATE_OPTION_KEYS.includes(k));
2334
+ if (unknown.length > 0) {
2335
+ logger.warn(`${LOG_PREFIX} unknown option${unknown.length === 1 ? "" : "s"} ignored: ${unknown.join(", ")}`);
2336
+ }
2337
+ const fileByRoute = /* @__PURE__ */ new Map();
2338
+ for (const file of htmlFiles(outDir, (m) => logger.warn(m))) {
2339
+ const route = routeOfHtmlFile(file);
2340
+ if (!isSafeRoute(route)) {
2341
+ logger.warn(`${LOG_PREFIX} ${file} cannot be a page URL (its name has a backslash or similar); skipped`);
2342
+ continue;
2343
+ }
2344
+ if (route !== "/" && isExcluded(route, resolved.exclude)) continue;
2345
+ const other = fileByRoute.get(route);
2346
+ if (other !== void 0) {
2347
+ logger.warn(`${LOG_PREFIX} ${file} and ${other} are both ${shortRoute(route)}; using ${other}`);
2348
+ continue;
2349
+ }
2350
+ fileByRoute.set(route, file);
2351
+ }
2352
+ if (fileByRoute.size === 0) throw new Error(`${LOG_PREFIX} no .html pages found in ${outDir}`);
2353
+ const files = [...fileByRoute.values()];
2354
+ const flat = files.some((f) => f !== "index.html" && !f.endsWith("/index.html"));
2355
+ const dir = files.some((f) => f.endsWith("/index.html"));
2356
+ const fileLayout = flat && dir ? "mixed" : flat ? "flat" : "directory";
2357
+ const stager = createFileStager({ outDir, publicDir: "", warn: (m) => logger.warn(m) });
2358
+ const written = /* @__PURE__ */ new Set();
2359
+ const kept = [];
2360
+ const protectedFiles = /* @__PURE__ */ new Set([fileOf(ROBOTS_PATH), fileOf(SITEMAP_PATH), fileOf(LLMS_TXT_PATH), fileOf(LLMS_FULL_TXT_PATH)]);
2361
+ if (resolved.agentMap) protectedFiles.add(resolved.agentMap.fileName);
2362
+ const differs = (target, source) => fs3.existsSync(target) && !fs3.readFileSync(target).equals(typeof source === "string" ? Buffer.from(source) : source);
2363
+ const guarded = {
2364
+ ...stager,
2365
+ stage(fileName, source) {
2366
+ const file = fileName.replace(/^\/+/, "");
2367
+ const target = path4.join(outDir, file);
2368
+ if (!options.overwrite && protectedFiles.has(file) && differs(target, source)) {
2369
+ if (!kept.includes(file)) {
2370
+ kept.push(file);
2371
+ logger.warn(`${LOG_PREFIX} ${file} already exists with different content; kept it (use overwrite to replace it)`);
2372
+ }
2373
+ return { file, status: "skipped" };
2374
+ }
2375
+ const r = stager.stage(fileName, source);
2376
+ if (r.status !== "skipped") written.add(r.file);
2377
+ return r;
2378
+ }
2379
+ };
2380
+ const pages = [];
2381
+ const result = [];
2382
+ const mdByRoute = /* @__PURE__ */ new Map();
2383
+ const bodyFallback = [];
2384
+ const utf16 = [];
2385
+ const ownLinks = [];
2386
+ const routes = dropFileCollisions(
2387
+ [...fileByRoute].map(([route, file]) => ({ route, file })).sort(byRoute),
2388
+ (p) => markdown ? [routeMdFile(p.route)] : [],
2389
+ /* @__PURE__ */ new Map(),
2390
+ (m) => logger.warn(m)
2391
+ );
2392
+ for (const { route, file } of routes) {
2393
+ const bytes = fs3.readFileSync(path4.join(outDir, file));
2394
+ const { charset, text: html } = decodeHtml(bytes);
2395
+ const url = publicUrl(siteUrl, route);
2396
+ const { document, dispose } = parseHtml(html);
2397
+ let page;
2398
+ try {
2399
+ const actions = resolved.agentMap ? staticActions(document) : [];
2400
+ const info = analyzeDocument(document, {
2401
+ pageUrl: url,
2402
+ contentSelector: markdown ? markdown.contentSelector : "main",
2403
+ route
2404
+ });
2405
+ const inPage = { "agent-map": { actions } };
2406
+ page = { route, info, listed: !info.noindex, inPage };
2407
+ } catch (e) {
2408
+ throw prefixed(e, `reading ${file} failed`);
2409
+ } finally {
2410
+ dispose();
2411
+ }
2412
+ pages.push(page);
2413
+ result.push({ route, file, listed: page.listed });
2414
+ if (!markdown) continue;
2415
+ if (page.info.contentSource === "body") bodyFallback.push(route);
2416
+ const md = await pageMarkdown(markdown, { route, url, info: page.info, html });
2417
+ mdByRoute.set(route, md);
2418
+ const mdFile = routeMdFile(route);
2419
+ if (differs(path4.join(outDir, mdFile), md)) {
2420
+ logger.warn(`${LOG_PREFIX} ${mdFile} already exists with different content; replacing it with the markdown of ${shortRoute(route)}`);
2421
+ }
2422
+ guarded.stage(mdFile, md);
2423
+ if (linkMarkdown) {
2424
+ const edit = setMarkdownLink(bytes, charset, publicMdUrl(siteUrl, route));
2425
+ if (edit === null) utf16.push(file);
2426
+ else if (edit.action === "kept") ownLinks.push(shortRoute(route));
2427
+ else if (edit.action !== "unchanged") guarded.stage(file, edit.bytes);
2428
+ }
2429
+ }
2430
+ const listed = pages.filter((p) => p.listed);
2431
+ if (resolved.sitemap) guarded.stage(fileOf(SITEMAP_PATH), buildSitemap(siteUrl, listed.map((p) => p.route)));
2432
+ guarded.stage(fileOf(ROBOTS_PATH), buildRobots(resolved));
2433
+ const extensions = resolved.agentMap ? [agentMapExtension({ siteUrl, fileName: resolved.agentMap.fileName })] : [];
2434
+ await stageSiteFiles({ siteUrl, markdown, llms: resolved.llms, extensions, logger }, guarded, {
2435
+ pages,
2436
+ listed,
2437
+ mdByRoute
2438
+ });
2439
+ stager.flush();
2440
+ if (markdown && bodyFallback.length > 0) {
2441
+ logger.warn(
2442
+ `${LOG_PREFIX} no [${CONTENT_ATTR}] or "${markdown.contentSelector}" element on ${bodyFallback.join(", ")}; content was taken from <body> minus header, nav, footer, aside`
2443
+ );
2444
+ }
2445
+ if (ownLinks.length > 0) {
2446
+ base.info(`${LOG_PREFIX} kept the page's own markdown link on ${ownLinks.join(", ")}`);
2447
+ }
2448
+ if (utf16.length > 0) {
2449
+ logger.warn(`${LOG_PREFIX} ${utf16.join(", ")} ${utf16.length === 1 ? "is" : "are"} UTF-16; the markdown link was not added`);
2450
+ }
2451
+ for (const w of pageLengthWarnings(listed)) logger.warn(w);
2452
+ base.info(`${LOG_PREFIX} generated agent files for ${pages.length} pages (${listed.length} listed) in ${outDir}`);
2453
+ return {
2454
+ outDir,
2455
+ fileLayout,
2456
+ pages: result.sort(byRoute),
2457
+ written: [...written].sort(compareStrings),
2458
+ kept: kept.sort(compareStrings),
2459
+ warnings
2460
+ };
2461
+ }
2462
+
1982
2463
  // src/vite/overlay.ts
1983
2464
  import { existsSync as existsSync2 } from "fs";
1984
2465
  import { createRequire as createRequire2 } from "module";
1985
- import path4 from "path";
2466
+ import path5 from "path";
1986
2467
  import { fileURLToPath as fileURLToPath2 } from "url";
1987
2468
  var OVERLAY_VIRTUAL_ID = "virtual:web-for-agents/overlay";
1988
2469
  var RESOLVED_ID = "\0" + OVERLAY_VIRTUAL_ID;
1989
2470
  var PACKAGE_NAME = "@anubhav-choubey/web-for-agents";
1990
2471
  var OVERLAY_DEPS = ["turndown", "turndown-plugin-gfm"];
1991
2472
  function overlayDepsFor(root) {
1992
- const req = createRequire2(path4.join(root, "noop.js"));
2473
+ const req = createRequire2(path5.join(root, "noop.js"));
1993
2474
  return OVERLAY_DEPS.map((dep) => {
1994
2475
  try {
1995
2476
  req.resolve(dep);
@@ -2014,7 +2495,7 @@ function overlayPlugin(o, siteUrl) {
2014
2495
  name: "web-for-agents:overlay",
2015
2496
  apply: (_config, env) => env.command === "serve" && !process.env.VITEST,
2016
2497
  config(config) {
2017
- return { optimizeDeps: { include: overlayDepsFor(path4.resolve(config.root ?? process.cwd())) } };
2498
+ return { optimizeDeps: { include: overlayDepsFor(path5.resolve(config.root ?? process.cwd())) } };
2018
2499
  },
2019
2500
  configResolved(config) {
2020
2501
  base = config.base;
@@ -2045,5 +2526,6 @@ function webForAgents(options) {
2045
2526
  }
2046
2527
  export {
2047
2528
  AI_BOTS,
2529
+ generateAgentFiles,
2048
2530
  webForAgents
2049
2531
  };