@bpinternal/site-scout 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.1.0_@microsoft+api-extractor@7.55.2_@types+node@22.19.1__@swc+core@1.15.3_@swc+h_0d370d8792226e40547a28534505c711/node_modules/tsup/assets/esm_shims.js","../src/web-fetch.ts","../src/guard.ts","../src/discovery/parse-robots.ts","../src/discovery/parse-sitemap.ts","../src/discovery/parse-llms.ts","../src/discovery/normalize.ts","../src/discovery/detect-site-type.ts","../src/discovery/score.ts","../src/discovery/build-tree.ts","../src/discovery/discover.ts","../src/selection/llm-select.ts","../src/selection/candidate-tree.ts","../src/selection/select.ts","../src/http-cache.ts","../src/discovery/cached-deps.ts","../src/index.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport { fileURLToPath } from 'url'\nimport path from 'path'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","// Shared best-effort web fetch + SSRF guard for site-analysis helpers\n// (FAQ detection, ecommerce-framework detection). Everything here is\n// best-effort: any failure degrades to `null`, never throws.\n//\n// SSRF defense (defense-in-depth — assume network egress is also restricted):\n// 1. Same-site string guard (isFetchableSameSite): plain http(s) only, on a\n// real domain (no IP literal, no bare/loopback host) sharing the business\n// site's registrable domain.\n// 2. Resolved-IP guard (hostResolvesToAllowed): the host is DNS-resolved and\n// rejected if any address is private/loopback/link-local/ULA/metadata —\n// so a subdomain A-recorded at 169.254.169.254 / RFC1918 (\"DNS rebinding\")\n// is blocked. Fails OPEN only when the host doesn't resolve at all\n// (NXDOMAIN/timeout) — an unreachable host can't be a target anyway.\n// 3. Manual redirects: redirect:'manual' + re-running (1)+(2) on every hop,\n// capped — so an approved same-site URL can't 30x to an internal host.\n// 4. Response-size cap — bounds memory on a hostile/huge body.\n// Residual gap: a single fetch re-resolves DNS internally, so active rebinding\n// (DNS that flips between our check and the fetch) isn't fully closed without\n// pinning the resolved IP; egress restrictions are the backstop for that.\n\nimport { lookup } from 'dns/promises'\n\nconst FETCH_TIMEOUT_MS = 6000\nconst DNS_TIMEOUT_MS = 2000\nconst MAX_REDIRECTS = 3\n/** Hard cap on a fetched body (~2 MB). Site-analysis bodies are tiny; this just\n * bounds memory against a hostile/huge response. */\nconst MAX_RESPONSE_BYTES = 2_000_000\nexport const SITE_FETCH_USER_AGENT = 'BotpressVibe/1.0 (+https://botpress.com) site-analyzer'\n\n/**\n * Read a response body as text, ENFORCING the size cap during streaming.\n * `content-length` is only a hint (absent on chunked/CDN responses), so we\n * read the body in chunks and bail as soon as we cross MAX_RESPONSE_BYTES —\n * never buffering a hostile multi-MB body fully into memory. Falls back to\n * `res.text()` (with a post-read length check) when the body isn't streamable.\n */\nasync function readBodyCapped(res: Response): Promise<string | null> {\n const reader = (res.body as any)?.getReader?.()\n if (!reader) {\n // No stream (e.g. a mocked Response) — buffer then check.\n const text = await res.text()\n return text.length > MAX_RESPONSE_BYTES ? null : text\n }\n const decoder = new TextDecoder()\n let out = ''\n let bytes = 0\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n bytes += value?.byteLength ?? 0\n if (bytes > MAX_RESPONSE_BYTES) {\n try {\n await reader.cancel()\n } catch {\n // best-effort\n }\n return null\n }\n out += decoder.decode(value, { stream: true })\n }\n out += decoder.decode()\n return out\n } catch {\n return null\n }\n}\n\n/** Fetch a URL's body as text, SSRF-guarded against `baseHost`. Null on any failure. */\nexport async function fetchText(url: string, baseHost: string, timeoutMs = FETCH_TIMEOUT_MS): Promise<string | null> {\n const res = await safeFetch(url, baseHost, timeoutMs)\n if (!res) return null\n return readBodyCapped(res)\n}\n\n/**\n * Fetch + parse a JSON endpoint, SSRF-guarded against `baseHost`. Tolerates\n * stores that serve JSON with a non-JSON content-type (e.g. `text/javascript`\n * on Shopify's `/products.json`) by parsing the raw text. Null on any failure\n * (network, non-2xx, blocked, unparseable).\n */\nexport async function fetchJson<T = unknown>(\n url: string,\n baseHost: string,\n timeoutMs = FETCH_TIMEOUT_MS\n): Promise<T | null> {\n const res = await safeFetch(url, baseHost, timeoutMs, 'application/json')\n if (!res) return null\n const text = await readBodyCapped(res)\n if (text == null) return null\n try {\n return JSON.parse(text) as T\n } catch {\n return null\n }\n}\n\n/**\n * One guarded fetch with manual redirect handling. Validates the same-site\n * string guard AND the resolved-IP guard before every hop (initial + each\n * redirect Location), capped at MAX_REDIRECTS. Returns the final 2xx Response,\n * or null if blocked / non-2xx / too many redirects.\n */\nasync function safeFetch(url: string, baseHost: string, timeoutMs: number, accept?: string): Promise<Response | null> {\n if (typeof fetch !== 'function') return null\n let target = url\n for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {\n if (!isFetchableSameSite(target, baseHost)) return null\n const host = hostOf(target)\n if (!host || !(await hostResolvesToAllowed(host))) return null\n\n const ctl = new AbortController()\n const timer = setTimeout(() => ctl.abort(), timeoutMs)\n let res: Response\n try {\n res = await fetch(target, {\n signal: ctl.signal,\n headers: { 'user-agent': SITE_FETCH_USER_AGENT, ...(accept ? { accept } : {}) },\n redirect: 'manual',\n })\n } catch {\n clearTimeout(timer)\n return null\n }\n clearTimeout(timer)\n\n const status = typeof res.status === 'number' ? res.status : 200\n if (status >= 300 && status < 400) {\n const loc = res.headers?.get?.('location')\n if (!loc) return null\n try {\n target = new URL(loc, target).toString()\n } catch {\n return null\n }\n continue // re-validate the redirect target on the next iteration\n }\n if (!res.ok) return null\n const len = Number(res.headers?.get?.('content-length') ?? '')\n if (Number.isFinite(len) && len > MAX_RESPONSE_BYTES) return null\n return res\n }\n return null // too many redirects\n}\n\n/** The origin (scheme + host) of a base URL, tolerating a bare host. */\nexport function siteOrigin(baseUrl: string): string | null {\n try {\n return new URL(baseUrl).origin\n } catch {\n try {\n return new URL(`https://${baseUrl}`).origin\n } catch {\n return null\n }\n }\n}\n\n/** Lower-cased hostname of a URL, tolerating a bare host. */\nexport function hostOf(url: string): string | null {\n try {\n return new URL(url).hostname.toLowerCase() || null\n } catch {\n try {\n return new URL(`https://${url}`).hostname.toLowerCase() || null\n } catch {\n return null\n }\n }\n}\n\n/** Registrable-ish domain (last two labels) — a coarse same-site key. */\nfunction registrableDomain(host: string): string {\n return host.split('.').slice(-2).join('.')\n}\n\nfunction isIpLiteral(host: string): boolean {\n // URL hostnames carry IPv6 without brackets, so a colon means IPv6.\n if (host.includes(':')) return true\n return /^\\d{1,3}(\\.\\d{1,3}){3}$/.test(host)\n}\n\n/**\n * Same-site string guard (no I/O — pure, unit-tested). Allows only plain http(s)\n * on a real domain (rejects IP literals, bare/loopback/no-dot hosts) that shares\n * the business site's registrable domain. NOTE: registrable domain is a coarse\n * last-two-labels key (not a true PSL eTLD+1), so it's imprecise on shared\n * suffixes like *.myshopify.com / *.co.uk — acceptable here since the resolved-IP\n * guard + egress restrictions cover the internal-host case.\n */\nexport function isFetchableSameSite(url: string, baseHost: string): boolean {\n let u: URL\n try {\n u = new URL(url)\n } catch {\n return false\n }\n if (u.protocol !== 'http:' && u.protocol !== 'https:') return false\n const host = u.hostname.toLowerCase()\n if (!host || host === 'localhost' || !host.includes('.') || isIpLiteral(host)) return false\n return registrableDomain(host) === registrableDomain(baseHost)\n}\n\n/** True if an IPv4 dotted-quad falls in a private/loopback/link-local/reserved range. */\nfunction isDisallowedIpv4(ip: string): boolean {\n const m = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ip)\n if (!m) return false\n const a = Number(m[1])\n const b = Number(m[2])\n const c = Number(m[3])\n if (a === 0 || a === 10 || a === 127) return true // \"this\", private, loopback\n if (a === 169 && b === 254) return true // link-local incl. cloud metadata 169.254.169.254\n if (a === 172 && b >= 16 && b <= 31) return true // private\n if (a === 192 && b === 168) return true // private\n if (a === 100 && b >= 64 && b <= 127) return true // CGNAT\n if (a === 192 && b === 0 && c === 0) return true // IETF protocol assignments\n if (a === 198 && (b === 18 || b === 19)) return true // benchmarking\n if (a >= 224) return true // multicast (224/4) + reserved (240/4) + broadcast\n return false\n}\n\n/** True if a resolved IP (v4 or v6) must not be fetched. Exported for tests. */\nexport function isDisallowedIp(ip: string): boolean {\n const addr = ip\n .toLowerCase()\n .replace(/^\\[|\\]$/g, '')\n .replace(/%.*$/, '') // strip [brackets] + zone id\n const mapped = /^::ffff:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/.exec(addr)\n if (mapped) return isDisallowedIpv4(mapped[1]!)\n if (addr.includes(':')) {\n if (addr === '::1' || addr === '::') return true // loopback / unspecified\n if (addr.startsWith('fc') || addr.startsWith('fd')) return true // ULA fc00::/7\n if (/^fe[89ab]/.test(addr)) return true // link-local fe80::/10\n if (/^fec/.test(addr)) return true // deprecated site-local\n if (addr.startsWith('ff')) return true // multicast\n return false\n }\n return isDisallowedIpv4(addr)\n}\n\n/**\n * Resolve `host` and reject if any address is private/internal. Fails OPEN when\n * the host doesn't resolve at all (NXDOMAIN/timeout/unavailable) — an\n * unreachable host can't be an SSRF target, and the subsequent fetch will fail\n * anyway. The danger we close is a host that RESOLVES to an internal IP.\n */\nasync function hostResolvesToAllowed(host: string): Promise<boolean> {\n if (isDisallowedIp(host)) return false // defensive (string guard already blocks literals)\n try {\n const records = (await Promise.race([\n lookup(host, { all: true }),\n new Promise<never>((_, reject) => setTimeout(() => reject(new Error('dns timeout')), DNS_TIMEOUT_MS)),\n ])) as { address: string }[]\n if (!records.length) return true\n return records.every((r) => !isDisallowedIp(r.address))\n } catch {\n return true\n }\n}\n","// ── Guardrail: refuse junk / mega-site inputs ──\n//\n// \"Scrape this site\" is meaningless or hostile for social networks, search\n// engines, and generic mega-platforms (the whole point of a support-agent KB is\n// a *company's own* site). We refuse these BEFORE any discovery so we never\n// crawl, e.g., wikipedia or x.com when a user pastes a vague URL.\n//\n// Matching is intentionally small + explicit: registrable domain match, plus a\n// few full-host matches for product subdomains (drive.google.com). Pure, no I/O.\n\nimport { hostOf } from './web-fetch'\n\n/** Registrable-ish domain (last two labels) — coarse same-site key. */\nfunction registrableDomain(host: string): string {\n return host.split('.').slice(-2).join('.')\n}\n\n/** Registrable domains we always refuse. */\nconst DENY_DOMAINS = new Set<string>([\n // search engines\n 'google.com',\n 'google.ca',\n 'bing.com',\n 'duckduckgo.com',\n 'baidu.com',\n 'yandex.com',\n // social networks\n 'facebook.com',\n 'instagram.com',\n 'x.com',\n 'twitter.com',\n 'reddit.com',\n 'linkedin.com',\n 'tiktok.com',\n 'pinterest.com',\n 'snapchat.com',\n 'threads.net',\n 'youtube.com',\n 'whatsapp.com',\n 'telegram.org',\n 'discord.com',\n // knowledge / UGC platforms where a \"site\" isn't a company KB\n 'wikipedia.org',\n 'wikimedia.org',\n 'quora.com',\n 'tumblr.com',\n])\n// NOTE: we deliberately DO NOT refuse legitimate company domains — apple.com,\n// microsoft.com, github.com, amazon.com, medium.com, etc. are real companies a\n// support bot may target. Only search engines, social networks, and pure\n// UGC/file-host platforms are refused.\n\n/** Full hosts we refuse even though their registrable domain might be allowed. */\nconst DENY_HOSTS = new Set<string>([\n 'drive.google.com',\n 'docs.google.com',\n 'sites.google.com',\n 'maps.google.com',\n 'play.google.com',\n 'mail.google.com',\n])\n\n/**\n * True when the input site must NOT be scraped. `website` may be a bare host,\n * a domain, or a full URL. Unparseable input is NOT denied here (discovery will\n * fail naturally / be handled elsewhere) — this only refuses *known* mega-sites.\n */\nexport function isUnsupportedSite(website: string): boolean {\n const host = hostOf(website)\n if (!host) return false\n if (DENY_HOSTS.has(host)) return true\n return DENY_DOMAINS.has(registrableDomain(host))\n}\n","// ── robots.txt parsing (wraps `robots-parser`) ──\n//\n// We only need two things from robots.txt:\n// - `Sitemap:` directives — the canonical (often only) pointer to sitemaps.\n// - `Disallow:` paths — a soft signal the owner considers a path uninteresting\n// (we down-weight, never hard-block: Disallow is about crawlers, not value).\n//\n// Delegated to the maintained `robots-parser` lib for spec edge cases; we keep a\n// thin, tested adapter returning the small shape the rest of the code wants.\n\nimport robotsParser from 'robots-parser'\n\nexport type RobotsInfo = {\n /** Absolute sitemap URLs declared via `Sitemap:` (deduped, order preserved). */\n sitemaps: string[]\n /** Disallowed path prefixes for `*` (deduped). */\n disallow: string[]\n}\n\n/**\n * @param text raw robots.txt body\n * @param robotsUrl the URL the robots.txt was fetched from (lib needs it to\n * resolve relative sitemap refs); defaults to a dummy origin.\n */\nexport function parseRobots(text: string, robotsUrl = 'https://example.com/robots.txt'): RobotsInfo {\n const parser = robotsParser(robotsUrl, text)\n\n const sitemaps = dedupe(parser.getSitemaps())\n\n // robots-parser doesn't expose raw Disallow lines, so pull them from the text\n // for the soft down-weight signal. Tolerant of case / inline comments / BOM.\n const disallow: string[] = []\n const seen = new Set<string>()\n for (const rawLine of text.split(/\\r?\\n/)) {\n const line = rawLine.replace(/^/, '').trim()\n if (!line || line.startsWith('#')) continue\n const colon = line.indexOf(':')\n if (colon === -1) continue\n if (line.slice(0, colon).trim().toLowerCase() !== 'disallow') continue\n let value = line.slice(colon + 1).trim()\n const hash = value.indexOf('#')\n if (hash !== -1) value = value.slice(0, hash).trim()\n if (value && !seen.has(value)) {\n seen.add(value)\n disallow.push(value)\n }\n }\n\n return { sitemaps, disallow }\n}\n\nfunction dedupe(arr: string[]): string[] {\n const seen = new Set<string>()\n const out: string[] = []\n for (const v of arr) {\n if (!seen.has(v)) {\n seen.add(v)\n out.push(v)\n }\n }\n return out\n}\n","// ── sitemap.xml parsing (wraps `fast-xml-parser`) ──\n//\n// Handles both sitemap *index* files (<sitemapindex> → child <sitemap><loc>)\n// and url sets (<urlset> → <url><loc>+<lastmod>/<priority>/<changefreq>). The\n// caller decides recursion (whether to fetch children) — this is a single-doc\n// parse. We use `fetchText` + this parser rather than a fetch-and-walk library\n// (sitemapper) so all network goes through our SSRF-guarded fetch + http-cache.\n//\n// fast-xml-parser is tolerant and pure-JS; we normalize its\n// single-vs-array quirk (one <url> → object, many → array) ourselves.\n\nimport { XMLParser } from 'fast-xml-parser'\n\nexport type SitemapKind = 'index' | 'urlset' | 'unknown'\n\nexport type SitemapEntry = {\n loc: string\n lastmod?: string\n priority?: number\n changefreq?: string\n}\n\nexport type ParsedSitemap = {\n kind: SitemapKind\n /** Child sitemap URLs when kind === 'index'. */\n sitemaps: string[]\n /** Page entries when kind === 'urlset'. */\n urls: SitemapEntry[]\n}\n\nconst parser = new XMLParser({\n ignoreAttributes: true,\n trimValues: true,\n // <loc> values are returned as strings; numbers stay parseable by us.\n parseTagValue: false,\n})\n\nfunction asArray<T>(v: T | T[] | undefined): T[] {\n if (v == null) return []\n return Array.isArray(v) ? v : [v]\n}\n\nfunction text(v: unknown): string | undefined {\n if (v == null) return undefined\n const s = String(v).trim()\n return s || undefined\n}\n\nexport function parseSitemap(xml: string): ParsedSitemap {\n let doc: any\n try {\n doc = parser.parse(xml)\n } catch {\n return { kind: 'unknown', sitemaps: [], urls: [] }\n }\n\n // Index: <sitemapindex><sitemap><loc>…\n if (doc?.sitemapindex) {\n const seen = new Set<string>()\n const sitemaps: string[] = []\n for (const sm of asArray(doc.sitemapindex.sitemap)) {\n const loc = text(sm?.loc)\n if (loc && !seen.has(loc)) {\n seen.add(loc)\n sitemaps.push(loc)\n }\n }\n return { kind: 'index', sitemaps, urls: [] }\n }\n\n // Urlset: <urlset><url><loc>…\n if (doc?.urlset) {\n const seen = new Set<string>()\n const urls: SitemapEntry[] = []\n for (const u of asArray(doc.urlset.url)) {\n const loc = text(u?.loc)\n if (!loc || seen.has(loc)) continue\n seen.add(loc)\n const lastmod = text(u?.lastmod)\n const changefreq = text(u?.changefreq)?.toLowerCase()\n const priorityRaw = text(u?.priority)\n const priority = priorityRaw != null ? Number(priorityRaw) : undefined\n urls.push({\n loc,\n ...(lastmod ? { lastmod } : {}),\n ...(priority != null && Number.isFinite(priority) ? { priority } : {}),\n ...(changefreq ? { changefreq } : {}),\n })\n }\n return { kind: 'urlset', sitemaps: [], urls }\n }\n\n return { kind: 'unknown', sitemaps: [], urls: [] }\n}\n","// ── llms.txt parser (pure) ──\n//\n// llms.txt (https://llmstxt.org) is a Markdown file at the site root that\n// curates the URLs an LLM should read — a *human-picked* relevance signal, the\n// highest-trust source we have. Structure: H1 title, optional blockquote\n// summary, then sections of Markdown link lists:\n//\n// # Title\n// > summary\n// ## Docs\n// - [Quickstart](https://x.com/docs/quickstart): get started\n// - [API](https://x.com/docs/api)\n//\n// We extract every Markdown link's URL + its link text (title). We ignore prose\n// and only keep absolute http(s) links. `llms-full.txt` is the same shape with\n// inlined content — we still only want its links here.\n\nexport type LlmsLink = {\n url: string\n title?: string\n}\n\nconst MD_LINK = /\\[([^\\]]*)\\]\\((https?:\\/\\/[^)\\s]+)\\)/g\n\nexport function parseLlms(text: string): LlmsLink[] {\n const out: LlmsLink[] = []\n const seen = new Set<string>()\n for (const m of text.matchAll(MD_LINK)) {\n const title = m[1]?.trim() || undefined\n const url = m[2]!.trim()\n if (seen.has(url)) continue\n seen.add(url)\n out.push({ url, ...(title ? { title } : {}) })\n }\n return out\n}\n","// ── URL normalization + content filtering (pure) ──\n//\n// Turns a raw discovered URL into a canonical key, decides whether it's a KB-\n// worthy *page* at all, and detects locale duplicates. All pure / unit-tested.\n//\n// Content policy (user requirement): keep only HTML pages, Markdown, and PDF.\n// Everything else — .xml/.rss, images, video, audio, archives, fonts, code/data\n// assets — is dropped. \"HTML page\" means: no file extension (clean route) OR an\n// explicit .html/.htm extension. A path ending in a known non-page extension is\n// rejected; an unknown extension is treated as a route (kept) since many sites\n// use dots in slugs.\n\n// Extensions we KEEP (content an LLM KB can use).\nconst KEEP_EXT = new Set(['html', 'htm', 'md', 'markdown', 'mdx', 'pdf'])\n\n// Extensions we explicitly DROP (assets / machine formats). Anything matching\n// here is never a page even if it slipped into a sitemap.\nconst DROP_EXT = new Set([\n // machine / feeds\n 'xml',\n 'rss',\n 'atom',\n 'json',\n 'csv',\n 'txt',\n 'yaml',\n 'yml',\n // images\n 'jpg',\n 'jpeg',\n 'png',\n 'gif',\n 'webp',\n 'svg',\n 'ico',\n 'bmp',\n 'tiff',\n 'avif',\n // video / audio\n 'mp4',\n 'webm',\n 'mov',\n 'avi',\n 'mkv',\n 'mp3',\n 'wav',\n 'ogg',\n 'm4a',\n // archives / binaries\n 'zip',\n 'gz',\n 'tar',\n 'rar',\n '7z',\n 'dmg',\n 'exe',\n 'apk',\n 'pkg',\n // fonts / code / data\n 'woff',\n 'woff2',\n 'ttf',\n 'otf',\n 'eot',\n 'js',\n 'mjs',\n 'css',\n 'map',\n 'wasm',\n // office (non-pdf) — usually not worth scraping for a support KB\n 'doc',\n 'docx',\n 'xls',\n 'xlsx',\n 'ppt',\n 'pptx',\n])\n\n/** Locale path segments we treat as translations (keep one, prefer English). */\nconst LOCALE_SEG =\n /^(?:[a-z]{2})(?:[-_][a-z]{2})?$/i /* en, fr, en-us, zh-cn, pt_br … (filtered against a real list below) */\n\n// A conservative real-language allowlist so we don't mistake a 2-letter content\n// segment (e.g. /ai, /hr for human-resources) for a locale.\nconst KNOWN_LANGS = new Set([\n 'en',\n 'fr',\n 'es',\n 'de',\n 'it',\n 'pt',\n 'nl',\n 'pl',\n 'ru',\n 'ja',\n 'zh',\n 'ko',\n 'ar',\n 'tr',\n 'sv',\n 'da',\n 'fi',\n 'no',\n 'cs',\n 'el',\n 'he',\n 'th',\n 'vi',\n 'id',\n 'uk',\n 'ro',\n 'hu',\n 'ms', // Malay\n 'tl', // Tagalog\n 'tw', // locale-style variant (zh-TW shorthand on some sites)\n 'hi', // Hindi\n 'bn', // Bengali\n 'sk', // Slovak\n 'bg', // Bulgarian\n 'sr',\n 'sl',\n 'et',\n 'lv',\n 'lt',\n 'fa',\n 'ur',\n 'ta',\n // NOTE: deliberately NOT adding 'hr' — too often means human-resources as a\n // content section, not Croatian. Erring toward keeping content over dropping it.\n])\n\nfunction extOf(pathname: string): string | null {\n const last = pathname.split('/').pop() ?? ''\n const dot = last.lastIndexOf('.')\n if (dot <= 0) return null\n return last.slice(dot + 1).toLowerCase()\n}\n\n/** Canonical form: lowercase host, strip default ports, drop hash, strip a\n * trailing slash, drop common tracking query params, keep meaningful query. */\nexport function normalizeUrl(raw: string): string | null {\n let u: URL\n try {\n u = new URL(raw)\n } catch {\n try {\n u = new URL(`https://${raw}`)\n } catch {\n return null\n }\n }\n if (u.protocol !== 'http:' && u.protocol !== 'https:') return null\n u.hash = ''\n u.hostname = u.hostname.toLowerCase()\n\n // drop tracking params, keep the rest (sorted for stable keys)\n const TRACKING = /^(utm_|gclid|fbclid|mc_|ref$|ref_|_hs|igshid|si$)/i\n const params = [...u.searchParams.entries()].filter(([k]) => !TRACKING.test(k))\n params.sort(([a], [b]) => a.localeCompare(b))\n u.search = ''\n for (const [k, v] of params) u.searchParams.append(k, v)\n\n // strip a trailing slash on the PATH only (keep the root \"/\"), then rebuild.\n if (u.pathname.length > 1 && u.pathname.endsWith('/')) {\n u.pathname = u.pathname.replace(/\\/+$/, '')\n }\n return u.toString()\n}\n\n/** True when the URL points at content we can use as KB (HTML / md / pdf). */\n// Framework route TEMPLATES that leak from SPA sitemaps / discovery — not real\n// pages: Next.js `[slug]`/`[...all]`, Express `:id`, Angular/other `{param}`,\n// and a bare `*` segment. These are placeholders, never fetchable content.\nconst ROUTE_TEMPLATE = /(\\[[^\\]]*\\]|\\{[^}]*\\}|\\/:[^/]+|\\/\\*(?:\\/|$))/\n\nexport function isRouteTemplate(url: string): boolean {\n try {\n return ROUTE_TEMPLATE.test(decodeURIComponent(new URL(url).pathname))\n } catch {\n return ROUTE_TEMPLATE.test(url)\n }\n}\n\nexport function isAllowedContent(url: string): boolean {\n let pathname: string\n try {\n pathname = new URL(url).pathname\n } catch {\n return false\n }\n // Drop framework route templates (/blog/[slug], /users/:id, /docs/{version}).\n if (isRouteTemplate(url)) return false\n const ext = extOf(pathname)\n if (ext == null) return true // clean route, no extension → HTML page\n if (KEEP_EXT.has(ext)) return true\n if (DROP_EXT.has(ext)) return false\n // unknown extension → likely a slug with a dot, keep it\n return true\n}\n\n/** The first meaningful (non-locale) path segment — the diversity bucket. */\nexport function sectionOf(url: string): string {\n let segs: string[]\n try {\n segs = new URL(url).pathname.split('/').filter(Boolean)\n } catch {\n return ''\n }\n for (const s of segs) {\n if (isLocaleSegment(s)) continue\n return s.toLowerCase()\n }\n return '' // homepage / locale-root\n}\n\nexport function isLocaleSegment(seg: string): boolean {\n if (!LOCALE_SEG.test(seg)) return false\n const lang = seg.slice(0, 2).toLowerCase()\n return KNOWN_LANGS.has(lang)\n}\n\n/** Strip the leading locale segment(s) so translations collapse to one key. */\nexport function localeAgnosticKey(url: string): string {\n try {\n const u = new URL(url)\n const segs = u.pathname.split('/').filter(Boolean)\n while (segs.length && isLocaleSegment(segs[0]!)) segs.shift()\n return `${u.hostname}/${segs.join('/')}`\n } catch {\n return url\n }\n}\n\n/** Is this URL an English (or locale-less) variant? Preferred when dedup'ing. */\nexport function isEnglishOrNeutral(url: string): boolean {\n try {\n const segs = new URL(url).pathname.split('/').filter(Boolean)\n const first = segs[0]\n if (!first || !isLocaleSegment(first)) return true\n return first.slice(0, 2).toLowerCase() === 'en'\n } catch {\n return true\n }\n}\n\n/** The Markdown-twin URL for a page, e.g. /docs/x → /docs/x.md (the `.html.md`\n * / `.md` convention). Used to PROBE whether a markdown version exists. */\nexport function markdownTwinUrls(url: string): string[] {\n try {\n const u = new URL(url)\n const p = u.pathname.replace(/\\/$/, '')\n const ext = extOf(p)\n const candidates: string[] = []\n if (ext === 'html' || ext === 'htm') {\n candidates.push(p.replace(/\\.html?$/, '.md'))\n } else if (ext == null) {\n candidates.push(p + '.md', p + '.html.md')\n }\n return [...new Set(candidates)].map((path) => `${u.origin}${path}`)\n } catch {\n return []\n }\n}\n","// ── Site-type detection + exclude patterns ──\n//\n// Detect platform-specific sites that need special URL handling. First (and so\n// far only) detector: SHOPIFY, detected the reliable way — hit the well-known\n// `/products.json` endpoint and confirm a Shopify-shaped payload. Only a real\n// Shopify storefront serves that shape. No URL-pattern guessing.\n//\n// When Shopify is confirmed we exclude the per-SKU `/products/<handle>` pages\n// (thousands of them, near-worthless for a *support* KB) and keep collections,\n// pages, policies, FAQ. Architected so more detectors (WooCommerce, Zendesk\n// help-centers, …) can be added later.\n\nimport type { DetectedSiteType } from '../types'\nimport { fetchJson, hostOf } from '../web-fetch'\n\n// Glob → matcher. `*` is permissive and CAN cross path segments (matching the\n// browser integration's glob semantics), matched against the URL pathname.\nexport function matchesGlob(url: string, glob: string): boolean {\n let path: string\n try {\n path = new URL(url).pathname\n } catch {\n return false\n }\n const re = new RegExp(\n '^' +\n glob\n .split('*')\n .map((s) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('.*') +\n '$'\n )\n return re.test(path)\n}\n\n/** The Shopify products.json shape: { products: [{ id, handle, variants, … }] }. */\nexport function isShopifyProductsPayload(payload: unknown): boolean {\n if (!payload || typeof payload !== 'object') return false\n const products = (payload as { products?: unknown }).products\n if (!Array.isArray(products)) return false\n if (products.length === 0) return true // empty catalog still answers in-shape\n const first = products[0] as Record<string, unknown>\n // Shopify products always carry a numeric/string id, a string handle, and a\n // variants array — a combination generic JSON endpoints don't share.\n return (\n first != null &&\n typeof first === 'object' &&\n 'handle' in first &&\n typeof first.handle === 'string' &&\n 'variants' in first &&\n Array.isArray((first as { variants?: unknown }).variants)\n )\n}\n\nconst SHOPIFY_EXCLUDES = [\n '/products/*', // individual SKU pages\n '*/products/*', // products nested under a collection\n '/cart*',\n '/account*',\n '/checkout*',\n]\nconst SHOPIFY_INCLUDE_HINTS = ['/collections', '/pages', '/policies', '/pages/faq', '/pages/contact']\n\n/**\n * Probe `/products.json` on the site origin and classify. `baseHost` is the\n * SSRF guard anchor. Returns `unknown` when the probe is absent/non-Shopify.\n * The probe call is the caller's to cache (via http-cache) when recording.\n */\nexport async function detectSiteType(\n siteUrl: string,\n baseHost: string,\n fetchJsonFn: typeof fetchJson = fetchJson\n): Promise<DetectedSiteType> {\n const origin = originOf(siteUrl)\n if (!origin) return unknownType()\n\n const payload = await fetchJsonFn(`${origin}/products.json?limit=1`, baseHost)\n if (isShopifyProductsPayload(payload)) {\n return { type: 'shopify', excludePatterns: [...SHOPIFY_EXCLUDES], includeHints: [...SHOPIFY_INCLUDE_HINTS] }\n }\n return unknownType()\n}\n\n/** Pure classification from an already-fetched products.json payload (tests). */\nexport function classifyFromProductsJson(payload: unknown): DetectedSiteType {\n if (isShopifyProductsPayload(payload)) {\n return { type: 'shopify', excludePatterns: [...SHOPIFY_EXCLUDES], includeHints: [...SHOPIFY_INCLUDE_HINTS] }\n }\n return unknownType()\n}\n\nfunction unknownType(): DetectedSiteType {\n return { type: 'unknown', excludePatterns: [], includeHints: [] }\n}\n\nfunction originOf(url: string): string | null {\n try {\n return new URL(url).origin\n } catch {\n try {\n return new URL(`https://${url}`).origin\n } catch {\n return null\n }\n }\n}\n\n/** Apply exclude patterns to a URL list (used after discovery). */\nexport function applyExcludes(urls: string[], patterns: string[]): string[] {\n if (patterns.length === 0) return urls\n return urls.filter((u) => !patterns.some((p) => matchesGlob(u, p)))\n}\n\n/** Same-registrable-domain helper for detectors that need it. */\nexport function sameSite(a: string, b: string): boolean {\n const ha = hostOf(a)\n const hb = hostOf(b)\n if (!ha || !hb) return false\n return ha.split('.').slice(-2).join('.') === hb.split('.').slice(-2).join('.')\n}\n","// ── Deterministic URL scorer (pure) ──\n//\n// Score a candidate purely from its URL string + sitemap metadata + which\n// sources saw it. No I/O, no LLM — fully unit-testable, and the layer the LLM\n// pass only *adjusts*, never replaces. Higher score = more useful as support KB.\n//\n// Every contribution pushes a string into `reasons[]` so tests (and debugging)\n// assert WHY a URL ranked where it did, not just a magic number.\n\nimport type { UrlMeta, ScoredUrl, ScoreWeights } from '../types'\nimport { sectionOf } from './normalize'\n\n// Section-value tiers (user-approved). Keys are matched against the first\n// meaningful path segment AND as a substring of the path for robustness.\nconst TOP = [\n 'docs',\n 'doc',\n 'documentation',\n 'help',\n 'support',\n 'faq',\n 'faqs',\n 'guide',\n 'guides',\n 'kb',\n 'knowledge-base',\n 'knowledgebase',\n 'pricing',\n 'plans',\n 'integrations',\n 'integration',\n 'api',\n 'apis',\n 'features',\n 'feature',\n 'getting-started',\n 'get-started',\n 'how-to',\n 'howto',\n 'tutorial',\n 'tutorials',\n 'contact',\n 'contact-us',\n]\nconst MID = [\n 'product',\n 'products',\n 'solutions',\n 'solution',\n 'use-cases',\n 'use-case',\n 'about',\n 'about-us',\n 'security',\n 'company',\n 'platform',\n 'services',\n 'service',\n]\nconst LOW = [\n 'blog',\n 'news',\n 'press',\n 'newsroom',\n 'events',\n 'event',\n 'case-studies',\n 'case-study',\n 'customers',\n 'webinar',\n 'webinars',\n 'resources',\n 'resource',\n 'library',\n 'podcast',\n 'stories',\n]\nconst DROP = [\n 'tag',\n 'tags',\n 'author',\n 'authors',\n 'category',\n 'categories',\n 'archive',\n 'archives',\n 'login',\n 'signin',\n 'sign-in',\n 'account',\n 'cart',\n 'checkout',\n 'register',\n 'signup',\n 'sign-up',\n 'wishlist',\n 'search',\n]\n\nfunction tierWeights(): Record<string, number> {\n const m: Record<string, number> = {}\n for (const s of TOP) m[s] = 10\n for (const s of MID) m[s] = 5\n for (const s of LOW) m[s] = 1.5\n for (const s of DROP) m[s] = -8\n return m\n}\n\nexport const DEFAULT_WEIGHTS: ScoreWeights = {\n sectionWeights: tierWeights(),\n defaultSectionWeight: 3, // unknown section: neutral-positive (homepage-ish)\n depthPenalty: 0.6,\n depthWaivedSections: ['docs', 'doc', 'documentation', 'help', 'support', 'kb', 'guide', 'guides', 'api'],\n recencyBonus: 3,\n priorityBonus: 2,\n multiSourceBonus: 1.5,\n llmsBonus: 8,\n queryParamPenalty: 1.5,\n}\n\n// Dated path like /2019/03/ or /2021-08-12/ — stale, low value.\nconst DATED_PATH = /\\/(?:19|20)\\d{2}(?:[/-](?:0?[1-9]|1[0-2]))?(?:[/-]\\d{1,2})?(?=\\/|$)/\n\n// Sitemap-name keywords that signal which discoverUrls fan-out is worth it.\n// Matched as substrings of the WHOLE sitemap URL (names like\n// \"sitemap-us-help.xml\", \"sitemap-v2-pdp-index.xml\" aren't path segments).\nconst SITEMAP_BOOST = ['help', 'support', 'docs', 'doc', 'kb', 'faq', 'guide', 'api', 'pricing', 'product-doc']\nconst SITEMAP_PENALTY = ['pdp', 'product', 'gridwall', 'snkrs', 'locator', 'article', 'blog', 'news', 'image', 'video']\n\n/**\n * Score a SITEMAP URL to decide fan-out priority. Boost help/docs-named\n * sitemaps; penalize product/blog/asset-named ones. The bare /sitemap.xml is\n * neutral-positive (it's the canonical root). Pure.\n */\nexport function scoreSitemapUrl(sitemapUrl: string): number {\n const lower = sitemapUrl.toLowerCase()\n let score = 1 // baseline: a real sitemap is worth fetching\n for (const k of SITEMAP_BOOST) if (lower.includes(k)) score += 6\n for (const k of SITEMAP_PENALTY) if (lower.includes(k)) score -= 5\n // a plain \"/sitemap.xml\" or \"/sitemap-index.xml\" with no keyword stays baseline+\n if (/\\/sitemap(-index)?\\.xml$/.test(lower)) score += 2\n return score\n}\n\nfunction pathSegments(url: string): string[] {\n try {\n return new URL(url).pathname.split('/').filter(Boolean)\n } catch {\n return []\n }\n}\n\n// Subdomain TOKENS that signal the real support/docs content. Matched against\n// the leftmost host label split on [-_.], so product-prefixed support subdomains\n// count too: x-series-support, k-series-support, resto-support, api-docs.\nconst HIGH_VALUE_SUB_TOKENS = new Set([\n 'help',\n 'support',\n 'docs',\n 'doc',\n 'kb',\n 'faq',\n 'knowledgebase',\n 'developer',\n 'developers',\n])\n\n/** The matched high-value token when the leading subdomain label contains one\n * (help/support/docs/…), else null. e.g. help.x.com → 'help',\n * x-series-support.x.com → 'support', api-docs.x.com → 'docs', www.x.com → null. */\nexport function highValueSubdomain(url: string): string | null {\n let host: string\n try {\n host = new URL(url).hostname.toLowerCase()\n } catch {\n return null\n }\n const label = host.split('.')[0]\n if (!label) return null\n for (const tok of label.split(/[-_]/)) {\n if (HIGH_VALUE_SUB_TOKENS.has(tok)) return tok\n }\n return null\n}\n\nfunction recencyScore(lastmod: string | undefined, now: number, bonus: number): number {\n if (!lastmod) return 0\n const t = Date.parse(lastmod)\n if (!Number.isFinite(t)) return 0\n const years = (now - t) / (365.25 * 24 * 3600 * 1000)\n if (years <= 0) return bonus\n // linear decay to 0 over ~2 years, never negative.\n return Math.max(0, bonus * (1 - years / 2))\n}\n\nexport function scoreUrl(\n url: string,\n meta: UrlMeta = { sources: [] },\n weights: ScoreWeights = DEFAULT_WEIGHTS,\n now: number = Date.parse('2026-06-30')\n): ScoredUrl {\n const reasons: string[] = []\n let score = 0\n\n const section = sectionOf(url)\n const segs = pathSegments(url)\n\n // 1. Section base value. Homepage (no section) gets a modest bonus — it's the\n // canonical entry and usually high-signal.\n if (section === '') {\n score += 6\n reasons.push('homepage (+6)')\n } else if (section in weights.sectionWeights) {\n const w = weights.sectionWeights[section]!\n score += w\n reasons.push(`section:${section} (${w >= 0 ? '+' : ''}${w})`)\n } else {\n score += weights.defaultSectionWeight\n reasons.push(`section:${section} default (+${weights.defaultSectionWeight})`)\n }\n\n // 1b. High-value SUBDOMAIN boost. A site's real support KB often lives on a\n // help./support./docs./kb. subdomain (e.g. help.wealthsimple.com,\n // marketplace.gohighlevel.com/docs) — boost those regardless of path so\n // they aren't crowded out by the main marketing site.\n const sub = highValueSubdomain(url)\n if (sub) {\n score += 8\n reasons.push(`subdomain:${sub} (+8)`)\n }\n\n // 2. A DROP keyword anywhere in the path is a strong negative even if it's not\n // the leading segment (e.g. /docs/login, /blog/author/jane).\n for (const seg of segs) {\n if (DROP.includes(seg.toLowerCase()) && seg.toLowerCase() !== section) {\n score += -4\n reasons.push(`drop-segment:${seg} (-4)`)\n break\n }\n }\n\n // 3. Depth penalty — gentler/waived in deep-content sections (deep docs good).\n const depth = Math.max(0, segs.length - 1)\n if (depth > 0) {\n const waived = weights.depthWaivedSections.includes(section)\n const perLevel = waived ? weights.depthPenalty * 0.25 : weights.depthPenalty\n const pen = -(perLevel * depth)\n if (pen !== 0) {\n score += pen\n reasons.push(`depth:${depth}${waived ? ' (waived section)' : ''} (${pen.toFixed(2)})`)\n }\n }\n\n // 4. Dated path penalty (stale time-bound content).\n try {\n if (DATED_PATH.test(new URL(url).pathname)) {\n score += -3\n reasons.push('dated-path (-3)')\n }\n } catch {\n // unparseable URL — skip the dated-path check (scoreUrl is a public export,\n // so it must not throw on a malformed URL and abort discover's map pass).\n }\n\n // 5. Recency from sitemap <lastmod>.\n const rec = recencyScore(meta.lastmod, now, weights.recencyBonus)\n if (rec > 0) {\n score += rec\n reasons.push(`recency (+${rec.toFixed(2)})`)\n }\n\n // 6. Sitemap <priority>.\n if (typeof meta.priority === 'number') {\n const p = weights.priorityBonus * meta.priority\n score += p\n reasons.push(`priority:${meta.priority} (+${p.toFixed(2)})`)\n }\n\n // 7. Source trust: llms.txt is human-curated → big bonus. Multi-source\n // corroboration → small bonus per extra source.\n if (meta.sources.includes('llms')) {\n score += weights.llmsBonus\n reasons.push(`llms.txt curated (+${weights.llmsBonus})`)\n }\n const extra = meta.sources.length - 1\n if (extra > 0) {\n const b = weights.multiSourceBonus * extra\n score += b\n reasons.push(`multi-source x${meta.sources.length} (+${b.toFixed(2)})`)\n }\n\n // 8. Query-param penalty (faceted / parametric pages are usually dupes).\n try {\n const q = new URL(url).searchParams\n const n = [...q.keys()].length\n if (n > 0) {\n const pen = -(weights.queryParamPenalty * n)\n score += pen\n reasons.push(`query-params:${n} (${pen.toFixed(2)})`)\n }\n } catch {\n // ignore\n }\n\n return { url, score, reasons, section, meta }\n}\n\nexport function scoreAll(items: { url: string; meta: UrlMeta }[], weights?: ScoreWeights, now?: number): ScoredUrl[] {\n return items.map((c) => scoreUrl(c.url, c.meta, weights, now)).sort((a, b) => b.score - a.score)\n}\n","// ── Prioritized tree builder (pure) ──\n//\n// Turns a flat list of scored URLs into the multi-level segment tree that is the\n// contract between discover() and select(). Each node = one path segment; the\n// root is the host. Folders aggregate `count` (# leaf URLs beneath) and `score`\n// (the MAX of their own + descendants, so a folder is as important as its best\n// page — a /docs folder with one killer page ranks high). Children are returned\n// sorted by score desc, so high-value pages (pricing, contact, faq) and folders\n// float to the top at every level. No I/O, fully unit-tested.\n\nimport type { ScoredUrl, TreeNode } from '../types'\nimport { isLocaleSegment } from './normalize'\n\ntype Mutable = TreeNode & { childMap: Map<string, Mutable> }\n\nfunction newNode(segment: string): Mutable {\n return { segment, children: [], childMap: new Map(), score: -Infinity, count: 0 }\n}\n\nexport function buildTree(scored: ScoredUrl[]): TreeNode {\n const root = newNode('')\n\n for (const item of scored) {\n let segs: string[]\n try {\n segs = new URL(item.url).pathname.split('/').filter(Boolean)\n } catch {\n continue\n }\n // Strip a leading locale segment (en, en-ca, …) so /en-ca/pricing trees\n // under \"pricing\" — restores section diversity for locale-prefixed sites.\n // The page's actual `url` is unchanged; only its tree position shifts.\n if (segs.length > 1 && isLocaleSegment(segs[0]!)) segs = segs.slice(1)\n\n // Walk/create the path; the final node carries the page (url+meta+score).\n let node = root\n for (const seg of segs) {\n let child = node.childMap.get(seg)\n if (!child) {\n child = newNode(seg)\n node.childMap.set(seg, child)\n node.children.push(child)\n }\n node = child\n }\n\n // Attach the page to its node. If two URLs normalize to the same node\n // (rare — trailing slash etc.), keep the higher score.\n if (node.url == null || item.score > node.score) {\n node.url = item.url\n node.meta = item.meta\n node.reasons = item.reasons\n }\n if (item.score > node.score) node.score = item.score\n }\n\n // Post-order: aggregate count (leaf pages) and propagate max score upward.\n finalize(root)\n return strip(root)\n}\n\nfunction finalize(node: Mutable): void {\n let count = node.url ? 1 : 0\n let maxScore = node.url ? node.score : -Infinity\n for (const child of node.children) {\n finalize(child as Mutable)\n count += child.count\n if (child.score > maxScore) maxScore = child.score\n }\n node.count = count\n // A folder with no page of its own still gets its best descendant's score.\n if (!Number.isFinite(node.score) || maxScore > node.score) node.score = maxScore\n if (!Number.isFinite(node.score)) node.score = 0\n // sort children by score desc, tie-break alphabetically for determinism\n node.children.sort((a, b) => b.score - a.score || a.segment.localeCompare(b.segment))\n}\n\n/** Drop the internal childMap so the returned tree is a clean TreeNode. */\nfunction strip(node: Mutable): TreeNode {\n return {\n segment: node.segment,\n ...(node.url ? { url: node.url } : {}),\n ...(node.meta ? { meta: node.meta } : {}),\n ...(node.reasons ? { reasons: node.reasons } : {}),\n score: node.score,\n count: node.count,\n children: node.children.map((c) => strip(c as Mutable)),\n }\n}\n\n/** Depth-first iterator over every node that carries a real page URL. */\nexport function* leafUrls(node: TreeNode): Generator<TreeNode> {\n if (node.url) yield node\n for (const c of node.children) yield* leafUrls(c)\n}\n\n/** Collect all page URLs in priority order (pre-order, children already sorted). */\nexport function flattenByPriority(root: TreeNode): TreeNode[] {\n const out: TreeNode[] = []\n const visit = (n: TreeNode) => {\n if (n.url) out.push(n)\n for (const c of n.children) visit(c)\n }\n visit(root)\n // pre-order follows the sorted-children order, but a deep high-score page can\n // sit under a low-score ancestor; do a final sort by score desc. Tie-break by\n // URL so the order is TOTAL (deterministic) even when many pages — e.g. dozens\n // of equal-score subdomain homepages — share a score.\n return out.sort((a, b) => b.score - a.score || (a.url ?? '').localeCompare(b.url ?? ''))\n}\n","// ── Discovery (unit #1) ──\n//\n// discover(website, opts) -> MasterMap. PURELY MECHANICAL (network + heuristic\n// prioritization); the only LLM touch is an optional search-query formulation\n// when robots/sitemaps come up empty. All I/O is injected (`deps`) so the unit\n// runs offline against fixtures.\n//\n// Sources, fanned out under a wall-clock budget + AbortSignal:\n// 1. browser.discoverUrls(domain) — Firecrawl map, bulk recall.\n// 2. robots.txt -> sitemap URLs; SCORE each sitemap; fan out discoverUrls to\n// the top few high-value sitemaps (each gets its own 1000) — this is how\n// docs/help subtrees get covered (botpress /docs, nike sitemap-us-help).\n// 3. root sitemap(s) parsed for lastmod/priority metadata (joined onto URLs).\n// 4. llms.txt / llms-full.txt — curated, highest trust.\n// 5. site: webSearch — finds UNLINKED help/docs subdomains; results can seed\n// a follow-up discoverUrls call (search-then-rediscover).\n// Re-discovers up to 3 rounds until \"enough\" URLs are found.\n//\n// Returns the prioritized TREE plus the steps taken; on abort/timeout returns\n// whatever was gathered (aborted: true).\n\nimport type { DiscoverOptions, DiscoveryStep, DiscoverySource, MasterMap, UrlMeta } from '../types'\nimport { parseRobots } from './parse-robots'\nimport { parseSitemap } from './parse-sitemap'\nimport { parseLlms } from './parse-llms'\nimport { normalizeUrl, isAllowedContent, isLocaleSegment, isEnglishOrNeutral, localeAgnosticKey } from './normalize'\nimport { detectSiteType, applyExcludes } from './detect-site-type'\nimport { scoreUrl, scoreSitemapUrl } from './score'\nimport { buildTree } from './build-tree'\nimport { fetchText, fetchJson, hostOf, siteOrigin } from '../web-fetch'\n\nexport type SearchResult = { name: string; url: string; snippet?: string }\n\nexport type DiscoverDeps = {\n fetchText: typeof fetchText\n fetchJson: typeof fetchJson\n discoverUrls: (args: { url: string; count: number; onlyHttps: boolean }) => Promise<{\n urls: string[]\n stopReason: string\n }>\n webSearch: (args: { query: string; includeSites?: string[]; count: number }) => Promise<{\n results: SearchResult[]\n }>\n}\n\nconst DEFAULTS = {\n budgetMs: 12_000,\n maxUrls: 100_000,\n maxSitemapFanout: 4, // extra discoverUrls calls on high-value sitemaps\n maxChildSitemaps: 12, // child sitemaps to parse for metadata\n enoughUrls: 200, // re-discover until at least this many (or rounds exhausted)\n maxRounds: 3,\n}\n\nexport async function discover(website: string, opts: DiscoverOptions = {}, deps: DiscoverDeps): Promise<MasterMap> {\n const d = deps\n const host = hostOf(website)\n if (!host) {\n return emptyMap('unknown')\n }\n const origin = siteOrigin(website) ?? `https://${host}`\n const cfg = { ...DEFAULTS, ...stripUndefined(opts) }\n\n const deadline = Date.now() + cfg.budgetMs\n const aborted = () => opts.signal?.aborted === true || Date.now() >= deadline\n const remaining = () => Math.max(500, deadline - Date.now())\n\n const steps: DiscoveryStep[] = []\n const log = (s: DiscoveryStep) => {\n steps.push(s)\n opts.onActivity?.(s)\n }\n\n // merge bucket: normalized URL -> metadata. Off-domain + locale filtering is\n // applied POST-merge (we can't know the canonical domain — which may differ\n // from the input, e.g. lightspeedpos.com → lightspeedhq.com — until results\n // are in).\n const byUrl = new Map<string, UrlMeta>()\n const add = (rawUrl: string, source: DiscoverySource, extra: Partial<UrlMeta> = {}) => {\n if (byUrl.size >= cfg.maxUrls) return\n const norm = normalizeUrl(rawUrl)\n if (!norm || hostOf(norm) == null) return\n if (isNonEnglishLocalePath(norm)) return\n const m = byUrl.get(norm)\n if (m) {\n if (!m.sources.includes(source)) m.sources.push(source)\n if (extra.lastmod && !m.lastmod) m.lastmod = extra.lastmod\n if (extra.priority != null && m.priority == null) m.priority = extra.priority\n if (extra.changefreq && !m.changefreq) m.changefreq = extra.changefreq\n if (extra.title && !m.title) m.title = extra.title\n if (extra.searchRank != null && m.searchRank == null) m.searchRank = extra.searchRank\n } else {\n byUrl.set(norm, { sources: [source], ...extra })\n }\n }\n\n // ── robots.txt → sitemap URLs (drives the fan-out + metadata) ──\n let sitemapUrls: string[] = []\n if (!aborted()) {\n const robotsTxt = await d.fetchText(`${origin}/robots.txt`, host, remaining()).catch(() => null)\n const robots = robotsTxt ? parseRobots(robotsTxt, `${origin}/robots.txt`) : { sitemaps: [], disallow: [] }\n sitemapUrls = robots.sitemaps.length ? robots.sitemaps : [`${origin}/sitemap.xml`]\n log({\n kind: 'robots',\n detail: robotsTxt ? `${robots.sitemaps.length} sitemap(s) declared` : 'no robots.txt',\n count: robots.sitemaps.length,\n })\n }\n\n // ── choose which sitemaps to fan out discoverUrls to (score by URL) ──\n const rankedSitemaps = rankSitemaps(sitemapUrls)\n const fanoutTargets = rankedSitemaps.slice(0, cfg.maxSitemapFanout)\n\n // ── parallel discovery branches ──\n const branches: Promise<unknown>[] = []\n\n // 1. discoverUrls on the bare domain (bulk).\n branches.push(\n discoverUrlsWithRetry(d, host)\n .then((r) => {\n r.urls.forEach((u) => add(u, 'discoverUrls'))\n log({ kind: 'discoverUrls', detail: `domain ${host}`, count: r.urls.length })\n })\n .catch(() => log({ kind: 'discoverUrls', detail: `domain ${host} failed`, count: 0 }))\n )\n\n // 1b. discoverUrls fan-out on high-value sitemaps (each its own 1000).\n for (const sm of fanoutTargets) {\n if (aborted()) break\n branches.push(\n d\n .discoverUrls({ url: sm, count: 1000, onlyHttps: true })\n .then((r) => {\n r.urls.forEach((u) => add(u, 'discoverUrls'))\n log({ kind: 'discoverUrls', detail: `sitemap ${sm}`, count: r.urls.length })\n })\n .catch(() => log({ kind: 'discoverUrls', detail: `sitemap ${sm} failed`, count: 0 }))\n )\n }\n\n // 2. parse root sitemaps for lastmod/priority metadata (bounded).\n branches.push(\n enrichFromSitemaps(rankedSitemaps.slice(0, 5), host, d, add, log, {\n maxChildren: cfg.maxChildSitemaps,\n remaining,\n aborted,\n })\n )\n\n // 3. llms.txt / llms-full.txt.\n branches.push(\n Promise.all([\n d.fetchText(`${origin}/llms.txt`, host, remaining()).catch(() => null),\n d.fetchText(`${origin}/llms-full.txt`, host, remaining()).catch(() => null),\n ]).then(([a, b]) => {\n let n = 0\n for (const text of [a, b]) {\n if (!text) continue\n for (const link of parseLlms(text)) {\n add(link.url, 'llms', link.title ? { title: link.title } : {})\n n++\n }\n }\n if (n) log({ kind: 'llms', detail: 'llms.txt curated links', count: n })\n })\n )\n\n // 4. site: search — find unlinked help/docs subdomains.\n const queries = buildSearchQueries(host, opts.topics ?? [])\n branches.push(\n Promise.all(\n queries.map((query) =>\n d\n .webSearch({ query, includeSites: [host], count: 15 })\n .then((r) => {\n r.results.forEach((res, i) => add(res.url, 'search', { searchRank: i + 1 }))\n log({ kind: 'search', detail: query, count: r.results.length })\n return r.results\n })\n .catch(() => [] as SearchResult[])\n )\n )\n )\n\n // 5. site-type probe (Shopify /products.json).\n const siteTypeP = detectSiteType(origin, host, d.fetchJson)\n .then((st) => {\n if (st.type !== 'unknown') log({ kind: 'site-type', detail: st.type })\n return st\n })\n .catch(() => ({ type: 'unknown' as const, excludePatterns: [] as string[], includeHints: [] as string[] }))\n\n await Promise.allSettled(branches)\n const siteType = await siteTypeP\n\n // ── re-discover rounds: if too few URLs, search wider + re-run discoverUrls\n // against any help/docs subdomain the search surfaced. ──\n let round = 1\n while (byUrl.size < cfg.enoughUrls && round < cfg.maxRounds && !aborted()) {\n round++\n const subdomainTargets = pickRediscoverTargets(byUrl, host)\n if (subdomainTargets.length === 0) break\n log({ kind: 'note', detail: `re-discover round ${round}: ${subdomainTargets.length} target(s)` })\n await Promise.allSettled(\n subdomainTargets.map((t) =>\n d\n .discoverUrls({ url: t, count: 1000, onlyHttps: true })\n .then((r) => {\n r.urls.forEach((u) => add(u, 'discoverUrls'))\n log({ kind: 'discoverUrls', detail: `re-discover ${t}`, count: r.urls.length })\n })\n .catch(() => {})\n )\n )\n }\n\n // ── post-merge: same-site filter ──\n // Keep only the target's registrable domain(s) + their subdomains, dropping\n // off-domain noise (ucp.dev, shop.app, a partner's site, newsroom on a vanity\n // domain). The canonical domain can differ from the input (lightspeedpos.com →\n // lightspeedhq.com), so we ADOPT the dominant registrable domain among results\n // plus the input domain.\n const allowedDomains = computeAllowedDomains(byUrl, host)\n log({ kind: 'note', detail: `same-site domains: ${[...allowedDomains].join(', ')}` })\n\n // ── post-merge: content filter + site-type excludes ──\n let entries = [...byUrl.entries()].filter(([u]) => {\n const h = hostOf(u)\n return h != null && allowedDomains.has(registrableDomain(h)) && isAllowedContent(u)\n })\n if (siteType.excludePatterns.length) {\n const before = entries.length\n const keep = new Set(\n applyExcludes(\n entries.map(([u]) => u),\n siteType.excludePatterns\n )\n )\n entries = entries.filter(([u]) => keep.has(u))\n log({ kind: 'site-type', detail: `${siteType.type} excludes`, count: before - entries.length })\n }\n\n // ── post-merge: collapse locale duplicates ──\n // The SAME page in different languages (/faq vs /en/faq vs /pt/faq) should be\n // ingested once. Keep the version on the site's DEFAULT locale — the\n // unprefixed path (kontigo's base is Spanish, botpress's is English) — falling\n // back to /en/ then any. Judged per locale-agnostic key.\n {\n const before = entries.length\n entries = dedupeLocales(entries)\n if (before !== entries.length) log({ kind: 'note', detail: 'locale-dedup', count: before - entries.length })\n }\n\n // ── score + build the prioritized tree ──\n const scored = entries.map(([url, meta]) => scoreUrl(url, meta, undefined, opts.now))\n const tree = buildTree(scored)\n\n return {\n tree,\n total: scored.length,\n steps,\n aborted: aborted() && byUrl.size < cfg.enoughUrls,\n siteType: siteType.type,\n }\n}\n\n// ── helpers ──\n\nfunction emptyMap(siteType: 'shopify' | 'unknown'): MasterMap {\n return { tree: { segment: '', children: [], score: 0, count: 0 }, total: 0, steps: [], aborted: false, siteType }\n}\n\nfunction stripUndefined<T extends object>(o: T): Partial<T> {\n return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined)) as Partial<T>\n}\n\n/** Score sitemap URLs the same way we score pages, so help/docs sitemaps fan\n * out first and per-locale/product sitemaps are deprioritized/dropped. */\nexport function rankSitemaps(sitemaps: string[]): string[] {\n return sitemaps\n .filter((u) => {\n const seg = firstSegment(u)\n return !(seg && isLocaleSegment(seg)) // drop non-English locale sitemaps up front\n })\n .map((u) => ({ u, s: scoreSitemapUrl(u) }))\n .sort((a, b) => b.s - a.s)\n .map((x) => x.u)\n}\n\nfunction firstSegment(url: string): string | undefined {\n try {\n return new URL(url).pathname.split('/').filter(Boolean)[0]\n } catch {\n return undefined\n }\n}\n\n/** Registrable-ish domain (last two labels) — coarse same-site key. */\nfunction registrableDomain(host: string): string {\n return host.split('.').slice(-2).join('.')\n}\n\n/**\n * The set of registrable domains we consider \"this site\": the input domain plus\n * the DOMINANT domain among discovered URLs (handles redirects/vanity domains,\n * e.g. lightspeedpos.com → lightspeedhq.com). Off-domain stragglers (a single\n * ucp.dev / shop.app) fall below the dominance bar and are excluded.\n */\nexport function computeAllowedDomains(byUrl: Map<string, UrlMeta>, inputHost: string): Set<string> {\n const counts = new Map<string, number>()\n for (const url of byUrl.keys()) {\n const h = hostOf(url)\n if (!h) continue\n const d = registrableDomain(h)\n counts.set(d, (counts.get(d) ?? 0) + 1)\n }\n const allowed = new Set<string>([registrableDomain(inputHost)])\n // adopt the single most common domain (the canonical site).\n let top: string | null = null\n let topN = 0\n for (const [d, n] of counts) {\n if (n > topN) {\n topN = n\n top = d\n }\n }\n if (top) allowed.add(top)\n return allowed\n}\n\n/** True when the URL's leading path segment is a NON-English locale (e.g. /fr/,\n * /fr-ca/, /jp/). English/neutral paths return false (kept). */\nfunction isNonEnglishLocalePath(url: string): boolean {\n const seg = firstSegment(url)\n if (!seg || !isLocaleSegment(seg)) return false\n return !isEnglishOrNeutral(url)\n}\n\n/** Call the PRIMARY discoverUrls, retrying ONCE if it throws or returns empty —\n * cheap insurance against a transient Firecrawl flake that would otherwise leave\n * us with only sitemap/search results (or nothing). */\nasync function discoverUrlsWithRetry(d: DiscoverDeps, host: string): Promise<{ urls: string[]; stopReason: string }> {\n const call = () => d.discoverUrls({ url: host, count: 1000, onlyHttps: true })\n try {\n const r = await call()\n if (r.urls.length > 0) return r\n } catch {\n // fall through to one retry\n }\n return call().catch(() => ({ urls: [] as string[], stopReason: 'error' }))\n}\n\n/** The leading locale segment of a URL path, or '' when unprefixed (default). */\nfunction localePrefix(url: string): string {\n const seg = firstSegment(url)\n return seg && isLocaleSegment(seg) ? seg.toLowerCase() : ''\n}\n\n/**\n * Collapse locale-equivalent URLs (same localeAgnosticKey) to ONE, preferring\n * the site's default locale: unprefixed path first, then /en/, then anything.\n * Ties keep the richer metadata entry (more sources). Preserves input order.\n */\nfunction dedupeLocales(entries: [string, UrlMeta][]): [string, UrlMeta][] {\n const best = new Map<string, [string, UrlMeta]>()\n const rank = (url: string) => {\n const p = localePrefix(url)\n if (p === '') return 0 // unprefixed = site default → best\n if (p.slice(0, 2) === 'en') return 1 // English fallback\n return 2\n }\n for (const entry of entries) {\n const key = localeAgnosticKey(entry[0])\n const cur = best.get(key)\n if (!cur) {\n best.set(key, entry)\n continue\n }\n const better =\n rank(entry[0]) < rank(cur[0]) ||\n (rank(entry[0]) === rank(cur[0]) && entry[1].sources.length > cur[1].sources.length)\n if (better) best.set(key, entry)\n }\n // return in a stable order (first-seen key order)\n const seen = new Set<string>()\n const out: [string, UrlMeta][] = []\n for (const entry of entries) {\n const key = localeAgnosticKey(entry[0])\n if (seen.has(key)) continue\n seen.add(key)\n out.push(best.get(key)!)\n }\n return out\n}\n\nfunction buildSearchQueries(host: string, topics: string[]): string[] {\n const queries = [`${host} help support documentation`]\n const topic = topics.find((t) => t.trim().length > 0)\n if (topic) queries.push(`${host} ${topic.slice(0, 40)}`)\n return queries\n}\n\n/** From the discovered URLs, pick help/docs SUBDOMAIN roots worth a dedicated\n * discoverUrls call (e.g. help.gohighlevel.com surfaced only via search). */\nfunction pickRediscoverTargets(byUrl: Map<string, UrlMeta>, host: string): string[] {\n const baseDomain = host.split('.').slice(-2).join('.')\n const subroots = new Set<string>()\n for (const url of byUrl.keys()) {\n const h = hostOf(url)\n if (!h || h === host) continue\n if (!h.endsWith(baseDomain)) continue\n if (/^(help|support|docs|kb|developer|developers)\\./.test(h)) {\n subroots.add(`https://${h}`)\n }\n }\n return [...subroots].slice(0, 3)\n}\n\nasync function enrichFromSitemaps(\n seeds: string[],\n host: string,\n deps: DiscoverDeps,\n add: (url: string, source: DiscoverySource, meta?: Partial<UrlMeta>) => void,\n log: (s: DiscoveryStep) => void,\n cfg: { maxChildren: number; remaining: () => number; aborted: () => boolean }\n): Promise<void> {\n let childBudget = cfg.maxChildren\n const seen = new Set<string>()\n\n const fetchAndParse = async (url: string, depth = 0): Promise<void> => {\n if (seen.has(url) || cfg.aborted() || depth > 3) return\n seen.add(url)\n const xml = await deps.fetchText(url, host, cfg.remaining()).catch(() => null)\n if (!xml) return\n\n // follow an HTML meta-refresh/canonical redirect to another sitemap.\n const redirect = htmlSitemapRedirect(xml, url)\n if (redirect && !seen.has(redirect)) {\n log({ kind: 'redirect', detail: `${url} → ${redirect}` })\n await fetchAndParse(redirect, depth + 1)\n return\n }\n\n const parsed = parseSitemap(xml)\n if (parsed.kind === 'index') {\n const children = prioritizeSitemapChildren(parsed.sitemaps).slice(0, Math.max(0, childBudget))\n childBudget -= children.length\n log({ kind: 'sitemap-index', detail: url, count: parsed.sitemaps.length })\n await Promise.allSettled(children.map((c) => fetchAndParse(c, depth + 1)))\n } else if (parsed.kind === 'urlset') {\n for (const e of parsed.urls) {\n add(e.loc, 'sitemap', {\n ...(e.lastmod ? { lastmod: e.lastmod } : {}),\n ...(e.priority != null ? { priority: e.priority } : {}),\n ...(e.changefreq ? { changefreq: e.changefreq } : {}),\n })\n }\n log({ kind: 'sitemap', detail: url, count: parsed.urls.length })\n }\n }\n\n await Promise.allSettled(seeds.map((s) => fetchAndParse(s, 0)))\n}\n\n/** If `xml` is actually an HTML page redirecting to another sitemap, return the\n * absolute target URL; else null. Handles meta-refresh + rel=canonical. */\nexport function htmlSitemapRedirect(body: string, base: string): string | null {\n const head = body.slice(0, 1000)\n if (!/^\\s*<!doctype html|^\\s*<html|<meta/i.test(head)) return null\n const refresh = /http-equiv=[\"']?refresh[\"']?[^>]*content=[\"'][^\"']*url=([^\"'>\\s]+)/i.exec(head)\n const canonical = /<link[^>]+rel=[\"']?canonical[\"']?[^>]*href=[\"']([^\"']+)[\"']/i.exec(head)\n const target = refresh?.[1] ?? canonical?.[1]\n if (!target) return null\n try {\n const abs = new URL(target, base).toString()\n return /sitemap/i.test(abs) ? abs : null\n } catch {\n return null\n }\n}\n\n/** English/neutral child sitemaps first; DROP non-English locale sitemaps. */\nexport function prioritizeSitemapChildren(children: string[]): string[] {\n const neutral: string[] = []\n const english: string[] = []\n for (const url of children) {\n const seg = firstSegment(url)\n if (seg && isLocaleSegment(seg)) {\n if (seg.slice(0, 2).toLowerCase() === 'en') english.push(url)\n } else {\n neutral.push(url)\n }\n }\n return [...neutral, ...english]\n}\n","// ── LLM selection (the decision) — single Sonnet call, INDEXED output, cached ──\n//\n// The heart of the rewrite. Given the compact candidate tree + company context\n// + optional use-case prompt + limit, ONE strong-model call picks the final\n// pages using world knowledge of what a support agent needs.\n//\n// SPEED: the tree numbers every pickable page `[i]`; the model returns the picked\n// NUMBERS (best-first), not rewritten URLs. Emitting ~50 ints instead of ~50 long\n// URLs cut the call ~4x (18s → ~5s) with identical selection quality — output\n// tokens were the bottleneck. We map indices back to URLs.\n//\n// Safety: cached via the injected cache dep (offline/deterministic in tests);\n// retried once; indices out-of-range are dropped (never fabricate a URL). The\n// LLM pick IS the decision — no deterministic fallback list.\n//\n// The LLM itself is an injected dep (SelectDeps.extract): this module owns the\n// prompt, the numbered tree, and the picks schema; the host owns transport and\n// model binding. A Haiku-class model suffices — the tree does the semantic\n// framing and output is just page numbers (A/B'd against Sonnet at ~half the\n// latency on the battle-test sites).\n\nimport { z } from '@bpinternal/zui'\nimport { renderNumberedTree } from './candidate-tree'\nimport type { Candidate } from '../types'\n\n// Bump when the prompt/schema changes (part of the cache key).\nconst SELECT_PROMPT_VERSION = 3\n\n/**\n * The one LLM touchpoint, host-provided. Mirrors zai's `extract` shape:\n * feed text + a schema + instructions, get validated structured output back.\n */\nexport type ExtractFn = <S extends z.ZodType>(\n input: string,\n schema: S,\n opts: { instructions: string }\n) => Promise<z.infer<S>>\n\n/**\n * Optional record/replay cache around side-effecting calls (the analogue of\n * viber's cognitive cache). Defaults to passthrough — production hosts don't\n * need to provide it.\n */\nexport type CacheFn = <T>(kind: string, keyParts: unknown, fn: () => Promise<T>) => Promise<T>\n\nexport type SelectDeps = {\n extract: ExtractFn\n cache?: CacheFn\n}\n\nexport type LlmSelectContext = {\n company?: string\n website?: string\n overview?: string\n /** Optional use-case refinement, e.g. \"focus on developer docs & API\". */\n prompt?: string\n}\n\nexport type LlmSelection = {\n /** Chosen URLs, best first, validated to exist in the candidate set. */\n urls: string[]\n rationale?: string\n}\n\nexport async function llmSelect(\n candidates: Candidate[],\n host: string,\n limit: number,\n ctx: LlmSelectContext,\n deps: SelectDeps\n): Promise<LlmSelection> {\n const { text: tree, index } = renderNumberedTree(candidates, host)\n\n const run = () =>\n deps.extract(\n `Site map for ${ctx.website ?? host} — a compact tree of the site's pages. Each pickable page is prefixed with a number [n]; folders show page counts; huge sections are folded to \"+N more pages…\":\\n\\n${tree}`,\n z.object({\n picks: z\n .array(z.number().int())\n .describe(`The ≤${limit} most useful page NUMBERS, BEST FIRST. Use the [n] numbers from the tree.`),\n rationale: z.string().optional(),\n }),\n {\n instructions: `You are building a knowledge base for an AI SUPPORT agent${ctx.company ? ` for ${ctx.company}` : ''}. Pick the ≤${limit} pages, BEST FIRST, that best let the agent answer real customer/visitor questions. Return their [n] NUMBERS.\n\n${ctx.overview ? `ABOUT THE BUSINESS: ${ctx.overview}\\n` : ''}${ctx.prompt ? `USE-CASE FOCUS: ${ctx.prompt}\\n` : ''}\nHow to choose (judgment about THIS business, not rules):\n- Prioritize pages that answer questions: what the business offers, pricing/plans, how it works, how to buy/apply/book, FAQ/help/support articles, policies (returns/shipping/privacy), contact, locations/hours, key product/service pages.\n- A page's URL slug tells you what it's about — use it (any language: Spanish tarifas/cuotas, Italian orari/contatti — judge by MEANING).\n- Judge whether this site's value is in its SERVICE/PRODUCT pages or its ARTICLES: a news org / non-profit's articles ARE the product (include many); a shop or club with a peripheral blog should mostly SKIP the blog and take the service pages.\n- Do NOT spend slots on near-identical BULK: individual product SKUs, every staff bio, every location, every dated promo/event, blog archives. Take a few representative ones at most.\n- Skip non-content: cart/checkout/login/account, tag/category/author archives, search/sitemap pages, dated marketing campaigns, thin utility/landing pages.\n- Prefer ONE language (the site's default) — don't pick the same page in multiple locales.\n\nReturn ≤${limit} page NUMBERS, best first.`,\n }\n )\n\n // Cache key must stay byte-stable across hosts: same kind + keyParts shape\n // as the original cognitive-cache call, so committed replay entries keep\n // hitting after extraction.\n const cache: CacheFn = deps.cache ?? (async (_kind, _keyParts, fn) => fn())\n const raw = await cache('url-selection.select', { v: SELECT_PROMPT_VERSION, tree, limit, ctx }, async () => {\n try {\n return await run()\n } catch {\n return await run() // retry once\n }\n }).catch(() => null)\n\n if (!raw || !Array.isArray(raw.picks)) return { urls: [], rationale: 'llm-select failed' }\n\n // map picked indices → URLs, dedup, cap, preserve order. Never fabricate.\n const seen = new Set<string>()\n const urls: string[] = []\n for (const p of raw.picks) {\n const i = typeof p === 'number' ? p : NaN\n if (!Number.isInteger(i) || i < 0 || i >= index.length) continue\n const url = index[i]!\n if (seen.has(url)) continue\n seen.add(url)\n urls.push(url)\n if (urls.length >= limit) break\n }\n return { urls, rationale: raw.rationale }\n}\n","// ── Candidate tree (pure) — the compact map the LLM selects from ──\n//\n// Turns the RAW discovered candidates into a compact, IMPORTANCE-RANKED text\n// map for one LLM pick call, within a max-LINES budget. No pre-filter, no\n// ranking decision — just \"show the most useful-looking pages, grouped, and fold\n// the bulk into '+N more' so the whole site fits in ~1000 lines\".\n//\n// Algorithm:\n// 1. Score every page's IMPORTANCE from path keywords (help/faq/pricing/contact/\n// hc/kb/shipping/returns/docs/… high; blog/press/investor/tag/dated/product\n// low). Only decides VISIBILITY under budget — the LLM makes the real pick.\n// 2. Group pages into coarse SECTIONS (first path segment, or first two for\n// /market/lang/ sites). Show pages in importance order, capped per section\n// (cap grown until we fill ~maxLines) so no one section hogs the budget and\n// help/category pages surface everywhere. Omitted pages per section → one\n// \"+N more pages under /x/\" line (bounded, since sections are bounded).\n// 3. Render sections by their best page's importance, pages numbered [i].\n//\n// Result: an importance-first view that fills the budget and always surfaces the\n// high-value pages of even a 40k-page catalog, with bulk folded to \"+N more\".\n\nimport type { Candidate } from '../types'\n\n// ── options ──\n\nexport type RenderOptions = {\n /** Max rendered lines (default 1000). */\n maxLines?: number\n}\n\nconst DEFAULTS: Required<RenderOptions> = { maxLines: 1000 }\n\n// ── importance (keyword tiers on the path) ──\n\nconst HIGH = [\n 'help',\n 'support',\n 'faq',\n 'faqs',\n 'hc',\n 'kb',\n 'knowledge-base',\n 'knowledgebase',\n 'docs',\n 'documentation',\n 'pricing',\n 'plans',\n 'price',\n 'prices',\n 'contact',\n 'contact-us',\n 'contacto',\n 'contatti',\n 'shipping',\n 'returns',\n 'return',\n 'refund',\n 'refunds',\n 'track',\n 'trackorder',\n 'order',\n 'orders',\n 'delivery',\n 'warranty',\n 'account',\n 'myaccount',\n 'sizing',\n 'size-guide',\n 'size-chart',\n 'how-it-works',\n 'get-started',\n 'getting-started',\n 'guide',\n 'guides',\n 'policy',\n 'policies',\n 'terms',\n 'privacy',\n 'about',\n 'about-us',\n 'integrations',\n 'api',\n 'gift-cards',\n 'gift-card',\n // multilingual high-value\n 'tarifas',\n 'cuotas',\n 'precios',\n 'orari',\n 'ayuda',\n 'aiuto',\n 'contacto',\n 'envios',\n 'devoluciones',\n]\nconst LOW = [\n 'blog',\n 'news',\n 'press',\n 'press-releases',\n 'investor',\n 'investors',\n 'investor-home',\n 'financials-filings',\n 'events-and-presentations',\n 'stock-information',\n 'esg',\n 'tag',\n 'tags',\n 'category',\n 'author',\n 'sitemap',\n 'archive',\n]\nconst HIGH_SET = new Set(HIGH)\nconst LOW_SET = new Set(LOW)\nconst DATED = /(^|[-/])(19|20)\\d{2}([-/]|$)/\n\n/** Importance of a page URL from its path keywords (higher = surface sooner). */\nexport function importanceOf(url: string): number {\n let path: string\n try {\n path = new URL(url).pathname.toLowerCase()\n } catch {\n return 0\n }\n const segs = path.split('/').filter(Boolean)\n if (segs.length === 0) return 100 // homepage — always top\n let score = 10 - Math.min(segs.length, 6) // shallow pages slightly preferred\n for (const s of segs) {\n if (HIGH_SET.has(s)) score += 40\n if (LOW_SET.has(s)) score -= 25\n }\n // substring catches (help-center, x-series-support, api-docs, /c/ categories)\n if (/help|support|faq|\\/hc\\/|pricing|contact|shipping|return|track|refund|docs?|guide|policy/.test(path)) score += 15\n if (/\\/c\\//.test(path)) score += 8 // category index (commerce taxonomy)\n if (/\\/p\\//.test(path)) score -= 15 // individual product page\n if (DATED.test(path)) score -= 15 // dated/ephemeral\n return score\n}\n\n// ── trie ──\n\n// ── render ──\n\ntype Page = { url: string; meta?: import('../types').UrlMeta; segs: string[]; imp: number }\n\nexport function renderNumberedTree(\n candidates: Candidate[],\n host: string,\n opts: RenderOptions = {}\n): { text: string; index: string[] } {\n const index: string[] = []\n const text = renderCandidateTree(candidates, host, opts, (url) => {\n const i = index.length\n index.push(url)\n return `[${i}] `\n })\n return { text, index }\n}\n\nexport function renderCandidateTree(\n candidates: Candidate[],\n host: string,\n opts: RenderOptions = {},\n numberPrefix?: (url: string) => string\n): string {\n const o = { ...DEFAULTS, ...opts }\n\n // 1. collect distinct pages (dedup by url), score importance.\n const seen = new Set<string>()\n const pages: Page[] = []\n for (const c of candidates) {\n if (seen.has(c.url)) continue\n seen.add(c.url)\n let segs: string[]\n try {\n segs = new URL(c.url).pathname.split('/').filter(Boolean)\n } catch {\n continue\n }\n pages.push({ url: c.url, meta: c.meta, segs, imp: importanceOf(c.url) })\n }\n const totalPages = pages.length\n\n // 2. choose which pages to SHOW. Global importance order, BUT cap how many\n // pages any single parent folder may contribute — so a 9k-product folder\n // shows only a few representative pages (the rest roll up to \"+N more\"),\n // and the budget spreads across MANY sections instead of dumping one bulk\n // tree. maxPerParent is tuned so the shown set fills ~maxLines: raise the\n // cap until we're near budget.\n const byImp = [...pages].sort((a, b) => b.imp - a.imp || a.segs.length - b.segs.length || a.url.localeCompare(b.url))\n const headerLines = 1\n\n // Coarse SECTION grouping for the fold: the first path segment, or the first\n // TWO when they're locale/market codes (…/us/en/… → section \"us/en/<seg>\").\n // Simple + robust: bounded number of sections, so the budget always holds and\n // the \"+N more\" fold count can't explode. Importance ordering (below) is what\n // ensures help/pricing/category pages are the ones shown; products fold.\n const LOCALE = /^[a-z]{2}([-_][a-z]{2})?$/i\n const MARKET = new Set(['us', 'ca', 'mx', 'uk', 'eu', 'intl', 'en', 'fr', 'es', 'de', 'it'])\n const groupKey = (segs: string[]): string => {\n if (segs.length <= 1) return '' // homepage + depth-1 pages share the root group\n // skip leading locale/market segments, then take one more as the section.\n let i = 0\n while (i < segs.length - 1 && (LOCALE.test(segs[i]!) || MARKET.has(segs[i]!.toLowerCase()))) i++\n return segs.slice(0, i + 1).join('/')\n }\n\n const totalByGroup = new Map<string, number>()\n for (const p of pages) totalByGroup.set(groupKey(p.segs), (totalByGroup.get(groupKey(p.segs)) ?? 0) + 1)\n\n // Select at a given per-section cap. shownByGroup[g] = pages shown in section g\n // (importance order). Lines = header + shown + (1 fold per section with omissions).\n const selectWithCap = (cap: number) => {\n const shownByGroup = new Map<string, Page[]>()\n let shownCount = 0\n for (const p of byImp) {\n const g = groupKey(p.segs)\n const arr = shownByGroup.get(g) ?? shownByGroup.set(g, []).get(g)!\n if (arr.length >= cap) continue\n arr.push(p)\n shownCount++\n }\n let foldLines = 0\n for (const [g, tot] of totalByGroup) if (tot > (shownByGroup.get(g)?.length ?? 0)) foldLines++\n return { shownByGroup, lines: headerLines + shownCount + foldLines }\n }\n\n let chosen = selectWithCap(1)\n for (let cap = 2; cap <= 1000; cap++) {\n const next = selectWithCap(cap)\n if (next.lines > o.maxLines) break\n chosen = next\n }\n const shownByGroup = chosen.shownByGroup\n\n // 3. render: sections ordered by their best page's importance; within a\n // section, pages in importance order; a \"+N more\" line per section with\n // omitted pages.\n const num = (url: string) => (numberPrefix ? numberPrefix(url) : '')\n const metaOf = (m?: import('../types').UrlMeta): string => {\n if (!m) return ''\n const bits: string[] = []\n if (m.sources?.includes('llms')) bits.push('llms')\n if (m.lastmod && recentish(m.lastmod)) bits.push('recent')\n return bits.length ? ` {${bits.join(',')}}` : ''\n }\n\n const groups = [...new Set([...totalByGroup.keys()])].sort((a, b) => {\n const ia = Math.max(-999, ...(shownByGroup.get(a)?.map((p) => p.imp) ?? []))\n const ib = Math.max(-999, ...(shownByGroup.get(b)?.map((p) => p.imp) ?? []))\n return ib - ia || a.localeCompare(b)\n })\n\n const lines: string[] = [`${host} — ${totalPages} pages:`]\n for (const g of groups) {\n const shownHere = (shownByGroup.get(g) ?? []).sort((a, b) => b.imp - a.imp || a.url.localeCompare(b.url))\n for (const p of shownHere) lines.push(`${num(p.url)}${pathStr(p.segs)}${metaOf(p.meta)}`)\n const omitted = (totalByGroup.get(g) ?? 0) - shownHere.length\n if (omitted > 0) {\n const under = g === '' ? '/' : `/${g}/`\n lines.push(`(+${omitted} more pages under ${under})`)\n }\n }\n return lines.join('\\n')\n}\n\n// ── helpers ──\n\nfunction pathStr(segs: string[]): string {\n return '/' + segs.join('/')\n}\nconst TWO_YEARS_MS = 2 * 365.25 * 24 * 3600 * 1000\nfunction recentish(lastmod: string): boolean {\n const t = Date.parse(lastmod)\n if (!Number.isFinite(t)) return false\n const ageMs = Date.now() - t\n return ageMs >= 0 && ageMs < TWO_YEARS_MS // within a rolling ~2-year window\n}\n\n/** Exact candidate URL set — for validation. */\nexport function candidateUrlSet(candidates: Candidate[]): Set<string> {\n return new Set(candidates.map((c) => c.url))\n}\n\n/** Resolver mapping a full URL OR a bare path back to the canonical full URL. */\nexport function candidateResolver(candidates: Candidate[]): (picked: string) => string | null {\n const byUrl = new Set(candidates.map((c) => c.url))\n const byPath = new Map<string, string>()\n for (const c of candidates) {\n try {\n const p = new URL(c.url).pathname || '/'\n if (!byPath.has(p)) byPath.set(p, c.url)\n } catch {\n /* skip */\n }\n }\n return (picked: string): string | null => {\n const s = picked.trim()\n if (!s) return null\n if (byUrl.has(s)) return s\n const noSlash = s.replace(/\\/$/, '')\n if (byUrl.has(noSlash)) return noSlash\n try {\n const p = new URL(s).pathname || '/'\n if (byPath.has(p)) return byPath.get(p)!\n } catch {\n if (s.startsWith('/')) {\n if (byPath.has(s)) return byPath.get(s)!\n if (byPath.has(noSlash)) return byPath.get(noSlash)!\n }\n }\n return null\n }\n}\n","// ── Selection (unit #2) — LLM-first ──\n//\n// select(map, opts) -> Selection. Takes the MasterMap from discover(), builds a\n// compact candidate tree from ALL discovered pages (no pre-filter, no scoring),\n// and lets ONE strong-model LLM call pick the best ≤limit URLs using world\n// knowledge of what a support agent needs (llm-select.ts). The LLM pick IS the\n// decision — there is no deterministic fallback list.\n\nimport type { Candidate } from '../types'\nimport type { MasterMap, Selection, SelectOptions, TreeNode } from '../types'\nimport { leafUrls } from '../discovery/build-tree'\nimport { hostOf } from '../web-fetch'\nimport { llmSelect, type SelectDeps } from './llm-select'\n\nconst DEFAULT_LIMIT = 100\n\nexport async function select(map: MasterMap, opts: SelectOptions = {}, deps: SelectDeps): Promise<Selection> {\n const limit = opts.limit ?? DEFAULT_LIMIT\n if (map.unsupported) return { urls: [], useful: 0 }\n\n // All discovered pages become candidates (the tree carries url+meta at leaves).\n const candidates: Candidate[] = [...leafUrls(map.tree)]\n .filter((n): n is TreeNode & { url: string } => !!n.url)\n .map((n) => ({ url: n.url, meta: n.meta ?? { sources: [] } }))\n\n if (candidates.length === 0) return { urls: [], useful: 0 }\n\n const host = hostOf(candidates[0]!.url) ?? ''\n\n // The single LLM pick over the compact candidate tree.\n const picked = await llmSelect(\n candidates,\n host,\n limit,\n {\n ...(opts.context?.company ? { company: opts.context.company } : {}),\n ...(opts.context?.website ? { website: opts.context.website } : {}),\n ...(opts.context?.overview ? { overview: opts.context.overview } : {}),\n ...(opts.prompt ? { prompt: opts.prompt } : {}),\n },\n deps\n )\n\n return { urls: picked.urls, useful: candidates.length }\n}\n","// ── HTTP fixture cache ──\n//\n// A record/replay cache for the *network* side of URL discovery — the analogue\n// of `cognitive-cache.ts` (which does the same for LLM calls). Discovery fetches\n// robots.txt / sitemaps / llms.txt and calls `browser.discoverUrls` /\n// `webSearch`; those are slow, paid, and non-deterministic. This wraps each such\n// call so that:\n//\n// - RECORD mode (`URL_FIXTURE_RECORD=1`): real call runs, result is written to\n// a committed fixture file keyed by (kind, args). Re-running is then free.\n// - REPLAY mode (default in tests): the call NEVER hits the network; the\n// recorded value is returned. A miss throws, so a test can't silently depend\n// on live network.\n// - PASSTHROUGH (default outside tests/recording): no caching, real call.\n//\n// Fixtures live next to the code as JSONL, one file per logical group (usually a\n// site), so a site's whole recorded surface is one reviewable diff.\n//\n// Keying mirrors cognitive-cache: stable-stringify args → fastHash. Identical\n// args → identical key → same fixture entry.\n\nimport fs from 'node:fs'\nimport path from 'node:path'\n\n// ── Hashing / stable stringify (kept in sync with cognitive-cache.ts) ──\n\nexport function fastHash(str: string): string {\n let hash = 0\n for (let i = 0; i < str.length; i++) {\n hash = (hash << 5) - hash + str.charCodeAt(i)\n hash |= 0\n }\n return (hash >>> 0).toString(16)\n}\n\nfunction sortKeys(input: any): any {\n if (Array.isArray(input)) return input.map(sortKeys)\n if (input && typeof input === 'object' && input.constructor === Object) {\n return Object.keys(input)\n .sort()\n .reduce(\n (acc, key) => {\n acc[key] = sortKeys(input[key])\n return acc\n },\n {} as Record<string, any>\n )\n }\n return input\n}\n\nexport function stableStringify(obj: unknown): string {\n return JSON.stringify(sortKeys(obj))\n}\n\n// ── Modes ──\n\nexport type HttpCacheMode = 'record' | 'replay' | 'passthrough'\n\n/**\n * Resolve the active mode from env. RECORD when `URL_FIXTURE_RECORD` is set\n * (the record-fixtures script sets it). REPLAY inside `bun test` (so tests are\n * offline + deterministic). PASSTHROUGH everywhere else (production).\n */\nexport function httpCacheMode(): HttpCacheMode {\n if (process.env.URL_FIXTURE_RECORD) return 'record'\n if (process.env.NODE_ENV === 'test' || process.env.BUN_TEST || process.env.VITEST || process.env.URL_FIXTURE_REPLAY) {\n return 'replay'\n }\n return 'passthrough'\n}\n\nconst FIXTURE_DIR = path.resolve(__dirname, './__fixtures__')\n\ntype FixtureEntry = {\n key: string\n kind: string\n group: string\n input: string\n value: unknown\n}\n\n// One in-memory map per group file, loaded lazily.\nconst loaded = new Map<string, Map<string, FixtureEntry>>()\n\nfunction groupPath(group: string): string {\n return path.join(FIXTURE_DIR, `${group}.jsonl`)\n}\n\nfunction loadGroup(group: string): Map<string, FixtureEntry> {\n const existing = loaded.get(group)\n if (existing) return existing\n const map = new Map<string, FixtureEntry>()\n try {\n const content = fs.readFileSync(groupPath(group), 'utf-8')\n for (const line of content.split(/\\r?\\n/).filter(Boolean)) {\n try {\n const entry = JSON.parse(line) as FixtureEntry\n map.set(entry.key, entry)\n } catch {\n // skip malformed line\n }\n }\n } catch {\n // no fixture file yet\n }\n loaded.set(group, map)\n return map\n}\n\nfunction appendEntry(entry: FixtureEntry): void {\n const map = loadGroup(entry.group)\n map.set(entry.key, entry)\n try {\n fs.mkdirSync(FIXTURE_DIR, { recursive: true })\n } catch {\n // best-effort\n }\n fs.appendFileSync(groupPath(entry.group), JSON.stringify(entry) + '\\n')\n}\n\nexport class FixtureMissError extends Error {\n constructor(kind: string, group: string, input: string) {\n super(\n `[http-cache] no recorded fixture for kind=\"${kind}\" group=\"${group}\".\\n` +\n `Re-record with: URL_FIXTURE_RECORD=1 bun run scripts/record-fixtures.ts\\n` +\n `input: ${input.slice(0, 300)}`\n )\n this.name = 'FixtureMissError'\n }\n}\n\nexport type CacheStats = { hits: number; misses: number; recorded: number }\nconst stats: CacheStats = { hits: 0, misses: 0, recorded: 0 }\nexport function httpCacheStats(): CacheStats {\n return { ...stats }\n}\n\n/**\n * Wrap one network-ish call so it records/replays against committed fixtures.\n *\n * @param group fixture file name (no ext) — usually the site, e.g. \"nike\".\n * @param kind logical operation, e.g. \"fetchText\" | \"discoverUrls\" | \"webSearch\".\n * @param args the call arguments (used as the cache key — must stable-stringify).\n * @param fn the real call, run only on a miss in record mode.\n */\nexport async function cachedHttp<T>(group: string, kind: string, args: unknown, fn: () => Promise<T>): Promise<T> {\n const mode = httpCacheMode()\n if (mode === 'passthrough') return fn()\n\n const key = fastHash(`${kind}:${stableStringify(args)}`)\n const map = loadGroup(group)\n const hit = map.get(key)\n\n if (hit) {\n stats.hits++\n return hit.value as T\n }\n\n if (mode === 'replay') {\n stats.misses++\n throw new FixtureMissError(kind, group, stableStringify(args))\n }\n\n // record mode — run the real call and persist.\n const value = await fn()\n appendEntry({ key, kind, group, input: stableStringify(args), value })\n stats.recorded++\n return value as T\n}\n\n/** Reset in-memory state (tests). */\nexport function __resetHttpCache(): void {\n loaded.clear()\n stats.hits = 0\n stats.misses = 0\n stats.recorded = 0\n}\n","// ── Fixture-cached discovery deps ──\n//\n// Wrap real I/O deps so every network call records/replays against the committed\n// fixture file `group` (the network analogue of the cognitive cache). Keyed by\n// call args, so the same (url/query) always hits the same fixture entry. In\n// production (passthrough mode) cachedHttp is a no-op, so this is safe to always\n// apply. See http-cache.ts.\n\nimport { cachedHttp } from '../http-cache'\nimport type { SiteScoutDeps } from '../index'\nimport type { fetchText, fetchJson } from '../web-fetch'\n\nexport function cachedDeps(group: string, real: SiteScoutDeps): SiteScoutDeps {\n return {\n // extract/cache pass through untouched — the LLM side has its own\n // record/replay via the injected cache dep.\n ...real,\n fetchText: ((url: string, baseHost: string, timeoutMs?: number) =>\n cachedHttp(group, 'fetchText', { url }, () => real.fetchText(url, baseHost, timeoutMs))) as typeof fetchText,\n fetchJson: ((url: string, baseHost: string, timeoutMs?: number) =>\n cachedHttp(group, 'fetchJson', { url }, () => real.fetchJson(url, baseHost, timeoutMs))) as typeof fetchJson,\n discoverUrls: (args) => cachedHttp(group, 'discoverUrls', args, () => real.discoverUrls(args)),\n webSearch: (args) => cachedHttp(group, 'webSearch', args, () => real.webSearch(args)),\n }\n}\n","// ── URL selection — public API ──\n//\n// Two independent units, composed:\n// discover(website, opts) -> MasterMap (prioritized tree)\n// select(map, opts) -> Selection (prioritized urls)\n//\n// findKnowledgeUrls() runs guard → discover → select for the common case, but\n// callers can use discover()/select() separately (e.g. show the tree, let a\n// user refine, then select).\n\nimport type { DiscoverOptions, MasterMap, Selection, SelectOptions, StopReason } from './types'\nimport { isUnsupportedSite } from './guard'\nimport { discover, type DiscoverDeps } from './discovery/discover'\nimport { select } from './selection/select'\nimport type { SelectDeps } from './selection/llm-select'\nimport { hostOf } from './web-fetch'\n\nexport type { DiscoverDeps, SearchResult } from './discovery/discover'\nexport type { SelectDeps, ExtractFn, CacheFn } from './selection/llm-select'\nexport { discover } from './discovery/discover'\nexport { select } from './selection/select'\nexport { cachedDeps } from './discovery/cached-deps'\nexport { fetchText, fetchJson, hostOf, siteOrigin } from './web-fetch'\nexport * from './types'\n\n/**\n * Everything the pipeline needs from its host: discovery I/O (fetch, URL\n * discovery, web search) plus the one LLM touchpoint and its optional cache.\n */\nexport type SiteScoutDeps = DiscoverDeps & SelectDeps\n\nexport type FindUrlsInput = {\n website: string\n limit?: number\n} & Pick<SelectOptions, 'prompt' | 'context'> &\n Pick<DiscoverOptions, 'topics' | 'signal' | 'budgetMs' | 'onActivity'>\n\n/** Convenience: guard → discover → select in one call. */\nexport async function findKnowledgeUrls(\n input: FindUrlsInput,\n deps: SiteScoutDeps,\n extra: { now?: number } = {}\n): Promise<Selection & { discovered: number; aborted: boolean; siteType: string; stopReason: StopReason }> {\n // Guardrail refuses social/search/mega-sites (and truly unparseable input) —\n // this is the ONLY path that yields 'unsupported_site'.\n if (isUnsupportedSite(input.website)) {\n return {\n urls: [],\n useful: 0,\n discovered: 0,\n aborted: false,\n siteType: 'unknown',\n stopReason: 'unsupported_site',\n }\n }\n if (!hostOf(input.website)) {\n return {\n urls: [],\n useful: 0,\n discovered: 0,\n aborted: false,\n siteType: 'unknown',\n stopReason: 'no_sources',\n }\n }\n\n const map: MasterMap = await discover(\n input.website,\n {\n ...(input.topics ? { topics: input.topics } : {}),\n ...(input.signal ? { signal: input.signal } : {}),\n ...(input.budgetMs != null ? { budgetMs: input.budgetMs } : {}),\n ...(input.onActivity ? { onActivity: input.onActivity } : {}),\n ...(extra.now != null ? { now: extra.now } : {}),\n } as DiscoverOptions,\n deps\n )\n\n const selection = await select(\n map,\n {\n ...(input.limit != null ? { limit: input.limit } : {}),\n ...(input.prompt ? { prompt: input.prompt } : {}),\n ...(input.context ? { context: input.context } : {}),\n },\n deps\n )\n\n const stopReason: StopReason = map.aborted ? 'time_limit_reached' : map.total === 0 ? 'no_sources' : 'ok'\n return { ...selection, discovered: map.total, aborted: map.aborted, siteType: map.siteType, stopReason }\n}\n"],"mappings":"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","names":["text","registrableDomain","text","parser","text","path","text","registrableDomain","DEFAULTS","LOW","path","text","shownByGroup","path","path"]}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@bpinternal/site-scout",
3
+ "version": "0.1.0",
4
+ "description": "Website URL discovery + LLM page selection — scouts a site for the pages worth reading (seeds support-bot knowledge bases).",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "source": "./src/index.ts",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": ["dist"],
12
+ "repository": {
13
+ "url": "https://github.com/botpress/genisys"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.cjs"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "check:lint": "eslint .",
26
+ "fix:lint": "eslint --fix .",
27
+ "check:type": "tsc --noEmit",
28
+ "test:watch": "vitest .",
29
+ "test": "vitest run"
30
+ },
31
+ "dependencies": {
32
+ "fast-xml-parser": "^5.9.3",
33
+ "robots-parser": "^3.0.1"
34
+ },
35
+ "peerDependencies": {
36
+ "@bpinternal/zui": ">=2.1.0 <3.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@bpinternal/zui": "~2.3.0",
40
+ "@repo/eslint-config": "workspace:*",
41
+ "@repo/typescript-config": "workspace:*",
42
+ "@types/node": "^22.7.4",
43
+ "tsup": "^8.1.0",
44
+ "typescript": "5.9.3",
45
+ "vitest": "1.6.0"
46
+ }
47
+ }