mcp-scraper 0.66.0 → 0.66.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/README.md +2 -2
  3. package/dist/{analytics-repository-IKE53S3E.js → analytics-repository-RUXBFSFX.js} +1 -2
  4. package/dist/bin/api-server.cjs +2898 -2075
  5. package/dist/bin/api-server.js +4 -5
  6. package/dist/bin/mcp-scraper-cli.cjs +1 -2
  7. package/dist/bin/mcp-scraper-cli.js +6 -7
  8. package/dist/bin/mcp-scraper-install.cjs +7 -3
  9. package/dist/bin/mcp-scraper-install.js +2 -3
  10. package/dist/bin/mcp-stdio-server.cjs +1669 -97
  11. package/dist/bin/mcp-stdio-server.js +11 -12
  12. package/dist/bin/paa-harvest.cjs +0 -1
  13. package/dist/bin/paa-harvest.js +4 -5
  14. package/dist/{chunk-VIWT35S6.js → chunk-3EYALCYK.js} +1 -2
  15. package/dist/{chunk-4LINSJNI.js → chunk-3TKKJQB4.js} +1 -2
  16. package/dist/{chunk-4VG3NUUY.js → chunk-55B3T5L2.js} +23 -28
  17. package/dist/{chunk-FG6LJGJ7.js → chunk-77LQV73B.js} +0 -1
  18. package/dist/{chunk-N54JS5WP.js → chunk-ABYBPODM.js} +1 -2
  19. package/dist/chunk-ALEBMPOW.js +6 -0
  20. package/dist/{chunk-2XUXVKT4.js → chunk-AYJHI2FK.js} +0 -1
  21. package/dist/{chunk-AAGVOPP5.js → chunk-CETDPNMZ.js} +24 -25
  22. package/dist/{chunk-5BEPOFKG.js → chunk-CNLLTD5B.js} +0 -1
  23. package/dist/{chunk-P7YFUNKR.js → chunk-DBWWIK7O.js} +3 -4
  24. package/dist/{chunk-EKJ65E3Z.js → chunk-DNM65UCK.js} +0 -1
  25. package/dist/{chunk-3YSEJTMC.js → chunk-GGZEC22A.js} +0 -1
  26. package/dist/{chunk-AJ4YGHG2.js → chunk-K7Y27ND3.js} +3 -14
  27. package/dist/{chunk-XPFRP5QT.js → chunk-KBTUPRTF.js} +3 -4
  28. package/dist/{chunk-5PIO7QBG.js → chunk-KXRWY7DZ.js} +3 -4
  29. package/dist/{chunk-DCWXVAQT.js → chunk-OZDCTNUV.js} +6 -2
  30. package/dist/{chunk-BIUQ6O2Y.js → chunk-OZJMVCDK.js} +0 -1
  31. package/dist/{chunk-X7UG2JCF.js → chunk-PGJQDMC2.js} +1 -2
  32. package/dist/{chunk-DKIG2TEE.js → chunk-PMCT2YQM.js} +2 -3
  33. package/dist/{chunk-G3P3ZDB4.js → chunk-RJ7JVYKU.js} +0 -1
  34. package/dist/{chunk-WD73OFL3.js → chunk-VXLU74YZ.js} +1 -2
  35. package/dist/{chunk-SRRI2HJL.js → chunk-YKL3O7IU.js} +1742 -104
  36. package/dist/{db-BRI465XQ.js → db-4RVZ3NYZ.js} +9 -10
  37. package/dist/{extract-bundle-QAI7JYV2.js → extract-bundle-EA5URLXN.js} +8 -9
  38. package/dist/index.cjs +0 -1
  39. package/dist/index.js +4 -5
  40. package/dist/{lead-list-enrichment-repository-ODWGRVCQ.js → lead-list-enrichment-repository-IBXNWM6H.js} +2 -3
  41. package/dist/{location-data-repository-67MMEVZQ.js → location-data-repository-WHHQUCUI.js} +3 -4
  42. package/dist/{server-MTKWBSAR.js → server-Q7VYGSGQ.js} +450 -1264
  43. package/dist/{site-extract-repository-J3RH3MOS.js → site-extract-repository-NTM4MDQN.js} +4 -5
  44. package/dist/{worker-K7MPPQ7I.js → worker-UTBUWEXQ.js} +8 -9
  45. package/package.json +4 -1
  46. package/dist/analytics-repository-IKE53S3E.js.map +0 -1
  47. package/dist/bin/api-server.cjs.map +0 -1
  48. package/dist/bin/api-server.js.map +0 -1
  49. package/dist/bin/mcp-scraper-cli.cjs.map +0 -1
  50. package/dist/bin/mcp-scraper-cli.js.map +0 -1
  51. package/dist/bin/mcp-scraper-install.cjs.map +0 -1
  52. package/dist/bin/mcp-scraper-install.js.map +0 -1
  53. package/dist/bin/mcp-stdio-server.cjs.map +0 -1
  54. package/dist/bin/mcp-stdio-server.js.map +0 -1
  55. package/dist/bin/paa-harvest.cjs.map +0 -1
  56. package/dist/bin/paa-harvest.js.map +0 -1
  57. package/dist/chunk-2XUXVKT4.js.map +0 -1
  58. package/dist/chunk-3YSEJTMC.js.map +0 -1
  59. package/dist/chunk-4LINSJNI.js.map +0 -1
  60. package/dist/chunk-4VG3NUUY.js.map +0 -1
  61. package/dist/chunk-5BEPOFKG.js.map +0 -1
  62. package/dist/chunk-5PIO7QBG.js.map +0 -1
  63. package/dist/chunk-AAGVOPP5.js.map +0 -1
  64. package/dist/chunk-AJ4YGHG2.js.map +0 -1
  65. package/dist/chunk-BIUQ6O2Y.js.map +0 -1
  66. package/dist/chunk-DCWXVAQT.js.map +0 -1
  67. package/dist/chunk-DKIG2TEE.js.map +0 -1
  68. package/dist/chunk-EKJ65E3Z.js.map +0 -1
  69. package/dist/chunk-FG6LJGJ7.js.map +0 -1
  70. package/dist/chunk-G3P3ZDB4.js.map +0 -1
  71. package/dist/chunk-HDYOVGC6.js +0 -7
  72. package/dist/chunk-HDYOVGC6.js.map +0 -1
  73. package/dist/chunk-N54JS5WP.js.map +0 -1
  74. package/dist/chunk-P7YFUNKR.js.map +0 -1
  75. package/dist/chunk-SRRI2HJL.js.map +0 -1
  76. package/dist/chunk-VIWT35S6.js.map +0 -1
  77. package/dist/chunk-WD73OFL3.js.map +0 -1
  78. package/dist/chunk-X7UG2JCF.js.map +0 -1
  79. package/dist/chunk-XPFRP5QT.js.map +0 -1
  80. package/dist/db-BRI465XQ.js.map +0 -1
  81. package/dist/extract-bundle-QAI7JYV2.js.map +0 -1
  82. package/dist/index.cjs.map +0 -1
  83. package/dist/index.js.map +0 -1
  84. package/dist/lead-list-enrichment-repository-ODWGRVCQ.js.map +0 -1
  85. package/dist/location-data-repository-67MMEVZQ.js.map +0 -1
  86. package/dist/server-MTKWBSAR.js.map +0 -1
  87. package/dist/site-extract-repository-J3RH3MOS.js.map +0 -1
  88. package/dist/worker-K7MPPQ7I.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/api/vendor-errors.ts"],"sourcesContent":["import {\n AuthenticationError as KernelAuthenticationError,\n APIConnectionError as KernelAPIConnectionError,\n APIConnectionTimeoutError as KernelAPIConnectionTimeoutError,\n InternalServerError as KernelInternalServerError,\n PermissionDeniedError as KernelPermissionDeniedError,\n} from '@onkernel/sdk'\n\nconst BRIGHTDATA_NOT_CONFIGURED_MESSAGE = 'SERP browser service is not configured'\n\nexport function isVendorUnavailableError(err: unknown, message: string): boolean {\n return err instanceof KernelAuthenticationError\n || err instanceof KernelPermissionDeniedError\n || err instanceof KernelAPIConnectionError\n || err instanceof KernelAPIConnectionTimeoutError\n || err instanceof KernelInternalServerError\n || /^401\\s+Invalid or disabled API key\\s*$/i.test(message.trim())\n || message === BRIGHTDATA_NOT_CONFIGURED_MESSAGE\n}\n"],"mappings":";AAAA;AAAA,EACE,uBAAuB;AAAA,EACvB,sBAAsB;AAAA,EACtB,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,yBAAyB;AAAA,OACpB;AAEP,IAAM,oCAAoC;AAEnC,SAAS,yBAAyB,KAAc,SAA0B;AAC/E,SAAO,eAAe,6BACjB,eAAe,+BACf,eAAe,4BACf,eAAe,mCACf,eAAe,6BACf,0CAA0C,KAAK,QAAQ,KAAK,CAAC,KAC7D,YAAY;AACnB;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/install-terminal.ts"],"sourcesContent":["export interface InstallTerminalOptions {\n color?: boolean\n version: string\n apiKeyConfigured?: boolean\n}\n\ntype Tone = 'cyan' | 'lime' | 'amber' | 'red' | 'muted' | 'bold'\n\nconst CODES: Record<Tone | 'reset', string> = {\n reset: '\\x1b[0m',\n cyan: '\\x1b[36m',\n lime: '\\x1b[32m',\n amber: '\\x1b[33m',\n red: '\\x1b[31m',\n muted: '\\x1b[90m',\n bold: '\\x1b[1m',\n}\n\nfunction colorize(value: string, tone: Tone, enabled: boolean): string {\n if (!enabled) return value\n return `${CODES[tone]}${value}${CODES.reset}`\n}\n\nfunction toolRow(label: string, tools: string[], enabled: boolean): string {\n const padded = label.padEnd(9, ' ')\n return ` ${colorize(padded, 'muted', enabled)} ${tools.join(colorize(' . ', 'muted', enabled))}`\n}\n\nexport function renderInstallTerminal(options: InstallTerminalOptions): string {\n const color = options.color ?? true\n const apiKeyValue = options.apiKeyConfigured ? '$MCP_SCRAPER_API_KEY' : 'sk_live_your_key'\n const ascii = String.raw`\n __ __ ____ ____ \n| \\/ |/ ___| _ \\\n| |\\/| | | | |_) |\n| | | | |___| __/\n|_| |_|\\____|_|\n\n ____ ____ ____ _ ____ _____ ____\n/ ___| / ___| _ \\ / \\ | _ \\| ____| _ \\\n\\___ \\| | | |_) | / _ \\ | |_) | _| | |_) |\n ___) | |___| _ < / ___ \\| __/| |___| _ <\n|____/ \\____|_| \\_\\/_/ \\_\\_| |_____|_| \\_\\\n`\n\n const claudeCommand = [\n `MCP_SCRAPER_API_KEY=${apiKeyValue} npx -y -p mcp-scraper@latest \\\\`,\n ' mcp-scraper-cli agent install claude --apply',\n ].join('\\n')\n\n const codexConfig = [\n '[mcp_servers.mcp-scraper]',\n 'command = \"npx\"',\n 'args = [\"-y\", \"-p\", \"mcp-scraper@latest\", \"mcp-scraper\"]',\n `env = { MCP_SCRAPER_API_KEY = \"${apiKeyValue}\" }`,\n ].join('\\n')\n\n return [\n colorize(`mcp-scraper v${options.version}`, 'bold', color),\n colorize('> mcp-scraper-install', 'muted', color),\n colorize(ascii, 'amber', color),\n `${colorize('MCP Scraper Agent', 'cyan', color)} . v${options.version} . mcpscraper.dev`,\n '1/1 install surfaces ready',\n colorize('Newest: any approved connection read can become an indexed Memory snapshot in one call. OAuth stays tenant-isolated and provider content is redacted and marked untrusted.', 'lime', color),\n '',\n `${colorize('Tools', 'cyan', color)} ${colorize('(259 MCP tools)', 'muted', color)}`,\n toolRow('search', ['harvest_paa', 'search_serp', 'maps_search', 'maps_place_intel'], color),\n toolRow('extract', ['extract_url', 'map_site_urls', 'extract_site', 'audit_site', 'directory_workflow'], color),\n toolRow('build', ['create_editorial_reading_room', 'rank_tracker_workflow', 'portable HTML'], color),\n toolRow('media', ['youtube_harvest', 'youtube_transcribe', 'facebook_ad_search', 'facebook_page_intel', 'facebook_ad_transcribe', 'facebook_video_transcribe', 'instagram_profile_content', 'instagram_media_download', 'reddit_thread'], color),\n toolRow('browser', ['serp_identity_create', 'serp_identity_list', 'browser_open', 'browser_profile_connect', 'browser_profile_list', 'browser_close', 'browser_screenshot', 'browser_read', 'browser_locate', 'browser_replay_mark', 'browser_replay_annotate'], color),\n toolRow('connect', ['list_service_connections', 'describe_service_connection_tool', 'import_service_connection_to_memory', 'export_connected_service_data', 'renew_connected_data_download', 'read_service_connection', 'call_service_connection_action'], color),\n toolRow('commons', ['commons_search_entities', 'commons_get_entity_linkset', 'commons_prepare_entity', 'commons_submit_entity', 'commons_prepare_publication', 'commons_claim_publication', 'commons_publish_editorial', 'commons_get_publication'], color),\n toolRow('account', ['credits_info', 'reports', 'MCP resources'], color),\n toolRow('memory', ['memory-put', 'memory-get', 'memory-search', 'list-vaults', 'record-fact', 'list-scheduled-actions'], color),\n `${colorize('Workflows', 'cyan', color)} ${colorize('(MCP + CLI + API)', 'muted', color)}`,\n toolRow('route', ['workflow_list', 'workflow_suggest', 'workflow_run', 'workflow_step', 'workflow_status', 'workflow_artifact_read'], color),\n toolRow('seo', ['directory', 'agent-packet', 'competitive audit', 'map/serp comparison', 'PAA/AIO briefs', 'scheduled runs'], color),\n '',\n colorize('Usage tips:', 'amber', color),\n 'Run mcp-scraper-install for this visible card. Run mcp-scraper-cli for setup utilities and subcommands.',\n 'Run mcp-scraper in a human terminal to print this card; MCP clients get the same command as a silent stdio server.',\n 'Explicit card command: npx -y -p mcp-scraper@latest mcp-scraper-install',\n 'Hosted browser sessions use direct/no-proxy egress by default.',\n 'Customer auth setup: run browser_profile_connect, send the watch_url, let the user sign in, then call browser_profile_list until AUTHENTICATED.',\n 'Connected account ranges: call export_connected_service_data once. It handles Gmail, Calendar, Zoom, and Resend pagination; do not loop read_service_connection over individual records.',\n 'Connected account RAG: call import_service_connection_to_memory for one bounded approved read. It writes a redacted, untrusted snapshot to a stable Memory path and embeds it for search.',\n 'Stack logins / reconnect: run browser_profile_connect again with the same profile name and another domain to add accounts or refresh a login.',\n 'Start with workflow_suggest for broad jobs like market analysis, ICP research, CRO audits, brand briefs, content gaps, and AI visibility.',\n 'For MCP clients, use mcp-scraper so one install can mix SERP, Maps, browser, reports, and saved MCP resources.',\n 'If you hit the concurrency limit, add 2 browsers for $5/month with mcp-scraper-cli billing concurrency checkout.',\n '',\n `${colorize('Ready.', 'lime', color)} Install the combined MCP server with one command:`,\n '',\n colorize('Setup doctor', 'amber', color),\n 'npx -y -p mcp-scraper@latest mcp-scraper-cli doctor',\n '',\n colorize('Hosted profile setup', 'amber', color),\n 'In your MCP client, call browser_profile_connect with email=\"seo@example.com\" and domain=\"chatgpt.com\".',\n 'Give the returned watch_url to the user. After they sign in, call browser_profile_list, then browser_open with the returned profile. Add more logins by calling browser_profile_connect again with the same profile and a new domain.',\n '',\n colorize('Claude Code one-command setup', 'amber', color),\n claudeCommand,\n 'Then fully exit Claude Code and open a new Claude terminal. Check with: claude mcp list',\n '',\n colorize('Codex config', 'amber', color),\n codexConfig,\n '',\n colorize('Claude Desktop Extension', 'amber', color),\n 'Download: https://mcpscraper.dev/downloads/mcp-scraper.mcpb',\n '',\n colorize('Safety note:', 'muted', color),\n 'mcp-scraper prints this card only when stdin/stdout are an interactive TTY. In MCP clients it writes only JSON-RPC to stdout.',\n 'Use --stdio or MCP_SCRAPER_FORCE_STDIO=1 to force server mode from a terminal.',\n '',\n ].join('\\n')\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/api/location-data-repository.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto'\nimport { getDb } from './db.js'\nimport { csvRecords } from '../directory/csv.js'\nimport type { DirectoryMarket, PopulationYear } from '../directory/location-db.js'\n\nexport const HOSTED_ZIP_DATASET_KIND = 'us_zip_groups' as const\nexport const HOSTED_CENSUS_DATASET_PREFIX = 'us_census_places' as const\nexport type HostedLocationDatasetKind = typeof HOSTED_ZIP_DATASET_KIND | `${typeof HOSTED_CENSUS_DATASET_PREFIX}:${string}`\nexport const MIN_NATIONWIDE_ZIP_COUNT = 25_000\n\nexport const REQUIRED_LOCATION_STATE_CODES = [\n 'AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FL', 'GA', 'HI', 'ID', 'IL', 'IN', 'IA',\n 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM',\n 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA',\n 'WV', 'WI', 'WY',\n] as const\n\nconst STATE_FIPS_BY_ABBR: Record<typeof REQUIRED_LOCATION_STATE_CODES[number], string> = {\n AL: '01', AK: '02', AZ: '04', AR: '05', CA: '06', CO: '08', CT: '09', DE: '10', DC: '11',\n FL: '12', GA: '13', HI: '15', ID: '16', IL: '17', IN: '18', IA: '19', KS: '20', KY: '21',\n LA: '22', ME: '23', MD: '24', MA: '25', MI: '26', MN: '27', MS: '28', MO: '29', MT: '30',\n NE: '31', NV: '32', NH: '33', NJ: '34', NM: '35', NY: '36', NC: '37', ND: '38', OH: '39',\n OK: '40', OR: '41', PA: '42', RI: '44', SC: '45', SD: '46', TN: '47', TX: '48', UT: '49',\n VT: '50', VA: '51', WA: '53', WV: '54', WI: '55', WY: '56',\n}\nconst REQUIRED_LOCATION_STATE_SET = new Set<string>(REQUIRED_LOCATION_STATE_CODES)\n\nexport interface HostedZipGroup {\n city: string\n cityKey: string\n state: string\n zips: string[]\n counties: string[]\n}\n\nexport interface HostedLocationDataset {\n id: string\n kind: HostedLocationDatasetKind\n sourceUrl: string | null\n sha256: string\n rowCount: number\n groupCount: number\n stateCount: number\n sourceRowCount: number\n rejectedRowCount: number\n zipCount: number\n coveredStates: string[]\n coverageComplete: boolean\n status: 'loading' | 'ready' | 'failed'\n createdAt: string\n completedAt: string | null\n error: string | null\n}\n\nexport interface HostedZipGroupLookup {\n dataset: HostedLocationDataset | null\n groups: Map<string, HostedZipGroup>\n}\n\nexport interface HostedZipImportResult {\n dataset: HostedLocationDataset\n duplicate: boolean\n}\n\nexport interface HostedLocationMarketQuery {\n state: string\n minPopulation: number\n populationYear: PopulationYear\n maxResults: number\n includeZipGroups: boolean\n cityQuery?: string\n zip?: string\n}\n\nexport interface HostedLocationMarketResult {\n markets: DirectoryMarket[]\n populationDataset: HostedLocationDataset | null\n zipDataset: HostedLocationDataset | null\n warnings: string[]\n}\n\ninterface HostedCensusPlace {\n city: string\n cityKey: string\n censusName: string\n estimatesBase2020: number | null\n populations: Record<PopulationYear, number | null>\n}\n\ninterface ParsedCensusDataset {\n sourceRowCount: number\n places: HostedCensusPlace[]\n}\n\ninterface ParsedZipDataset {\n sourceRowCount: number\n rowCount: number\n rejectedRowCount: number\n zipCount: number\n stateCount: number\n coveredStates: string[]\n groups: HostedZipGroup[]\n}\n\nconst IMPORT_BATCH_SIZE = 200\nconst MAX_SOURCE_URL_LENGTH = 2_000\n\nlet schemaDb: ReturnType<typeof getDb> | null = null\nlet schemaPromise: Promise<void> | null = null\n\nfunction normalizeHostedCityKey(value: string): string {\n return value.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim()\n}\n\nfunction stringArray(value: unknown): string[] {\n if (value == null) return []\n try {\n const parsed = JSON.parse(String(value)) as unknown\n return Array.isArray(parsed) ? parsed.map(String) : []\n } catch {\n return []\n }\n}\n\nfunction rowToDataset(row: Record<string, unknown>): HostedLocationDataset {\n return {\n id: String(row.id),\n kind: String(row.dataset_kind) as HostedLocationDatasetKind,\n sourceUrl: row.source_url == null ? null : String(row.source_url),\n sha256: String(row.sha256),\n rowCount: Number(row.row_count ?? 0),\n groupCount: Number(row.group_count ?? 0),\n stateCount: Number(row.state_count ?? 0),\n sourceRowCount: Number(row.source_row_count ?? row.row_count ?? 0),\n rejectedRowCount: Number(row.rejected_row_count ?? 0),\n zipCount: Number(row.zip_count ?? 0),\n coveredStates: stringArray(row.covered_states_json),\n coverageComplete: Number(row.coverage_complete ?? 0) === 1,\n status: String(row.status) as HostedLocationDataset['status'],\n createdAt: String(row.created_at),\n completedAt: row.completed_at == null ? null : String(row.completed_at),\n error: row.error == null ? null : String(row.error),\n }\n}\n\nfunction censusDatasetKind(state: string): `${typeof HOSTED_CENSUS_DATASET_PREFIX}:${string}` {\n return `${HOSTED_CENSUS_DATASET_PREFIX}:${state}`\n}\n\nfunction normalizedSourceUrl(value: string | null | undefined): string | null {\n const trimmed = value?.trim()\n if (!trimmed) return null\n if (trimmed.length > MAX_SOURCE_URL_LENGTH) throw new Error(`sourceUrl must be ${MAX_SOURCE_URL_LENGTH} characters or fewer`)\n let parsed: URL\n try {\n parsed = new URL(trimmed)\n } catch {\n throw new Error('sourceUrl must be a valid URL')\n }\n parsed.username = ''\n parsed.password = ''\n parsed.search = ''\n parsed.hash = ''\n return parsed.toString()\n}\n\nfunction normalizedZip(value: string): string | null {\n const digits = value.trim()\n if (!/^\\d{1,5}$/.test(digits)) return null\n return digits.padStart(5, '0')\n}\n\nfunction normalizedState(value: string): string | null {\n const state = value.trim().toUpperCase()\n return REQUIRED_LOCATION_STATE_SET.has(state) ? state : null\n}\n\nfunction csvColumn(record: Record<string, string>, names: string[]): string {\n for (const name of names) {\n const value = record[name]\n if (value !== undefined) return value\n }\n return ''\n}\n\nfunction numberOrNull(value: string | undefined): number | null {\n if (value === undefined || value.trim() === '') return null\n const parsed = Number(value)\n return Number.isFinite(parsed) && parsed >= 0 ? Math.trunc(parsed) : null\n}\n\nfunction displayCityFromCensus(name: string): string {\n if (/^Nashville-Davidson metropolitan government/i.test(name)) return 'Nashville'\n return name.replace(/\\s+(city|town|village|municipality|borough)$/i, '').trim()\n}\n\nexport function parseHostedCensusPlacesCsv(csv: string, expectedStateFips?: string): ParsedCensusDataset {\n const records = csvRecords(csv.replace(/^\\uFEFF/, ''))\n if (records.length === 0) throw new Error('Census place CSV contains no data rows.')\n const headers = new Set(Object.keys(records[0] ?? {}).map(header => header.trim()))\n const required = ['SUMLEV', 'STATE', 'NAME', 'POPESTIMATE2020', 'POPESTIMATE2021', 'POPESTIMATE2022', 'POPESTIMATE2023', 'POPESTIMATE2024', 'POPESTIMATE2025']\n const missing = required.filter(header => !headers.has(header))\n if (missing.length > 0) throw new Error(`Census place CSV is missing required columns: ${missing.join(', ')}.`)\n\n const placeRecords = records.filter(record => record.SUMLEV?.trim() === '162')\n if (expectedStateFips) {\n const expected = expectedStateFips.padStart(2, '0')\n const mismatched = placeRecords.filter(record => record.STATE?.trim().padStart(2, '0') !== expected)\n if (mismatched.length > 0) {\n const received = [...new Set(mismatched.map(record => record.STATE?.trim() || '(blank)'))].slice(0, 5)\n throw new Error(`Census place CSV STATE does not match requested FIPS ${expected}; received ${received.join(', ')}.`)\n }\n }\n\n const candidates = placeRecords\n .map((record): HostedCensusPlace | null => {\n const censusName = record.NAME?.trim() ?? ''\n if (!censusName) return null\n const city = displayCityFromCensus(censusName)\n const cityKey = normalizeHostedCityKey(city)\n if (!cityKey) return null\n const populations = {\n 2020: numberOrNull(record.POPESTIMATE2020),\n 2021: numberOrNull(record.POPESTIMATE2021),\n 2022: numberOrNull(record.POPESTIMATE2022),\n 2023: numberOrNull(record.POPESTIMATE2023),\n 2024: numberOrNull(record.POPESTIMATE2024),\n 2025: numberOrNull(record.POPESTIMATE2025),\n } satisfies Record<PopulationYear, number | null>\n if (Object.values(populations).every(value => value === null)) return null\n return {\n city,\n cityKey,\n censusName,\n estimatesBase2020: numberOrNull(record.ESTIMATESBASE2020),\n populations,\n }\n })\n .filter((place): place is HostedCensusPlace => place !== null)\n\n // Census can contain separate legal entities with the same normalized place\n // name in one state (for example, both a small \"Wilmington village\" and a\n // much larger \"Wilmington city\" in Illinois). The hosted table and the ZIP\n // join intentionally use one row per state/city key, so retain the largest\n // current place deterministically instead of failing the whole state import.\n const placesByCityKey = new Map<string, HostedCensusPlace>()\n const populationRank = (place: HostedCensusPlace): number => (\n place.populations[2025]\n ?? place.populations[2024]\n ?? place.populations[2023]\n ?? place.populations[2022]\n ?? place.populations[2021]\n ?? place.populations[2020]\n ?? place.estimatesBase2020\n ?? -1\n )\n for (const candidate of candidates) {\n const current = placesByCityKey.get(candidate.cityKey)\n if (!current\n || populationRank(candidate) > populationRank(current)\n || (populationRank(candidate) === populationRank(current)\n && candidate.censusName.localeCompare(current.censusName) < 0)) {\n placesByCityKey.set(candidate.cityKey, candidate)\n }\n }\n const places = [...placesByCityKey.values()]\n .sort((a, b) => a.city.localeCompare(b.city))\n\n if (places.length === 0) throw new Error('Census place CSV did not contain any valid place population rows.')\n return { sourceRowCount: records.length, places }\n}\n\nexport function parseHostedZipGroupsCsv(csv: string): ParsedZipDataset {\n const records = csvRecords(csv.replace(/^\\uFEFF/, ''))\n if (records.length === 0) throw new Error('Location ZIP CSV contains no data rows.')\n\n const first = records[0] ?? {}\n const headers = new Set(Object.keys(first).map(header => header.trim().toLowerCase()))\n const hasState = ['state_abbr', 'state', 'state_id'].some(header => headers.has(header))\n const hasCity = ['city', 'primary_city'].some(header => headers.has(header))\n const hasZip = ['zipcode', 'zip', 'zip_code'].some(header => headers.has(header))\n if (!hasState || !hasCity || !hasZip) {\n throw new Error('Location ZIP CSV must include state_abbr/state, city, and zipcode/zip columns.')\n }\n\n const grouped = new Map<string, { city: string; cityKey: string; state: string; zips: Set<string>; counties: Set<string> }>()\n let rowCount = 0\n let rejectedRowCount = 0\n const states = new Set<string>()\n const uniqueZips = new Set<string>()\n\n for (const rawRecord of records) {\n const record = Object.fromEntries(Object.entries(rawRecord).map(([key, value]) => [key.trim().toLowerCase(), value]))\n const state = normalizedState(csvColumn(record, ['state_abbr', 'state', 'state_id']))\n const city = csvColumn(record, ['city', 'primary_city']).trim()\n const zip = normalizedZip(csvColumn(record, ['zipcode', 'zip', 'zip_code']))\n const county = csvColumn(record, ['county', 'county_name']).trim()\n if (!state || !REQUIRED_LOCATION_STATE_SET.has(state) || !city || !zip) {\n rejectedRowCount += 1\n continue\n }\n\n const cityKey = normalizeHostedCityKey(city)\n if (!cityKey) {\n rejectedRowCount += 1\n continue\n }\n const key = `${state}\\0${cityKey}`\n let group = grouped.get(key)\n if (!group) {\n group = { city, cityKey, state, zips: new Set(), counties: new Set() }\n grouped.set(key, group)\n }\n group.zips.add(zip)\n if (county) group.counties.add(county)\n states.add(state)\n uniqueZips.add(zip)\n rowCount += 1\n }\n\n if (grouped.size === 0) throw new Error('Location ZIP CSV did not contain any valid US city/ZIP rows.')\n return {\n sourceRowCount: records.length,\n rowCount,\n rejectedRowCount,\n zipCount: uniqueZips.size,\n stateCount: states.size,\n coveredStates: [...states].sort(),\n groups: [...grouped.values()]\n .map(group => ({\n city: group.city,\n cityKey: group.cityKey,\n state: group.state,\n zips: [...group.zips].sort(),\n counties: [...group.counties].sort(),\n }))\n .sort((a, b) => a.state.localeCompare(b.state) || a.city.localeCompare(b.city)),\n }\n}\n\nfunction assertNationwideZipCoverage(parsed: ParsedZipDataset): void {\n const covered = new Set(parsed.coveredStates)\n const missingStates = REQUIRED_LOCATION_STATE_CODES.filter(state => !covered.has(state))\n const errors: string[] = []\n if (missingStates.length > 0) errors.push(`missing required states: ${missingStates.join(', ')}`)\n if (parsed.zipCount < MIN_NATIONWIDE_ZIP_COUNT) {\n errors.push(`only ${parsed.zipCount} unique ZIPs; at least ${MIN_NATIONWIDE_ZIP_COUNT} are required`)\n }\n if (errors.length > 0) {\n throw new Error(`Location ZIP snapshot is not nationwide and was not activated (${errors.join('; ')}; ${parsed.rejectedRowCount} rejected rows).`)\n }\n}\n\nexport async function ensureHostedLocationDataSchema(): Promise<void> {\n const db = getDb()\n if (schemaDb !== db) {\n schemaDb = db\n schemaPromise = null\n }\n if (!schemaPromise) {\n schemaPromise = (async () => {\n await db.execute(`\n CREATE TABLE IF NOT EXISTS location_data_imports (\n id TEXT PRIMARY KEY,\n dataset_kind TEXT NOT NULL,\n source_url TEXT,\n sha256 TEXT NOT NULL,\n row_count INTEGER NOT NULL DEFAULT 0,\n group_count INTEGER NOT NULL DEFAULT 0,\n state_count INTEGER NOT NULL DEFAULT 0,\n source_row_count INTEGER NOT NULL DEFAULT 0,\n rejected_row_count INTEGER NOT NULL DEFAULT 0,\n zip_count INTEGER NOT NULL DEFAULT 0,\n covered_states_json TEXT NOT NULL DEFAULT '[]',\n coverage_complete INTEGER NOT NULL DEFAULT 0,\n status TEXT NOT NULL,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n completed_at TEXT\n )\n `)\n const importColumns = new Set((await db.execute(`PRAGMA table_info(location_data_imports)`)).rows.map(row => String(row.name)))\n const missingImportColumns = [\n ['source_row_count', `INTEGER NOT NULL DEFAULT 0`],\n ['rejected_row_count', `INTEGER NOT NULL DEFAULT 0`],\n ['zip_count', `INTEGER NOT NULL DEFAULT 0`],\n ['covered_states_json', `TEXT NOT NULL DEFAULT '[]'`],\n ['coverage_complete', `INTEGER NOT NULL DEFAULT 0`],\n ] as const\n for (const [column, definition] of missingImportColumns) {\n if (!importColumns.has(column)) await db.execute(`ALTER TABLE location_data_imports ADD COLUMN ${column} ${definition}`)\n }\n await db.execute(`CREATE INDEX IF NOT EXISTS location_data_imports_kind_created ON location_data_imports(dataset_kind, created_at DESC)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS location_zip_groups (\n import_id TEXT NOT NULL REFERENCES location_data_imports(id) ON DELETE CASCADE,\n state_abbr TEXT NOT NULL,\n city_key TEXT NOT NULL,\n city TEXT NOT NULL,\n zips_json TEXT NOT NULL,\n counties_json TEXT NOT NULL,\n PRIMARY KEY(import_id, state_abbr, city_key)\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS location_zip_groups_state ON location_zip_groups(import_id, state_abbr, city_key)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS location_census_places (\n import_id TEXT NOT NULL REFERENCES location_data_imports(id) ON DELETE CASCADE,\n state_abbr TEXT NOT NULL,\n city_key TEXT NOT NULL,\n city TEXT NOT NULL,\n census_name TEXT NOT NULL,\n estimates_base_2020 INTEGER,\n population_2020 INTEGER,\n population_2021 INTEGER,\n population_2022 INTEGER,\n population_2023 INTEGER,\n population_2024 INTEGER,\n population_2025 INTEGER,\n PRIMARY KEY(import_id, state_abbr, city_key)\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS location_census_places_state ON location_census_places(import_id, state_abbr, city_key)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS location_data_active (\n dataset_kind TEXT PRIMARY KEY,\n import_id TEXT NOT NULL REFERENCES location_data_imports(id),\n activated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n })().catch(error => {\n schemaPromise = null\n throw error\n })\n }\n await schemaPromise\n}\n\nexport async function getActiveHostedDataset(kind: HostedLocationDatasetKind): Promise<HostedLocationDataset | null> {\n await ensureHostedLocationDataSchema()\n const result = await getDb().execute({\n sql: `\n SELECT i.*\n FROM location_data_active a\n JOIN location_data_imports i ON i.id = a.import_id\n WHERE a.dataset_kind = ? AND i.status = 'ready'\n LIMIT 1\n `,\n args: [kind],\n })\n const row = result.rows[0]\n return row ? rowToDataset(row as unknown as Record<string, unknown>) : null\n}\n\nexport async function getActiveHostedLocationDataset(): Promise<HostedLocationDataset | null> {\n return getActiveHostedDataset(HOSTED_ZIP_DATASET_KIND)\n}\n\nexport async function listActiveHostedLocationDatasets(): Promise<HostedLocationDataset[]> {\n await ensureHostedLocationDataSchema()\n const result = await getDb().execute(`\n SELECT i.*\n FROM location_data_active a\n JOIN location_data_imports i ON i.id = a.import_id\n WHERE i.status = 'ready'\n ORDER BY i.dataset_kind ASC\n `)\n return result.rows.map(row => rowToDataset(row as unknown as Record<string, unknown>))\n}\n\nexport async function getHostedZipGroups(stateInput: string): Promise<HostedZipGroupLookup> {\n const state = normalizedState(stateInput)\n if (!state) throw new Error('state must be a two-letter US state abbreviation')\n const dataset = await getActiveHostedLocationDataset()\n if (!dataset) return { dataset: null, groups: new Map() }\n\n const result = await getDb().execute({\n sql: `\n SELECT city, city_key, state_abbr, zips_json, counties_json\n FROM location_zip_groups\n WHERE import_id = ? AND state_abbr = ?\n ORDER BY city COLLATE NOCASE ASC\n `,\n args: [dataset.id, state],\n })\n const groups = new Map<string, HostedZipGroup>()\n for (const row of result.rows) {\n const cityKey = String(row.city_key)\n groups.set(cityKey, {\n city: String(row.city),\n cityKey,\n state: String(row.state_abbr),\n zips: JSON.parse(String(row.zips_json)) as string[],\n counties: JSON.parse(String(row.counties_json)) as string[],\n })\n }\n return { dataset, groups }\n}\n\nexport async function importHostedZipGroupsCsv(input: {\n csv: string\n sourceUrl?: string | null\n}): Promise<HostedZipImportResult> {\n await ensureHostedLocationDataSchema()\n const sourceUrl = normalizedSourceUrl(input.sourceUrl)\n const sha256 = createHash('sha256').update(input.csv).digest('hex')\n const active = await getActiveHostedLocationDataset()\n if (active?.sha256 === sha256) return { dataset: active, duplicate: true }\n\n const parsed = parseHostedZipGroupsCsv(input.csv)\n assertNationwideZipCoverage(parsed)\n const id = `loc_${randomUUID().replaceAll('-', '')}`\n const db = getDb()\n await db.execute({\n sql: `\n INSERT INTO location_data_imports (\n id, dataset_kind, source_url, sha256, row_count, group_count, state_count,\n source_row_count, rejected_row_count, zip_count, covered_states_json, coverage_complete, status\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 'loading')\n `,\n args: [\n id, HOSTED_ZIP_DATASET_KIND, sourceUrl, sha256, parsed.rowCount, parsed.groups.length, parsed.stateCount,\n parsed.sourceRowCount, parsed.rejectedRowCount, parsed.zipCount, JSON.stringify(parsed.coveredStates),\n ],\n })\n\n try {\n for (let offset = 0; offset < parsed.groups.length; offset += IMPORT_BATCH_SIZE) {\n const batch = parsed.groups.slice(offset, offset + IMPORT_BATCH_SIZE).map(group => ({\n sql: `\n INSERT INTO location_zip_groups (\n import_id, state_abbr, city_key, city, zips_json, counties_json\n ) VALUES (?, ?, ?, ?, ?, ?)\n `,\n args: [id, group.state, group.cityKey, group.city, JSON.stringify(group.zips), JSON.stringify(group.counties)],\n }))\n await db.batch(batch, 'write')\n }\n\n await db.batch([\n {\n sql: `UPDATE location_data_imports SET status = 'ready', completed_at = datetime('now'), error = NULL WHERE id = ? AND status = 'loading'`,\n args: [id],\n },\n {\n sql: `\n INSERT INTO location_data_active (dataset_kind, import_id, activated_at)\n VALUES (?, ?, datetime('now'))\n ON CONFLICT(dataset_kind) DO UPDATE SET import_id = excluded.import_id, activated_at = excluded.activated_at\n `,\n args: [HOSTED_ZIP_DATASET_KIND, id],\n },\n ], 'write')\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n await db.execute({\n sql: `UPDATE location_data_imports SET status = 'failed', error = ?, completed_at = datetime('now') WHERE id = ?`,\n args: [message.slice(0, 1_000), id],\n }).catch(() => undefined)\n throw error\n }\n\n const result = await db.execute({ sql: 'SELECT * FROM location_data_imports WHERE id = ? LIMIT 1', args: [id] })\n const row = result.rows[0]\n if (!row) throw new Error('Hosted location import completed without a durable metadata row.')\n return { dataset: rowToDataset(row as unknown as Record<string, unknown>), duplicate: false }\n}\n\nexport async function importHostedCensusPlacesCsv(input: {\n state: string\n csv: string\n sourceUrl?: string | null\n}): Promise<HostedZipImportResult> {\n await ensureHostedLocationDataSchema()\n const state = normalizedState(input.state)\n if (!state) throw new Error('state must be a two-letter US state abbreviation')\n const kind = censusDatasetKind(state)\n const sourceUrl = normalizedSourceUrl(input.sourceUrl)\n const sha256 = createHash('sha256').update(input.csv).digest('hex')\n const active = await getActiveHostedDataset(kind)\n if (active?.sha256 === sha256) return { dataset: active, duplicate: true }\n\n const stateFips = STATE_FIPS_BY_ABBR[state as keyof typeof STATE_FIPS_BY_ABBR]\n const parsed = parseHostedCensusPlacesCsv(input.csv, stateFips)\n const id = `loc_${randomUUID().replaceAll('-', '')}`\n const db = getDb()\n await db.execute({\n sql: `\n INSERT INTO location_data_imports (\n id, dataset_kind, source_url, sha256, row_count, group_count, state_count,\n source_row_count, rejected_row_count, zip_count, covered_states_json, coverage_complete, status\n ) VALUES (?, ?, ?, ?, ?, ?, 1, ?, 0, 0, ?, 1, 'loading')\n `,\n args: [id, kind, sourceUrl, sha256, parsed.places.length, parsed.places.length, parsed.sourceRowCount, JSON.stringify([state])],\n })\n\n try {\n for (let offset = 0; offset < parsed.places.length; offset += IMPORT_BATCH_SIZE) {\n const batch = parsed.places.slice(offset, offset + IMPORT_BATCH_SIZE).map(place => ({\n sql: `\n INSERT INTO location_census_places (\n import_id, state_abbr, city_key, city, census_name, estimates_base_2020,\n population_2020, population_2021, population_2022, population_2023,\n population_2024, population_2025\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n args: [\n id,\n state,\n place.cityKey,\n place.city,\n place.censusName,\n place.estimatesBase2020,\n place.populations[2020],\n place.populations[2021],\n place.populations[2022],\n place.populations[2023],\n place.populations[2024],\n place.populations[2025],\n ],\n }))\n await db.batch(batch, 'write')\n }\n\n await db.batch([\n {\n sql: `UPDATE location_data_imports SET status = 'ready', completed_at = datetime('now'), error = NULL WHERE id = ? AND status = 'loading'`,\n args: [id],\n },\n {\n sql: `\n INSERT INTO location_data_active (dataset_kind, import_id, activated_at)\n VALUES (?, ?, datetime('now'))\n ON CONFLICT(dataset_kind) DO UPDATE SET import_id = excluded.import_id, activated_at = excluded.activated_at\n `,\n args: [kind, id],\n },\n ], 'write')\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n await db.execute({\n sql: `UPDATE location_data_imports SET status = 'failed', error = ?, completed_at = datetime('now') WHERE id = ?`,\n args: [message.slice(0, 1_000), id],\n }).catch(() => undefined)\n throw error\n }\n\n const result = await db.execute({ sql: 'SELECT * FROM location_data_imports WHERE id = ? LIMIT 1', args: [id] })\n const row = result.rows[0]\n if (!row) throw new Error('Hosted Census import completed without a durable metadata row.')\n return { dataset: rowToDataset(row as unknown as Record<string, unknown>), duplicate: false }\n}\n\nexport async function queryHostedLocationMarkets(input: HostedLocationMarketQuery): Promise<HostedLocationMarketResult> {\n const state = normalizedState(input.state)\n if (!state) throw new Error('state must be a two-letter US state abbreviation')\n if (!Number.isSafeInteger(input.minPopulation) || input.minPopulation < 0) throw new Error('minPopulation must be a non-negative integer')\n if (!Number.isSafeInteger(input.maxResults) || input.maxResults < 1 || input.maxResults > 100) throw new Error('maxResults must be between 1 and 100')\n\n const populationDataset = await getActiveHostedDataset(censusDatasetKind(state))\n const needsZipDataset = input.includeZipGroups || Boolean(input.zip)\n const zipLookup = needsZipDataset\n ? await getHostedZipGroups(state)\n : { dataset: null, groups: new Map<string, HostedZipGroup>() }\n if (!populationDataset) {\n return { markets: [], populationDataset: null, zipDataset: zipLookup.dataset, warnings: [] }\n }\n\n const result = await getDb().execute({\n sql: `\n SELECT *\n FROM location_census_places\n WHERE import_id = ? AND state_abbr = ?\n ORDER BY city COLLATE NOCASE ASC\n `,\n args: [populationDataset.id, state],\n })\n const cityQuery = normalizeHostedCityKey(input.cityQuery ?? '')\n const zip = input.zip?.trim()\n const populationColumn = `population_${input.populationYear}`\n const markets = result.rows\n .map((row): DirectoryMarket | null => {\n const populationValue = row[populationColumn]\n if (populationValue == null) return null\n const population = Number(populationValue)\n if (!Number.isSafeInteger(population) || population < input.minPopulation) return null\n const city = String(row.city)\n const normalizedKey = String(row.city_key)\n if (cityQuery && !normalizedKey.includes(cityQuery)) return null\n const zipGroup = zipLookup.groups.get(normalizedKey)\n const zips = zipGroup?.zips ?? []\n if (zip && !zips.includes(zip)) return null\n return {\n city,\n state,\n location: `${city}, ${state}`,\n cityKey: `${city}|${state}`,\n censusName: String(row.census_name),\n population,\n populationYear: input.populationYear,\n estimatesBase2020: row.estimates_base_2020 == null ? null : Number(row.estimates_base_2020),\n zips: input.includeZipGroups ? zips : [],\n counties: input.includeZipGroups ? zipGroup?.counties ?? [] : [],\n }\n })\n .filter((market): market is DirectoryMarket => market !== null)\n .sort((a, b) => b.population - a.population || a.city.localeCompare(b.city))\n .slice(0, input.maxResults)\n\n const warnings: string[] = []\n if (input.includeZipGroups && zipLookup.dataset && markets.some(market => market.zips.length === 0)) {\n warnings.push('Some hosted Census places did not match the hosted ZIP city groups.')\n }\n return {\n markets,\n populationDataset,\n zipDataset: zipLookup.dataset,\n warnings,\n }\n}\n"],"mappings":"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","names":["key"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/api/private-artifacts.ts"],"sourcesContent":["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 | null\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\nconst DURABLE_ARTIFACT_EXPIRES_AT = '9999-12-31T23:59:59.999Z'\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): {\n prefix: string\n artifactTtlMs: number | null\n downloadTtlMs: number\n} {\n if (policy.artifactTtlMs !== null && (!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 if (validated.artifactTtlMs === 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 | null, 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 = expiresAt\n ? Math.min(Date.now() + policy.downloadTtlMs, expiresAt.getTime())\n : Date.now() + policy.downloadTtlMs\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 scopeSegments?: string[]\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 scope = (args.scopeSegments ?? []).map((segment, index) => requiredSafeSegment(segment, `scopeSegments[${index}]`))\n const timestamp = createdAtMs(args.createdAt)\n const requestedPathname = `${policy.prefix}${ownerId}/${scope.length ? `${scope.join('/')}/` : ''}${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 = policy.artifactTtlMs === null ? null : 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() ?? DURABLE_ARTIFACT_EXPIRES_AT,\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 scopeSegments?: string[]\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 scope = (args.scopeSegments ?? []).map((segment, index) => requiredSafeSegment(segment, `scopeSegments[${index}]`))\n const timestamp = createdAtMs(args.createdAt)\n const requestedPathname = `${policy.prefix}${ownerId}/${scope.length ? `${scope.join('/')}/` : ''}${timestamp}-${artifactKey}`\n const expiresAt = policy.artifactTtlMs === null ? null : 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() ?? DURABLE_ARTIFACT_EXPIRES_AT,\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 (args.policy.artifactTtlMs !== null && (!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() ?? DURABLE_ARTIFACT_EXPIRES_AT,\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 if (privateArtifactOwnerId(args.artifactId, args.policy.prefix) === null) return null\n const expiresAt = privateArtifactExpiresAt(args.artifactId, args.policy)\n if (args.policy.artifactTtlMs !== null && (!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\nexport async function readPrivateArtifactBuffer(args: {\n policy: PrivateArtifactPolicy\n artifactId: string\n maxBytes: number\n}): Promise<Buffer | null> {\n if (privateArtifactOwnerId(args.artifactId, args.policy.prefix) === null) return null\n const expiresAt = privateArtifactExpiresAt(args.artifactId, args.policy)\n if (args.policy.artifactTtlMs !== null && (!expiresAt || expiresAt.getTime() <= Date.now())) return null\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 if (buffer.length > args.maxBytes) throw new Error('private artifact exceeds read limit')\n return buffer\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/schemas.ts","../src/output/memory-library-sink.ts"],"sourcesContent":["import { z } from 'zod'\n\nexport const DEFAULT_PROXY_MODE = 'none' as const\nexport const DEFAULT_MAPS_PROXY_MODE = 'none' as const\n\nexport const HarvestOptionsSchema = z.object({\n query: z.string().min(1),\n location: z.string().optional(),\n gl: z.string().length(2).default('us'),\n hl: z.string().length(2).default('en'),\n device: z.enum(['desktop', 'mobile']).default('desktop'),\n proxyMode: z.enum(['location', 'configured', 'none']).default(DEFAULT_PROXY_MODE),\n proxyZip: z.string().regex(/^\\d{5}$/).optional(),\n keepDefaultProxy: z.boolean().optional(),\n requireUsEgress: z.boolean().optional(),\n maxAttempts: z.number().int().min(1).max(12).optional(),\n debug: z.boolean().default(false),\n depth: z.number().int().min(1).max(30).default(3),\n maxQuestions: z.number().int().min(1).max(1000).default(100),\n headless: z.boolean().default(false),\n profileDir: z.string().optional(),\n proxy: z.string().url().optional(),\n kernelApiKey: z.string().optional(),\n kernelProxyId: z.string().optional(),\n kernelProfileName: z.string().optional(),\n kernelProfileSaveChanges: z.boolean().optional(),\n kernelStealth: z.boolean().optional(),\n serpIdentity: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,63}$/).optional(),\n kernelProxyResolution: z.unknown().optional(),\n outputDir: z.string().default('./paa-output'),\n format: z.enum(['json', 'csv', 'both']).default('both'),\n serpOnly: z.boolean().default(false),\n questionsOnly: z.boolean().default(false),\n questionGrowthRecoveryRounds: z.number().int().min(0).max(4).default(2),\n pages: z.number().int().min(1).max(2).default(1),\n includeAllSerpFeatures: z.boolean().default(false),\n includeLocalPack: z.boolean().default(false),\n includeForums: z.boolean().default(false),\n includeVideos: z.boolean().default(false),\n includeAiOverview: z.boolean().default(false),\n includeWhatPeopleSaying: z.boolean().default(false),\n includeShortVideos: z.boolean().default(false),\n recency: z.enum(['day', 'week', 'month', 'year']).optional(),\n softDeadlineMs: z.number().optional(),\n})\n\nexport function recencyToTbs(recency: 'day' | 'week' | 'month' | 'year' | undefined): string | undefined {\n switch (recency) {\n case 'day': return 'qdr:d'\n case 'week': return 'qdr:w'\n case 'month': return 'qdr:m'\n case 'year': return 'qdr:y'\n default: return undefined\n }\n}\n\nexport const MapsPlaceOptionsSchema = z.object({\n businessName: z.string().min(1),\n location: z.string().min(1),\n gl: z.string().length(2).default('us'),\n hl: z.string().length(2).default('en'),\n includeReviews: z.boolean().default(false),\n maxReviews: z.number().int().min(1).max(500).default(50),\n includeServices: z.boolean().default(false),\n includeImages: z.boolean().default(false),\n imageScope: z.enum(['owner', 'all']).default('all'),\n maxImages: z.number().int().min(1).max(250).default(100),\n maxInlineImages: z.number().int().min(0).max(5).default(3),\n kernelApiKey: z.string().optional(),\n kernelProxyId: z.string().optional(),\n headless: z.boolean().default(true),\n})\n\nexport const MapsSearchOptionsSchema = z.object({\n query: z.string().min(1),\n location: z.string().optional(),\n gl: z.string().length(2).default('us'),\n hl: z.string().length(2).default('en'),\n maxResults: z.number().int().min(1).max(50).default(10),\n includeServices: z.boolean().default(false),\n proxyMode: z.enum(['location', 'configured', 'none']).default(DEFAULT_MAPS_PROXY_MODE),\n proxyZip: z.string().regex(/^\\d{5}$/).optional(),\n serpRedirect: z.boolean().optional(),\n forceDirectEgress: z.boolean().optional(),\n debug: z.boolean().default(false),\n kernelApiKey: z.string().optional(),\n kernelProxyId: z.string().optional(),\n kernelProxyResolution: z.unknown().optional(),\n headless: z.boolean().default(true),\n})\n\nexport const RawPAAItemSchema = z.object({\n question: z.string().min(1),\n answer: z.string().optional(),\n sourceTitle: z.string().optional(),\n sourceSite: z.string().optional(),\n sourceCite: z.string().optional(),\n})\n\nexport const RawMapsOverviewSchema = z.object({\n name: z.string().nullable(),\n rating: z.string().nullable(),\n reviewCount: z.string().nullable(),\n category: z.string().nullable(),\n address: z.string().nullable(),\n hoursSummary: z.string().nullable(),\n phone: z.string().nullable(),\n phoneDisplay: z.string().nullable(),\n website: z.string().nullable(),\n plusCode: z.string().nullable(),\n bookingUrl: z.string().nullable(),\n})\n\nexport const RawMapsHoursRowSchema = z.object({\n day: z.string(),\n hours: z.string(),\n})\n\nexport const RawMapsReviewStatsSchema = z.object({\n reviewHistogram: z.array(z.object({\n stars: z.number(),\n count: z.string(),\n })),\n reviewTopics: z.array(z.object({\n label: z.string(),\n count: z.string(),\n })),\n})\n\nexport const RawMapsReviewCardSchema = z.object({\n reviewId: z.string(),\n author: z.string().nullable(),\n stars: z.string().nullable(),\n date: z.string().nullable(),\n text: z.string().nullable(),\n ownerResponse: z.string().nullable(),\n})\n\nexport const RawMapsAboutAttributeSchema = z.object({\n section: z.string(),\n attribute: z.string(),\n})\n","export async function postToMemoryLibrary(args: { title: string; content: string; source: string }): Promise<void> {\n const url = process.env.MCP_MEMORY_INGEST_URL\n const key = process.env.MCP_MEMORY_KEY\n if (!url || !key) return\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ data: { apiKey: key, title: args.title, content: args.content, source: args.source } }),\n })\n const body = (await res.json().catch(() => null)) as { result?: { error?: boolean; message?: string } } | null\n if (!res.ok || body?.result?.error) {\n console.warn('[memory-library-sink] ingest not accepted:', res.status, body?.result?.message ?? '')\n }\n } catch (err) {\n console.warn('[memory-library-sink] ingest failed:', (err as Error)?.message)\n }\n}\n\nexport function memoryLibrarySinkEnabled(): boolean {\n return Boolean(process.env.MCP_MEMORY_INGEST_URL && process.env.MCP_MEMORY_KEY)\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/directory/csv.ts"],"sourcesContent":["export function parseCsv(text: string): string[][] {\n const rows: string[][] = []\n let row: string[] = []\n let field = ''\n let quoted = false\n\n for (let i = 0; i < text.length; i += 1) {\n const ch = text[i]\n const next = text[i + 1]\n\n if (quoted) {\n if (ch === '\"' && next === '\"') {\n field += '\"'\n i += 1\n } else if (ch === '\"') {\n quoted = false\n } else {\n field += ch\n }\n continue\n }\n\n if (ch === '\"') {\n quoted = true\n } else if (ch === ',') {\n row.push(field)\n field = ''\n } else if (ch === '\\n') {\n row.push(field)\n rows.push(row)\n row = []\n field = ''\n } else if (ch !== '\\r') {\n field += ch\n }\n }\n\n if (field.length > 0 || row.length > 0) {\n row.push(field)\n rows.push(row)\n }\n\n return rows\n}\n\nexport function csvRecords(text: string): Array<Record<string, string>> {\n const rows = parseCsv(text).filter(row => row.some(cell => cell.trim() !== ''))\n const header = rows[0]?.map(cell => cell.trim()) ?? []\n return rows.slice(1).map((row) => {\n const record: Record<string, string> = {}\n for (let i = 0; i < header.length; i += 1) {\n record[header[i]] = row[i] ?? ''\n }\n return record\n })\n}\n\nexport function csvCell(value: unknown): string {\n if (value === null || value === undefined) return ''\n const text = String(value)\n return /[\",\\n\\r]/.test(text) ? `\"${text.replace(/\"/g, '\"\"')}\"` : text\n}\n\nexport function rowsToCsv(headers: string[], rows: Array<Record<string, unknown>>): string {\n return [\n headers.join(','),\n ...rows.map(row => headers.map(header => csvCell(row[header])).join(',')),\n ].join('\\n') + '\\n'\n}\n"],"mappings":";AAAO,SAAS,SAAS,MAA0B;AACjD,QAAM,OAAmB,CAAC;AAC1B,MAAI,MAAgB,CAAC;AACrB,MAAI,QAAQ;AACZ,MAAI,SAAS;AAEb,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,UAAM,KAAK,KAAK,CAAC;AACjB,UAAM,OAAO,KAAK,IAAI,CAAC;AAEvB,QAAI,QAAQ;AACV,UAAI,OAAO,OAAO,SAAS,KAAK;AAC9B,iBAAS;AACT,aAAK;AAAA,MACP,WAAW,OAAO,KAAK;AACrB,iBAAS;AAAA,MACX,OAAO;AACL,iBAAS;AAAA,MACX;AACA;AAAA,IACF;AAEA,QAAI,OAAO,KAAK;AACd,eAAS;AAAA,IACX,WAAW,OAAO,KAAK;AACrB,UAAI,KAAK,KAAK;AACd,cAAQ;AAAA,IACV,WAAW,OAAO,MAAM;AACtB,UAAI,KAAK,KAAK;AACd,WAAK,KAAK,GAAG;AACb,YAAM,CAAC;AACP,cAAQ;AAAA,IACV,WAAW,OAAO,MAAM;AACtB,eAAS;AAAA,IACX;AAAA,EACF;AAEA,MAAI,MAAM,SAAS,KAAK,IAAI,SAAS,GAAG;AACtC,QAAI,KAAK,KAAK;AACd,SAAK,KAAK,GAAG;AAAA,EACf;AAEA,SAAO;AACT;AAEO,SAAS,WAAW,MAA6C;AACtE,QAAM,OAAO,SAAS,IAAI,EAAE,OAAO,SAAO,IAAI,KAAK,UAAQ,KAAK,KAAK,MAAM,EAAE,CAAC;AAC9E,QAAM,SAAS,KAAK,CAAC,GAAG,IAAI,UAAQ,KAAK,KAAK,CAAC,KAAK,CAAC;AACrD,SAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ;AAChC,UAAM,SAAiC,CAAC;AACxC,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AACzC,aAAO,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK;AAAA,IAChC;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAEO,SAAS,QAAQ,OAAwB;AAC9C,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,QAAM,OAAO,OAAO,KAAK;AACzB,SAAO,WAAW,KAAK,IAAI,IAAI,IAAI,KAAK,QAAQ,MAAM,IAAI,CAAC,MAAM;AACnE;AAEO,SAAS,UAAU,SAAmB,MAA8C;AACzF,SAAO;AAAA,IACL,QAAQ,KAAK,GAAG;AAAA,IAChB,GAAG,KAAK,IAAI,SAAO,QAAQ,IAAI,YAAU,QAAQ,IAAI,MAAM,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,EAC1E,EAAE,KAAK,IAAI,IAAI;AACjB;","names":[]}
@@ -1,7 +0,0 @@
1
- // src/version.ts
2
- var PACKAGE_VERSION = "0.66.0";
3
-
4
- export {
5
- PACKAGE_VERSION
6
- };
7
- //# sourceMappingURL=chunk-HDYOVGC6.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["export const PACKAGE_VERSION = '0.66.0'\n"],"mappings":";AAAO,IAAM,kBAAkB;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/api/lead-list-enrichment-repository.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto'\nimport { getDb } from './db.js'\nimport type {\n LeadColumnMap,\n LeadListEnrichmentOptions,\n LeadRowEnrichmentResult,\n NormalizedLeadRow,\n} from '../leads/lead-list-enrichment.js'\nimport type { LeadListResultArtifact } from './lead-list-enrichment-artifacts.js'\n\nexport type LeadListEnrichmentJobStatus =\n | 'initializing'\n | 'queued'\n | 'running'\n | 'settling'\n | 'complete'\n | 'partial'\n | 'empty'\n | 'failed'\n | 'cancelled'\n\nexport type LeadListEnrichmentRowStatus = 'pending' | 'running' | 'complete' | 'failed' | 'cancelled'\n\nexport interface DurableLeadListEnrichmentInput {\n rows: NormalizedLeadRow[]\n headers: string[]\n columnMap: LeadColumnMap\n options: LeadListEnrichmentOptions\n sourceDigest?: string | null\n}\n\nexport interface LeadListEnrichmentUsage {\n mapsAttempts: number\n pageAttempts: number\n pageSuccesses: number\n serpSearches: number\n}\n\nexport interface LeadListEnrichmentResultSummary {\n totalRows: number\n completeRows: number\n failedRows: number\n cancelledRows: number\n rowsWithContacts: number\n rowsWithPeople: number\n sample: LeadRowEnrichmentResult[]\n warnings: string[]\n}\n\nexport interface LeadListEnrichmentJobRecord {\n id: string\n userId: number\n idempotencyKey: string\n requestFingerprint: string\n input: DurableLeadListEnrichmentInput\n status: LeadListEnrichmentJobStatus\n totalRows: number\n doneRows: number\n failedRows: number\n cancelledRows: number\n usage: LeadListEnrichmentUsage\n concurrencyLockId: string | null\n debitKey: string\n holdMc: number\n billedMc: number | null\n result: LeadListEnrichmentResultSummary | null\n csvArtifact: LeadListResultArtifact | null\n xlsxArtifact: LeadListResultArtifact | null\n warnings: string[]\n cancelRequestedAt: string | null\n errorCode: string | null\n errorMessage: string | null\n retryable: boolean | null\n createdAt: string\n startedAt: string | null\n completedAt: string | null\n updatedAt: string\n}\n\nexport interface LeadListEnrichmentRowRecord {\n jobId: string\n ordinal: number\n inputDigest: string\n status: LeadListEnrichmentRowStatus\n result: LeadRowEnrichmentResult | null\n usage: LeadListEnrichmentUsage\n errorCode: string | null\n errorMessage: string | null\n retryable: boolean\n durationMs: number\n startedAt: string | null\n completedAt: string | null\n updatedAt: string\n}\n\nexport type LeadListEnrichmentOutboxStatus = 'held' | 'pending' | 'dispatching' | 'dispatched' | 'failed' | 'exhausted'\n\nexport interface LeadListEnrichmentOutboxRecord {\n id: string\n jobId: string\n eventName: 'mcp-scraper/lead-list-enrichment.requested'\n status: LeadListEnrichmentOutboxStatus\n attempts: number\n claimToken: string | null\n lockedUntil: string | null\n nextAttemptAt: string | null\n lastError: string | null\n createdAt: string\n dispatchedAt: string | null\n updatedAt: string\n}\n\nconst EVENT_NAME = 'mcp-scraper/lead-list-enrichment.requested' as const\nconst TERMINAL_JOB_STATUSES = new Set<LeadListEnrichmentJobStatus>(['complete', 'partial', 'empty', 'failed', 'cancelled'])\nconst TERMINAL_ROW_STATUSES = new Set<LeadListEnrichmentRowStatus>(['complete', 'failed', 'cancelled'])\nexport const LEAD_LIST_MAX_DISPATCH_ATTEMPTS = 10\nexport const LEAD_LIST_START_DEADLINE_MINUTES = 6 * 60\nexport const LEAD_LIST_RUNNING_STALE_MINUTES = 60\n\nlet schemaDb: ReturnType<typeof getDb> | null = null\nlet schemaPromise: Promise<void> | null = null\n\nfunction requiredBoundedString(value: string, field: string, max: number): string {\n const trimmed = value.trim()\n if (!trimmed || trimmed.length > max) throw new Error(`${field} is required and must be ${max} characters or fewer`)\n return trimmed\n}\n\nfunction nonNegativeSafeInteger(value: number, field: string): number {\n if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${field} must be a non-negative safe integer`)\n return value\n}\n\nfunction parseJson<T>(value: unknown): T | null {\n if (value == null) return null\n try { return JSON.parse(String(value)) as T } catch { return null }\n}\n\n/** Canonical JSON used for idempotency fingerprints and row input digests. */\nexport function stableCanonicalJson(value: unknown): string {\n const seen = new WeakSet<object>()\n const normalize = (input: unknown): unknown => {\n if (input === null || typeof input === 'string' || typeof input === 'boolean') return input\n if (typeof input === 'number') {\n if (!Number.isFinite(input)) throw new Error('canonical fingerprint input must contain finite numbers')\n return Object.is(input, -0) ? 0 : input\n }\n if (Array.isArray(input)) return input.map(normalize)\n if (typeof input === 'object') {\n if (seen.has(input as object)) throw new Error('canonical fingerprint input must not be cyclic')\n seen.add(input as object)\n const out: Record<string, unknown> = {}\n for (const key of Object.keys(input as Record<string, unknown>).sort()) {\n const child = (input as Record<string, unknown>)[key]\n if (child !== undefined) out[key] = normalize(child)\n }\n seen.delete(input as object)\n return out\n }\n throw new Error(`canonical fingerprint input contains unsupported ${typeof input}`)\n }\n return JSON.stringify(normalize(value))\n}\n\nexport function leadListEnrichmentFingerprint(input: DurableLeadListEnrichmentInput): string {\n return createHash('sha256').update(stableCanonicalJson(input)).digest('hex')\n}\n\nexport function leadListRowInputDigest(row: NormalizedLeadRow): string {\n return createHash('sha256').update(stableCanonicalJson(row)).digest('hex')\n}\n\nfunction emptyUsage(): LeadListEnrichmentUsage {\n return { mapsAttempts: 0, pageAttempts: 0, pageSuccesses: 0, serpSearches: 0 }\n}\n\nfunction rowToJob(row: Record<string, unknown>): LeadListEnrichmentJobRecord {\n const input = parseJson<DurableLeadListEnrichmentInput>(row.input_json)\n if (!input) throw new Error(`lead list enrichment job ${String(row.id ?? '')} has invalid durable input`)\n return {\n id: String(row.id), userId: Number(row.user_id), idempotencyKey: String(row.idempotency_key),\n requestFingerprint: String(row.request_fingerprint), input,\n status: String(row.status) as LeadListEnrichmentJobStatus,\n totalRows: Number(row.total_rows ?? 0), doneRows: Number(row.done_rows ?? 0),\n failedRows: Number(row.failed_rows ?? 0), cancelledRows: Number(row.cancelled_rows ?? 0),\n usage: {\n mapsAttempts: Number(row.maps_attempts ?? 0),\n pageAttempts: Number(row.page_attempts ?? 0),\n pageSuccesses: Number(row.page_successes ?? 0),\n serpSearches: Number(row.serp_searches ?? 0),\n },\n concurrencyLockId: row.concurrency_lock_id == null ? null : String(row.concurrency_lock_id),\n debitKey: String(row.debit_key), holdMc: Number(row.hold_mc ?? 0),\n billedMc: row.billed_mc == null ? null : Number(row.billed_mc),\n result: parseJson<LeadListEnrichmentResultSummary>(row.result_json),\n csvArtifact: parseJson<LeadListResultArtifact>(row.csv_artifact_json),\n xlsxArtifact: parseJson<LeadListResultArtifact>(row.xlsx_artifact_json),\n warnings: parseJson<string[]>(row.warnings_json) ?? [],\n cancelRequestedAt: row.cancel_requested_at == null ? null : String(row.cancel_requested_at),\n errorCode: row.error_code == null ? null : String(row.error_code),\n errorMessage: row.error_message == null ? null : String(row.error_message),\n retryable: row.retryable == null ? null : Number(row.retryable) === 1,\n createdAt: String(row.created_at ?? ''), startedAt: row.started_at == null ? null : String(row.started_at),\n completedAt: row.completed_at == null ? null : String(row.completed_at), updatedAt: String(row.updated_at ?? ''),\n }\n}\n\nfunction rowToCheckpoint(row: Record<string, unknown>): LeadListEnrichmentRowRecord {\n return {\n jobId: String(row.job_id), ordinal: Number(row.ordinal), inputDigest: String(row.input_digest),\n status: String(row.status) as LeadListEnrichmentRowStatus,\n result: parseJson<LeadRowEnrichmentResult>(row.result_json),\n usage: {\n mapsAttempts: Number(row.maps_attempts ?? 0), pageAttempts: Number(row.page_attempts ?? 0),\n pageSuccesses: Number(row.page_successes ?? 0),\n serpSearches: Number(row.serp_searches ?? 0),\n },\n errorCode: row.error_code == null ? null : String(row.error_code),\n errorMessage: row.error_message == null ? null : String(row.error_message),\n retryable: Number(row.retryable ?? 0) === 1, durationMs: Number(row.duration_ms ?? 0),\n startedAt: row.started_at == null ? null : String(row.started_at),\n completedAt: row.completed_at == null ? null : String(row.completed_at), updatedAt: String(row.updated_at ?? ''),\n }\n}\n\nfunction rowToOutbox(row: Record<string, unknown>): LeadListEnrichmentOutboxRecord {\n return {\n id: String(row.id), jobId: String(row.job_id), eventName: EVENT_NAME,\n status: String(row.status) as LeadListEnrichmentOutboxStatus, attempts: Number(row.attempts ?? 0),\n claimToken: row.locked_by == null ? null : String(row.locked_by),\n lockedUntil: row.locked_until == null ? null : String(row.locked_until),\n nextAttemptAt: row.next_attempt_at == null ? null : String(row.next_attempt_at),\n lastError: row.last_error == null ? null : String(row.last_error), createdAt: String(row.created_at ?? ''),\n dispatchedAt: row.dispatched_at == null ? null : String(row.dispatched_at), updatedAt: String(row.updated_at ?? ''),\n }\n}\n\nexport async function ensureLeadListEnrichmentRepositorySchema(): Promise<void> {\n const db = getDb()\n if (schemaDb !== db) { schemaDb = db; schemaPromise = null }\n if (!schemaPromise) schemaPromise = (async () => {\n await db.execute(`CREATE TABLE IF NOT EXISTS lead_list_enrichment_jobs (\n id TEXT PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id), idempotency_key TEXT NOT NULL,\n request_fingerprint TEXT NOT NULL, input_json TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'initializing',\n total_rows INTEGER NOT NULL DEFAULT 0, done_rows INTEGER NOT NULL DEFAULT 0,\n failed_rows INTEGER NOT NULL DEFAULT 0, cancelled_rows INTEGER NOT NULL DEFAULT 0,\n maps_attempts INTEGER NOT NULL DEFAULT 0, page_attempts INTEGER NOT NULL DEFAULT 0,\n page_successes INTEGER NOT NULL DEFAULT 0, serp_searches INTEGER NOT NULL DEFAULT 0,\n concurrency_lock_id TEXT, debit_key TEXT NOT NULL,\n hold_mc INTEGER NOT NULL DEFAULT 0, billed_mc INTEGER, result_json TEXT,\n csv_artifact_json TEXT, xlsx_artifact_json TEXT, warnings_json TEXT NOT NULL DEFAULT '[]',\n cancel_requested_at TEXT, error_code TEXT, error_message TEXT, retryable INTEGER,\n created_at TEXT NOT NULL DEFAULT (datetime('now')), started_at TEXT, completed_at TEXT,\n updated_at TEXT NOT NULL DEFAULT (datetime('now')), UNIQUE(user_id, idempotency_key)\n )`)\n try { await db.execute('ALTER TABLE lead_list_enrichment_jobs ADD COLUMN serp_searches INTEGER NOT NULL DEFAULT 0') } catch {}\n await db.execute('CREATE INDEX IF NOT EXISTS lead_list_enrichment_jobs_user_created ON lead_list_enrichment_jobs(user_id, created_at DESC)')\n await db.execute('CREATE INDEX IF NOT EXISTS lead_list_enrichment_jobs_status ON lead_list_enrichment_jobs(status, updated_at)')\n await db.execute(`CREATE TABLE IF NOT EXISTS lead_list_enrichment_rows (\n job_id TEXT NOT NULL REFERENCES lead_list_enrichment_jobs(id) ON DELETE CASCADE,\n ordinal INTEGER NOT NULL, input_digest TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending',\n result_json TEXT, maps_attempts INTEGER NOT NULL DEFAULT 0, page_attempts INTEGER NOT NULL DEFAULT 0,\n page_successes INTEGER NOT NULL DEFAULT 0, serp_searches INTEGER NOT NULL DEFAULT 0,\n error_code TEXT, error_message TEXT,\n retryable INTEGER NOT NULL DEFAULT 0, duration_ms INTEGER NOT NULL DEFAULT 0,\n started_at TEXT, completed_at TEXT, updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY(job_id, ordinal)\n )`)\n try { await db.execute('ALTER TABLE lead_list_enrichment_rows ADD COLUMN serp_searches INTEGER NOT NULL DEFAULT 0') } catch {}\n await db.execute('CREATE INDEX IF NOT EXISTS lead_list_enrichment_rows_job_status ON lead_list_enrichment_rows(job_id, status, ordinal)')\n await db.execute(`CREATE TABLE IF NOT EXISTS lead_list_enrichment_outbox (\n id TEXT PRIMARY KEY, job_id TEXT NOT NULL UNIQUE REFERENCES lead_list_enrichment_jobs(id) ON DELETE CASCADE,\n event_name TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'held', attempts INTEGER NOT NULL DEFAULT 0,\n locked_by TEXT, locked_until TEXT, next_attempt_at TEXT, last_error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')), dispatched_at TEXT,\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )`)\n await db.execute('CREATE INDEX IF NOT EXISTS lead_list_enrichment_outbox_dispatch ON lead_list_enrichment_outbox(status, next_attempt_at, locked_until, created_at)')\n })()\n return schemaPromise\n}\n\nexport async function getLeadListEnrichmentJob(id: string, userId?: number): Promise<LeadListEnrichmentJobRecord | null> {\n await ensureLeadListEnrichmentRepositorySchema()\n const result = userId == null\n ? await getDb().execute({ sql: 'SELECT * FROM lead_list_enrichment_jobs WHERE id = ? LIMIT 1', args: [id] })\n : await getDb().execute({ sql: 'SELECT * FROM lead_list_enrichment_jobs WHERE id = ? AND user_id = ? LIMIT 1', args: [id, userId] })\n return result.rows[0] ? rowToJob(result.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function getLeadListEnrichmentJobByIdempotencyKey(userId: number, idempotencyKey: string): Promise<LeadListEnrichmentJobRecord | null> {\n await ensureLeadListEnrichmentRepositorySchema()\n const result = await getDb().execute({\n sql: 'SELECT * FROM lead_list_enrichment_jobs WHERE user_id = ? AND idempotency_key = ? LIMIT 1',\n args: [userId, idempotencyKey],\n })\n return result.rows[0] ? rowToJob(result.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function createOrGetLeadListEnrichmentJob(input: {\n id: string; userId: number; idempotencyKey: string; requestFingerprint?: string\n input: DurableLeadListEnrichmentInput; holdMc: number; debitKey: string; concurrencyLockId?: string | null\n}): Promise<{ job: LeadListEnrichmentJobRecord; created: boolean; conflict: boolean }> {\n await ensureLeadListEnrichmentRepositorySchema()\n const id = requiredBoundedString(input.id, 'job id', 200)\n const idempotencyKey = requiredBoundedString(input.idempotencyKey, 'idempotency key', 200)\n const requestFingerprint = input.requestFingerprint ?? leadListEnrichmentFingerprint(input.input)\n requiredBoundedString(requestFingerprint, 'request fingerprint', 200)\n const debitKey = requiredBoundedString(input.debitKey, 'debit key', 500)\n const holdMc = nonNegativeSafeInteger(input.holdMc, 'holdMc')\n if (input.input.rows.length < 1 || input.input.rows.length > 250) throw new Error('lead list job must contain 1 to 250 rows')\n const commands: Array<{ sql: string; args: Array<string | number | null> }> = [{\n sql: `INSERT OR IGNORE INTO lead_list_enrichment_jobs (\n id,user_id,idempotency_key,request_fingerprint,input_json,total_rows,concurrency_lock_id,debit_key,hold_mc\n ) VALUES (?,?,?,?,?,?,?,?,?)`,\n args: [id, input.userId, idempotencyKey, requestFingerprint, JSON.stringify(input.input), input.input.rows.length,\n input.concurrencyLockId ?? null, debitKey, holdMc],\n }, {\n sql: `INSERT OR IGNORE INTO lead_list_enrichment_outbox(id,job_id,event_name,status)\n SELECT 'lead-list-enrichment:' || id,id,?,'held' FROM lead_list_enrichment_jobs\n WHERE user_id=? AND idempotency_key=? AND request_fingerprint=?`,\n args: [EVENT_NAME, input.userId, idempotencyKey, requestFingerprint],\n }]\n input.input.rows.forEach((row, ordinal) => commands.push({\n sql: `INSERT OR IGNORE INTO lead_list_enrichment_rows(job_id,ordinal,input_digest)\n SELECT id,?,? FROM lead_list_enrichment_jobs\n WHERE user_id=? AND idempotency_key=? AND request_fingerprint=?`,\n args: [ordinal, leadListRowInputDigest(row), input.userId, idempotencyKey, requestFingerprint],\n }))\n const results = await getDb().batch(commands, 'write')\n const job = await getLeadListEnrichmentJobByIdempotencyKey(input.userId, idempotencyKey)\n if (!job) throw new Error('lead list enrichment job could not be created')\n return { job, created: Number(results[0]?.rowsAffected ?? 0) > 0, conflict: job.requestFingerprint !== requestFingerprint }\n}\n\nexport async function setInitializingLeadListEnrichmentConcurrencyLock(jobId: string, concurrencyLockId: string | null) {\n await ensureLeadListEnrichmentRepositorySchema()\n await getDb().execute({\n sql: `UPDATE lead_list_enrichment_jobs SET concurrency_lock_id=?, updated_at=datetime('now') WHERE id=? AND status='initializing'`,\n args: [concurrencyLockId, jobId],\n })\n return getLeadListEnrichmentJob(jobId)\n}\n\nexport async function markLeadListEnrichmentJobQueued(jobId: string) {\n await ensureLeadListEnrichmentRepositorySchema()\n await getDb().batch([{\n sql: `UPDATE lead_list_enrichment_jobs SET status='queued',updated_at=datetime('now') WHERE id=? AND status='initializing'`, args: [jobId],\n }, {\n sql: `UPDATE lead_list_enrichment_outbox SET status='pending',next_attempt_at=NULL,updated_at=datetime('now')\n WHERE job_id=? AND status='held' AND EXISTS(SELECT 1 FROM lead_list_enrichment_jobs WHERE id=? AND status='queued')`,\n args: [jobId, jobId],\n }], 'write')\n return getLeadListEnrichmentJob(jobId)\n}\n\nexport async function markLeadListEnrichmentJobRunning(jobId: string) {\n await ensureLeadListEnrichmentRepositorySchema()\n await getDb().execute({\n sql: `UPDATE lead_list_enrichment_jobs SET status='running',started_at=COALESCE(started_at,datetime('now')),\n updated_at=datetime('now') WHERE id=? AND status IN ('queued','running') AND cancel_requested_at IS NULL`, args: [jobId],\n })\n const job = await getLeadListEnrichmentJob(jobId)\n return job?.status === 'running' ? job : null\n}\n\nexport async function claimLeadListEnrichmentRow(jobId: string, ordinal: number): Promise<LeadListEnrichmentRowRecord | null> {\n await ensureLeadListEnrichmentRepositorySchema()\n nonNegativeSafeInteger(ordinal, 'row ordinal')\n await getDb().execute({\n sql: `UPDATE lead_list_enrichment_rows SET status='running',started_at=COALESCE(started_at,datetime('now')),\n updated_at=datetime('now') WHERE job_id=? AND ordinal=? AND status IN ('pending','running')\n AND EXISTS(SELECT 1 FROM lead_list_enrichment_jobs WHERE id=? AND status='running' AND cancel_requested_at IS NULL)`,\n args: [jobId, ordinal, jobId],\n })\n return getLeadListEnrichmentRow(jobId, ordinal)\n}\n\nexport async function getLeadListEnrichmentRow(jobId: string, ordinal: number): Promise<LeadListEnrichmentRowRecord | null> {\n await ensureLeadListEnrichmentRepositorySchema()\n const result = await getDb().execute({ sql: 'SELECT * FROM lead_list_enrichment_rows WHERE job_id=? AND ordinal=? LIMIT 1', args: [jobId, ordinal] })\n return result.rows[0] ? rowToCheckpoint(result.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function listLeadListEnrichmentRows(jobId: string): Promise<LeadListEnrichmentRowRecord[]> {\n await ensureLeadListEnrichmentRepositorySchema()\n const result = await getDb().execute({ sql: 'SELECT * FROM lead_list_enrichment_rows WHERE job_id=? ORDER BY ordinal ASC', args: [jobId] })\n return result.rows.map(row => rowToCheckpoint(row as unknown as Record<string, unknown>))\n}\n\nexport async function saveLeadListEnrichmentRowResult(input: {\n jobId: string; ordinal: number; result: LeadRowEnrichmentResult; durationMs?: number\n}): Promise<LeadListEnrichmentRowRecord | null> {\n await ensureLeadListEnrichmentRepositorySchema()\n const usage = input.result.usage ?? emptyUsage()\n const rowStatus: LeadListEnrichmentRowStatus = input.result.rowStatus === 'failed' ? 'failed' : 'complete'\n const existing = await getLeadListEnrichmentRow(input.jobId, input.ordinal)\n if (existing && TERMINAL_ROW_STATUSES.has(existing.status)) return existing\n await getDb().batch([{\n sql: `UPDATE lead_list_enrichment_rows SET status=?,result_json=?,maps_attempts=?,page_attempts=?,page_successes=?,serp_searches=?,\n error_code=?,error_message=?,retryable=?,duration_ms=?,completed_at=datetime('now'),updated_at=datetime('now')\n WHERE job_id=? AND ordinal=? AND status IN ('pending','running')\n AND EXISTS(SELECT 1 FROM lead_list_enrichment_jobs WHERE id=? AND status='running')`,\n args: [rowStatus, JSON.stringify(input.result), nonNegativeSafeInteger(usage.mapsAttempts, 'mapsAttempts'),\n nonNegativeSafeInteger(usage.pageAttempts, 'pageAttempts'), nonNegativeSafeInteger(usage.pageSuccesses, 'pageSuccesses'),\n nonNegativeSafeInteger(usage.serpSearches, 'serpSearches'),\n input.result.error?.code ?? null, input.result.error?.message ?? null, input.result.error ? 1 : 0,\n nonNegativeSafeInteger(input.durationMs ?? 0, 'durationMs'), input.jobId, input.ordinal, input.jobId],\n }, {\n sql: `UPDATE lead_list_enrichment_jobs SET\n done_rows=(SELECT COUNT(*) FROM lead_list_enrichment_rows WHERE job_id=? AND status IN ('complete','failed','cancelled')),\n failed_rows=(SELECT COUNT(*) FROM lead_list_enrichment_rows WHERE job_id=? AND status='failed'),\n cancelled_rows=(SELECT COUNT(*) FROM lead_list_enrichment_rows WHERE job_id=? AND status='cancelled'),\n maps_attempts=(SELECT COALESCE(SUM(maps_attempts),0) FROM lead_list_enrichment_rows WHERE job_id=?),\n page_attempts=(SELECT COALESCE(SUM(page_attempts),0) FROM lead_list_enrichment_rows WHERE job_id=?),\n page_successes=(SELECT COALESCE(SUM(page_successes),0) FROM lead_list_enrichment_rows WHERE job_id=?),\n serp_searches=(SELECT COALESCE(SUM(serp_searches),0) FROM lead_list_enrichment_rows WHERE job_id=?),\n updated_at=datetime('now') WHERE id=? AND status='running'`,\n args: [input.jobId,input.jobId,input.jobId,input.jobId,input.jobId,input.jobId,input.jobId,input.jobId],\n }], 'write')\n return getLeadListEnrichmentRow(input.jobId, input.ordinal)\n}\n\nexport async function requestLeadListEnrichmentCancellation(jobId: string, userId?: number): Promise<LeadListEnrichmentJobRecord | null> {\n await ensureLeadListEnrichmentRepositorySchema()\n const ownerClause = userId == null ? '' : ' AND user_id=?'\n const args: Array<string | number> = [jobId]\n if (userId != null) args.push(userId)\n await getDb().batch([{\n sql: `UPDATE lead_list_enrichment_jobs SET cancel_requested_at=COALESCE(cancel_requested_at,datetime('now')),\n status=CASE WHEN status IN ('initializing','queued') THEN 'settling' ELSE status END,updated_at=datetime('now')\n WHERE id=?${ownerClause} AND status IN ('initializing','queued','running')`, args,\n }, {\n sql: `UPDATE lead_list_enrichment_outbox SET status='exhausted',locked_by=NULL,locked_until=NULL,next_attempt_at=NULL,\n last_error='Lead list enrichment was cancelled before execution.',updated_at=datetime('now')\n WHERE job_id=? AND EXISTS(SELECT 1 FROM lead_list_enrichment_jobs WHERE id=? AND cancel_requested_at IS NOT NULL AND status='settling')`,\n args: [jobId, jobId],\n }], 'write')\n return getLeadListEnrichmentJob(jobId, userId)\n}\n\nexport async function cancelPendingLeadListEnrichmentRows(jobId: string): Promise<number> {\n await ensureLeadListEnrichmentRepositorySchema()\n const results = await getDb().batch([{\n sql: `UPDATE lead_list_enrichment_rows SET status='cancelled',completed_at=datetime('now'),updated_at=datetime('now')\n WHERE job_id=? AND status='pending' AND EXISTS(SELECT 1 FROM lead_list_enrichment_jobs WHERE id=? AND cancel_requested_at IS NOT NULL)`,\n args: [jobId, jobId],\n }, {\n sql: `UPDATE lead_list_enrichment_jobs SET\n done_rows=(SELECT COUNT(*) FROM lead_list_enrichment_rows WHERE job_id=? AND status IN ('complete','failed','cancelled')),\n failed_rows=(SELECT COUNT(*) FROM lead_list_enrichment_rows WHERE job_id=? AND status='failed'),\n cancelled_rows=(SELECT COUNT(*) FROM lead_list_enrichment_rows WHERE job_id=? AND status='cancelled'),\n status=CASE WHEN status='running' THEN 'settling' ELSE status END,updated_at=datetime('now')\n WHERE id=? AND cancel_requested_at IS NOT NULL`, args: [jobId,jobId,jobId,jobId],\n }], 'write')\n return Number(results[0]?.rowsAffected ?? 0)\n}\n\nexport async function saveLeadListEnrichmentDeliverable(input: {\n jobId: string; result: LeadListEnrichmentResultSummary\n csvArtifact?: LeadListResultArtifact | null; xlsxArtifact?: LeadListResultArtifact | null; warnings?: string[]\n}): Promise<LeadListEnrichmentJobRecord | null> {\n await ensureLeadListEnrichmentRepositorySchema()\n await getDb().execute({\n sql: `UPDATE lead_list_enrichment_jobs SET status='settling',result_json=?,csv_artifact_json=?,xlsx_artifact_json=?,\n warnings_json=?,updated_at=datetime('now') WHERE id=? AND status IN ('running','settling')`,\n args: [JSON.stringify(input.result), input.csvArtifact == null ? null : JSON.stringify(input.csvArtifact),\n input.xlsxArtifact == null ? null : JSON.stringify(input.xlsxArtifact), JSON.stringify(input.warnings ?? input.result.warnings), input.jobId],\n })\n return getLeadListEnrichmentJob(input.jobId)\n}\n\nexport async function completeLeadListEnrichmentJob(input: {\n jobId: string; status: 'complete' | 'partial' | 'empty' | 'cancelled'; billedMc: number\n result?: LeadListEnrichmentResultSummary; csvArtifact?: LeadListResultArtifact | null\n xlsxArtifact?: LeadListResultArtifact | null; warnings?: string[]\n}): Promise<LeadListEnrichmentJobRecord> {\n await ensureLeadListEnrichmentRepositorySchema()\n const current = await getLeadListEnrichmentJob(input.jobId)\n if (!current) throw new Error('lead list enrichment job not found')\n const billedMc = nonNegativeSafeInteger(input.billedMc, 'billedMc')\n if (billedMc > current.holdMc) throw new Error('billedMc cannot exceed the lead list enrichment hold')\n if (TERMINAL_JOB_STATUSES.has(current.status)) {\n if (current.status === input.status && current.billedMc === billedMc) return current\n throw new Error('lead list enrichment job already has a conflicting terminal result')\n }\n await getDb().execute({\n sql: `UPDATE lead_list_enrichment_jobs SET status=?,billed_mc=?,result_json=COALESCE(?,result_json),\n csv_artifact_json=CASE WHEN ?=1 THEN ? ELSE csv_artifact_json END,\n xlsx_artifact_json=CASE WHEN ?=1 THEN ? ELSE xlsx_artifact_json END,\n warnings_json=CASE WHEN ?=1 THEN ? ELSE warnings_json END,error_code=NULL,error_message=NULL,retryable=NULL,\n completed_at=datetime('now'),updated_at=datetime('now') WHERE id=? AND status NOT IN ('complete','partial','empty','failed','cancelled')`,\n args: [input.status,billedMc,input.result === undefined ? null : JSON.stringify(input.result),\n input.csvArtifact === undefined ? 0 : 1,input.csvArtifact == null ? null : JSON.stringify(input.csvArtifact),\n input.xlsxArtifact === undefined ? 0 : 1,input.xlsxArtifact == null ? null : JSON.stringify(input.xlsxArtifact),\n input.warnings === undefined ? 0 : 1,JSON.stringify(input.warnings ?? []),input.jobId],\n })\n const completed = await getLeadListEnrichmentJob(input.jobId)\n if (!completed || completed.status !== input.status || completed.billedMc !== billedMc) throw new Error('lead list enrichment job completion conflicted')\n return completed\n}\n\nexport async function failLeadListEnrichmentJob(input: {\n jobId: string; errorCode: string; errorMessage: string; retryable: boolean; billedMc?: number | null\n}): Promise<LeadListEnrichmentJobRecord> {\n await ensureLeadListEnrichmentRepositorySchema()\n const current = await getLeadListEnrichmentJob(input.jobId)\n if (!current) throw new Error('lead list enrichment job not found')\n if (current.status !== 'failed' && TERMINAL_JOB_STATUSES.has(current.status)) throw new Error('completed lead list enrichment job cannot be failed')\n if (current.status === 'failed') return current\n const billedMc = input.billedMc == null ? null : nonNegativeSafeInteger(input.billedMc, 'billedMc')\n if (billedMc != null && billedMc > current.holdMc) throw new Error('billedMc cannot exceed the lead list enrichment hold')\n await getDb().execute({\n sql: `UPDATE lead_list_enrichment_jobs SET status='failed',billed_mc=COALESCE(?,billed_mc),error_code=?,error_message=?,retryable=?,\n completed_at=datetime('now'),updated_at=datetime('now') WHERE id=? AND status NOT IN ('complete','partial','empty','failed','cancelled')`,\n args: [billedMc,requiredBoundedString(input.errorCode,'error code',200),requiredBoundedString(input.errorMessage,'error message',2000),\n input.retryable ? 1 : 0,input.jobId],\n })\n const failed = await getLeadListEnrichmentJob(input.jobId)\n if (!failed || failed.status !== 'failed') throw new Error('lead list enrichment job failure conflicted')\n return failed\n}\n\nexport async function recordLeadListEnrichmentSettlement(jobId: string, billedMc: number) {\n await ensureLeadListEnrichmentRepositorySchema()\n const current = await getLeadListEnrichmentJob(jobId)\n if (!current) throw new Error('lead list enrichment job not found')\n const amount = nonNegativeSafeInteger(billedMc, 'billedMc')\n if (amount > current.holdMc) throw new Error('billedMc cannot exceed the lead list enrichment hold')\n await getDb().execute({\n sql: `UPDATE lead_list_enrichment_jobs SET billed_mc=COALESCE(billed_mc,?),updated_at=datetime('now')\n WHERE id=? AND status IN ('settling','failed','cancelled')`, args: [amount,jobId],\n })\n const updated = await getLeadListEnrichmentJob(jobId)\n if (!updated || updated.billedMc !== amount) throw new Error('lead list enrichment settlement conflicts with durable amount')\n return updated\n}\n\nexport async function listLeadListEnrichmentJobsNeedingSettlement(limit = 25) {\n await ensureLeadListEnrichmentRepositorySchema()\n const result = await getDb().execute({\n sql: `SELECT * FROM lead_list_enrichment_jobs WHERE status='settling' OR (status IN ('failed','cancelled') AND billed_mc IS NULL)\n ORDER BY updated_at ASC LIMIT ?`, args: [Math.max(1,Math.min(100,Math.trunc(limit)))],\n })\n return result.rows.map(row => rowToJob(row as unknown as Record<string, unknown>))\n}\n\nexport async function listExpiredLeadListEnrichmentStarts(limit=25,maxAttempts=LEAD_LIST_MAX_DISPATCH_ATTEMPTS,maxAgeMinutes=LEAD_LIST_START_DEADLINE_MINUTES) {\n await ensureLeadListEnrichmentRepositorySchema()\n const result = await getDb().execute({\n sql: `SELECT j.* FROM lead_list_enrichment_jobs j JOIN lead_list_enrichment_outbox o ON o.job_id=j.id\n WHERE j.status IN ('initializing','queued') AND j.billed_mc IS NULL AND (o.attempts>=? OR j.created_at<=datetime('now',?))\n AND (o.status IN ('held','pending','failed','dispatched') OR (o.status='dispatching' AND o.locked_until<=datetime('now')))\n ORDER BY j.updated_at ASC LIMIT ?`,\n args: [Math.max(1,Math.trunc(maxAttempts)),`-${Math.max(15,Math.trunc(maxAgeMinutes))} minutes`,Math.max(1,Math.min(100,Math.trunc(limit)))],\n })\n return result.rows.map(row => rowToJob(row as unknown as Record<string,unknown>))\n}\n\nexport async function failExpiredLeadListEnrichmentStart(jobId:string,maxAttempts=LEAD_LIST_MAX_DISPATCH_ATTEMPTS,maxAgeMinutes=LEAD_LIST_START_DEADLINE_MINUTES) {\n await ensureLeadListEnrichmentRepositorySchema()\n const results=await getDb().batch([{\n sql:`UPDATE lead_list_enrichment_jobs SET status='failed',billed_mc=CASE\n WHEN EXISTS(SELECT 1 FROM billing_debits d WHERE d.idempotency_key=lead_list_enrichment_jobs.debit_key\n AND d.user_id=lead_list_enrichment_jobs.user_id AND d.status IN ('applied','settled')) THEN NULL ELSE 0 END,\n error_code='lead_list_dispatch_expired',\n error_message='Lead list enrichment did not start before its dispatch deadline; the unused hold will be refunded.',retryable=1,\n completed_at=datetime('now'),updated_at=datetime('now') WHERE id=? AND status IN ('initializing','queued') AND billed_mc IS NULL\n AND EXISTS(SELECT 1 FROM lead_list_enrichment_outbox o WHERE o.job_id=lead_list_enrichment_jobs.id AND\n (o.attempts>=? OR lead_list_enrichment_jobs.created_at<=datetime('now',?)))`,\n args:[jobId,Math.max(1,Math.trunc(maxAttempts)),`-${Math.max(15,Math.trunc(maxAgeMinutes))} minutes`],\n },{\n sql:`UPDATE lead_list_enrichment_outbox SET status='exhausted',locked_by=NULL,locked_until=NULL,next_attempt_at=NULL,\n last_error='Lead list enrichment dispatch deadline exhausted.',updated_at=datetime('now') WHERE job_id=?\n AND EXISTS(SELECT 1 FROM lead_list_enrichment_jobs WHERE id=? AND error_code='lead_list_dispatch_expired')`,args:[jobId,jobId],\n }],'write')\n return Number(results[0]?.rowsAffected??0)===1?getLeadListEnrichmentJob(jobId):null\n}\n\nexport async function listStaleRunningLeadListEnrichmentJobs(limit=25,staleMinutes=LEAD_LIST_RUNNING_STALE_MINUTES) {\n await ensureLeadListEnrichmentRepositorySchema()\n const result=await getDb().execute({sql:`SELECT * FROM lead_list_enrichment_jobs WHERE status='running' AND billed_mc IS NULL\n AND updated_at<=datetime('now',?) ORDER BY updated_at ASC LIMIT ?`,args:[`-${Math.max(15,Math.trunc(staleMinutes))} minutes`,Math.max(1,Math.min(100,Math.trunc(limit)))]})\n return result.rows.map(row=>rowToJob(row as unknown as Record<string,unknown>))\n}\n\nexport async function failStaleRunningLeadListEnrichmentJob(jobId:string,staleMinutes=LEAD_LIST_RUNNING_STALE_MINUTES) {\n await ensureLeadListEnrichmentRepositorySchema()\n const results=await getDb().batch([{\n sql:`UPDATE lead_list_enrichment_jobs SET\n status=CASE WHEN cancel_requested_at IS NULL THEN 'failed' ELSE 'settling' END,\n error_code=CASE WHEN cancel_requested_at IS NULL THEN 'lead_list_worker_stale' ELSE NULL END,\n error_message=CASE WHEN cancel_requested_at IS NULL\n THEN 'Lead list enrichment stopped without a heartbeat; completed row checkpoints were preserved.' ELSE NULL END,\n retryable=CASE WHEN cancel_requested_at IS NULL THEN 1 ELSE NULL END,\n completed_at=CASE WHEN cancel_requested_at IS NULL THEN datetime('now') ELSE NULL END,updated_at=datetime('now')\n WHERE id=? AND status='running' AND billed_mc IS NULL\n AND updated_at<=datetime('now',?)`,args:[jobId,`-${Math.max(15,Math.trunc(staleMinutes))} minutes`],\n },{\n sql:`UPDATE lead_list_enrichment_outbox SET status='exhausted',locked_by=NULL,locked_until=NULL,next_attempt_at=NULL,\n last_error='Lead list enrichment worker heartbeat expired.',updated_at=datetime('now') WHERE job_id=?\n AND EXISTS(SELECT 1 FROM lead_list_enrichment_jobs WHERE id=? AND (error_code='lead_list_worker_stale'\n OR (status='settling' AND cancel_requested_at IS NOT NULL)))`,args:[jobId,jobId],\n }],'write')\n return Number(results[0]?.rowsAffected??0)===1?getLeadListEnrichmentJob(jobId):null\n}\n\nexport async function claimLeadListEnrichmentOutbox(input:{workerId:string;limit?:number;leaseSeconds?:number}) {\n await ensureLeadListEnrichmentRepositorySchema()\n const workerId=requiredBoundedString(input.workerId,'outbox worker id',160)\n const limit=Math.max(1,Math.min(100,Math.trunc(input.limit??25)))\n const leaseSeconds=Math.max(30,Math.min(3600,Math.trunc(input.leaseSeconds??120)))\n const claimToken=`${workerId}:${randomUUID()}`\n const results=await getDb().batch([{\n sql:`UPDATE lead_list_enrichment_outbox SET status='dispatching',attempts=attempts+1,locked_by=?,\n locked_until=datetime('now',?),updated_at=datetime('now') WHERE id IN (SELECT id FROM lead_list_enrichment_outbox\n WHERE attempts<${LEAD_LIST_MAX_DISPATCH_ATTEMPTS} AND created_at>datetime('now','-${LEAD_LIST_START_DEADLINE_MINUTES} minutes')\n AND (status='pending' OR (status='failed' AND (next_attempt_at IS NULL OR next_attempt_at<=datetime('now')))\n OR (status='dispatching' AND locked_until<=datetime('now'))) ORDER BY created_at ASC LIMIT ?)`,\n args:[claimToken,`+${leaseSeconds} seconds`,limit],\n },{sql:`SELECT * FROM lead_list_enrichment_outbox WHERE locked_by=? AND status='dispatching' ORDER BY created_at ASC`,args:[claimToken]}],'write')\n return results[1]?.rows.map(row=>rowToOutbox(row as unknown as Record<string,unknown>))??[]\n}\n\nexport async function markLeadListEnrichmentOutboxDispatched(id:string,claimToken:string) {\n await ensureLeadListEnrichmentRepositorySchema()\n const result=await getDb().execute({sql:`UPDATE lead_list_enrichment_outbox SET status='dispatched',locked_by=NULL,\n locked_until=NULL,next_attempt_at=NULL,last_error=NULL,dispatched_at=datetime('now'),updated_at=datetime('now')\n WHERE id=? AND status='dispatching' AND locked_by=?`,args:[id,claimToken]})\n return Number(result.rowsAffected??0)>0\n}\n\nexport async function markLeadListEnrichmentOutboxFailed(input:{id:string;claimToken:string;error:string;retryAfterSeconds?:number}) {\n await ensureLeadListEnrichmentRepositorySchema()\n const retryAfterSeconds=Math.max(1,Math.min(86400,Math.trunc(input.retryAfterSeconds??30)))\n const result=await getDb().execute({sql:`UPDATE lead_list_enrichment_outbox SET status='failed',locked_by=NULL,\n locked_until=NULL,next_attempt_at=datetime('now',?),last_error=?,updated_at=datetime('now')\n WHERE id=? AND status='dispatching' AND locked_by=?`,args:[`+${retryAfterSeconds} seconds`,String(input.error).slice(0,2000),input.id,input.claimToken]})\n return Number(result.rowsAffected??0)>0\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/media-extractor.ts","../src/api/extraction-problems.ts","../src/api/commons-embeddings.ts"],"sourcesContent":["import { createWriteStream, mkdirSync, rmSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join, extname, basename } from 'node:path'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable, Transform } from 'node:stream'\nimport { validatePublicHttpUrl } from '../api/url-utils.js'\nimport type { RenderedMediaDiscovery } from './rendered-media.js'\nimport { loadHtmlDocument, type HtmlDocument } from './html-document.js'\n\nconst AD_PATTERNS: string[] = [\n 'doubleclick.net', 'googlesyndication.com', 'googletagmanager.com',\n 'google-analytics.com', 'googletagservices.com', 'adservice.google',\n 'googletag.', 'pagead2.googlesyndication',\n 'facebook.net/tr', 'connect.facebook.net', 'fbcdn.net/rsrc',\n 'analytics.twitter.com', 'static.ads-twitter.com', 'ads.twitter.com',\n 't.co/i/adsct', 'pixel.advertising.com',\n 'hotjar.com', 'clarity.ms', 'quantserve.com', 'scorecardresearch.com',\n 'newrelic.com', 'nr-data.net', 'segment.io', 'segment.com',\n 'amplitude.com', 'mixpanel.com', 'heap.io', 'fullstory.com',\n 'moatads.com', 'criteo.com', 'adsrvr.org', 'rubiconproject.com',\n 'pubmatic.com', 'openx.net', 'appnexus.com', 'amazon-adsystem.com',\n 'media.net', 'yieldmo.com', 'triplelift.com', 'sharethrough.com',\n 'prebid.', 'smaato.net', 'indexworm.com', 'casalemedia.com',\n 'outbrain.com', 'taboola.com', 'revcontent.com', 'mgid.com',\n 'tawk.to', 'intercom.io', 'drift.com', 'hs-scripts.com',\n 'zopim.com', 'livechatinc.com', 'userlike.com',\n 'onetrust.com', 'cookielaw.org', 'cookieinformation.com', 'trustarc.com',\n '/ads/', '/ad/', '/banner/', '/banners/', '/pixel/', '/beacon/',\n '/tracking/', '/tracker/', '/remarketing/', '/conversion/',\n '1x1.gif', 'spacer.gif', 'blank.gif', 'transparent.gif',\n]\n\nconst IMAGE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif', '.avif', '.svg', '.tiff'])\nconst VIDEO_EXTS = new Set(['.mp4', '.webm', '.mov', '.avi', '.m4v', '.ogv', '.mkv'])\nconst AUDIO_EXTS = new Set(['.mp3', '.wav', '.ogg', '.aac', '.m4a', '.flac', '.opus'])\n\nexport type MediaType = 'image' | 'video' | 'audio'\n\nexport interface MediaAsset {\n url: string\n type: MediaType\n mimeType: string | null\n filename: string\n savedPath: string | null\n sizeBytes: number | null\n discoveryMethods: string[]\n altTexts: string[]\n contexts: string[]\n width: number | null\n height: number | null\n variants: string[]\n finalUrl?: string | null\n duplicateOf?: string | null\n sha256?: string | null\n downloadStatus?: 'downloaded' | 'failed' | 'not_attempted'\n downloadError?: string | null\n inlinePreview?: { data: string; mimeType: string } | null\n}\n\nexport interface MediaManifest {\n pageUrl: string\n outputDir: string | null\n assets: MediaAsset[]\n filteredCount: number\n totalFound: number\n staticFound: number\n renderedFound: number\n completeness: 'complete' | 'partial'\n exhausted: boolean\n stopReason: 'page_exhausted' | 'asset_limit' | 'scroll_round_limit' | 'render_unavailable'\n scrollRounds: number\n warnings: string[]\n artifact: {\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 localPath: string | null\n } | null\n}\n\nexport interface MediaExtractOptions {\n types?: MediaType[]\n outputDir?: string | null\n rendered?: RenderedMediaDiscovery | null\n maxAssets?: number\n}\n\nexport interface DiscoveredMediaCandidate {\n url: string\n type: MediaType | null\n discoveryMethods: string[]\n altTexts: string[]\n contexts: string[]\n width: number | null\n height: number | null\n}\n\nfunction isAdUrl(url: string): boolean {\n const lower = url.toLowerCase()\n return AD_PATTERNS.some(p => lower.includes(p))\n}\n\nfunction isDataUri(url: string): boolean {\n return url.startsWith('data:')\n}\n\nexport function typeFromUrl(url: string): MediaType | null {\n try {\n const ext = extname(new URL(url).pathname).toLowerCase()\n if (IMAGE_EXTS.has(ext)) return 'image'\n if (VIDEO_EXTS.has(ext)) return 'video'\n if (AUDIO_EXTS.has(ext)) return 'audio'\n } catch { /* non-parseable URL */ }\n return null\n}\n\nexport function typeFromMime(mime: string): MediaType | null {\n const lower = mime.toLowerCase()\n if (lower.startsWith('image/')) return 'image'\n if (lower.startsWith('video/')) return 'video'\n if (lower.startsWith('audio/')) return 'audio'\n return null\n}\n\nfunction resolveUrl(raw: string, base: string): string | null {\n if (!raw || isDataUri(raw)) return null\n try { return new URL(raw, base).href } catch { return null }\n}\n\nfunction safeFilename(url: string, index: number): string {\n try {\n const u = new URL(url)\n const base = basename(u.pathname).replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 80)\n return base || `asset-${index}`\n } catch {\n return `asset-${index}`\n }\n}\n\nfunction boundedText(value: string | undefined | null, max: number): string | null {\n const normalized = value?.replace(/&(?:amp|#0?38|#x26);/gi, '&').replace(/\\s+/g, ' ').trim()\n return normalized ? normalized.slice(0, max) : null\n}\n\nfunction dimensionsFromUrl(rawUrl: string): { width: number; height: number } | null {\n const match = rawUrl.match(/[-_/](\\d{2,5})x(\\d{2,5})(?=[._/?#-]|$)/i)\n return match ? { width: Number(match[1]), height: Number(match[2]) } : null\n}\n\nexport function mediaFamilyKey(rawUrl: string): string {\n try {\n const url = new URL(rawUrl)\n url.hash = ''\n url.pathname = url.pathname.replace(/-(?:\\d{2,5}x\\d{2,5}|scaled|e\\d{6,})(?=\\.[a-z0-9]{2,6}$)/i, '')\n for (const key of ['w', 'width', 'h', 'height', 'resize']) url.searchParams.delete(key)\n url.searchParams.sort()\n return url.href\n } catch {\n return rawUrl\n }\n}\n\nfunction candidateScore(candidate: DiscoveredMediaCandidate): number {\n const area = (candidate.width ?? 0) * (candidate.height ?? 0)\n const methodBoost = candidate.discoveryMethods.some(method => method.includes('lightbox')) ? 1_000_000_000_000 : 0\n const originalBoost = /-(?:\\d{2,5}x\\d{2,5}|scaled|e\\d{6,})(?=\\.[a-z0-9]{2,6}(?:[?#]|$))/i.test(candidate.url) ? 0 : 100_000_000_000\n return methodBoost + originalBoost + area\n}\n\nfunction mergeCandidate(target: Map<string, DiscoveredMediaCandidate>, incoming: DiscoveredMediaCandidate): void {\n const existing = target.get(incoming.url)\n if (!existing) {\n target.set(incoming.url, incoming)\n return\n }\n existing.discoveryMethods = [...new Set([...existing.discoveryMethods, ...incoming.discoveryMethods])]\n existing.altTexts = [...new Set([...existing.altTexts, ...incoming.altTexts])]\n existing.contexts = [...new Set([...existing.contexts, ...incoming.contexts])]\n if (!existing.type && incoming.type) existing.type = incoming.type\n if ((incoming.width ?? 0) * (incoming.height ?? 0) > (existing.width ?? 0) * (existing.height ?? 0)) {\n existing.width = incoming.width\n existing.height = incoming.height\n }\n}\n\nexport function discoverStaticMedia(html: string | HtmlDocument, baseUrl: string): DiscoveredMediaCandidate[] {\n const found = new Map<string, DiscoveredMediaCandidate>()\n const add = (\n raw: string | undefined,\n method: string,\n explicitType: MediaType | null = null,\n alt?: string | null,\n context?: string | null,\n dimensions?: { width: number; height: number } | null,\n ) => {\n const resolved = raw ? resolveUrl(raw.trim().replace(/&amp;/g, '&'), baseUrl) : null\n if (!resolved || isAdUrl(resolved)) return\n const inferredDimensions = dimensions ?? dimensionsFromUrl(resolved)\n mergeCandidate(found, {\n url: resolved,\n type: explicitType ?? typeFromUrl(resolved),\n discoveryMethods: [method],\n altTexts: alt ? [alt] : [],\n contexts: context ? [context] : [],\n width: inferredDimensions?.width ?? null,\n height: inferredDimensions?.height ?? null,\n })\n }\n const scanSrcset = (value: string | undefined, method: string, type: MediaType, alt?: string | null) => {\n for (const part of (value ?? '').split(',')) {\n const [url, descriptor] = part.trim().split(/\\s+/)\n if (!url) continue\n const width = descriptor?.match(/^(\\d+)w$/)?.[1]\n add(url, method, type, alt, null, width ? { width: Number(width), height: 0 } : null)\n }\n }\n const scan = (document: HtmlDocument, suffix = '') => {\n const $ = document.$\n for (const image of document.images) {\n const attrs = image.attributes\n const alt = boundedText(image.alt, 500)\n const dimensions = image.width && image.height ? { width: image.width, height: image.height } : null\n for (const attr of ['src', 'data-src', 'data-lazy-src', 'data-original', 'data-bg', 'data-background', 'data-bg-url', 'data-lazy-bg', 'data-echo']) {\n const value = attrs[attr]\n add(value, `static-${attr}${suffix}`, 'image', alt, null, dimensions)\n }\n scanSrcset(attrs.srcset, `static-srcset${suffix}`, 'image', alt)\n scanSrcset(attrs['data-srcset'], `static-data-srcset${suffix}`, 'image', alt)\n }\n $('source').each((_index, element) => {\n const node = $(element)\n const declared = node.attr('type') ?? ''\n const type: MediaType = declared.startsWith('video/') ? 'video' : declared.startsWith('audio/') ? 'audio' : 'image'\n add(node.attr('src'), `static-source${suffix}`, type)\n scanSrcset(node.attr('srcset'), `static-source-srcset${suffix}`, type)\n })\n $('video,audio').each((_index, element) => {\n const node = $(element)\n const type = element.tagName.toLowerCase() as 'video' | 'audio'\n add(node.attr('src'), `static-${type}${suffix}`, type)\n if (type === 'video') add(node.attr('poster'), `static-video-poster${suffix}`, 'image')\n })\n $('meta').each((_index, element) => {\n const node = $(element)\n const prop = (node.attr('property') ?? node.attr('name') ?? '').toLowerCase()\n if (prop === 'og:image' || prop === 'og:image:url' || prop === 'twitter:image' || prop === 'twitter:image:src') {\n add(node.attr('content'), `static-${prop}${suffix}`, 'image')\n }\n })\n $('link[href]').each((_index, element) => {\n const node = $(element)\n const rel = (node.attr('rel') ?? '').toLowerCase().split(/\\s+/)\n if (rel.some(value => value === 'icon' || value === 'apple-touch-icon')) {\n add(node.attr('href'), `static-icon${suffix}`, 'image')\n }\n })\n $('svg image').each((_index, element) => {\n const node = $(element)\n add(node.attr('href') ?? node.attr('xlink:href'), `static-svg-image${suffix}`, 'image')\n })\n $('a[href]').has('img').each((_index, element) => {\n const node = $(element)\n const href = node.attr('href')\n if (!href) return\n if (\n /lightbox|gallery|fancybox|glightbox|swipebox|elementor-open-lightbox/i.test(`${node.attr('class') ?? ''} ${node.attr('data-elementor-open-lightbox') ?? ''}`)\n || /\\.(?:avif|gif|jpe?g|png|svg|webp)(?:[?#]|$)/i.test(href)\n ) {\n const alt = boundedText(node.find('img').first().attr('alt'), 500)\n add(href, `static-lightbox-target${suffix}`, 'image', alt)\n }\n })\n const cssSources = $('style').toArray().map(element => $(element).text())\n $('[style]').each((_index, element) => { cssSources.push($(element).attr('style') ?? '') })\n for (const match of cssSources.join('\\n').matchAll(/(?:background(?:-image)?\\s*:\\s*)?url\\(\\s*(?:[\"']|&quot;|&#0?34;|&apos;|&#0?39;)?([^\"')&]+)(?:[\"']|&quot;|&#0?34;|&apos;|&#0?39;)?\\s*\\)/gi)) {\n add(match[1], `static-css-url${suffix}`, 'image')\n }\n for (const match of document.sourceHtml.matchAll(/[\"'](https?:\\/\\/[^\"'\\s<>]+\\.(?:avif|gif|jpe?g|png|svg|webp|mp4|webm|mp3|ogg)(?:\\?[^\"'\\s<>]*)?)[\"']/gi)) {\n add(match[1], `static-bare-url${suffix}`)\n }\n }\n\n const document = typeof html === 'string' ? loadHtmlDocument(html) : html\n scan(document)\n document.$('script').each((_index, element) => {\n const source = document.$(element).text()\n const unescaped = source\n .replace(/\\\\u003[cC]/g, '<').replace(/\\\\u003[eE]/g, '>').replace(/\\\\u0026/gi, '&')\n .replace(/\\\\[\"']/g, match => match.slice(1)).replace(/\\\\\\//g, '/')\n if (unescaped !== source && /<(?:img|source|video|audio)\\b/i.test(unescaped)) {\n scan(loadHtmlDocument(unescaped), '-json-unescaped')\n }\n })\n return [...found.values()]\n}\n\nexport function mergeMediaDiscovery(\n staticAssets: DiscoveredMediaCandidate[],\n rendered: RenderedMediaDiscovery | null | undefined,\n maxAssets = 100,\n): Array<DiscoveredMediaCandidate & { variants: string[] }> {\n const exact = new Map<string, DiscoveredMediaCandidate>()\n for (const asset of staticAssets) mergeCandidate(exact, asset)\n for (const asset of rendered?.assets ?? []) mergeCandidate(exact, { ...asset, type: asset.type })\n\n const families = new Map<string, DiscoveredMediaCandidate[]>()\n for (const asset of exact.values()) {\n const key = mediaFamilyKey(asset.url)\n const family = families.get(key) ?? []\n family.push(asset)\n families.set(key, family)\n }\n return [...families.values()]\n .map(family => {\n const ordered = [...family].sort((a, b) => candidateScore(b) - candidateScore(a) || a.url.localeCompare(b.url))\n const preferred = { ...ordered[0] }\n preferred.discoveryMethods = [...new Set(family.flatMap(asset => asset.discoveryMethods))]\n preferred.altTexts = [...new Set(family.flatMap(asset => asset.altTexts))]\n preferred.contexts = [...new Set(family.flatMap(asset => asset.contexts))]\n return { ...preferred, variants: ordered.slice(1).map(asset => asset.url) }\n })\n .sort((a, b) => candidateScore(b) - candidateScore(a) || a.url.localeCompare(b.url))\n .slice(0, Math.max(1, maxAssets))\n}\n\nexport function extractMediaUrls(html: string, baseUrl: string): string[] {\n return mergeMediaDiscovery(discoverStaticMedia(html, baseUrl), null, 2_000).map(asset => asset.url)\n}\n\nexport async function downloadAsset(\n url: string,\n destDir: string,\n filename: string,\n options: {\n expectedType?: MediaType\n maxBytes?: number\n consumeBytes?: (bytes: number) => boolean\n } = {},\n): Promise<{ savedPath: string; sizeBytes: number; mimeType: string | null }> {\n const maxBytes = Math.max(1, Math.min(options.maxBytes ?? 50 * 1024 * 1024, 100 * 1024 * 1024))\n let target = url\n let res: Response | null = null\n for (let redirects = 0; redirects <= 5; redirects++) {\n const checked = await validatePublicHttpUrl(target, { field: 'media URL' })\n if (checked.error || !checked.parsed) throw new Error(checked.error ?? 'Media URL was rejected')\n res = await fetch(checked.parsed.href, {\n signal: AbortSignal.timeout(15_000),\n redirect: 'manual',\n })\n if (res.status >= 300 && res.status < 400) {\n const location = res.headers.get('location')\n if (!location) throw new Error(`HTTP ${res.status} redirect did not include Location`)\n target = new URL(location, checked.parsed.href).href\n res = null\n continue\n }\n break\n }\n if (!res) throw new Error('Media download exceeded five redirects')\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n if (!res.body) throw new Error('Empty response body')\n\n const mimeType = res.headers.get('content-type')?.split(';')[0].trim() ?? null\n if (options.expectedType && (!mimeType || typeFromMime(mimeType) !== options.expectedType)) {\n throw new Error(`Expected ${options.expectedType} content but received ${mimeType ?? 'no content-type'}`)\n }\n const declaredBytes = Number(res.headers.get('content-length') ?? 0)\n if (Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {\n throw new Error(`Media asset exceeds ${maxBytes} byte limit`)\n }\n\n let dest = join(destDir, filename)\n if (mimeType && !extname(filename)) {\n const mimeExt: Record<string, string> = {\n 'image/jpeg': '.jpg', 'image/png': '.png', 'image/webp': '.webp',\n 'image/gif': '.gif', 'image/svg+xml': '.svg', 'image/avif': '.avif',\n 'video/mp4': '.mp4', 'video/webm': '.webm',\n 'audio/mpeg': '.mp3', 'audio/ogg': '.ogg', 'audio/wav': '.wav',\n }\n const ext = mimeExt[mimeType]\n if (ext) dest = dest + ext\n }\n\n const writer = createWriteStream(dest)\n await new Promise<void>((resolve, reject) => {\n writer.once('open', () => resolve())\n writer.once('error', reject)\n })\n let streamedBytes = 0\n const limiter = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n const bytes = chunk.length\n if (streamedBytes + bytes > maxBytes) {\n callback(new Error(`Media asset exceeds ${maxBytes} byte limit`))\n return\n }\n if (options.consumeBytes && !options.consumeBytes(bytes)) {\n callback(new Error('Media export total byte limit exceeded'))\n return\n }\n streamedBytes += bytes\n callback(null, chunk)\n },\n })\n try {\n await pipeline(Readable.fromWeb(res.body as import('stream/web').ReadableStream), limiter, writer)\n } catch (error) {\n writer.destroy()\n rmSync(dest, { force: true })\n throw error\n }\n\n const { statSync } = await import('node:fs')\n const sizeBytes = statSync(dest).size\n\n return { savedPath: dest, sizeBytes, mimeType }\n}\n\nexport async function harvestPageMedia(\n html: string,\n pageUrl: string,\n options: MediaExtractOptions = {},\n): Promise<MediaManifest> {\n const types = options.types ?? ['image', 'video', 'audio']\n const typesSet = new Set(types)\n const staticAssets = discoverStaticMedia(html, pageUrl)\n const maxAssets = Math.max(1, Math.min(options.maxAssets ?? 100, 250))\n const allDiscovered = mergeMediaDiscovery(staticAssets, options.rendered, 2_000)\n const discovered = allDiscovered.slice(0, maxAssets)\n const totalFound = new Set([\n ...staticAssets.map(asset => asset.url),\n ...(options.rendered?.assets ?? []).map(asset => asset.url),\n ]).size\n const kept = discovered.flatMap(asset => {\n const type = asset.type ?? typeFromUrl(asset.url)\n return type && typesSet.has(type) ? [{ ...asset, type }] : []\n })\n const filteredCount = totalFound - kept.length\n const retentionLimitReached = allDiscovered.length > maxAssets\n const exhausted = options.rendered?.exhausted === true && !retentionLimitReached\n const stopReason = retentionLimitReached ? 'asset_limit' : options.rendered?.stopReason ?? 'render_unavailable'\n const warnings = retentionLimitReached\n ? [`Media discovery found ${allDiscovered.length} responsive families; only the requested ${maxAssets} records were retained.`]\n : options.rendered\n ? options.rendered.exhausted ? [] : [`Rendered media discovery stopped with ${options.rendered.stopReason}; the inventory may be incomplete.`]\n : ['Rendered media discovery was unavailable; this manifest contains static-source evidence only.']\n\n const baseManifest: Omit<MediaManifest, 'outputDir' | 'assets'> = {\n pageUrl,\n staticFound: staticAssets.length,\n renderedFound: options.rendered?.assets.length ?? 0,\n filteredCount,\n totalFound,\n completeness: exhausted ? 'complete' as const : 'partial' as const,\n exhausted,\n stopReason,\n scrollRounds: options.rendered?.scrollRounds ?? 0,\n warnings,\n artifact: null,\n }\n\n if (options.outputDir === null) {\n return {\n ...baseManifest,\n outputDir: null,\n assets: kept.map((asset, i) => ({\n ...asset,\n mimeType: null, filename: safeFilename(asset.url, i), savedPath: null, sizeBytes: null,\n sha256: null, downloadStatus: 'not_attempted' as const, downloadError: null, inlinePreview: null,\n })),\n }\n }\n\n const domain = (() => { try { return new URL(pageUrl).hostname.replace(/^www\\./, '') } catch { return 'unknown' } })()\n const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)\n const outDir = options.outputDir ?? join(homedir(), 'Downloads', 'mcp-scraper', 'media', `${stamp}-${domain}`)\n mkdirSync(outDir, { recursive: true })\n\n const assets: MediaAsset[] = []\n\n await Promise.allSettled(\n kept.map(async (asset, i) => {\n const filename = safeFilename(asset.url, i)\n try {\n const { savedPath, sizeBytes, mimeType } = await downloadAsset(asset.url, outDir, filename)\n const resolvedType = mimeType ? (typeFromMime(mimeType) ?? asset.type) : asset.type\n assets.push({\n ...asset, type: resolvedType, mimeType, filename: basename(savedPath), savedPath, sizeBytes,\n sha256: null, downloadStatus: 'downloaded', downloadError: null, inlinePreview: null,\n })\n } catch (error) {\n assets.push({\n ...asset, mimeType: null, filename, savedPath: null, sizeBytes: null, sha256: null,\n downloadStatus: 'failed', downloadError: error instanceof Error ? error.message : 'media_download_failed', inlinePreview: null,\n })\n }\n })\n )\n\n assets.sort((a, b) => {\n if (a.savedPath && !b.savedPath) return -1\n if (!a.savedPath && b.savedPath) return 1\n return a.url.localeCompare(b.url)\n })\n\n return { ...baseManifest, outputDir: outDir, assets }\n}\n","import {\n buildPublicErrorEnvelope,\n type PublicChargeStatus,\n type PublicErrorDetails,\n type PublicErrorEnvelope,\n} from '../errors.js'\nimport { isVendorUnavailableError } from './vendor-errors.js'\n\nexport type ExtractionErrorCode =\n | 'vendor_unavailable'\n | 'page_not_found'\n | 'page_forbidden'\n | 'page_rate_limited'\n | 'page_server_error'\n | 'page_http_error'\n | 'bot_check_unresolved'\n | 'page_too_large'\n | 'page_unreachable'\n | 'browser_session_interrupted'\n | 'extraction_failed'\n\nexport interface ExtractionProblem {\n errorCode: ExtractionErrorCode\n httpStatus: number | null\n retryable: boolean\n}\n\nexport interface ExtractionErrorContext {\n chargeStatus?: PublicChargeStatus\n details?: PublicErrorDetails\n}\n\nexport type ExtractionErrorResponse = PublicErrorEnvelope & { error: string }\n\nconst UNREACHABLE_CODES = new Set([\n 'browser_navigation_blocked',\n 'browser_navigation_failed',\n 'browser_timeout',\n 'browser_session_failed',\n 'browser_result_missing',\n])\n\nfunction errorCode(err: unknown): string | undefined {\n return err instanceof Error ? (err as Error & { code?: string }).code : undefined\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nfunction httpStatusFromBrowserHttpCode(value: string | undefined): number | null {\n const match = value?.match(/^browser_http_(\\d+)$/)\n return match ? Number(match[1]) : null\n}\n\nfunction bucketHttpStatus(status: number): ExtractionErrorCode {\n if (status === 404) return 'page_not_found'\n if (status === 403) return 'page_forbidden'\n if (status === 429) return 'page_rate_limited'\n if (status >= 500 && status <= 599) return 'page_server_error'\n return 'page_http_error'\n}\n\nexport function classifyExtractionProblem(err: unknown): ExtractionProblem {\n const message = errorMessage(err)\n const code = errorCode(err)\n\n if (isVendorUnavailableError(err, message)) {\n return { errorCode: 'vendor_unavailable', httpStatus: null, retryable: true }\n }\n\n const httpStatus = httpStatusFromBrowserHttpCode(code) ?? httpStatusFromBrowserHttpCode(message.match(/^(browser_http_\\d+)/)?.[1])\n if (httpStatus != null) {\n return { errorCode: bucketHttpStatus(httpStatus), httpStatus, retryable: httpStatus === 429 || httpStatus >= 500 }\n }\n\n if (code === 'browser_challenge_unresolved' || /^browser_challenge_unresolved/.test(message)) {\n return { errorCode: 'bot_check_unresolved', httpStatus: null, retryable: true }\n }\n\n if (code === 'browser_response_too_large' || /exceeds \\d+ byte limit/i.test(message)) {\n return { errorCode: 'page_too_large', httpStatus: null, retryable: false }\n }\n\n if (/browser (?:has been )?closed|context closed|target page, context or browser has been closed|session closed/i.test(message)) {\n return { errorCode: 'browser_session_interrupted', httpStatus: null, retryable: true }\n }\n\n if (UNREACHABLE_CODES.has(code ?? '') || /timeout|navigation_failed|ERR_/i.test(message)) {\n return { errorCode: 'page_unreachable', httpStatus: null, retryable: true }\n }\n\n return { errorCode: 'extraction_failed', httpStatus: null, retryable: false }\n}\n\nexport function publicExtractionErrorMessage(code: ExtractionErrorCode, httpStatus: number | null): string {\n switch (code) {\n case 'page_not_found':\n return `The page could not be found (HTTP ${httpStatus ?? 404}). It may have been moved or deleted.`\n case 'page_forbidden':\n return `The site refused the request (HTTP ${httpStatus ?? 403}).`\n case 'page_rate_limited':\n return 'The site is rate-limiting requests right now (HTTP 429). Retrying after a short wait usually works.'\n case 'page_server_error':\n return `The page's own server returned an error (HTTP ${httpStatus}). This is on the target site's side, not something a retry here can fix.`\n case 'page_http_error':\n return `The page returned an unexpected HTTP ${httpStatus} response.`\n case 'bot_check_unresolved':\n return 'This site has automated-traffic protection (a bot/CAPTCHA check) that could not be resolved in time. Some sites are simply not extractable this way.'\n case 'page_too_large':\n return 'The page is larger than we can safely process.'\n case 'page_unreachable':\n return 'The page did not respond in time, or the connection could not be completed.'\n case 'browser_session_interrupted':\n return 'The browser session closed before extraction completed. A retry uses a fresh browser session.'\n case 'vendor_unavailable':\n return \"MCP Scraper's internal browser services require MCP Scraper team attention. Servers or IPs are down until this is fixed — this is not caused by your request. Please retry in a few minutes.\"\n case 'extraction_failed':\n return 'The page could not be extracted. Please retry, or contact support if this persists for the same URL.'\n }\n}\n\nexport function extractionProblemResponse(problem: ExtractionProblem, context: ExtractionErrorContext = {}): ExtractionErrorResponse {\n const message = publicExtractionErrorMessage(problem.errorCode, problem.httpStatus)\n const envelope = buildPublicErrorEnvelope({\n errorCode: problem.errorCode,\n retryable: problem.retryable,\n chargeStatus: context.chargeStatus,\n details: {\n ...(context.details ?? {}),\n ...(problem.httpStatus != null ? { http_status: problem.httpStatus } : {}),\n },\n })\n return { ...envelope, message, error: message }\n}\n\nexport function extractionWireStatus(problem: ExtractionProblem): 403 | 404 | 429 | 500 | 502 | 503 {\n if (problem.httpStatus === 404) return 404\n if (problem.httpStatus === 403) return 403\n if (problem.httpStatus === 429) return 429\n if (problem.httpStatus != null && problem.httpStatus >= 500 && problem.httpStatus <= 599) return 500\n if (problem.httpStatus != null) return 502\n if (problem.errorCode === 'vendor_unavailable') return 503\n return 502\n}\n\nconst WAYBACK_CAPTURE_MISSING = 'wayback_capture_missing'\n\nexport function publicizeExtractionFailure(\n failureCode: string | null | undefined,\n failureReason: string | null | undefined,\n): { failureCode: string | null; failureReason: string | null } {\n if (failureCode == null) return { failureCode: null, failureReason: null }\n if (failureCode === WAYBACK_CAPTURE_MISSING) return { failureCode, failureReason: failureReason ?? null }\n const err = new Error(failureReason ?? failureCode) as Error & { code?: string }\n err.code = failureCode\n const problem = classifyExtractionProblem(err)\n return { failureCode: problem.errorCode, failureReason: publicExtractionErrorMessage(problem.errorCode, problem.httpStatus) }\n}\n","import { neon, type NeonQueryFunction } from '@neondatabase/serverless'\nimport { getDb } from './db.js'\n\nexport const COMMONS_EMBED_PROVIDER = 'jina'\n\nlet _vectorSql: NeonQueryFunction<false, false> | null = null\nlet vectorSchemaReady = false\n\nexport function commonsEmbedModel(): string {\n return (process.env.JINA_EMBED_MODEL ?? 'jina-embeddings-v5-omni-small').trim()\n}\n\nexport function commonsEmbedDim(): number {\n return Number((process.env.JINA_EMBED_DIM ?? '1024').trim())\n}\n\nexport function commonsSemanticSearchConfigured(): boolean {\n return Boolean(process.env.JINA_API_KEY?.trim() && process.env.MEMORY_DATABASE_URL?.trim())\n}\n\nfunction vectorSql(): NeonQueryFunction<false, false> {\n if (_vectorSql) return _vectorSql\n const url = process.env.MEMORY_DATABASE_URL?.trim()\n if (!url) throw new Error('MEMORY_DATABASE_URL is not set; Commons semantic search needs the shared Postgres.')\n _vectorSql = neon(url)\n return _vectorSql\n}\n\nexport function resetCommonsVectorSchemaForTesting(): void {\n vectorSchemaReady = false\n _vectorSql = null\n}\n\nexport async function ensureCommonsVectorSchema(): Promise<void> {\n if (vectorSchemaReady) return\n const dimension = commonsEmbedDim()\n await vectorSql().query('CREATE EXTENSION IF NOT EXISTS vector')\n await vectorSql().query(`\n CREATE TABLE IF NOT EXISTS commons_index_vectors (\n document_id TEXT PRIMARY KEY,\n entity_id TEXT NOT NULL,\n document_type TEXT NOT NULL,\n title TEXT NOT NULL,\n embedding vector(${dimension}) NOT NULL,\n model TEXT NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL DEFAULT now()\n )\n `)\n await vectorSql().query('CREATE INDEX IF NOT EXISTS commons_index_vectors_entity ON commons_index_vectors(entity_id)')\n vectorSchemaReady = true\n}\n\nexport async function embedCommonsTexts(texts: string[]): Promise<number[][]> {\n const apiKey = process.env.JINA_API_KEY?.trim()\n if (!apiKey) throw new Error('JINA_API_KEY is not set; Commons semantic search cannot embed.')\n if (!texts.length) return []\n const response = await fetch('https://api.jina.ai/v1/embeddings', {\n method: 'POST',\n headers: { authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' },\n body: JSON.stringify({\n model: commonsEmbedModel(),\n dimensions: commonsEmbedDim(),\n input: texts.map(text => ({ text: text.slice(0, 8_000) })),\n }),\n signal: AbortSignal.timeout(60_000),\n })\n if (!response.ok) {\n throw new Error(`Jina embedding request failed with HTTP ${response.status}: ${(await response.text()).slice(0, 200)}`)\n }\n const payload = await response.json() as { data?: Array<{ embedding: number[] }> }\n const vectors = (payload.data ?? []).map(item => item.embedding)\n if (vectors.length !== texts.length) {\n throw new Error(`Jina returned ${vectors.length} embeddings for ${texts.length} inputs.`)\n }\n return vectors\n}\n\nexport async function embedQueuedCommonsDocuments(limit = 50): Promise<{\n claimed: number\n embedded: number\n failed: number\n remaining: number\n}> {\n if (!commonsSemanticSearchConfigured()) return { claimed: 0, embedded: 0, failed: 0, remaining: 0 }\n await ensureCommonsVectorSchema()\n const bounded = Math.max(1, Math.min(200, Math.floor(limit)))\n const queued = await getDb().execute({\n sql: `SELECT id, entity_id, document_type, title, text FROM commons_index_documents\n WHERE embedding_status IN ('queued', 'failed') ORDER BY updated_at ASC LIMIT ?`,\n args: [bounded],\n })\n const rows = queued.rows as unknown as Array<{ id: string; entity_id: string; document_type: string; title: string; text: string }>\n if (!rows.length) return { claimed: 0, embedded: 0, failed: 0, remaining: await queuedCommonsDocumentCount() }\n\n let embedded = 0\n let failed = 0\n const model = commonsEmbedModel()\n try {\n const vectors = await embedCommonsTexts(rows.map(row => `${row.title}\\n\\n${row.text}`))\n for (const [index, row] of rows.entries()) {\n const literal = `[${vectors[index].join(',')}]`\n await vectorSql().query(\n `INSERT INTO commons_index_vectors (document_id, entity_id, document_type, title, embedding, model, updated_at)\n VALUES ($1, $2, $3, $4, $5::vector, $6, now())\n ON CONFLICT (document_id) DO UPDATE SET entity_id = EXCLUDED.entity_id, document_type = EXCLUDED.document_type,\n title = EXCLUDED.title, embedding = EXCLUDED.embedding, model = EXCLUDED.model, updated_at = now()`,\n [row.id, row.entity_id, row.document_type, row.title, literal, model],\n )\n await getDb().execute({\n sql: `UPDATE commons_index_documents SET embedding_status = 'indexed', embedding_provider = ?, embedding_model = ?,\n vector_ref = ?, indexed_at = ?, error = NULL WHERE id = ?`,\n args: [COMMONS_EMBED_PROVIDER, model, row.id, new Date().toISOString(), row.id],\n })\n embedded += 1\n }\n } catch (error) {\n failed = rows.length - embedded\n const message = (error instanceof Error ? error.message : String(error)).slice(0, 500)\n for (const row of rows.slice(embedded)) {\n await getDb().execute({\n sql: `UPDATE commons_index_documents SET embedding_status = 'failed', error = ? WHERE id = ?`,\n args: [message, row.id],\n }).catch(() => undefined)\n }\n }\n return { claimed: rows.length, embedded, failed, remaining: await queuedCommonsDocumentCount() }\n}\n\nexport async function queuedCommonsDocumentCount(): Promise<number> {\n const result = await getDb().execute(`SELECT COUNT(*) AS n FROM commons_index_documents WHERE embedding_status IN ('queued', 'failed')`)\n return Number((result.rows[0] as unknown as { n: number })?.n ?? 0)\n}\n\nexport async function semanticCommonsEntityScores(query: string, limit = 40): Promise<Map<string, number>> {\n const scores = new Map<string, number>()\n if (!commonsSemanticSearchConfigured() || !query.trim()) return scores\n await ensureCommonsVectorSchema()\n const [vector] = await embedCommonsTexts([query])\n if (!vector) return scores\n const rows = await vectorSql().query(\n `SELECT entity_id, MAX(1 - (embedding <=> $1::vector)) AS score\n FROM commons_index_vectors GROUP BY entity_id ORDER BY score DESC LIMIT $2`,\n [`[${vector.join(',')}]`, Math.max(1, Math.min(100, limit))],\n ) as unknown as Array<{ entity_id: string; score: number }>\n for (const row of rows) scores.set(String(row.entity_id), Number(row.score))\n return scores\n}\n"],"mappings":"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","names":["document"]}