@astrofoundry/grimoire 3.32.0 → 3.32.1

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.
@@ -775,6 +775,32 @@ async function convertSource(sourceName, urls, contentSelector, removeSelectors,
775
775
  return pages.filter((page) => page !== void 0);
776
776
  }
777
777
 
778
+ // src/tokens.ts
779
+ import { createHash } from "node:crypto";
780
+ var IDENTIFIER_PATTERN = /(?<![A-Za-z0-9._])(?:[A-Za-z][A-Za-z0-9]*(?:[_.-][A-Za-z0-9]+)+|[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)+|(?:[A-Z][a-z0-9]+){2,})(?![A-Za-z0-9])/g;
781
+ var MIN_TOKEN_LENGTH = 4;
782
+ var MAX_TOKEN_LENGTH = 80;
783
+ var MAX_TOKENS_PER_CHUNK = 100;
784
+ function normalizeForTokens(text) {
785
+ return text.replace(/\]\([^)]*\)/g, "]").replace(/https?:\/\/\S+/g, " ").replace(/\\([_*[\]()#`~-])/g, "$1");
786
+ }
787
+ function extractIdentifierTokens(text, limit = MAX_TOKENS_PER_CHUNK) {
788
+ const seen = /* @__PURE__ */ new Set();
789
+ for (const match of normalizeForTokens(text).matchAll(IDENTIFIER_PATTERN)) {
790
+ const token = match[0].toLowerCase();
791
+ if (token.length < MIN_TOKEN_LENGTH || token.length > MAX_TOKEN_LENGTH) continue;
792
+ seen.add(token);
793
+ if (seen.size >= limit) break;
794
+ }
795
+ return [...seen];
796
+ }
797
+ function extractQueryTokens(query) {
798
+ return extractIdentifierTokens(query, 5);
799
+ }
800
+ function contentHash(text) {
801
+ return createHash("sha256").update(text).digest("hex");
802
+ }
803
+
778
804
  // src/chunker.ts
779
805
  var MAX_TOKENS = 500;
780
806
  function estimateTokens(text) {
@@ -1025,6 +1051,11 @@ function chunkMarkdown(markdown, source, url, title) {
1025
1051
  usedIds.add(id);
1026
1052
  return id;
1027
1053
  }
1054
+ const slugCounts = /* @__PURE__ */ new Map();
1055
+ for (const section of sections) {
1056
+ const slug = section.heading ? slugifyHeading(section.heading) : "intro";
1057
+ slugCounts.set(slug, (slugCounts.get(slug) ?? 0) + 1);
1058
+ }
1028
1059
  for (const section of sections) {
1029
1060
  const headingLine = section.heading ? `${"#".repeat(section.level)} ${section.heading}
1030
1061
 
@@ -1032,7 +1063,8 @@ function chunkMarkdown(markdown, source, url, title) {
1032
1063
  const body = section.lines.join("\n").trim();
1033
1064
  const content = headingLine + body;
1034
1065
  if (!content.trim()) continue;
1035
- const headingSlug = section.heading ? slugifyHeading(section.heading) : "intro";
1066
+ const baseSlug = section.heading ? slugifyHeading(section.heading) : "intro";
1067
+ const headingSlug = (slugCounts.get(baseSlug) ?? 0) > 1 ? `${baseSlug}-${contentHash(content).slice(0, 8)}` : baseSlug;
1036
1068
  if (estimateTokens(content) <= MAX_TOKENS) {
1037
1069
  chunks.push({
1038
1070
  id: uniqueId(headingSlug),
@@ -1065,32 +1097,6 @@ function chunkMarkdown(markdown, source, url, title) {
1065
1097
  return chunks;
1066
1098
  }
1067
1099
 
1068
- // src/tokens.ts
1069
- import { createHash } from "node:crypto";
1070
- var IDENTIFIER_PATTERN = /(?<![A-Za-z0-9._])(?:[A-Za-z][A-Za-z0-9]*(?:[_.-][A-Za-z0-9]+)+|[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)+|(?:[A-Z][a-z0-9]+){2,})(?![A-Za-z0-9])/g;
1071
- var MIN_TOKEN_LENGTH = 4;
1072
- var MAX_TOKEN_LENGTH = 80;
1073
- var MAX_TOKENS_PER_CHUNK = 100;
1074
- function normalizeForTokens(text) {
1075
- return text.replace(/\]\([^)]*\)/g, "]").replace(/https?:\/\/\S+/g, " ").replace(/\\([_*[\]()#`~-])/g, "$1");
1076
- }
1077
- function extractIdentifierTokens(text, limit = MAX_TOKENS_PER_CHUNK) {
1078
- const seen = /* @__PURE__ */ new Set();
1079
- for (const match of normalizeForTokens(text).matchAll(IDENTIFIER_PATTERN)) {
1080
- const token = match[0].toLowerCase();
1081
- if (token.length < MIN_TOKEN_LENGTH || token.length > MAX_TOKEN_LENGTH) continue;
1082
- seen.add(token);
1083
- if (seen.size >= limit) break;
1084
- }
1085
- return [...seen];
1086
- }
1087
- function extractQueryTokens(query) {
1088
- return extractIdentifierTokens(query, 5);
1089
- }
1090
- function contentHash(text) {
1091
- return createHash("sha256").update(text).digest("hex");
1092
- }
1093
-
1094
1100
  // src/embedder.ts
1095
1101
  import { GoogleGenerativeAI } from "@google/generative-ai";
1096
1102
  var BATCH_SIZE = 50;
@@ -1376,6 +1382,52 @@ async function vectorSearch(queryEmbedding, limit, source) {
1376
1382
  });
1377
1383
  }
1378
1384
 
1385
+ // src/sync.ts
1386
+ var EMBED_WINDOW = 1e3;
1387
+ var SHRINK_GUARD_MIN_EXISTING = 200;
1388
+ var SHRINK_GUARD_MAX_DELETE_RATIO = 0.5;
1389
+ var GROWTH_GUARD_MAX_RATIO = 5;
1390
+ async function syncChunks(sourceName, allChunks, urlCount, version, allowShrink = false) {
1391
+ console.log(" Comparing with Firestore...");
1392
+ const existing = await getSourceChunkHashes(sourceName);
1393
+ const currentIds = new Set(allChunks.map((c) => c.id));
1394
+ const toDelete = [...existing.keys()].filter((id) => !currentIds.has(id));
1395
+ const toEmbed = allChunks.filter(
1396
+ (chunk) => existing.get(chunk.id) !== contentHash(buildEmbedText(chunk))
1397
+ );
1398
+ console.log(
1399
+ ` Sync: ${toEmbed.length} to embed, ${allChunks.length - toEmbed.length} unchanged, ${toDelete.length} to delete.`
1400
+ );
1401
+ if (!allowShrink && existing.size >= SHRINK_GUARD_MIN_EXISTING && toDelete.length > existing.size * SHRINK_GUARD_MAX_DELETE_RATIO) {
1402
+ throw new Error(
1403
+ `Refusing to delete ${toDelete.length} of ${existing.size} stored chunks for "${sourceName}": URL discovery likely found only part of the site. Verify the sitemap/nav/urls.json, then re-run with --allow-shrink if the change is expected, or --full to rebuild from scratch.`
1404
+ );
1405
+ }
1406
+ if (!allowShrink && existing.size >= SHRINK_GUARD_MIN_EXISTING && allChunks.length > existing.size * GROWTH_GUARD_MAX_RATIO) {
1407
+ throw new Error(
1408
+ `Refusing to grow "${sourceName}" from ${existing.size} to ${allChunks.length} chunks (>${GROWTH_GUARD_MAX_RATIO}x): URL discovery may have escaped the doc tree. Verify include_patterns/nav_selector, then re-run with --allow-shrink if the growth is real.`
1409
+ );
1410
+ }
1411
+ for (let i = 0; i < toEmbed.length; i += EMBED_WINDOW) {
1412
+ const window = toEmbed.slice(i, i + EMBED_WINDOW);
1413
+ const embeddings = await embedTexts(window.map((c) => buildEmbedText(c)), {
1414
+ onProgress: (done) => {
1415
+ console.log(` [${i + done}/${toEmbed.length}] embedded`);
1416
+ }
1417
+ });
1418
+ await storeChunks(window, embeddings, (cur) => {
1419
+ console.log(` [${i + cur}/${toEmbed.length}] stored`);
1420
+ });
1421
+ }
1422
+ if (toDelete.length > 0) {
1423
+ await deleteChunksByIds(toDelete, (cur, total) => {
1424
+ console.log(` [${cur}/${total}] deleted`);
1425
+ });
1426
+ }
1427
+ await updateSourceMeta(sourceName, allChunks.length, urlCount, version);
1428
+ console.log(` Done. ${allChunks.length} chunks live for "${sourceName}".`);
1429
+ }
1430
+
1379
1431
  // src/search.ts
1380
1432
  var DEFAULT_CANDIDATES = 50;
1381
1433
  var RRF_K = 60;
@@ -1413,8 +1465,7 @@ async function search(query, options = {}) {
1413
1465
  pool.set(result.id, {
1414
1466
  id: result.id,
1415
1467
  data: result.data,
1416
- fusedScore: 1 / (RRF_K + rank + 1),
1417
- similarity: 1 - result.distance
1468
+ fusedScore: 1 / (RRF_K + rank + 1)
1418
1469
  });
1419
1470
  });
1420
1471
  lexicalHits.forEach((hit, rank) => {
@@ -1427,8 +1478,7 @@ async function search(query, options = {}) {
1427
1478
  pool.set(hit.id, {
1428
1479
  id: hit.id,
1429
1480
  data: hit.data,
1430
- fusedScore: lexicalScore,
1431
- similarity: null
1481
+ fusedScore: lexicalScore
1432
1482
  });
1433
1483
  });
1434
1484
  const fused = [...pool.values()].sort((a, b) => b.fusedScore - a.fusedScore);
@@ -1439,10 +1489,10 @@ async function search(query, options = {}) {
1439
1489
  const reranked = await rerank(query, documents, topN);
1440
1490
  return reranked.map((r) => toSearchResult(rerankPool[r.index], r.relevance_score));
1441
1491
  }
1442
- return fused.slice(0, topN).map((candidate) => {
1443
- const similarity = candidate.similarity ?? 0;
1444
- return toSearchResult(candidate, Math.max(0, (1 + similarity) / 2));
1445
- });
1492
+ const maxFused = fused[0].fusedScore;
1493
+ return fused.slice(0, topN).map(
1494
+ (candidate) => toSearchResult(candidate, candidate.fusedScore / maxFused)
1495
+ );
1446
1496
  }
1447
1497
 
1448
1498
  // src/apikey.ts
@@ -1756,50 +1806,6 @@ Source "${name}" added to config/sources.yaml`);
1756
1806
  await browser.close();
1757
1807
  }
1758
1808
  }
1759
- var EMBED_WINDOW = 1e3;
1760
- var SHRINK_GUARD_MIN_EXISTING = 200;
1761
- var SHRINK_GUARD_MAX_DELETE_RATIO = 0.5;
1762
- var GROWTH_GUARD_MAX_RATIO = 5;
1763
- async function syncChunks(sourceName, allChunks, urlCount, version, allowShrink = false) {
1764
- console.log(" Comparing with Firestore...");
1765
- const existing = await getSourceChunkHashes(sourceName);
1766
- const currentIds = new Set(allChunks.map((c) => c.id));
1767
- const toDelete = [...existing.keys()].filter((id) => !currentIds.has(id));
1768
- const toEmbed = allChunks.filter(
1769
- (chunk) => existing.get(chunk.id) !== contentHash(buildEmbedText(chunk))
1770
- );
1771
- console.log(
1772
- ` Sync: ${toEmbed.length} to embed, ${allChunks.length - toEmbed.length} unchanged, ${toDelete.length} to delete.`
1773
- );
1774
- if (!allowShrink && existing.size >= SHRINK_GUARD_MIN_EXISTING && toDelete.length > existing.size * SHRINK_GUARD_MAX_DELETE_RATIO) {
1775
- throw new Error(
1776
- `Refusing to delete ${toDelete.length} of ${existing.size} stored chunks for "${sourceName}": URL discovery likely found only part of the site. Verify the sitemap/nav/urls.json, then re-run with --allow-shrink if the change is expected, or --full to rebuild from scratch.`
1777
- );
1778
- }
1779
- if (!allowShrink && existing.size >= SHRINK_GUARD_MIN_EXISTING && allChunks.length > existing.size * GROWTH_GUARD_MAX_RATIO) {
1780
- throw new Error(
1781
- `Refusing to grow "${sourceName}" from ${existing.size} to ${allChunks.length} chunks (>${GROWTH_GUARD_MAX_RATIO}x): URL discovery may have escaped the doc tree. Verify include_patterns/nav_selector, then re-run with --allow-shrink if the growth is real.`
1782
- );
1783
- }
1784
- for (let i = 0; i < toEmbed.length; i += EMBED_WINDOW) {
1785
- const window = toEmbed.slice(i, i + EMBED_WINDOW);
1786
- const embeddings = await embedTexts(window.map((c) => buildEmbedText(c)), {
1787
- onProgress: (done) => {
1788
- console.log(` [${i + done}/${toEmbed.length}] embedded`);
1789
- }
1790
- });
1791
- await storeChunks(window, embeddings, (cur) => {
1792
- console.log(` [${i + cur}/${toEmbed.length}] stored`);
1793
- });
1794
- }
1795
- if (toDelete.length > 0) {
1796
- await deleteChunksByIds(toDelete, (cur, total) => {
1797
- console.log(` [${cur}/${total}] deleted`);
1798
- });
1799
- }
1800
- await updateSourceMeta(sourceName, allChunks.length, urlCount, version);
1801
- console.log(` Done. ${allChunks.length} chunks live for "${sourceName}".`);
1802
- }
1803
1809
  function applyExcludePatterns(items, getUrl, excludePatterns, label) {
1804
1810
  if (!excludePatterns || excludePatterns.length === 0) return items;
1805
1811
  const kept = items.filter(
@@ -2171,4 +2177,4 @@ var ADMIN_COMMANDS = {
2171
2177
  export {
2172
2178
  ADMIN_COMMANDS
2173
2179
  };
2174
- //# sourceMappingURL=admin-4C3QNVQE.js.map
2180
+ //# sourceMappingURL=admin-MA5SI5CH.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../node_modules/.pnpm/@joplin+turndown-plugin-gfm@1.0.67/node_modules/@joplin/turndown-plugin-gfm/lib/turndown-plugin-gfm.cjs.js", "../src/admin.ts", "../src/config.ts", "../src/scraper.ts", "../src/slug.ts", "../src/converter.ts", "../src/tokens.ts", "../src/chunker.ts", "../src/embedder.ts", "../src/store.ts", "../src/sync.ts", "../src/search.ts", "../src/apikey.ts", "../src/llms-ingest.ts"],
4
+ "sourcesContent": ["'use strict';\n\nObject.defineProperty(exports, '__esModule', { value: true });\n\nvar highlightRegExp = /highlight-(?:text|source)-([a-z0-9]+)/;\n\nfunction highlightedCodeBlock (turndownService) {\n turndownService.addRule('highlightedCodeBlock', {\n filter: function (node) {\n var firstChild = node.firstChild;\n return (\n node.nodeName === 'DIV' &&\n highlightRegExp.test(node.className) &&\n firstChild &&\n firstChild.nodeName === 'PRE'\n )\n },\n replacement: function (content, node, options) {\n var className = node.className || '';\n var language = (className.match(highlightRegExp) || [null, ''])[1];\n\n return (\n '\\n\\n' + options.fence + language + '\\n' +\n node.firstChild.textContent +\n '\\n' + options.fence + '\\n\\n'\n )\n }\n });\n}\n\nfunction strikethrough (turndownService) {\n turndownService.addRule('strikethrough', {\n filter: ['del', 's', 'strike'],\n replacement: function (content) {\n return '~~' + content + '~~'\n }\n });\n}\n\nvar indexOf = Array.prototype.indexOf;\nvar every = Array.prototype.every;\nvar rules = {};\nvar alignMap = { left: ':---', right: '---:', center: ':---:' };\n\nlet isCodeBlock_ = null;\nlet options_ = null;\n\n// We need to cache the result of tableShouldBeSkipped() as it is expensive.\n// Caching it means we went from about 9000 ms for rendering down to 90 ms.\n// Fixes https://github.com/laurent22/joplin/issues/6736\nconst tableShouldBeSkippedCache_ = new WeakMap();\n\nfunction getAlignment(node) {\n return node ? (node.getAttribute('align') || node.style.textAlign || '').toLowerCase() : '';\n}\n\nfunction getBorder(alignment) {\n return alignment ? alignMap[alignment] : '---';\n}\n\nfunction getColumnAlignment(table, columnIndex) {\n var votes = {\n left: 0,\n right: 0,\n center: 0,\n '': 0,\n };\n\n var align = '';\n\n for (var i = 0; i < table.rows.length; ++i) {\n var row = table.rows[i];\n if (columnIndex < row.childNodes.length) {\n var cellAlignment = getAlignment(row.childNodes[columnIndex]);\n ++votes[cellAlignment];\n\n if (votes[cellAlignment] > votes[align]) {\n align = cellAlignment;\n }\n }\n }\n\n return align;\n}\n\nrules.tableCell = {\n filter: ['th', 'td'],\n replacement: function (content, node) {\n if (tableShouldBeSkipped(nodeParentTable(node))) return content;\n return cell(content, node)\n }\n};\n\nrules.tableRow = {\n filter: 'tr',\n replacement: function (content, node) {\n const parentTable = nodeParentTable(node);\n if (tableShouldBeSkipped(parentTable)) return content;\n\n var borderCells = '';\n\n if (isHeadingRow(node)) {\n const colCount = tableColCount(parentTable);\n for (var i = 0; i < colCount; i++) {\n const childNode = i < node.childNodes.length ? node.childNodes[i] : null;\n var border = getBorder(getColumnAlignment(parentTable, i));\n borderCells += cell(border, childNode, i);\n }\n }\n return '\\n' + content + (borderCells ? '\\n' + borderCells : '')\n }\n};\n\nrules.table = {\n filter: function (node, options) {\n return node.nodeName === 'TABLE';\n },\n\n replacement: function (content, node) {\n // Only convert tables that can result in valid Markdown\n // Other tables are kept as HTML using `keep` (see below).\n if (tableShouldBeHtml(node, options_)) {\n let html = node.outerHTML;\n let divParent = nodeParentDiv(node);\n // Make table in HTML format horizontally scrollable by give table a div parent, so the width of the table is limited to the screen width.\n\t // see https://github.com/laurent22/joplin/pull/10161\n // test cases:\n // packages/app-cli/tests/html_to_md/preserve_nested_tables.html\n // packages/app-cli/tests/html_to_md/table_with_blockquote.html\n // packages/app-cli/tests/html_to_md/table_with_code_1.html\n // packages/app-cli/tests/html_to_md/table_with_code_2.html\n // packages/app-cli/tests/html_to_md/table_with_code_3.html\n // packages/app-cli/tests/html_to_md/table_with_heading.html\n // packages/app-cli/tests/html_to_md/table_with_hr.html\n // packages/app-cli/tests/html_to_md/table_with_list.html\n if (divParent === null || !divParent.classList.contains('joplin-table-wrapper')){\n return `\\n\\n<div class=\"joplin-table-wrapper\">${html}</div>\\n\\n`;\n } else {\n return html\n }\n } else {\n if (tableShouldBeSkipped(node)) return content;\n\n // Ensure there are no blank lines\n content = content.replace(/\\n+/g, '\\n');\n\n // If table has no heading, add an empty one so as to get a valid Markdown table\n var secondLine = content.trim().split('\\n');\n if (secondLine.length >= 2) secondLine = secondLine[1];\n var secondLineIsDivider = /\\| :?---/.test(secondLine);\n\n var columnCount = tableColCount(node);\n var emptyHeader = '';\n if (columnCount && !secondLineIsDivider) {\n emptyHeader = '|' + ' |'.repeat(columnCount) + '\\n' + '|';\n for (var columnIndex = 0; columnIndex < columnCount; ++columnIndex) {\n emptyHeader += ' ' + getBorder(getColumnAlignment(node, columnIndex)) + ' |';\n }\n }\n\n const captionNode = node.querySelector ? node.querySelector('caption') : node.caption;\n const captionContent = captionNode ? captionNode.textContent || '' : '';\n const caption = captionContent ? `${captionContent}\\n\\n` : '';\n const tableContent = `${emptyHeader}${content}`.trimStart();\n return `\\n\\n${caption}${tableContent}\\n\\n`;\n }\n }\n};\n\nrules.tableCaption = {\n filter: ['caption'],\n replacement: () => '',\n};\n\nrules.tableColgroup = {\n filter: ['colgroup', 'col'],\n replacement: () => '',\n};\n\nrules.tableSection = {\n filter: ['thead', 'tbody', 'tfoot'],\n replacement: function (content) {\n return content\n }\n};\n\n// A tr is a heading row if:\n// - the parent is a THEAD\n// - or if its the first child of the TABLE or the first TBODY (possibly\n// following a blank THEAD)\n// - and every cell is a TH\nfunction isHeadingRow (tr) {\n var parentNode = tr.parentNode;\n return (\n parentNode.nodeName === 'THEAD' ||\n (\n parentNode.firstChild === tr &&\n (parentNode.nodeName === 'TABLE' || isFirstTbody(parentNode)) &&\n every.call(tr.childNodes, function (n) { return n.nodeName === 'TH' })\n )\n )\n}\n\nfunction isFirstTbody (element) {\n var previousSibling = element.previousSibling;\n return (\n element.nodeName === 'TBODY' && (\n !previousSibling ||\n (\n previousSibling.nodeName === 'THEAD' &&\n /^\\s*$/i.test(previousSibling.textContent)\n )\n )\n )\n}\n\nfunction cell (content, node = null, index = null) {\n if (index === null) index = indexOf.call(node.parentNode.childNodes, node);\n var prefix = ' ';\n if (index === 0) prefix = '| ';\n let filteredContent = content.trim().replace(/\\n\\r/g, '<br>').replace(/\\n/g, \"<br>\");\n filteredContent = filteredContent.replace(/\\|+/g, '\\\\|');\n while (filteredContent.length < 3) filteredContent += ' ';\n if (node) filteredContent = handleColSpan(filteredContent, node, ' ');\n return prefix + filteredContent + ' |'\n}\n\nfunction nodeContainsTable(node) {\n if (!node.childNodes) return false;\n\n for (let i = 0; i < node.childNodes.length; i++) {\n const child = node.childNodes[i];\n if (child.nodeName === 'TABLE') return true;\n if (nodeContainsTable(child)) return true;\n }\n return false;\n}\n\nconst nodeContains = (node, types) => {\n if (!node.childNodes) return false;\n\n for (let i = 0; i < node.childNodes.length; i++) {\n const child = node.childNodes[i];\n if (types === 'code' && isCodeBlock_ && isCodeBlock_(child)) return true;\n if (types.includes(child.nodeName)) return true;\n if (nodeContains(child, types)) return true;\n }\n\n return false;\n};\n\n// Style properties that count as user customization.\n// Excludes TinyMCE/Joplin defaults:\n// - border-collapse: set by default on all tables\n// - width: set on every cell by TinyMCE\n// - text-align: converted to Markdown alignment (:---, :---:, ---:)\n// - height: false positives from TinyMCE defaults\nconst customStyleProperties = [\n 'background-color', 'background',\n 'border-color', 'border',\n 'border-top', 'border-right', 'border-bottom', 'border-left',\n 'border-style', 'border-width',\n 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',\n 'float', 'margin-left', 'margin-right',\n];\n\n// HTML attributes TinyMCE may set instead of CSS.\nconst customAttributeNames = [\n 'bgcolor',\n 'bordercolor',\n 'background',\n];\n\nconst nodeHasCustomStyle = (node) => {\n if (!node || !node.getAttribute) return false;\n const styleAttr = node.getAttribute('style');\n if (!styleAttr) return false;\n // Extract property names from the raw style string\n const properties = styleAttr.split(';')\n .map(s => s.split(':')[0].trim().toLowerCase())\n .filter(s => s.length > 0);\n for (let i = 0; i < properties.length; i++) {\n if (customStyleProperties.includes(properties[i])) return true;\n }\n return false;\n};\n\nconst hasNonDefaultSpacingAttribute = (node, name) => {\n if (!node || !node.getAttribute) return false;\n const value = node.getAttribute(name);\n if (value === null) return false;\n const normalisedValue = `${value}`.trim().toLowerCase();\n if (!normalisedValue) return false;\n if (normalisedValue === '0' || normalisedValue === '0px') return false;\n return true;\n};\n\nconst nodeHasCustomAttributes = (node) => {\n if (!node || !node.getAttribute) return false;\n\n for (let i = 0; i < customAttributeNames.length; i++) {\n const value = node.getAttribute(customAttributeNames[i]);\n if (value !== null && `${value}`.trim() !== '') return true;\n }\n\n if (node.nodeName === 'TABLE') {\n if (hasNonDefaultSpacingAttribute(node, 'cellpadding')) return true;\n if (hasNonDefaultSpacingAttribute(node, 'cellspacing')) return true;\n }\n\n return false;\n};\n\nconst nodeHasCustomFormatting = (node) => {\n return nodeHasCustomStyle(node) || nodeHasCustomAttributes(node);\n};\n\n// Returns true if the table or any of its rows/cells have custom formatting.\nconst tableHasCustomStyles = (tableNode) => {\n if (nodeHasCustomFormatting(tableNode)) return true;\n\n const rows = tableNode.rows;\n if (!rows) return false;\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n if (nodeHasCustomFormatting(row)) return true;\n for (let j = 0; j < row.childNodes.length; j++) {\n const cell = row.childNodes[j];\n if ((cell.nodeName === 'TD' || cell.nodeName === 'TH') && nodeHasCustomFormatting(cell)) {\n return true;\n }\n }\n }\n\n return false;\n};\n\nconst tableShouldBeHtml = (tableNode, options) => {\n const possibleTags = [\n 'UL',\n 'OL',\n 'H1',\n 'H2',\n 'H3',\n 'H4',\n 'H5',\n 'H6',\n 'HR',\n 'BLOCKQUOTE',\n ];\n\n // In general we should leave as HTML tables that include other tables. The\n // exception is with the Web Clipper when we import a web page with a layout\n // that's made of HTML tables. In that case we have this logic of removing the\n // outer table and keeping only the inner ones. For the Rich Text editor\n // however we always want to keep nested tables.\n if (options.preserveNestedTables) possibleTags.push('TABLE');\n\n return nodeContains(tableNode, 'code') ||\n nodeContains(tableNode, possibleTags) ||\n (options.preserveTableStyles && tableHasCustomStyles(tableNode));\n};\n\n// Various conditions under which a table should be skipped - i.e. each cell\n// will be rendered one after the other as if they were paragraphs.\nfunction tableShouldBeSkipped(tableNode) {\n const cached = tableShouldBeSkippedCache_.get(tableNode);\n if (cached !== undefined) return cached;\n\n const result = tableShouldBeSkipped_(tableNode);\n\n tableShouldBeSkippedCache_.set(tableNode, result);\n return result;\n}\n\nfunction tableShouldBeSkipped_(tableNode) {\n if (!tableNode) return true;\n if (!tableNode.rows) return true;\n if (tableNode.rows.length === 1 && tableNode.rows[0].childNodes.length <= 1) return true; // Table with only one cell\n if (nodeContainsTable(tableNode)) return true;\n return false;\n}\n\nfunction nodeParentDiv(node) {\n let parent = node.parentNode;\n while (parent.nodeName !== 'DIV') {\n parent = parent.parentNode;\n if (!parent) return null;\n }\n return parent;\n}\n\nfunction nodeParentTable(node) {\n let parent = node.parentNode;\n while (parent.nodeName !== 'TABLE') {\n parent = parent.parentNode;\n if (!parent) return null;\n }\n return parent;\n}\n\nfunction handleColSpan(content, node, emptyChar) {\n const colspan = node.getAttribute('colspan') || 1;\n for (let i = 1; i < colspan; i++) {\n content += ' | ' + emptyChar.repeat(3);\n }\n return content\n}\n\nfunction tableColCount(node) {\n let maxColCount = 0;\n for (let i = 0; i < node.rows.length; i++) {\n const row = node.rows[i];\n const colCount = row.childNodes.length;\n if (colCount > maxColCount) maxColCount = colCount;\n }\n return maxColCount\n}\n\nfunction tables (turndownService) {\n isCodeBlock_ = turndownService.isCodeBlock;\n options_ = turndownService.options;\n\n turndownService.keep(function (node) {\n if (node.nodeName === 'TABLE' && tableShouldBeHtml(node, turndownService.options)) return true;\n return false;\n });\n for (var key in rules) turndownService.addRule(key, rules[key]);\n}\n\nfunction taskListItems (turndownService) {\n turndownService.addRule('taskListItems', {\n filter: function (node) {\n const parent = node.parentNode;\n const grandparent = parent.parentNode;\n const grandparentIsListItem = !!grandparent && grandparent.nodeName === 'LI';\n return (node.type === 'checkbox' || node.getAttribute('role') === 'checkbox') && (\n parent.nodeName === 'LI'\n // Handles the case where the label contains the checkbox. For example,\n // <label><input ...> ...label text...</label>\n || (parent.nodeName === 'LABEL' && grandparentIsListItem)\n // Handles the case where the input is contained within a <span>\n // <li><span><input ...></span></li>\n || (parent.nodeName === 'SPAN' && grandparentIsListItem)\n )\n },\n replacement: function (content, node) {\n const checked = node.nodeName === 'INPUT' ? node.checked : node.getAttribute('aria-checked') === 'true';\n return (checked ? '[x]' : '[ ]') + ' '\n }\n });\n}\n\nfunction gfm (turndownService) {\n turndownService.use([\n highlightedCodeBlock,\n strikethrough,\n tables,\n taskListItems\n ]);\n}\n\nexports.gfm = gfm;\nexports.highlightedCodeBlock = highlightedCodeBlock;\nexports.strikethrough = strikethrough;\nexports.tables = tables;\nexports.taskListItems = taskListItems;\n", "import { parseArgs } from \"node:util\";\nimport { readFile, writeFile, readdir, rm } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { stringify } from \"yaml\";\nimport { bold, cyan, yellow } from \"./format.js\";\nimport { loadConfig, type SourceConfig } from \"./config.js\";\nimport { scrapeSource, createBrowser } from \"./scraper.js\";\nimport { slugifyUrl } from \"./slug.js\";\nimport { convertSource } from \"./converter.js\";\nimport { chunkMarkdown } from \"./chunker.js\";\nimport { syncChunks } from \"./sync.js\";\nimport {\n purgeSource,\n getAllSourcesMeta,\n getSourceMeta,\n deleteSourceMeta,\n} from \"./store.js\";\nimport { search } from \"./search.js\";\nimport { cmdApiKey } from \"./apikey.js\";\nimport { ingestLlmsFull } from \"./llms-ingest.js\";\n\nconst PROJECT_ROOT = resolve(import.meta.dirname, \"..\");\nconst CONFIG_PATH = join(PROJECT_ROOT, \"config\", \"sources.yaml\");\nconst DATA_DIR = join(PROJECT_ROOT, \"data\");\n\nfunction prompt(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {\n return new Promise((resolve) => rl.question(question, resolve));\n}\n\nasync function cmdAdd(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n url: { type: \"string\" },\n },\n allowPositionals: true,\n });\n\n const name = args.positionals[0];\n const url = args.values.url;\n\n if (!name || !url) {\n console.error(\"Usage: grimoire add <name> --url <start_url>\");\n process.exit(1);\n }\n\n console.log(\"Scanning page...\\n\");\n\n const browser = await createBrowser();\n const context = await browser.newContext();\n const page = await context.newPage();\n\n try {\n await page.goto(url, { waitUntil: \"domcontentloaded\" });\n\n const navCandidates = await page.evaluate(() => {\n const selectors = [\"nav\", \"[role='navigation']\"];\n const results: { selector: string; label: string; linkCount: number }[] = [];\n const seen = new Set<Element>();\n\n for (const sel of selectors) {\n for (const el of document.querySelectorAll(sel)) {\n if (seen.has(el)) continue;\n seen.add(el);\n const links = el.querySelectorAll(\"a[href]\");\n const label =\n el.getAttribute(\"aria-label\") ||\n el.getAttribute(\"class\") ||\n el.tagName.toLowerCase();\n results.push({\n selector: sel,\n label,\n linkCount: links.length,\n });\n }\n }\n\n return results.sort((a, b) => b.linkCount - a.linkCount);\n });\n\n if (navCandidates.length === 0) {\n console.error(\"No navigation elements found on this page.\");\n process.exit(1);\n }\n\n console.log(\"Navigation candidates:\");\n for (let i = 0; i < navCandidates.length; i++) {\n const c = navCandidates[i];\n console.log(` [${i + 1}] ${c.selector} (${c.label}) \u2014 ${c.linkCount} links`);\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n const navChoice = await prompt(rl, \"\\nSelect navigation: \");\n const navIndex = parseInt(navChoice, 10) - 1;\n if (isNaN(navIndex) || navIndex < 0 || navIndex >= navCandidates.length) {\n console.error(\"Invalid selection.\");\n rl.close();\n process.exit(1);\n }\n\n const selectedNav = navCandidates[navIndex];\n\n const parsedUrl = new URL(url);\n const defaultPattern = parsedUrl.pathname.replace(/\\/$/, \"\");\n\n const allLinks = await page.$$eval(\n `${selectedNav.selector} a[href]`,\n (links, pattern) => {\n return [...new Set(\n (links as HTMLAnchorElement[])\n .map((a) => a.href)\n .filter((h) => h.startsWith(\"http\") && !h.includes(\"?hl=\") && !h.endsWith(\"#\") && h.includes(pattern)),\n )];\n },\n defaultPattern,\n );\n\n console.log(`\\nFound ${allLinks.length} links matching ${defaultPattern}`);\n\n const patternInput = await prompt(rl, `Include pattern [default: ${defaultPattern}]: `);\n const includePattern = patternInput.trim() || defaultPattern;\n\n const excludeInput = await prompt(rl, \"Exclude patterns (comma-separated, optional): \");\n const excludePatterns = excludeInput.trim()\n ? excludeInput.split(\",\").map((p) => p.trim())\n : undefined;\n\n rl.close();\n\n const contentSelector = await page.evaluate(() => {\n if (document.querySelector(\"article\")) return \"article\";\n if (document.querySelector(\"main\")) return \"main\";\n return \"body\";\n });\n\n const removeSelectors = await page.evaluate(() => {\n const candidates = [\n { selector: \"nav\", label: \"nav\" },\n { selector: \"footer\", label: \"footer\" },\n { selector: \"[role='complementary']\", label: \"[role='complementary']\" },\n { selector: \"[role='banner']\", label: \"[role='banner']\" },\n { selector: \".breadcrumbs, .breadcrumb\", label: \".breadcrumbs\" },\n { selector: \".pagination-nav, .pagination\", label: \".pagination-nav\" },\n ];\n return candidates\n .filter((c) => document.querySelector(c.selector) !== null)\n .map((c) => c.label);\n });\n\n if (removeSelectors.length > 0) {\n console.log(`\\nDetected removable elements: ${removeSelectors.join(\", \")}`);\n }\n\n const parsedUrlForSitemap = new URL(url);\n let sitemapUrl: string | undefined;\n try {\n const sitemapCheck = await page.goto(`${parsedUrlForSitemap.origin}/sitemap.xml`, { waitUntil: \"domcontentloaded\", timeout: 10000 });\n if (sitemapCheck && sitemapCheck.status() === 200) {\n const body = await page.textContent(\"body\");\n if (body && (body.includes(\"<urlset\") || body.includes(\"<sitemapindex\"))) {\n sitemapUrl = `${parsedUrlForSitemap.origin}/sitemap.xml`;\n console.log(`\\nSitemap found: ${sitemapUrl}`);\n }\n }\n } catch {\n // No sitemap available\n }\n\n const source: SourceConfig = {\n name: name.replace(/-/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase()),\n start_url: url,\n ...(sitemapUrl ? { sitemap_url: sitemapUrl } : {}),\n nav_selector: selectedNav.selector,\n content_selector: contentSelector,\n include_patterns: [includePattern],\n ...(excludePatterns ? { exclude_patterns: excludePatterns } : {}),\n ...(removeSelectors.length > 0 ? { remove_selectors: removeSelectors } : {}),\n };\n\n let existingContent = \"\";\n try {\n existingContent = await readFile(CONFIG_PATH, \"utf-8\");\n } catch {\n existingContent = \"sources:\\n\";\n }\n\n const newEntry = stringify({ [name]: source }, { indent: 2 });\n const indented = newEntry\n .split(\"\\n\")\n .map((line) => (line.trim() ? ` ${line}` : \"\"))\n .join(\"\\n\");\n\n await writeFile(CONFIG_PATH, existingContent.trimEnd() + \"\\n\" + indented, \"utf-8\");\n\n console.log(`\\nSource \"${name}\" added to config/sources.yaml`);\n console.log(`Run \"grimoire refresh ${name}\" to start scraping.`);\n } finally {\n await browser.close();\n }\n}\n\ninterface RefreshPage {\n url: string;\n title: string;\n markdown: string;\n}\n\n// Cached urls.json/markdown reflect discovery at scrape time; applying\n// exclude_patterns here lets a config change take effect on the next\n// cached-input refresh without a full re-scrape.\nfunction applyExcludePatterns<T>(\n items: T[],\n getUrl: (item: T) => string,\n excludePatterns: string[] | undefined,\n label: string,\n): T[] {\n if (!excludePatterns || excludePatterns.length === 0) return items;\n const kept = items.filter(\n (item) => !excludePatterns.some((pattern) => getUrl(item).includes(pattern)),\n );\n if (kept.length < items.length) {\n console.log(` Excluded ${items.length - kept.length} ${label} via exclude_patterns.`);\n }\n return kept;\n}\n\nasync function readCachedMarkdownPages(mdDir: string): Promise<RefreshPage[]> {\n const mdFiles = await readdir(mdDir).catch(() => [] as string[]);\n const pages: RefreshPage[] = [];\n for (const f of mdFiles.filter((f) => f.endsWith(\".md\"))) {\n const content = await readFile(join(mdDir, f), \"utf-8\");\n const urlMatch = content.match(/^url: \"(.+)\"$/m);\n const titleMatch = content.match(/^title: \"(.+)\"$/m);\n if (!urlMatch) {\n console.warn(` WARNING: ${f} has no url in frontmatter, skipping page.`);\n continue;\n }\n pages.push({\n markdown: content,\n url: urlMatch[1],\n title: titleMatch?.[1] ?? \"Untitled\",\n });\n }\n return pages;\n}\n\nasync function recoverUrlsFromHtml(rawDir: string): Promise<string[]> {\n const urlsJsonPath = join(rawDir, \"urls.json\");\n try {\n return JSON.parse(await readFile(urlsJsonPath, \"utf-8\")) as string[];\n } catch {\n const rawFiles = await readdir(rawDir);\n const urls: string[] = [];\n let skipped = 0;\n for (const f of rawFiles.filter((f) => f.endsWith(\".html\"))) {\n const fileSlug = f.replace(/\\.html$/, \"\");\n const html = await readFile(join(rawDir, f), \"utf-8\");\n const match = html.match(/<link[^>]+rel=\"canonical\"[^>]+href=\"([^\"]+)\"/);\n if (match && slugifyUrl(match[1]) === fileSlug) { urls.push(match[1]); continue; }\n const ogMatch = html.match(/<meta[^>]+property=\"og:url\"[^>]+content=\"([^\"]+)\"/);\n if (ogMatch && slugifyUrl(ogMatch[1]) === fileSlug) { urls.push(ogMatch[1]); continue; }\n console.warn(` WARNING: cannot recover URL for ${f}, skipping page.`);\n skipped++;\n }\n if (skipped > 0) {\n console.warn(` Skipped ${skipped} pages with unrecoverable URLs. Provide urls.json to include them.`);\n }\n return urls;\n }\n}\n\nasync function cmdRefresh(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n full: { type: \"boolean\", default: false },\n all: { type: \"boolean\", default: false },\n concurrency: { type: \"string\" },\n limit: { type: \"string\" },\n \"from-html\": { type: \"boolean\", default: false },\n \"from-markdown\": { type: \"boolean\", default: false },\n \"skip-store\": { type: \"boolean\", default: false },\n \"allow-shrink\": { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n });\n\n const config = await loadConfig(CONFIG_PATH);\n const sourcesToRefresh = args.values.all\n ? Object.keys(config.sources)\n : [args.positionals[0]];\n\n if (!args.values.all && !sourcesToRefresh[0]) {\n console.error(\"Usage: grimoire refresh <source> [--full] [--from-html] [--from-markdown] [--skip-store] [--allow-shrink] [--limit <n>] [--concurrency <n>]\");\n process.exit(1);\n }\n\n const concurrencyOverride = args.values.concurrency ? parseInt(args.values.concurrency, 10) : undefined;\n const urlLimit = args.values.limit ? parseInt(args.values.limit, 10) : undefined;\n\n for (const sourceName of sourcesToRefresh) {\n const source = config.sources[sourceName];\n if (!source) {\n console.error(`Source \"${sourceName}\" not found in config.`);\n process.exit(1);\n }\n\n if (concurrencyOverride) {\n source.concurrency = concurrencyOverride;\n }\n\n const rawDir = join(DATA_DIR, \"raw\", sourceName);\n const mdDir = join(DATA_DIR, \"markdown\", sourceName);\n\n console.log(`\\nRefreshing \"${sourceName}\"...`);\n\n if (args.values.full) {\n console.log(\" Purging existing chunks...\");\n const deleted = await purgeSource(sourceName);\n console.log(` Deleted ${deleted} chunks.`);\n await rm(rawDir, { recursive: true, force: true });\n await rm(mdDir, { recursive: true, force: true });\n }\n\n let pages: RefreshPage[];\n\n if (args.values[\"from-markdown\"]) {\n console.log(\" Reading cached markdown...\");\n pages = await readCachedMarkdownPages(mdDir);\n if (pages.length === 0) {\n console.error(\" No cached markdown found. Run with --from-html first.\");\n process.exit(1);\n }\n pages = applyExcludePatterns(pages, (p) => p.url, source.exclude_patterns, \"cached pages\");\n console.log(` Found ${pages.length} cached pages.`);\n } else if (source.llms_full_url && !args.values[\"from-html\"]) {\n console.log(` Fetching llms-full.txt from ${source.llms_full_url}...`);\n pages = await ingestLlmsFull(\n source.llms_full_url,\n sourceName,\n source.start_url,\n DATA_DIR,\n (cur, total) => {\n console.log(` [${cur}/${total}] pages processed`);\n },\n );\n console.log(` Extracted ${pages.length} pages.`);\n } else {\n let urls: string[];\n if (args.values[\"from-html\"]) {\n console.log(\" Reading URLs from cached HTML...\");\n urls = await recoverUrlsFromHtml(rawDir);\n urls = applyExcludePatterns(urls, (u) => u, source.exclude_patterns, \"cached URLs\");\n console.log(` Found ${urls.length} cached pages.`);\n } else {\n console.log(\" Scraping URLs...\");\n urls = await scrapeSource(source, sourceName, DATA_DIR, (cur, total, url) => {\n console.log(` [${cur}/${total}] ${url}`);\n });\n console.log(` Found ${urls.length} pages.`);\n }\n\n if (urlLimit && urls.length > urlLimit) {\n urls = urls.slice(0, urlLimit);\n console.log(` Limited to ${urlLimit} pages.`);\n }\n\n console.log(\" Converting to markdown...\");\n pages = await convertSource(\n sourceName,\n urls,\n source.content_selector!,\n source.remove_selectors,\n source.remove_text_patterns,\n DATA_DIR,\n source.concurrency,\n (cur, total) => {\n if (cur % 10 === 0 || cur === total) console.log(` [${cur}/${total}] converted`);\n },\n );\n }\n\n console.log(\" Chunking...\");\n const allChunks = pages.flatMap((p) => chunkMarkdown(p.markdown, sourceName, p.url, p.title));\n console.log(` Created ${allChunks.length} chunks.`);\n\n if (args.values[\"skip-store\"]) {\n console.log(` Done. ${allChunks.length} chunks ready (dry run, no embed/store).`);\n continue;\n }\n\n await syncChunks(sourceName, allChunks, pages.length, source.version, args.values[\"allow-shrink\"]);\n }\n}\n\nasync function cmdSearch(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n source: { type: \"string\" },\n top: { type: \"string\" },\n candidates: { type: \"string\" },\n compact: { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n });\n\n const query = args.positionals.join(\" \");\n if (!query) {\n console.error(\"Usage: grimoire search \\\"<query>\\\" [--source <name>] [--top <n>] [--candidates <n>] [--compact]\");\n process.exit(1);\n }\n\n const topN = args.values.top ? parseInt(args.values.top, 10) : undefined;\n const candidates = args.values.candidates ? parseInt(args.values.candidates, 10) : undefined;\n const results = await search(query, { source: args.values.source, topN, candidates });\n\n if (results.length === 0) {\n console.log(\"No results found.\");\n return;\n }\n\n if (args.values.compact) {\n for (const r of results) {\n console.log(`${r.relevance_score.toFixed(4)} | ${r.source} | ${r.title} | ${r.heading_path.join(\" > \")} | ${r.url}`);\n }\n return;\n }\n\n for (let i = 0; i < results.length; i++) {\n const r = results[i];\n console.log(`\\n${bold(`[${i + 1}] ${r.title}`)} (${r.relevance_score.toFixed(4)})`);\n console.log(` ${cyan(r.url)}`);\n console.log(` ${yellow(r.heading_path.join(\" > \"))}`);\n console.log(` ${r.content.replace(/\\n/g, \" \")}`);\n }\n}\n\nasync function cmdList(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n names: { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n });\n\n const metas = await getAllSourcesMeta();\n\n if (metas.length === 0) {\n console.log(\"No sources have been refreshed yet.\");\n return;\n }\n\n if (args.values.names) {\n for (const meta of metas) {\n console.log(meta.source);\n }\n return;\n }\n\n console.log(\"\\nSources:\\n\");\n for (const meta of metas) {\n const ver = meta.version ? ` v${meta.version}` : \"\";\n console.log(` ${bold(meta.source)}${ver}`);\n console.log(` ${meta.chunk_count} chunks, ${meta.url_count} URLs, last refreshed ${meta.last_refreshed}`);\n }\n}\n\nasync function cmdStats(): Promise<void> {\n const metas = await getAllSourcesMeta();\n\n if (metas.length === 0) {\n console.log(\"No sources have been refreshed yet.\");\n return;\n }\n\n let totalChunks = 0;\n let totalUrls = 0;\n\n console.log(\"\\nSource Statistics:\\n\");\n for (const meta of metas) {\n const ver = meta.version ? ` v${meta.version}` : \"\";\n console.log(` ${bold(meta.source)}${ver}`);\n console.log(` Chunks: ${meta.chunk_count}`);\n console.log(` URLs: ${meta.url_count}`);\n console.log(` Last refreshed: ${meta.last_refreshed}`);\n totalChunks += meta.chunk_count;\n totalUrls += meta.url_count;\n }\n\n console.log(`\\n Total: ${totalChunks} chunks across ${totalUrls} URLs from ${metas.length} sources`);\n}\n\nasync function cmdExport(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n format: { type: \"string\", default: \"json\" },\n },\n allowPositionals: true,\n });\n\n const sourceName = args.positionals[0];\n if (!sourceName) {\n console.error(\"Usage: grimoire export <source> [--format json]\");\n process.exit(1);\n }\n\n const mdDir = join(DATA_DIR, \"markdown\", sourceName);\n let files: string[];\n try {\n files = await readdir(mdDir);\n } catch {\n console.error(`No markdown data found for source \"${sourceName}\".`);\n process.exit(1);\n }\n\n const pages = [];\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const content = await readFile(join(mdDir, file), \"utf-8\");\n pages.push({ file, content });\n }\n\n console.log(JSON.stringify(pages, null, 2));\n}\n\nasync function cmdDelete(): Promise<void> {\n const sourceName = process.argv[3];\n if (!sourceName) {\n console.error(\"Usage: grimoire delete <source>\");\n process.exit(1);\n }\n\n const meta = await getSourceMeta(sourceName);\n if (!meta) {\n console.error(`Source \"${sourceName}\" not found in Firestore.`);\n process.exit(1);\n }\n\n console.log(`Deleting \"${sourceName}\" (${meta.chunk_count} chunks)...`);\n const deleted = await purgeSource(sourceName);\n await deleteSourceMeta(sourceName);\n console.log(`Deleted ${deleted} chunks and source metadata for \"${sourceName}\".`);\n}\n\nasync function cmdScrapeUrls(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n concurrency: { type: \"string\" },\n },\n allowPositionals: true,\n });\n\n const sourceName = args.positionals[0];\n if (!sourceName) {\n console.error(\"Usage: grimoire scrape-urls <source> [--concurrency <n>]\");\n process.exit(1);\n }\n\n const config = await loadConfig(CONFIG_PATH);\n const source = config.sources[sourceName];\n if (!source) {\n console.error(`Source \"${sourceName}\" not found in config.`);\n process.exit(1);\n }\n\n const rawDir = join(DATA_DIR, \"raw\", sourceName);\n const urlsPath = join(rawDir, \"urls.json\");\n\n let urls: string[];\n try {\n urls = JSON.parse(await readFile(urlsPath, \"utf-8\"));\n } catch {\n console.error(`No urls.json found for \"${sourceName}\". Run 'grimoire refresh ${sourceName} --skip-store' first.`);\n process.exit(1);\n }\n\n const missing = urls.filter((url) => !existsSync(join(rawDir, `${slugifyUrl(url)}.html`)));\n console.log(`\\nTotal: ${urls.length}, Cached: ${urls.length - missing.length}, Missing: ${missing.length}`);\n\n if (missing.length === 0) {\n console.log(\"Nothing to scrape.\");\n return;\n }\n\n const concurrency = args.values.concurrency ? parseInt(args.values.concurrency, 10) : source.concurrency ?? 20;\n const browser = await createBrowser();\n const context = await browser.newContext(source.user_agent ? { userAgent: source.user_agent } : {});\n let done = 0;\n\n for (let i = 0; i < missing.length; i += concurrency) {\n const batch = missing.slice(i, i + concurrency);\n await Promise.all(batch.map(async (url) => {\n const page = await context.newPage();\n try {\n await page.goto(url, { waitUntil: source.headed ? \"networkidle\" : \"domcontentloaded\", timeout: 30000 });\n if (source.wait_for_selector) {\n await page.waitForSelector(source.wait_for_selector, { timeout: 30000 });\n }\n const html = await page.content();\n await writeFile(join(rawDir, `${slugifyUrl(url)}.html`), html, \"utf-8\");\n done++;\n if (done % 10 === 0 || done === missing.length) console.log(` [${done}/${missing.length}]`);\n } catch (e) {\n console.error(` FAILED: ${url} - ${e instanceof Error ? e.message : String(e)}`);\n } finally {\n await page.close();\n }\n }));\n }\n\n console.log(`Done. Fetched ${done} pages.`);\n await browser.close();\n}\n\nexport const ADMIN_COMMANDS: Record<string, () => Promise<void>> = {\n add: cmdAdd,\n refresh: cmdRefresh,\n delete: cmdDelete,\n \"scrape-urls\": cmdScrapeUrls,\n search: cmdSearch,\n list: cmdList,\n stats: cmdStats,\n export: cmdExport,\n apikey: cmdApiKey,\n};\n", "import { readFile } from \"node:fs/promises\";\nimport { parse } from \"yaml\";\n\nexport interface SourceConfig {\n name: string;\n version?: string;\n start_url: string;\n nav_selector?: string;\n content_selector?: string;\n remove_selectors?: string[];\n remove_text_patterns?: string[];\n include_patterns?: string[];\n exclude_patterns?: string[];\n rate_limit_ms?: number;\n concurrency?: number;\n headed?: boolean;\n sitemap_url?: string;\n user_agent?: string;\n llms_full_url?: string;\n wait_for_selector?: string;\n}\n\nexport interface GrimoireConfig {\n sources: Record<string, SourceConfig>;\n}\n\nconst REQUIRED_SOURCE_FIELDS = [\n \"name\",\n \"start_url\",\n] as const;\n\nconst SCRAPE_REQUIRED_FIELDS = [\n \"nav_selector\",\n \"content_selector\",\n] as const;\n\nexport function validateConfig(data: unknown): GrimoireConfig {\n if (typeof data !== \"object\" || data === null || !(\"sources\" in data)) {\n throw new Error(\"Config must have a 'sources' key\");\n }\n\n const { sources } = data as { sources: unknown };\n\n if (typeof sources !== \"object\" || sources === null) {\n throw new Error(\"'sources' must be an object\");\n }\n\n const entries = Object.entries(sources as Record<string, unknown>);\n\n if (entries.length === 0) {\n throw new Error(\"'sources' must contain at least one source\");\n }\n\n const validated: Record<string, SourceConfig> = {};\n\n for (const [key, value] of entries) {\n if (typeof value !== \"object\" || value === null) {\n throw new Error(`Source '${key}' must be an object`);\n }\n\n const source = value as Record<string, unknown>;\n\n for (const field of REQUIRED_SOURCE_FIELDS) {\n if (typeof source[field] !== \"string\" || source[field] === \"\") {\n throw new Error(`Source '${key}' is missing required field '${field}'`);\n }\n }\n\n if (!source.llms_full_url) {\n for (const field of SCRAPE_REQUIRED_FIELDS) {\n if (typeof source[field] !== \"string\" || source[field] === \"\") {\n throw new Error(`Source '${key}' is missing required field '${field}'`);\n }\n }\n }\n\n try {\n new URL(source.start_url as string);\n } catch {\n throw new Error(`Source '${key}' has invalid start_url: ${source.start_url}`);\n }\n\n if (source.remove_selectors !== undefined && !Array.isArray(source.remove_selectors)) {\n throw new Error(`Source '${key}': remove_selectors must be an array`);\n }\n\n if (source.remove_text_patterns !== undefined && !Array.isArray(source.remove_text_patterns)) {\n throw new Error(`Source '${key}': remove_text_patterns must be an array`);\n }\n\n if (source.include_patterns !== undefined && !Array.isArray(source.include_patterns)) {\n throw new Error(`Source '${key}': include_patterns must be an array`);\n }\n\n if (source.exclude_patterns !== undefined && !Array.isArray(source.exclude_patterns)) {\n throw new Error(`Source '${key}': exclude_patterns must be an array`);\n }\n\n if (source.rate_limit_ms !== undefined && typeof source.rate_limit_ms !== \"number\") {\n throw new Error(`Source '${key}': rate_limit_ms must be a number`);\n }\n\n if (source.concurrency !== undefined && typeof source.concurrency !== \"number\") {\n throw new Error(`Source '${key}': concurrency must be a number`);\n }\n\n validated[key] = {\n name: source.name as string,\n version: source.version as string | undefined,\n start_url: source.start_url as string,\n nav_selector: source.nav_selector as string | undefined,\n content_selector: source.content_selector as string | undefined,\n remove_selectors: source.remove_selectors as string[] | undefined,\n remove_text_patterns: source.remove_text_patterns as string[] | undefined,\n include_patterns: source.include_patterns as string[] | undefined,\n exclude_patterns: source.exclude_patterns as string[] | undefined,\n rate_limit_ms: source.rate_limit_ms as number | undefined,\n concurrency: source.concurrency as number | undefined,\n headed: source.headed as boolean | undefined,\n sitemap_url: source.sitemap_url as string | undefined,\n user_agent: source.user_agent as string | undefined,\n llms_full_url: source.llms_full_url as string | undefined,\n wait_for_selector: source.wait_for_selector as string | undefined,\n };\n }\n\n return { sources: validated };\n}\n\nexport async function loadConfig(path: string): Promise<GrimoireConfig> {\n const content = await readFile(path, \"utf-8\");\n const data = parse(content, { merge: true });\n return validateConfig(data);\n}\n", "import { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { chromium, type Browser, type Page } from \"playwright\";\nimport type { SourceConfig } from \"./config.js\";\nimport { slugifyUrl } from \"./slug.js\";\n\nexport function filterUrls(\n urls: string[],\n includePatterns?: string[],\n excludePatterns?: string[],\n): string[] {\n // Fragment variants (page#section) are the same document: keeping them\n // would scrape and chunk the same page once per anchor.\n let filtered = urls\n .map((url) => url.split(\"#\")[0])\n .filter((url) => url.startsWith(\"http\") && !url.includes(\"?hl=\"));\n\n if (includePatterns && includePatterns.length > 0) {\n filtered = filtered.filter((url) =>\n includePatterns.some((pattern) => url.includes(pattern)),\n );\n }\n\n if (excludePatterns && excludePatterns.length > 0) {\n filtered = filtered.filter(\n (url) => !excludePatterns.some((pattern) => url.includes(pattern)),\n );\n }\n\n return [...new Set(filtered)].sort();\n}\n\nasync function fetchSitemapUrls(sitemapUrl: string): Promise<string[]> {\n const response = await fetch(sitemapUrl);\n const xml = await response.text();\n const locs = [...xml.matchAll(/<loc>([^<]+)<\\/loc>/g)].map((m) => m[1]);\n\n if (xml.includes(\"<sitemapindex\")) {\n const nested = await Promise.all(locs.map((loc) => fetchSitemapUrls(loc)));\n return nested.flat();\n }\n\n return locs;\n}\n\nasync function discoverFromSitemap(sitemapUrl: string, source: SourceConfig): Promise<string[]> {\n const urls = await fetchSitemapUrls(sitemapUrl);\n return filterUrls(urls, source.include_patterns, source.exclude_patterns);\n}\n\nexport async function discoverUrls(\n page: Page,\n source: SourceConfig,\n): Promise<string[]> {\n if (source.sitemap_url) {\n return discoverFromSitemap(source.sitemap_url, source);\n }\n\n await page.goto(source.start_url, { waitUntil: source.headed ? \"networkidle\" : \"domcontentloaded\" });\n\n const rawUrls = await page.$$eval(\n `${source.nav_selector} a[href]`,\n (links) => links.map((a) => (a as HTMLAnchorElement).href),\n );\n\n const discovered = filterUrls(rawUrls, source.include_patterns, source.exclude_patterns);\n if (!discovered.includes(source.start_url)) {\n discovered.unshift(source.start_url);\n }\n return discovered;\n}\n\nconst NAV_TIMEOUT_MS_HEADED = 60000;\nconst NAV_TIMEOUT_MS = 30000;\n\nexport async function fetchPage(\n page: Page,\n url: string,\n headed?: boolean,\n waitForSelector?: string,\n): Promise<string> {\n try {\n await page.goto(url, {\n waitUntil: headed ? \"networkidle\" : \"domcontentloaded\",\n timeout: headed ? NAV_TIMEOUT_MS_HEADED : NAV_TIMEOUT_MS,\n });\n } catch (err) {\n // Bot-protected sites with polling widgets (Zendesk live chat, intercom)\n // never reach networkidle. The DOM has loaded by then, so accept the\n // timeout and fall through; wait_for_selector below gates content validity.\n if (!(err instanceof Error) || !/Timeout|Navigation timeout/i.test(err.message)) {\n throw err;\n }\n }\n if (waitForSelector) {\n await page.waitForSelector(waitForSelector, { timeout: NAV_TIMEOUT_MS_HEADED });\n }\n return page.content();\n}\n\nconst DEFAULT_CONCURRENCY = 50;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function runPool<T>(\n items: T[],\n concurrency: number,\n rateLimitMs: number,\n fn: (item: T, index: number) => Promise<void>,\n): Promise<void> {\n let nextIndex = 0;\n\n async function worker(): Promise<void> {\n let isFirst = true;\n while (nextIndex < items.length) {\n const index = nextIndex++;\n if (!isFirst && rateLimitMs > 0) {\n await sleep(rateLimitMs);\n }\n isFirst = false;\n await fn(items[index], index);\n }\n }\n\n const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker());\n await Promise.all(workers);\n}\n\nexport async function scrapeSource(\n source: SourceConfig,\n sourceName: string,\n dataDir: string,\n onProgress?: (current: number, total: number, url: string) => void,\n): Promise<string[]> {\n const rawDir = join(dataDir, \"raw\", sourceName);\n await mkdir(rawDir, { recursive: true });\n\n const concurrency = source.concurrency ?? DEFAULT_CONCURRENCY;\n const rateLimitMs = source.rate_limit_ms ?? 0;\n const browser = await chromium.launch({ channel: \"chrome\", headless: !source.headed });\n const context = await browser.newContext(source.user_agent ? { userAgent: source.user_agent } : {});\n\n const discoveryPage = await context.newPage();\n const urls = await discoverUrls(discoveryPage, source);\n await discoveryPage.close();\n\n let completed = 0;\n const succeeded: string[] = [];\n const failed: { url: string; error: string }[] = [];\n\n async function attemptScrape(url: string, maxAttempts: number): Promise<string | null> {\n let lastError: unknown;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const page = await context.newPage();\n try {\n const html = await fetchPage(page, url, source.headed, source.wait_for_selector);\n const slug = slugifyUrl(url);\n await writeFile(join(rawDir, `${slug}.html`), html, \"utf-8\");\n return null;\n } catch (err) {\n lastError = err;\n } finally {\n await page.close();\n }\n }\n return lastError instanceof Error ? lastError.message : String(lastError);\n }\n\n try {\n await runPool(urls, concurrency, rateLimitMs, async (url) => {\n const errorMsg = await attemptScrape(url, 2);\n if (errorMsg === null) {\n succeeded.push(url);\n } else {\n failed.push({ url, error: errorMsg });\n }\n completed++;\n onProgress?.(completed, urls.length, url);\n });\n\n // Recovery pass: retry transient failures sequentially with throttling.\n // Zendesk and similar bot-protected sites randomly time out under parallel\n // load even at low concurrency; a serialized retry usually catches them.\n if (failed.length > 0) {\n const toRetry = failed.splice(0).map((f) => f.url);\n const recoveryRateLimit = Math.max(rateLimitMs, 1000);\n console.log(` Recovery pass: retrying ${toRetry.length} failed pages sequentially...`);\n await runPool(toRetry, 1, recoveryRateLimit, async (url) => {\n const errorMsg = await attemptScrape(url, 2);\n if (errorMsg === null) {\n succeeded.push(url);\n } else {\n failed.push({ url, error: errorMsg });\n }\n });\n console.log(` Recovered ${toRetry.length - failed.length}/${toRetry.length} pages.`);\n }\n\n if (failed.length > 0) {\n console.error(` Skipped ${failed.length}/${urls.length} pages after recovery:`);\n for (const { url, error } of failed.slice(0, 10)) {\n console.error(` ${url} - ${error.split(\"\\n\")[0]}`);\n }\n if (failed.length > 10) console.error(` ...and ${failed.length - 10} more.`);\n }\n\n await writeFile(join(rawDir, \"urls.json\"), JSON.stringify(succeeded), \"utf-8\");\n return succeeded;\n } finally {\n await browser.close();\n }\n}\n\nexport async function createBrowser(): Promise<Browser> {\n return chromium.launch({ channel: \"chrome\" });\n}\n", "// Dependency-free: imported by the Cloud Function via chunker, so it must\n// not pull in playwright or any other admin-only package.\nexport function slugifyUrl(url: string): string {\n const parsed = new URL(url);\n return parsed.pathname\n .replace(/^\\//, \"\")\n .replace(/\\/$/, \"\")\n .replace(/\\//g, \"-\")\n .replace(/[^a-zA-Z0-9-]/g, \"\");\n}\n", "import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { JSDOM } from \"jsdom\";\nimport TurndownService from \"turndown\";\nimport { tables } from \"@joplin/turndown-plugin-gfm\";\nimport { slugifyUrl } from \"./slug.js\";\n\nconst turndown = new TurndownService({\n headingStyle: \"atx\",\n codeBlockStyle: \"fenced\",\n bulletListMarker: \"-\",\n});\nturndown.use(tables);\n\nexport interface ConvertedPage {\n source: string;\n url: string;\n title: string;\n markdown: string;\n}\n\nconst GENERIC_REMOVE = [\n \"style\",\n \"script\",\n \"noscript\",\n \"iframe\",\n \"svg\",\n];\n\nfunction cleanMarkdown(md: string, textPatterns?: string[]): string {\n let cleaned = md\n .replace(/^(#+)\\s*$/gm, \"\")\n .replace(/\\n{3,}/g, \"\\n\\n\");\n\n if (textPatterns) {\n for (const pattern of textPatterns) {\n cleaned = cleaned.replace(new RegExp(pattern, \"gm\"), \"\");\n }\n cleaned = cleaned.replace(/\\n{3,}/g, \"\\n\\n\");\n }\n\n return cleaned.trim();\n}\n\n// The GFM tables plugin keeps a table as raw HTML when its cells contain\n// code, lists, or other block markup. Reference tables (e.g. GCP flag docs)\n// are full of those, so flatten cell markup to plain text first - pipe rows\n// preserve the name/type/default association that flattened paragraphs lose.\nfunction flattenTableCellMarkup(contentEl: Element): void {\n for (const table of contentEl.querySelectorAll(\"table\")) {\n if (!table.isConnected) continue;\n for (const inner of table.querySelectorAll(\"table\")) {\n if (!inner.isConnected) continue;\n const rows = [...inner.rows]\n .map((row) =>\n [...row.cells]\n .map((c) => c.textContent?.replace(/\\s+/g, \" \").trim() ?? \"\")\n .filter(Boolean)\n .join(\" \"),\n )\n .filter(Boolean);\n inner.replaceWith(rows.join(\"; \"));\n }\n for (const code of table.querySelectorAll(\"code\")) {\n code.replaceWith(code.textContent ?? \"\");\n }\n for (const list of table.querySelectorAll(\"ul, ol\")) {\n if (!list.isConnected) continue;\n const items = [...list.querySelectorAll(\"li\")]\n .map((li) => li.textContent?.replace(/\\s+/g, \" \").trim() ?? \"\")\n .filter(Boolean);\n list.replaceWith(items.join(\"; \"));\n }\n for (const block of table.querySelectorAll(\"aside, blockquote, h1, h2, h3, h4, h5, h6\")) {\n if (!block.isConnected) continue;\n block.replaceWith(block.textContent?.replace(/\\s+/g, \" \").trim() ?? \"\");\n }\n }\n}\n\n// Table cells emit <br> for in-cell line breaks; replace with \"; \" so pipe\n// rows stay single-line and the embedded text carries no HTML tags.\nfunction cleanTableRowBreaks(md: string): string {\n return md\n .split(\"\\n\")\n .map((line) =>\n line.startsWith(\"|\") ? line.replace(/\\s*<br\\s*\\/?>\\s*/g, \"; \") : line,\n )\n .join(\"\\n\");\n}\n\n// Syntax highlighters wrap every code token in <span>, inflating some pages\n// to tens of MB; JSDOM expands that 50-100x in memory and OOMs the convert\n// stage. Spans are inline wrappers turndown ignores, so unwrapping them at\n// the text level changes nothing in the markdown output. Applied only to\n// oversized pages to keep normal conversions byte-identical.\nconst SPAN_STRIP_THRESHOLD_BYTES = 2_000_000;\n\nfunction stripInlineSpans(html: string): string {\n return html.replace(/<\\/?span\\b[^>]*>/g, \"\");\n}\n\nexport function extractContent(\n html: string,\n contentSelector: string,\n removeSelectors?: string[],\n removeTextPatterns?: string[],\n): string {\n if (html.length > SPAN_STRIP_THRESHOLD_BYTES) {\n html = stripInlineSpans(html);\n }\n const dom = new JSDOM(html);\n const doc = dom.window.document;\n\n const contentEl = doc.querySelector(contentSelector) ?? doc.body;\n\n const allSelectors = [...GENERIC_REMOVE, ...(removeSelectors ?? [])];\n for (const selector of allSelectors) {\n for (const el of contentEl.querySelectorAll(selector)) {\n el.remove();\n }\n }\n\n flattenTableCellMarkup(contentEl);\n\n return cleanMarkdown(\n cleanTableRowBreaks(turndown.turndown(contentEl.innerHTML)),\n removeTextPatterns,\n );\n}\n\n// Strip only the trailing site-name segment (after the last pipe, dash with\n// surrounding spaces, or en/em dash) so mid-title hyphens and inner segments\n// like \"ExportContext | Cloud SQL Admin API\" survive.\nexport function extractTitle(html: string): string {\n const dom = new JSDOM(html);\n const raw = dom.window.document\n .querySelector(\"title\")\n ?.textContent?.replace(/\\s+/g, \" \")\n .trim();\n if (!raw) return \"Untitled\";\n const stripped = raw\n .replace(/\\s*\\|[^|]*$/, \"\")\n .replace(/\\s*[\\u2013\\u2014][^\\u2013\\u2014]*$/, \"\")\n .replace(/\\s+-\\s+(?!.*\\s-\\s)[^|]*$/, \"\")\n .trim();\n return stripped || raw;\n}\n\nexport function buildFrontmatter(source: string, url: string, title: string): string {\n return [\n \"---\",\n `source: ${source}`,\n `url: \"${url}\"`,\n `title: \"${title.replace(/\"/g, '\\\\\"')}\"`,\n `fetched_at: \"${new Date().toISOString()}\"`,\n \"---\",\n ].join(\"\\n\");\n}\n\nexport function convertPage(\n html: string,\n source: string,\n url: string,\n contentSelector: string,\n removeSelectors?: string[],\n removeTextPatterns?: string[],\n): ConvertedPage {\n const title = extractTitle(html);\n const content = extractContent(html, contentSelector, removeSelectors, removeTextPatterns);\n const frontmatter = buildFrontmatter(source, url, title);\n const markdown = `${frontmatter}\\n\\n${content}`;\n\n return { source, url, title, markdown };\n}\n\nconst DEFAULT_CONCURRENCY = 10;\n\nexport async function convertSource(\n sourceName: string,\n urls: string[],\n contentSelector: string,\n removeSelectors: string[] | undefined,\n removeTextPatterns: string[] | undefined,\n dataDir: string,\n concurrency = DEFAULT_CONCURRENCY,\n onProgress?: (current: number, total: number, url: string) => void,\n): Promise<ConvertedPage[]> {\n const rawDir = join(dataDir, \"raw\", sourceName);\n const mdDir = join(dataDir, \"markdown\", sourceName);\n await mkdir(mdDir, { recursive: true });\n\n const pages: ConvertedPage[] = new Array(urls.length);\n let completed = 0;\n let nextIndex = 0;\n\n async function worker(): Promise<void> {\n while (nextIndex < urls.length) {\n const i = nextIndex++;\n const url = urls[i];\n const slug = slugifyUrl(url);\n const htmlPath = join(rawDir, `${slug}.html`);\n\n let html: string;\n try {\n html = await readFile(htmlPath, \"utf-8\");\n } catch {\n console.warn(` WARNING: no cached HTML for ${url}, skipping page. Run 'grimoire scrape-urls' to fetch it.`);\n completed++;\n continue;\n }\n\n const page = convertPage(html, sourceName, url, contentSelector, removeSelectors, removeTextPatterns);\n await writeFile(join(mdDir, `${slug}.md`), page.markdown, \"utf-8\");\n pages[i] = page;\n completed++;\n onProgress?.(completed, urls.length, url);\n }\n }\n\n const workers = Array.from(\n { length: Math.min(concurrency, urls.length) },\n () => worker(),\n );\n await Promise.all(workers);\n\n return pages.filter((page) => page !== undefined);\n}\n", "import { createHash } from \"node:crypto\";\n\n// Identifier-shaped tokens: snake_case, kebab-case, dotted.paths (and mixes),\n// camelCase, PascalCase. Plain prose words are deliberately excluded - the\n// lexical index exists to find exact identifiers that dense embeddings dilute,\n// while ordinary vocabulary is the vector search's job.\nconst IDENTIFIER_PATTERN =\n /(?<![A-Za-z0-9._])(?:[A-Za-z][A-Za-z0-9]*(?:[_.-][A-Za-z0-9]+)+|[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)+|(?:[A-Z][a-z0-9]+){2,})(?![A-Za-z0-9])/g;\n\nconst MIN_TOKEN_LENGTH = 4;\nconst MAX_TOKEN_LENGTH = 80;\nconst MAX_TOKENS_PER_CHUNK = 100;\n\n// Drop URLs/link targets (their hostnames and paths are identifier-shaped\n// noise) and undo markdown escaping (`max\\_connections` must tokenize as\n// max_connections).\nfunction normalizeForTokens(text: string): string {\n return text\n .replace(/\\]\\([^)]*\\)/g, \"]\")\n .replace(/https?:\\/\\/\\S+/g, \" \")\n .replace(/\\\\([_*[\\]()#`~-])/g, \"$1\");\n}\n\nexport function extractIdentifierTokens(text: string, limit = MAX_TOKENS_PER_CHUNK): string[] {\n const seen = new Set<string>();\n for (const match of normalizeForTokens(text).matchAll(IDENTIFIER_PATTERN)) {\n const token = match[0].toLowerCase();\n if (token.length < MIN_TOKEN_LENGTH || token.length > MAX_TOKEN_LENGTH) continue;\n seen.add(token);\n if (seen.size >= limit) break;\n }\n return [...seen];\n}\n\nexport function extractQueryTokens(query: string): string[] {\n return extractIdentifierTokens(query, 5);\n}\n\nexport function contentHash(text: string): string {\n return createHash(\"sha256\").update(text).digest(\"hex\");\n}\n", "import { slugifyUrl } from \"./slug.js\";\nimport { contentHash } from \"./tokens.js\";\nimport type { Chunk } from \"./types.js\";\n\nexport type { Chunk };\n\nconst MAX_TOKENS = 500;\n\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nexport function slugifyHeading(heading: string): string {\n return heading\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nexport function buildChunkId(\n source: string,\n url: string,\n headingSlug: string,\n index?: number,\n): string {\n const urlSlug = slugifyUrl(url);\n const prefix = `${source}::${urlSlug}::`;\n const maxSlugBytes = 1500 - Buffer.byteLength(prefix) - 10;\n const truncatedSlug =\n Buffer.byteLength(headingSlug) > maxSlugBytes\n ? Buffer.from(headingSlug).subarray(0, maxSlugBytes).toString()\n : headingSlug;\n const base = `${prefix}${truncatedSlug}`;\n return index !== undefined ? `${base}-${index}` : base;\n}\n\n// The text whose embedding represents the chunk. Prepending the page title\n// and heading path pulls dense reference chunks (flag tables, REST schemas)\n// toward queries that name the page or section, not just the body text.\n// The chunk's own heading is rendered at the top of content, so it is\n// dropped from the context prefix to avoid double-weighting it.\nexport function buildEmbedText(chunk: Chunk): string {\n let path = chunk.heading_path[0] === chunk.title\n ? chunk.heading_path.slice(1)\n : chunk.heading_path;\n if (chunk.content.startsWith(\"#\") && path.length > 0) {\n path = path.slice(0, -1);\n }\n const context = [chunk.title, ...path].filter(Boolean).join(\" > \");\n return context ? `${context}\\n\\n${chunk.content}` : chunk.content;\n}\n\ninterface HeadingSection {\n level: number;\n heading: string;\n headingPath: string[];\n lines: string[];\n}\n\nfunction parseHeadingSections(markdown: string): HeadingSection[] {\n const lines = markdown.split(\"\\n\");\n const sections: HeadingSection[] = [];\n const headingStack: string[] = [];\n const levelStack: number[] = [];\n\n let currentSection: HeadingSection = {\n level: 0,\n heading: \"\",\n headingPath: [],\n lines: [],\n };\n\n let inFence = false;\n let fenceMarker = \"\";\n\n for (const line of lines) {\n const fenceMatch = line.match(/^\\s*(```+|~~~+)/);\n if (fenceMatch) {\n const marker = fenceMatch[1][0];\n if (!inFence) {\n inFence = true;\n fenceMarker = marker;\n } else if (marker === fenceMarker) {\n inFence = false;\n }\n }\n\n const headingMatch = inFence ? null : line.match(/^(#{1,6})\\s+(.+)$/);\n\n if (headingMatch) {\n if (currentSection.lines.length > 0 || currentSection.heading !== \"\") {\n sections.push(currentSection);\n }\n\n const level = headingMatch[1].length;\n const heading = headingMatch[2].trim();\n\n while (levelStack.length > 0 && levelStack[levelStack.length - 1] >= level) {\n levelStack.pop();\n headingStack.pop();\n }\n\n headingStack.push(heading);\n levelStack.push(level);\n\n currentSection = {\n level,\n heading,\n headingPath: [...headingStack],\n lines: [],\n };\n } else {\n currentSection.lines.push(line);\n }\n }\n\n if (currentSection.lines.length > 0 || currentSection.heading !== \"\") {\n sections.push(currentSection);\n }\n\n return sections;\n}\n\ntype BlockKind = \"paragraph\" | \"fence\" | \"table\";\n\ninterface Block {\n kind: BlockKind;\n lines: string[];\n}\n\nfunction isTableLine(line: string): boolean {\n return line.trimStart().startsWith(\"|\");\n}\n\nfunction isTableSeparator(line: string): boolean {\n return /^\\s*\\|?[\\s:|-]+\\|?\\s*$/.test(line) && line.includes(\"-\");\n}\n\n// Group section lines into atomic blocks so splitting never lands inside a\n// fenced code block (blank lines in fences are not paragraph boundaries) and\n// tables can be split row-wise instead of as one opaque paragraph.\nfunction parseBlocks(lines: string[]): Block[] {\n const blocks: Block[] = [];\n let current: string[] = [];\n let kind: BlockKind = \"paragraph\";\n let inFence = false;\n let fenceMarker = \"\";\n\n function flush(): void {\n while (current.length > 0 && current[current.length - 1].trim() === \"\") {\n current.pop();\n }\n if (current.length > 0) {\n blocks.push({ kind, lines: current });\n }\n current = [];\n kind = \"paragraph\";\n }\n\n for (const line of lines) {\n if (inFence) {\n current.push(line);\n const fenceMatch = line.match(/^\\s*(```+|~~~+)/);\n if (fenceMatch && fenceMatch[1][0] === fenceMarker) {\n inFence = false;\n flush();\n }\n continue;\n }\n\n const fenceMatch = line.match(/^\\s*(```+|~~~+)/);\n if (fenceMatch) {\n flush();\n kind = \"fence\";\n inFence = true;\n fenceMarker = fenceMatch[1][0];\n current.push(line);\n continue;\n }\n\n if (isTableLine(line)) {\n if (kind !== \"table\") {\n flush();\n kind = \"table\";\n }\n current.push(line);\n continue;\n }\n\n if (kind === \"table\") {\n flush();\n }\n\n if (line.trim() === \"\") {\n flush();\n } else {\n current.push(line);\n }\n }\n\n flush();\n return blocks;\n}\n\n// A single line over the budget must be hard-split: gemini-embedding-001\n// caps input at 2048 tokens and silently truncates beyond it, making the\n// tail of an oversized chunk invisible to search.\nfunction splitLongLine(line: string, budget: number): string[] {\n const maxChars = budget * 4;\n if (line.length <= maxChars) return [line];\n\n const pieces: string[] = [];\n let rest = line;\n while (rest.length > maxChars) {\n let cut = rest.lastIndexOf(\" \", maxChars);\n if (cut < maxChars / 2) cut = maxChars;\n pieces.push(rest.slice(0, cut));\n rest = rest.slice(cut).trimStart();\n }\n if (rest) pieces.push(rest);\n return pieces;\n}\n\nfunction groupLines(lines: string[], budget: number): string[][] {\n const groups: string[][] = [];\n let current: string[] = [];\n let tokens = 0;\n\n for (const line of lines.flatMap((l) => splitLongLine(l, budget))) {\n const lineTokens = estimateTokens(line) + 1;\n if (tokens + lineTokens > budget && current.length > 0) {\n groups.push(current);\n current = [];\n tokens = 0;\n }\n current.push(line);\n tokens += lineTokens;\n }\n\n if (current.length > 0) groups.push(current);\n return groups;\n}\n\n// Split an oversized block into pieces that each stay valid markdown:\n// tables repeat the header row per piece, fences are re-opened and re-closed.\nfunction splitBlock(block: Block, budget: number): string[] {\n if (block.kind === \"table\") {\n const hasHeader = block.lines.length >= 2 && isTableSeparator(block.lines[1]);\n const header = hasHeader ? block.lines.slice(0, 2) : [];\n const rows = hasHeader ? block.lines.slice(2) : block.lines;\n const headerTokens = estimateTokens(header.join(\"\\n\"));\n return groupLines(rows, Math.max(budget - headerTokens, 50)).map((group) =>\n [...header, ...group].join(\"\\n\"),\n );\n }\n\n if (block.kind === \"fence\") {\n const opening = block.lines[0];\n const closing = block.lines[block.lines.length - 1].match(/^\\s*(```+|~~~+)\\s*$/)\n ? block.lines[block.lines.length - 1]\n : opening.match(/^\\s*(```+|~~~+)/)![1];\n const body = block.lines.slice(1, block.lines[block.lines.length - 1] === closing ? -1 : undefined);\n const frameTokens = estimateTokens(opening) + estimateTokens(closing) + 2;\n return groupLines(body, Math.max(budget - frameTokens, 50)).map((group) =>\n [opening, ...group, closing].join(\"\\n\"),\n );\n }\n\n return groupLines(block.lines, budget).map((group) => group.join(\"\\n\"));\n}\n\n// Pack blocks into parts within the token budget. Every part of a split\n// section carries the heading line so continuation chunks keep their context.\nfunction splitSectionIntoParts(\n blocks: Block[],\n headingLine: string,\n maxTokens: number,\n): string[] {\n const budget = Math.max(maxTokens - estimateTokens(headingLine), 100);\n const parts: string[][] = [];\n let current: string[] = [];\n let tokens = 0;\n\n function flush(): void {\n if (current.length > 0) {\n parts.push(current);\n current = [];\n tokens = 0;\n }\n }\n\n for (const block of blocks) {\n const text = block.lines.join(\"\\n\");\n const blockTokens = estimateTokens(text) + 2;\n\n if (blockTokens > budget) {\n flush();\n for (const piece of splitBlock(block, budget)) {\n parts.push([piece]);\n }\n continue;\n }\n\n if (tokens + blockTokens > budget) {\n flush();\n }\n current.push(text);\n tokens += blockTokens;\n }\n\n flush();\n return parts.map((blockTexts) => headingLine + blockTexts.join(\"\\n\\n\"));\n}\n\nfunction stripFrontmatter(markdown: string): string {\n if (markdown.startsWith(\"---\")) {\n const endIndex = markdown.indexOf(\"---\", 3);\n if (endIndex !== -1) {\n return markdown.slice(endIndex + 3).trim();\n }\n }\n return markdown;\n}\n\nexport function chunkMarkdown(\n markdown: string,\n source: string,\n url: string,\n title: string,\n): Chunk[] {\n const stripped = stripFrontmatter(markdown);\n const sections = parseHeadingSections(stripped);\n const chunks: Chunk[] = [];\n const usedIds = new Set<string>();\n\n function uniqueId(baseSlug: string): string {\n let id = buildChunkId(source, url, baseSlug);\n if (!usedIds.has(id)) {\n usedIds.add(id);\n return id;\n }\n let counter = 1;\n while (usedIds.has(buildChunkId(source, url, baseSlug, counter))) {\n counter++;\n }\n id = buildChunkId(source, url, baseSlug, counter);\n usedIds.add(id);\n return id;\n }\n\n // Sections whose headings slugify identically get a content-derived\n // suffix so the id-to-content mapping survives reordering; a positional\n // counter would silently swap which content lives under which id.\n const slugCounts = new Map<string, number>();\n for (const section of sections) {\n const slug = section.heading ? slugifyHeading(section.heading) : \"intro\";\n slugCounts.set(slug, (slugCounts.get(slug) ?? 0) + 1);\n }\n\n for (const section of sections) {\n const headingLine = section.heading\n ? `${\"#\".repeat(section.level)} ${section.heading}\\n\\n`\n : \"\";\n const body = section.lines.join(\"\\n\").trim();\n const content = headingLine + body;\n\n if (!content.trim()) continue;\n\n const baseSlug = section.heading\n ? slugifyHeading(section.heading)\n : \"intro\";\n const headingSlug = (slugCounts.get(baseSlug) ?? 0) > 1\n ? `${baseSlug}-${contentHash(content).slice(0, 8)}`\n : baseSlug;\n\n if (estimateTokens(content) <= MAX_TOKENS) {\n chunks.push({\n id: uniqueId(headingSlug),\n source,\n url,\n title,\n heading_path: section.headingPath,\n content,\n token_count: estimateTokens(content),\n });\n continue;\n }\n\n const blocks = parseBlocks(body.split(\"\\n\"));\n const parts = splitSectionIntoParts(blocks, headingLine, MAX_TOKENS);\n\n for (let i = 0; i < parts.length; i++) {\n const partContent = parts[i].trim();\n if (!partContent) continue;\n\n const partSlug = parts.length > 1 ? `${headingSlug}-${i}` : headingSlug;\n chunks.push({\n id: uniqueId(partSlug),\n source,\n url,\n title,\n heading_path: section.headingPath,\n content: partContent,\n token_count: estimateTokens(partContent),\n });\n }\n }\n\n return chunks;\n}\n", "import { GoogleGenerativeAI } from \"@google/generative-ai\";\n\nconst BATCH_SIZE = 50;\nconst MODEL = \"gemini-embedding-001\";\nconst OUTPUT_DIMENSIONALITY = 768;\nconst MAX_RETRIES = 5;\nconst RATE_LIMIT_BASE_DELAY_MS = 60000;\nconst NETWORK_BASE_DELAY_MS = 10000;\nconst BATCH_DELAY_MS = 2500;\n\nconst NETWORK_ERROR_PATTERNS = [\n \"fetch failed\",\n \"ECONNRESET\",\n \"ETIMEDOUT\",\n \"ECONNREFUSED\",\n \"EAI_AGAIN\",\n \"ENOTFOUND\",\n \"socket hang up\",\n \"UND_ERR_\",\n];\n\nlet genAI: GoogleGenerativeAI | undefined;\n\nfunction getClient(): GoogleGenerativeAI {\n if (!genAI) {\n const apiKey = process.env.GEMINI_API_KEY;\n if (!apiKey) {\n throw new Error(\"GEMINI_API_KEY environment variable is not set\");\n }\n genAI = new GoogleGenerativeAI(apiKey);\n }\n return genAI;\n}\n\nfunction classifyError(message: string): \"rate_limit\" | \"network\" | \"other\" {\n if (message.includes(\"429\") || message.includes(\"500\") || message.includes(\"502\") || message.includes(\"503\") || message.includes(\"504\")) {\n return \"rate_limit\";\n }\n if (NETWORK_ERROR_PATTERNS.some((p) => message.includes(p))) {\n return \"network\";\n }\n return \"other\";\n}\n\nexport interface EmbedOptions {\n onProgress?: (done: number, total: number) => void;\n maxRetries?: number;\n}\n\nexport async function embedTexts(\n texts: string[],\n options: EmbedOptions = {},\n): Promise<number[][]> {\n const client = getClient();\n const model = client.getGenerativeModel({ model: MODEL });\n\n const { onProgress } = options;\n const maxRetries = options.maxRetries ?? MAX_RETRIES;\n\n const embeddings: number[][] = [];\n\n for (let i = 0; i < texts.length; i += BATCH_SIZE) {\n const batch = texts.slice(i, i + BATCH_SIZE);\n const batchNumber = i / BATCH_SIZE + 1;\n\n let result;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n result = await model.batchEmbedContents({\n requests: batch.map((text) => ({\n content: { role: \"user\", parts: [{ text }] },\n outputDimensionality: OUTPUT_DIMENSIONALITY,\n })),\n });\n break;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const kind = classifyError(message);\n\n if (kind !== \"other\" && attempt < maxRetries - 1) {\n const baseDelay = kind === \"rate_limit\" ? RATE_LIMIT_BASE_DELAY_MS : NETWORK_BASE_DELAY_MS;\n const delay = baseDelay * Math.pow(2, attempt);\n const label = kind === \"rate_limit\" ? \"Rate limited\" : \"Network error\";\n console.log(` ${label} (batch ${batchNumber}), retrying in ${delay / 1000}s...`);\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n\n console.error(` Embedding failed at batch ${batchNumber} (chunks ${i + 1}-${i + batch.length}): ${message}`);\n throw err;\n }\n }\n\n for (const embedding of result!.embeddings) {\n embeddings.push(embedding.values);\n }\n\n onProgress?.(Math.min(i + BATCH_SIZE, texts.length), texts.length);\n\n if (i + BATCH_SIZE < texts.length) {\n await new Promise((resolve) => setTimeout(resolve, BATCH_DELAY_MS));\n }\n }\n\n return embeddings;\n}\n\n// Online query path - one shot, no retries. The Cloud Function request\n// timeout (60s) is shorter than the embedder's first rate-limit backoff\n// (60s), so any retry here would only ever cause a 504. Surface the error\n// to the caller, who can retry at their own cadence.\nexport async function embedText(text: string): Promise<number[]> {\n const [embedding] = await embedTexts([text], { maxRetries: 1 });\n return embedding;\n}\n", "import { initializeApp, applicationDefault, getApps } from \"firebase-admin/app\";\nimport {\n getFirestore,\n FieldValue,\n type Firestore,\n} from \"firebase-admin/firestore\";\nimport type { Chunk, SourceMeta } from \"./types.js\";\nimport { buildEmbedText } from \"./chunker.js\";\nimport { contentHash, extractIdentifierTokens } from \"./tokens.js\";\n\n// Vector-indexed writes are heavy; 500-doc batches against the vector index\n// can exceed the commit deadline on large sources (25k+ chunks). Smaller\n// batches commit faster and a short inter-batch pause smooths sustained\n// write throughput so the vector index backend keeps up.\nconst BATCH_SIZE = 200;\nconst INTER_BATCH_DELAY_MS = 150;\nconst MAX_RETRIES = 7;\nconst BASE_DELAY_MS = 5000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// Transient Firestore/gRPC failures worth retrying. batch.commit() with fixed\n// document ids is idempotent, so re-committing the same batch is safe.\nconst TRANSIENT_PATTERNS = [\n \"RESOURCE_EXHAUSTED\",\n \"Quota exceeded\",\n \"DEADLINE_EXCEEDED\",\n \"UNAVAILABLE\",\n \"ABORTED\",\n \"INTERNAL\",\n \"503\",\n \"ECONNRESET\",\n \"ETIMEDOUT\",\n];\n\nasync function retryOnTransient<T>(fn: () => Promise<T>): Promise<T> {\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n return await fn();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (TRANSIENT_PATTERNS.some((p) => message.includes(p)) && attempt < MAX_RETRIES - 1) {\n const delay = BASE_DELAY_MS * Math.pow(2, attempt);\n console.log(` Transient Firestore error (${message.split(\":\")[0].slice(0, 40)}), retrying in ${delay / 1000}s...`);\n await sleep(delay);\n continue;\n }\n throw err;\n }\n }\n throw new Error(\"Max retries exceeded\");\n}\n\nlet db: Firestore | undefined;\n\nfunction getDb(): Firestore {\n if (!db) {\n if (getApps().length === 0) {\n initializeApp({ credential: applicationDefault() });\n }\n db = getFirestore();\n }\n return db;\n}\n\nfunction chunksCol() {\n return getDb().collection(\"grimoire_chunks\");\n}\n\nfunction sourcesCol() {\n return getDb().collection(\"grimoire_sources\");\n}\n\nexport async function storeChunks(\n chunks: Chunk[],\n embeddings: number[][],\n onProgress?: (current: number, total: number) => void,\n): Promise<void> {\n const database = getDb();\n const col = chunksCol();\n\n for (let i = 0; i < chunks.length; i += BATCH_SIZE) {\n const batch = database.batch();\n const slice = chunks.slice(i, i + BATCH_SIZE);\n const embSlice = embeddings.slice(i, i + BATCH_SIZE);\n\n for (let j = 0; j < slice.length; j++) {\n const chunk = slice[j];\n batch.set(col.doc(chunk.id), {\n source: chunk.source,\n url: chunk.url,\n title: chunk.title,\n heading_path: chunk.heading_path,\n content: chunk.content,\n token_count: chunk.token_count,\n tokens: extractIdentifierTokens(chunk.content),\n content_hash: contentHash(buildEmbedText(chunk)),\n embedded_at: new Date().toISOString(),\n embedding: FieldValue.vector(embSlice[j]),\n });\n }\n\n await retryOnTransient(() => batch.commit());\n onProgress?.(Math.min(i + BATCH_SIZE, chunks.length), chunks.length);\n\n if (i + BATCH_SIZE < chunks.length) {\n await sleep(INTER_BATCH_DELAY_MS);\n }\n }\n}\n\nexport async function purgeSource(sourceName: string): Promise<number> {\n const database = getDb();\n const col = chunksCol();\n\n const snapshot = await col.where(\"source\", \"==\", sourceName).get();\n if (snapshot.empty) return 0;\n\n let batch = database.batch();\n let count = 0;\n\n for (const doc of snapshot.docs) {\n batch.delete(doc.ref);\n count++;\n if (count % BATCH_SIZE === 0) {\n await retryOnTransient(() => batch.commit());\n batch = database.batch();\n }\n }\n\n if (count % BATCH_SIZE !== 0) {\n await retryOnTransient(() => batch.commit());\n }\n\n return count;\n}\n\nexport async function updateSourceMeta(\n sourceName: string,\n chunkCount: number,\n urlCount: number,\n version?: string,\n): Promise<void> {\n await sourcesCol().doc(sourceName).set({\n source: sourceName,\n ...(version ? { version } : {}),\n last_refreshed: new Date().toISOString(),\n chunk_count: chunkCount,\n url_count: urlCount,\n });\n}\n\nexport async function getSourceMeta(sourceName: string): Promise<SourceMeta | null> {\n const doc = await sourcesCol().doc(sourceName).get();\n if (!doc.exists) return null;\n return doc.data() as SourceMeta;\n}\n\nexport async function getAllSourcesMeta(): Promise<SourceMeta[]> {\n const snapshot = await sourcesCol().get();\n return snapshot.docs.map((doc) => doc.data() as SourceMeta);\n}\n\nexport async function deleteSourceMeta(sourceName: string): Promise<void> {\n await sourcesCol().doc(sourceName).delete();\n}\n\nexport async function deleteChunksByIds(\n ids: string[],\n onProgress?: (current: number, total: number) => void,\n): Promise<void> {\n if (ids.length === 0) return;\n const database = getDb();\n const col = chunksCol();\n\n for (let i = 0; i < ids.length; i += BATCH_SIZE) {\n const batch = database.batch();\n const slice = ids.slice(i, i + BATCH_SIZE);\n for (const id of slice) {\n batch.delete(col.doc(id));\n }\n await retryOnTransient(() => batch.commit());\n onProgress?.(Math.min(i + BATCH_SIZE, ids.length), ids.length);\n }\n}\n\n// id -> content_hash for every stored chunk of a source. Chunks stored before\n// hashing existed map to \"\", which never equals a real hash, so they are\n// re-embedded on the next refresh.\nexport async function getSourceChunkHashes(sourceName: string): Promise<Map<string, string>> {\n const col = chunksCol();\n const snapshot = await col.where(\"source\", \"==\", sourceName).select(\"content_hash\").get();\n return new Map(\n snapshot.docs.map((doc) => [doc.id, (doc.data().content_hash as string | undefined) ?? \"\"]),\n );\n}\n\nexport interface TokenHit {\n id: string;\n data: Record<string, unknown>;\n matchedTokens: number;\n}\n\nconst TOKEN_QUERY_LIMIT = 40;\nconst TOKEN_RESULT_FIELDS = [\"source\", \"url\", \"title\", \"heading_path\", \"content\", \"token_count\"] as const;\n\n// Exact-identifier lookup: one array-contains query per token, merged by id.\n// Embeddings are deliberately not fetched (740+ floats per doc); lexical-only\n// candidates carry no vector similarity. Sources stored before the tokens\n// field existed simply produce no hits.\nexport async function tokenSearch(\n tokens: string[],\n source?: string,\n): Promise<TokenHit[]> {\n if (tokens.length === 0) return [];\n const col = chunksCol();\n\n const hits = new Map<string, TokenHit>();\n\n const snapshots = await Promise.all(\n tokens.map((token) => {\n let query = col.where(\"tokens\", \"array-contains\", token);\n if (source) {\n query = query.where(\"source\", \"==\", source);\n }\n return query.select(...TOKEN_RESULT_FIELDS).limit(TOKEN_QUERY_LIMIT).get();\n }),\n );\n\n for (const snapshot of snapshots) {\n for (const doc of snapshot.docs) {\n const existing = hits.get(doc.id);\n if (existing) {\n existing.matchedTokens++;\n } else {\n hits.set(doc.id, { id: doc.id, data: doc.data(), matchedTokens: 1 });\n }\n }\n }\n\n return [...hits.values()].sort((a, b) => b.matchedTokens - a.matchedTokens);\n}\n\nexport async function vectorSearch(\n queryEmbedding: number[],\n limit: number,\n source?: string,\n): Promise<{ id: string; data: Record<string, unknown>; distance: number }[]> {\n const col = chunksCol();\n\n let query = col as FirebaseFirestore.Query;\n if (source) {\n query = query.where(\"source\", \"==\", source);\n }\n\n const snapshot = await query\n .findNearest({\n vectorField: \"embedding\",\n queryVector: FieldValue.vector(queryEmbedding),\n limit,\n distanceMeasure: \"COSINE\",\n distanceResultField: \"_distance\",\n })\n .get();\n\n return snapshot.docs.map((doc) => {\n const data = doc.data();\n const distance = (data._distance as number) ?? 0;\n delete data._distance;\n delete data.embedding;\n return { id: doc.id, data, distance };\n });\n}\n", "import { buildEmbedText } from \"./chunker.js\";\nimport { contentHash } from \"./tokens.js\";\nimport type { Chunk } from \"./types.js\";\nimport { embedTexts } from \"./embedder.js\";\nimport {\n storeChunks,\n updateSourceMeta,\n deleteChunksByIds,\n getSourceChunkHashes,\n} from \"./store.js\";\n\n// Embed-and-store window. Each window is embedded then written before the\n// next starts, so heap stays flat regardless of source size, and an\n// interrupted refresh resumes for free: stored chunks hash-match next run.\nconst EMBED_WINDOW = 1000;\n\n// If URL discovery silently finds only part of a site (collapsed nav, broken\n// sitemap, missing urls.json), the sync would mass-delete live chunks.\n// Refuse implausible shrinkage; --allow-shrink overrides when expected.\nconst SHRINK_GUARD_MIN_EXISTING = 200;\nconst SHRINK_GUARD_MAX_DELETE_RATIO = 0.5;\n\n// The inverse failure: discovery escaping the doc tree (nav matching a whole\n// support portal, locale trees, forums) explodes the chunk count and burns\n// hours of embedding quota on junk. Refuse implausible growth too.\nconst GROWTH_GUARD_MAX_RATIO = 5;\n\nexport async function syncChunks(\n sourceName: string,\n allChunks: Chunk[],\n urlCount: number,\n version: string | undefined,\n allowShrink = false,\n): Promise<void> {\n console.log(\" Comparing with Firestore...\");\n const existing = await getSourceChunkHashes(sourceName);\n const currentIds = new Set(allChunks.map((c) => c.id));\n const toDelete = [...existing.keys()].filter((id) => !currentIds.has(id));\n const toEmbed = allChunks.filter(\n (chunk) => existing.get(chunk.id) !== contentHash(buildEmbedText(chunk)),\n );\n\n console.log(\n ` Sync: ${toEmbed.length} to embed, ${allChunks.length - toEmbed.length} unchanged, ${toDelete.length} to delete.`,\n );\n\n if (\n !allowShrink &&\n existing.size >= SHRINK_GUARD_MIN_EXISTING &&\n toDelete.length > existing.size * SHRINK_GUARD_MAX_DELETE_RATIO\n ) {\n throw new Error(\n `Refusing to delete ${toDelete.length} of ${existing.size} stored chunks for \"${sourceName}\": ` +\n `URL discovery likely found only part of the site. Verify the sitemap/nav/urls.json, ` +\n `then re-run with --allow-shrink if the change is expected, or --full to rebuild from scratch.`,\n );\n }\n\n if (\n !allowShrink &&\n existing.size >= SHRINK_GUARD_MIN_EXISTING &&\n allChunks.length > existing.size * GROWTH_GUARD_MAX_RATIO\n ) {\n throw new Error(\n `Refusing to grow \"${sourceName}\" from ${existing.size} to ${allChunks.length} chunks (>${GROWTH_GUARD_MAX_RATIO}x): ` +\n `URL discovery may have escaped the doc tree. Verify include_patterns/nav_selector, ` +\n `then re-run with --allow-shrink if the growth is real.`,\n );\n }\n\n for (let i = 0; i < toEmbed.length; i += EMBED_WINDOW) {\n const window = toEmbed.slice(i, i + EMBED_WINDOW);\n const embeddings = await embedTexts(window.map((c) => buildEmbedText(c)), {\n onProgress: (done) => {\n console.log(` [${i + done}/${toEmbed.length}] embedded`);\n },\n });\n await storeChunks(window, embeddings, (cur) => {\n console.log(` [${i + cur}/${toEmbed.length}] stored`);\n });\n }\n\n // Delete after all new content is live: a crash mid-sync must never leave\n // the source temporarily missing chunks that readers could still need.\n if (toDelete.length > 0) {\n await deleteChunksByIds(toDelete, (cur, total) => {\n console.log(` [${cur}/${total}] deleted`);\n });\n }\n\n await updateSourceMeta(sourceName, allChunks.length, urlCount, version);\n console.log(` Done. ${allChunks.length} chunks live for \"${sourceName}\".`);\n}\n", "import { embedText } from \"./embedder.js\";\nimport { vectorSearch, tokenSearch } from \"./store.js\";\nimport { rerank, rerankDocText, RERANK_POOL_SIZE } from \"./reranker.js\";\nimport { extractQueryTokens } from \"./tokens.js\";\nimport type { SearchResult } from \"./types.js\";\n\nexport type { SearchResult };\n\nconst DEFAULT_CANDIDATES = 50;\nconst RRF_K = 60;\n\nfunction hasReranker(): boolean {\n return !!process.env.RERANKER_URL;\n}\n\ninterface Candidate {\n id: string;\n data: Record<string, unknown>;\n fusedScore: number;\n}\n\nfunction contextualText(data: Record<string, unknown>): string {\n return rerankDocText(\n data.title as string,\n data.heading_path as string[],\n data.content as string,\n );\n}\n\nfunction toSearchResult(candidate: Candidate, relevance: number): SearchResult {\n const data = candidate.data;\n return {\n id: candidate.id,\n source: data.source as string,\n url: data.url as string,\n title: data.title as string,\n heading_path: data.heading_path as string[],\n content: data.content as string,\n relevance_score: relevance,\n };\n}\n\n// Hybrid retrieval: dense vector candidates fused with exact-identifier\n// token hits via reciprocal rank fusion. The lexical leg recovers identifiers\n// (max_connections, ExportContext) that sit outside the vector pool because\n// their chunk's embedding is dominated by sibling identifiers.\nexport async function search(\n query: string,\n options: { source?: string; candidates?: number; topN?: number } = {},\n): Promise<SearchResult[]> {\n const { source, candidates = DEFAULT_CANDIDATES, topN = 5 } = options;\n\n const queryEmbedding = await embedText(query);\n const [vectorResults, lexicalHits] = await Promise.all([\n vectorSearch(queryEmbedding, candidates, source),\n tokenSearch(extractQueryTokens(query), source),\n ]);\n\n const pool = new Map<string, Candidate>();\n\n vectorResults.forEach((result, rank) => {\n pool.set(result.id, {\n id: result.id,\n data: result.data,\n fusedScore: 1 / (RRF_K + rank + 1),\n });\n });\n\n lexicalHits.forEach((hit, rank) => {\n const lexicalScore = 1 / (RRF_K + rank + 1);\n const existing = pool.get(hit.id);\n if (existing) {\n existing.fusedScore += lexicalScore;\n return;\n }\n pool.set(hit.id, {\n id: hit.id,\n data: hit.data,\n fusedScore: lexicalScore,\n });\n });\n\n const fused = [...pool.values()].sort((a, b) => b.fusedScore - a.fusedScore);\n if (fused.length === 0) return [];\n\n if (hasReranker()) {\n const rerankPool = fused.slice(0, RERANK_POOL_SIZE);\n const documents = rerankPool.map((c) => contextualText(c.data));\n const reranked = await rerank(query, documents, topN);\n return reranked.map((r) => toSearchResult(rerankPool[r.index], r.relevance_score));\n }\n\n // Without a reranker, the displayed score is fusion strength relative to\n // the top result (always 1.0): monotonic with rank by construction, so the\n // ordering never contradicts the scores shown.\n const maxFused = fused[0].fusedScore;\n return fused.slice(0, topN).map((candidate) =>\n toSearchResult(candidate, candidate.fusedScore / maxFused),\n );\n}\n", "import { randomBytes, createHash } from \"node:crypto\";\nimport {\n getFirestore,\n type Firestore,\n} from \"firebase-admin/firestore\";\nimport { initializeApp, applicationDefault, getApps } from \"firebase-admin/app\";\nimport { bold } from \"./format.js\";\n\nlet db: Firestore | undefined;\n\nfunction getDb(): Firestore {\n if (!db) {\n if (getApps().length === 0) {\n initializeApp({ credential: applicationDefault() });\n }\n db = getFirestore();\n }\n return db;\n}\n\nfunction hashKey(key: string): string {\n return createHash(\"sha256\").update(key).digest(\"hex\");\n}\n\nfunction apiKeysCol() {\n return getDb().collection(\"grimoire_api_keys\");\n}\n\nexport async function createApiKey(name: string): Promise<string> {\n const raw = `grim_${randomBytes(32).toString(\"base64url\")}`;\n const hash = hashKey(raw);\n\n await apiKeysCol().doc(hash).set({\n name,\n created_at: new Date().toISOString(),\n last_used_at: null,\n });\n\n return raw;\n}\n\nexport async function listApiKeys(): Promise<void> {\n const snapshot = await apiKeysCol().get();\n\n if (snapshot.empty) {\n console.log(\"No API keys found.\");\n return;\n }\n\n console.log(\"\\nAPI Keys:\\n\");\n for (const doc of snapshot.docs) {\n const data = doc.data();\n const lastUsed = data.last_used_at ?? \"never\";\n console.log(` ${bold(data.name)}`);\n console.log(` Created: ${data.created_at}`);\n console.log(` Last used: ${lastUsed}`);\n }\n}\n\nexport async function deleteApiKey(name: string): Promise<void> {\n const snapshot = await apiKeysCol().where(\"name\", \"==\", name).get();\n\n if (snapshot.empty) {\n throw new Error(`No API key found with name \"${name}\".`);\n }\n\n for (const doc of snapshot.docs) {\n await doc.ref.delete();\n }\n\n console.log(`API key \"${name}\" deleted.`);\n}\n\nexport async function cmdApiKey(): Promise<void> {\n const subcommand = process.argv[3];\n const arg = process.argv[4];\n\n if (subcommand === \"create\") {\n if (!arg) {\n console.error(\"Usage: grimoire apikey create <name>\");\n process.exit(1);\n }\n const key = await createApiKey(arg);\n console.log(`\\nAPI key created for \"${arg}\":\\n`);\n console.log(` ${key}\\n`);\n console.log(\"Save this key \u2014 it will not be shown again.\");\n } else if (subcommand === \"list\") {\n await listApiKeys();\n } else if (subcommand === \"delete\") {\n if (!arg) {\n console.error(\"Usage: grimoire apikey delete <name>\");\n process.exit(1);\n }\n await deleteApiKey(arg);\n } else {\n console.error(\"Usage: grimoire apikey <create|list|delete> [name]\");\n process.exit(1);\n }\n}\n", "import { writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { slugifyUrl } from \"./slug.js\";\nimport { buildFrontmatter, type ConvertedPage } from \"./converter.js\";\n\ninterface LlmsPage {\n title: string;\n url: string;\n markdown: string;\n}\n\nconst BOILERPLATE_PATTERNS = [\n /^\\[Skip to content\\]\\([^)]*\\)\\s*$/gm,\n /^Was this helpful\\?\\s*$/gm,\n /^YesNo\\s*$/gm,\n /^\\[ Edit page \\]\\([^)]+\\) \\[ Report issue \\]\\([^)]+\\)\\s*$/gm,\n /^Copy page\\s*$/gm,\n /^```json\\n\\{\"@context\":\"https:\\/\\/schema\\.org\",\"@type\":\"BreadcrumbList\"[^`]*```\\s*$/gm,\n];\n\nfunction splitPages(content: string): LlmsPage[] {\n const pages: LlmsPage[] = [];\n const frontmatterPattern = /^---\\ntitle: (.+)\\n/gm;\n const boundaries: { index: number; title: string }[] = [];\n\n let match;\n while ((match = frontmatterPattern.exec(content)) !== null) {\n boundaries.push({ index: match.index, title: match[1] });\n }\n\n for (let i = 0; i < boundaries.length; i++) {\n const start = boundaries[i].index;\n const end = i + 1 < boundaries.length ? boundaries[i + 1].index : content.length;\n const raw = content.slice(start, end).trimEnd();\n\n const url = extractUrl(raw);\n if (!url) continue;\n\n const bodyStart = raw.indexOf(\"---\", 3);\n if (bodyStart === -1) continue;\n const body = raw.slice(raw.indexOf(\"\\n\", bodyStart) + 1);\n\n let cleaned = body;\n for (const pattern of BOILERPLATE_PATTERNS) {\n cleaned = cleaned.replace(pattern, \"\");\n }\n cleaned = cleaned.replace(/\\n{3,}/g, \"\\n\\n\").trim();\n\n if (!cleaned) continue;\n\n pages.push({\n title: boundaries[i].title,\n url,\n markdown: cleaned,\n });\n }\n\n return pages;\n}\n\nfunction extractUrl(pageContent: string): string | null {\n const match = pageContent.match(\n /```json\\n\\{\"@context\":\"https:\\/\\/schema\\.org\",\"@type\":\"BreadcrumbList\",\"itemListElement\":\\[(.+?)\\]\\}\\n```/,\n );\n if (!match) return null;\n\n const items = JSON.parse(`[${match[1]}]`) as { item?: { \"@id\": string } }[];\n const last = items[items.length - 1];\n if (!last?.item?.[\"@id\"]) return null;\n\n return `https://developers.cloudflare.com${last.item[\"@id\"]}`;\n}\n\nexport async function ingestLlmsFull(\n llmsFullUrl: string,\n sourceName: string,\n baseUrl: string,\n dataDir: string,\n onProgress?: (current: number, total: number) => void,\n): Promise<ConvertedPage[]> {\n const response = await fetch(llmsFullUrl);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${llmsFullUrl}: ${response.status} ${response.statusText}`);\n }\n const content = await response.text();\n\n const pages = splitPages(content);\n\n const mdDir = join(dataDir, \"markdown\", sourceName);\n await mkdir(mdDir, { recursive: true });\n\n const results: ConvertedPage[] = [];\n for (let i = 0; i < pages.length; i++) {\n const page = pages[i];\n const frontmatter = buildFrontmatter(sourceName, page.url, page.title);\n const fullMarkdown = `${frontmatter}\\n\\n${page.markdown}`;\n const slug = slugifyUrl(page.url);\n await writeFile(join(mdDir, `${slug}.md`), fullMarkdown, \"utf-8\");\n\n results.push({\n source: sourceName,\n url: page.url,\n title: page.title,\n markdown: fullMarkdown,\n });\n\n if (onProgress && ((i + 1) % 100 === 0 || i + 1 === pages.length)) {\n onProgress(i + 1, pages.length);\n }\n }\n\n return results;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["cell", "tables", "readFile", "writeFile", "join", "resolve", "readFile", "writeFile", "mkdir", "join", "DEFAULT_CONCURRENCY", "join", "mkdir", "readFile", "writeFile", "fenceMatch", "resolve", "BATCH_SIZE", "MAX_RETRIES", "sleep", "resolve", "createHash", "getFirestore", "initializeApp", "applicationDefault", "getApps", "db", "getDb", "getApps", "initializeApp", "applicationDefault", "getFirestore", "createHash", "writeFile", "mkdir", "join", "join", "mkdir", "writeFile", "join", "resolve", "readFile", "writeFile", "f"]
7
+ }
package/dist/cli.js CHANGED
@@ -304,12 +304,13 @@ FLAGS
304
304
  is configured. No effect when none is configured.
305
305
 
306
306
  RELEVANCE SCORES
307
- Range 0-1 (higher = better). >0.85 strong match, 0.6-0.85 relevant,
308
- <0.6 usually too weak to cite. "No results found." + exit 0 = clean miss.
309
- Results are ranked by hybrid vector + exact-identifier match; the score
310
- shown is vector similarity, so an exact identifier hit can rank first
311
- with a modest score. With RERANKER_URL set, results are reranked locally
312
- and the score is the reranker's relevance instead.
307
+ Range 0-1 (higher = better), always descending with rank.
308
+ "No results found." + exit 0 = clean miss.
309
+ Without a reranker the score is hybrid-fusion strength relative to the
310
+ top result (top result is always 1.0); judge by rank and content, not by
311
+ absolute score. With RERANKER_URL set the score is the local reranker's
312
+ relevance: >0.85 strong match, 0.6-0.85 relevant, <0.6 usually too weak
313
+ to cite.
313
314
 
314
315
  MANAGEMENT
315
316
  grimoire list [--names] Show indexed sources
@@ -440,7 +441,7 @@ async function main() {
440
441
  await cmdInit();
441
442
  return;
442
443
  }
443
- const { ADMIN_COMMANDS } = await import("./admin-4C3QNVQE.js");
444
+ const { ADMIN_COMMANDS } = await import("./admin-MA5SI5CH.js");
444
445
  const handler = ADMIN_COMMANDS[command];
445
446
  if (!handler) {
446
447
  console.error(`Unknown command: ${command}. Run "grimoire --help" for usage.`);
package/dist/cli.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/cli.ts", "../src/consumer-config.ts", "../src/consumer.ts"],
4
- "sourcesContent": ["import { parseArgs } from \"node:util\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { detectConsumerMode, resolveConsumerConfig, cmdInit, cmdRerank } from \"./consumer-config.js\";\nimport { cmdConsumerSearch, cmdConsumerList, cmdConsumerStats } from \"./consumer.js\";\n\nconst PROJECT_ROOT = resolve(import.meta.dirname, \"..\");\n\nconst envPath = join(PROJECT_ROOT, \".env\");\nif (existsSync(envPath)) {\n for (const line of readFileSync(envPath, \"utf-8\").split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n const key = trimmed.slice(0, eqIndex);\n const value = trimmed.slice(eqIndex + 1);\n if (!process.env[key]) {\n process.env[key] = value;\n }\n }\n}\n\nconst ADMIN_ONLY_COMMANDS = [\"add\", \"refresh\", \"delete\", \"scrape-urls\", \"export\", \"apikey\"];\n\nasync function cmdUpdate(): Promise<void> {\n const { execSync } = await import(\"node:child_process\");\n const pkg = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n console.log(`Current version: ${pkg.version}`);\n console.log(\"Checking for updates...\");\n execSync(\"npm install -g @astrofoundry/grimoire@latest\", { stdio: \"inherit\" });\n const updated = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n if (updated.version === pkg.version) {\n console.log(\"Already on the latest version.\");\n } else {\n console.log(`Updated to ${updated.version}.`);\n }\n}\n\nfunction showHelp(isConsumer: boolean): void {\n if (isConsumer) {\n console.log(`\ngrimoire - Documentation RAG\n\nUSAGE\n grimoire search \"<query>\" [--source <name>] [--top <n>] [--candidates <n>] [--compact] [--no-rerank]\n\nQUERY TIPS\n - Use the library's own terminology (\"Firestore batched writes\"\n not \"how do I do multi-write in firestore\")\n - Exact identifiers (max_connections, ExportContext, snake_case,\n dotted.paths, camelCase) are matched literally - include them verbatim\n - Scope with --source when you know the area\n - Rephrase if first search misses\n - Prefer --compact for scanning; omit for full snippets\n - Cite the URL in your answer when precision matters\n\nEXAMPLES\n grimoire search \"Firestore batched writes\" --source firebase-firestore --compact\n grimoire search \"react server components\" --top 3\n grimoire search \"mysql max_connections default\" --source gcp-cloud-sql\n grimoire list --names\n\nFLAGS\n --source <name> Scope to one indexed source. Run \\`grimoire list --names\\`\n for the full list (sources are added regularly).\n --top <n> Max results. Default: 5.\n --candidates <n> Retrieval pool size before final ranking. Default: 50,\n max: 200. Raise for broad/ambiguous queries.\n --compact One line per result: score | source | title | heading | url\n Default (non-compact): multi-line block with title, URL,\n heading path, and content snippet.\n --no-rerank Skip local reranking for this search even if a reranker\n is configured. No effect when none is configured.\n\nRELEVANCE SCORES\n Range 0-1 (higher = better). >0.85 strong match, 0.6-0.85 relevant,\n <0.6 usually too weak to cite. \"No results found.\" + exit 0 = clean miss.\n Results are ranked by hybrid vector + exact-identifier match; the score\n shown is vector similarity, so an exact identifier hit can rank first\n with a modest score. With RERANKER_URL set, results are reranked locally\n and the score is the reranker's relevance instead.\n\nMANAGEMENT\n grimoire list [--names] Show indexed sources\n grimoire stats Index statistics\n grimoire init Configure API connection (first-time setup)\n grimoire rerank Show reranker status (env, config, effective)\n grimoire rerank <url> Enable local reranking (saved to config)\n grimoire rerank off Disable local reranking (removes from config)\n grimoire update Update grimoire itself\n grimoire --version Print CLI version\n\nENVIRONMENT\n GRIMOIRE_API_URL API endpoint URL\n GRIMOIRE_API_KEY API key\n RERANKER_URL Optional. Local-network reranker endpoint; when\n set, search fetches a 50-result pool and reranks\n client-side. Overrides \"rerankerUrl\" in\n ~/.grimoire/config.json. If the reranker is\n unreachable, search warns on stderr and falls\n back to the API ranking after a 15s timeout.\n`);\n } else {\n console.log(`\ngrimoire - Documentation RAG System (admin)\n\nCommands:\n add <name> --url <url> Add a new documentation source\n refresh <source> Scrape, convert, then sync: only chunks\n whose content changed are re-embedded and\n written; removed chunks are deleted\n refresh <source> --from-html Re-convert from cached HTML, then sync\n refresh <source> --from-markdown Re-chunk from cached markdown, then sync\n refresh <source> --full Purge chunks + caches, re-scrape from scratch\n refresh <source> --skip-store Dry run: scrape/convert/chunk only,\n no embedding, no Firestore writes\n refresh <source> --allow-shrink Override the shrink/growth guards when a\n large chunk-count change is expected\n refresh --all Refresh all sources\n delete <source> Delete source and all its chunks\n scrape-urls <source> Fetch missing pages from urls.json\n update Update grimoire to the latest version\n search \"<query>\" [--source <n>] [--top <n>] [--candidates <n>] [--compact]\n Hybrid search (vector + exact identifiers)\n list [--names] List all sources\n stats Show chunk/source statistics\n export <source> Export source as JSON\n apikey create <name> Generate an API key\n apikey list List API keys\n apikey delete <name> Delete an API key\n`);\n }\n}\n\nasync function main(): Promise<void> {\n const command = process.argv[2];\n\n if (command === \"--version\" || command === \"-v\") {\n const pkg = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n console.log(pkg.version);\n process.exit(0);\n }\n\n const isConsumer = await detectConsumerMode();\n\n if (!command || command === \"--help\" || command === \"-h\") {\n showHelp(isConsumer);\n process.exit(0);\n }\n\n if (command === \"update\") {\n await cmdUpdate();\n return;\n }\n\n if (isConsumer) {\n if (command === \"init\") {\n await cmdInit();\n return;\n }\n\n if (command === \"rerank\") {\n await cmdRerank(process.argv[3]);\n return;\n }\n\n if (ADMIN_ONLY_COMMANDS.includes(command)) {\n console.error(`The '${command}' command is only available in admin mode.`);\n process.exit(1);\n }\n\n const config = await resolveConsumerConfig().catch(() => {\n console.error(\"Grimoire is not configured yet. Run 'grimoire init' to set up your API connection.\");\n process.exit(1);\n });\n\n if (command === \"search\") {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n source: { type: \"string\" },\n top: { type: \"string\" },\n candidates: { type: \"string\" },\n compact: { type: \"boolean\", default: false },\n \"no-rerank\": { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n });\n const query = args.positionals[0];\n if (!query) {\n console.error(\"Usage: grimoire search \\\"<query>\\\" [--source <name>] [--top <n>] [--candidates <n>] [--compact] [--no-rerank]\");\n process.exit(1);\n }\n const topN = args.values.top ? parseInt(args.values.top, 10) : undefined;\n const candidates = args.values.candidates ? parseInt(args.values.candidates, 10) : undefined;\n await cmdConsumerSearch(config, query, {\n source: args.values.source,\n topN,\n candidates,\n compact: args.values.compact,\n noRerank: args.values[\"no-rerank\"],\n });\n } else if (command === \"list\") {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: { names: { type: \"boolean\", default: false } },\n allowPositionals: true,\n });\n await cmdConsumerList(config, { names: args.values.names });\n } else if (command === \"stats\") {\n await cmdConsumerStats(config);\n } else {\n console.error(`Unknown command: ${command}. Run \"grimoire --help\" for usage.`);\n process.exit(1);\n }\n return;\n }\n\n if (command === \"init\") {\n await cmdInit();\n return;\n }\n\n const { ADMIN_COMMANDS } = await import(\"./admin.js\");\n const handler = ADMIN_COMMANDS[command];\n if (!handler) {\n console.error(`Unknown command: ${command}. Run \"grimoire --help\" for usage.`);\n process.exit(1);\n }\n\n await handler();\n}\n\nconst GCP_AUTH_PATTERNS = [\n \"Unable to detect a Project Id\",\n \"Could not load the default credentials\",\n \"invalid_grant\",\n \"invalid_rapt\",\n \"UNAUTHENTICATED\",\n \"Getting metadata from plugin failed\",\n];\n\nmain().catch((err: Error) => {\n const msg = err.message ?? String(err);\n if (GCP_AUTH_PATTERNS.some((p) => msg.includes(p))) {\n console.error(\"Google Cloud authentication failed. Re-authenticate with:\\n\\n gcloud auth application-default login\\n\");\n } else {\n console.error(`Error: ${msg}`);\n }\n process.exit(1);\n});\n", "import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { createInterface } from \"node:readline\";\n\nconst CONFIG_DIR = join(homedir(), \".grimoire\");\nconst CONFIG_FILE = join(CONFIG_DIR, \"config.json\");\n\nexport interface ConsumerConfig {\n apiUrl: string;\n apiKey: string;\n rerankerUrl?: string;\n}\n\nexport async function loadConsumerConfig(): Promise<ConsumerConfig | null> {\n const raw = await readFile(CONFIG_FILE, \"utf-8\").catch(() => null);\n if (!raw) return null;\n const data = JSON.parse(raw);\n if (typeof data.apiUrl === \"string\" && typeof data.apiKey === \"string\") {\n return {\n apiUrl: data.apiUrl,\n apiKey: data.apiKey,\n ...(typeof data.rerankerUrl === \"string\" && data.rerankerUrl\n ? { rerankerUrl: data.rerankerUrl }\n : {}),\n };\n }\n return null;\n}\n\nexport async function saveConsumerConfig(config: ConsumerConfig): Promise<void> {\n await mkdir(CONFIG_DIR, { recursive: true });\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// RERANKER_URL env always wins over the config file: the reranker is\n// network-specific, so the shell is the natural per-machine override.\nexport async function resolveConsumerConfig(): Promise<ConsumerConfig> {\n const envUrl = process.env.GRIMOIRE_API_URL;\n const envKey = process.env.GRIMOIRE_API_KEY;\n const envReranker = process.env.RERANKER_URL;\n\n if (envUrl && envKey) {\n return {\n apiUrl: envUrl,\n apiKey: envKey,\n ...(envReranker ? { rerankerUrl: envReranker } : {}),\n };\n }\n\n const fileConfig = await loadConsumerConfig();\n if (fileConfig) {\n return envReranker ? { ...fileConfig, rerankerUrl: envReranker } : fileConfig;\n }\n\n throw new Error(\"Grimoire is not configured. Run 'grimoire init' to set up.\");\n}\n\nexport function isConsumerMode(): boolean {\n return !!process.env.GRIMOIRE_API_URL;\n}\n\nexport async function detectConsumerMode(): Promise<boolean> {\n if (process.env.GOOGLE_APPLICATION_CREDENTIALS) return false;\n if (process.env.GRIMOIRE_API_URL) return true;\n const config = await loadConsumerConfig();\n return config !== null;\n}\n\nasync function setConfigRerankerUrl(url: string | undefined): Promise<void> {\n const existing = await loadConsumerConfig();\n if (!existing) {\n throw new Error(\"Grimoire is not configured. Run 'grimoire init' first.\");\n }\n await saveConsumerConfig({\n apiUrl: existing.apiUrl,\n apiKey: existing.apiKey,\n ...(url ? { rerankerUrl: url } : {}),\n });\n}\n\nexport async function cmdRerank(arg?: string): Promise<void> {\n if (!arg) {\n const envUrl = process.env.RERANKER_URL;\n const fileConfig = await loadConsumerConfig();\n const effective = envUrl ?? fileConfig?.rerankerUrl;\n console.log(`Env RERANKER_URL: ${envUrl ?? \"(not set)\"}`);\n console.log(`Config rerankerUrl: ${fileConfig?.rerankerUrl ?? \"(not set)\"}`);\n console.log(`Effective: ${effective ?? \"off\"}`);\n return;\n }\n\n if (arg === \"off\") {\n await setConfigRerankerUrl(undefined);\n console.log(`Reranker removed from ${CONFIG_FILE}.`);\n if (process.env.RERANKER_URL) {\n console.log(\"Note: RERANKER_URL is set in your environment and still takes precedence. Unset it too.\");\n }\n return;\n }\n\n new URL(arg);\n await setConfigRerankerUrl(arg);\n console.log(`Reranker set to ${arg} in ${CONFIG_FILE}.`);\n}\n\nexport async function cmdInit(): Promise<void> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (q: string): Promise<string> =>\n new Promise((resolve) => rl.question(q, resolve));\n\n const existing = await loadConsumerConfig();\n\n const apiUrl = await ask(`API URL${existing ? ` [${existing.apiUrl}]` : \"\"}: `);\n const apiKey = await ask(`API Key${existing ? \" [****]\" : \"\"}: `);\n const rerankerUrl = await ask(\n `Reranker URL (optional, local network)${existing?.rerankerUrl ? ` [${existing.rerankerUrl}]` : \"\"}: `,\n );\n\n const resolvedReranker = rerankerUrl.trim() || existing?.rerankerUrl;\n const config: ConsumerConfig = {\n apiUrl: apiUrl.trim() || existing?.apiUrl || \"\",\n apiKey: apiKey.trim() || existing?.apiKey || \"\",\n ...(resolvedReranker ? { rerankerUrl: resolvedReranker } : {}),\n };\n\n rl.close();\n\n if (!config.apiUrl || !config.apiKey) {\n throw new Error(\"Both API URL and API Key are required.\");\n }\n\n await saveConsumerConfig(config);\n console.log(`\\nSaved to ${CONFIG_FILE}`);\n}\n", "import type { SearchResult, SourceMeta } from \"./types.js\";\nimport type { ConsumerConfig } from \"./consumer-config.js\";\nimport { bold, cyan, yellow } from \"./format.js\";\nimport { rerank, rerankDocText, RERANK_POOL_SIZE } from \"./reranker.js\";\n\n// The API ranks by hybrid fusion server-side; when a reranker is reachable\n// (local network), fetch a larger pool and rerank client-side with the same\n// contextual text the admin pipeline uses.\nfunction contextualText(result: SearchResult): string {\n return rerankDocText(result.title, result.heading_path, result.content);\n}\n\nconst RERANK_TIMEOUT_MS = 30_000;\n\n// Reranking is an enhancement, not a dependency: on failure or timeout the\n// API's fused ranking is already in hand, so degrade to it with a warning.\nasync function rerankResults(\n rerankerUrl: string,\n query: string,\n results: SearchResult[],\n topN: number,\n): Promise<SearchResult[]> {\n try {\n const reranked = await rerank(\n query,\n results.map(contextualText),\n topN,\n rerankerUrl,\n RERANK_TIMEOUT_MS,\n );\n return reranked.map((r) => ({\n ...results[r.index],\n relevance_score: r.relevance_score,\n }));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `Warning: reranker at ${rerankerUrl} unavailable (${message}); using API ranking. ` +\n \"Use --no-rerank or 'grimoire rerank off' to silence this.\",\n );\n return results.slice(0, topN);\n }\n}\n\nasync function apiRequest<T>(config: ConsumerConfig, path: string, options?: RequestInit): Promise<T> {\n const url = `${config.apiUrl.replace(/\\/$/, \"\")}${path}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": config.apiKey,\n ...options?.headers,\n },\n });\n } catch {\n throw new Error(`Cannot reach Grimoire API at ${config.apiUrl}. Check your GRIMOIRE_API_URL.`);\n }\n\n if (response.status === 401 || response.status === 403) {\n throw new Error(\"Invalid API key. Check your GRIMOIRE_API_KEY or run 'grimoire init'.\");\n }\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n return response.json() as Promise<T>;\n}\n\nexport async function cmdConsumerSearch(\n config: ConsumerConfig,\n query: string,\n options: { source?: string; topN?: number; candidates?: number; compact?: boolean; noRerank?: boolean },\n): Promise<void> {\n const topN = options.topN ?? 5;\n const rerankerUrl = options.noRerank ? undefined : config.rerankerUrl;\n\n const data = await apiRequest<{ results: SearchResult[] }>(config, \"/search\", {\n method: \"POST\",\n body: JSON.stringify({\n query,\n source: options.source,\n topN: rerankerUrl ? Math.max(topN, RERANK_POOL_SIZE) : topN,\n candidates: options.candidates,\n }),\n });\n\n if (data.results.length === 0) {\n console.log(\"No results found.\");\n return;\n }\n\n if (rerankerUrl) {\n data.results = await rerankResults(rerankerUrl, query, data.results, topN);\n }\n\n if (options.compact) {\n for (const r of data.results) {\n console.log(`${r.relevance_score.toFixed(4)} | ${r.source} | ${r.title} | ${r.heading_path.join(\" > \")} | ${r.url}`);\n }\n return;\n }\n\n for (let i = 0; i < data.results.length; i++) {\n const r = data.results[i];\n console.log(`\\n${bold(`[${i + 1}] ${r.title}`)} (${r.relevance_score.toFixed(4)})`);\n console.log(` ${cyan(r.url)}`);\n console.log(` ${yellow(r.heading_path.join(\" > \"))}`);\n console.log(` ${r.content.replace(/\\n/g, \" \")}`);\n }\n}\n\nexport async function cmdConsumerList(config: ConsumerConfig, options?: { names?: boolean }): Promise<void> {\n const data = await apiRequest<{ sources: SourceMeta[] }>(config, \"/list\");\n\n if (data.sources.length === 0) {\n console.log(\"No sources available.\");\n return;\n }\n\n if (options?.names) {\n for (const s of data.sources) {\n console.log(s.source);\n }\n return;\n }\n\n console.log(\"\\nSources:\\n\");\n for (const s of data.sources) {\n const ver = s.version ? ` v${s.version}` : \"\";\n console.log(` ${bold(s.source)}${ver}`);\n console.log(` ${s.chunk_count} chunks, ${s.url_count} URLs, last refreshed ${s.last_refreshed}`);\n }\n}\n\nexport async function cmdConsumerStats(config: ConsumerConfig): Promise<void> {\n const data = await apiRequest<{ sources: SourceMeta[]; totalChunks: number; totalUrls: number }>(config, \"/stats\");\n\n if (data.sources.length === 0) {\n console.log(\"No sources have been refreshed yet.\");\n return;\n }\n\n console.log(\"\\nSource Statistics:\\n\");\n for (const s of data.sources) {\n const ver = s.version ? ` v${s.version}` : \"\";\n console.log(` ${bold(s.source)}${ver}`);\n console.log(` Chunks: ${s.chunk_count}`);\n console.log(` URLs: ${s.url_count}`);\n console.log(` Last refreshed: ${s.last_refreshed}`);\n }\n\n console.log(`\\n Total: ${data.totalChunks} chunks across ${data.totalUrls} URLs from ${data.sources.length} sources`);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { parseArgs } from \"node:util\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { detectConsumerMode, resolveConsumerConfig, cmdInit, cmdRerank } from \"./consumer-config.js\";\nimport { cmdConsumerSearch, cmdConsumerList, cmdConsumerStats } from \"./consumer.js\";\n\nconst PROJECT_ROOT = resolve(import.meta.dirname, \"..\");\n\nconst envPath = join(PROJECT_ROOT, \".env\");\nif (existsSync(envPath)) {\n for (const line of readFileSync(envPath, \"utf-8\").split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n const key = trimmed.slice(0, eqIndex);\n const value = trimmed.slice(eqIndex + 1);\n if (!process.env[key]) {\n process.env[key] = value;\n }\n }\n}\n\nconst ADMIN_ONLY_COMMANDS = [\"add\", \"refresh\", \"delete\", \"scrape-urls\", \"export\", \"apikey\"];\n\nasync function cmdUpdate(): Promise<void> {\n const { execSync } = await import(\"node:child_process\");\n const pkg = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n console.log(`Current version: ${pkg.version}`);\n console.log(\"Checking for updates...\");\n execSync(\"npm install -g @astrofoundry/grimoire@latest\", { stdio: \"inherit\" });\n const updated = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n if (updated.version === pkg.version) {\n console.log(\"Already on the latest version.\");\n } else {\n console.log(`Updated to ${updated.version}.`);\n }\n}\n\nfunction showHelp(isConsumer: boolean): void {\n if (isConsumer) {\n console.log(`\ngrimoire - Documentation RAG\n\nUSAGE\n grimoire search \"<query>\" [--source <name>] [--top <n>] [--candidates <n>] [--compact] [--no-rerank]\n\nQUERY TIPS\n - Use the library's own terminology (\"Firestore batched writes\"\n not \"how do I do multi-write in firestore\")\n - Exact identifiers (max_connections, ExportContext, snake_case,\n dotted.paths, camelCase) are matched literally - include them verbatim\n - Scope with --source when you know the area\n - Rephrase if first search misses\n - Prefer --compact for scanning; omit for full snippets\n - Cite the URL in your answer when precision matters\n\nEXAMPLES\n grimoire search \"Firestore batched writes\" --source firebase-firestore --compact\n grimoire search \"react server components\" --top 3\n grimoire search \"mysql max_connections default\" --source gcp-cloud-sql\n grimoire list --names\n\nFLAGS\n --source <name> Scope to one indexed source. Run \\`grimoire list --names\\`\n for the full list (sources are added regularly).\n --top <n> Max results. Default: 5.\n --candidates <n> Retrieval pool size before final ranking. Default: 50,\n max: 200. Raise for broad/ambiguous queries.\n --compact One line per result: score | source | title | heading | url\n Default (non-compact): multi-line block with title, URL,\n heading path, and content snippet.\n --no-rerank Skip local reranking for this search even if a reranker\n is configured. No effect when none is configured.\n\nRELEVANCE SCORES\n Range 0-1 (higher = better), always descending with rank.\n \"No results found.\" + exit 0 = clean miss.\n Without a reranker the score is hybrid-fusion strength relative to the\n top result (top result is always 1.0); judge by rank and content, not by\n absolute score. With RERANKER_URL set the score is the local reranker's\n relevance: >0.85 strong match, 0.6-0.85 relevant, <0.6 usually too weak\n to cite.\n\nMANAGEMENT\n grimoire list [--names] Show indexed sources\n grimoire stats Index statistics\n grimoire init Configure API connection (first-time setup)\n grimoire rerank Show reranker status (env, config, effective)\n grimoire rerank <url> Enable local reranking (saved to config)\n grimoire rerank off Disable local reranking (removes from config)\n grimoire update Update grimoire itself\n grimoire --version Print CLI version\n\nENVIRONMENT\n GRIMOIRE_API_URL API endpoint URL\n GRIMOIRE_API_KEY API key\n RERANKER_URL Optional. Local-network reranker endpoint; when\n set, search fetches a 50-result pool and reranks\n client-side. Overrides \"rerankerUrl\" in\n ~/.grimoire/config.json. If the reranker is\n unreachable, search warns on stderr and falls\n back to the API ranking after a 15s timeout.\n`);\n } else {\n console.log(`\ngrimoire - Documentation RAG System (admin)\n\nCommands:\n add <name> --url <url> Add a new documentation source\n refresh <source> Scrape, convert, then sync: only chunks\n whose content changed are re-embedded and\n written; removed chunks are deleted\n refresh <source> --from-html Re-convert from cached HTML, then sync\n refresh <source> --from-markdown Re-chunk from cached markdown, then sync\n refresh <source> --full Purge chunks + caches, re-scrape from scratch\n refresh <source> --skip-store Dry run: scrape/convert/chunk only,\n no embedding, no Firestore writes\n refresh <source> --allow-shrink Override the shrink/growth guards when a\n large chunk-count change is expected\n refresh --all Refresh all sources\n delete <source> Delete source and all its chunks\n scrape-urls <source> Fetch missing pages from urls.json\n update Update grimoire to the latest version\n search \"<query>\" [--source <n>] [--top <n>] [--candidates <n>] [--compact]\n Hybrid search (vector + exact identifiers)\n list [--names] List all sources\n stats Show chunk/source statistics\n export <source> Export source as JSON\n apikey create <name> Generate an API key\n apikey list List API keys\n apikey delete <name> Delete an API key\n`);\n }\n}\n\nasync function main(): Promise<void> {\n const command = process.argv[2];\n\n if (command === \"--version\" || command === \"-v\") {\n const pkg = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n console.log(pkg.version);\n process.exit(0);\n }\n\n const isConsumer = await detectConsumerMode();\n\n if (!command || command === \"--help\" || command === \"-h\") {\n showHelp(isConsumer);\n process.exit(0);\n }\n\n if (command === \"update\") {\n await cmdUpdate();\n return;\n }\n\n if (isConsumer) {\n if (command === \"init\") {\n await cmdInit();\n return;\n }\n\n if (command === \"rerank\") {\n await cmdRerank(process.argv[3]);\n return;\n }\n\n if (ADMIN_ONLY_COMMANDS.includes(command)) {\n console.error(`The '${command}' command is only available in admin mode.`);\n process.exit(1);\n }\n\n const config = await resolveConsumerConfig().catch(() => {\n console.error(\"Grimoire is not configured yet. Run 'grimoire init' to set up your API connection.\");\n process.exit(1);\n });\n\n if (command === \"search\") {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n source: { type: \"string\" },\n top: { type: \"string\" },\n candidates: { type: \"string\" },\n compact: { type: \"boolean\", default: false },\n \"no-rerank\": { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n });\n const query = args.positionals[0];\n if (!query) {\n console.error(\"Usage: grimoire search \\\"<query>\\\" [--source <name>] [--top <n>] [--candidates <n>] [--compact] [--no-rerank]\");\n process.exit(1);\n }\n const topN = args.values.top ? parseInt(args.values.top, 10) : undefined;\n const candidates = args.values.candidates ? parseInt(args.values.candidates, 10) : undefined;\n await cmdConsumerSearch(config, query, {\n source: args.values.source,\n topN,\n candidates,\n compact: args.values.compact,\n noRerank: args.values[\"no-rerank\"],\n });\n } else if (command === \"list\") {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: { names: { type: \"boolean\", default: false } },\n allowPositionals: true,\n });\n await cmdConsumerList(config, { names: args.values.names });\n } else if (command === \"stats\") {\n await cmdConsumerStats(config);\n } else {\n console.error(`Unknown command: ${command}. Run \"grimoire --help\" for usage.`);\n process.exit(1);\n }\n return;\n }\n\n if (command === \"init\") {\n await cmdInit();\n return;\n }\n\n const { ADMIN_COMMANDS } = await import(\"./admin.js\");\n const handler = ADMIN_COMMANDS[command];\n if (!handler) {\n console.error(`Unknown command: ${command}. Run \"grimoire --help\" for usage.`);\n process.exit(1);\n }\n\n await handler();\n}\n\nconst GCP_AUTH_PATTERNS = [\n \"Unable to detect a Project Id\",\n \"Could not load the default credentials\",\n \"invalid_grant\",\n \"invalid_rapt\",\n \"UNAUTHENTICATED\",\n \"Getting metadata from plugin failed\",\n];\n\nmain().catch((err: Error) => {\n const msg = err.message ?? String(err);\n if (GCP_AUTH_PATTERNS.some((p) => msg.includes(p))) {\n console.error(\"Google Cloud authentication failed. Re-authenticate with:\\n\\n gcloud auth application-default login\\n\");\n } else {\n console.error(`Error: ${msg}`);\n }\n process.exit(1);\n});\n", "import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { createInterface } from \"node:readline\";\n\nconst CONFIG_DIR = join(homedir(), \".grimoire\");\nconst CONFIG_FILE = join(CONFIG_DIR, \"config.json\");\n\nexport interface ConsumerConfig {\n apiUrl: string;\n apiKey: string;\n rerankerUrl?: string;\n}\n\nexport async function loadConsumerConfig(): Promise<ConsumerConfig | null> {\n const raw = await readFile(CONFIG_FILE, \"utf-8\").catch(() => null);\n if (!raw) return null;\n const data = JSON.parse(raw);\n if (typeof data.apiUrl === \"string\" && typeof data.apiKey === \"string\") {\n return {\n apiUrl: data.apiUrl,\n apiKey: data.apiKey,\n ...(typeof data.rerankerUrl === \"string\" && data.rerankerUrl\n ? { rerankerUrl: data.rerankerUrl }\n : {}),\n };\n }\n return null;\n}\n\nexport async function saveConsumerConfig(config: ConsumerConfig): Promise<void> {\n await mkdir(CONFIG_DIR, { recursive: true });\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// RERANKER_URL env always wins over the config file: the reranker is\n// network-specific, so the shell is the natural per-machine override.\nexport async function resolveConsumerConfig(): Promise<ConsumerConfig> {\n const envUrl = process.env.GRIMOIRE_API_URL;\n const envKey = process.env.GRIMOIRE_API_KEY;\n const envReranker = process.env.RERANKER_URL;\n\n if (envUrl && envKey) {\n return {\n apiUrl: envUrl,\n apiKey: envKey,\n ...(envReranker ? { rerankerUrl: envReranker } : {}),\n };\n }\n\n const fileConfig = await loadConsumerConfig();\n if (fileConfig) {\n return envReranker ? { ...fileConfig, rerankerUrl: envReranker } : fileConfig;\n }\n\n throw new Error(\"Grimoire is not configured. Run 'grimoire init' to set up.\");\n}\n\nexport function isConsumerMode(): boolean {\n return !!process.env.GRIMOIRE_API_URL;\n}\n\nexport async function detectConsumerMode(): Promise<boolean> {\n if (process.env.GOOGLE_APPLICATION_CREDENTIALS) return false;\n if (process.env.GRIMOIRE_API_URL) return true;\n const config = await loadConsumerConfig();\n return config !== null;\n}\n\nasync function setConfigRerankerUrl(url: string | undefined): Promise<void> {\n const existing = await loadConsumerConfig();\n if (!existing) {\n throw new Error(\"Grimoire is not configured. Run 'grimoire init' first.\");\n }\n await saveConsumerConfig({\n apiUrl: existing.apiUrl,\n apiKey: existing.apiKey,\n ...(url ? { rerankerUrl: url } : {}),\n });\n}\n\nexport async function cmdRerank(arg?: string): Promise<void> {\n if (!arg) {\n const envUrl = process.env.RERANKER_URL;\n const fileConfig = await loadConsumerConfig();\n const effective = envUrl ?? fileConfig?.rerankerUrl;\n console.log(`Env RERANKER_URL: ${envUrl ?? \"(not set)\"}`);\n console.log(`Config rerankerUrl: ${fileConfig?.rerankerUrl ?? \"(not set)\"}`);\n console.log(`Effective: ${effective ?? \"off\"}`);\n return;\n }\n\n if (arg === \"off\") {\n await setConfigRerankerUrl(undefined);\n console.log(`Reranker removed from ${CONFIG_FILE}.`);\n if (process.env.RERANKER_URL) {\n console.log(\"Note: RERANKER_URL is set in your environment and still takes precedence. Unset it too.\");\n }\n return;\n }\n\n new URL(arg);\n await setConfigRerankerUrl(arg);\n console.log(`Reranker set to ${arg} in ${CONFIG_FILE}.`);\n}\n\nexport async function cmdInit(): Promise<void> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (q: string): Promise<string> =>\n new Promise((resolve) => rl.question(q, resolve));\n\n const existing = await loadConsumerConfig();\n\n const apiUrl = await ask(`API URL${existing ? ` [${existing.apiUrl}]` : \"\"}: `);\n const apiKey = await ask(`API Key${existing ? \" [****]\" : \"\"}: `);\n const rerankerUrl = await ask(\n `Reranker URL (optional, local network)${existing?.rerankerUrl ? ` [${existing.rerankerUrl}]` : \"\"}: `,\n );\n\n const resolvedReranker = rerankerUrl.trim() || existing?.rerankerUrl;\n const config: ConsumerConfig = {\n apiUrl: apiUrl.trim() || existing?.apiUrl || \"\",\n apiKey: apiKey.trim() || existing?.apiKey || \"\",\n ...(resolvedReranker ? { rerankerUrl: resolvedReranker } : {}),\n };\n\n rl.close();\n\n if (!config.apiUrl || !config.apiKey) {\n throw new Error(\"Both API URL and API Key are required.\");\n }\n\n await saveConsumerConfig(config);\n console.log(`\\nSaved to ${CONFIG_FILE}`);\n}\n", "import type { SearchResult, SourceMeta } from \"./types.js\";\nimport type { ConsumerConfig } from \"./consumer-config.js\";\nimport { bold, cyan, yellow } from \"./format.js\";\nimport { rerank, rerankDocText, RERANK_POOL_SIZE } from \"./reranker.js\";\n\n// The API ranks by hybrid fusion server-side; when a reranker is reachable\n// (local network), fetch a larger pool and rerank client-side with the same\n// contextual text the admin pipeline uses.\nfunction contextualText(result: SearchResult): string {\n return rerankDocText(result.title, result.heading_path, result.content);\n}\n\nconst RERANK_TIMEOUT_MS = 30_000;\n\n// Reranking is an enhancement, not a dependency: on failure or timeout the\n// API's fused ranking is already in hand, so degrade to it with a warning.\nasync function rerankResults(\n rerankerUrl: string,\n query: string,\n results: SearchResult[],\n topN: number,\n): Promise<SearchResult[]> {\n try {\n const reranked = await rerank(\n query,\n results.map(contextualText),\n topN,\n rerankerUrl,\n RERANK_TIMEOUT_MS,\n );\n return reranked.map((r) => ({\n ...results[r.index],\n relevance_score: r.relevance_score,\n }));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `Warning: reranker at ${rerankerUrl} unavailable (${message}); using API ranking. ` +\n \"Use --no-rerank or 'grimoire rerank off' to silence this.\",\n );\n return results.slice(0, topN);\n }\n}\n\nasync function apiRequest<T>(config: ConsumerConfig, path: string, options?: RequestInit): Promise<T> {\n const url = `${config.apiUrl.replace(/\\/$/, \"\")}${path}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": config.apiKey,\n ...options?.headers,\n },\n });\n } catch {\n throw new Error(`Cannot reach Grimoire API at ${config.apiUrl}. Check your GRIMOIRE_API_URL.`);\n }\n\n if (response.status === 401 || response.status === 403) {\n throw new Error(\"Invalid API key. Check your GRIMOIRE_API_KEY or run 'grimoire init'.\");\n }\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n return response.json() as Promise<T>;\n}\n\nexport async function cmdConsumerSearch(\n config: ConsumerConfig,\n query: string,\n options: { source?: string; topN?: number; candidates?: number; compact?: boolean; noRerank?: boolean },\n): Promise<void> {\n const topN = options.topN ?? 5;\n const rerankerUrl = options.noRerank ? undefined : config.rerankerUrl;\n\n const data = await apiRequest<{ results: SearchResult[] }>(config, \"/search\", {\n method: \"POST\",\n body: JSON.stringify({\n query,\n source: options.source,\n topN: rerankerUrl ? Math.max(topN, RERANK_POOL_SIZE) : topN,\n candidates: options.candidates,\n }),\n });\n\n if (data.results.length === 0) {\n console.log(\"No results found.\");\n return;\n }\n\n if (rerankerUrl) {\n data.results = await rerankResults(rerankerUrl, query, data.results, topN);\n }\n\n if (options.compact) {\n for (const r of data.results) {\n console.log(`${r.relevance_score.toFixed(4)} | ${r.source} | ${r.title} | ${r.heading_path.join(\" > \")} | ${r.url}`);\n }\n return;\n }\n\n for (let i = 0; i < data.results.length; i++) {\n const r = data.results[i];\n console.log(`\\n${bold(`[${i + 1}] ${r.title}`)} (${r.relevance_score.toFixed(4)})`);\n console.log(` ${cyan(r.url)}`);\n console.log(` ${yellow(r.heading_path.join(\" > \"))}`);\n console.log(` ${r.content.replace(/\\n/g, \" \")}`);\n }\n}\n\nexport async function cmdConsumerList(config: ConsumerConfig, options?: { names?: boolean }): Promise<void> {\n const data = await apiRequest<{ sources: SourceMeta[] }>(config, \"/list\");\n\n if (data.sources.length === 0) {\n console.log(\"No sources available.\");\n return;\n }\n\n if (options?.names) {\n for (const s of data.sources) {\n console.log(s.source);\n }\n return;\n }\n\n console.log(\"\\nSources:\\n\");\n for (const s of data.sources) {\n const ver = s.version ? ` v${s.version}` : \"\";\n console.log(` ${bold(s.source)}${ver}`);\n console.log(` ${s.chunk_count} chunks, ${s.url_count} URLs, last refreshed ${s.last_refreshed}`);\n }\n}\n\nexport async function cmdConsumerStats(config: ConsumerConfig): Promise<void> {\n const data = await apiRequest<{ sources: SourceMeta[]; totalChunks: number; totalUrls: number }>(config, \"/stats\");\n\n if (data.sources.length === 0) {\n console.log(\"No sources have been refreshed yet.\");\n return;\n }\n\n console.log(\"\\nSource Statistics:\\n\");\n for (const s of data.sources) {\n const ver = s.version ? ` v${s.version}` : \"\";\n console.log(` ${bold(s.source)}${ver}`);\n console.log(` Chunks: ${s.chunk_count}`);\n console.log(` URLs: ${s.url_count}`);\n console.log(` Last refreshed: ${s.last_refreshed}`);\n }\n\n console.log(`\\n Total: ${data.totalChunks} chunks across ${data.totalUrls} URLs from ${data.sources.length} sources`);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["join", "resolve", "join"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrofoundry/grimoire",
3
- "version": "3.32.0",
3
+ "version": "3.32.1",
4
4
  "description": "Documentation RAG System",
5
5
  "keywords": [],
6
6
  "author": "",
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../node_modules/.pnpm/@joplin+turndown-plugin-gfm@1.0.67/node_modules/@joplin/turndown-plugin-gfm/lib/turndown-plugin-gfm.cjs.js", "../src/admin.ts", "../src/config.ts", "../src/scraper.ts", "../src/slug.ts", "../src/converter.ts", "../src/chunker.ts", "../src/tokens.ts", "../src/embedder.ts", "../src/store.ts", "../src/search.ts", "../src/apikey.ts", "../src/llms-ingest.ts"],
4
- "sourcesContent": ["'use strict';\n\nObject.defineProperty(exports, '__esModule', { value: true });\n\nvar highlightRegExp = /highlight-(?:text|source)-([a-z0-9]+)/;\n\nfunction highlightedCodeBlock (turndownService) {\n turndownService.addRule('highlightedCodeBlock', {\n filter: function (node) {\n var firstChild = node.firstChild;\n return (\n node.nodeName === 'DIV' &&\n highlightRegExp.test(node.className) &&\n firstChild &&\n firstChild.nodeName === 'PRE'\n )\n },\n replacement: function (content, node, options) {\n var className = node.className || '';\n var language = (className.match(highlightRegExp) || [null, ''])[1];\n\n return (\n '\\n\\n' + options.fence + language + '\\n' +\n node.firstChild.textContent +\n '\\n' + options.fence + '\\n\\n'\n )\n }\n });\n}\n\nfunction strikethrough (turndownService) {\n turndownService.addRule('strikethrough', {\n filter: ['del', 's', 'strike'],\n replacement: function (content) {\n return '~~' + content + '~~'\n }\n });\n}\n\nvar indexOf = Array.prototype.indexOf;\nvar every = Array.prototype.every;\nvar rules = {};\nvar alignMap = { left: ':---', right: '---:', center: ':---:' };\n\nlet isCodeBlock_ = null;\nlet options_ = null;\n\n// We need to cache the result of tableShouldBeSkipped() as it is expensive.\n// Caching it means we went from about 9000 ms for rendering down to 90 ms.\n// Fixes https://github.com/laurent22/joplin/issues/6736\nconst tableShouldBeSkippedCache_ = new WeakMap();\n\nfunction getAlignment(node) {\n return node ? (node.getAttribute('align') || node.style.textAlign || '').toLowerCase() : '';\n}\n\nfunction getBorder(alignment) {\n return alignment ? alignMap[alignment] : '---';\n}\n\nfunction getColumnAlignment(table, columnIndex) {\n var votes = {\n left: 0,\n right: 0,\n center: 0,\n '': 0,\n };\n\n var align = '';\n\n for (var i = 0; i < table.rows.length; ++i) {\n var row = table.rows[i];\n if (columnIndex < row.childNodes.length) {\n var cellAlignment = getAlignment(row.childNodes[columnIndex]);\n ++votes[cellAlignment];\n\n if (votes[cellAlignment] > votes[align]) {\n align = cellAlignment;\n }\n }\n }\n\n return align;\n}\n\nrules.tableCell = {\n filter: ['th', 'td'],\n replacement: function (content, node) {\n if (tableShouldBeSkipped(nodeParentTable(node))) return content;\n return cell(content, node)\n }\n};\n\nrules.tableRow = {\n filter: 'tr',\n replacement: function (content, node) {\n const parentTable = nodeParentTable(node);\n if (tableShouldBeSkipped(parentTable)) return content;\n\n var borderCells = '';\n\n if (isHeadingRow(node)) {\n const colCount = tableColCount(parentTable);\n for (var i = 0; i < colCount; i++) {\n const childNode = i < node.childNodes.length ? node.childNodes[i] : null;\n var border = getBorder(getColumnAlignment(parentTable, i));\n borderCells += cell(border, childNode, i);\n }\n }\n return '\\n' + content + (borderCells ? '\\n' + borderCells : '')\n }\n};\n\nrules.table = {\n filter: function (node, options) {\n return node.nodeName === 'TABLE';\n },\n\n replacement: function (content, node) {\n // Only convert tables that can result in valid Markdown\n // Other tables are kept as HTML using `keep` (see below).\n if (tableShouldBeHtml(node, options_)) {\n let html = node.outerHTML;\n let divParent = nodeParentDiv(node);\n // Make table in HTML format horizontally scrollable by give table a div parent, so the width of the table is limited to the screen width.\n\t // see https://github.com/laurent22/joplin/pull/10161\n // test cases:\n // packages/app-cli/tests/html_to_md/preserve_nested_tables.html\n // packages/app-cli/tests/html_to_md/table_with_blockquote.html\n // packages/app-cli/tests/html_to_md/table_with_code_1.html\n // packages/app-cli/tests/html_to_md/table_with_code_2.html\n // packages/app-cli/tests/html_to_md/table_with_code_3.html\n // packages/app-cli/tests/html_to_md/table_with_heading.html\n // packages/app-cli/tests/html_to_md/table_with_hr.html\n // packages/app-cli/tests/html_to_md/table_with_list.html\n if (divParent === null || !divParent.classList.contains('joplin-table-wrapper')){\n return `\\n\\n<div class=\"joplin-table-wrapper\">${html}</div>\\n\\n`;\n } else {\n return html\n }\n } else {\n if (tableShouldBeSkipped(node)) return content;\n\n // Ensure there are no blank lines\n content = content.replace(/\\n+/g, '\\n');\n\n // If table has no heading, add an empty one so as to get a valid Markdown table\n var secondLine = content.trim().split('\\n');\n if (secondLine.length >= 2) secondLine = secondLine[1];\n var secondLineIsDivider = /\\| :?---/.test(secondLine);\n\n var columnCount = tableColCount(node);\n var emptyHeader = '';\n if (columnCount && !secondLineIsDivider) {\n emptyHeader = '|' + ' |'.repeat(columnCount) + '\\n' + '|';\n for (var columnIndex = 0; columnIndex < columnCount; ++columnIndex) {\n emptyHeader += ' ' + getBorder(getColumnAlignment(node, columnIndex)) + ' |';\n }\n }\n\n const captionNode = node.querySelector ? node.querySelector('caption') : node.caption;\n const captionContent = captionNode ? captionNode.textContent || '' : '';\n const caption = captionContent ? `${captionContent}\\n\\n` : '';\n const tableContent = `${emptyHeader}${content}`.trimStart();\n return `\\n\\n${caption}${tableContent}\\n\\n`;\n }\n }\n};\n\nrules.tableCaption = {\n filter: ['caption'],\n replacement: () => '',\n};\n\nrules.tableColgroup = {\n filter: ['colgroup', 'col'],\n replacement: () => '',\n};\n\nrules.tableSection = {\n filter: ['thead', 'tbody', 'tfoot'],\n replacement: function (content) {\n return content\n }\n};\n\n// A tr is a heading row if:\n// - the parent is a THEAD\n// - or if its the first child of the TABLE or the first TBODY (possibly\n// following a blank THEAD)\n// - and every cell is a TH\nfunction isHeadingRow (tr) {\n var parentNode = tr.parentNode;\n return (\n parentNode.nodeName === 'THEAD' ||\n (\n parentNode.firstChild === tr &&\n (parentNode.nodeName === 'TABLE' || isFirstTbody(parentNode)) &&\n every.call(tr.childNodes, function (n) { return n.nodeName === 'TH' })\n )\n )\n}\n\nfunction isFirstTbody (element) {\n var previousSibling = element.previousSibling;\n return (\n element.nodeName === 'TBODY' && (\n !previousSibling ||\n (\n previousSibling.nodeName === 'THEAD' &&\n /^\\s*$/i.test(previousSibling.textContent)\n )\n )\n )\n}\n\nfunction cell (content, node = null, index = null) {\n if (index === null) index = indexOf.call(node.parentNode.childNodes, node);\n var prefix = ' ';\n if (index === 0) prefix = '| ';\n let filteredContent = content.trim().replace(/\\n\\r/g, '<br>').replace(/\\n/g, \"<br>\");\n filteredContent = filteredContent.replace(/\\|+/g, '\\\\|');\n while (filteredContent.length < 3) filteredContent += ' ';\n if (node) filteredContent = handleColSpan(filteredContent, node, ' ');\n return prefix + filteredContent + ' |'\n}\n\nfunction nodeContainsTable(node) {\n if (!node.childNodes) return false;\n\n for (let i = 0; i < node.childNodes.length; i++) {\n const child = node.childNodes[i];\n if (child.nodeName === 'TABLE') return true;\n if (nodeContainsTable(child)) return true;\n }\n return false;\n}\n\nconst nodeContains = (node, types) => {\n if (!node.childNodes) return false;\n\n for (let i = 0; i < node.childNodes.length; i++) {\n const child = node.childNodes[i];\n if (types === 'code' && isCodeBlock_ && isCodeBlock_(child)) return true;\n if (types.includes(child.nodeName)) return true;\n if (nodeContains(child, types)) return true;\n }\n\n return false;\n};\n\n// Style properties that count as user customization.\n// Excludes TinyMCE/Joplin defaults:\n// - border-collapse: set by default on all tables\n// - width: set on every cell by TinyMCE\n// - text-align: converted to Markdown alignment (:---, :---:, ---:)\n// - height: false positives from TinyMCE defaults\nconst customStyleProperties = [\n 'background-color', 'background',\n 'border-color', 'border',\n 'border-top', 'border-right', 'border-bottom', 'border-left',\n 'border-style', 'border-width',\n 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',\n 'float', 'margin-left', 'margin-right',\n];\n\n// HTML attributes TinyMCE may set instead of CSS.\nconst customAttributeNames = [\n 'bgcolor',\n 'bordercolor',\n 'background',\n];\n\nconst nodeHasCustomStyle = (node) => {\n if (!node || !node.getAttribute) return false;\n const styleAttr = node.getAttribute('style');\n if (!styleAttr) return false;\n // Extract property names from the raw style string\n const properties = styleAttr.split(';')\n .map(s => s.split(':')[0].trim().toLowerCase())\n .filter(s => s.length > 0);\n for (let i = 0; i < properties.length; i++) {\n if (customStyleProperties.includes(properties[i])) return true;\n }\n return false;\n};\n\nconst hasNonDefaultSpacingAttribute = (node, name) => {\n if (!node || !node.getAttribute) return false;\n const value = node.getAttribute(name);\n if (value === null) return false;\n const normalisedValue = `${value}`.trim().toLowerCase();\n if (!normalisedValue) return false;\n if (normalisedValue === '0' || normalisedValue === '0px') return false;\n return true;\n};\n\nconst nodeHasCustomAttributes = (node) => {\n if (!node || !node.getAttribute) return false;\n\n for (let i = 0; i < customAttributeNames.length; i++) {\n const value = node.getAttribute(customAttributeNames[i]);\n if (value !== null && `${value}`.trim() !== '') return true;\n }\n\n if (node.nodeName === 'TABLE') {\n if (hasNonDefaultSpacingAttribute(node, 'cellpadding')) return true;\n if (hasNonDefaultSpacingAttribute(node, 'cellspacing')) return true;\n }\n\n return false;\n};\n\nconst nodeHasCustomFormatting = (node) => {\n return nodeHasCustomStyle(node) || nodeHasCustomAttributes(node);\n};\n\n// Returns true if the table or any of its rows/cells have custom formatting.\nconst tableHasCustomStyles = (tableNode) => {\n if (nodeHasCustomFormatting(tableNode)) return true;\n\n const rows = tableNode.rows;\n if (!rows) return false;\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n if (nodeHasCustomFormatting(row)) return true;\n for (let j = 0; j < row.childNodes.length; j++) {\n const cell = row.childNodes[j];\n if ((cell.nodeName === 'TD' || cell.nodeName === 'TH') && nodeHasCustomFormatting(cell)) {\n return true;\n }\n }\n }\n\n return false;\n};\n\nconst tableShouldBeHtml = (tableNode, options) => {\n const possibleTags = [\n 'UL',\n 'OL',\n 'H1',\n 'H2',\n 'H3',\n 'H4',\n 'H5',\n 'H6',\n 'HR',\n 'BLOCKQUOTE',\n ];\n\n // In general we should leave as HTML tables that include other tables. The\n // exception is with the Web Clipper when we import a web page with a layout\n // that's made of HTML tables. In that case we have this logic of removing the\n // outer table and keeping only the inner ones. For the Rich Text editor\n // however we always want to keep nested tables.\n if (options.preserveNestedTables) possibleTags.push('TABLE');\n\n return nodeContains(tableNode, 'code') ||\n nodeContains(tableNode, possibleTags) ||\n (options.preserveTableStyles && tableHasCustomStyles(tableNode));\n};\n\n// Various conditions under which a table should be skipped - i.e. each cell\n// will be rendered one after the other as if they were paragraphs.\nfunction tableShouldBeSkipped(tableNode) {\n const cached = tableShouldBeSkippedCache_.get(tableNode);\n if (cached !== undefined) return cached;\n\n const result = tableShouldBeSkipped_(tableNode);\n\n tableShouldBeSkippedCache_.set(tableNode, result);\n return result;\n}\n\nfunction tableShouldBeSkipped_(tableNode) {\n if (!tableNode) return true;\n if (!tableNode.rows) return true;\n if (tableNode.rows.length === 1 && tableNode.rows[0].childNodes.length <= 1) return true; // Table with only one cell\n if (nodeContainsTable(tableNode)) return true;\n return false;\n}\n\nfunction nodeParentDiv(node) {\n let parent = node.parentNode;\n while (parent.nodeName !== 'DIV') {\n parent = parent.parentNode;\n if (!parent) return null;\n }\n return parent;\n}\n\nfunction nodeParentTable(node) {\n let parent = node.parentNode;\n while (parent.nodeName !== 'TABLE') {\n parent = parent.parentNode;\n if (!parent) return null;\n }\n return parent;\n}\n\nfunction handleColSpan(content, node, emptyChar) {\n const colspan = node.getAttribute('colspan') || 1;\n for (let i = 1; i < colspan; i++) {\n content += ' | ' + emptyChar.repeat(3);\n }\n return content\n}\n\nfunction tableColCount(node) {\n let maxColCount = 0;\n for (let i = 0; i < node.rows.length; i++) {\n const row = node.rows[i];\n const colCount = row.childNodes.length;\n if (colCount > maxColCount) maxColCount = colCount;\n }\n return maxColCount\n}\n\nfunction tables (turndownService) {\n isCodeBlock_ = turndownService.isCodeBlock;\n options_ = turndownService.options;\n\n turndownService.keep(function (node) {\n if (node.nodeName === 'TABLE' && tableShouldBeHtml(node, turndownService.options)) return true;\n return false;\n });\n for (var key in rules) turndownService.addRule(key, rules[key]);\n}\n\nfunction taskListItems (turndownService) {\n turndownService.addRule('taskListItems', {\n filter: function (node) {\n const parent = node.parentNode;\n const grandparent = parent.parentNode;\n const grandparentIsListItem = !!grandparent && grandparent.nodeName === 'LI';\n return (node.type === 'checkbox' || node.getAttribute('role') === 'checkbox') && (\n parent.nodeName === 'LI'\n // Handles the case where the label contains the checkbox. For example,\n // <label><input ...> ...label text...</label>\n || (parent.nodeName === 'LABEL' && grandparentIsListItem)\n // Handles the case where the input is contained within a <span>\n // <li><span><input ...></span></li>\n || (parent.nodeName === 'SPAN' && grandparentIsListItem)\n )\n },\n replacement: function (content, node) {\n const checked = node.nodeName === 'INPUT' ? node.checked : node.getAttribute('aria-checked') === 'true';\n return (checked ? '[x]' : '[ ]') + ' '\n }\n });\n}\n\nfunction gfm (turndownService) {\n turndownService.use([\n highlightedCodeBlock,\n strikethrough,\n tables,\n taskListItems\n ]);\n}\n\nexports.gfm = gfm;\nexports.highlightedCodeBlock = highlightedCodeBlock;\nexports.strikethrough = strikethrough;\nexports.tables = tables;\nexports.taskListItems = taskListItems;\n", "import { parseArgs } from \"node:util\";\nimport { readFile, writeFile, readdir, rm } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { stringify } from \"yaml\";\nimport { bold, cyan, yellow } from \"./format.js\";\nimport { loadConfig, type SourceConfig } from \"./config.js\";\nimport { scrapeSource, createBrowser } from \"./scraper.js\";\nimport { slugifyUrl } from \"./slug.js\";\nimport { convertSource } from \"./converter.js\";\nimport { chunkMarkdown, buildEmbedText } from \"./chunker.js\";\nimport { contentHash } from \"./tokens.js\";\nimport type { Chunk } from \"./types.js\";\nimport { embedTexts } from \"./embedder.js\";\nimport {\n storeChunks,\n purgeSource,\n updateSourceMeta,\n getAllSourcesMeta,\n getSourceMeta,\n deleteSourceMeta,\n deleteChunksByIds,\n getSourceChunkHashes,\n} from \"./store.js\";\nimport { search } from \"./search.js\";\nimport { cmdApiKey } from \"./apikey.js\";\nimport { ingestLlmsFull } from \"./llms-ingest.js\";\n\nconst PROJECT_ROOT = resolve(import.meta.dirname, \"..\");\nconst CONFIG_PATH = join(PROJECT_ROOT, \"config\", \"sources.yaml\");\nconst DATA_DIR = join(PROJECT_ROOT, \"data\");\n\nfunction prompt(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {\n return new Promise((resolve) => rl.question(question, resolve));\n}\n\nasync function cmdAdd(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n url: { type: \"string\" },\n },\n allowPositionals: true,\n });\n\n const name = args.positionals[0];\n const url = args.values.url;\n\n if (!name || !url) {\n console.error(\"Usage: grimoire add <name> --url <start_url>\");\n process.exit(1);\n }\n\n console.log(\"Scanning page...\\n\");\n\n const browser = await createBrowser();\n const context = await browser.newContext();\n const page = await context.newPage();\n\n try {\n await page.goto(url, { waitUntil: \"domcontentloaded\" });\n\n const navCandidates = await page.evaluate(() => {\n const selectors = [\"nav\", \"[role='navigation']\"];\n const results: { selector: string; label: string; linkCount: number }[] = [];\n const seen = new Set<Element>();\n\n for (const sel of selectors) {\n for (const el of document.querySelectorAll(sel)) {\n if (seen.has(el)) continue;\n seen.add(el);\n const links = el.querySelectorAll(\"a[href]\");\n const label =\n el.getAttribute(\"aria-label\") ||\n el.getAttribute(\"class\") ||\n el.tagName.toLowerCase();\n results.push({\n selector: sel,\n label,\n linkCount: links.length,\n });\n }\n }\n\n return results.sort((a, b) => b.linkCount - a.linkCount);\n });\n\n if (navCandidates.length === 0) {\n console.error(\"No navigation elements found on this page.\");\n process.exit(1);\n }\n\n console.log(\"Navigation candidates:\");\n for (let i = 0; i < navCandidates.length; i++) {\n const c = navCandidates[i];\n console.log(` [${i + 1}] ${c.selector} (${c.label}) \u2014 ${c.linkCount} links`);\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n const navChoice = await prompt(rl, \"\\nSelect navigation: \");\n const navIndex = parseInt(navChoice, 10) - 1;\n if (isNaN(navIndex) || navIndex < 0 || navIndex >= navCandidates.length) {\n console.error(\"Invalid selection.\");\n rl.close();\n process.exit(1);\n }\n\n const selectedNav = navCandidates[navIndex];\n\n const parsedUrl = new URL(url);\n const defaultPattern = parsedUrl.pathname.replace(/\\/$/, \"\");\n\n const allLinks = await page.$$eval(\n `${selectedNav.selector} a[href]`,\n (links, pattern) => {\n return [...new Set(\n (links as HTMLAnchorElement[])\n .map((a) => a.href)\n .filter((h) => h.startsWith(\"http\") && !h.includes(\"?hl=\") && !h.endsWith(\"#\") && h.includes(pattern)),\n )];\n },\n defaultPattern,\n );\n\n console.log(`\\nFound ${allLinks.length} links matching ${defaultPattern}`);\n\n const patternInput = await prompt(rl, `Include pattern [default: ${defaultPattern}]: `);\n const includePattern = patternInput.trim() || defaultPattern;\n\n const excludeInput = await prompt(rl, \"Exclude patterns (comma-separated, optional): \");\n const excludePatterns = excludeInput.trim()\n ? excludeInput.split(\",\").map((p) => p.trim())\n : undefined;\n\n rl.close();\n\n const contentSelector = await page.evaluate(() => {\n if (document.querySelector(\"article\")) return \"article\";\n if (document.querySelector(\"main\")) return \"main\";\n return \"body\";\n });\n\n const removeSelectors = await page.evaluate(() => {\n const candidates = [\n { selector: \"nav\", label: \"nav\" },\n { selector: \"footer\", label: \"footer\" },\n { selector: \"[role='complementary']\", label: \"[role='complementary']\" },\n { selector: \"[role='banner']\", label: \"[role='banner']\" },\n { selector: \".breadcrumbs, .breadcrumb\", label: \".breadcrumbs\" },\n { selector: \".pagination-nav, .pagination\", label: \".pagination-nav\" },\n ];\n return candidates\n .filter((c) => document.querySelector(c.selector) !== null)\n .map((c) => c.label);\n });\n\n if (removeSelectors.length > 0) {\n console.log(`\\nDetected removable elements: ${removeSelectors.join(\", \")}`);\n }\n\n const parsedUrlForSitemap = new URL(url);\n let sitemapUrl: string | undefined;\n try {\n const sitemapCheck = await page.goto(`${parsedUrlForSitemap.origin}/sitemap.xml`, { waitUntil: \"domcontentloaded\", timeout: 10000 });\n if (sitemapCheck && sitemapCheck.status() === 200) {\n const body = await page.textContent(\"body\");\n if (body && (body.includes(\"<urlset\") || body.includes(\"<sitemapindex\"))) {\n sitemapUrl = `${parsedUrlForSitemap.origin}/sitemap.xml`;\n console.log(`\\nSitemap found: ${sitemapUrl}`);\n }\n }\n } catch {\n // No sitemap available\n }\n\n const source: SourceConfig = {\n name: name.replace(/-/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase()),\n start_url: url,\n ...(sitemapUrl ? { sitemap_url: sitemapUrl } : {}),\n nav_selector: selectedNav.selector,\n content_selector: contentSelector,\n include_patterns: [includePattern],\n ...(excludePatterns ? { exclude_patterns: excludePatterns } : {}),\n ...(removeSelectors.length > 0 ? { remove_selectors: removeSelectors } : {}),\n };\n\n let existingContent = \"\";\n try {\n existingContent = await readFile(CONFIG_PATH, \"utf-8\");\n } catch {\n existingContent = \"sources:\\n\";\n }\n\n const newEntry = stringify({ [name]: source }, { indent: 2 });\n const indented = newEntry\n .split(\"\\n\")\n .map((line) => (line.trim() ? ` ${line}` : \"\"))\n .join(\"\\n\");\n\n await writeFile(CONFIG_PATH, existingContent.trimEnd() + \"\\n\" + indented, \"utf-8\");\n\n console.log(`\\nSource \"${name}\" added to config/sources.yaml`);\n console.log(`Run \"grimoire refresh ${name}\" to start scraping.`);\n } finally {\n await browser.close();\n }\n}\n\n// Embed-and-store window. Each window is embedded then written before the\n// next starts, so heap stays flat regardless of source size, and an\n// interrupted refresh resumes for free: stored chunks hash-match next run.\nconst EMBED_WINDOW = 1000;\n\n// If URL discovery silently finds only part of a site (collapsed nav, broken\n// sitemap, missing urls.json), the sync would mass-delete live chunks.\n// Refuse implausible shrinkage; --full purges upfront and bypasses this.\nconst SHRINK_GUARD_MIN_EXISTING = 200;\nconst SHRINK_GUARD_MAX_DELETE_RATIO = 0.5;\n\n// The inverse failure: discovery escaping the doc tree (nav matching a whole\n// support portal, locale trees, forums) explodes the chunk count and burns\n// hours of embedding quota on junk. Refuse implausible growth too.\nconst GROWTH_GUARD_MAX_RATIO = 5;\n\nasync function syncChunks(\n sourceName: string,\n allChunks: Chunk[],\n urlCount: number,\n version: string | undefined,\n allowShrink = false,\n): Promise<void> {\n console.log(\" Comparing with Firestore...\");\n const existing = await getSourceChunkHashes(sourceName);\n const currentIds = new Set(allChunks.map((c) => c.id));\n const toDelete = [...existing.keys()].filter((id) => !currentIds.has(id));\n const toEmbed = allChunks.filter(\n (chunk) => existing.get(chunk.id) !== contentHash(buildEmbedText(chunk)),\n );\n\n console.log(\n ` Sync: ${toEmbed.length} to embed, ${allChunks.length - toEmbed.length} unchanged, ${toDelete.length} to delete.`,\n );\n\n if (\n !allowShrink &&\n existing.size >= SHRINK_GUARD_MIN_EXISTING &&\n toDelete.length > existing.size * SHRINK_GUARD_MAX_DELETE_RATIO\n ) {\n throw new Error(\n `Refusing to delete ${toDelete.length} of ${existing.size} stored chunks for \"${sourceName}\": ` +\n `URL discovery likely found only part of the site. Verify the sitemap/nav/urls.json, ` +\n `then re-run with --allow-shrink if the change is expected, or --full to rebuild from scratch.`,\n );\n }\n\n if (\n !allowShrink &&\n existing.size >= SHRINK_GUARD_MIN_EXISTING &&\n allChunks.length > existing.size * GROWTH_GUARD_MAX_RATIO\n ) {\n throw new Error(\n `Refusing to grow \"${sourceName}\" from ${existing.size} to ${allChunks.length} chunks (>${GROWTH_GUARD_MAX_RATIO}x): ` +\n `URL discovery may have escaped the doc tree. Verify include_patterns/nav_selector, ` +\n `then re-run with --allow-shrink if the growth is real.`,\n );\n }\n\n for (let i = 0; i < toEmbed.length; i += EMBED_WINDOW) {\n const window = toEmbed.slice(i, i + EMBED_WINDOW);\n const embeddings = await embedTexts(window.map((c) => buildEmbedText(c)), {\n onProgress: (done) => {\n console.log(` [${i + done}/${toEmbed.length}] embedded`);\n },\n });\n await storeChunks(window, embeddings, (cur) => {\n console.log(` [${i + cur}/${toEmbed.length}] stored`);\n });\n }\n\n // Delete after all new content is live: a crash mid-sync must never leave\n // the source temporarily missing chunks that readers could still need.\n if (toDelete.length > 0) {\n await deleteChunksByIds(toDelete, (cur, total) => {\n console.log(` [${cur}/${total}] deleted`);\n });\n }\n\n await updateSourceMeta(sourceName, allChunks.length, urlCount, version);\n console.log(` Done. ${allChunks.length} chunks live for \"${sourceName}\".`);\n}\n\ninterface RefreshPage {\n url: string;\n title: string;\n markdown: string;\n}\n\n// Cached urls.json/markdown reflect discovery at scrape time; applying\n// exclude_patterns here lets a config change take effect on the next\n// cached-input refresh without a full re-scrape.\nfunction applyExcludePatterns<T>(\n items: T[],\n getUrl: (item: T) => string,\n excludePatterns: string[] | undefined,\n label: string,\n): T[] {\n if (!excludePatterns || excludePatterns.length === 0) return items;\n const kept = items.filter(\n (item) => !excludePatterns.some((pattern) => getUrl(item).includes(pattern)),\n );\n if (kept.length < items.length) {\n console.log(` Excluded ${items.length - kept.length} ${label} via exclude_patterns.`);\n }\n return kept;\n}\n\nasync function readCachedMarkdownPages(mdDir: string): Promise<RefreshPage[]> {\n const mdFiles = await readdir(mdDir).catch(() => [] as string[]);\n const pages: RefreshPage[] = [];\n for (const f of mdFiles.filter((f) => f.endsWith(\".md\"))) {\n const content = await readFile(join(mdDir, f), \"utf-8\");\n const urlMatch = content.match(/^url: \"(.+)\"$/m);\n const titleMatch = content.match(/^title: \"(.+)\"$/m);\n if (!urlMatch) {\n console.warn(` WARNING: ${f} has no url in frontmatter, skipping page.`);\n continue;\n }\n pages.push({\n markdown: content,\n url: urlMatch[1],\n title: titleMatch?.[1] ?? \"Untitled\",\n });\n }\n return pages;\n}\n\nasync function recoverUrlsFromHtml(rawDir: string): Promise<string[]> {\n const urlsJsonPath = join(rawDir, \"urls.json\");\n try {\n return JSON.parse(await readFile(urlsJsonPath, \"utf-8\")) as string[];\n } catch {\n const rawFiles = await readdir(rawDir);\n const urls: string[] = [];\n let skipped = 0;\n for (const f of rawFiles.filter((f) => f.endsWith(\".html\"))) {\n const fileSlug = f.replace(/\\.html$/, \"\");\n const html = await readFile(join(rawDir, f), \"utf-8\");\n const match = html.match(/<link[^>]+rel=\"canonical\"[^>]+href=\"([^\"]+)\"/);\n if (match && slugifyUrl(match[1]) === fileSlug) { urls.push(match[1]); continue; }\n const ogMatch = html.match(/<meta[^>]+property=\"og:url\"[^>]+content=\"([^\"]+)\"/);\n if (ogMatch && slugifyUrl(ogMatch[1]) === fileSlug) { urls.push(ogMatch[1]); continue; }\n console.warn(` WARNING: cannot recover URL for ${f}, skipping page.`);\n skipped++;\n }\n if (skipped > 0) {\n console.warn(` Skipped ${skipped} pages with unrecoverable URLs. Provide urls.json to include them.`);\n }\n return urls;\n }\n}\n\nasync function cmdRefresh(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n full: { type: \"boolean\", default: false },\n all: { type: \"boolean\", default: false },\n concurrency: { type: \"string\" },\n limit: { type: \"string\" },\n \"from-html\": { type: \"boolean\", default: false },\n \"from-markdown\": { type: \"boolean\", default: false },\n \"skip-store\": { type: \"boolean\", default: false },\n \"allow-shrink\": { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n });\n\n const config = await loadConfig(CONFIG_PATH);\n const sourcesToRefresh = args.values.all\n ? Object.keys(config.sources)\n : [args.positionals[0]];\n\n if (!args.values.all && !sourcesToRefresh[0]) {\n console.error(\"Usage: grimoire refresh <source> [--full] [--from-html] [--from-markdown] [--skip-store] [--allow-shrink] [--limit <n>] [--concurrency <n>]\");\n process.exit(1);\n }\n\n const concurrencyOverride = args.values.concurrency ? parseInt(args.values.concurrency, 10) : undefined;\n const urlLimit = args.values.limit ? parseInt(args.values.limit, 10) : undefined;\n\n for (const sourceName of sourcesToRefresh) {\n const source = config.sources[sourceName];\n if (!source) {\n console.error(`Source \"${sourceName}\" not found in config.`);\n process.exit(1);\n }\n\n if (concurrencyOverride) {\n source.concurrency = concurrencyOverride;\n }\n\n const rawDir = join(DATA_DIR, \"raw\", sourceName);\n const mdDir = join(DATA_DIR, \"markdown\", sourceName);\n\n console.log(`\\nRefreshing \"${sourceName}\"...`);\n\n if (args.values.full) {\n console.log(\" Purging existing chunks...\");\n const deleted = await purgeSource(sourceName);\n console.log(` Deleted ${deleted} chunks.`);\n await rm(rawDir, { recursive: true, force: true });\n await rm(mdDir, { recursive: true, force: true });\n }\n\n let pages: RefreshPage[];\n\n if (args.values[\"from-markdown\"]) {\n console.log(\" Reading cached markdown...\");\n pages = await readCachedMarkdownPages(mdDir);\n if (pages.length === 0) {\n console.error(\" No cached markdown found. Run with --from-html first.\");\n process.exit(1);\n }\n pages = applyExcludePatterns(pages, (p) => p.url, source.exclude_patterns, \"cached pages\");\n console.log(` Found ${pages.length} cached pages.`);\n } else if (source.llms_full_url && !args.values[\"from-html\"]) {\n console.log(` Fetching llms-full.txt from ${source.llms_full_url}...`);\n pages = await ingestLlmsFull(\n source.llms_full_url,\n sourceName,\n source.start_url,\n DATA_DIR,\n (cur, total) => {\n console.log(` [${cur}/${total}] pages processed`);\n },\n );\n console.log(` Extracted ${pages.length} pages.`);\n } else {\n let urls: string[];\n if (args.values[\"from-html\"]) {\n console.log(\" Reading URLs from cached HTML...\");\n urls = await recoverUrlsFromHtml(rawDir);\n urls = applyExcludePatterns(urls, (u) => u, source.exclude_patterns, \"cached URLs\");\n console.log(` Found ${urls.length} cached pages.`);\n } else {\n console.log(\" Scraping URLs...\");\n urls = await scrapeSource(source, sourceName, DATA_DIR, (cur, total, url) => {\n console.log(` [${cur}/${total}] ${url}`);\n });\n console.log(` Found ${urls.length} pages.`);\n }\n\n if (urlLimit && urls.length > urlLimit) {\n urls = urls.slice(0, urlLimit);\n console.log(` Limited to ${urlLimit} pages.`);\n }\n\n console.log(\" Converting to markdown...\");\n pages = await convertSource(\n sourceName,\n urls,\n source.content_selector!,\n source.remove_selectors,\n source.remove_text_patterns,\n DATA_DIR,\n source.concurrency,\n (cur, total) => {\n if (cur % 10 === 0 || cur === total) console.log(` [${cur}/${total}] converted`);\n },\n );\n }\n\n console.log(\" Chunking...\");\n const allChunks = pages.flatMap((p) => chunkMarkdown(p.markdown, sourceName, p.url, p.title));\n console.log(` Created ${allChunks.length} chunks.`);\n\n if (args.values[\"skip-store\"]) {\n console.log(` Done. ${allChunks.length} chunks ready (dry run, no embed/store).`);\n continue;\n }\n\n await syncChunks(sourceName, allChunks, pages.length, source.version, args.values[\"allow-shrink\"]);\n }\n}\n\nasync function cmdSearch(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n source: { type: \"string\" },\n top: { type: \"string\" },\n candidates: { type: \"string\" },\n compact: { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n });\n\n const query = args.positionals.join(\" \");\n if (!query) {\n console.error(\"Usage: grimoire search \\\"<query>\\\" [--source <name>] [--top <n>] [--candidates <n>] [--compact]\");\n process.exit(1);\n }\n\n const topN = args.values.top ? parseInt(args.values.top, 10) : undefined;\n const candidates = args.values.candidates ? parseInt(args.values.candidates, 10) : undefined;\n const results = await search(query, { source: args.values.source, topN, candidates });\n\n if (results.length === 0) {\n console.log(\"No results found.\");\n return;\n }\n\n if (args.values.compact) {\n for (const r of results) {\n console.log(`${r.relevance_score.toFixed(4)} | ${r.source} | ${r.title} | ${r.heading_path.join(\" > \")} | ${r.url}`);\n }\n return;\n }\n\n for (let i = 0; i < results.length; i++) {\n const r = results[i];\n console.log(`\\n${bold(`[${i + 1}] ${r.title}`)} (${r.relevance_score.toFixed(4)})`);\n console.log(` ${cyan(r.url)}`);\n console.log(` ${yellow(r.heading_path.join(\" > \"))}`);\n console.log(` ${r.content.replace(/\\n/g, \" \")}`);\n }\n}\n\nasync function cmdList(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n names: { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n });\n\n const metas = await getAllSourcesMeta();\n\n if (metas.length === 0) {\n console.log(\"No sources have been refreshed yet.\");\n return;\n }\n\n if (args.values.names) {\n for (const meta of metas) {\n console.log(meta.source);\n }\n return;\n }\n\n console.log(\"\\nSources:\\n\");\n for (const meta of metas) {\n const ver = meta.version ? ` v${meta.version}` : \"\";\n console.log(` ${bold(meta.source)}${ver}`);\n console.log(` ${meta.chunk_count} chunks, ${meta.url_count} URLs, last refreshed ${meta.last_refreshed}`);\n }\n}\n\nasync function cmdStats(): Promise<void> {\n const metas = await getAllSourcesMeta();\n\n if (metas.length === 0) {\n console.log(\"No sources have been refreshed yet.\");\n return;\n }\n\n let totalChunks = 0;\n let totalUrls = 0;\n\n console.log(\"\\nSource Statistics:\\n\");\n for (const meta of metas) {\n const ver = meta.version ? ` v${meta.version}` : \"\";\n console.log(` ${bold(meta.source)}${ver}`);\n console.log(` Chunks: ${meta.chunk_count}`);\n console.log(` URLs: ${meta.url_count}`);\n console.log(` Last refreshed: ${meta.last_refreshed}`);\n totalChunks += meta.chunk_count;\n totalUrls += meta.url_count;\n }\n\n console.log(`\\n Total: ${totalChunks} chunks across ${totalUrls} URLs from ${metas.length} sources`);\n}\n\nasync function cmdExport(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n format: { type: \"string\", default: \"json\" },\n },\n allowPositionals: true,\n });\n\n const sourceName = args.positionals[0];\n if (!sourceName) {\n console.error(\"Usage: grimoire export <source> [--format json]\");\n process.exit(1);\n }\n\n const mdDir = join(DATA_DIR, \"markdown\", sourceName);\n let files: string[];\n try {\n files = await readdir(mdDir);\n } catch {\n console.error(`No markdown data found for source \"${sourceName}\".`);\n process.exit(1);\n }\n\n const pages = [];\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const content = await readFile(join(mdDir, file), \"utf-8\");\n pages.push({ file, content });\n }\n\n console.log(JSON.stringify(pages, null, 2));\n}\n\nasync function cmdDelete(): Promise<void> {\n const sourceName = process.argv[3];\n if (!sourceName) {\n console.error(\"Usage: grimoire delete <source>\");\n process.exit(1);\n }\n\n const meta = await getSourceMeta(sourceName);\n if (!meta) {\n console.error(`Source \"${sourceName}\" not found in Firestore.`);\n process.exit(1);\n }\n\n console.log(`Deleting \"${sourceName}\" (${meta.chunk_count} chunks)...`);\n const deleted = await purgeSource(sourceName);\n await deleteSourceMeta(sourceName);\n console.log(`Deleted ${deleted} chunks and source metadata for \"${sourceName}\".`);\n}\n\nasync function cmdScrapeUrls(): Promise<void> {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n concurrency: { type: \"string\" },\n },\n allowPositionals: true,\n });\n\n const sourceName = args.positionals[0];\n if (!sourceName) {\n console.error(\"Usage: grimoire scrape-urls <source> [--concurrency <n>]\");\n process.exit(1);\n }\n\n const config = await loadConfig(CONFIG_PATH);\n const source = config.sources[sourceName];\n if (!source) {\n console.error(`Source \"${sourceName}\" not found in config.`);\n process.exit(1);\n }\n\n const rawDir = join(DATA_DIR, \"raw\", sourceName);\n const urlsPath = join(rawDir, \"urls.json\");\n\n let urls: string[];\n try {\n urls = JSON.parse(await readFile(urlsPath, \"utf-8\"));\n } catch {\n console.error(`No urls.json found for \"${sourceName}\". Run 'grimoire refresh ${sourceName} --skip-store' first.`);\n process.exit(1);\n }\n\n const missing = urls.filter((url) => !existsSync(join(rawDir, `${slugifyUrl(url)}.html`)));\n console.log(`\\nTotal: ${urls.length}, Cached: ${urls.length - missing.length}, Missing: ${missing.length}`);\n\n if (missing.length === 0) {\n console.log(\"Nothing to scrape.\");\n return;\n }\n\n const concurrency = args.values.concurrency ? parseInt(args.values.concurrency, 10) : source.concurrency ?? 20;\n const browser = await createBrowser();\n const context = await browser.newContext(source.user_agent ? { userAgent: source.user_agent } : {});\n let done = 0;\n\n for (let i = 0; i < missing.length; i += concurrency) {\n const batch = missing.slice(i, i + concurrency);\n await Promise.all(batch.map(async (url) => {\n const page = await context.newPage();\n try {\n await page.goto(url, { waitUntil: source.headed ? \"networkidle\" : \"domcontentloaded\", timeout: 30000 });\n if (source.wait_for_selector) {\n await page.waitForSelector(source.wait_for_selector, { timeout: 30000 });\n }\n const html = await page.content();\n await writeFile(join(rawDir, `${slugifyUrl(url)}.html`), html, \"utf-8\");\n done++;\n if (done % 10 === 0 || done === missing.length) console.log(` [${done}/${missing.length}]`);\n } catch (e) {\n console.error(` FAILED: ${url} - ${e instanceof Error ? e.message : String(e)}`);\n } finally {\n await page.close();\n }\n }));\n }\n\n console.log(`Done. Fetched ${done} pages.`);\n await browser.close();\n}\n\nexport const ADMIN_COMMANDS: Record<string, () => Promise<void>> = {\n add: cmdAdd,\n refresh: cmdRefresh,\n delete: cmdDelete,\n \"scrape-urls\": cmdScrapeUrls,\n search: cmdSearch,\n list: cmdList,\n stats: cmdStats,\n export: cmdExport,\n apikey: cmdApiKey,\n};\n", "import { readFile } from \"node:fs/promises\";\nimport { parse } from \"yaml\";\n\nexport interface SourceConfig {\n name: string;\n version?: string;\n start_url: string;\n nav_selector?: string;\n content_selector?: string;\n remove_selectors?: string[];\n remove_text_patterns?: string[];\n include_patterns?: string[];\n exclude_patterns?: string[];\n rate_limit_ms?: number;\n concurrency?: number;\n headed?: boolean;\n sitemap_url?: string;\n user_agent?: string;\n llms_full_url?: string;\n wait_for_selector?: string;\n}\n\nexport interface GrimoireConfig {\n sources: Record<string, SourceConfig>;\n}\n\nconst REQUIRED_SOURCE_FIELDS = [\n \"name\",\n \"start_url\",\n] as const;\n\nconst SCRAPE_REQUIRED_FIELDS = [\n \"nav_selector\",\n \"content_selector\",\n] as const;\n\nexport function validateConfig(data: unknown): GrimoireConfig {\n if (typeof data !== \"object\" || data === null || !(\"sources\" in data)) {\n throw new Error(\"Config must have a 'sources' key\");\n }\n\n const { sources } = data as { sources: unknown };\n\n if (typeof sources !== \"object\" || sources === null) {\n throw new Error(\"'sources' must be an object\");\n }\n\n const entries = Object.entries(sources as Record<string, unknown>);\n\n if (entries.length === 0) {\n throw new Error(\"'sources' must contain at least one source\");\n }\n\n const validated: Record<string, SourceConfig> = {};\n\n for (const [key, value] of entries) {\n if (typeof value !== \"object\" || value === null) {\n throw new Error(`Source '${key}' must be an object`);\n }\n\n const source = value as Record<string, unknown>;\n\n for (const field of REQUIRED_SOURCE_FIELDS) {\n if (typeof source[field] !== \"string\" || source[field] === \"\") {\n throw new Error(`Source '${key}' is missing required field '${field}'`);\n }\n }\n\n if (!source.llms_full_url) {\n for (const field of SCRAPE_REQUIRED_FIELDS) {\n if (typeof source[field] !== \"string\" || source[field] === \"\") {\n throw new Error(`Source '${key}' is missing required field '${field}'`);\n }\n }\n }\n\n try {\n new URL(source.start_url as string);\n } catch {\n throw new Error(`Source '${key}' has invalid start_url: ${source.start_url}`);\n }\n\n if (source.remove_selectors !== undefined && !Array.isArray(source.remove_selectors)) {\n throw new Error(`Source '${key}': remove_selectors must be an array`);\n }\n\n if (source.remove_text_patterns !== undefined && !Array.isArray(source.remove_text_patterns)) {\n throw new Error(`Source '${key}': remove_text_patterns must be an array`);\n }\n\n if (source.include_patterns !== undefined && !Array.isArray(source.include_patterns)) {\n throw new Error(`Source '${key}': include_patterns must be an array`);\n }\n\n if (source.exclude_patterns !== undefined && !Array.isArray(source.exclude_patterns)) {\n throw new Error(`Source '${key}': exclude_patterns must be an array`);\n }\n\n if (source.rate_limit_ms !== undefined && typeof source.rate_limit_ms !== \"number\") {\n throw new Error(`Source '${key}': rate_limit_ms must be a number`);\n }\n\n if (source.concurrency !== undefined && typeof source.concurrency !== \"number\") {\n throw new Error(`Source '${key}': concurrency must be a number`);\n }\n\n validated[key] = {\n name: source.name as string,\n version: source.version as string | undefined,\n start_url: source.start_url as string,\n nav_selector: source.nav_selector as string | undefined,\n content_selector: source.content_selector as string | undefined,\n remove_selectors: source.remove_selectors as string[] | undefined,\n remove_text_patterns: source.remove_text_patterns as string[] | undefined,\n include_patterns: source.include_patterns as string[] | undefined,\n exclude_patterns: source.exclude_patterns as string[] | undefined,\n rate_limit_ms: source.rate_limit_ms as number | undefined,\n concurrency: source.concurrency as number | undefined,\n headed: source.headed as boolean | undefined,\n sitemap_url: source.sitemap_url as string | undefined,\n user_agent: source.user_agent as string | undefined,\n llms_full_url: source.llms_full_url as string | undefined,\n wait_for_selector: source.wait_for_selector as string | undefined,\n };\n }\n\n return { sources: validated };\n}\n\nexport async function loadConfig(path: string): Promise<GrimoireConfig> {\n const content = await readFile(path, \"utf-8\");\n const data = parse(content, { merge: true });\n return validateConfig(data);\n}\n", "import { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { chromium, type Browser, type Page } from \"playwright\";\nimport type { SourceConfig } from \"./config.js\";\nimport { slugifyUrl } from \"./slug.js\";\n\nexport function filterUrls(\n urls: string[],\n includePatterns?: string[],\n excludePatterns?: string[],\n): string[] {\n // Fragment variants (page#section) are the same document: keeping them\n // would scrape and chunk the same page once per anchor.\n let filtered = urls\n .map((url) => url.split(\"#\")[0])\n .filter((url) => url.startsWith(\"http\") && !url.includes(\"?hl=\"));\n\n if (includePatterns && includePatterns.length > 0) {\n filtered = filtered.filter((url) =>\n includePatterns.some((pattern) => url.includes(pattern)),\n );\n }\n\n if (excludePatterns && excludePatterns.length > 0) {\n filtered = filtered.filter(\n (url) => !excludePatterns.some((pattern) => url.includes(pattern)),\n );\n }\n\n return [...new Set(filtered)].sort();\n}\n\nasync function fetchSitemapUrls(sitemapUrl: string): Promise<string[]> {\n const response = await fetch(sitemapUrl);\n const xml = await response.text();\n const locs = [...xml.matchAll(/<loc>([^<]+)<\\/loc>/g)].map((m) => m[1]);\n\n if (xml.includes(\"<sitemapindex\")) {\n const nested = await Promise.all(locs.map((loc) => fetchSitemapUrls(loc)));\n return nested.flat();\n }\n\n return locs;\n}\n\nasync function discoverFromSitemap(sitemapUrl: string, source: SourceConfig): Promise<string[]> {\n const urls = await fetchSitemapUrls(sitemapUrl);\n return filterUrls(urls, source.include_patterns, source.exclude_patterns);\n}\n\nexport async function discoverUrls(\n page: Page,\n source: SourceConfig,\n): Promise<string[]> {\n if (source.sitemap_url) {\n return discoverFromSitemap(source.sitemap_url, source);\n }\n\n await page.goto(source.start_url, { waitUntil: source.headed ? \"networkidle\" : \"domcontentloaded\" });\n\n const rawUrls = await page.$$eval(\n `${source.nav_selector} a[href]`,\n (links) => links.map((a) => (a as HTMLAnchorElement).href),\n );\n\n const discovered = filterUrls(rawUrls, source.include_patterns, source.exclude_patterns);\n if (!discovered.includes(source.start_url)) {\n discovered.unshift(source.start_url);\n }\n return discovered;\n}\n\nconst NAV_TIMEOUT_MS_HEADED = 60000;\nconst NAV_TIMEOUT_MS = 30000;\n\nexport async function fetchPage(\n page: Page,\n url: string,\n headed?: boolean,\n waitForSelector?: string,\n): Promise<string> {\n try {\n await page.goto(url, {\n waitUntil: headed ? \"networkidle\" : \"domcontentloaded\",\n timeout: headed ? NAV_TIMEOUT_MS_HEADED : NAV_TIMEOUT_MS,\n });\n } catch (err) {\n // Bot-protected sites with polling widgets (Zendesk live chat, intercom)\n // never reach networkidle. The DOM has loaded by then, so accept the\n // timeout and fall through; wait_for_selector below gates content validity.\n if (!(err instanceof Error) || !/Timeout|Navigation timeout/i.test(err.message)) {\n throw err;\n }\n }\n if (waitForSelector) {\n await page.waitForSelector(waitForSelector, { timeout: NAV_TIMEOUT_MS_HEADED });\n }\n return page.content();\n}\n\nconst DEFAULT_CONCURRENCY = 50;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function runPool<T>(\n items: T[],\n concurrency: number,\n rateLimitMs: number,\n fn: (item: T, index: number) => Promise<void>,\n): Promise<void> {\n let nextIndex = 0;\n\n async function worker(): Promise<void> {\n let isFirst = true;\n while (nextIndex < items.length) {\n const index = nextIndex++;\n if (!isFirst && rateLimitMs > 0) {\n await sleep(rateLimitMs);\n }\n isFirst = false;\n await fn(items[index], index);\n }\n }\n\n const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker());\n await Promise.all(workers);\n}\n\nexport async function scrapeSource(\n source: SourceConfig,\n sourceName: string,\n dataDir: string,\n onProgress?: (current: number, total: number, url: string) => void,\n): Promise<string[]> {\n const rawDir = join(dataDir, \"raw\", sourceName);\n await mkdir(rawDir, { recursive: true });\n\n const concurrency = source.concurrency ?? DEFAULT_CONCURRENCY;\n const rateLimitMs = source.rate_limit_ms ?? 0;\n const browser = await chromium.launch({ channel: \"chrome\", headless: !source.headed });\n const context = await browser.newContext(source.user_agent ? { userAgent: source.user_agent } : {});\n\n const discoveryPage = await context.newPage();\n const urls = await discoverUrls(discoveryPage, source);\n await discoveryPage.close();\n\n let completed = 0;\n const succeeded: string[] = [];\n const failed: { url: string; error: string }[] = [];\n\n async function attemptScrape(url: string, maxAttempts: number): Promise<string | null> {\n let lastError: unknown;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const page = await context.newPage();\n try {\n const html = await fetchPage(page, url, source.headed, source.wait_for_selector);\n const slug = slugifyUrl(url);\n await writeFile(join(rawDir, `${slug}.html`), html, \"utf-8\");\n return null;\n } catch (err) {\n lastError = err;\n } finally {\n await page.close();\n }\n }\n return lastError instanceof Error ? lastError.message : String(lastError);\n }\n\n try {\n await runPool(urls, concurrency, rateLimitMs, async (url) => {\n const errorMsg = await attemptScrape(url, 2);\n if (errorMsg === null) {\n succeeded.push(url);\n } else {\n failed.push({ url, error: errorMsg });\n }\n completed++;\n onProgress?.(completed, urls.length, url);\n });\n\n // Recovery pass: retry transient failures sequentially with throttling.\n // Zendesk and similar bot-protected sites randomly time out under parallel\n // load even at low concurrency; a serialized retry usually catches them.\n if (failed.length > 0) {\n const toRetry = failed.splice(0).map((f) => f.url);\n const recoveryRateLimit = Math.max(rateLimitMs, 1000);\n console.log(` Recovery pass: retrying ${toRetry.length} failed pages sequentially...`);\n await runPool(toRetry, 1, recoveryRateLimit, async (url) => {\n const errorMsg = await attemptScrape(url, 2);\n if (errorMsg === null) {\n succeeded.push(url);\n } else {\n failed.push({ url, error: errorMsg });\n }\n });\n console.log(` Recovered ${toRetry.length - failed.length}/${toRetry.length} pages.`);\n }\n\n if (failed.length > 0) {\n console.error(` Skipped ${failed.length}/${urls.length} pages after recovery:`);\n for (const { url, error } of failed.slice(0, 10)) {\n console.error(` ${url} - ${error.split(\"\\n\")[0]}`);\n }\n if (failed.length > 10) console.error(` ...and ${failed.length - 10} more.`);\n }\n\n await writeFile(join(rawDir, \"urls.json\"), JSON.stringify(succeeded), \"utf-8\");\n return succeeded;\n } finally {\n await browser.close();\n }\n}\n\nexport async function createBrowser(): Promise<Browser> {\n return chromium.launch({ channel: \"chrome\" });\n}\n", "// Dependency-free: imported by the Cloud Function via chunker, so it must\n// not pull in playwright or any other admin-only package.\nexport function slugifyUrl(url: string): string {\n const parsed = new URL(url);\n return parsed.pathname\n .replace(/^\\//, \"\")\n .replace(/\\/$/, \"\")\n .replace(/\\//g, \"-\")\n .replace(/[^a-zA-Z0-9-]/g, \"\");\n}\n", "import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { JSDOM } from \"jsdom\";\nimport TurndownService from \"turndown\";\nimport { tables } from \"@joplin/turndown-plugin-gfm\";\nimport { slugifyUrl } from \"./slug.js\";\n\nconst turndown = new TurndownService({\n headingStyle: \"atx\",\n codeBlockStyle: \"fenced\",\n bulletListMarker: \"-\",\n});\nturndown.use(tables);\n\nexport interface ConvertedPage {\n source: string;\n url: string;\n title: string;\n markdown: string;\n}\n\nconst GENERIC_REMOVE = [\n \"style\",\n \"script\",\n \"noscript\",\n \"iframe\",\n \"svg\",\n];\n\nfunction cleanMarkdown(md: string, textPatterns?: string[]): string {\n let cleaned = md\n .replace(/^(#+)\\s*$/gm, \"\")\n .replace(/\\n{3,}/g, \"\\n\\n\");\n\n if (textPatterns) {\n for (const pattern of textPatterns) {\n cleaned = cleaned.replace(new RegExp(pattern, \"gm\"), \"\");\n }\n cleaned = cleaned.replace(/\\n{3,}/g, \"\\n\\n\");\n }\n\n return cleaned.trim();\n}\n\n// The GFM tables plugin keeps a table as raw HTML when its cells contain\n// code, lists, or other block markup. Reference tables (e.g. GCP flag docs)\n// are full of those, so flatten cell markup to plain text first - pipe rows\n// preserve the name/type/default association that flattened paragraphs lose.\nfunction flattenTableCellMarkup(contentEl: Element): void {\n for (const table of contentEl.querySelectorAll(\"table\")) {\n if (!table.isConnected) continue;\n for (const inner of table.querySelectorAll(\"table\")) {\n if (!inner.isConnected) continue;\n const rows = [...inner.rows]\n .map((row) =>\n [...row.cells]\n .map((c) => c.textContent?.replace(/\\s+/g, \" \").trim() ?? \"\")\n .filter(Boolean)\n .join(\" \"),\n )\n .filter(Boolean);\n inner.replaceWith(rows.join(\"; \"));\n }\n for (const code of table.querySelectorAll(\"code\")) {\n code.replaceWith(code.textContent ?? \"\");\n }\n for (const list of table.querySelectorAll(\"ul, ol\")) {\n if (!list.isConnected) continue;\n const items = [...list.querySelectorAll(\"li\")]\n .map((li) => li.textContent?.replace(/\\s+/g, \" \").trim() ?? \"\")\n .filter(Boolean);\n list.replaceWith(items.join(\"; \"));\n }\n for (const block of table.querySelectorAll(\"aside, blockquote, h1, h2, h3, h4, h5, h6\")) {\n if (!block.isConnected) continue;\n block.replaceWith(block.textContent?.replace(/\\s+/g, \" \").trim() ?? \"\");\n }\n }\n}\n\n// Table cells emit <br> for in-cell line breaks; replace with \"; \" so pipe\n// rows stay single-line and the embedded text carries no HTML tags.\nfunction cleanTableRowBreaks(md: string): string {\n return md\n .split(\"\\n\")\n .map((line) =>\n line.startsWith(\"|\") ? line.replace(/\\s*<br\\s*\\/?>\\s*/g, \"; \") : line,\n )\n .join(\"\\n\");\n}\n\n// Syntax highlighters wrap every code token in <span>, inflating some pages\n// to tens of MB; JSDOM expands that 50-100x in memory and OOMs the convert\n// stage. Spans are inline wrappers turndown ignores, so unwrapping them at\n// the text level changes nothing in the markdown output. Applied only to\n// oversized pages to keep normal conversions byte-identical.\nconst SPAN_STRIP_THRESHOLD_BYTES = 2_000_000;\n\nfunction stripInlineSpans(html: string): string {\n return html.replace(/<\\/?span\\b[^>]*>/g, \"\");\n}\n\nexport function extractContent(\n html: string,\n contentSelector: string,\n removeSelectors?: string[],\n removeTextPatterns?: string[],\n): string {\n if (html.length > SPAN_STRIP_THRESHOLD_BYTES) {\n html = stripInlineSpans(html);\n }\n const dom = new JSDOM(html);\n const doc = dom.window.document;\n\n const contentEl = doc.querySelector(contentSelector) ?? doc.body;\n\n const allSelectors = [...GENERIC_REMOVE, ...(removeSelectors ?? [])];\n for (const selector of allSelectors) {\n for (const el of contentEl.querySelectorAll(selector)) {\n el.remove();\n }\n }\n\n flattenTableCellMarkup(contentEl);\n\n return cleanMarkdown(\n cleanTableRowBreaks(turndown.turndown(contentEl.innerHTML)),\n removeTextPatterns,\n );\n}\n\n// Strip only the trailing site-name segment (after the last pipe, dash with\n// surrounding spaces, or en/em dash) so mid-title hyphens and inner segments\n// like \"ExportContext | Cloud SQL Admin API\" survive.\nexport function extractTitle(html: string): string {\n const dom = new JSDOM(html);\n const raw = dom.window.document\n .querySelector(\"title\")\n ?.textContent?.replace(/\\s+/g, \" \")\n .trim();\n if (!raw) return \"Untitled\";\n const stripped = raw\n .replace(/\\s*\\|[^|]*$/, \"\")\n .replace(/\\s*[\\u2013\\u2014][^\\u2013\\u2014]*$/, \"\")\n .replace(/\\s+-\\s+(?!.*\\s-\\s)[^|]*$/, \"\")\n .trim();\n return stripped || raw;\n}\n\nexport function buildFrontmatter(source: string, url: string, title: string): string {\n return [\n \"---\",\n `source: ${source}`,\n `url: \"${url}\"`,\n `title: \"${title.replace(/\"/g, '\\\\\"')}\"`,\n `fetched_at: \"${new Date().toISOString()}\"`,\n \"---\",\n ].join(\"\\n\");\n}\n\nexport function convertPage(\n html: string,\n source: string,\n url: string,\n contentSelector: string,\n removeSelectors?: string[],\n removeTextPatterns?: string[],\n): ConvertedPage {\n const title = extractTitle(html);\n const content = extractContent(html, contentSelector, removeSelectors, removeTextPatterns);\n const frontmatter = buildFrontmatter(source, url, title);\n const markdown = `${frontmatter}\\n\\n${content}`;\n\n return { source, url, title, markdown };\n}\n\nconst DEFAULT_CONCURRENCY = 10;\n\nexport async function convertSource(\n sourceName: string,\n urls: string[],\n contentSelector: string,\n removeSelectors: string[] | undefined,\n removeTextPatterns: string[] | undefined,\n dataDir: string,\n concurrency = DEFAULT_CONCURRENCY,\n onProgress?: (current: number, total: number, url: string) => void,\n): Promise<ConvertedPage[]> {\n const rawDir = join(dataDir, \"raw\", sourceName);\n const mdDir = join(dataDir, \"markdown\", sourceName);\n await mkdir(mdDir, { recursive: true });\n\n const pages: ConvertedPage[] = new Array(urls.length);\n let completed = 0;\n let nextIndex = 0;\n\n async function worker(): Promise<void> {\n while (nextIndex < urls.length) {\n const i = nextIndex++;\n const url = urls[i];\n const slug = slugifyUrl(url);\n const htmlPath = join(rawDir, `${slug}.html`);\n\n let html: string;\n try {\n html = await readFile(htmlPath, \"utf-8\");\n } catch {\n console.warn(` WARNING: no cached HTML for ${url}, skipping page. Run 'grimoire scrape-urls' to fetch it.`);\n completed++;\n continue;\n }\n\n const page = convertPage(html, sourceName, url, contentSelector, removeSelectors, removeTextPatterns);\n await writeFile(join(mdDir, `${slug}.md`), page.markdown, \"utf-8\");\n pages[i] = page;\n completed++;\n onProgress?.(completed, urls.length, url);\n }\n }\n\n const workers = Array.from(\n { length: Math.min(concurrency, urls.length) },\n () => worker(),\n );\n await Promise.all(workers);\n\n return pages.filter((page) => page !== undefined);\n}\n", "import { slugifyUrl } from \"./slug.js\";\nimport type { Chunk } from \"./types.js\";\n\nexport type { Chunk };\n\nconst MAX_TOKENS = 500;\n\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nexport function slugifyHeading(heading: string): string {\n return heading\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nexport function buildChunkId(\n source: string,\n url: string,\n headingSlug: string,\n index?: number,\n): string {\n const urlSlug = slugifyUrl(url);\n const prefix = `${source}::${urlSlug}::`;\n const maxSlugBytes = 1500 - Buffer.byteLength(prefix) - 10;\n const truncatedSlug =\n Buffer.byteLength(headingSlug) > maxSlugBytes\n ? Buffer.from(headingSlug).subarray(0, maxSlugBytes).toString()\n : headingSlug;\n const base = `${prefix}${truncatedSlug}`;\n return index !== undefined ? `${base}-${index}` : base;\n}\n\n// The text whose embedding represents the chunk. Prepending the page title\n// and heading path pulls dense reference chunks (flag tables, REST schemas)\n// toward queries that name the page or section, not just the body text.\n// The chunk's own heading is rendered at the top of content, so it is\n// dropped from the context prefix to avoid double-weighting it.\nexport function buildEmbedText(chunk: Chunk): string {\n let path = chunk.heading_path[0] === chunk.title\n ? chunk.heading_path.slice(1)\n : chunk.heading_path;\n if (chunk.content.startsWith(\"#\") && path.length > 0) {\n path = path.slice(0, -1);\n }\n const context = [chunk.title, ...path].filter(Boolean).join(\" > \");\n return context ? `${context}\\n\\n${chunk.content}` : chunk.content;\n}\n\ninterface HeadingSection {\n level: number;\n heading: string;\n headingPath: string[];\n lines: string[];\n}\n\nfunction parseHeadingSections(markdown: string): HeadingSection[] {\n const lines = markdown.split(\"\\n\");\n const sections: HeadingSection[] = [];\n const headingStack: string[] = [];\n const levelStack: number[] = [];\n\n let currentSection: HeadingSection = {\n level: 0,\n heading: \"\",\n headingPath: [],\n lines: [],\n };\n\n let inFence = false;\n let fenceMarker = \"\";\n\n for (const line of lines) {\n const fenceMatch = line.match(/^\\s*(```+|~~~+)/);\n if (fenceMatch) {\n const marker = fenceMatch[1][0];\n if (!inFence) {\n inFence = true;\n fenceMarker = marker;\n } else if (marker === fenceMarker) {\n inFence = false;\n }\n }\n\n const headingMatch = inFence ? null : line.match(/^(#{1,6})\\s+(.+)$/);\n\n if (headingMatch) {\n if (currentSection.lines.length > 0 || currentSection.heading !== \"\") {\n sections.push(currentSection);\n }\n\n const level = headingMatch[1].length;\n const heading = headingMatch[2].trim();\n\n while (levelStack.length > 0 && levelStack[levelStack.length - 1] >= level) {\n levelStack.pop();\n headingStack.pop();\n }\n\n headingStack.push(heading);\n levelStack.push(level);\n\n currentSection = {\n level,\n heading,\n headingPath: [...headingStack],\n lines: [],\n };\n } else {\n currentSection.lines.push(line);\n }\n }\n\n if (currentSection.lines.length > 0 || currentSection.heading !== \"\") {\n sections.push(currentSection);\n }\n\n return sections;\n}\n\ntype BlockKind = \"paragraph\" | \"fence\" | \"table\";\n\ninterface Block {\n kind: BlockKind;\n lines: string[];\n}\n\nfunction isTableLine(line: string): boolean {\n return line.trimStart().startsWith(\"|\");\n}\n\nfunction isTableSeparator(line: string): boolean {\n return /^\\s*\\|?[\\s:|-]+\\|?\\s*$/.test(line) && line.includes(\"-\");\n}\n\n// Group section lines into atomic blocks so splitting never lands inside a\n// fenced code block (blank lines in fences are not paragraph boundaries) and\n// tables can be split row-wise instead of as one opaque paragraph.\nfunction parseBlocks(lines: string[]): Block[] {\n const blocks: Block[] = [];\n let current: string[] = [];\n let kind: BlockKind = \"paragraph\";\n let inFence = false;\n let fenceMarker = \"\";\n\n function flush(): void {\n while (current.length > 0 && current[current.length - 1].trim() === \"\") {\n current.pop();\n }\n if (current.length > 0) {\n blocks.push({ kind, lines: current });\n }\n current = [];\n kind = \"paragraph\";\n }\n\n for (const line of lines) {\n if (inFence) {\n current.push(line);\n const fenceMatch = line.match(/^\\s*(```+|~~~+)/);\n if (fenceMatch && fenceMatch[1][0] === fenceMarker) {\n inFence = false;\n flush();\n }\n continue;\n }\n\n const fenceMatch = line.match(/^\\s*(```+|~~~+)/);\n if (fenceMatch) {\n flush();\n kind = \"fence\";\n inFence = true;\n fenceMarker = fenceMatch[1][0];\n current.push(line);\n continue;\n }\n\n if (isTableLine(line)) {\n if (kind !== \"table\") {\n flush();\n kind = \"table\";\n }\n current.push(line);\n continue;\n }\n\n if (kind === \"table\") {\n flush();\n }\n\n if (line.trim() === \"\") {\n flush();\n } else {\n current.push(line);\n }\n }\n\n flush();\n return blocks;\n}\n\n// A single line over the budget must be hard-split: gemini-embedding-001\n// caps input at 2048 tokens and silently truncates beyond it, making the\n// tail of an oversized chunk invisible to search.\nfunction splitLongLine(line: string, budget: number): string[] {\n const maxChars = budget * 4;\n if (line.length <= maxChars) return [line];\n\n const pieces: string[] = [];\n let rest = line;\n while (rest.length > maxChars) {\n let cut = rest.lastIndexOf(\" \", maxChars);\n if (cut < maxChars / 2) cut = maxChars;\n pieces.push(rest.slice(0, cut));\n rest = rest.slice(cut).trimStart();\n }\n if (rest) pieces.push(rest);\n return pieces;\n}\n\nfunction groupLines(lines: string[], budget: number): string[][] {\n const groups: string[][] = [];\n let current: string[] = [];\n let tokens = 0;\n\n for (const line of lines.flatMap((l) => splitLongLine(l, budget))) {\n const lineTokens = estimateTokens(line) + 1;\n if (tokens + lineTokens > budget && current.length > 0) {\n groups.push(current);\n current = [];\n tokens = 0;\n }\n current.push(line);\n tokens += lineTokens;\n }\n\n if (current.length > 0) groups.push(current);\n return groups;\n}\n\n// Split an oversized block into pieces that each stay valid markdown:\n// tables repeat the header row per piece, fences are re-opened and re-closed.\nfunction splitBlock(block: Block, budget: number): string[] {\n if (block.kind === \"table\") {\n const hasHeader = block.lines.length >= 2 && isTableSeparator(block.lines[1]);\n const header = hasHeader ? block.lines.slice(0, 2) : [];\n const rows = hasHeader ? block.lines.slice(2) : block.lines;\n const headerTokens = estimateTokens(header.join(\"\\n\"));\n return groupLines(rows, Math.max(budget - headerTokens, 50)).map((group) =>\n [...header, ...group].join(\"\\n\"),\n );\n }\n\n if (block.kind === \"fence\") {\n const opening = block.lines[0];\n const closing = block.lines[block.lines.length - 1].match(/^\\s*(```+|~~~+)\\s*$/)\n ? block.lines[block.lines.length - 1]\n : opening.match(/^\\s*(```+|~~~+)/)![1];\n const body = block.lines.slice(1, block.lines[block.lines.length - 1] === closing ? -1 : undefined);\n const frameTokens = estimateTokens(opening) + estimateTokens(closing) + 2;\n return groupLines(body, Math.max(budget - frameTokens, 50)).map((group) =>\n [opening, ...group, closing].join(\"\\n\"),\n );\n }\n\n return groupLines(block.lines, budget).map((group) => group.join(\"\\n\"));\n}\n\n// Pack blocks into parts within the token budget. Every part of a split\n// section carries the heading line so continuation chunks keep their context.\nfunction splitSectionIntoParts(\n blocks: Block[],\n headingLine: string,\n maxTokens: number,\n): string[] {\n const budget = Math.max(maxTokens - estimateTokens(headingLine), 100);\n const parts: string[][] = [];\n let current: string[] = [];\n let tokens = 0;\n\n function flush(): void {\n if (current.length > 0) {\n parts.push(current);\n current = [];\n tokens = 0;\n }\n }\n\n for (const block of blocks) {\n const text = block.lines.join(\"\\n\");\n const blockTokens = estimateTokens(text) + 2;\n\n if (blockTokens > budget) {\n flush();\n for (const piece of splitBlock(block, budget)) {\n parts.push([piece]);\n }\n continue;\n }\n\n if (tokens + blockTokens > budget) {\n flush();\n }\n current.push(text);\n tokens += blockTokens;\n }\n\n flush();\n return parts.map((blockTexts) => headingLine + blockTexts.join(\"\\n\\n\"));\n}\n\nfunction stripFrontmatter(markdown: string): string {\n if (markdown.startsWith(\"---\")) {\n const endIndex = markdown.indexOf(\"---\", 3);\n if (endIndex !== -1) {\n return markdown.slice(endIndex + 3).trim();\n }\n }\n return markdown;\n}\n\nexport function chunkMarkdown(\n markdown: string,\n source: string,\n url: string,\n title: string,\n): Chunk[] {\n const stripped = stripFrontmatter(markdown);\n const sections = parseHeadingSections(stripped);\n const chunks: Chunk[] = [];\n const usedIds = new Set<string>();\n\n function uniqueId(baseSlug: string): string {\n let id = buildChunkId(source, url, baseSlug);\n if (!usedIds.has(id)) {\n usedIds.add(id);\n return id;\n }\n let counter = 1;\n while (usedIds.has(buildChunkId(source, url, baseSlug, counter))) {\n counter++;\n }\n id = buildChunkId(source, url, baseSlug, counter);\n usedIds.add(id);\n return id;\n }\n\n for (const section of sections) {\n const headingLine = section.heading\n ? `${\"#\".repeat(section.level)} ${section.heading}\\n\\n`\n : \"\";\n const body = section.lines.join(\"\\n\").trim();\n const content = headingLine + body;\n\n if (!content.trim()) continue;\n\n const headingSlug = section.heading\n ? slugifyHeading(section.heading)\n : \"intro\";\n\n if (estimateTokens(content) <= MAX_TOKENS) {\n chunks.push({\n id: uniqueId(headingSlug),\n source,\n url,\n title,\n heading_path: section.headingPath,\n content,\n token_count: estimateTokens(content),\n });\n continue;\n }\n\n const blocks = parseBlocks(body.split(\"\\n\"));\n const parts = splitSectionIntoParts(blocks, headingLine, MAX_TOKENS);\n\n for (let i = 0; i < parts.length; i++) {\n const partContent = parts[i].trim();\n if (!partContent) continue;\n\n const partSlug = parts.length > 1 ? `${headingSlug}-${i}` : headingSlug;\n chunks.push({\n id: uniqueId(partSlug),\n source,\n url,\n title,\n heading_path: section.headingPath,\n content: partContent,\n token_count: estimateTokens(partContent),\n });\n }\n }\n\n return chunks;\n}\n", "import { createHash } from \"node:crypto\";\n\n// Identifier-shaped tokens: snake_case, kebab-case, dotted.paths (and mixes),\n// camelCase, PascalCase. Plain prose words are deliberately excluded - the\n// lexical index exists to find exact identifiers that dense embeddings dilute,\n// while ordinary vocabulary is the vector search's job.\nconst IDENTIFIER_PATTERN =\n /(?<![A-Za-z0-9._])(?:[A-Za-z][A-Za-z0-9]*(?:[_.-][A-Za-z0-9]+)+|[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)+|(?:[A-Z][a-z0-9]+){2,})(?![A-Za-z0-9])/g;\n\nconst MIN_TOKEN_LENGTH = 4;\nconst MAX_TOKEN_LENGTH = 80;\nconst MAX_TOKENS_PER_CHUNK = 100;\n\n// Drop URLs/link targets (their hostnames and paths are identifier-shaped\n// noise) and undo markdown escaping (`max\\_connections` must tokenize as\n// max_connections).\nfunction normalizeForTokens(text: string): string {\n return text\n .replace(/\\]\\([^)]*\\)/g, \"]\")\n .replace(/https?:\\/\\/\\S+/g, \" \")\n .replace(/\\\\([_*[\\]()#`~-])/g, \"$1\");\n}\n\nexport function extractIdentifierTokens(text: string, limit = MAX_TOKENS_PER_CHUNK): string[] {\n const seen = new Set<string>();\n for (const match of normalizeForTokens(text).matchAll(IDENTIFIER_PATTERN)) {\n const token = match[0].toLowerCase();\n if (token.length < MIN_TOKEN_LENGTH || token.length > MAX_TOKEN_LENGTH) continue;\n seen.add(token);\n if (seen.size >= limit) break;\n }\n return [...seen];\n}\n\nexport function extractQueryTokens(query: string): string[] {\n return extractIdentifierTokens(query, 5);\n}\n\nexport function contentHash(text: string): string {\n return createHash(\"sha256\").update(text).digest(\"hex\");\n}\n", "import { GoogleGenerativeAI } from \"@google/generative-ai\";\n\nconst BATCH_SIZE = 50;\nconst MODEL = \"gemini-embedding-001\";\nconst OUTPUT_DIMENSIONALITY = 768;\nconst MAX_RETRIES = 5;\nconst RATE_LIMIT_BASE_DELAY_MS = 60000;\nconst NETWORK_BASE_DELAY_MS = 10000;\nconst BATCH_DELAY_MS = 2500;\n\nconst NETWORK_ERROR_PATTERNS = [\n \"fetch failed\",\n \"ECONNRESET\",\n \"ETIMEDOUT\",\n \"ECONNREFUSED\",\n \"EAI_AGAIN\",\n \"ENOTFOUND\",\n \"socket hang up\",\n \"UND_ERR_\",\n];\n\nlet genAI: GoogleGenerativeAI | undefined;\n\nfunction getClient(): GoogleGenerativeAI {\n if (!genAI) {\n const apiKey = process.env.GEMINI_API_KEY;\n if (!apiKey) {\n throw new Error(\"GEMINI_API_KEY environment variable is not set\");\n }\n genAI = new GoogleGenerativeAI(apiKey);\n }\n return genAI;\n}\n\nfunction classifyError(message: string): \"rate_limit\" | \"network\" | \"other\" {\n if (message.includes(\"429\") || message.includes(\"500\") || message.includes(\"502\") || message.includes(\"503\") || message.includes(\"504\")) {\n return \"rate_limit\";\n }\n if (NETWORK_ERROR_PATTERNS.some((p) => message.includes(p))) {\n return \"network\";\n }\n return \"other\";\n}\n\nexport interface EmbedOptions {\n onProgress?: (done: number, total: number) => void;\n maxRetries?: number;\n}\n\nexport async function embedTexts(\n texts: string[],\n options: EmbedOptions = {},\n): Promise<number[][]> {\n const client = getClient();\n const model = client.getGenerativeModel({ model: MODEL });\n\n const { onProgress } = options;\n const maxRetries = options.maxRetries ?? MAX_RETRIES;\n\n const embeddings: number[][] = [];\n\n for (let i = 0; i < texts.length; i += BATCH_SIZE) {\n const batch = texts.slice(i, i + BATCH_SIZE);\n const batchNumber = i / BATCH_SIZE + 1;\n\n let result;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n result = await model.batchEmbedContents({\n requests: batch.map((text) => ({\n content: { role: \"user\", parts: [{ text }] },\n outputDimensionality: OUTPUT_DIMENSIONALITY,\n })),\n });\n break;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const kind = classifyError(message);\n\n if (kind !== \"other\" && attempt < maxRetries - 1) {\n const baseDelay = kind === \"rate_limit\" ? RATE_LIMIT_BASE_DELAY_MS : NETWORK_BASE_DELAY_MS;\n const delay = baseDelay * Math.pow(2, attempt);\n const label = kind === \"rate_limit\" ? \"Rate limited\" : \"Network error\";\n console.log(` ${label} (batch ${batchNumber}), retrying in ${delay / 1000}s...`);\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n\n console.error(` Embedding failed at batch ${batchNumber} (chunks ${i + 1}-${i + batch.length}): ${message}`);\n throw err;\n }\n }\n\n for (const embedding of result!.embeddings) {\n embeddings.push(embedding.values);\n }\n\n onProgress?.(Math.min(i + BATCH_SIZE, texts.length), texts.length);\n\n if (i + BATCH_SIZE < texts.length) {\n await new Promise((resolve) => setTimeout(resolve, BATCH_DELAY_MS));\n }\n }\n\n return embeddings;\n}\n\n// Online query path - one shot, no retries. The Cloud Function request\n// timeout (60s) is shorter than the embedder's first rate-limit backoff\n// (60s), so any retry here would only ever cause a 504. Surface the error\n// to the caller, who can retry at their own cadence.\nexport async function embedText(text: string): Promise<number[]> {\n const [embedding] = await embedTexts([text], { maxRetries: 1 });\n return embedding;\n}\n", "import { initializeApp, applicationDefault, getApps } from \"firebase-admin/app\";\nimport {\n getFirestore,\n FieldValue,\n type Firestore,\n} from \"firebase-admin/firestore\";\nimport type { Chunk, SourceMeta } from \"./types.js\";\nimport { buildEmbedText } from \"./chunker.js\";\nimport { contentHash, extractIdentifierTokens } from \"./tokens.js\";\n\n// Vector-indexed writes are heavy; 500-doc batches against the vector index\n// can exceed the commit deadline on large sources (25k+ chunks). Smaller\n// batches commit faster and a short inter-batch pause smooths sustained\n// write throughput so the vector index backend keeps up.\nconst BATCH_SIZE = 200;\nconst INTER_BATCH_DELAY_MS = 150;\nconst MAX_RETRIES = 7;\nconst BASE_DELAY_MS = 5000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// Transient Firestore/gRPC failures worth retrying. batch.commit() with fixed\n// document ids is idempotent, so re-committing the same batch is safe.\nconst TRANSIENT_PATTERNS = [\n \"RESOURCE_EXHAUSTED\",\n \"Quota exceeded\",\n \"DEADLINE_EXCEEDED\",\n \"UNAVAILABLE\",\n \"ABORTED\",\n \"INTERNAL\",\n \"503\",\n \"ECONNRESET\",\n \"ETIMEDOUT\",\n];\n\nasync function retryOnTransient<T>(fn: () => Promise<T>): Promise<T> {\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n return await fn();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (TRANSIENT_PATTERNS.some((p) => message.includes(p)) && attempt < MAX_RETRIES - 1) {\n const delay = BASE_DELAY_MS * Math.pow(2, attempt);\n console.log(` Transient Firestore error (${message.split(\":\")[0].slice(0, 40)}), retrying in ${delay / 1000}s...`);\n await sleep(delay);\n continue;\n }\n throw err;\n }\n }\n throw new Error(\"Max retries exceeded\");\n}\n\nlet db: Firestore | undefined;\n\nfunction getDb(): Firestore {\n if (!db) {\n if (getApps().length === 0) {\n initializeApp({ credential: applicationDefault() });\n }\n db = getFirestore();\n }\n return db;\n}\n\nfunction chunksCol() {\n return getDb().collection(\"grimoire_chunks\");\n}\n\nfunction sourcesCol() {\n return getDb().collection(\"grimoire_sources\");\n}\n\nexport async function storeChunks(\n chunks: Chunk[],\n embeddings: number[][],\n onProgress?: (current: number, total: number) => void,\n): Promise<void> {\n const database = getDb();\n const col = chunksCol();\n\n for (let i = 0; i < chunks.length; i += BATCH_SIZE) {\n const batch = database.batch();\n const slice = chunks.slice(i, i + BATCH_SIZE);\n const embSlice = embeddings.slice(i, i + BATCH_SIZE);\n\n for (let j = 0; j < slice.length; j++) {\n const chunk = slice[j];\n batch.set(col.doc(chunk.id), {\n source: chunk.source,\n url: chunk.url,\n title: chunk.title,\n heading_path: chunk.heading_path,\n content: chunk.content,\n token_count: chunk.token_count,\n tokens: extractIdentifierTokens(chunk.content),\n content_hash: contentHash(buildEmbedText(chunk)),\n embedded_at: new Date().toISOString(),\n embedding: FieldValue.vector(embSlice[j]),\n });\n }\n\n await retryOnTransient(() => batch.commit());\n onProgress?.(Math.min(i + BATCH_SIZE, chunks.length), chunks.length);\n\n if (i + BATCH_SIZE < chunks.length) {\n await sleep(INTER_BATCH_DELAY_MS);\n }\n }\n}\n\nexport async function purgeSource(sourceName: string): Promise<number> {\n const database = getDb();\n const col = chunksCol();\n\n const snapshot = await col.where(\"source\", \"==\", sourceName).get();\n if (snapshot.empty) return 0;\n\n let batch = database.batch();\n let count = 0;\n\n for (const doc of snapshot.docs) {\n batch.delete(doc.ref);\n count++;\n if (count % BATCH_SIZE === 0) {\n await retryOnTransient(() => batch.commit());\n batch = database.batch();\n }\n }\n\n if (count % BATCH_SIZE !== 0) {\n await retryOnTransient(() => batch.commit());\n }\n\n return count;\n}\n\nexport async function updateSourceMeta(\n sourceName: string,\n chunkCount: number,\n urlCount: number,\n version?: string,\n): Promise<void> {\n await sourcesCol().doc(sourceName).set({\n source: sourceName,\n ...(version ? { version } : {}),\n last_refreshed: new Date().toISOString(),\n chunk_count: chunkCount,\n url_count: urlCount,\n });\n}\n\nexport async function getSourceMeta(sourceName: string): Promise<SourceMeta | null> {\n const doc = await sourcesCol().doc(sourceName).get();\n if (!doc.exists) return null;\n return doc.data() as SourceMeta;\n}\n\nexport async function getAllSourcesMeta(): Promise<SourceMeta[]> {\n const snapshot = await sourcesCol().get();\n return snapshot.docs.map((doc) => doc.data() as SourceMeta);\n}\n\nexport async function deleteSourceMeta(sourceName: string): Promise<void> {\n await sourcesCol().doc(sourceName).delete();\n}\n\nexport async function deleteChunksByIds(\n ids: string[],\n onProgress?: (current: number, total: number) => void,\n): Promise<void> {\n if (ids.length === 0) return;\n const database = getDb();\n const col = chunksCol();\n\n for (let i = 0; i < ids.length; i += BATCH_SIZE) {\n const batch = database.batch();\n const slice = ids.slice(i, i + BATCH_SIZE);\n for (const id of slice) {\n batch.delete(col.doc(id));\n }\n await retryOnTransient(() => batch.commit());\n onProgress?.(Math.min(i + BATCH_SIZE, ids.length), ids.length);\n }\n}\n\n// id -> content_hash for every stored chunk of a source. Chunks stored before\n// hashing existed map to \"\", which never equals a real hash, so they are\n// re-embedded on the next refresh.\nexport async function getSourceChunkHashes(sourceName: string): Promise<Map<string, string>> {\n const col = chunksCol();\n const snapshot = await col.where(\"source\", \"==\", sourceName).select(\"content_hash\").get();\n return new Map(\n snapshot.docs.map((doc) => [doc.id, (doc.data().content_hash as string | undefined) ?? \"\"]),\n );\n}\n\nexport interface TokenHit {\n id: string;\n data: Record<string, unknown>;\n matchedTokens: number;\n}\n\nconst TOKEN_QUERY_LIMIT = 40;\nconst TOKEN_RESULT_FIELDS = [\"source\", \"url\", \"title\", \"heading_path\", \"content\", \"token_count\"] as const;\n\n// Exact-identifier lookup: one array-contains query per token, merged by id.\n// Embeddings are deliberately not fetched (740+ floats per doc); lexical-only\n// candidates carry no vector similarity. Sources stored before the tokens\n// field existed simply produce no hits.\nexport async function tokenSearch(\n tokens: string[],\n source?: string,\n): Promise<TokenHit[]> {\n if (tokens.length === 0) return [];\n const col = chunksCol();\n\n const hits = new Map<string, TokenHit>();\n\n const snapshots = await Promise.all(\n tokens.map((token) => {\n let query = col.where(\"tokens\", \"array-contains\", token);\n if (source) {\n query = query.where(\"source\", \"==\", source);\n }\n return query.select(...TOKEN_RESULT_FIELDS).limit(TOKEN_QUERY_LIMIT).get();\n }),\n );\n\n for (const snapshot of snapshots) {\n for (const doc of snapshot.docs) {\n const existing = hits.get(doc.id);\n if (existing) {\n existing.matchedTokens++;\n } else {\n hits.set(doc.id, { id: doc.id, data: doc.data(), matchedTokens: 1 });\n }\n }\n }\n\n return [...hits.values()].sort((a, b) => b.matchedTokens - a.matchedTokens);\n}\n\nexport async function vectorSearch(\n queryEmbedding: number[],\n limit: number,\n source?: string,\n): Promise<{ id: string; data: Record<string, unknown>; distance: number }[]> {\n const col = chunksCol();\n\n let query = col as FirebaseFirestore.Query;\n if (source) {\n query = query.where(\"source\", \"==\", source);\n }\n\n const snapshot = await query\n .findNearest({\n vectorField: \"embedding\",\n queryVector: FieldValue.vector(queryEmbedding),\n limit,\n distanceMeasure: \"COSINE\",\n distanceResultField: \"_distance\",\n })\n .get();\n\n return snapshot.docs.map((doc) => {\n const data = doc.data();\n const distance = (data._distance as number) ?? 0;\n delete data._distance;\n delete data.embedding;\n return { id: doc.id, data, distance };\n });\n}\n", "import { embedText } from \"./embedder.js\";\nimport { vectorSearch, tokenSearch } from \"./store.js\";\nimport { rerank, rerankDocText, RERANK_POOL_SIZE } from \"./reranker.js\";\nimport { extractQueryTokens } from \"./tokens.js\";\nimport type { SearchResult } from \"./types.js\";\n\nexport type { SearchResult };\n\nconst DEFAULT_CANDIDATES = 50;\nconst RRF_K = 60;\n\nfunction hasReranker(): boolean {\n return !!process.env.RERANKER_URL;\n}\n\ninterface Candidate {\n id: string;\n data: Record<string, unknown>;\n fusedScore: number;\n similarity: number | null;\n}\n\nfunction contextualText(data: Record<string, unknown>): string {\n return rerankDocText(\n data.title as string,\n data.heading_path as string[],\n data.content as string,\n );\n}\n\nfunction toSearchResult(candidate: Candidate, relevance: number): SearchResult {\n const data = candidate.data;\n return {\n id: candidate.id,\n source: data.source as string,\n url: data.url as string,\n title: data.title as string,\n heading_path: data.heading_path as string[],\n content: data.content as string,\n relevance_score: relevance,\n };\n}\n\n// Hybrid retrieval: dense vector candidates fused with exact-identifier\n// token hits via reciprocal rank fusion. The lexical leg recovers identifiers\n// (max_connections, ExportContext) that sit outside the vector pool because\n// their chunk's embedding is dominated by sibling identifiers.\nexport async function search(\n query: string,\n options: { source?: string; candidates?: number; topN?: number } = {},\n): Promise<SearchResult[]> {\n const { source, candidates = DEFAULT_CANDIDATES, topN = 5 } = options;\n\n const queryEmbedding = await embedText(query);\n const [vectorResults, lexicalHits] = await Promise.all([\n vectorSearch(queryEmbedding, candidates, source),\n tokenSearch(extractQueryTokens(query), source),\n ]);\n\n const pool = new Map<string, Candidate>();\n\n vectorResults.forEach((result, rank) => {\n pool.set(result.id, {\n id: result.id,\n data: result.data,\n fusedScore: 1 / (RRF_K + rank + 1),\n similarity: 1 - result.distance,\n });\n });\n\n lexicalHits.forEach((hit, rank) => {\n const lexicalScore = 1 / (RRF_K + rank + 1);\n const existing = pool.get(hit.id);\n if (existing) {\n existing.fusedScore += lexicalScore;\n return;\n }\n pool.set(hit.id, {\n id: hit.id,\n data: hit.data,\n fusedScore: lexicalScore,\n similarity: null,\n });\n });\n\n const fused = [...pool.values()].sort((a, b) => b.fusedScore - a.fusedScore);\n if (fused.length === 0) return [];\n\n if (hasReranker()) {\n const rerankPool = fused.slice(0, RERANK_POOL_SIZE);\n const documents = rerankPool.map((c) => contextualText(c.data));\n const reranked = await rerank(query, documents, topN);\n return reranked.map((r) => toSearchResult(rerankPool[r.index], r.relevance_score));\n }\n\n return fused.slice(0, topN).map((candidate) => {\n const similarity = candidate.similarity ?? 0;\n return toSearchResult(candidate, Math.max(0, (1 + similarity) / 2));\n });\n}\n", "import { randomBytes, createHash } from \"node:crypto\";\nimport {\n getFirestore,\n type Firestore,\n} from \"firebase-admin/firestore\";\nimport { initializeApp, applicationDefault, getApps } from \"firebase-admin/app\";\nimport { bold } from \"./format.js\";\n\nlet db: Firestore | undefined;\n\nfunction getDb(): Firestore {\n if (!db) {\n if (getApps().length === 0) {\n initializeApp({ credential: applicationDefault() });\n }\n db = getFirestore();\n }\n return db;\n}\n\nfunction hashKey(key: string): string {\n return createHash(\"sha256\").update(key).digest(\"hex\");\n}\n\nfunction apiKeysCol() {\n return getDb().collection(\"grimoire_api_keys\");\n}\n\nexport async function createApiKey(name: string): Promise<string> {\n const raw = `grim_${randomBytes(32).toString(\"base64url\")}`;\n const hash = hashKey(raw);\n\n await apiKeysCol().doc(hash).set({\n name,\n created_at: new Date().toISOString(),\n last_used_at: null,\n });\n\n return raw;\n}\n\nexport async function listApiKeys(): Promise<void> {\n const snapshot = await apiKeysCol().get();\n\n if (snapshot.empty) {\n console.log(\"No API keys found.\");\n return;\n }\n\n console.log(\"\\nAPI Keys:\\n\");\n for (const doc of snapshot.docs) {\n const data = doc.data();\n const lastUsed = data.last_used_at ?? \"never\";\n console.log(` ${bold(data.name)}`);\n console.log(` Created: ${data.created_at}`);\n console.log(` Last used: ${lastUsed}`);\n }\n}\n\nexport async function deleteApiKey(name: string): Promise<void> {\n const snapshot = await apiKeysCol().where(\"name\", \"==\", name).get();\n\n if (snapshot.empty) {\n throw new Error(`No API key found with name \"${name}\".`);\n }\n\n for (const doc of snapshot.docs) {\n await doc.ref.delete();\n }\n\n console.log(`API key \"${name}\" deleted.`);\n}\n\nexport async function cmdApiKey(): Promise<void> {\n const subcommand = process.argv[3];\n const arg = process.argv[4];\n\n if (subcommand === \"create\") {\n if (!arg) {\n console.error(\"Usage: grimoire apikey create <name>\");\n process.exit(1);\n }\n const key = await createApiKey(arg);\n console.log(`\\nAPI key created for \"${arg}\":\\n`);\n console.log(` ${key}\\n`);\n console.log(\"Save this key \u2014 it will not be shown again.\");\n } else if (subcommand === \"list\") {\n await listApiKeys();\n } else if (subcommand === \"delete\") {\n if (!arg) {\n console.error(\"Usage: grimoire apikey delete <name>\");\n process.exit(1);\n }\n await deleteApiKey(arg);\n } else {\n console.error(\"Usage: grimoire apikey <create|list|delete> [name]\");\n process.exit(1);\n }\n}\n", "import { writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { slugifyUrl } from \"./slug.js\";\nimport { buildFrontmatter, type ConvertedPage } from \"./converter.js\";\n\ninterface LlmsPage {\n title: string;\n url: string;\n markdown: string;\n}\n\nconst BOILERPLATE_PATTERNS = [\n /^\\[Skip to content\\]\\([^)]*\\)\\s*$/gm,\n /^Was this helpful\\?\\s*$/gm,\n /^YesNo\\s*$/gm,\n /^\\[ Edit page \\]\\([^)]+\\) \\[ Report issue \\]\\([^)]+\\)\\s*$/gm,\n /^Copy page\\s*$/gm,\n /^```json\\n\\{\"@context\":\"https:\\/\\/schema\\.org\",\"@type\":\"BreadcrumbList\"[^`]*```\\s*$/gm,\n];\n\nfunction splitPages(content: string): LlmsPage[] {\n const pages: LlmsPage[] = [];\n const frontmatterPattern = /^---\\ntitle: (.+)\\n/gm;\n const boundaries: { index: number; title: string }[] = [];\n\n let match;\n while ((match = frontmatterPattern.exec(content)) !== null) {\n boundaries.push({ index: match.index, title: match[1] });\n }\n\n for (let i = 0; i < boundaries.length; i++) {\n const start = boundaries[i].index;\n const end = i + 1 < boundaries.length ? boundaries[i + 1].index : content.length;\n const raw = content.slice(start, end).trimEnd();\n\n const url = extractUrl(raw);\n if (!url) continue;\n\n const bodyStart = raw.indexOf(\"---\", 3);\n if (bodyStart === -1) continue;\n const body = raw.slice(raw.indexOf(\"\\n\", bodyStart) + 1);\n\n let cleaned = body;\n for (const pattern of BOILERPLATE_PATTERNS) {\n cleaned = cleaned.replace(pattern, \"\");\n }\n cleaned = cleaned.replace(/\\n{3,}/g, \"\\n\\n\").trim();\n\n if (!cleaned) continue;\n\n pages.push({\n title: boundaries[i].title,\n url,\n markdown: cleaned,\n });\n }\n\n return pages;\n}\n\nfunction extractUrl(pageContent: string): string | null {\n const match = pageContent.match(\n /```json\\n\\{\"@context\":\"https:\\/\\/schema\\.org\",\"@type\":\"BreadcrumbList\",\"itemListElement\":\\[(.+?)\\]\\}\\n```/,\n );\n if (!match) return null;\n\n const items = JSON.parse(`[${match[1]}]`) as { item?: { \"@id\": string } }[];\n const last = items[items.length - 1];\n if (!last?.item?.[\"@id\"]) return null;\n\n return `https://developers.cloudflare.com${last.item[\"@id\"]}`;\n}\n\nexport async function ingestLlmsFull(\n llmsFullUrl: string,\n sourceName: string,\n baseUrl: string,\n dataDir: string,\n onProgress?: (current: number, total: number) => void,\n): Promise<ConvertedPage[]> {\n const response = await fetch(llmsFullUrl);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${llmsFullUrl}: ${response.status} ${response.statusText}`);\n }\n const content = await response.text();\n\n const pages = splitPages(content);\n\n const mdDir = join(dataDir, \"markdown\", sourceName);\n await mkdir(mdDir, { recursive: true });\n\n const results: ConvertedPage[] = [];\n for (let i = 0; i < pages.length; i++) {\n const page = pages[i];\n const frontmatter = buildFrontmatter(sourceName, page.url, page.title);\n const fullMarkdown = `${frontmatter}\\n\\n${page.markdown}`;\n const slug = slugifyUrl(page.url);\n await writeFile(join(mdDir, `${slug}.md`), fullMarkdown, \"utf-8\");\n\n results.push({\n source: sourceName,\n url: page.url,\n title: page.title,\n markdown: fullMarkdown,\n });\n\n if (onProgress && ((i + 1) % 100 === 0 || i + 1 === pages.length)) {\n onProgress(i + 1, pages.length);\n }\n }\n\n return results;\n}\n"],
5
- "mappings": ";;;;;;;;;;;;AAAA;AAAA;AAAA;AAEA,WAAO,eAAe,SAAS,cAAc,EAAE,OAAO,KAAK,CAAC;AAE5D,QAAI,kBAAkB;AAEtB,aAAS,qBAAsB,iBAAiB;AAC9C,sBAAgB,QAAQ,wBAAwB;AAAA,QAC9C,QAAQ,SAAU,MAAM;AACtB,cAAI,aAAa,KAAK;AACtB,iBACE,KAAK,aAAa,SAClB,gBAAgB,KAAK,KAAK,SAAS,KACnC,cACA,WAAW,aAAa;AAAA,QAE5B;AAAA,QACA,aAAa,SAAU,SAAS,MAAM,SAAS;AAC7C,cAAI,YAAY,KAAK,aAAa;AAClC,cAAI,YAAY,UAAU,MAAM,eAAe,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC;AAEjE,iBACE,SAAS,QAAQ,QAAQ,WAAW,OACpC,KAAK,WAAW,cAChB,OAAO,QAAQ,QAAQ;AAAA,QAE3B;AAAA,MACF,CAAC;AAAA,IACH;AAEA,aAAS,cAAe,iBAAiB;AACvC,sBAAgB,QAAQ,iBAAiB;AAAA,QACvC,QAAQ,CAAC,OAAO,KAAK,QAAQ;AAAA,QAC7B,aAAa,SAAU,SAAS;AAC9B,iBAAO,OAAO,UAAU;AAAA,QAC1B;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,UAAU,MAAM,UAAU;AAC9B,QAAI,QAAQ,MAAM,UAAU;AAC5B,QAAI,QAAQ,CAAC;AACb,QAAI,WAAW,EAAE,MAAM,QAAQ,OAAO,QAAQ,QAAQ,QAAQ;AAE9D,QAAI,eAAe;AACnB,QAAI,WAAW;AAKf,QAAM,6BAA6B,oBAAI,QAAQ;AAE/C,aAAS,aAAa,MAAM;AAC1B,aAAO,QAAQ,KAAK,aAAa,OAAO,KAAK,KAAK,MAAM,aAAa,IAAI,YAAY,IAAI;AAAA,IAC3F;AAEA,aAAS,UAAU,WAAW;AAC5B,aAAO,YAAY,SAAS,SAAS,IAAI;AAAA,IAC3C;AAEA,aAAS,mBAAmB,OAAO,aAAa;AAC9C,UAAI,QAAQ;AAAA,QACV,MAAM;AAAA,QACN,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,IAAI;AAAA,MACN;AAEA,UAAI,QAAQ;AAEZ,eAAS,IAAI,GAAG,IAAI,MAAM,KAAK,QAAQ,EAAE,GAAG;AAC1C,YAAI,MAAM,MAAM,KAAK,CAAC;AACtB,YAAI,cAAc,IAAI,WAAW,QAAQ;AACvC,cAAI,gBAAgB,aAAa,IAAI,WAAW,WAAW,CAAC;AAC5D,YAAE,MAAM,aAAa;AAErB,cAAI,MAAM,aAAa,IAAI,MAAM,KAAK,GAAG;AACvC,oBAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAEA,UAAM,YAAY;AAAA,MAChB,QAAQ,CAAC,MAAM,IAAI;AAAA,MACnB,aAAa,SAAU,SAAS,MAAM;AACpC,YAAI,qBAAqB,gBAAgB,IAAI,CAAC,EAAG,QAAO;AACxD,eAAO,KAAK,SAAS,IAAI;AAAA,MAC3B;AAAA,IACF;AAEA,UAAM,WAAW;AAAA,MACf,QAAQ;AAAA,MACR,aAAa,SAAU,SAAS,MAAM;AACpC,cAAM,cAAc,gBAAgB,IAAI;AACxC,YAAI,qBAAqB,WAAW,EAAG,QAAO;AAE9C,YAAI,cAAc;AAElB,YAAI,aAAa,IAAI,GAAG;AACtB,gBAAM,WAAW,cAAc,WAAW;AAC1C,mBAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,kBAAM,YAAY,IAAI,KAAK,WAAW,SAAS,KAAK,WAAW,CAAC,IAAI;AACpE,gBAAI,SAAS,UAAU,mBAAmB,aAAa,CAAC,CAAC;AACzD,2BAAe,KAAK,QAAQ,WAAW,CAAC;AAAA,UAC1C;AAAA,QACF;AACA,eAAO,OAAO,WAAW,cAAc,OAAO,cAAc;AAAA,MAC9D;AAAA,IACF;AAEA,UAAM,QAAQ;AAAA,MACZ,QAAQ,SAAU,MAAM,SAAS;AAC/B,eAAO,KAAK,aAAa;AAAA,MAC3B;AAAA,MAEA,aAAa,SAAU,SAAS,MAAM;AAGpC,YAAI,kBAAkB,MAAM,QAAQ,GAAG;AACrC,cAAI,OAAO,KAAK;AAChB,cAAI,YAAY,cAAc,IAAI;AAYlC,cAAI,cAAc,QAAQ,CAAC,UAAU,UAAU,SAAS,sBAAsB,GAAE;AAC9E,mBAAO;AAAA;AAAA,oCAAyC,IAAI;AAAA;AAAA;AAAA,UACtD,OAAO;AACL,mBAAO;AAAA,UACT;AAAA,QACF,OAAO;AACL,cAAI,qBAAqB,IAAI,EAAG,QAAO;AAGvC,oBAAU,QAAQ,QAAQ,QAAQ,IAAI;AAGtC,cAAI,aAAa,QAAQ,KAAK,EAAE,MAAM,IAAI;AAC1C,cAAI,WAAW,UAAU,EAAG,cAAa,WAAW,CAAC;AACrD,cAAI,sBAAsB,WAAW,KAAK,UAAU;AAEpD,cAAI,cAAc,cAAc,IAAI;AACpC,cAAI,cAAc;AAClB,cAAI,eAAe,CAAC,qBAAqB;AACvC,0BAAc,MAAM,SAAS,OAAO,WAAW,IAAI;AACnD,qBAAS,cAAc,GAAG,cAAc,aAAa,EAAE,aAAa;AAClE,6BAAe,MAAM,UAAU,mBAAmB,MAAM,WAAW,CAAC,IAAI;AAAA,YAC1E;AAAA,UACF;AAEA,gBAAM,cAAc,KAAK,gBAAgB,KAAK,cAAc,SAAS,IAAI,KAAK;AAC9E,gBAAM,iBAAiB,cAAc,YAAY,eAAe,KAAK;AACrE,gBAAM,UAAU,iBAAiB,GAAG,cAAc;AAAA;AAAA,IAAS;AAC3D,gBAAM,eAAe,GAAG,WAAW,GAAG,OAAO,GAAG,UAAU;AAC1D,iBAAO;AAAA;AAAA,EAAO,OAAO,GAAG,YAAY;AAAA;AAAA;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAEA,UAAM,eAAe;AAAA,MACnB,QAAQ,CAAC,SAAS;AAAA,MAClB,aAAa,MAAM;AAAA,IACrB;AAEA,UAAM,gBAAgB;AAAA,MACpB,QAAQ,CAAC,YAAY,KAAK;AAAA,MAC1B,aAAa,MAAM;AAAA,IACrB;AAEA,UAAM,eAAe;AAAA,MACnB,QAAQ,CAAC,SAAS,SAAS,OAAO;AAAA,MAClC,aAAa,SAAU,SAAS;AAC9B,eAAO;AAAA,MACT;AAAA,IACF;AAOA,aAAS,aAAc,IAAI;AACzB,UAAI,aAAa,GAAG;AACpB,aACE,WAAW,aAAa,WAEtB,WAAW,eAAe,OACzB,WAAW,aAAa,WAAW,aAAa,UAAU,MAC3D,MAAM,KAAK,GAAG,YAAY,SAAU,GAAG;AAAE,eAAO,EAAE,aAAa;AAAA,MAAK,CAAC;AAAA,IAG3E;AAEA,aAAS,aAAc,SAAS;AAC9B,UAAI,kBAAkB,QAAQ;AAC9B,aACE,QAAQ,aAAa,YACnB,CAAC,mBAEC,gBAAgB,aAAa,WAC7B,SAAS,KAAK,gBAAgB,WAAW;AAAA,IAIjD;AAEA,aAAS,KAAM,SAAS,OAAO,MAAM,QAAQ,MAAM;AACjD,UAAI,UAAU,KAAM,SAAQ,QAAQ,KAAK,KAAK,WAAW,YAAY,IAAI;AACzE,UAAI,SAAS;AACb,UAAI,UAAU,EAAG,UAAS;AAC1B,UAAI,kBAAkB,QAAQ,KAAK,EAAE,QAAQ,SAAS,MAAM,EAAE,QAAQ,OAAO,MAAM;AACnF,wBAAkB,gBAAgB,QAAQ,QAAQ,KAAK;AACvD,aAAO,gBAAgB,SAAS,EAAG,oBAAmB;AACtD,UAAI,KAAM,mBAAkB,cAAc,iBAAiB,MAAM,GAAG;AACpE,aAAO,SAAS,kBAAkB;AAAA,IACpC;AAEA,aAAS,kBAAkB,MAAM;AAC/B,UAAI,CAAC,KAAK,WAAY,QAAO;AAE7B,eAAS,IAAI,GAAG,IAAI,KAAK,WAAW,QAAQ,KAAK;AAC/C,cAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,YAAI,MAAM,aAAa,QAAS,QAAO;AACvC,YAAI,kBAAkB,KAAK,EAAG,QAAO;AAAA,MACvC;AACA,aAAO;AAAA,IACT;AAEA,QAAM,eAAe,CAAC,MAAM,UAAU;AACpC,UAAI,CAAC,KAAK,WAAY,QAAO;AAE7B,eAAS,IAAI,GAAG,IAAI,KAAK,WAAW,QAAQ,KAAK;AAC/C,cAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,YAAI,UAAU,UAAU,gBAAgB,aAAa,KAAK,EAAG,QAAO;AACpE,YAAI,MAAM,SAAS,MAAM,QAAQ,EAAG,QAAO;AAC3C,YAAI,aAAa,OAAO,KAAK,EAAG,QAAO;AAAA,MACzC;AAEA,aAAO;AAAA,IACT;AAQA,QAAM,wBAAwB;AAAA,MAC5B;AAAA,MAAoB;AAAA,MACpB;AAAA,MAAgB;AAAA,MAChB;AAAA,MAAc;AAAA,MAAgB;AAAA,MAAiB;AAAA,MAC/C;AAAA,MAAgB;AAAA,MAChB;AAAA,MAAW;AAAA,MAAe;AAAA,MAAiB;AAAA,MAAkB;AAAA,MAC7D;AAAA,MAAS;AAAA,MAAe;AAAA,IAC1B;AAGA,QAAM,uBAAuB;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,QAAM,qBAAqB,CAAC,SAAS;AACnC,UAAI,CAAC,QAAQ,CAAC,KAAK,aAAc,QAAO;AACxC,YAAM,YAAY,KAAK,aAAa,OAAO;AAC3C,UAAI,CAAC,UAAW,QAAO;AAEvB,YAAM,aAAa,UAAU,MAAM,GAAG,EACnC,IAAI,OAAK,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,EAC7C,OAAO,OAAK,EAAE,SAAS,CAAC;AAC3B,eAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,YAAI,sBAAsB,SAAS,WAAW,CAAC,CAAC,EAAG,QAAO;AAAA,MAC5D;AACA,aAAO;AAAA,IACT;AAEA,QAAM,gCAAgC,CAAC,MAAM,SAAS;AACpD,UAAI,CAAC,QAAQ,CAAC,KAAK,aAAc,QAAO;AACxC,YAAM,QAAQ,KAAK,aAAa,IAAI;AACpC,UAAI,UAAU,KAAM,QAAO;AAC3B,YAAM,kBAAkB,GAAG,KAAK,GAAG,KAAK,EAAE,YAAY;AACtD,UAAI,CAAC,gBAAiB,QAAO;AAC7B,UAAI,oBAAoB,OAAO,oBAAoB,MAAO,QAAO;AACjE,aAAO;AAAA,IACT;AAEA,QAAM,0BAA0B,CAAC,SAAS;AACxC,UAAI,CAAC,QAAQ,CAAC,KAAK,aAAc,QAAO;AAExC,eAAS,IAAI,GAAG,IAAI,qBAAqB,QAAQ,KAAK;AACpD,cAAM,QAAQ,KAAK,aAAa,qBAAqB,CAAC,CAAC;AACvD,YAAI,UAAU,QAAQ,GAAG,KAAK,GAAG,KAAK,MAAM,GAAI,QAAO;AAAA,MACzD;AAEA,UAAI,KAAK,aAAa,SAAS;AAC7B,YAAI,8BAA8B,MAAM,aAAa,EAAG,QAAO;AAC/D,YAAI,8BAA8B,MAAM,aAAa,EAAG,QAAO;AAAA,MACjE;AAEA,aAAO;AAAA,IACT;AAEA,QAAM,0BAA0B,CAAC,SAAS;AACxC,aAAO,mBAAmB,IAAI,KAAK,wBAAwB,IAAI;AAAA,IACjE;AAGA,QAAM,uBAAuB,CAAC,cAAc;AAC1C,UAAI,wBAAwB,SAAS,EAAG,QAAO;AAE/C,YAAM,OAAO,UAAU;AACvB,UAAI,CAAC,KAAM,QAAO;AAElB,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,cAAM,MAAM,KAAK,CAAC;AAClB,YAAI,wBAAwB,GAAG,EAAG,QAAO;AACzC,iBAAS,IAAI,GAAG,IAAI,IAAI,WAAW,QAAQ,KAAK;AAC9C,gBAAMA,QAAO,IAAI,WAAW,CAAC;AAC7B,eAAKA,MAAK,aAAa,QAAQA,MAAK,aAAa,SAAS,wBAAwBA,KAAI,GAAG;AACvF,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAEA,QAAM,oBAAoB,CAAC,WAAW,YAAY;AAChD,YAAM,eAAe;AAAA,QACnB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAOA,UAAI,QAAQ,qBAAsB,cAAa,KAAK,OAAO;AAE3D,aAAO,aAAa,WAAW,MAAM,KACnC,aAAa,WAAW,YAAY,KACnC,QAAQ,uBAAuB,qBAAqB,SAAS;AAAA,IAClE;AAIA,aAAS,qBAAqB,WAAW;AACvC,YAAM,SAAS,2BAA2B,IAAI,SAAS;AACvD,UAAI,WAAW,OAAW,QAAO;AAEjC,YAAM,SAAS,sBAAsB,SAAS;AAE9C,iCAA2B,IAAI,WAAW,MAAM;AAChD,aAAO;AAAA,IACT;AAEA,aAAS,sBAAsB,WAAW;AACxC,UAAI,CAAC,UAAW,QAAO;AACvB,UAAI,CAAC,UAAU,KAAM,QAAO;AAC5B,UAAI,UAAU,KAAK,WAAW,KAAK,UAAU,KAAK,CAAC,EAAE,WAAW,UAAU,EAAG,QAAO;AACpF,UAAI,kBAAkB,SAAS,EAAG,QAAO;AACzC,aAAO;AAAA,IACT;AAEA,aAAS,cAAc,MAAM;AAC3B,UAAI,SAAS,KAAK;AAClB,aAAO,OAAO,aAAa,OAAO;AAChC,iBAAS,OAAO;AAChB,YAAI,CAAC,OAAQ,QAAO;AAAA,MACtB;AACA,aAAO;AAAA,IACT;AAEA,aAAS,gBAAgB,MAAM;AAC7B,UAAI,SAAS,KAAK;AAClB,aAAO,OAAO,aAAa,SAAS;AAClC,iBAAS,OAAO;AAChB,YAAI,CAAC,OAAQ,QAAO;AAAA,MACtB;AACA,aAAO;AAAA,IACT;AAEA,aAAS,cAAc,SAAS,MAAM,WAAW;AAC/C,YAAM,UAAU,KAAK,aAAa,SAAS,KAAK;AAChD,eAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,mBAAW,QAAQ,UAAU,OAAO,CAAC;AAAA,MACvC;AACA,aAAO;AAAA,IACT;AAEA,aAAS,cAAc,MAAM;AAC3B,UAAI,cAAc;AAClB,eAAS,IAAI,GAAG,IAAI,KAAK,KAAK,QAAQ,KAAK;AACzC,cAAM,MAAM,KAAK,KAAK,CAAC;AACvB,cAAM,WAAW,IAAI,WAAW;AAChC,YAAI,WAAW,YAAa,eAAc;AAAA,MAC5C;AACA,aAAO;AAAA,IACT;AAEA,aAASC,QAAQ,iBAAiB;AAChC,qBAAe,gBAAgB;AAC/B,iBAAW,gBAAgB;AAE3B,sBAAgB,KAAK,SAAU,MAAM;AACnC,YAAI,KAAK,aAAa,WAAW,kBAAkB,MAAM,gBAAgB,OAAO,EAAG,QAAO;AAC1F,eAAO;AAAA,MACT,CAAC;AACD,eAAS,OAAO,MAAO,iBAAgB,QAAQ,KAAK,MAAM,GAAG,CAAC;AAAA,IAChE;AAEA,aAAS,cAAe,iBAAiB;AACvC,sBAAgB,QAAQ,iBAAiB;AAAA,QACvC,QAAQ,SAAU,MAAM;AACtB,gBAAM,SAAS,KAAK;AACpB,gBAAM,cAAc,OAAO;AAC3B,gBAAM,wBAAwB,CAAC,CAAC,eAAe,YAAY,aAAa;AACxE,kBAAQ,KAAK,SAAS,cAAc,KAAK,aAAa,MAAM,MAAM,gBAChE,OAAO,aAAa,QAGhB,OAAO,aAAa,WAAW,yBAG/B,OAAO,aAAa,UAAU;AAAA,QAEtC;AAAA,QACA,aAAa,SAAU,SAAS,MAAM;AACpC,gBAAM,UAAU,KAAK,aAAa,UAAU,KAAK,UAAU,KAAK,aAAa,cAAc,MAAM;AACjG,kBAAQ,UAAU,QAAQ,SAAS;AAAA,QACrC;AAAA,MACF,CAAC;AAAA,IACH;AAEA,aAAS,IAAK,iBAAiB;AAC7B,sBAAgB,IAAI;AAAA,QAClB;AAAA,QACA;AAAA,QACAA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAEA,YAAQ,MAAM;AACd,YAAQ,uBAAuB;AAC/B,YAAQ,gBAAgB;AACxB,YAAQ,SAASA;AACjB,YAAQ,gBAAgB;AAAA;AAAA;;;ACndxB,SAAS,iBAAiB;AAC1B,SAAS,YAAAC,WAAU,aAAAC,YAAW,SAAS,UAAU;AACjD,SAAS,kBAAkB;AAC3B,SAAS,QAAAC,OAAM,eAAe;AAC9B,SAAS,uBAAuB;AAChC,SAAS,iBAAiB;;;ACL1B,SAAS,gBAAgB;AACzB,SAAS,aAAa;AAyBtB,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AACF;AAEA,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AACF;AAEO,SAAS,eAAe,MAA+B;AAC5D,MAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,aAAa,OAAO;AACrE,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AAEA,QAAM,EAAE,QAAQ,IAAI;AAEpB,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AAEA,QAAM,UAAU,OAAO,QAAQ,OAAkC;AAEjE,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AAEA,QAAM,YAA0C,CAAC;AAEjD,aAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AAClC,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,YAAM,IAAI,MAAM,WAAW,GAAG,qBAAqB;AAAA,IACrD;AAEA,UAAM,SAAS;AAEf,eAAW,SAAS,wBAAwB;AAC1C,UAAI,OAAO,OAAO,KAAK,MAAM,YAAY,OAAO,KAAK,MAAM,IAAI;AAC7D,cAAM,IAAI,MAAM,WAAW,GAAG,gCAAgC,KAAK,GAAG;AAAA,MACxE;AAAA,IACF;AAEA,QAAI,CAAC,OAAO,eAAe;AACzB,iBAAW,SAAS,wBAAwB;AAC1C,YAAI,OAAO,OAAO,KAAK,MAAM,YAAY,OAAO,KAAK,MAAM,IAAI;AAC7D,gBAAM,IAAI,MAAM,WAAW,GAAG,gCAAgC,KAAK,GAAG;AAAA,QACxE;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACF,UAAI,IAAI,OAAO,SAAmB;AAAA,IACpC,QAAQ;AACN,YAAM,IAAI,MAAM,WAAW,GAAG,4BAA4B,OAAO,SAAS,EAAE;AAAA,IAC9E;AAEA,QAAI,OAAO,qBAAqB,UAAa,CAAC,MAAM,QAAQ,OAAO,gBAAgB,GAAG;AACpF,YAAM,IAAI,MAAM,WAAW,GAAG,sCAAsC;AAAA,IACtE;AAEA,QAAI,OAAO,yBAAyB,UAAa,CAAC,MAAM,QAAQ,OAAO,oBAAoB,GAAG;AAC5F,YAAM,IAAI,MAAM,WAAW,GAAG,0CAA0C;AAAA,IAC1E;AAEA,QAAI,OAAO,qBAAqB,UAAa,CAAC,MAAM,QAAQ,OAAO,gBAAgB,GAAG;AACpF,YAAM,IAAI,MAAM,WAAW,GAAG,sCAAsC;AAAA,IACtE;AAEA,QAAI,OAAO,qBAAqB,UAAa,CAAC,MAAM,QAAQ,OAAO,gBAAgB,GAAG;AACpF,YAAM,IAAI,MAAM,WAAW,GAAG,sCAAsC;AAAA,IACtE;AAEA,QAAI,OAAO,kBAAkB,UAAa,OAAO,OAAO,kBAAkB,UAAU;AAClF,YAAM,IAAI,MAAM,WAAW,GAAG,mCAAmC;AAAA,IACnE;AAEA,QAAI,OAAO,gBAAgB,UAAa,OAAO,OAAO,gBAAgB,UAAU;AAC9E,YAAM,IAAI,MAAM,WAAW,GAAG,iCAAiC;AAAA,IACjE;AAEA,cAAU,GAAG,IAAI;AAAA,MACf,MAAM,OAAO;AAAA,MACb,SAAS,OAAO;AAAA,MAChB,WAAW,OAAO;AAAA,MAClB,cAAc,OAAO;AAAA,MACrB,kBAAkB,OAAO;AAAA,MACzB,kBAAkB,OAAO;AAAA,MACzB,sBAAsB,OAAO;AAAA,MAC7B,kBAAkB,OAAO;AAAA,MACzB,kBAAkB,OAAO;AAAA,MACzB,eAAe,OAAO;AAAA,MACtB,aAAa,OAAO;AAAA,MACpB,QAAQ,OAAO;AAAA,MACf,aAAa,OAAO;AAAA,MACpB,YAAY,OAAO;AAAA,MACnB,eAAe,OAAO;AAAA,MACtB,mBAAmB,OAAO;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,UAAU;AAC9B;AAEA,eAAsB,WAAW,MAAuC;AACtE,QAAM,UAAU,MAAM,SAAS,MAAM,OAAO;AAC5C,QAAM,OAAO,MAAM,SAAS,EAAE,OAAO,KAAK,CAAC;AAC3C,SAAO,eAAe,IAAI;AAC5B;;;ACrIA,SAAS,OAAO,iBAAiB;AACjC,SAAS,YAAY;AACrB,SAAS,gBAAyC;;;ACA3C,SAAS,WAAW,KAAqB;AAC9C,QAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,SAAO,OAAO,SACX,QAAQ,OAAO,EAAE,EACjB,QAAQ,OAAO,EAAE,EACjB,QAAQ,OAAO,GAAG,EAClB,QAAQ,kBAAkB,EAAE;AACjC;;;ADHO,SAAS,WACd,MACA,iBACA,iBACU;AAGV,MAAI,WAAW,KACZ,IAAI,CAAC,QAAQ,IAAI,MAAM,GAAG,EAAE,CAAC,CAAC,EAC9B,OAAO,CAAC,QAAQ,IAAI,WAAW,MAAM,KAAK,CAAC,IAAI,SAAS,MAAM,CAAC;AAElE,MAAI,mBAAmB,gBAAgB,SAAS,GAAG;AACjD,eAAW,SAAS;AAAA,MAAO,CAAC,QAC1B,gBAAgB,KAAK,CAAC,YAAY,IAAI,SAAS,OAAO,CAAC;AAAA,IACzD;AAAA,EACF;AAEA,MAAI,mBAAmB,gBAAgB,SAAS,GAAG;AACjD,eAAW,SAAS;AAAA,MAClB,CAAC,QAAQ,CAAC,gBAAgB,KAAK,CAAC,YAAY,IAAI,SAAS,OAAO,CAAC;AAAA,IACnE;AAAA,EACF;AAEA,SAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,EAAE,KAAK;AACrC;AAEA,eAAe,iBAAiB,YAAuC;AACrE,QAAM,WAAW,MAAM,MAAM,UAAU;AACvC,QAAM,MAAM,MAAM,SAAS,KAAK;AAChC,QAAM,OAAO,CAAC,GAAG,IAAI,SAAS,sBAAsB,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAEtE,MAAI,IAAI,SAAS,eAAe,GAAG;AACjC,UAAM,SAAS,MAAM,QAAQ,IAAI,KAAK,IAAI,CAAC,QAAQ,iBAAiB,GAAG,CAAC,CAAC;AACzE,WAAO,OAAO,KAAK;AAAA,EACrB;AAEA,SAAO;AACT;AAEA,eAAe,oBAAoB,YAAoB,QAAyC;AAC9F,QAAM,OAAO,MAAM,iBAAiB,UAAU;AAC9C,SAAO,WAAW,MAAM,OAAO,kBAAkB,OAAO,gBAAgB;AAC1E;AAEA,eAAsB,aACpB,MACA,QACmB;AACnB,MAAI,OAAO,aAAa;AACtB,WAAO,oBAAoB,OAAO,aAAa,MAAM;AAAA,EACvD;AAEA,QAAM,KAAK,KAAK,OAAO,WAAW,EAAE,WAAW,OAAO,SAAS,gBAAgB,mBAAmB,CAAC;AAEnG,QAAM,UAAU,MAAM,KAAK;AAAA,IACzB,GAAG,OAAO,YAAY;AAAA,IACtB,CAAC,UAAU,MAAM,IAAI,CAAC,MAAO,EAAwB,IAAI;AAAA,EAC3D;AAEA,QAAM,aAAa,WAAW,SAAS,OAAO,kBAAkB,OAAO,gBAAgB;AACvF,MAAI,CAAC,WAAW,SAAS,OAAO,SAAS,GAAG;AAC1C,eAAW,QAAQ,OAAO,SAAS;AAAA,EACrC;AACA,SAAO;AACT;AAEA,IAAM,wBAAwB;AAC9B,IAAM,iBAAiB;AAEvB,eAAsB,UACpB,MACA,KACA,QACA,iBACiB;AACjB,MAAI;AACF,UAAM,KAAK,KAAK,KAAK;AAAA,MACnB,WAAW,SAAS,gBAAgB;AAAA,MACpC,SAAS,SAAS,wBAAwB;AAAA,IAC5C,CAAC;AAAA,EACH,SAAS,KAAK;AAIZ,QAAI,EAAE,eAAe,UAAU,CAAC,8BAA8B,KAAK,IAAI,OAAO,GAAG;AAC/E,YAAM;AAAA,IACR;AAAA,EACF;AACA,MAAI,iBAAiB;AACnB,UAAM,KAAK,gBAAgB,iBAAiB,EAAE,SAAS,sBAAsB,CAAC;AAAA,EAChF;AACA,SAAO,KAAK,QAAQ;AACtB;AAEA,IAAM,sBAAsB;AAE5B,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAACC,aAAY,WAAWA,UAAS,EAAE,CAAC;AACzD;AAEA,eAAe,QACb,OACA,aACA,aACA,IACe;AACf,MAAI,YAAY;AAEhB,iBAAe,SAAwB;AACrC,QAAI,UAAU;AACd,WAAO,YAAY,MAAM,QAAQ;AAC/B,YAAM,QAAQ;AACd,UAAI,CAAC,WAAW,cAAc,GAAG;AAC/B,cAAM,MAAM,WAAW;AAAA,MACzB;AACA,gBAAU;AACV,YAAM,GAAG,MAAM,KAAK,GAAG,KAAK;AAAA,IAC9B;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,aAAa,MAAM,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC;AAC1F,QAAM,QAAQ,IAAI,OAAO;AAC3B;AAEA,eAAsB,aACpB,QACA,YACA,SACA,YACmB;AACnB,QAAM,SAAS,KAAK,SAAS,OAAO,UAAU;AAC9C,QAAM,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AAEvC,QAAM,cAAc,OAAO,eAAe;AAC1C,QAAM,cAAc,OAAO,iBAAiB;AAC5C,QAAM,UAAU,MAAM,SAAS,OAAO,EAAE,SAAS,UAAU,UAAU,CAAC,OAAO,OAAO,CAAC;AACrF,QAAM,UAAU,MAAM,QAAQ,WAAW,OAAO,aAAa,EAAE,WAAW,OAAO,WAAW,IAAI,CAAC,CAAC;AAElG,QAAM,gBAAgB,MAAM,QAAQ,QAAQ;AAC5C,QAAM,OAAO,MAAM,aAAa,eAAe,MAAM;AACrD,QAAM,cAAc,MAAM;AAE1B,MAAI,YAAY;AAChB,QAAM,YAAsB,CAAC;AAC7B,QAAM,SAA2C,CAAC;AAElD,iBAAe,cAAc,KAAa,aAA6C;AACrF,QAAI;AACJ,aAAS,UAAU,GAAG,UAAU,aAAa,WAAW;AACtD,YAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,UAAI;AACF,cAAM,OAAO,MAAM,UAAU,MAAM,KAAK,OAAO,QAAQ,OAAO,iBAAiB;AAC/E,cAAM,OAAO,WAAW,GAAG;AAC3B,cAAM,UAAU,KAAK,QAAQ,GAAG,IAAI,OAAO,GAAG,MAAM,OAAO;AAC3D,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,oBAAY;AAAA,MACd,UAAE;AACA,cAAM,KAAK,MAAM;AAAA,MACnB;AAAA,IACF;AACA,WAAO,qBAAqB,QAAQ,UAAU,UAAU,OAAO,SAAS;AAAA,EAC1E;AAEA,MAAI;AACF,UAAM,QAAQ,MAAM,aAAa,aAAa,OAAO,QAAQ;AAC3D,YAAM,WAAW,MAAM,cAAc,KAAK,CAAC;AAC3C,UAAI,aAAa,MAAM;AACrB,kBAAU,KAAK,GAAG;AAAA,MACpB,OAAO;AACL,eAAO,KAAK,EAAE,KAAK,OAAO,SAAS,CAAC;AAAA,MACtC;AACA;AACA,mBAAa,WAAW,KAAK,QAAQ,GAAG;AAAA,IAC1C,CAAC;AAKD,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,UAAU,OAAO,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG;AACjD,YAAM,oBAAoB,KAAK,IAAI,aAAa,GAAI;AACpD,cAAQ,IAAI,6BAA6B,QAAQ,MAAM,+BAA+B;AACtF,YAAM,QAAQ,SAAS,GAAG,mBAAmB,OAAO,QAAQ;AAC1D,cAAM,WAAW,MAAM,cAAc,KAAK,CAAC;AAC3C,YAAI,aAAa,MAAM;AACrB,oBAAU,KAAK,GAAG;AAAA,QACpB,OAAO;AACL,iBAAO,KAAK,EAAE,KAAK,OAAO,SAAS,CAAC;AAAA,QACtC;AAAA,MACF,CAAC;AACD,cAAQ,IAAI,eAAe,QAAQ,SAAS,OAAO,MAAM,IAAI,QAAQ,MAAM,SAAS;AAAA,IACtF;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,cAAQ,MAAM,aAAa,OAAO,MAAM,IAAI,KAAK,MAAM,wBAAwB;AAC/E,iBAAW,EAAE,KAAK,MAAM,KAAK,OAAO,MAAM,GAAG,EAAE,GAAG;AAChD,gBAAQ,MAAM,OAAO,GAAG,MAAM,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC,EAAE;AAAA,MACtD;AACA,UAAI,OAAO,SAAS,GAAI,SAAQ,MAAM,cAAc,OAAO,SAAS,EAAE,QAAQ;AAAA,IAChF;AAEA,UAAM,UAAU,KAAK,QAAQ,WAAW,GAAG,KAAK,UAAU,SAAS,GAAG,OAAO;AAC7E,WAAO;AAAA,EACT,UAAE;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AACF;AAEA,eAAsB,gBAAkC;AACtD,SAAO,SAAS,OAAO,EAAE,SAAS,SAAS,CAAC;AAC9C;;;AErNA,iCAAuB;AAJvB,SAAS,YAAAC,WAAU,aAAAC,YAAW,SAAAC,cAAa;AAC3C,SAAS,QAAAC,aAAY;AACrB,SAAS,aAAa;AACtB,OAAO,qBAAqB;AAI5B,IAAM,WAAW,IAAI,gBAAgB;AAAA,EACnC,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,kBAAkB;AACpB,CAAC;AACD,SAAS,IAAI,iCAAM;AASnB,IAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,cAAc,IAAY,cAAiC;AAClE,MAAI,UAAU,GACX,QAAQ,eAAe,EAAE,EACzB,QAAQ,WAAW,MAAM;AAE5B,MAAI,cAAc;AAChB,eAAW,WAAW,cAAc;AAClC,gBAAU,QAAQ,QAAQ,IAAI,OAAO,SAAS,IAAI,GAAG,EAAE;AAAA,IACzD;AACA,cAAU,QAAQ,QAAQ,WAAW,MAAM;AAAA,EAC7C;AAEA,SAAO,QAAQ,KAAK;AACtB;AAMA,SAAS,uBAAuB,WAA0B;AACxD,aAAW,SAAS,UAAU,iBAAiB,OAAO,GAAG;AACvD,QAAI,CAAC,MAAM,YAAa;AACxB,eAAW,SAAS,MAAM,iBAAiB,OAAO,GAAG;AACnD,UAAI,CAAC,MAAM,YAAa;AACxB,YAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EACxB;AAAA,QAAI,CAAC,QACJ,CAAC,GAAG,IAAI,KAAK,EACV,IAAI,CAAC,MAAM,EAAE,aAAa,QAAQ,QAAQ,GAAG,EAAE,KAAK,KAAK,EAAE,EAC3D,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,MACb,EACC,OAAO,OAAO;AACjB,YAAM,YAAY,KAAK,KAAK,IAAI,CAAC;AAAA,IACnC;AACA,eAAW,QAAQ,MAAM,iBAAiB,MAAM,GAAG;AACjD,WAAK,YAAY,KAAK,eAAe,EAAE;AAAA,IACzC;AACA,eAAW,QAAQ,MAAM,iBAAiB,QAAQ,GAAG;AACnD,UAAI,CAAC,KAAK,YAAa;AACvB,YAAM,QAAQ,CAAC,GAAG,KAAK,iBAAiB,IAAI,CAAC,EAC1C,IAAI,CAAC,OAAO,GAAG,aAAa,QAAQ,QAAQ,GAAG,EAAE,KAAK,KAAK,EAAE,EAC7D,OAAO,OAAO;AACjB,WAAK,YAAY,MAAM,KAAK,IAAI,CAAC;AAAA,IACnC;AACA,eAAW,SAAS,MAAM,iBAAiB,2CAA2C,GAAG;AACvF,UAAI,CAAC,MAAM,YAAa;AACxB,YAAM,YAAY,MAAM,aAAa,QAAQ,QAAQ,GAAG,EAAE,KAAK,KAAK,EAAE;AAAA,IACxE;AAAA,EACF;AACF;AAIA,SAAS,oBAAoB,IAAoB;AAC/C,SAAO,GACJ,MAAM,IAAI,EACV;AAAA,IAAI,CAAC,SACJ,KAAK,WAAW,GAAG,IAAI,KAAK,QAAQ,qBAAqB,IAAI,IAAI;AAAA,EACnE,EACC,KAAK,IAAI;AACd;AAOA,IAAM,6BAA6B;AAEnC,SAAS,iBAAiB,MAAsB;AAC9C,SAAO,KAAK,QAAQ,qBAAqB,EAAE;AAC7C;AAEO,SAAS,eACd,MACA,iBACA,iBACA,oBACQ;AACR,MAAI,KAAK,SAAS,4BAA4B;AAC5C,WAAO,iBAAiB,IAAI;AAAA,EAC9B;AACA,QAAM,MAAM,IAAI,MAAM,IAAI;AAC1B,QAAM,MAAM,IAAI,OAAO;AAEvB,QAAM,YAAY,IAAI,cAAc,eAAe,KAAK,IAAI;AAE5D,QAAM,eAAe,CAAC,GAAG,gBAAgB,GAAI,mBAAmB,CAAC,CAAE;AACnE,aAAW,YAAY,cAAc;AACnC,eAAW,MAAM,UAAU,iBAAiB,QAAQ,GAAG;AACrD,SAAG,OAAO;AAAA,IACZ;AAAA,EACF;AAEA,yBAAuB,SAAS;AAEhC,SAAO;AAAA,IACL,oBAAoB,SAAS,SAAS,UAAU,SAAS,CAAC;AAAA,IAC1D;AAAA,EACF;AACF;AAKO,SAAS,aAAa,MAAsB;AACjD,QAAM,MAAM,IAAI,MAAM,IAAI;AAC1B,QAAM,MAAM,IAAI,OAAO,SACpB,cAAc,OAAO,GACpB,aAAa,QAAQ,QAAQ,GAAG,EACjC,KAAK;AACR,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,WAAW,IACd,QAAQ,eAAe,EAAE,EACzB,QAAQ,sCAAsC,EAAE,EAChD,QAAQ,4BAA4B,EAAE,EACtC,KAAK;AACR,SAAO,YAAY;AACrB;AAEO,SAAS,iBAAiB,QAAgB,KAAa,OAAuB;AACnF,SAAO;AAAA,IACL;AAAA,IACA,WAAW,MAAM;AAAA,IACjB,SAAS,GAAG;AAAA,IACZ,WAAW,MAAM,QAAQ,MAAM,KAAK,CAAC;AAAA,IACrC,iBAAgB,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,IACxC;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEO,SAAS,YACd,MACA,QACA,KACA,iBACA,iBACA,oBACe;AACf,QAAM,QAAQ,aAAa,IAAI;AAC/B,QAAM,UAAU,eAAe,MAAM,iBAAiB,iBAAiB,kBAAkB;AACzF,QAAM,cAAc,iBAAiB,QAAQ,KAAK,KAAK;AACvD,QAAM,WAAW,GAAG,WAAW;AAAA;AAAA,EAAO,OAAO;AAE7C,SAAO,EAAE,QAAQ,KAAK,OAAO,SAAS;AACxC;AAEA,IAAMC,uBAAsB;AAE5B,eAAsB,cACpB,YACA,MACA,iBACA,iBACA,oBACA,SACA,cAAcA,sBACd,YAC0B;AAC1B,QAAM,SAASC,MAAK,SAAS,OAAO,UAAU;AAC9C,QAAM,QAAQA,MAAK,SAAS,YAAY,UAAU;AAClD,QAAMC,OAAM,OAAO,EAAE,WAAW,KAAK,CAAC;AAEtC,QAAM,QAAyB,IAAI,MAAM,KAAK,MAAM;AACpD,MAAI,YAAY;AAChB,MAAI,YAAY;AAEhB,iBAAe,SAAwB;AACrC,WAAO,YAAY,KAAK,QAAQ;AAC9B,YAAM,IAAI;AACV,YAAM,MAAM,KAAK,CAAC;AAClB,YAAM,OAAO,WAAW,GAAG;AAC3B,YAAM,WAAWD,MAAK,QAAQ,GAAG,IAAI,OAAO;AAE5C,UAAI;AACJ,UAAI;AACF,eAAO,MAAME,UAAS,UAAU,OAAO;AAAA,MACzC,QAAQ;AACN,gBAAQ,KAAK,iCAAiC,GAAG,0DAA0D;AAC3G;AACA;AAAA,MACF;AAEA,YAAM,OAAO,YAAY,MAAM,YAAY,KAAK,iBAAiB,iBAAiB,kBAAkB;AACpG,YAAMC,WAAUH,MAAK,OAAO,GAAG,IAAI,KAAK,GAAG,KAAK,UAAU,OAAO;AACjE,YAAM,CAAC,IAAI;AACX;AACA,mBAAa,WAAW,KAAK,QAAQ,GAAG;AAAA,IAC1C;AAAA,EACF;AAEA,QAAM,UAAU,MAAM;AAAA,IACpB,EAAE,QAAQ,KAAK,IAAI,aAAa,KAAK,MAAM,EAAE;AAAA,IAC7C,MAAM,OAAO;AAAA,EACf;AACA,QAAM,QAAQ,IAAI,OAAO;AAEzB,SAAO,MAAM,OAAO,CAAC,SAAS,SAAS,MAAS;AAClD;;;AC9NA,IAAM,aAAa;AAEZ,SAAS,eAAe,MAAsB;AACnD,SAAO,KAAK,KAAK,KAAK,SAAS,CAAC;AAClC;AAEO,SAAS,eAAe,SAAyB;AACtD,SAAO,QACJ,YAAY,EACZ,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,QAAQ,GAAG,EACnB,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EAAE;AACzB;AAEO,SAAS,aACd,QACA,KACA,aACA,OACQ;AACR,QAAM,UAAU,WAAW,GAAG;AAC9B,QAAM,SAAS,GAAG,MAAM,KAAK,OAAO;AACpC,QAAM,eAAe,OAAO,OAAO,WAAW,MAAM,IAAI;AACxD,QAAM,gBACJ,OAAO,WAAW,WAAW,IAAI,eAC7B,OAAO,KAAK,WAAW,EAAE,SAAS,GAAG,YAAY,EAAE,SAAS,IAC5D;AACN,QAAM,OAAO,GAAG,MAAM,GAAG,aAAa;AACtC,SAAO,UAAU,SAAY,GAAG,IAAI,IAAI,KAAK,KAAK;AACpD;AAOO,SAAS,eAAe,OAAsB;AACnD,MAAI,OAAO,MAAM,aAAa,CAAC,MAAM,MAAM,QACvC,MAAM,aAAa,MAAM,CAAC,IAC1B,MAAM;AACV,MAAI,MAAM,QAAQ,WAAW,GAAG,KAAK,KAAK,SAAS,GAAG;AACpD,WAAO,KAAK,MAAM,GAAG,EAAE;AAAA,EACzB;AACA,QAAM,UAAU,CAAC,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,OAAO,EAAE,KAAK,KAAK;AACjE,SAAO,UAAU,GAAG,OAAO;AAAA;AAAA,EAAO,MAAM,OAAO,KAAK,MAAM;AAC5D;AASA,SAAS,qBAAqB,UAAoC;AAChE,QAAM,QAAQ,SAAS,MAAM,IAAI;AACjC,QAAM,WAA6B,CAAC;AACpC,QAAM,eAAyB,CAAC;AAChC,QAAM,aAAuB,CAAC;AAE9B,MAAI,iBAAiC;AAAA,IACnC,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa,CAAC;AAAA,IACd,OAAO,CAAC;AAAA,EACV;AAEA,MAAI,UAAU;AACd,MAAI,cAAc;AAElB,aAAW,QAAQ,OAAO;AACxB,UAAM,aAAa,KAAK,MAAM,iBAAiB;AAC/C,QAAI,YAAY;AACd,YAAM,SAAS,WAAW,CAAC,EAAE,CAAC;AAC9B,UAAI,CAAC,SAAS;AACZ,kBAAU;AACV,sBAAc;AAAA,MAChB,WAAW,WAAW,aAAa;AACjC,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,UAAM,eAAe,UAAU,OAAO,KAAK,MAAM,mBAAmB;AAEpE,QAAI,cAAc;AAChB,UAAI,eAAe,MAAM,SAAS,KAAK,eAAe,YAAY,IAAI;AACpE,iBAAS,KAAK,cAAc;AAAA,MAC9B;AAEA,YAAM,QAAQ,aAAa,CAAC,EAAE;AAC9B,YAAM,UAAU,aAAa,CAAC,EAAE,KAAK;AAErC,aAAO,WAAW,SAAS,KAAK,WAAW,WAAW,SAAS,CAAC,KAAK,OAAO;AAC1E,mBAAW,IAAI;AACf,qBAAa,IAAI;AAAA,MACnB;AAEA,mBAAa,KAAK,OAAO;AACzB,iBAAW,KAAK,KAAK;AAErB,uBAAiB;AAAA,QACf;AAAA,QACA;AAAA,QACA,aAAa,CAAC,GAAG,YAAY;AAAA,QAC7B,OAAO,CAAC;AAAA,MACV;AAAA,IACF,OAAO;AACL,qBAAe,MAAM,KAAK,IAAI;AAAA,IAChC;AAAA,EACF;AAEA,MAAI,eAAe,MAAM,SAAS,KAAK,eAAe,YAAY,IAAI;AACpE,aAAS,KAAK,cAAc;AAAA,EAC9B;AAEA,SAAO;AACT;AASA,SAAS,YAAY,MAAuB;AAC1C,SAAO,KAAK,UAAU,EAAE,WAAW,GAAG;AACxC;AAEA,SAAS,iBAAiB,MAAuB;AAC/C,SAAO,yBAAyB,KAAK,IAAI,KAAK,KAAK,SAAS,GAAG;AACjE;AAKA,SAAS,YAAY,OAA0B;AAC7C,QAAM,SAAkB,CAAC;AACzB,MAAI,UAAoB,CAAC;AACzB,MAAI,OAAkB;AACtB,MAAI,UAAU;AACd,MAAI,cAAc;AAElB,WAAS,QAAc;AACrB,WAAO,QAAQ,SAAS,KAAK,QAAQ,QAAQ,SAAS,CAAC,EAAE,KAAK,MAAM,IAAI;AACtE,cAAQ,IAAI;AAAA,IACd;AACA,QAAI,QAAQ,SAAS,GAAG;AACtB,aAAO,KAAK,EAAE,MAAM,OAAO,QAAQ,CAAC;AAAA,IACtC;AACA,cAAU,CAAC;AACX,WAAO;AAAA,EACT;AAEA,aAAW,QAAQ,OAAO;AACxB,QAAI,SAAS;AACX,cAAQ,KAAK,IAAI;AACjB,YAAMI,cAAa,KAAK,MAAM,iBAAiB;AAC/C,UAAIA,eAAcA,YAAW,CAAC,EAAE,CAAC,MAAM,aAAa;AAClD,kBAAU;AACV,cAAM;AAAA,MACR;AACA;AAAA,IACF;AAEA,UAAM,aAAa,KAAK,MAAM,iBAAiB;AAC/C,QAAI,YAAY;AACd,YAAM;AACN,aAAO;AACP,gBAAU;AACV,oBAAc,WAAW,CAAC,EAAE,CAAC;AAC7B,cAAQ,KAAK,IAAI;AACjB;AAAA,IACF;AAEA,QAAI,YAAY,IAAI,GAAG;AACrB,UAAI,SAAS,SAAS;AACpB,cAAM;AACN,eAAO;AAAA,MACT;AACA,cAAQ,KAAK,IAAI;AACjB;AAAA,IACF;AAEA,QAAI,SAAS,SAAS;AACpB,YAAM;AAAA,IACR;AAEA,QAAI,KAAK,KAAK,MAAM,IAAI;AACtB,YAAM;AAAA,IACR,OAAO;AACL,cAAQ,KAAK,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,QAAM;AACN,SAAO;AACT;AAKA,SAAS,cAAc,MAAc,QAA0B;AAC7D,QAAM,WAAW,SAAS;AAC1B,MAAI,KAAK,UAAU,SAAU,QAAO,CAAC,IAAI;AAEzC,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO;AACX,SAAO,KAAK,SAAS,UAAU;AAC7B,QAAI,MAAM,KAAK,YAAY,KAAK,QAAQ;AACxC,QAAI,MAAM,WAAW,EAAG,OAAM;AAC9B,WAAO,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC;AAC9B,WAAO,KAAK,MAAM,GAAG,EAAE,UAAU;AAAA,EACnC;AACA,MAAI,KAAM,QAAO,KAAK,IAAI;AAC1B,SAAO;AACT;AAEA,SAAS,WAAW,OAAiB,QAA4B;AAC/D,QAAM,SAAqB,CAAC;AAC5B,MAAI,UAAoB,CAAC;AACzB,MAAI,SAAS;AAEb,aAAW,QAAQ,MAAM,QAAQ,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG;AACjE,UAAM,aAAa,eAAe,IAAI,IAAI;AAC1C,QAAI,SAAS,aAAa,UAAU,QAAQ,SAAS,GAAG;AACtD,aAAO,KAAK,OAAO;AACnB,gBAAU,CAAC;AACX,eAAS;AAAA,IACX;AACA,YAAQ,KAAK,IAAI;AACjB,cAAU;AAAA,EACZ;AAEA,MAAI,QAAQ,SAAS,EAAG,QAAO,KAAK,OAAO;AAC3C,SAAO;AACT;AAIA,SAAS,WAAW,OAAc,QAA0B;AAC1D,MAAI,MAAM,SAAS,SAAS;AAC1B,UAAM,YAAY,MAAM,MAAM,UAAU,KAAK,iBAAiB,MAAM,MAAM,CAAC,CAAC;AAC5E,UAAM,SAAS,YAAY,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC;AACtD,UAAM,OAAO,YAAY,MAAM,MAAM,MAAM,CAAC,IAAI,MAAM;AACtD,UAAM,eAAe,eAAe,OAAO,KAAK,IAAI,CAAC;AACrD,WAAO,WAAW,MAAM,KAAK,IAAI,SAAS,cAAc,EAAE,CAAC,EAAE;AAAA,MAAI,CAAC,UAChE,CAAC,GAAG,QAAQ,GAAG,KAAK,EAAE,KAAK,IAAI;AAAA,IACjC;AAAA,EACF;AAEA,MAAI,MAAM,SAAS,SAAS;AAC1B,UAAM,UAAU,MAAM,MAAM,CAAC;AAC7B,UAAM,UAAU,MAAM,MAAM,MAAM,MAAM,SAAS,CAAC,EAAE,MAAM,qBAAqB,IAC3E,MAAM,MAAM,MAAM,MAAM,SAAS,CAAC,IAClC,QAAQ,MAAM,iBAAiB,EAAG,CAAC;AACvC,UAAM,OAAO,MAAM,MAAM,MAAM,GAAG,MAAM,MAAM,MAAM,MAAM,SAAS,CAAC,MAAM,UAAU,KAAK,MAAS;AAClG,UAAM,cAAc,eAAe,OAAO,IAAI,eAAe,OAAO,IAAI;AACxE,WAAO,WAAW,MAAM,KAAK,IAAI,SAAS,aAAa,EAAE,CAAC,EAAE;AAAA,MAAI,CAAC,UAC/D,CAAC,SAAS,GAAG,OAAO,OAAO,EAAE,KAAK,IAAI;AAAA,IACxC;AAAA,EACF;AAEA,SAAO,WAAW,MAAM,OAAO,MAAM,EAAE,IAAI,CAAC,UAAU,MAAM,KAAK,IAAI,CAAC;AACxE;AAIA,SAAS,sBACP,QACA,aACA,WACU;AACV,QAAM,SAAS,KAAK,IAAI,YAAY,eAAe,WAAW,GAAG,GAAG;AACpE,QAAM,QAAoB,CAAC;AAC3B,MAAI,UAAoB,CAAC;AACzB,MAAI,SAAS;AAEb,WAAS,QAAc;AACrB,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,KAAK,OAAO;AAClB,gBAAU,CAAC;AACX,eAAS;AAAA,IACX;AAAA,EACF;AAEA,aAAW,SAAS,QAAQ;AAC1B,UAAM,OAAO,MAAM,MAAM,KAAK,IAAI;AAClC,UAAM,cAAc,eAAe,IAAI,IAAI;AAE3C,QAAI,cAAc,QAAQ;AACxB,YAAM;AACN,iBAAW,SAAS,WAAW,OAAO,MAAM,GAAG;AAC7C,cAAM,KAAK,CAAC,KAAK,CAAC;AAAA,MACpB;AACA;AAAA,IACF;AAEA,QAAI,SAAS,cAAc,QAAQ;AACjC,YAAM;AAAA,IACR;AACA,YAAQ,KAAK,IAAI;AACjB,cAAU;AAAA,EACZ;AAEA,QAAM;AACN,SAAO,MAAM,IAAI,CAAC,eAAe,cAAc,WAAW,KAAK,MAAM,CAAC;AACxE;AAEA,SAAS,iBAAiB,UAA0B;AAClD,MAAI,SAAS,WAAW,KAAK,GAAG;AAC9B,UAAM,WAAW,SAAS,QAAQ,OAAO,CAAC;AAC1C,QAAI,aAAa,IAAI;AACnB,aAAO,SAAS,MAAM,WAAW,CAAC,EAAE,KAAK;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,cACd,UACA,QACA,KACA,OACS;AACT,QAAM,WAAW,iBAAiB,QAAQ;AAC1C,QAAM,WAAW,qBAAqB,QAAQ;AAC9C,QAAM,SAAkB,CAAC;AACzB,QAAM,UAAU,oBAAI,IAAY;AAEhC,WAAS,SAAS,UAA0B;AAC1C,QAAI,KAAK,aAAa,QAAQ,KAAK,QAAQ;AAC3C,QAAI,CAAC,QAAQ,IAAI,EAAE,GAAG;AACpB,cAAQ,IAAI,EAAE;AACd,aAAO;AAAA,IACT;AACA,QAAI,UAAU;AACd,WAAO,QAAQ,IAAI,aAAa,QAAQ,KAAK,UAAU,OAAO,CAAC,GAAG;AAChE;AAAA,IACF;AACA,SAAK,aAAa,QAAQ,KAAK,UAAU,OAAO;AAChD,YAAQ,IAAI,EAAE;AACd,WAAO;AAAA,EACT;AAEA,aAAW,WAAW,UAAU;AAC9B,UAAM,cAAc,QAAQ,UACxB,GAAG,IAAI,OAAO,QAAQ,KAAK,CAAC,IAAI,QAAQ,OAAO;AAAA;AAAA,IAC/C;AACJ,UAAM,OAAO,QAAQ,MAAM,KAAK,IAAI,EAAE,KAAK;AAC3C,UAAM,UAAU,cAAc;AAE9B,QAAI,CAAC,QAAQ,KAAK,EAAG;AAErB,UAAM,cAAc,QAAQ,UACxB,eAAe,QAAQ,OAAO,IAC9B;AAEJ,QAAI,eAAe,OAAO,KAAK,YAAY;AACzC,aAAO,KAAK;AAAA,QACV,IAAI,SAAS,WAAW;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc,QAAQ;AAAA,QACtB;AAAA,QACA,aAAa,eAAe,OAAO;AAAA,MACrC,CAAC;AACD;AAAA,IACF;AAEA,UAAM,SAAS,YAAY,KAAK,MAAM,IAAI,CAAC;AAC3C,UAAM,QAAQ,sBAAsB,QAAQ,aAAa,UAAU;AAEnE,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,cAAc,MAAM,CAAC,EAAE,KAAK;AAClC,UAAI,CAAC,YAAa;AAElB,YAAM,WAAW,MAAM,SAAS,IAAI,GAAG,WAAW,IAAI,CAAC,KAAK;AAC5D,aAAO,KAAK;AAAA,QACV,IAAI,SAAS,QAAQ;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc,QAAQ;AAAA,QACtB,SAAS;AAAA,QACT,aAAa,eAAe,WAAW;AAAA,MACzC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;;;AC9YA,SAAS,kBAAkB;AAM3B,IAAM,qBACJ;AAEF,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AACzB,IAAM,uBAAuB;AAK7B,SAAS,mBAAmB,MAAsB;AAChD,SAAO,KACJ,QAAQ,gBAAgB,GAAG,EAC3B,QAAQ,mBAAmB,GAAG,EAC9B,QAAQ,sBAAsB,IAAI;AACvC;AAEO,SAAS,wBAAwB,MAAc,QAAQ,sBAAgC;AAC5F,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,SAAS,mBAAmB,IAAI,EAAE,SAAS,kBAAkB,GAAG;AACzE,UAAM,QAAQ,MAAM,CAAC,EAAE,YAAY;AACnC,QAAI,MAAM,SAAS,oBAAoB,MAAM,SAAS,iBAAkB;AACxE,SAAK,IAAI,KAAK;AACd,QAAI,KAAK,QAAQ,MAAO;AAAA,EAC1B;AACA,SAAO,CAAC,GAAG,IAAI;AACjB;AAEO,SAAS,mBAAmB,OAAyB;AAC1D,SAAO,wBAAwB,OAAO,CAAC;AACzC;AAEO,SAAS,YAAY,MAAsB;AAChD,SAAO,WAAW,QAAQ,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AACvD;;;ACxCA,SAAS,0BAA0B;AAEnC,IAAM,aAAa;AACnB,IAAM,QAAQ;AACd,IAAM,wBAAwB;AAC9B,IAAM,cAAc;AACpB,IAAM,2BAA2B;AACjC,IAAM,wBAAwB;AAC9B,IAAM,iBAAiB;AAEvB,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAI;AAEJ,SAAS,YAAgC;AACvC,MAAI,CAAC,OAAO;AACV,UAAM,SAAS,QAAQ,IAAI;AAC3B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,gDAAgD;AAAA,IAClE;AACA,YAAQ,IAAI,mBAAmB,MAAM;AAAA,EACvC;AACA,SAAO;AACT;AAEA,SAAS,cAAc,SAAqD;AAC1E,MAAI,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,KAAK,GAAG;AACvI,WAAO;AAAA,EACT;AACA,MAAI,uBAAuB,KAAK,CAAC,MAAM,QAAQ,SAAS,CAAC,CAAC,GAAG;AAC3D,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOA,eAAsB,WACpB,OACA,UAAwB,CAAC,GACJ;AACrB,QAAM,SAAS,UAAU;AACzB,QAAM,QAAQ,OAAO,mBAAmB,EAAE,OAAO,MAAM,CAAC;AAExD,QAAM,EAAE,WAAW,IAAI;AACvB,QAAM,aAAa,QAAQ,cAAc;AAEzC,QAAM,aAAyB,CAAC;AAEhC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,YAAY;AACjD,UAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,UAAU;AAC3C,UAAM,cAAc,IAAI,aAAa;AAErC,QAAI;AACJ,aAAS,UAAU,GAAG,UAAU,YAAY,WAAW;AACrD,UAAI;AACF,iBAAS,MAAM,MAAM,mBAAmB;AAAA,UACtC,UAAU,MAAM,IAAI,CAAC,UAAU;AAAA,YAC7B,SAAS,EAAE,MAAM,QAAQ,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE;AAAA,YAC3C,sBAAsB;AAAA,UACxB,EAAE;AAAA,QACJ,CAAC;AACD;AAAA,MACF,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,cAAM,OAAO,cAAc,OAAO;AAElC,YAAI,SAAS,WAAW,UAAU,aAAa,GAAG;AAChD,gBAAM,YAAY,SAAS,eAAe,2BAA2B;AACrE,gBAAM,QAAQ,YAAY,KAAK,IAAI,GAAG,OAAO;AAC7C,gBAAM,QAAQ,SAAS,eAAe,iBAAiB;AACvD,kBAAQ,IAAI,KAAK,KAAK,WAAW,WAAW,kBAAkB,QAAQ,GAAI,MAAM;AAChF,gBAAM,IAAI,QAAQ,CAACC,aAAY,WAAWA,UAAS,KAAK,CAAC;AACzD;AAAA,QACF;AAEA,gBAAQ,MAAM,+BAA+B,WAAW,YAAY,IAAI,CAAC,IAAI,IAAI,MAAM,MAAM,MAAM,OAAO,EAAE;AAC5G,cAAM;AAAA,MACR;AAAA,IACF;AAEA,eAAW,aAAa,OAAQ,YAAY;AAC1C,iBAAW,KAAK,UAAU,MAAM;AAAA,IAClC;AAEA,iBAAa,KAAK,IAAI,IAAI,YAAY,MAAM,MAAM,GAAG,MAAM,MAAM;AAEjE,QAAI,IAAI,aAAa,MAAM,QAAQ;AACjC,YAAM,IAAI,QAAQ,CAACA,aAAY,WAAWA,UAAS,cAAc,CAAC;AAAA,IACpE;AAAA,EACF;AAEA,SAAO;AACT;AAMA,eAAsB,UAAU,MAAiC;AAC/D,QAAM,CAAC,SAAS,IAAI,MAAM,WAAW,CAAC,IAAI,GAAG,EAAE,YAAY,EAAE,CAAC;AAC9D,SAAO;AACT;;;AClHA,SAAS,eAAe,oBAAoB,eAAe;AAC3D;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AASP,IAAMC,cAAa;AACnB,IAAM,uBAAuB;AAC7B,IAAMC,eAAc;AACpB,IAAM,gBAAgB;AAEtB,SAASC,OAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAACC,aAAY,WAAWA,UAAS,EAAE,CAAC;AACzD;AAIA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,eAAe,iBAAoB,IAAkC;AACnE,WAAS,UAAU,GAAG,UAAUF,cAAa,WAAW;AACtD,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,UAAI,mBAAmB,KAAK,CAAC,MAAM,QAAQ,SAAS,CAAC,CAAC,KAAK,UAAUA,eAAc,GAAG;AACpF,cAAM,QAAQ,gBAAgB,KAAK,IAAI,GAAG,OAAO;AACjD,gBAAQ,IAAI,gCAAgC,QAAQ,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,kBAAkB,QAAQ,GAAI,MAAM;AAClH,cAAMC,OAAM,KAAK;AACjB;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACA,QAAM,IAAI,MAAM,sBAAsB;AACxC;AAEA,IAAI;AAEJ,SAAS,QAAmB;AAC1B,MAAI,CAAC,IAAI;AACP,QAAI,QAAQ,EAAE,WAAW,GAAG;AAC1B,oBAAc,EAAE,YAAY,mBAAmB,EAAE,CAAC;AAAA,IACpD;AACA,SAAK,aAAa;AAAA,EACpB;AACA,SAAO;AACT;AAEA,SAAS,YAAY;AACnB,SAAO,MAAM,EAAE,WAAW,iBAAiB;AAC7C;AAEA,SAAS,aAAa;AACpB,SAAO,MAAM,EAAE,WAAW,kBAAkB;AAC9C;AAEA,eAAsB,YACpB,QACA,YACA,YACe;AACf,QAAM,WAAW,MAAM;AACvB,QAAM,MAAM,UAAU;AAEtB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAKF,aAAY;AAClD,UAAM,QAAQ,SAAS,MAAM;AAC7B,UAAM,QAAQ,OAAO,MAAM,GAAG,IAAIA,WAAU;AAC5C,UAAM,WAAW,WAAW,MAAM,GAAG,IAAIA,WAAU;AAEnD,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,QAAQ,MAAM,CAAC;AACrB,YAAM,IAAI,IAAI,IAAI,MAAM,EAAE,GAAG;AAAA,QAC3B,QAAQ,MAAM;AAAA,QACd,KAAK,MAAM;AAAA,QACX,OAAO,MAAM;AAAA,QACb,cAAc,MAAM;AAAA,QACpB,SAAS,MAAM;AAAA,QACf,aAAa,MAAM;AAAA,QACnB,QAAQ,wBAAwB,MAAM,OAAO;AAAA,QAC7C,cAAc,YAAY,eAAe,KAAK,CAAC;AAAA,QAC/C,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,WAAW,WAAW,OAAO,SAAS,CAAC,CAAC;AAAA,MAC1C,CAAC;AAAA,IACH;AAEA,UAAM,iBAAiB,MAAM,MAAM,OAAO,CAAC;AAC3C,iBAAa,KAAK,IAAI,IAAIA,aAAY,OAAO,MAAM,GAAG,OAAO,MAAM;AAEnE,QAAI,IAAIA,cAAa,OAAO,QAAQ;AAClC,YAAME,OAAM,oBAAoB;AAAA,IAClC;AAAA,EACF;AACF;AAEA,eAAsB,YAAY,YAAqC;AACrE,QAAM,WAAW,MAAM;AACvB,QAAM,MAAM,UAAU;AAEtB,QAAM,WAAW,MAAM,IAAI,MAAM,UAAU,MAAM,UAAU,EAAE,IAAI;AACjE,MAAI,SAAS,MAAO,QAAO;AAE3B,MAAI,QAAQ,SAAS,MAAM;AAC3B,MAAI,QAAQ;AAEZ,aAAW,OAAO,SAAS,MAAM;AAC/B,UAAM,OAAO,IAAI,GAAG;AACpB;AACA,QAAI,QAAQF,gBAAe,GAAG;AAC5B,YAAM,iBAAiB,MAAM,MAAM,OAAO,CAAC;AAC3C,cAAQ,SAAS,MAAM;AAAA,IACzB;AAAA,EACF;AAEA,MAAI,QAAQA,gBAAe,GAAG;AAC5B,UAAM,iBAAiB,MAAM,MAAM,OAAO,CAAC;AAAA,EAC7C;AAEA,SAAO;AACT;AAEA,eAAsB,iBACpB,YACA,YACA,UACA,SACe;AACf,QAAM,WAAW,EAAE,IAAI,UAAU,EAAE,IAAI;AAAA,IACrC,QAAQ;AAAA,IACR,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC7B,iBAAgB,oBAAI,KAAK,GAAE,YAAY;AAAA,IACvC,aAAa;AAAA,IACb,WAAW;AAAA,EACb,CAAC;AACH;AAEA,eAAsB,cAAc,YAAgD;AAClF,QAAM,MAAM,MAAM,WAAW,EAAE,IAAI,UAAU,EAAE,IAAI;AACnD,MAAI,CAAC,IAAI,OAAQ,QAAO;AACxB,SAAO,IAAI,KAAK;AAClB;AAEA,eAAsB,oBAA2C;AAC/D,QAAM,WAAW,MAAM,WAAW,EAAE,IAAI;AACxC,SAAO,SAAS,KAAK,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAe;AAC5D;AAEA,eAAsB,iBAAiB,YAAmC;AACxE,QAAM,WAAW,EAAE,IAAI,UAAU,EAAE,OAAO;AAC5C;AAEA,eAAsB,kBACpB,KACA,YACe;AACf,MAAI,IAAI,WAAW,EAAG;AACtB,QAAM,WAAW,MAAM;AACvB,QAAM,MAAM,UAAU;AAEtB,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAKA,aAAY;AAC/C,UAAM,QAAQ,SAAS,MAAM;AAC7B,UAAM,QAAQ,IAAI,MAAM,GAAG,IAAIA,WAAU;AACzC,eAAW,MAAM,OAAO;AACtB,YAAM,OAAO,IAAI,IAAI,EAAE,CAAC;AAAA,IAC1B;AACA,UAAM,iBAAiB,MAAM,MAAM,OAAO,CAAC;AAC3C,iBAAa,KAAK,IAAI,IAAIA,aAAY,IAAI,MAAM,GAAG,IAAI,MAAM;AAAA,EAC/D;AACF;AAKA,eAAsB,qBAAqB,YAAkD;AAC3F,QAAM,MAAM,UAAU;AACtB,QAAM,WAAW,MAAM,IAAI,MAAM,UAAU,MAAM,UAAU,EAAE,OAAO,cAAc,EAAE,IAAI;AACxF,SAAO,IAAI;AAAA,IACT,SAAS,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAK,IAAI,KAAK,EAAE,gBAAuC,EAAE,CAAC;AAAA,EAC5F;AACF;AAQA,IAAM,oBAAoB;AAC1B,IAAM,sBAAsB,CAAC,UAAU,OAAO,SAAS,gBAAgB,WAAW,aAAa;AAM/F,eAAsB,YACpB,QACA,QACqB;AACrB,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AACjC,QAAM,MAAM,UAAU;AAEtB,QAAM,OAAO,oBAAI,IAAsB;AAEvC,QAAM,YAAY,MAAM,QAAQ;AAAA,IAC9B,OAAO,IAAI,CAAC,UAAU;AACpB,UAAI,QAAQ,IAAI,MAAM,UAAU,kBAAkB,KAAK;AACvD,UAAI,QAAQ;AACV,gBAAQ,MAAM,MAAM,UAAU,MAAM,MAAM;AAAA,MAC5C;AACA,aAAO,MAAM,OAAO,GAAG,mBAAmB,EAAE,MAAM,iBAAiB,EAAE,IAAI;AAAA,IAC3E,CAAC;AAAA,EACH;AAEA,aAAW,YAAY,WAAW;AAChC,eAAW,OAAO,SAAS,MAAM;AAC/B,YAAM,WAAW,KAAK,IAAI,IAAI,EAAE;AAChC,UAAI,UAAU;AACZ,iBAAS;AAAA,MACX,OAAO;AACL,aAAK,IAAI,IAAI,IAAI,EAAE,IAAI,IAAI,IAAI,MAAM,IAAI,KAAK,GAAG,eAAe,EAAE,CAAC;AAAA,MACrE;AAAA,IACF;AAAA,EACF;AAEA,SAAO,CAAC,GAAG,KAAK,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,gBAAgB,EAAE,aAAa;AAC5E;AAEA,eAAsB,aACpB,gBACA,OACA,QAC4E;AAC5E,QAAM,MAAM,UAAU;AAEtB,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACV,YAAQ,MAAM,MAAM,UAAU,MAAM,MAAM;AAAA,EAC5C;AAEA,QAAM,WAAW,MAAM,MACpB,YAAY;AAAA,IACX,aAAa;AAAA,IACb,aAAa,WAAW,OAAO,cAAc;AAAA,IAC7C;AAAA,IACA,iBAAiB;AAAA,IACjB,qBAAqB;AAAA,EACvB,CAAC,EACA,IAAI;AAEP,SAAO,SAAS,KAAK,IAAI,CAAC,QAAQ;AAChC,UAAM,OAAO,IAAI,KAAK;AACtB,UAAM,WAAY,KAAK,aAAwB;AAC/C,WAAO,KAAK;AACZ,WAAO,KAAK;AACZ,WAAO,EAAE,IAAI,IAAI,IAAI,MAAM,SAAS;AAAA,EACtC,CAAC;AACH;;;AC1QA,IAAM,qBAAqB;AAC3B,IAAM,QAAQ;AAEd,SAAS,cAAuB;AAC9B,SAAO,CAAC,CAAC,QAAQ,IAAI;AACvB;AASA,SAAS,eAAe,MAAuC;AAC7D,SAAO;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACF;AAEA,SAAS,eAAe,WAAsB,WAAiC;AAC7E,QAAM,OAAO,UAAU;AACvB,SAAO;AAAA,IACL,IAAI,UAAU;AAAA,IACd,QAAQ,KAAK;AAAA,IACb,KAAK,KAAK;AAAA,IACV,OAAO,KAAK;AAAA,IACZ,cAAc,KAAK;AAAA,IACnB,SAAS,KAAK;AAAA,IACd,iBAAiB;AAAA,EACnB;AACF;AAMA,eAAsB,OACpB,OACA,UAAmE,CAAC,GAC3C;AACzB,QAAM,EAAE,QAAQ,aAAa,oBAAoB,OAAO,EAAE,IAAI;AAE9D,QAAM,iBAAiB,MAAM,UAAU,KAAK;AAC5C,QAAM,CAAC,eAAe,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,IACrD,aAAa,gBAAgB,YAAY,MAAM;AAAA,IAC/C,YAAY,mBAAmB,KAAK,GAAG,MAAM;AAAA,EAC/C,CAAC;AAED,QAAM,OAAO,oBAAI,IAAuB;AAExC,gBAAc,QAAQ,CAAC,QAAQ,SAAS;AACtC,SAAK,IAAI,OAAO,IAAI;AAAA,MAClB,IAAI,OAAO;AAAA,MACX,MAAM,OAAO;AAAA,MACb,YAAY,KAAK,QAAQ,OAAO;AAAA,MAChC,YAAY,IAAI,OAAO;AAAA,IACzB,CAAC;AAAA,EACH,CAAC;AAED,cAAY,QAAQ,CAAC,KAAK,SAAS;AACjC,UAAM,eAAe,KAAK,QAAQ,OAAO;AACzC,UAAM,WAAW,KAAK,IAAI,IAAI,EAAE;AAChC,QAAI,UAAU;AACZ,eAAS,cAAc;AACvB;AAAA,IACF;AACA,SAAK,IAAI,IAAI,IAAI;AAAA,MACf,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,YAAY;AAAA,MACZ,YAAY;AAAA,IACd,CAAC;AAAA,EACH,CAAC;AAED,QAAM,QAAQ,CAAC,GAAG,KAAK,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU;AAC3E,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,MAAI,YAAY,GAAG;AACjB,UAAM,aAAa,MAAM,MAAM,GAAG,gBAAgB;AAClD,UAAM,YAAY,WAAW,IAAI,CAAC,MAAM,eAAe,EAAE,IAAI,CAAC;AAC9D,UAAM,WAAW,MAAM,OAAO,OAAO,WAAW,IAAI;AACpD,WAAO,SAAS,IAAI,CAAC,MAAM,eAAe,WAAW,EAAE,KAAK,GAAG,EAAE,eAAe,CAAC;AAAA,EACnF;AAEA,SAAO,MAAM,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,cAAc;AAC7C,UAAM,aAAa,UAAU,cAAc;AAC3C,WAAO,eAAe,WAAW,KAAK,IAAI,IAAI,IAAI,cAAc,CAAC,CAAC;AAAA,EACpE,CAAC;AACH;;;ACnGA,SAAS,aAAa,cAAAI,mBAAkB;AACxC;AAAA,EACE,gBAAAC;AAAA,OAEK;AACP,SAAS,iBAAAC,gBAAe,sBAAAC,qBAAoB,WAAAC,gBAAe;AAG3D,IAAIC;AAEJ,SAASC,SAAmB;AAC1B,MAAI,CAACD,KAAI;AACP,QAAIE,SAAQ,EAAE,WAAW,GAAG;AAC1B,MAAAC,eAAc,EAAE,YAAYC,oBAAmB,EAAE,CAAC;AAAA,IACpD;AACA,IAAAJ,MAAKK,cAAa;AAAA,EACpB;AACA,SAAOL;AACT;AAEA,SAAS,QAAQ,KAAqB;AACpC,SAAOM,YAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK;AACtD;AAEA,SAAS,aAAa;AACpB,SAAOL,OAAM,EAAE,WAAW,mBAAmB;AAC/C;AAEA,eAAsB,aAAa,MAA+B;AAChE,QAAM,MAAM,QAAQ,YAAY,EAAE,EAAE,SAAS,WAAW,CAAC;AACzD,QAAM,OAAO,QAAQ,GAAG;AAExB,QAAM,WAAW,EAAE,IAAI,IAAI,EAAE,IAAI;AAAA,IAC/B;AAAA,IACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,cAAc;AAAA,EAChB,CAAC;AAED,SAAO;AACT;AAEA,eAAsB,cAA6B;AACjD,QAAM,WAAW,MAAM,WAAW,EAAE,IAAI;AAExC,MAAI,SAAS,OAAO;AAClB,YAAQ,IAAI,oBAAoB;AAChC;AAAA,EACF;AAEA,UAAQ,IAAI,eAAe;AAC3B,aAAW,OAAO,SAAS,MAAM;AAC/B,UAAM,OAAO,IAAI,KAAK;AACtB,UAAM,WAAW,KAAK,gBAAgB;AACtC,YAAQ,IAAI,KAAK,KAAK,KAAK,IAAI,CAAC,EAAE;AAClC,YAAQ,IAAI,gBAAgB,KAAK,UAAU,EAAE;AAC7C,YAAQ,IAAI,kBAAkB,QAAQ,EAAE;AAAA,EAC1C;AACF;AAEA,eAAsB,aAAa,MAA6B;AAC9D,QAAM,WAAW,MAAM,WAAW,EAAE,MAAM,QAAQ,MAAM,IAAI,EAAE,IAAI;AAElE,MAAI,SAAS,OAAO;AAClB,UAAM,IAAI,MAAM,+BAA+B,IAAI,IAAI;AAAA,EACzD;AAEA,aAAW,OAAO,SAAS,MAAM;AAC/B,UAAM,IAAI,IAAI,OAAO;AAAA,EACvB;AAEA,UAAQ,IAAI,YAAY,IAAI,YAAY;AAC1C;AAEA,eAAsB,YAA2B;AAC/C,QAAM,aAAa,QAAQ,KAAK,CAAC;AACjC,QAAM,MAAM,QAAQ,KAAK,CAAC;AAE1B,MAAI,eAAe,UAAU;AAC3B,QAAI,CAAC,KAAK;AACR,cAAQ,MAAM,sCAAsC;AACpD,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,UAAM,MAAM,MAAM,aAAa,GAAG;AAClC,YAAQ,IAAI;AAAA,uBAA0B,GAAG;AAAA,CAAM;AAC/C,YAAQ,IAAI,KAAK,GAAG;AAAA,CAAI;AACxB,YAAQ,IAAI,kDAA6C;AAAA,EAC3D,WAAW,eAAe,QAAQ;AAChC,UAAM,YAAY;AAAA,EACpB,WAAW,eAAe,UAAU;AAClC,QAAI,CAAC,KAAK;AACR,cAAQ,MAAM,sCAAsC;AACpD,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,UAAM,aAAa,GAAG;AAAA,EACxB,OAAO;AACL,YAAQ,MAAM,oDAAoD;AAClE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;;;AClGA,SAAS,aAAAM,YAAW,SAAAC,cAAa;AACjC,SAAS,QAAAC,aAAY;AAUrB,IAAM,uBAAuB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,WAAW,SAA6B;AAC/C,QAAM,QAAoB,CAAC;AAC3B,QAAM,qBAAqB;AAC3B,QAAM,aAAiD,CAAC;AAExD,MAAI;AACJ,UAAQ,QAAQ,mBAAmB,KAAK,OAAO,OAAO,MAAM;AAC1D,eAAW,KAAK,EAAE,OAAO,MAAM,OAAO,OAAO,MAAM,CAAC,EAAE,CAAC;AAAA,EACzD;AAEA,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,UAAM,QAAQ,WAAW,CAAC,EAAE;AAC5B,UAAM,MAAM,IAAI,IAAI,WAAW,SAAS,WAAW,IAAI,CAAC,EAAE,QAAQ,QAAQ;AAC1E,UAAM,MAAM,QAAQ,MAAM,OAAO,GAAG,EAAE,QAAQ;AAE9C,UAAM,MAAM,WAAW,GAAG;AAC1B,QAAI,CAAC,IAAK;AAEV,UAAM,YAAY,IAAI,QAAQ,OAAO,CAAC;AACtC,QAAI,cAAc,GAAI;AACtB,UAAM,OAAO,IAAI,MAAM,IAAI,QAAQ,MAAM,SAAS,IAAI,CAAC;AAEvD,QAAI,UAAU;AACd,eAAW,WAAW,sBAAsB;AAC1C,gBAAU,QAAQ,QAAQ,SAAS,EAAE;AAAA,IACvC;AACA,cAAU,QAAQ,QAAQ,WAAW,MAAM,EAAE,KAAK;AAElD,QAAI,CAAC,QAAS;AAEd,UAAM,KAAK;AAAA,MACT,OAAO,WAAW,CAAC,EAAE;AAAA,MACrB;AAAA,MACA,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEA,SAAS,WAAW,aAAoC;AACtD,QAAM,QAAQ,YAAY;AAAA,IACxB;AAAA,EACF;AACA,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,QAAQ,KAAK,MAAM,IAAI,MAAM,CAAC,CAAC,GAAG;AACxC,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,MAAI,CAAC,MAAM,OAAO,KAAK,EAAG,QAAO;AAEjC,SAAO,oCAAoC,KAAK,KAAK,KAAK,CAAC;AAC7D;AAEA,eAAsB,eACpB,aACA,YACA,SACA,SACA,YAC0B;AAC1B,QAAM,WAAW,MAAM,MAAM,WAAW;AACxC,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,mBAAmB,WAAW,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EAC7F;AACA,QAAM,UAAU,MAAM,SAAS,KAAK;AAEpC,QAAM,QAAQ,WAAW,OAAO;AAEhC,QAAM,QAAQC,MAAK,SAAS,YAAY,UAAU;AAClD,QAAMC,OAAM,OAAO,EAAE,WAAW,KAAK,CAAC;AAEtC,QAAM,UAA2B,CAAC;AAClC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,cAAc,iBAAiB,YAAY,KAAK,KAAK,KAAK,KAAK;AACrE,UAAM,eAAe,GAAG,WAAW;AAAA;AAAA,EAAO,KAAK,QAAQ;AACvD,UAAM,OAAO,WAAW,KAAK,GAAG;AAChC,UAAMC,WAAUF,MAAK,OAAO,GAAG,IAAI,KAAK,GAAG,cAAc,OAAO;AAEhE,YAAQ,KAAK;AAAA,MACX,QAAQ;AAAA,MACR,KAAK,KAAK;AAAA,MACV,OAAO,KAAK;AAAA,MACZ,UAAU;AAAA,IACZ,CAAC;AAED,QAAI,gBAAgB,IAAI,KAAK,QAAQ,KAAK,IAAI,MAAM,MAAM,SAAS;AACjE,iBAAW,IAAI,GAAG,MAAM,MAAM;AAAA,IAChC;AAAA,EACF;AAEA,SAAO;AACT;;;AXnFA,IAAM,eAAe,QAAQ,YAAY,SAAS,IAAI;AACtD,IAAM,cAAcG,MAAK,cAAc,UAAU,cAAc;AAC/D,IAAM,WAAWA,MAAK,cAAc,MAAM;AAE1C,SAAS,OAAO,IAAwC,UAAmC;AACzF,SAAO,IAAI,QAAQ,CAACC,aAAY,GAAG,SAAS,UAAUA,QAAO,CAAC;AAChE;AAEA,eAAe,SAAwB;AACrC,QAAM,OAAO,UAAU;AAAA,IACrB,MAAM,QAAQ,KAAK,MAAM,CAAC;AAAA,IAC1B,SAAS;AAAA,MACP,KAAK,EAAE,MAAM,SAAS;AAAA,IACxB;AAAA,IACA,kBAAkB;AAAA,EACpB,CAAC;AAED,QAAM,OAAO,KAAK,YAAY,CAAC;AAC/B,QAAM,MAAM,KAAK,OAAO;AAExB,MAAI,CAAC,QAAQ,CAAC,KAAK;AACjB,YAAQ,MAAM,8CAA8C;AAC5D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,oBAAoB;AAEhC,QAAM,UAAU,MAAM,cAAc;AACpC,QAAM,UAAU,MAAM,QAAQ,WAAW;AACzC,QAAM,OAAO,MAAM,QAAQ,QAAQ;AAEnC,MAAI;AACF,UAAM,KAAK,KAAK,KAAK,EAAE,WAAW,mBAAmB,CAAC;AAEtD,UAAM,gBAAgB,MAAM,KAAK,SAAS,MAAM;AAC9C,YAAM,YAAY,CAAC,OAAO,qBAAqB;AAC/C,YAAM,UAAoE,CAAC;AAC3E,YAAM,OAAO,oBAAI,IAAa;AAE9B,iBAAW,OAAO,WAAW;AAC3B,mBAAW,MAAM,SAAS,iBAAiB,GAAG,GAAG;AAC/C,cAAI,KAAK,IAAI,EAAE,EAAG;AAClB,eAAK,IAAI,EAAE;AACX,gBAAM,QAAQ,GAAG,iBAAiB,SAAS;AAC3C,gBAAM,QACJ,GAAG,aAAa,YAAY,KAC5B,GAAG,aAAa,OAAO,KACvB,GAAG,QAAQ,YAAY;AACzB,kBAAQ,KAAK;AAAA,YACX,UAAU;AAAA,YACV;AAAA,YACA,WAAW,MAAM;AAAA,UACnB,CAAC;AAAA,QACH;AAAA,MACF;AAEA,aAAO,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAAA,IACzD,CAAC;AAED,QAAI,cAAc,WAAW,GAAG;AAC9B,cAAQ,MAAM,4CAA4C;AAC1D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,IAAI,wBAAwB;AACpC,aAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,YAAM,IAAI,cAAc,CAAC;AACzB,cAAQ,IAAI,MAAM,IAAI,CAAC,KAAK,EAAE,QAAQ,KAAK,EAAE,KAAK,YAAO,EAAE,SAAS,QAAQ;AAAA,IAC9E;AAEA,UAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAE3E,UAAM,YAAY,MAAM,OAAO,IAAI,uBAAuB;AAC1D,UAAM,WAAW,SAAS,WAAW,EAAE,IAAI;AAC3C,QAAI,MAAM,QAAQ,KAAK,WAAW,KAAK,YAAY,cAAc,QAAQ;AACvE,cAAQ,MAAM,oBAAoB;AAClC,SAAG,MAAM;AACT,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,cAAc,cAAc,QAAQ;AAE1C,UAAM,YAAY,IAAI,IAAI,GAAG;AAC7B,UAAM,iBAAiB,UAAU,SAAS,QAAQ,OAAO,EAAE;AAE3D,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B,GAAG,YAAY,QAAQ;AAAA,MACvB,CAAC,OAAO,YAAY;AAClB,eAAO,CAAC,GAAG,IAAI;AAAA,UACZ,MACE,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,KAAK,CAAC,EAAE,SAAS,MAAM,KAAK,CAAC,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,OAAO,CAAC;AAAA,QACzG,CAAC;AAAA,MACH;AAAA,MACA;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,QAAW,SAAS,MAAM,mBAAmB,cAAc,EAAE;AAEzE,UAAM,eAAe,MAAM,OAAO,IAAI,6BAA6B,cAAc,KAAK;AACtF,UAAM,iBAAiB,aAAa,KAAK,KAAK;AAE9C,UAAM,eAAe,MAAM,OAAO,IAAI,gDAAgD;AACtF,UAAM,kBAAkB,aAAa,KAAK,IACtC,aAAa,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAC3C;AAEJ,OAAG,MAAM;AAET,UAAM,kBAAkB,MAAM,KAAK,SAAS,MAAM;AAChD,UAAI,SAAS,cAAc,SAAS,EAAG,QAAO;AAC9C,UAAI,SAAS,cAAc,MAAM,EAAG,QAAO;AAC3C,aAAO;AAAA,IACT,CAAC;AAED,UAAM,kBAAkB,MAAM,KAAK,SAAS,MAAM;AAChD,YAAM,aAAa;AAAA,QACjB,EAAE,UAAU,OAAO,OAAO,MAAM;AAAA,QAChC,EAAE,UAAU,UAAU,OAAO,SAAS;AAAA,QACtC,EAAE,UAAU,0BAA0B,OAAO,yBAAyB;AAAA,QACtE,EAAE,UAAU,mBAAmB,OAAO,kBAAkB;AAAA,QACxD,EAAE,UAAU,6BAA6B,OAAO,eAAe;AAAA,QAC/D,EAAE,UAAU,gCAAgC,OAAO,kBAAkB;AAAA,MACvE;AACA,aAAO,WACJ,OAAO,CAAC,MAAM,SAAS,cAAc,EAAE,QAAQ,MAAM,IAAI,EACzD,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,IACvB,CAAC;AAED,QAAI,gBAAgB,SAAS,GAAG;AAC9B,cAAQ,IAAI;AAAA,+BAAkC,gBAAgB,KAAK,IAAI,CAAC,EAAE;AAAA,IAC5E;AAEA,UAAM,sBAAsB,IAAI,IAAI,GAAG;AACvC,QAAI;AACJ,QAAI;AACF,YAAM,eAAe,MAAM,KAAK,KAAK,GAAG,oBAAoB,MAAM,gBAAgB,EAAE,WAAW,oBAAoB,SAAS,IAAM,CAAC;AACnI,UAAI,gBAAgB,aAAa,OAAO,MAAM,KAAK;AACjD,cAAM,OAAO,MAAM,KAAK,YAAY,MAAM;AAC1C,YAAI,SAAS,KAAK,SAAS,SAAS,KAAK,KAAK,SAAS,eAAe,IAAI;AACxE,uBAAa,GAAG,oBAAoB,MAAM;AAC1C,kBAAQ,IAAI;AAAA,iBAAoB,UAAU,EAAE;AAAA,QAC9C;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,UAAM,SAAuB;AAAA,MAC3B,MAAM,KAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ,SAAS,CAAC,MAAM,EAAE,YAAY,CAAC;AAAA,MACrE,WAAW;AAAA,MACX,GAAI,aAAa,EAAE,aAAa,WAAW,IAAI,CAAC;AAAA,MAChD,cAAc,YAAY;AAAA,MAC1B,kBAAkB;AAAA,MAClB,kBAAkB,CAAC,cAAc;AAAA,MACjC,GAAI,kBAAkB,EAAE,kBAAkB,gBAAgB,IAAI,CAAC;AAAA,MAC/D,GAAI,gBAAgB,SAAS,IAAI,EAAE,kBAAkB,gBAAgB,IAAI,CAAC;AAAA,IAC5E;AAEA,QAAI,kBAAkB;AACtB,QAAI;AACF,wBAAkB,MAAMC,UAAS,aAAa,OAAO;AAAA,IACvD,QAAQ;AACN,wBAAkB;AAAA,IACpB;AAEA,UAAM,WAAW,UAAU,EAAE,CAAC,IAAI,GAAG,OAAO,GAAG,EAAE,QAAQ,EAAE,CAAC;AAC5D,UAAM,WAAW,SACd,MAAM,IAAI,EACV,IAAI,CAAC,SAAU,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,EAAG,EAC9C,KAAK,IAAI;AAEZ,UAAMC,WAAU,aAAa,gBAAgB,QAAQ,IAAI,OAAO,UAAU,OAAO;AAEjF,YAAQ,IAAI;AAAA,UAAa,IAAI,gCAAgC;AAC7D,YAAQ,IAAI,yBAAyB,IAAI,sBAAsB;AAAA,EACjE,UAAE;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AACF;AAKA,IAAM,eAAe;AAKrB,IAAM,4BAA4B;AAClC,IAAM,gCAAgC;AAKtC,IAAM,yBAAyB;AAE/B,eAAe,WACb,YACA,WACA,UACA,SACA,cAAc,OACC;AACf,UAAQ,IAAI,+BAA+B;AAC3C,QAAM,WAAW,MAAM,qBAAqB,UAAU;AACtD,QAAM,aAAa,IAAI,IAAI,UAAU,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AACrD,QAAM,WAAW,CAAC,GAAG,SAAS,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;AACxE,QAAM,UAAU,UAAU;AAAA,IACxB,CAAC,UAAU,SAAS,IAAI,MAAM,EAAE,MAAM,YAAY,eAAe,KAAK,CAAC;AAAA,EACzE;AAEA,UAAQ;AAAA,IACN,WAAW,QAAQ,MAAM,cAAc,UAAU,SAAS,QAAQ,MAAM,eAAe,SAAS,MAAM;AAAA,EACxG;AAEA,MACE,CAAC,eACD,SAAS,QAAQ,6BACjB,SAAS,SAAS,SAAS,OAAO,+BAClC;AACA,UAAM,IAAI;AAAA,MACR,sBAAsB,SAAS,MAAM,OAAO,SAAS,IAAI,uBAAuB,UAAU;AAAA,IAG5F;AAAA,EACF;AAEA,MACE,CAAC,eACD,SAAS,QAAQ,6BACjB,UAAU,SAAS,SAAS,OAAO,wBACnC;AACA,UAAM,IAAI;AAAA,MACR,qBAAqB,UAAU,UAAU,SAAS,IAAI,OAAO,UAAU,MAAM,aAAa,sBAAsB;AAAA,IAGlH;AAAA,EACF;AAEA,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,cAAc;AACrD,UAAM,SAAS,QAAQ,MAAM,GAAG,IAAI,YAAY;AAChD,UAAM,aAAa,MAAM,WAAW,OAAO,IAAI,CAAC,MAAM,eAAe,CAAC,CAAC,GAAG;AAAA,MACxE,YAAY,CAAC,SAAS;AACpB,gBAAQ,IAAI,MAAM,IAAI,IAAI,IAAI,QAAQ,MAAM,YAAY;AAAA,MAC1D;AAAA,IACF,CAAC;AACD,UAAM,YAAY,QAAQ,YAAY,CAAC,QAAQ;AAC7C,cAAQ,IAAI,MAAM,IAAI,GAAG,IAAI,QAAQ,MAAM,UAAU;AAAA,IACvD,CAAC;AAAA,EACH;AAIA,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,kBAAkB,UAAU,CAAC,KAAK,UAAU;AAChD,cAAQ,IAAI,MAAM,GAAG,IAAI,KAAK,WAAW;AAAA,IAC3C,CAAC;AAAA,EACH;AAEA,QAAM,iBAAiB,YAAY,UAAU,QAAQ,UAAU,OAAO;AACtE,UAAQ,IAAI,WAAW,UAAU,MAAM,qBAAqB,UAAU,IAAI;AAC5E;AAWA,SAAS,qBACP,OACA,QACA,iBACA,OACK;AACL,MAAI,CAAC,mBAAmB,gBAAgB,WAAW,EAAG,QAAO;AAC7D,QAAM,OAAO,MAAM;AAAA,IACjB,CAAC,SAAS,CAAC,gBAAgB,KAAK,CAAC,YAAY,OAAO,IAAI,EAAE,SAAS,OAAO,CAAC;AAAA,EAC7E;AACA,MAAI,KAAK,SAAS,MAAM,QAAQ;AAC9B,YAAQ,IAAI,cAAc,MAAM,SAAS,KAAK,MAAM,IAAI,KAAK,wBAAwB;AAAA,EACvF;AACA,SAAO;AACT;AAEA,eAAe,wBAAwB,OAAuC;AAC5E,QAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,MAAM,MAAM,CAAC,CAAa;AAC/D,QAAM,QAAuB,CAAC;AAC9B,aAAW,KAAK,QAAQ,OAAO,CAACC,OAAMA,GAAE,SAAS,KAAK,CAAC,GAAG;AACxD,UAAM,UAAU,MAAMF,UAASF,MAAK,OAAO,CAAC,GAAG,OAAO;AACtD,UAAM,WAAW,QAAQ,MAAM,gBAAgB;AAC/C,UAAM,aAAa,QAAQ,MAAM,kBAAkB;AACnD,QAAI,CAAC,UAAU;AACb,cAAQ,KAAK,cAAc,CAAC,4CAA4C;AACxE;AAAA,IACF;AACA,UAAM,KAAK;AAAA,MACT,UAAU;AAAA,MACV,KAAK,SAAS,CAAC;AAAA,MACf,OAAO,aAAa,CAAC,KAAK;AAAA,IAC5B,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,eAAe,oBAAoB,QAAmC;AACpE,QAAM,eAAeA,MAAK,QAAQ,WAAW;AAC7C,MAAI;AACF,WAAO,KAAK,MAAM,MAAME,UAAS,cAAc,OAAO,CAAC;AAAA,EACzD,QAAQ;AACN,UAAM,WAAW,MAAM,QAAQ,MAAM;AACrC,UAAM,OAAiB,CAAC;AACxB,QAAI,UAAU;AACd,eAAW,KAAK,SAAS,OAAO,CAACE,OAAMA,GAAE,SAAS,OAAO,CAAC,GAAG;AAC3D,YAAM,WAAW,EAAE,QAAQ,WAAW,EAAE;AACxC,YAAM,OAAO,MAAMF,UAASF,MAAK,QAAQ,CAAC,GAAG,OAAO;AACpD,YAAM,QAAQ,KAAK,MAAM,8CAA8C;AACvE,UAAI,SAAS,WAAW,MAAM,CAAC,CAAC,MAAM,UAAU;AAAE,aAAK,KAAK,MAAM,CAAC,CAAC;AAAG;AAAA,MAAU;AACjF,YAAM,UAAU,KAAK,MAAM,mDAAmD;AAC9E,UAAI,WAAW,WAAW,QAAQ,CAAC,CAAC,MAAM,UAAU;AAAE,aAAK,KAAK,QAAQ,CAAC,CAAC;AAAG;AAAA,MAAU;AACvF,cAAQ,KAAK,qCAAqC,CAAC,kBAAkB;AACrE;AAAA,IACF;AACA,QAAI,UAAU,GAAG;AACf,cAAQ,KAAK,aAAa,OAAO,oEAAoE;AAAA,IACvG;AACA,WAAO;AAAA,EACT;AACF;AAEA,eAAe,aAA4B;AACzC,QAAM,OAAO,UAAU;AAAA,IACrB,MAAM,QAAQ,KAAK,MAAM,CAAC;AAAA,IAC1B,SAAS;AAAA,MACP,MAAM,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MACxC,KAAK,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MACvC,aAAa,EAAE,MAAM,SAAS;AAAA,MAC9B,OAAO,EAAE,MAAM,SAAS;AAAA,MACxB,aAAa,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MAC/C,iBAAiB,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MACnD,cAAc,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MAChD,gBAAgB,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,IACpD;AAAA,IACA,kBAAkB;AAAA,EACpB,CAAC;AAED,QAAM,SAAS,MAAM,WAAW,WAAW;AAC3C,QAAM,mBAAmB,KAAK,OAAO,MACjC,OAAO,KAAK,OAAO,OAAO,IAC1B,CAAC,KAAK,YAAY,CAAC,CAAC;AAExB,MAAI,CAAC,KAAK,OAAO,OAAO,CAAC,iBAAiB,CAAC,GAAG;AAC5C,YAAQ,MAAM,6IAA6I;AAC3J,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,sBAAsB,KAAK,OAAO,cAAc,SAAS,KAAK,OAAO,aAAa,EAAE,IAAI;AAC9F,QAAM,WAAW,KAAK,OAAO,QAAQ,SAAS,KAAK,OAAO,OAAO,EAAE,IAAI;AAEvE,aAAW,cAAc,kBAAkB;AACzC,UAAM,SAAS,OAAO,QAAQ,UAAU;AACxC,QAAI,CAAC,QAAQ;AACX,cAAQ,MAAM,WAAW,UAAU,wBAAwB;AAC3D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,qBAAqB;AACvB,aAAO,cAAc;AAAA,IACvB;AAEA,UAAM,SAASA,MAAK,UAAU,OAAO,UAAU;AAC/C,UAAM,QAAQA,MAAK,UAAU,YAAY,UAAU;AAEnD,YAAQ,IAAI;AAAA,cAAiB,UAAU,MAAM;AAE7C,QAAI,KAAK,OAAO,MAAM;AACpB,cAAQ,IAAI,8BAA8B;AAC1C,YAAM,UAAU,MAAM,YAAY,UAAU;AAC5C,cAAQ,IAAI,aAAa,OAAO,UAAU;AAC1C,YAAM,GAAG,QAAQ,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACjD,YAAM,GAAG,OAAO,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IAClD;AAEA,QAAI;AAEJ,QAAI,KAAK,OAAO,eAAe,GAAG;AAChC,cAAQ,IAAI,8BAA8B;AAC1C,cAAQ,MAAM,wBAAwB,KAAK;AAC3C,UAAI,MAAM,WAAW,GAAG;AACtB,gBAAQ,MAAM,yDAAyD;AACvE,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,cAAQ,qBAAqB,OAAO,CAAC,MAAM,EAAE,KAAK,OAAO,kBAAkB,cAAc;AACzF,cAAQ,IAAI,WAAW,MAAM,MAAM,gBAAgB;AAAA,IACrD,WAAW,OAAO,iBAAiB,CAAC,KAAK,OAAO,WAAW,GAAG;AAC5D,cAAQ,IAAI,iCAAiC,OAAO,aAAa,KAAK;AACtE,cAAQ,MAAM;AAAA,QACZ,OAAO;AAAA,QACP;AAAA,QACA,OAAO;AAAA,QACP;AAAA,QACA,CAAC,KAAK,UAAU;AACd,kBAAQ,IAAI,MAAM,GAAG,IAAI,KAAK,mBAAmB;AAAA,QACnD;AAAA,MACF;AACA,cAAQ,IAAI,eAAe,MAAM,MAAM,SAAS;AAAA,IAClD,OAAO;AACL,UAAI;AACJ,UAAI,KAAK,OAAO,WAAW,GAAG;AAC5B,gBAAQ,IAAI,oCAAoC;AAChD,eAAO,MAAM,oBAAoB,MAAM;AACvC,eAAO,qBAAqB,MAAM,CAAC,MAAM,GAAG,OAAO,kBAAkB,aAAa;AAClF,gBAAQ,IAAI,WAAW,KAAK,MAAM,gBAAgB;AAAA,MACpD,OAAO;AACL,gBAAQ,IAAI,oBAAoB;AAChC,eAAO,MAAM,aAAa,QAAQ,YAAY,UAAU,CAAC,KAAK,OAAO,QAAQ;AAC3E,kBAAQ,IAAI,MAAM,GAAG,IAAI,KAAK,KAAK,GAAG,EAAE;AAAA,QAC1C,CAAC;AACD,gBAAQ,IAAI,WAAW,KAAK,MAAM,SAAS;AAAA,MAC7C;AAEA,UAAI,YAAY,KAAK,SAAS,UAAU;AACtC,eAAO,KAAK,MAAM,GAAG,QAAQ;AAC7B,gBAAQ,IAAI,gBAAgB,QAAQ,SAAS;AAAA,MAC/C;AAEA,cAAQ,IAAI,6BAA6B;AACzC,cAAQ,MAAM;AAAA,QACZ;AAAA,QACA;AAAA,QACA,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,QACP;AAAA,QACA,OAAO;AAAA,QACP,CAAC,KAAK,UAAU;AACd,cAAI,MAAM,OAAO,KAAK,QAAQ,MAAO,SAAQ,IAAI,MAAM,GAAG,IAAI,KAAK,aAAa;AAAA,QAClF;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,IAAI,eAAe;AAC3B,UAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,cAAc,EAAE,UAAU,YAAY,EAAE,KAAK,EAAE,KAAK,CAAC;AAC5F,YAAQ,IAAI,aAAa,UAAU,MAAM,UAAU;AAEnD,QAAI,KAAK,OAAO,YAAY,GAAG;AAC7B,cAAQ,IAAI,WAAW,UAAU,MAAM,0CAA0C;AACjF;AAAA,IACF;AAEA,UAAM,WAAW,YAAY,WAAW,MAAM,QAAQ,OAAO,SAAS,KAAK,OAAO,cAAc,CAAC;AAAA,EACnG;AACF;AAEA,eAAe,YAA2B;AACxC,QAAM,OAAO,UAAU;AAAA,IACrB,MAAM,QAAQ,KAAK,MAAM,CAAC;AAAA,IAC1B,SAAS;AAAA,MACP,QAAQ,EAAE,MAAM,SAAS;AAAA,MACzB,KAAK,EAAE,MAAM,SAAS;AAAA,MACtB,YAAY,EAAE,MAAM,SAAS;AAAA,MAC7B,SAAS,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,IAC7C;AAAA,IACA,kBAAkB;AAAA,EACpB,CAAC;AAED,QAAM,QAAQ,KAAK,YAAY,KAAK,GAAG;AACvC,MAAI,CAAC,OAAO;AACV,YAAQ,MAAM,+FAAiG;AAC/G,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAO,KAAK,OAAO,MAAM,SAAS,KAAK,OAAO,KAAK,EAAE,IAAI;AAC/D,QAAM,aAAa,KAAK,OAAO,aAAa,SAAS,KAAK,OAAO,YAAY,EAAE,IAAI;AACnF,QAAM,UAAU,MAAM,OAAO,OAAO,EAAE,QAAQ,KAAK,OAAO,QAAQ,MAAM,WAAW,CAAC;AAEpF,MAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,IAAI,mBAAmB;AAC/B;AAAA,EACF;AAEA,MAAI,KAAK,OAAO,SAAS;AACvB,eAAW,KAAK,SAAS;AACvB,cAAQ,IAAI,GAAG,EAAE,gBAAgB,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,MAAM,EAAE,aAAa,KAAK,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE;AAAA,IACrH;AACA;AAAA,EACF;AAEA,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,IAAI,QAAQ,CAAC;AACnB,YAAQ,IAAI;AAAA,EAAK,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,gBAAgB,QAAQ,CAAC,CAAC,GAAG;AAClF,YAAQ,IAAI,OAAO,KAAK,EAAE,GAAG,CAAC,EAAE;AAChC,YAAQ,IAAI,OAAO,OAAO,EAAE,aAAa,KAAK,KAAK,CAAC,CAAC,EAAE;AACvD,YAAQ,IAAI,OAAO,EAAE,QAAQ,QAAQ,OAAO,GAAG,CAAC,EAAE;AAAA,EACpD;AACF;AAEA,eAAe,UAAyB;AACtC,QAAM,OAAO,UAAU;AAAA,IACrB,MAAM,QAAQ,KAAK,MAAM,CAAC;AAAA,IAC1B,SAAS;AAAA,MACP,OAAO,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,IAC3C;AAAA,IACA,kBAAkB;AAAA,EACpB,CAAC;AAED,QAAM,QAAQ,MAAM,kBAAkB;AAEtC,MAAI,MAAM,WAAW,GAAG;AACtB,YAAQ,IAAI,qCAAqC;AACjD;AAAA,EACF;AAEA,MAAI,KAAK,OAAO,OAAO;AACrB,eAAW,QAAQ,OAAO;AACxB,cAAQ,IAAI,KAAK,MAAM;AAAA,IACzB;AACA;AAAA,EACF;AAEA,UAAQ,IAAI,cAAc;AAC1B,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK;AACjD,YAAQ,IAAI,KAAK,KAAK,KAAK,MAAM,CAAC,GAAG,GAAG,EAAE;AAC1C,YAAQ,IAAI,OAAO,KAAK,WAAW,YAAY,KAAK,SAAS,yBAAyB,KAAK,cAAc,EAAE;AAAA,EAC7G;AACF;AAEA,eAAe,WAA0B;AACvC,QAAM,QAAQ,MAAM,kBAAkB;AAEtC,MAAI,MAAM,WAAW,GAAG;AACtB,YAAQ,IAAI,qCAAqC;AACjD;AAAA,EACF;AAEA,MAAI,cAAc;AAClB,MAAI,YAAY;AAEhB,UAAQ,IAAI,wBAAwB;AACpC,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK;AACjD,YAAQ,IAAI,KAAK,KAAK,KAAK,MAAM,CAAC,GAAG,GAAG,EAAE;AAC1C,YAAQ,IAAI,eAAe,KAAK,WAAW,EAAE;AAC7C,YAAQ,IAAI,aAAa,KAAK,SAAS,EAAE;AACzC,YAAQ,IAAI,uBAAuB,KAAK,cAAc,EAAE;AACxD,mBAAe,KAAK;AACpB,iBAAa,KAAK;AAAA,EACpB;AAEA,UAAQ,IAAI;AAAA,WAAc,WAAW,kBAAkB,SAAS,cAAc,MAAM,MAAM,UAAU;AACtG;AAEA,eAAe,YAA2B;AACxC,QAAM,OAAO,UAAU;AAAA,IACrB,MAAM,QAAQ,KAAK,MAAM,CAAC;AAAA,IAC1B,SAAS;AAAA,MACP,QAAQ,EAAE,MAAM,UAAU,SAAS,OAAO;AAAA,IAC5C;AAAA,IACA,kBAAkB;AAAA,EACpB,CAAC;AAED,QAAM,aAAa,KAAK,YAAY,CAAC;AACrC,MAAI,CAAC,YAAY;AACf,YAAQ,MAAM,iDAAiD;AAC/D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQA,MAAK,UAAU,YAAY,UAAU;AACnD,MAAI;AACJ,MAAI;AACF,YAAQ,MAAM,QAAQ,KAAK;AAAA,EAC7B,QAAQ;AACN,YAAQ,MAAM,sCAAsC,UAAU,IAAI;AAClE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQ,CAAC;AACf,aAAW,QAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,GAAG;AACzD,UAAM,UAAU,MAAME,UAASF,MAAK,OAAO,IAAI,GAAG,OAAO;AACzD,UAAM,KAAK,EAAE,MAAM,QAAQ,CAAC;AAAA,EAC9B;AAEA,UAAQ,IAAI,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAC5C;AAEA,eAAe,YAA2B;AACxC,QAAM,aAAa,QAAQ,KAAK,CAAC;AACjC,MAAI,CAAC,YAAY;AACf,YAAQ,MAAM,iCAAiC;AAC/C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAO,MAAM,cAAc,UAAU;AAC3C,MAAI,CAAC,MAAM;AACT,YAAQ,MAAM,WAAW,UAAU,2BAA2B;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,aAAa,UAAU,MAAM,KAAK,WAAW,aAAa;AACtE,QAAM,UAAU,MAAM,YAAY,UAAU;AAC5C,QAAM,iBAAiB,UAAU;AACjC,UAAQ,IAAI,WAAW,OAAO,oCAAoC,UAAU,IAAI;AAClF;AAEA,eAAe,gBAA+B;AAC5C,QAAM,OAAO,UAAU;AAAA,IACrB,MAAM,QAAQ,KAAK,MAAM,CAAC;AAAA,IAC1B,SAAS;AAAA,MACP,aAAa,EAAE,MAAM,SAAS;AAAA,IAChC;AAAA,IACA,kBAAkB;AAAA,EACpB,CAAC;AAED,QAAM,aAAa,KAAK,YAAY,CAAC;AACrC,MAAI,CAAC,YAAY;AACf,YAAQ,MAAM,0DAA0D;AACxE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,SAAS,MAAM,WAAW,WAAW;AAC3C,QAAM,SAAS,OAAO,QAAQ,UAAU;AACxC,MAAI,CAAC,QAAQ;AACX,YAAQ,MAAM,WAAW,UAAU,wBAAwB;AAC3D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,SAASA,MAAK,UAAU,OAAO,UAAU;AAC/C,QAAM,WAAWA,MAAK,QAAQ,WAAW;AAEzC,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,MAAM,MAAME,UAAS,UAAU,OAAO,CAAC;AAAA,EACrD,QAAQ;AACN,YAAQ,MAAM,2BAA2B,UAAU,4BAA4B,UAAU,uBAAuB;AAChH,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,UAAU,KAAK,OAAO,CAAC,QAAQ,CAAC,WAAWF,MAAK,QAAQ,GAAG,WAAW,GAAG,CAAC,OAAO,CAAC,CAAC;AACzF,UAAQ,IAAI;AAAA,SAAY,KAAK,MAAM,aAAa,KAAK,SAAS,QAAQ,MAAM,cAAc,QAAQ,MAAM,EAAE;AAE1G,MAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,IAAI,oBAAoB;AAChC;AAAA,EACF;AAEA,QAAM,cAAc,KAAK,OAAO,cAAc,SAAS,KAAK,OAAO,aAAa,EAAE,IAAI,OAAO,eAAe;AAC5G,QAAM,UAAU,MAAM,cAAc;AACpC,QAAM,UAAU,MAAM,QAAQ,WAAW,OAAO,aAAa,EAAE,WAAW,OAAO,WAAW,IAAI,CAAC,CAAC;AAClG,MAAI,OAAO;AAEX,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,aAAa;AACpD,UAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,WAAW;AAC9C,UAAM,QAAQ,IAAI,MAAM,IAAI,OAAO,QAAQ;AACzC,YAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,UAAI;AACF,cAAM,KAAK,KAAK,KAAK,EAAE,WAAW,OAAO,SAAS,gBAAgB,oBAAoB,SAAS,IAAM,CAAC;AACtG,YAAI,OAAO,mBAAmB;AAC5B,gBAAM,KAAK,gBAAgB,OAAO,mBAAmB,EAAE,SAAS,IAAM,CAAC;AAAA,QACzE;AACA,cAAM,OAAO,MAAM,KAAK,QAAQ;AAChC,cAAMG,WAAUH,MAAK,QAAQ,GAAG,WAAW,GAAG,CAAC,OAAO,GAAG,MAAM,OAAO;AACtE;AACA,YAAI,OAAO,OAAO,KAAK,SAAS,QAAQ,OAAQ,SAAQ,IAAI,MAAM,IAAI,IAAI,QAAQ,MAAM,GAAG;AAAA,MAC7F,SAAS,GAAG;AACV,gBAAQ,MAAM,aAAa,GAAG,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,MAClF,UAAE;AACA,cAAM,KAAK,MAAM;AAAA,MACnB;AAAA,IACF,CAAC,CAAC;AAAA,EACJ;AAEA,UAAQ,IAAI,iBAAiB,IAAI,SAAS;AAC1C,QAAM,QAAQ,MAAM;AACtB;AAEO,IAAM,iBAAsD;AAAA,EACjE,KAAK;AAAA,EACL,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AACV;",
6
- "names": ["cell", "tables", "readFile", "writeFile", "join", "resolve", "readFile", "writeFile", "mkdir", "join", "DEFAULT_CONCURRENCY", "join", "mkdir", "readFile", "writeFile", "fenceMatch", "resolve", "BATCH_SIZE", "MAX_RETRIES", "sleep", "resolve", "createHash", "getFirestore", "initializeApp", "applicationDefault", "getApps", "db", "getDb", "getApps", "initializeApp", "applicationDefault", "getFirestore", "createHash", "writeFile", "mkdir", "join", "join", "mkdir", "writeFile", "join", "resolve", "readFile", "writeFile", "f"]
7
- }