mcp-scraper 0.3.24 → 0.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/api-server.cjs +8220 -5991
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +320 -58
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +4 -3
- package/dist/bin/mcp-scraper-cli.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +3 -3
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +186 -49
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +5 -5
- package/dist/bin/paa-harvest.cjs +284 -10
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +5 -3
- package/dist/bin/paa-harvest.js.map +1 -1
- package/dist/{chunk-GZ4W72WF.js → chunk-2GHI2D6Q.js} +184 -48
- package/dist/chunk-2GHI2D6Q.js.map +1 -0
- package/dist/{chunk-ZAP7MYVO.js → chunk-3PRO376E.js} +2 -3
- package/dist/chunk-3PRO376E.js.map +1 -0
- package/dist/{chunk-LFATOGDF.js → chunk-BP27CZ5Q.js} +4 -3
- package/dist/chunk-BP27CZ5Q.js.map +1 -0
- package/dist/{chunk-DE7WP4UU.js → chunk-DOBQN3EY.js} +3 -3
- package/dist/chunk-DOBQN3EY.js.map +1 -0
- package/dist/{chunk-IGOEAKC7.js → chunk-G7PQ64LM.js} +274 -9
- package/dist/chunk-G7PQ64LM.js.map +1 -0
- package/dist/chunk-K443GQY5.js +24 -0
- package/dist/chunk-K443GQY5.js.map +1 -0
- package/dist/chunk-MS2UZ6HR.js +7 -0
- package/dist/chunk-MS2UZ6HR.js.map +1 -0
- package/dist/{chunk-H2R232HK.js → chunk-NNEIXK5L.js} +307 -62
- package/dist/chunk-NNEIXK5L.js.map +1 -0
- package/dist/{chunk-3KYRG7O7.js → chunk-TRNSXR46.js} +185 -2
- package/dist/chunk-TRNSXR46.js.map +1 -0
- package/dist/{chunk-JTWQGPKF.js → chunk-WYCER2HW.js} +33 -10
- package/dist/chunk-WYCER2HW.js.map +1 -0
- package/dist/{db-EG5ETPTY.js → db-FP2ABUU4.js} +32 -2
- package/dist/index.cjs +284 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/{server-2SRARAO5.js → server-HIGHKTEV.js} +1563 -275
- package/dist/server-HIGHKTEV.js.map +1 -0
- package/dist/{worker-UM5UM6O5.js → worker-UC6D2756.js} +7 -6
- package/dist/{worker-UM5UM6O5.js.map → worker-UC6D2756.js.map} +1 -1
- package/docs/kernel-proxy-future-enhancements.md +80 -0
- package/docs/spec-browser-routing.md +108 -0
- package/docs/spec-mcp-tools.md +146 -0
- package/docs/spec-server-instructions.md +94 -0
- package/docs/specs/audit-visual-demo.md +309 -0
- package/package.json +5 -2
- package/dist/chunk-3KYRG7O7.js.map +0 -1
- package/dist/chunk-DE7WP4UU.js.map +0 -1
- package/dist/chunk-GZ4W72WF.js.map +0 -1
- package/dist/chunk-H2R232HK.js.map +0 -1
- package/dist/chunk-H7R4PPMG.js +0 -7
- package/dist/chunk-H7R4PPMG.js.map +0 -1
- package/dist/chunk-IGOEAKC7.js.map +0 -1
- package/dist/chunk-JTWQGPKF.js.map +0 -1
- package/dist/chunk-LFATOGDF.js.map +0 -1
- package/dist/chunk-ZAP7MYVO.js.map +0 -1
- package/dist/server-2SRARAO5.js.map +0 -1
- /package/dist/{db-EG5ETPTY.js.map → db-FP2ABUU4.js.map} +0 -0
|
@@ -4,8 +4,7 @@ function browserServiceApiKey() {
|
|
|
4
4
|
return value || void 0;
|
|
5
5
|
}
|
|
6
6
|
function browserServiceProxyId() {
|
|
7
|
-
|
|
8
|
-
return value || void 0;
|
|
7
|
+
return void 0;
|
|
9
8
|
}
|
|
10
9
|
function browserServiceProfileName() {
|
|
11
10
|
const value = (process.env.BROWSER_AGENT_PROFILE_NAME ?? process.env.BROWSER_SERVICE_PROFILE_NAME ?? process.env.KERNEL_BROWSER_PROFILE_NAME ?? process.env.KERNEL_PROFILE_NAME)?.trim();
|
|
@@ -65,4 +64,4 @@ export {
|
|
|
65
64
|
RequestAbortedError,
|
|
66
65
|
LocationMismatchError
|
|
67
66
|
};
|
|
68
|
-
//# sourceMappingURL=chunk-
|
|
67
|
+
//# sourceMappingURL=chunk-3PRO376E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/browser-service-env.ts","../src/errors.ts"],"sourcesContent":["export function browserServiceApiKey(): string | undefined {\n const value = (process.env.BROWSER_SERVICE_API_KEY ?? process.env.KERNEL_API_KEY)?.trim()\n return value || undefined\n}\n\nexport function browserServiceProxyId(): string | undefined {\n return undefined\n}\n\nexport function browserServiceProfileName(): string | undefined {\n const value = (\n process.env.BROWSER_AGENT_PROFILE_NAME ??\n process.env.BROWSER_SERVICE_PROFILE_NAME ??\n process.env.KERNEL_BROWSER_PROFILE_NAME ??\n process.env.KERNEL_PROFILE_NAME\n )?.trim()\n return value || undefined\n}\n\nexport function browserServiceProfileSaveChanges(): boolean | undefined {\n const value = (\n process.env.BROWSER_AGENT_PROFILE_SAVE_CHANGES ??\n process.env.BROWSER_SERVICE_PROFILE_SAVE_CHANGES ??\n process.env.KERNEL_BROWSER_PROFILE_SAVE_CHANGES ??\n process.env.KERNEL_PROFILE_SAVE_CHANGES\n )?.trim().toLowerCase()\n if (!value) return undefined\n if (['1', 'true', 'yes', 'on'].includes(value)) return true\n if (['0', 'false', 'no', 'off'].includes(value)) return false\n return undefined\n}\n","export const RECAPTCHA_INSTRUCTIONS = 'Google returned a CAPTCHA. Run with --headless=false to re-warm the browser profile, then retry.'\n\nexport function sanitizeVendorName(message: string): string {\n return message\n .replace(/kernel\\.sh\\s+sessions?/gi, 'sessions')\n .replace(/kernel\\.sh\\s+session/gi, 'this session')\n .replace(/kernel\\.sh/gi, 'the service')\n .replace(/kernel\\s+sessions?/gi, 'sessions')\n .replace(/kernel\\s+session/gi, 'this session')\n .replace(/\\bkernel\\b/gi, 'the service')\n .replace(/ +/g, ' ')\n .trim()\n}\n\nexport class CaptchaError extends Error {\n readonly name = 'CaptchaError'\n constructor(public readonly instructions: string) {\n super(`CAPTCHA detected. ${instructions}`)\n }\n}\n\nexport class ExtractionError extends Error {\n readonly name = 'ExtractionError'\n constructor(message: string, public readonly cause?: unknown) {\n super(message)\n }\n}\n\nexport class RequestAbortedError extends Error {\n readonly name = 'RequestAbortedError'\n constructor(message = 'Request aborted before harvest completed') {\n super(message)\n }\n}\n\nexport class LocationMismatchError extends Error {\n readonly name = 'LocationMismatchError'\n constructor(message = 'Google returned results for a different location than requested') {\n super(message)\n }\n}\n"],"mappings":";AAAO,SAAS,uBAA2C;AACzD,QAAM,SAAS,QAAQ,IAAI,2BAA2B,QAAQ,IAAI,iBAAiB,KAAK;AACxF,SAAO,SAAS;AAClB;AAEO,SAAS,wBAA4C;AAC1D,SAAO;AACT;AAEO,SAAS,4BAAgD;AAC9D,QAAM,SACJ,QAAQ,IAAI,8BACZ,QAAQ,IAAI,gCACZ,QAAQ,IAAI,+BACZ,QAAQ,IAAI,sBACX,KAAK;AACR,SAAO,SAAS;AAClB;AAEO,SAAS,mCAAwD;AACtE,QAAM,SACJ,QAAQ,IAAI,sCACZ,QAAQ,IAAI,wCACZ,QAAQ,IAAI,uCACZ,QAAQ,IAAI,8BACX,KAAK,EAAE,YAAY;AACtB,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,CAAC,KAAK,QAAQ,OAAO,IAAI,EAAE,SAAS,KAAK,EAAG,QAAO;AACvD,MAAI,CAAC,KAAK,SAAS,MAAM,KAAK,EAAE,SAAS,KAAK,EAAG,QAAO;AACxD,SAAO;AACT;;;AC9BO,IAAM,yBAAyB;AAE/B,SAAS,mBAAmB,SAAyB;AAC1D,SAAO,QACJ,QAAQ,4BAA4B,UAAU,EAC9C,QAAQ,0BAA0B,cAAc,EAChD,QAAQ,gBAAgB,aAAa,EACrC,QAAQ,wBAAwB,UAAU,EAC1C,QAAQ,sBAAsB,cAAc,EAC5C,QAAQ,gBAAgB,aAAa,EACrC,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAEO,IAAM,eAAN,cAA2B,MAAM;AAAA,EAEtC,YAA4B,cAAsB;AAChD,UAAM,qBAAqB,YAAY,EAAE;AADf;AAAA,EAE5B;AAAA,EAF4B;AAAA,EADnB,OAAO;AAIlB;AAEO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAEzC,YAAY,SAAiC,OAAiB;AAC5D,UAAM,OAAO;AAD8B;AAAA,EAE7C;AAAA,EAF6C;AAAA,EADpC,OAAO;AAIlB;AAEO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EACpC,OAAO;AAAA,EAChB,YAAY,UAAU,4CAA4C;AAChE,UAAM,OAAO;AAAA,EACf;AACF;AAEO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EACtC,OAAO;AAAA,EAChB,YAAY,UAAU,mEAAmE;AACvF,UAAM,OAAO;AAAA,EACf;AACF;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/schemas.ts
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
var DEFAULT_PROXY_MODE = "
|
|
3
|
+
var DEFAULT_PROXY_MODE = "none";
|
|
4
4
|
var DEFAULT_MAPS_PROXY_MODE = "location";
|
|
5
5
|
var HarvestOptionsSchema = z.object({
|
|
6
6
|
query: z.string().min(1),
|
|
@@ -22,7 +22,8 @@ var HarvestOptionsSchema = z.object({
|
|
|
22
22
|
outputDir: z.string().default("./paa-output"),
|
|
23
23
|
format: z.enum(["json", "csv", "both"]).default("both"),
|
|
24
24
|
serpOnly: z.boolean().default(false),
|
|
25
|
-
pages: z.number().int().min(1).max(2).default(1)
|
|
25
|
+
pages: z.number().int().min(1).max(2).default(1),
|
|
26
|
+
softDeadlineMs: z.number().optional()
|
|
26
27
|
});
|
|
27
28
|
var MapsPlaceOptionsSchema = z.object({
|
|
28
29
|
businessName: z.string().min(1),
|
|
@@ -108,4 +109,4 @@ export {
|
|
|
108
109
|
RawMapsReviewStatsSchema,
|
|
109
110
|
RawMapsAboutAttributeSchema
|
|
110
111
|
};
|
|
111
|
-
//# sourceMappingURL=chunk-
|
|
112
|
+
//# sourceMappingURL=chunk-BP27CZ5Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schemas.ts"],"sourcesContent":["import { z } from 'zod'\n\nexport const DEFAULT_PROXY_MODE = 'none' as const\nexport const DEFAULT_MAPS_PROXY_MODE = 'location' 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 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 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 pages: z.number().int().min(1).max(2).default(1),\n softDeadlineMs: z.number().optional(),\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 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 proxyMode: z.enum(['location', 'configured', 'none']).default(DEFAULT_MAPS_PROXY_MODE),\n proxyZip: z.string().regex(/^\\d{5}$/).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"],"mappings":"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","names":[]}
|
|
@@ -50,11 +50,11 @@ function renderInstallTerminal(options) {
|
|
|
50
50
|
"1/1 install surfaces ready",
|
|
51
51
|
colorize("Newest: saved browser profiles that hold MANY site logins. Share the watch_url, user signs in, then verify with browser_profile_list. Browser sessions are direct/no-proxy by default.", "lime", color),
|
|
52
52
|
"",
|
|
53
|
-
`${colorize("Tools", "cyan", color)} ${colorize("(
|
|
53
|
+
`${colorize("Tools", "cyan", color)} ${colorize("(46 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),
|
|
57
|
-
toolRow("media", ["youtube_harvest", "youtube_transcribe", "facebook_ad_search", "facebook_page_intel", "facebook_ad_transcribe", "facebook_video_transcribe", "instagram_profile_content", "instagram_media_download"], color),
|
|
57
|
+
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),
|
|
58
58
|
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),
|
|
59
59
|
toolRow("account", ["credits_info", "reports", "MCP resources"], color),
|
|
60
60
|
`${colorize("Workflows", "cyan", color)} ${colorize("(MCP + CLI + API)", "muted", color)}`,
|
|
@@ -100,4 +100,4 @@ function renderInstallTerminal(options) {
|
|
|
100
100
|
export {
|
|
101
101
|
renderInstallTerminal
|
|
102
102
|
};
|
|
103
|
-
//# sourceMappingURL=chunk-
|
|
103
|
+
//# sourceMappingURL=chunk-DOBQN3EY.js.map
|
|
@@ -0,0 +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: saved browser profiles that hold MANY site logins. Share the watch_url, user signs in, then verify with browser_profile_list. Browser sessions are direct/no-proxy by default.', 'lime', color),\n '',\n `${colorize('Tools', 'cyan', color)} ${colorize('(46 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('account', ['credits_info', 'reports', 'MCP resources'], 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 'Customer auth setup: run browser_profile_connect, send the watch_url, let the user sign in, then call browser_profile_list until AUTHENTICATED.',\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":[]}
|
|
@@ -7,12 +7,206 @@ import {
|
|
|
7
7
|
browserServiceApiKey,
|
|
8
8
|
browserServiceProxyId,
|
|
9
9
|
sanitizeVendorName
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3PRO376E.js";
|
|
11
|
+
import {
|
|
12
|
+
postToMemoryLibrary
|
|
13
|
+
} from "./chunk-K443GQY5.js";
|
|
11
14
|
import {
|
|
12
15
|
DEFAULT_PROXY_MODE,
|
|
13
16
|
HarvestOptionsSchema,
|
|
14
17
|
RawPAAItemSchema
|
|
15
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-BP27CZ5Q.js";
|
|
19
|
+
import {
|
|
20
|
+
getDb
|
|
21
|
+
} from "./chunk-TRNSXR46.js";
|
|
22
|
+
|
|
23
|
+
// src/api/cost-context.ts
|
|
24
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
25
|
+
var storage = new AsyncLocalStorage();
|
|
26
|
+
function runWithCostContext(ctx, fn) {
|
|
27
|
+
return storage.run(ctx, fn);
|
|
28
|
+
}
|
|
29
|
+
function currentCostContext() {
|
|
30
|
+
return storage.getStore();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/api/cost-telemetry.ts
|
|
34
|
+
import { randomUUID } from "crypto";
|
|
35
|
+
|
|
36
|
+
// src/api/cost-rates.ts
|
|
37
|
+
var KERNEL_HEADLESS_USD_PER_SEC = 166667e-10;
|
|
38
|
+
var KERNEL_HEADFUL_USD_PER_SEC = 1333336e-10;
|
|
39
|
+
var HEADLESS_OPS = /* @__PURE__ */ new Set(["harvest", "maps_search", "yt_channel", "fb_search", "fb_ad", "instagram"]);
|
|
40
|
+
var FAL_WIZPER_USD_PER_AUDIO_MIN = 5e-3;
|
|
41
|
+
var DEEPINFRA_QWEN_USD_PER_1K_OUT_TOKENS = 4e-4;
|
|
42
|
+
function kernelCostUsd(ms, headful) {
|
|
43
|
+
const sec = Math.max(0, ms) / 1e3;
|
|
44
|
+
return sec * (headful ? KERNEL_HEADFUL_USD_PER_SEC : KERNEL_HEADLESS_USD_PER_SEC);
|
|
45
|
+
}
|
|
46
|
+
function vendorCostUsd(vendor, units) {
|
|
47
|
+
if (vendor === "fal_wizper") return Math.max(0, units) * FAL_WIZPER_USD_PER_AUDIO_MIN;
|
|
48
|
+
if (vendor === "deepinfra_qwen") return Math.max(0, units) / 1e3 * DEEPINFRA_QWEN_USD_PER_1K_OUT_TOKENS;
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/api/cost-telemetry.ts
|
|
53
|
+
var _ready = false;
|
|
54
|
+
async function migrateCostTelemetry() {
|
|
55
|
+
if (_ready) return;
|
|
56
|
+
const db = getDb();
|
|
57
|
+
await db.execute(`
|
|
58
|
+
CREATE TABLE IF NOT EXISTS kernel_session_log (
|
|
59
|
+
id TEXT PRIMARY KEY,
|
|
60
|
+
kernel_session_id TEXT,
|
|
61
|
+
op TEXT,
|
|
62
|
+
source TEXT NOT NULL,
|
|
63
|
+
probe_run_id TEXT,
|
|
64
|
+
user_id INTEGER,
|
|
65
|
+
stealth INTEGER,
|
|
66
|
+
headless_sent INTEGER,
|
|
67
|
+
proxy_used INTEGER,
|
|
68
|
+
fallback INTEGER NOT NULL DEFAULT 0,
|
|
69
|
+
opened_at TEXT,
|
|
70
|
+
closed_at TEXT,
|
|
71
|
+
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
72
|
+
est_cost_usd_headless REAL NOT NULL DEFAULT 0,
|
|
73
|
+
est_cost_usd_headful REAL NOT NULL DEFAULT 0,
|
|
74
|
+
error TEXT,
|
|
75
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
76
|
+
)
|
|
77
|
+
`);
|
|
78
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_op ON kernel_session_log(op)`);
|
|
79
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_probe ON kernel_session_log(probe_run_id)`);
|
|
80
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_created ON kernel_session_log(created_at)`);
|
|
81
|
+
try {
|
|
82
|
+
await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_source TEXT`);
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_type TEXT`);
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
await db.execute(`
|
|
90
|
+
CREATE TABLE IF NOT EXISTS vendor_usage_log (
|
|
91
|
+
id TEXT PRIMARY KEY,
|
|
92
|
+
op TEXT,
|
|
93
|
+
probe_run_id TEXT,
|
|
94
|
+
user_id INTEGER,
|
|
95
|
+
vendor TEXT NOT NULL,
|
|
96
|
+
model TEXT,
|
|
97
|
+
units REAL NOT NULL DEFAULT 0,
|
|
98
|
+
unit_type TEXT,
|
|
99
|
+
est_cost_usd REAL NOT NULL DEFAULT 0,
|
|
100
|
+
error TEXT,
|
|
101
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
102
|
+
)
|
|
103
|
+
`);
|
|
104
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_op ON vendor_usage_log(op)`);
|
|
105
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_probe ON vendor_usage_log(probe_run_id)`);
|
|
106
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_created ON vendor_usage_log(created_at)`);
|
|
107
|
+
await db.execute(`
|
|
108
|
+
CREATE TABLE IF NOT EXISTS cost_probe_runs (
|
|
109
|
+
id TEXT PRIMARY KEY,
|
|
110
|
+
tool TEXT NOT NULL,
|
|
111
|
+
target TEXT,
|
|
112
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
113
|
+
wall_ms INTEGER NOT NULL DEFAULT 0,
|
|
114
|
+
success INTEGER NOT NULL DEFAULT 0,
|
|
115
|
+
error TEXT,
|
|
116
|
+
http_only INTEGER NOT NULL DEFAULT 0,
|
|
117
|
+
kernel_used INTEGER NOT NULL DEFAULT 0,
|
|
118
|
+
kernel_sessions INTEGER NOT NULL DEFAULT 0,
|
|
119
|
+
kernel_seconds_total REAL NOT NULL DEFAULT 0,
|
|
120
|
+
kernel_tier_observed TEXT,
|
|
121
|
+
est_kernel_cost_usd_headless REAL NOT NULL DEFAULT 0,
|
|
122
|
+
est_kernel_cost_usd_headful REAL NOT NULL DEFAULT 0,
|
|
123
|
+
vendor_cost_usd REAL NOT NULL DEFAULT 0,
|
|
124
|
+
charged_credits REAL NOT NULL DEFAULT 0,
|
|
125
|
+
margin_usd_headless_starter REAL NOT NULL DEFAULT 0,
|
|
126
|
+
margin_usd_headful_starter REAL NOT NULL DEFAULT 0,
|
|
127
|
+
notes TEXT
|
|
128
|
+
)
|
|
129
|
+
`);
|
|
130
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS cost_probe_runs_tool ON cost_probe_runs(tool)`);
|
|
131
|
+
try {
|
|
132
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN units REAL`);
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN unit_type TEXT`);
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN mode TEXT`);
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
_ready = true;
|
|
144
|
+
}
|
|
145
|
+
async function recordKernelSession(r) {
|
|
146
|
+
try {
|
|
147
|
+
await migrateCostTelemetry();
|
|
148
|
+
const ctx = currentCostContext();
|
|
149
|
+
const durationMs = Math.max(0, r.closedAtMs - r.openedAtMs);
|
|
150
|
+
const db = getDb();
|
|
151
|
+
await db.execute({
|
|
152
|
+
sql: `INSERT INTO kernel_session_log
|
|
153
|
+
(id, kernel_session_id, op, source, probe_run_id, user_id, stealth, headless_sent, proxy_used, proxy_source, proxy_type, fallback, opened_at, closed_at, duration_ms, est_cost_usd_headless, est_cost_usd_headful, error)
|
|
154
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
155
|
+
args: [
|
|
156
|
+
randomUUID(),
|
|
157
|
+
r.kernelSessionId ?? null,
|
|
158
|
+
ctx?.op ?? null,
|
|
159
|
+
r.source,
|
|
160
|
+
ctx?.probeRunId ?? null,
|
|
161
|
+
ctx?.userId ?? null,
|
|
162
|
+
boolToInt(r.stealth),
|
|
163
|
+
boolToInt(r.headlessSent),
|
|
164
|
+
boolToInt(r.proxyUsed),
|
|
165
|
+
r.proxySource ?? null,
|
|
166
|
+
r.proxyType ?? null,
|
|
167
|
+
r.fallback ? 1 : 0,
|
|
168
|
+
new Date(r.openedAtMs).toISOString(),
|
|
169
|
+
new Date(r.closedAtMs).toISOString(),
|
|
170
|
+
durationMs,
|
|
171
|
+
kernelCostUsd(durationMs, false),
|
|
172
|
+
kernelCostUsd(durationMs, true),
|
|
173
|
+
r.error ?? null
|
|
174
|
+
]
|
|
175
|
+
});
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.warn("[cost-telemetry] recordKernelSession failed:", err instanceof Error ? err.message : String(err));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function recordVendorUsage(r) {
|
|
181
|
+
try {
|
|
182
|
+
await migrateCostTelemetry();
|
|
183
|
+
const ctx = currentCostContext();
|
|
184
|
+
const db = getDb();
|
|
185
|
+
await db.execute({
|
|
186
|
+
sql: `INSERT INTO vendor_usage_log
|
|
187
|
+
(id, op, probe_run_id, user_id, vendor, model, units, unit_type, est_cost_usd, error)
|
|
188
|
+
VALUES (?,?,?,?,?,?,?,?,?,?)`,
|
|
189
|
+
args: [
|
|
190
|
+
randomUUID(),
|
|
191
|
+
ctx?.op ?? null,
|
|
192
|
+
ctx?.probeRunId ?? null,
|
|
193
|
+
ctx?.userId ?? null,
|
|
194
|
+
r.vendor,
|
|
195
|
+
r.model ?? null,
|
|
196
|
+
r.units,
|
|
197
|
+
r.unitType,
|
|
198
|
+
vendorCostUsd(r.vendor, r.units),
|
|
199
|
+
r.error ?? null
|
|
200
|
+
]
|
|
201
|
+
});
|
|
202
|
+
} catch (err) {
|
|
203
|
+
console.warn("[cost-telemetry] recordVendorUsage failed:", err instanceof Error ? err.message : String(err));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function boolToInt(v) {
|
|
207
|
+
if (v === null || v === void 0) return null;
|
|
208
|
+
return v ? 1 : 0;
|
|
209
|
+
}
|
|
16
210
|
|
|
17
211
|
// src/driver/BrowserDriver.ts
|
|
18
212
|
import { chromium } from "playwright-extra";
|
|
@@ -206,6 +400,12 @@ var BrowserDriver = class {
|
|
|
206
400
|
page = null;
|
|
207
401
|
kernelClient = null;
|
|
208
402
|
kernelSessionId = null;
|
|
403
|
+
kernelOpenedAtMs = null;
|
|
404
|
+
kernelStealth = null;
|
|
405
|
+
kernelProxyUsed = null;
|
|
406
|
+
kernelProxySource = null;
|
|
407
|
+
kernelProxyType = null;
|
|
408
|
+
kernelHeadlessSent = null;
|
|
209
409
|
debugEnabled = false;
|
|
210
410
|
debugSnapshot = {
|
|
211
411
|
kernel: null,
|
|
@@ -237,9 +437,16 @@ var BrowserDriver = class {
|
|
|
237
437
|
if (config.kernelProfileName && config.kernelProfileSaveChanges === true) {
|
|
238
438
|
await ensureKernelProfile(this.kernelClient, config.kernelProfileName);
|
|
239
439
|
}
|
|
440
|
+
const costCtx = currentCostContext();
|
|
441
|
+
let useHeadless;
|
|
442
|
+
if (config.headlessMode === "headful" || costCtx?.forceHeadful === true) useHeadless = false;
|
|
443
|
+
else if (config.headlessMode === "headless") useHeadless = true;
|
|
444
|
+
else useHeadless = costCtx?.forceHeadless === true || process.env.KERNEL_HEADLESS_DISABLED !== "true" && !!costCtx?.op && HEADLESS_OPS.has(costCtx.op);
|
|
445
|
+
this.kernelHeadlessSent = useHeadless ? true : null;
|
|
240
446
|
const kernelBrowser = await this.kernelClient.browsers.create({
|
|
241
447
|
stealth: true,
|
|
242
448
|
timeout_seconds: timeoutSeconds,
|
|
449
|
+
...useHeadless ? { headless: true } : {},
|
|
243
450
|
...config.kernelProxyId ? { proxy_id: config.kernelProxyId } : {},
|
|
244
451
|
...config.kernelProfileName ? {
|
|
245
452
|
profile: {
|
|
@@ -249,9 +456,14 @@ var BrowserDriver = class {
|
|
|
249
456
|
} : {}
|
|
250
457
|
});
|
|
251
458
|
this.kernelSessionId = kernelBrowser.session_id;
|
|
459
|
+
this.kernelOpenedAtMs = Date.now();
|
|
460
|
+
this.kernelStealth = typeof kernelBrowser.stealth === "boolean" ? kernelBrowser.stealth : null;
|
|
461
|
+
this.kernelProxyUsed = typeof kernelBrowser.proxy_id === "string" ? Boolean(kernelBrowser.proxy_id) : Boolean(config.kernelProxyId);
|
|
462
|
+
this.kernelProxySource = config.kernelProxyResolution?.source ?? null;
|
|
463
|
+
this.kernelProxyType = config.kernelProxyResolution?.proxyType ?? null;
|
|
252
464
|
let defaultProxyDisabled = null;
|
|
253
465
|
let defaultProxyDisableError = null;
|
|
254
|
-
if (
|
|
466
|
+
if (!config.kernelProxyId) {
|
|
255
467
|
try {
|
|
256
468
|
await withTimeout(
|
|
257
469
|
this.kernelClient.browsers.update(this.kernelSessionId, { disable_default_proxy: true }),
|
|
@@ -602,11 +814,24 @@ var BrowserDriver = class {
|
|
|
602
814
|
const b = this.browser;
|
|
603
815
|
const sessionId = this.kernelSessionId;
|
|
604
816
|
const client = this.kernelClient;
|
|
817
|
+
const openedAtMs = this.kernelOpenedAtMs;
|
|
818
|
+
const stealth = this.kernelStealth;
|
|
819
|
+
const proxyUsed = this.kernelProxyUsed;
|
|
820
|
+
const proxySource = this.kernelProxySource;
|
|
821
|
+
const proxyType = this.kernelProxyType;
|
|
822
|
+
const headlessSent = this.kernelHeadlessSent;
|
|
605
823
|
this.browser = null;
|
|
606
824
|
this.context = null;
|
|
607
825
|
this.page = null;
|
|
608
826
|
this.kernelSessionId = null;
|
|
609
827
|
this.kernelClient = null;
|
|
828
|
+
this.kernelOpenedAtMs = null;
|
|
829
|
+
this.kernelHeadlessSent = null;
|
|
830
|
+
this.kernelProxySource = null;
|
|
831
|
+
this.kernelProxyType = null;
|
|
832
|
+
if (client && sessionId && openedAtMs != null) {
|
|
833
|
+
void recordKernelSession({ kernelSessionId: sessionId, source: "browser_driver", stealth, headlessSent, proxyUsed, proxySource, proxyType, openedAtMs, closedAtMs: Date.now() });
|
|
834
|
+
}
|
|
610
835
|
if (client && sessionId) {
|
|
611
836
|
console.info(JSON.stringify({
|
|
612
837
|
event: "kernel_browser_delete_started",
|
|
@@ -1321,6 +1546,7 @@ var PAAExtractor = class {
|
|
|
1321
1546
|
);
|
|
1322
1547
|
let round = 0;
|
|
1323
1548
|
while (seenQs.size < options.maxQuestions) {
|
|
1549
|
+
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
1324
1550
|
this.throwIfAborted(signal);
|
|
1325
1551
|
await this.throwIfCaptcha(page, "Google PAA expansion");
|
|
1326
1552
|
const beforeQs = await readAllQs();
|
|
@@ -1330,6 +1556,7 @@ var PAAExtractor = class {
|
|
|
1330
1556
|
if (unexpandedCount === 0) break;
|
|
1331
1557
|
this.reporter.onDepth(++round);
|
|
1332
1558
|
for (let ci = 0; ci < unexpandedCount; ci++) {
|
|
1559
|
+
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
1333
1560
|
this.throwIfAborted(signal);
|
|
1334
1561
|
try {
|
|
1335
1562
|
const btn = page.locator(unexpandedSel).first();
|
|
@@ -2000,6 +2227,7 @@ var OutputSerializer = class {
|
|
|
2000
2227
|
const filename = `${slug}-${Date.now()}.json`;
|
|
2001
2228
|
const fullPath = path.join(outputDir, filename);
|
|
2002
2229
|
await fs.writeFile(fullPath, JSON.stringify(result, null, 2), "utf8");
|
|
2230
|
+
await postToMemoryLibrary({ title: `${result.seed} scrape`, content: JSON.stringify(result), source: `mcp-scraper:${fullPath}` });
|
|
2003
2231
|
return fullPath;
|
|
2004
2232
|
}
|
|
2005
2233
|
async writeCSV(rows, outputDir) {
|
|
@@ -2197,11 +2425,13 @@ function proxyIdSuffix2(proxyId) {
|
|
|
2197
2425
|
return proxyId ? proxyId.slice(-6) : null;
|
|
2198
2426
|
}
|
|
2199
2427
|
function resolution(source, proxyMode, proxyId, target, error, disposable = false) {
|
|
2428
|
+
const proxyType = source === "disabled" ? "none" : source === "configured_fallback" ? "configured" : source === "isp_created" ? "isp" : source === "location_created" || source === "location_reused" ? "residential" : "unknown";
|
|
2200
2429
|
return {
|
|
2201
2430
|
kernelProxyId: proxyId,
|
|
2202
2431
|
...disposable && proxyId ? { disposableProxyId: proxyId } : {},
|
|
2203
2432
|
resolution: {
|
|
2204
2433
|
source,
|
|
2434
|
+
proxyType,
|
|
2205
2435
|
proxyMode,
|
|
2206
2436
|
proxyIdPresent: Boolean(proxyId),
|
|
2207
2437
|
proxyIdSuffix: proxyIdSuffix2(proxyId),
|
|
@@ -2396,6 +2626,22 @@ async function createFreshLocationProxy(kernel, options, target) {
|
|
|
2396
2626
|
createErrors.push(`${candidate.proxyName}: ${errorText2(err)}`);
|
|
2397
2627
|
}
|
|
2398
2628
|
}
|
|
2629
|
+
if (process.env.KERNEL_PROXY_TYPE !== "residential") {
|
|
2630
|
+
const ispTarget = stateTarget(target);
|
|
2631
|
+
try {
|
|
2632
|
+
const created = await kernel.proxies.create({
|
|
2633
|
+
type: "isp",
|
|
2634
|
+
name: freshProxyName(`mcp-serp-isp-${ispTarget.country.toLowerCase()}-${(ispTarget.state ?? "").toLowerCase()}`, options.attemptIndex),
|
|
2635
|
+
config: { country: ispTarget.country, ...ispTarget.state ? { state: ispTarget.state } : {} }
|
|
2636
|
+
});
|
|
2637
|
+
if (created.id) {
|
|
2638
|
+
return resolution("isp_created", options.proxyMode, created.id, ispTarget, null, true);
|
|
2639
|
+
}
|
|
2640
|
+
createErrors.push("isp: Kernel did not return a proxy id");
|
|
2641
|
+
} catch (err) {
|
|
2642
|
+
createErrors.push(`isp: ${errorText2(err)}`);
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2399
2645
|
return resolution("configured_fallback", options.proxyMode, options.configuredKernelProxyId, target, createErrors.join(" | "));
|
|
2400
2646
|
}
|
|
2401
2647
|
async function deleteKernelProxyId(kernelApiKey, proxyId) {
|
|
@@ -2679,9 +2925,11 @@ async function harvest(rawOptions) {
|
|
|
2679
2925
|
};
|
|
2680
2926
|
const requestedDebug = typeof raw.debug === "boolean" ? raw.debug : false;
|
|
2681
2927
|
const needsLocationEvidence = proxyMode === "location" && Boolean(proxyOpts.location);
|
|
2682
|
-
const maxAttempts = maxAttemptsForProxyMode(proxyMode);
|
|
2928
|
+
const maxAttempts = maxAttemptsForProxyMode(proxyMode) + 1;
|
|
2683
2929
|
const serializer = new OutputSerializer();
|
|
2684
2930
|
let lastError = null;
|
|
2931
|
+
let effectiveProxyMode = proxyMode;
|
|
2932
|
+
let proxyFailures = 0;
|
|
2685
2933
|
for (let i = 0; i < maxAttempts; i++) {
|
|
2686
2934
|
const attemptNumber = i + 1;
|
|
2687
2935
|
const startedAtMs = Date.now();
|
|
@@ -2689,18 +2937,19 @@ async function harvest(rawOptions) {
|
|
|
2689
2937
|
if (signal?.aborted) throw abortReason(signal);
|
|
2690
2938
|
const resolution2 = await resolveKernelProxyId({
|
|
2691
2939
|
...proxyOpts,
|
|
2940
|
+
proxyMode: effectiveProxyMode,
|
|
2692
2941
|
attemptIndex: i,
|
|
2693
|
-
fresh:
|
|
2942
|
+
fresh: effectiveProxyMode === "location"
|
|
2694
2943
|
});
|
|
2695
2944
|
const mergedAttempt = {
|
|
2696
2945
|
...raw,
|
|
2697
2946
|
kernelApiKey,
|
|
2698
2947
|
kernelProxyId: resolution2.kernelProxyId,
|
|
2699
2948
|
kernelProxyResolution: resolution2.resolution,
|
|
2700
|
-
proxyMode,
|
|
2949
|
+
proxyMode: effectiveProxyMode,
|
|
2701
2950
|
debug: requestedDebug || needsLocationEvidence
|
|
2702
2951
|
};
|
|
2703
|
-
if (
|
|
2952
|
+
if (effectiveProxyMode === "none") mergedAttempt.kernelProxyId = void 0;
|
|
2704
2953
|
const attemptOptions = HarvestOptionsSchema.parse(mergedAttempt);
|
|
2705
2954
|
await emitAttemptEvent(onAttemptEvent, {
|
|
2706
2955
|
type: "started",
|
|
@@ -2719,10 +2968,17 @@ async function harvest(rawOptions) {
|
|
|
2719
2968
|
location: attemptOptions.location ?? null,
|
|
2720
2969
|
max_questions: attemptOptions.maxQuestions
|
|
2721
2970
|
}));
|
|
2722
|
-
const
|
|
2971
|
+
const isHeadfulEscalation = i === maxAttempts - 1;
|
|
2972
|
+
const needHeadful = isHeadfulEscalation || !attemptOptions.serpOnly;
|
|
2973
|
+
const baseCtx = currentCostContext();
|
|
2974
|
+
const attempt = needHeadful && baseCtx ? await runWithCostContext({ ...baseCtx, forceHeadful: true }, () => extractOnce(attemptOptions, signal)) : await extractOnce(attemptOptions, signal);
|
|
2723
2975
|
if (attempt.error) {
|
|
2724
2976
|
const err = attempt.error;
|
|
2725
2977
|
const outcome = classifyAttemptError(err);
|
|
2978
|
+
if ((outcome === "captcha" || outcome === "proxy_tunnel_failed") && effectiveProxyMode === "location") {
|
|
2979
|
+
proxyFailures++;
|
|
2980
|
+
if (proxyFailures >= 2) effectiveProxyMode = "none";
|
|
2981
|
+
}
|
|
2726
2982
|
const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome));
|
|
2727
2983
|
if (outcome === "captcha") {
|
|
2728
2984
|
console.warn(JSON.stringify({
|
|
@@ -2824,6 +3080,10 @@ async function harvest(rawOptions) {
|
|
|
2824
3080
|
return finalResult;
|
|
2825
3081
|
} catch (err) {
|
|
2826
3082
|
const outcome = classifyAttemptError(err);
|
|
3083
|
+
if ((outcome === "captcha" || outcome === "proxy_tunnel_failed") && effectiveProxyMode === "location") {
|
|
3084
|
+
proxyFailures++;
|
|
3085
|
+
if (proxyFailures >= 2) effectiveProxyMode = "none";
|
|
3086
|
+
}
|
|
2827
3087
|
const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome));
|
|
2828
3088
|
if (outcome === "captcha") {
|
|
2829
3089
|
console.warn(JSON.stringify({
|
|
@@ -2881,10 +3141,15 @@ async function harvest(rawOptions) {
|
|
|
2881
3141
|
|
|
2882
3142
|
export {
|
|
2883
3143
|
MapsSelectors,
|
|
3144
|
+
runWithCostContext,
|
|
3145
|
+
currentCostContext,
|
|
3146
|
+
migrateCostTelemetry,
|
|
3147
|
+
recordKernelSession,
|
|
3148
|
+
recordVendorUsage,
|
|
2884
3149
|
buildYouTubeChannelVideosUrl,
|
|
2885
3150
|
BrowserDriver,
|
|
2886
3151
|
deleteKernelProxyId,
|
|
2887
3152
|
resolveKernelProxyId,
|
|
2888
3153
|
harvest
|
|
2889
3154
|
};
|
|
2890
|
-
//# sourceMappingURL=chunk-
|
|
3155
|
+
//# sourceMappingURL=chunk-G7PQ64LM.js.map
|