mcp-scraper 0.40.1 → 0.40.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/api-server.cjs +60220 -0
- package/dist/bin/api-server.cjs.map +1 -0
- package/dist/bin/api-server.d.cts +1 -0
- package/dist/bin/api-server.d.ts +1 -0
- package/dist/bin/api-server.js +38 -0
- package/dist/bin/api-server.js.map +1 -0
- package/dist/bin/mcp-scraper-cli.cjs +2671 -0
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -0
- package/dist/bin/mcp-scraper-cli.d.cts +1 -0
- package/dist/bin/mcp-scraper-cli.d.ts +1 -0
- package/dist/bin/mcp-scraper-cli.js +742 -0
- package/dist/bin/mcp-scraper-cli.js.map +1 -0
- package/dist/bin/mcp-scraper-install.cjs +129 -0
- package/dist/bin/mcp-scraper-install.cjs.map +1 -0
- package/dist/bin/mcp-scraper-install.d.cts +1 -0
- package/dist/bin/mcp-scraper-install.d.ts +1 -0
- package/dist/bin/mcp-scraper-install.js +27 -0
- package/dist/bin/mcp-scraper-install.js.map +1 -0
- package/dist/bin/mcp-stdio-server.cjs +12532 -0
- package/dist/bin/mcp-stdio-server.cjs.map +1 -0
- package/dist/bin/mcp-stdio-server.d.cts +1 -0
- package/dist/bin/mcp-stdio-server.d.ts +1 -0
- package/dist/bin/mcp-stdio-server.js +135 -0
- package/dist/bin/mcp-stdio-server.js.map +1 -0
- package/dist/bin/paa-harvest.cjs +3808 -0
- package/dist/bin/paa-harvest.cjs.map +1 -0
- package/dist/bin/paa-harvest.d.cts +1 -0
- package/dist/bin/paa-harvest.d.ts +1 -0
- package/dist/bin/paa-harvest.js +44 -0
- package/dist/bin/paa-harvest.js.map +1 -0
- package/dist/chunk-345BQXZH.js +712 -0
- package/dist/chunk-345BQXZH.js.map +1 -0
- package/dist/chunk-3PIWJS6Y.js +276 -0
- package/dist/chunk-3PIWJS6Y.js.map +1 -0
- package/dist/chunk-44HZLHDV.js +52 -0
- package/dist/chunk-44HZLHDV.js.map +1 -0
- package/dist/chunk-7XBBFBYY.js +3410 -0
- package/dist/chunk-7XBBFBYY.js.map +1 -0
- package/dist/chunk-CB5C3BPB.js +135 -0
- package/dist/chunk-CB5C3BPB.js.map +1 -0
- package/dist/chunk-FQI5PFE7.js +1866 -0
- package/dist/chunk-FQI5PFE7.js.map +1 -0
- package/dist/chunk-G3P3ZDB4.js +69 -0
- package/dist/chunk-G3P3ZDB4.js.map +1 -0
- package/dist/chunk-GUVKHCKE.js +3050 -0
- package/dist/chunk-GUVKHCKE.js.map +1 -0
- package/dist/chunk-K443GQY5.js +24 -0
- package/dist/chunk-K443GQY5.js.map +1 -0
- package/dist/chunk-LP6E462I.js +11555 -0
- package/dist/chunk-LP6E462I.js.map +1 -0
- package/dist/chunk-NVXNEOUQ.js +158 -0
- package/dist/chunk-NVXNEOUQ.js.map +1 -0
- package/dist/chunk-QZXKQB7Y.js +414 -0
- package/dist/chunk-QZXKQB7Y.js.map +1 -0
- package/dist/chunk-SBLGBZZB.js +647 -0
- package/dist/chunk-SBLGBZZB.js.map +1 -0
- package/dist/chunk-X2LKCX6H.js +499 -0
- package/dist/chunk-X2LKCX6H.js.map +1 -0
- package/dist/chunk-XELSA2MS.js +108 -0
- package/dist/chunk-XELSA2MS.js.map +1 -0
- package/dist/chunk-XORPNO3Z.js +684 -0
- package/dist/chunk-XORPNO3Z.js.map +1 -0
- package/dist/chunk-ZUJLSICT.js +7 -0
- package/dist/chunk-ZUJLSICT.js.map +1 -0
- package/dist/db-W3CP562I.js +239 -0
- package/dist/db-W3CP562I.js.map +1 -0
- package/dist/editorial-reading-room/assets/app.js +335 -0
- package/dist/editorial-reading-room/assets/index.html +131 -0
- package/dist/editorial-reading-room/assets/styles.css +1052 -0
- package/dist/extract-bundle-K4PG3RZJ.js +568 -0
- package/dist/extract-bundle-K4PG3RZJ.js.map +1 -0
- package/dist/index.cjs +4160 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +413 -0
- package/dist/index.d.ts +413 -0
- package/dist/index.js +338 -0
- package/dist/index.js.map +1 -0
- package/dist/location-data-repository-RLQX6SNM.js +35 -0
- package/dist/location-data-repository-RLQX6SNM.js.map +1 -0
- package/dist/server-KM3CFWCF.js +34674 -0
- package/dist/server-KM3CFWCF.js.map +1 -0
- package/dist/site-extract-repository-2SMMFKKL.js +62 -0
- package/dist/site-extract-repository-2SMMFKKL.js.map +1 -0
- package/dist/worker-FXAGFYOE.js +142 -0
- package/dist/worker-FXAGFYOE.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/url-utils.ts","../src/api/image-audit.ts","../src/api/seo-link-report.ts","../src/api/seo-issues.ts","../src/api/private-artifacts.ts"],"sourcesContent":["import { isIP } from 'node:net'\nimport { lookup } from 'node:dns/promises'\nimport ipaddr from 'ipaddr.js'\n\nfunction unbracketIpLiteral(value: string): string {\n return value.startsWith('[') && value.endsWith(']') ? value.slice(1, -1) : value\n}\n\nexport function isPrivateIpAddress(address: string): boolean {\n const normalized = unbracketIpLiteral(address)\n if (!isIP(normalized)) return false\n try {\n const parsed = ipaddr.parse(normalized)\n const isExtraNonGlobalIpv4 = (ipv4: InstanceType<typeof ipaddr.IPv4>): boolean => {\n const extraNonGlobal: Array<[string, number]> = [\n ['192.0.0.0', 24],\n ['192.0.2.0', 24],\n ['198.18.0.0', 15],\n ['198.51.100.0', 24],\n ['203.0.113.0', 24],\n ]\n return extraNonGlobal.some(([network, bits]) => ipv4.match(ipaddr.parse(network) as InstanceType<typeof ipaddr.IPv4>, bits))\n }\n if (parsed instanceof ipaddr.IPv6 && parsed.isIPv4MappedAddress()) {\n const mapped = parsed.toIPv4Address()\n return mapped.range() !== 'unicast' || isExtraNonGlobalIpv4(mapped)\n }\n if (parsed instanceof ipaddr.IPv4) {\n if (isExtraNonGlobalIpv4(parsed)) return true\n }\n // Despite the historical function name, outbound URL validation needs to\n // reject every non-global range: loopback/private/link-local plus CGNAT,\n // multicast, documentation, benchmark, reserved and unspecified space.\n return parsed.range() !== 'unicast'\n } catch { return true }\n}\n\nexport async function resolvesToPrivateAddress(hostname: string): Promise<boolean> {\n const host = unbracketIpLiteral(hostname.toLowerCase())\n if (host === 'localhost' || host.endsWith('.localhost') || host.endsWith('.local')) return true\n if (isPrivateIpAddress(host)) return true\n try {\n const addresses = await lookup(host, { all: true, verbatim: true })\n return addresses.length === 0 || addresses.some((entry) => isPrivateIpAddress(entry.address))\n } catch {\n // URL safety must fail closed when DNS cannot establish a public target.\n return true\n }\n}\n\nexport async function validatePublicHttpUrl(raw: string, opts: { field: string; requireHttps?: boolean }): Promise<{ parsed?: URL; error?: string }> {\n let parsed: URL\n try {\n parsed = new URL(raw.trim())\n } catch {\n return { error: `Invalid ${opts.field}` }\n }\n const allowedProtocols = opts.requireHttps ? ['https:'] : ['http:', 'https:']\n if (!allowedProtocols.includes(parsed.protocol)) {\n return { error: opts.requireHttps ? `${opts.field} must use https` : `${opts.field} must use http or https` }\n }\n if (await resolvesToPrivateAddress(parsed.hostname)) {\n return { error: `${opts.field} must resolve to a public internet host` }\n }\n return { parsed }\n}\n","import type { PageData } from './site-extractor.js'\nimport { validatePublicHttpUrl } from './url-utils.js'\n\nexport interface ImageRow {\n url: string\n status: number | null\n bytes: number | null\n size: string | null\n contentType: string | null\n format: string\n over100kb: boolean\n legacyFormat: boolean\n error?: string\n}\n\nexport interface ImageAuditSummary {\n unique: number\n sized: number\n totalBytes: number\n totalSize: string\n avgSize: string\n over100kb: number\n legacyFormat: number\n formatCounts: Record<string, number>\n sampleTruncated?: boolean\n sampleCap?: number\n}\n\nexport interface ImageAudit {\n rows: ImageRow[]\n summary: ImageAuditSummary\n}\n\nconst OVER_BYTES = 100 * 1024\nconst MODERN = new Set(['webp', 'avif', 'svg', 'svg+xml'])\n\nexport function formatBytes(n: number | null): string | null {\n if (n == null) return null\n if (n < 1024) return `${n} B`\n if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`\n return `${(n / (1024 * 1024)).toFixed(2)} MB`\n}\n\nconst decodeEntities = (u: string): string =>\n u.replace(/&/g, '&').replace(/�?38;/g, '&').replace(/&/gi, '&').trim()\n\nconst dedupKey = (u: string): string => u.replace(/^https?:\\/\\//i, '//').replace(/\\/$/, '')\n\nconst formatOf = (ct: string | null, url: string): string => {\n if (ct) { const m = ct.split(';')[0].trim().toLowerCase(); if (m.startsWith('image/')) return m.replace('image/', '') }\n return (url.split('?')[0].match(/\\.([a-z0-9]+)$/i)?.[1] || 'unknown').toLowerCase()\n}\n\nfunction collectUrls(pages: PageData[]): string[] {\n const byKey = new Map<string, string>()\n for (const p of pages) for (const raw of (p.imageLinks || [])) {\n const u = decodeEntities(raw)\n if (!/^https?:\\/\\//i.test(u)) continue\n if (/[{}]|%7[bd]/i.test(u)) continue\n const k = dedupKey(u)\n const prev = byKey.get(k)\n if (!prev || (/^https:/i.test(u) && /^http:/i.test(prev))) byKey.set(k, u)\n }\n return [...byKey.values()]\n}\n\nasync function sizeAndType(url: string, timeoutMs: number): Promise<Omit<ImageRow, 'size' | 'format' | 'over100kb' | 'legacyFormat'>> {\n const ctrl = new AbortController()\n const t = setTimeout(() => ctrl.abort(), timeoutMs)\n const read = (res: Response) => ({\n len: res.headers.get('content-length'),\n cr: res.headers.get('content-range'),\n ct: res.headers.get('content-type'),\n status: res.status,\n })\n const safeFetch = async (method: 'HEAD' | 'GET'): Promise<Response> => {\n let target = url\n for (let redirects = 0; redirects <= 5; redirects++) {\n const checked = await validatePublicHttpUrl(target, { field: 'image URL' })\n if (checked.error || !checked.parsed) throw new Error(checked.error ?? 'Image URL was rejected')\n const response = await fetch(checked.parsed.href, {\n method,\n ...(method === 'GET' ? { headers: { Range: 'bytes=0-0' } } : {}),\n redirect: 'manual',\n signal: ctrl.signal,\n })\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location')\n if (!location) throw new Error(`HTTP ${response.status} redirect did not include Location`)\n target = new URL(location, checked.parsed.href).href\n continue\n }\n return response\n }\n throw new Error('Image request exceeded five redirects')\n }\n try {\n let bytes: number | null = null\n let ct: string | null = null\n let status: number | null = null\n try {\n const h = read(await safeFetch('HEAD'))\n status = h.status; ct = h.ct\n if (h.len) bytes = Number(h.len)\n } catch { /* fall through to ranged GET */ }\n if (bytes == null) {\n const g = await safeFetch('GET')\n const r = read(g)\n status = status ?? r.status; ct = ct ?? r.ct\n if (r.cr && r.cr.includes('/')) { const tail = r.cr.split('/')[1]; if (tail && tail !== '*') bytes = Number(tail) }\n else if (r.len) bytes = Number(r.len)\n try { await g.body?.cancel() } catch { /* ignore */ }\n }\n return { url, status, bytes: Number.isFinite(bytes as number) ? bytes : null, contentType: ct }\n } catch (e) {\n return { url, status: null, bytes: null, contentType: null, error: String((e as Error).message || e) }\n } finally {\n clearTimeout(t)\n }\n}\n\nasync function pool<T, R>(items: T[], n: number, fn: (item: T) => Promise<R>): Promise<R[]> {\n const out = new Array<R>(items.length)\n let i = 0\n await Promise.all(Array.from({ length: Math.min(n, items.length) }, async () => {\n while (i < items.length) { const idx = i++; out[idx] = await fn(items[idx]) }\n }))\n return out\n}\n\nexport async function auditImages(\n pages: PageData[],\n opts: { concurrency?: number; timeoutMs?: number; max?: number } = {},\n): Promise<ImageAudit> {\n return auditImageUrls(collectUrls(pages), opts)\n}\n\nexport async function auditImageUrls(\n inputUrls: string[],\n opts: { concurrency?: number; timeoutMs?: number; max?: number; sampleTruncated?: boolean } = {},\n): Promise<ImageAudit> {\n const concurrency = opts.concurrency ?? 12\n const timeoutMs = opts.timeoutMs ?? 12_000\n const max = opts.max ?? 5000\n const urls = collectUrls([{ imageLinks: inputUrls } as PageData]).slice(0, max)\n\n const heads = await pool(urls, concurrency, (u) => sizeAndType(u, timeoutMs))\n const rows: ImageRow[] = heads.map(r => {\n const format = formatOf(r.contentType, r.url)\n return {\n ...r,\n size: formatBytes(r.bytes),\n format,\n over100kb: r.bytes != null && r.bytes > OVER_BYTES,\n legacyFormat: format !== 'unknown' && !MODERN.has(format),\n }\n })\n\n const sized = rows.filter(r => r.bytes != null)\n const totalBytes = sized.reduce((a, r) => a + (r.bytes as number), 0)\n const formatCounts: Record<string, number> = {}\n for (const r of rows) formatCounts[r.format] = (formatCounts[r.format] || 0) + 1\n\n return {\n rows: rows.sort((a, b) => (b.bytes ?? 0) - (a.bytes ?? 0)),\n summary: {\n unique: rows.length,\n sized: sized.length,\n totalBytes,\n totalSize: formatBytes(totalBytes) ?? '0 B',\n avgSize: formatBytes(Math.round(totalBytes / (sized.length || 1))) ?? '0 B',\n over100kb: rows.filter(r => r.over100kb).length,\n legacyFormat: rows.filter(r => r.legacyFormat).length,\n formatCounts,\n ...(opts.sampleTruncated ? { sampleTruncated: true, sampleCap: max } : {}),\n },\n }\n}\n\nexport function renderImageSection(audit: ImageAudit): string {\n const s = audit.summary\n const fmt = Object.entries(s.formatCounts).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(' ')\n const heaviest = audit.rows.filter(r => r.over100kb).slice(0, 15)\n const lines = [\n `## Images`,\n `**${s.unique} unique images** · ${s.totalSize} total · ${s.avgSize} avg · ${s.over100kb} over 100 KB · ${s.legacyFormat} legacy format`,\n `Formats: ${fmt}`,\n ]\n if (heaviest.length) {\n lines.push('', '| Size | Format | URL |', '|------|--------|-----|')\n for (const r of heaviest) lines.push(`| ${r.size} | ${r.format} | ${r.url} |`)\n }\n return lines.join('\\n')\n}\n","import type { LinkEdge, PageLinkMetrics } from './seo-link-graph.js'\n\nexport interface ExtDomainRow { domain: string; links: number; nofollow: number; pages: number }\n\nexport interface LinkReportSummary {\n internal: {\n totalLinks: number\n pages: number\n orphans: number\n brokenInternal: number\n avgInlinks: number\n avgOutlinks: number\n distribution: { zero: number; oneToTwo: number; threeToTen: number; elevenPlus: number }\n topByInlinks: Array<{ url: string; inlinks: number; outlinksInternal: number; outlinksExternal: number }>\n }\n external: {\n totalLinks: number\n uniqueDomains: number\n topDomains: ExtDomainRow[]\n }\n}\n\nexport interface LinkReport {\n summary: LinkReportSummary\n externalDomains: ExtDomainRow[]\n}\n\nfunction registrableDomain(host: string): string {\n const h = host.replace(/^www\\./, '').toLowerCase()\n const parts = h.split('.')\n return parts.length <= 2 ? h : parts.slice(-2).join('.')\n}\n\nexport function buildLinkReport(edges: LinkEdge[], metrics: PageLinkMetrics[], siteUrl: string): LinkReport {\n let siteReg = ''\n try { siteReg = registrableDomain(new URL(siteUrl).hostname) } catch { siteReg = '' }\n const internalEdges = edges.filter(e => e.internal)\n const pages = metrics.length\n const orphans = metrics.filter(m => m.orphan).length\n const brokenInternal = internalEdges.filter(e => e.targetStatus != null && e.targetStatus >= 400).length\n const sumInlinks = metrics.reduce((a, m) => a + m.inlinks, 0)\n const sumOutlinks = metrics.reduce((a, m) => a + m.outlinksInternal + m.outlinksExternal, 0)\n\n const distribution = { zero: 0, oneToTwo: 0, threeToTen: 0, elevenPlus: 0 }\n for (const m of metrics) {\n if (m.inlinks === 0) distribution.zero++\n else if (m.inlinks <= 2) distribution.oneToTwo++\n else if (m.inlinks <= 10) distribution.threeToTen++\n else distribution.elevenPlus++\n }\n\n const topByInlinks = [...metrics].sort((a, b) => b.inlinks - a.inlinks).slice(0, 20)\n .map(m => ({ url: m.url, inlinks: m.inlinks, outlinksInternal: m.outlinksInternal, outlinksExternal: m.outlinksExternal }))\n\n const domMap = new Map<string, { links: number; nofollow: number; pages: Set<string> }>()\n let externalTotal = 0\n for (const e of edges) {\n let domain: string\n try { domain = registrableDomain(new URL(e.to).hostname) } catch { continue }\n if (!domain || domain === siteReg) continue\n externalTotal++\n const d = domMap.get(domain) ?? { links: 0, nofollow: 0, pages: new Set<string>() }\n d.links++\n if (e.nofollow) d.nofollow++\n d.pages.add(e.from)\n domMap.set(domain, d)\n }\n const externalDomains: ExtDomainRow[] = [...domMap.entries()]\n .map(([domain, d]) => ({ domain, links: d.links, nofollow: d.nofollow, pages: d.pages.size }))\n .sort((a, b) => b.links - a.links)\n\n const round = (n: number) => Math.round(n * 10) / 10\n return {\n summary: {\n internal: {\n totalLinks: internalEdges.length,\n pages, orphans, brokenInternal,\n avgInlinks: pages ? round(sumInlinks / pages) : 0,\n avgOutlinks: pages ? round(sumOutlinks / pages) : 0,\n distribution,\n topByInlinks,\n },\n external: {\n totalLinks: externalTotal,\n uniqueDomains: externalDomains.length,\n topDomains: externalDomains.slice(0, 20),\n },\n },\n externalDomains,\n }\n}\n\nexport function renderLinkReport(r: LinkReport): string {\n const s = r.summary\n const lines = [\n `## Link analysis`,\n `**Internal:** ${s.internal.totalLinks} links · ${s.internal.pages} pages · avg ${s.internal.avgInlinks} inlinks/page · ${s.internal.orphans} orphans · ${s.internal.brokenInternal} broken`,\n `**External:** ${s.external.totalLinks} links to ${s.external.uniqueDomains} domains`,\n ``,\n `### Inlink distribution`,\n `- 0 inlinks (orphans): ${s.internal.distribution.zero}`,\n `- 1–2: ${s.internal.distribution.oneToTwo}`,\n `- 3–10: ${s.internal.distribution.threeToTen}`,\n `- 11+: ${s.internal.distribution.elevenPlus}`,\n ``,\n `### Top 20 internal pages by inlinks`,\n `| inlinks | out (int/ext) | URL |`,\n `|---|---|---|`,\n ...s.internal.topByInlinks.map(p => `| ${p.inlinks} | ${p.outlinksInternal}/${p.outlinksExternal} | ${p.url} |`),\n ``,\n `### Top 20 external domains by links`,\n `| links | nofollow | from pages | domain |`,\n `|---|---|---|---|`,\n ...s.external.topDomains.map(d => `| ${d.links} | ${d.nofollow} | ${d.pages} | ${d.domain} |`),\n ]\n return lines.join('\\n')\n}\n","import type { PageData } from './site-extractor.js'\nimport type { PageLinkMetrics } from './seo-link-graph.js'\n\nexport interface IssueGroup { count: number; urls: string[] }\nexport type IssueReport = Record<string, IssueGroup>\n\nconst THIN_WORDS = 200\nconst TITLE_MAX = 60\nconst TITLE_PX_MAX = 561\nconst TITLE_MIN = 30\nconst META_MAX = 155\nconst META_MIN = 70\nconst H1_MAX = 70\nconst URL_MAX = 115\n\nfunction dupes<T>(pages: PageData[], key: (p: PageData) => T | null | undefined): string[] {\n const groups = new Map<string, string[]>()\n for (const p of pages) {\n const k = key(p)\n if (k === null || k === undefined || k === '') continue\n const ks = String(k)\n if (!groups.has(ks)) groups.set(ks, [])\n groups.get(ks)!.push(p.url)\n }\n return [...groups.values()].filter(g => g.length > 1).flat()\n}\n\nexport function computeIssues(pages: PageData[], metrics: Map<string, PageLinkMetrics>, precomputedBrokenLinkPages?: Set<string>): IssueReport {\n const group = (urls: string[]): IssueGroup => ({ count: urls.length, urls: urls.slice(0, 200) })\n const where = (fn: (p: PageData) => boolean) => pages.filter(fn).map(p => p.url)\n const extracted = (p: PageData) => p.extractionStatus !== 'failed'\n const ok = (p: PageData) => extracted(p) && p.status === 200\n const extractedUrls = new Set(pages.filter(extracted).map(page => page.url))\n const pathname = (u: string) => { try { return new URL(u).pathname } catch { return '' } }\n\n const report: IssueReport = {\n 'title.missing': group(where(p => ok(p) && !p.title)),\n 'title.duplicate': group(dupes(pages.filter(ok), p => p.title)),\n 'title.tooLong': group(where(p => (p.titleLength ?? 0) > TITLE_MAX || (p.titlePixels ?? 0) > TITLE_PX_MAX)),\n 'title.tooShort': group(where(p => p.title != null && (p.titleLength ?? 0) < TITLE_MIN)),\n 'title.sameAsH1': group(where(p => !!p.title && !!p.h1 && p.title.trim() === p.h1.trim())),\n 'meta.missing': group(where(p => ok(p) && !p.metaDescription)),\n 'meta.duplicate': group(dupes(pages.filter(ok), p => p.metaDescription)),\n 'meta.tooLong': group(where(p => (p.metaDescLength ?? 0) > META_MAX)),\n 'meta.tooShort': group(where(p => p.metaDescription != null && (p.metaDescLength ?? 0) < META_MIN)),\n 'h1.missing': group(where(p => ok(p) && !p.h1)),\n 'h1.multiple': group(where(p => !!p.h1_2)),\n 'h1.duplicate': group(dupes(pages.filter(ok), p => p.h1)),\n 'h1.tooLong': group(where(p => (p.h1?.length ?? 0) > H1_MAX)),\n 'h2.missing': group(where(p => ok(p) && p.h2Count === 0)),\n // A transport/extraction failure is not an SEO conclusion about the URL.\n // Keep it in a dedicated crawl bucket so empty records cannot become a\n // wall of false missing-title/H1/non-indexable findings.\n 'crawl.extractionFailed': group(where(p => !extracted(p))),\n 'indexability.nonIndexable': group(where(p => extracted(p) && !p.indexable)),\n 'indexability.noindex': group(where(p => p.indexabilityReason === 'noindex')),\n 'canonical.missing': group(where(p => ok(p) && !p.canonicalUrl)),\n 'canonical.canonicalised': group(where(p => p.indexabilityReason === 'canonicalised')),\n 'response.broken4xx': group(where(p => (p.status ?? 0) >= 400 && (p.status ?? 0) < 500)),\n 'response.error5xx': group(where(p => (p.status ?? 0) >= 500)),\n 'response.redirect3xx': group(where(p => (p.status ?? 0) >= 300 && (p.status ?? 0) < 400)),\n 'response.noResponse': group(where(p => p.status === null)),\n 'content.thin': group(where(p => ok(p) && p.wordCount > 0 && p.wordCount < THIN_WORDS)),\n 'content.exactDuplicate': group(dupes(pages.filter(p => ok(p) && p.contentHash), p => p.contentHash)),\n 'images.missingAlt': group(where(p => extracted(p) && p.imagesMissingAlt > 0)),\n 'schema.missing': group(where(p => ok(p) && p.schemaTypes.length === 0)),\n 'url.tooLong': group(where(p => p.url.length > URL_MAX)),\n 'url.uppercase': group(where(p => /[A-Z]/.test(pathname(p.url)))),\n 'url.underscores': group(where(p => pathname(p.url).includes('_'))),\n 'links.orphan': group([...metrics.values()].filter(m => m.orphan && extractedUrls.has(m.url)).map(m => m.url)),\n }\n\n let brokenLinkPages = precomputedBrokenLinkPages\n if (!brokenLinkPages) {\n const normUrl = (u: string) => { try { const x = new URL(u); x.hash = ''; return (x.origin + x.pathname).replace(/\\/+$/, '') + x.search } catch { return u.replace(/#.*$/, '').replace(/\\/+$/, '') } }\n const statusByUrl = new Map(pages.map(p => [normUrl(p.url), p.status]))\n brokenLinkPages = new Set<string>()\n for (const p of pages) {\n for (const l of p.outlinks ?? []) {\n if (!l.internal) continue\n const st = statusByUrl.get(normUrl(l.href))\n if (st != null && st >= 400) brokenLinkPages.add(p.url)\n }\n }\n }\n report['links.brokenInternal'] = group([...brokenLinkPages])\n\n return report\n}\n\nexport function renderIssueReport(siteUrl: string, pages: PageData[], report: IssueReport, metrics: Map<string, PageLinkMetrics>): string {\n const total = pages.length\n const successful = pages.filter(page => page.extractionStatus !== 'failed').length\n const failed = total - successful\n const sev = (key: string): string => (\n key.startsWith('response.broken') || key.startsWith('response.error') || key.startsWith('links.broken') ? '🔴'\n : key.startsWith('title.missing') || key.startsWith('h1.missing') || key.startsWith('indexability') || key.startsWith('canonical.missing') ? '🟠'\n : '🟡'\n )\n const rows = Object.entries(report)\n .filter(([, g]) => g.count > 0)\n .sort((a, b) => b[1].count - a[1].count)\n .map(([k, g]) => `| ${sev(k)} | \\`${k}\\` | ${g.count} | ${g.urls.slice(0, 3).join(' · ')}${g.urls.length > 3 ? ' …' : ''} |`)\n\n const depths = [...metrics.values()].map(m => m.crawlDepth).filter((d): d is number => d != null)\n const maxDepth = depths.length ? Math.max(...depths) : 0\n const extractedUrls = new Set(pages.filter(page => page.extractionStatus !== 'failed').map(page => page.url))\n const orphans = [...metrics.values()].filter(m => m.orphan && extractedUrls.has(m.url)).length\n const topLinked = [...metrics.values()].sort((a, b) => b.inlinks - a.inlinks).slice(0, 5)\n\n return [\n `# SEO Crawl Report: ${siteUrl}`,\n `**${total} pages attempted** · ${successful} extracted · ${failed} failed · max crawl depth ${maxDepth} · ${orphans} orphan page(s)`,\n `\\n## Issues\\n| | Issue | Count | Examples |\\n|---|-------|-------|----------|\\n${rows.join('\\n') || '| ✅ | none | 0 | — |'}`,\n `\\n## Most-linked pages\\n${topLinked.map(m => `- ${m.inlinks} inlinks · depth ${m.crawlDepth ?? '—'} · ${m.url}`).join('\\n')}`,\n `\\n_Thresholds: title ${TITLE_MAX}ch/${TITLE_PX_MAX}px, meta ${META_MAX}ch, H1 ${H1_MAX}ch, thin <${THIN_WORDS} words, URL ${URL_MAX}ch. Pixel widths are estimates._`,\n ].join('\\n')\n}\n","import { createHash } from 'node:crypto'\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { createWriteStream } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\nimport { Readable, Transform } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\n\nexport interface PrivateArtifactPolicy {\n prefix: string\n artifactTtlMs: number\n downloadTtlMs: number\n token?: string | null\n localBaseDir?: string\n hosted?: boolean\n}\n\nexport interface PrivateArtifactPointer {\n artifactId: string\n filename: string\n contentType: string\n bytes: number\n sha256: string\n expiresAt: string\n downloadUrl: string | null\n downloadUrlExpiresAt: string | null\n}\n\nexport interface PrivateArtifactWindow {\n text: string\n totalBytes: number\n nextOffset: number | null\n}\n\nfunction hostedByEnvironment(): boolean {\n return process.env.VERCEL === '1' || process.env.NODE_ENV === 'production'\n}\n\nfunction policyHosted(policy: PrivateArtifactPolicy): boolean {\n return policy.hosted ?? hostedByEnvironment()\n}\n\nfunction policyLocalBaseDir(policy: PrivateArtifactPolicy): string {\n return policy.localBaseDir?.trim()\n || process.env.MCP_SCRAPER_OUTPUT_DIR?.trim()\n || join(homedir(), 'Downloads', 'mcp-scraper')\n}\n\nfunction normalizedPrefix(prefix: string): string {\n const normalized = prefix.replace(/^\\/+/, '').replace(/\\/+$/, '')\n if (!normalized || normalized.includes('..') || !/^[a-zA-Z0-9/_-]+$/.test(normalized)) {\n throw new Error('private artifact prefix is invalid')\n }\n return `${normalized}/`\n}\n\nfunction requiredSafeSegment(value: string, field: string): string {\n const trimmed = value.trim()\n if (!/^[a-zA-Z0-9_-]{1,160}$/.test(trimmed)) {\n throw new Error(`${field} must contain only letters, numbers, underscores, or hyphens`)\n }\n return trimmed\n}\n\nfunction requiredSafeArtifactKey(value: string): string {\n const trimmed = value.trim()\n if (!/^[a-zA-Z0-9_.-]{1,200}$/.test(trimmed) || trimmed === '.' || trimmed === '..') {\n throw new Error('private artifact key is invalid')\n }\n return trimmed\n}\n\nfunction safeDownloadFilename(value: string): string {\n const normalized = value.trim().replace(/[^a-zA-Z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '')\n return (normalized || 'artifact').slice(0, 160)\n}\n\nfunction createdAtMs(value: Date | string | number): number {\n const normalized = typeof value === 'string' && /^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/.test(value)\n ? `${value.replace(' ', 'T')}Z`\n : value\n const timestamp = normalized instanceof Date\n ? normalized.getTime()\n : typeof normalized === 'number'\n ? normalized\n : new Date(normalized).getTime()\n if (!Number.isSafeInteger(timestamp) || timestamp <= 0) throw new Error('private artifact createdAt is invalid')\n return timestamp\n}\n\nfunction validatePolicy(policy: PrivateArtifactPolicy): Required<Pick<PrivateArtifactPolicy, 'prefix' | 'artifactTtlMs' | 'downloadTtlMs'>> {\n if (!Number.isSafeInteger(policy.artifactTtlMs) || policy.artifactTtlMs <= 0) {\n throw new Error('private artifact TTL must be a positive integer')\n }\n if (!Number.isSafeInteger(policy.downloadTtlMs) || policy.downloadTtlMs <= 0) {\n throw new Error('private artifact download TTL must be a positive integer')\n }\n return {\n prefix: normalizedPrefix(policy.prefix),\n artifactTtlMs: policy.artifactTtlMs,\n downloadTtlMs: policy.downloadTtlMs,\n }\n}\n\nfunction artifactTimestamp(artifactId: string, prefix: string): number | null {\n const normalized = normalizedPrefix(prefix)\n if (!artifactId.startsWith(normalized)) return null\n const filename = artifactId.split('/').at(-1) ?? ''\n const match = filename.match(/^(\\d{13})-/)\n if (!match) return null\n const timestamp = Number(match[1])\n return Number.isSafeInteger(timestamp) && timestamp > 0 ? timestamp : null\n}\n\nexport function privateArtifactOwnerId(artifactId: string, prefix: string): string | null {\n const normalized = normalizedPrefix(prefix)\n if (!artifactId.startsWith(normalized) || artifactId.includes('..')) return null\n const rest = artifactId.slice(normalized.length)\n const segments = rest.split('/')\n if (segments.length !== 2) return null\n try {\n return requiredSafeSegment(segments[0] ?? '', 'ownerId')\n } catch {\n return null\n }\n}\n\nexport function privateArtifactExpiresAt(artifactId: string, policy: PrivateArtifactPolicy): Date | null {\n const validated = validatePolicy(policy)\n if (privateArtifactOwnerId(artifactId, validated.prefix) === null) return null\n const timestamp = artifactTimestamp(artifactId, validated.prefix)\n return timestamp === null ? null : new Date(timestamp + validated.artifactTtlMs)\n}\n\nasync function signedDownloadUrl(pathname: string, expiresAt: Date, policy: PrivateArtifactPolicy): Promise<{\n url: string\n expiresAt: string\n} | null> {\n const token = policy.token?.trim()\n if (!token) return null\n const validUntil = Math.min(Date.now() + policy.downloadTtlMs, expiresAt.getTime())\n if (validUntil <= Date.now()) return null\n const { issueSignedToken, presignUrl } = await import('@vercel/blob')\n const signedToken = await issueSignedToken({\n token,\n pathname,\n operations: ['get'],\n validUntil,\n })\n const { presignedUrl } = await presignUrl(signedToken, {\n access: 'private',\n operation: 'get',\n pathname,\n validUntil,\n })\n return { url: presignedUrl, expiresAt: new Date(validUntil).toISOString() }\n}\n\nexport async function createPrivateArtifact(args: {\n policy: PrivateArtifactPolicy\n ownerId: string\n artifactKey: string\n createdAt: Date | string | number\n filename: string\n contentType: string\n content: string | Buffer\n}): Promise<PrivateArtifactPointer> {\n const policy = { ...args.policy, ...validatePolicy(args.policy) }\n const ownerId = requiredSafeSegment(args.ownerId, 'ownerId')\n const artifactKey = requiredSafeArtifactKey(args.artifactKey)\n const timestamp = createdAtMs(args.createdAt)\n const requestedPathname = `${policy.prefix}${ownerId}/${timestamp}-${artifactKey}`\n const body = Buffer.isBuffer(args.content) ? args.content : Buffer.from(args.content)\n const bytes = body.length\n const sha256 = createHash('sha256').update(body).digest('hex')\n const expiresAt = new Date(timestamp + policy.artifactTtlMs)\n const token = policy.token?.trim() || null\n\n let artifactId = requestedPathname\n if (token) {\n const { put } = await import('@vercel/blob')\n const stored = await put(requestedPathname, body, {\n access: 'private',\n token,\n contentType: args.contentType,\n addRandomSuffix: false,\n allowOverwrite: true,\n cacheControlMaxAge: 60,\n multipart: bytes > 100 * 1024 * 1024,\n })\n artifactId = stored.pathname\n } else {\n if (policyHosted(policy)) throw new Error('private_artifact_blob_not_configured')\n const path = join(policyLocalBaseDir(policy), 'blobs', requestedPathname)\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, body)\n }\n\n const download = await signedDownloadUrl(artifactId, expiresAt, policy)\n return {\n artifactId,\n filename: safeDownloadFilename(args.filename),\n contentType: args.contentType,\n bytes,\n sha256,\n expiresAt: expiresAt.toISOString(),\n downloadUrl: download?.url ?? null,\n downloadUrlExpiresAt: download?.expiresAt ?? null,\n }\n}\n\nexport async function createPrivateArtifactFromStream(args: {\n policy: PrivateArtifactPolicy\n ownerId: string\n artifactKey: string\n createdAt: Date | string | number\n filename: string\n contentType: string\n content: Readable\n}): Promise<PrivateArtifactPointer> {\n const policy = { ...args.policy, ...validatePolicy(args.policy) }\n const ownerId = requiredSafeSegment(args.ownerId, 'ownerId')\n const artifactKey = requiredSafeArtifactKey(args.artifactKey)\n const timestamp = createdAtMs(args.createdAt)\n const requestedPathname = `${policy.prefix}${ownerId}/${timestamp}-${artifactKey}`\n const expiresAt = new Date(timestamp + policy.artifactTtlMs)\n const token = policy.token?.trim() || null\n const hash = createHash('sha256')\n let bytes = 0\n const meter = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n bytes += chunk.length\n hash.update(chunk)\n callback(null, chunk)\n },\n })\n\n let artifactId = requestedPathname\n if (token) {\n const { put } = await import('@vercel/blob')\n const pumping = pipeline(args.content, meter)\n try {\n const stored = await put(requestedPathname, meter, {\n access: 'private',\n token,\n contentType: args.contentType,\n addRandomSuffix: false,\n allowOverwrite: true,\n cacheControlMaxAge: 60,\n multipart: true,\n })\n await pumping\n artifactId = stored.pathname\n } catch (error) {\n args.content.destroy(error instanceof Error ? error : new Error(String(error)))\n meter.destroy(error instanceof Error ? error : new Error(String(error)))\n await pumping.catch(() => undefined)\n throw error\n }\n } else {\n if (policyHosted(policy)) throw new Error('private_artifact_blob_not_configured')\n const path = join(policyLocalBaseDir(policy), 'blobs', requestedPathname)\n await mkdir(dirname(path), { recursive: true })\n await pipeline(args.content, meter, createWriteStream(path))\n }\n\n const sha256 = hash.digest('hex')\n const download = await signedDownloadUrl(artifactId, expiresAt, policy)\n return {\n artifactId,\n filename: safeDownloadFilename(args.filename),\n contentType: args.contentType,\n bytes,\n sha256,\n expiresAt: expiresAt.toISOString(),\n downloadUrl: download?.url ?? null,\n downloadUrlExpiresAt: download?.expiresAt ?? null,\n }\n}\n\nexport async function renewPrivateArtifactDownload(args: {\n policy: PrivateArtifactPolicy\n artifactId: string\n ownerId: string\n}): Promise<{ downloadUrl: string; downloadUrlExpiresAt: string; expiresAt: string } | null> {\n if (privateArtifactOwnerId(args.artifactId, args.policy.prefix) !== args.ownerId) return null\n const expiresAt = privateArtifactExpiresAt(args.artifactId, args.policy)\n if (!expiresAt || expiresAt.getTime() <= Date.now()) return null\n const download = await signedDownloadUrl(args.artifactId, expiresAt, args.policy)\n if (!download) return null\n return {\n downloadUrl: download.url,\n downloadUrlExpiresAt: download.expiresAt,\n expiresAt: expiresAt.toISOString(),\n }\n}\n\nasync function streamToBuffer(stream: ReadableStream<Uint8Array>): Promise<Buffer> {\n const reader = stream.getReader()\n const chunks: Buffer[] = []\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (value) chunks.push(Buffer.from(value))\n }\n } finally {\n reader.releaseLock()\n }\n return Buffer.concat(chunks)\n}\n\nexport async function readPrivateArtifactWindow(args: {\n policy: PrivateArtifactPolicy\n artifactId: string\n offset: number\n maxBytes: number\n}): Promise<PrivateArtifactWindow | null> {\n const expiresAt = privateArtifactExpiresAt(args.artifactId, args.policy)\n if (!expiresAt || expiresAt.getTime() <= Date.now()) return null\n if (!Number.isSafeInteger(args.offset) || args.offset < 0) throw new Error('private artifact offset is invalid')\n if (!Number.isSafeInteger(args.maxBytes) || args.maxBytes <= 0) throw new Error('private artifact maxBytes is invalid')\n\n const token = args.policy.token?.trim() || null\n let buffer: Buffer\n if (token) {\n const { get } = await import('@vercel/blob')\n const result = await get(args.artifactId, { access: 'private', token, useCache: false })\n if (!result || result.statusCode !== 200) return null\n buffer = await streamToBuffer(result.stream)\n } else {\n if (policyHosted(args.policy)) return null\n try {\n buffer = await readFile(join(policyLocalBaseDir(args.policy), 'blobs', args.artifactId))\n } catch {\n return null\n }\n }\n\n const totalBytes = buffer.length\n const end = Math.min(totalBytes, args.offset + args.maxBytes)\n return {\n text: buffer.subarray(args.offset, end).toString('utf8'),\n totalBytes,\n nextOffset: end < totalBytes ? end : null,\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getDb
|
|
3
|
+
} from "./chunk-GUVKHCKE.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/browser-service-env.ts
|
|
6
|
+
function browserServiceApiKey() {
|
|
7
|
+
const value = (process.env.BROWSER_SERVICE_API_KEY ?? process.env.KERNEL_API_KEY)?.trim();
|
|
8
|
+
return value || void 0;
|
|
9
|
+
}
|
|
10
|
+
function browserServiceProxyId() {
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
function browserServiceProfileName() {
|
|
14
|
+
const value = (process.env.BROWSER_AGENT_PROFILE_NAME ?? process.env.BROWSER_SERVICE_PROFILE_NAME ?? process.env.KERNEL_BROWSER_PROFILE_NAME ?? process.env.KERNEL_PROFILE_NAME)?.trim();
|
|
15
|
+
return value || void 0;
|
|
16
|
+
}
|
|
17
|
+
function browserServiceProfileSaveChanges() {
|
|
18
|
+
const value = (process.env.BROWSER_AGENT_PROFILE_SAVE_CHANGES ?? process.env.BROWSER_SERVICE_PROFILE_SAVE_CHANGES ?? process.env.KERNEL_BROWSER_PROFILE_SAVE_CHANGES ?? process.env.KERNEL_PROFILE_SAVE_CHANGES)?.trim().toLowerCase();
|
|
19
|
+
if (!value) return void 0;
|
|
20
|
+
if (["1", "true", "yes", "on"].includes(value)) return true;
|
|
21
|
+
if (["0", "false", "no", "off"].includes(value)) return false;
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/api/cost-context.ts
|
|
26
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
27
|
+
var storage = new AsyncLocalStorage();
|
|
28
|
+
function runWithCostContext(ctx, fn) {
|
|
29
|
+
return storage.run(ctx, fn);
|
|
30
|
+
}
|
|
31
|
+
function currentCostContext() {
|
|
32
|
+
return storage.getStore();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/api/cost-telemetry.ts
|
|
36
|
+
import { randomUUID } from "crypto";
|
|
37
|
+
|
|
38
|
+
// src/api/cost-rates.ts
|
|
39
|
+
var KERNEL_HEADLESS_USD_PER_SEC = 166667e-10;
|
|
40
|
+
var KERNEL_HEADFUL_USD_PER_SEC = 1333336e-10;
|
|
41
|
+
var HEADLESS_OPS = /* @__PURE__ */ new Set(["serp", "fb_search", "fb_ad", "instagram"]);
|
|
42
|
+
var FAL_WIZPER_USD_PER_COMPUTE_SEC = 111e-5;
|
|
43
|
+
var FAL_WIZPER_WALLCLOCK_TO_COMPUTE_SEC_FACTOR = 4;
|
|
44
|
+
var DEEPINFRA_QWEN_USD_PER_1K_OUT_TOKENS = 4e-4;
|
|
45
|
+
function envRate(name, fallback) {
|
|
46
|
+
const raw = process.env[name]?.trim();
|
|
47
|
+
if (!raw) return fallback;
|
|
48
|
+
const parsed = Number(raw);
|
|
49
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
50
|
+
}
|
|
51
|
+
var NANGO_USD_PER_CONNECTION_MONTH = envRate("NANGO_USD_PER_CONNECTION_MONTH", 1);
|
|
52
|
+
var NANGO_USD_PER_FUNCTION_RUN = envRate("NANGO_USD_PER_FUNCTION_RUN", 1e-4);
|
|
53
|
+
var NANGO_USD_PER_PROXY_REQUEST = envRate("NANGO_USD_PER_PROXY_REQUEST", 1e-4);
|
|
54
|
+
var NANGO_USD_PER_COMPUTE_SEC = envRate("NANGO_USD_PER_COMPUTE_SEC", 2e-4);
|
|
55
|
+
function kernelCostUsd(ms, headful) {
|
|
56
|
+
const sec = Math.max(0, ms) / 1e3;
|
|
57
|
+
return sec * (headful ? KERNEL_HEADFUL_USD_PER_SEC : KERNEL_HEADLESS_USD_PER_SEC);
|
|
58
|
+
}
|
|
59
|
+
function vendorCostUsd(vendor, units) {
|
|
60
|
+
if (vendor === "fal_wizper") {
|
|
61
|
+
const estimatedComputeSec = Math.max(0, units) / FAL_WIZPER_WALLCLOCK_TO_COMPUTE_SEC_FACTOR;
|
|
62
|
+
return estimatedComputeSec * FAL_WIZPER_USD_PER_COMPUTE_SEC;
|
|
63
|
+
}
|
|
64
|
+
if (vendor === "deepinfra_qwen") return Math.max(0, units) / 1e3 * DEEPINFRA_QWEN_USD_PER_1K_OUT_TOKENS;
|
|
65
|
+
if (vendor === "openrouter") return Math.max(0, units);
|
|
66
|
+
if (vendor === "mcp_memory_video") return Math.max(0, units);
|
|
67
|
+
if (vendor === "mcp_memory_ai") return Math.max(0, units);
|
|
68
|
+
if (vendor === "nango_connection") return Math.max(0, units) * NANGO_USD_PER_CONNECTION_MONTH;
|
|
69
|
+
if (vendor === "nango_function_run") return Math.max(0, units) * NANGO_USD_PER_FUNCTION_RUN;
|
|
70
|
+
if (vendor === "nango_proxy_request") return Math.max(0, units) * NANGO_USD_PER_PROXY_REQUEST;
|
|
71
|
+
if (vendor === "nango_compute") return Math.max(0, units) * NANGO_USD_PER_COMPUTE_SEC;
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/api/cost-telemetry.ts
|
|
76
|
+
var _ready = false;
|
|
77
|
+
var _migrationPromise = null;
|
|
78
|
+
async function migrateCostTelemetry() {
|
|
79
|
+
if (_ready) return;
|
|
80
|
+
if (_migrationPromise) return _migrationPromise;
|
|
81
|
+
_migrationPromise = runCostTelemetryMigration().finally(() => {
|
|
82
|
+
_migrationPromise = null;
|
|
83
|
+
});
|
|
84
|
+
return _migrationPromise;
|
|
85
|
+
}
|
|
86
|
+
async function runCostTelemetryMigration() {
|
|
87
|
+
const db = getDb();
|
|
88
|
+
const existing = await db.execute(`
|
|
89
|
+
SELECT
|
|
90
|
+
(SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN ('kernel_session_log', 'vendor_usage_log', 'cost_probe_runs')) = 3
|
|
91
|
+
AND (SELECT COUNT(*) FROM pragma_table_info('kernel_session_log') WHERE name IN ('proxy_source', 'proxy_type', 'method')) = 3
|
|
92
|
+
AND (SELECT COUNT(*) FROM pragma_table_info('vendor_usage_log') WHERE name = 'method') = 1
|
|
93
|
+
AND (SELECT COUNT(*) FROM pragma_table_info('cost_probe_runs') WHERE name IN ('units', 'unit_type', 'mode')) = 3
|
|
94
|
+
AS ready
|
|
95
|
+
`);
|
|
96
|
+
if (Number(existing.rows[0]?.ready ?? 0) === 1) {
|
|
97
|
+
_ready = true;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await db.execute(`
|
|
101
|
+
CREATE TABLE IF NOT EXISTS kernel_session_log (
|
|
102
|
+
id TEXT PRIMARY KEY,
|
|
103
|
+
kernel_session_id TEXT,
|
|
104
|
+
op TEXT,
|
|
105
|
+
source TEXT NOT NULL,
|
|
106
|
+
probe_run_id TEXT,
|
|
107
|
+
user_id INTEGER,
|
|
108
|
+
stealth INTEGER,
|
|
109
|
+
headless_sent INTEGER,
|
|
110
|
+
proxy_used INTEGER,
|
|
111
|
+
fallback INTEGER NOT NULL DEFAULT 0,
|
|
112
|
+
opened_at TEXT,
|
|
113
|
+
closed_at TEXT,
|
|
114
|
+
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
115
|
+
est_cost_usd_headless REAL NOT NULL DEFAULT 0,
|
|
116
|
+
est_cost_usd_headful REAL NOT NULL DEFAULT 0,
|
|
117
|
+
error TEXT,
|
|
118
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
119
|
+
)
|
|
120
|
+
`);
|
|
121
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_op ON kernel_session_log(op)`);
|
|
122
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_probe ON kernel_session_log(probe_run_id)`);
|
|
123
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_created ON kernel_session_log(created_at)`);
|
|
124
|
+
try {
|
|
125
|
+
await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_source TEXT`);
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_type TEXT`);
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN method TEXT`);
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
await db.execute(`
|
|
137
|
+
CREATE TABLE IF NOT EXISTS vendor_usage_log (
|
|
138
|
+
id TEXT PRIMARY KEY,
|
|
139
|
+
op TEXT,
|
|
140
|
+
probe_run_id TEXT,
|
|
141
|
+
user_id INTEGER,
|
|
142
|
+
vendor TEXT NOT NULL,
|
|
143
|
+
model TEXT,
|
|
144
|
+
units REAL NOT NULL DEFAULT 0,
|
|
145
|
+
unit_type TEXT,
|
|
146
|
+
est_cost_usd REAL NOT NULL DEFAULT 0,
|
|
147
|
+
error TEXT,
|
|
148
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
149
|
+
)
|
|
150
|
+
`);
|
|
151
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_op ON vendor_usage_log(op)`);
|
|
152
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_probe ON vendor_usage_log(probe_run_id)`);
|
|
153
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_created ON vendor_usage_log(created_at)`);
|
|
154
|
+
try {
|
|
155
|
+
await db.execute(`ALTER TABLE vendor_usage_log ADD COLUMN method TEXT`);
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
await db.execute(`
|
|
159
|
+
CREATE TABLE IF NOT EXISTS cost_probe_runs (
|
|
160
|
+
id TEXT PRIMARY KEY,
|
|
161
|
+
tool TEXT NOT NULL,
|
|
162
|
+
target TEXT,
|
|
163
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
164
|
+
wall_ms INTEGER NOT NULL DEFAULT 0,
|
|
165
|
+
success INTEGER NOT NULL DEFAULT 0,
|
|
166
|
+
error TEXT,
|
|
167
|
+
http_only INTEGER NOT NULL DEFAULT 0,
|
|
168
|
+
kernel_used INTEGER NOT NULL DEFAULT 0,
|
|
169
|
+
kernel_sessions INTEGER NOT NULL DEFAULT 0,
|
|
170
|
+
kernel_seconds_total REAL NOT NULL DEFAULT 0,
|
|
171
|
+
kernel_tier_observed TEXT,
|
|
172
|
+
est_kernel_cost_usd_headless REAL NOT NULL DEFAULT 0,
|
|
173
|
+
est_kernel_cost_usd_headful REAL NOT NULL DEFAULT 0,
|
|
174
|
+
vendor_cost_usd REAL NOT NULL DEFAULT 0,
|
|
175
|
+
charged_credits REAL NOT NULL DEFAULT 0,
|
|
176
|
+
margin_usd_headless_starter REAL NOT NULL DEFAULT 0,
|
|
177
|
+
margin_usd_headful_starter REAL NOT NULL DEFAULT 0,
|
|
178
|
+
notes TEXT
|
|
179
|
+
)
|
|
180
|
+
`);
|
|
181
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS cost_probe_runs_tool ON cost_probe_runs(tool)`);
|
|
182
|
+
try {
|
|
183
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN units REAL`);
|
|
184
|
+
} catch {
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN unit_type TEXT`);
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN mode TEXT`);
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
194
|
+
_ready = true;
|
|
195
|
+
}
|
|
196
|
+
async function recordKernelSession(r) {
|
|
197
|
+
try {
|
|
198
|
+
await migrateCostTelemetry();
|
|
199
|
+
const ctx = currentCostContext();
|
|
200
|
+
const durationMs = Math.max(0, r.closedAtMs - r.openedAtMs);
|
|
201
|
+
const db = getDb();
|
|
202
|
+
await db.execute({
|
|
203
|
+
sql: `INSERT INTO kernel_session_log
|
|
204
|
+
(id, kernel_session_id, op, source, probe_run_id, user_id, stealth, headless_sent, proxy_used, proxy_source, proxy_type, fallback, opened_at, closed_at, duration_ms, est_cost_usd_headless, est_cost_usd_headful, error, method)
|
|
205
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
206
|
+
args: [
|
|
207
|
+
randomUUID(),
|
|
208
|
+
r.kernelSessionId ?? null,
|
|
209
|
+
ctx?.op ?? null,
|
|
210
|
+
r.source,
|
|
211
|
+
ctx?.probeRunId ?? null,
|
|
212
|
+
ctx?.userId ?? null,
|
|
213
|
+
boolToInt(r.stealth),
|
|
214
|
+
boolToInt(r.headlessSent),
|
|
215
|
+
boolToInt(r.proxyUsed),
|
|
216
|
+
r.proxySource ?? null,
|
|
217
|
+
r.proxyType ?? null,
|
|
218
|
+
r.fallback ? 1 : 0,
|
|
219
|
+
new Date(r.openedAtMs).toISOString(),
|
|
220
|
+
new Date(r.closedAtMs).toISOString(),
|
|
221
|
+
durationMs,
|
|
222
|
+
kernelCostUsd(durationMs, false),
|
|
223
|
+
kernelCostUsd(durationMs, true),
|
|
224
|
+
r.error ?? null,
|
|
225
|
+
ctx?.subOp ?? null
|
|
226
|
+
]
|
|
227
|
+
});
|
|
228
|
+
} catch (err) {
|
|
229
|
+
console.warn("[cost-telemetry] recordKernelSession failed:", err instanceof Error ? err.message : String(err));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async function recordVendorUsage(r) {
|
|
233
|
+
try {
|
|
234
|
+
await migrateCostTelemetry();
|
|
235
|
+
const ctx = currentCostContext();
|
|
236
|
+
const db = getDb();
|
|
237
|
+
await db.execute({
|
|
238
|
+
sql: `INSERT INTO vendor_usage_log
|
|
239
|
+
(id, op, probe_run_id, user_id, vendor, model, units, unit_type, est_cost_usd, error, method)
|
|
240
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?)`,
|
|
241
|
+
args: [
|
|
242
|
+
randomUUID(),
|
|
243
|
+
ctx?.op ?? null,
|
|
244
|
+
ctx?.probeRunId ?? null,
|
|
245
|
+
ctx?.userId ?? null,
|
|
246
|
+
r.vendor,
|
|
247
|
+
r.model ?? null,
|
|
248
|
+
r.units,
|
|
249
|
+
r.unitType,
|
|
250
|
+
vendorCostUsd(r.vendor, r.units),
|
|
251
|
+
r.error ?? null,
|
|
252
|
+
ctx?.subOp ?? null
|
|
253
|
+
]
|
|
254
|
+
});
|
|
255
|
+
} catch (err) {
|
|
256
|
+
console.warn("[cost-telemetry] recordVendorUsage failed:", err instanceof Error ? err.message : String(err));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function boolToInt(v) {
|
|
260
|
+
if (v === null || v === void 0) return null;
|
|
261
|
+
return v ? 1 : 0;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export {
|
|
265
|
+
browserServiceApiKey,
|
|
266
|
+
browserServiceProxyId,
|
|
267
|
+
browserServiceProfileName,
|
|
268
|
+
browserServiceProfileSaveChanges,
|
|
269
|
+
runWithCostContext,
|
|
270
|
+
currentCostContext,
|
|
271
|
+
HEADLESS_OPS,
|
|
272
|
+
vendorCostUsd,
|
|
273
|
+
recordKernelSession,
|
|
274
|
+
recordVendorUsage
|
|
275
|
+
};
|
|
276
|
+
//# sourceMappingURL=chunk-3PIWJS6Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/browser-service-env.ts","../src/api/cost-context.ts","../src/api/cost-telemetry.ts","../src/api/cost-rates.ts"],"sourcesContent":["export function browserServiceApiKey(): string | undefined {\n const value = (process.env.BROWSER_SERVICE_API_KEY ?? process.env.KERNEL_API_KEY)?.trim()\n return value || undefined\n}\n\nexport function browserServiceProxyId(): string | undefined {\n return undefined\n}\n\nexport function browserServiceProfileName(): string | undefined {\n const value = (\n process.env.BROWSER_AGENT_PROFILE_NAME ??\n process.env.BROWSER_SERVICE_PROFILE_NAME ??\n process.env.KERNEL_BROWSER_PROFILE_NAME ??\n process.env.KERNEL_PROFILE_NAME\n )?.trim()\n return value || undefined\n}\n\nexport function browserServiceProfileSaveChanges(): boolean | undefined {\n const value = (\n process.env.BROWSER_AGENT_PROFILE_SAVE_CHANGES ??\n process.env.BROWSER_SERVICE_PROFILE_SAVE_CHANGES ??\n process.env.KERNEL_BROWSER_PROFILE_SAVE_CHANGES ??\n process.env.KERNEL_PROFILE_SAVE_CHANGES\n )?.trim().toLowerCase()\n if (!value) return undefined\n if (['1', 'true', 'yes', 'on'].includes(value)) return true\n if (['0', 'false', 'no', 'off'].includes(value)) return false\n return undefined\n}\n","import { AsyncLocalStorage } from 'node:async_hooks'\n\nexport interface CostContext {\n op: string\n probeRunId?: string | null\n userId?: number | null\n forceHeadless?: boolean\n forceHeadful?: boolean\n subOp?: string | null\n headlessSentOut?: { value: boolean | null }\n}\n\nconst storage = new AsyncLocalStorage<CostContext>()\n\nexport function runWithCostContext<T>(ctx: CostContext, fn: () => T): T {\n return storage.run(ctx, fn)\n}\n\nexport function currentCostContext(): CostContext | undefined {\n return storage.getStore()\n}\n","import { randomUUID } from 'node:crypto'\nimport { getDb } from './db.js'\nimport { currentCostContext } from './cost-context.js'\nimport { kernelCostUsd, vendorCostUsd } from './cost-rates.js'\n\nlet _ready = false\nlet _migrationPromise: Promise<void> | null = null\n\nexport async function migrateCostTelemetry(): Promise<void> {\n if (_ready) return\n if (_migrationPromise) return _migrationPromise\n _migrationPromise = runCostTelemetryMigration().finally(() => {\n _migrationPromise = null\n })\n return _migrationPromise\n}\n\nasync function runCostTelemetryMigration(): Promise<void> {\n const db = getDb()\n const existing = await db.execute(`\n SELECT\n (SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN ('kernel_session_log', 'vendor_usage_log', 'cost_probe_runs')) = 3\n AND (SELECT COUNT(*) FROM pragma_table_info('kernel_session_log') WHERE name IN ('proxy_source', 'proxy_type', 'method')) = 3\n AND (SELECT COUNT(*) FROM pragma_table_info('vendor_usage_log') WHERE name = 'method') = 1\n AND (SELECT COUNT(*) FROM pragma_table_info('cost_probe_runs') WHERE name IN ('units', 'unit_type', 'mode')) = 3\n AS ready\n `)\n if (Number(existing.rows[0]?.ready ?? 0) === 1) {\n _ready = true\n return\n }\n\n await db.execute(`\n CREATE TABLE IF NOT EXISTS kernel_session_log (\n id TEXT PRIMARY KEY,\n kernel_session_id TEXT,\n op TEXT,\n source TEXT NOT NULL,\n probe_run_id TEXT,\n user_id INTEGER,\n stealth INTEGER,\n headless_sent INTEGER,\n proxy_used INTEGER,\n fallback INTEGER NOT NULL DEFAULT 0,\n opened_at TEXT,\n closed_at TEXT,\n duration_ms INTEGER NOT NULL DEFAULT 0,\n est_cost_usd_headless REAL NOT NULL DEFAULT 0,\n est_cost_usd_headful REAL NOT NULL DEFAULT 0,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_op ON kernel_session_log(op)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_probe ON kernel_session_log(probe_run_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_created ON kernel_session_log(created_at)`)\n try { await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_source TEXT`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_type TEXT`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN method TEXT`) } catch { void 0 }\n await db.execute(`\n CREATE TABLE IF NOT EXISTS vendor_usage_log (\n id TEXT PRIMARY KEY,\n op TEXT,\n probe_run_id TEXT,\n user_id INTEGER,\n vendor TEXT NOT NULL,\n model TEXT,\n units REAL NOT NULL DEFAULT 0,\n unit_type TEXT,\n est_cost_usd REAL NOT NULL DEFAULT 0,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_op ON vendor_usage_log(op)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_probe ON vendor_usage_log(probe_run_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_created ON vendor_usage_log(created_at)`)\n try { await db.execute(`ALTER TABLE vendor_usage_log ADD COLUMN method TEXT`) } catch { void 0 }\n await db.execute(`\n CREATE TABLE IF NOT EXISTS cost_probe_runs (\n id TEXT PRIMARY KEY,\n tool TEXT NOT NULL,\n target TEXT,\n started_at TEXT NOT NULL DEFAULT (datetime('now')),\n wall_ms INTEGER NOT NULL DEFAULT 0,\n success INTEGER NOT NULL DEFAULT 0,\n error TEXT,\n http_only INTEGER NOT NULL DEFAULT 0,\n kernel_used INTEGER NOT NULL DEFAULT 0,\n kernel_sessions INTEGER NOT NULL DEFAULT 0,\n kernel_seconds_total REAL NOT NULL DEFAULT 0,\n kernel_tier_observed TEXT,\n est_kernel_cost_usd_headless REAL NOT NULL DEFAULT 0,\n est_kernel_cost_usd_headful REAL NOT NULL DEFAULT 0,\n vendor_cost_usd REAL NOT NULL DEFAULT 0,\n charged_credits REAL NOT NULL DEFAULT 0,\n margin_usd_headless_starter REAL NOT NULL DEFAULT 0,\n margin_usd_headful_starter REAL NOT NULL DEFAULT 0,\n notes TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS cost_probe_runs_tool ON cost_probe_runs(tool)`)\n try { await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN units REAL`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN unit_type TEXT`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN mode TEXT`) } catch { void 0 }\n _ready = true\n}\n\nexport interface KernelSessionRecord {\n kernelSessionId?: string | null\n source: string\n stealth?: boolean | null\n headlessSent?: boolean | null\n proxyUsed?: boolean | null\n proxySource?: string | null\n proxyType?: string | null\n fallback?: boolean\n openedAtMs: number\n closedAtMs: number\n error?: string | null\n}\n\nexport async function recordKernelSession(r: KernelSessionRecord): Promise<void> {\n try {\n await migrateCostTelemetry()\n const ctx = currentCostContext()\n const durationMs = Math.max(0, r.closedAtMs - r.openedAtMs)\n const db = getDb()\n await db.execute({\n sql: `INSERT INTO kernel_session_log\n (id, kernel_session_id, op, source, probe_run_id, user_id, stealth, headless_sent, proxy_used, proxy_source, proxy_type, fallback, opened_at, closed_at, duration_ms, est_cost_usd_headless, est_cost_usd_headful, error, method)\n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n args: [\n randomUUID(),\n r.kernelSessionId ?? null,\n ctx?.op ?? null,\n r.source,\n ctx?.probeRunId ?? null,\n ctx?.userId ?? null,\n boolToInt(r.stealth),\n boolToInt(r.headlessSent),\n boolToInt(r.proxyUsed),\n r.proxySource ?? null,\n r.proxyType ?? null,\n r.fallback ? 1 : 0,\n new Date(r.openedAtMs).toISOString(),\n new Date(r.closedAtMs).toISOString(),\n durationMs,\n kernelCostUsd(durationMs, false),\n kernelCostUsd(durationMs, true),\n r.error ?? null,\n ctx?.subOp ?? null,\n ],\n })\n } catch (err) {\n console.warn('[cost-telemetry] recordKernelSession failed:', err instanceof Error ? err.message : String(err))\n }\n}\n\nexport interface VendorUsageRecord {\n vendor: string\n model?: string | null\n units: number\n unitType: string\n error?: string | null\n}\n\nexport async function recordVendorUsage(r: VendorUsageRecord): Promise<void> {\n try {\n await migrateCostTelemetry()\n const ctx = currentCostContext()\n const db = getDb()\n await db.execute({\n sql: `INSERT INTO vendor_usage_log\n (id, op, probe_run_id, user_id, vendor, model, units, unit_type, est_cost_usd, error, method)\n VALUES (?,?,?,?,?,?,?,?,?,?,?)`,\n args: [\n randomUUID(),\n ctx?.op ?? null,\n ctx?.probeRunId ?? null,\n ctx?.userId ?? null,\n r.vendor,\n r.model ?? null,\n r.units,\n r.unitType,\n vendorCostUsd(r.vendor, r.units),\n r.error ?? null,\n ctx?.subOp ?? null,\n ],\n })\n } catch (err) {\n console.warn('[cost-telemetry] recordVendorUsage failed:', err instanceof Error ? err.message : String(err))\n }\n}\n\nexport interface ProbeRunRecord {\n id: string\n tool: string\n target?: string | null\n wallMs: number\n success: boolean\n error?: string | null\n httpOnly: boolean\n kernelUsed: boolean\n kernelSessions: number\n kernelSecondsTotal: number\n kernelTierObserved?: string | null\n estKernelCostUsdHeadless: number\n estKernelCostUsdHeadful: number\n vendorCostUsd: number\n chargedCredits: number\n marginUsdHeadlessStarter: number\n marginUsdHeadfulStarter: number\n units?: number | null\n unitType?: string | null\n mode?: string | null\n notes?: string | null\n}\n\nexport async function recordProbeRun(r: ProbeRunRecord): Promise<void> {\n await migrateCostTelemetry()\n const db = getDb()\n await db.execute({\n sql: `INSERT INTO cost_probe_runs\n (id, tool, target, wall_ms, success, error, http_only, kernel_used, kernel_sessions, kernel_seconds_total, kernel_tier_observed, est_kernel_cost_usd_headless, est_kernel_cost_usd_headful, vendor_cost_usd, charged_credits, margin_usd_headless_starter, margin_usd_headful_starter, units, unit_type, mode, notes)\n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n args: [\n r.id, r.tool, r.target ?? null, r.wallMs, r.success ? 1 : 0, r.error ?? null,\n r.httpOnly ? 1 : 0, r.kernelUsed ? 1 : 0, r.kernelSessions, r.kernelSecondsTotal,\n r.kernelTierObserved ?? null, r.estKernelCostUsdHeadless, r.estKernelCostUsdHeadful,\n r.vendorCostUsd, r.chargedCredits, r.marginUsdHeadlessStarter, r.marginUsdHeadfulStarter,\n r.units ?? null, r.unitType ?? null, r.mode ?? null, r.notes ?? null,\n ],\n })\n}\n\nfunction boolToInt(v: boolean | null | undefined): number | null {\n if (v === null || v === undefined) return null\n return v ? 1 : 0\n}\n","export const KERNEL_HEADLESS_USD_PER_SEC = 0.0000166667\nexport const KERNEL_HEADFUL_USD_PER_SEC = 0.0001333336\n\nexport const HEADLESS_OPS = new Set<string>(['serp', 'fb_search', 'fb_ad', 'instagram'])\n\nexport const FAL_WIZPER_USD_PER_COMPUTE_SEC = 0.00111\nexport const FAL_WIZPER_WALLCLOCK_TO_COMPUTE_SEC_FACTOR = 4 // calibrated from 2 real Fal invoices (4.36x, 6.18x wall-clock vs actual)\nexport const DEEPINFRA_QWEN_USD_PER_1K_OUT_TOKENS = 0.0004\n\nfunction envRate(name: string, fallback: number): number {\n const raw = process.env[name]?.trim()\n if (!raw) return fallback\n const parsed = Number(raw)\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback\n}\n\nexport const NANGO_USD_PER_CONNECTION_MONTH = envRate('NANGO_USD_PER_CONNECTION_MONTH', 1)\nexport const NANGO_USD_PER_FUNCTION_RUN = envRate('NANGO_USD_PER_FUNCTION_RUN', 0.0001)\nexport const NANGO_USD_PER_PROXY_REQUEST = envRate('NANGO_USD_PER_PROXY_REQUEST', 0.0001)\nexport const NANGO_USD_PER_COMPUTE_SEC = envRate('NANGO_USD_PER_COMPUTE_SEC', 0.0002)\n\nexport const CONNECTED_TARGET_MARGIN_MULTIPLE = 2.25\n\nexport function kernelCostUsd(ms: number, headful: boolean): number {\n const sec = Math.max(0, ms) / 1000\n return sec * (headful ? KERNEL_HEADFUL_USD_PER_SEC : KERNEL_HEADLESS_USD_PER_SEC)\n}\n\nexport function vendorCostUsd(vendor: string, units: number): number {\n if (vendor === 'fal_wizper') {\n const estimatedComputeSec = Math.max(0, units) / FAL_WIZPER_WALLCLOCK_TO_COMPUTE_SEC_FACTOR\n return estimatedComputeSec * FAL_WIZPER_USD_PER_COMPUTE_SEC\n }\n if (vendor === 'deepinfra_qwen') return (Math.max(0, units) / 1000) * DEEPINFRA_QWEN_USD_PER_1K_OUT_TOKENS\n if (vendor === 'openrouter') return Math.max(0, units) // units IS the real USD cost OpenRouter reported, not a rate-multiplied estimate\n if (vendor === 'mcp_memory_video') return Math.max(0, units) // units IS the real costUsd mcp-memory's video pipeline reported (OpenRouter + transcription)\n if (vendor === 'mcp_memory_ai') return Math.max(0, units) // units IS the real costUsd mcp-memory reported via /ai-debit\n if (vendor === 'nango_connection') return Math.max(0, units) * NANGO_USD_PER_CONNECTION_MONTH\n if (vendor === 'nango_function_run') return Math.max(0, units) * NANGO_USD_PER_FUNCTION_RUN\n if (vendor === 'nango_proxy_request') return Math.max(0, units) * NANGO_USD_PER_PROXY_REQUEST\n if (vendor === 'nango_compute') return Math.max(0, units) * NANGO_USD_PER_COMPUTE_SEC\n return 0\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var RECAPTCHA_INSTRUCTIONS = "Google returned a CAPTCHA. Run with --headless=false to re-warm the browser profile, then retry.";
|
|
3
|
+
function sanitizeVendorName(message) {
|
|
4
|
+
return message.replace(/kernel\.sh\s+sessions?/gi, "sessions").replace(/kernel\.sh\s+session/gi, "this session").replace(/kernel\.sh/gi, "the service").replace(/kernel\s+sessions?/gi, "sessions").replace(/kernel\s+session/gi, "this session").replace(/\bkernel\b/gi, "the service").replace(/ +/g, " ").trim();
|
|
5
|
+
}
|
|
6
|
+
var CaptchaError = class extends Error {
|
|
7
|
+
constructor(instructions) {
|
|
8
|
+
super(`CAPTCHA detected. ${instructions}`);
|
|
9
|
+
this.instructions = instructions;
|
|
10
|
+
}
|
|
11
|
+
instructions;
|
|
12
|
+
name = "CaptchaError";
|
|
13
|
+
};
|
|
14
|
+
var ExtractionError = class extends Error {
|
|
15
|
+
constructor(message, cause) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
}
|
|
19
|
+
cause;
|
|
20
|
+
name = "ExtractionError";
|
|
21
|
+
};
|
|
22
|
+
var MapsSearchIncompletePageError = class extends Error {
|
|
23
|
+
constructor(pageState) {
|
|
24
|
+
super(`Google Maps search did not reach a complete result state (${pageState})`);
|
|
25
|
+
this.pageState = pageState;
|
|
26
|
+
}
|
|
27
|
+
pageState;
|
|
28
|
+
name = "MapsSearchIncompletePageError";
|
|
29
|
+
};
|
|
30
|
+
var RequestAbortedError = class extends Error {
|
|
31
|
+
name = "RequestAbortedError";
|
|
32
|
+
constructor(message = "Request aborted before harvest completed") {
|
|
33
|
+
super(message);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var LocationMismatchError = class extends Error {
|
|
37
|
+
name = "LocationMismatchError";
|
|
38
|
+
constructor(message = "Google returned results for a different location than requested") {
|
|
39
|
+
super(message);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
RECAPTCHA_INSTRUCTIONS,
|
|
45
|
+
sanitizeVendorName,
|
|
46
|
+
CaptchaError,
|
|
47
|
+
ExtractionError,
|
|
48
|
+
MapsSearchIncompletePageError,
|
|
49
|
+
RequestAbortedError,
|
|
50
|
+
LocationMismatchError
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=chunk-44HZLHDV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["export const RECAPTCHA_INSTRUCTIONS = 'Google returned a CAPTCHA. Run with --headless=false to re-warm the browser profile, then retry.'\n\nexport function sanitizeVendorName(message: string): string {\n return message\n .replace(/kernel\\.sh\\s+sessions?/gi, 'sessions')\n .replace(/kernel\\.sh\\s+session/gi, 'this session')\n .replace(/kernel\\.sh/gi, 'the service')\n .replace(/kernel\\s+sessions?/gi, 'sessions')\n .replace(/kernel\\s+session/gi, 'this session')\n .replace(/\\bkernel\\b/gi, 'the service')\n .replace(/ +/g, ' ')\n .trim()\n}\n\nexport class CaptchaError extends Error {\n readonly name = 'CaptchaError'\n constructor(public readonly instructions: string) {\n super(`CAPTCHA detected. ${instructions}`)\n }\n}\n\nexport class ExtractionError extends Error {\n readonly name = 'ExtractionError'\n constructor(message: string, public readonly cause?: unknown) {\n super(message)\n }\n}\n\n/**\n * Google Maps sometimes finishes navigation without rendering either result\n * cards or its explicit no-results message. Treating that page as a genuine\n * empty search loses data, so callers should retry it with a fresh browser (and\n * a fresh proxy when location rotation is enabled).\n */\nexport class MapsSearchIncompletePageError extends Error {\n readonly name = 'MapsSearchIncompletePageError'\n\n constructor(public readonly pageState: string) {\n super(`Google Maps search did not reach a complete result state (${pageState})`)\n }\n}\n\nexport class RequestAbortedError extends Error {\n readonly name = 'RequestAbortedError'\n constructor(message = 'Request aborted before harvest completed') {\n super(message)\n }\n}\n\nexport class LocationMismatchError extends Error {\n readonly name = 'LocationMismatchError'\n constructor(message = 'Google returned results for a different location than requested') {\n super(message)\n }\n}\n"],"mappings":";AAAO,IAAM,yBAAyB;AAE/B,SAAS,mBAAmB,SAAyB;AAC1D,SAAO,QACJ,QAAQ,4BAA4B,UAAU,EAC9C,QAAQ,0BAA0B,cAAc,EAChD,QAAQ,gBAAgB,aAAa,EACrC,QAAQ,wBAAwB,UAAU,EAC1C,QAAQ,sBAAsB,cAAc,EAC5C,QAAQ,gBAAgB,aAAa,EACrC,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAEO,IAAM,eAAN,cAA2B,MAAM;AAAA,EAEtC,YAA4B,cAAsB;AAChD,UAAM,qBAAqB,YAAY,EAAE;AADf;AAAA,EAE5B;AAAA,EAF4B;AAAA,EADnB,OAAO;AAIlB;AAEO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAEzC,YAAY,SAAiC,OAAiB;AAC5D,UAAM,OAAO;AAD8B;AAAA,EAE7C;AAAA,EAF6C;AAAA,EADpC,OAAO;AAIlB;AAQO,IAAM,gCAAN,cAA4C,MAAM;AAAA,EAGvD,YAA4B,WAAmB;AAC7C,UAAM,6DAA6D,SAAS,GAAG;AADrD;AAAA,EAE5B;AAAA,EAF4B;AAAA,EAFnB,OAAO;AAKlB;AAEO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EACpC,OAAO;AAAA,EAChB,YAAY,UAAU,4CAA4C;AAChE,UAAM,OAAO;AAAA,EACf;AACF;AAEO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EACtC,OAAO;AAAA,EAChB,YAAY,UAAU,mEAAmE;AACvF,UAAM,OAAO;AAAA,EACf;AACF;","names":[]}
|