mcp-scraper 0.33.6 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +2 -2
  2. package/dist/bin/api-server.cjs +13558 -12516
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/mcp-scraper-cli.cjs +13 -2
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +14 -4
  8. package/dist/bin/mcp-scraper-cli.js.map +1 -1
  9. package/dist/bin/mcp-scraper-install.cjs +2 -2
  10. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  11. package/dist/bin/mcp-scraper-install.js +2 -2
  12. package/dist/bin/mcp-stdio-server.cjs +117 -7
  13. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  14. package/dist/bin/mcp-stdio-server.js +7 -7
  15. package/dist/bin/paa-harvest.cjs +128 -13
  16. package/dist/bin/paa-harvest.cjs.map +1 -1
  17. package/dist/bin/paa-harvest.js +4 -4
  18. package/dist/{chunk-DUS4ENOY.js → chunk-2EDFOQD7.js} +2 -2
  19. package/dist/{chunk-DUS4ENOY.js.map → chunk-2EDFOQD7.js.map} +1 -1
  20. package/dist/{chunk-C2OD4ELT.js → chunk-3HBPKR5G.js} +2 -2
  21. package/dist/{chunk-A54ELKJK.js → chunk-4KQVIYIH.js} +139 -17
  22. package/dist/chunk-4KQVIYIH.js.map +1 -0
  23. package/dist/{chunk-AIK7WQD5.js → chunk-62DQAWPF.js} +141 -3
  24. package/dist/chunk-62DQAWPF.js.map +1 -0
  25. package/dist/{chunk-V73MPRU6.js → chunk-CB5C3BPB.js} +17 -1
  26. package/dist/chunk-CB5C3BPB.js.map +1 -0
  27. package/dist/{chunk-6ZUK4H66.js → chunk-J32XSMJI.js} +117 -8
  28. package/dist/chunk-J32XSMJI.js.map +1 -0
  29. package/dist/{chunk-LKIP7VDN.js → chunk-JBSGBSGT.js} +46 -3
  30. package/dist/chunk-JBSGBSGT.js.map +1 -0
  31. package/dist/chunk-NFMIHEYE.js +7 -0
  32. package/dist/chunk-NFMIHEYE.js.map +1 -0
  33. package/dist/{chunk-XL6YIWJ3.js → chunk-SUOHUXQS.js} +3 -3
  34. package/dist/{chunk-WJJTGRK4.js → chunk-V36LS5YV.js} +2 -2
  35. package/dist/{chunk-AT2PFWHW.js → chunk-ZID3WQID.js} +2 -2
  36. package/dist/{db-5SZQUI7A.js → db-YAI5AQOI.js} +16 -2
  37. package/dist/{extract-bundle-YNXTFCC3.js → extract-bundle-GPZRLBVY.js} +2 -2
  38. package/dist/index.cjs +128 -13
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +4 -1
  41. package/dist/index.d.ts +4 -1
  42. package/dist/index.js +4 -4
  43. package/dist/{server-SHOYO52X.js → server-LKVPEQFE.js} +1386 -778
  44. package/dist/server-LKVPEQFE.js.map +1 -0
  45. package/dist/{site-extract-repository-ZDTO7ODZ.js → site-extract-repository-JHMVHENZ.js} +4 -4
  46. package/dist/{worker-BJ64GCDE.js → worker-ZZHSYI3Y.js} +7 -7
  47. package/docs/mcp-tool-manifest.generated.json +213 -3
  48. package/package.json +1 -1
  49. package/dist/chunk-6ZUK4H66.js.map +0 -1
  50. package/dist/chunk-A54ELKJK.js.map +0 -1
  51. package/dist/chunk-AIK7WQD5.js.map +0 -1
  52. package/dist/chunk-F65IIUHI.js +0 -7
  53. package/dist/chunk-F65IIUHI.js.map +0 -1
  54. package/dist/chunk-LKIP7VDN.js.map +0 -1
  55. package/dist/chunk-V73MPRU6.js.map +0 -1
  56. package/dist/server-SHOYO52X.js.map +0 -1
  57. /package/dist/{chunk-C2OD4ELT.js.map → chunk-3HBPKR5G.js.map} +0 -0
  58. /package/dist/{chunk-XL6YIWJ3.js.map → chunk-SUOHUXQS.js.map} +0 -0
  59. /package/dist/{chunk-WJJTGRK4.js.map → chunk-V36LS5YV.js.map} +0 -0
  60. /package/dist/{chunk-AT2PFWHW.js.map → chunk-ZID3WQID.js.map} +0 -0
  61. /package/dist/{db-5SZQUI7A.js.map → db-YAI5AQOI.js.map} +0 -0
  62. /package/dist/{extract-bundle-YNXTFCC3.js.map → extract-bundle-GPZRLBVY.js.map} +0 -0
  63. /package/dist/{site-extract-repository-ZDTO7ODZ.js.map → site-extract-repository-JHMVHENZ.js.map} +0 -0
  64. /package/dist/{worker-BJ64GCDE.js.map → worker-ZZHSYI3Y.js.map} +0 -0
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  harvest
4
- } from "../chunk-A54ELKJK.js";
4
+ } from "../chunk-4KQVIYIH.js";
5
5
  import {
6
6
  browserServiceApiKey
7
- } from "../chunk-WJJTGRK4.js";
7
+ } from "../chunk-V36LS5YV.js";
8
8
  import "../chunk-K443GQY5.js";
9
- import "../chunk-V73MPRU6.js";
9
+ import "../chunk-CB5C3BPB.js";
10
10
  import "../chunk-M2S27J6Z.js";
11
- import "../chunk-AIK7WQD5.js";
11
+ import "../chunk-62DQAWPF.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import { Command } from "commander";
@@ -50,7 +50,7 @@ function renderInstallTerminal(options) {
50
50
  "1/1 install surfaces ready",
51
51
  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),
52
52
  "",
53
- `${colorize("Tools", "cyan", color)} ${colorize("(165 MCP tools)", "muted", color)}`,
53
+ `${colorize("Tools", "cyan", color)} ${colorize("(166 MCP tools)", "muted", color)}`,
54
54
  toolRow("search", ["harvest_paa", "search_serp", "maps_search", "maps_place_intel"], color),
55
55
  toolRow("extract", ["extract_url", "map_site_urls", "extract_site", "audit_site", "directory_workflow"], color),
56
56
  toolRow("build", ["rank_tracker_workflow", "cron plan", "database prompt"], color),
@@ -105,4 +105,4 @@ function renderInstallTerminal(options) {
105
105
  export {
106
106
  renderInstallTerminal
107
107
  };
108
- //# sourceMappingURL=chunk-DUS4ENOY.js.map
108
+ //# sourceMappingURL=chunk-2EDFOQD7.js.map
@@ -1 +1 @@
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('(165 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', ['rank_tracker_workflow', 'cron plan', 'database prompt'], 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', ['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('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 an extra slot 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
+ {"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('(166 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', ['rank_tracker_workflow', 'cron plan', 'database prompt'], 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', ['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('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 an extra slot 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":[]}
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  finishHarvestAttempt,
8
8
  startHarvestAttempt
9
- } from "./chunk-AIK7WQD5.js";
9
+ } from "./chunk-62DQAWPF.js";
10
10
 
11
11
  // src/api/harvest-problems.ts
12
12
  function errorMessage(err) {
@@ -155,4 +155,4 @@ export {
155
155
  harvestProblemResponse,
156
156
  createHarvestAttemptRecorder
157
157
  };
158
- //# sourceMappingURL=chunk-C2OD4ELT.js.map
158
+ //# sourceMappingURL=chunk-3HBPKR5G.js.map
@@ -5,15 +5,16 @@ import {
5
5
  currentCostContext,
6
6
  recordKernelSession,
7
7
  runWithCostContext
8
- } from "./chunk-WJJTGRK4.js";
8
+ } from "./chunk-V36LS5YV.js";
9
9
  import {
10
10
  postToMemoryLibrary
11
11
  } from "./chunk-K443GQY5.js";
12
12
  import {
13
13
  DEFAULT_PROXY_MODE,
14
14
  HarvestOptionsSchema,
15
- RawPAAItemSchema
16
- } from "./chunk-V73MPRU6.js";
15
+ RawPAAItemSchema,
16
+ recencyToTbs
17
+ } from "./chunk-CB5C3BPB.js";
17
18
  import {
18
19
  CaptchaError,
19
20
  ExtractionError,
@@ -484,6 +485,7 @@ var BrowserDriver = class {
484
485
  async navigateToSERP(query, uule, gl, hl, options) {
485
486
  const params = new URLSearchParams({ q: query, gl, hl, pws: "0" });
486
487
  if (options?.num) params.set("num", String(options.num));
488
+ if (options?.tbs) params.set("tbs", options.tbs);
487
489
  if (uule) params.set("uule", uule);
488
490
  const url = "https://www.google.com/search?" + params.toString();
489
491
  const navUrl = serpArrivalUrl(url);
@@ -2050,6 +2052,7 @@ var PAAExtractor = class {
2050
2052
  options.hl,
2051
2053
  {
2052
2054
  ...options.serpOnly ? { num: 100 } : {},
2055
+ ...recencyToTbs(options.recency) ? { tbs: recencyToTbs(options.recency) } : {},
2053
2056
  debug: options.debug
2054
2057
  }
2055
2058
  );
@@ -2069,6 +2072,8 @@ var PAAExtractor = class {
2069
2072
  let allOrganic2 = organicResults2;
2070
2073
  if ((options.pages ?? 1) >= 2) {
2071
2074
  const p2params = new URLSearchParams({ q: options.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
2075
+ if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
2076
+ if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
2072
2077
  if (uule) p2params.set("uule", uule);
2073
2078
  await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
2074
2079
  await this.throwIfCaptcha(page, "Google SERP page 2");
@@ -2128,6 +2133,8 @@ var PAAExtractor = class {
2128
2133
  let locationEvidence2 = initialLocationEvidence;
2129
2134
  if ((options.pages ?? 1) >= 2) {
2130
2135
  const p2params = new URLSearchParams({ q: options.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
2136
+ if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
2137
+ if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
2131
2138
  if (uule) p2params.set("uule", uule);
2132
2139
  await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
2133
2140
  await this.throwIfCaptcha(page, "Google SERP page 2");
@@ -2194,6 +2201,7 @@ var PAAExtractor = class {
2194
2201
  let locationEvidence = initialLocationEvidence;
2195
2202
  if ((options.pages ?? 1) >= 2) {
2196
2203
  const p2params = new URLSearchParams({ q: options.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
2204
+ if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
2197
2205
  if (uule) p2params.set("uule", uule);
2198
2206
  await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
2199
2207
  await this.throwIfCaptcha(page, "Google SERP page 2");
@@ -2357,7 +2365,92 @@ var ProgressReporter = class {
2357
2365
  };
2358
2366
 
2359
2367
  // src/kernel-proxy-resolver.ts
2368
+ import Kernel3 from "@onkernel/sdk";
2369
+
2370
+ // src/backup-proxy.ts
2371
+ import { randomInt } from "crypto";
2360
2372
  import Kernel2 from "@onkernel/sdk";
2373
+ var BACKUP_PROXY_HOST = "pr.oxylabs.io";
2374
+ var BACKUP_PROXY_PORT = 7777;
2375
+ var BACKUP_STICKY_SESSION_MINUTES = 10;
2376
+ var BACKUP_LAST_RESORT_ATTEMPTS = 2;
2377
+ function backupProxyUsername() {
2378
+ const value = process.env.OXYLABS_USERNAME?.trim();
2379
+ return value || void 0;
2380
+ }
2381
+ function backupProxyPassword() {
2382
+ const value = process.env.OXYLABS_PASSWORD?.trim();
2383
+ return value || void 0;
2384
+ }
2385
+ function backupProxyCountry() {
2386
+ const value = process.env.OXYLABS_COUNTRY?.trim();
2387
+ return (value || "US").toUpperCase();
2388
+ }
2389
+ function backupProxyAvailable() {
2390
+ return Boolean(backupProxyUsername() && backupProxyPassword());
2391
+ }
2392
+ function randomStickySessionId() {
2393
+ let digits = "";
2394
+ for (let i = 0; i < 10; i++) digits += String(randomInt(0, 10));
2395
+ return digits;
2396
+ }
2397
+ function buildBackupUsername(opts = {}) {
2398
+ const user = backupProxyUsername();
2399
+ if (!user) return void 0;
2400
+ const base = user.startsWith("customer-") ? user : `customer-${user}`;
2401
+ const country = (opts.country?.trim() || backupProxyCountry()).toUpperCase();
2402
+ const sticky = opts.sticky ? `-sessid-${randomStickySessionId()}-sesstime-${BACKUP_STICKY_SESSION_MINUTES}` : "";
2403
+ return `${base}-cc-${country}${sticky}`;
2404
+ }
2405
+ function scrubBackupProxyText(message) {
2406
+ return message.replace(/pr\.oxylabs\.io(:\d+)?/gi, "backup proxy").replace(/oxylabs/gi, "backup proxy");
2407
+ }
2408
+ function backupProxyName(attemptIndex) {
2409
+ const stamp = `${Date.now()}-${attemptIndex ?? 0}-${Math.random().toString(36).slice(2, 8)}`;
2410
+ return `mcp-backup-custom-${stamp}`;
2411
+ }
2412
+ async function createBackupProxyId(kernelApiKey, opts = {}) {
2413
+ const username = buildBackupUsername({ sticky: opts.sticky });
2414
+ const password = backupProxyPassword();
2415
+ if (!username || !password) throw new Error("backup proxy pool is not configured");
2416
+ const kernel = new Kernel2({ apiKey: kernelApiKey });
2417
+ try {
2418
+ const created = await kernel.proxies.create({
2419
+ type: "custom",
2420
+ name: backupProxyName(opts.attemptIndex),
2421
+ protocol: "http",
2422
+ config: {
2423
+ host: BACKUP_PROXY_HOST,
2424
+ port: BACKUP_PROXY_PORT,
2425
+ username,
2426
+ password
2427
+ }
2428
+ });
2429
+ if (!created.id) throw new Error("backup proxy creation did not return a proxy id");
2430
+ return created.id;
2431
+ } catch (err) {
2432
+ const message = err instanceof Error ? err.message : String(err);
2433
+ throw new Error(scrubBackupProxyText(message));
2434
+ }
2435
+ }
2436
+ async function createBackupProxyIdSafe(kernelApiKey, attemptIndex) {
2437
+ if (!kernelApiKey || !backupProxyAvailable()) return void 0;
2438
+ try {
2439
+ return await createBackupProxyId(kernelApiKey, { attemptIndex });
2440
+ } catch {
2441
+ return void 0;
2442
+ }
2443
+ }
2444
+ async function cleanupBackupProxyId(kernelApiKey, proxyId) {
2445
+ if (!kernelApiKey || !proxyId) return;
2446
+ try {
2447
+ const kernel = new Kernel2({ apiKey: kernelApiKey });
2448
+ await kernel.proxies.delete(proxyId);
2449
+ } catch {
2450
+ }
2451
+ }
2452
+
2453
+ // src/kernel-proxy-resolver.ts
2361
2454
  var US_STATE_CODES = {
2362
2455
  alabama: "AL",
2363
2456
  alaska: "AK",
@@ -2455,7 +2548,7 @@ function proxyIdSuffix2(proxyId) {
2455
2548
  return proxyId ? proxyId.slice(-6) : null;
2456
2549
  }
2457
2550
  function resolution(source, proxyMode, proxyId, target, error, disposable = false) {
2458
- const proxyType = source === "disabled" ? "none" : source === "configured_fallback" ? "configured" : source === "isp_created" ? "isp" : source === "location_created" || source === "location_reused" ? "residential" : "unknown";
2551
+ const proxyType = source === "disabled" ? "none" : source === "configured_fallback" ? "configured" : source === "isp_created" ? "isp" : source === "backup_custom" ? "custom" : source === "location_created" || source === "location_reused" ? "residential" : "unknown";
2459
2552
  return {
2460
2553
  kernelProxyId: proxyId,
2461
2554
  ...disposable && proxyId ? { disposableProxyId: proxyId } : {},
@@ -2639,6 +2732,26 @@ function freshTargetCandidates(target, explicitZip, attemptIndex) {
2639
2732
  out.push(withProxyName(fallbackTarget, freshProxyName(fallbackTarget.proxyName, attemptIndex)));
2640
2733
  return out;
2641
2734
  }
2735
+ async function settleCreateExhaustion(options, target, createErrors) {
2736
+ if (options.backupOnExhaustion && createErrors.length > 0 && options.kernelApiKey && backupProxyAvailable()) {
2737
+ try {
2738
+ const backupId = await createBackupProxyId(options.kernelApiKey, { attemptIndex: options.attemptIndex });
2739
+ return resolution("backup_custom", options.proxyMode, backupId, target, null, true);
2740
+ } catch (err) {
2741
+ createErrors.push(`backup: ${scrubBackupProxyText(errorText2(err))}`);
2742
+ }
2743
+ }
2744
+ return resolution("configured_fallback", options.proxyMode, options.configuredKernelProxyId, target, createErrors.join(" | "));
2745
+ }
2746
+ async function resolveBackupKernelProxyId(options) {
2747
+ if (!options.kernelApiKey || !backupProxyAvailable()) return null;
2748
+ try {
2749
+ const backupId = await createBackupProxyId(options.kernelApiKey, { attemptIndex: options.attemptIndex });
2750
+ return resolution("backup_custom", options.proxyMode, backupId, null, null, true);
2751
+ } catch {
2752
+ return null;
2753
+ }
2754
+ }
2642
2755
  async function createFreshLocationProxy(kernel, options, target) {
2643
2756
  const createErrors = [];
2644
2757
  for (const candidate of freshTargetCandidates(target, options.proxyZip, options.attemptIndex)) {
@@ -2672,11 +2785,11 @@ async function createFreshLocationProxy(kernel, options, target) {
2672
2785
  createErrors.push(`isp: ${errorText2(err)}`);
2673
2786
  }
2674
2787
  }
2675
- return resolution("configured_fallback", options.proxyMode, options.configuredKernelProxyId, target, createErrors.join(" | "));
2788
+ return settleCreateExhaustion(options, target, createErrors);
2676
2789
  }
2677
2790
  async function deleteKernelProxyId(kernelApiKey, proxyId) {
2678
2791
  if (!kernelApiKey || !proxyId) return;
2679
- const kernel = new Kernel2({ apiKey: kernelApiKey });
2792
+ const kernel = new Kernel3({ apiKey: kernelApiKey });
2680
2793
  await kernel.proxies.delete(proxyId);
2681
2794
  }
2682
2795
  async function resolveKernelProxyId(options) {
@@ -2690,7 +2803,7 @@ async function resolveKernelProxyId(options) {
2690
2803
  if (!target || !options.kernelApiKey) {
2691
2804
  return resolution("configured_fallback", options.proxyMode, options.configuredKernelProxyId, target, target ? null : "location could not be normalized to a US city/state proxy target");
2692
2805
  }
2693
- const kernel = new Kernel2({ apiKey: options.kernelApiKey });
2806
+ const kernel = new Kernel3({ apiKey: options.kernelApiKey });
2694
2807
  try {
2695
2808
  const attemptIndex = options.attemptIndex ?? 0;
2696
2809
  if (options.fresh) {
@@ -2712,7 +2825,7 @@ async function resolveKernelProxyId(options) {
2712
2825
  } catch (err) {
2713
2826
  createErrors2.push(`${escalatedTarget.state}: ${errorText2(err)}`);
2714
2827
  }
2715
- return resolution("configured_fallback", options.proxyMode, options.configuredKernelProxyId, escalatedTarget, createErrors2.join(" | "));
2828
+ return settleCreateExhaustion(options, escalatedTarget, createErrors2);
2716
2829
  }
2717
2830
  const proxies = await kernel.proxies.list();
2718
2831
  const zip = knownZipFor(target, options.proxyZip);
@@ -2791,7 +2904,7 @@ async function resolveKernelProxyId(options) {
2791
2904
  } catch (err) {
2792
2905
  createErrors.push(`${fallbackTarget.state}: ${errorText2(err)}`);
2793
2906
  }
2794
- return resolution("configured_fallback", options.proxyMode, options.configuredKernelProxyId, target, createErrors.join(" | "));
2907
+ return settleCreateExhaustion(options, target, createErrors);
2795
2908
  } catch (err) {
2796
2909
  return resolution("configured_fallback", options.proxyMode, options.configuredKernelProxyId, target, errorText2(err));
2797
2910
  }
@@ -2956,7 +3069,9 @@ async function harvest(rawOptions) {
2956
3069
  const requestedDebug = typeof raw.debug === "boolean" ? raw.debug : false;
2957
3070
  const needsLocationEvidence = proxyMode === "location" && Boolean(proxyOpts.location);
2958
3071
  const requestedMaxAttempts = raw.maxAttempts;
2959
- const maxAttempts = typeof requestedMaxAttempts === "number" && Number.isInteger(requestedMaxAttempts) && requestedMaxAttempts >= 1 ? requestedMaxAttempts : maxAttemptsForProxyMode(proxyMode) + 1;
3072
+ const baseMaxAttempts = typeof requestedMaxAttempts === "number" && Number.isInteger(requestedMaxAttempts) && requestedMaxAttempts >= 1 ? requestedMaxAttempts : maxAttemptsForProxyMode(proxyMode) + 1;
3073
+ const maxAttempts = baseMaxAttempts + (backupProxyAvailable() ? BACKUP_LAST_RESORT_ATTEMPTS : 0);
3074
+ const softDeadlineMs = typeof raw.softDeadlineMs === "number" ? raw.softDeadlineMs : null;
2960
3075
  const serializer = new OutputSerializer();
2961
3076
  let lastError = null;
2962
3077
  let effectiveProxyMode = proxyMode;
@@ -2966,12 +3081,16 @@ async function harvest(rawOptions) {
2966
3081
  const startedAtMs = Date.now();
2967
3082
  try {
2968
3083
  if (signal?.aborted) throw abortReason(signal);
2969
- const resolution2 = await resolveKernelProxyId({
3084
+ const isBackupAttempt = i >= baseMaxAttempts;
3085
+ if (isBackupAttempt && softDeadlineMs !== null && Date.now() >= softDeadlineMs) break;
3086
+ const resolution2 = isBackupAttempt ? await resolveBackupKernelProxyId({ kernelApiKey, proxyMode: effectiveProxyMode, attemptIndex: i }) : await resolveKernelProxyId({
2970
3087
  ...proxyOpts,
2971
3088
  proxyMode: effectiveProxyMode,
2972
3089
  attemptIndex: i,
2973
- fresh: effectiveProxyMode === "location"
3090
+ fresh: effectiveProxyMode === "location",
3091
+ backupOnExhaustion: true
2974
3092
  });
3093
+ if (!resolution2) break;
2975
3094
  const mergedAttempt = {
2976
3095
  ...raw,
2977
3096
  kernelApiKey,
@@ -2980,7 +3099,7 @@ async function harvest(rawOptions) {
2980
3099
  proxyMode: effectiveProxyMode,
2981
3100
  debug: requestedDebug || needsLocationEvidence
2982
3101
  };
2983
- if (effectiveProxyMode === "none") mergedAttempt.kernelProxyId = void 0;
3102
+ if (effectiveProxyMode === "none" && !isBackupAttempt) mergedAttempt.kernelProxyId = void 0;
2984
3103
  const attemptOptions = HarvestOptionsSchema.parse(mergedAttempt);
2985
3104
  await emitAttemptEvent(onAttemptEvent, {
2986
3105
  type: "started",
@@ -2999,10 +3118,8 @@ async function harvest(rawOptions) {
2999
3118
  location: attemptOptions.location ?? null,
3000
3119
  max_questions: attemptOptions.maxQuestions
3001
3120
  }));
3002
- const isHeadfulEscalation = i === maxAttempts - 1;
3003
- const needHeadful = isHeadfulEscalation || !attemptOptions.serpOnly;
3004
3121
  const baseCtx = currentCostContext();
3005
- const attempt = needHeadful && baseCtx ? await runWithCostContext({ ...baseCtx, forceHeadful: true }, () => extractOnce(attemptOptions, signal)) : await extractOnce(attemptOptions, signal);
3122
+ const attempt = baseCtx ? await runWithCostContext({ ...baseCtx, forceHeadful: true }, () => extractOnce(attemptOptions, signal)) : await extractOnce(attemptOptions, signal);
3006
3123
  if (attempt.error) {
3007
3124
  const err = attempt.error;
3008
3125
  const outcome = classifyAttemptError(err);
@@ -3175,8 +3292,13 @@ export {
3175
3292
  serpArrivalUrl,
3176
3293
  buildYouTubeChannelVideosUrl,
3177
3294
  BrowserDriver,
3295
+ BACKUP_LAST_RESORT_ATTEMPTS,
3296
+ backupProxyAvailable,
3297
+ createBackupProxyIdSafe,
3298
+ cleanupBackupProxyId,
3299
+ resolveBackupKernelProxyId,
3178
3300
  deleteKernelProxyId,
3179
3301
  resolveKernelProxyId,
3180
3302
  harvest
3181
3303
  };
3182
- //# sourceMappingURL=chunk-A54ELKJK.js.map
3304
+ //# sourceMappingURL=chunk-4KQVIYIH.js.map