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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/rates.ts","../src/api/harvest-problems.ts","../src/api/harvest-attempt-events.ts"],"sourcesContent":["export const MC_COSTS = {\n serp: 100,\n paa: 300,\n page_scrape: 100,\n url_map: 2_000,\n yt_channel: 200,\n yt_transcription: 200,\n fb_ad: 200,\n maps_search: 2_000,\n maps_place: 2_000,\n maps_review: 100,\n fb_search: 100,\n fb_transcribe: 200,\n instagram_profile: 200,\n instagram_media: 200,\n instagram_transcribe: 50,\n browser_minute: 12_000,\n reddit_thread: 300,\n} as const\n\nexport type McCostKey = keyof typeof MC_COSTS\n\nexport const MC_PER_BROWSER_MS = MC_COSTS.browser_minute / 60_000\n\nexport function browserActiveCostMc(activeMs: number): number {\n return Math.round(activeMs * MC_PER_BROWSER_MS)\n}\n\nexport const BROWSER_OPEN_MIN_BALANCE_MC = 1_000\n\nexport const MC_PER_CREDIT = 1_000\n\nexport const CREDIT_COST_CATALOG: Array<{\n key: McCostKey\n label: string\n aliases: string[]\n credits: number\n unit: string\n notes?: string\n}> = [\n {\n key: 'serp',\n label: 'SERP search',\n aliases: ['search_serp', 'serp', 'google search', 'organic results'],\n credits: mcToCredits(MC_COSTS.serp),\n unit: 'per search',\n notes: 'Returns AI Overview, PAA snippet, videos, forums, and local pack.',\n },\n {\n key: 'paa',\n label: 'PAA harvest',\n aliases: ['harvest_paa', 'paa', 'people also ask', 'questions'],\n credits: mcToCredits(MC_COSTS.paa),\n unit: 'per extracted question',\n notes: 'Includes full SERP feature extraction. Billed on actual questions returned — no cap enforced.',\n },\n {\n key: 'page_scrape',\n label: 'Page crawl / extract',\n aliases: ['extract_url', 'extract_site', 'page scrape', 'url scrape', 'single page', 'site crawl'],\n credits: mcToCredits(MC_COSTS.page_scrape),\n unit: 'per page',\n notes: 'Applies to both single-URL extraction and per-page site crawls.',\n },\n {\n key: 'url_map',\n label: 'Site URL mapping',\n aliases: ['map_site_urls', 'url map', 'site map', 'crawl urls'],\n credits: mcToCredits(MC_COSTS.url_map),\n unit: 'per mapping operation',\n notes: 'Flat rate for the full /map-urls call regardless of URL count discovered.',\n },\n {\n key: 'yt_channel',\n label: 'YouTube search / channel harvest',\n aliases: ['youtube_harvest', 'youtube search', 'youtube channel', 'yt_channel'],\n credits: mcToCredits(MC_COSTS.yt_channel),\n unit: 'per call',\n },\n {\n key: 'yt_transcription',\n label: 'YouTube transcription',\n aliases: ['youtube_transcribe', 'youtube transcript', 'transcription', 'yt_transcription'],\n credits: mcToCredits(MC_COSTS.yt_transcription),\n unit: 'per minute',\n notes: 'A 5-minute hold is taken, then reconciled to actual video duration.',\n },\n {\n key: 'fb_ad',\n label: 'Facebook search / ad lookup',\n aliases: ['facebook_page_intel', 'facebook_ad_search', 'facebook_ad', 'facebook ads', 'fb ads'],\n credits: mcToCredits(MC_COSTS.fb_ad),\n unit: 'per call',\n },\n {\n key: 'maps_search',\n label: 'Maps business search',\n aliases: ['maps_search', 'google maps search', 'gmb search', 'gbp search', 'business profiles'],\n credits: mcToCredits(MC_COSTS.maps_search),\n unit: 'per search',\n notes: 'Returns up to 50 Google Maps business/profile candidates. Use maps_place_intel to hydrate selected businesses.',\n },\n {\n key: 'maps_place',\n label: 'Maps business lookup',\n aliases: ['maps_place_intel', 'google maps', 'maps place', 'place intel'],\n credits: mcToCredits(MC_COSTS.maps_place),\n unit: 'per business',\n notes: 'Base lookup. Reviews billed separately per card at maps_review rate.',\n },\n {\n key: 'maps_review',\n label: 'Maps review',\n aliases: ['maps_reviews', 'google reviews', 'review cards', 'reviews'],\n credits: mcToCredits(MC_COSTS.maps_review),\n unit: 'per review card',\n notes: 'Charged after extraction when includeReviews is true.',\n },\n {\n key: 'fb_search',\n label: 'Facebook ad library search',\n aliases: ['facebook_search', 'fb_search', 'fb ad search'],\n credits: mcToCredits(MC_COSTS.fb_search),\n unit: 'per search',\n notes: 'Browser automation to search Facebook Ads Library by keyword.',\n },\n {\n key: 'fb_transcribe',\n label: 'Facebook video / ad transcription',\n aliases: ['facebook_transcribe', 'facebook_video_transcribe', 'fb_transcribe', 'fb ad transcript'],\n credits: mcToCredits(MC_COSTS.fb_transcribe),\n unit: 'per minute',\n notes: 'A hold is taken, then reconciled to actual video duration.',\n },\n {\n key: 'instagram_profile',\n label: 'Instagram profile content discovery',\n aliases: ['instagram_profile_content', 'instagram profile', 'instagram content list', 'ig profile'],\n credits: mcToCredits(MC_COSTS.instagram_profile),\n unit: 'per profile scan',\n notes: 'Browser extraction of public Instagram profile grid links. Complete history may require a logged-in profile.',\n },\n {\n key: 'instagram_media',\n label: 'Instagram media download',\n aliases: ['instagram_media_download', 'instagram reel download', 'instagram post download', 'ig media'],\n credits: mcToCredits(MC_COSTS.instagram_media),\n unit: 'per post or reel',\n notes: 'Extracts post text, image URLs, and reel audio/video tracks, with local downloads when the server can write files.',\n },\n {\n key: 'instagram_transcribe',\n label: 'Instagram media transcription',\n aliases: ['instagram transcript', 'instagram reel transcript', 'ig transcribe'],\n credits: mcToCredits(MC_COSTS.instagram_transcribe),\n unit: 'per call',\n notes: 'Whisper transcription of selected Instagram audio/video media via fal.ai.',\n },\n {\n key: 'browser_minute',\n label: 'Interactive browser session',\n aliases: ['browser_open', 'browser agent', 'browser_agent', 'live browser', 'browse', 'browser control', 'interactive browser'],\n credits: mcToCredits(MC_COSTS.browser_minute),\n unit: 'per minute of use',\n notes: 'Metered per minute of use for the whole time the browser session is open. Close the session to stop the meter; abandoned sessions are auto-closed after a short idle window.',\n },\n {\n key: 'reddit_thread',\n label: 'Reddit thread + comments',\n aliases: ['reddit', 'reddit_thread', 'reddit comments', 'subreddit', 'reddit post'],\n credits: mcToCredits(MC_COSTS.reddit_thread),\n unit: 'per thread',\n notes: 'Captures a Reddit post and its comment tree. Charged on success; refunded if the thread cannot be retrieved.',\n },\n]\n\nexport const CONCURRENCY_PRICE_ID = 'price_1Ta1NRS8aAcsk3TGwsRnYbix'\n\nexport interface SubscriptionTier { tier: string; label: string; price_id: string; monthly_usd: number; credits_mc: number; concurrency: number; intro_coupon: string | null }\n\nexport const SUBSCRIPTION_TIERS: Record<string, SubscriptionTier> = {\n 'price_1TmiHRS8aAcsk3TGwmSNfNIa': { tier: 'starter', label: 'Starter', price_id: 'price_1TmiHRS8aAcsk3TGwmSNfNIa', monthly_usd: 12, credits_mc: 8_000_000, concurrency: 3, intro_coupon: 'mcp-starter-1dollar-intro-12' },\n 'price_1Tmg1nS8aAcsk3TGe8zcnGTM': { tier: 'growth', label: 'Growth', price_id: 'price_1Tmg1nS8aAcsk3TGe8zcnGTM', monthly_usd: 100, credits_mc: 80_000_000, concurrency: 10, intro_coupon: null },\n 'price_1Tmg1nS8aAcsk3TGsZw34iXS': { tier: 'scale', label: 'Scale', price_id: 'price_1Tmg1nS8aAcsk3TGsZw34iXS', monthly_usd: 250, credits_mc: 240_000_000, concurrency: 20, intro_coupon: null },\n}\n\nexport const SUBSCRIPTION_TIER_BY_KEY: Record<string, SubscriptionTier> = Object.fromEntries(\n Object.values(SUBSCRIPTION_TIERS).map(t => [t.tier, t]),\n)\nexport const CONCURRENCY_SLOT_PRICE_USD = 5\nexport const CONCURRENCY_SLOT_PRICE_CURRENCY = 'usd'\nexport const CONCURRENCY_SLOT_PRICE_INTERVAL = 'month'\nexport const CONCURRENCY_SLOT_PRICE_LABEL = '$5/month'\nexport const CONCURRENCY_SLOT_TERMINAL_COMMAND = 'npx -y -p mcp-scraper@latest mcp-scraper-cli billing concurrency checkout'\n\nexport function concurrencySlotBillingInfo() {\n const billingUrl = process.env.CONCURRENCY_BILLING_URL ?? 'https://mcpscraper.dev/billing'\n return {\n product: 'Extra concurrency slot',\n price_label: CONCURRENCY_SLOT_PRICE_LABEL,\n unit_amount_usd: CONCURRENCY_SLOT_PRICE_USD,\n currency: CONCURRENCY_SLOT_PRICE_CURRENCY,\n interval: CONCURRENCY_SLOT_PRICE_INTERVAL,\n billing_url: billingUrl,\n terminal_command: CONCURRENCY_SLOT_TERMINAL_COMMAND,\n terminal_command_with_api_key_env: `MCP_SCRAPER_API_KEY=sk_live_your_key ${CONCURRENCY_SLOT_TERMINAL_COMMAND}`,\n }\n}\n\nexport const FREE_SIGNUP_MC = 100_000\nexport const FREE_MONTHLY_REFRESH_MC = 250_000\n\nexport const BALANCE_PRICE_IDS: Record<string, number> = {\n 'price_1TZx6rS8aAcsk3TGNMc1Vgpo': 11_000_000,\n 'price_1TZx6sS8aAcsk3TGxgqB7khO': 27_500_000,\n 'price_1TZx6tS8aAcsk3TG8PnJqHlG': 60_500_000,\n 'price_1TZx6tS8aAcsk3TGNgRMpy0e': 121_000_000,\n}\n\nexport const BALANCE_PACK_LABELS: Record<string, string> = {\n 'price_1TZx6rS8aAcsk3TGNMc1Vgpo': '$10',\n 'price_1TZx6sS8aAcsk3TGxgqB7khO': '$25',\n 'price_1TZx6tS8aAcsk3TG8PnJqHlG': '$50',\n 'price_1TZx6tS8aAcsk3TGNgRMpy0e': '$100',\n}\n\nexport function mcToCredits(mc: number): number {\n return mc / MC_PER_CREDIT\n}\n\nexport function insufficientBalanceResponse(balanceMc: number, requiredMc: number) {\n const topupUrl = process.env.TOPUP_URL ?? 'https://mcpscraper.dev/billing'\n const balanceCredits = mcToCredits(balanceMc)\n const requiredCredits = mcToCredits(requiredMc)\n return {\n error: 'insufficient_balance',\n error_code: 'insufficient_balance' as const,\n message: `Insufficient credits. Balance: ${balanceCredits} credits. This call requires ${requiredCredits} credits. Top up at ${topupUrl}`,\n balance_credits: balanceCredits,\n required_credits: requiredCredits,\n topup_url: topupUrl,\n }\n}\n\nexport interface MemoryPlan {\n plan: 'pro' | 'team'\n label: string\n price_id: string\n interval: 'month' | 'year'\n monthly_usd: number\n quota_bytes: number\n}\n\nexport const MEMORY_FREE_QUOTA_BYTES = 0.1e9 * 1\n\nexport const MEMORY_PLANS: Record<string, MemoryPlan> = {\n 'price_1TnMSTS8aAcsk3TGBgiwuvqL': { plan: 'pro', label: 'Pro', price_id: 'price_1TnMSTS8aAcsk3TGBgiwuvqL', interval: 'month', monthly_usd: 19, quota_bytes: 5e9 },\n 'price_1TnMSTS8aAcsk3TGWBVU2agY': { plan: 'pro', label: 'Pro', price_id: 'price_1TnMSTS8aAcsk3TGWBVU2agY', interval: 'year', monthly_usd: 19, quota_bytes: 5e9 },\n}\n\nexport const MEMORY_PLAN_QUOTA: Record<string, number> = { free: MEMORY_FREE_QUOTA_BYTES, pro: 5e9, team: 50e9 }\n\nexport const MEMORY_MARGIN_MULTIPLE = 3\nexport const MEMORY_FREE_COST_CAP_USD = 1.0\nexport const MEMORY_PRO_COST_BUDGET_USD = 19 / MEMORY_MARGIN_MULTIPLE\n\nexport const LedgerOperation = {\n TOPUP: 'topup',\n SUBSCRIPTION: 'subscription',\n SIGNUP_GRANT: 'signup_grant',\n MONTHLY_REFRESH: 'monthly_free_refresh',\n PAA: 'paa',\n PAA_REFUND: 'paa_refund',\n SERP: 'serp',\n REFUND: 'refund',\n TRANSCRIPTION: 'transcription',\n TRANSCRIPTION_HOLD: 'transcription_hold',\n TRANSCRIPTION_REFUND: 'transcription_refund',\n YT_CHANNEL: 'yt_channel',\n FB_AD: 'fb_ad',\n MAPS_SEARCH: 'maps_search',\n MAPS_PLACE: 'maps_place',\n MAPS_REVIEW: 'maps_review',\n MAPS_REVIEW_REFUND: 'maps_review_refund',\n EXTRACT_SITE: 'extract_site',\n EXTRACT_SITE_REFUND: 'extract_site_refund',\n EXTRACT_URL: 'page_scrape',\n URL_MAP: 'url_map',\n EXTRACT_SITE_HOLD: 'extract_site_hold',\n YT_CHANNEL_REFUND: 'yt_channel_refund',\n FB_AD_REFUND: 'fb_ad_refund',\n URL_MAP_REFUND: 'url_map_refund',\n FB_SEARCH: 'fb_search',\n FB_TRANSCRIBE: 'fb_transcribe',\n FB_SEARCH_REFUND: 'fb_search_refund',\n FB_TRANSCRIBE_REFUND: 'fb_transcribe_refund',\n INSTAGRAM_PROFILE: 'instagram_profile',\n INSTAGRAM_PROFILE_REFUND: 'instagram_profile_refund',\n INSTAGRAM_MEDIA: 'instagram_media',\n INSTAGRAM_MEDIA_REFUND:'instagram_media_refund',\n INSTAGRAM_TRANSCRIBE: 'instagram_transcribe',\n INSTAGRAM_TRANSCRIBE_REFUND: 'instagram_transcribe_refund',\n BROWSER_SESSION: 'browser_session',\n REDDIT_THREAD: 'reddit_thread',\n REDDIT_THREAD_REFUND: 'reddit_thread_refund',\n} as const\n\nexport type LedgerOperation = typeof LedgerOperation[keyof typeof LedgerOperation]\n","import { CaptchaError, LocationMismatchError, RequestAbortedError } from '../errors.js'\n\nexport type HarvestProblemCode =\n | 'request_aborted'\n | 'captcha_exhausted'\n | 'location_mismatch'\n | 'proxy_tunnel_failed'\n | 'proxy_unavailable'\n | 'harvest_timeout'\n | 'extraction_failed'\n\nexport interface HarvestProblem {\n error_code: HarvestProblemCode\n error_type: string\n message: string\n retryable: boolean\n httpStatus: number\n terminalStatus: 'cancelled' | 'failed'\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nfunction looksLikeTimeout(err: unknown, message: string): boolean {\n if (err instanceof DOMException && (err.name === 'TimeoutError' || err.name === 'AbortError')) return true\n return /timeout|timed out|Timeout \\d+ms exceeded|deadline/i.test(message)\n}\n\nfunction looksLikeCaptcha(message: string): boolean {\n return /captcha|recaptcha|unusual traffic|google\\.com\\/sorry|blocked/i.test(message)\n}\n\nfunction looksLikeProxyTunnelFailure(message: string): boolean {\n return /ERR_TUNNEL_CONNECTION_FAILED|ERR_PROXY_CONNECTION_FAILED|ERR_SOCKS_CONNECTION_FAILED|tunnel connection failed|proxy connection failed|transport error: proxy/i.test(message)\n}\n\nfunction looksLikeProxyUnavailable(message: string): boolean {\n return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message)\n}\n\nexport function classifyHarvestProblem(err: unknown): HarvestProblem {\n const message = errorMessage(err)\n\n if (err instanceof RequestAbortedError) {\n return {\n error_code: 'request_aborted',\n error_type: 'request_aborted',\n message,\n retryable: true,\n httpStatus: 408,\n terminalStatus: 'cancelled',\n }\n }\n\n if (err instanceof CaptchaError || looksLikeCaptcha(message)) {\n return {\n error_code: 'captcha_exhausted',\n error_type: 'captcha',\n message,\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (err instanceof LocationMismatchError) {\n return {\n error_code: 'location_mismatch',\n error_type: 'location_mismatch',\n message,\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeProxyTunnelFailure(message)) {\n return {\n error_code: 'proxy_tunnel_failed',\n error_type: 'proxy_tunnel_failed',\n message,\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeProxyUnavailable(message)) {\n return {\n error_code: 'proxy_unavailable',\n error_type: 'proxy_unavailable',\n message,\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeTimeout(err, message)) {\n return {\n error_code: 'harvest_timeout',\n error_type: 'timeout',\n message,\n retryable: true,\n httpStatus: 504,\n terminalStatus: 'failed',\n }\n }\n\n return {\n error_code: 'extraction_failed',\n error_type: 'extraction',\n message,\n retryable: false,\n httpStatus: 500,\n terminalStatus: 'failed',\n }\n}\n\nexport function serializeHarvestProblem(problem: HarvestProblem): string {\n return JSON.stringify({\n error_code: problem.error_code,\n error_type: problem.error_type,\n message: problem.message,\n retryable: problem.retryable,\n })\n}\n\nexport function harvestProblemResponse(problem: HarvestProblem): {\n error: string\n error_code: HarvestProblemCode\n error_type: string\n retryable: boolean\n} {\n return {\n error: problem.message,\n error_code: problem.error_code,\n error_type: problem.error_type,\n retryable: problem.retryable,\n }\n}\n","import type { HarvestAttemptLogEvent } from '../harvest.js'\nimport { finishHarvestAttempt, startHarvestAttempt } from './db.js'\n\nexport function createHarvestAttemptRecorder(jobId: string, userId: number | bigint) {\n return async (event: HarvestAttemptLogEvent): Promise<void> => {\n if (event.type === 'started') {\n await startHarvestAttempt({\n jobId,\n userId,\n attemptNumber: event.attemptNumber,\n maxAttempts: event.maxAttempts,\n query: event.query,\n location: event.location,\n maxQuestions: event.maxQuestions,\n startedAt: event.startedAt,\n })\n return\n }\n\n await finishHarvestAttempt({\n jobId,\n attemptNumber: event.attemptNumber,\n outcome: event.outcome,\n kernelSessionId: event.kernelSessionId,\n questionCount: event.questionCount,\n durationMs: event.durationMs,\n error: event.error,\n willRetry: event.willRetry,\n kernelDeleteStarted: event.cleanup.kernelDeleteStarted,\n kernelDeleteSucceeded: event.cleanup.kernelDeleteSucceeded,\n kernelDeleteError: event.cleanup.kernelDeleteError,\n browserCloseSucceeded: event.cleanup.browserCloseSucceeded,\n browserCloseError: event.cleanup.browserCloseError,\n debug: event.debug,\n completedAt: event.completedAt,\n })\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
completeJob,
|
|
13
13
|
completeKpoJob,
|
|
14
14
|
completeWorkflowRunRecord,
|
|
15
|
+
consumeCode,
|
|
15
16
|
consumePasswordResetToken,
|
|
16
17
|
countActiveJobsForUser,
|
|
17
18
|
countActiveUsers,
|
|
@@ -32,15 +33,20 @@ import {
|
|
|
32
33
|
failWorkflowRunRecord,
|
|
33
34
|
finishHarvestAttempt,
|
|
34
35
|
generateApiKey,
|
|
36
|
+
getClient,
|
|
35
37
|
getDb,
|
|
36
38
|
getJob,
|
|
37
39
|
getKpoJob,
|
|
38
40
|
getKpoPhaseLog,
|
|
39
41
|
getLedger,
|
|
42
|
+
getMemoryPlan,
|
|
43
|
+
getMemoryProvisioned,
|
|
44
|
+
getRefresh,
|
|
40
45
|
getUserByApiKey,
|
|
41
46
|
getUserByEmail,
|
|
42
47
|
getUserById,
|
|
43
48
|
getUserByStripeCustomerId,
|
|
49
|
+
getUserMemoryCreds,
|
|
44
50
|
getUserStats,
|
|
45
51
|
getWorkflowArtifact,
|
|
46
52
|
getWorkflowRun,
|
|
@@ -66,23 +72,32 @@ import {
|
|
|
66
72
|
migrate,
|
|
67
73
|
migrateExistingBalancesToLots,
|
|
68
74
|
patchWorkflowSchedule,
|
|
75
|
+
putCode,
|
|
76
|
+
putRefresh,
|
|
69
77
|
reconcileBalanceMc,
|
|
70
78
|
recordStripeEvent,
|
|
71
79
|
recordWorkflowWebhookDelivery,
|
|
80
|
+
registerClient,
|
|
72
81
|
replaceWorkflowArtifacts,
|
|
82
|
+
resolveUserByMemorySubscription,
|
|
73
83
|
revokeApiKey,
|
|
84
|
+
revokeRefresh,
|
|
74
85
|
rotateApiKey,
|
|
86
|
+
rotateRefresh,
|
|
75
87
|
setConcurrencySubId,
|
|
76
88
|
setDbForTesting,
|
|
77
89
|
setExtraConcurrencySlots,
|
|
90
|
+
setMemoryPlan,
|
|
91
|
+
setMemoryProvisioned,
|
|
78
92
|
setPassword,
|
|
79
93
|
setStripeCustomerId,
|
|
80
94
|
setSubscriptionTier,
|
|
95
|
+
setUserMemoryCreds,
|
|
81
96
|
startHarvestAttempt,
|
|
82
97
|
stripeEventAlreadyProcessed,
|
|
83
98
|
updateKpoJobState,
|
|
84
99
|
verifyPassword
|
|
85
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-TRNSXR46.js";
|
|
86
101
|
export {
|
|
87
102
|
CREDIT_LOT_TTL,
|
|
88
103
|
SiteAuditJobRowSchema,
|
|
@@ -97,6 +112,7 @@ export {
|
|
|
97
112
|
completeJob,
|
|
98
113
|
completeKpoJob,
|
|
99
114
|
completeWorkflowRunRecord,
|
|
115
|
+
consumeCode,
|
|
100
116
|
consumePasswordResetToken,
|
|
101
117
|
countActiveJobsForUser,
|
|
102
118
|
countActiveUsers,
|
|
@@ -117,15 +133,20 @@ export {
|
|
|
117
133
|
failWorkflowRunRecord,
|
|
118
134
|
finishHarvestAttempt,
|
|
119
135
|
generateApiKey,
|
|
136
|
+
getClient,
|
|
120
137
|
getDb,
|
|
121
138
|
getJob,
|
|
122
139
|
getKpoJob,
|
|
123
140
|
getKpoPhaseLog,
|
|
124
141
|
getLedger,
|
|
142
|
+
getMemoryPlan,
|
|
143
|
+
getMemoryProvisioned,
|
|
144
|
+
getRefresh,
|
|
125
145
|
getUserByApiKey,
|
|
126
146
|
getUserByEmail,
|
|
127
147
|
getUserById,
|
|
128
148
|
getUserByStripeCustomerId,
|
|
149
|
+
getUserMemoryCreds,
|
|
129
150
|
getUserStats,
|
|
130
151
|
getWorkflowArtifact,
|
|
131
152
|
getWorkflowRun,
|
|
@@ -151,21 +172,30 @@ export {
|
|
|
151
172
|
migrate,
|
|
152
173
|
migrateExistingBalancesToLots,
|
|
153
174
|
patchWorkflowSchedule,
|
|
175
|
+
putCode,
|
|
176
|
+
putRefresh,
|
|
154
177
|
reconcileBalanceMc,
|
|
155
178
|
recordStripeEvent,
|
|
156
179
|
recordWorkflowWebhookDelivery,
|
|
180
|
+
registerClient,
|
|
157
181
|
replaceWorkflowArtifacts,
|
|
182
|
+
resolveUserByMemorySubscription,
|
|
158
183
|
revokeApiKey,
|
|
184
|
+
revokeRefresh,
|
|
159
185
|
rotateApiKey,
|
|
186
|
+
rotateRefresh,
|
|
160
187
|
setConcurrencySubId,
|
|
161
188
|
setDbForTesting,
|
|
162
189
|
setExtraConcurrencySlots,
|
|
190
|
+
setMemoryPlan,
|
|
191
|
+
setMemoryProvisioned,
|
|
163
192
|
setPassword,
|
|
164
193
|
setStripeCustomerId,
|
|
165
194
|
setSubscriptionTier,
|
|
195
|
+
setUserMemoryCreds,
|
|
166
196
|
startHarvestAttempt,
|
|
167
197
|
stripeEventAlreadyProcessed,
|
|
168
198
|
updateKpoJobState,
|
|
169
199
|
verifyPassword
|
|
170
200
|
};
|
|
171
|
-
//# sourceMappingURL=db-
|
|
201
|
+
//# sourceMappingURL=db-FP2ABUU4.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -38,7 +38,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
38
38
|
|
|
39
39
|
// src/schemas.ts
|
|
40
40
|
var import_zod = require("zod");
|
|
41
|
-
var DEFAULT_PROXY_MODE = "
|
|
41
|
+
var DEFAULT_PROXY_MODE = "none";
|
|
42
42
|
var DEFAULT_MAPS_PROXY_MODE = "location";
|
|
43
43
|
var HarvestOptionsSchema = import_zod.z.object({
|
|
44
44
|
query: import_zod.z.string().min(1),
|
|
@@ -60,7 +60,8 @@ var HarvestOptionsSchema = import_zod.z.object({
|
|
|
60
60
|
outputDir: import_zod.z.string().default("./paa-output"),
|
|
61
61
|
format: import_zod.z.enum(["json", "csv", "both"]).default("both"),
|
|
62
62
|
serpOnly: import_zod.z.boolean().default(false),
|
|
63
|
-
pages: import_zod.z.number().int().min(1).max(2).default(1)
|
|
63
|
+
pages: import_zod.z.number().int().min(1).max(2).default(1),
|
|
64
|
+
softDeadlineMs: import_zod.z.number().optional()
|
|
64
65
|
});
|
|
65
66
|
var MapsPlaceOptionsSchema = import_zod.z.object({
|
|
66
67
|
businessName: import_zod.z.string().min(1),
|
|
@@ -140,8 +141,7 @@ function browserServiceApiKey() {
|
|
|
140
141
|
return value || void 0;
|
|
141
142
|
}
|
|
142
143
|
function browserServiceProxyId() {
|
|
143
|
-
|
|
144
|
-
return value || void 0;
|
|
144
|
+
return void 0;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
// src/driver/BrowserDriver.ts
|
|
@@ -267,6 +267,192 @@ var LocationMismatchError = class extends Error {
|
|
|
267
267
|
}
|
|
268
268
|
};
|
|
269
269
|
|
|
270
|
+
// src/api/cost-telemetry.ts
|
|
271
|
+
var import_node_crypto2 = require("crypto");
|
|
272
|
+
|
|
273
|
+
// src/api/db.ts
|
|
274
|
+
var import_http = require("@libsql/client/http");
|
|
275
|
+
var import_node_crypto = require("crypto");
|
|
276
|
+
var import_zod2 = require("zod");
|
|
277
|
+
var DB_URL = process.env.TURSO_DATABASE_URL ?? "file:./paa-api.db";
|
|
278
|
+
var DB_TOKEN = process.env.TURSO_AUTH_TOKEN;
|
|
279
|
+
var _db = null;
|
|
280
|
+
function getDb() {
|
|
281
|
+
if (!_db) _db = (0, import_http.createClient)({ url: DB_URL, authToken: DB_TOKEN });
|
|
282
|
+
return _db;
|
|
283
|
+
}
|
|
284
|
+
var SiteAuditJobRowSchema = import_zod2.z.object({
|
|
285
|
+
id: import_zod2.z.string(),
|
|
286
|
+
user_id: import_zod2.z.number(),
|
|
287
|
+
status: import_zod2.z.string().default("pending"),
|
|
288
|
+
client_domain: import_zod2.z.string(),
|
|
289
|
+
session_path: import_zod2.z.string(),
|
|
290
|
+
request: import_zod2.z.string(),
|
|
291
|
+
result: import_zod2.z.string().nullable(),
|
|
292
|
+
error: import_zod2.z.string().nullable(),
|
|
293
|
+
created_at: import_zod2.z.string(),
|
|
294
|
+
updated_at: import_zod2.z.string()
|
|
295
|
+
});
|
|
296
|
+
var SiteAuditPhaseLogRowSchema = import_zod2.z.object({
|
|
297
|
+
id: import_zod2.z.number(),
|
|
298
|
+
job_id: import_zod2.z.string(),
|
|
299
|
+
phase: import_zod2.z.string(),
|
|
300
|
+
completed_at: import_zod2.z.string(),
|
|
301
|
+
output_summary: import_zod2.z.string()
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// src/api/cost-context.ts
|
|
305
|
+
var import_node_async_hooks = require("async_hooks");
|
|
306
|
+
var storage = new import_node_async_hooks.AsyncLocalStorage();
|
|
307
|
+
function runWithCostContext(ctx, fn) {
|
|
308
|
+
return storage.run(ctx, fn);
|
|
309
|
+
}
|
|
310
|
+
function currentCostContext() {
|
|
311
|
+
return storage.getStore();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/api/cost-rates.ts
|
|
315
|
+
var KERNEL_HEADLESS_USD_PER_SEC = 166667e-10;
|
|
316
|
+
var KERNEL_HEADFUL_USD_PER_SEC = 1333336e-10;
|
|
317
|
+
var HEADLESS_OPS = /* @__PURE__ */ new Set(["harvest", "maps_search", "yt_channel", "fb_search", "fb_ad", "instagram"]);
|
|
318
|
+
function kernelCostUsd(ms, headful) {
|
|
319
|
+
const sec = Math.max(0, ms) / 1e3;
|
|
320
|
+
return sec * (headful ? KERNEL_HEADFUL_USD_PER_SEC : KERNEL_HEADLESS_USD_PER_SEC);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/api/cost-telemetry.ts
|
|
324
|
+
var _ready = false;
|
|
325
|
+
async function migrateCostTelemetry() {
|
|
326
|
+
if (_ready) return;
|
|
327
|
+
const db = getDb();
|
|
328
|
+
await db.execute(`
|
|
329
|
+
CREATE TABLE IF NOT EXISTS kernel_session_log (
|
|
330
|
+
id TEXT PRIMARY KEY,
|
|
331
|
+
kernel_session_id TEXT,
|
|
332
|
+
op TEXT,
|
|
333
|
+
source TEXT NOT NULL,
|
|
334
|
+
probe_run_id TEXT,
|
|
335
|
+
user_id INTEGER,
|
|
336
|
+
stealth INTEGER,
|
|
337
|
+
headless_sent INTEGER,
|
|
338
|
+
proxy_used INTEGER,
|
|
339
|
+
fallback INTEGER NOT NULL DEFAULT 0,
|
|
340
|
+
opened_at TEXT,
|
|
341
|
+
closed_at TEXT,
|
|
342
|
+
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
343
|
+
est_cost_usd_headless REAL NOT NULL DEFAULT 0,
|
|
344
|
+
est_cost_usd_headful REAL NOT NULL DEFAULT 0,
|
|
345
|
+
error TEXT,
|
|
346
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
347
|
+
)
|
|
348
|
+
`);
|
|
349
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_op ON kernel_session_log(op)`);
|
|
350
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_probe ON kernel_session_log(probe_run_id)`);
|
|
351
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_created ON kernel_session_log(created_at)`);
|
|
352
|
+
try {
|
|
353
|
+
await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_source TEXT`);
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_type TEXT`);
|
|
358
|
+
} catch {
|
|
359
|
+
}
|
|
360
|
+
await db.execute(`
|
|
361
|
+
CREATE TABLE IF NOT EXISTS vendor_usage_log (
|
|
362
|
+
id TEXT PRIMARY KEY,
|
|
363
|
+
op TEXT,
|
|
364
|
+
probe_run_id TEXT,
|
|
365
|
+
user_id INTEGER,
|
|
366
|
+
vendor TEXT NOT NULL,
|
|
367
|
+
model TEXT,
|
|
368
|
+
units REAL NOT NULL DEFAULT 0,
|
|
369
|
+
unit_type TEXT,
|
|
370
|
+
est_cost_usd REAL NOT NULL DEFAULT 0,
|
|
371
|
+
error TEXT,
|
|
372
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
373
|
+
)
|
|
374
|
+
`);
|
|
375
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_op ON vendor_usage_log(op)`);
|
|
376
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_probe ON vendor_usage_log(probe_run_id)`);
|
|
377
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_created ON vendor_usage_log(created_at)`);
|
|
378
|
+
await db.execute(`
|
|
379
|
+
CREATE TABLE IF NOT EXISTS cost_probe_runs (
|
|
380
|
+
id TEXT PRIMARY KEY,
|
|
381
|
+
tool TEXT NOT NULL,
|
|
382
|
+
target TEXT,
|
|
383
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
384
|
+
wall_ms INTEGER NOT NULL DEFAULT 0,
|
|
385
|
+
success INTEGER NOT NULL DEFAULT 0,
|
|
386
|
+
error TEXT,
|
|
387
|
+
http_only INTEGER NOT NULL DEFAULT 0,
|
|
388
|
+
kernel_used INTEGER NOT NULL DEFAULT 0,
|
|
389
|
+
kernel_sessions INTEGER NOT NULL DEFAULT 0,
|
|
390
|
+
kernel_seconds_total REAL NOT NULL DEFAULT 0,
|
|
391
|
+
kernel_tier_observed TEXT,
|
|
392
|
+
est_kernel_cost_usd_headless REAL NOT NULL DEFAULT 0,
|
|
393
|
+
est_kernel_cost_usd_headful REAL NOT NULL DEFAULT 0,
|
|
394
|
+
vendor_cost_usd REAL NOT NULL DEFAULT 0,
|
|
395
|
+
charged_credits REAL NOT NULL DEFAULT 0,
|
|
396
|
+
margin_usd_headless_starter REAL NOT NULL DEFAULT 0,
|
|
397
|
+
margin_usd_headful_starter REAL NOT NULL DEFAULT 0,
|
|
398
|
+
notes TEXT
|
|
399
|
+
)
|
|
400
|
+
`);
|
|
401
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS cost_probe_runs_tool ON cost_probe_runs(tool)`);
|
|
402
|
+
try {
|
|
403
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN units REAL`);
|
|
404
|
+
} catch {
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN unit_type TEXT`);
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN mode TEXT`);
|
|
412
|
+
} catch {
|
|
413
|
+
}
|
|
414
|
+
_ready = true;
|
|
415
|
+
}
|
|
416
|
+
async function recordKernelSession(r) {
|
|
417
|
+
try {
|
|
418
|
+
await migrateCostTelemetry();
|
|
419
|
+
const ctx = currentCostContext();
|
|
420
|
+
const durationMs = Math.max(0, r.closedAtMs - r.openedAtMs);
|
|
421
|
+
const db = getDb();
|
|
422
|
+
await db.execute({
|
|
423
|
+
sql: `INSERT INTO kernel_session_log
|
|
424
|
+
(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)
|
|
425
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
426
|
+
args: [
|
|
427
|
+
(0, import_node_crypto2.randomUUID)(),
|
|
428
|
+
r.kernelSessionId ?? null,
|
|
429
|
+
ctx?.op ?? null,
|
|
430
|
+
r.source,
|
|
431
|
+
ctx?.probeRunId ?? null,
|
|
432
|
+
ctx?.userId ?? null,
|
|
433
|
+
boolToInt(r.stealth),
|
|
434
|
+
boolToInt(r.headlessSent),
|
|
435
|
+
boolToInt(r.proxyUsed),
|
|
436
|
+
r.proxySource ?? null,
|
|
437
|
+
r.proxyType ?? null,
|
|
438
|
+
r.fallback ? 1 : 0,
|
|
439
|
+
new Date(r.openedAtMs).toISOString(),
|
|
440
|
+
new Date(r.closedAtMs).toISOString(),
|
|
441
|
+
durationMs,
|
|
442
|
+
kernelCostUsd(durationMs, false),
|
|
443
|
+
kernelCostUsd(durationMs, true),
|
|
444
|
+
r.error ?? null
|
|
445
|
+
]
|
|
446
|
+
});
|
|
447
|
+
} catch (err) {
|
|
448
|
+
console.warn("[cost-telemetry] recordKernelSession failed:", err instanceof Error ? err.message : String(err));
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function boolToInt(v) {
|
|
452
|
+
if (v === null || v === void 0) return null;
|
|
453
|
+
return v ? 1 : 0;
|
|
454
|
+
}
|
|
455
|
+
|
|
270
456
|
// src/driver/BrowserDriver.ts
|
|
271
457
|
import_playwright_extra.chromium.use((0, import_puppeteer_extra_plugin_stealth.default)());
|
|
272
458
|
var DESKTOP_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
@@ -354,6 +540,12 @@ var BrowserDriver = class {
|
|
|
354
540
|
page = null;
|
|
355
541
|
kernelClient = null;
|
|
356
542
|
kernelSessionId = null;
|
|
543
|
+
kernelOpenedAtMs = null;
|
|
544
|
+
kernelStealth = null;
|
|
545
|
+
kernelProxyUsed = null;
|
|
546
|
+
kernelProxySource = null;
|
|
547
|
+
kernelProxyType = null;
|
|
548
|
+
kernelHeadlessSent = null;
|
|
357
549
|
debugEnabled = false;
|
|
358
550
|
debugSnapshot = {
|
|
359
551
|
kernel: null,
|
|
@@ -385,9 +577,16 @@ var BrowserDriver = class {
|
|
|
385
577
|
if (config.kernelProfileName && config.kernelProfileSaveChanges === true) {
|
|
386
578
|
await ensureKernelProfile(this.kernelClient, config.kernelProfileName);
|
|
387
579
|
}
|
|
580
|
+
const costCtx = currentCostContext();
|
|
581
|
+
let useHeadless;
|
|
582
|
+
if (config.headlessMode === "headful" || costCtx?.forceHeadful === true) useHeadless = false;
|
|
583
|
+
else if (config.headlessMode === "headless") useHeadless = true;
|
|
584
|
+
else useHeadless = costCtx?.forceHeadless === true || process.env.KERNEL_HEADLESS_DISABLED !== "true" && !!costCtx?.op && HEADLESS_OPS.has(costCtx.op);
|
|
585
|
+
this.kernelHeadlessSent = useHeadless ? true : null;
|
|
388
586
|
const kernelBrowser = await this.kernelClient.browsers.create({
|
|
389
587
|
stealth: true,
|
|
390
588
|
timeout_seconds: timeoutSeconds,
|
|
589
|
+
...useHeadless ? { headless: true } : {},
|
|
391
590
|
...config.kernelProxyId ? { proxy_id: config.kernelProxyId } : {},
|
|
392
591
|
...config.kernelProfileName ? {
|
|
393
592
|
profile: {
|
|
@@ -397,9 +596,14 @@ var BrowserDriver = class {
|
|
|
397
596
|
} : {}
|
|
398
597
|
});
|
|
399
598
|
this.kernelSessionId = kernelBrowser.session_id;
|
|
599
|
+
this.kernelOpenedAtMs = Date.now();
|
|
600
|
+
this.kernelStealth = typeof kernelBrowser.stealth === "boolean" ? kernelBrowser.stealth : null;
|
|
601
|
+
this.kernelProxyUsed = typeof kernelBrowser.proxy_id === "string" ? Boolean(kernelBrowser.proxy_id) : Boolean(config.kernelProxyId);
|
|
602
|
+
this.kernelProxySource = config.kernelProxyResolution?.source ?? null;
|
|
603
|
+
this.kernelProxyType = config.kernelProxyResolution?.proxyType ?? null;
|
|
400
604
|
let defaultProxyDisabled = null;
|
|
401
605
|
let defaultProxyDisableError = null;
|
|
402
|
-
if (
|
|
606
|
+
if (!config.kernelProxyId) {
|
|
403
607
|
try {
|
|
404
608
|
await withTimeout(
|
|
405
609
|
this.kernelClient.browsers.update(this.kernelSessionId, { disable_default_proxy: true }),
|
|
@@ -750,11 +954,24 @@ var BrowserDriver = class {
|
|
|
750
954
|
const b = this.browser;
|
|
751
955
|
const sessionId = this.kernelSessionId;
|
|
752
956
|
const client = this.kernelClient;
|
|
957
|
+
const openedAtMs = this.kernelOpenedAtMs;
|
|
958
|
+
const stealth = this.kernelStealth;
|
|
959
|
+
const proxyUsed = this.kernelProxyUsed;
|
|
960
|
+
const proxySource = this.kernelProxySource;
|
|
961
|
+
const proxyType = this.kernelProxyType;
|
|
962
|
+
const headlessSent = this.kernelHeadlessSent;
|
|
753
963
|
this.browser = null;
|
|
754
964
|
this.context = null;
|
|
755
965
|
this.page = null;
|
|
756
966
|
this.kernelSessionId = null;
|
|
757
967
|
this.kernelClient = null;
|
|
968
|
+
this.kernelOpenedAtMs = null;
|
|
969
|
+
this.kernelHeadlessSent = null;
|
|
970
|
+
this.kernelProxySource = null;
|
|
971
|
+
this.kernelProxyType = null;
|
|
972
|
+
if (client && sessionId && openedAtMs != null) {
|
|
973
|
+
void recordKernelSession({ kernelSessionId: sessionId, source: "browser_driver", stealth, headlessSent, proxyUsed, proxySource, proxyType, openedAtMs, closedAtMs: Date.now() });
|
|
974
|
+
}
|
|
758
975
|
if (client && sessionId) {
|
|
759
976
|
console.info(JSON.stringify({
|
|
760
977
|
event: "kernel_browser_delete_started",
|
|
@@ -1469,6 +1686,7 @@ var PAAExtractor = class {
|
|
|
1469
1686
|
);
|
|
1470
1687
|
let round = 0;
|
|
1471
1688
|
while (seenQs.size < options.maxQuestions) {
|
|
1689
|
+
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
1472
1690
|
this.throwIfAborted(signal);
|
|
1473
1691
|
await this.throwIfCaptcha(page, "Google PAA expansion");
|
|
1474
1692
|
const beforeQs = await readAllQs();
|
|
@@ -1478,6 +1696,7 @@ var PAAExtractor = class {
|
|
|
1478
1696
|
if (unexpandedCount === 0) break;
|
|
1479
1697
|
this.reporter.onDepth(++round);
|
|
1480
1698
|
for (let ci = 0; ci < unexpandedCount; ci++) {
|
|
1699
|
+
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
1481
1700
|
this.throwIfAborted(signal);
|
|
1482
1701
|
try {
|
|
1483
1702
|
const btn = page.locator(unexpandedSel).first();
|
|
@@ -2141,6 +2360,28 @@ var PAAExtractor = class {
|
|
|
2141
2360
|
var import_node_fs = require("fs");
|
|
2142
2361
|
var import_node_path = __toESM(require("path"), 1);
|
|
2143
2362
|
var import_papaparse = __toESM(require("papaparse"), 1);
|
|
2363
|
+
|
|
2364
|
+
// src/output/memory-library-sink.ts
|
|
2365
|
+
async function postToMemoryLibrary(args) {
|
|
2366
|
+
const url = process.env.MCP_MEMORY_INGEST_URL;
|
|
2367
|
+
const key = process.env.MCP_MEMORY_KEY;
|
|
2368
|
+
if (!url || !key) return;
|
|
2369
|
+
try {
|
|
2370
|
+
const res = await fetch(url, {
|
|
2371
|
+
method: "POST",
|
|
2372
|
+
headers: { "content-type": "application/json" },
|
|
2373
|
+
body: JSON.stringify({ data: { apiKey: key, title: args.title, content: args.content, source: args.source } })
|
|
2374
|
+
});
|
|
2375
|
+
const body = await res.json().catch(() => null);
|
|
2376
|
+
if (!res.ok || body?.result?.error) {
|
|
2377
|
+
console.warn("[memory-library-sink] ingest not accepted:", res.status, body?.result?.message ?? "");
|
|
2378
|
+
}
|
|
2379
|
+
} catch (err) {
|
|
2380
|
+
console.warn("[memory-library-sink] ingest failed:", err?.message);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
// src/output/OutputSerializer.ts
|
|
2144
2385
|
var OutputSerializer = class {
|
|
2145
2386
|
async writeJSON(result, outputDir) {
|
|
2146
2387
|
await import_node_fs.promises.mkdir(outputDir, { recursive: true });
|
|
@@ -2148,6 +2389,7 @@ var OutputSerializer = class {
|
|
|
2148
2389
|
const filename = `${slug}-${Date.now()}.json`;
|
|
2149
2390
|
const fullPath = import_node_path.default.join(outputDir, filename);
|
|
2150
2391
|
await import_node_fs.promises.writeFile(fullPath, JSON.stringify(result, null, 2), "utf8");
|
|
2392
|
+
await postToMemoryLibrary({ title: `${result.seed} scrape`, content: JSON.stringify(result), source: `mcp-scraper:${fullPath}` });
|
|
2151
2393
|
return fullPath;
|
|
2152
2394
|
}
|
|
2153
2395
|
async writeCSV(rows, outputDir) {
|
|
@@ -2345,11 +2587,13 @@ function proxyIdSuffix2(proxyId) {
|
|
|
2345
2587
|
return proxyId ? proxyId.slice(-6) : null;
|
|
2346
2588
|
}
|
|
2347
2589
|
function resolution(source, proxyMode, proxyId, target, error, disposable = false) {
|
|
2590
|
+
const proxyType = source === "disabled" ? "none" : source === "configured_fallback" ? "configured" : source === "isp_created" ? "isp" : source === "location_created" || source === "location_reused" ? "residential" : "unknown";
|
|
2348
2591
|
return {
|
|
2349
2592
|
kernelProxyId: proxyId,
|
|
2350
2593
|
...disposable && proxyId ? { disposableProxyId: proxyId } : {},
|
|
2351
2594
|
resolution: {
|
|
2352
2595
|
source,
|
|
2596
|
+
proxyType,
|
|
2353
2597
|
proxyMode,
|
|
2354
2598
|
proxyIdPresent: Boolean(proxyId),
|
|
2355
2599
|
proxyIdSuffix: proxyIdSuffix2(proxyId),
|
|
@@ -2544,6 +2788,22 @@ async function createFreshLocationProxy(kernel, options, target) {
|
|
|
2544
2788
|
createErrors.push(`${candidate.proxyName}: ${errorText2(err)}`);
|
|
2545
2789
|
}
|
|
2546
2790
|
}
|
|
2791
|
+
if (process.env.KERNEL_PROXY_TYPE !== "residential") {
|
|
2792
|
+
const ispTarget = stateTarget(target);
|
|
2793
|
+
try {
|
|
2794
|
+
const created = await kernel.proxies.create({
|
|
2795
|
+
type: "isp",
|
|
2796
|
+
name: freshProxyName(`mcp-serp-isp-${ispTarget.country.toLowerCase()}-${(ispTarget.state ?? "").toLowerCase()}`, options.attemptIndex),
|
|
2797
|
+
config: { country: ispTarget.country, ...ispTarget.state ? { state: ispTarget.state } : {} }
|
|
2798
|
+
});
|
|
2799
|
+
if (created.id) {
|
|
2800
|
+
return resolution("isp_created", options.proxyMode, created.id, ispTarget, null, true);
|
|
2801
|
+
}
|
|
2802
|
+
createErrors.push("isp: Kernel did not return a proxy id");
|
|
2803
|
+
} catch (err) {
|
|
2804
|
+
createErrors.push(`isp: ${errorText2(err)}`);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2547
2807
|
return resolution("configured_fallback", options.proxyMode, options.configuredKernelProxyId, target, createErrors.join(" | "));
|
|
2548
2808
|
}
|
|
2549
2809
|
async function deleteKernelProxyId(kernelApiKey, proxyId) {
|
|
@@ -2827,9 +3087,11 @@ async function harvest(rawOptions) {
|
|
|
2827
3087
|
};
|
|
2828
3088
|
const requestedDebug = typeof raw.debug === "boolean" ? raw.debug : false;
|
|
2829
3089
|
const needsLocationEvidence = proxyMode === "location" && Boolean(proxyOpts.location);
|
|
2830
|
-
const maxAttempts = maxAttemptsForProxyMode(proxyMode);
|
|
3090
|
+
const maxAttempts = maxAttemptsForProxyMode(proxyMode) + 1;
|
|
2831
3091
|
const serializer = new OutputSerializer();
|
|
2832
3092
|
let lastError = null;
|
|
3093
|
+
let effectiveProxyMode = proxyMode;
|
|
3094
|
+
let proxyFailures = 0;
|
|
2833
3095
|
for (let i = 0; i < maxAttempts; i++) {
|
|
2834
3096
|
const attemptNumber = i + 1;
|
|
2835
3097
|
const startedAtMs = Date.now();
|
|
@@ -2837,18 +3099,19 @@ async function harvest(rawOptions) {
|
|
|
2837
3099
|
if (signal?.aborted) throw abortReason(signal);
|
|
2838
3100
|
const resolution2 = await resolveKernelProxyId({
|
|
2839
3101
|
...proxyOpts,
|
|
3102
|
+
proxyMode: effectiveProxyMode,
|
|
2840
3103
|
attemptIndex: i,
|
|
2841
|
-
fresh:
|
|
3104
|
+
fresh: effectiveProxyMode === "location"
|
|
2842
3105
|
});
|
|
2843
3106
|
const mergedAttempt = {
|
|
2844
3107
|
...raw,
|
|
2845
3108
|
kernelApiKey,
|
|
2846
3109
|
kernelProxyId: resolution2.kernelProxyId,
|
|
2847
3110
|
kernelProxyResolution: resolution2.resolution,
|
|
2848
|
-
proxyMode,
|
|
3111
|
+
proxyMode: effectiveProxyMode,
|
|
2849
3112
|
debug: requestedDebug || needsLocationEvidence
|
|
2850
3113
|
};
|
|
2851
|
-
if (
|
|
3114
|
+
if (effectiveProxyMode === "none") mergedAttempt.kernelProxyId = void 0;
|
|
2852
3115
|
const attemptOptions = HarvestOptionsSchema.parse(mergedAttempt);
|
|
2853
3116
|
await emitAttemptEvent(onAttemptEvent, {
|
|
2854
3117
|
type: "started",
|
|
@@ -2867,10 +3130,17 @@ async function harvest(rawOptions) {
|
|
|
2867
3130
|
location: attemptOptions.location ?? null,
|
|
2868
3131
|
max_questions: attemptOptions.maxQuestions
|
|
2869
3132
|
}));
|
|
2870
|
-
const
|
|
3133
|
+
const isHeadfulEscalation = i === maxAttempts - 1;
|
|
3134
|
+
const needHeadful = isHeadfulEscalation || !attemptOptions.serpOnly;
|
|
3135
|
+
const baseCtx = currentCostContext();
|
|
3136
|
+
const attempt = needHeadful && baseCtx ? await runWithCostContext({ ...baseCtx, forceHeadful: true }, () => extractOnce(attemptOptions, signal)) : await extractOnce(attemptOptions, signal);
|
|
2871
3137
|
if (attempt.error) {
|
|
2872
3138
|
const err = attempt.error;
|
|
2873
3139
|
const outcome = classifyAttemptError(err);
|
|
3140
|
+
if ((outcome === "captcha" || outcome === "proxy_tunnel_failed") && effectiveProxyMode === "location") {
|
|
3141
|
+
proxyFailures++;
|
|
3142
|
+
if (proxyFailures >= 2) effectiveProxyMode = "none";
|
|
3143
|
+
}
|
|
2874
3144
|
const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome));
|
|
2875
3145
|
if (outcome === "captcha") {
|
|
2876
3146
|
console.warn(JSON.stringify({
|
|
@@ -2972,6 +3242,10 @@ async function harvest(rawOptions) {
|
|
|
2972
3242
|
return finalResult;
|
|
2973
3243
|
} catch (err) {
|
|
2974
3244
|
const outcome = classifyAttemptError(err);
|
|
3245
|
+
if ((outcome === "captcha" || outcome === "proxy_tunnel_failed") && effectiveProxyMode === "location") {
|
|
3246
|
+
proxyFailures++;
|
|
3247
|
+
if (proxyFailures >= 2) effectiveProxyMode = "none";
|
|
3248
|
+
}
|
|
2975
3249
|
const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome));
|
|
2976
3250
|
if (outcome === "captcha") {
|
|
2977
3251
|
console.warn(JSON.stringify({
|